162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *	Bus error event handling code for DECstation/DECsystem 3100
462306a36Sopenharmony_ci *	and 2100 (KN01) systems equipped with parity error detection
562306a36Sopenharmony_ci *	logic.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci *	Copyright (c) 2005  Maciej W. Rozycki
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/init.h>
1162306a36Sopenharmony_ci#include <linux/interrupt.h>
1262306a36Sopenharmony_ci#include <linux/kernel.h>
1362306a36Sopenharmony_ci#include <linux/spinlock.h>
1462306a36Sopenharmony_ci#include <linux/types.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <asm/inst.h>
1762306a36Sopenharmony_ci#include <asm/irq_regs.h>
1862306a36Sopenharmony_ci#include <asm/mipsregs.h>
1962306a36Sopenharmony_ci#include <asm/page.h>
2062306a36Sopenharmony_ci#include <asm/ptrace.h>
2162306a36Sopenharmony_ci#include <asm/traps.h>
2262306a36Sopenharmony_ci#include <linux/uaccess.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#include <asm/dec/kn01.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci/* CP0 hazard avoidance. */
2862306a36Sopenharmony_ci#define BARRIER				\
2962306a36Sopenharmony_ci	__asm__ __volatile__(		\
3062306a36Sopenharmony_ci		".set	push\n\t"	\
3162306a36Sopenharmony_ci		".set	noreorder\n\t"	\
3262306a36Sopenharmony_ci		"nop\n\t"		\
3362306a36Sopenharmony_ci		".set	pop\n\t")
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci/*
3662306a36Sopenharmony_ci * Bits 7:0 of the Control Register are write-only -- the
3762306a36Sopenharmony_ci * corresponding bits of the Status Register have a different
3862306a36Sopenharmony_ci * meaning.  Hence we use a cache.  It speeds up things a bit
3962306a36Sopenharmony_ci * as well.
4062306a36Sopenharmony_ci *
4162306a36Sopenharmony_ci * There is no default value -- it has to be initialized.
4262306a36Sopenharmony_ci */
4362306a36Sopenharmony_ciu16 cached_kn01_csr;
4462306a36Sopenharmony_cistatic DEFINE_RAW_SPINLOCK(kn01_lock);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistatic inline void dec_kn01_be_ack(void)
4862306a36Sopenharmony_ci{
4962306a36Sopenharmony_ci	volatile u16 *csr = (void *)CKSEG1ADDR(KN01_SLOT_BASE + KN01_CSR);
5062306a36Sopenharmony_ci	unsigned long flags;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	raw_spin_lock_irqsave(&kn01_lock, flags);
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	*csr = cached_kn01_csr | KN01_CSR_MEMERR;	/* Clear bus IRQ. */
5562306a36Sopenharmony_ci	iob();
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(&kn01_lock, flags);
5862306a36Sopenharmony_ci}
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic int dec_kn01_be_backend(struct pt_regs *regs, int is_fixup, int invoker)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	volatile u32 *kn01_erraddr = (void *)CKSEG1ADDR(KN01_SLOT_BASE +
6362306a36Sopenharmony_ci							KN01_ERRADDR);
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	static const char excstr[] = "exception";
6662306a36Sopenharmony_ci	static const char intstr[] = "interrupt";
6762306a36Sopenharmony_ci	static const char cpustr[] = "CPU";
6862306a36Sopenharmony_ci	static const char mreadstr[] = "memory read";
6962306a36Sopenharmony_ci	static const char readstr[] = "read";
7062306a36Sopenharmony_ci	static const char writestr[] = "write";
7162306a36Sopenharmony_ci	static const char timestr[] = "timeout";
7262306a36Sopenharmony_ci	static const char paritystr[] = "parity error";
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	int data = regs->cp0_cause & 4;
7562306a36Sopenharmony_ci	unsigned int __user *pc = (unsigned int __user *)regs->cp0_epc +
7662306a36Sopenharmony_ci				  ((regs->cp0_cause & CAUSEF_BD) != 0);
7762306a36Sopenharmony_ci	union mips_instruction insn;
7862306a36Sopenharmony_ci	unsigned long entrylo, offset;
7962306a36Sopenharmony_ci	long asid, entryhi, vaddr;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	const char *kind, *agent, *cycle, *event;
8262306a36Sopenharmony_ci	unsigned long address;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	u32 erraddr = *kn01_erraddr;
8562306a36Sopenharmony_ci	int action = MIPS_BE_FATAL;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	/* Ack ASAP, so that any subsequent errors get caught. */
8862306a36Sopenharmony_ci	dec_kn01_be_ack();
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	kind = invoker ? intstr : excstr;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	agent = cpustr;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	if (invoker)
9562306a36Sopenharmony_ci		address = erraddr;
9662306a36Sopenharmony_ci	else {
9762306a36Sopenharmony_ci		/* Bloody hardware doesn't record the address for reads... */
9862306a36Sopenharmony_ci		if (data) {
9962306a36Sopenharmony_ci			/* This never faults. */
10062306a36Sopenharmony_ci			__get_user(insn.word, pc);
10162306a36Sopenharmony_ci			vaddr = regs->regs[insn.i_format.rs] +
10262306a36Sopenharmony_ci				insn.i_format.simmediate;
10362306a36Sopenharmony_ci		} else
10462306a36Sopenharmony_ci			vaddr = (long)pc;
10562306a36Sopenharmony_ci		if (KSEGX(vaddr) == CKSEG0 || KSEGX(vaddr) == CKSEG1)
10662306a36Sopenharmony_ci			address = CPHYSADDR(vaddr);
10762306a36Sopenharmony_ci		else {
10862306a36Sopenharmony_ci			/* Peek at what physical address the CPU used. */
10962306a36Sopenharmony_ci			asid = read_c0_entryhi();
11062306a36Sopenharmony_ci			entryhi = asid & (PAGE_SIZE - 1);
11162306a36Sopenharmony_ci			entryhi |= vaddr & ~(PAGE_SIZE - 1);
11262306a36Sopenharmony_ci			write_c0_entryhi(entryhi);
11362306a36Sopenharmony_ci			BARRIER;
11462306a36Sopenharmony_ci			tlb_probe();
11562306a36Sopenharmony_ci			/* No need to check for presence. */
11662306a36Sopenharmony_ci			tlb_read();
11762306a36Sopenharmony_ci			entrylo = read_c0_entrylo0();
11862306a36Sopenharmony_ci			write_c0_entryhi(asid);
11962306a36Sopenharmony_ci			offset = vaddr & (PAGE_SIZE - 1);
12062306a36Sopenharmony_ci			address = (entrylo & ~(PAGE_SIZE - 1)) | offset;
12162306a36Sopenharmony_ci		}
12262306a36Sopenharmony_ci	}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	/* Treat low 256MB as memory, high -- as I/O. */
12562306a36Sopenharmony_ci	if (address < 0x10000000) {
12662306a36Sopenharmony_ci		cycle = mreadstr;
12762306a36Sopenharmony_ci		event = paritystr;
12862306a36Sopenharmony_ci	} else {
12962306a36Sopenharmony_ci		cycle = invoker ? writestr : readstr;
13062306a36Sopenharmony_ci		event = timestr;
13162306a36Sopenharmony_ci	}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	if (is_fixup)
13462306a36Sopenharmony_ci		action = MIPS_BE_FIXUP;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	if (action != MIPS_BE_FIXUP)
13762306a36Sopenharmony_ci		printk(KERN_ALERT "Bus error %s: %s %s %s at %#010lx\n",
13862306a36Sopenharmony_ci			kind, agent, cycle, event, address);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	return action;
14162306a36Sopenharmony_ci}
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ciint dec_kn01_be_handler(struct pt_regs *regs, int is_fixup)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	return dec_kn01_be_backend(regs, is_fixup, 0);
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ciirqreturn_t dec_kn01_be_interrupt(int irq, void *dev_id)
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	volatile u16 *csr = (void *)CKSEG1ADDR(KN01_SLOT_BASE + KN01_CSR);
15162306a36Sopenharmony_ci	struct pt_regs *regs = get_irq_regs();
15262306a36Sopenharmony_ci	int action;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	if (!(*csr & KN01_CSR_MEMERR))
15562306a36Sopenharmony_ci		return IRQ_NONE;		/* Must have been video. */
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	action = dec_kn01_be_backend(regs, 0, 1);
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	if (action == MIPS_BE_DISCARD)
16062306a36Sopenharmony_ci		return IRQ_HANDLED;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	/*
16362306a36Sopenharmony_ci	 * FIXME: Find the affected processes and kill them, otherwise
16462306a36Sopenharmony_ci	 * we must die.
16562306a36Sopenharmony_ci	 *
16662306a36Sopenharmony_ci	 * The interrupt is asynchronously delivered thus EPC and RA
16762306a36Sopenharmony_ci	 * may be irrelevant, but are printed for a reference.
16862306a36Sopenharmony_ci	 */
16962306a36Sopenharmony_ci	printk(KERN_ALERT "Fatal bus interrupt, epc == %08lx, ra == %08lx\n",
17062306a36Sopenharmony_ci	       regs->cp0_epc, regs->regs[31]);
17162306a36Sopenharmony_ci	die("Unrecoverable bus error", regs);
17262306a36Sopenharmony_ci}
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_civoid __init dec_kn01_be_init(void)
17662306a36Sopenharmony_ci{
17762306a36Sopenharmony_ci	volatile u16 *csr = (void *)CKSEG1ADDR(KN01_SLOT_BASE + KN01_CSR);
17862306a36Sopenharmony_ci	unsigned long flags;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	raw_spin_lock_irqsave(&kn01_lock, flags);
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	/* Preset write-only bits of the Control Register cache. */
18362306a36Sopenharmony_ci	cached_kn01_csr = *csr;
18462306a36Sopenharmony_ci	cached_kn01_csr &= KN01_CSR_STATUS | KN01_CSR_PARDIS | KN01_CSR_TXDIS;
18562306a36Sopenharmony_ci	cached_kn01_csr |= KN01_CSR_LEDS;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	/* Enable parity error detection. */
18862306a36Sopenharmony_ci	cached_kn01_csr &= ~KN01_CSR_PARDIS;
18962306a36Sopenharmony_ci	*csr = cached_kn01_csr;
19062306a36Sopenharmony_ci	iob();
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(&kn01_lock, flags);
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	/* Clear any leftover errors from the firmware. */
19562306a36Sopenharmony_ci	dec_kn01_be_ack();
19662306a36Sopenharmony_ci}
197