platform总线--驱动发现设备的过程
来源:互联网 发布:微信视频强制分享源码 编辑:程序博客网 时间:2024/05/21 17:50
总线时物理存在,Linux系统提供了一种简单的总线flatform。
platform 不是物理存在的总线,而是逻辑概念。现代PC机提供了一条根总线(PCI 总线)管理设备,但是有些设备没有挂载在PCI总线上,不能由PCI总线管理,于是Linux内核虚拟了platform总线来统一管理这种设备。
1、从驱动发现设备的过程
platform总线虽然简单,但有总线的通用功能。我们选的例子是 q40kbd, 在driver/input/serio目录里,这是一个键盘驱动,使用了platform总线。
1.1、驱动的初始化
设备驱动一般从初始化函数进行分析,q40kbd_init作用是把驱动程序注册到系统,代码如下
static int __init q40kbd_init(void){int error;if (!MACH_IS_Q40)return -ENODEV;//驱动作为platform总线驱动 注册error = platform_driver_register(&q40kbd_driver);if (error)return error;//分配一个platform设备q40kbd_device = platform_device_alloc("q40kbd", -1);if (!q40kbd_device)goto err_unregister_driver;//platform 设备注册error = platform_device_add(q40kbd_device);if (error)goto err_free_device;return 0; err_free_device:platform_device_put(q40kbd_device); err_unregister_driver:platform_driver_unregister(&q40kbd_driver);return error;}这段代码首先注册一个platform驱动,然后注册一个platform设备。这个过程显示了platform总线的用法,PCI总线可以自动扫描设备,而platform总线时虚拟的总线,物理上并不存在,没有扫描设备的功能,所以platform需要直接注册设备
1.2、注册驱动
驱动注册调用函数是 platform_driver_register,代码如下:
/** * platform_driver_register - register a driver for platform-level devices * @drv: platform driver structure */int platform_driver_register(struct platform_driver *drv){drv->driver.bus = &platform_bus_type;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);}EXPORT_SYMBOL_GPL(platform_driver_register);platform_driver_register 函数把驱动总线设置为platform总线,然后依次设置 驱动的各个指针,最后调用driver_register函数注册驱动。driver_register 函数很简单,初始化之后就调用 bus_add_driver。
1.3、为总线增加一个驱动
bus_add_driver 的作用是为总线增加一个驱动,bus_add_driver代码如下【微差别】:
/** * bus_add_driver - Add a driver to the bus. * @drv: driver. */int bus_add_driver(struct device_driver *drv){struct bus_type *bus;struct driver_private *priv;int error = 0;bus = bus_get(drv->bus);if (!bus)return -EINVAL;pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);priv = kzalloc(sizeof(*priv), GFP_KERNEL);if (!priv) {error = -ENOMEM;goto out_put_bus;}klist_init(&priv->klist_devices, NULL, NULL);priv->driver = drv;drv->p = priv;priv->kobj.kset = bus->p->drivers_kset;error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, "%s", drv->name);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);module_add_driver(drv->owner, drv);error = driver_create_file(drv, &driver_attr_uevent);if (error) {printk(KERN_ERR "%s: uevent attr (%s) failed\n",__func__, drv->name);}error = driver_add_attrs(bus, drv);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;}
bus_add_driver函数使用了kobject_register 和 driver_add_attrs等函数为sysfs文件系统创建设备驱动相关的 目录 和 文件。 前面介绍过
1.4、驱动加载
真正执行驱动加载的是 driver_attach 函数,代码如下:
/** * driver_attach - try to bind driver to devices. * @drv: driver. * * Walk the list of devices that the bus has on it and try to * match the driver with each one. If driver_probe_device() * returns 0 and the @dev->driver is set, we've found a * compatible pair. */int driver_attach(struct device_driver * drv){return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);}EXPORT_SYMBOL_GPL(driver_attach);
/** * bus_for_each_dev - device iterator. * @bus: bus type. * @start: device to start iterating from. * @data: data for the callback. * @fn: function to be called for each device. * * Iterate over @bus's list of devices, and call @fn for each, * passing it @data. If @start is not NULL, we use that device to * begin iterating from. * * We check the return of @fn each time. If it returns anything * other than 0, we break out and return that value. * * NOTE: The device that returns a non-zero value is not retained * in any way, nor is its refcount incremented. If the caller needs * to retain this data, it should do so, and increment the reference * count in the supplied callback. */int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int (*fn)(struct device *, void *)){struct klist_iter i;struct device *dev;int error = 0;if (!bus)return -EINVAL; //初始化一个kList,从设备start开始klist_iter_init_node(&bus->p->klist_devices, &i, (start ? &start->p->knode_bus : NULL));while ((dev = next_device(&i)) && !error)error = fn(dev, data);klist_iter_exit(&i);return error;}EXPORT_SYMBOL_GPL(bus_for_each_dev);
初始化一个kList,从设备start开始:
int bus_for_each_dev (struct bus_type * bus, struct device * start, void *data, int (*fn) (struct device *, void *))
{
struct klist_liter i;
struct device * dev;
int error;
if (!bus)
return -EINVAL;
//初始化一个klist,
klist_iter_init_node( &bus->klist_device, &i, (start ? &start->knode : NULL)) ;
while (( dev = next_device(&i) && !error )
error = fn(dev, data);
klist_iter_exit( &i );
return error;
}
初始化一个kList,从设备start开始:
/lib/Klist.c
/** * klist_iter_init_node - Initialize a klist_iter structure. * @k: klist we're iterating. * @i: klist_iter we're filling. * @n: node to start with. * * Similar to klist_iter_init(), but starts the action off with @n, * instead of with the list head. */void klist_iter_init_node(struct klist *k, struct klist_iter *i, struct klist_node *n){i->i_klist = k;i->i_cur = n;if (n)kref_get(&n->n_ref);}//----include/linux/Klist.hstruct klist_iter {struct klist*i_klist;struct klist_node*i_cur;};//----include/linux/Klist.hstruct klist_node;struct klist {spinlock_tk_lock;struct list_headk_list;void(*get)(struct klist_node *);void(*put)(struct klist_node *);} __attribute__ ((aligned (sizeof(void *))));struct klist_node {void*n_klist;/* never access directly */struct list_headn_node;struct krefn_ref;};总线类型的结构体定义在 include/linux/Device.h 文件中:
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 subsys_private *p;};
/** * struct subsys_private - structure to hold the private to the driver core portions of the bus_type/class structure. * * @subsys - the struct kset that defines this subsystem * @devices_kset - the list of devices associated * * @drivers_kset - the list of drivers associated * @klist_devices - the klist to iterate over the @devices_kset * @klist_drivers - the klist to iterate over the @drivers_kset * @bus_notifier - the bus notifier list for anything that cares about things * on this bus. * @bus - pointer back to the struct bus_type that this structure is associated * with. * * @class_interfaces - list of class_interfaces associated * @glue_dirs - "glue" directory to put in-between the parent device to * avoid namespace conflicts * @class_mutex - mutex to protect the children, devices, and interfaces lists. * @class - pointer back to the struct class that this structure is associated * with. * * This structure is the one that is the actual kobject allowing struct * bus_type/class to be statically allocated safely. Nothing outside of the * driver core should ever touch these fields. */struct subsys_private {struct kset subsys;struct kset *devices_kset;struct kset *drivers_kset;struct klist klist_devices;struct klist klist_drivers;struct blocking_notifier_head bus_notifier;unsigned int drivers_autoprobe:1;struct bus_type *bus;struct list_head class_interfaces;struct kset glue_dirs;struct mutex class_mutex;struct class *class;};struct klist_node;struct klist {spinlock_tk_lock;struct list_headk_list;void(*get)(struct klist_node *);void(*put)(struct klist_node *);} __attribute__ ((aligned (sizeof(void *))));
1.5、遍历总线上已挂载的设备
遍历总线上已挂载的设备,起始位置是初始化klist_iter 结构设置的 start 设备,只遍历这个设备之后挂载的设备。当前场景设置的start设备为空,所以要遍历所有的platform总线设备。 对每个设备调用 fn 函数指针, fn就是传入的函数指针 __driver_attach,它的代码如下:
__driver_attach() ---/drivers/base/Dd.c
static int __driver_attach(struct device *dev, void *data){struct device_driver *drv = data;/* * Lock device and try to bind to it. We drop the error * here and always return 0, because we need to keep trying * to bind to devices and some drivers will return an error * simply if it didn't support the device. * * driver_probe_device() will spit a warning if there * is an error. */if (!driver_match_device(drv, dev))return 0;if (dev->parent)/* Needed for USB */device_lock(dev->parent);device_lock(dev);if (!dev->driver)driver_probe_device(drv, dev);device_unlock(dev);if (dev->parent)device_unlock(dev->parent);return 0;}static int __driver_attach(struct device *dev, void * data )
{
struct device_driver * drv = data;
if ( dev->parent )/* need for usb */
down(&dev->parent->sem);
down(&dev->sem);
if (!dev->driver)
driver_probe_device(drv, dev);
return 0;
}
__driver_attach获取设备的锁之后,调用driver_probe_device函数,代码如下:
int driver_proe_device(struct device_driver * drv, struct device *dev)
{
int ret =0;
//调用总线配置的match函数
if ( drv->bus->match && !drv->bus->match( dev, drv) )
goto Done;
pr_debug("%s: Matched Device %s with Driver %s \n", drv->bus->name, dev->bus_id, drv->name );
dev->driver = drv;
//总线的match函数通过后,继续调用总线的probe函数
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if(ret) {
dev->driver = NULL;
goto ProbeFailed;
}
}else if ( drv->probe ) {
// 如果驱动提供了probe函数,则调用驱动的probe 函数
ret = drv->probe(dev);
if (ret) {
dev->driver = NULL;
goto ProbeFailed;
}
}
//设备发现驱动,通过sysfs创建一些文件,和设备做符号链接
device_bind_driver(dev);
driver_probe_device函数分为两个步骤:
--第一步 调用总线提供的match函数; 如果检测通过,说明设备和驱动匹配,设备所指向的驱动指针要赋予为当前驱动
--第二步 探测probe。首先调用总线提供的probe函数,如果驱动有自己的probe函数,还要调用驱动的probe函数
Probe 目的是 总线或设备的进一步探测。比如硬盘控制器,本身是一个PCI设备,同时又提供硬盘接入的功能。那么它的驱动probe函数就要扫描scsi总线,把所有接入的硬盘都扫描出来。
1.5.1 match函数
platform总线的match函数就是platform_match
platform_match 函数很简单,比较驱动的名字和设备的名字是否相同,相同就可以匹配。
1.5.2 probe函数
platform_drv_probe 是封装的函数,简单调用了 驱动的probe函数,驱动probe函数就是 q40kbd_probe:
static int __devinit q40kbd_probe(struct platform_device *dev){q40kbd_port = kzalloc(sizeof(struct serio), GFP_KERNEL);if (!q40kbd_port)return -ENOMEM;q40kbd_port->id.type= SERIO_8042;q40kbd_port->open= q40kbd_open;q40kbd_port->close= q40kbd_close;q40kbd_port->dev.parent= &dev->dev;strlcpy(q40kbd_port->name, "Q40 Kbd Port", sizeof(q40kbd_port->name));strlcpy(q40kbd_port->phys, "Q40", sizeof(q40kbd_port->phys));serio_register_port(q40kbd_port);printk(KERN_INFO "serio: Q40 kbd registered\n");return 0;}q40kbd_probe函数设置了一个serio结构变量,然后注册到系统。
- platform总线--驱动发现设备的过程
- platform总线--从设备找到驱动的过程
- platform总线、设备与驱动
- platform总线、设备与驱动
- platform总线、设备与驱动
- platform总线、设备与驱动
- Linux驱动---------platform总线设备
- 总线设备驱动模型---platform
- platform总线、设备与驱动
- pwm设备驱动--platform总线
- 设备驱动的艺术之旅 - Platform 总线的应用
- 基于platform总线的mini2440的led设备驱动例子
- Linux驱动-platform总线设备驱动
- 基于platform总线的中断(按键)字符设备驱动设计
- 基于platform总线的中断(按键)字符设备驱动设计
- 基于platform总线的中断(按键)字符设备驱动设计
- 基于 platform 总线的设备驱动编写模式:
- 基于platform总线的中断(按键)字符设备驱动设计
- 洋河梦之蓝M9政府专供 是真是假?
- C++笔记之抽象类与接口类
- yii2的文件土拍你上传类UploadedFile的使用
- MySQL高可用数据库内核深度优化的四重定制
- MYSQL 5.7 INNODB 表空间
- platform总线--驱动发现设备的过程
- 【codevs 1021】玛丽卡
- 平时积累的一些java基础
- iOS开发--打印NSRange,CGRect,CGPoint等结构体
- Leetcode_6 : ZigZag Conversion
- Angularjs2 公共基本列表组件
- frufer 序列(no look)
- Qt5笔记之Qt5插件的生成与加载及json文件的读取
- 怎么上传新建项目到git上面