diff options
-rw-r--r-- | Documentation/remoteproc.txt | 324 | ||||
-rw-r--r-- | MAINTAINERS | 7 | ||||
-rw-r--r-- | drivers/Kconfig | 2 | ||||
-rw-r--r-- | drivers/Makefile | 1 | ||||
-rw-r--r-- | drivers/remoteproc/Kconfig | 3 | ||||
-rw-r--r-- | drivers/remoteproc/Makefile | 6 | ||||
-rw-r--r-- | drivers/remoteproc/remoteproc_core.c | 1410 | ||||
-rw-r--r-- | drivers/remoteproc/remoteproc_internal.h | 44 | ||||
-rw-r--r-- | include/linux/remoteproc.h | 265 |
9 files changed, 2062 insertions, 0 deletions
diff --git a/Documentation/remoteproc.txt b/Documentation/remoteproc.txt new file mode 100644 index 00000000000..23ff7349ffe --- /dev/null +++ b/Documentation/remoteproc.txt @@ -0,0 +1,324 @@ +Remote Processor Framework + +1. Introduction + +Modern SoCs typically have heterogeneous remote processor devices in asymmetric +multiprocessing (AMP) configurations, which may be running different instances +of operating system, whether it's Linux or any other flavor of real-time OS. + +OMAP4, for example, has dual Cortex-A9, dual Cortex-M3 and a C64x+ DSP. +In a typical configuration, the dual cortex-A9 is running Linux in a SMP +configuration, and each of the other three cores (two M3 cores and a DSP) +is running its own instance of RTOS in an AMP configuration. + +The remoteproc framework allows different platforms/architectures to +control (power on, load firmware, power off) those remote processors while +abstracting the hardware differences, so the entire driver doesn't need to be +duplicated. In addition, this framework also adds rpmsg virtio devices +for remote processors that supports this kind of communication. This way, +platform-specific remoteproc drivers only need to provide a few low-level +handlers, and then all rpmsg drivers will then just work +(for more information about the virtio-based rpmsg bus and its drivers, +please read Documentation/rpmsg.txt). + +2. User API + + int rproc_boot(struct rproc *rproc) + - Boot a remote processor (i.e. load its firmware, power it on, ...). + If the remote processor is already powered on, this function immediately + returns (successfully). + Returns 0 on success, and an appropriate error value otherwise. + Note: to use this function you should already have a valid rproc + handle. There are several ways to achieve that cleanly (devres, pdata, + the way remoteproc_rpmsg.c does this, or, if this becomes prevalent, we + might also consider using dev_archdata for this). See also + rproc_get_by_name() below. + + void rproc_shutdown(struct rproc *rproc) + - Power off a remote processor (previously booted with rproc_boot()). + In case @rproc is still being used by an additional user(s), then + this function will just decrement the power refcount and exit, + without really powering off the device. + Every call to rproc_boot() must (eventually) be accompanied by a call + to rproc_shutdown(). Calling rproc_shutdown() redundantly is a bug. + Notes: + - we're not decrementing the rproc's refcount, only the power refcount. + which means that the @rproc handle stays valid even after + rproc_shutdown() returns, and users can still use it with a subsequent + rproc_boot(), if needed. + - don't call rproc_shutdown() to unroll rproc_get_by_name(), exactly + because rproc_shutdown() _does not_ decrement the refcount of @rproc. + To decrement the refcount of @rproc, use rproc_put() (but _only_ if + you acquired @rproc using rproc_get_by_name()). + + struct rproc *rproc_get_by_name(const char *name) + - Find an rproc handle using the remote processor's name, and then + boot it. If it's already powered on, then just immediately return + (successfully). Returns the rproc handle on success, and NULL on failure. + This function increments the remote processor's refcount, so always + use rproc_put() to decrement it back once rproc isn't needed anymore. + Note: currently rproc_get_by_name() and rproc_put() are not used anymore + by the rpmsg bus and its drivers. We need to scrutinize the use cases + that still need them, and see if we can migrate them to use the non + name-based boot/shutdown interface. + + void rproc_put(struct rproc *rproc) + - Decrement @rproc's power refcount and shut it down if it reaches zero + (essentially by just calling rproc_shutdown), and then decrement @rproc's + validity refcount too. + After this function returns, @rproc may _not_ be used anymore, and its + handle should be considered invalid. + This function should be called _iff_ the @rproc handle was grabbed by + calling rproc_get_by_name(). + +3. Typical usage + +#include <linux/remoteproc.h> + +/* in case we were given a valid 'rproc' handle */ +int dummy_rproc_example(struct rproc *my_rproc) +{ + int ret; + + /* let's power on and boot our remote processor */ + ret = rproc_boot(my_rproc); + if (ret) { + /* + * something went wrong. handle it and leave. + */ + } + + /* + * our remote processor is now powered on... give it some work + */ + + /* let's shut it down now */ + rproc_shutdown(my_rproc); +} + +4. API for implementors + + struct rproc *rproc_alloc(struct device *dev, const char *name, + const struct rproc_ops *ops, + const char *firmware, int len) + - Allocate a new remote processor handle, but don't register + it yet. Required parameters are the underlying device, the + name of this remote processor, platform-specific ops handlers, + the name of the firmware to boot this rproc with, and the + length of private data needed by the allocating rproc driver (in bytes). + + This function should be used by rproc implementations during + initialization of the remote processor. + After creating an rproc handle using this function, and when ready, + implementations should then call rproc_register() to complete + the registration of the remote processor. + On success, the new rproc is returned, and on failure, NULL. + + Note: _never_ directly deallocate @rproc, even if it was not registered + yet. Instead, if you just need to unroll rproc_alloc(), use rproc_free(). + + void rproc_free(struct rproc *rproc) + - Free an rproc handle that was allocated by rproc_alloc. + This function should _only_ be used if @rproc was only allocated, + but not registered yet. + If @rproc was already successfully registered (by calling + rproc_register()), then use rproc_unregister() instead. + + int rproc_register(struct rproc *rproc) + - Register @rproc with the remoteproc framework, after it has been + allocated with rproc_alloc(). + This is called by the platform-specific rproc implementation, whenever + a new remote processor device is probed. + Returns 0 on success and an appropriate error code otherwise. + Note: this function initiates an asynchronous firmware loading + context, which will look for virtio devices supported by the rproc's + firmware. + If found, those virtio devices will be created and added, so as a result + of registering this remote processor, additional virtio drivers might get + probed. + Currently, though, we only support a single RPMSG virtio vdev per remote + processor. + + int rproc_unregister(struct rproc *rproc) + - Unregister a remote processor, and decrement its refcount. + If its refcount drops to zero, then @rproc will be freed. If not, + it will be freed later once the last reference is dropped. + + This function should be called when the platform specific rproc + implementation decides to remove the rproc device. it should + _only_ be called if a previous invocation of rproc_register() + has completed successfully. + + After rproc_unregister() returns, @rproc is _not_ valid anymore and + it shouldn't be used. More specifically, don't call rproc_free() + or try to directly free @rproc after rproc_unregister() returns; + none of these are needed, and calling them is a bug. + + Returns 0 on success and -EINVAL if @rproc isn't valid. + +5. Implementation callbacks + +These callbacks should be provided by platform-specific remoteproc +drivers: + +/** + * struct rproc_ops - platform-specific device handlers + * @start: power on the device and boot it + * @stop: power off the device + * @kick: kick a virtqueue (virtqueue id given as a parameter) + */ +struct rproc_ops { + int (*start)(struct rproc *rproc); + int (*stop)(struct rproc *rproc); + void (*kick)(struct rproc *rproc, int vqid); +}; + +Every remoteproc implementation should at least provide the ->start and ->stop +handlers. If rpmsg functionality is also desired, then the ->kick handler +should be provided as well. + +The ->start() handler takes an rproc handle and should then power on the +device and boot it (use rproc->priv to access platform-specific private data). +The boot address, in case needed, can be found in rproc->bootaddr (remoteproc +core puts there the ELF entry point). +On success, 0 should be returned, and on failure, an appropriate error code. + +The ->stop() handler takes an rproc handle and powers the device down. +On success, 0 is returned, and on failure, an appropriate error code. + +The ->kick() handler takes an rproc handle, and an index of a virtqueue +where new message was placed in. Implementations should interrupt the remote +processor and let it know it has pending messages. Notifying remote processors +the exact virtqueue index to look in is optional: it is easy (and not +too expensive) to go through the existing virtqueues and look for new buffers +in the used rings. + +6. Binary Firmware Structure + +At this point remoteproc only supports ELF32 firmware binaries. However, +it is quite expected that other platforms/devices which we'd want to +support with this framework will be based on different binary formats. + +When those use cases show up, we will have to decouple the binary format +from the framework core, so we can support several binary formats without +duplicating common code. + +When the firmware is parsed, its various segments are loaded to memory +according to the specified device address (might be a physical address +if the remote processor is accessing memory directly). + +In addition to the standard ELF segments, most remote processors would +also include a special section which we call "the resource table". + +The resource table contains system resources that the remote processor +requires before it should be powered on, such as allocation of physically +contiguous memory, or iommu mapping of certain on-chip peripherals. +Remotecore will only power up the device after all the resource table's +requirement are met. + +In addition to system resources, the resource table may also contain +resource entries that publish the existence of supported features +or configurations by the remote processor, such as trace buffers and +supported virtio devices (and their configurations). + +Currently the resource table is just an array of: + +/** + * struct fw_resource - describes an entry from the resource section + * @type: resource type + * @id: index number of the resource + * @da: device address of the resource + * @pa: physical address of the resource + * @len: size, in bytes, of the resource + * @flags: properties of the resource, e.g. iommu protection required + * @reserved: must be 0 atm + * @name: name of resource + */ +struct fw_resource { + u32 type; + u32 id; + u64 da; + u64 pa; + u32 len; + u32 flags; + u8 reserved[16]; + u8 name[48]; +} __packed; + +Some resources entries are mere announcements, where the host is informed +of specific remoteproc configuration. Other entries require the host to +do something (e.g. reserve a requested resource) and possibly also reply +by overwriting a member inside 'struct fw_resource' with info about the +allocated resource. + +Different resource entries use different members of this struct, +with different meanings. This is pretty limiting and error-prone, +so the plan is to move to variable-length TLV-based resource entries, +where each resource will begin with a type and length fields, followed by +its own specific structure. + +Here are the resource types that are currently being used: + +/** + * enum fw_resource_type - types of resource entries + * + * @RSC_CARVEOUT: request for allocation of a physically contiguous + * memory region. + * @RSC_DEVMEM: request to iommu_map a memory-based peripheral. + * @RSC_TRACE: announces the availability of a trace buffer into which + * the remote processor will be writing logs. In this case, + * 'da' indicates the device address where logs are written to, + * and 'len' is the size of the trace buffer. + * @RSC_VRING: request for allocation of a virtio vring (address should + * be indicated in 'da', and 'len' should contain the number + * of buffers supported by the vring). + * @RSC_VIRTIO_DEV: announces support for a virtio device, and serves as + * the virtio header. 'da' contains the virtio device + * features, 'pa' holds the virtio guest features (host + * will write them here after they're negotiated), 'len' + * holds the virtio status, and 'flags' holds the virtio + * device id (currently only VIRTIO_ID_RPMSG is supported). + */ +enum fw_resource_type { + RSC_CARVEOUT = 0, + RSC_DEVMEM = 1, + RSC_TRACE = 2, + RSC_VRING = 3, + RSC_VIRTIO_DEV = 4, + RSC_VIRTIO_CFG = 5, +}; + +Most of the resource entries share the basic idea of address/length +negotiation with the host: the firmware usually asks for memory +of size 'len' bytes, and the host needs to allocate it and provide +the device/physical address (when relevant) in 'da'/'pa' respectively. + +If the firmware is compiled with hard coded device addresses, and +can't handle dynamically allocated 'da' values, then the 'da' field +will contain the expected device addresses (today we actually only support +this scheme, as there aren't yet any use cases for dynamically allocated +device addresses). + +We also expect that platform-specific resource entries will show up +at some point. When that happens, we could easily add a new RSC_PLAFORM +type, and hand those resources to the platform-specific rproc driver to handle. + +7. Virtio and remoteproc + +The firmware should provide remoteproc information about virtio devices +that it supports, and their configurations: a RSC_VIRTIO_DEV resource entry +should specify the virtio device id, and subsequent RSC_VRING resource entries +should indicate the vring size (i.e. how many buffers do they support) and +where should they be mapped (i.e. which device address). Note: the alignment +between the consumer and producer parts of the vring is assumed to be 4096. + +At this point we only support a single virtio rpmsg device per remote +processor, but the plan is to remove this limitation. In addition, once we +move to TLV-based resource table, the plan is to have a single RSC_VIRTIO +entry per supported virtio device, which will include the virtio header, +the vrings information and the virtio config space. + +Of course, RSC_VIRTIO resource entries are only good enough for static +allocation of virtio devices. Dynamic allocations will also be made possible +using the rpmsg bus (similar to how we already do dynamic allocations of +rpmsg channels; read more about it in rpmsg.txt). diff --git a/MAINTAINERS b/MAINTAINERS index 89b70df91f4..b611319dc77 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5548,6 +5548,13 @@ S: Supported F: drivers/base/regmap/ F: include/linux/regmap.h +REMOTE PROCESSOR (REMOTEPROC) SUBSYSTEM +M: Ohad Ben-Cohen <ohad@wizery.com> +S: Maintained +F: drivers/remoteproc/ +F: Documentation/remoteproc.txt +F: include/linux/remoteproc.txt + RFKILL M: Johannes Berg <johannes@sipsolutions.net> L: linux-wireless@vger.kernel.org diff --git a/drivers/Kconfig b/drivers/Kconfig index 5afe5d1f199..27b34bf41d4 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -132,6 +132,8 @@ source "drivers/clocksource/Kconfig" source "drivers/iommu/Kconfig" +source "drivers/remoteproc/Kconfig" + source "drivers/virt/Kconfig" source "drivers/devfreq/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index c07be024b96..f1019b714f2 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -126,6 +126,7 @@ obj-y += clk/ obj-$(CONFIG_HWSPINLOCK) += hwspinlock/ obj-$(CONFIG_NFC) += nfc/ obj-$(CONFIG_IOMMU_SUPPORT) += iommu/ +obj-$(CONFIG_REMOTEPROC) += remoteproc/ # Virtualization drivers obj-$(CONFIG_VIRT_DRIVERS) += virt/ diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig new file mode 100644 index 00000000000..b250b15c068 --- /dev/null +++ b/drivers/remoteproc/Kconfig @@ -0,0 +1,3 @@ +# REMOTEPROC gets selected by whoever wants it +config REMOTEPROC + tristate diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile new file mode 100644 index 00000000000..2a5fd7992f5 --- /dev/null +++ b/drivers/remoteproc/Makefile @@ -0,0 +1,6 @@ +# +# Generic framework for controlling remote processors +# + +obj-$(CONFIG_REMOTEPROC) += remoteproc.o +remoteproc-y := remoteproc_core.o diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c new file mode 100644 index 00000000000..ad93d7d4ecb --- /dev/null +++ b/drivers/remoteproc/remoteproc_core.c @@ -0,0 +1,1410 @@ +/* + * Remote Processor Framework + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Copyright (C) 2011 Google, Inc. + * + * Ohad Ben-Cohen <ohad@wizery.com> + * Brian Swetland <swetland@google.com> + * Mark Grosen <mgrosen@ti.com> + * Fernando Guzman Lugo <fernando.lugo@ti.com> + * Suman Anna <s-anna@ti.com> + * Robert Tivy <rtivy@ti.com> + * Armando Uribe De Leon <x0095078@ti.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/dma-mapping.h> +#include <linux/firmware.h> +#include <linux/string.h> +#include <linux/debugfs.h> +#include <linux/remoteproc.h> +#include <linux/iommu.h> +#include <linux/klist.h> +#include <linux/elf.h> +#include <linux/virtio_ids.h> +#include <linux/virtio_ring.h> + +#include "remoteproc_internal.h" + +static void klist_rproc_get(struct klist_node *n); +static void klist_rproc_put(struct klist_node *n); + +/* + * klist of the available remote processors. + * + * We need this in order to support name-based lookups (needed by the + * rproc_get_by_name()). + * + * That said, we don't use rproc_get_by_name() anymore within the rpmsg + * framework. The use cases that do require its existence should be + * scrutinized, and hopefully migrated to rproc_boot() using device-based + * binding. + * + * If/when this materializes, we could drop the klist (and the by_name + * API). + */ +static DEFINE_KLIST(rprocs, klist_rproc_get, klist_rproc_put); + +typedef int (*rproc_handle_resources_t)(struct rproc *rproc, + struct fw_resource *rsc, int len); + +/* + * This is the IOMMU fault handler we register with the IOMMU API + * (when relevant; not all remote processors access memory through + * an IOMMU). + * + * IOMMU core will invoke this handler whenever the remote processor + * will try to access an unmapped device address. + * + * Currently this is mostly a stub, but it will be later used to trigger + * the recovery of the remote processor. + */ +static int rproc_iommu_fault(struct iommu_domain *domain, struct device *dev, + unsigned long iova, int flags) +{ + dev_err(dev, "iommu fault: da 0x%lx flags 0x%x\n", iova, flags); + + /* + * Let the iommu core know we're not really handling this fault; + * we just plan to use this as a recovery trigger. + */ + return -ENOSYS; +} + +static int rproc_enable_iommu(struct rproc *rproc) +{ + struct iommu_domain *domain; + struct device *dev = rproc->dev; + int ret; + + /* + * We currently use iommu_present() to decide if an IOMMU + * setup is needed. + * + * This works for simple cases, but will easily fail with + * platforms that do have an IOMMU, but not for this specific + * rproc. + * + * This will be easily solved by introducing hw capabilities + * that will be set by the remoteproc driver. + */ + if (!iommu_present(dev->bus)) { + dev_err(dev, "iommu not found\n"); + return -ENODEV; + } + + domain = iommu_domain_alloc(dev->bus); + if (!domain) { + dev_err(dev, "can't alloc iommu domain\n"); + return -ENOMEM; + } + + iommu_set_fault_handler(domain, rproc_iommu_fault); + + ret = iommu_attach_device(domain, dev); + if (ret) { + dev_err(dev, "can't attach iommu device: %d\n", ret); + goto free_domain; + } + + rproc->domain = domain; + + return 0; + +free_domain: + iommu_domain_free(domain); + return ret; +} + +static void rproc_disable_iommu(struct rproc *rproc) +{ + struct iommu_domain *domain = rproc->domain; + struct device *dev = rproc->dev; + + if (!domain) + return; + + iommu_detach_device(domain, dev); + iommu_domain_free(domain); + + return; +} + +/* + * Some remote processors will ask us to allocate them physically contiguous + * memory regions (which we call "carveouts"), and map them to specific + * device addresses (which are hardcoded in the firmware). + * + * They may then ask us to copy objects into specific device addresses (e.g. + * code/data sections) or expose us certain symbols in other device address + * (e.g. their trace buffer). + * + * This function is an internal helper with which we can go over the allocated + * carveouts and translate specific device address to kernel virtual addresses + * so we can access the referenced memory. + * + * Note: phys_to_virt(iommu_iova_to_phys(rproc->domain, da)) will work too, + * but only on kernel direct mapped RAM memory. Instead, we're just using + * here the output of the DMA API, which should be more correct. + */ +static void *rproc_da_to_va(struct rproc *rproc, u64 da, int len) +{ + struct rproc_mem_entry *carveout; + void *ptr = NULL; + + list_for_each_entry(carveout, &rproc->carveouts, node) { + int offset = da - carveout->da; + + /* try next carveout if da is too small */ + if (offset < 0) + continue; + + /* try next carveout if da is too large */ + if (offset + len > carveout->len) + continue; + + ptr = carveout->va + offset; + + break; + } + + return ptr; +} + +/** + * rproc_load_segments() - load firmware segments to memory + * @rproc: remote processor which will be booted using these fw segments + * @elf_data: the content of the ELF firmware image + * + * This function loads the firmware segments to memory, where the remote + * processor expects them. + * + * Some remote processors will expect their code and data to be placed + * in specific device addresses, and can't have them dynamically assigned. + * + * We currently support only those kind of remote processors, and expect + * the program header's paddr member to contain those addresses. We then go + * through the physically contiguous "carveout" memory regions which we + * allocated (and mapped) earlier on behalf of the remote processor, + * and "translate" device address to kernel addresses, so we can copy the + * segments where they are expected. + * + * Currently we only support remote processors that required carveout + * allocations and got them mapped onto their iommus. Some processors + * might be different: they might not have iommus, and would prefer to + * directly allocate memory for every segment/resource. This is not yet + * supported, though. + */ +static int rproc_load_segments(struct rproc *rproc, const u8 *elf_data) +{ + struct device *dev = rproc->dev; + struct elf32_hdr *ehdr; + struct elf32_phdr *phdr; + int i, ret = 0; + + ehdr = (struct elf32_hdr *)elf_data; + phdr = (struct elf32_phdr *)(elf_data + ehdr->e_phoff); + + /* go through the available ELF segments */ + for (i = 0; i < ehdr->e_phnum; i++, phdr++) { + u32 da = phdr->p_paddr; + u32 memsz = phdr->p_memsz; + u32 filesz = phdr->p_filesz; + void *ptr; + + if (phdr->p_type != PT_LOAD) + continue; + + dev_dbg(dev, "phdr: type %d da 0x%x memsz 0x%x filesz 0x%x\n", + phdr->p_type, da, memsz, filesz); + + if (filesz > memsz) { + dev_err(dev, "bad phdr filesz 0x%x memsz 0x%x\n", + filesz, memsz); + ret = -EINVAL; + break; + } + + /* grab the kernel address for this device address */ + ptr = rproc_da_to_va(rproc, da, memsz); + if (!ptr) { + dev_err(dev, "bad phdr da 0x%x mem 0x%x\n", da, memsz); + ret = -EINVAL; + break; + } + + /* put the segment where the remote processor expects it */ + if (phdr->p_filesz) + memcpy(ptr, elf_data + phdr->p_offset, filesz); + + /* + * Zero out remaining memory for this segment. + * + * This isn't strictly required since dma_alloc_coherent already + * did this for us. albeit harmless, we may consider removing + * this. + */ + if (memsz > filesz) + memset(ptr + filesz, 0, memsz - filesz); + } + + return ret; +} + +/** + * rproc_handle_virtio_hdr() - handle a virtio header resource + * @rproc: the remote processor + * @rsc: the resource descriptor + * + * The existence of this virtio hdr resource entry means that the firmware + * of this @rproc supports this virtio device. + * + * Currently we support only a single virtio device of type VIRTIO_ID_RPMSG, + * but the plan is to remove this limitation and support any number + * of virtio devices (and of any type). We'll also add support for dynamically + * adding (and removing) virtio devices over the rpmsg bus, but small + * firmwares that doesn't want to get involved with rpmsg will be able + * to simple use the resource table for this. + * + * At this point this virtio header entry is rather simple: it just + * announces the virtio device id and the supported virtio device features. + * The plan though is to extend this to include the vring information and + * the virtio config space, too (but first, some resource table overhaul + * is needed: move from fixed-sized to variable-length TLV entries). + * + * For now, the 'flags' member of the resource entry contains the virtio + * device id, the 'da' member contains the device features, and 'pa' is + * where we need to store the guest features once negotiation completes. + * As usual, the 'id' member of this resource contains the index of this + * resource type (i.e. is this the first virtio hdr entry, the 2nd, ...). + * + * Returns 0 on success, or an appropriate error code otherwise + */ +static int rproc_handle_virtio_hdr(struct rproc *rproc, struct fw_resource *rsc) +{ + struct rproc_vdev *rvdev; + + /* we only support VIRTIO_ID_RPMSG devices for now */ + if (rsc->flags != VIRTIO_ID_RPMSG) { + dev_warn(rproc->dev, "unsupported vdev: %d\n", rsc->flags); + return -EINVAL; + } + + /* we only support a single vdev per rproc for now */ + if (rsc->id || rproc->rvdev) { + dev_warn(rproc->dev, "redundant vdev entry: %s\n", rsc->name); + return -EINVAL; + } + + rvdev = kzalloc(sizeof(struct rproc_vdev), GFP_KERNEL); + if (!rvdev) + return -ENOMEM; + + /* remember the device features */ + rvdev->dfeatures = rsc->da; + + rproc->rvdev = rvdev; + rvdev->rproc = rproc; + + return 0; +} + +/** + * rproc_handle_vring() - handle a vring fw resource + * @rproc: the remote processor + * @rsc: the vring resource descriptor + * + * This resource entry requires allocation of non-cacheable memory + * for a virtio vring. Currently we only support two vrings per remote + * processor, required for the virtio rpmsg device. + * + * The 'len' member of @rsc should contain the number of buffers this vring + * support and 'da' should either contain the device address where + * the remote processor is expecting the vring, or indicate that + * dynamically allocation of the vring's device address is supported. + * + * Note: 'da' is currently not handled. This will be revised when the generic + * iommu-based DMA API will arrive, or a dynanic & non-iommu use case show + * up. Meanwhile, statically-addressed iommu-based images should use + * RSC_DEVMEM resource entries to map their require 'da' to the physical + * address of their base CMA region. + * + * Returns 0 on success, or an appropriate error code otherwise + */ +static int rproc_handle_vring(struct rproc *rproc, struct fw_resource *rsc) +{ + struct device *dev = rproc->dev; + struct rproc_vdev *rvdev = rproc->rvdev; + dma_addr_t dma; + int size, id = rsc->id; + void *va; + + /* no vdev is in place ? */ + if (!rvdev) { + dev_err(dev, "vring requested without a virtio dev entry\n"); + return -EINVAL; + } + + /* the firmware must provide the expected queue size */ + if (!rsc->len) { + dev_err(dev, "missing expected queue size\n"); + return -EINVAL; + } + + /* we currently support two vrings per rproc (for rx and tx) */ + if (id >= ARRAY_SIZE(rvdev->vring)) { + dev_err(dev, "%s: invalid vring id %d\n", rsc->name, id); + return -EINVAL; + } + + /* have we already allocated this vring id ? */ + if (rvdev->vring[id].len) { + dev_err(dev, "%s: duplicated id %d\n", rsc->name, id); + return -EINVAL; + } + + /* actual size of vring (in bytes) */ + size = PAGE_ALIGN(vring_size(rsc->len, AMP_VRING_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, size, &dma, GFP_KERNEL); + if (!va) { + dev_err(dev, "dma_alloc_coherent failed\n"); + return -ENOMEM; + } + + dev_dbg(dev, "vring%d: va %p dma %x qsz %d ring size %x\n", id, va, + dma, rsc->len, size); + + rvdev->vring[id].len = rsc->len; + rvdev->vring[id].va = va; + rvdev->vring[id].dma = dma; + + return 0; +} + +/** + * rproc_handle_trace() - handle a shared trace buffer resource + * @rproc: the remote processor + * @rsc: the trace resource descriptor + * + * In case the remote processor dumps trace logs into memory, + * export it via debugfs. + * + * Currently, the 'da' member of @rsc should contain the device address + * where the remote processor is dumping the traces. Later we could also + * support dynamically allocating this address using the generic + * DMA API (but currently there isn't a use case for that). + * + * Returns 0 on success, or an appropriate error code otherwise + */ +static int rproc_handle_trace(struct rproc *rproc, struct fw_resource *rsc) +{ + struct rproc_mem_entry *trace; + struct device *dev = rproc->dev; + void *ptr; + char name[15]; + + /* 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) { + dev_err(dev, "kzalloc trace failed\n"); + return -ENOMEM; + } + + /* set the trace buffer dma properties */ + trace->len = rsc->len; + trace->va = ptr; + + /* 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; + kfree(trace); + return -EINVAL; + } + + list_add_tail(&trace->node, &rproc->traces); + + rproc->num_traces++; + + dev_dbg(dev, "%s added: va %p, da 0x%llx, len 0x%x\n", name, ptr, + rsc->da, rsc->len); + + return 0; +} + +/** + * rproc_handle_devmem() - handle devmem resource entry + * @rproc: remote processor handle + * @rsc: the devmem resource entry + * + * Remote processors commonly need to access certain on-chip peripherals. + * + * Some of these remote processors access memory via an iommu device, + * and might require us to configure their iommu before they can access + * the on-chip peripherals they need. + * + * This resource entry is a request to map such a peripheral device. + * + * These devmem entries will contain the physical address of the device in + * the 'pa' member. If a specific device address is expected, then 'da' will + * contain it (currently this is the only use case supported). 'len' will + * contain the size of the physical region we need to map. + * + * Currently we just "trust" those devmem entries to contain valid physical + * addresses, but this is going to change: we want the implementations to + * tell us ranges of physical addresses the firmware is allowed to request, + * and not allow firmwares to request access to physical addresses that + * are outside those ranges. + */ +static int rproc_handle_devmem(struct rproc *rproc, struct fw_resource *rsc) +{ + struct rproc_mem_entry *mapping; + int ret; + + /* no point in handling this resource without a valid iommu domain */ + if (!rproc->domain) + return -EINVAL; + + mapping = kzalloc(sizeof(*mapping), GFP_KERNEL); + if (!mapping) { + dev_err(rproc->dev, "kzalloc mapping failed\n"); + return -ENOMEM; + } + + ret = iommu_map(rproc->domain, rsc->da, rsc->pa, rsc->len, rsc->flags); + if (ret) { + dev_err(rproc->dev, "failed to map devmem: %d\n", ret); + goto out; + } + + /* + * We'll need this info later when we'll want to unmap everything + * (e.g. on shutdown). + * + * 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; + list_add_tail(&mapping->node, &rproc->mappings); + + dev_dbg(rproc->dev, "mapped devmem pa 0x%llx, da 0x%llx, len 0x%x\n", + rsc->pa, rsc->da, rsc->len); + + return 0; + +out: + kfree(mapping); + return ret; +} + +/** + * rproc_handle_carveout() - handle phys contig memory allocation requests + * @rproc: rproc handle + * @rsc: the resource entry + * + * 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_resource *rsc) +{ + struct rproc_mem_entry *carveout, *mapping; + struct device *dev = rproc->dev; + dma_addr_t dma; + void *va; + int ret; + + mapping = kzalloc(sizeof(*mapping), GFP_KERNEL); + if (!mapping) { + dev_err(dev, "kzalloc mapping failed\n"); + return -ENOMEM; + } + + carveout = kzalloc(sizeof(*carveout), GFP_KERNEL); + if (!carveout) { + dev_err(dev, "kzalloc carveout failed\n"); + ret = -ENOMEM; + goto free_mapping; + } + + va = dma_alloc_coherent(dev, rsc->len, &dma, GFP_KERNEL); + if (!va) { + dev_err(dev, "failed to dma alloc carveout: %d\n", rsc->len); + ret = -ENOMEM; + goto free_carv; + |