天天看點

Linux裝置驅動的分層設計思想

在面向對象的程式設計中,可以為某一類相似的事物定義一個基類,而具體的事物可以繼承這個基類中的函數。如果對于繼承的這個事物而言,其某函數的實 現與基類一緻,那它就可以直接繼承基類的函數;相反,它可以重載之。這種面向對象的設計思想極大地提高了代碼的可重用能力,是對現實世界事物間關系的一種 良好呈現。

Linux核心完全由C語言和彙編語言寫成,但是卻頻繁用到了面向對象的設計思想。在裝置驅動方面,往往為同類的裝置設計了一個架構,而架構中的核 心層則實作了該裝置通用的一些功能。同樣的,如果具體的裝置不想使用核心層的函數,它可以重載之。舉個例子:

return_type core_funca(xxx_device * bottom_dev, param1_type param1, param1_type param2)

{

if (bottom_dev->funca)

return bottom_dev->funca(param1, param2);

/* 核心層通用的funca代碼 */

...

}

上述core_funca的實作中,會檢查底層裝置是否重載了funca(),如果重載了,就調用底層的代碼,否則,直接使用通用層的。這樣做的好 處是,核心層的代碼可以處理絕大多數該類裝置的funca()對應的功能,隻有少數特殊裝置需要重新實作funca()。

再看一個例子:

/*通用的步驟代碼A */

bottom_dev->funca_ops1();

/*通用的步驟代碼B */

bottom_dev->funca_ops2();

/*通用的步驟代碼C */

bottom_dev->funca_ops3();

上述代碼假定為了實作funca(),對于同類裝置而言,操作流程一緻,都要經過“通用代碼A、底層ops1、通用代碼B、底層ops2、通用代碼 C、底層ops3”這幾步,分層設計明顯帶來的好處是,對于通用代碼A、B、C,具體的底層驅動不需要再實作,而僅僅隻關心其底層的操作ops1、 ops2、ops3。

圖1明确反映了裝置驅動的核心層與具體裝置驅動的關系,實際上,這種分層可能隻有2層(圖1的a),也可能是多層的(圖1的b)。

<a href="http://www.linuxdriver.cn/blog_images/Linux_532/clip_image002.jpg"></a>

圖1 Linux裝置驅動的分層

這樣的分層化設計在Linux的input、RTC、MTD、I2 C、SPI、TTY、USB等諸多裝置驅動類型中屢見不鮮。下面的2節以input和RTC為例先行進行一番說明,當然,後續的章節會對幾個大的裝置類型 對應驅動的層次進行更詳細的分析。

輸入裝置(如按鍵、鍵盤、觸摸屏、滑鼠等)是典型的字元裝置,其一般的工作機理是底層在按鍵、觸摸等動作發送時産生一個中斷(或驅動通過timer 定時查詢),然後CPU通過SPI、I2 C或外部存儲器總線讀取鍵值、坐标等資料,放入1個緩沖區,字元裝置驅動管理該緩沖區,而驅動的read()接口讓使用者可以讀取鍵值、坐标等資料。

顯然,在這些工作中,隻是中斷、讀值是裝置相關的,而輸入事件的緩沖區管理以及字元裝置驅動的file_operations接口則對輸入裝置是通 用的。基于此,核心設計了輸入子系統,由核心層處理公共的工作。Linux核心輸入子系統的架構如圖2所示。

<a href="http://www.linuxdriver.cn/blog_images/Linux_532/clip_image004.jpg"></a>

圖2 Linux輸入裝置驅動的分層

輸入核心提供了底層輸入裝置驅動程式所需的API,如配置設定/釋放一個輸入裝置:

struct input_dev *input_allocate_device(void);

void input_free_device(struct input_dev *dev);

input_allocate_device()傳回的是1個input_dev的結構體,此結構體用于表征1個輸入裝置。

注冊/登出輸入裝置用的如下接口:

int __must_check input_register_device(struct input_dev *);

void input_unregister_device(struct input_dev *);

報告輸入事件用的如下接口:

/* 報告指定type、code的輸入事件 */

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);

/* 報告鍵值 */

void input_report_key(struct input_dev *dev, unsigned int code, int value);

/* 報告相對坐标 */

void input_report_rel(struct input_dev *dev, unsigned int code, int value);

/* 報告絕對坐标 */

void input_report_abs(struct input_dev *dev, unsigned int code, int value);

/* 報告同步事件 */

void input_sync(struct input_dev *dev);

而所有的輸入事件,核心都用統一的資料結構來描述,這個資料結構是input_event,形如代碼清單7。

代碼清單7 input_event結構體

1 struct input_event {

2 struct timeval time;

3 __u16 type;

4 __u16 code;

5 __s32 value;

6 };

drivers/input/keyboard/gpio_keys.c基于input架構實作了一個通用的GPIO按鍵驅動。該驅動基于 platform_driver架構,名為“gpio-keys”。它将硬體相關的資訊(如使用的GPIO号,電平等)屏蔽在闆檔案 platform_device的platform_data中,是以該驅動可應用于各個處理器,具有良好的跨平台性。代碼清單8列出了該驅動的 probe()函數。

代碼清單8 GPIO按鍵驅動的probe()函數

1 static int __devinit gpio_keys_probe(struct platform_device *pdev)

2 {

3 struct gpio_keys_platform_data *pdata = pdev-&gt;dev.platform_data;

4 struct gpio_keys_drvdata *ddata;

5 struct input_dev *input;

6 int i, error;

7 int wakeup = 0;

8

9 ddata = kzalloc(sizeof(struct gpio_keys_drvdata) +

10 pdata-&gt;nbuttons * sizeof(struct gpio_button_data),

11 GFP_KERNEL);

12 input = input_allocate_device();

13 if (!ddata || !input) {

14 error = -ENOMEM;

15 goto fail1;

16 }

17

18 platform_set_drvdata(pdev, ddata);

19

20 input-&gt;name = pdev-&gt;name;

21 input-&gt;phys = "gpio-keys/input0";

22 input-&gt;dev.parent = &amp;pdev-&gt;dev;

23

24 input-&gt;id.bustype = BUS_HOST;

25 input-&gt;id.vendor = 0x0001;

26 input-&gt;id.product = 0x0001;

27 input-&gt;id.version = 0x0100;

28

29 ddata-&gt;input = input;

30

31 for (i = 0; i &lt; pdata-&gt;nbuttons; i++) {

32 struct gpio_keys_button *button = &amp;pdata-&gt;buttons[i];

33 struct gpio_button_data *bdata = &amp;ddata-&gt;data[i];

34 int irq;

35 unsigned int type = button-&gt;type ?: EV_KEY;

36

37 bdata-&gt;input = input;

38 bdata-&gt;button = button;

39 setup_timer(&amp;bdata-&gt;timer,

40 gpio_check_button, (unsigned long)bdata);

41

42 ...

43 error = request_irq(irq, gpio_keys_isr,

44 IRQF_SAMPLE_RANDOM | IRQF_TRIGGER_RISING |

45 IRQF_TRIGGER_FALLING,

46 button-&gt;desc ? button-&gt;desc : "gpio_keys",

47 bdata);

48 if (error) {

49 ...

50 }

51

52 if (button-&gt;wakeup)

53 wakeup = 1;

54

55 input_set_capability(input, type, button-&gt;code);

56 }

57

58 error = input_register_device(input);

59 if (error) {

60 pr_err("gpio-keys: Unable to register input device, "

61 "error: %d\n", error);

62 goto fail2;

63 }

64

65 device_init_wakeup(&amp;pdev-&gt;dev, wakeup);

66

67 return 0;

68 ...

69 }

上述代碼的第12行配置設定了1個輸入裝置,第20~27行初始化了該input_dev的一些屬性,第58行注冊了這個輸入裝置。第31~56行則申 請了此GPIO按鍵裝置需要的中斷号,并初始化了timer。第55行設定此輸入裝置可告知的事情。

在注冊輸入裝置後,底層輸入裝置驅動的核心工作隻剩下在按鍵、觸摸等人為動作發生的時候,報告事件。代碼清單9列出了GPIO按鍵中斷發生時的事件 報告代碼。

代碼清單9 GPIO按鍵中斷發生時的事件報告

1 static void gpio_keys_report_event(struct gpio_button_data *bdata)

3 struct gpio_keys_button *button = bdata-&gt;button;

4 struct input_dev *input = bdata-&gt;input;

5 unsigned int type = button-&gt;type ?: EV_KEY;

6 int state = (gpio_get_value(button-&gt;gpio) ? 1 : 0) ^ button-&gt;active_low;

7

8 input_event(input, type, button-&gt;code, !!state);

9 input_sync(input);

10 }

11

12 static irqreturn_t gpio_keys_isr(int irq, void *dev_id)

13 {

14 struct gpio_button_data *bdata = dev_id;

15 struct gpio_keys_button *button = bdata-&gt;button;

16

17 BUG_ON(irq != gpio_to_irq(button-&gt;gpio));

18

19 if (button-&gt;debounce_interval)

20 mod_timer(&amp;bdata-&gt;timer,

21 jiffies + msecs_to_jiffies(button-&gt;debounce_interval));

22 else

23 gpio_keys_report_event(bdata);

24

25 return IRQ_HANDLED;

26 }

第8行是報告鍵值,而第9行是1個同步事件,暗示前面報告的消息屬于1個消息組。譬如使用者在報告完X坐标後,又報告Y坐标,之後報告1個同步事件, 應用程式即可知道前面報告的X、Y這2個事件屬于1組,它會将2者聯合起來形成1個(X,Y)的坐标。

代碼清單8第2行擷取platform_data,而platform_data實際上是定義GPIO按鍵硬體資訊的數組,第31行的for循環工 具這些資訊申請GPIO并初始化中斷,對于LDD6140電路闆而言,這些資訊如代碼清單10。

代碼清單10 LDD6410開發闆GPIO按鍵的platform_data

1 static struct gpio_keys_button ldd6410_buttons[] = {

3 .gpio = S3C64XX_GPN(0),

4 .code = KEY_DOWN,

5 .desc = "Down",

6 .active_low = 1,

7 },

8 {

9 .gpio = S3C64XX_GPN(1),

10 .code = KEY_ENTER,

11 .desc = "Enter",

12 .active_low = 1,

13 .wakeup = 1,

14 },

15 {

16 .gpio = S3C64XX_GPN(2),

17 .code = KEY_HOME,

18 .desc = "Home",

19 .active_low = 1,

20 },

21 {

22 .gpio = S3C64XX_GPN(3),

23 .code = KEY_POWER,

24 .desc = "Power",

25 .active_low = 1,

26 .wakeup = 1,

27 },

28 {

29 .gpio = S3C64XX_GPN(4),

30 .code = KEY_TAB,

31 .desc = "Tab",

32 .active_low = 1,

33 },

34 {

35 .gpio = S3C64XX_GPN(5),

36 .code = KEY_MENU,

37 .desc = "Menu",

38 .active_low = 1,

39 },

40 };

42 static struct gpio_keys_platform_data ldd6410_button_data = {

43 .buttons = ldd6410_buttons,

44 .nbuttons = ARRAY_SIZE(ldd6410_buttons),

45 };

46

47 static struct platform_device ldd6410_device_button = {

48 .name = "gpio-keys",

49 .id = -1,

50 .dev = {

51 .platform_data = &amp;ldd6410_button_data,

52 }

53 };

RTC(實時鐘)借助電池供電,在系統掉電的情況下依然可以行走。它通常還具有産生周期中斷以及産生鬧鐘(alarm)中斷的能力,是一種典型的字 符裝置。作為一種字元裝置驅動,RTC需要有file_operations中接口函數的實作,如open()、release()、read()、 poll()、ioctl()等,而典型的IOCTL包括RTC_SET_TIME、RTC_ALM_READ、RTC_ALM_SET、 RTC_IRQP_SET、RTC_IRQP_READ等,這些對于所有的RTC是通用的,隻有底層的具體實作是裝置相關的。

是以,drivers/rtc/rtc-dev.c實作了RTC驅動通用的字元裝置驅動層,它實作了file_opearations的成員函數以 及一些關于RTC的通用的控制代碼,并向底層導出rtc_device_register()、rtc_device_unregister()用于注冊 和登出RTC;導出rtc_class_ops結構體用于描述底層的RTC硬體操作。這一RTC通用層實作的結果是,底層的RTC驅動不再需要關心RTC 作為字元裝置驅動的具體實作,也無需關心一些通用的RTC控制邏輯,圖3表明了這種關系。

<a href="http://www.linuxdriver.cn/blog_images/Linux_532/clip_image006.jpg"></a>

圖3 Linux RTC裝置驅動的分層

drivers/rtc/rtc-s3c.c實作了S3C6410的RTC驅動,其注冊RTC以及綁定的rtc_class_ops的代碼如代碼清 單11。

代碼清單11 S3C6410 RTC驅動的rtc_class_ops執行個體與RTC注冊

1 static const struct rtc_class_ops s3c_rtcops = {

2 .open = s3c_rtc_open,

3 .release = s3c_rtc_release,

4 .ioctl = s3c_rtc_ioctl,

5 .read_time = s3c_rtc_gettime,

6 .set_time = s3c_rtc_settime,

7 .read_alarm = s3c_rtc_getalarm,

8 .set_alarm = s3c_rtc_setalarm,

9 .irq_set_freq = s3c_rtc_setfreq,

10 .irq_set_state = s3c_rtc_setpie,

11 .proc = s3c_rtc_proc,

12 };

13

14 static int s3c_rtc_probe(struct platform_device *pdev)

16 ...

17 rtc = rtc_device_register("s3c", &amp;pdev-&gt;dev, &amp;s3c_rtcops,

18 THIS_MODULE);

19 ...

20 }

 本文轉自 21cnbao 51CTO部落格,原文連結:http://blog.51cto.com/21cnbao/336263,如需轉載請自行聯系原作者

繼續閱讀