1. 前言
在Linux設(shè)備模型的抽象中,存在著一類稱作“Platform Device”的設(shè)備,內(nèi)核是這樣描述它們的(Documentation/driver-model/platform.txt):
Platform devices are devices that typically appear as autonomous entities in the system. This includes legacy port-based devices and host bridges to peripheral buses, and most controllers integrated into system-on-chip platforms.? What they usually have in common is direct addressing from a CPU bus.? Rarely, a platform_device will be connected through a segment of some other kind of bus; but its registers will still be directly addressable.
概括來(lái)說(shuō),Platform設(shè)備包括:基于端口的設(shè)備(已不推薦使用,保留下來(lái)只為兼容舊設(shè)備,legacy);連接物理總線的橋設(shè)備;集成在SOC平臺(tái)上面的控制器;連接在其它bus上的設(shè)備(很少見(jiàn))。等等。
這些設(shè)備有一個(gè)基本的特征:可以通過(guò)CPU bus直接尋址(例如在嵌入式系統(tǒng)常見(jiàn)的“寄存器”)。因此,由于這個(gè)共性,內(nèi)核在設(shè)備模型的基礎(chǔ)上(device和device_driver),對(duì)這些設(shè)備進(jìn)行了更進(jìn)一步的封裝,抽象出paltform bus、platform device和platform driver,以便驅(qū)動(dòng)開(kāi)發(fā)人員可以方便的開(kāi)發(fā)這類設(shè)備的驅(qū)動(dòng)。
可以說(shuō),paltform設(shè)備對(duì)Linux驅(qū)動(dòng)工程師是非常重要的,因?yàn)槲覀兙帉?xiě)的大多數(shù)設(shè)備驅(qū)動(dòng),都是為了驅(qū)動(dòng)plaftom設(shè)備。本文我們就來(lái)看看Platform設(shè)備在內(nèi)核中的實(shí)現(xiàn)。
2. Platform模塊的軟件架構(gòu)
內(nèi)核中Platform設(shè)備有關(guān)的實(shí)現(xiàn)位于include/linux/platform_device.h和drivers/base/platform.c兩個(gè)文件中,它的軟件架構(gòu)如下:
由圖片可知,Platform設(shè)備在內(nèi)核中的實(shí)現(xiàn)主要包括三個(gè)部分:
Platform Bus,基于底層bus模塊,抽象出一個(gè)虛擬的Platform bus,用于掛載Platform設(shè)備;?
Platform Device,基于底層device模塊,抽象出Platform Device,用于表示Platform設(shè)備;?
Platform Driver,基于底層device_driver模塊,抽象出Platform Driver,用于驅(qū)動(dòng)Platform設(shè)備。
其中Platform Device和Platform Driver會(huì)會(huì)其它Driver提供封裝好的API,具體可參考后面的描述。
3. Platform模塊向其它模塊提供的API匯整
Platform提供的接口包括:Platform Device和Platform Driver兩個(gè)數(shù)據(jù)結(jié)構(gòu),以及它們的操作函數(shù)。
3.1 數(shù)據(jù)結(jié)構(gòu)
1. 用于抽象Platform設(shè)備的數(shù)據(jù)結(jié)構(gòu)----“struct platform_device”:
1: /* include/linux/platform_device.h, line 22 */
2: struct platform_device {
3: const char *name;
4: int id;
5: bool id_auto;
6: struct device dev;
7: u32 num_resources;
8: struct resource *resource;
9:
10: const struct platform_device_id *id_entry;
11:
12: /* MFD cell pointer */
13: struct mfd_cell *mfd_cell;
14:
15: /* arch specific additions */
16: struct pdev_archdata archdata;
17: };
該結(jié)構(gòu)的解釋如下:
dev,真正的設(shè)備(Platform設(shè)備只是一個(gè)特殊的設(shè)備,因此其核心邏輯還是由底層的模塊實(shí)現(xiàn))。
name,設(shè)備的名稱,和struct device結(jié)構(gòu)中的init_name("Linux設(shè)備模型(5)_device和device driver”)意義相同。實(shí)際上,該名稱在設(shè)備注冊(cè)時(shí),會(huì)拷貝到dev.init_name中。
id,用于標(biāo)識(shí)該設(shè)備的ID。?
在“Linux設(shè)備模型(6)_Bus”中有提過(guò),內(nèi)核允許存在多個(gè)名稱相同的設(shè)備。而設(shè)備驅(qū)動(dòng)的probe,依賴于名稱,Linux采取的策略是:在bus的設(shè)備鏈表中查找device,和對(duì)應(yīng)的device_driver比對(duì)name,如果相同,則查看該設(shè)備是否已經(jīng)綁定了driver(查看其dev->driver指針是否為空),如果已綁定,則不會(huì)執(zhí)行probe動(dòng)作,如果沒(méi)有綁定,則以該device的指針為參數(shù),調(diào)用driver的probe接口。?
因此,在driver的probe接口中,通過(guò)判斷設(shè)備的ID,可以知道此次驅(qū)動(dòng)的設(shè)備是哪個(gè)。
id_auto,指示在注冊(cè)設(shè)備時(shí),是否自動(dòng)賦予ID值(不需要人為指定啦,可以懶一點(diǎn)啦)。
num_resources、resource,該設(shè)備的資源描述,由struct resource(include/linux/ioport.h)結(jié)構(gòu)抽象。?
在Linux中,系統(tǒng)資源包括I/O、Memory、Register、IRQ、DMA、Bus等多種類型。這些資源大多具有獨(dú)占性,不允許多個(gè)設(shè)備同時(shí)使用,因此Linux內(nèi)核提供了一些API,用于分配、管理這些資源。?
當(dāng)某個(gè)設(shè)備需要使用某些資源時(shí),只需利用struct resource組織這些資源(如名稱、類型、起始、結(jié)束地址等),并保存在該設(shè)備的resource指針中即可。然后在設(shè)備probe時(shí),設(shè)備需求會(huì)調(diào)用資源管理接口,分配、使用這些資源。而內(nèi)核的資源管理邏輯,可以判斷這些資源是否已被使用、是否可被使用等等。
id_entry,和內(nèi)核模塊相關(guān)的內(nèi)容,暫不說(shuō)明。
mfd_cell,和MFD設(shè)備相關(guān)的內(nèi)容,暫不說(shuō)明。
archdata,一個(gè)奇葩的存在?。∷哪康氖菫榱吮4嬉恍゛rchitecture相關(guān)的數(shù)據(jù),去看看arch/arm/include/asm/device.h中struct pdev_archdata結(jié)構(gòu)的定義,就知道這種放縱的設(shè)計(jì)有多么垃圾了。不管它了!!??
2. 用于抽象Platform設(shè)備驅(qū)動(dòng)的數(shù)據(jù)結(jié)構(gòu)----“struct platform_driver”:
1: /* include/linux/platform_device.h, line 173 */
2: struct platform_driver {
3: int (*probe)(struct platform_device *);
4: int (*remove)(struct platform_device *);
5: void (*shutdown)(struct platform_device *);
6: int (*suspend)(struct platform_device *, pm_message_t state);
7: int (*resume)(struct platform_device *);
8: struct device_driver driver;
9: const struct platform_device_id *id_table;
10: };
struct platform_driver結(jié)構(gòu)和struct device_driver非常類似,無(wú)非就是提供probe、remove、suspend、resume等回調(diào)函數(shù),這里不再細(xì)說(shuō)。
另外這里有一個(gè)id_table的指針,該指針和"Linux設(shè)備模型(5)_device和device driver”所描述的of_match_table、acpi_match_table的功能類似:提供其它方式的設(shè)備probe。?
我們?cè)?Linux設(shè)備模型(5)_device和device driver”講過(guò),內(nèi)核會(huì)在合適的時(shí)機(jī)檢查device和device_driver的名字,如果匹配,則執(zhí)行probe。其實(shí)除了名稱之外,還有一些寬泛的匹配方式,例如這里提到的各種match table,具體原理就先不羅嗦了,徒添煩惱!就當(dāng)沒(méi)看見(jiàn),呵呵。?
3.2 Platform Device提供的API
Platform Device主要提供設(shè)備的分配、注冊(cè)等接口,供其它driver使用,具體包括:
1: /* include/linux/platform_device.h */
2: extern int platform_device_register(struct platform_device *);
3: extern void platform_device_unregister(struct platform_device *);
4:
5: extern void arch_setup_pdev_archdata(struct platform_device *);
6: extern struct resource *platform_get_resource(struct platform_device *,
7: unsigned int, unsigned int);
8: extern int platform_get_irq(struct platform_device *, unsigned int);
9: extern struct resource *platform_get_resource_byname(struct platform_device *,
10: unsigned int,
11: const char *);
12: extern int platform_get_irq_byname(struct platform_device *, const char *);
13: extern int platform_add_devices(struct platform_device **, int);
14:
15: extern struct platform_device *platform_device_register_full(
16: const struct platform_device_info *pdevinfo);
17:
18: static inline struct platform_device *platform_device_register_resndata(
19: struct device *parent, const char *name, int id,
20: const struct resource *res, unsigned int num,
21: const void *data, size_t size)
22:
23: static inline struct platform_device *platform_device_register_simple(
24: const char *name, int id,
25: const struct resource *res, unsigned int num)
26:
27: static inline struct platform_device *platform_device_register_data(
28: struct device *parent, const char *name, int id,
29: const void *data, size_t size)
30:
31: extern struct platform_device *platform_device_alloc(const char *name, int id);
32: extern int platform_device_add_resources(struct platform_device *pdev,
33: const struct resource *res,
34: unsigned int num);
35: extern int platform_device_add_data(struct platform_device *pdev,
36: const void *data, size_t size);
37: extern int platform_device_add(struct platform_device *pdev);
38: extern void platform_device_del(struct platform_device *pdev);
39: extern void platform_device_put(struct platform_device *pdev);
platform_device_register、platform_device_unregister,Platform設(shè)備的注冊(cè)/注銷接口,和底層的device_register等接口類似。
arch_setup_pdev_archdata,設(shè)置platform_device變量中的archdata指針。
platform_get_resource、platform_get_irq、platform_get_resource_byname、platform_get_irq_byname,通過(guò)這些接口,可以獲取platform_device變量中的resource信息,以及直接獲取IRQ的number等等。
platform_device_register_full、platform_device_register_resndata、platform_device_register_simple、platform_device_register_data,其它形式的設(shè)備注冊(cè)。調(diào)用者只需要提供一些必要的信息,如name、ID、resource等,Platform模塊就會(huì)自動(dòng)分配一個(gè)struct platform_device變量,填充內(nèi)容后,注冊(cè)到內(nèi)核中。
platform_device_alloc,以name和id為參數(shù),動(dòng)態(tài)分配一個(gè)struct platform_device變量。
platform_device_add_resources,向platform device中增加資源描述。
platform_device_add_data,向platform device中添加自定義的數(shù)據(jù)(保存在pdev->dev.platform_data指針中)。
platform_device_add、platform_device_del、platform_device_put,其它操作接口。
3.3 Platform Driver提供的API
Platform Driver提供struct platform_driver的分配、注冊(cè)等功能,具體如下:
1: /* include/linux/platform_device.h */
2:
3: extern int platform_driver_register(struct platform_driver *);
4: extern void platform_driver_unregister(struct platform_driver *);
5:
6: /* non-hotpluggable platform devices may use this so that probe() and
7: * its support may live in __init sections, conserving runtime memory.
8: */
9: extern int platform_driver_probe(struct platform_driver *driver,
10: int (*probe)(struct platform_device *));
11:
12: static inline void *platform_get_drvdata(const struct platform_device *pdev)
13:
14: static inline void platform_set_drvdata(struct platform_device *pdev,
15: void *data)
platform_driver_registe、platform_driver_unregister,platform driver的注冊(cè)、注銷接口。
platform_driver_probe,主動(dòng)執(zhí)行probe動(dòng)作。
platform_set_drvdata、platform_get_drvdata,設(shè)置或者獲取driver保存在device變量中的私有數(shù)據(jù)。
3.4 懶人API
又是注冊(cè)platform device,又是注冊(cè)platform driver,看著挺啰嗦的。不過(guò)內(nèi)核想到了這點(diǎn),所以提供一個(gè)懶人API,可以同時(shí)注冊(cè)platform driver,并分配一個(gè)platform device:
1: extern struct platform_device *platform_create_bundle(
2: struct platform_driver *driver, int (*probe)(struct platform_device *),
3: struct resource *res, unsigned int n_res,
4: const void *data, size_t size);
只要提供一個(gè)platform_driver(要把driver的probe接口顯式的傳入),并告知該設(shè)備占用的資源信息,platform模塊就會(huì)幫忙分配資源,并執(zhí)行probe操作。對(duì)于那些不需要熱拔插的設(shè)備來(lái)說(shuō),這種方式是最省事的了。??
3.5 Early platform device/driver
內(nèi)核啟動(dòng)時(shí),要完成一定的初始化操作之后,才會(huì)處理device和driver的注冊(cè)及probe,因此在這之前,常規(guī)的platform設(shè)備是無(wú)法使用的。但是在Linux中,有些設(shè)備需要盡早使用(如在啟動(dòng)過(guò)程中充當(dāng)console輸出的serial設(shè)備),所以platform模塊提供了一種稱作Early platform device/driver的機(jī)制,允許驅(qū)動(dòng)開(kāi)發(fā)人員,在開(kāi)發(fā)驅(qū)動(dòng)時(shí),向內(nèi)核注冊(cè)可在內(nèi)核早期啟動(dòng)過(guò)程中使用的driver。這些機(jī)制提供了如下接口:
1: extern int early_platform_driver_register(struct early_platform_driver *epdrv,
2: char *buf);
3: extern void early_platform_add_devices(struct platform_device **devs, int num);
4:
5: static inline int is_early_platform_device(struct platform_device *pdev)
6: {
7: return !pdev->dev.driver;
8: }
9:
10: extern void early_platform_driver_register_all(char *class_str);
11: extern int early_platform_driver_probe(char *class_str,
12: int nr_probe, int user_only);
13: extern void early_platform_cleanup(void);
early_platform_driver_register,注冊(cè)一個(gè)用于Early device的driver。
early_platform_add_devices,添加一個(gè)Early device。
is_early_platform_device,判斷指定的device是否是Early device。
early_platform_driver_register_all,將指定class的所有driver注冊(cè)為Early device driver。
early_platform_driver_probe,probe指定class的Early device。
early_platform_cleanup,清除所有的Early device/driver。
4. Platform模塊的內(nèi)部動(dòng)作解析
4.1 Platform模塊的初始化
Platform模塊的初始化是由drivers/base/platform.c中platform_bus_init接口完成的,該接口的實(shí)現(xiàn)和動(dòng)作如下:
1: int __init platform_bus_init(void)
2: {
3: int error;
4:
5: early_platform_cleanup();
6:
7: error = device_register(&platform_bus);
8: if (error)
9: return error;
10: error = bus_register(&platform_bus_type);
11: if (error)
12: device_unregister(&platform_bus);
13: return error;
14: }
early_platform_cleanup,清除所有和Early device/driver相關(guān)的代碼。因?yàn)閳?zhí)行到這里的時(shí)候,證明系統(tǒng)已經(jīng)完成了Early階段的啟動(dòng),轉(zhuǎn)而進(jìn)行正常的設(shè)備初始化、啟動(dòng)操作,所以不再需要Early Platform相關(guān)的東西。
device_register,注冊(cè)一個(gè)名稱為platform_bus的設(shè)備,該設(shè)備的定義非常簡(jiǎn)單,只包含init_name(為“platform”)。該步驟會(huì)在sysfs中創(chuàng)建“/sys/devices/platform/”目錄,所有的Platform設(shè)備,都會(huì)包含在此目錄下。
bus_register,注冊(cè)一個(gè)名稱為platform_bus_type的bus,該bus的定義如下:?
struct bus_type platform_bus_type = {?
??????? .name?????????? = "platform",?
??????? .dev_attrs????? = platform_dev_attrs,?
??????? .match????????? = platform_match,?
??????? .uevent???????? = platform_uevent,?
??????? .pm???????????? = &platform_dev_pm_ops,?
};?
該步驟會(huì)在sysfs中創(chuàng)建“/sys/bus/platform/”目錄,同時(shí),結(jié)合“Linux設(shè)備模型(6)_Bus”的描述,會(huì)在“/sys/bus/platform/”目錄下,創(chuàng)建uevent attribute(/sys/bus/platform/uevent)、devices目錄、drivers目錄、drivers_probe和drivers_autoprobe兩個(gè)attribute(/sys/bus/platform/drivers_probe和/sys/bus/platform/drivers_autoprobe)。
4.2 platform device和platform driver的注冊(cè)
結(jié)合第3章的描述,platform device和platform driver的注冊(cè),由platform_device_add和platform_driver_register兩個(gè)接口實(shí)際實(shí)現(xiàn)。其內(nèi)部動(dòng)作分別如下。
platform_device_add的內(nèi)部動(dòng)作:
如果該設(shè)備沒(méi)有指定父設(shè)備,將其父設(shè)備設(shè)置為platform_bus,即“/sys/devices/platform/”所代表的設(shè)備,這時(shí)該設(shè)備的sysfs目錄即為“/sys/devices/platform/xxx_device”。
將該設(shè)備的bus指定為platform_bus_type(pdev->dev.bus = &platform_bus_type)。
根據(jù)設(shè)備的ID,修改或者設(shè)置設(shè)備的名稱。對(duì)于多個(gè)同名的設(shè)備,可以使用ID區(qū)分,在這里將實(shí)際名稱修改為“name.id”的形式。
調(diào)用resource模塊的insert_resource接口,將該設(shè)備需要使用的resource統(tǒng)一管理起來(lái)(我們知道,在這之前,只是聲明了本設(shè)備需要使用哪些resource,但resource模塊并不知情,也就無(wú)從管理,因此需要告知)。
調(diào)用device_add接口,將內(nèi)嵌的struct device變量添加到內(nèi)核中。
platform_driver_register的內(nèi)部動(dòng)作:
將該driver的bus指定為platform_bus_type(drv->driver.bus = &platform_bus_type)。
如果該platform driver提供了probe、remove、shutdown等回調(diào)函數(shù),將該它內(nèi)嵌的struct driver變量的probe、remove、shutdown等指針,設(shè)置為platform模塊提供函數(shù),包括platform_drv_probe、platform_drv_remove和platform_drv_shutdown。因?yàn)閜robe等動(dòng)作會(huì)從struct driver變量開(kāi)始,經(jīng)過(guò)platform_drv_xxx等接口的轉(zhuǎn)接,就可以到達(dá)platform diver自身的回調(diào)函數(shù)中。
調(diào)用driver_register接口,將內(nèi)嵌的struct driver變量添加到內(nèi)核中。
4.3 platform設(shè)備的probe
我們?cè)凇癓inux設(shè)備模型(6)_Bus”中講過(guò),設(shè)備的probe,都發(fā)生在向指定的bus添加device或者device_driver時(shí),由bus模塊的bus_probe_device,或者device_driver模塊driver_attach接口觸發(fā)。這里就不再詳細(xì)描述了。
?
評(píng)論
查看更多