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