qemu添加CPLD模拟
单板上使用了CPLD,所以想在qemu中增加CPLD的器件模拟。我是想把CPLD作为单板的外围i2c器件进行模拟的。因此照着葫芦画瓢,我找了aspeed上的EEPROM器件(源码在eeprom_at4.c)。
- 定义类型名
#define TYPE_ASPEED_CPLD "aspeed.cpld"
OBJECT_DECLARE_TYPE(AspeedCPLDState, AspeedCPLDClass, ASPEED_CPLD)
OBJECT_DECLARE_TYPE宏是声明QOM类型对应的结构体和辅助宏。
- 定义实例结构体
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就是寄存器模拟的载体。
- 定义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;
}
- 注册设备
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)
- 编译
因为模拟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即可。
- 使用
因为我模拟的CPLD是挂在BMC芯片的i2c-1(从i2c-0开始计算)下的:
./qemu-system-arm -M ast2600-evb \
-device aspeed.cpld,bus=aspeed.i2c.bus.1,address=0x20 \
...
-s -S

浙公网安备 33010602011771号