Linux设备模型之platform总线

     以下内容为转载,其中紫色字体部分是自己添加的部分,主要是根据调试LCD驱动的理解。
     platform bus多用在嵌入式SOC环境下, 和标准bus(pci/usb/...)上的设备相比,
集成在soc的设备有一些特殊性: 比如不可以hotplug, 无法自动probe, 有许多相关的

资源(irq/io/memory/...)需要在arch代码中传递过来(而不是可以自动probe到),

如lcdc_nt35510.c中的probe就根据id是否为0,来取得board-msm7x27.c注册的

lcdc_nt35510_panel_device这个设备的platform_data,而此platform_data

就是IO资源及配置。正是这些特殊之处才使得出现了platform device 及其特殊的
一些接口
(platform_device/platform driver )。注册到platform的device和driver的
设备节点分别是:
/sys/bus/platform/devices/<device的name> 
/sys/bus/platform/
drivers/<driver的name>
   

通过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,也有的只初始化platform_data,如board-msm7x27.c中的lcdc_nt35510_panel_device


设备占用的资源主要是两个方面: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 *)这个指针内核并不使用,而是驱动自身来定义及使用

lcdc_nt35510.c中在probe中会取得在board中初始化的platform_data,以对GPIO资源进行配置
比如说对于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;//指定总线类型为platform

if (drv->probe) //如果给probe赋值了

drv->driver.probe = platform_drv_probe;

if (drv->remove)/如果给remove赋值了

drv->driver.remove = platform_drv_remove;

if (drv->shutdown)/如果给 shutdown赋值了

drv->driver.shutdown = platform_drv_shutdown;

if (drv->suspend)/如果给suspend赋值了

drv->driver.suspend = platform_drv_suspend;

if (drv->resume)/如果给resume赋值了

drv->driver.resume = platform_drv_resume;

return driver_register(&drv->driver);

}

OK,如果device_driver的方法没有定义就会变成对应的platform_drv_*方法。
上面这个解释莫名其妙,我认为是错的,应该想表达:如果platform_driver的方法定义了就会变成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);//这里是调用 platform_driver的probe

}

事情很清楚,先把设备的device_driver转成platform_driver,同样转换device为platform_device。然后去调用platform_driver提供的函数。类型转换当然是通过container_of()宏实现的。 

因此,驱动只需要实现platform_driver中的方法,然后注册即可。 

关于注册,由上面的代码可知,最终也是通过 driver_register(&drv->driver)来做的。

上面讲了platform_device和paltform_driver,小结一下:
Linux设备模型中:bus_type、device、device_driver
《Linux设备驱动程序》的linux设备模型章中说到设备模型中,所有设备都通过总线相连。
添加设备devA,必须指定其device结构体的bus_type域,初始化其他域,然后调用device_register(&devA),将设备devA
注册到指定总线。
添加该设备驱动driverA,也必须指定其device_driver结构体的bus_type域,初始化其他域,然后调用driver_register(&driverA),
将该驱动注册到总线上。
如果驱动driverA和设备devA匹配成功,即调用probe函数成功,则建立他们之间的符号链接,即将设备与驱动捆绑起来。
而Linux源代码中大量使用platform_device,
platform_device是从device派生的,platform_driver是从device_driver派生的,
同样添加设备PlatformDevA,初始化platform_device结构体的dev域时,没有初始化其bus_type域,而是在注册函数中指定的(不是在platform_device_register,而是在这个函数调用其他函数,可以跟一下),从而将该设备添加在sys\bus\platform\devices目录下,
同样添加驱动PlatformDrvA,初始化platform_driver结构体的driver域时,也没有初始化其bus_type域,是在注册函数platform_driver_register中直接指定的,从而将该驱动添加在sys\bus\platform\drivers目录下。
platform_device_register和 platform_driver_register的定义位置是linux/drivers/base/platform.c。

 

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"。

 这里需要注意我的理解lcd在board—msm7x27.c中注册了name为lcdc_nt35510_vga,id为0的
而在lcdc_nt35510.c中模块加载时初始化就注册了name为lcdc_nt35510_vga的platform_driver,而后又注册name也 为lcdc_nt35510_vga而id=1的platform_device,所以由于name一致,driver的probe函数会被调用2次,在probe中也确实由根据device的id值来进行判断做不同的处理。
这是否可以理解成多个设备对应同一个驱动?好像是(代码上确实2个platform_device与同一个platform_driver match了)
又好像不是(以前认为是2个物理设备对应同一个驱动,如2个USB插入,对应的是同一个驱动),请知道的达人解惑一下


4.下面一个问题:资源怎么用?Platform_data一般怎么用?

资源描述的是设备占用的IO内存,IO端口(GPIO),及中断(irq)。

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区域.

这样既实现了设备模型的统一管理,又实现了保持不同设备的信息与方法的灵活性。

原文地址:Platform_device和platform_driver

由以上分析可知,设备驱动中引入platform的概念至少有如下2大好处:

1. 使得设备被挂接在一个总线上,因此,符合Linux 2.6的设备模型。其结果是,配套的sysfs结点、设备电源管理都成为可能。

2. 隔离BSP和驱动。在BSP中定义platform设备和设备使用的资源、设备的具体配置信息,而在驱动中,只需要通过通用API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。



你可能感兴趣的:(linux,IO,api,嵌入式,平台,跨平台)