<Linux 驱动基础架构>

1.platform总线概念

  在嵌入式系统中,许多外设(如I2C、RTC、SPI控制器等)并不直接挂载在PCI、USB等物理总线上,而是通过内存映射或寄存器直接与CPU交互。

  Platform机制是一种用于管理非总线型设备(如SoC内部集成的外设)的驱动模型,它通过虚拟的platform总线将设备(platform_device)和驱动(platform_driver)关联起来,简化了驱动开发流程。

 

2.platform机制核心组件

(1) platform_device

  • 作用:描述设备的硬件资源(如寄存器地址、中断号等)。
  • 关键字段:
    • name:设备名称,用于与驱动匹配。
    • id:设备ID(若只有一个设备,通常为-1)。
    • resource:指向设备资源的指针(如内存、IRQ等)。
  • 示例:
static struct resource led_resources[] = {
    [0] = {
        .start = 0x10000000,  // 寄存器起始地址
        .end   = 0x100000FF,  // 寄存器结束地址
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = 35,         // 中断号
        .end   = 35,
        .flags = IORESOURCE_IRQ,
    },
};

static struct platform_device led_device = {
    .name           = "led-device",
    .id             = -1,
    .num_resources  = ARRAY_SIZE(led_resources),
    .resource       = led_resources,
};
View Code

(2) platform_driver

  • 作用:实现设备的驱动逻辑(如初始化、操作函数等)。
  • 关键字段:
    • probe:设备与驱动匹配成功后调用,用于初始化设备。
    • remove:设备卸载时调用。
    • driver:嵌套的device_driver结构体,包含名称和匹配函数。
  • 示例:
static int led_probe(struct platform_device *pdev) {
    // 初始化设备(如映射寄存器、申请中断等)
    printk(KERN_INFO "LED device probed\n");
    return 0;
}

static struct platform_driver led_driver = {
    .driver = {
        .name   = "led-device",  // 必须与platform_device的name一致
        .owner  = THIS_MODULE,
    },
    .probe  = led_probe,
    .remove = led_remove,
};
View Code

 

3.platform匹配过程

  • 匹配规则:内核通过比较platform_device.nameplatform_driver.driver.name是否一致来匹配设备和驱动。
  • 流程:
    1. 设备注册:platform_device_register(&led_device)。该函数是为了生成platform_device。使用DTS描述硬件信息不需要调用该函数,内核会自动生成platform_device。
    2. 驱动注册:platform_driver_register(&led_driver)
    3. 匹配成功:调用驱动的probe函数。

其中platform_driver_register()有显式注册和宏辅助注册:

显式注册:
static int __init led_driver_init(void) {
    return platform_driver_register(&led_driver);
}
module_init(led_driver_init);

宏辅助注册:
module_platform_driver(led_driver);  // 自动生成初始化/退出函数
View Code

 

4.设备树与platform

  现代Linux内核(如4.x及以上)通常结合设备树使用Platform机制。

设备树节点:

led {
    compatible = "led-device";  // 必须与驱动的compatible属性一致
    reg = <0x10000000 0x100>;   // 寄存器地址和长度
    interrupts = <35>;          // 中断号
};

驱动匹配:驱动通过of_match_table指定兼容的设备树节点:

static const struct of_device_id led_of_match[] = {
    { .compatible = "led-device" },
    { /* sentinel */ }
};

static struct platform_driver led_driver = {
    .driver = {
        .name   = "led-device",
        .of_match_table = led_of_match,  // 支持设备树匹配
    },
    .probe  = led_probe,
};

of_match_table的作用:

  • 功能:它是一个 struct of_device_id 数组,用于匹配设备树节点中的 compatible 属性。
  • 匹配规则:当内核启动时,设备树解析器会遍历所有已注册的驱动,比较驱动的 of_match_table 与设备树节点的 compatible 字符串。如果匹配成功,则调用该驱动的 probe 函数。
  • 位置:它是 platform_driver 结构体的一个成员(通过 .driver.of_match_table 访问)。

内核匹配过程:

  • 内核解析设备树时,发现 compatible = "led-device" 的节点。
  • 遍历所有 platform_driver,找到 of_match_table 包含 "led-device" 的驱动(即 led_driver)。
  • 调用该驱动的 probe 函数(led_probe),传入设备树节点的资源(如 reginterrupts)。

 

GPIO demo:

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>

#define DRIVER_NAME "my_gpio"

static int my_gpio_probe(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;
    int gpio_num;

    // 从设备树获取GPIO编号
    gpio_num = of_get_named_gpio(np, "gpio-num", 0);
    if (gpio_num < 0) {
        dev_err(&pdev->dev, "Failed to get GPIO number\n");
        return gpio_num;
    }

    // 申请GPIO并设置初始方向(输出)
    if (gpio_request(gpio_num, "my_gpio") < 0) {
        dev_err(&pdev->dev, "Failed to request GPIO\n");
        return -EBUSY;
    }

    if (gpio_direction_output(gpio_num, 0) < 0) {
        dev_err(&pdev->dev, "Failed to set GPIO direction\n");
        gpio_free(gpio_num);
        return -EINVAL;
    }

    dev_info(&pdev->dev, "GPIO %d initialized (output)\n", gpio_num);
    return 0;
}

static int my_gpio_remove(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;
    int gpio_num = of_get_named_gpio(np, "gpio-num", 0);

    if (gpio_num >= 0) {
        gpio_free(gpio_num);
        dev_info(&pdev->dev, "GPIO %d released\n", gpio_num);
    }
    return 0;
}

static const struct of_device_id my_gpio_of_match[] = {
    { .compatible = "vendor,my-gpio" }, // 必须与设备树中的compatible一致
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_gpio_of_match);

static struct platform_driver my_gpio_driver = {
    .driver = {
        .name = DRIVER_NAME,
        .of_match_table = my_gpio_of_match,
    },
    .probe = my_gpio_probe,
    .remove = my_gpio_remove,
};

module_platform_driver(my_gpio_driver);

MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("GPIO Control Driver");
MODULE_LICENSE("GPL");
View Code

dts:

// 示例:在arch/arm/boot/dts/your_board.dts中添加
my_gpio {
    compatible = "vendor,my-gpio"; // 必须与驱动中的compatible一致
    gpio-num = <&gpio1 0>;         // 使用GPIO1的第0位(具体GPIO编号根据硬件手册调整)
    status = "okay";
};
View Code

 

 

5.总线的作用

  总线是Linux内核中用于抽象硬件连接关系的机制,其核心作用包括:

(1) 设备与驱动的自动匹配

  • 问题:若没有总线,驱动需要显式调用设备初始化函数(如init_device()),设备也需要显式注册驱动(如register_driver()),耦合性高。
  • 总线解决方案:
    • 设备通过struct device注册到总线(如platform_busi2c_bus等)。
    • 驱动通过struct device_driver注册到总线。
    • 内核通过匹配表(如of_match_table)或名称匹配自动关联设备和驱动,调用驱动的probe函数。
  • 优势:驱动和设备无需知道对方的存在,内核自动完成绑定。

(2) 资源统一管理

  • 问题:若没有总线,设备资源(如寄存器地址、中断号、时钟)需要手动管理,容易冲突或遗漏。
  • 总线解决方案:
    • 设备通过struct resource描述资源,驱动通过标准接口(如platform_get_resourcedevm_request_irq)获取资源。
    • 总线负责资源分配和冲突检测(如检查中断号是否重复)。
  • 优势:避免资源冲突,提高代码可移植性。

(3) 支持热插拔和动态管理

  • 问题:若没有总线,动态添加/移除设备需要手动处理驱动与设备的关联逻辑。
  • 总线解决方案:
    • 总线提供add_deviceremove_device接口,自动触发驱动的proberemove函数。
    • 支持设备树(Device Tree)或ACPI动态描述硬件。
  • 优势:支持嵌入式设备的热插拔或动态配置。

(4) 代码复用和模块化

  • 问题:若没有总线,每个设备驱动需要重复实现硬件初始化、资源管理等逻辑。
  • 总线解决方案:
    • 总线驱动(如platform_busi2c_bus)封装了通用逻辑(如资源管理、设备匹配)。
    • 具体驱动只需关注设备特定的操作(如I2C传输、SPI读写)。
  • 优势:减少重复代码,提高开发效率。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

posted @ 2025-06-22 16:58  一个不知道干嘛的小萌新  阅读(91)  评论(0)    收藏  举报