qemu自定义外设(腾讯元宝)
通过此框架,您可以快速实现复杂的自定义外设逻辑,并在QEMU环境中进行验证。返回QEMU虚拟CPU的当前时间(纳秒级精度),可通过。将设备内存映射到系统总线,客户机通过物理地址访问。记录非法访问,增强设备健壮性。
·
以下是如何在QEMU中实现支持寄存器访问的自定义外部设备的详细步骤和代码示例:
1. 设备代码实现(C语言)
c
复制
#include "hw/sysbus.h"
#include "qemu/timer.h"
#include "qapi/error.h"
#define TYPE_CUSTOM_DEVICE "custom-device"
#define CUSTOM_DEVICE(obj) OBJECT_CHECK(CustomDeviceState, (obj), TYPE_CUSTOM_DEVICE)
typedef struct CustomDeviceState {
SysBusDevice parent_obj;
// 寄存器定义
uint32_t reg_data;
uint64_t rtc_time; // 用于示例的实时时间寄存器
} CustomDeviceState;
// 寄存器偏移量定义
#define REG_DATA 0x00 // 数据寄存器
#define REG_RTC 0x04 // 时间寄存器
// MMIO读回调
static uint64_t custom_read(void *opaque, hwaddr offset, unsigned size) {
CustomDeviceState *s = opaque;
uint64_t value = 0;
switch (offset) {
case REG_DATA:
value = s->reg_data;
break;
case REG_RTC:
// 返回当前时间(纳秒转换为毫秒)
value = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 1000000;
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid offset 0x%" HWADDR_PRIx "\n",
__func__, offset);
break;
}
return value;
}
// MMIO写回调
static void custom_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) {
CustomDeviceState *s = opaque;
switch (offset) {
case REG_DATA:
s->reg_data = value;
break;
case REG_RTC:
// 时间寄存器为只读,忽略写入
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid offset 0x%" HWADDR_PRIx "\n",
__func__, offset);
break;
}
}
// 设备初始化
static const MemoryRegionOps custom_ops = {
.read = custom_read,
.write = custom_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.valid.min_access_size = 4,
.valid.max_access_size = 4,
};
static void custom_realize(DeviceState *dev, Error **errp) {
CustomDeviceState *s = CUSTOM_DEVICE(dev);
// 初始化寄存器
s->reg_data = 0xDEADBEEF;
s->rtc_time = 0;
// 创建MMIO内存区域
memory_region_init_io(&s->iomem, OBJECT(s), &custom_ops, s,
"custom-device", 0x1000); // 映射0x1000字节空间
sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
}
static void custom_class_init(ObjectClass *klass, void *data) {
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = custom_realize;
dc->desc = "Custom Device with Registers";
}
static const TypeInfo custom_info = {
.name = TYPE_CUSTOM_DEVICE,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(CustomDeviceState),
.class_init = custom_class_init,
};
static void custom_register_types(void) {
type_register_static(&custom_info);
}
type_init(custom_register_types)
2. 编译与集成
- 将代码保存为
hw/misc/custom_device.c - 修改QEMU的Makefile
在hw/misc/Makefile.objs中添加:makefile
复制
obj-y += custom_device.o - 重新编译QEMU
bash
复制
./configure --enable-debug make -j$(nproc)
3. 启动QEMU测试
通过命令行加载设备:
bash
复制
qemu-system-x86_64 \
-device custom-device \
-kernel your_kernel_image \
-append "root=/dev/sda1 console=ttyS0" \
-hda rootfs.img
4. 用户空间测试程序
c
复制
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#define DEVICE_ADDR 0x50000000 // 假设设备映射地址
#define DEVICE_SIZE 0x1000
int main() {
int fd = open("/dev/mem", O_RDWR | O_SYNC);
if (fd < 0) {
perror("open");
return 1;
}
// 映射设备内存
void *map = mmap(NULL, DEVICE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, DEVICE_ADDR);
if (map == MAP_FAILED) {
perror("mmap");
close(fd);
return 1;
}
// 读取数据寄存器
volatile uint32_t *reg_data = (volatile uint32_t *)(map + 0x00);
printf("Data Register: 0x%x\n", *reg_data);
// 读取时间寄存器
volatile uint64_t *reg_rtc = (volatile uint64_t *)(map + 0x04);
printf("RTC Time: %llu ms\n", *reg_rtc);
// 写入数据寄存器
*reg_data = 0x1234ABCD;
printf("New Data Value: 0x%x\n", *reg_data);
munmap(map, DEVICE_SIZE);
close(fd);
return 0;
}
编译并运行测试程序:
bash
复制
gcc test.c -o test
sudo ./test
关键点说明
-
寄存器映射
使用sysbus_init_mmio将设备内存映射到系统总线,客户机通过物理地址访问。 -
时间同步
示例中的REG_RTC返回QEMU虚拟CPU的当前时间(纳秒级精度),可通过qemu_clock_get_ns获取。 -
错误处理
使用qemu_log_mask记录非法访问,增强设备健壮性。 -
访问权限
在MemoryRegionOps中指定valid.min/max_access_size可限制访问粒度。
扩展方向
- 中断支持:通过
qemu_irq向客户机发送中断信号。 - DMA支持:实现DMA控制器接口。
- 设备树绑定:添加设备树节点描述,便于U-Boot等引导加载器识别。
通过此框架,您可以快速实现复杂的自定义外设逻辑,并在QEMU环境中进行验证。
更多推荐
所有评论(0)