// SPDX-License-Identifier: GPL-2.0-or-later /* * 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
*/ #include <linux/kernel.h> #include <linux/sched.h> #include <linux/sched/hotplug.h> #include <linux/smp.h> #include <linux/interrupt.h> #include <linux/irqdomain.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 <linux/pgtable.h>
/* 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
/* what sort of powersurge board we have */ staticint psurge_type = PSURGE_NONE;
/* irq for secondary cpus to report */ staticstruct irq_domain *psurge_host; int psurge_secondary_virq;
/* * Set and clear IPIs for powersurge.
*/ staticinlinevoid psurge_set_ipi(int cpu)
{ if (psurge_type == PSURGE_NONE) return; if (cpu == 0)
in_be32(psurge_pri_intr); elseif (psurge_type == PSURGE_DUAL)
out_8(psurge_sec_intr, 0); else
PSURGE_QUAD_OUT(PSURGE_QUAD_IRQ_SET, 1 << cpu);
}
staticinlinevoid psurge_clr_ipi(int cpu)
{ if (cpu > 0) { switch(psurge_type) { case PSURGE_DUAL:
out_8(psurge_sec_intr, ~0); break; 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 * use the generic demux helpers * -- paulus.
*/ static irqreturn_t psurge_ipi_intr(int irq, void *d)
{
psurge_clr_ipi(smp_processor_id());
smp_ipi_demux();
if (rc)
pr_err("Failed to setup secondary cpu IPI\n");
return rc;
}
/* * 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 !)
*/ staticint __init psurge_quad_probe(void)
{ int type; unsignedint 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(); asmvolatile("dcbf 0,%0" : : "r" (bogus) : "memory");
mb(); if (type != PSURGE_QUAD_IN(PSURGE_QUAD_BOARD_ID)) return PSURGE_DUAL;
} return type;
}
staticvoid __init psurge_quad_init(void)
{ int procbits;
staticvoid __init smp_psurge_probe(void)
{ int i, ncpus; struct device_node *dn;
/* * 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;
of_node_put(dn);
/* 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_present_mask.
*/ if (ncpus > NR_CPUS)
ncpus = NR_CPUS; for (i = 1; i < ncpus ; ++i)
set_cpu_present(i, true);
if (ppc_md.progress) ppc_md.progress("smp_psurge_probe - done", 0x352);
}
staticint __init smp_psurge_kick_cpu(int nr)
{ unsignedlong start = __pa(__secondary_start_pmac_0) + nr * 8; unsignedlong a, flags; int i, j;
/* Defining this here is evil ... but I prefer hiding that * crap to avoid giving people ideas that they can do the * same.
*/ externvolatileunsignedint cpu_callin_map[NR_CPUS];
/* may need to flush here if secondary bats aren't setup */ for (a = KERNELBASE; a < KERNELBASE + 0x800000; a += 32) asmvolatile("dcbf 0,%0" : : "r" (a) : "memory"); asmvolatile("sync");
if (ppc_md.progress) ppc_md.progress("smp_psurge_kick_cpu", 0x353);
/* This is going to freeze the timeebase, we disable interrupts */
local_irq_save(flags);
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) asmvolatile("nop" : : : "memory");
psurge_clr_ipi(nr);
/* * Also, because the timebase is frozen, we must not return to the * caller which will try to do udelay's etc... Instead, we wait -here- * for the CPU to callin.
*/ for (i = 0; i < 100000 && !cpu_callin_map[nr]; ++i) { for (j = 1; j < 10000; j++) asmvolatile("nop" : : : "memory"); asmvolatile("sync" : : : "memory");
} if (!cpu_callin_map[nr]) goto stuck;
/* And we do the TB sync here too for standard dual CPU cards */ if (psurge_type == PSURGE_DUAL) { while(!tb_req)
barrier();
tb_req = 0;
mb();
timebase = get_tb();
mb(); while (timebase)
barrier();
mb();
}
stuck: /* now interrupt the secondary, restarting both TBs */ if (psurge_type == PSURGE_DUAL)
psurge_set_ipi(1);
if (ppc_md.progress) ppc_md.progress("smp_psurge_kick_cpu - done", 0x354);
/* reset the entry point so if we get another intr we won't
* try to startup again */
out_be32(psurge_start, 0x100);
irq = irq_create_mapping(NULL, 30); if (request_irq(irq, psurge_ipi_intr, flags, "primary IPI", NULL))
printk(KERN_ERR "Couldn't get primary IPI interrupt");
}
staticvoid __init smp_psurge_take_timebase(void)
{ if (psurge_type != PSURGE_DUAL) return;
/* Look for the clock chip */
for_each_node_by_name(cc, "i2c-hwclock") {
p = of_get_parent(cc);
ok = p && of_device_is_compatible(p, "uni-n-i2c");
of_node_put(p); if (!ok) continue;
pmac_tb_clock_chip_host = pmac_i2c_find_bus(cc); if (pmac_tb_clock_chip_host == NULL) continue;
reg = of_get_property(cc, "reg", NULL); if (reg == NULL) continue; switch (*reg) { case 0xd2: if (of_device_is_compatible(cc,"pulsar-legacy-slewing")) {
pmac_tb_freeze = smp_core99_pulsar_tb_freeze;
pmac_tb_pulsar_addr = 0xd2;
name = "Pulsar";
} elseif (of_device_is_compatible(cc, "cy28508")) {
pmac_tb_freeze = smp_core99_cypress_tb_freeze;
name = "Cypress";
} break; case 0xd4:
pmac_tb_freeze = smp_core99_pulsar_tb_freeze;
pmac_tb_pulsar_addr = 0xd4;
name = "Pulsar"; break;
} if (pmac_tb_freeze != NULL) {
of_node_put(cc); break;
}
} if (pmac_tb_freeze != NULL) { /* Open i2c bus for synchronous access */ if (pmac_i2c_open(pmac_tb_clock_chip_host, 1)) {
printk(KERN_ERR "Failed top open i2c bus for clock" " sync, fallback to software sync !\n"); goto no_i2c_sync;
}
printk(KERN_INFO "Processor timebase sync using %s i2c clock\n",
name); return;
}
no_i2c_sync:
pmac_tb_freeze = NULL;
pmac_tb_clock_chip_host = NULL;
}
/* i2c based HW sync on some G5s */ if (of_machine_is_compatible("PowerMac7,2") ||
of_machine_is_compatible("PowerMac7,3") ||
of_machine_is_compatible("RackMac3,1"))
smp_core99_setup_i2c_hwsync(ncpus);
/* pfunc based HW sync on recent G5s */ if (pmac_tb_freeze == NULL) { struct device_node *cpus =
of_find_node_by_path("/cpus"); if (cpus &&
of_property_read_bool(cpus, "platform-cpu-timebase")) {
pmac_tb_freeze = smp_core99_pfunc_tb_freeze;
printk(KERN_INFO "Processor timebase sync using" " platform function\n");
}
of_node_put(cpus);
}
#else/* CONFIG_PPC64 */
/* GPIO based HW sync on ppc32 Core99 */ if (pmac_tb_freeze == NULL && !of_machine_is_compatible("MacRISC4")) { struct device_node *cpu; const u32 *tbprop = NULL;
if (ppc_md.progress) ppc_md.progress("smp_core99_probe", 0x345);
/* Count CPUs in the device-tree */
for_each_node_by_type(cpus, "cpu")
++ncpus;
printk(KERN_INFO "PowerMac SMP probe found %d cpus\n", ncpus);
/* Nothing more to do if less than 2 of them */ if (ncpus <= 1) return;
/* We need to perform some early initialisations before we can start * setting up SMP as we are running before initcalls
*/
pmac_pfunc_base_install();
pmac_i2c_init();
/* Setup various bits like timebase sync method, ability to nap, ... */
smp_core99_setup(ncpus);
/* Install IPIs */
mpic_request_ipis();
/* Collect l2cr and l3cr values from CPU 0 */
core99_init_caches(0);
}
if (ppc_md.progress)
ppc_md.progress("smp_core99_kick_cpu", 0x346);
local_irq_save(flags);
/* Save reset vector */
save_vector = *vector;
/* Setup fake reset vector that does * b __secondary_start_pmac_0 + nr*8
*/
target = (unsignedlong) __secondary_start_pmac_0 + nr * 8;
patch_branch(vector, target, BRANCH_SET_LINK);
/* Put some life in our friend */
pmac_call_feature(PMAC_FTR_RESET_CPU, NULL, nr, 0);
/* FIXME: We wait a bit for the CPU to take the exception, I should * instead wait for the entry code to set something for me. Well, * ideally, all that crap will be done in prom.c and the CPU left * in a RAM-based wait loop like CHRP.
*/
mdelay(1);
staticint smp_core99_cpu_prepare(unsignedint cpu)
{ int rc;
/* Open i2c bus if it was used for tb sync */ if (pmac_tb_clock_chip_host && !smp_core99_host_open) {
rc = pmac_i2c_open(pmac_tb_clock_chip_host, 1); if (rc) {
pr_err("Failed to open i2c bus for time sync\n"); return notifier_from_errno(rc);
}
smp_core99_host_open = 1;
} return 0;
}
staticint smp_core99_cpu_online(unsignedint cpu)
{ /* Close i2c bus if it was used for tb sync */ if (pmac_tb_clock_chip_host && smp_core99_host_open) {
pmac_i2c_close(pmac_tb_clock_chip_host);
smp_core99_host_open = 0;
} return 0;
} #endif/* CONFIG_HOTPLUG_CPU */
staticvoid __init smp_core99_bringup_done(void)
{ /* Close i2c bus if it was used for tb sync */ if (pmac_tb_clock_chip_host)
pmac_i2c_close(pmac_tb_clock_chip_host);
/* If we didn't start the second CPU, we must take * it off the bus.
*/ if (of_machine_is_compatible("MacRISC4") &&
num_online_cpus() < 2) {
set_cpu_present(1, false);
g5_phy_disable_cpu1();
} #ifdef CONFIG_HOTPLUG_CPU
cpuhp_setup_state_nocalls(CPUHP_POWERPC_PMAC_PREPARE, "powerpc/pmac:prepare", smp_core99_cpu_prepare,
NULL);
cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN, "powerpc/pmac:online",
smp_core99_cpu_online, NULL); #endif
if (ppc_md.progress)
ppc_md.progress("smp_core99_bringup_done", 0x349);
} #endif/* CONFIG_PPC64 */
#ifdef CONFIG_HOTPLUG_CPU
staticint smp_core99_cpu_disable(void)
{ int rc = generic_cpu_disable(); if (rc) return rc;
mpic_cpu_set_priority(0xf);
cleanup_cpu_mmu_context();
return 0;
}
#ifdef CONFIG_PPC32
staticvoid pmac_cpu_offline_self(void)
{ int cpu = smp_processor_id();
/* * Re-enable interrupts. The NAP code needs to enable them * anyways, do it now so we deal with the case where one already * happened while soft-disabled. * We shouldn't get any external interrupts, only decrementer, and the * decrementer handler is safe for use on offline CPUs
*/
local_irq_enable();
while (1) { /* let's not take timer interrupts too often ... */
set_dec(0x7fffffff);
/* Check for Core99 */
np = of_find_node_by_name(NULL, "uni-n"); if (!np)
np = of_find_node_by_name(NULL, "u3"); if (!np)
np = of_find_node_by_name(NULL, "u4"); if (np) {
of_node_put(np);
smp_ops = &core99_smp_ops;
} #ifdef CONFIG_PPC_PMAC32_PSURGE else { /* We have to set bits in cpu_possible_mask here since the * secondary CPU(s) aren't in the device tree. Various * things won't be initialized for CPUs not in the possible * map, so we really need to fix it up here.
*/ int cpu;
for (cpu = 1; cpu < 4 && cpu < NR_CPUS; ++cpu)
set_cpu_possible(cpu, true);
smp_ops = &psurge_smp_ops;
} #endif/* CONFIG_PPC_PMAC32_PSURGE */
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.