在Linux 2.6以后的设备驱动模型中,需关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。
platform总线
相对于USB、PCI、I2C、SPI等物理总线来说,platform总线是一种虚拟、抽象出来的总线,实际中并不存在这样的总线。
那为什么需要platform总线呢?
其实是Linux设备驱动模型为了保持设备驱动的统一性而虚拟出来的总线。因为对于usb设备、i2c设备、pci设备、spi设备等等,他们与cpu的通信都是直接挂在相应的总线下面与cpu进行数据交互的,但是在嵌入式系统当中,并不是所有的设备都能够归属于这些常见的总线,在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设,却不依附与此类总线。所以Linux驱动模型为了保持完整性,将这些设备挂在一条虚拟的总线上(platform总线),而不至于使得有些设备挂在总线上,另一些设备没有挂在总线上。
注意:所谓的platform_device并不是与字符设备、块设备和网络设备并列的概念,而是Linux系统提供的一种附加手段,例如,我们通常把在SoC内部集成的I2C、RTC、LCD、看门狗等控制器都归纳为platform_device,而它们本身就是字符设备。
platform总线的两大结构体
platform_device结构体
描述设备相关的结构体——platform_device结构体:
struct platform_device // platform总线设备
{
const char * name; // 平台设备的名字
int id; // ID 是用来区分如果设备名字相同的时候(通过在后面添加一个数字来代表不同的设备,因为有时候有这种需求)
struct device dev; // 内置的device结构体
u32 num_resources; // 资源结构体数量
struct resource * resource; // 指向一个资源结构体数组
const struct platform_device_id * id_entry; // 用来进行与设备驱动匹配用的id_table表
struct pdev_archdata archdata; // 自留地添加自己的东西
};
platform_device结构体中的struct resource结构体:
struct resource
{
resource_size_t start; // 资源的起始值,如果是地址,那么是物理地址,不是虚拟地址
resource_size_t end; // 资源的结束值,如果是地址,那么是物理地址,不是虚拟地址
const char * name; // 资源名
unsigned long flags; // 资源的标示,用来识别不同的资源
struct resource * parent, *sibling, *child; // 资源指针,可以构成链表
};
platform_driver结构体
描述驱动相关的结构体——platform_driver结构体:
struct platform_driver
{
int(*probe) (struct platform_device *); // 这个probe函数和 device_driver中的是一样的功能,但是一般是使用device_driver中的那个
int(*remove) (struct platform_device *); // 卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了
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 结构体
const struct platform_device_id * id_table; // 该设备驱动支持的设备的列表 他是通过这个指针去指向 platform_device_id 类型的数组
};
注意:直接填充platform_driver的suspend()、resume()做电源管理回调的方法目前已经过时,较好的做法是实现platform_driver的device_driver中的dev_pm_ops结构体成员。
platform设备、驱动的注册、注销
int platform_driver_register(struct platform_driver *); // 用来注册我们的设备驱动
void platform_driver_unregister(struct platform_driver *); // 用来卸载我们的设备驱动
int platform_device_register(struct platform_device *); // 用来注册我们的设备
void platform_device_unregister(struct platform_device *); // 用来卸载我们的设备
platform总线、设备、驱动之间的联系
platform设备添加到platform总线
将platform设备注册好然后初始化完成后,接下来就是要把platform设备添加到platform总线上去了,主要是通过函数platform_device_add()函数添加:
int platform_device_add(struct platform_device * pdev)
{
int i, ret = 0;
if (!pdev)
return - EINVAL;
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus; // 将平台设备的父设备设置为 platform_bus (对应的就是 /sys/devices/platform 这个目录)
pdev->dev.bus = &platform_bus_type; // 设置平台设备挂接在 platform总线下platform_bus_type
if (pdev->id != -1)
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id); // 给平台设备对应的对象设置名字 name.id (如果我们的 pdev->id 设置不等于-1时)
else
dev_set_name(&pdev->dev, "%s", pdev->name);
// 下面的for 循环是对平台设备资源的一些处理
for (i = 0; i < pdev->num_resources; i++)
{
……
}
pr_debug("Registering platform device '%s'. Parent at %s\n",
dev_name(&pdev->dev), dev_name(pdev->dev.parent));
ret= device_add(&pdev->dev); // 将平台设备添加到系统中去 /sys/devices/platform/xxx
f (ret == 0)
return ret;
return ret;
}
当设备被添加到platform总线上后,总线会调用bus_probe_device方法来获取当前的总线类型,得到总线类型并且发现了对应的总线之后,就会在对应的总线下调用device_attach()函数来查找可以与设备对应的驱动程序,然后绑定。
platform驱动添加到platform总线
platform驱动注册时调用platform_driver_register()函数:
int platform_driver_register(struct platform_driver * drv)
{
drv->driver.bus = &platform_bus_type; // 设置设备驱动 挂接在 platform平台总线下
// 下面做的就是对 drv 中的函数指针进行填充
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver); // 注册设备驱动
}
然后调用driver_register()函数会调用一个bus_add_driver()方法来将驱动程序和总线绑定,在这之中又会调用driver_attach函数来查找可以匹配相关驱动的设备:
int driver_attach(struct device_driver *drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); // 这个函数的功能就是: 依次去匹配bus总线下的各个设备
}
int bus_for_each_dev(struct bus_type * bus, struct device * start,
void * data, int(*fn) (struct device *, void *))
{
struct klist_iter i; // 定义一个klist_iter 结构体变量 包含: struct klist 和 struct klist_node
struct device * dev;
int error = 0;
if (!bus)
return - EINVAL;
klist_iter_init_node(&bus->p->klist_devices, &i, // 这个函数的功能就是将 klist_devices 和 knode_bus填充到 i 变量中
(start ? &start->p->knode_bus: NULL));
while ((dev = next_device(&i)) && !error) // 依次返回出总线上的各个设备结构体device
error = fn(dev, data); // 对于每一个设备和设备驱动都调用fn这个函数 直道成功 或者全部都匹配不上
klist_iter_exit(&i);
return error;
}
由此可知,当我们不管是先注册设备还是先注册设备驱动都会进行一次设备与设备驱动的匹配过程,匹配成功之后就会调用probe函数,匹配的原理就是去遍历总线下的相应的链表来找到挂接在他下面的设备或者设备驱动。
platform总线下的匹配
系统为platform总线定义了一个bus_type的实例platform_bus_type,其定义位于drivers/base/platform.c下:
struct bus_type platform_bus_type =
{
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
其中match()成员函数确定了platform_device和platform_driver之间是如何进行匹配的:
static int platform_match(struct device * dev, struct device_driver * drv)
{
struct platform_device * pdev = to_platform_device(dev);
struct platform_driver * pdrv = to_platform_driver(drv);
/* 基于设备树风格的匹配 */
if (of_driver_match_device(dev, drv))
return 1;
/*基于ACPI风格的匹配 */
if (acpi_driver_match_device(dev, drv))
return 1;
/*匹配ID表*/
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/*匹配platform_device设备名和驱动的名字*/
return (strcmp(pdev->name, drv->name) == 0);
}
匹配platform_device和platform_driver有4种可能性:
- 一是基于设备树风格的匹配;
- 二是基于ACPI风格的匹配;
- 三是匹配ID表(即platform_device设备名是否出现在platform_driver的ID表内);
- 第四种是匹配platform_device设备名和驱动的名字。