/* * arch/arm/mach-tegra/mcerr.c * * MC error code common to T3x and T11x. T20 has been left alone. * * Copyright (c) 2010-2012, NVIDIA Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mcerr.h" void __iomem *mc = IO_ADDRESS(TEGRA_MC_BASE); #ifdef MC_DUAL_CHANNEL void __iomem *mc1 = IO_ADDRESS(TEGRA_MC1_BASE); #endif static int arb_intr_mma_set(const char *arg, const struct kernel_param *kp); static int arb_intr_mma_get(char *buff, const struct kernel_param *kp); static void unthrottle_prints(struct work_struct *work); #ifdef CONFIG_ARCH_TEGRA_11x_SOC static int spurious_mc1_intr; #endif static struct arb_emem_intr_info arb_intr_info = { .lock = __SPIN_LOCK_UNLOCKED(arb_intr_info.lock), }; static int arb_intr_count; static struct kernel_param_ops arb_intr_mma_ops = { .get = arb_intr_mma_get, .set = arb_intr_mma_set, }; module_param_cb(arb_intr_mma_in_ms, &arb_intr_mma_ops, &arb_intr_info.arb_intr_mma, S_IRUGO | S_IWUSR); module_param(arb_intr_count, int, S_IRUGO | S_IWUSR); #ifdef CONFIG_ARCH_TEGRA_11x_SOC module_param(spurious_mc1_intr, int, S_IRUGO | S_IWUSR); #endif static const char *const smmu_page_attrib[] = { "SMMU: nr-nw-s", "SMMU: nr-nw-ns", "SMMU: nr-wr-s", "SMMU: nr-wr-ns", "SMMU: rd-nw-s", "SMMU: rd-nw-ns", "SMMU: rd-wr-s", "SMMU: rd-wr-ns" }; static DEFINE_SPINLOCK(mc_lock); static unsigned long error_count; static DECLARE_DELAYED_WORK(unthrottle_prints_work, unthrottle_prints); static struct dentry *mcerr_debugfs_dir; /* * Chip specific functions. */ static struct mcerr_chip_specific chip_specific; static int arb_intr_mma_set(const char *arg, const struct kernel_param *kp) { int ret; unsigned long flags; spin_lock_irqsave(&arb_intr_info.lock, flags); ret = param_set_int(arg, kp); spin_unlock_irqrestore(&arb_intr_info.lock, flags); return ret; } static int arb_intr_mma_get(char *buff, const struct kernel_param *kp) { return param_get_int(buff, kp); } static void arb_intr(void) { u64 time; u32 time_diff_ms; unsigned long flags; spin_lock_irqsave(&arb_intr_info.lock, flags); arb_intr_count++; time = sched_clock(); time_diff_ms = (time - arb_intr_info.time) >> 20; arb_intr_info.time = time; arb_intr_info.arb_intr_mma = ((MMA_HISTORY_SAMPLES - 1) * time_diff_ms + arb_intr_info.arb_intr_mma) / MMA_HISTORY_SAMPLES; spin_unlock_irqrestore(&arb_intr_info.lock, flags); } static void unthrottle_prints(struct work_struct *work) { unsigned long flags; spin_lock_irqsave(&mc_lock, flags); error_count = 0; spin_unlock_irqrestore(&mc_lock, flags); } /* * Common T3x/T11x MC error handling code. */ static irqreturn_t tegra_mc_error_isr(int irq, void *data) { void __iomem *err_mc = mc; struct mc_client *client = NULL; const char *mc_type; const char *mc_info; unsigned long count; u32 addr, err, stat; u32 is_write, is_secure; u32 client_id; stat = readl(mc + MC_INT_STATUS) & MC_INT_EN_MASK; __cancel_delayed_work(&unthrottle_prints_work); #ifdef MC_DUAL_CHANNEL /* * Interrupts can come from either MC; handle the case in which the * interrupt is generated by the second MC. */ if (stat & MC_INT_EXT_INTR_IN) { err_mc = mc1; stat = readl(err_mc + MC_INT_STATUS); #ifdef CONFIG_ARCH_TEGRA_11x_SOC /* * It appears like the secondary MC occasionally generates a * spurious interrupt. If so, just ignore this interrupt. */ if (!stat) { spurious_mc1_intr++; goto out; } } #endif /* CONFIG_ARCH_TEGRA_11x_SOC */ #endif if (stat & MC_INT_ARBITRATION_EMEM) { arb_intr(); if (stat == MC_INT_ARBITRATION_EMEM) goto out; } spin_lock(&mc_lock); count = ++error_count; spin_unlock(&mc_lock); err = readl(err_mc + MC_ERROR_STATUS); addr = readl(err_mc + MC_ERROR_ADDRESS); is_write = err & (1 << 16); is_secure = err & (1 << 17); client_id = err & 0x7f; client = &mc_clients[client_id]; mc_type = chip_specific.mcerr_type(err); mc_info = chip_specific.mcerr_info(stat); chip_specific.mcerr_info_update(client, stat); if (count >= MAX_PRINTS) { schedule_delayed_work(&unthrottle_prints_work, HZ/2); if (count == MAX_PRINTS) pr_err("Too many MC errors; throttling prints\n"); goto out; } chip_specific.mcerr_print(mc_type, err, addr, client, is_secure, is_write, mc_info); out: writel(stat, err_mc + MC_INT_STATUS); if (err_mc != mc) { readl(err_mc + MC_INT_STATUS); writel(MC_INT_EXT_INTR_IN, mc + MC_INT_STATUS); } return IRQ_HANDLED; } /* * Read the type of error that occured. */ static const char *mcerr_default_type(u32 err) { u32 type = (err >> 28) & 0x7; u32 attrib; switch (type) { case MC_ERR_DECERR_EMEM: return "DECERR_EMEM"; case MC_ERR_SECURITY_TRUSTZONE: return "SECURITY_TRUSTZONE"; case MC_ERR_SECURITY_CARVEOUT: return "SECURITY_CARVEOUT"; case MC_ERR_INVALID_SMMU_PAGE: attrib = (err >> 25) & 7; return smmu_page_attrib[attrib]; default: return "BAD"; } } static const char *mcerr_default_info(u32 stat) { if (stat & MC_INT_DECERR_EMEM) return "MC_DECERR"; else if (stat & MC_INT_SECURITY_VIOLATION) return "MC_SECURITY_ERR"; else if (stat & MC_INT_INVALID_SMMU_PAGE) return "MC_SMMU_ERR"; else return "unknown"; } static void mcerr_default_info_update(struct mc_client *c, u32 stat) { if (stat & MC_INT_DECERR_EMEM) c->intr_counts[0]++; else if (stat & MC_INT_SECURITY_VIOLATION) c->intr_counts[1]++; else if (stat & MC_INT_INVALID_SMMU_PAGE) c->intr_counts[2]++; else c->intr_counts[3]++; } static void mcerr_default_print(const char *mc_err, u32 err, u32 addr, const struct mc_client *client, int is_secure, int is_write, const char *mc_err_info) { pr_err("%s (0x%08X): %p %s (%s %s %s)\n", mc_err, err, (void *)addr, (client) ? client->name : "unknown", (is_secure) ? "secure" : "non-secure", (is_write) ? "write" : "read", mc_err_info); } /* * Print the MC err stats for each client. */ static int mcerr_default_debugfs_show(struct seq_file *s, void *v) { int i, j; int do_print; seq_printf(s, "%-24s %-9s %-9s %-9s %-9s\n", "client", "decerr", "secerr", "smmuerr", "unknown"); for (i = 0; i < chip_specific.nr_clients; i++) { do_print = 0; /* Only print clients who actually have errors. */ for (j = 0; j < INTR_COUNT; j++) { if (mc_clients[i].intr_counts[j]) { do_print = 1; break; } } if (do_print) seq_printf(s, "%-24s %-9u %-9u %-9u %-9u\n", mc_clients[i].name, mc_clients[i].intr_counts[0], mc_clients[i].intr_counts[1], mc_clients[i].intr_counts[2], mc_clients[i].intr_counts[3]); } return 0; } static int mcerr_debugfs_open(struct inode *inode, struct file *file) { return single_open(file, chip_specific.mcerr_debugfs_show, NULL); } static const struct file_operations mcerr_debugfs_fops = { .open = mcerr_debugfs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int __init tegra_mcerr_init(void) { u32 reg; int ret = 0; reg = 0x0F7F1010; writel(reg, mc + MC_RESERVED_RSV); #if defined(CONFIG_TEGRA_MC_EARLY_ACK) reg = readl(mc + MC_EMEM_ARB_OVERRIDE); reg |= 3; #if defined(CONFIG_TEGRA_ERRATA_1157520) if (tegra_revision == TEGRA_REVISION_A01) reg &= ~2; #endif writel(reg, mc + MC_EMEM_ARB_OVERRIDE); #endif chip_specific.mcerr_type = mcerr_default_type; chip_specific.mcerr_info = mcerr_default_info; chip_specific.mcerr_info_update = mcerr_default_info_update; chip_specific.mcerr_print = mcerr_default_print; chip_specific.mcerr_debugfs_show = mcerr_default_debugfs_show; chip_specific.nr_clients = 0; /* * mcerr_chip_specific_setup() can override any of the default * functions as it wishes. */ mcerr_chip_specific_setup(&chip_specific); if (request_irq(INT_MC_GENERAL, tegra_mc_error_isr, 0, "mc_status", NULL)) { pr_err("%s: unable to register MC error interrupt\n", __func__); ret = -ENXIO; } else { reg = MC_INT_EN_MASK; writel(reg, mc + MC_INT_MASK); } /* * Init the debugfs node for reporting errors from the MC. If this * fails thats a shame, but not a big enough deal to warrent failing * the init of the MC itself. */ mcerr_debugfs_dir = debugfs_create_dir("mc", NULL); if (mcerr_debugfs_dir == NULL) { pr_err("Failed to make debugfs node: %ld\n", PTR_ERR(mcerr_debugfs_dir)); goto done; } debugfs_create_file("mcerr", 0644, mcerr_debugfs_dir, NULL, &mcerr_debugfs_fops); done: return ret; } arch_initcall(tegra_mcerr_init);