当前位置: 首页 > news >正文

【Linux驱动开发】Linux 设备驱动中的总线机制

Linux 设备驱动中的总线机制

本文面向有一定内核基础的开发者,从架构、类型、核心数据结构到实战与性能优化,系统性解析 Linux 设备驱动的总线(bus)机制。内容以内核 4.4.94 为基线,标注与 5.x/6.x 版本的差异,并附带代码片段与文档引用。


1. Linux 总线架构概述

总线在设备驱动模型中的作用

  • 统一抽象设备与驱动的“连接通道”,负责设备枚举、匹配与生命周期回调(probe/remove/shutdown)。
  • 将不同物理/逻辑链路(PCI、USB、I2C、SPI、平台设备)纳入 driver core,以一致的方式出现在 sysfs(如 /sys/bus/*)。
  • 为热插拔与用户态集成提供事件接口(ueventudev),以及内核侧的通知机制(bus notifiers)。

总线、设备和驱动三者的关系

  • struct device 通过 bus_type 归属某条总线;struct device_driver 也声明其所属总线,两者由总线的 match 规则决定是否绑定。
  • 绑定成功后,调用驱动的 probe 完成资源获取与初始化,解绑时调用 remove 释放资源。
  • 在设备树/ACPI/Firmware 等提供的硬件描述基础上,设备对象被创建并挂接到对应总线再参与匹配。

内核中总线子系统的组织结构

  • 核心位于 driver core:drivers/base/bus.c 提供总线注册/注销、sysfs 集成与匹配流程(参考 drivers/base/bus.c:888)。
  • struct bus_type 描述总线能力,位于 include/linux/device.h:105;注册后在 /sys/bus/<name>/ 形成 kobject/kset 树。
  • 设备与驱动对象分别通过 klist 维护;notifier 接口用于跨子系统监听设备/驱动事件。

引用:

  • 代码:include/linux/device.h:105drivers/base/bus.c:888drivers/base/bus.c:1194
  • 文档:Documentation/driver-model/overview.txtDocumentation/driver-model/bus.txtDocumentation/driver-model/device.txtDocumentation/driver-model/driver.txt

2. 总线类型详解

PCI/PCIe 总线

  • 工作原理:配置空间(Type 0/1)、BAR 寄存器映射、能力结构(MSI/MSI-X、PCIe Cap)、链路层负责枚举与热插拔。
  • 驱动实现:通过 struct pci_driver 注册,内核枚举设备后调用 probe;使用 pci_enable_device()pci_request_regions()pci_iomap() 完成资源映射。
  • 中断:优先使用 MSI/MSI-X(更少共享、可配置亲和),API 如 pci_enable_msi()pci_enable_msix_range()

代码示例(简化版):

#include <linux/pci.h>static int demo_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{int err;err = pci_enable_device(pdev);if (err)return err;err = pci_request_regions(pdev, "demo");if (err)goto disable;if (!pci_is_enabled(pdev))goto release;// 试用 MSI(老版本 API,4.4 可用)pci_enable_msi(pdev);// BAR0 映射void __iomem *bar0 = pci_iomap(pdev, 0, 0);if (!bar0) {err = -ENOMEM;goto msi_off;}// TODO: init device using bar0return 0;
msi_off:pci_disable_msi(pdev);
release:pci_release_regions(pdev);
disable:pci_disable_device(pdev);return err;
}static void demo_pci_remove(struct pci_dev *pdev)
{pci_disable_msi(pdev);pci_release_regions(pdev);pci_disable_device(pdev);
}static const struct pci_device_id demo_ids[] = {{ PCI_DEVICE(0x1234, 0x5678) },{ 0 }
};
MODULE_DEVICE_TABLE(pci, demo_ids);static struct pci_driver demo_pci_driver = {.name = "demo_pci",.id_table = demo_ids,.probe = demo_pci_probe,.remove = demo_pci_remove,
};module_pci_driver(demo_pci_driver);

引用:drivers/misc/ioc4.c:480drivers/misc/hpilo.c:882pci_register_driver);配置访问:drivers/of/of_pci_irq.c:39pci_read_config_byte)。

版本差异:5.x/6.x 更推荐 pci_alloc_irq_vectors() 统一管理 MSI/MSI-X;引入 pcim_* 托管资源接口简化释放路径。

USB 总线

  • 架构:主机控制器(EHCI/XHCI 等)→ 根集线器 → 普通集线器 → 设备层级拓扑;设备通过描述符(device/configuration/interface/endpoint)在枚举时上报能力。
  • 枚举过程:端口复位→地址分配→读取设备/配置描述符→设置配置→创建接口与端点对象→驱动匹配。
  • 关键路径:usb_new_device() 完成设备注册与枚举,参考 drivers/usb/core/hcd.c:1049drivers/usb/core/hcd.c:1092;hub 处理过程参见 drivers/usb/core/hub.c:2254 注释。

代码示例(USB 接口驱动):

#include <linux/usb.h>static int demo_usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
{struct usb_device *udev = interface_to_usbdev(intf);dev_info(&intf->dev, "USB demo: vid=%04x pid=%04x\n",le16_to_cpu(udev->descriptor.idVendor),le16_to_cpu(udev->descriptor.idProduct));return 0;
}static void demo_usb_disconnect(struct usb_interface *intf)
{dev_info(&intf->dev, "USB demo disconnected\n");
}static const struct usb_device_id demo_table[] = {{ USB_DEVICE(0x1234, 0x5678) },{ }
};
MODULE_DEVICE_TABLE(usb, demo_table);static struct usb_driver demo_usb_driver = {.name = "demo_usb",.probe = demo_usb_probe,.disconnect = demo_usb_disconnect,.id_table = demo_table,
};module_usb_driver(demo_usb_driver);

版本差异:6.x 在 XHCI/热插拔鲁棒性、UASP/更高层协议支持上增强;匹配接口更多使用 fwnode 辅助统一固件路径。

I2C/SPI 串行总线

  • I2C:由控制器(i2c_adapter)发起消息,通过 7/10 位地址选择从设备(i2c_client);驱动通过 i2c_driverid_table / of_match_table 进行匹配。
  • SPI:控制器(spi_master)管理多个片选(CS),spi_device 以模式与速率配置;驱动为 spi_driver,匹配基于 modalias/设备树。

I2C 客户端驱动示例:

#include <linux/i2c.h>static int demo_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{int ret = i2c_smbus_read_byte_data(client, 0x00);if (ret < 0)return ret;dev_info(&client->dev, "reg0=0x%02x\n", ret);return 0;
}static int demo_i2c_remove(struct i2c_client *client)
{return 0;
}static const struct i2c_device_id demo_i2c_ids[] = {{ "demo_chip", 0 },{ }
};
MODULE_DEVICE_TABLE(i2c, demo_i2c_ids);static const struct of_device_id demo_i2c_of_match[] = {{ .compatible = "demo,v1" },{ }
};
MODULE_DEVICE_TABLE(of, demo_i2c_of_match);static struct i2c_driver demo_i2c_driver = {.driver = {.name = "demo_i2c",.of_match_table = demo_i2c_of_match,},.probe = demo_i2c_probe,.remove = demo_i2c_remove,.id_table = demo_i2c_ids,
};module_i2c_driver(demo_i2c_driver);

版本差异:5.x/6.x 在 I2C/SPI 顶层 API 基本稳定,但更多使用 devm_*(devres)管理资源,推广 fwnode 统一匹配。

平台总线(platform bus)

  • 特殊性:不对应外部可枚举物理总线,多为 SoC 内部外设;设备来源于设备树(DT)或 ACPI 表,挂载到 platform_bus_type
  • 注册流程参考:drivers/base/platform.c:1099bus_register(&platform_bus_type)),设备/驱动对象为 platform_device/platform_driver
  • 常见资源:IORESOURCE_MEM/IORESOURCE_IRQ,通过 platform_get_resource()devm_ioremap_resource() 等获取。

平台驱动模板:

#include <linux/platform_device.h>
#include <linux/of.h>static int demo_platform_probe(struct platform_device *pdev)
{struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);void __iomem *base = devm_ioremap_resource(&pdev->dev, res);if (IS_ERR(base))return PTR_ERR(base);return 0;
}static int demo_platform_remove(struct platform_device *pdev)
{return 0;
}static const struct of_device_id demo_of_match[] = {{ .compatible = "demo,platform" },{ }
};
MODULE_DEVICE_TABLE(of, demo_of_match);static struct platform_driver demo_platform_driver = {.driver = {.name = "demo_platform",.of_match_table = demo_of_match,},.probe = demo_platform_probe,.remove = demo_platform_remove,
};module_platform_driver(demo_platform_driver);

引用:Documentation/driver-model/platform.txtdrivers/base/platform.c:1099


3. 核心数据结构分析

struct bus_type 详解

位置:include/linux/device.h:105

关键字段与回调含义:

  • name/dev_name:总线名称与设备名前缀(影响 sysfs 和默认设备命名)。
  • bus_groups/dev_groups/drv_groups:为总线、设备、驱动在 sysfs 暴露的属性分组。
  • match(dev, drv):设备与驱动匹配函数,决定是否调用 probe
  • uevent(dev, env):热插拔事件扩展,用于填充用户态 udev 环境变量。
  • probe/remove/shutdown:驱动生命周期回调;shutdown 用于系统关机阶段设备收尾。
  • online/offline:设备上线/下线控制(部分总线支持)。
  • suspend/resume/pm:电源管理回调与统一 dev_pm_ops
  • iommu_ops:与 IOMMU 集成的钩子。
  • psubsys_private,driver core 的私有数据(kset/klist 管理)。

代码引用:include/linux/device.h:114-132

设备与驱动的匹配机制

  • 总线优先调用 bus_type.match;若为空,由通用规则(如 id_tableof_match_tableacpi_match_table)匹配。
  • PCI:根据 vendor/device/subsystem/classpci_device_id 数组匹配。
  • USB:按接口/类/子类/协议与 usb_device_id 匹配;接口驱动匹配到 usb_interface
  • I2C/SPI/Platform:基于 modalias、设备树 compatible 字段或 ACPI HID。
  • 失败路径与重试:驱动返回 -EPROBE_DEFER 触发稍后重试,以解决资源依赖的时序问题。

热插拔支持实现原理

  • 内核在设备出现/消失、绑定/解绑驱动时触发 uevent,用户空间通过 udev 规则进行设备节点创建、权限设置等。
  • 内核侧通知:bus notifiers(BUS_NOTIFY_* 枚举,参考 include/linux/device.h:184-194)供子系统或监控逻辑订阅事件。
  • 典型热插拔总线:PCIe(根端口 Hotplug、AER)、USB(拔插实时枚举),平台总线通常非热插拔。

4. 实际开发示例

如何注册自定义总线

#include <linux/device.h>static int demo_bus_match(struct device *dev, struct device_driver *drv)
{// 简例:匹配名字前缀相同return !!strstr(dev_name(dev), drv->name);
}static struct bus_type demo_bus = {.name = "demo_bus",.match = demo_bus_match,
};static int __init demo_bus_init(void)
{return bus_register(&demo_bus); // 参考 drivers/base/bus.c:888
}static void __exit demo_bus_exit(void)
{bus_unregister(&demo_bus);
}module_init(demo_bus_init);
module_exit(demo_bus_exit);
MODULE_LICENSE("GPL");

注册成功后,将在 /sys/bus/demo_bus/ 下出现相关节点。可通过自定义 bus_groups 暴露属性。

编写总线级驱动程序模板

static int demo_dev_probe(struct device *dev)
{dev_info(dev, "demo bus device probed\n");return 0;
}static int demo_dev_remove(struct device *dev)
{dev_info(dev, "demo bus device removed\n");return 0;
}static struct device_driver demo_driver = {.name = "demo",.bus = &demo_bus,.probe = demo_dev_probe,.remove = demo_dev_remove,
};static int __init demo_driver_init(void)
{return driver_register(&demo_driver);
}static void __exit demo_driver_exit(void)
{driver_unregister(&demo_driver);
}module_init(demo_driver_init);
module_exit(demo_driver_exit);

同时需要创建 struct device 并设置 dev.bus = &demo_bus 才会参与匹配。

调试技巧和常见问题解决

  • 启用调试:CONFIG_DEBUG_DRIVER=ydynamic_debugecho 'file drivers/base/* +p' > /sys/kernel/debug/dynamic_debug/control)。
  • 观察 sysfs:/sys/bus/<bus>/devices/sys/bus/<bus>/drivers
  • 处理 -EPROBE_DEFER:确认依赖资源(时钟、供电、IOMMU、跨驱动依赖)初始化顺序,考虑 module_async_probeprobe_type(参考 include/linux/device.h:220-224)。
  • 资源管理:优先 devm_* 系列自动释放;避免泄漏与竞态。
  • 热插拔问题:校验 uevent 环境、udev 规则与设备节点权限;PCIe 检查 AER/电源状态,USB 检查 hub 端口复位与电源策略。

5. 性能优化建议

总线传输效率优化

  • PCIe:使用 MSI/MSI-X 减少共享中断;合并 I/O、启用多队列(网络/存储);合理配置 readq/writeq 与缓存预取。
  • USB:批量传输(bulk)与 URB 聚合;降低小包频繁提交;异步提交减少上下文切换。
  • I2C/SPI:尽量使用 DMA capable 传输路径;对 SPI 使用更高 SCK 频率但注意目标时序与信号完整性。

中断处理最佳实践

  • 合理设置 IRQ 亲和与中断线程化(IRQF_ONESHOT);网络驱动结合 NAPI 降低软中断开销。
  • 使用 MSI/MSI-X 并分配 CPU 亲和到 NUMA 本地核;避免在中断上下文进行阻塞操作。

DMA 使用注意事项

  • 使用通用 DMA API:dma_alloc_coherentdma_map_{single,sg}dma_unmap_*,确保缓存一致性与内存屏障正确。
  • 校验设备与主机端地址宽度/对齐/段限制;启用 IOMMU 时检查映射失败与性能影响。
  • 在 5.x/6.x 更偏向使用 dma_alloc_attrs/dma_direct_map 的封装,并关注特定架构的同步细节。

版本差异与迁移提示

  • 4.4(当前):struct bus_type 字段如 uevent/match/pm/iommu_ops 已稳定;bus notifiersinclude/linux/device.h:175-194
  • 5.x:更广泛的异步 probe 默认化趋势(enum probe_type),fwnode 统一固件对象,pcim_* 托管 PCI 资源普及。
  • 6.x:USB/XHCI 改善、热插拔鲁棒性提升;IOMMU 与 DMA API 增强;更注重 devres 使用与能耗优化。

相关内核文档引用

  • Documentation/driver-model/overview.txt
  • Documentation/driver-model/bus.txt
  • Documentation/driver-model/device.txt
  • Documentation/driver-model/driver.txt
  • Documentation/driver-model/platform.txt
  • Documentation/power/runtime_pm.txt

观察与验证

  • 运行后查看 sysfs/sys/bus/<name>/devices/sys/bus/<name>/drivers
  • 使用 udev 规则验证热插拔事件(ACTION=add/removeSUBSYSTEM=<bus>)。
  • 结合源码引用快速定位:
    • include/linux/device.h:105struct bus_type 定义)
    • drivers/base/bus.c:888bus_register 实现)
    • drivers/base/platform.c:1099(平台总线注册)
    • drivers/usb/core/hcd.c:1049,1092usb_new_device 路径)
    • drivers/misc/ioc4.c:480(PCI 驱动注册示例)
http://www.dtcms.com/a/617976.html

相关文章:

  • 电压基准芯片详解:从原理到选型,附 TLV431 应用解析
  • 住房和城乡建设部网站监理工程师网站发送邮件功能
  • 开发第一个python程序
  • obet(Oracle Block Editor Tool)第二版发布
  • 【gas优化】2.11 Calldata 替换 Memory
  • 深度学习周报(11.10~11.16)
  • 阿里云建站论坛网站区块链网站建设方案
  • 李宏毅NLP-14-NLP任务
  • 惠普LaserJet Pro MFP M126a如何打印自检页
  • 南京大学cpp复习——面向对象第一部分(构造函数,拷贝构造函数,析构函数,移动构造函数,友元)
  • Stream 流核心速查表
  • 网站建设设计服务公司优化大师绿色版
  • STM32通信协议学习--I2C通信(了解)
  • 【技术选型】Go后台框架选型
  • 电子商务网站建设策划书范文西安优秀的集团门户网站建设费用
  • AI人工智能-语言模型-第六周(小白)
  • 找工作经验分享
  • 提供网站建设哪家好佛山外贸seo
  • 建站官网怎么做网页?
  • Qt编写28181推流分发服务/统计访问数量/无人观看超时关闭/等待重新点播/复用点播
  • CodexField Marketplace:重建内容与智能资产的链上市场结构
  • 微网站素材外贸家具网站
  • 后端服务发现工具,Consul与Eureka Consul vs Eureka:后端服务发现工具全面对比
  • 《动手学深度学习》6.5~6.6
  • 初识RabbitMQ
  • 历史数据分析——中国铝业
  • 网站建设设计公司类网站织梦模板 带手机端展厅设计图片
  • Kafka - 4 Kafka的副本同步机制
  • Windows 下 AI工具配置(一):Claude Code Router + Qwen
  • 西安网站设计公司哪家好网站开发怎么在页面上调用高德地图显示位置