Linux设备模型(总线、设备、驱动程序和类)

发布时间:2017-2-26 9:05:09 编辑:www.fx114.net 分享查询网我要评论
本篇文章主要介绍了"Linux设备模型(总线、设备、驱动程序和类)",主要涉及到Linux设备模型(总线、设备、驱动程序和类)方面的内容,对于Linux设备模型(总线、设备、驱动程序和类)感兴趣的同学可以参考一下。

Linux设备模型(总线、设备、驱动程序和类)之一:bus_type 总线是处理器和一个或多个设备之间的通道,在设备模型中,所有的设备都通过总线相连,甚至是内部的虚拟"platform"总线。可以通过ls -l /sys/bus看到系统加载的所有总线。 drwxr-xr-x root     root              1970-01-01 00:02 platform drwxr-xr-x root     root              1970-01-01 00:02 spi drwxr-xr-x root     root              1970-01-01 00:02 scsi drwxr-xr-x root     root              1970-01-01 00:02 usb drwxr-xr-x root     root              1970-01-01 00:02 serio drwxr-xr-x root     root              1970-01-01 00:02 i2c drwxr-xr-x root     root              1970-01-01 00:02 mmc drwxr-xr-x root     root              1970-01-01 00:02 sdio drwxr-xr-x root     root              1970-01-01 00:02 ac97       总线可以相互插入。设备模型展示了总线和它们所控制的设备之间的实际连接。在Linux 设备模型中,总线由bus_type 结构表示,定义在 <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 (*suspend_late)(struct device *dev, pm_message_t state);     int (*resume_early)(struct device *dev);     int (*resume)(struct device *dev);     struct pm_ext_ops *pm;     struct bus_type_private *p; }; 1,总线的注册和删除,总线的主要注册步骤: (1)申明和初始化bus_type 结构体。只有很少的bus_type 成员需要初始化,大部分都由设备模型核心控制。但必须为总线指定名字及一些必要的方法。例如: struct bus_type ldd_bus_type = {     .name = "ldd",     .match = ldd_match,     .uevent = ldd_uevent, }; (2)调用bus_register函数注册总线。int bus_register(struct bus_type *bus),该调用可能失败,所以必须始终检查返回值。 ret = bus_register(&ldd_bus_type); if (ret)    return ret; 若成功,新的总线子系统将被添加进系统,之后可以向总线添加设备。当必须从系统中删除一个总线时,调用: void bus_unregister(struct bus_type *bus);  2,总线方法      在 bus_type 结构中定义了许多方法,它们允许总线核心作为设备核心与单独的驱动程序之间提供服务的中介,主要介绍以下两个方法: int (*match)(struct device * dev, struct device_driver * drv); /*当一个新设备或者驱动被添加到这个总线时,这个方法会被调用一次或多次,若指定的驱动程序能够处理指定的设备,则返回非零值。*/ int (*uevent)(struct device *dev, struct kobj_uevent_env *env); /*在为用户空间产生热插拔事件之前,这个方法允许总线添加环境变量*/       对设备和驱动的迭代:若要编写总线层代码,可能不得不对所有已经注册到总线的设备或驱动进行一些迭代操作,这可能需要仔细研究嵌入到 bus_type 结构中的其他数据结构,但最好使用内核提供的辅助函数: int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int (*fn)(struct device *, void *)); int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int (*fn)(struct device_driver *, void *)); /*这两个函数迭代总线上的每个设备或驱动程序(内部分别有next_device和next_driver),将关联的device或device_driver传递给 fn,同时传递data 值。若start为NULL,则从第一个设备开始;否则从start之后的第一个设备开始。若fn返回非零值,迭代停止并且那个值从bus_for_each_dev 或bus_for_each_drv 返回。*/   3,总线属性      几乎Linux 设备模型中的每一层都提供添加属性的函数,总线层也不例外。bus_attribute 类型定义在<linux/device.h> 如下: struct bus_attribute {     struct attribute    attr;     ssize_t (*show)(struct bus_type *, char * buf);     ssize_t (*store)(struct bus_type *, const char * buf, size_t count); };      内核提供了一个宏在编译时创建和初始化bus_attribute 结构: BUS_ATTR(_name,_mode,_show,_store)/*这个宏声明一个结构,将bus_attr_作为给定_name 的前缀来创建总线的真正名称*/ /*总线的属性必须显式调用bus_create_file 来创建:*/ int bus_create_file(struct bus_type *bus, struct bus_attribute *attr); /*删除总线的属性调用:*/ void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr);        例如创建一个包含源码版本号简单属性方法如下: static ssize_t show_bus_version(struct bus_type *bus, char *buf) {       return snprintf(buf, PAGE_SIZE, "%s/n", Version); } static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL); //得到bus_attr_version /*在模块加载时创建属性文件:*/ if (bus_create_file(&ldd_bus_type, &bus_attr_version))       printk(KERN_NOTICE "Unable to create version attribute/n"); /*这个调用创建一个包含 lddbus 代码的版本号的属性文件(/sys/bus/ldd/version)*/ Linux设备模型(总线、设备、驱动程序和类)之二:device 在最底层,Linux 系统中的每个设备由一个struct device 代表: struct device {     struct klist  klist_children;     struct klist_node knode_parent; /* node in sibling list */     struct klist_node knode_driver;     struct klist_node knode_bus;     struct device  *parent; * 设备的 "父" 设备,该设备所属的设备,通常一个父设备是某种总线或者主控制器。如果 parent 是 NULL, 则该设备是顶层设备,较少见 */     struct kobject kobj;     char bus_id[BUS_ID_SIZE]; /* position on parent bus */     const char  *init_name; /* initial name of the device */     struct device_type *type;     unsigned  uevent_suppress:1;     struct semaphore sem; /* semaphore to synchronize calls to its driver.*/     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  *platform_data; /* Platform specific data, device core doesn't touch it */     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; /* internal for coherent mem override */     /* arch specific additions */     struct dev_archdata archdata;     spinlock_t  devres_lock;     struct list_head devres_head;     struct list_head node;     struct class  *class;     dev_t   devt; /* dev_t, creates the sysfs "dev" */     struct attribute_group **groups; /* optional groups */     void (*release)(struct device *dev);  /*当这个设备的最后引用被删除时,内核调用该方法; 它从被嵌入的kobject的release 方法中调用。所有注册到核心的设备结构必须有一个 release 方法, 否则内核将打印错误信息*/ }; (1)设备注册 在注册struct device 前,最少要设置parent, bus_id, bus, 和 release 成员,设备的注册和注销函数为: int device_register(struct device *dev); void device_unregister(struct device *dev);       一个实际的总线也是一个设备,所以必须单独注册,以下为lddbus注册它的虚拟总线设备: static void ldd_bus_release(struct device *dev) {       printk(KERN_DEBUG "lddbus release/n"); } struct device ldd_bus = {      .bus_id = "ldd0",      .release = ldd_bus_release }; /*这是顶层总线,parent 和 bus 成员为 NULL*/ /*作为第一个总线,它的名字为ldd0,这个总线设备的注册代码如下:*/ ret = device_register(&ldd_bus); if (ret)    printk(KERN_NOTICE "Unable to register ldd0/n"); /*一旦调用完成, 新总线ldd0会在sysfs中/sys/devices下显示,任何挂到这个总线的设备会在/sys/devices/ldd0 下显示*/ (2)设备属性 sysfs 中的设备入口可有属性,相关的结构是: struct device_attribute {     struct attribute attr;     ssize_t (*show)(struct device *dev, struct device_attribute *attr,char *buf);     ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); }; /*设备属性结构可使用以下宏(与BUS类比):*/ DEVICE_ATTR(_name,_mode,_show,_store); /*这个宏声明一个结构, 将 dev_attr_ 作为给定 _name 的前缀来命名设备属性 /*属性文件的实际处理使用以下函数:*/  int device_create_file(struct device *device,  struct device_attribute * entry);  void device_remove_file(struct device * dev,  struct device_attribute * attr); 一个实例是:终端执行:cd /sys/class/leds/lcd-backlight, ls回显: uevent subsystem device power brightness 这些属性可能都是通过device_create_file添加上去(至少brightness是这样)。进入device目录,再输入pwd, 回显:/sys/devices/platform/smdk-backlight。变换到devices目录下了,可见设备模型的不同构成是指向同一个设备的。       2.6下字符设备开始用struct cdev结构体表示,但是我想调用device_create_file(dev, &dev_attr_debug);函数在/sys中导出信息,device_create_file()的第一个入口参数类型为struct device结构体。问题是struct cdev与struct device这两个结构体没有任何联系的地方?答案是可以采用共同拥有的Kobjcet这个成员作为纽带,所以从子类cdev--->父类kobject--->子类device,推导得到:container_of(kobj)-->list_entry(entry)->(struct device*)  。因为containerof是从结构体指针成员找到结构体地址,所以从cdev的kobj可以找到父类kobject的地址,而所有的kobject的entery都是在一个链表里面,遍历这个链表,找到结构体成员为特定device结构的那一项。 (3)设备结构的嵌入       device 结构包含设备模型核心用来模拟系统的信息。但大部分子系统记录了关于它们拥有的设备的额外信息,所以很少单纯用device 结构代表设备,而是通常将其嵌入一个设备的高层结构体表示中。       lddbus 驱动创建了它自己的 device 类型(也即每类设备会建立自己的设备结构体,其中至少一个成员是struct device类型,比如video_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);        lddbus 导出的注册和注销接口如下: static void ldd_dev_release(struct device *dev) { } 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); //设备名拷贝入device结构体中       return device_register(&ldddev->dev);   //仍然用device_register注册,只不过上层打包了 } EXPORT_SYMBOL(register_ldd_device); void unregister_ldd_device(struct ldd_device *ldddev) {        device_unregister(&ldddev->dev); } EXPORT_SYMBOL(unregister_ldd_device); Linux设备模型(总线、设备、驱动程序和类)之三:device_driver && 多厂家驱动自动识别 设备模型跟踪所有系统已知的驱动,主要目的是使驱动程序核心能协调驱动和新设备之间的关系。一旦驱动在系统中是已知的对象就可能完成大量的工作。驱动程序的结构体device_driver 定义如下: struct device_driver {      const char  *name;       /*驱动程序的名字( 在 sysfs 中出现 )*/      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 pm_ops *pm;      struct driver_private *p; }; (1)驱动程序的注册和注销 /*注册device_driver 结构的函数是:*/ int driver_register(struct device_driver *drv); void driver_unregister(struct device_driver *drv); (2)驱动程序的属性 /*driver的属性结构在:*/ struct driver_attribute {      struct attribute attr;      ssize_t (*show)(struct device_driver *drv, char *buf);      ssize_t (*store)(struct device_driver *drv, const char *buf, size_t count); }; 宏定义DRIVER_ATTR(_name,_mode,_show,_store),它的原型是: #define DRIVER_ATTR(_name, _mode, _show, _store) \ struct driver_attribute driver_attr_##_name =  \  __ATTR(_name, _mode, _show, _store) 而#define __ATTR(_name,_mode,_show,_store) { \  .attr = {.name = __stringify(_name), .mode = _mode }, \  .show = _show,     \  .store = _store,     \ }      *属性文件创建的方法:*/ int driver_create_file(struct device_driver * drv, struct driver_attribute * attr); void driver_remove_file(struct device_driver * drv, struct driver_attribute * attr); (3)驱动程序结构的嵌入       对大多数驱动程序核心结构,device_driver 结构通常被嵌入到一个更高层的、总线相关的结构中。当然也有直接注册驱动的,不用嵌入到高层结构体。如driver_register(&wm97xx_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总线中相关的驱动注册和注销函数是: 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); } int register_ldd_driver(struct ldd_driver *driver)  //device_driver被嵌入到更高层结构体 {       int ret;       driver->driver.bus = &ldd_bus_type;       ret = driver_register(&driver->driver);/*注册底层的 device_driver 结构到核心*/       if (ret)            return ret;       driver->version_attr.attr.name = "version";/* driver_attribute 结构必须手工填充*/       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);/*建立版本属性,因为这个属性在运行时被创建,所以不能使用 DRIVER_ATTR 宏*/ } void unregister_ldd_driver(struct ldd_driver *driver) {       driver_unregister(&driver->driver); } EXPORT_SYMBOL(register_ldd_driver); EXPORT_SYMBOL(unregister_ldd_driver);   在sculld 中创建的 ldd_driver 结构如下: /* Device model stuff */ static struct ldd_driver sculld_driver = {     .version = "$Revision: 1.21 $",     .module = THIS_MODULE,     .driver = {         .name = "sculld",     }, }; =====================================================================================================================         关于不同芯片方案的自动识别,比如TP芯片、GSENSOR等的自动识别在实际生产和应用中都是很有必要的,大大简化软件维护和发布流程。以下的说明以MTK的智能机平台为基础,对于其他平台该方法并不适用,因为MTK自己有一套设备驱动管理流程,相当于在同一类驱动上都加了一层管理层。 (1)在注册MTK的TP的platform_driver时,名字是#define TPD_DEVICE            "mtk-tpd",所以其他TP备选芯片的i2c_driver中的driver->name也都必须是TPD_DEVICE,方便设备挂接。为什么一开始不加修改的在配置文件中同时编译两个TP型号,会出现编译错误(可能是运行错误)?因为每个TP的驱动都有i2c_add_driver,不可能在同一个TPD_DEVICE名字上挂接两个I2C设备。 (2)新的方法,就是在第一个编译加载的TP驱动中做读ID号的判断,如果没有识别到正确的ID,认定该芯片不符,则执行i2c_del_driver,以保证后来编译执行的其他TP能够继续挂接上TPD_DEVICE的platform_driver。 (3)过程:以melfas和mstar两颗TP芯片为例,如果前者在后者之前编译加载,后者不做改动,前者驱动做一定的修改。 定义个单文件内的全局变量:static int i2c_tetect =0; melfas的i2c_driver定义如下: [cpp] view plaincopy static struct i2c_driver tpd_i2c_driver =  {      .driver = {          .name = TPD_DEVICE,          .owner = THIS_MODULE,      },      .probe = tpd_probe,      .remove = __devexit_p(tpd_remove),      .id_table = tpd_id,      .detect = tpd_detect,      .address_data = &addr_data,  };   在它的tpd_probe中开始做个判断,没有读到正确的ID,i2c_tetect=-1;同时该函数也返回-1。注意:tpd_load_status = 1;是在每个TP probe驱动最后中都存在的语句,表示该驱动加载成功。 (4)在melfas的驱动中本地的tpd_local_init中, [cpp] view plaincopy  if(i2c_add_driver(&tpd_i2c_driver) != 0)   {       printk("melfas unable to add i2c driver.\n");       return -1;   }              //就算tpd_probe返回-1,标识该驱动没有正确识别到芯片,i2c_add_driver并不一定要返回-1。    if(i2c_tetect!=0)   //不等于0,说明在tpd_probe中赋值,标识判断不成功  {       i2c_del_driver(&tpd_i2c_driver);       //执行i2c_del_driver,留出空位       printk("melfas not detect .\n");       return -1;   }   (5)在MTK的TPD管理器中, [cpp] view plaincopy for(i = 1; i < TP_DRV_MAX_COUNT; i++)  {              /* add tpd driver into list */          if(tpd_driver_list[i].tpd_device_name != NULL)          {                  tpd_driver_list[i].tpd_local_init();                  if(tpd_load_status ==1) {                      TPD_DMESG("[mtk-tpd]tpd_probe, tpd_driver_name=%s\n", tpd_driver_list[i].tpd_device_name);                      g_tpd_drv = &tpd_driver_list[i];                      break;                  }          }       }         它会依次执行每个TP驱动的tpd_local_init,只有tpd_load_status为1的才是已经完整PROBE的驱动,即刻退出循环;否则继续下一个TP的匹配寻找。当然,此处也可以已  tpd_local_init函数的返回值作为判断标准,PROBE成功的返回0,不成功的会提前返回-1。        在实际生产中碰到过一个问题:TP的自动识别是靠读取MELFAS的设备ID号来判断的,如果读取不成功则是MSTAR的TP。但是存在一个问题,如果开机MELFAS的TP在第一次开机升级过程中断电,会造成TP不能工作也不能读ID号,必须强制升级才能保证TP的后续工作。这样当读取ID不成功有两种可能:一是MELFAS的TP,得强制升级;二是MSTAR的TP,所以程序中必须做判断处理。处理的原则必须保证: 1,MSTAR TP可用            (验证OK) 2,MELFAS 正常开机可用  (验证OK) 3,MELFAS 第一次开机升级断电可再次升级并可用(验证OK) 4,MELFAS 第一次开机升级只升一次(验证OK) Linux设备模型(总线、设备、驱动程序和类)之四:class_register 类是一个设备的高层视图,它抽象出了底层的实现细节,从而允许用户空间使用设备所提供的功能,而不用关心设备是如何连接和工作的。类成员通常由上层代码所控制,而无需驱动的明确支持。但有些情况下驱动也需要直接处理类。       几乎所有的类都显示在/sys/class目录中,可以通过ls -l /sys/class来显示。出于历史的原因,有一个例外:块设备显示在/sys/block目录中。在许多情况,类子系统是向用户空间导出信息的最好方法。当类子系统创建一个类时,它将完全拥有这个类,根本不用担心哪个模块拥有那些属性,而且信息的表示也比较友好。为了管理类,驱动程序核心导出了一些接口,其目的之一是提供包含设备号的属性以便自动创建设备节点,所以udev的使用离不开类。类函数和结构与设备模型的其他部分遵循相同的模式,可与前三篇文章类比。 (1)管理类的接口和注册注销函数      类由 struct class 的结构体来定义: struct class {      const char  *name; *每个类需要一个唯一的名字, 它将显示在 /sys/class 中*/      struct module  *owner;      struct class_attribute  *class_attrs;      struct device_attribute  *dev_attrs;      struct kobject   *dev_kobj;      int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);      void (*class_release)(struct class *class); /* 删除类本身的函数 */      void (*dev_release)(struct device *dev);           int (*suspend)(struct device *dev, pm_message_t state);      int (*resume)(struct device *dev);      struct pm_ops *pm;      struct class_private *p; };      /*类注册函数:*/ int class_register(struct class *cls); void class_unregister(struct class *cls); /*类属性的接口:*/ struct class_attribute {      struct attribute attr;      ssize_t (*show)(struct class *cls, char *buf);      ssize_t (*store)(struct class *cls, const char *buf, size_t count); }; CLASS_ATTR(_name,_mode,_show,_store); int class_create_file(struct class *cls, const struct class_attribute *attr); void class_remove_file(struct class *cls, const struct class_attribute *attr);、 (2)类设备,类存在的真正目的是给作为类成员的各个设备提供一个容器,成员由struct class_device 来表示,暂没用到。 (3)类接口       类子系统有一个 Linux 设备模型的其他部分找不到的附加概念,称为“接口”,可将它理解为一种设备加入或离开类时获得信息的触发机制,结构体如下: struct class_interface {       struct list_head node;       struct class  *class;       int (*add_dev)  (struct device *, struct class_interface *);       void (*remove_dev) (struct device *, struct class_interface *); }; /*注册或注销接口的函数:*/ int class_interface_register(struct class_interface *class_intf); void class_interface_unregister(struct class_interface *class_intf); /*一个类可注册多个接口*/         设定class的好处:设备驱动一般在注册的时候都会调用此类class的一些函数,主要作用就是在sys目录里面创建一些节点,比如cd到/sys/class下面可以看到这一类的设备,与这个相关的就是一些kobjects。当然对于一个新设备,可以注册进一个class也可以不注册进去,如果存在对应class的话注册进去更好。 (4)struct class的创建         很多时候,我们需要用到attr属性创建show/store属性时,缺少struct device对象。那么我们需要通过一系列操作来间接的达到目的,举例方法如下: struct class *firmware_class; struct device *firmware_cmd_dev;          在模块驱动的probe函数中,比如在int tpd_probe(struct i2c_client *client, const struct i2c_device_id *id)中,  firmware_class = class_create(THIS_MODULE, client->name);       //以name创建class  if(IS_ERR(firmware_class))   {           printk("Failed to create class(firmware)!\n");   }  firmware_cmd_dev = device_create(firmware_class,NULL, 0, NULL, "device");     //在class的基础上创建device为名的结构体  if(IS_ERR(firmware_cmd_dev))   {           printk("Failed to create device(firmware_cmd_dev)!\n");   } 之后即可通过firmware_cmd_dev作为struct device指针对象,用device_create_file来创建属性。属性路径如下: /sys/class/mtk-tpd/device/。

上一篇:solr-group by
下一篇:位运算

相关文章

相关评论

本站评论功能暂时取消,后续此功能例行通知。

一、不得利用本站危害国家安全、泄露国家秘密,不得侵犯国家社会集体的和公民的合法权益,不得利用本站制作、复制和传播不法有害信息!

二、互相尊重,对自己的言论和行为负责。