U-Boot Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Charles Perry <charles.perry@microchip.com>
To: <u-boot@lists.denx.de>
Cc: Rahul Pathak <rahul@summations.net>,
	Anup Patel <anup@brainfault.org>,
	Charles Perry <charles.perry@microchip.com>,
	Tom Rini <trini@konsulko.com>
Subject: [PATCH 3/7] firmware: rpmi: add support for shared memory transport
Date: Fri, 26 Jun 2026 13:15:44 -0700	[thread overview]
Message-ID: <20260626201613.1035208-4-charles.perry@microchip.com> (raw)
In-Reply-To: <20260626201613.1035208-1-charles.perry@microchip.com>

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 <charles.perry@microchip.com>
---
 drivers/firmware/rpmi/Kconfig      |   8 +
 drivers/firmware/rpmi/Makefile     |   1 +
 drivers/firmware/rpmi/rpmi-shmem.c | 474 +++++++++++++++++++++++++++++
 include/rpmi_proto.h               |  23 ++
 4 files changed, 506 insertions(+)
 create mode 100644 drivers/firmware/rpmi/rpmi-shmem.c

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..a3e529d4c685
--- /dev/null
+++ b/drivers/firmware/rpmi/rpmi-shmem.c
@@ -0,0 +1,474 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Microchip Technology Inc. All rights reserved.
+ */
+
+#define LOG_CATEGORY UCLASS_RPMI
+
+#include <dm.h>
+#include <log.h>
+#include <rpmi-uclass.h>
+#include <malloc.h>
+#include <asm/io.h>
+#include <dm/device_compat.h>
+#include <asm/types.h>
+#include <asm/barrier.h>
+#include <linux/delay.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);
+			if (le16_to_cpu(msg->token) == token)
+				break;
+
+			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);
+	}
+
+	/* 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)
+{
+	if (chan->con_priv)
+		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;
+
+	priv->doorbell_value =
+		dev_read_u32_default(dev, "riscv,a2p-doorbell-value", 0);
+
+	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.47.3


  parent reply	other threads:[~2026-06-26 21:20 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-26 20:15 [PATCH 0/7] Add support for RPMI to U-Boot Charles Perry
2026-06-26 20:15 ` [PATCH 1/7] firmware: add support for RPMI Charles Perry
2026-06-27  1:09   ` Yao Zi
2026-06-26 20:15 ` [PATCH 2/7] firmware: rpmi: add support for the SBI MPXY transport Charles Perry
2026-06-27  1:30   ` Yao Zi
2026-06-26 20:15 ` Charles Perry [this message]
2026-06-26 20:15 ` [PATCH 4/7] drivers: clk: add support for RPMI clocks Charles Perry
2026-06-27  1:47   ` Yao Zi
2026-06-26 20:15 ` [PATCH 5/7] drivers: power: add support for RPMI power domains Charles Perry
2026-06-26 20:15 ` [PATCH 6/7] firmware: rpmi: add a test and a sandbox driver Charles Perry
2026-06-26 20:15 ` [PATCH 7/7] firmware: rpmi: add a test and sandbox for device power Charles Perry

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260626201613.1035208-4-charles.perry@microchip.com \
    --to=charles.perry@microchip.com \
    --cc=anup@brainfault.org \
    --cc=rahul@summations.net \
    --cc=trini@konsulko.com \
    --cc=u-boot@lists.denx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox