From a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 Mon Sep 17 00:00:00 2001 From: Jinke Han Date: Tue, 8 Sep 2026 15:37:42 +0800 Subject: x86/kprobes: Fix crash when probing CS CALL instructions 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) Signed-off-by: Jinke Han Signed-off-by: Ingo Molnar Reviewed-by: Masami Hiramatsu (Google) Acked-by: Yafang Shao Acked-by: Borislav Petkov Cc: Peter Zijlstra Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000 --- arch/m68k/sun3/Makefile | 8 + arch/m68k/sun3/config.c | 221 ++++++++++++++++++++++ arch/m68k/sun3/dvma.c | 68 +++++++ arch/m68k/sun3/idprom.c | 134 +++++++++++++ arch/m68k/sun3/intersil.c | 70 +++++++ arch/m68k/sun3/leds.c | 16 ++ arch/m68k/sun3/mmu_emu.c | 427 ++++++++++++++++++++++++++++++++++++++++++ arch/m68k/sun3/prom/Makefile | 7 + arch/m68k/sun3/prom/console.c | 170 +++++++++++++++++ arch/m68k/sun3/prom/init.c | 36 ++++ arch/m68k/sun3/prom/misc.c | 95 ++++++++++ arch/m68k/sun3/prom/printf.c | 55 ++++++ arch/m68k/sun3/sun3.h | 22 +++ arch/m68k/sun3/sun3dvma.c | 359 +++++++++++++++++++++++++++++++++++ arch/m68k/sun3/sun3ints.c | 100 ++++++++++ 15 files changed, 1788 insertions(+) create mode 100644 arch/m68k/sun3/Makefile create mode 100644 arch/m68k/sun3/config.c create mode 100644 arch/m68k/sun3/dvma.c create mode 100644 arch/m68k/sun3/idprom.c create mode 100644 arch/m68k/sun3/intersil.c create mode 100644 arch/m68k/sun3/leds.c create mode 100644 arch/m68k/sun3/mmu_emu.c create mode 100644 arch/m68k/sun3/prom/Makefile create mode 100644 arch/m68k/sun3/prom/console.c create mode 100644 arch/m68k/sun3/prom/init.c create mode 100644 arch/m68k/sun3/prom/misc.c create mode 100644 arch/m68k/sun3/prom/printf.c create mode 100644 arch/m68k/sun3/sun3.h create mode 100644 arch/m68k/sun3/sun3dvma.c create mode 100644 arch/m68k/sun3/sun3ints.c (limited to 'arch/m68k/sun3') diff --git a/arch/m68k/sun3/Makefile b/arch/m68k/sun3/Makefile new file mode 100644 index 000000000..4e99e17d8 --- /dev/null +++ b/arch/m68k/sun3/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for Linux arch/m68k/sun3 source directory +# + +obj-y := sun3ints.o sun3dvma.o idprom.o + +obj-$(CONFIG_SUN3) += config.o mmu_emu.o leds.o dvma.o intersil.o prom/ diff --git a/arch/m68k/sun3/config.c b/arch/m68k/sun3/config.c new file mode 100644 index 000000000..cd8af809e --- /dev/null +++ b/arch/m68k/sun3/config.c @@ -0,0 +1,221 @@ +/* + * linux/arch/m68k/sun3/config.c + * + * Copyright (C) 1996,1997 Pekka Pietik{inen + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sun3.h" + +char sun3_reserved_pmeg[SUN3_PMEGS_NUM]; + +static void sun3_sched_init(void); + +volatile char* clock_va; +extern unsigned long availmem; +unsigned long num_pages; + +static void sun3_get_hardware_list(struct seq_file *m) +{ + seq_printf(m, "PROM Revision:\t%s\n", romvec->pv_monid); +} + +asmlinkage void __init sun3_init(void) +{ + unsigned char enable_register; + int i; + + m68k_machtype= MACH_SUN3; + m68k_cputype = CPU_68020; + m68k_fputype = FPU_68881; /* mc68881 actually */ + m68k_mmutype = MMU_SUN3; + clock_va = (char *) 0xfe06000; /* dark */ + sun3_intreg = (unsigned char *) 0xfe0a000; /* magic */ + sun3_disable_interrupts(); + + prom_init((void *)LINUX_OPPROM_BEGVM); + + GET_CONTROL_BYTE(AC_SENABLE,enable_register); + enable_register |= 0x50; /* Enable FPU */ + SET_CONTROL_BYTE(AC_SENABLE,enable_register); + GET_CONTROL_BYTE(AC_SENABLE,enable_register); + + /* This code looks suspicious, because it doesn't subtract + memory belonging to the kernel from the available space */ + + + memset(sun3_reserved_pmeg, 0, sizeof(sun3_reserved_pmeg)); + + /* Reserve important PMEGS */ + /* FIXME: These should be probed instead of hardcoded */ + + for (i=0; i<8; i++) /* Kernel PMEGs */ + sun3_reserved_pmeg[i] = 1; + + sun3_reserved_pmeg[247] = 1; /* ROM mapping */ + sun3_reserved_pmeg[248] = 1; /* AMD Ethernet */ + sun3_reserved_pmeg[251] = 1; /* VB area */ + sun3_reserved_pmeg[254] = 1; /* main I/O */ + + sun3_reserved_pmeg[249] = 1; + sun3_reserved_pmeg[252] = 1; + sun3_reserved_pmeg[253] = 1; + set_fc(USER_DATA); +} + +/* Without this, Bad Things happen when something calls arch_reset. */ +static void sun3_reboot (void) +{ + prom_reboot ("vmlinux"); +} + +static void sun3_halt (void) +{ + prom_halt (); +} + +/* sun3 bootmem allocation */ + +static void __init sun3_bootmem_alloc(unsigned long memory_start, + unsigned long memory_end) +{ + /* align start/end to page boundaries */ + memory_start = ((memory_start + (PAGE_SIZE-1)) & PAGE_MASK); + memory_end = memory_end & PAGE_MASK; + + max_pfn = num_pages = __pa(memory_end) >> PAGE_SHIFT; + + high_memory = (void *)memory_end; + availmem = memory_start; + + m68k_setup_node(0); +} + + +void __init config_sun3(void) +{ + unsigned long memory_start, memory_end; + + pr_info("ARCH: SUN3\n"); + idprom_init(); + + /* Subtract kernel memory from available memory */ + + mach_sched_init = sun3_sched_init; + mach_init_IRQ = sun3_init_IRQ; + mach_reset = sun3_reboot; + mach_get_model = sun3_get_model; + mach_hwclk = sun3_hwclk; + mach_halt = sun3_halt; + mach_get_hardware_list = sun3_get_hardware_list; + + memory_start = ((((unsigned long)_end) + 0x2000) & ~0x1fff); +// PROM seems to want the last couple of physical pages. --m + memory_end = *(romvec->pv_sun3mem) + PAGE_OFFSET - 2*PAGE_SIZE; + + m68k_num_memory=1; + m68k_memory[0].size=*(romvec->pv_sun3mem); + + sun3_bootmem_alloc(memory_start, memory_end); +} + +static void __init sun3_sched_init(void) +{ + sun3_disable_interrupts(); + intersil_clock->cmd_reg=(INTERSIL_RUN|INTERSIL_INT_DISABLE|INTERSIL_24H_MODE); + intersil_clock->int_reg=INTERSIL_HZ_100_MASK; + intersil_clear(); + sun3_enable_irq(5); + intersil_clock->cmd_reg=(INTERSIL_RUN|INTERSIL_INT_ENABLE|INTERSIL_24H_MODE); + sun3_enable_interrupts(); + intersil_clear(); +} + +#if IS_ENABLED(CONFIG_SUN3_SCSI) + +static const struct resource sun3_scsi_vme_rsrc[] __initconst = { + { + .flags = IORESOURCE_IRQ, + .start = SUN3_VEC_VMESCSI0, + .end = SUN3_VEC_VMESCSI0, + }, { + .flags = IORESOURCE_MEM, + .start = 0xff200000, + .end = 0xff200021, + }, { + .flags = IORESOURCE_IRQ, + .start = SUN3_VEC_VMESCSI1, + .end = SUN3_VEC_VMESCSI1, + }, { + .flags = IORESOURCE_MEM, + .start = 0xff204000, + .end = 0xff204021, + }, +}; + +/* + * Int: level 2 autovector + * IO: type 1, base 0x00140000, 5 bits phys space: A<4..0> + */ +static const struct resource sun3_scsi_rsrc[] __initconst = { + { + .flags = IORESOURCE_IRQ, + .start = 2, + .end = 2, + }, { + .flags = IORESOURCE_MEM, + .start = 0x00140000, + .end = 0x0014001f, + }, +}; + +static int __init sun3_platform_init(void) +{ + switch (idprom->id_machtype) { + case SM_SUN3 | SM_3_160: + case SM_SUN3 | SM_3_260: + platform_device_register_simple("sun3_scsi_vme", -1, + sun3_scsi_vme_rsrc, ARRAY_SIZE(sun3_scsi_vme_rsrc)); + break; + case SM_SUN3 | SM_3_50: + case SM_SUN3 | SM_3_60: + platform_device_register_simple("sun3_scsi", -1, + sun3_scsi_rsrc, ARRAY_SIZE(sun3_scsi_rsrc)); + break; + } + return 0; +} + +arch_initcall(sun3_platform_init); + +#endif diff --git a/arch/m68k/sun3/dvma.c b/arch/m68k/sun3/dvma.c new file mode 100644 index 000000000..83fcae6a0 --- /dev/null +++ b/arch/m68k/sun3/dvma.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * linux/arch/m68k/sun3/dvma.c + * + * Written by Sam Creasey + * + * Sun3 IOMMU routines used for dvma accesses. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + + +static unsigned long ptelist[120]; + +static unsigned long dvma_page(unsigned long kaddr, unsigned long vaddr) +{ + unsigned long pte; + unsigned long j; + pte_t ptep; + + j = *(volatile unsigned long *)kaddr; + *(volatile unsigned long *)kaddr = j; + + ptep = pfn_pte(virt_to_pfn((void *)kaddr), PAGE_KERNEL); + pte = pte_val(ptep); +// pr_info("dvma_remap: addr %lx -> %lx pte %08lx\n", kaddr, vaddr, pte); + if(ptelist[(vaddr & 0xff000) >> PAGE_SHIFT] != pte) { + sun3_put_pte(vaddr, pte); + ptelist[(vaddr & 0xff000) >> PAGE_SHIFT] = pte; + } + + return (vaddr + (kaddr & ~PAGE_MASK)); + +} + +int dvma_map_iommu(unsigned long kaddr, unsigned long baddr, + int len) +{ + + unsigned long end; + unsigned long vaddr; + + vaddr = dvma_btov(baddr); + + end = vaddr + len; + + while(vaddr < end) { + dvma_page(kaddr, vaddr); + kaddr += PAGE_SIZE; + vaddr += PAGE_SIZE; + } + + return 0; + +} + +void __init sun3_dvma_init(void) +{ + memset(ptelist, 0, sizeof(ptelist)); +} diff --git a/arch/m68k/sun3/idprom.c b/arch/m68k/sun3/idprom.c new file mode 100644 index 000000000..ca633a5f5 --- /dev/null +++ b/arch/m68k/sun3/idprom.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * idprom.c: Routines to load the idprom into kernel addresses and + * interpret the data contained within. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Sun3/3x models added by David Monro (davidm@psrg.cs.usyd.edu.au) + */ + +#include +#include +#include +#include +#include + +#include +#include +#include /* Fun with Sun released architectures. */ + +#include "sun3.h" + +struct idprom *idprom; +EXPORT_SYMBOL(idprom); + +static struct idprom idprom_buffer; + +/* Here is the master table of Sun machines which use some implementation + * of the Sparc CPU and have a meaningful IDPROM machtype value that we + * know about. See asm-sparc/machines.h for empirical constants. + */ +static struct Sun_Machine_Models Sun_Machines[NUM_SUN_MACHINES] = { +/* First, Sun3's */ + { .name = "Sun 3/160 Series", .id_machtype = (SM_SUN3 | SM_3_160) }, + { .name = "Sun 3/50", .id_machtype = (SM_SUN3 | SM_3_50) }, + { .name = "Sun 3/260 Series", .id_machtype = (SM_SUN3 | SM_3_260) }, + { .name = "Sun 3/110 Series", .id_machtype = (SM_SUN3 | SM_3_110) }, + { .name = "Sun 3/60", .id_machtype = (SM_SUN3 | SM_3_60) }, + { .name = "Sun 3/E", .id_machtype = (SM_SUN3 | SM_3_E) }, +/* Now, Sun3x's */ + { .name = "Sun 3/460 Series", .id_machtype = (SM_SUN3X | SM_3_460) }, + { .name = "Sun 3/80", .id_machtype = (SM_SUN3X | SM_3_80) }, +/* Then, Sun4's */ +// { .name = "Sun 4/100 Series", .id_machtype = (SM_SUN4 | SM_4_110) }, +// { .name = "Sun 4/200 Series", .id_machtype = (SM_SUN4 | SM_4_260) }, +// { .name = "Sun 4/300 Series", .id_machtype = (SM_SUN4 | SM_4_330) }, +// { .name = "Sun 4/400 Series", .id_machtype = (SM_SUN4 | SM_4_470) }, +/* And now, Sun4c's */ +// { .name = "Sun4c SparcStation 1", .id_machtype = (SM_SUN4C | SM_4C_SS1) }, +// { .name = "Sun4c SparcStation IPC", .id_machtype = (SM_SUN4C | SM_4C_IPC) }, +// { .name = "Sun4c SparcStation 1+", .id_machtype = (SM_SUN4C | SM_4C_SS1PLUS) }, +// { .name = "Sun4c SparcStation SLC", .id_machtype = (SM_SUN4C | SM_4C_SLC) }, +// { .name = "Sun4c SparcStation 2", .id_machtype = (SM_SUN4C | SM_4C_SS2) }, +// { .name = "Sun4c SparcStation ELC", .id_machtype = (SM_SUN4C | SM_4C_ELC) }, +// { .name = "Sun4c SparcStation IPX", .id_machtype = (SM_SUN4C | SM_4C_IPX) }, +/* Finally, early Sun4m's */ +// { .name = "Sun4m SparcSystem600", .id_machtype = (SM_SUN4M | SM_4M_SS60) }, +// { .name = "Sun4m SparcStation10/20", .id_machtype = (SM_SUN4M | SM_4M_SS50) }, +// { .name = "Sun4m SparcStation5", .id_machtype = (SM_SUN4M | SM_4M_SS40) }, +/* One entry for the OBP arch's which are sun4d, sun4e, and newer sun4m's */ +// { .name = "Sun4M OBP based system", .id_machtype = (SM_SUN4M_OBP | 0x0) } +}; + +static void __init display_system_type(unsigned char machtype) +{ + register int i; + + for (i = 0; i < NUM_SUN_MACHINES; i++) { + if(Sun_Machines[i].id_machtype == machtype) { + if (machtype != (SM_SUN4M_OBP | 0x00)) + pr_info("TYPE: %s\n", Sun_Machines[i].name); + else { +#if 0 + char sysname[128]; + + prom_getproperty(prom_root_node, "banner-name", + sysname, sizeof(sysname)); + pr_info("TYPE: %s\n", sysname); +#endif + } + return; + } + } + + prom_printf("IDPROM: Bogus id_machtype value, 0x%x\n", machtype); + prom_halt(); +} + +void sun3_get_model(char *model) +{ + register int i; + + for (i = 0; i < NUM_SUN_MACHINES; i++) { + if(Sun_Machines[i].id_machtype == idprom->id_machtype) { + strcpy(model, Sun_Machines[i].name); + return; + } + } +} + + + +/* Calculate the IDPROM checksum (xor of the data bytes). */ +static unsigned char __init calc_idprom_cksum(struct idprom *idprom) +{ + unsigned char cksum, i, *ptr = (unsigned char *)idprom; + + for (i = cksum = 0; i <= 0x0E; i++) + cksum ^= *ptr++; + + return cksum; +} + +/* Create a local IDPROM copy, verify integrity, and display information. */ +void __init idprom_init(void) +{ + prom_get_idprom((char *) &idprom_buffer, sizeof(idprom_buffer)); + + idprom = &idprom_buffer; + + if (idprom->id_format != 0x01) { + prom_printf("IDPROM: Unknown format type!\n"); + prom_halt(); + } + + if (idprom->id_cksum != calc_idprom_cksum(idprom)) { + prom_printf("IDPROM: Checksum failure (nvram=%x, calc=%x)!\n", + idprom->id_cksum, calc_idprom_cksum(idprom)); + prom_halt(); + } + + display_system_type(idprom->id_machtype); + + pr_info("Ethernet address: %pM\n", idprom->id_ethaddr); +} diff --git a/arch/m68k/sun3/intersil.c b/arch/m68k/sun3/intersil.c new file mode 100644 index 000000000..29674cfa9 --- /dev/null +++ b/arch/m68k/sun3/intersil.c @@ -0,0 +1,70 @@ +/* + * arch/m68k/sun3/intersil.c + * + * basic routines for accessing the intersil clock within the sun3 machines + * + * started 11/12/1999 Sam Creasey + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + */ + +#include +#include + +#include +#include +#include + +#include "sun3.h" + +/* bits to set for start/run of the intersil */ +#define STOP_VAL (INTERSIL_STOP | INTERSIL_INT_ENABLE | INTERSIL_24H_MODE) +#define START_VAL (INTERSIL_RUN | INTERSIL_INT_ENABLE | INTERSIL_24H_MODE) + +/* get/set hwclock */ + +int sun3_hwclk(int set, struct rtc_time *t) +{ + volatile struct intersil_dt *todintersil; + unsigned long flags; + + todintersil = (struct intersil_dt *) &intersil_clock->counter; + + local_irq_save(flags); + + intersil_clock->cmd_reg = STOP_VAL; + + /* set or read the clock */ + if(set) { + todintersil->csec = 0; + todintersil->hour = t->tm_hour; + todintersil->minute = t->tm_min; + todintersil->second = t->tm_sec; + todintersil->month = t->tm_mon + 1; + todintersil->day = t->tm_mday; + todintersil->year = (t->tm_year - 68) % 100; + todintersil->weekday = t->tm_wday; + } else { + /* read clock */ + t->tm_sec = todintersil->csec; + t->tm_hour = todintersil->hour; + t->tm_min = todintersil->minute; + t->tm_sec = todintersil->second; + t->tm_mon = todintersil->month - 1; + t->tm_mday = todintersil->day; + t->tm_year = todintersil->year + 68; + t->tm_wday = todintersil->weekday; + if (t->tm_year < 70) + t->tm_year += 100; + } + + intersil_clock->cmd_reg = START_VAL; + + local_irq_restore(flags); + + return 0; + +} + diff --git a/arch/m68k/sun3/leds.c b/arch/m68k/sun3/leds.c new file mode 100644 index 000000000..4bb95318f --- /dev/null +++ b/arch/m68k/sun3/leds.c @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include + +#include "sun3.h" + +void sun3_leds(unsigned char byte) +{ + unsigned char dfc; + + GET_DFC(dfc); + SET_DFC(FC_CONTROL); + SET_CONTROL_BYTE(AC_LEDS, byte); + SET_DFC(dfc); +} diff --git a/arch/m68k/sun3/mmu_emu.c b/arch/m68k/sun3/mmu_emu.c new file mode 100644 index 000000000..b39fc3717 --- /dev/null +++ b/arch/m68k/sun3/mmu_emu.c @@ -0,0 +1,427 @@ +// SPDX-License-Identifier: GPL-2.0 +/* +** Tablewalk MMU emulator +** +** by Toshiyasu Morita +** +** Started 1/16/98 @ 2:22 am +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sun3.h" + +#undef DEBUG_MMU_EMU +#define DEBUG_PROM_MAPS + +/* +** Defines +*/ + +#define CONTEXTS_NUM 8 +#define SEGMAPS_PER_CONTEXT_NUM 2048 +#define PAGES_PER_SEGMENT 16 +#define PMEGS_NUM 256 +#define PMEG_MASK 0xFF + +/* +** Globals +*/ + +unsigned long m68k_vmalloc_end; +EXPORT_SYMBOL(m68k_vmalloc_end); + +unsigned long pmeg_vaddr[PMEGS_NUM]; +unsigned char pmeg_alloc[PMEGS_NUM]; +unsigned char pmeg_ctx[PMEGS_NUM]; + +/* pointers to the mm structs for each task in each + context. 0xffffffff is a marker for kernel context */ +static struct mm_struct *ctx_alloc[CONTEXTS_NUM] = { + [0] = (struct mm_struct *)0xffffffff +}; + +/* has this context been mmdrop'd? */ +static unsigned char ctx_avail = CONTEXTS_NUM-1; + +/* array of pages to be marked off for the rom when we do mem_init later */ +/* 256 pages lets the rom take up to 2mb of physical ram.. I really + hope it never wants mote than that. */ +unsigned long rom_pages[256]; + +/* Print a PTE value in symbolic form. For debugging. */ +static void print_pte(pte_t pte) +{ +#if 0 + /* Verbose version. */ + unsigned long val = pte_val (pte); + pr_cont(" pte=%lx [addr=%lx", + val, (val & SUN3_PAGE_PGNUM_MASK) << PAGE_SHIFT); + if (val & SUN3_PAGE_VALID) pr_cont(" valid"); + if (val & SUN3_PAGE_WRITEABLE) pr_cont(" write"); + if (val & SUN3_PAGE_SYSTEM) pr_cont(" sys"); + if (val & SUN3_PAGE_NOCACHE) pr_cont(" nocache"); + if (val & SUN3_PAGE_ACCESSED) pr_cont(" accessed"); + if (val & SUN3_PAGE_MODIFIED) pr_cont(" modified"); + switch (val & SUN3_PAGE_TYPE_MASK) { + case SUN3_PAGE_TYPE_MEMORY: pr_cont(" memory"); break; + case SUN3_PAGE_TYPE_IO: pr_cont(" io"); break; + case SUN3_PAGE_TYPE_VME16: pr_cont(" vme16"); break; + case SUN3_PAGE_TYPE_VME32: pr_cont(" vme32"); break; + } + pr_cont("]\n"); +#else + /* Terse version. More likely to fit on a line. */ + unsigned long val = pte_val (pte); + char flags[7], *type; + + flags[0] = (val & SUN3_PAGE_VALID) ? 'v' : '-'; + flags[1] = (val & SUN3_PAGE_WRITEABLE) ? 'w' : '-'; + flags[2] = (val & SUN3_PAGE_SYSTEM) ? 's' : '-'; + flags[3] = (val & SUN3_PAGE_NOCACHE) ? 'x' : '-'; + flags[4] = (val & SUN3_PAGE_ACCESSED) ? 'a' : '-'; + flags[5] = (val & SUN3_PAGE_MODIFIED) ? 'm' : '-'; + flags[6] = '\0'; + + switch (val & SUN3_PAGE_TYPE_MASK) { + case SUN3_PAGE_TYPE_MEMORY: type = "memory"; break; + case SUN3_PAGE_TYPE_IO: type = "io" ; break; + case SUN3_PAGE_TYPE_VME16: type = "vme16" ; break; + case SUN3_PAGE_TYPE_VME32: type = "vme32" ; break; + default: type = "unknown?"; break; + } + + pr_cont(" pte=%08lx [%07lx %s %s]\n", + val, (val & SUN3_PAGE_PGNUM_MASK) << PAGE_SHIFT, flags, type); +#endif +} + +/* Print the PTE value for a given virtual address. For debugging. */ +void print_pte_vaddr (unsigned long vaddr) +{ + pr_cont(" vaddr=%lx [%02lx]", vaddr, sun3_get_segmap (vaddr)); + print_pte (__pte (sun3_get_pte (vaddr))); +} + +/* + * Initialise the MMU emulator. + */ +void __init mmu_emu_init(unsigned long bootmem_end) +{ + unsigned long seg, num; + int i,j; + + memset(rom_pages, 0, sizeof(rom_pages)); + memset(pmeg_vaddr, 0, sizeof(pmeg_vaddr)); + memset(pmeg_alloc, 0, sizeof(pmeg_alloc)); + memset(pmeg_ctx, 0, sizeof(pmeg_ctx)); + + /* pmeg align the end of bootmem, adding another pmeg, + * later bootmem allocations will likely need it */ + bootmem_end = (bootmem_end + (2 * SUN3_PMEG_SIZE)) & ~SUN3_PMEG_MASK; + + /* mark all of the pmegs used thus far as reserved */ + for (i=0; i < __pa(bootmem_end) / SUN3_PMEG_SIZE ; ++i) + pmeg_alloc[i] = 2; + + + /* I'm thinking that most of the top pmeg's are going to be + used for something, and we probably shouldn't risk it */ + for(num = 0xf0; num <= 0xff; num++) + pmeg_alloc[num] = 2; + + /* liberate all existing mappings in the rest of kernel space */ + for(seg = bootmem_end; seg < 0x0f800000; seg += SUN3_PMEG_SIZE) { + i = sun3_get_segmap(seg); + + if(!pmeg_alloc[i]) { +#ifdef DEBUG_MMU_EMU + pr_info("freed:"); + print_pte_vaddr (seg); +#endif + sun3_put_segmap(seg, SUN3_INVALID_PMEG); + } + } + + j = 0; + for (num=0, seg=0x0F800000; seg<0x10000000; seg+=16*PAGE_SIZE) { + if (sun3_get_segmap (seg) != SUN3_INVALID_PMEG) { +#ifdef DEBUG_PROM_MAPS + for(i = 0; i < 16; i++) { + pr_info("mapped:"); + print_pte_vaddr (seg + (i*PAGE_SIZE)); + break; + } +#endif + // the lowest mapping here is the end of our + // vmalloc region + if (!m68k_vmalloc_end) + m68k_vmalloc_end = seg; + + // mark the segmap alloc'd, and reserve any + // of the first 0xbff pages the hardware is + // already using... does any sun3 support > 24mb? + pmeg_alloc[sun3_get_segmap(seg)] = 2; + } + } + + dvma_init(); + + + /* blank everything below the kernel, and we've got the base + mapping to start all the contexts off with... */ + for(seg = 0; seg < PAGE_OFFSET; seg += SUN3_PMEG_SIZE) + sun3_put_segmap(seg, SUN3_INVALID_PMEG); + + set_fc(3); + for(seg = 0; seg < 0x10000000; seg += SUN3_PMEG_SIZE) { + i = sun3_get_segmap(seg); + for(j = 1; j < CONTEXTS_NUM; j++) + (*(romvec->pv_setctxt))(j, (void *)seg, i); + } + set_fc(USER_DATA); +} + +/* erase the mappings for a dead context. Uses the pg_dir for hints + as the pmeg tables proved somewhat unreliable, and unmapping all of + TASK_SIZE was much slower and no more stable. */ +/* todo: find a better way to keep track of the pmegs used by a + context for when they're cleared */ +void clear_context(unsigned long context) +{ + unsigned char oldctx; + unsigned long i; + + if (context) { + if (!ctx_alloc[context]) + panic("%s: context not allocated\n", __func__); + + ctx_alloc[context]->context = SUN3_INVALID_CONTEXT; + ctx_alloc[context] = (struct mm_struct *)0; + ctx_avail++; + } + + oldctx = sun3_get_context(); + + sun3_put_context(context); + + for (i = 0; i < SUN3_INVALID_PMEG; i++) { + if ((pmeg_ctx[i] == context) && (pmeg_alloc[i] == 1)) { + sun3_put_segmap(pmeg_vaddr[i], SUN3_INVALID_PMEG); + pmeg_ctx[i] = 0; + pmeg_alloc[i] = 0; + pmeg_vaddr[i] = 0; + } + } + + sun3_put_context(oldctx); +} + +/* gets an empty context. if full, kills the next context listed to + die first */ +/* This context invalidation scheme is, well, totally arbitrary, I'm + sure it could be much more intelligent... but it gets the job done + for now without much overhead in making it's decision. */ +/* todo: come up with optimized scheme for flushing contexts */ +unsigned long get_free_context(struct mm_struct *mm) +{ + unsigned long new = 1; + static unsigned char next_to_die = 1; + + if(!ctx_avail) { + /* kill someone to get our context */ + new = next_to_die; + clear_context(new); + next_to_die = (next_to_die + 1) & 0x7; + if(!next_to_die) + next_to_die++; + } else { + while(new < CONTEXTS_NUM) { + if(ctx_alloc[new]) + new++; + else + break; + } + // check to make sure one was really free... + if(new == CONTEXTS_NUM) + panic("%s: failed to find free context", __func__); + } + + ctx_alloc[new] = mm; + ctx_avail--; + + return new; +} + +/* + * Dynamically select a `spare' PMEG and use it to map virtual `vaddr' in + * `context'. Maintain internal PMEG management structures. This doesn't + * actually map the physical address, but does clear the old mappings. + */ +//todo: better allocation scheme? but is extra complexity worthwhile? +//todo: only clear old entries if necessary? how to tell? + +inline void mmu_emu_map_pmeg (int context, int vaddr) +{ + static unsigned char curr_pmeg = 128; + int i; + + /* Round address to PMEG boundary. */ + vaddr &= ~SUN3_PMEG_MASK; + + /* Find a spare one. */ + while (pmeg_alloc[curr_pmeg] == 2) + ++curr_pmeg; + + +#ifdef DEBUG_MMU_EMU + pr_info("mmu_emu_map_pmeg: pmeg %x to context %d vaddr %x\n", + curr_pmeg, context, vaddr); +#endif + + /* Invalidate old mapping for the pmeg, if any */ + if (pmeg_alloc[curr_pmeg] == 1) { + sun3_put_context(pmeg_ctx[curr_pmeg]); + sun3_put_segmap (pmeg_vaddr[curr_pmeg], SUN3_INVALID_PMEG); + sun3_put_context(context); + } + + /* Update PMEG management structures. */ + // don't take pmeg's away from the kernel... + if(vaddr >= PAGE_OFFSET) { + /* map kernel pmegs into all contexts */ + unsigned char i; + + for(i = 0; i < CONTEXTS_NUM; i++) { + sun3_put_context(i); + sun3_put_segmap (vaddr, curr_pmeg); + } + sun3_put_context(context); + pmeg_alloc[curr_pmeg] = 2; + pmeg_ctx[curr_pmeg] = 0; + + } + else { + pmeg_alloc[curr_pmeg] = 1; + pmeg_ctx[curr_pmeg] = context; + sun3_put_segmap (vaddr, curr_pmeg); + + } + pmeg_vaddr[curr_pmeg] = vaddr; + + /* Set hardware mapping and clear the old PTE entries. */ + for (i=0; i ? + +// kernel_fault is set when a kernel page couldn't be demand mapped, +// and forces another try using the kernel page table. basically a +// hack so that vmalloc would work correctly. + +int mmu_emu_handle_fault (unsigned long vaddr, int read_flag, int kernel_fault) +{ + unsigned long segment, offset; + unsigned char context; + pte_t *pte; + pgd_t * crp; + + if(current->mm == NULL) { + crp = swapper_pg_dir; + context = 0; + } else { + context = current->mm->context; + if(kernel_fault) + crp = swapper_pg_dir; + else + crp = current->mm->pgd; + } + +#ifdef DEBUG_MMU_EMU + pr_info("%s: vaddr=%lx type=%s crp=%px\n", __func__, vaddr, + str_read_write(read_flag), crp); +#endif + + segment = (vaddr >> SUN3_PMEG_SIZE_BITS) & 0x7FF; + offset = (vaddr >> SUN3_PTE_SIZE_BITS) & 0xF; + +#ifdef DEBUG_MMU_EMU + pr_info("%s: segment=%lx offset=%lx\n", __func__, segment, offset); +#endif + + pte = (pte_t *) pgd_val (*(crp + segment)); + +//todo: next line should check for valid pmd properly. + if (!pte) { +// pr_info("mmu_emu_handle_fault: invalid pmd\n"); + return 0; + } + + pte = (pte_t *) __va ((unsigned long)(pte + offset)); + + /* Make sure this is a valid page */ + if (!(pte_val (*pte) & SUN3_PAGE_VALID)) + return 0; + + /* Make sure there's a pmeg allocated for the page */ + if (sun3_get_segmap (vaddr&~SUN3_PMEG_MASK) == SUN3_INVALID_PMEG) + mmu_emu_map_pmeg (context, vaddr); + + /* Write the pte value to hardware MMU */ + sun3_put_pte (vaddr&PAGE_MASK, pte_val (*pte)); + + /* Update software copy of the pte value */ +// I'm not sure this is necessary. If this is required, we ought to simply +// copy this out when we reuse the PMEG or at some other convenient time. +// Doing it here is fairly meaningless, anyway, as we only know about the +// first access to a given page. --m + if (!read_flag) { + if (pte_val (*pte) & SUN3_PAGE_WRITEABLE) + pte_val (*pte) |= (SUN3_PAGE_ACCESSED + | SUN3_PAGE_MODIFIED); + else + return 0; /* Write-protect error. */ + } else + pte_val (*pte) |= SUN3_PAGE_ACCESSED; + +#ifdef DEBUG_MMU_EMU + pr_info("seg:%ld crp:%px ->", get_fc(), crp); + print_pte_vaddr (vaddr); + pr_cont("\n"); +#endif + + return 1; +} diff --git a/arch/m68k/sun3/prom/Makefile b/arch/m68k/sun3/prom/Makefile new file mode 100644 index 000000000..2f8c5912c --- /dev/null +++ b/arch/m68k/sun3/prom/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Makefile for the Sun Boot PROM interface library under +# Linux. +# + +obj-y := init.o console.o printf.o misc.o +#bootstr.o init.o misc.o segment.o console.o printf.o diff --git a/arch/m68k/sun3/prom/console.c b/arch/m68k/sun3/prom/console.c new file mode 100644 index 000000000..eb4f6c903 --- /dev/null +++ b/arch/m68k/sun3/prom/console.c @@ -0,0 +1,170 @@ +// 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) + */ + +#include +#include +#include +#include +#include +#include + +/* Non blocking get character from console input device, returns -1 + * if no input was taken. This can be used for polling. + */ +int +prom_nbgetchar(void) +{ + int i = -1; + unsigned long flags; + + local_irq_save(flags); + i = (*(romvec->pv_nbgetchar))(); + local_irq_restore(flags); + return i; /* Ugh, we could spin forever on unsupported proms ;( */ +} + +/* Non blocking put character to console device, returns -1 if + * unsuccessful. + */ +int +prom_nbputchar(char c) +{ + unsigned long flags; + int i = -1; + + local_irq_save(flags); + i = (*(romvec->pv_nbputchar))(c); + local_irq_restore(flags); + return i; /* Ugh, we could spin forever on unsupported proms ;( */ +} + +/* Blocking version of get character routine above. */ +char +prom_getchar(void) +{ + int character; + while((character = prom_nbgetchar()) == -1) ; + return (char) character; +} + +/* Blocking version of put character routine above. */ +void +prom_putchar(char c) +{ + while(prom_nbputchar(c) == -1) ; + return; +} + +/* Query for input device type */ +#if 0 +enum prom_input_device +prom_query_input_device() +{ + unsigned long flags; + int st_p; + char propb[64]; + char *p; + + switch(prom_vers) { + case PROM_V0: + case PROM_V2: + default: + switch(*romvec->pv_stdin) { + case PROMDEV_KBD: return PROMDEV_IKBD; + case PROMDEV_TTYA: return PROMDEV_ITTYA; + case PROMDEV_TTYB: return PROMDEV_ITTYB; + default: + return PROMDEV_I_UNK; + }; + case PROM_V3: + case PROM_P1275: + local_irq_save(flags); + st_p = (*romvec->pv_v2devops.v2_inst2pkg)(*romvec->pv_v2bootargs.fd_stdin); + __asm__ __volatile__("ld [%0], %%g6\n\t" : : + "r" (¤t_set[smp_processor_id()]) : + "memory"); + local_irq_restore(flags); + if(prom_node_has_property(st_p, "keyboard")) + return PROMDEV_IKBD; + prom_getproperty(st_p, "device_type", propb, sizeof(propb)); + if(strncmp(propb, "serial", sizeof("serial"))) + return PROMDEV_I_UNK; + prom_getproperty(prom_root_node, "stdin-path", propb, sizeof(propb)); + p = propb; + while(*p) p++; p -= 2; + if(p[0] == ':') { + if(p[1] == 'a') + return PROMDEV_ITTYA; + else if(p[1] == 'b') + return PROMDEV_ITTYB; + } + return PROMDEV_I_UNK; + }; +} +#endif + +/* Query for output device type */ + +#if 0 +enum prom_output_device +prom_query_output_device() +{ + unsigned long flags; + int st_p; + char propb[64]; + char *p; + int propl; + + switch(prom_vers) { + case PROM_V0: + switch(*romvec->pv_stdin) { + case PROMDEV_SCREEN: return PROMDEV_OSCREEN; + case PROMDEV_TTYA: return PROMDEV_OTTYA; + case PROMDEV_TTYB: return PROMDEV_OTTYB; + }; + break; + case PROM_V2: + case PROM_V3: + case PROM_P1275: + local_irq_save(flags); + st_p = (*romvec->pv_v2devops.v2_inst2pkg)(*romvec->pv_v2bootargs.fd_stdout); + __asm__ __volatile__("ld [%0], %%g6\n\t" : : + "r" (¤t_set[smp_processor_id()]) : + "memory"); + local_irq_restore(flags); + propl = prom_getproperty(st_p, "device_type", propb, sizeof(propb)); + if (propl >= 0 && propl == sizeof("display") && + strncmp("display", propb, sizeof("display")) == 0) + { + return PROMDEV_OSCREEN; + } + if(prom_vers == PROM_V3) { + if(strncmp("serial", propb, sizeof("serial"))) + return PROMDEV_O_UNK; + prom_getproperty(prom_root_node, "stdout-path", propb, sizeof(propb)); + p = propb; + while(*p) p++; p -= 2; + if(p[0]==':') { + if(p[1] == 'a') + return PROMDEV_OTTYA; + else if(p[1] == 'b') + return PROMDEV_OTTYB; + } + return PROMDEV_O_UNK; + } else { + /* This works on SS-2 (an early OpenFirmware) still. */ + switch(*romvec->pv_stdin) { + case PROMDEV_TTYA: return PROMDEV_OTTYA; + case PROMDEV_TTYB: return PROMDEV_OTTYB; + }; + } + break; + }; + return PROMDEV_O_UNK; +} +#endif diff --git a/arch/m68k/sun3/prom/init.c b/arch/m68k/sun3/prom/init.c new file mode 100644 index 000000000..0c6d99df6 --- /dev/null +++ b/arch/m68k/sun3/prom/init.c @@ -0,0 +1,36 @@ +// 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) + */ + +#include +#include + +#include +#include + +struct linux_romvec *romvec; +enum prom_major_version prom_vers; +unsigned int prom_rev, prom_prev; + +/* The root node of the prom device tree. */ +int 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; + + /* Initialization successful. */ + return; +} diff --git a/arch/m68k/sun3/prom/misc.c b/arch/m68k/sun3/prom/misc.c new file mode 100644 index 000000000..b8c95940d --- /dev/null +++ b/arch/m68k/sun3/prom/misc.c @@ -0,0 +1,95 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include + +/* Reset and reboot the machine with the command 'bcommand'. */ +void +prom_reboot(char *bcommand) +{ + unsigned long flags; + local_irq_save(flags); + (*(romvec->pv_reboot))(bcommand); + local_irq_restore(flags); +} + +/* Drop into the prom, with the chance to continue with the 'go' + * prom command. + */ +void +prom_cmdline(void) +{ +} + +/* Drop into the prom, but completely terminate the program. + * No chance of continuing. + */ +void +prom_halt(void) +{ + unsigned long flags; +again: + local_irq_save(flags); + (*(romvec->pv_halt))(); + local_irq_restore(flags); + goto again; /* PROM is out to get me -DaveM */ +} + +typedef void (*sfunc_t)(void); + +/* 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 i, oldsfc; + GET_SFC(oldsfc); + SET_SFC(FC_CONTROL); + for(i=0;ipv_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/m68k/sun3/prom/printf.c b/arch/m68k/sun3/prom/printf.c new file mode 100644 index 000000000..cb4934d39 --- /dev/null +++ b/arch/m68k/sun3/prom/printf.c @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * printf.c: Internal prom library printf facility. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + */ + +/* This routine is internal to the prom library, no one else should know + * about or use it! It's simple and smelly anyway.... + */ + +#include + +#include +#include + +#ifdef CONFIG_KGDB +extern int kgdb_initialized; +#endif + +static char ppbuf[1024]; + +void +prom_printf(char *fmt, ...) +{ + va_list args; + char ch, *bptr; + + va_start(args, fmt); + +#ifdef CONFIG_KGDB + ppbuf[0] = 'O'; + vsnprintf(ppbuf + 1, sizeof(ppbuf) - 1, fmt, args); +#else + vsnprintf(ppbuf, sizeof(ppbuf), fmt, args); +#endif + + bptr = ppbuf; + +#ifdef CONFIG_KGDB + if (kgdb_initialized) { + pr_info("kgdb_initialized = %d\n", kgdb_initialized); + putpacket(bptr, 1); + } else +#else + while((ch = *(bptr++)) != 0) { + if(ch == '\n') + prom_putchar('\r'); + + prom_putchar(ch); + } +#endif + va_end(args); + return; +} diff --git a/arch/m68k/sun3/sun3.h b/arch/m68k/sun3/sun3.h new file mode 100644 index 000000000..8d98c0aae --- /dev/null +++ b/arch/m68k/sun3/sun3.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include + +struct rtc_time; + +/* config.c */ +asmlinkage void sun3_init(void); + +/* idprom.c */ +void sun3_get_model(char *model); + +/* intersil.c */ +int sun3_hwclk(int set, struct rtc_time *t); + +/* leds.c */ +void sun3_leds(unsigned char byte); + +/* mmu_emu.c */ +void mmu_emu_init(unsigned long bootmem_end); +int mmu_emu_handle_fault(unsigned long vaddr, int read_flag, int kernel_fault); +void print_pte_vaddr(unsigned long vaddr); diff --git a/arch/m68k/sun3/sun3dvma.c b/arch/m68k/sun3/sun3dvma.c new file mode 100644 index 000000000..225fc735e --- /dev/null +++ b/arch/m68k/sun3/sun3dvma.c @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * linux/arch/m68k/sun3/sun3dvma.c + * + * Copyright (C) 2000 Sam Creasey + * + * Contains common routines for sun3/sun3x DVMA management. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#undef DVMA_DEBUG + +static unsigned long *iommu_use; + +#define dvma_index(baddr) ((baddr - DVMA_START) >> DVMA_PAGE_SHIFT) + +#define dvma_entry_use(baddr) (iommu_use[dvma_index(baddr)]) + +struct hole { + unsigned long start; + unsigned long end; + unsigned long size; + struct list_head list; +}; + +static struct list_head hole_list; +static struct list_head hole_cache; +static struct hole initholes[64]; + +#ifdef DVMA_DEBUG + +static unsigned long dvma_allocs; +static unsigned long dvma_frees; +static unsigned long long dvma_alloc_bytes; +static unsigned long long dvma_free_bytes; + +static void print_use(void) +{ + + int i; + int j = 0; + + pr_info("dvma entry usage:\n"); + + for(i = 0; i < IOMMU_TOTAL_ENTRIES; i++) { + if(!iommu_use[i]) + continue; + + j++; + + pr_info("dvma entry: %08x len %08lx\n", + (i << DVMA_PAGE_SHIFT) + DVMA_START, iommu_use[i]); + } + + pr_info("%d entries in use total\n", j); + + pr_info("allocation/free calls: %lu/%lu\n", dvma_allocs, dvma_frees); + pr_info("allocation/free bytes: %Lx/%Lx\n", dvma_alloc_bytes, + dvma_free_bytes); +} + +static void print_holes(struct list_head *holes) +{ + + struct list_head *cur; + struct hole *hole; + + pr_info("listing dvma holes\n"); + list_for_each(cur, holes) { + hole = list_entry(cur, struct hole, list); + + if((hole->start == 0) && (hole->end == 0) && (hole->size == 0)) + continue; + + pr_info("hole: start %08lx end %08lx size %08lx\n", + hole->start, hole->end, hole->size); + } + + pr_info("end of hole listing...\n"); +} +#endif /* DVMA_DEBUG */ + +static inline int refill(void) +{ + + struct hole *hole; + struct hole *prev = NULL; + struct list_head *cur; + int ret = 0; + + list_for_each(cur, &hole_list) { + hole = list_entry(cur, struct hole, list); + + if(!prev) { + prev = hole; + continue; + } + + if(hole->end == prev->start) { + hole->size += prev->size; + hole->end = prev->end; + list_move(&(prev->list), &hole_cache); + ret++; + } + + } + + return ret; +} + +static inline struct hole *rmcache(void) +{ + struct hole *ret; + + if(list_empty(&hole_cache)) { + if(!refill()) { + pr_crit("out of dvma hole cache!\n"); + BUG(); + } + } + + ret = list_entry(hole_cache.next, struct hole, list); + list_del(&(ret->list)); + + return ret; + +} + +static inline unsigned long get_baddr(int len, unsigned long align) +{ + + struct list_head *cur; + struct hole *hole; + + if(list_empty(&hole_list)) { +#ifdef DVMA_DEBUG + pr_crit("out of dvma holes! (printing hole cache)\n"); + print_holes(&hole_cache); + print_use(); +#endif + BUG(); + } + + list_for_each(cur, &hole_list) { + unsigned long newlen; + + hole = list_entry(cur, struct hole, list); + + if(align > DVMA_PAGE_SIZE) + newlen = len + ((hole->end - len) & (align-1)); + else + newlen = len; + + if(hole->size > newlen) { + hole->end -= newlen; + hole->size -= newlen; + dvma_entry_use(hole->end) = newlen; +#ifdef DVMA_DEBUG + dvma_allocs++; + dvma_alloc_bytes += newlen; +#endif + return hole->end; + } else if(hole->size == newlen) { + list_move(&(hole->list), &hole_cache); + dvma_entry_use(hole->start) = newlen; +#ifdef DVMA_DEBUG + dvma_allocs++; + dvma_alloc_bytes += newlen; +#endif + return hole->start; + } + + } + + pr_crit("unable to find dvma hole!\n"); + BUG(); + return 0; +} + +static inline int free_baddr(unsigned long baddr) +{ + + unsigned long len; + struct hole *hole; + struct list_head *cur; + + len = dvma_entry_use(baddr); + dvma_entry_use(baddr) = 0; + baddr &= DVMA_PAGE_MASK; + dvma_unmap_iommu(baddr, len); + +#ifdef DVMA_DEBUG + dvma_frees++; + dvma_free_bytes += len; +#endif + + list_for_each(cur, &hole_list) { + hole = list_entry(cur, struct hole, list); + + if(hole->end == baddr) { + hole->end += len; + hole->size += len; + return 0; + } else if(hole->start == (baddr + len)) { + hole->start = baddr; + hole->size += len; + return 0; + } + + } + + hole = rmcache(); + + hole->start = baddr; + hole->end = baddr + len; + hole->size = len; + +// list_add_tail(&(hole->list), cur); + list_add(&(hole->list), cur); + + return 0; + +} + +void __init dvma_init(void) +{ + + struct hole *hole; + int i; + + INIT_LIST_HEAD(&hole_list); + INIT_LIST_HEAD(&hole_cache); + + /* prepare the hole cache */ + for(i = 0; i < 64; i++) + list_add(&(initholes[i].list), &hole_cache); + + hole = rmcache(); + hole->start = DVMA_START; + hole->end = DVMA_END; + hole->size = DVMA_SIZE; + + list_add(&(hole->list), &hole_list); + + iommu_use = memblock_alloc_or_panic(IOMMU_TOTAL_ENTRIES * sizeof(unsigned long), + SMP_CACHE_BYTES); + dvma_unmap_iommu(DVMA_START, DVMA_SIZE); + + sun3_dvma_init(); +} + +unsigned long dvma_map_align(unsigned long kaddr, int len, int align) +{ + + unsigned long baddr; + unsigned long off; + + if(!len) + len = 0x800; + + if(!kaddr || !len) { +// pr_err("error: kaddr %lx len %x\n", kaddr, len); +// *(int *)4 = 0; + return 0; + } + + pr_debug("dvma_map request %08x bytes from %08lx\n", len, kaddr); + off = kaddr & ~DVMA_PAGE_MASK; + kaddr &= PAGE_MASK; + len += off; + len = ((len + (DVMA_PAGE_SIZE-1)) & DVMA_PAGE_MASK); + + if(align == 0) + align = DVMA_PAGE_SIZE; + else + align = ((align + (DVMA_PAGE_SIZE-1)) & DVMA_PAGE_MASK); + + baddr = get_baddr(len, align); +// pr_info("using baddr %lx\n", baddr); + + if(!dvma_map_iommu(kaddr, baddr, len)) + return (baddr + off); + + pr_crit("dvma_map failed kaddr %lx baddr %lx len %x\n", kaddr, baddr, + len); + BUG(); + return 0; +} +EXPORT_SYMBOL(dvma_map_align); + +void dvma_unmap(void *baddr) +{ + unsigned long addr; + + addr = (unsigned long)baddr; + /* check if this is a vme mapping */ + if(!(addr & 0x00f00000)) + addr |= 0xf00000; + + free_baddr(addr); + + return; + +} +EXPORT_SYMBOL(dvma_unmap); + +void *dvma_malloc_align(unsigned long len, unsigned long align) +{ + unsigned long kaddr; + unsigned long baddr; + unsigned long vaddr; + + if(!len) + return NULL; + + pr_debug("dvma_malloc request %lx bytes\n", len); + len = ((len + (DVMA_PAGE_SIZE-1)) & DVMA_PAGE_MASK); + + if((kaddr = __get_free_pages(GFP_ATOMIC, get_order(len))) == 0) + return NULL; + + if((baddr = (unsigned long)dvma_map_align(kaddr, len, align)) == 0) { + free_pages(kaddr, get_order(len)); + return NULL; + } + + vaddr = dvma_btov(baddr); + + if(dvma_map_cpu(kaddr, vaddr, len) < 0) { + dvma_unmap((void *)baddr); + free_pages(kaddr, get_order(len)); + return NULL; + } + + pr_debug("mapped %08lx bytes %08lx kern -> %08lx bus\n", len, kaddr, + baddr); + + return (void *)vaddr; + +} +EXPORT_SYMBOL(dvma_malloc_align); + +void dvma_free(void *vaddr) +{ + + return; + +} +EXPORT_SYMBOL(dvma_free); diff --git a/arch/m68k/sun3/sun3ints.c b/arch/m68k/sun3/sun3ints.c new file mode 100644 index 000000000..32eaf55f8 --- /dev/null +++ b/arch/m68k/sun3/sun3ints.c @@ -0,0 +1,100 @@ + /* + * linux/arch/m68k/sun3/sun3ints.c -- Sun-3(x) Linux interrupt handling code + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sun3.h" + +void sun3_disable_interrupts(void) +{ + sun3_disable_irq(0); +} + +void sun3_enable_interrupts(void) +{ + sun3_enable_irq(0); +} + +static unsigned char led_pattern[8] = { + (u8)~(0x80), (u8)~(0x01), + (u8)~(0x40), (u8)~(0x02), + (u8)~(0x20), (u8)~(0x04), + (u8)~(0x10), (u8)~(0x08) +}; + +volatile unsigned char* sun3_intreg; + +void sun3_enable_irq(unsigned int irq) +{ + *sun3_intreg |= (1 << irq); +} + +void sun3_disable_irq(unsigned int irq) +{ + *sun3_intreg &= ~(1 << irq); +} + +static irqreturn_t sun3_int7(int irq, void *dev_id) +{ + unsigned int cnt; + + cnt = kstat_irqs_cpu(irq, 0); + if (!(cnt % 2000)) + sun3_leds(led_pattern[cnt % 16000 / 2000]); + return IRQ_HANDLED; +} + +static irqreturn_t sun3_int5(int irq, void *dev_id) +{ + unsigned long flags; + unsigned int cnt; + + local_irq_save(flags); +#ifdef CONFIG_SUN3 + intersil_clear(); +#endif + sun3_disable_irq(5); + sun3_enable_irq(5); +#ifdef CONFIG_SUN3 + intersil_clear(); +#endif + legacy_timer_tick(1); + cnt = kstat_irqs_cpu(irq, 0); + if (!(cnt % 20)) + sun3_leds(led_pattern[cnt % 160 / 20]); + local_irq_restore(flags); + return IRQ_HANDLED; +} + +static irqreturn_t sun3_vec255(int irq, void *dev_id) +{ + return IRQ_HANDLED; +} + +void __init sun3_init_IRQ(void) +{ + *sun3_intreg = 1; + + m68k_setup_user_interrupt(VEC_USER, 128); + + if (request_irq(IRQ_AUTO_5, sun3_int5, 0, "clock", NULL)) + pr_err("Couldn't register %s interrupt\n", "int5"); + if (request_irq(IRQ_AUTO_7, sun3_int7, 0, "nmi", NULL)) + pr_err("Couldn't register %s interrupt\n", "int7"); + if (request_irq(IRQ_USER+127, sun3_vec255, 0, "vec255", NULL)) + pr_err("Couldn't register %s interrupt\n", "vec255"); +} -- cgit v1.3.1