Configure the i.MX8MP to boot the Cortex-M7 core alongside
the Cortex-A53 cores in an Asymmetric Multiprocessing (AMP)
configuration. The M7 firmware can be loaded and started from Linux
running on the A53 cores via the remoteproc framework.

CM7 boot is made optional. A GPR IRQ is connected to a cpuwait handler.
The handler translates the CPUWAIT into STOP and RUN.It follows the
classic Cortex-M boot sequence: initial SP and reset vector taken from
the vector table.

Signed-off-by: Gaurav Sharma <[email protected]>
---
 docs/system/arm/imx8m.rst   |  86 +++++++++++++++++++++-
 hw/arm/fsl-imx8mp.c         | 143 ++++++++++++++++++++++++++++++++++++
 include/hw/arm/fsl-imx8mp.h |  13 +++-
 3 files changed, 240 insertions(+), 2 deletions(-)

diff --git a/docs/system/arm/imx8m.rst b/docs/system/arm/imx8m.rst
index c482bf180f..d6729cbcc6 100644
--- a/docs/system/arm/imx8m.rst
+++ b/docs/system/arm/imx8m.rst
@@ -12,6 +12,7 @@ The ``imx8mp-evk`` and ``imx8mm-evk`` machines implement the
 following devices:
 
  * Up to 4 Cortex-A53 cores
+ * 1 Cortex-M7 core (``imx8mp-evk`` only)
  * Generic Interrupt Controller (GICv3)
  * 4 UARTs
  * 3 USDHC Storage Controllers
@@ -36,6 +37,88 @@ Boot options
 The ``imx8mp-evk`` and ``imx8mm-evk`` machines can start a Linux
 kernel directly using the standard ``-kernel`` functionality.
 
+Asymmetric Multiprocessing (AMP) Boot (``imx8mp-evk`` only)
+'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+The ``imx8mp-evk`` machine includes a Cortex-M7 core alongside the
+Cortex-A53 cores, enabling Asymmetric Multiprocessing (AMP). The M7
+firmware can be loaded from Linux using the remoteproc framework.
+
+There are 2 control paths for Cortex-M7 on iMX8MP:-
+1. Firmware-mediated (via SMC/ATF)
+2. MMIO driven path (via SRC and GPR access)
+
+``fsl,imx8mp-cm7-mmio`` exists specifically to select the MMIO path and avoid 
dependence on firmware interfaces that aren’t guaranteed in qemu.
+This mode uses the SRC syscon block and the IOMUXC GPR for start/stop control.
+
+Memory carveouts for resource table, vrings need to be specified in the 
``imx8mp-evk-rpmsg.dts``.
+Follow this application note to make the necessary changes - 
https://www.nxp.com/docs/en/application-note/AN5317.pdf
+
+When Linux boots CM7 via remoteproc, the typical flow is:
+
+1. Linux booted with imx8mp-evk-rpmsg.dtb
+2. Linux loads the CM7 ELF into a reserved DDR region
+3. Linux toggles the CM7 start/stop control (SRC/GPR CPUWAIT, etc.)
+4. CM7 starts executing from that DDR entry
+
+
+If you build a Cortex-M7 bare-metal firmware elf that is linked for a vector
+table base address other than the default 0x80000000, configure the CM7 vector 
base via the SoC property
+``cm7-vector-base``.
+
+Note:-Currently only DDR-linked bare-metal binaries are supported in qemu 
emulation.
+
+This can be set using a global property:
+
+.. code-block:: bash
+
+  -global fsl-imx8mp.cm7-vector-base=0x80000000
+
+In the absence of the above global property in qemu invocation, by default 
0x80000000 will be used.
+
+
+To run the i.MX 8M Plus model with the Cortex-M7 core enabled(4x A53 + 1x M7), 
start QEMU with
+
+.. code-block:: bash
+
+  -smp 4,maxcpus=5 -global fsl-imx8mp.enable-cm7=on
+
+
+Serial ports (UARTs)
+''''''''''''''''''''
+
+The i.MX 8M Plus EVK model provides four UARTs. QEMU connects each UART to a
+host character backend using the ``-serial`` option. This option can be used
+multiple times to create and wire multiple serial ports.
+
+The ``-serial`` options are positional:
+
+* the 1st ``-serial ...`` maps to ``serial0`` (UART1)
+* the 2nd ``-serial ...`` maps to ``serial1`` (UART2)
+* the 3rd ``-serial ...`` maps to ``serial2`` (UART3)
+* the 4th ``-serial ...`` maps to ``serial3`` (UART4)
+
+Example usage:- To enable serial console for the official M7 mcuxpresso sdk 
driver example - driver_examples/uart/polling which uses UART4, use:-
+
+.. code-block:: bash
+
+  -serial null -serial stdio -serial null -serial pty:/tmp/imx8mp-uart4
+
+This will create a symlink /tmp/imx8mp-uart4 pointed to the allocated PTY. On 
a different tab the console for UART4 can be opened using the following:-
+
+.. code-block:: bash
+
+  $ screen /tmp/imx8mp-uart4 115200
+
+
+Once Linux is running, the M7 firmware can be loaded and started via the 
remoteproc interface:
+
+.. code-block:: bash
+
+  # echo <firmware_name>.elf > /sys/class/remoteproc/remoteproc0/firmware
+  # echo start > /sys/class/remoteproc/remoteproc0/state
+
+
 Direct Linux Kernel Boot
 ''''''''''''''''''''''''
 
@@ -72,7 +155,8 @@ For i.MX 8M Plus EVK:
 .. code-block:: bash
 
   $ qemu-system-aarch64 -M imx8mp-evk \
-      -display none -serial null -serial stdio \
+      -display none -serial null -serial stdio -serial null -serial 
/tmp/imx8mp-uart4 \
+      -smp 4,maxcpus=5 -global fsl-imx8mp.enable-cm7=on \
       -kernel Image \
       -dtb imx8mp-evk.dtb \
       -append "root=/dev/mmcblk2p2" \
diff --git a/hw/arm/fsl-imx8mp.c b/hw/arm/fsl-imx8mp.c
index eec83deced..a5066c3ae2 100644
--- a/hw/arm/fsl-imx8mp.c
+++ b/hw/arm/fsl-imx8mp.c
@@ -196,6 +196,10 @@ static void fsl_imx8mp_init(Object *obj)
     FslImx8mpState *s = FSL_IMX8MP(obj);
     int i;
 
+    s->cm7_booted = false;
+
+    object_initialize_child(obj, "cm7", &s->cm7, TYPE_ARMV7M);
+
     object_initialize_child(obj, "gic", &s->gic, gicv3_class_name());
 
     object_initialize_child(obj, "ccm", &s->ccm, TYPE_IMX8MP_CCM);
@@ -269,6 +273,93 @@ static void fsl_imx8mp_init(Object *obj)
                             TYPE_FSL_IMX8M_PCIE_PHY);
 }
 
+static inline void imx8mp_cm7_halt(CPUState *m7cs)
+{
+    cpu_interrupt(m7cs, CPU_INTERRUPT_HALT);
+    m7cs->halted = 1;
+    qemu_cpu_kick(m7cs);
+}
+
+static inline void imx8mp_cm7_resume(CPUState *m7cs)
+{
+    /* Clear HALT interrupt (from STOP) and resume */
+    cpu_reset_interrupt(m7cs, CPU_INTERRUPT_HALT);
+    m7cs->halted = 0;
+    m7cs->stopped = 0;
+    cpu_resume(m7cs);
+    cpu_interrupt(m7cs, CPU_INTERRUPT_EXITTB);
+    qemu_cpu_kick(m7cs);
+}
+
+static void imx8mp_cm7_ctrl_apply(CPUState *cpu, run_on_cpu_data data)
+{
+    struct CM7CtlReq *r = data.host_ptr;
+    FslImx8mpState *s = r->s;
+    ARMCPU *m7 = s->cm7.cpu;
+    CPUState *m7cs = CPU(m7);
+
+    if (!r->run) {
+        /* STOP: halt the M7 */
+    imx8mp_cm7_halt(m7cs);
+        goto out;
+    }
+
+    /*
+     * RUN:
+     * CPUWAIT is modeled as a run/stop gate. On first RUN, boot from vector
+     * table. Subsequent RUN resumes execution without resetting CM7 state.
+     */
+    if (s->cm7_booted) {
+        imx8mp_cm7_resume(m7cs);
+        goto out;
+    }
+
+    uint32_t msp_le = 0, pc_le = 0;
+    uint32_t msp, pc;
+    hwaddr vbase = s->cm7_vector_base;
+
+    address_space_read(&address_space_memory, vbase,
+                       MEMTXATTRS_UNSPECIFIED, &msp_le, sizeof(msp_le));
+    address_space_read(&address_space_memory, vbase + 4,
+                       MEMTXATTRS_UNSPECIFIED, &pc_le, sizeof(pc_le));
+    msp = le32_to_cpu(msp_le);
+    pc  = le32_to_cpu(pc_le);
+
+
+    /* Clear Thumb indicator bit (bit0) */
+    pc &= ~1u;
+
+    cpu_reset(m7cs);
+
+    /* Set SP (R13) and PC (R15). Cortex-M uses Thumb */
+    m7->env.regs[13] = msp;
+    m7->env.regs[15] = pc;
+    m7->env.thumb = 1;
+
+    imx8mp_cm7_resume(m7cs);
+
+    s->cm7_booted = true;
+out:
+    g_free(r);
+};
+
+static void imx8mp_cm7_cpuwait_handler(void *opaque, int n, int level)
+{
+    FslImx8mpState *s = opaque;
+    (void)n;
+
+    if (!s->cm7.cpu) {
+        return;
+    }
+
+    struct CM7CtlReq *r = g_new0(struct CM7CtlReq, 1);
+    r->s = s;
+    r->run = !!level;
+
+    async_run_on_cpu(CPU(s->cm7.cpu), imx8mp_cm7_ctrl_apply,
+                     RUN_ON_CPU_HOST_PTR(r));
+}
+
 static void fsl_imx8mp_realize(DeviceState *dev, Error **errp)
 {
     MachineState *ms = MACHINE(qdev_get_machine());
@@ -277,6 +368,23 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error 
**errp)
     const char *cpu_type = ms->cpu_type ?: ARM_CPU_TYPE_NAME("cortex-a53");
     int i;
 
+    if (!s->enable_cm7) {
+        object_unparent(OBJECT(&s->cm7));
+    } else {
+        /*
+         * When CM7 is enabled we need one additional vCPU
+         * slot. If the user does not specify maxcpus=,
+         * QEMU defaults maxcpus to the current -smp count.
+         */
+        unsigned int need = ms->smp.cpus + 1;
+        if (ms->smp.max_cpus < need) {
+            error_setg(errp,
+                       "CM7 enabled requires -smp %u,maxcpus=%u (one extra 
vCPU slot for CM7)",
+                       ms->smp.cpus, need);
+            return;
+        }
+    }
+
     if (ms->smp.cpus > FSL_IMX8MP_NUM_CPUS) {
         error_setg(errp, "%s: Only %d CPUs are supported (%d requested)",
                    TYPE_FSL_IMX8MP, FSL_IMX8MP_NUM_CPUS, ms->smp.cpus);
@@ -459,6 +567,38 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error 
**errp)
     sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpr), 0,
                     fsl_imx8mp_memmap[FSL_IMX8MP_IOMUXC_GPR].addr);
 
+    if (s->enable_cm7) {
+        sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpr), 0,
+                   qemu_allocate_irq(imx8mp_cm7_cpuwait_handler, s, 0));
+
+    /* Realize Cortex-M7 subsystem */
+        DeviceState *cm7dev = DEVICE(&s->cm7);
+        DeviceState *ccmdev = DEVICE(&s->ccm);
+        qdev_prop_set_string(cm7dev, "cpu-type",
+                             ARM_CPU_TYPE_NAME("cortex-m7"));
+        qdev_prop_set_uint32(cm7dev, "num-irq", 64);
+        qdev_prop_set_bit(cm7dev, "enable-bitband", true);
+
+        /* CM7 vector table base (configurable) */
+        qdev_prop_set_uint32(cm7dev, "init-nsvtor", s->cm7_vector_base);
+
+        /* Connect CM7 clocks from CCM exported outputs */
+        qdev_connect_clock_in(cm7dev, "cpuclk",
+                              qdev_get_clock_out(ccmdev, "cm7_cpuclk"));
+        qdev_connect_clock_in(cm7dev, "refclk",
+                              qdev_get_clock_out(ccmdev, "cm7_refclk"));
+        object_property_set_link(OBJECT(&s->cm7), "memory",
+                                 OBJECT(get_system_memory()), &error_abort);
+
+        if (!sysbus_realize(SYS_BUS_DEVICE(&s->cm7), errp)) {
+            return;
+    }
+
+        CPUState *m7cs = CPU(s->cm7.cpu);
+        cpu_interrupt(m7cs, CPU_INTERRUPT_HALT);
+        m7cs->halted = 1;
+    }
+
     /* GPTs */
     object_property_set_int(OBJECT(&s->gpt5_gpt6_irq), "num-lines", 2,
                             &error_abort);
@@ -784,6 +924,9 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error 
**errp)
 static const Property fsl_imx8mp_properties[] = {
     DEFINE_PROP_UINT32("fec1-phy-num", FslImx8mpState, phy_num, 0),
     DEFINE_PROP_BOOL("fec1-phy-connected", FslImx8mpState, phy_connected, 
true),
+    DEFINE_PROP_BOOL("enable-cm7", FslImx8mpState, enable_cm7, false),
+    DEFINE_PROP_UINT32("cm7-vector-base", FslImx8mpState,
+                       cm7_vector_base, 0x80000000),
 };
 
 static void fsl_imx8mp_class_init(ObjectClass *oc, const void *data)
diff --git a/include/hw/arm/fsl-imx8mp.h b/include/hw/arm/fsl-imx8mp.h
index 94d198b932..335e4beb6c 100644
--- a/include/hw/arm/fsl-imx8mp.h
+++ b/include/hw/arm/fsl-imx8mp.h
@@ -10,6 +10,7 @@
 #define FSL_IMX8MP_H
 
 #include "target/arm/cpu.h"
+#include "hw/arm/armv7m.h"
 #include "hw/char/imx_serial.h"
 #include "hw/gpio/imx_gpio.h"
 #include "hw/i2c/imx_i2c.h"
@@ -30,6 +31,7 @@
 #include "hw/timer/imx_gpt.h"
 #include "hw/usb/hcd-dwc3.h"
 #include "hw/watchdog/wdt_imx2.h"
+#include "hw/core/qdev-clock.h"
 #include "hw/core/sysbus.h"
 #include "qom/object.h"
 #include "qemu/units.h"
@@ -43,7 +45,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(FslImx8mpState, FSL_IMX8MP)
 #define FSL_IMX8MP_MU1_A_IRQ  88
 
 enum FslImx8mpConfiguration {
-    FSL_IMX8MP_NUM_CPUS         = 4,
+    FSL_IMX8MP_NUM_CPUS         = 5,
     FSL_IMX8MP_NUM_ECSPIS       = 3,
     FSL_IMX8MP_NUM_GPIOS        = 5,
     FSL_IMX8MP_NUM_GPTS         = 6,
@@ -65,6 +67,10 @@ struct FslImx8mpState {
     SysBusDevice   parent_obj;
 
     ARMCPU             cpu[FSL_IMX8MP_NUM_CPUS];
+    ARMv7MState        cm7;
+    bool               enable_cm7;
+    bool               cm7_booted;
+    uint32_t           cm7_vector_base;
     GICv3State         gic;
     IMX8MPGPCState     gpc;
     IMX8MPGPRState     gpr;
@@ -91,6 +97,11 @@ struct FslImx8mpState {
     bool               phy_connected;
 };
 
+struct CM7CtlReq {
+    FslImx8mpState *s;
+    bool run;
+};
+
 enum FslImx8mpMemoryRegions {
     FSL_IMX8MP_A53_DAP,
     FSL_IMX8MP_AIPS1_CONFIGURATION,
-- 
2.34.1


Reply via email to