大家好,今天分享一篇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ū)ο蟮乃枷耄烧J(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ù)睦?,同時(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)。
?
審核編輯:湯梓紅
電子發(fā)燒友App















評(píng)論