以目前为止的逻辑,无论是获取设备属性信息,还是实现驱动逻辑,都是放在一个驱动模块中。在没有设备树的情况下,如果我们只需要修改设备信息(如寄存器地址),那么我们就需要重新编译整个驱动模块。
很显然,设备信息的变化不应该影响到驱动逻辑的正常运行,这就需要引入驱动分层的概念。
一、整体架构
驱动分层总体可以分为三层
- 设备层:负责管理设备属性信息,包含了一些外设硬件信息,如寄存器地址、引脚配置信息等
- 驱动层:负责驱使设备的正常运作,驱动程序借由总线传递控制信号、数据,进而来控制设备
- 总线:驱动和设备信息的月老,负责设备和驱动程序之间的通信和数据交换
驱动程序要想驱使设备,需要先让驱动与设备匹配,匹配工作由总线负责。只有当左侧的设备与右侧的驱动程序建立联系以后,驱动程序才可以驱使设备。
(1) 当我们向系统注册一个驱动,总线会在左侧查找是否存在与之匹配的设备;
(2) 当我们向系统注册一个设备,总线会在右侧查找是否存在与之匹配的驱动;
二、总线
Linux 内核使用 bus_type 结构体来表示总线,而 platform 总线是 bus_type 的一个实例,定义在 drivers/base/platform.c ,该文件中同样也定义了驱动和设备是否匹配的检测方式。
方式一:of 类型的匹配
设备树中包含 compatible 属性,而驱动中也有一个 of_match_table 成员变量,这个可以看做是驱动中的 compatible 属性。匹配时会检查设备树的compatible属性和驱动的 of_match_table 变量是否包含相同条目,如果存在,说明此设备和驱动匹配。
方式二:ACPI 匹配
驱动程序中包含一系列自己兼容的设备描述符,该设备描述符包含设备类型、供应商ID、设备ID等属性。以此作为xx设备是否与驱动匹配的判断依据。
方式三:id_table 匹配
在Linux内核中使用 platform_driver 结构体来表示 platform 驱动,每个 platform_driver 结构体有一个 id_table 成员变量,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型。
方式四:name 字段匹配
直接比较驱动模块 platform_driver 中 device_driver 的 name 变量 和设备树中的 name 属性(或platform_device的name变量)
三、设备层
在Linux内核中使用 platform_device 结构体来表示 platform 设备,该设备可以是一个真实存在的设备,也可以只是一个虚拟的设备。platform_device 结构体定义在 linux/platform_device.h 文件中。
1、platform_device
这里主要初始化下面三个成员变量:
- name:后续测试时使用第四种方式匹配,对此就需要用到 platform_device 的name变量
- resource:资源数组,保存了寄存器相关信息(寄存器的起始、终止地址)
- num_resources:resource 数组中资源的个数
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
2、resource
resource 结构体保存的是某个寄存器相关内容,resource 结构体的声明保存在 linux/ioport.h
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
- start:寄存器的起始地址
- end:寄存器的终止地址
- flags:表示资源类型,同样定义在 linux/ioport.h
3、注册 platform 设备
platform 设备注册接口原型如下:
/**
* @param pdev 要注册的platform设备
* @return 成功返回 0 ,失败返回负值
*/
int platform_device_register(struct platform_device *pdev);
4、注销 platform 设备
platform 设备注销接口原型如下:
/**
* @param drv 要注销的platform设备
*/
void platform_device_unregister(struct platform_device *pdev);
四、驱动层
在Linux内核中使用 platform_driver 结构体来表示 platform 驱动,该结构体定义在 linux/platform_device.h 文件中。
1、platform_driver
需要注意其中的 probe 函数和 remove 函数,当设备和驱动匹配成功,会调用probe函数;当 platform 驱动从内核中移除,会调用 remove 函数。
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 (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
2、device_driver
device_driver 保存与设备匹配时的相关驱动信息,在后续测试时使用第四种方式匹配,对此就需要用到 device_driver 结构体中的 name 变量
struct device_driver {
const char *name; // platform 驱动名称
struct bus_type *bus; // 总线类型
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
// ...
}
3、注册 platform 驱动
platform 驱动注册接口原型如下(本质是宏):
/**
* @param drv 要注册的platform驱动
* @return 成功返回 0 ,失败返回负值
*/
int platform_driver_register(struct platform_driver *drv);
4、获取platform设备资源
platform_get_resource 可获取platform_device 中的 resource 数组,从上面可以了解到 resource 数组保存的是寄存器地址信息。
/**
* @param pdev 要访问的platform设备
* @param type 资源类型,对应resource结构体中的 flag 成员
* @param index 访问resource数组的下标
* @return 成功返回 resource 数组的首地址,失败返回0
*/
struct resource *platform_get_resource(struct platform_device * pdev,
unsigned int type,
unsigned int index);
5、注销 platform 驱动
platform 驱动注销接口原型如下:
/**
* @param drv 要注销的platform驱动
*/
void platform_driver_unregister(struct platform_driver *drv);