目录
一、Linux 设备驱动分层和分离
1.设备驱动的分层思想
2.主机驱动和外设驱动分离思想
二、Platform 平台驱动模型
1.platform 设备
2.platform 驱动
3.platform 总线
一、Linux 设备驱动分层和分离 1.设备驱动的分层思想在面向对象的程序设计中, 可以为某一类相似的事物定义一个基类, 而具体的事物可以继承基类的成员。 如果对于继承的这个事物而言, 其某函数的实现与基类一致, 那它就可以直接继承基类的成员; 若不一致,它可以重载之。 这种面向对象的设计思想极大地提高了代码的可重用能力, 是对现实世界事物间关系的一种良好呈现。
Linux 内核完全由 C 语言和汇编语言写成, 但是却频繁用到了面向对象的设计思想。 在设备驱动方面,为同类的设备设计了一个框架, 框架中的核心层则实现了该设备通用的一些功能。 同样的, 如果具体的设备不想使用核心层的函数, 它可以重载之。
return_type core_funca(xxx_device * bottom_dev, param1_type param1, param1_type param2)
{
if(bottom_dev->funca)
return bottom_dev->funca(param1, param2);
/* 核心层通用的 funca 代码 */
...
}
上述 core_funca 的实现中, 会检查底层设备是否重载了 funca(), 如果重载了, 就调用底层的代码, 否则直接使用通用层的。 这样做的好处是, 核心层的代码可以处理绝大多数该类设备的funca()对应的功能,只有少数特殊设备需要重新实现 funca()。
copyreturn_type core_funca(xxx_device * bottom_dev, param1_type param1, param1_type param2)
{
/*通用的步骤代码 A */
...
bottom_dev->funca_ops1();
/*通用的步骤代码 B */
...
bottom_dev->funca_ops2();
/*通用的步骤代码 C */
...
bottom_dev->funca_ops3();
}
上述代码假定为了实现 funca(), 对于同类设备而言, 操作流程一致, 都要经过通用代码 A、 底层 ops1、通用代码 B、 底层 ops2、 通用代码 C、 底层 ops3这几步, 分层设计明显带来的好处是, 对于通用代码 A、B、 C, 具体的底层驱动不需要再实现, 而仅仅只关心其底层的操作ops1、 ops2、 ops3。
在 Linux 的分层化设计 ,input 子系统负责管理所有跟输入有关的驱动,包括键盘、鼠标、触摸等,最底层的就是设备原始驱动,负责获取输入设备的原始值,获取到的输入事件上报给 input 核心层。 input 核心层会处理各种 IO 模型,并且提供 file_operations 操作集合。分层的目的也是为了在不同的层处理不同的内容。
2.主机驱动和外设驱动分离思想在 Linux 设备驱动框架的设计中, 除了有分层设计实现以外, 还有分隔的思想。
假如现在有三个平台 A、 B 和 C,这三个平台上都有 MPU6050 ,I2C 接口的六轴传感器
每种平台下都有一个主机驱动和设备驱动,主机驱动是必须要的,不同的平台其 I2C 控制器不同。但是设备驱动没必要每个平台都写一个,因为不管对于不同的SOC ,MPU6050是一样,都是通过 I2C 接口读写数据,只需要一个 MPU6050 的驱动程序即可。
所以,每个平台的 I2C 控制器都提供一个统一的接口(也叫做主机驱动),每个设备的话也只提供一个驱动程序(设备驱动),每个设备通过统一的 I2C接口驱动来访问,这样就可以大大简化驱动文件
以上就是驱动的分隔,也就是将主机驱动和设备驱动分隔开来。比如 I2C、 SPI会采用驱动分隔的方式来简化驱动的开发。在实际的驱动开发中,一般 I2C 主机控制器驱动由半导体厂家编写好了,设备驱动一般由设备器件的厂家编写好了,开发人员只需要提供设备信息即可。
将设备信息从设备驱动中剥离开来,驱动使用标准方法去获取到设备信息 (比如从设备树中获 取到设备信息),根据获取到的设备信息来初始化设备。 驱动只负责驱动,设备只负责设备,总线法将两者进行匹配。这就是 Linux 中的总线(bus)、驱动(driver)和设备(device)模型,即驱动分离。
当向系统注册一个驱动的时,总线会在设备中查找与之匹配的设备,如果有,就将两者联系起来。同样的,当向系统中注册一个设备的时候,总线就会在驱动中查找与之匹配的设备,如果有,也联系起来。 Linux 内核中大量的驱动程序都采用总线、驱动和设备模式。
二、Platform 平台驱动模型在 Linux 2.6 以后的设备驱动模型中, 需关心总线、 设备和驱动这 3 个实体, 总线将设备和驱动绑定。在系统每注册一个设备的时候, 会寻找与之匹配的驱动;同样的,在系统每注册一个驱动的时候, 会寻找与之匹配的设备, 而匹配由总线完成。
Linux 设备和驱动通常都需要挂接在一种总线上, 对于本身依附于 PCI、 USB、 I2C、 SPI 等 的设备而言, 这自然不是问题。但是在嵌入式系统里面, 在 SoC 系统中集成的独立外设控制器、 挂接在 SoC内存空间的外设等却不依附于此类总线。 基于这一背景, Linux 发明了一种虚拟的总线, 称为 platform 总线, 相应的设备称为 platform_device, 驱动称为 platform_driver。
通过这种方式实现了此类设备和驱动的分离, 增强设备驱动的可移植性。平台总线模型也称为 platform 总线模型,是 Linux 内核虚拟出来的一条总线, 不是真实的导线。 平台总线模型就是把原来的驱动C文件给分成了俩个 C 文件,一个是 device.c, 一个是 driver.c 。把稳定不变的放在 driver.c 里面, 需要改变的就放在device.c 里面。
平台总线模型好处:
①可以提高代码的重用性 ②减少重复性代码
平台总线模型将设备代码和驱动代码分离, 将与硬件设备相关的都放到 device.c 文件里面,驱动部分代码放到 driver.c 文件里面。所以对于大量的同类设备而言,只需要修改设备文件信息,驱动文件不用修改,可以提高代码的重用性,减少重复性代码。
1.platform 设备在 platform 平台下用platform_device结构体表示platform设备, 如果内核支持设备树的话就不用使用 platform_device 来描述设备, 使用设备树去描述platform_device即可。
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;
};
name:设备名字,要和所使用的 platform 驱动的 name 字段相同,否则设备就无法匹配到对应的驱动。比如对应的 platform 驱动的name字段为xxx-gpio,则此name字段也要设置为xxx-gpio。
id :用来区分如果设备名字相同的时,通过在后面添加一个数字来代表不同的设备 dev:内置的device结构体
num_resources :资源数量,一般为resource 资源的大小(个数),ARRAY_SIZE 来测量一个数组的元素个数。
resource:指向一个资源结构体数组,即设备信息,比如外设寄存器等。 Linux 内核使用 resource结构体表示资源
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
start 和 end 分别表示资源的起始和终止信息,对于内存类的资源,表示内存起始和终止地址, name 表示资源名字, flags 表示资源类型,可选的资源类型定义在文件include/linux/ioport.h 里面
#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */
#define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
#define IORESOURCE_MEM 0x00000200
#define IORESOURCE_REG 0x00000300 /* Register offsets */
#define IORESOURCE_IRQ 0x00000400
#define IORESOURCE_DMA 0x00000800
#define IORESOURCE_BUS 0x00001000
......
/* PCI control bits. Shares IORESOURCE_BITS with above PCI ROM. */
#define IORESOURCE_PCI_FIXED (1driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
驱动和设备的匹配有四种方法。
①OF 类型的匹配 设备树采用的匹配方式,of_driver_match_device 函数定义在文件 include/linux/of_device.h 中。 device_driver 结构体(设备驱动)中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表,设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较,查看是否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后 probe 函数就会执行。
②ACPI 匹配方式
③id_table 匹配
每个 platform_driver 结构体有一个 id_table成员变量,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型
④名字匹配
如果第三种匹配方式的 id_table 不存在的话就直接比较驱动和设备的 name 字段,如果相等的话就匹配成功。
对于支持设备树的 Linux 版本号,一般设备驱动为了兼容性都支持设备树和无设备树两种匹配方式。即第一种匹配方式一般都会存在,第三种和第四种只要存在一种就可以,一般用的最多的还是第四种,直接比较驱动和设备的 name 字段。