总线时物理存在,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.h struct klist_iter { struct klist *i_klist; struct klist_node *i_cur; }; //----include/linux/Klist.h struct klist_node; struct klist { spinlock_t k_lock; struct list_head k_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_head n_node; struct kref n_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_t k_lock; struct list_head k_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结构变量,然后注册到系统。