通过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区域.
这样既实现了设备模型的统一管理,又实现了保持不同设备的信息与方法的灵活性。