QEMU RISC-V Server Platform (RVSP) 实现分析

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 介绍

RVSPMachineStatervsp-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 类型的入口点,在实例化阶段被调用,这个阶段已经完成了所有属性参数的动态配置解析和设置,之后就可以按照硬件依赖顺序完成所有组件的创建和配置。

初始化流程概览

整个初始化过程遵循以下关键步骤:

  1. 参数校验与基础设置
  2. 多 Socket CPU 架构初始化
  3. 内存区域映射建立
  4. 中断控制器实例化
  5. PCIe 子系统初始化
  6. 外设设备创建
  7. 设备树生成与加载
  8. 完成通知注册

详细初始化步骤

下面我们针对每个步骤进行补充说明:

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 设备 ID8086:2922(Intel ICH9 AHCI 控制器)
  • PCI 子系统 ID1af4:1100
  • BAR 配置
    • BAR4:I/O 空间保留
    • BAR5:32 位内存空间,映射 AHCI 的 ABAR(AHCI 基址寄存器)

DMA 引擎支持

  • 通过设置 PCI_COMMAND_MEMORYPCI_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 可靠传递:

  1. 清除挂起位:在 IMSIC 中清除用于同步的中断挂起位
  2. 获取互斥锁:锁定 APLIC 的 genmsi 寄存器
  3. 生成 MSI:写入 genmsi 寄存器向目标 CPU 发送中断
  4. 等待完成:轮询 Busy 位直至操作完成
  5. 确认送达:检查 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 支持的过程中,发现了补丁的许多问题,比如:

  1. 有些部件的地址空间映射错误 Re: [PATCH v3 4/4] hw/riscv/server_platform_ref.c: add riscv-iommu-sys - Chao Liu
  2. 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

需要支持的特性

  1. 增加 sdext 扩展
  2. 支持 RAS
  3. 支持 QoS

在上游最新的讨论中,希望在补全 sdext 以后,再考虑将 rvsp-ref 合入主线。

3 个赞