You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
448 lines
13 KiB
448 lines
13 KiB
/* Copyright (c) 2018, The Linux Foundation. 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 version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "GICT: %s(): " fmt, __func__
|
|
#include <linux/kernel.h>
|
|
#include <linux/edac.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/of_irq.h>
|
|
#include <soc/qcom/scm.h>
|
|
|
|
#include "edac_mc.h"
|
|
#include "edac_device.h"
|
|
|
|
#ifdef CONFIG_EDAC_GIC_PANIC_ON_CE
|
|
#define GICT_PANIC_ON_CE 1
|
|
#else
|
|
#define GICT_PANIC_ON_CE 0
|
|
#endif
|
|
|
|
#ifdef CONFIG_EDAC_GIC_PANIC_ON_UE
|
|
#define GICT_PANIC_ON_UE 1
|
|
#else
|
|
#define GICT_PANIC_ON_UE 0
|
|
#endif
|
|
|
|
#define EDAC_NODE "GICT"
|
|
#define RECORD_WIDTH(n) (64 * n)
|
|
#define GICT_ERR_FR(n) (RECORD_WIDTH(n) + 0x00)
|
|
#define GICT_ERR_CTRL(n) (RECORD_WIDTH(n) + 0x08)
|
|
#define GICT_ERR_STATUS(n) (RECORD_WIDTH(n) + 0x10)
|
|
#define GICT_ERR_ADDR(n) (RECORD_WIDTH(n) + 0x18)
|
|
#define GICT_ERR_MISC0(n) (RECORD_WIDTH(n) + 0x20)
|
|
#define GICT_ERR_MISC1(n) (RECORD_WIDTH(n) + 0x28)
|
|
#define GICT_ERRGSR 0xE000
|
|
#define GICT_ERRDEVARCH 0xFFBC
|
|
#define GICT_ERRIDR 0xFFC8
|
|
#define GICT_ERRIRQCR0 0xE800
|
|
#define GICT_ERRIRQCR1 (GICT_ERRIRQCR0+0x8)
|
|
|
|
#define GICD_FCTLR 0x0020
|
|
#define GICR_FCTLR 0x0020
|
|
#define GICR_OFFSET 0x20000
|
|
|
|
|
|
#define RECORD0 "Software error in GICD programming(UEO)"
|
|
#define RECORD1 "Correctable SPI RAM errors(CE)"
|
|
#define RECORD2 "Uncorrectable SPI RAM errors(UER)"
|
|
#define RECORD3 "Correctable SGI RAM errors(CE)"
|
|
#define RECORD4 "Uncorrectable SGI RAM errors(UER)"
|
|
#define RECORD5 "Correctable TGT cache errors(CE)"
|
|
#define RECORD6 "Correctable TGT cache errors(UER)"
|
|
#define RECORD7 "Correctable PPI RAM errors(CE)"
|
|
#define RECORD8 "Uncorrectable PPI RAM errors(UER)"
|
|
#define RECORD9 "Correctable LPI RAM errors(CE)"
|
|
#define RECORD10 "Uncorrectable LPI RAM errors(UER)"
|
|
#define RECORD11 "Correctable ITS RAM errors(CE)"
|
|
#define RECORD12 "Uncorrectable ITS RAM errors(UEO)"
|
|
#define RECORD13 "Uncorrectable 2xITS RAM errors(UER)"
|
|
#define RECORD14 "Uncorrectable 2xITS RAM errors(UER)"
|
|
#define RECORD(N) RECORD##N
|
|
|
|
#define ERR_FR_UI_MASK GENMASK(5, 4)
|
|
#define ERR_FR_FI_MASK GENMASK(7, 6)
|
|
#define ERR_FR_UE_MASK GENMASK(9, 8)
|
|
#define ERR_FR_CFI_MASK GENMASK(11, 10)
|
|
|
|
#define ERR_CTRL_UI_MASK BIT(2)
|
|
#define ERR_CTRL_FI_MASK BIT(3)
|
|
#define ERR_CTRL_UE_MASK BIT(4)
|
|
#define ERR_CTRL_CFI_MASK BIT(8)
|
|
|
|
#define ERR_STATUS_SERR_MASK GENMASK(7, 0)
|
|
#define ERR_STATUS_IERR_MASK GENMASK(15, 8)
|
|
#define ERR_STATUS_UET_MASK GENMASK(21, 20)
|
|
#define ERR_STATUS_CE_MASK GENMASK(25, 24)
|
|
#define ERR_STATUS_MV_MASK BIT(26)
|
|
#define ERR_STATUS_OF_MASK BIT(27)
|
|
#define ERR_STATUS_ER_MASK BIT(28)
|
|
#define ERR_STATUS_UE_MASK BIT(29)
|
|
#define ERR_STATUS_V_MASK BIT(30)
|
|
#define ERR_STATUS_AV_MASK BIT(31)
|
|
|
|
#define ERR_MISC0_COUNT_SHIFT 32
|
|
#define ERR_MISC0_COUNT_MASK GENMASK(39, 32)
|
|
#define FAULT_THRESHOLD 0xFF
|
|
|
|
#define ERR_IRQCR_SPIID_MASK GENMASK(9, 0)
|
|
|
|
#define ERR_ADDR_PADDR_MASK GENMASK(47, 0)
|
|
#define ERR_ADDR_NS_MASK BIT(63)
|
|
|
|
struct errors_edac {
|
|
const char *const msg;
|
|
void (*func)(struct edac_device_ctl_info *edac_dev,
|
|
int inst_nr, int block_nr, const char *msg);
|
|
};
|
|
#define GICT_IRQS 2
|
|
#define GICT_FAULT_IRQ_IDX 0
|
|
#define GICT_ERROR_IRQ_IDX 1
|
|
#define GICT_BUFFER_SIZE 32
|
|
static u32 gict_buffer[GICT_BUFFER_SIZE] __aligned(PAGE_SIZE);
|
|
|
|
static void gict_spi_recovery(struct edac_device_ctl_info *edac_dev,
|
|
int inst_nr, int block_nr, const char *msg);
|
|
|
|
static const struct errors_edac errors[] = {
|
|
{RECORD(0), edac_device_handle_ue },
|
|
{RECORD(1), edac_device_handle_ce },
|
|
{RECORD(2), gict_spi_recovery },
|
|
{RECORD(3), edac_device_handle_ce },
|
|
{RECORD(4), edac_device_handle_ue },
|
|
{RECORD(5), edac_device_handle_ce },
|
|
{RECORD(6), edac_device_handle_ce },
|
|
{RECORD(7), edac_device_handle_ce },
|
|
{RECORD(8), edac_device_handle_ue },
|
|
{RECORD(9), edac_device_handle_ce },
|
|
{RECORD(10), edac_device_handle_ue },
|
|
{RECORD(11), edac_device_handle_ce },
|
|
{RECORD(12), edac_device_handle_ue },
|
|
{RECORD(13), edac_device_handle_ue },
|
|
{RECORD(14), edac_device_handle_ue },
|
|
};
|
|
|
|
struct erp_drvdata {
|
|
struct edac_device_ctl_info *edev_ctl;
|
|
void __iomem *base;
|
|
u32 interrupt_config[GICT_IRQS];
|
|
u32 max_records;
|
|
struct mutex mutex;
|
|
};
|
|
static struct erp_drvdata *gict_edac;
|
|
|
|
static inline void gict_write(struct erp_drvdata *drv, u32 val, u32 off)
|
|
{
|
|
writel_relaxed(val, drv->base + off);
|
|
}
|
|
|
|
static inline u32 gict_read(struct erp_drvdata *drv, u32 off)
|
|
{
|
|
return readl_relaxed(drv->base + off);
|
|
}
|
|
|
|
static inline u64 gict_readq(struct erp_drvdata *drv, u32 off)
|
|
{
|
|
return readq_relaxed(drv->base + off);
|
|
}
|
|
|
|
static inline void gict_writeq(struct erp_drvdata *drv, u64 val, u32 off)
|
|
{
|
|
writeq_relaxed(val, drv->base + off);
|
|
}
|
|
|
|
#define for_each_bit(i, buf, max) \
|
|
for (i = find_first_bit((unsigned long *)buf, max); \
|
|
i < max; \
|
|
i = find_next_bit((unsigned long *)buf, max, i + 1))
|
|
|
|
#define SCM_SVC_GIC 0x1D
|
|
#define GIC_ERROR_RECOVERY_SMC_ID 0x01
|
|
#define MAX_IRQS 1023
|
|
/* RECORD(2) - Recovery of GIC SPI interrupts */
|
|
static void gict_spi_recovery(struct edac_device_ctl_info *edac_dev,
|
|
int inst_nr, int block_nr, const char *msg)
|
|
{
|
|
struct scm_desc desc;
|
|
u64 i;
|
|
int ret;
|
|
|
|
/*
|
|
* SPIs that are in the error state can be determined
|
|
* by reading the GICD_IERRRn register.
|
|
* Secure side reads GICD_IERRRn register into gict_buffer.
|
|
*/
|
|
memset(&gict_buffer, 0, sizeof(gict_buffer));
|
|
desc.args[0] = virt_to_phys(gict_buffer);
|
|
desc.args[1] = sizeof(gict_buffer);
|
|
desc.arginfo = SCM_ARGS(2, SCM_RW, SCM_VAL);
|
|
|
|
ret = scm_call2(SCM_SIP_FNID(SCM_SVC_GIC,
|
|
GIC_ERROR_RECOVERY_SMC_ID), &desc);
|
|
|
|
if (ret) {
|
|
pr_warn("Recovery SCM call failed\n");
|
|
return;
|
|
}
|
|
|
|
for_each_bit(i, gict_buffer, MAX_IRQS)
|
|
pr_warn("GICT: Corrupted SPI:%d", i);
|
|
}
|
|
|
|
static inline void gict_dump_err_record(int record, u64 errxstatus,
|
|
u64 errxmisc0, u64 errxmisc1, u64 errxaddr)
|
|
{
|
|
edac_printk(KERN_INFO, EDAC_NODE, "RECORD: %d", record);
|
|
edac_printk(KERN_INFO, EDAC_NODE, "%s\n", errors[record].msg);
|
|
edac_printk(KERN_INFO, EDAC_NODE, "ERRXSTATUS: %llx\n", errxstatus);
|
|
edac_printk(KERN_INFO, EDAC_NODE, "ERRXMISC0: %llx\n", errxmisc0);
|
|
edac_printk(KERN_INFO, EDAC_NODE, "ERRXMISC1: %llx\n", errxmisc1);
|
|
edac_printk(KERN_INFO, EDAC_NODE, "ERRXADDR: %llx\n", errxaddr);
|
|
}
|
|
|
|
static void handle_record(int record, int level,
|
|
struct edac_device_ctl_info *edev_ctl)
|
|
{
|
|
errors[record].func(edev_ctl, raw_smp_processor_id(),
|
|
level, errors[record].msg);
|
|
}
|
|
|
|
static void gict_check_error_records(struct erp_drvdata *drv)
|
|
{
|
|
u64 errxstatus, errxaddr, errxmisc0, errxmisc1, errgsr, i;
|
|
|
|
errgsr = gict_readq(drv, GICT_ERRGSR);
|
|
/*
|
|
* Lets first dump all error records
|
|
* details which are having a valid status.
|
|
*/
|
|
for_each_bit(i, &errgsr, drv->max_records) {
|
|
errxstatus = gict_readq(drv, GICT_ERR_STATUS(i));
|
|
if (errxstatus & ERR_STATUS_V_MASK) {
|
|
errxaddr = gict_readq(drv, GICT_ERR_ADDR(i));
|
|
errxmisc0 = gict_readq(drv, GICT_ERR_MISC0(i));
|
|
errxmisc1 = gict_readq(drv, GICT_ERR_MISC1(i));
|
|
gict_dump_err_record(i, errxstatus,
|
|
errxmisc0, errxmisc1, errxaddr);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void gict_handle_records(struct erp_drvdata *drv)
|
|
{
|
|
u64 errxstatus, errgsr, errxstatus_ack, i;
|
|
|
|
errgsr = gict_readq(drv, GICT_ERRGSR);
|
|
|
|
for_each_bit(i, &errgsr, drv->max_records) {
|
|
errxstatus = gict_readq(drv, GICT_ERR_STATUS(i));
|
|
errxstatus_ack = 0;
|
|
if (errxstatus & ERR_STATUS_V_MASK) {
|
|
handle_record(i, 0, drv->edev_ctl);
|
|
errxstatus_ack = ERR_STATUS_CE_MASK |
|
|
ERR_STATUS_UE_MASK | ERR_STATUS_UET_MASK |
|
|
ERR_STATUS_OF_MASK | ERR_STATUS_MV_MASK |
|
|
ERR_STATUS_AV_MASK | ERR_STATUS_V_MASK;
|
|
gict_write(drv, errxstatus_ack, GICT_ERR_STATUS(i));
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
static void configure_thresholds(struct erp_drvdata *drv)
|
|
{
|
|
u64 errxmisc0, i;
|
|
|
|
/*
|
|
* Configure ERRXMISC0 count to 0xFF so that fault
|
|
* interrupt raised on every error observed.
|
|
*/
|
|
for (i = 0; i <= drv->max_records; i++) {
|
|
errxmisc0 = gict_readq(drv, GICT_ERR_MISC0(i));
|
|
errxmisc0 |= (u64)FAULT_THRESHOLD << ERR_MISC0_COUNT_SHIFT;
|
|
gict_writeq(drv, errxmisc0, GICT_ERR_MISC0(i));
|
|
}
|
|
|
|
}
|
|
|
|
static irqreturn_t gict_fault_handler(int irq, void *drvdata)
|
|
{
|
|
struct erp_drvdata *drv = drvdata;
|
|
|
|
mutex_lock(&drv->mutex);
|
|
gict_check_error_records(drv);
|
|
gict_handle_records(drv);
|
|
configure_thresholds(drv);
|
|
mutex_unlock(&drv->mutex);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void configure_ctrl_registers(struct erp_drvdata *drv)
|
|
{
|
|
u64 errxfr, errxctrl, i;
|
|
|
|
configure_thresholds(drv);
|
|
|
|
/* Configure Control Register for CFI, UE, FI and UI faults/errors. */
|
|
for (i = 0; i <= drv->max_records; i++) {
|
|
errxfr = gict_readq(drv, GICT_ERR_FR(i));
|
|
errxctrl = gict_readq(drv, GICT_ERR_CTRL(i));
|
|
/* Configure to enable Correctable Fault Interrupt(CFI) */
|
|
if (errxfr & ERR_FR_CFI_MASK)
|
|
errxctrl = errxctrl | ERR_CTRL_CFI_MASK;
|
|
/* Configure to enable Uncorrectable Error(UE) */
|
|
if (errxfr & ERR_FR_UE_MASK)
|
|
errxctrl = errxctrl | ERR_CTRL_UE_MASK;
|
|
/* Configure to enable Uncorrectable Fault Interrupt(FI) */
|
|
if (errxfr & ERR_FR_FI_MASK)
|
|
errxctrl = errxctrl | ERR_CTRL_FI_MASK;
|
|
/* Configure to enable Uncorrectable Interrupt(UI) */
|
|
if (errxfr & ERR_FR_UI_MASK)
|
|
errxctrl = errxctrl | ERR_CTRL_UI_MASK;
|
|
|
|
gict_writeq(drv, errxctrl, GICT_ERR_CTRL(i));
|
|
}
|
|
}
|
|
|
|
static int gic_erp_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct erp_drvdata *drv;
|
|
struct resource *res;
|
|
int ret, errirq, faultirq;
|
|
u64 errirqcrx;
|
|
const char *err_irqname = "gict-err";
|
|
const char *fault_irqname = "gict-fault";
|
|
|
|
drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
|
|
if (!drv)
|
|
return -ENOMEM;
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gict-base");
|
|
if (!res)
|
|
return -ENXIO;
|
|
|
|
drv->base = devm_ioremap_resource(dev, res);
|
|
if (IS_ERR_OR_NULL(drv->base))
|
|
return -EBUSY;
|
|
|
|
ret = of_property_read_u32_array(dev->of_node,
|
|
"interrupt-config", drv->interrupt_config, GICT_IRQS);
|
|
if (ret)
|
|
return -ENXIO;
|
|
|
|
errirq = platform_get_irq_byname(pdev, err_irqname);
|
|
if (errirq < 0)
|
|
return errirq;
|
|
|
|
faultirq = platform_get_irq_byname(pdev, fault_irqname);
|
|
if (faultirq < 0)
|
|
return faultirq;
|
|
|
|
drv->edev_ctl = edac_device_alloc_ctl_info(0, "gic",
|
|
1, "T", 1, 1, NULL, 0,
|
|
edac_device_alloc_index());
|
|
|
|
if (!drv->edev_ctl)
|
|
return -ENOMEM;
|
|
|
|
drv->edev_ctl->dev = dev;
|
|
drv->edev_ctl->mod_name = dev_name(dev);
|
|
drv->edev_ctl->dev_name = dev_name(dev);
|
|
drv->edev_ctl->ctl_name = "GICT";
|
|
drv->edev_ctl->panic_on_ce = GICT_PANIC_ON_CE;
|
|
drv->edev_ctl->panic_on_ue = GICT_PANIC_ON_UE;
|
|
platform_set_drvdata(pdev, drv);
|
|
gict_edac = drv;
|
|
|
|
mutex_init(&drv->mutex);
|
|
ret = edac_device_add_device(drv->edev_ctl);
|
|
if (ret)
|
|
goto out_mem;
|
|
|
|
/*
|
|
* Find no of error records supported by GICT from ERRIDR register
|
|
* and configure control registers for all records supported.
|
|
*/
|
|
drv->max_records = gict_read(drv, GICT_ERRIDR);
|
|
configure_ctrl_registers(drv);
|
|
|
|
/* Configure GICT_ERRIRQCR0 register with fault_interrupt number */
|
|
errirqcrx = gict_readq(drv, GICT_ERRIRQCR0);
|
|
errirqcrx |= (drv->interrupt_config[GICT_FAULT_IRQ_IDX]
|
|
& ERR_IRQCR_SPIID_MASK);
|
|
gict_writeq(drv, errirqcrx, GICT_ERRIRQCR0);
|
|
|
|
/* Configure GICT_ERRIRQCR1 register with err_interrupt number */
|
|
errirqcrx = gict_readq(drv, GICT_ERRIRQCR1);
|
|
errirqcrx |= (drv->interrupt_config[GICT_ERROR_IRQ_IDX]
|
|
& ERR_IRQCR_SPIID_MASK);
|
|
gict_writeq(drv, errirqcrx, GICT_ERRIRQCR1);
|
|
|
|
ret = devm_request_threaded_irq(&pdev->dev, faultirq,
|
|
NULL, gict_fault_handler,
|
|
IRQF_ONESHOT | IRQF_TRIGGER_HIGH, fault_irqname, drv);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to request %s IRQ %d: %d\n",
|
|
fault_irqname, res->start, ret);
|
|
goto out_dev;
|
|
}
|
|
|
|
ret = devm_request_threaded_irq(&pdev->dev, errirq,
|
|
NULL, gict_fault_handler,
|
|
IRQF_ONESHOT | IRQF_TRIGGER_HIGH, err_irqname, drv);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to request %s IRQ %d: %d\n",
|
|
err_irqname, res->start, ret);
|
|
goto out_dev;
|
|
}
|
|
return ret;
|
|
|
|
out_dev:
|
|
edac_device_del_device(dev);
|
|
out_mem:
|
|
edac_device_free_ctl_info(drv->edev_ctl);
|
|
return ret;
|
|
}
|
|
|
|
static int gic_erp_remove(struct platform_device *pdev)
|
|
{
|
|
struct erp_drvdata *drv = dev_get_drvdata(&pdev->dev);
|
|
struct edac_device_ctl_info *edac_ctl = drv->edev_ctl;
|
|
|
|
edac_device_del_device(edac_ctl->dev);
|
|
edac_device_free_ctl_info(edac_ctl);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id gic_edac_match[] = {
|
|
{ .compatible = "arm,gic-600-erp", },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, gic_edac_match);
|
|
|
|
static struct platform_driver gic_edac_driver = {
|
|
.probe = gic_erp_probe,
|
|
.remove = gic_erp_remove,
|
|
.driver = {
|
|
.name = "gic_erp",
|
|
.of_match_table = of_match_ptr(gic_edac_match),
|
|
},
|
|
};
|
|
module_platform_driver(gic_edac_driver);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("GICT driver");
|
|
|