1. Linux 总线-设备-驱动模型概述
在嵌入式Linux开发中,理解总线-设备-驱动模型是掌握驱动开发的关键。这个模型是Linux内核为支持海量硬件设备而设计的核心架构,它通过将硬件描述与驱动实现分离,实现了极高的可扩展性。
1.1 模型产生的背景
在单片机裸机开发中,硬件和软件是紧密耦合的。比如要控制一个LED,开发者需要直接操作特定的寄存器:
c复制// 单片机控制LED的典型代码
#define LED_REG (*(volatile uint32_t *)0x40021000)
void led_on(void) {
LED_REG |= 0x01; // 直接操作特定地址的寄存器
}
这种方式的缺点非常明显:
- 硬件变更需要修改代码
- 无法支持动态设备插拔
- 代码难以在不同平台复用
1.2 模型的核心思想
Linux的总线-设备-驱动模型通过三个核心组件解决了这些问题:
- 总线(Bus):作为设备和驱动的中介,负责两者的匹配与管理
- 设备(Device):描述硬件特性(寄存器地址、中断号等)
- 驱动(Driver):实现硬件操作方法
这种架构带来了显著优势:
- 硬件描述与驱动逻辑分离
- 支持动态设备发现和驱动加载
- 同一驱动可支持多个相似设备
- 系统扩展性大幅提升
2. 总线(bus_type)深度解析
2.1 总线结构体详解
总线在内核中由struct bus_type表示,定义在include/linux/device.h中:
c复制struct bus_type {
const char *name; // 总线名称
int (*match)(struct device *dev, struct device_driver *drv); // 匹配函数
int (*uevent)(struct device *dev, struct kobj_uevent_env *env); // 热插拔事件
int (*probe)(struct device *dev); // 探测函数
struct subsys_private *p; // 私有数据(包含设备和驱动链表)
// ...其他成员省略...
};
2.1.1 总线名称(name)
总线名称决定了在sysfs中的展现形式。例如platform总线:
c复制struct bus_type platform_bus_type = {
.name = "platform",
// ...
};
注册后会在/sys/bus/下创建对应的目录:
bash复制/sys/bus/platform/
├── devices
├── drivers
└── uevent
2.1.2 匹配机制(match)
匹配函数是总线的核心,以platform总线为例,其匹配逻辑优先级为:
- 检查driver_override(调试用强制匹配)
- 设备树compatible匹配(嵌入式开发最常用)
- ACPI ID匹配(x86平台)
- ID表匹配(传统方式)
- 名称直接匹配(最后手段)
设备树匹配的典型用法:
dts复制// 设备树节点
leds {
compatible = "my,led";
// ...
};
c复制// 驱动中的匹配表
static const struct of_device_id led_of_match[] = {
{ .compatible = "my,led" },
{}
};
2.1.3 热插拔机制(uevent)
当设备状态变化时,内核通过uevent通知用户空间。例如插入USB设备时:
- 内核生成ADD事件
- udev收到事件
- 加载对应驱动模块
- 创建设备节点
开发者可以通过sysfs手动触发uevent:
bash复制echo add > /sys/devices/platform/my-device/uevent
2.2 总线运作流程
总线管理着两个关键链表:
- 设备链表(klist_devices)
- 驱动链表(klist_drivers)
当新设备注册时的匹配流程:
mermaid复制sequenceDiagram
participant Device
participant Bus
participant Driver
Device->>Bus: 注册设备
Bus->>Driver: 遍历驱动链表
loop 对每个驱动
Bus->>Bus: 调用match函数
alt 匹配成功
Bus->>Driver: 调用probe
Driver->>Device: 初始化设备
end
end
3. 设备(device)实现细节
3.1 设备结构体层次
Linux设备模型采用面向对象设计,基础结构体为struct device,各类设备通过包含基类实现继承:
c复制struct platform_device {
struct device dev; // 继承自device
// 平台设备特有成员
};
3.1.1 关键成员解析
parent:指向父设备,构建设备树状结构kobj:对应sysfs中的目录bus:所属总线类型driver:绑定的驱动指针of_node:关联的设备树节点
3.2 设备资源管理
设备资源通过struct resource描述:
c复制struct resource {
resource_size_t start; // 起始地址
resource_size_t end; // 结束地址
unsigned long flags; // 资源类型
// ...
};
资源类型包括:
- IORESOURCE_MEM:内存区域
- IORESOURCE_IRQ:中断号
- IORESOURCE_IO:I/O端口
获取资源的API:
c复制// 获取内存资源
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num);
// 获取中断号
int platform_get_irq(struct platform_device *dev, unsigned int num);
3.3 设备树与设备的关联
内核启动时解析设备树的过程:
- 扫描设备树二进制(.dtb)文件
- 为每个节点创建device_node结构体
- 根据节点类型创建platform_device
- 将of_node指针关联到device
设备树节点到sysfs的映射示例:
code复制/dts节点: /soc/i2c@4000000
sysfs路径: /sys/devices/platform/soc/4000000.i2c
4. 驱动(driver)实现机制
4.1 驱动结构体设计
驱动基础结构体struct device_driver:
c复制struct device_driver {
const char *name; // 驱动名称
struct bus_type *bus; // 所属总线
const struct of_device_id *of_match_table; // 匹配表
int (*probe)(struct device *dev); // 探测函数
int (*remove)(struct device *dev); // 移除函数
// ...
};
平台设备驱动通过struct platform_driver扩展:
c复制struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
struct device_driver driver;
// ...
};
4.2 驱动注册流程
典型驱动注册代码:
c复制static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my-device",
.of_match_table = my_of_match,
},
};
module_platform_driver(my_driver);
注册过程详解:
- 调用
platform_driver_register - 将驱动添加到总线驱动链表
- 总线遍历设备链表尝试匹配
- 匹配成功则调用probe
4.3 probe函数实现要点
一个完整的probe函数通常包含:
- 获取设备资源
- 申请IO内存
- 映射寄存器
- 初始化硬件
- 注册字符设备/网络设备等
示例代码:
c复制static int my_probe(struct platform_device *pdev)
{
// 1. 获取资源
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
// 2. 映射寄存器
void __iomem *regs = devm_ioremap_resource(&pdev->dev, res);
// 3. 获取中断
int irq = platform_get_irq(pdev, 0);
// 4. 初始化硬件
writew(0x55AA, regs + CONTROL_REG);
// 5. 注册设备
misc_register(&my_miscdev);
return 0;
}
5. 实际开发中的经验技巧
5.1 设备树匹配最佳实践
-
兼容性字符串格式:
code复制"厂商,型号" 例如:"ti,omap2-i2c" -
支持多兼容字符串:
dts复制compatible = "ti,am3352-i2c", "ti,omap4-i2c";驱动会按顺序尝试匹配
-
添加特定属性:
dts复制my_device { compatible = "my,device"; clock-frequency = <100000>; my-custom-param = "value"; };
5.2 资源管理API选择
-
传统资源获取:
c复制res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = ioremap(res->start, resource_size(res)); -
推荐使用devm_系列:
c复制
base = devm_ioremap_resource(&pdev->dev, res);优点:自动管理生命周期,防止资源泄漏
-
中断处理:
c复制
ret = devm_request_irq(&pdev->dev, irq, handler, flags, name, dev);
5.3 调试技巧
-
查看设备树匹配:
bash复制cat /proc/device-tree/soc/i2c@4000000/compatible -
检查驱动绑定状态:
bash复制ls -l /sys/bus/platform/devices/my-device/driver -
手动触发probe:
bash复制echo -n "my-device" > /sys/bus/platform/drivers/my-driver/bind -
查看资源信息:
bash复制cat /proc/iomem cat /proc/interrupts
5.4 常见问题排查
-
驱动未加载:
- 检查
dmesg | grep driver_name - 确认
.ko文件在/lib/modules/uname -r/
- 检查
-
匹配失败:
- 检查
compatible属性是否一致 - 确认驱动
of_match_table正确
- 检查
-
Probe失败:
- 检查资源获取是否成功
- 验证寄存器访问权限
- 确认中断号是否正确
-
设备未创建:
- 检查设备树是否被正确编译和加载
- 确认内核配置启用了设备树支持
6. 模型的实际应用案例
6.1 GPIO驱动实现
设备树节点:
dts复制leds {
compatible = "gpio-leds";
led0 {
label = "system:red:status";
gpios = <&gpio0 15 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
};
};
驱动匹配表:
c复制static const struct of_device_id gpio_led_dt_ids[] = {
{ .compatible = "gpio-leds" },
{}
};
6.2 I2C设备驱动
设备树配置:
dts复制&i2c1 {
status = "okay";
clock-frequency = <100000>;
temperature-sensor@48 {
compatible = "ti,tmp75";
reg = <0x48>;
};
};
驱动注册:
c复制static struct i2c_driver tmp75_driver = {
.driver = {
.name = "tmp75",
.of_match_table = tmp75_of_match,
},
.probe = tmp75_probe,
.remove = tmp75_remove,
.id_table = tmp75_id,
};
6.3 平台设备驱动完整示例
设备树:
dts复制my_device {
compatible = "my,custom-device";
reg = <0x10000000 0x1000>;
interrupts = <0 45 IRQ_TYPE_LEVEL_HIGH>;
status = "okay";
};
驱动代码:
c复制static int my_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *regs;
int irq, ret;
// 获取内存资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "no memory resource\n");
return -ENODEV;
}
// 映射寄存器
regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(regs))
return PTR_ERR(regs);
// 获取中断
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
// 注册中断处理
ret = devm_request_irq(&pdev->dev, irq, my_interrupt,
IRQF_SHARED, dev_name(&pdev->dev), pdev);
if (ret) {
dev_err(&pdev->dev, "cannot claim IRQ\n");
return ret;
}
// 初始化硬件
writew(0x1234, regs + CONTROL_REG);
dev_info(&pdev->dev, "device probed successfully\n");
return 0;
}
static const struct of_device_id my_of_match[] = {
{ .compatible = "my,custom-device" },
{},
};
MODULE_DEVICE_TABLE(of, my_of_match);
static struct platform_driver my_driver = {
.driver = {
.name = "my-device",
.of_match_table = my_of_match,
},
.probe = my_probe,
.remove = my_remove,
};
module_platform_driver(my_driver);
7. 高级主题与扩展
7.1 多设备支持
一个驱动可以支持多个设备实例,关键点:
-
在probe中分配设备特定数据:
c复制struct my_data { void __iomem *regs; int irq; // 其他设备特定数据 }; static int my_probe(struct platform_device *pdev) { struct my_data *data; data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; platform_set_drvdata(pdev, data); // ... } -
在remove中清理:
c复制static int my_remove(struct platform_device *pdev) { struct my_data *data = platform_get_drvdata(pdev); // 清理资源 return 0; }
7.2 电源管理集成
实现基本的电源管理回调:
c复制static int my_suspend(struct device *dev)
{
struct my_data *data = dev_get_drvdata(dev);
// 保存状态并进入低功耗模式
return 0;
}
static int my_resume(struct device *dev)
{
struct my_data *data = dev_get_drvdata(dev);
// 恢复状态
return 0;
}
static const struct dev_pm_ops my_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(my_suspend, my_resume)
};
static struct platform_driver my_driver = {
.driver = {
.pm = &my_pm_ops,
// ...
},
// ...
};
7.3 用户空间接口
创建sysfs属性文件:
c复制static ssize_t value_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct my_data *data = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", readl(data->regs + STATUS_REG));
}
static ssize_t value_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct my_data *data = dev_get_drvdata(dev);
u32 val;
if (kstrtou32(buf, 0, &val))
return -EINVAL;
writel(val, data->regs + CONTROL_REG);
return count;
}
static DEVICE_ATTR_RW(value);
static int my_probe(struct platform_device *pdev)
{
// ...
device_create_file(&pdev->dev, &dev_attr_value);
// ...
}
8. 性能优化与最佳实践
8.1 延迟初始化
对于非关键资源,可以使用延迟工作队列:
c复制static void delayed_init(struct work_struct *work)
{
// 初始化代码
}
static int my_probe(struct platform_device *pdev)
{
INIT_DELAYED_WORK(&data->work, delayed_init);
schedule_delayed_work(&data->work, msecs_to_jiffies(1000));
// ...
}
8.2 中断处理优化
-
使用线程化中断减少关中断时间:
c复制irq = platform_get_irq(pdev, 0); ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, my_threaded_irq, IRQF_ONESHOT, dev_name(&pdev->dev), pdev); -
中断共享处理:
c复制
ret = devm_request_irq(&pdev->dev, irq, my_interrupt, IRQF_SHARED, dev_name(&pdev->dev), pdev);
8.3 资源管理最佳实践
-
优先使用devm_系列API:
c复制data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); regs = devm_ioremap_resource(&pdev->dev, res); irq = devm_request_irq(&pdev->dev, irq, handler, flags, name, dev); -
合理使用DMA API:
c复制buf = dma_alloc_coherent(&pdev->dev, size, &dma_handle, GFP_KERNEL); // 使用DMA缓冲区 dma_free_coherent(&pdev->dev, size, buf, dma_handle); -
电源域管理:
c复制data->vdd = devm_regulator_get(&pdev->dev, "vdd"); regulator_enable(data->vdd); // 使用设备 regulator_disable(data->vdd);
9. 调试与问题排查实战
9.1 设备树调试技巧
-
检查设备树是否被正确解析:
bash复制ls /proc/device-tree/ -
查看特定节点属性:
bash复制
hexdump -C /proc/device-tree/soc/i2c@4000000/reg -
修改设备树后重新加载:
bash复制make dtbs cp arch/arm/boot/dts/myboard.dtb /boot/ reboot
9.2 驱动调试方法
-
使用动态调试:
c复制#define dev_dbg(dev, fmt, ...) \ dynamic_dev_dbg(dev, fmt, ##__VA_ARGS__) // 内核命令行添加 dyndbg="file drivers/mydriver/* +p" -
调试sysfs接口:
bash复制tree /sys/devices/platform/my-device/ cat /sys/devices/platform/my-device/registers -
使用ftrace跟踪函数调用:
bash复制echo function > /sys/kernel/debug/tracing/current_tracer echo my_probe > /sys/kernel/debug/tracing/set_ftrace_filter cat /sys/kernel/debug/tracing/trace_pipe
9.3 常见问题解决方案
-
驱动probe未被调用:
- 检查
compatible匹配 - 确认设备状态为
okay - 查看
/sys/bus/platform/devices/下设备是否存在
- 检查
-
资源获取失败:
- 检查设备树
reg和interrupts属性 - 确认资源索引号正确
- 验证父节点的
ranges属性
- 检查设备树
-
硬件无响应:
- 检查时钟和电源是否使能
- 验证物理连接
- 使用逻辑分析仪检查信号
-
内核崩溃:
- 检查指针是否为NULL
- 验证资源是否已映射
- 确认中断处理是否正确注销
10. 模型演进与未来趋势
10.1 设备树的演进
-
从C代码到设备树:
- 旧方式:
platform_device_register() - 新方式:设备树描述
- 旧方式:
-
设备树覆盖(Overlay):
bash复制
fdtoverlay -i base.dtb -o output.dtb overlay.dtbo -
动态设备树修改:
bash复制echo "fragment@0 { target-path = \"/\"; __overlay__ { newnode { ... }; }; };" > overlay.dts dtc -@ -I dts -O dtb -o overlay.dtbo overlay.dts
10.2 新驱动模型特性
-
统一设备属性接口:
c复制device_property_read_u32(dev, "clock-frequency", &clk); -
通用时钟框架:
c复制data->clk = devm_clk_get(dev, NULL); clk_prepare_enable(data->clk); -
复位控制支持:
c复制data->reset = devm_reset_control_get(dev, NULL); reset_control_deassert(data->reset);
10.3 异构系统支持
-
多总线设备支持:
c复制struct mfd_cell cells[] = { { .name = "my-i2c-device", .of_compatible = "my,i2c-device" }, { .name = "my-spi-device", .of_compatible = "my,spi-device" }, }; -
复杂IOMMU集成:
c复制
data->domain = iommu_domain_alloc(bus); iommu_attach_device(data->domain, dev); -
安全域隔离:
dts复制my_device { compatible = "my,secure-device"; iommus = <&smmu 0>; dma-coherent; };
通过深入理解Linux总线-设备-驱动模型,开发者可以更好地设计可维护、可扩展的驱动代码,适应不断变化的硬件环境。这个模型虽然增加了初期的学习成本,但为长期的项目维护和跨平台支持带来了巨大优势。