在Linux的设备驱动模型中,关心总线、设备和驱动这 3 个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成,设备和驱动谁先加载,最终都会通过总线来匹配设备和驱动。
一个现实的 Linux 设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI、USB、I2C、SPI等的设备而言,这自然不是问题,但是在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在 SoC内存空间的外设等确不依附于此类总线。基于这一背景,Linux发明了一种虚拟的总线,称为platform总线,除了本身依附于某类总线的设备外,其他设备的驱动都可以看做虚拟总线设备,相应的设备称为platform_device,而驱动成为platform_driver。引入platform 的意义:
(1)使得设备被挂接在一个总线上,因此,非总线设备也符合 Linux的设备模型。其结果是,配套的sysfs 结点、设备电源管理都成为可能。
(2)隔离BSP和驱动,在BSP中定义platform设备和设备使用的资源、设备的具体配置信息,而在驱动中,只需要通过通用 API 去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。
总线:各类总线如:PCI、USB、I2C、SPI以及虚拟platform总线;
设备:在板级文件(BSP)添加,是与具体平台有关的资源(中断源,IO地址等);
驱动:与平台无关的实现,具体硬件操作相关的代码。
在Linux驱动架构中,几乎不需要驱动开发人员再添加bus,因为Linux内核几乎集成所有总线bus,如PCI、USB、I2C、SPI等。并且总线bus中(与特定硬件相关的代码)已由芯片提供商编写完成。设备驱动层与特定device相干的就需要驱动工程师来实现了。
一、platform设备驱动
1.platform设备结构体
struct platform_device
{
const char *name; / * 设备名 */
u32 id; /* 设备ID*/
structdevice dev;
u32num_resources; / * 设备所使用各类资源数量 */
structresource * resource; / * 资源*/
};
2.platform驱动结构体
struct platform_driver
{
int (*probe)(struct platform_device *);
int(*remove) (struct platform_device *);
void(*shutdown) (struct platform_device *);
int(*suspend) (struct platform_device *, pm_message_t state);
int(*suspend_late) (struct platform_device *, pm_message_t state);
int(*resume_early) (struct platform_device *);
int(*resume) (struct platform_device *);
structpm_ext_ops *pm;
struct device_driverdriver;
};
3.platform总线结构体
struct bus_type platform_bus_type =
{
.name ="platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = PLATFORM_PM_OPS_PTR,
};
4.设备与驱动匹配
static int platform_match(struct device *dev, structdevice_driver *drv)
{
structplatform_device * pdev;
pdev =container_of(dev, struct platform_device, dev);
return(strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}
从代码看出,匹配 platform_device和 platform_driver主要看两者的 name字段是否相同,对 platform_device的定义通常在 BSP的板文件中实现。平台驱动的实现很简单,模块加载和卸载函数仅仅通过 platform_driver_register()、platform_driver_unregister()函数进行 platform_driver 的注册与注销,而原先注册和注销字符设备的工作已经被移交到platform_driver的probe()和remove()成员函数中。
5.平台设备资源
struct resource
{
resource__size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
通常只需要关心start、end和flags这3个字段,分别标明资源的开始值、结束值和类型,flags可以为 IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA 等。start、end 的含义会随着 flags 而变更,如当flags为 IORESOURCE_MEM 时,start、end分别表示该 platform_device 占据的内存的开始地址和结束地址;当 flags 为 IORESOURCE_IRQ 时,start、end 分别表示该 platform_device 使用的中断号的开始值和结束值,如果只使用了 1 个中断号,开始和结束值相同。对于同种类型的资源而言,可以有多份,例如说某设备占据了 2 个内存区域,则可以定义 2 个 IORESOURCE_MEM 资源。
对 resource 的定义也通常在 BSP 的板文件中进行,而在具体的设备驱动中透过 platform_get_resource()这样的 API 来获取,此 API 的原型为:
struct resource *platform_get_resource(struct platform_device*, unsigned int, unsigned int);
对于IRQ而言,platform_get_resource()还有一个进行了封装的变体platform_get_irq(),其原型为:
int platform_get_irq(struct platform_device *dev, unsignedint num);
二、设备总线驱动模型
kobject/kset 是设备模型的基本结构体,设备模型使用这两个结构体来完成设备的层次关系,但在实际的设备驱动编写中,我们基本上用不到kobject/kset这些结构体,是因为这些结构体又被嵌入到更大的结构体中,原因在于kobject/kset结构体只能表征设备的层次关系,但是一个设备的驱动,并不是简单的一个层次关系而已,因此,必需要把kobject/kset结构体嵌入到更大的结构体中,使用kobject/kset来表征层次关系,用其他的成员表示设备 驱动的具体功能。
在设备模型中,我们将看到,设备驱动主要是由总线,驱动程序,设备三个部分构成,通过这三个标准部件,把各种纷繁杂乱的设备归结过来,达到简化设备驱动编写的目的,也即我们编写的设备驱动,其实也只是这三部分中的一个很小的部分的。
我们编写的设备驱动程序,一定是先属于一个总线的驱动,比如属于USB总线,或者属于PCI总线,或者属于I2C总线,等等,因为我们编写的设备驱动,在注册、安装到系统时,系统会先检查驱动是属于哪个总线的(设备驱动编写时已经定义好),会把驱动加入到对应的总线的kset中,即把当前设备驱动的kobject加入到对应总线的kset中,形成层次关联。而当系统检测到有设备(硬件)存在,也会先判断设备是属于哪个总线的(硬件连接),然后遍历当前总线下的所有设备驱动程序,通过所属总线的探测函数,查找是否有设备 驱动程序匹配可以驱动当前的设备(一般是通过获得设备的PID,VID,跟驱动程序的PID,VID比较,看是否匹配而定),如果有驱动程序可以驱动设备,则把当前设备也加入到所属总线的kset中,如果没有可驱动设备的驱动程序,则只能在总线的设备链表中存在,而如果设备都无法通过总线的匹配,则也没有办法存在于总线的设备链表中。由于一条总线要管理总线上的所有驱动,同时要管理总线上的有所设备,则需要再把所有设备和所有驱动都分开,分别设立一个设备kset和一个设备驱动kset,用于管理所有的设备和设备驱动,如此,则总线kset实际上包含了两个kset(设备kset,设备驱动kset), 设备kset又包含了所有的当前总线的设备的kobject,设备驱动kset包含了所有的当前总线的设备驱动的kobject;而所有的总线,又形成了bus的kset,归结起来就形成下图的层次关系:
每个设备,都被挂接到不同的总线上,当设备挂接到对应的总线上 后,其所对应的总线类型就确定了,而设备在挂接到总线上时,总线先要扫描设备,看看设备是否适合总线的要求,如果适合了,那接着就要扫描整个总线上的设备驱动链表,查找是否有驱动程序可以管理设备,如果找到,则把设备结构体中的相应指针成员指向对应的驱动程序,如果暂时没有找到对应的设备驱动程序,则设备结构体中的指向驱动程序的指针暂时为空,表示还没有设备驱动,还在总线的设备队列中等待;而如果设备不能通过总线的检查,即不会出现在总线的设备列表上, 自然不会去扫描设备驱动链表,查找匹配的驱动了。
而每个设备驱动程序,都是被安装到对应的总线上的,不论是手动安装,还是自动安装,所谓安装,就是把驱动程序挂载到对应总线的驱动链表中,而挂载到对应的总线驱动链表,首先要满足总线的匹配要求,只有适合了要求,才能 挂载到总线的驱动链表,也只有到达这个步骤,系统才会扫描整个总线的设备链表,来查找是否有设备需要此驱动来管理,如果找到这个设备,则驱动程序中的设备 管理链表,会记录这个设备的地址,从而达到管理设备的目的。
经过上述的设备插入,或者驱动安装,系统就会出现只有设备,而没 有设备驱动程序的情况,也会出现,只有设备驱动程序,没有对应的设备的情况,此时,设备或者设备驱动程序,就会暂时在各自的队列里等待,一旦有驱动程序安 装,或新的设备插入,就都会自动的去扫描对应的链表,来检测是否有配对的可能。