驱动进化之路

1. 面向对象分离硬件资源驱动思想

背景:
在早期的嵌入设备和计算机系统中,硬件驱动程序通常是直接和硬件紧密耦合的。这种方式导致了驱动程序难以维护和扩展,因为每种新的硬件都可能需要全新的驱动代码。

思想:

  • 模块化:将硬件相关的代码从系统的其他部分中分离出来,形成独立的模块(驱动模块)。
  • 抽象层:引入硬件抽象层(HAL),使得上层软件可以不直接操作硬件,而是通过一层抽象的接口来进行。
  • 封装:使用面向对象的编程思想,将硬件的操作封装成类或对象,这样可以更容易管理和重用代码。

技术细节:

  • 使用类的继承和多态来实现不同硬件的抽象和具体实现。
  • 提供统一的接口来访问硬件资源,降低了对具体硬件的依赖。

例子:假设有一个LED连接到一个GPIO引脚上。

  • 模块化:创建一个独立的LED驱动模块,而不是将LED操作代码直接嵌入到应用层代码中。
  • 抽象层:定义一个抽象的LED类,包含打开、关闭等方法。
// LED抽象类
class LED {
public:
virtual void turnOn() = 0;
virtual void turnOff() = 0;
};

// GPIO LED 实现
class GPIO_LED : public LED {
private:
int pin;

public:
GPIO_LED(int gpioPin) : pin(gpioPin) {}

void turnOn() override {
// GPIO设置为高电平
}

void turnOff() override {
// GPIO设置为低电平
}
};

2. Platform Device 平台总线模型

随着硬件设备的复杂性增加,需要一种更系统化的方式来管理设备驱动和硬件资源。平台设备总线(Platform Bus)将所有硬件设备抽象为平台设备(Platform Devices)和平台驱动(Platform Drivers)。这种抽象使得驱动开发者不必关心具体的硬件细节,而是通过一套标准的接口来管理设备。

通过这种模型,驱动开发者可以集中精力在设备的具体操作上,而不需要处理复杂的总线协议或硬件初始化流程。平台总线为驱动提供了标准的生命周期管理和资源访问方式。

思想:

  • 平台设备:将硬件设备抽象为平台设备(Platform Device),这些设备通过平台总线(Platform Bus)连接到系统中。
  • 平台驱动:相应地,驱动程序也被抽象为平台驱动(Platform Driver),它们通过平台总线匹配到相应的设备。

技术细节:

  • 在Linux内核中,struct platform_device 和 struct platform_driver 是关键数据结构。
  • 设备和驱动通过设备树或者是硬编码的方式注册到平台总线上。
  • 使用总线驱动框架(如I2C、SPI等)来进一步抽象硬件接口。
// 定义平台设备的结构
struct platform_device {
// 设备的名称,用于匹配驱动
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
// 指向资源数组的指针,资源包括内存区域、IRQ等
struct resource *resource;
/* 其他字段省略 */
};

// 定义平台驱动的结构
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;
/* 其他字段省略 */
};

例子:同上,LED连接到GPIO。

  • 平台设备:定义一个平台设备来代表这个LED。
static struct platform_device led_device = {
.name = "led",
.id = -1,
.dev = {
.platform_data = &gpio_pin_number, // 假设这个GPIO引脚号作为平台数据
},
};
  • 平台驱动:编写一个平台驱动来操作这个LED。
static struct platform_driver led_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "led",
.owner = THIS_MODULE,
},
};

static int led_probe(struct platform_device *pdev)
{
// 获取设备的GPIO引脚信息并初始化LED
return 0;
}

static int led_remove(struct platform_device *pdev)
{
// 清理LED相关的资源
return 0;
}

技术细节:

  • 通过平台设备和平台驱动,系统可以自动匹配设备和驱动。led_probe 函数在匹配成功后初始化LED,led_remove 函数在设备卸载时清理资源。

匹配过程

平台设备与驱动的匹配过程大致如下:

  1. 设备注册:

    • 当内核启动或模块加载时,平台设备(struct platform_device)被注册到平台总线(platform_bus)上。
  2. 驱动注册:

    • 平台驱动(struct platform_driver)也需要注册到平台总线上。
  3. 设备树解析:

    • 如果使用设备树,内核会解析设备树文件(.dts或.dtb),从中获取设备信息,并创建相应的平台设备实例。
  4. 匹配尝试:

    • 内核尝试将注册的驱动与平台总线上的设备进行匹配。
    • 匹配过程主要遵循以下顺序:
      • 设备树匹配:优先尝试使用设备树匹配规则,检查设备的compatible属性与驱动的of_match_table是否匹配。
      • ID匹配:如果设备树匹配失败,尝试使用设备ID匹配。
      • 名称匹配:最后,尝试使用驱动和设备的名称匹配。
  5. 成功匹配:

    • 一旦找到匹配的设备和驱动,内核会调用驱动的probe方法。probe方法将接收到匹配的struct device(实际上是一个struct platform_device),驱动可以在这个方法中初始化设备。
  6. 资源分配:

    • 在probe方法中,驱动可以访问设备的资源(如IO内存、IRQ等),这些资源通常是通过设备树或平台设备注册时提供的。
  7. 设备绑定:

    • 如果probe方法成功,设备将绑定到驱动,设备现在可以正常工作。
  8. 失败处理:

    • 如果匹配失败或probe方法失败,内核会记录错误信息,驱动将不会绑定到设

3. 设备树(Device Tree)

背景:
随着SoC(System on Chip)的发展,硬件配置变得极为复杂和多样化,硬编码的方式变得难以维护。

思想:

  • 解耦硬件描述:将硬件的描述从内核代码中分离出来,形成独立的设备树描述文件(.dts, .dtsi)。
  • 动态配置:设备树提供了一种动态的、可配置的方式来描述系统硬件。

技术细节:

  • 设备树使用一种类似JSON的格式来描述硬件结构,包括节点(nodes)表示设备,属性(properties)表示设备的特性。
  • 内核中的设备树编译器(dtc)将设备树源文件编译成二进制格式(DTB),内核启动时加载DTB来配置硬件。
  • 驱动程序通过设备树中的信息来获取设备的地址、IRQ等信息,实现即插即用。

例子:同上,一个LED连接到GPIO。

  • 设备树描述:

    / {
    leds {
    compatible = "gpio-led";
    gpios = <&gpio 17 GPIO_ACTIVE_HIGH>; // 假设LED连接到GPIO17
    label = "led";
    };
    };
  • 驱动代码:现在驱动可以从设备树中获取LED的配置信息。

static int led_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
int gpio;

if (of_property_read_u32(node, "gpios", &gpio))
return -ENODEV;

// 使用GPIO进行LED操作
return 0;
}

设备树描述了硬件配置,驱动通过解析设备树节点来获取硬件信息(如GPIO引脚号)。这种方式使得硬件配置与内核代码解耦,硬件变化只需修改设备树文件即可。

进化意义:

  • 从面向对象到平台总线模型,再到设备树的演变过程中,硬件资源的管理变得越来越抽象和灵活。每个阶段都使得驱动开发和维护更加高效,减少了对具体硬件的依赖,增强了系统的可移植性和可维护性。通过LED点灯这个简单的例子,我们可以看到这种进化带来的实际好处:从最初的硬编码,到设备树的动态配置,使得系统对硬件变化的适应性大大增强。
  • 从面向对象到平台总线模型,再到设备树,硬件资源的管理和配置变得越来越抽象和通用,减少了对具体硬件的依赖,提升了系统的可移植性和可维护性。
  • 设备树的引入大大简化了内核的硬件描述方式,使得硬件支持的添加和修改变得更加灵活和高效。

这个过程展示了操作系统在面对越来越复杂的硬件环境时,不断优化和抽象硬件管理方法的演进过程。