diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2020-04-03 01:42:13 +0300 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2020-04-03 01:42:13 +0300 |
commit | c6570114316fbbce4ac5f970578adaf3cbf07ec3 (patch) | |
tree | 3a910bb902037af2f75e3a5b47a3bcc4d688b11e /drivers/remoteproc | |
parent | ac438771ccb4479528594c7e19f2c39cf1814a86 (diff) | |
parent | a7084c3d47c4aaedcca217ce87b7b5b5fe3cfa35 (diff) | |
download | linux-c6570114316fbbce4ac5f970578adaf3cbf07ec3.tar.xz |
Merge tag 'rproc-v5.7' of git://git.kernel.org/pub/scm/linux/kernel/git/andersson/remoteproc
Pull remoteproc updates from Bjorn Andersson:
- a range of improvements to the OMAP remoeteproc driver; among other
things adding devicetree, suspend/resume and watchdog support, and
adds support the remoteprocs in the DRA7xx SoC
- support for 64-bit firmware, extends the ELF loader to support this
and fixes for a number of race conditions in the recovery handling
- a generic mechanism to allow remoteproc drivers to sync state with
remote processors during a panic, and uses this to prepare Qualcomm
remote processors for post mortem analysis
- fixes to cleanly recover from crashes in the modem firmware on
production Qualcomm devices
* tag 'rproc-v5.7' of git://git.kernel.org/pub/scm/linux/kernel/git/andersson/remoteproc: (37 commits)
remoteproc/omap: Switch to SPDX license identifiers
remoteproc/omap: Add watchdog functionality for remote processors
remoteproc/omap: Report device exceptions and trigger recovery
remoteproc/omap: Add support for runtime auto-suspend/resume
remoteproc/omap: Add support for system suspend/resume
remoteproc/omap: Request a timer(s) for remoteproc usage
remoteproc/omap: Check for undefined mailbox messages
remoteproc/omap: Remove the platform_data header
remoteproc/omap: Add support for DRA7xx remote processors
remoteproc/omap: Initialize and assign reserved memory node
remoteproc/omap: Add the rproc ops .da_to_va() implementation
remoteproc/omap: Add support to parse internal memories from DT
remoteproc/omap: Add a sanity check for DSP boot address alignment
remoteproc/omap: Add device tree support
dt-bindings: remoteproc: Add OMAP remoteproc bindings
remoteproc: qcom: Introduce panic handler for PAS and ADSP
remoteproc: qcom: q6v5: Add common panic handler
remoteproc: Introduce "panic" callback in ops
remoteproc: Traverse rproc_list under RCU read lock
remoteproc: Fix NULL pointer dereference in rproc_virtio_notify
...
Diffstat (limited to 'drivers/remoteproc')
23 files changed, 1745 insertions, 218 deletions
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig index ffdb5bc25d6d..fbaed079b299 100644 --- a/drivers/remoteproc/Kconfig +++ b/drivers/remoteproc/Kconfig @@ -35,7 +35,7 @@ config MTK_SCP config OMAP_REMOTEPROC tristate "OMAP remoteproc support" - depends on ARCH_OMAP4 || SOC_OMAP5 + depends on ARCH_OMAP4 || SOC_OMAP5 || SOC_DRA7XX depends on OMAP_IOMMU select MAILBOX select OMAP2PLUS_MBOX @@ -52,6 +52,18 @@ config OMAP_REMOTEPROC It's safe to say N here if you're not interested in multimedia offloading or just want a bare minimum kernel. +config OMAP_REMOTEPROC_WATCHDOG + bool "OMAP remoteproc watchdog timer" + depends on OMAP_REMOTEPROC + default n + help + Say Y here to enable watchdog timer for remote processors. + + This option controls the watchdog functionality for the remote + processors in OMAP. Dedicated OMAP DMTimers are used by the remote + processors and triggers the timer interrupt upon a watchdog + detection. + config WKUP_M3_RPROC tristate "AMx3xx Wakeup M3 remoteproc support" depends on SOC_AM33XX || SOC_AM43XX diff --git a/drivers/remoteproc/imx_rproc.c b/drivers/remoteproc/imx_rproc.c index 3e72b6f38d4b..8957ed271d20 100644 --- a/drivers/remoteproc/imx_rproc.c +++ b/drivers/remoteproc/imx_rproc.c @@ -186,7 +186,7 @@ static int imx_rproc_stop(struct rproc *rproc) } static int imx_rproc_da_to_sys(struct imx_rproc *priv, u64 da, - int len, u64 *sys) + size_t len, u64 *sys) { const struct imx_rproc_dcfg *dcfg = priv->dcfg; int i; @@ -203,19 +203,19 @@ static int imx_rproc_da_to_sys(struct imx_rproc *priv, u64 da, } } - dev_warn(priv->dev, "Translation failed: da = 0x%llx len = 0x%x\n", + dev_warn(priv->dev, "Translation failed: da = 0x%llx len = 0x%zx\n", da, len); return -ENOENT; } -static void *imx_rproc_da_to_va(struct rproc *rproc, u64 da, int len) +static void *imx_rproc_da_to_va(struct rproc *rproc, u64 da, size_t len) { struct imx_rproc *priv = rproc->priv; void *va = NULL; u64 sys; int i; - if (len <= 0) + if (len == 0) return NULL; /* @@ -235,7 +235,8 @@ static void *imx_rproc_da_to_va(struct rproc *rproc, u64 da, int len) } } - dev_dbg(&rproc->dev, "da = 0x%llx len = 0x%x va = 0x%p\n", da, len, va); + dev_dbg(&rproc->dev, "da = 0x%llx len = 0x%zx va = 0x%p\n", + da, len, va); return va; } diff --git a/drivers/remoteproc/keystone_remoteproc.c b/drivers/remoteproc/keystone_remoteproc.c index 5c4658f00b3d..cd266163a65f 100644 --- a/drivers/remoteproc/keystone_remoteproc.c +++ b/drivers/remoteproc/keystone_remoteproc.c @@ -246,7 +246,7 @@ static void keystone_rproc_kick(struct rproc *rproc, int vqid) * can be used either by the remoteproc core for loading (when using kernel * remoteproc loader), or by any rpmsg bus drivers. */ -static void *keystone_rproc_da_to_va(struct rproc *rproc, u64 da, int len) +static void *keystone_rproc_da_to_va(struct rproc *rproc, u64 da, size_t len) { struct keystone_rproc *ksproc = rproc->priv; void __iomem *va = NULL; @@ -255,7 +255,7 @@ static void *keystone_rproc_da_to_va(struct rproc *rproc, u64 da, int len) size_t size; int i; - if (len <= 0) + if (len == 0) return NULL; for (i = 0; i < ksproc->num_mems; i++) { diff --git a/drivers/remoteproc/mtk_scp.c b/drivers/remoteproc/mtk_scp.c index 7ccdf64ff3ea..ea3743e7e794 100644 --- a/drivers/remoteproc/mtk_scp.c +++ b/drivers/remoteproc/mtk_scp.c @@ -320,7 +320,7 @@ stop: return ret; } -static void *scp_da_to_va(struct rproc *rproc, u64 da, int len) +static void *scp_da_to_va(struct rproc *rproc, u64 da, size_t len) { struct mtk_scp *scp = (struct mtk_scp *)rproc->priv; int offset; diff --git a/drivers/remoteproc/omap_remoteproc.c b/drivers/remoteproc/omap_remoteproc.c index 6398194075aa..cdb546f7232e 100644 --- a/drivers/remoteproc/omap_remoteproc.c +++ b/drivers/remoteproc/omap_remoteproc.c @@ -2,7 +2,7 @@ /* * OMAP Remote Processor driver * - * Copyright (C) 2011 Texas Instruments, Inc. + * Copyright (C) 2011-2020 Texas Instruments Incorporated - http://www.ti.com/ * Copyright (C) 2011 Google, Inc. * * Ohad Ben-Cohen <ohad@wizery.com> @@ -15,31 +15,466 @@ #include <linux/kernel.h> #include <linux/module.h> +#include <linux/clk.h> +#include <linux/clk/ti.h> #include <linux/err.h> +#include <linux/io.h> +#include <linux/of_device.h> +#include <linux/of_reserved_mem.h> #include <linux/platform_device.h> +#include <linux/pm_runtime.h> #include <linux/dma-mapping.h> +#include <linux/interrupt.h> #include <linux/remoteproc.h> #include <linux/mailbox_client.h> +#include <linux/omap-iommu.h> #include <linux/omap-mailbox.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> +#include <linux/reset.h> +#include <clocksource/timer-ti-dm.h> -#include <linux/platform_data/remoteproc-omap.h> +#include <linux/platform_data/dmtimer-omap.h> #include "omap_remoteproc.h" #include "remoteproc_internal.h" +/* default auto-suspend delay (ms) */ +#define DEFAULT_AUTOSUSPEND_DELAY 10000 + +/** + * struct omap_rproc_boot_data - boot data structure for the DSP omap rprocs + * @syscon: regmap handle for the system control configuration module + * @boot_reg: boot register offset within the @syscon regmap + * @boot_reg_shift: bit-field shift required for the boot address value in + * @boot_reg + */ +struct omap_rproc_boot_data { + struct regmap *syscon; + unsigned int boot_reg; + unsigned int boot_reg_shift; +}; + +/** + * struct omap_rproc_mem - internal memory structure + * @cpu_addr: MPU virtual address of the memory region + * @bus_addr: bus address used to access the memory region + * @dev_addr: device address of the memory region from DSP view + * @size: size of the memory region + */ +struct omap_rproc_mem { + void __iomem *cpu_addr; + phys_addr_t bus_addr; + u32 dev_addr; + size_t size; +}; + +/** + * struct omap_rproc_timer - data structure for a timer used by a omap rproc + * @odt: timer pointer + * @timer_ops: OMAP dmtimer ops for @odt timer + * @irq: timer irq + */ +struct omap_rproc_timer { + struct omap_dm_timer *odt; + const struct omap_dm_timer_ops *timer_ops; + int irq; +}; + /** * struct omap_rproc - omap remote processor state * @mbox: mailbox channel handle * @client: mailbox client to request the mailbox channel + * @boot_data: boot data structure for setting processor boot address + * @mem: internal memory regions data + * @num_mems: number of internal memory regions + * @num_timers: number of rproc timer(s) + * @num_wd_timers: number of rproc watchdog timers + * @timers: timer(s) info used by rproc + * @autosuspend_delay: auto-suspend delay value to be used for runtime pm + * @need_resume: if true a resume is needed in the system resume callback * @rproc: rproc handle + * @reset: reset handle + * @pm_comp: completion primitive to sync for suspend response + * @fck: functional clock for the remoteproc + * @suspend_acked: state machine flag to store the suspend request ack */ struct omap_rproc { struct mbox_chan *mbox; struct mbox_client client; + struct omap_rproc_boot_data *boot_data; + struct omap_rproc_mem *mem; + int num_mems; + int num_timers; + int num_wd_timers; + struct omap_rproc_timer *timers; + int autosuspend_delay; + bool need_resume; struct rproc *rproc; + struct reset_control *reset; + struct completion pm_comp; + struct clk *fck; + bool suspend_acked; }; /** + * struct omap_rproc_mem_data - memory definitions for an omap remote processor + * @name: name for this memory entry + * @dev_addr: device address for the memory entry + */ +struct omap_rproc_mem_data { + const char *name; + const u32 dev_addr; +}; + +/** + * struct omap_rproc_dev_data - device data for the omap remote processor + * @device_name: device name of the remote processor + * @mems: memory definitions for this remote processor + */ +struct omap_rproc_dev_data { + const char *device_name; + const struct omap_rproc_mem_data *mems; +}; + +/** + * omap_rproc_request_timer() - request a timer for a remoteproc + * @dev: device requesting the timer + * @np: device node pointer to the desired timer + * @timer: handle to a struct omap_rproc_timer to return the timer handle + * + * This helper function is used primarily to request a timer associated with + * a remoteproc. The returned handle is stored in the .odt field of the + * @timer structure passed in, and is used to invoke other timer specific + * ops (like starting a timer either during device initialization or during + * a resume operation, or for stopping/freeing a timer). + * + * Return: 0 on success, otherwise an appropriate failure + */ +static int omap_rproc_request_timer(struct device *dev, struct device_node *np, + struct omap_rproc_timer *timer) +{ + int ret; + + timer->odt = timer->timer_ops->request_by_node(np); + if (!timer->odt) { + dev_err(dev, "request for timer node %p failed\n", np); + return -EBUSY; + } + + ret = timer->timer_ops->set_source(timer->odt, OMAP_TIMER_SRC_SYS_CLK); + if (ret) { + dev_err(dev, "error setting OMAP_TIMER_SRC_SYS_CLK as source for timer node %p\n", + np); + timer->timer_ops->free(timer->odt); + return ret; + } + + /* clean counter, remoteproc code will set the value */ + timer->timer_ops->set_load(timer->odt, 0, 0); + + return 0; +} + +/** + * omap_rproc_start_timer() - start a timer for a remoteproc + * @timer: handle to a OMAP rproc timer + * + * This helper function is used to start a timer associated with a remoteproc, + * obtained using the request_timer ops. The helper function needs to be + * invoked by the driver to start the timer (during device initialization) + * or to just resume the timer. + * + * Return: 0 on success, otherwise a failure as returned by DMTimer ops + */ +static inline int omap_rproc_start_timer(struct omap_rproc_timer *timer) +{ + return timer->timer_ops->start(timer->odt); +} + +/** + * omap_rproc_stop_timer() - stop a timer for a remoteproc + * @timer: handle to a OMAP rproc timer + * + * This helper function is used to disable a timer associated with a + * remoteproc, and needs to be called either during a device shutdown + * or suspend operation. The separate helper function allows the driver + * to just stop a timer without having to release the timer during a + * suspend operation. + * + * Return: 0 on success, otherwise a failure as returned by DMTimer ops + */ +static inline int omap_rproc_stop_timer(struct omap_rproc_timer *timer) +{ + return timer->timer_ops->stop(timer->odt); +} + +/** + * omap_rproc_release_timer() - release a timer for a remoteproc + * @timer: handle to a OMAP rproc timer + * + * This helper function is used primarily to release a timer associated + * with a remoteproc. The dmtimer will be available for other clients to + * use once released. + * + * Return: 0 on success, otherwise a failure as returned by DMTimer ops + */ +static inline int omap_rproc_release_timer(struct omap_rproc_timer *timer) +{ + return timer->timer_ops->free(timer->odt); +} + +/** + * omap_rproc_get_timer_irq() - get the irq for a timer + * @timer: handle to a OMAP rproc timer + * + * This function is used to get the irq associated with a watchdog timer. The + * function is called by the OMAP remoteproc driver to register a interrupt + * handler to handle watchdog events on the remote processor. + * + * Return: irq id on success, otherwise a failure as returned by DMTimer ops + */ +static inline int omap_rproc_get_timer_irq(struct omap_rproc_timer *timer) +{ + return timer->timer_ops->get_irq(timer->odt); +} + +/** + * omap_rproc_ack_timer_irq() - acknowledge a timer irq + * @timer: handle to a OMAP rproc timer + * + * This function is used to clear the irq associated with a watchdog timer. The + * The function is called by the OMAP remoteproc upon a watchdog event on the + * remote processor to clear the interrupt status of the watchdog timer. + */ +static inline void omap_rproc_ack_timer_irq(struct omap_rproc_timer *timer) +{ + timer->timer_ops->write_status(timer->odt, OMAP_TIMER_INT_OVERFLOW); +} + +/** + * omap_rproc_watchdog_isr() - Watchdog ISR handler for remoteproc device + * @irq: IRQ number associated with a watchdog timer + * @data: IRQ handler data + * + * This ISR routine executes the required necessary low-level code to + * acknowledge a watchdog timer interrupt. There can be multiple watchdog + * timers associated with a rproc (like IPUs which have 2 watchdog timers, + * one per Cortex M3/M4 core), so a lookup has to be performed to identify + * the timer to acknowledge its interrupt. + * + * The function also invokes rproc_report_crash to report the watchdog event + * to the remoteproc driver core, to trigger a recovery. + * + * Return: IRQ_HANDLED on success, otherwise IRQ_NONE + */ +static irqreturn_t omap_rproc_watchdog_isr(int irq, void *data) +{ + struct rproc *rproc = data; + struct omap_rproc *oproc = rproc->priv; + struct device *dev = rproc->dev.parent; + struct omap_rproc_timer *timers = oproc->timers; + struct omap_rproc_timer *wd_timer = NULL; + int num_timers = oproc->num_timers + oproc->num_wd_timers; + int i; + + for (i = oproc->num_timers; i < num_timers; i++) { + if (timers[i].irq > 0 && irq == timers[i].irq) { + wd_timer = &timers[i]; + break; + } + } + + if (!wd_timer) { + dev_err(dev, "invalid timer\n"); + return IRQ_NONE; + } + + omap_rproc_ack_timer_irq(wd_timer); + + rproc_report_crash(rproc, RPROC_WATCHDOG); + + return IRQ_HANDLED; +} + +/** + * omap_rproc_enable_timers() - enable the timers for a remoteproc + * @rproc: handle of a remote processor + * @configure: boolean flag used to acquire and configure the timer handle + * + * This function is used primarily to enable the timers associated with + * a remoteproc. The configure flag is provided to allow the driver to + * to either acquire and start a timer (during device initialization) or + * to just start a timer (during a resume operation). + * + * Return: 0 on success, otherwise an appropriate failure + */ +static int omap_rproc_enable_timers(struct rproc *rproc, bool configure) +{ + int i; + int ret = 0; + struct platform_device *tpdev; + struct dmtimer_platform_data *tpdata; + const struct omap_dm_timer_ops *timer_ops; + struct omap_rproc *oproc = rproc->priv; + struct omap_rproc_timer *timers = oproc->timers; + struct device *dev = rproc->dev.parent; + struct device_node *np = NULL; + int num_timers = oproc->num_timers + oproc->num_wd_timers; + + if (!num_timers) + return 0; + + if (!configure) + goto start_timers; + + for (i = 0; i < num_timers; i++) { + if (i < oproc->num_timers) + np = of_parse_phandle(dev->of_node, "ti,timers", i); + else + np = of_parse_phandle(dev->of_node, + "ti,watchdog-timers", + (i - oproc->num_timers)); + if (!np) { + ret = -ENXIO; + dev_err(dev, "device node lookup for timer at index %d failed: %d\n", + i < oproc->num_timers ? i : + i - oproc->num_timers, ret); + goto free_timers; + } + + tpdev = of_find_device_by_node(np); + if (!tpdev) { + ret = -ENODEV; + dev_err(dev, "could not get timer platform device\n"); + goto put_node; + } + + tpdata = dev_get_platdata(&tpdev->dev); + put_device(&tpdev->dev); + if (!tpdata) { + ret = -EINVAL; + dev_err(dev, "dmtimer pdata structure NULL\n"); + goto put_node; + } + + timer_ops = tpdata->timer_ops; + if (!timer_ops || !timer_ops->request_by_node || + !timer_ops->set_source || !timer_ops->set_load || + !timer_ops->free || !timer_ops->start || + !timer_ops->stop || !timer_ops->get_irq || + !timer_ops->write_status) { + ret = -EINVAL; + dev_err(dev, "device does not have required timer ops\n"); + goto put_node; + } + + timers[i].irq = -1; + timers[i].timer_ops = timer_ops; + ret = omap_rproc_request_timer(dev, np, &timers[i]); + if (ret) { + dev_err(dev, "request for timer %p failed: %d\n", np, + ret); + goto put_node; + } + of_node_put(np); + + if (i >= oproc->num_timers) { + timers[i].irq = omap_rproc_get_timer_irq(&timers[i]); + if (timers[i].irq < 0) { + dev_err(dev, "get_irq for timer %p failed: %d\n", + np, timers[i].irq); + ret = -EBUSY; + goto free_timers; + } + + ret = request_irq(timers[i].irq, + omap_rproc_watchdog_isr, IRQF_SHARED, + "rproc-wdt", rproc); + if (ret) { + dev_err(dev, "error requesting irq for timer %p\n", + np); + omap_rproc_release_timer(&timers[i]); + timers[i].odt = NULL; + timers[i].timer_ops = NULL; + timers[i].irq = -1; + goto free_timers; + } + } + } + +start_timers: + for (i = 0; i < num_timers; i++) { + ret = omap_rproc_start_timer(&timers[i]); + if (ret) { + dev_err(dev, "start timer %p failed failed: %d\n", np, + ret); + break; + } + } + if (ret) { + while (i >= 0) { + omap_rproc_stop_timer(&timers[i]); + i--; + } + goto put_node; + } + return 0; + +put_node: + if (configure) + of_node_put(np); +free_timers: + while (i--) { + if (i >= oproc->num_timers) + free_irq(timers[i].irq, rproc); + omap_rproc_release_timer(&timers[i]); + timers[i].odt = NULL; + timers[i].timer_ops = NULL; + timers[i].irq = -1; + } + + return ret; +} + +/** + * omap_rproc_disable_timers() - disable the timers for a remoteproc + * @rproc: handle of a remote processor + * @configure: boolean flag used to release the timer handle + * + * This function is used primarily to disable the timers associated with + * a remoteproc. The configure flag is provided to allow the driver to + * to either stop and release a timer (during device shutdown) or to just + * stop a timer (during a suspend operation). + * + * Return: 0 on success or no timers + */ +static int omap_rproc_disable_timers(struct rproc *rproc, bool configure) +{ + int i; + struct omap_rproc *oproc = rproc->priv; + struct omap_rproc_timer *timers = oproc->timers; + int num_timers = oproc->num_timers + oproc->num_wd_timers; + + if (!num_timers) + return 0; + + for (i = 0; i < num_timers; i++) { + omap_rproc_stop_timer(&timers[i]); + if (configure) { + if (i >= oproc->num_timers) + free_irq(timers[i].irq, rproc); + omap_rproc_release_timer(&timers[i]); + timers[i].odt = NULL; + timers[i].timer_ops = NULL; + timers[i].irq = -1; + } + } + + return 0; +} + +/** * omap_rproc_mbox_callback() - inbound mailbox message handler * @client: mailbox client pointer used for requesting the mailbox channel * @data: mailbox payload @@ -65,13 +500,29 @@ static void omap_rproc_mbox_callback(struct mbox_client *client, void *data) switch (msg) { case RP_MBOX_CRASH: - /* just log this for now. later, we'll also do recovery */ + /* + * remoteproc detected an exception, notify the rproc core. + * The remoteproc core will handle the recovery. + */ dev_err(dev, "omap rproc %s crashed\n", name); + rproc_report_crash(oproc->rproc, RPROC_FATAL_ERROR); break; case RP_MBOX_ECHO_REPLY: dev_info(dev, "received echo reply from %s\n", name); break; + case RP_MBOX_SUSPEND_ACK: + /* Fall through */ + case RP_MBOX_SUSPEND_CANCEL: + oproc->suspend_acked = msg == RP_MBOX_SUSPEND_ACK; + complete(&oproc->pm_comp); + break; default: + if (msg >= RP_MBOX_READY && msg < RP_MBOX_END_MSG) + return; + if (msg > oproc->rproc->max_notifyid) { + dev_dbg(dev, "dropping unknown message 0x%x", msg); + return; + } /* msg contains the index of the triggered vring */ if (rproc_vq_interrupt(oproc->rproc, msg) == IRQ_NONE) dev_dbg(dev, "no message was found in vqid %d\n", msg); @@ -85,11 +536,52 @@ static void omap_rproc_kick(struct rproc *rproc, int vqid) struct device *dev = rproc->dev.parent; int ret; + /* wake up the rproc before kicking it */ + ret = pm_runtime_get_sync(dev); + if (WARN_ON(ret < 0)) { + dev_err(dev, "pm_runtime_get_sync() failed during kick, ret = %d\n", + ret); + pm_runtime_put_noidle(dev); + return; + } + /* send the index of the triggered virtqueue in the mailbox payload */ ret = mbox_send_message(oproc->mbox, (void *)vqid); if (ret < 0) dev_err(dev, "failed to send mailbox message, status = %d\n", ret); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); +} + +/** + * omap_rproc_write_dsp_boot_addr() - set boot address for DSP remote processor + * @rproc: handle of a remote processor + * + * Set boot address for a supported DSP remote processor. + * + * Return: 0 on success, or -EINVAL if boot address is not aligned properly + */ +static int omap_rproc_write_dsp_boot_addr(struct rproc *rproc) +{ + struct device *dev = rproc->dev.parent; + struct omap_rproc *oproc = rproc->priv; + struct omap_rproc_boot_data *bdata = oproc->boot_data; + u32 offset = bdata->boot_reg; + u32 value; + u32 mask; + + if (rproc->bootaddr & (SZ_1K - 1)) { + dev_err(dev, "invalid boot address 0x%llx, must be aligned on a 1KB boundary\n", + rproc->bootaddr); + return -EINVAL; + } + + value = rproc->bootaddr >> bdata->boot_reg_shift; + mask = ~(SZ_1K - 1) >> bdata->boot_reg_shift; + + return regmap_update_bits(bdata->syscon, offset, mask, value); } /* @@ -103,13 +595,14 @@ static int omap_rproc_start(struct rproc *rproc) { struct omap_rproc *oproc = rproc->priv; struct device *dev = rproc->dev.parent; - struct platform_device *pdev = to_platform_device(dev); - struct omap_rproc_pdata *pdata = pdev->dev.platform_data; int ret; struct mbox_client *client = &oproc->client; - if (pdata->set_bootaddr) - pdata->set_bootaddr(rproc->bootaddr); + if (oproc->boot_data) { + ret = omap_rproc_write_dsp_boot_addr(rproc); + if (ret) + return ret; + } client->dev = dev; client->tx_done = NULL; @@ -117,7 +610,7 @@ static int omap_rproc_start(struct rproc *rproc) client->tx_block = false; client->knows_txdone = false; - oproc->mbox = omap_mbox_request_channel(client, pdata->mbox_name); + oproc->mbox = mbox_request_channel(client, 0); if (IS_ERR(oproc->mbox)) { ret = -EBUSY; dev_err(dev, "mbox_request_channel failed: %ld\n", @@ -138,14 +631,34 @@ static int omap_rproc_start(struct rproc *rproc) goto put_mbox; } - ret = pdata->device_enable(pdev); + ret = omap_rproc_enable_timers(rproc, true); if (ret) { - dev_err(dev, "omap_device_enable failed: %d\n", ret); + dev_err(dev, "omap_rproc_enable_timers failed: %d\n", ret); goto put_mbox; } + ret = reset_control_deassert(oproc->reset); + if (ret) { + dev_err(dev, "reset control deassert failed: %d\n", ret); + goto disable_timers; + } + + /* + * remote processor is up, so update the runtime pm status and + * enable the auto-suspend. The device usage count is incremented + * manually for balancing it for auto-suspend + */ + pm_runtime_set_active(dev); + pm_runtime_use_autosuspend(dev); + pm_runtime_get_noresume(dev); + pm_runtime_enable(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + return 0; +disable_timers: + omap_rproc_disable_timers(rproc, true); put_mbox: mbox_free_channel(oproc->mbox); return ret; @@ -155,32 +668,638 @@ put_mbox: static int omap_rproc_stop(struct rproc *rproc) { struct device *dev = rproc->dev.parent; - struct platform_device *pdev = to_platform_device(dev); - struct omap_rproc_pdata *pdata = pdev->dev.platform_data; struct omap_rproc *oproc = rproc->priv; int ret; - ret = pdata->device_shutdown(pdev); - if (ret) + /* + * cancel any possible scheduled runtime suspend by incrementing + * the device usage count, and resuming the device. The remoteproc + * also needs to be woken up if suspended, to avoid the remoteproc + * OS to continue to remember any context that it has saved, and + * avoid potential issues in misindentifying a subsequent device + * reboot as a power restore boot + */ + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + pm_runtime_put_noidle(dev); return ret; + } + + ret = reset_control_assert(oproc->reset); + if (ret) + goto out; + + ret = omap_rproc_disable_timers(rproc, true); + if (ret) + goto enable_device; mbox_free_channel(oproc->mbox); + /* + * update the runtime pm states and status now that the remoteproc + * has stopped + */ + pm_runtime_disable(dev); + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_put_noidle(dev); + pm_runtime_set_suspended(dev); + return 0; + +enable_device: + reset_control_deassert(oproc->reset); +out: + /* schedule the next auto-suspend */ + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + return ret; +} + +/** + * omap_rproc_da_to_va() - internal memory translation helper + * @rproc: remote processor to apply the address translation for + * @da: device address to translate + * @len: length of the memory buffer + * + * Custom function implementing the rproc .da_to_va ops to provide address + * translation (device address to kernel virtual address) for internal RAMs + * present in a DSP or IPU device). The translated addresses can be used + * either by the remoteproc core for loading, or by any rpmsg bus drivers. + * + * Return: translated virtual address in kernel memory space on success, + * or NULL on failure. + */ +static void *omap_rproc_da_to_va(struct rproc *rproc, u64 da, size_t len) +{ + struct omap_rproc *oproc = rproc->priv; + int i; + u32 offset; + + if (len <= 0) + return NULL; + + if (!oproc->num_mems) + return NULL; + + for (i = 0; i < oproc->num_mems; i++) { + if (da >= oproc->mem[i].dev_addr && da + len <= + oproc->mem[i].dev_addr + oproc->mem[i].size) { + offset = da - oproc->mem[i].dev_addr; + /* __force to make sparse happy with type conversion */ + return (__force void *)(oproc->mem[i].cpu_addr + + offset); + } + } + + return NULL; } static const struct rproc_ops omap_rproc_ops = { .start = omap_rproc_start, .stop = omap_rproc_stop, .kick = omap_rproc_kick, + .da_to_va = omap_rproc_da_to_va, +}; + +#ifdef CONFIG_PM +static bool _is_rproc_in_standby(struct omap_rproc *oproc) +{ + return ti_clk_is_in_standby(oproc->fck); +} + +/* 1 sec is long enough time to let the remoteproc side suspend the device */ +#define DEF_SUSPEND_TIMEOUT 1000 +static int _omap_rproc_suspend(struct rproc *rproc, bool auto_suspend) +{ + struct device *dev = rproc->dev.parent; + struct omap_rproc *oproc = rproc->priv; + unsigned long to = msecs_to_jiffies(DEF_SUSPEND_TIMEOUT); + unsigned long ta = jiffies + to; + u32 suspend_msg = auto_suspend ? + RP_MBOX_SUSPEND_AUTO : RP_MBOX_SUSPEND_SYSTEM; + int ret; + + reinit_completion(&oproc->pm_comp); + oproc->suspend_acked = false; + ret = mbox_send_message(oproc->mbox, (void *)suspend_msg); + if (ret < 0) { + dev_err(dev, "PM mbox_send_message failed: %d\n", ret); + return ret; + } + + ret = wait_for_completion_timeout(&oproc->pm_comp, to); + if (!oproc->suspend_acked) + return -EBUSY; + + /* + * The remoteproc side is returning the ACK message before saving the + * context, because the context saving is performed within a SYS/BIOS + * function, and it cannot have any inter-dependencies against the IPC + * layer. Also, as the SYS/BIOS needs to preserve properly the processor + * register set, sending this ACK or signalling the completion of the + * context save through a shared memory variable can never be the + * absolute last thing to be executed on the remoteproc side, and the + * MPU cannot use the ACK message as a sync point to put the remoteproc + * into reset. The only way to ensure that the remote processor has + * completed saving the context is to check that the module has reached + * STANDBY state (after saving the context, the SYS/BIOS executes the + * appropriate target-specific WFI instruction causing the module to + * enter STANDBY). + */ + while (!_is_rproc_in_standby(oproc)) { + if (time_after(jiffies, ta)) + return -ETIME; + schedule(); + } + + ret = reset_control_assert(oproc->reset); + if (ret) { + dev_err(dev, "reset assert during suspend failed %d\n", ret); + return ret; + } + + ret = omap_rproc_disable_timers(rproc, false); + if (ret) { + dev_err(dev, "disabling timers during suspend failed %d\n", + ret); + goto enable_device; + } + + /* + * IOMMUs would have to be disabled specifically for runtime suspend. + * They are handled automatically through System PM callbacks for + * regular system suspend + */ + if (auto_suspend) { + ret = omap_iommu_domain_deactivate(rproc->domain); + if (ret) { + dev_err(dev, "iommu domain deactivate failed %d\n", + ret); + goto enable_timers; + } + } + + return 0; + +enable_timers: + /* ignore errors on re-enabling code */ + omap_rproc_enable_timers(rproc, false); +enable_device: + reset_control_deassert(oproc->reset); + return ret; +} + +static int _omap_rproc_resume(struct rproc *rproc, bool auto_suspend) +{ + struct device *dev = rproc->dev.parent; + struct omap_rproc *oproc = rproc->priv; + int ret; + + /* + * IOMMUs would have to be enabled specifically for runtime resume. + * They would have been already enabled automatically through System + * PM callbacks for regular system resume + */ + if (auto_suspend) { + ret = omap_iommu_domain_activate(rproc->domain); + if (ret) { + dev_err(dev, "omap_iommu activate failed %d\n", ret); + goto out; + } + } + + /* boot address could be lost after suspend, so restore it */ + if (oproc->boot_data) { + ret = omap_rproc_write_dsp_boot_addr(rproc); + if (ret) { + dev_err(dev, "boot address restore failed %d\n", ret); + goto suspend_iommu; + } + } + + ret = omap_rproc_enable_timers(rproc, false); + if (ret) { + dev_err(dev, "enabling timers during resume failed %d\n", ret); + goto suspend_iommu; + } + + ret = reset_control_deassert(oproc->reset); + if (ret) { + dev_err(dev, "reset deassert during resume failed %d\n", ret); + goto disable_timers; + } + + return 0; + +disable_timers: + omap_rproc_disable_timers(rproc, false); +suspend_iommu: + if (auto_suspend) + omap_iommu_domain_deactivate(rproc->domain); +out: + return ret; +} + +static int __maybe_unused omap_rproc_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rproc *rproc = platform_get_drvdata(pdev); + struct omap_rproc *oproc = rproc->priv; + int ret = 0; + + mutex_lock(&rproc->lock); + if (rproc->state == RPROC_OFFLINE) + goto out; + + if (rproc->state == RPROC_SUSPENDED) + goto out; + + if (rproc->state != RPROC_RUNNING) { + ret = -EBUSY; + goto out; + } + + ret = _omap_rproc_suspend(rproc, false); + if (ret) { + dev_err(dev, "suspend failed %d\n", ret); + goto out; + } + + /* + * remoteproc is running at the time of system suspend, so remember + * it so as to wake it up during system resume + */ + oproc->need_resume = true; + rproc->state = RPROC_SUSPENDED; + +out: + mutex_unlock(&rproc->lock); + return ret; +} + +static int __maybe_unused omap_rproc_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rproc *rproc = platform_get_drvdata(pdev); + struct omap_rproc *oproc = rproc->priv; + int ret = 0; + + mutex_lock(&rproc->lock); + if (rproc->state == RPROC_OFFLINE) + goto out; + + if (rproc->state != RPROC_SUSPENDED) { + ret = -EBUSY; + goto out; + } + + /* + * remoteproc was auto-suspended at the time of system suspend, + * so no need to wake-up the processor (leave it in suspended + * state, will be woken up during a subsequent runtime_resume) + */ + if (!oproc->need_resume) + goto out; + + ret = _omap_rproc_resume(rproc, false); + if (ret) { + dev_err(dev, "resume failed %d\n", ret); + goto out; + } + + oproc->need_resume = false; + rproc->state = RPROC_RUNNING; + + pm_runtime_mark_last_busy(dev); +out: + mutex_unlock(&rproc->lock); + return ret; +} + +static int omap_rproc_runtime_suspend(struct device *dev) +{ + struct rproc *rproc = dev_get_drvdata(dev); + struct omap_rproc *oproc = rproc->priv; + int ret; + + mutex_lock(&rproc->lock); + if (rproc->state == RPROC_CRASHED) { + dev_dbg(dev, "rproc cannot be runtime suspended when crashed!\n"); + ret = -EBUSY; + goto out; + } + + if (WARN_ON(rproc->state != RPROC_RUNNING)) { + dev_err(dev, "rproc cannot be runtime suspended when not running!\n"); + ret = -EBUSY; + goto out; + } + + /* + * do not even attempt suspend if the remote processor is not + * idled for runtime auto-suspend + */ + if (!_is_rproc_in_standby(oproc)) { + ret = -EBUSY; + goto abort; + } + + ret = _omap_rproc_suspend(rproc, true); + if (ret) + goto abort; + + rproc->state = RPROC_SUSPENDED; + mutex_unlock(&rproc->lock); + return 0; + +abort: + pm_runtime_mark_last_busy(dev); +out: + mutex_unlock(&rproc->lock); + return ret; +} + +static int omap_rproc_runtime_resume(struct device *dev) +{ + struct rproc *rproc = dev_get_drvdata(dev); + int ret; + + mutex_lock(&rproc->lock); + if (WARN_ON(rproc->state != RPROC_SUSPENDED)) { + dev_err(dev, "rproc cannot be runtime resumed if not suspended! state=%d\n", + rproc->state); + ret = -EBUSY; + goto out; + } + + ret = _omap_rproc_resume(rproc, true); + if (ret) { + dev_err(dev, "runtime resume failed %d\n", ret); + goto out; + } + + rproc->state = RPROC_RUNNING; +out: + mutex_unlock(&rproc->lock); + return ret; +} +#endif /* CONFIG_PM */ + +static const struct omap_rproc_mem_data ipu_mems[] = { + { .name = "l2ram", .dev_addr = 0x20000000 }, + { }, +}; + +static const struct omap_rproc_mem_data dra7_dsp_mems[] = { + { .name = "l2ram", .dev_addr = 0x800000 }, + { .name = "l1pram", .dev_addr = 0xe00000 }, + { .name = "l1dram", .dev_addr = 0xf00000 }, + { }, +}; + +static const struct omap_rproc_dev_data omap4_dsp_dev_data = { + .device_name = "dsp", +}; + +static const struct omap_rproc_dev_data omap4_ipu_dev_data = { + .device_name = "ipu", + .mems = ipu_mems, }; +static const struct omap_rproc_dev_data omap5_dsp_dev_data = { + .device_name = "dsp", +}; + +static const struct omap_rproc_dev_data omap5_ipu_dev_data = { + .device_name = "ipu", + .mems = ipu_mems, +}; + +static const struct omap_rproc_dev_data dra7_dsp_dev_data = { + .device_name = "dsp", + .mems = dra7_dsp_mems, +}; + +static const struct omap_rproc_dev_data dra7_ipu_dev_data = { + .device_name = "ipu", + .mems = ipu_mems, +}; + +static const struct of_device_id omap_rproc_of_match[] = { + { + .compatible = "ti,omap4-dsp", + .data = &omap4_dsp_dev_data, + }, + { + .compatible = "ti,omap4-ipu", + .data = &omap4_ipu_dev_data, + }, + { + .compatible = "ti,omap5-dsp", + .data = &omap5_dsp_dev_data, + }, + { + .compatible = "ti,omap5-ipu", + .data = &omap5_ipu_dev_data, + }, + { + .compatible = "ti,dra7-dsp", + .data = &dra7_dsp_dev_data, + }, + { + .compatible = "ti,dra7-ipu", + .data = &dra7_ipu_dev_data, + }, + { + /* end */ + }, +}; +MODULE_DEVICE_TABLE(of, omap_rproc_of_match); + +static const char *omap_rproc_get_firmware(struct platform_device *pdev) +{ + const char *fw_name; + int ret; + + ret = of_property_read_string(pdev->dev.of_node, "firmware-name", + &fw_name); + if (ret) + return ERR_PTR(ret); + + return fw_name; +} + +static int omap_rproc_get_boot_data(struct platform_device *pdev, + struct rproc *rproc) +{ + struct device_node *np = pdev->dev.of_node; + struct omap_rproc *oproc = rproc->priv; + const struct omap_rproc_dev_data *data; + int ret; + + data = of_device_get_match_data(&pdev->dev); + if (!data) + return -ENODEV; + + if (!of_property_read_bool(np, "ti,bootreg")) + return 0; + + oproc->boot_data = devm_kzalloc(&pdev->dev, sizeof(*oproc->boot_data), + GFP_KERNEL); + if (!oproc->boot_data) + return -ENOMEM; + + oproc->boot_data->syscon = + syscon_regmap_lookup_by_phandle(np, "ti,bootreg"); + if (IS_ERR(oproc->boot_data->syscon)) { + ret = PTR_ERR(oproc->boot_data->syscon); + return ret; + } + + if (of_property_read_u32_index(np, "ti,bootreg", 1, + &oproc->boot_data->boot_reg)) { + dev_err(&pdev->dev, "couldn't get the boot register\n"); + return -EINVAL; + } + + of_property_read_u32_index(np, "ti,bootreg", 2, + &oproc->boot_data->boot_reg_shift); + + return 0; +} + +static int omap_rproc_of_get_internal_memories(struct platform_device *pdev, + struct rproc *rproc) +{ + struct omap_rproc *oproc = rproc->priv; + struct device *dev = &pdev->dev; + const struct omap_rproc_dev_data *data; + struct resource *res; + int num_mems; + int i; + + data = of_device_get_match_data(dev); + if (!data) + return -ENODEV; + + if (!data->mems) + return 0; + + num_mems = of_property_count_elems_of_size(dev->of_node, "reg", + sizeof(u32)) / 2; + + oproc->mem = devm_kcalloc(dev, num_mems, sizeof(*oproc->mem), + GFP_KERNEL); + if (!oproc->mem) + return -ENOMEM; + + for (i = 0; data->mems[i].name; i++) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + data->mems[i].name); + if (!res) { + dev_err(dev, "no memory defined for %s\n", + data->mems[i].name); + return -ENOMEM; + } + oproc->mem[i].cpu_addr = devm_ioremap_resource(dev, res); + if (IS_ERR(oproc->mem[i].cpu_addr)) { + dev_err(dev, "failed to parse and map %s memory\n", + data->mems[i].name); + return PTR_ERR(oproc->mem[i].cpu_addr); + } + oproc->mem[i].bus_addr = res->start; + oproc->mem[i].dev_addr = data->mems[i].dev_addr; + oproc->mem[i].size = resource_size(res); + + dev_dbg(dev, "memory %8s: bus addr %pa size 0x%x va %pK da 0x%x\n", + data->mems[i].name, &oproc->mem[i].bus_addr, + oproc->mem[i].size, oproc->mem[i].cpu_addr, + oproc->mem[i].dev_addr); + } + oproc->num_mems = num_mems; + + return 0; +} + +#ifdef CONFIG_OMAP_REMOTEPROC_WATCHDOG +static int omap_rproc_count_wdog_timers(struct device *dev) +{ + struct device_node *np = dev->of_node; + int ret; + + ret = of_count_phandle_with_args(np, "ti,watchdog-timers", NULL); + if (ret <= 0) { + dev_dbg(dev, "device does not have watchdog timers, status = %d\n", + ret); + ret = 0; + } + + return ret; +} +#else +static int omap_rproc_count_wdog_timers(struct device *dev) +{ + return 0; +} +#endif + +static int omap_rproc_of_get_timers(struct platform_device *pdev, + struct rproc *rproc) +{ + struct device_node *np = pdev->dev.of_node; + struct omap_rproc *oproc = rproc->priv; + struct device *dev = &pdev->dev; + int num_timers; + + /* + * Timer nodes are directly used in client nodes as phandles, so + * retrieve the count using appropriate size + */ + oproc->num_timers = of_count_phandle_with_args(np, "ti,timers", NULL); + if (oproc->num_timers <= 0) { + dev_dbg(dev, "device does not have timers, status = %d\n", + oproc->num_timers); + oproc->num_timers = 0; + } + + oproc->num_wd_timers = omap_rproc_count_wdog_timers(dev); + + num_timers = oproc->num_timers + oproc->num_wd_timers; + if (num_timers) { + oproc->timers = devm_kcalloc(dev, num_timers, + sizeof(*oproc->timers), + GFP_KERNEL); + if (!oproc->timers) + return -ENOMEM; + + dev_dbg(dev, "device has %d tick timers and %d watchdog timers\n", + oproc->num_timers, oproc->num_wd_timers); + } + + return 0; +} + static int omap_rproc_probe(struct platform_device *pdev) { - struct omap_rproc_pdata *pdata = pdev->dev.platform_data; + struct device_node *np = pdev->dev.of_node; struct omap_rproc *oproc; struct rproc *rproc; + const char *firmware; int ret; + struct reset_control *reset; + + if (!np) { + dev_err(&pdev->dev, "only DT-based devices are supported\n"); + return -ENODEV; + } + + reset = devm_reset_control_array_get_exclusive(&pdev->dev); + if (IS_ERR(reset)) + return PTR_ERR(reset); + + firmware = omap_rproc_get_firmware(pdev); + if (IS_ERR(firmware)) + return PTR_ERR(firmware); ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); if (ret) { @@ -188,24 +1307,60 @@ static int omap_rproc_probe(struct platform_device *pdev) return ret; } - rproc = rproc_alloc(&pdev->dev, pdata->name, &omap_rproc_ops, - pdata->firmware, sizeof(*oproc)); + rproc = rproc_alloc(&pdev->dev, dev_name(&pdev->dev), &omap_rproc_ops, + firmware, sizeof(*oproc)); if (!rproc) return -ENOMEM; oproc = rproc->priv; oproc->rproc = rproc; + oproc->reset = reset; /* All existing OMAP IPU and DSP processors have an MMU */ rproc->has_iommu = true; + ret = omap_rproc_of_get_internal_memories(pdev, rproc); + if (ret) + goto free_rproc; + + ret = omap_rproc_get_boot_data(pdev, rproc); + if (ret) + goto free_rproc; + + ret = omap_rproc_of_get_timers(pdev, rproc); + if (ret) + goto free_rproc; + + init_completion(&oproc->pm_comp); + oproc->autosuspend_delay = DEFAULT_AUTOSUSPEND_DELAY; + + of_property_read_u32(pdev->dev.of_node, "ti,autosuspend-delay-ms", + &oproc->autosuspend_delay); + + pm_runtime_set_autosuspend_delay(&pdev->dev, oproc->autosuspend_delay); + + oproc->fck = devm_clk_get(&pdev->dev, 0); + if (IS_ERR(oproc->fck)) { + ret = PTR_ERR(oproc->fck); + goto free_rproc; + } + + ret = of_reserved_mem_device_init(&pdev->dev); + if (ret) { + dev_warn(&pdev->dev, "device does not have specific CMA pool.\n"); + dev_warn(&pdev->dev, "Typically this should be provided,\n"); + dev_warn(&pdev->dev, "only omit if you know what you are doing.\n"); + } + platform_set_drvdata(pdev, rproc); ret = rproc_add(rproc); if (ret) - goto free_rproc; + goto release_mem; return 0; +release_mem: + of_reserved_mem_device_release(&pdev->dev); free_rproc: rproc_free(rproc); return ret; @@ -217,15 +1372,24 @@ static int omap_rproc_remove(struct platform_device *pdev) rproc_del(rproc); rproc_free(rproc); + of_reserved_mem_device_release(&pdev->dev); return 0; } +static const struct dev_pm_ops omap_rproc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(omap_rproc_suspend, omap_rproc_resume) + SET_RUNTIME_PM_OPS(omap_rproc_runtime_suspend, + omap_rproc_runtime_resume, NULL) +}; + static struct platform_driver omap_rproc_driver = { .probe = omap_rproc_probe, .remove = omap_rproc_remove, .driver = { .name = "omap-rproc", + .pm = &omap_rproc_pm_ops, + .of_match_table = omap_rproc_of_match, }, }; diff --git a/drivers/remoteproc/omap_remoteproc.h b/drivers/remoteproc/omap_remoteproc.h index f6d2036d383d..828e13256c02 100644 --- a/drivers/remoteproc/omap_remoteproc.h +++ b/drivers/remoteproc/omap_remoteproc.h @@ -1,35 +1,10 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ /* * Remote processor messaging * - * Copyright (C) 2011 Texas Instruments, Inc. + * Copyright (C) 2011-2020 Texas Instruments, Inc. * Copyright (C) 2011 Google, Inc. * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * * Neither the name Texas Instruments nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _OMAP_RPMSG_H @@ -56,6 +31,22 @@ * * @RP_MBOX_ABORT_REQUEST: a "please crash" request, used for testing the * recovery mechanism (to some extent). + * + * @RP_MBOX_SUSPEND_AUTO: auto suspend request for the remote processor + * + * @RP_MBOX_SUSPEND_SYSTEM: system suspend request for the remote processor + * + * @RP_MBOX_SUSPEND_ACK: successful response from remote processor for a + * suspend request + * + * @RP_MBOX_SUSPEND_CANCEL: a cancel suspend response from a remote processor + * on a suspend request + * + * Introduce new message definitions if any here. + * + * @RP_MBOX_END_MSG: Indicates end of known/defined messages from remote core + * This should be the last definition. + * */ enum omap_rp_mbox_messages { RP_MBOX_READY = 0xFFFFFF00, @@ -64,6 +55,11 @@ enum omap_rp_mbox_messages { RP_MBOX_ECHO_REQUEST = 0xFFFFFF03, RP_MBOX_ECHO_REPLY = 0xFFFFFF04, RP_MBOX_ABORT_REQUEST = 0xFFFFFF05, + RP_MBOX_SUSPEND_AUTO = 0xFFFFFF10, + RP_MBOX_SUSPEND_SYSTEM = 0xFFFFFF11, + RP_MBOX_SUSPEND_ACK = 0xFFFFFF12, + RP_MBOX_SUSPEND_CANCEL = 0xFFFFFF13, + RP_MBOX_END_MSG = 0xFFFFFF14, }; #endif /* _OMAP_RPMSG_H */ diff --git a/drivers/remoteproc/qcom_q6v5.c b/drivers/remoteproc/qcom_q6v5.c index cb0f4a0be032..111a442c993c 100644 --- a/drivers/remoteproc/qcom_q6v5.c +++ b/drivers/remoteproc/qcom_q6v5.c @@ -15,6 +15,8 @@ #include <linux/remoteproc.h> #include "qcom_q6v5.h" +#define Q6V5_PANIC_DELAY_MS 200 + /** * qcom_q6v5_prepare() - reinitialize the qcom_q6v5 context before start * @q6v5: reference to qcom_q6v5 context to be reinitialized @@ -163,6 +165,24 @@ int qcom_q6v5_request_stop(struct qcom_q6v5 *q6v5) EXPORT_SYMBOL_GPL(qcom_q6v5_request_stop); /** + * qcom_q6v5_panic() - panic handler to invoke a stop on the remote + * @q6v5: reference to qcom_q6v5 context + * + * Set the stop bit and sleep in order to allow the remote processor to flush + * its caches etc for post mortem debugging. + * + * Return: 200ms + */ +unsigned long qcom_q6v5_panic(struct qcom_q6v5 *q6v5) +{ + qcom_smem_state_update_bits(q6v5->state, + BIT(q6v5->stop_bit), BIT(q6v5->stop_bit)); + + return Q6V5_PANIC_DELAY_MS; +} +EXPORT_SYMBOL_GPL(qcom_q6v5_panic); + +/** * qcom_q6v5_init() - initializer of the q6v5 common struct * @q6v5: handle to be initialized * @pdev: platform_device reference for acquiring resources diff --git a/drivers/remoteproc/qcom_q6v5.h b/drivers/remoteproc/qcom_q6v5.h index 7ac92c1e0f49..c4ed887c1499 100644 --- a/drivers/remoteproc/qcom_q6v5.h +++ b/drivers/remoteproc/qcom_q6v5.h @@ -42,5 +42,6 @@ int qcom_q6v5_prepare(struct qcom_q6v5 *q6v5); int qcom_q6v5_unprepare(struct qcom_q6v5 *q6v5); int qcom_q6v5_request_stop(struct qcom_q6v5 *q6v5); int qcom_q6v5_wait_for_start(struct qcom_q6v5 *q6v5, int timeout); +unsigned long qcom_q6v5_panic(struct qcom_q6v5 *q6v5); #endif diff --git a/drivers/remoteproc/qcom_q6v5_adsp.c b/drivers/remoteproc/qcom_q6v5_adsp.c index e953886b2eb7..24a3db961d5e 100644 --- a/drivers/remoteproc/qcom_q6v5_adsp.c +++ b/drivers/remoteproc/qcom_q6v5_adsp.c @@ -270,7 +270,7 @@ static int adsp_stop(struct rproc *rproc) return ret; } -static void *adsp_da_to_va(struct rproc *rproc, u64 da, int len) +static void *adsp_da_to_va(struct rproc *rproc, u64 da, size_t len) { struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; int offset; @@ -282,12 +282,20 @@ static void *adsp_da_to_va(struct rproc *rproc, u64 da, int len) return adsp->mem_region + offset; } +static unsigned long adsp_panic(struct rproc *rproc) +{ + struct qcom_adsp *adsp = rproc->priv; + + return qcom_q6v5_panic(&adsp->q6v5); +} + static const struct rproc_ops adsp_ops = { .start = adsp_start, .stop = adsp_stop, .da_to_va = adsp_da_to_va, .parse_fw = qcom_register_dump_segments, .load = adsp_load, + .panic = adsp_panic, }; static int adsp_init_clock(struct qcom_adsp *adsp, const char **clk_ids) diff --git a/drivers/remoteproc/qcom_q6v5_mss.c b/drivers/remoteproc/qcom_q6v5_mss.c index f9ccce76e44b..ce49c3236ff7 100644 --- a/drivers/remoteproc/qcom_q6v5_mss.c +++ b/drivers/remoteproc/qcom_q6v5_mss.c @@ -381,23 +381,33 @@ static void q6v5_pds_disable(struct q6v5 *qproc, struct device **pds, } static int q6v5_xfer_mem_ownership(struct q6v5 *qproc, int *current_perm, - bool remote_owner, phys_addr_t addr, + bool local, bool remote, phys_addr_t addr, size_t size) { - struct qcom_scm_vmperm next; + struct qcom_scm_vmperm next[2]; + int perms = 0; if (!qproc->need_mem_protection) return 0; - if (remote_owner && *current_perm == BIT(QCOM_SCM_VMID_MSS_MSA)) - return 0; - if (!remote_owner && *current_perm == BIT(QCOM_SCM_VMID_HLOS)) + + if (local == !!(*current_perm & BIT(QCOM_SCM_VMID_HLOS)) && + remote == !!(*current_perm & BIT(QCOM_SCM_VMID_MSS_MSA))) return 0; - next.vmid = remote_owner ? QCOM_SCM_VMID_MSS_MSA : QCOM_SCM_VMID_HLOS; - next.perm = remote_owner ? QCOM_SCM_PERM_RW : QCOM_SCM_PERM_RWX; + if (local) { + next[perms].vmid = QCOM_SCM_VMID_HLOS; + next[perms].perm = QCOM_SCM_PERM_RWX; + perms++; + } + + if (remote) { + next[perms].vmid = QCOM_SCM_VMID_MSS_MSA; + next[perms].perm = QCOM_SCM_PERM_RW; + perms++; + } return qcom_scm_assign_mem(addr, ALIGN(size, SZ_4K), - current_perm, &next, 1); + current_perm, next, perms); } static int q6v5_load(struct rproc *rproc, const struct firmware *fw) @@ -803,7 +813,8 @@ static int q6v5_mpss_init_image(struct q6v5 *qproc, const struct firmware *fw) /* Hypervisor mapping to access metadata by modem */ mdata_perm = BIT(QCOM_SCM_VMID_HLOS); - ret = q6v5_xfer_mem_ownership(qproc, &mdata_perm, true, phys, size); + ret = q6v5_xfer_mem_ownership(qproc, &mdata_perm, false, true, + phys, size); if (ret) { dev_err(qproc->dev, "assigning Q6 access to metadata failed: %d\n", ret); @@ -821,7 +832,8 @@ static int q6v5_mpss_init_image(struct q6v5 *qproc, const struct firmware *fw) dev_err(qproc->dev, "MPSS header authentication failed: %d\n", ret); /* Metadata authentication done, remove modem access */ - xferop_ret = q6v5_xfer_mem_ownership(qproc, &mdata_perm, false, phys, size); + xferop_ret = q6v5_xfer_mem_ownership(qproc, &mdata_perm, true, false, + phys, size); if (xferop_ret) dev_warn(qproc->dev, "mdt buffer not reclaimed system may become unstable\n"); @@ -908,7 +920,7 @@ static int q6v5_mba_load(struct q6v5 *qproc) } /* Assign MBA image access in DDR to q6 */ - ret = q6v5_xfer_mem_ownership(qproc, &qproc->mba_perm, true, + ret = q6v5_xfer_mem_ownership(qproc, &qproc->mba_perm, false, true, qproc->mba_phys, qproc->mba_size); if (ret) { dev_err(qproc->dev, @@ -945,8 +957,8 @@ halt_axi_ports: q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_nc); reclaim_mba: - xfermemop_ret = q6v5_xfer_mem_ownership(qproc, &qproc->mba_perm, false, - qproc->mba_phys, + xfermemop_ret = q6v5_xfer_mem_ownership(qproc, &qproc->mba_perm, true, + false, qproc->mba_phys, qproc->mba_size); if (xfermemop_ret) { dev_err(qproc->dev, @@ -1003,11 +1015,6 @@ static void q6v5_mba_reclaim(struct q6v5 *qproc) writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); } - ret = q6v5_xfer_mem_ownership(qproc, &qproc->mpss_perm, - false, qproc->mpss_phys, - qproc->mpss_size); - WARN_ON(ret); - q6v5_reset_assert(qproc); q6v5_clk_disable(qproc->dev, qproc->reset_clks, @@ -1021,7 +1028,7 @@ static void q6v5_mba_reclaim(struct q6v5 *qproc) /* In case of failure or coredump scenario where reclaiming MBA memory * could not happen reclaim it here. */ - ret = q6v5_xfer_mem_ownership(qproc, &qproc->mba_perm, false, + ret = q6v5_xfer_mem_ownership(qproc, &qproc->mba_perm, true, false, qproc->mba_phys, qproc->mba_size); WARN_ON(ret); @@ -1037,6 +1044,23 @@ static void q6v5_mba_reclaim(struct q6v5 *qproc) } } +static int q6v5_reload_mba(struct rproc *rproc) +{ + struct q6v5 *qproc = rproc->priv; + const struct firmware *fw; + int ret; + + ret = request_firmware(&fw, rproc->firmware, qproc->dev); + if (ret < 0) + return ret; + + q6v5_load(rproc, fw); + ret = q6v5_mba_load(qproc); + release_firmware(fw); + + return ret; +} + static int q6v5_mpss_load(struct q6v5 *qproc) { const struct elf32_phdr *phdrs; @@ -1048,6 +1072,7 @@ static int q6v5_mpss_load(struct q6v5 *qproc) phys_addr_t boot_addr; phys_addr_t min_addr = PHYS_ADDR_MAX; phys_addr_t max_addr = 0; + u32 code_length; bool relocate = false; char *fw_name; size_t fw_name_len; @@ -1097,6 +1122,24 @@ static int q6v5_mpss_load(struct q6v5 *qproc) max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K); } + /** + * In case of a modem subsystem restart on secure devices, the modem + * memory can be reclaimed only after MBA is loaded. For modem cold + * boot this will be a nop + */ + q6v5_xfer_mem_ownership(qproc, &qproc->mpss_perm, true, false, + qproc->mpss_phys, qproc->mpss_size); + + /* Share ownership between Linux and MSS, during segment loading */ + ret = q6v5_xfer_mem_ownership(qproc, &qproc->mpss_perm, true, true, + qproc->mpss_phys, qproc->mpss_size); + if (ret) { + dev_err(qproc->dev, + "assigning Q6 access to mpss memory failed: %d\n", ret); + ret = -EAGAIN; + goto release_firmware; + } + mpss_reloc = relocate ? min_addr : qproc->mpss_phys; qproc->mpss_reloc = mpss_reloc; /* Load firmware segments */ @@ -1145,10 +1188,25 @@ static int q6v5_mpss_load(struct q6v5 *qproc) phdr->p_memsz - phdr->p_filesz); } size += phdr->p_memsz; + + code_length = readl(qproc->rmb_base + RMB_PMI_CODE_LENGTH_REG); + if (!code_length) { + boot_addr = relocate ? qproc->mpss_phys : min_addr; + writel(boot_addr, qproc->rmb_base + RMB_PMI_CODE_START_REG); + writel(RMB_CMD_LOAD_READY, qproc->rmb_base + RMB_MBA_COMMAND_REG); + } + writel(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH_REG); + + ret = readl(qproc->rmb_base + RMB_MBA_STATUS_REG); + if (ret < 0) { + dev_err(qproc->dev, "MPSS authentication failed: %d\n", + ret); + goto release_firmware; + } } /* Transfer ownership of modem ddr region to q6 */ - ret = q6v5_xfer_mem_ownership(qproc, &qproc->mpss_perm, true, + ret = q6v5_xfer_mem_ownership(qproc, &qproc->mpss_perm, false, true, qproc->mpss_phys, qproc->mpss_size); if (ret) { dev_err(qproc->dev, @@ -1157,11 +1215,6 @@ static int q6v5_mpss_load(struct q6v5 *qproc) goto release_firmware; } - boot_addr = relocate ? qproc->mpss_phys : min_addr; - writel(boot_addr, qproc->rmb_base + RMB_PMI_CODE_START_REG); - writel(RMB_CMD_LOAD_READY, qproc->rmb_base + RMB_MBA_COMMAND_REG); - writel(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH_REG); - ret = q6v5_rmb_mba_wait(qproc, RMB_MBA_AUTH_COMPLETE, 10000); if (ret == -ETIMEDOUT) dev_err(qproc->dev, "MPSS authentication timed out\n"); @@ -1186,8 +1239,16 @@ static void qcom_q6v5_dump_segment(struct rproc *rproc, void *ptr = rproc_da_to_va(rproc, segment->da, segment->size); /* Unlock mba before copying segments */ - if (!qproc->dump_mba_loaded) - ret = q6v5_mba_load(qproc); + if (!qproc->dump_mba_loaded) { + ret = q6v5_reload_mba(rproc); + if (!ret) { + /* Reset ownership back to Linux to copy segments */ + ret = q6v5_xfer_mem_ownership(qproc, &qproc->mpss_perm, + true, false, + qproc->mpss_phys, + qproc->mpss_size); + } + } if (!ptr || ret) memset(dest, 0xff, segment->size); @@ -1198,8 +1259,14 @@ static void qcom_q6v5_dump_segment(struct rproc *rproc, /* Reclaim mba after copying segments */ if (qproc->dump_segment_mask == qproc->dump_complete_mask) { - if (qproc->dump_mba_loaded) + if (qproc->dump_mba_loaded) { + /* Try to reset ownership back to Q6 */ + q6v5_xfer_mem_ownership(qproc, &qproc->mpss_perm, + false, true, + qproc->mpss_phys, + qproc->mpss_size); q6v5_mba_reclaim(qproc); + } } } @@ -1225,8 +1292,8 @@ static int q6v5_start(struct rproc *rproc) goto reclaim_mpss; } - xfermemop_ret = q6v5_xfer_mem_ownership(qproc, &qproc->mba_perm, false, - qproc->mba_phys, + xfermemop_ret = q6v5_xfer_mem_ownership(qproc, &qproc->mba_perm, true, + false, qproc->mba_phys, qproc->mba_size); if (xfermemop_ret) dev_err(qproc->dev, @@ -1239,10 +1306,6 @@ static int q6v5_start(struct rproc *rproc) return 0; reclaim_mpss: - xfermemop_ret = q6v5_xfer_mem_ownership(qproc, &qproc->mpss_perm, - false, qproc->mpss_phys, - qproc->mpss_size); - WARN_ON(xfermemop_ret); q6v5_mba_reclaim(qproc); return ret; @@ -1264,7 +1327,7 @@ static int q6v5_stop(struct rproc *rproc) return 0; } -static void *q6v5_da_to_va(struct rproc *rproc, u64 da, int len) +static void *q6v5_da_to_va(struct rproc *rproc, u64 da, size_t len) { struct q6v5 *qproc = rproc->priv; int offset; diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index edf9d0e1a235..7a63efb85405 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -222,7 +222,7 @@ static int adsp_stop(struct rproc *rproc) return ret; } -static void *adsp_da_to_va(struct rproc *rproc, u64 da, int len) +static void *adsp_da_to_va(struct rproc *rproc, u64 da, size_t len) { struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; int offset; @@ -234,12 +234,20 @@ static void *adsp_da_to_va(struct rproc *rproc, u64 da, int len) return adsp->mem_region + offset; } +static unsigned long adsp_panic(struct rproc *rproc) +{ + struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; + + return qcom_q6v5_panic(&adsp->q6v5); +} + static const struct rproc_ops adsp_ops = { .start = adsp_start, .stop = adsp_stop, .da_to_va = adsp_da_to_va, .parse_fw = qcom_register_dump_segments, .load = adsp_load, + .panic = adsp_panic, }; static int adsp_init_clock(struct qcom_adsp *adsp) diff --git a/drivers/remoteproc/qcom_q6v5_wcss.c b/drivers/remoteproc/qcom_q6v5_wcss.c index f93e1e4a1cc0..f1924b740a10 100644 --- a/drivers/remoteproc/qcom_q6v5_wcss.c +++ b/drivers/remoteproc/qcom_q6v5_wcss.c @@ -406,7 +406,7 @@ static int q6v5_wcss_stop(struct rproc *rproc) return 0; } -static void *q6v5_wcss_da_to_va(struct rproc *rproc, u64 da, int len) +static void *q6v5_wcss_da_to_va(struct rproc *rproc, u64 da, size_t len) { struct q6v5_wcss *wcss = rproc->priv; int offset; diff --git a/drivers/remoteproc/qcom_wcnss.c b/drivers/remoteproc/qcom_wcnss.c index dc135754bb9c..0c7afd038f0d 100644 --- a/drivers/remoteproc/qcom_wcnss.c +++ b/drivers/remoteproc/qcom_wcnss.c @@ -287,7 +287,7 @@ static int wcnss_stop(struct rproc *rproc) return ret; } -static void *wcnss_da_to_va(struct rproc *rproc, u64 da, int len) +static void *wcnss_da_to_va(struct rproc *rproc, u64 da, size_t len) { struct qcom_wcnss *wcnss = (struct qcom_wcnss *)rproc->priv; int offset; diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c index 097f33e4f1f3..e12a54e67588 100644 --- a/drivers/remoteproc/remoteproc_core.c +++ b/drivers/remoteproc/remoteproc_core.c @@ -16,6 +16,7 @@ #define pr_fmt(fmt) "%s: " fmt, __func__ +#include <linux/delay.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/device.h> @@ -26,6 +27,7 @@ #include <linux/string.h> #include <linux/debugfs.h> #include <linux/devcoredump.h> +#include <linux/rculist.h> #include <linux/remoteproc.h> #include <linux/iommu.h> #include <linux/idr.h> @@ -38,11 +40,13 @@ #include <linux/platform_device.h> #include "remoteproc_internal.h" +#include "remoteproc_elf_helpers.h" #define HIGH_BITS_MASK 0xFFFFFFFF00000000ULL static DEFINE_MUTEX(rproc_list_mutex); static LIST_HEAD(rproc_list); +static struct notifier_block rproc_panic_nb; typedef int (*rproc_handle_resource_t)(struct rproc *rproc, void *, int offset, int avail); @@ -185,7 +189,7 @@ EXPORT_SYMBOL(rproc_va_to_pa); * here the output of the DMA API for the carveouts, which should be more * correct. */ -void *rproc_da_to_va(struct rproc *rproc, u64 da, int len) +void *rproc_da_to_va(struct rproc *rproc, u64 da, size_t len) { struct rproc_mem_entry *carveout; void *ptr = NULL; @@ -224,7 +228,8 @@ 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) + * @name: carveout name to find (format string) + * @...: optional parameters matching @name string * * Platform driver has the capability to register some pre-allacoted carveout * (physically contiguous memory regions) before rproc firmware loading and @@ -318,8 +323,9 @@ int rproc_alloc_vring(struct rproc_vdev *rvdev, int i) struct device *dev = &rproc->dev; struct rproc_vring *rvring = &rvdev->vring[i]; struct fw_rsc_vdev *rsc; - int ret, size, notifyid; + int ret, notifyid; struct rproc_mem_entry *mem; + size_t size; /* actual size of vring (in bytes) */ size = PAGE_ALIGN(vring_size(rvring->len, rvring->align)); @@ -445,6 +451,7 @@ static void rproc_rvdev_release(struct device *dev) * rproc_handle_vdev() - handle a vdev fw resource * @rproc: the remote processor * @rsc: the vring resource descriptor + * @offset: offset of the resource entry * @avail: size of available data (for sanity checking the image) * * This resource entry requests the host to statically register a virtio @@ -587,6 +594,7 @@ void rproc_vdev_release(struct kref *ref) * rproc_handle_trace() - handle a shared trace buffer resource * @rproc: the remote processor * @rsc: the trace resource descriptor + * @offset: offset of the resource entry * @avail: size of available data (for sanity checking the image) * * In case the remote processor dumps trace logs into memory, @@ -652,6 +660,7 @@ static int rproc_handle_trace(struct rproc *rproc, struct fw_rsc_trace *rsc, * rproc_handle_devmem() - handle devmem resource entry * @rproc: remote processor handle * @rsc: the devmem resource entry + * @offset: offset of the resource entry * @avail: size of available data (for sanity checking the image) * * Remote processors commonly need to access certain on-chip peripherals. @@ -746,11 +755,12 @@ static int rproc_alloc_carveout(struct rproc *rproc, 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", mem->len); + "failed to allocate dma memory: len 0x%zx\n", + mem->len); return -ENOMEM; } - dev_dbg(dev, "carveout va %pK, dma %pad, len 0x%x\n", + dev_dbg(dev, "carveout va %pK, dma %pad, len 0x%zx\n", va, &dma, mem->len); if (mem->da != FW_RSC_ADDR_ANY && !rproc->domain) { @@ -853,6 +863,7 @@ static int rproc_release_carveout(struct rproc *rproc, * rproc_handle_carveout() - handle phys contig memory allocation requests * @rproc: rproc handle * @rsc: the resource entry + * @offset: offset of the resource entry * @avail: size of available data (for image validation) * * This function will handle firmware requests for allocation of physically @@ -957,7 +968,7 @@ EXPORT_SYMBOL(rproc_add_carveout); */ struct rproc_mem_entry * rproc_mem_entry_init(struct device *dev, - void *va, dma_addr_t dma, int len, u32 da, + void *va, dma_addr_t dma, size_t len, u32 da, int (*alloc)(struct rproc *, struct rproc_mem_entry *), int (*release)(struct rproc *, struct rproc_mem_entry *), const char *name, ...) @@ -999,7 +1010,7 @@ EXPORT_SYMBOL(rproc_mem_entry_init); * provided by client. */ struct rproc_mem_entry * -rproc_of_resm_mem_entry_init(struct device *dev, u32 of_resm_idx, int len, +rproc_of_resm_mem_entry_init(struct device *dev, u32 of_resm_idx, size_t len, u32 da, const char *name, ...) { struct rproc_mem_entry *mem; @@ -1022,7 +1033,7 @@ rproc_of_resm_mem_entry_init(struct device *dev, u32 of_resm_idx, int len, } EXPORT_SYMBOL(rproc_of_resm_mem_entry_init); -/** +/* * A lookup table for resource handlers. The indices are defined in * enum fw_resource_type. */ @@ -1270,7 +1281,7 @@ static void rproc_resource_cleanup(struct rproc *rproc) unmapped = iommu_unmap(rproc->domain, entry->da, entry->len); if (unmapped != entry->len) { /* nothing much to do besides complaining */ - dev_err(dev, "failed to unmap %u/%zu\n", entry->len, + dev_err(dev, "failed to unmap %zx/%zu\n", entry->len, unmapped); } @@ -1564,20 +1575,21 @@ EXPORT_SYMBOL(rproc_coredump_add_custom_segment); static void rproc_coredump(struct rproc *rproc) { struct rproc_dump_segment *segment; - struct elf32_phdr *phdr; - struct elf32_hdr *ehdr; + void *phdr; + void *ehdr; size_t data_size; size_t offset; void *data; void *ptr; + u8 class = rproc->elf_class; int phnum = 0; if (list_empty(&rproc->dump_segments)) return; - data_size = sizeof(*ehdr); + data_size = elf_size_of_hdr(class); list_for_each_entry(segment, &rproc->dump_segments, node) { - data_size += sizeof(*phdr) + segment->size; + data_size += elf_size_of_phdr(class) + segment->size; phnum++; } @@ -1588,33 +1600,33 @@ static void rproc_coredump(struct rproc *rproc) ehdr = data; - memset(ehdr, 0, sizeof(*ehdr)); - memcpy(ehdr->e_ident, ELFMAG, SELFMAG); - ehdr->e_ident[EI_CLASS] = ELFCLASS32; - ehdr->e_ident[EI_DATA] = ELFDATA2LSB; - ehdr->e_ident[EI_VERSION] = EV_CURRENT; - ehdr->e_ident[EI_OSABI] = ELFOSABI_NONE; - ehdr->e_type = ET_CORE; - ehdr->e_machine = EM_NONE; - ehdr->e_version = EV_CURRENT; - ehdr->e_entry = rproc->bootaddr; - ehdr->e_phoff = sizeof(*ehdr); - ehdr->e_ehsize = sizeof(*ehdr); - ehdr->e_phentsize = sizeof(*phdr); - ehdr->e_phnum = phnum; - - phdr = data + ehdr->e_phoff; - offset = ehdr->e_phoff + sizeof(*phdr) * ehdr->e_phnum; + memset(ehdr, 0, elf_size_of_hdr(class)); + /* e_ident field is common for both elf32 and elf64 */ + elf_hdr_init_ident(ehdr, class); + + elf_hdr_set_e_type(class, ehdr, ET_CORE); + elf_hdr_set_e_machine(class, ehdr, EM_NONE); + elf_hdr_set_e_version(class, ehdr, EV_CURRENT); + elf_hdr_set_e_entry(class, ehdr, rproc->bootaddr); + elf_hdr_set_e_phoff(class, ehdr, elf_size_of_hdr(class)); + elf_hdr_set_e_ehsize(class, ehdr, elf_size_of_hdr(class)); + elf_hdr_set_e_phentsize(class, ehdr, elf_size_of_phdr(class)); + elf_hdr_set_e_phnum(class, ehdr, phnum); + + phdr = data + elf_hdr_get_e_phoff(class, ehdr); + offset = elf_hdr_get_e_phoff(class, ehdr); + offset += elf_size_of_phdr(class) * elf_hdr_get_e_phnum(class, ehdr); + list_for_each_entry(segment, &rproc->dump_segments, node) { - memset(phdr, 0, sizeof(*phdr)); - phdr->p_type = PT_LOAD; - phdr->p_offset = offset; - phdr->p_vaddr = segment->da; - phdr->p_paddr = segment->da; - phdr->p_filesz = segment->size; - phdr->p_memsz = segment->size; - phdr->p_flags = PF_R | PF_W | PF_X; - phdr->p_align = 0; + memset(phdr, 0, elf_size_of_phdr(class)); + elf_phdr_set_p_type(class, phdr, PT_LOAD); + elf_phdr_set_p_offset(class, phdr, offset); + elf_phdr_set_p_vaddr(class, phdr, segment->da); + elf_phdr_set_p_paddr(class, phdr, segment->da); + elf_phdr_set_p_filesz(class, phdr, segment->size); + elf_phdr_set_p_memsz(class, phdr, segment->size); + elf_phdr_set_p_flags(class, phdr, PF_R | PF_W | PF_X); + elf_phdr_set_p_align(class, phdr, 0); if (segment->dump) { segment->dump(rproc, segment, data + offset); @@ -1630,8 +1642,8 @@ static void rproc_coredump(struct rproc *rproc) } } - offset += phdr->p_filesz; - phdr++; + offset += elf_phdr_get_p_filesz(class, phdr); + phdr += elf_size_of_phdr(class); } dev_coredumpv(&rproc->dev, data, data_size, GFP_KERNEL); @@ -1653,12 +1665,16 @@ int rproc_trigger_recovery(struct rproc *rproc) struct device *dev = &rproc->dev; int ret; - dev_err(dev, "recovering %s\n", rproc->name); - ret = mutex_lock_interruptible(&rproc->lock); if (ret) return ret; + /* State could have changed before we got the mutex */ + if (rproc->state != RPROC_CRASHED) + goto unlock_mutex; + + dev_err(dev, "recovering %s\n", rproc->name); + ret = rproc_stop(rproc, true); if (ret) goto unlock_mutex; @@ -1685,6 +1701,7 @@ unlock_mutex: /** * rproc_crash_handler_work() - handle a crash + * @work: work treating the crash * * This function needs to handle everything related to a crash, like cpu * registers and stack dump, information to help to debug the fatal error, etc. @@ -1854,8 +1871,8 @@ struct rproc *rproc_get_by_phandle(phandle phandle) if (!np) return NULL; - mutex_lock(&rproc_list_mutex); - list_for_each_entry(r, &rproc_list, node) { + rcu_read_lock(); + list_for_each_entry_rcu(r, &rproc_list, node) { if (r->dev.parent && r->dev.parent->of_node == np) { /* prevent underlying implementation from being removed */ if (!try_module_get(r->dev.parent->driver->owner)) { @@ -1868,7 +1885,7 @@ struct rproc *rproc_get_by_phandle(phandle phandle) break; } } - mutex_unlock(&rproc_list_mutex); + rcu_read_unlock(); of_node_put(np); @@ -1925,7 +1942,7 @@ int rproc_add(struct rproc *rproc) /* expose to rproc_get_by_phandle users */ mutex_lock(&rproc_list_mutex); - list_add(&rproc->node, &rproc_list); + list_add_rcu(&rproc->node, &rproc_list); mutex_unlock(&rproc_list_mutex); return 0; @@ -2029,6 +2046,7 @@ struct rproc *rproc_alloc(struct device *dev, const char *name, rproc->name = name; rproc->priv = &rproc[1]; rproc->auto_boot = true; + rproc->elf_class = ELFCLASS32; device_initialize(&rproc->dev); rproc->dev.parent = dev; @@ -2053,7 +2071,8 @@ struct rproc *rproc_alloc(struct device *dev, const char *name, rproc->ops->load = rproc_elf_load_segments; rproc->ops->parse_fw = rproc_elf_load_rsc_table; rproc->ops->find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table; - rproc->ops->sanity_check = rproc_elf_sanity_check; + if (!rproc->ops->sanity_check) + rproc->ops->sanity_check = rproc_elf32_sanity_check; rproc->ops->get_boot_addr = rproc_elf_get_boot_addr; } @@ -2140,9 +2159,12 @@ int rproc_del(struct rproc *rproc) /* the rproc is downref'ed as soon as it's removed from the klist */ mutex_lock(&rproc_list_mutex); - list_del(&rproc->node); + list_del_rcu(&rproc->node); mutex_unlock(&rproc_list_mutex); + /* Ensure that no readers of rproc_list are still active */ + synchronize_rcu(); + device_del(&rproc->dev); return 0; @@ -2216,10 +2238,50 @@ void rproc_report_crash(struct rproc *rproc, enum rproc_crash_type type) } EXPORT_SYMBOL(rproc_report_crash); +static int rproc_panic_handler(struct notifier_block *nb, unsigned long event, + void *ptr) +{ + unsigned int longest = 0; + struct rproc *rproc; + unsigned int d; + + rcu_read_lock(); + list_for_each_entry_rcu(rproc, &rproc_list, node) { + if (!rproc->ops->panic || rproc->state != RPROC_RUNNING) + continue; + + d = rproc->ops->panic(rproc); + longest = max(longest, d); + } + rcu_read_unlock(); + + /* + * Delay for the longest requested duration before returning. This can + * be used by the remoteproc drivers to give the remote processor time + * to perform any requested operations (such as flush caches), when + * it's not possible to signal the Linux side due to the panic. + */ + mdelay(longest); + + return NOTIFY_DONE; +} + +static void __init rproc_init_panic(void) +{ + rproc_panic_nb.notifier_call = rproc_panic_handler; + atomic_notifier_chain_register(&panic_notifier_list, &rproc_panic_nb); +} + +static void __exit rproc_exit_panic(void) +{ + atomic_notifier_chain_unregister(&panic_notifier_list, &rproc_panic_nb); +} + static int __init remoteproc_init(void) { rproc_init_sysfs(); rproc_init_debugfs(); + rproc_init_panic(); return 0; } @@ -2229,6 +2291,7 @@ static void __exit remoteproc_exit(void) { ida_destroy(&rproc_dev_index); + rproc_exit_panic(); rproc_exit_debugfs(); rproc_exit_sysfs(); } diff --git a/drivers/remoteproc/remoteproc_debugfs.c b/drivers/remoteproc/remoteproc_debugfs.c index dd93cf04e17f..d734cadb16e3 100644 --- a/drivers/remoteproc/remoteproc_debugfs.c +++ b/drivers/remoteproc/remoteproc_debugfs.c @@ -138,16 +138,16 @@ rproc_recovery_write(struct file *filp, const char __user *user_buf, buf[count - 1] = '\0'; if (!strncmp(buf, "enabled", count)) { + /* change the flag and begin the recovery process if needed */ rproc->recovery_disabled = false; - /* if rproc has crashed, trigger recovery */ - if (rproc->state == RPROC_CRASHED) - rproc_trigger_recovery(rproc); + rproc_trigger_recovery(rproc); } else if (!strncmp(buf, "disabled", count)) { rproc->recovery_disabled = true; } else if (!strncmp(buf, "recover", count)) { - /* if rproc has crashed, trigger recovery */ - if (rproc->state == RPROC_CRASHED) - rproc_trigger_recovery(rproc); + /* begin the recovery process without changing the flag */ + rproc_trigger_recovery(rproc); + } else { + return -EINVAL; } return count; @@ -293,7 +293,7 @@ static int rproc_carveouts_show(struct seq_file *seq, void *p) 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); - seq_printf(seq, "\tLength: 0x%x Bytes\n\n", carveout->len); + seq_printf(seq, "\tLength: 0x%zx Bytes\n\n", carveout->len); } return 0; @@ -349,7 +349,7 @@ void rproc_create_debug_dir(struct rproc *rproc) debugfs_create_file("name", 0400, rproc->dbg_dir, rproc, &rproc_name_ops); - debugfs_create_file("recovery", 0400, rproc->dbg_dir, + debugfs_create_file("recovery", 0600, rproc->dbg_dir, rproc, &rproc_recovery_ops); debugfs_create_file("crash", 0200, rproc->dbg_dir, rproc, &rproc_crash_ops); diff --git a/drivers/remoteproc/remoteproc_elf_helpers.h b/drivers/remoteproc/remoteproc_elf_helpers.h new file mode 100644 index 000000000000..4b6be7b6bf4d --- /dev/null +++ b/drivers/remoteproc/remoteproc_elf_helpers.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Remote processor elf helpers defines + * + * Copyright (C) 2020 Kalray, Inc. + */ + +#ifndef REMOTEPROC_ELF_LOADER_H +#define REMOTEPROC_ELF_LOADER_H + +#include <linux/elf.h> +#include <linux/types.h> + +/** + * fw_elf_get_class - Get elf class + * @fw: the ELF firmware image + * + * Note that we use and elf32_hdr to access the class since the start of the + * struct is the same for both elf class + * + * Return: elf class of the firmware + */ +static inline u8 fw_elf_get_class(const struct firmware *fw) +{ + struct elf32_hdr *ehdr = (struct elf32_hdr *)fw->data; + + return ehdr->e_ident[EI_CLASS]; +} + +static inline void elf_hdr_init_ident(struct elf32_hdr *hdr, u8 class) +{ + memcpy(hdr->e_ident, ELFMAG, SELFMAG); + hdr->e_ident[EI_CLASS] = class; + hdr->e_ident[EI_DATA] = ELFDATA2LSB; + hdr->e_ident[EI_VERSION] = EV_CURRENT; + hdr->e_ident[EI_OSABI] = ELFOSABI_NONE; +} + +/* Generate getter and setter for a specific elf struct/field */ +#define ELF_GEN_FIELD_GET_SET(__s, __field, __type) \ +static inline __type elf_##__s##_get_##__field(u8 class, const void *arg) \ +{ \ + if (class == ELFCLASS32) \ + return (__type) ((const struct elf32_##__s *) arg)->__field; \ + else \ + return (__type) ((const struct elf64_##__s *) arg)->__field; \ +} \ +static inline void elf_##__s##_set_##__field(u8 class, void *arg, \ + __type value) \ +{ \ + if (class == ELFCLASS32) \ + ((struct elf32_##__s *) arg)->__field = (__type) value; \ + else \ + ((struct elf64_##__s *) arg)->__field = (__type) value; \ +} + +ELF_GEN_FIELD_GET_SET(hdr, e_entry, u64) +ELF_GEN_FIELD_GET_SET(hdr, e_phnum, u16) +ELF_GEN_FIELD_GET_SET(hdr, e_shnum, u16) +ELF_GEN_FIELD_GET_SET(hdr, e_phoff, u64) +ELF_GEN_FIELD_GET_SET(hdr, e_shoff, u64) +ELF_GEN_FIELD_GET_SET(hdr, e_shstrndx, u16) +ELF_GEN_FIELD_GET_SET(hdr, e_machine, u16) +ELF_GEN_FIELD_GET_SET(hdr, e_type, u16) +ELF_GEN_FIELD_GET_SET(hdr, e_version, u32) +ELF_GEN_FIELD_GET_SET(hdr, e_ehsize, u32) +ELF_GEN_FIELD_GET_SET(hdr, e_phentsize, u16) + +ELF_GEN_FIELD_GET_SET(phdr, p_paddr, u64) +ELF_GEN_FIELD_GET_SET(phdr, p_vaddr, u64) +ELF_GEN_FIELD_GET_SET(phdr, p_filesz, u64) +ELF_GEN_FIELD_GET_SET(phdr, p_memsz, u64) +ELF_GEN_FIELD_GET_SET(phdr, p_type, u32) +ELF_GEN_FIELD_GET_SET(phdr, p_offset, u64) +ELF_GEN_FIELD_GET_SET(phdr, p_flags, u32) +ELF_GEN_FIELD_GET_SET(phdr, p_align, u64) + +ELF_GEN_FIELD_GET_SET(shdr, sh_size, u64) +ELF_GEN_FIELD_GET_SET(shdr, sh_offset, u64) +ELF_GEN_FIELD_GET_SET(shdr, sh_name, u32) +ELF_GEN_FIELD_GET_SET(shdr, sh_addr, u64) + +#define ELF_STRUCT_SIZE(__s) \ +static inline unsigned long elf_size_of_##__s(u8 class) \ +{ \ + if (class == ELFCLASS32)\ + return sizeof(struct elf32_##__s); \ + else \ + return sizeof(struct elf64_##__s); \ +} + +ELF_STRUCT_SIZE(shdr) +ELF_STRUCT_SIZE(phdr) +ELF_STRUCT_SIZE(hdr) + +#endif /* REMOTEPROC_ELF_LOADER_H */ diff --git a/drivers/remoteproc/remoteproc_elf_loader.c b/drivers/remoteproc/remoteproc_elf_loader.c index 606aae166eba..16e2c496fd45 100644 --- a/drivers/remoteproc/remoteproc_elf_loader.c +++ b/drivers/remoteproc/remoteproc_elf_loader.c @@ -23,20 +23,29 @@ #include <linux/elf.h> #include "remoteproc_internal.h" +#include "remoteproc_elf_helpers.h" /** - * rproc_elf_sanity_check() - Sanity Check ELF firmware image + * rproc_elf_sanity_check() - Sanity Check for ELF32/ELF64 firmware image * @rproc: the remote processor handle * @fw: the ELF firmware image * - * Make sure this fw image is sane. + * Make sure this fw image is sane (ie a correct ELF32/ELF64 file). */ int rproc_elf_sanity_check(struct rproc *rproc, const struct firmware *fw) { const char *name = rproc->firmware; struct device *dev = &rproc->dev; + /* + * Elf files are beginning with the same structure. Thus, to simplify + * header parsing, we can use the elf32_hdr one for both elf64 and + * elf32. + */ struct elf32_hdr *ehdr; + u32 elf_shdr_get_size; + u64 phoff, shoff; char class; + u16 phnum; if (!fw) { dev_err(dev, "failed to load %s\n", name); @@ -50,13 +59,22 @@ int rproc_elf_sanity_check(struct rproc *rproc, const struct firmware *fw) ehdr = (struct elf32_hdr *)fw->data; - /* We only support ELF32 at this point */ + if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) { + dev_err(dev, "Image is corrupted (bad magic)\n"); + return -EINVAL; + } + class = ehdr->e_ident[EI_CLASS]; - if (class != ELFCLASS32) { + if (class != ELFCLASS32 && class != ELFCLASS64) { dev_err(dev, "Unsupported class: %d\n", class); return -EINVAL; } + if (class == ELFCLASS64 && fw->size < sizeof(struct elf64_hdr)) { + dev_err(dev, "elf64 header is too small\n"); + return -EINVAL; + } + /* We assume the firmware has the same endianness as the host */ # ifdef __LITTLE_ENDIAN if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB) { @@ -67,31 +85,55 @@ int rproc_elf_sanity_check(struct rproc *rproc, const struct firmware *fw) return -EINVAL; } - if (fw->size < ehdr->e_shoff + sizeof(struct elf32_shdr)) { - dev_err(dev, "Image is too small\n"); - return -EINVAL; - } + phoff = elf_hdr_get_e_phoff(class, fw->data); + shoff = elf_hdr_get_e_shoff(class, fw->data); + phnum = elf_hdr_get_e_phnum(class, fw->data); + elf_shdr_get_size = elf_size_of_shdr(class); - if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) { - dev_err(dev, "Image is corrupted (bad magic)\n"); + if (fw->size < shoff + elf_shdr_get_size) { + dev_err(dev, "Image is too small\n"); return -EINVAL; } - if (ehdr->e_phnum == 0) { + if (phnum == 0) { dev_err(dev, "No loadable segments\n"); return -EINVAL; } - if (ehdr->e_phoff > fw->size) { + if (phoff > fw->size) { dev_err(dev, "Firmware size is too small\n"); return -EINVAL; } + dev_dbg(dev, "Firmware is an elf%d file\n", + class == ELFCLASS32 ? 32 : 64); + return 0; } EXPORT_SYMBOL(rproc_elf_sanity_check); /** + * rproc_elf_sanity_check() - Sanity Check ELF32 firmware image + * @rproc: the remote processor handle + * @fw: the ELF32 firmware image + * + * Make sure this fw image is sane. + */ +int rproc_elf32_sanity_check(struct rproc *rproc, const struct firmware *fw) +{ + int ret = rproc_elf_sanity_check(rproc, fw); + + if (ret) + return ret; + + if (fw_elf_get_class(fw) == ELFCLASS32) + return 0; + + return -EINVAL; +} +EXPORT_SYMBOL(rproc_elf32_sanity_check); + +/** * rproc_elf_get_boot_addr() - Get rproc's boot address. * @rproc: the remote processor handle * @fw: the ELF firmware image @@ -102,11 +144,9 @@ EXPORT_SYMBOL(rproc_elf_sanity_check); * Note that the boot address is not a configurable property of all remote * processors. Some will always boot at a specific hard-coded address. */ -u32 rproc_elf_get_boot_addr(struct rproc *rproc, const struct firmware *fw) +u64 rproc_elf_get_boot_addr(struct rproc *rproc, const struct firmware *fw) { - struct elf32_hdr *ehdr = (struct elf32_hdr *)fw->data; - - return ehdr->e_entry; + return elf_hdr_get_e_entry(fw_elf_get_class(fw), fw->data); } EXPORT_SYMBOL(rproc_elf_get_boot_addr); @@ -137,53 +177,65 @@ EXPORT_SYMBOL(rproc_elf_get_boot_addr); int rproc_elf_load_segments(struct rproc *rproc, const struct firmware *fw) { struct device *dev = &rproc->dev; - struct elf32_hdr *ehdr; - struct elf32_phdr *phdr; + const void *ehdr, *phdr; int i, ret = 0; + u16 phnum; const u8 *elf_data = fw->data; + u8 class = fw_elf_get_class(fw); + u32 elf_phdr_get_size = elf_size_of_phdr(class); - ehdr = (struct elf32_hdr *)elf_data; - phdr = (struct elf32_phdr *)(elf_data + ehdr->e_phoff); + ehdr = elf_data; + phnum = elf_hdr_get_e_phnum(class, ehdr); + phdr = elf_data + elf_hdr_get_e_phoff(class, ehdr); /* 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; - u32 offset = phdr->p_offset; + for (i = 0; i < phnum; i++, phdr += elf_phdr_get_size) { + u64 da = elf_phdr_get_p_paddr(class, phdr); + u64 memsz = elf_phdr_get_p_memsz(class, phdr); + u64 filesz = elf_phdr_get_p_filesz(class, phdr); + u64 offset = elf_phdr_get_p_offset(class, phdr); + u32 type = elf_phdr_get_p_type(class, phdr); void *ptr; - if (phdr->p_type != PT_LOAD) + if (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); + dev_dbg(dev, "phdr: type %d da 0x%llx memsz 0x%llx filesz 0x%llx\n", + type, da, memsz, filesz); if (filesz > memsz) { - dev_err(dev, "bad phdr filesz 0x%x memsz 0x%x\n", + dev_err(dev, "bad phdr filesz 0x%llx memsz 0x%llx\n", filesz, memsz); ret = -EINVAL; break; } if (offset + filesz > fw->size) { - dev_err(dev, "truncated fw: need 0x%x avail 0x%zx\n", + dev_err(dev, "truncated fw: need 0x%llx avail 0x%zx\n", offset + filesz, fw->size); ret = -EINVAL; break; } + if (!rproc_u64_fit_in_size_t(memsz)) { + dev_err(dev, "size (%llx) does not fit in size_t type\n", + memsz); + ret = -EOVERFLOW; + 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); + dev_err(dev, "bad phdr da 0x%llx mem 0x%llx\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); + if (filesz) + memcpy(ptr, elf_data + offset, filesz); /* * Zero out remaining memory for this segment. @@ -196,28 +248,42 @@ int rproc_elf_load_segments(struct rproc *rproc, const struct firmware *fw) memset(ptr + filesz, 0, memsz - filesz); } + if (ret == 0) + rproc->elf_class = class; + return ret; } EXPORT_SYMBOL(rproc_elf_load_segments); -static struct elf32_shdr * -find_table(struct device *dev, struct elf32_hdr *ehdr, size_t fw_size) +static const void * +find_table(struct device *dev, const struct firmware *fw) { - struct elf32_shdr *shdr; + const void *shdr, *name_table_shdr; int i; const char *name_table; struct resource_table *table = NULL; - const u8 *elf_data = (void *)ehdr; + const u8 *elf_data = (void *)fw->data; + u8 class = fw_elf_get_class(fw); + size_t fw_size = fw->size; + const void *ehdr = elf_data; + u16 shnum = elf_hdr_get_e_shnum(class, ehdr); + u32 elf_shdr_get_size = elf_size_of_shdr(class); + u16 shstrndx = elf_hdr_get_e_shstrndx(class, ehdr); /* 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; - - for (i = 0; i < ehdr->e_shnum; i++, shdr++) { - u32 size = shdr->sh_size; - u32 offset = shdr->sh_offset; - - if (strcmp(name_table + shdr->sh_name, ".resource_table")) + /* First, get the section header according to the elf class */ + shdr = elf_data + elf_hdr_get_e_shoff(class, ehdr); + /* Compute name table section header entry in shdr array */ + name_table_shdr = shdr + (shstrndx * elf_shdr_get_size); + /* Finally, compute the name table section address in elf */ + name_table = elf_data + elf_shdr_get_sh_offset(class, name_table_shdr); + + for (i = 0; i < shnum; i++, shdr += elf_shdr_get_size) { + u64 size = elf_shdr_get_sh_size(class, shdr); + u64 offset = elf_shdr_get_sh_offset(class, shdr); + u32 name = elf_shdr_get_sh_name(class, shdr); + + if (strcmp(name_table + name, ".resource_table")) continue; table = (struct resource_table *)(elf_data + offset); @@ -270,21 +336,21 @@ find_table(struct device *dev, struct elf32_hdr *ehdr, size_t fw_size) */ int rproc_elf_load_rsc_table(struct rproc *rproc, const struct firmware *fw) { - struct elf32_hdr *ehdr; - struct elf32_shdr *shdr; + const void *shdr; struct device *dev = &rproc->dev; struct resource_table *table = NULL; const u8 *elf_data = fw->data; size_t tablesz; + u8 class = fw_elf_get_class(fw); + u64 sh_offset; - ehdr = (struct elf32_hdr *)elf_data; - - shdr = find_table(dev, ehdr, fw->size); + shdr = find_table(dev, fw); if (!shdr) return -EINVAL; - table = (struct resource_table *)(elf_data + shdr->sh_offset); - tablesz = shdr->sh_size; + sh_offset = elf_shdr_get_sh_offset(class, shdr); + table = (struct resource_table *)(elf_data + sh_offset); + tablesz = elf_shdr_get_sh_size(class, shdr); /* * Create a copy of the resource table. When a virtio device starts @@ -317,13 +383,24 @@ EXPORT_SYMBOL(rproc_elf_load_rsc_table); 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; + const void *shdr; + u64 sh_addr, sh_size; + u8 class = fw_elf_get_class(fw); + struct device *dev = &rproc->dev; - shdr = find_table(&rproc->dev, ehdr, fw->size); + shdr = find_table(&rproc->dev, fw); if (!shdr) return NULL; - return rproc_da_to_va(rproc, shdr->sh_addr, shdr->sh_size); + sh_addr = elf_shdr_get_sh_addr(class, shdr); + sh_size = elf_shdr_get_sh_size(class, shdr); + + if (!rproc_u64_fit_in_size_t(sh_size)) { + dev_err(dev, "size (%llx) does not fit in size_t type\n", + sh_size); + return NULL; + } + + return rproc_da_to_va(rproc, sh_addr, sh_size); } EXPORT_SYMBOL(rproc_elf_find_loaded_rsc_table); diff --git a/drivers/remoteproc/remoteproc_internal.h b/drivers/remoteproc/remoteproc_internal.h index 493ef9262411..b389dc79da81 100644 --- a/drivers/remoteproc/remoteproc_internal.h +++ b/drivers/remoteproc/remoteproc_internal.h @@ -50,12 +50,13 @@ void rproc_exit_sysfs(void); 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); +void *rproc_da_to_va(struct rproc *rproc, u64 da, size_t len); phys_addr_t rproc_va_to_pa(void *cpu_addr); int rproc_trigger_recovery(struct rproc *rproc); +int rproc_elf32_sanity_check(struct rproc *rproc, const struct firmware *fw); int rproc_elf_sanity_check(struct rproc *rproc, const struct firmware *fw); -u32 rproc_elf_get_boot_addr(struct rproc *rproc, const struct firmware *fw); +u64 rproc_elf_get_boot_addr(struct rproc *rproc, const struct firmware *fw); 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, @@ -73,7 +74,7 @@ int rproc_fw_sanity_check(struct rproc *rproc, const struct firmware *fw) } static inline -u32 rproc_get_boot_addr(struct rproc *rproc, const struct firmware *fw) +u64 rproc_get_boot_addr(struct rproc *rproc, const struct firmware *fw) { if (rproc->ops->get_boot_addr) return rproc->ops->get_boot_addr(rproc, fw); @@ -119,4 +120,13 @@ struct resource_table *rproc_find_loaded_rsc_table(struct rproc *rproc, return NULL; } +static inline +bool rproc_u64_fit_in_size_t(u64 val) +{ + if (sizeof(size_t) == sizeof(u64)) + return true; + + return (val <= (size_t) -1); +} + #endif /* REMOTEPROC_INTERNAL_H */ diff --git a/drivers/remoteproc/remoteproc_virtio.c b/drivers/remoteproc/remoteproc_virtio.c index 8c07cb2ca8ba..e61d738d9b47 100644 --- a/drivers/remoteproc/remoteproc_virtio.c +++ b/drivers/remoteproc/remoteproc_virtio.c @@ -320,6 +320,7 @@ static void rproc_virtio_dev_release(struct device *dev) /** * rproc_add_virtio_dev() - register an rproc-induced virtio device * @rvdev: the remote vdev + * @id: the device type identification (used to match it with a driver). * * This function registers a virtio device. This vdev's partent is * the rproc device. @@ -334,6 +335,13 @@ int rproc_add_virtio_dev(struct rproc_vdev *rvdev, int id) struct rproc_mem_entry *mem; int ret; + if (rproc->ops->kick == NULL) { + ret = -EINVAL; + dev_err(dev, ".kick method not defined for %s", + rproc->name); + goto out; + } + /* Try to find dedicated vdev buffer carveout */ mem = rproc_find_carveout_by_name(rproc, "vdev%dbuffer", rvdev->index); if (mem) { diff --git a/drivers/remoteproc/st_remoteproc.c b/drivers/remoteproc/st_remoteproc.c index ee13d23b43a9..a6cbfa452764 100644 --- a/drivers/remoteproc/st_remoteproc.c +++ b/drivers/remoteproc/st_remoteproc.c @@ -190,7 +190,7 @@ static int st_rproc_start(struct rproc *rproc) } } - dev_info(&rproc->dev, "Started from 0x%x\n", rproc->bootaddr); + dev_info(&rproc->dev, "Started from 0x%llx\n", rproc->bootaddr); return 0; @@ -233,7 +233,7 @@ static const struct rproc_ops st_rproc_ops = { .parse_fw = st_rproc_parse_fw, .load = rproc_elf_load_segments, .find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table, - .sanity_check = rproc_elf_sanity_check, + .sanity_check = rproc_elf32_sanity_check, .get_boot_addr = rproc_elf_get_boot_addr, }; diff --git a/drivers/remoteproc/st_slim_rproc.c b/drivers/remoteproc/st_slim_rproc.c index 04492fead3c8..3cca8b65a8db 100644 --- a/drivers/remoteproc/st_slim_rproc.c +++ b/drivers/remoteproc/st_slim_rproc.c @@ -174,7 +174,7 @@ static int slim_rproc_stop(struct rproc *rproc) return 0; } -static void *slim_rproc_da_to_va(struct rproc *rproc, u64 da, int len) +static void *slim_rproc_da_to_va(struct rproc *rproc, u64 da, size_t len) { struct st_slim_rproc *slim_rproc = rproc->priv; void *va = NULL; @@ -191,7 +191,7 @@ static void *slim_rproc_da_to_va(struct rproc *rproc, u64 da, int len) } } - dev_dbg(&rproc->dev, "da = 0x%llx len = 0x%x va = 0x%pK\n", + dev_dbg(&rproc->dev, "da = 0x%llx len = 0x%zx va = 0x%pK\n", da, len, va); return va; @@ -203,7 +203,7 @@ static const struct rproc_ops slim_rproc_ops = { .da_to_va = slim_rproc_da_to_va, .get_boot_addr = rproc_elf_get_boot_addr, .load = rproc_elf_load_segments, - .sanity_check = rproc_elf_sanity_check, + .sanity_check = rproc_elf32_sanity_check, }; /** diff --git a/drivers/remoteproc/stm32_rproc.c b/drivers/remoteproc/stm32_rproc.c index a18f88044111..6a66dbf2df40 100644 --- a/drivers/remoteproc/stm32_rproc.c +++ b/drivers/remoteproc/stm32_rproc.c @@ -505,7 +505,7 @@ static struct rproc_ops st_rproc_ops = { .load = rproc_elf_load_segments, .parse_fw = stm32_rproc_parse_fw, .find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table, - .sanity_check = rproc_elf_sanity_check, + .sanity_check = rproc_elf32_sanity_check, .get_boot_addr = rproc_elf_get_boot_addr, }; @@ -602,7 +602,7 @@ static int stm32_rproc_parse_dt(struct platform_device *pdev) err = stm32_rproc_get_syscon(np, "st,syscfg-pdds", &ddata->pdds); if (err) - dev_warn(dev, "failed to get pdds\n"); + dev_info(dev, "failed to get pdds\n"); rproc->auto_boot = of_property_read_bool(np, "st,auto-boot"); diff --git a/drivers/remoteproc/wkup_m3_rproc.c b/drivers/remoteproc/wkup_m3_rproc.c index 3984e585c847..b9349d684258 100644 --- a/drivers/remoteproc/wkup_m3_rproc.c +++ b/drivers/remoteproc/wkup_m3_rproc.c @@ -80,14 +80,14 @@ static int wkup_m3_rproc_stop(struct rproc *rproc) return 0; } -static void *wkup_m3_rproc_da_to_va(struct rproc *rproc, u64 da, int len) +static void *wkup_m3_rproc_da_to_va(struct rproc *rproc, u64 da, size_t len) { struct wkup_m3_rproc *wkupm3 = rproc->priv; void *va = NULL; int i; u32 offset; - if (len <= 0) + if (len == 0) return NULL; for (i = 0; i < WKUPM3_MEM_MAX; i++) { |