Chinaunix首页 | 论坛 | 博客
  • 博客访问: 166791
  • 博文数量: 101
  • 博客积分: 509
  • 博客等级: 下士
  • 技术积分: 680
  • 用 户 组: 普通用户
  • 注册时间: 2009-07-08 21:55
文章分类

全部博文(101)

文章存档

2013年(6)

2012年(79)

2011年(16)

分类:

2012-05-16 11:06:58

原文地址:总线设备驱动模型总结 作者:gongping11

我的环境:

主机开发环境:Fedora14

开发板:         TQ2440

编译器: arm-linux-gcc-4.3.2


总线设备驱动模型其实现主要是基于Kobjectsysfs等机制,对于驱动模型程序开发主要是理解三个元素:总线、设备、驱动的关系。三者之间因为一定的联系性实现对设备的控制。

首先是总线,总线是三者联系起来的基础,通过一种总线类型,将设备和驱动联系起来。总线类型中的match函数用来匹配设备和驱动。当匹配操作晚餐之后就会控制驱动程序中的probe函数。

总线设备驱动模型的设计主要包括三个元素的注册,将三个元素加载到内核中,然后通过内核的内部机制将三者联系起来。


首先,总线类型的注册,包括属性文件的添加,总线也是一种设备,也需要将总线设备注册。

其次,完成设备的注册和添加以及对设备添加设备属性文件,同时填充最基本的函数操作。

最后,完成驱动的注册和天极以及对设备驱动添加属性文件,同时填充最基本的函数操作。


1、总线


总线类型是通过结构体bus_type表示的。其源码如下所示:

  1. struct bus_type {

  2.     /*总线名*/

  3.     const char        *name;

  4.     /*总线、设备、驱动属性*/

  5.     struct bus_attribute    *bus_attrs;

  6.     struct device_attribute    *dev_attrs;

  7.     struct driver_attribute    *drv_attrs;

  8.     /*总线支持的函数操作*/

  9.     /*匹配函数,主要用来识别相应的设备和驱动,是两者直接形成关联

  10.       用来判断指定的驱动程序能否处理指定的设备

  11.     */

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

  13.     /*在进行热插拔事件之前,为设备配置环境变量操作函数*/

  14.     int (*uevent)(struct device *dev, struct kobj_uevent_env *env);

  15.     int (*probe)(struct device *dev);

  16.     int (*remove)(struct device *dev);

  17.     void (*shutdown)(struct device *dev);

  18.     int (*suspend)(struct device *dev, pm_message_t state);

  19.     int (*suspend_late)(struct device *dev, pm_message_t state);

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

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

  22.     struct dev_pm_ops *pm;

  23.    

  24.     struct bus_type_private *p;

  25. };

其中的int (*match)(struct device * dev, struct device_driver * drv)是必须实现的函数,因为这个函数主要是实现设备和驱动之间的匹配管理。其中匹配的具体逻辑关系需要驱动设计着设定。


int (*uevent)(struct device *dev, char **envp, int num_envp,char *buffer, int buffer_size)则在热插拔事件之前,允许总线为设备添加环境变量。


通常创建一种总线类型的过程中只要完成总线类型结构体的填充,然后完成相应的注册、属性文件创建即可实现总线类型的添加。并不需要对bus_type类型中的所有变量进行赋值,只要将其中的name,bus_attribute,match实现即可。


最后不要忘了总线也是设备,需要将总线设备添加到内核中(注册函数)。

关于总线类型的属性设置,实质上就是完成一个结构体的操作。

如下源码所示:

  1. struct bus_attribute {

  2.     /*属性结构体*/

  3.     struct attribute    attr;

  4.     /*属性读操作函数,即显示函数*/

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

  6.     /*属性写操作函数,也就是存储到结构体中*/

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

  8. };

 /*可以通过宏命令定义一个总线结构体,但是需要自己实现属性读写操作函数,如果没有,可设置为NULL*/

 /*总线属性定义宏*/

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

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



 /*__ATTR的宏实现如下所示:*/

 #define __ATTR(_name,_mode,_show,_store) { \

.attr = {.name = __stringify(_name), .mode = _mode }, \

.show = _show, \

.store = _store, \

 }

由于通常情况下需要查找总线的版本信息,可以将版本信息添加到属性的读属性操作函数中,这样就能显示具体的版本信息。

在宏定义中##是指链接符的作用,相当于将两个部分链接起来,比如bus_attr_##name = bus_attr_name。这是在宏定义中比较常用的定义方式之一。


总线类型的注册和总线类型属性文件创建,以及总线设备的注册主要是依据下面几个函数来实现:

  1. /*总线类型注册函数,由于可能会出错,因此必须对返回值进行检查*/

  2. int __must_check bus_register(struct bus_type *bus);

  3. /*总线类型释放函数*/

  4. void bus_unregister(struct bus_type *bus);

  /*总线文件属性创建函数,将相关的文件属性添加给总线类型,同时也必须检查返回值是否正确*/

  1. int __must_check bus_create_file(struct bus_type *,struct bus_attribute *);

  2. /*总线类型文件属性删除函数,将两者之间的关联性切断*/

  3. void bus_remove_file(struct bus_type *, struct bus_attribute *);

最后需要将总线设备添加到系统中,主要采用设备注册函数;

设备注册函数:

int __must_check device_register(struct device *dev);

设备释放函数:

void device_unregister(struct device *dev);

2、设备

  

设备的实现主要是依靠struct device函数实现的,设备的实现主要是对结构体的填充。实现相应的函数即可。

  1. struct device {

  2.     /*父设备,通常就是总线设备,这也是为什么需要将总线作为设备添加的原因*/

  3.     struct device        *parent;

  4.     struct device_private    *p;

  5.     struct kobject kobj;

  6.     /*init_name是新添加的,替代了原来的bus_id,但是init_name不能直接被读写操作*/

  7.     const char        *init_name; /* initial name of the device */

  8.     struct device_type    *type;

  9.     struct semaphore    sem;    /* semaphore to synchronize calls to

  10.                      * its driver.

  11.                      */

  12.     /*总线类型,主要是关联总线类型,这是前面添加的总线类型,通过相同的总线类型关联设备和驱动*/

  13.     struct bus_type    *bus;        /* type of bus device is on */

  14.     struct device_driver *driver;    /* which driver has allocated this device */

  15.     void        *driver_data;    /* data private to the driver */

  16.     void        *platform_data;    /* Platform specific data, device

  17.                      core doesn't touch it */

  18.     struct dev_pm_info    power;

  19. #ifdef CONFIG_NUMA

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

  21. #endif

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

  23.     u64        coherent_dma_mask; /* Like dma_mask, but for

  24.                      alloc_coherent mappings as

  25.                      not all hardware supports

  26.                      64 bit addresses for consistent

  27.                      allocations such descriptors. */

  28.     struct device_dma_parameters *dma_parms;

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

  30.     struct dma_coherent_mem    *dma_mem; /* internal for coherent mem

  31.                      override */

  32.     /* arch specific additions */

  33.     struct dev_archdata    archdata;

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

  35.     spinlock_t        devres_lock;

  36.     struct list_head    devres_head;

  37.     struct klist_node    knode_class;

  38.     struct class        *class;

  39.     struct attribute_group    **groups;    /* optional groups */

  40.     

  41.     /*必须实现的release函数*/

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

  43. };

  44. /*由于init_name 不能直接读写,只能通过*dev_name来读写设备名*/

  45. static inline const char *dev_name(const struct device *dev)

  46. {

  47.     return kobject_name(&dev->kobj);

  48. }

  49. /*实现对设备名的设置*/

  50. int dev_set_name(struct device *dev, const char *name, ...)

  51.             __attribute__((format(printf, 2, 3)));

  52. /*设备文件属性结构体,必须注意的改变点*/

  53. struct device_attribute {

  54.     /*属性值*/

  55.     struct attribute    attr;

  56.     /*设备属性读函数,必须注意是三个参数,不再是两个参数*/

  57.     ssize_t (*show)(struct device *dev, struct device_attribute *attr,char *buf);

  58.     /*设备属性写操作,必须注意是四个参数,不是三个参数*/

  59.     ssize_t (*store)(struct device *dev, struct device_attribute *attr,const char *buf, size_t count);

  60. };

  /*设备属性宏定义,主要用来实现设备文件属性*/

  1. #define DEVICE_ATTR(_name, _mode, _show, _store) \

  2. struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

  /*创建设备文件属性函数,必须检查返回值*/

  1. int __must_check device_create_file(struct device *device,struct device_attribute *entry);

  2. /*删除设备文件属性函数*/

  3. void device_remove_file(struct device *dev,struct device_attribute *attr);

  4. /*设备注册函数,必须检查返回值*/

  5. int __must_check device_register(struct device *dev);

  6. /*设备释放函数*/

  7. void device_unregister(struct device *dev);


需要注意的是linux-2.6.30内核以前,没有init_name元素,而是元素bus_id,这个主要是实现设备名的填充,但是linux-2.6.30内核之后的在struct deviceinit_name代替了bus_id,但是需要注意的是init_name不能直接被读写,当需要读写设备名时只能采用特定的函数实现:dev_name()set_dev_name()。当直接读写init_name会导致内核错误,出现Unable to handle kernel NULL pointer dereference at virtual address 00000000的错误。

最后注意device_attribute中的show,store函数不同于其他类型(总线、驱动)的函数,device_attribute中的showstore函数中的参数数量多了一个。


3、驱动


驱动管理一定的设备,其中的关系主要是内核的内部机制实现的,但是实现的具体逻辑需要在bus_type中的match函数中具体设计。通常是一定的设备名和驱动名匹配,当然也可以有其他的逻辑,具体的只需要设计好bus_type中的match函数。


驱动是由驱动结构体实现的。具体如下所示:

  1. /*驱动结构体*/

  2. struct device_driver {

  3.     /*驱动名,通常用来匹配设备*/

  4.     const char        *name;

  5.     /*关联的总线类型,总线、设备、驱动关联的总线类型*/

  6.     struct bus_type        *bus;

  7.     struct module        *owner;

  8.     const char         *mod_name;    /* used for built-in modules */

  9.     /*驱动中最应该实现的操作函数主要包括proberemove函数*/

  10.     /*当匹配完成以后的,入口函数*/

  11.     int (*probe) (struct device *dev);

  12.     /*驱动卸载时操作的相关函数,退出函数*/

  13.     int (*remove) (struct device *dev);

  14.     void (*shutdown) (struct device *dev);

  15.     int (*suspend) (struct device *dev, pm_message_t state);

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

  17.     struct attribute_group **groups;

  18.     struct dev_pm_ops *pm;

  19.     struct driver_private *p;

  20. };

  21. /*驱动注册函数,返回值必须检测*/

  22. int __must_check driver_register(struct device_driver *drv);

  23. /*驱动释放函数*/

  24. void driver_unregister(struct device_driver *drv);

  25. /*驱动属性结构体*/

  26. struct driver_attribute {

  27.     /*属性值*/

  28.     struct attribute attr;

  29.     /*属性读操作函数*/

  30.     ssize_t (*show)(struct device_driver *driver, char *buf);

  31.     /*属性写操作函数*/

  32.     ssize_t (*store)(struct device_driver *driver, const char *buf,

  33.              size_t count);

  34. };

  35. /*驱动属性定义宏命令*/

  36. #define DRIVER_ATTR(_name, _mode, _show, _store)    \

  37. struct driver_attribute driver_attr_##_name =        \

  38.     __ATTR(_name, _mode, _show, _store)

 /*驱动属性文件创建函数,返回值必须检测*/ 

  1. int __must_check driver_create_file(struct device_driver *driver,struct driver_attribute *attr);

  2. /*驱动属性文件移除函数*/

  3. void driver_remove_file(struct device_driver *driver,struct driver_attribute *attr);

驱动结构体的定义不需要完成所有元素的赋值,只需要完成主要的几个变量的赋值即可,其中主要的元素包含name,bus,以及proberemove函数的实现。

其中的probe函数是当总线中的match完成匹配操作以后,进入驱动的入口函数,因此必须实现。remove我认为就是对应的退出函数,因此也有必要实现。

驱动的注册,释放也有相关的函数来操作,主要是driver_register()driver_unregister()


总结:

1、在总线驱动模型中我认为最主要的是搞清楚三个不同的结构体,分别是总线、驱动、设备。了解三个元素对应的属性结构体以及相应的属性操作函数的差异性。

2、不同驱动设计的关键主要是完成不同结构体的填充过程,但是并不需要对结构体中所有的对象进行赋值,只需要完成重要的几个元素的值。

3、总线是一种类型,同时也是一种设备,在总线的相关处理中需要首先添加总线类型,然后添加总线设备,这是需要注意的。由于总线类型关联驱动和设备,因此需要导出总线类型变量。由于总线设备是设备的父设备,因此也需要将总线设备变量导出。同样在驱动和设备中也要导出相关的结构体变量,便于总线中的match函数实现驱动和设备的匹配操作。

4XXX_attr结构体基本相同,都是一个属性结构体和函数show()stroe()。但是不同的XXX可能会导致showstroe函数的参数发生变化。这需要对照源码。

5struct device中的init_name是一个特殊的量,不能直接读写操作,只能采用函数device_name()set_device_name来设置设备名。

6xxx_register()之类的函数,需要对返回值进行检查。因为很有可能不成功。

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