From: Subrahmanya Lingappa <[email protected]> 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 <[email protected]> --- 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; -- 2.43.0
