1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > Linux设备驱动模型之platform(平台)总线详解

Linux设备驱动模型之platform(平台)总线详解

时间:2019-05-01 12:30:07

相关推荐

Linux设备驱动模型之platform(平台)总线详解

/********************************************************/

内核版本:2.6.35.7

运行平台:三星s5pv210

/********************************************************/

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)

1 struct platform_device { // platform总线设备 2const char * name;// 平台设备的名字 3int id; // ID 是用来区分如果设备名字相同的时候(通过在后面添加一个数字来代表不同的设备,因为有时候有这种需求) 4struct device dev;// 内置的device结构体 5u32 num_resources;// 资源结构体数量 6struct resource * resource; // 指向一个资源结构体数组 7 8const struct platform_device_id *id_entry; // 用来进行与设备驱动匹配用的id_table表 9 10/* arch specific additions */11struct pdev_archdata archdata; // 自留地 添加自己的东西12 };

platform_device结构体中的struct resource结构体分析:

1 struct resource {// 资源结构体2resource_size_t start;// 资源的起始值,如果是地址,那么是物理地址,不是虚拟地址3resource_size_t end; // 资源的结束值,如果是地址,那么是物理地址,不是虚拟地址4const char *name; // 资源名5unsigned long flags; // 资源的标示,用来识别不同的资源6struct resource *parent, *sibling, *child; // 资源指针,可以构成链表7 };

platform_driver结构体:(include\linux\platform_device.h)

1 struct platform_driver {2int (*probe)(struct platform_device *);// 这个probe函数其实和 device_driver中的是一样的功能,但是一般是使用device_driver中的那个3int (*remove)(struct platform_device *); // 卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了4void (*shutdown)(struct platform_device *);5int (*suspend)(struct platform_device *, pm_message_t state);6int (*resume)(struct platform_device *);7struct device_driver driver;// 内置的device_driver 结构体 8const struct platform_device_id *id_table; // 该设备驱动支持的设备的列表 他是通过这个指针去指向 platform_device_id 类型的数组9 };

(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)相关结构体

1 struct bus_type { 2const char *name; // 总线名字 3struct bus_attribute *bus_attrs;// 该总线的属性 4struct device_attribute *dev_attrs; // 该总线下设备的属性 5struct driver_attribute *drv_attrs; // 该总线下设备驱动的属性 6 7int (*match)(struct device *dev, struct device_driver *drv);// 该总线下设备与设备驱动的匹配函数 8int (*uevent)(struct device *dev, struct kobj_uevent_env *env); // 事件函数 热拨插 9int (*probe)(struct device *dev); // 总线下的 探针函数10int (*remove)(struct device *dev);11void (*shutdown)(struct device *dev);12 13int (*suspend)(struct device *dev, pm_message_t state);14int (*resume)(struct device *dev);15 16const struct dev_pm_ops *pm; // 电源管理相关的17 18struct bus_type_private *p; // 总线的私有数据 p->subsys.kobj 表示该总线在驱动模型中对应的对象19 };

1 struct bus_type_private { 2struct kset subsys;// 这个是bus主要的kset 3struct kset *drivers_kset; // 这个kset指针用来指向该总线的 drivers目录的 4struct kset *devices_kset; // 这个kse指针用来指向该总线的devices目录的 5struct klist klist_devices; // 用来挂接该总线下的设备的一个链表头 6struct klist klist_drivers; // 用来挂接该总线下的设备驱动的一个链表头 7struct blocking_notifier_head bus_notifier; 8unsigned int drivers_autoprobe:1; // 是否需要在设备驱动注册时候子自动匹配设备 9struct bus_type *bus; // 指向本bus结构体10 };

(3)函数详解

bus_register:

1 int bus_register(struct bus_type *bus) 2 { 3int retval; 4struct bus_type_private *priv; // 定义一个bus_type_private 结构体指针 5 6priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL); // 申请分配内存 7if (!priv) 8 return -ENOMEM; 9 10priv->bus = bus; // 使用 priv->bus 指向我们传进来的bus11bus->p = priv; // 通过 bus->p 指向priv 这里其实就是将bus与priv建立关系,这个跟之前的device、class的设计是一样的12 13BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);14 15retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name); // 给我们的bus在设备驱动模型中的对象设置名字 bus->p->subsys.kobj16if (retval)17 goto out;18 19 // 这里就是对bus的私有数据进行一些填充20priv->subsys.kobj.kset = bus_kset;// 设置bus对象的父对象也就是 /sys/bus 这目录 作为他的上层目录 所有的具体的总线类型对象都是在这个目录下21priv->subsys.kobj.ktype = &bus_ktype; // 设置bus对象的 对象类型为 bus_ktype22priv->drivers_autoprobe = 1; // 配置为在注册设备或者是注册设备驱动时自动进行配置 这个就决定了为什么我们在注册设备或者是设备驱动能够进行自动匹配23 24retval = kset_register(&priv->subsys); // 注册kset结构体(内部会调用kobject_add_internal函数,也就是将bus对象添加到 /sys/bus/目录下, /sys/bus/xxx_busType 对应具体的总线)25if (retval)26 goto out;27 28retval = bus_create_file(bus, &bus_attr_uevent); // 在该bus下建立属性文件 (对应的就是 bus下的 uevent属性)29if (retval)30 goto bus_uevent_fail;31 32priv->devices_kset = kset_create_and_add("devices", NULL, // 在具体总线的目录下创建 kset 容器对象 /sys/bus/xxx_busType/devices33&priv->subsys.kobj); // 通过priv->devices_kset指针去指向 这个目录对应的对象34if (!priv->devices_kset) {35 retval = -ENOMEM;36 goto bus_devices_fail;37}38 39priv->drivers_kset = kset_create_and_add("drivers", NULL, // /sys/bus/xxx_busType/drivers40&priv->subsys.kobj); // 通过 priv->drivers_kset 指针去指向 这个目录对应的对象41if (!priv->drivers_kset) {42 retval = -ENOMEM;43 goto bus_drivers_fail;44}45 46klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); // 初始化链表 klist47klist_init(&priv->klist_drivers, NULL, NULL); // 初始化链表 klist48 49retval = add_probe_files(bus); // 添加探针文件 其实内部做的还是添加属性文件 /sys/bus/xxx_busType/drivers_probe /sys/bus/xxx_busType/drivers_autoprobe50if (retval)51 goto bus_probe_files_fail;52 53retval = bus_add_attrs(bus);// 根据 bus->bus_attrs 中的属性设置来添加属性文件54if (retval)55 goto bus_attrs_fail;56 57pr_debug("bus: '%s': registered\n", bus->name);58return 0;59 60 bus_attrs_fail:61remove_probe_files(bus);62 bus_probe_files_fail:63kset_unregister(bus->p->drivers_kset);64 bus_drivers_fail:65kset_unregister(bus->p->devices_kset);66 bus_devices_fail:67bus_remove_file(bus, &bus_attr_uevent);68 bus_uevent_fail:69kset_unregister(&bus->p->subsys);70kfree(bus->p);71 out:72bus->p = NULL;73return retval;74 }

4、platform平台设备注册

(1)platform平台总线注册函数: platform_device_register

/************************************************************************************/

platform_device_register

device_initialize

platform_device_add

device_add// 这个函数之前分析过了,这里就不再分析了

/***********************************************************************************/

(2)函数分析

1 int platform_device_add(struct platform_device *pdev) 2 { 3int i, ret = 0; 4 5if (!pdev) 6 return -EINVAL; 7 8if (!pdev->dev.parent)9 pdev->dev.parent = &platform_bus; // 将平台设备的父设备设置为 platform_bus (对应的就是 /sys/devices/platform 这个目录)10 11pdev->dev.bus = &platform_bus_type; // 设置平台设备挂接在 platform总线下platform_bus_type12 13if (pdev->id != -1)14 dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id); // 给平台设备对应的对象设置名字 name.id (如果我们的 pdev->id 设置不等于-1时)15else16 dev_set_name(&pdev->dev, "%s", pdev->name);17 18 // 下面的for 循环是对平台设备资源的一些处理19for (i = 0; i < pdev->num_resources; i++) {20 struct resource *p, *r = &pdev->resource[i];21 22 if (r->name == NULL)23 r->name = dev_name(&pdev->dev);24 25 p = r->parent;26 if (!p) {27 if (resource_type(r) == IORESOURCE_MEM)28 p = &iomem_resource;29 else if (resource_type(r) == IORESOURCE_IO)30 p = &ioport_resource;31 }32 33 if (p && insert_resource(p, r)) {34 printk(KERN_ERR35"%s: failed to claim resource %d\n",36dev_name(&pdev->dev), i);37 ret = -EBUSY;38 goto failed;39 }40}41 //42 43pr_debug("Registering platform device '%s'. Parent at %s\n",44dev_name(&pdev->dev), dev_name(pdev->dev.parent));45 46ret = device_add(&pdev->dev); // 将平台设备添加到系统中去 /sys/devices/platform/xxx47if (ret == 0)48 return ret;49 50 failed:51while (--i >= 0) {52 struct resource *r = &pdev->resource[i];53 unsigned long type = resource_type(r);54 55 if (type == IORESOURCE_MEM || type == IORESOURCE_IO)56 release_resource(r);57}58 59return ret;60 }

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:

1 int platform_driver_register(struct platform_driver *drv) 2 { 3drv->driver.bus = &platform_bus_type; // 设置设备驱动 挂接在 platform平台总线下 4 5 // 下面做的就是对 drv 中的函数指针进行填充 6if (drv->probe) 7 drv->driver.probe = platform_drv_probe; 8if (drv->remove) 9 drv->driver.remove = platform_drv_remove;10if (drv->shutdown)11 drv->driver.shutdown = platform_drv_shutdown;12 13return driver_register(&drv->driver); // 注册设备驱动14 }

driver_register:

1 int driver_register(struct device_driver *drv) 2 { 3int ret; 4struct device_driver *other; // 定义一个设备驱动指针 other 5 6BUG_ON(!drv->bus->p); 7 8if ((drv->bus->probe && drv->probe) || 9 (drv->bus->remove && drv->remove) ||10 (drv->bus->shutdown && drv->shutdown))11 printk(KERN_WARNING "Driver '%s' needs updating - please use "12 "bus_type methods\n", drv->name);13 14other = driver_find(drv->name, drv->bus); // 这个函数其实进行了一个校验 比对当前的 总线下是否存在名字和现在需要注册的设备驱动的名字相同的设备驱动15if (other) {16 put_driver(other); // 如果名字相同 直接打印错误 并退出17 printk(KERN_ERR "Error: Driver '%s' is already registered, "18 "aborting...\n", drv->name);19 return -EBUSY;20}21 22ret = bus_add_driver(drv); // 在总线挂接设备驱动 就是将设备驱动对应的kobj对象与组织建立关系23if (ret)24 return ret;25ret = driver_add_groups(drv, drv->groups); // 26if (ret)27 bus_remove_driver(drv);28return ret;29 }

bus_add_driver:

1 int bus_add_driver(struct device_driver *drv) 2 { 3struct bus_type *bus; // 定义一个bus_type 结构体指针 4struct driver_private *priv;// 定义一个 driver_private 指针 5int error = 0; 6 7bus = bus_get(drv->bus); // 获取 drv的bus 8if (!bus) 9 return -EINVAL;10 11pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);12 13priv = kzalloc(sizeof(*priv), GFP_KERNEL); // 给priv 申请分配内存空间14if (!priv) {15 error = -ENOMEM;16 goto out_put_bus;17}18klist_init(&priv->klist_devices, NULL, NULL); // 初始化 priv->klist_devices 链表19priv->driver = drv; // 使用 priv->driver 指向 drv20drv->p = priv; // 使用drv->p 指向 priv 这两步见多了 ,跟之前分析的是一样的意思 就是建立关系21priv->kobj.kset = bus->p->drivers_kset; // 设置设备驱动对象的父对象( 也就是指向一个 kset ) 父对象就是 /sys/bus/bus_type/drivers/ 这个目录对应的对象22error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, // 添加kobject 对象到目录层次中就能够在 /sys/bus/bus_type/drivers/ 目录中看到设备驱动对应的文件了23 "%s", drv->name); // priv->kobj->ktype = driver_ktype对象类型24if (error)25 goto out_unregister;26 27if (drv->bus->p->drivers_autoprobe) { // 如果定义了自动匹配设备标志位 则在线下面进行自动匹配28 error = driver_attach(drv); // 尝试将驱动绑定到设备 也就是通过这个函数进行设备与设备驱动的匹配29 if (error)30 goto out_unregister;31}32klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); // 链表挂接: priv->knode_bus 挂接到 bus->p->klist_drivers 链表头上去33module_add_driver(drv->owner, drv);34 35error = driver_create_file(drv, &driver_attr_uevent); // 建立属性文件: uevent36if (error) {37 printk(KERN_ERR "%s: uevent attr (%s) failed\n",38 __func__, drv->name);39}40error = driver_add_attrs(bus, drv);// 根据总线的 bus->drv_attrs 来建立属性文件41if (error) {42 /* How the hell do we get out of this pickle? Give up */43 printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n",44 __func__, drv->name);45}46 47if (!drv->suppress_bind_attrs) {48 error = add_bind_files(drv);49 if (error) {50 /* Ditto */51 printk(KERN_ERR "%s: add_bind_files(%s) failed\n",52 __func__, drv->name);53 }54}55 56kobject_uevent(&priv->kobj, KOBJ_ADD);57return 0;58 59 out_unregister:60kobject_put(&priv->kobj);61kfree(drv->p);62drv->p = NULL;63 out_put_bus:64bus_put(bus);65return error;66 }

driver_attach:

1 int driver_attach(struct device_driver *drv) 2 { 3return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); // 这个函数的功能就是: 依次去匹配bus总线下的各个设备 4 } 5

6 7 int bus_for_each_dev(struct bus_type *bus, struct device *start, 8 void *data, int (*fn)(struct device *, void *)) 9 { 10struct klist_iter i;// 定义一个klist_iter 结构体变量 包含: struct klist 和 struct klist_node 11struct device *dev; 12int error = 0; 13 14if (!bus) 15 return -EINVAL; 16 17klist_iter_init_node(&bus->p->klist_devices, &i,// 这个函数的功能就是将 klist_devices 和 knode_bus填充到 i 变量中 18 (start ? &start->p->knode_bus : NULL)); 19while ((dev = next_device(&i)) && !error) // 依次返回出总线上的各个设备结构体device 20 error = fn(dev, data); // 对于每一个设备和设备驱动都调用fn这个函数 直道成功 或者全部都匹配不上 21klist_iter_exit(&i); 22return error; 23 } 24 25

26 27 static int __driver_attach(struct device *dev, void *data) 28 { 29struct device_driver *drv = data;// 定义一个device_driver 指针 30 31/* 32* Lock device and try to bind to it. We drop the error 33* here and always return 0, because we need to keep trying 34* to bind to devices and some drivers will return an error 35* simply if it didn't support the device. 36* 37* driver_probe_device() will spit a warning if there 38* is an error. 39*/ 40 41if (!driver_match_device(drv, dev)) // 通过这个函数进行匹配 调用总线下的match 函数 42 return 0; 43 44if (dev->parent) /* Needed for USB */ 45 device_lock(dev->parent); 46device_lock(dev); 47if (!dev->driver) 48 driver_probe_device(drv, dev); // 调用probe函数 49device_unlock(dev); 50if (dev->parent) 51 device_unlock(dev->parent); 52 53return 0; 54 } 55 56

57 58 int driver_probe_device(struct device_driver *drv, struct device *dev) 59 { 60int ret = 0; 61 62if (!device_is_registered(dev))// 判断这个设备是否已经注册了 63 return -ENODEV; 64 65pr_debug("bus: '%s': %s: matched device %s with driver %s\n", 66drv->bus->name, __func__, dev_name(dev), drv->name); 67 68pm_runtime_get_noresume(dev); 69pm_runtime_barrier(dev); 70ret = really_probe(dev, drv); // 在这个函数中就会调用设备驱动或者是总线下的 probe 函数 71pm_runtime_put_sync(dev); 72 73return ret; 74 } 75

76 77 static int really_probe(struct device *dev, struct device_driver *drv) 78 { 79int ret = 0; 80 81atomic_inc(&probe_count); 82pr_debug("bus: '%s': %s: probing driver %s with device %s\n", 83drv->bus->name, __func__, drv->name, dev_name(dev)); 84WARN_ON(!list_empty(&dev->devres_head)); 85 86dev->driver = drv; // 使用 dev->driver 指针去指向 drv 这就使得这两者建立了一种关系 87if (driver_sysfs_add(dev)) { 88 printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n", 89 __func__, dev_name(dev)); 90 goto probe_failed; 91} 92 93if (dev->bus->probe) { // 如果总线下的probe函数存在 则调用优先调用这个函数 94 ret = dev->bus->probe(dev); 95 if (ret) 96 goto probe_failed; 97} else if (drv->probe) { // 否则调用设备驱动中的probe函数 98 ret = drv->probe(dev); // 所以由此可知: 总线中的probe函数具有更高的优先级 99 if (ret)100 goto probe_failed;101}102 103driver_bound(dev);104ret = 1;105pr_debug("bus: '%s': %s: bound device %s to driver %s\n",106drv->bus->name, __func__, dev_name(dev), drv->name);107goto done;108 109 probe_failed:110devres_release_all(dev);111driver_sysfs_remove(dev);112dev->driver = NULL;113 114if (ret != -ENODEV && ret != -ENXIO) {115 /* driver matched but the probe failed */116 printk(KERN_WARNING117"%s: probe of %s failed with error %d\n",118drv->name, dev_name(dev), ret);119}120/*121* Ignore errors returned by ->probe so that the next driver can try122* its luck.123*/124ret = 0;125 done:126atomic_dec(&probe_count);127wake_up(&probe_waitqueue);128return ret;129 }

上面说到了当注册platform平台设备驱动时会进行自动匹配的原理,那么当我们注册platform平台设备时进行自动匹配的代码在哪里呢?

其实这个之前在分析device_create函数时就已经分析过了,只不过没有去详细的分析:

/**********************************************/

platform_device_add

device_add

bus_probe_device // 关键就在这个函数

/*********************************************/

函数分析:

1 void bus_probe_device(struct device *dev) 2 { 3struct bus_type *bus = dev->bus; // 获取设备中的总线类型 bus_type 4int ret; 5 6if (bus && bus->p->drivers_autoprobe) { // 如果总线存在 并且 设置了自动进行设备与设备驱动匹配标志位 7 ret = device_attach(dev); // 则调用这个函数进行匹配 8 WARN_ON(ret < 0); 9}10 }11 12 13 14 15 int device_attach(struct device *dev)16 {17int ret = 0;18 19device_lock(dev);20if (dev->driver) { // 如果我们的设备早就绑定了设备驱动那么执行下面的21 ret = device_bind_driver(dev);22 if (ret == 0)23 ret = 1;24 else {25 dev->driver = NULL;26 ret = 0;27 }28} else {// 我们就分析这条29 pm_runtime_get_noresume(dev);30 ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach); // 遍历总线的链表匹配对应的设备驱动31 pm_runtime_put_sync(dev);32}33device_unlock(dev);34return ret;35 }36 // 到这里之后就和上面的其实是一样的了

总结: 所以由此可知,当我们不管是先注册设备还是先注册设备驱动都会进行一次设备与设备驱动的匹配过程,匹配成功之后就会调用probe函数,

匹配的原理就是去遍历总线下的相应的链表来找到挂接在他下面的设备或者设备驱动,所以由此可以看出来,这个东西的设计其实是很美的。

6、platform总线下的匹配函数

(1)platform_match函数

1 static int platform_match(struct device *dev, struct device_driver *drv) // 总线下的设备与设备驱动的匹配函数 2 { 3struct platform_device *pdev = to_platform_device(dev);// 通过device 变量获取到 platform_device 4struct platform_driver *pdrv = to_platform_driver(drv);// 通过 driver 获取 platform_driver 5 6/* match against the id table first */ 7if (pdrv->id_table) // 如果pdrv中的id_table 表存在 8 return platform_match_id(pdrv->id_table, pdev) != NULL; // 匹配id_table 9 10/* fall-back to driver name match */ // 第二个就是指直接匹配 pdev->namedrv->name 名字是否形同11return (strcmp(pdev->name, drv->name) == 0);12 }13 14 15 16 17 static const struct platform_device_id *platform_match_id(18 const struct platform_device_id *id,19 struct platform_device *pdev)20 {21while (id->name[0]) { // 循环去比较id_table数组中的各个id名字是否与pdev->name 相同22 if (strcmp(pdev->name, id->name) == 0) {23 pdev->id_entry = id; // 将id_table数组中的名字匹配上的 这个数组项 指针赋值给 pdev->id_entry24 return id; // 返回这个指针25 }26 id++;27}28return NULL;29 }

总结: 由上面可知platform总线下设备与设备驱动的匹配原理就是通过名字进行匹配的,先去匹配platform_driver中的id_table表中的各个名字与platform_device->name

名字是否相同,如果相同表示匹配成功直接返回,否则直接匹配platform_driver->name与platform_driver->name是否相同,相同则匹配成功,否则失败。

参考:《朱友鹏嵌入式Linux开发\5.Linux驱动开发\5.5.linux设备驱动模型》

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。