diff options
Diffstat (limited to 'big-little/switcher')
-rw-r--r-- | big-little/switcher/context/gic.c | 264 | ||||
-rw-r--r-- | big-little/switcher/context/ns_context.c | 295 | ||||
-rw-r--r-- | big-little/switcher/context/sh_vgic.c | 225 | ||||
-rw-r--r-- | big-little/switcher/trigger/async_switchover.c | 290 | ||||
-rw-r--r-- | big-little/switcher/trigger/handle_switchover.s | 61 | ||||
-rw-r--r-- | big-little/switcher/trigger/sync_switchover.c | 64 |
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; +} |