Add opt-in RPMI platform management to the riscv-server-ref machine for firmware clients that use the RPMI transport.
RPMI is disabled by default so the existing board remains migratable and retains its legacy reset and poweroff device-tree nodes. The rpmi=on option realizes the unmigratable RPMI device, advertises the Base, System Reset, HSM, and System Suspend services, and omits those legacy nodes. Explicit RPMI enablement rejects KVM and builds without librpmi. Document the property, migration restriction, build requirements, and both device-tree layouts. Signed-off-by: Subrahmanya Lingappa <[email protected]> --- docs/system/riscv/riscv-server-ref.rst | 43 +++++++ hw/riscv/Kconfig | 1 + hw/riscv/server_platform_ref.c | 149 ++++++++++++++++++++++++- meson.build | 4 + 4 files changed, 195 insertions(+), 2 deletions(-) diff --git a/docs/system/riscv/riscv-server-ref.rst b/docs/system/riscv/riscv-server-ref.rst index da043ed096..32c3201404 100644 --- a/docs/system/riscv/riscv-server-ref.rst +++ b/docs/system/riscv/riscv-server-ref.rst @@ -28,6 +28,7 @@ The main features included in the riscv-server-ref board are: * No fw_cfg device * No ACPI table * Minimal device tree nodes +* RISC-V RPMI platform management There are multiple ways of using this reference board. The spec compliant way is using an EDK2 image and a TPM device. The board was tested with the TPM @@ -60,6 +61,48 @@ And then start QEMU with: -netdev user,id=net0 -device e1000e,netdev=net0 \ -nographic +RPMI support +------------ + +The ``riscv-server-ref`` machine exposes RISC-V RPMI platform management when +the ``rpmi=on`` machine option is selected and QEMU is built with librpmi +support. RPMI is disabled by default. ``rpmi=off`` preserves the legacy +device tree and keeps the board migratable. An RPMI-enabled machine contains +an unmigratable RPMI device, so live migration is unavailable while +``rpmi=on`` is selected. RPMI is unavailable with KVM and requires a build +with librpmi. + +The initial ``riscv-server-ref`` RPMI implementation exposes Base, System +Reset, HSM, and System Suspend service groups. System Reset handles guest +shutdown and cold reboot requests through RPMI. +HSM uses the machine hart IDs for hart discovery, status, start, stop, and +suspend commands. System Suspend uses QEMU's suspend and wakeup path. + +When ``rpmi=on`` is selected, the generated device tree advertises reset, +shutdown, HSM, and suspend through RPMI service nodes and omits the legacy +syscon reboot and poweroff nodes. With the default ``rpmi=off`` setting, the +generated device tree retains the legacy syscon reboot and poweroff nodes. +RPMI is currently described through FDT only; this machine does not emit ACPI +tables for the RPMI transport or service groups. + +RPMI support targets the RISC-V Platform Management Interface (RPMI) +Specification v1.0 Ratified (`server-ref RPMI v1.0 specification`_). The +build dependency is provided by `server-ref librpmi`_. After installing +librpmi, configure QEMU with ``--enable-librpmi`` or ``-Dlibrpmi=enabled`` to +make the build fail if librpmi is unavailable. + +.. code-block:: bash + + qemu-system-riscv64 -M riscv-server-ref,rpmi=on \ + -bios fw_dynamic.bin \ + -kernel EDK2.fd \ + -drive file=nvme_disk.ext2,format=raw,id=hd0,if=none \ + -device ahci,id=ahci \ + -device ide-hd,drive=hd0,bus=ahci.0 \ + -nographic + .. _spec: https://github.com/riscv-non-isa/riscv-server-platform .. _tpm: https://qemu-project.gitlab.io/qemu/specs/tpm.html +.. _server-ref RPMI v1.0 specification: https://github.com/riscv-non-isa/riscv-rpmi/releases/download/v1.0/riscv-rpmi.pdf +.. _server-ref librpmi: https://github.com/riscv-software-src/librpmi diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index 28d108faf8..cae3de6993 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -86,6 +86,7 @@ config RISCV_SERVER_PLATFORM_REF select RISCV_IOMMU select RISCV_NUMA select SERIAL_MM + select RISCV_RPMI if LIBRPMI config SHAKTI_C bool diff --git a/hw/riscv/server_platform_ref.c b/hw/riscv/server_platform_ref.c index d18550b48d..ea580e475f 100644 --- a/hw/riscv/server_platform_ref.c +++ b/hw/riscv/server_platform_ref.c @@ -29,18 +29,20 @@ #include "hw/riscv/device-common.h" #include "hw/riscv/fdt-common.h" #include "hw/riscv/numa.h" +#include "hw/riscv/rpmi-fdt.h" #include "hw/riscv/iommu.h" #include "hw/riscv/riscv-iommu.h" #include "hw/riscv/riscv-iommu-bits.h" +#include "hw/misc/riscv_rpmi.h" #include "hw/intc/riscv_aclint.h" #include "hw/intc/riscv_aplic.h" #include "hw/intc/riscv_imsic.h" #include "chardev/char.h" #include "hw/char/serial-mm.h" #include "system/device_tree.h" +#include "system/kvm.h" #include "system/runstate.h" #include "system/system.h" -#include "system/kvm.h" #include "system/qtest.h" #include "system/tcg.h" #include "kvm/kvm_riscv.h" @@ -68,6 +70,9 @@ #define RVSERVER_PLATFORM_BUS_NUM_IRQS 8 +#define RVSERVER_RPMI_A2P_REQ_SIZE (16 * RPMI_QUEUE_SLOT_SIZE) +#define RVSERVER_RPMI_P2A_REQ_SIZE 0 + #define TYPE_RISCV_SERVER_REF_MACHINE MACHINE_TYPE_NAME("riscv-server-ref") OBJECT_DECLARE_SIMPLE_TYPE(RISCVServerRefMachineState, RISCV_SERVER_REF_MACHINE) @@ -83,6 +88,7 @@ struct RISCVServerRefMachineState { int fdt_size; int aia_guests; + bool have_rpmi; const MemMapEntry *memmap; DeviceState *platform_bus_dev; @@ -93,6 +99,8 @@ enum { RVSERVER_RESET_SYSCON, RVSERVER_RTC, RVSERVER_IOMMU_SYS, + RVSERVER_RPMI_SHMEM, + RVSERVER_RPMI_DOORBELL, RVSERVER_ACLINT, RVSERVER_APLIC_M, RVSERVER_APLIC_S, @@ -143,6 +151,8 @@ static const MemMapEntry rvserver_ref_memmap[] = { [RVSERVER_RESET_SYSCON] = { 0x100000, 0x1000 }, [RVSERVER_RTC] = { 0x101000, 0x1000 }, [RVSERVER_IOMMU_SYS] = { 0x102000, 0x1000 }, + [RVSERVER_RPMI_SHMEM] = { 0x110000, 0x20000 }, + [RVSERVER_RPMI_DOORBELL] = { 0x140000, 0x1000 }, [RVSERVER_ACLINT] = { 0x2000000, 0x10000 }, [RVSERVER_PCIE_PIO] = { 0x3000000, 0x10000 }, [RVSERVER_PLATFORM_BUS] = { 0x4000000, 0x2000000 }, @@ -283,6 +293,81 @@ static void create_fdt_sockets(RISCVServerRefMachineState *s, riscv_socket_fdt_write_distance_matrix(ms); } +#ifdef CONFIG_LIBRPMI +static const RiscvRpmiServiceConfig rvserver_ref_rpmi_services[] = { + { + .node_name = "sysreset", + .compatible = "riscv,rpmi-system-reset", + .service_group = RPMI_SRVGRP_SYSTEM_RESET, + }, + { + .node_name = "hsm", + .compatible = "riscv,rpmi-hsm", + .service_group = RPMI_SRVGRP_HSM, + }, + { + .node_name = "suspend", + .compatible = "riscv,rpmi-system-suspend", + .service_group = RPMI_SRVGRP_SYSTEM_SUSPEND, + }, +}; + +static void rvserver_ref_rpmi_system_reset(void) +{ + qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); +} + +static void rvserver_ref_rpmi_system_shutdown(void) +{ + qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN); +} + +static const RiscvRpmiMachineOps rvserver_ref_rpmi_machine_ops = { + .system_reset = rvserver_ref_rpmi_system_reset, + .system_shutdown = rvserver_ref_rpmi_system_shutdown, +}; + +static RiscvRpmiConfig rvserver_ref_rpmi_config( + RISCVServerRefMachineState *s, const uint32_t *hart_ids, + uint32_t hart_count) +{ + return (RiscvRpmiConfig) { + .doorbell_base = s->memmap[RVSERVER_RPMI_DOORBELL].base, + .shmem_base = s->memmap[RVSERVER_RPMI_SHMEM].base, + .shmem_size = s->memmap[RVSERVER_RPMI_SHMEM].size, + .a2p_req_size = RVSERVER_RPMI_A2P_REQ_SIZE, + .p2a_req_size = RVSERVER_RPMI_P2A_REQ_SIZE, + .platform_info = "QEMU RISC-V server-ref RPMI", + .machine_ops = &rvserver_ref_rpmi_machine_ops, + .hart_ids = hart_ids, + .hart_count = hart_count, + .services = rvserver_ref_rpmi_services, + .service_count = ARRAY_SIZE(rvserver_ref_rpmi_services), + }; +} + +static void create_fdt_rpmi(RISCVServerRefMachineState *s, uint32_t *phandle) +{ + uint32_t rpmi_mbox_handle; + RiscvRpmiFdtMboxConfig cfg = { + .shmem_base = s->memmap[RVSERVER_RPMI_SHMEM].base, + .doorbell_base = s->memmap[RVSERVER_RPMI_DOORBELL].base, + .a2p_req_size = RVSERVER_RPMI_A2P_REQ_SIZE, + .p2a_req_size = RVSERVER_RPMI_P2A_REQ_SIZE, + .doorbell_size = s->memmap[RVSERVER_RPMI_DOORBELL].size, + }; + + riscv_rpmi_fdt_add_mbox(MACHINE(s)->fdt, &cfg, phandle, + &rpmi_mbox_handle); + + for (uint32_t i = 0; i < ARRAY_SIZE(rvserver_ref_rpmi_services); i++) { + riscv_rpmi_fdt_add_service_node(MACHINE(s)->fdt, + &rvserver_ref_rpmi_services[i], + rpmi_mbox_handle); + } +} +#endif + static void finalize_fdt(RISCVServerRefMachineState *s) { uint32_t phandle = 1, irq_mmio_phandle = 1, msi_pcie_phandle = 1; @@ -307,10 +392,16 @@ static void finalize_fdt(RISCVServerRefMachineState *s) irq_pcie_phandle, msi_pcie_phandle, iommu_sys_phandle, RVSERVER_PCIE_IRQ); +#ifdef CONFIG_LIBRPMI + if (s->have_rpmi) { + create_fdt_rpmi(s, &phandle); + } +#endif riscv_create_fdt_syscon(ms->fdt, &phandle, rvserver_ref_memmap[RVSERVER_RESET_SYSCON].base, rvserver_ref_memmap[RVSERVER_RESET_SYSCON].size, - SYSCON_RESET, SYSCON_POWEROFF, false, true); + SYSCON_RESET, SYSCON_POWEROFF, false, + !s->have_rpmi); riscv_create_fdt_uart(ms->fdt, &rvserver_ref_memmap[RVSERVER_UART0], RVSERVER_UART0_IRQ, AIA_TYPE_APLIC_IMSIC, @@ -516,6 +607,17 @@ static void rvserver_ref_machine_init(MachineState *machine) } imsic_bits = imsic_num_bits(s->aia_guests + 1); + if (s->have_rpmi) { +#ifndef CONFIG_LIBRPMI + error_report("RISC-V RPMI support is not compiled in"); + exit(1); +#else + if (kvm_enabled()) { + error_report("RISC-V RPMI support is not available with KVM"); + exit(1); + } +#endif + } /* Initialize sockets */ mmio_irqchip = pcie_irqchip = NULL; @@ -598,6 +700,28 @@ static void rvserver_ref_machine_init(MachineState *machine) memmap[RVSERVER_RESET_SYSCON].base, reset_syscon_io); +#ifdef CONFIG_LIBRPMI + if (s->have_rpmi) { + MachineClass *mc = MACHINE_GET_CLASS(machine); + const CPUArchIdList *possible_cpus = mc->possible_cpu_arch_ids(machine); + g_autofree uint32_t *rpmi_hart_ids = + g_new0(uint32_t, machine->smp.cpus); + RiscvRpmiConfig rpmi_cfg; + Error *local_err = NULL; + + for (i = 0; i < machine->smp.cpus; i++) { + rpmi_hart_ids[i] = possible_cpus->cpus[i].arch_id; + } + + rpmi_cfg = rvserver_ref_rpmi_config(s, rpmi_hart_ids, + machine->smp.cpus); + if (!riscv_rpmi_create(&rpmi_cfg, &local_err)) { + error_report_err(local_err); + exit(1); + } + } +#endif + riscv_gpex_pcie_init(system_memory, pcie_irqchip, &rvserver_ref_memmap[RVSERVER_PCIE_ECAM], &rvserver_ref_memmap[RVSERVER_PCIE_MMIO], @@ -652,6 +776,7 @@ static void rvserver_ref_machine_instance_init(Object *obj) "pflash1", RVSERVER_FLASH_SECTOR_SIZE); s->aia_guests = RVSERVER_IRQCHIP_DEFAULT_AIA_GUESTS; + s->have_rpmi = false; } static void rvserver_ref_machine_instance_finalize(Object *obj) @@ -687,6 +812,20 @@ static void rvserver_ref_set_aia_guests(Object *obj, const char *val, } } +static bool rvserver_ref_get_rpmi(Object *obj, Error **errp) +{ + RISCVServerRefMachineState *s = RISCV_SERVER_REF_MACHINE(obj); + + return s->have_rpmi; +} + +static void rvserver_ref_set_rpmi(Object *obj, bool value, Error **errp) +{ + RISCVServerRefMachineState *s = RISCV_SERVER_REF_MACHINE(obj); + + s->have_rpmi = value; +} + static const HotplugHandler * rvserver_machine_get_hotplug_handler(MachineState *ms, DeviceState *dev) { @@ -747,6 +886,12 @@ static void rvserver_ref_machine_class_init(ObjectClass *oc, const void *data) "should be between 0 and %d.", RVSERVER_IRQCHIP_MAX_GUESTS); object_class_property_set_description(oc, "aia-guests", str); + object_class_property_add_bool(oc, "rpmi", rvserver_ref_get_rpmi, + rvserver_ref_set_rpmi); + object_class_property_set_description( + oc, "rpmi", "Set on/off to enable/disable RISC-V RPMI devices; " + "requires librpmi and is unavailable with KVM"); + assert(!mc->get_hotplug_handler); mc->get_hotplug_handler = rvserver_machine_get_hotplug_handler; hc->plug = rvserver_machine_device_plug_cb; diff --git a/meson.build b/meson.build index 434a193efe..41f6bcb676 100644 --- a/meson.build +++ b/meson.build @@ -3444,6 +3444,10 @@ foreach target : target_dirs foreach k, v: config_devices config_devices_data.set(k, 1) endforeach + if target == 'riscv64-softmmu' and get_option('default_devices') and \ + not librpmi.found() + warning('riscv-server-ref machine will be built without RPMI support; install librpmi and configure with --enable-librpmi to enable RPMI') + endif config_devices_mak_list += config_devices_mak config_devices_h += {target: configure_file(output: target + '-config-devices.h', configuration: config_devices_data)}
