嵌入式Linux之platform设备驱动框架详解
前言
在platform设备驱动模型中,需关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。在系统中每注册一个设备的时候,会寻找与之匹配的驱动;相同的,在系统中每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。
设备
设备注册部分的流程图如下图所示:
定义一个platform_device
结构体
static struct platform_device led_dev = {
.name = "myled", //#1
.id = -1, //#2
.num_resources = ARRAY_SIZE(led_resource), //#3
.resource = led_resource, //#4
.dev = { //#5
.release = led_release,
},
};
复制代码
- #1: 设备的名字,一般通过名字与相应的驱动进行匹配
- #2: 表示设备编号,-1表示只有一个这样的设备。
- #3和#4: 设备能提供的资源信息和资源的个数
- #5: 这个就是要注册进内核的设备
定义resource
结构体数组
static struct resource led_resource[] = {
[0] = { //#1
.start = 0x56000050, //#2
.end = 0x56000050 + 8 - 1, //#3
.flags = IORESOURCE_MEM, //#4
},
[1] = { //#5
.start = 5,
.end = 5,
.flags = IORESOURCE_IRQ,
}
};
复制代码
- #1: 该资源表示硬件资源的寄存器地址,在本例中表示io配置寄存器和数据寄存器地址
- #2和#3: 资源的起始地址和终止地址
- #4: 表示资源的类型,这里表示该资源是内存地址,
- #5: 该资源是可以操作的数据寄存器的哪一位,可以喝
#1
配合定位到具体的io口。
platform_device_register函数将设备挂到总线
static int led_dev_init(void)
{
platform_device_register(&led_dev); //#1
return 0;
}
int platform_device_register(struct platform_device * pdev)
{
device_initialize(&pdev->dev); //#2
return platform_device_add(pdev); //#3
}
复制代码
- #1: 该函数在
init
中调用 - #2: 首先进行dev的初始化
#3
函数主要内容如下:
int platform_device_add(struct platform_device *pdev)
{
pdev->dev.bus = &platform_bus_type; //#1
...insert_resource(...)... //#2
ret = device_add(&pdev->dev); //#3
}
int device_add(struct device *dev)
{
bus_attach_device(dev); //#4
}
void bus_attach_device(struct device * dev)
{
ret = device_attach(dev); //#5
}
int device_attach(struct device * dev)
{
//#6
ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
}
复制代码
- #1: 将device挂到bus上
- #2:将资源提交给内核
- #3 ~ #6: 完成device与bus上的驱动一一匹配
- #6: 其中的
__device_attach
函数,就是匹配成功后执行的操作,该函数内容在driver部分介绍。
驱动
drvier
部分的流程如下:
定义platform_driver
结构体
struct platform_driver led_drv = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "myled",
}
};
复制代码
定义probe
函数和remove
函数
这两个函数的内容类似于字符设备驱动中的init
函数和exit
函数,前者就是把通过设备层上传的资源进行端口初始化和生成节点,后者则是卸载掉这些初始化的结点和资源。
掉用platform_driver_register
将结构体挂到总线
static int led_drv_init(void)
{
platform_driver_register(&led_drv); //#1
return 0;
}
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type; //#2
driver_register(&drv->driver); //#3
}
复制代码
- #1:驱动装载时执行该函数
- #2:指定要挂载的总线
#3
函数内容如下:
int driver_register(struct device_driver * drv)
{
bus_add_driver(drv); //#1
}
int bus_add_driver(struct device_driver *drv)
{
error = driver_attach(drv); //#2
}
int driver_attach(struct device_driver * drv)
{
//#3
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
复制代码
- #1和#2: 函数依次执行最后到执行
#3
- #3:当检测到设备和驱动匹配后,执行
__driver_attach
函数,这个和设备层的过程一样。
__device_attach
函数的大致内容如下。
static int __driver_attach(struct device * dev, void * data)
{
driver_probe_device(drv, dev); //#1
}
int driver_probe_device(struct device_driver * drv, struct device * dev)
{
ret = really_probe(dev, drv); //#2
}
static int really_probe(struct device *dev, struct device_driver *drv)
{
if (dev->bus->probe) { //#3
ret = dev->bus->probe(dev);
} else if (drv->probe) { //#4
ret = drv->probe(dev);
}
}
复制代码
- #1和#2:依次调用这些函数进入到
driver_probe_device
函数 - #4: 当从总线上能找到
probe
函数时,调用probe()
函数。 - #5: 若从总线上没找到,就执行驱动里的
probe()
函数。一般情况下都是都是调用自己驱动里的probe
函数,只有在极少数的总线中,才会调用总线的probe
函数。 - 这段代码在不管从设备的初始化还是驱动的初始化都可以进行
probe
函数的调用。谁后初始化,谁调用probe
函数。
总线
总线是内核已经初始化好的,我们只需要用就行,它只起到连接设备和驱动的作用。分离分层模型只是一种概念,一种方法,可以用这用方法把任何驱动分成设备层、驱动层和总线,输入子系统也是用了这种概念编写的框架。
作者:小鹏不会飞
链接:https://juejin.cn/post/6864914630788153352
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。