/* * Copyright (C) 2005 Vivien Chappelier -- Inventel/Thomson * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #define DEBUG #undef VDEBUG #ifdef DEBUG #define dbg printk #else #define dbg(...) do { } while(0); #endif #ifdef VDEBUG #define vdbg printk #else #define vdbg(...) do { } while(0); #endif extern void show_registers(struct pt_regs *regs); extern void show_regs(struct pt_regs *regs); #define TIMEOUT (HZ/4) /* time to wait for the other core to boot */ /* CMT bits */ #define BRCM_CONFIG0_CMT (1 << 18) /* concurrent multi-threading support */ #define BRCM_CONFIG0_SIC (1 << 12) /* split I-cache for each thread */ #define CMT_INT_XIR_IP4 (1 << 31) /* external interrupt 4 routing */ #define CMT_INT_XIR_IP3 (1 << 30) /* external interrupt 3 routing */ #define CMT_INT_XIR_IP2 (1 << 29) /* external interrupt 2 routing */ #define CMT_INT_XIR_IP1 (1 << 28) /* external interrupt 1 routing */ #define CMT_INT_XIR_IP0 (1 << 27) /* external interrupt 0 routing */ #define CMT_INT_SIR_IP1 (1 << 16) /* software interrupt 1 routing */ #define CMT_INT_SIR_IP0 (1 << 15) /* software interrupt 0 routing */ #define CMT_INT_NMI_MASK 3 #define CMT_INT_NMI_T0 1 /* NMI interrupt routing to thread 0 */ #define CMT_INT_NMI_T1 2 /* NMI interrupt routing to thread 1 */ #define CMT_PRIO_TP1 (1 << 5) /* give exception priority to thread 1 */ #define CMT_PRIO_TP0 (1 << 4) /* give exception priority to thread 0 */ #define CMT_RSTSE (1 << 0) /* thread 1 reset */ #define CMT_LOCAL_TID (1 << 31) /* thread identifier */ /* * Return the thread ID where this code is executed */ int brcm_cmt_id(void) { unsigned int local; local = read_c0_brcm_cmt_local(); return((local & CMT_LOCAL_TID) != 0); } /* * SMP init and finish on secondary CPUs */ void bcm6358_smp_info(void) { unsigned int config; unsigned int introuting; config = read_c0_brcm_config0(); if(!(config & BRCM_CONFIG0_CMT)) { printk("SMP not supported on this processor\n"); return; } if(config & BRCM_CONFIG0_SIC) printk("Multicore CPU with split I-cache\n"); else printk("Multicore CPU with shared I-cache\n"); introuting = read_c0_brcm_cmt_int(); printk("Interrupt routing:\n"); if(introuting & CMT_INT_XIR_IP4) printk(" IP4: set A to T1, set B to T0\n"); else printk(" IP4: set A to T0, set B to T1\n"); if(introuting & CMT_INT_XIR_IP3) printk(" IP3: set A to T1, set B to T0\n"); else printk(" IP3: set A to T0, set B to T1\n"); if(introuting & CMT_INT_XIR_IP2) printk(" IP2: set A to T1, set B to T0\n"); else printk(" IP2: set A to T0, set B to T1\n"); if(introuting & CMT_INT_XIR_IP1) printk(" IP1: set A to T1, set B to T0\n"); else printk(" IP1: set A to T0, set B to T1\n"); if(introuting & CMT_INT_XIR_IP0) printk(" IP0: set A to T1, set B to T0\n"); else printk(" IP0: set A to T0, set B to T1\n"); if(introuting & CMT_INT_SIR_IP1) printk(" SOFT1: set A to T1, set B to T0\n"); else printk(" SOFT1: set A to T0, set B to T1\n"); if(introuting & CMT_INT_SIR_IP0) printk(" SOFT0: set A to T1, set B to T0\n"); else printk(" SOFT0: set A to T0, set B to T1\n"); if((introuting & CMT_INT_NMI_MASK) == 1) printk(" NMI routed to thread 0\n"); else printk(" NMI routed to thread 1\n"); } /* This mailbox contains the ipi message. It is fast as the CPU cores share the same data cache. */ struct letter { unsigned int action; spinlock_t lock; }; static struct letter mailbox[NR_CPUS]; void core_send_ipi(int cpu, unsigned int action) { unsigned int softint; unsigned long flags; vdbg("core_send_ipi cpu=%d action=0x%08x\n", cpu, action); if (cpu) softint = CAUSEF_IP1; else softint = CAUSEF_IP0; spin_lock_irqsave(&mailbox[cpu].lock, flags); mailbox[cpu].action |= action; set_c0_cause(softint); spin_unlock_irqrestore(&mailbox[cpu].lock, flags); } /* Schedule a tasklet on the other core */ static DEFINE_SPINLOCK(smp_tasklet_lock); struct tasklet_struct *smp_tasklet; void smp_tasklet_hi_schedule(int cpu, struct tasklet_struct *tasklet) { spin_lock(&smp_tasklet_lock); smp_tasklet = tasklet; core_send_ipi(cpu, SMP_SCHEDULE_TASKLET); while (spin_is_locked(&smp_tasklet_lock)) core_send_ipi(cpu, 0); } extern void local_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs); static irqreturn_t bcm6358_mailbox_interrupt(int irq, void *dev_id, struct pt_regs *regs) { int cpu = smp_processor_id(); unsigned int action; unsigned int softint; if (cpu) softint = CAUSEF_IP1; else softint = CAUSEF_IP0; /* Open the mailbox and figure out what we're supposed to do */ spin_lock(&mailbox[cpu].lock); action = mailbox[cpu].action; mailbox[cpu].action = 0; clear_c0_cause(softint); spin_unlock(&mailbox[cpu].lock); vdbg("ipi on T%d, cpu %d, action = 0x%08x\n", brcm_cmt_id(), cpu, action); /* * Nothing to do for SMP_RESCHEDULE_YOURSELF; * returning from the interrupt will do the reschedule for us */ if (action & SMP_CALL_FUNCTION) smp_call_function_interrupt(); /* The second timer interrupt is simulated using an ipi */ if (action & SMP_TIMER_INTERRUPT) local_timer_interrupt(irq, NULL, regs); /* Allows to schedule a tasklet on a different core */ if (action & SMP_SCHEDULE_TASKLET) { tasklet_hi_schedule(smp_tasklet); spin_unlock(&smp_tasklet_lock); } #ifdef DEBUG /* Dump stack frame for debugging */ if (action & SMP_DUMP_STACK) { if(user_mode(regs)) show_regs(regs); else show_registers(regs); } #endif return(IRQ_HANDLED); } /* * Common setup before any secondaries are started */ void __init prom_prepare_cpus(unsigned int max_cpus) { int cpu; unsigned int config; /* set up the primary cpu info */ cpus_clear(phys_cpu_present_map); cpu_set(0, phys_cpu_present_map); cpu_set(0, cpu_online_map); __cpu_number_map[0] = 0; __cpu_logical_map[0] = 0; config = read_c0_brcm_config0(); /* check for a secondary cpu */ if(config & BRCM_CONFIG0_CMT) { cpu_set(1, phys_cpu_present_map); __cpu_logical_map[1] = 0; /* same physical CPU */ } else return; /* initialize the mailboxes */ for(cpu = 0; cpu < NR_CPUS; cpu++) { spin_lock_init(&mailbox[cpu].lock); mailbox[cpu].action = 0; } /* register ipi interrupt for current processor */ if(request_irq(INTERRUPT_ID_SOFTWARE_0, bcm6358_mailbox_interrupt, SA_INTERRUPT, "cpu0soft", 0)) { printk("Cannot register ipi interrupt\n"); return; } /* register ipi interrupt for second processor */ if(request_irq(INTERRUPT_ID_SOFTWARE_1, bcm6358_mailbox_interrupt, SA_SAMPLE_RANDOM | SA_INTERRUPT, "cpu1soft", 0)) { printk("Cannot register ipi interrupt\n"); return; } } /* * Setup the PC, SP, and GP of a secondary processor and start it running: * * The secondary core is reset and starts running at 0xbfc00000. The bootloader * detects it is running on the secondary core and does the following instead * of running the normal boot code: * - flush the I-cache * - enable software interrupt 1 * - route software interrupt 0 and 1 across cores * - wait for interrupt * In the meantime, the primary core polls the common interrupt routing * register until the software interrupts are crossed, meaning the secondary * core is ready to receive interrupts. It then stores the values of the * desired PC, SP and GP in the cmt_bootstrap_* variables. There is no need to * flush the D-cache here as it is shared between the cores. The interrupt * vector is then changed to cmt_bootstrap_IRQ (which includes flushing the * corresponding entry in the primary core I-cache) and an interrupt is sent * to the secondary core. The secondary core takes the interrupt, jumping to * cmt_bootstrap_IRQ, which loads SP and GP from the cmt_bootstrap_* variables * and stores cmt_bootstrap_pc to the CP0 EPC register. It finally returns from * interrupt to the desired PC. */ extern asmlinkage void cmt_bootstrap_IRQ(void); unsigned long cmt_bootstrap_sp; unsigned long cmt_bootstrap_gp; unsigned long cmt_bootstrap_pc; extern asmlinkage void invtlIRQ(void); DEFINE_SPINLOCK(cmt_bootstrap_lock); void prom_boot_secondary(int cpu, struct task_struct *idle) { unsigned int ctrl; unsigned int flags; unsigned long timeout = TIMEOUT; dbg("booting secondary core\n"); /* take thread 1 out of reset */ ctrl = read_c0_brcm_cmt_ctrl(); write_c0_brcm_cmt_ctrl(ctrl | CMT_RSTSE | CMT_PRIO_TP0); dbg("secondary core reset\n"); /* wait for thread 1 to be ready to receive interrupts */ timeout += jiffies; while (time_before(jiffies, timeout) && !(read_c0_brcm_cmt_int() & CMT_INT_SIR_IP1)); if (time_after_eq(jiffies, timeout)) { printk(KERN_ERR "secondary CPU did not respond!\n"); return; } dbg("secondary core ready\n"); /* store the desired values for the secondary processor registers */ cmt_bootstrap_pc = (unsigned long)&smp_bootstrap; cmt_bootstrap_sp = __KSTK_TOS(idle); cmt_bootstrap_gp = (unsigned long)idle->thread_info; /* install the bootstrap interrupt handler */ local_irq_save(flags); set_except_vector(0, cmt_bootstrap_IRQ); /* send an ipi to thread 1 */ spin_lock(&cmt_bootstrap_lock); set_c0_cause(CAUSEF_IP1); /* wait until the interrupt is taken by thread 1 */ while(spin_is_locked(&cmt_bootstrap_lock)) mb(); /* restore the normal irq handler */ set_except_vector(0, invtlIRQ); local_irq_restore(flags); } /* * Code to run on secondary just after probing the CPU */ extern void __init brcm_irq_setup(void); void prom_init_secondary(void) { spin_unlock(&cmt_bootstrap_lock); /* install the interrupt exception vector */ /* this is needed to ensure the bootstrap vector */ /* is not stalling in the I-cache of TP1 */ brcm_irq_setup(); } /* * Do any tidying up before marking online and running the idle * loop */ void prom_smp_finish(void) { local_irq_enable(); } /* * Final cleanup after all secondaries booted */ void prom_cpus_done(void) { bcm6358_smp_info(); } /* * EBI access locking * When one thread accesses the EBI bus while the other is doing a memory * read, there is a hazard that the EBI access will be done twice. Therefore * we must lock the other core out of the way when doing EBI accesses that * are sensitive to multiple read/writes, such as flash accesses. * The other core is caught by an ipi and locked waiting for the EBI lock. * We use the NMI routing in the shared CMT interrupt routing register as the * lock to ensure no memory access is made on the other core while waiting * for the lock. Any other common CP0 R/W location would do the trick. */ DEFINE_SPINLOCK(bus_lock); void ebi_lock_ipi(void *info) { int introuting; introuting = read_c0_brcm_cmt_int(); introuting &= ~CMT_INT_NMI_MASK; introuting |= CMT_INT_NMI_T1; write_c0_brcm_cmt_int(introuting); while ((read_c0_brcm_cmt_int() & 3) == 2); } void ebi_lock(void) { if (num_online_cpus() < 2) return; spin_lock(&bus_lock); while((read_c0_brcm_cmt_int() & 3) == 2); smp_call_function(ebi_lock_ipi, NULL, 0, 0); while((read_c0_brcm_cmt_int() & 3) == 1); } void ebi_unlock(void) { int introuting; if (num_online_cpus() < 2) return; introuting = read_c0_brcm_cmt_int(); introuting &= ~CMT_INT_NMI_MASK; introuting |= CMT_INT_NMI_T0; write_c0_brcm_cmt_int(introuting); spin_unlock(&bus_lock); } EXPORT_SYMBOL(smp_tasklet_hi_schedule);