2. 编写第一个虚拟设备
任务说明
本案例目标是在QEMU源码树中实现一个最小PCI虚拟设备,使其能够被QEMU正确注册,并在客户机(guest OS)中通过lspci识别到该设备。设备本身不实现具体功能,仅提供基本的PCI配置空间和BAR0内存区域,用于验证设备从注册到客户机可见的完整链路。
实现方案
在QEMU的hw/misc目录下新建设备源文件,使用QEMU标准设备接口(TYPE_PCI_DEVICE)实现一个PCI设备。设备ID使用QEMU官方预留的测试范围(0x0001),厂商ID使用0x1234(QEMU虚拟设备常用厂商ID)。设备提供4KB大小的BAR0内存区域,客户机可通过该区域进行读写访问。
代码实现
// hw/misc/qemu_demo_pci.c
#include "qemu/osdep.h"
#include "hw/pci/pci_device.h"
#include "hw/pci/msi.h"
#include "qemu/log.h"
#include "qemu/module.h"
#define TYPE_DEMO_PCI_DEVICE "demo-pci"
OBJECT_DECLARE_SIMPLE_TYPE(DemoPCIState, DEMO_PCI_DEVICE)
#define DEMO_PCI_VENDOR_ID 0x1234
#define DEMO_PCI_DEVICE_ID 0x0001
#define DEMO_PCI_BAR0_SIZE 0x1000 // 4KB
typedef struct DemoPCIState {
PCIDevice parent_obj;
MemoryRegion bar0;
} DemoPCIState;
static uint64_t demo_pci_bar0_read(void *opaque, hwaddr addr, unsigned size)
{
// 简单返回固定值,用于验证读写通路
return 0xDEADBEEF;
}
static void demo_pci_bar0_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
{
// 写操作不做处理,仅保证通路可用
}
static const MemoryRegionOps demo_pci_bar0_ops = {
.read = demo_pci_bar0_read,
.write = demo_pci_bar0_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl = {
.min_access_size = 1,
.max_access_size = 4,
},
};
static void demo_pci_realize(PCIDevice *pci_dev, Error **errp)
{
DemoPCIState *s = DEMO_PCI_DEVICE(pci_dev);
// 设置PCI配置空间中的设备ID和厂商ID
pci_set_word(pci_dev->config + PCI_VENDOR_ID, DEMO_PCI_VENDOR_ID);
pci_set_word(pci_dev->config + PCI_DEVICE_ID, DEMO_PCI_DEVICE_ID);
// 初始化BAR0内存区域
memory_region_init_io(&s->bar0, OBJECT(s), &demo_pci_bar0_ops,
s, "demo-pci-bar0", DEMO_PCI_BAR0_SIZE);
pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar0);
}
static void demo_pci_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
k->realize = demo_pci_realize;
k->vendor_id = DEMO_PCI_VENDOR_ID;
k->device_id = DEMO_PCI_DEVICE_ID;
k->class_id = PCI_CLASS_OTHERS;
dc->desc = "QEMU Demo PCI Device";
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
}
static const TypeInfo demo_pci_info = {
.name = TYPE_DEMO_PCI_DEVICE,
.parent = TYPE_PCI_DEVICE,
.instance_size = sizeof(DemoPCIState),
.class_init = demo_pci_class_init,
.interfaces = (InterfaceInfo[]) {
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
{ }
},
};
static void demo_pci_register_types(void)
{
type_register_static(&demo_pci_info);
}
type_init(demo_pci_register_types)
编译集成
将上述文件加入QEMU构建系统。在hw/misc/meson.build中添加:
system_ss.add(when: 'CONFIG_DEMO_PCI',
if_true: files('qemu_demo_pci.c'))
在hw/misc/Kconfig中添加:
config DEMO_PCI
bool
default y
depends on PCI
运行与验证
重新编译QEMU后,使用以下命令行启动客户机:
./qemu-system-x86_64 \
-machine q35,accel=kvm \
-m 1G \
-device demo-pci \
-drive file=guest.img,format=raw \
-nographic
在客户机中执行lspci命令,预期输出中应包含类似以下行:
00:04.0 Unclassified device [0000]: Device 1234:0001
该输出表明设备已被客户机PCI子系统正确枚举。进一步可通过setpci命令读取BAR0地址,然后使用devmem或编写简单驱动访问BAR0区域,验证读写通路正常。
判定标准
- QEMU编译通过,无报错;
- 启动命令中
-device demo-pci被正确识别,无设备创建失败提示; - 客户机内
lspci能列出厂商ID为1234、设备ID为0001的设备; - 通过
lspci -v能看到BAR0已分配内存地址且大小为4KB。