1.1 設備驅動核心層和例化
在面向對象的程序設計中,能夠爲某一類類似的事物定義一個基類,而具體的事物能夠繼承這個基類中的函數。若是對於繼承的這個事物而言,其某函數的實 現與基類一致,那它就能夠直接繼承基類的函數;相反,它能夠重載之。這種面向對象的設計思想極大地提升了代碼的可重用能力,是對現實世界事物間關係的一種 良好呈現。
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()。
再看一個例子:
return_type core_funca(xxx_device * bottom_dev, param1_type param1, param1_type param2)
{
/*通用的步驟代碼A */
...
bottom_dev->funca_ops1();
/*通用的步驟代碼B */
...
bottom_dev->funca_ops2();
/*通用的步驟代碼C */
...
bottom_dev->funca_ops3();
}
上述代碼假定爲了實現funca(),對於同類設備而言,操做流程一致,都要通過「通用代碼A、底層ops一、通用代碼B、底層ops二、通用代碼 C、底層ops3」這幾步,分層設計明顯帶來的好處是,對於通用代碼A、B、C,具體的底層驅動不須要再實現,而僅僅只關心其底層的操做ops一、 ops二、ops3。
圖1明確反映了設備驅動的核心層與具體設備驅動的關係,實際上,這種分層可能只有2層(圖1的a),也多是多層的(圖1的b)。
圖1 Linux設備驅動的分層
這樣的分層化設計在Linux的input、RTC、MTD、I
2 C、SPI、TTY、USB等諸多設備驅動類型中家常便飯。下面的2節以input和RTC爲例先行進行一番說明,固然,後續的章節會對幾個大的設備類型 對應驅動的層次進行更詳細的分析。
1.2 輸入設備驅動
輸入設備(如按鍵、鍵盤、觸摸屏、鼠標等)是典型的字符設備,其通常的工做機理是底層在按鍵、觸摸等動做發送時產生一箇中斷(或驅動經過timer 定時查詢),而後CPU經過SPI、I
2 C或外部存儲器總線讀取鍵值、座標等數據,放入1個緩衝區,字符設備驅動管理該緩衝區,而驅動的read()接口讓用戶能夠讀取鍵值、座標等數據。
顯然,在這些工做中,只是中斷、讀值是設備相關的,而輸入事件的緩衝區管理以及字符設備驅動的file_operations接口則對輸入設備是通 用的。基於此,內核設計了輸入子系統,由核心層處理公共的工做。Linux內核輸入子系統的框架如圖2所示。
圖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->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->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->name = pdev->name;
21 input->phys = "gpio-keys/input0";
22 input->dev.parent = &pdev->dev;
23
24 input->id.bustype = BUS_HOST;
25 input->id.vendor = 0x0001;
26 input->id.product = 0x0001;
27 input->id.version = 0x0100;
28
29 ddata->input = input;
30
31 for (i = 0; i < pdata->nbuttons; i++) {
32 struct gpio_keys_button *button = &pdata->buttons[i];
33 struct gpio_button_data *bdata = &ddata->data[i];
34 int irq;
35 unsigned int type = button->type ?: EV_KEY;
36
37 bdata->input = input;
38 bdata->button = button;
39 setup_timer(&bdata->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->desc ? button->desc : "gpio_keys",
47 bdata);
48 if (error) {
49 ...
50 }
51
52 if (button->wakeup)
53 wakeup = 1;
54
55 input_set_capability(input, type, button->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(&pdev->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)
2 {
3 struct gpio_keys_button *button = bdata->button;
4 struct input_dev *input = bdata->input;
5 unsigned int type = button->type ?: EV_KEY;
6 int state = (gpio_get_value(button->gpio) ? 1 : 0) ^ button->active_low;
7
8 input_event(input, type, button->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->button;
16
17 BUG_ON(irq != gpio_to_irq(button->gpio));
18
19 if (button->debounce_interval)
20 mod_timer(&bdata->timer,
21 jiffies + msecs_to_jiffies(button->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[] = {
2 {
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 };
41
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 = &ldd6410_button_data,
52 }
53 };
1.3 RTC設備驅動
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代表了這種關係。
圖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)
15 {
16 ...
17 rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,
18 THIS_MODULE);
19 ...
20 }