[RFC PATCH 04/10] drivers: add RPMsg framework

Tanmay Shah tanmay.shah at amd.com
Tue Jul 25 16:06:44 CEST 2023


RPMsg framework is used to communicate to remote processor
using rpmsg protocol.

This framework is ported from the Linux kernel
directory: drivers/rpmsg/
kernel version: 6.4-rc2 (d848a4819d85)

Signed-off-by: Tanmay Shah <tanmay.shah at amd.com>
---
 MAINTAINERS                        |   7 +
 arch/sandbox/dts/test.dts          |   8 +
 drivers/Kconfig                    |   2 +
 drivers/Makefile                   |   1 +
 drivers/rpmsg/Kconfig              |  31 +++
 drivers/rpmsg/Makefile             |  10 +
 drivers/rpmsg/rpmsg-uclass.c       | 156 ++++++++++++
 drivers/rpmsg/rpmsg_internal.h     |  52 ++++
 drivers/rpmsg/sandbox_test_rpmsg.c |  88 +++++++
 drivers/rpmsg/virtio_rpmsg_bus.c   | 384 +++++++++++++++++++++++++++++
 drivers/virtio/virtio-uclass.c     |   1 +
 include/dm/uclass-id.h             |   1 +
 include/rpmsg.h                    | 140 +++++++++++
 include/rproc_virtio.h             |   8 +-
 include/virtio.h                   |   4 +-
 include/virtio_ring.h              |  15 ++
 test/dm/Makefile                   |   1 +
 test/dm/rpmsg.c                    |  41 +++
 18 files changed, 947 insertions(+), 3 deletions(-)
 create mode 100644 drivers/rpmsg/Kconfig
 create mode 100644 drivers/rpmsg/Makefile
 create mode 100644 drivers/rpmsg/rpmsg-uclass.c
 create mode 100644 drivers/rpmsg/rpmsg_internal.h
 create mode 100644 drivers/rpmsg/sandbox_test_rpmsg.c
 create mode 100644 drivers/rpmsg/virtio_rpmsg_bus.c
 create mode 100644 include/rpmsg.h
 create mode 100644 test/dm/rpmsg.c

diff --git a/MAINTAINERS b/MAINTAINERS
index c4a32a0956..876a7fdbdf 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1365,6 +1365,13 @@ F:	drivers/usb/gadget/f_rockusb.c
 F:	cmd/rockusb.c
 F:	doc/README.rockusb
 
+RPMSG
+M:	Tanmay Shah <tanmay.shah at amd.com>
+S:	Maintained
+F:	drivers/rpmsg/*
+F:	include/rpmsg.h
+F:	test/dm/rpmsg.c
+
 SANDBOX
 M:	Simon Glass <sjg at chromium.org>
 S:	Maintained
diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index b5509eee8c..fca1a591fb 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -1247,6 +1247,14 @@
 		compatible = "sandbox,sandbox-rng";
 	};
 
+	rpmsg_1: rpmsg at 1 {
+		compatible = "sandbox,test-rpmsg";
+	};
+
+	rpmsg_2: rpmsg at 2 {
+		compatible = "sandbox,test-rpmsg";
+	};
+
 	rproc_1: rproc at 1 {
 		compatible = "sandbox,test-processor";
 		remoteproc-name = "remoteproc-test-dev1";
diff --git a/drivers/Kconfig b/drivers/Kconfig
index a25f6ae02f..69700f1f83 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -112,6 +112,8 @@ source "drivers/reset/Kconfig"
 
 source "drivers/rng/Kconfig"
 
+source "drivers/rpmsg/Kconfig"
+
 source "drivers/rtc/Kconfig"
 
 source "drivers/scsi/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index 3bc6d279d7..68e8d8b065 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -109,6 +109,7 @@ obj-y += mfd/
 obj-y += mtd/
 obj-y += pwm/
 obj-y += reset/
+obj-y += rpmsg/
 obj-y += input/
 obj-y += iommu/
 # SOC specific infrastructure drivers.
diff --git a/drivers/rpmsg/Kconfig b/drivers/rpmsg/Kconfig
new file mode 100644
index 0000000000..4efb8dfcd7
--- /dev/null
+++ b/drivers/rpmsg/Kconfig
@@ -0,0 +1,31 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2023, Advanced Micro devices, Inc.
+
+menu "RPMsg drivers"
+
+# RPMsg gets selected by drivers as needed
+# All users should depend on DM
+config RPMSG
+	bool
+	depends on DM
+
+config VIRTIO_RPMSG_BUS
+	bool "virtio rpmsg bus"
+	depends on DM
+	select RPMSG
+	select REMOTEPROC_VIRTIO
+	help
+	  Say 'y' here to enable virtio based RPMsg. RPMsg allows
+	  U-Boot drivers to communicate with remote processors.
+
+config RPMSG_SANDBOX
+	bool "RPMsg driver for sandbox platform"
+	depends on DM
+	select RPMSG
+	depends on SANDBOX
+	help
+	  Say 'y' here to add sandbox driver for RPMsg framework used
+	  for dummy communication with remote processor on sandbox platform
+
+endmenu
diff --git a/drivers/rpmsg/Makefile b/drivers/rpmsg/Makefile
new file mode 100644
index 0000000000..21611725ea
--- /dev/null
+++ b/drivers/rpmsg/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2023, Advanced Micro Devices, Inc.
+
+obj-$(CONFIG_RPMSG) += rpmsg-uclass.o
+
+obj-$(CONFIG_RPMSG_SANDBOX) += sandbox_test_rpmsg.o
+
+# virtio driver for rpmsg
+obj-$(CONFIG_VIRTIO_RPMSG_BUS) += virtio_rpmsg_bus.o
diff --git a/drivers/rpmsg/rpmsg-uclass.c b/drivers/rpmsg/rpmsg-uclass.c
new file mode 100644
index 0000000000..3e749a5827
--- /dev/null
+++ b/drivers/rpmsg/rpmsg-uclass.c
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * remote processor messaging bus
+ *
+ * Copyright (C) 2023, Advanced Micro Devices, Inc.
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <log.h>
+#include <malloc.h>
+#include <rpmsg.h>
+#include <virtio.h>
+#include <dm/device_compat.h>
+#include <dm/device-internal.h>
+#include <dm/lists.h>
+#include <dm/uclass.h>
+#include <dm/uclass-internal.h>
+
+#include "rpmsg_internal.h"
+
+int rpmsg_init(int core_id)
+{
+	struct udevice *udev = NULL;
+	int ret;
+
+	ret = uclass_find_device_by_seq(UCLASS_RPMSG, core_id, &udev);
+	if (ret) {
+		debug("can't find rpmsg dev for core_id %d\n", core_id);
+		return ret;
+	}
+
+	ret = device_probe(udev);
+	if (ret)
+		debug("failed to probe rpmsg dev, ret = %d\n", ret);
+
+	return ret;
+}
+
+static int rpmsg_find_device(int core_id, struct udevice **rpdev)
+{
+	int core_count;
+
+	core_count = uclass_id_count(UCLASS_RPMSG);
+	if (core_id >= core_count) {
+		debug("invalid core id = %d\n", core_id);
+		return -EINVAL;
+	}
+
+	return uclass_find_device(UCLASS_RPMSG, core_id, rpdev);
+}
+
+/**
+ * rpmsg_send() - send a message across to the remote processor
+ * @core_id: remote processor core id
+ * @data: payload of message
+ * @len: length of payload
+ *
+ * This function sends @data of length @len on the @core_id endpoint.
+ * The message will be sent to the remote processor which the @core_id
+ * belongs to, using @ept's address and its associated rpmsg
+ * device destination addresses.
+ * In case there are no TX buffers available, the function will fail
+ * immediately
+ *
+ * Return: 0 on success and an appropriate error value on failure.
+ */
+int rpmsg_send(int core_id, void *data, int len)
+{
+	struct udevice *rpdev = NULL;
+	const struct rpmsg_device_ops *ops;
+	int ret;
+
+	ret = rpmsg_find_device(core_id, &rpdev);
+	if (ret) {
+		debug("no rpmsg device for core = %d, ret = %d\n", core_id, ret);
+		return ret;
+	}
+	if (!rpdev)
+		return -ENODEV;
+
+	ops = (const struct rpmsg_device_ops *)device_get_ops(rpdev);
+	if (!ops) {
+		debug("send op not registered for device %s\n", rpdev->name);
+		return -EINVAL;
+	}
+
+	return ops->send(rpdev, data, len);
+}
+
+int rpmsg_recv(int core_id, rpmsg_rx_cb_t cb)
+{
+	struct udevice *rpdev = NULL;
+	const struct rpmsg_device_ops *ops;
+	int ret;
+
+	ret = rpmsg_find_device(core_id, &rpdev);
+	if (ret) {
+		debug("no rpmsg device for core = %d, ret = %d\n", core_id, ret);
+		return ret;
+	}
+	if (!rpdev)
+		return -ENODEV;
+
+	ops = (const struct rpmsg_device_ops *)device_get_ops(rpdev);
+	if (!ops) {
+		debug("recv op not registered for device %s\n", rpdev->name);
+		return -EINVAL;
+	}
+
+	return ops->recv(rpdev, cb);
+}
+
+void rpmsg_debug_data(int core_id, int vq_id)
+{
+	struct udevice *rpdev = NULL;
+	const struct rpmsg_device_ops *ops;
+	int ret;
+
+	if (vq_id > 1)
+		debug("vq_id %d not supported\n", vq_id);
+
+	ret = rpmsg_find_device(core_id, &rpdev);
+	if (ret || !rpdev) {
+		debug("no rpmsg device for core = %d, ret = %d\n", core_id, ret);
+		return;
+	}
+
+	ops = (const struct rpmsg_device_ops *)device_get_ops(rpdev);
+	if (!ops || !ops->debug_data) {
+		debug("recv op not registered for device %s\n", rpdev->name);
+		return;
+	}
+
+	ops->debug_data(rpdev, vq_id);
+}
+
+int rpmsg_uclass_init(struct uclass *class)
+{
+	int ret;
+
+	/* make sure virtio framework is initialized */
+	ret = virtio_init();
+	if (ret)
+		debug("virtio init failed, %d\n", ret);
+
+	return ret;
+}
+
+UCLASS_DRIVER(rpmsg_bus) = {
+	.name	= "rpmsg_bus",
+	.id	= UCLASS_RPMSG,
+	.init   = rpmsg_uclass_init,
+	.flags	= DM_UC_FLAG_SEQ_ALIAS,
+};
diff --git a/drivers/rpmsg/rpmsg_internal.h b/drivers/rpmsg/rpmsg_internal.h
new file mode 100644
index 0000000000..4f7bf9fb90
--- /dev/null
+++ b/drivers/rpmsg/rpmsg_internal.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * remote processor messaging bus internals
+ *
+ * Copyright (C) 2011 Texas Instruments, Inc.
+ * Copyright (C) 2011 Google, Inc.
+ * Copyright (C) 2023, Advanced Micro Devices, Inc.
+ *
+ * Ohad Ben-Cohen <ohad at wizery.com>
+ * Brian Swetland <swetland at google.com>
+ */
+
+#ifndef __RPMSG_INTERNAL_H__
+#define __RPMSG_INTERNAL_H__
+
+#include <rpmsg.h>
+#include <virtio.h>
+
+/**
+ * struct rpmsg_hdr - common header for all rpmsg messages
+ * @src: source address
+ * @dst: destination address
+ * @reserved: reserved for future use
+ * @len: length of payload (in bytes)
+ * @flags: message flags
+ * @data: @len bytes of message payload data
+ *
+ * Every message sent(/received) on the rpmsg bus begins with this header.
+ */
+struct rpmsg_hdr {
+	__rpmsg32 src;
+	__rpmsg32 dst;
+	__rpmsg32 reserved;
+	__rpmsg16 len;
+	__rpmsg16 flags;
+	u8 data[];
+} __packed;
+
+/**
+ * struct rpmsg_device_ops - indirection table for the rpmsg_device operations
+ * @send: send data to core
+ * @recv: recv data to buf. data limited to buf_len bytes and buf is expected
+ *        to have that much memory allocated
+ * @debug_data: calls virtqueue_dump debug utility to print vq data
+ */
+struct rpmsg_device_ops {
+	int (*send)(struct udevice *rpdev, void *data, int len);
+	int (*recv)(struct udevice *rpdev, rpmsg_rx_cb_t cb);
+	void (*debug_data)(struct udevice *rpdev, int vq_id);
+};
+
+#endif
diff --git a/drivers/rpmsg/sandbox_test_rpmsg.c b/drivers/rpmsg/sandbox_test_rpmsg.c
new file mode 100644
index 0000000000..c94254aa4e
--- /dev/null
+++ b/drivers/rpmsg/sandbox_test_rpmsg.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023, Advanced Micro Devices, Inc.
+ */
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <log.h>
+#include <rpmsg.h>
+#include <asm/io.h>
+
+#include "rpmsg_internal.h"
+
+#define TEST_RPMSG_BUF_SIZE	512
+
+struct sandbox_test_devdata {
+	char rpmsg_data_buf[TEST_RPMSG_BUF_SIZE];
+	int msg_len;
+};
+
+static int sandbox_test_rpmsg_send(struct udevice *rpdev, void *msg, int len)
+{
+	/* ToDo: (maintainer) Implement send functionality for sandbox drv */
+	struct sandbox_test_devdata *data = dev_get_priv(rpdev);
+
+	if (len > TEST_RPMSG_BUF_SIZE)
+		len = TEST_RPMSG_BUF_SIZE;
+
+	data->msg_len = len;
+	memcpy(data->rpmsg_data_buf, msg, len);
+
+	return 0;
+}
+
+static int sandbox_test_rpmsg_recv(struct udevice *rpdev, rpmsg_rx_cb_t cb)
+{
+	/* ToDo: (maintainer) Implement recv functionality for sandbox drv */
+	struct sandbox_test_devdata *data = dev_get_priv(rpdev);
+	int len;
+
+	len = data->msg_len;
+
+	/* as of now only 1 buffer is used for testing. so third arg is 1 */
+	if (cb)
+		cb(&data->rpmsg_data_buf, len, 1);
+
+	return 0;
+}
+
+static void sandbox_test_rpmsg_debug_data(struct udevice *rpdev, int vq_id)
+{
+	/*
+	 * ToDo: (maintainer) Implement debug_data functionality
+	 * for sandbox drv
+	 */
+}
+
+int sandbox_test_rpmsg_probe(struct udevice *dev)
+{
+	/* ToDo: (maintainer) any init work */
+	debug("sandbox driver probbed\n");
+
+	return 0;
+}
+
+static const struct rpmsg_device_ops sandbox_test_rpmsg_ops = {
+	.send = sandbox_test_rpmsg_send,
+	.recv = sandbox_test_rpmsg_recv,
+	.debug_data = sandbox_test_rpmsg_debug_data,
+};
+
+static const struct udevice_id sandbox_ids[] = {
+	{.compatible = "sandbox,test-rpmsg"},
+	{}
+};
+
+U_BOOT_DRIVER(sandbox_test_rpmsg) = {
+	.name = "sandbox_test_rpmsg",
+	.of_match = sandbox_ids,
+	.id = UCLASS_RPMSG,
+	.ops = &sandbox_test_rpmsg_ops,
+	.probe = sandbox_test_rpmsg_probe,
+	.priv_auto = sizeof(struct sandbox_test_devdata),
+};
+
+U_BOOT_DRVINFO(sandbox_test_rpmsg) = {
+	.name = "sandbox_test_rpmsg",
+};
diff --git a/drivers/rpmsg/virtio_rpmsg_bus.c b/drivers/rpmsg/virtio_rpmsg_bus.c
new file mode 100644
index 0000000000..a41fab177d
--- /dev/null
+++ b/drivers/rpmsg/virtio_rpmsg_bus.c
@@ -0,0 +1,384 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2011 Texas Instruments, Inc.
+ * Copyright (C) 2011 Google, Inc.
+ * Copyright (C) 2023, Advanced Micro Devices, Inc.
+ *
+ * RPMsg virtio driver
+ * File proted from the Linux kernel:
+ * drivers/rpmsg/virtio_rpmsg_bus
+ */
+
+#include <common.h>
+#include <asm/dma-mapping.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <linux/compat.h>
+#include <linux/errno.h>
+#include <remoteproc.h>
+#include <rproc_virtio.h>
+#include <rpmsg.h>
+#include <virtio_types.h>
+#include <virtio.h>
+#include <virtio_ring.h>
+
+#include "rpmsg_internal.h"
+
+/*
+ * Allocate buffers of 512 bytes each for communications. The
+ * number of buffers will be computed from the number of buffers supported
+ * by the vring, upto a maximum of 512 buffers (256 in each direction).
+ *
+ * Each buffer will have 16 bytes for the msg header and 496 bytes for
+ * the payload.
+ *
+ * This will utilize a maximum total space of 256KB for the buffers.
+ *
+ * In future add support for user-provided buffers.
+ * This will allow bigger buffer size flexibility, and can also be used
+ * to achieve zero-copy messaging.
+ *
+ * Note that these numbers are purely a decision of this driver - user
+ * can change this without changing anything in the firmware of the remote
+ * processor.
+ */
+#define MAX_RPMSG_NUM_BUFS	(512)
+#define MAX_RPMSG_BUF_SIZE	(512)
+
+#define RPMSG_UBOOT_ADDR	(1035)
+
+/*
+ * Local addresses are dynamically allocated on-demand.
+ * We do not dynamically assign addresses from the low 1024 range,
+ * in order to reserve that address range for predefined services.
+ */
+#define RPMSG_RESERVED_ADDRESSES	(1024)
+
+/**
+ * struct virtproc_info - virtual remote processor state
+ * @vdev:	the virtio device
+ * @vqs:	rx and tx virtqueues
+ * @rvq:	rx virtqueue
+ * @svq:	tx virtqueue
+ * @rbufs:	kernel address of rx buffers
+ * @sbufs:	kernel address of tx buffers
+ * @num_bufs:	total number of buffers for rx and tx
+ * @buf_size:   size of one rx or tx buffer
+ * @last_sbuf:	index of last tx buffer used
+ * @bufs_dma:	dma base addr of the buffers
+ *
+ * This structure stores the rpmsg state of a given virtio remote processor
+ * device (there might be several virtio proc devices for each physical
+ * remote processor).
+ */
+struct virtproc_info {
+	struct udevice *vdev;
+
+	union {
+		struct virtqueue *vqs[2];
+		struct {
+			struct virtqueue *rvq;
+			struct virtqueue *svq;
+		};
+	};
+	void *rbufs, *sbufs;
+	unsigned int num_bufs;
+	unsigned int buf_size;
+	int last_sbuf;
+	unsigned long bufs_dma;
+};
+
+/* The feature bitmap for virtio rpmsg */
+#define VIRTIO_RPMSG_F_NS	0 /* RP doesn't support name service */
+
+/**
+ * rpmsg_sg_init - initialize scatterlist according to cpu address location
+ * @sg: scatterlist to fill
+ * @cpu_addr: virtual address of the buffer
+ * @len: buffer length
+ *
+ * An internal function filling scatterlist according to virtual address
+ * location (in vmalloc or in kernel).
+ */
+static void
+rpmsg_sg_init(struct virtio_sg *sg, void *cpu_addr, unsigned int len)
+{
+	sg->addr = cpu_addr;
+	sg->length = len;
+}
+
+void *get_tx_buf(struct virtproc_info *vrp)
+{
+	unsigned int len;
+	void *ret;
+
+	if (!vrp->svq) {
+		dev_err(vrp->vdev, "send vq not available for dev\n");
+		return NULL;
+	}
+
+	/*
+	 * either pick the next unused tx buffer
+	 * (half of our buffers are used for sending messages)
+	 */
+	if (vrp->last_sbuf < vrp->num_bufs / 2)
+		ret = vrp->sbufs + (vrp->buf_size * vrp->last_sbuf++);
+	/* or recycle a used one */
+	else
+		ret = virtqueue_get_buf(vrp->svq, &len);
+
+	return ret;
+}
+
+int virtio_rpmsg_bus_send(struct udevice *vdev, void *data, int len)
+{
+	struct virtproc_info *vrp;
+	struct rpmsg_hdr *msg;
+	struct virtio_sg sg = {0};
+	struct virtio_sg *sgs[] = { &sg };
+	int msg_len, err = 0;
+
+	vrp = dev_get_priv(vdev);
+
+	if (len > (vrp->buf_size - sizeof(struct rpmsg_hdr))) {
+		dev_err(vdev, "msg len %d is out of bound\n", len);
+		return -EMSGSIZE;
+	}
+
+	msg = get_tx_buf(vrp);
+	if (!msg) {
+		dev_err(vdev, "rpmsg can't get tx buffer\n");
+		return -ENOMEM;
+	}
+
+	msg->len = cpu_to_rpmsg16(len);
+	msg->src = cpu_to_rpmsg32(RPMSG_UBOOT_ADDR);
+	msg->dst = cpu_to_rpmsg32(RPMSG_UBOOT_ADDR);
+	msg->flags = 0;
+	msg->reserved = 0;
+	memcpy(msg->data, (char *)data, len);
+	msg_len = len + sizeof(*msg);
+	rpmsg_sg_init(&sg, msg, msg_len);
+
+	/* add messages to the remote processor's virtqueue */
+	err = virtqueue_add(vrp->svq, sgs, 1, 0);
+	if (err) {
+		/*
+		 * need to reclaim the buffer or it's lost.
+		 * memory won't lost but rpmsg won't use it for tx.
+		 * this will wait for buffer management overhaul.
+		 */
+		dev_err(vdev, "failed to add out buf\n");
+		return err;
+	}
+
+	/* tell remote processor that it has pending message to read */
+	virtqueue_kick(vrp->svq);
+
+	return 0;
+}
+
+static int rpmsg_recv_single(struct virtproc_info *vrp, struct rpmsg_hdr *msg,
+			     unsigned int len, int msgs_received,
+			     rpmsg_rx_cb_t cb)
+{
+	struct virtio_sg sg;
+	struct virtio_sg *sgs[] = { &sg };
+	bool little_endian = virtio_is_little_endian(vrp->vdev);
+	unsigned int msg_len = __rpmsg16_to_cpu(little_endian, msg->len);
+	int err;
+
+	debug("From: 0x%x, To: 0x%x, Len: %d, Flags: %d, Reserved: %d\n",
+	      __rpmsg32_to_cpu(little_endian, msg->src),
+	      __rpmsg32_to_cpu(little_endian, msg->dst), msg_len,
+	      __rpmsg16_to_cpu(little_endian, msg->flags),
+	      __rpmsg32_to_cpu(little_endian, msg->reserved));
+
+	/*
+	 * We currently use fixed-sized buffers, so trivially sanitize
+	 * the reported payload length.
+	 */
+	if (len > vrp->buf_size ||
+	    msg_len > (len - sizeof(struct rpmsg_hdr))) {
+		debug("inbound msg too big: (%d, %d)\n", len, msg_len);
+		return -EINVAL;
+	}
+
+	/* call rpmsg callback */
+	if (cb && msg->dst == RPMSG_UBOOT_ADDR)
+		cb(msg->data, msg_len, msgs_received);
+
+	/* publish the real size of the buffer */
+	rpmsg_sg_init(&sg, msg, vrp->buf_size);
+
+	/* add the buffer back to the remote processor's virtqueue */
+	err = virtqueue_add(vrp->rvq, sgs, 0, 1);
+	if (err < 0) {
+		dev_err(vrp->vdev, "failed to add a virtqueue buffer %d\n", err);
+		return err;
+	}
+
+	return 0;
+}
+
+int virtio_rpmsg_bus_recv(struct udevice *vdev, rpmsg_rx_cb_t cb)
+{
+	struct virtproc_info *vrp = dev_get_priv(vdev);
+	struct rpmsg_hdr *msg;
+	unsigned int len, msgs_received = 0;
+	int err;
+
+	rproc_flush_dcache(vdev->parent);
+
+	msg = virtqueue_get_buf(vrp->rvq, &len);
+	if (!msg) {
+		debug("uhm, incoming signal, but no used buffer ?\n");
+		return -ENODATA;
+	}
+
+	while (msg) {
+		err = rpmsg_recv_single(vrp, msg, len, msgs_received, cb);
+		if (err) {
+			debug("rpmsg failed to recv msg\n");
+			return err;
+		}
+
+		msgs_received++;
+
+		msg = virtqueue_get_buf(vrp->rvq, &len);
+	}
+
+	debug("rpmsg messages received = %d\n", msgs_received);
+
+	return 0;
+}
+
+static int virtio_rpmsg_bus_remove(struct udevice *vdev)
+{
+	virtio_reset(vdev);
+
+	return 0;
+}
+
+static int virtio_rpmsg_bus_probe(struct udevice *uvdev)
+{
+	struct rproc_rvdev_data *rvdev_data;
+	struct rproc_mem_entry *vdev_buf;
+	struct virtproc_info *vrp;
+	size_t total_buf_space;
+	int err = 0, i;
+	void *bufs_va;
+
+	vrp = dev_get_priv(uvdev);
+	if (!vrp) {
+		dev_err(uvdev, "vrp not available\n");
+		return -EINVAL;
+	}
+
+	vrp->vdev = uvdev;
+	rvdev_data = (struct rproc_rvdev_data *)dev_get_plat(uvdev->parent);
+	vdev_buf = rvdev_data->vdev_buf;
+	if (!vdev_buf) {
+		dev_err(uvdev, "vdev buffer isn't availablne\n");
+		return -ENOMEM;
+	}
+
+	err = virtio_find_vqs(uvdev, 2, vrp->vqs);
+	if (err) {
+		dev_err(uvdev, "failed to find vqs with err = %d\n", err);
+		return -EINVAL;
+	}
+
+	if (!vrp->vqs[0] || !vrp->vqs[1]) {
+		dev_err(uvdev, "failed to find vq\n");
+		return -EINVAL;
+	}
+
+	/* symmetric tx/rx vrings are expected */
+	if (virtqueue_get_vring_size(vrp->rvq) != virtqueue_get_vring_size(vrp->svq))
+		dev_warn(uvdev, "rx vq and tx vq are not same size\n");
+
+	/* less buffers are needed if vrings are small */
+	if (virtqueue_get_vring_size(vrp->rvq) < MAX_RPMSG_NUM_BUFS / 2)
+		vrp->num_bufs = virtqueue_get_vring_size(vrp->rvq) * 2;
+	else
+		vrp->num_bufs = MAX_RPMSG_NUM_BUFS;
+
+	vrp->buf_size = MAX_RPMSG_BUF_SIZE;
+	vrp->last_sbuf = 0;
+
+	total_buf_space = vrp->num_bufs * vrp->buf_size;
+
+	if (vdev_buf->len < total_buf_space) {
+		dev_err(uvdev, "not enough vdev buffer memory\n");
+		return -EINVAL;
+	}
+
+	/* allocate coherent memory for the buffers */
+	bufs_va = vdev_buf->va;
+	if (!bufs_va) {
+		err = -ENOMEM;
+		goto vqs_del;
+	}
+
+	/* half of the buffers is dedicated for RX */
+	vrp->rbufs = bufs_va;
+
+	/* and half is dedicated for TX */
+	vrp->sbufs = bufs_va + (total_buf_space / 2);
+
+	/* set up the receive buffers */
+	for (i = 0; i < vrp->num_bufs / 2; i++) {
+		struct virtio_sg sg;
+		struct virtio_sg *sgs[] = { &sg };
+
+		void *cpu_addr = vrp->rbufs + i * vrp->buf_size;
+
+		rpmsg_sg_init(&sg, cpu_addr, vrp->buf_size);
+
+		err = virtqueue_add(vrp->rvq, sgs, 0, 1);
+		if (err) /* sanity check; this can't really happen */
+			dev_warn(uvdev, "adding inbuf for dev %s, vq %d failed\n",
+				 vrp->vdev->name, vrp->rvq->index);
+	}
+
+	/* ToDo: Support Named service announcement */
+
+	/* let remote know that host can receive data */
+	virtqueue_kick(vrp->rvq);
+
+	return 0;
+
+vqs_del:
+	virtio_del_vqs(uvdev);
+
+	return err;
+}
+
+static void virtio_rpmsg_bus_debug_data(struct udevice *uvdev, int vq_id)
+{
+	struct virtproc_info *vrp;
+
+	vrp = dev_get_priv(uvdev);
+
+	if (vrp->vqs[vq_id])
+		virtqueue_dump(vrp->vqs[vq_id]);
+	else
+		dev_info(uvdev, "virtqueue %d not found\n", vq_id);
+}
+
+static const struct rpmsg_device_ops virtio_rpmsg_dev_ops = {
+	.send = virtio_rpmsg_bus_send,
+	.recv = virtio_rpmsg_bus_recv,
+	.debug_data = virtio_rpmsg_bus_debug_data,
+};
+
+U_BOOT_DRIVER(virtio_rpmsg_bus) = {
+	.name	= VIRTIO_RPROC_DRV_NAME,
+	.id	= UCLASS_RPMSG,
+	.probe	= virtio_rpmsg_bus_probe,
+	.remove = virtio_rpmsg_bus_remove,
+	.ops	= &virtio_rpmsg_dev_ops,
+	.priv_auto	= sizeof(struct virtproc_info),
+	.flags	= DM_FLAG_ACTIVE_DMA,
+};
diff --git a/drivers/virtio/virtio-uclass.c b/drivers/virtio/virtio-uclass.c
index 31bb21c534..0c76479c45 100644
--- a/drivers/virtio/virtio-uclass.c
+++ b/drivers/virtio/virtio-uclass.c
@@ -31,6 +31,7 @@ static const char *const virtio_drv_name[VIRTIO_ID_MAX_NUM] = {
 	[VIRTIO_ID_NET]		= VIRTIO_NET_DRV_NAME,
 	[VIRTIO_ID_BLOCK]	= VIRTIO_BLK_DRV_NAME,
 	[VIRTIO_ID_RNG]		= VIRTIO_RNG_DRV_NAME,
+	[VIRTIO_ID_RPROC]	= VIRTIO_RPROC_DRV_NAME,
 };
 
 int virtio_get_config(struct udevice *vdev, unsigned int offset,
diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
index 307ad6931c..a1a4746a63 100644
--- a/include/dm/uclass-id.h
+++ b/include/dm/uclass-id.h
@@ -114,6 +114,7 @@ enum uclass_id {
 	UCLASS_REMOTEPROC,	/* Remote Processor device */
 	UCLASS_RESET,		/* Reset controller device */
 	UCLASS_RNG,		/* Random Number Generator */
+	UCLASS_RPMSG,		/* RPMsg device */
 	UCLASS_RTC,		/* Real time clock device */
 	UCLASS_SCMI_AGENT,	/* Interface with an SCMI server */
 	UCLASS_SCSI,		/* SCSI device */
diff --git a/include/rpmsg.h b/include/rpmsg.h
new file mode 100644
index 0000000000..bed61240ca
--- /dev/null
+++ b/include/rpmsg.h
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _RPMSG_H_
+#define _RPMSG_H_
+
+/**
+ * struct dm_rpmsg_uclass_pdata - platform data for a CPU
+ * @name: Platform-specific way of naming the RPMsg platform device
+ * @driver_plat_data: driver specific platform data that may be needed.
+ *
+ * This can be accessed with dev_get_uclass_plat() for any UCLASS_RPMSG
+ * device.
+ *
+ */
+struct dm_rpmsg_uclass_pdata {
+	const char *name;
+	void *driver_plat_data;
+};
+
+typedef __u16 __bitwise __rpmsg16;
+typedef __u32 __bitwise __rpmsg32;
+typedef __u64 __bitwise __rpmsg64;
+
+#define RPMSG_ADDR_ANY		0xFFFFFFFF
+
+#define RPMSG_NAME_SIZE 32
+
+static inline bool rpmsg_is_little_endian(void)
+{
+#ifdef __LITTLE_ENDIAN
+	return true;
+#else
+	return false;
+#endif
+}
+
+static inline u16 __rpmsg16_to_cpu(bool little_endian, __rpmsg16 val)
+{
+	if (little_endian)
+		return le16_to_cpu((__force __le16)val);
+	else
+		return be16_to_cpu((__force __be16)val);
+}
+
+static inline __rpmsg16 __cpu_to_rpmsg16(bool little_endian, u16 val)
+{
+	if (little_endian)
+		return (__force __rpmsg16)cpu_to_le16(val);
+	else
+		return (__force __rpmsg16)cpu_to_be16(val);
+}
+
+static inline u32 __rpmsg32_to_cpu(bool little_endian, __rpmsg32 val)
+{
+	if (little_endian)
+		return le32_to_cpu((__force __le32)val);
+	else
+		return be32_to_cpu((__force __be32)val);
+}
+
+static inline __rpmsg32 __cpu_to_rpmsg32(bool little_endian, u32 val)
+{
+	if (little_endian)
+		return (__force __rpmsg32)cpu_to_le32(val);
+	else
+		return (__force __rpmsg32)cpu_to_be32(val);
+}
+
+static inline u64 __rpmsg64_to_cpu(bool little_endian, __rpmsg64 val)
+{
+	if (little_endian)
+		return le64_to_cpu((__force __le64)val);
+	else
+		return be64_to_cpu((__force __be64)val);
+}
+
+static inline __rpmsg64 __cpu_to_rpmsg64(bool little_endian, u64 val)
+{
+	if (little_endian)
+		return (__force __rpmsg64)cpu_to_le64(val);
+	else
+		return (__force __rpmsg64)cpu_to_be64(val);
+}
+
+static inline u16 rpmsg16_to_cpu(__rpmsg16 val)
+{
+	return __rpmsg16_to_cpu(rpmsg_is_little_endian(), val);
+}
+
+static inline __rpmsg16 cpu_to_rpmsg16(u16 val)
+{
+	return __cpu_to_rpmsg16(rpmsg_is_little_endian(), val);
+}
+
+static inline u32 rpmsg32_to_cpu(__rpmsg32 val)
+{
+	return __rpmsg32_to_cpu(rpmsg_is_little_endian(), val);
+}
+
+static inline __rpmsg32 cpu_to_rpmsg32(u32 val)
+{
+	return __cpu_to_rpmsg32(rpmsg_is_little_endian(), val);
+}
+
+static inline u64 rpmsg64_to_cpu(__rpmsg64 val)
+{
+	return __rpmsg64_to_cpu(rpmsg_is_little_endian(), val);
+}
+
+static inline __rpmsg64 cpu_to_rpmsg64(u64 val)
+{
+	return __cpu_to_rpmsg64(rpmsg_is_little_endian(), val);
+}
+
+typedef int (*rpmsg_rx_cb_t)(void *buf, int msg_len, u32 msgs_received);
+
+#if IS_ENABLED(CONFIG_RPMSG)
+
+int rpmsg_init(int core_id);
+int rpmsg_send(int core_id, void *data, int len);
+int rpmsg_recv(int core_id, rpmsg_rx_cb_t cb);
+
+#else
+int rpmsg_init(int core_id)
+{
+	return -ENODEV;
+}
+
+int rpmsg_send(int core_id, void *data, int len)
+{
+	return -ENODEV;
+}
+
+int rpmsg_recv(int core_id, rpmsg_rx_cb_t cb)
+{
+	return -ENODEV;
+}
+
+#endif /* IS_ENABLED(CONFIG_RPMSG) */
+
+#endif /* _RPMSG_H */
diff --git a/include/rproc_virtio.h b/include/rproc_virtio.h
index cbe8ff420f..96d0e3d15c 100644
--- a/include/rproc_virtio.h
+++ b/include/rproc_virtio.h
@@ -9,15 +9,19 @@
 #include <remoteproc.h>
 #include <dm/device.h>
 
-void rproc_flush_dcache(struct udevice *dev);
-
 #ifndef CONFIG_SANDBOX
 
+void rproc_flush_dcache(struct udevice *dev);
+
 int rproc_virtio_create_dev(struct udevice *parent, struct fw_rsc_vdev *rsc,
 			    int offset);
 
 #else
 
+void rproc_flush_dcache(struct udevice *dev)
+{
+}
+
 int rproc_virtio_create_dev(struct udevice *parent, struct fw_rsc_vdev *rsc,
 			    int offset)
 {
diff --git a/include/virtio.h b/include/virtio.h
index 16d0f8aa7f..35274a3593 100644
--- a/include/virtio.h
+++ b/include/virtio.h
@@ -26,11 +26,13 @@
 #define VIRTIO_ID_NET		1 /* virtio net */
 #define VIRTIO_ID_BLOCK		2 /* virtio block */
 #define VIRTIO_ID_RNG		4 /* virtio rng */
-#define VIRTIO_ID_MAX_NUM	5
+#define VIRTIO_ID_RPROC         7 /* virtio rproc */
+#define VIRTIO_ID_MAX_NUM	8
 
 #define VIRTIO_NET_DRV_NAME	"virtio-net"
 #define VIRTIO_BLK_DRV_NAME	"virtio-blk"
 #define VIRTIO_RNG_DRV_NAME	"virtio-rng"
+#define VIRTIO_RPROC_DRV_NAME	"virtio-rpmsg-bus"
 
 /* Status byte for guest to report progress, and synchronize features */
 
diff --git a/include/virtio_ring.h b/include/virtio_ring.h
index 4a9b4078ee..e0cd773913 100644
--- a/include/virtio_ring.h
+++ b/include/virtio_ring.h
@@ -264,6 +264,21 @@ struct virtqueue *vring_create_virtqueue(unsigned int index, unsigned int num,
 					 unsigned int vring_align,
 					 struct udevice *udev);
 
+/**
+ * vring_new_virtqueue - create a virtqueue at user defined address
+ *
+ * @index:	the index of the queue
+ * @vring:	vring created at user defined address
+ * @udev:	the virtio transport udevice
+ * @return:	the virtqueue pointer or NULL if failed
+ *
+ * This creates a virtqueue using vring address decided by the user of API
+ *
+ * This API is supposed to be called by the virtio transport driver in the
+ * virtio find_vqs() uclass method.
+ */
+struct virtqueue *vring_new_virtqueue(unsigned int index, struct vring vring,
+				      struct udevice *udev);
 /**
  * vring_del_virtqueue - destroy a virtqueue
  *
diff --git a/test/dm/Makefile b/test/dm/Makefile
index 3799b1ae8f..732c35a496 100644
--- a/test/dm/Makefile
+++ b/test/dm/Makefile
@@ -97,6 +97,7 @@ obj-$(CONFIG_RAM) += ram.o
 obj-y += regmap.o
 obj-$(CONFIG_REMOTEPROC) += remoteproc.o
 obj-$(CONFIG_DM_RESET) += reset.o
+obj-$(CONFIG_RPMSG) += rpmsg.o
 obj-$(CONFIG_SYSRESET) += sysreset.o
 obj-$(CONFIG_DM_REGULATOR) += regulator.o
 obj-$(CONFIG_DM_RNG) += rng.o
diff --git a/test/dm/rpmsg.c b/test/dm/rpmsg.c
new file mode 100644
index 0000000000..ecd2ba3680
--- /dev/null
+++ b/test/dm/rpmsg.c
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023, Advanced Micro Devices, Inc.
+ */
+#include <common.h>
+#include <dm.h>
+#include <elf.h>
+#include <errno.h>
+#include <rpmsg.h>
+#include <asm/io.h>
+#include <dm/test.h>
+#include <test/test.h>
+#include <test/ut.h>
+
+static char test_buf[512];
+
+static int rpmsg_rx_cb(void *buf, int msg_len, u32 msgs_received)
+{
+	memcpy(test_buf, buf, msg_len);
+
+	return 0;
+}
+
+/**
+ * dm_test_rpmsg() - test the operations after initializations
+ * @uts:	unit test state
+ *
+ * @return:	0 if all test pass
+ */
+static int dm_test_rpmsg(struct unit_test_state *uts)
+{
+	ut_assertok(rpmsg_init(0));
+	ut_assertok(rpmsg_send(0, "test rpmsg", strlen("test rpmsg")));
+	ut_assertok(rpmsg_recv(0, rpmsg_rx_cb));
+
+	ut_asserteq_str(test_buf, "test rpmsg");
+
+	return 0;
+}
+
+DM_TEST(dm_test_rpmsg, UT_TESTF_SCAN_PDATA | UT_TESTF_FLAT_TREE);
-- 
2.25.1



More information about the U-Boot mailing list