本节使用总线设备驱动模型,点亮和熄灭led小灯。
上一节,在驱动程序中指定引脚,每次修改都需要重新编译内核并且需要查引脚,十分麻烦。使用总线设备驱动模型不用重新编译内核就可以改变引脚。
总线设备驱动模型由以下三部分组成:
- 总线 —— Bus;
- 设备 —— Dev;
- 驱动 —— Drv;
其中 Dev 和 Drv 都依赖于Bus(纯软件概念,不涉及硬件设备)。
它们的用途如下:
- Dev:指定硬件资源;
- Drv:
a.分配/设置/注册file_operations结构体;
b.根据Dev指定的硬件资源来操作硬件;
问:在Dev中是如何指定硬件资源的?
答:以面向对象的思想,在内核中定义一个dev时,也是去分配/设置/注册一个结构体,这个结构体是一个平台设备。
在\linux-4.19-rc3\include\linux\platform_device.h中,有一个platform_device结构体,dev中会分配/设置/注册这个结构体。
Drv中也是类似的操作,去分配/配置/注册platform_driver结构体。
问: 内核里面有很多Dev,也有很多Drv,怎么知道哪个Dev对应哪个Drv呢?
答:它们之间需要依靠一条总线(Bus)来匹配。
在Bus里面,我们常用的是平台总线(platform bus),在platform bus里面有一个match函数,这个match函数就是来比较匹配Drv和Dev。
如果确定某个dev和某个drv匹配,则会调用drv的drv->probe函数。
问:match是怎么匹配Dev和Drv的?
答:在Dev设备的平台设备结构体platform_device中有一个name成员,它就是这个设备的名字。
在Drv驱动的platform_driver结构体中有一个id_table成员,这是一个结构体指针,指针指向的结构体有两个成员,name和driver_data;还有一个结构体成员driver,driver结构体中有一个name成员。
查看platform_match函数,其中of_driver_match_device函数的of是open firmware,开放固件,这个是使用设备树来匹配的,先不管。
后面pdrv->id_table,如果该驱动有id_table的话,就会调用platform_match_id函数。
在platform_match_id函数中,会将id_table的每一项,与dev设备的名字进行匹配。如果两个名字相同,就表示它们是匹配的。
可以在id_table中添加多个设备名,表示这个驱动支持多款设备,比如:
这样,即时设置不同的dev名,也可以选择相同的驱动。
如果没有id_table,则直接比较平台设备的名字和驱动的drive成员中的名字。
综上,优先比较id_table的名字,没有匹配则再比较driver的名字 。
一旦匹配成功,就调用drv里面的probe函数。
机制讲完,现在开始写代码。
Dev
首先是分配/设置/注册一个platform_device。
resource如下,其中flags设置为IORESOURCE_MEM,由于没有引脚资源,所以设置为mem资源,实际上我们不将它做mem资源使用,会在驱动程序中将引脚读出来,当做引脚使用。
release函数为空。
然后,与drv类似,有init函数和exit函数,在init函数中注册platform_device,在exit函数中卸载platform_device。
Drv
在之前的led_drv.c的基础上进行修改。
首先,不在代码中写死led_pin。
然后,创建一个platform_driver驱动。
增加probe函数,一旦dev和drv匹配成功,就会调用drv的probe函数。
在probe函数中,获取dev的MEM资源,其中0代表MEM资源的第0个,将start设为led_pin,然后和之前的入口函数一样,创建类和dev设备,删除以前的入口函数。
led_remove函数中卸载字符设备,销毁led_class,删除以前的出口函数。
同样需要注册和卸载平台驱动platform_driver。
测试
编译完成后将dev和drv的ko库推入板子,将app推入板子。
先insmod drv,可以看到这时候并没有led设备产生,因为bus没有检测到对应的dev设备。
insmod dev设备后,可以在dev目录下看到led设备了。
然后调用app应用,可以看到根据不同的指令,可以通知小灯点亮或者熄灭。
现在如果需要控制另一个小灯,只要修改dev.c的代码就可以了,drv.c的代码可以不用改动。
将控制的引脚改为F6。
重新编译上传,将原来的led_dev设备卸载掉。
加载新的led_dev,通过不同的指令可以控制另一个小灯的亮灭。
可以看到,总线设备驱动模型可以把代码分为dev和drv两部分,其中drv是比较固定的,dev则是用来指定硬件资源。
最后,我们来看看注册platform_device和注册platform_driver所触发的match和probe过程是怎么进行的。
可以想象,当我们注册一个platform_device的时候,实际上是将一个platform_device加入到链表中,每次有新的platform_device加入时,就会把platform_driver的链表中的成员一一取出,和这个platform_device进行匹配;同理,platform_driver也是类似的。
所以,无论是先注册platform_device还是先注册platform_driver,都没有关系,结果都是一样的。