summaryrefslogtreecommitdiffstats
path: root/arch/mips/dec
diff options
context:
space:
mode:
authorJinke Han <jinkehan@didiglobal.com>2026-09-08 15:37:42 +0800
committerIngo Molnar <mingo@kernel.org>2026-09-17 09:48:42 +0200
commita5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch)
tree73fef824f9a846cf935869d0dbc5070ef6e2f741 /arch/mips/dec
downloadlinux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz
linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Signed-off-by: Jinke Han <jinkehan@didiglobal.com> Signed-off-by: Ingo Molnar <mingo@kernel.org> Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Acked-by: Yafang Shao <laoar.shao@gmail.com> Acked-by: Borislav Petkov <bp@alien8.de> Cc: Peter Zijlstra <peterz@infradead.org> Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'arch/mips/dec')
-rw-r--r--arch/mips/dec/Makefile10
-rw-r--r--arch/mips/dec/Platform7
-rw-r--r--arch/mips/dec/ecc-berr.c280
-rw-r--r--arch/mips/dec/int-handler.S311
-rw-r--r--arch/mips/dec/ioasic-irq.c109
-rw-r--r--arch/mips/dec/kn01-berr.c196
-rw-r--r--arch/mips/dec/kn02-irq.c75
-rw-r--r--arch/mips/dec/kn02xa-berr.c141
-rw-r--r--arch/mips/dec/platform.c151
-rw-r--r--arch/mips/dec/prom/Makefile9
-rw-r--r--arch/mips/dec/prom/cmdline.c40
-rw-r--r--arch/mips/dec/prom/console.c44
-rw-r--r--arch/mips/dec/prom/dectypes.h15
-rw-r--r--arch/mips/dec/prom/identify.c187
-rw-r--r--arch/mips/dec/prom/init.c141
-rw-r--r--arch/mips/dec/prom/locore.S30
-rw-r--r--arch/mips/dec/prom/memory.c117
-rw-r--r--arch/mips/dec/reset.c43
-rw-r--r--arch/mips/dec/setup.c774
-rw-r--r--arch/mips/dec/tc.c95
-rw-r--r--arch/mips/dec/time.c172
-rw-r--r--arch/mips/dec/wbflush.c92
22 files changed, 3039 insertions, 0 deletions
diff --git a/arch/mips/dec/Makefile b/arch/mips/dec/Makefile
new file mode 100644
index 000000000..c9f62f1da
--- /dev/null
+++ b/arch/mips/dec/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for the DECstation family specific parts of the kernel
+#
+
+obj-y := ecc-berr.o int-handler.o ioasic-irq.o kn01-berr.o \
+ kn02-irq.o kn02xa-berr.o platform.o reset.o setup.o time.o
+
+obj-$(CONFIG_TC) += tc.o
+obj-$(CONFIG_CPU_HAS_WB) += wbflush.o
diff --git a/arch/mips/dec/Platform b/arch/mips/dec/Platform
new file mode 100644
index 000000000..c82391e83
--- /dev/null
+++ b/arch/mips/dec/Platform
@@ -0,0 +1,7 @@
+#
+# DECstation family
+#
+cflags-$(CONFIG_MACH_DECSTATION) += \
+ -I$(srctree)/arch/mips/include/asm/mach-dec
+libs-$(CONFIG_MACH_DECSTATION) += arch/mips/dec/prom/
+load-$(CONFIG_MACH_DECSTATION) += 0xffffffff80040000
diff --git a/arch/mips/dec/ecc-berr.c b/arch/mips/dec/ecc-berr.c
new file mode 100644
index 000000000..3d41ff07d
--- /dev/null
+++ b/arch/mips/dec/ecc-berr.c
@@ -0,0 +1,280 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Bus error event handling code for systems equipped with ECC
+ * handling logic, i.e. DECstation/DECsystem 5000/200 (KN02),
+ * 5000/240 (KN03), 5000/260 (KN05) and DECsystem 5900 (KN03),
+ * 5900/260 (KN05) systems.
+ *
+ * Copyright (c) 2003, 2005, 2026 Maciej W. Rozycki
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/ratelimit.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+
+#include <asm/addrspace.h>
+#include <asm/bootinfo.h>
+#include <asm/cpu.h>
+#include <asm/cpu-type.h>
+#include <asm/irq_regs.h>
+#include <asm/processor.h>
+#include <asm/ptrace.h>
+#include <asm/traps.h>
+
+#include <asm/dec/ecc.h>
+#include <asm/dec/kn02.h>
+#include <asm/dec/kn03.h>
+#include <asm/dec/kn05.h>
+
+static volatile u32 *kn0x_erraddr;
+static volatile u32 *kn0x_chksyn;
+
+static inline void dec_ecc_be_ack(void)
+{
+ *kn0x_erraddr = 0; /* any write clears the IRQ */
+ iob();
+}
+
+static int dec_ecc_be_backend(struct pt_regs *regs, int is_fixup, int invoker)
+{
+ static const char excstr[] = "exception";
+ static const char intstr[] = "interrupt";
+ static const char cpustr[] = "CPU";
+ static const char dmastr[] = "DMA";
+ static const char readstr[] = "read";
+ static const char mreadstr[] = "memory read";
+ static const char writestr[] = "write";
+ static const char mwritstr[] = "partial memory write";
+ static const char timestr[] = "timeout";
+ static const char overstr[] = "overrun";
+ static const char eccstr[] = "ECC error";
+
+ static DEFINE_RATELIMIT_STATE(rs,
+ DEFAULT_RATELIMIT_INTERVAL,
+ DEFAULT_RATELIMIT_BURST);
+
+ const char *kind, *agent, *cycle, *event;
+ const char *status = "", *xbit = "", *fmt = "";
+ unsigned long address;
+ u16 syn = 0, sngl;
+
+ int i = 0;
+
+ u32 erraddr = *kn0x_erraddr;
+ u32 chksyn = *kn0x_chksyn;
+ int action = MIPS_BE_FATAL;
+
+ /* For non-ECC ack ASAP, so that any subsequent errors get caught. */
+ if ((erraddr & (KN0X_EAR_VALID | KN0X_EAR_ECCERR)) == KN0X_EAR_VALID)
+ dec_ecc_be_ack();
+
+ kind = invoker ? intstr : excstr;
+
+ if (!(erraddr & KN0X_EAR_VALID)) {
+ /* No idea what happened. */
+ pr_alert_ratelimited("Unidentified bus error %s\n", kind);
+ return action;
+ }
+
+ agent = (erraddr & KN0X_EAR_CPU) ? cpustr : dmastr;
+
+ if (erraddr & KN0X_EAR_ECCERR) {
+ /* An ECC error on a CPU or DMA transaction. */
+ cycle = (erraddr & KN0X_EAR_WRITE) ? mwritstr : mreadstr;
+ event = eccstr;
+ } else {
+ /* A CPU timeout or a DMA overrun. */
+ cycle = (erraddr & KN0X_EAR_WRITE) ? writestr : readstr;
+ event = (erraddr & KN0X_EAR_CPU) ? timestr : overstr;
+ }
+
+ address = erraddr & KN0X_EAR_ADDRESS;
+ /* For ECC errors on reads adjust for MT pipelining. */
+ if ((erraddr & (KN0X_EAR_WRITE | KN0X_EAR_ECCERR)) == KN0X_EAR_ECCERR)
+ address = (address & ~0xfffLL) | ((address - 5) & 0xfffLL);
+ address <<= 2;
+
+ /* Only CPU errors are fixable. */
+ if (erraddr & KN0X_EAR_CPU && is_fixup)
+ action = MIPS_BE_FIXUP;
+
+ if (erraddr & KN0X_EAR_ECCERR) {
+ static const u8 data_sbit[32] = {
+ 0x4f, 0x4a, 0x52, 0x54, 0x57, 0x58, 0x5b, 0x5d,
+ 0x23, 0x25, 0x26, 0x29, 0x2a, 0x2c, 0x31, 0x34,
+ 0x0e, 0x0b, 0x13, 0x15, 0x16, 0x19, 0x1a, 0x1c,
+ 0x62, 0x64, 0x67, 0x68, 0x6b, 0x6d, 0x70, 0x75,
+ };
+ static const u8 data_mbit[25] = {
+ 0x07, 0x0d, 0x1f,
+ 0x2f, 0x32, 0x37, 0x38, 0x3b, 0x3d, 0x3e,
+ 0x43, 0x45, 0x46, 0x49, 0x4c, 0x51, 0x5e,
+ 0x61, 0x6e, 0x73, 0x76, 0x79, 0x7a, 0x7c, 0x7f,
+ };
+ static const char sbestr[] = "corrected single";
+ static const char dbestr[] = "uncorrectable double";
+ static const char mbestr[] = "uncorrectable multiple";
+
+ if (!(address & 0x4))
+ syn = chksyn; /* Low bank. */
+ else
+ syn = chksyn >> 16; /* High bank. */
+
+ if (!(syn & KN0X_ESR_VLDLO)) {
+ /* Ack now, no rewrite will happen. */
+ dec_ecc_be_ack();
+
+ fmt = KERN_ALERT "%s" "invalid\n";
+ } else {
+ sngl = syn & KN0X_ESR_SNGLO;
+ syn &= KN0X_ESR_SYNLO;
+
+ /*
+ * Multibit errors may be tagged incorrectly;
+ * check the syndrome explicitly.
+ */
+ for (i = 0; i < 25; i++)
+ if (syn == data_mbit[i])
+ break;
+
+ if (i < 25) {
+ status = mbestr;
+ } else if (!sngl) {
+ status = dbestr;
+ } else {
+ volatile u32 *ptr =
+ (void *)CKSEG1ADDR(address);
+
+ *ptr = *ptr; /* Rewrite. */
+ iob();
+
+ status = sbestr;
+ action = MIPS_BE_DISCARD;
+ }
+
+ /* Ack now, now we've rewritten (or not). */
+ dec_ecc_be_ack();
+
+ if (syn && syn == (syn & -syn)) {
+ if (syn == 0x01) {
+ fmt = KERN_ALERT "%s"
+ "%#04x -- %s bit error "
+ "at check bit C%s\n";
+ xbit = "X";
+ } else {
+ fmt = KERN_ALERT "%s"
+ "%#04x -- %s bit error "
+ "at check bit C%s%u\n";
+ }
+ i = syn >> 2;
+ } else {
+ for (i = 0; i < 32; i++)
+ if (syn == data_sbit[i])
+ break;
+ if (i < 32)
+ fmt = KERN_ALERT "%s"
+ "%#04x -- %s bit error "
+ "at data bit D%s%u\n";
+ else
+ fmt = KERN_ALERT "%s"
+ "%#04x -- %s bit error\n";
+ }
+ }
+ }
+
+ if (action != MIPS_BE_FIXUP && __ratelimit(&rs)) {
+ printk(KERN_ALERT "Bus error %s: %s %s %s at %#010lx\n",
+ kind, agent, cycle, event, address);
+
+ if (erraddr & KN0X_EAR_ECCERR)
+ printk(fmt, " ECC syndrome ", syn, status, xbit, i);
+ }
+
+ return action;
+}
+
+int dec_ecc_be_handler(struct pt_regs *regs, int is_fixup)
+{
+ return dec_ecc_be_backend(regs, is_fixup, 0);
+}
+
+irqreturn_t dec_ecc_be_interrupt(int irq, void *dev_id)
+{
+ struct pt_regs *regs = get_irq_regs();
+
+ int action = dec_ecc_be_backend(regs, 0, 1);
+
+ if (action == MIPS_BE_DISCARD)
+ return IRQ_HANDLED;
+
+ /*
+ * FIXME: Find the affected processes and kill them, otherwise
+ * we must die.
+ *
+ * The interrupt is asynchronously delivered thus EPC and RA
+ * may be irrelevant, but are printed for a reference.
+ */
+ printk(KERN_ALERT "Fatal bus interrupt, epc == %08lx, ra == %08lx\n",
+ regs->cp0_epc, regs->regs[31]);
+ die("Unrecoverable bus error", regs);
+}
+
+
+/*
+ * Initialization differs a bit between KN02 and KN03/KN05, so we
+ * need two variants. Once set up, all systems can be handled the
+ * same way.
+ */
+static inline void dec_kn02_be_init(void)
+{
+ volatile u32 *csr = (void *)CKSEG1ADDR(KN02_SLOT_BASE + KN02_CSR);
+
+ kn0x_erraddr = (void *)CKSEG1ADDR(KN02_SLOT_BASE + KN02_ERRADDR);
+ kn0x_chksyn = (void *)CKSEG1ADDR(KN02_SLOT_BASE + KN02_CHKSYN);
+
+ /* Preset write-only bits of the Control Register cache. */
+ cached_kn02_csr = *csr | KN02_CSR_LEDS;
+
+ /* Set normal ECC detection and generation. */
+ cached_kn02_csr &= ~(KN02_CSR_DIAGCHK | KN02_CSR_DIAGGEN);
+ /* Enable ECC correction. */
+ cached_kn02_csr |= KN02_CSR_CORRECT;
+ *csr = cached_kn02_csr;
+ iob();
+}
+
+static inline void dec_kn03_be_init(void)
+{
+ volatile u32 *mcr = (void *)CKSEG1ADDR(KN03_SLOT_BASE + IOASIC_MCR);
+ volatile u32 *mbcs = (void *)CKSEG1ADDR(KN4K_SLOT_BASE + KN4K_MB_CSR);
+
+ kn0x_erraddr = (void *)CKSEG1ADDR(KN03_SLOT_BASE + IOASIC_ERRADDR);
+ kn0x_chksyn = (void *)CKSEG1ADDR(KN03_SLOT_BASE + IOASIC_CHKSYN);
+
+ /*
+ * Set normal ECC detection and generation, enable ECC correction.
+ * For KN05 we also need to make sure EE (?) is enabled in the MB.
+ * Otherwise DBE/IBE exceptions would be masked but bus error
+ * interrupts would still arrive, resulting in an inevitable crash
+ * if get_dbe() triggers one.
+ */
+ *mcr = (*mcr & ~(KN03_MCR_DIAGCHK | KN03_MCR_DIAGGEN)) |
+ KN03_MCR_CORRECT;
+ if (current_cpu_type() == CPU_R4400SC)
+ *mbcs |= KN4K_MB_CSR_EE;
+ fast_iob();
+}
+
+void __init dec_ecc_be_init(void)
+{
+ if (mips_machtype == MACH_DS5000_200)
+ dec_kn02_be_init();
+ else
+ dec_kn03_be_init();
+
+ /* Clear any leftover errors from the firmware. */
+ dec_ecc_be_ack();
+}
diff --git a/arch/mips/dec/int-handler.S b/arch/mips/dec/int-handler.S
new file mode 100644
index 000000000..a0b439c90
--- /dev/null
+++ b/arch/mips/dec/int-handler.S
@@ -0,0 +1,311 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 1995, 1996, 1997 Paul M. Antoine and Harald Koerfgen
+ * Copyright (C) 2000, 2001, 2002, 2003, 2005 Maciej W. Rozycki
+ *
+ * Written by Ralf Baechle and Andreas Busse, modified for DECstation
+ * support by Paul Antoine and Harald Koerfgen.
+ *
+ * completely rewritten:
+ * Copyright (C) 1998 Harald Koerfgen
+ *
+ * Rewritten extensively for controller-driven IRQ support
+ * by Maciej W. Rozycki.
+ */
+
+#include <asm/addrspace.h>
+#include <asm/asm.h>
+#include <asm/mipsregs.h>
+#include <asm/regdef.h>
+#include <asm/stackframe.h>
+
+#include <asm/dec/interrupts.h>
+#include <asm/dec/ioasic_addrs.h>
+#include <asm/dec/ioasic_ints.h>
+#include <asm/dec/kn01.h>
+#include <asm/dec/kn02.h>
+#include <asm/dec/kn02xa.h>
+#include <asm/dec/kn03.h>
+
+#define KN02_CSR_BASE CKSEG1ADDR(KN02_SLOT_BASE + KN02_CSR)
+#define KN02XA_IOASIC_BASE CKSEG1ADDR(KN02XA_SLOT_BASE + IOASIC_IOCTL)
+#define KN03_IOASIC_BASE CKSEG1ADDR(KN03_SLOT_BASE + IOASIC_IOCTL)
+
+ .text
+ .set noreorder
+/*
+ * plat_irq_dispatch: Interrupt handler for DECstations
+ *
+ * We follow the model in the Indy interrupt code by David Miller, where he
+ * says: a lot of complication here is taken away because:
+ *
+ * 1) We handle one interrupt and return, sitting in a loop
+ * and moving across all the pending IRQ bits in the cause
+ * register is _NOT_ the answer, the common case is one
+ * pending IRQ so optimize in that direction.
+ *
+ * 2) We need not check against bits in the status register
+ * IRQ mask, that would make this routine slow as hell.
+ *
+ * 3) Linux only thinks in terms of all IRQs on or all IRQs
+ * off, nothing in between like BSD spl() brain-damage.
+ *
+ * Furthermore, the IRQs on the DECstations look basically (barring
+ * software IRQs which we don't use at all) like...
+ *
+ * DS2100/3100's, aka kn01, aka Pmax:
+ *
+ * MIPS IRQ Source
+ * -------- ------
+ * 0 Software (ignored)
+ * 1 Software (ignored)
+ * 2 SCSI
+ * 3 Lance Ethernet
+ * 4 DZ11 serial
+ * 5 RTC
+ * 6 Memory Controller & Video
+ * 7 FPU
+ *
+ * DS5000/200, aka kn02, aka 3max:
+ *
+ * MIPS IRQ Source
+ * -------- ------
+ * 0 Software (ignored)
+ * 1 Software (ignored)
+ * 2 TurboChannel
+ * 3 RTC
+ * 4 Reserved
+ * 5 Memory Controller
+ * 6 Reserved
+ * 7 FPU
+ *
+ * DS5000/1xx's, aka kn02ba, aka 3min:
+ *
+ * MIPS IRQ Source
+ * -------- ------
+ * 0 Software (ignored)
+ * 1 Software (ignored)
+ * 2 TurboChannel Slot 0
+ * 3 TurboChannel Slot 1
+ * 4 TurboChannel Slot 2
+ * 5 TurboChannel Slot 3 (ASIC)
+ * 6 Halt button
+ * 7 FPU/R4k timer
+ *
+ * DS5000/2x's, aka kn02ca, aka maxine:
+ *
+ * MIPS IRQ Source
+ * -------- ------
+ * 0 Software (ignored)
+ * 1 Software (ignored)
+ * 2 Periodic Interrupt (100usec)
+ * 3 RTC
+ * 4 I/O write timeout
+ * 5 TurboChannel (ASIC)
+ * 6 Halt Keycode from Access.Bus keyboard (CTRL-ALT-ENTER)
+ * 7 FPU/R4k timer
+ *
+ * DS5000/2xx's, aka kn03, aka 3maxplus:
+ *
+ * MIPS IRQ Source
+ * -------- ------
+ * 0 Software (ignored)
+ * 1 Software (ignored)
+ * 2 System Board (ASIC)
+ * 3 RTC
+ * 4 Reserved
+ * 5 Memory
+ * 6 Halt Button
+ * 7 FPU/R4k timer
+ *
+ * We handle the IRQ according to _our_ priority (see setup.c),
+ * then we just return. If multiple IRQs are pending then we will
+ * just take another exception, big deal.
+ */
+ .align 5
+ NESTED(plat_irq_dispatch, PT_SIZE, ra)
+ .set noreorder
+
+ /*
+ * Get pending Interrupts
+ */
+ mfc0 t0,CP0_CAUSE # get pending interrupts
+ mfc0 t1,CP0_STATUS
+#if defined(CONFIG_32BIT) && defined(CONFIG_MIPS_FP_SUPPORT)
+ lw t2,cpu_fpu_mask
+#endif
+ andi t0,ST0_IM # CAUSE.CE may be non-zero!
+ and t0,t1 # isolate allowed ones
+
+ beqz t0,spurious
+
+#if defined(CONFIG_32BIT) && defined(CONFIG_MIPS_FP_SUPPORT)
+ and t2,t0
+ bnez t2,fpu # handle FPU immediately
+#endif
+
+ /*
+ * Find irq with highest priority
+ */
+ # open coded PTR_LA t1, cpu_mask_nr_tbl
+#if defined(CONFIG_32BIT) || defined(KBUILD_64BIT_SYM32)
+ # open coded la t1, cpu_mask_nr_tbl
+ lui t1, %hi(cpu_mask_nr_tbl)
+ addiu t1, %lo(cpu_mask_nr_tbl)
+#else
+#error GCC `-msym32' option required for 64-bit DECstation builds
+#endif
+1: lw t2,(t1)
+ nop
+ and t2,t0
+ beqz t2,1b
+ addu t1,2*PTRSIZE # delay slot
+
+ /*
+ * Do the low-level stuff
+ */
+ lw a0,(-PTRSIZE)(t1)
+ nop
+ bgez a0,handle_it # irq_nr >= 0?
+ # irq_nr < 0: it is an address
+ nop
+ jr a0
+ # a trick to save a branch:
+ lui t2,(KN03_IOASIC_BASE>>16)&0xffff
+ # upper part of IOASIC Address
+
+/*
+ * Handle "IRQ Controller" Interrupts
+ * Masked Interrupts are still visible and have to be masked "by hand".
+ */
+ FEXPORT(kn02_io_int) # 3max
+ lui t0,(KN02_CSR_BASE>>16)&0xffff
+ # get interrupt status and mask
+ lw t0,(t0)
+ nop
+ andi t1,t0,KN02_IRQ_ALL
+ b 1f
+ srl t0,16 # shift interrupt mask
+
+ FEXPORT(kn02xa_io_int) # 3min/maxine
+ lui t2,(KN02XA_IOASIC_BASE>>16)&0xffff
+ # upper part of IOASIC Address
+
+ FEXPORT(kn03_io_int) # 3max+ (t2 loaded earlier)
+ lw t0,IO_REG_SIR(t2) # get status: IOASIC sir
+ lw t1,IO_REG_SIMR(t2) # get mask: IOASIC simr
+ nop
+
+1: and t0,t1 # mask out allowed ones
+
+ beqz t0,spurious
+
+ /*
+ * Find irq with highest priority
+ */
+ # open coded PTR_LA t1,asic_mask_nr_tbl
+#if defined(CONFIG_32BIT) || defined(KBUILD_64BIT_SYM32)
+ # open coded la t1, asic_mask_nr_tbl
+ lui t1, %hi(asic_mask_nr_tbl)
+ addiu t1, %lo(asic_mask_nr_tbl)
+#else
+#error GCC `-msym32' option required for 64-bit DECstation builds
+#endif
+2: lw t2,(t1)
+ nop
+ and t2,t0
+ beq zero,t2,2b
+ addu t1,2*PTRSIZE # delay slot
+
+ /*
+ * Do the low-level stuff
+ */
+ lw a0,%lo(-PTRSIZE)(t1)
+ nop
+ bgez a0,handle_it # irq_nr >= 0?
+ # irq_nr < 0: it is an address
+ nop
+ jr a0
+ nop # delay slot
+
+/*
+ * Dispatch low-priority interrupts. We reconsider all status
+ * bits again, which looks like a lose, but it makes the code
+ * simple and O(log n), so it gets compensated.
+ */
+ FEXPORT(cpu_all_int) # HALT, timers, software junk
+ li a0,DEC_CPU_IRQ_BASE
+ srl t0,CAUSEB_IP
+ li t1,CAUSEF_IP>>CAUSEB_IP # mask
+ b 1f
+ li t2,4 # nr of bits / 2
+
+ FEXPORT(kn02_all_int) # impossible ?
+ li a0,KN02_IRQ_BASE
+ li t1,KN02_IRQ_ALL # mask
+ b 1f
+ li t2,4 # nr of bits / 2
+
+ FEXPORT(asic_all_int) # various I/O ASIC junk
+ li a0,IO_IRQ_BASE
+ li t1,IO_IRQ_ALL # mask
+ b 1f
+ li t2,8 # nr of bits / 2
+
+/*
+ * Dispatch DMA interrupts -- O(log n).
+ */
+ FEXPORT(asic_dma_int) # I/O ASIC DMA events
+ li a0,IO_IRQ_BASE+IO_INR_DMA
+ srl t0,IO_INR_DMA
+ li t1,IO_IRQ_DMA>>IO_INR_DMA # mask
+ li t2,8 # nr of bits / 2
+
+ /*
+ * Find irq with highest priority.
+ * Highest irq number takes precedence.
+ */
+1: srlv t3,t1,t2
+2: xor t1,t3
+ and t3,t0,t1
+ beqz t3,3f
+ nop
+ move t0,t3
+ addu a0,t2
+3: srl t2,1
+ bnez t2,2b
+ srlv t3,t1,t2
+
+handle_it:
+ j do_IRQ
+ nop
+
+#if defined(CONFIG_32BIT) && defined(CONFIG_MIPS_FP_SUPPORT)
+fpu:
+ lw t0,fpu_kstat_irq
+ nop
+ lw t1,(t0)
+ nop
+ addu t1,1
+ j handle_fpe_int
+ sw t1,(t0)
+#endif
+
+spurious:
+ j spurious_interrupt
+ nop
+ END(plat_irq_dispatch)
+
+/*
+ * Generic unimplemented interrupt routines -- cpu_mask_nr_tbl
+ * and asic_mask_nr_tbl are initialized to point all interrupts here.
+ * The tables are then filled in by machine-specific initialisation
+ * in dec_setup().
+ */
+ FEXPORT(dec_intr_unimplemented)
+ move a1,t0 # cheats way of printing an arg!
+ ASM_PANIC("Unimplemented cpu interrupt! CP0_CAUSE: 0x%08x");
+
+ FEXPORT(asic_intr_unimplemented)
+ move a1,t0 # cheats way of printing an arg!
+ ASM_PANIC("Unimplemented asic interrupt! ASIC ISR: 0x%08x");
diff --git a/arch/mips/dec/ioasic-irq.c b/arch/mips/dec/ioasic-irq.c
new file mode 100644
index 000000000..c690ef333
--- /dev/null
+++ b/arch/mips/dec/ioasic-irq.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * DEC I/O ASIC interrupts.
+ *
+ * Copyright (c) 2002, 2003, 2013 Maciej W. Rozycki
+ */
+
+#include <linux/init.h>
+#include <linux/irq.h>
+#include <linux/types.h>
+
+#include <asm/dec/ioasic.h>
+#include <asm/dec/ioasic_addrs.h>
+#include <asm/dec/ioasic_ints.h>
+
+static int ioasic_irq_base;
+
+static void unmask_ioasic_irq(struct irq_data *d)
+{
+ u32 simr;
+
+ simr = ioasic_read(IO_REG_SIMR);
+ simr |= (1 << (d->irq - ioasic_irq_base));
+ ioasic_write(IO_REG_SIMR, simr);
+}
+
+static void mask_ioasic_irq(struct irq_data *d)
+{
+ u32 simr;
+
+ simr = ioasic_read(IO_REG_SIMR);
+ simr &= ~(1 << (d->irq - ioasic_irq_base));
+ ioasic_write(IO_REG_SIMR, simr);
+}
+
+static void ack_ioasic_irq(struct irq_data *d)
+{
+ mask_ioasic_irq(d);
+ fast_iob();
+}
+
+static struct irq_chip ioasic_irq_type = {
+ .name = "IO-ASIC",
+ .irq_ack = ack_ioasic_irq,
+ .irq_mask = mask_ioasic_irq,
+ .irq_mask_ack = ack_ioasic_irq,
+ .irq_unmask = unmask_ioasic_irq,
+};
+
+static void clear_ioasic_dma_irq(struct irq_data *d)
+{
+ u32 sir;
+
+ sir = ~(1 << (d->irq - ioasic_irq_base));
+ ioasic_write(IO_REG_SIR, sir);
+ fast_iob();
+}
+
+static struct irq_chip ioasic_dma_irq_type = {
+ .name = "IO-ASIC-DMA",
+ .irq_ack = clear_ioasic_dma_irq,
+ .irq_mask = mask_ioasic_irq,
+ .irq_unmask = unmask_ioasic_irq,
+ .irq_eoi = clear_ioasic_dma_irq,
+};
+
+/*
+ * I/O ASIC implements two kinds of DMA interrupts, informational and
+ * error interrupts.
+ *
+ * The former do not stop DMA and should be cleared as soon as possible
+ * so that if they retrigger before the handler has completed, usually as
+ * a side effect of actions taken by the handler, then they are reissued.
+ * These use the `handle_edge_irq' handler that clears the request right
+ * away.
+ *
+ * The latter stop DMA and do not resume it until the interrupt has been
+ * cleared. This cannot be done until after a corrective action has been
+ * taken and this also means they will not retrigger. Therefore they use
+ * the `handle_fasteoi_irq' handler that only clears the request on the
+ * way out.
+ *
+ * This mask has `1' bits in the positions of informational interrupts.
+ */
+#define IO_IRQ_DMA_INFO \
+ (IO_IRQ_MASK(IO_INR_SCC0A_RXDMA) | \
+ IO_IRQ_MASK(IO_INR_SCC1A_RXDMA) | \
+ IO_IRQ_MASK(IO_INR_ISDN_TXDMA) | \
+ IO_IRQ_MASK(IO_INR_ISDN_RXDMA) | \
+ IO_IRQ_MASK(IO_INR_ASC_DMA))
+
+void __init init_ioasic_irqs(int base)
+{
+ int i;
+
+ /* Mask interrupts. */
+ ioasic_write(IO_REG_SIMR, 0);
+ fast_iob();
+
+ for (i = base; i < base + IO_INR_DMA; i++)
+ irq_set_chip_and_handler(i, &ioasic_irq_type,
+ handle_level_irq);
+ for (; i < base + IO_IRQ_LINES; i++)
+ irq_set_chip_and_handler(i, &ioasic_dma_irq_type,
+ 1 << (i - base) & IO_IRQ_DMA_INFO ?
+ handle_edge_irq : handle_fasteoi_irq);
+
+ ioasic_irq_base = base;
+}
diff --git a/arch/mips/dec/kn01-berr.c b/arch/mips/dec/kn01-berr.c
new file mode 100644
index 000000000..27addf7f3
--- /dev/null
+++ b/arch/mips/dec/kn01-berr.c
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Bus error event handling code for DECstation/DECsystem 3100
+ * and 2100 (KN01) systems equipped with parity error detection
+ * logic.
+ *
+ * Copyright (c) 2005, 2026 Maciej W. Rozycki
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#include <asm/inst.h>
+#include <asm/irq_regs.h>
+#include <asm/mipsregs.h>
+#include <asm/page.h>
+#include <asm/ptrace.h>
+#include <asm/traps.h>
+#include <linux/uaccess.h>
+
+#include <asm/dec/kn01.h>
+
+
+/* CP0 hazard avoidance. */
+#define BARRIER \
+ __asm__ __volatile__( \
+ ".set push\n\t" \
+ ".set noreorder\n\t" \
+ "nop\n\t" \
+ ".set pop\n\t")
+
+/*
+ * Bits 7:0 of the Control Register are write-only -- the
+ * corresponding bits of the Status Register have a different
+ * meaning. Hence we use a cache. It speeds up things a bit
+ * as well.
+ *
+ * There is no default value -- it has to be initialized.
+ */
+u16 cached_kn01_csr;
+static DEFINE_RAW_SPINLOCK(kn01_lock);
+
+
+static inline void dec_kn01_be_ack(void)
+{
+ volatile u16 *csr = (void *)CKSEG1ADDR(KN01_SLOT_BASE + KN01_CSR);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&kn01_lock, flags);
+
+ *csr = cached_kn01_csr | KN01_CSR_MEMERR; /* Clear bus IRQ. */
+ iob();
+
+ raw_spin_unlock_irqrestore(&kn01_lock, flags);
+}
+
+static int dec_kn01_be_backend(struct pt_regs *regs, int is_fixup, int invoker)
+{
+ volatile u32 *kn01_erraddr = (void *)CKSEG1ADDR(KN01_SLOT_BASE +
+ KN01_ERRADDR);
+
+ static const char excstr[] = "exception";
+ static const char intstr[] = "interrupt";
+ static const char cpustr[] = "CPU";
+ static const char mreadstr[] = "memory read";
+ static const char readstr[] = "read";
+ static const char writestr[] = "write";
+ static const char timestr[] = "timeout";
+ static const char paritystr[] = "parity error";
+
+ int data = regs->cp0_cause & 4;
+ unsigned int __user *pc = (unsigned int __user *)regs->cp0_epc +
+ ((regs->cp0_cause & CAUSEF_BD) != 0);
+ union mips_instruction insn;
+ unsigned long entrylo, offset;
+ long asid, entryhi, vaddr;
+
+ const char *kind, *agent, *cycle, *event;
+ unsigned long address;
+
+ u32 erraddr = *kn01_erraddr;
+ int action = MIPS_BE_FATAL;
+
+ /* Ack ASAP, so that any subsequent errors get caught. */
+ dec_kn01_be_ack();
+
+ kind = invoker ? intstr : excstr;
+
+ agent = cpustr;
+
+ if (invoker)
+ address = erraddr;
+ else {
+ /* Bloody hardware doesn't record the address for reads... */
+ if (data) {
+ /* This never faults. */
+ __get_user(insn.word, pc);
+ vaddr = regs->regs[insn.i_format.rs] +
+ insn.i_format.simmediate;
+ } else
+ vaddr = (long)pc;
+ if (KSEGX(vaddr) == CKSEG0 || KSEGX(vaddr) == CKSEG1)
+ address = CPHYSADDR(vaddr);
+ else {
+ /* Peek at what physical address the CPU used. */
+ asid = read_c0_entryhi();
+ entryhi = asid & (PAGE_SIZE - 1);
+ entryhi |= vaddr & ~(PAGE_SIZE - 1);
+ write_c0_entryhi(entryhi);
+ BARRIER;
+ tlb_probe();
+ /* No need to check for presence. */
+ tlb_read();
+ entrylo = read_c0_entrylo0();
+ write_c0_entryhi(asid);
+ offset = vaddr & (PAGE_SIZE - 1);
+ address = (entrylo & ~(PAGE_SIZE - 1)) | offset;
+ }
+ }
+
+ /* Treat low 256MB as memory, high -- as I/O. */
+ if (address < 0x10000000) {
+ cycle = mreadstr;
+ event = paritystr;
+ } else {
+ cycle = invoker ? writestr : readstr;
+ event = timestr;
+ }
+
+ if (is_fixup)
+ action = MIPS_BE_FIXUP;
+
+ if (action != MIPS_BE_FIXUP)
+ pr_alert_ratelimited("Bus error %s: %s %s %s at %#010lx\n",
+ kind, agent, cycle, event, address);
+
+ return action;
+}
+
+int dec_kn01_be_handler(struct pt_regs *regs, int is_fixup)
+{
+ return dec_kn01_be_backend(regs, is_fixup, 0);
+}
+
+irqreturn_t dec_kn01_be_interrupt(int irq, void *dev_id)
+{
+ volatile u16 *csr = (void *)CKSEG1ADDR(KN01_SLOT_BASE + KN01_CSR);
+ struct pt_regs *regs = get_irq_regs();
+ int action;
+
+ if (!(*csr & KN01_CSR_MEMERR))
+ return IRQ_NONE; /* Must have been video. */
+
+ action = dec_kn01_be_backend(regs, 0, 1);
+
+ if (action == MIPS_BE_DISCARD)
+ return IRQ_HANDLED;
+
+ /*
+ * FIXME: Find the affected processes and kill them, otherwise
+ * we must die.
+ *
+ * The interrupt is asynchronously delivered thus EPC and RA
+ * may be irrelevant, but are printed for a reference.
+ */
+ printk(KERN_ALERT "Fatal bus interrupt, epc == %08lx, ra == %08lx\n",
+ regs->cp0_epc, regs->regs[31]);
+ die("Unrecoverable bus error", regs);
+}
+
+
+void __init dec_kn01_be_init(void)
+{
+ volatile u16 *csr = (void *)CKSEG1ADDR(KN01_SLOT_BASE + KN01_CSR);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&kn01_lock, flags);
+
+ /* Preset write-only bits of the Control Register cache. */
+ cached_kn01_csr = *csr;
+ cached_kn01_csr &= KN01_CSR_STATUS | KN01_CSR_PARDIS | KN01_CSR_TXDIS;
+ cached_kn01_csr |= KN01_CSR_LEDS;
+
+ /* Enable parity error detection. */
+ cached_kn01_csr &= ~KN01_CSR_PARDIS;
+ *csr = cached_kn01_csr;
+ iob();
+
+ raw_spin_unlock_irqrestore(&kn01_lock, flags);
+
+ /* Clear any leftover errors from the firmware. */
+ dec_kn01_be_ack();
+}
diff --git a/arch/mips/dec/kn02-irq.c b/arch/mips/dec/kn02-irq.c
new file mode 100644
index 000000000..7e18de574
--- /dev/null
+++ b/arch/mips/dec/kn02-irq.c
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * DECstation 5000/200 (KN02) Control and Status Register
+ * interrupts.
+ *
+ * Copyright (c) 2002, 2003, 2005 Maciej W. Rozycki
+ */
+
+#include <linux/init.h>
+#include <linux/irq.h>
+#include <linux/types.h>
+
+#include <asm/dec/kn02.h>
+
+
+/*
+ * Bits 7:0 of the Control Register are write-only -- the
+ * corresponding bits of the Status Register have a different
+ * meaning. Hence we use a cache. It speeds up things a bit
+ * as well.
+ *
+ * There is no default value -- it has to be initialized.
+ */
+u32 cached_kn02_csr;
+
+static int kn02_irq_base;
+
+static void unmask_kn02_irq(struct irq_data *d)
+{
+ volatile u32 *csr = (volatile u32 *)CKSEG1ADDR(KN02_SLOT_BASE +
+ KN02_CSR);
+
+ cached_kn02_csr |= (1 << (d->irq - kn02_irq_base + 16));
+ *csr = cached_kn02_csr;
+}
+
+static void mask_kn02_irq(struct irq_data *d)
+{
+ volatile u32 *csr = (volatile u32 *)CKSEG1ADDR(KN02_SLOT_BASE +
+ KN02_CSR);
+
+ cached_kn02_csr &= ~(1 << (d->irq - kn02_irq_base + 16));
+ *csr = cached_kn02_csr;
+}
+
+static void ack_kn02_irq(struct irq_data *d)
+{
+ mask_kn02_irq(d);
+ iob();
+}
+
+static struct irq_chip kn02_irq_type = {
+ .name = "KN02-CSR",
+ .irq_ack = ack_kn02_irq,
+ .irq_mask = mask_kn02_irq,
+ .irq_mask_ack = ack_kn02_irq,
+ .irq_unmask = unmask_kn02_irq,
+};
+
+void __init init_kn02_irqs(int base)
+{
+ volatile u32 *csr = (volatile u32 *)CKSEG1ADDR(KN02_SLOT_BASE +
+ KN02_CSR);
+ int i;
+
+ /* Mask interrupts. */
+ cached_kn02_csr &= ~KN02_CSR_IOINTEN;
+ *csr = cached_kn02_csr;
+ iob();
+
+ for (i = base; i < base + KN02_IRQ_LINES; i++)
+ irq_set_chip_and_handler(i, &kn02_irq_type, handle_level_irq);
+
+ kn02_irq_base = base;
+}
diff --git a/arch/mips/dec/kn02xa-berr.c b/arch/mips/dec/kn02xa-berr.c
new file mode 100644
index 000000000..6e494db1e
--- /dev/null
+++ b/arch/mips/dec/kn02xa-berr.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Bus error event handling code for 5000-series systems equipped
+ * with parity error detection logic, i.e. DECstation/DECsystem
+ * 5000/120, /125, /133 (KN02-BA), 5000/150 (KN04-BA) and Personal
+ * DECstation/DECsystem 5000/20, /25, /33 (KN02-CA), 5000/50
+ * (KN04-CA) systems.
+ *
+ * Copyright (c) 2005, 2026 Maciej W. Rozycki
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/ratelimit.h>
+#include <linux/types.h>
+
+#include <asm/addrspace.h>
+#include <asm/cpu-type.h>
+#include <asm/irq_regs.h>
+#include <asm/ptrace.h>
+#include <asm/traps.h>
+
+#include <asm/dec/kn02ca.h>
+#include <asm/dec/kn02xa.h>
+#include <asm/dec/kn05.h>
+
+static inline void dec_kn02xa_be_ack(void)
+{
+ volatile u32 *mer = (void *)CKSEG1ADDR(KN02XA_MER);
+ volatile u32 *mem_intr = (void *)CKSEG1ADDR(KN02XA_MEM_INTR);
+
+ *mer = KN02CA_MER_INTR; /* Clear errors; keep the ARC IRQ. */
+ *mem_intr = 0; /* Any write clears the bus IRQ. */
+ iob();
+}
+
+static int dec_kn02xa_be_backend(struct pt_regs *regs, int is_fixup,
+ int invoker)
+{
+ volatile u32 *kn02xa_mer = (void *)CKSEG1ADDR(KN02XA_MER);
+ volatile u32 *kn02xa_ear = (void *)CKSEG1ADDR(KN02XA_EAR);
+
+ static const char excstr[] = "exception";
+ static const char intstr[] = "interrupt";
+ static const char cpustr[] = "CPU";
+ static const char mreadstr[] = "memory read";
+ static const char readstr[] = "read";
+ static const char writestr[] = "write";
+ static const char timestr[] = "timeout";
+ static const char paritystr[] = "parity error";
+ static const char lanestat[][4] = { " OK", "BAD" };
+
+ static DEFINE_RATELIMIT_STATE(rs,
+ DEFAULT_RATELIMIT_INTERVAL,
+ DEFAULT_RATELIMIT_BURST);
+
+ const char *kind, *agent, *cycle, *event;
+ unsigned long address;
+
+ u32 mer = *kn02xa_mer;
+ u32 ear = *kn02xa_ear;
+ int action = MIPS_BE_FATAL;
+
+ /* Ack ASAP, so that any subsequent errors get caught. */
+ dec_kn02xa_be_ack();
+
+ kind = invoker ? intstr : excstr;
+
+ /* No DMA errors? */
+ agent = cpustr;
+
+ address = ear & KN02XA_EAR_ADDRESS;
+
+ /* Low 256MB is decoded as memory, high -- as TC. */
+ if (address < 0x10000000) {
+ cycle = mreadstr;
+ event = paritystr;
+ } else {
+ cycle = invoker ? writestr : readstr;
+ event = timestr;
+ }
+
+ if (is_fixup)
+ action = MIPS_BE_FIXUP;
+
+ if (action != MIPS_BE_FIXUP && __ratelimit(&rs)) {
+ printk(KERN_ALERT "Bus error %s: %s %s %s at %#010lx\n",
+ kind, agent, cycle, event, address);
+
+ if (address < 0x10000000)
+ printk(KERN_ALERT " Byte lane status %#3x -- "
+ "#3: %s, #2: %s, #1: %s, #0: %s\n",
+ (mer & KN02XA_MER_BYTERR) >> 8,
+ lanestat[(mer & KN02XA_MER_BYTERR_3) != 0],
+ lanestat[(mer & KN02XA_MER_BYTERR_2) != 0],
+ lanestat[(mer & KN02XA_MER_BYTERR_1) != 0],
+ lanestat[(mer & KN02XA_MER_BYTERR_0) != 0]);
+ }
+
+ return action;
+}
+
+int dec_kn02xa_be_handler(struct pt_regs *regs, int is_fixup)
+{
+ return dec_kn02xa_be_backend(regs, is_fixup, 0);
+}
+
+irqreturn_t dec_kn02xa_be_interrupt(int irq, void *dev_id)
+{
+ struct pt_regs *regs = get_irq_regs();
+ int action = dec_kn02xa_be_backend(regs, 0, 1);
+
+ if (action == MIPS_BE_DISCARD)
+ return IRQ_HANDLED;
+
+ /*
+ * FIXME: Find the affected processes and kill them, otherwise
+ * we must die.
+ *
+ * The interrupt is asynchronously delivered thus EPC and RA
+ * may be irrelevant, but are printed for a reference.
+ */
+ printk(KERN_ALERT "Fatal bus interrupt, epc == %08lx, ra == %08lx\n",
+ regs->cp0_epc, regs->regs[31]);
+ die("Unrecoverable bus error", regs);
+}
+
+
+void __init dec_kn02xa_be_init(void)
+{
+ volatile u32 *mbcs = (void *)CKSEG1ADDR(KN4K_SLOT_BASE + KN4K_MB_CSR);
+
+ /* For KN04 we need to make sure EE (?) is enabled in the MB. */
+ if (current_cpu_type() == CPU_R4000SC)
+ *mbcs |= KN4K_MB_CSR_EE;
+ fast_iob();
+
+ /* Clear any leftover errors from the firmware. */
+ dec_kn02xa_be_ack();
+}
diff --git a/arch/mips/dec/platform.c b/arch/mips/dec/platform.c
new file mode 100644
index 000000000..a005246a0
--- /dev/null
+++ b/arch/mips/dec/platform.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * DEC platform devices.
+ *
+ * Copyright (c) 2014 Maciej W. Rozycki
+ */
+
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/mc146818rtc.h>
+#include <linux/platform_device.h>
+
+#include <asm/bootinfo.h>
+
+#include <asm/dec/interrupts.h>
+#include <asm/dec/ioasic_addrs.h>
+#include <asm/dec/kn01.h>
+#include <asm/dec/kn02.h>
+#include <asm/dec/system.h>
+
+static struct resource dec_rtc_resources[] = {
+ {
+ .name = "rtc",
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+static struct cmos_rtc_board_info dec_rtc_info = {
+ .flags = CMOS_RTC_FLAGS_NOFREQ,
+ .address_space = 64,
+};
+
+static struct platform_device dec_rtc_device = {
+ .name = "rtc_cmos",
+ .id = PLATFORM_DEVID_NONE,
+ .dev.platform_data = &dec_rtc_info,
+ .resource = dec_rtc_resources,
+ .num_resources = ARRAY_SIZE(dec_rtc_resources),
+};
+
+static struct platform_device *dec_rtc_devices[] __initdata = {
+ &dec_rtc_device,
+};
+
+static struct resource dec_dz_resources[] = {
+ { .name = "dz", .flags = IORESOURCE_MEM, },
+ { .name = "dz", .flags = IORESOURCE_IRQ, },
+};
+
+static struct platform_device dec_dz_device = {
+ .name = "dz",
+ .id = PLATFORM_DEVID_NONE,
+ .resource = dec_dz_resources,
+ .num_resources = ARRAY_SIZE(dec_dz_resources),
+};
+
+static struct platform_device *dec_dz_devices[] __initdata = {
+ &dec_dz_device,
+};
+
+static struct resource dec_zs_resources[][2] = {
+ {
+ { .name = "scc0", .flags = IORESOURCE_MEM, },
+ { .name = "scc0", .flags = IORESOURCE_IRQ, },
+ },
+ {
+ { .name = "scc1", .flags = IORESOURCE_MEM, },
+ { .name = "scc1", .flags = IORESOURCE_IRQ, },
+ },
+};
+
+static struct platform_device dec_zs_device[] = {
+ {
+ .name = "zs",
+ .id = 0,
+ .resource = dec_zs_resources[0],
+ .num_resources = ARRAY_SIZE(dec_zs_resources[0]),
+ },
+ {
+ .name = "zs",
+ .id = 1,
+ .resource = dec_zs_resources[1],
+ .num_resources = ARRAY_SIZE(dec_zs_resources[1]),
+ },
+};
+
+static int __init dec_add_devices(void)
+{
+ struct platform_device *dec_zs_devices[ARRAY_SIZE(dec_zs_device)];
+ int ret1, ret2, ret3;
+ int num_dz, num_zs;
+ int irq, i;
+
+ dec_rtc_resources[0].start = RTC_PORT(0);
+ dec_rtc_resources[0].end = RTC_PORT(0) + dec_kn_slot_size - 1;
+
+ i = 0;
+ irq = dec_interrupt[DEC_IRQ_DZ11];
+ if (IS_ENABLED(CONFIG_32BIT) && irq >= 0) {
+ resource_size_t base;
+
+ switch (mips_machtype) {
+ case MACH_DS23100:
+ case MACH_DS5100:
+ base = dec_kn_slot_base + KN01_DZ11;
+ break;
+ default:
+ base = dec_kn_slot_base + KN02_DZ11;
+ break;
+ }
+ dec_dz_device.resource[0].start = base;
+ dec_dz_device.resource[0].end = base + dec_kn_slot_size - 1;
+ dec_dz_device.resource[1].start = irq;
+ dec_dz_device.resource[1].end = irq;
+ i++;
+ }
+ num_dz = i;
+
+ i = 0;
+ irq = dec_interrupt[DEC_IRQ_SCC0];
+ if (irq >= 0) {
+ resource_size_t base = dec_kn_slot_base + IOASIC_SCC0;
+
+ dec_zs_device[i].resource[0].start = base;
+ dec_zs_device[i].resource[0].end = base + dec_kn_slot_size - 1;
+ dec_zs_device[i].resource[1].start = irq;
+ dec_zs_device[i].resource[1].end = irq;
+ dec_zs_devices[i] = &dec_zs_device[i];
+ i++;
+ }
+ irq = dec_interrupt[DEC_IRQ_SCC1];
+ if (irq >= 0) {
+ resource_size_t base = dec_kn_slot_base + IOASIC_SCC1;
+
+ dec_zs_device[i].resource[0].start = base;
+ dec_zs_device[i].resource[0].end = base + dec_kn_slot_size - 1;
+ dec_zs_device[i].resource[1].start = irq;
+ dec_zs_device[i].resource[1].end = irq;
+ dec_zs_devices[i] = &dec_zs_device[i];
+ i++;
+ }
+ num_zs = i;
+
+ ret1 = platform_add_devices(dec_rtc_devices, 1);
+ ret2 = IS_ENABLED(CONFIG_32BIT) ?
+ platform_add_devices(dec_dz_devices, num_dz) : 0;
+ ret3 = platform_add_devices(dec_zs_devices, num_zs);
+ return ret1 ? ret1 : ret2 ? ret2 : ret3;
+}
+
+device_initcall(dec_add_devices);
diff --git a/arch/mips/dec/prom/Makefile b/arch/mips/dec/prom/Makefile
new file mode 100644
index 000000000..2bad87551
--- /dev/null
+++ b/arch/mips/dec/prom/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for the DECstation prom monitor library routines
+# under Linux.
+#
+
+lib-y += init.o memory.o cmdline.o identify.o console.o
+
+lib-$(CONFIG_CPU_R3000) += locore.o
diff --git a/arch/mips/dec/prom/cmdline.c b/arch/mips/dec/prom/cmdline.c
new file mode 100644
index 000000000..3ed63280a
--- /dev/null
+++ b/arch/mips/dec/prom/cmdline.c
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * cmdline.c: read the command line passed to us by the PROM.
+ *
+ * Copyright (C) 1998 Harald Koerfgen
+ * Copyright (C) 2002, 2004 Maciej W. Rozycki
+ */
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+#include <asm/bootinfo.h>
+#include <asm/dec/prom.h>
+
+#undef PROM_DEBUG
+
+void __init prom_init_cmdline(s32 argc, s32 *argv, u32 magic)
+{
+ char *arg;
+ int start_arg, i;
+
+ /*
+ * collect args and prepare cmd_line
+ */
+ if (!prom_is_rex(magic))
+ start_arg = 1;
+ else
+ start_arg = 2;
+ for (i = start_arg; i < argc; i++) {
+ arg = (void *)(long)(argv[i]);
+ strcat(arcs_cmdline, arg);
+ if (i < (argc - 1))
+ strcat(arcs_cmdline, " ");
+ }
+
+#ifdef PROM_DEBUG
+ printk("arcs_cmdline: %s\n", &(arcs_cmdline[0]));
+#endif
+}
diff --git a/arch/mips/dec/prom/console.c b/arch/mips/dec/prom/console.c
new file mode 100644
index 000000000..b4f0dba3f
--- /dev/null
+++ b/arch/mips/dec/prom/console.c
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * DECstation PROM-based early console support.
+ *
+ * Copyright (C) 2004, 2007, 2026 Maciej W. Rozycki
+ */
+#include <linux/bug.h>
+#include <linux/console.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+
+#include <asm/dec/prom.h>
+
+static void __init prom_console_write(struct console *con, const char *s,
+ unsigned int c)
+{
+ static char buf[81] __initdata = { 0 };
+ unsigned int chunk = sizeof(buf) - 1;
+
+ BUG_ON((long)buf != (int)(long)buf);
+
+ while (c > 0) {
+ if (chunk > c)
+ chunk = c;
+ memcpy(buf, s, chunk);
+ buf[chunk] = '\0';
+ prom_printf("%s", buf);
+ s += chunk;
+ c -= chunk;
+ }
+}
+
+static struct console promcons __initdata = {
+ .name = "prom",
+ .write = prom_console_write,
+ .flags = CON_BOOT | CON_PRINTBUFFER,
+ .index = -1,
+};
+
+void __init register_prom_console(void)
+{
+ register_console(&promcons);
+}
diff --git a/arch/mips/dec/prom/dectypes.h b/arch/mips/dec/prom/dectypes.h
new file mode 100644
index 000000000..9fcbcc7cd
--- /dev/null
+++ b/arch/mips/dec/prom/dectypes.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef DECTYPES
+#define DECTYPES
+
+#define DS2100_3100 1 /* DS2100/3100 Pmax */
+#define DS5000_200 2 /* DS5000/200 3max */
+#define DS5000_1XX 3 /* DS5000/1xx kmin */
+#define DS5000_2X0 4 /* DS5000/2x0 3max+ */
+#define DS5800 5 /* DS5800 Isis */
+#define DS5400 6 /* DS5400 MIPSfair */
+#define DS5000_XX 7 /* DS5000/xx maxine */
+#define DS5500 11 /* DS5500 MIPSfair-2 */
+#define DS5100 12 /* DS5100 MIPSmate */
+
+#endif
diff --git a/arch/mips/dec/prom/identify.c b/arch/mips/dec/prom/identify.c
new file mode 100644
index 000000000..80cd14cd1
--- /dev/null
+++ b/arch/mips/dec/prom/identify.c
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * identify.c: machine identification code.
+ *
+ * Copyright (C) 1998 Harald Koerfgen and Paul M. Antoine
+ * Copyright (C) 2002, 2003, 2004, 2005 Maciej W. Rozycki
+ */
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mc146818rtc.h>
+#include <linux/export.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+#include <asm/bootinfo.h>
+
+#include <asm/dec/ioasic.h>
+#include <asm/dec/ioasic_addrs.h>
+#include <asm/dec/kn01.h>
+#include <asm/dec/kn02.h>
+#include <asm/dec/kn02ba.h>
+#include <asm/dec/kn02ca.h>
+#include <asm/dec/kn03.h>
+#include <asm/dec/kn230.h>
+#include <asm/dec/prom.h>
+#include <asm/dec/system.h>
+
+#include "dectypes.h"
+
+static const char *dec_system_strings[] = {
+ [MACH_DSUNKNOWN] "unknown DECstation",
+ [MACH_DS23100] "DECstation 2100/3100",
+ [MACH_DS5100] "DECsystem 5100",
+ [MACH_DS5000_200] "DECstation 5000/200",
+ [MACH_DS5000_1XX] "DECstation 5000/1xx",
+ [MACH_DS5000_XX] "Personal DECstation 5000/xx",
+ [MACH_DS5000_2X0] "DECstation 5000/2x0",
+ [MACH_DS5400] "DECsystem 5400",
+ [MACH_DS5500] "DECsystem 5500",
+ [MACH_DS5800] "DECsystem 5800",
+ [MACH_DS5900] "DECsystem 5900",
+};
+
+const char *get_system_type(void)
+{
+#define STR_BUF_LEN 64
+ static char system[STR_BUF_LEN];
+ static int called = 0;
+
+ if (called == 0) {
+ called = 1;
+ snprintf(system, STR_BUF_LEN, "Digital %s",
+ dec_system_strings[mips_machtype]);
+ }
+
+ return system;
+}
+
+
+/*
+ * Setup essential system-specific memory addresses. We need them
+ * early. Semantically the functions belong to prom/init.c, but they
+ * are compact enough we want them inlined. --macro
+ */
+volatile u8 *dec_rtc_base;
+
+EXPORT_SYMBOL(dec_rtc_base);
+
+static inline void prom_init_kn01(void)
+{
+ dec_kn_slot_base = KN01_SLOT_BASE;
+ dec_kn_slot_size = KN01_SLOT_SIZE;
+
+ dec_rtc_base = (void *)CKSEG1ADDR(dec_kn_slot_base + KN01_RTC);
+}
+
+static inline void prom_init_kn230(void)
+{
+ dec_kn_slot_base = KN01_SLOT_BASE;
+ dec_kn_slot_size = KN01_SLOT_SIZE;
+
+ dec_rtc_base = (void *)CKSEG1ADDR(dec_kn_slot_base + KN01_RTC);
+}
+
+static inline void prom_init_kn02(void)
+{
+ dec_kn_slot_base = KN02_SLOT_BASE;
+ dec_kn_slot_size = KN02_SLOT_SIZE;
+ dec_tc_bus = 1;
+
+ dec_rtc_base = (void *)CKSEG1ADDR(dec_kn_slot_base + KN02_RTC);
+}
+
+static inline void prom_init_kn02xa(void)
+{
+ dec_kn_slot_base = KN02XA_SLOT_BASE;
+ dec_kn_slot_size = IOASIC_SLOT_SIZE;
+ dec_tc_bus = 1;
+
+ ioasic_base = (void *)CKSEG1ADDR(dec_kn_slot_base + IOASIC_IOCTL);
+ dec_rtc_base = (void *)CKSEG1ADDR(dec_kn_slot_base + IOASIC_TOY);
+}
+
+static inline void prom_init_kn03(void)
+{
+ dec_kn_slot_base = KN03_SLOT_BASE;
+ dec_kn_slot_size = IOASIC_SLOT_SIZE;
+ dec_tc_bus = 1;
+
+ ioasic_base = (void *)CKSEG1ADDR(dec_kn_slot_base + IOASIC_IOCTL);
+ dec_rtc_base = (void *)CKSEG1ADDR(dec_kn_slot_base + IOASIC_TOY);
+}
+
+
+void __init prom_identify_arch(u32 magic)
+{
+ unsigned char dec_cpunum, dec_firmrev, dec_etc, dec_systype;
+ u32 dec_sysid;
+
+ if (!prom_is_rex(magic)) {
+ dec_sysid = simple_strtoul(prom_getenv("systype"),
+ (char **)0, 0);
+ } else {
+ dec_sysid = rex_getsysid();
+ if (dec_sysid == 0) {
+ printk("Zero sysid returned from PROM! "
+ "Assuming a PMAX-like machine.\n");
+ dec_sysid = 1;
+ }
+ }
+
+ dec_cpunum = (dec_sysid & 0xff000000) >> 24;
+ dec_systype = (dec_sysid & 0xff0000) >> 16;
+ dec_firmrev = (dec_sysid & 0xff00) >> 8;
+ dec_etc = dec_sysid & 0xff;
+
+ /*
+ * FIXME: This may not be an exhaustive list of DECStations/Servers!
+ * Put all model-specific initialisation calls here.
+ */
+ switch (dec_systype) {
+ case DS2100_3100:
+ mips_machtype = MACH_DS23100;
+ prom_init_kn01();
+ break;
+ case DS5100: /* DS5100 MIPSMATE */
+ mips_machtype = MACH_DS5100;
+ prom_init_kn230();
+ break;
+ case DS5000_200: /* DS5000 3max */
+ mips_machtype = MACH_DS5000_200;
+ prom_init_kn02();
+ break;
+ case DS5000_1XX: /* DS5000/100 3min */
+ mips_machtype = MACH_DS5000_1XX;
+ prom_init_kn02xa();
+ break;
+ case DS5000_2X0: /* DS5000/240 3max+ or DS5900 bigmax */
+ mips_machtype = MACH_DS5000_2X0;
+ prom_init_kn03();
+ if (!(ioasic_read(IO_REG_SIR) & KN03_IO_INR_3MAXP))
+ mips_machtype = MACH_DS5900;
+ break;
+ case DS5000_XX: /* Personal DS5000/xx maxine */
+ mips_machtype = MACH_DS5000_XX;
+ prom_init_kn02xa();
+ break;
+ case DS5800: /* DS5800 Isis */
+ mips_machtype = MACH_DS5800;
+ break;
+ case DS5400: /* DS5400 MIPSfair */
+ mips_machtype = MACH_DS5400;
+ break;
+ case DS5500: /* DS5500 MIPSfair-2 */
+ mips_machtype = MACH_DS5500;
+ break;
+ default:
+ mips_machtype = MACH_DSUNKNOWN;
+ break;
+ }
+
+ if (mips_machtype == MACH_DSUNKNOWN)
+ printk("This is an %s, id is %x\n",
+ dec_system_strings[mips_machtype], dec_systype);
+ else
+ printk("This is a %s\n", dec_system_strings[mips_machtype]);
+}
diff --git a/arch/mips/dec/prom/init.c b/arch/mips/dec/prom/init.c
new file mode 100644
index 000000000..e46c7acce
--- /dev/null
+++ b/arch/mips/dec/prom/init.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * init.c: PROM library initialisation code.
+ *
+ * Copyright (C) 1998 Harald Koerfgen
+ * Copyright (C) 2002, 2004, 2026 Maciej W. Rozycki
+ */
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/linkage.h>
+#include <linux/smp.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+#include <asm/bootinfo.h>
+#include <asm/cpu.h>
+#include <asm/cpu-type.h>
+#include <asm/processor.h>
+
+#include <asm/dec/prom.h>
+
+
+#ifdef CONFIG_64BIT
+unsigned long o32_stk[O32_STK_SIZE] __initdata = { 0 };
+#endif
+
+int (*__rex_bootinit)(void);
+int (*__rex_bootread)(void);
+int (*__rex_getbitmap)(memmap *);
+unsigned long *(*__rex_slot_address)(int);
+void *(*__rex_gettcinfo)(void);
+int (*__rex_getsysid)(void);
+void (*__rex_clear_cache)(void);
+
+int (*__prom_getchar)(void);
+char *(*__prom_getenv)(char *);
+int (*__prom_printf)(char *, ...);
+
+int (*__pmax_open)(char*, int);
+int (*__pmax_lseek)(int, long, int);
+int (*__pmax_read)(int, void *, int);
+int (*__pmax_close)(int);
+
+
+/*
+ * Detect which PROM the DECSTATION has, and set the callback vectors
+ * appropriately.
+ */
+static void __init which_prom(s32 magic, s32 *prom_vec)
+{
+ /*
+ * No sign of the REX PROM's magic number means we assume a non-REX
+ * machine (i.e. we're on a DS2100/3100, DS5100 or DS5000/2xx)
+ */
+ if (prom_is_rex(magic)) {
+ /*
+ * Set up prom abstraction structure with REX entry points.
+ */
+ __rex_bootinit =
+ (void *)(long)*(prom_vec + REX_PROM_BOOTINIT);
+ __rex_bootread =
+ (void *)(long)*(prom_vec + REX_PROM_BOOTREAD);
+ __rex_getbitmap =
+ (void *)(long)*(prom_vec + REX_PROM_GETBITMAP);
+ __prom_getchar =
+ (void *)(long)*(prom_vec + REX_PROM_GETCHAR);
+ __prom_getenv =
+ (void *)(long)*(prom_vec + REX_PROM_GETENV);
+ __rex_getsysid =
+ (void *)(long)*(prom_vec + REX_PROM_GETSYSID);
+ __rex_gettcinfo =
+ (void *)(long)*(prom_vec + REX_PROM_GETTCINFO);
+ __prom_printf =
+ (void *)(long)*(prom_vec + REX_PROM_PRINTF);
+ __rex_slot_address =
+ (void *)(long)*(prom_vec + REX_PROM_SLOTADDR);
+ __rex_clear_cache =
+ (void *)(long)*(prom_vec + REX_PROM_CLEARCACHE);
+ } else {
+ /*
+ * Set up prom abstraction structure with non-REX entry points.
+ */
+ __prom_getchar = (void *)PMAX_PROM_GETCHAR;
+ __prom_getenv = (void *)PMAX_PROM_GETENV;
+ __prom_printf = (void *)PMAX_PROM_PRINTF;
+ __pmax_open = (void *)PMAX_PROM_OPEN;
+ __pmax_lseek = (void *)PMAX_PROM_LSEEK;
+ __pmax_read = (void *)PMAX_PROM_READ;
+ __pmax_close = (void *)PMAX_PROM_CLOSE;
+ }
+}
+
+void __init prom_init(void)
+{
+ extern void dec_machine_halt(void);
+ static const char cpu_msg[] __initconst =
+ "Sorry, this kernel is compiled for a wrong CPU type!\n";
+ s32 argc = fw_arg0;
+ s32 *argv = (void *)fw_arg1;
+ u32 magic = fw_arg2;
+ s32 *prom_vec = (void *)fw_arg3;
+
+ /*
+ * Determine which PROM we have
+ * (and therefore which machine we're on!)
+ */
+ which_prom(magic, prom_vec);
+
+ if (prom_is_rex(magic))
+ rex_clear_cache();
+
+ /* Register the early console. */
+ register_prom_console();
+
+ /* Were we compiled with the right CPU option? */
+#if defined(CONFIG_CPU_R3000)
+ if ((current_cpu_type() == CPU_R4000SC) ||
+ (current_cpu_type() == CPU_R4400SC)) {
+ static const char r4k_msg[] __initconst =
+ "Please recompile with \"CONFIG_CPU_R4X00 = y\".\n";
+ printk(cpu_msg);
+ printk(r4k_msg);
+ dec_machine_halt();
+ }
+#endif
+
+#if defined(CONFIG_CPU_R4X00)
+ if ((current_cpu_type() == CPU_R3000) ||
+ (current_cpu_type() == CPU_R3000A)) {
+ static const char r3k_msg[] __initconst =
+ "Please recompile with \"CONFIG_CPU_R3000 = y\".\n";
+ printk(cpu_msg);
+ printk(r3k_msg);
+ dec_machine_halt();
+ }
+#endif
+
+ prom_meminit(magic);
+ prom_identify_arch(magic);
+ prom_init_cmdline(argc, argv, magic);
+}
diff --git a/arch/mips/dec/prom/locore.S b/arch/mips/dec/prom/locore.S
new file mode 100644
index 000000000..0eb8fab62
--- /dev/null
+++ b/arch/mips/dec/prom/locore.S
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * locore.S
+ */
+#include <asm/asm.h>
+#include <asm/regdef.h>
+#include <asm/mipsregs.h>
+
+ .text
+
+/*
+ * Simple general exception handling routine. This one is used for the
+ * Memory sizing routine for pmax machines. HK
+ */
+
+NESTED(genexcept_early, 0, sp)
+ .set noat
+ .set noreorder
+
+ mfc0 k0, CP0_STATUS
+ la k1, mem_err
+
+ sw k0, 0(k1)
+
+ mfc0 k0, CP0_EPC
+ nop
+ addiu k0, 4 # skip the causing instruction
+ jr k0
+ rfe
+END(genexcept_early)
diff --git a/arch/mips/dec/prom/memory.c b/arch/mips/dec/prom/memory.c
new file mode 100644
index 000000000..44490c30d
--- /dev/null
+++ b/arch/mips/dec/prom/memory.c
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * memory.c: memory initialisation code.
+ *
+ * Copyright (C) 1998 Harald Koerfgen, Frieder Streffer and Paul M. Antoine
+ * Copyright (C) 2000, 2002 Maciej W. Rozycki
+ */
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/memblock.h>
+#include <linux/types.h>
+
+#include <asm/addrspace.h>
+#include <asm/dec/machtype.h>
+#include <asm/dec/prom.h>
+#include <asm/page.h>
+#include <asm/sections.h>
+
+
+volatile unsigned long mem_err; /* So we know an error occurred */
+
+/*
+ * Probe memory in 4MB chunks, waiting for an error to tell us we've fallen
+ * off the end of real memory. Only suitable for the 2100/3100's (PMAX).
+ */
+
+#define CHUNK_SIZE 0x400000
+
+static __init void pmax_setup_memory_region(void)
+{
+ volatile unsigned char *memory_page, dummy;
+ char old_handler[0x80];
+ extern char genexcept_early;
+
+ /* Install exception handler */
+ memcpy(&old_handler, (void *)(CKSEG0 + 0x80), 0x80);
+ memcpy((void *)(CKSEG0 + 0x80), &genexcept_early, 0x80);
+
+ /* read unmapped and uncached (KSEG1)
+ * DECstations have at least 4MB RAM
+ * Assume less than 480MB of RAM, as this is max for 5000/2xx
+ * FIXME this should be replaced by the first free page!
+ */
+ for (memory_page = (unsigned char *)CKSEG1 + CHUNK_SIZE;
+ mem_err == 0 && memory_page < (unsigned char *)CKSEG1 + 0x1e00000;
+ memory_page += CHUNK_SIZE) {
+ dummy = *memory_page;
+ }
+ memcpy((void *)(CKSEG0 + 0x80), &old_handler, 0x80);
+
+ memblock_add(0, (unsigned long)memory_page - CKSEG1 - CHUNK_SIZE);
+}
+
+/*
+ * Use the REX prom calls to get hold of the memory bitmap, and thence
+ * determine memory size.
+ */
+static __init void rex_setup_memory_region(void)
+{
+ int i, bitmap_size;
+ unsigned long mem_start = 0, mem_size = 0;
+ memmap *bm;
+
+ /* some free 64k */
+ bm = (memmap *)CKSEG0ADDR(0x28000);
+
+ bitmap_size = rex_getbitmap(bm);
+
+ for (i = 0; i < bitmap_size; i++) {
+ /* FIXME: very simplistically only add full sets of pages */
+ if (bm->bitmap[i] == 0xff)
+ mem_size += (8 * bm->pagesize);
+ else if (!mem_size)
+ mem_start += (8 * bm->pagesize);
+ else {
+ memblock_add(mem_start, mem_size);
+ mem_start += mem_size + (8 * bm->pagesize);
+ mem_size = 0;
+ }
+ }
+ if (mem_size)
+ memblock_add(mem_start, mem_size);
+}
+
+void __init prom_meminit(u32 magic)
+{
+ if (!prom_is_rex(magic))
+ pmax_setup_memory_region();
+ else
+ rex_setup_memory_region();
+}
+
+void __init prom_free_prom_memory(void)
+{
+ unsigned long end;
+
+ /*
+ * Free everything below the kernel itself but leave
+ * the first page reserved for the exception handlers.
+ */
+
+#if IS_ENABLED(CONFIG_DECLANCE)
+ /*
+ * Leave 128 KB reserved for Lance memory for
+ * IOASIC DECstations.
+ *
+ * XXX: save this address for use in dec_lance.c?
+ */
+ if (IOASIC)
+ end = __pa(&_text) - 0x00020000;
+ else
+#endif
+ end = __pa(&_text);
+
+ free_init_pages("unused PROM memory", PAGE_SIZE, end);
+}
diff --git a/arch/mips/dec/reset.c b/arch/mips/dec/reset.c
new file mode 100644
index 000000000..ee1ad38f4
--- /dev/null
+++ b/arch/mips/dec/reset.c
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Reset a DECstation machine.
+ *
+ * Copyright (C) 199x the Anonymous
+ * Copyright (C) 2001, 2002, 2003 Maciej W. Rozycki
+ */
+#include <linux/interrupt.h>
+#include <linux/linkage.h>
+
+#include <asm/addrspace.h>
+
+#include <asm/dec/reset.h>
+
+typedef void __noreturn (* noret_func_t)(void);
+
+static inline void __noreturn back_to_prom(void)
+{
+ noret_func_t func = (void *)CKSEG1ADDR(0x1fc00000);
+
+ func();
+}
+
+void __noreturn dec_machine_restart(char *command)
+{
+ back_to_prom();
+}
+
+void __noreturn dec_machine_halt(void)
+{
+ back_to_prom();
+}
+
+void __noreturn dec_machine_power_off(void)
+{
+ /* DECstations don't have a software power switch */
+ back_to_prom();
+}
+
+irqreturn_t dec_intr_halt(int irq, void *dev_id)
+{
+ dec_machine_halt();
+}
diff --git a/arch/mips/dec/setup.c b/arch/mips/dec/setup.c
new file mode 100644
index 000000000..78ad28ac1
--- /dev/null
+++ b/arch/mips/dec/setup.c
@@ -0,0 +1,774 @@
+/*
+ * System-specific setup, especially interrupts.
+ *
+ * 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.
+ *
+ * Copyright (C) 1998 Harald Koerfgen
+ * Copyright (C) 2000, 2001, 2002, 2003, 2005, 2020 Maciej W. Rozycki
+ */
+#include <linux/console.h>
+#include <linux/export.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/irqnr.h>
+#include <linux/memblock.h>
+#include <linux/param.h>
+#include <linux/percpu-defs.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/pm.h>
+
+#include <asm/addrspace.h>
+#include <asm/bootinfo.h>
+#include <asm/cpu.h>
+#include <asm/cpu-features.h>
+#include <asm/cpu-type.h>
+#include <asm/irq.h>
+#include <asm/irq_cpu.h>
+#include <asm/mipsregs.h>
+#include <asm/page.h>
+#include <asm/reboot.h>
+#include <asm/sections.h>
+#include <asm/time.h>
+#include <asm/traps.h>
+#include <asm/wbflush.h>
+
+#include <asm/dec/interrupts.h>
+#include <asm/dec/ioasic.h>
+#include <asm/dec/ioasic_addrs.h>
+#include <asm/dec/ioasic_ints.h>
+#include <asm/dec/kn01.h>
+#include <asm/dec/kn02.h>
+#include <asm/dec/kn02ba.h>
+#include <asm/dec/kn02ca.h>
+#include <asm/dec/kn03.h>
+#include <asm/dec/kn230.h>
+#include <asm/dec/reset.h>
+#include <asm/dec/system.h>
+
+
+unsigned long dec_kn_slot_base, dec_kn_slot_size;
+
+EXPORT_SYMBOL(dec_kn_slot_base);
+EXPORT_SYMBOL(dec_kn_slot_size);
+
+int dec_tc_bus;
+
+DEFINE_SPINLOCK(ioasic_ssr_lock);
+EXPORT_SYMBOL(ioasic_ssr_lock);
+
+volatile u32 *ioasic_base;
+
+EXPORT_SYMBOL(ioasic_base);
+
+/*
+ * IRQ routing and priority tables. Priorities are set as follows:
+ *
+ * KN01 KN230 KN02 KN02-BA KN02-CA KN03
+ *
+ * MEMORY CPU CPU CPU ASIC CPU CPU
+ * RTC CPU CPU CPU ASIC CPU CPU
+ * DMA - - - ASIC ASIC ASIC
+ * SERIAL0 CPU CPU CSR ASIC ASIC ASIC
+ * SERIAL1 - - - ASIC - ASIC
+ * SCSI CPU CPU CSR ASIC ASIC ASIC
+ * ETHERNET CPU * CSR ASIC ASIC ASIC
+ * other - - - ASIC - -
+ * TC2 - - CSR CPU ASIC ASIC
+ * TC1 - - CSR CPU ASIC ASIC
+ * TC0 - - CSR CPU ASIC ASIC
+ * other - CPU - CPU ASIC ASIC
+ * other - - - - CPU CPU
+ *
+ * * -- shared with SCSI
+ */
+
+int dec_interrupt[DEC_NR_INTS] = {
+ [0 ... DEC_NR_INTS - 1] = -1
+};
+
+EXPORT_SYMBOL(dec_interrupt);
+
+int_ptr cpu_mask_nr_tbl[DEC_MAX_CPU_INTS][2] = {
+ { { .i = ~0 }, { .p = dec_intr_unimplemented } },
+};
+int_ptr asic_mask_nr_tbl[DEC_MAX_ASIC_INTS][2] = {
+ { { .i = ~0 }, { .p = asic_intr_unimplemented } },
+};
+int cpu_fpu_mask = DEC_CPU_IRQ_MASK(DEC_CPU_INR_FPU);
+int *fpu_kstat_irq;
+
+static irq_handler_t busirq_handler;
+static unsigned int busirq_flags = IRQF_NO_THREAD;
+
+/*
+ * Bus error (DBE/IBE exceptions and bus interrupts) handling setup.
+ */
+static void __init dec_be_init(void)
+{
+ switch (mips_machtype) {
+ case MACH_DS23100: /* DS2100/DS3100 Pmin/Pmax */
+ mips_set_be_handler(dec_kn01_be_handler);
+ busirq_handler = dec_kn01_be_interrupt;
+ busirq_flags |= IRQF_SHARED;
+ dec_kn01_be_init();
+ break;
+ case MACH_DS5000_1XX: /* DS5000/1xx 3min */
+ case MACH_DS5000_XX: /* DS5000/xx Maxine */
+ mips_set_be_handler(dec_kn02xa_be_handler);
+ busirq_handler = dec_kn02xa_be_interrupt;
+ dec_kn02xa_be_init();
+ break;
+ case MACH_DS5000_200: /* DS5000/200 3max */
+ case MACH_DS5000_2X0: /* DS5000/240 3max+ */
+ case MACH_DS5900: /* DS5900 bigmax */
+ mips_set_be_handler(dec_ecc_be_handler);
+ busirq_handler = dec_ecc_be_interrupt;
+ dec_ecc_be_init();
+ break;
+ }
+}
+
+void __init plat_mem_setup(void)
+{
+ board_be_init = dec_be_init;
+
+ wbflush_setup();
+
+ _machine_restart = dec_machine_restart;
+ _machine_halt = dec_machine_halt;
+ pm_power_off = dec_machine_power_off;
+
+ ioport_resource.start = ~0UL;
+ ioport_resource.end = 0UL;
+
+ /* Stay away from the firmware working memory area for now. */
+ memblock_reserve(PHYS_OFFSET, __pa_symbol(&_text) - PHYS_OFFSET);
+}
+
+/*
+ * Machine-specific initialisation for KN01, aka DS2100 (aka Pmin)
+ * or DS3100 (aka Pmax).
+ */
+static int kn01_interrupt[DEC_NR_INTS] __initdata = {
+ [DEC_IRQ_CASCADE] = -1,
+ [DEC_IRQ_AB_RECV] = -1,
+ [DEC_IRQ_AB_XMIT] = -1,
+ [DEC_IRQ_DZ11] = DEC_CPU_IRQ_NR(KN01_CPU_INR_DZ11),
+ [DEC_IRQ_ASC] = -1,
+ [DEC_IRQ_FLOPPY] = -1,
+ [DEC_IRQ_FPU] = DEC_CPU_IRQ_NR(DEC_CPU_INR_FPU),
+ [DEC_IRQ_HALT] = -1,
+ [DEC_IRQ_ISDN] = -1,
+ [DEC_IRQ_LANCE] = DEC_CPU_IRQ_NR(KN01_CPU_INR_LANCE),
+ [DEC_IRQ_BUS] = DEC_CPU_IRQ_NR(KN01_CPU_INR_BUS),
+ [DEC_IRQ_PSU] = -1,
+ [DEC_IRQ_RTC] = DEC_CPU_IRQ_NR(KN01_CPU_INR_RTC),
+ [DEC_IRQ_SCC0] = -1,
+ [DEC_IRQ_SCC1] = -1,
+ [DEC_IRQ_SII] = DEC_CPU_IRQ_NR(KN01_CPU_INR_SII),
+ [DEC_IRQ_TC0] = -1,
+ [DEC_IRQ_TC1] = -1,
+ [DEC_IRQ_TC2] = -1,
+ [DEC_IRQ_TIMER] = -1,
+ [DEC_IRQ_VIDEO] = DEC_CPU_IRQ_NR(KN01_CPU_INR_VIDEO),
+ [DEC_IRQ_ASC_MERR] = -1,
+ [DEC_IRQ_ASC_ERR] = -1,
+ [DEC_IRQ_ASC_DMA] = -1,
+ [DEC_IRQ_FLOPPY_ERR] = -1,
+ [DEC_IRQ_ISDN_ERR] = -1,
+ [DEC_IRQ_ISDN_RXDMA] = -1,
+ [DEC_IRQ_ISDN_TXDMA] = -1,
+ [DEC_IRQ_LANCE_MERR] = -1,
+ [DEC_IRQ_SCC0A_RXERR] = -1,
+ [DEC_IRQ_SCC0A_RXDMA] = -1,
+ [DEC_IRQ_SCC0A_TXERR] = -1,
+ [DEC_IRQ_SCC0A_TXDMA] = -1,
+ [DEC_IRQ_AB_RXERR] = -1,
+ [DEC_IRQ_AB_RXDMA] = -1,
+ [DEC_IRQ_AB_TXERR] = -1,
+ [DEC_IRQ_AB_TXDMA] = -1,
+ [DEC_IRQ_SCC1A_RXERR] = -1,
+ [DEC_IRQ_SCC1A_RXDMA] = -1,
+ [DEC_IRQ_SCC1A_TXERR] = -1,
+ [DEC_IRQ_SCC1A_TXDMA] = -1,
+};
+
+static int_ptr kn01_cpu_mask_nr_tbl[][2] __initdata = {
+ { { .i = DEC_CPU_IRQ_MASK(KN01_CPU_INR_BUS) },
+ { .i = DEC_CPU_IRQ_NR(KN01_CPU_INR_BUS) } },
+ { { .i = DEC_CPU_IRQ_MASK(KN01_CPU_INR_RTC) },
+ { .i = DEC_CPU_IRQ_NR(KN01_CPU_INR_RTC) } },
+ { { .i = DEC_CPU_IRQ_MASK(KN01_CPU_INR_DZ11) },
+ { .i = DEC_CPU_IRQ_NR(KN01_CPU_INR_DZ11) } },
+ { { .i = DEC_CPU_IRQ_MASK(KN01_CPU_INR_SII) },
+ { .i = DEC_CPU_IRQ_NR(KN01_CPU_INR_SII) } },
+ { { .i = DEC_CPU_IRQ_MASK(KN01_CPU_INR_LANCE) },
+ { .i = DEC_CPU_IRQ_NR(KN01_CPU_INR_LANCE) } },
+ { { .i = DEC_CPU_IRQ_ALL },
+ { .p = cpu_all_int } },
+};
+
+static void __init dec_init_kn01(void)
+{
+ /* IRQ routing. */
+ memcpy(&dec_interrupt, &kn01_interrupt,
+ sizeof(kn01_interrupt));
+
+ /* CPU IRQ priorities. */
+ memcpy(&cpu_mask_nr_tbl, &kn01_cpu_mask_nr_tbl,
+ sizeof(kn01_cpu_mask_nr_tbl));
+
+ mips_cpu_irq_init();
+
+} /* dec_init_kn01 */
+
+
+/*
+ * Machine-specific initialisation for KN230, aka DS5100, aka MIPSmate.
+ */
+static int kn230_interrupt[DEC_NR_INTS] __initdata = {
+ [DEC_IRQ_CASCADE] = -1,
+ [DEC_IRQ_AB_RECV] = -1,
+ [DEC_IRQ_AB_XMIT] = -1,
+ [DEC_IRQ_DZ11] = DEC_CPU_IRQ_NR(KN230_CPU_INR_DZ11),
+ [DEC_IRQ_ASC] = -1,
+ [DEC_IRQ_FLOPPY] = -1,
+ [DEC_IRQ_FPU] = DEC_CPU_IRQ_NR(DEC_CPU_INR_FPU),
+ [DEC_IRQ_HALT] = DEC_CPU_IRQ_NR(KN230_CPU_INR_HALT),
+ [DEC_IRQ_ISDN] = -1,
+ [DEC_IRQ_LANCE] = DEC_CPU_IRQ_NR(KN230_CPU_INR_LANCE),
+ [DEC_IRQ_BUS] = DEC_CPU_IRQ_NR(KN230_CPU_INR_BUS),
+ [DEC_IRQ_PSU] = -1,
+ [DEC_IRQ_RTC] = DEC_CPU_IRQ_NR(KN230_CPU_INR_RTC),
+ [DEC_IRQ_SCC0] = -1,
+ [DEC_IRQ_SCC1] = -1,
+ [DEC_IRQ_SII] = DEC_CPU_IRQ_NR(KN230_CPU_INR_SII),
+ [DEC_IRQ_TC0] = -1,
+ [DEC_IRQ_TC1] = -1,
+ [DEC_IRQ_TC2] = -1,
+ [DEC_IRQ_TIMER] = -1,
+ [DEC_IRQ_VIDEO] = -1,
+ [DEC_IRQ_ASC_MERR] = -1,
+ [DEC_IRQ_ASC_ERR] = -1,
+ [DEC_IRQ_ASC_DMA] = -1,
+ [DEC_IRQ_FLOPPY_ERR] = -1,
+ [DEC_IRQ_ISDN_ERR] = -1,
+ [DEC_IRQ_ISDN_RXDMA] = -1,
+ [DEC_IRQ_ISDN_TXDMA] = -1,
+ [DEC_IRQ_LANCE_MERR] = -1,
+ [DEC_IRQ_SCC0A_RXERR] = -1,
+ [DEC_IRQ_SCC0A_RXDMA] = -1,
+ [DEC_IRQ_SCC0A_TXERR] = -1,
+ [DEC_IRQ_SCC0A_TXDMA] = -1,
+ [DEC_IRQ_AB_RXERR] = -1,
+ [DEC_IRQ_AB_RXDMA] = -1,
+ [DEC_IRQ_AB_TXERR] = -1,
+ [DEC_IRQ_AB_TXDMA] = -1,
+ [DEC_IRQ_SCC1A_RXERR] = -1,
+ [DEC_IRQ_SCC1A_RXDMA] = -1,
+ [DEC_IRQ_SCC1A_TXERR] = -1,
+ [DEC_IRQ_SCC1A_TXDMA] = -1,
+};
+
+static int_ptr kn230_cpu_mask_nr_tbl[][2] __initdata = {
+ { { .i = DEC_CPU_IRQ_MASK(KN230_CPU_INR_BUS) },
+ { .i = DEC_CPU_IRQ_NR(KN230_CPU_INR_BUS) } },
+ { { .i = DEC_CPU_IRQ_MASK(KN230_CPU_INR_RTC) },
+ { .i = DEC_CPU_IRQ_NR(KN230_CPU_INR_RTC) } },
+ { { .i = DEC_CPU_IRQ_MASK(KN230_CPU_INR_DZ11) },
+ { .i = DEC_CPU_IRQ_NR(KN230_CPU_INR_DZ11) } },
+ { { .i = DEC_CPU_IRQ_MASK(KN230_CPU_INR_SII) },
+ { .i = DEC_CPU_IRQ_NR(KN230_CPU_INR_SII) } },
+ { { .i = DEC_CPU_IRQ_ALL },
+ { .p = cpu_all_int } },
+};
+
+static void __init dec_init_kn230(void)
+{
+ /* IRQ routing. */
+ memcpy(&dec_interrupt, &kn230_interrupt,
+ sizeof(kn230_interrupt));
+
+ /* CPU IRQ priorities. */
+ memcpy(&cpu_mask_nr_tbl, &kn230_cpu_mask_nr_tbl,
+ sizeof(kn230_cpu_mask_nr_tbl));
+
+ mips_cpu_irq_init();
+
+} /* dec_init_kn230 */
+
+
+/*
+ * Machine-specific initialisation for KN02, aka DS5000/200, aka 3max.
+ */
+static int kn02_interrupt[DEC_NR_INTS] __initdata = {
+ [DEC_IRQ_CASCADE] = DEC_CPU_IRQ_NR(KN02_CPU_INR_CASCADE),
+ [DEC_IRQ_AB_RECV] = -1,
+ [DEC_IRQ_AB_XMIT] = -1,
+ [DEC_IRQ_DZ11] = KN02_IRQ_NR(KN02_CSR_INR_DZ11),
+ [DEC_IRQ_ASC] = KN02_IRQ_NR(KN02_CSR_INR_ASC),
+ [DEC_IRQ_FLOPPY] = -1,
+ [DEC_IRQ_FPU] = DEC_CPU_IRQ_NR(DEC_CPU_INR_FPU),
+ [DEC_IRQ_HALT] = -1,
+ [DEC_IRQ_ISDN] = -1,
+ [DEC_IRQ_LANCE] = KN02_IRQ_NR(KN02_CSR_INR_LANCE),
+ [DEC_IRQ_BUS] = DEC_CPU_IRQ_NR(KN02_CPU_INR_BUS),
+ [DEC_IRQ_PSU] = -1,
+ [DEC_IRQ_RTC] = DEC_CPU_IRQ_NR(KN02_CPU_INR_RTC),
+ [DEC_IRQ_SCC0] = -1,
+ [DEC_IRQ_SCC1] = -1,
+ [DEC_IRQ_SII] = -1,
+ [DEC_IRQ_TC0] = KN02_IRQ_NR(KN02_CSR_INR_TC0),
+ [DEC_IRQ_TC1] = KN02_IRQ_NR(KN02_CSR_INR_TC1),
+ [DEC_IRQ_TC2] = KN02_IRQ_NR(KN02_CSR_INR_TC2),
+ [DEC_IRQ_TIMER] = -1,
+ [DEC_IRQ_VIDEO] = -1,
+ [DEC_IRQ_ASC_MERR] = -1,
+ [DEC_IRQ_ASC_ERR] = -1,
+ [DEC_IRQ_ASC_DMA] = -1,
+ [DEC_IRQ_FLOPPY_ERR] = -1,
+ [DEC_IRQ_ISDN_ERR] = -1,
+ [DEC_IRQ_ISDN_RXDMA] = -1,
+ [DEC_IRQ_ISDN_TXDMA] = -1,
+ [DEC_IRQ_LANCE_MERR] = -1,
+ [DEC_IRQ_SCC0A_RXERR] = -1,
+ [DEC_IRQ_SCC0A_RXDMA] = -1,
+ [DEC_IRQ_SCC0A_TXERR] = -1,
+ [DEC_IRQ_SCC0A_TXDMA] = -1,
+ [DEC_IRQ_AB_RXERR] = -1,
+ [DEC_IRQ_AB_RXDMA] = -1,
+ [DEC_IRQ_AB_TXERR] = -1,
+ [DEC_IRQ_AB_TXDMA] = -1,
+ [DEC_IRQ_SCC1A_RXERR] = -1,
+ [DEC_IRQ_SCC1A_RXDMA] = -1,
+ [DEC_IRQ_SCC1A_TXERR] = -1,
+ [DEC_IRQ_SCC1A_TXDMA] = -1,
+};
+
+static int_ptr kn02_cpu_mask_nr_tbl[][2] __initdata = {
+ { { .i = DEC_CPU_IRQ_MASK(KN02_CPU_INR_BUS) },
+ { .i = DEC_CPU_IRQ_NR(KN02_CPU_INR_BUS) } },
+ { { .i = DEC_CPU_IRQ_MASK(KN02_CPU_INR_RTC) },
+ { .i = DEC_CPU_IRQ_NR(KN02_CPU_INR_RTC) } },
+ { { .i = DEC_CPU_IRQ_MASK(KN02_CPU_INR_CASCADE) },
+ { .p = kn02_io_int } },
+ { { .i = DEC_CPU_IRQ_ALL },
+ { .p = cpu_all_int } },
+};
+
+static int_ptr kn02_asic_mask_nr_tbl[][2] __initdata = {
+ { { .i = KN02_IRQ_MASK(KN02_CSR_INR_DZ11) },
+ { .i = KN02_IRQ_NR(KN02_CSR_INR_DZ11) } },
+ { { .i = KN02_IRQ_MASK(KN02_CSR_INR_ASC) },
+ { .i = KN02_IRQ_NR(KN02_CSR_INR_ASC) } },
+ { { .i = KN02_IRQ_MASK(KN02_CSR_INR_LANCE) },
+ { .i = KN02_IRQ_NR(KN02_CSR_INR_LANCE) } },
+ { { .i = KN02_IRQ_MASK(KN02_CSR_INR_TC2) },
+ { .i = KN02_IRQ_NR(KN02_CSR_INR_TC2) } },
+ { { .i = KN02_IRQ_MASK(KN02_CSR_INR_TC1) },
+ { .i = KN02_IRQ_NR(KN02_CSR_INR_TC1) } },
+ { { .i = KN02_IRQ_MASK(KN02_CSR_INR_TC0) },
+ { .i = KN02_IRQ_NR(KN02_CSR_INR_TC0) } },
+ { { .i = KN02_IRQ_ALL },
+ { .p = kn02_all_int } },
+};
+
+static void __init dec_init_kn02(void)
+{
+ /* IRQ routing. */
+ memcpy(&dec_interrupt, &kn02_interrupt,
+ sizeof(kn02_interrupt));
+
+ /* CPU IRQ priorities. */
+ memcpy(&cpu_mask_nr_tbl, &kn02_cpu_mask_nr_tbl,
+ sizeof(kn02_cpu_mask_nr_tbl));
+
+ /* KN02 CSR IRQ priorities. */
+ memcpy(&asic_mask_nr_tbl, &kn02_asic_mask_nr_tbl,
+ sizeof(kn02_asic_mask_nr_tbl));
+
+ mips_cpu_irq_init();
+ init_kn02_irqs(KN02_IRQ_BASE);
+
+} /* dec_init_kn02 */
+
+
+/*
+ * Machine-specific initialisation for KN02-BA, aka DS5000/1xx
+ * (xx = 20, 25, 33), aka 3min. Also applies to KN04(-BA), aka
+ * DS5000/150, aka 4min.
+ */
+static int kn02ba_interrupt[DEC_NR_INTS] __initdata = {
+ [DEC_IRQ_CASCADE] = DEC_CPU_IRQ_NR(KN02BA_CPU_INR_CASCADE),
+ [DEC_IRQ_AB_RECV] = -1,
+ [DEC_IRQ_AB_XMIT] = -1,
+ [DEC_IRQ_DZ11] = -1,
+ [DEC_IRQ_ASC] = IO_IRQ_NR(KN02BA_IO_INR_ASC),
+ [DEC_IRQ_FLOPPY] = -1,
+ [DEC_IRQ_FPU] = DEC_CPU_IRQ_NR(DEC_CPU_INR_FPU),
+ [DEC_IRQ_HALT] = DEC_CPU_IRQ_NR(KN02BA_CPU_INR_HALT),
+ [DEC_IRQ_ISDN] = -1,
+ [DEC_IRQ_LANCE] = IO_IRQ_NR(KN02BA_IO_INR_LANCE),
+ [DEC_IRQ_BUS] = IO_IRQ_NR(KN02BA_IO_INR_BUS),
+ [DEC_IRQ_PSU] = IO_IRQ_NR(KN02BA_IO_INR_PSU),
+ [DEC_IRQ_RTC] = IO_IRQ_NR(KN02BA_IO_INR_RTC),
+ [DEC_IRQ_SCC0] = IO_IRQ_NR(KN02BA_IO_INR_SCC0),
+ [DEC_IRQ_SCC1] = IO_IRQ_NR(KN02BA_IO_INR_SCC1),
+ [DEC_IRQ_SII] = -1,
+ [DEC_IRQ_TC0] = DEC_CPU_IRQ_NR(KN02BA_CPU_INR_TC0),
+ [DEC_IRQ_TC1] = DEC_CPU_IRQ_NR(KN02BA_CPU_INR_TC1),
+ [DEC_IRQ_TC2] = DEC_CPU_IRQ_NR(KN02BA_CPU_INR_TC2),
+ [DEC_IRQ_TIMER] = -1,
+ [DEC_IRQ_VIDEO] = -1,
+ [DEC_IRQ_ASC_MERR] = IO_IRQ_NR(IO_INR_ASC_MERR),
+ [DEC_IRQ_ASC_ERR] = IO_IRQ_NR(IO_INR_ASC_ERR),
+ [DEC_IRQ_ASC_DMA] = IO_IRQ_NR(IO_INR_ASC_DMA),
+ [DEC_IRQ_FLOPPY_ERR] = -1,
+ [DEC_IRQ_ISDN_ERR] = -1,
+ [DEC_IRQ_ISDN_RXDMA] = -1,
+ [DEC_IRQ_ISDN_TXDMA] = -1,
+ [DEC_IRQ_LANCE_MERR] = IO_IRQ_NR(IO_INR_LANCE_MERR),
+ [DEC_IRQ_SCC0A_RXERR] = IO_IRQ_NR(IO_INR_SCC0A_RXERR),
+ [DEC_IRQ_SCC0A_RXDMA] = IO_IRQ_NR(IO_INR_SCC0A_RXDMA),
+ [DEC_IRQ_SCC0A_TXERR] = IO_IRQ_NR(IO_INR_SCC0A_TXERR),
+ [DEC_IRQ_SCC0A_TXDMA] = IO_IRQ_NR(IO_INR_SCC0A_TXDMA),
+ [DEC_IRQ_AB_RXERR] = -1,
+ [DEC_IRQ_AB_RXDMA] = -1,
+ [DEC_IRQ_AB_TXERR] = -1,
+ [DEC_IRQ_AB_TXDMA] = -1,
+ [DEC_IRQ_SCC1A_RXERR] = IO_IRQ_NR(IO_INR_SCC1A_RXERR),
+ [DEC_IRQ_SCC1A_RXDMA] = IO_IRQ_NR(IO_INR_SCC1A_RXDMA),
+ [DEC_IRQ_SCC1A_TXERR] = IO_IRQ_NR(IO_INR_SCC1A_TXERR),
+ [DEC_IRQ_SCC1A_TXDMA] = IO_IRQ_NR(IO_INR_SCC1A_TXDMA),
+};
+
+static int_ptr kn02ba_cpu_mask_nr_tbl[][2] __initdata = {
+ { { .i = DEC_CPU_IRQ_MASK(KN02BA_CPU_INR_CASCADE) },
+ { .p = kn02xa_io_int } },
+ { { .i = DEC_CPU_IRQ_MASK(KN02BA_CPU_INR_TC2) },
+ { .i = DEC_CPU_IRQ_NR(KN02BA_CPU_INR_TC2) } },
+ { { .i = DEC_CPU_IRQ_MASK(KN02BA_CPU_INR_TC1) },
+ { .i = DEC_CPU_IRQ_NR(KN02BA_CPU_INR_TC1) } },
+ { { .i = DEC_CPU_IRQ_MASK(KN02BA_CPU_INR_TC0) },
+ { .i = DEC_CPU_IRQ_NR(KN02BA_CPU_INR_TC0) } },
+ { { .i = DEC_CPU_IRQ_ALL },
+ { .p = cpu_all_int } },
+};
+
+static int_ptr kn02ba_asic_mask_nr_tbl[][2] __initdata = {
+ { { .i = IO_IRQ_MASK(KN02BA_IO_INR_BUS) },
+ { .i = IO_IRQ_NR(KN02BA_IO_INR_BUS) } },
+ { { .i = IO_IRQ_MASK(KN02BA_IO_INR_RTC) },
+ { .i = IO_IRQ_NR(KN02BA_IO_INR_RTC) } },
+ { { .i = IO_IRQ_DMA },
+ { .p = asic_dma_int } },
+ { { .i = IO_IRQ_MASK(KN02BA_IO_INR_SCC0) },
+ { .i = IO_IRQ_NR(KN02BA_IO_INR_SCC0) } },
+ { { .i = IO_IRQ_MASK(KN02BA_IO_INR_SCC1) },
+ { .i = IO_IRQ_NR(KN02BA_IO_INR_SCC1) } },
+ { { .i = IO_IRQ_MASK(KN02BA_IO_INR_ASC) },
+ { .i = IO_IRQ_NR(KN02BA_IO_INR_ASC) } },
+ { { .i = IO_IRQ_MASK(KN02BA_IO_INR_LANCE) },
+ { .i = IO_IRQ_NR(KN02BA_IO_INR_LANCE) } },
+ { { .i = IO_IRQ_ALL },
+ { .p = asic_all_int } },
+};
+
+static void __init dec_init_kn02ba(void)
+{
+ /* IRQ routing. */
+ memcpy(&dec_interrupt, &kn02ba_interrupt,
+ sizeof(kn02ba_interrupt));
+
+ /* CPU IRQ priorities. */
+ memcpy(&cpu_mask_nr_tbl, &kn02ba_cpu_mask_nr_tbl,
+ sizeof(kn02ba_cpu_mask_nr_tbl));
+
+ /* I/O ASIC IRQ priorities. */
+ memcpy(&asic_mask_nr_tbl, &kn02ba_asic_mask_nr_tbl,
+ sizeof(kn02ba_asic_mask_nr_tbl));
+
+ mips_cpu_irq_init();
+ init_ioasic_irqs(IO_IRQ_BASE);
+
+} /* dec_init_kn02ba */
+
+
+/*
+ * Machine-specific initialisation for KN02-CA, aka DS5000/xx,
+ * (xx = 20, 25, 33), aka MAXine. Also applies to KN04(-CA), aka
+ * DS5000/50, aka 4MAXine.
+ */
+static int kn02ca_interrupt[DEC_NR_INTS] __initdata = {
+ [DEC_IRQ_CASCADE] = DEC_CPU_IRQ_NR(KN02CA_CPU_INR_CASCADE),
+ [DEC_IRQ_AB_RECV] = IO_IRQ_NR(KN02CA_IO_INR_AB_RECV),
+ [DEC_IRQ_AB_XMIT] = IO_IRQ_NR(KN02CA_IO_INR_AB_XMIT),
+ [DEC_IRQ_DZ11] = -1,
+ [DEC_IRQ_ASC] = IO_IRQ_NR(KN02CA_IO_INR_ASC),
+ [DEC_IRQ_FLOPPY] = IO_IRQ_NR(KN02CA_IO_INR_FLOPPY),
+ [DEC_IRQ_FPU] = DEC_CPU_IRQ_NR(DEC_CPU_INR_FPU),
+ [DEC_IRQ_HALT] = DEC_CPU_IRQ_NR(KN02CA_CPU_INR_HALT),
+ [DEC_IRQ_ISDN] = IO_IRQ_NR(KN02CA_IO_INR_ISDN),
+ [DEC_IRQ_LANCE] = IO_IRQ_NR(KN02CA_IO_INR_LANCE),
+ [DEC_IRQ_BUS] = DEC_CPU_IRQ_NR(KN02CA_CPU_INR_BUS),
+ [DEC_IRQ_PSU] = -1,
+ [DEC_IRQ_RTC] = DEC_CPU_IRQ_NR(KN02CA_CPU_INR_RTC),
+ [DEC_IRQ_SCC0] = IO_IRQ_NR(KN02CA_IO_INR_SCC0),
+ [DEC_IRQ_SCC1] = -1,
+ [DEC_IRQ_SII] = -1,
+ [DEC_IRQ_TC0] = IO_IRQ_NR(KN02CA_IO_INR_TC0),
+ [DEC_IRQ_TC1] = IO_IRQ_NR(KN02CA_IO_INR_TC1),
+ [DEC_IRQ_TC2] = -1,
+ [DEC_IRQ_TIMER] = DEC_CPU_IRQ_NR(KN02CA_CPU_INR_TIMER),
+ [DEC_IRQ_VIDEO] = IO_IRQ_NR(KN02CA_IO_INR_VIDEO),
+ [DEC_IRQ_ASC_MERR] = IO_IRQ_NR(IO_INR_ASC_MERR),
+ [DEC_IRQ_ASC_ERR] = IO_IRQ_NR(IO_INR_ASC_ERR),
+ [DEC_IRQ_ASC_DMA] = IO_IRQ_NR(IO_INR_ASC_DMA),
+ [DEC_IRQ_FLOPPY_ERR] = IO_IRQ_NR(IO_INR_FLOPPY_ERR),
+ [DEC_IRQ_ISDN_ERR] = IO_IRQ_NR(IO_INR_ISDN_ERR),
+ [DEC_IRQ_ISDN_RXDMA] = IO_IRQ_NR(IO_INR_ISDN_RXDMA),
+ [DEC_IRQ_ISDN_TXDMA] = IO_IRQ_NR(IO_INR_ISDN_TXDMA),
+ [DEC_IRQ_LANCE_MERR] = IO_IRQ_NR(IO_INR_LANCE_MERR),
+ [DEC_IRQ_SCC0A_RXERR] = IO_IRQ_NR(IO_INR_SCC0A_RXERR),
+ [DEC_IRQ_SCC0A_RXDMA] = IO_IRQ_NR(IO_INR_SCC0A_RXDMA),
+ [DEC_IRQ_SCC0A_TXERR] = IO_IRQ_NR(IO_INR_SCC0A_TXERR),
+ [DEC_IRQ_SCC0A_TXDMA] = IO_IRQ_NR(IO_INR_SCC0A_TXDMA),
+ [DEC_IRQ_AB_RXERR] = IO_IRQ_NR(IO_INR_AB_RXERR),
+ [DEC_IRQ_AB_RXDMA] = IO_IRQ_NR(IO_INR_AB_RXDMA),
+ [DEC_IRQ_AB_TXERR] = IO_IRQ_NR(IO_INR_AB_TXERR),
+ [DEC_IRQ_AB_TXDMA] = IO_IRQ_NR(IO_INR_AB_TXDMA),
+ [DEC_IRQ_SCC1A_RXERR] = -1,
+ [DEC_IRQ_SCC1A_RXDMA] = -1,
+ [DEC_IRQ_SCC1A_TXERR] = -1,
+ [DEC_IRQ_SCC1A_TXDMA] = -1,
+};
+
+static int_ptr kn02ca_cpu_mask_nr_tbl[][2] __initdata = {
+ { { .i = DEC_CPU_IRQ_MASK(KN02CA_CPU_INR_BUS) },
+ { .i = DEC_CPU_IRQ_NR(KN02CA_CPU_INR_BUS) } },
+ { { .i = DEC_CPU_IRQ_MASK(KN02CA_CPU_INR_RTC) },
+ { .i = DEC_CPU_IRQ_NR(KN02CA_CPU_INR_RTC) } },
+ { { .i = DEC_CPU_IRQ_MASK(KN02CA_CPU_INR_CASCADE) },
+ { .p = kn02xa_io_int } },
+ { { .i = DEC_CPU_IRQ_ALL },
+ { .p = cpu_all_int } },
+};
+
+static int_ptr kn02ca_asic_mask_nr_tbl[][2] __initdata = {
+ { { .i = IO_IRQ_DMA },
+ { .p = asic_dma_int } },
+ { { .i = IO_IRQ_MASK(KN02CA_IO_INR_SCC0) },
+ { .i = IO_IRQ_NR(KN02CA_IO_INR_SCC0) } },
+ { { .i = IO_IRQ_MASK(KN02CA_IO_INR_ASC) },
+ { .i = IO_IRQ_NR(KN02CA_IO_INR_ASC) } },
+ { { .i = IO_IRQ_MASK(KN02CA_IO_INR_LANCE) },
+ { .i = IO_IRQ_NR(KN02CA_IO_INR_LANCE) } },
+ { { .i = IO_IRQ_MASK(KN02CA_IO_INR_TC1) },
+ { .i = IO_IRQ_NR(KN02CA_IO_INR_TC1) } },
+ { { .i = IO_IRQ_MASK(KN02CA_IO_INR_TC0) },
+ { .i = IO_IRQ_NR(KN02CA_IO_INR_TC0) } },
+ { { .i = IO_IRQ_ALL },
+ { .p = asic_all_int } },
+};
+
+static void __init dec_init_kn02ca(void)
+{
+ /* IRQ routing. */
+ memcpy(&dec_interrupt, &kn02ca_interrupt,
+ sizeof(kn02ca_interrupt));
+
+ /* CPU IRQ priorities. */
+ memcpy(&cpu_mask_nr_tbl, &kn02ca_cpu_mask_nr_tbl,
+ sizeof(kn02ca_cpu_mask_nr_tbl));
+
+ /* I/O ASIC IRQ priorities. */
+ memcpy(&asic_mask_nr_tbl, &kn02ca_asic_mask_nr_tbl,
+ sizeof(kn02ca_asic_mask_nr_tbl));
+
+ mips_cpu_irq_init();
+ init_ioasic_irqs(IO_IRQ_BASE);
+
+} /* dec_init_kn02ca */
+
+
+/*
+ * Machine-specific initialisation for KN03, aka DS5000/240,
+ * aka 3max+ and DS5900, aka BIGmax. Also applies to KN05, aka
+ * DS5000/260, aka 4max+ and DS5900/260.
+ */
+static int kn03_interrupt[DEC_NR_INTS] __initdata = {
+ [DEC_IRQ_CASCADE] = DEC_CPU_IRQ_NR(KN03_CPU_INR_CASCADE),
+ [DEC_IRQ_AB_RECV] = -1,
+ [DEC_IRQ_AB_XMIT] = -1,
+ [DEC_IRQ_DZ11] = -1,
+ [DEC_IRQ_ASC] = IO_IRQ_NR(KN03_IO_INR_ASC),
+ [DEC_IRQ_FLOPPY] = -1,
+ [DEC_IRQ_FPU] = DEC_CPU_IRQ_NR(DEC_CPU_INR_FPU),
+ [DEC_IRQ_HALT] = DEC_CPU_IRQ_NR(KN03_CPU_INR_HALT),
+ [DEC_IRQ_ISDN] = -1,
+ [DEC_IRQ_LANCE] = IO_IRQ_NR(KN03_IO_INR_LANCE),
+ [DEC_IRQ_BUS] = DEC_CPU_IRQ_NR(KN03_CPU_INR_BUS),
+ [DEC_IRQ_PSU] = IO_IRQ_NR(KN03_IO_INR_PSU),
+ [DEC_IRQ_RTC] = DEC_CPU_IRQ_NR(KN03_CPU_INR_RTC),
+ [DEC_IRQ_SCC0] = IO_IRQ_NR(KN03_IO_INR_SCC0),
+ [DEC_IRQ_SCC1] = IO_IRQ_NR(KN03_IO_INR_SCC1),
+ [DEC_IRQ_SII] = -1,
+ [DEC_IRQ_TC0] = IO_IRQ_NR(KN03_IO_INR_TC0),
+ [DEC_IRQ_TC1] = IO_IRQ_NR(KN03_IO_INR_TC1),
+ [DEC_IRQ_TC2] = IO_IRQ_NR(KN03_IO_INR_TC2),
+ [DEC_IRQ_TIMER] = -1,
+ [DEC_IRQ_VIDEO] = -1,
+ [DEC_IRQ_ASC_MERR] = IO_IRQ_NR(IO_INR_ASC_MERR),
+ [DEC_IRQ_ASC_ERR] = IO_IRQ_NR(IO_INR_ASC_ERR),
+ [DEC_IRQ_ASC_DMA] = IO_IRQ_NR(IO_INR_ASC_DMA),
+ [DEC_IRQ_FLOPPY_ERR] = -1,
+ [DEC_IRQ_ISDN_ERR] = -1,
+ [DEC_IRQ_ISDN_RXDMA] = -1,
+ [DEC_IRQ_ISDN_TXDMA] = -1,
+ [DEC_IRQ_LANCE_MERR] = IO_IRQ_NR(IO_INR_LANCE_MERR),
+ [DEC_IRQ_SCC0A_RXERR] = IO_IRQ_NR(IO_INR_SCC0A_RXERR),
+ [DEC_IRQ_SCC0A_RXDMA] = IO_IRQ_NR(IO_INR_SCC0A_RXDMA),
+ [DEC_IRQ_SCC0A_TXERR] = IO_IRQ_NR(IO_INR_SCC0A_TXERR),
+ [DEC_IRQ_SCC0A_TXDMA] = IO_IRQ_NR(IO_INR_SCC0A_TXDMA),
+ [DEC_IRQ_AB_RXERR] = -1,
+ [DEC_IRQ_AB_RXDMA] = -1,
+ [DEC_IRQ_AB_TXERR] = -1,
+ [DEC_IRQ_AB_TXDMA] = -1,
+ [DEC_IRQ_SCC1A_RXERR] = IO_IRQ_NR(IO_INR_SCC1A_RXERR),
+ [DEC_IRQ_SCC1A_RXDMA] = IO_IRQ_NR(IO_INR_SCC1A_RXDMA),
+ [DEC_IRQ_SCC1A_TXERR] = IO_IRQ_NR(IO_INR_SCC1A_TXERR),
+ [DEC_IRQ_SCC1A_TXDMA] = IO_IRQ_NR(IO_INR_SCC1A_TXDMA),
+};
+
+static int_ptr kn03_cpu_mask_nr_tbl[][2] __initdata = {
+ { { .i = DEC_CPU_IRQ_MASK(KN03_CPU_INR_BUS) },
+ { .i = DEC_CPU_IRQ_NR(KN03_CPU_INR_BUS) } },
+ { { .i = DEC_CPU_IRQ_MASK(KN03_CPU_INR_RTC) },
+ { .i = DEC_CPU_IRQ_NR(KN03_CPU_INR_RTC) } },
+ { { .i = DEC_CPU_IRQ_MASK(KN03_CPU_INR_CASCADE) },
+ { .p = kn03_io_int } },
+ { { .i = DEC_CPU_IRQ_ALL },
+ { .p = cpu_all_int } },
+};
+
+static int_ptr kn03_asic_mask_nr_tbl[][2] __initdata = {
+ { { .i = IO_IRQ_DMA },
+ { .p = asic_dma_int } },
+ { { .i = IO_IRQ_MASK(KN03_IO_INR_SCC0) },
+ { .i = IO_IRQ_NR(KN03_IO_INR_SCC0) } },
+ { { .i = IO_IRQ_MASK(KN03_IO_INR_SCC1) },
+ { .i = IO_IRQ_NR(KN03_IO_INR_SCC1) } },
+ { { .i = IO_IRQ_MASK(KN03_IO_INR_ASC) },
+ { .i = IO_IRQ_NR(KN03_IO_INR_ASC) } },
+ { { .i = IO_IRQ_MASK(KN03_IO_INR_LANCE) },
+ { .i = IO_IRQ_NR(KN03_IO_INR_LANCE) } },
+ { { .i = IO_IRQ_MASK(KN03_IO_INR_TC2) },
+ { .i = IO_IRQ_NR(KN03_IO_INR_TC2) } },
+ { { .i = IO_IRQ_MASK(KN03_IO_INR_TC1) },
+ { .i = IO_IRQ_NR(KN03_IO_INR_TC1) } },
+ { { .i = IO_IRQ_MASK(KN03_IO_INR_TC0) },
+ { .i = IO_IRQ_NR(KN03_IO_INR_TC0) } },
+ { { .i = IO_IRQ_ALL },
+ { .p = asic_all_int } },
+};
+
+static void __init dec_init_kn03(void)
+{
+ /* IRQ routing. */
+ memcpy(&dec_interrupt, &kn03_interrupt,
+ sizeof(kn03_interrupt));
+
+ /* CPU IRQ priorities. */
+ memcpy(&cpu_mask_nr_tbl, &kn03_cpu_mask_nr_tbl,
+ sizeof(kn03_cpu_mask_nr_tbl));
+
+ /* I/O ASIC IRQ priorities. */
+ memcpy(&asic_mask_nr_tbl, &kn03_asic_mask_nr_tbl,
+ sizeof(kn03_asic_mask_nr_tbl));
+
+ mips_cpu_irq_init();
+ init_ioasic_irqs(IO_IRQ_BASE);
+
+} /* dec_init_kn03 */
+
+
+void __init arch_init_irq(void)
+{
+ switch (mips_machtype) {
+ case MACH_DS23100: /* DS2100/DS3100 Pmin/Pmax */
+ dec_init_kn01();
+ break;
+ case MACH_DS5100: /* DS5100 MIPSmate */
+ dec_init_kn230();
+ break;
+ case MACH_DS5000_200: /* DS5000/200 3max */
+ dec_init_kn02();
+ break;
+ case MACH_DS5000_1XX: /* DS5000/1xx 3min */
+ dec_init_kn02ba();
+ break;
+ case MACH_DS5000_2X0: /* DS5000/240 3max+ */
+ case MACH_DS5900: /* DS5900 bigmax */
+ dec_init_kn03();
+ break;
+ case MACH_DS5000_XX: /* Personal DS5000/xx */
+ dec_init_kn02ca();
+ break;
+ case MACH_DS5800: /* DS5800 Isis */
+ panic("Don't know how to set this up!");
+ break;
+ case MACH_DS5400: /* DS5400 MIPSfair */
+ panic("Don't know how to set this up!");
+ break;
+ case MACH_DS5500: /* DS5500 MIPSfair-2 */
+ panic("Don't know how to set this up!");
+ break;
+ }
+
+ /* Free the FPU interrupt if the exception is present. */
+ if (!cpu_has_nofpuex) {
+ cpu_fpu_mask = 0;
+ dec_interrupt[DEC_IRQ_FPU] = -1;
+ }
+ /* Free the halt interrupt unused on R4k systems. */
+ if (current_cpu_type() == CPU_R4000SC ||
+ current_cpu_type() == CPU_R4400SC)
+ dec_interrupt[DEC_IRQ_HALT] = -1;
+
+ /* Register board interrupts: FPU and cascade. */
+ if (IS_ENABLED(CONFIG_MIPS_FP_SUPPORT) &&
+ dec_interrupt[DEC_IRQ_FPU] >= 0 && cpu_has_fpu) {
+ struct irq_desc *desc_fpu;
+ int irq_fpu;
+
+ irq_fpu = dec_interrupt[DEC_IRQ_FPU];
+ if (request_irq(irq_fpu, no_action, IRQF_NO_THREAD, "fpu",
+ NULL))
+ pr_err("Failed to register fpu interrupt\n");
+ desc_fpu = irq_to_desc(irq_fpu);
+ fpu_kstat_irq = this_cpu_ptr(&desc_fpu->kstat_irqs->cnt);
+ }
+ if (dec_interrupt[DEC_IRQ_CASCADE] >= 0) {
+ if (request_irq(dec_interrupt[DEC_IRQ_CASCADE], no_action,
+ IRQF_NO_THREAD, "cascade", NULL))
+ pr_err("Failed to register cascade interrupt\n");
+ }
+ /* Register the bus error interrupt. */
+ if (dec_interrupt[DEC_IRQ_BUS] >= 0 && busirq_handler) {
+ if (request_irq(dec_interrupt[DEC_IRQ_BUS], busirq_handler,
+ busirq_flags, "bus error", busirq_handler))
+ pr_err("Failed to register bus error interrupt\n");
+ }
+ /* Register the HALT interrupt. */
+ if (dec_interrupt[DEC_IRQ_HALT] >= 0) {
+ if (request_irq(dec_interrupt[DEC_IRQ_HALT], dec_intr_halt,
+ IRQF_NO_THREAD, "halt", NULL))
+ pr_err("Failed to register halt interrupt\n");
+ }
+}
diff --git a/arch/mips/dec/tc.c b/arch/mips/dec/tc.c
new file mode 100644
index 000000000..dba583976
--- /dev/null
+++ b/arch/mips/dec/tc.c
@@ -0,0 +1,95 @@
+/*
+ * TURBOchannel architecture calls.
+ *
+ * Copyright (c) Harald Koerfgen, 1998
+ * Copyright (c) 2001, 2003, 2005, 2006 Maciej W. Rozycki
+ * Copyright (c) 2005 James Simmons
+ *
+ * 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/compiler.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/tc.h>
+#include <linux/types.h>
+
+#include <asm/addrspace.h>
+#include <asm/bootinfo.h>
+#include <asm/paccess.h>
+
+#include <asm/dec/interrupts.h>
+#include <asm/dec/prom.h>
+#include <asm/dec/system.h>
+
+/*
+ * Protected read byte from TURBOchannel slot space.
+ */
+int tc_preadb(u8 *valp, void __iomem *addr)
+{
+ return get_dbe(*valp, (u8 *)addr);
+}
+
+/*
+ * Get TURBOchannel bus information as specified by the spec, plus
+ * the slot space base address and the number of slots.
+ */
+int __init tc_bus_get_info(struct tc_bus *tbus)
+{
+ if (!dec_tc_bus)
+ return -ENXIO;
+
+ memcpy(&tbus->info, rex_gettcinfo(), sizeof(tbus->info));
+ tbus->slot_base = CPHYSADDR((long)rex_slot_address(0));
+
+ switch (mips_machtype) {
+ case MACH_DS5000_200:
+ tbus->num_tcslots = 7;
+ break;
+ case MACH_DS5000_2X0:
+ case MACH_DS5900:
+ tbus->ext_slot_base = 0x20000000;
+ tbus->ext_slot_size = 0x20000000;
+ fallthrough;
+ case MACH_DS5000_1XX:
+ tbus->num_tcslots = 3;
+ break;
+ case MACH_DS5000_XX:
+ tbus->num_tcslots = 2;
+ default:
+ break;
+ }
+ return 0;
+}
+
+/*
+ * Get the IRQ for the specified slot.
+ */
+void __init tc_device_get_irq(struct tc_dev *tdev)
+{
+ switch (tdev->slot) {
+ case 0:
+ tdev->interrupt = dec_interrupt[DEC_IRQ_TC0];
+ break;
+ case 1:
+ tdev->interrupt = dec_interrupt[DEC_IRQ_TC1];
+ break;
+ case 2:
+ tdev->interrupt = dec_interrupt[DEC_IRQ_TC2];
+ break;
+ /*
+ * Yuck! DS5000/200 onboard devices
+ */
+ case 5:
+ tdev->interrupt = dec_interrupt[DEC_IRQ_TC5];
+ break;
+ case 6:
+ tdev->interrupt = dec_interrupt[DEC_IRQ_TC6];
+ break;
+ default:
+ tdev->interrupt = -1;
+ break;
+ }
+}
diff --git a/arch/mips/dec/time.c b/arch/mips/dec/time.c
new file mode 100644
index 000000000..c38686f89
--- /dev/null
+++ b/arch/mips/dec/time.c
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 1991, 1992, 1995 Linus Torvalds
+ * Copyright (C) 2000, 2003 Maciej W. Rozycki
+ *
+ * This file contains the time handling details for PC-style clocks as
+ * found in some MIPS systems.
+ *
+ */
+#include <linux/bcd.h>
+#include <linux/init.h>
+#include <linux/mc146818rtc.h>
+#include <linux/param.h>
+
+#include <asm/cpu-features.h>
+#include <asm/ds1287.h>
+#include <asm/time.h>
+#include <asm/dec/interrupts.h>
+#include <asm/dec/ioasic.h>
+#include <asm/dec/machtype.h>
+
+void read_persistent_clock64(struct timespec64 *ts)
+{
+ unsigned int year, mon, day, hour, min, sec, real_year;
+ unsigned long flags;
+
+ spin_lock_irqsave(&rtc_lock, flags);
+
+ do {
+ sec = CMOS_READ(RTC_SECONDS);
+ min = CMOS_READ(RTC_MINUTES);
+ hour = CMOS_READ(RTC_HOURS);
+ day = CMOS_READ(RTC_DAY_OF_MONTH);
+ mon = CMOS_READ(RTC_MONTH);
+ year = CMOS_READ(RTC_YEAR);
+ /*
+ * The PROM will reset the year to either '72 or '73.
+ * Therefore we store the real year separately, in one
+ * of unused BBU RAM locations.
+ */
+ real_year = CMOS_READ(RTC_DEC_YEAR);
+ } while (sec != CMOS_READ(RTC_SECONDS));
+
+ spin_unlock_irqrestore(&rtc_lock, flags);
+
+ if (!(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
+ sec = bcd2bin(sec);
+ min = bcd2bin(min);
+ hour = bcd2bin(hour);
+ day = bcd2bin(day);
+ mon = bcd2bin(mon);
+ year = bcd2bin(year);
+ }
+
+ year += real_year - 72 + 2000;
+
+ ts->tv_sec = mktime64(year, mon, day, hour, min, sec);
+ ts->tv_nsec = 0;
+}
+
+/*
+ * In order to set the CMOS clock precisely, update_persistent_clock64 has to
+ * be called 500 ms after the second nowtime has started, because when
+ * nowtime is written into the registers of the CMOS clock, it will
+ * jump to the next second precisely 500 ms later. Check the Dallas
+ * DS1287 data sheet for details.
+ */
+int update_persistent_clock64(struct timespec64 now)
+{
+ time64_t nowtime = now.tv_sec;
+ int retval = 0;
+ int real_seconds, real_minutes, cmos_minutes;
+ unsigned char save_control, save_freq_select;
+
+ /* irq are locally disabled here */
+ spin_lock(&rtc_lock);
+ /* tell the clock it's being set */
+ save_control = CMOS_READ(RTC_CONTROL);
+ CMOS_WRITE((save_control | RTC_SET), RTC_CONTROL);
+
+ /* stop and reset prescaler */
+ save_freq_select = CMOS_READ(RTC_FREQ_SELECT);
+ CMOS_WRITE((save_freq_select | RTC_DIV_RESET2), RTC_FREQ_SELECT);
+
+ cmos_minutes = CMOS_READ(RTC_MINUTES);
+ if (!(save_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD)
+ cmos_minutes = bcd2bin(cmos_minutes);
+
+ /*
+ * since we're only adjusting minutes and seconds,
+ * don't interfere with hour overflow. This avoids
+ * messing with unknown time zones but requires your
+ * RTC not to be off by more than 15 minutes
+ */
+ real_minutes = div_s64_rem(nowtime, 60, &real_seconds);
+ if (((abs(real_minutes - cmos_minutes) + 15) / 30) & 1)
+ real_minutes += 30; /* correct for half hour time zone */
+ real_minutes %= 60;
+
+ if (abs(real_minutes - cmos_minutes) < 30) {
+ if (!(save_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
+ real_seconds = bin2bcd(real_seconds);
+ real_minutes = bin2bcd(real_minutes);
+ }
+ CMOS_WRITE(real_seconds, RTC_SECONDS);
+ CMOS_WRITE(real_minutes, RTC_MINUTES);
+ } else {
+ printk_once(KERN_NOTICE
+ "set_rtc_mmss: can't update from %d to %d\n",
+ cmos_minutes, real_minutes);
+ retval = -1;
+ }
+
+ /* The following flags have to be released exactly in this order,
+ * otherwise the DS1287 will not reset the oscillator and will not
+ * update precisely 500 ms later. You won't find this mentioned
+ * in the Dallas Semiconductor data sheets, but who believes data
+ * sheets anyway ... -- Markus Kuhn
+ */
+ CMOS_WRITE(save_control, RTC_CONTROL);
+ CMOS_WRITE(save_freq_select, RTC_FREQ_SELECT);
+ spin_unlock(&rtc_lock);
+
+ return retval;
+}
+
+void __init plat_time_init(void)
+{
+ int ioasic_clock = 0;
+ u32 start, end;
+ int i = HZ / 8;
+
+ /* Set up the rate of periodic DS1287 interrupts. */
+ ds1287_set_base_clock(HZ);
+
+ /* On some I/O ASIC systems we have the I/O ASIC's counter. */
+ if (IOASIC)
+ ioasic_clock = dec_ioasic_clocksource_init() == 0;
+ if (cpu_has_counter) {
+ ds1287_timer_state();
+ while (!ds1287_timer_state())
+ ;
+
+ start = read_c0_count();
+
+ while (i--)
+ while (!ds1287_timer_state())
+ ;
+
+ end = read_c0_count();
+
+ mips_hpt_frequency = (end - start) * 8;
+ printk(KERN_INFO "MIPS counter frequency %dHz\n",
+ mips_hpt_frequency);
+
+ /*
+ * All R4k DECstations suffer from the CP0 Count erratum,
+ * so we can't use the timer as a clock source, and a clock
+ * event both at a time. An accurate wall clock is more
+ * important than a high-precision interval timer so only
+ * use the timer as a clock source, and not a clock event
+ * if there's no I/O ASIC counter available to serve as a
+ * clock source.
+ */
+ if (!ioasic_clock) {
+ init_r4k_clocksource();
+ mips_hpt_frequency = 0;
+ }
+ }
+
+ ds1287_clockevent_init(dec_interrupt[DEC_IRQ_RTC]);
+}
diff --git a/arch/mips/dec/wbflush.c b/arch/mips/dec/wbflush.c
new file mode 100644
index 000000000..dad64d178
--- /dev/null
+++ b/arch/mips/dec/wbflush.c
@@ -0,0 +1,92 @@
+/*
+ * Setup the right wbflush routine for the different DECstations.
+ *
+ * Created with information from:
+ * DECstation 3100 Desktop Workstation Functional Specification
+ * DECstation 5000/200 KN02 System Module Functional Specification
+ * mipsel-linux-objdump --disassemble vmunix | grep "wbflush" :-)
+ *
+ * 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.
+ *
+ * Copyright (C) 1998 Harald Koerfgen
+ * Copyright (C) 2002 Maciej W. Rozycki
+ */
+
+#include <linux/export.h>
+#include <linux/init.h>
+
+#include <asm/bootinfo.h>
+#include <asm/wbflush.h>
+#include <asm/barrier.h>
+
+static void wbflush_kn01(void);
+static void wbflush_kn210(void);
+static void wbflush_mips(void);
+
+void (*__wbflush) (void);
+
+void __init wbflush_setup(void)
+{
+ switch (mips_machtype) {
+ case MACH_DS23100:
+ case MACH_DS5000_200: /* DS5000 3max */
+ __wbflush = wbflush_kn01;
+ break;
+ case MACH_DS5100: /* DS5100 MIPSMATE */
+ __wbflush = wbflush_kn210;
+ break;
+ case MACH_DS5000_1XX: /* DS5000/100 3min */
+ case MACH_DS5000_XX: /* Personal DS5000/2x */
+ case MACH_DS5000_2X0: /* DS5000/240 3max+ */
+ case MACH_DS5900: /* DS5900 bigmax */
+ default:
+ __wbflush = wbflush_mips;
+ break;
+ }
+}
+
+/*
+ * For the DS3100 and DS5000/200 the R2020/R3220 writeback buffer functions
+ * as part of Coprocessor 0.
+ */
+static void wbflush_kn01(void)
+{
+ asm(".set\tpush\n\t"
+ ".set\tnoreorder\n\t"
+ "1:\tbc0f\t1b\n\t"
+ "nop\n\t"
+ ".set\tpop");
+}
+
+/*
+ * For the DS5100 the writeback buffer seems to be a part of Coprocessor 3.
+ * But CP3 has to enabled first.
+ */
+static void wbflush_kn210(void)
+{
+ asm(".set\tpush\n\t"
+ ".set\tnoreorder\n\t"
+ "mfc0\t$2,$12\n\t"
+ "lui\t$3,0x8000\n\t"
+ "or\t$3,$2,$3\n\t"
+ "mtc0\t$3,$12\n\t"
+ "nop\n"
+ "1:\tbc3f\t1b\n\t"
+ "nop\n\t"
+ "mtc0\t$2,$12\n\t"
+ "nop\n\t"
+ ".set\tpop"
+ : : : "$2", "$3");
+}
+
+/*
+ * I/O ASIC systems use a standard writeback buffer that gets flushed
+ * upon an uncached read.
+ */
+static void wbflush_mips(void)
+{
+ __fast_iob();
+}
+EXPORT_SYMBOL(__wbflush);