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