在Linux 2.6以后的设备驱动模型中,需关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。
以并列的方式展示内核相关的设备描述、硬件相关的驱动描述两部分内容。
内核相关的设备描述 | 硬件相关的驱动描述 |
结构体platform_device
排版有问题,凑合看吧 | 结构体platform_driver
|
接口函数: int platform_device_register(struct platform_device *); // 用来注册我们的设备 void platform_device_unregister(struct platform_device *); // 用来卸载我们的设备 | int platform_driver_register(struct platform_driver *); // 用来注册我们的设备驱动 void platform_driver_unregister(struct platform_driver *); // 用来卸载我们的设备驱动 |
为了完成在板文件中添加device,platform设备的工作,需要在板文件arch/arm/mach-<soc名>/mach-<板名>.c)中添加相应的代码,将这个设备添加注册到系统中。并最终通过类似于platform_add_devices()的函数把这个platform_device注册进系统。如果一切顺利,我们会在/sys/devices/platform目录下看对应设备名字的子目录 | platform_driver_register()、platform_driver_unregister()函数进行platform_driver的注册与注销,而原先 注册和注销字符设备的工作已经被移交到platform_driver的probe()和remove()成员函数中。 |
通过device和driver的名字,两者进行匹配。
*****************************************************************************************************************************
当我们注册platform平台设备时进行自动匹配的代码在哪里呢?
platform_device_add
device_add
bus_probe_device // 关键就在这个函数
void bus_probe_device(struct device *dev)
{
struct bus_type *bus = dev->bus; // 获取设备中的总线类型 bus_type
int ret;
if (bus && bus->p->drivers_autoprobe) { // 如果总线存在 并且 设置了自动进行设备与设备驱动匹配标志位
ret = device_attach(dev); // 则调用这个函数进行匹配
WARN_ON(ret < 0);
}
}
int device_attach(struct device *dev)
{
int ret = 0;
device_lock(dev);
if (dev->driver) { // 如果我们的设备早就绑定了设备驱动 那么执行下面的
ret = device_bind_driver(dev);
if (ret == 0)
ret = 1;
else {
dev->driver = NULL;
ret = 0;
}
} else { // 我们就分析这条
pm_runtime_get_noresume(dev);
ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach); // 遍历总线的链表匹配对应的设备驱动
pm_runtime_put_sync(dev);
}
device_unlock(dev);
return ret;
}
// 到这里之后就和上面的其实是一样的了
总结: 所以由此可知,当我们不管是先注册设备还是先注册设备驱动都会进行一次设备与设备驱动的匹配过程,匹配成功之后就会调用probe函数,
匹配的原理就是去遍历总线下的相应的链表来找到挂接在他下面的设备或者设备驱动,所以由此可以看出来,这个东西的设计其实是很美的。
static int platform_match(struct device *dev, struct device_driver *drv) // 总线下的设备与设备驱动的匹配函数
{
struct platform_device *pdev = to_platform_device(dev); // 通过device 变量获取到 platform_device
struct platform_driver *pdrv = to_platform_driver(drv); // 通过 driver 获取 platform_driver
/* match against the id table first */
if (pdrv->id_table) // 如果pdrv中的id_table 表存在
return platform_match_id(pdrv->id_table, pdev) != NULL; // 匹配id_table
/* fall-back to driver name match */ // 第二个就是指直接匹配 pdev->name drv->name 名字是否形同
return (strcmp(pdev->name, drv->name) == 0);
}
static const struct platform_device_id *platform_match_id(
const struct platform_device_id *id,
struct platform_device *pdev)
{
while (id->name[0]) { // 循环去比较id_table数组中的各个id名字是否与pdev->name 相同
if (strcmp(pdev->name, id->name) == 0) {
pdev->id_entry = id; // 将id_table数组中的名字匹配上的 这个数组项 指针赋值给 pdev->id_entry
return id; // 返回这个指针
}
id++;
}
return NULL;
}
总结: 由上面可知platform总线下设备与设备驱动的匹配原理就是通过名字进行匹配的,先去匹配platform_driver中的id_table表中的各个名字与platform_device->name
名字是否相同,如果相同表示匹配成功直接返回,否则直接匹配platform_driver->name与platform_driver->name是否相同,相同则匹配成功,否则失败。
------------------------------------------------------------------------------------------------------------------------------
整体原理分析
首先相对于usb、pci、i2c等物理总线来说,platform总线是虚拟的、抽象出来的,但是用法基类似。
从无到有分4步进行:
(1)第一步:系统启动时在bus系统中注册platform
platform.c文件内int __init platform_bus_init(void)函数负责初始化。通过函数device_register(&platform_bus);注册,平台总线是作为设备注册进系统的
(2)第二步:内核移植的人负责提供platform_device
mach-smdkv210.c中static void __init smdkv210_machine_init(void)向系统添加设备。
platform_add_devices(smdkv210_devices, ARRAY_SIZE(smdkv210_devices));
//形参为
static struct platform_device *smdkv210_devices[] __initdata = {
&s5pv210_device_iis0,
&s5pv210_device_ac97,
&s3c_device_adc,
&s3c_device_ts,
&s3c_device_wdt,
};
//以IIS总线为例
struct platform_device s5pv210_device_iis0 = {
.name = "s3c64xx-iis", //总线名
.id = 0,
.num_resources = ARRAY_SIZE(s5pv210_iis0_resource),
.resource = s5pv210_iis0_resource,
.dev = {
.platform_data = &s3c_i2s_pdata,
},
};
//.platform_data = &s3c_i2s_pdata,这个数据是device结构体里的子项原型为void *platform_data;,是一个无类型的指 针,目的是在这个框架内添加关于设备自定义的内容,可以是一个结构体指针,也可以是一个函数指针。
/**
①如果是结构体指针,这个结构体可用于描述某个平台设备,比如描述一个GPIO的端口号、寄存器地址、操作函数指针等。
②如果是函数指针,可在指定函数内进行对设备的操作
**/
(3)第三步:写驱动的人负责提供platform_driver
比如这个s3c64xx-iis平台总线,搜索这个总线名,即可轻松找到使用次总线的驱动,以下面的内容为例,这个总线用于声音数据的传输。
static struct platform_driver s3c64xx_iis_driver = {
.probe = s3c64xx_iis_dev_probe,
.remove = s3c64xx_iis_dev_remove,
.driver = {
.name = "s3c64xx-iis",
.owner = THIS_MODULE,
},
};
s3c64xx_iis_dev_probe是这个设备驱动的初始化函数。初始化之后设备就正常运行了
(4)第四步:platform的match函数发现driver和device匹配后,调用driver的probe函数来完成驱动的初始化和安装,然后设备就工作起来了
那么这个总线实现的分层结构是什么样的呢?
————————————————————————————————————————————————————
以下内容暂时作废
举例说明:
S5PV210的uart驱动
首先是初始化平台设备E:\v210\x210_src\arch\arm\plat-samsung\init.c文件 的s3c_arch_init(void)函数调用platform_add_devices(s3c24xx_uart_devs, nr_uarts);添加平台设备,
E:\v210\x210_src\drivers\serial\s5pv210.c添加驱动
static int __init s5p_serial_init(void)
{
return s3c24xx_serial_init(&s5p_serial_driver, *s5p_uart_inf);
}