[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20251028092232.773991-6-niravkumarlaxmidas.rabara@altera.com>
Date: Tue, 28 Oct 2025 17:22:31 +0800
From: niravkumarlaxmidas.rabara@...era.com
To: dinguyen@...nel.org,
matthew.gerlach@...era.com,
robh@...nel.org,
krzk+dt@...nel.org,
conor+dt@...nel.org,
bp@...en8.de,
tony.luck@...el.com
Cc: linux-edac@...r.kernel.org,
devicetree@...r.kernel.org,
linux-kernel@...r.kernel.org,
Niravkumar L Rabara <niravkumarlaxmidas.rabara@...era.com>
Subject: [PATCH 5/6] EDAC/altera: Add support for CRAM SEU error handling on SoCFPGA
From: Niravkumar L Rabara <niravkumarlaxmidas.rabara@...era.com>
Add new EDAC driver support for detecting and handling Single Event Upset
(SEU) errors in the FPGA Configuration RAM (CRAM) on Altera SoCFPGA
devices.
The Secure Device Manager (SDM) is responsible for detecting correctable
and uncorrectable SEU errors and notifies the CPU through a dedicated
interrupt. Upon receiving the interrupt, the driver invokes an SMC call
to the ARM Trusted Firmware (ATF) to query the error status.
The ATF, in turn, communicates with the SDM via the mailbox interface to
retrieve the error details and returns to the driver.
Signed-off-by: Niravkumar L Rabara <niravkumarlaxmidas.rabara@...era.com>
---
drivers/edac/Kconfig | 12 ++
drivers/edac/altera_edac.c | 178 +++++++++++++++++++
drivers/edac/altera_edac.h | 9 +
include/linux/firmware/intel/stratix10-smc.h | 37 ++++
4 files changed, 236 insertions(+)
diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig
index 33a9fccde2fe..701b15e73a39 100644
--- a/drivers/edac/Kconfig
+++ b/drivers/edac/Kconfig
@@ -477,6 +477,18 @@ config EDAC_ALTERA_SDMMC
Support for error detection and correction on the
Altera SDMMC FIFO Memory for Altera SoCs.
+config EDAC_ALTERA_CRAM_SEU
+ bool "Altera CRAM SEU"
+ depends on EDAC_ALTERA=y && 64BIT
+ help
+ Support for error detection and correction on Altera SoCs for
+ FPGA Configuration RAM(CRAM) Single Event Upset(SEU).
+ The SEU errors caused by radiation or other transient events are
+ monitored by the Secure Device Manager (SDM), which notifies the
+ CPU through a dedicated interrupt.
+ This driver uses an SMC interface to query the error status and
+ report events to the EDAC framework.
+
config EDAC_SIFIVE
bool "Sifive platform EDAC driver"
depends on EDAC=y && SIFIVE_CCACHE
diff --git a/drivers/edac/altera_edac.c b/drivers/edac/altera_edac.c
index a82c3b01be1a..ac2151c625a2 100644
--- a/drivers/edac/altera_edac.c
+++ b/drivers/edac/altera_edac.c
@@ -656,6 +656,19 @@ static const struct file_operations altr_edac_a10_device_inject_fops __maybe_unu
.llseek = generic_file_llseek,
};
+#if IS_ENABLED(CONFIG_EDAC_ALTERA_CRAM_SEU)
+static ssize_t __maybe_unused
+altr_edac_seu_trig(struct file *file, const char __user *user_buf,
+ size_t count, loff_t *ppos);
+
+static const struct file_operations
+altr_edac_cram_inject_fops __maybe_unused = {
+ .open = simple_open,
+ .write = altr_edac_seu_trig,
+ .llseek = generic_file_llseek,
+};
+#endif
+
#ifdef CONFIG_EDAC_ALTERA_IO96B
static ssize_t __maybe_unused
altr_edac_io96b_device_trig(struct file *file, const char __user *user_buf,
@@ -1492,6 +1505,56 @@ static const struct edac_device_prv_data a10_usbecc_data = {
#endif /* CONFIG_EDAC_ALTERA_USB */
+#if IS_ENABLED(CONFIG_EDAC_ALTERA_CRAM_SEU)
+static irqreturn_t seu_irq_handler(int irq, void *dev_id)
+{
+ struct altr_edac_device_dev *dci = dev_id;
+ struct arm_smccc_res result;
+
+ arm_smccc_smc(INTEL_SIP_SMC_SEU_ERR_STATUS, 0,
+ 0, 0, 0, 0, 0, 0, &result);
+
+ if ((u32)result.a0) {
+ edac_printk(KERN_ERR, EDAC_DEVICE,
+ "SEU %s: Count=0x%X, SecAddr=0x%X, ErrData=0x%X\n",
+ ((u32)result.a2 & BIT(28)) == 0 ? "UE" : "CE",
+ (u32)result.a0, (u32)result.a1, (u32)result.a2);
+
+ if ((u32)result.a2 & BIT(28))
+ edac_device_handle_ce(dci->edac_dev, 0, 0, dci->edac_dev_name);
+ else
+ edac_device_handle_ue(dci->edac_dev, 0, 0, dci->edac_dev_name);
+ }
+ return IRQ_HANDLED;
+}
+
+static ssize_t __maybe_unused
+altr_edac_seu_trig(struct file *file, const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct edac_device_ctl_info *edac_dci = file->private_data;
+ struct altr_edac_device_dev *dev = edac_dci->pvt_info;
+ u8 trig_type;
+ struct arm_smccc_res result;
+
+ if (!user_buf || get_user(trig_type, user_buf))
+ return -EFAULT;
+
+ if (trig_type == ALTR_UE_TRIGGER_CHAR)
+ arm_smccc_smc(INTEL_SIP_SMC_SAFE_INJECT_SEU_ERR,
+ ((u64)dev->seu.ue_msb << 32) |
+ dev->seu.ue_lsb,
+ 2, 0, 0, 0, 0, 0, &result);
+ else
+ arm_smccc_smc(INTEL_SIP_SMC_SAFE_INJECT_SEU_ERR,
+ ((u64)dev->seu.ce_msb << 32) |
+ dev->seu.ce_lsb, 2, 0, 0, 0,
+ 0, 0, &result);
+
+ return count;
+}
+#endif
+
/********************** QSPI Device Functions **********************/
#ifdef CONFIG_EDAC_ALTERA_QSPI
@@ -2031,6 +2094,117 @@ static int get_s10_sdram_edac_resource(struct device_node *np,
return ret;
}
+#if IS_ENABLED(CONFIG_EDAC_ALTERA_CRAM_SEU)
+static int altr_edac_seu_device_add(struct altr_arria10_edac *edac,
+ struct platform_device *pdev, struct device_node *dev_node)
+{
+ struct edac_device_ctl_info *dci;
+ struct altr_edac_device_dev *altdev;
+ char *ecc_name = kstrdup(dev_node->name, GFP_KERNEL);
+ int edac_idx;
+ int seu_irq;
+ int rc = 0;
+
+ seu_irq = platform_get_irq_byname(pdev, "sdm_seu");
+ if (seu_irq < 0) {
+ dev_warn(&pdev->dev, "no %s IRQ defined\n", "sdm_seu");
+ return 0;
+ }
+
+ edac_idx = edac_device_alloc_index();
+ dci = edac_device_alloc_ctl_info(sizeof(*altdev), ecc_name,
+ 1, ecc_name, 1, 0, edac_idx);
+ if (!dci) {
+ edac_printk(KERN_ERR, EDAC_DEVICE,
+ "%s: Unable to allocate EDAC device\n", ecc_name);
+ rc = -ENOMEM;
+ goto err_release_group;
+ }
+
+ altdev = dci->pvt_info;
+ dci->dev = edac->dev;
+ altdev->edac_dev_name = ecc_name;
+ altdev->edac_idx = edac_idx;
+ altdev->edac = edac;
+ altdev->edac_dev = dci;
+ altdev->ddev = *edac->dev;
+ dci->dev = &altdev->ddev;
+ dci->ctl_name = "Altera ECC Manager";
+ dci->mod_name = ecc_name;
+ dci->dev_name = ecc_name;
+
+ rc = of_property_read_u32(dev_node, "altr,seu-safe-inject-ce-msb",
+ &altdev->seu.ce_msb);
+ if (rc) {
+ edac_printk(KERN_ERR, EDAC_DEVICE,
+ "Missing - altr,seu-safe-inject-ce-msb\n");
+ return -EINVAL;
+ }
+
+ rc = of_property_read_u32(dev_node, "altr,seu-safe-inject-ce-lsb",
+ &altdev->seu.ce_lsb);
+ if (rc) {
+ edac_printk(KERN_ERR, EDAC_DEVICE,
+ "Missing - altr,seu-safe-inject-ce-lsb\n");
+ return -EINVAL;
+ }
+
+ rc = of_property_read_u32(dev_node, "altr,seu-safe-inject-ue-msb",
+ &altdev->seu.ue_msb);
+ if (rc) {
+ edac_printk(KERN_ERR, EDAC_DEVICE,
+ "Missing - altr,seu-safe-inject-ue-msb\n");
+ return -EINVAL;
+ }
+
+ rc = of_property_read_u32(dev_node, "altr,seu-safe-inject-ue-lsb",
+ &altdev->seu.ue_lsb);
+ if (rc) {
+ edac_printk(KERN_ERR, EDAC_DEVICE,
+ "Missing - altr,seu-safe-inject-ue-lsb\n");
+ return -EINVAL;
+ }
+
+ altdev->seu_irq = seu_irq;
+ rc = devm_request_threaded_irq(edac->dev, altdev->seu_irq, NULL,
+ seu_irq_handler, IRQF_ONESHOT,
+ ecc_name, altdev);
+ if (rc) {
+ edac_printk(KERN_ERR, EDAC_DEVICE, "No SEU IRQ resource\n");
+ goto err_release_group1;
+ }
+
+ rc = edac_device_add_device(dci);
+ if (rc) {
+ dev_err(edac->dev, "edac_device_add_device failed\n");
+ rc = -ENOMEM;
+ goto err_release_group1;
+ }
+
+ if (IS_ENABLED(CONFIG_EDAC_DEBUG)) {
+ altdev->debugfs_dir = edac_debugfs_create_dir(ecc_name);
+ if (!altdev->debugfs_dir) {
+ rc = -EBUSY;
+ goto err_release_group1;
+ }
+
+ if (!edac_debugfs_create_file("altr_trigger", 0200,
+ altdev->debugfs_dir, dci,
+ &altr_edac_cram_inject_fops))
+ debugfs_remove_recursive(altdev->debugfs_dir);
+ }
+ return 0;
+
+err_release_group1:
+ edac_device_free_ctl_info(dci);
+err_release_group:
+ edac_printk(KERN_ERR, EDAC_DEVICE,
+ "%s:Error setting up EDAC device: %d\n", ecc_name, rc);
+
+ return rc;
+}
+#endif
+
static int altr_edac_a10_device_add(struct altr_arria10_edac *edac,
struct device_node *np)
{
@@ -2421,6 +2595,10 @@ static int altr_edac_a10_probe(struct platform_device *pdev)
if (of_match_node(altr_edac_a10_device_of_match, child))
altr_edac_a10_device_add(edac, child);
+#if IS_ENABLED(CONFIG_EDAC_ALTERA_CRAM_SEU)
+ else if (of_device_is_compatible(child, "altr,socfpga-cram-seu"))
+ altr_edac_seu_device_add(edac, pdev, child);
+#endif
#ifdef CONFIG_EDAC_ALTERA_SDRAM
else if (of_device_is_compatible(child, "altr,sdram-edac-a10"))
diff --git a/drivers/edac/altera_edac.h b/drivers/edac/altera_edac.h
index a2c8b80eefa8..8b475dc692e1 100644
--- a/drivers/edac/altera_edac.h
+++ b/drivers/edac/altera_edac.h
@@ -410,6 +410,13 @@ struct edac_device_prv_data {
bool panic;
};
+struct altr_seu {
+ u32 ce_msb;
+ u32 ce_lsb;
+ u32 ue_msb;
+ u32 ue_lsb;
+};
+
struct altr_edac_device_dev {
struct list_head next;
void __iomem *base;
@@ -424,6 +431,8 @@ struct altr_edac_device_dev {
int edac_idx;
int io96b0_irq;
int io96b1_irq;
+ int seu_irq;
+ struct altr_seu seu;
};
struct altr_arria10_edac {
diff --git a/include/linux/firmware/intel/stratix10-smc.h b/include/linux/firmware/intel/stratix10-smc.h
index 283597022e61..87e13683776f 100644
--- a/include/linux/firmware/intel/stratix10-smc.h
+++ b/include/linux/firmware/intel/stratix10-smc.h
@@ -620,6 +620,43 @@ INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_COMPLETED_WRITE)
#define INTEL_SIP_SMC_FCS_GET_PROVISION_DATA \
INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FCS_GET_PROVISION_DATA)
+/**
+ * Request INTEL_SIP_SMC_SEU_ERR_STATUS
+ * Sync call to get previous Double Bit ECC error information.
+ *
+ * Call register usage:
+ * a0 INTEL_SIP_SMC_SEU_ERR_STATUS
+ * a1-7 not used
+ *
+ * Return status:
+ * a0 INTEL_SIP_SMC_STATUS_OK, INTEL_SIP_SMC_STATUS_NOT_SUPPORTED or
+ * INTEL_SIP_SMC_STATUS_ERROR
+ * a1 error count of response data
+ * a2 sector address of response data
+ * a3 error information
+ */
+#define INTEL_SIP_SMC_FUNCID_SEU_ERR_STATUS 153
+#define INTEL_SIP_SMC_SEU_ERR_STATUS \
+ INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_SEU_ERR_STATUS)
+
+/**
+ * Request INTEL_SIP_SMC_SAFE_INJECT_SEU_ERR
+ * Sync call to inject SEU Error.
+ *
+ * Call register usage:
+ * a0 INTEL_SIP_SMC_FUNCID_SAFE_INJECT_SEU_ERR
+ * a1 Number of words
+ * a2-7 not used
+ *
+ * Return status:
+ * a0 INTEL_SIP_SMC_STATUS_OK, INTEL_SIP_SMC_STATUS_NOT_SUPPORTED or
+ * INTEL_SIP_SMC_STATUS_ERROR
+ * a1-a3 Not used
+ */
+#define INTEL_SIP_SMC_FUNCID_SAFE_INJECT_SEU_ERR 154
+#define INTEL_SIP_SMC_SAFE_INJECT_SEU_ERR \
+ INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_SAFE_INJECT_SEU_ERR)
+
/**
* Request INTEL_SIP_SMC_IO96B_INJECT_ECC_ERR
* Sync call to inject IO96B ECC Error.
--
2.25.1
Powered by blists - more mailing lists