以目前为止的逻辑,无论是获取设备属性信息,还是实现驱动逻辑,都是放在一个驱动模块中。在没有设备树的情况下,如果我们只需要修改设备信息(如寄存器地址),那么我们就需要重新编译整个驱动模块。
很显然,设备信息的变化不应该影响到驱动逻辑的正常运行,这就需要引入驱动分层的概念。
驱动分层总体可以分为三层
驱动程序要想驱使设备,需要先让驱动与设备匹配,匹配工作由总线负责。只有当左侧的设备与右侧的驱动程序建立联系以后,驱动程序才可以驱使设备。
(1) 当我们向系统注册一个驱动,总线会在左侧查找是否存在与之匹配的设备;
(2) 当我们向系统注册一个设备,总线会在右侧查找是否存在与之匹配的驱动;
=============== 不使用设备树 ===============
不使用设备树时需要手动注册 platform 设备和 platform 驱动,手动注册 platform 设备其实就是在向内核添加硬件外设信息。
=============== 使用设备树 ===============
使用设备树后,无需自己手动注册 platform 设备,只需注册 platform 驱动。在开发板上电加载Linux 内核和设备树的时候,会自动将设备树信息转换成 platform_device 类型。
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 文件中。
这里主要初始化下面三个成员变量:
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;
};
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;
};
platform 设备注册接口原型如下:
/**
* @param pdev 要注册的platform设备
* @return 成功返回 0 ,失败返回负值
*/
int platform_device_register(struct platform_device *pdev);
platform 设备注销接口原型如下:
/**
* @param drv 要注销的platform设备
*/
void platform_device_unregister(struct platform_device *pdev);
在Linux内核中使用 platform_driver 结构体来表示 platform 驱动,该结构体定义在 linux/platform_device.h 文件中。
需要注意其中的 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;
};
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;
// ...
}
platform 驱动注册接口原型如下(本质是宏):
/**
* @param drv 要注册的platform驱动
* @return 成功返回 0 ,失败返回负值
*/
int platform_driver_register(struct platform_driver *drv);
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);
platform 驱动注销接口原型如下:
/**
* @param drv 要注销的platform驱动
*/
void platform_driver_unregister(struct platform_driver *drv);