From: Subrahmanya Lingappa <[email protected]> RPMI (RISC-V Platform Management Interface) is the RISC-V non-ISA platform management specification. It defines a shared-memory transport, message protocol, and service groups for platform management.
Add the QEMU transport backend and Base service plumbing for virt using librpmi. This implementation targets the RISC-V Platform Management Interface (RPMI) Specification v1.0 Ratified: https://github.com/riscv-non-isa/riscv-rpmi/releases/download/v1.0/riscv-rpmi.pdf The librpmi backend is maintained at: https://github.com/riscv-software-src/librpmi Signed-off-by: Subrahmanya Lingappa <[email protected]> --- MAINTAINERS | 2 + hw/misc/Kconfig | 4 + hw/misc/meson.build | 3 + hw/misc/riscv_rpmi.c | 476 ++++++++++++++++++++++++++++++++++ hw/misc/riscv_rpmi_internal.h | 24 ++ include/hw/misc/riscv_rpmi.h | 90 +++++++ 6 files changed, 599 insertions(+) create mode 100644 hw/misc/riscv_rpmi.c create mode 100644 hw/misc/riscv_rpmi_internal.h create mode 100644 include/hw/misc/riscv_rpmi.h diff --git a/MAINTAINERS b/MAINTAINERS index 93df53d87f..8e91a31757 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -359,6 +359,8 @@ F: target/riscv/ F: hw/char/riscv_htif.c F: hw/riscv/ F: hw/intc/riscv* +F: hw/misc/riscv_rpmi* +F: include/hw/misc/riscv_rpmi.h F: include/hw/char/riscv_htif.h F: include/hw/riscv/ F: common-user/host/riscv* diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index 1543ee6653..998944c342 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -258,3 +258,7 @@ config XLNX_ZYNQ_DDRC bool source macio/Kconfig + +config RISCV_RPMI + bool + depends on LIBRPMI diff --git a/hw/misc/meson.build b/hw/misc/meson.build index 23265f6035..e7c2ebe28f 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -168,3 +168,6 @@ system_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa_ec.c')) # HPPA devices system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c')) +system_ss.add(when: 'CONFIG_RISCV_RPMI', if_true: [files( + 'riscv_rpmi.c', +), librpmi]) diff --git a/hw/misc/riscv_rpmi.c b/hw/misc/riscv_rpmi.c new file mode 100644 index 0000000000..c4b115b659 --- /dev/null +++ b/hw/misc/riscv_rpmi.c @@ -0,0 +1,476 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * RISC-V RPMI transport device. + * + * Copyright (c) 2026 Qualcomm Technologies, Inc. + * Author: + * Subrahmanya Lingappa <[email protected]> + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "exec/cpu-common.h" +#include "hw/core/qdev-properties.h" +#include "hw/misc/riscv_rpmi.h" +#include "hw/misc/riscv_rpmi_internal.h" +#include "migration/vmstate.h" +#include "system/address-spaces.h" +#include "system/runstate.h" +#include "librpmi.h" +#include "librpmi_env.h" + +void *rpmi_env_zalloc(rpmi_size_t size) +{ + return g_malloc0(size); +} + +void rpmi_env_free(void *ptr) +{ + g_free(ptr); +} + +void rpmi_env_writel(rpmi_uint64_t addr, rpmi_uint32_t val) +{ + cpu_physical_memory_write(addr, &val, sizeof(val)); +} + +static bool riscv_rpmi_addr_in_range(uint64_t addr, uint32_t len, + uint64_t base, uint64_t size) +{ + return len && size >= len && addr >= base && addr - base <= size - len; +} + +static enum rpmi_error shmem_qemu_read(void *priv, rpmi_uint64_t addr, + void *buf, rpmi_uint32_t len) +{ + RiscvRpmiState *s = RISCV_RPMI(priv); + + if (!riscv_rpmi_addr_in_range(addr, len, s->shmem_base, s->shmem_size) || + addr != (rpmi_uint64_t)addr || len != (rpmi_uint32_t)len) { + return RPMI_ERR_INVALID_ADDR; + } + + cpu_physical_memory_read(addr, buf, len); + return RPMI_SUCCESS; +} + +static enum rpmi_error shmem_qemu_write(void *priv, rpmi_uint64_t addr, + const void *buf, rpmi_uint32_t len) +{ + RiscvRpmiState *s = RISCV_RPMI(priv); + + if (!riscv_rpmi_addr_in_range(addr, len, s->shmem_base, s->shmem_size) || + addr != (rpmi_uint64_t)addr || len != (rpmi_uint32_t)len) { + return RPMI_ERR_INVALID_ADDR; + } + + cpu_physical_memory_write(addr, buf, len); + return RPMI_SUCCESS; +} + +static enum rpmi_error shmem_qemu_fill(void *priv, rpmi_uint64_t addr, + char ch, rpmi_uint32_t len) +{ + while (len--) { + shmem_qemu_write(priv, addr++, &ch, sizeof(ch)); + } + + return RPMI_SUCCESS; +} + +const struct rpmi_shmem_platform_ops rpmi_shmem_qemu_ops = { + .read = shmem_qemu_read, + .write = shmem_qemu_write, + .fill = shmem_qemu_fill, +}; + +static uint64_t riscv_rpmi_read(void *opaque, hwaddr offset, unsigned int size) +{ + RiscvRpmiState *s = opaque; + + if (offset != 0 || size != 4) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid read at 0x%" HWADDR_PRIx " size %u\n", + __func__, offset, size); + return 0; + } + + return s->doorbell; +} + +static bool riscv_rpmi_transport_indices_valid(RiscvRpmiState *s); + +static void riscv_rpmi_write(void *opaque, hwaddr offset, uint64_t value, + unsigned int size) +{ + RiscvRpmiState *s = opaque; + + if (offset != 0 || size != 4) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid write at 0x%" HWADDR_PRIx " size %u\n", + __func__, offset, size); + return; + } + + s->doorbell = value; + + if (value == 1) { + if (s->context && riscv_rpmi_transport_indices_valid(s)) { + rpmi_context_process_a2p_request(s->context); + rpmi_context_process_all_events(s->context); + } + s->doorbell = 0; + } +} + +static const MemoryRegionOps riscv_rpmi_ops = { + .read = riscv_rpmi_read, + .write = riscv_rpmi_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static bool riscv_rpmi_queue_indices_valid(RiscvRpmiState *s, + uint64_t queue_base, + uint32_t queue_size) +{ + uint8_t *shmem = memory_region_get_ram_ptr(&s->shmem); + uint32_t data_slots = queue_size / RPMI_QUEUE_SLOT_SIZE - 2; + uint32_t head; + uint32_t tail; + + head = ldl_le_p(shmem + queue_base); + tail = ldl_le_p(shmem + queue_base + RPMI_QUEUE_SLOT_SIZE); + if (head >= data_slots || tail >= data_slots) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid queue indices head %u tail %u slots %u\n", + __func__, head, tail, data_slots); + return false; + } + + return true; +} + +static bool riscv_rpmi_transport_indices_valid(RiscvRpmiState *s) +{ + if (!s->has_shmem || !s->a2p_req_size) { + return false; + } + + if (!riscv_rpmi_queue_indices_valid(s, 0, s->a2p_req_size)) { + return false; + } + + return riscv_rpmi_queue_indices_valid(s, s->a2p_req_size, + s->a2p_req_size); +} + +static void riscv_rpmi_configure_base(RiscvRpmiState *s, + const RiscvRpmiConfig *cfg) +{ + s->platform_info = g_strdup(cfg->platform_info); + s->services = cfg->services; + s->service_count = cfg->service_count; + + if (cfg->hart_count) { + s->hart_count = cfg->hart_count; + if (cfg->hart_ids) { + s->hart_ids = g_memdup2(cfg->hart_ids, + cfg->hart_count * sizeof(*cfg->hart_ids)); + } + } +} + +static void riscv_rpmi_init(Object *obj) +{ + RiscvRpmiState *s = RISCV_RPMI(obj); + + memory_region_init_io(&s->mmio, obj, &riscv_rpmi_ops, s, + TYPE_RISCV_RPMI, RPMI_DBREG_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio); +} + +static void riscv_rpmi_reset(DeviceState *dev) +{ + RiscvRpmiState *s = RISCV_RPMI(dev); + + s->doorbell = 0; + + if (s->has_shmem) { + memset(memory_region_get_ram_ptr(&s->shmem), 0, s->shmem_size); + memory_region_set_dirty(&s->shmem, 0, s->shmem_size); + } + +} + +static void riscv_rpmi_cleanup(RiscvRpmiState *s) +{ + + + if (s->context) { + rpmi_context_destroy(s->context); + s->context = NULL; + } + + if (s->transport) { + rpmi_transport_shmem_destroy(s->transport); + s->transport = NULL; + } + + if (s->rpmi_shmem) { + rpmi_shmem_destroy(s->rpmi_shmem); + s->rpmi_shmem = NULL; + } + + if (s->has_shmem) { + memory_region_del_subregion(get_system_memory(), &s->shmem); + s->has_shmem = false; + } +} + +bool riscv_rpmi_service_enabled(RiscvRpmiState *s, RiscvRpmiServiceKind kind) +{ + uint32_t i; + + for (i = 0; i < s->service_count; i++) { + if (s->services[i].kind == kind) { + return true; + } + } + + return false; +} + +static bool riscv_rpmi_validate_config(RiscvRpmiState *s, Error **errp) +{ + uint64_t queue_bytes; + + if (!s->shmem_size) { + error_setg(errp, "RPMI shared memory size must be non-zero"); + return false; + } + + if (s->shmem_base & (RPMI_QUEUE_SLOT_SIZE - 1)) { + error_setg(errp, "RPMI shared memory base must be %u-byte aligned", + RPMI_QUEUE_SLOT_SIZE); + return false; + } + + if (!s->a2p_req_size) { + error_setg(errp, "RPMI A2P request queue size must be non-zero"); + return false; + } + + if (!s->hart_count || !s->hart_ids) { + error_setg(errp, "RPMI transport requires hart IDs"); + return false; + } + + for (uint32_t i = 0; i < s->hart_count; i++) { + for (uint32_t j = i + 1; j < s->hart_count; j++) { + if (s->hart_ids[i] == s->hart_ids[j]) { + error_setg(errp, "RPMI hart ID %u is duplicated", + s->hart_ids[i]); + return false; + } + } + } + + if (s->service_count && !s->services) { + error_setg(errp, "RPMI service count requires service descriptors"); + return false; + } + + if (s->a2p_req_size % RPMI_QUEUE_SLOT_SIZE) { + error_setg(errp, + "RPMI A2P request queue size must be a multiple of %u", + RPMI_QUEUE_SLOT_SIZE); + return false; + } + + if (s->p2a_req_size % RPMI_QUEUE_SLOT_SIZE) { + error_setg(errp, + "RPMI P2A request queue size must be a multiple of %u", + RPMI_QUEUE_SLOT_SIZE); + return false; + } + + queue_bytes = 2 * (uint64_t)s->a2p_req_size + + 2 * (uint64_t)s->p2a_req_size; + if (queue_bytes > s->shmem_size) { + error_setg(errp, + "RPMI shared memory size 0x%" PRIx64 + " is too small for queues 0x%" PRIx64, + s->shmem_size, queue_bytes); + return false; + } + + return true; +} + +static bool riscv_rpmi_add_service_group(RiscvRpmiState *s, + const RiscvRpmiServiceConfig *service, + Error **errp) +{ + switch (service->kind) { + default: + error_setg(errp, "unsupported RPMI service kind %d", service->kind); + return false; + } +} + +static bool riscv_rpmi_init_services(RiscvRpmiState *s, Error **errp) +{ + uint32_t i; + + for (i = 0; i < s->service_count; i++) { + if (!riscv_rpmi_add_service_group(s, &s->services[i], errp)) { + return false; + } + } + + return true; +} + +static bool riscv_rpmi_init_context(RiscvRpmiState *s, Error **errp) +{ + const char *platform_info = s->platform_info ?: RPMI_PLAT_INFO; + + s->rpmi_shmem = rpmi_shmem_create("rpmi-shmem", s->shmem_base, + s->shmem_size, + &rpmi_shmem_qemu_ops, s); + if (!s->rpmi_shmem) { + error_setg(errp, "failed to create RPMI shared memory backend"); + return false; + } + + s->transport = rpmi_transport_shmem_create("rpmi-shmem-transport", + RPMI_QUEUE_SLOT_SIZE, + s->a2p_req_size, + s->p2a_req_size, + s->rpmi_shmem); + if (!s->transport) { + error_setg(errp, "failed to create RPMI shared memory transport"); + return false; + } + + s->context = rpmi_context_create("rpmi-virt", s->transport, + RPMI_SRVGRP_ID_MAX_COUNT, + RPMI_PRIVILEGE_M_MODE, + strlen(platform_info) + 1, + platform_info); + if (!s->context) { + error_setg(errp, "failed to create RPMI context"); + return false; + } + + return riscv_rpmi_init_services(s, errp); +} + +static void riscv_rpmi_realize(DeviceState *dev, Error **errp) +{ + RiscvRpmiState *s = RISCV_RPMI(dev); + g_autofree char *name = NULL; + + if (!riscv_rpmi_validate_config(s, errp)) { + return; + } + + name = g_strdup_printf("rpmi-shmem@%" PRIx64, s->shmem_base); + if (!memory_region_init_ram(&s->shmem, OBJECT(dev), name, s->shmem_size, + errp)) { + return; + } + + memory_region_add_subregion(get_system_memory(), s->shmem_base, &s->shmem); + s->has_shmem = true; + + if (!riscv_rpmi_init_context(s, errp)) { + riscv_rpmi_cleanup(s); + } +} + +static void riscv_rpmi_unrealize(DeviceState *dev) +{ + riscv_rpmi_cleanup(RISCV_RPMI(dev)); +} + +static const VMStateDescription riscv_rpmi_vmstate = { + .name = TYPE_RISCV_RPMI, + .unmigratable = 1, +}; + +static const Property riscv_rpmi_properties[] = { + DEFINE_PROP_UINT64("shmem-base", RiscvRpmiState, shmem_base, 0), + DEFINE_PROP_UINT64("shmem-size", RiscvRpmiState, shmem_size, 0), + DEFINE_PROP_UINT32("a2p-req-size", RiscvRpmiState, a2p_req_size, 0), + DEFINE_PROP_UINT32("p2a-req-size", RiscvRpmiState, p2a_req_size, 0), +}; + +static void riscv_rpmi_finalize(Object *obj) +{ + RiscvRpmiState *s = RISCV_RPMI(obj); + + g_free(s->platform_info); + g_free(s->hart_ids); +} + +static void riscv_rpmi_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = riscv_rpmi_realize; + dc->unrealize = riscv_rpmi_unrealize; + dc->vmsd = &riscv_rpmi_vmstate; + device_class_set_legacy_reset(dc, riscv_rpmi_reset); + device_class_set_props(dc, riscv_rpmi_properties); +} + +static const TypeInfo riscv_rpmi_info = { + .name = TYPE_RISCV_RPMI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(RiscvRpmiState), + .instance_init = riscv_rpmi_init, + .instance_finalize = riscv_rpmi_finalize, + .class_init = riscv_rpmi_class_init, +}; + +static void riscv_rpmi_register_types(void) +{ + type_register_static(&riscv_rpmi_info); +} + +type_init(riscv_rpmi_register_types) + +DeviceState *riscv_rpmi_create(const RiscvRpmiConfig *cfg, Error **errp) +{ + DeviceState *dev; + RiscvRpmiState *s; + + if (!cfg) { + error_setg(errp, "missing RPMI configuration"); + return NULL; + } + + dev = qdev_new(TYPE_RISCV_RPMI); + qdev_prop_set_uint64(dev, "shmem-base", cfg->shmem_base); + qdev_prop_set_uint64(dev, "shmem-size", cfg->shmem_size); + qdev_prop_set_uint32(dev, "a2p-req-size", cfg->a2p_req_size); + qdev_prop_set_uint32(dev, "p2a-req-size", cfg->p2a_req_size); + + s = RISCV_RPMI(dev); + riscv_rpmi_configure_base(s, cfg); + + if (!sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), errp)) { + return NULL; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, cfg->doorbell_base); + return dev; +} diff --git a/hw/misc/riscv_rpmi_internal.h b/hw/misc/riscv_rpmi_internal.h new file mode 100644 index 0000000000..447c0acb55 --- /dev/null +++ b/hw/misc/riscv_rpmi_internal.h @@ -0,0 +1,24 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * RISC-V RPMI internal service helpers. + * + * Copyright (c) 2026 Qualcomm Technologies, Inc. + * Author: + * Subrahmanya Lingappa <[email protected]> + */ + +#ifndef HW_MISC_RISCV_RPMI_INTERNAL_H +#define HW_MISC_RISCV_RPMI_INTERNAL_H + +#include "qapi/error.h" +#include "hw/misc/riscv_rpmi.h" +#include "librpmi.h" + +#define RPMI_PLAT_INFO "QEMU RISC-V RPMI" + +extern const struct rpmi_shmem_platform_ops rpmi_shmem_qemu_ops; +bool riscv_rpmi_service_enabled(RiscvRpmiState *s, + RiscvRpmiServiceKind kind); + +#endif diff --git a/include/hw/misc/riscv_rpmi.h b/include/hw/misc/riscv_rpmi.h new file mode 100644 index 0000000000..cb2658e57e --- /dev/null +++ b/include/hw/misc/riscv_rpmi.h @@ -0,0 +1,90 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * RISC-V RPMI definitions shared by RPMI transport and FDT helpers. + * + * Copyright (c) 2026 Qualcomm Technologies, Inc. + * Author: + * Subrahmanya Lingappa <[email protected]> + */ + +#ifndef HW_MISC_RISCV_RPMI_H +#define HW_MISC_RISCV_RPMI_H + +#include "exec/hwaddr.h" +#include "hw/core/sysbus.h" +#include "qom/object.h" +#include "qemu/notify.h" + +#define RPMI_QUEUE_SLOT_SIZE 64 +#define RPMI_DBREG_SIZE 0x1000 + +#define RPMI_ALL_NUM_QUEUES 4 +#define RPMI_A2P_NUM_QUEUES 2 +#define RPMI_ALL_NUM_REGS (RPMI_ALL_NUM_QUEUES + 1) +#define RPMI_A2P_NUM_REGS (RPMI_A2P_NUM_QUEUES + 1) + +#define VIRT_RPMI_A2P_REQ_SIZE (16 * RPMI_QUEUE_SLOT_SIZE) +#define VIRT_RPMI_P2A_REQ_SIZE 0 + + +#define TYPE_RISCV_RPMI "riscv-rpmi" +OBJECT_DECLARE_SIMPLE_TYPE(RiscvRpmiState, RISCV_RPMI) + +struct rpmi_context; +struct rpmi_service_group; +struct rpmi_shmem; +struct rpmi_transport; + +typedef enum RiscvRpmiServiceKind { + RISCV_RPMI_SERVICE_INVALID = 0, +} RiscvRpmiServiceKind; + +typedef struct RiscvRpmiServiceConfig { + RiscvRpmiServiceKind kind; + const char *node_name; + const char *compatible; + uint32_t service_group; + bool has_mpxy_channel; + uint32_t mpxy_channel; +} RiscvRpmiServiceConfig; + +typedef struct RiscvRpmiConfig { + hwaddr doorbell_base; + hwaddr shmem_base; + hwaddr shmem_size; + uint32_t a2p_req_size; + uint32_t p2a_req_size; + const char *platform_info; + + const uint32_t *hart_ids; + uint32_t hart_count; + const RiscvRpmiServiceConfig *services; + uint32_t service_count; +} RiscvRpmiConfig; + +struct RiscvRpmiState { + SysBusDevice parent_obj; + + MemoryRegion mmio; + MemoryRegion shmem; + uint64_t shmem_base; + uint64_t shmem_size; + uint32_t a2p_req_size; + uint32_t p2a_req_size; + char *platform_info; + + uint32_t *hart_ids; + uint32_t hart_count; + const RiscvRpmiServiceConfig *services; + uint32_t service_count; + uint32_t doorbell; + struct rpmi_shmem *rpmi_shmem; + struct rpmi_transport *transport; + struct rpmi_context *context; + bool has_shmem; +}; + +DeviceState *riscv_rpmi_create(const RiscvRpmiConfig *cfg, Error **errp); + +#endif -- 2.43.0
