1. 平台设备驱动概述
在嵌入式Linux开发中,平台设备驱动(Platform Driver)是管理片上系统(SoC)内置外设的核心框架。不同于PCI、USB等标准总线设备,这些SoC内置外设(如GPIO、UART、I2C控制器等)通常没有标准的枚举机制,而是通过内存映射I/O(MMIO)直接访问。
为什么需要专门的平台设备驱动框架?主要基于以下技术考量:
- 资源固定性:SoC外设的寄存器地址、中断号等资源在芯片设计阶段就已确定,无法像PCI设备那样动态分配
- 无标准枚举:缺乏像PCI配置空间那样的标准发现机制
- 统一管理需求:需要为这类设备提供一致的设备模型和电源管理
典型应用场景包括:
- 嵌入式处理器(如RK3588、i.MX6系列)的各种内置控制器
- 专用加速器(如视频编解码模块)
- 系统级外设(时钟控制器、中断控制器等)
2. 平台总线机制解析
2.1 平台总线的本质
platform_bus_type是Linux内核中定义的虚拟总线,其核心作用是:
c复制struct bus_type platform_bus_type = {
.name = "platform",
.match = platform_match, // 关键匹配函数
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
与物理总线的关键区别:
- 无硬件探测:依赖软件显式注册设备和驱动
- 匹配逻辑可定制:通过platform_match()实现多种匹配方式
- 资源管理集中化:统一处理内存、中断等资源分配
2.2 总线初始化流程
平台总线的初始化发生在内核启动早期:
- 调用platform_bus_init()注册总线类型
- 创建设备节点/platform(通过device_register)
- 建立资源管理基础设施
关键代码路径:
bash复制start_kernel
→ rest_init
→ kernel_init
→ do_basic_setup
→ driver_init
→ platform_bus_init
3. 平台设备实现细节
3.1 设备数据结构
platform_device结构体包含关键信息:
c复制struct platform_device {
const char *name; // 必须与驱动匹配
int id; // 实例编号(同类型多个设备时)
struct resource *resource; // 资源数组
struct device dev; // 内嵌的通用设备结构
};
资源定义示例(UART设备):
c复制static struct resource uart_resources[] = {
[0] = {
.start = 0x101F1000, // 寄存器基地址
.end = 0x101F1FFF,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = 37, // 中断号
.flags = IORESOURCE_IRQ,
}
};
3.2 设备注册方式
静态注册(推荐):
- 定义platform_device结构体
- 调用platform_device_register()
- 通常在板级初始化代码中完成
动态注册:
c复制struct platform_device *pdev;
pdev = platform_device_alloc("my-device", -1);
platform_device_add_resources(pdev, res, ARRAY_SIZE(res));
platform_device_add(pdev);
4. 平台驱动开发实践
4.1 驱动结构设计
典型平台驱动包含:
c复制static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my-device",
.of_match_table = my_of_match,
},
};
4.2 关键函数实现
probe函数要点:
c复制static int my_probe(struct platform_device *pdev)
{
// 1. 获取资源
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
void __iomem *base = devm_ioremap_resource(&pdev->dev, res);
// 2. 获取中断
int irq = platform_get_irq(pdev, 0);
// 3. 初始化硬件
writew(0x00, base + REG_CTRL);
// 4. 注册字符设备等操作
cdev_init(&my_cdev, &my_fops);
devm_cdev_add(&pdev->dev, &my_cdev, devno);
return 0;
}
remove函数注意事项:
- 必须释放所有分配的资源
- 使用devm_系列函数可自动管理资源
- 处理可能的电源状态变更
5. 设备与驱动匹配机制
5.1 匹配优先级
platform_match()按以下顺序尝试匹配:
- 设备树兼容性:of_driver_match_device()
- ACPI ID匹配:acpi_driver_match_device()
- ID表匹配:platform_match_id()
- 名称匹配:strcmp(pdev->name, drv->name)
5.2 设备树匹配示例
驱动侧:
c复制static const struct of_device_id my_of_match[] = {
{ .compatible = "vendor,my-device" },
{},
};
MODULE_DEVICE_TABLE(of, my_of_match);
设备树节点:
dts复制my_device: my-device@101F1000 {
compatible = "vendor,my-device";
reg = <0x101F1000 0x1000>;
interrupts = <37 IRQ_TYPE_LEVEL_HIGH>;
};
6. 资源管理高级技巧
6.1 资源获取最佳实践
内存资源:
c复制// 自动管理版本(推荐)
void __iomem *base = devm_platform_ioremap_resource(pdev, 0);
// 传统方式
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = ioremap(res->start, resource_size(res));
中断处理:
c复制irq = platform_get_irq(pdev, 0);
ret = devm_request_irq(&pdev->dev, irq, my_isr,
IRQF_TRIGGER_HIGH, "my-device", priv);
6.2 时钟管理
c复制// 获取时钟
struct clk *clk = devm_clk_get(&pdev->dev, "core");
// 配置时钟
clk_set_rate(clk, 50000000);
// 使能时钟
clk_prepare_enable(clk);
7. 完整驱动示例分析
7.1 简单GPIO控制器驱动
c复制#include <linux/platform_device.h>
#include <linux/module.h>
static int gpio_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *base;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
// 初始化GPIO方向寄存器
iowrite32(0xFFFF0000, base + GPIO_DIR);
dev_info(&pdev->dev, "GPIO controller initialized\n");
return 0;
}
static struct platform_driver gpio_driver = {
.driver = {
.name = "simple-gpio",
.of_match_table = of_match_ptr(simple_gpio_of_match),
},
.probe = gpio_probe,
};
module_platform_driver(gpio_driver);
7.2 设备树绑定
dts复制gpio_controller: gpio@10000000 {
compatible = "simple-gpio";
reg = <0x10000000 0x1000>;
#gpio-cells = <2>;
gpio-controller;
};
8. 调试与问题排查
8.1 常见问题
-
匹配失败:
- 检查/sys/bus/platform/devices和drivers目录
- 确认名称或compatible字符串完全一致
- 使用dev_dbg()输出匹配过程
-
资源获取失败:
- 检查设备树reg/interrupts属性
- 确认资源索引号正确
- 使用devres API自动管理资源
-
probe函数未执行:
- 检查模块依赖关系
- 确认没有deferred probe情况
- 查看dmesg | grep probe输出
8.2 调试技巧
-
sysfs接口:
bash复制ls /sys/bus/platform/devices # 查看已注册设备 cat /sys/kernel/debug/platform/devices # 详细设备信息 -
动态日志:
c复制dev_dbg(&pdev->dev, "Register value: 0x%08x\n", readl(base + REG_STAT)); -
延迟探测处理:
c复制if (IS_ERR(clk)) { if (PTR_ERR(clk) == -EPROBE_DEFER) return -EPROBE_DEFER; // 其他错误处理 }
在实际开发中,平台设备驱动框架的灵活运用需要结合具体硬件特性和内核版本差异。建议在实现新驱动时,参考内核源码中同类型驱动的实现方式(如drivers/tty/serial/下各种UART驱动),可以避免很多常见陷阱。
