Commit c6570114 authored by Linus Torvalds's avatar Linus Torvalds

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
  ...
parents ac438771 a7084c3d
# SPDX-License-Identifier: (GPL-2.0-only or BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/remoteproc/ti,omap-remoteproc.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: OMAP4+ Remoteproc Devices
maintainers:
- Suman Anna <s-anna@ti.com>
description:
The OMAP family of SoCs usually have one or more slave processor sub-systems
that are used to offload some of the processor-intensive tasks, or to manage
other hardware accelerators, for achieving various system level goals.
The processor cores in the sub-system are usually behind an IOMMU, and may
contain additional sub-modules like Internal RAM and/or ROMs, L1 and/or L2
caches, an Interrupt Controller, a Cache Controller etc.
The OMAP SoCs usually have a DSP processor sub-system and/or an IPU processor
sub-system. The DSP processor sub-system can contain any of the TI's C64x,
C66x or C67x family of DSP cores as the main execution unit. The IPU processor
sub-system usually contains either a Dual-Core Cortex-M3 or Dual-Core
Cortex-M4 processors.
Each remote processor sub-system is represented as a single DT node. Each node
has a number of required or optional properties that enable the OS running on
the host processor (MPU) to perform the device management of the remote
processor and to communicate with the remote processor. The various properties
can be classified as constant or variable. The constant properties are
dictated by the SoC and does not change from one board to another having the
same SoC. Examples of constant properties include 'iommus', 'reg'. The
variable properties are dictated by the system integration aspects such as
memory on the board, or configuration used within the corresponding firmware
image. Examples of variable properties include 'mboxes', 'memory-region',
'timers', 'watchdog-timers' etc.
properties:
compatible:
enum:
- ti,omap4-dsp
- ti,omap5-dsp
- ti,dra7-dsp
- ti,omap4-ipu
- ti,omap5-ipu
- ti,dra7-ipu
iommus:
minItems: 1
maxItems: 2
description: |
phandles to OMAP IOMMU nodes, that need to be programmed
for this remote processor to access any external RAM memory or
other peripheral device address spaces. This property usually
has only a single phandle. Multiple phandles are used only in
cases where the sub-system has different ports for different
sub-modules within the processor sub-system (eg: DRA7 DSPs),
and need the same programming in both the MMUs.
mboxes:
minItems: 1
maxItems: 2
description: |
OMAP Mailbox specifier denoting the sub-mailbox, to be used for
communication with the remote processor. The specifier format is
as per the bindings,
Documentation/devicetree/bindings/mailbox/omap-mailbox.txt
This property should match with the sub-mailbox node used in
the firmware image.
clocks:
description: |
Main functional clock for the remote processor
resets:
description: |
Reset handles for the remote processor
firmware-name:
description: |
Default name of the firmware to load to the remote processor.
# Optional properties:
# --------------------
# Some of these properties are mandatory on some SoCs, and some are optional
# depending on the configuration of the firmware image to be executed on the
# remote processor. The conditions are mentioned for each property.
#
# The following are the optional properties:
memory-region:
$ref: /schemas/types.yaml#/definitions/phandle
description: |
phandle to the reserved memory node to be associated
with the remoteproc device. The reserved memory node
can be a CMA memory node, and should be defined as
per the bindings,
Documentation/devicetree/bindings/reserved-memory/reserved-memory.txt
reg:
description: |
Address space for any remoteproc memories present on
the SoC. Should contain an entry for each value in
'reg-names'. These are mandatory for all DSP and IPU
processors that have them (OMAP4/OMAP5 DSPs do not have
any RAMs)
reg-names:
description: |
Required names for each of the address spaces defined in
the 'reg' property. Expects the names from the following
list, in the specified order, each representing the corresponding
internal RAM memory region.
minItems: 1
maxItems: 3
items:
- const: l2ram
- const: l1pram
- const: l1dram
ti,bootreg:
$ref: /schemas/types.yaml#/definitions/phandle-array
description: |
Should be a triple of the phandle to the System Control
Configuration region that contains the boot address
register, the register offset of the boot address
register within the System Control module, and the bit
shift within the register. This property is required for
all the DSP instances on OMAP4, OMAP5 and DRA7xx SoCs.
ti,autosuspend-delay-ms:
description: |
Custom autosuspend delay for the remoteproc in milliseconds.
Recommended values is preferable to be in the order of couple
of seconds. A negative value can also be used to disable the
autosuspend behavior.
ti,timers:
$ref: /schemas/types.yaml#/definitions/phandle-array
description: |
One or more phandles to OMAP DMTimer nodes, that serve
as System/Tick timers for the OS running on the remote
processors. This will usually be a single timer if the
processor sub-system is running in SMP mode, or one per
core in the processor sub-system. This can also be used
to reserve specific timers to be dedicated to the
remote processors.
This property is mandatory on remote processors requiring
external tick wakeup, and to support Power Management
features. The timers to be used should match with the
timers used in the firmware image.
ti,watchdog-timers:
$ref: /schemas/types.yaml#/definitions/phandle-array
description: |
One or more phandles to OMAP DMTimer nodes, used to
serve as Watchdog timers for the processor cores. This
will usually be one per executing processor core, even
if the processor sub-system is running a SMP OS.
The timers to be used should match with the watchdog
timers used in the firmware image.
if:
properties:
compatible:
enum:
- ti,dra7-dsp
then:
properties:
reg:
minItems: 3
maxItems: 3
required:
- reg
- reg-names
- ti,bootreg
else:
if:
properties:
compatible:
enum:
- ti,omap4-ipu
- ti,omap5-ipu
- ti,dra7-ipu
then:
properties:
reg:
minItems: 1
maxItems: 1
ti,bootreg: false
required:
- reg
- reg-names
else:
properties:
reg: false
required:
- ti,bootreg
required:
- compatible
- iommus
- mboxes
- clocks
- resets
- firmware-name
additionalProperties: false
examples:
- |
//Example 1: OMAP4 DSP
/* DSP Reserved Memory node */
#include <dt-bindings/clock/omap4.h>
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
dsp_memory_region: dsp-memory@98000000 {
compatible = "shared-dma-pool";
reg = <0x98000000 0x800000>;
reusable;
};
};
/* DSP node */
ocp {
dsp: dsp {
compatible = "ti,omap4-dsp";
ti,bootreg = <&scm_conf 0x304 0>;
iommus = <&mmu_dsp>;
mboxes = <&mailbox &mbox_dsp>;
memory-region = <&dsp_memory_region>;
ti,timers = <&timer5>;
ti,watchdog-timers = <&timer6>;
clocks = <&tesla_clkctrl OMAP4_DSP_CLKCTRL 0>;
resets = <&prm_tesla 0>, <&prm_tesla 1>;
firmware-name = "omap4-dsp-fw.xe64T";
};
};
- |+
//Example 2: OMAP5 IPU
/* IPU Reserved Memory node */
#include <dt-bindings/clock/omap5.h>
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ipu_memory_region: ipu-memory@95800000 {
compatible = "shared-dma-pool";
reg = <0 0x95800000 0 0x3800000>;
reusable;
};
};
/* IPU node */
ocp {
#address-cells = <1>;
#size-cells = <1>;
ipu: ipu@55020000 {
compatible = "ti,omap5-ipu";
reg = <0x55020000 0x10000>;
reg-names = "l2ram";
iommus = <&mmu_ipu>;
mboxes = <&mailbox &mbox_ipu>;
memory-region = <&ipu_memory_region>;
ti,timers = <&timer3>, <&timer4>;
ti,watchdog-timers = <&timer9>, <&timer11>;
clocks = <&ipu_clkctrl OMAP5_MMU_IPU_CLKCTRL 0>;
resets = <&prm_core 2>;
firmware-name = "omap5-ipu-fw.xem4";
};
};
- |+
//Example 3: DRA7xx/AM57xx DSP
/* DSP1 Reserved Memory node */
#include <dt-bindings/clock/dra7.h>
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
dsp1_memory_region: dsp1-memory@99000000 {
compatible = "shared-dma-pool";
reg = <0x0 0x99000000 0x0 0x4000000>;
reusable;
};
};
/* DSP1 node */
ocp {
#address-cells = <1>;
#size-cells = <1>;
dsp1: dsp@40800000 {
compatible = "ti,dra7-dsp";
reg = <0x40800000 0x48000>,
<0x40e00000 0x8000>,
<0x40f00000 0x8000>;
reg-names = "l2ram", "l1pram", "l1dram";
ti,bootreg = <&scm_conf 0x55c 0>;
iommus = <&mmu0_dsp1>, <&mmu1_dsp1>;
mboxes = <&mailbox5 &mbox_dsp1_ipc3x>;
memory-region = <&dsp1_memory_region>;
ti,timers = <&timer5>;
ti,watchdog-timers = <&timer10>;
resets = <&prm_dsp1 0>;
clocks = <&dsp1_clkctrl DRA7_DSP1_MMU0_DSP1_CLKCTRL 0>;
firmware-name = "dra7-dsp1-fw.xe66";
};
};
......@@ -230,7 +230,7 @@ in the used rings.
Binary Firmware Structure
=========================
At this point remoteproc only supports ELF32 firmware binaries. However,
At this point remoteproc supports ELF32 and ELF64 firmware binaries. However,
it is quite expected that other platforms/devices which we'd want to
support with this framework will be based on different binary formats.
......
......@@ -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
......
......@@ -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;
}
......
......@@ -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++) {
......
......@@ -320,7 +320,7 @@ static int scp_start(struct rproc *rproc)
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;
......
......@@ -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,30 +15,465 @@
#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
......@@ -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 @@ static int omap_rproc_start(struct rproc *rproc)
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,
},
};
......
/* 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 */
......@@ -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
......@@ -162,6 +164,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
......
......@@ -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
......@@ -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)
......
......@@ -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 @@ static int q6v5_mba_load(struct q6v5 *qproc)
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;
......
......@@ -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)
......
......@@ -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;
......
......@@ -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;
......
......@@ -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 @@ int rproc_trigger_recovery(struct rproc *rproc)
/**
* 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();
}
......
......@@ -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);
......
/* 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 */
......@@ -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,30 +85,54 @@ 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
......@@ -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);
......@@ -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 */
......@@ -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) {
......
......@@ -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,
};
......
......@@ -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,
};
/**
......
......@@ -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");
......
......@@ -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++) {
......
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Remote Processor - omap-specific bits
*
* Copyright (C) 2011 Texas Instruments, Inc.
* Copyright (C) 2011 Google, Inc.
*/
#ifndef _PLAT_REMOTEPROC_H
#define _PLAT_REMOTEPROC_H
struct rproc_ops;
struct platform_device;
/*
* struct omap_rproc_pdata - omap remoteproc's platform data
* @name: the remoteproc's name
* @oh_name: omap hwmod device
* @oh_name_opt: optional, secondary omap hwmod device
* @firmware: name of firmware file to load
* @mbox_name: name of omap mailbox device to use with this rproc
* @ops: start/stop rproc handlers
* @device_enable: omap-specific handler for enabling a device
* @device_shutdown: omap-specific handler for shutting down a device
* @set_bootaddr: omap-specific handler for setting the rproc boot address
*/
struct omap_rproc_pdata {
const char *name;
const char *oh_name;
const char *oh_name_opt;
const char *firmware;
const char *mbox_name;
const struct rproc_ops *ops;
int (*device_enable)(struct platform_device *pdev);
int (*device_shutdown)(struct platform_device *pdev);
void (*set_bootaddr)(u32);
};
#if defined(CONFIG_OMAP_REMOTEPROC) || defined(CONFIG_OMAP_REMOTEPROC_MODULE)
void __init omap_rproc_reserve_cma(void);
#else
static inline void __init omap_rproc_reserve_cma(void)
{
}
#endif
#endif /* _PLAT_REMOTEPROC_H */
......@@ -329,7 +329,7 @@ struct rproc;
struct rproc_mem_entry {
void *va;
dma_addr_t dma;
int len;
size_t len;
u32 da;
void *priv;
char name[32];
......@@ -369,12 +369,14 @@ enum rsc_handling_status {
* expects to find it
* @sanity_check: sanity check the fw image
* @get_boot_addr: get boot address to entry point specified in firmware
* @panic: optional callback to react to system panic, core will delay
* panic at least the returned number of milliseconds
*/
struct rproc_ops {
int (*start)(struct rproc *rproc);
int (*stop)(struct rproc *rproc);
void (*kick)(struct rproc *rproc, int vqid);
void * (*da_to_va)(struct rproc *rproc, u64 da, int len);
void * (*da_to_va)(struct rproc *rproc, u64 da, size_t len);
int (*parse_fw)(struct rproc *rproc, const struct firmware *fw);
int (*handle_rsc)(struct rproc *rproc, u32 rsc_type, void *rsc,
int offset, int avail);
......@@ -382,7 +384,8 @@ struct rproc_ops {
struct rproc *rproc, const struct firmware *fw);
int (*load)(struct rproc *rproc, const struct firmware *fw);
int (*sanity_check)(struct rproc *rproc, const struct firmware *fw);
u32 (*get_boot_addr)(struct rproc *rproc, const struct firmware *fw);
u64 (*get_boot_addr)(struct rproc *rproc, const struct firmware *fw);
unsigned long (*panic)(struct rproc *rproc);
};
/**
......@@ -498,7 +501,7 @@ struct rproc {
int num_traces;
struct list_head carveouts;
struct list_head mappings;
u32 bootaddr;
u64 bootaddr;
struct list_head rvdevs;
struct list_head subdevs;
struct idr notifyids;
......@@ -514,6 +517,7 @@ struct rproc {
bool auto_boot;
struct list_head dump_segments;
int nb_vdev;
u8 elf_class;
};
/**
......@@ -599,13 +603,13 @@ void rproc_add_carveout(struct rproc *rproc, struct rproc_mem_entry *mem);
struct rproc_mem_entry *
rproc_mem_entry_init(struct device *dev,
void *va, dma_addr_t dma, int len, u32 da,
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, ...);
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, ...);
int rproc_boot(struct rproc *rproc);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment