forked from Minki/linux
3d1229d6ae
This patch merges, to some extent, the PPC32 and PPC64 kexec implementations.
We adopt the PPC32 approach of having ppc_md callbacks for the kexec functions.
The current PPC64 implementation becomes the "default" implementation for PPC64
which platforms can select if they need no special treatment.
I've added these default callbacks to pseries/maple/cell/powermac, this means
iSeries no longer supports kexec - but it never worked anyway.
I've renamed PPC32's machine_kexec_simple to default_machine_kexec, inline with
PPC64. Judging by the comments it might be better named machine_kexec_non_of,
or something, but at the moment it's the only implementation for PPC32 so it's
the "default".
Kexec requires machine_shutdown(), which is in machine_kexec.c on PPC32, but we
already have in setup-common.c on powerpc. All this does is call
ppc_md.nvram_sync, which only powermac implements, so instead make
machine_shutdown a ppc_md member and have it call core99_nvram_sync directly
on powermac.
I've also stuck relocate_kernel.S into misc_32.S for powerpc.
Built for ARCH=ppc, and 32 & 64 bit ARCH=powerpc, with KEXEC=y/n. Booted on
P5 LPAR and successfully kexec'ed.
Should apply on top of 493f25ef40
.
Signed-off-by: Michael Ellerman <michael@ellerman.id.au>
Signed-off-by: Paul Mackerras <paulus@samba.org>
592 lines
14 KiB
C
592 lines
14 KiB
C
/*
|
|
* Common boot and setup code for both 32-bit and 64-bit.
|
|
* Extracted from arch/powerpc/kernel/setup_64.c.
|
|
*
|
|
* Copyright (C) 2001 PPC64 Team, IBM Corp
|
|
*
|
|
* 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/module.h>
|
|
#include <linux/string.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/reboot.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/initrd.h>
|
|
#include <linux/ide.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/console.h>
|
|
#include <linux/utsname.h>
|
|
#include <linux/tty.h>
|
|
#include <linux/root_dev.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/unistd.h>
|
|
#include <linux/serial.h>
|
|
#include <linux/serial_8250.h>
|
|
#include <asm/io.h>
|
|
#include <asm/prom.h>
|
|
#include <asm/processor.h>
|
|
#include <asm/vdso_datapage.h>
|
|
#include <asm/pgtable.h>
|
|
#include <asm/smp.h>
|
|
#include <asm/elf.h>
|
|
#include <asm/machdep.h>
|
|
#include <asm/time.h>
|
|
#include <asm/cputable.h>
|
|
#include <asm/sections.h>
|
|
#include <asm/btext.h>
|
|
#include <asm/nvram.h>
|
|
#include <asm/setup.h>
|
|
#include <asm/system.h>
|
|
#include <asm/rtas.h>
|
|
#include <asm/iommu.h>
|
|
#include <asm/serial.h>
|
|
#include <asm/cache.h>
|
|
#include <asm/page.h>
|
|
#include <asm/mmu.h>
|
|
#include <asm/lmb.h>
|
|
#include <asm/xmon.h>
|
|
|
|
#include "setup.h"
|
|
|
|
#undef DEBUG
|
|
|
|
#ifdef DEBUG
|
|
#include <asm/udbg.h>
|
|
#define DBG(fmt...) udbg_printf(fmt)
|
|
#else
|
|
#define DBG(fmt...)
|
|
#endif
|
|
|
|
#ifdef CONFIG_PPC_MULTIPLATFORM
|
|
int _machine = 0;
|
|
EXPORT_SYMBOL(_machine);
|
|
#endif
|
|
|
|
unsigned long klimit = (unsigned long) _end;
|
|
|
|
/*
|
|
* This still seems to be needed... -- paulus
|
|
*/
|
|
struct screen_info screen_info = {
|
|
.orig_x = 0,
|
|
.orig_y = 25,
|
|
.orig_video_cols = 80,
|
|
.orig_video_lines = 25,
|
|
.orig_video_isVGA = 1,
|
|
.orig_video_points = 16
|
|
};
|
|
|
|
#ifdef __DO_IRQ_CANON
|
|
/* XXX should go elsewhere eventually */
|
|
int ppc_do_canonicalize_irqs;
|
|
EXPORT_SYMBOL(ppc_do_canonicalize_irqs);
|
|
#endif
|
|
|
|
/* also used by kexec */
|
|
void machine_shutdown(void)
|
|
{
|
|
if (ppc_md.machine_shutdown)
|
|
ppc_md.machine_shutdown();
|
|
}
|
|
|
|
void machine_restart(char *cmd)
|
|
{
|
|
machine_shutdown();
|
|
ppc_md.restart(cmd);
|
|
#ifdef CONFIG_SMP
|
|
smp_send_stop();
|
|
#endif
|
|
printk(KERN_EMERG "System Halted, OK to turn off power\n");
|
|
local_irq_disable();
|
|
while (1) ;
|
|
}
|
|
|
|
void machine_power_off(void)
|
|
{
|
|
machine_shutdown();
|
|
ppc_md.power_off();
|
|
#ifdef CONFIG_SMP
|
|
smp_send_stop();
|
|
#endif
|
|
printk(KERN_EMERG "System Halted, OK to turn off power\n");
|
|
local_irq_disable();
|
|
while (1) ;
|
|
}
|
|
/* Used by the G5 thermal driver */
|
|
EXPORT_SYMBOL_GPL(machine_power_off);
|
|
|
|
void (*pm_power_off)(void) = machine_power_off;
|
|
EXPORT_SYMBOL_GPL(pm_power_off);
|
|
|
|
void machine_halt(void)
|
|
{
|
|
machine_shutdown();
|
|
ppc_md.halt();
|
|
#ifdef CONFIG_SMP
|
|
smp_send_stop();
|
|
#endif
|
|
printk(KERN_EMERG "System Halted, OK to turn off power\n");
|
|
local_irq_disable();
|
|
while (1) ;
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_TAU
|
|
extern u32 cpu_temp(unsigned long cpu);
|
|
extern u32 cpu_temp_both(unsigned long cpu);
|
|
#endif /* CONFIG_TAU */
|
|
|
|
#ifdef CONFIG_SMP
|
|
DEFINE_PER_CPU(unsigned int, pvr);
|
|
#endif
|
|
|
|
static int show_cpuinfo(struct seq_file *m, void *v)
|
|
{
|
|
unsigned long cpu_id = (unsigned long)v - 1;
|
|
unsigned int pvr;
|
|
unsigned short maj;
|
|
unsigned short min;
|
|
|
|
if (cpu_id == NR_CPUS) {
|
|
#if defined(CONFIG_SMP) && defined(CONFIG_PPC32)
|
|
unsigned long bogosum = 0;
|
|
int i;
|
|
for (i = 0; i < NR_CPUS; ++i)
|
|
if (cpu_online(i))
|
|
bogosum += loops_per_jiffy;
|
|
seq_printf(m, "total bogomips\t: %lu.%02lu\n",
|
|
bogosum/(500000/HZ), bogosum/(5000/HZ) % 100);
|
|
#endif /* CONFIG_SMP && CONFIG_PPC32 */
|
|
seq_printf(m, "timebase\t: %lu\n", ppc_tb_freq);
|
|
|
|
if (ppc_md.show_cpuinfo != NULL)
|
|
ppc_md.show_cpuinfo(m);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* We only show online cpus: disable preempt (overzealous, I
|
|
* knew) to prevent cpu going down. */
|
|
preempt_disable();
|
|
if (!cpu_online(cpu_id)) {
|
|
preempt_enable();
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_SMP
|
|
pvr = per_cpu(pvr, cpu_id);
|
|
#else
|
|
pvr = mfspr(SPRN_PVR);
|
|
#endif
|
|
maj = (pvr >> 8) & 0xFF;
|
|
min = pvr & 0xFF;
|
|
|
|
seq_printf(m, "processor\t: %lu\n", cpu_id);
|
|
seq_printf(m, "cpu\t\t: ");
|
|
|
|
if (cur_cpu_spec->pvr_mask)
|
|
seq_printf(m, "%s", cur_cpu_spec->cpu_name);
|
|
else
|
|
seq_printf(m, "unknown (%08x)", pvr);
|
|
|
|
#ifdef CONFIG_ALTIVEC
|
|
if (cpu_has_feature(CPU_FTR_ALTIVEC))
|
|
seq_printf(m, ", altivec supported");
|
|
#endif /* CONFIG_ALTIVEC */
|
|
|
|
seq_printf(m, "\n");
|
|
|
|
#ifdef CONFIG_TAU
|
|
if (cur_cpu_spec->cpu_features & CPU_FTR_TAU) {
|
|
#ifdef CONFIG_TAU_AVERAGE
|
|
/* more straightforward, but potentially misleading */
|
|
seq_printf(m, "temperature \t: %u C (uncalibrated)\n",
|
|
cpu_temp(cpu_id));
|
|
#else
|
|
/* show the actual temp sensor range */
|
|
u32 temp;
|
|
temp = cpu_temp_both(cpu_id);
|
|
seq_printf(m, "temperature \t: %u-%u C (uncalibrated)\n",
|
|
temp & 0xff, temp >> 16);
|
|
#endif
|
|
}
|
|
#endif /* CONFIG_TAU */
|
|
|
|
/*
|
|
* Assume here that all clock rates are the same in a
|
|
* smp system. -- Cort
|
|
*/
|
|
if (ppc_proc_freq)
|
|
seq_printf(m, "clock\t\t: %lu.%06luMHz\n",
|
|
ppc_proc_freq / 1000000, ppc_proc_freq % 1000000);
|
|
|
|
if (ppc_md.show_percpuinfo != NULL)
|
|
ppc_md.show_percpuinfo(m, cpu_id);
|
|
|
|
/* If we are a Freescale core do a simple check so
|
|
* we dont have to keep adding cases in the future */
|
|
if (PVR_VER(pvr) & 0x8000) {
|
|
maj = PVR_MAJ(pvr);
|
|
min = PVR_MIN(pvr);
|
|
} else {
|
|
switch (PVR_VER(pvr)) {
|
|
case 0x0020: /* 403 family */
|
|
maj = PVR_MAJ(pvr) + 1;
|
|
min = PVR_MIN(pvr);
|
|
break;
|
|
case 0x1008: /* 740P/750P ?? */
|
|
maj = ((pvr >> 8) & 0xFF) - 1;
|
|
min = pvr & 0xFF;
|
|
break;
|
|
default:
|
|
maj = (pvr >> 8) & 0xFF;
|
|
min = pvr & 0xFF;
|
|
break;
|
|
}
|
|
}
|
|
|
|
seq_printf(m, "revision\t: %hd.%hd (pvr %04x %04x)\n",
|
|
maj, min, PVR_VER(pvr), PVR_REV(pvr));
|
|
|
|
#ifdef CONFIG_PPC32
|
|
seq_printf(m, "bogomips\t: %lu.%02lu\n",
|
|
loops_per_jiffy / (500000/HZ),
|
|
(loops_per_jiffy / (5000/HZ)) % 100);
|
|
#endif
|
|
|
|
#ifdef CONFIG_SMP
|
|
seq_printf(m, "\n");
|
|
#endif
|
|
|
|
preempt_enable();
|
|
return 0;
|
|
}
|
|
|
|
static void *c_start(struct seq_file *m, loff_t *pos)
|
|
{
|
|
unsigned long i = *pos;
|
|
|
|
return i <= NR_CPUS ? (void *)(i + 1) : NULL;
|
|
}
|
|
|
|
static void *c_next(struct seq_file *m, void *v, loff_t *pos)
|
|
{
|
|
++*pos;
|
|
return c_start(m, pos);
|
|
}
|
|
|
|
static void c_stop(struct seq_file *m, void *v)
|
|
{
|
|
}
|
|
|
|
struct seq_operations cpuinfo_op = {
|
|
.start =c_start,
|
|
.next = c_next,
|
|
.stop = c_stop,
|
|
.show = show_cpuinfo,
|
|
};
|
|
|
|
#ifdef CONFIG_PPC_MULTIPLATFORM
|
|
static int __init set_preferred_console(void)
|
|
{
|
|
struct device_node *prom_stdout = NULL;
|
|
char *name;
|
|
u32 *spd;
|
|
int offset = 0;
|
|
|
|
DBG(" -> set_preferred_console()\n");
|
|
|
|
/* The user has requested a console so this is already set up. */
|
|
if (strstr(saved_command_line, "console=")) {
|
|
DBG(" console was specified !\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (!of_chosen) {
|
|
DBG(" of_chosen is NULL !\n");
|
|
return -ENODEV;
|
|
}
|
|
/* We are getting a weird phandle from OF ... */
|
|
/* ... So use the full path instead */
|
|
name = (char *)get_property(of_chosen, "linux,stdout-path", NULL);
|
|
if (name == NULL) {
|
|
DBG(" no linux,stdout-path !\n");
|
|
return -ENODEV;
|
|
}
|
|
prom_stdout = of_find_node_by_path(name);
|
|
if (!prom_stdout) {
|
|
DBG(" can't find stdout package %s !\n", name);
|
|
return -ENODEV;
|
|
}
|
|
DBG("stdout is %s\n", prom_stdout->full_name);
|
|
|
|
name = (char *)get_property(prom_stdout, "name", NULL);
|
|
if (!name) {
|
|
DBG(" stdout package has no name !\n");
|
|
goto not_found;
|
|
}
|
|
spd = (u32 *)get_property(prom_stdout, "current-speed", NULL);
|
|
|
|
if (0)
|
|
;
|
|
#ifdef CONFIG_SERIAL_8250_CONSOLE
|
|
else if (strcmp(name, "serial") == 0) {
|
|
int i;
|
|
u32 *reg = (u32 *)get_property(prom_stdout, "reg", &i);
|
|
if (i > 8) {
|
|
switch (reg[1]) {
|
|
case 0x3f8:
|
|
offset = 0;
|
|
break;
|
|
case 0x2f8:
|
|
offset = 1;
|
|
break;
|
|
case 0x898:
|
|
offset = 2;
|
|
break;
|
|
case 0x890:
|
|
offset = 3;
|
|
break;
|
|
default:
|
|
/* We dont recognise the serial port */
|
|
goto not_found;
|
|
}
|
|
}
|
|
}
|
|
#endif /* CONFIG_SERIAL_8250_CONSOLE */
|
|
#ifdef CONFIG_PPC_PSERIES
|
|
else if (strcmp(name, "vty") == 0) {
|
|
u32 *reg = (u32 *)get_property(prom_stdout, "reg", NULL);
|
|
char *compat = (char *)get_property(prom_stdout, "compatible", NULL);
|
|
|
|
if (reg && compat && (strcmp(compat, "hvterm-protocol") == 0)) {
|
|
/* Host Virtual Serial Interface */
|
|
switch (reg[0]) {
|
|
case 0x30000000:
|
|
offset = 0;
|
|
break;
|
|
case 0x30000001:
|
|
offset = 1;
|
|
break;
|
|
default:
|
|
goto not_found;
|
|
}
|
|
of_node_put(prom_stdout);
|
|
DBG("Found hvsi console at offset %d\n", offset);
|
|
return add_preferred_console("hvsi", offset, NULL);
|
|
} else {
|
|
/* pSeries LPAR virtual console */
|
|
of_node_put(prom_stdout);
|
|
DBG("Found hvc console\n");
|
|
return add_preferred_console("hvc", 0, NULL);
|
|
}
|
|
}
|
|
#endif /* CONFIG_PPC_PSERIES */
|
|
#ifdef CONFIG_SERIAL_PMACZILOG_CONSOLE
|
|
else if (strcmp(name, "ch-a") == 0)
|
|
offset = 0;
|
|
else if (strcmp(name, "ch-b") == 0)
|
|
offset = 1;
|
|
#endif /* CONFIG_SERIAL_PMACZILOG_CONSOLE */
|
|
else
|
|
goto not_found;
|
|
of_node_put(prom_stdout);
|
|
|
|
DBG("Found serial console at ttyS%d\n", offset);
|
|
|
|
if (spd) {
|
|
static char __initdata opt[16];
|
|
sprintf(opt, "%d", *spd);
|
|
return add_preferred_console("ttyS", offset, opt);
|
|
} else
|
|
return add_preferred_console("ttyS", offset, NULL);
|
|
|
|
not_found:
|
|
DBG("No preferred console found !\n");
|
|
of_node_put(prom_stdout);
|
|
return -ENODEV;
|
|
}
|
|
console_initcall(set_preferred_console);
|
|
#endif /* CONFIG_PPC_MULTIPLATFORM */
|
|
|
|
void __init check_for_initrd(void)
|
|
{
|
|
#ifdef CONFIG_BLK_DEV_INITRD
|
|
unsigned long *prop;
|
|
|
|
DBG(" -> check_for_initrd()\n");
|
|
|
|
if (of_chosen) {
|
|
prop = (unsigned long *)get_property(of_chosen,
|
|
"linux,initrd-start", NULL);
|
|
if (prop != NULL) {
|
|
initrd_start = (unsigned long)__va(*prop);
|
|
prop = (unsigned long *)get_property(of_chosen,
|
|
"linux,initrd-end", NULL);
|
|
if (prop != NULL) {
|
|
initrd_end = (unsigned long)__va(*prop);
|
|
initrd_below_start_ok = 1;
|
|
} else
|
|
initrd_start = 0;
|
|
}
|
|
}
|
|
|
|
/* If we were passed an initrd, set the ROOT_DEV properly if the values
|
|
* look sensible. If not, clear initrd reference.
|
|
*/
|
|
if (initrd_start >= KERNELBASE && initrd_end >= KERNELBASE &&
|
|
initrd_end > initrd_start)
|
|
ROOT_DEV = Root_RAM0;
|
|
else
|
|
initrd_start = initrd_end = 0;
|
|
|
|
if (initrd_start)
|
|
printk("Found initrd at 0x%lx:0x%lx\n", initrd_start, initrd_end);
|
|
|
|
DBG(" <- check_for_initrd()\n");
|
|
#endif /* CONFIG_BLK_DEV_INITRD */
|
|
}
|
|
|
|
#ifdef CONFIG_SMP
|
|
|
|
/**
|
|
* setup_cpu_maps - initialize the following cpu maps:
|
|
* cpu_possible_map
|
|
* cpu_present_map
|
|
* cpu_sibling_map
|
|
*
|
|
* Having the possible map set up early allows us to restrict allocations
|
|
* of things like irqstacks to num_possible_cpus() rather than NR_CPUS.
|
|
*
|
|
* We do not initialize the online map here; cpus set their own bits in
|
|
* cpu_online_map as they come up.
|
|
*
|
|
* This function is valid only for Open Firmware systems. finish_device_tree
|
|
* must be called before using this.
|
|
*
|
|
* While we're here, we may as well set the "physical" cpu ids in the paca.
|
|
*/
|
|
void __init smp_setup_cpu_maps(void)
|
|
{
|
|
struct device_node *dn = NULL;
|
|
int cpu = 0;
|
|
int swap_cpuid = 0;
|
|
|
|
while ((dn = of_find_node_by_type(dn, "cpu")) && cpu < NR_CPUS) {
|
|
int *intserv;
|
|
int j, len = sizeof(u32), nthreads = 1;
|
|
|
|
intserv = (int *)get_property(dn, "ibm,ppc-interrupt-server#s",
|
|
&len);
|
|
if (intserv)
|
|
nthreads = len / sizeof(int);
|
|
else {
|
|
intserv = (int *) get_property(dn, "reg", NULL);
|
|
if (!intserv)
|
|
intserv = &cpu; /* assume logical == phys */
|
|
}
|
|
|
|
for (j = 0; j < nthreads && cpu < NR_CPUS; j++) {
|
|
cpu_set(cpu, cpu_present_map);
|
|
set_hard_smp_processor_id(cpu, intserv[j]);
|
|
|
|
if (intserv[j] == boot_cpuid_phys)
|
|
swap_cpuid = cpu;
|
|
cpu_set(cpu, cpu_possible_map);
|
|
cpu++;
|
|
}
|
|
}
|
|
|
|
/* Swap CPU id 0 with boot_cpuid_phys, so we can always assume that
|
|
* boot cpu is logical 0.
|
|
*/
|
|
if (boot_cpuid_phys != get_hard_smp_processor_id(0)) {
|
|
u32 tmp;
|
|
tmp = get_hard_smp_processor_id(0);
|
|
set_hard_smp_processor_id(0, boot_cpuid_phys);
|
|
set_hard_smp_processor_id(swap_cpuid, tmp);
|
|
}
|
|
|
|
#ifdef CONFIG_PPC64
|
|
/*
|
|
* On pSeries LPAR, we need to know how many cpus
|
|
* could possibly be added to this partition.
|
|
*/
|
|
if (_machine == PLATFORM_PSERIES_LPAR &&
|
|
(dn = of_find_node_by_path("/rtas"))) {
|
|
int num_addr_cell, num_size_cell, maxcpus;
|
|
unsigned int *ireg;
|
|
|
|
num_addr_cell = prom_n_addr_cells(dn);
|
|
num_size_cell = prom_n_size_cells(dn);
|
|
|
|
ireg = (unsigned int *)
|
|
get_property(dn, "ibm,lrdr-capacity", NULL);
|
|
|
|
if (!ireg)
|
|
goto out;
|
|
|
|
maxcpus = ireg[num_addr_cell + num_size_cell];
|
|
|
|
/* Double maxcpus for processors which have SMT capability */
|
|
if (cpu_has_feature(CPU_FTR_SMT))
|
|
maxcpus *= 2;
|
|
|
|
if (maxcpus > NR_CPUS) {
|
|
printk(KERN_WARNING
|
|
"Partition configured for %d cpus, "
|
|
"operating system maximum is %d.\n",
|
|
maxcpus, NR_CPUS);
|
|
maxcpus = NR_CPUS;
|
|
} else
|
|
printk(KERN_INFO "Partition configured for %d cpus.\n",
|
|
maxcpus);
|
|
|
|
for (cpu = 0; cpu < maxcpus; cpu++)
|
|
cpu_set(cpu, cpu_possible_map);
|
|
out:
|
|
of_node_put(dn);
|
|
}
|
|
|
|
/*
|
|
* Do the sibling map; assume only two threads per processor.
|
|
*/
|
|
for_each_cpu(cpu) {
|
|
cpu_set(cpu, cpu_sibling_map[cpu]);
|
|
if (cpu_has_feature(CPU_FTR_SMT))
|
|
cpu_set(cpu ^ 0x1, cpu_sibling_map[cpu]);
|
|
}
|
|
|
|
vdso_data->processorCount = num_present_cpus();
|
|
#endif /* CONFIG_PPC64 */
|
|
}
|
|
#endif /* CONFIG_SMP */
|
|
|
|
#ifdef CONFIG_XMON
|
|
static int __init early_xmon(char *p)
|
|
{
|
|
/* ensure xmon is enabled */
|
|
if (p) {
|
|
if (strncmp(p, "on", 2) == 0)
|
|
xmon_init(1);
|
|
if (strncmp(p, "off", 3) == 0)
|
|
xmon_init(0);
|
|
if (strncmp(p, "early", 5) != 0)
|
|
return 0;
|
|
}
|
|
xmon_init(1);
|
|
debugger(NULL);
|
|
|
|
return 0;
|
|
}
|
|
early_param("xmon", early_xmon);
|
|
#endif
|