在QEMU中定制AST2600设备:从Machine定义到硬件模拟实战
1. 理解AST2600与QEMU模拟基础
AST2600是Aspeed公司推出的高性能SoC芯片,广泛应用于服务器管理、工业控制等领域。它集成了双核Cortex-A7处理器、丰富的外设接口和硬件加速引擎。在实际开发中,我们经常需要在QEMU中模拟AST2600的特定硬件配置,以便进行固件开发和测试。
QEMU作为开源的硬件模拟器,允许我们创建虚拟的Machine类型来模拟特定硬件平台。与物理开发板相比,QEMU模拟具有以下优势:
- 快速迭代:无需等待硬件,随时修改配置
- 调试方便:可以设置断点、单步执行
- 成本低廉:不需要购买多套硬件设备
在开始定制之前,建议先熟悉QEMU的基本架构。QEMU通过Machine类型来描述整个硬件平台,每个Machine包含CPU、内存、外设等组件。对于AST2600,QEMU已经提供了基础的ast2600-evb Machine类型,我们可以基于它进行扩展。
2. 创建自定义Machine类型
2.1 修改aspeed.c文件
首先需要在QEMU源码的hw/arm/aspeed.c文件中添加新的Machine类型。这里我们以创建一个名为"pf12"的Machine为例:
static const TypeInfo aspeed_machine_types[] = { // ...其他Machine定义... { .name = MACHINE_TYPE_NAME("ast2600-evb"), .parent = TYPE_ASPEED_MACHINE, .class_init = aspeed_machine_ast2600_evb_class_init, }, { .name = MACHINE_TYPE_NAME("pf12"), .parent = TYPE_ASPEED_MACHINE, .class_init = aspeed_machine_pf12_class_init, }, // ...其他Machine定义... };这段代码在QEMU的类型系统中注册了一个新的Machine类型"pf12",它继承自ASPEED_MACHINE基类,并使用aspeed_machine_pf12_class_init函数进行初始化。
2.2 实现Machine类初始化函数
接下来需要实现aspeed_machine_pf12_class_init函数,这个函数定义了Machine的各种属性:
static void aspeed_machine_pf12_class_init(ObjectClass *oc, void *data) { MachineClass *mc = MACHINE_CLASS(oc); AspeedMachineClass *amc = ASPEED_MACHINE_CLASS(oc); mc->desc = "Aspeed AST2600 EVB (Cortex-A7)"; amc->soc_name = "ast2600-a3"; amc->hw_strap1 = AST2600_EVB_HW_STRAP1; amc->hw_strap2 = AST2600_EVB_HW_STRAP2|0xC800; // 启用ABR功能 amc->fmc_model = "mx66u51235f"; amc->spi_model = "mx66u51235f"; amc->num_cs = 2; amc->macs_mask = ASPEED_MAC0_ON | ASPEED_MAC1_ON | ASPEED_MAC2_ON | ASPEED_MAC3_ON; amc->i2c_init = pf12_i2c_init; // 自定义I2C设备初始化 mc->default_ram_size = 1 * GiB; mc->default_cpus = mc->min_cpus = mc->max_cpus = aspeed_soc_num_cpus(amc->soc_name); mc->reset = pf12_reset; // 自定义复位函数 }这个函数设置了Machine的各种参数,包括SoC型号、硬件strap值、Flash型号、MAC地址启用情况等。最重要的是指定了自定义的I2C初始化函数pf12_i2c_init和复位函数pf12_reset。
3. 定制I2C设备
3.1 I2C设备初始化函数实现
AST2600支持多个I2C总线,我们需要根据实际硬件配置添加相应的I2C设备。以下是pf12_i2c_init函数的实现示例:
static void pf12_i2c_init(AspeedMachineState *bmc) { AspeedSoCState *soc = &bmc->soc; DeviceState *dev; // 添加温度传感器 i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 1), TYPE_TMP105, 0x4d); // 初始化EEPROM设备 uint8_t *eeprom_buf2 = g_malloc0(8 * 1024); uint8_t buf2[] = {0x08, 0xbf, 0xb8, 0x0c, 0xbe, 0xac, 0x00, 0xfe}; memcpy(eeprom_buf2, buf2, sizeof(buf2)); smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 6), 0x56, eeprom_buf2); // 初始化FRU EEPROM uint8_t *eeprom_fru = g_malloc0(8 * 256); uint8_t fru[] = {0x01, 0x00, 0x01, 0x05, 0x0f, 0x00, 0x00, 0xea}; memcpy(eeprom_fru, fru, sizeof(fru)); smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 6), 0x54, eeprom_fru); // 添加RTC设备 i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 2), TYPE_DS1338, 0x10); // 添加更多温度传感器 i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 7), TYPE_TMP105, 0x49); i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 8), TYPE_TMP105, 0x4B); }这个函数主要做了以下几件事:
- 在I2C总线1上添加了一个TMP105温度传感器
- 初始化了两个EEPROM设备,分别存储MAC地址和FRU信息
- 在I2C总线2上添加了DS1338 RTC芯片
- 在其他I2C总线上添加了更多温度传感器
3.2 设备数据初始化技巧
在实际项目中,EEPROM等设备通常需要预置数据。我们可以通过以下方式处理:
- 静态数据初始化:像上面示例那样,直接在代码中定义静态数据
- 从文件加载:可以读取外部文件来初始化设备数据
- 运行时动态修改:通过QEMU monitor或QMP接口在运行时修改设备数据
对于复杂的设备数据,建议使用外部文件加载的方式,这样可以在不重新编译QEMU的情况下更新设备数据。
4. 实现自定义复位逻辑
4.1 复位函数实现
AST2600的复位逻辑可能需要根据具体硬件进行调整。以下是pf12_reset函数的实现示例:
static void pf12_reset(MachineState *state, ShutdownCause reason) { AspeedMachineState *bmc = ASPEED_MACHINE(state); AspeedGPIOState *gpio = &bmc->soc.gpio; // 调用默认复位函数 qemu_devices_reset(reason); /* 设置板卡ID */ object_property_set_bool(OBJECT(gpio), "gpioV4", true, &error_fatal); object_property_set_bool(OBJECT(gpio), "gpioV5", true, &error_fatal); object_property_set_bool(OBJECT(gpio), "gpioV6", true, &error_fatal); object_property_set_bool(OBJECT(gpio), "gpioV7", false, &error_fatal); /* 设置槽位存在信号(低电平有效) */ object_property_set_bool(OBJECT(gpio), "gpioH4", false, &error_fatal); object_property_set_bool(OBJECT(gpio), "gpioH5", true, &error_fatal); object_property_set_bool(OBJECT(gpio), "gpioH6", true, &error_fatal); object_property_set_bool(OBJECT(gpio), "gpioH7", true, &error_fatal); }这个复位函数主要做了两件事:
- 调用默认的复位函数
qemu_devices_reset完成基础复位 - 设置特定的GPIO状态,模拟实际硬件上电时的GPIO配置
4.2 复位时的注意事项
在实现自定义复位逻辑时,需要注意以下几点:
- 复位顺序:确保先调用默认复位函数,再执行自定义逻辑
- 错误处理:使用
error_fatal确保配置失败时能够及时发现 - 状态一致性:确保复位后的状态与实际硬件一致
- 调试输出:可以添加调试打印,方便排查复位相关问题
5. 运行时参数配置
5.1 Machine参数支持
AST2600 Machine支持多种运行时参数配置,这为测试不同硬件配置提供了便利。相关代码通常在aspeed_machine_class_props_init函数中实现:
static void aspeed_machine_class_props_init(ObjectClass *oc) { object_class_property_add_bool(oc, "execute-in-place", aspeed_get_mmio_exec, aspeed_set_mmio_exec); object_class_property_set_description(oc, "execute-in-place", "boot directly from CE0 flash device"); object_class_property_add_str(oc, "bmc-console", aspeed_get_bmc_console, aspeed_set_bmc_console); object_class_property_set_description(oc, "bmc-console", "Change the default UART to \"uartX\""); object_class_property_add_str(oc, "fmc-model", aspeed_get_fmc_model, aspeed_set_fmc_model); object_class_property_set_description(oc, "fmc-model", "Change the FMC Flash model"); object_class_property_add_str(oc, "spi-model", aspeed_get_spi_model, aspeed_set_spi_model); object_class_property_set_description(oc, "spi-model", "Change the SPI Flash model"); }这些参数允许我们在启动QEMU时动态配置:
execute-in-place:设置是否从Flash直接执行bmc-console:指定控制台使用的UARTfmc-model和spi-model:指定Flash芯片型号
5.2 使用示例
启动QEMU时可以这样使用这些参数:
qemu-system-arm -M ast2600-evb \ -drive file=./flash.img,format=raw,if=mtd \ -nographic \ -serial mon:stdio \ -global driver=ast2600.evb,property=execute-in-place,value=on \ -global driver=ast2600.evb,property=fmc-model,value="mx66u51235f"这种灵活的配置方式使得我们可以轻松测试不同的硬件配置,而无需重新编译QEMU。
6. 调试技巧与常见问题
在实际开发中,调试是不可避免的。以下是一些实用的调试技巧:
- QEMU监控器:使用
-monitor stdio参数启动QEMU,可以进入监控器查看设备状态 - GDB调试:使用
-s -S参数启动QEMU,然后通过GDB连接进行调试 - 设备树查看:在U-Boot中使用
fdt print命令查看设备树信息 - 日志输出:在关键函数中添加
printf或使用QEMU的日志系统
常见问题包括:
- 设备未正确初始化:检查设备初始化函数是否被调用
- 中断不工作:检查中断控制器配置和中断号是否正确
- 内存映射错误:检查设备的内存区域是否与其他设备冲突
- 时序问题:适当添加延迟或调整设备响应时间
在开发过程中,建议逐步添加设备功能,每添加一个功能就进行测试,这样可以快速定位问题。
