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 <[email protected]>
+ */
+
+#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