大家好,今天分享一篇Linux驅(qū)動(dòng)軟件設(shè)計(jì)思想的文章。由于文章較長(zhǎng),可以先收藏后再慢慢看。
一、Linux驅(qū)動(dòng)的軟件架構(gòu)
1.1 出發(fā)點(diǎn)
為適應(yīng)多種體系架構(gòu)的硬件,增強(qiáng)系統(tǒng)的可重用和跨平臺(tái)能力。
1.2 分離思想
為達(dá)到一個(gè)驅(qū)動(dòng)最好一行都不改就可以適用任何硬件平臺(tái)的目的,將驅(qū)動(dòng)與設(shè)備分離開(kāi)來(lái),驅(qū)動(dòng)只管驅(qū)動(dòng),設(shè)備只管設(shè)備,而驅(qū)動(dòng)以某種通用的標(biāo)準(zhǔn)途徑去拿板級(jí)信息,從而降低驅(qū)動(dòng)與設(shè)備的耦合程度。
1.3 分層思想
對(duì)于同類(lèi)設(shè)備,其基本框架都是一樣的,那么提煉出一個(gè)中間層,例如:對(duì)于 Input 設(shè)備(按鍵、鍵盤(pán)、觸摸屏、鼠標(biāo))來(lái)說(shuō),盡管 file_operation、I/O模型不可或缺,但基本框架都是一樣的,因此可提煉出一個(gè)?Input?核心層,把跟 Linux 接口以及整個(gè)一套?input?事件的匯報(bào)機(jī)制都在這里面實(shí)現(xiàn)。
二、platform設(shè)備驅(qū)動(dòng)
platform:linux中的一種虛擬總線。一個(gè)現(xiàn)實(shí)的linux設(shè)備和驅(qū)動(dòng)通常都需要掛接在一種總線上(方便管理),例如PCI、USB、I2C、SPI等,但是對(duì)于在Soc系統(tǒng)中集成的獨(dú)立外設(shè)控制器、掛接在Soc內(nèi)存空間的外設(shè)等卻不能依附于上述總線,這時(shí)候linux就發(fā)明了一種虛擬總線,來(lái)管理這一類(lèi)的設(shè)備(沒(méi)有實(shí)體的硬件總線電路)。 ? ? ?
platform設(shè)備驅(qū)動(dòng)模型中,分為設(shè)備、驅(qū)動(dòng)、總線3個(gè)實(shí)體,分別稱(chēng)為?platform_device、paltform_driver?和?platform總線,總線負(fù)責(zé)將設(shè)備和驅(qū)動(dòng)進(jìn)行綁定。在系統(tǒng)每注冊(cè)一個(gè)設(shè)備時(shí),會(huì)尋找與之匹配的驅(qū)動(dòng);相反的,在系統(tǒng)每注冊(cè)一個(gè)驅(qū)動(dòng)時(shí),會(huì)尋找與之匹配的設(shè)備,而匹配的過(guò)程則由總線完成。
2.1 platform設(shè)備
platform設(shè)備:由?platform_device?結(jié)構(gòu)體構(gòu)成,負(fù)責(zé)管理外設(shè)的資源,例如?I/O資源、內(nèi)存資源、中斷資源等等。 原型:linux/platform_device.h中
struct?platform_device?{ ????const?char????*?name; ????int????id; ????struct?device?dev; ????u32????num_resources; ????struct?resource????*?resource; ???? ????const?struct?platform_device_id????*id_entry; ???? ????/*?MFD?cell?pointer?*/ ????struct?mfd_cell?*mfd_cell; ???? ????/*?arch?specific?additions?*/ ????struct?pdev_archdata?archdata; };
?
2.1.1 resource 結(jié)構(gòu)體
resource?結(jié)構(gòu)體,描述了?platform_device?的資源:
struct?resource?{ ????resource_size_t?start;????????//資源的開(kāi)始 ????resource_size_t?end;????????//資源的結(jié)束 ????const?char?*name; ????unsigned?long?flags;????????//資源的類(lèi)型 ????struct?resource?*parent,?*sibling,?*child; };參數(shù)?flags?常用類(lèi)型?IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA等。參數(shù)?start?和?end?的含義會(huì)隨著?flags?的不同有所變化。
1)flags?為?IORESOURCE_MEM,start、end?分別表示該platform_device占據(jù)的內(nèi)存的開(kāi)始與結(jié)束地址;
2)flags?為?IORESOURCE_IRQ,start、end?分別表示該platform_device 使用的中斷號(hào)的開(kāi)始值與結(jié)束值,如果使用 1個(gè)中斷號(hào),開(kāi)始與結(jié)束值相同; 同類(lèi)型的資源可以有多份,例如某設(shè)備占據(jù)了多個(gè)內(nèi)存區(qū)域,則可以定義多個(gè)?IORESOURCE_MEM。 例如在?arch/arm/mach-at91/board-sam9261ek.c?板文件中為 DM9000 網(wǎng)卡定義的 resource:
static?struct?resource?dm9000_resource[]?=?{ ????[0]?=?{ ????????.start????=?AT91_CHIPSELECT_2, ????????.end????=?AT91_CHIPSELECT_2?+?3, ????????.flags????=?IORESOURCE_MEM ????}, ????[1]?=?{ ????????.start????=?AT91_CHIPSELECT_2?+?0x44, ????????.end????=?AT91_CHIPSELECT_2?+?0xFF, ????????.flags????=?IORESOURCE_MEM ????}, ????[2]?=?{ ????????.start????=?AT91_PIN_PC11, ????????.end????=?AT91_PIN_PC11, ????????.flags????=?IORESOURCE_IRQ ????????|?IORESOURCE_IRQ_LOWEDGE?|?IORESOURCE_IRQ_HIGHEDGE, ????} };
?
2.1.2 device 結(jié)構(gòu)體中的 platform_data 資源
設(shè)備除了可在 BSP 中定義資源以外,還可以附加一些數(shù)據(jù)信息,因?yàn)閷?duì)設(shè)備的硬件描述除了中斷、內(nèi)存等標(biāo)準(zhǔn)資源以外,可能還會(huì)有一些配置信息,而這些配置信息也依賴(lài)于板,不適宜直接放在設(shè)備驅(qū)動(dòng)上。 因此,platform_device?提供可供每個(gè)設(shè)備驅(qū)動(dòng)自定義的?platform_data?形式以支持添加一些數(shù)據(jù)信息,即 Linux 內(nèi)核不對(duì)這塊的數(shù)據(jù)做要求。
device?結(jié)構(gòu)體:
/** ?*?struct?device?-?The?basic?device?structure ...... ?*?@platform_data:?Platform?data?specific?to?the?device. ?*?????????Example:?For?devices?on?custom?boards,?as?typical?of?embedded ?*?????????and?SOC?based?hardware,?Linux?often?uses?platform_data?to?point ?*?????????to?board-specific?structures?describing?devices?and?how?they ?*?????????are?wired.??That?can?include?what?ports?are?available,?chip ?*?????????variants,?which?GPIO?pins?act?in?what?additional?roles,?and?so ?*?????????on.??This?shrinks?the?"Board?Support?Packages"?(BSPs)?and ?*?????????minimizes?board-specific?#ifdefs?in?drivers. ...... ?*/ struct?device?{ ????...... ????void?*platform_data;????/*?Platform?specific?data,?device ???????????????????????????core?doesn't?touch?it?*/ ????...... };
?
例如在?arch/arm/mach-at91/board-sam9261ek.c?板文件中,將?platform_data?定義了?dm9000_plat_data?結(jié)構(gòu)體,完成定義后,將MAC地址、總線寬度、板上有無(wú)EEPROM信息等放入:
?
static?struct?dm9000_plat_data?dm9000_platdata?=?{ ????.flags?=?DM9000_PLATF_16BITONLY?|?DM9000_PLATF_NO_EEPROM, }; static?struct?platform_device?dm9000_device?=?{ ????.name?=?"dm9000", ????.id????=?0, ????.num_resources?=?ARRAY_SIZE(dm9000_resource), ????.resource?=?dm9000_resource, ????.dev?=?{ ????????.platform_data????=?&dm9000_platdata, ????} };
?
2.1.3 platform_device 的注冊(cè)
對(duì)于Linux 2.6 ARM 平臺(tái)而言,對(duì)?platform_device?的定義通常在 BSP 的板文件中實(shí)現(xiàn),在板文件中,將?platform_device?歸納為一個(gè)數(shù)組,隨著板文件的加載,最終通過(guò)?platform_add_devices()?函數(shù)統(tǒng)一注冊(cè)。 platform_add_devices()?函數(shù)可以將平臺(tái)設(shè)備添加到系統(tǒng)中,這個(gè)函數(shù)的原型為:
int?platform_add_devices(struct?platform_device?**devs,?int?num)第一個(gè)參數(shù)為平臺(tái)設(shè)備數(shù)組的指針,第二個(gè)參數(shù)為平臺(tái)設(shè)備的數(shù)量,函數(shù)的內(nèi)部是調(diào)用?platform_device_register()?函數(shù)逐一注冊(cè)平臺(tái)設(shè)備。 如果注冊(cè)順利,可在?sys/devices/platform?目錄下看到相應(yīng)名字的子目錄。 Linux 3.x 之后,ARM Linux 不太以編碼的形式去填寫(xiě)?platform_device?和注冊(cè),更傾向于根據(jù)設(shè)備樹(shù)中的內(nèi)容自動(dòng)展開(kāi)platform_device。
?
2.2 platform驅(qū)動(dòng)
platform驅(qū)動(dòng):由?platform_driver?結(jié)構(gòu)體構(gòu)成,負(fù)責(zé)驅(qū)動(dòng)的操作實(shí)現(xiàn),例如加載、卸載、關(guān)閉、懸掛、恢復(fù)等。原型位于?linux/platform_driver.h中:
?
struct?platform_driver?{ ????int?(*probe)(struct?platform_device?*); ????int?(*remove)(struct?platform_device?*); ????void?(*shutdown)(struct?platform_device?*); ????int?(*suspend)(struct?platform_device?*,?pm_message_t?state); ????int?(*resume)(struct?platform_device?*); ????struct?device_driver?driver; ????const?struct?platform_device_id?*id_table; }; ?/*?@probe:????Called?to?query?the?existence?of?a?specific?device, ?*????????whether?this?driver?can?work?with?it,?and?bind?the?driver ?*????????to?a?specific?device. ?*?@remove:????Called?when?the?device?is?removed?from?the?system?to ?*????????unbind?a?device?from?this?driver. ?*?@shutdown:????Called?at?shut-down?time?to?quiesce?the?device. ?*?@suspend:????Called?to?put?the?device?to?sleep?mode.?Usually?to?a ?*????????low?power?state. ?*?@resume:????Called?to?bring?a?device?from?sleep?mode. ?*/probe()?和?remove()?分別對(duì)應(yīng)驅(qū)動(dòng)在加載、卸載時(shí)執(zhí)行的操作。 而直接填充?platform_driver?的?suspend()、resume()?做電源管理回調(diào)的方法目前已經(jīng)過(guò)時(shí),較好的做法是實(shí)現(xiàn)?platfrom_driver?的?device_driver?中?dev_pm_ops?結(jié)構(gòu)體成員。
?
2.2.1 device_driver 結(jié)構(gòu)體
?
struct?device_driver?{ ????const?char?*name; ????struct?bus_type?*bus; ???? ????struct?module?*owner; ????const?char?*mod_name;????/*?used?for?built-in?modules?*/ ???? ????bool?suppress_bind_attrs;????/*?disables?bind/unbind?via?sysfs?*/ ???? ????const?struct?of_device_id????*of_match_table; ???? ????int?(*probe)?(struct?device?*dev); ????int?(*remove)?(struct?device?*dev); ????void?(*shutdown)?(struct?device?*dev); ????int?(*suspend)?(struct?device?*dev,?pm_message_t?state); ????int?(*resume)?(struct?device?*dev); ????const?struct?attribute_group?**groups; ???? ????const?struct?dev_pm_ops?*pm; ???? ????struct?driver_private?*p; };
?
與?platform_driver?地位對(duì)等的?i2c_driver、spi_driver、usb_driver、pci_driver中都包含了?device_driver結(jié)構(gòu)體實(shí)例成員。它其實(shí)描述了各種 xxx_driver(xxx是總線名)在驅(qū)動(dòng)意義上的一些共性。
2.2.2 驅(qū)動(dòng)中獲取板的資源
獲取設(shè)備中?resource?資源:drivers/net/dm9000.c?中的?dm9000_probe()函數(shù)
????db->addr_res?=?platform_get_resource(pdev,?IORESOURCE_MEM,?0); ????db->data_res?=?platform_get_resource(pdev,?IORESOURCE_MEM,?1); ????db->irq_res??=?platform_get_resource(pdev,?IORESOURCE_IRQ,?0);或者:
db->irq_wake?=?platform_get_irq(pdev,?1);實(shí)際上是調(diào)用了?platform_get_resource(dev, IORESOURCE_IRQ, num); 獲取設(shè)備中?platform_data?資源:drivers/net/dm9000.c?中的?dm9000_probe()函數(shù)
struct?dm9000_plat_data?*pdata?=?pdev->dev.platform_data;
?
2.2.3 platform_driver 的注冊(cè)
通過(guò)?platform_driver_register()、platform_driver_unregister()?進(jìn)行?platform_driver?的注冊(cè)于注銷(xiāo)。
?
static?int?__init dm9000_init(void) { ????printk(KERN_INFO?"%s?Ethernet?Driver,?V%s ",?CARDNAME,?DRV_VERSION); ???? ????return?platform_driver_register(&dm9000_driver); } static?void?__exit dm9000_cleanup(void) { ????platform_driver_unregister(&dm9000_driver); } module_init(dm9000_init); module_exit(dm9000_cleanup);而原本的字符設(shè)備(或其它設(shè)備)的注冊(cè)和注銷(xiāo)工作移交到?platform_driver?的?probe()?和?remove()?成員函數(shù)中。以這樣的形式對(duì)字符設(shè)備驅(qū)動(dòng)進(jìn)行注冊(cè),只是套了一層?platform_driver?的外殼,并沒(méi)有改變是字符設(shè)備的本質(zhì)。 例如在?drivers/net/dm9000.c?中,還是將其定義為網(wǎng)絡(luò)設(shè)備,只是將網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)的注冊(cè)流程放在?probe()?中:
static?const?struct?net_device_ops?dm9000_netdev_ops?=?{ ????.ndo_open?=?dm9000_open, ????.ndo_stop?=?dm9000_stop, ????.ndo_start_xmit?=?dm9000_start_xmit, ????.ndo_tx_timeout?=?dm9000_timeout, ????.ndo_set_multicast_list?=?dm9000_hash_table, ????.ndo_do_ioctl?=?dm9000_ioctl, ????.ndo_change_mtu?=?eth_change_mtu, ????.ndo_set_features?=?dm9000_set_features, ????.ndo_validate_addr?=?eth_validate_addr, ????.ndo_set_mac_address?=?eth_mac_addr, #ifdef?CONFIG_NET_POLL_CONTROLLER ????.ndo_poll_controller?=?dm9000_poll_controller, #endif };
?
2.3 platform總線
platform總線:負(fù)責(zé)管理外設(shè)與驅(qū)動(dòng)之間的匹配。
系統(tǒng)為 platfrom總線 定義了一個(gè)?bus_type?的實(shí)例?platform_bus_type,其定義位于?drivers/base/platform.c下:
?
struct?bus_type?platform_bus_type?=?{ ????.name?=?"platform", ????.dev_attrs?=?platform_dev_attrs, ????.match?=?platform_match, ????.uevent?=?platform_uevent, ????.pm????=?&platform_dev_pm_ops, };
?
2.3.1 .match 成員函數(shù)
重點(diǎn)關(guān)注其?match()?成員函數(shù),此成員函數(shù)確定了?platform_device?和?platform_driver?之間是如何進(jìn)行匹配的。
?
static?int?platform_match(struct?device?*dev,?struct?device_driver?*drv) { ????struct?platform_device?*pdev?=?to_platform_device(dev); ????struct?platform_driver?*pdrv?=?to_platform_driver(drv); ????/*?Attempt?an?OF?style?match?first?*/ ????if?(of_driver_match_device(dev,?drv)) ????????return?1; ????/*?Then?try?to?match?against?the?id?table?*/ ????if?(pdrv->id_table) ????????return?platform_match_id(pdrv->id_table,?pdev)?!=?NULL; ????/*?fall-back?to?driver?name?match?*/ ????return?(strcmp(pdev->name,?drv->name)?==?0); }可以看出?platform_device?和?platform_driver?之間匹配有 3 種可能性:
?
基于設(shè)備樹(shù)風(fēng)格的匹配;
匹配 ID 表(即 platform_device 設(shè)備名是否出現(xiàn)在 platform_driver 的 ID 表內(nèi));
匹配 platform_device 設(shè)備名和驅(qū)動(dòng)的名字。
2.3.2 platform總線的注冊(cè)
?
start_kernel() ????rest_init() ????????kernel_init() ????????????do_basic_setup() ????????????????driver_init() ????????????????????platform_bus_init() ???? int?__init?platform_bus_init(void) { ????int?error; ????early_platform_cleanup();????????????????//早期的平臺(tái)清理 ????error?=?device_register(&platform_bus);??//注冊(cè)設(shè)備?(在/sys/devices/目錄下建立?platform目錄對(duì)應(yīng)的設(shè)備對(duì)象??/sys/devices/platform/)? ????if?(error) ????????return?error; ????error?=??bus_register(&platform_bus_type);//總線注冊(cè) ????if?(error) ????????device_unregister(&platform_bus); ????return?error; }
?
2.3.3 platform總線自動(dòng)匹配
?
platform_device_register() ????platform_device_add() ????????device_add() ????????????bus_probe_device() ????????????????device_attach() ????????????????????bus_for_each_drv() ????????????????????????---------- platform_driver_register() ????driver_register() ????????bus_add_driver() ????????????driver_attach() ????????????????bus_for_each_dev()??????? ????????????????????---------無(wú)論是先注冊(cè)設(shè)備還是先注冊(cè)設(shè)備驅(qū)動(dòng),都會(huì)進(jìn)行一次設(shè)備與設(shè)備驅(qū)動(dòng)的匹配過(guò)程,匹配成功之后就會(huì)將其進(jìn)行綁定,匹配的原理就是去遍歷總線下設(shè)備或者設(shè)備驅(qū)動(dòng)的鏈表。
?
2.4 platform 的優(yōu)點(diǎn)
使得設(shè)備被掛接在一個(gè)總線上,符合 Linux 2.6 以后內(nèi)核的設(shè)備模型。其結(jié)果是使配套的 sysfs 節(jié)點(diǎn)、設(shè)備電源管理都成為可能。
將 BSP 和 驅(qū)動(dòng)隔離。在 BSP 中定義 platform 設(shè)備和設(shè)備使用的資源、設(shè)備的具體配置信息,而在驅(qū)動(dòng)中,只需要通過(guò)通用 API 去獲取資源和數(shù)據(jù),做到了板相關(guān)代碼和驅(qū)動(dòng)代碼的分離,使得驅(qū)動(dòng)具有更好的可擴(kuò)展性和跨平臺(tái)性。
讓一個(gè)驅(qū)動(dòng)支持多個(gè)設(shè)備實(shí)例。譬如 DM9000 的驅(qū)動(dòng)只有一份,但是我們可以在板級(jí)添加多份 DM9000 的 platform_device,他們都可以與唯一的驅(qū)動(dòng)匹配。
在 Linux 3.x之后的內(nèi)核中,DM9000 驅(qū)動(dòng)可通過(guò)設(shè)備樹(shù)的方法被枚舉,添加的動(dòng)作只需要簡(jiǎn)單的修改 dts 文件。(詳細(xì)的后續(xù)再貼鏈接)
三、設(shè)備驅(qū)動(dòng)的分層思想
在面向?qū)ο蟮某绦蛟O(shè)計(jì)中,可以為某一類(lèi)相似的事物定義一個(gè)基類(lèi),而具體的事物可以繼承這個(gè)基類(lèi)中的函數(shù)。如果對(duì)于繼承的這個(gè)事物而言,某成員函數(shù)的實(shí)現(xiàn)與基類(lèi)一致,那它就可以直接繼承基類(lèi)的函數(shù);相反,它也可以重寫(xiě)(Overriding),對(duì)父類(lèi)的函數(shù)進(jìn)行重新定義。 ? ? ? ?若子類(lèi)中的方法與父類(lèi)中的某方法具有相同的方法名、返回類(lèi)型和參數(shù)表,則新方法將覆蓋原有的方法。這樣可以極大的提高代碼的可重用能力。 雖然 Linux 內(nèi)核完全是由 C 和 匯編寫(xiě)的,但卻頻繁用到了面向?qū)ο蟮脑O(shè)計(jì)思想。在設(shè)備驅(qū)動(dòng)方面,往往為同類(lèi)的設(shè)備設(shè)計(jì)一個(gè)框架,而框架中的核心層則實(shí)現(xiàn)了該設(shè)備通用的一些功能。同樣的,如果具體的設(shè)備不想使用核心層的函數(shù),也可以重寫(xiě)。
例1:
?
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() 函數(shù)的實(shí)現(xiàn)中,會(huì)檢查底層設(shè)備是否重載了 core_funca()。如果重載了,就調(diào)用底層的代碼,否則,直接使用通用層的。 這樣做的好處是,核心層的代碼可以處理絕大多數(shù)該類(lèi)設(shè)備的 core_funca() 對(duì)應(yīng)的功能,只有少數(shù)特殊設(shè)備需要重新實(shí)現(xiàn) core_funca()。
?
例2:
?
return_type?core_funca(xxx_device?*?bottom_dev,?param1_type?param1,?param1_type?param2) { ????/*?通用的步驟代碼A?*/ ????typea_dev_commonA(); ????... ???? ????/*?底層操作?ops1?*/ ????bottom_dev->funca_ops1(); ???? ????/*?通用的步驟代碼B?*/ ????typea_dev_commonB(); ????... ????/*?底層操作?ops2?*/???? ????bottom_dev->funca_ops2(); ???? ????/*?通用的步驟代碼C?*/ ????typea_dev_commonC(); ????... ???? ????/*?底層操作?ops3?*/ ????bottom_dev->funca_ops3(); }上述代碼假定為了實(shí)現(xiàn)funca(),對(duì)于同類(lèi)設(shè)備而言,操作流程一致,都要經(jīng)過(guò)“通用代碼A、底層ops1、通用代碼B、底層ops2、通用代碼C、底層ops3”這幾步,分層設(shè)計(jì)明顯帶來(lái)的好處是,對(duì)于通用代碼A、B、C,具體的底層驅(qū)動(dòng)不需要再實(shí)現(xiàn)(抽離出來(lái),放到核心層實(shí)現(xiàn)),而僅僅只關(guān)心其底層的操作ops1、ops2、ops3。 下圖明確反映了設(shè)備驅(qū)動(dòng)的核心層與具體設(shè)備驅(qū)動(dòng)的關(guān)系,實(shí)際上,這種分層可能只有2層,也可能是多層。 這樣的分層設(shè)計(jì)在 Linux 的 Input、RTC、MTD、I2C、SPI、tty、USB等諸多類(lèi)型設(shè)備驅(qū)動(dòng)中都存在。
?
3.1 輸入設(shè)備驅(qū)動(dòng)
輸入設(shè)備(如按鍵、鍵盤(pán)、觸摸屏、鼠標(biāo)等)是典型的字符設(shè)備,其一般的工作機(jī)理是底層在按鍵、觸摸等動(dòng)作發(fā)送時(shí)產(chǎn)生一個(gè)中斷(或驅(qū)動(dòng)通過(guò) Timer 定時(shí)查詢(xún)),然后CPU通過(guò)SPI、I2C 或外部存儲(chǔ)器總線讀取鍵值、坐標(biāo)等數(shù)據(jù),放入1個(gè)緩沖區(qū),字符設(shè)備驅(qū)動(dòng)管理該緩沖區(qū),而驅(qū)動(dòng)的 read() 接口讓用戶(hù)可以讀取鍵值、坐標(biāo)等數(shù)據(jù)。 顯然,在這些工作中,只有中斷、讀值是設(shè)備相關(guān)的,而輸入事件的緩沖區(qū)管理以及字符設(shè)備驅(qū)動(dòng)的 file_operations 接口則對(duì)輸入設(shè)備是通用的?;诖?,內(nèi)核設(shè)計(jì)了輸入子系統(tǒng),由核心層處理公共的工作。
3.1.1 輸入核心提供了底層輸入設(shè)備驅(qū)動(dòng)程序所需的API
如分配/釋放一個(gè)輸入設(shè)備:
?
struct?input_dev?*input_allocate_device(void);????//返回的結(jié)構(gòu)體用于表征1個(gè)輸入設(shè)備。 void?input_free_device(struct?input_dev?*dev);
?
注冊(cè)/注銷(xiāo)輸入設(shè)備用的如下接口:
?
int?__must_check?input_register_device(struct?input_dev?*); void?input_unregister_device(struct?input_dev?*);
?
報(bào)告輸入事件用的如下接口:
?
/*?報(bào)告指定type、code的輸入事件?*/ void?input_event(struct?input_dev?*dev,?unsigned?int?type,?unsigned?int?code,?int?value); /*?報(bào)告鍵值?*/ void?input_report_key(struct?input_dev?*dev,?unsigned?int?code,?int?value); /*?報(bào)告相對(duì)坐標(biāo)?*/ void?input_report_rel(struct?input_dev?*dev,?unsigned?int?code,?int?value); /*?報(bào)告絕對(duì)坐標(biāo)?*/ void?input_report_abs(struct?input_dev?*dev,?unsigned?int?code,?int?value); /*?報(bào)告同步事件?*/ void?input_sync(struct?input_dev?*dev);
?
而所有的輸入事件,內(nèi)核都用統(tǒng)一的數(shù)據(jù)結(jié)構(gòu)來(lái)描述,這個(gè)數(shù)據(jù)結(jié)構(gòu)是input_event:
?
struct?input_event?{ ????struct?timeval?time; ????__u16?type; ????__u16?code; ????__s32?value; };
?
3.1.2 案例:gpio按鍵驅(qū)動(dòng)
drivers/input/keyboard/gpio_keys.c?是基于 input 架構(gòu)實(shí)現(xiàn)的一個(gè)通用的 GPIO 按鍵驅(qū)動(dòng)。該驅(qū)動(dòng)基于 platform_driver架構(gòu),名為 “gpio-keys”。它將硬件相關(guān)的信息(如使用的GPIO號(hào),電平等)屏蔽在板文件 platform_device 的 platform_data 中,因此該驅(qū)動(dòng)可應(yīng)用于各個(gè)處理器,具有良好的跨平臺(tái)性。
該驅(qū)動(dòng)的?probe()?函數(shù):
?
static?int?__devinit?gpio_keys_probe(struct?platform_device?*pdev) { ????...... ????input?=?input_allocate_device();????//分配一個(gè)輸入設(shè)備 ????......???????????????????????????????????? ????input->name?=?pdata->name???:?pdev->name;????//初始化該?input_dev?的一些屬性 ????input->phys?=?"gpio-keys/input0"; ????input->dev.parent?=?&pdev->dev; ????input->open?=?gpio_keys_open; ????input->close?=?gpio_keys_close; ???? ????input->id.bustype?=?BUS_HOST; ????input->id.vendor?=?0x0001; ????input->id.product?=?0x0001; ????input->id.version?=?0x0100;???????????????? ????...... ????for?(i?=?0;?i?nbuttons;?i++)?{????????//初始化所用到的?GPIO ????????struct?gpio_keys_button?*button?=?&pdata->buttons[i]; ????????struct?gpio_button_data?*bdata?=?&ddata->data[i]; ????????unsigned?int?type?=?button->type??:?EV_KEY; ???????? ????????bdata->input?=?input; ????????bdata->button?=?button; ???????? ????????error?=?gpio_keys_setup_key(pdev,?bdata,?button); ????????if?(error) ????????????goto?fail2; ???????? ????????if?(button->wakeup) ????????????wakeup?=?1; ???????? ????????input_set_capability(input,?type,?button->code); ????} ????...... ????error?=?input_register_device(input);????????//注冊(cè)輸入設(shè)備 ????...... }在注冊(cè)輸入設(shè)備后,底層輸入設(shè)備驅(qū)動(dòng)的核心工作只剩下在按鍵、觸摸等人為動(dòng)作發(fā)生時(shí)報(bào)告事件。在中斷服務(wù)函數(shù)中,GPIO 按鍵驅(qū)動(dòng)通過(guò)?input_event()、input_sync()?這樣的函數(shù)來(lái)匯報(bào)按鍵事件以及同步事件。 從底層的 GPIO 按鍵驅(qū)動(dòng)可以看出,該驅(qū)動(dòng)中沒(méi)有任何 file_operation 的動(dòng)作,也沒(méi)有各種 I/O 模型,注冊(cè)進(jìn)入系統(tǒng)也用的是?input_register_device()?這樣與 input 相關(guān)的 API。 這是由于與 Linux VFS 接口的這一部分代碼全部都在?drivers/input/evdev.c?中實(shí)現(xiàn)了: input?核心層的 file_operations 和 read() 函數(shù):
static?ssize_t?evdev_read(struct?file?*file,?char?__user?*buffer, ??????????????size_t?count,?loff_t?*ppos) { ????struct?evdev_client?*client?=?file->private_data; ????struct?evdev?*evdev?=?client->evdev; ????struct?input_event?event; ????int?retval; ????if?(count?f_flags?&?O_NONBLOCK))?{????????????????//檢查是否是非阻塞訪問(wèn) ????????????retval?=?wait_event_interruptible(evdev->wait, ????????????????client->packet_head?!=?client->tail?||?!evdev->exist); ????????if?(retval) ????????????return?retval; ????} ???? ????if?(!evdev->exist) ????????return?-ENODEV; ????while?(retval?+?input_event_size()?<=?count?&&????????//處理了阻塞的睡眠情況 ????????????evdev_fetch_next_event(client,?&event))?{ ???? ????????if?(input_event_to_user(buffer?+?retval,?&event)) ????????????return?-EFAULT; ???? ????????retval?+=?input_event_size(); ????} ????if?(retval?==?0?&&?file->f_flags?&?O_NONBLOCK) ????????retval?=?-EAGAIN; ????return?retval; }
?
3.2 RTC 設(shè)備驅(qū)動(dòng)
RTC (實(shí)時(shí)時(shí)鐘)借助電池供電,在系統(tǒng)掉電的情況下依然可以正常計(jì)時(shí)。通常還具有產(chǎn)生周期性中斷以及鬧鐘中斷的能力,是一種典型的字符設(shè)備。 作為一種字符設(shè)備驅(qū)動(dòng),RTC 需要實(shí)現(xiàn) file_operations 中的接口函數(shù),例如 open()、read()等等。而 RTC 典型的 IOCTL 包括?RTC_SET_TIME、RTC_ALM_READ、RTC_ALM_SET、RTC_IRQP_SET、RTC_IRQP_READ等,這些對(duì)于 RTC 來(lái)說(shuō)是通用的,那么這些通用的就放在 RTC 的核心層,而與設(shè)備相關(guān)的具體實(shí)現(xiàn)則放在底層。
與 RTC 核心有關(guān)的文件有: /drivers/rtc/class.c????????//該文件向linux設(shè)備模型核心注冊(cè)了一個(gè)類(lèi)RTC,然后向驅(qū)動(dòng)程序提供了注冊(cè)/注銷(xiāo)接口 /drivers/rtc/rtc-dev.c??????//該文件定義了基本的設(shè)備文件操作函數(shù),如:open,read等 /drivers/rtc/interface.c????//該文件主要提供用戶(hù)程序與RTC驅(qū)動(dòng)的接口函數(shù),用戶(hù)程序一般通過(guò)ioctl與RTC??????????????????????????????//驅(qū)動(dòng)交互,這里定義了每個(gè)ioctl命令需要調(diào)用的函數(shù) /drivers/rtc/rtc-sysfs.c????//與sysfs有關(guān) /drivers/rtc/rtc-proc.c?????//與proc文件系統(tǒng)有關(guān) /include/linux/rtc.h????????//定義了與RTC有關(guān)的數(shù)據(jù)結(jié)構(gòu)
?
RTC 驅(qū)動(dòng)模型如下圖:
下面主要了解 RTC 核心 的以下幾點(diǎn):
實(shí)現(xiàn) file_operations 的成員函數(shù)以及一些通用的關(guān)于 RTC 的控制代碼;
向底層導(dǎo)出?rtc_device_register()、?rtc_device_unregister()以注冊(cè)和注銷(xiāo) RTC;
導(dǎo)出 rtc_class_ops 結(jié)構(gòu)體以描述底層的 RTC 硬件操作。
在這樣的驅(qū)動(dòng)模型下,底層的 RTC 驅(qū)動(dòng)不再需要關(guān)心 RTC 作為字符設(shè)備驅(qū)動(dòng)的具體實(shí)現(xiàn),也無(wú)需關(guān)心一些通用的 RTC 控制邏輯。關(guān)系如下:
以S3C6410 的 RTC驅(qū)動(dòng)為例:
RTC 核心:
1. 在文件?drivers/rtc/rtc-dev.c?中:實(shí)現(xiàn) file_operations 相關(guān)成員函數(shù)
?
static?const?struct?file_operations?rtc_dev_fops?=?{ ????.owner????????=?THIS_MODULE, ????.llseek????????=?no_llseek, ????.read????????=?rtc_dev_read, ????.poll????????=?rtc_dev_poll, ????.unlocked_ioctl????=?rtc_dev_ioctl, ????.open????????=?rtc_dev_open, ????.release????????????=?rtc_dev_release, ????.fasync????????=?rtc_dev_fasync, };
?
2. 在文件?drivers/rtc/class.c中:向底層提供注冊(cè)/注銷(xiāo)接口
?
struct?rtc_device?*rtc_device_register(const?char?*name,?struct?device?*dev, ????????????????????const?struct?rtc_class_ops?*ops, ????????????????????struct?module?*owner) void?rtc_device_unregister(struct?rtc_device?*rtc)
?
3. 在文件?drivers/rtc/class.h中:導(dǎo)出 rtc_class_ops 結(jié)構(gòu)體
?
struct?rtc_class_ops?{ ????int?(*open)(struct?device?*); ????void?(*release)(struct?device?*); ????int?(*ioctl)(struct?device?*,?unsigned?int,?unsigned?long); ????int?(*read_time)(struct?device?*,?struct?rtc_time?*); ????int?(*set_time)(struct?device?*,?struct?rtc_time?*); ????int?(*read_alarm)(struct?device?*,?struct?rtc_wkalrm?*); ????int?(*set_alarm)(struct?device?*,?struct?rtc_wkalrm?*); ????int?(*proc)(struct?device?*,?struct?seq_file?*); ????int?(*set_mmss)(struct?device?*,?unsigned?long?secs); ????int?(*read_callback)(struct?device?*,?int?data); ????int?(*alarm_irq_enable)(struct?device?*,?unsigned?int?enabled); };
?
S3C6410底層:在drivers/rtc/rtc-s3c.c?文件中
其注冊(cè) RTC 以及綁定 rtc_class_ops:
?
static?const?struct?rtc_class_ops?s3c_rtcops?=?{ ????.read_time????=?s3c_rtc_gettime, ????.set_time????=?s3c_rtc_settime, ????.read_alarm????=?s3c_rtc_getalarm, ????.set_alarm????=?s3c_rtc_setalarm, ????.alarm_irq_enable?=?s3c_rtc_setaie, }; static?int?__devinit?s3c_rtc_probe(struct?platform_device?*pdev) { ????...... ????/*?register?RTC?and?exit?*/ ????rtc?=?rtc_device_register("s3c",?&pdev->dev,?&s3c_rtcops, ??????????????????THIS_MODULE); ????...... }drivers/rtc/rtc-dev.c?以及其調(diào)用的drivers/rtc/interface.c?等 RTC 核心層相當(dāng)于把 file_operations 中的 open()、release()、讀取和設(shè)置時(shí)間等,都間接 “轉(zhuǎn)發(fā)” 給了底層的實(shí)例。如下摘取部分 RTC 核心層調(diào)用具體底層驅(qū)動(dòng) callback 的過(guò)程:
?
1)open:
?
/*?文件 drivers/rtc/rtc-dev.c 中:?*/ static?int?rtc_dev_open(struct?inode?*inode,?struct?file?*file) { ????const?struct?rtc_class_ops?*ops?=?rtc->ops; ????...... ????err?=?ops->open???ops->open(rtc->dev.parent)?:?0; ????...... }
?
2)IOCTL的 命令:
?
/*?文件?drivers/rtc/rtc-dev.c?中?*/ static?long?rtc_dev_ioctl(struct?file?*file,?unsigned?int?cmd,?unsigned?long?arg) { ????...... ????switch?(cmd)?{ ????case?RTC_ALM_READ: ????????......???? ????????err?=?rtc_read_alarm(rtc,?&alarm); ????????...... ????case?RTC_ALM_SET: ????????...... ????case?RTC_SET_TIME: ????????......???????? ????????return?rtc_set_time(rtc,?&tm); ????...... ????} ????...... } /*?文件?drivers/rtc/interface.c?中?*/ static?int?__rtc_read_time(struct?rtc_device?*rtc,?struct?rtc_time?*tm) { ????int?err; ????if?(!rtc->ops) ????????err?=?-ENODEV; ????else?if?(!rtc->ops->read_time)????????//回調(diào) ????????err?=?-EINVAL; ????...... } ???? int?rtc_read_time(struct?rtc_device?*rtc,?struct?rtc_time?*tm) { ????...... ????err?=?__rtc_read_time(rtc,?tm); ????...... }
?
3.3 Framebuffer 設(shè)備驅(qū)動(dòng)
未深入,參考《Linux設(shè)備驅(qū)動(dòng)開(kāi)發(fā)詳解:基于最新的Linux 4.0內(nèi)核》
3.4 終端設(shè)備驅(qū)動(dòng)
在 Linux 系統(tǒng)中,終端是一種字符型設(shè)備,它有多種類(lèi)型,通常使用 tty (Teletype)來(lái)簡(jiǎn)稱(chēng)各種類(lèi)型的終端設(shè)備。在嵌入式系統(tǒng)中,最常用的是 UART 串行端口。
3.4.1 內(nèi)核中 tty 的層次結(jié)構(gòu)
圖中包含三個(gè)層次:
tty_io.c:tty 核心;
n_tty.c:tty 線路規(guī)程;
xxx_tty.c:tty 驅(qū)動(dòng)實(shí)例。
3.4.1.1 tty_io.c
tty_io.c?本身是一個(gè)標(biāo)準(zhǔn)的字符設(shè)備驅(qū)動(dòng),因此,它對(duì)上有字符設(shè)備的職責(zé),需實(shí)現(xiàn)?file_operations?結(jié)構(gòu)體成員函數(shù)。 但 tty 核心層對(duì)下又定義了?tty_driver?的架構(gòu),因此 tty 設(shè)備驅(qū)動(dòng)的主體工作就變成了填充?tty_driver?結(jié)構(gòu)體中的成員,實(shí)現(xiàn)其成員?tty_operations?結(jié)構(gòu)體的成員函數(shù),而不再是去實(shí)現(xiàn)?file_operations?結(jié)構(gòu)體成員函數(shù)這一級(jí)的工作。
struct?tty_driver?{ ????...... ????/* ????*?Driver?methods ????*/ ???? ????const?struct?tty_operations?*ops; ????struct?list_head?tty_drivers; }; struct?tty_operations?{ ????struct?tty_struct?*?(*lookup)(struct?tty_driver?*driver, ????struct?inode?*inode,?int?idx); ????int??(*install)(struct?tty_driver?*driver,?struct?tty_struct?*tty); ????void?(*remove)(struct?tty_driver?*driver,?struct?tty_struct?*tty); ????int??(*open)(struct?tty_struct?*?tty,?struct?file?*?filp); ????void?(*close)(struct?tty_struct?*?tty,?struct?file?*?filp); ????void?(*shutdown)(struct?tty_struct?*tty); ????void?(*cleanup)(struct?tty_struct?*tty); ????int??(*write)(struct?tty_struct?*?tty, ????????const?unsigned?char?*buf,?int?count); ????...... ????const?struct?file_operations?*proc_fops; };
?
3.4.1.2 n_tty.c
n_tty.c:tty 線路規(guī)程的工作是以特殊的方式格式化從一個(gè)用戶(hù)或者硬件收到的數(shù)據(jù),這種格式化常常采用一個(gè)協(xié)議轉(zhuǎn)換的形式。
3.4.2 tty 設(shè)備的發(fā)送/接收流程
發(fā)送流程:tty 核心從一個(gè)用戶(hù)獲取將要發(fā)送給一個(gè) tty 設(shè)備的數(shù)據(jù),tty 核心將數(shù)據(jù)傳遞給 tty 線路規(guī)程驅(qū)動(dòng),接著數(shù)據(jù)被傳遞到 tty 驅(qū)動(dòng),tty 驅(qū)動(dòng)將數(shù)據(jù)轉(zhuǎn)換為可以發(fā)送給硬件的格式。 從?tty_driver?操作集?tty_operations?的成員函數(shù) write() 函數(shù)接收3個(gè)參數(shù):tty_struct、發(fā)送數(shù)據(jù)指針和發(fā)送的字節(jié)數(shù)。該函數(shù)是被?file_operations?的 write() 成員函數(shù)間接觸發(fā)調(diào)用的。 接收流程:從 tty 硬件接收到的數(shù)據(jù)向上交給 tty 驅(qū)動(dòng),接著進(jìn)入 tty 線路規(guī)程驅(qū)動(dòng),再進(jìn)入 tty 核心,在這里它被一個(gè)用戶(hù)獲取。 tty 驅(qū)動(dòng)一般收到字符后會(huì)通過(guò)?tty_flip_buffer_push()?將接收緩沖區(qū)推到線路規(guī)程。
3.4.3 串口核心層
盡管一個(gè)特定的底層 UART 設(shè)備驅(qū)動(dòng)完全可以遵循上述?tty_driver?的方法來(lái)設(shè)計(jì),即定義tty_driver 并實(shí)現(xiàn)?tty_operations?中的成員函數(shù),但是鑒于串口之間的共性,Linux 考慮在文件?drivers/tty/serial/serial_core.c?中實(shí)現(xiàn) UART 設(shè)備的通用 tty 驅(qū)動(dòng)層(稱(chēng)為串口核心層)。這樣,UART 驅(qū)動(dòng)的主要任務(wù)就進(jìn)一步演變成了實(shí)現(xiàn) 文件?serial_core.c中定義的一組 uart_xxx 接口,而不是 tty_xxx 接口。 按照面向?qū)ο蟮乃枷?,可認(rèn)為 tty_driver 是字符設(shè)備的泛化、serial_core 是 tty_driver 的泛化,而具體的串口驅(qū)動(dòng)又是 serial_core 的泛化。 在串口核心層又定義新的?uart_driver?結(jié)構(gòu)體和其操作集?uart_ops。一個(gè)底層的 UART 驅(qū)動(dòng)需要?jiǎng)?chuàng)建和通過(guò)?uart_register_driver()?注冊(cè)一個(gè)?uart_driver?而不是?tty_driver。
struct?uart_driver?{ ????struct?module???????*owner; ????const?char????????*driver_name; ????const?char????????*dev_name; ????int?????????????major; ????int?????????????minor; ????int?????????????nr; ????struct?console?????*cons; ???? ????/* ????*?these?are?private;?the?low?level?driver?should?not ????*?touch?these;?they?should?be?initialised?to?NULL ????*/ ????struct?uart_state????*state; ????struct?tty_driver????*tty_driver; }; int?uart_register_driver(struct?uart_driver?*drv); void?uart_unregister_driver(struct?uart_driver?*drv);uart_driver 結(jié)構(gòu)體在本質(zhì)上是派生自 tty_driver 結(jié)構(gòu)體,因此,uart_driver 結(jié)構(gòu)體中包含 tty_dirver 結(jié)構(gòu)體成員。 tty_operations?在UART 這個(gè)層面上也被進(jìn)一步泛化為?uart_ops:
struct?uart_ops?{ ????unsigned?int????(*tx_empty)(struct?uart_port?*); ????void????(*set_mctrl)(struct?uart_port?*,?unsigned?int?mctrl); ????unsigned?int????(*get_mctrl)(struct?uart_port?*); ????void????(*stop_tx)(struct?uart_port?*); ????void????(*start_tx)(struct?uart_port?*); ????void????(*send_xchar)(struct?uart_port?*,?char?ch); ????void????(*stop_rx)(struct?uart_port?*); ????void????(*enable_ms)(struct?uart_port?*); ????void????(*break_ctl)(struct?uart_port?*,?int?ctl); ????int?????(*startup)(struct?uart_port?*); ????void????(*shutdown)(struct?uart_port?*); ????void????(*flush_buffer)(struct?uart_port?*); ????void????(*set_termios)(struct?uart_port?*,?struct?ktermios?*new, ???????????struct?ktermios?*old); ????void????(*set_ldisc)(struct?uart_port?*,?int?new); ????void????(*pm)(struct?uart_port?*,?unsigned?int?state, ?????????unsigned?int?oldstate); ????int?????(*set_wake)(struct?uart_port?*,?unsigned?int?state); ????void????(*wake_peer)(struct?uart_port?*); ???? ????/* ????*?Return?a?string?describing?the?type?of?the?port ????*/ ????const?char?*(*type)(struct?uart_port?*); ???? ????/* ????*?Release?IO?and?memory?resources?used?by?the?port. ????*?This?includes?iounmap?if?necessary. ????*/ ????void????(*release_port)(struct?uart_port?*); ???? ????/* ????*?Request?IO?and?memory?resources?used?by?the?port. ????*?This?includes?iomapping?the?port?if?necessary. ????*/ ????int?????(*request_port)(struct?uart_port?*); ????void????(*config_port)(struct?uart_port?*,?int); ????int?????(*verify_port)(struct?uart_port?*,?struct?serial_struct?*); ????int?????(*ioctl)(struct?uart_port?*,?unsigned?int,?unsigned?long); #ifdef?CONFIG_CONSOLE_POLL ????void????(*poll_put_char)(struct?uart_port?*,?unsigned?char); ????int?????(*poll_get_char)(struct?uart_port?*); #endif };由于?driver/tty/serial/serial_core.c?是一個(gè)?tty_driver?,因此在 serial_core.c 中,存在一個(gè) tty_operations 的實(shí)例,這個(gè)實(shí)例的成員函數(shù)會(huì)進(jìn)一步調(diào)用 struct uart_ops 的成員函數(shù),這樣就把 file_operaions 里的成員函數(shù)、tty_operations 的成員函數(shù)和 uart_ops 的成員函數(shù)串起來(lái)。
?
3.5 misc 設(shè)備驅(qū)動(dòng)
......
3.6 驅(qū)動(dòng)核心層
核心層的 3 大職責(zé):
對(duì)上提供接口。file_operations 的讀、寫(xiě)、ioctl 都被中間層搞定,各種 I/O 模型也被處理掉了。
中間層實(shí)現(xiàn)通用邏輯??梢员坏讓痈鞣N實(shí)例共享的代碼都被中間層搞定,避免底層重復(fù)實(shí)現(xiàn)。
對(duì)下定義框架。底層的驅(qū)動(dòng)不再需要關(guān)心 Linux 內(nèi)核 VFS 的接口和各種可能的 I/O 模型,而只需處理與具體硬件相關(guān)的訪問(wèn)。
這種分層有時(shí)候還不是兩層,可以有更多層,在軟件上呈現(xiàn)為面向?qū)ο罄镱?lèi)繼承和多態(tài)的狀態(tài)。
四、主機(jī)驅(qū)動(dòng)與外設(shè)驅(qū)動(dòng)分離的設(shè)計(jì)思想
4.1 主機(jī)驅(qū)動(dòng)與外設(shè)驅(qū)動(dòng)分離
Linux 中的 SPI、I2C、USB 等子系統(tǒng)都是典型的利用主機(jī)驅(qū)動(dòng)和外設(shè)驅(qū)動(dòng)分離的思想。 讓主機(jī)端只負(fù)責(zé)產(chǎn)生總線上的傳輸波形,而外設(shè)端只是通過(guò)標(biāo)準(zhǔn)的 API 來(lái)讓主機(jī)端以適當(dāng)?shù)牟ㄐ卧L問(wèn)自身。涉及 4 個(gè)軟件模塊: 1. 主機(jī)端的驅(qū)動(dòng)。根據(jù)具體的 SPI、I2C、USB 等控制器的硬件手冊(cè),操作具體的控制器,產(chǎn)生總線的各種波形。 2. 連接主機(jī)和外設(shè)的紐帶。外設(shè)不直接調(diào)用主機(jī)端的驅(qū)動(dòng)來(lái)產(chǎn)生波形,而是調(diào)用一個(gè)標(biāo)準(zhǔn)的 API。由這個(gè)標(biāo)準(zhǔn)的 API 把這個(gè)波形的傳輸請(qǐng)求間接 “轉(zhuǎn)發(fā)” 給具體的主機(jī)端驅(qū)動(dòng)。最好在這里把關(guān)于波形的描述也以某種數(shù)據(jù)結(jié)構(gòu)標(biāo)準(zhǔn)化。 3. 外設(shè)端的驅(qū)動(dòng)。外設(shè)接在 SPI、I2C、USB 這樣的總線上,但是它們本身可以是觸摸屏、網(wǎng)卡、聲卡或任意一種類(lèi)型的設(shè)備。當(dāng)這些外設(shè)要求 SPI 、I2C、USB等去訪問(wèn)它的時(shí)候,它調(diào)用 “連接主機(jī)和外設(shè)的紐帶” 模塊的標(biāo)準(zhǔn) API。 4. 板級(jí)邏輯。用來(lái)描述主機(jī)和外設(shè)是如何互聯(lián)的,它相當(dāng)于一個(gè) “路由表”。假設(shè)板子上有多個(gè) SPI 控制器和多個(gè) SPI 外設(shè),那究竟誰(shuí)接在誰(shuí)上面?管理互聯(lián)關(guān)系,既不是主機(jī)端的責(zé)任,也不是外設(shè)端的責(zé)任,這屬于板級(jí)邏輯的責(zé)任。 linux 通過(guò)上述設(shè)計(jì)方法,劃分為 4 個(gè)輕量級(jí)的小模塊,各個(gè)模塊各司其職。
4.2 Linux SPI 主機(jī)和設(shè)備驅(qū)動(dòng)
4.2.1 SPI 主機(jī)驅(qū)動(dòng)
在 Linux 中,通過(guò)?spi_master?結(jié)構(gòu)體來(lái)描述一個(gè) SPI 主動(dòng)控制器驅(qū)動(dòng)其主要成員由主機(jī)控制器的序號(hào)、片選數(shù)量、SPI 模式、時(shí)鐘設(shè)置相關(guān)函數(shù) 和 數(shù)據(jù)傳輸相關(guān)函數(shù)。 文件spi/spi.h?中
struct?spi_master?{ ????struct?device?dev; ???? ????struct?list_head?list; ???? ????/*?other?than?negative?(==?assign?one?dynamically),?bus_num?is?fully ????*?board-specific.??usually?that?simplifies?to?being?SOC-specific. ????*?example:??one?SOC?has?three?SPI?controllers,?numbered?0..2, ????*?and?one?board's?schematics?might?show?it?using?SPI-2.??software ????*?would?normally?use?bus_num=2?for?that?controller. ????*/ ????s16????????????bus_num; ???? ????/*?chipselects?will?be?integral?to?many?controllers;?some?others ????*?might?use?board-specific?GPIOs. ????*/ ????u16????????????num_chipselect; ???? ????/*?some?SPI?controllers?pose?alignment?requirements?on?DMAable ????*?buffers;?let?protocol?drivers?know?about?these?requirements. ????*/ ????u16????????????dma_alignment; ???? ????/*?spi_device.mode?flags?understood?by?this?controller?driver?*/ ????u16????????????mode_bits; ???? ????/*?other?constraints?relevant?to?this?driver?*/ ????u16????????????flags; ????#define?SPI_MASTER_HALF_DUPLEX????BIT(0)????????/*?can't?do?full?duplex?*/ ????#define?SPI_MASTER_NO_RX????BIT(1)????????/*?can't?do?buffer?read?*/ ????#define?SPI_MASTER_NO_TX????BIT(2)????????/*?can't?do?buffer?write?*/ ???? ????/*?lock?and?mutex?for?SPI?bus?locking?*/ ????spinlock_t?bus_lock_spinlock; ????struct?mutex?bus_lock_mutex; ???? ????/*?flag?indicating?that?the?SPI?bus?is?locked?for?exclusive?use?*/ ????bool?bus_lock_flag; ???? ????/*?Setup?mode?and?clock,?etc?(spi?driver?may?call?many?times). ????* ????*?IMPORTANT:??this?may?be?called?when?transfers?to?another ????*?device?are?active.??DO?NOT?UPDATE?SHARED?REGISTERS?in?ways ????*?which?could?break?those?transfers. ????*/ ????int?(*setup)(struct?spi_device?*spi); ???? ????/*?bidirectional?bulk?transfers ????* ????*?+?The?transfer()?method?may?not?sleep;?its?main?role?is ????*???just?to?add?the?message?to?the?queue. ????*?+?For?now?there's?no?remove-from-queue?operation,?or ????*???any?other?request?management ????*?+?To?a?given?spi_device,?message?queueing?is?pure?fifo ????* ????*?+?The?master's?main?job?is?to?process?its?message?queue, ????*???selecting?a?chip?then?transferring?data ????*?+?If?there?are?multiple?spi_device?children,?the?i/o?queue ????*???arbitration?algorithm?is?unspecified?(round?robin,?fifo, ????*???priority,?reservations,?preemption,?etc) ????* ????*?+?Chipselect?stays?active?during?the?entire?message ????*???(unless?modified?by?spi_transfer.cs_change?!=?0). ????*?+?The?message?transfers?use?clock?and?SPI?mode?parameters ????*???previously?established?by?setup()?for?this?device ????*/ ????int?(*transfer)(struct?spi_device?*spi, ????????????struct?spi_message?*mesg); ???? ????/*?called?on?release()?to?free?memory?provided?by?spi_master?*/ ????void?(*cleanup)(struct?spi_device?*spi); };分配、注冊(cè)和注銷(xiāo) SPI 主機(jī)的 API 由 SPI 核心提供:文件?drivers/spi/spi.c
struct?spi_master?*spi_alloc_master(struct?device?*dev,?unsigned?size); int?spi_register_master(struct?spi_master?*master); void?spi_unregister_master(struct?spi_master?*master);SPI 主機(jī)控制器驅(qū)動(dòng)主體是實(shí)現(xiàn)了?spi_master?的 transfer()、setup() 這樣的成員函數(shù)。也可能實(shí)現(xiàn)?spi_bitbang?的 txrx_buf()、setup_transfer()、chipselect() 這樣的成員函數(shù)。
?
例如在文件?driver/spi/spi_s3c24xx.c?中:
?
static?int?__init?s3c24xx_spi_probe(struct?platform_device?*pdev) { ????struct?s3c2410_spi_info?*pdata; ????struct?s3c24xx_spi?*hw; ????struct?spi_master?*master; ????struct?resource?*res; ????...... ????/*?initialise?fiq?handler?*/ ???? ????s3c24xx_spi_initfiq(hw); ???? ????/*?setup?the?master?state.?*/ ???? ????/*?the?spi->mode?bits?understood?by?this?driver:?*/ ????master->mode_bits?=?SPI_CPOL?|?SPI_CPHA?|?SPI_CS_HIGH;????//設(shè)置模式 ???? ????master->num_chipselect?=?hw->pdata->num_cs;???????????????//設(shè)置片選序號(hào) ????master->bus_num?=?pdata->bus_num;??????????????????????//主機(jī)控制器的序號(hào) ???? ????/*?setup?the?state?for?the?bitbang?driver?*/ ???? ????hw->bitbang.master?????????=?hw->master; ????hw->bitbang.setup_transfer?=?s3c24xx_spi_setupxfer; ????hw->bitbang.chipselect?????=?s3c24xx_spi_chipsel; ????hw->bitbang.txrx_bufs??????=?s3c24xx_spi_txrx; ???? ????hw->master->setup??=?s3c24xx_spi_setup; ????hw->master->cleanup?=?s3c24xx_spi_cleanup; ????...... }
?
4.2.2 紐帶
......
4.2.3 SPI 外設(shè)驅(qū)動(dòng)
在 Linux 中,通過(guò) spi_driver 結(jié)構(gòu)體來(lái)描述一個(gè) SPI 外設(shè)驅(qū)動(dòng),這個(gè)外設(shè)驅(qū)動(dòng)可以認(rèn)為是 spi_mater 的客戶(hù)端驅(qū)動(dòng)。SPI 只是一種總線,spi_driver 的作用只是將 SPI 外設(shè)掛接在該總線上,因此在 spi_driver 的 probe() 成員函數(shù)中,將注冊(cè) SPI 外設(shè)本身所屬設(shè)備驅(qū)動(dòng)的類(lèi)型。 文件?spi/spi.h?中:
struct?spi_driver?{ ????const?struct?spi_device_id?*id_table; ????int????(*probe)(struct?spi_device?*spi); ????int????(*remove)(struct?spi_device?*spi); ????void?(*shutdown)(struct?spi_device?*spi); ????int????(*suspend)(struct?spi_device?*spi,?pm_message_t?mesg); ????int????(*resume)(struct?spi_device?*spi); ????struct?device_driver?driver; }; static?int?spi_drv_probe(struct?device?*dev) { ????const?struct?spi_driver?*sdrv?=?to_spi_driver(dev->driver); ????return?sdrv->probe(to_spi_device(dev)); } int?spi_register_driver(struct?spi_driver?*sdrv) { ????sdrv->driver.bus?=?&spi_bus_type; ????if?(sdrv->probe) ????????sdrv->driver.probe?=?spi_drv_probe; ????if?(sdrv->remove) ????????sdrv->driver.remove?=?spi_drv_remove; ????if?(sdrv->shutdown) ????????sdrv->driver.shutdown?=?spi_drv_shutdown; ????return?driver_register(&sdrv->driver); }可看出,spi_driver 結(jié)構(gòu)體 和 platform_driver 結(jié)構(gòu)體有極大的相似性,都有 prob()、remove()、suspend()、resume()這樣的接口和 device_driver 的實(shí)例。(這幾乎是一切客戶(hù)端驅(qū)動(dòng)的常用模板)
?
在SPI 外設(shè)驅(qū)動(dòng)中(文件?spi/spi.h?與?driver/spi/spi.c?):
1.?spi_tansfer?結(jié)構(gòu)體:通過(guò) SPI 總線進(jìn)行數(shù)據(jù)傳輸?shù)慕涌凇?2.?spi_message?結(jié)構(gòu)體:組織一個(gè)或多個(gè)spi_transfer,從而完成一次完整的 SPI 傳輸流程。 3. 初始化?spi_message:
static?inline?void?spi_message_init(struct?spi_message?*m);4. 將?spi_transfer?添加到?spi_message?隊(duì)列:
spi_message_add_tail(struct?spi_transfer?*t,?struct?spi_message?*m);5.?spi_message?的同步傳輸 API,阻塞等待這個(gè)消息被處理完:
spi_sync(struct?spi_device?*spi,?struct?spi_message?*message);6.?spi_message?的異步傳輸 API,不會(huì)阻塞等待這個(gè)消息被處理完,但可在 spi_message 的?complete?字段掛接一個(gè)回調(diào)函數(shù),當(dāng)消息被處理完成后,該函數(shù)會(huì)被調(diào)用:
spi_async(struct?spi_device?*spi,?struct?spi_message?*message);7. 初始化?spi_transfer、spi_message?并進(jìn)行 SPI 數(shù)據(jù)傳輸?shù)睦樱瑫r(shí)?spi_write()?、?spi_read()?也是SPI 核心層的兩個(gè)通用API,在外設(shè)驅(qū)動(dòng)中可直接調(diào)用進(jìn)行簡(jiǎn)單的純寫(xiě)、純讀操作:
static?inline?int spi_write(struct?spi_device?*spi,?const?void?*buf,?size_t?len) { ????struct?spi_transfer?t?=?{ ????????????.tx_buf?=?buf, ????????????.len????=?len, ????????}; ????struct?spi_message?m; ???? ????spi_message_init(&m); ????spi_message_add_tail(&t,?&m); ????return?spi_sync(spi,?&m); } static?inline?int spi_read(struct?spi_device?*spi,?void?*buf,?size_t?len) { ????struct?spi_transfer????t?=?{ ????????.rx_buf????=?buf, ????????.len????=?len, ????}; ????struct?spi_message????m; ???? ????spi_message_init(&m); ????spi_message_add_tail(&t,?&m); ????return?spi_sync(spi,?&m); }
?
4.2.4 SPI 板級(jí)邏輯
通 platform_driver 對(duì)應(yīng)著一個(gè)platform_device一樣,spi_driver 也對(duì)應(yīng)著一個(gè) spi_device;platform_device 需要在 BSP 的板文件中添加板信息數(shù)據(jù),同樣的 spi_device 也需要。 spi_device 的板信息用?spi_board_info?結(jié)構(gòu)體描述,該結(jié)構(gòu)體記錄著 SPI 外設(shè)使用的主機(jī)控制器序號(hào)、片選序號(hào)、數(shù)據(jù)比特率、SPI 傳輸模式等。?? 兩種方式添加板級(jí)信息: 1. 與 platfrom_add_devices 添加 platform_device 類(lèi)似,通過(guò)?spi_register_board_info()?在 Linux 啟動(dòng)過(guò)程中的 機(jī)器?init_machine()?函數(shù)中進(jìn)行注冊(cè): 在文件?arch/arm/mach-exynos/mach-itop4412.c?中:
static?struct?spi_board_info?spi_board_info[]?__initdata?=?{ ????{ ????????.modalias????=?"lms501kf03", ????????.platform_data????=?NULL, ????????.max_speed_hz????=?1200000, ????????.bus_num????=?LCD_BUS_NUM, ????????.chip_select????=?0, ????????.mode????????=?SPI_MODE_3, ????????.controller_data?=?(void?*)DISPLAY_CS, ????} }; spi_register_board_info(spi_board_info,?ARRAY_SIZE(spi_board_info));2. 在 ARM Linux 3.x 之后的內(nèi)核在改為設(shè)備樹(shù)后,不再需要正在?arch/arm/mach-xxx?中編碼 SPI 的板級(jí)信息了,而傾向于在 SPI 控制器節(jié)點(diǎn)下填寫(xiě)子節(jié)點(diǎn)。
?
審核編輯:湯梓紅
評(píng)論
查看更多