From patchwork Sat Jan 13 00:50:26 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kyle Yan X-Patchwork-Id: 10161955 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 283C860327 for ; Sat, 13 Jan 2018 00:50:52 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 0865A2897A for ; Sat, 13 Jan 2018 00:50:52 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id F0291289A8; Sat, 13 Jan 2018 00:50:51 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-4.2 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from bombadil.infradead.org (bombadil.infradead.org [65.50.211.133]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id DD86C2897A for ; Sat, 13 Jan 2018 00:50:50 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:MIME-Version:Cc:List-Subscribe: List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id:Message-Id:Date: Subject:To:From:Reply-To:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To: References:List-Owner; bh=6vczBZJcenDS0b+oFtMQHvrmmOB7ee4ZOa0hvRazpsY=; b=sgd ga2r6lOo6vLJyTgtR6DVtyVWqiFq/Y0ovsF361MQdutD8ioFYCE89ZxIw7rVpwHpLZ3UHXgNIXjn9 X0jvU0YqlRj8InGPAm3Kyu+vhYc+ADfAn1JLJyli0SSa22DtOcEI3m9hbxJjVUUFBPrULwuKBbRCn AhTrO4tiXwYd5BTGLfn720eTHd9icYg7IAXJwugSjt4MR6Uv7BFJNWLGlIA2GQOeiUcUTnqruxrJG puIW8SrfcaEWbcpbrBTszAuIRl/K+6r1j5F5U3cOcnEOfSbuyb2i4MU4PrUEvqmvAIwaTYxCYuk2q 6UeY4IWLcHDctBcMh9y3U0AKkxk/juA==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.89 #1 (Red Hat Linux)) id 1eaA22-00085x-P7; Sat, 13 Jan 2018 00:50:46 +0000 Received: from smtp.codeaurora.org ([198.145.29.96]) by bombadil.infradead.org with esmtps (Exim 4.89 #1 (Red Hat Linux)) id 1eaA1y-00084N-JZ for linux-arm-kernel@lists.infradead.org; Sat, 13 Jan 2018 00:50:44 +0000 Received: by smtp.codeaurora.org (Postfix, from userid 1000) id CE35860BE5; Sat, 13 Jan 2018 00:50:30 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=codeaurora.org; s=default; t=1515804630; bh=kjUCJglbraHEq3DfCDAQyXERVEWZjzZTKGrfl+EZYSQ=; h=From:To:Cc:Subject:Date:From; b=NQ3h5saCFlUITABpQf8y8FBUS/D2bYFiXakNNQGDMOmIPQdT3BfTgc3HZuZsCfbI4 SqeJokMEEkwEsFHkGOmnbiAaQn3d6XvdlWkIJ8CxKRAg6RXaWatalslP5H4Jscxx++ CdPpsh4QjibdGOMczl1AkSno6zG9O5CoRwLOkfGI= Received: from kyan-linux.qualcomm.com (i-global254.qualcomm.com [199.106.103.254]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-SHA256 (128/128 bits)) (No client certificate requested) (Authenticated sender: kyan@smtp.codeaurora.org) by smtp.codeaurora.org (Postfix) with ESMTPSA id BACFD607A4; Sat, 13 Jan 2018 00:50:29 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=codeaurora.org; s=default; t=1515804630; bh=kjUCJglbraHEq3DfCDAQyXERVEWZjzZTKGrfl+EZYSQ=; h=From:To:Cc:Subject:Date:From; b=NQ3h5saCFlUITABpQf8y8FBUS/D2bYFiXakNNQGDMOmIPQdT3BfTgc3HZuZsCfbI4 SqeJokMEEkwEsFHkGOmnbiAaQn3d6XvdlWkIJ8CxKRAg6RXaWatalslP5H4Jscxx++ CdPpsh4QjibdGOMczl1AkSno6zG9O5CoRwLOkfGI= DMARC-Filter: OpenDMARC Filter v1.3.2 smtp.codeaurora.org BACFD607A4 Authentication-Results: pdx-caf-mail.web.codeaurora.org; dmarc=none (p=none dis=none) header.from=codeaurora.org Authentication-Results: pdx-caf-mail.web.codeaurora.org; spf=none smtp.mailfrom=kyan@codeaurora.org From: Kyle Yan To: bp@alien8.de, mchehab@kernel.org Subject: [PATCH v1] EDAC, armv8: Add Cache Error Reporting driver for ARMv8 processors Date: Fri, 12 Jan 2018 16:50:26 -0800 Message-Id: <1515804626-21254-1-git-send-email-kyan@codeaurora.org> X-Mailer: git-send-email 1.9.1 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20180112_165042_746147_B702B6EF X-CRM114-Status: GOOD ( 21.54 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kyle Yan , linux-arm-kernel@lists.infradead.org, linux-edac@vger.kernel.org MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Virus-Scanned: ClamAV using ClamSMTP Interrupt based EDAC driver for ARMv8 processors that implement RAS for error detection of CPU caches and lso allows optional polling of error syndrome registers if interrupts are not supported. Signed-off-by: Kyle Yan --- drivers/edac/Kconfig | 21 ++ drivers/edac/Makefile | 1 + drivers/edac/armv8_edac.c | 489 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 511 insertions(+) create mode 100644 drivers/edac/armv8_edac.c diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig index 96afb2a..47a68e3 100644 --- a/drivers/edac/Kconfig +++ b/drivers/edac/Kconfig @@ -457,4 +457,25 @@ config EDAC_XGENE Support for error detection and correction on the APM X-Gene family of SOCs. +config EDAC_ARMV8 + depends on (ARM || ARM64) + tristate "ARMv8 L1/L2/L3/SCU Caches ECC" + help + Support for error detection and correction on ARMv8 cores + supporting RAS features. Reports errors caught by ARMv8 + ECC mechanism. + For debugging issues having to do with stability and overall system + health, you should probably say 'Y' here. + +config EDAC_ARMV8_POLL + depends on EDAC_ARMV8 + bool "Poll on ARMv8 ECC registers" + help + This option chooses whether or not you want to poll on the Kryo3xx + ECC registers. When this is enabled, the polling rate can be set as + a module parameter. By default, it will call the polling function + every second. + This option should only be used if the associated interrupt lines + are not enabled. + endif # EDAC diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile index 0fd9ffa..57113ba 100644 --- a/drivers/edac/Makefile +++ b/drivers/edac/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_EDAC_IE31200) += ie31200_edac.o obj-$(CONFIG_EDAC_X38) += x38_edac.o obj-$(CONFIG_EDAC_I82860) += i82860_edac.o obj-$(CONFIG_EDAC_R82600) += r82600_edac.o +obj-$(CONFIG_EDAC_ARMV8) += armv8_edac.o amd64_edac_mod-y := amd64_edac.o amd64_edac_mod-$(CONFIG_EDAC_DEBUG) += amd64_edac_dbg.o diff --git a/drivers/edac/armv8_edac.c b/drivers/edac/armv8_edac.c new file mode 100644 index 0000000..d986c47 --- /dev/null +++ b/drivers/edac/armv8_edac.c @@ -0,0 +1,489 @@ +/* Copyright (c) 2016-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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "edac_mc.h" +#include "edac_device.h" + +static int poll_msec = 1000; +module_param(poll_msec, int, 0444); + +static bool panic_on_ue = 0; +module_param_named(panic_on_ue, panic_on_ue, bool, 0664); + +#define L1 0x0 +#define L2 0x1 +#define L3 0x2 + +#define EDAC_CPU "armv8_edac" + +#define ERRXSTATUS_VALID(a) ((a >> 30) & 0x1) +#define ERRXSTATUS_UE(a) ((a >> 29) & 0x1) +#define ERRXSTATUS_SERR(a) (a & 0xFF) + +#define ERRXMISC_LVL(a) ((a >> 1) & 0x7) +#define ERRXMISC_WAY(a) ((a >> 28) & 0xF) + +#define ERRXCTLR_ENABLE 0x10f +#define ERRXMISC_OVERFLOW 0x7F7F00000000ULL + +static inline void set_errxctlr_el1(void) +{ + asm volatile("msr s3_0_c5_c4_1, %0" : : "r" (ERRXCTLR_ENABLE)); +} + +static inline void set_errxmisc_overflow(void) +{ + asm volatile("msr s3_0_c5_c5_0, %0" : : "r" (ERRXMISC_OVERFLOW)); +} + +static inline void write_errselr_el1(u64 val) +{ + asm volatile("msr s3_0_c5_c3_1, %0" : : "r" (val)); +} + +static inline u64 read_errxstatus_el1(void) +{ + u64 val; + + asm volatile("mrs %0, s3_0_c5_c4_2" : "=r" (val)); + return val; +} + +static inline u64 read_errxmisc_el1(void) +{ + u64 val; + + asm volatile("mrs %0, s3_0_c5_c5_0" : "=r" (val)); + return val; +} + +static inline void clear_errxstatus_valid(u64 val) +{ + asm volatile("msr s3_0_c5_c4_2, %0" : : "r" (val)); +} + +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); +}; + +static const struct errors_edac errors[] = { + { "L1 Correctable Error", edac_device_handle_ce }, + { "L1 Uncorrectable Error", edac_device_handle_ue }, + { "L2 Correctable Error", edac_device_handle_ce }, + { "L2 Uncorrectable Error", edac_device_handle_ue }, + { "L3 Correctable Error", edac_device_handle_ce }, + { "L3 Uncorrectable Error", edac_device_handle_ue }, +}; + +#define L1_CE 0 +#define L1_UE 1 +#define L2_CE 2 +#define L2_UE 3 +#define L3_CE 4 +#define L3_UE 5 + +#define DATA_BUF_ERR 0x2 +#define CACHE_DATA_ERR 0x6 +#define CACHE_TAG_DIRTY_ERR 0x7 +#define TLB_PARITY_ERR_DATA 0x8 +#define TLB_PARITY_ERR_TAG 0x9 +#define BUS_ERROR 0x12 + +struct erp_drvdata { + struct edac_device_ctl_info *edev_ctl; + struct erp_drvdata __percpu **erp_cpu_drvdata; + struct notifier_block nb_pm; + int ppi; +}; + +static struct erp_drvdata *panic_handler_drvdata; + +static DEFINE_SPINLOCK(armv8_edac_lock); + +static void l1_l2_irq_enable(void *info) +{ + int irq = *(int *)info; + + enable_percpu_irq(irq, IRQ_TYPE_LEVEL_HIGH); +} + +static int request_erp_irq(struct platform_device *pdev, const char *propname, + const char *desc, irq_handler_t handler, + void *ed, int percpu) +{ + int rc; + struct resource *r; + struct erp_drvdata *drv = ed; + + r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, propname); + + if (!r) { + pr_err("ARMv8 CPU ERP: Could not find <%s> IRQ property. Proceeding anyway.\n", + propname); + goto out; + } + + if (!percpu) { + rc = devm_request_threaded_irq(&pdev->dev, r->start, NULL, + handler, + IRQF_ONESHOT | IRQF_TRIGGER_HIGH, + desc, + ed); + + if (rc) { + pr_err("ARMv8 CPU ERP: Failed to request IRQ %d: %d (%s / %s). Proceeding anyway.\n", + (int) r->start, rc, propname, desc); + goto out; + } + + } else { + drv->erp_cpu_drvdata = alloc_percpu(struct erp_drvdata *); + if (!drv->erp_cpu_drvdata) { + pr_err("Failed to allocate percpu erp data\n"); + goto out; + } + + *raw_cpu_ptr(drv->erp_cpu_drvdata) = drv; + rc = request_percpu_irq(r->start, handler, desc, + drv->erp_cpu_drvdata); + + if (rc) { + pr_err("ARMv8 CPU ERP: Failed to request IRQ %d: %d (%s / %s). Proceeding anyway.\n", + (int) r->start, rc, propname, desc); + goto out_free; + } + + drv->ppi = r->start; + on_each_cpu(l1_l2_irq_enable, &(r->start), 1); + } + + return 0; + +out_free: + free_percpu(drv->erp_cpu_drvdata); + drv->erp_cpu_drvdata = NULL; +out: + return 1; +} + +static void dump_err_reg(int errorcode, int level, u64 errxstatus, u64 errxmisc, + struct edac_device_ctl_info *edev_ctl) +{ + edac_printk(KERN_CRIT, EDAC_CPU, "ERRXSTATUS_EL1: %llx\n", errxstatus); + edac_printk(KERN_CRIT, EDAC_CPU, "ERRXMISC_EL1: %llx\n", errxmisc); + edac_printk(KERN_CRIT, EDAC_CPU, "Cache level: L%d\n", level+1); + + switch (ERRXSTATUS_SERR(errxstatus)) { + case DATA_BUF_ERR: + edac_printk(KERN_CRIT, EDAC_CPU, "ECC Error from internal data buffer\n"); + break; + + case CACHE_DATA_ERR: + edac_printk(KERN_CRIT, EDAC_CPU, "ECC Error from cache data RAM\n"); + break; + + case CACHE_TAG_DIRTY_ERR: + edac_printk(KERN_CRIT, EDAC_CPU, "ECC Error from cache tag or dirty RAM\n"); + break; + + case TLB_PARITY_ERR_DATA: + edac_printk(KERN_CRIT, EDAC_CPU, "Parity error on TLB DATA RAM\n"); + break; + + case TLB_PARITY_ERR_TAG: + edac_printk(KERN_CRIT, EDAC_CPU, "Parity error on TLB TAG RAM\n"); + break; + + case BUS_ERROR: + edac_printk(KERN_CRIT, EDAC_CPU, "Bus Error\n"); + break; + } + + if (level == L3) + edac_printk(KERN_CRIT, EDAC_CPU, + "Way: %d\n", (int) ERRXMISC_WAY(errxmisc)); + else + edac_printk(KERN_CRIT, EDAC_CPU, + "Way: %d\n", (int) ERRXMISC_WAY(errxmisc) >> 2); + + edev_ctl->panic_on_ue = panic_on_ue; + errors[errorcode].func(edev_ctl, smp_processor_id(), + level, errors[errorcode].msg); +} + +static void armv8_parse_l1_l2_cache_error(u64 errxstatus, u64 errxmisc, + struct edac_device_ctl_info *edev_ctl) +{ + switch (ERRXMISC_LVL(errxmisc)) { + case L1: + if (ERRXSTATUS_UE(errxstatus)) + dump_err_reg(L1_UE, L1, errxstatus, errxmisc, + edev_ctl); + else + dump_err_reg(L1_CE, L1, errxstatus, errxmisc, + edev_ctl); + break; + case L2: + if (ERRXSTATUS_UE(errxstatus)) + dump_err_reg(L2_UE, L2, errxstatus, errxmisc, + edev_ctl); + else + dump_err_reg(L2_CE, L2, errxstatus, errxmisc, + edev_ctl); + break; + default: + edac_printk(KERN_CRIT, EDAC_CPU, "Unknown ERRXMISC_LVL value\n"); + } +} + +static bool armv8_check_l1_l2_ecc(void *info) +{ + struct edac_device_ctl_info *edev_ctl = info; + u64 errxstatus; + u64 errxmisc; + unsigned long flags; + + spin_lock_irqsave(&armv8_edac_lock, flags); + write_errselr_el1(0); + errxstatus = read_errxstatus_el1(); + + if (ERRXSTATUS_VALID(errxstatus)) { + errxmisc = read_errxmisc_el1(); + edac_printk(KERN_CRIT, EDAC_CPU, + "CPU%d detected a L1/L2 cache error\n", + smp_processor_id()); + + armv8_parse_l1_l2_cache_error(errxstatus, errxmisc, edev_ctl); + clear_errxstatus_valid(errxstatus); + spin_unlock_irqrestore(&armv8_edac_lock, flags); + return true; + } + spin_unlock_irqrestore(&armv8_edac_lock, flags); + return false; +} + +static bool armv8_check_l3_scu_error(struct edac_device_ctl_info *edev_ctl) +{ + u64 errxstatus = 0; + u64 errxmisc = 0; + unsigned long flags; + + spin_lock_irqsave(&armv8_edac_lock, flags); + write_errselr_el1(1); + errxstatus = read_errxstatus_el1(); + errxmisc = read_errxmisc_el1(); + + if (ERRXSTATUS_VALID(errxstatus) && + ERRXMISC_LVL(errxmisc) == L3) { + if (ERRXSTATUS_UE(errxstatus)) { + edac_printk(KERN_CRIT, EDAC_CPU, "Detected L3 uncorrectable error\n"); + dump_err_reg(L3_UE, L3, errxstatus, errxmisc, + edev_ctl); + } else { + edac_printk(KERN_CRIT, EDAC_CPU, "Detected L3 correctable error\n"); + dump_err_reg(L3_CE, L3, errxstatus, errxmisc, + edev_ctl); + } + + clear_errxstatus_valid(errxstatus); + spin_unlock_irqrestore(&armv8_edac_lock, flags); + return true; + } + spin_unlock_irqrestore(&armv8_edac_lock, flags); + return false; +} + +static void armv8_check_l1_l2_ecc_helper(void *info) +{ + armv8_check_l1_l2_ecc(info); +} + +void armv8_poll_cache_errors(struct edac_device_ctl_info *edev_ctl) +{ + int cpu; + + if (!edev_ctl) + edev_ctl = panic_handler_drvdata->edev_ctl; + + armv8_check_l3_scu_error(edev_ctl); + for_each_possible_cpu(cpu) { + smp_call_function_single(cpu, armv8_check_l1_l2_ecc_helper, + edev_ctl, 0); + } +} + +static irqreturn_t armv8_l1_l2_handler(int irq, void *drvdata) +{ + if (armv8_check_l1_l2_ecc(panic_handler_drvdata->edev_ctl)) + return IRQ_HANDLED; + return IRQ_NONE; +} + +static irqreturn_t armv8_l3_scu_handler(int irq, void *drvdata) +{ + struct erp_drvdata *drv = drvdata; + struct edac_device_ctl_info *edev_ctl = drv->edev_ctl; + + if (armv8_check_l3_scu_error(edev_ctl)) + return IRQ_HANDLED; + return IRQ_NONE; +} + +static void initialize_registers(void *info) +{ + set_errxctlr_el1(); + set_errxmisc_overflow(); +} + +static void init_regs_on_cpu(bool all_cpus) +{ + int cpu; + + write_errselr_el1(0); + if (all_cpus) { + for_each_possible_cpu(cpu) + smp_call_function_single(cpu, initialize_registers, + NULL, 1); + } else { + initialize_registers(NULL); + } + + write_errselr_el1(1); + initialize_registers(NULL); +} + +static int armv8_pmu_cpu_pm_notify(struct notifier_block *self, + unsigned long action, void *v) +{ + switch (action) { + case CPU_PM_EXIT: + init_regs_on_cpu(false); + armv8_check_l3_scu_error(panic_handler_drvdata->edev_ctl); + armv8_check_l1_l2_ecc(panic_handler_drvdata->edev_ctl); + break; + } + + return NOTIFY_OK; +} + +static int armv8_cpu_erp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct erp_drvdata *drv; + int rc = 0; + int fail = 0; + + init_regs_on_cpu(true); + + drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); + + if (!drv) + return -ENOMEM; + + drv->edev_ctl = edac_device_alloc_ctl_info(0, "cpu", + num_possible_cpus(), "L", 3, 1, NULL, 0, + edac_device_alloc_index()); + + if (!drv->edev_ctl) + return -ENOMEM; + + if (IS_ENABLED(CONFIG_EDAC_ARMV8_POLL)) { + drv->edev_ctl->edac_check = armv8_poll_cache_errors; + drv->edev_ctl->poll_msec = poll_msec; + } + + 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 = "cache"; + drv->edev_ctl->panic_on_ue = panic_on_ue; + drv->nb_pm.notifier_call = armv8_pmu_cpu_pm_notify; + platform_set_drvdata(pdev, drv); + + rc = edac_device_add_device(drv->edev_ctl); + if (rc) + goto out_mem; + + panic_handler_drvdata = drv; + + if (!IS_ENABLED(CONFIG_EDAC_ARMV8_POLL)) { + fail += request_erp_irq(pdev, "l1-l2-irq", + "l1_l2_irq", + armv8_l1_l2_handler, drv, 1); + + fail += request_erp_irq(pdev, "l3-scu-irq", + "l3_scu_irq", + armv8_l3_scu_handler, drv, 0); + + if (fail == of_irq_count(dev->of_node)) { + pr_err("ERP: Could not request any IRQs. Giving up.\n"); + rc = -ENODEV; + goto out_dev; + } + } + + cpu_pm_register_notifier(&(drv->nb_pm)); + + return 0; + +out_dev: + edac_device_del_device(dev); +out_mem: + edac_device_free_ctl_info(drv->edev_ctl); + return rc; +} + +static int armv8_cpu_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; + + if (drv->erp_cpu_drvdata) { + free_percpu_irq(drv->ppi, drv->erp_cpu_drvdata); + free_percpu(drv->erp_cpu_drvdata); + } + + edac_device_del_device(edac_ctl->dev); + edac_device_free_ctl_info(edac_ctl); + + return 0; +} + +static const struct of_device_id armv8_cpu_erp_match_table[] = { + { .compatible = "arm,armv8-cpu-erp" }, + { } +}; + +static struct platform_driver armv8_cpu_erp_driver = { + .probe = armv8_cpu_erp_probe, + .remove = armv8_cpu_erp_remove, + .driver = { + .name = "armv8_cpu_cache_erp", + .of_match_table = of_match_ptr(armv8_cpu_erp_match_table), + }, +}; +module_platform_driver(armv8_cpu_erp_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ARMv8 EDAC driver");