通過Platform機制開發發底層驅動的大致流程為: 定義 platform_device---注冊 platform_device ---定義 platform_driver-----注冊 platform_driver。
1. Platform_device 定義於 kernel/include/linux/platform_device.h中,
struct platform_device {
const char * name;
u32 id;
struct device dev;
u32 num_resources;
struct resource * resource;
};
定義一個platform_device一般需要初始化兩個方面的內容:設備占用的資源resource和設備私有數據dev.platform_data。最重要的是resource
設備占用的資源主要是兩個方面:IO內存和irq資源。
Resource定義於kernel/include/linux/ioport.h中,
struct resource {
const char *name;
unsigned long start, end;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
實際上是對地址范圍及其屬性的一個描述。最后幾個用於樹型結構的指針是內核用於管理所有資源的。
而platform_data則是設置給struct device dev;中的platform_data指針(void *)。這個指針內核並不使用,而是驅動自身來定義及使用。
比如說對於DM9000,對應的platform_data定義於include/linux/dm9000.H中。
struct dm9000_plat_data {
unsigned int flags;
void (*inblk)(void __iomem *reg, void *data, int len);
void (*outblk)(void __iomem *reg, void *data, int len);
void (*dumpblk)(void __iomem *reg, int len);
};
OK,初始化完資源和platform_data,一個平台設備就定義好了。把這個平台設備變量的地址添加到資源列表中去。比如在2410平台:
在arm/arm/mach-s3c2410/mach-smdk2410.c把設備地址添加到*smdk2410_devices[] __initdata 數組中去。
最后在arch/arm/mach-3sc2410/cpu.c 中初始化函數__init s3c_arch_init(void)會對smdk2410_devices[]每一個設備的指針ptr調用platform_device_register(ptr)。主要是建立device的層次結構(建立sysfs入口),將設備占用的資源添加到內核資源管理。接下來看看platform_driver:
2. platform_driver結構定義於include/linux/platform_device.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;
};
它內部封裝了一個device_driver,更有意思的是其它的全是函數,並且這些函數名與device_driver中提供的一樣,只是參數由device * 變成了 platform_device * 。
驅動應該實現platform_driver中的這些操作,而內嵌的device_driver中的對應函數則在注冊時被指定為內核指定的操作,這些指定操作只是把調用參數轉換成platform_driver和platform_device來調用platform_driver提供的操作而已。 好像有點亂。。不過代碼可以解釋一切:
平台驅動注冊:
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type;
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
if (drv->suspend)
drv->driver.suspend = platform_drv_suspend;
if (drv->resume)
drv->driver.resume = platform_drv_resume;
return driver_register(&drv->driver);
}
OK,如果device_driver的方法沒有定義就會變成對應的platform_drv_*方法。
來看看其中的一個的實現:比如 platform_drv_probe
static int platform_drv_probe(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
return drv->probe(dev);
}
事情很清楚,先把設備的device_driver轉成platform_driver,同樣轉換device為platform_device。然后去調用platform_driver提供的函數。類型轉換當然是通過container_of()宏實現的。
因此,驅動只需要實現platform_driver中的方法。然后注冊即可。
關於注冊,由上面的代碼可知,最終也是通過 driver_register(&drv->driver);來做的。
3.更深入的小窺一下平台設備與平台驅動的注冊:
根據LDD3中指出的設備模型,一個設備和驅動必然屬於某一個總線。Platform_device和platform-driver在層次上隸屬於叫platform_bus_type的總線類型。OK,平台驅動注冊的時候(平台設備必須先於驅動注冊)將引用它所屬總線的匹配函數去決定總線上每一個設備是否屬於自己。然后二者建立聯系:設備的驅動指針指向該驅動,驅動的設備列表中加入匹配的設備。
當然,這是在設備和驅動這一層面來說的,更深入一層,kobjects和ksets建立層次關系,建立sysfs入口等等。。
注意,platform_bus_type的匹配函數只是比較一下driver和device的name是否相同。因此,同一設備的platform_device和platform_driver的name應該設為相同的。見platform_bus_type匹配函數定義:
static int platform_match(struct device * dev, struct device_driver * drv)
{
struct platform_device *pdev = container_of(dev, struct platform_device, dev);
return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}
因此,dm9000的platform_device和platform_driver的name都為"Dm9000"。
4.下面一個問題:資源怎么用??Platform_data一般怎么用?
資源描述的是設備占用的IO內存,IO端口,及中斷線。
Dm9000驅動中是這樣使用的。這符合慣例:
在probe中獲取資源,並且申請資源,最后映射到內核空間,把映射結果保存起來。
在net_device中的open函數里,注冊中斷處理函數。
Platform_data的使用極為靈活,首先platform_data結構不同設備之間沒有定論,一般可用來保存特定於設備的一些配置,操作等。比如對於DM9000,可以存在按字節,按字訪問的不同模式,因此其platform_data定義成這樣:
struct dm9000_plat_data {
unsigned int flags;
void (*inblk)(void __iomem *reg, void *data, int len);
void (*outblk)(void __iomem *reg, void *data, int len);
void (*dumpblk)(void __iomem *reg, int len);
};
其中flags是8/16位模式的選擇標志,下面三個是在該模式下的IO存取函數。
然后Dm9000驅動只使用了它的flags標志,其余的並不使用。
因為對於網絡net_device,有一個叫着private_data的指針,在分配一個net_device的時候可以讓內核為其開辟指定大小的內存。這部分內存可以通過net_device訪問,而且內容也是驅動開發者自定義的。在DM9000的驅動中,net_devict的private_data使用了一個叫board_info的結構體來包括更多設備相關的信息和操作。
dm9000_plat_data提供的內容也被包括進board_info。因此驅動只使用了初始時設置的flags,除此外dm9000_plat_data中的方法沒有使用的必要。
從中得到的啟示:
Device 包含一個platform_data。
Net_device則包含一個private區域.
這樣既實現了設備模型的統一管理,又實現了保持不同設備的信息與方法的靈活性。
