cregit-Linux how code gets into the kernel

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

PersonTokensPropCommitsCommitProp
Paul Mackerras35100.00%2100.00%
Total35100.00%2100.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

PersonTokensPropCommitsCommitProp
Paul Mackerras14590.62%266.67%
Benjamin Herrenschmidt159.38%133.33%
Total160100.00%3100.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

PersonTokensPropCommitsCommitProp
Paul Mackerras18189.60%360.00%
Benjamin Herrenschmidt188.91%120.00%
Olaf Hering31.49%120.00%
Total202100.00%5100.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

PersonTokensPropCommitsCommitProp
Paul Mackerras11695.08%150.00%
Anton Blanchard64.92%150.00%
Total122100.00%2100.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

PersonTokensPropCommitsCommitProp
Paul Mackerras11498.28%150.00%
Anton Blanchard21.72%150.00%
Total116100.00%2100.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

PersonTokensPropCommitsCommitProp
Michael Neuling9197.85%150.00%
Anton Blanchard22.15%150.00%
Total93100.00%2100.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

PersonTokensPropCommitsCommitProp
Paul Mackerras20555.11%120.00%
Benjamin Herrenschmidt12433.33%120.00%
Michael Neuling349.14%120.00%
Daniel Axtens51.34%120.00%
Anton Blanchard41.08%120.00%
Total372100.00%5100.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

PersonTokensPropCommitsCommitProp
Paul Mackerras19596.06%266.67%
Anton Blanchard83.94%133.33%
Total203100.00%3100.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

PersonTokensPropCommitsCommitProp
Paul Mackerras121100.00%1100.00%
Total121100.00%1100.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

PersonTokensPropCommitsCommitProp
Paul Mackerras5196.23%133.33%
Benjamin Herrenschmidt11.89%133.33%
Tobias Klauser11.89%133.33%
Total53100.00%3100.00%


static inline int prom_getproplen(phandle node, const char *pname) { return call_prom("getproplen", 2, 1, node, ADDR(pname)); }

Contributors

PersonTokensPropCommitsCommitProp
Paul Mackerras3093.75%250.00%
Benjamin Herrenschmidt13.12%125.00%
Tobias Klauser13.12%125.00%
Total32100.00%4100.00%


static void add_string(char **str, const char *q) { char *p = *str; while (*q) *p++ = *q++; *p++ = ' '; *str = p; }

Contributors

PersonTokensPropCommitsCommitProp
Paul Mackerras47100.00%1100.00%
Total47100.00%1100.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

PersonTokensPropCommitsCommitProp
Paul Mackerras79100.00%1100.00%
Total79100.00%1100.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

PersonTokensPropCommitsCommitProp
Paul Mackerras186100.00%2100.00%
Total186100.00%2100.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

PersonTokensPropCommitsCommitProp
Benjamin Krill12499.20%150.00%
Benjamin Herrenschmidt10.80%150.00%
Total125100.00%2100.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

PersonTokensPropCommitsCommitProp
Benjamin Krill10799.07%150.00%
Benjamin Herrenschmidt10.93%150.00%
Total108100.00%2100.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");