1.LED驱动程序框架设计-分离
在上一篇博文中编写的驱动程序仅能操作固定开发板上的一个LED灯。考虑现实情况,使用同一款主控芯片,不同厂家可能会设计不同的PCB板,针对硬件使用的芯片引脚各不相同,但是同一款主控针对同一硬件的设置都是相似的。因此,我们考虑针对通用LED驱动程序,主控芯片,开发板来分离出三层,以便我们的LED驱动程序能够支持不同主控不同开发板使用:

1.1 开发板层
led_resources.h
1 |
|
board_A_imx6ull.c
1 |
|
在该层主要定义开发板上所分别的LED资源,通过定义结构体led_resources并向上一层提供get函数。
在示例中定义了GPIO5_3和GPIO1_1两个LED引脚。针对不同的开发板需要编写不同.c文件来定义LED引脚。
1.2 芯片层
led_operations.h
1 |
|
在芯片层的.h文件中,定义所有可能成为LED引脚的相关寄存器地址(当然作为测试仅定义了GPIO5_3和GPIO1_1的相关寄存器),以及定义该芯片实现LED操作所需要的函数,以供上一层调用。
chip_imx6ull_gpio.c
1 |
|
在芯片层的.c文件中实现了led_operations结构体中定义函数,该.c文件所实现的函数是针对imx6ull芯片而开发的。支持其他芯片在新的.c文件中实现相应的操作逻辑。
1.3 通用LED驱动程序
1 |
|
该驱动程序就是通用的驱动框架,只不过具体的引脚以及不同芯片针对引脚的寄存器操作都被分离到芯片层和开发板层了。在使用时,只需要利用下面提供的Makefile将通用LED驱动和相应的芯片.c文件和相应开发板的.c文件进行编译链接即可:
1 | KERN_DIR = /home/book/100ask_imx6ull_mini-sdk/Linux-4.9.88 |
通过分层的方式,从而实现了解耦合,使得我们的LED驱动程序具有通用性。
1.4 测试程序
1 |
|
2. 总线设备驱动模型
在第1节的基础上,进一步考虑支持开发板上的所有硬件资源。在第1节中,我们主要定义了两个结构体:
1 | struct led_resources { |
并通过相应的.c文件对结构体的内容进行填充。不同的硬件对应的引脚配置和操作函数大体上都是类似的,我们可以考虑定义一个通用的引脚配置结构体struct platform_device和通用的操作配置结构体struct platform_driver,然后在一个.c文件中实现针对主控芯片的所有硬件的操作函数,在另一个.c文件中实现针对某一款开发板的所有硬件的引脚定义,从而将第1节的分离思想应用到所有的硬件:

2.1 理解Bus/Dev/Drv模型
结构体platform_device的定义:
1 | struct platform_device { |
目前用到的几个结构体成员:
- name:自定义的设备名称,可以根据该名称将
platform_device与platform_driver进行匹配 - resource:硬件引脚资源结构体
- num_resources:资源的数量
- driver_override:首先匹配
driver_override所表示的platform_driver
结构体struct resource:
1 | struct resource { |
start/end:用来表示地址的
u32整型数据name:引脚名称标识
flag:标识引脚资源类型,后面
platform_get_resource的参数 type 指的类别就是下面这些1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/*
* IO resources have these defined flags.
*
* PCI devices expose these flags to userspace in the "resource" sysfs file,
* so don't move them.
*/
结构体platform_driver:
1 | struct platform_driver { |
比较重要的几个成员:
- probe:当总线扫描到设备树中的
platform_device并与相应的platform_driver进行匹配之后,内核会调用probe函数,probe(探针)函数包含了初始化设备的逻辑。 - remove:当总线或者内核监测到硬件设备拔出事件之后,通常会触发对应驱动程序调用remove函数,该函数包含清理设备资源的逻辑。
- driver->name:与
platform_device的name或者driver_override成员进行匹配的名称 - id_table:表示该 drv 支持若干 个 device,它里面列出了各个 device 的
{.name, .driver_data},其中的name表示该 drv 支持的设备的名字,driver_data是些提供给该 device 的私有数据,与platform_device的name进行匹配
2.1.1 Dev和Drv的匹配
(1) 匹配优先级(大概了解,后面详细讲)
- 最先比较
platform_device.driver_override和platform_driver.driver.name - 然后比较
platform_device.name和platform_driver.id_table[i].name - 最后比较
platform_device.name和platform_driver.driver.name
(2) 函数调用关系
1 | platform_device_register |
2.1.2 常用函数
这些函数在内核源码:drivers/base/platform.c
(1) 注册/取消注册
1 | platform_device_register/ platform_device_unregister |
(2) 获取资源
返回该 dev 中某类型(type)资源中的第几个(num):
1 | struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num) |
返回该 dev 所用的第几个(num)中断:
1 | int platform_get_irq(struct platform_device *dev, unsigned int num) |
通过名字(name)返回该 dev 的某类型(type)资源:
1 | struct resource *platform_get_resource_byname(struct platform_device *dev, unsigned int type, const char *name) |
通过名字(name)返回该 dev 的中断号:
1 | int platform_get_irq_byname(struct platform_device *dev, const char *name) |
2.2 编写代码
2.2.1 开发板层
board_A_imx6ull.c
1 | static void led_dev_release(struct device *dev) |
当模块安装时会调用led_dev_init,模块卸载时会调用led_dev_exit。
注:必须要提供release函数,如果不提供则在调用platform_device_unregister时会出现警告:
1 | WARN(1, KERN_ERR "Device '%s' does not have a release() " |
2.2.2 芯片层
chip_imx6ull_gpio.c
这一部分对应芯片层的硬件操作,和上一节一致:
1 | static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */ |
这一部分对应platform_driver配置和安装和卸载函数:
1 | static int g_ledpins[100]; |
2.2.3 通用驱动层
1 | static int major = 0; |
通用驱动层的代码和之前几乎一样,只不过设备文件的创建因为和硬件资源的分配有关,所以放到了芯片层完成。
EXPORT_SYMBOL():EXPORT_SYMBOL标签内定义的函数或者符号对全部内核代码公开,使用EXPORT_SYMBOL可以将一个函数以符号的方式导出给其他模块使用。