162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2002,2003 Broadcom Corporation 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci/* 762306a36Sopenharmony_ci * The Bus Watcher monitors internal bus transactions and maintains 862306a36Sopenharmony_ci * counts of transactions with error status, logging details and 962306a36Sopenharmony_ci * causing one of several interrupts. This driver provides a handler 1062306a36Sopenharmony_ci * for those interrupts which aggregates the counts (to avoid 1162306a36Sopenharmony_ci * saturating the 8-bit counters) and provides a presence in 1262306a36Sopenharmony_ci * /proc/bus_watcher if PROC_FS is on. 1362306a36Sopenharmony_ci */ 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include <linux/init.h> 1662306a36Sopenharmony_ci#include <linux/kernel.h> 1762306a36Sopenharmony_ci#include <linux/interrupt.h> 1862306a36Sopenharmony_ci#include <linux/sched.h> 1962306a36Sopenharmony_ci#include <linux/proc_fs.h> 2062306a36Sopenharmony_ci#include <linux/seq_file.h> 2162306a36Sopenharmony_ci#include <asm/io.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#include <asm/sibyte/sb1250.h> 2462306a36Sopenharmony_ci#include <asm/sibyte/sb1250_regs.h> 2562306a36Sopenharmony_ci#include <asm/sibyte/sb1250_int.h> 2662306a36Sopenharmony_ci#include <asm/sibyte/sb1250_scd.h> 2762306a36Sopenharmony_ci#ifdef CONFIG_SIBYTE_BCM1x80 2862306a36Sopenharmony_ci#include <asm/sibyte/bcm1480_regs.h> 2962306a36Sopenharmony_ci#endif 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistruct bw_stats_struct { 3362306a36Sopenharmony_ci uint64_t status; 3462306a36Sopenharmony_ci uint32_t l2_err; 3562306a36Sopenharmony_ci uint32_t memio_err; 3662306a36Sopenharmony_ci int status_printed; 3762306a36Sopenharmony_ci unsigned long l2_cor_d; 3862306a36Sopenharmony_ci unsigned long l2_bad_d; 3962306a36Sopenharmony_ci unsigned long l2_cor_t; 4062306a36Sopenharmony_ci unsigned long l2_bad_t; 4162306a36Sopenharmony_ci unsigned long mem_cor_d; 4262306a36Sopenharmony_ci unsigned long mem_bad_d; 4362306a36Sopenharmony_ci unsigned long bus_error; 4462306a36Sopenharmony_ci} bw_stats; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_cistatic void print_summary(uint32_t status, uint32_t l2_err, 4862306a36Sopenharmony_ci uint32_t memio_err) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci printk("Bus watcher error counters: %08x %08x\n", l2_err, memio_err); 5162306a36Sopenharmony_ci printk("\nLast recorded signature:\n"); 5262306a36Sopenharmony_ci printk("Request %02x from %d, answered by %d with Dcode %d\n", 5362306a36Sopenharmony_ci (unsigned int)(G_SCD_BERR_TID(status) & 0x3f), 5462306a36Sopenharmony_ci (int)(G_SCD_BERR_TID(status) >> 6), 5562306a36Sopenharmony_ci (int)G_SCD_BERR_RID(status), 5662306a36Sopenharmony_ci (int)G_SCD_BERR_DCODE(status)); 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci/* 6062306a36Sopenharmony_ci * check_bus_watcher is exported for use in situations where we want 6162306a36Sopenharmony_ci * to see the most recent status of the bus watcher, which might have 6262306a36Sopenharmony_ci * already been destructively read out of the registers. 6362306a36Sopenharmony_ci * 6462306a36Sopenharmony_ci * notes: this is currently used by the cache error handler 6562306a36Sopenharmony_ci * should provide locking against the interrupt handler 6662306a36Sopenharmony_ci */ 6762306a36Sopenharmony_civoid check_bus_watcher(void) 6862306a36Sopenharmony_ci{ 6962306a36Sopenharmony_ci u32 status, l2_err, memio_err; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci#if defined(CONFIG_SIBYTE_BCM112X) || defined(CONFIG_SIBYTE_SB1250) 7262306a36Sopenharmony_ci /* Use non-destructive register */ 7362306a36Sopenharmony_ci status = csr_in32(IOADDR(A_SCD_BUS_ERR_STATUS_DEBUG)); 7462306a36Sopenharmony_ci#elif defined(CONFIG_SIBYTE_BCM1x80) 7562306a36Sopenharmony_ci /* Use non-destructive register */ 7662306a36Sopenharmony_ci /* Same as 1250 except BUS_ERR_STATUS_DEBUG is in a different place. */ 7762306a36Sopenharmony_ci status = csr_in32(IOADDR(A_BCM1480_BUS_ERR_STATUS_DEBUG)); 7862306a36Sopenharmony_ci#else 7962306a36Sopenharmony_ci#error bus watcher being built for unknown Sibyte SOC! 8062306a36Sopenharmony_ci#endif 8162306a36Sopenharmony_ci if (!(status & 0x7fffffff)) { 8262306a36Sopenharmony_ci printk("Using last values reaped by bus watcher driver\n"); 8362306a36Sopenharmony_ci status = bw_stats.status; 8462306a36Sopenharmony_ci l2_err = bw_stats.l2_err; 8562306a36Sopenharmony_ci memio_err = bw_stats.memio_err; 8662306a36Sopenharmony_ci } else { 8762306a36Sopenharmony_ci l2_err = csr_in32(IOADDR(A_BUS_L2_ERRORS)); 8862306a36Sopenharmony_ci memio_err = csr_in32(IOADDR(A_BUS_MEM_IO_ERRORS)); 8962306a36Sopenharmony_ci } 9062306a36Sopenharmony_ci if (status & ~(1UL << 31)) 9162306a36Sopenharmony_ci print_summary(status, l2_err, memio_err); 9262306a36Sopenharmony_ci else 9362306a36Sopenharmony_ci printk("Bus watcher indicates no error\n"); 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci#ifdef CONFIG_PROC_FS 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci/* For simplicity, I want to assume a single read is required each 9962306a36Sopenharmony_ci time */ 10062306a36Sopenharmony_cistatic int bw_proc_show(struct seq_file *m, void *v) 10162306a36Sopenharmony_ci{ 10262306a36Sopenharmony_ci struct bw_stats_struct *stats = m->private; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci seq_puts(m, "SiByte Bus Watcher statistics\n"); 10562306a36Sopenharmony_ci seq_puts(m, "-----------------------------\n"); 10662306a36Sopenharmony_ci seq_printf(m, "L2-d-cor %8ld\nL2-d-bad %8ld\n", 10762306a36Sopenharmony_ci stats->l2_cor_d, stats->l2_bad_d); 10862306a36Sopenharmony_ci seq_printf(m, "L2-t-cor %8ld\nL2-t-bad %8ld\n", 10962306a36Sopenharmony_ci stats->l2_cor_t, stats->l2_bad_t); 11062306a36Sopenharmony_ci seq_printf(m, "MC-d-cor %8ld\nMC-d-bad %8ld\n", 11162306a36Sopenharmony_ci stats->mem_cor_d, stats->mem_bad_d); 11262306a36Sopenharmony_ci seq_printf(m, "IO-err %8ld\n", stats->bus_error); 11362306a36Sopenharmony_ci seq_puts(m, "\nLast recorded signature:\n"); 11462306a36Sopenharmony_ci seq_printf(m, "Request %02x from %d, answered by %d with Dcode %d\n", 11562306a36Sopenharmony_ci (unsigned int)(G_SCD_BERR_TID(stats->status) & 0x3f), 11662306a36Sopenharmony_ci (int)(G_SCD_BERR_TID(stats->status) >> 6), 11762306a36Sopenharmony_ci (int)G_SCD_BERR_RID(stats->status), 11862306a36Sopenharmony_ci (int)G_SCD_BERR_DCODE(stats->status)); 11962306a36Sopenharmony_ci /* XXXKW indicate multiple errors between printings, or stats 12062306a36Sopenharmony_ci collection (or both)? */ 12162306a36Sopenharmony_ci if (stats->status & M_SCD_BERR_MULTERRS) 12262306a36Sopenharmony_ci seq_puts(m, "Multiple errors observed since last check.\n"); 12362306a36Sopenharmony_ci if (stats->status_printed) { 12462306a36Sopenharmony_ci seq_puts(m, "(no change since last printing)\n"); 12562306a36Sopenharmony_ci } else { 12662306a36Sopenharmony_ci stats->status_printed = 1; 12762306a36Sopenharmony_ci } 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci return 0; 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_cistatic void create_proc_decoder(struct bw_stats_struct *stats) 13362306a36Sopenharmony_ci{ 13462306a36Sopenharmony_ci struct proc_dir_entry *ent; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci ent = proc_create_single_data("bus_watcher", S_IWUSR | S_IRUGO, NULL, 13762306a36Sopenharmony_ci bw_proc_show, stats); 13862306a36Sopenharmony_ci if (!ent) { 13962306a36Sopenharmony_ci printk(KERN_INFO "Unable to initialize bus_watcher /proc entry\n"); 14062306a36Sopenharmony_ci return; 14162306a36Sopenharmony_ci } 14262306a36Sopenharmony_ci} 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci#endif /* CONFIG_PROC_FS */ 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci/* 14762306a36Sopenharmony_ci * sibyte_bw_int - handle bus watcher interrupts and accumulate counts 14862306a36Sopenharmony_ci * 14962306a36Sopenharmony_ci * notes: possible re-entry due to multiple sources 15062306a36Sopenharmony_ci * should check/indicate saturation 15162306a36Sopenharmony_ci */ 15262306a36Sopenharmony_cistatic irqreturn_t sibyte_bw_int(int irq, void *data) 15362306a36Sopenharmony_ci{ 15462306a36Sopenharmony_ci struct bw_stats_struct *stats = data; 15562306a36Sopenharmony_ci unsigned long cntr; 15662306a36Sopenharmony_ci#ifdef CONFIG_SIBYTE_BW_TRACE 15762306a36Sopenharmony_ci int i; 15862306a36Sopenharmony_ci#endif 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci#ifdef CONFIG_SIBYTE_BW_TRACE 16162306a36Sopenharmony_ci csr_out32(M_SCD_TRACE_CFG_FREEZE, IOADDR(A_SCD_TRACE_CFG)); 16262306a36Sopenharmony_ci csr_out32(M_SCD_TRACE_CFG_START_READ, IOADDR(A_SCD_TRACE_CFG)); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci for (i=0; i<256*6; i++) 16562306a36Sopenharmony_ci printk("%016llx\n", 16662306a36Sopenharmony_ci (long long)__raw_readq(IOADDR(A_SCD_TRACE_READ))); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci csr_out32(M_SCD_TRACE_CFG_RESET, IOADDR(A_SCD_TRACE_CFG)); 16962306a36Sopenharmony_ci csr_out32(M_SCD_TRACE_CFG_START, IOADDR(A_SCD_TRACE_CFG)); 17062306a36Sopenharmony_ci#endif 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci /* Destructive read, clears register and interrupt */ 17362306a36Sopenharmony_ci stats->status = csr_in32(IOADDR(A_SCD_BUS_ERR_STATUS)); 17462306a36Sopenharmony_ci stats->status_printed = 0; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci stats->l2_err = cntr = csr_in32(IOADDR(A_BUS_L2_ERRORS)); 17762306a36Sopenharmony_ci stats->l2_cor_d += G_SCD_L2ECC_CORR_D(cntr); 17862306a36Sopenharmony_ci stats->l2_bad_d += G_SCD_L2ECC_BAD_D(cntr); 17962306a36Sopenharmony_ci stats->l2_cor_t += G_SCD_L2ECC_CORR_T(cntr); 18062306a36Sopenharmony_ci stats->l2_bad_t += G_SCD_L2ECC_BAD_T(cntr); 18162306a36Sopenharmony_ci csr_out32(0, IOADDR(A_BUS_L2_ERRORS)); 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci stats->memio_err = cntr = csr_in32(IOADDR(A_BUS_MEM_IO_ERRORS)); 18462306a36Sopenharmony_ci stats->mem_cor_d += G_SCD_MEM_ECC_CORR(cntr); 18562306a36Sopenharmony_ci stats->mem_bad_d += G_SCD_MEM_ECC_BAD(cntr); 18662306a36Sopenharmony_ci stats->bus_error += G_SCD_MEM_BUSERR(cntr); 18762306a36Sopenharmony_ci csr_out32(0, IOADDR(A_BUS_MEM_IO_ERRORS)); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci return IRQ_HANDLED; 19062306a36Sopenharmony_ci} 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ciint __init sibyte_bus_watcher(void) 19362306a36Sopenharmony_ci{ 19462306a36Sopenharmony_ci memset(&bw_stats, 0, sizeof(struct bw_stats_struct)); 19562306a36Sopenharmony_ci bw_stats.status_printed = 1; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci if (request_irq(K_INT_BAD_ECC, sibyte_bw_int, 0, "Bus watcher", &bw_stats)) { 19862306a36Sopenharmony_ci printk("Failed to register bus watcher BAD_ECC irq\n"); 19962306a36Sopenharmony_ci return -1; 20062306a36Sopenharmony_ci } 20162306a36Sopenharmony_ci if (request_irq(K_INT_COR_ECC, sibyte_bw_int, 0, "Bus watcher", &bw_stats)) { 20262306a36Sopenharmony_ci free_irq(K_INT_BAD_ECC, &bw_stats); 20362306a36Sopenharmony_ci printk("Failed to register bus watcher COR_ECC irq\n"); 20462306a36Sopenharmony_ci return -1; 20562306a36Sopenharmony_ci } 20662306a36Sopenharmony_ci if (request_irq(K_INT_IO_BUS, sibyte_bw_int, 0, "Bus watcher", &bw_stats)) { 20762306a36Sopenharmony_ci free_irq(K_INT_BAD_ECC, &bw_stats); 20862306a36Sopenharmony_ci free_irq(K_INT_COR_ECC, &bw_stats); 20962306a36Sopenharmony_ci printk("Failed to register bus watcher IO_BUS irq\n"); 21062306a36Sopenharmony_ci return -1; 21162306a36Sopenharmony_ci } 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci#ifdef CONFIG_PROC_FS 21462306a36Sopenharmony_ci create_proc_decoder(&bw_stats); 21562306a36Sopenharmony_ci#endif 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci#ifdef CONFIG_SIBYTE_BW_TRACE 21862306a36Sopenharmony_ci csr_out32((M_SCD_TRSEQ_ASAMPLE | M_SCD_TRSEQ_DSAMPLE | 21962306a36Sopenharmony_ci K_SCD_TRSEQ_TRIGGER_ALL), 22062306a36Sopenharmony_ci IOADDR(A_SCD_TRACE_SEQUENCE_0)); 22162306a36Sopenharmony_ci csr_out32(M_SCD_TRACE_CFG_RESET, IOADDR(A_SCD_TRACE_CFG)); 22262306a36Sopenharmony_ci csr_out32(M_SCD_TRACE_CFG_START, IOADDR(A_SCD_TRACE_CFG)); 22362306a36Sopenharmony_ci#endif 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci return 0; 22662306a36Sopenharmony_ci} 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_cidevice_initcall(sibyte_bus_watcher); 229