diff options
| author | Jinke Han <jinkehan@didiglobal.com> | 2026-09-08 15:37:42 +0800 |
|---|---|---|
| committer | Ingo Molnar <mingo@kernel.org> | 2026-09-17 09:48:42 +0200 |
| commit | a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch) | |
| tree | 73fef824f9a846cf935869d0dbc5070ef6e2f741 /arch/sparc/prom | |
| download | linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip | |
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
When using eBPF to probe CS CALL instructions within a function,
a crash can be triggered.
The eBPF tool probes offset 257 of the __hrtimer_run_queues()
function:
<__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1)
<__hrtimer_run_queues+254>: mov %r14,%rdi
<__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12>
<__hrtimer_run_queues+263>: mov %eax,%r12d
<__hrtimer_run_queues+266>: xchg %ax,%ax
<__hrtimer_run_queues+268>: mov %r13,%rdi
Which triggers this crash:
BUG: unable to handle page fault for address: 00000000000f41c9
#PF: supervisor write access in kernel mode
#PF: error_code(0x0002) - not-present page
PGD 0 P4D 0
Oops: 0002 [#1] SMP NOPTI
CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P
RIP: 0010:__hrtimer_run_queues+0x106/0x230
Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is
at the 6th byte of the above CS CALL instruction. Since the CS CALL
instruction occupies 6 bytes, the exception occurred in the middle of that
call instruction.
The root cause is that when using eBPF tools to probe in the middle of a
function, a kprobe with INT3 is used as the underlying implementation.
During single-step emulation of the original CALL instruction,
int3_emulate_call() assumes that the probed CALL instruction is 5 bytes
long. However, the actual CS-prefixed CALL instruction occupies 6 bytes,
so it constructs an incorrect exception return address. When the CPU
returns from the kprobe handler, the next instruction to be executed is at
the address of the last byte of that CS CALL instruction. Coincidentally,
starting from that address, the CPU fetches and decodes a completely
different instruction, which ultimately triggers a kernel crash.
Fix the issue by using the actual instruction length obtained from
the instruction decoder when constructing the exception return
address, rather than relying on the hardcoded CALL_INSN_SIZE macro.
[ mingo: Refined the changelog ]
Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step")
Suggested-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Signed-off-by: Jinke Han <jinkehan@didiglobal.com>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Acked-by: Yafang Shao <laoar.shao@gmail.com>
Acked-by: Borislav Petkov <bp@alien8.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'arch/sparc/prom')
| -rw-r--r-- | arch/sparc/prom/Makefile | 16 | ||||
| -rw-r--r-- | arch/sparc/prom/bootstr_32.c | 63 | ||||
| -rw-r--r-- | arch/sparc/prom/bootstr_64.c | 44 | ||||
| -rw-r--r-- | arch/sparc/prom/cif.S | 48 | ||||
| -rw-r--r-- | arch/sparc/prom/console_32.c | 57 | ||||
| -rw-r--r-- | arch/sparc/prom/console_64.c | 46 | ||||
| -rw-r--r-- | arch/sparc/prom/init_32.c | 76 | ||||
| -rw-r--r-- | arch/sparc/prom/init_64.c | 54 | ||||
| -rw-r--r-- | arch/sparc/prom/memory.c | 88 | ||||
| -rw-r--r-- | arch/sparc/prom/misc_32.c | 128 | ||||
| -rw-r--r-- | arch/sparc/prom/misc_64.c | 447 | ||||
| -rw-r--r-- | arch/sparc/prom/mp.c | 44 | ||||
| -rw-r--r-- | arch/sparc/prom/p1275.c | 55 | ||||
| -rw-r--r-- | arch/sparc/prom/printf.c | 70 | ||||
| -rw-r--r-- | arch/sparc/prom/ranges.c | 114 | ||||
| -rw-r--r-- | arch/sparc/prom/tree_32.c | 310 | ||||
| -rw-r--r-- | arch/sparc/prom/tree_64.c | 393 |
17 files changed, 2053 insertions, 0 deletions
diff --git a/arch/sparc/prom/Makefile b/arch/sparc/prom/Makefile new file mode 100644 index 000000000..92db8bb4a --- /dev/null +++ b/arch/sparc/prom/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for the Sun Boot PROM interface library under +# Linux. +# + +lib-y := bootstr_$(BITS).o +lib-y += init_$(BITS).o +lib-$(CONFIG_SPARC32) += memory.o +lib-y += misc_$(BITS).o +lib-$(CONFIG_SPARC32) += mp.o +lib-$(CONFIG_SPARC32) += ranges.o +lib-y += console_$(BITS).o +lib-y += printf.o +lib-y += tree_$(BITS).o +lib-$(CONFIG_SPARC64) += p1275.o +lib-$(CONFIG_SPARC64) += cif.o diff --git a/arch/sparc/prom/bootstr_32.c b/arch/sparc/prom/bootstr_32.c new file mode 100644 index 000000000..1c7cd258b --- /dev/null +++ b/arch/sparc/prom/bootstr_32.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * bootstr.c: Boot string/argument acquisition from the PROM. + * + * Copyright(C) 1995 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <linux/string.h> +#include <asm/oplib.h> +#include <linux/init.h> + +#define BARG_LEN 256 +static char barg_buf[BARG_LEN] = { 0 }; +static char fetched __initdata = 0; + +char * __init +prom_getbootargs(void) +{ + int iter; + char *cp, *arg; + + /* This check saves us from a panic when bootfd patches args. */ + if (fetched) { + return barg_buf; + } + + switch (prom_vers) { + case PROM_V0: + cp = barg_buf; + /* Start from 1 and go over fd(0,0,0)kernel */ + for (iter = 1; iter < 8; iter++) { + arg = (*(romvec->pv_v0bootargs))->argv[iter]; + if (arg == NULL) + break; + while (*arg != 0) { + /* Leave place for space and null. */ + if (cp >= barg_buf + BARG_LEN - 2) + /* We might issue a warning here. */ + break; + *cp++ = *arg++; + } + *cp++ = ' '; + if (cp >= barg_buf + BARG_LEN - 1) + /* We might issue a warning here. */ + break; + } + *cp = 0; + break; + case PROM_V2: + case PROM_V3: + /* + * V3 PROM cannot supply as with more than 128 bytes + * of an argument. But a smart bootstrap loader can. + */ + strscpy(barg_buf, *romvec->pv_v2bootargs.bootargs, sizeof(barg_buf)); + break; + default: + break; + } + + fetched = 1; + return barg_buf; +} diff --git a/arch/sparc/prom/bootstr_64.c b/arch/sparc/prom/bootstr_64.c new file mode 100644 index 000000000..f1cc34d99 --- /dev/null +++ b/arch/sparc/prom/bootstr_64.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * bootstr.c: Boot string/argument acquisition from the PROM. + * + * Copyright(C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright(C) 1996,1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/string.h> +#include <linux/init.h> +#include <asm/oplib.h> + +/* WARNING: The boot loader knows that these next three variables come one right + * after another in the .data section. Do not move this stuff into + * the .bss section or it will break things. + */ + +/* We limit BARG_LEN to 1024 because this is the size of the + * 'barg_out' command line buffer in the SILO bootloader. + */ +#define BARG_LEN 1024 +struct { + int bootstr_len; + int bootstr_valid; + char bootstr_buf[BARG_LEN]; +} bootstr_info = { + .bootstr_len = BARG_LEN, +#ifdef CONFIG_CMDLINE + .bootstr_valid = 1, + .bootstr_buf = CONFIG_CMDLINE, +#endif +}; + +char * __init +prom_getbootargs(void) +{ + /* This check saves us from a panic when bootfd patches args. */ + if (bootstr_info.bootstr_valid) + return bootstr_info.bootstr_buf; + prom_getstring(prom_chosen_node, "bootargs", + bootstr_info.bootstr_buf, BARG_LEN); + bootstr_info.bootstr_valid = 1; + return bootstr_info.bootstr_buf; +} diff --git a/arch/sparc/prom/cif.S b/arch/sparc/prom/cif.S new file mode 100644 index 000000000..dd06bb1fb --- /dev/null +++ b/arch/sparc/prom/cif.S @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* cif.S: PROM entry/exit assembler trampolines. + * + * Copyright (C) 1996, 1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + * Copyright (C) 2005, 2006 David S. Miller <davem@davemloft.net> + */ + +#include <asm/pstate.h> +#include <asm/cpudata.h> +#include <asm/thread_info.h> + + .text + .globl prom_cif_direct +prom_cif_direct: + save %sp, -192, %sp + sethi %hi(p1275buf), %o1 + or %o1, %lo(p1275buf), %o1 + ldx [%o1 + 0x0008], %l2 ! prom_cif_handler + mov %g4, %l0 + mov %g5, %l1 + mov %g6, %l3 + call %l2 + mov %i0, %o0 ! prom_args + mov %l0, %g4 + mov %l1, %g5 + mov %l3, %g6 + ret + restore + + .globl prom_cif_callback +prom_cif_callback: + sethi %hi(p1275buf), %o1 + or %o1, %lo(p1275buf), %o1 + save %sp, -192, %sp + TRAP_LOAD_THREAD_REG(%g6, %g1) + LOAD_PER_CPU_BASE(%g5, %g6, %g4, %g3, %o0) + ldx [%g6 + TI_TASK], %g4 + call prom_world + mov 0, %o0 + ldx [%i1 + 0x000], %l2 + call %l2 + mov %i0, %o0 + mov %o0, %l1 + call prom_world + mov 1, %o0 + ret + restore %l1, 0, %o0 + diff --git a/arch/sparc/prom/console_32.c b/arch/sparc/prom/console_32.c new file mode 100644 index 000000000..6404e5bbb --- /dev/null +++ b/arch/sparc/prom/console_32.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * console.c: Routines that deal with sending and receiving IO + * to/from the current console device using the PROM. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1998 Pete Zaitcev <zaitcev@yahoo.com> + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <linux/string.h> + +extern void restore_current(void); + +/* Non blocking put character to console device, returns -1 if + * unsuccessful. + */ +static int prom_nbputchar(const char *buf) +{ + unsigned long flags; + int i = -1; + + spin_lock_irqsave(&prom_lock, flags); + switch(prom_vers) { + case PROM_V0: + if ((*(romvec->pv_nbputchar))(*buf)) + i = 1; + break; + case PROM_V2: + case PROM_V3: + if ((*(romvec->pv_v2devops).v2_dev_write)(*romvec->pv_v2bootargs.fd_stdout, + buf, 0x1) == 1) + i = 1; + break; + default: + break; + } + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + return i; /* Ugh, we could spin forever on unsupported proms ;( */ +} + +void prom_console_write_buf(const char *buf, int len) +{ + while (len) { + int n = prom_nbputchar(buf); + if (n < 0) + continue; + len--; + buf++; + } +} + diff --git a/arch/sparc/prom/console_64.c b/arch/sparc/prom/console_64.c new file mode 100644 index 000000000..86dace787 --- /dev/null +++ b/arch/sparc/prom/console_64.c @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0 +/* console.c: Routines that deal with sending and receiving IO + * to/from the current console device using the PROM. + * + * Copyright (C) 1995 David S. Miller (davem@davemloft.net) + * Copyright (C) 1996,1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <linux/string.h> + +static int __prom_console_write_buf(const char *buf, int len) +{ + unsigned long args[7]; + int ret; + + args[0] = (unsigned long) "write"; + args[1] = 3; + args[2] = 1; + args[3] = (unsigned int) prom_stdout; + args[4] = (unsigned long) buf; + args[5] = (unsigned int) len; + args[6] = (unsigned long) -1; + + p1275_cmd_direct(args); + + ret = (int) args[6]; + if (ret < 0) + return -1; + return ret; +} + +void prom_console_write_buf(const char *buf, int len) +{ + while (len) { + int n = __prom_console_write_buf(buf, len); + if (n < 0) + continue; + len -= n; + buf += len; + } +} diff --git a/arch/sparc/prom/init_32.c b/arch/sparc/prom/init_32.c new file mode 100644 index 000000000..d20470166 --- /dev/null +++ b/arch/sparc/prom/init_32.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * init.c: Initialize internal variables used by the PROM + * library functions. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> + +#include <asm/openprom.h> +#include <asm/oplib.h> + +struct linux_romvec *romvec; +EXPORT_SYMBOL(romvec); + +enum prom_major_version prom_vers; +unsigned int prom_rev, prom_prev; + +/* The root node of the prom device tree. */ +phandle prom_root_node; +EXPORT_SYMBOL(prom_root_node); + +/* Pointer to the device tree operations structure. */ +struct linux_nodeops *prom_nodeops; + +/* You must call prom_init() before you attempt to use any of the + * routines in the prom library. + * It gets passed the pointer to the PROM vector. + */ + +void __init prom_init(struct linux_romvec *rp) +{ + romvec = rp; + + switch(romvec->pv_romvers) { + case 0: + prom_vers = PROM_V0; + break; + case 2: + prom_vers = PROM_V2; + break; + case 3: + prom_vers = PROM_V3; + break; + default: + prom_printf("PROMLIB: Bad PROM version %d\n", + romvec->pv_romvers); + prom_halt(); + break; + } + + prom_rev = romvec->pv_plugin_revision; + prom_prev = romvec->pv_printrev; + prom_nodeops = romvec->pv_nodeops; + + prom_root_node = prom_getsibling(0); + if ((prom_root_node == 0) || ((s32)prom_root_node == -1)) + prom_halt(); + + if((((unsigned long) prom_nodeops) == 0) || + (((unsigned long) prom_nodeops) == -1)) + prom_halt(); + + prom_meminit(); + + prom_ranges_init(); + + printk("PROMLIB: Sun Boot Prom Version %d Revision %d\n", + romvec->pv_romvers, prom_rev); + + /* Initialization successful. */ +} diff --git a/arch/sparc/prom/init_64.c b/arch/sparc/prom/init_64.c new file mode 100644 index 000000000..f7b8a1a86 --- /dev/null +++ b/arch/sparc/prom/init_64.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * init.c: Initialize internal variables used by the PROM + * library functions. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1996,1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/string.h> +#include <linux/ctype.h> + +#include <asm/openprom.h> +#include <asm/oplib.h> + +/* OBP version string. */ +char prom_version[80]; + +/* The root node of the prom device tree. */ +int prom_stdout; +phandle prom_chosen_node; + +/* You must call prom_init() before you attempt to use any of the + * routines in the prom library. + * It gets passed the pointer to the PROM vector. + */ +void __init prom_init(void *cif_handler) +{ + phandle node; + + prom_cif_init(cif_handler); + + prom_chosen_node = prom_finddevice(prom_chosen_path); + if (!prom_chosen_node || (s32)prom_chosen_node == -1) + prom_halt(); + + prom_stdout = prom_getint(prom_chosen_node, "stdout"); + + node = prom_finddevice("/openprom"); + if (!node || (s32)node == -1) + prom_halt(); + + prom_getstring(node, "version", prom_version, sizeof(prom_version)); + + prom_printf("\n"); +} + +void __init prom_init_report(void) +{ + printk("PROMLIB: Sun IEEE Boot Prom '%s'\n", prom_version); + printk("PROMLIB: Root node compatible: %s\n", prom_root_compatible); +} diff --git a/arch/sparc/prom/memory.c b/arch/sparc/prom/memory.c new file mode 100644 index 000000000..269d6ab5e --- /dev/null +++ b/arch/sparc/prom/memory.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0 +/* memory.c: Prom routine for acquiring various bits of information + * about RAM on the machine, both virtual and physical. + * + * Copyright (C) 1995, 2008 David S. Miller (davem@davemloft.net) + * Copyright (C) 1997 Michael A. Griffith (grif@acm.org) + */ + +#include <linux/kernel.h> +#include <linux/sort.h> +#include <linux/init.h> + +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/page.h> + +static int __init prom_meminit_v0(void) +{ + struct linux_mlist_v0 *p; + int index; + + index = 0; + for (p = *(romvec->pv_v0mem.v0_available); p; p = p->theres_more) { + sp_banks[index].base_addr = (unsigned long) p->start_adr; + sp_banks[index].num_bytes = p->num_bytes; + index++; + } + + return index; +} + +static int __init prom_meminit_v2(void) +{ + struct linux_prom_registers reg[64]; + phandle node; + int size, num_ents, i; + + node = prom_searchsiblings(prom_getchild(prom_root_node), "memory"); + size = prom_getproperty(node, "available", (char *) reg, sizeof(reg)); + num_ents = size / sizeof(struct linux_prom_registers); + + for (i = 0; i < num_ents; i++) { + sp_banks[i].base_addr = reg[i].phys_addr; + sp_banks[i].num_bytes = reg[i].reg_size; + } + + return num_ents; +} + +static int sp_banks_cmp(const void *a, const void *b) +{ + const struct sparc_phys_banks *x = a, *y = b; + + if (x->base_addr > y->base_addr) + return 1; + if (x->base_addr < y->base_addr) + return -1; + return 0; +} + +/* Initialize the memory lists based upon the prom version. */ +void __init prom_meminit(void) +{ + int i, num_ents = 0; + + switch (prom_vers) { + case PROM_V0: + num_ents = prom_meminit_v0(); + break; + + case PROM_V2: + case PROM_V3: + num_ents = prom_meminit_v2(); + break; + + default: + break; + } + sort(sp_banks, num_ents, sizeof(struct sparc_phys_banks), + sp_banks_cmp, NULL); + + /* Sentinel. */ + sp_banks[num_ents].base_addr = 0xdeadbeef; + sp_banks[num_ents].num_bytes = 0; + + for (i = 0; i < num_ents; i++) + sp_banks[i].num_bytes &= PAGE_MASK; +} diff --git a/arch/sparc/prom/misc_32.c b/arch/sparc/prom/misc_32.c new file mode 100644 index 000000000..625750924 --- /dev/null +++ b/arch/sparc/prom/misc_32.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * misc.c: Miscellaneous prom functions that don't belong + * anywhere else. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/module.h> + +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/auxio.h> + +extern void restore_current(void); + +DEFINE_SPINLOCK(prom_lock); + +/* Reset and reboot the machine with the command 'bcommand'. */ +void +prom_reboot(char *bcommand) +{ + unsigned long flags; + spin_lock_irqsave(&prom_lock, flags); + (*(romvec->pv_reboot))(bcommand); + /* Never get here. */ + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); +} + +/* Forth evaluate the expression contained in 'fstring'. */ +void +prom_feval(char *fstring) +{ + unsigned long flags; + if(!fstring || fstring[0] == 0) + return; + spin_lock_irqsave(&prom_lock, flags); + if(prom_vers == PROM_V0) + (*(romvec->pv_fortheval.v0_eval))(strlen(fstring), fstring); + else + (*(romvec->pv_fortheval.v2_eval))(fstring); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); +} +EXPORT_SYMBOL(prom_feval); + +/* Drop into the prom, with the chance to continue with the 'go' + * prom command. + */ +void +prom_cmdline(void) +{ + unsigned long flags; + + spin_lock_irqsave(&prom_lock, flags); + (*(romvec->pv_abort))(); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + set_auxio(AUXIO_LED, 0); +} + +/* Drop into the prom, but completely terminate the program. + * No chance of continuing. + */ +void __noreturn +prom_halt(void) +{ + unsigned long flags; +again: + spin_lock_irqsave(&prom_lock, flags); + (*(romvec->pv_halt))(); + /* Never get here. */ + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + goto again; /* PROM is out to get me -DaveM */ +} + +typedef void (*sfunc_t)(void); + +/* Set prom sync handler to call function 'funcp'. */ +void +prom_setsync(sfunc_t funcp) +{ + if(!funcp) return; + *romvec->pv_synchook = funcp; +} + +/* Get the idprom and stuff it into buffer 'idbuf'. Returns the + * format type. 'num_bytes' is the number of bytes that your idbuf + * has space for. Returns 0xff on error. + */ +unsigned char +prom_get_idprom(char *idbuf, int num_bytes) +{ + int len; + + len = prom_getproplen(prom_root_node, "idprom"); + if((len>num_bytes) || (len==-1)) return 0xff; + if(!prom_getproperty(prom_root_node, "idprom", idbuf, num_bytes)) + return idbuf[0]; + + return 0xff; +} + +/* Get the major prom version number. */ +int +prom_version(void) +{ + return romvec->pv_romvers; +} + +/* Get the prom plugin-revision. */ +int +prom_getrev(void) +{ + return prom_rev; +} + +/* Get the prom firmware print revision. */ +int +prom_getprev(void) +{ + return prom_prev; +} diff --git a/arch/sparc/prom/misc_64.c b/arch/sparc/prom/misc_64.c new file mode 100644 index 000000000..3792736ff --- /dev/null +++ b/arch/sparc/prom/misc_64.c @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * misc.c: Miscellaneous prom functions that don't belong + * anywhere else. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1996,1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/module.h> + +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/ldc.h> + +static int prom_service_exists(const char *service_name) +{ + unsigned long args[5]; + + args[0] = (unsigned long) "test"; + args[1] = 1; + args[2] = 1; + args[3] = (unsigned long) service_name; + args[4] = (unsigned long) -1; + + p1275_cmd_direct(args); + + if (args[4]) + return 0; + return 1; +} + +void prom_sun4v_guest_soft_state(void) +{ + const char *svc = "SUNW,soft-state-supported"; + unsigned long args[3]; + + if (!prom_service_exists(svc)) + return; + args[0] = (unsigned long) svc; + args[1] = 0; + args[2] = 0; + p1275_cmd_direct(args); +} + +/* Reset and reboot the machine with the command 'bcommand'. */ +void prom_reboot(const char *bcommand) +{ + unsigned long args[4]; + +#ifdef CONFIG_SUN_LDOMS + if (ldom_domaining_enabled) + ldom_reboot(bcommand); +#endif + args[0] = (unsigned long) "boot"; + args[1] = 1; + args[2] = 0; + args[3] = (unsigned long) bcommand; + + p1275_cmd_direct(args); +} + +/* Forth evaluate the expression contained in 'fstring'. */ +void prom_feval(const char *fstring) +{ + unsigned long args[5]; + + if (!fstring || fstring[0] == 0) + return; + args[0] = (unsigned long) "interpret"; + args[1] = 1; + args[2] = 1; + args[3] = (unsigned long) fstring; + args[4] = (unsigned long) -1; + + p1275_cmd_direct(args); +} +EXPORT_SYMBOL(prom_feval); + +/* Drop into the prom, with the chance to continue with the 'go' + * prom command. + */ +void prom_cmdline(void) +{ + unsigned long args[3]; + unsigned long flags; + + local_irq_save(flags); + +#ifdef CONFIG_SMP + smp_capture(); +#endif + + args[0] = (unsigned long) "enter"; + args[1] = 0; + args[2] = 0; + + p1275_cmd_direct(args); + +#ifdef CONFIG_SMP + smp_release(); +#endif + + local_irq_restore(flags); +} + +/* Drop into the prom, but completely terminate the program. + * No chance of continuing. + */ +void notrace prom_halt(void) +{ + unsigned long args[3]; + +#ifdef CONFIG_SUN_LDOMS + if (ldom_domaining_enabled) + ldom_power_off(); +#endif +again: + args[0] = (unsigned long) "exit"; + args[1] = 0; + args[2] = 0; + p1275_cmd_direct(args); + goto again; /* PROM is out to get me -DaveM */ +} + +void prom_halt_power_off(void) +{ + unsigned long args[3]; + +#ifdef CONFIG_SUN_LDOMS + if (ldom_domaining_enabled) + ldom_power_off(); +#endif + args[0] = (unsigned long) "SUNW,power-off"; + args[1] = 0; + args[2] = 0; + p1275_cmd_direct(args); + + /* if nothing else helps, we just halt */ + prom_halt(); +} + +/* Get the idprom and stuff it into buffer 'idbuf'. Returns the + * format type. 'num_bytes' is the number of bytes that your idbuf + * has space for. Returns 0xff on error. + */ +unsigned char prom_get_idprom(char *idbuf, int num_bytes) +{ + int len; + + len = prom_getproplen(prom_root_node, "idprom"); + if ((len >num_bytes) || (len == -1)) + return 0xff; + if (!prom_getproperty(prom_root_node, "idprom", idbuf, num_bytes)) + return idbuf[0]; + + return 0xff; +} + +static int prom_get_mmu_ihandle(void) +{ + phandle node; + int ret; + + if (prom_mmu_ihandle_cache != 0) + return prom_mmu_ihandle_cache; + + node = prom_finddevice(prom_chosen_path); + ret = prom_getint(node, prom_mmu_name); + if (ret == -1 || ret == 0) + prom_mmu_ihandle_cache = -1; + else + prom_mmu_ihandle_cache = ret; + + return ret; +} + +static int prom_get_memory_ihandle(void) +{ + static int memory_ihandle_cache; + phandle node; + int ret; + + if (memory_ihandle_cache != 0) + return memory_ihandle_cache; + + node = prom_finddevice("/chosen"); + ret = prom_getint(node, "memory"); + if (ret == -1 || ret == 0) + memory_ihandle_cache = -1; + else + memory_ihandle_cache = ret; + + return ret; +} + +/* Load explicit I/D TLB entries. */ +static long tlb_load(const char *type, unsigned long index, + unsigned long tte_data, unsigned long vaddr) +{ + unsigned long args[9]; + + args[0] = (unsigned long) prom_callmethod_name; + args[1] = 5; + args[2] = 1; + args[3] = (unsigned long) type; + args[4] = (unsigned int) prom_get_mmu_ihandle(); + args[5] = vaddr; + args[6] = tte_data; + args[7] = index; + args[8] = (unsigned long) -1; + + p1275_cmd_direct(args); + + return (long) args[8]; +} + +long prom_itlb_load(unsigned long index, + unsigned long tte_data, + unsigned long vaddr) +{ + return tlb_load("SUNW,itlb-load", index, tte_data, vaddr); +} + +long prom_dtlb_load(unsigned long index, + unsigned long tte_data, + unsigned long vaddr) +{ + return tlb_load("SUNW,dtlb-load", index, tte_data, vaddr); +} + +int prom_map(int mode, unsigned long size, + unsigned long vaddr, unsigned long paddr) +{ + unsigned long args[11]; + int ret; + + args[0] = (unsigned long) prom_callmethod_name; + args[1] = 7; + args[2] = 1; + args[3] = (unsigned long) prom_map_name; + args[4] = (unsigned int) prom_get_mmu_ihandle(); + args[5] = (unsigned int) mode; + args[6] = size; + args[7] = vaddr; + args[8] = 0; + args[9] = paddr; + args[10] = (unsigned long) -1; + + p1275_cmd_direct(args); + + ret = (int) args[10]; + if (ret == 0) + ret = -1; + return ret; +} + +void prom_unmap(unsigned long size, unsigned long vaddr) +{ + unsigned long args[7]; + + args[0] = (unsigned long) prom_callmethod_name; + args[1] = 4; + args[2] = 0; + args[3] = (unsigned long) prom_unmap_name; + args[4] = (unsigned int) prom_get_mmu_ihandle(); + args[5] = size; + args[6] = vaddr; + + p1275_cmd_direct(args); +} + +/* Set aside physical memory which is not touched or modified + * across soft resets. + */ +int prom_retain(const char *name, unsigned long size, + unsigned long align, unsigned long *paddr) +{ + unsigned long args[11]; + + args[0] = (unsigned long) prom_callmethod_name; + args[1] = 5; + args[2] = 3; + args[3] = (unsigned long) "SUNW,retain"; + args[4] = (unsigned int) prom_get_memory_ihandle(); + args[5] = align; + args[6] = size; + args[7] = (unsigned long) name; + args[8] = (unsigned long) -1; + args[9] = (unsigned long) -1; + args[10] = (unsigned long) -1; + + p1275_cmd_direct(args); + + if (args[8]) + return (int) args[8]; + + /* Next we get "phys_high" then "phys_low". On 64-bit + * the phys_high cell is don't care since the phys_low + * cell has the full value. + */ + *paddr = args[10]; + + return 0; +} + +/* Get "Unumber" string for the SIMM at the given + * memory address. Usually this will be of the form + * "Uxxxx" where xxxx is a decimal number which is + * etched into the motherboard next to the SIMM slot + * in question. + */ +int prom_getunumber(int syndrome_code, + unsigned long phys_addr, + char *buf, int buflen) +{ + unsigned long args[12]; + + args[0] = (unsigned long) prom_callmethod_name; + args[1] = 7; + args[2] = 2; + args[3] = (unsigned long) "SUNW,get-unumber"; + args[4] = (unsigned int) prom_get_memory_ihandle(); + args[5] = buflen; + args[6] = (unsigned long) buf; + args[7] = 0; + args[8] = phys_addr; + args[9] = (unsigned int) syndrome_code; + args[10] = (unsigned long) -1; + args[11] = (unsigned long) -1; + + p1275_cmd_direct(args); + + return (int) args[10]; +} + +/* Power management extensions. */ +void prom_sleepself(void) +{ + unsigned long args[3]; + + args[0] = (unsigned long) "SUNW,sleep-self"; + args[1] = 0; + args[2] = 0; + p1275_cmd_direct(args); +} + +int prom_sleepsystem(void) +{ + unsigned long args[4]; + + args[0] = (unsigned long) "SUNW,sleep-system"; + args[1] = 0; + args[2] = 1; + args[3] = (unsigned long) -1; + p1275_cmd_direct(args); + + return (int) args[3]; +} + +int prom_wakeupsystem(void) +{ + unsigned long args[4]; + + args[0] = (unsigned long) "SUNW,wakeup-system"; + args[1] = 0; + args[2] = 1; + args[3] = (unsigned long) -1; + p1275_cmd_direct(args); + + return (int) args[3]; +} + +#ifdef CONFIG_SMP +void prom_startcpu(int cpunode, unsigned long pc, unsigned long arg) +{ + unsigned long args[6]; + + args[0] = (unsigned long) "SUNW,start-cpu"; + args[1] = 3; + args[2] = 0; + args[3] = (unsigned int) cpunode; + args[4] = pc; + args[5] = arg; + p1275_cmd_direct(args); +} + +void prom_startcpu_cpuid(int cpuid, unsigned long pc, unsigned long arg) +{ + unsigned long args[6]; + + args[0] = (unsigned long) "SUNW,start-cpu-by-cpuid"; + args[1] = 3; + args[2] = 0; + args[3] = (unsigned int) cpuid; + args[4] = pc; + args[5] = arg; + p1275_cmd_direct(args); +} + +void prom_stopcpu_cpuid(int cpuid) +{ + unsigned long args[4]; + + args[0] = (unsigned long) "SUNW,stop-cpu-by-cpuid"; + args[1] = 1; + args[2] = 0; + args[3] = (unsigned int) cpuid; + p1275_cmd_direct(args); +} + +void prom_stopself(void) +{ + unsigned long args[3]; + + args[0] = (unsigned long) "SUNW,stop-self"; + args[1] = 0; + args[2] = 0; + p1275_cmd_direct(args); +} + +void prom_idleself(void) +{ + unsigned long args[3]; + + args[0] = (unsigned long) "SUNW,idle-self"; + args[1] = 0; + args[2] = 0; + p1275_cmd_direct(args); +} + +void prom_resumecpu(int cpunode) +{ + unsigned long args[4]; + + args[0] = (unsigned long) "SUNW,resume-cpu"; + args[1] = 1; + args[2] = 0; + args[3] = (unsigned int) cpunode; + p1275_cmd_direct(args); +} +#endif diff --git a/arch/sparc/prom/mp.c b/arch/sparc/prom/mp.c new file mode 100644 index 000000000..67cf0e957 --- /dev/null +++ b/arch/sparc/prom/mp.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mp.c: OpenBoot Prom Multiprocessor support routines. Don't call + * these on a UP or else you will halt and catch fire. ;) + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> + +#include <asm/openprom.h> +#include <asm/oplib.h> + +extern void restore_current(void); + +/* Start cpu with prom-tree node 'cpunode' using context described + * by 'ctable_reg' in context 'ctx' at program counter 'pc'. + * + * XXX Have to look into what the return values mean. XXX + */ +int +prom_startcpu(int cpunode, struct linux_prom_registers *ctable_reg, int ctx, char *pc) +{ + int ret; + unsigned long flags; + + spin_lock_irqsave(&prom_lock, flags); + switch(prom_vers) { + case PROM_V0: + case PROM_V2: + default: + ret = -1; + break; + case PROM_V3: + ret = (*(romvec->v3_cpustart))(cpunode, (int) ctable_reg, ctx, pc); + break; + } + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + + return ret; +} diff --git a/arch/sparc/prom/p1275.c b/arch/sparc/prom/p1275.c new file mode 100644 index 000000000..51c3f984b --- /dev/null +++ b/arch/sparc/prom/p1275.c @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * p1275.c: Sun IEEE 1275 PROM low level interface routines + * + * Copyright (C) 1996,1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/smp.h> +#include <linux/string.h> +#include <linux/spinlock.h> +#include <linux/irqflags.h> + +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/spitfire.h> +#include <asm/pstate.h> +#include <asm/ldc.h> + +struct { + long prom_callback; /* 0x00 */ + void (*prom_cif_handler)(long *); /* 0x08 */ +} p1275buf; + +extern void prom_world(int); + +extern void prom_cif_direct(unsigned long *args); +extern void prom_cif_callback(void); + +/* + * This provides SMP safety on the p1275buf. + */ +DEFINE_RAW_SPINLOCK(prom_entry_lock); + +void p1275_cmd_direct(unsigned long *args) +{ + unsigned long flags; + + local_save_flags(flags); + local_irq_restore((unsigned long)PIL_NMI); + raw_spin_lock(&prom_entry_lock); + + prom_world(1); + prom_cif_direct(args); + prom_world(0); + + raw_spin_unlock(&prom_entry_lock); + local_irq_restore(flags); +} + +void prom_cif_init(void *cif_handler) +{ + p1275buf.prom_cif_handler = (void (*)(long *))cif_handler; +} diff --git a/arch/sparc/prom/printf.c b/arch/sparc/prom/printf.c new file mode 100644 index 000000000..dcee3dfa6 --- /dev/null +++ b/arch/sparc/prom/printf.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * printf.c: Internal prom library printf facility. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + * Copyright (c) 2002 Pete Zaitcev (zaitcev@yahoo.com) + * + * We used to warn all over the code: DO NOT USE prom_printf(), + * and yet people do. Anton's banking code was outputting banks + * with prom_printf for most of the 2.4 lifetime. Since an effective + * stick is not available, we deployed a carrot: an early printk + * through PROM by means of -p boot option. This ought to fix it. + * USE printk; if you need, deploy -p. + */ + +#include <linux/kernel.h> +#include <linux/compiler.h> +#include <linux/spinlock.h> + +#include <asm/openprom.h> +#include <asm/oplib.h> + +#define CONSOLE_WRITE_BUF_SIZE 1024 + +static char ppbuf[1024]; +static char console_write_buf[CONSOLE_WRITE_BUF_SIZE]; +static DEFINE_RAW_SPINLOCK(console_write_lock); + +void notrace prom_write(const char *buf, unsigned int n) +{ + unsigned int dest_len; + unsigned long flags; + char *dest; + + dest = console_write_buf; + raw_spin_lock_irqsave(&console_write_lock, flags); + + dest_len = 0; + while (n-- != 0) { + char ch = *buf++; + if (ch == '\n') { + *dest++ = '\r'; + dest_len++; + } + *dest++ = ch; + dest_len++; + if (dest_len >= CONSOLE_WRITE_BUF_SIZE - 1) { + prom_console_write_buf(console_write_buf, dest_len); + dest = console_write_buf; + dest_len = 0; + } + } + if (dest_len) + prom_console_write_buf(console_write_buf, dest_len); + + raw_spin_unlock_irqrestore(&console_write_lock, flags); +} + +void notrace prom_printf(const char *fmt, ...) +{ + va_list args; + int i; + + va_start(args, fmt); + i = vscnprintf(ppbuf, sizeof(ppbuf), fmt, args); + va_end(args); + + prom_write(ppbuf, i); +} diff --git a/arch/sparc/prom/ranges.c b/arch/sparc/prom/ranges.c new file mode 100644 index 000000000..20cb828bc --- /dev/null +++ b/arch/sparc/prom/ranges.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ranges.c: Handle ranges in newer proms for obio/sbus. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/init.h> +#include <linux/module.h> + +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/types.h> + +static struct linux_prom_ranges promlib_obio_ranges[PROMREG_MAX]; +static int num_obio_ranges; + +/* Adjust register values based upon the ranges parameters. */ +static void prom_adjust_regs(struct linux_prom_registers *regp, int nregs, + struct linux_prom_ranges *rangep, int nranges) +{ + int regc, rngc; + + for (regc = 0; regc < nregs; regc++) { + for (rngc = 0; rngc < nranges; rngc++) + if (regp[regc].which_io == rangep[rngc].ot_child_space) + break; /* Fount it */ + if (rngc == nranges) /* oops */ + prom_printf("adjust_regs: Could not find range with matching bus type...\n"); + regp[regc].which_io = rangep[rngc].ot_parent_space; + regp[regc].phys_addr -= rangep[rngc].ot_child_base; + regp[regc].phys_addr += rangep[rngc].ot_parent_base; + } +} + +static void prom_adjust_ranges(struct linux_prom_ranges *ranges1, int nranges1, + struct linux_prom_ranges *ranges2, int nranges2) +{ + int rng1c, rng2c; + + for (rng1c = 0; rng1c < nranges1; rng1c++) { + for (rng2c = 0; rng2c < nranges2; rng2c++) + if (ranges1[rng1c].ot_parent_space == ranges2[rng2c].ot_child_space && + ranges1[rng1c].ot_parent_base >= ranges2[rng2c].ot_child_base && + ranges2[rng2c].ot_child_base + ranges2[rng2c].or_size - ranges1[rng1c].ot_parent_base > 0U) + break; + if (rng2c == nranges2) /* oops */ + prom_printf("adjust_ranges: Could not find matching bus type...\n"); + else if (ranges1[rng1c].ot_parent_base + ranges1[rng1c].or_size > ranges2[rng2c].ot_child_base + ranges2[rng2c].or_size) + ranges1[rng1c].or_size = ranges2[rng2c].ot_child_base + ranges2[rng2c].or_size - ranges1[rng1c].ot_parent_base; + ranges1[rng1c].ot_parent_space = ranges2[rng2c].ot_parent_space; + ranges1[rng1c].ot_parent_base += ranges2[rng2c].ot_parent_base; + } +} + +/* Apply probed obio ranges to registers passed, if no ranges return. */ +void prom_apply_obio_ranges(struct linux_prom_registers *regs, int nregs) +{ + if (num_obio_ranges) + prom_adjust_regs(regs, nregs, promlib_obio_ranges, num_obio_ranges); +} +EXPORT_SYMBOL(prom_apply_obio_ranges); + +void __init prom_ranges_init(void) +{ + phandle node, obio_node; + int success; + + num_obio_ranges = 0; + + /* Check for obio and sbus ranges. */ + node = prom_getchild(prom_root_node); + obio_node = prom_searchsiblings(node, "obio"); + + if (obio_node) { + success = prom_getproperty(obio_node, "ranges", + (char *) promlib_obio_ranges, + sizeof(promlib_obio_ranges)); + if (success != -1) + num_obio_ranges = (success / sizeof(struct linux_prom_ranges)); + } + + if (num_obio_ranges) + prom_printf("PROMLIB: obio_ranges %d\n", num_obio_ranges); +} + +void prom_apply_generic_ranges(phandle node, phandle parent, + struct linux_prom_registers *regs, int nregs) +{ + int success; + int num_ranges; + struct linux_prom_ranges ranges[PROMREG_MAX]; + + success = prom_getproperty(node, "ranges", + (char *) ranges, + sizeof(ranges)); + if (success != -1) { + num_ranges = (success / sizeof(struct linux_prom_ranges)); + if (parent) { + struct linux_prom_ranges parent_ranges[PROMREG_MAX]; + int num_parent_ranges; + + success = prom_getproperty(parent, "ranges", + (char *) parent_ranges, + sizeof(parent_ranges)); + if (success != -1) { + num_parent_ranges = (success / sizeof(struct linux_prom_ranges)); + prom_adjust_ranges(ranges, num_ranges, parent_ranges, num_parent_ranges); + } + } + prom_adjust_regs(regs, nregs, ranges, num_ranges); + } +} diff --git a/arch/sparc/prom/tree_32.c b/arch/sparc/prom/tree_32.c new file mode 100644 index 000000000..0fed89375 --- /dev/null +++ b/arch/sparc/prom/tree_32.c @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * tree.c: Basic device tree traversal/scanning for the Linux + * prom library. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <linux/string.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/ctype.h> +#include <linux/module.h> + +#include <asm/openprom.h> +#include <asm/oplib.h> + +extern void restore_current(void); + +static char promlib_buf[128]; + +/* Internal version of prom_getchild that does not alter return values. */ +static phandle __prom_getchild(phandle node) +{ + unsigned long flags; + phandle cnode; + + spin_lock_irqsave(&prom_lock, flags); + cnode = prom_nodeops->no_child(node); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + + return cnode; +} + +/* Return the child of node 'node' or zero if no this node has no + * direct descendent. + */ +phandle prom_getchild(phandle node) +{ + phandle cnode; + + if ((s32)node == -1) + return 0; + + cnode = __prom_getchild(node); + if (cnode == 0 || (s32)cnode == -1) + return 0; + + return cnode; +} +EXPORT_SYMBOL(prom_getchild); + +/* Internal version of prom_getsibling that does not alter return values. */ +static phandle __prom_getsibling(phandle node) +{ + unsigned long flags; + phandle cnode; + + spin_lock_irqsave(&prom_lock, flags); + cnode = prom_nodeops->no_nextnode(node); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + + return cnode; +} + +/* Return the next sibling of node 'node' or zero if no more siblings + * at this level of depth in the tree. + */ +phandle prom_getsibling(phandle node) +{ + phandle sibnode; + + if ((s32)node == -1) + return 0; + + sibnode = __prom_getsibling(node); + if (sibnode == 0 || (s32)sibnode == -1) + return 0; + + return sibnode; +} +EXPORT_SYMBOL(prom_getsibling); + +/* Return the length in bytes of property 'prop' at node 'node'. + * Return -1 on error. + */ +int prom_getproplen(phandle node, const char *prop) +{ + int ret; + unsigned long flags; + + if((!node) || (!prop)) + return -1; + + spin_lock_irqsave(&prom_lock, flags); + ret = prom_nodeops->no_proplen(node, prop); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + return ret; +} +EXPORT_SYMBOL(prom_getproplen); + +/* Acquire a property 'prop' at node 'node' and place it in + * 'buffer' which has a size of 'bufsize'. If the acquisition + * was successful the length will be returned, else -1 is returned. + */ +int prom_getproperty(phandle node, const char *prop, char *buffer, int bufsize) +{ + int plen, ret; + unsigned long flags; + + plen = prom_getproplen(node, prop); + if((plen > bufsize) || (plen == 0) || (plen == -1)) + return -1; + /* Ok, things seem all right. */ + spin_lock_irqsave(&prom_lock, flags); + ret = prom_nodeops->no_getprop(node, prop, buffer); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + return ret; +} +EXPORT_SYMBOL(prom_getproperty); + +/* Acquire an integer property and return its value. Returns -1 + * on failure. + */ +int prom_getint(phandle node, char *prop) +{ + static int intprop; + + if(prom_getproperty(node, prop, (char *) &intprop, sizeof(int)) != -1) + return intprop; + + return -1; +} +EXPORT_SYMBOL(prom_getint); + +/* Acquire an integer property, upon error return the passed default + * integer. + */ +int prom_getintdefault(phandle node, char *property, int deflt) +{ + int retval; + + retval = prom_getint(node, property); + if(retval == -1) return deflt; + + return retval; +} +EXPORT_SYMBOL(prom_getintdefault); + +/* Acquire a boolean property, 1=TRUE 0=FALSE. */ +int prom_getbool(phandle node, char *prop) +{ + int retval; + + retval = prom_getproplen(node, prop); + if(retval == -1) return 0; + return 1; +} +EXPORT_SYMBOL(prom_getbool); + +/* Acquire a property whose value is a string, returns a null + * string on error. The char pointer is the user supplied string + * buffer. + */ +void prom_getstring(phandle node, char *prop, char *user_buf, int ubuf_size) +{ + int len; + + len = prom_getproperty(node, prop, user_buf, ubuf_size); + if(len != -1) return; + user_buf[0] = 0; +} +EXPORT_SYMBOL(prom_getstring); + + +/* Search siblings at 'node_start' for a node with name + * 'nodename'. Return node if successful, zero if not. + */ +phandle prom_searchsiblings(phandle node_start, char *nodename) +{ + + phandle thisnode; + int error; + + for(thisnode = node_start; thisnode; + thisnode=prom_getsibling(thisnode)) { + error = prom_getproperty(thisnode, "name", promlib_buf, + sizeof(promlib_buf)); + /* Should this ever happen? */ + if(error == -1) continue; + if(strcmp(nodename, promlib_buf)==0) return thisnode; + } + + return 0; +} +EXPORT_SYMBOL(prom_searchsiblings); + +/* Interal version of nextprop that does not alter return values. */ +static char *__prom_nextprop(phandle node, char * oprop) +{ + unsigned long flags; + char *prop; + + spin_lock_irqsave(&prom_lock, flags); + prop = prom_nodeops->no_nextprop(node, oprop); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + + return prop; +} + +/* Return the property type string after property type 'oprop' + * at node 'node' . Returns empty string if no more + * property types for this node. + */ +char *prom_nextprop(phandle node, char *oprop, char *buffer) +{ + if (node == 0 || (s32)node == -1) + return ""; + + return __prom_nextprop(node, oprop); +} +EXPORT_SYMBOL(prom_nextprop); + +phandle prom_finddevice(char *name) +{ + char nbuf[128]; + char *s = name, *d; + phandle node = prom_root_node, node2; + unsigned int which_io, phys_addr; + struct linux_prom_registers reg[PROMREG_MAX]; + + while (*s++) { + if (!*s) return node; /* path '.../' is legal */ + node = prom_getchild(node); + + for (d = nbuf; *s != 0 && *s != '@' && *s != '/';) + *d++ = *s++; + *d = 0; + + node = prom_searchsiblings(node, nbuf); + if (!node) + return 0; + + if (*s == '@') { + if (isxdigit(s[1]) && s[2] == ',') { + which_io = simple_strtoul(s+1, NULL, 16); + phys_addr = simple_strtoul(s+3, &d, 16); + if (d != s + 3 && (!*d || *d == '/') + && d <= s + 3 + 8) { + node2 = node; + while (node2 && (s32)node2 != -1) { + if (prom_getproperty (node2, "reg", (char *)reg, sizeof (reg)) > 0) { + if (which_io == reg[0].which_io && phys_addr == reg[0].phys_addr) { + node = node2; + break; + } + } + node2 = prom_getsibling(node2); + if (!node2 || (s32)node2 == -1) + break; + node2 = prom_searchsiblings(prom_getsibling(node2), nbuf); + } + } + } + while (*s != 0 && *s != '/') s++; + } + } + return node; +} +EXPORT_SYMBOL(prom_finddevice); + +/* Set property 'pname' at node 'node' to value 'value' which has a length + * of 'size' bytes. Return the number of bytes the prom accepted. + */ +int prom_setprop(phandle node, const char *pname, char *value, int size) +{ + unsigned long flags; + int ret; + + if (size == 0) + return 0; + if ((pname == NULL) || (value == NULL)) + return 0; + spin_lock_irqsave(&prom_lock, flags); + ret = prom_nodeops->no_setprop(node, pname, value, size); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + return ret; +} +EXPORT_SYMBOL(prom_setprop); + +phandle prom_inst2pkg(int inst) +{ + phandle node; + unsigned long flags; + + spin_lock_irqsave(&prom_lock, flags); + node = (*romvec->pv_v2devops.v2_inst2pkg)(inst); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + if ((s32)node == -1) + return 0; + return node; +} diff --git a/arch/sparc/prom/tree_64.c b/arch/sparc/prom/tree_64.c new file mode 100644 index 000000000..7388339bb --- /dev/null +++ b/arch/sparc/prom/tree_64.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * tree.c: Basic device tree traversal/scanning for the Linux + * prom library. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1996,1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/string.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/module.h> + +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/ldc.h> + +static phandle prom_node_to_node(const char *type, phandle node) +{ + unsigned long args[5]; + + args[0] = (unsigned long) type; + args[1] = 1; + args[2] = 1; + args[3] = (unsigned int) node; + args[4] = (unsigned long) -1; + + p1275_cmd_direct(args); + + return (phandle) args[4]; +} + +/* Return the child of node 'node' or zero if no this node has no + * direct descendent. + */ +inline phandle __prom_getchild(phandle node) +{ + return prom_node_to_node("child", node); +} + +phandle prom_getchild(phandle node) +{ + phandle cnode; + + if ((s32)node == -1) + return 0; + cnode = __prom_getchild(node); + if ((s32)cnode == -1) + return 0; + return cnode; +} +EXPORT_SYMBOL(prom_getchild); + +inline phandle prom_getparent(phandle node) +{ + phandle cnode; + + if ((s32)node == -1) + return 0; + cnode = prom_node_to_node("parent", node); + if ((s32)cnode == -1) + return 0; + return cnode; +} + +/* Return the next sibling of node 'node' or zero if no more siblings + * at this level of depth in the tree. + */ +inline phandle __prom_getsibling(phandle node) +{ + return prom_node_to_node(prom_peer_name, node); +} + +phandle prom_getsibling(phandle node) +{ + phandle sibnode; + + if ((s32)node == -1) + return 0; + sibnode = __prom_getsibling(node); + if ((s32)sibnode == -1) + return 0; + + return sibnode; +} +EXPORT_SYMBOL(prom_getsibling); + +/* Return the length in bytes of property 'prop' at node 'node'. + * Return -1 on error. + */ +int prom_getproplen(phandle node, const char *prop) +{ + unsigned long args[6]; + + if (!node || !prop) + return -1; + + args[0] = (unsigned long) "getproplen"; + args[1] = 2; + args[2] = 1; + args[3] = (unsigned int) node; + args[4] = (unsigned long) prop; + args[5] = (unsigned long) -1; + + p1275_cmd_direct(args); + + return (int) args[5]; +} +EXPORT_SYMBOL(prom_getproplen); + +/* Acquire a property 'prop' at node 'node' and place it in + * 'buffer' which has a size of 'bufsize'. If the acquisition + * was successful the length will be returned, else -1 is returned. + */ +int prom_getproperty(phandle node, const char *prop, + char *buffer, int bufsize) +{ + unsigned long args[8]; + int plen; + + plen = prom_getproplen(node, prop); + if ((plen > bufsize) || (plen == 0) || (plen == -1)) + return -1; + + args[0] = (unsigned long) prom_getprop_name; + args[1] = 4; + args[2] = 1; + args[3] = (unsigned int) node; + args[4] = (unsigned long) prop; + args[5] = (unsigned long) buffer; + args[6] = bufsize; + args[7] = (unsigned long) -1; + + p1275_cmd_direct(args); + + return (int) args[7]; +} +EXPORT_SYMBOL(prom_getproperty); + +/* Acquire an integer property and return its value. Returns -1 + * on failure. + */ +int prom_getint(phandle node, const char *prop) +{ + int intprop; + + if (prom_getproperty(node, prop, (char *) &intprop, sizeof(int)) != -1) + return intprop; + + return -1; +} +EXPORT_SYMBOL(prom_getint); + +/* Acquire an integer property, upon error return the passed default + * integer. + */ + +int prom_getintdefault(phandle node, const char *property, int deflt) +{ + int retval; + + retval = prom_getint(node, property); + if (retval == -1) + return deflt; + + return retval; +} +EXPORT_SYMBOL(prom_getintdefault); + +/* Acquire a boolean property, 1=TRUE 0=FALSE. */ +int prom_getbool(phandle node, const char *prop) +{ + int retval; + + retval = prom_getproplen(node, prop); + if (retval == -1) + return 0; + return 1; +} +EXPORT_SYMBOL(prom_getbool); + +/* Acquire a property whose value is a string, returns a null + * string on error. The char pointer is the user supplied string + * buffer. + */ +void prom_getstring(phandle node, const char *prop, char *user_buf, + int ubuf_size) +{ + int len; + + len = prom_getproperty(node, prop, user_buf, ubuf_size); + if (len != -1) + return; + user_buf[0] = 0; +} +EXPORT_SYMBOL(prom_getstring); + +/* Does the device at node 'node' have name 'name'? + * YES = 1 NO = 0 + */ +int prom_nodematch(phandle node, const char *name) +{ + char namebuf[128]; + prom_getproperty(node, "name", namebuf, sizeof(namebuf)); + if (strcmp(namebuf, name) == 0) + return 1; + return 0; +} + +/* Search siblings at 'node_start' for a node with name + * 'nodename'. Return node if successful, zero if not. + */ +phandle prom_searchsiblings(phandle node_start, const char *nodename) +{ + phandle thisnode; + int error; + char promlib_buf[128]; + + for(thisnode = node_start; thisnode; + thisnode=prom_getsibling(thisnode)) { + error = prom_getproperty(thisnode, "name", promlib_buf, + sizeof(promlib_buf)); + /* Should this ever happen? */ + if(error == -1) continue; + if(strcmp(nodename, promlib_buf)==0) return thisnode; + } + + return 0; +} +EXPORT_SYMBOL(prom_searchsiblings); + +static const char *prom_nextprop_name = "nextprop"; + +/* Return the first property type for node 'node'. + * buffer should be at least 32B in length + */ +char *prom_firstprop(phandle node, char *buffer) +{ + unsigned long args[7]; + + *buffer = 0; + if ((s32)node == -1) + return buffer; + + args[0] = (unsigned long) prom_nextprop_name; + args[1] = 3; + args[2] = 1; + args[3] = (unsigned int) node; + args[4] = 0; + args[5] = (unsigned long) buffer; + args[6] = (unsigned long) -1; + + p1275_cmd_direct(args); + + return buffer; +} +EXPORT_SYMBOL(prom_firstprop); + +/* Return the property type string after property type 'oprop' + * at node 'node' . Returns NULL string if no more + * property types for this node. + */ +char *prom_nextprop(phandle node, const char *oprop, char *buffer) +{ + unsigned long args[7]; + char buf[32]; + + if ((s32)node == -1) { + *buffer = 0; + return buffer; + } + if (oprop == buffer) { + strscpy(buf, oprop); + oprop = buf; + } + + args[0] = (unsigned long) prom_nextprop_name; + args[1] = 3; + args[2] = 1; + args[3] = (unsigned int) node; + args[4] = (unsigned long) oprop; + args[5] = (unsigned long) buffer; + args[6] = (unsigned long) -1; + + p1275_cmd_direct(args); + + return buffer; +} +EXPORT_SYMBOL(prom_nextprop); + +phandle prom_finddevice(const char *name) +{ + unsigned long args[5]; + + if (!name) + return 0; + args[0] = (unsigned long) "finddevice"; + args[1] = 1; + args[2] = 1; + args[3] = (unsigned long) name; + args[4] = (unsigned long) -1; + + p1275_cmd_direct(args); + + return (int) args[4]; +} +EXPORT_SYMBOL(prom_finddevice); + +int prom_node_has_property(phandle node, const char *prop) +{ + char buf [32]; + + *buf = 0; + do { + prom_nextprop(node, buf, buf); + if (!strcmp(buf, prop)) + return 1; + } while (*buf); + return 0; +} +EXPORT_SYMBOL(prom_node_has_property); + +/* Set property 'pname' at node 'node' to value 'value' which has a length + * of 'size' bytes. Return the number of bytes the prom accepted. + */ +int +prom_setprop(phandle node, const char *pname, char *value, int size) +{ + unsigned long args[8]; + + if (size == 0) + return 0; + if ((pname == NULL) || (value == NULL)) + return 0; + +#ifdef CONFIG_SUN_LDOMS + if (ldom_domaining_enabled) { + ldom_set_var(pname, value); + return 0; + } +#endif + args[0] = (unsigned long) "setprop"; + args[1] = 4; + args[2] = 1; + args[3] = (unsigned int) node; + args[4] = (unsigned long) pname; + args[5] = (unsigned long) value; + args[6] = size; + args[7] = (unsigned long) -1; + + p1275_cmd_direct(args); + + return (int) args[7]; +} +EXPORT_SYMBOL(prom_setprop); + +inline phandle prom_inst2pkg(int inst) +{ + unsigned long args[5]; + phandle node; + + args[0] = (unsigned long) "instance-to-package"; + args[1] = 1; + args[2] = 1; + args[3] = (unsigned int) inst; + args[4] = (unsigned long) -1; + + p1275_cmd_direct(args); + + node = (int) args[4]; + if ((s32)node == -1) + return 0; + return node; +} + +int prom_ihandle2path(int handle, char *buffer, int bufsize) +{ + unsigned long args[7]; + + args[0] = (unsigned long) "instance-to-path"; + args[1] = 3; + args[2] = 1; + args[3] = (unsigned int) handle; + args[4] = (unsigned long) buffer; + args[5] = bufsize; + args[6] = (unsigned long) -1; + + p1275_cmd_direct(args); + + return (int) args[6]; +} |
