tegrakernel/kernel/nvidia/drivers/platform/tegra/bridge_mca.c

802 lines
19 KiB
C

/*
* 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 <asm/traps.h>
#include <linux/clk.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/platform/tegra/bridge_mca.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <soc/tegra/chip-id.h>
#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");