summaryrefslogtreecommitdiffstats
path: root/arch/m68k/sun3
diff options
context:
space:
mode:
Diffstat (limited to 'arch/m68k/sun3')
-rw-r--r--arch/m68k/sun3/Makefile7
-rw-r--r--arch/m68k/sun3/config.c226
-rw-r--r--arch/m68k/sun3/dvma.c69
-rw-r--r--arch/m68k/sun3/idprom.c132
-rw-r--r--arch/m68k/sun3/intersil.c76
-rw-r--r--arch/m68k/sun3/leds.c14
-rw-r--r--arch/m68k/sun3/mmu_emu.c429
-rw-r--r--arch/m68k/sun3/prom/Makefile6
-rw-r--r--arch/m68k/sun3/prom/console.c170
-rw-r--r--arch/m68k/sun3/prom/init.c36
-rw-r--r--arch/m68k/sun3/prom/misc.c95
-rw-r--r--arch/m68k/sun3/prom/printf.c56
-rw-r--r--arch/m68k/sun3/sun3dvma.c377
-rw-r--r--arch/m68k/sun3/sun3ints.c102
14 files changed, 1795 insertions, 0 deletions
diff --git a/arch/m68k/sun3/Makefile b/arch/m68k/sun3/Makefile
new file mode 100644
index 000000000..38ba0e0ce
--- /dev/null
+++ b/arch/m68k/sun3/Makefile
@@ -0,0 +1,7 @@
+#
+# 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
diff --git a/arch/m68k/sun3/config.c b/arch/m68k/sun3/config.c
new file mode 100644
index 000000000..79a2bb857
--- /dev/null
+++ b/arch/m68k/sun3/config.c
@@ -0,0 +1,226 @@
+/*
+ * 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 <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/seq_file.h>
+#include <linux/tty.h>
+#include <linux/console.h>
+#include <linux/init.h>
+#include <linux/bootmem.h>
+#include <linux/platform_device.h>
+
+#include <asm/oplib.h>
+#include <asm/setup.h>
+#include <asm/contregs.h>
+#include <asm/movs.h>
+#include <asm/pgtable.h>
+#include <asm/pgalloc.h>
+#include <asm/sun3-head.h>
+#include <asm/sun3mmu.h>
+#include <asm/machdep.h>
+#include <asm/machines.h>
+#include <asm/idprom.h>
+#include <asm/intersil.h>
+#include <asm/irq.h>
+#include <asm/sections.h>
+#include <asm/segment.h>
+#include <asm/sun3ints.h>
+
+char sun3_reserved_pmeg[SUN3_PMEGS_NUM];
+
+extern u32 sun3_gettimeoffset(void);
+static void sun3_sched_init(irq_handler_t handler);
+extern void sun3_get_model (char* model);
+extern int sun3_hwclk(int set, struct rtc_time *t);
+
+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);
+}
+
+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_fs(KERNEL_DS);
+}
+
+/* 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)
+{
+ unsigned long start_page;
+
+ /* align start/end to page boundaries */
+ memory_start = ((memory_start + (PAGE_SIZE-1)) & PAGE_MASK);
+ memory_end = memory_end & PAGE_MASK;
+
+ start_page = __pa(memory_start) >> PAGE_SHIFT;
+ 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;
+ arch_gettimeoffset = sun3_gettimeoffset;
+ 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(irq_handler_t timer_routine)
+{
+ 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,
+ },
+};
+
+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..5f92c72b0
--- /dev/null
+++ b/arch/m68k/sun3/dvma.c
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/arch/m68k/sun3/dvma.c
+ *
+ * Written by Sam Creasey
+ *
+ * Sun3 IOMMU routines used for dvma accesses.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/bootmem.h>
+#include <linux/list.h>
+#include <asm/page.h>
+#include <asm/pgtable.h>
+#include <asm/sun3mmu.h>
+#include <asm/dvma.h>
+
+
+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(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..1ace5353d
--- /dev/null
+++ b/arch/m68k/sun3/idprom.c
@@ -0,0 +1,132 @@
+// 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/string.h>
+
+#include <asm/oplib.h>
+#include <asm/idprom.h>
+#include <asm/machines.h> /* Fun with Sun released architectures. */
+
+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(unsigned 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..d911070af
--- /dev/null
+++ b/arch/m68k/sun3/intersil.c
@@ -0,0 +1,76 @@
+/*
+ * 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 <linux/kernel.h>
+#include <linux/rtc.h>
+
+#include <asm/errno.h>
+#include <asm/intersil.h>
+#include <asm/machdep.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)
+
+/* does this need to be implemented? */
+u32 sun3_gettimeoffset(void)
+{
+ return 1000;
+}
+
+
+/* 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..7c67b58eb
--- /dev/null
+++ b/arch/m68k/sun3/leds.c
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <asm/contregs.h>
+#include <asm/sun3mmu.h>
+#include <asm/io.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..d30da12a1
--- /dev/null
+++ b/arch/m68k/sun3/mmu_emu.c
@@ -0,0 +1,429 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+** Tablewalk MMU emulator
+**
+** by Toshiyasu Morita
+**
+** Started 1/16/98 @ 2:22 am
+*/
+
+#include <linux/init.h>
+#include <linux/mman.h>
+#include <linux/mm.h>
+#include <linux/kernel.h>
+#include <linux/ptrace.h>
+#include <linux/delay.h>
+#include <linux/bootmem.h>
+#include <linux/bitops.h>
+#include <linux/module.h>
+#include <linux/sched/mm.h>
+
+#include <asm/setup.h>
+#include <asm/traps.h>
+#include <linux/uaccess.h>
+#include <asm/page.h>
+#include <asm/pgtable.h>
+#include <asm/sun3mmu.h>
+#include <asm/segment.h>
+#include <asm/oplib.h>
+#include <asm/mmu_context.h>
+#include <asm/dvma.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. */
+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_fs(MAKE_MM_SEG(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_fs(KERNEL_DS);
+
+}
+
+/* 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("clear_context: context not allocated\n");
+
+ 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("get_free_context: failed to find free context");
+ }
+
+ 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<SUN3_PMEG_SIZE; i+=SUN3_PTE_SIZE)
+ sun3_put_pte (vaddr + i, SUN3_PAGE_SYSTEM);
+
+ /* Consider a different one next time. */
+ ++curr_pmeg;
+}
+
+/*
+ * Handle a pagefault at virtual address `vaddr'; check if there should be a
+ * page there (specifically, whether the software pagetables indicate that
+ * there is). This is necessary due to the limited size of the second-level
+ * Sun3 hardware pagetables (256 groups of 16 pages). If there should be a
+ * mapping present, we select a `spare' PMEG and use it to create a mapping.
+ * `read_flag' is nonzero for a read fault; zero for a write. Returns nonzero
+ * if we successfully handled the fault.
+ */
+//todo: should we bump minor pagefault counter? if so, here or in caller?
+//todo: possibly inline this into bus_error030 in <asm/buserror.h> ?
+
+// 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("mmu_emu_handle_fault: vaddr=%lx type=%s crp=%p\n",
+ vaddr, read_flag ? "read" : "write", crp);
+#endif
+
+ segment = (vaddr >> SUN3_PMEG_SIZE_BITS) & 0x7FF;
+ offset = (vaddr >> SUN3_PTE_SIZE_BITS) & 0xF;
+
+#ifdef DEBUG_MMU_EMU
+ pr_info("mmu_emu_handle_fault: segment=%lx offset=%lx\n", 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:%p ->", get_fs().seg, 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..da7eac06b
--- /dev/null
+++ b/arch/m68k/sun3/prom/Makefile
@@ -0,0 +1,6 @@
+# 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 <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <asm/openprom.h>
+#include <asm/oplib.h>
+#include <linux/string.h>
+
+/* 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" (&current_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" (&current_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 <linux/kernel.h>
+#include <linux/init.h>
+
+#include <asm/openprom.h>
+#include <asm/oplib.h>
+
+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 <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <asm/sun3-head.h>
+#include <asm/idprom.h>
+#include <asm/openprom.h>
+#include <asm/oplib.h>
+#include <asm/movs.h>
+
+/* 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;i<num_bytes; i++)
+ {
+ /* There is a problem with the GET_CONTROL_BYTE
+ macro; defining the extra variable
+ gets around it.
+ */
+ int c;
+ GET_CONTROL_BYTE(SUN3_IDPROM_BASE + i, c);
+ idbuf[i] = c;
+ }
+ SET_SFC(oldsfc);
+ return idbuf[0];
+}
+
+/* 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/m68k/sun3/prom/printf.c b/arch/m68k/sun3/prom/printf.c
new file mode 100644
index 000000000..b6724cc66
--- /dev/null
+++ b/arch/m68k/sun3/prom/printf.c
@@ -0,0 +1,56 @@
+// 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 <linux/kernel.h>
+
+#include <asm/openprom.h>
+#include <asm/oplib.h>
+
+#ifdef CONFIG_KGDB
+extern int kgdb_initialized;
+#endif
+
+static char ppbuf[1024];
+
+void
+prom_printf(char *fmt, ...)
+{
+ va_list args;
+ char ch, *bptr;
+ int i;
+
+ va_start(args, fmt);
+
+#ifdef CONFIG_KGDB
+ ppbuf[0] = 'O';
+ i = vsprintf(ppbuf + 1, fmt, args) + 1;
+#else
+ i = vsprintf(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/sun3dvma.c b/arch/m68k/sun3/sun3dvma.c
new file mode 100644
index 000000000..8546922ad
--- /dev/null
+++ b/arch/m68k/sun3/sun3dvma.c
@@ -0,0 +1,377 @@
+// 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 <linux/bootmem.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/gfp.h>
+#include <linux/mm.h>
+#include <linux/list.h>
+
+#include <asm/page.h>
+#include <asm/pgtable.h>
+#include <asm/dvma.h>
+
+#undef DVMA_DEBUG
+
+#ifdef CONFIG_SUN3X
+extern void dvma_unmap_iommu(unsigned long baddr, int len);
+#else
+static inline void dvma_unmap_iommu(unsigned long a, int b)
+{
+}
+#endif
+
+#ifdef CONFIG_SUN3
+extern void sun3_dvma_init(void);
+#endif
+
+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;
+ unsigned long orig_baddr;
+
+ orig_baddr = baddr;
+ 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 = alloc_bootmem(IOMMU_TOTAL_ENTRIES * sizeof(unsigned long));
+
+ dvma_unmap_iommu(DVMA_START, DVMA_SIZE);
+
+#ifdef CONFIG_SUN3
+ sun3_dvma_init();
+#endif
+
+}
+
+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..a5824abb4
--- /dev/null
+++ b/arch/m68k/sun3/sun3ints.c
@@ -0,0 +1,102 @@
+ /*
+ * 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 <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/kernel_stat.h>
+#include <linux/interrupt.h>
+#include <asm/segment.h>
+#include <asm/intersil.h>
+#include <asm/oplib.h>
+#include <asm/sun3ints.h>
+#include <asm/irq_regs.h>
+#include <linux/seq_file.h>
+
+extern void sun3_leds (unsigned char);
+
+void sun3_disable_interrupts(void)
+{
+ sun3_disable_irq(0);
+}
+
+void sun3_enable_interrupts(void)
+{
+ sun3_enable_irq(0);
+}
+
+static int led_pattern[8] = {
+ ~(0x80), ~(0x01),
+ ~(0x40), ~(0x02),
+ ~(0x20), ~(0x04),
+ ~(0x10), ~(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
+ xtime_update(1);
+ update_process_times(user_mode(get_irq_regs()));
+ 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");
+}