Linux设备驱动模型之上层容器

 记得刚工作的时候,当时为了查证一个I2C的问题,硬着头皮跟了一下Linux下i2c总线的驱动代码,两个字:吐血。主要是跟着跟着就跟到了Linux设备驱动模型的核心里去了,这里面数据结构复杂,函数调用的关系跟着跟着就不知道哪对哪了。归根结底,没仔细阅读过Linux设备驱动模型的核心代码,我想只要理解了这部分内容,上层的一些总线驱动比如i2c、spi、platform等等都将会变得简单。于是,狠下心来好好研究一下Linux设备驱动的核心代码。虽然很困难,但是贵在尝试,贵在坚持。

 

    好了,废话这么多,直接看门见山了,Linux设备驱动的核心:bus、device、driver。下面我们仔细分析这几个数据结构及它们之间的关系。宏观上来看,系统里所有的设备(device)都通过总线(bus)相连,每个设备(device)都需要有一个驱动(device_driver)来驱动它,为了管理方便(应该是这个原因吧?),一条总线(bus)上会有两条链:一条链接系统中所有通过此总线(bus)与CPU进行通信的设备(device),另一条链接系统中所有挂载在此总线(bus)上的设备(device)的设备驱动(device_driver),每个设备(device)都对应唯一的一个驱动(device_driver),一个驱动(device_driver)可以驱动不同的设备(device)。

 

一、总线(bus)

    总线是CPU与外设之间的通道。在设备模型中,所有的设备都通过总线相连。用bus_type结构表示总线( <linux/device.h> ):

 

struct bus_type {
 const char  *name;//总线名,显示在/sysfs/bus/目录下,如/sysfs/bus/i2c
 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 (*suspend_late)(struct device *dev, pm_message_t state);
 int (*resume_early)(struct device *dev);
 int (*resume)(struct device *dev);

 struct dev_pm_ops *pm;//电源管理接口,这里不讨论

 struct bus_type_private *p;//总线的私有数据,定义于driver/base/base.h
};

 

struct bus_type_private {
 struct kset subsys;//代表该总线的kset
 struct kset *drivers_kset;//挂接在该总线上的驱动的kset,如/sysfs/bus/i2c/drivers
 struct kset *devices_kset;//挂载在该总线上的设备的kset,如/sysfs/bus/i2c/devices
 struct klist klist_devices;//挂接在该总线上的设备链表
 struct klist klist_drivers;//挂接在该总线上的驱动链表
 struct blocking_notifier_head bus_notifier;//通知链,暂不讨论
 unsigned int drivers_autoprobe:1;
 struct bus_type *bus;//指向与之关联的bus_type
};

 

1、总线的注册

    函数bus_register用来向系统注册一个总线:int bus_register(struct bus_type *bus);

    以i2c总线为例,看看内核如何注册i2c总线的。首先,要准备名为“i2c”的bus_type结构:

 

/* drivers/i2c/i2c-core.c */

struct bus_type i2c_bus_type = {
 .name  = "i2c",
 .dev_attrs = i2c_dev_attrs,
/* 以下暂时省略总线的方法 */
};

 

    然后调用bus_register来注册i2c总线:

 

static int __init i2c_init(void)
{
 int retval;

 retval = bus_register(&i2c_bus_type);
......
}

 

    在调用bus_register(&i2c_bus_type)之后,将会在/sysfs/bus下看到i2c目录,在/sysfs/bus/i2c目录下看到devices和drivers目录。下面简单跟一下bus_register的源码(不考虑操作失败的情况):

 

首先,分配bus_type对应的私有数据结构
 struct bus_type_private *priv;

 priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL);

将私有数据结构和bus_type对应起来

 priv->bus = bus;
 bus->p = priv;

 

设置总线的名字如“i2c” 

retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);

 

设置总线所属的kset,此处设置为bus_kset,bus_kset是在buses_init内创建的:

        int __init buses_init(void)
        {
             bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
             ......
        }
不清楚将bus_kset当成/sysfs/bus目录,而priv->subsys当成/sysfs/bus/xxx(如/sysfs/bus/i2c)目录是否更容易理解其中的层次关系?

 priv->subsys.kobj.kset = bus_kset;

 

设置总线的类型(kobj_type),关于kobj_type,请参考前一篇文章
 priv->subsys.kobj.ktype = &bus_ktype;
 priv->drivers_autoprobe = 1;

 

向系统注册总线,总线也是一个kset,当下面语句成功执行后,会在/sysfs/bus下看到相应的xxx目录(xxx对应bus->name)

 retval = kset_register(&priv->subsys);
 

创建总线的属性文件,这些文件将显示在/sysfs/bus/xxx/目录下,有关热插拔的东东,我就先直接忽略了,以后再看吧

 retval = bus_create_file(bus, &bus_attr_uevent);

 

下面代码段的成功执行将在/sysfs/bus/xxx/目录下增加devices目录,想象一下,挂在xxx总线上的所有设备应该都会在/sysfs/bus/xxx/devices目录下出现?(符号链接?)

 priv->devices_kset = kset_create_and_add("devices", NULL,&priv->subsys.kobj);

下面代码段的成功执行将在/sysfs/bus/xxx/目录下增加drivers目录,挂在xxx总线上的所有驱动也应该都会在/sysfs/bus/xxx/drivers目录下出现?(符号链接?)

 priv->drivers_kset = kset_create_and_add("drivers", NULL,&priv->subsys.kobj);
 

初始化总线上的设备链表,并且把引用和释放该链表上的设备分别交由klist_devices_get和klist_devices_put函数负责

 klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);

 

初始化总线上的驱动链表,并且不提供通过总线引用和释放该链表上的驱动
 klist_init(&priv->klist_drivers, NULL, NULL);

 

最后为总线添加属性,如果成功添加,则在/sysfs/bus/xxx/目录下创建相应的属性文件。如果bus->bus_attrs为空(如i2c_bus_type),则不进行任何操作

 retval = bus_add_attrs(bus);

 

2、总线的方法

    在bus_type结构中定义了许多方法,这些方法允许总线核心作为中间介质,在设备核心与驱动程序之间提供服务。目前,我们主要讨论其中的match方法,如果有需要,以后再添加其他方法的讨论:

         int (*match)(struct device *dev, struct device_driver *drv);

    当一个总线上的新设备或者新驱动被添加时,会一次或者多次调用这个函数,用来“匹配”总线上的设备和驱动。

 

3、总线上的一些基本操作

int bus_for_each_dev(struct bus_type *bus, struct device *start,void *data, int (*fn)(struct device *, void *));

    该函数迭代了总线上的每个设备,将相关的device结构传递给函数fn,同时传递data值。如果start是NULL,将从总线上的第一个设备开始迭代;否则将从start后的第一个设备开始迭代。总线上的所有设备均挂接在bus->p->klist_devices链表。

 

int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
       void *data, int (*fn)(struct device_driver *, void *));

     该函数和bus_for_each_dev类似,只不过它操作的对象是device_driver而已。总线上的所有设备均挂接在bus->p->klist_drivers链表。

 

二、设备(device)

    在底层,一个device结构代表一个设备:

struct device {
 struct device  *parent;

 struct device_private *p;

 struct kobject kobj;
 const char  *init_name; /* initial name of the device */
......

 struct bus_type *bus;  /* type of bus device is on */
 struct device_driver *driver; /* which driver has allocated this
        device */
 void  *driver_data; /* data private to the driver */
......

 void (*release)(struct device *dev);
};

 

    不知道为什么,总感觉这个结构有点凌乱,完全不像bus_type那样看起来整洁。目前,我们只讨论理解Linux设备驱动模型必须要掌握的一些成员。

 

struct device  *parent

    父设备指针。通常,一个父设备是某种总线或者宿主控制器。如果parent为NULL,代表该设备是顶层设备,但是这种情况很少出现。

 

struct kobject kobj

    表示该设备并把它链接到设备模型中的kobject。请注意,做为一个通用的准则,device->kobj->parent与&device->parent->kobj是相同的。

 

struct bus_type *bus

    标识了该设备链接在哪一个总线上

   

struct device_driver *driver

    管理该设备的驱动程序,一个设备只能有一个驱动程序

 

void  *driver_data

    设备驱动程序使用的私有数据

 

void (*release)(struct device *dev);

    当指向设备的最后一个引用被删除时,内核将会调用该方法。所有向核心注册的device结构都必须有一个release方法,否则内核将打印出错信息

 

struct device_private *p

    设备的私有数据,可以看出该结构体维护了比较复杂的链表,我们尽可能地避过这些链表,以免影响我们对主要部分的理解
struct device_private {
 struct klist klist_children;//该设备的子设备链表
 struct klist_node knode_parent;//此结点将device链入device的parent的children链表
 struct klist_node knode_driver;//此结点将device链入device对应的driver的devices链表
 struct klist_node knode_bus;//此结点将device链入到对应的bus的devices链表
 struct device *device;//指向与之相关的device
};

    从该数据结构中可以看出,一个device被链入到N个不同的链表中去,方便了通过某种准则快速定位某个device。

 

 

1、设备的初始化

 

 

2、设备注册

    函数device_register用于注册一个设备:

int device_register(struct device *dev)
{
 device_initialize(dev);//初始化dev里的一些成员
 return device_add(dev);//将dev加入到系统中去
}

    需要注意的是,即便是该函数调用失败,也不要直接使用kfree来释放dev占用的资源,而应该使用put_device。

 void device_initialize(struct device *dev)
{
 dev->kobj.kset = devices_kset;
 kobject_init(&dev->kobj, &device_ktype);
......
}

 

    下面,简单看一下device_add函数吧:

int device_add(struct device *dev)
{
 struct device *parent = NULL;
 ......

增加dev的引用计数

 dev = get_device(dev);
 ...... 

 

分配device的私有数据空间,并设置私有数据空间的device指针指向dev

 dev->p = kzalloc(sizeof(*dev->p), GFP_KERNEL);
 ......

 dev->p->device = dev;

 ......

 

这里,暂且不管为什么要用init_name这个字段,下面代码的作用就是设置dev在设备层次中的名字,即设置dev->kobj.name

 if (dev->init_name) {
  dev_set_name(dev, dev->init_name);
  dev->init_name = NULL;
 }

......

 

增加对dev父设备的引用计数,并设置dev和其父设备之间的关系:dev->kobj.parent设置为&dev->parent->kobj

 parent = get_device(dev->parent);
 setup_parent(dev, parent);

......

 

将dev加入到设备层次中去,即把dev->kobj加入到层次中去。注意,之前已经设置为dev->kobj的名字了,所以此处最后一个参数传递NULL。若成功调用kobject_add,则将在/sysfs/devices目录下创建名为dev->kobj.name的目录。
 error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
......

 

为该设备添加uevent属性文件,若成功调用,则在/sysfs/devices/xxx/下创建uevent文件

 error = device_create_file(dev, &uevent_attr);
......

 

为设备添加其他属性,此处不深究了

 error = device_add_attrs(dev);
......

 

将该设备加入到其对应总线上(注意,此函数并未把设备加入到总线上的devices链表上,这个动作将会在bus_attach_device函数里完成),然后再创建一些符号链接

 error = bus_add_device(dev);
......

 

产生一个热插拔事件:add

 kobject_uevent(&dev->kobj, KOBJ_ADD);

 

将此设备正式地加入到总线上,该函数下面会继续深入地分析

 bus_attach_device(dev);

 ......
}

   

    bus_attach_device从名字上看意思是将device依附到bus上,实际完成的功能是:①为device在总线上寻找合适的驱动(device_driver)并将它们关联起来 ②如果关联成功,再将device加入到总线的devices链表中。

void bus_attach_device(struct device *dev)
{
 struct bus_type *bus = dev->bus;
 int ret = 0;

 if (bus) {
  if (bus->p->drivers_autoprobe)
   ret = device_attach(dev);//为dev在总线的drivers链表中寻找合适的驱动并将它们关联起来
  WARN_ON(ret < 0);
  if (ret >= 0)
   klist_add_tail(&dev->p->knode_bus,&bus->p->klist_devices);//如果关联成功,再将device加入到总线的devices链表中
 }
}

 

int device_attach(struct device *dev)
{
 int ret = 0;

 down(&dev->sem);
 if (dev->driver) {
  ret = device_bind_driver(dev);
  if (ret == 0)
   ret = 1;
  else {
   dev->driver = NULL;
   ret = 0;
  }
 } else {
  ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
 }
 up(&dev->sem);
 return ret;
}

 

    对于device_attach,如果dev已经指向了一个driver了,即dev->driver不为NULL,那么直接调用device_bind_driver将该driver和dev进行绑定;如果dev->driver为NULL,则调用bus_for_each_drv遍历总线上的所有drivers,直到为dev寻找到合适的driver为止,并将找到的driver和dev进行绑定。

 

    我们先来看一下device_bind_driver函数,该函数为一个device绑定一个driver:

int device_bind_driver(struct device *dev)
{
 int ret;

 ret = driver_sysfs_add(dev);
 if (!ret)
  driver_bound(dev);
 return ret;
}

    driver_sysfs_add为dev的驱动dev->driver在sysfs中创建了一些符号链接(此处不深究这些符号链接了,以后有机会在慢慢研究),然后调用driver_bound函数将dev加入到与之相关联的driver的devices链表中(注:一个dev只能由一个driver来驱动,但是一个driver却可以驱动多个dev,driver的devices链表中的设备就是该driver所支持的所有设备)。

 

    接下来,再看看__device_attach函数:

static int __device_attach(struct device_driver *drv, void *data)
{
 struct device *dev = data;

 if (!driver_match_device(drv, dev))
  return 0;

 return driver_probe_device(drv, dev);
}

  

    __device_attach首先调用driver_match_device,看看该driver是否支持该dev:

static inline int driver_match_device(struct device_driver *drv,
          struct device *dev)
{
 return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}

    如果总线定义了match方法,则调用它。

 

    接下来调用driver_probe_device,尝试将device和driver进行绑定:

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

 ret = really_probe(dev, drv);

 return ret;
}

 

static int really_probe(struct device *dev, struct device_driver *drv)
{
......

 dev->driver = drv;
......

如果总线定义了probe方法,则调用它对dev进行探测

 if (dev->bus->probe) {
  ret = dev->bus->probe(dev);
......

 }

否则,如果驱动定义了探测设备的方法,则调用它对dev进行探测,可见总线定义的方法的优先级高一点

else if (drv->probe) {
  ret = drv->probe(dev);
......

 }

 

如果代码成功运行到这里,则说明找到了合适的driver,接下来就可以调用driver_bound将找到的dirver和dev绑定

 driver_bound(dev);
......
}

 

static void driver_bound(struct device *dev)
{

......

将dev加入到driver的devices链表中去

 klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);

 

三、驱动(driver)

    设备模型跟踪所有系统所知道的设备。进行跟踪的主要原因是让驱动程序核心协调驱动程序与新设备之间的关系。驱动程序由以下结构定义:

struct device_driver {
 const char  *name;//驱动的名字
 struct bus_type  *bus;//驱动依附的总线

 struct module  *owner;
 const char   *mod_name; /* used for built-in modules */

 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);
 struct attribute_group **groups;

 struct dev_pm_ops *pm;

 struct driver_private *p;//驱动的私有数据
};

 

struct driver_private {
 struct kobject kobj;//表示该driver的kobject
 struct klist klist_devices;//当前驱动程序能操作的设备链表
 struct klist_node knode_bus;//此结点将driver链入bus的drivers链表中
 struct module_kobject *mkobj;
 struct device_driver *driver;//指向与之关联的device_driver结构
};

 

1、驱动的注册

    函数driver_register用于向系统注册一个driver: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);

    driver依附的总线和该driver都定义了probe方法(remove、shutdown),则给出警告,需要重新定义driver结构,即不定义driver的probe方法,因为核心的代码总是先使用总线上定义的方法,如really_probe方法;

 

② other = driver_find(drv->name, drv->bus);
 if (other) {
  put_driver(other);
  printk(KERN_ERR "Error: Driver '%s' is already registered, "
   "aborting.../n", drv->name);
  return -EEXIST;
 }

    调用driver_find从总线上寻找名为drv->name的驱动,如果找到了,说明drv已经注册过了,则给出错误打印,返回出错。

 

③ ret = bus_add_driver(drv);

    如果drv没有被注册过,则将drv加入到该drv所要依附的总线上去。

int bus_add_driver(struct device_driver *drv)
{
 struct bus_type *bus;
 struct driver_private *priv;

......

 增加bus的引用计数

 bus = bus_get(drv->bus);
......

分配device_driver的私有数据结构

priv = kzalloc(sizeof(*priv), GFP_KERNEL);
......

关联device_driver和其私有数据结构

 priv->driver = drv;
 drv->p = priv;

 

设置device_driver所属的kset
 priv->kobj.kset = bus->p->drivers_kset;

 

将此drv加入到设备模型层次中去,若成功,将在/sysfs/bus/xxx/drivers目录下创建名为drv->name的目录
 error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
         "%s", drv->name);
......

 

如果总线设置了让driver自动进行探测,那么调用driver_attach函数来为此drv寻找合适的device,后面会继续分析该函数

 if (drv->bus->p->drivers_autoprobe) {
  error = driver_attach(drv);
......
 }

 

将此drv加入到总线上的drivers链表中去
 klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
......

 

创建一些属性文件,若创建成功,这些文件都会在/sysfs/bus/xxx/drivers/yyy目录下显示

 error = driver_create_file(drv, &driver_attr_uevent);
......

 error = driver_add_attrs(bus, drv);
......
 error = add_bind_files(drv);
......

产生一个热插拔事件:add

 kobject_uevent(&priv->kobj, KOBJ_ADD);
......

}

 

    接下来,分析driver_attach函数:

int driver_attach(struct device_driver *drv)
{
 return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}

    该函数试图遍历总线上的所有设备,从而找出一个drv支持的设备,并将它们绑定。匹配drv和设备的操作由函数__driver_attach完成。

 

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.
  */

匹配drv和dev,在上一节已有所分析

 if (!driver_match_device(drv, dev))
  return 0;

 if (dev->parent) /* Needed for USB */
  down(&dev->parent->sem);
 down(&dev->sem);

 

如果设备还没有绑定driver,即dev->driver为NULL,那么调用driver_probe_device来尝试将这个设备和该driver进行绑定,关于该函数,上一节也有过简单的分析
 if (!dev->driver)
  driver_probe_device(drv, dev);
 up(&dev->sem);
 if (dev->parent)
  up(&dev->parent->sem);

 return 0;
}

 

四、总结

     下面总结一下bus_type、device和device_driver之间关系:

1、bus_type 相当于一个容器,是device 和device_driver 的管理机构,它包含了一个device 集合(kset)和一个device_driver 集合(kset)。其中,device集合包含了挂在该总线下的所有设备,这些设备通过链表链接起来;device_driver集合包含了挂在该总线下的所有驱动程序,这些驱动程序通过链表链接起来;


2、device_driver 挂在某个bus_type 下面,包含了一个device 集合(kset),表示这个驱动程序可以驱动的所有设备,同样通过链表来管理这些设备。device_driver 还包含一个bus_type 指针,表示驱动程序所在的总线;


3、device 挂在某个bus_type 下面,包含了一个device_driver 指针,表示这个设备对应的设备驱动程序。device 还包含一个bus_type 指针,表示设备所在的总线;


    需要说明的是,一个实际的总线在设备驱动模型中是用两个结构表示的:bus_type 和device。bus_type 代表总线类型,出现在/sys/bus/目录下;device 代表总线设备,出现在/sys/devices/目录下,这表明实际的总线本质上是一种设备。

 

未完,待更新......


个人补充:

__device_attach-->driver_probe_device(drv, dev);

__driver_attach-->driver_probe_device(drv, dev);

device_register和驱动driver_register先后注册的影响和关系
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值