QEMU RISC-V Server Platform 参考板(rvsp-ref board)的整体架构基于标准化合规和开发测试环境 两大核心目标构建。该实现严格遵循 RISC-V Server Platform 1.0 规范,为操作系统(比如 OpenEuler RISC-V)和 Hypervisor 等可移植系统软件提供标准化的硬件和软件能力支持。
在设计上,rvsp-ref 基于 QEMU 现有的 riscv virt 机器类型进行扩展,复用大量 riscv virt 代码以降低开发复杂度,并且定义了虚拟 CPU 类型 rvsp-ref-cpu ,确保符合服务器平台规范要求(支持RVA23 ISA Profile、Sv48、Svadu、H扩展等)。
rvsp-ref 支持最大核数由 RVSP_CPUS_MAX 定义,默认最大支持 512 个核心,通过 valid_cpu_types 限制仅允许使用合规的 CPU 类型。
因此原有软件栈只要适配了 QEMU virt Machine,再迁移到 rvsp-ref 上面就会很方便。
目前笔者基于上游最新补丁构建了一个可以运行 OpenEuler RISC-V 的 rvsp-ref 分支,使用如下命令获取源码:
git clone -b riscv-server-platform git@github.com:zevorn/qemu.git
关于 RVSP-REF 的详细介绍,可以看这篇文章: 在 QEMU RISC-V 服务器参考平台(rvsp-ref)上运行 OpenEuler RISC-V 25.09 - openEuler - RISC-V 开发者社区。
下面笔者将从 QEMU rvsp-ref 的整体架构、关键数据结构、初始化流程、关键部件实现、后续计划等几个章节进行描述。
一、整体架构
硬件模拟情况
rvsp-ref 目前包含的主要部件如下:
| 组件类别 | 具体实现 | 规范要求 |
|---|---|---|
| CPU | RVA23s64 + 其他必要ext | 缺少 sdext |
| 中断控制器 | AIA架构(IMSIC+APLIC) | 符合服务器平台标准 |
| PCIe接口 | 根复合体+AHCI+物理NIC | 保留 virtio-pci |
| 存储系统 | 双PFlash闪存设备 | 固件存储支持 |
| 基础外设 | RTC、UART等 | 必要硬件组件 |
面向 Guest 软件的接口
- 仅设备树支持:生成最小必要的设备树节点,用于裸机或简单固件引导
- 无ACPI表:上游建议由 EDK II 根据 rvsp-ref 提供的 dtb,来生成 ACPI 表提供给内核
- 配置简化:移除 fw_cfg 设备,减少与特定QEMU功能的耦合
与 virt 的对比
为了更直观的感受 rvsp-ref 与 virt 的不同,我们对一些重要部件进行对比分析:
| 特性 | RVSP-REF | virt机器 |
|---|---|---|
| CPU 类型 | 严格符合 RVSP 1.0 规范 | 动态可配置 |
| 设备类型 | 物理 PCIe 设备 | VirtIO 设备 |
| 固件接口 | 仅设备树 | 设备树+ACPI |
| 配置机制 | 无 fw_cfg | 支持 fw_cfg |
| 目标用途 | 服务器合规测试 | 通用虚拟化 |
可以看出来,rvsp-ref 的设计通过标准化硬件组件模拟、精简的软件接口以及严格的合规性要求,为 RISC-V 服务器生态提供了可靠的虚拟开发平台,注重真实性同时保持与现有virt 机器的兼容性。
二、关键数据结构
QEMU rvsp-ref 的实现围绕几个核心数据结构展开,这些结构体承载了机器状态、硬件组件和配置信息。
RVSPMachineState 介绍
RVSPMachineState 是 rvsp-ref 的 QOM 对象,继承自 QEMU 的标准 MachineState:
// hw/riscv/server_platform_ref.c
struct RVSPMachineState {
/*< private >*/
MachineState parent; // 继承自MachineState基类
/*< public >*/
Notifier machine_done; // 机器初始化完成通知器
RISCVHartArrayState soc[RVSP_SOCKETS_MAX]; // 多socket的RISC-V hart数组
DeviceState *irqchip[RVSP_SOCKETS_MAX]; // 每个socket的中断控制器
PFlashCFI01 *flash[2]; // 2个CFI闪存设备
int fdt_size; // 设备树大小
int aia_guests; // AIA(高级中断架构)客户数
const MemMapEntry *memmap; // 内存映射表指针
};
下面对关键字段进行补充说明:
- soc:支持多 socket 架构,每个 socket 包含一组 RISC-V CPU 核,最大支持 4 个socket(
RVSP_SOCKETS_MAX = 4) - irqchip:为每个socket维护独立的中断控制器实例,支持AIA架构的分布式中断处理
- flash:管理两个并行闪存设备(pflash0/pflash1),用于固件存储
- aia_guests:配置 AIA 架构支持的客户数量,影响 IMSIC 的 MMIO 空间分配,后续版本会改成常量,严格遵循规范
内存映射表:硬件地址空间布局
内存映射通过 MemMapEntry rvsp_ref_memmap[] 数组定义,关键区域包括:
| 设备区域 | 基地址 | 大小 | 用途 |
|---|---|---|---|
| RVSP_DRAM | 0x80000000 | 0xff80000000ull (≈1024GB) | 主内存区域 |
| RVSP_PCIE_ECAM | 0x30000000 | 0x10000000 | PCIe配置空间 |
| RVSP_PCIE_MMIO | 0x40000000 | 0x40000000 | PCIe设备MMIO |
| RVSP_PCIE_MMIO_HIGH | 0x10000000000ull | 0x10000000000ull | 高位PCIe MMIO |
| RVSP_FLASH | 0x20000000 | 0x4000000 (64MB) | 双PFlash设备 |
| RVSP_IMSIC_M/S | 0x24000000/0x28000000 | RVSP_IMSIC_MAX_SIZE | AIA中断控制器 |
| RVSP_APLIC_M/S | 0xc000000/0xd000000 | APLIC_SIZE(RVSP_CPUS_MAX) | 高级PLIC |
基本上和 virt 保持一致,由于提供了 dtb ,OpebSBI 和 内核可以自动识别:
- PCIe 空间分层:提供标准 ECAM、常规 MMIO 和高位 MMIO 三部分,支持大规模 PCIe 设备映射
- 中断控制器分离:IMSIC 和 APLIC 分别管理消息信号中断和线中断,符合 AIA 规范
- Flash 区域:64MB 空间支持双 Bank 闪存,扇区大小配置为 256 KiB
常量配置
rvsp-ref 代码中定义了一系列关键配置常量,主要是设置上限值,基本都是可以在启动参数里可以配置的:
CPU与拓扑配置:
#define RVSP_CPUS_MAX_BITS 9 // 支持最多512核(2^9)
#define RVSP_SOCKETS_MAX_BITS 2 // 支持最多4个Socket(2^2)
中断系统配置:
#define RVSP_IRQCHIP_NUM_MSIS 255 // MSI中断数量上限
#define RVSP_IRQCHIP_NUM_SOURCES 96 // 中断源数量
#define RVSP_IRQCHIP_MAX_GUESTS 7 // 最大Guest数量(2^3-1)
设备树生成配置:
#define FDT_PCI_ADDR_CELLS 3 // PCI地址3单元格式
#define FDT_APLIC_INT_CELLS 2 // APLIC中断2单元格式
三、初始化流程
QEMU rvsp-ref 的初始化流程在 rvsp_ref_machine_init() 函数中实现,该函数作为 Machine 类型的入口点,在实例化阶段被调用,这个阶段已经完成了所有属性参数的动态配置解析和设置,之后就可以按照硬件依赖顺序完成所有组件的创建和配置。
初始化流程概览
整个初始化过程遵循以下关键步骤:
- 参数校验与基础设置
- 多 Socket CPU 架构初始化
- 内存区域映射建立
- 中断控制器实例化
- PCIe 子系统初始化
- 外设设备创建
- 设备树生成与加载
- 完成通知注册
详细初始化步骤
下面我们针对每个步骤进行补充说明:
1. 参数校验阶段
这里和 virt 比较类似,检查的内容都是比较常规的:
/* 检查 Socket 数量限制 */
if (RVSP_SOCKETS_MAX < socket_count) {
error_report("number of sockets/nodes should be less than %d", RVSP_SOCKETS_MAX);
exit(1);
}
/* 检查 ACLINT 仅支持 TCG 模式 */
if (!rvsp_aclint_allowed()) {
error_report("'aclint' is only available with TCG acceleration");
exit(1);
}
2. 多 Socket 架构初始化
对每个 Socket(通过 socket_count 确定)执行循环初始化,也是与 virt 保持一致:
- Hart ID 连续性校验:
riscv_socket_check_hartids() - 获取基础 Hart ID 和数量:
riscv_socket_first_hartid()、riscv_socket_hart_count() - RISCV Hart 阵列初始化:
object_initialize_child(OBJECT(machine), soc_name, &s->soc[i], TYPE_RISCV_HART_ARRAY);
object_property_set_str(OBJECT(&s->soc[i]), "cpu-type", machine->cpu_type, &error_abort);
object_property_set_int(OBJECT(&s->soc[i]), "hartid-base", base_hartid, &error_abort);
object_property_set_int(OBJECT(&s->soc[i]), "num-harts", hart_count, &error_abort);
sysbus_realize_and_unref(SYS_BUS_DEVICE(&s->soc[i]), &error_fatal);
- 每 Socket 中断控制器创建:
s->irqchip[i] = rvsp_ref_create_aia(s->aia_guests, memmap, i, base_hartid, hart_c
3. 内存区域映射建立
- 系统主内存(DRAM):
memory_region_add_subregion(system_memory, memmap[RVSP_DRAM].base, machine->ram) - 掩膜 ROM(MROM):初始化并映射到
RVSP_MROM地址 - 复位系统控制器:初始化 IO 区域并映射到
RVSP_RESET_SYSCON
4. PCIe 主机控制器初始化
gpex_pcie_init(system_memory, pcie_irqchip, s)
建立完整的 PCIe 地址空间映射,包括 ECAM、MMIO 和 PIO 区域。
5. 外设设备实例化
按照硬件依赖顺序创建各类外设:
基础外设:
- 串口(UART0):
serial_mm_init(),绑定到RVSP_UART0地址和中断 - 实时时钟(RTC):
sysbus_create_simple("goldfish_rtc", ...) - IOMMU 系统设备:初始化并实现
sysbus_realize_and_unref()
存储设备:
- 闪存(PFlash):循环初始化两个闪存设备:
for (i = 0; i < ARRAY_SIZE(s->flash); i++) {
pflash_cfi01_legacy_drive(s->flash[i], drive_get(IF_PFLASH, 0, i));
}
rvsp_flash_maps(s, system_memory);
6. 设备树处理
- 条件加载:若用户提供 DTB(
machine->dtb),直接调用load_device_tree() - 动态生成:否则调用
create_fdt(s, memmap)生成默认设备树
7. 完成通知注册
s->machine_done.notify = rvsp_ref_machine_done;
qemu_add_machine_init_done_notifier(&s->machine_done);
rvsp_ref_machine_done 回调函数负责后续的固件/内核加载和复位向量设置。
总结关键设计特点
严格的依赖顺序:CPU → 内存 → 中断 → PCIe → 外设,确保硬件组件按正确顺序初始化。
错误处理机制:每个关键步骤都包含严格的参数校验和错误处理,确保初始化失败时能够优雅退出。
模块化设计:各组件初始化函数职责单一,便于维护和扩展。
设备树灵活性:支持外部 DTB 加载和内部动态生成两种模式,适应不同使用场景。
整个初始化流程确保了 RVSP-REF 机器能够以符合 RISC-V Server Platform 规范的方式启动,为操作系统和固件提供稳定可靠的硬件模拟环境。
四、关键部件实现
PCIe 子系统实现
RVSP-REF 的 PCIe 子系统采用标准的 GPEX(Generic PCI Express)主机控制器架构,通过 gpex_pcie_init() 函数完成根复合体的初始化与配置。该实现严格遵循 PCIe 规范,同时针对服务器平台特性进行了专门优化。
4.1 GPEX PCIe 主机控制器配置
寄存器映射配置通过 object_property_set_* 函数设置关键地址空间属性:
- ECAM 空间:基地址
0x3000_0000,大小0x1000_0000(256MB) - 低 4G MMIO 空间:基地址
0x4000_0000,大小0x4000_0000(1GB) - 高 4G MMIO 空间:基地址
0x1_0000_0000_0000,大小0x1_0000_0000_0000(1TB) - PIO 空间:基地址
0x0300_0000,大小0x1_0000(64KB)
中断配置采用硬件 swizzling 机制,将端点设备的 INTx 中断收敛到 4 个标准中断线:
for (i = 0; i < PCI_NUM_PINS; i++) {
sysbus_connect_irq(SYS_BUS_DEVICE(dev), i,
qdev_get_gpio_in(irqchip, RVSP_PCIE_IRQ + i));
gpex_set_irq_num(GPEX_HOST(dev), i, RVSP_PCIE_IRQ + i);
}
4.2 AHCI SATA 控制器实现
AHCI 控制器作为标准 PCIe 端点设备实现,关键特性包括:
设备标识与配置
- PCI 设备 ID:
8086:2922(Intel ICH9 AHCI 控制器) - PCI 子系统 ID:
1af4:1100 - BAR 配置:
- BAR4:I/O 空间保留
- BAR5:32 位内存空间,映射 AHCI 的 ABAR(AHCI 基址寄存器)
DMA 引擎支持
- 通过设置
PCI_COMMAND_MEMORY和PCI_COMMAND_MASTER启用内存访问和总线主控能力 - 定义
ahci_dma_ops结构体管理 SATA 数据传输的 DMA 生命周期 - 支持命令列表、FIS 接收区和数据缓冲区的地址映射
4.3 e1000e 网卡设备模拟
e1000e 网卡作为物理 NIC 的代表,实现完整的 PCIe 端点功能:
MAC 地址分配
- 默认地址:
52:54:00:12:34:56 - 支持
qemu_macaddr_default_if_unset()自动分配 - MAC 地址存储在
E1000EState.conf.macaddr结构中
中断映射机制
- MSI-X 支持:通过
e1000e_init_msix()初始化多向量中断配置 - 中断寄存器:
E1000_IVAR0用于配置中断向量分配 - 支持 RX/TX 队列分别配置中断向量
寄存器布局
- MMIO 区域:128KB(索引 0)
- Flash 区域:128KB(索引 1)
- IO 空间:32 字节(索引 2)
- MSI-X 区域:16KB(索引 3)
4.4 PCIe 中断映射机制
RVSP-REF 采用以 MSI/MSI-X 为核心的现代中断架构:
MSI/MSI-X 强制要求
- 系统必须支持 Message Signaled Interrupts(规则
MSI_010) - 禁用 INTx:SoC 不得支持基于 INTx 虚拟线缆的中断信号(规则
MSI_020) - 仅在遗留设备场景下允许 INTx 模拟
AIA 中断控制器连接
- IMSIC 角色:每个 CPU 配置 S 模式中断文件,支持至少 255 个中断标识符
- APLIC 角色:将线缆中断转换为 MSI,确保所有外部中断以 MSI 形式传递
- 虚拟化支持:在 KVM 模式下保证中断隔离与性能
具体实现方式
- 为 PCIe 设备分配独立的中断控制器实例(
s->irqchip[1]) - 设备树通过
msi-parent属性关联 PCIe 节点与 IMSIC - INTx 中断经 APLIC 转换为 MSI(规则
IIC_080)
4.5 地址空间布局与 BAR 分配
PCIe 地址空间通过 rvsp_ref_memmap 数组统一管理:
| 空间类型 | 基地址 | 大小 | 用途 |
|---|---|---|---|
| ECAM | 0x3000_0000 |
256MB | PCIe 配置空间访问 |
| MMIO(32位) | 0x4000_0000 |
1GB | 32位MMIO设备映射 |
| MMIO(64位) | 0x1_0000_0000_0000 |
1TB | 64位MMIO设备映射 |
| PIO | 0x0300_0000 |
64KB | 端口IO操作 |
BAR 分配策略
- 使用内存区域别名机制实现地址转换
- 通过
memory_region_init_alias创建 ECAM 和 MMIO 别名区域 - PIO 空间通过
sysbus_mmio_map直接映射 - 支持
pci_allow_0_address = true,允许 PCIe 设备使用 0 地址
该 PCIe 子系统设计确保了 RVSP-REF 平台在保持标准兼容性的同时,为服务器级操作系统和 Hypervisor 提供了真实、可靠的硬件模拟环境。
中断系统实现
QEMU rvsp-ref 的中断系统严格按照 RISC-V Server Platform 规范要求,基于 AIA(Advanced Interrupt Architecture) 架构实现,完全取代传统的 PLIC 方案。该系统由 IMSIC(Incoming MSI Controller) 和 APLIC(Advanced Platform-Level Interrupt Controller) 两级控制器协同工作,为多核服务器环境提供高效的中断处理能力。
AIA 控制器实例化与连接
每个处理器 Socket 都拥有独立的中断控制器实例,通过 rvsp_ref_create_aia() 函数完成初始化:
s->irqchip[i] = rvsp_ref_create_aia(s->aia_guests, memmap, i, base_hartid, hart_count);
该函数的核心职责包括:
- IMSIC 配置:根据
aia_guests参数计算每个 CPU 的中断文件数量,映射到对应的 MMIO 区域(M-mode: 0x2400_0000, S-mode: 0x2800_0000) - APLIC 实例化:创建 APLIC 设备并配置输入源(共 96 个,编号从
RVSP_PCIE_IRQ开始) - 控制器互联:将 APLIC 的 MSI 输出连接到对应 CPU 的 IMSIC 中断文件
中断路由机制
MSI/MSI-X 优先原则
QEMU rvsp-ref 严格遵循 MSI_010 规则,强制所有 PCIe 设备使用 MSI/MSI-X 中断:
- 设备树指向:PCIe 节点通过
msi-parent = <&imsic>属性直接指向 IMSIC 控制器 - 中断标识符:每个 CPU 支持最多 255 个 MSI 中断 ID(
RVSP_IRQCHIP_NUM_MSIS) - 虚拟化扩展:支持最多 7 个 Guest(
RVSP_IRQCHIP_MAX_GUESTS),为每个虚拟 CPU 提供独立的 VS-mode 中断文件
INTx 遗留兼容处理
对于必须支持 INTx 的遗留场景,系统遵循 IIC_080 规则:
- PCIe INTx 连线:GPEX 控制器的 4 条 INTx 线连接到 APLIC 输入:
for (i = 0; i < PCI_NUM_PINS; i++) {
irq = qdev_get_gpio_in(irqchip, RVSP_PCIE_IRQ + i);
sysbus_connect_irq(SYS_BUS_DEVICE(gpex), i, irq);
}
- 转换机制:APLIC 将收到的 INTx 中断转换为 MSI 写入目标 CPU 的 IMSIC
- 隔离设计:PCIe 域使用独立的
irqchip[1]实例,与 MMIO 设备的中断控制器隔离
中断处理流程
1. MSI 传递路径
PCIe设备 --[MSI写入]--> IMSIC --[中断信号]--> RISC-V CPU
2. INTx-to-MSI 转换路径
PCIe设备 --[INTx信号]--> APLIC --[MSI转换]--> IMSIC --[中断信号]--> RISC-V CPU
3. 同步保障机制
APLIC 与 IMSIC 之间通过严格的同步流程确保 MSI 可靠传递:
- 清除挂起位:在 IMSIC 中清除用于同步的中断挂起位
- 获取互斥锁:锁定 APLIC 的
genmsi寄存器 - 生成 MSI:写入
genmsi寄存器向目标 CPU 发送中断 - 等待完成:轮询
Busy位直至操作完成 - 确认送达:检查 IMSIC 中断挂起位确认中断已接收
虚拟化支持
QEMU rvsp-ref 的 AIA 实现充分考虑了虚拟化需求:
- Guest 中断文件:每个虚拟 CPU 拥有独立的 VS-mode 中断文件,支持直接虚拟中断注入
- KVM 优化:IMSIC 可由内核直接模拟,APLIC 在用户空间处理,实现性能与功能的平衡
- 优先级管理:通过 CSR 寄存器(
miselect/mireg,siselect/sireg)动态配置中断优先级,支持多特权模式下的灵活调度
设备树节点描述
系统在设备树生成阶段为中断控制器创建完整的描述信息:
IMSIC 节点属性
interrupt-controller;
#interrupt-cells = <0>;
msi-controller;
reg = <基址 大小>;
interrupts-extended = <各CPU的中断线引用>;
APLIC 节点属性
interrupt-controller;
#interrupt-cells = <2>;
reg = <基址 大小>;
interrupts-extended = <连接到各CPU的引用>;
五、后续计划
目前存在的问题
在 QEMU rvsp-ref 支持的过程中,发现了补丁的许多问题,比如:
- 有些部件的地址空间映射错误 Re: [PATCH v3 4/4] hw/riscv/server_platform_ref.c: add riscv-iommu-sys - Chao Liu
- RVSP-REF CPU 在继承自 vendor 时不能正常启动内核 Re: [PATCH v3 2/4] target/riscv: Add server platform reference cpu - Chao Liu
因此上游将在近期更新 v4 版本的补丁: Re: [PATCH v3 0/4] hw/riscv: Add Server Platform Reference Board - Daniel Henrique Barboza
需要支持的特性
- 增加 sdext 扩展
- 支持 RAS
- 支持 QoS
在上游最新的讨论中,希望在补全 sdext 以后,再考虑将 rvsp-ref 合入主线。