Linux驱动开发13之再论platform平台总线
1.平台总线体系工作流程
第一步:定义初始化platform bus,并注册platform到内核,在/sys目录下出现各种相关设备
第二步:内核移植人员提供platform_devices,定义各种platform_devices,然后注册
第三步:写驱动的人负责提供platform_driver,定义相关的platform_driver,然后注册
第四步:platform的match函数发现driver和device匹配后,调用driver的probe函数来完成驱动的初始化和安装,然后设备就工作起来了
第五步:操作相关的设备
2.第一步:platform本身怎么注册的?什么时候注册的?
答案:系统启动的时候注册的!
我们来看下图:
01020: int __init platform_bus_init(void)
01021: {
01022: int error;
01024: early_platform_cleanup(); //清除platform设备链表
01026: error = device_register(&platform_bus); //该函数把设备名为platform 的设备platform_bus注册到系统中,其他的platform的设备都会以它为parent。它在sysfs中目录下.即 /sys/devices/platform。platform_bus总线也是设备,所以也要进行设备的注册.
//struct device platform_bus = {
//.init_name = "platform",
}
//将平台bus作为一个设备注册,出现在sys文件系统的devices目录
01027: if (error)
01028: return error;
01029: error = bus_register(&platform_bus_type); //接着bus_register(&platform_bus_type)注册了platform_bus_type总线.
/*
struct bus_type platform_bus_type = {
.name = “platform”,
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = PLATFORM_PM_OPS_PTR,
};
*/
//默认platform_bus_type中没有定义probe函数;
//注册平台类型的bus,将出现sys文件系统在bus目录下,创建一个platform的目录,以及相关属性文件
01030: if (error)
01031: device_unregister(&platform_bus); //注册平台类型的bus,将出现sys文件系统在bus目录下,创建一个platform的目录,以及相关属性文件
01032: return error;
01033: }
3.第二步:platform_devices的定义与注册--------驱动工程师进行实例化
3.1定义与开放的接口
struct platform_device {
const char * name; // 平台总线下设备的名字
int id;
struct device dev; // 所有设备通用的属性部分
u32 num_resources; // 设备使用到的resource的个数
struct resource * resource; // 设备使用到的资源数组的首地址
const struct platform_device_id *id_entry; // 设备ID表
/* arch specific additions */
struct pdev_archdata archdata; // 自留地,用来提供扩展性的
};
实例化由内核驱动开发工程师和普通驱动开发工程师添加,主要是添加内核struct i2c_board_info。具体可参考上一章节的再论I2C驱动模型之device。
3.2 platform_device的注册
platform_device_register和其他总线方法的特殊注册方法,但最终还是调用的device_register。
0324: int platform_device_register(struct platform_device *pdev)
00325: {
00326: device_initialize(&pdev->dev);
00327: return platform_device_add(pdev);
00328:}
我们在设备模型的分析中知道了把设备添加到系统要调用device_initialize()和platform_device_add(pdev)函数。
Platform设备的注册分两种方式:
a -- 对于platform设备的初注册,内核源码提供了platform_device_add()函数,输入参数platform_device可以是静态的全局设备,它是进行一系列的操作后调用device_add()将设备注册到相应的总线(platform总线)上,内核代码中platform设备的其他注册函数都是基于这个函数,如platform_device_register()、platform_device_register_simple()、platform_device_register_data()等。
b -- 另外一种机制就是动态申请platform_device_alloc()一个platform_device设备,然后通过platform_device_add_resources及platform_device_add_data等添加相关资源和属性。
无论哪一种platform_device,最终都将通过platform_device_add这册到platform总线上。区别在于第二步:其实platform_device_add()包括device_add(),不过要先注册resources,然后将设备挂接到特定的platform总线。
4.第四步,platform_drver的定义与注册----驱动工程师进行调用
4.1 定于与开放的接口
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; // 所有设备共用的一些属性
const struct platform_device_id *id_table; // 设备ID表
};
4.2 platform_driver的注册
00483: int platform_driver_register(struct
00483: platform_driver *drv)
00484: {
00485: drv->driver.bus = &platform_bus_type;
00486: if (drv->probe)
00487: drv->driver.probe = platform_drv_probe;
00488: if (drv->remove)
00489: drv->driver.remove = platform_drv_remove;
00490: if (drv->shutdown)
00491: drv->driver.shutdown = platform_drv_shutdown;
00492:
00493: return driver_register(&drv->driver);
00494: }
platform_driver_register和其他总线方法的特殊注册方法,但最终还是调用的driver_register。
我们在设备驱动模型的分析中已经知道驱动在注册要调用driver_register(),platform driver的注册函数platform_driver_register()同样也是进行其它的一些初始化后调用driver_register()将驱动注册到platform_bus_type总线上. 然后设定了platform_driver内嵌的driver的probe、remove、shutdown函数。
5.第四步:platform的match函数发现driver和device匹配
00671: static int platform_match(struct device *dev, struct device_driver *drv)
00672: {
00673: struct platform_device *pdev = to_platform_device(dev);
00674: struct platform_driver *pdrv = to_platform_driver(drv);
00675:
00676: /* match against the id table first */
00677: if (pdrv->id_table)
00678: return platform_match_id(pdrv->id_table, pdev)!= NULL;
00680: /* fall-back to driver name match */
00681: return (strcmp(pdev->name, drv->name) == 0);
00682: }
前面提到,实现platform模型的过程就是总线对设备和驱动的匹配过程 。打个比方,就好比相亲,总线是红娘,设备是男方,驱动是女方:
a -- 红娘(总线)负责男方(设备)和女方(驱动)的撮合;
b -- 男方(女方)找到红娘,说我来登记一下,看有没有合适的姑娘(汉子)—— 设备或驱动的注册;
c -- 红娘这时候就需要看看有没有八字(二者的name 字段)匹配的姑娘(汉子)——match 函数进行匹配,看name是否相同;
d -- 如果八字不合,就告诉男方(女方)没有合适的对象,先等着,别急着乱做事 —— 设备和驱动会等待,直到匹配成功;
e -- 终于遇到八字匹配的了,那就结婚呗!接完婚,男方就向女方交代,我有多少存款,我的房子在哪,钱放在哪等等( struct resource *resource),女方说好啊,于是去房子里拿钱,去给男方买菜啦,给自己买衣服、化妆品、首饰啊等等(int (*probe)(struct platform_device *) 匹配成功后驱动执行的第一个函数),当然如果男的跟小三跑了(设备卸载),女方也不会继续待下去的( int (*remove)(struct platform_device *))。
platform_match函数就是平台总线的匹配方法。该函数的工作方法是:如果有id_table就说明驱动可能支持多个设备,所以这时候要去对比id_table中所有的name,只要找到一个相同的就匹配上了不再找了,如果找完id_table都还没找到就说明每匹配上;如果没有id_table或者没匹配上,那就直接对比device和driver的name,如果匹配上就匹配上了,如果还没匹配上那就匹配失败。
总结:
1、从这三个函数的代码可以看到,又找到了相应的platform_driver和platform_device,然后调用platform_driver的probe、remove、shutdown函数。这是一种高明的做法:
在不针对某个驱动具体的probe、remove、shutdown指向的函数,而通过上三个过度函数来找到platform_driver,然后调用probe、remove、shutdown接口。
如果设备和驱动都注册了,就可以通过bus ->match、bus->probe或driver->probe进行设备驱动匹配了。
2、驱动注册的时候platform_driver_register()->driver_register()->bus_add_driver()->driver_attach()->bus_for_each_dev(),
对每个挂在虚拟的platform bus的设备作__driver_attach()->driver_probe_device()->drv->bus->match()==platform_match()->比较strncmp(pdev->name, drv->name, BUS_ID_SIZE),如果相符就调用platform_drv_probe()->driver->probe(),如果probe成功则绑定该设备到该驱动。
6.为什么需要platform驱动,有什么好处?不用platform驱动可以吗?
引入platform模型符合Linux 设备模型 —— 总线、设备、驱动,设备模型中配套的sysfs节点都可以用,方便我们的开发;当然你也可以选择不用,不过就失去了一些platform带来的便利;
设备驱动中引入platform 概念,隔离BSP和驱动。在BSP中定义platform设备和设备使用的资源、设备的具体匹配信息。而在驱动中,只需要通过API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。
5.整个platform总线驱动模型怎么工作的?从系统启动开始