总线设备驱动模型在这个I2C子设备案例中,如何体现的
在你这个 PCIe-FPGA-I2C 例子里,Linux 的“总线-设备-驱动模型”不是只出现一次,而是出现了两层:
第一层:PCIe 总线设备驱动模型
PCIe bus
└── PCIe FPGA device
└── fpga_pci_driver
第二层:I2C 总线设备驱动模型
I2C bus,也就是 i2c_adapter
└── I2C client device,比如 tmp102 / eeprom / pmbus
└── 对应的 i2c_driver
也就是说:
PCIe 负责发现 FPGA 这个大设备;I2C 子系统负责管理 FPGA 内部 I2C 控制器下面挂的 I2C 小设备。
1. Linux 总线设备驱动模型的通用形式
Linux driver model 可以抽象成:
bus
├── device
└── driver
匹配成功后:
bus.match(device, driver)
↓
driver.probe(device)
内核文档中也说明,驱动的 probe() 会在设备和驱动绑定时被调用,并且驱动通常会把通用 struct device 转成 bus-specific 类型,例如 struct pci_dev 或其他设备类型。(内核文档)
所以最核心是:
设备挂在哪条 bus 上,就由那条 bus 的规则来匹配 driver。
2. 第一层:PCIe 总线模型
你的 FPGA 板卡先是一个 PCIe Endpoint。
Linux PCI core 枚举后,会创建:
struct pci_dev
这个 pci_dev 挂在 PCI bus 上。
然后你写 PCIe 父驱动:
static const struct pci_device_id fpga_ids[] = {
{ PCI_DEVICE(0x1234, 0x5678) },
{ 0, }
};
static struct pci_driver fpga_pci_driver = {
.name = "fpga-pcie",
.id_table = fpga_ids,
.probe = fpga_pci_probe,
.remove = fpga_pci_remove,
};
PCI 驱动文档说明,struct pci_driver 里包含驱动名、感兴趣的设备 ID 表、probe 等成员,PCI core 会用 id_table 匹配设备并调用 probe()。(内核文档)
这时第一层模型是:
PCI bus
├── device: struct pci_dev
│ └── 代表 FPGA PCIe Endpoint
│
└── driver: struct pci_driver
└── fpga_pci_driver
匹配过程:
PCI core 发现 FPGA
↓
创建 pci_dev
↓
用 Vendor ID / Device ID 匹配 pci_driver.id_table
↓
匹配成功
↓
调用 fpga_pci_probe()
fpga_pci_probe() 里做:
pci_enable_device()
pci_request_regions()
pci_iomap() / pcim_iomap()
得到 BAR0
初始化 FPGA
到这里为止,Linux 只知道:
有一个 PCIe FPGA 设备。
还不知道:
FPGA 内部有一个 I2C 控制器。
3. PCIe 父驱动创建/注册 FPGA-I2C 控制器
在 fpga_pci_probe() 里,你可以做两种事情。
简单方式:
直接注册 i2c_adapter
工程化方式:
创建一个 fpga-i2c 子设备
让 fpga-i2c 子驱动 probe
在子驱动里注册 i2c_adapter
不管哪种方式,结果都是:
Linux I2C core 里多了一条 I2C bus,也就是一个 i2c_adapter。
例如:
fi2c->adap.algo = &fpga_i2c_algo;
fi2c->adap.dev.parent = &pdev->dev;
i2c_set_adapdata(&fi2c->adap, fi2c);
devm_i2c_add_adapter(&pdev->dev, &fi2c->adap);
Linux I2C 文档说,I2C controller 在 Linux 中也叫 adapter 或 bus;controller driver 通常在 drivers/i2c/busses/ 目录下。(内核文档)
I2C 文档也说明,I2C adapter driver 用来抽象控制器硬件,它可以绑定到 PCI 设备或 platform device,并暴露 struct i2c_adapter。(内核文档)
这句话正好对应你的场景:
FPGA I2C Controller 是硬件;
fpga-i2c adapter driver 是它的 Linux 驱动;
struct i2c_adapter 是 Linux I2C core 看到的总线对象。
4. 第二层:I2C 总线模型
注册 i2c_adapter 后,Linux 里出现一条 I2C 总线,例如:
i2c-5
这时候第二层总线模型开始体现:
I2C bus / i2c_adapter
├── device: struct i2c_client
│ ├── tmp102 at 0x48
│ ├── eeprom at 0x50
│ └── pmbus device at 0x58
│
└── driver: struct i2c_driver
├── tmp102_driver
├── at24_driver
└── pmbus_driver
也就是说:
i2c_adapter 代表 I2C 控制器/总线;
i2c_client 代表挂在这条总线上的 I2C 从设备;
i2c_driver 代表这些 I2C 从设备的驱动。
I2C client driver 文档说明,I2C/SMBus 设备驱动是面向挂在 I2C 总线上的 client 设备来写的。(内核文档)
5. I2C 设备和 I2C 驱动怎么匹配?
假设 FPGA I2C 总线上有一个 TMP102 温度传感器,地址 0x48。
你可以创建一个 I2C client:
echo tmp102 0x48 > /sys/bus/i2c/devices/i2c-5/new_device
这会创建类似:
5-0048
也就是:
i2c-5 总线上的 0x48 设备
Linux I2C core 会尝试匹配对应的 i2c_driver,比如 tmp102_driver。
匹配成功后调用:
tmp102_probe()
这就是第二层 bus-device-driver 模型:
I2C core 创建 i2c_client
↓
I2C bus 匹配 i2c_driver
↓
调用 tmp102_driver.probe()
6. 关键:两个 probe 分别干不同的事
这里很重要。
第一层 probe:PCIe 父驱动 probe
fpga_pci_probe(struct pci_dev *pdev, ...)
它负责:
识别 PCIe FPGA
映射 BAR
初始化 FPGA 全局资源
注册/创建 FPGA-I2C adapter
它面对的是:
PCIe 设备
第二层 probe:I2C client driver probe
例如:
tmp102_probe(struct i2c_client *client)
它负责:
识别 TMP102 芯片
读取 TMP102 寄存器
注册 hwmon
暴露 temp1_input
它面对的是:
I2C 从设备
它不关心底层 I2C controller 是:
SoC I2C
USB-I2C
GPIO bitbang I2C
PCIe-FPGA-I2C
它只知道自己有一个 struct i2c_client,可以通过 I2C core 发传输。
7. FPGA-I2C adapter driver 在中间起什么作用?
它是第二层 I2C 总线的“控制器驱动”。
它注册:
struct i2c_adapter
并实现:
struct i2c_algorithm {
.master_xfer = fpga_i2c_master_xfer,
.functionality = fpga_i2c_func,
};
当 TMP102 驱动要读温度时:
tmp102 driver
↓
i2c_smbus_read_word_data()
↓
Linux I2C core
↓
i2c_transfer()
↓
fpga_i2c_master_xfer()
↓
writel/readl BAR
↓
FPGA I2C 状态机
↓
SCL/SDA
↓
TMP102
所以在第二层 I2C 模型里:
fpga_i2c_adapter = I2C bus/controller
tmp102/eeprom = I2C device/client
tmp102_driver/eeprom_driver = I2C device driver
8. 用完整层级图表示
你的系统可以画成这样:
PCIe bus
│
├── device: FPGA PCIe Endpoint
│ 类型:struct pci_dev
│ 匹配:Vendor ID / Device ID
│
└── driver: fpga_pci_driver
类型:struct pci_driver
probe: fpga_pci_probe()
作用:
- 映射 BAR0
- 初始化 FPGA
- 注册 fpga_i2c_adapter
│
▼
I2C bus / adapter: i2c-5
类型:struct i2c_adapter
传输函数:fpga_i2c_master_xfer()
│
├── device: tmp102 at 0x48
│ 类型:struct i2c_client
│ driver: tmp102_driver
│
├── device: eeprom at 0x50
│ 类型:struct i2c_client
│ driver: at24_driver
│
└── device: pmbus at 0x58
类型:struct i2c_client
driver: pmbus_driver
这就是总线设备驱动模型在这个例子里的体现。
9. 你可以把它理解成两次“注册到标准模型”
第一次:
FPGA 作为 PCIe 设备注册到 PCI bus 模型
对应:
pci_dev + pci_driver
第二次:
FPGA 内部 I2C 控制器作为 I2C adapter 注册到 I2C bus 模型
对应:
i2c_adapter + i2c_client + i2c_driver
所以完整过程是:
PCIe 枚举发现 FPGA
↓
PCI bus 匹配 fpga_pci_driver
↓
fpga_pci_probe()
↓
映射 BAR,发现内部有 I2C 控制器
↓
注册 i2c_adapter
↓
Linux I2C core 出现 i2c-X
↓
在 i2c-X 上创建设备 0x48 / 0x50
↓
I2C bus 匹配 tmp102 / eeprom 驱动
↓
这些 I2C 驱动通过 fpga_i2c_master_xfer() 间接访问 FPGA BAR
10. 最核心一句话
在这个例子中,PCIe 总线模型负责把 FPGA 这个“大设备”绑定到 PCIe 父驱动;I2C 总线模型负责把 FPGA 内部 I2C 控制器抽象成一条 I2C bus,再把挂在这条 bus 上的温度传感器、EEPROM 等“小设备”绑定到各自的 I2C 驱动。
换句话说:
PCIe 层解决:Linux 怎么找到 FPGA?
I2C adapter 层解决:Linux 怎么把 FPGA 内部 I2C 控制器当成一条 I2C 总线?
I2C client 层解决:Linux 怎么把这条总线上的具体 I2C 芯片交给标准驱动?
所以你前面理解的“适配层桥接”没有错,只是放到总线设备驱动模型里看,就是:
pci_driver.probe()
创建/注册 i2c_adapter
i2c_adapter
成为新的 I2C bus
i2c_client
挂到这个 I2C bus 上
i2c_driver
匹配并驱动这些 I2C client
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)