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