[Cbe-oss-dev] [PATCH 2/2] edac: Add Cell memory controller

Benjamin Herrenschmidt benh at kernel.crashing.org
Wed Oct 3 16:29:49 EST 2007


Adds driver for the Cell memory controller when used without a
Hypervisor such as on the IBM Cell blades. There might still
be some improvements to do to this such as finding if it's
possible to properly obtain more details about the address
of the error but it's good enough already to report CE counts
which is our main priority at the moment.

Signed-off-by: Benjamin Herrenschmidt <benh at kernel.crashing.org>
---

 arch/powerpc/platforms/cell/cbe_regs.h |   48 +++++-
 arch/powerpc/platforms/cell/setup.c    |   10 +
 drivers/edac/Kconfig                   |    7 
 drivers/edac/Makefile                  |    1 
 drivers/edac/cell-edac.c               |  258 +++++++++++++++++++++++++++++++++
 5 files changed, 322 insertions(+), 2 deletions(-)

Index: linux-work/arch/powerpc/platforms/cell/cbe_regs.h
===================================================================
--- linux-work.orig/arch/powerpc/platforms/cell/cbe_regs.h	2007-09-28 11:42:05.000000000 +1000
+++ linux-work/arch/powerpc/platforms/cell/cbe_regs.h	2007-10-03 14:17:25.000000000 +1000
@@ -244,16 +244,60 @@ struct cbe_mic_tm_regs {
 	u64	slow_fast_timer_0;				/* 0x0090 */
 	u64	slow_next_timer_0;				/* 0x0098 */
 
-	u8	pad_0x00a0_0x01c0[0x01c0 - 0x0a0];		/* 0x00a0 */
+	u8	pad_0x00a0_0x00f8[0x00f8 - 0x00a0];		/* 0x00a0 */
+	u64    	mic_df_ecc_address_0;				/* 0x00f8 */
+
+	u8	pad_0x0100_0x01b8[0x01b8 - 0x0100];		/* 0x0100 */
+	u64    	mic_df_ecc_address_1;				/* 0x01b8 */
 
 	u64	mic_ctl_cnfg_1;					/* 0x01c0 */
 #define CBE_MIC_DISABLE_PWR_SAV_1	0x8000000000000000LL
+
 	u64	pad_0x01c8;					/* 0x01c8 */
 
 	u64	slow_fast_timer_1;				/* 0x01d0 */
 	u64	slow_next_timer_1;				/* 0x01d8 */
 
-	u8	pad_0x01e0_0x1000[0x1000 - 0x01e0];		/* 0x01e0 */
+	u8	pad_0x01e0_0x0208[0x0208 - 0x01e0];		/* 0x01e0 */
+	u64	mic_exc;					/* 0x0208 */
+#define CBE_MIC_EXC_BLOCK_SCRUB		0x0800000000000000ULL
+#define CBE_MIC_EXC_FAST_SCRUB		0x0100000000000000ULL
+
+	u64	mic_mnt_cfg;					/* 0x0210 */
+#define CBE_MIC_MNT_CFG_CHAN_0_POP	0x0002000000000000ULL
+#define CBE_MIC_MNT_CFG_CHAN_1_POP	0x0004000000000000ULL
+
+	u64	mic_df_config;					/* 0x0218 */
+#define CBE_MIC_ECC_DISABLE_0		0x4000000000000000ULL
+#define CBE_MIC_ECC_REP_SINGLE_0	0x2000000000000000ULL
+#define CBE_MIC_ECC_DISABLE_1		0x0080000000000000ULL
+#define CBE_MIC_ECC_REP_SINGLE_1	0x0040000000000000ULL
+
+	u8	pad_0x0220_0x0230[0x0230 - 0x0220];		/* 0x0220 */
+	u64	mic_fir;					/* 0x0230 */
+#define CBE_MIC_FIR_ECC_SINGLE_0_ERR	0x0200000000000000ULL
+#define CBE_MIC_FIR_ECC_MULTI_0_ERR	0x0100000000000000ULL
+#define CBE_MIC_FIR_ECC_SINGLE_1_ERR	0x0080000000000000ULL
+#define CBE_MIC_FIR_ECC_MULTI_1_ERR	0x0040000000000000ULL
+#define CBE_MIC_FIR_ECC_ERR_MASK	0xffff000000000000ULL
+#define CBE_MIC_FIR_ECC_SINGLE_0_CTE	0x0000020000000000ULL
+#define CBE_MIC_FIR_ECC_MULTI_0_CTE	0x0000010000000000ULL
+#define CBE_MIC_FIR_ECC_SINGLE_1_CTE	0x0000008000000000ULL
+#define CBE_MIC_FIR_ECC_MULTI_1_CTE	0x0000004000000000ULL
+#define CBE_MIC_FIR_ECC_CTE_MASK	0x0000ffff00000000ULL
+#define CBE_MIC_FIR_ECC_SINGLE_0_RESET	0x0000000002000000ULL
+#define CBE_MIC_FIR_ECC_MULTI_0_RESET	0x0000000001000000ULL
+#define CBE_MIC_FIR_ECC_SINGLE_1_RESET	0x0000000000800000ULL
+#define CBE_MIC_FIR_ECC_MULTI_1_RESET	0x0000000000400000ULL
+#define CBE_MIC_FIR_ECC_RESET_MASK	0x00000000ffff0000ULL
+#define CBE_MIC_FIR_ECC_SINGLE_0_SET	0x0000000000000200ULL
+#define CBE_MIC_FIR_ECC_MULTI_0_SET	0x0000000000000100ULL
+#define CBE_MIC_FIR_ECC_SINGLE_1_SET	0x0000000000000080ULL
+#define CBE_MIC_FIR_ECC_MULTI_1_SET	0x0000000000000040ULL
+#define CBE_MIC_FIR_ECC_SET_MASK	0x000000000000ffffULL
+	u64	mic_fir_debug;					/* 0x0238 */
+
+	u8	pad_0x0240_0x1000[0x1000 - 0x0240];		/* 0x0240 */
 };
 
 extern struct cbe_mic_tm_regs __iomem *cbe_get_mic_tm_regs(struct device_node *np);
Index: linux-work/arch/powerpc/platforms/cell/setup.c
===================================================================
--- linux-work.orig/arch/powerpc/platforms/cell/setup.c	2007-09-28 11:42:05.000000000 +1000
+++ linux-work/arch/powerpc/platforms/cell/setup.c	2007-10-03 14:17:25.000000000 +1000
@@ -83,12 +83,22 @@ static void cell_progress(char *s, unsig
 
 static int __init cell_publish_devices(void)
 {
+	int node;
+
 	if (!machine_is(cell))
 		return 0;
 
 	/* Publish OF platform devices for southbridge IOs */
 	of_platform_bus_probe(NULL, NULL, NULL);
 
+	/* There is no device for the MIC memory controller, thus we create
+	 * a platform device for it to attach the EDAC driver to.
+	 */
+	for_each_online_node(node) {
+		if (cbe_get_cpu_mic_tm_regs(cbe_node_to_cpu(node)) == NULL)
+			continue;
+		platform_device_register_simple("cbe-mic", node, NULL, 0);
+	}
 	return 0;
 }
 device_initcall(cell_publish_devices);
Index: linux-work/drivers/edac/Kconfig
===================================================================
--- linux-work.orig/drivers/edac/Kconfig	2007-09-28 11:42:06.000000000 +1000
+++ linux-work/drivers/edac/Kconfig	2007-10-03 15:48:03.000000000 +1000
@@ -131,5 +131,12 @@ config EDAC_PASEMI
 	  Support for error detection and correction on PA Semi
 	  PWRficient.
 
+config EDAC_CELL
+	tristate "Cell Broadband Engine memory controller"
+	depends on EDAC_MM_EDAC && PPC_CELL_NATIVE
+	help
+	  Support for error detection and correction on the
+	  Cell Broadband Engine internal memory controller
+	  on platform without a hypervisor
 
 endif # EDAC
Index: linux-work/drivers/edac/Makefile
===================================================================
--- linux-work.orig/drivers/edac/Makefile	2007-09-28 11:42:06.000000000 +1000
+++ linux-work/drivers/edac/Makefile	2007-10-03 15:48:03.000000000 +1000
@@ -28,4 +28,5 @@ obj-$(CONFIG_EDAC_I3000)		+= i3000_edac.
 obj-$(CONFIG_EDAC_I82860)		+= i82860_edac.o
 obj-$(CONFIG_EDAC_R82600)		+= r82600_edac.o
 obj-$(CONFIG_EDAC_PASEMI)		+= pasemi_edac.o
+obj-$(CONFIG_EDAC_CELL)			+= cell-edac.o
 
Index: linux-work/drivers/edac/cell-edac.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-work/drivers/edac/cell-edac.c	2007-10-03 15:48:35.000000000 +1000
@@ -0,0 +1,258 @@
+/*
+ * Cell MIC driver for ECC counting
+ *
+ * Copyright 2007 Benjamin Herrenschmidt, IBM Corp.
+ *                <benh at kernel.crashing.org>
+ *
+ * This file may be distributed under the terms of the
+ * GNU General Public License.
+ */
+#undef DEBUG
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/stop_machine.h>
+#include <asm/io.h>
+#include <asm/machdep.h>
+#include <../arch/powerpc/platforms/cell/cbe_regs.h>
+
+#include "edac_core.h"
+
+struct cell_edac_priv
+{
+	struct cbe_mic_tm_regs __iomem	*regs;
+	int				node;
+	int				chanmask;
+#ifdef DEBUG
+	u64				prev_fir;
+#endif
+};
+
+static void cell_edac_count_ce(struct mem_ctl_info *mci, int chan, u64 ar)
+{
+	struct cell_edac_priv		*priv = mci->pvt_info;
+	struct csrow_info		*csrow = &mci->csrows[0];
+	unsigned long			address, pfn, offset;
+
+	dev_dbg(mci->dev, "ECC CE err on node %d, channel %d, ar = 0x%016lx\n",
+		priv->node, chan, ar);
+
+	/* Address decoding is likely a bit bogus, to dbl check */
+	address = (ar & 0xffffffffe0000000ul) >> 29;
+	if (priv->chanmask == 0x3)
+		address = (address << 1) | chan;
+	pfn = address >> PAGE_SHIFT;
+	offset = address & ~PAGE_MASK;
+
+	/* TODO: Decoding of the error addresss */
+	edac_mc_handle_ce(mci, csrow->first_page + pfn, offset,
+			  0, 0, chan, "");
+}
+
+static void cell_edac_count_ue(struct mem_ctl_info *mci, int chan, u64 ar)
+{
+	struct cell_edac_priv		*priv = mci->pvt_info;
+	struct csrow_info		*csrow = &mci->csrows[0];
+	unsigned long			address, pfn, offset;
+
+	dev_dbg(mci->dev, "ECC UE err on node %d, channel %d, ar = 0x%016lx\n",
+		priv->node, chan, ar);
+
+	/* Address decoding is likely a bit bogus, to dbl check */
+	address = (ar & 0xffffffffe0000000ul) >> 29;
+	if (priv->chanmask == 0x3)
+		address = (address << 1) | chan;
+	pfn = address >> PAGE_SHIFT;
+	offset = address & ~PAGE_MASK;
+
+	/* TODO: Decoding of the error addresss */
+	edac_mc_handle_ue(mci, csrow->first_page + pfn, offset, 0, "");
+}
+
+static void cell_edac_check(struct mem_ctl_info *mci)
+{
+	struct cell_edac_priv		*priv = mci->pvt_info;
+	u64				fir, addreg, clear = 0;
+
+	fir = in_be64(&priv->regs->mic_fir);
+#ifdef DEBUG
+	if (fir != priv->prev_fir) {
+		dev_dbg(mci->dev, "fir change : 0x%016lx\n", fir);
+		priv->prev_fir = fir;
+	}
+#endif
+	if ((priv->chanmask & 0x1) && (fir & CBE_MIC_FIR_ECC_SINGLE_0_ERR)) {
+		addreg = in_be64(&priv->regs->mic_df_ecc_address_0);
+		clear |= CBE_MIC_FIR_ECC_SINGLE_0_RESET;
+		cell_edac_count_ce(mci, 0, addreg);
+	}
+	if ((priv->chanmask & 0x2) && (fir & CBE_MIC_FIR_ECC_SINGLE_1_ERR)) {
+		addreg = in_be64(&priv->regs->mic_df_ecc_address_1);
+		clear |= CBE_MIC_FIR_ECC_SINGLE_1_RESET;
+		cell_edac_count_ce(mci, 1, addreg);
+	}
+	if ((priv->chanmask & 0x1) && (fir & CBE_MIC_FIR_ECC_MULTI_0_ERR)) {
+		addreg = in_be64(&priv->regs->mic_df_ecc_address_0);
+		clear |= CBE_MIC_FIR_ECC_MULTI_0_RESET;
+		cell_edac_count_ue(mci, 0, addreg);
+	}
+	if ((priv->chanmask & 0x2) && (fir & CBE_MIC_FIR_ECC_MULTI_1_ERR)) {
+		addreg = in_be64(&priv->regs->mic_df_ecc_address_1);
+		clear |= CBE_MIC_FIR_ECC_MULTI_1_RESET;
+		cell_edac_count_ue(mci, 1, addreg);
+	}
+
+	/* The procedure for clearing FIR bits is a bit ... weird */
+	if (clear) {
+		fir &= ~(CBE_MIC_FIR_ECC_ERR_MASK | CBE_MIC_FIR_ECC_SET_MASK);
+		fir |= CBE_MIC_FIR_ECC_RESET_MASK;
+		fir &= ~clear;
+		out_be64(&priv->regs->mic_fir, fir);
+		(void)in_be64(&priv->regs->mic_fir);
+		mb();
+#ifdef DEBUG
+		fir = in_be64(&priv->regs->mic_fir);
+		dev_dbg(mci->dev, "fir clear  : 0x%016lx\n", fir);
+#endif
+	}
+}
+
+static void __devinit cell_edac_init_csrows(struct mem_ctl_info *mci)
+{
+	struct csrow_info		*csrow = &mci->csrows[0];
+	struct cell_edac_priv		*priv = mci->pvt_info;
+	struct device_node		*np;
+
+	for (np = NULL;
+	     (np = of_find_node_by_name(np, "memory")) != NULL;) {
+		struct resource r;
+
+		/* We "know" that the Cell firmware only creates one entry
+		 * in the "memory" nodes. If that changes, this code will
+		 * need to be adapted.
+		 */
+		if (of_address_to_resource(np, 0, &r))
+			continue;
+		if (of_node_to_nid(np) != priv->node)
+			continue;
+		csrow->first_page = r.start >> PAGE_SHIFT;
+		csrow->nr_pages = (r.end - r.start + 1) >> PAGE_SHIFT;
+		csrow->last_page = csrow->first_page + csrow->nr_pages - 1;
+		csrow->mtype = MEM_XDR;
+		csrow->edac_mode = EDAC_FLAG_EC | EDAC_FLAG_SECDED;
+		dev_dbg(mci->dev,
+			"Initialized on node %d, chanmask=0x%x,"
+			" first_page=0x%lx, nr_pages=0x%x\n",
+			priv->node, priv->chanmask,
+			csrow->first_page, csrow->nr_pages);
+		break;
+	}
+}
+
+static int __devinit cell_edac_probe(struct platform_device *pdev)
+{
+	struct cbe_mic_tm_regs __iomem	*regs;
+	struct mem_ctl_info		*mci;
+	struct cell_edac_priv		*priv;
+	u64				reg;
+	int				rc, chanmask;
+
+	regs = cbe_get_cpu_mic_tm_regs(cbe_node_to_cpu(pdev->id));
+	if (regs == NULL)
+		return -ENODEV;
+
+	/* Get channel population */
+	reg = in_be64(&regs->mic_mnt_cfg);
+	dev_dbg(&pdev->dev, "MIC_MNT_CFG = 0x%016lx\n", reg);
+	chanmask = 0;
+	if (reg & CBE_MIC_MNT_CFG_CHAN_0_POP)
+		chanmask |= 0x1;
+	if (reg & CBE_MIC_MNT_CFG_CHAN_1_POP)
+		chanmask |= 0x2;
+	if (chanmask == 0) {
+		dev_warn(&pdev->dev,
+			 "Yuck ! No channel populated ? Aborting !\n");
+		return -ENODEV;
+	}
+	dev_dbg(&pdev->dev, "Initial FIR = 0x%016lx\n",
+		in_be64(&regs->mic_fir));
+
+	/* Allocate & init EDAC MC data structure */
+	mci = edac_mc_alloc(sizeof(struct cell_edac_priv), 1,
+			    chanmask == 3 ? 2 : 1, pdev->id);
+	if (mci == NULL)
+		return -ENOMEM;
+	priv = mci->pvt_info;
+	priv->regs = regs;
+	priv->node = pdev->id;
+	priv->chanmask = chanmask;
+	mci->dev = &pdev->dev;
+	mci->mtype_cap = MEM_FLAG_XDR;
+	mci->edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_EC | EDAC_FLAG_SECDED;
+	mci->edac_cap = EDAC_FLAG_EC | EDAC_FLAG_SECDED;
+	mci->mod_name = "cell-edac";
+	mci->ctl_name = "MIC";
+	mci->dev_name = pdev->dev.bus_id;
+	mci->edac_check = cell_edac_check;
+	cell_edac_init_csrows(mci);
+
+	/* Register with EDAC core */
+	rc = edac_mc_add_mc(mci);
+	if (rc) {
+		dev_err(&pdev->dev, "failed to register with EDAC core\n");
+		edac_mc_free(mci);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int __devexit cell_edac_remove(struct platform_device *pdev)
+{
+	struct mem_ctl_info *mci = edac_mc_del_mc(&pdev->dev);
+	if (mci)
+		edac_mc_free(mci);
+	return 0;
+}
+
+static struct platform_driver cell_edac_driver =
+{
+	.driver		= {
+		.name	= "cbe-mic",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= cell_edac_probe,
+	.remove		= cell_edac_remove,
+};
+
+static int __init cell_edac_init(void)
+{
+	/* Sanity check registers data structure */
+	BUILD_BUG_ON(offsetof(struct cbe_mic_tm_regs,
+			      mic_df_ecc_address_0) != 0xf8);
+	BUILD_BUG_ON(offsetof(struct cbe_mic_tm_regs,
+			      mic_df_ecc_address_1) != 0x1b8);
+	BUILD_BUG_ON(offsetof(struct cbe_mic_tm_regs,
+			      mic_df_config) != 0x218);
+	BUILD_BUG_ON(offsetof(struct cbe_mic_tm_regs,
+			      mic_fir) != 0x230);
+	BUILD_BUG_ON(offsetof(struct cbe_mic_tm_regs,
+			      mic_mnt_cfg) != 0x210);
+	BUILD_BUG_ON(offsetof(struct cbe_mic_tm_regs,
+			      mic_exc) != 0x208);
+
+	return platform_driver_register(&cell_edac_driver);
+}
+
+static void __exit cell_edac_exit(void)
+{
+	platform_driver_unregister(&cell_edac_driver);
+}
+
+module_init(cell_edac_init);
+module_exit(cell_edac_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh at kernel.crashing.org>");
+MODULE_DESCRIPTION("ECC counting for Cell MIC");



More information about the cbe-oss-dev mailing list