1*1c33be57SNicolas Pitre /* 2*1c33be57SNicolas Pitre * arch/arm/common/bL_switcher.c -- big.LITTLE cluster switcher core driver 3*1c33be57SNicolas Pitre * 4*1c33be57SNicolas Pitre * Created by: Nicolas Pitre, March 2012 5*1c33be57SNicolas Pitre * Copyright: (C) 2012-2013 Linaro Limited 6*1c33be57SNicolas Pitre * 7*1c33be57SNicolas Pitre * This program is free software; you can redistribute it and/or modify 8*1c33be57SNicolas Pitre * it under the terms of the GNU General Public License version 2 as 9*1c33be57SNicolas Pitre * published by the Free Software Foundation. 10*1c33be57SNicolas Pitre */ 11*1c33be57SNicolas Pitre 12*1c33be57SNicolas Pitre #include <linux/init.h> 13*1c33be57SNicolas Pitre #include <linux/kernel.h> 14*1c33be57SNicolas Pitre #include <linux/module.h> 15*1c33be57SNicolas Pitre #include <linux/sched.h> 16*1c33be57SNicolas Pitre #include <linux/interrupt.h> 17*1c33be57SNicolas Pitre #include <linux/cpu_pm.h> 18*1c33be57SNicolas Pitre #include <linux/workqueue.h> 19*1c33be57SNicolas Pitre #include <linux/mm.h> 20*1c33be57SNicolas Pitre #include <linux/string.h> 21*1c33be57SNicolas Pitre #include <linux/irqchip/arm-gic.h> 22*1c33be57SNicolas Pitre 23*1c33be57SNicolas Pitre #include <asm/smp_plat.h> 24*1c33be57SNicolas Pitre #include <asm/suspend.h> 25*1c33be57SNicolas Pitre #include <asm/mcpm.h> 26*1c33be57SNicolas Pitre #include <asm/bL_switcher.h> 27*1c33be57SNicolas Pitre 28*1c33be57SNicolas Pitre 29*1c33be57SNicolas Pitre /* 30*1c33be57SNicolas Pitre * Use our own MPIDR accessors as the generic ones in asm/cputype.h have 31*1c33be57SNicolas Pitre * __attribute_const__ and we don't want the compiler to assume any 32*1c33be57SNicolas Pitre * constness here as the value _does_ change along some code paths. 33*1c33be57SNicolas Pitre */ 34*1c33be57SNicolas Pitre 35*1c33be57SNicolas Pitre static int read_mpidr(void) 36*1c33be57SNicolas Pitre { 37*1c33be57SNicolas Pitre unsigned int id; 38*1c33be57SNicolas Pitre asm volatile ("mrc p15, 0, %0, c0, c0, 5" : "=r" (id)); 39*1c33be57SNicolas Pitre return id & MPIDR_HWID_BITMASK; 40*1c33be57SNicolas Pitre } 41*1c33be57SNicolas Pitre 42*1c33be57SNicolas Pitre /* 43*1c33be57SNicolas Pitre * bL switcher core code. 44*1c33be57SNicolas Pitre */ 45*1c33be57SNicolas Pitre 46*1c33be57SNicolas Pitre static void bL_do_switch(void *_unused) 47*1c33be57SNicolas Pitre { 48*1c33be57SNicolas Pitre unsigned mpidr, cpuid, clusterid, ob_cluster, ib_cluster; 49*1c33be57SNicolas Pitre 50*1c33be57SNicolas Pitre /* 51*1c33be57SNicolas Pitre * We now have a piece of stack borrowed from the init task's. 52*1c33be57SNicolas Pitre * Let's also switch to init_mm right away to match it. 53*1c33be57SNicolas Pitre */ 54*1c33be57SNicolas Pitre cpu_switch_mm(init_mm.pgd, &init_mm); 55*1c33be57SNicolas Pitre 56*1c33be57SNicolas Pitre pr_debug("%s\n", __func__); 57*1c33be57SNicolas Pitre 58*1c33be57SNicolas Pitre mpidr = read_mpidr(); 59*1c33be57SNicolas Pitre cpuid = MPIDR_AFFINITY_LEVEL(mpidr, 0); 60*1c33be57SNicolas Pitre clusterid = MPIDR_AFFINITY_LEVEL(mpidr, 1); 61*1c33be57SNicolas Pitre ob_cluster = clusterid; 62*1c33be57SNicolas Pitre ib_cluster = clusterid ^ 1; 63*1c33be57SNicolas Pitre 64*1c33be57SNicolas Pitre /* 65*1c33be57SNicolas Pitre * Our state has been saved at this point. Let's release our 66*1c33be57SNicolas Pitre * inbound CPU. 67*1c33be57SNicolas Pitre */ 68*1c33be57SNicolas Pitre mcpm_set_entry_vector(cpuid, ib_cluster, cpu_resume); 69*1c33be57SNicolas Pitre sev(); 70*1c33be57SNicolas Pitre 71*1c33be57SNicolas Pitre /* 72*1c33be57SNicolas Pitre * From this point, we must assume that our counterpart CPU might 73*1c33be57SNicolas Pitre * have taken over in its parallel world already, as if execution 74*1c33be57SNicolas Pitre * just returned from cpu_suspend(). It is therefore important to 75*1c33be57SNicolas Pitre * be very careful not to make any change the other guy is not 76*1c33be57SNicolas Pitre * expecting. This is why we need stack isolation. 77*1c33be57SNicolas Pitre * 78*1c33be57SNicolas Pitre * Fancy under cover tasks could be performed here. For now 79*1c33be57SNicolas Pitre * we have none. 80*1c33be57SNicolas Pitre */ 81*1c33be57SNicolas Pitre 82*1c33be57SNicolas Pitre /* Let's put ourself down. */ 83*1c33be57SNicolas Pitre mcpm_cpu_power_down(); 84*1c33be57SNicolas Pitre 85*1c33be57SNicolas Pitre /* should never get here */ 86*1c33be57SNicolas Pitre BUG(); 87*1c33be57SNicolas Pitre } 88*1c33be57SNicolas Pitre 89*1c33be57SNicolas Pitre /* 90*1c33be57SNicolas Pitre * Stack isolation. To ensure 'current' remains valid, we just borrow 91*1c33be57SNicolas Pitre * a slice of the init/idle task which should be fairly lightly used. 92*1c33be57SNicolas Pitre * The borrowed area starts just above the thread_info structure located 93*1c33be57SNicolas Pitre * at the very bottom of the stack, aligned to a cache line. 94*1c33be57SNicolas Pitre */ 95*1c33be57SNicolas Pitre #define STACK_SIZE 256 96*1c33be57SNicolas Pitre extern void call_with_stack(void (*fn)(void *), void *arg, void *sp); 97*1c33be57SNicolas Pitre static int bL_switchpoint(unsigned long _arg) 98*1c33be57SNicolas Pitre { 99*1c33be57SNicolas Pitre unsigned int mpidr = read_mpidr(); 100*1c33be57SNicolas Pitre unsigned int cpuid = MPIDR_AFFINITY_LEVEL(mpidr, 0); 101*1c33be57SNicolas Pitre unsigned int clusterid = MPIDR_AFFINITY_LEVEL(mpidr, 1); 102*1c33be57SNicolas Pitre unsigned int cpu_index = cpuid + clusterid * MAX_CPUS_PER_CLUSTER; 103*1c33be57SNicolas Pitre void *stack = &init_thread_info + 1; 104*1c33be57SNicolas Pitre stack = PTR_ALIGN(stack, L1_CACHE_BYTES); 105*1c33be57SNicolas Pitre stack += cpu_index * STACK_SIZE + STACK_SIZE; 106*1c33be57SNicolas Pitre call_with_stack(bL_do_switch, (void *)_arg, stack); 107*1c33be57SNicolas Pitre BUG(); 108*1c33be57SNicolas Pitre } 109*1c33be57SNicolas Pitre 110*1c33be57SNicolas Pitre /* 111*1c33be57SNicolas Pitre * Generic switcher interface 112*1c33be57SNicolas Pitre */ 113*1c33be57SNicolas Pitre 114*1c33be57SNicolas Pitre /* 115*1c33be57SNicolas Pitre * bL_switch_to - Switch to a specific cluster for the current CPU 116*1c33be57SNicolas Pitre * @new_cluster_id: the ID of the cluster to switch to. 117*1c33be57SNicolas Pitre * 118*1c33be57SNicolas Pitre * This function must be called on the CPU to be switched. 119*1c33be57SNicolas Pitre * Returns 0 on success, else a negative status code. 120*1c33be57SNicolas Pitre */ 121*1c33be57SNicolas Pitre static int bL_switch_to(unsigned int new_cluster_id) 122*1c33be57SNicolas Pitre { 123*1c33be57SNicolas Pitre unsigned int mpidr, cpuid, clusterid, ob_cluster, ib_cluster, this_cpu; 124*1c33be57SNicolas Pitre int ret; 125*1c33be57SNicolas Pitre 126*1c33be57SNicolas Pitre mpidr = read_mpidr(); 127*1c33be57SNicolas Pitre cpuid = MPIDR_AFFINITY_LEVEL(mpidr, 0); 128*1c33be57SNicolas Pitre clusterid = MPIDR_AFFINITY_LEVEL(mpidr, 1); 129*1c33be57SNicolas Pitre ob_cluster = clusterid; 130*1c33be57SNicolas Pitre ib_cluster = clusterid ^ 1; 131*1c33be57SNicolas Pitre 132*1c33be57SNicolas Pitre if (new_cluster_id == clusterid) 133*1c33be57SNicolas Pitre return 0; 134*1c33be57SNicolas Pitre 135*1c33be57SNicolas Pitre pr_debug("before switch: CPU %d in cluster %d\n", cpuid, clusterid); 136*1c33be57SNicolas Pitre 137*1c33be57SNicolas Pitre /* Close the gate for our entry vectors */ 138*1c33be57SNicolas Pitre mcpm_set_entry_vector(cpuid, ob_cluster, NULL); 139*1c33be57SNicolas Pitre mcpm_set_entry_vector(cpuid, ib_cluster, NULL); 140*1c33be57SNicolas Pitre 141*1c33be57SNicolas Pitre /* 142*1c33be57SNicolas Pitre * Let's wake up the inbound CPU now in case it requires some delay 143*1c33be57SNicolas Pitre * to come online, but leave it gated in our entry vector code. 144*1c33be57SNicolas Pitre */ 145*1c33be57SNicolas Pitre ret = mcpm_cpu_power_up(cpuid, ib_cluster); 146*1c33be57SNicolas Pitre if (ret) { 147*1c33be57SNicolas Pitre pr_err("%s: mcpm_cpu_power_up() returned %d\n", __func__, ret); 148*1c33be57SNicolas Pitre return ret; 149*1c33be57SNicolas Pitre } 150*1c33be57SNicolas Pitre 151*1c33be57SNicolas Pitre /* 152*1c33be57SNicolas Pitre * From this point we are entering the switch critical zone 153*1c33be57SNicolas Pitre * and can't take any interrupts anymore. 154*1c33be57SNicolas Pitre */ 155*1c33be57SNicolas Pitre local_irq_disable(); 156*1c33be57SNicolas Pitre local_fiq_disable(); 157*1c33be57SNicolas Pitre 158*1c33be57SNicolas Pitre this_cpu = smp_processor_id(); 159*1c33be57SNicolas Pitre 160*1c33be57SNicolas Pitre /* redirect GIC's SGIs to our counterpart */ 161*1c33be57SNicolas Pitre gic_migrate_target(cpuid + ib_cluster*4); 162*1c33be57SNicolas Pitre 163*1c33be57SNicolas Pitre /* 164*1c33be57SNicolas Pitre * Raise a SGI on the inbound CPU to make sure it doesn't stall 165*1c33be57SNicolas Pitre * in a possible WFI, such as in mcpm_power_down(). 166*1c33be57SNicolas Pitre */ 167*1c33be57SNicolas Pitre arch_send_wakeup_ipi_mask(cpumask_of(this_cpu)); 168*1c33be57SNicolas Pitre 169*1c33be57SNicolas Pitre ret = cpu_pm_enter(); 170*1c33be57SNicolas Pitre 171*1c33be57SNicolas Pitre /* we can not tolerate errors at this point */ 172*1c33be57SNicolas Pitre if (ret) 173*1c33be57SNicolas Pitre panic("%s: cpu_pm_enter() returned %d\n", __func__, ret); 174*1c33be57SNicolas Pitre 175*1c33be57SNicolas Pitre /* Flip the cluster in the CPU logical map for this CPU. */ 176*1c33be57SNicolas Pitre cpu_logical_map(this_cpu) ^= (1 << 8); 177*1c33be57SNicolas Pitre 178*1c33be57SNicolas Pitre /* Let's do the actual CPU switch. */ 179*1c33be57SNicolas Pitre ret = cpu_suspend(0, bL_switchpoint); 180*1c33be57SNicolas Pitre if (ret > 0) 181*1c33be57SNicolas Pitre panic("%s: cpu_suspend() returned %d\n", __func__, ret); 182*1c33be57SNicolas Pitre 183*1c33be57SNicolas Pitre /* We are executing on the inbound CPU at this point */ 184*1c33be57SNicolas Pitre mpidr = read_mpidr(); 185*1c33be57SNicolas Pitre cpuid = MPIDR_AFFINITY_LEVEL(mpidr, 0); 186*1c33be57SNicolas Pitre clusterid = MPIDR_AFFINITY_LEVEL(mpidr, 1); 187*1c33be57SNicolas Pitre pr_debug("after switch: CPU %d in cluster %d\n", cpuid, clusterid); 188*1c33be57SNicolas Pitre BUG_ON(clusterid != ib_cluster); 189*1c33be57SNicolas Pitre 190*1c33be57SNicolas Pitre mcpm_cpu_powered_up(); 191*1c33be57SNicolas Pitre 192*1c33be57SNicolas Pitre ret = cpu_pm_exit(); 193*1c33be57SNicolas Pitre 194*1c33be57SNicolas Pitre local_fiq_enable(); 195*1c33be57SNicolas Pitre local_irq_enable(); 196*1c33be57SNicolas Pitre 197*1c33be57SNicolas Pitre if (ret) 198*1c33be57SNicolas Pitre pr_err("%s exiting with error %d\n", __func__, ret); 199*1c33be57SNicolas Pitre return ret; 200*1c33be57SNicolas Pitre } 201*1c33be57SNicolas Pitre 202*1c33be57SNicolas Pitre struct switch_args { 203*1c33be57SNicolas Pitre unsigned int cluster; 204*1c33be57SNicolas Pitre struct work_struct work; 205*1c33be57SNicolas Pitre }; 206*1c33be57SNicolas Pitre 207*1c33be57SNicolas Pitre static void __bL_switch_to(struct work_struct *work) 208*1c33be57SNicolas Pitre { 209*1c33be57SNicolas Pitre struct switch_args *args = container_of(work, struct switch_args, work); 210*1c33be57SNicolas Pitre bL_switch_to(args->cluster); 211*1c33be57SNicolas Pitre } 212*1c33be57SNicolas Pitre 213*1c33be57SNicolas Pitre /* 214*1c33be57SNicolas Pitre * bL_switch_request - Switch to a specific cluster for the given CPU 215*1c33be57SNicolas Pitre * 216*1c33be57SNicolas Pitre * @cpu: the CPU to switch 217*1c33be57SNicolas Pitre * @new_cluster_id: the ID of the cluster to switch to. 218*1c33be57SNicolas Pitre * 219*1c33be57SNicolas Pitre * This function causes a cluster switch on the given CPU. If the given 220*1c33be57SNicolas Pitre * CPU is the same as the calling CPU then the switch happens right away. 221*1c33be57SNicolas Pitre * Otherwise the request is put on a work queue to be scheduled on the 222*1c33be57SNicolas Pitre * remote CPU. 223*1c33be57SNicolas Pitre */ 224*1c33be57SNicolas Pitre void bL_switch_request(unsigned int cpu, unsigned int new_cluster_id) 225*1c33be57SNicolas Pitre { 226*1c33be57SNicolas Pitre unsigned int this_cpu = get_cpu(); 227*1c33be57SNicolas Pitre struct switch_args args; 228*1c33be57SNicolas Pitre 229*1c33be57SNicolas Pitre if (cpu == this_cpu) { 230*1c33be57SNicolas Pitre bL_switch_to(new_cluster_id); 231*1c33be57SNicolas Pitre put_cpu(); 232*1c33be57SNicolas Pitre return; 233*1c33be57SNicolas Pitre } 234*1c33be57SNicolas Pitre put_cpu(); 235*1c33be57SNicolas Pitre 236*1c33be57SNicolas Pitre args.cluster = new_cluster_id; 237*1c33be57SNicolas Pitre INIT_WORK_ONSTACK(&args.work, __bL_switch_to); 238*1c33be57SNicolas Pitre schedule_work_on(cpu, &args.work); 239*1c33be57SNicolas Pitre flush_work(&args.work); 240*1c33be57SNicolas Pitre } 241*1c33be57SNicolas Pitre EXPORT_SYMBOL_GPL(bL_switch_request); 242