From b1e1c0d118b5f460f13d08f7a4f25210aaa88641 Mon Sep 17 00:00:00 2001 From: Lionel VITTE 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 #include #include +#include +#include #include #include +#include #include #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 #include +#include #include #include #include @@ -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 for STMicroelectronics. + */ + +#include +#include +#include +#include +#include +#include + +#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 "); +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 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 for STMicroelectronics. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +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 for STMicroelectronics. + * Fabien Dessenne for STMicroelectronics. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +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 for STMicroelectronics. */ +#include #include #include #include @@ -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 for STMicroelectronics. + * Derived from the imx-rmpsg and omap-rpmsg implementations. + */ + +#include +#include +#include +#include +#include +#include +#include + +#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 "); +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