2017-03-23 16:37:35

by Matt Redfearn

[permalink] [raw]
Subject: [PATCH v6 0/4] MIPS: Remote processor driver


The MIPS remote processor driver allows non-Linux firmware to take
control of and execute on one of the systems VPEs, turning the system
into a hybrid of SMP Linux and AMP.

This is useful to allow running bare metal code, or an RTOS, on one or
more CPUs while allowing Linux to continue running on those remaining.

This functionality is conceptually similar to the VPE loader, an arch
specific mechanism which has been in the MIPS Linux kernel since 2005.
This driver is an attempt to make that functionality more standard by
moving it into the remoteproc subsystem.

A system CPU must be offlined from Linux and the driver enabled for that
CPU via the drivers sysfs interface. The remoteproc subsystem then takes
control of the CPU and loads a separate firmware image to it. A full
description is available at [1]. Example firmware images for the driver
and host programs to test them are available at [2].

The first patches in this series lay the groundwork for the driver
before it is added. The last series deprecates the VPE loader.

This functionality is supported on:
- MIPS32r2 devices implementing the MIPS MT ASE for multithreading, such
as interAptiv.
- MIPS32r6 devices implementing VPs, such as I6400.

Limitations:
- The remoteproc core supports only 32bit ELFs. Therefore it is only
possible to run 32bit firmware on the remote processor. Also, for
virtio communication, pointers are passed from the kernel to firmware.
There can be no mismatch in pointer sizes between the kernel and
firmware, so this limits the host kernel to 32bit as well.

The functionality has been tested on the Ci40 board which has a 2 core 2
thread interAptiv.

This series is based on v4.11-rc3

[1] http://wiki.prplfoundation.org/w/images/d/df/MIPS_OS_Remote_Processor_Driver_Whitepaper_1.0.9.pdf
[2] https://github.com/MIPS/mips-rproc-example


Changes in v6:
Rebase on Linux 4.11-rc3
Change to set_current_state() as set_task_state has been removed.

Changes in v5:
Depend on !64bit since this driver only works with 32bit kernels
Set mproc->tsk state to TASK_DEAD before freeing it to avoid warning
Flush icache of each carveout so that icache sees latest data written

Changes in v4:
Fix inconsistency of Linux CPU number and VP ID
Have a single mips-rproc device to be parent to each CPU's rproc device.
Support per-device coherence introduced in v4.9
Add a sysfs interface to control the mask of cpus available to rproc

Changes in v3:
Update GIC context saving to use CPU hotplug state machine
Update MIPS remoteproc driver to use CPU hotplug state machine
Remove sysfs interface from MIPS rproc driver, now provided by the core.
Drop patches that Ralf has already merged to mips-next

Changes in v2:
Add dependence on additional patches to mips-gic in commit log
Incorporate changes from Marc Zynger's review:
- Remove CONTEXT_SAVING define.
- Make saved local state a per-cpu variable
- Make gic_save_* static functions when enabled, and do { } while(0)
otherwise

Lisa Parratt (1):
MIPS: CPS: Add VP(E) stealing

Matt Redfearn (3):
irqchip: mips-gic: Add context saving for MIPS_REMOTEPROC
remoteproc/MIPS: Add a remoteproc driver for MIPS
MIPS: Deprecate VPE Loader

Documentation/ABI/testing/sysfs-devices-mips-rproc | 13 +
arch/mips/Kconfig | 12 +-
arch/mips/include/asm/smp-cps.h | 8 +
arch/mips/kernel/smp-cps.c | 162 +++++-
arch/mips/kernel/smp.c | 12 +
drivers/irqchip/irq-mips-gic.c | 207 ++++++-
drivers/remoteproc/Kconfig | 11 +
drivers/remoteproc/Makefile | 1 +
drivers/remoteproc/mips_remoteproc.c | 599 +++++++++++++++++++++
9 files changed, 1010 insertions(+), 15 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-devices-mips-rproc
create mode 100644 drivers/remoteproc/mips_remoteproc.c

--
2.7.4


2017-03-23 16:37:48

by Matt Redfearn

[permalink] [raw]
Subject: [PATCH v6 2/4] MIPS: CPS: Add VP(E) stealing

From: Lisa Parratt <[email protected]>

VP(E) stealing provides a mechanism for removing an offline Virtual
Processor from the Linux kernel such that it is available to run bare
metal code.
Once the CPU has been offlined from Linux, the CPU can be given a task
to run via mips_cps_steal_cpu_and_execute(). The CPU is removed from the
cpu_present mask and is set up to execute from address entry_fn. Stack
space is assigned via the tsk task_struct so that C initialisation code
may be used.
To return the CPU back to Linux control, mips_cps_halt_and_return_cpu
will arrange to halt the CPU and return it to the cpu_present mask. It
is then available to be brought online again via CPU hotplug.

This mechanism is used by the MIPS remote processor driver to allow
CPUs within the system to execute bare metal code, not under control of
the kernel.

Signed-off-by: Lisa Parratt <[email protected]>
Signed-off-by: Matt Redfearn <[email protected]>
---

Changes in v6: None
Changes in v5: None
Changes in v4: None
Changes in v3: None
Changes in v2: None

arch/mips/Kconfig | 7 ++
arch/mips/include/asm/smp-cps.h | 8 ++
arch/mips/kernel/smp-cps.c | 162 ++++++++++++++++++++++++++++++++++++++--
arch/mips/kernel/smp.c | 12 +++
4 files changed, 182 insertions(+), 7 deletions(-)

diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index a008a9f03072..6cdc23da8761 100644
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -2381,6 +2381,13 @@ config MIPS_CPS
no external assistance. It is safe to enable this when hardware
support is unavailable.

+config MIPS_CPU_STEAL
+ bool "VPE stealing"
+ depends on HOTPLUG_CPU && MIPS_CPS
+ help
+ Select this is you wish to be able to run bare metal code on offline
+ VPEs.
+
config MIPS_CPS_PM
depends on MIPS_CPS
select MIPS_CPC
diff --git a/arch/mips/include/asm/smp-cps.h b/arch/mips/include/asm/smp-cps.h
index 2ae1f61a4a95..5cab043d7e6f 100644
--- a/arch/mips/include/asm/smp-cps.h
+++ b/arch/mips/include/asm/smp-cps.h
@@ -34,6 +34,14 @@ extern void mips_cps_boot_vpes(struct core_boot_config *cfg, unsigned vpe);
extern void mips_cps_pm_save(void);
extern void mips_cps_pm_restore(void);

+#ifdef CONFIG_MIPS_CPU_STEAL
+
+extern int mips_cps_steal_cpu_and_execute(unsigned int cpu, void *entry_fn,
+ struct task_struct *tsk);
+extern int mips_cps_halt_and_return_cpu(unsigned int cpu);
+
+#endif /* CONFIG_MIPS_CPU_STEAL */
+
#ifdef CONFIG_MIPS_CPS

extern bool mips_cps_smp_in_use(void);
diff --git a/arch/mips/kernel/smp-cps.c b/arch/mips/kernel/smp-cps.c
index 6d45f05538c8..3a1e08a0c327 100644
--- a/arch/mips/kernel/smp-cps.c
+++ b/arch/mips/kernel/smp-cps.c
@@ -8,6 +8,7 @@
* option) any later version.
*/

+#include <linux/cpu.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/irqchip/mips-gic.h>
@@ -40,6 +41,31 @@ static int __init setup_nothreads(char *s)
}
early_param("nothreads", setup_nothreads);

+#ifdef CONFIG_MIPS_CPU_STEAL
+struct cpumask cpu_stolen_mask;
+
+static inline bool cpu_stolen(int cpu)
+{
+ return cpumask_test_cpu(cpu, &cpu_stolen_mask);
+}
+
+static inline void set_cpu_stolen(int cpu, bool state)
+{
+ if (state)
+ cpumask_set_cpu(cpu, &cpu_stolen_mask);
+ else
+ cpumask_clear_cpu(cpu, &cpu_stolen_mask);
+}
+#else
+static inline bool cpu_stolen(int cpu)
+{
+ return false;
+}
+
+static inline void set_cpu_stolen(int cpu, bool state) { }
+
+#endif /* CONFIG_MIPS_CPU_STEAL */
+
static unsigned core_vpe_count(unsigned core)
{
unsigned cfg;
@@ -110,6 +136,10 @@ static void __init cps_smp_setup(void)
write_gcr_bev_base(core_entry);
}

+#ifdef CONFIG_MIPS_CPU_STEAL
+ cpumask_clear(&cpu_stolen_mask);
+#endif /* CONFIG_MIPS_CPU_STEAL */
+
#ifdef CONFIG_MIPS_MT_FPAFF
/* If we have an FPU, enroll ourselves in the FPU-full mask */
if (cpu_has_fpu)
@@ -288,7 +318,7 @@ static void remote_vpe_boot(void *dummy)
mips_cps_boot_vpes(core_cfg, cpu_vpe_id(&current_cpu_data));
}

-static void cps_boot_secondary(int cpu, struct task_struct *idle)
+static void cps_start_secondary(int cpu, void *entry_fn, struct task_struct *tsk)
{
unsigned core = cpu_data[cpu].core;
unsigned vpe_id = cpu_vpe_id(&cpu_data[cpu]);
@@ -298,9 +328,9 @@ static void cps_boot_secondary(int cpu, struct task_struct *idle)
unsigned int remote;
int err;

- vpe_cfg->pc = (unsigned long)&smp_bootstrap;
- vpe_cfg->sp = __KSTK_TOS(idle);
- vpe_cfg->gp = (unsigned long)task_thread_info(idle);
+ vpe_cfg->pc = (unsigned long)entry_fn;
+ vpe_cfg->sp = __KSTK_TOS(tsk);
+ vpe_cfg->gp = (unsigned long)task_thread_info(tsk);

atomic_or(1 << cpu_vpe_id(&cpu_data[cpu]), &core_cfg->vpe_mask);

@@ -348,6 +378,11 @@ static void cps_boot_secondary(int cpu, struct task_struct *idle)
preempt_enable();
}

+static void cps_boot_secondary(int cpu, struct task_struct *idle)
+{
+ cps_start_secondary(cpu, &smp_bootstrap, idle);
+}
+
static void cps_init_secondary(void)
{
/* Disable MT - we only want to run 1 TC per VPE */
@@ -399,6 +434,28 @@ static int cps_cpu_disable(void)
if (!cps_pm_support_state(CPS_PM_POWER_GATED))
return -EINVAL;

+#ifdef CONFIG_MIPS_CPU_STEAL
+ /*
+ * With the MT ASE only VPEs in the same core may read / write the
+ * control registers of other VPEs. Therefore to maintain control of
+ * any stolen VPEs at least one sibling VPE must be kept online.
+ */
+ if (cpu_has_mipsmt) {
+ int stolen, siblings = 0;
+
+ for_each_cpu((stolen), &cpu_stolen_mask)
+ if (cpu_data[stolen].core == cpu_data[cpu].core)
+ siblings++;
+
+ if (siblings == 1)
+ /*
+ * When a VPE has been stolen, keep at least one of it's
+ * siblings around in order to control it.
+ */
+ return -EBUSY;
+ }
+#endif /* CONFIG_MIPS_CPU_STEAL */
+
core_cfg = &mips_cps_core_bootcfg[current_cpu_data.core];
atomic_sub(1 << cpu_vpe_id(&current_cpu_data), &core_cfg->vpe_mask);
smp_mb__after_atomic();
@@ -430,7 +487,7 @@ void play_dead(void)
core = cpu_data[cpu].core;

/* Look for another online VPE within the core */
- for_each_online_cpu(cpu_death_sibling) {
+ for_each_possible_cpu(cpu_death_sibling) {
if (cpu_data[cpu_death_sibling].core != core)
continue;

@@ -438,8 +495,11 @@ void play_dead(void)
* There is an online VPE within the core. Just halt
* this TC and leave the core alone.
*/
- cpu_death = CPU_DEATH_HALT;
- break;
+ if (cpu_online(cpu_death_sibling) ||
+ cpu_stolen(cpu_death_sibling))
+ cpu_death = CPU_DEATH_HALT;
+ if (cpu_online(cpu_death_sibling))
+ break;
}
}

@@ -470,6 +530,94 @@ void play_dead(void)
panic("Failed to offline CPU %u", cpu);
}

+#ifdef CONFIG_MIPS_CPU_STEAL
+
+/* Find an online sibling CPU (another VPE in the same core) */
+static inline int mips_cps_get_online_sibling(unsigned int cpu)
+{
+ int sibling;
+
+ for_each_online_cpu(sibling)
+ if (cpu_data[sibling].core == cpu_data[cpu].core)
+ return sibling;
+
+ return -1;
+}
+
+int mips_cps_steal_cpu_and_execute(unsigned int cpu, void *entry_fn,
+ struct task_struct *tsk)
+{
+ int err = -EINVAL;
+
+ preempt_disable();
+
+ if (!cpu_present(cpu) || cpu_online(cpu) || cpu_stolen(cpu))
+ goto out;
+
+ if (cpu_has_mipsmt && (mips_cps_get_online_sibling(cpu) < 0))
+ pr_warn("CPU%d has no online siblings to control it\n", cpu);
+ else {
+ set_cpu_present(cpu, false);
+ set_cpu_stolen(cpu, true);
+
+ cps_start_secondary(cpu, entry_fn, tsk);
+ err = 0;
+ }
+out:
+ preempt_enable();
+ return err;
+}
+
+static void mips_cps_halt_sibling(void *ptr_cpu)
+{
+ unsigned int cpu = (unsigned long)ptr_cpu;
+ unsigned int vpe_id = cpu_vpe_id(&cpu_data[cpu]);
+ unsigned long flags;
+ int vpflags;
+
+ local_irq_save(flags);
+ vpflags = dvpe();
+ settc(vpe_id);
+ write_tc_c0_tchalt(TCHALT_H);
+ evpe(vpflags);
+ local_irq_restore(flags);
+}
+
+int mips_cps_halt_and_return_cpu(unsigned int cpu)
+{
+ unsigned int core = cpu_data[cpu].core;
+ unsigned int vpe_id = cpu_vpe_id(&cpu_data[cpu]);
+
+ if (!cpu_stolen(cpu))
+ return -EINVAL;
+
+ if (cpu_has_mipsmt && (core == cpu_data[smp_processor_id()].core))
+ mips_cps_halt_sibling((void *)(unsigned long)cpu);
+ else if (cpu_has_mipsmt) {
+ int sibling = mips_cps_get_online_sibling(cpu);
+
+ if (sibling < 0) {
+ pr_warn("CPU%d has no online siblings\n", cpu);
+ return -EINVAL;
+ }
+
+ if (smp_call_function_single(sibling, mips_cps_halt_sibling,
+ (void *)(unsigned long)cpu, 1))
+ panic("Failed to call sibling CPU\n");
+
+ } else if (cpu_has_vp) {
+ mips_cm_lock_other(core, vpe_id);
+ write_cpc_co_vp_stop(1 << vpe_id);
+ mips_cm_unlock_other();
+ }
+
+ set_cpu_stolen(cpu, false);
+ set_cpu_present(cpu, true);
+ return 0;
+}
+
+#endif /* CONFIG_MIPS_CPU_STEAL */
+
static void wait_for_sibling_halt(void *ptr_cpu)
{
unsigned cpu = (unsigned long)ptr_cpu;
diff --git a/arch/mips/kernel/smp.c b/arch/mips/kernel/smp.c
index 6e71130549ea..ae1ffc3e33eb 100644
--- a/arch/mips/kernel/smp.c
+++ b/arch/mips/kernel/smp.c
@@ -235,6 +235,18 @@ static void smp_ipi_init_one(unsigned int virq,
struct irqaction *action)
{
int ret;
+#ifdef CONFIG_MIPS_CPU_STEAL
+ struct irq_data *data;
+ /*
+ * A bit of a hack to ensure that the ipi_offset is 0.
+ * This is to deal with removing / reallocating IPIs
+ * to subsets of the possible CPUs, where the IPI IRQ domain
+ * will set ipi_offset to the first cpu in the cpumask when the
+ * IPI is reallocated.
+ */
+ data = irq_get_irq_data(virq);
+ data->common->ipi_offset = 0;
+#endif /* CONFIG_MIPS_CPU_STEAL */

irq_set_handler(virq, handle_percpu_irq);
ret = setup_irq(virq, action);
--
2.7.4

2017-03-23 16:38:00

by Matt Redfearn

[permalink] [raw]
Subject: [PATCH v6 3/4] remoteproc/MIPS: Add a remoteproc driver for MIPS

This driver allows a MIPS processor offlined from Linux to be used as a
remote processor. The processor can then handle real-time tasks or
perform coprocessing while remaining processors are available to Linux,
effectively making the system hybrid of SMP Linux and AMP.

A sysfs interface is provided to allow control of which system CPUs may
be acquired by the driver when offlined from Linux.

Coprocessor firmware must abide by the remoteproc standard, i.e.
implement the resource table containing memory layouts and virtio device
descriptions, and additionally abide by the MIPS UHI coprocessor boot
protocol in the startup code.

Example firmware and host executables to test them are located at [1].

[1] https://github.com/MIPS/mips-rproc-example

Signed-off-by: Lisa Parratt <[email protected]>
Signed-off-by: Matt Redfearn <[email protected]>

---

Changes in v6:
Change to set_current_state() as set_task_state has been removed.

Changes in v5:
Depend on !64bit since this driver only works with 32bit kernels
Set mproc->tsk state to TASK_DEAD before freeing it to avoid warning
Flush icache of each carveout so that icache sees latest data written

Changes in v4:
Have a single mips-rproc device to be parent to each CPU's rproc device.
Support per-device coherence introduced in v4.9
Add a sysfs interface to control the mask of cpus available to rproc

Changes in v3:
Update MIPS remoteproc driver to use CPU hotplug state machine
Remove sysfs interface from MIPS rproc driver, now provided by the core.
Drop patches that Ralf has already merged to mips-next

Changes in v2: None

Documentation/ABI/testing/sysfs-devices-mips-rproc | 13 +
drivers/remoteproc/Kconfig | 11 +
drivers/remoteproc/Makefile | 1 +
drivers/remoteproc/mips_remoteproc.c | 599 +++++++++++++++++++++
4 files changed, 624 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-devices-mips-rproc
create mode 100644 drivers/remoteproc/mips_remoteproc.c

diff --git a/Documentation/ABI/testing/sysfs-devices-mips-rproc b/Documentation/ABI/testing/sysfs-devices-mips-rproc
new file mode 100644
index 000000000000..b06f6671807a
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-devices-mips-rproc
@@ -0,0 +1,13 @@
+What: /sys/devices/mips-rproc/cpus
+Date: October 2016
+Contact: Matt Redfearn <[email protected]>
+Description:
+ CPU topology file describing which CPUs may be used by the
+ MIPS remote processor driver when offline from Linux.
+
+ This can be read to observe the current setting, or written to
+ change the allowed CPUs.
+
+ The format is compatible with cpulist_parse()
+ [see <linux/cpumask.h>], for example to enable the MIPS remote
+ processor driver on CPUs 1,2 & 3, write "1-3" into this file.
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index 65f86bc24c07..558b67184723 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -71,6 +71,17 @@ config DA8XX_REMOTEPROC
It's safe to say n here if you're not interested in multimedia
offloading.

+config MIPS_REMOTEPROC
+ tristate "MIPS remoteproc support"
+ depends on MIPS_CPS && HAS_DMA && !64BIT
+ depends on REMOTEPROC
+ select CMA
+ select MIPS_CPU_STEAL
+ help
+ Say y here to support using offline cores/VPEs as remote processors
+ via the remote processor framework.
+ If unsure say N.
+
config QCOM_ADSP_PIL
tristate "Qualcomm ADSP Peripheral Image Loader"
depends on OF && ARCH_QCOM
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index ffc5e430df27..9525debba686 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -11,6 +11,7 @@ remoteproc-y += remoteproc_elf_loader.o
obj-$(CONFIG_OMAP_REMOTEPROC) += omap_remoteproc.o
obj-$(CONFIG_WKUP_M3_RPROC) += wkup_m3_rproc.o
obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o
+obj-$(CONFIG_MIPS_REMOTEPROC) += mips_remoteproc.o
obj-$(CONFIG_QCOM_ADSP_PIL) += qcom_adsp_pil.o
obj-$(CONFIG_QCOM_RPROC_COMMON) += qcom_common.o
obj-$(CONFIG_QCOM_Q6V5_PIL) += qcom_q6v5_pil.o
diff --git a/drivers/remoteproc/mips_remoteproc.c b/drivers/remoteproc/mips_remoteproc.c
new file mode 100644
index 000000000000..9a0e5e06c0b1
--- /dev/null
+++ b/drivers/remoteproc/mips_remoteproc.c
@@ -0,0 +1,599 @@
+/*
+ * MIPS Remote Processor driver
+ *
+ * Copyright (C) 2016 Imagination Technologies
+ * Lisa Parratt <[email protected]>
+ * Matt Redfearn <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/cpu.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/remoteproc.h>
+#include <linux/sched/task.h>
+
+#include <asm/cacheflush.h>
+#include <asm/smp-cps.h>
+#include <asm/tlbflush.h>
+#include <asm/tlbmisc.h>
+
+#include "remoteproc_internal.h"
+
+struct mips_rproc {
+ char name[16];
+ struct rproc *rproc;
+ struct task_struct *tsk;
+ unsigned int cpu;
+ int ipi_linux;
+ int ipi_remote;
+};
+
+/* Parent device for MIPS remoteproc */
+static struct device mips_rproc_dev;
+
+/* Array of allocated MIPS remote processor instances */
+static struct mips_rproc *mips_rprocs[NR_CPUS];
+
+/* Bitmap used to identify which CPUs are available to rproc */
+static cpumask_var_t mips_rproc_cpumask;
+
+/* Dynamic CPU hotplug state associated with this driver */
+static int cpuhp_state;
+
+/* Add wired entry to map a device address to physical memory */
+static void mips_map_page(unsigned long da, unsigned long pa, int c,
+ unsigned long pagesize)
+{
+ unsigned long pa2 = pa + (pagesize / 2);
+ unsigned long entryhi, entrylo0, entrylo1;
+ unsigned long pagemask = pagesize - 0x2000;
+
+ pa = (pa >> 6) & (ULONG_MAX << MIPS_ENTRYLO_PFN_SHIFT);
+ pa2 = (pa2 >> 6) & (ULONG_MAX << MIPS_ENTRYLO_PFN_SHIFT);
+ entryhi = da & 0xfffffe000;
+ entrylo0 = (c << ENTRYLO_C_SHIFT) | ENTRYLO_D | ENTRYLO_V | pa;
+ entrylo1 = (c << ENTRYLO_C_SHIFT) | ENTRYLO_D | ENTRYLO_V | pa2;
+
+ pr_debug("Create wired entry %d, CCA %d\n", read_c0_wired(), c);
+ pr_debug(" EntryHi: 0x%016lx\n", entryhi);
+ pr_debug(" EntryLo0: 0x%016lx\n", entrylo0);
+ pr_debug(" EntryLo1: 0x%016lx\n", entrylo1);
+ pr_debug(" Pagemask: 0x%016lx\n", pagemask);
+ pr_debug("\n");
+
+ add_wired_entry(entrylo0, entrylo1, entryhi, pagemask);
+}
+
+/* Compute the largest page mask a physical address can be mapped with */
+static unsigned long mips_rproc_largest_pm(unsigned long pa,
+ unsigned long maxmask)
+{
+ unsigned long mask;
+ /* Find address bits limiting alignment */
+ unsigned long shift = ffs(pa);
+
+ /* Obey MIPS restrictions on page sizes */
+ if (pa) {
+ if (shift & 1)
+ shift -= 2;
+ else
+ shift--;
+ }
+ mask = ULONG_MAX << shift;
+ return maxmask & ~mask;
+}
+
+/* Compute the page mask one step larger than a given page mask */
+static unsigned long mips_rproc_next_pm(unsigned long pm, unsigned long maxmask)
+{
+#define PM_SHIFT 13
+ return ((pm << 2) | (0x3 << PM_SHIFT)) & maxmask;
+}
+
+/*
+ * Add mappings to the TLB such that memory allocated by the kernel for a
+ * firmware component appears at the right virtual address
+ */
+static inline void mips_rproc_map(unsigned long da, unsigned long pa, int c,
+ unsigned long size, unsigned long maxmask)
+{
+ /* minimum mappable size is 2 * 4k pages */
+ const unsigned long min_map_sz = 0x2000;
+ unsigned long bigmask, nextmask;
+ unsigned long distance, target;
+ unsigned long page2_size; /* Size of the 2 buddy pages */
+
+ do {
+ /* Compute the current largest page mask */
+ bigmask = mips_rproc_largest_pm(pa, maxmask);
+ /* Compute the next largest pagesize */
+ nextmask = mips_rproc_next_pm(bigmask, maxmask);
+ /*
+ * Compute the distance from our current physical address to
+ * the next page boundary.
+ */
+ distance = (nextmask + min_map_sz) - (pa & nextmask);
+ /*
+ * Decide between searching to get to the next highest page
+ * boundary or finishing.
+ */
+ target = distance < size ? distance : size;
+ while (target) {
+ /* Find the largest supported page size that will fit */
+ for (page2_size = maxmask + min_map_sz;
+ (page2_size > min_map_sz) && (page2_size > target);
+ page2_size /= 4) {
+ }
+ /* Emit it */
+ mips_map_page(da, pa, c, page2_size);
+ /* Move to next step */
+ size -= page2_size;
+ da += page2_size;
+ pa += page2_size;
+ target -= page2_size;
+ }
+ } while (size);
+}
+
+static int mips_rproc_carveouts(struct rproc *rproc, int max_pagemask)
+{
+ struct rproc_mem_entry *carveout;
+
+ list_for_each_entry(carveout, &rproc->carveouts, node) {
+ int c = CONF_CM_CACHABLE_COW;
+
+ dev_dbg(&rproc->dev,
+ "carveout mapping da 0x%x -> %pad length 0x%x, CCA %d",
+ carveout->da, &carveout->dma, carveout->len, c);
+
+ mips_rproc_map(carveout->da, carveout->dma, c,
+ carveout->len, max_pagemask);
+ flush_icache_range((unsigned long)carveout->va,
+ (unsigned long)carveout->va + carveout->len);
+ }
+ return 0;
+}
+
+static int mips_rproc_vdevs(struct rproc *rproc, int max_pagemask)
+{
+ struct rproc_vdev *rvdev;
+
+ list_for_each_entry(rvdev, &rproc->rvdevs, node) {
+ int i, size;
+
+ for (i = 0; i < ARRAY_SIZE(rvdev->vring); i++) {
+ struct rproc_vring *vring = &rvdev->vring[i];
+ unsigned long pa = vring->dma;
+ int c;
+
+ if (plat_device_is_coherent(&mips_rproc_dev)) {
+ /*
+ * The DMA API will allocate cacheable buffers
+ * for shared resources, so the firmware should
+ * also access those buffers cached
+ */
+ c = (_page_cachable_default >> _CACHE_SHIFT);
+ } else {
+ /*
+ * Otherwise, shared buffers should be accessed
+ * uncached
+ */
+ c = CONF_CM_UNCACHED;
+ }
+
+ /* actual size of vring (in bytes) */
+ size = PAGE_ALIGN(vring_size(vring->len, vring->align));
+
+ dev_dbg(&rproc->dev,
+ "vring mapping da %pad -> %pad length 0x%x, CCA %d",
+ &vring->dma, &vring->dma, size, c);
+
+ mips_rproc_map(pa, pa, c, size, max_pagemask);
+ }
+ }
+ return 0;
+}
+
+static void mips_rproc_cpu_entry(void)
+{
+ struct mips_rproc *mproc = mips_rprocs[smp_processor_id()];
+ struct rproc *rproc = mproc->rproc;
+ int ipi_to_remote = ipi_get_hwirq(mproc->ipi_remote, mproc->cpu);
+ int ipi_from_remote = ipi_get_hwirq(mproc->ipi_linux, 0);
+ unsigned long old_pagemask, max_pagemask;
+ void (*fw_entry)(int, int ipi_to_remote, int ipi_from_remote, int);
+
+ dev_info(&rproc->dev, "%s booting firmware %s\n",
+ rproc->name, rproc->firmware);
+
+ /* Get the maximum pagemask supported on this CPU */
+ old_pagemask = read_c0_pagemask();
+ write_c0_pagemask(~0);
+ back_to_back_c0_hazard();
+ max_pagemask = read_c0_pagemask();
+ write_c0_pagemask(old_pagemask);
+ back_to_back_c0_hazard();
+
+ /* Start with no wired entries */
+ write_c0_wired(0);
+
+ /* Flush all previous TLB entries */
+ local_flush_tlb_all();
+
+ /* Set ASID 0 */
+ write_c0_entryhi(0);
+
+ /* Map firmware resources into virtual memory */
+ mips_rproc_carveouts(rproc, max_pagemask);
+ mips_rproc_vdevs(rproc, max_pagemask);
+
+ dev_dbg(&rproc->dev, "IPI to remote: %d\n", ipi_to_remote);
+ dev_dbg(&rproc->dev, "IPI from remote: %d\n", ipi_from_remote);
+
+ /* Hand off the CPU to the firmware */
+ dev_dbg(&rproc->dev, "Jumping to firmware at 0x%x\n", rproc->bootaddr);
+
+ /* We're done with the task struct that provided the stack we've used */
+ set_current_state(TASK_DEAD);
+
+ /* Jump into the firmware, obeying the firmware protocol. */
+ fw_entry = (void *)rproc->bootaddr;
+ fw_entry(-3, ipi_to_remote, ipi_from_remote, 0);
+}
+
+static irqreturn_t mips_rproc_ipi_handler(int irq, void *dev_id)
+{
+ /* Synthetic interrupts shouldn't need acking */
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t mips_rproc_vq_int(int irq, void *p)
+{
+ struct rproc *rproc = (struct rproc *)p;
+ void *entry;
+ int id;
+
+ /* We don't have a mailbox, so iterate over all vqs and kick them. */
+ idr_for_each_entry(&rproc->notifyids, entry, id)
+ rproc_vq_interrupt(rproc, id);
+
+ return IRQ_HANDLED;
+}
+
+/* Helper function to find the IPI domain */
+static struct irq_domain *ipi_domain(void)
+{
+ struct device_node *node = of_irq_find_parent(of_root);
+ struct irq_domain *ipidomain;
+
+ ipidomain = irq_find_matching_host(node, DOMAIN_BUS_IPI);
+ /*
+ * Some platforms have half DT setup. So if we found irq node but
+ * didn't find an ipidomain, try to search for one that is not in the
+ * DT.
+ */
+ if (node && !ipidomain)
+ ipidomain = irq_find_matching_host(NULL, DOMAIN_BUS_IPI);
+
+ return ipidomain;
+}
+
+int mips_rproc_op_start(struct rproc *rproc)
+{
+ struct mips_rproc *mproc = rproc->priv;
+ int err;
+ int cpu = mproc->cpu;
+
+ /* Create task for the CPU to use before handing off to firmware */
+ mproc->tsk = fork_idle(cpu);
+ if (IS_ERR(mproc->tsk)) {
+ dev_err(&rproc->dev, "fork_idle() failed for CPU%d\n", cpu);
+ return -ENOMEM;
+ }
+
+ /* We won't be needing the Linux IPIs anymore */
+ if (mips_smp_ipi_free(get_cpu_mask(cpu))) {
+ dev_err(&rproc->dev, "Failed to reserve incoming kick\n");
+ goto exit_free_tsk;
+ }
+
+ /*
+ * Direct IPIs from the remote processor to CPU0 since that can't be
+ * offlined while the remote CPU is running.
+ */
+ mproc->ipi_linux = irq_reserve_ipi(ipi_domain(), get_cpu_mask(0));
+ if (!mproc->ipi_linux) {
+ dev_err(&rproc->dev, "Failed to reserve incoming kick\n");
+ goto exit_restore_ipi;
+ }
+
+ mproc->ipi_remote = irq_reserve_ipi(ipi_domain(), get_cpu_mask(cpu));
+ if (!mproc->ipi_remote) {
+ dev_err(&rproc->dev, "Failed to reserve outgoing kick\n");
+ goto exit_destroy_ipi_linux;
+ }
+
+ /* register incoming ipi */
+ err = request_threaded_irq(mproc->ipi_linux, mips_rproc_ipi_handler,
+ mips_rproc_vq_int, 0,
+ "mips-rproc IPI in", rproc);
+ if (err) {
+ dev_err(&rproc->dev, "Failed to register incoming kick: %d\n",
+ err);
+ goto exit_destroy_ipi_remote;
+ }
+
+ if (mips_cps_steal_cpu_and_execute(cpu, &mips_rproc_cpu_entry,
+ mproc->tsk)) {
+ dev_err(&rproc->dev, "Failed to steal CPU%d for remote\n", cpu);
+ goto exit_free_irq;
+ }
+
+ return 0;
+
+exit_free_irq:
+ free_irq(mproc->ipi_linux, rproc);
+exit_destroy_ipi_remote:
+ irq_destroy_ipi(mproc->ipi_remote, get_cpu_mask(cpu));
+exit_destroy_ipi_linux:
+ irq_destroy_ipi(mproc->ipi_linux, get_cpu_mask(0));
+exit_restore_ipi:
+ /* Set up the Linux IPIs again */
+ mips_smp_ipi_allocate(get_cpu_mask(cpu));
+exit_free_tsk:
+ free_task(mproc->tsk);
+
+ return -EINVAL;
+}
+
+int mips_rproc_op_stop(struct rproc *rproc)
+{
+ struct mips_rproc *mproc = rproc->priv;
+
+ free_irq(mproc->ipi_linux, rproc);
+
+ irq_destroy_ipi(mproc->ipi_linux, get_cpu_mask(0));
+ irq_destroy_ipi(mproc->ipi_remote, get_cpu_mask(mproc->cpu));
+
+ /* Set up the Linux IPIs again */
+ mips_smp_ipi_allocate(get_cpu_mask(mproc->cpu));
+
+ free_task(mproc->tsk);
+
+ return mips_cps_halt_and_return_cpu(mproc->cpu);
+}
+
+void mips_rproc_op_kick(struct rproc *rproc, int vqid)
+{
+ struct mips_rproc *mproc = rproc->priv;
+
+ if (rproc->state == RPROC_RUNNING)
+ ipi_send_single(mproc->ipi_remote, mproc->cpu);
+}
+
+static const struct rproc_ops mips_rproc_proc_ops = {
+ .start = mips_rproc_op_start,
+ .stop = mips_rproc_op_stop,
+ .kick = mips_rproc_op_kick,
+};
+
+/* Create an rproc instance in response to CPU down */
+static int mips_rproc_device_register(unsigned int cpu)
+{
+ char *template = "mips-cpu%u";
+ struct rproc *rproc;
+ struct mips_rproc *mproc;
+ int err;
+
+ if (!cpumask_test_cpu(cpu, mips_rproc_cpumask))
+ /* The CPU is not in the mask, so don't register rproc on it */
+ return 0;
+
+ pr_debug("Allocating MIPS rproc for cpu%d\n", cpu);
+
+ if (mips_rprocs[cpu]) {
+ dev_err(&mips_rproc_dev, "CPU%d in use\n", cpu);
+ return 0;
+ }
+
+ mproc = kzalloc(sizeof(struct mips_rproc), GFP_KERNEL);
+ if (!mproc) {
+ err = -ENOMEM;
+ goto exit;
+ }
+
+ snprintf(mproc->name, sizeof(mproc->name), template, cpu);
+ mproc->cpu = cpu;
+
+ rproc = rproc_alloc(&mips_rproc_dev, mproc->name,
+ &mips_rproc_proc_ops, NULL,
+ sizeof(struct mips_rproc *));
+ if (!rproc) {
+ dev_err(&mips_rproc_dev, "Error allocating rproc\n");
+ err = -ENOMEM;
+ goto exit_free_mproc;
+ }
+
+ mproc->rproc = rproc;
+ rproc->priv = (void *)mproc;
+
+ err = rproc_add(rproc);
+ if (err) {
+ dev_err(&mips_rproc_dev, "Failed to add rproc: %d\n", err);
+ goto exit_free_rproc;
+ }
+
+ mips_rprocs[cpu] = mproc;
+ return 0;
+
+exit_free_rproc:
+ rproc_free(rproc);
+exit_free_mproc:
+ kfree(mproc);
+exit:
+ return err;
+}
+
+/* Destroy rproc instance in response to CPU up */
+static int mips_rproc_device_unregister(unsigned int cpu)
+{
+ struct mips_rproc *mproc = mips_rprocs[cpu];
+
+ if (!mproc)
+ /* No rproc instance has been created for this CPU */
+ return 0;
+
+ pr_debug("Deallocating MIPS rproc for cpu%d\n", cpu);
+
+ rproc_del(mproc->rproc);
+ rproc_put(mproc->rproc);
+ kfree(mproc);
+
+ mips_rprocs[cpu] = NULL;
+ return 0;
+}
+
+/* Show MIPS CPUs available to rproc */
+static ssize_t cpus_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return cpumap_print_to_pagebuf(true, buf, mips_rproc_cpumask);
+}
+
+/* Allow MIPS CPUs to be made available to rproc */
+static ssize_t cpus_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ static cpumask_var_t new_mask;
+ int err, cpu;
+
+ err = cpulist_parse(buf, new_mask);
+ if (err)
+ return err;
+
+ /* Prevent CPU hotplug on/offlining CPUs while we do this */
+ get_online_cpus();
+
+ for_each_possible_cpu(cpu) {
+ if (cpumask_test_cpu(cpu, mips_rproc_cpumask) &&
+ !cpumask_test_cpu(cpu, new_mask)) {
+ /* CPU no longer allowed. Release any instance on it */
+ cpumask_clear_cpu(cpu, mips_rproc_cpumask);
+ mips_rproc_device_unregister(cpu);
+
+ } else if (!cpumask_test_cpu(cpu, mips_rproc_cpumask) &&
+ cpumask_test_cpu(cpu, new_mask)) {
+ /* If the CPU isn't online, start an instance */
+ cpumask_set_cpu(cpu, mips_rproc_cpumask);
+ if (!cpu_online(cpu))
+ mips_rproc_device_register(cpu);
+ }
+ }
+ put_online_cpus();
+ return count;
+}
+static DEVICE_ATTR_RW(cpus);
+
+static struct attribute *mips_rproc_attrs[] = {
+ &dev_attr_cpus.attr,
+ NULL
+};
+
+static const struct attribute_group mips_rproc_devgroup = {
+ .attrs = mips_rproc_attrs
+};
+
+static const struct attribute_group *mips_rproc_devgroups[] = {
+ &mips_rproc_devgroup,
+ NULL
+};
+static struct device_type mips_rproc_type = {
+ .groups = mips_rproc_devgroups,
+};
+
+static struct platform_driver mips_rproc_driver = {
+ .driver = {
+ .name = "mips-rproc",
+ },
+};
+
+static int __init mips_rproc_init(void)
+{
+ int err;
+
+ if ((!cpu_has_mipsmt) && (!cpu_has_vp)) {
+ pr_debug("MIPS rproc not supported on this cpu\n");
+ return -EIO;
+ }
+
+ mips_rproc_dev.driver = &mips_rproc_driver.driver;
+ mips_rproc_dev.type = &mips_rproc_type;
+ dev_set_name(&mips_rproc_dev, "mips-rproc");
+
+ /* Set device to have coherent DMA ops */
+ arch_setup_dma_ops(&mips_rproc_dev, 0, 0, NULL, 1);
+
+ err = device_register(&mips_rproc_dev);
+ if (err) {
+ dev_err(&mips_rproc_dev, "Error adding MIPS rproc: %d\n", err);
+ return err;
+ }
+
+ /*
+ * Register with the cpu hotplug state machine.
+ * This driver requires opposite sense to "normal" drivers, since the
+ * driver is activated for offline CPUs via the teardown callback and
+ * deactivated via the online callback.
+ */
+ err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "MIPS:REMOTEPROC",
+ mips_rproc_device_unregister,
+ mips_rproc_device_register);
+ if (err < 0) {
+ device_unregister(&mips_rproc_dev);
+ return err;
+ }
+
+ cpuhp_state = err;
+
+ return 0;
+}
+
+static void __exit mips_rproc_exit(void)
+{
+ int cpu;
+
+ if (cpuhp_state) {
+ /*
+ * Unregister with the cpu hotplug state machine, but don't call
+ * the teardown callback, since that would try to start the
+ * remote processor device.
+ */
+ __cpuhp_remove_state(cpuhp_state, false);
+ cpuhp_state = 0;
+ }
+
+ get_online_cpus();
+ /* Unregister devices created for any offline CPUs */
+ for_each_possible_cpu(cpu)
+ mips_rproc_device_unregister(cpu);
+ put_online_cpus();
+}
+
+late_initcall(mips_rproc_init);
+module_exit(mips_rproc_exit);
+
+module_platform_driver(mips_rproc_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MIPS Remote Processor control driver");
--
2.7.4

2017-03-23 16:38:07

by Matt Redfearn

[permalink] [raw]
Subject: [PATCH v6 4/4] MIPS: Deprecate VPE Loader

The MIPS remote processor driver (CONFIG_MIPS_REMOTEPROC) provides a
more standard mechanism for using one or more VPs as coprocessors
running separate firmware.

Here we deprecate this mechanism before it is removed.

Signed-off-by: Matt Redfearn <[email protected]>
---

Changes in v6: None
Changes in v5: None
Changes in v4: None
Changes in v3: None
Changes in v2: None

arch/mips/Kconfig | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index 6cdc23da8761..dbec6893c6e7 100644
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -2303,7 +2303,7 @@ config MIPSR2_TO_R6_EMULATOR
final kernel image.

config MIPS_VPE_LOADER
- bool "VPE loader support."
+ bool "VPE loader support (DEPRECATED)"
depends on SYS_SUPPORTS_MULTITHREADING && MODULES
select CPU_MIPSR2_IRQ_VI
select CPU_MIPSR2_IRQ_EI
@@ -2312,6 +2312,9 @@ config MIPS_VPE_LOADER
Includes a loader for loading an elf relocatable object
onto another VPE and running it.

+ Unless you have a specific need, you should use CONFIG_MIPS_REMOTEPROC
+ instead of this.
+
config MIPS_VPE_LOADER_CMP
bool
default "y"
--
2.7.4

2017-03-23 16:37:47

by Matt Redfearn

[permalink] [raw]
Subject: [PATCH v6 1/4] irqchip: mips-gic: Add context saving for MIPS_REMOTEPROC

The MIPS remote processor driver allows non-Linux firmware to take
control of and execute on one of the systems VPEs. If that VPE is
brought back under Linux, it is necessary to ensure that all GIC
interrupts are routed and masked as Linux expects them, as the firmware
can have done anything it likes with the GIC configuration (hopefully
just for that VPEs local interrupt sources, but allow for shared
external interrupts as well).

The configuration of shared and local CPU interrupts is maintained and
updated every time a change is made. When a CPU is brought online, the
saved configuration is restored.

These functions will also be useful for restoring GIC context after a
suspend to RAM.

Signed-off-by: Matt Redfearn <[email protected]>
---

Changes in v6:
Rebase on Linux 4.11-rc3

Changes in v5: None
Changes in v4:
Fix inconsistency of Linux CPU number and VP ID

Changes in v3:
Update GIC context saving to use CPU hotplug state machine

Changes in v2:
Add dependence on additional patches to mips-gic in commit log
Incorporate changes from Marc Zynger's review:
- Remove CONTEXT_SAVING define.
- Make saved local state a per-cpu variable
- Make gic_save_* static functions when enabled, and do { } while(0)
otherwise

drivers/irqchip/irq-mips-gic.c | 207 +++++++++++++++++++++++++++++++++++++++--
1 file changed, 200 insertions(+), 7 deletions(-)

diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c
index 11d12bccc4e7..15c0feb8a52f 100644
--- a/drivers/irqchip/irq-mips-gic.c
+++ b/drivers/irqchip/irq-mips-gic.c
@@ -8,6 +8,7 @@
*/
#include <linux/bitmap.h>
#include <linux/clocksource.h>
+#include <linux/cpu.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
@@ -56,6 +57,79 @@ static unsigned int timer_cpu_pin;
static struct irq_chip gic_level_irq_controller, gic_edge_irq_controller;
DECLARE_BITMAP(ipi_resrv, GIC_MAX_INTRS);

+#ifdef CONFIG_MIPS_REMOTEPROC
+struct gic_local_state_t {
+ u8 mask;
+};
+
+DEFINE_PER_CPU(struct gic_local_state_t, gic_local_state);
+
+static void gic_save_local_rmask(int cpu, int mask)
+{
+ struct gic_local_state_t *state = per_cpu_ptr(&gic_local_state, cpu);
+
+ state->mask &= mask;
+}
+
+static void gic_save_local_smask(int cpu, int mask)
+{
+ struct gic_local_state_t *state = per_cpu_ptr(&gic_local_state, cpu);
+
+ state->mask |= mask;
+}
+
+static struct {
+ unsigned vpe: 8;
+ unsigned pin: 4;
+
+ unsigned polarity: 1;
+ unsigned trigger: 1;
+ unsigned dual_edge: 1;
+ unsigned mask: 1;
+} gic_shared_state[GIC_MAX_INTRS];
+
+static void gic_save_shared_vpe(int intr, int vpe)
+{
+ gic_shared_state[intr].vpe = vpe;
+}
+
+static void gic_save_shared_pin(int intr, int pin)
+{
+ gic_shared_state[intr].pin = pin;
+}
+
+static void gic_save_shared_polarity(int intr, int polarity)
+{
+ gic_shared_state[intr].polarity = polarity;
+}
+
+static void gic_save_shared_trigger(int intr, int trigger)
+{
+ gic_shared_state[intr].trigger = trigger;
+}
+
+static void gic_save_shared_dual_edge(int intr, int dual_edge)
+{
+ gic_shared_state[intr].dual_edge = dual_edge;
+}
+
+static void gic_save_shared_mask(int intr, int mask)
+{
+ gic_shared_state[intr].mask = mask;
+}
+
+#else
+#define gic_save_local_rmask(cpu, i) do { } while (0)
+#define gic_save_local_smask(cpu, i) do { } while (0)
+
+#define gic_save_shared_vpe(i, v) do { } while (0)
+#define gic_save_shared_pin(i, p) do { } while (0)
+#define gic_save_shared_polarity(i, p) do { } while (0)
+#define gic_save_shared_trigger(i, t) do { } while (0)
+#define gic_save_shared_dual_edge(i, d) do { } while (0)
+#define gic_save_shared_mask(i, m) do { } while (0)
+#endif /* CONFIG_MIPS_REMOTEPROC */
+
static void __gic_irq_dispatch(void);

static inline u32 gic_read32(unsigned int reg)
@@ -105,52 +179,94 @@ static inline void gic_update_bits(unsigned int reg, unsigned long mask,
gic_write(reg, regval);
}

-static inline void gic_reset_mask(unsigned int intr)
+static inline void gic_write_reset_mask(unsigned int intr)
{
gic_write(GIC_REG(SHARED, GIC_SH_RMASK) + GIC_INTR_OFS(intr),
1ul << GIC_INTR_BIT(intr));
}

-static inline void gic_set_mask(unsigned int intr)
+static inline void gic_reset_mask(unsigned int intr)
+{
+ gic_save_shared_mask(intr, 0);
+ gic_write_reset_mask(intr);
+}
+
+static inline void gic_write_set_mask(unsigned int intr)
{
gic_write(GIC_REG(SHARED, GIC_SH_SMASK) + GIC_INTR_OFS(intr),
1ul << GIC_INTR_BIT(intr));
}

-static inline void gic_set_polarity(unsigned int intr, unsigned int pol)
+static inline void gic_set_mask(unsigned int intr)
+{
+ gic_save_shared_mask(intr, 1);
+ gic_write_set_mask(intr);
+}
+
+static inline void gic_write_polarity(unsigned int intr, unsigned int pol)
{
gic_update_bits(GIC_REG(SHARED, GIC_SH_SET_POLARITY) +
GIC_INTR_OFS(intr), 1ul << GIC_INTR_BIT(intr),
(unsigned long)pol << GIC_INTR_BIT(intr));
}

-static inline void gic_set_trigger(unsigned int intr, unsigned int trig)
+static inline void gic_set_polarity(unsigned int intr, unsigned int pol)
+{
+ gic_save_shared_polarity(intr, pol);
+ gic_write_polarity(intr, pol);
+}
+
+static inline void gic_write_trigger(unsigned int intr, unsigned int trig)
{
gic_update_bits(GIC_REG(SHARED, GIC_SH_SET_TRIGGER) +
GIC_INTR_OFS(intr), 1ul << GIC_INTR_BIT(intr),
(unsigned long)trig << GIC_INTR_BIT(intr));
}

-static inline void gic_set_dual_edge(unsigned int intr, unsigned int dual)
+static inline void gic_set_trigger(unsigned int intr, unsigned int trig)
+{
+ gic_save_shared_trigger(intr, trig);
+ gic_write_trigger(intr, trig);
+}
+
+static inline void gic_write_dual_edge(unsigned int intr, unsigned int dual)
{
gic_update_bits(GIC_REG(SHARED, GIC_SH_SET_DUAL) + GIC_INTR_OFS(intr),
1ul << GIC_INTR_BIT(intr),
(unsigned long)dual << GIC_INTR_BIT(intr));
}

-static inline void gic_map_to_pin(unsigned int intr, unsigned int pin)
+static inline void gic_set_dual_edge(unsigned int intr, unsigned int dual)
+{
+ gic_save_shared_dual_edge(intr, dual);
+ gic_write_dual_edge(intr, dual);
+}
+
+static inline void gic_write_map_to_pin(unsigned int intr, unsigned int pin)
{
gic_write32(GIC_REG(SHARED, GIC_SH_INTR_MAP_TO_PIN_BASE) +
GIC_SH_MAP_TO_PIN(intr), GIC_MAP_TO_PIN_MSK | pin);
}

-static inline void gic_map_to_vpe(unsigned int intr, unsigned int vpe)
+static inline void gic_map_to_pin(unsigned int intr, unsigned int pin)
+{
+ gic_save_shared_pin(intr, pin);
+ gic_write_map_to_pin(intr, pin);
+}
+
+static inline void gic_write_map_to_vpe(unsigned int intr, unsigned int vpe)
{
gic_write(GIC_REG(SHARED, GIC_SH_INTR_MAP_TO_VPE_BASE) +
GIC_SH_MAP_TO_VPE_REG_OFF(intr, vpe),
GIC_SH_MAP_TO_VPE_REG_BIT(vpe));
}

+static inline void gic_map_to_vpe(unsigned int intr, unsigned int vpe)
+{
+ gic_save_shared_vpe(intr, vpe);
+ gic_write_map_to_vpe(intr, vpe);
+}
+
#ifdef CONFIG_CLKSRC_MIPS_GIC
u64 gic_read_count(void)
{
@@ -527,6 +643,7 @@ static void gic_mask_local_irq(struct irq_data *d)
{
int intr = GIC_HWIRQ_TO_LOCAL(d->hwirq);

+ gic_save_local_rmask(smp_processor_id(), (1 << intr));
gic_write32(GIC_REG(VPE_LOCAL, GIC_VPE_RMASK), 1 << intr);
}

@@ -534,6 +651,7 @@ static void gic_unmask_local_irq(struct irq_data *d)
{
int intr = GIC_HWIRQ_TO_LOCAL(d->hwirq);

+ gic_save_local_smask(smp_processor_id(), (1 << intr));
gic_write32(GIC_REG(VPE_LOCAL, GIC_VPE_SMASK), 1 << intr);
}

@@ -551,6 +669,7 @@ static void gic_mask_local_irq_all_vpes(struct irq_data *d)

spin_lock_irqsave(&gic_lock, flags);
for (i = 0; i < gic_vpes; i++) {
+ gic_save_local_rmask(i, 1 << intr);
gic_write(GIC_REG(VPE_LOCAL, GIC_VPE_OTHER_ADDR),
mips_cm_vp_id(i));
gic_write32(GIC_REG(VPE_OTHER, GIC_VPE_RMASK), 1 << intr);
@@ -566,6 +685,7 @@ static void gic_unmask_local_irq_all_vpes(struct irq_data *d)

spin_lock_irqsave(&gic_lock, flags);
for (i = 0; i < gic_vpes; i++) {
+ gic_save_local_smask(i, 1 << intr);
gic_write(GIC_REG(VPE_LOCAL, GIC_VPE_OTHER_ADDR),
mips_cm_vp_id(i));
gic_write32(GIC_REG(VPE_OTHER, GIC_VPE_SMASK), 1 << intr);
@@ -996,6 +1116,74 @@ static void __init gic_map_interrupts(struct device_node *node)
gic_map_single_int(node, GIC_LOCAL_INT_FDC);
}

+#ifdef CONFIG_MIPS_REMOTEPROC
+static void gic_restore_shared(void)
+{
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&gic_lock, flags);
+ for (i = 0; i < gic_shared_intrs; i++) {
+ gic_write_polarity(i, gic_shared_state[i].polarity);
+ gic_write_trigger(i, gic_shared_state[i].trigger);
+ gic_write_dual_edge(i, gic_shared_state[i].dual_edge);
+ gic_write_map_to_vpe(i, gic_shared_state[i].vpe);
+ gic_write_map_to_pin(i, gic_shared_state[i].pin);
+
+ if (gic_shared_state[i].mask)
+ gic_write_set_mask(i);
+ else
+ gic_write_reset_mask(i);
+ }
+ spin_unlock_irqrestore(&gic_lock, flags);
+}
+
+static void gic_restore_local(unsigned int cpu)
+{
+ struct gic_local_state_t state;
+ int hw, virq, intr, mask;
+ unsigned long flags;
+
+ for (hw = 0; hw < GIC_NUM_LOCAL_INTRS; hw++) {
+ intr = GIC_LOCAL_TO_HWIRQ(hw);
+ virq = irq_linear_revmap(gic_irq_domain, intr);
+ gic_local_irq_domain_map(gic_irq_domain, virq, hw);
+ }
+
+ local_irq_save(flags);
+ gic_write(GIC_REG(VPE_LOCAL, GIC_VPE_OTHER_ADDR),
+ mips_cm_vp_id(cpu));
+
+ /* Enable EIC mode if necessary */
+ gic_write32(GIC_REG(VPE_OTHER, GIC_VPE_CTL), cpu_has_veic);
+
+ /* Restore interrupt masks */
+ state = per_cpu(gic_local_state, cpu);
+ mask = state.mask;
+ gic_write32(GIC_REG(VPE_OTHER, GIC_VPE_RMASK), ~mask);
+ gic_write32(GIC_REG(VPE_OTHER, GIC_VPE_SMASK), mask);
+
+ local_irq_restore(flags);
+}
+
+/*
+ * The MIPS remote processor driver allows non-Linux firmware to take control
+ * of and execute on one of the systems VPEs. If that VPE is brought back under
+ * Linux, it is necessary to ensure that all GIC interrupts are routed and
+ * masked as Linux expects them, as the firmware can have done anything it
+ * likes with the GIC configuration (hopefully just for that VPEs local
+ * interrupt sources, but allow for shared external interrupts as well).
+ */
+static int gic_cpu_online(unsigned int cpu)
+{
+ gic_restore_shared();
+ gic_restore_local(cpu);
+
+ return 0;
+}
+
+#endif /* CONFIG_MIPS_REMOTEPROC */
+
static void __init __gic_init(unsigned long gic_base_addr,
unsigned long gic_addrspace_size,
unsigned int cpu_vec, unsigned int irqbase,
@@ -1096,6 +1284,11 @@ static void __init __gic_init(unsigned long gic_base_addr,

gic_basic_init();
gic_map_interrupts(node);
+
+#ifdef CONFIG_MIPS_REMOTEPROC
+ cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "MIPS:GIC(REMOTEPROC)",
+ gic_cpu_online, NULL);
+#endif /* CONFIG_MIPS_REMOTEPROC */
}

void __init gic_init(unsigned long gic_base_addr,
--
2.7.4

2017-04-03 10:11:11

by Matt Redfearn

[permalink] [raw]
Subject: Re: [PATCH v6 3/4] remoteproc/MIPS: Add a remoteproc driver for MIPS

Hi Bjorn,

Please could you provide any comments / review on this driver?

Thanks,

Matt


On 23/03/17 16:37, Matt Redfearn wrote:
> This driver allows a MIPS processor offlined from Linux to be used as a
> remote processor. The processor can then handle real-time tasks or
> perform coprocessing while remaining processors are available to Linux,
> effectively making the system hybrid of SMP Linux and AMP.
>
> A sysfs interface is provided to allow control of which system CPUs may
> be acquired by the driver when offlined from Linux.
>
> Coprocessor firmware must abide by the remoteproc standard, i.e.
> implement the resource table containing memory layouts and virtio device
> descriptions, and additionally abide by the MIPS UHI coprocessor boot
> protocol in the startup code.
>
> Example firmware and host executables to test them are located at [1].
>
> [1] https://github.com/MIPS/mips-rproc-example
>
> Signed-off-by: Lisa Parratt <[email protected]>
> Signed-off-by: Matt Redfearn <[email protected]>
>
> ---
>
> Changes in v6:
> Change to set_current_state() as set_task_state has been removed.
>
> Changes in v5:
> Depend on !64bit since this driver only works with 32bit kernels
> Set mproc->tsk state to TASK_DEAD before freeing it to avoid warning
> Flush icache of each carveout so that icache sees latest data written
>
> Changes in v4:
> Have a single mips-rproc device to be parent to each CPU's rproc device.
> Support per-device coherence introduced in v4.9
> Add a sysfs interface to control the mask of cpus available to rproc
>
> Changes in v3:
> Update MIPS remoteproc driver to use CPU hotplug state machine
> Remove sysfs interface from MIPS rproc driver, now provided by the core.
> Drop patches that Ralf has already merged to mips-next
>
> Changes in v2: None
>
> Documentation/ABI/testing/sysfs-devices-mips-rproc | 13 +
> drivers/remoteproc/Kconfig | 11 +
> drivers/remoteproc/Makefile | 1 +
> drivers/remoteproc/mips_remoteproc.c | 599 +++++++++++++++++++++
> 4 files changed, 624 insertions(+)
> create mode 100644 Documentation/ABI/testing/sysfs-devices-mips-rproc
> create mode 100644 drivers/remoteproc/mips_remoteproc.c
>
> diff --git a/Documentation/ABI/testing/sysfs-devices-mips-rproc b/Documentation/ABI/testing/sysfs-devices-mips-rproc
> new file mode 100644
> index 000000000000..b06f6671807a
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-devices-mips-rproc
> @@ -0,0 +1,13 @@
> +What: /sys/devices/mips-rproc/cpus
> +Date: October 2016
> +Contact: Matt Redfearn <[email protected]>
> +Description:
> + CPU topology file describing which CPUs may be used by the
> + MIPS remote processor driver when offline from Linux.
> +
> + This can be read to observe the current setting, or written to
> + change the allowed CPUs.
> +
> + The format is compatible with cpulist_parse()
> + [see <linux/cpumask.h>], for example to enable the MIPS remote
> + processor driver on CPUs 1,2 & 3, write "1-3" into this file.
> diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
> index 65f86bc24c07..558b67184723 100644
> --- a/drivers/remoteproc/Kconfig
> +++ b/drivers/remoteproc/Kconfig
> @@ -71,6 +71,17 @@ config DA8XX_REMOTEPROC
> It's safe to say n here if you're not interested in multimedia
> offloading.
>
> +config MIPS_REMOTEPROC
> + tristate "MIPS remoteproc support"
> + depends on MIPS_CPS && HAS_DMA && !64BIT
> + depends on REMOTEPROC
> + select CMA
> + select MIPS_CPU_STEAL
> + help
> + Say y here to support using offline cores/VPEs as remote processors
> + via the remote processor framework.
> + If unsure say N.
> +
> config QCOM_ADSP_PIL
> tristate "Qualcomm ADSP Peripheral Image Loader"
> depends on OF && ARCH_QCOM
> diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
> index ffc5e430df27..9525debba686 100644
> --- a/drivers/remoteproc/Makefile
> +++ b/drivers/remoteproc/Makefile
> @@ -11,6 +11,7 @@ remoteproc-y += remoteproc_elf_loader.o
> obj-$(CONFIG_OMAP_REMOTEPROC) += omap_remoteproc.o
> obj-$(CONFIG_WKUP_M3_RPROC) += wkup_m3_rproc.o
> obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o
> +obj-$(CONFIG_MIPS_REMOTEPROC) += mips_remoteproc.o
> obj-$(CONFIG_QCOM_ADSP_PIL) += qcom_adsp_pil.o
> obj-$(CONFIG_QCOM_RPROC_COMMON) += qcom_common.o
> obj-$(CONFIG_QCOM_Q6V5_PIL) += qcom_q6v5_pil.o
> diff --git a/drivers/remoteproc/mips_remoteproc.c b/drivers/remoteproc/mips_remoteproc.c
> new file mode 100644
> index 000000000000..9a0e5e06c0b1
> --- /dev/null
> +++ b/drivers/remoteproc/mips_remoteproc.c
> @@ -0,0 +1,599 @@
> +/*
> + * MIPS Remote Processor driver
> + *
> + * Copyright (C) 2016 Imagination Technologies
> + * Lisa Parratt <[email protected]>
> + * Matt Redfearn <[email protected]>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License as published by the
> + * Free Software Foundation; either version 2 of the License, or (at your
> + * option) any later version.
> + */
> +
> +#include <linux/cpu.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/irq.h>
> +#include <linux/module.h>
> +#include <linux/of_irq.h>
> +#include <linux/platform_device.h>
> +#include <linux/remoteproc.h>
> +#include <linux/sched/task.h>
> +
> +#include <asm/cacheflush.h>
> +#include <asm/smp-cps.h>
> +#include <asm/tlbflush.h>
> +#include <asm/tlbmisc.h>
> +
> +#include "remoteproc_internal.h"
> +
> +struct mips_rproc {
> + char name[16];
> + struct rproc *rproc;
> + struct task_struct *tsk;
> + unsigned int cpu;
> + int ipi_linux;
> + int ipi_remote;
> +};
> +
> +/* Parent device for MIPS remoteproc */
> +static struct device mips_rproc_dev;
> +
> +/* Array of allocated MIPS remote processor instances */
> +static struct mips_rproc *mips_rprocs[NR_CPUS];
> +
> +/* Bitmap used to identify which CPUs are available to rproc */
> +static cpumask_var_t mips_rproc_cpumask;
> +
> +/* Dynamic CPU hotplug state associated with this driver */
> +static int cpuhp_state;
> +
> +/* Add wired entry to map a device address to physical memory */
> +static void mips_map_page(unsigned long da, unsigned long pa, int c,
> + unsigned long pagesize)
> +{
> + unsigned long pa2 = pa + (pagesize / 2);
> + unsigned long entryhi, entrylo0, entrylo1;
> + unsigned long pagemask = pagesize - 0x2000;
> +
> + pa = (pa >> 6) & (ULONG_MAX << MIPS_ENTRYLO_PFN_SHIFT);
> + pa2 = (pa2 >> 6) & (ULONG_MAX << MIPS_ENTRYLO_PFN_SHIFT);
> + entryhi = da & 0xfffffe000;
> + entrylo0 = (c << ENTRYLO_C_SHIFT) | ENTRYLO_D | ENTRYLO_V | pa;
> + entrylo1 = (c << ENTRYLO_C_SHIFT) | ENTRYLO_D | ENTRYLO_V | pa2;
> +
> + pr_debug("Create wired entry %d, CCA %d\n", read_c0_wired(), c);
> + pr_debug(" EntryHi: 0x%016lx\n", entryhi);
> + pr_debug(" EntryLo0: 0x%016lx\n", entrylo0);
> + pr_debug(" EntryLo1: 0x%016lx\n", entrylo1);
> + pr_debug(" Pagemask: 0x%016lx\n", pagemask);
> + pr_debug("\n");
> +
> + add_wired_entry(entrylo0, entrylo1, entryhi, pagemask);
> +}
> +
> +/* Compute the largest page mask a physical address can be mapped with */
> +static unsigned long mips_rproc_largest_pm(unsigned long pa,
> + unsigned long maxmask)
> +{
> + unsigned long mask;
> + /* Find address bits limiting alignment */
> + unsigned long shift = ffs(pa);
> +
> + /* Obey MIPS restrictions on page sizes */
> + if (pa) {
> + if (shift & 1)
> + shift -= 2;
> + else
> + shift--;
> + }
> + mask = ULONG_MAX << shift;
> + return maxmask & ~mask;
> +}
> +
> +/* Compute the page mask one step larger than a given page mask */
> +static unsigned long mips_rproc_next_pm(unsigned long pm, unsigned long maxmask)
> +{
> +#define PM_SHIFT 13
> + return ((pm << 2) | (0x3 << PM_SHIFT)) & maxmask;
> +}
> +
> +/*
> + * Add mappings to the TLB such that memory allocated by the kernel for a
> + * firmware component appears at the right virtual address
> + */
> +static inline void mips_rproc_map(unsigned long da, unsigned long pa, int c,
> + unsigned long size, unsigned long maxmask)
> +{
> + /* minimum mappable size is 2 * 4k pages */
> + const unsigned long min_map_sz = 0x2000;
> + unsigned long bigmask, nextmask;
> + unsigned long distance, target;
> + unsigned long page2_size; /* Size of the 2 buddy pages */
> +
> + do {
> + /* Compute the current largest page mask */
> + bigmask = mips_rproc_largest_pm(pa, maxmask);
> + /* Compute the next largest pagesize */
> + nextmask = mips_rproc_next_pm(bigmask, maxmask);
> + /*
> + * Compute the distance from our current physical address to
> + * the next page boundary.
> + */
> + distance = (nextmask + min_map_sz) - (pa & nextmask);
> + /*
> + * Decide between searching to get to the next highest page
> + * boundary or finishing.
> + */
> + target = distance < size ? distance : size;
> + while (target) {
> + /* Find the largest supported page size that will fit */
> + for (page2_size = maxmask + min_map_sz;
> + (page2_size > min_map_sz) && (page2_size > target);
> + page2_size /= 4) {
> + }
> + /* Emit it */
> + mips_map_page(da, pa, c, page2_size);
> + /* Move to next step */
> + size -= page2_size;
> + da += page2_size;
> + pa += page2_size;
> + target -= page2_size;
> + }
> + } while (size);
> +}
> +
> +static int mips_rproc_carveouts(struct rproc *rproc, int max_pagemask)
> +{
> + struct rproc_mem_entry *carveout;
> +
> + list_for_each_entry(carveout, &rproc->carveouts, node) {
> + int c = CONF_CM_CACHABLE_COW;
> +
> + dev_dbg(&rproc->dev,
> + "carveout mapping da 0x%x -> %pad length 0x%x, CCA %d",
> + carveout->da, &carveout->dma, carveout->len, c);
> +
> + mips_rproc_map(carveout->da, carveout->dma, c,
> + carveout->len, max_pagemask);
> + flush_icache_range((unsigned long)carveout->va,
> + (unsigned long)carveout->va + carveout->len);
> + }
> + return 0;
> +}
> +
> +static int mips_rproc_vdevs(struct rproc *rproc, int max_pagemask)
> +{
> + struct rproc_vdev *rvdev;
> +
> + list_for_each_entry(rvdev, &rproc->rvdevs, node) {
> + int i, size;
> +
> + for (i = 0; i < ARRAY_SIZE(rvdev->vring); i++) {
> + struct rproc_vring *vring = &rvdev->vring[i];
> + unsigned long pa = vring->dma;
> + int c;
> +
> + if (plat_device_is_coherent(&mips_rproc_dev)) {
> + /*
> + * The DMA API will allocate cacheable buffers
> + * for shared resources, so the firmware should
> + * also access those buffers cached
> + */
> + c = (_page_cachable_default >> _CACHE_SHIFT);
> + } else {
> + /*
> + * Otherwise, shared buffers should be accessed
> + * uncached
> + */
> + c = CONF_CM_UNCACHED;
> + }
> +
> + /* actual size of vring (in bytes) */
> + size = PAGE_ALIGN(vring_size(vring->len, vring->align));
> +
> + dev_dbg(&rproc->dev,
> + "vring mapping da %pad -> %pad length 0x%x, CCA %d",
> + &vring->dma, &vring->dma, size, c);
> +
> + mips_rproc_map(pa, pa, c, size, max_pagemask);
> + }
> + }
> + return 0;
> +}
> +
> +static void mips_rproc_cpu_entry(void)
> +{
> + struct mips_rproc *mproc = mips_rprocs[smp_processor_id()];
> + struct rproc *rproc = mproc->rproc;
> + int ipi_to_remote = ipi_get_hwirq(mproc->ipi_remote, mproc->cpu);
> + int ipi_from_remote = ipi_get_hwirq(mproc->ipi_linux, 0);
> + unsigned long old_pagemask, max_pagemask;
> + void (*fw_entry)(int, int ipi_to_remote, int ipi_from_remote, int);
> +
> + dev_info(&rproc->dev, "%s booting firmware %s\n",
> + rproc->name, rproc->firmware);
> +
> + /* Get the maximum pagemask supported on this CPU */
> + old_pagemask = read_c0_pagemask();
> + write_c0_pagemask(~0);
> + back_to_back_c0_hazard();
> + max_pagemask = read_c0_pagemask();
> + write_c0_pagemask(old_pagemask);
> + back_to_back_c0_hazard();
> +
> + /* Start with no wired entries */
> + write_c0_wired(0);
> +
> + /* Flush all previous TLB entries */
> + local_flush_tlb_all();
> +
> + /* Set ASID 0 */
> + write_c0_entryhi(0);
> +
> + /* Map firmware resources into virtual memory */
> + mips_rproc_carveouts(rproc, max_pagemask);
> + mips_rproc_vdevs(rproc, max_pagemask);
> +
> + dev_dbg(&rproc->dev, "IPI to remote: %d\n", ipi_to_remote);
> + dev_dbg(&rproc->dev, "IPI from remote: %d\n", ipi_from_remote);
> +
> + /* Hand off the CPU to the firmware */
> + dev_dbg(&rproc->dev, "Jumping to firmware at 0x%x\n", rproc->bootaddr);
> +
> + /* We're done with the task struct that provided the stack we've used */
> + set_current_state(TASK_DEAD);
> +
> + /* Jump into the firmware, obeying the firmware protocol. */
> + fw_entry = (void *)rproc->bootaddr;
> + fw_entry(-3, ipi_to_remote, ipi_from_remote, 0);
> +}
> +
> +static irqreturn_t mips_rproc_ipi_handler(int irq, void *dev_id)
> +{
> + /* Synthetic interrupts shouldn't need acking */
> + return IRQ_WAKE_THREAD;
> +}
> +
> +static irqreturn_t mips_rproc_vq_int(int irq, void *p)
> +{
> + struct rproc *rproc = (struct rproc *)p;
> + void *entry;
> + int id;
> +
> + /* We don't have a mailbox, so iterate over all vqs and kick them. */
> + idr_for_each_entry(&rproc->notifyids, entry, id)
> + rproc_vq_interrupt(rproc, id);
> +
> + return IRQ_HANDLED;
> +}
> +
> +/* Helper function to find the IPI domain */
> +static struct irq_domain *ipi_domain(void)
> +{
> + struct device_node *node = of_irq_find_parent(of_root);
> + struct irq_domain *ipidomain;
> +
> + ipidomain = irq_find_matching_host(node, DOMAIN_BUS_IPI);
> + /*
> + * Some platforms have half DT setup. So if we found irq node but
> + * didn't find an ipidomain, try to search for one that is not in the
> + * DT.
> + */
> + if (node && !ipidomain)
> + ipidomain = irq_find_matching_host(NULL, DOMAIN_BUS_IPI);
> +
> + return ipidomain;
> +}
> +
> +int mips_rproc_op_start(struct rproc *rproc)
> +{
> + struct mips_rproc *mproc = rproc->priv;
> + int err;
> + int cpu = mproc->cpu;
> +
> + /* Create task for the CPU to use before handing off to firmware */
> + mproc->tsk = fork_idle(cpu);
> + if (IS_ERR(mproc->tsk)) {
> + dev_err(&rproc->dev, "fork_idle() failed for CPU%d\n", cpu);
> + return -ENOMEM;
> + }
> +
> + /* We won't be needing the Linux IPIs anymore */
> + if (mips_smp_ipi_free(get_cpu_mask(cpu))) {
> + dev_err(&rproc->dev, "Failed to reserve incoming kick\n");
> + goto exit_free_tsk;
> + }
> +
> + /*
> + * Direct IPIs from the remote processor to CPU0 since that can't be
> + * offlined while the remote CPU is running.
> + */
> + mproc->ipi_linux = irq_reserve_ipi(ipi_domain(), get_cpu_mask(0));
> + if (!mproc->ipi_linux) {
> + dev_err(&rproc->dev, "Failed to reserve incoming kick\n");
> + goto exit_restore_ipi;
> + }
> +
> + mproc->ipi_remote = irq_reserve_ipi(ipi_domain(), get_cpu_mask(cpu));
> + if (!mproc->ipi_remote) {
> + dev_err(&rproc->dev, "Failed to reserve outgoing kick\n");
> + goto exit_destroy_ipi_linux;
> + }
> +
> + /* register incoming ipi */
> + err = request_threaded_irq(mproc->ipi_linux, mips_rproc_ipi_handler,
> + mips_rproc_vq_int, 0,
> + "mips-rproc IPI in", rproc);
> + if (err) {
> + dev_err(&rproc->dev, "Failed to register incoming kick: %d\n",
> + err);
> + goto exit_destroy_ipi_remote;
> + }
> +
> + if (mips_cps_steal_cpu_and_execute(cpu, &mips_rproc_cpu_entry,
> + mproc->tsk)) {
> + dev_err(&rproc->dev, "Failed to steal CPU%d for remote\n", cpu);
> + goto exit_free_irq;
> + }
> +
> + return 0;
> +
> +exit_free_irq:
> + free_irq(mproc->ipi_linux, rproc);
> +exit_destroy_ipi_remote:
> + irq_destroy_ipi(mproc->ipi_remote, get_cpu_mask(cpu));
> +exit_destroy_ipi_linux:
> + irq_destroy_ipi(mproc->ipi_linux, get_cpu_mask(0));
> +exit_restore_ipi:
> + /* Set up the Linux IPIs again */
> + mips_smp_ipi_allocate(get_cpu_mask(cpu));
> +exit_free_tsk:
> + free_task(mproc->tsk);
> +
> + return -EINVAL;
> +}
> +
> +int mips_rproc_op_stop(struct rproc *rproc)
> +{
> + struct mips_rproc *mproc = rproc->priv;
> +
> + free_irq(mproc->ipi_linux, rproc);
> +
> + irq_destroy_ipi(mproc->ipi_linux, get_cpu_mask(0));
> + irq_destroy_ipi(mproc->ipi_remote, get_cpu_mask(mproc->cpu));
> +
> + /* Set up the Linux IPIs again */
> + mips_smp_ipi_allocate(get_cpu_mask(mproc->cpu));
> +
> + free_task(mproc->tsk);
> +
> + return mips_cps_halt_and_return_cpu(mproc->cpu);
> +}
> +
> +void mips_rproc_op_kick(struct rproc *rproc, int vqid)
> +{
> + struct mips_rproc *mproc = rproc->priv;
> +
> + if (rproc->state == RPROC_RUNNING)
> + ipi_send_single(mproc->ipi_remote, mproc->cpu);
> +}
> +
> +static const struct rproc_ops mips_rproc_proc_ops = {
> + .start = mips_rproc_op_start,
> + .stop = mips_rproc_op_stop,
> + .kick = mips_rproc_op_kick,
> +};
> +
> +/* Create an rproc instance in response to CPU down */
> +static int mips_rproc_device_register(unsigned int cpu)
> +{
> + char *template = "mips-cpu%u";
> + struct rproc *rproc;
> + struct mips_rproc *mproc;
> + int err;
> +
> + if (!cpumask_test_cpu(cpu, mips_rproc_cpumask))
> + /* The CPU is not in the mask, so don't register rproc on it */
> + return 0;
> +
> + pr_debug("Allocating MIPS rproc for cpu%d\n", cpu);
> +
> + if (mips_rprocs[cpu]) {
> + dev_err(&mips_rproc_dev, "CPU%d in use\n", cpu);
> + return 0;
> + }
> +
> + mproc = kzalloc(sizeof(struct mips_rproc), GFP_KERNEL);
> + if (!mproc) {
> + err = -ENOMEM;
> + goto exit;
> + }
> +
> + snprintf(mproc->name, sizeof(mproc->name), template, cpu);
> + mproc->cpu = cpu;
> +
> + rproc = rproc_alloc(&mips_rproc_dev, mproc->name,
> + &mips_rproc_proc_ops, NULL,
> + sizeof(struct mips_rproc *));
> + if (!rproc) {
> + dev_err(&mips_rproc_dev, "Error allocating rproc\n");
> + err = -ENOMEM;
> + goto exit_free_mproc;
> + }
> +
> + mproc->rproc = rproc;
> + rproc->priv = (void *)mproc;
> +
> + err = rproc_add(rproc);
> + if (err) {
> + dev_err(&mips_rproc_dev, "Failed to add rproc: %d\n", err);
> + goto exit_free_rproc;
> + }
> +
> + mips_rprocs[cpu] = mproc;
> + return 0;
> +
> +exit_free_rproc:
> + rproc_free(rproc);
> +exit_free_mproc:
> + kfree(mproc);
> +exit:
> + return err;
> +}
> +
> +/* Destroy rproc instance in response to CPU up */
> +static int mips_rproc_device_unregister(unsigned int cpu)
> +{
> + struct mips_rproc *mproc = mips_rprocs[cpu];
> +
> + if (!mproc)
> + /* No rproc instance has been created for this CPU */
> + return 0;
> +
> + pr_debug("Deallocating MIPS rproc for cpu%d\n", cpu);
> +
> + rproc_del(mproc->rproc);
> + rproc_put(mproc->rproc);
> + kfree(mproc);
> +
> + mips_rprocs[cpu] = NULL;
> + return 0;
> +}
> +
> +/* Show MIPS CPUs available to rproc */
> +static ssize_t cpus_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + return cpumap_print_to_pagebuf(true, buf, mips_rproc_cpumask);
> +}
> +
> +/* Allow MIPS CPUs to be made available to rproc */
> +static ssize_t cpus_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + static cpumask_var_t new_mask;
> + int err, cpu;
> +
> + err = cpulist_parse(buf, new_mask);
> + if (err)
> + return err;
> +
> + /* Prevent CPU hotplug on/offlining CPUs while we do this */
> + get_online_cpus();
> +
> + for_each_possible_cpu(cpu) {
> + if (cpumask_test_cpu(cpu, mips_rproc_cpumask) &&
> + !cpumask_test_cpu(cpu, new_mask)) {
> + /* CPU no longer allowed. Release any instance on it */
> + cpumask_clear_cpu(cpu, mips_rproc_cpumask);
> + mips_rproc_device_unregister(cpu);
> +
> + } else if (!cpumask_test_cpu(cpu, mips_rproc_cpumask) &&
> + cpumask_test_cpu(cpu, new_mask)) {
> + /* If the CPU isn't online, start an instance */
> + cpumask_set_cpu(cpu, mips_rproc_cpumask);
> + if (!cpu_online(cpu))
> + mips_rproc_device_register(cpu);
> + }
> + }
> + put_online_cpus();
> + return count;
> +}
> +static DEVICE_ATTR_RW(cpus);
> +
> +static struct attribute *mips_rproc_attrs[] = {
> + &dev_attr_cpus.attr,
> + NULL
> +};
> +
> +static const struct attribute_group mips_rproc_devgroup = {
> + .attrs = mips_rproc_attrs
> +};
> +
> +static const struct attribute_group *mips_rproc_devgroups[] = {
> + &mips_rproc_devgroup,
> + NULL
> +};
> +static struct device_type mips_rproc_type = {
> + .groups = mips_rproc_devgroups,
> +};
> +
> +static struct platform_driver mips_rproc_driver = {
> + .driver = {
> + .name = "mips-rproc",
> + },
> +};
> +
> +static int __init mips_rproc_init(void)
> +{
> + int err;
> +
> + if ((!cpu_has_mipsmt) && (!cpu_has_vp)) {
> + pr_debug("MIPS rproc not supported on this cpu\n");
> + return -EIO;
> + }
> +
> + mips_rproc_dev.driver = &mips_rproc_driver.driver;
> + mips_rproc_dev.type = &mips_rproc_type;
> + dev_set_name(&mips_rproc_dev, "mips-rproc");
> +
> + /* Set device to have coherent DMA ops */
> + arch_setup_dma_ops(&mips_rproc_dev, 0, 0, NULL, 1);
> +
> + err = device_register(&mips_rproc_dev);
> + if (err) {
> + dev_err(&mips_rproc_dev, "Error adding MIPS rproc: %d\n", err);
> + return err;
> + }
> +
> + /*
> + * Register with the cpu hotplug state machine.
> + * This driver requires opposite sense to "normal" drivers, since the
> + * driver is activated for offline CPUs via the teardown callback and
> + * deactivated via the online callback.
> + */
> + err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "MIPS:REMOTEPROC",
> + mips_rproc_device_unregister,
> + mips_rproc_device_register);
> + if (err < 0) {
> + device_unregister(&mips_rproc_dev);
> + return err;
> + }
> +
> + cpuhp_state = err;
> +
> + return 0;
> +}
> +
> +static void __exit mips_rproc_exit(void)
> +{
> + int cpu;
> +
> + if (cpuhp_state) {
> + /*
> + * Unregister with the cpu hotplug state machine, but don't call
> + * the teardown callback, since that would try to start the
> + * remote processor device.
> + */
> + __cpuhp_remove_state(cpuhp_state, false);
> + cpuhp_state = 0;
> + }
> +
> + get_online_cpus();
> + /* Unregister devices created for any offline CPUs */
> + for_each_possible_cpu(cpu)
> + mips_rproc_device_unregister(cpu);
> + put_online_cpus();
> +}
> +
> +late_initcall(mips_rproc_init);
> +module_exit(mips_rproc_exit);
> +
> +module_platform_driver(mips_rproc_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("MIPS Remote Processor control driver");

2017-06-14 11:26:24

by Ralf Baechle

[permalink] [raw]
Subject: Re: [PATCH v6 3/4] remoteproc/MIPS: Add a remoteproc driver for MIPS

ping again ...

Ralf

On Mon, Apr 03, 2017 at 11:11:04AM +0100, Matt Redfearn wrote:

> Hi Bjorn,
>
> Please could you provide any comments / review on this driver?
>
> Thanks,
>
> Matt
>
>
> On 23/03/17 16:37, Matt Redfearn wrote:
> > This driver allows a MIPS processor offlined from Linux to be used as a
> > remote processor. The processor can then handle real-time tasks or
> > perform coprocessing while remaining processors are available to Linux,
> > effectively making the system hybrid of SMP Linux and AMP.
> >
> > A sysfs interface is provided to allow control of which system CPUs may
> > be acquired by the driver when offlined from Linux.
> >
> > Coprocessor firmware must abide by the remoteproc standard, i.e.
> > implement the resource table containing memory layouts and virtio device
> > descriptions, and additionally abide by the MIPS UHI coprocessor boot
> > protocol in the startup code.
> >
> > Example firmware and host executables to test them are located at [1].
> >
> > [1] https://github.com/MIPS/mips-rproc-example
> >
> > Signed-off-by: Lisa Parratt <[email protected]>
> > Signed-off-by: Matt Redfearn <[email protected]>
> >
> > ---
> >
> > Changes in v6:
> > Change to set_current_state() as set_task_state has been removed.
> >
> > Changes in v5:
> > Depend on !64bit since this driver only works with 32bit kernels
> > Set mproc->tsk state to TASK_DEAD before freeing it to avoid warning
> > Flush icache of each carveout so that icache sees latest data written
> >
> > Changes in v4:
> > Have a single mips-rproc device to be parent to each CPU's rproc device.
> > Support per-device coherence introduced in v4.9
> > Add a sysfs interface to control the mask of cpus available to rproc
> >
> > Changes in v3:
> > Update MIPS remoteproc driver to use CPU hotplug state machine
> > Remove sysfs interface from MIPS rproc driver, now provided by the core.
> > Drop patches that Ralf has already merged to mips-next
> >
> > Changes in v2: None
> >
> > Documentation/ABI/testing/sysfs-devices-mips-rproc | 13 +
> > drivers/remoteproc/Kconfig | 11 +
> > drivers/remoteproc/Makefile | 1 +
> > drivers/remoteproc/mips_remoteproc.c | 599 +++++++++++++++++++++
> > 4 files changed, 624 insertions(+)
> > create mode 100644 Documentation/ABI/testing/sysfs-devices-mips-rproc
> > create mode 100644 drivers/remoteproc/mips_remoteproc.c
> >
> > diff --git a/Documentation/ABI/testing/sysfs-devices-mips-rproc b/Documentation/ABI/testing/sysfs-devices-mips-rproc
> > new file mode 100644
> > index 000000000000..b06f6671807a
> > --- /dev/null
> > +++ b/Documentation/ABI/testing/sysfs-devices-mips-rproc
> > @@ -0,0 +1,13 @@
> > +What: /sys/devices/mips-rproc/cpus
> > +Date: October 2016
> > +Contact: Matt Redfearn <[email protected]>
> > +Description:
> > + CPU topology file describing which CPUs may be used by the
> > + MIPS remote processor driver when offline from Linux.
> > +
> > + This can be read to observe the current setting, or written to
> > + change the allowed CPUs.
> > +
> > + The format is compatible with cpulist_parse()
> > + [see <linux/cpumask.h>], for example to enable the MIPS remote
> > + processor driver on CPUs 1,2 & 3, write "1-3" into this file.
> > diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
> > index 65f86bc24c07..558b67184723 100644
> > --- a/drivers/remoteproc/Kconfig
> > +++ b/drivers/remoteproc/Kconfig
> > @@ -71,6 +71,17 @@ config DA8XX_REMOTEPROC
> > It's safe to say n here if you're not interested in multimedia
> > offloading.
> > +config MIPS_REMOTEPROC
> > + tristate "MIPS remoteproc support"
> > + depends on MIPS_CPS && HAS_DMA && !64BIT
> > + depends on REMOTEPROC
> > + select CMA
> > + select MIPS_CPU_STEAL
> > + help
> > + Say y here to support using offline cores/VPEs as remote processors
> > + via the remote processor framework.
> > + If unsure say N.
> > +
> > config QCOM_ADSP_PIL
> > tristate "Qualcomm ADSP Peripheral Image Loader"
> > depends on OF && ARCH_QCOM
> > diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
> > index ffc5e430df27..9525debba686 100644
> > --- a/drivers/remoteproc/Makefile
> > +++ b/drivers/remoteproc/Makefile
> > @@ -11,6 +11,7 @@ remoteproc-y += remoteproc_elf_loader.o
> > obj-$(CONFIG_OMAP_REMOTEPROC) += omap_remoteproc.o
> > obj-$(CONFIG_WKUP_M3_RPROC) += wkup_m3_rproc.o
> > obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o
> > +obj-$(CONFIG_MIPS_REMOTEPROC) += mips_remoteproc.o
> > obj-$(CONFIG_QCOM_ADSP_PIL) += qcom_adsp_pil.o
> > obj-$(CONFIG_QCOM_RPROC_COMMON) += qcom_common.o
> > obj-$(CONFIG_QCOM_Q6V5_PIL) += qcom_q6v5_pil.o
> > diff --git a/drivers/remoteproc/mips_remoteproc.c b/drivers/remoteproc/mips_remoteproc.c
> > new file mode 100644
> > index 000000000000..9a0e5e06c0b1
> > --- /dev/null
> > +++ b/drivers/remoteproc/mips_remoteproc.c
> > @@ -0,0 +1,599 @@
> > +/*
> > + * MIPS Remote Processor driver
> > + *
> > + * Copyright (C) 2016 Imagination Technologies
> > + * Lisa Parratt <[email protected]>
> > + * Matt Redfearn <[email protected]>
> > + *
> > + * This program is free software; you can redistribute it and/or modify it
> > + * under the terms of the GNU General Public License as published by the
> > + * Free Software Foundation; either version 2 of the License, or (at your
> > + * option) any later version.
> > + */
> > +
> > +#include <linux/cpu.h>
> > +#include <linux/dma-mapping.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/io.h>
> > +#include <linux/irq.h>
> > +#include <linux/module.h>
> > +#include <linux/of_irq.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/remoteproc.h>
> > +#include <linux/sched/task.h>
> > +
> > +#include <asm/cacheflush.h>
> > +#include <asm/smp-cps.h>
> > +#include <asm/tlbflush.h>
> > +#include <asm/tlbmisc.h>
> > +
> > +#include "remoteproc_internal.h"
> > +
> > +struct mips_rproc {
> > + char name[16];
> > + struct rproc *rproc;
> > + struct task_struct *tsk;
> > + unsigned int cpu;
> > + int ipi_linux;
> > + int ipi_remote;
> > +};
> > +
> > +/* Parent device for MIPS remoteproc */
> > +static struct device mips_rproc_dev;
> > +
> > +/* Array of allocated MIPS remote processor instances */
> > +static struct mips_rproc *mips_rprocs[NR_CPUS];
> > +
> > +/* Bitmap used to identify which CPUs are available to rproc */
> > +static cpumask_var_t mips_rproc_cpumask;
> > +
> > +/* Dynamic CPU hotplug state associated with this driver */
> > +static int cpuhp_state;
> > +
> > +/* Add wired entry to map a device address to physical memory */
> > +static void mips_map_page(unsigned long da, unsigned long pa, int c,
> > + unsigned long pagesize)
> > +{
> > + unsigned long pa2 = pa + (pagesize / 2);
> > + unsigned long entryhi, entrylo0, entrylo1;
> > + unsigned long pagemask = pagesize - 0x2000;
> > +
> > + pa = (pa >> 6) & (ULONG_MAX << MIPS_ENTRYLO_PFN_SHIFT);
> > + pa2 = (pa2 >> 6) & (ULONG_MAX << MIPS_ENTRYLO_PFN_SHIFT);
> > + entryhi = da & 0xfffffe000;
> > + entrylo0 = (c << ENTRYLO_C_SHIFT) | ENTRYLO_D | ENTRYLO_V | pa;
> > + entrylo1 = (c << ENTRYLO_C_SHIFT) | ENTRYLO_D | ENTRYLO_V | pa2;
> > +
> > + pr_debug("Create wired entry %d, CCA %d\n", read_c0_wired(), c);
> > + pr_debug(" EntryHi: 0x%016lx\n", entryhi);
> > + pr_debug(" EntryLo0: 0x%016lx\n", entrylo0);
> > + pr_debug(" EntryLo1: 0x%016lx\n", entrylo1);
> > + pr_debug(" Pagemask: 0x%016lx\n", pagemask);
> > + pr_debug("\n");
> > +
> > + add_wired_entry(entrylo0, entrylo1, entryhi, pagemask);
> > +}
> > +
> > +/* Compute the largest page mask a physical address can be mapped with */
> > +static unsigned long mips_rproc_largest_pm(unsigned long pa,
> > + unsigned long maxmask)
> > +{
> > + unsigned long mask;
> > + /* Find address bits limiting alignment */
> > + unsigned long shift = ffs(pa);
> > +
> > + /* Obey MIPS restrictions on page sizes */
> > + if (pa) {
> > + if (shift & 1)
> > + shift -= 2;
> > + else
> > + shift--;
> > + }
> > + mask = ULONG_MAX << shift;
> > + return maxmask & ~mask;
> > +}
> > +
> > +/* Compute the page mask one step larger than a given page mask */
> > +static unsigned long mips_rproc_next_pm(unsigned long pm, unsigned long maxmask)
> > +{
> > +#define PM_SHIFT 13
> > + return ((pm << 2) | (0x3 << PM_SHIFT)) & maxmask;
> > +}
> > +
> > +/*
> > + * Add mappings to the TLB such that memory allocated by the kernel for a
> > + * firmware component appears at the right virtual address
> > + */
> > +static inline void mips_rproc_map(unsigned long da, unsigned long pa, int c,
> > + unsigned long size, unsigned long maxmask)
> > +{
> > + /* minimum mappable size is 2 * 4k pages */
> > + const unsigned long min_map_sz = 0x2000;
> > + unsigned long bigmask, nextmask;
> > + unsigned long distance, target;
> > + unsigned long page2_size; /* Size of the 2 buddy pages */
> > +
> > + do {
> > + /* Compute the current largest page mask */
> > + bigmask = mips_rproc_largest_pm(pa, maxmask);
> > + /* Compute the next largest pagesize */
> > + nextmask = mips_rproc_next_pm(bigmask, maxmask);
> > + /*
> > + * Compute the distance from our current physical address to
> > + * the next page boundary.
> > + */
> > + distance = (nextmask + min_map_sz) - (pa & nextmask);
> > + /*
> > + * Decide between searching to get to the next highest page
> > + * boundary or finishing.
> > + */
> > + target = distance < size ? distance : size;
> > + while (target) {
> > + /* Find the largest supported page size that will fit */
> > + for (page2_size = maxmask + min_map_sz;
> > + (page2_size > min_map_sz) && (page2_size > target);
> > + page2_size /= 4) {
> > + }
> > + /* Emit it */
> > + mips_map_page(da, pa, c, page2_size);
> > + /* Move to next step */
> > + size -= page2_size;
> > + da += page2_size;
> > + pa += page2_size;
> > + target -= page2_size;
> > + }
> > + } while (size);
> > +}
> > +
> > +static int mips_rproc_carveouts(struct rproc *rproc, int max_pagemask)
> > +{
> > + struct rproc_mem_entry *carveout;
> > +
> > + list_for_each_entry(carveout, &rproc->carveouts, node) {
> > + int c = CONF_CM_CACHABLE_COW;
> > +
> > + dev_dbg(&rproc->dev,
> > + "carveout mapping da 0x%x -> %pad length 0x%x, CCA %d",
> > + carveout->da, &carveout->dma, carveout->len, c);
> > +
> > + mips_rproc_map(carveout->da, carveout->dma, c,
> > + carveout->len, max_pagemask);
> > + flush_icache_range((unsigned long)carveout->va,
> > + (unsigned long)carveout->va + carveout->len);
> > + }
> > + return 0;
> > +}
> > +
> > +static int mips_rproc_vdevs(struct rproc *rproc, int max_pagemask)
> > +{
> > + struct rproc_vdev *rvdev;
> > +
> > + list_for_each_entry(rvdev, &rproc->rvdevs, node) {
> > + int i, size;
> > +
> > + for (i = 0; i < ARRAY_SIZE(rvdev->vring); i++) {
> > + struct rproc_vring *vring = &rvdev->vring[i];
> > + unsigned long pa = vring->dma;
> > + int c;
> > +
> > + if (plat_device_is_coherent(&mips_rproc_dev)) {
> > + /*
> > + * The DMA API will allocate cacheable buffers
> > + * for shared resources, so the firmware should
> > + * also access those buffers cached
> > + */
> > + c = (_page_cachable_default >> _CACHE_SHIFT);
> > + } else {
> > + /*
> > + * Otherwise, shared buffers should be accessed
> > + * uncached
> > + */
> > + c = CONF_CM_UNCACHED;
> > + }
> > +
> > + /* actual size of vring (in bytes) */
> > + size = PAGE_ALIGN(vring_size(vring->len, vring->align));
> > +
> > + dev_dbg(&rproc->dev,
> > + "vring mapping da %pad -> %pad length 0x%x, CCA %d",
> > + &vring->dma, &vring->dma, size, c);
> > +
> > + mips_rproc_map(pa, pa, c, size, max_pagemask);
> > + }
> > + }
> > + return 0;
> > +}
> > +
> > +static void mips_rproc_cpu_entry(void)
> > +{
> > + struct mips_rproc *mproc = mips_rprocs[smp_processor_id()];
> > + struct rproc *rproc = mproc->rproc;
> > + int ipi_to_remote = ipi_get_hwirq(mproc->ipi_remote, mproc->cpu);
> > + int ipi_from_remote = ipi_get_hwirq(mproc->ipi_linux, 0);
> > + unsigned long old_pagemask, max_pagemask;
> > + void (*fw_entry)(int, int ipi_to_remote, int ipi_from_remote, int);
> > +
> > + dev_info(&rproc->dev, "%s booting firmware %s\n",
> > + rproc->name, rproc->firmware);
> > +
> > + /* Get the maximum pagemask supported on this CPU */
> > + old_pagemask = read_c0_pagemask();
> > + write_c0_pagemask(~0);
> > + back_to_back_c0_hazard();
> > + max_pagemask = read_c0_pagemask();
> > + write_c0_pagemask(old_pagemask);
> > + back_to_back_c0_hazard();
> > +
> > + /* Start with no wired entries */
> > + write_c0_wired(0);
> > +
> > + /* Flush all previous TLB entries */
> > + local_flush_tlb_all();
> > +
> > + /* Set ASID 0 */
> > + write_c0_entryhi(0);
> > +
> > + /* Map firmware resources into virtual memory */
> > + mips_rproc_carveouts(rproc, max_pagemask);
> > + mips_rproc_vdevs(rproc, max_pagemask);
> > +
> > + dev_dbg(&rproc->dev, "IPI to remote: %d\n", ipi_to_remote);
> > + dev_dbg(&rproc->dev, "IPI from remote: %d\n", ipi_from_remote);
> > +
> > + /* Hand off the CPU to the firmware */
> > + dev_dbg(&rproc->dev, "Jumping to firmware at 0x%x\n", rproc->bootaddr);
> > +
> > + /* We're done with the task struct that provided the stack we've used */
> > + set_current_state(TASK_DEAD);
> > +
> > + /* Jump into the firmware, obeying the firmware protocol. */
> > + fw_entry = (void *)rproc->bootaddr;
> > + fw_entry(-3, ipi_to_remote, ipi_from_remote, 0);
> > +}
> > +
> > +static irqreturn_t mips_rproc_ipi_handler(int irq, void *dev_id)
> > +{
> > + /* Synthetic interrupts shouldn't need acking */
> > + return IRQ_WAKE_THREAD;
> > +}
> > +
> > +static irqreturn_t mips_rproc_vq_int(int irq, void *p)
> > +{
> > + struct rproc *rproc = (struct rproc *)p;
> > + void *entry;
> > + int id;
> > +
> > + /* We don't have a mailbox, so iterate over all vqs and kick them. */
> > + idr_for_each_entry(&rproc->notifyids, entry, id)
> > + rproc_vq_interrupt(rproc, id);
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
> > +/* Helper function to find the IPI domain */
> > +static struct irq_domain *ipi_domain(void)
> > +{
> > + struct device_node *node = of_irq_find_parent(of_root);
> > + struct irq_domain *ipidomain;
> > +
> > + ipidomain = irq_find_matching_host(node, DOMAIN_BUS_IPI);
> > + /*
> > + * Some platforms have half DT setup. So if we found irq node but
> > + * didn't find an ipidomain, try to search for one that is not in the
> > + * DT.
> > + */
> > + if (node && !ipidomain)
> > + ipidomain = irq_find_matching_host(NULL, DOMAIN_BUS_IPI);
> > +
> > + return ipidomain;
> > +}
> > +
> > +int mips_rproc_op_start(struct rproc *rproc)
> > +{
> > + struct mips_rproc *mproc = rproc->priv;
> > + int err;
> > + int cpu = mproc->cpu;
> > +
> > + /* Create task for the CPU to use before handing off to firmware */
> > + mproc->tsk = fork_idle(cpu);
> > + if (IS_ERR(mproc->tsk)) {
> > + dev_err(&rproc->dev, "fork_idle() failed for CPU%d\n", cpu);
> > + return -ENOMEM;
> > + }
> > +
> > + /* We won't be needing the Linux IPIs anymore */
> > + if (mips_smp_ipi_free(get_cpu_mask(cpu))) {
> > + dev_err(&rproc->dev, "Failed to reserve incoming kick\n");
> > + goto exit_free_tsk;
> > + }
> > +
> > + /*
> > + * Direct IPIs from the remote processor to CPU0 since that can't be
> > + * offlined while the remote CPU is running.
> > + */
> > + mproc->ipi_linux = irq_reserve_ipi(ipi_domain(), get_cpu_mask(0));
> > + if (!mproc->ipi_linux) {
> > + dev_err(&rproc->dev, "Failed to reserve incoming kick\n");
> > + goto exit_restore_ipi;
> > + }
> > +
> > + mproc->ipi_remote = irq_reserve_ipi(ipi_domain(), get_cpu_mask(cpu));
> > + if (!mproc->ipi_remote) {
> > + dev_err(&rproc->dev, "Failed to reserve outgoing kick\n");
> > + goto exit_destroy_ipi_linux;
> > + }
> > +
> > + /* register incoming ipi */
> > + err = request_threaded_irq(mproc->ipi_linux, mips_rproc_ipi_handler,
> > + mips_rproc_vq_int, 0,
> > + "mips-rproc IPI in", rproc);
> > + if (err) {
> > + dev_err(&rproc->dev, "Failed to register incoming kick: %d\n",
> > + err);
> > + goto exit_destroy_ipi_remote;
> > + }
> > +
> > + if (mips_cps_steal_cpu_and_execute(cpu, &mips_rproc_cpu_entry,
> > + mproc->tsk)) {
> > + dev_err(&rproc->dev, "Failed to steal CPU%d for remote\n", cpu);
> > + goto exit_free_irq;
> > + }
> > +
> > + return 0;
> > +
> > +exit_free_irq:
> > + free_irq(mproc->ipi_linux, rproc);
> > +exit_destroy_ipi_remote:
> > + irq_destroy_ipi(mproc->ipi_remote, get_cpu_mask(cpu));
> > +exit_destroy_ipi_linux:
> > + irq_destroy_ipi(mproc->ipi_linux, get_cpu_mask(0));
> > +exit_restore_ipi:
> > + /* Set up the Linux IPIs again */
> > + mips_smp_ipi_allocate(get_cpu_mask(cpu));
> > +exit_free_tsk:
> > + free_task(mproc->tsk);
> > +
> > + return -EINVAL;
> > +}
> > +
> > +int mips_rproc_op_stop(struct rproc *rproc)
> > +{
> > + struct mips_rproc *mproc = rproc->priv;
> > +
> > + free_irq(mproc->ipi_linux, rproc);
> > +
> > + irq_destroy_ipi(mproc->ipi_linux, get_cpu_mask(0));
> > + irq_destroy_ipi(mproc->ipi_remote, get_cpu_mask(mproc->cpu));
> > +
> > + /* Set up the Linux IPIs again */
> > + mips_smp_ipi_allocate(get_cpu_mask(mproc->cpu));
> > +
> > + free_task(mproc->tsk);
> > +
> > + return mips_cps_halt_and_return_cpu(mproc->cpu);
> > +}
> > +
> > +void mips_rproc_op_kick(struct rproc *rproc, int vqid)
> > +{
> > + struct mips_rproc *mproc = rproc->priv;
> > +
> > + if (rproc->state == RPROC_RUNNING)
> > + ipi_send_single(mproc->ipi_remote, mproc->cpu);
> > +}
> > +
> > +static const struct rproc_ops mips_rproc_proc_ops = {
> > + .start = mips_rproc_op_start,
> > + .stop = mips_rproc_op_stop,
> > + .kick = mips_rproc_op_kick,
> > +};
> > +
> > +/* Create an rproc instance in response to CPU down */
> > +static int mips_rproc_device_register(unsigned int cpu)
> > +{
> > + char *template = "mips-cpu%u";
> > + struct rproc *rproc;
> > + struct mips_rproc *mproc;
> > + int err;
> > +
> > + if (!cpumask_test_cpu(cpu, mips_rproc_cpumask))
> > + /* The CPU is not in the mask, so don't register rproc on it */
> > + return 0;
> > +
> > + pr_debug("Allocating MIPS rproc for cpu%d\n", cpu);
> > +
> > + if (mips_rprocs[cpu]) {
> > + dev_err(&mips_rproc_dev, "CPU%d in use\n", cpu);
> > + return 0;
> > + }
> > +
> > + mproc = kzalloc(sizeof(struct mips_rproc), GFP_KERNEL);
> > + if (!mproc) {
> > + err = -ENOMEM;
> > + goto exit;
> > + }
> > +
> > + snprintf(mproc->name, sizeof(mproc->name), template, cpu);
> > + mproc->cpu = cpu;
> > +
> > + rproc = rproc_alloc(&mips_rproc_dev, mproc->name,
> > + &mips_rproc_proc_ops, NULL,
> > + sizeof(struct mips_rproc *));
> > + if (!rproc) {
> > + dev_err(&mips_rproc_dev, "Error allocating rproc\n");
> > + err = -ENOMEM;
> > + goto exit_free_mproc;
> > + }
> > +
> > + mproc->rproc = rproc;
> > + rproc->priv = (void *)mproc;
> > +
> > + err = rproc_add(rproc);
> > + if (err) {
> > + dev_err(&mips_rproc_dev, "Failed to add rproc: %d\n", err);
> > + goto exit_free_rproc;
> > + }
> > +
> > + mips_rprocs[cpu] = mproc;
> > + return 0;
> > +
> > +exit_free_rproc:
> > + rproc_free(rproc);
> > +exit_free_mproc:
> > + kfree(mproc);
> > +exit:
> > + return err;
> > +}
> > +
> > +/* Destroy rproc instance in response to CPU up */
> > +static int mips_rproc_device_unregister(unsigned int cpu)
> > +{
> > + struct mips_rproc *mproc = mips_rprocs[cpu];
> > +
> > + if (!mproc)
> > + /* No rproc instance has been created for this CPU */
> > + return 0;
> > +
> > + pr_debug("Deallocating MIPS rproc for cpu%d\n", cpu);
> > +
> > + rproc_del(mproc->rproc);
> > + rproc_put(mproc->rproc);
> > + kfree(mproc);
> > +
> > + mips_rprocs[cpu] = NULL;
> > + return 0;
> > +}
> > +
> > +/* Show MIPS CPUs available to rproc */
> > +static ssize_t cpus_show(struct device *dev, struct device_attribute *attr,
> > + char *buf)
> > +{
> > + return cpumap_print_to_pagebuf(true, buf, mips_rproc_cpumask);
> > +}
> > +
> > +/* Allow MIPS CPUs to be made available to rproc */
> > +static ssize_t cpus_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + static cpumask_var_t new_mask;
> > + int err, cpu;
> > +
> > + err = cpulist_parse(buf, new_mask);
> > + if (err)
> > + return err;
> > +
> > + /* Prevent CPU hotplug on/offlining CPUs while we do this */
> > + get_online_cpus();
> > +
> > + for_each_possible_cpu(cpu) {
> > + if (cpumask_test_cpu(cpu, mips_rproc_cpumask) &&
> > + !cpumask_test_cpu(cpu, new_mask)) {
> > + /* CPU no longer allowed. Release any instance on it */
> > + cpumask_clear_cpu(cpu, mips_rproc_cpumask);
> > + mips_rproc_device_unregister(cpu);
> > +
> > + } else if (!cpumask_test_cpu(cpu, mips_rproc_cpumask) &&
> > + cpumask_test_cpu(cpu, new_mask)) {
> > + /* If the CPU isn't online, start an instance */
> > + cpumask_set_cpu(cpu, mips_rproc_cpumask);
> > + if (!cpu_online(cpu))
> > + mips_rproc_device_register(cpu);
> > + }
> > + }
> > + put_online_cpus();
> > + return count;
> > +}
> > +static DEVICE_ATTR_RW(cpus);
> > +
> > +static struct attribute *mips_rproc_attrs[] = {
> > + &dev_attr_cpus.attr,
> > + NULL
> > +};
> > +
> > +static const struct attribute_group mips_rproc_devgroup = {
> > + .attrs = mips_rproc_attrs
> > +};
> > +
> > +static const struct attribute_group *mips_rproc_devgroups[] = {
> > + &mips_rproc_devgroup,
> > + NULL
> > +};
> > +static struct device_type mips_rproc_type = {
> > + .groups = mips_rproc_devgroups,
> > +};
> > +
> > +static struct platform_driver mips_rproc_driver = {
> > + .driver = {
> > + .name = "mips-rproc",
> > + },
> > +};
> > +
> > +static int __init mips_rproc_init(void)
> > +{
> > + int err;
> > +
> > + if ((!cpu_has_mipsmt) && (!cpu_has_vp)) {
> > + pr_debug("MIPS rproc not supported on this cpu\n");
> > + return -EIO;
> > + }
> > +
> > + mips_rproc_dev.driver = &mips_rproc_driver.driver;
> > + mips_rproc_dev.type = &mips_rproc_type;
> > + dev_set_name(&mips_rproc_dev, "mips-rproc");
> > +
> > + /* Set device to have coherent DMA ops */
> > + arch_setup_dma_ops(&mips_rproc_dev, 0, 0, NULL, 1);
> > +
> > + err = device_register(&mips_rproc_dev);
> > + if (err) {
> > + dev_err(&mips_rproc_dev, "Error adding MIPS rproc: %d\n", err);
> > + return err;
> > + }
> > +
> > + /*
> > + * Register with the cpu hotplug state machine.
> > + * This driver requires opposite sense to "normal" drivers, since the
> > + * driver is activated for offline CPUs via the teardown callback and
> > + * deactivated via the online callback.
> > + */
> > + err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "MIPS:REMOTEPROC",
> > + mips_rproc_device_unregister,
> > + mips_rproc_device_register);
> > + if (err < 0) {
> > + device_unregister(&mips_rproc_dev);
> > + return err;
> > + }
> > +
> > + cpuhp_state = err;
> > +
> > + return 0;
> > +}
> > +
> > +static void __exit mips_rproc_exit(void)
> > +{
> > + int cpu;
> > +
> > + if (cpuhp_state) {
> > + /*
> > + * Unregister with the cpu hotplug state machine, but don't call
> > + * the teardown callback, since that would try to start the
> > + * remote processor device.
> > + */
> > + __cpuhp_remove_state(cpuhp_state, false);
> > + cpuhp_state = 0;
> > + }
> > +
> > + get_online_cpus();
> > + /* Unregister devices created for any offline CPUs */
> > + for_each_possible_cpu(cpu)
> > + mips_rproc_device_unregister(cpu);
> > + put_online_cpus();
> > +}
> > +
> > +late_initcall(mips_rproc_init);
> > +module_exit(mips_rproc_exit);
> > +
> > +module_platform_driver(mips_rproc_driver);
> > +
> > +MODULE_LICENSE("GPL v2");
> > +MODULE_DESCRIPTION("MIPS Remote Processor control driver");