<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, };
(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, };
3.platform匹配过程
- 匹配规则:内核通过比较
platform_device.name和platform_driver.driver.name是否一致来匹配设备和驱动。 - 流程:
- 设备注册:
platform_device_register(&led_device)。该函数是为了生成platform_device。使用DTS描述硬件信息不需要调用该函数,内核会自动生成platform_device。 - 驱动注册:
platform_driver_register(&led_driver)。 - 匹配成功:调用驱动的
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); // 自动生成初始化/退出函数
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),传入设备树节点的资源(如reg、interrupts)。
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");
dts:
// 示例:在arch/arm/boot/dts/your_board.dts中添加 my_gpio { compatible = "vendor,my-gpio"; // 必须与驱动中的compatible一致 gpio-num = <&gpio1 0>; // 使用GPIO1的第0位(具体GPIO编号根据硬件手册调整) status = "okay"; };
5.总线的作用
总线是Linux内核中用于抽象硬件连接关系的机制,其核心作用包括:
(1) 设备与驱动的自动匹配
- 问题:若没有总线,驱动需要显式调用设备初始化函数(如
init_device()),设备也需要显式注册驱动(如register_driver()),耦合性高。 - 总线解决方案:
- 设备通过
struct device注册到总线(如platform_bus、i2c_bus等)。 - 驱动通过
struct device_driver注册到总线。 - 内核通过匹配表(如
of_match_table)或名称匹配自动关联设备和驱动,调用驱动的probe函数。
- 设备通过
- 优势:驱动和设备无需知道对方的存在,内核自动完成绑定。
(2) 资源统一管理
- 问题:若没有总线,设备资源(如寄存器地址、中断号、时钟)需要手动管理,容易冲突或遗漏。
- 总线解决方案:
- 设备通过
struct resource描述资源,驱动通过标准接口(如platform_get_resource、devm_request_irq)获取资源。 - 总线负责资源分配和冲突检测(如检查中断号是否重复)。
- 设备通过
- 优势:避免资源冲突,提高代码可移植性。
(3) 支持热插拔和动态管理
- 问题:若没有总线,动态添加/移除设备需要手动处理驱动与设备的关联逻辑。
- 总线解决方案:
- 总线提供
add_device和remove_device接口,自动触发驱动的probe和remove函数。 - 支持设备树(Device Tree)或ACPI动态描述硬件。
- 总线提供
- 优势:支持嵌入式设备的热插拔或动态配置。
(4) 代码复用和模块化
- 问题:若没有总线,每个设备驱动需要重复实现硬件初始化、资源管理等逻辑。
- 总线解决方案:
- 总线驱动(如
platform_bus、i2c_bus)封装了通用逻辑(如资源管理、设备匹配)。 - 具体驱动只需关注设备特定的操作(如I2C传输、SPI读写)。
- 总线驱动(如
- 优势:减少重复代码,提高开发效率。
浙公网安备 33010602011771号