meta-st-stm32mp/recipes-kernel/linux/linux-stm32mp/4.19/4.19.94/0021-ARM-stm32mp1-r3-REMOTE...

4020 lines
109 KiB
Diff

From b1e1c0d118b5f460f13d08f7a4f25210aaa88641 Mon Sep 17 00:00:00 2001
From: Lionel VITTE <lionel.vitte@st.com>
Date: Fri, 8 Nov 2019 16:52:45 +0100
Subject: [PATCH 21/31] ARM stm32mp1 r3 REMOTEPROC RPMSG RESET
---
drivers/remoteproc/Kconfig | 36 ++
drivers/remoteproc/Makefile | 3 +
drivers/remoteproc/remoteproc_core.c | 689 ++++++++++++++++++++-----
drivers/remoteproc/remoteproc_debugfs.c | 22 +-
drivers/remoteproc/remoteproc_internal.h | 12 +-
drivers/remoteproc/remoteproc_virtio.c | 58 ++-
drivers/remoteproc/rproc_srm_core.c | 303 +++++++++++
drivers/remoteproc/rproc_srm_core.h | 98 ++++
drivers/remoteproc/rproc_srm_dev.c | 744 +++++++++++++++++++++++++++
drivers/remoteproc/stm32_rproc.c | 858 +++++++++++++++++++++++++++++++
drivers/reset/reset-stm32mp1.c | 48 ++
drivers/rpmsg/Kconfig | 9 +
drivers/rpmsg/Makefile | 1 +
drivers/rpmsg/rpmsg_core.c | 19 +
drivers/rpmsg/rpmsg_internal.h | 2 +
drivers/rpmsg/rpmsg_tty.c | 310 +++++++++++
drivers/rpmsg/virtio_rpmsg_bus.c | 17 +-
include/linux/remoteproc.h | 38 +-
include/linux/rpmsg.h | 9 +
19 files changed, 3127 insertions(+), 149 deletions(-)
create mode 100644 drivers/remoteproc/rproc_srm_core.c
create mode 100644 drivers/remoteproc/rproc_srm_core.h
create mode 100644 drivers/remoteproc/rproc_srm_dev.c
create mode 100644 drivers/remoteproc/stm32_rproc.c
create mode 100644 drivers/rpmsg/rpmsg_tty.c
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index 052d4dd..c1f3f00 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -13,6 +13,25 @@ config REMOTEPROC
if REMOTEPROC
+config REMOTEPROC_SRM_CORE
+ tristate "Remoteproc System Resource Manager core"
+ depends on RPMSG
+ help
+ Say y here to enable the core driver of the remoteproc System Resource
+ Manager (SRM).
+ The SRM handles resources allocated to remote processors.
+ The core part is in charge of controlling the device children.
+
+config REMOTEPROC_SRM_DEV
+ tristate "Remoteproc System Resource Manager device"
+ depends on REMOTEPROC_SRM_CORE
+ help
+ Say y here to enable the device driver of the remoteproc System
+ Resource Manager (SRM).
+ The SRM handles resources allocated to remote processors.
+ The device part is in charge of reserving and initializing resources
+ for a peripheral assigned to a coprocessor.
+
config IMX_REMOTEPROC
tristate "IMX6/7 remoteproc support"
depends on SOC_IMX6SX || SOC_IMX7D
@@ -181,6 +200,23 @@ config ST_REMOTEPROC
config ST_SLIM_REMOTEPROC
tristate
+config STM32_RPROC
+ tristate "STM32 remoteproc support"
+ depends on ARCH_STM32
+ depends on REMOTEPROC
+ select MAILBOX
+ select REMOTEPROC_SRM_CORE
+ select REMOTEPROC_SRM_DEV
+ help
+ Say y here to support STM32 MCU processors via the
+ remote processor framework.
+
+ You want to say y here in order to enable AMP
+ use-cases to run on your platform (dedicated firmware could be
+ offloaded to remote MCU processors using this framework).
+
+ This can be either built-in or a loadable module.
+
endif # REMOTEPROC
endmenu
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index 03332fa..1e43aa6 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -9,6 +9,8 @@ remoteproc-y += remoteproc_debugfs.o
remoteproc-y += remoteproc_sysfs.o
remoteproc-y += remoteproc_virtio.o
remoteproc-y += remoteproc_elf_loader.o
+obj-$(CONFIG_REMOTEPROC_SRM_CORE) += rproc_srm_core.o
+obj-$(CONFIG_REMOTEPROC_SRM_DEV) += rproc_srm_dev.o
obj-$(CONFIG_IMX_REMOTEPROC) += imx_rproc.o
obj-$(CONFIG_OMAP_REMOTEPROC) += omap_remoteproc.o
obj-$(CONFIG_WKUP_M3_RPROC) += wkup_m3_rproc.o
@@ -25,3 +27,4 @@ qcom_wcnss_pil-y += qcom_wcnss.o
qcom_wcnss_pil-y += qcom_wcnss_iris.o
obj-$(CONFIG_ST_REMOTEPROC) += st_remoteproc.o
obj-$(CONFIG_ST_SLIM_REMOTEPROC) += st_slim_rproc.o
+obj-$(CONFIG_STM32_RPROC) += stm32_rproc.o
diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c
index aa62067..6430747 100644
--- a/drivers/remoteproc/remoteproc_core.c
+++ b/drivers/remoteproc/remoteproc_core.c
@@ -39,8 +39,11 @@
#include <linux/idr.h>
#include <linux/elf.h>
#include <linux/crc32.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/platform_device.h>
#include <linux/virtio_ids.h>
#include <linux/virtio_ring.h>
+#include <linux/of_platform.h>
#include <asm/byteorder.h>
#include "remoteproc_internal.h"
@@ -53,6 +56,11 @@ typedef int (*rproc_handle_resources_t)(struct rproc *rproc,
typedef int (*rproc_handle_resource_t)(struct rproc *rproc,
void *, int offset, int avail);
+static int rproc_alloc_carveout(struct rproc *rproc,
+ struct rproc_mem_entry *mem);
+static int rproc_release_carveout(struct rproc *rproc,
+ struct rproc_mem_entry *mem);
+
/* Unique indices for remoteproc devices */
static DEFINE_IDA(rproc_dev_index);
@@ -140,6 +148,23 @@ static void rproc_disable_iommu(struct rproc *rproc)
iommu_domain_free(domain);
}
+phys_addr_t rproc_va_to_pa(void *cpu_addr)
+{
+ /*
+ * Return physical address according to virtual address location
+ * - in vmalloc: if region ioremapped or defined as dma_alloc_coherent
+ * - in kernel: if region allocated in generic dma memory pool
+ */
+ if (is_vmalloc_addr(cpu_addr)) {
+ return page_to_phys(vmalloc_to_page(cpu_addr)) +
+ offset_in_page(cpu_addr);
+ }
+
+ WARN_ON(!virt_addr_valid(cpu_addr));
+ return virt_to_phys(cpu_addr);
+}
+EXPORT_SYMBOL(rproc_va_to_pa);
+
/**
* rproc_da_to_va() - lookup the kernel virtual address for a remoteproc address
* @rproc: handle of a remote processor
@@ -183,6 +208,10 @@ void *rproc_da_to_va(struct rproc *rproc, u64 da, int len)
list_for_each_entry(carveout, &rproc->carveouts, node) {
int offset = da - carveout->da;
+ /* Verify that carveout is allocated */
+ if (!carveout->va)
+ continue;
+
/* try next carveout if da is too small */
if (offset < 0)
continue;
@@ -201,27 +230,128 @@ void *rproc_da_to_va(struct rproc *rproc, u64 da, int len)
}
EXPORT_SYMBOL(rproc_da_to_va);
+/**
+ * rproc_find_carveout_by_name() - lookup the carveout region by a name
+ * @rproc: handle of a remote processor
+ * @name,..: carveout name to find (standard printf format)
+ *
+ * Platform driver has the capability to register some pre-allacoted carveout
+ * (physically contiguous memory regions) before rproc firmware loading and
+ * associated resource table analysis. These regions may be dedicated memory
+ * regions internal to the coprocessor or specified DDR region with specific
+ * attributes
+ *
+ * This function is a helper function with which we can go over the
+ * allocated carveouts and return associated region characteristics like
+ * coprocessor address, length or processor virtual address.
+ *
+ * Return: a valid pointer on carveout entry on success or NULL on failure.
+ */
+struct rproc_mem_entry *
+rproc_find_carveout_by_name(struct rproc *rproc, const char *name, ...)
+{
+ va_list args;
+ char _name[32];
+ struct rproc_mem_entry *carveout, *mem = NULL;
+
+ if (!name)
+ return NULL;
+
+ va_start(args, name);
+ vsnprintf(_name, sizeof(_name), name, args);
+ va_end(args);
+
+ list_for_each_entry(carveout, &rproc->carveouts, node) {
+ /* Compare carveout and requested names */
+ if (!strcmp(carveout->name, _name)) {
+ mem = carveout;
+ break;
+ }
+ }
+
+ return mem;
+}
+
+/**
+ * rproc_check_carveout_da() - Check specified carveout da configuration
+ * @rproc: handle of a remote processor
+ * @mem: pointer on carveout to check
+ * @da: area device address
+ * @len: associated area size
+ *
+ * This function is a helper function to verify requested device area (couple
+ * da, len) is part of specified carevout.
+ *
+ * Return: 0 if carveout matchs request else -ENOMEM
+ */
+static int rproc_check_carveout_da(struct rproc *rproc,
+ struct rproc_mem_entry *mem, u32 da, u32 len)
+{
+ struct device *dev = &rproc->dev;
+ int delta;
+
+ /* Check requested resource length */
+ if (len > mem->len) {
+ dev_err(dev, "Registered carveout doesn't fit len request\n");
+ return -EINVAL;
+ }
+
+ if (da != FW_RSC_ADDR_ANY && mem->da == FW_RSC_ADDR_ANY) {
+ /* Address doesn't match registered carveout configuration */
+ return -EINVAL;
+ } else if (da != FW_RSC_ADDR_ANY && mem->da != FW_RSC_ADDR_ANY) {
+ delta = da - mem->da;
+
+ /* Check requested resource belongs to registered carveout */
+ if (delta < 0) {
+ dev_err(dev,
+ "Registered carveout doesn't fit da request\n");
+ return -EINVAL;
+ }
+
+ if (delta + len > mem->len) {
+ dev_err(dev,
+ "Registered carveout doesn't fit len request\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
int rproc_alloc_vring(struct rproc_vdev *rvdev, int i)
{
struct rproc *rproc = rvdev->rproc;
struct device *dev = &rproc->dev;
struct rproc_vring *rvring = &rvdev->vring[i];
struct fw_rsc_vdev *rsc;
- dma_addr_t dma;
- void *va;
int ret, size, notifyid;
+ struct rproc_mem_entry *mem;
/* actual size of vring (in bytes) */
size = PAGE_ALIGN(vring_size(rvring->len, rvring->align));
- /*
- * Allocate non-cacheable memory for the vring. In the future
- * this call will also configure the IOMMU for us
- */
- va = dma_alloc_coherent(dev->parent, size, &dma, GFP_KERNEL);
- if (!va) {
- dev_err(dev->parent, "dma_alloc_coherent failed\n");
- return -EINVAL;
+ rsc = (void *)rproc->table_ptr + rvdev->rsc_offset;
+
+ /* Search for pre-registered carveout */
+ mem = rproc_find_carveout_by_name(rproc, "vdev%dvring%d", rvdev->index,
+ i);
+ if (mem) {
+ if (rproc_check_carveout_da(rproc, mem, rsc->vring[i].da, size))
+ return -ENOMEM;
+ } else {
+ /* Register carveout in in list */
+ mem = rproc_mem_entry_init(dev, 0, 0, size, rsc->vring[i].da,
+ rproc_alloc_carveout,
+ rproc_release_carveout,
+ "vdev%dvring%d",
+ rvdev->index, i);
+ if (!mem) {
+ dev_err(dev, "Can't allocate memory entry structure\n");
+ return -ENOMEM;
+ }
+
+ rproc_add_carveout(rproc, mem);
}
/*
@@ -232,7 +362,6 @@ int rproc_alloc_vring(struct rproc_vdev *rvdev, int i)
ret = idr_alloc(&rproc->notifyids, rvring, 0, 0, GFP_KERNEL);
if (ret < 0) {
dev_err(dev, "idr_alloc failed: %d\n", ret);
- dma_free_coherent(dev->parent, size, va, dma);
return ret;
}
notifyid = ret;
@@ -241,21 +370,9 @@ int rproc_alloc_vring(struct rproc_vdev *rvdev, int i)
if (notifyid > rproc->max_notifyid)
rproc->max_notifyid = notifyid;
- dev_dbg(dev, "vring%d: va %pK dma %pad size 0x%x idr %d\n",
- i, va, &dma, size, notifyid);
-
- rvring->va = va;
- rvring->dma = dma;
rvring->notifyid = notifyid;
- /*
- * Let the rproc know the notifyid and da of this vring.
- * Not all platforms use dma_alloc_coherent to automatically
- * set up the iommu. In this case the device address (da) will
- * hold the physical address and not the device address.
- */
- rsc = (void *)rproc->table_ptr + rvdev->rsc_offset;
- rsc->vring[i].da = dma;
+ /* Let the rproc know the notifyid of this vring.*/
rsc->vring[i].notifyid = notifyid;
return 0;
}
@@ -287,12 +404,10 @@ rproc_parse_vring(struct rproc_vdev *rvdev, struct fw_rsc_vdev *rsc, int i)
void rproc_free_vring(struct rproc_vring *rvring)
{
- int size = PAGE_ALIGN(vring_size(rvring->len, rvring->align));
struct rproc *rproc = rvring->rvdev->rproc;
int idx = rvring->rvdev->vring - rvring;
struct fw_rsc_vdev *rsc;
- dma_free_coherent(rproc->dev.parent, size, rvring->va, rvring->dma);
idr_remove(&rproc->notifyids, rvring->notifyid);
/* reset resource entry info */
@@ -316,6 +431,20 @@ static void rproc_vdev_do_stop(struct rproc_subdev *subdev, bool crashed)
}
/**
+ * rproc_rvdev_release() - release the existence of a rvdev
+ *
+ * @dev: the subdevice's dev
+ */
+static void rproc_rvdev_release(struct device *dev)
+{
+ struct rproc_vdev *rvdev = container_of(dev, struct rproc_vdev, dev);
+
+ of_reserved_mem_device_release(dev);
+
+ kfree(rvdev);
+}
+
+/**
* rproc_handle_vdev() - handle a vdev fw resource
* @rproc: the remote processor
* @rsc: the vring resource descriptor
@@ -348,6 +477,7 @@ static int rproc_handle_vdev(struct rproc *rproc, struct fw_rsc_vdev *rsc,
struct device *dev = &rproc->dev;
struct rproc_vdev *rvdev;
int i, ret;
+ char name[16];
/* make sure resource isn't truncated */
if (sizeof(*rsc) + rsc->num_of_vrings * sizeof(struct fw_rsc_vdev_vring)
@@ -379,6 +509,30 @@ static int rproc_handle_vdev(struct rproc *rproc, struct fw_rsc_vdev *rsc,
rvdev->id = rsc->id;
rvdev->rproc = rproc;
+ rvdev->index = rproc->nb_vdev++;
+
+ /* Initialise vdev subdevice */
+ snprintf(name, sizeof(name), "vdev%dbuffer", rvdev->index);
+ rvdev->dev.parent = rproc->dev.parent;
+ rvdev->dev.release = rproc_rvdev_release;
+ dev_set_name(&rvdev->dev, "%s#%s", dev_name(rvdev->dev.parent), name);
+ dev_set_drvdata(&rvdev->dev, rvdev);
+
+ ret = device_register(&rvdev->dev);
+ if (ret) {
+ put_device(&rvdev->dev);
+ return ret;
+ }
+ /* Make device dma capable by inheriting from parent's capabilities */
+ set_dma_ops(&rvdev->dev, get_dma_ops(rproc->dev.parent));
+
+ ret = dma_coerce_mask_and_coherent(&rvdev->dev,
+ dma_get_mask(rproc->dev.parent));
+ if (ret) {
+ dev_warn(dev,
+ "Failed to set DMA mask %llx. Trying to continue... %x\n",
+ dma_get_mask(rproc->dev.parent), ret);
+ }
/* parse the vrings */
for (i = 0; i < rsc->num_of_vrings; i++) {
@@ -410,7 +564,7 @@ static int rproc_handle_vdev(struct rproc *rproc, struct fw_rsc_vdev *rsc,
for (i--; i >= 0; i--)
rproc_free_vring(&rvdev->vring[i]);
free_rvdev:
- kfree(rvdev);
+ device_unregister(&rvdev->dev);
return ret;
}
@@ -423,15 +577,12 @@ void rproc_vdev_release(struct kref *ref)
for (id = 0; id < ARRAY_SIZE(rvdev->vring); id++) {
rvring = &rvdev->vring[id];
- if (!rvring->va)
- continue;
-
rproc_free_vring(rvring);
}
rproc_remove_subdev(rproc, &rvdev->subdev);
list_del(&rvdev->node);
- kfree(rvdev);
+ device_unregister(&rvdev->dev);
}
/**
@@ -453,9 +604,8 @@ void rproc_vdev_release(struct kref *ref)
static int rproc_handle_trace(struct rproc *rproc, struct fw_rsc_trace *rsc,
int offset, int avail)
{
- struct rproc_mem_entry *trace;
+ struct rproc_debug_trace *trace;
struct device *dev = &rproc->dev;
- void *ptr;
char name[15];
if (sizeof(*rsc) > avail) {
@@ -469,28 +619,23 @@ static int rproc_handle_trace(struct rproc *rproc, struct fw_rsc_trace *rsc,
return -EINVAL;
}
- /* what's the kernel address of this resource ? */
- ptr = rproc_da_to_va(rproc, rsc->da, rsc->len);
- if (!ptr) {
- dev_err(dev, "erroneous trace resource entry\n");
- return -EINVAL;
- }
-
trace = kzalloc(sizeof(*trace), GFP_KERNEL);
if (!trace)
return -ENOMEM;
/* set the trace buffer dma properties */
- trace->len = rsc->len;
- trace->va = ptr;
+ trace->trace_mem.len = rsc->len;
+ trace->trace_mem.da = rsc->da;
+
+ /* set pointer on rproc device */
+ trace->rproc = rproc;
/* make sure snprintf always null terminates, even if truncating */
snprintf(name, sizeof(name), "trace%d", rproc->num_traces);
/* create the debugfs entry */
- trace->priv = rproc_create_trace_file(name, rproc, trace);
- if (!trace->priv) {
- trace->va = NULL;
+ trace->tfile = rproc_create_trace_file(name, rproc, trace);
+ if (!trace->tfile) {
kfree(trace);
return -EINVAL;
}
@@ -499,8 +644,8 @@ static int rproc_handle_trace(struct rproc *rproc, struct fw_rsc_trace *rsc,
rproc->num_traces++;
- dev_dbg(dev, "%s added: va %pK, da 0x%x, len 0x%x\n",
- name, ptr, rsc->da, rsc->len);
+ dev_dbg(dev, "%s added: da 0x%x, len 0x%x\n",
+ name, rsc->da, rsc->len);
return 0;
}
@@ -584,61 +729,43 @@ static int rproc_handle_devmem(struct rproc *rproc, struct fw_rsc_devmem *rsc,
}
/**
- * rproc_handle_carveout() - handle phys contig memory allocation requests
+ * rproc_alloc_carveout() - allocated specified carveout
* @rproc: rproc handle
- * @rsc: the resource entry
- * @avail: size of available data (for image validation)
+ * @mem: the memory entry to allocate
*
- * This function will handle firmware requests for allocation of physically
- * contiguous memory regions.
- *
- * These request entries should come first in the firmware's resource table,
- * as other firmware entries might request placing other data objects inside
- * these memory regions (e.g. data/code segments, trace resource entries, ...).
- *
- * Allocating memory this way helps utilizing the reserved physical memory
- * (e.g. CMA) more efficiently, and also minimizes the number of TLB entries
- * needed to map it (in case @rproc is using an IOMMU). Reducing the TLB
- * pressure is important; it may have a substantial impact on performance.
+ * This function allocate specified memory entry @mem using
+ * dma_alloc_coherent() as default allocator
*/
-static int rproc_handle_carveout(struct rproc *rproc,
- struct fw_rsc_carveout *rsc,
- int offset, int avail)
+static int rproc_alloc_carveout(struct rproc *rproc,
+ struct rproc_mem_entry *mem)
{
- struct rproc_mem_entry *carveout, *mapping;
+ struct rproc_mem_entry *mapping = NULL;
struct device *dev = &rproc->dev;
dma_addr_t dma;
void *va;
int ret;
- if (sizeof(*rsc) > avail) {
- dev_err(dev, "carveout rsc is truncated\n");
- return -EINVAL;
- }
-
- /* make sure reserved bytes are zeroes */
- if (rsc->reserved) {
- dev_err(dev, "carveout rsc has non zero reserved bytes\n");
- return -EINVAL;
- }
-
- dev_dbg(dev, "carveout rsc: name: %s, da 0x%x, pa 0x%x, len 0x%x, flags 0x%x\n",
- rsc->name, rsc->da, rsc->pa, rsc->len, rsc->flags);
-
- carveout = kzalloc(sizeof(*carveout), GFP_KERNEL);
- if (!carveout)
- return -ENOMEM;
-
- va = dma_alloc_coherent(dev->parent, rsc->len, &dma, GFP_KERNEL);
+ va = dma_alloc_coherent(dev->parent, mem->len, &dma, GFP_KERNEL);
if (!va) {
dev_err(dev->parent,
- "failed to allocate dma memory: len 0x%x\n", rsc->len);
- ret = -ENOMEM;
- goto free_carv;
+ "failed to allocate dma memory: len 0x%x\n", mem->len);
+ return -ENOMEM;
}
dev_dbg(dev, "carveout va %pK, dma %pad, len 0x%x\n",
- va, &dma, rsc->len);
+ va, &dma, mem->len);
+
+ if (mem->da != FW_RSC_ADDR_ANY && !rproc->domain) {
+ /*
+ * Check requested da is equal to dma address
+ * and print a warn message in case of missalignment.
+ * Don't stop rproc_start sequence as coprocessor may
+ * build pa to da translation on its side.
+ */
+ if (mem->da != (u32)dma)
+ dev_warn(dev->parent,
+ "Allocated carveout doesn't fit device address request\n");
+ }
/*
* Ok, this is non-standard.
@@ -657,15 +784,15 @@ static int rproc_handle_carveout(struct rproc *rproc,
* to use the iommu-based DMA API: we expect 'dma' to contain the
* physical address in this case.
*/
- if (rproc->domain) {
+ if (mem->da != FW_RSC_ADDR_ANY && rproc->domain) {
mapping = kzalloc(sizeof(*mapping), GFP_KERNEL);
if (!mapping) {
ret = -ENOMEM;
goto dma_free;
}
- ret = iommu_map(rproc->domain, rsc->da, dma, rsc->len,
- rsc->flags);
+ ret = iommu_map(rproc->domain, mem->da, dma, mem->len,
+ mem->flags);
if (ret) {
dev_err(dev, "iommu_map failed: %d\n", ret);
goto free_mapping;
@@ -678,52 +805,226 @@ static int rproc_handle_carveout(struct rproc *rproc,
* We can't trust the remote processor not to change the
* resource table, so we must maintain this info independently.
*/
- mapping->da = rsc->da;
- mapping->len = rsc->len;
+ mapping->da = mem->da;
+ mapping->len = mem->len;
list_add_tail(&mapping->node, &rproc->mappings);
dev_dbg(dev, "carveout mapped 0x%x to %pad\n",
- rsc->da, &dma);
+ mem->da, &dma);
}
- /*
- * Some remote processors might need to know the pa
- * even though they are behind an IOMMU. E.g., OMAP4's
- * remote M3 processor needs this so it can control
- * on-chip hardware accelerators that are not behind
- * the IOMMU, and therefor must know the pa.
- *
- * Generally we don't want to expose physical addresses
- * if we don't have to (remote processors are generally
- * _not_ trusted), so we might want to do this only for
- * remote processor that _must_ have this (e.g. OMAP4's
- * dual M3 subsystem).
- *
- * Non-IOMMU processors might also want to have this info.
- * In this case, the device address and the physical address
- * are the same.
- */
- rsc->pa = dma;
+ if (mem->da == FW_RSC_ADDR_ANY) {
+ /* Update device address as undefined by requester */
+ if (sizeof(dma_addr_t) > sizeof(u32))
+ dev_warn(dev, "DMA address cast in 32bit to fit resource table format\n");
- carveout->va = va;
- carveout->len = rsc->len;
- carveout->dma = dma;
- carveout->da = rsc->da;
+ mem->da = (u32)dma;
+ }
- list_add_tail(&carveout->node, &rproc->carveouts);
+ mem->dma = dma;
+ mem->va = va;
return 0;
free_mapping:
kfree(mapping);
dma_free:
- dma_free_coherent(dev->parent, rsc->len, va, dma);
-free_carv:
- kfree(carveout);
+ dma_free_coherent(dev->parent, mem->len, va, dma);
return ret;
}
-/*
+/**
+ * rproc_release_carveout() - release acquired carveout
+ * @rproc: rproc handle
+ * @mem: the memory entry to release
+ *
+ * This function releases specified memory entry @mem allocated via
+ * rproc_alloc_carveout() function by @rproc.
+ */
+static int rproc_release_carveout(struct rproc *rproc,
+ struct rproc_mem_entry *mem)
+{
+ struct device *dev = &rproc->dev;
+
+ /* clean up carveout allocations */
+ dma_free_coherent(dev->parent, mem->len, mem->va, mem->dma);
+ return 0;
+}
+
+/**
+ * rproc_handle_carveout() - handle phys contig memory allocation requests
+ * @rproc: rproc handle
+ * @rsc: the resource entry
+ * @avail: size of available data (for image validation)
+ *
+ * This function will handle firmware requests for allocation of physically
+ * contiguous memory regions.
+ *
+ * These request entries should come first in the firmware's resource table,
+ * as other firmware entries might request placing other data objects inside
+ * these memory regions (e.g. data/code segments, trace resource entries, ...).
+ *
+ * Allocating memory this way helps utilizing the reserved physical memory
+ * (e.g. CMA) more efficiently, and also minimizes the number of TLB entries
+ * needed to map it (in case @rproc is using an IOMMU). Reducing the TLB
+ * pressure is important; it may have a substantial impact on performance.
+ */
+static int rproc_handle_carveout(struct rproc *rproc,
+ struct fw_rsc_carveout *rsc,
+ int offset, int avail)
+{
+ struct rproc_mem_entry *carveout;
+ struct device *dev = &rproc->dev;
+
+ if (sizeof(*rsc) > avail) {
+ dev_err(dev, "carveout rsc is truncated\n");
+ return -EINVAL;
+ }
+
+ /* make sure reserved bytes are zeroes */
+ if (rsc->reserved) {
+ dev_err(dev, "carveout rsc has non zero reserved bytes\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(dev, "carveout rsc: name: %s, da 0x%x, pa 0x%x, len 0x%x, flags 0x%x\n",
+ rsc->name, rsc->da, rsc->pa, rsc->len, rsc->flags);
+
+ /*
+ * Check carveout rsc already part of a registered carveout,
+ * Search by name, then check the da and length
+ */
+ carveout = rproc_find_carveout_by_name(rproc, rsc->name);
+
+ if (carveout) {
+ if (carveout->rsc_offset != FW_RSC_ADDR_ANY) {
+ dev_err(dev,
+ "Carveout already associated to resource table\n");
+ return -ENOMEM;
+ }
+
+ if (rproc_check_carveout_da(rproc, carveout, rsc->da, rsc->len))
+ return -ENOMEM;
+
+ /* Update memory carveout with resource table info */
+ carveout->rsc_offset = offset;
+ carveout->flags = rsc->flags;
+
+ return 0;
+ }
+
+ /* Register carveout in in list */
+ carveout = rproc_mem_entry_init(dev, 0, 0, rsc->len, rsc->da,
+ rproc_alloc_carveout,
+ rproc_release_carveout, rsc->name);
+ if (!carveout) {
+ dev_err(dev, "Can't allocate memory entry structure\n");
+ return -ENOMEM;
+ }
+
+ carveout->flags = rsc->flags;
+ carveout->rsc_offset = offset;
+ rproc_add_carveout(rproc, carveout);
+
+ return 0;
+}
+
+/**
+ * rproc_add_carveout() - register an allocated carveout region
+ * @rproc: rproc handle
+ * @mem: memory entry to register
+ *
+ * This function registers specified memory entry in @rproc carveouts list.
+ * Specified carveout should have been allocated before registering.
+ */
+void rproc_add_carveout(struct rproc *rproc, struct rproc_mem_entry *mem)
+{
+ list_add_tail(&mem->node, &rproc->carveouts);
+}
+EXPORT_SYMBOL(rproc_add_carveout);
+
+/**
+ * rproc_mem_entry_init() - allocate and initialize rproc_mem_entry struct
+ * @dev: pointer on device struct
+ * @va: virtual address
+ * @dma: dma address
+ * @len: memory carveout length
+ * @da: device address
+ * @alloc: memory carveout allocation function
+ * @release: memory carveout release function
+ * @name: carveout name
+ *
+ * This function allocates a rproc_mem_entry struct and fill it with parameters
+ * provided by client.
+ */
+struct rproc_mem_entry *
+rproc_mem_entry_init(struct device *dev,
+ void *va, dma_addr_t dma, int len, u32 da,
+ int (*alloc)(struct rproc *, struct rproc_mem_entry *),
+ int (*release)(struct rproc *, struct rproc_mem_entry *),
+ const char *name, ...)
+{
+ struct rproc_mem_entry *mem;
+ va_list args;
+
+ mem = kzalloc(sizeof(*mem), GFP_KERNEL);
+ if (!mem)
+ return mem;
+
+ mem->va = va;
+ mem->dma = dma;
+ mem->da = da;
+ mem->len = len;
+ mem->alloc = alloc;
+ mem->release = release;
+ mem->rsc_offset = FW_RSC_ADDR_ANY;
+ mem->of_resm_idx = -1;
+
+ va_start(args, name);
+ vsnprintf(mem->name, sizeof(mem->name), name, args);
+ va_end(args);
+
+ return mem;
+}
+EXPORT_SYMBOL(rproc_mem_entry_init);
+
+/**
+ * rproc_of_resm_mem_entry_init() - allocate and initialize rproc_mem_entry struct
+ * from a reserved memory phandle
+ * @dev: pointer on device struct
+ * @of_resm_idx: reserved memory phandle index in "memory-region"
+ * @len: memory carveout length
+ * @da: device address
+ * @name: carveout name
+ *
+ * This function allocates a rproc_mem_entry struct and fill it with parameters
+ * provided by client.
+ */
+struct rproc_mem_entry *
+rproc_of_resm_mem_entry_init(struct device *dev, u32 of_resm_idx, int len,
+ u32 da, const char *name, ...)
+{
+ struct rproc_mem_entry *mem;
+ va_list args;
+
+ mem = kzalloc(sizeof(*mem), GFP_KERNEL);
+ if (!mem)
+ return mem;
+
+ mem->da = da;
+ mem->len = len;
+ mem->rsc_offset = FW_RSC_ADDR_ANY;
+ mem->of_resm_idx = of_resm_idx;
+
+ va_start(args, name);
+ vsnprintf(mem->name, sizeof(mem->name), name, args);
+ va_end(args);
+
+ return mem;
+}
+EXPORT_SYMBOL(rproc_of_resm_mem_entry_init);
+
+/**
* A lookup table for resource handlers. The indices are defined in
* enum fw_resource_type.
*/
@@ -845,6 +1146,74 @@ static void rproc_unprepare_subdevices(struct rproc *rproc)
}
/**
+ * rproc_alloc_registered_carveouts() - allocate all carveouts registered
+ * in the list
+ * @rproc: the remote processor handle
+ *
+ * This function parses registered carveout list, performs allocation
+ * if alloc() ops registered and updates resource table information
+ * if rsc_offset set.
+ *
+ * Return: 0 on success
+ */
+static int rproc_alloc_registered_carveouts(struct rproc *rproc)
+{
+ struct rproc_mem_entry *entry, *tmp;
+ struct fw_rsc_carveout *rsc;
+ struct device *dev = &rproc->dev;
+ int ret;
+
+ list_for_each_entry_safe(entry, tmp, &rproc->carveouts, node) {
+ if (entry->alloc) {
+ ret = entry->alloc(rproc, entry);
+ if (ret) {
+ dev_err(dev, "Unable to allocate carveout %s: %d\n",
+ entry->name, ret);
+ return -ENOMEM;
+ }
+ }
+
+ if (entry->rsc_offset != FW_RSC_ADDR_ANY) {
+ /* update resource table */
+ rsc = (void *)rproc->table_ptr + entry->rsc_offset;
+
+ /*
+ * Some remote processors might need to know the pa
+ * even though they are behind an IOMMU. E.g., OMAP4's
+ * remote M3 processor needs this so it can control
+ * on-chip hardware accelerators that are not behind
+ * the IOMMU, and therefor must know the pa.
+ *
+ * Generally we don't want to expose physical addresses
+ * if we don't have to (remote processors are generally
+ * _not_ trusted), so we might want to do this only for
+ * remote processor that _must_ have this (e.g. OMAP4's
+ * dual M3 subsystem).
+ *
+ * Non-IOMMU processors might also want to have this info.
+ * In this case, the device address and the physical address
+ * are the same.
+ */
+
+ /* Use va if defined else dma to generate pa */
+ if (sizeof(dma_addr_t) > sizeof(u32) ||
+ sizeof(phys_addr_t) > sizeof(u32))
+ dev_warn(dev, "Physical address cast in 32bit to fit resource table format\n");
+
+ if (entry->va)
+ rsc->pa = (u32)rproc_va_to_pa(entry->va);
+ else
+ rsc->pa = (u32)entry->dma;
+
+ rsc->da = entry->da;
+ rsc->len = entry->len;
+ }
+ }
+
+ return 0;
+}
+
+/**
* rproc_coredump_cleanup() - clean up dump_segments list
* @rproc: the remote processor handle
*/
@@ -867,16 +1236,17 @@ static void rproc_coredump_cleanup(struct rproc *rproc)
*/
static void rproc_resource_cleanup(struct rproc *rproc)
{
+ struct rproc_debug_trace *trace, *trace_tmp;
struct rproc_mem_entry *entry, *tmp;
struct rproc_vdev *rvdev, *rvtmp;
struct device *dev = &rproc->dev;
/* clean up debugfs trace entries */
- list_for_each_entry_safe(entry, tmp, &rproc->traces, node) {
- rproc_remove_trace_file(entry->priv);
+ list_for_each_entry_safe(trace, trace_tmp, &rproc->traces, node) {
+ rproc_remove_trace_file(trace->tfile);
rproc->num_traces--;
- list_del(&entry->node);
- kfree(entry);
+ list_del(&trace->node);
+ kfree(trace);
}
/* clean up iommu mapping entries */
@@ -896,8 +1266,8 @@ static void rproc_resource_cleanup(struct rproc *rproc)
/* clean up carveout allocations */
list_for_each_entry_safe(entry, tmp, &rproc->carveouts, node) {
- dma_free_coherent(dev->parent, entry->len, entry->va,
- entry->dma);
+ if (entry->release)
+ entry->release(rproc, entry);
list_del(&entry->node);
kfree(entry);
}
@@ -987,7 +1357,11 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw)
if (ret)
return ret;
- dev_info(dev, "Booting fw image %s, size %zd\n", name, fw->size);
+ if (fw)
+ dev_info(dev, "Booting fw image %s, size %zd\n", name,
+ fw->size);
+ else
+ dev_info(dev, "Synchronizing with early booted co-processor\n");
/*
* if enabling an IOMMU isn't relevant for this rproc, this is
@@ -1009,6 +1383,9 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw)
/* reset max_notifyid */
rproc->max_notifyid = -1;
+ /* reset handled vdev */
+ rproc->nb_vdev = 0;
+
/* handle fw resources which are required to boot rproc */
ret = rproc_handle_resources(rproc, rproc_loading_handlers);
if (ret) {
@@ -1016,6 +1393,14 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw)
goto clean_up_resources;
}
+ /* Allocate carveout resources associated to rproc */
+ ret = rproc_alloc_registered_carveouts(rproc);
+ if (ret) {
+ dev_err(dev, "Failed to allocate associated carveouts: %d\n",
+ ret);
+ goto clean_up_resources;
+ }
+
ret = rproc_start(rproc, fw);
if (ret)
goto clean_up_resources;
@@ -1071,6 +1456,9 @@ static int rproc_stop(struct rproc *rproc, bool crashed)
struct device *dev = &rproc->dev;
int ret;
+ if (rproc->state == RPROC_OFFLINE)
+ return 0;
+
/* Stop any subdevices for the remote processor */
rproc_stop_subdevices(rproc, crashed);
@@ -1229,6 +1617,13 @@ int rproc_trigger_recovery(struct rproc *rproc)
/* generate coredump */
rproc_coredump(rproc);
+ if (!rproc->firmware) {
+ /* we don't know how to recover it, so try to shutdown it*/
+ mutex_unlock(&rproc->lock);
+ rproc_shutdown(rproc);
+ return 0;
+ }
+
/* load firmware */
ret = request_firmware(&firmware_p, rproc->firmware, dev);
if (ret < 0) {
@@ -1290,7 +1685,7 @@ static void rproc_crash_handler_work(struct work_struct *work)
*/
int rproc_boot(struct rproc *rproc)
{
- const struct firmware *firmware_p;
+ const struct firmware *firmware_p = NULL;
struct device *dev;
int ret;
@@ -1321,11 +1716,17 @@ int rproc_boot(struct rproc *rproc)
dev_info(dev, "powering up %s\n", rproc->name);
- /* load firmware */
- ret = request_firmware(&firmware_p, rproc->firmware, dev);
- if (ret < 0) {
- dev_err(dev, "request_firmware failed: %d\n", ret);
- goto downref_rproc;
+ if (!rproc->early_boot) {
+ /* load firmware */
+ ret = request_firmware(&firmware_p, rproc->firmware, dev);
+ if (ret < 0) {
+ dev_err(dev, "request_firmware failed: %d\n", ret);
+ goto downref_rproc;
+ }
+ } else {
+ /* set firmware name to null as unknown */
+ kfree(rproc->firmware);
+ rproc->firmware = NULL;
}
ret = rproc_fw_boot(rproc, firmware_p);
@@ -1479,8 +1880,22 @@ int rproc_add(struct rproc *rproc)
/* create debugfs entries */
rproc_create_debug_dir(rproc);
- /* if rproc is marked always-on, request it to boot */
- if (rproc->auto_boot) {
+ /* add resource manager device */
+ ret = devm_of_platform_populate(dev->parent);
+ if (ret < 0)
+ return ret;
+
+ if (rproc->early_boot) {
+ /*
+ * If rproc is marked already booted, no need to wait
+ * for firmware.
+ * Just handle associated resources and start sub devices
+ */
+ ret = rproc_boot(rproc);
+ if (ret < 0)
+ return ret;
+ } else if (rproc->auto_boot) {
+ /* if rproc is marked always-on, request it to boot */
ret = rproc_trigger_auto_boot(rproc);
if (ret < 0)
return ret;
@@ -1706,6 +2121,8 @@ int rproc_del(struct rproc *rproc)
list_del(&rproc->node);
mutex_unlock(&rproc_list_mutex);
+ of_platform_depopulate(rproc->dev.parent);
+
device_del(&rproc->dev);
return 0;
diff --git a/drivers/remoteproc/remoteproc_debugfs.c b/drivers/remoteproc/remoteproc_debugfs.c
index a5c29f2..11240b4 100644
--- a/drivers/remoteproc/remoteproc_debugfs.c
+++ b/drivers/remoteproc/remoteproc_debugfs.c
@@ -47,10 +47,23 @@ static struct dentry *rproc_dbg;
static ssize_t rproc_trace_read(struct file *filp, char __user *userbuf,
size_t count, loff_t *ppos)
{
- struct rproc_mem_entry *trace = filp->private_data;
- int len = strnlen(trace->va, trace->len);
+ struct rproc_debug_trace *data = filp->private_data;
+ struct rproc_mem_entry *trace = &data->trace_mem;
+ void *va;
+ char buf[100];
+ int len;
+
+ va = rproc_da_to_va(data->rproc, trace->da, trace->len);
+
+ if (!va) {
+ len = scnprintf(buf, sizeof(buf), "Trace %s not available\n",
+ trace->name);
+ va = buf;
+ } else {
+ len = strnlen(va, trace->len);
+ }
- return simple_read_from_buffer(userbuf, count, ppos, trace->va, len);
+ return simple_read_from_buffer(userbuf, count, ppos, va, len);
}
static const struct file_operations trace_rproc_ops = {
@@ -260,6 +273,7 @@ static int rproc_carveouts_show(struct seq_file *seq, void *p)
list_for_each_entry(carveout, &rproc->carveouts, node) {
seq_puts(seq, "Carveout memory entry:\n");
+ seq_printf(seq, "\tName: %s\n", carveout->name);
seq_printf(seq, "\tVirtual address: %pK\n", carveout->va);
seq_printf(seq, "\tDMA address: %pad\n", &carveout->dma);
seq_printf(seq, "\tDevice address: 0x%x\n", carveout->da);
@@ -287,7 +301,7 @@ void rproc_remove_trace_file(struct dentry *tfile)
}
struct dentry *rproc_create_trace_file(const char *name, struct rproc *rproc,
- struct rproc_mem_entry *trace)
+ struct rproc_debug_trace *trace)
{
struct dentry *tfile;
diff --git a/drivers/remoteproc/remoteproc_internal.h b/drivers/remoteproc/remoteproc_internal.h
index 7570beb..b130a3d 100644
--- a/drivers/remoteproc/remoteproc_internal.h
+++ b/drivers/remoteproc/remoteproc_internal.h
@@ -25,6 +25,13 @@
struct rproc;
+struct rproc_debug_trace {
+ struct rproc *rproc;
+ struct dentry *tfile;
+ struct list_head node;
+ struct rproc_mem_entry trace_mem;
+};
+
/* from remoteproc_core.c */
void rproc_release(struct kref *kref);
irqreturn_t rproc_vq_interrupt(struct rproc *rproc, int vq_id);
@@ -37,7 +44,7 @@ void rproc_remove_virtio_dev(struct rproc_vdev *rvdev);
/* from remoteproc_debugfs.c */
void rproc_remove_trace_file(struct dentry *tfile);
struct dentry *rproc_create_trace_file(const char *name, struct rproc *rproc,
- struct rproc_mem_entry *trace);
+ struct rproc_debug_trace *trace);
void rproc_delete_debug_dir(struct rproc *rproc);
void rproc_create_debug_dir(struct rproc *rproc);
void rproc_init_debugfs(void);
@@ -52,6 +59,7 @@ void rproc_free_vring(struct rproc_vring *rvring);
int rproc_alloc_vring(struct rproc_vdev *rvdev, int i);
void *rproc_da_to_va(struct rproc *rproc, u64 da, int len);
+phys_addr_t rproc_va_to_pa(void *cpu_addr);
int rproc_trigger_recovery(struct rproc *rproc);
int rproc_elf_sanity_check(struct rproc *rproc, const struct firmware *fw);
@@ -60,6 +68,8 @@ int rproc_elf_load_segments(struct rproc *rproc, const struct firmware *fw);
int rproc_elf_load_rsc_table(struct rproc *rproc, const struct firmware *fw);
struct resource_table *rproc_elf_find_loaded_rsc_table(struct rproc *rproc,
const struct firmware *fw);
+struct rproc_mem_entry *
+rproc_find_carveout_by_name(struct rproc *rproc, const char *name, ...);
static inline
int rproc_fw_sanity_check(struct rproc *rproc, const struct firmware *fw)
diff --git a/drivers/remoteproc/remoteproc_virtio.c b/drivers/remoteproc/remoteproc_virtio.c
index bbecd44..78462f5 100644
--- a/drivers/remoteproc/remoteproc_virtio.c
+++ b/drivers/remoteproc/remoteproc_virtio.c
@@ -17,7 +17,9 @@
* GNU General Public License for more details.
*/
+#include <linux/dma-mapping.h>
#include <linux/export.h>
+#include <linux/of_reserved_mem.h>
#include <linux/remoteproc.h>
#include <linux/virtio.h>
#include <linux/virtio_config.h>
@@ -76,7 +78,9 @@ static struct virtqueue *rp_find_vq(struct virtio_device *vdev,
struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
struct rproc *rproc = vdev_to_rproc(vdev);
struct device *dev = &rproc->dev;
+ struct rproc_mem_entry *mem;
struct rproc_vring *rvring;
+ struct fw_rsc_vdev *rsc;
struct virtqueue *vq;
void *addr;
int len, size;
@@ -88,8 +92,14 @@ static struct virtqueue *rp_find_vq(struct virtio_device *vdev,
if (!name)
return NULL;
+ /* Search allocated memory region by name */
+ mem = rproc_find_carveout_by_name(rproc, "vdev%dvring%d", rvdev->index,
+ id);
+ if (!mem || !mem->va)
+ return ERR_PTR(-ENOMEM);
+
rvring = &rvdev->vring[id];
- addr = rvring->va;
+ addr = mem->va;
len = rvring->len;
/* zero vring */
@@ -114,6 +124,10 @@ static struct virtqueue *rp_find_vq(struct virtio_device *vdev,
rvring->vq = vq;
vq->priv = rvring;
+ /* Update vring in resource table */
+ rsc = (void *)rproc->table_ptr + rvdev->rsc_offset;
+ rsc->vring[id].da = mem->da;
+
return vq;
}
@@ -303,10 +317,50 @@ static void rproc_virtio_dev_release(struct device *dev)
int rproc_add_virtio_dev(struct rproc_vdev *rvdev, int id)
{
struct rproc *rproc = rvdev->rproc;
- struct device *dev = &rproc->dev;
+ struct device *dev = &rvdev->dev;
struct virtio_device *vdev = &rvdev->vdev;
+ struct rproc_mem_entry *mem;
int ret;
+ /* Try to find dedicated vdev buffer carveout */
+ mem = rproc_find_carveout_by_name(rproc, "vdev%dbuffer", rvdev->index);
+ if (mem) {
+ phys_addr_t pa;
+
+ if (mem->of_resm_idx != -1) {
+ struct device_node *np = rproc->dev.parent->of_node;
+
+ /* Associate reserved memory to vdev device */
+ ret = of_reserved_mem_device_init_by_idx(dev, np,
+ mem->of_resm_idx);
+ if (ret) {
+ dev_err(dev, "Can't associate reserved memory\n");
+ goto out;
+ }
+ } else {
+ if (mem->va) {
+ dev_warn(dev, "vdev %d buffer already mapped\n",
+ rvdev->index);
+ pa = rproc_va_to_pa(mem->va);
+ } else {
+ /* Use dma address as carveout no memmapped yet */
+ pa = (phys_addr_t)mem->dma;
+ }
+
+ /* Associate vdev buffer memory pool to vdev subdev */
+ ret = dmam_declare_coherent_memory(dev, pa,
+ mem->da,
+ mem->len,
+ DMA_MEMORY_EXCLUSIVE);
+ if (ret < 0) {
+ dev_err(dev, "Failed to associate buffer\n");
+ goto out;
+ }
+ }
+ }
+
+ /* Reset vdev struct as you don't know how it has been previously allocated */
+ memset(vdev, 0, sizeof(struct virtio_device));
vdev->id.device = id,
vdev->config = &rproc_virtio_config_ops,
vdev->dev.parent = dev;
diff --git a/drivers/remoteproc/rproc_srm_core.c b/drivers/remoteproc/rproc_srm_core.c
new file mode 100644
index 0000000..fc61e8b
--- /dev/null
+++ b/drivers/remoteproc/rproc_srm_core.c
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Author: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
+ */
+
+#include <linux/component.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/remoteproc.h>
+#include <linux/rpmsg.h>
+
+#include "rproc_srm_core.h"
+
+#define BIND_TIMEOUT 10000
+
+struct rproc_srm_core {
+ struct device *dev;
+ struct completion all_bound;
+ int bind_status;
+ atomic_t prepared;
+ struct rproc_subdev subdev;
+ struct rpmsg_driver rpdrv;
+ struct blocking_notifier_head notifier;
+};
+
+#define to_rproc_srm_core(s) container_of(s, struct rproc_srm_core, subdev)
+
+static struct rproc_srm_core *rpmsg_srm_to_core(struct rpmsg_device *rpdev)
+{
+ struct rpmsg_driver *rpdrv;
+ struct rproc_srm_core *core;
+
+ rpdrv = container_of(rpdev->dev.driver, struct rpmsg_driver, drv);
+ core = container_of(rpdrv, struct rproc_srm_core, rpdrv);
+
+ return core;
+}
+
+int rpmsg_srm_send(struct rpmsg_endpoint *ept, struct rpmsg_srm_msg *msg)
+{
+ int ret;
+
+ ret = rpmsg_send(ept, (void *)msg, sizeof(*msg));
+ if (ret)
+ dev_err(&ept->rpdev->dev, "rpmsg_send failed: %d\n", ret);
+
+ return ret;
+}
+EXPORT_SYMBOL(rpmsg_srm_send);
+
+static int rpmsg_srm_cb(struct rpmsg_device *rpdev, void *data, int len,
+ void *priv, u32 src)
+{
+ struct rproc_srm_core *core = rpmsg_srm_to_core(rpdev);
+ struct rpmsg_srm_msg_desc desc;
+ int ret;
+
+ desc.ept = rpdev->ept;
+ desc.msg = data;
+
+ ret = blocking_notifier_call_chain(&core->notifier, 0, &desc);
+
+ if (!(ret & NOTIFY_STOP_MASK)) {
+ dev_warn(&rpdev->dev, "unknown device\n");
+ desc.msg->message_type = RPROC_SRM_MSG_ERROR;
+ rpmsg_srm_send(desc.ept, desc.msg);
+ }
+
+ return 0;
+}
+
+static int rpmsg_srm_probe(struct rpmsg_device *rpdev)
+{
+ int ret;
+
+ dev_dbg(&rpdev->dev, "%s\n", __func__);
+
+ /* Send an empty message to complete the initialization */
+ ret = rpmsg_send(rpdev->ept, NULL, 0);
+ if (ret)
+ dev_err(&rpdev->dev, "failed to send init message\n");
+
+ return ret;
+}
+
+static void rpmsg_srm_remove(struct rpmsg_device *rpdev)
+{
+ /* Note : the remove ops is mandatory */
+ dev_dbg(&rpdev->dev, "%s\n", __func__);
+}
+
+static struct rpmsg_device_id rpmsg_srm_id_table[] = {
+ { .name = "rproc-srm" },
+ { },
+};
+MODULE_DEVICE_TABLE(rpmsg, rpmsg_srm_id_table);
+
+static struct rpmsg_driver rpmsg_srm_drv = {
+ .drv.name = "rpmsg_srm",
+ .id_table = rpmsg_srm_id_table,
+ .probe = rpmsg_srm_probe,
+ .callback = rpmsg_srm_cb,
+ .remove = rpmsg_srm_remove,
+};
+
+int rproc_srm_core_register_notifier(struct rproc_srm_core *core,
+ struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&core->notifier, nb);
+}
+EXPORT_SYMBOL(rproc_srm_core_register_notifier);
+
+int rproc_srm_core_unregister_notifier(struct rproc_srm_core *core,
+ struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&core->notifier, nb);
+}
+EXPORT_SYMBOL(rproc_srm_core_unregister_notifier);
+
+static int compare_of(struct device *dev, void *data)
+{
+ return dev->of_node == data;
+}
+
+static void release_of(struct device *dev, void *data)
+{
+ of_node_put(data);
+}
+
+static void rproc_srm_core_unbind(struct device *dev)
+{
+ component_unbind_all(dev, NULL);
+}
+
+static int rproc_srm_core_bind(struct device *dev)
+{
+ struct rproc_srm_core *rproc_srm_core = dev_get_drvdata(dev);
+
+ rproc_srm_core->bind_status = component_bind_all(dev, NULL);
+ complete(&rproc_srm_core->all_bound);
+
+ return rproc_srm_core->bind_status;
+}
+
+static const struct component_master_ops srm_comp_ops = {
+ .bind = rproc_srm_core_bind,
+ .unbind = rproc_srm_core_unbind,
+};
+
+static int rproc_srm_core_prepare(struct rproc_subdev *subdev)
+{
+ struct rproc_srm_core *rproc_srm_core = to_rproc_srm_core(subdev);
+ struct device *dev = rproc_srm_core->dev;
+ struct device_node *node = dev->of_node;
+ struct device_node *child_np;
+ struct component_match *match = NULL;
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ init_completion(&rproc_srm_core->all_bound);
+
+ ret = devm_of_platform_populate(dev);
+ if (ret) {
+ dev_err(dev, "cannot populate node (%d)\n", ret);
+ return ret;
+ }
+
+ child_np = of_get_next_available_child(node, NULL);
+
+ while (child_np) {
+ of_node_get(child_np);
+ component_match_add_release(dev, &match, release_of, compare_of,
+ child_np);
+ child_np = of_get_next_available_child(node, child_np);
+ }
+
+ if (!match) {
+ dev_dbg(dev, "No available child\n");
+ goto done;
+ }
+
+ ret = component_master_add_with_match(dev, &srm_comp_ops, match);
+ if (ret)
+ goto depopulate;
+
+ /* Wait for every child to be bound */
+ if (!wait_for_completion_timeout(&rproc_srm_core->all_bound,
+ msecs_to_jiffies(BIND_TIMEOUT))) {
+ dev_err(dev, "failed to bind one or more system resource device(s)\n");
+ ret = -ETIMEDOUT;
+ goto master;
+ }
+
+ ret = rproc_srm_core->bind_status;
+ if (ret) {
+ dev_err(dev, "failed to bind\n");
+ goto master;
+ }
+
+ /* Register rpmsg driver for dynamic management */
+ rproc_srm_core->rpdrv = rpmsg_srm_drv;
+ ret = register_rpmsg_driver(&rproc_srm_core->rpdrv);
+ if (ret) {
+ dev_err(dev, "failed to register rpmsg drv\n");
+ goto master;
+ }
+
+done:
+ atomic_inc(&rproc_srm_core->prepared);
+
+ return 0;
+
+master:
+ component_master_del(dev, &srm_comp_ops);
+depopulate:
+ devm_of_platform_depopulate(dev);
+ return ret;
+}
+
+static void rproc_srm_core_unprepare(struct rproc_subdev *subdev)
+{
+ struct rproc_srm_core *rproc_srm_core = to_rproc_srm_core(subdev);
+ struct device *dev = rproc_srm_core->dev;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (!atomic_read(&rproc_srm_core->prepared))
+ return;
+
+ atomic_dec(&rproc_srm_core->prepared);
+
+ unregister_rpmsg_driver(&rproc_srm_core->rpdrv);
+
+ component_master_del(dev, &srm_comp_ops);
+ devm_of_platform_depopulate(dev);
+}
+
+static int rproc_srm_core_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rproc *rproc = dev_get_drvdata(dev->parent);
+ struct rproc_srm_core *rproc_srm_core;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ rproc_srm_core = devm_kzalloc(dev, sizeof(struct rproc_srm_core),
+ GFP_KERNEL);
+ if (!rproc_srm_core)
+ return -ENOMEM;
+
+ rproc_srm_core->dev = dev;
+ BLOCKING_INIT_NOTIFIER_HEAD(&rproc_srm_core->notifier);
+
+ /* Register rproc subdevice with (un)prepare ops */
+ rproc_srm_core->subdev.prepare = rproc_srm_core_prepare;
+ rproc_srm_core->subdev.unprepare = rproc_srm_core_unprepare;
+ rproc_add_subdev(rproc, &rproc_srm_core->subdev);
+
+ dev_set_drvdata(dev, rproc_srm_core);
+
+ return 0;
+}
+
+static int rproc_srm_core_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rproc_srm_core *rproc_srm_core = dev_get_drvdata(dev);
+ struct rproc *rproc = dev_get_drvdata(dev->parent);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (atomic_read(&rproc->power) > 0)
+ dev_warn(dev, "Releasing resources while firmware running!\n");
+
+ rproc_srm_core_unprepare(&rproc_srm_core->subdev);
+
+ return 0;
+}
+
+static const struct of_device_id rproc_srm_core_match[] = {
+ { .compatible = "rproc-srm-core", },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, rproc_srm_core_match);
+
+static struct platform_driver rproc_srm_core_driver = {
+ .probe = rproc_srm_core_probe,
+ .remove = rproc_srm_core_remove,
+ .driver = {
+ .name = "rproc-srm-core",
+ .of_match_table = of_match_ptr(rproc_srm_core_match),
+ },
+};
+
+module_platform_driver(rproc_srm_core_driver);
+
+MODULE_AUTHOR("Fabien Dessenne <fabien.dessenne@st.com>");
+MODULE_DESCRIPTION("Remoteproc System Resource Manager driver - core");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/remoteproc/rproc_srm_core.h b/drivers/remoteproc/rproc_srm_core.h
new file mode 100644
index 0000000..7dffdb38
--- /dev/null
+++ b/drivers/remoteproc/rproc_srm_core.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Author: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
+ */
+
+#ifndef _RPROC_SRM_CORE_H_
+#define _RPROC_SRM_CORE_H_
+
+/**
+ * Message type used in resource manager rpmsg:
+ * RPROC_SRM_MSG_GETCONFIG: Request to get the configuration of a resource
+ * RPROC_SRM_MSG_SETCONFIG: Request to set the configuration of a resource
+ * RPROC_SRM_MSG_ERROR: Error when processing a request
+ */
+#define RPROC_SRM_MSG_GETCONFIG 0x00
+#define RPROC_SRM_MSG_SETCONFIG 0x01
+#define RPROC_SRM_MSG_ERROR 0xFF
+
+/**
+ * Resource type used in resource manager rpmsg:
+ * RPROC_SRM_RSC_CLOCK: clock resource
+ * RPROC_SRM_RSC_REGU: regulator resource
+ */
+#define RPROC_SRM_RSC_CLOCK 0x00
+#define RPROC_SRM_RSC_REGU 0x01
+
+/**
+ * struct clock_cfg - clock configuration used in resource manager rpmsg
+ * @index: clock index
+ * @name: clock name
+ * @rate: clock rate request (in SetConfig message) or current status (in
+ * GetConfig message)
+ */
+struct clock_cfg {
+ u32 index;
+ u8 name[16];
+ u32 rate;
+};
+
+/**
+ * struct regu_cfg - regu configuration used in resource manager rpmsg
+ * @index: regulator index
+ * @name: regulator name
+ * @enable: regulator enable/disable request (in SetConfig message) or
+ * current status (in GetConfig message)
+ * @curr_voltage_mv: current regulator voltage in mV (meaningful in
+ * SetConfig message)
+ * @min_voltage_mv: regulator min voltage request in mV (meaningful in
+ * SetConfig message)
+ * @max_voltage_mv: regulator max voltage request in mV (meaningful in
+ * SetConfig message)
+ */
+struct regu_cfg {
+ u32 index;
+ u8 name[16];
+ u32 enable;
+ u32 curr_voltage_mv;
+ u32 min_voltage_mv;
+ u32 max_voltage_mv;
+};
+
+/**
+ * struct rpmsg_srm_msg - message structure used between processors to
+ * dynamically update resources configuration
+ * @message_type: type of the message: see RPROC_SRM_MSG*
+ * @device_id: an identifier specifying the device owning the resources.
+ * This is implementation dependent. As example it may be the
+ * device name or the device address.
+ * @rsc_type: the type of the resource for which the configuration applies:
+ * see RPROC_SRM_RSC*
+ * @clock_cfg: clock config - relevant if &rsc_type is RPROC_SRM_RSC_CLOCK
+ * @regu_cfg: regulator config - relevant if &rsc_type is RPROC_SRM_RSC_REGU
+ */
+struct rpmsg_srm_msg {
+ u32 message_type;
+ u8 device_id[32];
+ u32 rsc_type;
+ union {
+ struct clock_cfg clock_cfg;
+ struct regu_cfg regu_cfg;
+ };
+};
+
+struct rpmsg_srm_msg_desc {
+ struct rpmsg_endpoint *ept;
+ struct rpmsg_srm_msg *msg;
+};
+
+struct rproc_srm_core;
+
+int rproc_srm_core_register_notifier(struct rproc_srm_core *core,
+ struct notifier_block *nb);
+int rproc_srm_core_unregister_notifier(struct rproc_srm_core *core,
+ struct notifier_block *nb);
+int rpmsg_srm_send(struct rpmsg_endpoint *ept, struct rpmsg_srm_msg *msg);
+
+#endif
diff --git a/drivers/remoteproc/rproc_srm_dev.c b/drivers/remoteproc/rproc_srm_dev.c
new file mode 100644
index 0000000..6b164da
--- /dev/null
+++ b/drivers/remoteproc/rproc_srm_dev.c
@@ -0,0 +1,744 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Author: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/component.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/remoteproc.h>
+
+#include "rproc_srm_core.h"
+
+struct rproc_srm_clk_info {
+ struct list_head list;
+ unsigned int index;
+ struct clk *clk;
+ const char *name;
+ bool parent_enabled;
+};
+
+struct rproc_srm_regu_info {
+ struct list_head list;
+ unsigned int index;
+ struct regulator *regu;
+ const char *name;
+ bool enabled;
+};
+
+struct rproc_srm_irq_info {
+ struct list_head list;
+ unsigned int index;
+ char *name;
+ int irq;
+ bool enabled;
+};
+
+struct rproc_srm_dev {
+ struct device *dev;
+ struct rproc_srm_core *core;
+ struct notifier_block nb;
+ bool early_boot;
+
+ struct list_head clk_list_head;
+ struct list_head regu_list_head;
+ struct list_head irq_list_head;
+};
+
+/* Irqs */
+static void rproc_srm_dev_irqs_put(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct rproc_srm_irq_info *i, *tmp;
+
+ list_for_each_entry_safe(i, tmp, &rproc_srm_dev->irq_list_head, list) {
+ devm_free_irq(dev, i->irq, NULL);
+ dev_dbg(dev, "Put irq %d (%s)\n", i->irq, i->name);
+ list_del(&i->list);
+ }
+}
+
+static irqreturn_t rproc_srm_dev_irq_handler(int irq, void *dev)
+{
+ dev_warn(dev, "Spurious interrupt\n");
+ return IRQ_HANDLED;
+}
+
+static int rproc_srm_dev_irqs_get(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct device_node *np = dev->of_node;
+ struct rproc_srm_irq_info *info;
+ const char *name;
+ int nr, ret, irq;
+ unsigned int i;
+
+ if (!np)
+ return 0;
+
+ nr = platform_irq_count(pdev);
+ if (!nr)
+ return 0;
+
+ if (rproc_srm_dev->early_boot)
+ /*
+ * Do not overwrite the irq configuration.
+ * No need to parse irq from DT since the resource manager does
+ * not offer any service to update the irq config.
+ */
+ return 0;
+
+ for (i = 0; i < nr; i++) {
+ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ irq = platform_get_irq(pdev, i);
+ if (irq <= 0) {
+ ret = irq;
+ dev_err(dev, "Failed to get irq (%d)\n", ret);
+ goto err;
+ }
+
+ info->irq = irq;
+
+ /* Register a dummy irq handleras not used by Linux */
+ ret = devm_request_irq(dev, info->irq,
+ rproc_srm_dev_irq_handler, 0,
+ dev_name(dev), NULL);
+ if (ret) {
+ dev_err(dev, "Failed to request irq (%d)\n", ret);
+ goto err;
+ }
+
+ /*
+ * Disable IRQ. Since it is used by the remote processor we
+ * must not use the 'irq lazy disable' optimization
+ */
+ irq_set_status_flags(info->irq, IRQ_DISABLE_UNLAZY);
+ disable_irq(info->irq);
+
+ /* Note: "interrupt-names" is optional */
+ if (!of_property_read_string_index(np, "interrupt-names", i,
+ &name))
+ info->name = devm_kstrdup(dev, name, GFP_KERNEL);
+ else
+ info->name = devm_kstrdup(dev, "", GFP_KERNEL);
+
+ info->index = i;
+
+ list_add_tail(&info->list, &rproc_srm_dev->irq_list_head);
+ dev_dbg(dev, "Got irq %d (%s)\n", info->irq, info->name);
+ }
+
+ return 0;
+
+err:
+ rproc_srm_dev_irqs_put(rproc_srm_dev);
+
+ return ret;
+}
+
+/* Clocks */
+static void rproc_srm_dev_clocks_unsetup(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct rproc_srm_clk_info *c;
+
+ list_for_each_entry(c, &rproc_srm_dev->clk_list_head, list) {
+ if (!c->parent_enabled)
+ continue;
+
+ clk_disable_unprepare(clk_get_parent(c->clk));
+ c->parent_enabled = false;
+ dev_dbg(rproc_srm_dev->dev, "clk %d (%s) unsetup\n",
+ c->index, c->name);
+ }
+}
+
+static int rproc_srm_dev_clocks_setup(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct rproc_srm_clk_info *c;
+ int ret;
+
+ /*
+ * Prepare and enable the parent clocks.
+ * Since the clock tree is under the exclusive control of the master
+ * processor, we need to configure the clock tree of the targeted clock.
+ * We do not want to enable the clock itself, which is under the
+ * responsibility of the remote processor.
+ * Hence we prepare and enable the parent clock.
+ */
+
+ list_for_each_entry(c, &rproc_srm_dev->clk_list_head, list) {
+ if (c->parent_enabled)
+ continue;
+
+ ret = clk_prepare_enable(clk_get_parent(c->clk));
+ if (ret) {
+ dev_err(rproc_srm_dev->dev,
+ "clk %d (%s) parent enable failed\n",
+ c->index, c->name);
+ rproc_srm_dev_clocks_unsetup(rproc_srm_dev);
+ return ret;
+ }
+ c->parent_enabled = true;
+ dev_dbg(rproc_srm_dev->dev, "clk %d (%s) parent enabled\n",
+ c->index, c->name);
+ }
+
+ return 0;
+}
+
+static struct rproc_srm_clk_info
+ *rproc_srm_dev_clock_find(struct rproc_srm_dev *rproc_srm_dev,
+ struct clock_cfg *cfg)
+{
+ struct rproc_srm_clk_info *ci;
+
+ /* Search by index (if valid value) otherwise search by name */
+ list_for_each_entry(ci, &rproc_srm_dev->clk_list_head, list) {
+ if (cfg->index != U32_MAX) {
+ if (ci->index == cfg->index)
+ return ci;
+ } else {
+ if (!strcmp(ci->name, cfg->name))
+ return ci;
+ }
+ }
+
+ return NULL;
+}
+
+static int rproc_srm_dev_clock_set_cfg(struct rproc_srm_dev *rproc_srm_dev,
+ struct clock_cfg *cfg)
+{
+ struct rproc_srm_clk_info *c;
+ struct device *dev = rproc_srm_dev->dev;
+ int ret;
+
+ c = rproc_srm_dev_clock_find(rproc_srm_dev, cfg);
+
+ if (!c) {
+ dev_err(dev, "unknown clock (id %d)\n", cfg->index);
+ return -EINVAL;
+ }
+
+ if (cfg->rate && clk_get_rate(c->clk) != cfg->rate) {
+ ret = clk_set_rate(c->clk, cfg->rate);
+ if (ret) {
+ dev_err(dev, "clk set rate failed\n");
+ return ret;
+ }
+
+ dev_dbg(dev, "clk %d (%s) rate = %d\n", c->index, c->name,
+ cfg->rate);
+ }
+
+ return 0;
+}
+
+static int rproc_srm_dev_clock_get_cfg(struct rproc_srm_dev *rproc_srm_dev,
+ struct clock_cfg *cfg)
+{
+ struct rproc_srm_clk_info *c;
+
+ c = rproc_srm_dev_clock_find(rproc_srm_dev, cfg);
+ if (!c) {
+ dev_err(rproc_srm_dev->dev, "unknown clock (%d)\n", cfg->index);
+ return -EINVAL;
+ }
+
+ strlcpy(cfg->name, c->name, sizeof(cfg->name));
+ cfg->index = c->index;
+ cfg->rate = (u32)clk_get_rate(c->clk);
+
+ return 0;
+}
+
+static void rproc_srm_dev_clocks_put(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct rproc_srm_clk_info *c, *tmp;
+
+ list_for_each_entry_safe(c, tmp, &rproc_srm_dev->clk_list_head, list) {
+ clk_put(c->clk);
+ dev_dbg(dev, "put clock %d (%s)\n", c->index, c->name);
+ list_del(&c->list);
+ }
+}
+
+static int rproc_srm_dev_clocks_get(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct device_node *np = dev->of_node;
+ struct rproc_srm_clk_info *c;
+ const char *name;
+ int nb_c, ret;
+ unsigned int i;
+
+ if (!np)
+ return 0;
+
+ nb_c = of_clk_get_parent_count(np);
+ if (!nb_c)
+ return 0;
+
+ for (i = 0; i < nb_c; i++) {
+ c = devm_kzalloc(dev, sizeof(*c), GFP_KERNEL);
+ if (!c) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ c->clk = of_clk_get(np, i);
+ if (IS_ERR(c->clk)) {
+ dev_err(dev, "clock %d KO (%ld)\n", i,
+ PTR_ERR(c->clk));
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ /* Note: "clock-names" is optional */
+ if (!of_property_read_string_index(np, "clock-names", i,
+ &name))
+ c->name = devm_kstrdup(dev, name, GFP_KERNEL);
+ else
+ c->name = devm_kstrdup(dev, "", GFP_KERNEL);
+
+ c->index = i;
+
+ list_add_tail(&c->list, &rproc_srm_dev->clk_list_head);
+ dev_dbg(dev, "got clock %d (%s)\n", c->index, c->name);
+ }
+
+ return 0;
+
+err:
+ rproc_srm_dev_clocks_put(rproc_srm_dev);
+ return ret;
+}
+
+/* Regulators */
+static void rproc_srm_dev_regus_unsetup(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct rproc_srm_regu_info *r;
+ struct device *dev = rproc_srm_dev->dev;
+
+ list_for_each_entry(r, &rproc_srm_dev->regu_list_head, list) {
+ if (!r->enabled)
+ continue;
+
+ if (regulator_disable(r->regu)) {
+ dev_warn(dev, "regu %d disabled failed\n", r->index);
+ continue;
+ }
+
+ r->enabled = false;
+ dev_dbg(dev, "regu %d (%s) disabled\n", r->index, r->name);
+ }
+}
+
+static int rproc_srm_dev_regus_setup(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct rproc_srm_regu_info *r;
+ int ret;
+
+ /* Enable all the regulators */
+ list_for_each_entry(r, &rproc_srm_dev->regu_list_head, list) {
+ if (r->enabled)
+ continue;
+
+ /* in early_boot mode sync on hw */
+ if (rproc_srm_dev->early_boot && !regulator_is_enabled(r->regu))
+ continue;
+
+ ret = regulator_enable(r->regu);
+ if (ret) {
+ dev_err(rproc_srm_dev->dev, "regu %d (%s) failed\n",
+ r->index, r->name);
+ rproc_srm_dev_regus_unsetup(rproc_srm_dev);
+ return ret;
+ }
+ r->enabled = true;
+ dev_dbg(rproc_srm_dev->dev, "regu %d (%s) enabled\n",
+ r->index, r->name);
+ }
+
+ return 0;
+}
+
+static struct rproc_srm_regu_info
+ *rproc_srm_dev_regu_find(struct rproc_srm_dev *rproc_srm_dev,
+ struct regu_cfg *cfg)
+{
+ struct rproc_srm_regu_info *ri;
+
+ list_for_each_entry(ri, &rproc_srm_dev->regu_list_head, list) {
+ if (cfg->index != U32_MAX) {
+ if (ri->index == cfg->index)
+ return ri;
+ } else {
+ if (!strcmp(ri->name, cfg->name))
+ return ri;
+ }
+ }
+
+ return NULL;
+}
+
+static int rproc_srm_dev_regu_set_cfg(struct rproc_srm_dev *rproc_srm_dev,
+ struct regu_cfg *cfg)
+{
+ struct rproc_srm_regu_info *r;
+ struct device *dev = rproc_srm_dev->dev;
+ int ret;
+
+ r = rproc_srm_dev_regu_find(rproc_srm_dev, cfg);
+ if (!r) {
+ dev_err(dev, "unknown regu (%d)\n", cfg->index);
+ return -EINVAL;
+ }
+
+ if (!r->enabled && cfg->enable) {
+ ret = regulator_enable(r->regu);
+ if (ret) {
+ dev_err(dev, "regu %d enable failed\n", r->index);
+ return ret;
+ }
+ r->enabled = true;
+ dev_dbg(dev, "regu %d (%s) enabled\n", r->index, r->name);
+ } else if (r->enabled && !cfg->enable) {
+ ret = regulator_disable(r->regu);
+ if (ret) {
+ dev_err(dev, "regu %d disable failed\n", r->index);
+ return ret;
+ }
+ r->enabled = false;
+ dev_dbg(dev, "regu %d (%s) disabled\n", r->index, r->name);
+ }
+
+ if (cfg->min_voltage_mv || cfg->max_voltage_mv) {
+ ret = regulator_set_voltage(r->regu, cfg->min_voltage_mv * 1000,
+ cfg->max_voltage_mv * 1000);
+ if (ret) {
+ dev_err(dev, "regu %d set voltage failed\n", r->index);
+ return ret;
+ }
+
+ dev_dbg(dev, "regu %d (%s) voltage = [%d - %d] mv\n", r->index,
+ r->name, cfg->min_voltage_mv, cfg->max_voltage_mv);
+ }
+
+ return 0;
+}
+
+static int rproc_srm_dev_regu_get_cfg(struct rproc_srm_dev *rproc_srm_dev,
+ struct regu_cfg *cfg)
+{
+ struct rproc_srm_regu_info *r;
+ struct device *dev = rproc_srm_dev->dev;
+ int v;
+
+ r = rproc_srm_dev_regu_find(rproc_srm_dev, cfg);
+ if (!r) {
+ dev_err(dev, "unknown regu (%d)\n", cfg->index);
+ return -EINVAL;
+ }
+
+ strlcpy(cfg->name, r->name, sizeof(cfg->name));
+ cfg->index = r->index;
+ cfg->enable = r->enabled;
+ cfg->min_voltage_mv = 0;
+ cfg->max_voltage_mv = 0;
+
+ v = regulator_get_voltage(r->regu);
+ if (v < 0) {
+ dev_warn(dev, "cannot get %s voltage\n", r->name);
+ cfg->curr_voltage_mv = 0;
+ } else {
+ cfg->curr_voltage_mv = v / 1000;
+ }
+
+ return 0;
+}
+
+static void rproc_srm_dev_regus_put(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct rproc_srm_regu_info *r, *tmp;
+
+ list_for_each_entry_safe(r, tmp, &rproc_srm_dev->regu_list_head, list) {
+ devm_regulator_put(r->regu);
+ dev_dbg(dev, "put regu %d (%s)\n", r->index, r->name);
+ list_del(&r->list);
+ }
+}
+
+static int rproc_srm_dev_regus_get(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct device_node *np = dev->of_node;
+ struct property *p;
+ const char *n;
+ char *name;
+ struct rproc_srm_regu_info *r;
+ int ret, nb_s = 0;
+
+ if (!np)
+ return 0;
+
+ for_each_property_of_node(np, p) {
+ n = strstr(p->name, "-supply");
+ if (!n || n == p->name)
+ continue;
+
+ r = devm_kzalloc(dev, sizeof(*r), GFP_KERNEL);
+ if (!r) {
+ ret = -ENOMEM;
+ goto err_list;
+ }
+
+ name = devm_kstrdup(dev, p->name, GFP_KERNEL);
+ name[strlen(p->name) - strlen("-supply")] = '\0';
+ r->name = name;
+
+ r->regu = devm_regulator_get(dev, r->name);
+ if (IS_ERR(r->regu)) {
+ dev_err(dev, "cannot get regu %s\n", r->name);
+ ret = -EINVAL;
+ goto err_list;
+ }
+
+ r->index = nb_s++;
+
+ list_add_tail(&r->list, &rproc_srm_dev->regu_list_head);
+ dev_dbg(dev, "got regu %d (%s)\n", r->index, r->name);
+ }
+
+ return 0;
+
+err_list:
+ rproc_srm_dev_regus_put(rproc_srm_dev);
+ return ret;
+}
+
+/* Core */
+static int rproc_srm_dev_notify_cb(struct notifier_block *nb, unsigned long evt,
+ void *data)
+{
+ struct rproc_srm_dev *rproc_srm_dev =
+ container_of(nb, struct rproc_srm_dev, nb);
+ struct device *dev = rproc_srm_dev->dev;
+ struct rpmsg_srm_msg_desc *desc;
+ struct rpmsg_srm_msg *i, o;
+ int ret = 0;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ desc = (struct rpmsg_srm_msg_desc *)data;
+ i = desc->msg;
+ o = *i;
+
+ /* Check if 'device_id' (name / addr ) matches this device */
+ if (!strstr(dev_name(dev), i->device_id))
+ return NOTIFY_DONE;
+
+ switch (i->message_type) {
+ case RPROC_SRM_MSG_SETCONFIG:
+ switch (i->rsc_type) {
+ case RPROC_SRM_RSC_CLOCK:
+ ret = rproc_srm_dev_clock_set_cfg(rproc_srm_dev,
+ &i->clock_cfg);
+ if (!ret)
+ ret = rproc_srm_dev_clock_get_cfg(rproc_srm_dev,
+ &o.clock_cfg);
+ break;
+ case RPROC_SRM_RSC_REGU:
+ ret = rproc_srm_dev_regu_set_cfg(rproc_srm_dev,
+ &i->regu_cfg);
+ if (!ret)
+ ret = rproc_srm_dev_regu_get_cfg(rproc_srm_dev,
+ &o.regu_cfg);
+ break;
+ default:
+ dev_warn(dev, "bad rsc type (%d)\n", i->rsc_type);
+ ret = -EINVAL;
+ break;
+ }
+ break;
+ case RPROC_SRM_MSG_GETCONFIG:
+ switch (i->rsc_type) {
+ case RPROC_SRM_RSC_CLOCK:
+ ret = rproc_srm_dev_clock_get_cfg(rproc_srm_dev,
+ &o.clock_cfg);
+ break;
+ case RPROC_SRM_RSC_REGU:
+ ret = rproc_srm_dev_regu_get_cfg(rproc_srm_dev,
+ &o.regu_cfg);
+ break;
+ default:
+ dev_warn(dev, "bad rsc type (%d)\n", i->rsc_type);
+ ret = -EINVAL;
+ break;
+ }
+ break;
+ default:
+ dev_warn(dev, "bad msg type (%d)\n", i->message_type);
+ ret = -EINVAL;
+ break;
+ }
+
+ /* Send return msg */
+ if (ret)
+ o.message_type = RPROC_SRM_MSG_ERROR;
+
+ ret = rpmsg_srm_send(desc->ept, &o);
+
+ return ret ? NOTIFY_BAD : NOTIFY_STOP;
+}
+
+static void
+rproc_srm_dev_unbind(struct device *dev, struct device *master, void *data)
+{
+ struct rproc_srm_dev *rproc_srm_dev = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ rproc_srm_dev_regus_unsetup(rproc_srm_dev);
+ rproc_srm_dev_clocks_unsetup(rproc_srm_dev);
+
+ /* For IRQs: nothing to unsetup */
+}
+
+static int
+rproc_srm_dev_bind(struct device *dev, struct device *master, void *data)
+{
+ struct rproc_srm_dev *rproc_srm_dev = dev_get_drvdata(dev);
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ ret = rproc_srm_dev_clocks_setup(rproc_srm_dev);
+ if (ret)
+ return ret;
+
+ ret = rproc_srm_dev_regus_setup(rproc_srm_dev);
+ if (ret)
+ return ret;
+
+ /* For IRQs: nothing to setup */
+ return 0;
+}
+
+static const struct component_ops rproc_srm_dev_ops = {
+ .bind = rproc_srm_dev_bind,
+ .unbind = rproc_srm_dev_unbind,
+};
+
+static int rproc_srm_dev_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rproc_srm_dev *rproc_srm_dev;
+ struct rproc *rproc;
+ int ret;
+
+ dev_dbg(dev, "%s for node %s\n", __func__, dev->of_node->name);
+
+ rproc_srm_dev = devm_kzalloc(dev, sizeof(struct rproc_srm_dev),
+ GFP_KERNEL);
+ if (!rproc_srm_dev)
+ return -ENOMEM;
+
+ rproc_srm_dev->dev = dev;
+ rproc = (struct rproc *)dev_get_drvdata(dev->parent->parent);
+ rproc_srm_dev->early_boot = rproc->early_boot;
+ rproc_srm_dev->core = dev_get_drvdata(dev->parent);
+
+ INIT_LIST_HEAD(&rproc_srm_dev->clk_list_head);
+ INIT_LIST_HEAD(&rproc_srm_dev->regu_list_head);
+ INIT_LIST_HEAD(&rproc_srm_dev->irq_list_head);
+
+ /* Get clocks, regu and irqs */
+ ret = rproc_srm_dev_clocks_get(rproc_srm_dev);
+ if (ret)
+ return ret;
+
+ ret = rproc_srm_dev_regus_get(rproc_srm_dev);
+ if (ret)
+ goto err_get;
+
+ ret = rproc_srm_dev_irqs_get(rproc_srm_dev);
+ if (ret)
+ goto err_get;
+
+ rproc_srm_dev->nb.notifier_call = rproc_srm_dev_notify_cb;
+ ret = rproc_srm_core_register_notifier(rproc_srm_dev->core,
+ &rproc_srm_dev->nb);
+ if (ret)
+ goto err_register;
+
+ dev_set_drvdata(dev, rproc_srm_dev);
+
+ return component_add(dev, &rproc_srm_dev_ops);
+
+err_register:
+ rproc_srm_core_unregister_notifier(rproc_srm_dev->core,
+ &rproc_srm_dev->nb);
+err_get:
+ rproc_srm_dev_irqs_put(rproc_srm_dev);
+ rproc_srm_dev_regus_put(rproc_srm_dev);
+ rproc_srm_dev_clocks_put(rproc_srm_dev);
+ return ret;
+}
+
+static int rproc_srm_dev_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rproc_srm_dev *rproc_srm_dev = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ component_del(dev, &rproc_srm_dev_ops);
+
+ rproc_srm_core_unregister_notifier(rproc_srm_dev->core,
+ &rproc_srm_dev->nb);
+
+ rproc_srm_dev_irqs_put(rproc_srm_dev);
+ rproc_srm_dev_regus_put(rproc_srm_dev);
+ rproc_srm_dev_clocks_put(rproc_srm_dev);
+
+ return 0;
+}
+
+static const struct of_device_id rproc_srm_dev_match[] = {
+ { .compatible = "rproc-srm-dev", },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, rproc_srm_dev_match);
+
+static struct platform_driver rproc_srm_dev_driver = {
+ .probe = rproc_srm_dev_probe,
+ .remove = rproc_srm_dev_remove,
+ .driver = {
+ .name = "rproc-srm-dev",
+ .of_match_table = of_match_ptr(rproc_srm_dev_match),
+ },
+};
+
+module_platform_driver(rproc_srm_dev_driver);
+
+MODULE_AUTHOR("Fabien Dessenne <fabien.dessenne@st.com>");
+MODULE_DESCRIPTION("Remoteproc System Resource Manager driver - dev");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/remoteproc/stm32_rproc.c b/drivers/remoteproc/stm32_rproc.c
new file mode 100644
index 0000000..21a569a
--- /dev/null
+++ b/drivers/remoteproc/stm32_rproc.c
@@ -0,0 +1,858 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Authors: Ludovic Barre <ludovic.barre@st.com> for STMicroelectronics.
+ * Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mailbox_client.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/pm_wakeirq.h>
+#include <linux/regmap.h>
+#include <linux/remoteproc.h>
+#include <linux/reset.h>
+#include <linux/workqueue.h>
+
+#include "remoteproc_internal.h"
+
+#define HOLD_BOOT 0
+#define RELEASE_BOOT 1
+
+#define MBOX_NB_VQ 2
+#define MBOX_NB_MBX 3
+
+#define STM32_SMC_RCC 0x82001000
+#define STM32_SMC_REG_WRITE 0x1
+
+#define STM32_MBX_VQ0 "vq0"
+#define STM32_MBX_VQ0_ID 0
+#define STM32_MBX_VQ1 "vq1"
+#define STM32_MBX_VQ1_ID 1
+#define STM32_MBX_SHUTDOWN "shutdown"
+
+#define RSC_TBL_SIZE (1024)
+
+struct stm32_syscon {
+ struct regmap *map;
+ u32 reg;
+ u32 mask;
+};
+
+struct stm32_rproc_mem {
+ char name[20];
+ void __iomem *cpu_addr;
+ phys_addr_t bus_addr;
+ u32 dev_addr;
+ size_t size;
+};
+
+struct stm32_rproc_mem_ranges {
+ u32 dev_addr;
+ u32 bus_addr;
+ u32 size;
+};
+
+struct stm32_mbox {
+ const unsigned char name[10];
+ struct mbox_chan *chan;
+ struct mbox_client client;
+ struct work_struct vq_work;
+ int vq_id;
+};
+
+struct stm32_rproc {
+ struct reset_control *rst;
+ struct stm32_syscon hold_boot;
+ struct stm32_syscon pdds;
+ int wdg_irq;
+ u32 nb_rmems;
+ struct stm32_rproc_mem *rmems;
+ struct stm32_mbox mb[MBOX_NB_MBX];
+ struct workqueue_struct *workqueue;
+ bool secured_soc;
+ void __iomem *rsc_va;
+};
+
+static int stm32_rproc_pa_to_da(struct rproc *rproc, phys_addr_t pa, u64 *da)
+{
+ unsigned int i;
+ struct stm32_rproc *ddata = rproc->priv;
+ struct stm32_rproc_mem *p_mem;
+
+ for (i = 0; i < ddata->nb_rmems; i++) {
+ p_mem = &ddata->rmems[i];
+
+ if (pa < p_mem->bus_addr ||
+ pa >= p_mem->bus_addr + p_mem->size)
+ continue;
+ *da = pa - p_mem->bus_addr + p_mem->dev_addr;
+ dev_dbg(rproc->dev.parent, "pa %#x to da %llx\n", pa, *da);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int stm32_rproc_da_to_pa(struct rproc *rproc, u64 da, phys_addr_t *pa)
+{
+ unsigned int i;
+ struct stm32_rproc *ddata = rproc->priv;
+ struct stm32_rproc_mem *p_mem;
+
+ for (i = 0; i < ddata->nb_rmems; i++) {
+ p_mem = &ddata->rmems[i];
+
+ if (da < p_mem->dev_addr ||
+ da >= p_mem->dev_addr + p_mem->size)
+ continue;
+ *pa = da - p_mem->dev_addr + p_mem->bus_addr;
+ dev_dbg(rproc->dev.parent, "da %llx to pa %#x\n", da, *pa);
+ return 0;
+ }
+
+ dev_err(rproc->dev.parent, "can't translate da %llx\n", da);
+
+ return -EINVAL;
+}
+
+static int stm32_rproc_mem_alloc(struct rproc *rproc,
+ struct rproc_mem_entry *mem)
+{
+ struct device *dev = rproc->dev.parent;
+ void *va;
+
+ dev_dbg(dev, "map memory: %pa+%zx\n", &mem->dma, mem->len);
+ va = ioremap_wc(mem->dma, mem->len);
+ if (IS_ERR_OR_NULL(va)) {
+ dev_err(dev, "Unable to map memory region: %pa+%zx\n",
+ &mem->dma, mem->len);
+ return -ENOMEM;
+ }
+
+ /* Update memory entry va */
+ mem->va = va;
+
+ return 0;
+}
+
+static int stm32_rproc_mem_release(struct rproc *rproc,
+ struct rproc_mem_entry *mem)
+{
+ dev_dbg(rproc->dev.parent, "unmap memory: %pa\n", &mem->dma);
+ iounmap(mem->va);
+
+ return 0;
+}
+
+static int stm32_rproc_elf_load_segments(struct rproc *rproc,
+ const struct firmware *fw)
+{
+ if (!rproc->early_boot)
+ return rproc_elf_load_segments(rproc, fw);
+
+ return 0;
+}
+
+static int stm32_rproc_of_memory_translations(struct rproc *rproc)
+{
+ struct device *dev = rproc->dev.parent;
+ struct stm32_rproc *ddata = rproc->priv;
+ struct device_node *np = dev->of_node;
+ struct stm32_rproc_mem *p_mems;
+ struct stm32_rproc_mem_ranges *mem_range;
+ int cnt, array_size, i, ret = 0;
+
+ cnt = of_property_count_elems_of_size(np, "ranges",
+ sizeof(*mem_range));
+ if (cnt <= 0) {
+ dev_err(dev, "%s: ranges property not defined\n", __func__);
+ return -EINVAL;
+ }
+
+ p_mems = devm_kcalloc(dev, cnt, sizeof(*p_mems), GFP_KERNEL);
+ if (!p_mems)
+ return -ENOMEM;
+ mem_range = kcalloc(cnt, sizeof(*mem_range), GFP_KERNEL);
+ if (!mem_range)
+ return -ENOMEM;
+
+ array_size = cnt * sizeof(struct stm32_rproc_mem_ranges) / sizeof(u32);
+
+ ret = of_property_read_u32_array(np, "ranges",
+ (u32 *)mem_range, array_size);
+ if (ret) {
+ dev_err(dev, "error while get ranges property: %x\n", ret);
+ goto free_mem;
+ }
+
+ for (i = 0; i < cnt; i++) {
+ p_mems[i].bus_addr = mem_range[i].bus_addr;
+ p_mems[i].dev_addr = mem_range[i].dev_addr;
+ p_mems[i].size = mem_range[i].size;
+
+ dev_dbg(dev, "memory range[%i]: da %#x, pa %#x, size %#x:\n",
+ i, p_mems[i].dev_addr, p_mems[i].bus_addr,
+ p_mems[i].size);
+ }
+
+ ddata->rmems = p_mems;
+ ddata->nb_rmems = cnt;
+
+free_mem:
+ kfree(mem_range);
+ return ret;
+}
+
+static int stm32_rproc_mbox_idx(struct rproc *rproc, const unsigned char *name)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ddata->mb); i++) {
+ if (!strncmp(ddata->mb[i].name, name, strlen(name)))
+ return i;
+ }
+ dev_err(&rproc->dev, "mailbox %s not found\n", name);
+
+ return -EINVAL;
+}
+
+static int stm32_rproc_elf_load_rsc_table(struct rproc *rproc,
+ const struct firmware *fw)
+{
+ int status;
+ struct resource_table *table = NULL;
+ struct stm32_rproc *ddata = rproc->priv;
+
+ if (!rproc->early_boot) {
+ status = rproc_elf_load_rsc_table(rproc, fw);
+ if (status)
+ goto no_rsc_table;
+
+ return 0;
+ }
+
+ if (ddata->rsc_va) {
+ table = (struct resource_table *)ddata->rsc_va;
+ /* Assuming that the resource table fits in 1kB is fair */
+ rproc->cached_table = kmemdup(table, RSC_TBL_SIZE, GFP_KERNEL);
+ if (!rproc->cached_table)
+ return -ENOMEM;
+
+ rproc->table_ptr = rproc->cached_table;
+ rproc->table_sz = RSC_TBL_SIZE;
+ return 0;
+ }
+
+ rproc->cached_table = NULL;
+ rproc->table_ptr = NULL;
+ rproc->table_sz = 0;
+
+no_rsc_table:
+ dev_warn(&rproc->dev, "no resource table found for this firmware\n");
+ return 0;
+}
+
+static int stm32_rproc_parse_fw(struct rproc *rproc, const struct firmware *fw)
+{
+ struct device *dev = rproc->dev.parent;
+ struct device_node *np = dev->of_node;
+ struct of_phandle_iterator it;
+ struct rproc_mem_entry *mem;
+ struct reserved_mem *rmem;
+ u64 da;
+ int index = 0;
+
+ /* Register associated reserved memory regions */
+ of_phandle_iterator_init(&it, np, "memory-region", NULL, 0);
+ while (of_phandle_iterator_next(&it) == 0) {
+ rmem = of_reserved_mem_lookup(it.node);
+ if (!rmem) {
+ dev_err(dev, "unable to acquire memory-region\n");
+ return -EINVAL;
+ }
+
+ if (stm32_rproc_pa_to_da(rproc, rmem->base, &da) < 0) {
+ dev_err(dev, "memory region not valid %pa\n",
+ &rmem->base);
+ return -EINVAL;
+ }
+
+ /* No need to map vdev buffer */
+ if (strcmp(it.node->name, "vdev0buffer")) {
+ /* Register memory region */
+ mem = rproc_mem_entry_init(dev, NULL,
+ (dma_addr_t)rmem->base,
+ rmem->size, da,
+ stm32_rproc_mem_alloc,
+ stm32_rproc_mem_release,
+ it.node->name);
+
+ if (mem)
+ rproc_coredump_add_segment(rproc, da,
+ rmem->size);
+ } else {
+ /* Register reserved memory for vdev buffer alloc */
+ mem = rproc_of_resm_mem_entry_init(dev, index,
+ rmem->size,
+ rmem->base,
+ it.node->name);
+ }
+
+ if (!mem)
+ return -ENOMEM;
+
+ rproc_add_carveout(rproc, mem);
+ index++;
+ }
+
+ return stm32_rproc_elf_load_rsc_table(rproc, fw);
+}
+
+static struct resource_table *
+stm32_rproc_elf_find_loaded_rsc_table(struct rproc *rproc,
+ const struct firmware *fw)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+
+ if (!rproc->early_boot)
+ return rproc_elf_find_loaded_rsc_table(rproc, fw);
+
+ return (struct resource_table *)ddata->rsc_va;
+}
+
+static int stm32_rproc_elf_sanity_check(struct rproc *rproc,
+ const struct firmware *fw)
+{
+ if (!rproc->early_boot)
+ return rproc_elf_sanity_check(rproc, fw);
+
+ return 0;
+}
+
+static u32 stm32_rproc_elf_get_boot_addr(struct rproc *rproc,
+ const struct firmware *fw)
+{
+ if (!rproc->early_boot)
+ return rproc_elf_get_boot_addr(rproc, fw);
+
+ return 0;
+}
+
+static irqreturn_t stm32_rproc_wdg(int irq, void *data)
+{
+ struct rproc *rproc = data;
+
+ rproc_report_crash(rproc, RPROC_WATCHDOG);
+
+ return IRQ_HANDLED;
+}
+
+static void stm32_rproc_mb_vq_work(struct work_struct *work)
+{
+ struct stm32_mbox *mb = container_of(work, struct stm32_mbox, vq_work);
+ struct rproc *rproc = dev_get_drvdata(mb->client.dev);
+
+ if (rproc_vq_interrupt(rproc, mb->vq_id) == IRQ_NONE)
+ dev_dbg(&rproc->dev, "no message found in vq%d\n", mb->vq_id);
+}
+
+static void stm32_rproc_mb_callback(struct mbox_client *cl, void *data)
+{
+ struct rproc *rproc = dev_get_drvdata(cl->dev);
+ struct stm32_mbox *mb = container_of(cl, struct stm32_mbox, client);
+ struct stm32_rproc *ddata = rproc->priv;
+
+ queue_work(ddata->workqueue, &mb->vq_work);
+}
+
+static void stm32_rproc_free_mbox(struct rproc *rproc)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(ddata->mb); i++) {
+ if (ddata->mb[i].chan)
+ mbox_free_channel(ddata->mb[i].chan);
+ ddata->mb[i].chan = NULL;
+ }
+}
+
+static const struct stm32_mbox stm32_rproc_mbox[MBOX_NB_MBX] = {
+ {
+ .name = STM32_MBX_VQ0,
+ .vq_id = STM32_MBX_VQ0_ID,
+ .client = {
+ .rx_callback = stm32_rproc_mb_callback,
+ .tx_block = false,
+ },
+ },
+ {
+ .name = STM32_MBX_VQ1,
+ .vq_id = STM32_MBX_VQ1_ID,
+ .client = {
+ .rx_callback = stm32_rproc_mb_callback,
+ .tx_block = false,
+ },
+ },
+ {
+ .name = STM32_MBX_SHUTDOWN,
+ .vq_id = -1,
+ .client = {
+ .tx_block = true,
+ .tx_done = NULL,
+ .tx_tout = 500, /* 500 ms time out */
+ },
+ }
+};
+
+static int stm32_rproc_request_mbox(struct rproc *rproc)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+ struct device *dev = &rproc->dev;
+ unsigned int i;
+ const unsigned char *name;
+ struct mbox_client *cl;
+
+ /* Initialise mailbox structure table */
+ memcpy(ddata->mb, stm32_rproc_mbox, sizeof(stm32_rproc_mbox));
+
+ for (i = 0; i < MBOX_NB_MBX; i++) {
+ name = ddata->mb[i].name;
+
+ cl = &ddata->mb[i].client;
+ cl->dev = dev->parent;
+
+ ddata->mb[i].chan = mbox_request_channel_byname(cl, name);
+ if (PTR_ERR(ddata->mb[i].chan) == -EPROBE_DEFER) {
+ return -EPROBE_DEFER;
+ } else if (IS_ERR(ddata->mb[i].chan)) {
+ dev_warn(dev, "cannot get %s mbox\n", name);
+ ddata->mb[i].chan = NULL;
+ }
+ if (ddata->mb[i].vq_id >= 0) {
+ INIT_WORK(&ddata->mb[i].vq_work,
+ stm32_rproc_mb_vq_work);
+ }
+ }
+
+ return 0;
+}
+
+static int stm32_rproc_set_hold_boot(struct rproc *rproc, bool hold)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+ struct stm32_syscon hold_boot = ddata->hold_boot;
+ struct arm_smccc_res smc_res;
+ int val, err;
+
+ val = hold ? HOLD_BOOT : RELEASE_BOOT;
+
+ if (ddata->secured_soc) {
+ arm_smccc_smc(STM32_SMC_RCC, STM32_SMC_REG_WRITE,
+ hold_boot.reg, val, 0, 0, 0, 0, &smc_res);
+ err = smc_res.a0;
+ } else {
+ err = regmap_update_bits(hold_boot.map, hold_boot.reg,
+ hold_boot.mask, val);
+ }
+
+ if (err)
+ dev_err(&rproc->dev, "failed to set hold boot\n");
+
+ return err;
+}
+
+static void stm32_rproc_add_coredump_trace(struct rproc *rproc)
+{
+ struct rproc_debug_trace *trace;
+ struct rproc_dump_segment *segment;
+ bool already_added;
+
+ list_for_each_entry(trace, &rproc->traces, node) {
+ already_added = false;
+
+ list_for_each_entry(segment, &rproc->dump_segments, node) {
+ if (segment->da == trace->trace_mem.da) {
+ already_added = true;
+ break;
+ }
+ }
+
+ if (!already_added)
+ rproc_coredump_add_segment(rproc, trace->trace_mem.da,
+ trace->trace_mem.len);
+ }
+}
+
+static int stm32_rproc_start(struct rproc *rproc)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+ int err;
+
+ stm32_rproc_add_coredump_trace(rproc);
+
+ /* clear remote proc Deep Sleep */
+ if (ddata->pdds.map && !rproc->early_boot) {
+ err = regmap_update_bits(ddata->pdds.map, ddata->pdds.reg,
+ ddata->pdds.mask, 0);
+ if (err) {
+ dev_err(&rproc->dev, "failed to clear pdds\n");
+ return err;
+ }
+ }
+
+ /*
+ * If M4 previously started by bootloader, just guarantee holdboot
+ * is set to catch any crash.
+ */
+ if (!rproc->early_boot) {
+ err = stm32_rproc_set_hold_boot(rproc, false);
+ if (err)
+ return err;
+ }
+
+ return stm32_rproc_set_hold_boot(rproc, true);
+}
+
+static int stm32_rproc_stop(struct rproc *rproc)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+ int err, dummy_data, idx;
+
+ /* request shutdown of the remote processor */
+ if (rproc->state != RPROC_OFFLINE) {
+ idx = stm32_rproc_mbox_idx(rproc, STM32_MBX_SHUTDOWN);
+ if (idx >= 0 && ddata->mb[idx].chan) {
+ /* a dummy data is sent to allow to block on transmit */
+ err = mbox_send_message(ddata->mb[idx].chan,
+ &dummy_data);
+ if (err < 0)
+ dev_warn(&rproc->dev, "warning: remote FW shutdown without ack\n");
+ }
+ }
+
+ err = stm32_rproc_set_hold_boot(rproc, true);
+ if (err)
+ return err;
+
+ err = reset_control_assert(ddata->rst);
+ if (err) {
+ dev_err(&rproc->dev, "failed to assert the reset\n");
+ return err;
+ }
+
+ /* to allow platform Standby power mode, set remote proc Deep Sleep */
+ if (ddata->pdds.map) {
+ err = regmap_update_bits(ddata->pdds.map, ddata->pdds.reg,
+ ddata->pdds.mask, 1);
+ if (err) {
+ dev_err(&rproc->dev, "failed to set pdds\n");
+ return err;
+ }
+ }
+
+ /* Reset early_boot state as we stop the co-processor */
+ rproc->early_boot = false;
+
+ return 0;
+}
+
+static void stm32_rproc_kick(struct rproc *rproc, int vqid)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+ unsigned int i;
+ int err;
+
+ if (WARN_ON(vqid >= MBOX_NB_VQ))
+ return;
+
+ for (i = 0; i < MBOX_NB_MBX; i++) {
+ if (vqid != ddata->mb[i].vq_id)
+ continue;
+ if (!ddata->mb[i].chan)
+ return;
+ err = mbox_send_message(ddata->mb[i].chan, (void *)vqid);
+ if (err < 0)
+ dev_err(&rproc->dev, "%s: failed (%s, err:%d)\n",
+ __func__, ddata->mb[i].name, err);
+ return;
+ }
+}
+
+static struct rproc_ops st_rproc_ops = {
+ .start = stm32_rproc_start,
+ .stop = stm32_rproc_stop,
+ .kick = stm32_rproc_kick,
+ .load = stm32_rproc_elf_load_segments,
+ .parse_fw = stm32_rproc_parse_fw,
+ .find_loaded_rsc_table = stm32_rproc_elf_find_loaded_rsc_table,
+ .sanity_check = stm32_rproc_elf_sanity_check,
+ .get_boot_addr = stm32_rproc_elf_get_boot_addr,
+};
+
+static const struct of_device_id stm32_rproc_match[] = {
+ { .compatible = "st,stm32mp1-rproc" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, stm32_rproc_match);
+
+static int stm32_rproc_get_syscon(struct device_node *np, const char *prop,
+ struct stm32_syscon *syscon)
+{
+ int err = 0;
+
+ syscon->map = syscon_regmap_lookup_by_phandle(np, prop);
+ if (IS_ERR(syscon->map)) {
+ err = PTR_ERR(syscon->map);
+ syscon->map = NULL;
+ goto out;
+ }
+
+ err = of_property_read_u32_index(np, prop, 1, &syscon->reg);
+ if (err)
+ goto out;
+
+ err = of_property_read_u32_index(np, prop, 2, &syscon->mask);
+
+out:
+ return err;
+}
+
+static int stm32_rproc_parse_dt(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct rproc *rproc = platform_get_drvdata(pdev);
+ struct stm32_rproc *ddata = rproc->priv;
+ struct stm32_syscon tz, rsctbl;
+ phys_addr_t rsc_pa;
+ u32 rsc_da;
+ unsigned int tzen;
+ int err, irq;
+
+ irq = platform_get_irq_byname(pdev, "wdg");
+ if (irq == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ if (irq > 0) {
+ err = devm_request_irq(dev, irq, stm32_rproc_wdg, 0,
+ dev_name(dev), rproc);
+ if (err) {
+ dev_err(dev, "failed to request wdg irq\n");
+ return err;
+ }
+
+ ddata->wdg_irq = irq;
+
+ if (of_property_read_bool(np, "wakeup-source")) {
+ device_init_wakeup(dev, true);
+ dev_pm_set_wake_irq(dev, irq);
+ }
+
+ dev_info(dev, "wdg irq registered\n");
+ }
+
+ ddata->rst = devm_reset_control_get(dev, "mcu_rst");
+ if (IS_ERR(ddata->rst)) {
+ dev_err(dev, "failed to get mcu reset\n");
+ return PTR_ERR(ddata->rst);
+ }
+
+ /*
+ * if platform is secured the hold boot bit must be written by
+ * smc call and read normally.
+ * if not secure the hold boot bit could be read/write normally
+ */
+ err = stm32_rproc_get_syscon(np, "st,syscfg-tz", &tz);
+ if (err) {
+ dev_err(dev, "failed to get tz syscfg\n");
+ return err;
+ }
+
+ err = regmap_read(tz.map, tz.reg, &tzen);
+ if (err) {
+ dev_err(&rproc->dev, "failed to read tzen\n");
+ return err;
+ }
+ ddata->secured_soc = tzen & tz.mask;
+
+ err = stm32_rproc_get_syscon(np, "st,syscfg-holdboot",
+ &ddata->hold_boot);
+ if (err) {
+ dev_err(dev, "failed to get hold boot\n");
+ return err;
+ }
+
+ err = stm32_rproc_get_syscon(np, "st,syscfg-pdds", &ddata->pdds);
+ if (err)
+ dev_warn(dev, "pdds not supported\n");
+
+
+ rproc->auto_boot = of_property_read_bool(np, "auto_boot");
+ rproc->recovery_disabled = !of_property_read_bool(np, "recovery");
+
+ err = stm32_rproc_of_memory_translations(rproc);
+ if (err)
+ return err;
+
+ if (of_property_read_bool(np, "early-booted")) {
+ rproc->early_boot = true;
+
+ if (stm32_rproc_get_syscon(np, "st,syscfg-rsc-tbl", &rsctbl)) {
+ /* no rsc table syscon (optional) */
+ dev_warn(dev, "rsc tbl syscon not supported\n");
+ goto bail;
+ }
+
+ err = regmap_read(rsctbl.map, rsctbl.reg, &rsc_da);
+ if (err) {
+ dev_err(&rproc->dev, "failed to read rsc tbl addr\n");
+ return err;
+ }
+ if (!rsc_da)
+ /* no rsc table */
+ goto bail;
+
+ err = stm32_rproc_da_to_pa(rproc, rsc_da, &rsc_pa);
+ if (err)
+ return err;
+
+ ddata->rsc_va = devm_ioremap_wc(dev, rsc_pa, RSC_TBL_SIZE);
+ if (IS_ERR_OR_NULL(ddata->rsc_va)) {
+ dev_err(dev, "Unable to map memory region: %pa+%zx\n",
+ &rsc_pa, RSC_TBL_SIZE);
+ ddata->rsc_va = NULL;
+ return -ENOMEM;
+ }
+ }
+bail:
+ return 0;
+}
+
+static int stm32_rproc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct stm32_rproc *ddata;
+ struct device_node *np = dev->of_node;
+ struct rproc *rproc;
+ int ret;
+
+ rproc = rproc_alloc(dev, np->name, &st_rproc_ops, NULL, sizeof(*ddata));
+ if (!rproc)
+ return -ENOMEM;
+
+ rproc->has_iommu = false;
+ ddata = rproc->priv;
+ ddata->workqueue = create_workqueue(dev_name(dev));
+ if (!ddata->workqueue) {
+ dev_err(dev, "cannot create workqueue\n");
+ ret = -ENOMEM;
+ goto free_rproc;
+ }
+
+ platform_set_drvdata(pdev, rproc);
+
+ ret = stm32_rproc_parse_dt(pdev);
+ if (ret)
+ goto free_wkq;
+
+ if (!rproc->early_boot) {
+ ret = stm32_rproc_stop(rproc);
+ if (ret)
+ goto free_wkq;
+ }
+
+ ret = stm32_rproc_request_mbox(rproc);
+ if (ret)
+ goto free_wkq;
+
+ ret = rproc_add(rproc);
+ if (ret)
+ goto free_mb;
+
+ return 0;
+
+free_mb:
+ stm32_rproc_free_mbox(rproc);
+free_wkq:
+ destroy_workqueue(ddata->workqueue);
+free_rproc:
+ if (device_may_wakeup(dev)) {
+ dev_pm_clear_wake_irq(dev);
+ device_init_wakeup(dev, false);
+ }
+ rproc_free(rproc);
+ return ret;
+}
+
+static int stm32_rproc_remove(struct platform_device *pdev)
+{
+ struct rproc *rproc = platform_get_drvdata(pdev);
+ struct stm32_rproc *ddata = rproc->priv;
+ struct device *dev = &pdev->dev;
+
+ if (atomic_read(&rproc->power) > 0)
+ rproc_shutdown(rproc);
+
+ rproc_del(rproc);
+ stm32_rproc_free_mbox(rproc);
+ destroy_workqueue(ddata->workqueue);
+
+ if (device_may_wakeup(dev)) {
+ dev_pm_clear_wake_irq(dev);
+ device_init_wakeup(dev, false);
+ }
+ rproc_free(rproc);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int stm32_rproc_suspend(struct device *dev)
+{
+ struct rproc *rproc = dev_get_drvdata(dev);
+ struct stm32_rproc *ddata = rproc->priv;
+
+ if (device_may_wakeup(dev))
+ return enable_irq_wake(ddata->wdg_irq);
+
+ return 0;
+}
+
+static int stm32_rproc_resume(struct device *dev)
+{
+ struct rproc *rproc = dev_get_drvdata(dev);
+ struct stm32_rproc *ddata = rproc->priv;
+
+ if (device_may_wakeup(dev))
+ return disable_irq_wake(ddata->wdg_irq);
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(stm32_rproc_pm_ops,
+ stm32_rproc_suspend, stm32_rproc_resume);
+
+static struct platform_driver stm32_rproc_driver = {
+ .probe = stm32_rproc_probe,
+ .remove = stm32_rproc_remove,
+ .driver = {
+ .name = "stm32-rproc",
+ .pm = &stm32_rproc_pm_ops,
+ .of_match_table = of_match_ptr(stm32_rproc_match),
+ },
+};
+module_platform_driver(stm32_rproc_driver);
+
+MODULE_DESCRIPTION("STM32 Remote Processor Control Driver");
+MODULE_AUTHOR("Ludovic Barre <ludovic.barre@st.com>");
+MODULE_LICENSE("GPL v2");
+
diff --git a/drivers/reset/reset-stm32mp1.c b/drivers/reset/reset-stm32mp1.c
index b221a28..d46c47b 100644
--- a/drivers/reset/reset-stm32mp1.c
+++ b/drivers/reset/reset-stm32mp1.c
@@ -4,6 +4,7 @@
* Author: Gabriel Fernandez <gabriel.fernandez@st.com> for STMicroelectronics.
*/
+#include <linux/arm-smccc.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/io.h>
@@ -13,11 +14,50 @@
#define CLR_OFFSET 0x4
+#define STM32_RCC_TZCR 0x0
+#define CLR_OFFSET 0x4
+
+#define STM32MP1_SVC_RCC 0x82001000
+
+#define SMT32_SPI6_R 3136
+#define STM32_AXIM_R 3216
+#define STM32_MCU_R 8225
+
struct stm32_reset_data {
struct reset_controller_dev rcdev;
void __iomem *membase;
};
+static int soc_secured;
+
+static int is_stm32_id_secured(unsigned long id)
+{
+ if (id >= SMT32_SPI6_R && id <= STM32_AXIM_R)
+ return 1;
+
+ if (id == STM32_MCU_R)
+ return 1;
+
+ return 0;
+}
+
+static int reset_stm32_secure_update(struct reset_controller_dev *rcdev,
+ unsigned long id, bool assert)
+{
+ struct arm_smccc_res res;
+ int bank = id / BITS_PER_LONG;
+ int offset = id % BITS_PER_LONG;
+
+ if (assert)
+ arm_smccc_smc(STM32MP1_SVC_RCC, 0x1, (bank * 4),
+ BIT(offset), 0, 0, 0, 0, &res);
+ else
+ arm_smccc_smc(STM32MP1_SVC_RCC, 0x1, (bank * 4) + CLR_OFFSET,
+ BIT(offset), 0, 0, 0, 0, &res);
+
+ return 0;
+}
+
static inline struct stm32_reset_data *
to_stm32_reset_data(struct reset_controller_dev *rcdev)
{
@@ -45,12 +85,18 @@ static int stm32_reset_update(struct reset_controller_dev *rcdev,
static int stm32_reset_assert(struct reset_controller_dev *rcdev,
unsigned long id)
{
+ if (soc_secured && is_stm32_id_secured(id))
+ return reset_stm32_secure_update(rcdev, id, true);
+
return stm32_reset_update(rcdev, id, true);
}
static int stm32_reset_deassert(struct reset_controller_dev *rcdev,
unsigned long id)
{
+ if (soc_secured && is_stm32_id_secured(id))
+ return reset_stm32_secure_update(rcdev, id, false);
+
return stm32_reset_update(rcdev, id, false);
}
@@ -101,6 +147,8 @@ static int stm32_reset_probe(struct platform_device *pdev)
data->rcdev.ops = &stm32_reset_ops;
data->rcdev.of_node = dev->of_node;
+ soc_secured = readl(membase + STM32_RCC_TZCR) & 0x1;
+
return devm_reset_controller_register(dev, &data->rcdev);
}
diff --git a/drivers/rpmsg/Kconfig b/drivers/rpmsg/Kconfig
index d0322b4..88759a4 100644
--- a/drivers/rpmsg/Kconfig
+++ b/drivers/rpmsg/Kconfig
@@ -55,4 +55,13 @@ config RPMSG_VIRTIO
select RPMSG
select VIRTIO
+config RPMSG_TTY
+ tristate "RPMSG tty driver"
+ depends on RPMSG
+ help
+ Say y here to export rpmsg endpoints as tty console, usually found
+ in /dev/ttyRPMSG.
+ This makes it possible for user-space programs to send and receive
+ rpmsg messages as a standard tty protocol.
+
endmenu
diff --git a/drivers/rpmsg/Makefile b/drivers/rpmsg/Makefile
index 9aa8595..107145c 100644
--- a/drivers/rpmsg/Makefile
+++ b/drivers/rpmsg/Makefile
@@ -5,4 +5,5 @@ obj-$(CONFIG_RPMSG_QCOM_GLINK_RPM) += qcom_glink_rpm.o
obj-$(CONFIG_RPMSG_QCOM_GLINK_NATIVE) += qcom_glink_native.o
obj-$(CONFIG_RPMSG_QCOM_GLINK_SMEM) += qcom_glink_smem.o
obj-$(CONFIG_RPMSG_QCOM_SMD) += qcom_smd.o
+obj-$(CONFIG_RPMSG_TTY) += rpmsg_tty.o
obj-$(CONFIG_RPMSG_VIRTIO) += virtio_rpmsg_bus.o
diff --git a/drivers/rpmsg/rpmsg_core.c b/drivers/rpmsg/rpmsg_core.c
index 8122807..edb8549 100644
--- a/drivers/rpmsg/rpmsg_core.c
+++ b/drivers/rpmsg/rpmsg_core.c
@@ -283,6 +283,25 @@ int rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src, u32 dst,
}
EXPORT_SYMBOL(rpmsg_trysend_offchannel);
+/**
+ * rpmsg_get_buffer_size()
+ * This function returns buffer size available for sending messages.
+ *
+ * @ept: the rpmsg endpoint
+ *
+ * Returns buffer size on success and an appropriate error value on failure.
+ */
+int rpmsg_get_buffer_size(struct rpmsg_endpoint *ept)
+{
+ if (WARN_ON(!ept))
+ return -EINVAL;
+ if (!ept->ops->get_buffer_size)
+ return -ENXIO;
+
+ return ept->ops->get_buffer_size(ept);
+}
+EXPORT_SYMBOL(rpmsg_get_buffer_size);
+
/*
* match an rpmsg channel with a channel info struct.
* this is used to make sure we're not creating rpmsg devices for channels
diff --git a/drivers/rpmsg/rpmsg_internal.h b/drivers/rpmsg/rpmsg_internal.h
index 0d791c3..65bcb52 100644
--- a/drivers/rpmsg/rpmsg_internal.h
+++ b/drivers/rpmsg/rpmsg_internal.h
@@ -46,6 +46,7 @@ struct rpmsg_device_ops {
* @trysend: see @rpmsg_trysend(), required
* @trysendto: see @rpmsg_trysendto(), optional
* @trysend_offchannel: see @rpmsg_trysend_offchannel(), optional
+ * @get_buffer_size: see @rpmsg_get_buffer_size(), optional
*
* Indirection table for the operations that a rpmsg backend should implement.
* In addition to @destroy_ept, the backend must at least implement @send and
@@ -65,6 +66,7 @@ struct rpmsg_endpoint_ops {
void *data, int len);
__poll_t (*poll)(struct rpmsg_endpoint *ept, struct file *filp,
poll_table *wait);
+ int (*get_buffer_size)(struct rpmsg_endpoint *ept);
};
int rpmsg_register_device(struct rpmsg_device *rpdev);
diff --git a/drivers/rpmsg/rpmsg_tty.c b/drivers/rpmsg/rpmsg_tty.c
new file mode 100644
index 0000000..5776389
--- /dev/null
+++ b/drivers/rpmsg/rpmsg_tty.c
@@ -0,0 +1,310 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Author: Arnaud Pouliquen <arnaud.pouliquen@st.com> for STMicroelectronics.
+ * Derived from the imx-rmpsg and omap-rpmsg implementations.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/rpmsg.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/virtio.h>
+
+#define MAX_TTY_RPMSG_INDEX 32 /* Should be enough for a while */
+
+static LIST_HEAD(rpmsg_tty_list); /* tty instances list */
+static DEFINE_MUTEX(rpmsg_tty_lock); /* protect tty list */
+
+static struct tty_driver *rpmsg_tty_driver;
+static struct tty_port_operations rpmsg_tty_port_ops = { };
+
+struct rpmsg_tty_port {
+ struct tty_port port; /* TTY port data */
+ struct list_head list; /* TTY device list */
+ u32 id; /* tty rpmsg index */
+ spinlock_t rx_lock; /* message reception lock */
+ struct rpmsg_device *rpdev; /* handle rpmsg device */
+};
+
+static int rpmsg_tty_cb(struct rpmsg_device *rpdev, void *data, int len,
+ void *priv, u32 src)
+{
+ int space;
+ unsigned char *cbuf;
+ struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
+
+ /* Flush the recv-ed none-zero data to tty node */
+ if (len == 0)
+ return -EINVAL;
+
+ dev_dbg(&rpdev->dev, "msg(<- src 0x%x) len %d\n", src, len);
+
+ print_hex_dump_debug(__func__, DUMP_PREFIX_NONE, 16, 1, data, len,
+ true);
+
+ spin_lock(&cport->rx_lock);
+ space = tty_prepare_flip_string(&cport->port, &cbuf, len);
+ if (space <= 0) {
+ dev_err(&rpdev->dev, "No memory for tty_prepare_flip_string\n");
+ spin_unlock(&cport->rx_lock);
+ return -ENOMEM;
+ }
+
+ if (space != len)
+ dev_warn(&rpdev->dev, "Trunc buffer from %d to %d\n",
+ len, space);
+
+ memcpy(cbuf, data, space);
+ tty_flip_buffer_push(&cport->port);
+ spin_unlock(&cport->rx_lock);
+
+ return 0;
+}
+
+static struct rpmsg_tty_port *rpmsg_tty_get(unsigned int index)
+{
+ struct rpmsg_tty_port *cport;
+
+ mutex_lock(&rpmsg_tty_lock);
+ list_for_each_entry(cport, &rpmsg_tty_list, list) {
+ if (index == cport->id) {
+ mutex_unlock(&rpmsg_tty_lock);
+ return cport;
+ }
+ }
+ mutex_unlock(&rpmsg_tty_lock);
+
+ return NULL;
+}
+
+static int rpmsg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+ struct rpmsg_tty_port *cport = rpmsg_tty_get(tty->index);
+
+ if (!cport)
+ return -ENODEV;
+
+ return tty_port_install(&cport->port, driver, tty);
+}
+
+static int rpmsg_tty_open(struct tty_struct *tty, struct file *filp)
+{
+ struct rpmsg_tty_port *cport = rpmsg_tty_get(tty->index);
+
+ if (!cport)
+ return -ENODEV;
+
+ return tty_port_open(tty->port, tty, filp);
+}
+
+static void rpmsg_tty_close(struct tty_struct *tty, struct file *filp)
+{
+ struct rpmsg_tty_port *cport = rpmsg_tty_get(tty->index);
+
+ if (!cport)
+ return;
+ return tty_port_close(tty->port, tty, filp);
+}
+
+static int rpmsg_tty_write(struct tty_struct *tty, const unsigned char *buf,
+ int total)
+{
+ int count, ret = 0;
+ const unsigned char *tbuf;
+ struct rpmsg_tty_port *cport = container_of(tty->port,
+ struct rpmsg_tty_port, port);
+ struct rpmsg_device *rpdev = cport->rpdev;
+ int msg_size;
+
+ dev_dbg(&rpdev->dev, "%s: send message from tty->index = %d\n",
+ __func__, tty->index);
+
+ if (!buf) {
+ dev_err(&rpdev->dev, "buf shouldn't be null.\n");
+ return -ENOMEM;
+ }
+
+ msg_size = rpmsg_get_buffer_size(rpdev->ept);
+ if (msg_size < 0)
+ return msg_size;
+
+ count = total;
+ tbuf = buf;
+ do {
+ /* send a message to our remote processor */
+ ret = rpmsg_trysend(rpdev->ept, (void *)tbuf,
+ count > msg_size ? msg_size : count);
+ if (ret) {
+ dev_dbg(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
+ return 0;
+ }
+
+ if (count > msg_size) {
+ count -= msg_size;
+ tbuf += msg_size;
+ } else {
+ count = 0;
+ }
+ } while (count > 0);
+
+ return total;
+}
+
+static int rpmsg_tty_write_room(struct tty_struct *tty)
+{
+ struct rpmsg_tty_port *cport = container_of(tty->port,
+ struct rpmsg_tty_port, port);
+ struct rpmsg_device *rpdev = cport->rpdev;
+
+ /* report the space in the rpmsg buffer */
+ return rpmsg_get_buffer_size(rpdev->ept);
+}
+
+static const struct tty_operations rpmsg_tty_ops = {
+ .install = rpmsg_tty_install,
+ .open = rpmsg_tty_open,
+ .close = rpmsg_tty_close,
+ .write = rpmsg_tty_write,
+ .write_room = rpmsg_tty_write_room,
+};
+
+static int rpmsg_tty_probe(struct rpmsg_device *rpdev)
+{
+ struct rpmsg_tty_port *cport, *tmp;
+ unsigned int index;
+ struct device *tty_dev;
+
+ cport = devm_kzalloc(&rpdev->dev, sizeof(*cport), GFP_KERNEL);
+ if (!cport)
+ return -ENOMEM;
+
+ tty_port_init(&cport->port);
+ cport->port.ops = &rpmsg_tty_port_ops;
+ spin_lock_init(&cport->rx_lock);
+
+ cport->port.low_latency = cport->port.flags | ASYNC_LOW_LATENCY;
+
+ cport->rpdev = rpdev;
+
+ /* get free index */
+ mutex_lock(&rpmsg_tty_lock);
+ for (index = 0; index < MAX_TTY_RPMSG_INDEX; index++) {
+ bool id_found = false;
+
+ list_for_each_entry(tmp, &rpmsg_tty_list, list) {
+ if (index == tmp->id) {
+ id_found = true;
+ break;
+ }
+ }
+ if (!id_found)
+ break;
+ }
+
+ tty_dev = tty_port_register_device(&cport->port, rpmsg_tty_driver,
+ index, &rpdev->dev);
+ if (IS_ERR(tty_dev)) {
+ dev_err(&rpdev->dev, "failed to register tty port\n");
+ tty_port_destroy(&cport->port);
+ mutex_unlock(&rpmsg_tty_lock);
+ return PTR_ERR(tty_dev);
+ }
+
+ cport->id = index;
+ list_add_tail(&cport->list, &rpmsg_tty_list);
+ mutex_unlock(&rpmsg_tty_lock);
+ dev_set_drvdata(&rpdev->dev, cport);
+
+ dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x : ttyRPMSG%d\n",
+ rpdev->src, rpdev->dst, index);
+
+ return 0;
+}
+
+static void rpmsg_tty_remove(struct rpmsg_device *rpdev)
+{
+ struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
+
+ /* User hang up to release the tty */
+ if (tty_port_initialized(&cport->port))
+ tty_port_tty_hangup(&cport->port, false);
+ tty_port_destroy(&cport->port);
+ tty_unregister_device(rpmsg_tty_driver, cport->id);
+ list_del(&cport->list);
+
+ dev_info(&rpdev->dev, "rpmsg tty device %d is removed\n", cport->id);
+}
+
+static struct rpmsg_device_id rpmsg_driver_tty_id_table[] = {
+ { .name = "rpmsg-tty-channel" },
+ { },
+};
+MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_tty_id_table);
+
+static struct rpmsg_driver rpmsg_tty_rmpsg_drv = {
+ .drv.name = KBUILD_MODNAME,
+ .drv.owner = THIS_MODULE,
+ .id_table = rpmsg_driver_tty_id_table,
+ .probe = rpmsg_tty_probe,
+ .callback = rpmsg_tty_cb,
+ .remove = rpmsg_tty_remove,
+};
+
+static int __init rpmsg_tty_init(void)
+{
+ int err;
+
+ rpmsg_tty_driver = tty_alloc_driver(MAX_TTY_RPMSG_INDEX, 0);
+ if (IS_ERR(rpmsg_tty_driver))
+ return PTR_ERR(rpmsg_tty_driver);
+
+ rpmsg_tty_driver->driver_name = "rpmsg_tty";
+ rpmsg_tty_driver->name = "ttyRPMSG";
+ rpmsg_tty_driver->major = TTYAUX_MAJOR;
+ rpmsg_tty_driver->minor_start = 3;
+ rpmsg_tty_driver->type = TTY_DRIVER_TYPE_CONSOLE;
+ rpmsg_tty_driver->init_termios = tty_std_termios;
+ rpmsg_tty_driver->flags = TTY_DRIVER_REAL_RAW |
+ TTY_DRIVER_DYNAMIC_DEV;
+
+ tty_set_operations(rpmsg_tty_driver, &rpmsg_tty_ops);
+
+ /* Disable unused mode by default */
+ rpmsg_tty_driver->init_termios = tty_std_termios;
+ rpmsg_tty_driver->init_termios.c_lflag &= ~(ECHO | ICANON);
+ rpmsg_tty_driver->init_termios.c_oflag &= ~(OPOST | ONLCR);
+
+ err = tty_register_driver(rpmsg_tty_driver);
+ if (err < 0) {
+ pr_err("Couldn't install rpmsg tty driver: err %d\n", err);
+ goto tty_error;
+ }
+ err = register_rpmsg_driver(&rpmsg_tty_rmpsg_drv);
+
+ if (!err)
+ return 0;
+
+ tty_unregister_driver(rpmsg_tty_driver);
+
+tty_error:
+ put_tty_driver(rpmsg_tty_driver);
+
+ return err;
+}
+
+static void __exit rpmsg_tty_exit(void)
+{
+ unregister_rpmsg_driver(&rpmsg_tty_rmpsg_drv);
+ tty_unregister_driver(rpmsg_tty_driver);
+ put_tty_driver(rpmsg_tty_driver);
+}
+
+module_init(rpmsg_tty_init);
+module_exit(rpmsg_tty_exit);
+
+MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>");
+MODULE_DESCRIPTION("virtio remote processor messaging tty driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/rpmsg/virtio_rpmsg_bus.c b/drivers/rpmsg/virtio_rpmsg_bus.c
index 664f957..f1f3032 100644
--- a/drivers/rpmsg/virtio_rpmsg_bus.c
+++ b/drivers/rpmsg/virtio_rpmsg_bus.c
@@ -175,6 +175,7 @@ static int virtio_rpmsg_trysendto(struct rpmsg_endpoint *ept, void *data,
int len, u32 dst);
static int virtio_rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src,
u32 dst, void *data, int len);
+static int virtio_get_buffer_size(struct rpmsg_endpoint *ept);
static const struct rpmsg_endpoint_ops virtio_endpoint_ops = {
.destroy_ept = virtio_rpmsg_destroy_ept,
@@ -184,6 +185,7 @@ static const struct rpmsg_endpoint_ops virtio_endpoint_ops = {
.trysend = virtio_rpmsg_trysend,
.trysendto = virtio_rpmsg_trysendto,
.trysend_offchannel = virtio_rpmsg_trysend_offchannel,
+ .get_buffer_size = virtio_get_buffer_size,
};
/**
@@ -699,6 +701,15 @@ static int virtio_rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src,
return rpmsg_send_offchannel_raw(rpdev, src, dst, data, len, false);
}
+static int virtio_get_buffer_size(struct rpmsg_endpoint *ept)
+{
+ struct rpmsg_device *rpdev = ept->rpdev;
+ struct virtio_rpmsg_channel *vch = to_virtio_rpmsg_channel(rpdev);
+ struct virtproc_info *vrp = vch->vrp;
+
+ return vrp->buf_size - sizeof(struct rpmsg_hdr);
+}
+
static int rpmsg_recv_single(struct virtproc_info *vrp, struct device *dev,
struct rpmsg_hdr *msg, unsigned int len)
{
@@ -912,7 +923,7 @@ static int rpmsg_probe(struct virtio_device *vdev)
total_buf_space = vrp->num_bufs * vrp->buf_size;
/* allocate coherent memory for the buffers */
- bufs_va = dma_alloc_coherent(vdev->dev.parent->parent,
+ bufs_va = dma_alloc_coherent(vdev->dev.parent,
total_buf_space, &vrp->bufs_dma,
GFP_KERNEL);
if (!bufs_va) {
@@ -980,7 +991,7 @@ static int rpmsg_probe(struct virtio_device *vdev)
return 0;
free_coherent:
- dma_free_coherent(vdev->dev.parent->parent, total_buf_space,
+ dma_free_coherent(vdev->dev.parent, total_buf_space,
bufs_va, vrp->bufs_dma);
vqs_del:
vdev->config->del_vqs(vrp->vdev);
@@ -1015,7 +1026,7 @@ static void rpmsg_remove(struct virtio_device *vdev)
vdev->config->del_vqs(vrp->vdev);
- dma_free_coherent(vdev->dev.parent->parent, total_buf_space,
+ dma_free_coherent(vdev->dev.parent, total_buf_space,
vrp->rbufs, vrp->bufs_dma);
kfree(vrp);
diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h
index e3c5d85..cd540c0 100644
--- a/include/linux/remoteproc.h
+++ b/include/linux/remoteproc.h
@@ -305,14 +305,22 @@ struct fw_rsc_vdev {
struct fw_rsc_vdev_vring vring[0];
} __packed;
+struct rproc;
+
/**
* struct rproc_mem_entry - memory entry descriptor
* @va: virtual address
* @dma: dma address
* @len: length, in bytes
* @da: device address
+ * @release: release associated memory
* @priv: associated data
+ * @name: associated memory region name (optional)
* @node: list node
+ * @rsc_offset: offset in resource table
+ * @flags: iommu protection flags
+ * @of_resm_idx: reserved memory phandle index
+ * @alloc: specific memory allocator function
*/
struct rproc_mem_entry {
void *va;
@@ -320,10 +328,15 @@ struct rproc_mem_entry {
int len;
u32 da;
void *priv;
+ char name[32];
struct list_head node;
+ u32 rsc_offset;
+ u32 flags;
+ u32 of_resm_idx;
+ int (*alloc)(struct rproc *rproc, struct rproc_mem_entry *mem);
+ int (*release)(struct rproc *rproc, struct rproc_mem_entry *mem);
};
-struct rproc;
struct firmware;
/**
@@ -440,6 +453,8 @@ struct rproc_dump_segment {
* @table_sz: size of @cached_table
* @has_iommu: flag to indicate if remote processor is behind an MMU
* @dump_segments: list of segments in the firmware
+ * @early_boot: remote processor has been booted before kernel boot
+ * @nb_vdev: number of vdev currently handled by rproc
*/
struct rproc {
struct list_head node;
@@ -472,6 +487,8 @@ struct rproc {
bool has_iommu;
bool auto_boot;
struct list_head dump_segments;
+ bool early_boot;
+ int nb_vdev;
};
/**
@@ -499,7 +516,6 @@ struct rproc_subdev {
/**
* struct rproc_vring - remoteproc vring state
* @va: virtual address
- * @dma: dma address
* @len: length, in bytes
* @da: device address
* @align: vring alignment
@@ -509,7 +525,6 @@ struct rproc_subdev {
*/
struct rproc_vring {
void *va;
- dma_addr_t dma;
int len;
u32 da;
u32 align;
@@ -521,6 +536,7 @@ struct rproc_vring {
/**
* struct rproc_vdev - remoteproc state for a supported virtio device
* @refcount: reference counter for the vdev and vring allocations
+ * @dev: sub device associated to the virtio device
* @subdev: handle for registering the vdev as a rproc subdevice
* @id: virtio device id (as in virtio_ids.h)
* @node: list node
@@ -528,11 +544,13 @@ struct rproc_vring {
* @vdev: the virio device
* @vring: the vrings for this vdev
* @rsc_offset: offset of the vdev's resource entry
+ * @index: vdev position versus other vdev declared in resource table
*/
struct rproc_vdev {
struct kref refcount;
struct rproc_subdev subdev;
+ struct device dev;
unsigned int id;
struct list_head node;
@@ -540,6 +558,7 @@ struct rproc_vdev {
struct virtio_device vdev;
struct rproc_vring vring[RVDEV_NUM_VRINGS];
u32 rsc_offset;
+ u32 index;
};
struct rproc *rproc_get_by_phandle(phandle phandle);
@@ -553,6 +572,19 @@ int rproc_add(struct rproc *rproc);
int rproc_del(struct rproc *rproc);
void rproc_free(struct rproc *rproc);
+void rproc_add_carveout(struct rproc *rproc, struct rproc_mem_entry *mem);
+
+struct rproc_mem_entry *
+rproc_mem_entry_init(struct device *dev,
+ void *va, dma_addr_t dma, int len, u32 da,
+ int (*alloc)(struct rproc *, struct rproc_mem_entry *),
+ int (*release)(struct rproc *, struct rproc_mem_entry *),
+ const char *name, ...);
+
+struct rproc_mem_entry *
+rproc_of_resm_mem_entry_init(struct device *dev, u32 of_resm_idx, int len,
+ u32 da, const char *name, ...);
+
int rproc_boot(struct rproc *rproc);
void rproc_shutdown(struct rproc *rproc);
void rproc_report_crash(struct rproc *rproc, enum rproc_crash_type type);
diff --git a/include/linux/rpmsg.h b/include/linux/rpmsg.h
index 9fe156d..2af7674 100644
--- a/include/linux/rpmsg.h
+++ b/include/linux/rpmsg.h
@@ -135,6 +135,7 @@ int rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src, u32 dst,
__poll_t rpmsg_poll(struct rpmsg_endpoint *ept, struct file *filp,
poll_table *wait);
+int rpmsg_get_buffer_size(struct rpmsg_endpoint *ept);
#else
static inline int register_rpmsg_device(struct rpmsg_device *dev)
@@ -242,6 +243,14 @@ static inline __poll_t rpmsg_poll(struct rpmsg_endpoint *ept,
return 0;
}
+static int rpmsg_get_buffer_size(struct rpmsg_endpoint *ept)
+{
+ /* This shouldn't be possible */
+ WARN_ON(1);
+
+ return -ENXIO;
+}
+
#endif /* IS_ENABLED(CONFIG_RPMSG) */
/* use a macro to avoid include chaining to get THIS_MODULE */
--
2.7.4