Linux設備驅動的分層設計思想

火星人 @ 2014-03-09 , reply:0


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、底層ops1、通用代碼B、底層ops2、通用代碼 C、底層ops3」這幾步,分層設計明顯帶來的好處是,對於通用代碼A、B、C,具體的底層驅動不需要再實現,而僅僅只關心其底層的操作ops1、 ops2、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、I2 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 }




[火星人 via ] Linux設備驅動的分層設計思想已經有148次圍觀

http://www.coctec.com/docs/linux/show-post-50936.html