/* * * * SMP support for ppc. * * Written by Cort Dougan (cort@cs.nmt.edu) borrowing a great * deal of code from the sparc and intel versions. * * Copyright (C) 1999 Cort Dougan <cort@cs.nmt.edu> * * PowerPC-64 Support added by Dave Engebretsen, Peter Bergner, and * Mike Corrigan {engebret|bergner|mikec}@us.ibm.com * * 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. */ #include <linux/config.h> #include <linux/kernel.h> #include <linux/sched.h> #include <linux/smp.h> #include <linux/smp_lock.h> #include <linux/interrupt.h> #include <linux/kernel_stat.h> #include <linux/delay.h> #include <linux/init.h> #include <linux/spinlock.h> #include <linux/cache.h> #include <linux/err.h> #include <asm/ptrace.h> #include <asm/atomic.h> #include <asm/irq.h> #include <asm/page.h> #include <asm/pgtable.h> #include <asm/hardirq.h> #include <asm/softirq.h> #include <asm/io.h> #include <asm/prom.h> #include <asm/smp.h> #include <asm/naca.h> #include <asm/paca.h> #include <asm/iSeries/LparData.h> #include <asm/iSeries/HvCall.h> #include <asm/iSeries/HvCallCfg.h> #include <asm/time.h> #include <asm/ppcdebug.h> #include "open_pic.h" #include <asm/machdep.h> int smp_threads_ready = 0; spinlock_t kernel_flag __cacheline_aligned = SPIN_LOCK_UNLOCKED; unsigned long cache_decay_ticks; /* initialised so it doesnt end up in bss */ unsigned long cpu_online_map = 0; int boot_cpuid = 0; static struct smp_ops_t *smp_ops; volatile unsigned long cpu_callin_map[NR_CPUS]; extern unsigned char stab_array[]; extern int cpu_idle(void *unused); void smp_call_function_interrupt(void); void smp_message_pass(int target, int msg, unsigned long data, int wait); static unsigned long iSeries_smp_message[NR_CPUS]; void xics_setup_cpu(void); void xics_cause_IPI(int cpu); /* * XICS only has a single IPI, so encode the messages per CPU */ struct xics_ipi_struct { volatile unsigned long value; } ____cacheline_aligned; struct xics_ipi_struct xics_ipi_message[NR_CPUS] __cacheline_aligned; #define smp_message_pass(t,m,d,w) smp_ops->message_pass((t),(m),(d),(w)) static inline void set_tb(unsigned int upper, unsigned int lower) { mtspr(SPRN_TBWL, 0); mtspr(SPRN_TBWU, upper); mtspr(SPRN_TBWL, lower); } void iSeries_smp_message_recv( struct pt_regs * regs ) { int cpu = smp_processor_id(); int msg; if ( num_online_cpus() < 2 ) return; for ( msg = 0; msg < 4; ++msg ) if ( test_and_clear_bit( msg, &iSeries_smp_message[cpu] ) ) smp_message_recv( msg, regs ); } static void smp_iSeries_message_pass(int target, int msg, unsigned long data, int wait) { int i; for (i = 0; i < NR_CPUS; ++i) { if (!cpu_online(i)) continue; if ((target == MSG_ALL) || (target == i) || ((target == MSG_ALL_BUT_SELF) && (i != smp_processor_id())) ) { set_bit(msg, &iSeries_smp_message[i]); HvCall_sendIPI(&(paca[i])); } } } #ifdef CONFIG_PPC_ISERIES static int smp_iSeries_numProcs(void) { unsigned np, i; struct ItLpPaca * lpPaca; np = 0; for (i=0; i < MAX_PACAS; ++i) { lpPaca = paca[i].xLpPacaPtr; if ( lpPaca->xDynProcStatus < 2 ) { ++np; } } return np; } #endif static int smp_iSeries_probe(void) { unsigned i; unsigned np = 0; struct ItLpPaca *lpPaca; for (i=0; i < MAX_PACAS; ++i) { lpPaca = paca[i].xLpPacaPtr; if (lpPaca->xDynProcStatus < 2) { paca[i].active = 1; ++np; } } return np; } static void smp_iSeries_kick_cpu(int nr) { struct ItLpPaca * lpPaca; /* Verify we have a Paca for processor nr */ if ( ( nr <= 0 ) || ( nr >= MAX_PACAS ) ) return; /* Verify that our partition has a processor nr */ lpPaca = paca[nr].xLpPacaPtr; if ( lpPaca->xDynProcStatus >= 2 ) return; /* The information for processor bringup must * be written out to main store before we release * the processor. */ mb(); /* The processor is currently spinning, waiting * for the xProcStart field to become non-zero * After we set xProcStart, the processor will * continue on to secondary_start in iSeries_head.S */ paca[nr].xProcStart = 1; } static void smp_iSeries_setup_cpu(int nr) { } /* This is called very early. */ void smp_init_iSeries(void) { smp_ops = &ppc_md.smp_ops; smp_ops->message_pass = smp_iSeries_message_pass; smp_ops->probe = smp_iSeries_probe; smp_ops->kick_cpu = smp_iSeries_kick_cpu; smp_ops->setup_cpu = smp_iSeries_setup_cpu; #ifdef CONFIG_PPC_ISERIES #warning fix for iseries naca->processorCount = smp_iSeries_numProcs(); #endif } static void smp_openpic_message_pass(int target, int msg, unsigned long data, int wait) { /* make sure we're sending something that translates to an IPI */ if ( msg > 0x3 ){ printk("SMP %d: smp_message_pass: unknown msg %d\n", smp_processor_id(), msg); return; } switch ( target ) { case MSG_ALL: openpic_cause_IPI(msg, 0xffffffff); break; case MSG_ALL_BUT_SELF: openpic_cause_IPI(msg, 0xffffffff & ~(1 << smp_processor_id())); break; default: openpic_cause_IPI(msg, 1<<target); break; } } static int smp_chrp_probe(void) { int i; int nr_cpus = 0; for (i = 0; i < NR_CPUS; i++) { if (cpu_possible(i)) nr_cpus++; } if (nr_cpus > 1) openpic_request_IPIs(); return nr_cpus; } static void smp_kick_cpu(int nr) { /* Verify we have a Paca for processor nr */ if ( ( nr <= 0 ) || ( nr >= MAX_PACAS ) ) return; /* The information for processor bringup must * be written out to main store before we release * the processor. */ mb(); /* The processor is currently spinning, waiting * for the xProcStart field to become non-zero * After we set xProcStart, the processor will * continue on to secondary_start */ paca[nr].xProcStart = 1; } static void smp_space_timers(unsigned int max_cpus) { int i; unsigned long offset = tb_ticks_per_jiffy / max_cpus; unsigned long previous_tb = paca[boot_cpuid].next_jiffy_update_tb; for (i = 0; i < NR_CPUS; i++) { if (cpu_possible(i) && i != boot_cpuid) { paca[i].next_jiffy_update_tb = previous_tb + offset; previous_tb = paca[i].next_jiffy_update_tb; } } } static void __devinit pSeries_setup_cpu(int cpu) { if (OpenPIC_Addr) { do_openpic_setup_cpu(); } else { if (cpu != boot_cpuid) xics_setup_cpu(); } } static void smp_xics_message_pass(int target, int msg, unsigned long data, int wait) { int i; for (i = 0; i < NR_CPUS; ++i) { if (!cpu_online(i)) continue; if (target == MSG_ALL || target == i || (target == MSG_ALL_BUT_SELF && i != smp_processor_id())) { set_bit(msg, &xics_ipi_message[i].value); mb(); xics_cause_IPI(i); } } } static int smp_xics_probe(void) { int i; int nr_cpus = 0; for (i = 0; i < NR_CPUS; i++) { if (cpu_possible(i)) nr_cpus++; } return nr_cpus; } static spinlock_t timebase_lock = SPIN_LOCK_UNLOCKED; static unsigned long timebase = 0; static void __devinit pSeries_give_timebase(void) { spin_lock(&timebase_lock); rtas_call(rtas_token("freeze-time-base"), 0, 1, NULL); timebase = get_tb(); spin_unlock(&timebase_lock); while (timebase) barrier(); rtas_call(rtas_token("thaw-time-base"), 0, 1, NULL); } static void __devinit pSeries_take_timebase(void) { while (!timebase) barrier(); spin_lock(&timebase_lock); set_tb(timebase, timebase >> 32); timebase = 0; spin_unlock(&timebase_lock); } /* This is called very early */ void __init smp_init_pSeries(void) { smp_ops = &ppc_md.smp_ops; if (naca->interrupt_controller == IC_OPEN_PIC) { smp_ops->message_pass = smp_openpic_message_pass; smp_ops->probe = smp_chrp_probe; } else { smp_ops->message_pass = smp_xics_message_pass; smp_ops->probe = smp_xics_probe; } if (naca->platform == PLATFORM_PSERIES) { smp_ops->give_timebase = pSeries_give_timebase; smp_ops->take_timebase = pSeries_take_timebase; } smp_ops->kick_cpu = smp_kick_cpu; smp_ops->setup_cpu = pSeries_setup_cpu; } void smp_local_timer_interrupt(struct pt_regs * regs) { if (!--(get_paca()->prof_counter)) { update_process_times(user_mode(regs)); (get_paca()->prof_counter)=get_paca()->prof_multiplier; } } void smp_message_recv(int msg, struct pt_regs *regs) { switch( msg ) { case PPC_MSG_CALL_FUNCTION: smp_call_function_interrupt(); break; case PPC_MSG_RESCHEDULE: /* XXX Do we have to do this? */ set_need_resched(); break; #if 0 case PPC_MSG_MIGRATE_TASK: /* spare */ break; #endif #ifdef CONFIG_XMON case PPC_MSG_XMON_BREAK: xmon(regs); break; #endif /* CONFIG_XMON */ default: printk("SMP %d: smp_message_recv(): unknown msg %d\n", smp_processor_id(), msg); break; } } void smp_send_reschedule(int cpu) { smp_message_pass(cpu, PPC_MSG_RESCHEDULE, 0, 0); } /* * this function sends a reschedule IPI to all (other) CPUs. * This should only be used if some 'global' task became runnable, * such as a RT task, that must be handled now. The first CPU * that manages to grab the task will run it. */ void smp_send_reschedule_all(void) { smp_message_pass(MSG_ALL_BUT_SELF, PPC_MSG_RESCHEDULE, 0, 0); } #ifdef CONFIG_XMON void smp_send_xmon_break(int cpu) { smp_message_pass(cpu, PPC_MSG_XMON_BREAK, 0, 0); } #endif /* CONFIG_XMON */ static void stop_this_cpu(void *dummy) { local_irq_disable(); while (1) ; } void smp_send_stop(void) { smp_call_function(stop_this_cpu, NULL, 1, 0); } /* * Structure and data for smp_call_function(). This is designed to minimise * static memory requirements. It also looks cleaner. * Stolen from the i386 version. */ static spinlock_t call_lock __cacheline_aligned_in_smp = SPIN_LOCK_UNLOCKED; static struct call_data_struct { void (*func) (void *info); void *info; atomic_t started; atomic_t finished; int wait; } *call_data; /* * This function sends a 'generic call function' IPI to all other CPUs * in the system. * * [SUMMARY] Run a function on all other CPUs. * <func> The function to run. This must be fast and non-blocking. * <info> An arbitrary pointer to pass to the function. * <nonatomic> currently unused. * <wait> If true, wait (atomically) until function has completed on other CPUs. * [RETURNS] 0 on success, else a negative status code. Does not return until * remote CPUs are nearly ready to execute <<func>> or are or have executed. * * You must not call this function with disabled interrupts or from a * hardware interrupt handler or from a bottom half handler. */ int smp_call_function (void (*func) (void *info), void *info, int nonatomic, int wait) { struct call_data_struct data; int ret = -1, cpus = num_online_cpus()-1; int timeout; if (!cpus) return 0; data.func = func; data.info = info; atomic_set(&data.started, 0); data.wait = wait; if (wait) atomic_set(&data.finished, 0); spin_lock(&call_lock); call_data = &data; /* Send a message to all other CPUs and wait for them to respond */ smp_message_pass(MSG_ALL_BUT_SELF, PPC_MSG_CALL_FUNCTION, 0, 0); /* Wait for response */ timeout = 8000000; while (atomic_read(&data.started) != cpus) { HMT_low(); if (--timeout == 0) { printk("smp_call_function on cpu %d: other cpus not responding (%d)\n", smp_processor_id(), atomic_read(&data.started)); #ifdef CONFIG_XMON xmon(0); #endif #ifdef CONFIG_PPC_ISERIES HvCall_terminateMachineSrc(); #endif goto out; } barrier(); udelay(1); } if (wait) { timeout = 1000000; while (atomic_read(&data.finished) != cpus) { HMT_low(); if (--timeout == 0) { printk("smp_call_function on cpu %d: other cpus not finishing (%d/%d)\n", smp_processor_id(), atomic_read(&data.finished), atomic_read(&data.started)); #ifdef CONFIG_PPC_ISERIES HvCall_terminateMachineSrc(); #endif goto out; } barrier(); udelay(1); } } ret = 0; out: HMT_medium(); spin_unlock(&call_lock); return ret; } void smp_call_function_interrupt(void) { void (*func) (void *info) = call_data->func; void *info = call_data->info; int wait = call_data->wait; /* * Notify initiating CPU that I've grabbed the data and am * about to execute the function */ atomic_inc(&call_data->started); /* * At this point the info structure may be out of scope unless wait==1 */ (*func)(info); if (wait) atomic_inc(&call_data->finished); } extern unsigned long decr_overclock; extern struct gettimeofday_struct do_gtod; struct thread_info *current_set[NR_CPUS]; static void __devinit smp_store_cpu_info(int id) { paca[id].pvr = _get_PVR(); } void __init smp_prepare_cpus(unsigned int max_cpus) { int i; /* Fixup boot cpu */ smp_store_cpu_info(smp_processor_id()); cpu_callin_map[smp_processor_id()] = 1; for (i = 0; i < NR_CPUS; i++) { paca[i].prof_counter = 1; paca[i].prof_multiplier = 1; if (i != boot_cpuid) { void *tmp; /* * the boot cpu segment table is statically * initialized to real address 0x5000. The * Other processor's tables are created and * initialized here. */ tmp = &stab_array[PAGE_SIZE * (i-1)]; memset(tmp, 0, PAGE_SIZE); paca[i].xStab_data.virt = (unsigned long)tmp; paca[i].xStab_data.real = (unsigned long)__v2a(tmp); paca[i].default_decr = tb_ticks_per_jiffy / decr_overclock; } } /* * XXX very rough. */ cache_decay_ticks = HZ/100; #ifndef CONFIG_PPC_ISERIES paca[boot_cpuid].next_jiffy_update_tb = tb_last_stamp = get_tb(); /* * Should update do_gtod.stamp_xsec. * For now we leave it which means the time can be some * number of msecs off until someone does a settimeofday() */ do_gtod.tb_orig_stamp = tb_last_stamp; #endif max_cpus = smp_ops->probe(); smp_space_timers(max_cpus); } int __cpu_up(unsigned int cpu) { struct pt_regs regs; struct task_struct *p; int c; /* create a process for the processor */ /* only regs.msr is actually used, and 0 is OK for it */ memset(®s, 0, sizeof(struct pt_regs)); p = do_fork(CLONE_VM|CLONE_IDLETASK, 0, ®s, 0); if (IS_ERR(p)) panic("failed fork for CPU %u: %li", cpu, PTR_ERR(p)); init_idle(p, cpu); unhash_process(p); paca[cpu].xCurrent = (u64)p; current_set[cpu] = p->thread_info; /* wake up cpus */ smp_ops->kick_cpu(cpu); /* * wait to see if the cpu made a callin (is actually up). * use this value that I found through experimentation. * -- Cort */ for (c = 5000; c && !cpu_callin_map[cpu]; c--) udelay(100); if (!cpu_callin_map[cpu]) { printk("Processor %u is stuck.\n", cpu); return -ENOENT; } printk("Processor %u found.\n", cpu); if (smp_ops->give_timebase) smp_ops->give_timebase(); set_bit(cpu, &cpu_online_map); return 0; } /* Activate a secondary processor. */ int __devinit start_secondary(void *unused) { unsigned int cpu = smp_processor_id(); atomic_inc(&init_mm.mm_count); current->active_mm = &init_mm; smp_store_cpu_info(cpu); set_dec(paca[cpu].default_decr); cpu_callin_map[cpu] = 1; smp_ops->setup_cpu(cpu); if (smp_ops->take_timebase) smp_ops->take_timebase(); /* XXX required? */ local_irq_enable(); return cpu_idle(NULL); } int setup_profiling_timer(unsigned int multiplier) { return 0; } void smp_cpus_done(unsigned int max_cpus) { smp_ops->setup_cpu(boot_cpuid); /* XXX fix this, xics currently relies on it - Anton */ smp_threads_ready = 1; }