Chinaunix首页 | 论坛 | 博客
  • 博客访问: 66742
  • 博文数量: 21
  • 博客积分: 290
  • 博客等级: 二等列兵
  • 技术积分: 286
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-30 16:16
文章分类
文章存档

2019年(3)

2017年(1)

2012年(17)

我的朋友

分类: LINUX

2012-04-23 20:32:38

设备模型之二 总线设备驱动                          

           2012-04-19

一、概要分析
     Linux设备模型中三个很重要的概念就是总线、设备和驱动,即bus,device和driver。它们分别对应的数据结构分别为struct bus_type,struct device和struct device_driver。
    总线是处理器与一个或多个设备之间的通道,在设备模型中,所有的设备都通过总线相连。在最底层,Linux系统中的每一个设备都用device结构的一个实例来表示。而驱动则是使总线上的设备能够完成它应该完成的功能

    在系统中有多种总线,如PCI总线、SCSI总线等。系统中的多个设备和驱动是通过总线让它们联系起来的。在bus_type中两个很重要的成员就是 struct kset drivers和struct kset devices 2.6.32.2中已被 struct bus_type_private 结构包含 它分别代表了连接在这个总线上的两个链,一个是设备链表,另一个则是设备驱动链表。也就是说,通过一个总线描述符,就可以找到挂载到这条总线上的设备,以及支持该总线的不同的设备驱动程序。

1.1 bus_type 相当于一个容器,是device 和device_driver 的管理机构,它  

包含了一个device 集合(kset)和一个device_driver 集合(kset),分别表 

示挂在这个总线下的所有设备和所有设备驱动程序。

1.2 device_driver 挂在某个bus_type 下面,包含了一个device 集合(kset),

表示这个驱动程序操作(或控制)的所有设备。device_driver 还包含一个 

bus_type 指针,表示驱动程序所在的总线。

1.3 device 挂在某个bus_type 下面,包含了一个device_driver 指针,表示   

这个设备对应的设备驱动程序。device 还包含一个bus_type 指针,表示 

设备所在的总线。需要说明的是,一个实际的总线在设备驱动模型中是用两 

个结构表示的:bus_type 和device。bus_type 代表总线类型,出现在

/sys/bus/目录下;device 代表总线设备,出现在/sys/devices/录下,

表明实际的总线本质上是一种设备。


1.2 总线、设备与驱动的绑定
    在系统启动时,它会对每种类型的总线创建一个描述符,并将使用该总线的设备链接到该总线描述符的devices链上来。也即是说在系统初始化时,它会扫描连接了哪些设备,并且为每个设备建立一个struce device变量,然后将该变量链接到这个设备所连接的总线的描述符上去。另一方面,每当加载了一个设备驱动,则系统也会准备一个struct device_driver结构的变量,然后再将这个变量也链接到它所在总线的描述符的drivers链上去。       
    对于设备来说,在结构体struct device中有两个重要的成员,一个是struct bus_type *bus,另一个是struct device_driver *driver。bus成员就表示该设备是链接到哪一个总线上的,而driver成员就表示当前设备是由哪个驱动程序所驱动的。对于驱动程序来说,在结构体struct device_driver中也有两个成员,struct bus_type *bus和struct list_head devices,这里的bus成员也是指向这个驱动是链接到哪个总线上的,而devices这个链表则是表示当前这个驱动程序可以去进行驱动的那些设备。一个驱动程序可以支持一个或多个设备,而一个设备则只会绑定给一个驱动程序
     对于device与device_driver之间建立联系的方式,主要有两种方式。第一种,在计算机启动的时候,总线开始扫描连接在其上的设备,为每个设备建立一个struct device变量并链接到该总线的devices链上,然后开始初始化不同的驱动程序,驱动程序到它所在的总线的devices链上去遍历每一个还没有被绑定给某个驱动的设备,然后再查看是否能够支持这种设备,如果它能够支持这种设备,则将这个设备与这个驱动联系起来。即,将这个设备的device变量加到驱动的devices链上,同时让struct device中的device_driver指向当前这个驱动。第二种则是热插拔。也即是在系统运行时插入了设备,此时内核会去查找在该bus链上注册了的device_driver,然后再将设备与驱动联系起来。

1.3 Question:

a)设备与驱动根据什么规则联系起来?

b)驱动程序/设备如何找到总线设备的?

以后内容采用 2.6.32.2 内核,有一些跟以前不一样。以后采用使用方法形式来说明:(例子在device_module文件夹中)总线设备驱动操作方法非常的类似,我主要以bus进行说明而已。设备和驱动主要按照bus方法进行操作即可。

二、总线bus

第一步:定义数据结构

【NOTE】很少 bus_type 成员要求初始化; 大部分由设备模型核心处理. 但是, 我们确实不得不指定总线的名子, 以及任何伴随它的方法。

头文件:

#include 

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 bus_type_private *p;            /*总线类型的私有数据*/         

};

struct bus_type_private {

  struct kset subsys;                  /*与该总线相关的子系统*/

  struct kset *drivers_kset;           /*总线驱动程序的KSET*/

  struct kset *devices_kset;           /*挂载在总线上的设备KSET*/

  struct klist klist_devices;          /*挂载在总线上的设备链表*/   

  struct klist klist_drivers;           /*驱动程序链表*/

  struct blocking_notifier_head bus_notifier;

  unsigned int drivers_autoprobe:1;

  struct bus_type *bus;

};

Q1:后来注册的设备\驱动如何添加到 struct kset *devices_kset 、struct kset *drivers_kset?

Q2:后来注册的设备\驱动如何寻找到总线?

 

第二步:实现总线方法

   int (*match)(struct device *device, struct device_driver *driver);  

   这个方法被调用, 大概多次, 无论何时一个新设备或者驱动被添加给这个总线. 它应当返回一个非零值如果给定的设备可被给定的驱动处理. (我们马上进入设备和 device_driver 结构的细节). 这个函数必须在总线级别处理, 因为那是合适的逻辑存在的地方; 核心内核不能知道如何匹配每个可能总线类型的设备和驱动. 

int (*uevent) (struct device *device, char **envp, int num_env p, char *buffer, int buffer_size);  

这个模块允许总线添加变量到环境中, 在产生一个热插拔事件在用户空间之前。

第三步:注册总线

int bus_register(struct bus_type *bus); 

注册总线一定要检查其返回值,可能会注册失败。

/*退出时注销总线*/

void bus_unregister(struct bus_type *bus);  

第四步:创建总线属性文件==>用于与空间进行交互

Q:如何将传送进去的 buf 跟内核交互==>读出的buf 哪里来,写入的 buf 要到哪里去?

struct bus_attribute {

  struct attribute attr;

  ssize_t (*show)(struct bus_type *bus, char *buf);

  ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);

};

#define BUS_ATTR(_name, _mode, _show, _store) \

  struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)

 

  int bus_create_file(struct bus_type *bus, struct bus_attribute *attr);

 /*删除属性文件*/

  void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr);  

 

三、设备device

#include 

struct device {

  struct device *parent;       /*父设备*/

  struct device_private *p;       /*设备结构的私有数据*/

  struct kobject kobj;              /*kobject对象*/

  const char *init_name;     /*设备初始化名字替换了 bus_id */

  struct device_type *type;   /*设备方法==>类似于 kobject_ktype*/ 

  struct semaphore sem;       /*信号量*/

  struct bus_type *bus;   /*设备所在的总线*/

  struct device_driver *driver;/*该设备的驱动,怎样跟驱动匹配呢?*/

  void *platform_data;       /*平台总线私有数据*/

  struct dev_pm_info power;

#ifdef CONFIG_NUMA

   int numa_node; /* NUMA node this device is close to */

#endif

   u64 *dma_mask; /* dma mask (if dma'able device) */

   u64 coherent_dma_mask;

   struct device_dma_parameters *dma_parms;

   struct list_head dma_pools; /* dma pools (if dma'ble) */

   struct dma_coherent_mem *dma_mem; 

   struct dev_archdata archdata;

   dev_t devt; /* dev_t, creates the sysfs "dev" */

   spinlock_t devres_lock;

   struct list_head devres_head;

   struct klist_node knode_class;

   struct class *class;

   const struct attribute_group **groups;

   void (*release)(struct device *dev); /*为何必须有呢?没有行吗*/

};

struct device_private {

   struct klist klist_children;

   struct klist_node knode_parent;

   struct klist_node knode_driver;

   struct klist_node knode_bus;

   void *driver_data;                   /*我们所关心的数据*/

   struct device *device;

};

Q1:利用结构中的kobject 跟 kset 进行关联。

Q2:在进行设备注册时,就将设备添加到总线中去,利用bus_add_device(dev) 实现的。

大家可以去 drivers/base/core.c 看源代码
int device_register(struct device *dev)
{
    device_initialize(dev);               //初始化设备
    return device_add(dev);              //添加设备
}

void device_initialize(struct device *dev)
{

    /*

     *设置设备的kobject所属集合,利用了kobject 挂载到devices_kset其  

     *实在第一层,sys/devices/... 而在sys/bus/xxx 目录下是个链接文件
     */
    dev->kobj.kset = devices_kset;             

kobject_init(&dev->kobj, &device_ktype);    //初始化设备的kobject 

INIT_LIST_HEAD(&dev->dma_pools);//初始化设备的DMA池,用于传大数
    mutex_init(&dev->mutex);                 //初始化互斥锁
    lockdep_set_novalidate_class(&dev->mutex);
    spin_lock_init(&dev->devres_lock);     //初始化自旋锁,用于同步子设备链表
    INIT_LIST_HEAD(&dev->devres_head);      //初始化子设备链表头
    device_pm_init(dev);
    set_dev_node(dev, -1);
}

int device_add(struct device *dev)
{
       ...             
     error = device_private_init(dev);       //初始化设备的私有成员
      ...
     parent = get_device(dev->parent);         //增加父设备kobject的引用
     setup_parent(dev, parent);             //设置该设备kobject父对象
      ...
     error = kobject_add(&dev->kobj, dev->kobj.parent, NULL); //将设备kobject添加进父对象设备模型

      ... 
     error = bus_add_device(dev);          //将设备添加进总线中

...
      bus_probe_device(dev);            //现在该为设备在总线上寻找合适的驱动了
    ...
}
    到了这里就将上面的几个问题给解决了,这对于驱动也是类似的在这里就总结一下一个设备添加和删除过程:当在总线上添加(或删除)设备时,会依次遍历总线上的所有驱动程序,调用总线定义的match()方法检查要加入(或删除)的设备是否和驱动程序“匹配”,如果匹配成功,则对设备调用总线或驱动程序中定义的probe(或remove)方法。添加设备时产生KOBJ_ADD uevent,删除设备时产生KOBJ_REMOVE uevent

设备添加流程:

device_register(dev)->device_add(dev)-> kobject_uevent(KOBJ_ADD)

device_add(dev)->bus_attach_device(dev)->device_attach(dev)

->__device_attach(drv,dev)->bus_type():match(dev,drv)

then ->driver_probe_device()->really_probe()

->bus_type:probe()或device_driver:probe()

设备删除

device_unregister(dev)->device_del(dev)->bus_remove_device(dev)

->device_release_driver(dev) ->__ device_release_driver(dev)

->bus_type:remove()或device_driver:remove()

device_del(dev)->kobject_uevent(KOBJ_REMOVE) 

 四、驱动driver

 (1)添加驱动程序
 driver_register(drv)-> bus_add_driver (drv)-> driver_attach(drv)
 ->__driver_attach(dev,drv) ->bus_type():match(dev,drv) then
->driver_probe_device()->really_probe()->bus_type:probe() device_driver:probe()

 driver_register(drv)-> kobject_uevent(KOBJ_ADD)
 (2) 驱动程序删除
  driver_unregister(dev)->bus_remove_driver(drv)-> driver_detach(drv)
 ->__ device_release_driver(dev) [dev-drv 管理的所有设备]
 ->bus_type:remove() 或device_driver:remove()
 

五、内嵌的设备和驱动 【linux设备驱动程序第三版】
     设备结构包含设备模型核心需要的来模型化系统的信息. 大部分子系统, 但是, 跟踪关于它们驻留的设备的额外信息. 结果, 对设备很少由空设备结构所代表; 相反, 这个结构, 如同 kobject 结构, 常常是嵌入一个更高级的设备表示中. 如果你查看 struct pci_dev  的定义或者 struct usb_device 的定义, 你会发现一个 struct device 埋在其中. 常常地, 低层驱动甚至不知道 struct device, 但是有例外. lddbus 驱动创建它自己的设备类型( struct ldd_device ) 并且期望单独的设备驱动来注册它们的设备使用这个类型. 它是一个简单结构: 

struct ldd_device { 

 char *name; 

 struct ldd_driver *driver; 

 struct device dev;  

};  

#define to_ldd_device(dev) container_of(dev, struct ldd_device, dev);  

这个结构允许驱动提供一个实际的名子给设备( 这可以清楚地不同于它的总线 ID, 存储于设备结构) 以及一个这些驱动信息的指针. 给真实设备的结构常常还包含关于供应者信息, 设备型号, 设备配置, 使用的资源, 等等. 可以在 struct pci_dev () 或者 struct usb_device () 中找到好的例子. 一个方便的宏( to_ldd_device ) 也为 struct ldd_device 定义, 使得容易转换指向被嵌入的结构的指针为 ldd_device 指针. 

lddbus 输出的注册接口看来如此: 

int register_ldd_device(struct ldd_device *ldddev)  

 ldddev->dev.bus = &ldd_bus_type; 

 ldddev->dev.parent = &ldd_bus; 

 ldddev->dev.release = ldd_dev_release; 

 strncpy(ldddev->dev.bus_id, ldddev->name, BUS_ID_SIZE); 

 return device_register(&ldddev->dev);  

EXPORT_SYMBOL(register_ldd_device); 

这里, 我们简单地填充一些嵌入的设备结构成员( 单个驱动不应当需要知道这个 ), 并且注册这个设备到驱动核心. 如果我们想添加总线特定的属性到设备, 我们可在这里做。如同大部分驱动核心结构的情形, device_driver 结构常常被发现嵌到一个更高级的, 总线特定的结构. lddbus 子系统不会和这样的趋势相反, 因此它已定义了它自己的 ldd_driver 结构: 

struct ldd_driver { 

 char *version; 

 struct module *module; 

 struct device_driver driver; 

 struct driver_attribute version_attr;  

};  

#define to_ldd_driver(drv) container_of(drv, struct ldd_driver, driver);  

这里, 我们要求每个驱动提供特定当前软件版本, 并且 lddbus 输出这个版本字串为它知道的每个驱动. 总线特定的驱动注册函数是: 

int register_ldd_driver(struct ldd_driver *driver) 

{  

 int ret; 

 driver->driver.bus = &ldd_bus_type; 

 ret = driver_register(&driver->driver); 

 if (ret) 

 return ret; 

 driver->version_attr.attr.name = "version"; 

 driver->version_attr.attr.owner = driver->module; 

 driver->version_attr.attr.mode = S_IRUGO; 

 driver->version_attr.show = show_version; 

 driver->version_attr.store = NULL; 

 return driver_create_file(&driver->driver, &driver->version_attr); 

这个函数的第一部分只注册低级的 device_driver 结构到核心; 剩下的建立版本属性. 因为这个属性在运行时被创建, 我们不能使用 DRIVER_ATTR 宏; 反之,  driver_attribute 结构必须手工填充. 注意我们设定属性的拥有者为驱动模块, 不是 lddbus 模块; 这样做的理由是可以在为这个属性的 show 函数的实现中见到: 

static ssize_t show_version(struct device_driver *driver, char *buf) 

 struct ldd_driver *ldriver = to_ldd_driver(driver); 

 sprintf(buf, "%s\n", ldriver->version); 

 return strlen(buf); 

有些来源于各位大侠博客:

%EC%C5%B6%AF%B0%AE%C9%F1/blog/item/173b12648e84adcd8db10d2a.html

阅读(3680) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~