Release 4.14 arch/powerpc/kernel/prom_init.c
/*
* Procedures for interfacing to Open Firmware.
*
* Paul Mackerras August 1996.
* Copyright (C) 1996-2005 Paul Mackerras.
*
* Adapted for 64bit PowerPC by Dave Engebretsen and Peter Bergner.
* {engebret|bergner}@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.
*/
#undef DEBUG_PROM
/* we cannot use FORTIFY as it brings in new symbols */
#define __NO_FORTIFY
#include <stdarg.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/threads.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/pci.h>
#include <linux/proc_fs.h>
#include <linux/stringify.h>
#include <linux/delay.h>
#include <linux/initrd.h>
#include <linux/bitops.h>
#include <asm/prom.h>
#include <asm/rtas.h>
#include <asm/page.h>
#include <asm/processor.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/smp.h>
#include <asm/mmu.h>
#include <asm/pgtable.h>
#include <asm/iommu.h>
#include <asm/btext.h>
#include <asm/sections.h>
#include <asm/machdep.h>
#include <asm/opal.h>
#include <asm/asm-prototypes.h>
#include <linux/linux_logo.h>
/*
* Eventually bump that one up
*/
#define DEVTREE_CHUNK_SIZE 0x100000
/*
* This is the size of the local memory reserve map that gets copied
* into the boot params passed to the kernel. That size is totally
* flexible as the kernel just reads the list until it encounters an
* entry with size 0, so it can be changed without breaking binary
* compatibility
*/
#define MEM_RESERVE_MAP_SIZE 8
/*
* prom_init() is called very early on, before the kernel text
* and data have been mapped to KERNELBASE. At this point the code
* is running at whatever address it has been loaded at.
* On ppc32 we compile with -mrelocatable, which means that references
* to extern and static variables get relocated automatically.
* ppc64 objects are always relocatable, we just need to relocate the
* TOC.
*
* Because OF may have mapped I/O devices into the area starting at
* KERNELBASE, particularly on CHRP machines, we can't safely call
* OF once the kernel has been mapped to KERNELBASE. Therefore all
* OF calls must be done within prom_init().
*
* ADDR is used in calls to call_prom. The 4th and following
* arguments to call_prom should be 32-bit values.
* On ppc64, 64 bit values are truncated to 32 bits (and
* fortunately don't get interpreted as two arguments).
*/
#define ADDR(x) (u32)(unsigned long)(x)
#ifdef CONFIG_PPC64
#define OF_WORKAROUNDS 0
#else
#define OF_WORKAROUNDS of_workarounds
int of_workarounds;
#endif
#define OF_WA_CLAIM 1
/* do phys/virt claim separately, then map */
#define OF_WA_LONGTRAIL 2
/* work around longtrail bugs */
#define PROM_BUG() do { \
prom_printf("kernel BUG at %s line 0x%x!\n", \
__FILE__, __LINE__); \
__asm__ __volatile__(".long " BUG_ILLEGAL_INSTR); \
} while (0)
#ifdef DEBUG_PROM
#define prom_debug(x...) prom_printf(x)
#else
#define prom_debug(x...)
#endif
typedef u32 prom_arg_t;
struct prom_args {
__be32 service;
__be32 nargs;
__be32 nret;
__be32 args[10];
};
struct prom_t {
ihandle root;
phandle chosen;
int cpu;
ihandle stdout;
ihandle mmumap;
ihandle memory;
};
struct mem_map_entry {
__be64 base;
__be64 size;
};
typedef __be32 cell_t;
extern void __start(unsigned long r3, unsigned long r4, unsigned long r5,
unsigned long r6, unsigned long r7, unsigned long r8,
unsigned long r9);
#ifdef CONFIG_PPC64
extern int enter_prom(struct prom_args *args, unsigned long entry);
#else
static inline int enter_prom(struct prom_args *args, unsigned long entry)
{
return ((int (*)(struct prom_args *))entry)(args);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Paul Mackerras | 35 | 100.00% | 2 | 100.00% |
Total | 35 | 100.00% | 2 | 100.00% |
#endif
extern void copy_and_flush(unsigned long dest, unsigned long src,
unsigned long size, unsigned long offset);
/* prom structure */
static struct prom_t __initdata prom;
static unsigned long prom_entry __initdata;
#define PROM_SCRATCH_SIZE 256
static char __initdata of_stdout_device[256];
static char __initdata prom_scratch[PROM_SCRATCH_SIZE];
static unsigned long __initdata dt_header_start;
static unsigned long __initdata dt_struct_start, dt_struct_end;
static unsigned long __initdata dt_string_start, dt_string_end;
static unsigned long __initdata prom_initrd_start, prom_initrd_end;
#ifdef CONFIG_PPC64
static int __initdata prom_iommu_force_on;
static int __initdata prom_iommu_off;
static unsigned long __initdata prom_tce_alloc_start;
static unsigned long __initdata prom_tce_alloc_end;
#endif
static bool __initdata prom_radix_disable;
struct platform_support {
bool hash_mmu;
bool radix_mmu;
bool radix_gtse;
bool xive;
};
/* Platforms codes are now obsolete in the kernel. Now only used within this
* file and ultimately gone too. Feel free to change them if you need, they
* are not shared with anything outside of this file anymore
*/
#define PLATFORM_PSERIES 0x0100
#define PLATFORM_PSERIES_LPAR 0x0101
#define PLATFORM_LPAR 0x0001
#define PLATFORM_POWERMAC 0x0400
#define PLATFORM_GENERIC 0x0500
#define PLATFORM_OPAL 0x0600
static int __initdata of_platform;
static char __initdata prom_cmd_line[COMMAND_LINE_SIZE];
static unsigned long __initdata prom_memory_limit;
static unsigned long __initdata alloc_top;
static unsigned long __initdata alloc_top_high;
static unsigned long __initdata alloc_bottom;
static unsigned long __initdata rmo_top;
static unsigned long __initdata ram_top;
static struct mem_map_entry __initdata mem_reserve_map[MEM_RESERVE_MAP_SIZE];
static int __initdata mem_reserve_cnt;
static cell_t __initdata regbuf[1024];
static bool rtas_has_query_cpu_stopped;
/*
* Error results ... some OF calls will return "-1" on error, some
* will return 0, some will return either. To simplify, here are
* macros to use with any ihandle or phandle return value to check if
* it is valid
*/
#define PROM_ERROR (-1u)
#define PHANDLE_VALID(p) ((p) != 0 && (p) != PROM_ERROR)
#define IHANDLE_VALID(i) ((i) != 0 && (i) != PROM_ERROR)
/* This is the one and *ONLY* place where we actually call open
* firmware.
*/
static int __init call_prom(const char *service, int nargs, int nret, ...)
{
int i;
struct prom_args args;
va_list list;
args.service = cpu_to_be32(ADDR(service));
args.nargs = cpu_to_be32(nargs);
args.nret = cpu_to_be32(nret);
va_start(list, nret);
for (i = 0; i < nargs; i++)
args.args[i] = cpu_to_be32(va_arg(list, prom_arg_t));
va_end(list);
for (i = 0; i < nret; i++)
args.args[nargs+i] = 0;
if (enter_prom(&args, prom_entry) < 0)
return PROM_ERROR;
return (nret > 0) ? be32_to_cpu(args.args[nargs]) : 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Paul Mackerras | 145 | 90.62% | 2 | 66.67% |
Benjamin Herrenschmidt | 15 | 9.38% | 1 | 33.33% |
Total | 160 | 100.00% | 3 | 100.00% |
static int __init call_prom_ret(const char *service, int nargs, int nret,
prom_arg_t *rets, ...)
{
int i;
struct prom_args args;
va_list list;
args.service = cpu_to_be32(ADDR(service));
args.nargs = cpu_to_be32(nargs);
args.nret = cpu_to_be32(nret);
va_start(list, rets);
for (i = 0; i < nargs; i++)
args.args[i] = cpu_to_be32(va_arg(list, prom_arg_t));
va_end(list);
for (i = 0; i < nret; i++)
args.args[nargs+i] = 0;
if (enter_prom(&args, prom_entry) < 0)
return PROM_ERROR;
if (rets != NULL)
for (i = 1; i < nret; ++i)
rets[i-1] = be32_to_cpu(args.args[nargs+i]);
return (nret > 0) ? be32_to_cpu(args.args[nargs]) : 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Paul Mackerras | 181 | 89.60% | 3 | 60.00% |
Benjamin Herrenschmidt | 18 | 8.91% | 1 | 20.00% |
Olaf Hering | 3 | 1.49% | 1 | 20.00% |
Total | 202 | 100.00% | 5 | 100.00% |
static void __init prom_print(const char *msg)
{
const char *p, *q;
if (prom.stdout == 0)
return;
for (p = msg; *p != 0; p = q) {
for (q = p; *q != 0 && *q != '\n'; ++q)
;
if (q > p)
call_prom("write", 3, 1, prom.stdout, p, q - p);
if (*q == 0)
break;
++q;
call_prom("write", 3, 1, prom.stdout, ADDR("\r\n"), 2);
}
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Paul Mackerras | 116 | 95.08% | 1 | 50.00% |
Anton Blanchard | 6 | 4.92% | 1 | 50.00% |
Total | 122 | 100.00% | 2 | 100.00% |
static void __init prom_print_hex(unsigned long val)
{
int i, nibbles = sizeof(val)*2;
char buf[sizeof(val)*2+1];
for (i = nibbles-1; i >= 0; i--) {
buf[i] = (val & 0xf) + '0';
if (buf[i] > '9')
buf[i] += ('a'-'0'-10);
val >>= 4;
}
buf[nibbles] = '\0';
call_prom("write", 3, 1, prom.stdout, buf, nibbles);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Paul Mackerras | 114 | 98.28% | 1 | 50.00% |
Anton Blanchard | 2 | 1.72% | 1 | 50.00% |
Total | 116 | 100.00% | 2 | 100.00% |
/* max number of decimal digits in an unsigned long */
#define UL_DIGITS 21
static void __init prom_print_dec(unsigned long val)
{
int i, size;
char buf[UL_DIGITS+1];
for (i = UL_DIGITS-1; i >= 0; i--) {
buf[i] = (val % 10) + '0';
val = val/10;
if (val == 0)
break;
}
/* shift stuff down */
size = UL_DIGITS - i;
call_prom("write", 3, 1, prom.stdout, buf+i, size);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Michael Neuling | 91 | 97.85% | 1 | 50.00% |
Anton Blanchard | 2 | 2.15% | 1 | 50.00% |
Total | 93 | 100.00% | 2 | 100.00% |
static void __init prom_printf(const char *format, ...)
{
const char *p, *q, *s;
va_list args;
unsigned long v;
long vs;
va_start(args, format);
for (p = format; *p != 0; p = q) {
for (q = p; *q != 0 && *q != '\n' && *q != '%'; ++q)
;
if (q > p)
call_prom("write", 3, 1, prom.stdout, p, q - p);
if (*q == 0)
break;
if (*q == '\n') {
++q;
call_prom("write", 3, 1, prom.stdout,
ADDR("\r\n"), 2);
continue;
}
++q;
if (*q == 0)
break;
switch (*q) {
case 's':
++q;
s = va_arg(args, const char *);
prom_print(s);
break;
case 'x':
++q;
v = va_arg(args, unsigned long);
prom_print_hex(v);
break;
case 'd':
++q;
vs = va_arg(args, int);
if (vs < 0) {
prom_print("-");
vs = -vs;
}
prom_print_dec(vs);
break;
case 'l':
++q;
if (*q == 0)
break;
else if (*q == 'x') {
++q;
v = va_arg(args, unsigned long);
prom_print_hex(v);
} else if (*q == 'u') { /* '%lu' */
++q;
v = va_arg(args, unsigned long);
prom_print_dec(v);
} else if (*q == 'd') { /* %ld */
++q;
vs = va_arg(args, long);
if (vs < 0) {
prom_print("-");
vs = -vs;
}
prom_print_dec(vs);
}
break;
}
}
va_end(args);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Paul Mackerras | 205 | 55.11% | 1 | 20.00% |
Benjamin Herrenschmidt | 124 | 33.33% | 1 | 20.00% |
Michael Neuling | 34 | 9.14% | 1 | 20.00% |
Daniel Axtens | 5 | 1.34% | 1 | 20.00% |
Anton Blanchard | 4 | 1.08% | 1 | 20.00% |
Total | 372 | 100.00% | 5 | 100.00% |
static unsigned int __init prom_claim(unsigned long virt, unsigned long size,
unsigned long align)
{
if (align == 0 && (OF_WORKAROUNDS & OF_WA_CLAIM)) {
/*
* Old OF requires we claim physical and virtual separately
* and then map explicitly (assuming virtual mode)
*/
int ret;
prom_arg_t result;
ret = call_prom_ret("call-method", 5, 2, &result,
ADDR("claim"), prom.memory,
align, size, virt);
if (ret != 0 || result == -1)
return -1;
ret = call_prom_ret("call-method", 5, 2, &result,
ADDR("claim"), prom.mmumap,
align, size, virt);
if (ret != 0) {
call_prom("call-method", 4, 1, ADDR("release"),
prom.memory, size, virt);
return -1;
}
/* the 0x12 is M (coherence) + PP == read/write */
call_prom("call-method", 6, 1,
ADDR("map"), prom.mmumap, 0x12, size, virt, virt);
return virt;
}
return call_prom("claim", 3, 1, (prom_arg_t)virt, (prom_arg_t)size,
(prom_arg_t)align);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Paul Mackerras | 195 | 96.06% | 2 | 66.67% |
Anton Blanchard | 8 | 3.94% | 1 | 33.33% |
Total | 203 | 100.00% | 3 | 100.00% |
static void __init __attribute__((noreturn)) prom_panic(const char *reason)
{
prom_print(reason);
/* Do not call exit because it clears the screen on pmac
* it also causes some sort of double-fault on early pmacs */
if (of_platform == PLATFORM_POWERMAC)
asm("trap\n");
/* ToDo: should put up an SRC here on pSeries */
call_prom("exit", 0, 0);
for (;;) /* should never get here */
;
}
static int __init prom_next_node(phandle *nodep)
{
phandle node;
if ((node = *nodep) != 0
&& (*nodep = call_prom("child", 1, 1, node)) != 0)
return 1;
if ((*nodep = call_prom("peer", 1, 1, node)) != 0)
return 1;
for (;;) {
if ((node = call_prom("parent", 1, 1, node)) == 0)
return 0;
if ((*nodep = call_prom("peer", 1, 1, node)) != 0)
return 1;
}
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Paul Mackerras | 121 | 100.00% | 1 | 100.00% |
Total | 121 | 100.00% | 1 | 100.00% |
static inline int prom_getprop(phandle node, const char *pname,
void *value, size_t valuelen)
{
return call_prom("getprop", 4, 1, node, ADDR(pname),
(u32)(unsigned long) value, (u32) valuelen);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Paul Mackerras | 51 | 96.23% | 1 | 33.33% |
Benjamin Herrenschmidt | 1 | 1.89% | 1 | 33.33% |
Tobias Klauser | 1 | 1.89% | 1 | 33.33% |
Total | 53 | 100.00% | 3 | 100.00% |
static inline int prom_getproplen(phandle node, const char *pname)
{
return call_prom("getproplen", 2, 1, node, ADDR(pname));
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Paul Mackerras | 30 | 93.75% | 2 | 50.00% |
Benjamin Herrenschmidt | 1 | 3.12% | 1 | 25.00% |
Tobias Klauser | 1 | 3.12% | 1 | 25.00% |
Total | 32 | 100.00% | 4 | 100.00% |
static void add_string(char **str, const char *q)
{
char *p = *str;
while (*q)
*p++ = *q++;
*p++ = ' ';
*str = p;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Paul Mackerras | 47 | 100.00% | 1 | 100.00% |
Total | 47 | 100.00% | 1 | 100.00% |
static char *tohex(unsigned int x)
{
static char digits[] = "0123456789abcdef";
static char result[9];
int i;
result[8] = 0;
i = 8;
do {
--i;
result[i] = digits[x & 0xf];
x >>= 4;
} while (x != 0 && i > 0);
return &result[i];
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Paul Mackerras | 79 | 100.00% | 1 | 100.00% |
Total | 79 | 100.00% | 1 | 100.00% |
static int __init prom_setprop(phandle node, const char *nodename,
const char *pname, void *value, size_t valuelen)
{
char cmd[256], *p;
if (!(OF_WORKAROUNDS & OF_WA_LONGTRAIL))
return call_prom("setprop", 4, 1, node, ADDR(pname),
(u32)(unsigned long) value, (u32) valuelen);
/* gah... setprop doesn't work on longtrail, have to use interpret */
p = cmd;
add_string(&p, "dev");
add_string(&p, nodename);
add_string(&p, tohex((u32)(unsigned long) value));
add_string(&p, tohex(valuelen));
add_string(&p, tohex(ADDR(pname)));
add_string(&p, tohex(strlen(pname)));
add_string(&p, "property");
*p = 0;
return call_prom("interpret", 1, 1, (u32)(unsigned long) cmd);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Paul Mackerras | 186 | 100.00% | 2 | 100.00% |
Total | 186 | 100.00% | 2 | 100.00% |
/* We can't use the standard versions because of relocation headaches. */
#define isxdigit(c) (('0' <= (c) && (c) <= '9') \
|| ('a' <= (c) && (c) <= 'f') \
|| ('A' <= (c) && (c) <= 'F'))
#define isdigit(c) ('0' <= (c) && (c) <= '9')
#define islower(c) ('a' <= (c) && (c) <= 'z')
#define toupper(c) (islower(c) ? ((c) - 'a' + 'A') : (c))
static unsigned long prom_strtoul(const char *cp, const char **endp)
{
unsigned long result = 0, base = 10, value;
if (*cp == '0') {
base = 8;
cp++;
if (toupper(*cp) == 'X') {
cp++;
base = 16;
}
}
while (isxdigit(*cp) &&
(value = isdigit(*cp) ? *cp - '0' : toupper(*cp) - 'A' + 10) < base) {
result = result * base + value;
cp++;
}
if (endp)
*endp = cp;
return result;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Benjamin Krill | 124 | 99.20% | 1 | 50.00% |
Benjamin Herrenschmidt | 1 | 0.80% | 1 | 50.00% |
Total | 125 | 100.00% | 2 | 100.00% |
static unsigned long prom_memparse(const char *ptr, const char **retptr)
{
unsigned long ret = prom_strtoul(ptr, retptr);
int shift = 0;
/*
* We can't use a switch here because GCC *may* generate a
* jump table which won't work, because we're not running at
* the address we're linked at.
*/
if ('G' == **retptr || 'g' == **retptr)
shift = 30;
if ('M' == **retptr || 'm' == **retptr)
shift = 20;
if ('K' == **retptr || 'k' == **retptr)
shift = 10;
if (shift) {
ret <<= shift;
(*retptr)++;
}
return ret;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Benjamin Krill | 107 | 99.07% | 1 | 50.00% |
Benjamin Herrenschmidt | 1 | 0.93% | 1 | 50.00% |
Total | 108 | 100.00% | 2 | 100.00% |
/*
* Early parsing of the command line passed to the kernel, used for
* "mem=x" and the options that affect the iommu
*/
static void __init early_cmdline_parse(void)
{
const char *opt;
char *p;
int l = 0;
prom_cmd_line[0] = 0;
p = prom_cmd_line;
if ((long)prom.chosen > 0)
l = prom_getprop(prom.chosen, "bootargs", p, COMMAND_LINE_SIZE-1);
#ifdef CONFIG_CMDLINE
if (l <= 0 || p[0] == '\0') /* dbl check */
strlcpy(prom_cmd_line,
CONFIG_CMDLINE, sizeof(prom_cmd_line));
#endif /* CONFIG_CMDLINE */
prom_printf("command line: %s\n", prom_cmd_line);
#ifdef CONFIG_PPC64
opt = strstr(prom_cmd_line, "iommu=");
if (opt) {
prom_printf("iommu opt is: %s\n", opt);
opt += 6;
while (*opt && *opt == ' ')
opt++;
if (!strncmp(opt, "off", 3))
prom_iommu_off = 1;
else if (!strncmp(opt, "force", 5))
prom_iommu_force_on = 1;
}
#endif
opt = strstr(prom_cmd_line, "mem=");
if (opt) {
opt += 4;
prom_memory_limit = prom_memparse(opt, (const char **)&opt);
#ifdef CONFIG_PPC64
/* Align to 16 MB == size of ppc64 large page */
prom_memory_limit = ALIGN(prom_memory_limit, 0x1000000);
#endif
}
opt = strstr(prom_cmd_line, "disable_radix");