Linux设备模型
一、概述
前面讲到了设备驱动的编写方法,但是我们会发现我们编写的驱动不够智能,或者说是实用性很差。比如:
- 设备和驱动没有分离。这样一旦硬件信息改变,我们的驱动就无法使用了。
- 不能查看相应的设备和驱动的的信息,比如Windows中有设备管理器,我们就可以比较方便的查看相关的设备和驱动的信息。
- 不能自动创建设备节点
- 驱动不能自动加载
- 没有电源管理
等等这些都是我们前面编写驱动程序的缺点,那么有什么比较好的办法可以解决吗?当然,Linux也提供了一套设备驱动模型用于Linux平台下的设备管理。接下来,我们一起来学习下如何使我们的设备驱动程序变得更加智能。
二、设备模型基础
前面我们提到了,关于设备和驱动信息的展示。在Linux系统中有一个sysfs伪文件系统,挂载与/sys目录下,该目录罗列了设备、驱动和硬件相关的信息。**伪文件系统是在系统运行时才会有内容,也就是为文件系统的目录、文件、以及软连接都是动态成的。**下面我们来看一下生成这些信息的一个重要的数据结构–struct kobject
当向内核成功添加一个kobject对象后,底层的代码会自动在sys/目录下生成一个子目录。另外kobject可以附加一些属性,并绑定操作这些属性的方法,当向内核成功添加一个kobject对象后,其附加的属性会被底层的代码自动实现为对象对应的目录下文件。用户访问这些文件最终变成了调用操作属性的方法来访问其属性。最后通过sys的API接口可以将两个kobject对象关联起来,形成软链接。
三、总线、设备和驱动
USB总线会在外部留出很多USB接口,挂载很多USB设备。为了让这些设备能正常工作,系统上也会安装其对应的驱动。虽然这些驱动在硬件上和USB总线没有直接的连接,但是从软件层面来看,他们是注册在USB总线下面的。比如,当接入一个USB设备时,USB总线会立即感知到这件事,并去遍历所有注册在USB总线上的驱动(在这个过程中可能会自动加载一个匹配的USB驱动),然后调用驱动中的一段代码来探测是否能够驱动刚插入的USB设备,如果可以,那么总线完成驱动和设备之间的绑定。

结合上面的图,我们可以发现,该模型一共可分为三个对象:总线、设备、驱动。Linux设备模型给这三种对象各自定义了对应的类:struct bus_type、struct device、struct device_driver。
三者都内嵌了struct kobject或kobject kset,于是就会生成对应的目录。在这里我们使用了面向对象的思想来理解这个模型。将这三个对象分别进行实现,就实现了设备和驱动的分离。
设备专门用来描述设备所占有的资源信息,驱动和设备绑定成功后,驱动负责从设备中动态获取这些资源信息,当设备资源改变后,仅仅只是设备改变了而已,驱动的代码可以不做任何修改。
这就大大提高了驱动代码的通用性。另外我们需要记住:总线是连接设备和驱动这两者的桥梁,是一条重要的纽带。
三、平台设备及其驱动
1. 平台设备
要满足Linux设备模型,就必须有总线、设备和驱动。但是有的设备并没有对应的物理总线,比如LED、蜂鸣器等。为此,内核专门开发了一种虚拟总线—platform总线。platform 总线专门用来连接这些没有物理总线的设备或者一些不支持热插拔的设备。(热插拔会在后面讲到,这里先不表)
平台设备使用struct platform_device来表示,定义如下:
struct platform_device{
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resources *resources;
cosnt struct platform_device_id *id_entry;
/*MFD cell pointer*/
struct mfd_cell *mfd_cell;
/*arch specific additions*/
struct pdev_archdata archdata;
};
@name: 设备的名字,在平台总线match函数中可用于不同的平台 驱动的匹配
@id: 设备的ID号,用于区别类型不同的平台设备
@dev: 内嵌的struct device
@num_resources: 平台设备使用的资源个数
@resource: 平台设备的资源列表(数组),指向资源数组中首元素
@id_entry: 用于同平台驱动匹配的ID,在平台总线的match函数中首先尝试匹配该ID,如果不成功再尝试使用name成员来匹配。
上面提到了很重要的一点:即总线是如何将设备与驱动匹配上的,即ID和name。
在平台设备中,最关键的就是设备使用的资源信息的描述,这是实现设备驱动分离的关键。struct resource的定义如下:
struct resource{
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent,*sibling,*child;
};
@start: 资源的开始,对于I/O内存来说就是起始的内存地址,对于中断资源来说就是起始的中断号,对于DMA资源来说就是起始的DMA通道号。
@end: 资源的结束
@flags: 资源的标志,定义在"include/linux/ioport.h"文件中,最常见的几个如下:
IORESOURCE_MEM: 资源类型是内存资源,也包括IO内存
IORESOURCE_IRQ: 资源类型是中断资源
IORESOURCE_DMA: 资源的类型是DMA通道资源
平台资源可以组成一个树形结构,由成员parent、sibling、child组成
平台设备及其资源常存在于BSP(Board Support Package, 板级支持包)文件中,该文件通常包含和目标板相关的一些代码。
向平台总线注册和注销平台设备的API接口主要如下:
int platform_add_device(struct platform_device **dev,int num);
/*功能:用于一次注册多个平台设备,其本质是通过多次调用platform_device_register来实现的。*/
int platform_device_register(struct platform_device *pdev);
/*一次只注册一个设备*/
void platform_device_unregister(struct platform_device *pdev);
/* 注销平台设备 */
当平台总线发现有和平台设备匹配的驱动时,就会调用平台驱动内的一个函数,并传递匹配的平台设备结构地址,平台驱动就可以从中获取设备资源信息。主要函数如下:
struct resource *platform_get_resources(strcut platform_device *dev,unsigned int type,unsigned int num);
resource_size_t resouce_size(const struct resource *res);
@platform_get_resource: 从平台dev中获取类型为type、序号为num的资源。
@Resource_size: 返回资源的大小,其值为end-start+1。
2. 平台驱动
平台驱动是用struct platform_driver结构来表示的,其定义如下:
struct platform_driver{
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
int (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *,pm_message_t state);
int (*resume)(struct platform_device_id *id_table);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
我们主要关心以下参数:
@ probe: 总线发现有匹配的平台设备时调用
@ remove: 所驱动的平台设备被移除时或平台驱动注销时调用。
@ shutdown、suspend和resume: 电源管理函数,在要求设备掉电、挂起和恢复时被调用。内嵌的struct device_driver的pm成员也有对应的电源管理函数
@ id_table: 平台驱动可以驱动的平台设备id列表,可用于及平台设备的匹配。
向平台总线注册和注销的平台驱动主要函数如下:
platform_driver_register(drv);
void platform_driver_unregister(struct platform_driver *);
3. 热插拔
前面有提到热插拔这个词,但是并没有展开来说,下面我们来简单看一下热插拔的原理。
我们可以通过加载内核驱动模块来向系统添加设备驱动,也可以通过移除模块来删除设备驱动。这样来讲,我们要做的事就很多,而且当有设备添加后,内核不能自动的加载驱动。显得非常不智能。
对于这样的操作,我们肯定会想使设备被添加到系统后 ,其驱动能够自动被加载,这对于实际的可支持热插拔的硬件来说就非常有必要有必要。比如,我们插入一个USB无线网卡,那么对应的驱动就应该自动加载,而不是由用户来手动加载。简单来讲,其实这就是热插拔。
要实现热插拔功能,即内核识别设备后自动加载设备对应的驱动,就必须要利用到一个工具—udev,在嵌入式系统中通常用mdev,其性能比udev要弱很多,但也可以移植udev到嵌入式系统上。
关于udev实现热插拔简单来讲:使用了Linux设备模型后,任何设备的添加、删除或状态修改都会导致内核向用户空间发送相应的事件,这个事件叫uevent,和kobject密切关联。这样用户空间就可以捕获这些事件来完成某些操作,比如自动加载驱动、自动创建和删除设备节点、修改权限、创建软连接、修改网络设备的名字等等。目前实现这个功能的工具就是udev(或mdev),这是一个用户空间的应用程序,用来捕获来自内核空间发来的事件,然后根据对应的规则文件进行相关操作。udev的规则文件为**/etc/udev/rules.d目录下后缀为.rules**的文件。
规则文件的一些简单语法规则:
- 用#来进行注释
- 除注释外就是一条条的规则,每条规则至少包含一个键值对,键分为匹配和赋值两种类型。如果内核发来的事件匹配了规则中所匹配键值对,键分为匹配和赋值两种类型。如果内核发来的事件匹配了规则中的所有匹配键的值,那么这条规则就可以得到应用,并且赋值键被赋予指定的值。一条规则包含了一个或多个键值对,这些键值对用逗号隔开,每个键由操作符规定一个操作。
1598

被折叠的 条评论
为什么被折叠?



