Add rpmsg capabilities to remoteproc

Tie in mbox and reset driver and add support for virtio carveouts.
Also move the reserved mem for device mem to the remoteproc node.

Change-Id: I7e8878d32ae7c02d5b43198e45652e77f8ff79ca
diff --git a/remoteproc/ethosu_remoteproc.c b/remoteproc/ethosu_remoteproc.c
index 3b45071..b1f8793 100644
--- a/remoteproc/ethosu_remoteproc.c
+++ b/remoteproc/ethosu_remoteproc.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021-2022 Arm Limited. All rights reserved.
+ * Copyright 2021-2023 Arm Limited and/or its affiliates
  *
  * This program is free software and is provided to you under the terms of the
  * GNU General Public License version 2 as published by the Free Software
@@ -17,26 +17,37 @@
  *
  * SPDX-License-Identifier: GPL-2.0-only
  */
-
+#include <linux/dma-mapping.h>
 #include <linux/firmware.h>
 #include <linux/io.h>
+#include <linux/irqreturn.h>
 #include <linux/kernel.h>
+#include <linux/mailbox_client.h>
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
 #include <linux/of_device.h>
+#include <linux/of_reserved_mem.h>
 #include <linux/platform_device.h>
 #include <linux/remoteproc.h>
 #include <linux/reset.h>
 #include <linux/version.h>
+#include <linux/workqueue.h>
+
+/****************************************************************************
+ * Defines
+ ****************************************************************************/
+
+#define DMA_ADDR_BITS 32 /* Number of address bits */
 
 #define ETHOSU_RPROC_DRIVER_VERSION "0.0.1"
 
 #define DEFAULT_FW_FILE "arm-ethos-u65.fw"
-#define DEFAULT_AUTO_BOOT (false)
+#define DEFAULT_AUTO_BOOT false
 
 /* firmware naming module parameter */
 static char fw_filename_param[256] = DEFAULT_FW_FILE;
+
 /* As the remoteproc is setup at probe, just allow the filename readonly */
 module_param_string(filename, fw_filename_param, sizeof(fw_filename_param),
 		    0444);
@@ -44,218 +55,241 @@
 		 "Filename for firmware image for Ethos-U remoteproc");
 
 static bool auto_boot = DEFAULT_AUTO_BOOT;
-module_param(auto_boot, bool, DEFAULT_AUTO_BOOT);
+module_param(auto_boot, bool, 0);
 MODULE_PARM_DESC(auto_boot, "Set to one to auto boot at load.");
 
 struct ethosu_rproc {
-	struct device            *dev;
-	struct reset_control     *rstc;
-	struct rproc_mem_mapping *map;
-	size_t                   map_size;
+	struct device           *dev;
+	struct reset_control    *rstc;
+	struct mbox_client      mbox_client;
+	struct mbox_chan        *ch_rx;
+	struct mbox_chan        *ch_tx;
+	struct workqueue_struct *wq;
+	struct work_struct      work;
 };
 
-struct rproc_mem_mapping {
-	const char   *name;
-	phys_addr_t  rproc_addr;
-	void __iomem *vaddr;
-	size_t       size;
-};
+/* declaration is in remoteproc_internal.h */
+extern irqreturn_t rproc_vq_interrupt(struct rproc *rproc,
+				      int vq_id);
 
-struct ethosu_rproc_config {
-	struct fw_config *fw;
-};
+static void ethosu_mbox_bottom(struct work_struct *work)
+{
+	struct ethosu_rproc *erproc = container_of(
+		work, struct ethosu_rproc, work);
+	struct rproc *rproc = dev_get_drvdata(erproc->dev);
 
-/*****************************************************************************/
+	dev_dbg(&rproc->dev, "Handle interrupt");
+
+	rproc_vq_interrupt(rproc, 0);
+}
+
+static void ethosu_mbox_top(struct mbox_client *client,
+			    void *message)
+{
+	struct ethosu_rproc *erproc = container_of(
+		client, struct ethosu_rproc, mbox_client);
+
+	queue_work(erproc->wq, &erproc->work);
+}
+
+static dma_addr_t ethosu_of_pa_to_da(struct rproc *rproc,
+				     const phys_addr_t pa,
+				     const size_t size)
+{
+	static const char of_rproc_ranges[] = "ethosu,dma-ranges";
+
+	struct device *dev = rproc->dev.parent;
+	struct device_node *np = dev->of_node;
+	const __be32 *rproc_ranges = of_get_property(np, of_rproc_ranges, NULL);
+	const int addr_cells = of_n_addr_cells(np);
+	const int size_cells = of_n_size_cells(np);
+	const int ranges_cells = addr_cells + addr_cells + size_cells;
+	int ranges_cnt;
+	int i;
+
+	ranges_cnt = of_property_count_elems_of_size(
+		np, of_rproc_ranges, ranges_cells * sizeof(u32));
+
+	for (i = 0; i < ranges_cnt; i++) {
+		const int offset = i * ranges_cells;
+
+		const uint64_t of_da = of_read_number(
+			&rproc_ranges[offset], addr_cells);
+		const uint64_t of_pa = of_read_number(
+			&rproc_ranges[offset + addr_cells], addr_cells);
+		const uint64_t of_size = of_read_number(
+			&rproc_ranges[offset + addr_cells + addr_cells],
+			size_cells);
+
+		if (pa >= of_pa && (pa + size) <= (of_pa + of_size)) {
+			const dma_addr_t da = of_da + pa - of_pa;
+
+			dev_dbg(dev, "PA to DA. pa=0x%llx, da=0x%llx", pa, da);
+
+			return da;
+		}
+	}
+
+	return (dma_addr_t)(-1);
+}
+
+static int ethosu_add_carveout(struct rproc *rproc,
+			       const phys_addr_t pa,
+			       const size_t size,
+			       const char *name)
+{
+	struct device *dev = rproc->dev.parent;
+	dma_addr_t da;
+	void __iomem *va;
+	struct rproc_mem_entry *mem;
+
+	da = ethosu_of_pa_to_da(rproc, pa, size);
+	if (da == (dma_addr_t)(-1)) {
+		dev_err(dev, "No mapping found for PA. pa=%llx, size=%zu", pa,
+			size);
+
+		return -ENOMEM;
+	}
+
+	va = devm_ioremap_wc(dev, pa, size);
+	if (!va) {
+		dev_err(dev, "Failed to remap address. pa=%llx, len=%zu", pa,
+			size);
+
+		return -ENOMEM;
+	}
+
+	mem = rproc_mem_entry_init(dev, va, pa, size, da, NULL, NULL, name);
+	if (!mem)
+		return -ENOMEM;
+
+	dev_info(dev, "Add carveout mapping. dma=%llx, da=%x, va=%p, len=%zu",
+		 mem->dma, mem->da, mem->va, mem->len);
+
+	rproc_add_carveout(rproc, mem);
+
+	return 0;
+}
+
+static int ethosu_rproc_prepare(struct rproc *rproc)
+{
+	struct device *dev = rproc->dev.parent;
+	struct device_node *np = dev->of_node;
+	struct of_phandle_iterator it;
+	struct resource res;
+	int i;
+	int ret;
+
+	dev_info(dev, "Preparing Ethos-U");
+
+	/* Add carveout for each 'reg' device tree entry */
+	for (i = 0; of_address_to_resource(np, i, &res) == 0; i++) {
+		dev_info(dev, "Found resource. start=%llx, size=%llx",
+			 res.start, resource_size(&res));
+
+		ret = ethosu_add_carveout(rproc, res.start,
+					  resource_size(&res), res.name);
+		if (ret)
+			return ret;
+	}
+
+	of_phandle_iterator_init(&it, np, "memory-region", NULL, 0);
+	while (of_phandle_iterator_next(&it) == 0) {
+		struct reserved_mem *res_mem = of_reserved_mem_lookup(it.node);
+
+		if (!res_mem) {
+			dev_err(dev, "Failed to look up memory region. node=%p",
+				it.node);
+
+			return -EINVAL;
+		}
+
+		dev_info(dev,
+			 "Found memory region. pa=%llx, size=%llu, name=%s",
+			 res_mem->base, res_mem->size, it.node->name);
+
+		ret = ethosu_add_carveout(rproc, res_mem->base, res_mem->size,
+					  it.node->name);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
 
 static int ethosu_rproc_start(struct rproc *rproc)
 {
-	struct ethosu_rproc *ethosu = (struct ethosu_rproc *)rproc->priv;
-	struct device *dev = ethosu->dev;
+	struct ethosu_rproc *erproc = (struct ethosu_rproc *)rproc->priv;
+	struct device *dev = erproc->dev;
 
-	dev_info(dev, "Starting up Ethos-U subsystem CPU!");
+	dev_info(dev, "Starting up Ethos-U subsystem CPU");
 
-	return reset_control_deassert(ethosu->rstc);
+	return reset_control_deassert(erproc->rstc);
 }
 
 static int ethosu_rproc_stop(struct rproc *rproc)
 {
-	struct ethosu_rproc *ethosu = (struct ethosu_rproc *)rproc->priv;
-	struct device *dev = ethosu->dev;
+	struct ethosu_rproc *erproc = (struct ethosu_rproc *)rproc->priv;
+	struct device *dev = erproc->dev;
 
-	dev_info(dev, "Stopping Ethos-U subsystem CPU!");
+	dev_info(dev, "Stopping Ethos-U subsystem CPU");
 
-	return reset_control_assert(ethosu->rstc);
+	return reset_control_assert(erproc->rstc);
 }
 
 static void ethosu_rproc_kick(struct rproc *rproc,
 			      int vqid)
 {
-	return;
-}
+	struct ethosu_rproc *erproc = (struct ethosu_rproc *)rproc->priv;
 
-#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 13, 0)
-static void *ethosu_da_to_va(struct rproc *rproc,
-			     u64 da,
-			     size_t len,
-			     bool *is_iomem)
-#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0) && LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0)
-static void *ethosu_da_to_va(struct rproc *rproc,
-			     u64 da,
-			     size_t len)
-#else
-static void *ethosu_da_to_va(struct rproc *rproc,
-			     u64 da,
-			     int len)
-#endif
-{
-	struct ethosu_rproc *ethosu = (struct ethosu_rproc *)rproc->priv;
-	int offset;
-	int i;
+	dev_dbg(&rproc->dev, "Kicking Ethos-U remoteproc vqid: %d!", vqid);
 
-	for (i = 0; i < ethosu->map_size; i++)
-		if (da >= ethosu->map[i].rproc_addr &&
-		    da < (ethosu->map[i].rproc_addr + ethosu->map[i].size)) {
-			offset = da - ethosu->map[i].rproc_addr;
-			dev_info(ethosu->dev,
-				 "mapping %llx to %p (offset: 0x%x)", da,
-				 (void *)(ethosu->map[i].vaddr + offset),
-				 offset);
-
-			return (void *)(ethosu->map[i].vaddr + offset);
-		}
-
-	return NULL;
+	mbox_send_message(erproc->ch_tx, (void *)&vqid);
 }
 
 static const struct rproc_ops ethosu_rproc_ops = {
-	.start    = &ethosu_rproc_start,
-	.stop     = &ethosu_rproc_stop,
-	.kick     = &ethosu_rproc_kick,
-	.da_to_va = &ethosu_da_to_va,
+	.prepare = &ethosu_rproc_prepare,
+	.start   = &ethosu_rproc_start,
+	.stop    = &ethosu_rproc_stop,
+	.kick    = &ethosu_rproc_kick,
 };
 
-/**
- * Since the remote side doesn't yet support rpmsg just return an
- * empty resource table when asked about it.
- */
-struct resource_table *ethosu_rproc_find_rsc_table(struct rproc *rproc,
-						   const struct firmware *fw,
-						   int *tablesz)
+static int ethosu_mailbox_init(struct ethosu_rproc *erproc)
 {
-	static struct resource_table table = { .ver = 1, };
-	struct ethosu_rproc *ethosu = (struct ethosu_rproc *)rproc->priv;
+	struct device *dev = erproc->dev;
+	struct mbox_client *cl = &erproc->mbox_client;
 
-	dev_info(ethosu->dev, "Sizeof struct resource_table : %zu",
-		 sizeof(table));
-	*tablesz = sizeof(table);
+	INIT_WORK(&erproc->work, ethosu_mbox_bottom);
 
-	return &table;
-}
-
-/*****************************************************************************/
-
-static int ethosu_rproc_of_memory_translations(struct platform_device *pdev,
-					       struct ethosu_rproc *ethosu_rproc)
-{
-	const char *const of_rproc_address_cells =
-		"#ethosu,rproc-address-cells";
-	const char *const of_rproc_ranges = "ethosu,rproc-ranges";
-	const char *const of_rproc_ranges_names = "ethosu,rproc-names";
-
-	struct device *dev = &pdev->dev;
-	struct device_node *np = dev->of_node;
-	struct rproc_mem_mapping *mem_map;
-	const __be32 *rproc_ranges;
-
-	int addr_cells, rproc_addr_cells, size_cells, cells_for_array_element;
-	int i, len, cnt, name_cnt, ret = 0;
-
-	if (of_property_read_u32(np, of_rproc_address_cells,
-				 &rproc_addr_cells)) {
-		dev_info(dev, "%s not defined in dtb", of_rproc_address_cells);
-
-		return -ENODEV;
-	}
-
-	addr_cells = of_n_addr_cells(np);
-	size_cells = of_n_size_cells(np);
-
-	dev_dbg(dev, "Using %d remote proc address cells for parsing mapping",
-		rproc_addr_cells);
-	dev_dbg(dev,
-		"Using %d of size %d parent address cells for parsing mapping",
-		addr_cells, size_cells);
-
-	cells_for_array_element = addr_cells + rproc_addr_cells + size_cells;
-
-	cnt = of_property_count_elems_of_size(np, of_rproc_ranges,
-					      cells_for_array_element);
-	cnt /= sizeof(u32);
-
-	if (cnt <= 0) {
-		dev_info(dev, "No remoteproc memory mapping ranges found.");
-
-		return 0;
-	}
-
-	name_cnt = of_property_count_strings(np, of_rproc_ranges_names);
-	if (name_cnt > 0 && name_cnt != cnt) {
-		dev_err(dev, "Mismatch length for %s and %s", of_rproc_ranges,
-			of_rproc_ranges_names);
+	erproc->wq = create_singlethread_workqueue("ethosu_rproc_wq");
+	if (!erproc->wq) {
+		dev_err(dev, "Failed to create work queue");
 
 		return -EINVAL;
 	}
 
-	mem_map = devm_kcalloc(dev, cnt, sizeof(*mem_map), GFP_KERNEL);
-	if (!mem_map)
-		return -ENOMEM;
+	cl->dev = dev;
+	cl->rx_callback = ethosu_mbox_top;
+	cl->tx_prepare = NULL;
+	cl->tx_done = NULL;
+	cl->tx_block = true;
+	cl->knows_txdone = false;
+	cl->tx_tout = 500;
 
-	rproc_ranges = of_get_property(np, of_rproc_ranges, &len);
+	erproc->ch_rx = mbox_request_channel_byname(cl, "rx");
+	if (IS_ERR(erproc->ch_rx)) {
+		dev_err(dev, "Failed to request mbox chan rx");
 
-	for (i = 0; i < cnt; i++) {
-		struct resource *r;
-		const char *name = NULL;
-		int n;
-
-		of_property_read_string_index(np, of_rproc_ranges_names, i,
-					      &name);
-		mem_map[i].name = name;
-		n = i * cells_for_array_element;
-		mem_map[i].rproc_addr =
-			of_read_number(&rproc_ranges[n + addr_cells],
-				       rproc_addr_cells);
-		mem_map[i].size =
-			of_read_number(&rproc_ranges[n + addr_cells +
-						     rproc_addr_cells],
-				       size_cells);
-
-		r = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
-		if (!r) {
-			dev_err(&pdev->dev, "Failed to get '%s' resource.\n",
-				name);
-
-			return -EINVAL;
-		}
-
-		mem_map[i].vaddr = devm_ioremap_wc(dev, r->start,
-						   mem_map[i].size);
-		if (IS_ERR(mem_map[i].vaddr)) {
-			dev_err(dev, "Failed to remap '%s'", name);
-
-			return PTR_ERR(mem_map[i].vaddr);
-		}
-
-		dev_dbg(dev,
-			"rproc memory mapping[%i]=%s: da %llx, va, %pa, size %zx:\n",
-			i, name, mem_map[i].rproc_addr, &mem_map[i].vaddr,
-			mem_map[i].size);
+		return PTR_ERR(erproc->ch_rx);
 	}
 
-	ethosu_rproc->map = mem_map;
-	ethosu_rproc->map_size = cnt;
-	dev_dbg(dev, "rproc memory mapped %zx regions", ethosu_rproc->map_size);
+	erproc->ch_tx = mbox_request_channel_byname(cl, "tx");
+	if (IS_ERR(erproc->ch_tx)) {
+		dev_info(dev, "Using same channel for RX and TX");
+		erproc->ch_tx = erproc->ch_rx;
+	}
 
-	return ret;
+	return 0;
 }
 
 static const struct of_device_id ethosu_rproc_match[] = {
@@ -267,47 +301,62 @@
 {
 	struct device *dev = &pdev->dev;
 	struct device_node *np = dev->of_node;
-	struct ethosu_rproc *ethosu_rproc;
+	struct ethosu_rproc *erproc;
 	struct rproc *rproc;
-	int ret = -ENODEV;
+	int ret;
 
-	rproc = rproc_alloc(dev, np->name, &ethosu_rproc_ops,
-			    fw_filename_param,
-			    sizeof(*ethosu_rproc));
-	if (!rproc) {
-		ret = -ENOMEM;
-		goto out;
-	}
+	/* map the first 'memory-region' for DMA-mapping */
+	ret = of_reserved_mem_device_init(dev);
+	if (ret)
+		return ret;
+
+	dma_set_mask_and_coherent(dev, DMA_BIT_MASK(DMA_ADDR_BITS));
+
+	rproc = devm_rproc_alloc(dev, np->name, &ethosu_rproc_ops,
+				 fw_filename_param,
+				 sizeof(*erproc));
+	if (!rproc)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, rproc);
 
 	/* Configure rproc */
 	rproc->has_iommu = false;
 	rproc->auto_boot = auto_boot;
 
-	platform_set_drvdata(pdev, rproc);
-
-	ethosu_rproc = rproc->priv;
-	ethosu_rproc->dev = dev;
+	/* Configure Ethos-U rproc */
+	erproc = rproc->priv;
+	erproc->dev = dev;
 
 	/* Get the reset handler for the subsystem */
-	ethosu_rproc->rstc = devm_reset_control_get_exclusive_by_index(dev, 0);
-	if (IS_ERR(ethosu_rproc->rstc)) {
-		dev_err(&pdev->dev, "Failed to get reset controller.\n");
-		ret = PTR_ERR(ethosu_rproc->rstc);
-		goto free_rproc;
+	erproc->rstc = devm_reset_control_get_exclusive_by_index(dev, 0);
+	if (IS_ERR(erproc->rstc)) {
+		dev_err(&pdev->dev, "Failed to get reset controller.");
+
+		return PTR_ERR(erproc->rstc);
 	}
 
-	/* Get the translation from device memory to kernel space */
-	ret = ethosu_rproc_of_memory_translations(pdev, ethosu_rproc);
+	/* Allocate and initialize mailbox client */
+	ret = ethosu_mailbox_init(erproc);
 	if (ret)
-		goto free_rproc;
+		return ret;
 
 	ret = rproc_add(rproc);
+	if (ret) {
+		dev_err(dev, "Failed to add rproc");
+		goto free_mbox;
+	}
 
-free_rproc:
-	if (ret)
-		rproc_free(rproc);
+	return 0;
 
-out:
+free_mbox:
+	if (erproc->wq)
+		destroy_workqueue(erproc->wq);
+
+	mbox_free_channel(erproc->ch_rx);
+
+	if (erproc->ch_tx != erproc->ch_rx)
+		mbox_free_channel(erproc->ch_tx);
 
 	return ret;
 }
@@ -315,9 +364,17 @@
 static int ethosu_rproc_remove(struct platform_device *pdev)
 {
 	struct rproc *rproc = platform_get_drvdata(pdev);
+	struct ethosu_rproc *erproc = rproc->priv;
+
+	if (erproc->wq)
+		destroy_workqueue(erproc->wq);
+
+	if (erproc->ch_tx != erproc->ch_rx)
+		mbox_free_channel(erproc->ch_tx);
+
+	mbox_free_channel(erproc->ch_rx);
 
 	rproc_del(rproc);
-	rproc_free(rproc);
 
 	return 0;
 }