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_driveri2c_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
用户接口:再额外注册字符设备
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐