You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MS DOS下读取RAM SPD EEPROM失败,求代码问题排查与解决

在MS DOS下读取RAM SPD EEPROM的问题排查与修正

你的16位DOS应用读取SPD EEPROM得到垃圾数据,核心问题出在SMBus基址获取方式错误、操作流程不符合SMBus协议、状态寄存器检查逻辑错误这几个方面,以下是具体分析和修正方案:

一、代码中的关键问题

1. 硬编码SMBus控制器PCI地址,兼容性极差

你直接使用0x8000FB20作为PCI配置空间地址,这是假设SMBus控制器固定在总线0、设备23(0x17)、功能0的偏移0x20位置,但不同主板的SMBus控制器PCI位置差异极大,硬编码会导致绝大多数主板无法正确获取基址。

另外,PCI基址寄存器(BAR)是32位的,你仅保留低16位的操作虽然在实模式下勉强可行,但没有先验证设备是否存在,直接读取的地址可能无效。

2. SMBus读操作流程不符合协议规范

SPD EEPROM遵循SMBus协议,单个字节读取的标准流程是:

等待总线空闲 → 发送起始条件 → 发送设备写地址(如0xA0)并等待ACK → 发送读取偏移地址并等待ACK → 发送重复起始条件 → 发送设备读地址(如0xA1)并等待ACK → 读取数据字节 → 发送NACK → 发送停止条件

你的代码直接硬写端口值(如outportb(base+0x02, 0x48)),没有处理ACK校验、起始/停止条件,完全依赖固定延时,这种写法仅适用于特定型号控制器的私有命令,不具备通用性,且极易出错。

3. WaitReady函数检查错误的状态位

不同SMBus控制器的状态寄存器定义不同,以常见的Intel ICH系列为例,状态寄存器(偏移0x00)的BUSY位是bit6(0x40),而你检查的是bit0(0x01,通常是中断标志位),导致等待的不是总线空闲状态,操作可能在总线忙时执行,直接产生垃圾数据。

4. 固定延时不合理且不可靠

你用int 15h ah=86h延时20ms,远超过SMBus单字节操作所需的时间(100kHz总线速度下,单字节操作仅需约100μs),且固定延时无法适配不同控制器的操作速度,可能导致操作未完成就读取数据,或浪费不必要的时间。

二、修正方案

1. 动态枚举SMBus控制器基址

遍历PCI总线,找到设备类为0x0C(串行总线控制器)、子类为0x05(SMBus控制器)的设备,读取其BAR0寄存器获取IO基址:

uint16_t FindSMBusBase()
{
    for (int bus = 0; bus < 256; bus++) {
        for (int dev = 0; dev < 32; dev++) {
            for (int func = 0; func < 8; func++) {
                uint32_t addr = 0x80000000 | (bus << 16) | (dev << 11) | (func << 8) | 0x00;
                uint16_t vendorDevId;
                __asm {
                    mov eax, addr
                    mov dx, 0xCF8
                    out dx, eax
                    mov dx, 0xCFC
                    in ax, dx
                    mov [vendorDevId], ax
                }
                if (vendorDevId == 0xFFFF) continue; // 设备不存在

                uint16_t classSubclass;
                addr = 0x80000000 | (bus << 16) | (dev << 11) | (func << 8) | 0x08;
                __asm {
                    mov eax, addr
                    mov dx, 0xCF8
                    out dx, eax
                    mov dx, 0xCFC
                    in ax, dx
                    mov [classSubclass], ax
                }
                BYTE subclass = (classSubclass >> 8) & 0xFF;
                BYTE class = classSubclass & 0xFF;
                if (class == 0x0C && subclass == 0x05) {
                    uint16_t bar0;
                    addr = 0x80000000 | (bus << 16) | (dev << 11) | (func << 8) | 0x10;
                    __asm {
                        mov eax, addr
                        mov dx, 0xCF8
                        out dx, eax
                        mov dx, 0xCFC
                        in ax, dx
                        mov [bar0], ax
                    }
                    return bar0 & 0xFFFE; // 清除IO空间标志位(最低位)
                }
            }
        }
    }
    return 0; // 未找到SMBus控制器
}

2. 修正WaitReady函数的状态检查

以Intel ICH系列为例,修改为检查BUSY位:

bool WaitReady(WORD base)
{
    BYTE status;
    do {
        __asm {
            mov dx, base
            in al, dx
            mov [status], al
        }
    } while ((status & 0x40) != 0); // 等待BUSY位(bit6)清零
    return true;
}

3. 遵循SMBus协议实现读操作

以下是基于Intel ICH系列控制器的标准单字节读取实现(包含ACK校验):

bool ReadSPD(WORD base, BYTE offset, BYTE devAddr, BYTE *data)
{
    if (!WaitReady(base)) return false;

    // 发送起始条件+写设备地址
    outportb(base + 0x02, 0x48); // START + WRITE
    outportb(base + 0x03, devAddr); // 设备写地址(如0xA0)
    WaitReady(base);
    BYTE status = inportb(base);
    if ((status & 0x02) == 0) return false; // 未收到ACK,操作失败

    // 发送偏移地址
    outportb(base + 0x03, offset);
    WaitReady(base);
    status = inportb(base);
    if ((status & 0x02) == 0) return false;

    // 发送重复起始条件+读设备地址
    outportb(base + 0x02, 0x4C); // START + READ
    outportb(base + 0x03, devAddr | 0x01); // 设备读地址(如0xA1)
    WaitReady(base);
    status = inportb(base);
    if ((status & 0x02) == 0) return false;

    // 读取数据+发送NACK+停止条件
    outportb(base + 0x02, 0x0C); // READ + NACK + STOP
    WaitReady(base);
    *data = inportb(base + 0x05);

    return true;
}

4. 修改main函数逻辑

使用动态枚举的基址,增加错误处理:

void main()
{
    uint16_t smbusBase = FindSMBusBase();
    if (smbusBase == 0) {
        print("SMBus controller not found!\n");
        return;
    }

    BYTE value;
    for (int i = 0; i < 256; i++) {
        if (ReadSPD(smbusBase, i, 0xA0, &value)) {
            print("%02X ", value);
            if ((i + 1) % 16 == 0) print("\n");
        } else {
            print("Error reading offset %02X\n", i);
        }
    }
}

三、额外注意事项

  • 不同品牌的SMBus控制器(如AMD、VIA)寄存器定义可能不同,需根据对应主板的芯片组手册调整操作逻辑;
  • 实模式下需确保编译器支持32位汇编指令(如使用Turbo C++ 3.0及以上版本,并开启386支持);
  • SPD EEPROM的设备地址可能为0xA0或0xA2,若读取失败可尝试切换地址。

内容的提问来源于stack exchange,提问作者photonepoch421

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 11:58:10