diff options
-rw-r--r-- | drivers/remoteproc/Kconfig | 27 | ||||
-rw-r--r-- | drivers/remoteproc/Makefile | 1 | ||||
-rw-r--r-- | drivers/remoteproc/da8xx_remoteproc.c | 324 | ||||
-rw-r--r-- | drivers/remoteproc/remoteproc_core.c | 211 | ||||
-rw-r--r-- | drivers/remoteproc/remoteproc_elf_loader.c | 100 | ||||
-rw-r--r-- | drivers/remoteproc/remoteproc_internal.h | 15 | ||||
-rw-r--r-- | drivers/remoteproc/remoteproc_virtio.c | 79 | ||||
-rw-r--r-- | drivers/remoteproc/ste_modem_rproc.c | 45 | ||||
-rw-r--r-- | include/linux/remoteproc.h | 13 |
9 files changed, 677 insertions, 138 deletions
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig index c6d77e20622c..d4d377c40ec9 100644 --- a/drivers/remoteproc/Kconfig +++ b/drivers/remoteproc/Kconfig @@ -4,13 +4,15 @@ menu "Remoteproc drivers" config REMOTEPROC tristate depends on HAS_DMA + select CRC32 select FW_LOADER select VIRTIO + select VIRTUALIZATION config OMAP_REMOTEPROC tristate "OMAP remoteproc support" depends on HAS_DMA - depends on ARCH_OMAP4 + depends on ARCH_OMAP4 || SOC_OMAP5 depends on OMAP_IOMMU depends on OMAP_MBOX_FWK select REMOTEPROC @@ -38,4 +40,27 @@ config STE_MODEM_RPROC This can be either built-in or a loadable module. If unsure say N. +config DA8XX_REMOTEPROC + tristate "DA8xx/OMAP-L13x remoteproc support" + depends on ARCH_DAVINCI_DA8XX + select CMA + select REMOTEPROC + select RPMSG + help + Say y here to support DA8xx/OMAP-L13x remote processors via the + remote processor framework. + + You want to say y here in order to enable AMP + use-cases to run on your platform (multimedia codecs are + offloaded to remote DSP processors using this framework). + + This module controls the name of the firmware file that gets + loaded on the DSP. This file must reside in the /lib/firmware + directory. It can be specified via the module parameter + da8xx_fw_name=<filename>, and if not specified will default to + "rproc-dsp-fw". + + It's safe to say n here if you're not interested in multimedia + offloading. + endmenu diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile index 391b65181c05..ac2ff75686d2 100644 --- a/drivers/remoteproc/Makefile +++ b/drivers/remoteproc/Makefile @@ -9,3 +9,4 @@ remoteproc-y += remoteproc_virtio.o remoteproc-y += remoteproc_elf_loader.o obj-$(CONFIG_OMAP_REMOTEPROC) += omap_remoteproc.o obj-$(CONFIG_STE_MODEM_RPROC) += ste_modem_rproc.o +obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o diff --git a/drivers/remoteproc/da8xx_remoteproc.c b/drivers/remoteproc/da8xx_remoteproc.c new file mode 100644 index 000000000000..9b2e60afa1a6 --- /dev/null +++ b/drivers/remoteproc/da8xx_remoteproc.c @@ -0,0 +1,324 @@ +/* + * Remote processor machine-specific module for DA8XX + * + * Copyright (C) 2013 Texas Instruments, Inc. + * + * 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. + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/remoteproc.h> + +#include <mach/clock.h> /* for davinci_clk_reset_assert/deassert() */ + +#include "remoteproc_internal.h" + +static char *da8xx_fw_name; +module_param(da8xx_fw_name, charp, S_IRUGO); +MODULE_PARM_DESC(da8xx_fw_name, + "\n\t\tName of DSP firmware file in /lib/firmware" + " (if not specified defaults to 'rproc-dsp-fw')"); + +/* + * OMAP-L138 Technical References: + * http://www.ti.com/product/omap-l138 + */ +#define SYSCFG_CHIPSIG0 BIT(0) +#define SYSCFG_CHIPSIG1 BIT(1) +#define SYSCFG_CHIPSIG2 BIT(2) +#define SYSCFG_CHIPSIG3 BIT(3) +#define SYSCFG_CHIPSIG4 BIT(4) + +/** + * struct da8xx_rproc - da8xx remote processor instance state + * @rproc: rproc handle + * @dsp_clk: placeholder for platform's DSP clk + * @ack_fxn: chip-specific ack function for ack'ing irq + * @irq_data: ack_fxn function parameter + * @chipsig: virt ptr to DSP interrupt registers (CHIPSIG & CHIPSIG_CLR) + * @bootreg: virt ptr to DSP boot address register (HOST1CFG) + * @irq: irq # used by this instance + */ +struct da8xx_rproc { + struct rproc *rproc; + struct clk *dsp_clk; + void (*ack_fxn)(struct irq_data *data); + struct irq_data *irq_data; + void __iomem *chipsig; + void __iomem *bootreg; + int irq; +}; + +/** + * handle_event() - inbound virtqueue message workqueue function + * + * This function is registered as a kernel thread and is scheduled by the + * kernel handler. + */ +static irqreturn_t handle_event(int irq, void *p) +{ + struct rproc *rproc = (struct rproc *)p; + + /* Process incoming buffers on all our vrings */ + rproc_vq_interrupt(rproc, 0); + rproc_vq_interrupt(rproc, 1); + + return IRQ_HANDLED; +} + +/** + * da8xx_rproc_callback() - inbound virtqueue message handler + * + * This handler is invoked directly by the kernel whenever the remote + * core (DSP) has modified the state of a virtqueue. There is no + * "payload" message indicating the virtqueue index as is the case with + * mailbox-based implementations on OMAP4. As such, this handler "polls" + * each known virtqueue index for every invocation. + */ +static irqreturn_t da8xx_rproc_callback(int irq, void *p) +{ + struct rproc *rproc = (struct rproc *)p; + struct da8xx_rproc *drproc = (struct da8xx_rproc *)rproc->priv; + u32 chipsig; + + chipsig = readl(drproc->chipsig); + if (chipsig & SYSCFG_CHIPSIG0) { + /* Clear interrupt level source */ + writel(SYSCFG_CHIPSIG0, drproc->chipsig + 4); + + /* + * ACK intr to AINTC. + * + * It has already been ack'ed by the kernel before calling + * this function, but since the ARM<->DSP interrupts in the + * CHIPSIG register are "level" instead of "pulse" variety, + * we need to ack it after taking down the level else we'll + * be called again immediately after returning. + */ + drproc->ack_fxn(drproc->irq_data); + + return IRQ_WAKE_THREAD; + } + + return IRQ_HANDLED; +} + +static int da8xx_rproc_start(struct rproc *rproc) +{ + struct device *dev = rproc->dev.parent; + struct da8xx_rproc *drproc = (struct da8xx_rproc *)rproc->priv; + struct clk *dsp_clk = drproc->dsp_clk; + + /* hw requires the start (boot) address be on 1KB boundary */ + if (rproc->bootaddr & 0x3ff) { + dev_err(dev, "invalid boot address: must be aligned to 1KB\n"); + + return -EINVAL; + } + + writel(rproc->bootaddr, drproc->bootreg); + + clk_enable(dsp_clk); + davinci_clk_reset_deassert(dsp_clk); + + return 0; +} + +static int da8xx_rproc_stop(struct rproc *rproc) +{ + struct da8xx_rproc *drproc = rproc->priv; + + clk_disable(drproc->dsp_clk); + + return 0; +} + +/* kick a virtqueue */ +static void da8xx_rproc_kick(struct rproc *rproc, int vqid) +{ + struct da8xx_rproc *drproc = (struct da8xx_rproc *)rproc->priv; + + /* Interupt remote proc */ + writel(SYSCFG_CHIPSIG2, drproc->chipsig); +} + +static struct rproc_ops da8xx_rproc_ops = { + .start = da8xx_rproc_start, + .stop = da8xx_rproc_stop, + .kick = da8xx_rproc_kick, +}; + +static int reset_assert(struct device *dev) +{ + struct clk *dsp_clk; + + dsp_clk = clk_get(dev, NULL); + if (IS_ERR(dsp_clk)) { + dev_err(dev, "clk_get error: %ld\n", PTR_ERR(dsp_clk)); + return PTR_RET(dsp_clk); + } + + davinci_clk_reset_assert(dsp_clk); + clk_put(dsp_clk); + + return 0; +} + +static int da8xx_rproc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct da8xx_rproc *drproc; + struct rproc *rproc; + struct irq_data *irq_data; + struct resource *bootreg_res; + struct resource *chipsig_res; + struct clk *dsp_clk; + void __iomem *chipsig; + void __iomem *bootreg; + int irq; + int ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "platform_get_irq(pdev, 0) error: %d\n", irq); + return irq; + } + + irq_data = irq_get_irq_data(irq); + if (!irq_data) { + dev_err(dev, "irq_get_irq_data(%d): NULL\n", irq); + return -EINVAL; + } + + bootreg_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!bootreg_res) { + dev_err(dev, + "platform_get_resource(IORESOURCE_MEM, 0): NULL\n"); + return -EADDRNOTAVAIL; + } + + chipsig_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!chipsig_res) { + dev_err(dev, + "platform_get_resource(IORESOURCE_MEM, 1): NULL\n"); + return -EADDRNOTAVAIL; + } + + bootreg = devm_ioremap_resource(dev, bootreg_res); + if (IS_ERR(bootreg)) + return PTR_ERR(bootreg); + + chipsig = devm_ioremap_resource(dev, chipsig_res); + if (IS_ERR(chipsig)) + return PTR_ERR(chipsig); + + dsp_clk = devm_clk_get(dev, NULL); + if (IS_ERR(dsp_clk)) { + dev_err(dev, "clk_get error: %ld\n", PTR_ERR(dsp_clk)); + + return PTR_ERR(dsp_clk); + } + + rproc = rproc_alloc(dev, "dsp", &da8xx_rproc_ops, da8xx_fw_name, + sizeof(*drproc)); + if (!rproc) + return -ENOMEM; + + drproc = rproc->priv; + drproc->rproc = rproc; + + platform_set_drvdata(pdev, rproc); + + /* everything the ISR needs is now setup, so hook it up */ + ret = devm_request_threaded_irq(dev, irq, da8xx_rproc_callback, + handle_event, 0, "da8xx-remoteproc", + rproc); + if (ret) { + dev_err(dev, "devm_request_threaded_irq error: %d\n", ret); + goto free_rproc; + } + + /* + * rproc_add() can end up enabling the DSP's clk with the DSP + * *not* in reset, but da8xx_rproc_start() needs the DSP to be + * held in reset at the time it is called. + */ + ret = reset_assert(dev); + if (ret) + goto free_rproc; + + drproc->chipsig = chipsig; + drproc->bootreg = bootreg; + drproc->ack_fxn = irq_data->chip->irq_ack; + drproc->irq_data = irq_data; + drproc->irq = irq; + drproc->dsp_clk = dsp_clk; + + ret = rproc_add(rproc); + if (ret) { + dev_err(dev, "rproc_add failed: %d\n", ret); + goto free_rproc; + } + + return 0; + +free_rproc: + rproc_put(rproc); + + return ret; +} + +static int da8xx_rproc_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rproc *rproc = platform_get_drvdata(pdev); + struct da8xx_rproc *drproc = (struct da8xx_rproc *)rproc->priv; + + /* + * It's important to place the DSP in reset before going away, + * since a subsequent insmod of this module may enable the DSP's + * clock before its program/boot-address has been loaded and + * before this module's probe has had a chance to reset the DSP. + * Without the reset, the DSP can lockup permanently when it + * begins executing garbage. + */ + reset_assert(dev); + + /* + * The devm subsystem might end up releasing things before + * freeing the irq, thus allowing an interrupt to sneak in while + * the device is being removed. This should prevent that. + */ + disable_irq(drproc->irq); + + devm_clk_put(dev, drproc->dsp_clk); + + rproc_del(rproc); + rproc_put(rproc); + + return 0; +} + +static struct platform_driver da8xx_rproc_driver = { + .probe = da8xx_rproc_probe, + .remove = da8xx_rproc_remove, + .driver = { + .name = "davinci-rproc", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(da8xx_rproc_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DA8XX Remote Processor control driver"); diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c index 814af5ab8a72..022dc635d01e 100644 --- a/drivers/remoteproc/remoteproc_core.c +++ b/drivers/remoteproc/remoteproc_core.c @@ -37,6 +37,7 @@ #include <linux/iommu.h> #include <linux/idr.h> #include <linux/elf.h> +#include <linux/crc32.h> #include <linux/virtio_ids.h> #include <linux/virtio_ring.h> #include <asm/byteorder.h> @@ -45,7 +46,8 @@ typedef int (*rproc_handle_resources_t)(struct rproc *rproc, struct resource_table *table, int len); -typedef int (*rproc_handle_resource_t)(struct rproc *rproc, void *, int avail); +typedef int (*rproc_handle_resource_t)(struct rproc *rproc, + void *, int offset, int avail); /* Unique indices for remoteproc devices */ static DEFINE_IDA(rproc_dev_index); @@ -192,6 +194,7 @@ 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; @@ -202,7 +205,6 @@ int rproc_alloc_vring(struct rproc_vdev *rvdev, int i) /* * Allocate non-cacheable memory for the vring. In the future * this call will also configure the IOMMU for us - * TODO: let the rproc know the da of this vring */ va = dma_alloc_coherent(dev->parent, size, &dma, GFP_KERNEL); if (!va) { @@ -213,7 +215,6 @@ int rproc_alloc_vring(struct rproc_vdev *rvdev, int i) /* * Assign an rproc-wide unique index for this vring * TODO: assign a notifyid for rvdev updates as well - * TODO: let the rproc know the notifyid of this vring * TODO: support predefined notifyids (via resource table) */ ret = idr_alloc(&rproc->notifyids, rvring, 0, 0, GFP_KERNEL); @@ -224,9 +225,6 @@ int rproc_alloc_vring(struct rproc_vdev *rvdev, int i) } notifyid = ret; - /* Store largest notifyid */ - rproc->max_notifyid = max(rproc->max_notifyid, notifyid); - dev_dbg(dev, "vring%d: va %p dma %llx size %x idr %d\n", i, va, (unsigned long long)dma, size, notifyid); @@ -234,6 +232,15 @@ int rproc_alloc_vring(struct rproc_vdev *rvdev, int i) 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; + rsc->vring[i].notifyid = notifyid; return 0; } @@ -268,25 +275,20 @@ rproc_parse_vring(struct rproc_vdev *rvdev, struct fw_rsc_vdev *rsc, int i) return 0; } -static int rproc_max_notifyid(int id, void *p, void *data) -{ - int *maxid = data; - *maxid = max(*maxid, id); - return 0; -} - 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 maxid = 0; + 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); - /* Find the largest remaining notifyid */ - idr_for_each(&rproc->notifyids, rproc_max_notifyid, &maxid); - rproc->max_notifyid = maxid; + /* reset resource entry info */ + rsc = (void *)rproc->table_ptr + rvring->rvdev->rsc_offset; + rsc->vring[idx].da = 0; + rsc->vring[idx].notifyid = -1; } /** @@ -317,7 +319,7 @@ void rproc_free_vring(struct rproc_vring *rvring) * Returns 0 on success, or an appropriate error code otherwise */ static int rproc_handle_vdev(struct rproc *rproc, struct fw_rsc_vdev *rsc, - int avail) + int offset, int avail) { struct device *dev = &rproc->dev; struct rproc_vdev *rvdev; @@ -358,8 +360,8 @@ static int rproc_handle_vdev(struct rproc *rproc, struct fw_rsc_vdev *rsc, goto free_rvdev; } - /* remember the device features */ - rvdev->dfeatures = rsc->dfeatures; + /* remember the resource offset*/ + rvdev->rsc_offset = offset; list_add_tail(&rvdev->node, &rproc->rvdevs); @@ -394,7 +396,7 @@ free_rvdev: * Returns 0 on success, or an appropriate error code otherwise */ static int rproc_handle_trace(struct rproc *rproc, struct fw_rsc_trace *rsc, - int avail) + int offset, int avail) { struct rproc_mem_entry *trace; struct device *dev = &rproc->dev; @@ -476,7 +478,7 @@ static int rproc_handle_trace(struct rproc *rproc, struct fw_rsc_trace *rsc, * are outside those ranges. */ static int rproc_handle_devmem(struct rproc *rproc, struct fw_rsc_devmem *rsc, - int avail) + int offset, int avail) { struct rproc_mem_entry *mapping; struct device *dev = &rproc->dev; @@ -549,7 +551,9 @@ out: * 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 avail) + struct fw_rsc_carveout *rsc, + int offset, int avail) + { struct rproc_mem_entry *carveout, *mapping; struct device *dev = &rproc->dev; @@ -671,28 +675,45 @@ free_carv: return ret; } +static int rproc_count_vrings(struct rproc *rproc, struct fw_rsc_vdev *rsc, + int offset, int avail) +{ + /* Summarize the number of notification IDs */ + rproc->max_notifyid += rsc->num_of_vrings; + + return 0; +} + /* * A lookup table for resource handlers. The indices are defined in * enum fw_resource_type. */ -static rproc_handle_resource_t rproc_handle_rsc[] = { +static rproc_handle_resource_t rproc_loading_handlers[RSC_LAST] = { [RSC_CARVEOUT] = (rproc_handle_resource_t)rproc_handle_carveout, [RSC_DEVMEM] = (rproc_handle_resource_t)rproc_handle_devmem, [RSC_TRACE] = (rproc_handle_resource_t)rproc_handle_trace, [RSC_VDEV] = NULL, /* VDEVs were handled upon registrarion */ }; +static rproc_handle_resource_t rproc_vdev_handler[RSC_LAST] = { + [RSC_VDEV] = (rproc_handle_resource_t)rproc_handle_vdev, +}; + +static rproc_handle_resource_t rproc_count_vrings_handler[RSC_LAST] = { + [RSC_VDEV] = (rproc_handle_resource_t)rproc_count_vrings, +}; + /* handle firmware resource entries before booting the remote processor */ -static int -rproc_handle_boot_rsc(struct rproc *rproc, struct resource_table *table, int len) +static int rproc_handle_resources(struct rproc *rproc, int len, + rproc_handle_resource_t handlers[RSC_LAST]) { struct device *dev = &rproc->dev; rproc_handle_resource_t handler; int ret = 0, i; - for (i = 0; i < table->num; i++) { - int offset = table->offset[i]; - struct fw_rsc_hdr *hdr = (void *)table + offset; + for (i = 0; i < rproc->table_ptr->num; i++) { + int offset = rproc->table_ptr->offset[i]; + struct fw_rsc_hdr *hdr = (void *)rproc->table_ptr + offset; int avail = len - offset - sizeof(*hdr); void *rsc = (void *)hdr + sizeof(*hdr); @@ -709,45 +730,11 @@ rproc_handle_boot_rsc(struct rproc *rproc, struct resource_table *table, int len continue; } - handler = rproc_handle_rsc[hdr->type]; + handler = handlers[hdr->type]; if (!handler) continue; - ret = handler(rproc, rsc, avail); - if (ret) - break; - } - - return ret; -} - -/* handle firmware resource entries while registering the remote processor */ -static int -rproc_handle_virtio_rsc(struct rproc *rproc, struct resource_table *table, int len) -{ - struct device *dev = &rproc->dev; - int ret = 0, i; - - for (i = 0; i < table->num; i++) { - int offset = table->offset[i]; - struct fw_rsc_hdr *hdr = (void *)table + offset; - int avail = len - offset - sizeof(*hdr); - struct fw_rsc_vdev *vrsc; - - /* make sure table isn't truncated */ - if (avail < 0) { - dev_err(dev, "rsc table is truncated\n"); - return -EINVAL; - } - - dev_dbg(dev, "%s: rsc type %d\n", __func__, hdr->type); - - if (hdr->type != RSC_VDEV) - continue; - - vrsc = (struct fw_rsc_vdev *)hdr->data; - - ret = rproc_handle_vdev(rproc, vrsc, avail); + ret = handler(rproc, rsc, offset + sizeof(*hdr), avail); if (ret) break; } @@ -805,9 +792,12 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) { struct device *dev = &rproc->dev; const char *name = rproc->firmware; - struct resource_table *table; + struct resource_table *table, *loaded_table; int ret, tablesz; + if (!rproc->table_ptr) + return -ENOMEM; + ret = rproc_fw_sanity_check(rproc, fw); if (ret) return ret; @@ -833,8 +823,15 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) goto clean_up; } + /* Verify that resource table in loaded fw is unchanged */ + if (rproc->table_csum != crc32(0, table, tablesz)) { + dev_err(dev, "resource checksum failed, fw changed?\n"); + ret = -EINVAL; + goto clean_up; + } + /* handle fw resources which are required to boot rproc */ - ret = rproc_handle_boot_rsc(rproc, table, tablesz); + ret = rproc_handle_resources(rproc, tablesz, rproc_loading_handlers); if (ret) { dev_err(dev, "Failed to process resources: %d\n", ret); goto clean_up; @@ -847,6 +844,19 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) goto clean_up; } + /* + * The starting device has been given the rproc->cached_table as the + * resource table. The address of the vring along with the other + * allocated resources (carveouts etc) is stored in cached_table. + * In order to pass this information to the remote device we must + * copy this information to device memory. + */ + loaded_table = rproc_find_loaded_rsc_table(rproc, fw); + if (!loaded_table) + goto clean_up; + + memcpy(loaded_table, rproc->cached_table, tablesz); + /* power up the remote processor */ ret = rproc->ops->start(rproc); if (ret) { @@ -854,6 +864,13 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) goto clean_up; } + /* + * Update table_ptr so that all subsequent vring allocations and + * virtio fields manipulation update the actual loaded resource table + * in device memory. + */ + rproc->table_ptr = loaded_table; + rproc->state = RPROC_RUNNING; dev_info(dev, "remote processor %s is now up\n", rproc->name); @@ -888,11 +905,30 @@ static void rproc_fw_config_virtio(const struct firmware *fw, void *context) if (!table) goto out; - /* look for virtio devices and register them */ - ret = rproc_handle_virtio_rsc(rproc, table, tablesz); + rproc->table_csum = crc32(0, table, tablesz); + + /* + * Create a copy of the resource table. When a virtio device starts + * and calls vring_new_virtqueue() the address of the allocated vring + * will be stored in the cached_table. Before the device is started, + * cached_table will be copied into devic memory. + */ + rproc->cached_table = kmalloc(tablesz, GFP_KERNEL); + if (!rproc->cached_table) + goto out; + + memcpy(rproc->cached_table, table, tablesz); + rproc->table_ptr = rproc->cached_table; + + /* count the number of notify-ids */ + rproc->max_notifyid = -1; + ret = rproc_handle_resources(rproc, tablesz, rproc_count_vrings_handler); if (ret) goto out; + /* look for virtio devices and register them */ + ret = rproc_handle_resources(rproc, tablesz, rproc_vdev_handler); + out: release_firmware(fw); /* allow rproc_del() contexts, if any, to proceed */ @@ -950,6 +986,9 @@ int rproc_trigger_recovery(struct rproc *rproc) /* wait until there is no more rproc users */ wait_for_completion(&rproc->crash_comp); + /* Free the copy of the resource table */ + kfree(rproc->cached_table); + return rproc_add_virtio_devices(rproc); } @@ -1105,6 +1144,9 @@ void rproc_shutdown(struct rproc *rproc) rproc_disable_iommu(rproc); + /* Give the next start a clean resource table */ + rproc->table_ptr = rproc->cached_table; + /* if in crash state, unlock crash handler */ if (rproc->state == RPROC_CRASHED) complete_all(&rproc->crash_comp); @@ -1196,11 +1238,11 @@ static struct device_type rproc_type = { * @dev: the underlying device * @name: name of this remote processor * @ops: platform-specific handlers (mainly start/stop) - * @firmware: name of firmware file to load + * @firmware: name of firmware file to load, can be NULL * @len: length of private data needed by the rproc driver (in bytes) * * Allocates a new remote processor handle, but does not register - * it yet. + * it yet. if @firmware is NULL, a default name is used. * * This function should be used by rproc implementations during initialization * of the remote processor. @@ -1219,19 +1261,39 @@ struct rproc *rproc_alloc(struct device *dev, const char *name, const char *firmware, int len) { struct rproc *rproc; + char *p, *template = "rproc-%s-fw"; + int name_len = 0; if (!dev || !name || !ops) return NULL; - rproc = kzalloc(sizeof(struct rproc) + len, GFP_KERNEL); + if (!firmware) + /* + * Make room for default firmware name (minus %s plus '\0'). + * If the caller didn't pass in a firmware name then + * construct a default name. We're already glomming 'len' + * bytes onto the end of the struct rproc allocation, so do + * a few more for the default firmware name (but only if + * the caller doesn't pass one). + */ + name_len = strlen(name) + strlen(template) - 2 + 1; + + rproc = kzalloc(sizeof(struct rproc) + len + name_len, GFP_KERNEL); if (!rproc) { dev_err(dev, "%s: kzalloc failed\n", __func__); return NULL; } + if (!firmware) { + p = (char *)rproc + sizeof(struct rproc) + len; + snprintf(p, name_len, template, name); + } else { + p = (char *)firmware; + } + + rproc->firmware = p; rproc->name = name; rproc->ops = ops; - rproc->firmware = firmware; rproc->priv = &rproc[1]; device_initialize(&rproc->dev); @@ -1315,6 +1377,9 @@ int rproc_del(struct rproc *rproc) list_for_each_entry_safe(rvdev, tmp, &rproc->rvdevs, node) rproc_remove_virtio_dev(rvdev); + /* Free the copy of the resource table */ + kfree(rproc->cached_table); + device_del(&rproc->dev); return 0; diff --git a/drivers/remoteproc/remoteproc_elf_loader.c b/drivers/remoteproc/remoteproc_elf_loader.c index 0d36f94ab51d..ce283a5b42a1 100644 --- a/drivers/remoteproc/remoteproc_elf_loader.c +++ b/drivers/remoteproc/remoteproc_elf_loader.c @@ -208,41 +208,22 @@ rproc_elf_load_segments(struct rproc *rproc, const struct firmware *fw) return ret; } -/** - * rproc_elf_find_rsc_table() - find the resource table - * @rproc: the rproc handle - * @fw: the ELF firmware image - * @tablesz: place holder for providing back the table size - * - * This function finds the resource table inside the remote processor's - * firmware. It is used both upon the registration of @rproc (in order - * to look for and register the supported virito devices), and when the - * @rproc is booted. - * - * Returns the pointer to the resource table if it is found, and write its - * size into @tablesz. If a valid table isn't found, NULL is returned - * (and @tablesz isn't set). - */ -static struct resource_table * -rproc_elf_find_rsc_table(struct rproc *rproc, const struct firmware *fw, - int *tablesz) +static struct elf32_shdr * +find_table(struct device *dev, struct elf32_hdr *ehdr, size_t fw_size) { - struct elf32_hdr *ehdr; struct elf32_shdr *shdr; + int i; const char *name_table; - struct device *dev = &rproc->dev; struct resource_table *table = NULL; - int i; - const u8 *elf_data = fw->data; + const u8 *elf_data = (void *)ehdr; - ehdr = (struct elf32_hdr *)elf_data; + /* look for the resource table and handle it */ shdr = (struct elf32_shdr *)(elf_data + ehdr->e_shoff); name_table = elf_data + shdr[ehdr->e_shstrndx].sh_offset; - /* look for the resource table and handle it */ for (i = 0; i < ehdr->e_shnum; i++, shdr++) { - int size = shdr->sh_size; - int offset = shdr->sh_offset; + u32 size = shdr->sh_size; + u32 offset = shdr->sh_offset; if (strcmp(name_table + shdr->sh_name, ".resource_table")) continue; @@ -250,7 +231,7 @@ rproc_elf_find_rsc_table(struct rproc *rproc, const struct firmware *fw, table = (struct resource_table *)(elf_data + offset); /* make sure we have the entire table */ - if (offset + size > fw->size) { + if (offset + size > fw_size || offset + size < size) { dev_err(dev, "resource table truncated\n"); return NULL; } @@ -280,16 +261,77 @@ rproc_elf_find_rsc_table(struct rproc *rproc, const struct firmware *fw, return NULL; } - *tablesz = shdr->sh_size; - break; + return shdr; } + return NULL; +} + +/** + * rproc_elf_find_rsc_table() - find the resource table + * @rproc: the rproc handle + * @fw: the ELF firmware image + * @tablesz: place holder for providing back the table size + * + * This function finds the resource table inside the remote processor's + * firmware. It is used both upon the registration of @rproc (in order + * to look for and register the supported virito devices), and when the + * @rproc is booted. + * + * Returns the pointer to the resource table if it is found, and write its + * size into @tablesz. If a valid table isn't found, NULL is returned + * (and @tablesz isn't set). + */ +static struct resource_table * +rproc_elf_find_rsc_table(struct rproc *rproc, const struct firmware *fw, + int *tablesz) +{ + struct elf32_hdr *ehdr; + struct elf32_shdr *shdr; + struct device *dev = &rproc->dev; + struct resource_table *table = NULL; + const u8 *elf_data = fw->data; + + ehdr = (struct elf32_hdr *)elf_data; + + shdr = find_table(dev, ehdr, fw->size); + if (!shdr) + return NULL; + + table = (struct resource_table *)(elf_data + shdr->sh_offset); + *tablesz = shdr->sh_size; + return table; } +/** + * rproc_elf_find_loaded_rsc_table() - find the loaded resource table + * @rproc: the rproc handle + * @fw: the ELF firmware image + * + * This function finds the location of the loaded resource table. Don't + * call this function if the table wasn't loaded yet - it's a bug if you do. + * + * Returns the pointer to the resource table if it is found or NULL otherwise. + * If the table wasn't loaded yet the result is unspecified. + */ +static struct resource_table * +rproc_elf_find_loaded_rsc_table(struct rproc *rproc, const struct firmware *fw) +{ + struct elf32_hdr *ehdr = (struct elf32_hdr *)fw->data; + struct elf32_shdr *shdr; + + shdr = find_table(&rproc->dev, ehdr, fw->size); + if (!shdr) + return NULL; + + return rproc_da_to_va(rproc, shdr->sh_addr, shdr->sh_size); +} + const struct rproc_fw_ops rproc_elf_fw_ops = { .load = rproc_elf_load_segments, .find_rsc_table = rproc_elf_find_rsc_table, + .find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table, .sanity_check = rproc_elf_sanity_check, .get_boot_addr = rproc_elf_get_boot_addr }; diff --git a/drivers/remoteproc/remoteproc_internal.h b/drivers/remoteproc/remoteproc_internal.h index 7bb66482d061..157e762c1571 100644 --- a/drivers/remoteproc/remoteproc_internal.h +++ b/drivers/remoteproc/remoteproc_internal.h @@ -27,7 +27,8 @@ struct rproc; /** * struct rproc_fw_ops - firmware format specific operations. - * @find_rsc_table: finds the resource table inside the firmware image + * @find_rsc_table: find the resource table inside the firmware image + * @find_loaded_rsc_table: find the loaded resouce table * @load: load firmeware to memory, where the remote processor * expects to find it * @sanity_check: sanity check the fw image @@ -37,6 +38,8 @@ struct rproc_fw_ops { struct resource_table *(*find_rsc_table) (struct rproc *rproc, const struct firmware *fw, int *tablesz); + struct resource_table *(*find_loaded_rsc_table)(struct rproc *rproc, + const struct firmware *fw); int (*load)(struct rproc *rproc, const struct firmware *fw); int (*sanity_check)(struct rproc *rproc, const struct firmware *fw); u32 (*get_boot_addr)(struct rproc *rproc, const struct firmware *fw); @@ -102,6 +105,16 @@ struct resource_table *rproc_find_rsc_table(struct rproc *rproc, return NULL; } +static inline +struct resource_table *rproc_find_loaded_rsc_table(struct rproc *rproc, + const struct firmware *fw) +{ + if (rproc->fw_ops->find_loaded_rsc_table) + return rproc->fw_ops->find_loaded_rsc_table(rproc, fw); + + return NULL; +} + extern const struct rproc_fw_ops rproc_elf_fw_ops; #endif /* REMOTEPROC_INTERNAL_H */ diff --git a/drivers/remoteproc/remoteproc_virtio.c b/drivers/remoteproc/remoteproc_virtio.c index afed9b7731c4..b09c75c21b60 100644 --- a/drivers/remoteproc/remoteproc_virtio.c +++ b/drivers/remoteproc/remoteproc_virtio.c @@ -173,25 +173,35 @@ error: return ret; } -/* - * We don't support yet real virtio status semantics. - * - * The plan is to provide this via the VDEV resource entry - * which is part of the firmware: this way the remote processor - * will be able to access the status values as set by us. - */ static u8 rproc_virtio_get_status(struct virtio_device *vdev) { - return 0; + struct rproc_vdev *rvdev = vdev_to_rvdev(vdev); + struct fw_rsc_vdev *rsc; + + rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset; + + return rsc->status; } static void rproc_virtio_set_status(struct virtio_device *vdev, u8 status) { + struct rproc_vdev *rvdev = vdev_to_rvdev(vdev); + struct fw_rsc_vdev *rsc; + + rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset; + + rsc->status = status; dev_dbg(&vdev->dev, "status: %d\n", status); } static void rproc_virtio_reset(struct virtio_device *vdev) { + struct rproc_vdev *rvdev = vdev_to_rvdev(vdev); + struct fw_rsc_vdev *rsc; + + rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset; + + rsc->status = 0; dev_dbg(&vdev->dev, "reset !\n"); } @@ -199,13 +209,19 @@ static void rproc_virtio_reset(struct virtio_device *vdev) static u32 rproc_virtio_get_features(struct virtio_device *vdev) { struct rproc_vdev *rvdev = vdev_to_rvdev(vdev); + struct fw_rsc_vdev *rsc; + + rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset; - return rvdev->dfeatures; + return rsc->dfeatures; } static void rproc_virtio_finalize_features(struct virtio_device *vdev) { struct rproc_vdev *rvdev = vdev_to_rvdev(vdev); + struct fw_rsc_vdev *rsc; + + rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset; /* Give virtio_ring a chance to accept features */ vring_transport_features(vdev); @@ -213,13 +229,44 @@ static void rproc_virtio_finalize_features(struct virtio_device *vdev) /* * Remember the finalized features of our vdev, and provide it * to the remote processor once it is powered on. - * - * Similarly to the status field, we don't expose yet the negotiated - * features to the remote processors at this point. This will be - * fixed as part of a small resource table overhaul and then an - * extension of the virtio resource entries. */ - rvdev->gfeatures = vdev->features[0]; + rsc->gfeatures = vdev->features[0]; +} + +static void rproc_virtio_get(struct virtio_device *vdev, unsigned offset, + void *buf, unsigned len) +{ + struct rproc_vdev *rvdev = vdev_to_rvdev(vdev); + struct fw_rsc_vdev *rsc; + void *cfg; + + rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset; + cfg = &rsc->vring[rsc->num_of_vrings]; + + if (offset + len > rsc->config_len || offset + len < len) { + dev_err(&vdev->dev, "rproc_virtio_get: access out of bounds\n"); + return; + } + + memcpy(buf, cfg + offset, len); +} + +static void rproc_virtio_set(struct virtio_device *vdev, unsigned offset, + const void *buf, unsigned len) +{ + struct rproc_vdev *rvdev = vdev_to_rvdev(vdev); + struct fw_rsc_vdev *rsc; + void *cfg; + + rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset; + cfg = &rsc->vring[rsc->num_of_vrings]; + + if (offset + len > rsc->config_len || offset + len < len) { + dev_err(&vdev->dev, "rproc_virtio_set: access out of bounds\n"); + return; + } + + memcpy(cfg + offset, buf, len); } static const struct virtio_config_ops rproc_virtio_config_ops = { @@ -230,6 +277,8 @@ static const struct virtio_config_ops rproc_virtio_config_ops = { .reset = rproc_virtio_reset, .set_status = rproc_virtio_set_status, .get_status = rproc_virtio_get_status, + .get = rproc_virtio_get, + .set = rproc_virtio_set, }; /* diff --git a/drivers/remoteproc/ste_modem_rproc.c b/drivers/remoteproc/ste_modem_rproc.c index fb95c4220052..1ec39a4c0b3e 100644 --- a/drivers/remoteproc/ste_modem_rproc.c +++ b/drivers/remoteproc/ste_modem_rproc.c @@ -64,26 +64,18 @@ static int sproc_load_segments(struct rproc *rproc, const struct firmware *fw) } /* Find the entry for resource table in the Table of Content */ -static struct ste_toc_entry *sproc_find_rsc_entry(const struct firmware *fw) +static const struct ste_toc_entry *sproc_find_rsc_entry(const void *data) { int i; - struct ste_toc *toc; - - if (!fw) - return NULL; - - toc = (void *)fw->data; + const struct ste_toc *toc; + toc = data; /* Search the table for the resource table */ for (i = 0; i < SPROC_MAX_TOC_ENTRIES && toc->table[i].start != 0xffffffff; i++) { - if (!strncmp(toc->table[i].name, SPROC_RESOURCE_NAME, - sizeof(toc->table[i].name))) { - if (toc->table[i].start > fw->size) - return NULL; + sizeof(toc->table[i].name))) return &toc->table[i]; - } } return NULL; @@ -96,9 +88,12 @@ sproc_find_rsc_table(struct rproc *rproc, const struct firmware *fw, { struct sproc *sproc = rproc->priv; struct resource_table *table; - struct ste_toc_entry *entry; + const struct ste_toc_entry *entry; - entry = sproc_find_rsc_entry(fw); + if (!fw) + return NULL; + + entry = sproc_find_rsc_entry(fw->data); if (!entry) { sproc_err(sproc, "resource table not found in fw\n"); return NULL; @@ -149,10 +144,30 @@ sproc_find_rsc_table(struct rproc *rproc, const struct firmware *fw, return table; } +/* Find the resource table inside the remote processor's firmware. */ +static struct resource_table * +sproc_find_loaded_rsc_table(struct rproc *rproc, const struct firmware *fw) +{ + struct sproc *sproc = rproc->priv; + const struct ste_toc_entry *entry; + + if (!fw || !sproc->fw_addr) + return NULL; + + entry = sproc_find_rsc_entry(sproc->fw_addr); + if (!entry) { + sproc_err(sproc, "resource table not found in fw\n"); + return NULL; + } + + return sproc->fw_addr + entry->start; +} + /* STE modem firmware handler operations */ const struct rproc_fw_ops sproc_fw_ops = { .load = sproc_load_segments, .find_rsc_table = sproc_find_rsc_table, + .find_loaded_rsc_table = sproc_find_loaded_rsc_table, }; /* Kick the modem with specified notification id */ @@ -198,7 +213,7 @@ static int sproc_start(struct rproc *rproc) } /* Subscribe to notifications */ - for (i = 0; i < rproc->max_notifyid; i++) { + for (i = 0; i <= rproc->max_notifyid; i++) { err = sproc->mdev->ops.kick_subscribe(sproc->mdev, i); if (err) { sproc_err(sproc, diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h index faf33324c78f..9e7e745dac55 100644 --- a/include/linux/remoteproc.h +++ b/include/linux/remoteproc.h @@ -401,6 +401,9 @@ enum rproc_crash_type { * @crash_comp: completion used to sync crash handler and the rproc reload * @recovery_disabled: flag that state if recovery was disabled * @max_notifyid: largest allocated notify id. + * @table_ptr: pointer to the resource table in effect + * @cached_table: copy of the resource table + * @table_csum: checksum of the resource table */ struct rproc { struct klist_node node; @@ -429,9 +432,13 @@ struct rproc { struct completion crash_comp; bool recovery_disabled; int max_notifyid; + struct resource_table *table_ptr; + struct resource_table *cached_table; + u32 table_csum; }; /* we currently support only two vrings per rvdev */ + #define RVDEV_NUM_VRINGS 2 /** @@ -462,16 +469,14 @@ struct rproc_vring { * @rproc: the rproc handle * @vdev: the virio device * @vring: the vrings for this vdev - * @dfeatures: virtio device features - * @gfeatures: virtio guest features + * @rsc_offset: offset of the vdev's resource entry */ struct rproc_vdev { struct list_head node; struct rproc *rproc; struct virtio_device vdev; struct rproc_vring vring[RVDEV_NUM_VRINGS]; - unsigned long dfeatures; - unsigned long gfeatures; + u32 rsc_offset; }; struct rproc *rproc_alloc(struct device *dev, const char *name, |