/* * Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #define BUS_ADDR_MASK 0x3fffffff #define BUS_ERROR_TYPE_MASK 0x3e0 #define BUS_ERROR_TYPE_SHIFT 5 static struct bridge_mca_error t18x_axi_errors[] = { {.desc = "Reserved"}, /* 0 */ {.desc = "Illegal TZ G0 security access"}, {.desc = "Illegal TZ G1 security access"}, {.desc = "Illegal TZ G2 security access"}, {.desc = "Illegal TZ G3 security access"}, {.desc = "Illegal TZ G4 security access"}, /* 5 */ {.desc = "Illegal TZ G5 security access"}, {.desc = "Illegal TZ G6 security access"}, {.desc = "Illegal TZ G7 security access"}, {.desc = "Illegal priviledged access"}, {.desc = "Address decode error generated by bridge"}, /* 10 */ {.desc = "Insecure access to a slave"}, {.desc = "Insecure master ID access"}, {.desc = "Unsupported FIXED access type"}, {.desc = "Unsupported WRAP access type"}, {.desc = "Unsupported INCR access type"}, /* 15 */ {.desc = "Unsupported burst size error"}, {.desc = "Unsupported alignment type error"}, {.desc = "Timeout error"}, {.desc = "Illegal BE access error"}, {.desc = "Slave error"}, /* 20 */ {.desc = "Slave decode error"}, {.desc = "Bridge error"}, }; static char *src_ids[] = { "Reserved", /* 0000 */ "CCPLEX", /* 0001 */ "CCPLEX-DPMU", /* 0010 */ "BPMP", /* 0011 */ "SPE", /* 0100 */ "CPE/SCE", /* 0101 */ "DMA_PER", /* 0110 */ "TSECA", /* 0111 */ "TSECB", /* 1000 */ "JTAGM", /* 1001 */ "CSITE", /* 1010 */ "APE", /* 1011 */ "Reserved", /* 1100 */ "Reserved", /* 1101 */ "Reserved", /* 1110 */ "Bridge", /* 1111 */ }; static char *dma_modes[] = { "APB Read/MC Write", "Memory to Memory Copy", "MC Read/APB Write", "Fixed Pattern" }; static LIST_HEAD(bridge_list); static DEFINE_RAW_SPINLOCK(bridge_lock); static void print_mca(struct seq_file *file, const char *fmt, ...) { va_list args; struct va_format vaf; va_start(args, fmt); if (file) { seq_vprintf(file, fmt, args); } else { vaf.fmt = fmt; vaf.va = &args; pr_crit("%pV", &vaf); } va_end(args); } static void print_cache(struct seq_file *file, u32 cache) { if ((cache & 0x3) == 0x0) { print_mca(file, "\tCache: 0x%x -- Strongly Ordered\n", cache); return; } if ((cache & 0x3) == 0x1) { print_mca(file, "\tCache: 0x%x -- Device\n", cache); return; } switch (cache) { case 0x2: print_mca(file, "\tCache: 0x%x -- Non-cacheable/Non-Bufferable\n", cache); break; case 0x3: print_mca(file, "\tCache: 0x%x -- Non-cacheable/Bufferable\n", cache); break; default: print_mca(file, "\tCache: 0x%x -- Cacheable\n", cache); } } static void print_prot(struct seq_file *file, u32 prot) { char *data_str; char *secure_str; char *priv_str; data_str = (prot & 0x4) ? "Instruction" : "Data"; secure_str = (prot & 0x2) ? "Non-Secure" : "Secure"; priv_str = (prot & 0x1) ? "Privileged" : "Unprivileged"; print_mca(file, "\tProtection: 0x%x -- %s, %s, %s Access\n", prot, priv_str, secure_str, data_str); } static void print_axi_id(struct seq_file *file, u32 src_id, u32 axi_id) { char *cp; switch (src_id) { case BRIDGE_SRCID_CCPLEX: case BRIDGE_SRCID_CCPLEX_DPMU: if (axi_id & 0x40) { print_mca(file, "\tAXI_ID: 0x%x -- DPMU\n", axi_id); } else { if (axi_id < 0x04) print_mca(file, "\tAXI_ID: 0x%x -- Denver Core %d\n", axi_id, axi_id); else if (axi_id < 0x08) print_mca(file, "\tAXI_ID: 0x%x -- A57 Core %d\n", axi_id, axi_id - 0x4); else print_mca(file, "\tAXI_ID: 0x%x -- Pool B\n", axi_id); } break; case BRIDGE_SRCID_BPMP: switch ((axi_id >> 3) & 0x7) { case 0x0: cp = "DMAD2NIC"; break; case 0x1: cp = "CPUD2NIC"; break; case 0x2: cp = "CPU2NIC"; break; case 0x6: cp = "CVC2NIC"; break; default: cp = "Unknown"; break; } print_mca(file, "\tAXI_ID: 0x%x -- Submaster ID: %s, ID: %d\n", axi_id, cp, axi_id & 0x7); break; case BRIDGE_SRCID_CPE_SCE: case BRIDGE_SRCID_SPE: switch ((axi_id >> 4) & 0x3) { case 0x0: cp = "DMAD2NIC"; break; case 0x2: cp = "CPUD2NIC"; break; default: cp = "Unknown"; break; } print_mca(file, "\tAXI_ID: 0x%x -- Submaster ID: %s, ID: %d\n", axi_id, cp, axi_id & 0xf); break; case BRIDGE_SRCID_APE: print_mca(file, "\tAXI_ID: 0x%x -- %s Access, ID: 0x%x\n", axi_id, (axi_id & 0x40) ? "ADMA" : "ADSP", axi_id & 0x3f); break; case BRIDGE_SRCID_DMA_PER: cp = dma_modes[(axi_id >> 5) & 0x3]; print_mca(file, "\tAXI_ID: 0x%x -- DMA Mode = %s, Channel ID = 0x%x\n", axi_id, cp, axi_id & 0x1f); break; case BRIDGE_SRCID_CSITE: if (axi_id & 0x1) { print_mca(file, "\tAXI_ID: 0x%x -- CSITE\n", axi_id); } else { switch (axi_id) { case 0x00: print_mca(file, "\tAXI_ID: 0x%x -- MC Port 0\n", axi_id); break; case 0x02: print_mca(file, "\tAXI_ID: 0x%x -- MC Port 1\n", axi_id); break; default: print_mca(file, "\tAXI_ID: 0x%x\n", axi_id); } } break; default: print_mca(file, "\tAXI_ID: 0x%x\n", axi_id); } } static void print_bank_info(struct seq_file *file, struct bridge_mca_bank *bank) { int count = 0; u32 bus_addr; u32 bus_status; u32 error_type; u32 id; u32 len; u32 prot; u32 axi_id; u32 src_id; u32 cache; u32 burst; u64 addr; struct resource *res = NULL; while (bank->error_fifo_count(bank->vaddr)) { bus_status = bank->error_status(bank->vaddr); bus_addr = bank->error_status(bank->vaddr); if (((bus_status >> 16) & 0xffff) == 0xdead && ((bus_addr >> 16) & 0xffff) == 0xdead) break; if (count > 0) print_mca(file, "======================================\n"); addr = get_bridge_addr(bus_addr); burst = get_bridge_burst(bus_addr); id = get_bridge_id(bus_status); error_type = get_bridge_err_type(bus_status); len = get_bridge_len(bus_status); prot = get_bridge_prot(bus_status); axi_id = get_bridge_axi_id(bus_status); src_id = get_bridge_src_id(bus_status); cache = get_bridge_cache(bus_status); print_mca(file, "Raw FIFO Entry: %d\n", count); print_mca(file, "\tADDR: 0x%x\n", bus_addr); print_mca(file, "\tSTAT: 0x%x\n", bus_status); print_mca(file, "--------------------------------------\n"); print_mca(file, "Decoded FIFO Entry: %d\n", count); print_mca(file, "\tDirection: %s\n", bus_status & BRIDGE_RW ? "READ" : "WRITE"); print_mca(file, "\tBridge ID: 0x%x\n", id); print_mca(file, "\tError Type: 0x%x -- %s\n", error_type, (error_type >= bank->max_error ? "Unknown" : bank->errors[error_type].desc)); print_mca(file, "\tLength: %d\n", len); print_prot(file, prot); print_mca(file, "\tSource ID: 0x%x -- %s\n", src_id, src_ids[src_id]); print_axi_id(file, src_id, axi_id); print_cache(file, cache); print_mca(file, "\tBurst: 0x%x\n", burst); res = locate_resource(&iomem_resource, addr); if (res == NULL) print_mca(file, "\tAddress: 0x%llx (Unknown Device)\n", addr); else print_mca(file, "\tAddress: 0x%llx -- %s + 0x%llx\n", addr, res->name, addr - res->start); count += 1; } } static void print_bank(struct bridge_mca_bank *bank) { if (bank->error_fifo_count(bank->vaddr) == 0) return; pr_crit("**************************************\n"); pr_crit("CPU%d Machine check error in %s@0x%llx:\n", smp_processor_id(), bank->name, bank->bank); print_bank_info(NULL, bank); pr_crit("**************************************\n"); } static int bridge_serr_hook(struct pt_regs *regs, int reason, unsigned int esr, void *priv) { struct bridge_mca_bank *bank = priv; int retval = 1; if (!bank->seen_error && bank->error_fifo_count(bank->vaddr)) { print_bank(bank); bank->seen_error = 1; retval = 0; } return retval; } #ifdef CONFIG_DEBUG_FS static DEFINE_MUTEX(bridge_mca_mutex); static struct dentry *bridge_timeout_dir; static struct dentry *bridge_timeout_node; static int created_root = false; static int bridge_mca_show(struct seq_file *file, void *data) { struct bridge_mca_bank *bank; mutex_lock(&bridge_mca_mutex); list_for_each_entry(bank, &bridge_list, node) { print_mca(file, "Bridge %s@0x%llx:\n", bank->name, bank->bank); if (bank->error_fifo_count(bank->vaddr) == 0x0) { print_mca(file, "\tNo Errors\n"); continue; } print_bank_info(file, bank); } mutex_unlock(&bridge_mca_mutex); return 0; } static int bridge_mca_open(struct inode *inode, struct file *file) { return single_open(file, bridge_mca_show, inode->i_private); } static const struct file_operations bridge_mca_fops = { .open = bridge_mca_open, .read = seq_read, .llseek = seq_lseek, .release = single_release }; static int bridge_timeout_show(struct seq_file *s, void *unused) { struct device *dev = s->private; struct resource *res_base; struct bridge_mca_bank *bank; unsigned long flags; struct platform_device *pdev; if (WARN_ON(!dev)) return -EINVAL; pdev = to_platform_device(dev); res_base = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res_base) { dev_err(&pdev->dev, "Could not find base address"); return -ENOENT; } raw_spin_lock_irqsave(&bridge_lock, flags); list_for_each_entry(bank, &bridge_list, node) { if (bank->bank == res_base->start) { seq_printf(s, "timeout = %u\n", bank->timeout); break; } } raw_spin_unlock_irqrestore(&bridge_lock, flags); return 0; } static ssize_t bridge_timeout_set(struct file *file, const char __user *addr, size_t len, loff_t *pos) { struct seq_file *m = file->private_data; struct device *dev = m ? m->private : NULL; struct resource *res_base; struct bridge_mca_bank *bank; unsigned long flags; struct platform_device *pdev; u32 timeout; int ret; if (WARN_ON(!dev)) return -EINVAL; pdev = to_platform_device(dev); ret = kstrtouint_from_user(addr, len, 10, &timeout); if (ret < 0) return ret; if (timeout > 100000) { dev_err(&pdev->dev, "bad value of timeout = %u\n", timeout); return -EINVAL; } res_base = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res_base) { dev_err(&pdev->dev, "Could not find base address"); return -ENOENT; } raw_spin_lock_irqsave(&bridge_lock, flags); list_for_each_entry(bank, &bridge_list, node) { if (bank->bank == res_base->start) break; } raw_spin_unlock_irqrestore(&bridge_lock, flags); bank->setup_timeout(bank->vaddr, timeout, &pdev->dev); bank->timeout = timeout; return len; } static int bridge_timeout_open(struct inode *inode, struct file *file) { return single_open(file, bridge_timeout_show, inode->i_private); } static const struct file_operations timeout_fops = { .open = bridge_timeout_open, .read = seq_read, .write = bridge_timeout_set, .llseek = seq_lseek, .release = single_release }; static int bridge_timeout_dbgfs_init(struct bridge_mca_bank *bank, struct device *dev) { char devname[50]; snprintf(devname, sizeof(devname), "%s@0x%llx", bank->name, bank->bank); bridge_timeout_dir = debugfs_create_dir(devname, NULL); if (!bridge_timeout_dir) { dev_err(dev, "Failed to create bridge timeout debugfs dir"); return -ENODEV; } bridge_timeout_node = debugfs_create_file("timeout", S_IRUGO, bridge_timeout_dir, dev, &timeout_fops); if (!bridge_timeout_node) { dev_err(dev, "Failed to create brige_timeout_node"); return -ENODEV; } return 0; } static int bridge_mca_dbgfs_init(void) { struct dentry *d; if (!created_root) { d = debugfs_create_file("tegra_bridge_mca", S_IRUGO, NULL, NULL, &bridge_mca_fops); if (IS_ERR_OR_NULL(d)) { pr_err("%s: could not create 'tegra_bridge_mca' node\n", __func__); return PTR_ERR(d); } created_root = true; } return 0; } #else static int bridge_mca_dbgfs_init(void) { return 0; } static int bridge_timeout_dbgfs_init(struct bridge_mca_bank *bank, struct device *dev) { return 0; } #endif #define AXIAPB_TIMEOUT_TIMER 0x2c8 #define AXI2APB_ERROR_STATUS 0x2ec #define AXI2APB_FIFO_STATUS3 0x2f8 #define AXI2APB_FIFO_ERROR_SHIFT 13 #define AXI2APB_FIFO_ERROR_MASK 0x1f static unsigned int axi2apb_error_status(void __iomem *addr) { unsigned int error_status; error_status = readl(addr+AXI2APB_ERROR_STATUS); writel(0xFFFFFFFF, addr+AXI2APB_ERROR_STATUS); return error_status; } static unsigned int axi2apb_error_fifo_count(void __iomem *addr) { unsigned int fifo_status; fifo_status = readl(addr+AXI2APB_FIFO_STATUS3); fifo_status >>= AXI2APB_FIFO_ERROR_SHIFT; fifo_status &= AXI2APB_FIFO_ERROR_MASK; return fifo_status; } static unsigned int axi2apb_setup_timeout(void __iomem *addr, u32 timeout, struct device *dev) { unsigned long rate; struct clk *clk; u32 value = 0; clk = devm_clk_get(dev, "axi_cbb"); if (IS_ERR(clk)) { dev_info(dev, "can not get axi_cbb clock\n"); return PTR_ERR(clk); } rate = clk_get_rate(clk); /*get rate in MHz*/ rate = rate / 1000000; dev_info(dev, "axi_cbb clk rate = %lu MHZ, timeout = %u useconds\n", rate, timeout); /*get timeout val for programming timer reg*/ if (rate < 0x1000 && timeout < 0x100000 && rate > 0 && timeout > 0) { value = (u32)(rate * timeout); /* set timeout. By default, timeout config is enabled*/ writel(value, addr + AXIAPB_TIMEOUT_TIMER); dev_info(dev, "enabled timeout = %u\n", value); } else dev_err(dev, "error enabling timeout = %u: rate=%lu, timeout=%u\n", value, rate, timeout); return 0; } static struct tegra_bridge_data axi2apb_data = { .name = "AXI2APB", .error_status = axi2apb_error_status, .error_fifo_count = axi2apb_error_fifo_count, .setup_timeout = axi2apb_setup_timeout, .errors = t18x_axi_errors, .max_error = ARRAY_SIZE(t18x_axi_errors), .timeout = 97000 /* Default value of timer */ }; #define AXIP2P_SLV_RD_RESP_TIMER 0xf8 #define AXIP2P_SLV_WR_RESP_TIMER 0xfc #define AXIP2P_TIMEOUT_CONFIG 0x104 #define AXIP2P_TIMEOUT_CONFIG_EN 0x00000001 #define AXIP2P_ERROR_STATUS 0x11c #define AXIP2P_FIFO_STATUS 0x120 #define AXIP2P_FIFO_ERROR_SHIFT 26 #define AXIP2P_FIFO_ERROR_MASK 0x3f static unsigned int axip2p_error_status(void __iomem *addr) { unsigned int error_status; error_status = readl(addr+AXIP2P_ERROR_STATUS); writel(0xFFFFFFFF, addr+AXIP2P_ERROR_STATUS); return error_status; } static unsigned int axip2p_error_fifo_count(void __iomem *addr) { unsigned int fifo_status; fifo_status = readl(addr+AXIP2P_FIFO_STATUS); fifo_status >>= AXIP2P_FIFO_ERROR_SHIFT; fifo_status &= AXIP2P_FIFO_ERROR_MASK; return fifo_status; } static unsigned int axip2p_setup_timeout(void __iomem *addr, u32 timeout, struct device *dev) { unsigned long rate; struct clk *clk; u32 value = 0, tc_value = 0; clk = devm_clk_get(dev, "axi_cbb"); if (IS_ERR(clk)) { dev_info(dev, "can not get axi_cbb clock\n"); return PTR_ERR(clk); } rate = clk_get_rate(clk); /*get rate in MHz*/ rate = rate / 1000000; dev_info(dev, "axi_cbb clk rate = %lu MHZ, timeout = %u useconds\n", rate, timeout); /*get timeout val for programming timer reg*/ if (rate < 0x1000 && timeout < 0x100000 && rate > 0 && timeout > 0) { value = (u32)(rate * timeout); /*Program timeout*/ writel(value, addr + AXIP2P_SLV_RD_RESP_TIMER); writel(value, addr + AXIP2P_SLV_WR_RESP_TIMER); /* Enable timeout */ tc_value = readl(addr + AXIP2P_TIMEOUT_CONFIG); writel(tc_value | AXIP2P_TIMEOUT_CONFIG_EN, addr + AXIP2P_TIMEOUT_CONFIG); dev_info(dev, "enabled timeout = %u\n", value); } else dev_err(dev, "error enabling timeout = %u: rate=%lu, timeout=%u\n", value, rate, timeout); return 0; } static struct tegra_bridge_data axip2p_data = { .name = "AXIP2P", .error_status = axip2p_error_status, .error_fifo_count = axip2p_error_fifo_count, .setup_timeout = axip2p_setup_timeout, .errors = t18x_axi_errors, .max_error = ARRAY_SIZE(t18x_axi_errors), .timeout = 97000 /* Default value of timer */ }; static struct of_device_id tegra18_bridge_match[] = { {.compatible = "nvidia,tegra186-AXI2APB-bridge", .data = &axi2apb_data}, {.compatible = "nvidia,tegra186-AXIP2P-bridge", .data = &axip2p_data}, {}, }; MODULE_DEVICE_TABLE(of, tegra18_bridge_match); static int tegra18_bridge_probe(struct platform_device *pdev) { struct resource *res_base; struct bridge_mca_bank *bank; const struct tegra_bridge_data *bdata; struct serr_hook *hook; const struct of_device_id *match; unsigned long flags; int rc; u32 timeout; /* * Bridges don't exist on the simulator */ if (tegra_cpu_is_asim()) return 0; match = of_match_device(of_match_ptr(tegra18_bridge_match), &pdev->dev); if (!match) { dev_err(&pdev->dev, "No device match found"); return -ENODEV; } bdata = match->data; res_base = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res_base) { dev_err(&pdev->dev, "Could not find base address"); return -ENOENT; } rc = bridge_mca_dbgfs_init(); if (rc) return rc; bank = devm_kzalloc(&pdev->dev, sizeof(*bank), GFP_KERNEL); bank->bank = res_base->start; bank->vaddr = devm_ioremap_resource(&pdev->dev, res_base); if(IS_ERR(bank->vaddr)) return -EPERM; bank->name = bdata->name; bank->error_status = bdata->error_status; bank->error_fifo_count = bdata->error_fifo_count; bank->errors = bdata->errors; bank->max_error = bdata->max_error; bank->setup_timeout = bdata->setup_timeout; bank->seen_error = 0; bank->timeout = bdata->timeout; rc = bridge_timeout_dbgfs_init(bank, &pdev->dev); if (rc) return rc; hook = devm_kzalloc(&pdev->dev, sizeof(*hook), GFP_KERNEL); hook->fn = bridge_serr_hook; hook->priv = bank; bank->hook = hook; raw_spin_lock_irqsave(&bridge_lock, flags); list_add(&bank->node, &bridge_list); raw_spin_unlock_irqrestore(&bridge_lock, flags); register_serr_hook(hook); /* * Flush out (and report) any early bridge errors. */ print_bank_info(NULL, bank); if (of_property_read_u32(pdev->dev.of_node, "timeout", &timeout) == 0) { if (timeout > 0 && timeout < 100000) { bank->setup_timeout(bank->vaddr, timeout, &pdev->dev); bank->timeout = timeout; } else dev_err(&pdev->dev, "error setting up timeout = %u\n", timeout); } dev_info(&pdev->dev, "bridge probed OK\n"); return 0; } static int tegra18_bridge_remove(struct platform_device *pdev) { struct resource *res_base; struct bridge_mca_bank *bank; unsigned long flags; res_base = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res_base) return 0; raw_spin_lock_irqsave(&bridge_lock, flags); list_for_each_entry(bank, &bridge_list, node) { if (bank->bank == res_base->start) { unregister_serr_hook(bank->hook); list_del(&bank->node); break; } } raw_spin_unlock_irqrestore(&bridge_lock, flags); return 0; } static struct platform_driver platform_driver = { .probe = tegra18_bridge_probe, .remove = tegra18_bridge_remove, .driver = { .owner = THIS_MODULE, .name = "tegra18-bridge", .of_match_table = of_match_ptr(tegra18_bridge_match), }, }; module_platform_driver(platform_driver); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Bridge Machine Check / SError handler");