1. 程式人生 > >linux驅動開發6驅動框架之led

linux驅動開發6驅動框架之led

1.何謂驅動框架

1.1驅動是誰寫的

1)驅動開發工程師

2)核心維護者

1.2驅動程式設計協作要求

1)介面標準化

2)儘量降低驅動開發者難度

1.3到底什麼是驅動框架

1)核心中驅動部分維護者針對每個種類的驅動設計一套成熟的、標準的、典型的驅動實現,然後把不同廠家的同類硬體驅動中相同的部分抽出來自己實現好,再把不同部分留出介面給具體的驅動開發工程師來實現,這就叫驅動框架。

2)核心維護者在核心中設計了一些統一管控系統資源的體系,這些體系讓核心能夠對資源在各個驅動之間的使用統一協調和分配,保證整個核心的穩定健康執行。譬如系統中所有的GPIO就屬於系統資源,每個驅動模組如果要使用某個GPIO

就要先呼叫特殊的介面先申請,申請到後使用,使用完後要釋放。又譬如中斷號也是一種資源,驅動在使用前也必須去申請。這也是驅動框架的組成部分。

3)一些特定的介面函式、一些特定的資料結構,這些是驅動框架的直接表現。

2.核心驅動框架中LED的基本情況

2.1相關檔案

1)drivers/leds目錄,這個目錄就是驅動框架規定的LED這種硬體的驅動應該待的地方。

2)led-class.c和led-core.c,這兩個檔案加起來屬於LED驅動框架的第1部分,這兩個檔案是核心開發者提供的,他們描述的是核心中所有廠家的不同LED硬體的相同部分的邏輯。

3)leds-xxxx.c,這個檔案是LED

驅動框架的第2部分,是由不同廠商的驅動工程師編寫新增的,廠商驅動工程師結合自己公司的硬體的不同情況來對LED進行操作,使用第一部分提供的介面來和驅動框架進行互動,最終實現驅動的功能。

2.2九鼎移植的核心中led驅動

1)九鼎實際未使用核心推薦的led驅動框架

2)drivers/char/led/x210-led.c

2.3案例分析驅動框架的使用

1)以leds-s3c24xx.c為例。leds-s3c24xx.c中通過呼叫led_classdev_register來完成我們的LED驅動的註冊,而led_classdev_register是在drivers/leds/led-class.c中定義的。所以其實SoC廠商的驅動工程師是呼叫核心開發者在驅動框架中提供的介面來實現自己的驅動的。

2)驅動框架的關鍵點就是:分清楚核心開發者提供了什麼,驅動開發者自己要提供什麼

2.4典型的驅動開發行業現狀

1)核心開發者對驅動框架進行開發和維護、升級,對應led-class.c和led-core.c

2)SoC廠商的驅動工程師對裝置驅動原始碼進行編寫、除錯,提供參考版本,對應leds-s3c24xx.c

3)做產品的廠商的驅動工程師以SoC廠商提供的驅動原始碼為基礎,來做移植和除錯

3.初步分析led驅動框架原始碼

3.1涉及到的檔案

1)led-core.c

2)led-class.c

3.2.subsys_initcall

1)經過基本分析,發現LED驅動框架中核心開發者實現的部分主要是led-class.c

2)我們發現led-class.c就是一個核心模組,對led-class.c分析應該從下往上,遵從對模組的基本分析方法。

3)為什麼LED驅動框架中核心開發者實現的部分要實現成一個模組?因為核心開發者希望這個驅動框架是可以被裝載/解除安裝的。這樣當我們核心使用者不需要這個驅動框架時可以完全去掉,需要時可以隨時加上。

4)subsys_initcall是一個巨集,定義在linux/init.h中。經過對這個巨集進行展開,發現這個巨集的功能是:將其宣告的函式放到一個特定的段:.initcall4.init。

subsys_initcall

         __define_initcall("4",fn,4)

5)分析module_init巨集,可以看出它將函式放到了.initcall6.init段中。

module_init

         __initcall

                   device_initcall

                            __define_initcall("6",fn,6)

6)核心在啟動過程中需要順序的做很多事,核心如何實現按照先後順序去做很多初始化操作。核心的解決方案就是給核心啟動時要呼叫的所有函式歸類,然後每個類按照一定的次序去呼叫執行。這些分類名就叫.initcalln.init。n的值從1到8。核心開發者在編寫核心程式碼時只要將函式設定合適的級別,這些函式就會被連結的時候放入特定的段,核心啟動時再按照段順序去依次執行各個段即可。

7)經過分析,可以看出,subsys_initcall和module_init的作用是一樣的,只不過前者所宣告的函式要比後者在核心啟動時的執行順序更早

3.3led_class_attrs

1)什麼是attribute,對應將來/sys/class/leds/目錄裡的內容,一般是檔案和資料夾。這些檔案其實就是sysfs開放給應用層的一些操作介面(非常類似於/dev/目錄下的那些裝置檔案)

2)attribute有什麼用,作用就是讓應用程式可以通過/sys/class/leds/目錄下面的屬性檔案來操作驅動進而操作硬體裝置。

3)attribute其實是另一條驅動實現的路線。有區別於之前講的file_operations那條線。

3.4.led_classdev_register

led_classdev_register

         device_create

1)分析可知,led_classdev_register這個函式其實就是去建立一個屬於leds這個類的一個裝置。其實就是去註冊一個裝置。所以這個函式其實就是led驅動框架中核心開發者提供給SoC廠家驅動開發者的一個註冊驅動的介面。

2)當我們使用led驅動框架去編寫驅動的時候,這個led_classdev_register函式的作用類似於我們之前使用file_operations方式去註冊字元裝置驅動時的register_chrdev函式。

4.在核心中新增或去除某個驅動

4.1去除九鼎移植的LED驅動

1)九鼎移植的驅動在應用層的介面在/sys/devices/platform/x210-led/目錄下,有led1、led2、led3、led4四個裝置檔案,各自管理一個led。

2)要去掉九鼎自己移植的led驅動,要在make menucofig中去掉選擇項,然後重新make得到zImage,然後重啟時啟動這個新的zImage即可。

3)新的核心啟動後,如果/sys/devices/platform/目錄下已經沒有了x210-led這個目錄,就說明我們去掉這個驅動成功了。

4)為什麼make menuconfig就能去掉這個驅動?

4.2新增led驅動框架支援

當前核心中是沒有LED驅動框架的,我們要去新增它。

4.3.sysfs中的內容分析

4.4.後續展望:完成leds-x210.c

5.基於驅動框架寫led驅動

5.1分析

1)參考哪裡?  drivers/leds/leds-s3c24xx.c

2)關鍵點:led_classdev_register

5.2動手寫led驅動模組

5.3程式碼實踐

#include <linux/module.h>       // module_init  module_exit

#include <linux/init.h>         // __init   __exit

#include <linux/fs.h>

#include <asm/uaccess.h>

#include <mach/regs-gpio.h>

#include <mach/gpio-bank.h>     // arch/arm/mach-s5pv210/include/mach/gpio-bank.h

#include <linux/string.h>

#include <linux/io.h>

#include <linux/ioport.h>

#include <linux/leds.h>

static struct led_classdev  mydev;

static void s5pv210_led_set(struct led_classdev *led_cdev, enum led_brightness brightness)

{

    printk(KERN_INFO "s5pv210_led_set.\n");

}

static int __init s5pv210_led_init(void)

{

    //驅動安裝時呼叫該函式

    //使用函式的目的就是去使用led驅動框架提供的設備註冊函式來註冊一個裝置

    int ret = -1;

    mydev.name = "led1";

    mydev.brightness = 0;

    mydev.brightness_set = s5pv210_led_set;

    ret = led_classdev_register(NULL, &mydev);

    if (ret < 0) {

        printk(KERN_ERR "led_classdev_register failed\n");

        return ret;

    }

    return 0;

}

static void __exit s5pv210_led_exit(void)

{

    led_classdev_unregister(&mydev);

}

module_init(s5pv210_led_init);

module_exit(s5pv210_led_exit);

// MODULE_xxx這種巨集作用是用來新增模組描述資訊

MODULE_AUTHOR("tom");

MODULE_DESCRIPTION("S5PV210 LED driver");

MODULE_LICENSE("GPL");

MODULE_ALIAS("s5pv210_led");

1)除錯

2)分析

通過分析看出:

第1:我們寫的驅動確實工作了,被載入了,/sys/class/leds/目錄下確實多出來了一個表示裝置的資料夾。資料夾裡面有相應的操控led硬體的2個屬性brightness和max_brightness

第2:led-class.c中brightness方法有一個show方法和store方法,這兩個方法對應使用者在/sys/class/leds/myled/brightness目錄下直接去讀寫這個檔案時實際執行的程式碼。

當我們show brightness時,實際就會執行led_brightness_show函式

當我們echo 1 > brightness時,實際就會執行led_brightness_store函式

3)show方法實際要做的就是讀取LED硬體資訊,然後把硬體資訊返回給我們即可。所以show方法和store方法必要要會去操控硬體。但是led-class.c檔案又屬於驅動框架中的檔案,它本身無法直接讀取具體硬體,因此在show和store方法中使用函式指標的方式呼叫了struct led_classdev結構體中的相應的讀取/寫入硬體資訊的方法。

4)struct led_classdev結構體中的實際用來讀寫硬體資訊的函式,就是我們自己寫的驅動檔案leds-s5pv210.c中要提供的。

5.4新增硬體操作

#include <linux/module.h>       // module_init  module_exit

#include <linux/init.h>         // __init   __exit

#include <linux/fs.h>

#include <asm/uaccess.h>

#include <mach/regs-gpio.h>

#include <mach/gpio-bank.h>     // arch/arm/mach-s5pv210/include/mach/gpio-bank.h

#include <linux/string.h>

#include <linux/io.h>

#include <linux/ioport.h>

#include <linux/leds.h>

#define GPJ0CON     S5PV210_GPJ0CON

#define GPJ0DAT     S5PV210_GPJ0DAT

static struct led_classdev  mydev;

static void s5pv210_led_set(struct led_classdev *led_cdev, enum led_brightness value)

{

    printk(KERN_INFO "s5pv210_led_set.\n");

    //這裡根據使用者設定的值來操作硬體

    //使用者設定的值就是value

    if (value == LED_OFF)

    {

        //使用者給了個0,希望LED滅

        writel(0x11111111, GPJ0CON);

        writel(((1<<3) | (1<<4) | (1<<5)), GPJ0DAT);

    }

    else

    {

        //使用者給的非零,希望LED亮

        writel(0x11111111, GPJ0CON);

        writel(((0<<3) | (0<<4) | (0<<5)), GPJ0DAT);

    }

}

static int __init s5pv210_led_init(void)

{

    //驅動安裝時呼叫該函式

    //使用函式的目的就是去使用led驅動框架提供的設備註冊函式來註冊一個裝置

    int ret = -1;

    mydev.name = "led1";

    mydev.brightness = 0;

    mydev.brightness_set = s5pv210_led_set;

    ret = led_classdev_register(NULL, &mydev);

    if (ret < 0) {

        printk(KERN_ERR "led_classdev_register failed\n");

        return ret;

    }

    return 0;

}

static void __exit s5pv210_led_exit(void)

{

    led_classdev_unregister(&mydev);

}

module_init(s5pv210_led_init);

module_exit(s5pv210_led_exit);

// MODULE_xxx這種巨集作用是用來新增模組描述資訊

MODULE_AUTHOR("tom");

MODULE_DESCRIPTION("S5PV210 LED driver");

MODULE_LICENSE("GPL");

MODULE_ALIAS("s5pv210_led");

5.5在驅動中將4個LED分開

1)好處。驅動層實現對各個LED裝置的獨立訪問,並嚮應用層展示出4個操作介面led1、led2、led3、led4,這樣應用層可以完全按照自己的需要對LED進行控制。

驅動的設計理念:不要對最終需求功能進行假定,而應該只是直接的對硬體的操作。有一個概念就是:機制和策略的問題。在硬體操作上驅動只應該提供機制(單純提供應用機制)而不是策略(應用層根據需求來操作)。策略由應用程式來做。

2)如何實現

5.6和leds-s3c24xx.c的不同

5.7gpiolib引入

1)一個事實:很多硬體都要用到GPIO、GPIO會複用

2)如果同一個GPIO被2個驅動同時控制了,就會出現bug

3)核心提供gpiolib來統一管理系統中所有GPIO

4)gpiolib本身屬於驅動框架的一部分