3. 设备寄存器读写测试
任务说明
本案例为QEMU虚拟设备添加一组MMIO寄存器,并编写测试用例验证寄存器读写行为正确。设备模拟一个简单的状态寄存器组:包含版本号寄存器(只读)、控制寄存器(可读写)、状态寄存器(只读,由设备内部逻辑驱动)。测试需覆盖:初始值正确性、只读寄存器写操作被忽略、可读写寄存器写后读回一致。
设备实现
// regdev.c - 简单MMIO寄存器设备
#include "qemu/osdep.h"
#include "hw/pci/pci_device.h"
#include "qemu/log.h"
#include "qapi/error.h"
#define TYPE_REGDEV "regdev"
OBJECT_DECLARE_SIMPLE_TYPE(RegDevState, REGDEV)
#define REGDEV_MMIO_BAR 0
#define REGDEV_MMIO_SIZE 0x1000
#define REG_VERSION 0x00 // 只读,固定0x0001
#define REG_CONTROL 0x04 // 可读写
#define REG_STATUS 0x08 // 只读,bit0反映CONTROL bit0取反
typedef struct {
PCIDevice parent_obj;
MemoryRegion mmio;
uint32_t control;
} RegDevState;
static uint64_t regdev_mmio_read(void *opaque, hwaddr addr, unsigned size)
{
RegDevState *s = opaque;
uint32_t val = 0;
switch (addr) {
case REG_VERSION:
val = 0x0001;
break;
case REG_CONTROL:
val = s->control;
break;
case REG_STATUS:
val = (s->control & 1) ? 0 : 1; // 逻辑反相
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "regdev: bad read addr 0x%"HWADDR_PRIx"\n", addr);
break;
}
return val;
}
static void regdev_mmio_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
{
RegDevState *s = opaque;
switch (addr) {
case REG_CONTROL:
s->control = (uint32_t)val;
break;
case REG_VERSION:
case REG_STATUS:
// 只读寄存器,写操作忽略
qemu_log_mask(LOG_GUEST_ERROR, "regdev: write to RO reg 0x%"HWADDR_PRIx"\n", addr);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "regdev: bad write addr 0x%"HWADDR_PRIx"\n", addr);
break;
}
}
static const MemoryRegionOps regdev_mmio_ops = {
.read = regdev_mmio_read,
.write = regdev_mmio_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl = { .min_access_size = 4, .max_access_size = 4 },
};
static void regdev_realize(PCIDevice *pci_dev, Error **errp)
{
RegDevState *s = REGDEV(pci_dev);
memory_region_init_io(&s->mmio, OBJECT(s), ®dev_mmio_ops, s,
"regdev-mmio", REGDEV_MMIO_SIZE);
pci_register_bar(pci_dev, REGDEV_MMIO_BAR, PCI_BASE_ADDRESS_SPACE_MEMORY,
&s->mmio);
}
static void regdev_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
k->realize = regdev_realize;
k->vendor_id = 0x1234;
k->device_id = 0x5678;
k->class_id = PCI_CLASS_OTHERS;
dc->desc = "Simple MMIO register device";
}
static const TypeInfo regdev_info = {
.name = TYPE_REGDEV,
.parent = TYPE_PCI_DEVICE,
.instance_size = sizeof(RegDevState),
.class_init = regdev_class_init,
};
static void regdev_register_types(void)
{
type_register_static(®dev_info);
}
type_init(regdev_register_types)
测试程序
// test_regdev.c - 在guest中验证寄存器读写
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <assert.h>
#define PCI_DEV "/sys/bus/pci/devices/0000:00:03.0/resource0"
#define REG_VERSION 0x00
#define REG_CONTROL 0x04
#define REG_STATUS 0x08
int main(void)
{
int fd = open(PCI_DEV, O_RDWR | O_SYNC);
if (fd < 0) { perror("open"); return 1; }
volatile uint32_t *regs = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (regs == MAP_FAILED) { perror("mmap"); return 1; }
// 1. 初始值检查
assert(regs[REG_VERSION/4] == 0x0001);
assert(regs[REG_CONTROL/4] == 0x0000);
assert(regs[REG_STATUS/4] == 0x0001); // control bit0=0 → status=1
// 2. 只读寄存器写忽略
regs[REG_VERSION/4] = 0xFFFF;
regs[REG_STATUS/4] = 0xAAAA;
assert(regs[REG_VERSION/4] == 0x0001);
assert(regs[REG_STATUS/4] == 0x0001);
// 3. 可读写寄存器写后读回
regs[REG_CONTROL/4] = 0x5A5A;
assert(regs[REG_CONTROL/4] == 0x5A5A);
assert(regs[REG_STATUS/4] == 0x0000); // bit0=0 → status=1? 0x5A5A bit0=0 → status=1
regs[REG_CONTROL/4] = 0x0001;
assert(regs[REG_CONTROL/4] == 0x0001);
assert(regs[REG_STATUS/4] == 0x0000); // bit0=1 → status=0
printf("ALL REGDEV TESTS PASSED\n");
munmap((void*)regs, 0x1000);
close(fd);
return 0;
}
编译与运行
# 编译设备(在QEMU源码树内)
make qemu-x86_64
# 启动带设备的虚拟机(假设已有磁盘镜像disk.img)
./qemu-system-x86_64 -machine q35 -m 512 \
-drive file=disk.img,format=raw \
-device regdev \
-net none -nographic
# 在guest内编译并运行测试
gcc -o test_regdev test_regdev.c
./test_regdev
验证方法
测试程序通过PCI sysfs接口将设备BAR0映射到用户空间,直接以32位小端访问寄存器。判定标准:程序输出ALL REGDEV TESTS PASSED且退出码为0,表示三项断言全部通过——初始值正确、只读寄存器写入被忽略、控制寄存器读写一致且状态寄存器逻辑正确。若断言失败,程序会打印失败行号并异常退出。
关键点说明
- 设备通过
pci_register_bar将MMIO区域暴露为PCI BAR0,guest可直接映射访问。 - 只读寄存器在write回调中仅记录guest错误日志,不修改内部状态,保证写忽略语义。
- 测试程序使用
assert硬校验,任何一项失败都会导致非零退出码,便于自动化集成。 - 设备ID需与QEMU命令行
-device regdev匹配,PCI地址可能因总线拓扑变化,测试中硬编码为00:03.0,若实际地址不同需调整。