分享

关于设备模型、设备与驱动关联的过程分析 - linux设备/驱动

 langhuayipian 2010-08-31

关于设备模型、设备与驱动关联的全过程分析 

 

      本文的大多数内容参考了:对于网络上设备与驱动关联的全过程分析(I2C方式)一文。在此特别感谢这位作者前辈的无私奉献。 本人只是添加了一些个人理解和补充。

          在Linux操作系统中,驱动程序的加载分为两种:内核启动时自动加载和用户手动加载;硬件设备也可以采用两种方式添加到系统中:在系统启动前及系统运行时的热插拨。下面,我们以arm体系结构下的at91处理器中的I2C控制器为例,介绍一下硬件设备及相关的驱动程序是如何绑定及松绑的。

 

 

1.     平台驱动注册过程

    具体的目录如下:

关于设备模型、设备与驱动关联的全过程分析... 1

1.1 at91_i2c_init()函数... 1

1.2 platform_driver_register()函数... 2

1.3     driver_register()函数... 4

1.4 bus_add_driver()函数... 5

1.5 dd.c文件driver_attach()函数... 7

 

1.1 at91_i2c_init()函数
在文件drivers/i2c/busses/i2c-at91.c中,定义了结构体struct platform_driver并进行了初始化,通过使用module_init()宏进行声明,当模块被加载到内核时会调用 at91_i2c_init()函数。在此函数中,调用了platform_driver_register()函数来完成注册。

 

static struct platform_driver at91_i2c_driver = {

.probe= at91_i2c_probe,

.remove = __devexit_p(at91_i2c_remove),

.suspend= at91_i2c_suspend,

.resume= at91_i2c_resume,

.driver= {

.name
= "at91_i2c",
.owner
= THIS_MODULE,

},

};
static int __init at91_i2c_init(void)
{
     return
platform_driver_register(&at91_i2c_driver);
}

 

1.2 platform_driver_register()函数
在文件drivers/base/platform.c中,实现并导出了platform_driver_register()函数,以便使其他模块中的函数可以调用此函数。它在完成简单的包装后,调用了driver_register()函数,完成了从平台实现到Linux内核实现的过渡。

int platform_driver_register(struct platform_driver *drv)
{

 

   /*设置成platform_bus_type这个很重要,因为driverdevice是通过bus联系在一起的,具体在本例中是通过  platform_bus_type中注册的回调例程和属性来是实现的, driverdevice的匹配就是通过 platform_bus_type注册的回到例程platform_match ()来完成的。*/

drv->driver.bus = &platform_bus_type;

//really_probe函数中,回调了platform_drv_probe函数
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;

if (drv->suspend)
drv->driver.suspend = platform_drv_suspend;

if (drv->resume)
drv->driver.resume = platform_drv_resume;
return driver_register(&drv->driver);

}

EXPORT_SYMBOL_GPL(platform_driver_register);

platform_driver_register()函数中,对总线附值使用了如下语句,给总线类型和相关操作函数赋值。drv->driver.bus = &platform_bus_type;

 

struct bus_type platform_bus_type = {

    .name       = "platform",

    .dev_attrs  = platform_dev_attrs,

    .match      = platform_match,

    .uevent     = platform_uevent,

    .suspend    = platform_suspend,

    .suspend_late   = platform_suspend_late,

    .resume_early   = platform_resume_early,

    .resume     = platform_resume,

};

EXPORT_SYMBOL_GPL(platform_bus_type);

总线bus是联系driverdevice的中间枢纽。Device通过所属的bus找到driver.

struct bus_type {

    const char      * name;

 

    struct subsystem    subsys;

    struct kset     drivers;  // drivers-list链表包含了所有注册到该busdriver.

    struct kset     devices; // devices ->list链表包含了所有注册到该busdevices.

    struct klist        klist_devices; //

    struct klist        klist_drivers;

    struct blocking_notifier_head bus_notifier;

    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, char **envp,

                  int num_envp, char *buffer, int buffer_size);

    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 (*suspend_late)(struct device * dev, pm_message_t state);

    int (*resume_early)(struct device * dev);

    int (*resume)(struct device * dev);

};

在此,我们需要关注一下platform_match()platform_drv_probe()函数。platform_match() 函数确定驱动与设备的关联,而platform_drv_probe()函数会在随后介绍的函数中被调用。
//比较驱动信息中的name与设备信息中的name两者是否一致
static int platform_match(struct device * dev, struct device_driver * drv)
{

struct platform_device *pdev = container_of(dev, struct platform_device, dev);

//通过device找到他所属的platform_device

return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) = = 0);

}

linux kernel源码中有一个神奇的container_of宏,可以根据一个结构体中成员的地址计算出结构体自身的地址。

完全可以这样理解container_of宏:

#define container_of(ptr, type, member) (type *)((char *)ptr - offset_of(type,member)) 
 

对于实现匹配关系的设备和驱动应有如下关系:

platform_device -device device通过链表连接到BUS.

platform_driver-driverdevice_driver类型的结构体):device通过链表连接到BUS.

platform_device -device->device_driver(指针)该指针指向了platform_driver-driver

这样platform_deviceplatform_driver 就通过platform_device -device联系在一起了。

因此我们可以通过platform_device -device 找到对应的platform_deviceplatform_driver

这样就很容易理解下面的platform_drv_probe函数了:

static int platform_drv_probe(struct device *_dev)

{

    struct platform_driver *drv = to_platform_driver(_dev->driver);

    struct platform_device *dev = to_platform_device(_dev);

    return drv->probe(dev);//转去执行platform_driver中定义的probe函数。

}

对于挂到总线上的设备、驱动是通过:ksetkobject建立各个层次之间的联系。对于ksetkobject。可以参考DDR3设备管理的章节。

至此对设备(device)、总线(bus)、驱动(device_driver)的关系有了一个大概的了解。

对于2.6内核中更上面一层的封装:platform_deviceplatform_driver也有了大概的了解。

 

1.3   driver_register()函数
在文件drivers/base/driver.c中,实现了driver_register()函数。在此函数中,初始化结构体struct device_driver中的klist_deviceunloaded字段,通过klist_device字段,可以保存此驱动支持的设备链表,通过完成接口机制,完成线程间的同步。链表和完成接口的详细信息可以参考文献[1]。返回bus_add_driver()函数的运行结果。
/** *driver_register - register driver with bus *
@drv:driver to register *

We pass off most of the work to the bus_add_driver() call, *
since most of the things we have to do deal with the bus structures.

*The one interesting aspect is that we setup drv->unloaded *
as a completion that gets complete when the driver reference count reaches 0. */

int driver_register(struct device_driver * drv)
{

//如果总线的方法和设备自己的方法同时存在,将打印告警信息

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);

}

klist_init(&drv->klist_devices, NULL, NULL); //driver驱动上的设备链表清空

init_completion(&drv->unloaded);

return bus_add_driver(drv); //将本driver驱动注册登记到drv->bus所在的总线上。

在内核中以driver成员变量kobject,表示driver.

所谓的注册即是: driver中的内核成员对象kobject的链表kobj->entry,加入到bus总线的bus_type ->kset->list 链表中。这样就可以通过总线,找到所有定义在该总线下的kobject(driver).

}

1.4 bus_add_driver()函数
在文件drivers/base/bus.c中实现了bus_add_driver()函数,它通过语句klist_add_tail(&drv->knode_bus, &bus->klist_drivers); 将驱动信息保存到总线结构中,在设备注册过程中,我们就可以明白此语句的作用了。在此语句之前,调用了driver_attach()函数。

/** *bus_add_driver - Add a driver to the bus. *@drv:driver. * */

 

int bus_add_driver(struct device_driver *drv)
{
   struct bus_type * bus = get_bus(drv->bus);
//获取总线内容,即前面定义的

platform_bus_type

int error = 0;

if (!bus)
return 0;

pr_debug("bus %s: add driver %s\n", bus->name, drv->name);

 /*kobject_set_name :设置kboj->name[KOBJ_NAME_LEN]数组内容,如果KOBJ_NAME_LEN长度不够,会调用kmalloc申请之后kobj->k_name指针或者指向kboj->name或者指向kmalloc返回地址*/

error = kobject_set_name(&drv->kobj, "%s", drv->name);

//设置kboj->name成员= drv->name

if (error)

goto out_put_bus; //释放总线

 

drv->kobj.kset = &bus->drivers;  //设置device_driver结构体的kobj.kset成员变量(是一个kset指针变量)。 很重要,它指向总线(bus)kset成员,bus->drivers成员driverskset类型。在platform_driver_register ()函数中有如下总线赋值语句:drv->driver.bus = &platform_bus_type;

platform_bus_type为内核定义的一类总线(bus)。此处的drv->kobj.kset指针指向了总线(bus) kset成员。总线的kset成员是kobject的顶层容器,包含了定义在该总线下面所有的kobject

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

/* kobject_register()理解drvkobj登记到管理它的bus->kset集合上去。同时再根据层级关系创建相应的目录文件 

注册登记该kobj,如果该kobj属于某个kset,那么将自己的entry节点(list_head)挂接到该ksetlist链表上,以示自己需要该kset的滋润,同时kobj->parent=&kset->kobj,parent指向kset用来管理自己的kobj
如果该kobjparent已经定义,那么简单的将parent的引用计数加1(如果该kobj不属于kset,而属于parent,那么简单的将parent的引用计数加1.
对于kobj属于某个kset的情况,可以实现kset向下查找kobj,也可以实现kobj向上查找kset

对于kobj属于某个parent的情况,查找只能是单向的,只能kobj找到parent,parent不能查找该parent挂接的kobj们。parent是用来明显建立亲子关系图的标志性变量,当然在kset也能若隐若现的显露出这种关系,但总不如parent正宗和高效。之后调用create_dir()创建该kobjsysfs中的目录文件
最后调用kobject_uevent()KOBJ_ADD事件通知到用户空间的守护进程*/

 

if ((error = kobject_register(&drv->kobj))) //driver挂到bus总线上。
goto out_put_bus;

error = driver_attach(drv); //查找所有注册在该bus上的device,当有devicenamedriver->name一样时。即找到了该driver对应的设备。在里面调用的really_probe()函数中,实现了设备与驱动的绑定。语句如下:dev->driver = drv;ret = drv->probe(dev)

 

if (error)
goto out_unregister;
klist_add_tail(&drv->knode_bus, &bus->klist_drivers);
module_add_driver(drv->owner, drv);

/*所以一个驱动需要维持住1klist链条和一个kobj层次结构--驱动drv->kobj对象,内核一方面使用该kobjsysfs中建立统一的与该kobj对应的目录对象供用户空间访问,另一方面使用该kobj的引用计数来获悉该kobj设备的繁忙与空闲情况,
//当本kobj对象的引用计数到达0,只要其他条件允许,那么说明集成本kobj的结构体对象不再使用,内核得知这个情况很重要,因为这对内核进行进一步的决策提供了详细的证据资料,进而对物理设备进行细致的电源管理成了可能,//:hub1上的所有端口设备都被拔掉之后,hub1就可以安全的进入省电模式了,而这个功能在2.4内核中是找不到的.

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",
__FUNCTION__, drv->name);
}
error =
add_bind_files(drv);
if (error) {
/* Ditto */
printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
__FUNCTION__, drv->name);
}
return error;

out_unregister:
kobject_unregister(&drv->kobj);

out_put_bus:
put_bus(bus);
return error;

}

 

下面说明设备驱动是如何知道总线对应设备的

1.5 dd.c文件driver_attach()函数

在文件drivers/base/dd.c中,实现了设备与驱动交互的核心函数。

1.5.1 driver_attach()函数
函数driver_attach()返回bus_for_each_dev()函数的运行结果。bus_for_each_dev()函数的原型如下:
int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data,
int (*fn) (struct device *, void *));
该函数迭代了在总线上的每个设备,将相关的device结构传递给fn,同时传递data值。如果startNULL,将从总线上的第一个设备开始迭代;否则将从start后的第一个设备开始迭代。如果fn返回一个非零值,将停止迭代,而这个值也会从该函数返回(摘自<<Linux设备驱动程序>>第三版)。
该函数是如何知道总线上的每个设备的呢?在设备注册过程中,我会详细介绍。
/* *drivers/base/dd.c - The core device/driver interactions. * * This file contains the (sometimes tricky) code that controls the *interactions between devices and drivers, which primarily includes *driver binding and unbinding. *//** *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);
}
1.5.2 __driver_attach()函数
函数__driver_attach()在调用driver_probe_device()函数前,需要进行线程间的互斥处理。
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 (dev->parent)

down(&dev->parent->sem);
down(&dev->sem);

 

if (!dev->driver)
driver_probe_device(drv, dev);

up(&dev->sem);

if (dev->parent)

up(&dev->parent->sem);
return 0;

}  

 

1.5.3 driver_probe_device()函数

driver_probe_device()函数中,调用了match函数platform_match(),如果它返回0,表示驱动与设备不一致,函数返回;否则,调用really_probe()函数。
/** * driver_probe_device - attempt to bind device & driver together
* @drv: driver to bind a device to * @dev: device to try to bind to the driver * * First, we call the bus's match function, if one present, which should * compare the device IDs the driver supports with the device IDs of the * device. Note we don't do this ourselves because we don't know the * format of the ID structures, nor what is to be considered a match and * what is not. * * This function returns 1 if a match is found, an error if one occurs *(that is not -ENODEV or -ENXIO), and 0 otherwise. * * This function must be called with @dev->sem held.  When called for a * USB interface, @dev->parent->sem must be held as well. */

 

int driver_probe_device(struct device_driver * drv, struct device * dev)
{

struct stupid_thread_structure *data;
    struct task_struct *probe_task;
    int ret = 0;
    if (!device_is_registered(dev))
       return -ENODEV;
    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);
    data = kmalloc(sizeof(*data), GFP_KERNEL);

  if (!data)
       return -ENOMEM;
    data->drv = drv;
    data->dev = dev;

if (drv->multithread_probe) {
       probe_task = kthread_run(really_probe, data,
                   "probe-%s", dev->bus_id);
       if (IS_ERR(probe_task))
           ret = really_probe(data);
    } else
       ret = really_probe(data);
done:
    return ret;
}

struct stupid_thread_structure {
    struct device_driver *drv;
    struct device *dev;
};

1.5.4 really_probe()函数
really_probe()函数中,实现了设备与驱动的绑定。语句如下:dev->driver = drv;
ret = drv->probe(dev)
; probe()函数的实现如下:
include/linux/platform_device.h
#define to_platform_device(x) container_of((x), struct platform_device, dev)
drivers/base/platform.c
#define to_platform_driver(drv) (container_of((drv), struct platform_driver, driver))

 

static int platform_drv_probe(struct device *_dev)
{

struct platform_driver *drv = to_platform_driver(_dev->driver);

    struct platform_device *dev = to_platform_device(_dev);
    return drv->probe(dev);
}

在此函数中,回调了我们在i2c-at91.c文件中实现的探测函数at91_i2c_probe(),至此,平台驱动的注册过程结束。
static atomic_t probe_count = ATOMIC_INIT(0);

static DECLARE_WAIT_QUEUE_HEAD(probe_waitqueue);

 

static int really_probe(void *void_data)
{
    struct stupid_thread_structure *data = void_data;
    struct device_driver *drv = data->drv;
    struct device *dev = data->dev;

int ret = 0;
    atomic_inc(&probe_count);
    pr_debug("%s: Probing driver %s with device %s\n",
        drv->bus->name, drv->name, dev->bus_id);

WARN_ON(!list_empty(&dev->devres_head));

 

dev->driver = drv;
    if (driver_sysfs_add(dev)) {
       printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
           __FUNCTION__, dev->bus_id);
       goto probe_failed;
    }

    if (dev->bus->probe) {
       ret = dev->bus->probe(dev);
       if (ret)
           goto probe_failed;
    } else if (drv->probe) {
       ret = drv->probe(dev);
       if (ret)
           goto probe_failed;
    }
    //
设备与驱动绑定后,对系统中已注册的组件进行事件通知。
    driver_bound(dev);
    ret = 1;
    pr_debug("%s: Bound Device %s to Driver %s\n",
        drv->bus->name, dev->bus_id, drv->name);
    goto done;
probe_failed:
    devres_release_all(dev);
    driver_sysfs_remove(dev);
    dev->driver = NULL;
    if (ret != -ENODEV && ret != -ENXIO) {
       /* driver matched but the probe failed */
       printk(KERN_WARNING
              "%s: probe of %s failed with error %d\n",
              drv->name, dev->bus_id, ret);
    }
   /* * Ignore errors returned by ->probe so that the next driver can try  * its luck. */

ret = 0;
done:
    kfree(data);
    atomic_dec(&probe_count);
    wake_up(&probe_waitqueue);
    return ret;
}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多