From 57f0f512b273f60d52568b8c6b77e17f5636edc0 Mon Sep 17 00:00:00 2001 From: André Fabian Silva Delgado Date: Wed, 5 Aug 2015 17:04:01 -0300 Subject: Initial import --- arch/metag/kernel/smp.c | 665 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 665 insertions(+) create mode 100644 arch/metag/kernel/smp.c (limited to 'arch/metag/kernel/smp.c') diff --git a/arch/metag/kernel/smp.c b/arch/metag/kernel/smp.c new file mode 100644 index 000000000..ac3a199e3 --- /dev/null +++ b/arch/metag/kernel/smp.c @@ -0,0 +1,665 @@ +/* + * Copyright (C) 2009,2010,2011 Imagination Technologies Ltd. + * + * Copyright (C) 2002 ARM Limited, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SYSC_DCPART(n) (SYSC_DCPART0 + SYSC_xCPARTn_STRIDE * (n)) +#define SYSC_ICPART(n) (SYSC_ICPART0 + SYSC_xCPARTn_STRIDE * (n)) + +DECLARE_PER_CPU(PTBI, pTBI); + +void *secondary_data_stack; + +/* + * structures for inter-processor calls + * - A collection of single bit ipi messages. + */ +struct ipi_data { + spinlock_t lock; + unsigned long ipi_count; + unsigned long bits; +}; + +static DEFINE_PER_CPU(struct ipi_data, ipi_data) = { + .lock = __SPIN_LOCK_UNLOCKED(ipi_data.lock), +}; + +static DEFINE_SPINLOCK(boot_lock); + +static DECLARE_COMPLETION(cpu_running); + +/* + * "thread" is assumed to be a valid Meta hardware thread ID. + */ +static int boot_secondary(unsigned int thread, struct task_struct *idle) +{ + u32 val; + + /* + * set synchronisation state between this boot processor + * and the secondary one + */ + spin_lock(&boot_lock); + + core_reg_write(TXUPC_ID, 0, thread, (unsigned int)secondary_startup); + core_reg_write(TXUPC_ID, 1, thread, 0); + + /* + * Give the thread privilege (PSTAT) and clear potentially problematic + * bits in the process (namely ISTAT, CBMarker, CBMarkerI, LSM_STEP). + */ + core_reg_write(TXUCT_ID, TXSTATUS_REGNUM, thread, TXSTATUS_PSTAT_BIT); + + /* Clear the minim enable bit. */ + val = core_reg_read(TXUCT_ID, TXPRIVEXT_REGNUM, thread); + core_reg_write(TXUCT_ID, TXPRIVEXT_REGNUM, thread, val & ~0x80); + + /* + * set the ThreadEnable bit (0x1) in the TXENABLE register + * for the specified thread - off it goes! + */ + val = core_reg_read(TXUCT_ID, TXENABLE_REGNUM, thread); + core_reg_write(TXUCT_ID, TXENABLE_REGNUM, thread, val | 0x1); + + /* + * now the secondary core is starting up let it run its + * calibrations, then wait for it to finish + */ + spin_unlock(&boot_lock); + + return 0; +} + +/** + * describe_cachepart_change: describe a change to cache partitions. + * @thread: Hardware thread number. + * @label: Label of cache type, e.g. "dcache" or "icache". + * @sz: Total size of the cache. + * @old: Old cache partition configuration (*CPART* register). + * @new: New cache partition configuration (*CPART* register). + * + * If the cache partition has changed, prints a message to the log describing + * those changes. + */ +static void describe_cachepart_change(unsigned int thread, const char *label, + unsigned int sz, unsigned int old, + unsigned int new) +{ + unsigned int lor1, land1, gor1, gand1; + unsigned int lor2, land2, gor2, gand2; + unsigned int diff = old ^ new; + + if (!diff) + return; + + pr_info("Thread %d: %s partition changed:", thread, label); + if (diff & (SYSC_xCPARTL_OR_BITS | SYSC_xCPARTL_AND_BITS)) { + lor1 = (old & SYSC_xCPARTL_OR_BITS) >> SYSC_xCPARTL_OR_S; + lor2 = (new & SYSC_xCPARTL_OR_BITS) >> SYSC_xCPARTL_OR_S; + land1 = (old & SYSC_xCPARTL_AND_BITS) >> SYSC_xCPARTL_AND_S; + land2 = (new & SYSC_xCPARTL_AND_BITS) >> SYSC_xCPARTL_AND_S; + pr_cont(" L:%#x+%#x->%#x+%#x", + (lor1 * sz) >> 4, + ((land1 + 1) * sz) >> 4, + (lor2 * sz) >> 4, + ((land2 + 1) * sz) >> 4); + } + if (diff & (SYSC_xCPARTG_OR_BITS | SYSC_xCPARTG_AND_BITS)) { + gor1 = (old & SYSC_xCPARTG_OR_BITS) >> SYSC_xCPARTG_OR_S; + gor2 = (new & SYSC_xCPARTG_OR_BITS) >> SYSC_xCPARTG_OR_S; + gand1 = (old & SYSC_xCPARTG_AND_BITS) >> SYSC_xCPARTG_AND_S; + gand2 = (new & SYSC_xCPARTG_AND_BITS) >> SYSC_xCPARTG_AND_S; + pr_cont(" G:%#x+%#x->%#x+%#x", + (gor1 * sz) >> 4, + ((gand1 + 1) * sz) >> 4, + (gor2 * sz) >> 4, + ((gand2 + 1) * sz) >> 4); + } + if (diff & SYSC_CWRMODE_BIT) + pr_cont(" %sWR", + (new & SYSC_CWRMODE_BIT) ? "+" : "-"); + if (diff & SYSC_DCPART_GCON_BIT) + pr_cont(" %sGCOn", + (new & SYSC_DCPART_GCON_BIT) ? "+" : "-"); + pr_cont("\n"); +} + +/** + * setup_smp_cache: ensure cache coherency for new SMP thread. + * @thread: New hardware thread number. + * + * Ensures that coherency is enabled and that the threads share the same cache + * partitions. + */ +static void setup_smp_cache(unsigned int thread) +{ + unsigned int this_thread, lflags; + unsigned int dcsz, dcpart_this, dcpart_old, dcpart_new; + unsigned int icsz, icpart_old, icpart_new; + + /* + * Copy over the current thread's cache partition configuration to the + * new thread so that they share cache partitions. + */ + __global_lock2(lflags); + this_thread = hard_processor_id(); + /* Share dcache partition */ + dcpart_this = metag_in32(SYSC_DCPART(this_thread)); + dcpart_old = metag_in32(SYSC_DCPART(thread)); + dcpart_new = dcpart_this; +#if PAGE_OFFSET < LINGLOBAL_BASE + /* + * For the local data cache to be coherent the threads must also have + * GCOn enabled. + */ + dcpart_new |= SYSC_DCPART_GCON_BIT; + metag_out32(dcpart_new, SYSC_DCPART(this_thread)); +#endif + metag_out32(dcpart_new, SYSC_DCPART(thread)); + /* Share icache partition too */ + icpart_new = metag_in32(SYSC_ICPART(this_thread)); + icpart_old = metag_in32(SYSC_ICPART(thread)); + metag_out32(icpart_new, SYSC_ICPART(thread)); + __global_unlock2(lflags); + + /* + * Log if the cache partitions were altered so the user is aware of any + * potential unintentional cache wastage. + */ + dcsz = get_dcache_size(); + icsz = get_dcache_size(); + describe_cachepart_change(this_thread, "dcache", dcsz, + dcpart_this, dcpart_new); + describe_cachepart_change(thread, "dcache", dcsz, + dcpart_old, dcpart_new); + describe_cachepart_change(thread, "icache", icsz, + icpart_old, icpart_new); +} + +int __cpu_up(unsigned int cpu, struct task_struct *idle) +{ + unsigned int thread = cpu_2_hwthread_id[cpu]; + int ret; + + load_pgd(swapper_pg_dir, thread); + + flush_tlb_all(); + + setup_smp_cache(thread); + + /* + * Tell the secondary CPU where to find its idle thread's stack. + */ + secondary_data_stack = task_stack_page(idle); + + wmb(); + + /* + * Now bring the CPU into our world. + */ + ret = boot_secondary(thread, idle); + if (ret == 0) { + /* + * CPU was successfully started, wait for it + * to come online or time out. + */ + wait_for_completion_timeout(&cpu_running, + msecs_to_jiffies(1000)); + + if (!cpu_online(cpu)) + ret = -EIO; + } + + secondary_data_stack = NULL; + + if (ret) { + pr_crit("CPU%u: processor failed to boot\n", cpu); + + /* + * FIXME: We need to clean up the new idle thread. --rmk + */ + } + + return ret; +} + +#ifdef CONFIG_HOTPLUG_CPU + +/* + * __cpu_disable runs on the processor to be shutdown. + */ +int __cpu_disable(void) +{ + unsigned int cpu = smp_processor_id(); + + /* + * Take this CPU offline. Once we clear this, we can't return, + * and we must not schedule until we're ready to give up the cpu. + */ + set_cpu_online(cpu, false); + + /* + * OK - migrate IRQs away from this CPU + */ + migrate_irqs(); + + /* + * Flush user cache and TLB mappings, and then remove this CPU + * from the vm mask set of all processes. + */ + flush_cache_all(); + local_flush_tlb_all(); + + clear_tasks_mm_cpumask(cpu); + + return 0; +} + +/* + * called on the thread which is asking for a CPU to be shutdown - + * waits until shutdown has completed, or it is timed out. + */ +void __cpu_die(unsigned int cpu) +{ + if (!cpu_wait_death(cpu, 1)) + pr_err("CPU%u: unable to kill\n", cpu); +} + +/* + * Called from the idle thread for the CPU which has been shutdown. + * + * Note that we do not return from this function. If this cpu is + * brought online again it will need to run secondary_startup(). + */ +void cpu_die(void) +{ + local_irq_disable(); + idle_task_exit(); + + (void)cpu_report_death(); + + asm ("XOR TXENABLE, D0Re0,D0Re0\n"); +} +#endif /* CONFIG_HOTPLUG_CPU */ + +/* + * Called by both boot and secondaries to move global data into + * per-processor storage. + */ +void smp_store_cpu_info(unsigned int cpuid) +{ + struct cpuinfo_metag *cpu_info = &per_cpu(cpu_data, cpuid); + + cpu_info->loops_per_jiffy = loops_per_jiffy; +} + +/* + * This is the secondary CPU boot entry. We're using this CPUs + * idle thread stack and the global page tables. + */ +asmlinkage void secondary_start_kernel(void) +{ + struct mm_struct *mm = &init_mm; + unsigned int cpu = smp_processor_id(); + + /* + * All kernel threads share the same mm context; grab a + * reference and switch to it. + */ + atomic_inc(&mm->mm_users); + atomic_inc(&mm->mm_count); + current->active_mm = mm; + cpumask_set_cpu(cpu, mm_cpumask(mm)); + enter_lazy_tlb(mm, current); + local_flush_tlb_all(); + + /* + * TODO: Some day it might be useful for each Linux CPU to + * have its own TBI structure. That would allow each Linux CPU + * to run different interrupt handlers for the same IRQ + * number. + * + * For now, simply copying the pointer to the boot CPU's TBI + * structure is sufficient because we always want to run the + * same interrupt handler whatever CPU takes the interrupt. + */ + per_cpu(pTBI, cpu) = __TBI(TBID_ISTAT_BIT); + + if (!per_cpu(pTBI, cpu)) + panic("No TBI found!"); + + per_cpu_trap_init(cpu); + + preempt_disable(); + + setup_priv(); + + notify_cpu_starting(cpu); + + pr_info("CPU%u (thread %u): Booted secondary processor\n", + cpu, cpu_2_hwthread_id[cpu]); + + calibrate_delay(); + smp_store_cpu_info(cpu); + + /* + * OK, now it's safe to let the boot CPU continue + */ + set_cpu_online(cpu, true); + complete(&cpu_running); + + /* + * Enable local interrupts. + */ + tbi_startup_interrupt(TBID_SIGNUM_TRT); + local_irq_enable(); + + /* + * OK, it's off to the idle thread for us + */ + cpu_startup_entry(CPUHP_ONLINE); +} + +void __init smp_cpus_done(unsigned int max_cpus) +{ + int cpu; + unsigned long bogosum = 0; + + for_each_online_cpu(cpu) + bogosum += per_cpu(cpu_data, cpu).loops_per_jiffy; + + pr_info("SMP: Total of %d processors activated (%lu.%02lu BogoMIPS).\n", + num_online_cpus(), + bogosum / (500000/HZ), + (bogosum / (5000/HZ)) % 100); +} + +void __init smp_prepare_cpus(unsigned int max_cpus) +{ + unsigned int cpu = smp_processor_id(); + + init_new_context(current, &init_mm); + current_thread_info()->cpu = cpu; + + smp_store_cpu_info(cpu); + init_cpu_present(cpu_possible_mask); +} + +void __init smp_prepare_boot_cpu(void) +{ + unsigned int cpu = smp_processor_id(); + + per_cpu(pTBI, cpu) = __TBI(TBID_ISTAT_BIT); + + if (!per_cpu(pTBI, cpu)) + panic("No TBI found!"); +} + +static void smp_cross_call(cpumask_t callmap, enum ipi_msg_type msg); + +static void send_ipi_message(const struct cpumask *mask, enum ipi_msg_type msg) +{ + unsigned long flags; + unsigned int cpu; + cpumask_t map; + + cpumask_clear(&map); + local_irq_save(flags); + + for_each_cpu(cpu, mask) { + struct ipi_data *ipi = &per_cpu(ipi_data, cpu); + + spin_lock(&ipi->lock); + + /* + * KICK interrupts are queued in hardware so we'll get + * multiple interrupts if we call smp_cross_call() + * multiple times for one msg. The problem is that we + * only have one bit for each message - we can't queue + * them in software. + * + * The first time through ipi_handler() we'll clear + * the msg bit, having done all the work. But when we + * return we'll get _another_ interrupt (and another, + * and another until we've handled all the queued + * KICKs). Running ipi_handler() when there's no work + * to do is bad because that's how kick handler + * chaining detects who the KICK was intended for. + * See arch/metag/kernel/kick.c for more details. + * + * So only add 'cpu' to 'map' if we haven't already + * queued a KICK interrupt for 'msg'. + */ + if (!(ipi->bits & (1 << msg))) { + ipi->bits |= 1 << msg; + cpumask_set_cpu(cpu, &map); + } + + spin_unlock(&ipi->lock); + } + + /* + * Call the platform specific cross-CPU call function. + */ + smp_cross_call(map, msg); + + local_irq_restore(flags); +} + +void arch_send_call_function_ipi_mask(const struct cpumask *mask) +{ + send_ipi_message(mask, IPI_CALL_FUNC); +} + +void arch_send_call_function_single_ipi(int cpu) +{ + send_ipi_message(cpumask_of(cpu), IPI_CALL_FUNC); +} + +void show_ipi_list(struct seq_file *p) +{ + unsigned int cpu; + + seq_puts(p, "IPI:"); + + for_each_present_cpu(cpu) + seq_printf(p, " %10lu", per_cpu(ipi_data, cpu).ipi_count); + + seq_putc(p, '\n'); +} + +static DEFINE_SPINLOCK(stop_lock); + +/* + * Main handler for inter-processor interrupts + * + * For Meta, the ipimask now only identifies a single + * category of IPI (Bit 1 IPIs have been replaced by a + * different mechanism): + * + * Bit 0 - Inter-processor function call + */ +static int do_IPI(void) +{ + unsigned int cpu = smp_processor_id(); + struct ipi_data *ipi = &per_cpu(ipi_data, cpu); + unsigned long msgs, nextmsg; + int handled = 0; + + ipi->ipi_count++; + + spin_lock(&ipi->lock); + msgs = ipi->bits; + nextmsg = msgs & -msgs; + ipi->bits &= ~nextmsg; + spin_unlock(&ipi->lock); + + if (nextmsg) { + handled = 1; + + nextmsg = ffz(~nextmsg); + switch (nextmsg) { + case IPI_RESCHEDULE: + scheduler_ipi(); + break; + + case IPI_CALL_FUNC: + generic_smp_call_function_interrupt(); + break; + + default: + pr_crit("CPU%u: Unknown IPI message 0x%lx\n", + cpu, nextmsg); + break; + } + } + + return handled; +} + +void smp_send_reschedule(int cpu) +{ + send_ipi_message(cpumask_of(cpu), IPI_RESCHEDULE); +} + +static void stop_this_cpu(void *data) +{ + unsigned int cpu = smp_processor_id(); + + if (system_state == SYSTEM_BOOTING || + system_state == SYSTEM_RUNNING) { + spin_lock(&stop_lock); + pr_crit("CPU%u: stopping\n", cpu); + dump_stack(); + spin_unlock(&stop_lock); + } + + set_cpu_online(cpu, false); + + local_irq_disable(); + + hard_processor_halt(HALT_OK); +} + +void smp_send_stop(void) +{ + smp_call_function(stop_this_cpu, NULL, 0); +} + +/* + * not supported here + */ +int setup_profiling_timer(unsigned int multiplier) +{ + return -EINVAL; +} + +/* + * We use KICKs for inter-processor interrupts. + * + * For every CPU in "callmap" the IPI data must already have been + * stored in that CPU's "ipi_data" member prior to calling this + * function. + */ +static void kick_raise_softirq(cpumask_t callmap, unsigned int irq) +{ + int cpu; + + for_each_cpu(cpu, &callmap) { + unsigned int thread; + + thread = cpu_2_hwthread_id[cpu]; + + BUG_ON(thread == BAD_HWTHREAD_ID); + + metag_out32(1, T0KICKI + (thread * TnXKICK_STRIDE)); + } +} + +static TBIRES ipi_handler(TBIRES State, int SigNum, int Triggers, + int Inst, PTBI pTBI, int *handled) +{ + *handled = do_IPI(); + + return State; +} + +static struct kick_irq_handler ipi_irq = { + .func = ipi_handler, +}; + +static void smp_cross_call(cpumask_t callmap, enum ipi_msg_type msg) +{ + kick_raise_softirq(callmap, 1); +} + +static inline unsigned int get_core_count(void) +{ + int i; + unsigned int ret = 0; + + for (i = 0; i < CONFIG_NR_CPUS; i++) { + if (core_reg_read(TXUCT_ID, TXENABLE_REGNUM, i)) + ret++; + } + + return ret; +} + +/* + * Initialise the CPU possible map early - this describes the CPUs + * which may be present or become present in the system. + */ +void __init smp_init_cpus(void) +{ + unsigned int i, ncores = get_core_count(); + + /* If no hwthread_map early param was set use default mapping */ + for (i = 0; i < NR_CPUS; i++) + if (cpu_2_hwthread_id[i] == BAD_HWTHREAD_ID) { + cpu_2_hwthread_id[i] = i; + hwthread_id_2_cpu[i] = i; + } + + for (i = 0; i < ncores; i++) + set_cpu_possible(i, true); + + kick_register_func(&ipi_irq); +} -- cgit v1.2.3-54-g00ecf