* [PATCH v2 00/10] riscv: add initial RPMI virt support
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 5:43 UTC (permalink / raw)
To: qemu-devel
Add optional librpmi support and the initial RISC-V virt RPMI
implementation. This series introduces the shared-memory transport, FDT
helpers, opt-in virt wiring, and the Base, System Reset, HSM, and System
Suspend service groups with qtest and functional coverage.
The implementation targets the RISC-V Platform Management Interface
(RPMI) Specification v1.0 Ratified and uses librpmi as the backend.
Changes since v1:
- Resend with the oss.qualcomm.com author, signoff, and mail metadata.
Subrahmanya Lingappa (10):
build: add optional librpmi dependency
hw/misc: add RISC-V RPMI transport and Base support
hw/riscv: add RPMI FDT helpers
hw/riscv/virt: add opt-in RPMI base support
riscv: add RPMI system reset service
riscv: add RPMI HSM service
riscv: add RPMI system suspend service
docs/system/riscv: document initial virt RPMI support
tests/qtest: cover initial RISC-V RPMI services
tests/functional: add initial RPMI virt guest smoke
Kconfig.host | 3 +
MAINTAINERS | 3 +
docs/system/riscv/virt.rst | 50 ++
hw/misc/Kconfig | 4 +
hw/misc/meson.build | 6 +
hw/misc/riscv_rpmi.c | 558 +++++++++++++++++
hw/misc/riscv_rpmi_hsm.c | 287 +++++++++
hw/misc/riscv_rpmi_internal.h | 42 ++
hw/misc/riscv_rpmi_sysreset.c | 206 +++++++
hw/misc/riscv_rpmi_syssusp.c | 191 ++++++
hw/riscv/Kconfig | 1 +
hw/riscv/meson.build | 1 +
hw/riscv/rpmi-fdt.c | 103 ++++
hw/riscv/virt.c | 167 +++++
include/hw/misc/riscv_rpmi.h | 126 ++++
include/hw/riscv/rpmi-fdt.h | 41 ++
include/hw/riscv/virt.h | 3 +
meson.build | 14 +
meson_options.txt | 2 +
scripts/meson-buildoptions.sh | 3 +
tests/functional/riscv64/meson.build | 2 +
tests/functional/riscv64/test_rpmi_virt.py | 172 ++++++
tests/qtest/meson.build | 2 +
tests/qtest/riscv-rpmi-test.c | 684 +++++++++++++++++++++
24 files changed, 2671 insertions(+)
create mode 100644 hw/misc/riscv_rpmi.c
create mode 100644 hw/misc/riscv_rpmi_hsm.c
create mode 100644 hw/misc/riscv_rpmi_internal.h
create mode 100644 hw/misc/riscv_rpmi_sysreset.c
create mode 100644 hw/misc/riscv_rpmi_syssusp.c
create mode 100644 hw/riscv/rpmi-fdt.c
create mode 100644 include/hw/misc/riscv_rpmi.h
create mode 100644 include/hw/riscv/rpmi-fdt.h
create mode 100644 tests/functional/riscv64/test_rpmi_virt.py
create mode 100644 tests/qtest/riscv-rpmi-test.c
--
2.43.0
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v2 01/10] build: add optional librpmi dependency
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 5:43 UTC (permalink / raw)
To: qemu-devel
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add a meson option and config probe for librpmi so RISC-V machines can
opt into RPMI support when the library is available. Keep the dependency
optional so default builds without librpmi continue to work.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
Kconfig.host | 3 +++
meson.build | 14 ++++++++++++++
meson_options.txt | 2 ++
scripts/meson-buildoptions.sh | 3 +++
4 files changed, 22 insertions(+)
diff --git a/Kconfig.host b/Kconfig.host
index 933425c74b..6f98dadb2e 100644
--- a/Kconfig.host
+++ b/Kconfig.host
@@ -8,6 +8,9 @@ config LINUX
config LIBCBOR
bool
+config LIBRPMI
+ bool
+
config GNUTLS
bool
diff --git a/meson.build b/meson.build
index 19e123423b..103b4649b8 100644
--- a/meson.build
+++ b/meson.build
@@ -2005,6 +2005,17 @@ if lzo.found() and not cc.links('''
endif
endif
+librpmi = not_found
+if not get_option('librpmi').auto() or have_system
+ librpmi = dependency('librpmi', required: get_option('librpmi'),
+ method: 'pkg-config')
+ if librpmi.found() and not cc.has_header_symbol('librpmi.h',
+ 'rpmi_service_group_logging_create',
+ dependencies: librpmi)
+ error('librpmi support requires the RPMI logging service group API')
+ endif
+endif
+
numa = not_found
if not get_option('numa').auto() or have_system or have_tools
numa = cc.find_library('numa', has_headers: ['numa.h'],
@@ -2427,6 +2438,7 @@ config_host_data.set('CONFIG_LINUX', host_os == 'linux')
config_host_data.set('CONFIG_POSIX', host_os != 'windows')
config_host_data.set('CONFIG_WIN32', host_os == 'windows')
config_host_data.set('CONFIG_LZO', lzo.found())
+config_host_data.set('CONFIG_LIBRPMI', librpmi.found())
config_host_data.set('CONFIG_MPATH', mpathpersist.found())
config_host_data.set('CONFIG_BLKIO', blkio.found())
if blkio.found()
@@ -3258,6 +3270,7 @@ host_kconfig = \
(multiprocess_allowed ? ['CONFIG_MULTIPROCESS_ALLOWED=y'] : []) + \
(vfio_user_server_allowed ? ['CONFIG_VFIO_USER_SERVER_ALLOWED=y'] : []) + \
(hv_balloon ? ['CONFIG_HV_BALLOON_POSSIBLE=y'] : []) + \
+ (librpmi.found() ? ['CONFIG_LIBRPMI=y'] : []) + \
(have_rust ? ['CONFIG_HAVE_RUST=y'] : [])
ignored = [ 'TARGET_XML_FILES', 'TARGET_ABI_DIR' ]
@@ -4971,6 +4984,7 @@ summary_info += {'TPM support': have_tpm}
summary_info += {'IGVM support': igvm}
summary_info += {'libssh support': libssh}
summary_info += {'lzo support': lzo}
+summary_info += {'librpmi support': librpmi}
summary_info += {'snappy support': snappy}
summary_info += {'bzip2 support': libbzip2}
summary_info += {'lzfse support': liblzfse}
diff --git a/meson_options.txt b/meson_options.txt
index a07cb47d35..9f773a7886 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -204,6 +204,8 @@ option('lzfse', type : 'feature', value : 'auto',
description: 'lzfse support for DMG images')
option('lzo', type : 'feature', value : 'auto',
description: 'lzo compression support')
+option('librpmi', type : 'feature', value : 'auto',
+ description: 'RISC-V RPMI support')
option('pvg', type: 'feature', value: 'auto',
description: 'macOS paravirtualized graphics support')
option('rbd', type : 'feature', value : 'auto',
diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
index c003985047..e8a20068e7 100644
--- a/scripts/meson-buildoptions.sh
+++ b/scripts/meson-buildoptions.sh
@@ -140,6 +140,7 @@ meson_options_help() {
printf "%s\n" ' libkeyutils Linux keyutils support'
printf "%s\n" ' libnfs libnfs block device driver'
printf "%s\n" ' libpmem libpmem support'
+ printf "%s\n" ' librpmi RISC-V RPMI support'
printf "%s\n" ' libssh ssh block device support'
printf "%s\n" ' libudev Use libudev to enumerate host devices'
printf "%s\n" ' libusb libusb support for USB passthrough'
@@ -376,6 +377,8 @@ _meson_option_parse() {
--disable-libnfs) printf "%s" -Dlibnfs=disabled ;;
--enable-libpmem) printf "%s" -Dlibpmem=enabled ;;
--disable-libpmem) printf "%s" -Dlibpmem=disabled ;;
+ --enable-librpmi) printf "%s" -Dlibrpmi=enabled ;;
+ --disable-librpmi) printf "%s" -Dlibrpmi=disabled ;;
--enable-libssh) printf "%s" -Dlibssh=enabled ;;
--disable-libssh) printf "%s" -Dlibssh=disabled ;;
--enable-libudev) printf "%s" -Dlibudev=enabled ;;
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v2 02/10] hw/misc: add RISC-V RPMI transport and Base support
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 5:43 UTC (permalink / raw)
To: qemu-devel
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
RPMI (RISC-V Platform Management Interface) is the RISC-V non-ISA
platform management specification. It defines a shared-memory transport,
message protocol, and service groups for platform management.
Add the QEMU transport backend and Base service plumbing for virt using
librpmi.
This implementation targets the RISC-V Platform Management Interface
(RPMI) Specification v1.0 Ratified:
https://github.com/riscv-non-isa/riscv-rpmi/releases/download/v1.0/riscv-rpmi.pdf
The librpmi backend is maintained at:
https://github.com/riscv-software-src/librpmi
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
MAINTAINERS | 2 +
hw/misc/Kconfig | 4 +
hw/misc/meson.build | 3 +
hw/misc/riscv_rpmi.c | 476 ++++++++++++++++++++++++++++++++++
hw/misc/riscv_rpmi_internal.h | 24 ++
include/hw/misc/riscv_rpmi.h | 90 +++++++
6 files changed, 599 insertions(+)
create mode 100644 hw/misc/riscv_rpmi.c
create mode 100644 hw/misc/riscv_rpmi_internal.h
create mode 100644 include/hw/misc/riscv_rpmi.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 93df53d87f..8e91a31757 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -359,6 +359,8 @@ F: target/riscv/
F: hw/char/riscv_htif.c
F: hw/riscv/
F: hw/intc/riscv*
+F: hw/misc/riscv_rpmi*
+F: include/hw/misc/riscv_rpmi.h
F: include/hw/char/riscv_htif.h
F: include/hw/riscv/
F: common-user/host/riscv*
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 1543ee6653..998944c342 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -258,3 +258,7 @@ config XLNX_ZYNQ_DDRC
bool
source macio/Kconfig
+
+config RISCV_RPMI
+ bool
+ depends on LIBRPMI
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 23265f6035..e7c2ebe28f 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -168,3 +168,6 @@ system_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa_ec.c'))
# HPPA devices
system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c'))
+system_ss.add(when: 'CONFIG_RISCV_RPMI', if_true: [files(
+ 'riscv_rpmi.c',
+), librpmi])
diff --git a/hw/misc/riscv_rpmi.c b/hw/misc/riscv_rpmi.c
new file mode 100644
index 0000000000..c4b115b659
--- /dev/null
+++ b/hw/misc/riscv_rpmi.c
@@ -0,0 +1,476 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI transport device.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "exec/cpu-common.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/misc/riscv_rpmi.h"
+#include "hw/misc/riscv_rpmi_internal.h"
+#include "migration/vmstate.h"
+#include "system/address-spaces.h"
+#include "system/runstate.h"
+#include "librpmi.h"
+#include "librpmi_env.h"
+
+void *rpmi_env_zalloc(rpmi_size_t size)
+{
+ return g_malloc0(size);
+}
+
+void rpmi_env_free(void *ptr)
+{
+ g_free(ptr);
+}
+
+void rpmi_env_writel(rpmi_uint64_t addr, rpmi_uint32_t val)
+{
+ cpu_physical_memory_write(addr, &val, sizeof(val));
+}
+
+static bool riscv_rpmi_addr_in_range(uint64_t addr, uint32_t len,
+ uint64_t base, uint64_t size)
+{
+ return len && size >= len && addr >= base && addr - base <= size - len;
+}
+
+static enum rpmi_error shmem_qemu_read(void *priv, rpmi_uint64_t addr,
+ void *buf, rpmi_uint32_t len)
+{
+ RiscvRpmiState *s = RISCV_RPMI(priv);
+
+ if (!riscv_rpmi_addr_in_range(addr, len, s->shmem_base, s->shmem_size) ||
+ addr != (rpmi_uint64_t)addr || len != (rpmi_uint32_t)len) {
+ return RPMI_ERR_INVALID_ADDR;
+ }
+
+ cpu_physical_memory_read(addr, buf, len);
+ return RPMI_SUCCESS;
+}
+
+static enum rpmi_error shmem_qemu_write(void *priv, rpmi_uint64_t addr,
+ const void *buf, rpmi_uint32_t len)
+{
+ RiscvRpmiState *s = RISCV_RPMI(priv);
+
+ if (!riscv_rpmi_addr_in_range(addr, len, s->shmem_base, s->shmem_size) ||
+ addr != (rpmi_uint64_t)addr || len != (rpmi_uint32_t)len) {
+ return RPMI_ERR_INVALID_ADDR;
+ }
+
+ cpu_physical_memory_write(addr, buf, len);
+ return RPMI_SUCCESS;
+}
+
+static enum rpmi_error shmem_qemu_fill(void *priv, rpmi_uint64_t addr,
+ char ch, rpmi_uint32_t len)
+{
+ while (len--) {
+ shmem_qemu_write(priv, addr++, &ch, sizeof(ch));
+ }
+
+ return RPMI_SUCCESS;
+}
+
+const struct rpmi_shmem_platform_ops rpmi_shmem_qemu_ops = {
+ .read = shmem_qemu_read,
+ .write = shmem_qemu_write,
+ .fill = shmem_qemu_fill,
+};
+
+static uint64_t riscv_rpmi_read(void *opaque, hwaddr offset, unsigned int size)
+{
+ RiscvRpmiState *s = opaque;
+
+ if (offset != 0 || size != 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid read at 0x%" HWADDR_PRIx " size %u\n",
+ __func__, offset, size);
+ return 0;
+ }
+
+ return s->doorbell;
+}
+
+static bool riscv_rpmi_transport_indices_valid(RiscvRpmiState *s);
+
+static void riscv_rpmi_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned int size)
+{
+ RiscvRpmiState *s = opaque;
+
+ if (offset != 0 || size != 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid write at 0x%" HWADDR_PRIx " size %u\n",
+ __func__, offset, size);
+ return;
+ }
+
+ s->doorbell = value;
+
+ if (value == 1) {
+ if (s->context && riscv_rpmi_transport_indices_valid(s)) {
+ rpmi_context_process_a2p_request(s->context);
+ rpmi_context_process_all_events(s->context);
+ }
+ s->doorbell = 0;
+ }
+}
+
+static const MemoryRegionOps riscv_rpmi_ops = {
+ .read = riscv_rpmi_read,
+ .write = riscv_rpmi_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static bool riscv_rpmi_queue_indices_valid(RiscvRpmiState *s,
+ uint64_t queue_base,
+ uint32_t queue_size)
+{
+ uint8_t *shmem = memory_region_get_ram_ptr(&s->shmem);
+ uint32_t data_slots = queue_size / RPMI_QUEUE_SLOT_SIZE - 2;
+ uint32_t head;
+ uint32_t tail;
+
+ head = ldl_le_p(shmem + queue_base);
+ tail = ldl_le_p(shmem + queue_base + RPMI_QUEUE_SLOT_SIZE);
+ if (head >= data_slots || tail >= data_slots) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid queue indices head %u tail %u slots %u\n",
+ __func__, head, tail, data_slots);
+ return false;
+ }
+
+ return true;
+}
+
+static bool riscv_rpmi_transport_indices_valid(RiscvRpmiState *s)
+{
+ if (!s->has_shmem || !s->a2p_req_size) {
+ return false;
+ }
+
+ if (!riscv_rpmi_queue_indices_valid(s, 0, s->a2p_req_size)) {
+ return false;
+ }
+
+ return riscv_rpmi_queue_indices_valid(s, s->a2p_req_size,
+ s->a2p_req_size);
+}
+
+static void riscv_rpmi_configure_base(RiscvRpmiState *s,
+ const RiscvRpmiConfig *cfg)
+{
+ s->platform_info = g_strdup(cfg->platform_info);
+ s->services = cfg->services;
+ s->service_count = cfg->service_count;
+
+ if (cfg->hart_count) {
+ s->hart_count = cfg->hart_count;
+ if (cfg->hart_ids) {
+ s->hart_ids = g_memdup2(cfg->hart_ids,
+ cfg->hart_count * sizeof(*cfg->hart_ids));
+ }
+ }
+}
+
+static void riscv_rpmi_init(Object *obj)
+{
+ RiscvRpmiState *s = RISCV_RPMI(obj);
+
+ memory_region_init_io(&s->mmio, obj, &riscv_rpmi_ops, s,
+ TYPE_RISCV_RPMI, RPMI_DBREG_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+}
+
+static void riscv_rpmi_reset(DeviceState *dev)
+{
+ RiscvRpmiState *s = RISCV_RPMI(dev);
+
+ s->doorbell = 0;
+
+ if (s->has_shmem) {
+ memset(memory_region_get_ram_ptr(&s->shmem), 0, s->shmem_size);
+ memory_region_set_dirty(&s->shmem, 0, s->shmem_size);
+ }
+
+}
+
+static void riscv_rpmi_cleanup(RiscvRpmiState *s)
+{
+
+
+ if (s->context) {
+ rpmi_context_destroy(s->context);
+ s->context = NULL;
+ }
+
+ if (s->transport) {
+ rpmi_transport_shmem_destroy(s->transport);
+ s->transport = NULL;
+ }
+
+ if (s->rpmi_shmem) {
+ rpmi_shmem_destroy(s->rpmi_shmem);
+ s->rpmi_shmem = NULL;
+ }
+
+ if (s->has_shmem) {
+ memory_region_del_subregion(get_system_memory(), &s->shmem);
+ s->has_shmem = false;
+ }
+}
+
+bool riscv_rpmi_service_enabled(RiscvRpmiState *s, RiscvRpmiServiceKind kind)
+{
+ uint32_t i;
+
+ for (i = 0; i < s->service_count; i++) {
+ if (s->services[i].kind == kind) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool riscv_rpmi_validate_config(RiscvRpmiState *s, Error **errp)
+{
+ uint64_t queue_bytes;
+
+ if (!s->shmem_size) {
+ error_setg(errp, "RPMI shared memory size must be non-zero");
+ return false;
+ }
+
+ if (s->shmem_base & (RPMI_QUEUE_SLOT_SIZE - 1)) {
+ error_setg(errp, "RPMI shared memory base must be %u-byte aligned",
+ RPMI_QUEUE_SLOT_SIZE);
+ return false;
+ }
+
+ if (!s->a2p_req_size) {
+ error_setg(errp, "RPMI A2P request queue size must be non-zero");
+ return false;
+ }
+
+ if (!s->hart_count || !s->hart_ids) {
+ error_setg(errp, "RPMI transport requires hart IDs");
+ return false;
+ }
+
+ for (uint32_t i = 0; i < s->hart_count; i++) {
+ for (uint32_t j = i + 1; j < s->hart_count; j++) {
+ if (s->hart_ids[i] == s->hart_ids[j]) {
+ error_setg(errp, "RPMI hart ID %u is duplicated",
+ s->hart_ids[i]);
+ return false;
+ }
+ }
+ }
+
+ if (s->service_count && !s->services) {
+ error_setg(errp, "RPMI service count requires service descriptors");
+ return false;
+ }
+
+ if (s->a2p_req_size % RPMI_QUEUE_SLOT_SIZE) {
+ error_setg(errp,
+ "RPMI A2P request queue size must be a multiple of %u",
+ RPMI_QUEUE_SLOT_SIZE);
+ return false;
+ }
+
+ if (s->p2a_req_size % RPMI_QUEUE_SLOT_SIZE) {
+ error_setg(errp,
+ "RPMI P2A request queue size must be a multiple of %u",
+ RPMI_QUEUE_SLOT_SIZE);
+ return false;
+ }
+
+ queue_bytes = 2 * (uint64_t)s->a2p_req_size +
+ 2 * (uint64_t)s->p2a_req_size;
+ if (queue_bytes > s->shmem_size) {
+ error_setg(errp,
+ "RPMI shared memory size 0x%" PRIx64
+ " is too small for queues 0x%" PRIx64,
+ s->shmem_size, queue_bytes);
+ return false;
+ }
+
+ return true;
+}
+
+static bool riscv_rpmi_add_service_group(RiscvRpmiState *s,
+ const RiscvRpmiServiceConfig *service,
+ Error **errp)
+{
+ switch (service->kind) {
+ default:
+ error_setg(errp, "unsupported RPMI service kind %d", service->kind);
+ return false;
+ }
+}
+
+static bool riscv_rpmi_init_services(RiscvRpmiState *s, Error **errp)
+{
+ uint32_t i;
+
+ for (i = 0; i < s->service_count; i++) {
+ if (!riscv_rpmi_add_service_group(s, &s->services[i], errp)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool riscv_rpmi_init_context(RiscvRpmiState *s, Error **errp)
+{
+ const char *platform_info = s->platform_info ?: RPMI_PLAT_INFO;
+
+ s->rpmi_shmem = rpmi_shmem_create("rpmi-shmem", s->shmem_base,
+ s->shmem_size,
+ &rpmi_shmem_qemu_ops, s);
+ if (!s->rpmi_shmem) {
+ error_setg(errp, "failed to create RPMI shared memory backend");
+ return false;
+ }
+
+ s->transport = rpmi_transport_shmem_create("rpmi-shmem-transport",
+ RPMI_QUEUE_SLOT_SIZE,
+ s->a2p_req_size,
+ s->p2a_req_size,
+ s->rpmi_shmem);
+ if (!s->transport) {
+ error_setg(errp, "failed to create RPMI shared memory transport");
+ return false;
+ }
+
+ s->context = rpmi_context_create("rpmi-virt", s->transport,
+ RPMI_SRVGRP_ID_MAX_COUNT,
+ RPMI_PRIVILEGE_M_MODE,
+ strlen(platform_info) + 1,
+ platform_info);
+ if (!s->context) {
+ error_setg(errp, "failed to create RPMI context");
+ return false;
+ }
+
+ return riscv_rpmi_init_services(s, errp);
+}
+
+static void riscv_rpmi_realize(DeviceState *dev, Error **errp)
+{
+ RiscvRpmiState *s = RISCV_RPMI(dev);
+ g_autofree char *name = NULL;
+
+ if (!riscv_rpmi_validate_config(s, errp)) {
+ return;
+ }
+
+ name = g_strdup_printf("rpmi-shmem@%" PRIx64, s->shmem_base);
+ if (!memory_region_init_ram(&s->shmem, OBJECT(dev), name, s->shmem_size,
+ errp)) {
+ return;
+ }
+
+ memory_region_add_subregion(get_system_memory(), s->shmem_base, &s->shmem);
+ s->has_shmem = true;
+
+ if (!riscv_rpmi_init_context(s, errp)) {
+ riscv_rpmi_cleanup(s);
+ }
+}
+
+static void riscv_rpmi_unrealize(DeviceState *dev)
+{
+ riscv_rpmi_cleanup(RISCV_RPMI(dev));
+}
+
+static const VMStateDescription riscv_rpmi_vmstate = {
+ .name = TYPE_RISCV_RPMI,
+ .unmigratable = 1,
+};
+
+static const Property riscv_rpmi_properties[] = {
+ DEFINE_PROP_UINT64("shmem-base", RiscvRpmiState, shmem_base, 0),
+ DEFINE_PROP_UINT64("shmem-size", RiscvRpmiState, shmem_size, 0),
+ DEFINE_PROP_UINT32("a2p-req-size", RiscvRpmiState, a2p_req_size, 0),
+ DEFINE_PROP_UINT32("p2a-req-size", RiscvRpmiState, p2a_req_size, 0),
+};
+
+static void riscv_rpmi_finalize(Object *obj)
+{
+ RiscvRpmiState *s = RISCV_RPMI(obj);
+
+ g_free(s->platform_info);
+ g_free(s->hart_ids);
+}
+
+static void riscv_rpmi_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = riscv_rpmi_realize;
+ dc->unrealize = riscv_rpmi_unrealize;
+ dc->vmsd = &riscv_rpmi_vmstate;
+ device_class_set_legacy_reset(dc, riscv_rpmi_reset);
+ device_class_set_props(dc, riscv_rpmi_properties);
+}
+
+static const TypeInfo riscv_rpmi_info = {
+ .name = TYPE_RISCV_RPMI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RiscvRpmiState),
+ .instance_init = riscv_rpmi_init,
+ .instance_finalize = riscv_rpmi_finalize,
+ .class_init = riscv_rpmi_class_init,
+};
+
+static void riscv_rpmi_register_types(void)
+{
+ type_register_static(&riscv_rpmi_info);
+}
+
+type_init(riscv_rpmi_register_types)
+
+DeviceState *riscv_rpmi_create(const RiscvRpmiConfig *cfg, Error **errp)
+{
+ DeviceState *dev;
+ RiscvRpmiState *s;
+
+ if (!cfg) {
+ error_setg(errp, "missing RPMI configuration");
+ return NULL;
+ }
+
+ dev = qdev_new(TYPE_RISCV_RPMI);
+ qdev_prop_set_uint64(dev, "shmem-base", cfg->shmem_base);
+ qdev_prop_set_uint64(dev, "shmem-size", cfg->shmem_size);
+ qdev_prop_set_uint32(dev, "a2p-req-size", cfg->a2p_req_size);
+ qdev_prop_set_uint32(dev, "p2a-req-size", cfg->p2a_req_size);
+
+ s = RISCV_RPMI(dev);
+ riscv_rpmi_configure_base(s, cfg);
+
+ if (!sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), errp)) {
+ return NULL;
+ }
+
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, cfg->doorbell_base);
+ return dev;
+}
diff --git a/hw/misc/riscv_rpmi_internal.h b/hw/misc/riscv_rpmi_internal.h
new file mode 100644
index 0000000000..447c0acb55
--- /dev/null
+++ b/hw/misc/riscv_rpmi_internal.h
@@ -0,0 +1,24 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI internal service helpers.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#ifndef HW_MISC_RISCV_RPMI_INTERNAL_H
+#define HW_MISC_RISCV_RPMI_INTERNAL_H
+
+#include "qapi/error.h"
+#include "hw/misc/riscv_rpmi.h"
+#include "librpmi.h"
+
+#define RPMI_PLAT_INFO "QEMU RISC-V RPMI"
+
+extern const struct rpmi_shmem_platform_ops rpmi_shmem_qemu_ops;
+bool riscv_rpmi_service_enabled(RiscvRpmiState *s,
+ RiscvRpmiServiceKind kind);
+
+#endif
diff --git a/include/hw/misc/riscv_rpmi.h b/include/hw/misc/riscv_rpmi.h
new file mode 100644
index 0000000000..cb2658e57e
--- /dev/null
+++ b/include/hw/misc/riscv_rpmi.h
@@ -0,0 +1,90 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI definitions shared by RPMI transport and FDT helpers.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#ifndef HW_MISC_RISCV_RPMI_H
+#define HW_MISC_RISCV_RPMI_H
+
+#include "exec/hwaddr.h"
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+#include "qemu/notify.h"
+
+#define RPMI_QUEUE_SLOT_SIZE 64
+#define RPMI_DBREG_SIZE 0x1000
+
+#define RPMI_ALL_NUM_QUEUES 4
+#define RPMI_A2P_NUM_QUEUES 2
+#define RPMI_ALL_NUM_REGS (RPMI_ALL_NUM_QUEUES + 1)
+#define RPMI_A2P_NUM_REGS (RPMI_A2P_NUM_QUEUES + 1)
+
+#define VIRT_RPMI_A2P_REQ_SIZE (16 * RPMI_QUEUE_SLOT_SIZE)
+#define VIRT_RPMI_P2A_REQ_SIZE 0
+
+
+#define TYPE_RISCV_RPMI "riscv-rpmi"
+OBJECT_DECLARE_SIMPLE_TYPE(RiscvRpmiState, RISCV_RPMI)
+
+struct rpmi_context;
+struct rpmi_service_group;
+struct rpmi_shmem;
+struct rpmi_transport;
+
+typedef enum RiscvRpmiServiceKind {
+ RISCV_RPMI_SERVICE_INVALID = 0,
+} RiscvRpmiServiceKind;
+
+typedef struct RiscvRpmiServiceConfig {
+ RiscvRpmiServiceKind kind;
+ const char *node_name;
+ const char *compatible;
+ uint32_t service_group;
+ bool has_mpxy_channel;
+ uint32_t mpxy_channel;
+} RiscvRpmiServiceConfig;
+
+typedef struct RiscvRpmiConfig {
+ hwaddr doorbell_base;
+ hwaddr shmem_base;
+ hwaddr shmem_size;
+ uint32_t a2p_req_size;
+ uint32_t p2a_req_size;
+ const char *platform_info;
+
+ const uint32_t *hart_ids;
+ uint32_t hart_count;
+ const RiscvRpmiServiceConfig *services;
+ uint32_t service_count;
+} RiscvRpmiConfig;
+
+struct RiscvRpmiState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ MemoryRegion shmem;
+ uint64_t shmem_base;
+ uint64_t shmem_size;
+ uint32_t a2p_req_size;
+ uint32_t p2a_req_size;
+ char *platform_info;
+
+ uint32_t *hart_ids;
+ uint32_t hart_count;
+ const RiscvRpmiServiceConfig *services;
+ uint32_t service_count;
+ uint32_t doorbell;
+ struct rpmi_shmem *rpmi_shmem;
+ struct rpmi_transport *transport;
+ struct rpmi_context *context;
+ bool has_shmem;
+};
+
+DeviceState *riscv_rpmi_create(const RiscvRpmiConfig *cfg, Error **errp);
+
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v2 03/10] hw/riscv: add RPMI FDT helpers
2026-07-10 8:39 ` [PATCH v2 " Subrahmanya Lingappa
(?)
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
-1 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 5:43 UTC (permalink / raw)
To: qemu-devel
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add helper routines for describing RPMI shared-memory mailbox transports
and service child nodes in the RISC-V virt device tree.
The helpers keep RPMI node construction in one place so the virt machine
can add service groups without open-coding compatible strings, channel
IDs, interrupts, and shared-memory references at each call site.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
hw/riscv/meson.build | 1 +
hw/riscv/rpmi-fdt.c | 103 ++++++++++++++++++++++++++++++++++++
include/hw/riscv/rpmi-fdt.h | 30 +++++++++++
3 files changed, 134 insertions(+)
create mode 100644 hw/riscv/rpmi-fdt.c
create mode 100644 include/hw/riscv/rpmi-fdt.h
diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build
index b70a054579..529ec273fd 100644
--- a/hw/riscv/meson.build
+++ b/hw/riscv/meson.build
@@ -3,6 +3,7 @@ riscv_ss.add(files('boot.c'))
riscv_ss.add(files('fdt-common.c'))
riscv_ss.add(when: 'CONFIG_RISCV_NUMA', if_true: files('numa.c'))
riscv_ss.add(files('riscv_hart.c'))
+riscv_ss.add(files('rpmi-fdt.c'))
riscv_ss.add(when: 'CONFIG_OPENTITAN', if_true: files('opentitan.c'))
riscv_ss.add(when: 'CONFIG_RISCV_VIRT', if_true: files('virt.c'))
riscv_ss.add(when: 'CONFIG_SHAKTI_C', if_true: files('shakti_c.c'))
diff --git a/hw/riscv/rpmi-fdt.c b/hw/riscv/rpmi-fdt.c
new file mode 100644
index 0000000000..b2b3daea1c
--- /dev/null
+++ b/hw/riscv/rpmi-fdt.c
@@ -0,0 +1,103 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI device-tree helpers
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bswap.h"
+#include "hw/riscv/rpmi-fdt.h"
+#include "hw/misc/riscv_rpmi.h"
+#include "system/device_tree.h"
+
+void riscv_rpmi_fdt_add_mbox(void *fdt,
+ const RiscvRpmiFdtMboxConfig *cfg,
+ uint32_t *phandle,
+ uint32_t *mbox_handle)
+{
+ g_autofree char *name = NULL;
+ hwaddr a2p_req_base, p2a_ack_base, p2a_req_base, a2p_ack_base;
+ static const char * const regnames_all[RPMI_ALL_NUM_REGS] = {
+ "a2p-req", "p2a-ack", "p2a-req", "a2p-ack", "a2p-doorbell"
+ };
+ static const char * const regnames_a2p[RPMI_A2P_NUM_REGS] = {
+ "a2p-req", "p2a-ack", "a2p-doorbell"
+ };
+
+ a2p_req_base = cfg->shmem_base;
+ p2a_ack_base = a2p_req_base + cfg->a2p_req_size;
+ p2a_req_base = p2a_ack_base + cfg->a2p_req_size;
+ a2p_ack_base = p2a_req_base + cfg->p2a_req_size;
+
+ *mbox_handle = (*phandle)++;
+ name = g_strdup_printf("/soc/mailbox@%" HWADDR_PRIx, cfg->shmem_base);
+ qemu_fdt_add_subnode(fdt, name);
+ qemu_fdt_setprop_string(fdt, name, "compatible", "riscv,rpmi-shmem-mbox");
+ qemu_fdt_setprop_cell(fdt, name, "riscv,slot-size", RPMI_QUEUE_SLOT_SIZE);
+ qemu_fdt_setprop_cell(fdt, name, "#mbox-cells", 1);
+
+ if (cfg->p2a_req_size) {
+ qemu_fdt_setprop_string_array(fdt, name, "reg-names",
+ (char **)®names_all,
+ ARRAY_SIZE(regnames_all));
+ qemu_fdt_setprop_cells(fdt, name, "reg",
+ (uint32_t)(a2p_req_base >> 32), (uint32_t)a2p_req_base,
+ 0x0, cfg->a2p_req_size,
+ (uint32_t)(p2a_ack_base >> 32), (uint32_t)p2a_ack_base,
+ 0x0, cfg->a2p_req_size,
+ (uint32_t)(p2a_req_base >> 32), (uint32_t)p2a_req_base,
+ 0x0, cfg->p2a_req_size,
+ (uint32_t)(a2p_ack_base >> 32), (uint32_t)a2p_ack_base,
+ 0x0, cfg->p2a_req_size,
+ (uint32_t)(cfg->doorbell_base >> 32), (uint32_t)cfg->doorbell_base,
+ 0x0, cfg->doorbell_size);
+ } else {
+ qemu_fdt_setprop_string_array(fdt, name, "reg-names",
+ (char **)®names_a2p,
+ ARRAY_SIZE(regnames_a2p));
+ qemu_fdt_setprop_cells(fdt, name, "reg",
+ (uint32_t)(a2p_req_base >> 32), (uint32_t)a2p_req_base,
+ 0x0, cfg->a2p_req_size,
+ (uint32_t)(p2a_ack_base >> 32), (uint32_t)p2a_ack_base,
+ 0x0, cfg->a2p_req_size,
+ (uint32_t)(cfg->doorbell_base >> 32), (uint32_t)cfg->doorbell_base,
+ 0x0, cfg->doorbell_size);
+ }
+
+ qemu_fdt_setprop_cells(fdt, name, "phandle", *mbox_handle);
+}
+
+void riscv_rpmi_fdt_add_service(void *fdt, hwaddr shmem_base,
+ const char *node_name,
+ const char *compatible,
+ uint32_t mbox_handle,
+ uint32_t service_group,
+ bool has_mpxy_channel,
+ uint32_t mpxy_channel)
+{
+ g_autofree char *name = g_strdup_printf("/soc/mailbox@%" HWADDR_PRIx "/%s",
+ shmem_base, node_name);
+
+ qemu_fdt_add_subnode(fdt, name);
+ qemu_fdt_setprop_string(fdt, name, "compatible", compatible);
+ qemu_fdt_setprop_cells(fdt, name, "mboxes", mbox_handle, service_group);
+ if (has_mpxy_channel) {
+ qemu_fdt_setprop_cell(fdt, name, "riscv,sbi-mpxy-channel-id",
+ mpxy_channel);
+ }
+}
+
+void riscv_rpmi_fdt_add_service_node(void *fdt, hwaddr shmem_base,
+ const RiscvRpmiServiceConfig *service,
+ uint32_t mbox_handle)
+{
+ riscv_rpmi_fdt_add_service(fdt, shmem_base, service->node_name,
+ service->compatible, mbox_handle,
+ service->service_group,
+ service->has_mpxy_channel,
+ service->mpxy_channel);
+}
diff --git a/include/hw/riscv/rpmi-fdt.h b/include/hw/riscv/rpmi-fdt.h
new file mode 100644
index 0000000000..f053ae5d57
--- /dev/null
+++ b/include/hw/riscv/rpmi-fdt.h
@@ -0,0 +1,30 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI device-tree helpers
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#ifndef HW_RISCV_RPMI_FDT_H
+#define HW_RISCV_RPMI_FDT_H
+
+#include "exec/hwaddr.h"
+#include "hw/misc/riscv_rpmi.h"
+
+typedef struct RiscvRpmiFdtMboxConfig {
+ hwaddr shmem_base;
+ hwaddr doorbell_base;
+ uint32_t a2p_req_size;
+ uint32_t p2a_req_size;
+ uint32_t doorbell_size;
+} RiscvRpmiFdtMboxConfig;
+
+void riscv_rpmi_fdt_add_mbox(void *fdt,
+ const RiscvRpmiFdtMboxConfig *cfg,
+ uint32_t *phandle,
+ uint32_t *mbox_handle);
+
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v2 04/10] hw/riscv/virt: add opt-in RPMI base support
2026-07-10 8:39 ` [PATCH v2 " Subrahmanya Lingappa
(?)
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
-1 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 5:43 UTC (permalink / raw)
To: qemu-devel
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add the virt machine rpmi=on/off option and wire the initial RPMI Base
transport into machine realization.
The option remains disabled by default and requires TCG plus librpmi.
Invalid accelerator/build combinations fail during machine
initialization. When enabled, virt allocates the RPMI shared-memory
transport, creates the RPMI device, configures Base service metadata,
and emits the corresponding device-tree nodes for firmware discovery.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
hw/riscv/Kconfig | 1 +
hw/riscv/virt.c | 96 ++++++++++++++++++++++++++++++++++++
include/hw/misc/riscv_rpmi.h | 10 ++++
include/hw/riscv/virt.h | 3 ++
4 files changed, 110 insertions(+)
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index 54e41a6afc..4e25be113a 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -49,6 +49,7 @@ config RISCV_VIRT
imply VIRTIO_VGA
imply TEST_DEVICES
imply TPM_TIS_SYSBUS
+ imply RISCV_RPMI
select DEVICE_TREE
select RISCV_NUMA
select GOLDFISH_RTC
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index 33775a61fd..e52ccfae1f 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -45,6 +45,8 @@
#include "hw/intc/riscv_aplic.h"
#include "hw/intc/sifive_plic.h"
#include "hw/misc/sifive_test.h"
+#include "hw/misc/riscv_rpmi.h"
+#include "hw/riscv/rpmi-fdt.h"
#include "hw/core/platform-bus.h"
#include "chardev/char.h"
#include "system/device_tree.h"
@@ -97,6 +99,8 @@ static const MemMapEntry virt_memmap[] = {
[VIRT_UART0] = { 0x10000000, 0x100 },
[VIRT_VIRTIO] = { 0x10001000, 0x1000 },
[VIRT_FW_CFG] = { 0x10100000, 0x18 },
+ [VIRT_RPMI_SHMEM] = { 0x10200000, 0x20000 },
+ [VIRT_RPMI_DOORBELL] = { 0x10230000, 0x1000 },
[VIRT_FLASH] = { 0x20000000, 0x4000000 },
[VIRT_IMSIC_M] = { 0x24000000, VIRT_IMSIC_MAX_SIZE },
[VIRT_IMSIC_S] = { 0x28000000, VIRT_IMSIC_MAX_SIZE },
@@ -1002,6 +1006,40 @@ static void create_fdt_iommu(RISCVVirtState *s, uint16_t bdf)
s->pci_iommu_bdf = bdf;
}
+
+static RiscvRpmiConfig virt_rpmi_config(RISCVVirtState *s,
+ const uint32_t *hart_ids,
+ uint32_t hart_count)
+{
+ return (RiscvRpmiConfig) {
+ .doorbell_base = s->memmap[VIRT_RPMI_DOORBELL].base,
+ .shmem_base = s->memmap[VIRT_RPMI_SHMEM].base,
+ .shmem_size = s->memmap[VIRT_RPMI_SHMEM].size,
+ .a2p_req_size = VIRT_RPMI_A2P_REQ_SIZE,
+ .p2a_req_size = VIRT_RPMI_P2A_REQ_SIZE,
+ .platform_info = "QEMU RISC-V virt RPMI",
+ .hart_ids = hart_ids,
+ .hart_count = hart_count,
+ };
+}
+
+static void create_fdt_rpmi(RISCVVirtState *s, uint32_t *phandle,
+ uint32_t msi_phandle)
+{
+ RiscvRpmiConfig rpmi_cfg = virt_rpmi_config(s, NULL, 0);
+ uint32_t rpmi_mbox_handle;
+ RiscvRpmiFdtMboxConfig cfg = {
+ .shmem_base = rpmi_cfg.shmem_base,
+ .doorbell_base = rpmi_cfg.doorbell_base,
+ .a2p_req_size = rpmi_cfg.a2p_req_size,
+ .p2a_req_size = rpmi_cfg.p2a_req_size,
+ .doorbell_size = s->memmap[VIRT_RPMI_DOORBELL].size,
+ };
+
+ riscv_rpmi_fdt_add_mbox(MACHINE(s)->fdt, &cfg, phandle,
+ &rpmi_mbox_handle);
+}
+
static void finalize_fdt(RISCVVirtState *s)
{
uint32_t phandle = 1, irq_mmio_phandle = 1, msi_pcie_phandle = 1;
@@ -1021,6 +1059,10 @@ static void finalize_fdt(RISCVVirtState *s)
create_fdt_pcie(s, irq_pcie_phandle, msi_pcie_phandle,
iommu_sys_phandle);
+ if (s->have_rpmi) {
+ create_fdt_rpmi(s, &phandle, msi_pcie_phandle);
+ }
+
create_fdt_reset(s, &phandle);
create_fdt_uart(s, irq_mmio_phandle);
@@ -1410,6 +1452,18 @@ static void virt_machine_init(MachineState *machine)
exit(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 = virtio_irqchip = pcie_irqchip = NULL;
for (i = 0; i < socket_count; i++) {
@@ -1552,6 +1606,25 @@ static void virt_machine_init(MachineState *machine)
/* SiFive Test MMIO device */
sifive_test_create(s->memmap[VIRT_TEST].base);
+ 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 = virt_rpmi_config(s, rpmi_hart_ids, machine->smp.cpus);
+ if (!riscv_rpmi_create(&rpmi_cfg, &local_err)) {
+ error_report_err(local_err);
+ exit(1);
+ }
+ }
+
/* VirtIO MMIO devices */
for (i = 0; i < VIRTIO_COUNT; i++) {
sysbus_create_simple("virtio-mmio",
@@ -1636,6 +1709,7 @@ static void virt_machine_instance_init(Object *obj)
s->oem_id = g_strndup(ACPI_BUILD_APPNAME6, 6);
s->oem_table_id = g_strndup(ACPI_BUILD_APPNAME8, 8);
+ s->have_rpmi = false;
s->acpi = ON_OFF_AUTO_AUTO;
s->iommu_sys = ON_OFF_AUTO_AUTO;
}
@@ -1710,6 +1784,21 @@ static void virt_set_aclint(Object *obj, bool value, Error **errp)
s->have_aclint = value;
}
+
+static bool virt_get_rpmi(Object *obj, Error **errp)
+{
+ RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
+
+ return s->have_rpmi;
+}
+
+static void virt_set_rpmi(Object *obj, bool value, Error **errp)
+{
+ RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
+
+ s->have_rpmi = value;
+}
+
bool virt_is_iommu_sys_enabled(RISCVVirtState *s)
{
return s->iommu_sys == ON_OFF_AUTO_ON;
@@ -1831,6 +1920,13 @@ static void virt_machine_class_init(ObjectClass *oc, const void *data)
"enable/disable emulating "
"ACLINT devices");
+
+ object_class_property_add_bool(oc, "rpmi", virt_get_rpmi,
+ virt_set_rpmi);
+ object_class_property_set_description(oc, "rpmi",
+ "Set on/off to enable/disable "
+ "RISC-V RPMI devices");
+
object_class_property_add_str(oc, "aia", virt_get_aia,
virt_set_aia);
object_class_property_set_description(oc, "aia",
diff --git a/include/hw/misc/riscv_rpmi.h b/include/hw/misc/riscv_rpmi.h
index cb2658e57e..b5d8a32f9b 100644
--- a/include/hw/misc/riscv_rpmi.h
+++ b/include/hw/misc/riscv_rpmi.h
@@ -85,6 +85,16 @@ struct RiscvRpmiState {
bool has_shmem;
};
+#ifdef CONFIG_LIBRPMI
DeviceState *riscv_rpmi_create(const RiscvRpmiConfig *cfg, Error **errp);
+#else
+static inline DeviceState *riscv_rpmi_create(const RiscvRpmiConfig *cfg,
+ Error **errp)
+{
+ (void)cfg;
+ (void)errp;
+ return NULL;
+}
+#endif
#endif
diff --git a/include/hw/riscv/virt.h b/include/hw/riscv/virt.h
index 18a2a323a3..f7c48613bf 100644
--- a/include/hw/riscv/virt.h
+++ b/include/hw/riscv/virt.h
@@ -55,6 +55,7 @@ struct RISCVVirtState {
int fdt_size;
bool have_aclint;
+ bool have_rpmi;
RISCVVirtAIAType aia_type;
int aia_guests;
char *oem_id;
@@ -79,6 +80,8 @@ enum {
VIRT_UART0,
VIRT_VIRTIO,
VIRT_FW_CFG,
+ VIRT_RPMI_SHMEM,
+ VIRT_RPMI_DOORBELL,
VIRT_IMSIC_M,
VIRT_IMSIC_S,
VIRT_FLASH,
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v2 05/10] riscv: add RPMI system reset service
2026-07-10 8:39 ` [PATCH v2 " Subrahmanya Lingappa
(?)
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
-1 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 5:43 UTC (permalink / raw)
To: qemu-devel
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add a QEMU implementation of the RPMI System Reset service group.
The service advertises shutdown and cold reboot reset types, validates
guest requests, and dispatches accepted requests through the machine
reset and shutdown callbacks. This lets firmware use RPMI reset commands
while keeping the board-specific reset policy in the RISC-V virt machine.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
hw/misc/meson.build | 1 +
hw/misc/riscv_rpmi.c | 38 ++++++-
hw/misc/riscv_rpmi_internal.h | 9 ++
hw/misc/riscv_rpmi_sysreset.c | 206 ++++++++++++++++++++++++++++++++++
hw/riscv/virt.c | 40 +++++++
include/hw/misc/riscv_rpmi.h | 13 ++-
include/hw/riscv/rpmi-fdt.h | 11 ++
7 files changed, 313 insertions(+), 5 deletions(-)
create mode 100644 hw/misc/riscv_rpmi_sysreset.c
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index e7c2ebe28f..bca7819ee0 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -170,4 +170,5 @@ system_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa_ec.c'))
system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c'))
system_ss.add(when: 'CONFIG_RISCV_RPMI', if_true: [files(
'riscv_rpmi.c',
+ 'riscv_rpmi_sysreset.c',
), librpmi])
diff --git a/hw/misc/riscv_rpmi.c b/hw/misc/riscv_rpmi.c
index c4b115b659..7a3f570ee7 100644
--- a/hw/misc/riscv_rpmi.c
+++ b/hw/misc/riscv_rpmi.c
@@ -175,6 +175,8 @@ static void riscv_rpmi_configure_base(RiscvRpmiState *s,
const RiscvRpmiConfig *cfg)
{
s->platform_info = g_strdup(cfg->platform_info);
+ s->machine_ops = cfg->machine_ops;
+ s->machine_opaque = cfg->machine_opaque;
s->services = cfg->services;
s->service_count = cfg->service_count;
@@ -211,7 +213,9 @@ static void riscv_rpmi_reset(DeviceState *dev)
static void riscv_rpmi_cleanup(RiscvRpmiState *s)
{
-
+ for (uint32_t i = ARRAY_SIZE(riscv_rpmi_service_ops); i > 0; i--) {
+ riscv_rpmi_service_ops[i - 1].remove(s);
+ }
if (s->context) {
rpmi_context_destroy(s->context);
@@ -247,6 +251,30 @@ bool riscv_rpmi_service_enabled(RiscvRpmiState *s, RiscvRpmiServiceKind kind)
return false;
}
+bool riscv_rpmi_context_add_group(RiscvRpmiState *s,
+ struct rpmi_service_group *group,
+ const char *name,
+ Error **errp)
+{
+ enum rpmi_error rc;
+
+ rc = rpmi_context_add_group(s->context, group);
+ if (rc != RPMI_SUCCESS) {
+ error_setg(errp, "failed to add RPMI %s service group: %d", name, rc);
+ return false;
+ }
+
+ return true;
+}
+
+void riscv_rpmi_context_remove_group(RiscvRpmiState *s,
+ struct rpmi_service_group *group)
+{
+ if (s->context && group) {
+ rpmi_context_remove_group(s->context, group);
+ }
+}
+
static bool riscv_rpmi_validate_config(RiscvRpmiState *s, Error **errp)
{
uint64_t queue_bytes;
@@ -318,11 +346,15 @@ static bool riscv_rpmi_add_service_group(RiscvRpmiState *s,
const RiscvRpmiServiceConfig *service,
Error **errp)
{
- switch (service->kind) {
- default:
+ const RiscvRpmiServiceOps *ops;
+
+ ops = riscv_rpmi_service_ops_by_kind(service->kind);
+ if (!ops) {
error_setg(errp, "unsupported RPMI service kind %d", service->kind);
return false;
}
+
+ return ops->add(s, errp);
}
static bool riscv_rpmi_init_services(RiscvRpmiState *s, Error **errp)
diff --git a/hw/misc/riscv_rpmi_internal.h b/hw/misc/riscv_rpmi_internal.h
index 447c0acb55..fe09804e69 100644
--- a/hw/misc/riscv_rpmi_internal.h
+++ b/hw/misc/riscv_rpmi_internal.h
@@ -20,5 +20,14 @@
extern const struct rpmi_shmem_platform_ops rpmi_shmem_qemu_ops;
bool riscv_rpmi_service_enabled(RiscvRpmiState *s,
RiscvRpmiServiceKind kind);
+bool riscv_rpmi_context_add_group(RiscvRpmiState *s,
+ struct rpmi_service_group *group,
+ const char *name,
+ Error **errp);
+void riscv_rpmi_context_remove_group(RiscvRpmiState *s,
+ struct rpmi_service_group *group);
+
+bool riscv_rpmi_sysreset_add(RiscvRpmiState *s, Error **errp);
+void riscv_rpmi_sysreset_remove(RiscvRpmiState *s);
#endif
diff --git a/hw/misc/riscv_rpmi_sysreset.c b/hw/misc/riscv_rpmi_sysreset.c
new file mode 100644
index 0000000000..da6ef4c566
--- /dev/null
+++ b/hw/misc/riscv_rpmi_sysreset.c
@@ -0,0 +1,206 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI System Reset service.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#include "qemu/osdep.h"
+#include "riscv_rpmi_internal.h"
+#include "qemu/log.h"
+#include "librpmi_env.h"
+
+typedef struct RiscvRpmiSysresetType {
+ uint32_t type;
+ void (*action)(RiscvRpmiState *s);
+} RiscvRpmiSysresetType;
+
+typedef struct RiscvRpmiSysresetGroup {
+ struct rpmi_service_group group;
+ struct rpmi_service services[RPMI_SYSRST_SRV_ID_MAX];
+ RiscvRpmiState *rpmi;
+} RiscvRpmiSysresetGroup;
+
+static void riscv_rpmi_sysreset_reboot(RiscvRpmiState *s)
+{
+ const RiscvRpmiMachineOps *ops = s->machine_ops;
+
+ if (ops && ops->system_reset) {
+ ops->system_reset(s->machine_opaque);
+ }
+}
+
+static void riscv_rpmi_sysreset_shutdown(RiscvRpmiState *s)
+{
+ const RiscvRpmiMachineOps *ops = s->machine_ops;
+
+ if (ops && ops->system_shutdown) {
+ ops->system_shutdown(s->machine_opaque);
+ }
+}
+
+static const RiscvRpmiSysresetType riscv_rpmi_sysreset_types[] = {
+ {
+ .type = RPMI_SYSRST_TYPE_SHUTDOWN,
+ .action = riscv_rpmi_sysreset_shutdown,
+ }, {
+ .type = RPMI_SYSRST_TYPE_COLD_REBOOT,
+ .action = riscv_rpmi_sysreset_reboot,
+ },
+};
+
+static const RiscvRpmiSysresetType *riscv_rpmi_sysreset_type_by_id(
+ uint32_t reset_type)
+{
+ for (uint32_t index = 0; index < ARRAY_SIZE(riscv_rpmi_sysreset_types);
+ index++) {
+ if (riscv_rpmi_sysreset_types[index].type == reset_type) {
+ return &riscv_rpmi_sysreset_types[index];
+ }
+ }
+
+ return NULL;
+}
+
+static void riscv_rpmi_do_system_reset(RiscvRpmiState *s,
+ rpmi_uint32_t reset_type)
+{
+ const RiscvRpmiSysresetType *type;
+
+ type = riscv_rpmi_sysreset_type_by_id(reset_type);
+ if (type) {
+ type->action(s);
+ return;
+ }
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: unsupported reset type %u\n",
+ __func__, reset_type);
+}
+
+static enum rpmi_error riscv_rpmi_sysreset_get_attributes(
+ struct rpmi_service_group *group, struct rpmi_service *service,
+ struct rpmi_transport *trans, rpmi_uint16_t request_data_len,
+ const rpmi_uint8_t *request_data, rpmi_uint16_t *response_data_len,
+ rpmi_uint8_t *response_data)
+{
+ uint32_t reset_type = ldl_le_p(request_data);
+ uint32_t *resp = (uint32_t *)response_data;
+
+ *response_data_len = 2 * sizeof(*resp);
+ stl_le_p(&resp[0], RPMI_SUCCESS);
+ stl_le_p(&resp[1], riscv_rpmi_sysreset_type_by_id(reset_type) ?
+ RPMI_SYSRST_ATTRS_FLAGS_RESETTYPE : 0);
+
+ return RPMI_SUCCESS;
+}
+
+static enum rpmi_error riscv_rpmi_sysreset_do_reset(
+ struct rpmi_service_group *group, struct rpmi_service *service,
+ struct rpmi_transport *trans, rpmi_uint16_t request_data_len,
+ const rpmi_uint8_t *request_data, rpmi_uint16_t *response_data_len,
+ rpmi_uint8_t *response_data)
+{
+ RiscvRpmiSysresetGroup *sysreset = group->priv;
+ uint32_t reset_type = ldl_le_p(request_data);
+ uint32_t *resp = (uint32_t *)response_data;
+
+ *response_data_len = sizeof(*resp);
+
+ if (!riscv_rpmi_sysreset_type_by_id(reset_type)) {
+ stl_le_p(resp, (uint32_t)RPMI_ERR_INVALID_PARAM);
+ return RPMI_SUCCESS;
+ }
+
+ stl_le_p(resp, RPMI_SUCCESS);
+ riscv_rpmi_do_system_reset(sysreset->rpmi, reset_type);
+ return RPMI_SUCCESS;
+}
+
+static const struct rpmi_service riscv_rpmi_sysreset_services[] = {
+ [RPMI_SYSRST_SRV_ENABLE_NOTIFICATION] = {
+ .service_id = RPMI_SYSRST_SRV_ENABLE_NOTIFICATION,
+ .min_a2p_request_datalen = 8,
+ },
+ [RPMI_SYSRST_SRV_GET_ATTRIBUTES] = {
+ .service_id = RPMI_SYSRST_SRV_GET_ATTRIBUTES,
+ .min_a2p_request_datalen = 4,
+ .process_a2p_request = riscv_rpmi_sysreset_get_attributes,
+ },
+ [RPMI_SYSRST_SRV_SYSTEM_RESET] = {
+ .service_id = RPMI_SYSRST_SRV_SYSTEM_RESET,
+ .min_a2p_request_datalen = 4,
+ .process_a2p_request = riscv_rpmi_sysreset_do_reset,
+ },
+};
+
+static struct rpmi_service_group *riscv_rpmi_sysreset_create(RiscvRpmiState *s)
+{
+ RiscvRpmiSysresetGroup *sysreset;
+ struct rpmi_service_group *group;
+
+ sysreset = g_new0(RiscvRpmiSysresetGroup, 1);
+ sysreset->rpmi = s;
+ memcpy(sysreset->services, riscv_rpmi_sysreset_services,
+ sizeof(riscv_rpmi_sysreset_services));
+
+ group = &sysreset->group;
+ group->name = "sysreset";
+ group->servicegroup_id = RPMI_SRVGRP_SYSTEM_RESET;
+ group->max_service_id = RPMI_SYSRST_SRV_ID_MAX;
+ group->servicegroup_version =
+ RPMI_BASE_VERSION(RPMI_SPEC_VERSION_MAJOR, RPMI_SPEC_VERSION_MINOR);
+ group->privilege_level_bitmap = RPMI_PRIVILEGE_M_MODE_MASK;
+ group->services = sysreset->services;
+ group->lock = rpmi_env_alloc_lock();
+ group->priv = sysreset;
+
+ return group;
+}
+
+static void riscv_rpmi_sysreset_destroy(struct rpmi_service_group *group)
+{
+ if (!group) {
+ return;
+ }
+
+ rpmi_env_free_lock(group->lock);
+ g_free(group->priv);
+}
+
+bool riscv_rpmi_sysreset_add(RiscvRpmiState *s, Error **errp)
+{
+ struct rpmi_service_group *group;
+
+ if (s->sysreset_group) {
+ error_setg(errp, "duplicate RPMI sysreset service descriptor");
+ return false;
+ }
+
+ group = riscv_rpmi_sysreset_create(s);
+ if (!group) {
+ error_setg(errp, "failed to create RPMI sysreset service group");
+ return false;
+ }
+
+ if (!riscv_rpmi_context_add_group(s, group, "sysreset", errp)) {
+ riscv_rpmi_sysreset_destroy(group);
+ return false;
+ }
+
+ s->sysreset_group = group;
+ return true;
+}
+
+void riscv_rpmi_sysreset_remove(RiscvRpmiState *s)
+{
+ if (!s->sysreset_group) {
+ return;
+ }
+
+ riscv_rpmi_context_remove_group(s, s->sysreset_group);
+ riscv_rpmi_sysreset_destroy(s->sysreset_group);
+ s->sysreset_group = NULL;
+}
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index e52ccfae1f..273a9aa04f 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -1007,6 +1007,35 @@ static void create_fdt_iommu(RISCVVirtState *s, uint16_t bdf)
}
+static const RiscvRpmiServiceConfig virt_rpmi_services[] = {
+ {
+ .kind = RISCV_RPMI_SERVICE_SYSRESET,
+ .node_name = "sysreset",
+ .compatible = "riscv,rpmi-system-reset",
+ .service_group = RISCV_RPMI_SRVGRP_SYSTEM_RESET,
+ },
+};
+
+static uint32_t virt_rpmi_service_count(RISCVVirtState *s)
+{
+ return ARRAY_SIZE(virt_rpmi_services);
+}
+
+static void virt_rpmi_system_reset(void *opaque)
+{
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+}
+
+static void virt_rpmi_system_shutdown(void *opaque)
+{
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+}
+
+static const RiscvRpmiMachineOps virt_rpmi_machine_ops = {
+ .system_reset = virt_rpmi_system_reset,
+ .system_shutdown = virt_rpmi_system_shutdown,
+};
+
static RiscvRpmiConfig virt_rpmi_config(RISCVVirtState *s,
const uint32_t *hart_ids,
uint32_t hart_count)
@@ -1018,8 +1047,12 @@ static RiscvRpmiConfig virt_rpmi_config(RISCVVirtState *s,
.a2p_req_size = VIRT_RPMI_A2P_REQ_SIZE,
.p2a_req_size = VIRT_RPMI_P2A_REQ_SIZE,
.platform_info = "QEMU RISC-V virt RPMI",
+ .machine_ops = &virt_rpmi_machine_ops,
+ .machine_opaque = s,
.hart_ids = hart_ids,
.hart_count = hart_count,
+ .services = virt_rpmi_services,
+ .service_count = virt_rpmi_service_count(s),
};
}
@@ -1028,6 +1061,7 @@ static void create_fdt_rpmi(RISCVVirtState *s, uint32_t *phandle,
{
RiscvRpmiConfig rpmi_cfg = virt_rpmi_config(s, NULL, 0);
uint32_t rpmi_mbox_handle;
+ uint32_t i;
RiscvRpmiFdtMboxConfig cfg = {
.shmem_base = rpmi_cfg.shmem_base,
.doorbell_base = rpmi_cfg.doorbell_base,
@@ -1038,6 +1072,12 @@ static void create_fdt_rpmi(RISCVVirtState *s, uint32_t *phandle,
riscv_rpmi_fdt_add_mbox(MACHINE(s)->fdt, &cfg, phandle,
&rpmi_mbox_handle);
+
+ for (i = 0; i < rpmi_cfg.service_count; i++) {
+ riscv_rpmi_fdt_add_service_node(MACHINE(s)->fdt, rpmi_cfg.shmem_base,
+ &rpmi_cfg.services[i],
+ rpmi_mbox_handle);
+ }
}
static void finalize_fdt(RISCVVirtState *s)
diff --git a/include/hw/misc/riscv_rpmi.h b/include/hw/misc/riscv_rpmi.h
index b5d8a32f9b..d11db87731 100644
--- a/include/hw/misc/riscv_rpmi.h
+++ b/include/hw/misc/riscv_rpmi.h
@@ -27,6 +27,7 @@
#define VIRT_RPMI_A2P_REQ_SIZE (16 * RPMI_QUEUE_SLOT_SIZE)
#define VIRT_RPMI_P2A_REQ_SIZE 0
+#define RISCV_RPMI_SRVGRP_SYSTEM_RESET 3
#define TYPE_RISCV_RPMI "riscv-rpmi"
OBJECT_DECLARE_SIMPLE_TYPE(RiscvRpmiState, RISCV_RPMI)
@@ -38,8 +39,13 @@ struct rpmi_transport;
typedef enum RiscvRpmiServiceKind {
RISCV_RPMI_SERVICE_INVALID = 0,
+ RISCV_RPMI_SERVICE_SYSRESET,
} RiscvRpmiServiceKind;
+typedef struct RiscvRpmiMachineOps {
+ void (*system_reset)(void *opaque);
+ void (*system_shutdown)(void *opaque);
+} RiscvRpmiMachineOps;
typedef struct RiscvRpmiServiceConfig {
RiscvRpmiServiceKind kind;
const char *node_name;
@@ -56,7 +62,8 @@ typedef struct RiscvRpmiConfig {
uint32_t a2p_req_size;
uint32_t p2a_req_size;
const char *platform_info;
-
+ const RiscvRpmiMachineOps *machine_ops;
+ void *machine_opaque;
const uint32_t *hart_ids;
uint32_t hart_count;
const RiscvRpmiServiceConfig *services;
@@ -73,7 +80,9 @@ struct RiscvRpmiState {
uint32_t a2p_req_size;
uint32_t p2a_req_size;
char *platform_info;
-
+ const RiscvRpmiMachineOps *machine_ops;
+ void *machine_opaque;
+ struct rpmi_service_group *sysreset_group;
uint32_t *hart_ids;
uint32_t hart_count;
const RiscvRpmiServiceConfig *services;
diff --git a/include/hw/riscv/rpmi-fdt.h b/include/hw/riscv/rpmi-fdt.h
index f053ae5d57..af0e8a65ab 100644
--- a/include/hw/riscv/rpmi-fdt.h
+++ b/include/hw/riscv/rpmi-fdt.h
@@ -26,5 +26,16 @@ void riscv_rpmi_fdt_add_mbox(void *fdt,
const RiscvRpmiFdtMboxConfig *cfg,
uint32_t *phandle,
uint32_t *mbox_handle);
+void riscv_rpmi_fdt_add_service(void *fdt, hwaddr shmem_base,
+ const char *node_name,
+ const char *compatible,
+ uint32_t mbox_handle,
+ uint32_t service_group,
+ bool has_mpxy_channel,
+ uint32_t mpxy_channel);
+
+void riscv_rpmi_fdt_add_service_node(void *fdt, hwaddr shmem_base,
+ const RiscvRpmiServiceConfig *service,
+ uint32_t mbox_handle);
#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v2 06/10] riscv: add RPMI HSM service
2026-07-10 8:39 ` [PATCH v2 " Subrahmanya Lingappa
(?)
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
-1 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 5:43 UTC (permalink / raw)
To: qemu-devel
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add the RPMI Hart State Management service group for RISC-V virt.
The service exposes the configured virt hart IDs to librpmi and keeps
per-hart RPMI hardware state in the RPMI device. Start, stop, and
suspend requests update QEMU CPU halted state and resume/kick vCPUs as
needed so firmware can exercise RPMI HSM operations against the emulated
topology.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
hw/misc/meson.build | 1 +
hw/misc/riscv_rpmi.c | 46 ++++++
hw/misc/riscv_rpmi_hsm.c | 280 ++++++++++++++++++++++++++++++++++
hw/misc/riscv_rpmi_internal.h | 4 +
hw/riscv/virt.c | 6 +
include/hw/misc/riscv_rpmi.h | 6 +
6 files changed, 343 insertions(+)
create mode 100644 hw/misc/riscv_rpmi_hsm.c
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index bca7819ee0..fc5b11ed57 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -171,4 +171,5 @@ system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c'))
system_ss.add(when: 'CONFIG_RISCV_RPMI', if_true: [files(
'riscv_rpmi.c',
'riscv_rpmi_sysreset.c',
+ 'riscv_rpmi_hsm.c',
), librpmi])
diff --git a/hw/misc/riscv_rpmi.c b/hw/misc/riscv_rpmi.c
index 7a3f570ee7..820b1d5d63 100644
--- a/hw/misc/riscv_rpmi.c
+++ b/hw/misc/riscv_rpmi.c
@@ -171,6 +171,49 @@ static bool riscv_rpmi_transport_indices_valid(RiscvRpmiState *s)
s->a2p_req_size);
}
+typedef struct RiscvRpmiServiceOps {
+ RiscvRpmiServiceKind kind;
+ void (*configure)(RiscvRpmiState *s, const RiscvRpmiConfig *cfg);
+ bool (*add)(RiscvRpmiState *s, Error **errp);
+ void (*remove)(RiscvRpmiState *s);
+} RiscvRpmiServiceOps;
+
+static const RiscvRpmiServiceOps riscv_rpmi_service_ops[] = {
+ {
+ .kind = RISCV_RPMI_SERVICE_SYSRESET,
+ .add = riscv_rpmi_sysreset_add,
+ .remove = riscv_rpmi_sysreset_remove,
+ }, {
+ .kind = RISCV_RPMI_SERVICE_HSM,
+ .add = riscv_rpmi_hsm_add,
+ .remove = riscv_rpmi_hsm_remove,
+ },
+};
+
+static const RiscvRpmiServiceOps *riscv_rpmi_service_ops_by_kind(
+ RiscvRpmiServiceKind kind)
+{
+ for (uint32_t i = 0; i < ARRAY_SIZE(riscv_rpmi_service_ops); i++) {
+ if (riscv_rpmi_service_ops[i].kind == kind) {
+ return &riscv_rpmi_service_ops[i];
+ }
+ }
+
+ return NULL;
+}
+
+static void riscv_rpmi_configure_services(RiscvRpmiState *s,
+ const RiscvRpmiConfig *cfg)
+{
+ for (uint32_t i = 0; i < ARRAY_SIZE(riscv_rpmi_service_ops); i++) {
+ const RiscvRpmiServiceOps *ops = &riscv_rpmi_service_ops[i];
+
+ if (ops->configure && riscv_rpmi_service_enabled(s, ops->kind)) {
+ ops->configure(s, cfg);
+ }
+ }
+}
+
static void riscv_rpmi_configure_base(RiscvRpmiState *s,
const RiscvRpmiConfig *cfg)
{
@@ -187,6 +230,8 @@ static void riscv_rpmi_configure_base(RiscvRpmiState *s,
cfg->hart_count * sizeof(*cfg->hart_ids));
}
}
+
+ riscv_rpmi_configure_services(s, cfg);
}
static void riscv_rpmi_init(Object *obj)
@@ -209,6 +254,7 @@ static void riscv_rpmi_reset(DeviceState *dev)
memory_region_set_dirty(&s->shmem, 0, s->shmem_size);
}
+ riscv_rpmi_hsm_reset(s);
}
static void riscv_rpmi_cleanup(RiscvRpmiState *s)
diff --git a/hw/misc/riscv_rpmi_hsm.c b/hw/misc/riscv_rpmi_hsm.c
new file mode 100644
index 0000000000..9a9859b10c
--- /dev/null
+++ b/hw/misc/riscv_rpmi_hsm.c
@@ -0,0 +1,280 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI HSM service.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#include "qemu/osdep.h"
+#include "riscv_rpmi_internal.h"
+#include "hw/core/cpu.h"
+#include "librpmi_env.h"
+
+static const struct rpmi_hsm_suspend_type riscv_rpmi_hsm_suspend_types[] = {
+ {
+ .type = 0,
+ .info = {
+ .flags = 0,
+ .entry_latency_us = 0,
+ .exit_latency_us = 0,
+ .wakeup_latency_us = 0,
+ .min_residency_us = 0,
+ },
+ },
+};
+
+typedef struct RiscvRpmiHsmStateTransition {
+ enum rpmi_hart_hw_state state;
+ bool halted;
+ bool resume;
+} RiscvRpmiHsmStateTransition;
+
+static const RiscvRpmiHsmStateTransition riscv_rpmi_hsm_state_transitions[] = {
+ {
+ .state = RPMI_HART_HW_STATE_STARTED,
+ .halted = false,
+ .resume = true,
+ }, {
+ .state = RPMI_HART_HW_STATE_STOPPED,
+ .halted = true,
+ .resume = false,
+ }, {
+ .state = RPMI_HART_HW_STATE_SUSPENDED,
+ .halted = true,
+ .resume = false,
+ },
+};
+
+static const RiscvRpmiHsmStateTransition *riscv_rpmi_hsm_state_transition(
+ enum rpmi_hart_hw_state state)
+{
+ for (uint32_t index = 0;
+ index < ARRAY_SIZE(riscv_rpmi_hsm_state_transitions); index++) {
+ if (riscv_rpmi_hsm_state_transitions[index].state == state) {
+ return &riscv_rpmi_hsm_state_transitions[index];
+ }
+ }
+
+ return NULL;
+}
+
+static CPUState *riscv_rpmi_hart_cpu(RiscvRpmiState *s, uint32_t hart_index)
+{
+ if (hart_index >= s->hart_count || !s->hart_ids) {
+ return NULL;
+ }
+
+ return cpu_by_arch_id(s->hart_ids[hart_index]);
+}
+
+static void riscv_rpmi_hsm_set_hw_state(RiscvRpmiState *s,
+ uint32_t hart_index,
+ enum rpmi_hart_hw_state state)
+{
+ CPUState *cpu = riscv_rpmi_hart_cpu(s, hart_index);
+ const RiscvRpmiHsmStateTransition *transition;
+
+ if (hart_index >= s->hart_count || !s->hsm_hw_states) {
+ return;
+ }
+
+ s->hsm_hw_states[hart_index] = state;
+ if (!cpu) {
+ return;
+ }
+
+ transition = riscv_rpmi_hsm_state_transition(state);
+ if (!transition) {
+ return;
+ }
+
+ cpu->halted = transition->halted;
+ if (transition->resume) {
+ cpu_resume(cpu);
+ } else {
+ qemu_cpu_kick(cpu);
+ }
+}
+
+static enum rpmi_error riscv_rpmi_hsm_start_prepare(
+ void *priv, rpmi_uint32_t hart_index, rpmi_uint64_t start_addr)
+{
+ RiscvRpmiState *s = priv;
+ CPUState *cpu = riscv_rpmi_hart_cpu(s, hart_index);
+
+ if (!cpu) {
+ return RPMI_ERR_INVALID_PARAM;
+ }
+
+ riscv_rpmi_hsm_set_hw_state(s, hart_index, RPMI_HART_HW_STATE_STARTED);
+ return RPMI_SUCCESS;
+}
+
+static void riscv_rpmi_hsm_start_finalize(void *priv,
+ rpmi_uint32_t hart_index,
+ rpmi_uint64_t start_addr)
+{
+}
+
+static enum rpmi_error riscv_rpmi_hsm_stop_prepare(void *priv,
+ rpmi_uint32_t hart_index)
+{
+ RiscvRpmiState *s = priv;
+
+ if (!riscv_rpmi_hart_cpu(s, hart_index)) {
+ return RPMI_ERR_INVALID_PARAM;
+ }
+
+ riscv_rpmi_hsm_set_hw_state(s, hart_index, RPMI_HART_HW_STATE_STOPPED);
+ return RPMI_SUCCESS;
+}
+
+static void riscv_rpmi_hsm_stop_finalize(void *priv, rpmi_uint32_t hart_index)
+{
+}
+
+static enum rpmi_error riscv_rpmi_hsm_suspend_prepare(
+ void *priv, rpmi_uint32_t hart_index,
+ const struct rpmi_hsm_suspend_type *suspend_type,
+ rpmi_uint64_t resume_addr)
+{
+ RiscvRpmiState *s = priv;
+
+ if (!suspend_type || !riscv_rpmi_hart_cpu(s, hart_index)) {
+ return RPMI_ERR_INVALID_PARAM;
+ }
+
+ riscv_rpmi_hsm_set_hw_state(s, hart_index, RPMI_HART_HW_STATE_SUSPENDED);
+ return RPMI_SUCCESS;
+}
+
+static void riscv_rpmi_hsm_suspend_finalize(
+ void *priv, rpmi_uint32_t hart_index,
+ const struct rpmi_hsm_suspend_type *suspend_type,
+ rpmi_uint64_t resume_addr)
+{
+}
+
+static enum rpmi_hart_hw_state riscv_rpmi_hsm_get_hw_state(
+ void *priv, rpmi_uint32_t hart_index)
+{
+ RiscvRpmiState *s = priv;
+
+ if (hart_index >= s->hart_count || !s->hsm_hw_states) {
+ return RPMI_HART_HW_STATE_STOPPED;
+ }
+
+ return s->hsm_hw_states[hart_index];
+}
+
+static const struct rpmi_hsm_platform_ops riscv_rpmi_hsm_ops = {
+ .hart_get_hw_state = riscv_rpmi_hsm_get_hw_state,
+ .hart_start_prepare = riscv_rpmi_hsm_start_prepare,
+ .hart_start_finalize = riscv_rpmi_hsm_start_finalize,
+ .hart_stop_prepare = riscv_rpmi_hsm_stop_prepare,
+ .hart_stop_finalize = riscv_rpmi_hsm_stop_finalize,
+ .hart_suspend_prepare = riscv_rpmi_hsm_suspend_prepare,
+ .hart_suspend_finalize = riscv_rpmi_hsm_suspend_finalize,
+};
+
+static bool riscv_rpmi_hsm_create(RiscvRpmiState *s,
+ struct rpmi_service_group **group,
+ Error **errp)
+{
+ struct rpmi_hsm *hsm;
+
+ if (!s->hart_count || !s->hart_ids) {
+ error_setg(errp, "RPMI HSM service requires hart IDs");
+ return false;
+ }
+
+ s->hsm_hw_states = g_new0(uint32_t, s->hart_count);
+ for (uint32_t i = 0; i < s->hart_count; i++) {
+ s->hsm_hw_states[i] = RPMI_HART_HW_STATE_STARTED;
+ }
+
+ hsm = rpmi_hsm_create(s->hart_count, s->hart_ids,
+ ARRAY_SIZE(riscv_rpmi_hsm_suspend_types),
+ riscv_rpmi_hsm_suspend_types,
+ &riscv_rpmi_hsm_ops, s);
+ if (!hsm) {
+ g_clear_pointer(&s->hsm_hw_states, g_free);
+ error_setg(errp, "failed to create RPMI HSM context");
+ return false;
+ }
+
+ *group = rpmi_service_group_hsm_create(hsm);
+ if (!*group) {
+ rpmi_hsm_destroy(hsm);
+ g_clear_pointer(&s->hsm_hw_states, g_free);
+ error_setg(errp, "failed to create RPMI HSM service group");
+ return false;
+ }
+
+ s->hsm = hsm;
+ return true;
+}
+
+static void riscv_rpmi_hsm_destroy(RiscvRpmiState *s)
+{
+ if (s->hsm_group) {
+ rpmi_env_free_lock(s->hsm_group->lock);
+ s->hsm_group->lock = NULL;
+ rpmi_service_group_hsm_destroy(s->hsm_group);
+ s->hsm_group = NULL;
+ }
+
+ if (s->hsm) {
+ rpmi_hsm_destroy(s->hsm);
+ s->hsm = NULL;
+ }
+
+ g_clear_pointer(&s->hsm_hw_states, g_free);
+}
+
+
+bool riscv_rpmi_hsm_add(RiscvRpmiState *s, Error **errp)
+{
+ struct rpmi_service_group *group;
+
+ if (s->hsm_group) {
+ error_setg(errp, "duplicate RPMI HSM service descriptor");
+ return false;
+ }
+
+ if (!riscv_rpmi_hsm_create(s, &group, errp)) {
+ return false;
+ }
+
+ if (!riscv_rpmi_context_add_group(s, group, "HSM", errp)) {
+ s->hsm_group = group;
+ riscv_rpmi_hsm_destroy(s);
+ return false;
+ }
+
+ s->hsm_group = group;
+ return true;
+}
+
+void riscv_rpmi_hsm_remove(RiscvRpmiState *s)
+{
+ riscv_rpmi_context_remove_group(s, s->hsm_group);
+ riscv_rpmi_hsm_destroy(s);
+}
+
+void riscv_rpmi_hsm_reset(RiscvRpmiState *s)
+{
+ if (!s->hsm_hw_states) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < s->hart_count; i++) {
+ s->hsm_hw_states[i] = RPMI_HART_HW_STATE_STARTED;
+ }
+ if (s->hsm) {
+ rpmi_hsm_process_state_changes(s->hsm);
+ }
+}
diff --git a/hw/misc/riscv_rpmi_internal.h b/hw/misc/riscv_rpmi_internal.h
index fe09804e69..42a6708907 100644
--- a/hw/misc/riscv_rpmi_internal.h
+++ b/hw/misc/riscv_rpmi_internal.h
@@ -30,4 +30,8 @@ void riscv_rpmi_context_remove_group(RiscvRpmiState *s,
bool riscv_rpmi_sysreset_add(RiscvRpmiState *s, Error **errp);
void riscv_rpmi_sysreset_remove(RiscvRpmiState *s);
+bool riscv_rpmi_hsm_add(RiscvRpmiState *s, Error **errp);
+void riscv_rpmi_hsm_remove(RiscvRpmiState *s);
+void riscv_rpmi_hsm_reset(RiscvRpmiState *s);
+
#endif
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index 273a9aa04f..2b5f367bdb 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -1014,6 +1014,12 @@ static const RiscvRpmiServiceConfig virt_rpmi_services[] = {
.compatible = "riscv,rpmi-system-reset",
.service_group = RISCV_RPMI_SRVGRP_SYSTEM_RESET,
},
+ {
+ .kind = RISCV_RPMI_SERVICE_HSM,
+ .node_name = "hsm",
+ .compatible = "riscv,rpmi-hsm",
+ .service_group = RISCV_RPMI_SRVGRP_HSM,
+ },
};
static uint32_t virt_rpmi_service_count(RISCVVirtState *s)
diff --git a/include/hw/misc/riscv_rpmi.h b/include/hw/misc/riscv_rpmi.h
index d11db87731..37c56ea2e8 100644
--- a/include/hw/misc/riscv_rpmi.h
+++ b/include/hw/misc/riscv_rpmi.h
@@ -28,6 +28,7 @@
#define VIRT_RPMI_P2A_REQ_SIZE 0
#define RISCV_RPMI_SRVGRP_SYSTEM_RESET 3
+#define RISCV_RPMI_SRVGRP_HSM 5
#define TYPE_RISCV_RPMI "riscv-rpmi"
OBJECT_DECLARE_SIMPLE_TYPE(RiscvRpmiState, RISCV_RPMI)
@@ -36,10 +37,12 @@ struct rpmi_context;
struct rpmi_service_group;
struct rpmi_shmem;
struct rpmi_transport;
+struct rpmi_hsm;
typedef enum RiscvRpmiServiceKind {
RISCV_RPMI_SERVICE_INVALID = 0,
RISCV_RPMI_SERVICE_SYSRESET,
+ RISCV_RPMI_SERVICE_HSM,
} RiscvRpmiServiceKind;
typedef struct RiscvRpmiMachineOps {
@@ -83,6 +86,9 @@ struct RiscvRpmiState {
const RiscvRpmiMachineOps *machine_ops;
void *machine_opaque;
struct rpmi_service_group *sysreset_group;
+ struct rpmi_hsm *hsm;
+ struct rpmi_service_group *hsm_group;
+ uint32_t *hsm_hw_states;
uint32_t *hart_ids;
uint32_t hart_count;
const RiscvRpmiServiceConfig *services;
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v2 07/10] riscv: add RPMI system suspend service
2026-07-10 8:39 ` [PATCH v2 " Subrahmanya Lingappa
(?)
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
-1 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 5:43 UTC (permalink / raw)
To: qemu-devel
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add the RPMI System Suspend service group for RISC-V virt.
The service advertises suspend-to-RAM support through librpmi,
registers QEMU wakeup support, and routes the suspend finalize callback
to the machine suspend path. On wakeup it processes RPMI group events
and defers the HSM hart resume until vCPUs are ready to run again.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
hw/misc/meson.build | 1 +
hw/misc/riscv_rpmi.c | 4 +
hw/misc/riscv_rpmi_hsm.c | 9 +-
hw/misc/riscv_rpmi_internal.h | 5 +
hw/misc/riscv_rpmi_syssusp.c | 191 ++++++++++++++++++++++++++++++++++
hw/riscv/virt.c | 25 +++++
include/hw/misc/riscv_rpmi.h | 11 ++
7 files changed, 245 insertions(+), 1 deletion(-)
create mode 100644 hw/misc/riscv_rpmi_syssusp.c
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index fc5b11ed57..cb093a8642 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -172,4 +172,5 @@ system_ss.add(when: 'CONFIG_RISCV_RPMI', if_true: [files(
'riscv_rpmi.c',
'riscv_rpmi_sysreset.c',
'riscv_rpmi_hsm.c',
+ 'riscv_rpmi_syssusp.c',
), librpmi])
diff --git a/hw/misc/riscv_rpmi.c b/hw/misc/riscv_rpmi.c
index 820b1d5d63..08691861b0 100644
--- a/hw/misc/riscv_rpmi.c
+++ b/hw/misc/riscv_rpmi.c
@@ -187,6 +187,10 @@ static const RiscvRpmiServiceOps riscv_rpmi_service_ops[] = {
.kind = RISCV_RPMI_SERVICE_HSM,
.add = riscv_rpmi_hsm_add,
.remove = riscv_rpmi_hsm_remove,
+ }, {
+ .kind = RISCV_RPMI_SERVICE_SYSSUSP,
+ .add = riscv_rpmi_syssusp_add,
+ .remove = riscv_rpmi_syssusp_remove,
},
};
diff --git a/hw/misc/riscv_rpmi_hsm.c b/hw/misc/riscv_rpmi_hsm.c
index 9a9859b10c..bacdabcac6 100644
--- a/hw/misc/riscv_rpmi_hsm.c
+++ b/hw/misc/riscv_rpmi_hsm.c
@@ -235,7 +235,6 @@ static void riscv_rpmi_hsm_destroy(RiscvRpmiState *s)
g_clear_pointer(&s->hsm_hw_states, g_free);
}
-
bool riscv_rpmi_hsm_add(RiscvRpmiState *s, Error **errp)
{
struct rpmi_service_group *group;
@@ -278,3 +277,11 @@ void riscv_rpmi_hsm_reset(RiscvRpmiState *s)
rpmi_hsm_process_state_changes(s->hsm);
}
}
+
+void riscv_rpmi_hsm_resume(RiscvRpmiState *s, uint32_t hart_index)
+{
+ riscv_rpmi_hsm_set_hw_state(s, hart_index, RPMI_HART_HW_STATE_STARTED);
+ if (s->hsm) {
+ rpmi_hsm_process_state_changes(s->hsm);
+ }
+}
diff --git a/hw/misc/riscv_rpmi_internal.h b/hw/misc/riscv_rpmi_internal.h
index 42a6708907..34cab35c32 100644
--- a/hw/misc/riscv_rpmi_internal.h
+++ b/hw/misc/riscv_rpmi_internal.h
@@ -33,5 +33,10 @@ void riscv_rpmi_sysreset_remove(RiscvRpmiState *s);
bool riscv_rpmi_hsm_add(RiscvRpmiState *s, Error **errp);
void riscv_rpmi_hsm_remove(RiscvRpmiState *s);
void riscv_rpmi_hsm_reset(RiscvRpmiState *s);
+void riscv_rpmi_hsm_resume(RiscvRpmiState *s, uint32_t hart_index);
+
+bool riscv_rpmi_syssusp_add(RiscvRpmiState *s, Error **errp);
+void riscv_rpmi_syssusp_remove(RiscvRpmiState *s);
+
#endif
diff --git a/hw/misc/riscv_rpmi_syssusp.c b/hw/misc/riscv_rpmi_syssusp.c
new file mode 100644
index 0000000000..d455e08163
--- /dev/null
+++ b/hw/misc/riscv_rpmi_syssusp.c
@@ -0,0 +1,191 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI System Suspend service.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#include "qemu/osdep.h"
+#include "riscv_rpmi_internal.h"
+#include "qemu/timer.h"
+#include "system/runstate.h"
+
+static const struct rpmi_system_suspend_type riscv_rpmi_syssusp_types[] = {
+ {
+ .type = RPMI_SYSSUSP_TYPE_SUSPEND_TO_RAM,
+ .attr = RPMI_SYSSUSP_ATTRS_FLAGS_RESUMEADDR,
+ },
+};
+
+static enum rpmi_error riscv_rpmi_syssusp_prepare(
+ void *priv, rpmi_uint32_t hart_index,
+ const struct rpmi_system_suspend_type *syssusp_type,
+ rpmi_uint64_t resume_addr)
+{
+ RiscvRpmiState *s = priv;
+
+ if (s->machine_ops && s->machine_ops->register_wakeup_support) {
+ s->machine_ops->register_wakeup_support(s->machine_opaque);
+ }
+ return RPMI_SUCCESS;
+}
+
+static rpmi_bool_t riscv_rpmi_syssusp_ready(void *priv,
+ rpmi_uint32_t hart_index)
+{
+ return true;
+}
+
+static void riscv_rpmi_syssusp_finalize(
+ void *priv, rpmi_uint32_t hart_index,
+ const struct rpmi_system_suspend_type *syssusp_type,
+ rpmi_uint64_t resume_addr)
+{
+ RiscvRpmiState *s = priv;
+
+ if (s->machine_ops && s->machine_ops->system_suspend) {
+ s->machine_ops->system_suspend(s->machine_opaque);
+ }
+}
+
+static rpmi_bool_t riscv_rpmi_syssusp_can_resume(void *priv,
+ rpmi_uint32_t hart_index)
+{
+ RiscvRpmiState *s = priv;
+
+ if (s->machine_ops && s->machine_ops->system_can_resume) {
+ return s->machine_ops->system_can_resume(s->machine_opaque);
+ }
+
+ return true;
+}
+
+static enum rpmi_error riscv_rpmi_syssusp_resume(
+ void *priv, rpmi_uint32_t hart_index,
+ const struct rpmi_system_suspend_type *syssusp_type,
+ rpmi_uint64_t resume_addr)
+{
+ RiscvRpmiState *s = priv;
+
+ s->syssusp_resume_hart_index = hart_index;
+ s->syssusp_resume_pending = true;
+ return RPMI_SUCCESS;
+}
+
+static const struct rpmi_syssusp_platform_ops riscv_rpmi_syssusp_ops = {
+ .system_suspend_prepare = riscv_rpmi_syssusp_prepare,
+ .system_suspend_ready = riscv_rpmi_syssusp_ready,
+ .system_suspend_finalize = riscv_rpmi_syssusp_finalize,
+ .system_suspend_can_resume = riscv_rpmi_syssusp_can_resume,
+ .system_suspend_resume = riscv_rpmi_syssusp_resume,
+};
+
+static void riscv_rpmi_wakeup_timer(void *opaque)
+{
+ RiscvRpmiState *s = opaque;
+
+ if (s->syssusp_resume_pending) {
+ riscv_rpmi_hsm_resume(s, s->syssusp_resume_hart_index);
+ s->syssusp_resume_pending = false;
+ }
+}
+
+static void riscv_rpmi_wakeup_notify(Notifier *notifier, void *data)
+{
+ RiscvRpmiState *s = container_of(notifier, RiscvRpmiState,
+ wakeup_notifier);
+
+ if (s->context) {
+ rpmi_context_process_group_events(s->context,
+ RPMI_SRVGRP_SYSTEM_SUSPEND);
+ }
+ if (s->wakeup_timer) {
+ /*
+ * Wakeup notifiers run before vCPUs resume. Defer the HSM
+ * kick so OpenSBI can leave WFI after QEMU restarts execution.
+ */
+ timer_mod(s->wakeup_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 10);
+ }
+}
+
+static bool riscv_rpmi_syssusp_create(RiscvRpmiState *s,
+ struct rpmi_service_group **group,
+ Error **errp)
+{
+ if (!s->hsm) {
+ error_setg(errp, "RPMI system suspend service requires HSM service");
+ return false;
+ }
+
+ if (s->machine_ops && s->machine_ops->register_wakeup_support) {
+ s->machine_ops->register_wakeup_support(s->machine_opaque);
+ }
+ if (!s->wakeup_timer) {
+ s->wakeup_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
+ riscv_rpmi_wakeup_timer, s);
+ }
+ if (!s->wakeup_notifier_registered) {
+ s->wakeup_notifier.notify = riscv_rpmi_wakeup_notify;
+ qemu_register_wakeup_notifier(&s->wakeup_notifier);
+ s->wakeup_notifier_registered = true;
+ }
+
+ *group = rpmi_service_group_syssusp_create(
+ s->hsm, ARRAY_SIZE(riscv_rpmi_syssusp_types),
+ riscv_rpmi_syssusp_types, &riscv_rpmi_syssusp_ops, s);
+ if (!*group) {
+ error_setg(errp, "failed to create RPMI system suspend service group");
+ return false;
+ }
+
+ return true;
+}
+
+static void riscv_rpmi_syssusp_destroy(RiscvRpmiState *s)
+{
+ if (s->wakeup_notifier_registered) {
+ notifier_remove(&s->wakeup_notifier);
+ s->wakeup_notifier_registered = false;
+ }
+ if (s->wakeup_timer) {
+ timer_free(s->wakeup_timer);
+ s->wakeup_timer = NULL;
+ }
+
+ if (s->syssusp_group) {
+ rpmi_service_group_syssusp_destroy(s->syssusp_group);
+ s->syssusp_group = NULL;
+ }
+}
+
+bool riscv_rpmi_syssusp_add(RiscvRpmiState *s, Error **errp)
+{
+ struct rpmi_service_group *group;
+
+ if (s->syssusp_group) {
+ error_setg(errp, "duplicate RPMI system suspend descriptor");
+ return false;
+ }
+
+ if (!riscv_rpmi_syssusp_create(s, &group, errp)) {
+ return false;
+ }
+
+ if (!riscv_rpmi_context_add_group(s, group, "system suspend", errp)) {
+ s->syssusp_group = group;
+ riscv_rpmi_syssusp_destroy(s);
+ return false;
+ }
+
+ s->syssusp_group = group;
+ return true;
+}
+
+void riscv_rpmi_syssusp_remove(RiscvRpmiState *s)
+{
+ riscv_rpmi_context_remove_group(s, s->syssusp_group);
+ riscv_rpmi_syssusp_destroy(s);
+}
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index 2b5f367bdb..21c218a78d 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -51,6 +51,7 @@
#include "chardev/char.h"
#include "system/device_tree.h"
#include "system/system.h"
+#include "system/runstate.h"
#include "system/tcg.h"
#include "system/kvm.h"
#include "system/tpm.h"
@@ -1020,6 +1021,12 @@ static const RiscvRpmiServiceConfig virt_rpmi_services[] = {
.compatible = "riscv,rpmi-hsm",
.service_group = RISCV_RPMI_SRVGRP_HSM,
},
+ {
+ .kind = RISCV_RPMI_SERVICE_SYSSUSP,
+ .node_name = "suspend",
+ .compatible = "riscv,rpmi-system-suspend",
+ .service_group = RISCV_RPMI_SRVGRP_SYSTEM_SUSPEND,
+ },
};
static uint32_t virt_rpmi_service_count(RISCVVirtState *s)
@@ -1037,9 +1044,27 @@ static void virt_rpmi_system_shutdown(void *opaque)
qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
}
+static void virt_rpmi_system_suspend(void *opaque)
+{
+ qemu_system_suspend_request();
+}
+
+static void virt_rpmi_register_wakeup_support(void *opaque)
+{
+ qemu_register_wakeup_support();
+}
+
+static bool virt_rpmi_system_can_resume(void *opaque)
+{
+ return runstate_check(RUN_STATE_RUNNING);
+}
+
static const RiscvRpmiMachineOps virt_rpmi_machine_ops = {
.system_reset = virt_rpmi_system_reset,
.system_shutdown = virt_rpmi_system_shutdown,
+ .system_suspend = virt_rpmi_system_suspend,
+ .register_wakeup_support = virt_rpmi_register_wakeup_support,
+ .system_can_resume = virt_rpmi_system_can_resume,
};
static RiscvRpmiConfig virt_rpmi_config(RISCVVirtState *s,
diff --git a/include/hw/misc/riscv_rpmi.h b/include/hw/misc/riscv_rpmi.h
index 37c56ea2e8..a4fe2aac63 100644
--- a/include/hw/misc/riscv_rpmi.h
+++ b/include/hw/misc/riscv_rpmi.h
@@ -29,6 +29,7 @@
#define RISCV_RPMI_SRVGRP_SYSTEM_RESET 3
#define RISCV_RPMI_SRVGRP_HSM 5
+#define RISCV_RPMI_SRVGRP_SYSTEM_SUSPEND 4
#define TYPE_RISCV_RPMI "riscv-rpmi"
OBJECT_DECLARE_SIMPLE_TYPE(RiscvRpmiState, RISCV_RPMI)
@@ -43,11 +44,15 @@ typedef enum RiscvRpmiServiceKind {
RISCV_RPMI_SERVICE_INVALID = 0,
RISCV_RPMI_SERVICE_SYSRESET,
RISCV_RPMI_SERVICE_HSM,
+ RISCV_RPMI_SERVICE_SYSSUSP,
} RiscvRpmiServiceKind;
typedef struct RiscvRpmiMachineOps {
void (*system_reset)(void *opaque);
void (*system_shutdown)(void *opaque);
+ void (*system_suspend)(void *opaque);
+ void (*register_wakeup_support)(void *opaque);
+ bool (*system_can_resume)(void *opaque);
} RiscvRpmiMachineOps;
typedef struct RiscvRpmiServiceConfig {
RiscvRpmiServiceKind kind;
@@ -86,9 +91,15 @@ struct RiscvRpmiState {
const RiscvRpmiMachineOps *machine_ops;
void *machine_opaque;
struct rpmi_service_group *sysreset_group;
+ struct rpmi_service_group *syssusp_group;
struct rpmi_hsm *hsm;
struct rpmi_service_group *hsm_group;
uint32_t *hsm_hw_states;
+ Notifier wakeup_notifier;
+ bool wakeup_notifier_registered;
+ QEMUTimer *wakeup_timer;
+ bool syssusp_resume_pending;
+ uint32_t syssusp_resume_hart_index;
uint32_t *hart_ids;
uint32_t hart_count;
const RiscvRpmiServiceConfig *services;
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v2 08/10] docs/system/riscv: document initial virt RPMI support
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 5:43 UTC (permalink / raw)
To: qemu-devel
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Document the opt-in virt RPMI transport and the initial HSM, system
reset, and system suspend service groups.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
docs/system/riscv/virt.rst | 50 ++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/docs/system/riscv/virt.rst b/docs/system/riscv/virt.rst
index 60850970ce..8e88f780e1 100644
--- a/docs/system/riscv/virt.rst
+++ b/docs/system/riscv/virt.rst
@@ -146,6 +146,56 @@ The following machine-specific options are supported:
Enables the riscv-iommu-sys platform device. Defaults to 'off'.
+- rpmi=[on|off]
+
+ Enables the RISC-V RPMI shared-memory transport. This option defaults to
+ "off", is TCG-only, and is available only when QEMU is built with librpmi
+ support. This support targets the RISC-V Platform Management Interface
+ (RPMI) Specification v1.0 Ratified (`RPMI v1.0 specification`_). The initial
+ ``virt`` implementation exposes RPMI system reset, system suspend, and HSM
+ service groups. System reset can request shutdown or cold reboot through
+ RPMI. HSM uses the ``virt`` hart IDs and supports hart discovery, status,
+ start, stop, and suspend commands. System suspend uses QEMU's suspend and
+ wakeup path. Migration is currently blocked while RPMI transport and service
+ VMState support is being designed.
+
+ The build dependency is provided by `librpmi`_. Install librpmi so that
+ ``pkg-config`` can find ``librpmi.pc``, then configure QEMU with
+ ``--enable-librpmi`` or ``-Dlibrpmi=enabled``.
+
+.. _RPMI v1.0 specification: https://github.com/riscv-non-isa/riscv-rpmi/releases/download/v1.0/riscv-rpmi.pdf
+.. _librpmi: https://github.com/riscv-software-src/librpmi
+
+RPMI firmware and guest smoke
+-----------------------------
+
+A firmware-level RPMI smoke test can be run with OpenSBI, a Linux kernel, and a
+RISC-V disk image:
+
+.. code-block:: bash
+
+ $ qemu-system-riscv64 \
+ -machine virt,rpmi=on \
+ -cpu rv64 -smp 4 -m 4G -nographic \
+ -bios /path/to/fw_jump.bin \
+ -kernel /path/to/Image \
+ -append "root=/dev/vda rw console=ttyS0 earlycon=sbi" \
+ -drive file=/path/to/rootfs.img,format=raw,if=virtio,snapshot=on
+
+The OpenSBI banner should report RPMI-backed HSM and system suspend support.
+After the guest reaches a shell, normal guest reboot/poweroff commands exercise
+the RPMI system reset path.
+
+RPMI device tree policy
+-----------------------
+
+RPMI is currently described through FDT only; no ACPI representation is emitted
+for the RPMI transport or service groups. The ``virt`` machine uses the
+``riscv,rpmi-shmem-mbox`` shared-memory mailbox node plus child service nodes
+with ``riscv,rpmi-*`` compatible strings. These binding names are part of the
+RPMI RFC surface and may be revised if the RISC-V RPMI bindings change during
+review.
+
Running Linux kernel
--------------------
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v2 09/10] tests/qtest: cover initial RISC-V RPMI services
2026-07-10 8:39 ` [PATCH v2 " Subrahmanya Lingappa
(?)
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
-1 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 5:43 UTC (permalink / raw)
To: qemu-devel
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add qtests for the RPMI transport plus Base, system reset, HSM, and
system suspend service groups.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
MAINTAINERS | 1 +
tests/qtest/meson.build | 2 +
tests/qtest/riscv-rpmi-test.c | 684 ++++++++++++++++++++++++++++++++++
3 files changed, 687 insertions(+)
create mode 100644 tests/qtest/riscv-rpmi-test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 8e91a31757..6dd71c5a82 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -361,6 +361,7 @@ F: hw/riscv/
F: hw/intc/riscv*
F: hw/misc/riscv_rpmi*
F: include/hw/misc/riscv_rpmi.h
+F: tests/qtest/riscv-rpmi-test.c
F: include/hw/char/riscv_htif.h
F: include/hw/riscv/
F: common-user/host/riscv*
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 4897325d84..d54b79cbcf 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -290,6 +290,8 @@ qtests_riscv32 = \
qtests_riscv64 = ['riscv-csr-test'] + \
(unpack_edk2_blobs ? ['bios-tables-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_RISCV_RPMI') ?
+ ['riscv-rpmi-test'] : []) + \
(config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and
config_all_devices.has_key('CONFIG_RISCV_IOMMU') ?
['iommu-riscv-test'] : []) + \
diff --git a/tests/qtest/riscv-rpmi-test.c b/tests/qtest/riscv-rpmi-test.c
new file mode 100644
index 0000000000..ecd5b910c7
--- /dev/null
+++ b/tests/qtest/riscv-rpmi-test.c
@@ -0,0 +1,684 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * QTests for RISC-V RPMI devices.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+#include "libqtest.h"
+#include "qobject/qdict.h"
+
+#define RPMI_SHMEM_BASE 0x10200000ULL
+#define RPMI_DOORBELL_BASE 0x10230000ULL
+#define RPMI_IMSIC_S_BASE 0x28000000ULL
+#define RPMI_CPPC_FASTCHAN_BASE 0x10240000ULL
+#define RPMI_CPPC_FASTCHAN_SIZE 0x4000
+#define RPMI_CPPC_FASTCHAN_FEEDBACK_OFFSET 0x2000
+#define RPMI_SLOT_SIZE 64
+
+#define RPMI_A2P_HEAD RPMI_SHMEM_BASE
+#define RPMI_A2P_TAIL (RPMI_SHMEM_BASE + RPMI_SLOT_SIZE)
+#define RPMI_A2P_SLOT0 (RPMI_SHMEM_BASE + 2 * RPMI_SLOT_SIZE)
+
+#define RPMI_SRVGRP_BASE 0x0001
+#define RPMI_SRVGRP_SYSTEM_MSI 0x0002
+#define RPMI_SRVGRP_SYSTEM_RESET 0x0003
+#define RPMI_SRVGRP_SYSTEM_SUSPEND 0x0004
+#define RPMI_SRVGRP_HSM 0x0005
+#define RPMI_SRVGRP_CPPC 0x0006
+#define RPMI_SRVGRP_CLOCK 0x0008
+#define RPMI_SRVGRP_MANAGEMENT_MODE 0x000b
+#define RPMI_SRVGRP_LOGGING 0x000e
+#define RPMI_BASE_SRV_GET_PLATFORM_INFO 0x05
+#define RPMI_BASE_SRV_PROBE_SERVICE_GROUP 0x06
+#define RPMI_SYSMSI_SRV_GET_ATTRIBUTES 0x02
+#define RPMI_SYSMSI_SRV_GET_MSI_ATTRIBUTES 0x03
+#define RPMI_SYSMSI_SRV_SET_MSI_STATE 0x04
+#define RPMI_SYSMSI_SRV_GET_MSI_STATE 0x05
+#define RPMI_SYSMSI_SRV_SET_MSI_TARGET 0x06
+#define RPMI_SYSMSI_SRV_GET_MSI_TARGET 0x07
+#define RPMI_SYSRST_SRV_GET_ATTRIBUTES 0x02
+#define RPMI_SYSRST_SRV_SYSTEM_RESET 0x03
+#define RPMI_HSM_SRV_GET_HART_STATUS 0x02
+#define RPMI_HSM_SRV_GET_HART_LIST 0x03
+#define RPMI_HSM_SRV_GET_SUSPEND_TYPES 0x04
+#define RPMI_HSM_SRV_GET_SUSPEND_INFO 0x05
+#define RPMI_HSM_SRV_HART_START 0x06
+#define RPMI_HSM_SRV_HART_STOP 0x07
+#define RPMI_HSM_SRV_HART_SUSPEND 0x08
+#define RPMI_SYSSUSP_SRV_GET_ATTRIBUTES 0x02
+#define RPMI_SYSSUSP_SRV_SYSTEM_SUSPEND 0x03
+#define RPMI_CPPC_SRV_PROBE_REG 0x02
+#define RPMI_CPPC_SRV_READ_REG 0x03
+#define RPMI_CPPC_SRV_WRITE_REG 0x04
+#define RPMI_CPPC_SRV_GET_FAST_CHANNEL_REGION 0x05
+#define RPMI_CPPC_SRV_GET_FAST_CHANNEL_OFFSET 0x06
+#define RPMI_CPPC_SRV_GET_HART_LIST 0x07
+#define RPMI_CPPC_NOMINAL_PERF 0x01
+#define RPMI_CPPC_DESIRED_PERF 0x05
+#define RPMI_CPPC_COUNTER_WRAPAROUND_TIME 0x0a
+#define RPMI_CPPC_REFERENCE_PERF_COUNTER 0x0b
+#define RPMI_CPPC_DELIVERED_PERF_COUNTER 0x0c
+#define RPMI_CPPC_REG_LEN_64 64
+#define RPMI_CLK_SRV_GET_NUM_CLOCKS 0x02
+#define RPMI_CLK_SRV_GET_ATTRIBUTES 0x03
+#define RPMI_CLK_SRV_GET_SUPPORTED_RATES 0x04
+#define RPMI_CLK_SRV_SET_CONFIG 0x05
+#define RPMI_CLK_SRV_GET_CONFIG 0x06
+#define RPMI_CLK_SRV_SET_RATE 0x07
+#define RPMI_CLK_SRV_GET_RATE 0x08
+#define RPMI_MM_SRV_GET_ATTRIBUTES 0x02
+#define RPMI_MM_SRV_COMMUNICATE 0x03
+#define RPMI_LOGGING_SRV_SET_CONFIG 0x02
+#define RPMI_MSG_NORMAL_REQUEST 0x00
+#define RPMI_MSG_POSTED_REQUEST 0x01
+#define RPMI_MSG_ACKNOWLEDGEMENT 0x02
+#define RPMI_SYSRST_TYPE_SHUTDOWN 0x00
+#define RPMI_SYSRST_TYPE_COLD_REBOOT 0x01
+#define RPMI_SYSRST_TYPE_INVALID 0x03
+#define RPMI_SYSRST_ATTRS_FLAGS_RESETTYPE 1
+#define RPMI_SYS_MSI_SHUTDOWN_INDEX 0
+#define RPMI_SYS_MSI_REBOOT_INDEX 1
+#define RPMI_SYS_MSI_SUSPEND_INDEX 2
+#define RPMI_SYS_NUM_MSI 4
+#define RPMI_SYSMSI_MSI_STATE_ENABLE 1
+#define RPMI_SYSMSI_MSI_STATE_PENDING 2
+#define RPMI_TOKEN 0x55aa
+#define RPMI_ERR_NOTSUPP 0xfffffffeU
+#define RPMI_ERR_INVALID_PARAM 0xfffffffdU
+#define RPMI_ERR_INVALID_ADDR 0xfffffffbU
+#define RPMI_ERR_DENIED 0xfffffffcU
+#define RPMI_HSM_HART_STATE_STARTED 0x00
+#define RPMI_HSM_HART_STATE_STOPPED 0x01
+#define RPMI_HSM_HART_STATE_SUSPENDED 0x04
+#define VIRT_RPMI_CPPC_NOMINAL_PERF 30
+#define VIRT_RPMI_CLOCK_COUNT 6
+#define VIRT_RPMI_MM_VERSION 0x10000
+#define VIRT_RPMI_SHMEM_SIZE 0x20000
+#define RPMI_MM_INPUT_OFFSET 0x3000
+#define RPMI_MM_OUTPUT_OFFSET 0x3800
+#define RPMI_MM_BUFFER_SIZE 0x400
+#define RPMI_MM_INPUT_BASE (RPMI_SHMEM_BASE + RPMI_MM_INPUT_OFFSET)
+#define RPMI_MM_OUTPUT_BASE (RPMI_SHMEM_BASE + RPMI_MM_OUTPUT_OFFSET)
+#define MM_EFI_COMM_HEADER_SIZE 24
+#define EFI_VAR_COMM_HEADER_SIZE 16
+#define EFI_VAR_ACCESS_NAME_OFFSET 36
+#define EFI_VAR_NEXT_NAME_OFFSET 24
+#define EFI_VAR_FN_GET_VARIABLE 1
+#define EFI_VAR_FN_GET_NEXT_VARIABLE_NAME 2
+#define EFI_VAR_FN_SET_VARIABLE 3
+#define EFI_SUCCESS 0ULL
+#define EFI_INVALID_PARAMETER 0x8000000000000002ULL
+#define EFI_BUFFER_TOO_SMALL 0x8000000000000005ULL
+#define EFI_NOT_FOUND 0x800000000000000eULL
+#define EFI_VARIABLE_NON_VOLATILE 0x00000001
+#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x00000002
+#define EFI_VARIABLE_RUNTIME_ACCESS 0x00000004
+
+#define RPMI_P2A_ACK_BASE (RPMI_SHMEM_BASE + 16 * RPMI_SLOT_SIZE)
+#define RPMI_P2A_ACK_HEAD RPMI_P2A_ACK_BASE
+#define RPMI_P2A_ACK_TAIL (RPMI_P2A_ACK_BASE + RPMI_SLOT_SIZE)
+#define RPMI_P2A_ACK_SLOT0 (RPMI_P2A_ACK_BASE + 2 * RPMI_SLOT_SIZE)
+
+static uint64_t rpmi_response_base;
+
+static uint64_t rpmi_queue_slot(uint64_t queue_base, uint32_t index)
+{
+ return queue_base + (index + 2) * RPMI_SLOT_SIZE;
+}
+
+static void rpmi_send_request(QTestState *qts, uint16_t service_group,
+ uint8_t service_id, uint8_t request_type,
+ const uint32_t *data, size_t data_words)
+{
+ uint32_t tail = qtest_readl(qts, RPMI_A2P_TAIL);
+ uint64_t slot = rpmi_queue_slot(RPMI_SHMEM_BASE, tail);
+ size_t i;
+
+ qtest_writew(qts, slot, service_group);
+ qtest_writeb(qts, slot + 2, service_id);
+ qtest_writeb(qts, slot + 3, request_type);
+ qtest_writew(qts, slot + 4, data_words * sizeof(*data));
+ qtest_writew(qts, slot + 6, RPMI_TOKEN);
+
+ for (i = 0; i < data_words; i++) {
+ qtest_writel(qts, slot + 8 + i * sizeof(*data), data[i]);
+ }
+
+ g_test_message(
+ "RPMI_A2P_REQ shmem=0x%016" PRIx64 " doorbell=0x%016" PRIx64
+ " group=0x%04x service=0x%02x type=0x%02x data_len=%zu"
+ " token=0x%04x a2p_tail=%u slot=0x%016" PRIx64,
+ (uint64_t)RPMI_SHMEM_BASE, (uint64_t)RPMI_DOORBELL_BASE,
+ service_group, service_id, request_type, data_words * sizeof(*data),
+ RPMI_TOKEN, tail, slot);
+
+ qtest_writel(qts, RPMI_A2P_TAIL, (tail + 1) % 16);
+ qtest_writel(qts, RPMI_DOORBELL_BASE, 1);
+}
+
+static uint32_t rpmi_response_word(QTestState *qts, unsigned int word)
+{
+ return qtest_readl(qts, rpmi_response_base + 8 + word * sizeof(uint32_t));
+}
+
+static void rpmi_expect_ack(QTestState *qts, uint16_t service_group,
+ uint8_t service_id, uint16_t data_len)
+{
+ uint32_t head = qtest_readl(qts, RPMI_P2A_ACK_HEAD);
+ uint32_t tail = qtest_readl(qts, RPMI_P2A_ACK_TAIL);
+
+ g_assert_cmphex(tail, !=, head);
+ rpmi_response_base = rpmi_queue_slot(RPMI_P2A_ACK_BASE, head);
+ g_assert_cmphex(qtest_readw(qts, rpmi_response_base), ==, service_group);
+ g_assert_cmphex(qtest_readb(qts, rpmi_response_base + 2), ==,
+ service_id);
+ g_assert_cmphex(qtest_readb(qts, rpmi_response_base + 3), ==,
+ RPMI_MSG_ACKNOWLEDGEMENT);
+ g_assert_cmphex(qtest_readw(qts, rpmi_response_base + 4), ==, data_len);
+ g_assert_cmphex(qtest_readw(qts, rpmi_response_base + 6), ==,
+ RPMI_TOKEN);
+ g_test_message(
+ "RPMI_P2A_ACK shmem=0x%016" PRIx64
+ " group=0x%04x service=0x%02x type=0x%02x data_len=%u"
+ " token=0x%04x p2a_head=%u slot=0x%016" PRIx64
+ " status=0x%08x",
+ (uint64_t)RPMI_SHMEM_BASE, service_group, service_id,
+ RPMI_MSG_ACKNOWLEDGEMENT, data_len, RPMI_TOKEN, head,
+ rpmi_response_base,
+ data_len >= sizeof(uint32_t) ? rpmi_response_word(qts, 0) : 0);
+ qtest_writel(qts, RPMI_P2A_ACK_HEAD, (head + 1) % 16);
+}
+
+static void rpmi_send_sysreset(QTestState *qts, uint32_t reset_type,
+ uint8_t request_type)
+{
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_SYSTEM_RESET, request_type,
+ &reset_type, 1);
+}
+
+static void rpmi_expect_qemu_failure(const char *extra_args,
+ const char *stderr_needle)
+{
+ g_autoptr(GError) error = NULL;
+ g_auto(GStrv) split_args = NULL;
+ g_autoptr(GPtrArray) argv = g_ptr_array_new();
+ g_autofree char *stderr_data = NULL;
+ gint wait_status;
+ gboolean spawned;
+
+ g_assert_true(g_shell_parse_argv(extra_args, NULL, &split_args, &error));
+ g_assert_no_error(error);
+
+ g_ptr_array_add(argv, (gpointer)qtest_qemu_binary(NULL));
+ for (char **arg = split_args; *arg; arg++) {
+ g_ptr_array_add(argv, *arg);
+ }
+ g_ptr_array_add(argv, NULL);
+
+ spawned = g_spawn_sync(NULL, (char **)argv->pdata, NULL,
+ G_SPAWN_STDOUT_TO_DEV_NULL,
+ NULL, NULL, NULL, &stderr_data,
+ &wait_status, &error);
+ g_assert_true(spawned);
+ g_assert_no_error(error);
+ g_assert_false(g_spawn_check_exit_status(wait_status, NULL));
+ g_assert_nonnull(stderr_data);
+ g_assert_nonnull(strstr(stderr_data, stderr_needle));
+}
+
+static void test_rpmi_machine_realize_off(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=off");
+ qtest_quit(qts);
+}
+
+static void test_rpmi_machine_rejects_too_many_harts(void)
+{
+ rpmi_expect_qemu_failure(
+ "-machine virt,rpmi=on -smp 513 -display none -S",
+ "max CPUs supported by machine 'virt' is 512");
+}
+
+static void test_rpmi_base_platform_info(void)
+{
+ static const char expected[] = "QEMU RISC-V virt RPMI";
+ QTestState *qts;
+ size_t i;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ rpmi_send_request(qts, RPMI_SRVGRP_BASE,
+ RPMI_BASE_SRV_GET_PLATFORM_INFO,
+ RPMI_MSG_NORMAL_REQUEST, NULL, 0);
+
+ rpmi_expect_ack(qts, RPMI_SRVGRP_BASE,
+ RPMI_BASE_SRV_GET_PLATFORM_INFO,
+ 2 * sizeof(uint32_t) + sizeof(expected));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, sizeof(expected));
+ for (i = 0; i < sizeof(expected); i++) {
+ g_assert_cmphex(qtest_readb(qts, RPMI_P2A_ACK_SLOT0 + 16 + i), ==,
+ expected[i]);
+ }
+
+ qtest_quit(qts);
+}
+
+static void rpmi_probe_group(QTestState *qts, uint32_t service_group,
+ bool present)
+{
+ rpmi_send_request(qts, RPMI_SRVGRP_BASE,
+ RPMI_BASE_SRV_PROBE_SERVICE_GROUP,
+ RPMI_MSG_NORMAL_REQUEST, &service_group, 1);
+
+ rpmi_expect_ack(qts, RPMI_SRVGRP_BASE,
+ RPMI_BASE_SRV_PROBE_SERVICE_GROUP,
+ 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ if (present) {
+ g_assert_cmphex(rpmi_response_word(qts, 1), !=, 0);
+ } else {
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, 0);
+ }
+}
+
+static void test_rpmi_base_probe_service_groups(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on,aia=aplic-imsic");
+ rpmi_probe_group(qts, RPMI_SRVGRP_BASE, true);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_SYSTEM_RESET, true);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_HSM, true);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_SYSTEM_SUSPEND, true);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_CPPC, false);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_SYSTEM_MSI, false);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_CLOCK, false);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_MANAGEMENT_MODE, false);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_LOGGING, false);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_sysreset_attrs(void)
+{
+ QTestState *qts;
+ uint32_t reset_type = RPMI_SYSRST_TYPE_SHUTDOWN;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES,
+ RPMI_MSG_NORMAL_REQUEST, &reset_type, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES, 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==,
+ RPMI_SYSRST_ATTRS_FLAGS_RESETTYPE);
+
+ qtest_system_reset(qts);
+ reset_type = RPMI_SYSRST_TYPE_COLD_REBOOT;
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES,
+ RPMI_MSG_NORMAL_REQUEST, &reset_type, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES, 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==,
+ RPMI_SYSRST_ATTRS_FLAGS_RESETTYPE);
+
+ qtest_system_reset(qts);
+ reset_type = RPMI_SYSRST_TYPE_INVALID;
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES,
+ RPMI_MSG_NORMAL_REQUEST, &reset_type, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES, 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_sysreset_shutdown(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ rpmi_send_sysreset(qts, RPMI_SYSRST_TYPE_SHUTDOWN,
+ RPMI_MSG_POSTED_REQUEST);
+ qtest_qmp_eventwait(qts, "SHUTDOWN");
+ qtest_quit(qts);
+}
+
+static void test_rpmi_sysreset_cold_reboot(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on -no-reboot");
+ rpmi_send_sysreset(qts, RPMI_SYSRST_TYPE_COLD_REBOOT,
+ RPMI_MSG_POSTED_REQUEST);
+ qtest_qmp_eventwait(qts, "SHUTDOWN");
+ qtest_quit(qts);
+}
+
+static void test_rpmi_sysreset_invalid_type(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ rpmi_send_sysreset(qts, RPMI_SYSRST_TYPE_INVALID,
+ RPMI_MSG_NORMAL_REQUEST);
+
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_SYSTEM_RESET, sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==,
+ RPMI_ERR_INVALID_PARAM);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_repeated_reset_after_traffic(void)
+{
+ QTestState *qts;
+ uint32_t reset_type = RPMI_SYSRST_TYPE_SHUTDOWN;
+
+ qts = qtest_init("-machine virt,rpmi=on,aia=aplic-imsic");
+ for (uint32_t i = 0; i < 5; i++) {
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES,
+ RPMI_MSG_NORMAL_REQUEST,
+ &reset_type, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES,
+ 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ qtest_system_reset(qts);
+ }
+ qtest_quit(qts);
+}
+
+static void test_rpmi_reset_clears_transport(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ rpmi_send_sysreset(qts, RPMI_SYSRST_TYPE_INVALID,
+ RPMI_MSG_NORMAL_REQUEST);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_SYSTEM_RESET, sizeof(uint32_t));
+
+ qtest_system_reset(qts);
+
+ g_assert_cmphex(qtest_readl(qts, RPMI_A2P_TAIL), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, RPMI_P2A_ACK_TAIL), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, RPMI_DOORBELL_BASE), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_doorbell_invalid_access(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ qtest_writeb(qts, RPMI_DOORBELL_BASE, 1);
+ qtest_writel(qts, RPMI_DOORBELL_BASE + 4, 1);
+ g_assert_cmphex(qtest_readl(qts, RPMI_DOORBELL_BASE), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_queue_bounds(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ qtest_writel(qts, RPMI_A2P_TAIL, 0x1000);
+ qtest_writel(qts, RPMI_DOORBELL_BASE, 1);
+ g_assert_cmphex(qtest_readl(qts, RPMI_A2P_HEAD), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, RPMI_P2A_ACK_TAIL), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_migration_blocked(void)
+{
+ QTestState *qts;
+ QDict *error;
+ const char *desc;
+
+ qts = qtest_init("-machine virt,rpmi=on -S");
+ error = qtest_qmp_assert_failure_ref(qts,
+ "{ 'execute': 'migrate',"
+ " 'arguments': { 'uri': 'exec:cat > /dev/null' } }");
+ desc = qdict_get_try_str(error, "desc");
+
+ g_assert_nonnull(desc);
+ g_assert_nonnull(strstr(desc, "non-migratable device"));
+ g_assert_nonnull(strstr(desc, "riscv-rpmi"));
+
+ qobject_unref(error);
+ qtest_quit(qts);
+}
+
+static void test_rpmi_hsm_hart_list(void)
+{
+ QTestState *qts;
+ uint32_t start_index = 0;
+
+ qts = qtest_init("-machine virt,rpmi=on -smp 4");
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_LIST,
+ RPMI_MSG_NORMAL_REQUEST, &start_index, 1);
+
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_LIST,
+ 7 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 2), ==, 4);
+ g_assert_cmphex(rpmi_response_word(qts, 3), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 4), ==, 1);
+ g_assert_cmphex(rpmi_response_word(qts, 5), ==, 2);
+ g_assert_cmphex(rpmi_response_word(qts, 6), ==, 3);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_hsm_multi_socket_hart_list(void)
+{
+ QTestState *qts;
+ uint32_t start_index = 0;
+
+ qts = qtest_init("-machine virt,rpmi=on "
+ "-smp 4,sockets=2,cores=2,threads=1");
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_LIST,
+ RPMI_MSG_NORMAL_REQUEST, &start_index, 1);
+
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_LIST,
+ 7 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 2), ==, 4);
+ g_assert_cmphex(rpmi_response_word(qts, 3), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 4), ==, 1);
+ g_assert_cmphex(rpmi_response_word(qts, 5), ==, 2);
+ g_assert_cmphex(rpmi_response_word(qts, 6), ==, 3);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_hsm_hart_status(void)
+{
+ QTestState *qts;
+ uint32_t hart_id = 3;
+
+ qts = qtest_init("-machine virt,rpmi=on -smp 4");
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_STATUS,
+ RPMI_MSG_NORMAL_REQUEST, &hart_id, 1);
+
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_STATUS,
+ 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==,
+ RPMI_HSM_HART_STATE_STARTED);
+
+ qtest_quit(qts);
+}
+
+static void rpmi_expect_hsm_status(QTestState *qts, uint32_t hart_id,
+ uint32_t expected_state)
+{
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_STATUS,
+ RPMI_MSG_NORMAL_REQUEST, &hart_id, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_STATUS,
+ 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, expected_state);
+}
+
+static void test_rpmi_hsm_hart_control(void)
+{
+ QTestState *qts;
+ uint32_t hart_id = 1;
+ uint32_t stop_request[] = { hart_id };
+ uint32_t start_request[] = { hart_id, 0x80000000, 0 };
+ uint32_t suspend_request[] = { hart_id, 0, 0x80001000, 0 };
+ uint32_t start_index = 0;
+ uint32_t suspend_type = 0;
+
+ qts = qtest_init("-machine virt,rpmi=on -smp 2");
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_SUSPEND_TYPES,
+ RPMI_MSG_NORMAL_REQUEST, &start_index, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_SUSPEND_TYPES,
+ 4 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 2), ==, 1);
+ g_assert_cmphex(rpmi_response_word(qts, 3), ==, 0);
+
+ qtest_system_reset(qts);
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_SUSPEND_INFO,
+ RPMI_MSG_NORMAL_REQUEST, &suspend_type, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_SUSPEND_INFO,
+ 6 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+
+ qtest_system_reset(qts);
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_HART_STOP,
+ RPMI_MSG_NORMAL_REQUEST, stop_request,
+ ARRAY_SIZE(stop_request));
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_HART_STOP,
+ sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ rpmi_expect_hsm_status(qts, hart_id, RPMI_HSM_HART_STATE_STOPPED);
+
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_HART_START,
+ RPMI_MSG_NORMAL_REQUEST, start_request,
+ ARRAY_SIZE(start_request));
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_HART_START,
+ sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ rpmi_expect_hsm_status(qts, hart_id, RPMI_HSM_HART_STATE_STARTED);
+
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_HART_SUSPEND,
+ RPMI_MSG_NORMAL_REQUEST, suspend_request,
+ ARRAY_SIZE(suspend_request));
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_HART_SUSPEND,
+ sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ rpmi_expect_hsm_status(qts, hart_id, RPMI_HSM_HART_STATE_SUSPENDED);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_syssusp_attrs_and_suspend(void)
+{
+ QTestState *qts;
+ uint32_t suspend_type = 0;
+ uint32_t suspend_request[] = { 0, 0, 0x80000000, 0 };
+
+ qts = qtest_init("-machine virt,rpmi=on -smp 1");
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_SUSPEND,
+ RPMI_SYSSUSP_SRV_GET_ATTRIBUTES,
+ RPMI_MSG_NORMAL_REQUEST, &suspend_type, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_SUSPEND,
+ RPMI_SYSSUSP_SRV_GET_ATTRIBUTES,
+ 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, 3);
+
+ qtest_system_reset(qts);
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_SUSPEND,
+ RPMI_SYSSUSP_SRV_SYSTEM_SUSPEND,
+ RPMI_MSG_NORMAL_REQUEST, suspend_request,
+ ARRAY_SIZE(suspend_request));
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_SUSPEND,
+ RPMI_SYSSUSP_SRV_SYSTEM_SUSPEND, sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ qtest_qmp_eventwait(qts, "SUSPEND");
+ qtest_qmp_assert_success(qts, "{ 'execute': 'system_wakeup' }");
+ qtest_qmp_eventwait(qts, "WAKEUP");
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ if (qtest_has_machine("virt")) {
+ qtest_add_func("/riscv/rpmi/machine/realize-off",
+ test_rpmi_machine_realize_off);
+ qtest_add_func("/riscv/rpmi/machine/rejects-too-many-harts",
+ test_rpmi_machine_rejects_too_many_harts);
+ qtest_add_func("/riscv/rpmi/base/platform-info",
+ test_rpmi_base_platform_info);
+ qtest_add_func("/riscv/rpmi/base/probe-service-groups",
+ test_rpmi_base_probe_service_groups);
+ qtest_add_func("/riscv/rpmi/sysreset/attrs",
+ test_rpmi_sysreset_attrs);
+ qtest_add_func("/riscv/rpmi/sysreset/shutdown",
+ test_rpmi_sysreset_shutdown);
+ qtest_add_func("/riscv/rpmi/sysreset/cold-reboot",
+ test_rpmi_sysreset_cold_reboot);
+ qtest_add_func("/riscv/rpmi/sysreset/invalid-type",
+ test_rpmi_sysreset_invalid_type);
+ qtest_add_func("/riscv/rpmi/reset/clears-transport",
+ test_rpmi_reset_clears_transport);
+ qtest_add_func("/riscv/rpmi/negative/doorbell-invalid-access",
+ test_rpmi_doorbell_invalid_access);
+ qtest_add_func("/riscv/rpmi/negative/queue-bounds",
+ test_rpmi_queue_bounds);
+ qtest_add_func("/riscv/rpmi/reset/repeated-after-traffic",
+ test_rpmi_repeated_reset_after_traffic);
+ qtest_add_func("/riscv/rpmi/migration/blocked",
+ test_rpmi_migration_blocked);
+ qtest_add_func("/riscv/rpmi/hsm/hart-list",
+ test_rpmi_hsm_hart_list);
+ qtest_add_func("/riscv/rpmi/hsm/multi-socket-hart-list",
+ test_rpmi_hsm_multi_socket_hart_list);
+ qtest_add_func("/riscv/rpmi/hsm/hart-status",
+ test_rpmi_hsm_hart_status);
+ qtest_add_func("/riscv/rpmi/hsm/hart-control",
+ test_rpmi_hsm_hart_control);
+ qtest_add_func("/riscv/rpmi/syssusp/attrs-and-suspend",
+ test_rpmi_syssusp_attrs_and_suspend);
+ }
+
+ return g_test_run();
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v2 10/10] tests/functional: add initial RPMI virt guest smoke
2026-07-10 8:39 ` [PATCH v2 " Subrahmanya Lingappa
(?)
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
-1 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 5:43 UTC (permalink / raw)
To: qemu-devel
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Boot an RPMI-enabled RISC-V virt guest and exercise the initial HSM,
system suspend, and system reset paths.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
tests/functional/riscv64/meson.build | 2 +
tests/functional/riscv64/test_rpmi_virt.py | 172 +++++++++++++++++++++
2 files changed, 174 insertions(+)
create mode 100644 tests/functional/riscv64/test_rpmi_virt.py
diff --git a/tests/functional/riscv64/meson.build b/tests/functional/riscv64/meson.build
index 5871211e89..619a0735fa 100644
--- a/tests/functional/riscv64/meson.build
+++ b/tests/functional/riscv64/meson.build
@@ -3,6 +3,7 @@
test_riscv64_timeouts = {
'boston' : 120,
'tuxrun' : 120,
+ 'rpmi_virt' : 300,
}
tests_riscv64_system_quick = [
@@ -15,4 +16,5 @@ tests_riscv64_system_thorough = [
'boston',
'sifive_u',
'tuxrun',
+ 'rpmi_virt',
]
diff --git a/tests/functional/riscv64/test_rpmi_virt.py b/tests/functional/riscv64/test_rpmi_virt.py
new file mode 100644
index 0000000000..c0f96f7d8d
--- /dev/null
+++ b/tests/functional/riscv64/test_rpmi_virt.py
@@ -0,0 +1,172 @@
+#!/usr/bin/env python3
+#
+# RISC-V virt RPMI Linux guest integration tests
+#
+# Copyright (c) 2026 Ventana Micro Systems Inc.
+# Copyright (c) 2026 Qualcomm Technologies, Inc.
+#
+# Author:
+# Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import os
+from pathlib import Path
+import subprocess
+import time
+
+from qemu_test import QemuSystemTest
+from qemu_test import exec_command
+from qemu_test import exec_command_and_wait_for_pattern
+from qemu_test import get_qemu_img
+from qemu_test import wait_for_console_pattern
+
+
+class RiscvVirtRpmiGuestTest(QemuSystemTest):
+ """Boot Linux and exercise RPMI-backed services through guest APIs.
+
+ This test intentionally keeps guest artifacts external. Set:
+
+ QEMU_TEST_RPMI_OPENSBI=/path/to/fw_jump.bin
+ QEMU_TEST_RPMI_KERNEL=/path/to/Image
+ QEMU_TEST_RPMI_ROOTFS=/path/to/rootfs.ext4
+
+ Exact RPMI packet command-ID coverage remains in riscv-rpmi qtests. This
+ functional test verifies Linux/OpenSBI integration paths visible from a
+ real guest without adding probe hooks to production RPMI sources.
+ """
+
+ timeout = 360
+ PROMPT = 'root@tuxtest:~#'
+
+ def _require_path(self, env_name):
+ value = os.getenv(env_name)
+ if not value:
+ self.skipTest(f'{env_name} is required')
+ path = Path(value)
+ if not path.exists():
+ self.skipTest(f'{env_name} does not exist: {path}')
+ return path
+
+ def setUp(self):
+ super().setUp()
+ self.opensbi = self._require_path('QEMU_TEST_RPMI_OPENSBI')
+ self.kernel = self._require_path('QEMU_TEST_RPMI_KERNEL')
+ self.rootfs = self._require_path('QEMU_TEST_RPMI_ROOTFS')
+ self.qemu_img = get_qemu_img(self)
+
+ def _create_rootfs_overlay(self):
+ overlay = Path(self.scratch_file('rootfs.qcow2'))
+ subprocess.run([
+ self.qemu_img, 'create', '-q', '-f', 'qcow2', '-F', 'raw',
+ '-b', str(self.rootfs), str(overlay),
+ ], check=True)
+ return overlay
+
+ def _boot_guest(self, smp=4):
+ self.set_machine('virt')
+ self.machine = 'virt,rpmi=on,aia=aplic-imsic'
+ rootfs_overlay = self._create_rootfs_overlay()
+
+ self.vm.set_console()
+ self.vm.add_args(
+ '-cpu', 'rv64',
+ '-smp', str(smp),
+ '-m', '1G',
+ '-bios', str(self.opensbi),
+ '-kernel', str(self.kernel),
+ '-append', 'printk.time=0 root=/dev/vda rw console=ttyS0 '
+ 'earlycon=sbi loglevel=8 no_console_suspend',
+ '-blockdev', 'driver=qcow2,file.driver=file,'
+ f'file.filename={rootfs_overlay},node-name=hd0',
+ '-device', 'virtio-blk-device,drive=hd0',
+ )
+ self.vm.launch()
+
+ def _expect_rpmi_firmware_and_linux(self):
+ patterns = [
+ 'Platform HSM Device : rpmi-hsm',
+ 'Platform Suspend Device : rpmi-system-suspend',
+ 'Standard SBI Extensions',
+ 'susp',
+ 'tuxtest login:',
+ ]
+ for pattern in patterns:
+ wait_for_console_pattern(self, pattern)
+ exec_command_and_wait_for_pattern(self, 'root', self.PROMPT)
+
+ def _run_marker_command(self, command, marker, failure_marker=None):
+ out = exec_command_and_wait_for_pattern(self, command, marker,
+ failure_message=failure_marker)
+ self.assertIn(marker.encode(), out)
+ return out
+
+ def _validate_hsm_boot_start(self, online_cpus):
+ self._run_marker_command(
+ 'echo RPMI_LINUX_HSM_START_BEGIN; '
+ 'cat /sys/devices/system/cpu/present; '
+ 'online=$(cat /sys/devices/system/cpu/online); '
+ 'echo "$online"; '
+ f'if [ "$online" = "{online_cpus}" ]; '
+ 'then echo RPMI_LINUX_HSM_START_PASS; '
+ 'else echo RPMI_LINUX_HSM_START_FAIL; fi',
+ 'RPMI_LINUX_HSM_START_PASS', 'RPMI_LINUX_HSM_START_FAIL')
+
+ def _validate_hsm_stop(self):
+ self._run_marker_command(
+ 'echo RPMI_LINUX_HSM_STOP_BEGIN; '
+ 'if [ -w /sys/devices/system/cpu/cpu1/online ]; then '
+ 'echo 0 > /sys/devices/system/cpu/cpu1/online && '
+ '[ "$(cat /sys/devices/system/cpu/online)" = 0 ] && '
+ 'echo RPMI_LINUX_HSM_STOP_PASS || echo RPMI_LINUX_HSM_STOP_FAIL; '
+ 'else echo RPMI_LINUX_HSM_STOP_FAIL; fi',
+ 'RPMI_LINUX_HSM_STOP_PASS', 'RPMI_LINUX_HSM_STOP_FAIL')
+
+ def _validate_system_suspend(self):
+ exec_command(
+ self,
+ 'echo RPMI_LINUX_SUSPEND_BEGIN; '
+ 'if grep -qw mem /sys/power/state; then '
+ 'echo mem > /sys/power/state; '
+ 'else echo RPMI_LINUX_SUSPEND_FAIL; fi')
+ wait_for_console_pattern(self, 'RPMI_LINUX_SUSPEND_BEGIN')
+ wait_for_console_pattern(self, 'PM: suspend entry')
+ self.vm.event_wait('SUSPEND', 10)
+ self.vm.qmp('system_wakeup')
+ self.vm.event_wait('WAKEUP', 10)
+ wait_for_console_pattern(self, 'PM: suspend exit')
+ wait_for_console_pattern(self, self.PROMPT)
+ self._run_marker_command(
+ 'marker=RPMI_LINUX_WAKEUP; echo ${marker}_PASS',
+ 'RPMI_LINUX_WAKEUP_PASS')
+
+ def _validate_reset_poweroff(self):
+ exec_command(self, 'echo RPMI_LINUX_RESET_POWEROFF_BEGIN; poweroff -f')
+ wait_for_console_pattern(self, 'RPMI_LINUX_RESET_POWEROFF_BEGIN')
+ wait_for_console_pattern(self, 'reboot: Power down')
+ self.vm.wait()
+
+ def test_linux_guest_service_enumeration(self):
+ self._boot_guest(smp=4)
+ self._expect_rpmi_firmware_and_linux()
+ self._validate_hsm_boot_start('0-3')
+
+ def test_linux_guest_hsm_stop(self):
+ self._boot_guest(smp=2)
+ self._expect_rpmi_firmware_and_linux()
+ self._validate_hsm_boot_start('0-1')
+ self._validate_hsm_stop()
+
+ def test_linux_guest_suspend(self):
+ self._boot_guest(smp=1)
+ self._expect_rpmi_firmware_and_linux()
+ self._validate_system_suspend()
+
+ def test_linux_guest_reset_poweroff(self):
+ self._boot_guest(smp=1)
+ self._expect_rpmi_firmware_and_linux()
+ self._validate_reset_poweroff()
+
+
+if __name__ == '__main__':
+ QemuSystemTest.main()
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v2 00/10] riscv: add initial RPMI virt support
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 8:39 UTC (permalink / raw)
To: qemu-riscv; +Cc: qemu-devel, daniel.barboza, Subrahmanya Lingappa
Add optional librpmi support and the initial RISC-V virt RPMI
implementation. This series introduces the shared-memory transport, FDT
helpers, opt-in virt wiring, and the Base, System Reset, HSM, and System
Suspend service groups with qtest and functional coverage.
The implementation targets the RISC-V Platform Management Interface
(RPMI) Specification v1.0 Ratified and uses librpmi as the backend.
Changes since v1:
- Resend with the oss.qualcomm.com author, signoff, and mail metadata.
Subrahmanya Lingappa (10):
build: add optional librpmi dependency
hw/misc: add RISC-V RPMI transport and Base support
hw/riscv: add RPMI FDT helpers
hw/riscv/virt: add opt-in RPMI base support
riscv: add RPMI system reset service
riscv: add RPMI HSM service
riscv: add RPMI system suspend service
docs/system/riscv: document initial virt RPMI support
tests/qtest: cover initial RISC-V RPMI services
tests/functional: add initial RPMI virt guest smoke
Kconfig.host | 3 +
MAINTAINERS | 3 +
docs/system/riscv/virt.rst | 50 ++
hw/misc/Kconfig | 4 +
hw/misc/meson.build | 6 +
hw/misc/riscv_rpmi.c | 558 +++++++++++++++++
hw/misc/riscv_rpmi_hsm.c | 287 +++++++++
hw/misc/riscv_rpmi_internal.h | 42 ++
hw/misc/riscv_rpmi_sysreset.c | 206 +++++++
hw/misc/riscv_rpmi_syssusp.c | 191 ++++++
hw/riscv/Kconfig | 1 +
hw/riscv/meson.build | 1 +
hw/riscv/rpmi-fdt.c | 103 ++++
hw/riscv/virt.c | 167 +++++
include/hw/misc/riscv_rpmi.h | 126 ++++
include/hw/riscv/rpmi-fdt.h | 41 ++
include/hw/riscv/virt.h | 3 +
meson.build | 14 +
meson_options.txt | 2 +
scripts/meson-buildoptions.sh | 3 +
tests/functional/riscv64/meson.build | 2 +
tests/functional/riscv64/test_rpmi_virt.py | 172 ++++++
tests/qtest/meson.build | 2 +
tests/qtest/riscv-rpmi-test.c | 684 +++++++++++++++++++++
24 files changed, 2671 insertions(+)
create mode 100644 hw/misc/riscv_rpmi.c
create mode 100644 hw/misc/riscv_rpmi_hsm.c
create mode 100644 hw/misc/riscv_rpmi_internal.h
create mode 100644 hw/misc/riscv_rpmi_sysreset.c
create mode 100644 hw/misc/riscv_rpmi_syssusp.c
create mode 100644 hw/riscv/rpmi-fdt.c
create mode 100644 include/hw/misc/riscv_rpmi.h
create mode 100644 include/hw/riscv/rpmi-fdt.h
create mode 100644 tests/functional/riscv64/test_rpmi_virt.py
create mode 100644 tests/qtest/riscv-rpmi-test.c
--
2.43.0
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v2 01/10] build: add optional librpmi dependency
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 8:39 UTC (permalink / raw)
To: qemu-riscv; +Cc: qemu-devel, daniel.barboza, Subrahmanya Lingappa
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add a meson option and config probe for librpmi so RISC-V machines can
opt into RPMI support when the library is available. Keep the dependency
optional so default builds without librpmi continue to work.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
Kconfig.host | 3 +++
meson.build | 14 ++++++++++++++
meson_options.txt | 2 ++
scripts/meson-buildoptions.sh | 3 +++
4 files changed, 22 insertions(+)
diff --git a/Kconfig.host b/Kconfig.host
index 933425c74b..6f98dadb2e 100644
--- a/Kconfig.host
+++ b/Kconfig.host
@@ -8,6 +8,9 @@ config LINUX
config LIBCBOR
bool
+config LIBRPMI
+ bool
+
config GNUTLS
bool
diff --git a/meson.build b/meson.build
index 19e123423b..103b4649b8 100644
--- a/meson.build
+++ b/meson.build
@@ -2005,6 +2005,17 @@ if lzo.found() and not cc.links('''
endif
endif
+librpmi = not_found
+if not get_option('librpmi').auto() or have_system
+ librpmi = dependency('librpmi', required: get_option('librpmi'),
+ method: 'pkg-config')
+ if librpmi.found() and not cc.has_header_symbol('librpmi.h',
+ 'rpmi_service_group_logging_create',
+ dependencies: librpmi)
+ error('librpmi support requires the RPMI logging service group API')
+ endif
+endif
+
numa = not_found
if not get_option('numa').auto() or have_system or have_tools
numa = cc.find_library('numa', has_headers: ['numa.h'],
@@ -2427,6 +2438,7 @@ config_host_data.set('CONFIG_LINUX', host_os == 'linux')
config_host_data.set('CONFIG_POSIX', host_os != 'windows')
config_host_data.set('CONFIG_WIN32', host_os == 'windows')
config_host_data.set('CONFIG_LZO', lzo.found())
+config_host_data.set('CONFIG_LIBRPMI', librpmi.found())
config_host_data.set('CONFIG_MPATH', mpathpersist.found())
config_host_data.set('CONFIG_BLKIO', blkio.found())
if blkio.found()
@@ -3258,6 +3270,7 @@ host_kconfig = \
(multiprocess_allowed ? ['CONFIG_MULTIPROCESS_ALLOWED=y'] : []) + \
(vfio_user_server_allowed ? ['CONFIG_VFIO_USER_SERVER_ALLOWED=y'] : []) + \
(hv_balloon ? ['CONFIG_HV_BALLOON_POSSIBLE=y'] : []) + \
+ (librpmi.found() ? ['CONFIG_LIBRPMI=y'] : []) + \
(have_rust ? ['CONFIG_HAVE_RUST=y'] : [])
ignored = [ 'TARGET_XML_FILES', 'TARGET_ABI_DIR' ]
@@ -4971,6 +4984,7 @@ summary_info += {'TPM support': have_tpm}
summary_info += {'IGVM support': igvm}
summary_info += {'libssh support': libssh}
summary_info += {'lzo support': lzo}
+summary_info += {'librpmi support': librpmi}
summary_info += {'snappy support': snappy}
summary_info += {'bzip2 support': libbzip2}
summary_info += {'lzfse support': liblzfse}
diff --git a/meson_options.txt b/meson_options.txt
index a07cb47d35..9f773a7886 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -204,6 +204,8 @@ option('lzfse', type : 'feature', value : 'auto',
description: 'lzfse support for DMG images')
option('lzo', type : 'feature', value : 'auto',
description: 'lzo compression support')
+option('librpmi', type : 'feature', value : 'auto',
+ description: 'RISC-V RPMI support')
option('pvg', type: 'feature', value: 'auto',
description: 'macOS paravirtualized graphics support')
option('rbd', type : 'feature', value : 'auto',
diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
index c003985047..e8a20068e7 100644
--- a/scripts/meson-buildoptions.sh
+++ b/scripts/meson-buildoptions.sh
@@ -140,6 +140,7 @@ meson_options_help() {
printf "%s\n" ' libkeyutils Linux keyutils support'
printf "%s\n" ' libnfs libnfs block device driver'
printf "%s\n" ' libpmem libpmem support'
+ printf "%s\n" ' librpmi RISC-V RPMI support'
printf "%s\n" ' libssh ssh block device support'
printf "%s\n" ' libudev Use libudev to enumerate host devices'
printf "%s\n" ' libusb libusb support for USB passthrough'
@@ -376,6 +377,8 @@ _meson_option_parse() {
--disable-libnfs) printf "%s" -Dlibnfs=disabled ;;
--enable-libpmem) printf "%s" -Dlibpmem=enabled ;;
--disable-libpmem) printf "%s" -Dlibpmem=disabled ;;
+ --enable-librpmi) printf "%s" -Dlibrpmi=enabled ;;
+ --disable-librpmi) printf "%s" -Dlibrpmi=disabled ;;
--enable-libssh) printf "%s" -Dlibssh=enabled ;;
--disable-libssh) printf "%s" -Dlibssh=disabled ;;
--enable-libudev) printf "%s" -Dlibudev=enabled ;;
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v2 02/10] hw/misc: add RISC-V RPMI transport and Base support
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 8:39 UTC (permalink / raw)
To: qemu-riscv; +Cc: qemu-devel, daniel.barboza, Subrahmanya Lingappa
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
RPMI (RISC-V Platform Management Interface) is the RISC-V non-ISA
platform management specification. It defines a shared-memory transport,
message protocol, and service groups for platform management.
Add the QEMU transport backend and Base service plumbing for virt using
librpmi.
This implementation targets the RISC-V Platform Management Interface
(RPMI) Specification v1.0 Ratified:
https://github.com/riscv-non-isa/riscv-rpmi/releases/download/v1.0/riscv-rpmi.pdf
The librpmi backend is maintained at:
https://github.com/riscv-software-src/librpmi
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
MAINTAINERS | 2 +
hw/misc/Kconfig | 4 +
hw/misc/meson.build | 3 +
hw/misc/riscv_rpmi.c | 476 ++++++++++++++++++++++++++++++++++
hw/misc/riscv_rpmi_internal.h | 24 ++
include/hw/misc/riscv_rpmi.h | 90 +++++++
6 files changed, 599 insertions(+)
create mode 100644 hw/misc/riscv_rpmi.c
create mode 100644 hw/misc/riscv_rpmi_internal.h
create mode 100644 include/hw/misc/riscv_rpmi.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 93df53d87f..8e91a31757 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -359,6 +359,8 @@ F: target/riscv/
F: hw/char/riscv_htif.c
F: hw/riscv/
F: hw/intc/riscv*
+F: hw/misc/riscv_rpmi*
+F: include/hw/misc/riscv_rpmi.h
F: include/hw/char/riscv_htif.h
F: include/hw/riscv/
F: common-user/host/riscv*
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 1543ee6653..998944c342 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -258,3 +258,7 @@ config XLNX_ZYNQ_DDRC
bool
source macio/Kconfig
+
+config RISCV_RPMI
+ bool
+ depends on LIBRPMI
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 23265f6035..e7c2ebe28f 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -168,3 +168,6 @@ system_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa_ec.c'))
# HPPA devices
system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c'))
+system_ss.add(when: 'CONFIG_RISCV_RPMI', if_true: [files(
+ 'riscv_rpmi.c',
+), librpmi])
diff --git a/hw/misc/riscv_rpmi.c b/hw/misc/riscv_rpmi.c
new file mode 100644
index 0000000000..c4b115b659
--- /dev/null
+++ b/hw/misc/riscv_rpmi.c
@@ -0,0 +1,476 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI transport device.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "exec/cpu-common.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/misc/riscv_rpmi.h"
+#include "hw/misc/riscv_rpmi_internal.h"
+#include "migration/vmstate.h"
+#include "system/address-spaces.h"
+#include "system/runstate.h"
+#include "librpmi.h"
+#include "librpmi_env.h"
+
+void *rpmi_env_zalloc(rpmi_size_t size)
+{
+ return g_malloc0(size);
+}
+
+void rpmi_env_free(void *ptr)
+{
+ g_free(ptr);
+}
+
+void rpmi_env_writel(rpmi_uint64_t addr, rpmi_uint32_t val)
+{
+ cpu_physical_memory_write(addr, &val, sizeof(val));
+}
+
+static bool riscv_rpmi_addr_in_range(uint64_t addr, uint32_t len,
+ uint64_t base, uint64_t size)
+{
+ return len && size >= len && addr >= base && addr - base <= size - len;
+}
+
+static enum rpmi_error shmem_qemu_read(void *priv, rpmi_uint64_t addr,
+ void *buf, rpmi_uint32_t len)
+{
+ RiscvRpmiState *s = RISCV_RPMI(priv);
+
+ if (!riscv_rpmi_addr_in_range(addr, len, s->shmem_base, s->shmem_size) ||
+ addr != (rpmi_uint64_t)addr || len != (rpmi_uint32_t)len) {
+ return RPMI_ERR_INVALID_ADDR;
+ }
+
+ cpu_physical_memory_read(addr, buf, len);
+ return RPMI_SUCCESS;
+}
+
+static enum rpmi_error shmem_qemu_write(void *priv, rpmi_uint64_t addr,
+ const void *buf, rpmi_uint32_t len)
+{
+ RiscvRpmiState *s = RISCV_RPMI(priv);
+
+ if (!riscv_rpmi_addr_in_range(addr, len, s->shmem_base, s->shmem_size) ||
+ addr != (rpmi_uint64_t)addr || len != (rpmi_uint32_t)len) {
+ return RPMI_ERR_INVALID_ADDR;
+ }
+
+ cpu_physical_memory_write(addr, buf, len);
+ return RPMI_SUCCESS;
+}
+
+static enum rpmi_error shmem_qemu_fill(void *priv, rpmi_uint64_t addr,
+ char ch, rpmi_uint32_t len)
+{
+ while (len--) {
+ shmem_qemu_write(priv, addr++, &ch, sizeof(ch));
+ }
+
+ return RPMI_SUCCESS;
+}
+
+const struct rpmi_shmem_platform_ops rpmi_shmem_qemu_ops = {
+ .read = shmem_qemu_read,
+ .write = shmem_qemu_write,
+ .fill = shmem_qemu_fill,
+};
+
+static uint64_t riscv_rpmi_read(void *opaque, hwaddr offset, unsigned int size)
+{
+ RiscvRpmiState *s = opaque;
+
+ if (offset != 0 || size != 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid read at 0x%" HWADDR_PRIx " size %u\n",
+ __func__, offset, size);
+ return 0;
+ }
+
+ return s->doorbell;
+}
+
+static bool riscv_rpmi_transport_indices_valid(RiscvRpmiState *s);
+
+static void riscv_rpmi_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned int size)
+{
+ RiscvRpmiState *s = opaque;
+
+ if (offset != 0 || size != 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid write at 0x%" HWADDR_PRIx " size %u\n",
+ __func__, offset, size);
+ return;
+ }
+
+ s->doorbell = value;
+
+ if (value == 1) {
+ if (s->context && riscv_rpmi_transport_indices_valid(s)) {
+ rpmi_context_process_a2p_request(s->context);
+ rpmi_context_process_all_events(s->context);
+ }
+ s->doorbell = 0;
+ }
+}
+
+static const MemoryRegionOps riscv_rpmi_ops = {
+ .read = riscv_rpmi_read,
+ .write = riscv_rpmi_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static bool riscv_rpmi_queue_indices_valid(RiscvRpmiState *s,
+ uint64_t queue_base,
+ uint32_t queue_size)
+{
+ uint8_t *shmem = memory_region_get_ram_ptr(&s->shmem);
+ uint32_t data_slots = queue_size / RPMI_QUEUE_SLOT_SIZE - 2;
+ uint32_t head;
+ uint32_t tail;
+
+ head = ldl_le_p(shmem + queue_base);
+ tail = ldl_le_p(shmem + queue_base + RPMI_QUEUE_SLOT_SIZE);
+ if (head >= data_slots || tail >= data_slots) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid queue indices head %u tail %u slots %u\n",
+ __func__, head, tail, data_slots);
+ return false;
+ }
+
+ return true;
+}
+
+static bool riscv_rpmi_transport_indices_valid(RiscvRpmiState *s)
+{
+ if (!s->has_shmem || !s->a2p_req_size) {
+ return false;
+ }
+
+ if (!riscv_rpmi_queue_indices_valid(s, 0, s->a2p_req_size)) {
+ return false;
+ }
+
+ return riscv_rpmi_queue_indices_valid(s, s->a2p_req_size,
+ s->a2p_req_size);
+}
+
+static void riscv_rpmi_configure_base(RiscvRpmiState *s,
+ const RiscvRpmiConfig *cfg)
+{
+ s->platform_info = g_strdup(cfg->platform_info);
+ s->services = cfg->services;
+ s->service_count = cfg->service_count;
+
+ if (cfg->hart_count) {
+ s->hart_count = cfg->hart_count;
+ if (cfg->hart_ids) {
+ s->hart_ids = g_memdup2(cfg->hart_ids,
+ cfg->hart_count * sizeof(*cfg->hart_ids));
+ }
+ }
+}
+
+static void riscv_rpmi_init(Object *obj)
+{
+ RiscvRpmiState *s = RISCV_RPMI(obj);
+
+ memory_region_init_io(&s->mmio, obj, &riscv_rpmi_ops, s,
+ TYPE_RISCV_RPMI, RPMI_DBREG_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+}
+
+static void riscv_rpmi_reset(DeviceState *dev)
+{
+ RiscvRpmiState *s = RISCV_RPMI(dev);
+
+ s->doorbell = 0;
+
+ if (s->has_shmem) {
+ memset(memory_region_get_ram_ptr(&s->shmem), 0, s->shmem_size);
+ memory_region_set_dirty(&s->shmem, 0, s->shmem_size);
+ }
+
+}
+
+static void riscv_rpmi_cleanup(RiscvRpmiState *s)
+{
+
+
+ if (s->context) {
+ rpmi_context_destroy(s->context);
+ s->context = NULL;
+ }
+
+ if (s->transport) {
+ rpmi_transport_shmem_destroy(s->transport);
+ s->transport = NULL;
+ }
+
+ if (s->rpmi_shmem) {
+ rpmi_shmem_destroy(s->rpmi_shmem);
+ s->rpmi_shmem = NULL;
+ }
+
+ if (s->has_shmem) {
+ memory_region_del_subregion(get_system_memory(), &s->shmem);
+ s->has_shmem = false;
+ }
+}
+
+bool riscv_rpmi_service_enabled(RiscvRpmiState *s, RiscvRpmiServiceKind kind)
+{
+ uint32_t i;
+
+ for (i = 0; i < s->service_count; i++) {
+ if (s->services[i].kind == kind) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool riscv_rpmi_validate_config(RiscvRpmiState *s, Error **errp)
+{
+ uint64_t queue_bytes;
+
+ if (!s->shmem_size) {
+ error_setg(errp, "RPMI shared memory size must be non-zero");
+ return false;
+ }
+
+ if (s->shmem_base & (RPMI_QUEUE_SLOT_SIZE - 1)) {
+ error_setg(errp, "RPMI shared memory base must be %u-byte aligned",
+ RPMI_QUEUE_SLOT_SIZE);
+ return false;
+ }
+
+ if (!s->a2p_req_size) {
+ error_setg(errp, "RPMI A2P request queue size must be non-zero");
+ return false;
+ }
+
+ if (!s->hart_count || !s->hart_ids) {
+ error_setg(errp, "RPMI transport requires hart IDs");
+ return false;
+ }
+
+ for (uint32_t i = 0; i < s->hart_count; i++) {
+ for (uint32_t j = i + 1; j < s->hart_count; j++) {
+ if (s->hart_ids[i] == s->hart_ids[j]) {
+ error_setg(errp, "RPMI hart ID %u is duplicated",
+ s->hart_ids[i]);
+ return false;
+ }
+ }
+ }
+
+ if (s->service_count && !s->services) {
+ error_setg(errp, "RPMI service count requires service descriptors");
+ return false;
+ }
+
+ if (s->a2p_req_size % RPMI_QUEUE_SLOT_SIZE) {
+ error_setg(errp,
+ "RPMI A2P request queue size must be a multiple of %u",
+ RPMI_QUEUE_SLOT_SIZE);
+ return false;
+ }
+
+ if (s->p2a_req_size % RPMI_QUEUE_SLOT_SIZE) {
+ error_setg(errp,
+ "RPMI P2A request queue size must be a multiple of %u",
+ RPMI_QUEUE_SLOT_SIZE);
+ return false;
+ }
+
+ queue_bytes = 2 * (uint64_t)s->a2p_req_size +
+ 2 * (uint64_t)s->p2a_req_size;
+ if (queue_bytes > s->shmem_size) {
+ error_setg(errp,
+ "RPMI shared memory size 0x%" PRIx64
+ " is too small for queues 0x%" PRIx64,
+ s->shmem_size, queue_bytes);
+ return false;
+ }
+
+ return true;
+}
+
+static bool riscv_rpmi_add_service_group(RiscvRpmiState *s,
+ const RiscvRpmiServiceConfig *service,
+ Error **errp)
+{
+ switch (service->kind) {
+ default:
+ error_setg(errp, "unsupported RPMI service kind %d", service->kind);
+ return false;
+ }
+}
+
+static bool riscv_rpmi_init_services(RiscvRpmiState *s, Error **errp)
+{
+ uint32_t i;
+
+ for (i = 0; i < s->service_count; i++) {
+ if (!riscv_rpmi_add_service_group(s, &s->services[i], errp)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool riscv_rpmi_init_context(RiscvRpmiState *s, Error **errp)
+{
+ const char *platform_info = s->platform_info ?: RPMI_PLAT_INFO;
+
+ s->rpmi_shmem = rpmi_shmem_create("rpmi-shmem", s->shmem_base,
+ s->shmem_size,
+ &rpmi_shmem_qemu_ops, s);
+ if (!s->rpmi_shmem) {
+ error_setg(errp, "failed to create RPMI shared memory backend");
+ return false;
+ }
+
+ s->transport = rpmi_transport_shmem_create("rpmi-shmem-transport",
+ RPMI_QUEUE_SLOT_SIZE,
+ s->a2p_req_size,
+ s->p2a_req_size,
+ s->rpmi_shmem);
+ if (!s->transport) {
+ error_setg(errp, "failed to create RPMI shared memory transport");
+ return false;
+ }
+
+ s->context = rpmi_context_create("rpmi-virt", s->transport,
+ RPMI_SRVGRP_ID_MAX_COUNT,
+ RPMI_PRIVILEGE_M_MODE,
+ strlen(platform_info) + 1,
+ platform_info);
+ if (!s->context) {
+ error_setg(errp, "failed to create RPMI context");
+ return false;
+ }
+
+ return riscv_rpmi_init_services(s, errp);
+}
+
+static void riscv_rpmi_realize(DeviceState *dev, Error **errp)
+{
+ RiscvRpmiState *s = RISCV_RPMI(dev);
+ g_autofree char *name = NULL;
+
+ if (!riscv_rpmi_validate_config(s, errp)) {
+ return;
+ }
+
+ name = g_strdup_printf("rpmi-shmem@%" PRIx64, s->shmem_base);
+ if (!memory_region_init_ram(&s->shmem, OBJECT(dev), name, s->shmem_size,
+ errp)) {
+ return;
+ }
+
+ memory_region_add_subregion(get_system_memory(), s->shmem_base, &s->shmem);
+ s->has_shmem = true;
+
+ if (!riscv_rpmi_init_context(s, errp)) {
+ riscv_rpmi_cleanup(s);
+ }
+}
+
+static void riscv_rpmi_unrealize(DeviceState *dev)
+{
+ riscv_rpmi_cleanup(RISCV_RPMI(dev));
+}
+
+static const VMStateDescription riscv_rpmi_vmstate = {
+ .name = TYPE_RISCV_RPMI,
+ .unmigratable = 1,
+};
+
+static const Property riscv_rpmi_properties[] = {
+ DEFINE_PROP_UINT64("shmem-base", RiscvRpmiState, shmem_base, 0),
+ DEFINE_PROP_UINT64("shmem-size", RiscvRpmiState, shmem_size, 0),
+ DEFINE_PROP_UINT32("a2p-req-size", RiscvRpmiState, a2p_req_size, 0),
+ DEFINE_PROP_UINT32("p2a-req-size", RiscvRpmiState, p2a_req_size, 0),
+};
+
+static void riscv_rpmi_finalize(Object *obj)
+{
+ RiscvRpmiState *s = RISCV_RPMI(obj);
+
+ g_free(s->platform_info);
+ g_free(s->hart_ids);
+}
+
+static void riscv_rpmi_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = riscv_rpmi_realize;
+ dc->unrealize = riscv_rpmi_unrealize;
+ dc->vmsd = &riscv_rpmi_vmstate;
+ device_class_set_legacy_reset(dc, riscv_rpmi_reset);
+ device_class_set_props(dc, riscv_rpmi_properties);
+}
+
+static const TypeInfo riscv_rpmi_info = {
+ .name = TYPE_RISCV_RPMI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RiscvRpmiState),
+ .instance_init = riscv_rpmi_init,
+ .instance_finalize = riscv_rpmi_finalize,
+ .class_init = riscv_rpmi_class_init,
+};
+
+static void riscv_rpmi_register_types(void)
+{
+ type_register_static(&riscv_rpmi_info);
+}
+
+type_init(riscv_rpmi_register_types)
+
+DeviceState *riscv_rpmi_create(const RiscvRpmiConfig *cfg, Error **errp)
+{
+ DeviceState *dev;
+ RiscvRpmiState *s;
+
+ if (!cfg) {
+ error_setg(errp, "missing RPMI configuration");
+ return NULL;
+ }
+
+ dev = qdev_new(TYPE_RISCV_RPMI);
+ qdev_prop_set_uint64(dev, "shmem-base", cfg->shmem_base);
+ qdev_prop_set_uint64(dev, "shmem-size", cfg->shmem_size);
+ qdev_prop_set_uint32(dev, "a2p-req-size", cfg->a2p_req_size);
+ qdev_prop_set_uint32(dev, "p2a-req-size", cfg->p2a_req_size);
+
+ s = RISCV_RPMI(dev);
+ riscv_rpmi_configure_base(s, cfg);
+
+ if (!sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), errp)) {
+ return NULL;
+ }
+
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, cfg->doorbell_base);
+ return dev;
+}
diff --git a/hw/misc/riscv_rpmi_internal.h b/hw/misc/riscv_rpmi_internal.h
new file mode 100644
index 0000000000..447c0acb55
--- /dev/null
+++ b/hw/misc/riscv_rpmi_internal.h
@@ -0,0 +1,24 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI internal service helpers.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#ifndef HW_MISC_RISCV_RPMI_INTERNAL_H
+#define HW_MISC_RISCV_RPMI_INTERNAL_H
+
+#include "qapi/error.h"
+#include "hw/misc/riscv_rpmi.h"
+#include "librpmi.h"
+
+#define RPMI_PLAT_INFO "QEMU RISC-V RPMI"
+
+extern const struct rpmi_shmem_platform_ops rpmi_shmem_qemu_ops;
+bool riscv_rpmi_service_enabled(RiscvRpmiState *s,
+ RiscvRpmiServiceKind kind);
+
+#endif
diff --git a/include/hw/misc/riscv_rpmi.h b/include/hw/misc/riscv_rpmi.h
new file mode 100644
index 0000000000..cb2658e57e
--- /dev/null
+++ b/include/hw/misc/riscv_rpmi.h
@@ -0,0 +1,90 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI definitions shared by RPMI transport and FDT helpers.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#ifndef HW_MISC_RISCV_RPMI_H
+#define HW_MISC_RISCV_RPMI_H
+
+#include "exec/hwaddr.h"
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+#include "qemu/notify.h"
+
+#define RPMI_QUEUE_SLOT_SIZE 64
+#define RPMI_DBREG_SIZE 0x1000
+
+#define RPMI_ALL_NUM_QUEUES 4
+#define RPMI_A2P_NUM_QUEUES 2
+#define RPMI_ALL_NUM_REGS (RPMI_ALL_NUM_QUEUES + 1)
+#define RPMI_A2P_NUM_REGS (RPMI_A2P_NUM_QUEUES + 1)
+
+#define VIRT_RPMI_A2P_REQ_SIZE (16 * RPMI_QUEUE_SLOT_SIZE)
+#define VIRT_RPMI_P2A_REQ_SIZE 0
+
+
+#define TYPE_RISCV_RPMI "riscv-rpmi"
+OBJECT_DECLARE_SIMPLE_TYPE(RiscvRpmiState, RISCV_RPMI)
+
+struct rpmi_context;
+struct rpmi_service_group;
+struct rpmi_shmem;
+struct rpmi_transport;
+
+typedef enum RiscvRpmiServiceKind {
+ RISCV_RPMI_SERVICE_INVALID = 0,
+} RiscvRpmiServiceKind;
+
+typedef struct RiscvRpmiServiceConfig {
+ RiscvRpmiServiceKind kind;
+ const char *node_name;
+ const char *compatible;
+ uint32_t service_group;
+ bool has_mpxy_channel;
+ uint32_t mpxy_channel;
+} RiscvRpmiServiceConfig;
+
+typedef struct RiscvRpmiConfig {
+ hwaddr doorbell_base;
+ hwaddr shmem_base;
+ hwaddr shmem_size;
+ uint32_t a2p_req_size;
+ uint32_t p2a_req_size;
+ const char *platform_info;
+
+ const uint32_t *hart_ids;
+ uint32_t hart_count;
+ const RiscvRpmiServiceConfig *services;
+ uint32_t service_count;
+} RiscvRpmiConfig;
+
+struct RiscvRpmiState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ MemoryRegion shmem;
+ uint64_t shmem_base;
+ uint64_t shmem_size;
+ uint32_t a2p_req_size;
+ uint32_t p2a_req_size;
+ char *platform_info;
+
+ uint32_t *hart_ids;
+ uint32_t hart_count;
+ const RiscvRpmiServiceConfig *services;
+ uint32_t service_count;
+ uint32_t doorbell;
+ struct rpmi_shmem *rpmi_shmem;
+ struct rpmi_transport *transport;
+ struct rpmi_context *context;
+ bool has_shmem;
+};
+
+DeviceState *riscv_rpmi_create(const RiscvRpmiConfig *cfg, Error **errp);
+
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v2 03/10] hw/riscv: add RPMI FDT helpers
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 8:39 UTC (permalink / raw)
To: qemu-riscv; +Cc: qemu-devel, daniel.barboza, Subrahmanya Lingappa
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add helper routines for describing RPMI shared-memory mailbox transports
and service child nodes in the RISC-V virt device tree.
The helpers keep RPMI node construction in one place so the virt machine
can add service groups without open-coding compatible strings, channel
IDs, interrupts, and shared-memory references at each call site.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
hw/riscv/meson.build | 1 +
hw/riscv/rpmi-fdt.c | 103 ++++++++++++++++++++++++++++++++++++
include/hw/riscv/rpmi-fdt.h | 30 +++++++++++
3 files changed, 134 insertions(+)
create mode 100644 hw/riscv/rpmi-fdt.c
create mode 100644 include/hw/riscv/rpmi-fdt.h
diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build
index b70a054579..529ec273fd 100644
--- a/hw/riscv/meson.build
+++ b/hw/riscv/meson.build
@@ -3,6 +3,7 @@ riscv_ss.add(files('boot.c'))
riscv_ss.add(files('fdt-common.c'))
riscv_ss.add(when: 'CONFIG_RISCV_NUMA', if_true: files('numa.c'))
riscv_ss.add(files('riscv_hart.c'))
+riscv_ss.add(files('rpmi-fdt.c'))
riscv_ss.add(when: 'CONFIG_OPENTITAN', if_true: files('opentitan.c'))
riscv_ss.add(when: 'CONFIG_RISCV_VIRT', if_true: files('virt.c'))
riscv_ss.add(when: 'CONFIG_SHAKTI_C', if_true: files('shakti_c.c'))
diff --git a/hw/riscv/rpmi-fdt.c b/hw/riscv/rpmi-fdt.c
new file mode 100644
index 0000000000..b2b3daea1c
--- /dev/null
+++ b/hw/riscv/rpmi-fdt.c
@@ -0,0 +1,103 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI device-tree helpers
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bswap.h"
+#include "hw/riscv/rpmi-fdt.h"
+#include "hw/misc/riscv_rpmi.h"
+#include "system/device_tree.h"
+
+void riscv_rpmi_fdt_add_mbox(void *fdt,
+ const RiscvRpmiFdtMboxConfig *cfg,
+ uint32_t *phandle,
+ uint32_t *mbox_handle)
+{
+ g_autofree char *name = NULL;
+ hwaddr a2p_req_base, p2a_ack_base, p2a_req_base, a2p_ack_base;
+ static const char * const regnames_all[RPMI_ALL_NUM_REGS] = {
+ "a2p-req", "p2a-ack", "p2a-req", "a2p-ack", "a2p-doorbell"
+ };
+ static const char * const regnames_a2p[RPMI_A2P_NUM_REGS] = {
+ "a2p-req", "p2a-ack", "a2p-doorbell"
+ };
+
+ a2p_req_base = cfg->shmem_base;
+ p2a_ack_base = a2p_req_base + cfg->a2p_req_size;
+ p2a_req_base = p2a_ack_base + cfg->a2p_req_size;
+ a2p_ack_base = p2a_req_base + cfg->p2a_req_size;
+
+ *mbox_handle = (*phandle)++;
+ name = g_strdup_printf("/soc/mailbox@%" HWADDR_PRIx, cfg->shmem_base);
+ qemu_fdt_add_subnode(fdt, name);
+ qemu_fdt_setprop_string(fdt, name, "compatible", "riscv,rpmi-shmem-mbox");
+ qemu_fdt_setprop_cell(fdt, name, "riscv,slot-size", RPMI_QUEUE_SLOT_SIZE);
+ qemu_fdt_setprop_cell(fdt, name, "#mbox-cells", 1);
+
+ if (cfg->p2a_req_size) {
+ qemu_fdt_setprop_string_array(fdt, name, "reg-names",
+ (char **)®names_all,
+ ARRAY_SIZE(regnames_all));
+ qemu_fdt_setprop_cells(fdt, name, "reg",
+ (uint32_t)(a2p_req_base >> 32), (uint32_t)a2p_req_base,
+ 0x0, cfg->a2p_req_size,
+ (uint32_t)(p2a_ack_base >> 32), (uint32_t)p2a_ack_base,
+ 0x0, cfg->a2p_req_size,
+ (uint32_t)(p2a_req_base >> 32), (uint32_t)p2a_req_base,
+ 0x0, cfg->p2a_req_size,
+ (uint32_t)(a2p_ack_base >> 32), (uint32_t)a2p_ack_base,
+ 0x0, cfg->p2a_req_size,
+ (uint32_t)(cfg->doorbell_base >> 32), (uint32_t)cfg->doorbell_base,
+ 0x0, cfg->doorbell_size);
+ } else {
+ qemu_fdt_setprop_string_array(fdt, name, "reg-names",
+ (char **)®names_a2p,
+ ARRAY_SIZE(regnames_a2p));
+ qemu_fdt_setprop_cells(fdt, name, "reg",
+ (uint32_t)(a2p_req_base >> 32), (uint32_t)a2p_req_base,
+ 0x0, cfg->a2p_req_size,
+ (uint32_t)(p2a_ack_base >> 32), (uint32_t)p2a_ack_base,
+ 0x0, cfg->a2p_req_size,
+ (uint32_t)(cfg->doorbell_base >> 32), (uint32_t)cfg->doorbell_base,
+ 0x0, cfg->doorbell_size);
+ }
+
+ qemu_fdt_setprop_cells(fdt, name, "phandle", *mbox_handle);
+}
+
+void riscv_rpmi_fdt_add_service(void *fdt, hwaddr shmem_base,
+ const char *node_name,
+ const char *compatible,
+ uint32_t mbox_handle,
+ uint32_t service_group,
+ bool has_mpxy_channel,
+ uint32_t mpxy_channel)
+{
+ g_autofree char *name = g_strdup_printf("/soc/mailbox@%" HWADDR_PRIx "/%s",
+ shmem_base, node_name);
+
+ qemu_fdt_add_subnode(fdt, name);
+ qemu_fdt_setprop_string(fdt, name, "compatible", compatible);
+ qemu_fdt_setprop_cells(fdt, name, "mboxes", mbox_handle, service_group);
+ if (has_mpxy_channel) {
+ qemu_fdt_setprop_cell(fdt, name, "riscv,sbi-mpxy-channel-id",
+ mpxy_channel);
+ }
+}
+
+void riscv_rpmi_fdt_add_service_node(void *fdt, hwaddr shmem_base,
+ const RiscvRpmiServiceConfig *service,
+ uint32_t mbox_handle)
+{
+ riscv_rpmi_fdt_add_service(fdt, shmem_base, service->node_name,
+ service->compatible, mbox_handle,
+ service->service_group,
+ service->has_mpxy_channel,
+ service->mpxy_channel);
+}
diff --git a/include/hw/riscv/rpmi-fdt.h b/include/hw/riscv/rpmi-fdt.h
new file mode 100644
index 0000000000..f053ae5d57
--- /dev/null
+++ b/include/hw/riscv/rpmi-fdt.h
@@ -0,0 +1,30 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI device-tree helpers
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#ifndef HW_RISCV_RPMI_FDT_H
+#define HW_RISCV_RPMI_FDT_H
+
+#include "exec/hwaddr.h"
+#include "hw/misc/riscv_rpmi.h"
+
+typedef struct RiscvRpmiFdtMboxConfig {
+ hwaddr shmem_base;
+ hwaddr doorbell_base;
+ uint32_t a2p_req_size;
+ uint32_t p2a_req_size;
+ uint32_t doorbell_size;
+} RiscvRpmiFdtMboxConfig;
+
+void riscv_rpmi_fdt_add_mbox(void *fdt,
+ const RiscvRpmiFdtMboxConfig *cfg,
+ uint32_t *phandle,
+ uint32_t *mbox_handle);
+
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v2 04/10] hw/riscv/virt: add opt-in RPMI base support
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 8:39 UTC (permalink / raw)
To: qemu-riscv; +Cc: qemu-devel, daniel.barboza, Subrahmanya Lingappa
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add the virt machine rpmi=on/off option and wire the initial RPMI Base
transport into machine realization.
The option remains disabled by default and requires TCG plus librpmi.
Invalid accelerator/build combinations fail during machine
initialization. When enabled, virt allocates the RPMI shared-memory
transport, creates the RPMI device, configures Base service metadata,
and emits the corresponding device-tree nodes for firmware discovery.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
hw/riscv/Kconfig | 1 +
hw/riscv/virt.c | 96 ++++++++++++++++++++++++++++++++++++
include/hw/misc/riscv_rpmi.h | 10 ++++
include/hw/riscv/virt.h | 3 ++
4 files changed, 110 insertions(+)
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index 54e41a6afc..4e25be113a 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -49,6 +49,7 @@ config RISCV_VIRT
imply VIRTIO_VGA
imply TEST_DEVICES
imply TPM_TIS_SYSBUS
+ imply RISCV_RPMI
select DEVICE_TREE
select RISCV_NUMA
select GOLDFISH_RTC
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index 33775a61fd..e52ccfae1f 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -45,6 +45,8 @@
#include "hw/intc/riscv_aplic.h"
#include "hw/intc/sifive_plic.h"
#include "hw/misc/sifive_test.h"
+#include "hw/misc/riscv_rpmi.h"
+#include "hw/riscv/rpmi-fdt.h"
#include "hw/core/platform-bus.h"
#include "chardev/char.h"
#include "system/device_tree.h"
@@ -97,6 +99,8 @@ static const MemMapEntry virt_memmap[] = {
[VIRT_UART0] = { 0x10000000, 0x100 },
[VIRT_VIRTIO] = { 0x10001000, 0x1000 },
[VIRT_FW_CFG] = { 0x10100000, 0x18 },
+ [VIRT_RPMI_SHMEM] = { 0x10200000, 0x20000 },
+ [VIRT_RPMI_DOORBELL] = { 0x10230000, 0x1000 },
[VIRT_FLASH] = { 0x20000000, 0x4000000 },
[VIRT_IMSIC_M] = { 0x24000000, VIRT_IMSIC_MAX_SIZE },
[VIRT_IMSIC_S] = { 0x28000000, VIRT_IMSIC_MAX_SIZE },
@@ -1002,6 +1006,40 @@ static void create_fdt_iommu(RISCVVirtState *s, uint16_t bdf)
s->pci_iommu_bdf = bdf;
}
+
+static RiscvRpmiConfig virt_rpmi_config(RISCVVirtState *s,
+ const uint32_t *hart_ids,
+ uint32_t hart_count)
+{
+ return (RiscvRpmiConfig) {
+ .doorbell_base = s->memmap[VIRT_RPMI_DOORBELL].base,
+ .shmem_base = s->memmap[VIRT_RPMI_SHMEM].base,
+ .shmem_size = s->memmap[VIRT_RPMI_SHMEM].size,
+ .a2p_req_size = VIRT_RPMI_A2P_REQ_SIZE,
+ .p2a_req_size = VIRT_RPMI_P2A_REQ_SIZE,
+ .platform_info = "QEMU RISC-V virt RPMI",
+ .hart_ids = hart_ids,
+ .hart_count = hart_count,
+ };
+}
+
+static void create_fdt_rpmi(RISCVVirtState *s, uint32_t *phandle,
+ uint32_t msi_phandle)
+{
+ RiscvRpmiConfig rpmi_cfg = virt_rpmi_config(s, NULL, 0);
+ uint32_t rpmi_mbox_handle;
+ RiscvRpmiFdtMboxConfig cfg = {
+ .shmem_base = rpmi_cfg.shmem_base,
+ .doorbell_base = rpmi_cfg.doorbell_base,
+ .a2p_req_size = rpmi_cfg.a2p_req_size,
+ .p2a_req_size = rpmi_cfg.p2a_req_size,
+ .doorbell_size = s->memmap[VIRT_RPMI_DOORBELL].size,
+ };
+
+ riscv_rpmi_fdt_add_mbox(MACHINE(s)->fdt, &cfg, phandle,
+ &rpmi_mbox_handle);
+}
+
static void finalize_fdt(RISCVVirtState *s)
{
uint32_t phandle = 1, irq_mmio_phandle = 1, msi_pcie_phandle = 1;
@@ -1021,6 +1059,10 @@ static void finalize_fdt(RISCVVirtState *s)
create_fdt_pcie(s, irq_pcie_phandle, msi_pcie_phandle,
iommu_sys_phandle);
+ if (s->have_rpmi) {
+ create_fdt_rpmi(s, &phandle, msi_pcie_phandle);
+ }
+
create_fdt_reset(s, &phandle);
create_fdt_uart(s, irq_mmio_phandle);
@@ -1410,6 +1452,18 @@ static void virt_machine_init(MachineState *machine)
exit(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 = virtio_irqchip = pcie_irqchip = NULL;
for (i = 0; i < socket_count; i++) {
@@ -1552,6 +1606,25 @@ static void virt_machine_init(MachineState *machine)
/* SiFive Test MMIO device */
sifive_test_create(s->memmap[VIRT_TEST].base);
+ 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 = virt_rpmi_config(s, rpmi_hart_ids, machine->smp.cpus);
+ if (!riscv_rpmi_create(&rpmi_cfg, &local_err)) {
+ error_report_err(local_err);
+ exit(1);
+ }
+ }
+
/* VirtIO MMIO devices */
for (i = 0; i < VIRTIO_COUNT; i++) {
sysbus_create_simple("virtio-mmio",
@@ -1636,6 +1709,7 @@ static void virt_machine_instance_init(Object *obj)
s->oem_id = g_strndup(ACPI_BUILD_APPNAME6, 6);
s->oem_table_id = g_strndup(ACPI_BUILD_APPNAME8, 8);
+ s->have_rpmi = false;
s->acpi = ON_OFF_AUTO_AUTO;
s->iommu_sys = ON_OFF_AUTO_AUTO;
}
@@ -1710,6 +1784,21 @@ static void virt_set_aclint(Object *obj, bool value, Error **errp)
s->have_aclint = value;
}
+
+static bool virt_get_rpmi(Object *obj, Error **errp)
+{
+ RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
+
+ return s->have_rpmi;
+}
+
+static void virt_set_rpmi(Object *obj, bool value, Error **errp)
+{
+ RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
+
+ s->have_rpmi = value;
+}
+
bool virt_is_iommu_sys_enabled(RISCVVirtState *s)
{
return s->iommu_sys == ON_OFF_AUTO_ON;
@@ -1831,6 +1920,13 @@ static void virt_machine_class_init(ObjectClass *oc, const void *data)
"enable/disable emulating "
"ACLINT devices");
+
+ object_class_property_add_bool(oc, "rpmi", virt_get_rpmi,
+ virt_set_rpmi);
+ object_class_property_set_description(oc, "rpmi",
+ "Set on/off to enable/disable "
+ "RISC-V RPMI devices");
+
object_class_property_add_str(oc, "aia", virt_get_aia,
virt_set_aia);
object_class_property_set_description(oc, "aia",
diff --git a/include/hw/misc/riscv_rpmi.h b/include/hw/misc/riscv_rpmi.h
index cb2658e57e..b5d8a32f9b 100644
--- a/include/hw/misc/riscv_rpmi.h
+++ b/include/hw/misc/riscv_rpmi.h
@@ -85,6 +85,16 @@ struct RiscvRpmiState {
bool has_shmem;
};
+#ifdef CONFIG_LIBRPMI
DeviceState *riscv_rpmi_create(const RiscvRpmiConfig *cfg, Error **errp);
+#else
+static inline DeviceState *riscv_rpmi_create(const RiscvRpmiConfig *cfg,
+ Error **errp)
+{
+ (void)cfg;
+ (void)errp;
+ return NULL;
+}
+#endif
#endif
diff --git a/include/hw/riscv/virt.h b/include/hw/riscv/virt.h
index 18a2a323a3..f7c48613bf 100644
--- a/include/hw/riscv/virt.h
+++ b/include/hw/riscv/virt.h
@@ -55,6 +55,7 @@ struct RISCVVirtState {
int fdt_size;
bool have_aclint;
+ bool have_rpmi;
RISCVVirtAIAType aia_type;
int aia_guests;
char *oem_id;
@@ -79,6 +80,8 @@ enum {
VIRT_UART0,
VIRT_VIRTIO,
VIRT_FW_CFG,
+ VIRT_RPMI_SHMEM,
+ VIRT_RPMI_DOORBELL,
VIRT_IMSIC_M,
VIRT_IMSIC_S,
VIRT_FLASH,
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v2 05/10] riscv: add RPMI system reset service
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 8:39 UTC (permalink / raw)
To: qemu-riscv; +Cc: qemu-devel, daniel.barboza, Subrahmanya Lingappa
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add a QEMU implementation of the RPMI System Reset service group.
The service advertises shutdown and cold reboot reset types, validates
guest requests, and dispatches accepted requests through the machine
reset and shutdown callbacks. This lets firmware use RPMI reset commands
while keeping the board-specific reset policy in the RISC-V virt machine.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
hw/misc/meson.build | 1 +
hw/misc/riscv_rpmi.c | 38 ++++++-
hw/misc/riscv_rpmi_internal.h | 9 ++
hw/misc/riscv_rpmi_sysreset.c | 206 ++++++++++++++++++++++++++++++++++
hw/riscv/virt.c | 40 +++++++
include/hw/misc/riscv_rpmi.h | 13 ++-
include/hw/riscv/rpmi-fdt.h | 11 ++
7 files changed, 313 insertions(+), 5 deletions(-)
create mode 100644 hw/misc/riscv_rpmi_sysreset.c
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index e7c2ebe28f..bca7819ee0 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -170,4 +170,5 @@ system_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa_ec.c'))
system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c'))
system_ss.add(when: 'CONFIG_RISCV_RPMI', if_true: [files(
'riscv_rpmi.c',
+ 'riscv_rpmi_sysreset.c',
), librpmi])
diff --git a/hw/misc/riscv_rpmi.c b/hw/misc/riscv_rpmi.c
index c4b115b659..7a3f570ee7 100644
--- a/hw/misc/riscv_rpmi.c
+++ b/hw/misc/riscv_rpmi.c
@@ -175,6 +175,8 @@ static void riscv_rpmi_configure_base(RiscvRpmiState *s,
const RiscvRpmiConfig *cfg)
{
s->platform_info = g_strdup(cfg->platform_info);
+ s->machine_ops = cfg->machine_ops;
+ s->machine_opaque = cfg->machine_opaque;
s->services = cfg->services;
s->service_count = cfg->service_count;
@@ -211,7 +213,9 @@ static void riscv_rpmi_reset(DeviceState *dev)
static void riscv_rpmi_cleanup(RiscvRpmiState *s)
{
-
+ for (uint32_t i = ARRAY_SIZE(riscv_rpmi_service_ops); i > 0; i--) {
+ riscv_rpmi_service_ops[i - 1].remove(s);
+ }
if (s->context) {
rpmi_context_destroy(s->context);
@@ -247,6 +251,30 @@ bool riscv_rpmi_service_enabled(RiscvRpmiState *s, RiscvRpmiServiceKind kind)
return false;
}
+bool riscv_rpmi_context_add_group(RiscvRpmiState *s,
+ struct rpmi_service_group *group,
+ const char *name,
+ Error **errp)
+{
+ enum rpmi_error rc;
+
+ rc = rpmi_context_add_group(s->context, group);
+ if (rc != RPMI_SUCCESS) {
+ error_setg(errp, "failed to add RPMI %s service group: %d", name, rc);
+ return false;
+ }
+
+ return true;
+}
+
+void riscv_rpmi_context_remove_group(RiscvRpmiState *s,
+ struct rpmi_service_group *group)
+{
+ if (s->context && group) {
+ rpmi_context_remove_group(s->context, group);
+ }
+}
+
static bool riscv_rpmi_validate_config(RiscvRpmiState *s, Error **errp)
{
uint64_t queue_bytes;
@@ -318,11 +346,15 @@ static bool riscv_rpmi_add_service_group(RiscvRpmiState *s,
const RiscvRpmiServiceConfig *service,
Error **errp)
{
- switch (service->kind) {
- default:
+ const RiscvRpmiServiceOps *ops;
+
+ ops = riscv_rpmi_service_ops_by_kind(service->kind);
+ if (!ops) {
error_setg(errp, "unsupported RPMI service kind %d", service->kind);
return false;
}
+
+ return ops->add(s, errp);
}
static bool riscv_rpmi_init_services(RiscvRpmiState *s, Error **errp)
diff --git a/hw/misc/riscv_rpmi_internal.h b/hw/misc/riscv_rpmi_internal.h
index 447c0acb55..fe09804e69 100644
--- a/hw/misc/riscv_rpmi_internal.h
+++ b/hw/misc/riscv_rpmi_internal.h
@@ -20,5 +20,14 @@
extern const struct rpmi_shmem_platform_ops rpmi_shmem_qemu_ops;
bool riscv_rpmi_service_enabled(RiscvRpmiState *s,
RiscvRpmiServiceKind kind);
+bool riscv_rpmi_context_add_group(RiscvRpmiState *s,
+ struct rpmi_service_group *group,
+ const char *name,
+ Error **errp);
+void riscv_rpmi_context_remove_group(RiscvRpmiState *s,
+ struct rpmi_service_group *group);
+
+bool riscv_rpmi_sysreset_add(RiscvRpmiState *s, Error **errp);
+void riscv_rpmi_sysreset_remove(RiscvRpmiState *s);
#endif
diff --git a/hw/misc/riscv_rpmi_sysreset.c b/hw/misc/riscv_rpmi_sysreset.c
new file mode 100644
index 0000000000..da6ef4c566
--- /dev/null
+++ b/hw/misc/riscv_rpmi_sysreset.c
@@ -0,0 +1,206 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI System Reset service.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#include "qemu/osdep.h"
+#include "riscv_rpmi_internal.h"
+#include "qemu/log.h"
+#include "librpmi_env.h"
+
+typedef struct RiscvRpmiSysresetType {
+ uint32_t type;
+ void (*action)(RiscvRpmiState *s);
+} RiscvRpmiSysresetType;
+
+typedef struct RiscvRpmiSysresetGroup {
+ struct rpmi_service_group group;
+ struct rpmi_service services[RPMI_SYSRST_SRV_ID_MAX];
+ RiscvRpmiState *rpmi;
+} RiscvRpmiSysresetGroup;
+
+static void riscv_rpmi_sysreset_reboot(RiscvRpmiState *s)
+{
+ const RiscvRpmiMachineOps *ops = s->machine_ops;
+
+ if (ops && ops->system_reset) {
+ ops->system_reset(s->machine_opaque);
+ }
+}
+
+static void riscv_rpmi_sysreset_shutdown(RiscvRpmiState *s)
+{
+ const RiscvRpmiMachineOps *ops = s->machine_ops;
+
+ if (ops && ops->system_shutdown) {
+ ops->system_shutdown(s->machine_opaque);
+ }
+}
+
+static const RiscvRpmiSysresetType riscv_rpmi_sysreset_types[] = {
+ {
+ .type = RPMI_SYSRST_TYPE_SHUTDOWN,
+ .action = riscv_rpmi_sysreset_shutdown,
+ }, {
+ .type = RPMI_SYSRST_TYPE_COLD_REBOOT,
+ .action = riscv_rpmi_sysreset_reboot,
+ },
+};
+
+static const RiscvRpmiSysresetType *riscv_rpmi_sysreset_type_by_id(
+ uint32_t reset_type)
+{
+ for (uint32_t index = 0; index < ARRAY_SIZE(riscv_rpmi_sysreset_types);
+ index++) {
+ if (riscv_rpmi_sysreset_types[index].type == reset_type) {
+ return &riscv_rpmi_sysreset_types[index];
+ }
+ }
+
+ return NULL;
+}
+
+static void riscv_rpmi_do_system_reset(RiscvRpmiState *s,
+ rpmi_uint32_t reset_type)
+{
+ const RiscvRpmiSysresetType *type;
+
+ type = riscv_rpmi_sysreset_type_by_id(reset_type);
+ if (type) {
+ type->action(s);
+ return;
+ }
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: unsupported reset type %u\n",
+ __func__, reset_type);
+}
+
+static enum rpmi_error riscv_rpmi_sysreset_get_attributes(
+ struct rpmi_service_group *group, struct rpmi_service *service,
+ struct rpmi_transport *trans, rpmi_uint16_t request_data_len,
+ const rpmi_uint8_t *request_data, rpmi_uint16_t *response_data_len,
+ rpmi_uint8_t *response_data)
+{
+ uint32_t reset_type = ldl_le_p(request_data);
+ uint32_t *resp = (uint32_t *)response_data;
+
+ *response_data_len = 2 * sizeof(*resp);
+ stl_le_p(&resp[0], RPMI_SUCCESS);
+ stl_le_p(&resp[1], riscv_rpmi_sysreset_type_by_id(reset_type) ?
+ RPMI_SYSRST_ATTRS_FLAGS_RESETTYPE : 0);
+
+ return RPMI_SUCCESS;
+}
+
+static enum rpmi_error riscv_rpmi_sysreset_do_reset(
+ struct rpmi_service_group *group, struct rpmi_service *service,
+ struct rpmi_transport *trans, rpmi_uint16_t request_data_len,
+ const rpmi_uint8_t *request_data, rpmi_uint16_t *response_data_len,
+ rpmi_uint8_t *response_data)
+{
+ RiscvRpmiSysresetGroup *sysreset = group->priv;
+ uint32_t reset_type = ldl_le_p(request_data);
+ uint32_t *resp = (uint32_t *)response_data;
+
+ *response_data_len = sizeof(*resp);
+
+ if (!riscv_rpmi_sysreset_type_by_id(reset_type)) {
+ stl_le_p(resp, (uint32_t)RPMI_ERR_INVALID_PARAM);
+ return RPMI_SUCCESS;
+ }
+
+ stl_le_p(resp, RPMI_SUCCESS);
+ riscv_rpmi_do_system_reset(sysreset->rpmi, reset_type);
+ return RPMI_SUCCESS;
+}
+
+static const struct rpmi_service riscv_rpmi_sysreset_services[] = {
+ [RPMI_SYSRST_SRV_ENABLE_NOTIFICATION] = {
+ .service_id = RPMI_SYSRST_SRV_ENABLE_NOTIFICATION,
+ .min_a2p_request_datalen = 8,
+ },
+ [RPMI_SYSRST_SRV_GET_ATTRIBUTES] = {
+ .service_id = RPMI_SYSRST_SRV_GET_ATTRIBUTES,
+ .min_a2p_request_datalen = 4,
+ .process_a2p_request = riscv_rpmi_sysreset_get_attributes,
+ },
+ [RPMI_SYSRST_SRV_SYSTEM_RESET] = {
+ .service_id = RPMI_SYSRST_SRV_SYSTEM_RESET,
+ .min_a2p_request_datalen = 4,
+ .process_a2p_request = riscv_rpmi_sysreset_do_reset,
+ },
+};
+
+static struct rpmi_service_group *riscv_rpmi_sysreset_create(RiscvRpmiState *s)
+{
+ RiscvRpmiSysresetGroup *sysreset;
+ struct rpmi_service_group *group;
+
+ sysreset = g_new0(RiscvRpmiSysresetGroup, 1);
+ sysreset->rpmi = s;
+ memcpy(sysreset->services, riscv_rpmi_sysreset_services,
+ sizeof(riscv_rpmi_sysreset_services));
+
+ group = &sysreset->group;
+ group->name = "sysreset";
+ group->servicegroup_id = RPMI_SRVGRP_SYSTEM_RESET;
+ group->max_service_id = RPMI_SYSRST_SRV_ID_MAX;
+ group->servicegroup_version =
+ RPMI_BASE_VERSION(RPMI_SPEC_VERSION_MAJOR, RPMI_SPEC_VERSION_MINOR);
+ group->privilege_level_bitmap = RPMI_PRIVILEGE_M_MODE_MASK;
+ group->services = sysreset->services;
+ group->lock = rpmi_env_alloc_lock();
+ group->priv = sysreset;
+
+ return group;
+}
+
+static void riscv_rpmi_sysreset_destroy(struct rpmi_service_group *group)
+{
+ if (!group) {
+ return;
+ }
+
+ rpmi_env_free_lock(group->lock);
+ g_free(group->priv);
+}
+
+bool riscv_rpmi_sysreset_add(RiscvRpmiState *s, Error **errp)
+{
+ struct rpmi_service_group *group;
+
+ if (s->sysreset_group) {
+ error_setg(errp, "duplicate RPMI sysreset service descriptor");
+ return false;
+ }
+
+ group = riscv_rpmi_sysreset_create(s);
+ if (!group) {
+ error_setg(errp, "failed to create RPMI sysreset service group");
+ return false;
+ }
+
+ if (!riscv_rpmi_context_add_group(s, group, "sysreset", errp)) {
+ riscv_rpmi_sysreset_destroy(group);
+ return false;
+ }
+
+ s->sysreset_group = group;
+ return true;
+}
+
+void riscv_rpmi_sysreset_remove(RiscvRpmiState *s)
+{
+ if (!s->sysreset_group) {
+ return;
+ }
+
+ riscv_rpmi_context_remove_group(s, s->sysreset_group);
+ riscv_rpmi_sysreset_destroy(s->sysreset_group);
+ s->sysreset_group = NULL;
+}
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index e52ccfae1f..273a9aa04f 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -1007,6 +1007,35 @@ static void create_fdt_iommu(RISCVVirtState *s, uint16_t bdf)
}
+static const RiscvRpmiServiceConfig virt_rpmi_services[] = {
+ {
+ .kind = RISCV_RPMI_SERVICE_SYSRESET,
+ .node_name = "sysreset",
+ .compatible = "riscv,rpmi-system-reset",
+ .service_group = RISCV_RPMI_SRVGRP_SYSTEM_RESET,
+ },
+};
+
+static uint32_t virt_rpmi_service_count(RISCVVirtState *s)
+{
+ return ARRAY_SIZE(virt_rpmi_services);
+}
+
+static void virt_rpmi_system_reset(void *opaque)
+{
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+}
+
+static void virt_rpmi_system_shutdown(void *opaque)
+{
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+}
+
+static const RiscvRpmiMachineOps virt_rpmi_machine_ops = {
+ .system_reset = virt_rpmi_system_reset,
+ .system_shutdown = virt_rpmi_system_shutdown,
+};
+
static RiscvRpmiConfig virt_rpmi_config(RISCVVirtState *s,
const uint32_t *hart_ids,
uint32_t hart_count)
@@ -1018,8 +1047,12 @@ static RiscvRpmiConfig virt_rpmi_config(RISCVVirtState *s,
.a2p_req_size = VIRT_RPMI_A2P_REQ_SIZE,
.p2a_req_size = VIRT_RPMI_P2A_REQ_SIZE,
.platform_info = "QEMU RISC-V virt RPMI",
+ .machine_ops = &virt_rpmi_machine_ops,
+ .machine_opaque = s,
.hart_ids = hart_ids,
.hart_count = hart_count,
+ .services = virt_rpmi_services,
+ .service_count = virt_rpmi_service_count(s),
};
}
@@ -1028,6 +1061,7 @@ static void create_fdt_rpmi(RISCVVirtState *s, uint32_t *phandle,
{
RiscvRpmiConfig rpmi_cfg = virt_rpmi_config(s, NULL, 0);
uint32_t rpmi_mbox_handle;
+ uint32_t i;
RiscvRpmiFdtMboxConfig cfg = {
.shmem_base = rpmi_cfg.shmem_base,
.doorbell_base = rpmi_cfg.doorbell_base,
@@ -1038,6 +1072,12 @@ static void create_fdt_rpmi(RISCVVirtState *s, uint32_t *phandle,
riscv_rpmi_fdt_add_mbox(MACHINE(s)->fdt, &cfg, phandle,
&rpmi_mbox_handle);
+
+ for (i = 0; i < rpmi_cfg.service_count; i++) {
+ riscv_rpmi_fdt_add_service_node(MACHINE(s)->fdt, rpmi_cfg.shmem_base,
+ &rpmi_cfg.services[i],
+ rpmi_mbox_handle);
+ }
}
static void finalize_fdt(RISCVVirtState *s)
diff --git a/include/hw/misc/riscv_rpmi.h b/include/hw/misc/riscv_rpmi.h
index b5d8a32f9b..d11db87731 100644
--- a/include/hw/misc/riscv_rpmi.h
+++ b/include/hw/misc/riscv_rpmi.h
@@ -27,6 +27,7 @@
#define VIRT_RPMI_A2P_REQ_SIZE (16 * RPMI_QUEUE_SLOT_SIZE)
#define VIRT_RPMI_P2A_REQ_SIZE 0
+#define RISCV_RPMI_SRVGRP_SYSTEM_RESET 3
#define TYPE_RISCV_RPMI "riscv-rpmi"
OBJECT_DECLARE_SIMPLE_TYPE(RiscvRpmiState, RISCV_RPMI)
@@ -38,8 +39,13 @@ struct rpmi_transport;
typedef enum RiscvRpmiServiceKind {
RISCV_RPMI_SERVICE_INVALID = 0,
+ RISCV_RPMI_SERVICE_SYSRESET,
} RiscvRpmiServiceKind;
+typedef struct RiscvRpmiMachineOps {
+ void (*system_reset)(void *opaque);
+ void (*system_shutdown)(void *opaque);
+} RiscvRpmiMachineOps;
typedef struct RiscvRpmiServiceConfig {
RiscvRpmiServiceKind kind;
const char *node_name;
@@ -56,7 +62,8 @@ typedef struct RiscvRpmiConfig {
uint32_t a2p_req_size;
uint32_t p2a_req_size;
const char *platform_info;
-
+ const RiscvRpmiMachineOps *machine_ops;
+ void *machine_opaque;
const uint32_t *hart_ids;
uint32_t hart_count;
const RiscvRpmiServiceConfig *services;
@@ -73,7 +80,9 @@ struct RiscvRpmiState {
uint32_t a2p_req_size;
uint32_t p2a_req_size;
char *platform_info;
-
+ const RiscvRpmiMachineOps *machine_ops;
+ void *machine_opaque;
+ struct rpmi_service_group *sysreset_group;
uint32_t *hart_ids;
uint32_t hart_count;
const RiscvRpmiServiceConfig *services;
diff --git a/include/hw/riscv/rpmi-fdt.h b/include/hw/riscv/rpmi-fdt.h
index f053ae5d57..af0e8a65ab 100644
--- a/include/hw/riscv/rpmi-fdt.h
+++ b/include/hw/riscv/rpmi-fdt.h
@@ -26,5 +26,16 @@ void riscv_rpmi_fdt_add_mbox(void *fdt,
const RiscvRpmiFdtMboxConfig *cfg,
uint32_t *phandle,
uint32_t *mbox_handle);
+void riscv_rpmi_fdt_add_service(void *fdt, hwaddr shmem_base,
+ const char *node_name,
+ const char *compatible,
+ uint32_t mbox_handle,
+ uint32_t service_group,
+ bool has_mpxy_channel,
+ uint32_t mpxy_channel);
+
+void riscv_rpmi_fdt_add_service_node(void *fdt, hwaddr shmem_base,
+ const RiscvRpmiServiceConfig *service,
+ uint32_t mbox_handle);
#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v2 06/10] riscv: add RPMI HSM service
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 8:39 UTC (permalink / raw)
To: qemu-riscv; +Cc: qemu-devel, daniel.barboza, Subrahmanya Lingappa
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add the RPMI Hart State Management service group for RISC-V virt.
The service exposes the configured virt hart IDs to librpmi and keeps
per-hart RPMI hardware state in the RPMI device. Start, stop, and
suspend requests update QEMU CPU halted state and resume/kick vCPUs as
needed so firmware can exercise RPMI HSM operations against the emulated
topology.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
hw/misc/meson.build | 1 +
hw/misc/riscv_rpmi.c | 46 ++++++
hw/misc/riscv_rpmi_hsm.c | 280 ++++++++++++++++++++++++++++++++++
hw/misc/riscv_rpmi_internal.h | 4 +
hw/riscv/virt.c | 6 +
include/hw/misc/riscv_rpmi.h | 6 +
6 files changed, 343 insertions(+)
create mode 100644 hw/misc/riscv_rpmi_hsm.c
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index bca7819ee0..fc5b11ed57 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -171,4 +171,5 @@ system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c'))
system_ss.add(when: 'CONFIG_RISCV_RPMI', if_true: [files(
'riscv_rpmi.c',
'riscv_rpmi_sysreset.c',
+ 'riscv_rpmi_hsm.c',
), librpmi])
diff --git a/hw/misc/riscv_rpmi.c b/hw/misc/riscv_rpmi.c
index 7a3f570ee7..820b1d5d63 100644
--- a/hw/misc/riscv_rpmi.c
+++ b/hw/misc/riscv_rpmi.c
@@ -171,6 +171,49 @@ static bool riscv_rpmi_transport_indices_valid(RiscvRpmiState *s)
s->a2p_req_size);
}
+typedef struct RiscvRpmiServiceOps {
+ RiscvRpmiServiceKind kind;
+ void (*configure)(RiscvRpmiState *s, const RiscvRpmiConfig *cfg);
+ bool (*add)(RiscvRpmiState *s, Error **errp);
+ void (*remove)(RiscvRpmiState *s);
+} RiscvRpmiServiceOps;
+
+static const RiscvRpmiServiceOps riscv_rpmi_service_ops[] = {
+ {
+ .kind = RISCV_RPMI_SERVICE_SYSRESET,
+ .add = riscv_rpmi_sysreset_add,
+ .remove = riscv_rpmi_sysreset_remove,
+ }, {
+ .kind = RISCV_RPMI_SERVICE_HSM,
+ .add = riscv_rpmi_hsm_add,
+ .remove = riscv_rpmi_hsm_remove,
+ },
+};
+
+static const RiscvRpmiServiceOps *riscv_rpmi_service_ops_by_kind(
+ RiscvRpmiServiceKind kind)
+{
+ for (uint32_t i = 0; i < ARRAY_SIZE(riscv_rpmi_service_ops); i++) {
+ if (riscv_rpmi_service_ops[i].kind == kind) {
+ return &riscv_rpmi_service_ops[i];
+ }
+ }
+
+ return NULL;
+}
+
+static void riscv_rpmi_configure_services(RiscvRpmiState *s,
+ const RiscvRpmiConfig *cfg)
+{
+ for (uint32_t i = 0; i < ARRAY_SIZE(riscv_rpmi_service_ops); i++) {
+ const RiscvRpmiServiceOps *ops = &riscv_rpmi_service_ops[i];
+
+ if (ops->configure && riscv_rpmi_service_enabled(s, ops->kind)) {
+ ops->configure(s, cfg);
+ }
+ }
+}
+
static void riscv_rpmi_configure_base(RiscvRpmiState *s,
const RiscvRpmiConfig *cfg)
{
@@ -187,6 +230,8 @@ static void riscv_rpmi_configure_base(RiscvRpmiState *s,
cfg->hart_count * sizeof(*cfg->hart_ids));
}
}
+
+ riscv_rpmi_configure_services(s, cfg);
}
static void riscv_rpmi_init(Object *obj)
@@ -209,6 +254,7 @@ static void riscv_rpmi_reset(DeviceState *dev)
memory_region_set_dirty(&s->shmem, 0, s->shmem_size);
}
+ riscv_rpmi_hsm_reset(s);
}
static void riscv_rpmi_cleanup(RiscvRpmiState *s)
diff --git a/hw/misc/riscv_rpmi_hsm.c b/hw/misc/riscv_rpmi_hsm.c
new file mode 100644
index 0000000000..9a9859b10c
--- /dev/null
+++ b/hw/misc/riscv_rpmi_hsm.c
@@ -0,0 +1,280 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI HSM service.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#include "qemu/osdep.h"
+#include "riscv_rpmi_internal.h"
+#include "hw/core/cpu.h"
+#include "librpmi_env.h"
+
+static const struct rpmi_hsm_suspend_type riscv_rpmi_hsm_suspend_types[] = {
+ {
+ .type = 0,
+ .info = {
+ .flags = 0,
+ .entry_latency_us = 0,
+ .exit_latency_us = 0,
+ .wakeup_latency_us = 0,
+ .min_residency_us = 0,
+ },
+ },
+};
+
+typedef struct RiscvRpmiHsmStateTransition {
+ enum rpmi_hart_hw_state state;
+ bool halted;
+ bool resume;
+} RiscvRpmiHsmStateTransition;
+
+static const RiscvRpmiHsmStateTransition riscv_rpmi_hsm_state_transitions[] = {
+ {
+ .state = RPMI_HART_HW_STATE_STARTED,
+ .halted = false,
+ .resume = true,
+ }, {
+ .state = RPMI_HART_HW_STATE_STOPPED,
+ .halted = true,
+ .resume = false,
+ }, {
+ .state = RPMI_HART_HW_STATE_SUSPENDED,
+ .halted = true,
+ .resume = false,
+ },
+};
+
+static const RiscvRpmiHsmStateTransition *riscv_rpmi_hsm_state_transition(
+ enum rpmi_hart_hw_state state)
+{
+ for (uint32_t index = 0;
+ index < ARRAY_SIZE(riscv_rpmi_hsm_state_transitions); index++) {
+ if (riscv_rpmi_hsm_state_transitions[index].state == state) {
+ return &riscv_rpmi_hsm_state_transitions[index];
+ }
+ }
+
+ return NULL;
+}
+
+static CPUState *riscv_rpmi_hart_cpu(RiscvRpmiState *s, uint32_t hart_index)
+{
+ if (hart_index >= s->hart_count || !s->hart_ids) {
+ return NULL;
+ }
+
+ return cpu_by_arch_id(s->hart_ids[hart_index]);
+}
+
+static void riscv_rpmi_hsm_set_hw_state(RiscvRpmiState *s,
+ uint32_t hart_index,
+ enum rpmi_hart_hw_state state)
+{
+ CPUState *cpu = riscv_rpmi_hart_cpu(s, hart_index);
+ const RiscvRpmiHsmStateTransition *transition;
+
+ if (hart_index >= s->hart_count || !s->hsm_hw_states) {
+ return;
+ }
+
+ s->hsm_hw_states[hart_index] = state;
+ if (!cpu) {
+ return;
+ }
+
+ transition = riscv_rpmi_hsm_state_transition(state);
+ if (!transition) {
+ return;
+ }
+
+ cpu->halted = transition->halted;
+ if (transition->resume) {
+ cpu_resume(cpu);
+ } else {
+ qemu_cpu_kick(cpu);
+ }
+}
+
+static enum rpmi_error riscv_rpmi_hsm_start_prepare(
+ void *priv, rpmi_uint32_t hart_index, rpmi_uint64_t start_addr)
+{
+ RiscvRpmiState *s = priv;
+ CPUState *cpu = riscv_rpmi_hart_cpu(s, hart_index);
+
+ if (!cpu) {
+ return RPMI_ERR_INVALID_PARAM;
+ }
+
+ riscv_rpmi_hsm_set_hw_state(s, hart_index, RPMI_HART_HW_STATE_STARTED);
+ return RPMI_SUCCESS;
+}
+
+static void riscv_rpmi_hsm_start_finalize(void *priv,
+ rpmi_uint32_t hart_index,
+ rpmi_uint64_t start_addr)
+{
+}
+
+static enum rpmi_error riscv_rpmi_hsm_stop_prepare(void *priv,
+ rpmi_uint32_t hart_index)
+{
+ RiscvRpmiState *s = priv;
+
+ if (!riscv_rpmi_hart_cpu(s, hart_index)) {
+ return RPMI_ERR_INVALID_PARAM;
+ }
+
+ riscv_rpmi_hsm_set_hw_state(s, hart_index, RPMI_HART_HW_STATE_STOPPED);
+ return RPMI_SUCCESS;
+}
+
+static void riscv_rpmi_hsm_stop_finalize(void *priv, rpmi_uint32_t hart_index)
+{
+}
+
+static enum rpmi_error riscv_rpmi_hsm_suspend_prepare(
+ void *priv, rpmi_uint32_t hart_index,
+ const struct rpmi_hsm_suspend_type *suspend_type,
+ rpmi_uint64_t resume_addr)
+{
+ RiscvRpmiState *s = priv;
+
+ if (!suspend_type || !riscv_rpmi_hart_cpu(s, hart_index)) {
+ return RPMI_ERR_INVALID_PARAM;
+ }
+
+ riscv_rpmi_hsm_set_hw_state(s, hart_index, RPMI_HART_HW_STATE_SUSPENDED);
+ return RPMI_SUCCESS;
+}
+
+static void riscv_rpmi_hsm_suspend_finalize(
+ void *priv, rpmi_uint32_t hart_index,
+ const struct rpmi_hsm_suspend_type *suspend_type,
+ rpmi_uint64_t resume_addr)
+{
+}
+
+static enum rpmi_hart_hw_state riscv_rpmi_hsm_get_hw_state(
+ void *priv, rpmi_uint32_t hart_index)
+{
+ RiscvRpmiState *s = priv;
+
+ if (hart_index >= s->hart_count || !s->hsm_hw_states) {
+ return RPMI_HART_HW_STATE_STOPPED;
+ }
+
+ return s->hsm_hw_states[hart_index];
+}
+
+static const struct rpmi_hsm_platform_ops riscv_rpmi_hsm_ops = {
+ .hart_get_hw_state = riscv_rpmi_hsm_get_hw_state,
+ .hart_start_prepare = riscv_rpmi_hsm_start_prepare,
+ .hart_start_finalize = riscv_rpmi_hsm_start_finalize,
+ .hart_stop_prepare = riscv_rpmi_hsm_stop_prepare,
+ .hart_stop_finalize = riscv_rpmi_hsm_stop_finalize,
+ .hart_suspend_prepare = riscv_rpmi_hsm_suspend_prepare,
+ .hart_suspend_finalize = riscv_rpmi_hsm_suspend_finalize,
+};
+
+static bool riscv_rpmi_hsm_create(RiscvRpmiState *s,
+ struct rpmi_service_group **group,
+ Error **errp)
+{
+ struct rpmi_hsm *hsm;
+
+ if (!s->hart_count || !s->hart_ids) {
+ error_setg(errp, "RPMI HSM service requires hart IDs");
+ return false;
+ }
+
+ s->hsm_hw_states = g_new0(uint32_t, s->hart_count);
+ for (uint32_t i = 0; i < s->hart_count; i++) {
+ s->hsm_hw_states[i] = RPMI_HART_HW_STATE_STARTED;
+ }
+
+ hsm = rpmi_hsm_create(s->hart_count, s->hart_ids,
+ ARRAY_SIZE(riscv_rpmi_hsm_suspend_types),
+ riscv_rpmi_hsm_suspend_types,
+ &riscv_rpmi_hsm_ops, s);
+ if (!hsm) {
+ g_clear_pointer(&s->hsm_hw_states, g_free);
+ error_setg(errp, "failed to create RPMI HSM context");
+ return false;
+ }
+
+ *group = rpmi_service_group_hsm_create(hsm);
+ if (!*group) {
+ rpmi_hsm_destroy(hsm);
+ g_clear_pointer(&s->hsm_hw_states, g_free);
+ error_setg(errp, "failed to create RPMI HSM service group");
+ return false;
+ }
+
+ s->hsm = hsm;
+ return true;
+}
+
+static void riscv_rpmi_hsm_destroy(RiscvRpmiState *s)
+{
+ if (s->hsm_group) {
+ rpmi_env_free_lock(s->hsm_group->lock);
+ s->hsm_group->lock = NULL;
+ rpmi_service_group_hsm_destroy(s->hsm_group);
+ s->hsm_group = NULL;
+ }
+
+ if (s->hsm) {
+ rpmi_hsm_destroy(s->hsm);
+ s->hsm = NULL;
+ }
+
+ g_clear_pointer(&s->hsm_hw_states, g_free);
+}
+
+
+bool riscv_rpmi_hsm_add(RiscvRpmiState *s, Error **errp)
+{
+ struct rpmi_service_group *group;
+
+ if (s->hsm_group) {
+ error_setg(errp, "duplicate RPMI HSM service descriptor");
+ return false;
+ }
+
+ if (!riscv_rpmi_hsm_create(s, &group, errp)) {
+ return false;
+ }
+
+ if (!riscv_rpmi_context_add_group(s, group, "HSM", errp)) {
+ s->hsm_group = group;
+ riscv_rpmi_hsm_destroy(s);
+ return false;
+ }
+
+ s->hsm_group = group;
+ return true;
+}
+
+void riscv_rpmi_hsm_remove(RiscvRpmiState *s)
+{
+ riscv_rpmi_context_remove_group(s, s->hsm_group);
+ riscv_rpmi_hsm_destroy(s);
+}
+
+void riscv_rpmi_hsm_reset(RiscvRpmiState *s)
+{
+ if (!s->hsm_hw_states) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < s->hart_count; i++) {
+ s->hsm_hw_states[i] = RPMI_HART_HW_STATE_STARTED;
+ }
+ if (s->hsm) {
+ rpmi_hsm_process_state_changes(s->hsm);
+ }
+}
diff --git a/hw/misc/riscv_rpmi_internal.h b/hw/misc/riscv_rpmi_internal.h
index fe09804e69..42a6708907 100644
--- a/hw/misc/riscv_rpmi_internal.h
+++ b/hw/misc/riscv_rpmi_internal.h
@@ -30,4 +30,8 @@ void riscv_rpmi_context_remove_group(RiscvRpmiState *s,
bool riscv_rpmi_sysreset_add(RiscvRpmiState *s, Error **errp);
void riscv_rpmi_sysreset_remove(RiscvRpmiState *s);
+bool riscv_rpmi_hsm_add(RiscvRpmiState *s, Error **errp);
+void riscv_rpmi_hsm_remove(RiscvRpmiState *s);
+void riscv_rpmi_hsm_reset(RiscvRpmiState *s);
+
#endif
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index 273a9aa04f..2b5f367bdb 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -1014,6 +1014,12 @@ static const RiscvRpmiServiceConfig virt_rpmi_services[] = {
.compatible = "riscv,rpmi-system-reset",
.service_group = RISCV_RPMI_SRVGRP_SYSTEM_RESET,
},
+ {
+ .kind = RISCV_RPMI_SERVICE_HSM,
+ .node_name = "hsm",
+ .compatible = "riscv,rpmi-hsm",
+ .service_group = RISCV_RPMI_SRVGRP_HSM,
+ },
};
static uint32_t virt_rpmi_service_count(RISCVVirtState *s)
diff --git a/include/hw/misc/riscv_rpmi.h b/include/hw/misc/riscv_rpmi.h
index d11db87731..37c56ea2e8 100644
--- a/include/hw/misc/riscv_rpmi.h
+++ b/include/hw/misc/riscv_rpmi.h
@@ -28,6 +28,7 @@
#define VIRT_RPMI_P2A_REQ_SIZE 0
#define RISCV_RPMI_SRVGRP_SYSTEM_RESET 3
+#define RISCV_RPMI_SRVGRP_HSM 5
#define TYPE_RISCV_RPMI "riscv-rpmi"
OBJECT_DECLARE_SIMPLE_TYPE(RiscvRpmiState, RISCV_RPMI)
@@ -36,10 +37,12 @@ struct rpmi_context;
struct rpmi_service_group;
struct rpmi_shmem;
struct rpmi_transport;
+struct rpmi_hsm;
typedef enum RiscvRpmiServiceKind {
RISCV_RPMI_SERVICE_INVALID = 0,
RISCV_RPMI_SERVICE_SYSRESET,
+ RISCV_RPMI_SERVICE_HSM,
} RiscvRpmiServiceKind;
typedef struct RiscvRpmiMachineOps {
@@ -83,6 +86,9 @@ struct RiscvRpmiState {
const RiscvRpmiMachineOps *machine_ops;
void *machine_opaque;
struct rpmi_service_group *sysreset_group;
+ struct rpmi_hsm *hsm;
+ struct rpmi_service_group *hsm_group;
+ uint32_t *hsm_hw_states;
uint32_t *hart_ids;
uint32_t hart_count;
const RiscvRpmiServiceConfig *services;
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v2 07/10] riscv: add RPMI system suspend service
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 8:39 UTC (permalink / raw)
To: qemu-riscv; +Cc: qemu-devel, daniel.barboza, Subrahmanya Lingappa
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add the RPMI System Suspend service group for RISC-V virt.
The service advertises suspend-to-RAM support through librpmi,
registers QEMU wakeup support, and routes the suspend finalize callback
to the machine suspend path. On wakeup it processes RPMI group events
and defers the HSM hart resume until vCPUs are ready to run again.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
hw/misc/meson.build | 1 +
hw/misc/riscv_rpmi.c | 4 +
hw/misc/riscv_rpmi_hsm.c | 9 +-
hw/misc/riscv_rpmi_internal.h | 5 +
hw/misc/riscv_rpmi_syssusp.c | 191 ++++++++++++++++++++++++++++++++++
hw/riscv/virt.c | 25 +++++
include/hw/misc/riscv_rpmi.h | 11 ++
7 files changed, 245 insertions(+), 1 deletion(-)
create mode 100644 hw/misc/riscv_rpmi_syssusp.c
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index fc5b11ed57..cb093a8642 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -172,4 +172,5 @@ system_ss.add(when: 'CONFIG_RISCV_RPMI', if_true: [files(
'riscv_rpmi.c',
'riscv_rpmi_sysreset.c',
'riscv_rpmi_hsm.c',
+ 'riscv_rpmi_syssusp.c',
), librpmi])
diff --git a/hw/misc/riscv_rpmi.c b/hw/misc/riscv_rpmi.c
index 820b1d5d63..08691861b0 100644
--- a/hw/misc/riscv_rpmi.c
+++ b/hw/misc/riscv_rpmi.c
@@ -187,6 +187,10 @@ static const RiscvRpmiServiceOps riscv_rpmi_service_ops[] = {
.kind = RISCV_RPMI_SERVICE_HSM,
.add = riscv_rpmi_hsm_add,
.remove = riscv_rpmi_hsm_remove,
+ }, {
+ .kind = RISCV_RPMI_SERVICE_SYSSUSP,
+ .add = riscv_rpmi_syssusp_add,
+ .remove = riscv_rpmi_syssusp_remove,
},
};
diff --git a/hw/misc/riscv_rpmi_hsm.c b/hw/misc/riscv_rpmi_hsm.c
index 9a9859b10c..bacdabcac6 100644
--- a/hw/misc/riscv_rpmi_hsm.c
+++ b/hw/misc/riscv_rpmi_hsm.c
@@ -235,7 +235,6 @@ static void riscv_rpmi_hsm_destroy(RiscvRpmiState *s)
g_clear_pointer(&s->hsm_hw_states, g_free);
}
-
bool riscv_rpmi_hsm_add(RiscvRpmiState *s, Error **errp)
{
struct rpmi_service_group *group;
@@ -278,3 +277,11 @@ void riscv_rpmi_hsm_reset(RiscvRpmiState *s)
rpmi_hsm_process_state_changes(s->hsm);
}
}
+
+void riscv_rpmi_hsm_resume(RiscvRpmiState *s, uint32_t hart_index)
+{
+ riscv_rpmi_hsm_set_hw_state(s, hart_index, RPMI_HART_HW_STATE_STARTED);
+ if (s->hsm) {
+ rpmi_hsm_process_state_changes(s->hsm);
+ }
+}
diff --git a/hw/misc/riscv_rpmi_internal.h b/hw/misc/riscv_rpmi_internal.h
index 42a6708907..34cab35c32 100644
--- a/hw/misc/riscv_rpmi_internal.h
+++ b/hw/misc/riscv_rpmi_internal.h
@@ -33,5 +33,10 @@ void riscv_rpmi_sysreset_remove(RiscvRpmiState *s);
bool riscv_rpmi_hsm_add(RiscvRpmiState *s, Error **errp);
void riscv_rpmi_hsm_remove(RiscvRpmiState *s);
void riscv_rpmi_hsm_reset(RiscvRpmiState *s);
+void riscv_rpmi_hsm_resume(RiscvRpmiState *s, uint32_t hart_index);
+
+bool riscv_rpmi_syssusp_add(RiscvRpmiState *s, Error **errp);
+void riscv_rpmi_syssusp_remove(RiscvRpmiState *s);
+
#endif
diff --git a/hw/misc/riscv_rpmi_syssusp.c b/hw/misc/riscv_rpmi_syssusp.c
new file mode 100644
index 0000000000..d455e08163
--- /dev/null
+++ b/hw/misc/riscv_rpmi_syssusp.c
@@ -0,0 +1,191 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI System Suspend service.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#include "qemu/osdep.h"
+#include "riscv_rpmi_internal.h"
+#include "qemu/timer.h"
+#include "system/runstate.h"
+
+static const struct rpmi_system_suspend_type riscv_rpmi_syssusp_types[] = {
+ {
+ .type = RPMI_SYSSUSP_TYPE_SUSPEND_TO_RAM,
+ .attr = RPMI_SYSSUSP_ATTRS_FLAGS_RESUMEADDR,
+ },
+};
+
+static enum rpmi_error riscv_rpmi_syssusp_prepare(
+ void *priv, rpmi_uint32_t hart_index,
+ const struct rpmi_system_suspend_type *syssusp_type,
+ rpmi_uint64_t resume_addr)
+{
+ RiscvRpmiState *s = priv;
+
+ if (s->machine_ops && s->machine_ops->register_wakeup_support) {
+ s->machine_ops->register_wakeup_support(s->machine_opaque);
+ }
+ return RPMI_SUCCESS;
+}
+
+static rpmi_bool_t riscv_rpmi_syssusp_ready(void *priv,
+ rpmi_uint32_t hart_index)
+{
+ return true;
+}
+
+static void riscv_rpmi_syssusp_finalize(
+ void *priv, rpmi_uint32_t hart_index,
+ const struct rpmi_system_suspend_type *syssusp_type,
+ rpmi_uint64_t resume_addr)
+{
+ RiscvRpmiState *s = priv;
+
+ if (s->machine_ops && s->machine_ops->system_suspend) {
+ s->machine_ops->system_suspend(s->machine_opaque);
+ }
+}
+
+static rpmi_bool_t riscv_rpmi_syssusp_can_resume(void *priv,
+ rpmi_uint32_t hart_index)
+{
+ RiscvRpmiState *s = priv;
+
+ if (s->machine_ops && s->machine_ops->system_can_resume) {
+ return s->machine_ops->system_can_resume(s->machine_opaque);
+ }
+
+ return true;
+}
+
+static enum rpmi_error riscv_rpmi_syssusp_resume(
+ void *priv, rpmi_uint32_t hart_index,
+ const struct rpmi_system_suspend_type *syssusp_type,
+ rpmi_uint64_t resume_addr)
+{
+ RiscvRpmiState *s = priv;
+
+ s->syssusp_resume_hart_index = hart_index;
+ s->syssusp_resume_pending = true;
+ return RPMI_SUCCESS;
+}
+
+static const struct rpmi_syssusp_platform_ops riscv_rpmi_syssusp_ops = {
+ .system_suspend_prepare = riscv_rpmi_syssusp_prepare,
+ .system_suspend_ready = riscv_rpmi_syssusp_ready,
+ .system_suspend_finalize = riscv_rpmi_syssusp_finalize,
+ .system_suspend_can_resume = riscv_rpmi_syssusp_can_resume,
+ .system_suspend_resume = riscv_rpmi_syssusp_resume,
+};
+
+static void riscv_rpmi_wakeup_timer(void *opaque)
+{
+ RiscvRpmiState *s = opaque;
+
+ if (s->syssusp_resume_pending) {
+ riscv_rpmi_hsm_resume(s, s->syssusp_resume_hart_index);
+ s->syssusp_resume_pending = false;
+ }
+}
+
+static void riscv_rpmi_wakeup_notify(Notifier *notifier, void *data)
+{
+ RiscvRpmiState *s = container_of(notifier, RiscvRpmiState,
+ wakeup_notifier);
+
+ if (s->context) {
+ rpmi_context_process_group_events(s->context,
+ RPMI_SRVGRP_SYSTEM_SUSPEND);
+ }
+ if (s->wakeup_timer) {
+ /*
+ * Wakeup notifiers run before vCPUs resume. Defer the HSM
+ * kick so OpenSBI can leave WFI after QEMU restarts execution.
+ */
+ timer_mod(s->wakeup_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 10);
+ }
+}
+
+static bool riscv_rpmi_syssusp_create(RiscvRpmiState *s,
+ struct rpmi_service_group **group,
+ Error **errp)
+{
+ if (!s->hsm) {
+ error_setg(errp, "RPMI system suspend service requires HSM service");
+ return false;
+ }
+
+ if (s->machine_ops && s->machine_ops->register_wakeup_support) {
+ s->machine_ops->register_wakeup_support(s->machine_opaque);
+ }
+ if (!s->wakeup_timer) {
+ s->wakeup_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
+ riscv_rpmi_wakeup_timer, s);
+ }
+ if (!s->wakeup_notifier_registered) {
+ s->wakeup_notifier.notify = riscv_rpmi_wakeup_notify;
+ qemu_register_wakeup_notifier(&s->wakeup_notifier);
+ s->wakeup_notifier_registered = true;
+ }
+
+ *group = rpmi_service_group_syssusp_create(
+ s->hsm, ARRAY_SIZE(riscv_rpmi_syssusp_types),
+ riscv_rpmi_syssusp_types, &riscv_rpmi_syssusp_ops, s);
+ if (!*group) {
+ error_setg(errp, "failed to create RPMI system suspend service group");
+ return false;
+ }
+
+ return true;
+}
+
+static void riscv_rpmi_syssusp_destroy(RiscvRpmiState *s)
+{
+ if (s->wakeup_notifier_registered) {
+ notifier_remove(&s->wakeup_notifier);
+ s->wakeup_notifier_registered = false;
+ }
+ if (s->wakeup_timer) {
+ timer_free(s->wakeup_timer);
+ s->wakeup_timer = NULL;
+ }
+
+ if (s->syssusp_group) {
+ rpmi_service_group_syssusp_destroy(s->syssusp_group);
+ s->syssusp_group = NULL;
+ }
+}
+
+bool riscv_rpmi_syssusp_add(RiscvRpmiState *s, Error **errp)
+{
+ struct rpmi_service_group *group;
+
+ if (s->syssusp_group) {
+ error_setg(errp, "duplicate RPMI system suspend descriptor");
+ return false;
+ }
+
+ if (!riscv_rpmi_syssusp_create(s, &group, errp)) {
+ return false;
+ }
+
+ if (!riscv_rpmi_context_add_group(s, group, "system suspend", errp)) {
+ s->syssusp_group = group;
+ riscv_rpmi_syssusp_destroy(s);
+ return false;
+ }
+
+ s->syssusp_group = group;
+ return true;
+}
+
+void riscv_rpmi_syssusp_remove(RiscvRpmiState *s)
+{
+ riscv_rpmi_context_remove_group(s, s->syssusp_group);
+ riscv_rpmi_syssusp_destroy(s);
+}
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index 2b5f367bdb..21c218a78d 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -51,6 +51,7 @@
#include "chardev/char.h"
#include "system/device_tree.h"
#include "system/system.h"
+#include "system/runstate.h"
#include "system/tcg.h"
#include "system/kvm.h"
#include "system/tpm.h"
@@ -1020,6 +1021,12 @@ static const RiscvRpmiServiceConfig virt_rpmi_services[] = {
.compatible = "riscv,rpmi-hsm",
.service_group = RISCV_RPMI_SRVGRP_HSM,
},
+ {
+ .kind = RISCV_RPMI_SERVICE_SYSSUSP,
+ .node_name = "suspend",
+ .compatible = "riscv,rpmi-system-suspend",
+ .service_group = RISCV_RPMI_SRVGRP_SYSTEM_SUSPEND,
+ },
};
static uint32_t virt_rpmi_service_count(RISCVVirtState *s)
@@ -1037,9 +1044,27 @@ static void virt_rpmi_system_shutdown(void *opaque)
qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
}
+static void virt_rpmi_system_suspend(void *opaque)
+{
+ qemu_system_suspend_request();
+}
+
+static void virt_rpmi_register_wakeup_support(void *opaque)
+{
+ qemu_register_wakeup_support();
+}
+
+static bool virt_rpmi_system_can_resume(void *opaque)
+{
+ return runstate_check(RUN_STATE_RUNNING);
+}
+
static const RiscvRpmiMachineOps virt_rpmi_machine_ops = {
.system_reset = virt_rpmi_system_reset,
.system_shutdown = virt_rpmi_system_shutdown,
+ .system_suspend = virt_rpmi_system_suspend,
+ .register_wakeup_support = virt_rpmi_register_wakeup_support,
+ .system_can_resume = virt_rpmi_system_can_resume,
};
static RiscvRpmiConfig virt_rpmi_config(RISCVVirtState *s,
diff --git a/include/hw/misc/riscv_rpmi.h b/include/hw/misc/riscv_rpmi.h
index 37c56ea2e8..a4fe2aac63 100644
--- a/include/hw/misc/riscv_rpmi.h
+++ b/include/hw/misc/riscv_rpmi.h
@@ -29,6 +29,7 @@
#define RISCV_RPMI_SRVGRP_SYSTEM_RESET 3
#define RISCV_RPMI_SRVGRP_HSM 5
+#define RISCV_RPMI_SRVGRP_SYSTEM_SUSPEND 4
#define TYPE_RISCV_RPMI "riscv-rpmi"
OBJECT_DECLARE_SIMPLE_TYPE(RiscvRpmiState, RISCV_RPMI)
@@ -43,11 +44,15 @@ typedef enum RiscvRpmiServiceKind {
RISCV_RPMI_SERVICE_INVALID = 0,
RISCV_RPMI_SERVICE_SYSRESET,
RISCV_RPMI_SERVICE_HSM,
+ RISCV_RPMI_SERVICE_SYSSUSP,
} RiscvRpmiServiceKind;
typedef struct RiscvRpmiMachineOps {
void (*system_reset)(void *opaque);
void (*system_shutdown)(void *opaque);
+ void (*system_suspend)(void *opaque);
+ void (*register_wakeup_support)(void *opaque);
+ bool (*system_can_resume)(void *opaque);
} RiscvRpmiMachineOps;
typedef struct RiscvRpmiServiceConfig {
RiscvRpmiServiceKind kind;
@@ -86,9 +91,15 @@ struct RiscvRpmiState {
const RiscvRpmiMachineOps *machine_ops;
void *machine_opaque;
struct rpmi_service_group *sysreset_group;
+ struct rpmi_service_group *syssusp_group;
struct rpmi_hsm *hsm;
struct rpmi_service_group *hsm_group;
uint32_t *hsm_hw_states;
+ Notifier wakeup_notifier;
+ bool wakeup_notifier_registered;
+ QEMUTimer *wakeup_timer;
+ bool syssusp_resume_pending;
+ uint32_t syssusp_resume_hart_index;
uint32_t *hart_ids;
uint32_t hart_count;
const RiscvRpmiServiceConfig *services;
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v2 08/10] docs/system/riscv: document initial virt RPMI support
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 8:39 UTC (permalink / raw)
To: qemu-riscv; +Cc: qemu-devel, daniel.barboza, Subrahmanya Lingappa
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Document the opt-in virt RPMI transport and the initial HSM, system
reset, and system suspend service groups.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
docs/system/riscv/virt.rst | 50 ++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/docs/system/riscv/virt.rst b/docs/system/riscv/virt.rst
index 60850970ce..8e88f780e1 100644
--- a/docs/system/riscv/virt.rst
+++ b/docs/system/riscv/virt.rst
@@ -146,6 +146,56 @@ The following machine-specific options are supported:
Enables the riscv-iommu-sys platform device. Defaults to 'off'.
+- rpmi=[on|off]
+
+ Enables the RISC-V RPMI shared-memory transport. This option defaults to
+ "off", is TCG-only, and is available only when QEMU is built with librpmi
+ support. This support targets the RISC-V Platform Management Interface
+ (RPMI) Specification v1.0 Ratified (`RPMI v1.0 specification`_). The initial
+ ``virt`` implementation exposes RPMI system reset, system suspend, and HSM
+ service groups. System reset can request shutdown or cold reboot through
+ RPMI. HSM uses the ``virt`` hart IDs and supports hart discovery, status,
+ start, stop, and suspend commands. System suspend uses QEMU's suspend and
+ wakeup path. Migration is currently blocked while RPMI transport and service
+ VMState support is being designed.
+
+ The build dependency is provided by `librpmi`_. Install librpmi so that
+ ``pkg-config`` can find ``librpmi.pc``, then configure QEMU with
+ ``--enable-librpmi`` or ``-Dlibrpmi=enabled``.
+
+.. _RPMI v1.0 specification: https://github.com/riscv-non-isa/riscv-rpmi/releases/download/v1.0/riscv-rpmi.pdf
+.. _librpmi: https://github.com/riscv-software-src/librpmi
+
+RPMI firmware and guest smoke
+-----------------------------
+
+A firmware-level RPMI smoke test can be run with OpenSBI, a Linux kernel, and a
+RISC-V disk image:
+
+.. code-block:: bash
+
+ $ qemu-system-riscv64 \
+ -machine virt,rpmi=on \
+ -cpu rv64 -smp 4 -m 4G -nographic \
+ -bios /path/to/fw_jump.bin \
+ -kernel /path/to/Image \
+ -append "root=/dev/vda rw console=ttyS0 earlycon=sbi" \
+ -drive file=/path/to/rootfs.img,format=raw,if=virtio,snapshot=on
+
+The OpenSBI banner should report RPMI-backed HSM and system suspend support.
+After the guest reaches a shell, normal guest reboot/poweroff commands exercise
+the RPMI system reset path.
+
+RPMI device tree policy
+-----------------------
+
+RPMI is currently described through FDT only; no ACPI representation is emitted
+for the RPMI transport or service groups. The ``virt`` machine uses the
+``riscv,rpmi-shmem-mbox`` shared-memory mailbox node plus child service nodes
+with ``riscv,rpmi-*`` compatible strings. These binding names are part of the
+RPMI RFC surface and may be revised if the RISC-V RPMI bindings change during
+review.
+
Running Linux kernel
--------------------
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v2 09/10] tests/qtest: cover initial RISC-V RPMI services
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 8:39 UTC (permalink / raw)
To: qemu-riscv; +Cc: qemu-devel, daniel.barboza, Subrahmanya Lingappa
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add qtests for the RPMI transport plus Base, system reset, HSM, and
system suspend service groups.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
MAINTAINERS | 1 +
tests/qtest/meson.build | 2 +
tests/qtest/riscv-rpmi-test.c | 684 ++++++++++++++++++++++++++++++++++
3 files changed, 687 insertions(+)
create mode 100644 tests/qtest/riscv-rpmi-test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 8e91a31757..6dd71c5a82 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -361,6 +361,7 @@ F: hw/riscv/
F: hw/intc/riscv*
F: hw/misc/riscv_rpmi*
F: include/hw/misc/riscv_rpmi.h
+F: tests/qtest/riscv-rpmi-test.c
F: include/hw/char/riscv_htif.h
F: include/hw/riscv/
F: common-user/host/riscv*
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 4897325d84..d54b79cbcf 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -290,6 +290,8 @@ qtests_riscv32 = \
qtests_riscv64 = ['riscv-csr-test'] + \
(unpack_edk2_blobs ? ['bios-tables-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_RISCV_RPMI') ?
+ ['riscv-rpmi-test'] : []) + \
(config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and
config_all_devices.has_key('CONFIG_RISCV_IOMMU') ?
['iommu-riscv-test'] : []) + \
diff --git a/tests/qtest/riscv-rpmi-test.c b/tests/qtest/riscv-rpmi-test.c
new file mode 100644
index 0000000000..ecd5b910c7
--- /dev/null
+++ b/tests/qtest/riscv-rpmi-test.c
@@ -0,0 +1,684 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * QTests for RISC-V RPMI devices.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+#include "libqtest.h"
+#include "qobject/qdict.h"
+
+#define RPMI_SHMEM_BASE 0x10200000ULL
+#define RPMI_DOORBELL_BASE 0x10230000ULL
+#define RPMI_IMSIC_S_BASE 0x28000000ULL
+#define RPMI_CPPC_FASTCHAN_BASE 0x10240000ULL
+#define RPMI_CPPC_FASTCHAN_SIZE 0x4000
+#define RPMI_CPPC_FASTCHAN_FEEDBACK_OFFSET 0x2000
+#define RPMI_SLOT_SIZE 64
+
+#define RPMI_A2P_HEAD RPMI_SHMEM_BASE
+#define RPMI_A2P_TAIL (RPMI_SHMEM_BASE + RPMI_SLOT_SIZE)
+#define RPMI_A2P_SLOT0 (RPMI_SHMEM_BASE + 2 * RPMI_SLOT_SIZE)
+
+#define RPMI_SRVGRP_BASE 0x0001
+#define RPMI_SRVGRP_SYSTEM_MSI 0x0002
+#define RPMI_SRVGRP_SYSTEM_RESET 0x0003
+#define RPMI_SRVGRP_SYSTEM_SUSPEND 0x0004
+#define RPMI_SRVGRP_HSM 0x0005
+#define RPMI_SRVGRP_CPPC 0x0006
+#define RPMI_SRVGRP_CLOCK 0x0008
+#define RPMI_SRVGRP_MANAGEMENT_MODE 0x000b
+#define RPMI_SRVGRP_LOGGING 0x000e
+#define RPMI_BASE_SRV_GET_PLATFORM_INFO 0x05
+#define RPMI_BASE_SRV_PROBE_SERVICE_GROUP 0x06
+#define RPMI_SYSMSI_SRV_GET_ATTRIBUTES 0x02
+#define RPMI_SYSMSI_SRV_GET_MSI_ATTRIBUTES 0x03
+#define RPMI_SYSMSI_SRV_SET_MSI_STATE 0x04
+#define RPMI_SYSMSI_SRV_GET_MSI_STATE 0x05
+#define RPMI_SYSMSI_SRV_SET_MSI_TARGET 0x06
+#define RPMI_SYSMSI_SRV_GET_MSI_TARGET 0x07
+#define RPMI_SYSRST_SRV_GET_ATTRIBUTES 0x02
+#define RPMI_SYSRST_SRV_SYSTEM_RESET 0x03
+#define RPMI_HSM_SRV_GET_HART_STATUS 0x02
+#define RPMI_HSM_SRV_GET_HART_LIST 0x03
+#define RPMI_HSM_SRV_GET_SUSPEND_TYPES 0x04
+#define RPMI_HSM_SRV_GET_SUSPEND_INFO 0x05
+#define RPMI_HSM_SRV_HART_START 0x06
+#define RPMI_HSM_SRV_HART_STOP 0x07
+#define RPMI_HSM_SRV_HART_SUSPEND 0x08
+#define RPMI_SYSSUSP_SRV_GET_ATTRIBUTES 0x02
+#define RPMI_SYSSUSP_SRV_SYSTEM_SUSPEND 0x03
+#define RPMI_CPPC_SRV_PROBE_REG 0x02
+#define RPMI_CPPC_SRV_READ_REG 0x03
+#define RPMI_CPPC_SRV_WRITE_REG 0x04
+#define RPMI_CPPC_SRV_GET_FAST_CHANNEL_REGION 0x05
+#define RPMI_CPPC_SRV_GET_FAST_CHANNEL_OFFSET 0x06
+#define RPMI_CPPC_SRV_GET_HART_LIST 0x07
+#define RPMI_CPPC_NOMINAL_PERF 0x01
+#define RPMI_CPPC_DESIRED_PERF 0x05
+#define RPMI_CPPC_COUNTER_WRAPAROUND_TIME 0x0a
+#define RPMI_CPPC_REFERENCE_PERF_COUNTER 0x0b
+#define RPMI_CPPC_DELIVERED_PERF_COUNTER 0x0c
+#define RPMI_CPPC_REG_LEN_64 64
+#define RPMI_CLK_SRV_GET_NUM_CLOCKS 0x02
+#define RPMI_CLK_SRV_GET_ATTRIBUTES 0x03
+#define RPMI_CLK_SRV_GET_SUPPORTED_RATES 0x04
+#define RPMI_CLK_SRV_SET_CONFIG 0x05
+#define RPMI_CLK_SRV_GET_CONFIG 0x06
+#define RPMI_CLK_SRV_SET_RATE 0x07
+#define RPMI_CLK_SRV_GET_RATE 0x08
+#define RPMI_MM_SRV_GET_ATTRIBUTES 0x02
+#define RPMI_MM_SRV_COMMUNICATE 0x03
+#define RPMI_LOGGING_SRV_SET_CONFIG 0x02
+#define RPMI_MSG_NORMAL_REQUEST 0x00
+#define RPMI_MSG_POSTED_REQUEST 0x01
+#define RPMI_MSG_ACKNOWLEDGEMENT 0x02
+#define RPMI_SYSRST_TYPE_SHUTDOWN 0x00
+#define RPMI_SYSRST_TYPE_COLD_REBOOT 0x01
+#define RPMI_SYSRST_TYPE_INVALID 0x03
+#define RPMI_SYSRST_ATTRS_FLAGS_RESETTYPE 1
+#define RPMI_SYS_MSI_SHUTDOWN_INDEX 0
+#define RPMI_SYS_MSI_REBOOT_INDEX 1
+#define RPMI_SYS_MSI_SUSPEND_INDEX 2
+#define RPMI_SYS_NUM_MSI 4
+#define RPMI_SYSMSI_MSI_STATE_ENABLE 1
+#define RPMI_SYSMSI_MSI_STATE_PENDING 2
+#define RPMI_TOKEN 0x55aa
+#define RPMI_ERR_NOTSUPP 0xfffffffeU
+#define RPMI_ERR_INVALID_PARAM 0xfffffffdU
+#define RPMI_ERR_INVALID_ADDR 0xfffffffbU
+#define RPMI_ERR_DENIED 0xfffffffcU
+#define RPMI_HSM_HART_STATE_STARTED 0x00
+#define RPMI_HSM_HART_STATE_STOPPED 0x01
+#define RPMI_HSM_HART_STATE_SUSPENDED 0x04
+#define VIRT_RPMI_CPPC_NOMINAL_PERF 30
+#define VIRT_RPMI_CLOCK_COUNT 6
+#define VIRT_RPMI_MM_VERSION 0x10000
+#define VIRT_RPMI_SHMEM_SIZE 0x20000
+#define RPMI_MM_INPUT_OFFSET 0x3000
+#define RPMI_MM_OUTPUT_OFFSET 0x3800
+#define RPMI_MM_BUFFER_SIZE 0x400
+#define RPMI_MM_INPUT_BASE (RPMI_SHMEM_BASE + RPMI_MM_INPUT_OFFSET)
+#define RPMI_MM_OUTPUT_BASE (RPMI_SHMEM_BASE + RPMI_MM_OUTPUT_OFFSET)
+#define MM_EFI_COMM_HEADER_SIZE 24
+#define EFI_VAR_COMM_HEADER_SIZE 16
+#define EFI_VAR_ACCESS_NAME_OFFSET 36
+#define EFI_VAR_NEXT_NAME_OFFSET 24
+#define EFI_VAR_FN_GET_VARIABLE 1
+#define EFI_VAR_FN_GET_NEXT_VARIABLE_NAME 2
+#define EFI_VAR_FN_SET_VARIABLE 3
+#define EFI_SUCCESS 0ULL
+#define EFI_INVALID_PARAMETER 0x8000000000000002ULL
+#define EFI_BUFFER_TOO_SMALL 0x8000000000000005ULL
+#define EFI_NOT_FOUND 0x800000000000000eULL
+#define EFI_VARIABLE_NON_VOLATILE 0x00000001
+#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x00000002
+#define EFI_VARIABLE_RUNTIME_ACCESS 0x00000004
+
+#define RPMI_P2A_ACK_BASE (RPMI_SHMEM_BASE + 16 * RPMI_SLOT_SIZE)
+#define RPMI_P2A_ACK_HEAD RPMI_P2A_ACK_BASE
+#define RPMI_P2A_ACK_TAIL (RPMI_P2A_ACK_BASE + RPMI_SLOT_SIZE)
+#define RPMI_P2A_ACK_SLOT0 (RPMI_P2A_ACK_BASE + 2 * RPMI_SLOT_SIZE)
+
+static uint64_t rpmi_response_base;
+
+static uint64_t rpmi_queue_slot(uint64_t queue_base, uint32_t index)
+{
+ return queue_base + (index + 2) * RPMI_SLOT_SIZE;
+}
+
+static void rpmi_send_request(QTestState *qts, uint16_t service_group,
+ uint8_t service_id, uint8_t request_type,
+ const uint32_t *data, size_t data_words)
+{
+ uint32_t tail = qtest_readl(qts, RPMI_A2P_TAIL);
+ uint64_t slot = rpmi_queue_slot(RPMI_SHMEM_BASE, tail);
+ size_t i;
+
+ qtest_writew(qts, slot, service_group);
+ qtest_writeb(qts, slot + 2, service_id);
+ qtest_writeb(qts, slot + 3, request_type);
+ qtest_writew(qts, slot + 4, data_words * sizeof(*data));
+ qtest_writew(qts, slot + 6, RPMI_TOKEN);
+
+ for (i = 0; i < data_words; i++) {
+ qtest_writel(qts, slot + 8 + i * sizeof(*data), data[i]);
+ }
+
+ g_test_message(
+ "RPMI_A2P_REQ shmem=0x%016" PRIx64 " doorbell=0x%016" PRIx64
+ " group=0x%04x service=0x%02x type=0x%02x data_len=%zu"
+ " token=0x%04x a2p_tail=%u slot=0x%016" PRIx64,
+ (uint64_t)RPMI_SHMEM_BASE, (uint64_t)RPMI_DOORBELL_BASE,
+ service_group, service_id, request_type, data_words * sizeof(*data),
+ RPMI_TOKEN, tail, slot);
+
+ qtest_writel(qts, RPMI_A2P_TAIL, (tail + 1) % 16);
+ qtest_writel(qts, RPMI_DOORBELL_BASE, 1);
+}
+
+static uint32_t rpmi_response_word(QTestState *qts, unsigned int word)
+{
+ return qtest_readl(qts, rpmi_response_base + 8 + word * sizeof(uint32_t));
+}
+
+static void rpmi_expect_ack(QTestState *qts, uint16_t service_group,
+ uint8_t service_id, uint16_t data_len)
+{
+ uint32_t head = qtest_readl(qts, RPMI_P2A_ACK_HEAD);
+ uint32_t tail = qtest_readl(qts, RPMI_P2A_ACK_TAIL);
+
+ g_assert_cmphex(tail, !=, head);
+ rpmi_response_base = rpmi_queue_slot(RPMI_P2A_ACK_BASE, head);
+ g_assert_cmphex(qtest_readw(qts, rpmi_response_base), ==, service_group);
+ g_assert_cmphex(qtest_readb(qts, rpmi_response_base + 2), ==,
+ service_id);
+ g_assert_cmphex(qtest_readb(qts, rpmi_response_base + 3), ==,
+ RPMI_MSG_ACKNOWLEDGEMENT);
+ g_assert_cmphex(qtest_readw(qts, rpmi_response_base + 4), ==, data_len);
+ g_assert_cmphex(qtest_readw(qts, rpmi_response_base + 6), ==,
+ RPMI_TOKEN);
+ g_test_message(
+ "RPMI_P2A_ACK shmem=0x%016" PRIx64
+ " group=0x%04x service=0x%02x type=0x%02x data_len=%u"
+ " token=0x%04x p2a_head=%u slot=0x%016" PRIx64
+ " status=0x%08x",
+ (uint64_t)RPMI_SHMEM_BASE, service_group, service_id,
+ RPMI_MSG_ACKNOWLEDGEMENT, data_len, RPMI_TOKEN, head,
+ rpmi_response_base,
+ data_len >= sizeof(uint32_t) ? rpmi_response_word(qts, 0) : 0);
+ qtest_writel(qts, RPMI_P2A_ACK_HEAD, (head + 1) % 16);
+}
+
+static void rpmi_send_sysreset(QTestState *qts, uint32_t reset_type,
+ uint8_t request_type)
+{
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_SYSTEM_RESET, request_type,
+ &reset_type, 1);
+}
+
+static void rpmi_expect_qemu_failure(const char *extra_args,
+ const char *stderr_needle)
+{
+ g_autoptr(GError) error = NULL;
+ g_auto(GStrv) split_args = NULL;
+ g_autoptr(GPtrArray) argv = g_ptr_array_new();
+ g_autofree char *stderr_data = NULL;
+ gint wait_status;
+ gboolean spawned;
+
+ g_assert_true(g_shell_parse_argv(extra_args, NULL, &split_args, &error));
+ g_assert_no_error(error);
+
+ g_ptr_array_add(argv, (gpointer)qtest_qemu_binary(NULL));
+ for (char **arg = split_args; *arg; arg++) {
+ g_ptr_array_add(argv, *arg);
+ }
+ g_ptr_array_add(argv, NULL);
+
+ spawned = g_spawn_sync(NULL, (char **)argv->pdata, NULL,
+ G_SPAWN_STDOUT_TO_DEV_NULL,
+ NULL, NULL, NULL, &stderr_data,
+ &wait_status, &error);
+ g_assert_true(spawned);
+ g_assert_no_error(error);
+ g_assert_false(g_spawn_check_exit_status(wait_status, NULL));
+ g_assert_nonnull(stderr_data);
+ g_assert_nonnull(strstr(stderr_data, stderr_needle));
+}
+
+static void test_rpmi_machine_realize_off(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=off");
+ qtest_quit(qts);
+}
+
+static void test_rpmi_machine_rejects_too_many_harts(void)
+{
+ rpmi_expect_qemu_failure(
+ "-machine virt,rpmi=on -smp 513 -display none -S",
+ "max CPUs supported by machine 'virt' is 512");
+}
+
+static void test_rpmi_base_platform_info(void)
+{
+ static const char expected[] = "QEMU RISC-V virt RPMI";
+ QTestState *qts;
+ size_t i;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ rpmi_send_request(qts, RPMI_SRVGRP_BASE,
+ RPMI_BASE_SRV_GET_PLATFORM_INFO,
+ RPMI_MSG_NORMAL_REQUEST, NULL, 0);
+
+ rpmi_expect_ack(qts, RPMI_SRVGRP_BASE,
+ RPMI_BASE_SRV_GET_PLATFORM_INFO,
+ 2 * sizeof(uint32_t) + sizeof(expected));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, sizeof(expected));
+ for (i = 0; i < sizeof(expected); i++) {
+ g_assert_cmphex(qtest_readb(qts, RPMI_P2A_ACK_SLOT0 + 16 + i), ==,
+ expected[i]);
+ }
+
+ qtest_quit(qts);
+}
+
+static void rpmi_probe_group(QTestState *qts, uint32_t service_group,
+ bool present)
+{
+ rpmi_send_request(qts, RPMI_SRVGRP_BASE,
+ RPMI_BASE_SRV_PROBE_SERVICE_GROUP,
+ RPMI_MSG_NORMAL_REQUEST, &service_group, 1);
+
+ rpmi_expect_ack(qts, RPMI_SRVGRP_BASE,
+ RPMI_BASE_SRV_PROBE_SERVICE_GROUP,
+ 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ if (present) {
+ g_assert_cmphex(rpmi_response_word(qts, 1), !=, 0);
+ } else {
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, 0);
+ }
+}
+
+static void test_rpmi_base_probe_service_groups(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on,aia=aplic-imsic");
+ rpmi_probe_group(qts, RPMI_SRVGRP_BASE, true);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_SYSTEM_RESET, true);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_HSM, true);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_SYSTEM_SUSPEND, true);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_CPPC, false);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_SYSTEM_MSI, false);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_CLOCK, false);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_MANAGEMENT_MODE, false);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_LOGGING, false);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_sysreset_attrs(void)
+{
+ QTestState *qts;
+ uint32_t reset_type = RPMI_SYSRST_TYPE_SHUTDOWN;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES,
+ RPMI_MSG_NORMAL_REQUEST, &reset_type, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES, 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==,
+ RPMI_SYSRST_ATTRS_FLAGS_RESETTYPE);
+
+ qtest_system_reset(qts);
+ reset_type = RPMI_SYSRST_TYPE_COLD_REBOOT;
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES,
+ RPMI_MSG_NORMAL_REQUEST, &reset_type, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES, 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==,
+ RPMI_SYSRST_ATTRS_FLAGS_RESETTYPE);
+
+ qtest_system_reset(qts);
+ reset_type = RPMI_SYSRST_TYPE_INVALID;
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES,
+ RPMI_MSG_NORMAL_REQUEST, &reset_type, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES, 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_sysreset_shutdown(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ rpmi_send_sysreset(qts, RPMI_SYSRST_TYPE_SHUTDOWN,
+ RPMI_MSG_POSTED_REQUEST);
+ qtest_qmp_eventwait(qts, "SHUTDOWN");
+ qtest_quit(qts);
+}
+
+static void test_rpmi_sysreset_cold_reboot(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on -no-reboot");
+ rpmi_send_sysreset(qts, RPMI_SYSRST_TYPE_COLD_REBOOT,
+ RPMI_MSG_POSTED_REQUEST);
+ qtest_qmp_eventwait(qts, "SHUTDOWN");
+ qtest_quit(qts);
+}
+
+static void test_rpmi_sysreset_invalid_type(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ rpmi_send_sysreset(qts, RPMI_SYSRST_TYPE_INVALID,
+ RPMI_MSG_NORMAL_REQUEST);
+
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_SYSTEM_RESET, sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==,
+ RPMI_ERR_INVALID_PARAM);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_repeated_reset_after_traffic(void)
+{
+ QTestState *qts;
+ uint32_t reset_type = RPMI_SYSRST_TYPE_SHUTDOWN;
+
+ qts = qtest_init("-machine virt,rpmi=on,aia=aplic-imsic");
+ for (uint32_t i = 0; i < 5; i++) {
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES,
+ RPMI_MSG_NORMAL_REQUEST,
+ &reset_type, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES,
+ 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ qtest_system_reset(qts);
+ }
+ qtest_quit(qts);
+}
+
+static void test_rpmi_reset_clears_transport(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ rpmi_send_sysreset(qts, RPMI_SYSRST_TYPE_INVALID,
+ RPMI_MSG_NORMAL_REQUEST);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_SYSTEM_RESET, sizeof(uint32_t));
+
+ qtest_system_reset(qts);
+
+ g_assert_cmphex(qtest_readl(qts, RPMI_A2P_TAIL), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, RPMI_P2A_ACK_TAIL), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, RPMI_DOORBELL_BASE), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_doorbell_invalid_access(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ qtest_writeb(qts, RPMI_DOORBELL_BASE, 1);
+ qtest_writel(qts, RPMI_DOORBELL_BASE + 4, 1);
+ g_assert_cmphex(qtest_readl(qts, RPMI_DOORBELL_BASE), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_queue_bounds(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ qtest_writel(qts, RPMI_A2P_TAIL, 0x1000);
+ qtest_writel(qts, RPMI_DOORBELL_BASE, 1);
+ g_assert_cmphex(qtest_readl(qts, RPMI_A2P_HEAD), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, RPMI_P2A_ACK_TAIL), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_migration_blocked(void)
+{
+ QTestState *qts;
+ QDict *error;
+ const char *desc;
+
+ qts = qtest_init("-machine virt,rpmi=on -S");
+ error = qtest_qmp_assert_failure_ref(qts,
+ "{ 'execute': 'migrate',"
+ " 'arguments': { 'uri': 'exec:cat > /dev/null' } }");
+ desc = qdict_get_try_str(error, "desc");
+
+ g_assert_nonnull(desc);
+ g_assert_nonnull(strstr(desc, "non-migratable device"));
+ g_assert_nonnull(strstr(desc, "riscv-rpmi"));
+
+ qobject_unref(error);
+ qtest_quit(qts);
+}
+
+static void test_rpmi_hsm_hart_list(void)
+{
+ QTestState *qts;
+ uint32_t start_index = 0;
+
+ qts = qtest_init("-machine virt,rpmi=on -smp 4");
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_LIST,
+ RPMI_MSG_NORMAL_REQUEST, &start_index, 1);
+
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_LIST,
+ 7 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 2), ==, 4);
+ g_assert_cmphex(rpmi_response_word(qts, 3), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 4), ==, 1);
+ g_assert_cmphex(rpmi_response_word(qts, 5), ==, 2);
+ g_assert_cmphex(rpmi_response_word(qts, 6), ==, 3);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_hsm_multi_socket_hart_list(void)
+{
+ QTestState *qts;
+ uint32_t start_index = 0;
+
+ qts = qtest_init("-machine virt,rpmi=on "
+ "-smp 4,sockets=2,cores=2,threads=1");
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_LIST,
+ RPMI_MSG_NORMAL_REQUEST, &start_index, 1);
+
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_LIST,
+ 7 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 2), ==, 4);
+ g_assert_cmphex(rpmi_response_word(qts, 3), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 4), ==, 1);
+ g_assert_cmphex(rpmi_response_word(qts, 5), ==, 2);
+ g_assert_cmphex(rpmi_response_word(qts, 6), ==, 3);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_hsm_hart_status(void)
+{
+ QTestState *qts;
+ uint32_t hart_id = 3;
+
+ qts = qtest_init("-machine virt,rpmi=on -smp 4");
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_STATUS,
+ RPMI_MSG_NORMAL_REQUEST, &hart_id, 1);
+
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_STATUS,
+ 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==,
+ RPMI_HSM_HART_STATE_STARTED);
+
+ qtest_quit(qts);
+}
+
+static void rpmi_expect_hsm_status(QTestState *qts, uint32_t hart_id,
+ uint32_t expected_state)
+{
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_STATUS,
+ RPMI_MSG_NORMAL_REQUEST, &hart_id, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_STATUS,
+ 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, expected_state);
+}
+
+static void test_rpmi_hsm_hart_control(void)
+{
+ QTestState *qts;
+ uint32_t hart_id = 1;
+ uint32_t stop_request[] = { hart_id };
+ uint32_t start_request[] = { hart_id, 0x80000000, 0 };
+ uint32_t suspend_request[] = { hart_id, 0, 0x80001000, 0 };
+ uint32_t start_index = 0;
+ uint32_t suspend_type = 0;
+
+ qts = qtest_init("-machine virt,rpmi=on -smp 2");
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_SUSPEND_TYPES,
+ RPMI_MSG_NORMAL_REQUEST, &start_index, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_SUSPEND_TYPES,
+ 4 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 2), ==, 1);
+ g_assert_cmphex(rpmi_response_word(qts, 3), ==, 0);
+
+ qtest_system_reset(qts);
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_SUSPEND_INFO,
+ RPMI_MSG_NORMAL_REQUEST, &suspend_type, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_SUSPEND_INFO,
+ 6 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+
+ qtest_system_reset(qts);
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_HART_STOP,
+ RPMI_MSG_NORMAL_REQUEST, stop_request,
+ ARRAY_SIZE(stop_request));
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_HART_STOP,
+ sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ rpmi_expect_hsm_status(qts, hart_id, RPMI_HSM_HART_STATE_STOPPED);
+
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_HART_START,
+ RPMI_MSG_NORMAL_REQUEST, start_request,
+ ARRAY_SIZE(start_request));
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_HART_START,
+ sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ rpmi_expect_hsm_status(qts, hart_id, RPMI_HSM_HART_STATE_STARTED);
+
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_HART_SUSPEND,
+ RPMI_MSG_NORMAL_REQUEST, suspend_request,
+ ARRAY_SIZE(suspend_request));
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_HART_SUSPEND,
+ sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ rpmi_expect_hsm_status(qts, hart_id, RPMI_HSM_HART_STATE_SUSPENDED);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_syssusp_attrs_and_suspend(void)
+{
+ QTestState *qts;
+ uint32_t suspend_type = 0;
+ uint32_t suspend_request[] = { 0, 0, 0x80000000, 0 };
+
+ qts = qtest_init("-machine virt,rpmi=on -smp 1");
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_SUSPEND,
+ RPMI_SYSSUSP_SRV_GET_ATTRIBUTES,
+ RPMI_MSG_NORMAL_REQUEST, &suspend_type, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_SUSPEND,
+ RPMI_SYSSUSP_SRV_GET_ATTRIBUTES,
+ 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, 3);
+
+ qtest_system_reset(qts);
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_SUSPEND,
+ RPMI_SYSSUSP_SRV_SYSTEM_SUSPEND,
+ RPMI_MSG_NORMAL_REQUEST, suspend_request,
+ ARRAY_SIZE(suspend_request));
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_SUSPEND,
+ RPMI_SYSSUSP_SRV_SYSTEM_SUSPEND, sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ qtest_qmp_eventwait(qts, "SUSPEND");
+ qtest_qmp_assert_success(qts, "{ 'execute': 'system_wakeup' }");
+ qtest_qmp_eventwait(qts, "WAKEUP");
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ if (qtest_has_machine("virt")) {
+ qtest_add_func("/riscv/rpmi/machine/realize-off",
+ test_rpmi_machine_realize_off);
+ qtest_add_func("/riscv/rpmi/machine/rejects-too-many-harts",
+ test_rpmi_machine_rejects_too_many_harts);
+ qtest_add_func("/riscv/rpmi/base/platform-info",
+ test_rpmi_base_platform_info);
+ qtest_add_func("/riscv/rpmi/base/probe-service-groups",
+ test_rpmi_base_probe_service_groups);
+ qtest_add_func("/riscv/rpmi/sysreset/attrs",
+ test_rpmi_sysreset_attrs);
+ qtest_add_func("/riscv/rpmi/sysreset/shutdown",
+ test_rpmi_sysreset_shutdown);
+ qtest_add_func("/riscv/rpmi/sysreset/cold-reboot",
+ test_rpmi_sysreset_cold_reboot);
+ qtest_add_func("/riscv/rpmi/sysreset/invalid-type",
+ test_rpmi_sysreset_invalid_type);
+ qtest_add_func("/riscv/rpmi/reset/clears-transport",
+ test_rpmi_reset_clears_transport);
+ qtest_add_func("/riscv/rpmi/negative/doorbell-invalid-access",
+ test_rpmi_doorbell_invalid_access);
+ qtest_add_func("/riscv/rpmi/negative/queue-bounds",
+ test_rpmi_queue_bounds);
+ qtest_add_func("/riscv/rpmi/reset/repeated-after-traffic",
+ test_rpmi_repeated_reset_after_traffic);
+ qtest_add_func("/riscv/rpmi/migration/blocked",
+ test_rpmi_migration_blocked);
+ qtest_add_func("/riscv/rpmi/hsm/hart-list",
+ test_rpmi_hsm_hart_list);
+ qtest_add_func("/riscv/rpmi/hsm/multi-socket-hart-list",
+ test_rpmi_hsm_multi_socket_hart_list);
+ qtest_add_func("/riscv/rpmi/hsm/hart-status",
+ test_rpmi_hsm_hart_status);
+ qtest_add_func("/riscv/rpmi/hsm/hart-control",
+ test_rpmi_hsm_hart_control);
+ qtest_add_func("/riscv/rpmi/syssusp/attrs-and-suspend",
+ test_rpmi_syssusp_attrs_and_suspend);
+ }
+
+ return g_test_run();
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v2 10/10] tests/functional: add initial RPMI virt guest smoke
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 8:39 UTC (permalink / raw)
To: qemu-riscv; +Cc: qemu-devel, daniel.barboza, Subrahmanya Lingappa
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Boot an RPMI-enabled RISC-V virt guest and exercise the initial HSM,
system suspend, and system reset paths.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
tests/functional/riscv64/meson.build | 2 +
tests/functional/riscv64/test_rpmi_virt.py | 172 +++++++++++++++++++++
2 files changed, 174 insertions(+)
create mode 100644 tests/functional/riscv64/test_rpmi_virt.py
diff --git a/tests/functional/riscv64/meson.build b/tests/functional/riscv64/meson.build
index 5871211e89..619a0735fa 100644
--- a/tests/functional/riscv64/meson.build
+++ b/tests/functional/riscv64/meson.build
@@ -3,6 +3,7 @@
test_riscv64_timeouts = {
'boston' : 120,
'tuxrun' : 120,
+ 'rpmi_virt' : 300,
}
tests_riscv64_system_quick = [
@@ -15,4 +16,5 @@ tests_riscv64_system_thorough = [
'boston',
'sifive_u',
'tuxrun',
+ 'rpmi_virt',
]
diff --git a/tests/functional/riscv64/test_rpmi_virt.py b/tests/functional/riscv64/test_rpmi_virt.py
new file mode 100644
index 0000000000..c0f96f7d8d
--- /dev/null
+++ b/tests/functional/riscv64/test_rpmi_virt.py
@@ -0,0 +1,172 @@
+#!/usr/bin/env python3
+#
+# RISC-V virt RPMI Linux guest integration tests
+#
+# Copyright (c) 2026 Ventana Micro Systems Inc.
+# Copyright (c) 2026 Qualcomm Technologies, Inc.
+#
+# Author:
+# Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import os
+from pathlib import Path
+import subprocess
+import time
+
+from qemu_test import QemuSystemTest
+from qemu_test import exec_command
+from qemu_test import exec_command_and_wait_for_pattern
+from qemu_test import get_qemu_img
+from qemu_test import wait_for_console_pattern
+
+
+class RiscvVirtRpmiGuestTest(QemuSystemTest):
+ """Boot Linux and exercise RPMI-backed services through guest APIs.
+
+ This test intentionally keeps guest artifacts external. Set:
+
+ QEMU_TEST_RPMI_OPENSBI=/path/to/fw_jump.bin
+ QEMU_TEST_RPMI_KERNEL=/path/to/Image
+ QEMU_TEST_RPMI_ROOTFS=/path/to/rootfs.ext4
+
+ Exact RPMI packet command-ID coverage remains in riscv-rpmi qtests. This
+ functional test verifies Linux/OpenSBI integration paths visible from a
+ real guest without adding probe hooks to production RPMI sources.
+ """
+
+ timeout = 360
+ PROMPT = 'root@tuxtest:~#'
+
+ def _require_path(self, env_name):
+ value = os.getenv(env_name)
+ if not value:
+ self.skipTest(f'{env_name} is required')
+ path = Path(value)
+ if not path.exists():
+ self.skipTest(f'{env_name} does not exist: {path}')
+ return path
+
+ def setUp(self):
+ super().setUp()
+ self.opensbi = self._require_path('QEMU_TEST_RPMI_OPENSBI')
+ self.kernel = self._require_path('QEMU_TEST_RPMI_KERNEL')
+ self.rootfs = self._require_path('QEMU_TEST_RPMI_ROOTFS')
+ self.qemu_img = get_qemu_img(self)
+
+ def _create_rootfs_overlay(self):
+ overlay = Path(self.scratch_file('rootfs.qcow2'))
+ subprocess.run([
+ self.qemu_img, 'create', '-q', '-f', 'qcow2', '-F', 'raw',
+ '-b', str(self.rootfs), str(overlay),
+ ], check=True)
+ return overlay
+
+ def _boot_guest(self, smp=4):
+ self.set_machine('virt')
+ self.machine = 'virt,rpmi=on,aia=aplic-imsic'
+ rootfs_overlay = self._create_rootfs_overlay()
+
+ self.vm.set_console()
+ self.vm.add_args(
+ '-cpu', 'rv64',
+ '-smp', str(smp),
+ '-m', '1G',
+ '-bios', str(self.opensbi),
+ '-kernel', str(self.kernel),
+ '-append', 'printk.time=0 root=/dev/vda rw console=ttyS0 '
+ 'earlycon=sbi loglevel=8 no_console_suspend',
+ '-blockdev', 'driver=qcow2,file.driver=file,'
+ f'file.filename={rootfs_overlay},node-name=hd0',
+ '-device', 'virtio-blk-device,drive=hd0',
+ )
+ self.vm.launch()
+
+ def _expect_rpmi_firmware_and_linux(self):
+ patterns = [
+ 'Platform HSM Device : rpmi-hsm',
+ 'Platform Suspend Device : rpmi-system-suspend',
+ 'Standard SBI Extensions',
+ 'susp',
+ 'tuxtest login:',
+ ]
+ for pattern in patterns:
+ wait_for_console_pattern(self, pattern)
+ exec_command_and_wait_for_pattern(self, 'root', self.PROMPT)
+
+ def _run_marker_command(self, command, marker, failure_marker=None):
+ out = exec_command_and_wait_for_pattern(self, command, marker,
+ failure_message=failure_marker)
+ self.assertIn(marker.encode(), out)
+ return out
+
+ def _validate_hsm_boot_start(self, online_cpus):
+ self._run_marker_command(
+ 'echo RPMI_LINUX_HSM_START_BEGIN; '
+ 'cat /sys/devices/system/cpu/present; '
+ 'online=$(cat /sys/devices/system/cpu/online); '
+ 'echo "$online"; '
+ f'if [ "$online" = "{online_cpus}" ]; '
+ 'then echo RPMI_LINUX_HSM_START_PASS; '
+ 'else echo RPMI_LINUX_HSM_START_FAIL; fi',
+ 'RPMI_LINUX_HSM_START_PASS', 'RPMI_LINUX_HSM_START_FAIL')
+
+ def _validate_hsm_stop(self):
+ self._run_marker_command(
+ 'echo RPMI_LINUX_HSM_STOP_BEGIN; '
+ 'if [ -w /sys/devices/system/cpu/cpu1/online ]; then '
+ 'echo 0 > /sys/devices/system/cpu/cpu1/online && '
+ '[ "$(cat /sys/devices/system/cpu/online)" = 0 ] && '
+ 'echo RPMI_LINUX_HSM_STOP_PASS || echo RPMI_LINUX_HSM_STOP_FAIL; '
+ 'else echo RPMI_LINUX_HSM_STOP_FAIL; fi',
+ 'RPMI_LINUX_HSM_STOP_PASS', 'RPMI_LINUX_HSM_STOP_FAIL')
+
+ def _validate_system_suspend(self):
+ exec_command(
+ self,
+ 'echo RPMI_LINUX_SUSPEND_BEGIN; '
+ 'if grep -qw mem /sys/power/state; then '
+ 'echo mem > /sys/power/state; '
+ 'else echo RPMI_LINUX_SUSPEND_FAIL; fi')
+ wait_for_console_pattern(self, 'RPMI_LINUX_SUSPEND_BEGIN')
+ wait_for_console_pattern(self, 'PM: suspend entry')
+ self.vm.event_wait('SUSPEND', 10)
+ self.vm.qmp('system_wakeup')
+ self.vm.event_wait('WAKEUP', 10)
+ wait_for_console_pattern(self, 'PM: suspend exit')
+ wait_for_console_pattern(self, self.PROMPT)
+ self._run_marker_command(
+ 'marker=RPMI_LINUX_WAKEUP; echo ${marker}_PASS',
+ 'RPMI_LINUX_WAKEUP_PASS')
+
+ def _validate_reset_poweroff(self):
+ exec_command(self, 'echo RPMI_LINUX_RESET_POWEROFF_BEGIN; poweroff -f')
+ wait_for_console_pattern(self, 'RPMI_LINUX_RESET_POWEROFF_BEGIN')
+ wait_for_console_pattern(self, 'reboot: Power down')
+ self.vm.wait()
+
+ def test_linux_guest_service_enumeration(self):
+ self._boot_guest(smp=4)
+ self._expect_rpmi_firmware_and_linux()
+ self._validate_hsm_boot_start('0-3')
+
+ def test_linux_guest_hsm_stop(self):
+ self._boot_guest(smp=2)
+ self._expect_rpmi_firmware_and_linux()
+ self._validate_hsm_boot_start('0-1')
+ self._validate_hsm_stop()
+
+ def test_linux_guest_suspend(self):
+ self._boot_guest(smp=1)
+ self._expect_rpmi_firmware_and_linux()
+ self._validate_system_suspend()
+
+ def test_linux_guest_reset_poweroff(self):
+ self._boot_guest(smp=1)
+ self._expect_rpmi_firmware_and_linux()
+ self._validate_reset_poweroff()
+
+
+if __name__ == '__main__':
+ QemuSystemTest.main()
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH 00/10] riscv: add initial RPMI virt support
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 13:00 UTC (permalink / raw)
To: qemu-riscv; +Cc: Subrahmanya Lingappa
Add optional librpmi support and the initial RISC-V virt RPMI
implementation. This series introduces the shared-memory transport, FDT
helpers, opt-in virt wiring, and the Base, System Reset, HSM, and System
Suspend service groups with qtest and functional coverage.
The implementation targets the RISC-V Platform Management Interface
(RPMI) Specification v1.0 Ratified and uses librpmi as the backend.
Subrahmanya Lingappa (10):
build: add optional librpmi dependency
hw/misc: add RISC-V RPMI transport and Base support
hw/riscv: add RPMI FDT helpers
hw/riscv/virt: add opt-in RPMI base support
riscv: add RPMI system reset service
riscv: add RPMI HSM service
riscv: add RPMI system suspend service
docs/system/riscv: document initial virt RPMI support
tests/qtest: cover initial RISC-V RPMI services
tests/functional: add initial RPMI virt guest smoke
Kconfig.host | 3 +
MAINTAINERS | 3 +
docs/system/riscv/virt.rst | 50 ++
hw/misc/Kconfig | 4 +
hw/misc/meson.build | 6 +
hw/misc/riscv_rpmi.c | 558 +++++++++++++++++
hw/misc/riscv_rpmi_hsm.c | 287 +++++++++
hw/misc/riscv_rpmi_internal.h | 42 ++
hw/misc/riscv_rpmi_sysreset.c | 206 +++++++
hw/misc/riscv_rpmi_syssusp.c | 191 ++++++
hw/riscv/Kconfig | 1 +
hw/riscv/meson.build | 1 +
hw/riscv/rpmi-fdt.c | 103 ++++
hw/riscv/virt.c | 167 +++++
include/hw/misc/riscv_rpmi.h | 126 ++++
include/hw/riscv/rpmi-fdt.h | 41 ++
include/hw/riscv/virt.h | 3 +
meson.build | 14 +
meson_options.txt | 2 +
scripts/meson-buildoptions.sh | 3 +
tests/functional/riscv64/meson.build | 2 +
tests/functional/riscv64/test_rpmi_virt.py | 172 ++++++
tests/qtest/meson.build | 2 +
tests/qtest/riscv-rpmi-test.c | 684 +++++++++++++++++++++
24 files changed, 2671 insertions(+)
create mode 100644 hw/misc/riscv_rpmi.c
create mode 100644 hw/misc/riscv_rpmi_hsm.c
create mode 100644 hw/misc/riscv_rpmi_internal.h
create mode 100644 hw/misc/riscv_rpmi_sysreset.c
create mode 100644 hw/misc/riscv_rpmi_syssusp.c
create mode 100644 hw/riscv/rpmi-fdt.c
create mode 100644 include/hw/misc/riscv_rpmi.h
create mode 100644 include/hw/riscv/rpmi-fdt.h
create mode 100644 tests/functional/riscv64/test_rpmi_virt.py
create mode 100644 tests/qtest/riscv-rpmi-test.c
--
2.43.0
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH 01/10] build: add optional librpmi dependency
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 13:00 UTC (permalink / raw)
To: qemu-riscv
Cc: Subrahmanya Lingappa, Paolo Bonzini, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé,
Pierrick Bouvier, Alex Bennée, open list:All patches CC here
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add a meson option and config probe for librpmi so RISC-V machines can
opt into RPMI support when the library is available. Keep the dependency
optional so default builds without librpmi continue to work.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
Kconfig.host | 3 +++
meson.build | 14 ++++++++++++++
meson_options.txt | 2 ++
scripts/meson-buildoptions.sh | 3 +++
4 files changed, 22 insertions(+)
diff --git a/Kconfig.host b/Kconfig.host
index 933425c74b..6f98dadb2e 100644
--- a/Kconfig.host
+++ b/Kconfig.host
@@ -8,6 +8,9 @@ config LINUX
config LIBCBOR
bool
+config LIBRPMI
+ bool
+
config GNUTLS
bool
diff --git a/meson.build b/meson.build
index 19e123423b..103b4649b8 100644
--- a/meson.build
+++ b/meson.build
@@ -2005,6 +2005,17 @@ if lzo.found() and not cc.links('''
endif
endif
+librpmi = not_found
+if not get_option('librpmi').auto() or have_system
+ librpmi = dependency('librpmi', required: get_option('librpmi'),
+ method: 'pkg-config')
+ if librpmi.found() and not cc.has_header_symbol('librpmi.h',
+ 'rpmi_service_group_logging_create',
+ dependencies: librpmi)
+ error('librpmi support requires the RPMI logging service group API')
+ endif
+endif
+
numa = not_found
if not get_option('numa').auto() or have_system or have_tools
numa = cc.find_library('numa', has_headers: ['numa.h'],
@@ -2427,6 +2438,7 @@ config_host_data.set('CONFIG_LINUX', host_os == 'linux')
config_host_data.set('CONFIG_POSIX', host_os != 'windows')
config_host_data.set('CONFIG_WIN32', host_os == 'windows')
config_host_data.set('CONFIG_LZO', lzo.found())
+config_host_data.set('CONFIG_LIBRPMI', librpmi.found())
config_host_data.set('CONFIG_MPATH', mpathpersist.found())
config_host_data.set('CONFIG_BLKIO', blkio.found())
if blkio.found()
@@ -3258,6 +3270,7 @@ host_kconfig = \
(multiprocess_allowed ? ['CONFIG_MULTIPROCESS_ALLOWED=y'] : []) + \
(vfio_user_server_allowed ? ['CONFIG_VFIO_USER_SERVER_ALLOWED=y'] : []) + \
(hv_balloon ? ['CONFIG_HV_BALLOON_POSSIBLE=y'] : []) + \
+ (librpmi.found() ? ['CONFIG_LIBRPMI=y'] : []) + \
(have_rust ? ['CONFIG_HAVE_RUST=y'] : [])
ignored = [ 'TARGET_XML_FILES', 'TARGET_ABI_DIR' ]
@@ -4971,6 +4984,7 @@ summary_info += {'TPM support': have_tpm}
summary_info += {'IGVM support': igvm}
summary_info += {'libssh support': libssh}
summary_info += {'lzo support': lzo}
+summary_info += {'librpmi support': librpmi}
summary_info += {'snappy support': snappy}
summary_info += {'bzip2 support': libbzip2}
summary_info += {'lzfse support': liblzfse}
diff --git a/meson_options.txt b/meson_options.txt
index a07cb47d35..9f773a7886 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -204,6 +204,8 @@ option('lzfse', type : 'feature', value : 'auto',
description: 'lzfse support for DMG images')
option('lzo', type : 'feature', value : 'auto',
description: 'lzo compression support')
+option('librpmi', type : 'feature', value : 'auto',
+ description: 'RISC-V RPMI support')
option('pvg', type: 'feature', value: 'auto',
description: 'macOS paravirtualized graphics support')
option('rbd', type : 'feature', value : 'auto',
diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
index c003985047..e8a20068e7 100644
--- a/scripts/meson-buildoptions.sh
+++ b/scripts/meson-buildoptions.sh
@@ -140,6 +140,7 @@ meson_options_help() {
printf "%s\n" ' libkeyutils Linux keyutils support'
printf "%s\n" ' libnfs libnfs block device driver'
printf "%s\n" ' libpmem libpmem support'
+ printf "%s\n" ' librpmi RISC-V RPMI support'
printf "%s\n" ' libssh ssh block device support'
printf "%s\n" ' libudev Use libudev to enumerate host devices'
printf "%s\n" ' libusb libusb support for USB passthrough'
@@ -376,6 +377,8 @@ _meson_option_parse() {
--disable-libnfs) printf "%s" -Dlibnfs=disabled ;;
--enable-libpmem) printf "%s" -Dlibpmem=enabled ;;
--disable-libpmem) printf "%s" -Dlibpmem=disabled ;;
+ --enable-librpmi) printf "%s" -Dlibrpmi=enabled ;;
+ --disable-librpmi) printf "%s" -Dlibrpmi=disabled ;;
--enable-libssh) printf "%s" -Dlibssh=enabled ;;
--disable-libssh) printf "%s" -Dlibssh=disabled ;;
--enable-libudev) printf "%s" -Dlibudev=enabled ;;
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH 02/10] hw/misc: add RISC-V RPMI transport and Base support
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 13:00 UTC (permalink / raw)
To: qemu-riscv
Cc: Subrahmanya Lingappa, Paolo Bonzini, Palmer Dabbelt,
Alistair Francis, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei,
Chao Liu, open list:All patches CC here
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
RPMI (RISC-V Platform Management Interface) is the RISC-V non-ISA
platform management specification. It defines a shared-memory transport,
message protocol, and service groups for platform management.
Add the QEMU transport backend and Base service plumbing for virt using
librpmi.
This implementation targets the RISC-V Platform Management Interface
(RPMI) Specification v1.0 Ratified:
https://github.com/riscv-non-isa/riscv-rpmi/releases/download/v1.0/riscv-rpmi.pdf
The librpmi backend is maintained at:
https://github.com/riscv-software-src/librpmi
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
MAINTAINERS | 2 +
hw/misc/Kconfig | 4 +
hw/misc/meson.build | 3 +
hw/misc/riscv_rpmi.c | 476 ++++++++++++++++++++++++++++++++++
hw/misc/riscv_rpmi_internal.h | 24 ++
include/hw/misc/riscv_rpmi.h | 90 +++++++
6 files changed, 599 insertions(+)
create mode 100644 hw/misc/riscv_rpmi.c
create mode 100644 hw/misc/riscv_rpmi_internal.h
create mode 100644 include/hw/misc/riscv_rpmi.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 93df53d87f..8e91a31757 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -359,6 +359,8 @@ F: target/riscv/
F: hw/char/riscv_htif.c
F: hw/riscv/
F: hw/intc/riscv*
+F: hw/misc/riscv_rpmi*
+F: include/hw/misc/riscv_rpmi.h
F: include/hw/char/riscv_htif.h
F: include/hw/riscv/
F: common-user/host/riscv*
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 1543ee6653..998944c342 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -258,3 +258,7 @@ config XLNX_ZYNQ_DDRC
bool
source macio/Kconfig
+
+config RISCV_RPMI
+ bool
+ depends on LIBRPMI
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 23265f6035..e7c2ebe28f 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -168,3 +168,6 @@ system_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa_ec.c'))
# HPPA devices
system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c'))
+system_ss.add(when: 'CONFIG_RISCV_RPMI', if_true: [files(
+ 'riscv_rpmi.c',
+), librpmi])
diff --git a/hw/misc/riscv_rpmi.c b/hw/misc/riscv_rpmi.c
new file mode 100644
index 0000000000..c4b115b659
--- /dev/null
+++ b/hw/misc/riscv_rpmi.c
@@ -0,0 +1,476 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI transport device.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "exec/cpu-common.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/misc/riscv_rpmi.h"
+#include "hw/misc/riscv_rpmi_internal.h"
+#include "migration/vmstate.h"
+#include "system/address-spaces.h"
+#include "system/runstate.h"
+#include "librpmi.h"
+#include "librpmi_env.h"
+
+void *rpmi_env_zalloc(rpmi_size_t size)
+{
+ return g_malloc0(size);
+}
+
+void rpmi_env_free(void *ptr)
+{
+ g_free(ptr);
+}
+
+void rpmi_env_writel(rpmi_uint64_t addr, rpmi_uint32_t val)
+{
+ cpu_physical_memory_write(addr, &val, sizeof(val));
+}
+
+static bool riscv_rpmi_addr_in_range(uint64_t addr, uint32_t len,
+ uint64_t base, uint64_t size)
+{
+ return len && size >= len && addr >= base && addr - base <= size - len;
+}
+
+static enum rpmi_error shmem_qemu_read(void *priv, rpmi_uint64_t addr,
+ void *buf, rpmi_uint32_t len)
+{
+ RiscvRpmiState *s = RISCV_RPMI(priv);
+
+ if (!riscv_rpmi_addr_in_range(addr, len, s->shmem_base, s->shmem_size) ||
+ addr != (rpmi_uint64_t)addr || len != (rpmi_uint32_t)len) {
+ return RPMI_ERR_INVALID_ADDR;
+ }
+
+ cpu_physical_memory_read(addr, buf, len);
+ return RPMI_SUCCESS;
+}
+
+static enum rpmi_error shmem_qemu_write(void *priv, rpmi_uint64_t addr,
+ const void *buf, rpmi_uint32_t len)
+{
+ RiscvRpmiState *s = RISCV_RPMI(priv);
+
+ if (!riscv_rpmi_addr_in_range(addr, len, s->shmem_base, s->shmem_size) ||
+ addr != (rpmi_uint64_t)addr || len != (rpmi_uint32_t)len) {
+ return RPMI_ERR_INVALID_ADDR;
+ }
+
+ cpu_physical_memory_write(addr, buf, len);
+ return RPMI_SUCCESS;
+}
+
+static enum rpmi_error shmem_qemu_fill(void *priv, rpmi_uint64_t addr,
+ char ch, rpmi_uint32_t len)
+{
+ while (len--) {
+ shmem_qemu_write(priv, addr++, &ch, sizeof(ch));
+ }
+
+ return RPMI_SUCCESS;
+}
+
+const struct rpmi_shmem_platform_ops rpmi_shmem_qemu_ops = {
+ .read = shmem_qemu_read,
+ .write = shmem_qemu_write,
+ .fill = shmem_qemu_fill,
+};
+
+static uint64_t riscv_rpmi_read(void *opaque, hwaddr offset, unsigned int size)
+{
+ RiscvRpmiState *s = opaque;
+
+ if (offset != 0 || size != 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid read at 0x%" HWADDR_PRIx " size %u\n",
+ __func__, offset, size);
+ return 0;
+ }
+
+ return s->doorbell;
+}
+
+static bool riscv_rpmi_transport_indices_valid(RiscvRpmiState *s);
+
+static void riscv_rpmi_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned int size)
+{
+ RiscvRpmiState *s = opaque;
+
+ if (offset != 0 || size != 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid write at 0x%" HWADDR_PRIx " size %u\n",
+ __func__, offset, size);
+ return;
+ }
+
+ s->doorbell = value;
+
+ if (value == 1) {
+ if (s->context && riscv_rpmi_transport_indices_valid(s)) {
+ rpmi_context_process_a2p_request(s->context);
+ rpmi_context_process_all_events(s->context);
+ }
+ s->doorbell = 0;
+ }
+}
+
+static const MemoryRegionOps riscv_rpmi_ops = {
+ .read = riscv_rpmi_read,
+ .write = riscv_rpmi_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static bool riscv_rpmi_queue_indices_valid(RiscvRpmiState *s,
+ uint64_t queue_base,
+ uint32_t queue_size)
+{
+ uint8_t *shmem = memory_region_get_ram_ptr(&s->shmem);
+ uint32_t data_slots = queue_size / RPMI_QUEUE_SLOT_SIZE - 2;
+ uint32_t head;
+ uint32_t tail;
+
+ head = ldl_le_p(shmem + queue_base);
+ tail = ldl_le_p(shmem + queue_base + RPMI_QUEUE_SLOT_SIZE);
+ if (head >= data_slots || tail >= data_slots) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid queue indices head %u tail %u slots %u\n",
+ __func__, head, tail, data_slots);
+ return false;
+ }
+
+ return true;
+}
+
+static bool riscv_rpmi_transport_indices_valid(RiscvRpmiState *s)
+{
+ if (!s->has_shmem || !s->a2p_req_size) {
+ return false;
+ }
+
+ if (!riscv_rpmi_queue_indices_valid(s, 0, s->a2p_req_size)) {
+ return false;
+ }
+
+ return riscv_rpmi_queue_indices_valid(s, s->a2p_req_size,
+ s->a2p_req_size);
+}
+
+static void riscv_rpmi_configure_base(RiscvRpmiState *s,
+ const RiscvRpmiConfig *cfg)
+{
+ s->platform_info = g_strdup(cfg->platform_info);
+ s->services = cfg->services;
+ s->service_count = cfg->service_count;
+
+ if (cfg->hart_count) {
+ s->hart_count = cfg->hart_count;
+ if (cfg->hart_ids) {
+ s->hart_ids = g_memdup2(cfg->hart_ids,
+ cfg->hart_count * sizeof(*cfg->hart_ids));
+ }
+ }
+}
+
+static void riscv_rpmi_init(Object *obj)
+{
+ RiscvRpmiState *s = RISCV_RPMI(obj);
+
+ memory_region_init_io(&s->mmio, obj, &riscv_rpmi_ops, s,
+ TYPE_RISCV_RPMI, RPMI_DBREG_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+}
+
+static void riscv_rpmi_reset(DeviceState *dev)
+{
+ RiscvRpmiState *s = RISCV_RPMI(dev);
+
+ s->doorbell = 0;
+
+ if (s->has_shmem) {
+ memset(memory_region_get_ram_ptr(&s->shmem), 0, s->shmem_size);
+ memory_region_set_dirty(&s->shmem, 0, s->shmem_size);
+ }
+
+}
+
+static void riscv_rpmi_cleanup(RiscvRpmiState *s)
+{
+
+
+ if (s->context) {
+ rpmi_context_destroy(s->context);
+ s->context = NULL;
+ }
+
+ if (s->transport) {
+ rpmi_transport_shmem_destroy(s->transport);
+ s->transport = NULL;
+ }
+
+ if (s->rpmi_shmem) {
+ rpmi_shmem_destroy(s->rpmi_shmem);
+ s->rpmi_shmem = NULL;
+ }
+
+ if (s->has_shmem) {
+ memory_region_del_subregion(get_system_memory(), &s->shmem);
+ s->has_shmem = false;
+ }
+}
+
+bool riscv_rpmi_service_enabled(RiscvRpmiState *s, RiscvRpmiServiceKind kind)
+{
+ uint32_t i;
+
+ for (i = 0; i < s->service_count; i++) {
+ if (s->services[i].kind == kind) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool riscv_rpmi_validate_config(RiscvRpmiState *s, Error **errp)
+{
+ uint64_t queue_bytes;
+
+ if (!s->shmem_size) {
+ error_setg(errp, "RPMI shared memory size must be non-zero");
+ return false;
+ }
+
+ if (s->shmem_base & (RPMI_QUEUE_SLOT_SIZE - 1)) {
+ error_setg(errp, "RPMI shared memory base must be %u-byte aligned",
+ RPMI_QUEUE_SLOT_SIZE);
+ return false;
+ }
+
+ if (!s->a2p_req_size) {
+ error_setg(errp, "RPMI A2P request queue size must be non-zero");
+ return false;
+ }
+
+ if (!s->hart_count || !s->hart_ids) {
+ error_setg(errp, "RPMI transport requires hart IDs");
+ return false;
+ }
+
+ for (uint32_t i = 0; i < s->hart_count; i++) {
+ for (uint32_t j = i + 1; j < s->hart_count; j++) {
+ if (s->hart_ids[i] == s->hart_ids[j]) {
+ error_setg(errp, "RPMI hart ID %u is duplicated",
+ s->hart_ids[i]);
+ return false;
+ }
+ }
+ }
+
+ if (s->service_count && !s->services) {
+ error_setg(errp, "RPMI service count requires service descriptors");
+ return false;
+ }
+
+ if (s->a2p_req_size % RPMI_QUEUE_SLOT_SIZE) {
+ error_setg(errp,
+ "RPMI A2P request queue size must be a multiple of %u",
+ RPMI_QUEUE_SLOT_SIZE);
+ return false;
+ }
+
+ if (s->p2a_req_size % RPMI_QUEUE_SLOT_SIZE) {
+ error_setg(errp,
+ "RPMI P2A request queue size must be a multiple of %u",
+ RPMI_QUEUE_SLOT_SIZE);
+ return false;
+ }
+
+ queue_bytes = 2 * (uint64_t)s->a2p_req_size +
+ 2 * (uint64_t)s->p2a_req_size;
+ if (queue_bytes > s->shmem_size) {
+ error_setg(errp,
+ "RPMI shared memory size 0x%" PRIx64
+ " is too small for queues 0x%" PRIx64,
+ s->shmem_size, queue_bytes);
+ return false;
+ }
+
+ return true;
+}
+
+static bool riscv_rpmi_add_service_group(RiscvRpmiState *s,
+ const RiscvRpmiServiceConfig *service,
+ Error **errp)
+{
+ switch (service->kind) {
+ default:
+ error_setg(errp, "unsupported RPMI service kind %d", service->kind);
+ return false;
+ }
+}
+
+static bool riscv_rpmi_init_services(RiscvRpmiState *s, Error **errp)
+{
+ uint32_t i;
+
+ for (i = 0; i < s->service_count; i++) {
+ if (!riscv_rpmi_add_service_group(s, &s->services[i], errp)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool riscv_rpmi_init_context(RiscvRpmiState *s, Error **errp)
+{
+ const char *platform_info = s->platform_info ?: RPMI_PLAT_INFO;
+
+ s->rpmi_shmem = rpmi_shmem_create("rpmi-shmem", s->shmem_base,
+ s->shmem_size,
+ &rpmi_shmem_qemu_ops, s);
+ if (!s->rpmi_shmem) {
+ error_setg(errp, "failed to create RPMI shared memory backend");
+ return false;
+ }
+
+ s->transport = rpmi_transport_shmem_create("rpmi-shmem-transport",
+ RPMI_QUEUE_SLOT_SIZE,
+ s->a2p_req_size,
+ s->p2a_req_size,
+ s->rpmi_shmem);
+ if (!s->transport) {
+ error_setg(errp, "failed to create RPMI shared memory transport");
+ return false;
+ }
+
+ s->context = rpmi_context_create("rpmi-virt", s->transport,
+ RPMI_SRVGRP_ID_MAX_COUNT,
+ RPMI_PRIVILEGE_M_MODE,
+ strlen(platform_info) + 1,
+ platform_info);
+ if (!s->context) {
+ error_setg(errp, "failed to create RPMI context");
+ return false;
+ }
+
+ return riscv_rpmi_init_services(s, errp);
+}
+
+static void riscv_rpmi_realize(DeviceState *dev, Error **errp)
+{
+ RiscvRpmiState *s = RISCV_RPMI(dev);
+ g_autofree char *name = NULL;
+
+ if (!riscv_rpmi_validate_config(s, errp)) {
+ return;
+ }
+
+ name = g_strdup_printf("rpmi-shmem@%" PRIx64, s->shmem_base);
+ if (!memory_region_init_ram(&s->shmem, OBJECT(dev), name, s->shmem_size,
+ errp)) {
+ return;
+ }
+
+ memory_region_add_subregion(get_system_memory(), s->shmem_base, &s->shmem);
+ s->has_shmem = true;
+
+ if (!riscv_rpmi_init_context(s, errp)) {
+ riscv_rpmi_cleanup(s);
+ }
+}
+
+static void riscv_rpmi_unrealize(DeviceState *dev)
+{
+ riscv_rpmi_cleanup(RISCV_RPMI(dev));
+}
+
+static const VMStateDescription riscv_rpmi_vmstate = {
+ .name = TYPE_RISCV_RPMI,
+ .unmigratable = 1,
+};
+
+static const Property riscv_rpmi_properties[] = {
+ DEFINE_PROP_UINT64("shmem-base", RiscvRpmiState, shmem_base, 0),
+ DEFINE_PROP_UINT64("shmem-size", RiscvRpmiState, shmem_size, 0),
+ DEFINE_PROP_UINT32("a2p-req-size", RiscvRpmiState, a2p_req_size, 0),
+ DEFINE_PROP_UINT32("p2a-req-size", RiscvRpmiState, p2a_req_size, 0),
+};
+
+static void riscv_rpmi_finalize(Object *obj)
+{
+ RiscvRpmiState *s = RISCV_RPMI(obj);
+
+ g_free(s->platform_info);
+ g_free(s->hart_ids);
+}
+
+static void riscv_rpmi_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = riscv_rpmi_realize;
+ dc->unrealize = riscv_rpmi_unrealize;
+ dc->vmsd = &riscv_rpmi_vmstate;
+ device_class_set_legacy_reset(dc, riscv_rpmi_reset);
+ device_class_set_props(dc, riscv_rpmi_properties);
+}
+
+static const TypeInfo riscv_rpmi_info = {
+ .name = TYPE_RISCV_RPMI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RiscvRpmiState),
+ .instance_init = riscv_rpmi_init,
+ .instance_finalize = riscv_rpmi_finalize,
+ .class_init = riscv_rpmi_class_init,
+};
+
+static void riscv_rpmi_register_types(void)
+{
+ type_register_static(&riscv_rpmi_info);
+}
+
+type_init(riscv_rpmi_register_types)
+
+DeviceState *riscv_rpmi_create(const RiscvRpmiConfig *cfg, Error **errp)
+{
+ DeviceState *dev;
+ RiscvRpmiState *s;
+
+ if (!cfg) {
+ error_setg(errp, "missing RPMI configuration");
+ return NULL;
+ }
+
+ dev = qdev_new(TYPE_RISCV_RPMI);
+ qdev_prop_set_uint64(dev, "shmem-base", cfg->shmem_base);
+ qdev_prop_set_uint64(dev, "shmem-size", cfg->shmem_size);
+ qdev_prop_set_uint32(dev, "a2p-req-size", cfg->a2p_req_size);
+ qdev_prop_set_uint32(dev, "p2a-req-size", cfg->p2a_req_size);
+
+ s = RISCV_RPMI(dev);
+ riscv_rpmi_configure_base(s, cfg);
+
+ if (!sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), errp)) {
+ return NULL;
+ }
+
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, cfg->doorbell_base);
+ return dev;
+}
diff --git a/hw/misc/riscv_rpmi_internal.h b/hw/misc/riscv_rpmi_internal.h
new file mode 100644
index 0000000000..447c0acb55
--- /dev/null
+++ b/hw/misc/riscv_rpmi_internal.h
@@ -0,0 +1,24 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI internal service helpers.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#ifndef HW_MISC_RISCV_RPMI_INTERNAL_H
+#define HW_MISC_RISCV_RPMI_INTERNAL_H
+
+#include "qapi/error.h"
+#include "hw/misc/riscv_rpmi.h"
+#include "librpmi.h"
+
+#define RPMI_PLAT_INFO "QEMU RISC-V RPMI"
+
+extern const struct rpmi_shmem_platform_ops rpmi_shmem_qemu_ops;
+bool riscv_rpmi_service_enabled(RiscvRpmiState *s,
+ RiscvRpmiServiceKind kind);
+
+#endif
diff --git a/include/hw/misc/riscv_rpmi.h b/include/hw/misc/riscv_rpmi.h
new file mode 100644
index 0000000000..cb2658e57e
--- /dev/null
+++ b/include/hw/misc/riscv_rpmi.h
@@ -0,0 +1,90 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI definitions shared by RPMI transport and FDT helpers.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#ifndef HW_MISC_RISCV_RPMI_H
+#define HW_MISC_RISCV_RPMI_H
+
+#include "exec/hwaddr.h"
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+#include "qemu/notify.h"
+
+#define RPMI_QUEUE_SLOT_SIZE 64
+#define RPMI_DBREG_SIZE 0x1000
+
+#define RPMI_ALL_NUM_QUEUES 4
+#define RPMI_A2P_NUM_QUEUES 2
+#define RPMI_ALL_NUM_REGS (RPMI_ALL_NUM_QUEUES + 1)
+#define RPMI_A2P_NUM_REGS (RPMI_A2P_NUM_QUEUES + 1)
+
+#define VIRT_RPMI_A2P_REQ_SIZE (16 * RPMI_QUEUE_SLOT_SIZE)
+#define VIRT_RPMI_P2A_REQ_SIZE 0
+
+
+#define TYPE_RISCV_RPMI "riscv-rpmi"
+OBJECT_DECLARE_SIMPLE_TYPE(RiscvRpmiState, RISCV_RPMI)
+
+struct rpmi_context;
+struct rpmi_service_group;
+struct rpmi_shmem;
+struct rpmi_transport;
+
+typedef enum RiscvRpmiServiceKind {
+ RISCV_RPMI_SERVICE_INVALID = 0,
+} RiscvRpmiServiceKind;
+
+typedef struct RiscvRpmiServiceConfig {
+ RiscvRpmiServiceKind kind;
+ const char *node_name;
+ const char *compatible;
+ uint32_t service_group;
+ bool has_mpxy_channel;
+ uint32_t mpxy_channel;
+} RiscvRpmiServiceConfig;
+
+typedef struct RiscvRpmiConfig {
+ hwaddr doorbell_base;
+ hwaddr shmem_base;
+ hwaddr shmem_size;
+ uint32_t a2p_req_size;
+ uint32_t p2a_req_size;
+ const char *platform_info;
+
+ const uint32_t *hart_ids;
+ uint32_t hart_count;
+ const RiscvRpmiServiceConfig *services;
+ uint32_t service_count;
+} RiscvRpmiConfig;
+
+struct RiscvRpmiState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ MemoryRegion shmem;
+ uint64_t shmem_base;
+ uint64_t shmem_size;
+ uint32_t a2p_req_size;
+ uint32_t p2a_req_size;
+ char *platform_info;
+
+ uint32_t *hart_ids;
+ uint32_t hart_count;
+ const RiscvRpmiServiceConfig *services;
+ uint32_t service_count;
+ uint32_t doorbell;
+ struct rpmi_shmem *rpmi_shmem;
+ struct rpmi_transport *transport;
+ struct rpmi_context *context;
+ bool has_shmem;
+};
+
+DeviceState *riscv_rpmi_create(const RiscvRpmiConfig *cfg, Error **errp);
+
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH 03/10] hw/riscv: add RPMI FDT helpers
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 13:00 UTC (permalink / raw)
To: qemu-riscv
Cc: Subrahmanya Lingappa, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu,
open list:All patches CC here
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add helper routines for describing RPMI shared-memory mailbox transports
and service child nodes in the RISC-V virt device tree.
The helpers keep RPMI node construction in one place so the virt machine
can add service groups without open-coding compatible strings, channel
IDs, interrupts, and shared-memory references at each call site.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
hw/riscv/meson.build | 1 +
hw/riscv/rpmi-fdt.c | 103 ++++++++++++++++++++++++++++++++++++
include/hw/riscv/rpmi-fdt.h | 30 +++++++++++
3 files changed, 134 insertions(+)
create mode 100644 hw/riscv/rpmi-fdt.c
create mode 100644 include/hw/riscv/rpmi-fdt.h
diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build
index b70a054579..529ec273fd 100644
--- a/hw/riscv/meson.build
+++ b/hw/riscv/meson.build
@@ -3,6 +3,7 @@ riscv_ss.add(files('boot.c'))
riscv_ss.add(files('fdt-common.c'))
riscv_ss.add(when: 'CONFIG_RISCV_NUMA', if_true: files('numa.c'))
riscv_ss.add(files('riscv_hart.c'))
+riscv_ss.add(files('rpmi-fdt.c'))
riscv_ss.add(when: 'CONFIG_OPENTITAN', if_true: files('opentitan.c'))
riscv_ss.add(when: 'CONFIG_RISCV_VIRT', if_true: files('virt.c'))
riscv_ss.add(when: 'CONFIG_SHAKTI_C', if_true: files('shakti_c.c'))
diff --git a/hw/riscv/rpmi-fdt.c b/hw/riscv/rpmi-fdt.c
new file mode 100644
index 0000000000..b2b3daea1c
--- /dev/null
+++ b/hw/riscv/rpmi-fdt.c
@@ -0,0 +1,103 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI device-tree helpers
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bswap.h"
+#include "hw/riscv/rpmi-fdt.h"
+#include "hw/misc/riscv_rpmi.h"
+#include "system/device_tree.h"
+
+void riscv_rpmi_fdt_add_mbox(void *fdt,
+ const RiscvRpmiFdtMboxConfig *cfg,
+ uint32_t *phandle,
+ uint32_t *mbox_handle)
+{
+ g_autofree char *name = NULL;
+ hwaddr a2p_req_base, p2a_ack_base, p2a_req_base, a2p_ack_base;
+ static const char * const regnames_all[RPMI_ALL_NUM_REGS] = {
+ "a2p-req", "p2a-ack", "p2a-req", "a2p-ack", "a2p-doorbell"
+ };
+ static const char * const regnames_a2p[RPMI_A2P_NUM_REGS] = {
+ "a2p-req", "p2a-ack", "a2p-doorbell"
+ };
+
+ a2p_req_base = cfg->shmem_base;
+ p2a_ack_base = a2p_req_base + cfg->a2p_req_size;
+ p2a_req_base = p2a_ack_base + cfg->a2p_req_size;
+ a2p_ack_base = p2a_req_base + cfg->p2a_req_size;
+
+ *mbox_handle = (*phandle)++;
+ name = g_strdup_printf("/soc/mailbox@%" HWADDR_PRIx, cfg->shmem_base);
+ qemu_fdt_add_subnode(fdt, name);
+ qemu_fdt_setprop_string(fdt, name, "compatible", "riscv,rpmi-shmem-mbox");
+ qemu_fdt_setprop_cell(fdt, name, "riscv,slot-size", RPMI_QUEUE_SLOT_SIZE);
+ qemu_fdt_setprop_cell(fdt, name, "#mbox-cells", 1);
+
+ if (cfg->p2a_req_size) {
+ qemu_fdt_setprop_string_array(fdt, name, "reg-names",
+ (char **)®names_all,
+ ARRAY_SIZE(regnames_all));
+ qemu_fdt_setprop_cells(fdt, name, "reg",
+ (uint32_t)(a2p_req_base >> 32), (uint32_t)a2p_req_base,
+ 0x0, cfg->a2p_req_size,
+ (uint32_t)(p2a_ack_base >> 32), (uint32_t)p2a_ack_base,
+ 0x0, cfg->a2p_req_size,
+ (uint32_t)(p2a_req_base >> 32), (uint32_t)p2a_req_base,
+ 0x0, cfg->p2a_req_size,
+ (uint32_t)(a2p_ack_base >> 32), (uint32_t)a2p_ack_base,
+ 0x0, cfg->p2a_req_size,
+ (uint32_t)(cfg->doorbell_base >> 32), (uint32_t)cfg->doorbell_base,
+ 0x0, cfg->doorbell_size);
+ } else {
+ qemu_fdt_setprop_string_array(fdt, name, "reg-names",
+ (char **)®names_a2p,
+ ARRAY_SIZE(regnames_a2p));
+ qemu_fdt_setprop_cells(fdt, name, "reg",
+ (uint32_t)(a2p_req_base >> 32), (uint32_t)a2p_req_base,
+ 0x0, cfg->a2p_req_size,
+ (uint32_t)(p2a_ack_base >> 32), (uint32_t)p2a_ack_base,
+ 0x0, cfg->a2p_req_size,
+ (uint32_t)(cfg->doorbell_base >> 32), (uint32_t)cfg->doorbell_base,
+ 0x0, cfg->doorbell_size);
+ }
+
+ qemu_fdt_setprop_cells(fdt, name, "phandle", *mbox_handle);
+}
+
+void riscv_rpmi_fdt_add_service(void *fdt, hwaddr shmem_base,
+ const char *node_name,
+ const char *compatible,
+ uint32_t mbox_handle,
+ uint32_t service_group,
+ bool has_mpxy_channel,
+ uint32_t mpxy_channel)
+{
+ g_autofree char *name = g_strdup_printf("/soc/mailbox@%" HWADDR_PRIx "/%s",
+ shmem_base, node_name);
+
+ qemu_fdt_add_subnode(fdt, name);
+ qemu_fdt_setprop_string(fdt, name, "compatible", compatible);
+ qemu_fdt_setprop_cells(fdt, name, "mboxes", mbox_handle, service_group);
+ if (has_mpxy_channel) {
+ qemu_fdt_setprop_cell(fdt, name, "riscv,sbi-mpxy-channel-id",
+ mpxy_channel);
+ }
+}
+
+void riscv_rpmi_fdt_add_service_node(void *fdt, hwaddr shmem_base,
+ const RiscvRpmiServiceConfig *service,
+ uint32_t mbox_handle)
+{
+ riscv_rpmi_fdt_add_service(fdt, shmem_base, service->node_name,
+ service->compatible, mbox_handle,
+ service->service_group,
+ service->has_mpxy_channel,
+ service->mpxy_channel);
+}
diff --git a/include/hw/riscv/rpmi-fdt.h b/include/hw/riscv/rpmi-fdt.h
new file mode 100644
index 0000000000..f053ae5d57
--- /dev/null
+++ b/include/hw/riscv/rpmi-fdt.h
@@ -0,0 +1,30 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI device-tree helpers
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#ifndef HW_RISCV_RPMI_FDT_H
+#define HW_RISCV_RPMI_FDT_H
+
+#include "exec/hwaddr.h"
+#include "hw/misc/riscv_rpmi.h"
+
+typedef struct RiscvRpmiFdtMboxConfig {
+ hwaddr shmem_base;
+ hwaddr doorbell_base;
+ uint32_t a2p_req_size;
+ uint32_t p2a_req_size;
+ uint32_t doorbell_size;
+} RiscvRpmiFdtMboxConfig;
+
+void riscv_rpmi_fdt_add_mbox(void *fdt,
+ const RiscvRpmiFdtMboxConfig *cfg,
+ uint32_t *phandle,
+ uint32_t *mbox_handle);
+
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH 04/10] hw/riscv/virt: add opt-in RPMI base support
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 13:00 UTC (permalink / raw)
To: qemu-riscv
Cc: Subrahmanya Lingappa, Paolo Bonzini, Palmer Dabbelt,
Alistair Francis, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei,
Chao Liu, open list:All patches CC here
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add the virt machine rpmi=on/off option and wire the initial RPMI Base
transport into machine realization.
The option remains disabled by default and requires TCG plus librpmi.
Invalid accelerator/build combinations fail during machine
initialization. When enabled, virt allocates the RPMI shared-memory
transport, creates the RPMI device, configures Base service metadata,
and emits the corresponding device-tree nodes for firmware discovery.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
hw/riscv/Kconfig | 1 +
hw/riscv/virt.c | 96 ++++++++++++++++++++++++++++++++++++
include/hw/misc/riscv_rpmi.h | 10 ++++
include/hw/riscv/virt.h | 3 ++
4 files changed, 110 insertions(+)
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index 54e41a6afc..4e25be113a 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -49,6 +49,7 @@ config RISCV_VIRT
imply VIRTIO_VGA
imply TEST_DEVICES
imply TPM_TIS_SYSBUS
+ imply RISCV_RPMI
select DEVICE_TREE
select RISCV_NUMA
select GOLDFISH_RTC
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index 33775a61fd..e52ccfae1f 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -45,6 +45,8 @@
#include "hw/intc/riscv_aplic.h"
#include "hw/intc/sifive_plic.h"
#include "hw/misc/sifive_test.h"
+#include "hw/misc/riscv_rpmi.h"
+#include "hw/riscv/rpmi-fdt.h"
#include "hw/core/platform-bus.h"
#include "chardev/char.h"
#include "system/device_tree.h"
@@ -97,6 +99,8 @@ static const MemMapEntry virt_memmap[] = {
[VIRT_UART0] = { 0x10000000, 0x100 },
[VIRT_VIRTIO] = { 0x10001000, 0x1000 },
[VIRT_FW_CFG] = { 0x10100000, 0x18 },
+ [VIRT_RPMI_SHMEM] = { 0x10200000, 0x20000 },
+ [VIRT_RPMI_DOORBELL] = { 0x10230000, 0x1000 },
[VIRT_FLASH] = { 0x20000000, 0x4000000 },
[VIRT_IMSIC_M] = { 0x24000000, VIRT_IMSIC_MAX_SIZE },
[VIRT_IMSIC_S] = { 0x28000000, VIRT_IMSIC_MAX_SIZE },
@@ -1002,6 +1006,40 @@ static void create_fdt_iommu(RISCVVirtState *s, uint16_t bdf)
s->pci_iommu_bdf = bdf;
}
+
+static RiscvRpmiConfig virt_rpmi_config(RISCVVirtState *s,
+ const uint32_t *hart_ids,
+ uint32_t hart_count)
+{
+ return (RiscvRpmiConfig) {
+ .doorbell_base = s->memmap[VIRT_RPMI_DOORBELL].base,
+ .shmem_base = s->memmap[VIRT_RPMI_SHMEM].base,
+ .shmem_size = s->memmap[VIRT_RPMI_SHMEM].size,
+ .a2p_req_size = VIRT_RPMI_A2P_REQ_SIZE,
+ .p2a_req_size = VIRT_RPMI_P2A_REQ_SIZE,
+ .platform_info = "QEMU RISC-V virt RPMI",
+ .hart_ids = hart_ids,
+ .hart_count = hart_count,
+ };
+}
+
+static void create_fdt_rpmi(RISCVVirtState *s, uint32_t *phandle,
+ uint32_t msi_phandle)
+{
+ RiscvRpmiConfig rpmi_cfg = virt_rpmi_config(s, NULL, 0);
+ uint32_t rpmi_mbox_handle;
+ RiscvRpmiFdtMboxConfig cfg = {
+ .shmem_base = rpmi_cfg.shmem_base,
+ .doorbell_base = rpmi_cfg.doorbell_base,
+ .a2p_req_size = rpmi_cfg.a2p_req_size,
+ .p2a_req_size = rpmi_cfg.p2a_req_size,
+ .doorbell_size = s->memmap[VIRT_RPMI_DOORBELL].size,
+ };
+
+ riscv_rpmi_fdt_add_mbox(MACHINE(s)->fdt, &cfg, phandle,
+ &rpmi_mbox_handle);
+}
+
static void finalize_fdt(RISCVVirtState *s)
{
uint32_t phandle = 1, irq_mmio_phandle = 1, msi_pcie_phandle = 1;
@@ -1021,6 +1059,10 @@ static void finalize_fdt(RISCVVirtState *s)
create_fdt_pcie(s, irq_pcie_phandle, msi_pcie_phandle,
iommu_sys_phandle);
+ if (s->have_rpmi) {
+ create_fdt_rpmi(s, &phandle, msi_pcie_phandle);
+ }
+
create_fdt_reset(s, &phandle);
create_fdt_uart(s, irq_mmio_phandle);
@@ -1410,6 +1452,18 @@ static void virt_machine_init(MachineState *machine)
exit(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 = virtio_irqchip = pcie_irqchip = NULL;
for (i = 0; i < socket_count; i++) {
@@ -1552,6 +1606,25 @@ static void virt_machine_init(MachineState *machine)
/* SiFive Test MMIO device */
sifive_test_create(s->memmap[VIRT_TEST].base);
+ 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 = virt_rpmi_config(s, rpmi_hart_ids, machine->smp.cpus);
+ if (!riscv_rpmi_create(&rpmi_cfg, &local_err)) {
+ error_report_err(local_err);
+ exit(1);
+ }
+ }
+
/* VirtIO MMIO devices */
for (i = 0; i < VIRTIO_COUNT; i++) {
sysbus_create_simple("virtio-mmio",
@@ -1636,6 +1709,7 @@ static void virt_machine_instance_init(Object *obj)
s->oem_id = g_strndup(ACPI_BUILD_APPNAME6, 6);
s->oem_table_id = g_strndup(ACPI_BUILD_APPNAME8, 8);
+ s->have_rpmi = false;
s->acpi = ON_OFF_AUTO_AUTO;
s->iommu_sys = ON_OFF_AUTO_AUTO;
}
@@ -1710,6 +1784,21 @@ static void virt_set_aclint(Object *obj, bool value, Error **errp)
s->have_aclint = value;
}
+
+static bool virt_get_rpmi(Object *obj, Error **errp)
+{
+ RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
+
+ return s->have_rpmi;
+}
+
+static void virt_set_rpmi(Object *obj, bool value, Error **errp)
+{
+ RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
+
+ s->have_rpmi = value;
+}
+
bool virt_is_iommu_sys_enabled(RISCVVirtState *s)
{
return s->iommu_sys == ON_OFF_AUTO_ON;
@@ -1831,6 +1920,13 @@ static void virt_machine_class_init(ObjectClass *oc, const void *data)
"enable/disable emulating "
"ACLINT devices");
+
+ object_class_property_add_bool(oc, "rpmi", virt_get_rpmi,
+ virt_set_rpmi);
+ object_class_property_set_description(oc, "rpmi",
+ "Set on/off to enable/disable "
+ "RISC-V RPMI devices");
+
object_class_property_add_str(oc, "aia", virt_get_aia,
virt_set_aia);
object_class_property_set_description(oc, "aia",
diff --git a/include/hw/misc/riscv_rpmi.h b/include/hw/misc/riscv_rpmi.h
index cb2658e57e..b5d8a32f9b 100644
--- a/include/hw/misc/riscv_rpmi.h
+++ b/include/hw/misc/riscv_rpmi.h
@@ -85,6 +85,16 @@ struct RiscvRpmiState {
bool has_shmem;
};
+#ifdef CONFIG_LIBRPMI
DeviceState *riscv_rpmi_create(const RiscvRpmiConfig *cfg, Error **errp);
+#else
+static inline DeviceState *riscv_rpmi_create(const RiscvRpmiConfig *cfg,
+ Error **errp)
+{
+ (void)cfg;
+ (void)errp;
+ return NULL;
+}
+#endif
#endif
diff --git a/include/hw/riscv/virt.h b/include/hw/riscv/virt.h
index 18a2a323a3..f7c48613bf 100644
--- a/include/hw/riscv/virt.h
+++ b/include/hw/riscv/virt.h
@@ -55,6 +55,7 @@ struct RISCVVirtState {
int fdt_size;
bool have_aclint;
+ bool have_rpmi;
RISCVVirtAIAType aia_type;
int aia_guests;
char *oem_id;
@@ -79,6 +80,8 @@ enum {
VIRT_UART0,
VIRT_VIRTIO,
VIRT_FW_CFG,
+ VIRT_RPMI_SHMEM,
+ VIRT_RPMI_DOORBELL,
VIRT_IMSIC_M,
VIRT_IMSIC_S,
VIRT_FLASH,
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH 05/10] riscv: add RPMI system reset service
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 13:00 UTC (permalink / raw)
To: qemu-riscv
Cc: Subrahmanya Lingappa, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu,
open list:All patches CC here
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add a QEMU implementation of the RPMI System Reset service group.
The service advertises shutdown and cold reboot reset types, validates
guest requests, and dispatches accepted requests through the machine
reset and shutdown callbacks. This lets firmware use RPMI reset commands
while keeping the board-specific reset policy in the RISC-V virt machine.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
hw/misc/meson.build | 1 +
hw/misc/riscv_rpmi.c | 38 ++++++-
hw/misc/riscv_rpmi_internal.h | 9 ++
hw/misc/riscv_rpmi_sysreset.c | 206 ++++++++++++++++++++++++++++++++++
hw/riscv/virt.c | 40 +++++++
include/hw/misc/riscv_rpmi.h | 13 ++-
include/hw/riscv/rpmi-fdt.h | 11 ++
7 files changed, 313 insertions(+), 5 deletions(-)
create mode 100644 hw/misc/riscv_rpmi_sysreset.c
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index e7c2ebe28f..bca7819ee0 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -170,4 +170,5 @@ system_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa_ec.c'))
system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c'))
system_ss.add(when: 'CONFIG_RISCV_RPMI', if_true: [files(
'riscv_rpmi.c',
+ 'riscv_rpmi_sysreset.c',
), librpmi])
diff --git a/hw/misc/riscv_rpmi.c b/hw/misc/riscv_rpmi.c
index c4b115b659..7a3f570ee7 100644
--- a/hw/misc/riscv_rpmi.c
+++ b/hw/misc/riscv_rpmi.c
@@ -175,6 +175,8 @@ static void riscv_rpmi_configure_base(RiscvRpmiState *s,
const RiscvRpmiConfig *cfg)
{
s->platform_info = g_strdup(cfg->platform_info);
+ s->machine_ops = cfg->machine_ops;
+ s->machine_opaque = cfg->machine_opaque;
s->services = cfg->services;
s->service_count = cfg->service_count;
@@ -211,7 +213,9 @@ static void riscv_rpmi_reset(DeviceState *dev)
static void riscv_rpmi_cleanup(RiscvRpmiState *s)
{
-
+ for (uint32_t i = ARRAY_SIZE(riscv_rpmi_service_ops); i > 0; i--) {
+ riscv_rpmi_service_ops[i - 1].remove(s);
+ }
if (s->context) {
rpmi_context_destroy(s->context);
@@ -247,6 +251,30 @@ bool riscv_rpmi_service_enabled(RiscvRpmiState *s, RiscvRpmiServiceKind kind)
return false;
}
+bool riscv_rpmi_context_add_group(RiscvRpmiState *s,
+ struct rpmi_service_group *group,
+ const char *name,
+ Error **errp)
+{
+ enum rpmi_error rc;
+
+ rc = rpmi_context_add_group(s->context, group);
+ if (rc != RPMI_SUCCESS) {
+ error_setg(errp, "failed to add RPMI %s service group: %d", name, rc);
+ return false;
+ }
+
+ return true;
+}
+
+void riscv_rpmi_context_remove_group(RiscvRpmiState *s,
+ struct rpmi_service_group *group)
+{
+ if (s->context && group) {
+ rpmi_context_remove_group(s->context, group);
+ }
+}
+
static bool riscv_rpmi_validate_config(RiscvRpmiState *s, Error **errp)
{
uint64_t queue_bytes;
@@ -318,11 +346,15 @@ static bool riscv_rpmi_add_service_group(RiscvRpmiState *s,
const RiscvRpmiServiceConfig *service,
Error **errp)
{
- switch (service->kind) {
- default:
+ const RiscvRpmiServiceOps *ops;
+
+ ops = riscv_rpmi_service_ops_by_kind(service->kind);
+ if (!ops) {
error_setg(errp, "unsupported RPMI service kind %d", service->kind);
return false;
}
+
+ return ops->add(s, errp);
}
static bool riscv_rpmi_init_services(RiscvRpmiState *s, Error **errp)
diff --git a/hw/misc/riscv_rpmi_internal.h b/hw/misc/riscv_rpmi_internal.h
index 447c0acb55..fe09804e69 100644
--- a/hw/misc/riscv_rpmi_internal.h
+++ b/hw/misc/riscv_rpmi_internal.h
@@ -20,5 +20,14 @@
extern const struct rpmi_shmem_platform_ops rpmi_shmem_qemu_ops;
bool riscv_rpmi_service_enabled(RiscvRpmiState *s,
RiscvRpmiServiceKind kind);
+bool riscv_rpmi_context_add_group(RiscvRpmiState *s,
+ struct rpmi_service_group *group,
+ const char *name,
+ Error **errp);
+void riscv_rpmi_context_remove_group(RiscvRpmiState *s,
+ struct rpmi_service_group *group);
+
+bool riscv_rpmi_sysreset_add(RiscvRpmiState *s, Error **errp);
+void riscv_rpmi_sysreset_remove(RiscvRpmiState *s);
#endif
diff --git a/hw/misc/riscv_rpmi_sysreset.c b/hw/misc/riscv_rpmi_sysreset.c
new file mode 100644
index 0000000000..da6ef4c566
--- /dev/null
+++ b/hw/misc/riscv_rpmi_sysreset.c
@@ -0,0 +1,206 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI System Reset service.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#include "qemu/osdep.h"
+#include "riscv_rpmi_internal.h"
+#include "qemu/log.h"
+#include "librpmi_env.h"
+
+typedef struct RiscvRpmiSysresetType {
+ uint32_t type;
+ void (*action)(RiscvRpmiState *s);
+} RiscvRpmiSysresetType;
+
+typedef struct RiscvRpmiSysresetGroup {
+ struct rpmi_service_group group;
+ struct rpmi_service services[RPMI_SYSRST_SRV_ID_MAX];
+ RiscvRpmiState *rpmi;
+} RiscvRpmiSysresetGroup;
+
+static void riscv_rpmi_sysreset_reboot(RiscvRpmiState *s)
+{
+ const RiscvRpmiMachineOps *ops = s->machine_ops;
+
+ if (ops && ops->system_reset) {
+ ops->system_reset(s->machine_opaque);
+ }
+}
+
+static void riscv_rpmi_sysreset_shutdown(RiscvRpmiState *s)
+{
+ const RiscvRpmiMachineOps *ops = s->machine_ops;
+
+ if (ops && ops->system_shutdown) {
+ ops->system_shutdown(s->machine_opaque);
+ }
+}
+
+static const RiscvRpmiSysresetType riscv_rpmi_sysreset_types[] = {
+ {
+ .type = RPMI_SYSRST_TYPE_SHUTDOWN,
+ .action = riscv_rpmi_sysreset_shutdown,
+ }, {
+ .type = RPMI_SYSRST_TYPE_COLD_REBOOT,
+ .action = riscv_rpmi_sysreset_reboot,
+ },
+};
+
+static const RiscvRpmiSysresetType *riscv_rpmi_sysreset_type_by_id(
+ uint32_t reset_type)
+{
+ for (uint32_t index = 0; index < ARRAY_SIZE(riscv_rpmi_sysreset_types);
+ index++) {
+ if (riscv_rpmi_sysreset_types[index].type == reset_type) {
+ return &riscv_rpmi_sysreset_types[index];
+ }
+ }
+
+ return NULL;
+}
+
+static void riscv_rpmi_do_system_reset(RiscvRpmiState *s,
+ rpmi_uint32_t reset_type)
+{
+ const RiscvRpmiSysresetType *type;
+
+ type = riscv_rpmi_sysreset_type_by_id(reset_type);
+ if (type) {
+ type->action(s);
+ return;
+ }
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: unsupported reset type %u\n",
+ __func__, reset_type);
+}
+
+static enum rpmi_error riscv_rpmi_sysreset_get_attributes(
+ struct rpmi_service_group *group, struct rpmi_service *service,
+ struct rpmi_transport *trans, rpmi_uint16_t request_data_len,
+ const rpmi_uint8_t *request_data, rpmi_uint16_t *response_data_len,
+ rpmi_uint8_t *response_data)
+{
+ uint32_t reset_type = ldl_le_p(request_data);
+ uint32_t *resp = (uint32_t *)response_data;
+
+ *response_data_len = 2 * sizeof(*resp);
+ stl_le_p(&resp[0], RPMI_SUCCESS);
+ stl_le_p(&resp[1], riscv_rpmi_sysreset_type_by_id(reset_type) ?
+ RPMI_SYSRST_ATTRS_FLAGS_RESETTYPE : 0);
+
+ return RPMI_SUCCESS;
+}
+
+static enum rpmi_error riscv_rpmi_sysreset_do_reset(
+ struct rpmi_service_group *group, struct rpmi_service *service,
+ struct rpmi_transport *trans, rpmi_uint16_t request_data_len,
+ const rpmi_uint8_t *request_data, rpmi_uint16_t *response_data_len,
+ rpmi_uint8_t *response_data)
+{
+ RiscvRpmiSysresetGroup *sysreset = group->priv;
+ uint32_t reset_type = ldl_le_p(request_data);
+ uint32_t *resp = (uint32_t *)response_data;
+
+ *response_data_len = sizeof(*resp);
+
+ if (!riscv_rpmi_sysreset_type_by_id(reset_type)) {
+ stl_le_p(resp, (uint32_t)RPMI_ERR_INVALID_PARAM);
+ return RPMI_SUCCESS;
+ }
+
+ stl_le_p(resp, RPMI_SUCCESS);
+ riscv_rpmi_do_system_reset(sysreset->rpmi, reset_type);
+ return RPMI_SUCCESS;
+}
+
+static const struct rpmi_service riscv_rpmi_sysreset_services[] = {
+ [RPMI_SYSRST_SRV_ENABLE_NOTIFICATION] = {
+ .service_id = RPMI_SYSRST_SRV_ENABLE_NOTIFICATION,
+ .min_a2p_request_datalen = 8,
+ },
+ [RPMI_SYSRST_SRV_GET_ATTRIBUTES] = {
+ .service_id = RPMI_SYSRST_SRV_GET_ATTRIBUTES,
+ .min_a2p_request_datalen = 4,
+ .process_a2p_request = riscv_rpmi_sysreset_get_attributes,
+ },
+ [RPMI_SYSRST_SRV_SYSTEM_RESET] = {
+ .service_id = RPMI_SYSRST_SRV_SYSTEM_RESET,
+ .min_a2p_request_datalen = 4,
+ .process_a2p_request = riscv_rpmi_sysreset_do_reset,
+ },
+};
+
+static struct rpmi_service_group *riscv_rpmi_sysreset_create(RiscvRpmiState *s)
+{
+ RiscvRpmiSysresetGroup *sysreset;
+ struct rpmi_service_group *group;
+
+ sysreset = g_new0(RiscvRpmiSysresetGroup, 1);
+ sysreset->rpmi = s;
+ memcpy(sysreset->services, riscv_rpmi_sysreset_services,
+ sizeof(riscv_rpmi_sysreset_services));
+
+ group = &sysreset->group;
+ group->name = "sysreset";
+ group->servicegroup_id = RPMI_SRVGRP_SYSTEM_RESET;
+ group->max_service_id = RPMI_SYSRST_SRV_ID_MAX;
+ group->servicegroup_version =
+ RPMI_BASE_VERSION(RPMI_SPEC_VERSION_MAJOR, RPMI_SPEC_VERSION_MINOR);
+ group->privilege_level_bitmap = RPMI_PRIVILEGE_M_MODE_MASK;
+ group->services = sysreset->services;
+ group->lock = rpmi_env_alloc_lock();
+ group->priv = sysreset;
+
+ return group;
+}
+
+static void riscv_rpmi_sysreset_destroy(struct rpmi_service_group *group)
+{
+ if (!group) {
+ return;
+ }
+
+ rpmi_env_free_lock(group->lock);
+ g_free(group->priv);
+}
+
+bool riscv_rpmi_sysreset_add(RiscvRpmiState *s, Error **errp)
+{
+ struct rpmi_service_group *group;
+
+ if (s->sysreset_group) {
+ error_setg(errp, "duplicate RPMI sysreset service descriptor");
+ return false;
+ }
+
+ group = riscv_rpmi_sysreset_create(s);
+ if (!group) {
+ error_setg(errp, "failed to create RPMI sysreset service group");
+ return false;
+ }
+
+ if (!riscv_rpmi_context_add_group(s, group, "sysreset", errp)) {
+ riscv_rpmi_sysreset_destroy(group);
+ return false;
+ }
+
+ s->sysreset_group = group;
+ return true;
+}
+
+void riscv_rpmi_sysreset_remove(RiscvRpmiState *s)
+{
+ if (!s->sysreset_group) {
+ return;
+ }
+
+ riscv_rpmi_context_remove_group(s, s->sysreset_group);
+ riscv_rpmi_sysreset_destroy(s->sysreset_group);
+ s->sysreset_group = NULL;
+}
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index e52ccfae1f..273a9aa04f 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -1007,6 +1007,35 @@ static void create_fdt_iommu(RISCVVirtState *s, uint16_t bdf)
}
+static const RiscvRpmiServiceConfig virt_rpmi_services[] = {
+ {
+ .kind = RISCV_RPMI_SERVICE_SYSRESET,
+ .node_name = "sysreset",
+ .compatible = "riscv,rpmi-system-reset",
+ .service_group = RISCV_RPMI_SRVGRP_SYSTEM_RESET,
+ },
+};
+
+static uint32_t virt_rpmi_service_count(RISCVVirtState *s)
+{
+ return ARRAY_SIZE(virt_rpmi_services);
+}
+
+static void virt_rpmi_system_reset(void *opaque)
+{
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+}
+
+static void virt_rpmi_system_shutdown(void *opaque)
+{
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+}
+
+static const RiscvRpmiMachineOps virt_rpmi_machine_ops = {
+ .system_reset = virt_rpmi_system_reset,
+ .system_shutdown = virt_rpmi_system_shutdown,
+};
+
static RiscvRpmiConfig virt_rpmi_config(RISCVVirtState *s,
const uint32_t *hart_ids,
uint32_t hart_count)
@@ -1018,8 +1047,12 @@ static RiscvRpmiConfig virt_rpmi_config(RISCVVirtState *s,
.a2p_req_size = VIRT_RPMI_A2P_REQ_SIZE,
.p2a_req_size = VIRT_RPMI_P2A_REQ_SIZE,
.platform_info = "QEMU RISC-V virt RPMI",
+ .machine_ops = &virt_rpmi_machine_ops,
+ .machine_opaque = s,
.hart_ids = hart_ids,
.hart_count = hart_count,
+ .services = virt_rpmi_services,
+ .service_count = virt_rpmi_service_count(s),
};
}
@@ -1028,6 +1061,7 @@ static void create_fdt_rpmi(RISCVVirtState *s, uint32_t *phandle,
{
RiscvRpmiConfig rpmi_cfg = virt_rpmi_config(s, NULL, 0);
uint32_t rpmi_mbox_handle;
+ uint32_t i;
RiscvRpmiFdtMboxConfig cfg = {
.shmem_base = rpmi_cfg.shmem_base,
.doorbell_base = rpmi_cfg.doorbell_base,
@@ -1038,6 +1072,12 @@ static void create_fdt_rpmi(RISCVVirtState *s, uint32_t *phandle,
riscv_rpmi_fdt_add_mbox(MACHINE(s)->fdt, &cfg, phandle,
&rpmi_mbox_handle);
+
+ for (i = 0; i < rpmi_cfg.service_count; i++) {
+ riscv_rpmi_fdt_add_service_node(MACHINE(s)->fdt, rpmi_cfg.shmem_base,
+ &rpmi_cfg.services[i],
+ rpmi_mbox_handle);
+ }
}
static void finalize_fdt(RISCVVirtState *s)
diff --git a/include/hw/misc/riscv_rpmi.h b/include/hw/misc/riscv_rpmi.h
index b5d8a32f9b..d11db87731 100644
--- a/include/hw/misc/riscv_rpmi.h
+++ b/include/hw/misc/riscv_rpmi.h
@@ -27,6 +27,7 @@
#define VIRT_RPMI_A2P_REQ_SIZE (16 * RPMI_QUEUE_SLOT_SIZE)
#define VIRT_RPMI_P2A_REQ_SIZE 0
+#define RISCV_RPMI_SRVGRP_SYSTEM_RESET 3
#define TYPE_RISCV_RPMI "riscv-rpmi"
OBJECT_DECLARE_SIMPLE_TYPE(RiscvRpmiState, RISCV_RPMI)
@@ -38,8 +39,13 @@ struct rpmi_transport;
typedef enum RiscvRpmiServiceKind {
RISCV_RPMI_SERVICE_INVALID = 0,
+ RISCV_RPMI_SERVICE_SYSRESET,
} RiscvRpmiServiceKind;
+typedef struct RiscvRpmiMachineOps {
+ void (*system_reset)(void *opaque);
+ void (*system_shutdown)(void *opaque);
+} RiscvRpmiMachineOps;
typedef struct RiscvRpmiServiceConfig {
RiscvRpmiServiceKind kind;
const char *node_name;
@@ -56,7 +62,8 @@ typedef struct RiscvRpmiConfig {
uint32_t a2p_req_size;
uint32_t p2a_req_size;
const char *platform_info;
-
+ const RiscvRpmiMachineOps *machine_ops;
+ void *machine_opaque;
const uint32_t *hart_ids;
uint32_t hart_count;
const RiscvRpmiServiceConfig *services;
@@ -73,7 +80,9 @@ struct RiscvRpmiState {
uint32_t a2p_req_size;
uint32_t p2a_req_size;
char *platform_info;
-
+ const RiscvRpmiMachineOps *machine_ops;
+ void *machine_opaque;
+ struct rpmi_service_group *sysreset_group;
uint32_t *hart_ids;
uint32_t hart_count;
const RiscvRpmiServiceConfig *services;
diff --git a/include/hw/riscv/rpmi-fdt.h b/include/hw/riscv/rpmi-fdt.h
index f053ae5d57..af0e8a65ab 100644
--- a/include/hw/riscv/rpmi-fdt.h
+++ b/include/hw/riscv/rpmi-fdt.h
@@ -26,5 +26,16 @@ void riscv_rpmi_fdt_add_mbox(void *fdt,
const RiscvRpmiFdtMboxConfig *cfg,
uint32_t *phandle,
uint32_t *mbox_handle);
+void riscv_rpmi_fdt_add_service(void *fdt, hwaddr shmem_base,
+ const char *node_name,
+ const char *compatible,
+ uint32_t mbox_handle,
+ uint32_t service_group,
+ bool has_mpxy_channel,
+ uint32_t mpxy_channel);
+
+void riscv_rpmi_fdt_add_service_node(void *fdt, hwaddr shmem_base,
+ const RiscvRpmiServiceConfig *service,
+ uint32_t mbox_handle);
#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH 06/10] riscv: add RPMI HSM service
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 13:00 UTC (permalink / raw)
To: qemu-riscv
Cc: Subrahmanya Lingappa, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu,
open list:All patches CC here
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add the RPMI Hart State Management service group for RISC-V virt.
The service exposes the configured virt hart IDs to librpmi and keeps
per-hart RPMI hardware state in the RPMI device. Start, stop, and
suspend requests update QEMU CPU halted state and resume/kick vCPUs as
needed so firmware can exercise RPMI HSM operations against the emulated
topology.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
hw/misc/meson.build | 1 +
hw/misc/riscv_rpmi.c | 46 ++++++
hw/misc/riscv_rpmi_hsm.c | 280 ++++++++++++++++++++++++++++++++++
hw/misc/riscv_rpmi_internal.h | 4 +
hw/riscv/virt.c | 6 +
include/hw/misc/riscv_rpmi.h | 6 +
6 files changed, 343 insertions(+)
create mode 100644 hw/misc/riscv_rpmi_hsm.c
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index bca7819ee0..fc5b11ed57 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -171,4 +171,5 @@ system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c'))
system_ss.add(when: 'CONFIG_RISCV_RPMI', if_true: [files(
'riscv_rpmi.c',
'riscv_rpmi_sysreset.c',
+ 'riscv_rpmi_hsm.c',
), librpmi])
diff --git a/hw/misc/riscv_rpmi.c b/hw/misc/riscv_rpmi.c
index 7a3f570ee7..820b1d5d63 100644
--- a/hw/misc/riscv_rpmi.c
+++ b/hw/misc/riscv_rpmi.c
@@ -171,6 +171,49 @@ static bool riscv_rpmi_transport_indices_valid(RiscvRpmiState *s)
s->a2p_req_size);
}
+typedef struct RiscvRpmiServiceOps {
+ RiscvRpmiServiceKind kind;
+ void (*configure)(RiscvRpmiState *s, const RiscvRpmiConfig *cfg);
+ bool (*add)(RiscvRpmiState *s, Error **errp);
+ void (*remove)(RiscvRpmiState *s);
+} RiscvRpmiServiceOps;
+
+static const RiscvRpmiServiceOps riscv_rpmi_service_ops[] = {
+ {
+ .kind = RISCV_RPMI_SERVICE_SYSRESET,
+ .add = riscv_rpmi_sysreset_add,
+ .remove = riscv_rpmi_sysreset_remove,
+ }, {
+ .kind = RISCV_RPMI_SERVICE_HSM,
+ .add = riscv_rpmi_hsm_add,
+ .remove = riscv_rpmi_hsm_remove,
+ },
+};
+
+static const RiscvRpmiServiceOps *riscv_rpmi_service_ops_by_kind(
+ RiscvRpmiServiceKind kind)
+{
+ for (uint32_t i = 0; i < ARRAY_SIZE(riscv_rpmi_service_ops); i++) {
+ if (riscv_rpmi_service_ops[i].kind == kind) {
+ return &riscv_rpmi_service_ops[i];
+ }
+ }
+
+ return NULL;
+}
+
+static void riscv_rpmi_configure_services(RiscvRpmiState *s,
+ const RiscvRpmiConfig *cfg)
+{
+ for (uint32_t i = 0; i < ARRAY_SIZE(riscv_rpmi_service_ops); i++) {
+ const RiscvRpmiServiceOps *ops = &riscv_rpmi_service_ops[i];
+
+ if (ops->configure && riscv_rpmi_service_enabled(s, ops->kind)) {
+ ops->configure(s, cfg);
+ }
+ }
+}
+
static void riscv_rpmi_configure_base(RiscvRpmiState *s,
const RiscvRpmiConfig *cfg)
{
@@ -187,6 +230,8 @@ static void riscv_rpmi_configure_base(RiscvRpmiState *s,
cfg->hart_count * sizeof(*cfg->hart_ids));
}
}
+
+ riscv_rpmi_configure_services(s, cfg);
}
static void riscv_rpmi_init(Object *obj)
@@ -209,6 +254,7 @@ static void riscv_rpmi_reset(DeviceState *dev)
memory_region_set_dirty(&s->shmem, 0, s->shmem_size);
}
+ riscv_rpmi_hsm_reset(s);
}
static void riscv_rpmi_cleanup(RiscvRpmiState *s)
diff --git a/hw/misc/riscv_rpmi_hsm.c b/hw/misc/riscv_rpmi_hsm.c
new file mode 100644
index 0000000000..9a9859b10c
--- /dev/null
+++ b/hw/misc/riscv_rpmi_hsm.c
@@ -0,0 +1,280 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI HSM service.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#include "qemu/osdep.h"
+#include "riscv_rpmi_internal.h"
+#include "hw/core/cpu.h"
+#include "librpmi_env.h"
+
+static const struct rpmi_hsm_suspend_type riscv_rpmi_hsm_suspend_types[] = {
+ {
+ .type = 0,
+ .info = {
+ .flags = 0,
+ .entry_latency_us = 0,
+ .exit_latency_us = 0,
+ .wakeup_latency_us = 0,
+ .min_residency_us = 0,
+ },
+ },
+};
+
+typedef struct RiscvRpmiHsmStateTransition {
+ enum rpmi_hart_hw_state state;
+ bool halted;
+ bool resume;
+} RiscvRpmiHsmStateTransition;
+
+static const RiscvRpmiHsmStateTransition riscv_rpmi_hsm_state_transitions[] = {
+ {
+ .state = RPMI_HART_HW_STATE_STARTED,
+ .halted = false,
+ .resume = true,
+ }, {
+ .state = RPMI_HART_HW_STATE_STOPPED,
+ .halted = true,
+ .resume = false,
+ }, {
+ .state = RPMI_HART_HW_STATE_SUSPENDED,
+ .halted = true,
+ .resume = false,
+ },
+};
+
+static const RiscvRpmiHsmStateTransition *riscv_rpmi_hsm_state_transition(
+ enum rpmi_hart_hw_state state)
+{
+ for (uint32_t index = 0;
+ index < ARRAY_SIZE(riscv_rpmi_hsm_state_transitions); index++) {
+ if (riscv_rpmi_hsm_state_transitions[index].state == state) {
+ return &riscv_rpmi_hsm_state_transitions[index];
+ }
+ }
+
+ return NULL;
+}
+
+static CPUState *riscv_rpmi_hart_cpu(RiscvRpmiState *s, uint32_t hart_index)
+{
+ if (hart_index >= s->hart_count || !s->hart_ids) {
+ return NULL;
+ }
+
+ return cpu_by_arch_id(s->hart_ids[hart_index]);
+}
+
+static void riscv_rpmi_hsm_set_hw_state(RiscvRpmiState *s,
+ uint32_t hart_index,
+ enum rpmi_hart_hw_state state)
+{
+ CPUState *cpu = riscv_rpmi_hart_cpu(s, hart_index);
+ const RiscvRpmiHsmStateTransition *transition;
+
+ if (hart_index >= s->hart_count || !s->hsm_hw_states) {
+ return;
+ }
+
+ s->hsm_hw_states[hart_index] = state;
+ if (!cpu) {
+ return;
+ }
+
+ transition = riscv_rpmi_hsm_state_transition(state);
+ if (!transition) {
+ return;
+ }
+
+ cpu->halted = transition->halted;
+ if (transition->resume) {
+ cpu_resume(cpu);
+ } else {
+ qemu_cpu_kick(cpu);
+ }
+}
+
+static enum rpmi_error riscv_rpmi_hsm_start_prepare(
+ void *priv, rpmi_uint32_t hart_index, rpmi_uint64_t start_addr)
+{
+ RiscvRpmiState *s = priv;
+ CPUState *cpu = riscv_rpmi_hart_cpu(s, hart_index);
+
+ if (!cpu) {
+ return RPMI_ERR_INVALID_PARAM;
+ }
+
+ riscv_rpmi_hsm_set_hw_state(s, hart_index, RPMI_HART_HW_STATE_STARTED);
+ return RPMI_SUCCESS;
+}
+
+static void riscv_rpmi_hsm_start_finalize(void *priv,
+ rpmi_uint32_t hart_index,
+ rpmi_uint64_t start_addr)
+{
+}
+
+static enum rpmi_error riscv_rpmi_hsm_stop_prepare(void *priv,
+ rpmi_uint32_t hart_index)
+{
+ RiscvRpmiState *s = priv;
+
+ if (!riscv_rpmi_hart_cpu(s, hart_index)) {
+ return RPMI_ERR_INVALID_PARAM;
+ }
+
+ riscv_rpmi_hsm_set_hw_state(s, hart_index, RPMI_HART_HW_STATE_STOPPED);
+ return RPMI_SUCCESS;
+}
+
+static void riscv_rpmi_hsm_stop_finalize(void *priv, rpmi_uint32_t hart_index)
+{
+}
+
+static enum rpmi_error riscv_rpmi_hsm_suspend_prepare(
+ void *priv, rpmi_uint32_t hart_index,
+ const struct rpmi_hsm_suspend_type *suspend_type,
+ rpmi_uint64_t resume_addr)
+{
+ RiscvRpmiState *s = priv;
+
+ if (!suspend_type || !riscv_rpmi_hart_cpu(s, hart_index)) {
+ return RPMI_ERR_INVALID_PARAM;
+ }
+
+ riscv_rpmi_hsm_set_hw_state(s, hart_index, RPMI_HART_HW_STATE_SUSPENDED);
+ return RPMI_SUCCESS;
+}
+
+static void riscv_rpmi_hsm_suspend_finalize(
+ void *priv, rpmi_uint32_t hart_index,
+ const struct rpmi_hsm_suspend_type *suspend_type,
+ rpmi_uint64_t resume_addr)
+{
+}
+
+static enum rpmi_hart_hw_state riscv_rpmi_hsm_get_hw_state(
+ void *priv, rpmi_uint32_t hart_index)
+{
+ RiscvRpmiState *s = priv;
+
+ if (hart_index >= s->hart_count || !s->hsm_hw_states) {
+ return RPMI_HART_HW_STATE_STOPPED;
+ }
+
+ return s->hsm_hw_states[hart_index];
+}
+
+static const struct rpmi_hsm_platform_ops riscv_rpmi_hsm_ops = {
+ .hart_get_hw_state = riscv_rpmi_hsm_get_hw_state,
+ .hart_start_prepare = riscv_rpmi_hsm_start_prepare,
+ .hart_start_finalize = riscv_rpmi_hsm_start_finalize,
+ .hart_stop_prepare = riscv_rpmi_hsm_stop_prepare,
+ .hart_stop_finalize = riscv_rpmi_hsm_stop_finalize,
+ .hart_suspend_prepare = riscv_rpmi_hsm_suspend_prepare,
+ .hart_suspend_finalize = riscv_rpmi_hsm_suspend_finalize,
+};
+
+static bool riscv_rpmi_hsm_create(RiscvRpmiState *s,
+ struct rpmi_service_group **group,
+ Error **errp)
+{
+ struct rpmi_hsm *hsm;
+
+ if (!s->hart_count || !s->hart_ids) {
+ error_setg(errp, "RPMI HSM service requires hart IDs");
+ return false;
+ }
+
+ s->hsm_hw_states = g_new0(uint32_t, s->hart_count);
+ for (uint32_t i = 0; i < s->hart_count; i++) {
+ s->hsm_hw_states[i] = RPMI_HART_HW_STATE_STARTED;
+ }
+
+ hsm = rpmi_hsm_create(s->hart_count, s->hart_ids,
+ ARRAY_SIZE(riscv_rpmi_hsm_suspend_types),
+ riscv_rpmi_hsm_suspend_types,
+ &riscv_rpmi_hsm_ops, s);
+ if (!hsm) {
+ g_clear_pointer(&s->hsm_hw_states, g_free);
+ error_setg(errp, "failed to create RPMI HSM context");
+ return false;
+ }
+
+ *group = rpmi_service_group_hsm_create(hsm);
+ if (!*group) {
+ rpmi_hsm_destroy(hsm);
+ g_clear_pointer(&s->hsm_hw_states, g_free);
+ error_setg(errp, "failed to create RPMI HSM service group");
+ return false;
+ }
+
+ s->hsm = hsm;
+ return true;
+}
+
+static void riscv_rpmi_hsm_destroy(RiscvRpmiState *s)
+{
+ if (s->hsm_group) {
+ rpmi_env_free_lock(s->hsm_group->lock);
+ s->hsm_group->lock = NULL;
+ rpmi_service_group_hsm_destroy(s->hsm_group);
+ s->hsm_group = NULL;
+ }
+
+ if (s->hsm) {
+ rpmi_hsm_destroy(s->hsm);
+ s->hsm = NULL;
+ }
+
+ g_clear_pointer(&s->hsm_hw_states, g_free);
+}
+
+
+bool riscv_rpmi_hsm_add(RiscvRpmiState *s, Error **errp)
+{
+ struct rpmi_service_group *group;
+
+ if (s->hsm_group) {
+ error_setg(errp, "duplicate RPMI HSM service descriptor");
+ return false;
+ }
+
+ if (!riscv_rpmi_hsm_create(s, &group, errp)) {
+ return false;
+ }
+
+ if (!riscv_rpmi_context_add_group(s, group, "HSM", errp)) {
+ s->hsm_group = group;
+ riscv_rpmi_hsm_destroy(s);
+ return false;
+ }
+
+ s->hsm_group = group;
+ return true;
+}
+
+void riscv_rpmi_hsm_remove(RiscvRpmiState *s)
+{
+ riscv_rpmi_context_remove_group(s, s->hsm_group);
+ riscv_rpmi_hsm_destroy(s);
+}
+
+void riscv_rpmi_hsm_reset(RiscvRpmiState *s)
+{
+ if (!s->hsm_hw_states) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < s->hart_count; i++) {
+ s->hsm_hw_states[i] = RPMI_HART_HW_STATE_STARTED;
+ }
+ if (s->hsm) {
+ rpmi_hsm_process_state_changes(s->hsm);
+ }
+}
diff --git a/hw/misc/riscv_rpmi_internal.h b/hw/misc/riscv_rpmi_internal.h
index fe09804e69..42a6708907 100644
--- a/hw/misc/riscv_rpmi_internal.h
+++ b/hw/misc/riscv_rpmi_internal.h
@@ -30,4 +30,8 @@ void riscv_rpmi_context_remove_group(RiscvRpmiState *s,
bool riscv_rpmi_sysreset_add(RiscvRpmiState *s, Error **errp);
void riscv_rpmi_sysreset_remove(RiscvRpmiState *s);
+bool riscv_rpmi_hsm_add(RiscvRpmiState *s, Error **errp);
+void riscv_rpmi_hsm_remove(RiscvRpmiState *s);
+void riscv_rpmi_hsm_reset(RiscvRpmiState *s);
+
#endif
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index 273a9aa04f..2b5f367bdb 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -1014,6 +1014,12 @@ static const RiscvRpmiServiceConfig virt_rpmi_services[] = {
.compatible = "riscv,rpmi-system-reset",
.service_group = RISCV_RPMI_SRVGRP_SYSTEM_RESET,
},
+ {
+ .kind = RISCV_RPMI_SERVICE_HSM,
+ .node_name = "hsm",
+ .compatible = "riscv,rpmi-hsm",
+ .service_group = RISCV_RPMI_SRVGRP_HSM,
+ },
};
static uint32_t virt_rpmi_service_count(RISCVVirtState *s)
diff --git a/include/hw/misc/riscv_rpmi.h b/include/hw/misc/riscv_rpmi.h
index d11db87731..37c56ea2e8 100644
--- a/include/hw/misc/riscv_rpmi.h
+++ b/include/hw/misc/riscv_rpmi.h
@@ -28,6 +28,7 @@
#define VIRT_RPMI_P2A_REQ_SIZE 0
#define RISCV_RPMI_SRVGRP_SYSTEM_RESET 3
+#define RISCV_RPMI_SRVGRP_HSM 5
#define TYPE_RISCV_RPMI "riscv-rpmi"
OBJECT_DECLARE_SIMPLE_TYPE(RiscvRpmiState, RISCV_RPMI)
@@ -36,10 +37,12 @@ struct rpmi_context;
struct rpmi_service_group;
struct rpmi_shmem;
struct rpmi_transport;
+struct rpmi_hsm;
typedef enum RiscvRpmiServiceKind {
RISCV_RPMI_SERVICE_INVALID = 0,
RISCV_RPMI_SERVICE_SYSRESET,
+ RISCV_RPMI_SERVICE_HSM,
} RiscvRpmiServiceKind;
typedef struct RiscvRpmiMachineOps {
@@ -83,6 +86,9 @@ struct RiscvRpmiState {
const RiscvRpmiMachineOps *machine_ops;
void *machine_opaque;
struct rpmi_service_group *sysreset_group;
+ struct rpmi_hsm *hsm;
+ struct rpmi_service_group *hsm_group;
+ uint32_t *hsm_hw_states;
uint32_t *hart_ids;
uint32_t hart_count;
const RiscvRpmiServiceConfig *services;
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH 07/10] riscv: add RPMI system suspend service
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 13:00 UTC (permalink / raw)
To: qemu-riscv
Cc: Subrahmanya Lingappa, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu,
open list:All patches CC here
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add the RPMI System Suspend service group for RISC-V virt.
The service advertises suspend-to-RAM support through librpmi,
registers QEMU wakeup support, and routes the suspend finalize callback
to the machine suspend path. On wakeup it processes RPMI group events
and defers the HSM hart resume until vCPUs are ready to run again.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
hw/misc/meson.build | 1 +
hw/misc/riscv_rpmi.c | 4 +
hw/misc/riscv_rpmi_hsm.c | 9 +-
hw/misc/riscv_rpmi_internal.h | 5 +
hw/misc/riscv_rpmi_syssusp.c | 191 ++++++++++++++++++++++++++++++++++
hw/riscv/virt.c | 25 +++++
include/hw/misc/riscv_rpmi.h | 11 ++
7 files changed, 245 insertions(+), 1 deletion(-)
create mode 100644 hw/misc/riscv_rpmi_syssusp.c
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index fc5b11ed57..cb093a8642 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -172,4 +172,5 @@ system_ss.add(when: 'CONFIG_RISCV_RPMI', if_true: [files(
'riscv_rpmi.c',
'riscv_rpmi_sysreset.c',
'riscv_rpmi_hsm.c',
+ 'riscv_rpmi_syssusp.c',
), librpmi])
diff --git a/hw/misc/riscv_rpmi.c b/hw/misc/riscv_rpmi.c
index 820b1d5d63..08691861b0 100644
--- a/hw/misc/riscv_rpmi.c
+++ b/hw/misc/riscv_rpmi.c
@@ -187,6 +187,10 @@ static const RiscvRpmiServiceOps riscv_rpmi_service_ops[] = {
.kind = RISCV_RPMI_SERVICE_HSM,
.add = riscv_rpmi_hsm_add,
.remove = riscv_rpmi_hsm_remove,
+ }, {
+ .kind = RISCV_RPMI_SERVICE_SYSSUSP,
+ .add = riscv_rpmi_syssusp_add,
+ .remove = riscv_rpmi_syssusp_remove,
},
};
diff --git a/hw/misc/riscv_rpmi_hsm.c b/hw/misc/riscv_rpmi_hsm.c
index 9a9859b10c..bacdabcac6 100644
--- a/hw/misc/riscv_rpmi_hsm.c
+++ b/hw/misc/riscv_rpmi_hsm.c
@@ -235,7 +235,6 @@ static void riscv_rpmi_hsm_destroy(RiscvRpmiState *s)
g_clear_pointer(&s->hsm_hw_states, g_free);
}
-
bool riscv_rpmi_hsm_add(RiscvRpmiState *s, Error **errp)
{
struct rpmi_service_group *group;
@@ -278,3 +277,11 @@ void riscv_rpmi_hsm_reset(RiscvRpmiState *s)
rpmi_hsm_process_state_changes(s->hsm);
}
}
+
+void riscv_rpmi_hsm_resume(RiscvRpmiState *s, uint32_t hart_index)
+{
+ riscv_rpmi_hsm_set_hw_state(s, hart_index, RPMI_HART_HW_STATE_STARTED);
+ if (s->hsm) {
+ rpmi_hsm_process_state_changes(s->hsm);
+ }
+}
diff --git a/hw/misc/riscv_rpmi_internal.h b/hw/misc/riscv_rpmi_internal.h
index 42a6708907..34cab35c32 100644
--- a/hw/misc/riscv_rpmi_internal.h
+++ b/hw/misc/riscv_rpmi_internal.h
@@ -33,5 +33,10 @@ void riscv_rpmi_sysreset_remove(RiscvRpmiState *s);
bool riscv_rpmi_hsm_add(RiscvRpmiState *s, Error **errp);
void riscv_rpmi_hsm_remove(RiscvRpmiState *s);
void riscv_rpmi_hsm_reset(RiscvRpmiState *s);
+void riscv_rpmi_hsm_resume(RiscvRpmiState *s, uint32_t hart_index);
+
+bool riscv_rpmi_syssusp_add(RiscvRpmiState *s, Error **errp);
+void riscv_rpmi_syssusp_remove(RiscvRpmiState *s);
+
#endif
diff --git a/hw/misc/riscv_rpmi_syssusp.c b/hw/misc/riscv_rpmi_syssusp.c
new file mode 100644
index 0000000000..d455e08163
--- /dev/null
+++ b/hw/misc/riscv_rpmi_syssusp.c
@@ -0,0 +1,191 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * RISC-V RPMI System Suspend service.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#include "qemu/osdep.h"
+#include "riscv_rpmi_internal.h"
+#include "qemu/timer.h"
+#include "system/runstate.h"
+
+static const struct rpmi_system_suspend_type riscv_rpmi_syssusp_types[] = {
+ {
+ .type = RPMI_SYSSUSP_TYPE_SUSPEND_TO_RAM,
+ .attr = RPMI_SYSSUSP_ATTRS_FLAGS_RESUMEADDR,
+ },
+};
+
+static enum rpmi_error riscv_rpmi_syssusp_prepare(
+ void *priv, rpmi_uint32_t hart_index,
+ const struct rpmi_system_suspend_type *syssusp_type,
+ rpmi_uint64_t resume_addr)
+{
+ RiscvRpmiState *s = priv;
+
+ if (s->machine_ops && s->machine_ops->register_wakeup_support) {
+ s->machine_ops->register_wakeup_support(s->machine_opaque);
+ }
+ return RPMI_SUCCESS;
+}
+
+static rpmi_bool_t riscv_rpmi_syssusp_ready(void *priv,
+ rpmi_uint32_t hart_index)
+{
+ return true;
+}
+
+static void riscv_rpmi_syssusp_finalize(
+ void *priv, rpmi_uint32_t hart_index,
+ const struct rpmi_system_suspend_type *syssusp_type,
+ rpmi_uint64_t resume_addr)
+{
+ RiscvRpmiState *s = priv;
+
+ if (s->machine_ops && s->machine_ops->system_suspend) {
+ s->machine_ops->system_suspend(s->machine_opaque);
+ }
+}
+
+static rpmi_bool_t riscv_rpmi_syssusp_can_resume(void *priv,
+ rpmi_uint32_t hart_index)
+{
+ RiscvRpmiState *s = priv;
+
+ if (s->machine_ops && s->machine_ops->system_can_resume) {
+ return s->machine_ops->system_can_resume(s->machine_opaque);
+ }
+
+ return true;
+}
+
+static enum rpmi_error riscv_rpmi_syssusp_resume(
+ void *priv, rpmi_uint32_t hart_index,
+ const struct rpmi_system_suspend_type *syssusp_type,
+ rpmi_uint64_t resume_addr)
+{
+ RiscvRpmiState *s = priv;
+
+ s->syssusp_resume_hart_index = hart_index;
+ s->syssusp_resume_pending = true;
+ return RPMI_SUCCESS;
+}
+
+static const struct rpmi_syssusp_platform_ops riscv_rpmi_syssusp_ops = {
+ .system_suspend_prepare = riscv_rpmi_syssusp_prepare,
+ .system_suspend_ready = riscv_rpmi_syssusp_ready,
+ .system_suspend_finalize = riscv_rpmi_syssusp_finalize,
+ .system_suspend_can_resume = riscv_rpmi_syssusp_can_resume,
+ .system_suspend_resume = riscv_rpmi_syssusp_resume,
+};
+
+static void riscv_rpmi_wakeup_timer(void *opaque)
+{
+ RiscvRpmiState *s = opaque;
+
+ if (s->syssusp_resume_pending) {
+ riscv_rpmi_hsm_resume(s, s->syssusp_resume_hart_index);
+ s->syssusp_resume_pending = false;
+ }
+}
+
+static void riscv_rpmi_wakeup_notify(Notifier *notifier, void *data)
+{
+ RiscvRpmiState *s = container_of(notifier, RiscvRpmiState,
+ wakeup_notifier);
+
+ if (s->context) {
+ rpmi_context_process_group_events(s->context,
+ RPMI_SRVGRP_SYSTEM_SUSPEND);
+ }
+ if (s->wakeup_timer) {
+ /*
+ * Wakeup notifiers run before vCPUs resume. Defer the HSM
+ * kick so OpenSBI can leave WFI after QEMU restarts execution.
+ */
+ timer_mod(s->wakeup_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 10);
+ }
+}
+
+static bool riscv_rpmi_syssusp_create(RiscvRpmiState *s,
+ struct rpmi_service_group **group,
+ Error **errp)
+{
+ if (!s->hsm) {
+ error_setg(errp, "RPMI system suspend service requires HSM service");
+ return false;
+ }
+
+ if (s->machine_ops && s->machine_ops->register_wakeup_support) {
+ s->machine_ops->register_wakeup_support(s->machine_opaque);
+ }
+ if (!s->wakeup_timer) {
+ s->wakeup_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
+ riscv_rpmi_wakeup_timer, s);
+ }
+ if (!s->wakeup_notifier_registered) {
+ s->wakeup_notifier.notify = riscv_rpmi_wakeup_notify;
+ qemu_register_wakeup_notifier(&s->wakeup_notifier);
+ s->wakeup_notifier_registered = true;
+ }
+
+ *group = rpmi_service_group_syssusp_create(
+ s->hsm, ARRAY_SIZE(riscv_rpmi_syssusp_types),
+ riscv_rpmi_syssusp_types, &riscv_rpmi_syssusp_ops, s);
+ if (!*group) {
+ error_setg(errp, "failed to create RPMI system suspend service group");
+ return false;
+ }
+
+ return true;
+}
+
+static void riscv_rpmi_syssusp_destroy(RiscvRpmiState *s)
+{
+ if (s->wakeup_notifier_registered) {
+ notifier_remove(&s->wakeup_notifier);
+ s->wakeup_notifier_registered = false;
+ }
+ if (s->wakeup_timer) {
+ timer_free(s->wakeup_timer);
+ s->wakeup_timer = NULL;
+ }
+
+ if (s->syssusp_group) {
+ rpmi_service_group_syssusp_destroy(s->syssusp_group);
+ s->syssusp_group = NULL;
+ }
+}
+
+bool riscv_rpmi_syssusp_add(RiscvRpmiState *s, Error **errp)
+{
+ struct rpmi_service_group *group;
+
+ if (s->syssusp_group) {
+ error_setg(errp, "duplicate RPMI system suspend descriptor");
+ return false;
+ }
+
+ if (!riscv_rpmi_syssusp_create(s, &group, errp)) {
+ return false;
+ }
+
+ if (!riscv_rpmi_context_add_group(s, group, "system suspend", errp)) {
+ s->syssusp_group = group;
+ riscv_rpmi_syssusp_destroy(s);
+ return false;
+ }
+
+ s->syssusp_group = group;
+ return true;
+}
+
+void riscv_rpmi_syssusp_remove(RiscvRpmiState *s)
+{
+ riscv_rpmi_context_remove_group(s, s->syssusp_group);
+ riscv_rpmi_syssusp_destroy(s);
+}
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index 2b5f367bdb..21c218a78d 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -51,6 +51,7 @@
#include "chardev/char.h"
#include "system/device_tree.h"
#include "system/system.h"
+#include "system/runstate.h"
#include "system/tcg.h"
#include "system/kvm.h"
#include "system/tpm.h"
@@ -1020,6 +1021,12 @@ static const RiscvRpmiServiceConfig virt_rpmi_services[] = {
.compatible = "riscv,rpmi-hsm",
.service_group = RISCV_RPMI_SRVGRP_HSM,
},
+ {
+ .kind = RISCV_RPMI_SERVICE_SYSSUSP,
+ .node_name = "suspend",
+ .compatible = "riscv,rpmi-system-suspend",
+ .service_group = RISCV_RPMI_SRVGRP_SYSTEM_SUSPEND,
+ },
};
static uint32_t virt_rpmi_service_count(RISCVVirtState *s)
@@ -1037,9 +1044,27 @@ static void virt_rpmi_system_shutdown(void *opaque)
qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
}
+static void virt_rpmi_system_suspend(void *opaque)
+{
+ qemu_system_suspend_request();
+}
+
+static void virt_rpmi_register_wakeup_support(void *opaque)
+{
+ qemu_register_wakeup_support();
+}
+
+static bool virt_rpmi_system_can_resume(void *opaque)
+{
+ return runstate_check(RUN_STATE_RUNNING);
+}
+
static const RiscvRpmiMachineOps virt_rpmi_machine_ops = {
.system_reset = virt_rpmi_system_reset,
.system_shutdown = virt_rpmi_system_shutdown,
+ .system_suspend = virt_rpmi_system_suspend,
+ .register_wakeup_support = virt_rpmi_register_wakeup_support,
+ .system_can_resume = virt_rpmi_system_can_resume,
};
static RiscvRpmiConfig virt_rpmi_config(RISCVVirtState *s,
diff --git a/include/hw/misc/riscv_rpmi.h b/include/hw/misc/riscv_rpmi.h
index 37c56ea2e8..a4fe2aac63 100644
--- a/include/hw/misc/riscv_rpmi.h
+++ b/include/hw/misc/riscv_rpmi.h
@@ -29,6 +29,7 @@
#define RISCV_RPMI_SRVGRP_SYSTEM_RESET 3
#define RISCV_RPMI_SRVGRP_HSM 5
+#define RISCV_RPMI_SRVGRP_SYSTEM_SUSPEND 4
#define TYPE_RISCV_RPMI "riscv-rpmi"
OBJECT_DECLARE_SIMPLE_TYPE(RiscvRpmiState, RISCV_RPMI)
@@ -43,11 +44,15 @@ typedef enum RiscvRpmiServiceKind {
RISCV_RPMI_SERVICE_INVALID = 0,
RISCV_RPMI_SERVICE_SYSRESET,
RISCV_RPMI_SERVICE_HSM,
+ RISCV_RPMI_SERVICE_SYSSUSP,
} RiscvRpmiServiceKind;
typedef struct RiscvRpmiMachineOps {
void (*system_reset)(void *opaque);
void (*system_shutdown)(void *opaque);
+ void (*system_suspend)(void *opaque);
+ void (*register_wakeup_support)(void *opaque);
+ bool (*system_can_resume)(void *opaque);
} RiscvRpmiMachineOps;
typedef struct RiscvRpmiServiceConfig {
RiscvRpmiServiceKind kind;
@@ -86,9 +91,15 @@ struct RiscvRpmiState {
const RiscvRpmiMachineOps *machine_ops;
void *machine_opaque;
struct rpmi_service_group *sysreset_group;
+ struct rpmi_service_group *syssusp_group;
struct rpmi_hsm *hsm;
struct rpmi_service_group *hsm_group;
uint32_t *hsm_hw_states;
+ Notifier wakeup_notifier;
+ bool wakeup_notifier_registered;
+ QEMUTimer *wakeup_timer;
+ bool syssusp_resume_pending;
+ uint32_t syssusp_resume_hart_index;
uint32_t *hart_ids;
uint32_t hart_count;
const RiscvRpmiServiceConfig *services;
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH 08/10] docs/system/riscv: document initial virt RPMI support
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 13:00 UTC (permalink / raw)
To: qemu-riscv
Cc: Subrahmanya Lingappa, Pierrick Bouvier,
open list:All patches CC here
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Document the opt-in virt RPMI transport and the initial HSM, system
reset, and system suspend service groups.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
docs/system/riscv/virt.rst | 50 ++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/docs/system/riscv/virt.rst b/docs/system/riscv/virt.rst
index 60850970ce..8e88f780e1 100644
--- a/docs/system/riscv/virt.rst
+++ b/docs/system/riscv/virt.rst
@@ -146,6 +146,56 @@ The following machine-specific options are supported:
Enables the riscv-iommu-sys platform device. Defaults to 'off'.
+- rpmi=[on|off]
+
+ Enables the RISC-V RPMI shared-memory transport. This option defaults to
+ "off", is TCG-only, and is available only when QEMU is built with librpmi
+ support. This support targets the RISC-V Platform Management Interface
+ (RPMI) Specification v1.0 Ratified (`RPMI v1.0 specification`_). The initial
+ ``virt`` implementation exposes RPMI system reset, system suspend, and HSM
+ service groups. System reset can request shutdown or cold reboot through
+ RPMI. HSM uses the ``virt`` hart IDs and supports hart discovery, status,
+ start, stop, and suspend commands. System suspend uses QEMU's suspend and
+ wakeup path. Migration is currently blocked while RPMI transport and service
+ VMState support is being designed.
+
+ The build dependency is provided by `librpmi`_. Install librpmi so that
+ ``pkg-config`` can find ``librpmi.pc``, then configure QEMU with
+ ``--enable-librpmi`` or ``-Dlibrpmi=enabled``.
+
+.. _RPMI v1.0 specification: https://github.com/riscv-non-isa/riscv-rpmi/releases/download/v1.0/riscv-rpmi.pdf
+.. _librpmi: https://github.com/riscv-software-src/librpmi
+
+RPMI firmware and guest smoke
+-----------------------------
+
+A firmware-level RPMI smoke test can be run with OpenSBI, a Linux kernel, and a
+RISC-V disk image:
+
+.. code-block:: bash
+
+ $ qemu-system-riscv64 \
+ -machine virt,rpmi=on \
+ -cpu rv64 -smp 4 -m 4G -nographic \
+ -bios /path/to/fw_jump.bin \
+ -kernel /path/to/Image \
+ -append "root=/dev/vda rw console=ttyS0 earlycon=sbi" \
+ -drive file=/path/to/rootfs.img,format=raw,if=virtio,snapshot=on
+
+The OpenSBI banner should report RPMI-backed HSM and system suspend support.
+After the guest reaches a shell, normal guest reboot/poweroff commands exercise
+the RPMI system reset path.
+
+RPMI device tree policy
+-----------------------
+
+RPMI is currently described through FDT only; no ACPI representation is emitted
+for the RPMI transport or service groups. The ``virt`` machine uses the
+``riscv,rpmi-shmem-mbox`` shared-memory mailbox node plus child service nodes
+with ``riscv,rpmi-*`` compatible strings. These binding names are part of the
+RPMI RFC surface and may be revised if the RISC-V RPMI bindings change during
+review.
+
Running Linux kernel
--------------------
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH 09/10] tests/qtest: cover initial RISC-V RPMI services
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 13:00 UTC (permalink / raw)
To: qemu-riscv
Cc: Subrahmanya Lingappa, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu,
open list:All patches CC here
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Add qtests for the RPMI transport plus Base, system reset, HSM, and
system suspend service groups.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
MAINTAINERS | 1 +
tests/qtest/meson.build | 2 +
tests/qtest/riscv-rpmi-test.c | 684 ++++++++++++++++++++++++++++++++++
3 files changed, 687 insertions(+)
create mode 100644 tests/qtest/riscv-rpmi-test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 8e91a31757..6dd71c5a82 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -361,6 +361,7 @@ F: hw/riscv/
F: hw/intc/riscv*
F: hw/misc/riscv_rpmi*
F: include/hw/misc/riscv_rpmi.h
+F: tests/qtest/riscv-rpmi-test.c
F: include/hw/char/riscv_htif.h
F: include/hw/riscv/
F: common-user/host/riscv*
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 4897325d84..d54b79cbcf 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -290,6 +290,8 @@ qtests_riscv32 = \
qtests_riscv64 = ['riscv-csr-test'] + \
(unpack_edk2_blobs ? ['bios-tables-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_RISCV_RPMI') ?
+ ['riscv-rpmi-test'] : []) + \
(config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and
config_all_devices.has_key('CONFIG_RISCV_IOMMU') ?
['iommu-riscv-test'] : []) + \
diff --git a/tests/qtest/riscv-rpmi-test.c b/tests/qtest/riscv-rpmi-test.c
new file mode 100644
index 0000000000..ecd5b910c7
--- /dev/null
+++ b/tests/qtest/riscv-rpmi-test.c
@@ -0,0 +1,684 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * QTests for RISC-V RPMI devices.
+ *
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ * Author:
+ * Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+ */
+
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+#include "libqtest.h"
+#include "qobject/qdict.h"
+
+#define RPMI_SHMEM_BASE 0x10200000ULL
+#define RPMI_DOORBELL_BASE 0x10230000ULL
+#define RPMI_IMSIC_S_BASE 0x28000000ULL
+#define RPMI_CPPC_FASTCHAN_BASE 0x10240000ULL
+#define RPMI_CPPC_FASTCHAN_SIZE 0x4000
+#define RPMI_CPPC_FASTCHAN_FEEDBACK_OFFSET 0x2000
+#define RPMI_SLOT_SIZE 64
+
+#define RPMI_A2P_HEAD RPMI_SHMEM_BASE
+#define RPMI_A2P_TAIL (RPMI_SHMEM_BASE + RPMI_SLOT_SIZE)
+#define RPMI_A2P_SLOT0 (RPMI_SHMEM_BASE + 2 * RPMI_SLOT_SIZE)
+
+#define RPMI_SRVGRP_BASE 0x0001
+#define RPMI_SRVGRP_SYSTEM_MSI 0x0002
+#define RPMI_SRVGRP_SYSTEM_RESET 0x0003
+#define RPMI_SRVGRP_SYSTEM_SUSPEND 0x0004
+#define RPMI_SRVGRP_HSM 0x0005
+#define RPMI_SRVGRP_CPPC 0x0006
+#define RPMI_SRVGRP_CLOCK 0x0008
+#define RPMI_SRVGRP_MANAGEMENT_MODE 0x000b
+#define RPMI_SRVGRP_LOGGING 0x000e
+#define RPMI_BASE_SRV_GET_PLATFORM_INFO 0x05
+#define RPMI_BASE_SRV_PROBE_SERVICE_GROUP 0x06
+#define RPMI_SYSMSI_SRV_GET_ATTRIBUTES 0x02
+#define RPMI_SYSMSI_SRV_GET_MSI_ATTRIBUTES 0x03
+#define RPMI_SYSMSI_SRV_SET_MSI_STATE 0x04
+#define RPMI_SYSMSI_SRV_GET_MSI_STATE 0x05
+#define RPMI_SYSMSI_SRV_SET_MSI_TARGET 0x06
+#define RPMI_SYSMSI_SRV_GET_MSI_TARGET 0x07
+#define RPMI_SYSRST_SRV_GET_ATTRIBUTES 0x02
+#define RPMI_SYSRST_SRV_SYSTEM_RESET 0x03
+#define RPMI_HSM_SRV_GET_HART_STATUS 0x02
+#define RPMI_HSM_SRV_GET_HART_LIST 0x03
+#define RPMI_HSM_SRV_GET_SUSPEND_TYPES 0x04
+#define RPMI_HSM_SRV_GET_SUSPEND_INFO 0x05
+#define RPMI_HSM_SRV_HART_START 0x06
+#define RPMI_HSM_SRV_HART_STOP 0x07
+#define RPMI_HSM_SRV_HART_SUSPEND 0x08
+#define RPMI_SYSSUSP_SRV_GET_ATTRIBUTES 0x02
+#define RPMI_SYSSUSP_SRV_SYSTEM_SUSPEND 0x03
+#define RPMI_CPPC_SRV_PROBE_REG 0x02
+#define RPMI_CPPC_SRV_READ_REG 0x03
+#define RPMI_CPPC_SRV_WRITE_REG 0x04
+#define RPMI_CPPC_SRV_GET_FAST_CHANNEL_REGION 0x05
+#define RPMI_CPPC_SRV_GET_FAST_CHANNEL_OFFSET 0x06
+#define RPMI_CPPC_SRV_GET_HART_LIST 0x07
+#define RPMI_CPPC_NOMINAL_PERF 0x01
+#define RPMI_CPPC_DESIRED_PERF 0x05
+#define RPMI_CPPC_COUNTER_WRAPAROUND_TIME 0x0a
+#define RPMI_CPPC_REFERENCE_PERF_COUNTER 0x0b
+#define RPMI_CPPC_DELIVERED_PERF_COUNTER 0x0c
+#define RPMI_CPPC_REG_LEN_64 64
+#define RPMI_CLK_SRV_GET_NUM_CLOCKS 0x02
+#define RPMI_CLK_SRV_GET_ATTRIBUTES 0x03
+#define RPMI_CLK_SRV_GET_SUPPORTED_RATES 0x04
+#define RPMI_CLK_SRV_SET_CONFIG 0x05
+#define RPMI_CLK_SRV_GET_CONFIG 0x06
+#define RPMI_CLK_SRV_SET_RATE 0x07
+#define RPMI_CLK_SRV_GET_RATE 0x08
+#define RPMI_MM_SRV_GET_ATTRIBUTES 0x02
+#define RPMI_MM_SRV_COMMUNICATE 0x03
+#define RPMI_LOGGING_SRV_SET_CONFIG 0x02
+#define RPMI_MSG_NORMAL_REQUEST 0x00
+#define RPMI_MSG_POSTED_REQUEST 0x01
+#define RPMI_MSG_ACKNOWLEDGEMENT 0x02
+#define RPMI_SYSRST_TYPE_SHUTDOWN 0x00
+#define RPMI_SYSRST_TYPE_COLD_REBOOT 0x01
+#define RPMI_SYSRST_TYPE_INVALID 0x03
+#define RPMI_SYSRST_ATTRS_FLAGS_RESETTYPE 1
+#define RPMI_SYS_MSI_SHUTDOWN_INDEX 0
+#define RPMI_SYS_MSI_REBOOT_INDEX 1
+#define RPMI_SYS_MSI_SUSPEND_INDEX 2
+#define RPMI_SYS_NUM_MSI 4
+#define RPMI_SYSMSI_MSI_STATE_ENABLE 1
+#define RPMI_SYSMSI_MSI_STATE_PENDING 2
+#define RPMI_TOKEN 0x55aa
+#define RPMI_ERR_NOTSUPP 0xfffffffeU
+#define RPMI_ERR_INVALID_PARAM 0xfffffffdU
+#define RPMI_ERR_INVALID_ADDR 0xfffffffbU
+#define RPMI_ERR_DENIED 0xfffffffcU
+#define RPMI_HSM_HART_STATE_STARTED 0x00
+#define RPMI_HSM_HART_STATE_STOPPED 0x01
+#define RPMI_HSM_HART_STATE_SUSPENDED 0x04
+#define VIRT_RPMI_CPPC_NOMINAL_PERF 30
+#define VIRT_RPMI_CLOCK_COUNT 6
+#define VIRT_RPMI_MM_VERSION 0x10000
+#define VIRT_RPMI_SHMEM_SIZE 0x20000
+#define RPMI_MM_INPUT_OFFSET 0x3000
+#define RPMI_MM_OUTPUT_OFFSET 0x3800
+#define RPMI_MM_BUFFER_SIZE 0x400
+#define RPMI_MM_INPUT_BASE (RPMI_SHMEM_BASE + RPMI_MM_INPUT_OFFSET)
+#define RPMI_MM_OUTPUT_BASE (RPMI_SHMEM_BASE + RPMI_MM_OUTPUT_OFFSET)
+#define MM_EFI_COMM_HEADER_SIZE 24
+#define EFI_VAR_COMM_HEADER_SIZE 16
+#define EFI_VAR_ACCESS_NAME_OFFSET 36
+#define EFI_VAR_NEXT_NAME_OFFSET 24
+#define EFI_VAR_FN_GET_VARIABLE 1
+#define EFI_VAR_FN_GET_NEXT_VARIABLE_NAME 2
+#define EFI_VAR_FN_SET_VARIABLE 3
+#define EFI_SUCCESS 0ULL
+#define EFI_INVALID_PARAMETER 0x8000000000000002ULL
+#define EFI_BUFFER_TOO_SMALL 0x8000000000000005ULL
+#define EFI_NOT_FOUND 0x800000000000000eULL
+#define EFI_VARIABLE_NON_VOLATILE 0x00000001
+#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x00000002
+#define EFI_VARIABLE_RUNTIME_ACCESS 0x00000004
+
+#define RPMI_P2A_ACK_BASE (RPMI_SHMEM_BASE + 16 * RPMI_SLOT_SIZE)
+#define RPMI_P2A_ACK_HEAD RPMI_P2A_ACK_BASE
+#define RPMI_P2A_ACK_TAIL (RPMI_P2A_ACK_BASE + RPMI_SLOT_SIZE)
+#define RPMI_P2A_ACK_SLOT0 (RPMI_P2A_ACK_BASE + 2 * RPMI_SLOT_SIZE)
+
+static uint64_t rpmi_response_base;
+
+static uint64_t rpmi_queue_slot(uint64_t queue_base, uint32_t index)
+{
+ return queue_base + (index + 2) * RPMI_SLOT_SIZE;
+}
+
+static void rpmi_send_request(QTestState *qts, uint16_t service_group,
+ uint8_t service_id, uint8_t request_type,
+ const uint32_t *data, size_t data_words)
+{
+ uint32_t tail = qtest_readl(qts, RPMI_A2P_TAIL);
+ uint64_t slot = rpmi_queue_slot(RPMI_SHMEM_BASE, tail);
+ size_t i;
+
+ qtest_writew(qts, slot, service_group);
+ qtest_writeb(qts, slot + 2, service_id);
+ qtest_writeb(qts, slot + 3, request_type);
+ qtest_writew(qts, slot + 4, data_words * sizeof(*data));
+ qtest_writew(qts, slot + 6, RPMI_TOKEN);
+
+ for (i = 0; i < data_words; i++) {
+ qtest_writel(qts, slot + 8 + i * sizeof(*data), data[i]);
+ }
+
+ g_test_message(
+ "RPMI_A2P_REQ shmem=0x%016" PRIx64 " doorbell=0x%016" PRIx64
+ " group=0x%04x service=0x%02x type=0x%02x data_len=%zu"
+ " token=0x%04x a2p_tail=%u slot=0x%016" PRIx64,
+ (uint64_t)RPMI_SHMEM_BASE, (uint64_t)RPMI_DOORBELL_BASE,
+ service_group, service_id, request_type, data_words * sizeof(*data),
+ RPMI_TOKEN, tail, slot);
+
+ qtest_writel(qts, RPMI_A2P_TAIL, (tail + 1) % 16);
+ qtest_writel(qts, RPMI_DOORBELL_BASE, 1);
+}
+
+static uint32_t rpmi_response_word(QTestState *qts, unsigned int word)
+{
+ return qtest_readl(qts, rpmi_response_base + 8 + word * sizeof(uint32_t));
+}
+
+static void rpmi_expect_ack(QTestState *qts, uint16_t service_group,
+ uint8_t service_id, uint16_t data_len)
+{
+ uint32_t head = qtest_readl(qts, RPMI_P2A_ACK_HEAD);
+ uint32_t tail = qtest_readl(qts, RPMI_P2A_ACK_TAIL);
+
+ g_assert_cmphex(tail, !=, head);
+ rpmi_response_base = rpmi_queue_slot(RPMI_P2A_ACK_BASE, head);
+ g_assert_cmphex(qtest_readw(qts, rpmi_response_base), ==, service_group);
+ g_assert_cmphex(qtest_readb(qts, rpmi_response_base + 2), ==,
+ service_id);
+ g_assert_cmphex(qtest_readb(qts, rpmi_response_base + 3), ==,
+ RPMI_MSG_ACKNOWLEDGEMENT);
+ g_assert_cmphex(qtest_readw(qts, rpmi_response_base + 4), ==, data_len);
+ g_assert_cmphex(qtest_readw(qts, rpmi_response_base + 6), ==,
+ RPMI_TOKEN);
+ g_test_message(
+ "RPMI_P2A_ACK shmem=0x%016" PRIx64
+ " group=0x%04x service=0x%02x type=0x%02x data_len=%u"
+ " token=0x%04x p2a_head=%u slot=0x%016" PRIx64
+ " status=0x%08x",
+ (uint64_t)RPMI_SHMEM_BASE, service_group, service_id,
+ RPMI_MSG_ACKNOWLEDGEMENT, data_len, RPMI_TOKEN, head,
+ rpmi_response_base,
+ data_len >= sizeof(uint32_t) ? rpmi_response_word(qts, 0) : 0);
+ qtest_writel(qts, RPMI_P2A_ACK_HEAD, (head + 1) % 16);
+}
+
+static void rpmi_send_sysreset(QTestState *qts, uint32_t reset_type,
+ uint8_t request_type)
+{
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_SYSTEM_RESET, request_type,
+ &reset_type, 1);
+}
+
+static void rpmi_expect_qemu_failure(const char *extra_args,
+ const char *stderr_needle)
+{
+ g_autoptr(GError) error = NULL;
+ g_auto(GStrv) split_args = NULL;
+ g_autoptr(GPtrArray) argv = g_ptr_array_new();
+ g_autofree char *stderr_data = NULL;
+ gint wait_status;
+ gboolean spawned;
+
+ g_assert_true(g_shell_parse_argv(extra_args, NULL, &split_args, &error));
+ g_assert_no_error(error);
+
+ g_ptr_array_add(argv, (gpointer)qtest_qemu_binary(NULL));
+ for (char **arg = split_args; *arg; arg++) {
+ g_ptr_array_add(argv, *arg);
+ }
+ g_ptr_array_add(argv, NULL);
+
+ spawned = g_spawn_sync(NULL, (char **)argv->pdata, NULL,
+ G_SPAWN_STDOUT_TO_DEV_NULL,
+ NULL, NULL, NULL, &stderr_data,
+ &wait_status, &error);
+ g_assert_true(spawned);
+ g_assert_no_error(error);
+ g_assert_false(g_spawn_check_exit_status(wait_status, NULL));
+ g_assert_nonnull(stderr_data);
+ g_assert_nonnull(strstr(stderr_data, stderr_needle));
+}
+
+static void test_rpmi_machine_realize_off(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=off");
+ qtest_quit(qts);
+}
+
+static void test_rpmi_machine_rejects_too_many_harts(void)
+{
+ rpmi_expect_qemu_failure(
+ "-machine virt,rpmi=on -smp 513 -display none -S",
+ "max CPUs supported by machine 'virt' is 512");
+}
+
+static void test_rpmi_base_platform_info(void)
+{
+ static const char expected[] = "QEMU RISC-V virt RPMI";
+ QTestState *qts;
+ size_t i;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ rpmi_send_request(qts, RPMI_SRVGRP_BASE,
+ RPMI_BASE_SRV_GET_PLATFORM_INFO,
+ RPMI_MSG_NORMAL_REQUEST, NULL, 0);
+
+ rpmi_expect_ack(qts, RPMI_SRVGRP_BASE,
+ RPMI_BASE_SRV_GET_PLATFORM_INFO,
+ 2 * sizeof(uint32_t) + sizeof(expected));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, sizeof(expected));
+ for (i = 0; i < sizeof(expected); i++) {
+ g_assert_cmphex(qtest_readb(qts, RPMI_P2A_ACK_SLOT0 + 16 + i), ==,
+ expected[i]);
+ }
+
+ qtest_quit(qts);
+}
+
+static void rpmi_probe_group(QTestState *qts, uint32_t service_group,
+ bool present)
+{
+ rpmi_send_request(qts, RPMI_SRVGRP_BASE,
+ RPMI_BASE_SRV_PROBE_SERVICE_GROUP,
+ RPMI_MSG_NORMAL_REQUEST, &service_group, 1);
+
+ rpmi_expect_ack(qts, RPMI_SRVGRP_BASE,
+ RPMI_BASE_SRV_PROBE_SERVICE_GROUP,
+ 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ if (present) {
+ g_assert_cmphex(rpmi_response_word(qts, 1), !=, 0);
+ } else {
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, 0);
+ }
+}
+
+static void test_rpmi_base_probe_service_groups(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on,aia=aplic-imsic");
+ rpmi_probe_group(qts, RPMI_SRVGRP_BASE, true);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_SYSTEM_RESET, true);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_HSM, true);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_SYSTEM_SUSPEND, true);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_CPPC, false);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_SYSTEM_MSI, false);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_CLOCK, false);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_MANAGEMENT_MODE, false);
+ qtest_system_reset(qts);
+ rpmi_probe_group(qts, RPMI_SRVGRP_LOGGING, false);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_sysreset_attrs(void)
+{
+ QTestState *qts;
+ uint32_t reset_type = RPMI_SYSRST_TYPE_SHUTDOWN;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES,
+ RPMI_MSG_NORMAL_REQUEST, &reset_type, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES, 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==,
+ RPMI_SYSRST_ATTRS_FLAGS_RESETTYPE);
+
+ qtest_system_reset(qts);
+ reset_type = RPMI_SYSRST_TYPE_COLD_REBOOT;
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES,
+ RPMI_MSG_NORMAL_REQUEST, &reset_type, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES, 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==,
+ RPMI_SYSRST_ATTRS_FLAGS_RESETTYPE);
+
+ qtest_system_reset(qts);
+ reset_type = RPMI_SYSRST_TYPE_INVALID;
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES,
+ RPMI_MSG_NORMAL_REQUEST, &reset_type, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES, 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_sysreset_shutdown(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ rpmi_send_sysreset(qts, RPMI_SYSRST_TYPE_SHUTDOWN,
+ RPMI_MSG_POSTED_REQUEST);
+ qtest_qmp_eventwait(qts, "SHUTDOWN");
+ qtest_quit(qts);
+}
+
+static void test_rpmi_sysreset_cold_reboot(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on -no-reboot");
+ rpmi_send_sysreset(qts, RPMI_SYSRST_TYPE_COLD_REBOOT,
+ RPMI_MSG_POSTED_REQUEST);
+ qtest_qmp_eventwait(qts, "SHUTDOWN");
+ qtest_quit(qts);
+}
+
+static void test_rpmi_sysreset_invalid_type(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ rpmi_send_sysreset(qts, RPMI_SYSRST_TYPE_INVALID,
+ RPMI_MSG_NORMAL_REQUEST);
+
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_SYSTEM_RESET, sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==,
+ RPMI_ERR_INVALID_PARAM);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_repeated_reset_after_traffic(void)
+{
+ QTestState *qts;
+ uint32_t reset_type = RPMI_SYSRST_TYPE_SHUTDOWN;
+
+ qts = qtest_init("-machine virt,rpmi=on,aia=aplic-imsic");
+ for (uint32_t i = 0; i < 5; i++) {
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES,
+ RPMI_MSG_NORMAL_REQUEST,
+ &reset_type, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_GET_ATTRIBUTES,
+ 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ qtest_system_reset(qts);
+ }
+ qtest_quit(qts);
+}
+
+static void test_rpmi_reset_clears_transport(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ rpmi_send_sysreset(qts, RPMI_SYSRST_TYPE_INVALID,
+ RPMI_MSG_NORMAL_REQUEST);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_RESET,
+ RPMI_SYSRST_SRV_SYSTEM_RESET, sizeof(uint32_t));
+
+ qtest_system_reset(qts);
+
+ g_assert_cmphex(qtest_readl(qts, RPMI_A2P_TAIL), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, RPMI_P2A_ACK_TAIL), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, RPMI_DOORBELL_BASE), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_doorbell_invalid_access(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ qtest_writeb(qts, RPMI_DOORBELL_BASE, 1);
+ qtest_writel(qts, RPMI_DOORBELL_BASE + 4, 1);
+ g_assert_cmphex(qtest_readl(qts, RPMI_DOORBELL_BASE), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_queue_bounds(void)
+{
+ QTestState *qts;
+
+ qts = qtest_init("-machine virt,rpmi=on");
+ qtest_writel(qts, RPMI_A2P_TAIL, 0x1000);
+ qtest_writel(qts, RPMI_DOORBELL_BASE, 1);
+ g_assert_cmphex(qtest_readl(qts, RPMI_A2P_HEAD), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, RPMI_P2A_ACK_TAIL), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_migration_blocked(void)
+{
+ QTestState *qts;
+ QDict *error;
+ const char *desc;
+
+ qts = qtest_init("-machine virt,rpmi=on -S");
+ error = qtest_qmp_assert_failure_ref(qts,
+ "{ 'execute': 'migrate',"
+ " 'arguments': { 'uri': 'exec:cat > /dev/null' } }");
+ desc = qdict_get_try_str(error, "desc");
+
+ g_assert_nonnull(desc);
+ g_assert_nonnull(strstr(desc, "non-migratable device"));
+ g_assert_nonnull(strstr(desc, "riscv-rpmi"));
+
+ qobject_unref(error);
+ qtest_quit(qts);
+}
+
+static void test_rpmi_hsm_hart_list(void)
+{
+ QTestState *qts;
+ uint32_t start_index = 0;
+
+ qts = qtest_init("-machine virt,rpmi=on -smp 4");
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_LIST,
+ RPMI_MSG_NORMAL_REQUEST, &start_index, 1);
+
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_LIST,
+ 7 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 2), ==, 4);
+ g_assert_cmphex(rpmi_response_word(qts, 3), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 4), ==, 1);
+ g_assert_cmphex(rpmi_response_word(qts, 5), ==, 2);
+ g_assert_cmphex(rpmi_response_word(qts, 6), ==, 3);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_hsm_multi_socket_hart_list(void)
+{
+ QTestState *qts;
+ uint32_t start_index = 0;
+
+ qts = qtest_init("-machine virt,rpmi=on "
+ "-smp 4,sockets=2,cores=2,threads=1");
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_LIST,
+ RPMI_MSG_NORMAL_REQUEST, &start_index, 1);
+
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_LIST,
+ 7 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 2), ==, 4);
+ g_assert_cmphex(rpmi_response_word(qts, 3), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 4), ==, 1);
+ g_assert_cmphex(rpmi_response_word(qts, 5), ==, 2);
+ g_assert_cmphex(rpmi_response_word(qts, 6), ==, 3);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_hsm_hart_status(void)
+{
+ QTestState *qts;
+ uint32_t hart_id = 3;
+
+ qts = qtest_init("-machine virt,rpmi=on -smp 4");
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_STATUS,
+ RPMI_MSG_NORMAL_REQUEST, &hart_id, 1);
+
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_STATUS,
+ 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==,
+ RPMI_HSM_HART_STATE_STARTED);
+
+ qtest_quit(qts);
+}
+
+static void rpmi_expect_hsm_status(QTestState *qts, uint32_t hart_id,
+ uint32_t expected_state)
+{
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_STATUS,
+ RPMI_MSG_NORMAL_REQUEST, &hart_id, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_HART_STATUS,
+ 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, expected_state);
+}
+
+static void test_rpmi_hsm_hart_control(void)
+{
+ QTestState *qts;
+ uint32_t hart_id = 1;
+ uint32_t stop_request[] = { hart_id };
+ uint32_t start_request[] = { hart_id, 0x80000000, 0 };
+ uint32_t suspend_request[] = { hart_id, 0, 0x80001000, 0 };
+ uint32_t start_index = 0;
+ uint32_t suspend_type = 0;
+
+ qts = qtest_init("-machine virt,rpmi=on -smp 2");
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_SUSPEND_TYPES,
+ RPMI_MSG_NORMAL_REQUEST, &start_index, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_SUSPEND_TYPES,
+ 4 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 2), ==, 1);
+ g_assert_cmphex(rpmi_response_word(qts, 3), ==, 0);
+
+ qtest_system_reset(qts);
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_SUSPEND_INFO,
+ RPMI_MSG_NORMAL_REQUEST, &suspend_type, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_GET_SUSPEND_INFO,
+ 6 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+
+ qtest_system_reset(qts);
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_HART_STOP,
+ RPMI_MSG_NORMAL_REQUEST, stop_request,
+ ARRAY_SIZE(stop_request));
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_HART_STOP,
+ sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ rpmi_expect_hsm_status(qts, hart_id, RPMI_HSM_HART_STATE_STOPPED);
+
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_HART_START,
+ RPMI_MSG_NORMAL_REQUEST, start_request,
+ ARRAY_SIZE(start_request));
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_HART_START,
+ sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ rpmi_expect_hsm_status(qts, hart_id, RPMI_HSM_HART_STATE_STARTED);
+
+ rpmi_send_request(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_HART_SUSPEND,
+ RPMI_MSG_NORMAL_REQUEST, suspend_request,
+ ARRAY_SIZE(suspend_request));
+ rpmi_expect_ack(qts, RPMI_SRVGRP_HSM, RPMI_HSM_SRV_HART_SUSPEND,
+ sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ rpmi_expect_hsm_status(qts, hart_id, RPMI_HSM_HART_STATE_SUSPENDED);
+
+ qtest_quit(qts);
+}
+
+static void test_rpmi_syssusp_attrs_and_suspend(void)
+{
+ QTestState *qts;
+ uint32_t suspend_type = 0;
+ uint32_t suspend_request[] = { 0, 0, 0x80000000, 0 };
+
+ qts = qtest_init("-machine virt,rpmi=on -smp 1");
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_SUSPEND,
+ RPMI_SYSSUSP_SRV_GET_ATTRIBUTES,
+ RPMI_MSG_NORMAL_REQUEST, &suspend_type, 1);
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_SUSPEND,
+ RPMI_SYSSUSP_SRV_GET_ATTRIBUTES,
+ 2 * sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ g_assert_cmphex(rpmi_response_word(qts, 1), ==, 3);
+
+ qtest_system_reset(qts);
+ rpmi_send_request(qts, RPMI_SRVGRP_SYSTEM_SUSPEND,
+ RPMI_SYSSUSP_SRV_SYSTEM_SUSPEND,
+ RPMI_MSG_NORMAL_REQUEST, suspend_request,
+ ARRAY_SIZE(suspend_request));
+ rpmi_expect_ack(qts, RPMI_SRVGRP_SYSTEM_SUSPEND,
+ RPMI_SYSSUSP_SRV_SYSTEM_SUSPEND, sizeof(uint32_t));
+ g_assert_cmphex(rpmi_response_word(qts, 0), ==, 0);
+ qtest_qmp_eventwait(qts, "SUSPEND");
+ qtest_qmp_assert_success(qts, "{ 'execute': 'system_wakeup' }");
+ qtest_qmp_eventwait(qts, "WAKEUP");
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ if (qtest_has_machine("virt")) {
+ qtest_add_func("/riscv/rpmi/machine/realize-off",
+ test_rpmi_machine_realize_off);
+ qtest_add_func("/riscv/rpmi/machine/rejects-too-many-harts",
+ test_rpmi_machine_rejects_too_many_harts);
+ qtest_add_func("/riscv/rpmi/base/platform-info",
+ test_rpmi_base_platform_info);
+ qtest_add_func("/riscv/rpmi/base/probe-service-groups",
+ test_rpmi_base_probe_service_groups);
+ qtest_add_func("/riscv/rpmi/sysreset/attrs",
+ test_rpmi_sysreset_attrs);
+ qtest_add_func("/riscv/rpmi/sysreset/shutdown",
+ test_rpmi_sysreset_shutdown);
+ qtest_add_func("/riscv/rpmi/sysreset/cold-reboot",
+ test_rpmi_sysreset_cold_reboot);
+ qtest_add_func("/riscv/rpmi/sysreset/invalid-type",
+ test_rpmi_sysreset_invalid_type);
+ qtest_add_func("/riscv/rpmi/reset/clears-transport",
+ test_rpmi_reset_clears_transport);
+ qtest_add_func("/riscv/rpmi/negative/doorbell-invalid-access",
+ test_rpmi_doorbell_invalid_access);
+ qtest_add_func("/riscv/rpmi/negative/queue-bounds",
+ test_rpmi_queue_bounds);
+ qtest_add_func("/riscv/rpmi/reset/repeated-after-traffic",
+ test_rpmi_repeated_reset_after_traffic);
+ qtest_add_func("/riscv/rpmi/migration/blocked",
+ test_rpmi_migration_blocked);
+ qtest_add_func("/riscv/rpmi/hsm/hart-list",
+ test_rpmi_hsm_hart_list);
+ qtest_add_func("/riscv/rpmi/hsm/multi-socket-hart-list",
+ test_rpmi_hsm_multi_socket_hart_list);
+ qtest_add_func("/riscv/rpmi/hsm/hart-status",
+ test_rpmi_hsm_hart_status);
+ qtest_add_func("/riscv/rpmi/hsm/hart-control",
+ test_rpmi_hsm_hart_control);
+ qtest_add_func("/riscv/rpmi/syssusp/attrs-and-suspend",
+ test_rpmi_syssusp_attrs_and_suspend);
+ }
+
+ return g_test_run();
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH 10/10] tests/functional: add initial RPMI virt guest smoke
@ 2026-07-10 8:39 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-10 13:00 UTC (permalink / raw)
To: qemu-riscv
Cc: Subrahmanya Lingappa, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu,
open list:All patches CC here
From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Boot an RPMI-enabled RISC-V virt guest and exercise the initial HSM,
system suspend, and system reset paths.
Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
---
tests/functional/riscv64/meson.build | 2 +
tests/functional/riscv64/test_rpmi_virt.py | 172 +++++++++++++++++++++
2 files changed, 174 insertions(+)
create mode 100644 tests/functional/riscv64/test_rpmi_virt.py
diff --git a/tests/functional/riscv64/meson.build b/tests/functional/riscv64/meson.build
index 5871211e89..619a0735fa 100644
--- a/tests/functional/riscv64/meson.build
+++ b/tests/functional/riscv64/meson.build
@@ -3,6 +3,7 @@
test_riscv64_timeouts = {
'boston' : 120,
'tuxrun' : 120,
+ 'rpmi_virt' : 300,
}
tests_riscv64_system_quick = [
@@ -15,4 +16,5 @@ tests_riscv64_system_thorough = [
'boston',
'sifive_u',
'tuxrun',
+ 'rpmi_virt',
]
diff --git a/tests/functional/riscv64/test_rpmi_virt.py b/tests/functional/riscv64/test_rpmi_virt.py
new file mode 100644
index 0000000000..c0f96f7d8d
--- /dev/null
+++ b/tests/functional/riscv64/test_rpmi_virt.py
@@ -0,0 +1,172 @@
+#!/usr/bin/env python3
+#
+# RISC-V virt RPMI Linux guest integration tests
+#
+# Copyright (c) 2026 Ventana Micro Systems Inc.
+# Copyright (c) 2026 Qualcomm Technologies, Inc.
+#
+# Author:
+# Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import os
+from pathlib import Path
+import subprocess
+import time
+
+from qemu_test import QemuSystemTest
+from qemu_test import exec_command
+from qemu_test import exec_command_and_wait_for_pattern
+from qemu_test import get_qemu_img
+from qemu_test import wait_for_console_pattern
+
+
+class RiscvVirtRpmiGuestTest(QemuSystemTest):
+ """Boot Linux and exercise RPMI-backed services through guest APIs.
+
+ This test intentionally keeps guest artifacts external. Set:
+
+ QEMU_TEST_RPMI_OPENSBI=/path/to/fw_jump.bin
+ QEMU_TEST_RPMI_KERNEL=/path/to/Image
+ QEMU_TEST_RPMI_ROOTFS=/path/to/rootfs.ext4
+
+ Exact RPMI packet command-ID coverage remains in riscv-rpmi qtests. This
+ functional test verifies Linux/OpenSBI integration paths visible from a
+ real guest without adding probe hooks to production RPMI sources.
+ """
+
+ timeout = 360
+ PROMPT = 'root@tuxtest:~#'
+
+ def _require_path(self, env_name):
+ value = os.getenv(env_name)
+ if not value:
+ self.skipTest(f'{env_name} is required')
+ path = Path(value)
+ if not path.exists():
+ self.skipTest(f'{env_name} does not exist: {path}')
+ return path
+
+ def setUp(self):
+ super().setUp()
+ self.opensbi = self._require_path('QEMU_TEST_RPMI_OPENSBI')
+ self.kernel = self._require_path('QEMU_TEST_RPMI_KERNEL')
+ self.rootfs = self._require_path('QEMU_TEST_RPMI_ROOTFS')
+ self.qemu_img = get_qemu_img(self)
+
+ def _create_rootfs_overlay(self):
+ overlay = Path(self.scratch_file('rootfs.qcow2'))
+ subprocess.run([
+ self.qemu_img, 'create', '-q', '-f', 'qcow2', '-F', 'raw',
+ '-b', str(self.rootfs), str(overlay),
+ ], check=True)
+ return overlay
+
+ def _boot_guest(self, smp=4):
+ self.set_machine('virt')
+ self.machine = 'virt,rpmi=on,aia=aplic-imsic'
+ rootfs_overlay = self._create_rootfs_overlay()
+
+ self.vm.set_console()
+ self.vm.add_args(
+ '-cpu', 'rv64',
+ '-smp', str(smp),
+ '-m', '1G',
+ '-bios', str(self.opensbi),
+ '-kernel', str(self.kernel),
+ '-append', 'printk.time=0 root=/dev/vda rw console=ttyS0 '
+ 'earlycon=sbi loglevel=8 no_console_suspend',
+ '-blockdev', 'driver=qcow2,file.driver=file,'
+ f'file.filename={rootfs_overlay},node-name=hd0',
+ '-device', 'virtio-blk-device,drive=hd0',
+ )
+ self.vm.launch()
+
+ def _expect_rpmi_firmware_and_linux(self):
+ patterns = [
+ 'Platform HSM Device : rpmi-hsm',
+ 'Platform Suspend Device : rpmi-system-suspend',
+ 'Standard SBI Extensions',
+ 'susp',
+ 'tuxtest login:',
+ ]
+ for pattern in patterns:
+ wait_for_console_pattern(self, pattern)
+ exec_command_and_wait_for_pattern(self, 'root', self.PROMPT)
+
+ def _run_marker_command(self, command, marker, failure_marker=None):
+ out = exec_command_and_wait_for_pattern(self, command, marker,
+ failure_message=failure_marker)
+ self.assertIn(marker.encode(), out)
+ return out
+
+ def _validate_hsm_boot_start(self, online_cpus):
+ self._run_marker_command(
+ 'echo RPMI_LINUX_HSM_START_BEGIN; '
+ 'cat /sys/devices/system/cpu/present; '
+ 'online=$(cat /sys/devices/system/cpu/online); '
+ 'echo "$online"; '
+ f'if [ "$online" = "{online_cpus}" ]; '
+ 'then echo RPMI_LINUX_HSM_START_PASS; '
+ 'else echo RPMI_LINUX_HSM_START_FAIL; fi',
+ 'RPMI_LINUX_HSM_START_PASS', 'RPMI_LINUX_HSM_START_FAIL')
+
+ def _validate_hsm_stop(self):
+ self._run_marker_command(
+ 'echo RPMI_LINUX_HSM_STOP_BEGIN; '
+ 'if [ -w /sys/devices/system/cpu/cpu1/online ]; then '
+ 'echo 0 > /sys/devices/system/cpu/cpu1/online && '
+ '[ "$(cat /sys/devices/system/cpu/online)" = 0 ] && '
+ 'echo RPMI_LINUX_HSM_STOP_PASS || echo RPMI_LINUX_HSM_STOP_FAIL; '
+ 'else echo RPMI_LINUX_HSM_STOP_FAIL; fi',
+ 'RPMI_LINUX_HSM_STOP_PASS', 'RPMI_LINUX_HSM_STOP_FAIL')
+
+ def _validate_system_suspend(self):
+ exec_command(
+ self,
+ 'echo RPMI_LINUX_SUSPEND_BEGIN; '
+ 'if grep -qw mem /sys/power/state; then '
+ 'echo mem > /sys/power/state; '
+ 'else echo RPMI_LINUX_SUSPEND_FAIL; fi')
+ wait_for_console_pattern(self, 'RPMI_LINUX_SUSPEND_BEGIN')
+ wait_for_console_pattern(self, 'PM: suspend entry')
+ self.vm.event_wait('SUSPEND', 10)
+ self.vm.qmp('system_wakeup')
+ self.vm.event_wait('WAKEUP', 10)
+ wait_for_console_pattern(self, 'PM: suspend exit')
+ wait_for_console_pattern(self, self.PROMPT)
+ self._run_marker_command(
+ 'marker=RPMI_LINUX_WAKEUP; echo ${marker}_PASS',
+ 'RPMI_LINUX_WAKEUP_PASS')
+
+ def _validate_reset_poweroff(self):
+ exec_command(self, 'echo RPMI_LINUX_RESET_POWEROFF_BEGIN; poweroff -f')
+ wait_for_console_pattern(self, 'RPMI_LINUX_RESET_POWEROFF_BEGIN')
+ wait_for_console_pattern(self, 'reboot: Power down')
+ self.vm.wait()
+
+ def test_linux_guest_service_enumeration(self):
+ self._boot_guest(smp=4)
+ self._expect_rpmi_firmware_and_linux()
+ self._validate_hsm_boot_start('0-3')
+
+ def test_linux_guest_hsm_stop(self):
+ self._boot_guest(smp=2)
+ self._expect_rpmi_firmware_and_linux()
+ self._validate_hsm_boot_start('0-1')
+ self._validate_hsm_stop()
+
+ def test_linux_guest_suspend(self):
+ self._boot_guest(smp=1)
+ self._expect_rpmi_firmware_and_linux()
+ self._validate_system_suspend()
+
+ def test_linux_guest_reset_poweroff(self):
+ self._boot_guest(smp=1)
+ self._expect_rpmi_firmware_and_linux()
+ self._validate_reset_poweroff()
+
+
+if __name__ == '__main__':
+ QemuSystemTest.main()
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH v2 01/10] build: add optional librpmi dependency
2026-07-10 8:39 ` [PATCH v2 " Subrahmanya Lingappa
(?)
(?)
@ 2026-07-13 1:41 ` Alistair Francis
2026-07-14 7:41 ` Subrahmanya Lingappa
-1 siblings, 1 reply; 35+ messages in thread
From: Alistair Francis @ 2026-07-13 1:41 UTC (permalink / raw)
To: Subrahmanya Lingappa; +Cc: qemu-devel
On Fri, Jul 10, 2026 at 10:23 PM Subrahmanya Lingappa
<subrahmanya.lingappa@oss.qualcomm.com> wrote:
>
> From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
>
> Add a meson option and config probe for librpmi so RISC-V machines can
> opt into RPMI support when the library is available. Keep the dependency
> optional so default builds without librpmi continue to work.
>
> Signed-off-by: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
Can we add some documentation of how get librpmi support to work? Do I
just apt/dnf/pacman/apk install it? Do I need a certain version?
Alistair
> ---
> Kconfig.host | 3 +++
> meson.build | 14 ++++++++++++++
> meson_options.txt | 2 ++
> scripts/meson-buildoptions.sh | 3 +++
> 4 files changed, 22 insertions(+)
>
> diff --git a/Kconfig.host b/Kconfig.host
> index 933425c74b..6f98dadb2e 100644
> --- a/Kconfig.host
> +++ b/Kconfig.host
> @@ -8,6 +8,9 @@ config LINUX
> config LIBCBOR
> bool
>
> +config LIBRPMI
> + bool
> +
> config GNUTLS
> bool
>
> diff --git a/meson.build b/meson.build
> index 19e123423b..103b4649b8 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -2005,6 +2005,17 @@ if lzo.found() and not cc.links('''
> endif
> endif
>
> +librpmi = not_found
> +if not get_option('librpmi').auto() or have_system
> + librpmi = dependency('librpmi', required: get_option('librpmi'),
> + method: 'pkg-config')
> + if librpmi.found() and not cc.has_header_symbol('librpmi.h',
> + 'rpmi_service_group_logging_create',
> + dependencies: librpmi)
> + error('librpmi support requires the RPMI logging service group API')
> + endif
> +endif
> +
> numa = not_found
> if not get_option('numa').auto() or have_system or have_tools
> numa = cc.find_library('numa', has_headers: ['numa.h'],
> @@ -2427,6 +2438,7 @@ config_host_data.set('CONFIG_LINUX', host_os == 'linux')
> config_host_data.set('CONFIG_POSIX', host_os != 'windows')
> config_host_data.set('CONFIG_WIN32', host_os == 'windows')
> config_host_data.set('CONFIG_LZO', lzo.found())
> +config_host_data.set('CONFIG_LIBRPMI', librpmi.found())
> config_host_data.set('CONFIG_MPATH', mpathpersist.found())
> config_host_data.set('CONFIG_BLKIO', blkio.found())
> if blkio.found()
> @@ -3258,6 +3270,7 @@ host_kconfig = \
> (multiprocess_allowed ? ['CONFIG_MULTIPROCESS_ALLOWED=y'] : []) + \
> (vfio_user_server_allowed ? ['CONFIG_VFIO_USER_SERVER_ALLOWED=y'] : []) + \
> (hv_balloon ? ['CONFIG_HV_BALLOON_POSSIBLE=y'] : []) + \
> + (librpmi.found() ? ['CONFIG_LIBRPMI=y'] : []) + \
> (have_rust ? ['CONFIG_HAVE_RUST=y'] : [])
>
> ignored = [ 'TARGET_XML_FILES', 'TARGET_ABI_DIR' ]
> @@ -4971,6 +4984,7 @@ summary_info += {'TPM support': have_tpm}
> summary_info += {'IGVM support': igvm}
> summary_info += {'libssh support': libssh}
> summary_info += {'lzo support': lzo}
> +summary_info += {'librpmi support': librpmi}
> summary_info += {'snappy support': snappy}
> summary_info += {'bzip2 support': libbzip2}
> summary_info += {'lzfse support': liblzfse}
> diff --git a/meson_options.txt b/meson_options.txt
> index a07cb47d35..9f773a7886 100644
> --- a/meson_options.txt
> +++ b/meson_options.txt
> @@ -204,6 +204,8 @@ option('lzfse', type : 'feature', value : 'auto',
> description: 'lzfse support for DMG images')
> option('lzo', type : 'feature', value : 'auto',
> description: 'lzo compression support')
> +option('librpmi', type : 'feature', value : 'auto',
> + description: 'RISC-V RPMI support')
> option('pvg', type: 'feature', value: 'auto',
> description: 'macOS paravirtualized graphics support')
> option('rbd', type : 'feature', value : 'auto',
> diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
> index c003985047..e8a20068e7 100644
> --- a/scripts/meson-buildoptions.sh
> +++ b/scripts/meson-buildoptions.sh
> @@ -140,6 +140,7 @@ meson_options_help() {
> printf "%s\n" ' libkeyutils Linux keyutils support'
> printf "%s\n" ' libnfs libnfs block device driver'
> printf "%s\n" ' libpmem libpmem support'
> + printf "%s\n" ' librpmi RISC-V RPMI support'
> printf "%s\n" ' libssh ssh block device support'
> printf "%s\n" ' libudev Use libudev to enumerate host devices'
> printf "%s\n" ' libusb libusb support for USB passthrough'
> @@ -376,6 +377,8 @@ _meson_option_parse() {
> --disable-libnfs) printf "%s" -Dlibnfs=disabled ;;
> --enable-libpmem) printf "%s" -Dlibpmem=enabled ;;
> --disable-libpmem) printf "%s" -Dlibpmem=disabled ;;
> + --enable-librpmi) printf "%s" -Dlibrpmi=enabled ;;
> + --disable-librpmi) printf "%s" -Dlibrpmi=disabled ;;
> --enable-libssh) printf "%s" -Dlibssh=enabled ;;
> --disable-libssh) printf "%s" -Dlibssh=disabled ;;
> --enable-libudev) printf "%s" -Dlibudev=enabled ;;
> --
> 2.43.0
>
>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v2 01/10] build: add optional librpmi dependency
2026-07-13 1:41 ` Alistair Francis
@ 2026-07-14 7:41 ` Subrahmanya Lingappa
0 siblings, 0 replies; 35+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-14 7:41 UTC (permalink / raw)
To: Alistair Francis; +Cc: qemu-devel
[-- Attachment #1: Type: text/plain, Size: 5954 bytes --]
Alistair,
On Mon, Jul 13, 2026 at 7:11 AM Alistair Francis <alistair23@gmail.com>
wrote:
> On Fri, Jul 10, 2026 at 10:23 PM Subrahmanya Lingappa
> <subrahmanya.lingappa@oss.qualcomm.com> wrote:
> >
> > From: Subrahmanya Lingappa <subrahmanya.lingappa@oss.qualcomm.com>
> >
> > Add a meson option and config probe for librpmi so RISC-V machines can
> > opt into RPMI support when the library is available. Keep the dependency
> > optional so default builds without librpmi continue to work.
> >
> > Signed-off-by: Subrahmanya Lingappa <
> subrahmanya.lingappa@oss.qualcomm.com>
>
> Can we add some documentation of how get librpmi support to work? Do I
> just apt/dnf/pacman/apk install it? Do I need a certain version?
>
> Yes, good point. The v2 docs only said that librpmi must be installed so
pkg-config can find librpmi.pc, which is too terse. I will expand the
docs to
state that QEMU requires librpmi 1.0.0 or newer,
show source install steps from the upstream librpmi repo, and mention the
Debian/RPM package targets provided by librpmi, while it's also documented
in detail within librpmi repo.
Subrahmanya
Alistair
>
> > ---
> > Kconfig.host | 3 +++
> > meson.build | 14 ++++++++++++++
> > meson_options.txt | 2 ++
> > scripts/meson-buildoptions.sh | 3 +++
> > 4 files changed, 22 insertions(+)
> >
> > diff --git a/Kconfig.host b/Kconfig.host
> > index 933425c74b..6f98dadb2e 100644
> > --- a/Kconfig.host
> > +++ b/Kconfig.host
> > @@ -8,6 +8,9 @@ config LINUX
> > config LIBCBOR
> > bool
> >
> > +config LIBRPMI
> > + bool
> > +
> > config GNUTLS
> > bool
> >
> > diff --git a/meson.build b/meson.build
> > index 19e123423b..103b4649b8 100644
> > --- a/meson.build
> > +++ b/meson.build
> > @@ -2005,6 +2005,17 @@ if lzo.found() and not cc.links('''
> > endif
> > endif
> >
> > +librpmi = not_found
> > +if not get_option('librpmi').auto() or have_system
> > + librpmi = dependency('librpmi', required: get_option('librpmi'),
> > + method: 'pkg-config')
> > + if librpmi.found() and not cc.has_header_symbol('librpmi.h',
> > +
> 'rpmi_service_group_logging_create',
> > + dependencies: librpmi)
> > + error('librpmi support requires the RPMI logging service group API')
> > + endif
> > +endif
> > +
> > numa = not_found
> > if not get_option('numa').auto() or have_system or have_tools
> > numa = cc.find_library('numa', has_headers: ['numa.h'],
> > @@ -2427,6 +2438,7 @@ config_host_data.set('CONFIG_LINUX', host_os ==
> 'linux')
> > config_host_data.set('CONFIG_POSIX', host_os != 'windows')
> > config_host_data.set('CONFIG_WIN32', host_os == 'windows')
> > config_host_data.set('CONFIG_LZO', lzo.found())
> > +config_host_data.set('CONFIG_LIBRPMI', librpmi.found())
> > config_host_data.set('CONFIG_MPATH', mpathpersist.found())
> > config_host_data.set('CONFIG_BLKIO', blkio.found())
> > if blkio.found()
> > @@ -3258,6 +3270,7 @@ host_kconfig = \
> > (multiprocess_allowed ? ['CONFIG_MULTIPROCESS_ALLOWED=y'] : []) + \
> > (vfio_user_server_allowed ? ['CONFIG_VFIO_USER_SERVER_ALLOWED=y'] :
> []) + \
> > (hv_balloon ? ['CONFIG_HV_BALLOON_POSSIBLE=y'] : []) + \
> > + (librpmi.found() ? ['CONFIG_LIBRPMI=y'] : []) + \
> > (have_rust ? ['CONFIG_HAVE_RUST=y'] : [])
> >
> > ignored = [ 'TARGET_XML_FILES', 'TARGET_ABI_DIR' ]
> > @@ -4971,6 +4984,7 @@ summary_info += {'TPM support': have_tpm}
> > summary_info += {'IGVM support': igvm}
> > summary_info += {'libssh support': libssh}
> > summary_info += {'lzo support': lzo}
> > +summary_info += {'librpmi support': librpmi}
> > summary_info += {'snappy support': snappy}
> > summary_info += {'bzip2 support': libbzip2}
> > summary_info += {'lzfse support': liblzfse}
> > diff --git a/meson_options.txt b/meson_options.txt
> > index a07cb47d35..9f773a7886 100644
> > --- a/meson_options.txt
> > +++ b/meson_options.txt
> > @@ -204,6 +204,8 @@ option('lzfse', type : 'feature', value : 'auto',
> > description: 'lzfse support for DMG images')
> > option('lzo', type : 'feature', value : 'auto',
> > description: 'lzo compression support')
> > +option('librpmi', type : 'feature', value : 'auto',
> > + description: 'RISC-V RPMI support')
> > option('pvg', type: 'feature', value: 'auto',
> > description: 'macOS paravirtualized graphics support')
> > option('rbd', type : 'feature', value : 'auto',
> > diff --git a/scripts/meson-buildoptions.sh
> b/scripts/meson-buildoptions.sh
> > index c003985047..e8a20068e7 100644
> > --- a/scripts/meson-buildoptions.sh
> > +++ b/scripts/meson-buildoptions.sh
> > @@ -140,6 +140,7 @@ meson_options_help() {
> > printf "%s\n" ' libkeyutils Linux keyutils support'
> > printf "%s\n" ' libnfs libnfs block device driver'
> > printf "%s\n" ' libpmem libpmem support'
> > + printf "%s\n" ' librpmi RISC-V RPMI support'
> > printf "%s\n" ' libssh ssh block device support'
> > printf "%s\n" ' libudev Use libudev to enumerate host
> devices'
> > printf "%s\n" ' libusb libusb support for USB passthrough'
> > @@ -376,6 +377,8 @@ _meson_option_parse() {
> > --disable-libnfs) printf "%s" -Dlibnfs=disabled ;;
> > --enable-libpmem) printf "%s" -Dlibpmem=enabled ;;
> > --disable-libpmem) printf "%s" -Dlibpmem=disabled ;;
> > + --enable-librpmi) printf "%s" -Dlibrpmi=enabled ;;
> > + --disable-librpmi) printf "%s" -Dlibrpmi=disabled ;;
> > --enable-libssh) printf "%s" -Dlibssh=enabled ;;
> > --disable-libssh) printf "%s" -Dlibssh=disabled ;;
> > --enable-libudev) printf "%s" -Dlibudev=enabled ;;
> > --
> > 2.43.0
> >
> >
>
[-- Attachment #2: Type: text/html, Size: 8364 bytes --]
^ permalink raw reply [flat|nested] 35+ messages in thread
end of thread, other threads:[~2026-07-14 7:42 UTC | newest]
Thread overview: 35+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-10 5:43 [PATCH v2 00/10] riscv: add initial RPMI virt support Subrahmanya Lingappa
2026-07-10 13:00 ` [PATCH " Subrahmanya Lingappa
2026-07-10 8:39 ` [PATCH v2 " Subrahmanya Lingappa
2026-07-10 5:43 ` [PATCH v2 01/10] build: add optional librpmi dependency Subrahmanya Lingappa
2026-07-10 13:00 ` [PATCH " Subrahmanya Lingappa
2026-07-10 8:39 ` [PATCH v2 " Subrahmanya Lingappa
2026-07-13 1:41 ` Alistair Francis
2026-07-14 7:41 ` Subrahmanya Lingappa
2026-07-10 5:43 ` [PATCH v2 02/10] hw/misc: add RISC-V RPMI transport and Base support Subrahmanya Lingappa
2026-07-10 13:00 ` [PATCH " Subrahmanya Lingappa
2026-07-10 8:39 ` [PATCH v2 " Subrahmanya Lingappa
2026-07-10 5:43 ` [PATCH v2 03/10] hw/riscv: add RPMI FDT helpers Subrahmanya Lingappa
2026-07-10 5:43 ` Subrahmanya Lingappa
2026-07-10 13:00 ` [PATCH " Subrahmanya Lingappa
2026-07-10 8:39 ` [PATCH v2 " Subrahmanya Lingappa
2026-07-10 5:43 ` [PATCH v2 04/10] hw/riscv/virt: add opt-in RPMI base support Subrahmanya Lingappa
2026-07-10 13:00 ` [PATCH " Subrahmanya Lingappa
2026-07-10 8:39 ` [PATCH v2 " Subrahmanya Lingappa
2026-07-10 5:43 ` [PATCH v2 05/10] riscv: add RPMI system reset service Subrahmanya Lingappa
2026-07-10 5:43 ` Subrahmanya Lingappa
2026-07-10 13:00 ` [PATCH " Subrahmanya Lingappa
2026-07-10 8:39 ` [PATCH v2 " Subrahmanya Lingappa
2026-07-10 5:43 ` [PATCH v2 06/10] riscv: add RPMI HSM service Subrahmanya Lingappa
2026-07-10 5:43 ` Subrahmanya Lingappa
2026-07-10 13:00 ` [PATCH " Subrahmanya Lingappa
2026-07-10 8:39 ` [PATCH v2 " Subrahmanya Lingappa
2026-07-10 5:43 ` [PATCH v2 07/10] riscv: add RPMI system suspend service Subrahmanya Lingappa
2026-07-10 13:00 ` [PATCH " Subrahmanya Lingappa
2026-07-10 8:39 ` [PATCH v2 " Subrahmanya Lingappa
2026-07-10 5:43 ` [PATCH v2 08/10] docs/system/riscv: document initial virt RPMI support Subrahmanya Lingappa
2026-07-10 13:00 ` [PATCH " Subrahmanya Lingappa
2026-07-10 8:39 ` [PATCH v2 " Subrahmanya Lingappa
2026-07-10 5:43 ` [PATCH v2 09/10] tests/qtest: cover initial RISC-V RPMI services Subrahmanya Lingappa
2026-07-10 5:43 ` Subrahmanya Lingappa
2026-07-10 13:00 ` [PATCH " Subrahmanya Lingappa
2026-07-10 8:39 ` [PATCH v2 " Subrahmanya Lingappa
2026-07-10 5:43 ` [PATCH v2 10/10] tests/functional: add initial RPMI virt guest smoke Subrahmanya Lingappa
2026-07-10 5:43 ` Subrahmanya Lingappa
2026-07-10 13:00 ` [PATCH " Subrahmanya Lingappa
2026-07-10 8:39 ` [PATCH v2 " Subrahmanya Lingappa
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.