从Linux2.6
开始Linux加入了一套驱动管理和注册机制—platform
总线驱动模型。platform
总线是一条虚拟总线(只有一条),这类总线没有对应的硬件结构。platform_device
为相应的设备,platform_driver
为相应的驱动。与传统的bus/device/driver
机制相比,platform
由内核统一进行管理,提高了代码的可移植性和安全性。
所谓的platform_device
并不是与字符设备、块设备和网络设备并列的概念,而是Linux系统提供的一种附加手段。Linux总线设备驱动模型的框架如下图所示:
从图中我们可以很清楚的看出Linux platform
总线设备驱动模型的整体架构。在总线设备驱动模型中,需关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。
当向内核注册驱动程序时,要调用platform_driver_register
函数将驱动程序注册到总线,并将其放入所属总线的drv
链表中,注册驱动的时候还会调用所属总线的match
函数寻找该总线上与之匹配的设备,如果找到与之匹配的设备则会调用相应的probe函数将相应的设备和驱动进行绑定,这一匹配过程是由总线自动完成的。
参考:Linux总线、设备、驱动模型_babyzhaoshu521的博客-CSDN博客_linux总线驱动模型
Platform
平台设备驱动模型的作用是将驱动的实现和资源分离,是一个虚拟的总线平台。这其中存在三个成员platform_bus
,platform_device
,platform_driver
。platform_device
和platform_driver
注册不分先后顺序。
platform_bus:由链表实现,不对应实际的物理总线。
platform_device:驱动的资源比如一些 I/O端口,中断号之类的。
platform_driver:驱动的功能实现比如 注册驱动,实现file_operations 等
用于描述驱动的实现。通过platform_driver
的name
成员匹配上device
后probe
函数会被调用,在设备拔出时系统会调用remove
成员做清理工作。
struct platform_driver {
int (*probe)(struct platform_device *); // device和driver的name匹配成功后调用probe函数
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; // id_table->name没有设置时,使用driver->name 进行匹配
const struct platform_device_id *id_table;
};
struct platform_device_id {
char name[PLATFORM_NAME_SIZE]; // 用来和platform_device->name进行匹配
kernel_ulong_t driver_data;
};
/* platform_device注册和卸载函数。 */
int platform_device_register(struct platform_device *pdev);
void platform_device_unregister(struct platform_device *pdev);
/* platform_driver注册和卸载函数。 */
int platform_driver_register(struct platform_driver *drv);
void platform_driver_unregister(struct platform_driver *drv);
/* 获取platform_device中保存的资源 */
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num);
/* 获取platform_device的中断资源 */
int platform_get_irq(struct platform_device *dev, unsigned int num)
/* 用于批量注册平台设备 */
int platform_add_devices(struct platform_device **devs, int num)
这是一个LED灯的驱动,我们就以LED中的platform_driver
为例进行分析:
struct platform_driver led_drv = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "myled",
.of_match_table = of_match_leds, /* 能支持哪些来自于dts的platform_device */
}
};
probe
函数,当驱动与设备匹配成功以后 probe
函数就会执行,非常重要的函数。一般驱动的提供者会编写,如果自己要编写一个全新的驱动,那么 probe
就需要自行实现。
remove
函数,platform_driver
结构体中的 remove
成员变量,当关闭 platform
设备驱动的时候此函数就会执行,以前在驱动卸载 exit
函数里面要做的事情就放到此函数中来。比如,使用 iounmap
释放内存、删除 cdev
,注销设备号等等。
driver
成员,为 device_driver
结构体变量,Linux 内核里面大量使用到了面向对象的思维,device_driver
相当于基类,提供了最基础的驱动框架。plaform_driver
继承了这个基类,然后在此基础上又添加了一些特有的成员变量。
platform
驱动框架如下所示:
/* 设备结构体 */
struct xxx_dev{
struct cdev cdev;
/* 设备结构体其他具体内容 */
};
struct xxx_dev xxxdev; /* 定义个设备结构体变量 */
static int xxx_open(struct inode *inode, struct file *filp)
{
/* 函数具体内容 */
return 0;
}
static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
/* 函数具体内容 */
return 0;
}
/*
* 字符设备驱动操作集
*/
static struct file_operations xxx_fops = {
.owner = THIS_MODULE,
.open = xxx_open,
.write = xxx_write,
};
/*
* platform 驱动的 probe 函数
* 驱动与设备匹配成功以后此函数就会执行
*/
static int xxx_probe(struct platform_device *dev)
{
......
cdev_init(&xxxdev.cdev, &xxx_fops); /* 注册字符设备驱动 */
/* 函数具体内容 */
return 0;
}
static int xxx_remove(struct platform_device *dev)
{
......
cdev_del(&xxxdev.cdev);/* 删除 cdev */
/* 函数具体内容 */
return 0;
}
/* 匹配列表 */
static const struct of_device_id xxx_of_match[] = {
{ .compatible = "xxx-gpio" },
{ /* Sentinel */ }
};
/*
* platform 平台驱动结构体
*/
static struct platform_driver xxx_driver = {
.driver = {
.name = "xxx",
.of_match_table = xxx_of_match,
},
.probe = xxx_probe,
.remove = xxx_remove,
};
/* 驱动模块加载 */
static int __init xxxdriver_init(void)
{
return platform_driver_register(&xxx_driver);
}
/* 驱动模块卸载 */
static void __exit xxxdriver_exit(void)
{
platform_driver_unregister(&xxx_driver);
}
module_init(xxxdriver_init);
module_exit(xxxdriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jamesbin");
第 1~27 行,传统的字符设备驱动,所谓的 platform 驱动并不是独立于字符设备驱动、块设备驱动和网络设备驱动之外的其他种类的驱动。platform 只是为了驱动的分离与分层而提出来的一种框架,其驱动的具体实现还是需要字符设备驱动、块设备驱动或网络设备驱动。
第 33~39 行,xxx_probe 函数,当驱动和设备匹配成功以后此函数就会执行,以前在驱动入口 init 函数里面编写的字符设备驱动程序就全部放到此 probe 函数里面。比如注册字符设备驱动、添加 cdev、创建类等等。
第 41~47 行,xxx_remove 函数,platform_driver 结构体中的 remove 成员变量,当关闭 platform设备驱动的时候此函数就会执行,以前在驱动卸载 exit 函数里面要做的事情就放到此函数中来。比如,使用 iounmap 释放内存、删除 cdev,注销设备号等等。
第 50~53 行,xxx_of_match 匹配表,如果使用设备树的话将通过此匹配表进行驱动和设备的匹配。 第 51 行设置了一个匹配项,此匹配项的 compatible 值为“xxx-gpio”,因此当设备树中设备节点的 compatible 属性值为“xxx-gpio”的时候此设备就会与此驱动匹配。
第 52 行是一个标记,of_device_id 表最后一个匹配项必须是空的。
第 58 ~ 65 行,定义一个 platform_driver 结构体变量 xxx_driver,表示 platform 驱动,第 59~62行设置 paltform_driver 中的 device_driver 成员变量的 name 和 of_match_table 这两个属性。其中name 属性用于传统的驱动与设备匹配,也就是检查驱动和设备的 name 字段是不是相同。of_match_table 属性就是用于设备树下的驱动与设备检查。对于一个完整的驱动程序,必须提供有设备树和无设备树两种匹配方法。
第63 和 64 这两行设置 probe 和 remove 这两成员变量。
第68~71行,驱动入口函数,调用platform_driver_register函数向Linux内核注册一个platform驱动,也就是上面定义的 xxx_driver 结构体变量。
第 74~77 行,驱动出口函数,调用 platform_driver_unregister 函数卸载前面注册的 platform驱动。
1)定义一个 platform_driver 结构体变量。
2)实现probe函数。
3)实现remove函数。
4)实现of_match_table。
5)调用platform_driver_register 函数向 Linux 内核注册一个 platform 驱动。
6)调用platform_driver_unregister 函数卸载 platform 驱动。
platform_device
的注册过程可以简化为以下过程:
struct platform_device *pdev; // 定义一个平台设备并初始化
platform_device_register(pdev) // 注册
->>platform_device_add(pdev)
->>device_add(&pdev->dev)
->>bus_probe_device(&pdev->dev)
->>device_attach(&pdev->dev)
->>bus_for_each_drv(&(pdev->dev)->bus, NULL, &pdev->dev, __device_attach)
->>__device_attach(drv, &pdev->dev)
->>driver_probe_device(drv, &pdev->dev)
->>really_probe(&pdev->dev, drv)
->>&pdev->dev->bus->probe(dev) 或 drv->probe(dev)
到此Linux内核的总线设备驱动模型分析完毕。从上面的分析过程可以看出,所谓的platform_device
并不是与字符设备、块设备和网络设备并列的概念,而是Linux系统提供的一种附加手段。