Linux 下“发现设备”的机制
Linux 下“发现设备”的机制主要分几大类。你可以先记住一个总原则:
谁发现设备,谁创建对应的 device 对象;你就要写对应的 driver 框架去匹配它。
也就是说,发现机制不同,最终驱动框架就不同。
1. 第一类:总线协议自己能枚举设备
这类设备本身有标准枚举机制,Linux 可以主动扫描/枚举出来。
典型包括:
| 发现机制 | 典型设备 | Linux 设备对象 | 驱动框架 |
|---|---|---|---|
| PCI / PCIe 枚举 | PCIe FPGA、网卡、NVMe、GPU | struct pci_dev |
struct pci_driver |
| USB 枚举 | U盘、USB摄像头、USB串口 | struct usb_device / USB interface |
struct usb_driver |
| SCSI/SATA/SAS 扫描 | 硬盘、光驱、存储设备 | SCSI device / block device | SCSI / libata / block 框架 |
| NVMe 枚举 | NVMe SSD | PCI device + NVMe controller | PCI + NVMe 框架 |
比如 PCIe 设备,主机上电后 Root Complex 会枚举 PCIe 总线,读取设备配置空间,拿到 Vendor ID、Device ID、BAR、中断能力等信息。Linux PCI core 会据此创建 struct pci_dev,然后拿它去匹配 struct pci_driver。Linux PCI 驱动文档也说明,PCI 驱动通过注册 pci_driver,由 PCI 子系统在匹配设备时调用驱动的 probe()。(Linux内核文档)
所以你的 PCIe FPGA 板卡属于这一类:
PCIe 枚举
↓
Linux 创建 struct pci_dev
↓
匹配 struct pci_driver
↓
调用 pci_driver.probe()
因此驱动框架就是:
struct pci_driver
而不是 platform_driver、i2c_driver 或者单纯字符设备驱动。
2. 第二类:Firmware 描述设备
有些设备总线本身不能自动枚举,Linux 需要通过固件描述知道它们存在。
主要有两种:
Device Tree
ACPI
2.1 Device Tree 发现设备
常见于 ARM、Zynq、嵌入式 Linux。
比如设备树里写:
my_dma@43c00000 {
compatible = "mycompany,my-dma";
reg = <0x43c00000 0x10000>;
interrupts = <0 61 4>;
};
这段意思是:
这里有一个设备
寄存器基地址是 0x43c00000
大小是 0x10000
中断号是 61
匹配字符串是 "mycompany,my-dma"
Linux 启动时解析 Device Tree,创建对应的 platform_device,然后用 compatible 字符串去匹配 platform_driver。Linux 的 Device Tree 文档说明,Device Tree 是 Linux 描述硬件布局的重要方式;platform 驱动文档也说明,driver core 会在 device 和 driver 匹配后调用 probe()。(Linux内核文档)
所以这种路径是:
Device Tree
↓
Linux 创建 platform_device
↓
匹配 platform_driver
↓
调用 platform_driver.probe()
对应驱动框架是:
struct platform_driver
2.2 ACPI 发现设备
常见于 x86 PC、服务器、笔记本。
ACPI 表由 BIOS/UEFI 提供,里面描述了平台设备、电源管理、中断、I2C/SPI/UART 等设备信息。Linux ACPI 文档说明,ACPI 可以枚举 platform、SPI、I2C 以及某些 UART 后面的设备,并创建对应的物理设备对象。(Linux内核文档)
路径类似:
BIOS/UEFI ACPI table
↓
Linux ACPI core 解析
↓
创建 platform_device / i2c_client / spi_device 等
↓
匹配对应 driver
所以 ACPI 不一定只创建 platform_device,它也可能创建 I2C/SPI 子设备。
3. 第三类:父设备驱动创建子设备
这是非常重要的一类。
有些设备不是一开始就被系统直接看到,而是:
先发现一个父设备
父设备驱动 probe 后
再创建它下面的子设备
典型场景:
| 父设备 | 子设备 | 说明 |
|---|---|---|
| PCIe 板卡 | 板卡上的 I2C 芯片 | PCIe 驱动起来后,注册内部 I2C adapter,再创建 i2c_client |
| USB 设备 | 多个 USB interface | USB core 把一个物理设备拆成多个 interface 给不同驱动 |
| MFD 芯片 | GPIO / RTC / regulator / codec | 一个芯片内部有多个功能块 |
| MDIO 控制器 | Ethernet PHY | MAC 驱动注册 MDIO bus,再扫描 PHY |
| SCSI HBA | 磁盘 LUN | HBA 先被发现,再扫描下面的磁盘 |
| FPGA Manager / Overlay | 动态加载后的逻辑外设 | FPGA 配置后再生成新设备 |
比如一个 PCIe FPGA 板卡上,FPGA 内部实现了一个 I2C 控制器。流程可能是:
PCIe 枚举发现 FPGA
↓
pci_driver.probe()
↓
驱动映射 BAR
↓
发现 FPGA 内部有 I2C controller
↓
注册 i2c_adapter
↓
I2C core 再创建 i2c_client
↓
匹配 i2c_driver
所以这里有两层发现:
第一层:PCIe 发现 FPGA 板卡
第二层:FPGA 驱动创建内部子设备
这也是为什么复杂设备里会出现“一个物理设备对应多个 Linux 驱动”的情况。
4. 第四类:I2C / SPI 这类“不能自发现”的总线
I2C 和 SPI 很特殊。
它们不像 PCIe、USB 那样可以标准枚举。大多数 I2C/SPI 从设备不会主动告诉 Linux:
我是谁
我在哪里
我的型号是什么
所以 Linux 需要通过其他方式知道:
哪个总线上
哪个地址/片选
挂了什么芯片
4.1 I2C 设备发现方式
I2C 设备通常有几种实例化方式:
| 方式 | 说明 |
|---|---|
| Device Tree | 嵌入式最常见 |
| ACPI | x86/笔记本/服务器常见 |
| board info | 老式板级代码 |
| 父驱动显式创建 | 比如 PCIe/USB 设备内部挂 I2C 芯片 |
| sysfs 手动创建 | 调试用,例如 new_device |
Linux I2C 文档明确提到,I2C 设备可以显式实例化,例如填充 struct i2c_board_info 并调用 i2c_new_client_device()。(Linux内核文档)
I2C 的典型路径是:
Device Tree / ACPI / board info / 父驱动创建
↓
Linux 创建 struct i2c_client
↓
匹配 struct i2c_driver
↓
调用 i2c_driver.probe()
所以 I2C 驱动框架是:
struct i2c_driver
但注意:
I2C 设备不是靠 I2C 总线自己扫描出来的,通常是被描述出来或者被父驱动创建出来的。
4.2 SPI 设备发现方式
SPI 也类似。
SPI 设备通常由:
Device Tree
ACPI
board info
父驱动创建
来描述。
路径是:
固件/板级信息/父驱动
↓
创建 struct spi_device
↓
匹配 struct spi_driver
↓
调用 spi_driver.probe()
SPI 也不能像 PCIe 那样自动读出 Vendor ID / Device ID。
5. 第五类:platform_device 静态注册
这是老式或者简单嵌入式系统里常见的方式。
设备不是通过 Device Tree,也不是通过 ACPI,而是内核板级代码里直接注册:
platform_device_register(&my_device);
或者注册一组:
platform_add_devices(...);
然后驱动用:
platform_driver_register(&my_driver);
匹配方式通常是:
device name
driver name
或者通过 of_match_table / acpi_match_table。
这类方式现在在很多新项目里被 Device Tree / ACPI 替代了,但内核里仍然存在。
路径是:
板级代码注册 platform_device
↓
platform bus
↓
匹配 platform_driver
↓
调用 probe()
6. 第六类:热插拔 / 重新扫描
有些设备不是开机时就存在,而是运行中插入或重新扫描出来。
典型包括:
| 机制 | 例子 |
|---|---|
| PCIe Hotplug | 插入 PCIe 热插拔设备 |
| USB Hotplug | 插入 U盘、USB串口 |
| Thunderbolt Hotplug | 外接扩展坞 |
| PCI rescan | 手动触发 PCIe 总线重新扫描 |
| SCSI scan | 手动扫描新磁盘 |
| Device Tree overlay | 运行时加载新的 DT overlay |
| FPGA reconfiguration | FPGA 重新配置后出现新逻辑外设 |
例如 PCIe 可以通过 rescan 重新发现设备:
echo 1 > /sys/bus/pci/rescan
但本质仍然是:
PCI core 重新扫描
↓
发现新 pci_dev
↓
匹配 pci_driver
也就是说,热插拔只是“发现时机”不同,不改变驱动框架。
7. 第七类:用户手动创建设备
有些设备 Linux 不会自动知道,可以由用户手动告诉内核。
比如 I2C 调试时,可以手动创建一个设备:
echo tmp102 0x48 > /sys/bus/i2c/devices/i2c-1/new_device
这会告诉 I2C core:
在 i2c-1 总线上,0x48 地址有一个 tmp102 设备
然后 Linux 创建 i2c_client,再匹配 i2c_driver。
这种方式常用于:
调试
验证
没有设备树/ACPI 描述的临时场景
不太适合作为正式产品的主要发现方式。
8. 第八类:虚拟设备 / 软件总线
还有一些设备不是传统物理硬件,而是由虚拟化或软件框架创建。
典型包括:
| 机制 | 设备 |
|---|---|
| virtio | 虚拟网卡、虚拟磁盘、虚拟 console |
| vhost | 虚拟化后端设备 |
| rpmsg | 异构多核通信设备 |
| remoteproc | 远程处理器设备 |
| auxiliary bus | 一个大设备拆出的辅助子功能 |
| MFD | 多功能芯片拆出的子设备 |
这类设备的发现路径通常是:
父框架/虚拟化层/远程处理器框架创建 device
↓
挂到对应 bus
↓
匹配对应 driver
9. 重要区分:udev 不是硬件发现机制
很多人会把 udev 和设备发现混在一起。
严格说:
内核负责发现硬件并创建 device
udev 负责根据内核事件创建设备节点、设置权限、加载规则
例如:
PCIe 设备被 PCI core 发现
↓
pci_driver probe 成功
↓
驱动注册字符设备
↓
内核产生 uevent
↓
udev 创建 /dev/mydma
所以:
udev不是发现 PCIe 设备的人,它只是用户空间里的设备节点管理器。
10. 也要区分:字符设备不是硬件发现机制
比如你写:
register_chrdev();
misc_register();
cdev_add();
这只是创建用户态访问入口:
/dev/xxx
它不是发现硬件。
对于 PCIe FPGA DMA,正确结构是:
PCIe 发现机制
↓
pci_driver 绑定硬件
↓
probe 里初始化 BAR / DMA / IRQ
↓
注册字符设备
↓
用户通过 /dev/mydma 访问
所以不能说:
我写字符设备驱动,所以不用 pci_driver
更准确是:
底层用 pci_driver 发现和绑定 PCIe 设备
上层用字符设备给用户态提供接口
11. 总结表:Linux 常见设备发现机制
| 发现机制 | 谁发现设备 | 创建什么对象 | 驱动框架 | 例子 |
|---|---|---|---|---|
| PCI/PCIe 枚举 | PCI core | pci_dev |
pci_driver |
FPGA PCIe 板卡、网卡、NVMe |
| USB 枚举 | USB core | usb_device / interface |
usb_driver |
U盘、USB摄像头 |
| Device Tree | OF/DT core | platform_device / i2c_client / spi_device |
platform/I2C/SPI | ARM SoC 外设 |
| ACPI | ACPI core | platform/I2C/SPI/UART 等设备 | 对应 driver | x86 平台设备 |
| board info | 板级代码 | platform/I2C/SPI device | 对应 driver | 老式嵌入式 |
| 父驱动创建 | parent driver | 子 device | 子系统 driver | MFD、MDIO、FPGA 内部 I2C |
| 总线重新扫描 | bus core | 新 device | 对应 driver | PCI rescan、SCSI scan |
| 用户手动创建 | sysfs/configfs | 对应 device | 对应 driver | I2C new_device |
| 虚拟化/软件总线 | virtio/rpmsg/auxiliary 等 | 虚拟 device | 对应 driver | virtio-net、rpmsg |
12. 对你最重要的判断方法
你以后看到一个硬件,先不要问“我要写什么模板”,而是按这个顺序判断:
1. lspci 能不能看到?
能 → PCIe 设备 → pci_driver
2. lsusb 能不能看到?
能 → USB 设备 → usb_driver
3. 设备树里有没有 compatible/reg/interrupts?
有 → 多数是 platform_driver,也可能是 i2c_driver/spi_driver
4. 是 I2C 地址设备吗?
是 → i2c_driver,但设备通常要由 DT/ACPI/父驱动/手动创建
5. 是 SPI 片选设备吗?
是 → spi_driver,但设备通常要由 DT/ACPI/父驱动创建
6. 是 SoC 内部寄存器外设吗?
是 → platform_driver
7. 是 PCIe 设备内部又挂了子功能吗?
是 → 先 pci_driver,再由父驱动创建子设备
8. 只是想给用户态 /dev 接口?
那是字符设备接口,不是硬件发现路径
13. 一句话记忆
能自报家门的总线,比如 PCIe/USB,靠总线枚举发现;不能自报家门的设备,比如 platform/I2C/SPI,靠 Device Tree、ACPI、板级信息或父驱动创建;用户态的
/dev只是访问入口,不负责发现硬件。
对你的 PCIe FPGA 来说,最关键就是:
发现机制:PCIe 枚举
内核对象:struct pci_dev
驱动框架:struct pci_driver
匹配依据:Vendor ID / Device ID
资源来源:BAR / IRQ / DMA capability
用户接口:再额外注册字符设备
更多推荐


所有评论(0)