疑似 i2c 主从切换不够快,造成错过网卡回复报文

问题描述

我们在验证 I2C 主从功能,基于 SDK ioctl 接口进行简单主从切换验证

1、BMC 作为主机,向对端设备发送 ipmb 报文

2、BMC 切换 i2c 为从模式(地址 0x20)

3、轮询缓冲区,检查是否接收到报文,始终未收到数据(对端会作为主机写入 BMC 的 0x20 地址)

但是,如果首先切换 BMC 为从机,然后去对端设备上,使用调试命令向 BMC 0x20 地址写入数据,BMC 可以在缓冲区中正常接收到数据,说明 BMC 侧的从机功能无问题:

在对端向主 BMC 写入数据:

读取缓冲区:

当前高度怀疑,在先作为主机 + 后切换为从机的场景下,主从切换不够快,对端设备回复时 BMC 未能作为从机工作,进而造成问题现象

使用示波器抓取链路报文证实了这一点

首先是 BMC 向对端 0x30 地址发出 I2C 请求

约 9ms 后,对端向 BMC 0x20 地址发出了 I2C 请求,但是连续六次报文重发,BMC 始终没有 ACK,从机功能未正常工作

报文分析:

这是一组 I²C 波形,承载的是 IPMB 协议(基于 I²C 的 IPMI 通信)。第一张图里 BMC 发出了一条 Get Device ID 请求,**每个字节都有 ACK,包括最后一个**。第二张图是设备回传响应,但**地址字节被 NACK 了**,设备一直在重试。

黄色 CH1 是 SCL,紫色 CH3 是 SDA。我先从截图里按颜色提取出两条走线,再在每个 SCL 高电平处采样 SDA 来解码。屏幕上一格约 100 个像素,对应 100 µs,所以大约 1 µs 一个像素。SCL 每个周期约 10 µs,即 100 kHz 标准模式。

## 图 1:BMC 的 Get Device ID 请求,全部 ACK

一次完整传输:START → 7 个字节(每个 8 位数据 + 1 位 ACK,共 63 个时钟)→ STOP。

| # | 字节 | 第 9 位 | IPMB 含义 |
|---|---|---|---|
| 1 | `0x30` | ACK | 目标地址 rsSA:7 位地址 0x18,写操作 |
| 2 | `0x18` | ACK | NetFn = 0x06(App 请求),rsLUN = 0 |
| 3 | `0xB8` | ACK | 校验和 1:−(0x30+0x18) = 0xB8,正确 |
| 4 | `0x20` | ACK | 请求方地址 rqSA = 0x20(BMC 的标准地址) |
| 5 | `0x04` | ACK | rqSeq = 1,rqLUN = 0 |
| 6 | `0x01` | ACK | 命令 Cmd = 0x01,**Get Device ID** |
| 7 | `0xDB` | **ACK** | 校验和 2:−(0x20+0x04+0x01) = 0xDB,正确 |

- 最后一个字节 0xDB 的第 9 个时钟里,SDA 被从机拉低,说明**有 ACK**。随后 SCL 为高时 SDA 上升,这是正常的 STOP。
- 两个校验和都符合 IPMB 的补码校验规则,可以确认解码正确,协议判断也成立。
- 第 3 个字节的第一位(1)处,SDA 在 SCL 上升沿前约 2–3 µs 才升到高电平,因为 SDA 上升沿比较缓。对 100 kHz 来说仍满足建立时间要求,所以判为 1 是可靠的。

## 图 2:响应方回传,地址被 NACK(约 10 ms 后)

这张图里有 6 次完全相同的短传输,每次间隔约 130 µs:

```
START → 0x20 (0010 0000) → 第 9 位 SDA = 高 → NACK → STOP
```

- 0x20 就是图 1 里的请求方地址 rqSA,写操作。按 IPMB 的规则,响应方要自己作为主机,把响应写回请求方。所以这应该是 0x30 这个设备在发 Get Device ID 的响应。
- 每次第 9 个时钟 SDA 都是高电平,**没有任何设备 ACK 0x20**。设备只能发 STOP,然后不断重试。

## 结论和排查方向

- **请求方向正常**:0x30 这个设备收到了完整的请求,每个字节都 ACK 了。
- **响应方向失败**:BMC(0x20)在这时没有以从机身份响应自己的地址。常见原因有:
  1. BMC 一侧的 I²C 控制器发完请求后没有切到从机模式,或者从机地址没配成 0x20(7 位 0x10)。
  2. BMC 的从机接收缓冲区还没准备好,或者驱动没开启 slave 功能。在 Linux 上可以查一下 `ipmb-dev-int` 或 `ssif`,以及 slave 设备有没有注册。
  3. 先确认 0x20 确实是这条总线上 BMC 的地址。

另外,第二张图上示波器自动测量显示「频率 25 kHz / 周期 40 µs」,和实际 SCL 周期(约 10 µs)对不上,估计是测量门限或统计区间的问题,不影响上面的解码。

环境信息

26.6 版本

使用的主从切换接口是:

    I2C_INIT_S init_s = { .drv_id = BUS_ID, .speed = I2C_SPEED, .is_slave = 1,
                          .address = SLAVE_ADDR, .sda_hold_ratio = SDA_HOLD_DFLT };
    if (ioctl(fd, I2C_CMD_INIT, &init_s) < 0) {
        logprint("INIT slave failed: %s", strerror(errno));
        ret = 1; goto out;
    }

使用的缓冲区读取接口是:

        I2C_SLAVE_CACHE_RDWR_S rd = { .drv_id = BUS_ID, .offset = 0,
            .size = REPLY_MAX_LEN, .timeout = SLAVE_RD_TIMEOUT, .buff = reply };
        logprint("  poll %d/%d ...", attempt + 1, POLL_MAX);
        if (ioctl(fd, I2C_CMD_SLAVE_CACHE_RD, &rd) < 0) {
            logprint("  SLAVE_CACHE_RD failed: %s", strerror(errno));

期望结果

能及时完成主从切换,并收到对端发来的回复


openubmc smbus主从切换模式没有效果 - 交流互助区 - openUBMC 论坛