【Leo的手记】Linux设备驱动程序手记4-平台设备驱动

1.平台设备驱动
对于通用的设备驱动程序,将业务逻辑和具体的硬件资源分开是一个很方便且整洁的设计思想。在Linux系统内核源码的设计中,提供了一套被称为平台设备的设备类型,该类型并不对应于某一类物理设备,而是对系统设备的一类抽象。平台设备驱动分为platform_driver和platform_device两大类实现并统一挂载于platform设备总线上,以实现对业务逻辑和硬件资源的分离。其思路为,platform_driver提供了对于逻辑操作的实现,platform_device由着具体的硬件实现在使用时加载注册到系统之中。platform_device中提供对所需硬件资源的描述,该platform_device将被platform设备总线在其加载时匹配到对应的platform_driver并触发platform_driver的probe方法,并提供一类方法能使得platform_driver能够获得当前所触发的platform_device中的相关资源描述信息。最终实现硬件初始化与具体操作。
在Linux系统内核源码中,platform_device和platform_driver的定义均以结构体的形式所确定。如下
platform_device结构体
platform_driver结构体
其中,device_driver结构体定义如下
2.平台设备总线如何匹配platform_device和platform_driver
平台设备总线使用着两个链表,分别为用于存储设备的dev和用于存储驱动的drv,在注册platform_device或platform_driver时,会被分别放入对应的链表。在platform_device或者platform_driver进行注册时,会对对方链表中的成员进行一一比较。如果匹配成功,则会调用平台设备驱动中的probe方法。而匹配规则如下:
先使用platform_device中的driver_override成员去匹配platform_driver中的driver成员中的name成员。
再使用platform_device中的name成员去匹配platform_driver中的id_table成员。
最后匹配platform_device中的name成员去匹配platform_driver中的driver成员中的name成员。
3.注册平台设备和平台设备驱动
分别实现platform_device和platform_driver结构体,按照匹配规则对相应的结构体成员进行赋值。最后通过platform_device_register和platform_driver_register方法向系统注册平台设备和平台设备驱动。需要注意的是,由于平台设备所提供的分离特性便于复用驱动代码,所以最好是将platform_device和platform_driver的实现编译为不同的内核模块。
platform_device示例代码如下
该代码仅仅包含了注册平台设备的过程,没有实现任何资源的定义。
对应的平台设备驱动如下
分别编译两份内核模块。
编译完成后,分别加载模块并查看内核日志。为了验证平台设备的匹配过程,我们分别以先驱动后设备以及先设备后驱动的加载方式加载两模块。

先加载平台设备驱动后加载平台设备
可以看到,平台设备驱动程序先注册成功,然后在加载注册平台设备时,先触发了设备探针,执行了probe方法,然后提示平台设备注册完成。

可以看到,先是在加载平台设备后内核日志输出平台设备注册成功,在加载平台驱动时,触发了设备探针。这便是平台设备与驱动之间互相匹配的过程。而在日志中我们可以看出,在卸载平台设备驱动或卸载设备时,remove方法会被调用。事实上,当platform_driver存在于内核并卸载与其匹配的platform_device时,会触发对应驱动程序的remove方法,而在platform_device未被卸载而卸载platform_driver时,将会卸载每一个platform_device。
4.平台设备资源的传递
对于platform_device,通常是需要指定一些资源描述信息,用以提供给platform_driver进行有针对性的初始化操作。因此如何将资源的描述与定义内置到相应的驱动中,并传递到对应的平台设备驱动,则是一个需要处理的问题。
在Linux平台设备驱动框架下,platform_device可以通过其resource成员来定义所使用的硬件资源。 其resource成员是struct resource类型的数组,struct resource定义如下
通常,我们关注它的start,end和flags成员,分别表示资源起始值,结束值和资源类型。start和end的具体含义随flags的不同具有不同的确切定义。如下
flags为IORESOURCE_MEM时,start和end表示设备所占据的内存的起始地址与结束地址。
flags为IORESOURCE_IRQ时,start和end表示设备所使用的中断号的开始值和结束值(左闭右闭)
而在platform_driver中,可以使用platform_get_resource方法进行资源的获取。其函数原型如下
dev:指向platform_device结构的指针,表示平台设备。
type:资源的类型,如IORESOURCE_MEM
index:资源的索引
我们在上一个代码中进行修改,添加资源。代码如下
platform_device代码
platform_driver代码
编译后,运行结果如下


可以看到,平台设备驱动在匹配时,通过probe方法传参得到的platform_device句柄获取了其资源。
5.平台设备其他信息的传递
在驱动程序对设备进行操作时,其所需的资源并非只是对于硬件资源的一些定义,另有譬如一些额外的配置信息需要传递,这一类的数据可以借用resource进行表示但其语义性差。Linux平台设备结构中提供了platform_data的支持。其可由驱动进行自定义。在使用时可以只存放一个结构体指针。解析时根据指针进行解析。该成员位于platform_device的device成员中的driver_data成员。该成员是void类型的指针。
我们可以在上一个例子的基础上通过driver_data传递一些额外的数据,示例代码如下
以上代码定义了一个自定义的结构体,封装为一个头文件leotest.h被platform_device和platform_driver所包含。
在platform_device中,添加对应数据的定义。
而在platform_driver中,通过成员的引用获取其地址。
运行结果如下

6.通过平台设备总线创建字符设备
我们可以通过平台设备实现对于字符设备的动态注册。在此我们实现一组平台设备及其驱动。在驱动中管理和维护设备号,在设备中实现对资源的定义。
在此实现一种暂时不需要定义资源的字符设备的实现,代码如下
platform_driver实现
platform_device实现
将platform_device复制一份,并修改其name成员,作为第二个platform_device。
将platform_driver和两个platform_device交叉编译后放入SD卡,插入F1c200s核心板中,加载platform_driver。如下图

可以看到,内核模块得到了正确的加载,但是相应的class并没有创建,然后加载第一个platform_device

发现class得到了创建,同时在/dev目录下也出现了leotest63的设备,加载第二个platform_device后,系统中创建了第二个leotest设备

当我们卸载platform_driver时,所有的设备和设备类都会被销毁

由此可以实现通过平台设备总线实现对设备的管理。