QEMU 自定义外设模板#
此模板为开发 QEMU 中的外设提供了指南。它包含了详细的解释和代码注释。
1. 引入必要的头文件#
这些头文件是大多数 QEMU 设备模型所必需的。
#include "qemu/osdep.h" // OS 兼容层
#include "hw/irq.h" // 中断请求 (IRQ) 处理
#include "hw/qdev-properties.h" // QEMU 设备属性管理
#include "migration/vmstate.h" // 虚拟机迁移支持
#include "qemu/log.h" // QEMU 日志功能
#include "qemu/module.h" // QEMU 模块初始化c2. 定义设备状态结构体#
定义一个结构体来保存自定义设备的内部状态。这包括设备所需的寄存器和状态标志。
typedef struct CustomDeviceState {
SysBusDevice parent_obj; // 系统总线设备的基础结构
uint32_t reg1; // 示例寄存器 1
uint32_t reg2; // 示例寄存器 2
uint64_t tick_offset; // 时间或时钟偏移
bool enable; // 设备启用标志
QEMUTimer *timer; // 定时器,用于计划事件
qemu_irq irq; // 中断请求
MemoryRegion iomem; // 内存映射 I/O 区域
} CustomDeviceState;c3. 设备初始化与重置#
- reset(): 重置设备的内部状态和寄存器。
- init(): 初始化设备,包括内存和中断连接。
static void custom_device_reset(DeviceState *dev)
{
CustomDeviceState *s = CUSTOM_DEVICE(dev);
s->reg1 = 0; // 将寄存器 1 重置为 0
s->reg2 = 0; // 将寄存器 2 重置为 0
s->enable = true; // 默认启用设备
}
static void custom_device_init(Object *obj)
{
CustomDeviceState *s = CUSTOM_DEVICE(obj);
// 初始化中断处理(GPIO 输出用于中断信号)
qdev_init_gpio_out(DEVICE(s), &s->irq, 1);
// 初始化内存映射 I/O(MMIO)区域
memory_region_init_io(&s->iomem, obj, &custom_device_ops, s, "CustomDevice", 0x100);
// 将 MMIO 区域和中断连接到系统总线
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
}c4. 实现读写函数#
这些函数定义了设备如何处理对寄存器的读写请求。
// 读取 MMIO 区域的函数
static uint64_t custom_device_read(void *opaque, hwaddr offset, unsigned size)
{
CustomDeviceState *s = opaque; // 访问设备状态
switch (offset) {
case 0x00:
return s->reg1; // 返回 reg1 的值
case 0x04:
return s->reg2; // 返回 reg2 的值
default:
return 0; // 无效的偏移,返回 0
}
}
// 写入 MMIO 区域的函数
static void custom_device_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
{
CustomDeviceState *s = opaque; // 访问设备状态
switch (offset) {
case 0x00:
s->reg1 = val; // 将值写入 reg1
break;
case 0x04:
s->reg2 = val; // 将值写入 reg2
break;
default:
break; // 无效的偏移,不执行操作
}
}c5. 定义 MemoryRegion 操作#
此部分将读写函数绑定到设备的内存区域。
static const MemoryRegionOps custom_device_ops = {
.read = custom_device_read, // 绑定读取函数
.write = custom_device_write, // 绑定写入函数
.endianness = DEVICE_NATIVE_ENDIAN, // 使用本地字节序
};c6. 支持虚拟机迁移(可选)#
如果设备需要支持虚拟机迁移,开发者需要定义哪些字段应被保存和恢复。
static const VMStateDescription vmstate_custom_device = {
.name = "custom_device", // VM 状态名称
.version_id = 1, // 状态版本 ID
.minimum_version_id = 1, // 最小兼容版本 ID
.fields = (VMStateField[]) {
VMSTATE_UINT32(reg1, CustomDeviceState), // 保存/恢复 reg1
VMSTATE_UINT32(reg2, CustomDeviceState), // 保存/恢复 reg2
VMSTATE_END_OF_LIST() // 状态字段列表结束
}
};c7. 实现并注册设备#
此函数实现设备的逻辑。在这里,我们初始化定时器并连接中断。
static void custom_device_realize(DeviceState *dev, Error **errp)
{
CustomDeviceState *s = CUSTOM_DEVICE(dev);
// 创建一个新的定时器,该定时器将在 custom_device_interrupt 函数中触发
s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, custom_device_interrupt, s);
// 获取并连接中断线路
qemu_irq plic_irq = qdev_get_gpio_in(DEVICE(plic), 7);
qdev_connect_gpio_out(DEVICE(s), 0, plic_irq);
}
// 初始化设备类
static void custom_device_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->reset = custom_device_reset; // 绑定重置函数
dc->vmsd = &vmstate_custom_device; // 绑定 VM 状态描述
dc->realize = custom_device_realize; // 绑定实现函数
}
// 注册设备类型
static const TypeInfo custom_device_info = {
.name = TYPE_CUSTOM_DEVICE, // 设备名称
.parent = TYPE_SYS_BUS_DEVICE, // 父类
.instance_size = sizeof(CustomDeviceState), // 实例大小
.instance_init = custom_device_init, // 实例初始化函数
.class_init = custom_device_class_init // 类初始化函数
};
// 将设备类型注册到 QEMU
static void custom_device_register_types(void)
{
type_register_static(&custom_device_info); // 注册设备类型
}
// 在 QEMU 初始化时调用注册函数
type_init(custom_device_register_types);c通过遵循此模板,开发者可以在 QEMU 中实现支持 MMIO、中断和可选迁移功能的自定义外设。你可以根据具体的设备需求修改此模板。