Linux设备驱动模型
我们在写最简单的设备驱动程序的时候,我们将所有的硬件信息都保存在了驱动代码中,这样有一个非常明显的不足:会导致驱动程序的通用性极差,一旦硬件平台或硬件连接有锁改变,就一定要修改驱动代码。为了解决这个问题,Linux在2.6版本之后,添加了“总线—设备—驱动”的Linux设备模型,有效地实现了设备和驱动的分离。
该设备模型通过几个数据结构来反映当前系统中总线、设备以及驱动的工作状况,提出了以下几个重要概念:
- 设备(device):挂载在某个总线的物理设备;
- 驱动(driver):与特定设备相关的软件,负责初始化该设备以及提供一些操作该设备的操作方式;
- 总线(bus):负责管理挂载对应总线的设备以及驱动;
- 类(class):对于具有相同功能的设备,归结到一种类别,进行分类管理;
1、总线
在实际的应用中,大部分的外设都是连接在总线上,它们之间的物理连接,如下图所示:
在驱动设计中,总线驱动的主要工作是负责管理两个链表。分别是添加到该总线的设备链表以及注册到该总线的驱动链表。当你向总线添加(移除)一个设备(驱动)时,便会在对应的列表上添加新的节点,同时对挂载在该总线的驱动以及设备进行匹配,在匹配过程中会忽略掉那些已经有驱动匹配的设备。
前面我们说过,内核是以数据结构的方式来管理总线、设备和驱动的,那么在内核中总线的数据结构是怎样的呢?内核中使用结构体bus_type来表示总线,具体内容如下:
struct bus_type {
const char *name;
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
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);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
struct subsys_private *p;
};
- name:总线的名称,当新注册一种总线的时候,系统会自动在/sys/bus目录下创建一个同名的目录。
- drv_groups 、dev_groups 、bus_groups: 分别表示驱动、设备以及总线的属性。这些属性可以是内部变量、字符串等等。通常会对应的/sys目录下在以文件的形式存在,对于驱动而言,在目录/sys/bus/< bus-name >/driver/< driver-name >存放了设备的默认属性;设备则在目录/sys/bus/< bus-name >/devices/< driver-name >中。这些文件一般是可读写的,用户可以通过读写操作来获取和设置这些attribute的值。
- match : 函数指针,当向总线注册一个新的设备或者是新的驱动时,会自动调用该回调函数进行设备和驱动的匹配。
- uevent:总线上的设备发生添加、移除或者其它动作时,就会调用该函数。
- probe : 当总线将设备以及驱动相匹配match之后,执行该回调函数,最终会调用驱动提供的probe函数。
- remove : 当设备从总线移除时,调用该回调函数。
- suspend、resume : 电源管理的相关函数,当总线进入睡眠模式时,会调用suspend回调函数;而resume回调函数则是在唤醒总线的状态下执行。
- p:该结构体用于存放特定的私有数据,其成员klist_devices和klist_drivers记录了挂载在该总线的设备和驱动。
我们可以通过下面两个函数向内核注册或注销一个总线。
//注册总线
int bus_register(struct bus_type *bus);
//注销总线
void bus_unregister(struct bus_type *bus);
但是,Linux内核已经为我们写好了大部分总线驱动,我们一般不用自行去注册一个新的总线。
/sys/bus/目录中包含了当前系统中已经注册了的所有总线,例如i2c,spi,platform等。每个总线目录都拥有两个子目录devices和drivers,分别记录着挂载在该总线的所有设备以及驱动。
2、设备
在内核使用device结构体来描述我们的物理设备,如下所示:
struct device {
const char *init_name;
struct device *parent;
struct bus_type *bus;
struct device_driver *driver;
void *platform_data;
void *driver_data;
struct device_node *of_node;
dev_t devt;
struct class *class;
void (*release)(struct device *dev);
const struct attribute_group **groups; /* optional groups */
};
- init_name:指定该设备的名称,总线匹配时,其中一种方式就是比较名字,来进行配对;
- parent:表示该设备的父对象;
- bus:表示该设备依赖于哪个总线,注册设备时,内核会将该设备注册到对应的总线;
- of_node:存放设备树中匹配的设备节点。当内核使能设备树,总线负责将驱动的of_match_table以及设备树的compatible属性进行比较之后,将匹配的节点保存到该变量;
- platform_data:特定设备的私有数据,通常定义在板级文件中;
- driver_data:同上,驱动层可通过dev_set/get_drvdata函数来获取该成员;
- class:指向了该设备对应类;
- dev:设备的设备号;
- release:回调函数,当设备被注销时,会调用该函数;(注意:如果未定义该函数,移除设备时,会报错; 若设备注销时无需处理,可定义一个空的release函数)。
- group:指向struct attribute_group类型的指针,指定该设备的属性;
我们可以通过下面两个内核API函数向内核注册或注销一个设备:
//注册设备
int device_register(struct device *dev);
//注销设备
void device_unregister(struct device *dev);
当成功注册总线时,会在/sys/bus目录下创建对应总线的目录,该目录下有两个子目录,分别是drivers和devices,我们使用device_register注册的设备从属于某个总线时,该总线的devices目录下便会存在该设备文件。
3、驱动
设备能否正常工作,取决于驱动。驱动需要告诉内核,自己可以驱动哪些设备,如何初始化设备。在内核中,使用device_driver结构体来描述我们的驱动。
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
const struct attribute_group **groups;
};
- name:指定驱动名称,总线进行匹配时,利用该成员与设备名进行比较;
- bus:表示该驱动依赖于哪个总线,内核需要保证在驱动执行之前,对应的总线能够正常工作;
- suppress_bind_attrs:布尔量,用于指定是否通过sysfs导出bind与unbind文件,bind与unbind文件是驱动用于绑定/解绑关联的设备;
- owner:表示该驱动的拥有者,一般设置为THIS_MODULE;
- of_match_table:指定该驱动支持的设备类型。当内核使能设备树时,会利用该成员与设备树中的compatible属性进行比较;
- remove:当设备从操作系统中拔出或者是系统重启时,会调用该回调函数;
- probe:当驱动以及设备匹配后,会执行该回调函数,对设备进行初始化。通常的代码,都是以main函数开始执行的,但是在内核的驱动代码,都是从probe函数开始的;
- group:指向struct attribute_group类型的指针,指定该驱动的属性;
内核提供了driver_register函数以及driver_unregister函数来注册/注销驱动,成功注册的驱动会记录在/sys/bus/< bus >/drivers目录,函数原型如下所示:
int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);