1. 前言
在Linux設備模型的抽象中,存在著一類稱作“Platform Device”的設備,內核是這樣描述它們的(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.
概括來說,Platform設備包括:基于端口的設備(已不推薦使用,保留下來只為兼容舊設備,legacy);連接物理總線的橋設備;集成在SOC平臺上面的控制器;連接在其它bus上的設備(很少見)。等等。
這些設備有一個基本的特征:可以通過CPU bus直接尋址(例如在嵌入式系統常見的“寄存器”)。因此,由于這個共性,內核在設備模型的基礎上(device和device_driver),對這些設備進行了更進一步的封裝,抽象出paltform bus、platform device和platform driver,以便驅動開發人員可以方便的開發這類設備的驅動。
可以說,paltform設備對Linux驅動工程師是非常重要的,因為我們編寫的大多數設備驅動,都是為了驅動plaftom設備。本文我們就來看看Platform設備在內核中的實現。
2. Platform模塊的軟件架構
內核中Platform設備有關的實現位于include/linux/platform_device.h和drivers/base/platform.c兩個文件中,它的軟件架構如下:
由圖片可知,Platform設備在內核中的實現主要包括三個部分:
Platform Bus,基于底層bus模塊,抽象出一個虛擬的Platform bus,用于掛載Platform設備;?
Platform Device,基于底層device模塊,抽象出Platform Device,用于表示Platform設備;?
Platform Driver,基于底層device_driver模塊,抽象出Platform Driver,用于驅動Platform設備。
其中Platform Device和Platform Driver會會其它Driver提供封裝好的API,具體可參考后面的描述。
3. Platform模塊向其它模塊提供的API匯整
Platform提供的接口包括:Platform Device和Platform Driver兩個數據結構,以及它們的操作函數。
3.1 數據結構
1. 用于抽象Platform設備的數據結構----“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: };
該結構的解釋如下:
dev,真正的設備(Platform設備只是一個特殊的設備,因此其核心邏輯還是由底層的模塊實現)。
name,設備的名稱,和struct device結構中的init_name("Linux設備模型(5)_device和device driver”)意義相同。實際上,該名稱在設備注冊時,會拷貝到dev.init_name中。
id,用于標識該設備的ID。?
在“Linux設備模型(6)_Bus”中有提過,內核允許存在多個名稱相同的設備。而設備驅動的probe,依賴于名稱,Linux采取的策略是:在bus的設備鏈表中查找device,和對應的device_driver比對name,如果相同,則查看該設備是否已經綁定了driver(查看其dev->driver指針是否為空),如果已綁定,則不會執行probe動作,如果沒有綁定,則以該device的指針為參數,調用driver的probe接口。?
因此,在driver的probe接口中,通過判斷設備的ID,可以知道此次驅動的設備是哪個。
id_auto,指示在注冊設備時,是否自動賦予ID值(不需要人為指定啦,可以懶一點啦)。
num_resources、resource,該設備的資源描述,由struct resource(include/linux/ioport.h)結構抽象。?
在Linux中,系統資源包括I/O、Memory、Register、IRQ、DMA、Bus等多種類型。這些資源大多具有獨占性,不允許多個設備同時使用,因此Linux內核提供了一些API,用于分配、管理這些資源。?
當某個設備需要使用某些資源時,只需利用struct resource組織這些資源(如名稱、類型、起始、結束地址等),并保存在該設備的resource指針中即可。然后在設備probe時,設備需求會調用資源管理接口,分配、使用這些資源。而內核的資源管理邏輯,可以判斷這些資源是否已被使用、是否可被使用等等。
id_entry,和內核模塊相關的內容,暫不說明。
mfd_cell,和MFD設備相關的內容,暫不說明。
archdata,一個奇葩的存在??!它的目的是為了保存一些architecture相關的數據,去看看arch/arm/include/asm/device.h中struct pdev_archdata結構的定義,就知道這種放縱的設計有多么垃圾了。不管它了??!??
2. 用于抽象Platform設備驅動的數據結構----“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結構和struct device_driver非常類似,無非就是提供probe、remove、suspend、resume等回調函數,這里不再細說。
另外這里有一個id_table的指針,該指針和"Linux設備模型(5)_device和device driver”所描述的of_match_table、acpi_match_table的功能類似:提供其它方式的設備probe。?
我們在"Linux設備模型(5)_device和device driver”講過,內核會在合適的時機檢查device和device_driver的名字,如果匹配,則執行probe。其實除了名稱之外,還有一些寬泛的匹配方式,例如這里提到的各種match table,具體原理就先不羅嗦了,徒添煩惱!就當沒看見,呵呵。?
3.2 Platform Device提供的API
Platform Device主要提供設備的分配、注冊等接口,供其它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設備的注冊/注銷接口,和底層的device_register等接口類似。
arch_setup_pdev_archdata,設置platform_device變量中的archdata指針。
platform_get_resource、platform_get_irq、platform_get_resource_byname、platform_get_irq_byname,通過這些接口,可以獲取platform_device變量中的resource信息,以及直接獲取IRQ的number等等。
platform_device_register_full、platform_device_register_resndata、platform_device_register_simple、platform_device_register_data,其它形式的設備注冊。調用者只需要提供一些必要的信息,如name、ID、resource等,Platform模塊就會自動分配一個struct platform_device變量,填充內容后,注冊到內核中。
platform_device_alloc,以name和id為參數,動態分配一個struct platform_device變量。
platform_device_add_resources,向platform device中增加資源描述。
platform_device_add_data,向platform device中添加自定義的數據(保存在pdev->dev.platform_data指針中)。
platform_device_add、platform_device_del、platform_device_put,其它操作接口。
3.3 Platform Driver提供的API
Platform Driver提供struct platform_driver的分配、注冊等功能,具體如下:
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的注冊、注銷接口。
platform_driver_probe,主動執行probe動作。
platform_set_drvdata、platform_get_drvdata,設置或者獲取driver保存在device變量中的私有數據。
3.4 懶人API
又是注冊platform device,又是注冊platform driver,看著挺啰嗦的。不過內核想到了這點,所以提供一個懶人API,可以同時注冊platform driver,并分配一個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);
只要提供一個platform_driver(要把driver的probe接口顯式的傳入),并告知該設備占用的資源信息,platform模塊就會幫忙分配資源,并執行probe操作。對于那些不需要熱拔插的設備來說,這種方式是最省事的了。??
3.5 Early platform device/driver
內核啟動時,要完成一定的初始化操作之后,才會處理device和driver的注冊及probe,因此在這之前,常規的platform設備是無法使用的。但是在Linux中,有些設備需要盡早使用(如在啟動過程中充當console輸出的serial設備),所以platform模塊提供了一種稱作Early platform device/driver的機制,允許驅動開發人員,在開發驅動時,向內核注冊可在內核早期啟動過程中使用的driver。這些機制提供了如下接口:
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,注冊一個用于Early device的driver。
early_platform_add_devices,添加一個Early device。
is_early_platform_device,判斷指定的device是否是Early device。
early_platform_driver_register_all,將指定class的所有driver注冊為Early device driver。
early_platform_driver_probe,probe指定class的Early device。
early_platform_cleanup,清除所有的Early device/driver。
4. Platform模塊的內部動作解析
4.1 Platform模塊的初始化
Platform模塊的初始化是由drivers/base/platform.c中platform_bus_init接口完成的,該接口的實現和動作如下:
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相關的代碼。因為執行到這里的時候,證明系統已經完成了Early階段的啟動,轉而進行正常的設備初始化、啟動操作,所以不再需要Early Platform相關的東西。
device_register,注冊一個名稱為platform_bus的設備,該設備的定義非常簡單,只包含init_name(為“platform”)。該步驟會在sysfs中創建“/sys/devices/platform/”目錄,所有的Platform設備,都會包含在此目錄下。
bus_register,注冊一個名稱為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,?
};?
該步驟會在sysfs中創建“/sys/bus/platform/”目錄,同時,結合“Linux設備模型(6)_Bus”的描述,會在“/sys/bus/platform/”目錄下,創建uevent attribute(/sys/bus/platform/uevent)、devices目錄、drivers目錄、drivers_probe和drivers_autoprobe兩個attribute(/sys/bus/platform/drivers_probe和/sys/bus/platform/drivers_autoprobe)。
4.2 platform device和platform driver的注冊
結合第3章的描述,platform device和platform driver的注冊,由platform_device_add和platform_driver_register兩個接口實際實現。其內部動作分別如下。
platform_device_add的內部動作:
如果該設備沒有指定父設備,將其父設備設置為platform_bus,即“/sys/devices/platform/”所代表的設備,這時該設備的sysfs目錄即為“/sys/devices/platform/xxx_device”。
將該設備的bus指定為platform_bus_type(pdev->dev.bus = &platform_bus_type)。
根據設備的ID,修改或者設置設備的名稱。對于多個同名的設備,可以使用ID區分,在這里將實際名稱修改為“name.id”的形式。
調用resource模塊的insert_resource接口,將該設備需要使用的resource統一管理起來(我們知道,在這之前,只是聲明了本設備需要使用哪些resource,但resource模塊并不知情,也就無從管理,因此需要告知)。
調用device_add接口,將內嵌的struct device變量添加到內核中。
platform_driver_register的內部動作:
將該driver的bus指定為platform_bus_type(drv->driver.bus = &platform_bus_type)。
如果該platform driver提供了probe、remove、shutdown等回調函數,將該它內嵌的struct driver變量的probe、remove、shutdown等指針,設置為platform模塊提供函數,包括platform_drv_probe、platform_drv_remove和platform_drv_shutdown。因為probe等動作會從struct driver變量開始,經過platform_drv_xxx等接口的轉接,就可以到達platform diver自身的回調函數中。
調用driver_register接口,將內嵌的struct driver變量添加到內核中。
4.3 platform設備的probe
我們在“Linux設備模型(6)_Bus”中講過,設備的probe,都發生在向指定的bus添加device或者device_driver時,由bus模塊的bus_probe_device,或者device_driver模塊driver_attach接口觸發。這里就不再詳細描述了。
?
評論
查看更多