summaryrefslogtreecommitdiff
path: root/big-little/switcher
diff options
context:
space:
mode:
Diffstat (limited to 'big-little/switcher')
-rw-r--r--big-little/switcher/context/gic.c264
-rw-r--r--big-little/switcher/context/ns_context.c295
-rw-r--r--big-little/switcher/context/sh_vgic.c225
-rw-r--r--big-little/switcher/trigger/async_switchover.c290
-rw-r--r--big-little/switcher/trigger/handle_switchover.s61
-rw-r--r--big-little/switcher/trigger/sync_switchover.c64
6 files changed, 1199 insertions, 0 deletions
diff --git a/big-little/switcher/context/gic.c b/big-little/switcher/context/gic.c
new file mode 100644
index 0000000..4195346
--- /dev/null
+++ b/big-little/switcher/context/gic.c
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2011, ARM Limited. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with
+ * or without modification, are permitted provided that the
+ * following conditions are met:
+ *
+ * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the
+ * above copyright notice, this list of conditions and
+ * the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of ARM nor the names of its
+ * contributors may be used to endorse or promote products
+ * derived from this software without specific prior written
+ * permission.
+ */
+
+#include "virt_helpers.h"
+#include "misc.h"
+
+struct set_and_clear_regs {
+ volatile unsigned int set[32], clear[32];
+};
+
+typedef struct {
+ /* 0x000 */ volatile unsigned int control;
+ const unsigned int controller_type;
+ const unsigned int implementer;
+ const char padding1[116];
+ /* 0x080 */ volatile unsigned int security[32];
+ /* 0x100 */ struct set_and_clear_regs enable;
+ /* 0x200 */ struct set_and_clear_regs pending;
+ /* 0x300 */ struct set_and_clear_regs active;
+ /* 0x400 */ volatile unsigned int priority[256];
+ /* 0x800 */ volatile unsigned int target[256];
+ /* 0xC00 */ volatile unsigned int configuration[64];
+ /* 0xD00 */ const char padding3[512];
+ /* 0xF00 */ volatile unsigned int software_interrupt;
+ const char padding4[12];
+ /* 0xF10 */ volatile unsigned int sgi_clr_pending[4];
+ /* 0xF20 */ volatile unsigned int sgi_set_pending[4];
+ const char padding5[176];
+ /* 0xFE0 */ unsigned const int peripheral_id[4];
+ /* 0xFF0 */ unsigned const int primecell_id[4];
+} interrupt_distributor;
+
+typedef struct {
+ /* 0x00 */ volatile unsigned int control;
+ /* 0x04 */ volatile unsigned int priority_mask;
+ /* 0x08 */ volatile unsigned int binary_point;
+ /* 0x0c */ volatile unsigned const int interrupt_ack;
+ /* 0x10 */ volatile unsigned int end_of_interrupt;
+ /* 0x14 */ volatile unsigned const int running_priority;
+ /* 0x18 */ volatile unsigned const int highest_pending;
+} cpu_interface;
+
+/*
+ * Saves the GIC CPU interface context
+ * Requires 3 words of memory
+ */
+void save_gic_interface(unsigned int *pointer, unsigned gic_interface_address)
+{
+ cpu_interface *ci = (cpu_interface *) gic_interface_address;
+
+ pointer[0] = ci->control;
+ pointer[1] = ci->priority_mask;
+ pointer[2] = ci->binary_point;
+
+}
+
+/*
+ * Saves this CPU's banked parts of the distributor
+ * Returns non-zero if an SGI/PPI interrupt is pending (after saving all required context)
+ * Requires 19 words of memory
+ */
+int save_gic_distributor_private(unsigned int *pointer,
+ unsigned gic_distributor_address)
+{
+ interrupt_distributor *id =
+ (interrupt_distributor *) gic_distributor_address;
+ unsigned int *ptr = 0x0;
+
+ *pointer = id->enable.set[0];
+ ++pointer;
+ memcpy((void *) pointer, (const void *) id->priority, 8 << 2);
+ pointer += 8;
+ memcpy((void *) pointer, (const void *) id->target, 8 << 2);
+ pointer += 8;
+
+ /* Save just the PPI configurations (SGIs are not configurable) */
+ *pointer = id->configuration[1];
+ ++pointer;
+
+ /*
+ * Private peripheral interrupts need to be replayed on
+ * the destination cpu interface for consistency. This
+ * is the responsibility of the peripheral driver. When
+ * it sees a pending interrupt while saving its context
+ * it should record enough information to recreate the
+ * interrupt while restoring.
+ * We don't save the Pending/Active status and clear it
+ * so that it does not interfere when we are back.
+ */
+ id->pending.clear[0] = 0xffffffff;
+ id->active.clear[0] = 0xffffffff;
+
+ /*
+ * IPIs are different and can be replayed just by saving
+ * and restoring the set/clear pending registers
+ */
+ ptr = pointer;
+ memcpy((void *) pointer, (const void *) id->sgi_set_pending, 4 << 2);
+ pointer += 8;
+
+ /*
+ * Clear the pending SGIs on this cpuif so that they don't
+ * interfere with the wfi later on.
+ */
+ memcpy((void *) id->sgi_clr_pending, (const void *) ptr, 4 << 2);
+
+ if (*pointer) {
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+/*
+ * Saves the shared parts of the distributor
+ * Requires 1 word of memory, plus 20 words for each block of 32 SPIs (max 641 words)
+ * Returns non-zero if an SPI interrupt is pending (after saving all required context)
+ */
+int save_gic_distributor_shared(unsigned int *pointer,
+ unsigned gic_distributor_address)
+{
+ int retval = 0;
+ interrupt_distributor *id =
+ (interrupt_distributor *) gic_distributor_address;
+ unsigned num_spis = 0;
+
+ /* Calculate how many SPIs the GIC supports */
+ num_spis = 32 * (id->controller_type & 0x1f);
+
+ /* Save rest of GIC configuration */
+ if (num_spis) {
+ memcpy((void *) pointer, (const void *) (id->target + 8), (num_spis / 4) << 2);
+ pointer += num_spis / 4;
+ }
+
+ /* Save control register */
+ *pointer = id->control;
+ ++pointer;
+
+ return retval;
+}
+
+void restore_gic_interface(unsigned int *pointer,
+ unsigned gic_interface_address)
+{
+ cpu_interface *ci = (cpu_interface *) gic_interface_address;
+
+ ci->priority_mask = pointer[1];
+ ci->binary_point = pointer[2];
+
+ /* Restore control register last */
+ ci->control = pointer[0];
+}
+
+void restore_gic_distributor_private(unsigned int *pointer,
+ unsigned gic_distributor_address)
+{
+ interrupt_distributor *id =
+ (interrupt_distributor *) gic_distributor_address;
+ unsigned ctr, prev_val = 0, prev_ctr = 0;
+
+ id->enable.set[0] = *pointer;
+ ++pointer;
+
+ memcpy((void *) id->priority, (const void *) pointer, 8 << 2);
+ pointer += 8;
+ memcpy((void *) id->target, (const void *) pointer, 8 << 2);
+ pointer += 8;
+
+ /* Restore just the PPI configurations (SGIs are not configurable) */
+ id->configuration[1] = *pointer;
+ ++pointer;
+
+ /*
+ * Clear active and pending PPIs as they will be recreated by the
+ * peripiherals
+ */
+ id->active.clear[0] = 0xffffffff;
+ id->pending.clear[0] = 0xffffffff;
+
+ /*
+ * Restore pending IPIs
+ */
+ for (ctr = 0; ctr < 4; ctr++) {
+ if(!pointer[ctr])
+ continue;
+
+ if(pointer[ctr] == prev_val) {
+ pointer[ctr] = pointer[prev_ctr];
+ } else {
+ prev_val = pointer[ctr];
+ prev_ctr = ctr;
+ remap_cpuif(&pointer[ctr]);
+ }
+ }
+
+ memcpy((void *) id->sgi_set_pending, (const void *) pointer, 4 << 2);
+ pointer += 4;
+
+ id->pending.set[0] = *pointer;
+
+ return;
+}
+
+/*
+ * Optimized routine to restore the shared vgic distributor interface.
+ * Saving on outbound and restoring on inbound is redundant as the
+ * context is non-volatile across a switch. Hence, simply R-M-W on
+ * the inbound and remove the 'save' function from the outbound
+ * critical path.
+ */
+void restore_gic_distributor_shared(unsigned int *pointer,
+ unsigned gic_distributor_address)
+{
+ interrupt_distributor *id =
+ (interrupt_distributor *) gic_distributor_address;
+ unsigned num_spis;
+ unsigned ctr, prev_val = 0, prev_ctr = 0;
+
+ /* Calculate how many SPIs the GIC supports */
+ num_spis = 32 * ((id->controller_type) & 0x1f);
+
+ /* Restore rest of GIC configuration */
+ if (num_spis) {
+
+ memcpy((void *) pointer, (const void *) (id->target + 8), (num_spis / 4) << 2);
+
+ for (ctr = 0; ctr < num_spis / 4; ctr++) {
+ if(!pointer[ctr])
+ continue;
+
+ if(pointer[ctr] == prev_val) {
+ pointer[ctr] = pointer[prev_ctr];
+ } else {
+ prev_val = pointer[ctr];
+ prev_ctr = ctr;
+ remap_cpuif(&pointer[ctr]);
+ }
+ }
+
+ memcpy((void *) (id->target + 8), (const void *) pointer, (num_spis / 4) << 2);
+ }
+
+ return;
+}
diff --git a/big-little/switcher/context/ns_context.c b/big-little/switcher/context/ns_context.c
new file mode 100644
index 0000000..891f5bb
--- /dev/null
+++ b/big-little/switcher/context/ns_context.c
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2011, ARM Limited. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with
+ * or without modification, are permitted provided that the
+ * following conditions are met:
+ *
+ * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the
+ * above copyright notice, this list of conditions and
+ * the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of ARM nor the names of its
+ * contributors may be used to endorse or promote products
+ * derived from this software without specific prior written
+ * permission.
+ */
+
+#include "virt_helpers.h"
+#include "vgiclib.h"
+#include "gic_registers.h"
+#include "int_master.h"
+#include "context.h"
+#include "bl.h"
+#include "misc.h"
+#include "events.h"
+#include "virtualisor.h"
+#include "helpers.h"
+
+extern void gic_enable_int(unsigned);
+extern void SetupVGIC(unsigned);
+extern unsigned async_switchover;
+extern unsigned hyp_timer_trigger;
+
+/* Bakery locks to serialize access to the tube. */
+static bakery_t lock_tube0 __attribute__ ((section("BL_DV_PAGE"))) = { 0 };
+static bakery_t lock_tube1 __attribute__ ((section("BL_DV_PAGE"))) = { 0 };
+
+/*
+ * Top level structure which encapsulates the context of the entire
+ * Kingfisher system
+ */
+system_context switcher_context = {0};
+
+void stop_generic_timer(generic_timer_context *ctr_ctx)
+{
+ /*
+ * Disable the timer and mask the irq to prevent
+ * suprious interrupts on this cpu interface. It
+ * will bite us when we come back if we don't. It
+ * will be replayed on the inbound cluster.
+ */
+ write_cntp_ctl(TIMER_MASK_IRQ);
+
+
+ /*
+ * If the local timer interrupt was being used as
+ * the asynchronous trigger, then it was disabled
+ * in handle_interrupt() to prevent this level-
+ * triggerred interrupt from firing. Now that its
+ * been acked at the peripheral. We can renable it
+ */
+ if(!hyp_timer_trigger) {
+ if (ctr_ctx->cntp_ctl & TIMER_IRQ_STAT)
+ gic_enable_int(LCL_TIMER_IRQ);
+ }
+
+ return;
+}
+
+void save_context(unsigned first_cpu)
+{
+ unsigned cpu_id = read_cpuid();
+ unsigned cluster_id = read_clusterid();
+ cpu_context *ns_cpu_ctx =
+ &switcher_context.cluster.core[cpu_id].ns_cpu_ctx;
+ unsigned *pmon_context = ns_cpu_ctx->pmon_regs;
+ unsigned *gp_context = ns_cpu_ctx->banked_cpu_regs;
+ unsigned *vfp_context = ns_cpu_ctx->vfp_regs;
+ banked_cp15_context *cp15_context = &ns_cpu_ctx->banked_cp15_regs;
+ gic_cpu_context *gic_pvt_context = &ns_cpu_ctx->gic_cpu_ctx;
+ generic_timer_context *cp15_timer_ctx = &ns_cpu_ctx->cp15_timer_ctx;
+ cp15_fault_regs *fault_ctx = &cp15_context->ns_cp15_fault_regs;
+
+ write_trace(&lock_tube0, NS_TUBE0, "Context Save Start", read_cntpct(), 0x0, 0x0);
+
+ /*
+ * Good place to bring the inbound cluster out of reset, but first
+ * we need to save the secure world context.
+ */
+ write_trace(&lock_tube0, NS_TUBE0, "Secure Context Save Start", read_cntpct(), 0x0, 0x0);
+ smc(SMC_SEC_SAVE, (unsigned) hyp_warm_reset_handler);
+ write_trace(&lock_tube0, NS_TUBE0, "Secure Context Save End", read_cntpct(), 0x0, 0x0);
+
+ /*
+ * Save the 32-bit Generic timer context & stop them
+ */
+ save_generic_timer((unsigned *) cp15_timer_ctx, 0x1);
+ stop_generic_timer(cp15_timer_ctx);
+
+ /*
+ * Save v7 generic performance monitors
+ * Save cpu general purpose banked registers
+ * Save cp15 context
+ */
+ save_performance_monitors(pmon_context);
+ save_banked_registers(gp_context);
+ save_cp15(cp15_context->cp15_misc_regs);
+ save_control_registers(cp15_context->cp15_ctrl_regs, 0x0);
+ save_mmu(cp15_context->cp15_mmu_regs);
+ save_fault_status((unsigned *) fault_ctx);
+
+ /*
+ * Check if non-secure world has access to the vfp/neon registers
+ * and save them if so.
+ */
+ if (read_nsacr() & (0x3 << 10))
+ save_vfp(vfp_context);
+
+
+ /*
+ * Disable the GIC CPU interface tp prevent interrupts from waking
+ * the core from wfi() subsequently.
+ */
+ write32(GIC_IC_PHY_BASE + GICC_CTL, 0x0);
+
+ /* Save vGIC virtual cpu interface (cpu view) context */
+ save_gic_interface(gic_pvt_context->gic_cpu_if_regs, VGIC_VM_PHY_BASE);
+
+ /*
+ * Save the HYP view registers. These registers contain a snapshot
+ * of all the physical interrupts acknowledged till we
+ * entered this HYP mode.
+ */
+ vgic_savestate(cpu_id);
+
+ /*
+ * TODO:
+ * Is it safe for the secondary cpu to save its context
+ * while the GIC distributor is on. Should be as its
+ * banked context and the cpu itself is the only one
+ * who can change it. Still have to consider cases e.g
+ * SGIs/Localtimers becoming pending.
+ */
+ save_gic_distributor_private(gic_pvt_context->gic_dist_if_pvt_regs,
+ GIC_ID_PHY_BASE);
+
+ /* Safe place to save the Virtualisor context */
+ SaveVirtualisor(first_cpu);
+
+ /*
+ * Indicate to the inbound side that the context has been saved and is ready
+ * for pickup.
+ */
+ write_trace(&lock_tube0, NS_TUBE0, "Context Save End", read_cntpct(), 0x0, 0x0);
+ set_event(OB_CONTEXT_DONE, cpu_id);
+
+ /*
+ * Now, we wait for the inbound cluster to signal that its done atleast picking
+ * up the saved context.
+ */
+ if (cpu_id == first_cpu) {
+ wait_for_events(IB_CONTEXT_DONE);
+ write_trace(&lock_tube0, NS_TUBE0, "Inbound done", read_cntpct(), 0x0, 0x0);
+ }
+
+ return;
+}
+
+void restore_context(unsigned first_cpu)
+{
+ unsigned cpu_id = read_cpuid();
+ unsigned cluster_id = read_clusterid();
+ unsigned warm_reset = 1;
+ cpu_context *ns_cpu_ctx =
+ &switcher_context.cluster.core[cpu_id].ns_cpu_ctx;
+ global_context *gbl_context = &switcher_context.cluster.ns_cluster_ctx;
+ unsigned *pmon_context = ns_cpu_ctx->pmon_regs;
+ unsigned *gp_context = ns_cpu_ctx->banked_cpu_regs;
+ unsigned *vfp_context = ns_cpu_ctx->vfp_regs;
+ gic_cpu_context *gic_pvt_context = &ns_cpu_ctx->gic_cpu_ctx;
+ generic_timer_context *cp15_timer_ctx = &ns_cpu_ctx->cp15_timer_ctx;
+ banked_cp15_context *cp15_context = &ns_cpu_ctx->banked_cp15_regs;
+ cp15_fault_regs *fault_ctx = &cp15_context->ns_cp15_fault_regs;
+ vm_context *src = 0x0;
+ vm_context *dest = 0x0;
+ unsigned dest_cpuif = 0x0;
+ unsigned src_cpuif = 0x0;
+
+ /*
+ * Map cpuids to cpu interface numbers so that cpu interface
+ * specific context can be correctly restored on the external
+ * vGIC.
+ */
+ map_cpuif(cluster_id, cpu_id);
+ SetupVGIC(warm_reset);
+
+ /*
+ * Inbound headstart i.e. the vGIC configuration, secure context
+ * restore & cache invalidation has been done. Now wait for the
+ * outbound to provide the context.
+ */
+ write_trace(&lock_tube1, NS_TUBE1, "Wait for context", read_cntpct(), 0x0, 0x0);
+ wait_for_event(OB_CONTEXT_DONE, cpu_id);
+ reset_event(OB_CONTEXT_DONE, cpu_id);
+
+ /*
+ * First cpu restores the global context while the others take
+ * care of their own.
+ */
+ write_trace(&lock_tube1, NS_TUBE1, "Context Restore Start ", read_cntpct(), 0x0, 0x0);
+ if (cpu_id == first_cpu)
+ restore_gic_distributor_shared(gbl_context->gic_dist_if_regs,
+ GIC_ID_PHY_BASE);
+ restore_gic_distributor_private(gic_pvt_context->gic_dist_if_pvt_regs,
+ GIC_ID_PHY_BASE);
+ vgic_loadstate(cpu_id);
+
+ SetupVirtualisor(first_cpu);
+
+ /* Restore NS VGIC context */
+ restore_gic_interface(gic_pvt_context->gic_cpu_if_regs,
+ VGIC_VM_PHY_BASE);
+
+ /*
+ * Check if non-secure world has access to the vfp/neon registers
+ * and save them if so.
+ */
+ if (read_nsacr() & (0x3 << 10))
+ restore_vfp(vfp_context);
+
+ /*
+ * Restore cp15 context
+ * Restore cpu general purpose banked registers
+ * Restore v7 generic performance monitors
+ * Restore the 32-bit Generic timer context
+ */
+ restore_fault_status((unsigned *) fault_ctx);
+ restore_mmu(cp15_context->cp15_mmu_regs);
+ restore_control_registers(cp15_context->cp15_ctrl_regs, 0x0);
+ restore_cp15(cp15_context->cp15_misc_regs);
+ restore_banked_registers(gp_context);
+ restore_performance_monitors(pmon_context);
+ restore_generic_timer((unsigned *) cp15_timer_ctx, 0x1);
+
+ /*
+ * Paranoid check to ensure that all HYP/Secure context & Virtualisor
+ * is restored before any core enters the non-secure mode to use it.
+ */
+ if (cpu_id == first_cpu) {
+ set_events(HYP_CONTEXT_DONE);
+ }
+ wait_for_event(HYP_CONTEXT_DONE, cpu_id);
+ reset_event(HYP_CONTEXT_DONE, cpu_id);
+
+ /*
+ * Return the saved general purpose registers saved above the HYP mode
+ * stack of our counterpart cpu on the other cluster.
+ */
+ dest_cpuif = get_cpuif(cluster_id, cpu_id);
+ src_cpuif = get_cpuif(!cluster_id, cpu_id);
+ dest = &guestos_state[dest_cpuif].context;
+ src = &guestos_state[src_cpuif].context;
+
+ dest->gp_regs[0] = src->gp_regs[0];
+ dest->gp_regs[1] = src->gp_regs[1];
+ dest->gp_regs[2] = src->gp_regs[2];
+ dest->gp_regs[3] = src->gp_regs[3];
+ dest->gp_regs[4] = src->gp_regs[4];
+ dest->gp_regs[5] = src->gp_regs[5];
+ dest->gp_regs[6] = src->gp_regs[6];
+ dest->gp_regs[7] = src->gp_regs[7];
+ dest->gp_regs[8] = src->gp_regs[8];
+ dest->gp_regs[9] = src->gp_regs[9];
+ dest->gp_regs[10] = src->gp_regs[10];
+ dest->gp_regs[11] = src->gp_regs[11];
+ dest->gp_regs[12] = src->gp_regs[12];
+ dest->gp_regs[13] = src->gp_regs[13];
+ dest->gp_regs[14] = src->gp_regs[14];
+ dest->elr_hyp = src->elr_hyp;
+ dest->spsr = src->spsr;
+ dest->usr_lr = src->usr_lr;
+
+ write_trace(&lock_tube1, NS_TUBE1, "Context Restore End", read_cntpct(), 0x0, 0x0);
+ set_event(IB_CONTEXT_DONE, cpu_id);
+
+ if (async_switchover && cpu_id == first_cpu)
+ enable_trigger(read_cntfrq());
+
+ return;
+}
diff --git a/big-little/switcher/context/sh_vgic.c b/big-little/switcher/context/sh_vgic.c
new file mode 100644
index 0000000..a13f862
--- /dev/null
+++ b/big-little/switcher/context/sh_vgic.c
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2011, ARM Limited. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with
+ * or without modification, are permitted provided that the
+ * following conditions are met:
+ *
+ * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the
+ * above copyright notice, this list of conditions and
+ * the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of ARM nor the names of its
+ * contributors may be used to endorse or promote products
+ * derived from this software without specific prior written
+ * permission.
+ */
+
+#include "virt_helpers.h"
+#include "gic_registers.h"
+#include "misc.h"
+#include "context.h"
+
+/*
+ * Private data structure that maps each cpuid in a
+ * multicluster system to its physical cpu interface
+ * id when a shared vGIC is used.
+ */
+static unsigned int cpuif_map[MAX_CLUSTERS][MAX_CORES];
+
+/*
+ * Private data structure that maps each cpu interface
+ * id to the corresponding cpuid & clusterid. In each
+ * entry top 4 bits store the cluster id while the bottom
+ * 4 store the cpuid.
+ *
+ * TODO:
+ * No real need for this data structure. Should be
+ * possible to get this info from the previous data
+ * structure and the knowledge of number of clusters
+ * and cpus from the KFSCB
+ */
+static unsigned int cpuinfo_map[MAX_CPUIFS];
+
+/*
+ * IPI to use for cpu interface discovery.
+ */
+#define CPUIF_IPI 0xf
+
+/*
+ * In the presence of the Switcher and the shared vGIC
+ * find the mapping between the cpu interface and the
+ * cpu id. This is required to:
+ * a) Set processor targets correctly during context
+ * save & restore and normal operation (IPI handling)
+ * b) Restoring the context of pending IPIs on the inbound
+ * cluster.
+ * Ideally a platform defined register should have done the
+ * trick. However, we rely on a software mechanism to obtain
+ * this information.
+ *
+ * Assumptions:
+ * a) Expected to be used only in the "Switching" case when
+ * there is a mismatch between the cpuids and the cpuif ids
+ * on the "other" cluster
+ * b) In the "Switching" case with external vGIC, the distributor
+ * interface should never get disabled.
+ * c) Always called in Secure world
+ *
+ * Idea is that, without disturbing the existing GIC state too
+ * much (outbound might be doing things with it), we need to
+ * ensure that only the IPI which we choose gets through our
+ * cpu interface. This should not be a problem as the SPIs will
+ * be targetted to the outbound cluster cpus & there will be no
+ * local peripheral interrupts expected. There is paranoia about
+ * getting IPIs from the outbound but this can be dealt with by
+ * manipulating the IPI priorities so that we only see what we
+ * want to see.
+ *
+ * TODO:
+ * Assuming no IPIs will be received at this point of time. So
+ * no changes will be made to the priority mask registers.
+ */
+unsigned map_cpuif(unsigned cluster_id, unsigned cpu_id)
+{
+ unsigned cpuif_id = 0;
+
+ cpuif_id = bitindex(read32(GIC_ID_PHY_BASE + GICD_CPUS) & 0xff);
+ cpuif_map[cluster_id][cpu_id] = cpuif_id;
+ cpuinfo_map[cpuif_id] = (cluster_id << 4) | cpu_id;
+
+ return 0;
+}
+
+/*
+ * Given a cpu and cluster id find the cpu interface it maps to.
+ */
+unsigned get_cpuif(unsigned cluster_id, unsigned cpu_id)
+{
+ return cpuif_map[cluster_id][cpu_id];
+}
+
+/*
+ * Given a cpu interface id, find what cpu and cluster id it maps to.
+ */
+unsigned get_cpuinfo(unsigned cpuif)
+{
+ return cpuinfo_map[cpuif];
+}
+
+/*
+ * Given a cpu interface mask, find the corresponding cpuid mask on that cluster.
+ */
+unsigned get_cpu_mask(unsigned cpuif_mask)
+{
+ unsigned num_bytes = sizeof(unsigned int) / sizeof(unsigned char), ctr;
+ unsigned cpuif = 0, clusterid = read_clusterid(), cpu_mask = 0;
+ unsigned cpuid = 0;
+
+ for (ctr = 0; ctr < num_bytes; ctr++) { /* Iterate through the cpu_mask byte wise */
+ unsigned byte = 0;
+ unsigned char lz = 0;
+
+ byte = (cpuif_mask >> (ctr << 3)) & 0xff;
+ while ((lz = __clz(byte)) != 0x20) {
+ cpuif = 31 - lz;
+ byte &= ~(1 << cpuif); /* Clear the bit just discovered */
+ cpuid = get_cpuinfo(cpuif) & 0xf;
+ cpu_mask |= (1 << cpuid) << (ctr << 3);
+ }
+ }
+
+ return cpu_mask;
+}
+
+/*
+ * Given a cpu mask, find the corresponding cpu interface mask on that cluster.
+ */
+unsigned get_cpuif_mask(unsigned cpu_mask)
+{
+ unsigned num_bytes = sizeof(unsigned int) / sizeof(unsigned char), ctr;
+ unsigned cpuif = 0, clusterid = read_clusterid(), cpuif_mask = 0;
+ unsigned cpuid = 0;
+
+ for (ctr = 0; ctr < num_bytes; ctr++) { /* Iterate through the cpu_mask byte wise */
+ unsigned byte = 0;
+ unsigned char lz = 0;
+
+ byte = (cpu_mask >> (ctr << 3)) & 0xff;
+ while ((lz = __clz(byte)) != 0x20) {
+ cpuid = 31 - lz;
+ byte &= ~(1 << cpuid); /* Clear the bit just discovered */
+ cpuif = get_cpuif(clusterid, cpuid);
+ cpuif_mask |= (1 << cpuif) << (ctr << 3);
+ }
+ }
+
+ return cpuif_mask;
+}
+
+/*
+ * Given a cpu interface mask, find its corresponding mask on the other cluster
+ * NOTE: Creates the new mask in-place.
+ */
+#if 1
+/*
+ * This is the fast version of remapping cpu interface ids to cpuids. Instead of
+ * remapping each bit (target interface) in the arg passed, it simply shifts all
+ * the bits by the number of cpus available.
+ */
+unsigned remap_cpuif(unsigned *cpuif_mask)
+{
+ unsigned cluster_id = read_clusterid(), num_cpus = num_secondaries() + 1;
+
+
+ if(cluster_id == EAGLE)
+ *cpuif_mask = *cpuif_mask >> num_cpus;
+ else
+ *cpuif_mask = *cpuif_mask << num_cpus;
+
+ return 0;
+}
+#else
+unsigned remap_cpuif(unsigned *cpuif_mask)
+{
+ unsigned ib_cpuif_mask = 0, ob_cpuif = 0, ib_cpuif = 0, ob_cpuid =
+ 0, ob_clusterid = 0, ib_cpuid = 0, ib_clusterid = 0;
+ unsigned num_bytes = sizeof(unsigned int) / sizeof(unsigned char), ctr;
+
+ for (ctr = 0; ctr < num_bytes; ctr++) {
+ unsigned byte = 0;
+ unsigned char lz = 0;
+
+ byte = (*cpuif_mask >> (ctr << 3)) & 0xff;
+
+ while ((lz = __clz(byte)) != 0x20) {
+ ob_cpuif = 31 - lz;
+ byte &= ~(1 << ob_cpuif); /* Clear the bit just discovered */
+ ob_cpuid = get_cpuinfo(ob_cpuif) & 0xf;
+ ob_clusterid = (get_cpuinfo(ob_cpuif) >> 4) & 0xf;
+
+ /*
+ * TODO: Can we assume that the inbound and outbound clusters will
+ * always be logical complements of each other
+ */
+ ib_clusterid = !ob_clusterid;
+
+ /*
+ * TODO: Assuming that the cpuids have a 1:1 mapping i.e. cpuX on
+ * one cluster will always map to cpuX on the other cluster.
+ */
+ ib_cpuid = ob_cpuid;
+ ib_cpuif = get_cpuif(ib_clusterid, ib_cpuid);
+ ib_cpuif_mask |= (1 << ib_cpuif) << (ctr << 3);
+ }
+ }
+
+ *cpuif_mask = ib_cpuif_mask;
+ return 0;
+}
+#endif
diff --git a/big-little/switcher/trigger/async_switchover.c b/big-little/switcher/trigger/async_switchover.c
new file mode 100644
index 0000000..056c8a1
--- /dev/null
+++ b/big-little/switcher/trigger/async_switchover.c
@@ -0,0 +1,290 @@
+/*
+ * Copyright (c) 2011, ARM Limited. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with
+ * or without modification, are permitted provided that the
+ * following conditions are met:
+ *
+ * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the
+ * above copyright notice, this list of conditions and
+ * the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of ARM nor the names of its
+ * contributors may be used to endorse or promote products
+ * derived from this software without specific prior written
+ * permission.
+ */
+
+#include "virt_helpers.h"
+#include "misc.h"
+#include "stdlib.h"
+#include "gic_registers.h"
+
+extern void gic_enable_int(unsigned);
+extern void gic_disable_int(unsigned);
+extern void gic_send_ipi(unsigned, unsigned);
+extern void gic_eoi_int(unsigned);
+extern void gic_deactivate_int(unsigned);
+extern int __rand_r(struct _rand_state *);
+/*
+ * Set of flags used by the interrupt handling code
+ * to distinguish between IPIs sent by the big-little
+ * code and the payload software.
+ * TODO: Assumes only one cpu will send an IPI at a
+ * time rather than multiple cpus sending the same
+ * IPI to each other at the same time from within the
+ * HYP mode.
+ */
+static unsigned lock_ipi_check;
+static unsigned hyp_ipi_check[16];
+static unsigned timer_count;
+/* Support for the switchover interval randomly but sanely */
+static unsigned rand_async_switches = RAND_ASYNC;
+/* Use HYP timer for async switches */
+unsigned hyp_timer_trigger = USE_HYP_TIMERS;
+
+/*
+ * Returns the id of the first IPI that is not pending on
+ * our cpu interface or the first IPI that is pending but
+ * was not generated by us. Returns 16 if no such IPI is
+ * found
+ */
+static unsigned get_free_ipi(void)
+{
+ unsigned ctr, shift, cpu_if_bit, cpu_id = read_cpuid(), cluster_id =
+ read_clusterid();
+
+ cpu_if_bit = 1 << get_cpuif(cluster_id, cpu_id);
+
+ /* Find the register offset */
+ for (ctr = 0; ctr < 4; ctr++)
+ /* Check whether IPI<shift> has already been generated by us */
+ for (shift = 0; shift < 4; shift++) {
+ if (read32
+ (GIC_ID_PHY_BASE + GICD_SPENDSGIR +
+ (ctr << 2)) & (cpu_if_bit << (shift << 3)))
+ continue;
+
+ return (ctr << 2) + shift;
+ }
+
+ return 16;
+}
+
+static void ack_trigger(void)
+{
+ unsigned ctl = 0;
+
+ ctl = read_cnthp_ctl();
+ if (ctl & TIMER_IRQ_STAT) {
+ /* Disable timer and mask interrupt */
+ write_cnthp_ctl(TIMER_MASK_IRQ);
+ } else {
+ printf("Spurious HYP timer irq \n");
+ panic();
+ }
+
+ return;
+}
+
+/*
+ * Broadcast first available IPI so that all cpus can start switching to
+ * the other cluster.
+ */
+void signal_switchover(void)
+{
+ unsigned ipi_no = 0x0;
+
+ /* If x is the no. of cpus then corresponding mask would be (1 << x) - 1 */
+ unsigned cpu_mask = (1 << (num_secondaries() + 1)) - 1;
+ /*
+ * Map the target cpuids to their cpu interfaces as the 1:1 mapping
+ * no longer exists with the external vGIC.
+ */
+ unsigned cpuif_mask = get_cpuif_mask(cpu_mask);
+
+ /*
+ * Send an ipi to all the cpus in the cluster including ourselves
+ * to start a switch to the inbound cluster. First choose a non-
+ * pending IPI to avoid a clash with the OS.
+ */
+ ipi_no = get_free_ipi();
+
+ /*
+ * For this IPI set the mask in our global variable. We do it, payload software
+ * does not. But, first check whether any earlier IPIs have already been acked
+ */
+ while (hyp_ipi_check[ipi_no]) ;
+ spin_lock(&lock_ipi_check);
+ hyp_ipi_check[ipi_no] = cpuif_mask;
+ dsb();
+ spin_unlock(&lock_ipi_check);
+
+ /* Send the IPI to the cpu_mask */
+ gic_send_ipi(cpuif_mask, ipi_no);
+
+ return;
+}
+
+unsigned check_switchover_ipi(unsigned cpu_if, unsigned ipi_no)
+{
+ unsigned rc = FALSE;
+
+ spin_lock(&lock_ipi_check);
+ /*
+ * If this IPI was sent by the big-little code then our cpu_if bit must have
+ * been set in the ipi_check flag. Reset the bit an indicate that its an
+ * internal IPI.
+ */
+ if (hyp_ipi_check[ipi_no] & (1 << cpu_if)) {
+ rc = TRUE;
+ hyp_ipi_check[ipi_no] &= ~(1 << cpu_if);
+ dsb();
+ }
+ spin_unlock(&lock_ipi_check);
+
+ return rc;
+}
+
+unsigned check_trigger(unsigned int_id, unsigned int_ack)
+{
+ unsigned cpuid = read_cpuid();
+ unsigned platform = (read32(KFSCB_BASE + KFS_ID) >> 20) & 0xf;
+
+ /*
+ * If we are not using HYP mode timers for triggering a switchover
+ * then check whether this is a suitable local timer interrupt to
+ * switch
+ */
+ if (hyp_timer_trigger == FALSE) {
+ /*
+ * We need to hijack every 128th timer interrupt on cpu0 and
+ * use it as a stimulus to switchover
+ */
+ if (cpuid == 0 && int_id == LCL_TIMER_IRQ)
+ timer_count++;
+
+ if (timer_count & LCL_TIMER_FREQ)
+ return FALSE;
+ }
+ /*
+ * Trigger a switchover upon getting a HYP timer IRQ. Its
+ * targetted only to cpu0.
+ */
+ else if (int_id != HYP_TIMER_IRQ)
+ return FALSE;
+
+ /*
+ * Do the needful now that it is confirmed that we need to move
+ * to the other cluster
+ */
+
+ /* Indicator on emulation that switches are actually taking place */
+ if (platform != 0x1)
+ printf("%d", read_clusterid());
+
+ /*
+ * Send an IPI to all the cores in this cluster to start
+ * a switchover.
+ */
+ signal_switchover();
+
+ if (hyp_timer_trigger)
+ ack_trigger();
+ else
+ /*
+ * Complete handling of the local timer interrupt at the physical gic
+ * level. Its disabled as its level triggerred and will reassert as
+ * soon as we leave this function since its not been cleared at the
+ * peripheral just yet. The local timer context is saved and this irq
+ * cleared in "save_hyp_context". The interrupt is enabled then.
+ */
+ gic_disable_int(int_id);
+
+ /* Finish handling this interrupt */
+ gic_eoi_int(int_ack);
+ if (read32(GIC_IC_PHY_BASE + GICC_CTL) & 0x200)
+ gic_deactivate_int(int_ack);
+
+ return TRUE;
+}
+
+void keep_trigger_alive(void)
+{
+ /*
+ * The OS might have disabled the HYP timer interrupt
+ * while setting up its view of the vGIC. So enable
+ * it if disabled upon receiving any other interrupt.
+ * Better than virtualising vGIC accesses on the TARGET
+ * CPU.
+ */
+ if (hyp_timer_trigger)
+ if (!
+ (read32(GIC_ID_PHY_BASE + GICD_ENABLESET) &
+ (1 << HYP_TIMER_IRQ)))
+ gic_enable_int(HYP_TIMER_IRQ);
+
+ return;
+}
+
+void enable_trigger(unsigned tval)
+{
+ unsigned ctl = TIMER_ENABLE;
+ unsigned platform = read32((KFSCB_BASE + KFS_ID) >> 20) & 0xf;
+
+ /*
+ * No need to lock this as its accessed by only one cpu
+ * per cluster and that too one at a time.
+ */
+ static unsigned int rand_no = 0xdeadbeef;
+ static struct _rand_state buffer;
+
+ /*
+ * Nothing needs to be done if physical local timers
+ * are being used for doing a switchover.
+ */
+ if (hyp_timer_trigger == TRUE) {
+ if (rand_async_switches) {
+ _srand_r(&buffer, rand_no);
+ rand_no = (unsigned) _rand_r(&buffer);
+ }
+
+ /* Enable timer and unmask interrupt */
+ write_cnthp_ctl(ctl);
+
+ if (rand_async_switches) {
+ unsigned interval;
+
+ /*
+ * TODO: Assuming that the tval is always 12000000
+ * Increment or decrement the timer value randomly
+ * but never by more than a factor of 10
+ */
+ if (rand_no % 2)
+ interval = tval * (rand_no % 10);
+ else
+ interval = tval / (rand_no % 10);
+
+ write_cnthp_tval(interval);
+
+ } else {
+ /*
+ * Program the timer to fire every 12000000 instructions
+ * on the FastModel while 1500000 cycles on the Emulator
+ */
+ if (platform == 0x1)
+ write_cnthp_tval(tval);
+ else
+ write_cnthp_tval(tval >> 3);
+ }
+
+ gic_enable_int(HYP_TIMER_IRQ);
+ }
+
+ return;
+}
diff --git a/big-little/switcher/trigger/handle_switchover.s b/big-little/switcher/trigger/handle_switchover.s
new file mode 100644
index 0000000..d18ba21
--- /dev/null
+++ b/big-little/switcher/trigger/handle_switchover.s
@@ -0,0 +1,61 @@
+ ;
+ ; Copyright (c) 2011, ARM Limited. All rights reserved.
+ ;
+ ; Redistribution and use in source and binary forms, with
+ ; or without modification, are permitted provided that the
+ ; following conditions are met:
+ ;
+ ; Redistributions of source code must retain the above
+ ; copyright notice, this list of conditions and the
+ ; following disclaimer.
+ ;
+ ; Redistributions in binary form must reproduce the
+ ; above copyright notice, this list of conditions and
+ ; the following disclaimer in the documentation
+ ; and/or other materials provided with the distribution.
+ ;
+ ; Neither the name of ARM nor the names of its
+ ; contributors may be used to endorse or promote products
+ ; derived from this software without specific prior written
+ ; permission.
+ ;
+
+ AREA SwitchoverCode, CODE, READONLY, ALIGN=5
+
+ PRESERVE8
+
+ IMPORT save_context
+ IMPORT smc
+ EXPORT switch_cluster
+
+SMC_SEC_SHUTDOWN EQU 0x2
+
+ ; ----------------------------------------------------
+ ; This function directs the switchover to the inbound
+ ; cluster. The context is first saved, stacks switched
+ ; & the cluster is powered down.
+ ; We need to switch stacks from being resident in normal
+ ; WBWA/S memory to SO memory to prevent potential stack
+ ; corruption after turning off the C bit in the HSCTLR.
+ ; Subsequent accesses will be SO while there will be
+ ; valid cache lines of the stack from prior accesses
+ ; ----------------------------------------------------
+switch_cluster FUNCTION
+ ; ----------------------------------------------------
+ ; We don't push any registers on the stack as we are
+ ; not going to return from this function
+ ; ----------------------------------------------------
+ MOV r4, r0
+ BL save_context
+ ; ----------------------------------------------------
+ ; We are now through with saving the context and the
+ ; inbound cluster has started picking it up. Switch to
+ ; the secure world to clean the caches and power down
+ ; the cluster
+ ; ----------------------------------------------------
+ MOV r0, #SMC_SEC_SHUTDOWN
+ BL smc
+ ENDFUNC
+
+ END
+
diff --git a/big-little/switcher/trigger/sync_switchover.c b/big-little/switcher/trigger/sync_switchover.c
new file mode 100644
index 0000000..ad257bc
--- /dev/null
+++ b/big-little/switcher/trigger/sync_switchover.c
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2011, ARM Limited. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with
+ * or without modification, are permitted provided that the
+ * following conditions are met:
+ *
+ * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the
+ * above copyright notice, this list of conditions and
+ * the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of ARM nor the names of its
+ * contributors may be used to endorse or promote products
+ * derived from this software without specific prior written
+ * permission.
+ */
+
+#include "misc.h"
+#include "virt_helpers.h"
+#include "bl.h"
+
+extern void signal_switchover(void);
+
+unsigned is_hvc()
+{
+ return ((read_hsr() >> 26) == 0x12 ? TRUE : FALSE);
+}
+
+unsigned HandleHVC(vm_context * context)
+{
+ unsigned opcode = read_hsr() & 0xffff;
+ unsigned rc = FALSE;
+
+ switch(opcode) {
+
+ /*
+ * HVC call to switch to the other cluster. This is done
+ * by sending a switchover IPI to all the cores in the cluster.
+ */
+ case SYNC_SWITCHOVER:
+ signal_switchover();
+ rc = TRUE;
+ break;
+
+ /*
+ * HVC call to return the physical MPIDR
+ */
+ case READ_MPIDR:
+ context->gp_regs[0] = read_mpidr();
+ rc = TRUE;
+ break;
+
+ default:
+ break;
+
+ }
+
+ return rc;
+}