aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/soc/marvell/octeontx2-ghes/otx2-einj.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/soc/marvell/octeontx2-ghes/otx2-einj.c')
-rw-r--r--drivers/soc/marvell/octeontx2-ghes/otx2-einj.c166
1 files changed, 166 insertions, 0 deletions
diff --git a/drivers/soc/marvell/octeontx2-ghes/otx2-einj.c b/drivers/soc/marvell/octeontx2-ghes/otx2-einj.c
new file mode 100644
index 000000000000..812abc15e90f
--- /dev/null
+++ b/drivers/soc/marvell/octeontx2-ghes/otx2-einj.c
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * OcteonTX2 memory controller ECC injection
+ * Copyright Marvell Technologies. (C) 2019-2020. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+
+/*
+ * All DRAM/cache controller hardware is handled by ATF on these platforms
+ * and not visible to Non-Secure OS kernel.
+ * The EDAC functions are passed to ATF by OCTEONTX_EDAC SMC, which performs
+ * injection and reporting, and copies log stream back to kernel for reporting
+ * detail in syslog.
+ *
+ * This is minimal SMC stub approach, minimally providing hooks for usermode
+ * error-injection tools, to exercise either the legacy EDAC code of pre-4.18,
+ * or the standard SDEI/GHES RAS handling possible in newer kernels.
+ * It knows nothing of either, just asks ATF to corrupt memory.
+ * This allows LMC/etc details to be hidden from EL2, all RAS/EDAC
+ * work going to ATF/EL3 for security.
+ *
+ * For further details, see:
+ * ATF's docs/plat/marvell/marvell_ras.txt
+ * include/plat/marvell/octeontx/otx2/plat_ras.h
+ */
+
+#define OCTEONTX_EDAC 0xc2000c0b
+/* x1 is one of the following ... */
+#define OCTEONTX_EDAC_VER 0 /* report version */
+#define OCTEONTX_EDAC_INJECT 3 /* x2=addr x3=flags _F_xxx below */
+#define OCTEONTX_EDAC_MDC_CONST 4 /* read CAVM_MDC_CONST */
+#define OCTEONTX_EDAC_MDC_RW 5 /* read/write MDC */
+#define OCTEONTX_EDAC_MDC_ROM 6 /* read MDC_RAS_ROM x2=addr */
+
+#define OCTEONTX_EDAC_F_BITMASK 0x007 /* single bit to corrupt */
+#define OCTEONTX_EDAC_F_MULTI 0x008 /* corrupt multiple bits */
+#define OCTEONTX_EDAC_F_CLEVEL 0x070 /* cache level to corrupt (L0 == DRAM) */
+#define OCTEONTX_EDAC_F_ICACHE 0x080 /* Icache, not Dcache */
+#define OCTEONTX_EDAC_F_REREAD 0x100 /* read-back in EL3 */
+#define OCTEONTX_EDAC_F_PHYS 0x200 /* target is EL3-physical, not EL012 */
+
+#include <linux/arm-smccc.h>
+
+/*
+ * Module parameters are used here instead of debugfs because debugfs requires
+ * a kernel configuration option to be enabled, which potentially requires
+ * a configuration change and kernel rebuild.
+ * The use of error injection via this module is meant to be available at all
+ * times (when the module is loaded) and should not require a special kernel.
+ */
+static u64 smc_params[7];
+static u64 smc_result;
+static int smc_argc;
+
+/* an easily recognized value for logs */
+static const u64 test_val = 0x5555555555555555;
+
+/* target address for please-corrupt-EL1/EL2 I-cache/DRAM */
+static u64 ecc_test_target_fn(void)
+{
+ return test_val;
+}
+
+static int otx2_edac_smc(void)
+{
+ /* target address for please-corrupt-EL1/EL2 D-cache/DRAM: */
+ u64 ecc_test_target_data = test_val;
+ struct arm_smccc_res res;
+ bool test_read = false;
+ bool test_call = false;
+ u64 *a = smc_params;
+
+ /*
+ * Replace magic ECC-injection addresses:
+ * special ECC-injection addresses 0-3/4-7 are substituted by
+ * EL0-3 code as instr/data targets at that execution level.
+ * Any 0/4 addresses will have already been substituted
+ * by EL0 test harness, here we substitute EL1/EL2 targets.
+ * While 3/7 are replaced by ATF with its own test objects,
+ * we remind it to reread in its own context.
+ */
+ if (a[0] == OCTEONTX_EDAC_INJECT) {
+ a[2] &= ~OCTEONTX_EDAC_F_REREAD;
+ switch (a[1]) {
+ case 1 ... 2: /* EL0..EL2 D-space target */
+ a[1] = (u64)&ecc_test_target_data;
+ test_read = true;
+ break; /* EL0..EL2 I-space target */
+ case 5 ... 6:
+ a[1] = (u64)ecc_test_target_fn;
+ test_call = true;
+ break;
+ case 3: /* EL3 targets */
+ case 7:
+ a[2] |= OCTEONTX_EDAC_F_REREAD;
+ break;
+ }
+ }
+
+ arm_smccc_smc(OCTEONTX_EDAC, a[0], a[1], a[2], /* x1-x3 */
+ a[3], a[4], a[5], a[6], &res); /* x4-x7, result */
+ trace_printk("%s: OCTEONTX_EDAC(%llx, %llx, %llx, %llx) -> e?%ld\n",
+ __func__, a[0], a[1], a[2], a[3], res.a0);
+
+ if (test_read && ecc_test_target_data != test_val)
+ trace_printk("%s test_read mismatch\n", __func__);
+ if (test_call && ecc_test_target_fn() != test_val)
+ trace_printk("%s test_call mismatch\n", __func__);
+
+ return res.a0;
+}
+
+static int smc_params_set(const char *_str, const struct kernel_param *kp)
+{
+ /* as with param_array_set(), temporarily overwrites string */
+ char *str = (char *)_str;
+ int rc;
+
+ trace_printk("%s: (%s)\n", __func__, str);
+
+ if (!str)
+ return -EINVAL;
+
+ smc_result = -EBUSY;
+
+ for (smc_argc = 0; smc_argc < 7 && *str; smc_argc++) {
+ int len = strcspn(str, ",");
+ char *nxt = len ? str + len + 1 : "";
+
+ if (len)
+ str[len] = '\0';
+ rc = kstrtoull(str, 0, &smc_params[smc_argc]);
+
+ trace_printk("%s: (%s/%s) smc_params[%d]=%llx e?%d\n",
+ __func__, str, nxt, smc_argc,
+ smc_params[smc_argc], rc);
+ if (len)
+ str[len] = ',';
+ str = nxt;
+ trace_printk("%s: smc_params[%d]=%llx\n",
+ __func__, smc_argc, smc_params[smc_argc]);
+ }
+
+ smc_result = otx2_edac_smc();
+ trace_printk("%s: result: %llx\n", __func__, smc_result);
+ return 0;
+}
+
+static int smc_params_get(char *buffer, const struct kernel_param *kp)
+{
+ return sprintf(buffer, "%lld\n", smc_result);
+}
+
+static const struct kernel_param_ops smc_params_ops = {
+ .set = smc_params_set,
+ .get = smc_params_get,
+};
+
+module_param_cb(smc_params, &smc_params_ops, smc_params, 0644);
+MODULE_PARM_DESC(smc_params, "call/return values for OCTEONTX_EDAC SMC");
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Marvell Semiconductor");
+MODULE_DESCRIPTION("OcteonTX2 ECC injector stub");