📄 smp.c
字号:
/* * SMP support for power macintosh. * * We support both the old "powersurge" SMP architecture * and the current Core99 (G4 PowerMac) machines. * * Note that we don't support the very first rev. of * Apple/DayStar 2 CPUs board, the one with the funky * watchdog. Hopefully, none of these should be there except * maybe internally to Apple. I should probably still add some * code to detect this card though and disable SMP. --BenH. * * Support Macintosh G4 SMP by Troy Benjegerdes (hozer@drgw.net) * and Ben Herrenschmidt <benh@kernel.crashing.org>. * * Support for DayStar quad CPU cards * Copyright (C) XLR8, Inc. 1994-2000 * * 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/kernel.h>#include <linux/sched.h>#include <linux/smp.h>#include <linux/interrupt.h>#include <linux/kernel_stat.h>#include <linux/delay.h>#include <linux/init.h>#include <linux/spinlock.h>#include <linux/errno.h>#include <linux/hardirq.h>#include <linux/cpu.h>#include <linux/compiler.h>#include <asm/ptrace.h>#include <asm/atomic.h>#include <asm/irq.h>#include <asm/page.h>#include <asm/pgtable.h>#include <asm/sections.h>#include <asm/io.h>#include <asm/prom.h>#include <asm/smp.h>#include <asm/machdep.h>#include <asm/pmac_feature.h>#include <asm/time.h>#include <asm/mpic.h>#include <asm/cacheflush.h>#include <asm/keylargo.h>#include <asm/pmac_low_i2c.h>#include <asm/pmac_pfunc.h>#define DEBUG#ifdef DEBUG#define DBG(fmt...) udbg_printf(fmt)#else#define DBG(fmt...)#endifextern void __secondary_start_pmac_0(void);extern int pmac_pfunc_base_install(void);#ifdef CONFIG_PPC32/* Sync flag for HW tb sync */static volatile int sec_tb_reset = 0;/* * Powersurge (old powermac SMP) support. *//* Addresses for powersurge registers */#define HAMMERHEAD_BASE 0xf8000000#define HHEAD_CONFIG 0x90#define HHEAD_SEC_INTR 0xc0/* register for interrupting the primary processor on the powersurge *//* N.B. this is actually the ethernet ROM! */#define PSURGE_PRI_INTR 0xf3019000/* register for storing the start address for the secondary processor *//* N.B. this is the PCI config space address register for the 1st bridge */#define PSURGE_START 0xf2800000/* Daystar/XLR8 4-CPU card */#define PSURGE_QUAD_REG_ADDR 0xf8800000#define PSURGE_QUAD_IRQ_SET 0#define PSURGE_QUAD_IRQ_CLR 1#define PSURGE_QUAD_IRQ_PRIMARY 2#define PSURGE_QUAD_CKSTOP_CTL 3#define PSURGE_QUAD_PRIMARY_ARB 4#define PSURGE_QUAD_BOARD_ID 6#define PSURGE_QUAD_WHICH_CPU 7#define PSURGE_QUAD_CKSTOP_RDBK 8#define PSURGE_QUAD_RESET_CTL 11#define PSURGE_QUAD_OUT(r, v) (out_8(quad_base + ((r) << 4) + 4, (v)))#define PSURGE_QUAD_IN(r) (in_8(quad_base + ((r) << 4) + 4) & 0x0f)#define PSURGE_QUAD_BIS(r, v) (PSURGE_QUAD_OUT((r), PSURGE_QUAD_IN(r) | (v)))#define PSURGE_QUAD_BIC(r, v) (PSURGE_QUAD_OUT((r), PSURGE_QUAD_IN(r) & ~(v)))/* virtual addresses for the above */static volatile u8 __iomem *hhead_base;static volatile u8 __iomem *quad_base;static volatile u32 __iomem *psurge_pri_intr;static volatile u8 __iomem *psurge_sec_intr;static volatile u32 __iomem *psurge_start;/* values for psurge_type */#define PSURGE_NONE -1#define PSURGE_DUAL 0#define PSURGE_QUAD_OKEE 1#define PSURGE_QUAD_COTTON 2#define PSURGE_QUAD_ICEGRASS 3/* what sort of powersurge board we have */static int psurge_type = PSURGE_NONE;/* * Set and clear IPIs for powersurge. */static inline void psurge_set_ipi(int cpu){ if (psurge_type == PSURGE_NONE) return; if (cpu == 0) in_be32(psurge_pri_intr); else if (psurge_type == PSURGE_DUAL) out_8(psurge_sec_intr, 0); else PSURGE_QUAD_OUT(PSURGE_QUAD_IRQ_SET, 1 << cpu);}static inline void psurge_clr_ipi(int cpu){ if (cpu > 0) { switch(psurge_type) { case PSURGE_DUAL: out_8(psurge_sec_intr, ~0); case PSURGE_NONE: break; default: PSURGE_QUAD_OUT(PSURGE_QUAD_IRQ_CLR, 1 << cpu); } }}/* * On powersurge (old SMP powermac architecture) we don't have * separate IPIs for separate messages like openpic does. Instead * we have a bitmap for each processor, where a 1 bit means that * the corresponding message is pending for that processor. * Ideally each cpu's entry would be in a different cache line. * -- paulus. */static unsigned long psurge_smp_message[NR_CPUS];void psurge_smp_message_recv(void){ int cpu = smp_processor_id(); int msg; /* clear interrupt */ psurge_clr_ipi(cpu); if (num_online_cpus() < 2) return; /* make sure there is a message there */ for (msg = 0; msg < 4; msg++) if (test_and_clear_bit(msg, &psurge_smp_message[cpu])) smp_message_recv(msg);}irqreturn_t psurge_primary_intr(int irq, void *d){ psurge_smp_message_recv(); return IRQ_HANDLED;}static void smp_psurge_message_pass(int target, int msg){ int i; if (num_online_cpus() < 2) return; for_each_online_cpu(i) { if (target == MSG_ALL || (target == MSG_ALL_BUT_SELF && i != smp_processor_id()) || target == i) { set_bit(msg, &psurge_smp_message[i]); psurge_set_ipi(i); } }}/* * Determine a quad card presence. We read the board ID register, we * force the data bus to change to something else, and we read it again. * It it's stable, then the register probably exist (ugh !) */static int __init psurge_quad_probe(void){ int type; unsigned int i; type = PSURGE_QUAD_IN(PSURGE_QUAD_BOARD_ID); if (type < PSURGE_QUAD_OKEE || type > PSURGE_QUAD_ICEGRASS || type != PSURGE_QUAD_IN(PSURGE_QUAD_BOARD_ID)) return PSURGE_DUAL; /* looks OK, try a slightly more rigorous test */ /* bogus is not necessarily cacheline-aligned, though I don't suppose that really matters. -- paulus */ for (i = 0; i < 100; i++) { volatile u32 bogus[8]; bogus[(0+i)%8] = 0x00000000; bogus[(1+i)%8] = 0x55555555; bogus[(2+i)%8] = 0xFFFFFFFF; bogus[(3+i)%8] = 0xAAAAAAAA; bogus[(4+i)%8] = 0x33333333; bogus[(5+i)%8] = 0xCCCCCCCC; bogus[(6+i)%8] = 0xCCCCCCCC; bogus[(7+i)%8] = 0x33333333; wmb(); asm volatile("dcbf 0,%0" : : "r" (bogus) : "memory"); mb(); if (type != PSURGE_QUAD_IN(PSURGE_QUAD_BOARD_ID)) return PSURGE_DUAL; } return type;}static void __init psurge_quad_init(void){ int procbits; if (ppc_md.progress) ppc_md.progress("psurge_quad_init", 0x351); procbits = ~PSURGE_QUAD_IN(PSURGE_QUAD_WHICH_CPU); if (psurge_type == PSURGE_QUAD_ICEGRASS) PSURGE_QUAD_BIS(PSURGE_QUAD_RESET_CTL, procbits); else PSURGE_QUAD_BIC(PSURGE_QUAD_CKSTOP_CTL, procbits); mdelay(33); out_8(psurge_sec_intr, ~0); PSURGE_QUAD_OUT(PSURGE_QUAD_IRQ_CLR, procbits); PSURGE_QUAD_BIS(PSURGE_QUAD_RESET_CTL, procbits); if (psurge_type != PSURGE_QUAD_ICEGRASS) PSURGE_QUAD_BIS(PSURGE_QUAD_CKSTOP_CTL, procbits); PSURGE_QUAD_BIC(PSURGE_QUAD_PRIMARY_ARB, procbits); mdelay(33); PSURGE_QUAD_BIC(PSURGE_QUAD_RESET_CTL, procbits); mdelay(33); PSURGE_QUAD_BIS(PSURGE_QUAD_PRIMARY_ARB, procbits); mdelay(33);}static int __init smp_psurge_probe(void){ int i, ncpus; struct device_node *dn; /* We don't do SMP on the PPC601 -- paulus */ if (PVR_VER(mfspr(SPRN_PVR)) == 1) return 1; /* * The powersurge cpu board can be used in the generation * of powermacs that have a socket for an upgradeable cpu card, * including the 7500, 8500, 9500, 9600. * The device tree doesn't tell you if you have 2 cpus because * OF doesn't know anything about the 2nd processor. * Instead we look for magic bits in magic registers, * in the hammerhead memory controller in the case of the * dual-cpu powersurge board. -- paulus. */ dn = of_find_node_by_name(NULL, "hammerhead"); if (dn == NULL) return 1; of_node_put(dn); hhead_base = ioremap(HAMMERHEAD_BASE, 0x800); quad_base = ioremap(PSURGE_QUAD_REG_ADDR, 1024); psurge_sec_intr = hhead_base + HHEAD_SEC_INTR; psurge_type = psurge_quad_probe(); if (psurge_type != PSURGE_DUAL) { psurge_quad_init(); /* All released cards using this HW design have 4 CPUs */ ncpus = 4; } else { iounmap(quad_base); if ((in_8(hhead_base + HHEAD_CONFIG) & 0x02) == 0) { /* not a dual-cpu card */ iounmap(hhead_base); psurge_type = PSURGE_NONE; return 1; } ncpus = 2; } psurge_start = ioremap(PSURGE_START, 4); psurge_pri_intr = ioremap(PSURGE_PRI_INTR, 4); /* * This is necessary because OF doesn't know about the * secondary cpu(s), and thus there aren't nodes in the * device tree for them, and smp_setup_cpu_maps hasn't * set their bits in cpu_possible_map and cpu_present_map. */ if (ncpus > NR_CPUS) ncpus = NR_CPUS; for (i = 1; i < ncpus ; ++i) { cpu_set(i, cpu_present_map); set_hard_smp_processor_id(i, i); } if (ppc_md.progress) ppc_md.progress("smp_psurge_probe - done", 0x352); return ncpus;}static void __init smp_psurge_kick_cpu(int nr){ unsigned long start = __pa(__secondary_start_pmac_0) + nr * 8; unsigned long a; int i; /* may need to flush here if secondary bats aren't setup */ for (a = KERNELBASE; a < KERNELBASE + 0x800000; a += 32) asm volatile("dcbf 0,%0" : : "r" (a) : "memory"); asm volatile("sync"); if (ppc_md.progress) ppc_md.progress("smp_psurge_kick_cpu", 0x353); out_be32(psurge_start, start); mb(); psurge_set_ipi(nr); /* * We can't use udelay here because the timebase is now frozen. */ for (i = 0; i < 2000; ++i) barrier(); psurge_clr_ipi(nr); if (ppc_md.progress) ppc_md.progress("smp_psurge_kick_cpu - done", 0x354);}/* * With the dual-cpu powersurge board, the decrementers and timebases * of both cpus are frozen after the secondary cpu is started up, * until we give the secondary cpu another interrupt. This routine * uses this to get the timebases synchronized. * -- paulus. */static void __init psurge_dual_sync_tb(int cpu_nr){ int t; set_dec(tb_ticks_per_jiffy); /* XXX fixme */ set_tb(0, 0); if (cpu_nr > 0) { mb(); sec_tb_reset = 1; return; } /* wait for the secondary to have reset its TB before proceeding */ for (t = 10000000; t > 0 && !sec_tb_reset; --t) ; /* now interrupt the secondary, starting both TBs */ psurge_set_ipi(1);}static struct irqaction psurge_irqaction = { .handler = psurge_primary_intr, .flags = IRQF_DISABLED, .mask = CPU_MASK_NONE, .name = "primary IPI",};static void __init smp_psurge_setup_cpu(int cpu_nr){ if (cpu_nr == 0) { /* If we failed to start the second CPU, we should still * send it an IPI to start the timebase & DEC or we might * have them stuck. */ if (num_online_cpus() < 2) { if (psurge_type == PSURGE_DUAL) psurge_set_ipi(1); return; } /* reset the entry point so if we get another intr we won't * try to startup again */ out_be32(psurge_start, 0x100); if (setup_irq(30, &psurge_irqaction)) printk(KERN_ERR "Couldn't get primary IPI interrupt"); } if (psurge_type == PSURGE_DUAL) psurge_dual_sync_tb(cpu_nr);}void __init smp_psurge_take_timebase(void){ /* Dummy implementation */}void __init smp_psurge_give_timebase(void){ /* Dummy implementation */}/* PowerSurge-style Macs */struct smp_ops_t psurge_smp_ops = { .message_pass = smp_psurge_message_pass, .probe = smp_psurge_probe, .kick_cpu = smp_psurge_kick_cpu, .setup_cpu = smp_psurge_setup_cpu, .give_timebase = smp_psurge_give_timebase, .take_timebase = smp_psurge_take_timebase,};#endif /* CONFIG_PPC32 - actually powersurge support *//* * Core 99 and later support */static void (*pmac_tb_freeze)(int freeze);static u64 timebase;static int tb_req;static void smp_core99_give_timebase(void){ unsigned long flags; local_irq_save(flags); while(!tb_req) barrier(); tb_req = 0; (*pmac_tb_freeze)(1); mb(); timebase = get_tb(); mb(); while (timebase) barrier(); mb(); (*pmac_tb_freeze)(0); mb();
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -