总线芯片驱动开发指南
概述
总线芯片驱动是南向硬件访问的底座,把 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. 范围与术语
总线芯片驱动不描述一块完整板卡。上层网卡、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/ 目录。 |
| ABI | include/devmon/driver_abi.h 的 C 接口。每个设备树 SO 导出 register_device_driver。 |
device_manager | 内部对象单例工厂,按 class_name 创建、按 object_name 索引。 |
device_topo | devmon 侧拓扑引擎:解析 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 双层对象
每个总线 / 芯片都有两套对象,职责不能混:
设备树对象(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_3 | I2c | bus_i2c | libI2c.so |
Hisport_0 | Hisport | bus_hisport | libHisport.so |
I2cMux_Pca9545_PCA9545_4 | I2cMux | bus_i2c_mux | libI2cMux.so |
Pca9545_PCA9545 | Pca9545 | chip_pca9545 | libPca9545.so |
Chip_MCU1 | Chip | chip_base 或 chip_mcu_switch | libChip.so |
Eeprom_IEU | Eeprom | chip_eeprom | libEeprom.so |
device_name、工厂 key、对象名前缀三者必须一致(区分大小写)。写成 PCA9545_0 会去找工厂 "PCA9545",创建失败。
2.3 BlockIO 调用路径
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. 目录与关键文件
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。 |
optype | 0 读 / 1 写。 |
protocol_flag | I2cOverHisport 上 == 2 走 MCTP;I2C 上 == 0x02 走 SMBus。 |
cs | SPI over Hisport 片选。 |
返回值:HAS_OK(0)成功,HAS_ERR(-1)或底层驱动错误码表示失败。
4.2 bus_base
路径:drivers/internal/bus/bus_base/bus.h。
物理总线必须覆盖:
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):
[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_channel | MUX 必须成对覆盖。 |
| 访问 | bit_* / block_* / block_write_read / combo_block_write_read | 分页、换算、RMW。 |
| 透传 | forward_by_front_chip / get_class_name | SMC 等前级。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 最小结构
{
"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 准备文件清单
新增如下路径:
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)。
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。
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 实现设备树对象
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 只绑定内部对象:
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 末尾:
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 调用方进程可能崩溃。
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 注册工厂
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 章后,确认:
- 内部单测
tests/drivers/bus/{name}/的read/write通过。 - ABI
init后find_object能取到内部对象。 - 产物
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 准备文件清单
新增如下路径:
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 只额外声明通道数:
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 实现内部对象
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 必须先调基类:
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() 写控制寄存器:
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 实现设备树对象
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。反射:
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)。工厂:
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 章后,确认:
- 内部
init先调用了chip_base::init。MUX 的open_channel/close_channel成对生效。 - ABI
device_name、工厂 key、对象名前缀三者一致。 - 单测
tests/drivers/chip/{name}/通过,且 ABIinit能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 | 两次独立事务。 |
每个方法都是:
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。路径形如:
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 共享库,禁止在测试侧重复编译驱动源文件。
# 源码只编译进 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,新增请按本节模板。
还需要按顺序完成:
- 在
drivers/internal/bus/meson.build或chip/meson.build增加subdir('bus_xxx'),并加入bus_sources/chip_sources。 - 在
drivers/bus/meson.build或drivers/chip/meson.build增加subdir('xxx')。 - 在
manager.hinclude 头文件,在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单测通过,ABIinit能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) 为准。 |
| 链接 / 测试找不到 SO | meson 未 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 是否与手册一致。