1、什么是platform(平台)总线?
相对于USB、PCI、I2C、SPI等物理总线来说,platform总线是一种虚拟、抽象出来的总线,实际中并不存在这样的总线。
那为什么需要platform总线呢?其实是Linux设备驱动模型为了保持设备驱动的统一性而虚拟出来的总线。因为对于usb设备、i2c设备、
pci设备、spi设备等等,他们与cpu的通信都是直接挂在相应的总线下面与我们的cpu进行数据交互的,但是在我们的嵌入式系统当中,
并不是所有的设备都能够归属于这些常见的总线,在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设
却不依附与此类总线。所以Linux驱动模型为了保持完整性,将这些设备挂在一条虚拟的总线上(platform总线),而不至于使得有些
设备挂在总线上,另一些设备没有挂在总线上。
platform总线相关代码:driver\base\platform.c 文件
相关结构体定义:include\linux\platform_device.h 文件中
2、platform总线管理下的2员大将
(1)两个结构体platform_device和platform_driver
对于任何一种Linux设备驱动模型下的总线都由两个部分组成:描述设备相关的结构体和描述驱动相关的结构体
在platform总线下就是platform_device和platform_driver,下面是对两个结构体的各个元素进行分析:
platform_device结构体:(include\linux\platform_device.h)
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表
/* arch specific additions */
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结构体:(include\linux\platform_device.h)
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 类型的数组
};
(2)两组接口函数(driver\base\platform.c)
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 *); // 用来卸载我们的设备
3、platform平台总线的初始化
(1)platform平台总线的注册初始化: platform_bus_init
//
platform_bus_init
early_platform_cleanup // 进行一些早期的平台清理
device_register // 注册设备 (在/sys/devices/目录下建立 platform目录对应的设备对象 /sys/devices/platform/)
bus_register // 总线注册
/*/
(2)相关结构体
struct bus_type {
const char *name; // 总线名字
struct bus_attribute *bus_attrs; // 该总线的属性
struct device_attribute *dev_attrs; // 该总线下设备的属性
struct driver_attribute *drv_attrs; // 该总线下设备驱动的属性
int (*match)(struct device *dev, struct device_driver *drv); // 该总线下设备与设备驱动的匹配函数
int (*uevent)(struct device *dev, struct kobj_uevent_env *env); // 事件函数 热拨插
int (*probe)(struct device *dev); // 总线下的 探针函数
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm; // 电源管理相关的
struct bus_type_private *p; // 总线的私有数据 p->subsys.kobj 表示该总线在驱动模型中对应的对象
};
struct bus_type_private {
struct kset subsys; // 这个是bus主要的kset
struct kset *drivers_kset; // 这个kset指针用来指向该总线的 drivers目录的
struct kset *devices_kset; // 这个kse指针用来指向该总线的devices目录的
struct klist klist_devices; // 用来挂接该总线下的设备的一个链表头
struct klist klist_drivers; // 用来挂接该总线下的设备驱动的一个链表头
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1; // 是否需要在设备驱动注册时候子自动匹配设备
struct bus_type *bus; // 指向本bus结构体
};
(3)函数详解
bus_register:
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++) {
struct resource *p, *r = &pdev->resource;
if (r->name == NULL)
r->name = dev_name(&pdev->dev);
p = r->parent;
if (!p) {
if (resource_type(r) == IORESOURCE_MEM)
p = &iomem_resource;
else if (resource_type(r) == IORESOURCE_IO)
p = &ioport_resource;
}
if (p && insert_resource(p, r)) {
printk(KERN_ERR
"%s: failed to claim resource %d\n",
dev_name(&pdev->dev), i);
ret = -EBUSY;
goto failed;
}
}
//////////////////////////////////////////////////////////////////
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
if (ret == 0)
return ret;
failed:
while (--i >= 0) {
struct resource *r = &pdev->resource;
unsigned long type = resource_type(r);
if (type == IORESOURCE_MEM || type == IORESOURCE_IO)
release_resource(r);
}
return ret;
}
5、platform平台设备驱动注册
(1)platform平台设备驱动注册函数: platform_driver_register
/*********/
platform_driver_register
driver_register
driver_find
bus_add_driver
kobject_init_and_add
driver_attach
klist_add_tail
module_add_driver
driver_create_file
driver_add_attrs
driver_add_groups
//
(2)函数详解
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); // 注册设备驱动
}
int driver_register(struct device_driver *drv)
{
int ret;
struct device_driver *other; // 定义一个设备驱动指针 other
BUG_ON(!drv->bus->p);
if ((drv->bus->probe && drv->probe) ||
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown && drv->shutdown))
printk(KERN_WARNING "Driver '%s' needs updating - please use "
"bus_type methods\n", drv->name);
other = driver_find(drv->name, drv->bus); // 这个函数其实进行了一个校验 比对当前的 总线下是否存在名字和现在需要注册的设备驱动的名字相同的设备驱动
if (other) {
put_driver(other); // 如果名字相同 直接打印错误 并退出
printk(KERN_ERR "Error: Driver '%s' is already registered, "
"aborting...\n", drv->name);
return -EBUSY;
}
ret = bus_add_driver(drv); // 在总线挂接设备驱动 就是将设备驱动对应的kobj对象与组织建立关系
if (ret)
return ret;
ret = driver_add_groups(drv, drv->groups); //
if (ret)
bus_remove_driver(drv);
return ret;
}
bus_add_driver:
int bus_add_driver(struct device_driver *drv)
{
struct bus_type *bus; // 定义一个bus_type 结构体指针
struct driver_private *priv; // 定义一个 driver_private 指针
int error = 0;
bus = bus_get(drv->bus); // 获取 drv的bus
if (!bus)
return -EINVAL;
pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);
priv = kzalloc(sizeof(*priv), GFP_KERNEL); // 给priv 申请分配内存空间
if (!priv) {
error = -ENOMEM;
goto out_put_bus;
}
klist_init(&priv->klist_devices, NULL, NULL); // 初始化 priv->klist_devices 链表
priv->driver = drv; // 使用 priv->driver 指向 drv
drv->p = priv; // 使用drv->p 指向 priv 这两步见多了 ,跟之前分析的是一样的意思 就是建立关系
priv->kobj.kset = bus->p->drivers_kset; // 设置设备驱动对象的父对象( 也就是指向一个 kset ) 父对象就是 /sys/bus/bus_type/drivers/ 这个目录对应的对象
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, // 添加kobject 对象到目录层次中 就能够在 /sys/bus/bus_type/drivers/ 目录中看到设备驱动对应的文件了
"%s", drv->name); // priv->kobj->ktype = driver_ktype 对象类型
if (error)
goto out_unregister;
if (drv->bus->p->drivers_autoprobe) { // 如果定义了自动匹配设备标志位 则在线下面进行自动匹配
error = driver_attach(drv); // 尝试将驱动绑定到设备 也就是通过这个函数进行设备与设备驱动的匹配
if (error)
goto out_unregister;
}
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); // 链表挂接: priv->knode_bus 挂接到 bus->p->klist_drivers 链表头上去
module_add_driver(drv->owner, drv);
error = driver_create_file(drv, &driver_attr_uevent); // 建立属性文件: uevent
if (error) {
printk(KERN_ERR "%s: uevent attr (%s) failed\n",
__func__, drv->name);
}
error = driver_add_attrs(bus, drv); // 根据总线的 bus->drv_attrs 来建立属性文件
if (error) {
/* How the hell do we get out of this pickle? Give up */
printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n",
__func__, drv->name);
}
if (!drv->suppress_bind_attrs) {
error = add_bind_files(drv);
if (error) {
/* Ditto */
printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
__func__, drv->name);
}
}
kobject_uevent(&priv->kobj, KOBJ_ADD);
return 0;
out_unregister:
kobject_put(&priv->kobj);
kfree(drv->p);
drv->p = NULL;
out_put_bus:
bus_put(bus);
return error;
}
driver_attach:
上面说到了当注册platform平台设备驱动时会进行自动匹配的原理,那么当我们注册platform平台设备时进行自动匹配的代码在哪里呢?
其实这个之前在分析device_create函数时就已经分析过了,只不过没有去详细的分析:
/*/
platform_device_add
device_add
bus_probe_device // 关键就在这个函数
//
函数分析:
int driver_register(struct device_driver *drv)
{
int ret;
struct device_driver *other; // 定义一个设备驱动指针 other
BUG_ON(!drv->bus->p);
if ((drv->bus->probe && drv->probe) ||
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown && drv->shutdown))
printk(KERN_WARNING "Driver '%s' needs updating - please use "
"bus_type methods\n", drv->name);
other = driver_find(drv->name, drv->bus); // 这个函数其实进行了一个校验 比对当前的 总线下是否存在名字和现在需要注册的设备驱动的名字相同的设备驱动
if (other) {
put_driver(other); // 如果名字相同 直接打印错误 并退出
printk(KERN_ERR "Error: Driver '%s' is already registered, "
"aborting...\n", drv->name);
return -EBUSY;
}
ret = bus_add_driver(drv); // 在总线挂接设备驱动 就是将设备驱动对应的kobj对象与组织建立关系
if (ret)
return ret;
ret = driver_add_groups(drv, drv->groups); //
if (ret)
bus_remove_driver(drv);
return ret;
}
总结: 所以由此可知,当我们不管是先注册设备还是先注册设备驱动都会进行一次设备与设备驱动的匹配过程,匹配成功之后就会调用probe函数,
匹配的原理就是去遍历总线下的相应的链表来找到挂接在他下面的设备或者设备驱动,所以由此可以看出来,这个东西的设计其实是很美的。
6、platform总线下的匹配函数
(1)platform_match函数
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是否相同,相同则匹配成功,否则失败