总线时物理存在,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开始:
{
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结构变量,然后注册到系统。