diff --git a/hw/arm-misc.h b/hw/arm-misc.h
index 6e8ae6b..5e5204b 100644
--- a/hw/arm-misc.h
+++ b/hw/arm-misc.h
@@ -30,12 +30,29 @@ struct arm_boot_info {
     const char *kernel_cmdline;
     const char *initrd_filename;
     target_phys_addr_t loader_start;
+    /* multicore boards that use the default secondary core boot functions
+     * need to put the address of the secondary boot code, the boot reg,
+     * and the GIC address in the next 3 values, respectively. boards that
+     * have their own boot functions can use these values as they want.
+     */
     target_phys_addr_t smp_loader_start;
     target_phys_addr_t smp_bootreg_addr;
     target_phys_addr_t smp_priv_base;
     int nb_cpus;
     int board_id;
     int (*atag_board)(const struct arm_boot_info *info, void *p);
+    /* multicore boards that use the default secondary core boot functions
+     * can ignore these two function calls. If the default functions won't
+     * work, then write_secondary_boot() should write a suitable blob of
+     * code mimicing the secondary CPU startup process used by the board's
+     * boot loader/boot ROM code, and secondary_cpu_reset_hook() should
+     * perform any necessary CPU reset handling and set the PC for thei
+     * secondary CPUs to point at this boot blob.
+     */
+    void (*write_secondary_boot)(CPUState *env,
+                                 const struct arm_boot_info *info);
+    void (*secondary_cpu_reset_hook)(CPUState *env,
+                                     const struct arm_boot_info *info);
     /* Used internally by arm_boot.c */
     int is_linux;
     target_phys_addr_t initrd_size;
diff --git a/hw/arm_boot.c b/hw/arm_boot.c
index bf509a8..35ca22f 100644
--- a/hw/arm_boot.c
+++ b/hw/arm_boot.c
@@ -28,8 +28,20 @@ static uint32_t bootloader[] = {
   0  /* Kernel entry point.  Set by integratorcp_init.  */
 };
 
-/* Entry point for secondary CPUs.  Enable interrupt controller and
-   Issue WFI until start address is written to system controller.  */
+/* Handling for secondary CPU boot in a multicore system.
+ * Unlike the uniprocessor/primary CPU boot, this is platform
+ * dependent. The default code here is based on the secondary
+ * CPU boot protocol used on realview/vexpress boards, with
+ * some parameterisation to increase its flexibility.
+ * QEMU platform models for which this code is not appropriate
+ * should override write_secondary_boot and secondary_cpu_reset_hook
+ * instead.
+ *
+ * This code enables the interrupt controllers for the secondary
+ * CPUs and then puts all the secondary CPUs into a loop waiting
+ * for an interprocessor interrupt and polling a configurable
+ * location for the kernel secondary CPU entry point.
+ */
 static uint32_t smpboot[] = {
   0xe59f201c, /* ldr r2, privbase */
   0xe59f001c, /* ldr r0, startaddr */
@@ -44,6 +56,26 @@ static uint32_t smpboot[] = {
   0           /* bootreg: Boot register address is held here */
 };
 
+static void default_write_secondary(CPUState *env,
+                                    const struct arm_boot_info *info)
+{
+    int n;
+    smpboot[ARRAY_SIZE(smpboot) - 1] = info->smp_bootreg_addr;
+    smpboot[ARRAY_SIZE(smpboot) - 2] = info->smp_priv_base;
+    for (n = 0; n < ARRAY_SIZE(smpboot); n++) {
+        smpboot[n] = tswap32(smpboot[n]);
+    }
+    rom_add_blob_fixed("smpboot", smpboot, sizeof(smpboot),
+                       info->smp_loader_start);
+}
+
+static void default_reset_secondary(CPUState *env,
+                                    const struct arm_boot_info *info)
+{
+    stl_phys_notdirty(info->smp_bootreg_addr, 0);
+    env->regs[15] = info->smp_loader_start;
+}
+
 #define WRITE_WORD(p, value) do { \
     stl_phys_notdirty(p, value);  \
     p += 4;                       \
@@ -197,8 +229,7 @@ static void do_cpu_reset(void *opaque)
                                     info->loader_start);
                 }
             } else {
-                stl_phys_notdirty(info->smp_bootreg_addr, 0);
-                env->regs[15] = info->smp_loader_start;
+                info->secondary_cpu_reset_hook(env, info);
             }
         }
     }
@@ -220,6 +251,13 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info)
         exit(1);
     }
 
+    if (!info->secondary_cpu_reset_hook) {
+        info->secondary_cpu_reset_hook = default_reset_secondary;
+    }
+    if (!info->write_secondary_boot) {
+        info->write_secondary_boot = default_write_secondary;
+    }
+
     if (info->nb_cpus == 0)
         info->nb_cpus = 1;
 
@@ -273,13 +311,7 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info)
         rom_add_blob_fixed("bootloader", bootloader, sizeof(bootloader),
                            info->loader_start);
         if (info->nb_cpus > 1) {
-            smpboot[ARRAY_SIZE(smpboot) - 1] = info->smp_bootreg_addr;
-            smpboot[ARRAY_SIZE(smpboot) - 2] = info->smp_priv_base;
-            for (n = 0; n < ARRAY_SIZE(smpboot); n++) {
-                smpboot[n] = tswap32(smpboot[n]);
-            }
-            rom_add_blob_fixed("smpboot", smpboot, sizeof(smpboot),
-                               info->smp_loader_start);
+            info->write_secondary_boot(env, info);
         }
         info->initrd_size = initrd_size;
     }
