qemu添加CPLD模拟

单板上使用了CPLD,所以想在qemu中增加CPLD的器件模拟。我是想把CPLD作为单板的外围i2c器件进行模拟的。因此照着葫芦画瓢,我找了aspeed上的EEPROM器件(源码在eeprom_at4.c)。

  1. 定义类型名
#define TYPE_ASPEED_CPLD "aspeed.cpld"
OBJECT_DECLARE_TYPE(AspeedCPLDState, AspeedCPLDClass, ASPEED_CPLD)

OBJECT_DECLARE_TYPE宏是声明QOM类型对应的结构体和辅助宏。

  1. 定义实例结构体
struct AspeedCPLDState
{
    /*< private >*/
    I2CSlave parent_obj;
    /*< public >*/
    uint8_t active_reg;    // 记录当前操作的寄存器地址
    bool addr_byte_received; // 标志位:是否已经接收到了地址字节
    uint8_t regs[0x100];

};

结构体的名称一定要和声明的QOM类型相同。我们是想模拟i2c设备,i2c设备的‘’私有变量‘是定义在I2CSlave结构体中,所以我们定义的结构体中需要包含它。CPLD一般都是提供寄存器给BMC访问,qemu中也是主要模拟寄存器的读写。regs就是寄存器模拟的载体

  1. 定义CPLD器件读写的事件
struct I2CSlaveClass {
    DeviceClass parent_class;

    /* Master to slave. Returns non-zero for a NAK, 0 for success. */
    int (*send)(I2CSlave *s, uint8_t data);

    /*
     * Slave to master.  This cannot fail, the device should always
     * return something here.
     */
    uint8_t (*recv)(I2CSlave *s);

    /*
     * Notify the slave of a bus state change.  For start event,
     * returns non-zero to NAK an operation.  For other events the
     * return code is not used and should be zero.
     */
    int (*event)(I2CSlave *s, enum i2c_event event);
    
    ....
};

I2CSlaveClass定义了send、recv和event函数。当上层调用i2c驱动读写设备时,就会调用到我们实现的回调函数中。而event不是单纯i2c设备处理读写的,而对于i2c总线上事务变化时进行处理的。例如at24c_eeprom_event 里,它做了几件典型的事:

  • I2C_START_SEND / I2C_FINISH 时把 haveaddr 清零
  • 在 START_RECV / FINISH 时,如果 EEPROM 内容改过,就把数据刷回 backing file
  • 收到 I2C_NACK 时什么都不做

因为我模拟的CPLD只涉及到i2c的读写,所以我主要实现了send和recv函数:

static int aspeed_cpld_send(I2CSlave *i2c, uint8_t data) {
    AspeedCPLDState *s = ASPEED_CPLD(i2c);

    if (!s->addr_byte_received) {
        /* 接收到的第一个字节:设为当前操作的寄存器 */
        s->active_reg = data;
        s->addr_byte_received = true;
    } else {
        /* 后续字节:直接写入该寄存器 */
        /* 这里你可以直接调用你原有的 write 逻辑函数 */
        s->regs[s->active_reg] = data;
        
        // 如果你希望支持连续写而不自动增加指针,这里就保持 active_reg 不变
        // 如果支持连续写,通常还是会 active_reg++
    }
    return 0;
}

static uint8_t aspeed_cpld_recv(I2CSlave *i2c) {
    AspeedCPLDState *s = ASPEED_CPLD(i2c);
    
    /* 直接根据当前 active_reg 返回值 */
    uint8_t val = s->regs[s->active_reg];
    
    /* 如果是标准的 I2C 读取,通常读取后不自动加指针,
       除非主设备继续 ACK 读取下一个字节 */
    return val;
}
  1. 注册设备
static const TypeInfo aspeed_cpld_info = {
    .name = TYPE_ASPEED_CPLD,
    .parent = TYPE_I2C_SLAVE,
    .instance_size = sizeof(AspeedCPLDState),
    .class_init = aspeed_cpld_class_init,
};

static void aspeed_cpld_register_types(void)
{
    type_register_static(&aspeed_cpld_info);
}
type_init(aspeed_cpld_register_types)
  1. 编译

因为模拟CPLD的代码是在hw/misc文件夹下,所以我们要把对应的源文件添加到编译当中,需要修改hw/misc文件下的meson.build文件,把新增源文件添加进来:

system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files(
  'aspeed_hace.c',
  'aspeed_lpc.c',
   ....
  'aspeed_cpld.c'))

然后再重新进行make即可。

  1. 使用

因为我模拟的CPLD是挂在BMC芯片的i2c-1(从i2c-0开始计算)下的:

./qemu-system-arm -M ast2600-evb \
    -device aspeed.cpld,bus=aspeed.i2c.bus.1,address=0x20 \
    ...
    -s -S
posted @ 2026-03-31 20:25  cockpunctual  阅读(1)  评论(0)    收藏  举报