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