代码仓
中
总线芯片驱动开发指南
更新时间: 2026/08/20
在AtomGit上查看源码

总线芯片驱动开发指南 ​

概述 ​

总线芯片驱动是南向硬件访问的底座,把 CSR 拓扑里的每个 Bus / Chip 节点变成可访问对象。本文面向需要在 component_drivers 中新增或修改总线、芯片驱动的开发人员。

说明

相对旧稿删除了已不存在的 libhas/、Hi182x / SMBusHuawei 示例和未落地的 SPI 教程。现行实现以 device_topo、device_manager 与 ABI 为准,并以 I2C、PCA9545 作为可对照源码的模板。

环境要求 ​

硬件要求:交叉编译需要目标板与 libsoc_adapter。原生测试可用打桩驱动,不强制真实总线硬件。 软件要求:component_drivers 源码(以 drivers/internal/、drivers/bus/、drivers/chip/ 为准)、meson 构建、能运行 tests/drivers/ 单测。 权限要求:具备源码编译与单测执行权限。

目录 ​

  1. 范围与术语
  2. 架构与调用链
  3. 目录与关键文件
  4. 基类与 ACCESS_DATA_S
  5. CSR 配置与对象命名
  6. 新增 Bus 驱动
  7. 新增 Chip 驱动
  8. 对外接口、级联与错误处理
  9. 构建与注册
  10. 检查清单与常见问题

1. 范围与术语 ​

总线芯片驱动不描述一块完整板卡。上层网卡、GPU、电源、Connector、Accessor 通过统一接口读写 I2C、Hisport、MUX、EEPROM 等器件。

术语含义
CSR部件自描述(.sr / .dds / 测试用 json)。含 ManagementTopology 与 Objects。
设备树对象drivers/bus/、drivers/chip/ 中继承 mc::engine::object 的类,对 devmon 暴露 ABI 与属性。
内部器件对象drivers/internal/ 中继承 bus_base / chip_base 的类,执行真正的硬件 I/O。
HAS历史称呼。现行实现就是内部器件层 + ACCESS_DATA_S,没有独立 libhas/ 目录。
ABIinclude/devmon/driver_abi.h 的 C 接口。每个设备树 SO 导出 register_device_driver。
device_manager内部对象单例工厂,按 class_name 创建、按 object_name 索引。
device_topodevmon 侧拓扑引擎:解析 CSR、创建内部对象、dlopen SO、接线 left_bus / host_bus / left_chip。
host_bus根物理总线。Interface_Chip 加锁必须锁它。
left_bus芯片直连的那条总线,可能是 I2cMux 虚总线。
left_chip前级 MUX / SMC 等,访问前会 open_channel。

协议栈(NCSI、MCTP、PMBUS)在 libraries/,不属于本指南。CSR1 专用类型(GPIO、JTAG、CPLD 等)由 COMPONENT_DRIVERS_ENABLE_UNIDEV 控制,不写入默认检查清单。


2. 架构与调用链 ​

2.1 双层对象 ​

每个总线 / 芯片都有两套对象,职责不能混:

text
设备树对象(libI2c.so / libPca9545.so)
  I2c_object / pca9545_object
    └── Interface_*(属性 + 对外方法)
          └── 裸指针 ──► 内部对象
内部对象(libInternal.so,device_manager 持有 shared_ptr)
  bus_i2c / chip_pca9545
    └── DRIVER_I2C::I2c 等底层驱动(生产环境来自 libsoc_adapter)

设备树 init() 不会 new 内部对象,只按 object_name 从 device_manager::find_object 取已有实例。内部对象由 device_topo 在解析 CSR 时先 create_bus / create_chip。

2.2 加载顺序 ​

类型名不是 CSR 里的单独字段,而是 对象名第一个 _ 之前的子串:

对象名抽出的类型内部类设备树 SO
I2c_3I2cbus_i2clibI2c.so
Hisport_0Hisportbus_hisportlibHisport.so
I2cMux_Pca9545_PCA9545_4I2cMuxbus_i2c_muxlibI2cMux.so
Pca9545_PCA9545Pca9545chip_pca9545libPca9545.so
Chip_MCU1Chipchip_base 或 chip_mcu_switchlibChip.so
Eeprom_IEUEepromchip_eepromlibEeprom.so

device_name、工厂 key、对象名前缀三者必须一致(区分大小写)。写成 PCA9545_0 会去找工厂 "PCA9545",创建失败。

2.3 BlockIO 调用路径 ​

text
Interface_Chip::BlockIORead
  → BusLock(*host_bus)                 // 锁根物理总线,不是 I2cMux
  → chip_base::block_read
  → data_access → pack ACCESS_DATA_S → input()
       ├─ 同 topo_path 的 follower 转给 master(ChipMerge)
       ├─ 有 left_chip:
       │    类名 "smc" → forward_by_front_chip
       │    否则 open_channel(left_bus) → left_chip->output() → close_channel
       └─ 只有 left_bus:bus->read / write
  → 物理驱动(如 DRIVER_I2C::I2c)

注意

open_channel 失败必须返回,不能继续访问,否则可能写到错误通道。

2.4 默认支持的类型(CSR2) ​

总线:I2c、Hisport、I2cMux;打开 chipv2 时还有 I2cOverHisport。

芯片:Chip(drivers/chip/complex/ 通用对象)、Pca9545、Pca9555、Lm75、Eeprom、Ads78、Ina、Smc。

I2cMux 是虚总线:只保存 ChannelId,read / write / bus_lock / bus_unlock 全部抛错。它的 get_id() 返回通道号,供 PCA9545 等 MUX 的 open_channel 使用。


3. 目录与关键文件 ​

text
component_drivers_blue/
├── drivers/
│   ├── bus/                      # 总线设备树对象(每个类型一个 SO)
│   │   ├── i2c/  i2c_mux/  hisport/  i2c_over_hisport/
│   ├── chip/                     # 芯片设备树对象
│   │   ├── interface/            # Interface_Chip、BlockIO、SelfDescriptionRecord
│   │   ├── pca9545/ pca9555/ lm75/ eeprom/ ads78/ powermeter/ smc/ complex/
│   ├── internal/                 # 内部器件 + device_manager → libInternal.so
│   │   ├── hasdef.h              # ACCESS_DATA_S
│   │   ├── manager.h/.cpp
│   │   ├── bus/bus_base/ bus_i2c/ bus_i2c_mux/ bus_hisport/ ...
│   │   └── chip/chip_base/ chip_pca9545/ chip_eeprom/ ...
│   ├── accessor/  scanner/  connector/
│   └── csr1/                     # CSR1 设备树对象(unidev)
├── gen/include/device_tree/interface/
│   ├── Bus/I2c.h  Hisport.h  I2cMux.h
│   ├── Chip.h                    # bmc.dev.Chip 基础属性与 IO 方法
│   └── Chip/Pca9545.h  Ina.h  BlockIO.h  SelfDescriptionRecord.h
├── include/devmon/driver_abi.h
└── tests/drivers/bus/  chip/  internal/

对照源码时优先看:

  • 物理总线模板:drivers/internal/bus/bus_i2c/ + drivers/bus/i2c/。
  • 虚总线模板:drivers/internal/bus/bus_i2c_mux/ + drivers/bus/i2c_mux/。
  • MUX 芯片模板:drivers/internal/chip/chip_pca9545/ + drivers/chip/pca9545/。
  • 通用芯片:drivers/chip/complex/(工厂 key "Chip")。
  • CSR 样例:tests/drivers/chip/eeprom/00000001040302044498.json。

4. 基类与 ACCESS_DATA_S ​

4.1 ACCESS_DATA_S ​

定义在 drivers/internal/hasdef.h,是总线 read / write 的唯一入参。芯片层由 chip_base::pack_access_data 填充。

字段含义
addr器件地址。I2C 路径只取低 8 位,CSR 里通常是 8-bit 写地址(7-bit << 1)。
offset / offset_width寄存器或 EEPROM 偏移,及偏移Byte数(0~4)。offset == 0xffffffff 走 I2C / Hisport 扩展读(无寄存器寻址,in_buffer 作写前缀)。
length读写长度。
in_buffer / out_buffer写数据 / 读回数据。
mask位操作掩码。I2C 写路径不用它;bit_read 在读回后按掩码提取。
read_time_out / write_time_out超时。
drv_write_delay写后延时(ms),对应 CSR DrvWriteDelay。
optype0 读 / 1 写。
protocol_flagI2cOverHisport 上 == 2 走 MCTP;I2C 上 == 0x02 走 SMBus。
csSPI over Hisport 片选。

返回值:HAS_OK(0)成功,HAS_ERR(-1)或底层驱动错误码表示失败。

4.2 bus_base ​

路径:drivers/internal/bus/bus_base/bus.h。

物理总线必须覆盖:

cpp
virtual uint32_t read(ACCESS_DATA_S& input)  = 0;
virtual uint32_t write(ACCESS_DATA_S& input) = 0;
virtual void     bus_lock()   = 0;
virtual void     bus_unlock() = 0;
virtual uint8_t  get_id();    // 默认 0;I2C 返回 Id,I2cMux 返回 ChannelId

工厂创建成功后应调用 set_class_name("I2c") 等,供 build_topo_path() 生成 BusType:BusId。还有 set_left_chip、业务锁 BusLockInfo、单次最大传输长度。

I2C 组帧(bus_i2c::format_tx_buf):

text
[addr 1B] [offset 大端,OffsetWidth Byte] [payload length Byte]

OffsetWidth = 0 时没有偏移段(PCA9545 切通道)。普通读 re_read_cnt = 1;扩展读重试 3 次。

4.3 chip_base ​

路径:drivers/internal/chip/chip_base/chip.h。基类已实现默认 I/O,子类只覆盖差异。

可覆盖:

类别函数何时覆盖
配置 / 自检chip_init / chip_test / chip_reset写阈值、复位脚等。chip_init 在拓扑接好后的 start() 调用,不是 init(csr)。
通道open_channel / close_channelMUX 必须成对覆盖。
访问bit_* / block_* / block_write_read / combo_block_write_read分页、换算、RMW。
透传forward_by_front_chip / get_class_nameSMC 等前级。get_class_name 需返回稳定短名(如 "lm75"、"smc")。
插件plugin_request默认:unidev 走 Lua,否则写 0x1f 再读 129 Byte。
诊断dump默认输出读写计数与错误历史 CSV。

init(csr) 解析 Address、OffsetWidth、AddrWidth、超时、块大小、间隔、重试、DrvWriteDelay。遇到 $sync: / $ref: / <= 等绑定表达式时跳过,留默认值,由设备树 property 系统再绑。

说明

WriteRetryTimes / ReadRetryTimes 会写入成员,但 data_access 与 bus_i2c 当前不按它们重试。真正用到写间隔的是 EEPROM 分页写的 WriteInterval。

CSR2 的 Interface_Chip 运行时只把 DrvWriteDelay 的变更写回内部对象。改 Address 等 property 不会自动同步到 chip_base(CSR1 对象才普遍绑了 update_*)。


5. CSR 配置与对象命名 ​

没有合法 CSR,device_topo 不会 create_*,设备树 init 里 find_object 必然失败。

5.1 最小结构 ​

json
{
  "ManagementTopology": {
    "Anchor": { "Buses": ["I2c_3"] },
    "I2c_3": { "Chips": ["Pca9545_Mux0"] },
    "Pca9545_Mux0": { "Buses": ["I2cMux_Pca9545_Mux0_0"] },
    "I2cMux_Pca9545_Mux0_0": { "Chips": ["Eeprom_Board"] }
  },
  "Objects": {
    "I2c_3": { "Id": 3, "Speed": 0, "Mode": 0, "SlaveAddr": 0, "UseSmbus": false },
    "Pca9545_Mux0": {
      "Address": 226, "OffsetWidth": 0, "AddrWidth": 1,
      "WriteTmout": 100, "ReadTmout": 100, "ChannelNum": 4
    },
    "I2cMux_Pca9545_Mux0_0": { "ChannelId": 0 },
    "Eeprom_Board": {
      "Address": 174, "OffsetWidth": 2, "AddrWidth": 1,
      "WriteTmout": 100, "ReadTmout": 100,
      "RwBlockSize": 32, "WriteInterval": 20
    }
  }
}

完整板卡样例见 tests/drivers/chip/eeprom/00000001040302044498.json(Hisport → PCA9545 → 多路 I2cMux → EEPROM / PCA9555 / Connector)。

5.2 接线规则(device_topo) ​

  • 芯片 left_bus = 父总线。
  • 有父芯片时:left_chip = 父芯片,host_bus = 父芯片的 host_bus(根物理总线)。
  • 无父芯片:host_bus = 父总线。
  • 子总线 left_chip = 当前芯片。

访问 Eeprom_Board 时:left_bus 是 I2cMux(不能 lock),left_chip 是 PCA9545(先 open_channel),host_bus 是 I2c_3(加锁并真正发事务)。

5.3 属性约定 ​

I2c(bmc.dev.Bus.I2c):Id、Speed(0→100k、1→400k、2→3400k,其它值当 kHz)、Mode、SlaveAddr、UseSmbus。

I2cMux(bmc.dev.Bus.I2cMux):只有 ChannelId,没有 Id。不是所有总线都必须声明 Id 属性。

Hisport(bmc.dev.Bus.Hisport):Id、BusNum、RegNum、收发 / 标志寄存器、最大长度等,见 gen/include/device_tree/interface/Bus/Hisport.h。

Chip 基础(bmc.dev.Chip):Address、OffsetWidth、AddrWidth、WriteTmout、ReadTmout、RwBlockSize、WriteInterval、ReadInterval、WriteRetryTimes、ReadRetryTimes、DrvWriteDelay。

PCA9545 另有 ChannelNum。Chip + "SwitchSupported": true 时内部对象为 chip_mcu_switch。


6. 新增 Bus 驱动 ​

以下以物理总线为模板。虚总线(通道选择)为可选路径,请对照 I2cMux:只解析 ChannelId,read / write / lock / unlock 抛 invalid_op_exception。

6.1 准备文件清单 ​

新增如下路径:

text
gen/include/device_tree/interface/Bus/{Name}.h
drivers/internal/bus/bus_{name}/{name}.h  {name}.cpp  meson.build
drivers/bus/{name}/
  {name}.h  {name}.cpp  {name}_abi.cpp  meson.build
  interface/i_{name}.h  i_{name}.cpp

另外还要改:drivers/internal/manager.h 的 include、manager.cpp 注册工厂;drivers/internal/bus/meson.build 与 drivers/bus/meson.build 增加 subdir。

6.2 新增设备树接口 ​

MC_INTERFACE 路径:"bmc.dev.Bus.{Name}"。物理总线一般有 Id;虚总线按硬件语义定义(如 ChannelId)。

cpp
namespace dev::gen {
class MC_API I2c : public mc::engine::interface<I2c> {
public:
    MC_INTERFACE("bmc.dev.Bus.I2c")  // 须与 CSR 抽出的类型名一致
    property<uint8_t>  Id;
    property<uint32_t> Speed;
    property<uint8_t>  Mode;
    property<uint8_t>  SlaveAddr;
    property<bool>     UseSmbus;
};
}

6.3 实现内部对象 ​

继承 bus_base,init 从 CSR 取属性并创建底层驱动。read / write 只处理 ACCESS_DATA_S。

cpp
class bus_i2c : public bus_base {
public:
    bool init(mc::mutable_dict& csr_object);           // 解析 CSR 并创建底层驱动
    uint32_t read(ACCESS_DATA_S& input) override;
    uint32_t write(ACCESS_DATA_S& input) override;
    uint8_t  get_id() override;
    void     bus_lock() override;
    void     bus_unlock() override;
private:
    uint8_t  m_id{};
    uint32_t m_speed = 100;
    std::unique_ptr<DRIVER_I2C::I2c> m_i2c_drv;
};

测试 / 原生构建链入 tests/drivers/bus/i2c/drivers/i2c.cpp 打桩;交叉编译链 libsoc_adapter。不要在驱动里直接 new 一套与工厂无关的总线对象。

6.4 实现设备树对象 ​

cpp
class I2c_object : public mc::engine::object<I2c_object> {
public:
    MC_OBJECT(I2c_object, "I2c_object", "${object_name}", (Interface_I2c))
    bool init(mc::mutable_dict& csr_object, const mc::dict& connector);
    Interface_I2c m_interface_i2c;
};

MC_OBJECT 第二参数在仓内不完全统一(I2c_object 为 "I2c_object",Hisport_object 为 "Hisport")。运行时匹配靠 ABI device_name 与对象名前缀,新增时建议与工厂 key 一致(如 "Hisport")。

init 只绑定内部对象:

cpp
auto& manager = dev::device_manager::get_instance();
// 内部对象已由 device_topo 创建,这里只按 object_name 查找
auto  bus_variant = manager.find_object(csr_object["object_name"].as<std::string>(),
                                        dev::object_type::bus);
auto  bus_ptr = std::get<std::shared_ptr<bus_base>>(bus_variant);
m_interface_i2c.m_bus_i2c = static_cast<dev::bus_i2c*>(bus_ptr.get());
m_interface_i2c.from_variant(csr_object);

from_variant 用 contains() 再 as<T>(),缺省走接口类构造函数里的默认值。.cpp 末尾:

cpp
MC_REFLECT(dev::I2c_object, ((m_interface_i2c, "bmc.dev.Bus.I2c")))
MC_REFLECT(dev::Interface_I2c)

接口类持有内部对象的 裸指针;生命周期由 device_manager 的 shared_ptr 管理。

6.5 实现 ABI ​

extern "C" 中实现 create_* / init_* / start_* / stop_*,可选 dump_*。

注意

禁止把 C++ 异常漏到 C 边界,否则 ABI 调用方进程可能崩溃。

cpp
device_driver_t i2c_device_driver = {
    .device_name = "I2c",  // 须与对象名前缀、工厂 key 一致
    .ctor  = create_i2c,
    .init  = init_i2c,
    .start = start_i2c,
    .stop  = stop_i2c
};

status_t register_device_driver(device_driver_t** device_driver, uint8_t* count)
{
    *device_driver = i2c_device_driver_list;
    *count = sizeof(i2c_device_driver_list) / sizeof(i2c_device_driver_list[0]);
    return STATUS_OK;  // create / init / start / stop 内须捕获异常后再返回
}

ctor 原型是 (void* service, const char* object_name),不是旧文档里的 create(config_json) / get_device_driver()。

6.6 注册工厂 ​

cpp
m_bus_factories["I2c"] = [](mc::mutable_dict& csr_data) -> std::shared_ptr<bus_base> {
    auto ptr = std::make_shared<bus_i2c>();
    if (ptr && ptr->init(csr_data)) {
        ptr->set_class_name("I2c");  // 供 topo_path 使用,须与工厂 key 一致
        return ptr;
    }
    return nullptr;  // init 失败则不注册
};

同名对象已存在时 create_bus 直接返回表中实例(跨 position 共享总线)。meson 与单测注册见构建与注册。

6.7 验证新增结果 ​

完成本章与第 9 章后,确认:

  1. 内部单测 tests/drivers/bus/{name}/ 的 read / write 通过。
  2. ABI init 后 find_object 能取到内部对象。
  3. 产物 lib{Name}.so 已拷贝到 drivers_install_dir。

7. 新增 Chip 驱动 ​

通用寄存器类芯片可只复用 Chip(complex_object + chip_base),不必新 SO。需要 MUX、分页、换算或专有属性时再新增类型。

7.1 确认是否复用 Chip ​

先确认不能复用 "Chip" / complex,再新增类型。所有 Chip 设备树对象都必须包含 Interface_Chip(drivers/chip/interface/)。专有属性再加 Interface_{Name}。meson.build 通过 chip_interface_sources 链入公共 i_chip.cpp。

7.2 准备文件清单 ​

新增如下路径:

text
gen/include/device_tree/interface/Chip/{Name}.h   # 仅专有属性时需要
drivers/internal/chip/chip_{name}/{name}.h  {name}.cpp  meson.build
drivers/chip/{name}/
  {name}.h  {name}.cpp  {name}_abi.cpp  meson.build
  interface/i_{name}.h  i_{name}.cpp                  # 有专有接口时

7.3 新增设备树接口 ​

基础属性在 bmc.dev.Chip,不要重复定义。PCA9545 只额外声明通道数:

cpp
namespace dev::gen {
class MC_API Pca9545 : public mc::engine::interface<Pca9545> {
public:
    MC_INTERFACE("bmc.dev.Chip.Pca9545")  // 专有接口名,基础属性仍走 bmc.dev.Chip
    property<uint8_t> ChannelNum;
};
}

EEPROM 没有 Chip/Eeprom.h。分页用基础属性 RwBlockSize / WriteInterval,自描述用 bmc.dev.Chip.SelfDescriptionRecord。

7.4 实现内部对象 ​

cpp
class chip_pca9545 : public chip_base {
public:
    bool    init(mc::mutable_dict& csr_object);
    int32_t open_channel(std::shared_ptr<bus_base> bus) override;
    int32_t close_channel(std::shared_ptr<bus_base> bus) override;
    int32_t chip_reset() override;
};

init 必须先调基类:

cpp
bool chip_pca9545::init(mc::mutable_dict& csr_object)
{
    // 先解析 Address、超时等基础字段
    if (!chip_base::init(csr_object)) {
        return false;
    }
    if (csr_object.contains("ChannelNum")) {
        m_channel_num = csr_object["ChannelNum"].as<uint8_t>();
    }
    return true;
}

通道切换按下游虚总线的 get_id() 写控制寄存器:

cpp
uint8_t write_reg = static_cast<uint8_t>(1 << bus->get_id());
block_write(0, {write_reg}, empty, error);   // OffsetWidth=0,无寄存器寻址

关闭通道写 0x00。open_channel 失败用 MC_THROW 或返回 HAS_ERR,不要继续 I/O。

按需覆盖的其它模式:

  • PCA9555:bit_write 先把 config 寄存器对应位配成输出,再 RMW 输出寄存器。
  • EEPROM:block_read / block_write 按页拆分,写完 sleep(WriteInterval)。
  • LM75:start() 里调 chip_init() 写温度阈值;覆盖 bit_read 做换算。

7.5 实现设备树对象 ​

cpp
class pca9545_object : public mc::engine::object<pca9545_object> {
public:
    MC_OBJECT(pca9545_object, "Pca9545", "${object_name}",
              (Interface_Pca9545)(Interface_Chip))
    bool init(mc::mutable_dict& csr_object, const mc::dict& connector);
    std::string dump();
    Interface_Pca9545 m_interface_pca9545;
    Interface_Chip    m_interface_chip;
};

init 中把同一个 chip_pca9545* 赋给各接口的裸指针,再分别 from_variant。反射:

cpp
MC_REFLECT(dev::pca9545_object,
           ((m_interface_chip, "bmc.dev.Chip"))
           ((m_interface_pca9545, "bmc.dev.Chip.Pca9545")))

需要硬件侧初始化时实现 start()(如 eeprom_object::start 解析 header,lm75_object 调 chip_init())。总线类 start / stop 多为空操作。

7.6 实现 ABI 与工厂 ​

device_name 用 "Pca9545"。芯片 SO 建议实现 dump(调用 chip_base::dump)。工厂:

cpp
m_chip_factories["Pca9545"] = [](mc::mutable_dict& csr_data) -> std::shared_ptr<chip_base> {
    auto ptr = std::make_shared<chip_pca9545>();
    if (ptr && ptr->init(csr_data)) {
        return ptr;
    }
    return nullptr;  // init 失败则不注册
};

通用 "Chip" 工厂在 SwitchSupported == true 时创建 chip_mcu_switch,否则 chip_base。meson 与单测注册见构建与注册。

7.7 验证新增结果 ​

完成本章与第 9 章后,确认:

  1. 内部 init 先调用了 chip_base::init。MUX 的 open_channel / close_channel 成对生效。
  2. ABI device_name、工厂 key、对象名前缀三者一致。
  3. 单测 tests/drivers/chip/{name}/ 通过,且 ABI init 能 find_object。

8. 对外接口、级联与错误处理 ​

8.1 Interface_Chip(上层真正调用的 API) ​

声明在 gen/include/device_tree/interface/Chip.h,实现 drivers/chip/interface/i_chip.cpp。

方法内部实现说明
BitIORead(offset, length, mask)bit_read读后按 mask 提取,length 仅 1 / 2 / 4。
BitIOWrite(...)bit_write按 mask 做读改写。
BlockIORead(offset, length)block_read连续块读。
BlockIOWrite(offset, buffer)block_write连续块写。
BlockIOWriteRead(indata, read_length)block_write_read,offset=0xffffffff扩展写读。
BlockIOComboWriteRead(...)先 block_write 再 block_read两次独立事务。

每个方法都是:

cpp
BusLock lock(*m_chip->get_host_bus());  // 锁根物理总线,不要锁 I2cMux
auto result = m_chip->block_read(...);
if (result != HAS_OK) {
    MC_THROW(mc::method_call_exception, "BlockIORead failed, ...");
}

BusLock(drivers/chip/bus_lock.h)构造时 bus_lock(),析构时 bus_unlock() 且不抛异常。

bmc.kepler.Chip.BlockIO 的 PluginRequest 走 plugin_request。Lua 插件设计见《Chip 器件插件机制详细设计说明书》。

Accessor 通过 CSR 的 Chip + Offset + Mask + Size 调 BitIO,不在芯片 SO 内实现轮询。

8.2 级联与 ChipMerge ​

chip_base::input / output 沿 left_chip 递归。首次 I/O 按 topo_path 注册到 ChipMergeRegistry:相同路径的后续实例成为 follower,I/O 转到 master。路径形如:

text
Hisport:0@Pca9545:e2w1_0-I2cMux:4@Eeprom:aew1_2

多份 CSR 描述同一颗物理芯片时会合并。调试时 dump() 统计记在 master 上。

8.3 错误处理 ​

  • 内部 I/O:返回 HAS_OK / 非 0;失败写入 m_last_access_error 与最多 10 条 m_error_history。
  • 设备树 / 接口:MC_THROW + elog;ABI 捕获后返回 STATUS_ERROR。
  • open_channel 失败:记录并返回,不继续 output。
  • 参数:空 CSR、空 in_buffer、非法 offset_width 在对应层抛 invalid_arg_exception / invalid_op_exception。
  • 日志:tlog / dlog / ilog / wlog / elog,格式 "${key}" + ("key", value)。

9. 构建与注册 ​

内部实现链接进 一个 libInternal.so。每种设备树类型一个 SO:libI2c.so、libPca9545.so。测试通过 dlopen 加载 SO,因此必须把产物拷到 drivers_install_dir。

必须采用 core 静态库 + link_whole 共享库,禁止在测试侧重复编译驱动源文件。

meson
# 源码只编译进 core,避免测试再编一份
xxx_core_lib = static_library(
    'XxxCore',
    xxx_sources,
    include_directories: xxx_include_dirs,
    dependencies: [internal_dep, libmcpp_deps],
    cpp_args: ['-Os', '-ffunction-sections', '-fdata-sections'],
    pic: true,
    install: false,
)

# 空 sources + link_whole,生成运行时 SO
xxx_lib = shared_library(
    'Xxx',
    [],
    dependencies: [internal_dep, libmcpp_deps],
    link_whole: [xxx_core_lib],
    link_args: common_link_args,
    name_prefix: 'lib',
    name_suffix: 'so',
    install: true,
    install_dir: drivers_install_dir,
)

# 单测链接 core,不要再列驱动 .cpp
xxx_test_dep = declare_dependency(
    link_whole: xxx_core_lib,
    dependencies: [internal_dep, libmcpp_deps],
)

测试拷贝使用 stamp 文件。cp 建议 --remove-destination。现行部分 meson 仍写 cp -f,新增请按本节模板。

还需要按顺序完成:

  1. 在 drivers/internal/bus/meson.build 或 chip/meson.build 增加 subdir('bus_xxx'),并加入 bus_sources / chip_sources。
  2. 在 drivers/bus/meson.build 或 drivers/chip/meson.build 增加 subdir('xxx')。
  3. 在 manager.h include 头文件,在 init_factory_maps() 注册,总线调用 set_class_name。

单测放 tests/drivers/bus/{name}/、tests/drivers/chip/{name}/、tests/drivers/internal/。ABI 测试 dlopen 对应 libXxx.so,test() 声明 depends: all_copy_targets 并设置 LD_LIBRARY_PATH。

9.1 验证构建结果 ​

构建完成后确认:ninja 能生成 libInternal.so 与 libXxx.so;测试目录能加载到拷贝后的 SO;LD_LIBRARY_PATH 指向 drivers_install_dir。


10. 检查清单与常见问题 ​

10.1 新增 Bus ​

  • [ ] 已新增 gen/include/device_tree/interface/Bus/{Name}.h,并声明 MC_INTERFACE("bmc.dev.Bus.{Name}")。
  • [ ] 已在 drivers/internal/bus/bus_{name}/ 实现纯虚函数(虚总线除 get_id 外应抛错)。
  • [ ] 已在 drivers/bus/{name}/ 实现设备树对象、from_variant、ABI、MC_REFLECT。
  • [ ] meson 已在 internal 与 drivers 两处增加 subdir,并采用 core + link_whole 与 copy stamp。
  • [ ] 已在 manager.h / manager.cpp 注册工厂,调用 set_class_name,且 key 与 device_name、对象名前缀一致。
  • [ ] CSR 的 Objects 与拓扑同名,物理总线属性与接口一致。
  • [ ] 内部 read / write 单测通过,ABI init 能 find_object。

10.2 新增 Chip ​

  • [ ] 已确认不能复用 "Chip" / complex。
  • [ ] 仅在有专有属性时新增 Chip/{Name}.h,并且挂了 Interface_Chip。
  • [ ] 内部 init 先调 chip_base::init。MUX 已覆盖 open_channel / close_channel。
  • [ ] 设备树 init 只 find_object。需要硬件配置时在 start() 调 chip_init。
  • [ ] ABI device_name、工厂 key、对象名前缀一致,并建议实现 dump。
  • [ ] meson 与 manager 注册与第 9 章一致。
  • [ ] CSR 的 Address 为 8-bit 写地址,OffsetWidth 与手册一致(PCA9545 常为 0,EEPROM 常为 2)。

10.3 常见问题 ​

现象原因处理
Unsupported chip/bus type 或 create 返回 nullptr未注册工厂,或对象名前缀与 key 不一致。检查 extract_device_type 与 m_*_factories。
bus/chip not found拓扑未 create_*,或设备树 init 早于内部对象。检查 CSR 拓扑与 object_name。
段错误 / 空 host_bus未接线或锁了 I2cMux。确认 set_host_bus;锁 get_host_bus()。
读写到错误通道open_channel 失败仍继续,或 ChannelId 错。失败即返回;核对 I2cMux 的 ChannelId。
配置不生效from_variant 字段名与 CSR 不一致;或改了 property 但 CSR2 未回写内部对象。对齐字段;Address 等以内部 init(csr) 为准。
链接 / 测试找不到 SOmeson 未 copy,或测试未依赖 all_copy_targets。按第 9 章补 copy 与 LD_LIBRARY_PATH。
ABI 进程崩溃异常穿过 extern "C"。全部 catch 并返回状态码。

调试一次失败的 BlockIO,按顺序查:对象名能否抽出正确类型 → device_manager 是否已有内部对象 → host_bus / left_bus / left_chip 是否接上 → open_channel 是否成功 → addr / offset_width 是否与手册一致。