From 6f3508f61c814ee852c199988a62bd954c50dfc1 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 20 Jan 2016 12:54:51 +0300 Subject: [PATCH 01/13] EDAC, amd64_edac: Shift wrapping issue in f1x_get_norm_dct_addr() dct_sel_base_off is declared as a u64 but we're only using the lower 32 bits because of a shift wrapping bug. This can possibly truncate the upper 16 bits of DctSelBaseOffset[47:26], causing us to misdecode the CS row. Fixes: c8e518d5673d ('amd64_edac: Sanitize f10_get_base_addr_offset') Signed-off-by: Dan Carpenter Cc: Aravind Gopalakrishnan Cc: linux-edac Cc: Link: http://lkml.kernel.org/r/20160120095451.GB19898@mwanda Signed-off-by: Borislav Petkov --- drivers/edac/amd64_edac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/edac/amd64_edac.c b/drivers/edac/amd64_edac.c index 9eee13ef83a5..d87a47547ba5 100644 --- a/drivers/edac/amd64_edac.c +++ b/drivers/edac/amd64_edac.c @@ -1452,7 +1452,7 @@ static u64 f1x_get_norm_dct_addr(struct amd64_pvt *pvt, u8 range, u64 chan_off; u64 dram_base = get_dram_base(pvt, range); u64 hole_off = f10_dhar_offset(pvt); - u64 dct_sel_base_off = (pvt->dct_sel_hi & 0xFFFFFC00) << 16; + u64 dct_sel_base_off = (u64)(pvt->dct_sel_hi & 0xFFFFFC00) << 16; if (hi_rng) { /* From 5979b84dc1f91fa2866aa8223a802f51f87e5b6a Mon Sep 17 00:00:00 2001 From: Loc Ho Date: Fri, 22 Jan 2016 13:47:03 -0700 Subject: [PATCH 02/13] Documentation, EDAC: Update xgene binding for missing register bus Update the APM X-Gene SoC EDAC documentation binding to reference the missing register bus for the SoC EDAC. Signed-off-by: Loc Ho Cc: Arnd Bergmann Cc: devicetree@vger.kernel.org Cc: Ian Campbell Cc: Kumar Gala Cc: linux-arm-kernel@lists.infradead.org Cc: linux-edac Cc: Mark Rutland Cc: mchehab@osg.samsung.com Cc: patches@apm.com Cc: Pawel Moll Cc: Rob Herring Link: http://lkml.kernel.org/r/1453495625-28006-2-git-send-email-lho@apm.com Signed-off-by: Borislav Petkov --- .../devicetree/bindings/edac/apm-xgene-edac.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/devicetree/bindings/edac/apm-xgene-edac.txt b/Documentation/devicetree/bindings/edac/apm-xgene-edac.txt index 78e2a31c58d0..1006b0489464 100644 --- a/Documentation/devicetree/bindings/edac/apm-xgene-edac.txt +++ b/Documentation/devicetree/bindings/edac/apm-xgene-edac.txt @@ -16,6 +16,10 @@ Required properties: - regmap-mcba : Regmap of the MCB-A (memory bridge) resource. - regmap-mcbb : Regmap of the MCB-B (memory bridge) resource. - regmap-efuse : Regmap of the PMD efuse resource. +- regmap-rb : Regmap of the register bus resource. This property + is optional only for compatibility. If the RB + error conditions are not cleared, it will + continuously generate interrupt. - reg : First resource shall be the CPU bus (PCP) resource. - interrupts : Interrupt-specifier for MCU, PMD, L3, or SoC error IRQ(s). @@ -64,6 +68,11 @@ Example: reg = <0x0 0x1054a000 0x0 0x20>; }; + rb: rb@7e000000 { + compatible = "apm,xgene-rb", "syscon"; + reg = <0x0 0x7e000000 0x0 0x10>; + }; + edac@78800000 { compatible = "apm,xgene-edac"; #address-cells = <2>; @@ -73,6 +82,7 @@ Example: regmap-mcba = <&mcba>; regmap-mcbb = <&mcbb>; regmap-efuse = <&efuse>; + regmap-rb = <&rb>; reg = <0x0 0x78800000 0x0 0x100>; interrupts = <0x0 0x20 0x4>, <0x0 0x21 0x4>, From 4d67e3ce75f97148ed127c53d6b0fc854d8b3867 Mon Sep 17 00:00:00 2001 From: Loc Ho Date: Fri, 22 Jan 2016 13:47:04 -0700 Subject: [PATCH 03/13] EDAC, xgene: Add missing SoC register bus error handling Add missing register bus error handling for APM X-Gene EDAC SoC and fix a checking condition for CE error promoted to UE. Signed-off-by: Loc Ho Cc: Arnd Bergmann Cc: devicetree@vger.kernel.org Cc: linux-arm-kernel@lists.infradead.org Cc: linux-edac Cc: Mauro Carvalho Chehab Cc: patches@apm.com Link: http://lkml.kernel.org/r/1453495625-28006-3-git-send-email-lho@apm.com Signed-off-by: Borislav Petkov --- drivers/edac/xgene_edac.c | 70 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/drivers/edac/xgene_edac.c b/drivers/edac/xgene_edac.c index 41f876414a18..bf19b6e3bd12 100644 --- a/drivers/edac/xgene_edac.c +++ b/drivers/edac/xgene_edac.c @@ -61,6 +61,7 @@ struct xgene_edac { struct regmap *mcba_map; struct regmap *mcbb_map; struct regmap *efuse_map; + struct regmap *rb_map; void __iomem *pcp_csr; spinlock_t lock; struct dentry *dfs; @@ -1057,7 +1058,7 @@ static bool xgene_edac_l3_promote_to_uc_err(u32 l3cesr, u32 l3celr) case 0x041: return true; } - } else if (L3C_ELR_ERRSYN(l3celr) == 9) + } else if (L3C_ELR_ERRWAY(l3celr) == 9) return true; return false; @@ -1353,6 +1354,17 @@ static int xgene_edac_l3_remove(struct xgene_edac_dev_ctx *l3) #define GLBL_MDED_ERRH 0x0848 #define GLBL_MDED_ERRHMASK 0x084c +/* IO Bus Registers */ +#define RBCSR 0x0000 +#define STICKYERR_MASK BIT(0) +#define RBEIR 0x0008 +#define AGENT_OFFLINE_ERR_MASK BIT(30) +#define UNIMPL_RBPAGE_ERR_MASK BIT(29) +#define WORD_ALIGNED_ERR_MASK BIT(28) +#define PAGE_ACCESS_ERR_MASK BIT(27) +#define WRITE_ACCESS_MASK BIT(26) +#define RBERRADDR_RD(src) ((src) & 0x03FFFFFF) + static const char * const soc_mem_err_v1[] = { "10GbE0", "10GbE1", @@ -1470,6 +1482,51 @@ static void xgene_edac_rb_report(struct edac_device_ctl_info *edac_dev) u32 err_addr_hi; u32 reg; + /* If the register bus resource isn't available, just skip it */ + if (!ctx->edac->rb_map) + goto rb_skip; + + /* + * Check RB access errors + * 1. Out of range + * 2. Un-implemented page + * 3. Un-aligned access + * 4. Offline slave IP + */ + if (regmap_read(ctx->edac->rb_map, RBCSR, ®)) + return; + if (reg & STICKYERR_MASK) { + bool write; + u32 address; + + dev_err(edac_dev->dev, "IOB bus access error(s)\n"); + if (regmap_read(ctx->edac->rb_map, RBEIR, ®)) + return; + write = reg & WRITE_ACCESS_MASK ? 1 : 0; + address = RBERRADDR_RD(reg); + if (reg & AGENT_OFFLINE_ERR_MASK) + dev_err(edac_dev->dev, + "IOB bus %s access to offline agent error\n", + write ? "write" : "read"); + if (reg & UNIMPL_RBPAGE_ERR_MASK) + dev_err(edac_dev->dev, + "IOB bus %s access to unimplemented page error\n", + write ? "write" : "read"); + if (reg & WORD_ALIGNED_ERR_MASK) + dev_err(edac_dev->dev, + "IOB bus %s word aligned access error\n", + write ? "write" : "read"); + if (reg & PAGE_ACCESS_ERR_MASK) + dev_err(edac_dev->dev, + "IOB bus %s to page out of range access error\n", + write ? "write" : "read"); + if (regmap_write(ctx->edac->rb_map, RBEIR, 0)) + return; + if (regmap_write(ctx->edac->rb_map, RBCSR, 0)) + return; + } +rb_skip: + /* IOB Bridge agent transaction error interrupt */ reg = readl(ctx->dev_csr + IOBBATRANSERRINTSTS); if (!reg) @@ -1852,6 +1909,17 @@ static int xgene_edac_probe(struct platform_device *pdev) goto out_err; } + /* + * NOTE: The register bus resource is optional for compatibility + * reason. + */ + edac->rb_map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "regmap-rb"); + if (IS_ERR(edac->rb_map)) { + dev_warn(edac->dev, "missing syscon regmap rb\n"); + edac->rb_map = NULL; + } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); edac->pcp_csr = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(edac->pcp_csr)) { From f5793c970888e48542de4ae152d16308873f29e4 Mon Sep 17 00:00:00 2001 From: Loc Ho Date: Fri, 22 Jan 2016 13:47:05 -0700 Subject: [PATCH 04/13] arm64: Update the APM X-Gene EDAC node with the RB register resource Add the RB register resource as requires with the updated driver. Signed-off-by: Loc Ho Cc: Arnd Bergmann Cc: Catalin Marinas Cc: devicetree@vger.kernel.org Cc: Duc Dang Cc: Ian Campbell Cc: Kumar Gala Cc: linux-arm-kernel@lists.infradead.org Cc: linux-edac Cc: Mark Rutland Cc: mchehab@osg.samsung.com Cc: patches@apm.com Cc: Pawel Moll Cc: Rob Herring Cc: Will Deacon Link: http://lkml.kernel.org/r/1453495625-28006-4-git-send-email-lho@apm.com Signed-off-by: Borislav Petkov --- arch/arm64/boot/dts/apm/apm-storm.dtsi | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/arm64/boot/dts/apm/apm-storm.dtsi b/arch/arm64/boot/dts/apm/apm-storm.dtsi index fe30f7671ea3..3e40bd469cea 100644 --- a/arch/arm64/boot/dts/apm/apm-storm.dtsi +++ b/arch/arm64/boot/dts/apm/apm-storm.dtsi @@ -493,6 +493,11 @@ efuse: efuse@1054a000 { reg = <0x0 0x1054a000 0x0 0x20>; }; + rb: rb@7e000000 { + compatible = "apm,xgene-rb", "syscon"; + reg = <0x0 0x7e000000 0x0 0x10>; + }; + edac@78800000 { compatible = "apm,xgene-edac"; #address-cells = <2>; @@ -502,6 +507,7 @@ edac@78800000 { regmap-mcba = <&mcba>; regmap-mcbb = <&mcbb>; regmap-efuse = <&efuse>; + regmap-rb = <&rb>; reg = <0x0 0x78800000 0x0 0x100>; interrupts = <0x0 0x20 0x4>, <0x0 0x21 0x4>, From 096676061987c613bdacddbae838cb63a815db94 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Tue, 2 Feb 2016 10:59:53 +0100 Subject: [PATCH 05/13] EDAC: Balance workqueue setup and teardown We use the ->edac_check function pointers to determine whether we need to setup a polling workqueue. However, the destroy path is not balanced and we might try to teardown an unitialized workqueue. Balance init and destroy paths by looking at ->edac_check in both cases. Set op_state to OP_OFFLINE *before* destroying anything. Reported-by: Zhiqiang Hou Cc: Varun Sethi Signed-off-by: Borislav Petkov --- drivers/edac/edac_mc.c | 15 +++++++-------- drivers/edac/edac_pci.c | 8 +++----- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/drivers/edac/edac_mc.c b/drivers/edac/edac_mc.c index 8adfc167c2e3..50802c154915 100644 --- a/drivers/edac/edac_mc.c +++ b/drivers/edac/edac_mc.c @@ -583,8 +583,6 @@ static void edac_mc_workq_setup(struct mem_ctl_info *mci, unsigned msec) */ static void edac_mc_workq_teardown(struct mem_ctl_info *mci) { - mci->op_state = OP_OFFLINE; - edac_stop_work(&mci->work); } @@ -772,7 +770,7 @@ int edac_mc_add_mc_with_groups(struct mem_ctl_info *mci, } /* If there IS a check routine, then we are running POLLED */ - if (mci->edac_check != NULL) { + if (mci->edac_check) { /* This instance is NOW RUNNING */ mci->op_state = OP_RUNNING_POLL; @@ -823,15 +821,16 @@ struct mem_ctl_info *edac_mc_del_mc(struct device *dev) return NULL; } + /* mark MCI offline: */ + mci->op_state = OP_OFFLINE; + if (!del_mc_from_global_list(mci)) edac_mc_owner = NULL; + mutex_unlock(&mem_ctls_mutex); - /* flush workq processes */ - edac_mc_workq_teardown(mci); - - /* marking MCI offline */ - mci->op_state = OP_OFFLINE; + if (mci->edac_check) + edac_mc_workq_teardown(mci); /* remove from sysfs */ edac_remove_sysfs_mci_device(mci); diff --git a/drivers/edac/edac_pci.c b/drivers/edac/edac_pci.c index 99685388d3fb..f0e8c3d01ed5 100644 --- a/drivers/edac/edac_pci.c +++ b/drivers/edac/edac_pci.c @@ -241,8 +241,6 @@ static void edac_pci_workq_teardown(struct edac_pci_ctl_info *pci) { edac_dbg(0, "\n"); - pci->op_state = OP_OFFLINE; - edac_stop_work(&pci->work); } @@ -289,7 +287,7 @@ int edac_pci_add_device(struct edac_pci_ctl_info *pci, int edac_idx) goto fail1; } - if (pci->edac_check != NULL) { + if (pci->edac_check) { pci->op_state = OP_RUNNING_POLL; edac_pci_workq_setup(pci, 1000); @@ -350,8 +348,8 @@ struct edac_pci_ctl_info *edac_pci_del_device(struct device *dev) mutex_unlock(&edac_pci_ctls_mutex); - /* stop the workq timer */ - edac_pci_workq_teardown(pci); + if (pci->edac_check) + edac_pci_workq_teardown(pci); edac_printk(KERN_INFO, EDAC_PCI, "Removed device %d for %s %s: DEV %s\n", From 626a7a4dba2ad672b66b675f961de214f0ad0c74 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Tue, 2 Feb 2016 11:06:41 +0100 Subject: [PATCH 06/13] EDAC: Kill workqueue setup/teardown functions We have the generic wrappers now, use those. edac_pci_workq_setup() had an unused argument anyway. Signed-off-by: Borislav Petkov --- drivers/edac/edac_mc.c | 43 ++++------------------------------------- drivers/edac/edac_pci.c | 35 ++++----------------------------- 2 files changed, 8 insertions(+), 70 deletions(-) diff --git a/drivers/edac/edac_mc.c b/drivers/edac/edac_mc.c index 50802c154915..050493edb505 100644 --- a/drivers/edac/edac_mc.c +++ b/drivers/edac/edac_mc.c @@ -551,41 +551,6 @@ static void edac_mc_workq_function(struct work_struct *work_req) edac_queue_work(&mci->work, msecs_to_jiffies(edac_mc_get_poll_msec())); } -/* - * edac_mc_workq_setup - * initialize a workq item for this mci - * passing in the new delay period in msec - * - * locking model: - * - * called with the mem_ctls_mutex held - */ -static void edac_mc_workq_setup(struct mem_ctl_info *mci, unsigned msec) -{ - edac_dbg(0, "\n"); - - /* if this instance is not in the POLL state, then simply return */ - if (mci->op_state != OP_RUNNING_POLL) - return; - - INIT_DELAYED_WORK(&mci->work, edac_mc_workq_function); - - edac_queue_work(&mci->work, msecs_to_jiffies(msec)); -} - -/* - * edac_mc_workq_teardown - * stop the workq processing on this mci - * - * locking model: - * - * called WITHOUT lock held - */ -static void edac_mc_workq_teardown(struct mem_ctl_info *mci) -{ - edac_stop_work(&mci->work); -} - /* * edac_mc_reset_delay_period(unsigned long value) * @@ -769,12 +734,12 @@ int edac_mc_add_mc_with_groups(struct mem_ctl_info *mci, goto fail1; } - /* If there IS a check routine, then we are running POLLED */ if (mci->edac_check) { - /* This instance is NOW RUNNING */ mci->op_state = OP_RUNNING_POLL; - edac_mc_workq_setup(mci, edac_mc_get_poll_msec()); + INIT_DELAYED_WORK(&mci->work, edac_mc_workq_function); + edac_queue_work(&mci->work, msecs_to_jiffies(edac_mc_get_poll_msec())); + } else { mci->op_state = OP_RUNNING_INTERRUPT; } @@ -830,7 +795,7 @@ struct mem_ctl_info *edac_mc_del_mc(struct device *dev) mutex_unlock(&mem_ctls_mutex); if (mci->edac_check) - edac_mc_workq_teardown(mci); + edac_stop_work(&mci->work); /* remove from sysfs */ edac_remove_sysfs_mci_device(mci); diff --git a/drivers/edac/edac_pci.c b/drivers/edac/edac_pci.c index f0e8c3d01ed5..79945e0df0dc 100644 --- a/drivers/edac/edac_pci.c +++ b/drivers/edac/edac_pci.c @@ -215,35 +215,6 @@ static void edac_pci_workq_function(struct work_struct *work_req) mutex_unlock(&edac_pci_ctls_mutex); } -/* - * edac_pci_workq_setup() - * initialize a workq item for this edac_pci instance - * passing in the new delay period in msec - * - * locking model: - * called when 'edac_pci_ctls_mutex' is locked - */ -static void edac_pci_workq_setup(struct edac_pci_ctl_info *pci, - unsigned int msec) -{ - edac_dbg(0, "\n"); - - INIT_DELAYED_WORK(&pci->work, edac_pci_workq_function); - - edac_queue_work(&pci->work, msecs_to_jiffies(edac_pci_get_poll_msec())); -} - -/* - * edac_pci_workq_teardown() - * stop the workq processing on this edac_pci instance - */ -static void edac_pci_workq_teardown(struct edac_pci_ctl_info *pci) -{ - edac_dbg(0, "\n"); - - edac_stop_work(&pci->work); -} - /* * edac_pci_alloc_index: Allocate a unique PCI index number * @@ -290,7 +261,9 @@ int edac_pci_add_device(struct edac_pci_ctl_info *pci, int edac_idx) if (pci->edac_check) { pci->op_state = OP_RUNNING_POLL; - edac_pci_workq_setup(pci, 1000); + INIT_DELAYED_WORK(&pci->work, edac_pci_workq_function); + edac_queue_work(&pci->work, msecs_to_jiffies(edac_pci_get_poll_msec())); + } else { pci->op_state = OP_RUNNING_INTERRUPT; } @@ -349,7 +322,7 @@ struct edac_pci_ctl_info *edac_pci_del_device(struct device *dev) mutex_unlock(&edac_pci_ctls_mutex); if (pci->edac_check) - edac_pci_workq_teardown(pci); + edac_stop_work(&pci->work); edac_printk(KERN_INFO, EDAC_PCI, "Removed device %d for %s %s: DEV %s\n", From 06e912d4d4c2624c169997e26b3d7b5746735a14 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Tue, 2 Feb 2016 11:36:11 +0100 Subject: [PATCH 07/13] EDAC: Cleanup/sync workqueue functions They're both running only when ->edac_check is initialized so remove that check from the workqueue function itself. Synchronize/generalize the ->op_state check between the two. Kill useless comments, while at it. Signed-off-by: Borislav Petkov --- drivers/edac/edac_mc.c | 8 +++----- drivers/edac/edac_pci.c | 30 +++++++++++++++--------------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/drivers/edac/edac_mc.c b/drivers/edac/edac_mc.c index 050493edb505..1472f48c8ac6 100644 --- a/drivers/edac/edac_mc.c +++ b/drivers/edac/edac_mc.c @@ -535,19 +535,17 @@ static void edac_mc_workq_function(struct work_struct *work_req) mutex_lock(&mem_ctls_mutex); - /* if this control struct has movd to offline state, we are done */ - if (mci->op_state == OP_OFFLINE) { + if (mci->op_state != OP_RUNNING_POLL) { mutex_unlock(&mem_ctls_mutex); return; } - /* Only poll controllers that are running polled and have a check */ - if (edac_mc_assert_error_check_and_clear() && (mci->edac_check != NULL)) + if (edac_mc_assert_error_check_and_clear()) mci->edac_check(mci); mutex_unlock(&mem_ctls_mutex); - /* Reschedule */ + /* Queue ourselves again. */ edac_queue_work(&mci->work, msecs_to_jiffies(edac_mc_get_poll_msec())); } diff --git a/drivers/edac/edac_pci.c b/drivers/edac/edac_pci.c index 79945e0df0dc..8f2f2899a7a2 100644 --- a/drivers/edac/edac_pci.c +++ b/drivers/edac/edac_pci.c @@ -195,23 +195,23 @@ static void edac_pci_workq_function(struct work_struct *work_req) mutex_lock(&edac_pci_ctls_mutex); - if (pci->op_state == OP_RUNNING_POLL) { - /* we might be in POLL mode, but there may NOT be a poll func - */ - if ((pci->edac_check != NULL) && edac_pci_get_check_errors()) - pci->edac_check(pci); - - /* if we are on a one second period, then use round */ - msec = edac_pci_get_poll_msec(); - if (msec == 1000) - delay = round_jiffies_relative(msecs_to_jiffies(msec)); - else - delay = msecs_to_jiffies(msec); - - /* Reschedule only if we are in POLL mode */ - edac_queue_work(&pci->work, delay); + if (pci->op_state != OP_RUNNING_POLL) { + mutex_unlock(&edac_pci_ctls_mutex); + return; } + if (edac_pci_get_check_errors()) + pci->edac_check(pci); + + /* if we are on a one second period, then use round */ + msec = edac_pci_get_poll_msec(); + if (msec == 1000) + delay = round_jiffies_relative(msecs_to_jiffies(msec)); + else + delay = msecs_to_jiffies(msec); + + edac_queue_work(&pci->work, delay); + mutex_unlock(&edac_pci_ctls_mutex); } From f2b59ac66f980e7636c15e2ebbd09f0ef46881ab Mon Sep 17 00:00:00 2001 From: Sudip Mukherjee Date: Tue, 2 Feb 2016 21:09:33 +0530 Subject: [PATCH 08/13] EDAC, mpc85xx: Silence unused variable warning We were getting this build warning: drivers/edac/mpc85xx_edac.c:1247:6: warning: unused variable 'pvr' pvr is only used if CONFIG_FSL_SOC_BOOKE is defined. Declare it __maybe_unused. Suggested-by: Guenter Roeck Signed-off-by: Sudip Mukherjee Reviewed-by: Johannes Thumshirn Cc: linux-edac Link: http://lkml.kernel.org/r/1454427573-7994-1-git-send-email-sudipm.mukherjee@gmail.com Signed-off-by: Borislav Petkov --- drivers/edac/mpc85xx_edac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/edac/mpc85xx_edac.c b/drivers/edac/mpc85xx_edac.c index b7139c160baf..ca63d0da8889 100644 --- a/drivers/edac/mpc85xx_edac.c +++ b/drivers/edac/mpc85xx_edac.c @@ -1244,7 +1244,7 @@ static struct platform_driver * const drivers[] = { static int __init mpc85xx_mc_init(void) { int res = 0; - u32 pvr = 0; + u32 __maybe_unused pvr = 0; printk(KERN_INFO "Freescale(R) MPC85xx EDAC driver, " "(C) 2006 Montavista Software\n"); From 9bf4f005672073f6bae2edf84e6cb5c4fb16ffc6 Mon Sep 17 00:00:00 2001 From: Thor Thayer Date: Tue, 9 Feb 2016 18:29:25 -0600 Subject: [PATCH 09/13] EDAC: Use edac_debugfs_remove_recursive() in edac_debugfs_exit() debugfs_remove() is used to remove a file or a directory from the debugfs filesystem on an EDAC device exit. However edac_debugfs might not be empty. This is similar to 30f84a891bf6 ("EDAC: Use edac_debugfs_remove_recursive()") which changed the EDAC MCI code to use edac_debugfs_remove_recursive(). Suggested-by: Borislav Petkov Signed-off-by: Thor Thayer Cc: linux-edac Link: http://lkml.kernel.org/r/1455064165-3816-1-git-send-email-tthayer@opensource.altera.com Signed-off-by: Borislav Petkov --- drivers/edac/debugfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/edac/debugfs.c b/drivers/edac/debugfs.c index 54d2f668cb0a..92dbb7e2320c 100644 --- a/drivers/edac/debugfs.c +++ b/drivers/edac/debugfs.c @@ -53,7 +53,7 @@ int __init edac_debugfs_init(void) void edac_debugfs_exit(void) { - debugfs_remove(edac_debugfs); + debugfs_remove_recursive(edac_debugfs); } int edac_create_debugfs_nodes(struct mem_ctl_info *mci) From c3eea1942a16db52ebea0382bd5826f75b9b7e9b Mon Sep 17 00:00:00 2001 From: Thor Thayer Date: Wed, 10 Feb 2016 13:26:21 -0600 Subject: [PATCH 10/13] EDAC, altera: Add Altera L2 cache and OCRAM support Add L2 Cache and On-Chip RAM EDAC support for the Altera SoCs. The SDRAM controller is using the Memory Controller model. Each type of ECC is individually configurable. Signed-off-by: Thor Thayer Cc: devicetree@vger.kernel.org Cc: dinguyen@opensource.altera.com Cc: galak@codeaurora.org Cc: grant.likely@linaro.org Cc: ijc+devicetree@hellion.org.uk Cc: linux-arm-kernel@lists.infradead.org Cc: linux@arm.linux.org.uk Cc: linux-doc@vger.kernel.org Cc: linux-edac Cc: mark.rutland@arm.com Cc: Mauro Carvalho Chehab Cc: pawel.moll@arm.com Cc: robh+dt@kernel.org Link: http://lkml.kernel.org/r/1455132384-17108-1-git-send-email-tthayer@opensource.altera.com Signed-off-by: Borislav Petkov --- drivers/edac/Kconfig | 26 +- drivers/edac/Makefile | 2 +- drivers/edac/altera_edac.c | 492 ++++++++++++++++++++++++++++++++++++- 3 files changed, 512 insertions(+), 8 deletions(-) diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig index ef25000a5bc6..37755e63cc28 100644 --- a/drivers/edac/Kconfig +++ b/drivers/edac/Kconfig @@ -367,14 +367,30 @@ config EDAC_OCTEON_PCI Support for error detection and correction on the Cavium Octeon family of SOCs. -config EDAC_ALTERA_MC - bool "Altera SDRAM Memory Controller EDAC" +config EDAC_ALTERA + bool "Altera SOCFPGA ECC" depends on EDAC_MM_EDAC=y && ARCH_SOCFPGA help Support for error detection and correction on the - Altera SDRAM memory controller. Note that the - preloader must initialize the SDRAM before loading - the kernel. + Altera SOCs. This must be selected for SDRAM ECC. + Note that the preloader must initialize the SDRAM + before loading the kernel. + +config EDAC_ALTERA_L2C + bool "Altera L2 Cache ECC" + depends on EDAC_ALTERA=y + select CACHE_L2X0 + help + Support for error detection and correction on the + Altera L2 cache Memory for Altera SoCs. This option + requires L2 cache so it will force that selection. + +config EDAC_ALTERA_OCRAM + bool "Altera On-Chip RAM ECC" + depends on EDAC_ALTERA=y && SRAM && GENERIC_ALLOCATOR + help + Support for error detection and correction on the + Altera On-Chip RAM Memory for Altera SoCs. config EDAC_SYNOPSYS tristate "Synopsys DDR Memory Controller" diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile index be163e20fe56..f9e4a3e0e6e9 100644 --- a/drivers/edac/Makefile +++ b/drivers/edac/Makefile @@ -67,6 +67,6 @@ obj-$(CONFIG_EDAC_OCTEON_L2C) += octeon_edac-l2c.o obj-$(CONFIG_EDAC_OCTEON_LMC) += octeon_edac-lmc.o obj-$(CONFIG_EDAC_OCTEON_PCI) += octeon_edac-pci.o -obj-$(CONFIG_EDAC_ALTERA_MC) += altera_edac.o +obj-$(CONFIG_EDAC_ALTERA) += altera_edac.o obj-$(CONFIG_EDAC_SYNOPSYS) += synopsys_edac.o obj-$(CONFIG_EDAC_XGENE) += xgene_edac.o diff --git a/drivers/edac/altera_edac.c b/drivers/edac/altera_edac.c index 929640981d8a..63e42098726d 100644 --- a/drivers/edac/altera_edac.c +++ b/drivers/edac/altera_edac.c @@ -1,5 +1,5 @@ /* - * Copyright Altera Corporation (C) 2014-2015. All rights reserved. + * Copyright Altera Corporation (C) 2014-2016. All rights reserved. * Copyright 2011-2012 Calxeda, Inc. * * This program is free software; you can redistribute it and/or modify it @@ -17,8 +17,10 @@ * Adapted from the highbank_mc_edac driver. */ +#include #include #include +#include #include #include #include @@ -34,6 +36,7 @@ #define EDAC_MOD_STR "altera_edac" #define EDAC_VERSION "1" +#define EDAC_DEVICE "Altera" static const struct altr_sdram_prv_data c5_data = { .ecc_ctrl_offset = CV_CTLCFG_OFST, @@ -75,6 +78,31 @@ static const struct altr_sdram_prv_data a10_data = { .ue_set_mask = A10_DIAGINT_TDERRA_MASK, }; +/************************** EDAC Device Defines **************************/ + +/* OCRAM ECC Management Group Defines */ +#define ALTR_MAN_GRP_OCRAM_ECC_OFFSET 0x04 +#define ALTR_OCR_ECC_EN BIT(0) +#define ALTR_OCR_ECC_INJS BIT(1) +#define ALTR_OCR_ECC_INJD BIT(2) +#define ALTR_OCR_ECC_SERR BIT(3) +#define ALTR_OCR_ECC_DERR BIT(4) + +/* L2 ECC Management Group Defines */ +#define ALTR_MAN_GRP_L2_ECC_OFFSET 0x00 +#define ALTR_L2_ECC_EN BIT(0) +#define ALTR_L2_ECC_INJS BIT(1) +#define ALTR_L2_ECC_INJD BIT(2) + +#define ALTR_UE_TRIGGER_CHAR 'U' /* Trigger for UE */ +#define ALTR_TRIGGER_READ_WRD_CNT 32 /* Line size x 4 */ +#define ALTR_TRIG_OCRAM_BYTE_SIZE 128 /* Line size x 4 */ +#define ALTR_TRIG_L2C_BYTE_SIZE 4096 /* Full Page */ + +/*********************** EDAC Memory Controller Functions ****************/ + +/* The SDRAM controller uses the EDAC Memory Controller framework. */ + static irqreturn_t altr_sdram_mc_err_handler(int irq, void *dev_id) { struct mem_ctl_info *mci = dev_id; @@ -504,6 +532,466 @@ static struct platform_driver altr_sdram_edac_driver = { module_platform_driver(altr_sdram_edac_driver); +/************************* EDAC Parent Probe *************************/ + +static const struct of_device_id altr_edac_device_of_match[]; + +static const struct of_device_id altr_edac_of_match[] = { + { .compatible = "altr,socfpga-ecc-manager" }, + {}, +}; +MODULE_DEVICE_TABLE(of, altr_edac_of_match); + +static int altr_edac_probe(struct platform_device *pdev) +{ + of_platform_populate(pdev->dev.of_node, altr_edac_device_of_match, + NULL, &pdev->dev); + return 0; +} + +static struct platform_driver altr_edac_driver = { + .probe = altr_edac_probe, + .driver = { + .name = "socfpga_ecc_manager", + .of_match_table = altr_edac_of_match, + }, +}; +module_platform_driver(altr_edac_driver); + +/************************* EDAC Device Functions *************************/ + +/* + * EDAC Device Functions (shared between various IPs). + * The discrete memories use the EDAC Device framework. The probe + * and error handling functions are very similar between memories + * so they are shared. The memory allocation and freeing for EDAC + * trigger testing are different for each memory. + */ + +const struct edac_device_prv_data ocramecc_data; +const struct edac_device_prv_data l2ecc_data; + +struct edac_device_prv_data { + int (*setup)(struct platform_device *pdev, void __iomem *base); + int ce_clear_mask; + int ue_clear_mask; + char dbgfs_name[20]; + void * (*alloc_mem)(size_t size, void **other); + void (*free_mem)(void *p, size_t size, void *other); + int ecc_enable_mask; + int ce_set_mask; + int ue_set_mask; + int trig_alloc_sz; +}; + +struct altr_edac_device_dev { + void __iomem *base; + int sb_irq; + int db_irq; + const struct edac_device_prv_data *data; + struct dentry *debugfs_dir; + char *edac_dev_name; +}; + +static irqreturn_t altr_edac_device_handler(int irq, void *dev_id) +{ + irqreturn_t ret_value = IRQ_NONE; + struct edac_device_ctl_info *dci = dev_id; + struct altr_edac_device_dev *drvdata = dci->pvt_info; + const struct edac_device_prv_data *priv = drvdata->data; + + if (irq == drvdata->sb_irq) { + if (priv->ce_clear_mask) + writel(priv->ce_clear_mask, drvdata->base); + edac_device_handle_ce(dci, 0, 0, drvdata->edac_dev_name); + ret_value = IRQ_HANDLED; + } else if (irq == drvdata->db_irq) { + if (priv->ue_clear_mask) + writel(priv->ue_clear_mask, drvdata->base); + edac_device_handle_ue(dci, 0, 0, drvdata->edac_dev_name); + panic("\nEDAC:ECC_DEVICE[Uncorrectable errors]\n"); + ret_value = IRQ_HANDLED; + } else { + WARN_ON(1); + } + + return ret_value; +} + +static ssize_t altr_edac_device_trig(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) + +{ + u32 *ptemp, i, error_mask; + int result = 0; + u8 trig_type; + unsigned long flags; + struct edac_device_ctl_info *edac_dci = file->private_data; + struct altr_edac_device_dev *drvdata = edac_dci->pvt_info; + const struct edac_device_prv_data *priv = drvdata->data; + void *generic_ptr = edac_dci->dev; + + if (!user_buf || get_user(trig_type, user_buf)) + return -EFAULT; + + if (!priv->alloc_mem) + return -ENOMEM; + + /* + * Note that generic_ptr is initialized to the device * but in + * some alloc_functions, this is overridden and returns data. + */ + ptemp = priv->alloc_mem(priv->trig_alloc_sz, &generic_ptr); + if (!ptemp) { + edac_printk(KERN_ERR, EDAC_DEVICE, + "Inject: Buffer Allocation error\n"); + return -ENOMEM; + } + + if (trig_type == ALTR_UE_TRIGGER_CHAR) + error_mask = priv->ue_set_mask; + else + error_mask = priv->ce_set_mask; + + edac_printk(KERN_ALERT, EDAC_DEVICE, + "Trigger Error Mask (0x%X)\n", error_mask); + + local_irq_save(flags); + /* write ECC corrupted data out. */ + for (i = 0; i < (priv->trig_alloc_sz / sizeof(*ptemp)); i++) { + /* Read data so we're in the correct state */ + rmb(); + if (ACCESS_ONCE(ptemp[i])) + result = -1; + /* Toggle Error bit (it is latched), leave ECC enabled */ + writel(error_mask, drvdata->base); + writel(priv->ecc_enable_mask, drvdata->base); + ptemp[i] = i; + } + /* Ensure it has been written out */ + wmb(); + local_irq_restore(flags); + + if (result) + edac_printk(KERN_ERR, EDAC_DEVICE, "Mem Not Cleared\n"); + + /* Read out written data. ECC error caused here */ + for (i = 0; i < ALTR_TRIGGER_READ_WRD_CNT; i++) + if (ACCESS_ONCE(ptemp[i]) != i) + edac_printk(KERN_ERR, EDAC_DEVICE, + "Read doesn't match written data\n"); + + if (priv->free_mem) + priv->free_mem(ptemp, priv->trig_alloc_sz, generic_ptr); + + return count; +} + +static const struct file_operations altr_edac_device_inject_fops = { + .open = simple_open, + .write = altr_edac_device_trig, + .llseek = generic_file_llseek, +}; + +static void altr_create_edacdev_dbgfs(struct edac_device_ctl_info *edac_dci, + const struct edac_device_prv_data *priv) +{ + struct altr_edac_device_dev *drvdata = edac_dci->pvt_info; + + if (!IS_ENABLED(CONFIG_EDAC_DEBUG)) + return; + + drvdata->debugfs_dir = edac_debugfs_create_dir(drvdata->edac_dev_name); + if (!drvdata->debugfs_dir) + return; + + if (!edac_debugfs_create_file(priv->dbgfs_name, S_IWUSR, + drvdata->debugfs_dir, edac_dci, + &altr_edac_device_inject_fops)) + debugfs_remove_recursive(drvdata->debugfs_dir); +} + +static const struct of_device_id altr_edac_device_of_match[] = { +#ifdef CONFIG_EDAC_ALTERA_L2C + { .compatible = "altr,socfpga-l2-ecc", .data = (void *)&l2ecc_data }, +#endif +#ifdef CONFIG_EDAC_ALTERA_OCRAM + { .compatible = "altr,socfpga-ocram-ecc", + .data = (void *)&ocramecc_data }, +#endif + {}, +}; +MODULE_DEVICE_TABLE(of, altr_edac_device_of_match); + +/* + * altr_edac_device_probe() + * This is a generic EDAC device driver that will support + * various Altera memory devices such as the L2 cache ECC and + * OCRAM ECC as well as the memories for other peripherals. + * Module specific initialization is done by passing the + * function index in the device tree. + */ +static int altr_edac_device_probe(struct platform_device *pdev) +{ + struct edac_device_ctl_info *dci; + struct altr_edac_device_dev *drvdata; + struct resource *r; + int res = 0; + struct device_node *np = pdev->dev.of_node; + char *ecc_name = (char *)np->name; + static int dev_instance; + + if (!devres_open_group(&pdev->dev, NULL, GFP_KERNEL)) { + edac_printk(KERN_ERR, EDAC_DEVICE, + "Unable to open devm\n"); + return -ENOMEM; + } + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + edac_printk(KERN_ERR, EDAC_DEVICE, + "Unable to get mem resource\n"); + res = -ENODEV; + goto fail; + } + + if (!devm_request_mem_region(&pdev->dev, r->start, resource_size(r), + dev_name(&pdev->dev))) { + edac_printk(KERN_ERR, EDAC_DEVICE, + "%s:Error requesting mem region\n", ecc_name); + res = -EBUSY; + goto fail; + } + + dci = edac_device_alloc_ctl_info(sizeof(*drvdata), ecc_name, + 1, ecc_name, 1, 0, NULL, 0, + dev_instance++); + + if (!dci) { + edac_printk(KERN_ERR, EDAC_DEVICE, + "%s: Unable to allocate EDAC device\n", ecc_name); + res = -ENOMEM; + goto fail; + } + + drvdata = dci->pvt_info; + dci->dev = &pdev->dev; + platform_set_drvdata(pdev, dci); + drvdata->edac_dev_name = ecc_name; + + drvdata->base = devm_ioremap(&pdev->dev, r->start, resource_size(r)); + if (!drvdata->base) + goto fail1; + + /* Get driver specific data for this EDAC device */ + drvdata->data = of_match_node(altr_edac_device_of_match, np)->data; + + /* Check specific dependencies for the module */ + if (drvdata->data->setup) { + res = drvdata->data->setup(pdev, drvdata->base); + if (res) + goto fail1; + } + + drvdata->sb_irq = platform_get_irq(pdev, 0); + res = devm_request_irq(&pdev->dev, drvdata->sb_irq, + altr_edac_device_handler, + 0, dev_name(&pdev->dev), dci); + if (res) + goto fail1; + + drvdata->db_irq = platform_get_irq(pdev, 1); + res = devm_request_irq(&pdev->dev, drvdata->db_irq, + altr_edac_device_handler, + 0, dev_name(&pdev->dev), dci); + if (res) + goto fail1; + + dci->mod_name = "Altera ECC Manager"; + dci->dev_name = drvdata->edac_dev_name; + + res = edac_device_add_device(dci); + if (res) + goto fail1; + + altr_create_edacdev_dbgfs(dci, drvdata->data); + + devres_close_group(&pdev->dev, NULL); + + return 0; + +fail1: + edac_device_free_ctl_info(dci); +fail: + devres_release_group(&pdev->dev, NULL); + edac_printk(KERN_ERR, EDAC_DEVICE, + "%s:Error setting up EDAC device: %d\n", ecc_name, res); + + return res; +} + +static int altr_edac_device_remove(struct platform_device *pdev) +{ + struct edac_device_ctl_info *dci = platform_get_drvdata(pdev); + struct altr_edac_device_dev *drvdata = dci->pvt_info; + + debugfs_remove_recursive(drvdata->debugfs_dir); + edac_device_del_device(&pdev->dev); + edac_device_free_ctl_info(dci); + + return 0; +} + +static struct platform_driver altr_edac_device_driver = { + .probe = altr_edac_device_probe, + .remove = altr_edac_device_remove, + .driver = { + .name = "altr_edac_device", + .of_match_table = altr_edac_device_of_match, + }, +}; +module_platform_driver(altr_edac_device_driver); + +/*********************** OCRAM EDAC Device Functions *********************/ + +#ifdef CONFIG_EDAC_ALTERA_OCRAM + +static void *ocram_alloc_mem(size_t size, void **other) +{ + struct device_node *np; + struct gen_pool *gp; + void *sram_addr; + + np = of_find_compatible_node(NULL, NULL, "altr,socfpga-ocram-ecc"); + if (!np) + return NULL; + + gp = of_gen_pool_get(np, "iram", 0); + of_node_put(np); + if (!gp) + return NULL; + + sram_addr = (void *)gen_pool_alloc(gp, size); + if (!sram_addr) + return NULL; + + memset(sram_addr, 0, size); + /* Ensure data is written out */ + wmb(); + + /* Remember this handle for freeing later */ + *other = gp; + + return sram_addr; +} + +static void ocram_free_mem(void *p, size_t size, void *other) +{ + gen_pool_free((struct gen_pool *)other, (u32)p, size); +} + +/* + * altr_ocram_check_deps() + * Test for OCRAM cache ECC dependencies upon entry because + * platform specific startup should have initialized the + * On-Chip RAM memory and enabled the ECC. + * Can't turn on ECC here because accessing un-initialized + * memory will cause CE/UE errors possibly causing an ABORT. + */ +static int altr_ocram_check_deps(struct platform_device *pdev, + void __iomem *base) +{ + if (readl(base) & ALTR_OCR_ECC_EN) + return 0; + + edac_printk(KERN_ERR, EDAC_DEVICE, + "OCRAM: No ECC present or ECC disabled.\n"); + return -ENODEV; +} + +const struct edac_device_prv_data ocramecc_data = { + .setup = altr_ocram_check_deps, + .ce_clear_mask = (ALTR_OCR_ECC_EN | ALTR_OCR_ECC_SERR), + .ue_clear_mask = (ALTR_OCR_ECC_EN | ALTR_OCR_ECC_DERR), + .dbgfs_name = "altr_ocram_trigger", + .alloc_mem = ocram_alloc_mem, + .free_mem = ocram_free_mem, + .ecc_enable_mask = ALTR_OCR_ECC_EN, + .ce_set_mask = (ALTR_OCR_ECC_EN | ALTR_OCR_ECC_INJS), + .ue_set_mask = (ALTR_OCR_ECC_EN | ALTR_OCR_ECC_INJD), + .trig_alloc_sz = ALTR_TRIG_OCRAM_BYTE_SIZE, +}; + +#endif /* CONFIG_EDAC_ALTERA_OCRAM */ + +/********************* L2 Cache EDAC Device Functions ********************/ + +#ifdef CONFIG_EDAC_ALTERA_L2C + +static void *l2_alloc_mem(size_t size, void **other) +{ + struct device *dev = *other; + void *ptemp = devm_kzalloc(dev, size, GFP_KERNEL); + + if (!ptemp) + return NULL; + + /* Make sure everything is written out */ + wmb(); + + /* + * Clean all cache levels up to LoC (includes L2) + * This ensures the corrupted data is written into + * L2 cache for readback test (which causes ECC error). + */ + flush_cache_all(); + + return ptemp; +} + +static void l2_free_mem(void *p, size_t size, void *other) +{ + struct device *dev = other; + + if (dev && p) + devm_kfree(dev, p); +} + +/* + * altr_l2_check_deps() + * Test for L2 cache ECC dependencies upon entry because + * platform specific startup should have initialized the L2 + * memory and enabled the ECC. + * Bail if ECC is not enabled. + * Note that L2 Cache Enable is forced at build time. + */ +static int altr_l2_check_deps(struct platform_device *pdev, + void __iomem *base) +{ + if (readl(base) & ALTR_L2_ECC_EN) + return 0; + + edac_printk(KERN_ERR, EDAC_DEVICE, + "L2: No ECC present, or ECC disabled\n"); + return -ENODEV; +} + +const struct edac_device_prv_data l2ecc_data = { + .setup = altr_l2_check_deps, + .ce_clear_mask = 0, + .ue_clear_mask = 0, + .dbgfs_name = "altr_l2_trigger", + .alloc_mem = l2_alloc_mem, + .free_mem = l2_free_mem, + .ecc_enable_mask = ALTR_L2_ECC_EN, + .ce_set_mask = (ALTR_L2_ECC_EN | ALTR_L2_ECC_INJS), + .ue_set_mask = (ALTR_L2_ECC_EN | ALTR_L2_ECC_INJD), + .trig_alloc_sz = ALTR_TRIG_L2C_BYTE_SIZE, +}; + +#endif /* CONFIG_EDAC_ALTERA_L2C */ + MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Thor Thayer"); -MODULE_DESCRIPTION("EDAC Driver for Altera SDRAM Controller"); +MODULE_DESCRIPTION("EDAC Driver for Altera Memories"); From d31e2e846b35370a2317a211a50d8248d46fc83b Mon Sep 17 00:00:00 2001 From: Thor Thayer Date: Wed, 10 Feb 2016 13:26:22 -0600 Subject: [PATCH 11/13] ARM: dts: Add Altera L2 Cache and OCRAM EDAC entries Add the device tree entries and bindings needed to support the Altera L2 cache and On-Chip RAM EDAC. This patch relies upon an earlier patch to declare and setup On-chip RAM properly: 8b907c8b62ac ("arm: dts: socfpga: Add OCRAM node") Signed-off-by: Thor Thayer Acked-by: Rob Herring Cc: devicetree@vger.kernel.org Cc: Dinh Nguyen Cc: galak@codeaurora.org Cc: grant.likely@linaro.org Cc: Ian Campbell Cc: ijc+devicetree@hellion.org.uk Cc: Kumar Gala Cc: linux-arm-kernel@lists.infradead.org Cc: linux@arm.linux.org.uk Cc: linux-doc@vger.kernel.org Cc: linux-edac Cc: Mark Rutland Cc: m.chehab@samsung.com Cc: Pawel Moll Cc: Rob Herring Cc: Russell King Link: http://lkml.kernel.org/r/1455132384-17108-2-git-send-email-tthayer@opensource.altera.com Signed-off-by: Borislav Petkov --- .../bindings/arm/altera/socfpga-eccmgr.txt | 49 +++++++++++++++++++ arch/arm/boot/dts/socfpga.dtsi | 20 ++++++++ 2 files changed, 69 insertions(+) create mode 100644 Documentation/devicetree/bindings/arm/altera/socfpga-eccmgr.txt diff --git a/Documentation/devicetree/bindings/arm/altera/socfpga-eccmgr.txt b/Documentation/devicetree/bindings/arm/altera/socfpga-eccmgr.txt new file mode 100644 index 000000000000..885f93d14ef9 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/altera/socfpga-eccmgr.txt @@ -0,0 +1,49 @@ +Altera SoCFPGA ECC Manager +This driver uses the EDAC framework to implement the SOCFPGA ECC Manager. +The ECC Manager counts and corrects single bit errors and counts/handles +double bit errors which are uncorrectable. + +Required Properties: +- compatible : Should be "altr,socfpga-ecc-manager" +- #address-cells: must be 1 +- #size-cells: must be 1 +- ranges : standard definition, should translate from local addresses + +Subcomponents: + +L2 Cache ECC +Required Properties: +- compatible : Should be "altr,socfpga-l2-ecc" +- reg : Address and size for ECC error interrupt clear registers. +- interrupts : Should be single bit error interrupt, then double bit error + interrupt. Note the rising edge type. + +On Chip RAM ECC +Required Properties: +- compatible : Should be "altr,socfpga-ocram-ecc" +- reg : Address and size for ECC error interrupt clear registers. +- iram : phandle to On-Chip RAM definition. +- interrupts : Should be single bit error interrupt, then double bit error + interrupt. Note the rising edge type. + +Example: + + eccmgr: eccmgr@ffd08140 { + compatible = "altr,socfpga-ecc-manager"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + l2-ecc@ffd08140 { + compatible = "altr,socfpga-l2-ecc"; + reg = <0xffd08140 0x4>; + interrupts = <0 36 1>, <0 37 1>; + }; + + ocram-ecc@ffd08144 { + compatible = "altr,socfpga-ocram-ecc"; + reg = <0xffd08144 0x4>; + iram = <&ocram>; + interrupts = <0 178 1>, <0 179 1>; + }; + }; diff --git a/arch/arm/boot/dts/socfpga.dtsi b/arch/arm/boot/dts/socfpga.dtsi index 3ed4abdaaa9c..15cbc747c242 100644 --- a/arch/arm/boot/dts/socfpga.dtsi +++ b/arch/arm/boot/dts/socfpga.dtsi @@ -656,6 +656,26 @@ i2c3: i2c@ffc07000 { status = "disabled"; }; + eccmgr: eccmgr@ffd08140 { + compatible = "altr,socfpga-ecc-manager"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + l2-ecc@ffd08140 { + compatible = "altr,socfpga-l2-ecc"; + reg = <0xffd08140 0x4>; + interrupts = <0 36 1>, <0 37 1>; + }; + + ocram-ecc@ffd08144 { + compatible = "altr,socfpga-ocram-ecc"; + reg = <0xffd08144 0x4>; + iram = <&ocram>; + interrupts = <0 178 1>, <0 179 1>; + }; + }; + L2: l2-cache@fffef000 { compatible = "arm,pl310-cache"; reg = <0xfffef000 0x1000>; From 4d1138380ed6f1cca622e7de8bcc877c04a52d0d Mon Sep 17 00:00:00 2001 From: Thor Thayer Date: Wed, 10 Feb 2016 13:26:23 -0600 Subject: [PATCH 12/13] ARM: socfpga: Enable L2 cache ECC on startup Enable ECC for L2 cache on machine startup. The ECC has to be enabled before data is stored in memory otherwise the ECC will fail on reads. Signed-off-by: Thor Thayer Acked-by: Dinh Nguyen Cc: devicetree@vger.kernel.org Cc: galak@codeaurora.org Cc: grant.likely@linaro.org Cc: ijc+devicetree@hellion.org.uk Cc: linux-arm-kernel@lists.infradead.org Cc: linux@arm.linux.org.uk Cc: linux-doc@vger.kernel.org Cc: linux-edac Cc: mark.rutland@arm.com Cc: m.chehab@samsung.com Cc: pawel.moll@arm.com Cc: robh+dt@kernel.org Cc: Russell King Link: http://lkml.kernel.org/r/1455132384-17108-3-git-send-email-tthayer@opensource.altera.com Signed-off-by: Borislav Petkov --- arch/arm/mach-socfpga/Makefile | 1 + arch/arm/mach-socfpga/core.h | 1 + arch/arm/mach-socfpga/l2_cache.c | 41 ++++++++++++++++++++++++++++++++ arch/arm/mach-socfpga/socfpga.c | 2 ++ 4 files changed, 45 insertions(+) create mode 100644 arch/arm/mach-socfpga/l2_cache.c diff --git a/arch/arm/mach-socfpga/Makefile b/arch/arm/mach-socfpga/Makefile index b8f9e238e4ab..e9ab7c9444ff 100644 --- a/arch/arm/mach-socfpga/Makefile +++ b/arch/arm/mach-socfpga/Makefile @@ -5,3 +5,4 @@ obj-y := socfpga.o obj-$(CONFIG_SMP) += headsmp.o platsmp.o obj-$(CONFIG_SOCFPGA_SUSPEND) += pm.o self-refresh.o +obj-$(CONFIG_EDAC_ALTERA_L2C) += l2_cache.o diff --git a/arch/arm/mach-socfpga/core.h b/arch/arm/mach-socfpga/core.h index 5bc6ea87cdf7..eb55d66a0eac 100644 --- a/arch/arm/mach-socfpga/core.h +++ b/arch/arm/mach-socfpga/core.h @@ -36,6 +36,7 @@ extern void socfpga_init_clocks(void); extern void socfpga_sysmgr_init(void); +void socfpga_init_l2_ecc(void); extern void __iomem *sys_manager_base_addr; extern void __iomem *rst_manager_base_addr; diff --git a/arch/arm/mach-socfpga/l2_cache.c b/arch/arm/mach-socfpga/l2_cache.c new file mode 100644 index 000000000000..e3907ab58d05 --- /dev/null +++ b/arch/arm/mach-socfpga/l2_cache.c @@ -0,0 +1,41 @@ +/* + * Copyright Altera Corporation (C) 2016. 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +#include +#include +#include + +void socfpga_init_l2_ecc(void) +{ + struct device_node *np; + void __iomem *mapped_l2_edac_addr; + + np = of_find_compatible_node(NULL, NULL, "altr,socfpga-l2-ecc"); + if (!np) { + pr_err("Unable to find socfpga-l2-ecc in dtb\n"); + return; + } + + mapped_l2_edac_addr = of_iomap(np, 0); + of_node_put(np); + if (!mapped_l2_edac_addr) { + pr_err("Unable to find L2 ECC mapping in dtb\n"); + return; + } + + /* Enable ECC */ + writel(0x01, mapped_l2_edac_addr); + iounmap(mapped_l2_edac_addr); +} diff --git a/arch/arm/mach-socfpga/socfpga.c b/arch/arm/mach-socfpga/socfpga.c index a1c0efaa8794..dd1ff07b0472 100644 --- a/arch/arm/mach-socfpga/socfpga.c +++ b/arch/arm/mach-socfpga/socfpga.c @@ -59,6 +59,8 @@ static void __init socfpga_init_irq(void) { irqchip_init(); socfpga_sysmgr_init(); + if (IS_ENABLED(CONFIG_EDAC_ALTERA_L2C)) + socfpga_init_l2_ecc(); } static void socfpga_cyclone5_restart(enum reboot_mode mode, const char *cmd) From 7cc5a5d3cd4cca0a3852d1500e8c50fe191bcc9d Mon Sep 17 00:00:00 2001 From: Thor Thayer Date: Wed, 10 Feb 2016 13:26:24 -0600 Subject: [PATCH 13/13] ARM: socfpga: Enable OCRAM ECC on startup Enable ECC for On-Chip RAM on machine startup. The ECC has to be enabled before data is stored in memory otherwise the ECC will fail on reads. Signed-off-by: Thor Thayer Acked-by: Dinh Nguyen Cc: devicetree@vger.kernel.org Cc: dougthompson@xmission.com Cc: galak@codeaurora.org Cc: grant.likely@linaro.org Cc: ijc+devicetree@hellion.org.uk Cc: linux-arm-kernel@lists.infradead.org Cc: linux@arm.linux.org.uk Cc: linux-doc@vger.kernel.org Cc: linux-edac Cc: mark.rutland@arm.com Cc: m.chehab@samsung.com Cc: pawel.moll@arm.com Cc: robh+dt@kernel.org Cc: Russell King Cc: tthayer.linux@gmail.com Cc: tthayer@opensource.altera.com Link: http://lkml.kernel.org/r/1455132384-17108-4-git-send-email-tthayer@opensource.altera.com Signed-off-by: Borislav Petkov --- arch/arm/mach-socfpga/Makefile | 1 + arch/arm/mach-socfpga/core.h | 1 + arch/arm/mach-socfpga/ocram.c | 49 +++++++++++++++++++++++++++++++++ arch/arm/mach-socfpga/socfpga.c | 3 ++ 4 files changed, 54 insertions(+) create mode 100644 arch/arm/mach-socfpga/ocram.c diff --git a/arch/arm/mach-socfpga/Makefile b/arch/arm/mach-socfpga/Makefile index e9ab7c9444ff..ed15db19e561 100644 --- a/arch/arm/mach-socfpga/Makefile +++ b/arch/arm/mach-socfpga/Makefile @@ -6,3 +6,4 @@ obj-y := socfpga.o obj-$(CONFIG_SMP) += headsmp.o platsmp.o obj-$(CONFIG_SOCFPGA_SUSPEND) += pm.o self-refresh.o obj-$(CONFIG_EDAC_ALTERA_L2C) += l2_cache.o +obj-$(CONFIG_EDAC_ALTERA_OCRAM) += ocram.o diff --git a/arch/arm/mach-socfpga/core.h b/arch/arm/mach-socfpga/core.h index eb55d66a0eac..575195be6687 100644 --- a/arch/arm/mach-socfpga/core.h +++ b/arch/arm/mach-socfpga/core.h @@ -37,6 +37,7 @@ extern void socfpga_init_clocks(void); extern void socfpga_sysmgr_init(void); void socfpga_init_l2_ecc(void); +void socfpga_init_ocram_ecc(void); extern void __iomem *sys_manager_base_addr; extern void __iomem *rst_manager_base_addr; diff --git a/arch/arm/mach-socfpga/ocram.c b/arch/arm/mach-socfpga/ocram.c new file mode 100644 index 000000000000..60ec643ac2be --- /dev/null +++ b/arch/arm/mach-socfpga/ocram.c @@ -0,0 +1,49 @@ +/* + * Copyright Altera Corporation (C) 2016. 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +#include +#include +#include +#include +#include + +#define ALTR_OCRAM_CLEAR_ECC 0x00000018 +#define ALTR_OCRAM_ECC_EN 0x00000019 + +void socfpga_init_ocram_ecc(void) +{ + struct device_node *np; + void __iomem *mapped_ocr_edac_addr; + + /* Find the OCRAM EDAC device tree node */ + np = of_find_compatible_node(NULL, NULL, "altr,socfpga-ocram-ecc"); + if (!np) { + pr_err("Unable to find socfpga-ocram-ecc\n"); + return; + } + + mapped_ocr_edac_addr = of_iomap(np, 0); + of_node_put(np); + if (!mapped_ocr_edac_addr) { + pr_err("Unable to map OCRAM ecc regs.\n"); + return; + } + + /* Clear any pending OCRAM ECC interrupts, then enable ECC */ + writel(ALTR_OCRAM_CLEAR_ECC, mapped_ocr_edac_addr); + writel(ALTR_OCRAM_ECC_EN, mapped_ocr_edac_addr); + + iounmap(mapped_ocr_edac_addr); +} diff --git a/arch/arm/mach-socfpga/socfpga.c b/arch/arm/mach-socfpga/socfpga.c index dd1ff07b0472..7e0aad2ec3d1 100644 --- a/arch/arm/mach-socfpga/socfpga.c +++ b/arch/arm/mach-socfpga/socfpga.c @@ -61,6 +61,9 @@ static void __init socfpga_init_irq(void) socfpga_sysmgr_init(); if (IS_ENABLED(CONFIG_EDAC_ALTERA_L2C)) socfpga_init_l2_ecc(); + + if (IS_ENABLED(CONFIG_EDAC_ALTERA_OCRAM)) + socfpga_init_ocram_ecc(); } static void socfpga_cyclone5_restart(enum reboot_mode mode, const char *cmd)