在你这个 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
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐