summaryrefslogtreecommitdiffstats
path: root/drivers/remoteproc
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2020-04-02 15:42:13 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2020-04-02 15:42:13 -0700
commitc6570114316fbbce4ac5f970578adaf3cbf07ec3 (patch)
tree3a910bb902037af2f75e3a5b47a3bcc4d688b11e /drivers/remoteproc
parentac438771ccb4479528594c7e19f2c39cf1814a86 (diff)
parenta7084c3d47c4aaedcca217ce87b7b5b5fe3cfa35 (diff)
downloadlinux-c6570114316fbbce4ac5f970578adaf3cbf07ec3.tar.bz2
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')
-rw-r--r--drivers/remoteproc/Kconfig14
-rw-r--r--drivers/remoteproc/imx_rproc.c11
-rw-r--r--drivers/remoteproc/keystone_remoteproc.c4
-rw-r--r--drivers/remoteproc/mtk_scp.c2
-rw-r--r--drivers/remoteproc/omap_remoteproc.c1200
-rw-r--r--drivers/remoteproc/omap_remoteproc.h50
-rw-r--r--drivers/remoteproc/qcom_q6v5.c20
-rw-r--r--drivers/remoteproc/qcom_q6v5.h1
-rw-r--r--drivers/remoteproc/qcom_q6v5_adsp.c10
-rw-r--r--drivers/remoteproc/qcom_q6v5_mss.c133
-rw-r--r--drivers/remoteproc/qcom_q6v5_pas.c10
-rw-r--r--drivers/remoteproc/qcom_q6v5_wcss.c2
-rw-r--r--drivers/remoteproc/qcom_wcnss.c2
-rw-r--r--drivers/remoteproc/remoteproc_core.c161
-rw-r--r--drivers/remoteproc/remoteproc_debugfs.c16
-rw-r--r--drivers/remoteproc/remoteproc_elf_helpers.h96
-rw-r--r--drivers/remoteproc/remoteproc_elf_loader.c189
-rw-r--r--drivers/remoteproc/remoteproc_internal.h16
-rw-r--r--drivers/remoteproc/remoteproc_virtio.c8
-rw-r--r--drivers/remoteproc/st_remoteproc.c4
-rw-r--r--drivers/remoteproc/st_slim_rproc.c6
-rw-r--r--drivers/remoteproc/stm32_rproc.c4
-rw-r--r--drivers/remoteproc/wkup_m3_rproc.c4
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++) {