Skip to contentSkip to conversation

ARM: Machine specific boot blobs

RFC In review Peter Crosthwaite 2 participants 7 messages Oct 10, 2015 – Oct 19, 2015 Arm/Zynq architecture

lore
Share & tools
lore
#0 lore raw

cover ARM: Machine specific boot blobs

Hi,

This adds support for machine specific primary boot blobs. This can be used to install little bits of firmware or boot code without having to throw the whole QEMU bootloader out and BYO (with device drivers and all).

It is then used to fix two boards, Zynq and Highbank, both which have small but critical expectations of pre-boot software setup.

Regards,
Peter

Peter Crosthwaite (3):
arm: boot: Add board specific setup code API
arm: highbank: Implement dummy monitor
arm: xilinx_zynq: Add linux pre-boot

 hw/arm/boot.c        | 11 +++++++++++
 hw/arm/highbank.c    | 56 +++++++++++++++++++++++++++++++++++++++++-----------
 hw/arm/xilinx_zynq.c | 41 +++++++++++++++++++++++++++++++++++++-
 include/hw/arm/arm.h |  7 +++++++
 4 files changed, 103 insertions(+), 12 deletions(-)
Signature · 1 line
1.9.1
#1 lore raw

RFC 1/3 arm: boot: Add board specific setup code API

Add an API for boards to inject their own preboot software (or firmware) seqeuence.

The software then returns to bootloader via the link register. This allows boards to do their own little bits of firmware setup without needed to replace the bootloader completely (which is the requirement for existing firmware support).

---
 hw/arm/boot.c        | 11 +++++++++++
 include/hw/arm/arm.h |  7 +++++++
 2 files changed, 18 insertions(+)
hw/arm/boot.c +11 -0
index bef451b..a0dd1df 100644
@@ -31,6 +31,7 @@ typedef enum {
     FIXUP_NONE = 0,   /* do nothing */
     FIXUP_TERMINATOR, /* end of insns */
     FIXUP_BOARDID,    /* overwrite with board ID number */
+    FIXUP_BOARDSETUP, /* overwrite with board specific setup code address */
     FIXUP_ARGPTR,     /* overwrite with pointer to kernel args */
     FIXUP_ENTRYPOINT, /* overwrite with kernel entry point */
     FIXUP_GIC_CPU_IF, /* overwrite with GIC CPU interface address */
@@ -60,6 +61,11 @@ static const ARMInsnFixup bootloader_aarch64[] = {
 
 /* The worlds second smallest bootloader.  Set r0-r2, then jump to kernel.  */
 static const ARMInsnFixup bootloader[] = {
+    { 0xe28fe008 }, /* add     lr, pc, #8 */
+    { 0xe59ff000 }, /* ldr     pc, [pc, #0] */
+    { 0 },
+    { 0, FIXUP_BOARDSETUP },
+#define BOOTLOADER_NO_BOARDSETUP_OFFSET 4
     { 0xe3a00000 }, /* mov     r0, #0 */
     { 0xe59f1004 }, /* ldr     r1, [pc, #4] */
     { 0xe59f2004 }, /* ldr     r2, [pc, #4] */
@@ -131,6 +137,7 @@ static void write_bootloader(const char *name, hwaddr addr,
         case FIXUP_NONE:
             break;
         case FIXUP_BOARDID:
+        case FIXUP_BOARDSETUP:
         case FIXUP_ARGPTR:
         case FIXUP_ENTRYPOINT:
         case FIXUP_GIC_CPU_IF:
@@ -640,6 +647,9 @@ static void arm_load_kernel_notify(Notifier *notifier, void *data)
         elf_machine = EM_AARCH64;
     } else {
         primary_loader = bootloader;
+        if (!info->board_setup_blob) {
+            primary_loader += BOOTLOADER_NO_BOARDSETUP_OFFSET;
+        }
         kernel_load_offset = KERNEL_LOAD_ADDR;
         elf_machine = EM_ARM;
     }
@@ -745,6 +755,7 @@ static void arm_load_kernel_notify(Notifier *notifier, void *data)
         info->initrd_size = initrd_size;
 
         fixupcontext[FIXUP_BOARDID] = info->board_id;
+        fixupcontext[FIXUP_BOARDSETUP] = info->board_setup_blob_addr;
 
         /* for device tree boot, we pass the DTB directly in r2. Otherwise
          * we point to the kernel args.
include/hw/arm/arm.h +7 -0
index 4dcd4f9..0e3a652 100644
@@ -87,6 +87,13 @@ struct arm_boot_info {
      * -pflash. It also implies that fw_cfg_find() will succeed.
      */
     bool firmware_loaded;
+
+    /* Address as which board specific loader/setup code exists. If enabled,
+     * this code-blob will run before anything else. It must return to the
+     * caller via the link register. There is no stack setup.
+     */
+    bool board_setup_blob;
+    hwaddr board_setup_blob_addr;
 };
 
 /**
Signature · 1 line
1.9.1
#2 lore raw

RFC 2/3 arm: highbank: Implement dummy monitor

In reply to Peter Crosthwaite

Firstly, enable monitor mode and PSCI, both are which are features of this board.

In addition to PSCI, this board also uses SMC for cache maintainence ops. This means we need a secure monitor to catch these and nop them. Use the ARM boot board setup blob to implement this.

FIXME: The blob population should be made Linux specific.

---
 hw/arm/highbank.c | 56 ++++++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 45 insertions(+), 11 deletions(-)
hw/arm/highbank.c +45 -11
index be04b27..1fca057 100644
@@ -28,6 +28,9 @@
 #include "exec/address-spaces.h"
 #include "qemu/error-report.h"
 
+#define FW_BLOB_ADDR            0x0
+#define LOAD_ADDR               0x1000
+
 #define SMP_BOOT_ADDR           0x100
 #define SMP_BOOT_REG            0x40
 #define MPCORE_PERIPHBASE       0xfff10000
@@ -36,6 +39,36 @@
 
 /* Board init.  */
 
+static void hb_write_fw_blob(void)
+{
+    int n;
+    uint32_t fw_blob[] = {
+        /* Reset */
+        0xe320f000, /* nop */
+        0xe320f000, /* nop */
+        /* smc */
+        0xe10f0000, /* mrs r0, CPSR */
+        0xe200001f, /* and r0, r0, #0x1f - mask off mode bits */
+        0xe3500016, /* cmp r0, #0x16 - are we in monitor mode? */
+        /* if (!monitor_mode) { */
+            0x11600070, /* smcne - go to monitor mode */
+            0x112fff1e, /* bxne lr - return to caller */
+        /* } */
+        /* do setup from monitor mode */
+        0xe3a00000 + FW_BLOB_ADDR, /* mov r0, #FW_BLOB_ADDR */
+        0xee0c0f30, /* mcr p15, 0, r0, c12, c0, 1 - set mvbar */
+        0xe58fe008, /* save lr */
+        0xe8dfc000, /* exception return */
+        0,
+        0,
+        0, /* exception return link will end up here */
+    };
+    for (n = 0; n < ARRAY_SIZE(fw_blob); n++) {
+        fw_blob[n] = tswap32(fw_blob[n]);
+    }
+    rom_add_blob_fixed("firmware", fw_blob, sizeof(fw_blob), FW_BLOB_ADDR);
+}
+
 static void hb_write_secondary(ARMCPU *cpu, const struct arm_boot_info *info)
 {
     int n;
@@ -248,16 +281,13 @@ static void calxeda_init(MachineState *machine, enum cxmachines machine_id)
         cpuobj = object_new(object_class_get_name(oc));
         cpu = ARM_CPU(cpuobj);
 
-        /* By default A9 and A15 CPUs have EL3 enabled.  This board does not
-         * currently support EL3 so the CPU EL3 property is disabled before
-         * realization.
-         */
-        if (object_property_find(cpuobj, "has_el3", NULL)) {
-            object_property_set_bool(cpuobj, false, "has_el3", &err);
-            if (err) {
-                error_report_err(err);
-                exit(1);
-            }
+        object_property_set_int(cpuobj, QEMU_PSCI_CONDUIT_SMC,
+                                "psci-conduit", &error_abort);
+
+        if (n) {
+            /* Secondary CPUs start in PSCI powered-down state */
+            object_property_set_bool(cpuobj, true,
+                                     "start-powered-off", &error_abort);
         }
 
         if (object_property_find(cpuobj, "reset-cbar", NULL)) {
@@ -375,9 +405,13 @@ static void calxeda_init(MachineState *machine, enum cxmachines machine_id)
      */
     highbank_binfo.board_id = -1;
     highbank_binfo.nb_cpus = smp_cpus;
-    highbank_binfo.loader_start = 0;
+    highbank_binfo.loader_start = LOAD_ADDR;
     highbank_binfo.write_secondary_boot = hb_write_secondary;
     highbank_binfo.secondary_cpu_reset_hook = hb_reset_secondary;
+    highbank_binfo.board_setup_blob = true;
+    highbank_binfo.board_setup_blob_addr = FW_BLOB_ADDR;
+
+    hb_write_fw_blob();
     arm_load_kernel(ARM_CPU(first_cpu), &highbank_binfo);
 }
 
Signature · 1 line
1.9.1
#3 lore raw

RFC 3/3 arm: xilinx_zynq: Add linux pre-boot

In reply to Peter Crosthwaite

Add a Linux-specific pre-boot routine that matches the device specific bootloaders behaviour. This is needed for modern Linux that expects the ARM PLL in SLCR to be a more even value (not 26).

FIXME: The blob population should only happen on Linux boots.

---
 hw/arm/xilinx_zynq.c | 41 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 40 insertions(+), 1 deletion(-)
hw/arm/xilinx_zynq.c +40 -1
index 9f89483..5bf03de 100644
@@ -43,6 +43,41 @@ static const int dma_irqs[8] = {
     46, 47, 48, 49, 72, 73, 74, 75
 };
 
+#define LOAD_ADDR       0x1000
+#define FW_BLOB_ADDR    0x0
+
+#define SLCR_ARM_PLL_OFFSET     0x100
+#define SLCR_LOCK_OFFSET        0x004
+#define SLCR_UNLOCK_OFFSET      0x008
+
+#define SLCR_XILINX_UNLOCK_KEY  0xdf0d
+#define SLCR_XILINX_LOCK_KEY    0x767b
+
+#define SLCR_WRITE(addr, val) \
+        0xe3a01000 + extract32((val), 0, 8), \
+        0xe3811c00 + extract32((val), 8, 8), \
+        0xe3811800 + extract32((val), 16, 8), \
+        0xe3811400 + extract32((val), 24, 8), \
+        0xe5801000 + (addr),
+
+static void zynq_write_fw_blob(void)
+{
+    int n;
+    uint32_t fw_blob[] = {
+        0xe3a004f8, /* mov r0, #0xf8000000 */
+        SLCR_WRITE(SLCR_UNLOCK_OFFSET, SLCR_XILINX_UNLOCK_KEY)
+        SLCR_WRITE(SLCR_ARM_PLL_OFFSET, 0x00014008)
+        //SLCR_WRITE(0x120, 0x1f000200)
+        SLCR_WRITE(SLCR_LOCK_OFFSET, SLCR_XILINX_LOCK_KEY)
+        0xe12fff1e, /* bx lr */
+    };
+    for (n = 0; n < ARRAY_SIZE(fw_blob); n++) {
+        fw_blob[n] = tswap32(fw_blob[n]);
+    }
+    rom_add_blob_fixed("firmware", fw_blob, sizeof(fw_blob), FW_BLOB_ADDR);
+}
+
+
 static struct arm_boot_info zynq_binfo = {};
 
 static void gem_init(NICInfo *nd, uint32_t base, qemu_irq irq)
@@ -251,7 +286,11 @@ static void zynq_init(MachineState *machine)
     zynq_binfo.initrd_filename = initrd_filename;
     zynq_binfo.nb_cpus = 1;
     zynq_binfo.board_id = 0xd32;
-    zynq_binfo.loader_start = 0;
+    zynq_binfo.loader_start = LOAD_ADDR;
+    zynq_binfo.board_setup_blob = true;
+    zynq_binfo.board_setup_blob_addr = FW_BLOB_ADDR;
+
+    zynq_write_fw_blob();
     arm_load_kernel(ARM_CPU(first_cpu), &zynq_binfo);
 }
 
Signature · 1 line
1.9.1
#4 lore raw
In reply to Peter Crosthwaite

Ping!

Although a little featurish and RFCish, this does repair the boot of two ARM machines so it is very much a bugfix and we should consider for 2.5. Otherwise we go to release (again) with two broken machines.

Regards,
Peter

Quoted 27 lines · Peter Crosthwaite

Hi,

This adds support for machine specific primary boot blobs. This can be used to install little bits of firmware or boot code without having to throw the whole QEMU bootloader out and BYO (with device drivers and all).

It is then used to fix two boards, Zynq and Highbank, both which have small but critical expectations of pre-boot software setup.

Regards,
Peter

Peter Crosthwaite (3):
arm: boot: Add board specific setup code API
arm: highbank: Implement dummy monitor
arm: xilinx_zynq: Add linux pre-boot

 hw/arm/boot.c        | 11 +++++++++++
 hw/arm/highbank.c    | 56 +++++++++++++++++++++++++++++++++++++++++-----------
 hw/arm/xilinx_zynq.c | 41 +++++++++++++++++++++++++++++++++++++-
 include/hw/arm/arm.h |  7 +++++++
 4 files changed, 103 insertions(+), 12 deletions(-)

--
1.9.1

#5 lore raw
Quoted 5 lines · Peter Crosthwaite

Ping!

Although a little featurish and RFCish, this does repair the boot of two ARM machines so it is very much a bugfix and we should consider for 2.5. Otherwise we go to release (again) with two broken machines.

The general idea looks ok to me, but sending a patchset out labelled RFC when we hit softfreeze is a pretty good way to cause people to ignore it :-)

Patch 1 looks OK I think; haven't looked too closely at the others.

How are you picking the firmware blob addresses ?

thanks
-- PMM

#6 lore raw
Quoted 8 lines · Peter Maydell
Quoted 5 lines · Peter Crosthwaite

Ping!

Although a little featurish and RFCish, this does repair the boot of two ARM machines so it is very much a bugfix and we should consider for 2.5. Otherwise we go to release (again) with two broken machines.

Quoted 8 lines

The general idea looks ok to me, but sending a patchset out labelled RFC when we hit softfreeze is a pretty good way to cause people to ignore it :-)

Patch 1 looks OK I think; haven't looked too closely at the others.

How are you picking the firmware blob addresses ?

It is flexible as far as arm/boot.c is concerned and set as board specific policy. A board could chose to place it at an arbitrary address to mimic a real bootrom which might be desirable. For both Highbank and Zynq I put FW @0 and relocated the QEMU bootloader to a small non-zero.

Regards,
Peter

Quoted 2 lines

thanks
-- PMM