随着技术的不断进步,系统拓扑接口越来越复杂,对智能电源管理、热插拔支持要求也越来越高,为适应这些需求,2.6内核提供了全新的内核设备模型。设备模型三元素:总线、设备、驱动。另:采用总线模型,极大的提高程序可移植性。
1.总线
总线就是处理器与设备(包括SOC设备,比如SPI控制器)之间的通道,在设备模型中,所有的设备都通过总线相连,Linux的虚拟总线platform总线。在Linux设备模型中,总线由bus_type结构表示,在 定义
- struct bus_type {
- const char * name;/*总线类型名称*/
- * owner;/*指向模块的指针(如果有), 此模块负责操作这个总线*/
- ;/*与该总线相关的子系统*/
- ;/*总线驱动程序的kset*/
- ;/* 挂在该总线的所有设备的kset*/
- ;/*与该总线相关的驱动程序链表*/
- ;/*挂接在该总线的设备链表*/
- ;
- * bus_attrs; /*总线属性*/
- * dev_attrs; /*设备属性,指向为每个加入总线的设备建立的默认属性链表*/
- * 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);
- (*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);
- (*resume)(struct device * dev);
- /*处理热插拔、电源管理、探测和移除等事件的方法*/
- int drivers_autoprobe:1;
- };
在新版内核中,这个结构体更简洁,隐藏了无需驱动知道的一些成员:
- /*linux-3.1.6*/
- struct bus_type {
- const char *name;
- *bus_attrs;
- *dev_attrs;
- *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);
- (*shutdown)(struct device *dev);
- int (*suspend)(struct device *dev, pm_message_t state);
- int (*resume)(struct device *dev);
- const struct dev_pm_ops *pm;
- *p;
- };
(1)总线注册过程:
① 申明和初始化 bus_type 结构体。只有很少的 bus_type 成员需要初始化,大部分都由设备模型核心控制。但必须为总线指定名字及一些必要的方法。例如
- struct bus_type my_bus_type = {
- .name = "my_bus",
- .match = my_match,
- };
②调用bus_register函数注册总线
- int bus_register(struct bus_type * bus)
调用可能失败, 所以必须始终检查返回值。若成功,新的总线子系统将被添加进系统,并可在 sysfs 的 /sys/bus 下看到。之后可以向总线添加设备。
- ret = bus_register(&my_bus_type);
- if (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, char **envp, int num_envp, char *buffer, int buffer_size);
- /*在为用户空间产生热插拔事件之前,这个方法允许总线添加环境变量(参数和 kset 的uevent方法相同)*/
例如:
- static int my_match(struct device *dev, struct device_driver *driver)
- {
- !strncmp(dev->bus_id, driver->name, strlen(driver->name));
- }
(3)对设备和驱动的迭代
若要编写总线层代码, 可能不得不对所有已经注册到总线的设备或驱动进行一些操作,这可能需要仔细研究嵌入到 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 *));
- /*这两个函数迭代总线上的每个设备或驱动程序, 将关联的 device 或 device_driver 传递给 fn, 同时传递 data 值。若 start 为 NULL, 则从第一个设备开始; 否则从 start 之后的第一个设备开始。若 fn 返回非零值, 迭代停止并且那个值从 bus_for_each_dev 或bus_for_each_drv 返回。*/
(4)总线属性
几乎 Linux 设备模型中的每一层都提供添加属性的函数, 总线层也不例外。bus_attribute 类型定义在 如下:
- struct bus_attribute {
- ;
- (*show)(struct bus_type *, char * buf);
- (*store)(struct bus_type *, const char * buf, size_t count);
- };
可以看出struct bus_attribute 和struct attribute 很相似,其实大部分在 kobject 级上的设备模型层都是以这种方式工作。
内核提供了一个宏在编译时创建和初始化 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);
- /*删除总线的属性调用:*/
- (struct bus_type *bus, struct bus_attribute *attr);
例如:
- /*
- * Export a simple attribute.
- */
- (struct bus_type *bus, char *buf)
- {
- (buf, PAGE_SIZE, "%s\n", Version);
- }
- (version_file, S_IRUGO, show_bus_version, NULL);
- //将会在/sys/bus/my_bus/下显示version_file文件
- if (bus_create_file(&my_bus_type, &bus_attr_version_file))
- printk(KERN_NOTICE "Fail to create version attribute!\n");
2.设备
Linux 系统中的每个设备由一个 struct device 代表:
- struct device {
- ;
- ; /* node in sibling list */
- ;
- ;
- *parent;/* 设备的 "父" 设备,该设备所属的设备,通常一个父设备是某种总线或者主控制器. 如果 parent 是 NULL, 则该设备是顶层设备,较少见 */
- ;/*代表该设备并将其连接到结构体系中的 kobject; 注意:作为通用的规则, device->kobj->parent 应等于 device->parent->kobj*/
- [BUS_ID_SIZE];/*在总线上唯一标识该设备的字符串;例如: PCI 设备使用标准的 PCI ID 格式, 包含:域, 总线, 设备, 和功能号.*/
- *type;
- :1;
- :1;
- ;
- *devt_attr;
- ; /* semaphore to synchronize calls to its driver. */
- * bus; /*标识该设备连接在何种类型的总线上*/
- *driver; /*管理该设备的驱动程序*/
- *driver_data; /*该设备驱动使用的私有数据成员*/
- *platform_data; /* Platform specific data, device core doesn't touch it */
- ;
- #ifdef CONFIG_NUMA
- int numa_node; /* NUMA node this device is close to */
- #endif
- *dma_mask; /* dma mask (if dma'able device) */
- ;/* Like dma_mask, but for
- alloc_coherent mappings as
- not all hardware supports
- for consistent
- . */
- ; /* dma pools (if dma'ble) */
- *dma_mem; /* internal for coherent mem override */
- /* arch specific additions */
- ;
- ;
- ;
- /* class_device migration path */
- ;
- class *class;
- ; /* dev_t, creates the sysfs "dev" */
- **groups; /* optional groups */
- (*release)(struct device * dev);/*当这个设备的最后引用被删除时,内核调用该方法; 它从被嵌入的 kobject 的 release 方法中调用。所有注册到核心的设备结构必须有一个 release 方法, 否则内核将打印错误信息*/
- };
- /*在注册 struct device 前,最少要设置parent, bus_id, bus, 和 release 成员*/
(1)设备注册
- int device_register(struct device *dev);
- (struct device *dev);
一个实际的总线也是一个设备,所以必须单独注册,例如
- static void my_bus_release(struct device *dev)
- {
- printk(KERN_DEBUG "my bus release\n");
- }
- struct device my_bus_dev = {
- .bus_id = "my_bus0",
- .release = my_bus_release
- };
- /*注册总线设备*/
- = device_register(&my_bus_dev);
- if (ret)
- (KERN_NOTICE "Fail to register device:my_bus!\n");
(2)设备属性
sysfs 中的设备入口可有属性,相关的结构是:
- /* interface for exporting device attributes 这个结构体和《LDD3》中的不同,已经被更新过了,请特别注意!*/
- {
- ;
- (*show)(struct device *dev, struct device_attribute *attr,char *buf);
- (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
- };
- /*设备属性结构可在编译时建立, 使用以下宏:*/
- (_name,_mode,_show,_store);
- /*这个宏声明一个结构, 将 dev_attr_ 作为给定 _name 的前缀来命名设备属性
- /*属性文件的实际处理使用以下函数:*/
- int device_create_file(struct device *device, struct device_attribute * entry);
- (struct device * dev, struct device_attribute * attr);
(3)设备结构的嵌入
device 结构包含设备模型核心用来模拟系统的信息。但大部分子系统记录了关于它们又拥有的设备的额外信息,所以很少单纯用 device 结构代表设备,而是,通常将其嵌入一个设备的高层表示中。底层驱动几乎不知道 struct device。
lddbus 驱动创建了它自己的 device 类型,并期望每个设备驱动使用这个类型来注册它们的设备:
struct ldd_device { |
lddbus 导出的注册和注销接口如下:
/* |
3.设备驱动程序
(1)驱动定义
- /*定义在<linux/device.h>*/
- {
- const char * name;/*驱动程序的名字( 在 sysfs 中出现 )*/
- * bus;/*驱动程序所操作的总线类型*/
- ;/*内嵌的kobject对象*/
- ;/*当前驱动程序能操作的设备链表*/
- ;
- * owner;
- const char * mod_name; /* used for built-in modules */
- * mkobj;
- int (*probe) (struct device * dev);/*查询一个特定设备是否存在及驱动是否可以使用它的函数*/
- int (*remove) (struct device * dev);/*将设备从系统中删除*/
- (*shutdown) (struct device * dev);/*关闭设备*/
- int (*suspend) (struct device * dev, pm_message_t state);
- int (*resume) (struct device * dev);
- };
- /*注册device_driver 结构的函数是:*/
- int driver_register(struct device_driver *drv);
- (struct device_driver *drv);
- /*driver的属性结构在:*/
- {
- ;
- (*show)(struct device_driver *drv, char *buf);
- (*store)(struct device_driver *drv, const char *buf, size_t count);
- };
- (_name,_mode,_show,_store)
- /*属性文件创建的方法:*/
- int driver_create_file(struct device_driver * drv, struct driver_attribute * attr);
- (struct device_driver * drv, struct driver_attribute * attr);
- /*bus_type 结构含有一个成员( drv_attrs ) 指向一组为属于该总线的所有设备创建的默认属性*/
在新版内核中,这个结构体更简洁,隐藏了无需驱动知道的一些成员:
- struct device_driver {
- const char *name;
- struct bus_type *bus;
- *owner;
- const char *mod_name; /* used for built-in modules */
- ; /* disables bind/unbind via sysfs */
- const struct of_device_id *of_match_table;
- 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 attribute_group **groups;
- const struct dev_pm_ops *pm;
- *p;
- };
- struct driver_private {
- struct kobject kobj;
- struct klist klist_devices;
- struct klist_node knode_bus;
- struct module_kobject *mkobj;
- struct device_driver *driver;
- };
- #define to_driver(obj) container_of(obj, struct driver_private, kobj)
(2)驱动程序结构的嵌入
对大多数驱动程序核心结构, device_driver 结构通常被嵌入到一个更高层的、总线相关的结构中。
以lddbus 子系统为例,它定义了ldd_driver 结构:
struct ldd_driver { |
lddbus总线中相关的驱动注册和注销函数是:
/* |
在sculld 中创建的 ldd_driver 结构如下:
/* Device model stuff */ |
类 子系统
类是一个设备的高层视图, 它抽象出了底层的实现细节,从而允许用户空间使用设备所提供的功能, 而不用关心设备是如何连接和工作的。类成员通常由上层代码所控制, 而无需驱动的明确支持。但有些情况下驱动也需要直接处理类。
几乎所有的类都显示在 /sys/class 目录中。出于历史的原因,有一个例外:块设备显示在 /sys/block目录中。在许多情况, 类子系统是向用户空间导出信息的最好方法。当类子系统创建一个类时, 它将完全拥有这个类,根本不用担心哪个模块拥有那些属性,而且信息的表示也比较友好。
为了管理类,驱动程序核心导出了一些接口,其目的之一是提供包含设备号的属性以便自动创建设备节点,所以udev的使用离不开类。类函数和结构与设备模型的其他部分遵循相同的模式,所以真正崭新的概念是很少的。
注意:class_simple 是老接口,在2.6.13中已被删除,这里不再研究。
管理类的接口
类由 struct class 的结构体来定义:
/* |
在更新的内核里,这个结构体变得简洁了,删除了一些成员:
/*in Linux 2.6.26.5*/ |
类设备(在新内核中已被删除)
类存在的真正目的是给作为类成员的各个设备提供一个容器,成员由 struct class_device 来表示:
struct class_device { |
类接口
类子系统有一个 Linux 设备模型的其他部分找不到的附加概念,称为“接口”, 可将它理解为一种设备加入或离开类时获得信息的触发机制,结构体如下:
struct class_interface { /*当一个类设备被加入到在 class_interface 结构中指定的类时, 将调用接口的 add 函数,进行一些设备需要的额外设置,通常是添加更多属性或其他的一些工作*/ |
测试代码: device_module.rar
Tekkaman的博客,国嵌的代码,特别感谢国嵌的代码和视频,设备模型这一块直到最近看了国嵌的视频和代码,才真正理解。设备模型从一个更高的层次来看待内核驱动,现在再看以前的很多代码,有了更高层次的,更好的全局理解,此模块以后还要把LDD3多看几遍。