Charles,
On Wed, Oct 14, 2026 at 7:47 PM Charles Perry
<[email protected]> wrote:
>
> Add support for transport of RPMI messages over shared memory as
> described in the RPMI spec [1]. This uses two or four ring buffers
> composed of a fixed number of "slots" to pass messages between the AP
> and the PuC. U-Boot is the Application processor (AP) and sends messages
> through the a2p-req queue and receives response though the p2a-ack
> queue. There is no support for platform microcontroller (PuC) initiated
> requests nor notifications.
>
> This transport can be used in M or S mode U-Boot, but in the S-mode
> case, one must make sure that the supervisor software isn't already
> using the AP side of the shared memory.
>
> [1]: https://github.com/riscv-non-isa/riscv-rpmi (chapter 2)
>
> Signed-off-by: Charles Perry <[email protected]>
> ---
> Changes in v4:
> - Fix the default value of the doorbell from 0 to 1
>
> Changes in v3: None
>
> Changes in v2: (from Yao Zi review)
> - Reorder headers
> - Don't null check pointers before free()
> ---
> drivers/firmware/rpmi/Kconfig | 8 +
> drivers/firmware/rpmi/Makefile | 1 +
> drivers/firmware/rpmi/rpmi-shmem.c | 473
> +++++++++++++++++++++++++++++++++++++
> include/rpmi_proto.h | 23 ++
> 4 files changed, 505 insertions(+)
>
> diff --git a/drivers/firmware/rpmi/Kconfig b/drivers/firmware/rpmi/Kconfig
> index 708d931263ff..421a1fa9deba 100644
> --- a/drivers/firmware/rpmi/Kconfig
> +++ b/drivers/firmware/rpmi/Kconfig
> @@ -16,3 +16,11 @@ config RPMI_SBI_MPXY
> (MPXY) extension of the supervisor binary interface (SBI). The
> specification for the MPXY extension is available at:
> https://github.com/riscv-non-isa/riscv-sbi-doc (chapter 20)
> +
> +config RPMI_SHMEM
> + bool "RPMI over shared memory transport"
> + depends on RPMI_FIRMWARE
> + help
> + Enable support for transport of RPMI messages over shared memory as
> + described in chapter 2 of the RPMI specification, available at:
> + https://github.com/riscv-non-isa/riscv-rpmi
> diff --git a/drivers/firmware/rpmi/Makefile b/drivers/firmware/rpmi/Makefile
> index 71c4aeaed910..4f4bcb929db4 100644
> --- a/drivers/firmware/rpmi/Makefile
> +++ b/drivers/firmware/rpmi/Makefile
> @@ -1,2 +1,3 @@
> obj-y += rpmi-uclass.o
> obj-$(CONFIG_RPMI_SBI_MPXY) += rpmi-sbi-mpxy.o
> +obj-$(CONFIG_RPMI_SHMEM) += rpmi-shmem.o
> diff --git a/drivers/firmware/rpmi/rpmi-shmem.c
> b/drivers/firmware/rpmi/rpmi-shmem.c
> new file mode 100644
> index 000000000000..db9949e39c7e
> --- /dev/null
> +++ b/drivers/firmware/rpmi/rpmi-shmem.c
> @@ -0,0 +1,473 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2026 Microchip Technology Inc. All rights reserved.
> + */
> +
> +#define LOG_CATEGORY UCLASS_RPMI
> +
> +#include <asm/barrier.h>
> +#include <asm/io.h>
> +#include <asm/types.h>
> +#include <dm.h>
> +#include <dm/device_compat.h>
> +#include <linux/delay.h>
> +#include <log.h>
> +#include <malloc.h>
> +#include <rpmi-uclass.h>
> +
> +#define RPMI_SLOT_SIZE_MIN 64
> +
> +enum rpmi_queue_idx {
> + RPMI_QUEUE_IDX_A2P_REQ = 0,
> + RPMI_QUEUE_IDX_P2A_ACK = 1,
> + RPMI_QUEUE_IDX_P2A_REQ = 2,
> + RPMI_QUEUE_IDX_A2P_ACK = 3,
> + RPMI_QUEUE_IDX_MAX_COUNT = 4,
> + RPMI_A2P_DOORBELL = 5,
> +};
> +
> +struct shmem_queue {
> + u32 num_slot;
> + __le32 *headptr;
> + __le32 *tailptr;
> + void *buffer;
> +};
> +
> +struct rpmi_shmem {
> + struct udevice *dev;
> + struct shmem_queue a2p_req;
> + struct shmem_queue p2a_ack;
> + int slot_size;
> + int rx_timeout_ms;
> + u32 token;
> + void *doorbell;
> + u32 doorbell_value;
> + u32 spec_version;
> + u32 impl_version;
> + u32 impl_id;
> +};
> +
> +struct rpmi_shmem_chan_priv {
> + struct rpmi_shmem *priv;
> + u32 servicegroup_id;
> + u32 servicegroup_version;
> +};
> +
> +static int rpmi_shmem_queue_idx(const char *name)
> +{
> + static const struct {
> + int id;
> + const char *name;
> + } id_names[] = {
> + { RPMI_QUEUE_IDX_A2P_REQ, "a2p-req" },
> + { RPMI_QUEUE_IDX_P2A_ACK, "p2a-ack" },
> + { RPMI_QUEUE_IDX_P2A_REQ, "p2a-req" },
> + { RPMI_QUEUE_IDX_A2P_ACK, "a2p-ack" },
> + { RPMI_A2P_DOORBELL, "a2p-doorbell" },
> + };
> +
> + for (int i = 0; i < ARRAY_SIZE(id_names); i++) {
> + if (strcmp(name, id_names[i].name) == 0)
> + return id_names[i].id;
> + }
> +
> + return -ENODEV;
> +}
> +
> +static bool rpmi_smq_queue_full(struct shmem_queue *qctx)
> +{
> + return ((le32_to_cpu(*qctx->tailptr) + 1) % qctx->num_slot ==
> + le32_to_cpu(*qctx->headptr));
> +}
> +
> +/* 31 0
> + * +---------------------+-----------------------+
> + * | FLAGS | SERVICE_ID | SERVICEGROUP_ID |
> + * +---------------------+-----------------------+
> + * | TOKEN | DATA LENGTH |
> + * +---------------------+-----------------------+
> + * | DATA/PAYLOAD |
> + * +---------------------------------------------+
> + */
> +struct rpmi_message_header {
> + __le16 servicegroup_id;
> + u8 service_id;
> + u8 flags;
> + __le16 datalen;
> + __le16 token;
> +} __packed;
> +
> +static int rpmi_smq_rx(struct rpmi_shmem *priv, struct shmem_queue *qctx,
> + u32 service_group_id, u32 service_id, u16 token,
> + void *response, unsigned long max_response_len,
> + unsigned long *out_response_len)
> +{
> + struct rpmi_message_header *msg;
> + u32 dlen, headidx, tailidx;
> + int rxretry = 0;
> + void *src;
> +
> + headidx = le32_to_cpu(*qctx->headptr);
> +
> + /* Wait for the Rx message with matching token, discard any other
> + * messages received in the mean time
> + */
> + while (true) {
> + tailidx = le32_to_cpu(*qctx->tailptr);
> +
> + if (headidx != tailidx) {
> + msg = (void *)qctx->buffer +
> + (headidx * priv->slot_size);
The transmit path uses `wmb()` before publishing the tail, but this receive
path consumes the slot immediately after observing the tail. Please add the
platform-appropriate acquire/read barrier before reading `msg` and document
the ordering requirement for this shared-memory mapping.
> + if (le16_to_cpu(msg->token) == token)
> + break;
The response is accepted using only the token. This function already receives
`service_group_id` and `service_id`; please compare those fields with the
response header as well. The token counter starts again for a new driver
instance, while old replies can remain in the shared queue across a firmware
transition. An old reply for a different service carrying the same token could
otherwise satisfy this request.
> +
> + headidx = (headidx + 1) % qctx->num_slot;
> + }
> +
> + mdelay(1);
> + rxretry += 1;
> + if (rxretry > priv->rx_timeout_ms)
> + return -ETIMEDOUT;
> + }
> +
> + /* Extract data from the message */
> + if (response) {
> + dlen = le16_to_cpu(msg->datalen);
> + if (dlen > max_response_len)
> + dlen = max_response_len;
> + if (out_response_len)
> + *out_response_len = dlen;
> + src = (void *)msg + sizeof(struct rpmi_message_header);
> + memcpy(response, src, dlen);
`dlen` is bounded only by the caller's response buffer, not by the current
shared-memory slot. The current clock callers use response buffers smaller
than the slot payload, but this generic transport accepts arbitrary response
sizes. Please reject a `dlen` greater than
`slot_size - sizeof(struct rpmi_message_header)` before copying, so a malformed
response or future caller cannot make the read cross the slot boundary.
> + }
> +
> + /* Update the head index */
> + headidx = (headidx + 1) % qctx->num_slot;
> + *qctx->headptr = cpu_to_le32(headidx);
> +
> + return 0;
> +}
> +
> +static int rpmi_smq_tx(struct rpmi_shmem *priv, struct shmem_queue *qctx,
> + u32 service_group_id, u32 service_id,
> + enum rpmi_message_type type, u16 token,
> + const void *request, unsigned long request_len)
> +{
> + struct rpmi_message_header header = { 0 };
> + u32 tailidx;
> + void *dst;
> +
> + /* Tx sanity checks */
> + if (request_len >
> + (priv->slot_size - sizeof(struct rpmi_message_header)))
> + return -EINVAL;
> +
> + if (rpmi_smq_queue_full(qctx))
> + return -ENOMEM;
> +
> + tailidx = le32_to_cpu(*qctx->tailptr);
> +
> + /* Prepare the header to be written into the slot */
> + header.servicegroup_id = cpu_to_le16(service_group_id);
> + header.service_id = service_id;
> + header.flags = type;
> + header.datalen = cpu_to_le16(request_len);
> + header.token = cpu_to_le16(token);
> +
> + /* Write header into the slot */
> + dst = (char *)qctx->buffer + (tailidx * priv->slot_size);
> + memcpy(dst, &header, sizeof(header));
> + dst += sizeof(header);
> +
> + /* Write data into the slot */
> + if (request)
> + memcpy(dst, request, request_len);
> +
> + /* Make sure queue chanages are visible to PuC before updating tail */
> + wmb();
> +
> + /* Update the tail/write index */
> + tailidx = (tailidx + 1) % qctx->num_slot;
> + *qctx->tailptr = cpu_to_le32(tailidx);
> +
> + /* Ring the RPMI doorbell if present */
> + if (priv->doorbell)
> + writel(priv->doorbell_value, priv->doorbell);
> +
> + return 0;
> +}
> +
> +static int rpmi_shmem_request_priv(struct rpmi_shmem *priv, u32
> servicegroup_id,
> + u32 service_id, const void *request,
> + unsigned long request_len, void *response,
> + unsigned long max_response_len,
> + unsigned long *out_response_len)
> +{
> + enum rpmi_message_type type = response ? RPMI_MSG_NORMAL_REQUEST :
> + RPMI_MSG_POSTED_REQUEST;
> + int err;
> +
> + if (!request && request_len)
> + return -EINVAL;
> +
> + err = rpmi_smq_tx(priv, &priv->a2p_req, servicegroup_id, service_id,
> + type, priv->token, request, request_len);
> + if (err) {
> + dev_err(priv->dev, "tx failed %i\n", err);
> + goto out;
> + }
> +
> + if (response) {
> + err = rpmi_smq_rx(priv, &priv->p2a_ack, servicegroup_id,
> + service_id, priv->token, response,
> + max_response_len, out_response_len);
> + if (err) {
> + dev_err(priv->dev, "rx failed %i\n", err);
> + goto out;
> + }
> + }
> +
> +out:
> + priv->token++;
> + return err;
> +}
> +
> +static int rpmi_shmem_request(struct rpmi_chan *chan, u32 service_id,
> + const void *request, unsigned long request_len,
> + void *response, unsigned long max_response_len,
> + unsigned long *out_response_len)
> +{
> + struct rpmi_shmem *priv = dev_get_priv(chan->dev);
> + struct rpmi_shmem_chan_priv *chan_priv = chan->con_priv;
> +
> + return rpmi_shmem_request_priv(priv, chan_priv->servicegroup_id,
> + service_id, request, request_len,
> + response, max_response_len,
> + out_response_len);
> +}
> +
> +static int rpmi_shmem_get_attr(struct rpmi_chan *chan,
> + enum rpmi_attribute_id id, u32 *data)
> +{
> + struct rpmi_shmem *priv = dev_get_priv(chan->dev);
> + struct rpmi_shmem_chan_priv *chan_priv = chan->con_priv;
> +
> + switch (id) {
> + case RPMI_ATTR_SPEC_VERSION:
> + *data = priv->spec_version;
> + break;
> + case RPMI_ATTR_MAX_MSG_DATA_SIZE:
> + *data = priv->slot_size - sizeof(struct rpmi_message_header);
> + break;
> + case RPMI_ATTR_SERVICEGROUP_ID:
> + *data = chan_priv->servicegroup_id;
> + break;
> + case RPMI_ATTR_SERVICEGROUP_VERSION:
> + *data = chan_priv->servicegroup_version;
> + break;
> + case RPMI_ATTR_IMPL_ID:
> + *data = priv->impl_id;
> + break;
> + case RPMI_ATTR_IMPL_VERSION:
> + *data = priv->impl_version;
> + break;
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + return 0;
> +}
> +
> +static int rpmi_shmem_base_service_req(struct rpmi_shmem *priv, u32
> service_id,
> + const u32 *arg_in, u32 *arg_out)
> +{
> + int len = 0;
> + u32 rx[2];
> + u32 tx[1];
> + int ret;
> +
> + if (arg_in) {
> + tx[0] = cpu_to_le32(*arg_in);
> + len = sizeof(*arg_in);
> + }
> +
> + ret = rpmi_shmem_request_priv(priv, RPMI_SRVGRP_BASE, service_id, tx,
> + len, rx, sizeof(rx), NULL);
> + if (ret) {
> + dev_err(priv->dev, "BASE service %i failed (%i)\n",
> service_id,
> + ret);
> + return ret;
> + }
> +
> + if (rx[0]) {
> + ret = rpmi_to_linux_error(le32_to_cpu(rx[0]));
> + dev_err(priv->dev, "BASE service %i returned code %i\n",
> + service_id, ret);
> + return ret;
> + }
> +
> + if (arg_out)
> + *arg_out = le32_to_cpu(rx[1]);
> +
> + return 0;
> +}
> +
> +static int rpmi_shmem_of_xlate(struct rpmi_chan *chan,
> + struct ofnode_phandle_args *args)
> +{
> + if (args->args_count != 1)
> + return -EINVAL;
> +
> + // args[0] is the servicegroup id.
> + chan->id = args->args[0];
> +
> + return 0;
> +}
> +
> +static int rpmi_shmem_open(struct rpmi_chan *chan)
> +{
> + struct rpmi_shmem *priv = dev_get_priv(chan->dev);
> + u32 servicegroup_id, servicegroup_version;
> + struct rpmi_shmem_chan_priv *chan_priv;
> + int ret;
> +
> + servicegroup_id = chan->id;
> +
> + ret = rpmi_shmem_base_service_req(priv,
> + RPMI_BASE_SRV_PROBE_SERVICE_GROUP,
> + &servicegroup_id,
> + &servicegroup_version);
> + if (ret)
> + return ret;
> +
> + chan_priv = malloc(sizeof(*chan_priv));
> + if (!chan_priv)
> + return -ENOMEM;
> +
> + chan->con_priv = chan_priv;
> +
> + chan_priv->priv = priv;
> + chan_priv->servicegroup_id = servicegroup_id;
> + chan_priv->servicegroup_version = servicegroup_version;
> +
> + return 0;
> +}
> +
> +static void rpmi_shmem_close(struct rpmi_chan *chan)
> +{
> + free(chan->con_priv);
> +}
> +
> +static int rpmi_shmem_probe(struct udevice *dev)
> +{
> + struct rpmi_shmem *priv = dev_get_priv(dev);
> + int i, ret, qidx, qcount, qslotcnt;
> + struct shmem_queue *queue;
> + u32 arg_in, arg_out;
> + const char *qname;
> + fdt_size_t qsize;
> + void *start;
> +
> + priv->dev = dev;
> + priv->rx_timeout_ms = 20;
> +
> + priv->slot_size = dev_read_u32_default(dev, "riscv,slot-size", 0);
> + if (priv->slot_size < RPMI_SLOT_SIZE_MIN)
> + return -EINVAL;
The probe enforces only the minimum slot size. RPMI also requires a power-of-
two slot size; validate that property before deriving queue pointers and slot
counts.
regards,
Subrahmanya
> +
> + priv->doorbell_value =
> + dev_read_u32_default(dev, "riscv,a2p-doorbell-value", 1);
> +
> + qcount = dev_read_string_count(dev, "reg-names");
> + if (qcount < 0 || qcount > 5)
> + return -EINVAL;
> +
> + for (i = 0; i < qcount; i++) {
> + ret = dev_read_string_index(dev, "reg-names", i, &qname);
> + if (ret)
> + continue;
> +
> + qidx = rpmi_shmem_queue_idx(qname);
> + if (qidx < 0)
> + continue;
> +
> + start = dev_read_addr_size_index_ptr(dev, i, &qsize);
> + if (!start)
> + continue;
> +
> + if (qidx == RPMI_A2P_DOORBELL) {
> + priv->doorbell = start;
> + } else {
> + qslotcnt = qsize / priv->slot_size;
> + if (qslotcnt < 4)
> + continue;
> +
> + if (qidx == RPMI_QUEUE_IDX_A2P_REQ)
> + queue = &priv->a2p_req;
> + else if (qidx == RPMI_QUEUE_IDX_P2A_ACK)
> + queue = &priv->p2a_ack;
> + else
> + continue;
> +
> + queue->headptr = start;
> + queue->tailptr = start + priv->slot_size;
> + queue->buffer = start + (2 * priv->slot_size);
> + queue->num_slot = qslotcnt - 2;
> + }
> + }
> +
> + if (!priv->a2p_req.num_slot || !priv->p2a_ack.num_slot)
> + return -EINVAL;
> +
> + arg_in = RPMI_SRVGRP_BASE;
> + ret = rpmi_shmem_base_service_req(
> + priv, RPMI_BASE_SRV_PROBE_SERVICE_GROUP, &arg_in, &arg_out);
> + if (ret)
> + return ret;
> +
> + if (arg_out < RPMI_MKVER(1, 0))
> + return -EINVAL;
> +
> + ret = rpmi_shmem_base_service_req(
> + priv, RPMI_BASE_SRV_GET_IMPLEMENTATION_VERSION, NULL,
> &arg_out);
> + if (ret)
> + return ret;
> + priv->impl_version = arg_out;
> +
> + ret = rpmi_shmem_base_service_req(
> + priv, RPMI_BASE_SRV_GET_IMPLEMENTATION_IDN, NULL, &arg_out);
> + if (ret)
> + return ret;
> + priv->impl_id = arg_out;
> +
> + ret = rpmi_shmem_base_service_req(priv,
> RPMI_BASE_SRV_GET_SPEC_VERSION,
> + NULL, &arg_out);
> + if (ret)
> + return ret;
> + priv->spec_version = arg_out;
> +
> + return 0;
> +}
> +
> +static const struct udevice_id rpmi_shmem_ids[] = {
> + { .compatible = "riscv,rpmi-shmem-mbox" },
> + {}
> +};
> +
> +static const struct rpmi_transport_ops rpmi_shmem_ops = {
> + .of_xlate = rpmi_shmem_of_xlate,
> + .get_attr = rpmi_shmem_get_attr,
> + .request = rpmi_shmem_request,
> + .open = rpmi_shmem_open,
> + .close = rpmi_shmem_close,
> +};
> +
> +U_BOOT_DRIVER(rpmi_shmem) = {
> + .name = "rpmi-shmem",
> + .id = UCLASS_RPMI,
> + .of_match = rpmi_shmem_ids,
> + .probe = rpmi_shmem_probe,
> + .priv_auto = sizeof(struct rpmi_shmem),
> + .ops = &rpmi_shmem_ops,
> + .flags = DM_FLAG_PRE_RELOC,
> +};
> diff --git a/include/rpmi_proto.h b/include/rpmi_proto.h
> index 012ae93d7317..4b557bfeedf5 100644
> --- a/include/rpmi_proto.h
> +++ b/include/rpmi_proto.h
> @@ -60,4 +60,27 @@ enum rpmi_servicegroup_id {
> RPMI_SRVGRP_VENDOR_END = 0xFFFF,
> };
>
> +/** RPMI Base ServiceGroup Service IDs */
> +enum rpmi_base_service_id {
> + RPMI_BASE_SRV_ENABLE_NOTIFICATION = 0x01,
> + RPMI_BASE_SRV_GET_IMPLEMENTATION_VERSION = 0x02,
> + RPMI_BASE_SRV_GET_IMPLEMENTATION_IDN = 0x03,
> + RPMI_BASE_SRV_GET_SPEC_VERSION = 0x04,
> + RPMI_BASE_SRV_GET_PLATFORM_INFO = 0x05,
> + RPMI_BASE_SRV_PROBE_SERVICE_GROUP = 0x06,
> + RPMI_BASE_SRV_GET_ATTRIBUTES = 0x07,
> +};
> +
> +/** RPMI Messages Types */
> +enum rpmi_message_type {
> + /* Normal request backed with ack */
> + RPMI_MSG_NORMAL_REQUEST = 0x0,
> + /* Request without any ack */
> + RPMI_MSG_POSTED_REQUEST = 0x1,
> + /* Acknowledgment for normal request message */
> + RPMI_MSG_ACKNOWLDGEMENT = 0x2,
> + /* Notification message */
> + RPMI_MSG_NOTIFICATION = 0x3,
> +};
> +
> #endif
>
> --
> 2.53.0
>