diff options
Diffstat (limited to 'drivers/edac')
-rw-r--r-- | drivers/edac/Kconfig | 23 | ||||
-rw-r--r-- | drivers/edac/Makefile | 4 | ||||
-rw-r--r-- | drivers/edac/amd64_edac.c | 17 | ||||
-rw-r--r-- | drivers/edac/amd64_edac.h | 5 | ||||
-rw-r--r-- | drivers/edac/amd64_edac_dbg.c | 207 | ||||
-rw-r--r-- | drivers/edac/edac_core.h | 11 | ||||
-rw-r--r-- | drivers/edac/edac_device_sysfs.c | 18 | ||||
-rw-r--r-- | drivers/edac/edac_mc.c | 12 | ||||
-rw-r--r-- | drivers/edac/edac_mc_sysfs.c | 93 | ||||
-rw-r--r-- | drivers/edac/edac_mce_amd.c | 452 | ||||
-rw-r--r-- | drivers/edac/edac_module.c | 79 | ||||
-rw-r--r-- | drivers/edac/edac_module.h | 1 | ||||
-rw-r--r-- | drivers/edac/edac_pci_sysfs.c | 10 | ||||
-rw-r--r-- | drivers/edac/edac_stub.c | 51 | ||||
-rw-r--r-- | drivers/edac/i7300_edac.c | 1247 | ||||
-rw-r--r-- | drivers/edac/i7core_edac.c | 432 | ||||
-rw-r--r-- | drivers/edac/i82443bxgx_edac.c | 2 | ||||
-rw-r--r-- | drivers/edac/mce_amd.c | 680 | ||||
-rw-r--r-- | drivers/edac/mce_amd.h (renamed from drivers/edac/edac_mce_amd.h) | 59 | ||||
-rw-r--r-- | drivers/edac/mce_amd_inj.c | 171 |
20 files changed, 2587 insertions, 987 deletions
diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig index 70bb350de99..f436a2fa9f3 100644 --- a/drivers/edac/Kconfig +++ b/drivers/edac/Kconfig @@ -39,7 +39,7 @@ config EDAC_DEBUG there're four debug levels (x=0,1,2,3 from low to high). Usually you should select 'N'. - config EDAC_DECODE_MCE +config EDAC_DECODE_MCE tristate "Decode MCEs in human-readable form (only on AMD for now)" depends on CPU_SUP_AMD && X86_MCE default y @@ -51,6 +51,16 @@ config EDAC_DEBUG which occur really early upon boot, before the module infrastructure has been initialized. +config EDAC_MCE_INJ + tristate "Simple MCE injection interface over /sysfs" + depends on EDAC_DECODE_MCE + default n + help + This is a simple interface to inject MCEs over /sysfs and test + the MCE decoding code in EDAC. + + This is currently AMD-only. + config EDAC_MM_EDAC tristate "Main Memory EDAC (Error Detection And Correction) reporting" help @@ -66,13 +76,13 @@ config EDAC_MCE config EDAC_AMD64 tristate "AMD64 (Opteron, Athlon64) K8, F10h, F11h" - depends on EDAC_MM_EDAC && K8_NB && X86_64 && PCI && EDAC_DECODE_MCE + depends on EDAC_MM_EDAC && AMD_NB && X86_64 && PCI && EDAC_DECODE_MCE help Support for error detection and correction on the AMD 64 Families of Memory Controllers (K8, F10h and F11h) config EDAC_AMD64_ERROR_INJECTION - bool "Sysfs Error Injection facilities" + bool "Sysfs HW Error injection facilities" depends on EDAC_AMD64 help Recent Opterons (Family 10h and later) provide for Memory Error @@ -199,6 +209,13 @@ config EDAC_I5100 Support for error detection and correction the Intel San Clemente MCH. +config EDAC_I7300 + tristate "Intel Clarksboro MCH" + depends on EDAC_MM_EDAC && X86 && PCI + help + Support for error detection and correction the Intel + Clarksboro MCH (Intel 7300 chipset). + config EDAC_MPC85XX tristate "Freescale MPC83xx / MPC85xx" depends on EDAC_MM_EDAC && FSL_SOC && (PPC_83xx || PPC_85xx) diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile index ca6b1bb24cc..b3781399b38 100644 --- a/drivers/edac/Makefile +++ b/drivers/edac/Makefile @@ -17,6 +17,9 @@ ifdef CONFIG_PCI edac_core-objs += edac_pci.o edac_pci_sysfs.o endif +obj-$(CONFIG_EDAC_MCE_INJ) += mce_amd_inj.o + +edac_mce_amd-objs := mce_amd.o obj-$(CONFIG_EDAC_DECODE_MCE) += edac_mce_amd.o obj-$(CONFIG_EDAC_AMD76X) += amd76x_edac.o @@ -24,6 +27,7 @@ obj-$(CONFIG_EDAC_CPC925) += cpc925_edac.o obj-$(CONFIG_EDAC_I5000) += i5000_edac.o obj-$(CONFIG_EDAC_I5100) += i5100_edac.o obj-$(CONFIG_EDAC_I5400) += i5400_edac.o +obj-$(CONFIG_EDAC_I7300) += i7300_edac.o obj-$(CONFIG_EDAC_I7CORE) += i7core_edac.o obj-$(CONFIG_EDAC_E7XXX) += e7xxx_edac.o obj-$(CONFIG_EDAC_E752X) += e752x_edac.o diff --git a/drivers/edac/amd64_edac.c b/drivers/edac/amd64_edac.c index e7d5d6b5dcf..8521401bbd7 100644 --- a/drivers/edac/amd64_edac.c +++ b/drivers/edac/amd64_edac.c @@ -1,5 +1,5 @@ #include "amd64_edac.h" -#include <asm/k8.h> +#include <asm/amd_nb.h> static struct edac_pci_ctl_info *amd64_ctl_pci; @@ -2073,11 +2073,18 @@ static inline void __amd64_decode_bus_error(struct mem_ctl_info *mci, amd64_handle_ue(mci, info); } -void amd64_decode_bus_error(int node_id, struct err_regs *regs) +void amd64_decode_bus_error(int node_id, struct mce *m, u32 nbcfg) { struct mem_ctl_info *mci = mci_lookup[node_id]; + struct err_regs regs; - __amd64_decode_bus_error(mci, regs); + regs.nbsl = (u32) m->status; + regs.nbsh = (u32)(m->status >> 32); + regs.nbeal = (u32) m->addr; + regs.nbeah = (u32)(m->addr >> 32); + regs.nbcfg = nbcfg; + + __amd64_decode_bus_error(mci, ®s); /* * Check the UE bit of the NB status high register, if set generate some @@ -2086,7 +2093,7 @@ void amd64_decode_bus_error(int node_id, struct err_regs *regs) * * FIXME: this should go somewhere else, if at all. */ - if (regs->nbsh & K8_NBSH_UC_ERR && !report_gart_errors) + if (regs.nbsh & K8_NBSH_UC_ERR && !report_gart_errors) edac_mc_handle_ue_no_info(mci, "UE bit is set"); } @@ -2927,7 +2934,7 @@ static int __init amd64_edac_init(void) * to finish initialization of the MC instances. */ err = -ENODEV; - for (nb = 0; nb < num_k8_northbridges; nb++) { + for (nb = 0; nb < k8_northbridges.num; nb++) { if (!pvt_lookup[nb]) continue; diff --git a/drivers/edac/amd64_edac.h b/drivers/edac/amd64_edac.h index 613b9381e71..044aee4f944 100644 --- a/drivers/edac/amd64_edac.h +++ b/drivers/edac/amd64_edac.h @@ -72,7 +72,7 @@ #include <linux/edac.h> #include <asm/msr.h> #include "edac_core.h" -#include "edac_mce_amd.h" +#include "mce_amd.h" #define amd64_printk(level, fmt, arg...) \ edac_printk(level, "amd64", fmt, ##arg) @@ -482,11 +482,10 @@ extern const char *rrrr_msgs[16]; extern const char *to_msgs[2]; extern const char *pp_msgs[4]; extern const char *ii_msgs[4]; -extern const char *ext_msgs[32]; extern const char *htlink_msgs[8]; #ifdef CONFIG_EDAC_DEBUG -#define NUM_DBG_ATTRS 9 +#define NUM_DBG_ATTRS 5 #else #define NUM_DBG_ATTRS 0 #endif diff --git a/drivers/edac/amd64_edac_dbg.c b/drivers/edac/amd64_edac_dbg.c index 59cf2cf6e11..e3562288f4c 100644 --- a/drivers/edac/amd64_edac_dbg.c +++ b/drivers/edac/amd64_edac_dbg.c @@ -1,167 +1,16 @@ #include "amd64_edac.h" -/* - * accept a hex value and store it into the virtual error register file, field: - * nbeal and nbeah. Assume virtual error values have already been set for: NBSL, - * NBSH and NBCFG. Then proceed to map the error values to a MC, CSROW and - * CHANNEL - */ -static ssize_t amd64_nbea_store(struct mem_ctl_info *mci, const char *data, - size_t count) -{ - struct amd64_pvt *pvt = mci->pvt_info; - unsigned long long value; - int ret = 0; - - ret = strict_strtoull(data, 16, &value); - if (ret != -EINVAL) { - debugf0("received NBEA= 0x%llx\n", value); - - /* place the value into the virtual error packet */ - pvt->ctl_error_info.nbeal = (u32) value; - value >>= 32; - pvt->ctl_error_info.nbeah = (u32) value; - - /* Process the Mapping request */ - /* TODO: Add race prevention */ - amd_decode_nb_mce(pvt->mc_node_id, &pvt->ctl_error_info, 1); - - return count; - } - return ret; -} - -/* display back what the last NBEA (MCA NB Address (MC4_ADDR)) was written */ -static ssize_t amd64_nbea_show(struct mem_ctl_info *mci, char *data) -{ - struct amd64_pvt *pvt = mci->pvt_info; - u64 value; - - value = pvt->ctl_error_info.nbeah; - value <<= 32; - value |= pvt->ctl_error_info.nbeal; - - return sprintf(data, "%llx\n", value); -} - -/* store the NBSL (MCA NB Status Low (MC4_STATUS)) value user desires */ -static ssize_t amd64_nbsl_store(struct mem_ctl_info *mci, const char *data, - size_t count) -{ - struct amd64_pvt *pvt = mci->pvt_info; - unsigned long value; - int ret = 0; - - ret = strict_strtoul(data, 16, &value); - if (ret != -EINVAL) { - debugf0("received NBSL= 0x%lx\n", value); - - pvt->ctl_error_info.nbsl = (u32) value; - - return count; - } - return ret; -} - -/* display back what the last NBSL value written */ -static ssize_t amd64_nbsl_show(struct mem_ctl_info *mci, char *data) -{ - struct amd64_pvt *pvt = mci->pvt_info; - u32 value; - - value = pvt->ctl_error_info.nbsl; - - return sprintf(data, "%x\n", value); -} - -/* store the NBSH (MCA NB Status High) value user desires */ -static ssize_t amd64_nbsh_store(struct mem_ctl_info *mci, const char *data, - size_t count) -{ - struct amd64_pvt *pvt = mci->pvt_info; - unsigned long value; - int ret = 0; - - ret = strict_strtoul(data, 16, &value); - if (ret != -EINVAL) { - debugf0("received NBSH= 0x%lx\n", value); - - pvt->ctl_error_info.nbsh = (u32) value; - - return count; - } - return ret; -} - -/* display back what the last NBSH value written */ -static ssize_t amd64_nbsh_show(struct mem_ctl_info *mci, char *data) -{ - struct amd64_pvt *pvt = mci->pvt_info; - u32 value; - - value = pvt->ctl_error_info.nbsh; - - return sprintf(data, "%x\n", value); +#define EDAC_DCT_ATTR_SHOW(reg) \ +static ssize_t amd64_##reg##_show(struct mem_ctl_info *mci, char *data) \ +{ \ + struct amd64_pvt *pvt = mci->pvt_info; \ + return sprintf(data, "0x%016llx\n", (u64)pvt->reg); \ } -/* accept and store the NBCFG (MCA NB Configuration) value user desires */ -static ssize_t amd64_nbcfg_store(struct mem_ctl_info *mci, - const char *data, size_t count) -{ - struct amd64_pvt *pvt = mci->pvt_info; - unsigned long value; - int ret = 0; - - ret = strict_strtoul(data, 16, &value); - if (ret != -EINVAL) { - debugf0("received NBCFG= 0x%lx\n", value); - - pvt->ctl_error_info.nbcfg = (u32) value; - - return count; - } - return ret; -} - -/* various show routines for the controls of a MCI */ -static ssize_t amd64_nbcfg_show(struct mem_ctl_info *mci, char *data) -{ - struct amd64_pvt *pvt = mci->pvt_info; - - return sprintf(data, "%x\n", pvt->ctl_error_info.nbcfg); -} - - -static ssize_t amd64_dhar_show(struct mem_ctl_info *mci, char *data) -{ - struct amd64_pvt *pvt = mci->pvt_info; - - return sprintf(data, "%x\n", pvt->dhar); -} - - -static ssize_t amd64_dbam_show(struct mem_ctl_info *mci, char *data) -{ - struct amd64_pvt *pvt = mci->pvt_info; - - return sprintf(data, "%x\n", pvt->dbam0); -} - - -static ssize_t amd64_topmem_show(struct mem_ctl_info *mci, char *data) -{ - struct amd64_pvt *pvt = mci->pvt_info; - - return sprintf(data, "%llx\n", pvt->top_mem); -} - - -static ssize_t amd64_topmem2_show(struct mem_ctl_info *mci, char *data) -{ - struct amd64_pvt *pvt = mci->pvt_info; - - return sprintf(data, "%llx\n", pvt->top_mem2); -} +EDAC_DCT_ATTR_SHOW(dhar); +EDAC_DCT_ATTR_SHOW(dbam0); +EDAC_DCT_ATTR_SHOW(top_mem); +EDAC_DCT_ATTR_SHOW(top_mem2); static ssize_t amd64_hole_show(struct mem_ctl_info *mci, char *data) { @@ -182,38 +31,6 @@ struct mcidev_sysfs_attribute amd64_dbg_attrs[] = { { .attr = { - .name = "nbea_ctl", - .mode = (S_IRUGO | S_IWUSR) - }, - .show = amd64_nbea_show, - .store = amd64_nbea_store, - }, - { - .attr = { - .name = "nbsl_ctl", - .mode = (S_IRUGO | S_IWUSR) - }, - .show = amd64_nbsl_show, - .store = amd64_nbsl_store, - }, - { - .attr = { - .name = "nbsh_ctl", - .mode = (S_IRUGO | S_IWUSR) - }, - .show = amd64_nbsh_show, - .store = amd64_nbsh_store, - }, - { - .attr = { - .name = "nbcfg_ctl", - .mode = (S_IRUGO | S_IWUSR) - }, - .show = amd64_nbcfg_show, - .store = amd64_nbcfg_store, - }, - { - .attr = { .name = "dhar", .mode = (S_IRUGO) }, @@ -225,7 +42,7 @@ struct mcidev_sysfs_attribute amd64_dbg_attrs[] = { .name = "dbam", .mode = (S_IRUGO) }, - .show = amd64_dbam_show, + .show = amd64_dbam0_show, .store = NULL, }, { @@ -233,7 +50,7 @@ struct mcidev_sysfs_attribute amd64_dbg_attrs[] = { .name = "topmem", .mode = (S_IRUGO) }, - .show = amd64_topmem_show, + .show = amd64_top_mem_show, .store = NULL, }, { @@ -241,7 +58,7 @@ struct mcidev_sysfs_attribute amd64_dbg_attrs[] = { .name = "topmem2", .mode = (S_IRUGO) }, - .show = amd64_topmem2_show, + .show = amd64_top_mem2_show, .store = NULL, }, { diff --git a/drivers/edac/edac_core.h b/drivers/edac/edac_core.h index ce7146677e9..d7ca43a828b 100644 --- a/drivers/edac/edac_core.h +++ b/drivers/edac/edac_core.h @@ -42,8 +42,10 @@ #if PAGE_SHIFT < 20 #define PAGES_TO_MiB( pages ) ( ( pages ) >> ( 20 - PAGE_SHIFT ) ) +#define MiB_TO_PAGES(mb) ((mb) >> (20 - PAGE_SHIFT)) #else /* PAGE_SHIFT > 20 */ #define PAGES_TO_MiB( pages ) ( ( pages ) << ( PAGE_SHIFT - 20 ) ) +#define MiB_TO_PAGES(mb) ((mb) >> (PAGE_SHIFT - 20)) #endif #define edac_printk(level, prefix, fmt, arg...) \ @@ -328,7 +330,7 @@ struct csrow_info { struct mcidev_sysfs_group { const char *name; /* group name */ - struct mcidev_sysfs_attribute *mcidev_attr; /* group attributes */ + const struct mcidev_sysfs_attribute *mcidev_attr; /* group attributes */ }; struct mcidev_sysfs_group_kobj { @@ -336,7 +338,7 @@ struct mcidev_sysfs_group_kobj { struct kobject kobj; /* kobj for the group */ - struct mcidev_sysfs_group *grp; /* group description table */ + const struct mcidev_sysfs_group *grp; /* group description table */ struct mem_ctl_info *mci; /* the parent */ }; @@ -347,7 +349,7 @@ struct mcidev_sysfs_group_kobj { struct mcidev_sysfs_attribute { /* It should use either attr or grp */ struct attribute attr; - struct mcidev_sysfs_group *grp; /* Points to a group of attributes */ + const struct mcidev_sysfs_group *grp; /* Points to a group of attributes */ /* Ops for show/store values at the attribute - not used on group */ ssize_t (*show)(struct mem_ctl_info *,char *); @@ -440,7 +442,7 @@ struct mem_ctl_info { * If attributes are desired, then set to array of attributes * If no attributes are desired, leave NULL */ - struct mcidev_sysfs_attribute *mc_driver_sysfs_attributes; + const struct mcidev_sysfs_attribute *mc_driver_sysfs_attributes; /* work struct for this MC */ struct delayed_work work; @@ -810,6 +812,7 @@ extern struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows, extern int edac_mc_add_mc(struct mem_ctl_info *mci); extern void edac_mc_free(struct mem_ctl_info *mci); extern struct mem_ctl_info *edac_mc_find(int idx); +extern struct mem_ctl_info *find_mci_by_dev(struct device *dev); extern struct mem_ctl_info *edac_mc_del_mc(struct device *dev); extern int edac_mc_find_csrow_by_page(struct mem_ctl_info *mci, unsigned long page); diff --git a/drivers/edac/edac_device_sysfs.c b/drivers/edac/edac_device_sysfs.c index 070968178a2..400de071cab 100644 --- a/drivers/edac/edac_device_sysfs.c +++ b/drivers/edac/edac_device_sysfs.c @@ -1,7 +1,7 @@ /* * file for managing the edac_device class of devices for EDAC * - * (C) 2007 SoftwareBitMaker (http://www.softwarebitmaker.com) + * (C) 2007 SoftwareBitMaker * * This file may be distributed under the terms of the * GNU General Public License. @@ -13,6 +13,7 @@ #include <linux/ctype.h> #include <linux/module.h> #include <linux/slab.h> +#include <linux/edac.h> #include "edac_core.h" #include "edac_module.h" @@ -235,7 +236,7 @@ int edac_device_register_sysfs_main_kobj(struct edac_device_ctl_info *edac_dev) debugf1("%s()\n", __func__); /* get the /sys/devices/system/edac reference */ - edac_class = edac_get_edac_class(); + edac_class = edac_get_sysfs_class(); if (edac_class == NULL) { debugf1("%s() no edac_class error\n", __func__); err = -ENODEV; @@ -255,7 +256,7 @@ int edac_device_register_sysfs_main_kobj(struct edac_device_ctl_info *edac_dev) if (!try_module_get(edac_dev->owner)) { err = -ENODEV; - goto err_out; + goto err_mod_get; } /* register */ @@ -282,6 +283,9 @@ int edac_device_register_sysfs_main_kobj(struct edac_device_ctl_info *edac_dev) err_kobj_reg: module_put(edac_dev->owner); +err_mod_get: + edac_put_sysfs_class(); + err_out: return err; } @@ -290,12 +294,11 @@ err_out: * edac_device_unregister_sysfs_main_kobj: * the '..../edac/<name>' kobject */ -void edac_device_unregister_sysfs_main_kobj( - struct edac_device_ctl_info *edac_dev) +void edac_device_unregister_sysfs_main_kobj(struct edac_device_ctl_info *dev) { debugf0("%s()\n", __func__); debugf4("%s() name of kobject is: %s\n", - __func__, kobject_name(&edac_dev->kobj)); + __func__, kobject_name(&dev->kobj)); /* * Unregister the edac device's kobject and @@ -304,7 +307,8 @@ void edac_device_unregister_sysfs_main_kobj( * a) module_put() this module * b) 'kfree' the memory */ - kobject_put(&edac_dev->kobj); + kobject_put(&dev->kobj); + edac_put_sysfs_class(); } /* edac_dev -> instance information */ diff --git a/drivers/edac/edac_mc.c b/drivers/edac/edac_mc.c index 6b21e25f7a8..ba6586a69cc 100644 --- a/drivers/edac/edac_mc.c +++ b/drivers/edac/edac_mc.c @@ -207,6 +207,7 @@ struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows, } mci->op_state = OP_ALLOC; + INIT_LIST_HEAD(&mci->grp_kobj_list); /* * Initialize the 'root' kobj for the edac_mc controller @@ -234,18 +235,24 @@ EXPORT_SYMBOL_GPL(edac_mc_alloc); */ void edac_mc_free(struct mem_ctl_info *mci) { + debugf1("%s()\n", __func__); + edac_mc_unregister_sysfs_main_kobj(mci); + + /* free the mci instance memory here */ + kfree(mci); } EXPORT_SYMBOL_GPL(edac_mc_free); -/* +/** * find_mci_by_dev * * scan list of controllers looking for the one that manages * the 'dev' device + * @dev: pointer to a struct device related with the MCI */ -static struct mem_ctl_info *find_mci_by_dev(struct device *dev) +struct mem_ctl_info *find_mci_by_dev(struct device *dev) { struct mem_ctl_info *mci; struct list_head *item; @@ -261,6 +268,7 @@ static struct mem_ctl_info *find_mci_by_dev(struct device *dev) return NULL; } +EXPORT_SYMBOL_GPL(find_mci_by_dev); /* * handler for EDAC to check if NMI type handler has asserted interrupt diff --git a/drivers/edac/edac_mc_sysfs.c b/drivers/edac/edac_mc_sysfs.c index 8aad94d10c0..dce61f7ba38 100644 --- a/drivers/edac/edac_mc_sysfs.c +++ b/drivers/edac/edac_mc_sysfs.c @@ -11,6 +11,7 @@ #include <linux/ctype.h> #include <linux/slab.h> +#include <linux/edac.h> #include <linux/bug.h> #include "edac_core.h" @@ -630,9 +631,6 @@ static void edac_mci_control_release(struct kobject *kobj) /* decrement the module ref count */ module_put(mci->owner); - - /* free the mci instance memory here */ - kfree(mci); } static struct kobj_type ktype_mci = { @@ -712,6 +710,8 @@ fail_out: */ void edac_mc_unregister_sysfs_main_kobj(struct mem_ctl_info *mci) { + debugf1("%s()\n", __func__); + /* delete the kobj from the mc_kset */ kobject_put(&mci->edac_mci_kobj); } @@ -759,8 +759,6 @@ static void edac_inst_grp_release(struct kobject *kobj) grp = container_of(kobj, struct mcidev_sysfs_group_kobj, kobj); mci = grp->mci; - - kobject_put(&mci->edac_mci_kobj); } /* Intermediate show/store table */ @@ -783,7 +781,7 @@ static struct kobj_type ktype_inst_grp = { * object tree. */ static int edac_create_mci_instance_attributes(struct mem_ctl_info *mci, - struct mcidev_sysfs_attribute *sysfs_attrib, + const struct mcidev_sysfs_attribute *sysfs_attrib, struct kobject *kobj) { int err; @@ -791,6 +789,7 @@ static int edac_create_mci_instance_attributes(struct mem_ctl_info *mci, debugf1("%s()\n", __func__); while (sysfs_attrib) { + debugf1("%s() sysfs_attrib = %p\n",__func__, sysfs_attrib); if (sysfs_attrib->grp) { struct mcidev_sysfs_group_kobj *grp_kobj; @@ -798,10 +797,9 @@ static int edac_create_mci_instance_attributes(struct mem_ctl_info *mci, if (!grp_kobj) return -ENOMEM; - list_add_tail(&grp_kobj->list, &mci->grp_kobj_list); - grp_kobj->grp = sysfs_attrib->grp; grp_kobj->mci = mci; + list_add_tail(&grp_kobj->list, &mci->grp_kobj_list); debugf0("%s() grp %s, mci %p\n", __func__, sysfs_attrib->grp->name, mci); @@ -810,26 +808,28 @@ static int edac_create_mci_instance_attributes(struct mem_ctl_info *mci, &ktype_inst_grp, &mci->edac_mci_kobj, sysfs_attrib->grp->name); - if (err) + if (err < 0) { + printk(KERN_ERR "kobject_init_and_add failed: %d\n", err); return err; - + } err = edac_create_mci_instance_attributes(mci, grp_kobj->grp->mcidev_attr, &grp_kobj->kobj); - if (err) + if (err < 0) return err; } else if (sysfs_attrib->attr.name) { debugf0("%s() file %s\n", __func__, sysfs_attrib->attr.name); err = sysfs_create_file(kobj, &sysfs_attrib->attr); + if (err < 0) { + printk(KERN_ERR "sysfs_create_file failed: %d\n", err); + return err; + } } else break; - if (err) { - return err; - } sysfs_attrib++; } @@ -842,7 +842,7 @@ static int edac_create_mci_instance_attributes(struct mem_ctl_info *mci, * directory of this mci instance. */ static void edac_remove_mci_instance_attributes(struct mem_ctl_info *mci, - struct mcidev_sysfs_attribute *sysfs_attrib, + const struct mcidev_sysfs_attribute *sysfs_attrib, struct kobject *kobj, int count) { struct mcidev_sysfs_group_kobj *grp_kobj, *tmp; @@ -854,13 +854,24 @@ static void edac_remove_mci_instance_attributes(struct mem_ctl_info *mci, * Remove first all the atributes */ while (sysfs_attrib) { + debugf1("%s() sysfs_attrib = %p\n",__func__, sysfs_attrib); if (sysfs_attrib->grp) { - list_for_each_entry(grp_kobj, &mci->grp_kobj_list, - list) - if (grp_kobj->grp == sysfs_attrib->grp) + debugf1("%s() seeking for group %s\n", + __func__, sysfs_attrib->grp->name); + list_for_each_entry(grp_kobj, + &mci->grp_kobj_list, list) { + debugf1("%s() grp_kobj->grp = %p\n",__func__, grp_kobj->grp); + if (grp_kobj->grp == sysfs_attrib->grp) { edac_remove_mci_instance_attributes(mci, grp_kobj->grp->mcidev_attr, &grp_kobj->kobj, count + 1); + debugf0("%s() group %s\n", __func__, + sysfs_attrib->grp->name); + kobject_put(&grp_kobj->kobj); + } + } + debugf1("%s() end of seeking for group %s\n", + __func__, sysfs_attrib->grp->name); } else if (sysfs_attrib->attr.name) { debugf0("%s() file %s\n", __func__, sysfs_attrib->attr.name); @@ -870,15 +881,14 @@ static void edac_remove_mci_instance_attributes(struct mem_ctl_info *mci, sysfs_attrib++; } - /* - * Now that all attributes got removed, it is save to remove all groups - */ - if (!count) - list_for_each_entry_safe(grp_kobj, tmp, &mci->grp_kobj_list, - list) { - debugf0("%s() grp %s\n", __func__, grp_kobj->grp->name); - kobject_put(&grp_kobj->kobj); - } + /* Remove the group objects */ + if (count) + return; + list_for_each_entry_safe(grp_kobj, tmp, + &mci->grp_kobj_list, list) { + list_del(&grp_kobj->list); + kfree(grp_kobj); + } } @@ -970,6 +980,7 @@ void edac_remove_sysfs_mci_device(struct mem_ctl_info *mci) debugf0("%s()\n", __func__); /* remove all csrow kobjects */ + debugf0("%s() unregister this mci kobj\n", __func__); for (i = 0; i < mci->nr_csrows; i++) { if (mci->csrows[i].nr_pages > 0) { debugf0("%s() unreg csrow-%d\n", __func__, i); @@ -977,20 +988,20 @@ void edac_remove_sysfs_mci_device(struct mem_ctl_info *mci) } } - debugf0("%s() remove_link\n", __func__); + /* remove this mci instance's attribtes */ + if (mci->mc_driver_sysfs_attributes) { + debugf0("%s() unregister mci private attributes\n", __func__); + edac_remove_mci_instance_attributes(mci, + mci->mc_driver_sysfs_attributes, + &mci->edac_mci_kobj, 0); + } /* remove the symlink */ + debugf0("%s() remove_link\n", __func__); sysfs_remove_link(&mci->edac_mci_kobj, EDAC_DEVICE_SYMLINK); - debugf0("%s() remove_mci_instance\n", __func__); - - /* remove this mci instance's attribtes */ - edac_remove_mci_instance_attributes(mci, - mci->mc_driver_sysfs_attributes, - &mci->edac_mci_kobj, 0); - debugf0("%s() unregister this mci kobj\n", __func__); - /* unregister this instance's kobject */ + debugf0("%s() remove_mci_instance\n", __func__); kobject_put(&mci->edac_mci_kobj); } @@ -1011,13 +1022,13 @@ void edac_remove_sysfs_mci_device(struct mem_ctl_info *mci) */ int edac_sysfs_setup_mc_kset(void) { - int err = 0; + int err = -EINVAL; struct sysdev_class *edac_class; debugf1("%s()\n", __func__); /* get the /sys/devices/system/edac class reference */ - edac_class = edac_get_edac_class(); + edac_class = edac_get_sysfs_class(); if (edac_class == NULL) { debugf1("%s() no edac_class error=%d\n", __func__, err); goto fail_out; @@ -1028,15 +1039,16 @@ int edac_sysfs_setup_mc_kset(void) if (!mc_kset) { err = -ENOMEM; debugf1("%s() Failed to register '.../edac/mc'\n", __func__); - goto fail_out; + goto fail_kset; } debugf1("%s() Registered '.../edac/mc' kobject\n", __func__); return 0; +fail_kset: + edac_put_sysfs_class(); - /* error unwind stack */ fail_out: return err; } @@ -1049,5 +1061,6 @@ fail_out: void edac_sysfs_teardown_mc_kset(void) { kset_unregister(mc_kset); + edac_put_sysfs_class(); } diff --git a/drivers/edac/edac_mce_amd.c b/drivers/edac/edac_mce_amd.c deleted file mode 100644 index 9014df6f605..00000000000 --- a/drivers/edac/edac_mce_amd.c +++ /dev/null @@ -1,452 +0,0 @@ -#include <linux/module.h> -#include "edac_mce_amd.h" - -static bool report_gart_errors; -static void (*nb_bus_decoder)(int node_id, struct err_regs *regs); - -void amd_report_gart_errors(bool v) -{ - report_gart_errors = v; -} -EXPORT_SYMBOL_GPL(amd_report_gart_errors); - -void amd_register_ecc_decoder(void (*f)(int, struct err_regs *)) -{ - nb_bus_decoder = f; -} -EXPORT_SYMBOL_GPL(amd_register_ecc_decoder); - -void amd_unregister_ecc_decoder(void (*f)(int, struct err_regs *)) -{ - if (nb_bus_decoder) { - WARN_ON(nb_bus_decoder != f); - - nb_bus_decoder = NULL; - } -} -EXPORT_SYMBOL_GPL(amd_unregister_ecc_decoder); - -/* - * string representation for the different MCA reported error types, see F3x48 - * or MSR0000_0411. - */ -const char *tt_msgs[] = { /* transaction type */ - "instruction", - "data", - "generic", - "reserved" -}; -EXPORT_SYMBOL_GPL(tt_msgs); - -const char *ll_msgs[] = { /* cache level */ - "L0", - "L1", - "L2", - "L3/generic" -}; -EXPORT_SYMBOL_GPL(ll_msgs); - -const char *rrrr_msgs[] = { - "generic", - "generic read", - "generic write", - "data read", - "data write", - "inst fetch", - "prefetch", - "evict", - "snoop", - "reserved RRRR= 9", - "reserved RRRR= 10", - "reserved RRRR= 11", - "reserved RRRR= 12", - "reserved RRRR= 13", - "reserved RRRR= 14", - "reserved RRRR= 15" -}; -EXPORT_SYMBOL_GPL(rrrr_msgs); - -const char *pp_msgs[] = { /* participating processor */ - "local node originated (SRC)", - "local node responded to request (RES)", - "local node observed as 3rd party (OBS)", - "generic" -}; -EXPORT_SYMBOL_GPL(pp_msgs); - -const char *to_msgs[] = { - "no timeout", - "timed out" -}; -EXPORT_SYMBOL_GPL(to_msgs); - -const char *ii_msgs[] = { /* memory or i/o */ - "mem access", - "reserved", - "i/o access", - "generic" -}; -EXPORT_SYMBOL_GPL(ii_msgs); - -/* - * Map the 4 or 5 (family-specific) bits of Extended Error code to the - * string table. - */ -const char *ext_msgs[] = { - "K8 ECC error", /* 0_0000b */ - "CRC error on link", /* 0_0001b */ - "Sync error packets on link", /* 0_0010b */ - "Master Abort during link operation", /* 0_0011b */ - "Target Abort during link operation", /* 0_0100b */ - "Invalid GART PTE entry during table walk", /* 0_0101b */ - "Unsupported atomic RMW command received", /* 0_0110b */ - "WDT error: NB transaction timeout", /* 0_0111b */ - "ECC/ChipKill ECC error", /* 0_1000b */ - "SVM DEV Error", /* 0_1001b */ - "Link Data error", /* 0_1010b */ - "Link/L3/Probe Filter Protocol error", /* 0_1011b */ - "NB Internal Arrays Parity error", /* 0_1100b */ - "DRAM Address/Control Parity error", /* 0_1101b */ - "Link Transmission error", /* 0_1110b */ - "GART/DEV Table Walk Data error" /* 0_1111b */ - "Res 0x100 error", /* 1_0000b */ - "Res 0x101 error", /* 1_0001b */ - "Res 0x102 error", /* 1_0010b */ - "Res 0x103 error", /* 1_0011b */ - "Res 0x104 error", /* 1_0100b */ - "Res 0x105 error", /* 1_0101b */ - "Res 0x106 error", /* 1_0110b */ - "Res 0x107 error", /* 1_0111b */ - "Res 0x108 error", /* 1_1000b */ - "Res 0x109 error", /* 1_1001b */ - "Res 0x10A error", /* 1_1010b */ - "Res 0x10B error", /* 1_1011b */ - "ECC error in L3 Cache Data", /* 1_1100b */ - "L3 Cache Tag error", /* 1_1101b */ - "L3 Cache LRU Parity error", /* 1_1110b */ - "Probe Filter error" /* 1_1111b */ -}; -EXPORT_SYMBOL_GPL(ext_msgs); - -static void amd_decode_dc_mce(u64 mc0_status) -{ - u32 ec = mc0_status & 0xffff; - u32 xec = (mc0_status >> 16) & 0xf; - - pr_emerg("Data Cache Error"); - - if (xec == 1 && TLB_ERROR(ec)) - pr_cont(": %s TLB multimatch.\n", LL_MSG(ec)); - else if (xec == 0) { - if (mc0_status & (1ULL << 40)) - pr_cont(" during Data Scrub.\n"); - else if (TLB_ERROR(ec)) - pr_cont(": %s TLB parity error.\n", LL_MSG(ec)); - else if (MEM_ERROR(ec)) { - u8 ll = ec & 0x3; - u8 tt = (ec >> 2) & 0x3; - u8 rrrr = (ec >> 4) & 0xf; - - /* see F10h BKDG (31116), Table 92. */ - if (ll == 0x1) { - if (tt != 0x1) - goto wrong_dc_mce; - - pr_cont(": Data/Tag %s error.\n", RRRR_MSG(ec)); - - } else if (ll == 0x2 && rrrr == 0x3) - pr_cont(" during L1 linefill from L2.\n"); - else - goto wrong_dc_mce; - } else if (BUS_ERROR(ec) && boot_cpu_data.x86 == 0xf) - pr_cont(" during system linefill.\n"); - else - goto wrong_dc_mce; - } else - goto wrong_dc_mce; - - return; - -wrong_dc_mce: - pr_warning("Corrupted DC MCE info?\n"); -} - -static void amd_decode_ic_mce(u64 mc1_status) -{ - u32 ec = mc1_status & 0xffff; - u32 xec = (mc1_status >> 16) & 0xf; - - pr_emerg("Instruction Cache Error"); - - if (xec == 1 && TLB_ERROR(ec)) - pr_cont(": %s TLB multimatch.\n", LL_MSG(ec)); - else if (xec == 0) { - if (TLB_ERROR(ec)) - pr_cont(": %s TLB Parity error.\n", LL_MSG(ec)); - else if (BUS_ERROR(ec)) { - if (boot_cpu_data.x86 == 0xf && - (mc1_status & (1ULL << 58))) - pr_cont(" during system linefill.\n"); - else - pr_cont(" during attempted NB data read.\n"); - } else if (MEM_ERROR(ec)) { - u8 ll = ec & 0x3; - u8 rrrr = (ec >> 4) & 0xf; - - if (ll == 0x2) - pr_cont(" during a linefill from L2.\n"); - else if (ll == 0x1) { - - switch (rrrr) { - case 0x5: - pr_cont(": Parity error during " - "data load.\n"); - break; - - case 0x7: - pr_cont(": Copyback Parity/Victim" - " error.\n"); - break; - - case 0x8: - pr_cont(": Tag Snoop error.\n"); - break; - - default: - goto wrong_ic_mce; - break; - } - } - } else - goto wrong_ic_mce; - } else - goto wrong_ic_mce; - - return; - -wrong_ic_mce: - pr_warning("Corrupted IC MCE info?\n"); -} - -static void amd_decode_bu_mce(u64 mc2_status) -{ - u32 ec = mc2_status & 0xffff; - u32 xec = (mc2_status >> 16) & 0xf; - - pr_emerg("Bus Unit Error"); - - if (xec == 0x1) - pr_cont(" in the write data buffers.\n"); - else if (xec == 0x3) - pr_cont(" in the victim data buffers.\n"); - else if (xec == 0x2 && MEM_ERROR(ec)) - pr_cont(": %s error in the L2 cache tags.\n", RRRR_MSG(ec)); - else if (xec == 0x0) { - if (TLB_ERROR(ec)) - pr_cont(": %s error in a Page Descriptor Cache or " - "Guest TLB.\n", TT_MSG(ec)); - else if (BUS_ERROR(ec)) - pr_cont(": %s/ECC error in data read from NB: %s.\n", - RRRR_MSG(ec), PP_MSG(ec)); - else if (MEM_ERROR(ec)) { - u8 rrrr = (ec >> 4) & 0xf; - - if (rrrr >= 0x7) - pr_cont(": %s error during data copyback.\n", - RRRR_MSG(ec)); - else if (rrrr <= 0x1) - pr_cont(": %s parity/ECC error during data " - "access from L2.\n", RRRR_MSG(ec)); - else - goto wrong_bu_mce; - } else - goto wrong_bu_mce; - } else - goto wrong_bu_mce; - - return; - -wrong_bu_mce: - pr_warning("Corrupted BU MCE info?\n"); -} - -static void amd_decode_ls_mce(u64 mc3_status) -{ - u32 ec = mc3_status & 0xffff; - u32 xec = (mc3_status >> 16) & 0xf; - - pr_emerg("Load Store Error"); - - if (xec == 0x0) { - u8 rrrr = (ec >> 4) & 0xf; - - if (!BUS_ERROR(ec) || (rrrr != 0x3 && rrrr != 0x4)) - goto wrong_ls_mce; - - pr_cont(" during %s.\n", RRRR_MSG(ec)); - } - return; - -wrong_ls_mce: - pr_warning("Corrupted LS MCE info?\n"); -} - -void amd_decode_nb_mce(int node_id, struct err_regs *regs, int handle_errors) -{ - u32 ec = ERROR_CODE(regs->nbsl); - - if (!handle_errors) - return; - - /* - * GART TLB error reporting is disabled by default. Bail out early. - */ - if (TLB_ERROR(ec) && !report_gart_errors) - return; - - pr_emerg("Northbridge Error, node %d", node_id); - - /* - * F10h, revD can disable ErrCpu[3:0] so check that first and also the - * value encoding has changed so interpret those differently - */ - if ((boot_cpu_data.x86 == 0x10) && - (boot_cpu_data.x86_model > 7)) { - if (regs->nbsh & K8_NBSH_ERR_CPU_VAL) - pr_cont(", core: %u\n", (u8)(regs->nbsh & 0xf)); - } else { - u8 assoc_cpus = regs->nbsh & 0xf; - - if (assoc_cpus > 0) - pr_cont(", core: %d", fls(assoc_cpus) - 1); - - pr_cont("\n"); - } - - pr_emerg("%s.\n", EXT_ERR_MSG(regs->nbsl)); - - if (BUS_ERROR(ec) && nb_bus_decoder) - nb_bus_decoder(node_id, regs); -} -EXPORT_SYMBOL_GPL(amd_decode_nb_mce); - -static void amd_decode_fr_mce(u64 mc5_status) -{ - /* we have only one error signature so match all fields at once. */ - if ((mc5_status & 0xffff) == 0x0f0f) - pr_emerg(" FR Error: CPU Watchdog timer expire.\n"); - else - pr_warning("Corrupted FR MCE info?\n"); -} - -static inline void amd_decode_err_code(unsigned int ec) -{ - if (TLB_ERROR(ec)) { - pr_emerg("Transaction: %s, Cache Level %s\n", - TT_MSG(ec), LL_MSG(ec)); - } else if (MEM_ERROR(ec)) { - pr_emerg("Transaction: %s, Type: %s, Cache Level: %s", - RRRR_MSG(ec), TT_MSG(ec), LL_MSG(ec)); - } else if (BUS_ERROR(ec)) { - pr_emerg("Transaction type: %s(%s), %s, Cache Level: %s, " - "Participating Processor: %s\n", - RRRR_MSG(ec), II_MSG(ec), TO_MSG(ec), LL_MSG(ec), - PP_MSG(ec)); - } else - pr_warning("Huh? Unknown MCE error 0x%x\n", ec); -} - -static int amd_decode_mce(struct notifier_block *nb, unsigned long val, - void *data) -{ - struct mce *m = (struct mce *)data; - struct err_regs regs; - int node, ecc; - - pr_emerg("MC%d_STATUS: ", m->bank); - - pr_cont("%sorrected error, other errors lost: %s, " - "CPU context corrupt: %s", - ((m->status & MCI_STATUS_UC) ? "Unc" : "C"), - ((m->status & MCI_STATUS_OVER) ? "yes" : "no"), - ((m->status & MCI_STATUS_PCC) ? "yes" : "no")); - - /* do the two bits[14:13] together */ - ecc = (m->status >> 45) & 0x3; - if (ecc) - pr_cont(", %sECC Error", ((ecc == 2) ? "C" : "U")); - - pr_cont("\n"); - - switch (m->bank) { - case 0: - amd_decode_dc_mce(m->status); - break; - - case 1: - amd_decode_ic_mce(m->status); - break; - - case 2: - amd_decode_bu_mce(m->status); - break; - - case 3: - amd_decode_ls_mce(m->status); - break; - - case 4: - regs.nbsl = (u32) m->status; - regs.nbsh = (u32)(m->status >> 32); - regs.nbeal = (u32) m->addr; - regs.nbeah = (u32)(m->addr >> 32); - node = amd_get_nb_id(m->extcpu); - - amd_decode_nb_mce(node, ®s, 1); - break; - - case 5: - amd_decode_fr_mce(m->status); - break; - - default: - break; - } - - amd_decode_err_code(m->status & 0xffff); - - return NOTIFY_STOP; -} - -static struct notifier_block amd_mce_dec_nb = { - .notifier_call = amd_decode_mce, -}; - -static int __init mce_amd_init(void) -{ - /* - * We can decode MCEs for K8, F10h and F11h CPUs: - */ - if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD) - return 0; - - if (boot_cpu_data.x86 < 0xf || boot_cpu_data.x86 > 0x11) - return 0; - - atomic_notifier_chain_register(&x86_mce_decoder_chain, &amd_mce_dec_nb); - - return 0; -} -early_initcall(mce_amd_init); - -#ifdef MODULE -static void __exit mce_amd_exit(void) -{ - atomic_notifier_chain_unregister(&x86_mce_decoder_chain, &amd_mce_dec_nb); -} - -MODULE_DESCRIPTION("AMD MCE decoder"); -MODULE_ALIAS("edac-mce-amd"); -MODULE_LICENSE("GPL"); -module_exit(mce_amd_exit); -#endif diff --git a/drivers/edac/edac_module.c b/drivers/edac/edac_module.c index 7e1374afd96..be4b075c309 100644 --- a/drivers/edac/edac_module.c +++ b/drivers/edac/edac_module.c @@ -27,15 +27,6 @@ EXPORT_SYMBOL_GPL(edac_debug_level); struct workqueue_struct *edac_workqueue; /* - * sysfs object: /sys/devices/system/edac - * need to export to other files in this modules - */ -static struct sysdev_class edac_class = { - .name = "edac", -}; -static int edac_class_valid; - -/* * edac_op_state_to_string() */ char *edac_op_state_to_string(int opstate) @@ -55,60 +46,6 @@ char *edac_op_state_to_string(int opstate) } /* - * edac_get_edac_class() - * - * return pointer to the edac class of 'edac' - */ -struct sysdev_class *edac_get_edac_class(void) -{ - struct sysdev_class *classptr = NULL; - - if (edac_class_valid) - classptr = &edac_class; - - return classptr; -} - -/* - * edac_register_sysfs_edac_name() - * - * register the 'edac' into /sys/devices/system - * - * return: - * 0 success - * !0 error - */ -static int edac_register_sysfs_edac_name(void) -{ - int err; - - /* create the /sys/devices/system/edac directory */ - err = sysdev_class_register(&edac_class); - - if (err) { - debugf1("%s() error=%d\n", __func__, err); - return err; - } - - edac_class_valid = 1; - return 0; -} - -/* - * sysdev_class_unregister() - * - * unregister the 'edac' from /sys/devices/system - */ -static void edac_unregister_sysfs_edac_name(void) -{ - /* only if currently registered, then unregister it */ - if (edac_class_valid) - sysdev_class_unregister(&edac_class); - - edac_class_valid = 0; -} - -/* * edac_workqueue_setup * initialize the edac work queue for polling operations */ @@ -154,21 +91,11 @@ static int __init edac_init(void) edac_pci_clear_parity_errors(); /* - * perform the registration of the /sys/devices/system/edac class object - */ - if (edac_register_sysfs_edac_name()) { - edac_printk(KERN_ERR, EDAC_MC, - "Error initializing 'edac' kobject\n"); - err = -ENODEV; - goto error; - } - - /* * now set up the mc_kset under the edac class object */ err = edac_sysfs_setup_mc_kset(); if (err) - goto sysfs_setup_fail; + goto error; /* Setup/Initialize the workq for this core */ err = edac_workqueue_setup(); @@ -183,9 +110,6 @@ static int __init edac_init(void) workq_fail: edac_sysfs_teardown_mc_kset(); -sysfs_setup_fail: - edac_unregister_sysfs_edac_name(); - error: return err; } @@ -201,7 +125,6 @@ static void __exit edac_exit(void) /* tear down the various subsystems */ edac_workqueue_teardown(); edac_sysfs_teardown_mc_kset(); - edac_unregister_sysfs_edac_name(); } /* diff --git a/drivers/edac/edac_module.h b/drivers/edac/edac_module.h index 233d4798c3a..17aabb7b90e 100644 --- a/drivers/edac/edac_module.h +++ b/drivers/edac/edac_module.h @@ -42,7 +42,6 @@ extern void edac_device_unregister_sysfs_main_kobj( struct edac_device_ctl_info *edac_dev); extern int edac_device_create_sysfs(struct edac_device_ctl_info *edac_dev); extern void edac_device_remove_sysfs(struct edac_device_ctl_info *edac_dev); -extern struct sysdev_class *edac_get_edac_class(void); /* edac core workqueue: single CPU mode */ extern struct workqueue_struct *edac_workqueue; diff --git a/drivers/edac/edac_pci_sysfs.c b/drivers/edac/edac_pci_sysfs.c index c39697df9cb..023b01cb517 100644 --- a/drivers/edac/edac_pci_sysfs.c +++ b/drivers/edac/edac_pci_sysfs.c @@ -7,7 +7,7 @@ * */ #include <linux/module.h> -#include <linux/sysdev.h> +#include <linux/edac.h> #include <linux/slab.h> #include <linux/ctype.h> @@ -354,7 +354,7 @@ static int edac_pci_main_kobj_setup(void) /* First time, so create the main kobject and its * controls and atributes */ - edac_class = edac_get_edac_class(); + edac_class = edac_get_sysfs_class(); if (edac_class == NULL) { debugf1("%s() no edac_class\n", __func__); err = -ENODEV; @@ -368,7 +368,7 @@ static int edac_pci_main_kobj_setup(void) if (!try_module_get(THIS_MODULE)) { debugf1("%s() try_module_get() failed\n", __func__); err = -ENODEV; - goto decrement_count_fail; + goto mod_get_fail; } edac_pci_top_main_kobj = kzalloc(sizeof(struct kobject), GFP_KERNEL); @@ -403,6 +403,9 @@ kobject_init_and_add_fail: kzalloc_fail: module_put(THIS_MODULE); +mod_get_fail: + edac_put_sysfs_class(); + decrement_count_fail: /* if are on this error exit, nothing to tear down */ atomic_dec(&edac_pci_sysfs_refcount); @@ -429,6 +432,7 @@ static void edac_pci_main_kobj_teardown(void) __func__); kobject_put(edac_pci_top_main_kobj); } + edac_put_sysfs_class(); } /* diff --git a/drivers/edac/edac_stub.c b/drivers/edac/edac_stub.c index 20b428aa155..aab970760b7 100644 --- a/drivers/edac/edac_stub.c +++ b/drivers/edac/edac_stub.c @@ -3,10 +3,13 @@ * * Author: Dave Jiang <djiang@mvista.com> * - * 2007 (c) MontaVista Software, Inc. This file is licensed under - * the terms of the GNU General Public License version 2. This program - * is licensed "as is" without any warranty of any kind, whether express - * or implied. + * 2007 (c) MontaVista Software, Inc. + * 2010 (c) Advanced Micro Devices Inc. + * Borislav Petkov <borislav.petkov@amd.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. * */ #include <linux/module.h> @@ -23,6 +26,8 @@ EXPORT_SYMBOL_GPL(edac_handlers); int edac_err_assert = 0; EXPORT_SYMBOL_GPL(edac_err_assert); +static atomic_t edac_class_valid = ATOMIC_INIT(0); + /* * called to determine if there is an EDAC driver interested in * knowing an event (such as NMI) occurred @@ -44,3 +49,41 @@ void edac_atomic_assert_error(void) edac_err_assert++; } EXPORT_SYMBOL_GPL(edac_atomic_assert_error); + +/* + * sysfs object: /sys/devices/system/edac + * need to export to other files + */ +struct sysdev_class edac_class = { + .name = "edac", +}; +EXPORT_SYMBOL_GPL(edac_class); + +/* return pointer to the 'edac' node in sysfs */ +struct sysdev_class *edac_get_sysfs_class(void) +{ + int err = 0; + + if (atomic_read(&edac_class_valid)) + goto out; + + /* create the /sys/devices/system/edac directory */ + err = sysdev_class_register(&edac_class); + if (err) { + printk(KERN_ERR "Error registering toplevel EDAC sysfs dir\n"); + return NULL; + } + +out: + atomic_inc(&edac_class_valid); + return &edac_class; +} +EXPORT_SYMBOL_GPL(edac_get_sysfs_class); + +void edac_put_sysfs_class(void) +{ + /* last user unregisters it */ + if (atomic_dec_and_test(&edac_class_valid)) + sysdev_class_unregister(&edac_class); +} +EXPORT_SYMBOL_GPL(edac_put_sysfs_class); diff --git a/drivers/edac/i7300_edac.c b/drivers/edac/i7300_edac.c new file mode 100644 index 00000000000..05523b50427 --- /dev/null +++ b/drivers/edac/i7300_edac.c @@ -0,0 +1,1247 @@ +/* + * Intel 7300 class Memory Controllers kernel module (Clarksboro) + * + * This file may be distributed under the terms of the + * GNU General Public License version 2 only. + * + * Copyright (c) 2010 by: + * Mauro Carvalho Chehab <mchehab@redhat.com> + * + * Red Hat Inc. http://www.redhat.com + * + * Intel 7300 Chipset Memory Controller Hub (MCH) - Datasheet + * http://www.intel.com/Assets/PDF/datasheet/318082.pdf + * + * TODO: The chipset allow checking for PCI Express errors also. Currently, + * the driver covers only memory error errors + * + * This driver uses "csrows" EDAC attribute to represent DIMM slot# + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> +#include <linux/slab.h> +#include <linux/edac.h> +#include <linux/mmzone.h> + +#include "edac_core.h" + +/* + * Alter this version for the I7300 module when modifications are made + */ +#define I7300_REVISION " Ver: 1.0.0 " __DATE__ + +#define EDAC_MOD_STR "i7300_edac" + +#define i7300_printk(level, fmt, arg...) \ + edac_printk(level, "i7300", fmt, ##arg) + +#define i7300_mc_printk(mci, level, fmt, arg...) \ + edac_mc_chipset_printk(mci, level, "i7300", fmt, ##arg) + +/*********************************************** + * i7300 Limit constants Structs and static vars + ***********************************************/ + +/* + * Memory topology is organized as: + * Branch 0 - 2 channels: channels 0 and 1 (FDB0 PCI dev 21.0) + * Branch 1 - 2 channels: channels 2 and 3 (FDB1 PCI dev 22.0) + * Each channel can have to 8 DIMM sets (called as SLOTS) + * Slots should generally be filled in pairs + * Except on Single Channel mode of operation + * just slot 0/channel0 filled on this mode + * On normal operation mode, the two channels on a branch should be + * filled together for the same SLOT# + * When in mirrored mode, Branch 1 replicate memory at Branch 0, so, the four + * channels on both branches should be filled + */ + +/* Limits for i7300 */ +#define MAX_SLOTS 8 +#define MAX_BRANCHES 2 +#define MAX_CH_PER_BRANCH 2 +#define MAX_CHANNELS (MAX_CH_PER_BRANCH * MAX_BRANCHES) +#define MAX_MIR 3 + +#define to_channel(ch, branch) ((((branch)) << 1) | (ch)) + +#define to_csrow(slot, ch, branch) \ + (to_channel(ch, branch) | ((slot) << 2)) + +/* Device name and register DID (Device ID) */ +struct i7300_dev_info { + const char *ctl_name; /* name for this device */ + u16 fsb_mapping_errors; /* DID for the branchmap,control */ +}; + +/* Table of devices attributes supported by this driver */ +static const struct i7300_dev_info i7300_devs[] = { + { + .ctl_name = "I7300", + .fsb_mapping_errors = PCI_DEVICE_ID_INTEL_I7300_MCH_ERR, + }, +}; + +struct i7300_dimm_info { + int megabytes; /* size, 0 means not present */ +}; + +/* driver private data structure */ +struct i7300_pvt { + struct pci_dev *pci_dev_16_0_fsb_ctlr; /* 16.0 */ + struct pci_dev *pci_dev_16_1_fsb_addr_map; /* 16.1 */ + struct pci_dev *pci_dev_16_2_fsb_err_regs; /* 16.2 */ + struct pci_dev *pci_dev_2x_0_fbd_branch[MAX_BRANCHES]; /* 21.0 and 22.0 */ + + u16 tolm; /* top of low memory */ + u64 ambase; /* AMB BAR */ + + u32 mc_settings; /* Report several settings */ + u32 mc_settings_a; + + u16 mir[MAX_MIR]; /* Memory Interleave Reg*/ + + u16 mtr[MAX_SLOTS][MAX_BRANCHES]; /* Memory Technlogy Reg */ + u16 ambpresent[MAX_CHANNELS]; /* AMB present regs */ + + /* DIMM information matrix, allocating architecture maximums */ + struct i7300_dimm_info dimm_info[MAX_SLOTS][MAX_CHANNELS]; + + /* Temporary buffer for use when preparing error messages */ + char *tmp_prt_buffer; +}; + +/* FIXME: Why do we need to have this static? */ +static struct edac_pci_ctl_info *i7300_pci; + +/*************************************************** + * i7300 Register definitions for memory enumeration + ***************************************************/ + +/* + * Device 16, + * Function 0: System Address (not documented) + * Function 1: Memory Branch Map, Control, Errors Register + */ + + /* OFFSETS for Function 0 */ +#define AMBASE 0x48 /* AMB Mem Mapped Reg Region Base */ +#define MAXCH 0x56 /* Max Channel Number */ +#define MAXDIMMPERCH 0x57 /* Max DIMM PER Channel Number */ + + /* OFFSETS for Function 1 */ +#define MC_SETTINGS 0x40 + #define IS_MIRRORED(mc) ((mc) & (1 << 16)) + #define IS_ECC_ENABLED(mc) ((mc) & (1 << 5)) + #define IS_RETRY_ENABLED(mc) ((mc) & (1 << 31)) + #define IS_SCRBALGO_ENHANCED(mc) ((mc) & (1 << 8)) + +#define MC_SETTINGS_A 0x58 + #define IS_SINGLE_MODE(mca) ((mca) & (1 << 14)) + +#define TOLM 0x6C + +#define MIR0 0x80 +#define MIR1 0x84 +#define MIR2 0x88 + +/* + * Note: Other Intel EDAC drivers use AMBPRESENT to identify if the available + * memory. From datasheet item 7.3.1 (FB-DIMM technology & organization), it + * seems that we cannot use this information directly for the same usage. + * Each memory slot may have up to 2 AMB interfaces, one for income and another + * for outcome interface to the next slot. + * For now, the driver just stores the AMB present registers, but rely only at + * the MTR info to detect memory. + * Datasheet is also not clear about how to map each AMBPRESENT registers to + * one of the 4 available channels. + */ +#define AMBPRESENT_0 0x64 +#define AMBPRESENT_1 0x66 + +const static u16 mtr_regs[MAX_SLOTS] = { + 0x80, 0x84, 0x88, 0x8c, + 0x82, 0x86, 0x8a, 0x8e +}; + +/* + * Defines to extract the vaious fields from the + * MTRx - Memory Technology Registers + */ +#define MTR_DIMMS_PRESENT(mtr) ((mtr) & (1 << 8)) +#define MTR_DIMMS_ETHROTTLE(mtr) ((mtr) & (1 << 7)) +#define MTR_DRAM_WIDTH(mtr) (((mtr) & (1 << 6)) ? 8 : 4) +#define MTR_DRAM_BANKS(mtr) (((mtr) & (1 << 5)) ? 8 : 4) +#define MTR_DIMM_RANKS(mtr) (((mtr) & (1 << 4)) ? 1 : 0) +#define MTR_DIMM_ROWS(mtr) (((mtr) >> 2) & 0x3) +#define MTR_DRAM_BANKS_ADDR_BITS 2 +#define MTR_DIMM_ROWS_ADDR_BITS(mtr) (MTR_DIMM_ROWS(mtr) + 13) +#define MTR_DIMM_COLS(mtr) ((mtr) & 0x3) +#define MTR_DIMM_COLS_ADDR_BITS(mtr) (MTR_DIMM_COLS(mtr) + 10) + +#ifdef CONFIG_EDAC_DEBUG +/* MTR NUMROW */ +static const char *numrow_toString[] = { + "8,192 - 13 rows", + "16,384 - 14 rows", + "32,768 - 15 rows", + "65,536 - 16 rows" +}; + +/* MTR NUMCOL */ +static const char *numcol_toString[] = { + "1,024 - 10 columns", + "2,048 - 11 columns", + "4,096 - 12 columns", + "reserved" +}; +#endif + +/************************************************ + * i7300 Register definitions for error detection + ************************************************/ + +/* + * Device 16.1: FBD Error Registers + */ +#define FERR_FAT_FBD 0x98 +static const char *ferr_fat_fbd_name[] = { + [22] = "Non-Redundant Fast Reset Timeout", + [2] = ">Tmid Thermal event with intelligent throttling disabled", + [1] = "Memory or FBD configuration CRC read error", + [0] = "Memory Write error on non-redundant retry or " + "FBD configuration Write error on retry", +}; +#define GET_FBD_FAT_IDX(fbderr) (fbderr & (3 << 28)) +#define FERR_FAT_FBD_ERR_MASK ((1 << 0) | (1 << 1) | (1 << 2) | (1 << 3)) + +#define FERR_NF_FBD 0xa0 +static const char *ferr_nf_fbd_name[] = { + [24] = "DIMM-Spare Copy Completed", + [23] = "DIMM-Spare Copy Initiated", + [22] = "Redundant Fast Reset Timeout", + [21] = "Memory Write error on redundant retry", + [18] = "SPD protocol Error", + [17] = "FBD Northbound parity error on FBD Sync Status", + [16] = "Correctable Patrol Data ECC", + [15] = "Correctable Resilver- or Spare-Copy Data ECC", + [14] = "Correctable Mirrored Demand Data ECC", + [13] = "Correctable Non-Mirrored Demand Data ECC", + [11] = "Memory or FBD configuration CRC read error", + [10] = "FBD Configuration Write error on first attempt", + [9] = "Memory Write error on first attempt", + [8] = "Non-Aliased Uncorrectable Patrol Data ECC", + [7] = "Non-Aliased Uncorrectable Resilver- or Spare-Copy Data ECC", + [6] = "Non-Aliased Uncorrectable Mirrored Demand Data ECC", + [5] = "Non-Aliased Uncorrectable Non-Mirrored Demand Data ECC", + [4] = "Aliased Uncorrectable Patrol Data ECC", + [3] = "Aliased Uncorrectable Resilver- or Spare-Copy Data ECC", + [2] = "Aliased Uncorrectable Mirrored Demand Data ECC", + [1] = "Aliased Uncorrectable Non-Mirrored Demand Data ECC", + [0] = "Uncorrectable Data ECC on Replay", +}; +#define GET_FBD_NF_IDX(fbderr) (fbderr & (3 << 28)) +#define FERR_NF_FBD_ERR_MASK ((1 << 24) | (1 << 23) | (1 << 22) | (1 << 21) |\ + (1 << 18) | (1 << 17) | (1 << 16) | (1 << 15) |\ + (1 << 14) | (1 << 13) | (1 << 11) | (1 << 10) |\ + (1 << 9) | (1 << 8) | (1 << 7) | (1 << 6) |\ + (1 << 5) | (1 << 4) | (1 << 3) | (1 << 2) |\ + (1 << 1) | (1 << 0)) + +#define EMASK_FBD 0xa8 +#define EMASK_FBD_ERR_MASK ((1 << 27) | (1 << 26) | (1 << 25) | (1 << 24) |\ + (1 << 22) | (1 << 21) | (1 << 20) | (1 << 19) |\ + (1 << 18) | (1 << 17) | (1 << 16) | (1 << 14) |\ + (1 << 13) | (1 << 12) | (1 << 11) | (1 << 10) |\ + (1 << 9) | (1 << 8) | (1 << 7) | (1 << 6) |\ + (1 << 5) | (1 << 4) | (1 << 3) | (1 << 2) |\ + (1 << 1) | (1 << 0)) + +/* + * Device 16.2: Global Error Registers + */ + +#define FERR_GLOBAL_HI 0x48 +static const char *ferr_global_hi_name[] = { + [3] = "FSB 3 Fatal Error", + [2] = "FSB 2 Fatal Error", + [1] = "FSB 1 Fatal Error", + [0] = "FSB 0 Fatal Error", +}; +#define ferr_global_hi_is_fatal(errno) 1 + +#define FERR_GLOBAL_LO 0x40 +static const char *ferr_global_lo_name[] = { + [31] = "Internal MCH Fatal Error", + [30] = "Intel QuickData Technology Device Fatal Error", + [29] = "FSB1 Fatal Error", + [28] = "FSB0 Fatal Error", + [27] = "FBD Channel 3 Fatal Error", + [26] = "FBD Channel 2 Fatal Error", + [25] = "FBD Channel 1 Fatal Error", + [24] = "FBD Channel 0 Fatal Error", + [23] = "PCI Express Device 7Fatal Error", + [22] = "PCI Express Device 6 Fatal Error", + [21] = "PCI Express Device 5 Fatal Error", + [20] = "PCI Express Device 4 Fatal Error", + [19] = "PCI Express Device 3 Fatal Error", + [18] = "PCI Express Device 2 Fatal Error", + [17] = "PCI Express Device 1 Fatal Error", + [16] = "ESI Fatal Error", + [15] = "Internal MCH Non-Fatal Error", + [14] = "Intel QuickData Technology Device Non Fatal Error", + [13] = "FSB1 Non-Fatal Error", + [12] = "FSB 0 Non-Fatal Error", + [11] = "FBD Channel 3 Non-Fatal Error", + [10] = "FBD Channel 2 Non-Fatal Error", + [9] = "FBD Channel 1 Non-Fatal Error", + [8] = "FBD Channel 0 Non-Fatal Error", + [7] = "PCI Express Device 7 Non-Fatal Error", + [6] = "PCI Express Device 6 Non-Fatal Error", + [5] = "PCI Express Device 5 Non-Fatal Error", + [4] = "PCI Express Device 4 Non-Fatal Error", + [3] = "PCI Express Device 3 Non-Fatal Error", + [2] = "PCI Express Device 2 Non-Fatal Error", + [1] = "PCI Express Device 1 Non-Fatal Error", + [0] = "ESI Non-Fatal Error", +}; +#define ferr_global_lo_is_fatal(errno) ((errno < 16) ? 0 : 1) + +#define NRECMEMA 0xbe + #define NRECMEMA_BANK(v) (((v) >> 12) & 7) + #define NRECMEMA_RANK(v) (((v) >> 8) & 15) + +#define NRECMEMB 0xc0 + #define NRECMEMB_IS_WR(v) ((v) & (1 << 31)) + #define NRECMEMB_CAS(v) (((v) >> 16) & 0x1fff) + #define NRECMEMB_RAS(v) ((v) & 0xffff) + +#define REDMEMA 0xdc + +#define REDMEMB 0x7c + #define IS_SECOND_CH(v) ((v) * (1 << 17)) + +#define RECMEMA 0xe0 + #define RECMEMA_BANK(v) (((v) >> 12) & 7) + #define RECMEMA_RANK(v) (((v) >> 8) & 15) + +#define RECMEMB 0xe4 + #define RECMEMB_IS_WR(v) ((v) & (1 << 31)) + #define RECMEMB_CAS(v) (((v) >> 16) & 0x1fff) + #define RECMEMB_RAS(v) ((v) & 0xffff) + +/******************************************** + * i7300 Functions related to error detection + ********************************************/ + +/** + * get_err_from_table() - Gets the error message from a table + * @table: table name (array of char *) + * @size: number of elements at the table + * @pos: position of the element to be returned + * + * This is a small routine that gets the pos-th element of a table. If the + * element doesn't exist (or it is empty), it returns "reserved". + * Instead of calling it directly, the better is to call via the macro + * GET_ERR_FROM_TABLE(), that automatically checks the table size via + * ARRAY_SIZE() macro + */ +static const char *get_err_from_table(const char *table[], int size, int pos) +{ + if (unlikely(pos >= size)) + return "Reserved"; + + if (unlikely(!table[pos])) + return "Reserved"; + + return table[pos]; +} + +#define GET_ERR_FROM_TABLE(table, pos) \ + get_err_from_table(table, ARRAY_SIZE(table), pos) + +/** + * i7300_process_error_global() - Retrieve the hardware error information from + * the hardware global error registers and + * sends it to dmesg + * @mci: struct mem_ctl_info pointer + */ +static void i7300_process_error_global(struct mem_ctl_info *mci) +{ + struct i7300_pvt *pvt; + u32 errnum, value; + unsigned long errors; + const char *specific; + bool is_fatal; + + pvt = mci->pvt_info; + + /* read in the 1st FATAL error register */ + pci_read_config_dword(pvt->pci_dev_16_2_fsb_err_regs, + FERR_GLOBAL_HI, &value); + if (unlikely(value)) { + errors = value; + errnum = find_first_bit(&errors, + ARRAY_SIZE(ferr_global_hi_name)); + specific = GET_ERR_FROM_TABLE(ferr_global_hi_name, errnum); + is_fatal = ferr_global_hi_is_fatal(errnum); + + /* Clear the error bit */ + pci_write_config_dword(pvt->pci_dev_16_2_fsb_err_regs, + FERR_GLOBAL_HI, value); + + goto error_global; + } + + pci_read_config_dword(pvt->pci_dev_16_2_fsb_err_regs, + FERR_GLOBAL_LO, &value); + if (unlikely(value)) { + errors = value; + errnum = find_first_bit(&errors, + ARRAY_SIZE(ferr_global_lo_name)); + specific = GET_ERR_FROM_TABLE(ferr_global_lo_name, errnum); + is_fatal = ferr_global_lo_is_fatal(errnum); + + /* Clear the error bit */ + pci_write_config_dword(pvt->pci_dev_16_2_fsb_err_regs, + FERR_GLOBAL_LO, value); + + goto error_global; + } + return; + +error_global: + i7300_mc_printk(mci, KERN_EMERG, "%s misc error: %s\n", + is_fatal ? "Fatal" : "NOT fatal", specific); +} + +/** + * i7300_process_fbd_error() - Retrieve the hardware error information from + * the FBD error registers and sends it via + * EDAC error API calls + * @mci: struct mem_ctl_info pointer + */ +static void i7300_process_fbd_error(struct mem_ctl_info *mci) +{ + struct i7300_pvt *pvt; + u32 errnum, value; + u16 val16; + unsigned branch, channel, bank, rank, cas, ras; + u32 syndrome; + + unsigned long errors; + const char *specific; + bool is_wr; + + pvt = mci->pvt_info; + + /* read in the 1st FATAL error register */ + pci_read_config_dword(pvt->pci_dev_16_1_fsb_addr_map, + FERR_FAT_FBD, &value); + if (unlikely(value & FERR_FAT_FBD_ERR_MASK)) { + errors = value & FERR_FAT_FBD_ERR_MASK ; + errnum = find_first_bit(&errors, + ARRAY_SIZE(ferr_fat_fbd_name)); + specific = GET_ERR_FROM_TABLE(ferr_fat_fbd_name, errnum); + + branch = (GET_FBD_FAT_IDX(value) == 2) ? 1 : 0; + pci_read_config_word(pvt->pci_dev_16_1_fsb_addr_map, + NRECMEMA, &val16); + bank = NRECMEMA_BANK(val16); + rank = NRECMEMA_RANK(val16); + + pci_read_config_dword(pvt->pci_dev_16_1_fsb_addr_map, + NRECMEMB, &value); + + is_wr = NRECMEMB_IS_WR(value); + cas = NRECMEMB_CAS(value); + ras = NRECMEMB_RAS(value); + + snprintf(pvt->tmp_prt_buffer, PAGE_SIZE, + "FATAL (Branch=%d DRAM-Bank=%d %s " + "RAS=%d CAS=%d Err=0x%lx (%s))", + branch, bank, + is_wr ? "RDWR" : "RD", + ras, cas, + errors, specific); + + /* Call the helper to output message */ + edac_mc_handle_fbd_ue(mci, rank, branch << 1, + (branch << 1) + 1, + pvt->tmp_prt_buffer); + } + + /* read in the 1st NON-FATAL error register */ + pci_read_config_dword(pvt->pci_dev_16_1_fsb_addr_map, + FERR_NF_FBD, &value); + if (unlikely(value & FERR_NF_FBD_ERR_MASK)) { + errors = value & FERR_NF_FBD_ERR_MASK; + errnum = find_first_bit(&errors, + ARRAY_SIZE(ferr_nf_fbd_name)); + specific = GET_ERR_FROM_TABLE(ferr_nf_fbd_name, errnum); + + /* Clear the error bit */ + pci_write_config_dword(pvt->pci_dev_16_2_fsb_err_regs, + FERR_GLOBAL_LO, value); + + pci_read_config_dword(pvt->pci_dev_16_1_fsb_addr_map, + REDMEMA, &syndrome); + + branch = (GET_FBD_FAT_IDX(value) == 2) ? 1 : 0; + pci_read_config_word(pvt->pci_dev_16_1_fsb_addr_map, + RECMEMA, &val16); + bank = RECMEMA_BANK(val16); + rank = RECMEMA_RANK(val16); + + pci_read_config_dword(pvt->pci_dev_16_1_fsb_addr_map, + RECMEMB, &value); + + is_wr = RECMEMB_IS_WR(value); + cas = RECMEMB_CAS(value); + ras = RECMEMB_RAS(value); + + pci_read_config_dword(pvt->pci_dev_16_1_fsb_addr_map, + REDMEMB, &value); + + channel = (branch << 1); + if (IS_SECOND_CH(value)) + channel++; + + /* Form out message */ + snprintf(pvt->tmp_prt_buffer, PAGE_SIZE, + "Corrected error (Branch=%d, Channel %d), " + " DRAM-Bank=%d %s " + "RAS=%d CAS=%d, CE Err=0x%lx, Syndrome=0x%08x(%s))", + branch, channel, + bank, + is_wr ? "RDWR" : "RD", + ras, cas, + errors, syndrome, specific); + + /* + * Call the helper to output message + * NOTE: Errors are reported per-branch, and not per-channel + * Currently, we don't know how to identify the right + * channel. + */ + edac_mc_handle_fbd_ce(mci, rank, channel, + pvt->tmp_prt_buffer); + } + return; +} + +/** + * i7300_check_error() - Calls the error checking subroutines + * @mci: struct mem_ctl_info pointer + */ +static void i7300_check_error(struct mem_ctl_info *mci) +{ + i7300_process_error_global(mci); + i7300_process_fbd_error(mci); +}; + +/** + * i7300_clear_error() - Clears the error registers + * @mci: struct mem_ctl_info pointer + */ +static void i7300_clear_error(struct mem_ctl_info *mci) +{ + struct i7300_pvt *pvt = mci->pvt_info; + u32 value; + /* + * All error values are RWC - we need to read and write 1 to the + * bit that we want to cleanup + */ + + /* Clear global error registers */ + pci_read_config_dword(pvt->pci_dev_16_2_fsb_err_regs, + FERR_GLOBAL_HI, &value); + pci_write_config_dword(pvt->pci_dev_16_2_fsb_err_regs, + FERR_GLOBAL_HI, value); + + pci_read_config_dword(pvt->pci_dev_16_2_fsb_err_regs, + FERR_GLOBAL_LO, &value); + pci_write_config_dword(pvt->pci_dev_16_2_fsb_err_regs, + FERR_GLOBAL_LO, value); + + /* Clear FBD error registers */ + pci_read_config_dword(pvt->pci_dev_16_1_fsb_addr_map, + FERR_FAT_FBD, &value); + pci_write_config_dword(pvt->pci_dev_16_1_fsb_addr_map, + FERR_FAT_FBD, value); + + pci_read_config_dword(pvt->pci_dev_16_1_fsb_addr_map, + FERR_NF_FBD, &value); + pci_write_config_dword(pvt->pci_dev_16_1_fsb_addr_map, + FERR_NF_FBD, value); +} + +/** + * i7300_enable_error_reporting() - Enable the memory reporting logic at the + * hardware + * @mci: struct mem_ctl_info pointer + */ +static void i7300_enable_error_reporting(struct mem_ctl_info *mci) +{ + struct i7300_pvt *pvt = mci->pvt_info; + u32 fbd_error_mask; + + /* Read the FBD Error Mask Register */ + pci_read_config_dword(pvt->pci_dev_16_1_fsb_addr_map, + EMASK_FBD, &fbd_error_mask); + + /* Enable with a '0' */ + fbd_error_mask &= ~(EMASK_FBD_ERR_MASK); + + pci_write_config_dword(pvt->pci_dev_16_1_fsb_addr_map, + EMASK_FBD, fbd_error_mask); +} + +/************************************************ + * i7300 Functions related to memory enumberation + ************************************************/ + +/** + * decode_mtr() - Decodes the MTR descriptor, filling the edac structs + * @pvt: pointer to the private data struct used by i7300 driver + * @slot: DIMM slot (0 to 7) + * @ch: Channel number within the branch (0 or 1) + * @branch: Branch number (0 or 1) + * @dinfo: Pointer to DIMM info where dimm size is stored + * @p_csrow: Pointer to the struct csrow_info that corresponds to that element + */ +static int decode_mtr(struct i7300_pvt *pvt, + int slot, int ch, int branch, + struct i7300_dimm_info *dinfo, + struct csrow_info *p_csrow, + u32 *nr_pages) +{ + int mtr, ans, addrBits, channel; + + channel = to_channel(ch, branch); + + mtr = pvt->mtr[slot][branch]; + ans = MTR_DIMMS_PRESENT(mtr) ? 1 : 0; + + debugf2("\tMTR%d CH%d: DIMMs are %s (mtr)\n", + slot, channel, + ans ? "Present" : "NOT Present"); + + /* Determine if there is a DIMM present in this DIMM slot */ + if (!ans) + return 0; + + /* Start with the number of bits for a Bank + * on the DRAM */ + addrBits = MTR_DRAM_BANKS_ADDR_BITS; + /* Add thenumber of ROW bits */ + addrBits += MTR_DIMM_ROWS_ADDR_BITS(mtr); + /* add the number of COLUMN bits */ + addrBits += MTR_DIMM_COLS_ADDR_BITS(mtr); + /* add the number of RANK bits */ + addrBits += MTR_DIMM_RANKS(mtr); + + addrBits += 6; /* add 64 bits per DIMM */ + addrBits -= 20; /* divide by 2^^20 */ + addrBits -= 3; /* 8 bits per bytes */ + + dinfo->megabytes = 1 << addrBits; + *nr_pages = dinfo->megabytes << 8; + + debugf2("\t\tWIDTH: x%d\n", MTR_DRAM_WIDTH(mtr)); + + debugf2("\t\tELECTRICAL THROTTLING is %s\n", + MTR_DIMMS_ETHROTTLE(mtr) ? "enabled" : "disabled"); + + debugf2("\t\tNUMBANK: %d bank(s)\n", MTR_DRAM_BANKS(mtr)); + debugf2("\t\tNUMRANK: %s\n", MTR_DIMM_RANKS(mtr) ? "double" : "single"); + debugf2("\t\tNUMROW: %s\n", numrow_toString[MTR_DIMM_ROWS(mtr)]); + debugf2("\t\tNUMCOL: %s\n", numcol_toString[MTR_DIMM_COLS(mtr)]); + debugf2("\t\tSIZE: %d MB\n", dinfo->megabytes); + + p_csrow->grain = 8; + p_csrow->mtype = MEM_FB_DDR2; + p_csrow->csrow_idx = slot; + p_csrow->page_mask = 0; + + /* + * The type of error detection actually depends of the + * mode of operation. When it is just one single memory chip, at + * socket 0, channel 0, it uses 8-byte-over-32-byte SECDED+ code. + * In normal or mirrored mode, it uses Lockstep mode, + * with the possibility of using an extended algorithm for x8 memories + * See datasheet Sections 7.3.6 to 7.3.8 + */ + + if (IS_SINGLE_MODE(pvt->mc_settings_a)) { + p_csrow->edac_mode = EDAC_SECDED; + debugf2("\t\tECC code is 8-byte-over-32-byte SECDED+ code\n"); + } else { + debugf2("\t\tECC code is on Lockstep mode\n"); + if (MTR_DRAM_WIDTH(mtr) == 8) + p_csrow->edac_mode = EDAC_S8ECD8ED; + else + p_csrow->edac_mode = EDAC_S4ECD4ED; + } + + /* ask what device type on this row */ + if (MTR_DRAM_WIDTH(mtr) == 8) { + debugf2("\t\tScrub algorithm for x8 is on %s mode\n", + IS_SCRBALGO_ENHANCED(pvt->mc_settings) ? + "enhanced" : "normal"); + + p_csrow->dtype = DEV_X8; + } else + p_csrow->dtype = DEV_X4; + + return mtr; +} + +/** + * print_dimm_size() - Prints dump of the memory organization + * @pvt: pointer to the private data struct used by i7300 driver + * + * Useful for debug. If debug is disabled, this routine do nothing + */ +static void print_dimm_size(struct i7300_pvt *pvt) +{ +#ifdef CONFIG_EDAC_DEBUG + struct i7300_dimm_info *dinfo; + char *p; + int space, n; + int channel, slot; + + space = PAGE_SIZE; + p = pvt->tmp_prt_buffer; + + n = snprintf(p, space, " "); + p += n; + space -= n; + for (channel = 0; channel < MAX_CHANNELS; channel++) { + n = snprintf(p, space, "channel %d | ", channel); + p += n; + space -= n; + } + debugf2("%s\n", pvt->tmp_prt_buffer); + p = pvt->tmp_prt_buffer; + space = PAGE_SIZE; + n = snprintf(p, space, "-------------------------------" + "------------------------------"); + p += n; + space -= n; + debugf2("%s\n", pvt->tmp_prt_buffer); + p = pvt->tmp_prt_buffer; + space = PAGE_SIZE; + + for (slot = 0; slot < MAX_SLOTS; slot++) { + n = snprintf(p, space, "csrow/SLOT %d ", slot); + p += n; + space -= n; + + for (channel = 0; channel < MAX_CHANNELS; channel++) { + dinfo = &pvt->dimm_info[slot][channel]; + n = snprintf(p, space, "%4d MB | ", dinfo->megabytes); + p += n; + space -= n; + } + + debugf2("%s\n", pvt->tmp_prt_buffer); + p = pvt->tmp_prt_buffer; + space = PAGE_SIZE; + } + + n = snprintf(p, space, "-------------------------------" + "------------------------------"); + p += n; + space -= n; + debugf2("%s\n", pvt->tmp_prt_buffer); + p = pvt->tmp_prt_buffer; + space = PAGE_SIZE; +#endif +} + +/** + * i7300_init_csrows() - Initialize the 'csrows' table within + * the mci control structure with the + * addressing of memory. + * @mci: struct mem_ctl_info pointer + */ +static int i7300_init_csrows(struct mem_ctl_info *mci) +{ + struct i7300_pvt *pvt; + struct i7300_dimm_info *dinfo; + struct csrow_info *p_csrow; + int rc = -ENODEV; + int mtr; + int ch, branch, slot, channel; + u32 last_page = 0, nr_pages; + + pvt = mci->pvt_info; + + debugf2("Memory Technology Registers:\n"); + + /* Get the AMB present registers for the four channels */ + for (branch = 0; branch < MAX_BRANCHES; branch++) { + /* Read and dump branch 0's MTRs */ + channel = to_channel(0, branch); + pci_read_config_word(pvt->pci_dev_2x_0_fbd_branch[branch], + AMBPRESENT_0, + &pvt->ambpresent[channel]); + debugf2("\t\tAMB-present CH%d = 0x%x:\n", + channel, pvt->ambpresent[channel]); + + channel = to_channel(1, branch); + pci_read_config_word(pvt->pci_dev_2x_0_fbd_branch[branch], + AMBPRESENT_1, + &pvt->ambpresent[channel]); + debugf2("\t\tAMB-present CH%d = 0x%x:\n", + channel, pvt->ambpresent[channel]); + } + + /* Get the set of MTR[0-7] regs by each branch */ + for (slot = 0; slot < MAX_SLOTS; slot++) { + int where = mtr_regs[slot]; + for (branch = 0; branch < MAX_BRANCHES; branch++) { + pci_read_config_word(pvt->pci_dev_2x_0_fbd_branch[branch], + where, + &pvt->mtr[slot][branch]); + for (ch = 0; ch < MAX_BRANCHES; ch++) { + int channel = to_channel(ch, branch); + + dinfo = &pvt->dimm_info[slot][channel]; + p_csrow = &mci->csrows[slot]; + + mtr = decode_mtr(pvt, slot, ch, branch, + dinfo, p_csrow, &nr_pages); + /* if no DIMMS on this row, continue */ + if (!MTR_DIMMS_PRESENT(mtr)) + continue; + + /* Update per_csrow memory count */ + p_csrow->nr_pages += nr_pages; + p_csrow->first_page = last_page; + last_page += nr_pages; + p_csrow->last_page = last_page; + + rc = 0; + } + } + } + + return rc; +} + +/** + * decode_mir() - Decodes Memory Interleave Register (MIR) info + * @int mir_no: number of the MIR register to decode + * @mir: array with the MIR data cached on the driver + */ +static void decode_mir(int mir_no, u16 mir[MAX_MIR]) +{ + if (mir[mir_no] & 3) + debugf2("MIR%d: limit= 0x%x Branch(es) that participate:" + " %s %s\n", + mir_no, + (mir[mir_no] >> 4) & 0xfff, + (mir[mir_no] & 1) ? "B0" : "", + (mir[mir_no] & 2) ? "B1" : ""); +} + +/** + * i7300_get_mc_regs() - Get the contents of the MC enumeration registers + * @mci: struct mem_ctl_info pointer + * + * Data read is cached internally for its usage when needed + */ +static int i7300_get_mc_regs(struct mem_ctl_info *mci) +{ + struct i7300_pvt *pvt; + u32 actual_tolm; + int i, rc; + + pvt = mci->pvt_info; + + pci_read_config_dword(pvt->pci_dev_16_0_fsb_ctlr, AMBASE, + (u32 *) &pvt->ambase); + + debugf2("AMBASE= 0x%lx\n", (long unsigned int)pvt->ambase); + + /* Get the Branch Map regs */ + pci_read_config_word(pvt->pci_dev_16_1_fsb_addr_map, TOLM, &pvt->tolm); + pvt->tolm >>= 12; + debugf2("TOLM (number of 256M regions) =%u (0x%x)\n", pvt->tolm, + pvt->tolm); + + actual_tolm = (u32) ((1000l * pvt->tolm) >> (30 - 28)); + debugf2("Actual TOLM byte addr=%u.%03u GB (0x%x)\n", + actual_tolm/1000, actual_tolm % 1000, pvt->tolm << 28); + + /* Get memory controller settings */ + pci_read_config_dword(pvt->pci_dev_16_1_fsb_addr_map, MC_SETTINGS, + &pvt->mc_settings); + pci_read_config_dword(pvt->pci_dev_16_1_fsb_addr_map, MC_SETTINGS_A, + &pvt->mc_settings_a); + + if (IS_SINGLE_MODE(pvt->mc_settings_a)) + debugf0("Memory controller operating on single mode\n"); + else + debugf0("Memory controller operating on %s mode\n", + IS_MIRRORED(pvt->mc_settings) ? "mirrored" : "non-mirrored"); + + debugf0("Error detection is %s\n", + IS_ECC_ENABLED(pvt->mc_settings) ? "enabled" : "disabled"); + debugf0("Retry is %s\n", + IS_RETRY_ENABLED(pvt->mc_settings) ? "enabled" : "disabled"); + + /* Get Memory Interleave Range registers */ + pci_read_config_word(pvt->pci_dev_16_1_fsb_addr_map, MIR0, + &pvt->mir[0]); + pci_read_config_word(pvt->pci_dev_16_1_fsb_addr_map, MIR1, + &pvt->mir[1]); + pci_read_config_word(pvt->pci_dev_16_1_fsb_addr_map, MIR2, + &pvt->mir[2]); + + /* Decode the MIR regs */ + for (i = 0; i < MAX_MIR; i++) + decode_mir(i, pvt->mir); + + rc = i7300_init_csrows(mci); + if (rc < 0) + return rc; + + /* Go and determine the size of each DIMM and place in an + * orderly matrix */ + print_dimm_size(pvt); + + return 0; +} + +/************************************************* + * i7300 Functions related to device probe/release + *************************************************/ + +/** + * i7300_put_devices() - Release the PCI devices + * @mci: struct mem_ctl_info pointer + */ +static void i7300_put_devices(struct mem_ctl_info *mci) +{ + struct i7300_pvt *pvt; + int branch; + + pvt = mci->pvt_info; + + /* Decrement usage count for devices */ + for (branch = 0; branch < MAX_CH_PER_BRANCH; branch++) + pci_dev_put(pvt->pci_dev_2x_0_fbd_branch[branch]); + pci_dev_put(pvt->pci_dev_16_2_fsb_err_regs); + pci_dev_put(pvt->pci_dev_16_1_fsb_addr_map); +} + +/** + * i7300_get_devices() - Find and perform 'get' operation on the MCH's + * device/functions we want to reference for this driver + * @mci: struct mem_ctl_info pointer + * + * Access and prepare the several devices for usage: + * I7300 devices used by this driver: + * Device 16, functions 0,1 and 2: PCI_DEVICE_ID_INTEL_I7300_MCH_ERR + * Device 21 function 0: PCI_DEVICE_ID_INTEL_I7300_MCH_FB0 + * Device 22 function 0: PCI_DEVICE_ID_INTEL_I7300_MCH_FB1 + */ +static int __devinit i7300_get_devices(struct mem_ctl_info *mci) +{ + struct i7300_pvt *pvt; + struct pci_dev *pdev; + + pvt = mci->pvt_info; + + /* Attempt to 'get' the MCH register we want */ + pdev = NULL; + while (!pvt->pci_dev_16_1_fsb_addr_map || + !pvt->pci_dev_16_2_fsb_err_regs) { + pdev = pci_get_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_I7300_MCH_ERR, pdev); + if (!pdev) { + /* End of list, leave */ + i7300_printk(KERN_ERR, + "'system address,Process Bus' " + "device not found:" + "vendor 0x%x device 0x%x ERR funcs " + "(broken BIOS?)\n", + PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_I7300_MCH_ERR); + goto error; + } + + /* Store device 16 funcs 1 and 2 */ + switch (PCI_FUNC(pdev->devfn)) { + case 1: + pvt->pci_dev_16_1_fsb_addr_map = pdev; + break; + case 2: + pvt->pci_dev_16_2_fsb_err_regs = pdev; + break; + } + } + + debugf1("System Address, processor bus- PCI Bus ID: %s %x:%x\n", + pci_name(pvt->pci_dev_16_0_fsb_ctlr), + pvt->pci_dev_16_0_fsb_ctlr->vendor, + pvt->pci_dev_16_0_fsb_ctlr->device); + debugf1("Branchmap, control and errors - PCI Bus ID: %s %x:%x\n", + pci_name(pvt->pci_dev_16_1_fsb_addr_map), + pvt->pci_dev_16_1_fsb_addr_map->vendor, + pvt->pci_dev_16_1_fsb_addr_map->device); + debugf1("FSB Error Regs - PCI Bus ID: %s %x:%x\n", + pci_name(pvt->pci_dev_16_2_fsb_err_regs), + pvt->pci_dev_16_2_fsb_err_regs->vendor, + pvt->pci_dev_16_2_fsb_err_regs->device); + + pvt->pci_dev_2x_0_fbd_branch[0] = pci_get_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_I7300_MCH_FB0, + NULL); + if (!pvt->pci_dev_2x_0_fbd_branch[0]) { + i7300_printk(KERN_ERR, + "MC: 'BRANCH 0' device not found:" + "vendor 0x%x device 0x%x Func 0 (broken BIOS?)\n", + PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_I7300_MCH_FB0); + goto error; + } + + pvt->pci_dev_2x_0_fbd_branch[1] = pci_get_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_I7300_MCH_FB1, + NULL); + if (!pvt->pci_dev_2x_0_fbd_branch[1]) { + i7300_printk(KERN_ERR, + "MC: 'BRANCH 1' device not found:" + "vendor 0x%x device 0x%x Func 0 " + "(broken BIOS?)\n", + PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_I7300_MCH_FB1); + goto error; + } + + return 0; + +error: + i7300_put_devices(mci); + return -ENODEV; +} + +/** + * i7300_init_one() - Probe for one instance of the device + * @pdev: struct pci_dev pointer + * @id: struct pci_device_id pointer - currently unused + */ +static int __devinit i7300_init_one(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct mem_ctl_info *mci; + struct i7300_pvt *pvt; + int num_channels; + int num_dimms_per_channel; + int num_csrows; + int rc; + + /* wake up device */ + rc = pci_enable_device(pdev); + if (rc == -EIO) + return rc; + + debugf0("MC: " __FILE__ ": %s(), pdev bus %u dev=0x%x fn=0x%x\n", + __func__, + pdev->bus->number, + PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn)); + + /* We only are looking for func 0 of the set */ + if (PCI_FUNC(pdev->devfn) != 0) + return -ENODEV; + + /* As we don't have a motherboard identification routine to determine + * actual number of slots/dimms per channel, we thus utilize the + * resource as specified by the chipset. Thus, we might have + * have more DIMMs per channel than actually on the mobo, but this + * allows the driver to support upto the chipset max, without + * some fancy mobo determination. + */ + num_dimms_per_channel = MAX_SLOTS; + num_channels = MAX_CHANNELS; + num_csrows = MAX_SLOTS * MAX_CHANNELS; + + debugf0("MC: %s(): Number of - Channels= %d DIMMS= %d CSROWS= %d\n", + __func__, num_channels, num_dimms_per_channel, num_csrows); + + /* allocate a new MC control structure */ + mci = edac_mc_alloc(sizeof(*pvt), num_csrows, num_channels, 0); + + if (mci == NULL) + return -ENOMEM; + + debugf0("MC: " __FILE__ ": %s(): mci = %p\n", __func__, mci); + + mci->dev = &pdev->dev; /* record ptr to the generic device */ + + pvt = mci->pvt_info; + pvt->pci_dev_16_0_fsb_ctlr = pdev; /* Record this device in our private */ + + pvt->tmp_prt_buffer = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!pvt->tmp_prt_buffer) { + edac_mc_free(mci); + return -ENOMEM; + } + + /* 'get' the pci devices we want to reserve for our use */ + if (i7300_get_devices(mci)) + goto fail0; + + mci->mc_idx = 0; + mci->mtype_cap = MEM_FLAG_FB_DDR2; + mci->edac_ctl_cap = EDAC_FLAG_NONE; + mci->edac_cap = EDAC_FLAG_NONE; + mci->mod_name = "i7300_edac.c"; + mci->mod_ver = I7300_REVISION; + mci->ctl_name = i7300_devs[0].ctl_name; + mci->dev_name = pci_name(pdev); + mci->ctl_page_to_phys = NULL; + + /* Set the function pointer to an actual operation function */ + mci->edac_check = i7300_check_error; + + /* initialize the MC control structure 'csrows' table + * with the mapping and control information */ + if (i7300_get_mc_regs(mci)) { + debugf0("MC: Setting mci->edac_cap to EDAC_FLAG_NONE\n" + " because i7300_init_csrows() returned nonzero " + "value\n"); + mci->edac_cap = EDAC_FLAG_NONE; /* no csrows found */ + } else { + debugf1("MC: Enable error reporting now\n"); + i7300_enable_error_reporting(mci); + } + + /* add this new MC control structure to EDAC's list of MCs */ + if (edac_mc_add_mc(mci)) { + debugf0("MC: " __FILE__ + ": %s(): failed edac_mc_add_mc()\n", __func__); + /* FIXME: perhaps some code should go here that disables error + * reporting if we just enabled it + */ + goto fail1; + } + + i7300_clear_error(mci); + + /* allocating generic PCI control info */ + i7300_pci = edac_pci_create_generic_ctl(&pdev->dev, EDAC_MOD_STR); + if (!i7300_pci) { + printk(KERN_WARNING + "%s(): Unable to create PCI control\n", + __func__); + printk(KERN_WARNING + "%s(): PCI error report via EDAC not setup\n", + __func__); + } + + return 0; + + /* Error exit unwinding stack */ +fail1: + + i7300_put_devices(mci); + +fail0: + kfree(pvt->tmp_prt_buffer); + edac_mc_free(mci); + return -ENODEV; +} + +/** + * i7300_remove_one() - Remove the driver + * @pdev: struct pci_dev pointer + */ +static void __devexit i7300_remove_one(struct pci_dev *pdev) +{ + struct mem_ctl_info *mci; + char *tmp; + + debugf0(__FILE__ ": %s()\n", __func__); + + if (i7300_pci) + edac_pci_release_generic_ctl(i7300_pci); + + mci = edac_mc_del_mc(&pdev->dev); + if (!mci) + return; + + tmp = ((struct i7300_pvt *)mci->pvt_info)->tmp_prt_buffer; + + /* retrieve references to resources, and free those resources */ + i7300_put_devices(mci); + + kfree(tmp); + edac_mc_free(mci); +} + +/* + * pci_device_id: table for which devices we are looking for + * + * Has only 8086:360c PCI ID + */ +static const struct pci_device_id i7300_pci_tbl[] __devinitdata = { + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_I7300_MCH_ERR)}, + {0,} /* 0 terminated list. */ +}; + +MODULE_DEVICE_TABLE(pci, i7300_pci_tbl); + +/* + * i7300_driver: pci_driver structure for this module + */ +static struct pci_driver i7300_driver = { + .name = "i7300_edac", + .probe = i7300_init_one, + .remove = __devexit_p(i7300_remove_one), + .id_table = i7300_pci_tbl, +}; + +/** + * i7300_init() - Registers the driver + */ +static int __init i7300_init(void) +{ + int pci_rc; + + debugf2("MC: " __FILE__ ": %s()\n", __func__); + + /* Ensure that the OPSTATE is set correctly for POLL or NMI */ + opstate_init(); + + pci_rc = pci_register_driver(&i7300_driver); + + return (pci_rc < 0) ? pci_rc : 0; +} + +/** + * i7300_init() - Unregisters the driver + */ +static void __exit i7300_exit(void) +{ + debugf2("MC: " __FILE__ ": %s()\n", __func__); + pci_unregister_driver(&i7300_driver); +} + +module_init(i7300_init); +module_exit(i7300_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@redhat.com>"); +MODULE_AUTHOR("Red Hat Inc. (http://www.redhat.com)"); +MODULE_DESCRIPTION("MC Driver for Intel I7300 memory controllers - " + I7300_REVISION); + +module_param(edac_op_state, int, 0444); +MODULE_PARM_DESC(edac_op_state, "EDAC Error Reporting state: 0=Poll,1=NMI"); diff --git a/drivers/edac/i7core_edac.c b/drivers/edac/i7core_edac.c index 0fd5b85a0f7..362861c1577 100644 --- a/drivers/edac/i7core_edac.c +++ b/drivers/edac/i7core_edac.c @@ -39,6 +39,14 @@ #include "edac_core.h" +/* Static vars */ +static LIST_HEAD(i7core_edac_list); +static DEFINE_MUTEX(i7core_edac_lock); +static int probed; + +static int use_pci_fixup; +module_param(use_pci_fixup, int, 0444); +MODULE_PARM_DESC(use_pci_fixup, "Enable PCI fixup to seek for hidden devices"); /* * This is used for Nehalem-EP and Nehalem-EX devices, where the non-core * registers start at bus 255, and are not reported by BIOS. @@ -212,8 +220,8 @@ struct pci_id_descr { }; struct pci_id_table { - struct pci_id_descr *descr; - int n_devs; + const struct pci_id_descr *descr; + int n_devs; }; struct i7core_dev { @@ -235,8 +243,6 @@ struct i7core_pvt { struct i7core_inject inject; struct i7core_channel channel[NUM_CHANS]; - int channels; /* Number of active channels */ - int ce_count_available; int csrow_map[NUM_CHANS][MAX_DIMMS]; @@ -261,22 +267,22 @@ struct i7core_pvt { /* Count indicator to show errors not got */ unsigned mce_overrun; -}; -/* Static vars */ -static LIST_HEAD(i7core_edac_list); -static DEFINE_MUTEX(i7core_edac_lock); + /* Struct to control EDAC polling */ + struct edac_pci_ctl_info *i7core_pci; +}; #define PCI_DESCR(device, function, device_id) \ .dev = (device), \ .func = (function), \ .dev_id = (device_id) -struct pci_id_descr pci_dev_descr_i7core_nehalem[] = { +static const struct pci_id_descr pci_dev_descr_i7core_nehalem[] = { /* Memory controller */ { PCI_DESCR(3, 0, PCI_DEVICE_ID_INTEL_I7_MCR) }, { PCI_DESCR(3, 1, PCI_DEVICE_ID_INTEL_I7_MC_TAD) }, - /* Exists only for RDIMM */ + + /* Exists only for RDIMM */ { PCI_DESCR(3, 2, PCI_DEVICE_ID_INTEL_I7_MC_RAS), .optional = 1 }, { PCI_DESCR(3, 4, PCI_DEVICE_ID_INTEL_I7_MC_TEST) }, @@ -297,19 +303,9 @@ struct pci_id_descr pci_dev_descr_i7core_nehalem[] = { { PCI_DESCR(6, 1, PCI_DEVICE_ID_INTEL_I7_MC_CH2_ADDR) }, { PCI_DESCR(6, 2, PCI_DEVICE_ID_INTEL_I7_MC_CH2_RANK) }, { PCI_DESCR(6, 3, PCI_DEVICE_ID_INTEL_I7_MC_CH2_TC) }, - - /* Generic Non-core registers */ - /* - * This is the PCI device on i7core and on Xeon 35xx (8086:2c41) - * On Xeon 55xx, however, it has a different id (8086:2c40). So, - * the probing code needs to test for the other address in case of - * failure of this one - */ - { PCI_DESCR(0, 0, PCI_DEVICE_ID_INTEL_I7_NONCORE) }, - }; -struct pci_id_descr pci_dev_descr_lynnfield[] = { +static const struct pci_id_descr pci_dev_descr_lynnfield[] = { { PCI_DESCR( 3, 0, PCI_DEVICE_ID_INTEL_LYNNFIELD_MCR) }, { PCI_DESCR( 3, 1, PCI_DEVICE_ID_INTEL_LYNNFIELD_MC_TAD) }, { PCI_DESCR( 3, 4, PCI_DEVICE_ID_INTEL_LYNNFIELD_MC_TEST) }, @@ -323,15 +319,9 @@ struct pci_id_descr pci_dev_descr_lynnfield[] = { { PCI_DESCR( 5, 1, PCI_DEVICE_ID_INTEL_LYNNFIELD_MC_CH1_ADDR) }, { PCI_DESCR( 5, 2, PCI_DEVICE_ID_INTEL_LYNNFIELD_MC_CH1_RANK) }, { PCI_DESCR( 5, 3, PCI_DEVICE_ID_INTEL_LYNNFIELD_MC_CH1_TC) }, - - /* - * This is the PCI device has an alternate address on some - * processors like Core i7 860 - */ - { PCI_DESCR( 0, 0, PCI_DEVICE_ID_INTEL_LYNNFIELD_NONCORE) }, }; -struct pci_id_descr pci_dev_descr_i7core_westmere[] = { +static const struct pci_id_descr pci_dev_descr_i7core_westmere[] = { /* Memory controller */ { PCI_DESCR(3, 0, PCI_DEVICE_ID_INTEL_LYNNFIELD_MCR_REV2) }, { PCI_DESCR(3, 1, PCI_DEVICE_ID_INTEL_LYNNFIELD_MC_TAD_REV2) }, @@ -356,17 +346,14 @@ struct pci_id_descr pci_dev_descr_i7core_westmere[] = { { PCI_DESCR(6, 1, PCI_DEVICE_ID_INTEL_LYNNFIELD_MC_CH2_ADDR_REV2) }, { PCI_DESCR(6, 2, PCI_DEVICE_ID_INTEL_LYNNFIELD_MC_CH2_RANK_REV2) }, { PCI_DESCR(6, 3, PCI_DEVICE_ID_INTEL_LYNNFIELD_MC_CH2_TC_REV2) }, - - /* Generic Non-core registers */ - { PCI_DESCR(0, 0, PCI_DEVICE_ID_INTEL_LYNNFIELD_NONCORE_REV2) }, - }; -#define PCI_ID_TABLE_ENTRY(A) { A, ARRAY_SIZE(A) } -struct pci_id_table pci_dev_table[] = { +#define PCI_ID_TABLE_ENTRY(A) { .descr=A, .n_devs = ARRAY_SIZE(A) } +static const struct pci_id_table pci_dev_table[] = { PCI_ID_TABLE_ENTRY(pci_dev_descr_i7core_nehalem), PCI_ID_TABLE_ENTRY(pci_dev_descr_lynnfield), PCI_ID_TABLE_ENTRY(pci_dev_descr_i7core_westmere), + {0,} /* 0 terminated list. */ }; /* @@ -378,8 +365,6 @@ static const struct pci_device_id i7core_pci_tbl[] __devinitdata = { {0,} /* 0 terminated list. */ }; -static struct edac_pci_ctl_info *i7core_pci; - /**************************************************************************** Anciliary status routines ****************************************************************************/ @@ -442,6 +427,36 @@ static struct i7core_dev *get_i7core_dev(u8 socket) return NULL; } +static struct i7core_dev *alloc_i7core_dev(u8 socket, + const struct pci_id_table *table) +{ + struct i7core_dev *i7core_dev; + + i7core_dev = kzalloc(sizeof(*i7core_dev), GFP_KERNEL); + if (!i7core_dev) + return NULL; + + i7core_dev->pdev = kzalloc(sizeof(*i7core_dev->pdev) * table->n_devs, + GFP_KERNEL); + if (!i7core_dev->pdev) { + kfree(i7core_dev); + return NULL; + } + + i7core_dev->socket = socket; + i7core_dev->n_devs = table->n_devs; + list_add_tail(&i7core_dev->list, &i7core_edac_list); + + return i7core_dev; +} + +static void free_i7core_dev(struct i7core_dev *i7core_dev) +{ + list_del(&i7core_dev->list); + kfree(i7core_dev->pdev); + kfree(i7core_dev); +} + /**************************************************************************** Memory check routines ****************************************************************************/ @@ -484,7 +499,7 @@ static struct pci_dev *get_pdev_slot_func(u8 socket, unsigned slot, * to add a fake description for csrows. * So, this driver is attributing one DIMM memory for one csrow. */ -static int i7core_get_active_channels(u8 socket, unsigned *channels, +static int i7core_get_active_channels(const u8 socket, unsigned *channels, unsigned *csrows) { struct pci_dev *pdev = NULL; @@ -545,12 +560,13 @@ static int i7core_get_active_channels(u8 socket, unsigned *channels, return 0; } -static int get_dimm_config(struct mem_ctl_info *mci, int *csrow) +static int get_dimm_config(const struct mem_ctl_info *mci) { struct i7core_pvt *pvt = mci->pvt_info; struct csrow_info *csr; struct pci_dev *pdev; int i, j; + int csrow = 0; unsigned long last_page = 0; enum edac_type mode; enum mem_type mtype; @@ -664,13 +680,9 @@ static int get_dimm_config(struct mem_ctl_info *mci, int *csrow) RANKOFFSET(dimm_dod[j]), banks, ranks, rows, cols); -#if PAGE_SHIFT > 20 - npages = size >> (PAGE_SHIFT - 20); -#else - npages = size << (20 - PAGE_SHIFT); -#endif + npages = MiB_TO_PAGES(size); - csr = &mci->csrows[*csrow]; + csr = &mci->csrows[csrow]; csr->first_page = last_page + 1; last_page += npages; csr->last_page = last_page; @@ -678,13 +690,13 @@ static int get_dimm_config(struct mem_ctl_info *mci, int *csrow) csr->page_mask = 0; csr->grain = 8; - csr->csrow_idx = *csrow; + csr->csrow_idx = csrow; csr->nr_channels = 1; csr->channels[0].chan_idx = i; csr->channels[0].ce_count = 0; - pvt->csrow_map[i][j] = *csrow; + pvt->csrow_map[i][j] = csrow; switch (banks) { case 4: @@ -703,7 +715,7 @@ static int get_dimm_config(struct mem_ctl_info *mci, int *csrow) csr->edac_mode = mode; csr->mtype = mtype; - (*csrow)++; + csrow++; } pci_read_config_dword(pdev, MC_SAG_CH_0, &value[0]); @@ -736,7 +748,7 @@ static int get_dimm_config(struct mem_ctl_info *mci, int *csrow) we're disabling error injection on all write calls to the sysfs nodes that controls the error code injection. */ -static int disable_inject(struct mem_ctl_info *mci) +static int disable_inject(const struct mem_ctl_info *mci) { struct i7core_pvt *pvt = mci->pvt_info; @@ -921,7 +933,7 @@ DECLARE_ADDR_MATCH(bank, 32); DECLARE_ADDR_MATCH(page, 0x10000); DECLARE_ADDR_MATCH(col, 0x4000); -static int write_and_test(struct pci_dev *dev, int where, u32 val) +static int write_and_test(struct pci_dev *dev, const int where, const u32 val) { u32 read; int count; @@ -1120,35 +1132,34 @@ DECLARE_COUNTER(2); * Sysfs struct */ - -static struct mcidev_sysfs_attribute i7core_addrmatch_attrs[] = { +static const struct mcidev_sysfs_attribute i7core_addrmatch_attrs[] = { ATTR_ADDR_MATCH(channel), ATTR_ADDR_MATCH(dimm), ATTR_ADDR_MATCH(rank), ATTR_ADDR_MATCH(bank), ATTR_ADDR_MATCH(page), ATTR_ADDR_MATCH(col), - { .attr = { .name = NULL } } + { } /* End of list */ }; -static struct mcidev_sysfs_group i7core_inject_addrmatch = { +static const struct mcidev_sysfs_group i7core_inject_addrmatch = { .name = "inject_addrmatch", .mcidev_attr = i7core_addrmatch_attrs, }; -static struct mcidev_sysfs_attribute i7core_udimm_counters_attrs[] = { +static const struct mcidev_sysfs_attribute i7core_udimm_counters_attrs[] = { ATTR_COUNTER(0), ATTR_COUNTER(1), ATTR_COUNTER(2), { .attr = { .name = NULL } } }; -static struct mcidev_sysfs_group i7core_udimm_counters = { +static const struct mcidev_sysfs_group i7core_udimm_counters = { .name = "all_channel_counts", .mcidev_attr = i7core_udimm_counters_attrs, }; -static struct mcidev_sysfs_attribute i7core_sysfs_attrs[] = { +static const struct mcidev_sysfs_attribute i7core_sysfs_rdimm_attrs[] = { { .attr = { .name = "inject_section", @@ -1180,8 +1191,44 @@ static struct mcidev_sysfs_attribute i7core_sysfs_attrs[] = { .show = i7core_inject_enable_show, .store = i7core_inject_enable_store, }, - { .attr = { .name = NULL } }, /* Reserved for udimm counters */ - { .attr = { .name = NULL } } + { } /* End of list */ +}; + +static const struct mcidev_sysfs_attribute i7core_sysfs_udimm_attrs[] = { + { + .attr = { + .name = "inject_section", + .mode = (S_IRUGO | S_IWUSR) + }, + .show = i7core_inject_section_show, + .store = i7core_inject_section_store, + }, { + .attr = { + .name = "inject_type", + .mode = (S_IRUGO | S_IWUSR) + }, + .show = i7core_inject_type_show, + .store = i7core_inject_type_store, + }, { + .attr = { + .name = "inject_eccmask", + .mode = (S_IRUGO | S_IWUSR) + }, + .show = i7core_inject_eccmask_show, + .store = i7core_inject_eccmask_store, + }, { + .grp = &i7core_inject_addrmatch, + }, { + .attr = { + .name = "inject_enable", + .mode = (S_IRUGO | S_IWUSR) + }, + .show = i7core_inject_enable_show, + .store = i7core_inject_enable_store, + }, { + .grp = &i7core_udimm_counters, + }, + { } /* End of list */ }; /**************************************************************************** @@ -1189,7 +1236,7 @@ static struct mcidev_sysfs_attribute i7core_sysfs_attrs[] = { ****************************************************************************/ /* - * i7core_put_devices 'put' all the devices that we have + * i7core_put_all_devices 'put' all the devices that we have * reserved via 'get' */ static void i7core_put_devices(struct i7core_dev *i7core_dev) @@ -1206,23 +1253,23 @@ static void i7core_put_devices(struct i7core_dev *i7core_dev) PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn)); pci_dev_put(pdev); } - kfree(i7core_dev->pdev); - list_del(&i7core_dev->list); - kfree(i7core_dev); } static void i7core_put_all_devices(void) { struct i7core_dev *i7core_dev, *tmp; - list_for_each_entry_safe(i7core_dev, tmp, &i7core_edac_list, list) + list_for_each_entry_safe(i7core_dev, tmp, &i7core_edac_list, list) { i7core_put_devices(i7core_dev); + free_i7core_dev(i7core_dev); + } } -static void __init i7core_xeon_pci_fixup(struct pci_id_table *table) +static void __init i7core_xeon_pci_fixup(const struct pci_id_table *table) { struct pci_dev *pdev = NULL; int i; + /* * On Xeon 55xx, the Intel Quckpath Arch Generic Non-core pci buses * aren't announced by acpi. So, we need to use a legacy scan probing @@ -1257,16 +1304,18 @@ static unsigned i7core_pci_lastbus(void) } /* - * i7core_get_devices Find and perform 'get' operation on the MCH's + * i7core_get_all_devices Find and perform 'get' operation on the MCH's * device/functions we want to reference for this driver * * Need to 'get' device 16 func 1 and func 2 */ -int i7core_get_onedevice(struct pci_dev **prev, int devno, - struct pci_id_descr *dev_descr, unsigned n_devs, - unsigned last_bus) +static int i7core_get_onedevice(struct pci_dev **prev, + const struct pci_id_table *table, + const unsigned devno, + const unsigned last_bus) { struct i7core_dev *i7core_dev; + const struct pci_id_descr *dev_descr = &table->descr[devno]; struct pci_dev *pdev = NULL; u8 bus = 0; @@ -1275,20 +1324,6 @@ int i7core_get_onedevice(struct pci_dev **prev, int devno, pdev = pci_get_device(PCI_VENDOR_ID_INTEL, dev_descr->dev_id, *prev); - /* - * On Xeon 55xx, the Intel Quckpath Arch Generic Non-core regs - * is at addr 8086:2c40, instead of 8086:2c41. So, we need - * to probe for the alternate address in case of failure - */ - if (dev_descr->dev_id == PCI_DEVICE_ID_INTEL_I7_NONCORE && !pdev) - pdev = pci_get_device(PCI_VENDOR_ID_INTEL, - PCI_DEVICE_ID_INTEL_I7_NONCORE_ALT, *prev); - - if (dev_descr->dev_id == PCI_DEVICE_ID_INTEL_LYNNFIELD_NONCORE && !pdev) - pdev = pci_get_device(PCI_VENDOR_ID_INTEL, - PCI_DEVICE_ID_INTEL_LYNNFIELD_NONCORE_ALT, - *prev); - if (!pdev) { if (*prev) { *prev = pdev; @@ -1315,18 +1350,11 @@ int i7core_get_onedevice(struct pci_dev **prev, int devno, i7core_dev = get_i7core_dev(socket); if (!i7core_dev) { - i7core_dev = kzalloc(sizeof(*i7core_dev), GFP_KERNEL); - if (!i7core_dev) - return -ENOMEM; - i7core_dev->pdev = kzalloc(sizeof(*i7core_dev->pdev) * n_devs, - GFP_KERNEL); - if (!i7core_dev->pdev) { - kfree(i7core_dev); + i7core_dev = alloc_i7core_dev(socket, table); + if (!i7core_dev) { + pci_dev_put(pdev); return -ENOMEM; } - i7core_dev->socket = socket; - i7core_dev->n_devs = n_devs; - list_add_tail(&i7core_dev->list, &i7core_edac_list); } if (i7core_dev->pdev[devno]) { @@ -1368,27 +1396,31 @@ int i7core_get_onedevice(struct pci_dev **prev, int devno, dev_descr->func, PCI_VENDOR_ID_INTEL, dev_descr->dev_id); + /* + * As stated on drivers/pci/search.c, the reference count for + * @from is always decremented if it is not %NULL. So, as we need + * to get all devices up to null, we need to do a get for the device + */ + pci_dev_get(pdev); + *prev = pdev; return 0; } -static int i7core_get_devices(struct pci_id_table *table) +static int i7core_get_all_devices(void) { int i, rc, last_bus; struct pci_dev *pdev = NULL; - struct pci_id_descr *dev_descr; + const struct pci_id_table *table = pci_dev_table; last_bus = i7core_pci_lastbus(); while (table && table->descr) { - dev_descr = table->descr; for (i = 0; i < table->n_devs; i++) { pdev = NULL; do { - rc = i7core_get_onedevice(&pdev, i, - &dev_descr[i], - table->n_devs, + rc = i7core_get_onedevice(&pdev, table, i, last_bus); if (rc < 0) { if (i == 0) { @@ -1404,7 +1436,6 @@ static int i7core_get_devices(struct pci_id_table *table) } return 0; - return 0; } static int mci_bind_devs(struct mem_ctl_info *mci, @@ -1414,10 +1445,6 @@ static int mci_bind_devs(struct mem_ctl_info *mci, struct pci_dev *pdev; int i, func, slot; - /* Associates i7core_dev and mci for future usage */ - pvt->i7core_dev = i7core_dev; - i7core_dev->mci = mci; - pvt->is_registered = 0; for (i = 0; i < i7core_dev->n_devs; i++) { pdev = i7core_dev->pdev[i]; @@ -1448,15 +1475,6 @@ static int mci_bind_devs(struct mem_ctl_info *mci, pvt->is_registered = 1; } - /* - * Add extra nodes to count errors on udimm - * For registered memory, this is not needed, since the counters - * are already displayed at the standard locations - */ - if (!pvt->is_registered) - i7core_sysfs_attrs[ARRAY_SIZE(i7core_sysfs_attrs)-2].grp = - &i7core_udimm_counters; - return 0; error: @@ -1470,7 +1488,9 @@ error: Error check routines ****************************************************************************/ static void i7core_rdimm_update_csrow(struct mem_ctl_info *mci, - int chan, int dimm, int add) + const int chan, + const int dimm, + const int add) { char *msg; struct i7core_pvt *pvt = mci->pvt_info; @@ -1487,7 +1507,10 @@ static void i7core_rdimm_update_csrow(struct mem_ctl_info *mci, } static void i7core_rdimm_update_ce_count(struct mem_ctl_info *mci, - int chan, int new0, int new1, int new2) + const int chan, + const int new0, + const int new1, + const int new2) { struct i7core_pvt *pvt = mci->pvt_info; int add0 = 0, add1 = 0, add2 = 0; @@ -1641,7 +1664,7 @@ static void i7core_udimm_check_mc_ecc_err(struct mem_ctl_info *mci) * fields */ static void i7core_mce_output_error(struct mem_ctl_info *mci, - struct mce *m) + const struct mce *m) { struct i7core_pvt *pvt = mci->pvt_info; char *type, *optype, *err, *msg; @@ -1845,28 +1868,85 @@ static int i7core_mce_check_error(void *priv, struct mce *mce) return 1; } -static int i7core_register_mci(struct i7core_dev *i7core_dev, - int num_channels, int num_csrows) +static void i7core_pci_ctl_create(struct i7core_pvt *pvt) +{ + pvt->i7core_pci = edac_pci_create_generic_ctl( + &pvt->i7core_dev->pdev[0]->dev, + EDAC_MOD_STR); + if (unlikely(!pvt->i7core_pci)) + pr_warn("Unable to setup PCI error report via EDAC\n"); +} + +static void i7core_pci_ctl_release(struct i7core_pvt *pvt) +{ + if (likely(pvt->i7core_pci)) + edac_pci_release_generic_ctl(pvt->i7core_pci); + else + i7core_printk(KERN_ERR, + "Couldn't find mem_ctl_info for socket %d\n", + pvt->i7core_dev->socket); + pvt->i7core_pci = NULL; +} + +static void i7core_unregister_mci(struct i7core_dev *i7core_dev) +{ + struct mem_ctl_info *mci = i7core_dev->mci; + struct i7core_pvt *pvt; + + if (unlikely(!mci || !mci->pvt_info)) { + debugf0("MC: " __FILE__ ": %s(): dev = %p\n", + __func__, &i7core_dev->pdev[0]->dev); + + i7core_printk(KERN_ERR, "Couldn't find mci handler\n"); + return; + } + + pvt = mci->pvt_info; + + debugf0("MC: " __FILE__ ": %s(): mci = %p, dev = %p\n", + __func__, mci, &i7core_dev->pdev[0]->dev); + + /* Disable MCE NMI handler */ + edac_mce_unregister(&pvt->edac_mce); + + /* Disable EDAC polling */ + i7core_pci_ctl_release(pvt); + + /* Remove MC sysfs nodes */ + edac_mc_del_mc(mci->dev); + + debugf1("%s: free mci struct\n", mci->ctl_name); + kfree(mci->ctl_name); + edac_mc_free(mci); + i7core_dev->mci = NULL; +} + +static int i7core_register_mci(struct i7core_dev *i7core_dev) { struct mem_ctl_info *mci; struct i7core_pvt *pvt; - int csrow = 0; - int rc; + int rc, channels, csrows; + + /* Check the number of active and not disabled channels */ + rc = i7core_get_active_channels(i7core_dev->socket, &channels, &csrows); + if (unlikely(rc < 0)) + return rc; /* allocate a new MC control structure */ - mci = edac_mc_alloc(sizeof(*pvt), num_csrows, num_channels, - i7core_dev->socket); + mci = edac_mc_alloc(sizeof(*pvt), csrows, channels, i7core_dev->socket); if (unlikely(!mci)) return -ENOMEM; - debugf0("MC: " __FILE__ ": %s(): mci = %p\n", __func__, mci); - - /* record ptr to the generic device */ - mci->dev = &i7core_dev->pdev[0]->dev; + debugf0("MC: " __FILE__ ": %s(): mci = %p, dev = %p\n", + __func__, mci, &i7core_dev->pdev[0]->dev); pvt = mci->pvt_info; memset(pvt, 0, sizeof(*pvt)); + /* Associates i7core_dev and mci for future usage */ + pvt->i7core_dev = i7core_dev; + i7core_dev->mci = mci; + /* * FIXME: how to handle RDDR3 at MCI level? It is possible to have * Mixed RDDR3/UDDR3 with Nehalem, provided that they are on different @@ -1881,17 +1961,23 @@ static int i7core_register_mci(struct i7core_dev *i7core_dev, i7core_dev->socket); mci->dev_name = pci_name(i7core_dev->pdev[0]); mci->ctl_page_to_phys = NULL; - mci->mc_driver_sysfs_attributes = i7core_sysfs_attrs; - /* Set the function pointer to an actual operation function */ - mci->edac_check = i7core_check_error; /* Store pci devices at mci for faster access */ rc = mci_bind_devs(mci, i7core_dev); if (unlikely(rc < 0)) - goto fail; + goto fail0; + + if (pvt->is_registered) + mci->mc_driver_sysfs_attributes = i7core_sysfs_rdimm_attrs; + else + mci->mc_driver_sysfs_attributes = i7core_sysfs_udimm_attrs; /* Get dimm basic config */ - get_dimm_config(mci, &csrow); + get_dimm_config(mci); + /* record ptr to the generic device */ + mci->dev = &i7core_dev->pdev[0]->dev; + /* Set the function pointer to an actual operation function */ + mci->edac_check = i7core_check_error; /* add this new MC control structure to EDAC's list of MCs */ if (unlikely(edac_mc_add_mc(mci))) { @@ -1902,19 +1988,7 @@ static int i7core_register_mci(struct i7core_dev *i7core_dev, */ rc = -EINVAL; - goto fail; - } - - /* allocating generic PCI control info */ - i7core_pci = edac_pci_create_generic_ctl(&i7core_dev->pdev[0]->dev, - EDAC_MOD_STR); - if (unlikely(!i7core_pci)) { - printk(KERN_WARNING - "%s(): Unable to create PCI control\n", - __func__); - printk(KERN_WARNING - "%s(): PCI error report via EDAC not setup\n", - __func__); + goto fail0; } /* Default error mask is any memory */ @@ -1925,19 +1999,28 @@ static int i7core_register_mci(struct i7core_dev *i7core_dev, pvt->inject.page = -1; pvt->inject.col = -1; + /* allocating generic PCI control info */ + i7core_pci_ctl_create(pvt); + /* Registers on edac_mce in order to receive memory errors */ pvt->edac_mce.priv = mci; pvt->edac_mce.check_error = i7core_mce_check_error; - rc = edac_mce_register(&pvt->edac_mce); if (unlikely(rc < 0)) { debugf0("MC: " __FILE__ ": %s(): failed edac_mce_register()\n", __func__); + goto fail1; } -fail: - if (rc < 0) - edac_mc_free(mci); + return 0; + +fail1: + i7core_pci_ctl_release(pvt); + edac_mc_del_mc(mci->dev); +fail0: + kfree(mci->ctl_name); + edac_mc_free(mci); + i7core_dev->mci = NULL; return rc; } @@ -1949,8 +2032,6 @@ fail: * < 0 for error code */ -static int probed = 0; - static int __devinit i7core_probe(struct pci_dev *pdev, const struct pci_device_id *id) { @@ -1965,25 +2046,16 @@ static int __devinit i7core_probe(struct pci_dev *pdev, */ if (unlikely(probed >= 1)) { mutex_unlock(&i7core_edac_lock); - return -EINVAL; + return -ENODEV; } probed++; - rc = i7core_get_devices(pci_dev_table); + rc = i7core_get_all_devices(); if (unlikely(rc < 0)) goto fail0; list_for_each_entry(i7core_dev, &i7core_edac_list, list) { - int channels; - int csrows; - - /* Check the number of active and not disabled channels */ - rc = i7core_get_active_channels(i7core_dev->socket, - &channels, &csrows); - if (unlikely(rc < 0)) - goto fail1; - - rc = i7core_register_mci(i7core_dev, channels, csrows); + rc = i7core_register_mci(i7core_dev); if (unlikely(rc < 0)) goto fail1; } @@ -1994,6 +2066,9 @@ static int __devinit i7core_probe(struct pci_dev *pdev, return 0; fail1: + list_for_each_entry(i7core_dev, &i7core_edac_list, list) + i7core_unregister_mci(i7core_dev); + i7core_put_all_devices(); fail0: mutex_unlock(&i7core_edac_lock); @@ -2006,14 +2081,10 @@ fail0: */ static void __devexit i7core_remove(struct pci_dev *pdev) { - struct mem_ctl_info *mci; - struct i7core_dev *i7core_dev, *tmp; + struct i7core_dev *i7core_dev; debugf0(__FILE__ ": %s()\n", __func__); - if (i7core_pci) - edac_pci_release_generic_ctl(i7core_pci); - /* * we have a trouble here: pdev value for removal will be wrong, since * it will point to the X58 register used to detect that the machine @@ -2023,22 +2094,18 @@ static void __devexit i7core_remove(struct pci_dev *pdev) */ mutex_lock(&i7core_edac_lock); - list_for_each_entry_safe(i7core_dev, tmp, &i7core_edac_list, list) { - mci = edac_mc_del_mc(&i7core_dev->pdev[0]->dev); - if (mci) { - struct i7core_pvt *pvt = mci->pvt_info; - - i7core_dev = pvt->i7core_dev; - edac_mce_unregister(&pvt->edac_mce); - kfree(mci->ctl_name); - edac_mc_free(mci); - i7core_put_devices(i7core_dev); - } else { - i7core_printk(KERN_ERR, - "Couldn't find mci for socket %d\n", - i7core_dev->socket); - } + + if (unlikely(!probed)) { + mutex_unlock(&i7core_edac_lock); + return; } + + list_for_each_entry(i7core_dev, &i7core_edac_list, list) + i7core_unregister_mci(i7core_dev); + + /* Release PCI resources */ + i7core_put_all_devices(); + probed--; mutex_unlock(&i7core_edac_lock); @@ -2070,7 +2137,8 @@ static int __init i7core_init(void) /* Ensure that the OPSTATE is set correctly for POLL or NMI */ opstate_init(); - i7core_xeon_pci_fixup(pci_dev_table); + if (use_pci_fixup) + i7core_xeon_pci_fixup(pci_dev_table); pci_rc = pci_register_driver(&i7core_driver); diff --git a/drivers/edac/i82443bxgx_edac.c b/drivers/edac/i82443bxgx_edac.c index a2fa1feed72..678405ab04e 100644 --- a/drivers/edac/i82443bxgx_edac.c +++ b/drivers/edac/i82443bxgx_edac.c @@ -12,7 +12,7 @@ * 440GX fix by Jason Uhlenkott <juhlenko@akamai.com>. * * Written with reference to 82443BX Host Bridge Datasheet: - * http://www.intel.com/design/chipsets/440/documentation.htm + * http://download.intel.com/design/chipsets/datashts/29063301.pdf * references to this document given in []. * * This module doesn't support the 440LX, but it may be possible to diff --git a/drivers/edac/mce_amd.c b/drivers/edac/mce_amd.c new file mode 100644 index 00000000000..c0181093b49 --- /dev/null +++ b/drivers/edac/mce_amd.c @@ -0,0 +1,680 @@ +#include <linux/module.h> +#include <linux/slab.h> + +#include "mce_amd.h" + +static struct amd_decoder_ops *fam_ops; + +static u8 nb_err_cpumask = 0xf; + +static bool report_gart_errors; +static void (*nb_bus_decoder)(int node_id, struct mce *m, u32 nbcfg); + +void amd_report_gart_errors(bool v) +{ + report_gart_errors = v; +} +EXPORT_SYMBOL_GPL(amd_report_gart_errors); + +void amd_register_ecc_decoder(void (*f)(int, struct mce *, u32)) +{ + nb_bus_decoder = f; +} +EXPORT_SYMBOL_GPL(amd_register_ecc_decoder); + +void amd_unregister_ecc_decoder(void (*f)(int, struct mce *, u32)) +{ + if (nb_bus_decoder) { + WARN_ON(nb_bus_decoder != f); + + nb_bus_decoder = NULL; + } +} +EXPORT_SYMBOL_GPL(amd_unregister_ecc_decoder); + +/* + * string representation for the different MCA reported error types, see F3x48 + * or MSR0000_0411. + */ + +/* transaction type */ +const char *tt_msgs[] = { "INSN", "DATA", "GEN", "RESV" }; +EXPORT_SYMBOL_GPL(tt_msgs); + +/* cache level */ +const char *ll_msgs[] = { "RESV", "L1", "L2", "L3/GEN" }; +EXPORT_SYMBOL_GPL(ll_msgs); + +/* memory transaction type */ +const char *rrrr_msgs[] = { + "GEN", "RD", "WR", "DRD", "DWR", "IRD", "PRF", "EV", "SNP" +}; +EXPORT_SYMBOL_GPL(rrrr_msgs); + +/* participating processor */ +const char *pp_msgs[] = { "SRC", "RES", "OBS", "GEN" }; +EXPORT_SYMBOL_GPL(pp_msgs); + +/* request timeout */ +const char *to_msgs[] = { "no timeout", "timed out" }; +EXPORT_SYMBOL_GPL(to_msgs); + +/* memory or i/o */ +const char *ii_msgs[] = { "MEM", "RESV", "IO", "GEN" }; +EXPORT_SYMBOL_GPL(ii_msgs); + +static const char *f10h_nb_mce_desc[] = { + "HT link data error", + "Protocol error (link, L3, probe filter, etc.)", + "Parity error in NB-internal arrays", + "Link Retry due to IO link transmission error", + "L3 ECC data cache error", + "ECC error in L3 cache tag", + "L3 LRU parity bits error", + "ECC Error in the Probe Filter directory" +}; + +static bool f12h_dc_mce(u16 ec) +{ + bool ret = false; + + if (MEM_ERROR(ec)) { + u8 ll = ec & 0x3; + ret = true; + + if (ll == LL_L2) + pr_cont("during L1 linefill from L2.\n"); + else if (ll == LL_L1) + pr_cont("Data/Tag %s error.\n", RRRR_MSG(ec)); + else + ret = false; + } + return ret; +} + +static bool f10h_dc_mce(u16 ec) +{ + u8 r4 = (ec >> 4) & 0xf; + u8 ll = ec & 0x3; + + if (r4 == R4_GEN && ll == LL_L1) { + pr_cont("during data scrub.\n"); + return true; + } + return f12h_dc_mce(ec); +} + +static bool k8_dc_mce(u16 ec) +{ + if (BUS_ERROR(ec)) { + pr_cont("during system linefill.\n"); + return true; + } + + return f10h_dc_mce(ec); +} + +static bool f14h_dc_mce(u16 ec) +{ + u8 r4 = (ec >> 4) & 0xf; + u8 ll = ec & 0x3; + u8 tt = (ec >> 2) & 0x3; + u8 ii = tt; + bool ret = true; + + if (MEM_ERROR(ec)) { + + if (tt != TT_DATA || ll != LL_L1) + return false; + + switch (r4) { + case R4_DRD: + case R4_DWR: + pr_cont("Data/Tag parity error due to %s.\n", + (r4 == R4_DRD ? "load/hw prf" : "store")); + break; + case R4_EVICT: + pr_cont("Copyback parity error on a tag miss.\n"); + break; + case R4_SNOOP: + pr_cont("Tag parity error during snoop.\n"); + break; + default: + ret = false; + } + } else if (BUS_ERROR(ec)) { + + if ((ii != II_MEM && ii != II_IO) || ll != LL_LG) + return false; + + pr_cont("System read data error on a "); + + switch (r4) { + case R4_RD: + pr_cont("TLB reload.\n"); + break; + case R4_DWR: + pr_cont("store.\n"); + break; + case R4_DRD: + pr_cont("load.\n"); + break; + default: + ret = false; + } + } else { + ret = false; + } + + return ret; +} + +static void amd_decode_dc_mce(struct mce *m) +{ + u16 ec = m->status & 0xffff; + u8 xec = (m->status >> 16) & 0xf; + + pr_emerg(HW_ERR "Data Cache Error: "); + + /* TLB error signatures are the same across families */ + if (TLB_ERROR(ec)) { + u8 tt = (ec >> 2) & 0x3; + + if (tt == TT_DATA) { + pr_cont("%s TLB %s.\n", LL_MSG(ec), + (xec ? "multimatch" : "parity error")); + return; + } + else + goto wrong_dc_mce; + } + + if (!fam_ops->dc_mce(ec)) + goto wrong_dc_mce; + + return; + +wrong_dc_mce: + pr_emerg(HW_ERR "Corrupted DC MCE info?\n"); +} + +static bool k8_ic_mce(u16 ec) +{ + u8 ll = ec & 0x3; + u8 r4 = (ec >> 4) & 0xf; + bool ret = true; + + if (!MEM_ERROR(ec)) + return false; + + if (ll == 0x2) + pr_cont("during a linefill from L2.\n"); + else if (ll == 0x1) { + switch (r4) { + case R4_IRD: + pr_cont("Parity error during data load.\n"); + break; + + case R4_EVICT: + pr_cont("Copyback Parity/Victim error.\n"); + break; + + case R4_SNOOP: + pr_cont("Tag Snoop error.\n"); + break; + + default: + ret = false; + break; + } + } else + ret = false; + + return ret; +} + +static bool f14h_ic_mce(u16 ec) +{ + u8 ll = ec & 0x3; + u8 tt = (ec >> 2) & 0x3; + u8 r4 = (ec >> 4) & 0xf; + bool ret = true; + + if (MEM_ERROR(ec)) { + if (tt != 0 || ll != 1) + ret = false; + + if (r4 == R4_IRD) + pr_cont("Data/tag array parity error for a tag hit.\n"); + else if (r4 == R4_SNOOP) + pr_cont("Tag error during snoop/victimization.\n"); + else + ret = false; + } + return ret; +} + +static void amd_decode_ic_mce(struct mce *m) +{ + u16 ec = m->status & 0xffff; + u8 xec = (m->status >> 16) & 0xf; + + pr_emerg(HW_ERR "Instruction Cache Error: "); + + if (TLB_ERROR(ec)) + pr_cont("%s TLB %s.\n", LL_MSG(ec), + (xec ? "multimatch" : "parity error")); + else if (BUS_ERROR(ec)) { + bool k8 = (boot_cpu_data.x86 == 0xf && (m->status & BIT_64(58))); + + pr_cont("during %s.\n", (k8 ? "system linefill" : "NB data read")); + } else if (fam_ops->ic_mce(ec)) + ; + else + pr_emerg(HW_ERR "Corrupted IC MCE info?\n"); +} + +static void amd_decode_bu_mce(struct mce *m) +{ + u32 ec = m->status & 0xffff; + u32 xec = (m->status >> 16) & 0xf; + + pr_emerg(HW_ERR "Bus Unit Error"); + + if (xec == 0x1) + pr_cont(" in the write data buffers.\n"); + else if (xec == 0x3) + pr_cont(" in the victim data buffers.\n"); + else if (xec == 0x2 && MEM_ERROR(ec)) + pr_cont(": %s error in the L2 cache tags.\n", RRRR_MSG(ec)); + else if (xec == 0x0) { + if (TLB_ERROR(ec)) + pr_cont(": %s error in a Page Descriptor Cache or " + "Guest TLB.\n", TT_MSG(ec)); + else if (BUS_ERROR(ec)) + pr_cont(": %s/ECC error in data read from NB: %s.\n", + RRRR_MSG(ec), PP_MSG(ec)); + else if (MEM_ERROR(ec)) { + u8 rrrr = (ec >> 4) & 0xf; + + if (rrrr >= 0x7) + pr_cont(": %s error during data copyback.\n", + RRRR_MSG(ec)); + else if (rrrr <= 0x1) + pr_cont(": %s parity/ECC error during data " + "access from L2.\n", RRRR_MSG(ec)); + else + goto wrong_bu_mce; + } else + goto wrong_bu_mce; + } else + goto wrong_bu_mce; + + return; + +wrong_bu_mce: + pr_emerg(HW_ERR "Corrupted BU MCE info?\n"); +} + +static void amd_decode_ls_mce(struct mce *m) +{ + u16 ec = m->status & 0xffff; + u8 xec = (m->status >> 16) & 0xf; + + if (boot_cpu_data.x86 == 0x14) { + pr_emerg("You shouldn't be seeing an LS MCE on this cpu family," + " please report on LKML.\n"); + return; + } + + pr_emerg(HW_ERR "Load Store Error"); + + if (xec == 0x0) { + u8 r4 = (ec >> 4) & 0xf; + + if (!BUS_ERROR(ec) || (r4 != R4_DRD && r4 != R4_DWR)) + goto wrong_ls_mce; + + pr_cont(" during %s.\n", RRRR_MSG(ec)); + } else + goto wrong_ls_mce; + + return; + +wrong_ls_mce: + pr_emerg(HW_ERR "Corrupted LS MCE info?\n"); +} + +static bool k8_nb_mce(u16 ec, u8 xec) +{ + bool ret = true; + + switch (xec) { + case 0x1: + pr_cont("CRC error detected on HT link.\n"); + break; + + case 0x5: + pr_cont("Invalid GART PTE entry during GART table walk.\n"); + break; + + case 0x6: + pr_cont("Unsupported atomic RMW received from an IO link.\n"); + break; + + case 0x0: + case 0x8: + if (boot_cpu_data.x86 == 0x11) + return false; + + pr_cont("DRAM ECC error detected on the NB.\n"); + break; + + case 0xd: + pr_cont("Parity error on the DRAM addr/ctl signals.\n"); + break; + + default: + ret = false; + break; + } + + return ret; +} + +static bool f10h_nb_mce(u16 ec, u8 xec) +{ + bool ret = true; + u8 offset = 0; + + if (k8_nb_mce(ec, xec)) + return true; + + switch(xec) { + case 0xa ... 0xc: + offset = 10; + break; + + case 0xe: + offset = 11; + break; + + case 0xf: + if (TLB_ERROR(ec)) + pr_cont("GART Table Walk data error.\n"); + else if (BUS_ERROR(ec)) + pr_cont("DMA Exclusion Vector Table Walk error.\n"); + else + ret = false; + + goto out; + break; + + case 0x1c ... 0x1f: + offset = 24; + break; + + default: + ret = false; + + goto out; + break; + } + + pr_cont("%s.\n", f10h_nb_mce_desc[xec - offset]); + +out: + return ret; +} + +static bool nb_noop_mce(u16 ec, u8 xec) +{ + return false; +} + +void amd_decode_nb_mce(int node_id, struct mce *m, u32 nbcfg) +{ + u8 xec = (m->status >> 16) & 0x1f; + u16 ec = m->status & 0xffff; + u32 nbsh = (u32)(m->status >> 32); + + pr_emerg(HW_ERR "Northbridge Error, node %d: ", node_id); + + /* + * F10h, revD can disable ErrCpu[3:0] so check that first and also the + * value encoding has changed so interpret those differently + */ + if ((boot_cpu_data.x86 == 0x10) && + (boot_cpu_data.x86_model > 7)) { + if (nbsh & K8_NBSH_ERR_CPU_VAL) + pr_cont(", core: %u", (u8)(nbsh & nb_err_cpumask)); + } else { + u8 assoc_cpus = nbsh & nb_err_cpumask; + + if (assoc_cpus > 0) + pr_cont(", core: %d", fls(assoc_cpus) - 1); + } + + switch (xec) { + case 0x2: + pr_cont("Sync error (sync packets on HT link detected).\n"); + return; + + case 0x3: + pr_cont("HT Master abort.\n"); + return; + + case 0x4: + pr_cont("HT Target abort.\n"); + return; + + case 0x7: + pr_cont("NB Watchdog timeout.\n"); + return; + + case 0x9: + pr_cont("SVM DMA Exclusion Vector error.\n"); + return; + + default: + break; + } + + if (!fam_ops->nb_mce(ec, xec)) + goto wrong_nb_mce; + + if (boot_cpu_data.x86 == 0xf || boot_cpu_data.x86 == 0x10) + if ((xec == 0x8 || xec == 0x0) && nb_bus_decoder) + nb_bus_decoder(node_id, m, nbcfg); + + return; + +wrong_nb_mce: + pr_emerg(HW_ERR "Corrupted NB MCE info?\n"); +} +EXPORT_SYMBOL_GPL(amd_decode_nb_mce); + +static void amd_decode_fr_mce(struct mce *m) +{ + if (boot_cpu_data.x86 == 0xf || + boot_cpu_data.x86 == 0x11) + goto wrong_fr_mce; + + /* we have only one error signature so match all fields at once. */ + if ((m->status & 0xffff) == 0x0f0f) { + pr_emerg(HW_ERR "FR Error: CPU Watchdog timer expire.\n"); + return; + } + +wrong_fr_mce: + pr_emerg(HW_ERR "Corrupted FR MCE info?\n"); +} + +static inline void amd_decode_err_code(u16 ec) +{ + if (TLB_ERROR(ec)) { + pr_emerg(HW_ERR "Transaction: %s, Cache Level: %s\n", + TT_MSG(ec), LL_MSG(ec)); + } else if (MEM_ERROR(ec)) { + pr_emerg(HW_ERR "Transaction: %s, Type: %s, Cache Level: %s\n", + RRRR_MSG(ec), TT_MSG(ec), LL_MSG(ec)); + } else if (BUS_ERROR(ec)) { + pr_emerg(HW_ERR "Transaction: %s (%s), %s, Cache Level: %s, " + "Participating Processor: %s\n", + RRRR_MSG(ec), II_MSG(ec), TO_MSG(ec), LL_MSG(ec), + PP_MSG(ec)); + } else + pr_emerg(HW_ERR "Huh? Unknown MCE error 0x%x\n", ec); +} + +/* + * Filter out unwanted MCE signatures here. + */ +static bool amd_filter_mce(struct mce *m) +{ + u8 xec = (m->status >> 16) & 0x1f; + + /* + * NB GART TLB error reporting is disabled by default. + */ + if (m->bank == 4 && xec == 0x5 && !report_gart_errors) + return true; + + return false; +} + +int amd_decode_mce(struct notifier_block *nb, unsigned long val, void *data) +{ + struct mce *m = (struct mce *)data; + int node, ecc; + + if (amd_filter_mce(m)) + return NOTIFY_STOP; + + pr_emerg(HW_ERR "MC%d_STATUS: ", m->bank); + + pr_cont("%sorrected error, other errors lost: %s, " + "CPU context corrupt: %s", + ((m->status & MCI_STATUS_UC) ? "Unc" : "C"), + ((m->status & MCI_STATUS_OVER) ? "yes" : "no"), + ((m->status & MCI_STATUS_PCC) ? "yes" : "no")); + + /* do the two bits[14:13] together */ + ecc = (m->status >> 45) & 0x3; + if (ecc) + pr_cont(", %sECC Error", ((ecc == 2) ? "C" : "U")); + + pr_cont("\n"); + + switch (m->bank) { + case 0: + amd_decode_dc_mce(m); + break; + + case 1: + amd_decode_ic_mce(m); + break; + + case 2: + amd_decode_bu_mce(m); + break; + + case 3: + amd_decode_ls_mce(m); + break; + + case 4: + node = amd_get_nb_id(m->extcpu); + amd_decode_nb_mce(node, m, 0); + break; + + case 5: + amd_decode_fr_mce(m); + break; + + default: + break; + } + + amd_decode_err_code(m->status & 0xffff); + + return NOTIFY_STOP; +} +EXPORT_SYMBOL_GPL(amd_decode_mce); + +static struct notifier_block amd_mce_dec_nb = { + .notifier_call = amd_decode_mce, +}; + +static int __init mce_amd_init(void) +{ + if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD) + return 0; + + if ((boot_cpu_data.x86 < 0xf || boot_cpu_data.x86 > 0x12) && + (boot_cpu_data.x86 != 0x14 || boot_cpu_data.x86_model > 0xf)) + return 0; + + fam_ops = kzalloc(sizeof(struct amd_decoder_ops), GFP_KERNEL); + if (!fam_ops) + return -ENOMEM; + + switch (boot_cpu_data.x86) { + case 0xf: + fam_ops->dc_mce = k8_dc_mce; + fam_ops->ic_mce = k8_ic_mce; + fam_ops->nb_mce = k8_nb_mce; + break; + + case 0x10: + fam_ops->dc_mce = f10h_dc_mce; + fam_ops->ic_mce = k8_ic_mce; + fam_ops->nb_mce = f10h_nb_mce; + break; + + case 0x11: + fam_ops->dc_mce = k8_dc_mce; + fam_ops->ic_mce = k8_ic_mce; + fam_ops->nb_mce = f10h_nb_mce; + break; + + case 0x12: + fam_ops->dc_mce = f12h_dc_mce; + fam_ops->ic_mce = k8_ic_mce; + fam_ops->nb_mce = nb_noop_mce; + break; + + case 0x14: + nb_err_cpumask = 0x3; + fam_ops->dc_mce = f14h_dc_mce; + fam_ops->ic_mce = f14h_ic_mce; + fam_ops->nb_mce = nb_noop_mce; + break; + + default: + printk(KERN_WARNING "Huh? What family is that: %d?!\n", + boot_cpu_data.x86); + kfree(fam_ops); + return -EINVAL; + } + + pr_info("MCE: In-kernel MCE decoding enabled.\n"); + + atomic_notifier_chain_register(&x86_mce_decoder_chain, &amd_mce_dec_nb); + + return 0; +} +early_initcall(mce_amd_init); + +#ifdef MODULE +static void __exit mce_amd_exit(void) +{ + atomic_notifier_chain_unregister(&x86_mce_decoder_chain, &amd_mce_dec_nb); + kfree(fam_ops); +} + +MODULE_DESCRIPTION("AMD MCE decoder"); +MODULE_ALIAS("edac-mce-amd"); +MODULE_LICENSE("GPL"); +module_exit(mce_amd_exit); +#endif diff --git a/drivers/edac/edac_mce_amd.h b/drivers/edac/mce_amd.h index df23ee065f7..35f6e0e3b29 100644 --- a/drivers/edac/edac_mce_amd.h +++ b/drivers/edac/mce_amd.h @@ -1,11 +1,14 @@ #ifndef _EDAC_MCE_AMD_H #define _EDAC_MCE_AMD_H +#include <linux/notifier.h> + #include <asm/mce.h> +#define BIT_64(n) (U64_C(1) << (n)) + #define ERROR_CODE(x) ((x) & 0xffff) #define EXT_ERROR_CODE(x) (((x) >> 16) & 0x1f) -#define EXT_ERR_MSG(x) ext_msgs[EXT_ERROR_CODE(x)] #define LOW_SYNDROME(x) (((x) >> 15) & 0xff) #define HIGH_SYNDROME(x) (((x) >> 24) & 0xff) @@ -20,13 +23,14 @@ #define II_MSG(x) ii_msgs[II(x)] #define LL(x) (((x) >> 0) & 0x3) #define LL_MSG(x) ll_msgs[LL(x)] -#define RRRR(x) (((x) >> 4) & 0xf) -#define RRRR_MSG(x) rrrr_msgs[RRRR(x)] #define TO(x) (((x) >> 8) & 0x1) #define TO_MSG(x) to_msgs[TO(x)] #define PP(x) (((x) >> 9) & 0x3) #define PP_MSG(x) pp_msgs[PP(x)] +#define RRRR(x) (((x) >> 4) & 0xf) +#define RRRR_MSG(x) ((RRRR(x) < 9) ? rrrr_msgs[RRRR(x)] : "Wrong R4!") + #define K8_NBSH 0x4C #define K8_NBSH_VALID_BIT BIT(31) @@ -41,13 +45,45 @@ #define K8_NBSH_UECC BIT(13) #define K8_NBSH_ERR_SCRUBER BIT(8) +enum tt_ids { + TT_INSTR = 0, + TT_DATA, + TT_GEN, + TT_RESV, +}; + +enum ll_ids { + LL_RESV = 0, + LL_L1, + LL_L2, + LL_LG, +}; + +enum ii_ids { + II_MEM = 0, + II_RESV, + II_IO, + II_GEN, +}; + +enum rrrr_ids { + R4_GEN = 0, + R4_RD, + R4_WR, + R4_DRD, + R4_DWR, + R4_IRD, + R4_PREF, + R4_EVICT, + R4_SNOOP, +}; + extern const char *tt_msgs[]; extern const char *ll_msgs[]; extern const char *rrrr_msgs[]; extern const char *pp_msgs[]; extern const char *to_msgs[]; extern const char *ii_msgs[]; -extern const char *ext_msgs[]; /* * relevant NB regs @@ -60,10 +96,19 @@ struct err_regs { u32 nbeal; }; +/* + * per-family decoder ops + */ +struct amd_decoder_ops { + bool (*dc_mce)(u16); + bool (*ic_mce)(u16); + bool (*nb_mce)(u16, u8); +}; void amd_report_gart_errors(bool); -void amd_register_ecc_decoder(void (*f)(int, struct err_regs *)); -void amd_unregister_ecc_decoder(void (*f)(int, struct err_regs *)); -void amd_decode_nb_mce(int, struct err_regs *, int); +void amd_register_ecc_decoder(void (*f)(int, struct mce *, u32)); +void amd_unregister_ecc_decoder(void (*f)(int, struct mce *, u32)); +void amd_decode_nb_mce(int, struct mce *, u32); +int amd_decode_mce(struct notifier_block *nb, unsigned long val, void *data); #endif /* _EDAC_MCE_AMD_H */ diff --git a/drivers/edac/mce_amd_inj.c b/drivers/edac/mce_amd_inj.c new file mode 100644 index 00000000000..8d0688f36d4 --- /dev/null +++ b/drivers/edac/mce_amd_inj.c @@ -0,0 +1,171 @@ +/* + * A simple MCE injection facility for testing the MCE decoding code. This + * driver should be built as module so that it can be loaded on production + * kernels for testing purposes. + * + * This file may be distributed under the terms of the GNU General Public + * License version 2. + * + * Copyright (c) 2010: Borislav Petkov <borislav.petkov@amd.com> + * Advanced Micro Devices Inc. + */ + +#include <linux/kobject.h> +#include <linux/sysdev.h> +#include <linux/edac.h> +#include <asm/mce.h> + +#include "mce_amd.h" + +struct edac_mce_attr { + struct attribute attr; + ssize_t (*show) (struct kobject *kobj, struct edac_mce_attr *attr, char *buf); + ssize_t (*store)(struct kobject *kobj, struct edac_mce_attr *attr, + const char *buf, size_t count); +}; + +#define EDAC_MCE_ATTR(_name, _mode, _show, _store) \ +static struct edac_mce_attr mce_attr_##_name = __ATTR(_name, _mode, _show, _store) + +static struct kobject *mce_kobj; + +/* + * Collect all the MCi_XXX settings + */ +static struct mce i_mce; + +#define MCE_INJECT_STORE(reg) \ +static ssize_t edac_inject_##reg##_store(struct kobject *kobj, \ + struct edac_mce_attr *attr, \ + const char *data, size_t count)\ +{ \ + int ret = 0; \ + unsigned long value; \ + \ + ret = strict_strtoul(data, 16, &value); \ + if (ret < 0) \ + printk(KERN_ERR "Error writing MCE " #reg " field.\n"); \ + \ + i_mce.reg = value; \ + \ + return count; \ +} + +MCE_INJECT_STORE(status); +MCE_INJECT_STORE(misc); +MCE_INJECT_STORE(addr); + +#define MCE_INJECT_SHOW(reg) \ +static ssize_t edac_inject_##reg##_show(struct kobject *kobj, \ + struct edac_mce_attr *attr, \ + char *buf) \ +{ \ + return sprintf(buf, "0x%016llx\n", i_mce.reg); \ +} + +MCE_INJECT_SHOW(status); +MCE_INJECT_SHOW(misc); +MCE_INJECT_SHOW(addr); + +EDAC_MCE_ATTR(status, 0644, edac_inject_status_show, edac_inject_status_store); +EDAC_MCE_ATTR(misc, 0644, edac_inject_misc_show, edac_inject_misc_store); +EDAC_MCE_ATTR(addr, 0644, edac_inject_addr_show, edac_inject_addr_store); + +/* + * This denotes into which bank we're injecting and triggers + * the injection, at the same time. + */ +static ssize_t edac_inject_bank_store(struct kobject *kobj, + struct edac_mce_attr *attr, + const char *data, size_t count) +{ + int ret = 0; + unsigned long value; + + ret = strict_strtoul(data, 10, &value); + if (ret < 0) { + printk(KERN_ERR "Invalid bank value!\n"); + return -EINVAL; + } + + if (value > 5) { + printk(KERN_ERR "Non-existant MCE bank: %lu\n", value); + return -EINVAL; + } + + i_mce.bank = value; + + amd_decode_mce(NULL, 0, &i_mce); + + return count; +} + +static ssize_t edac_inject_bank_show(struct kobject *kobj, + struct edac_mce_attr *attr, char *buf) +{ + return sprintf(buf, "%d\n", i_mce.bank); +} + +EDAC_MCE_ATTR(bank, 0644, edac_inject_bank_show, edac_inject_bank_store); + +static struct edac_mce_attr *sysfs_attrs[] = { &mce_attr_status, &mce_attr_misc, + &mce_attr_addr, &mce_attr_bank +}; + +static int __init edac_init_mce_inject(void) +{ + struct sysdev_class *edac_class = NULL; + int i, err = 0; + + edac_class = edac_get_sysfs_class(); + if (!edac_class) + return -EINVAL; + + mce_kobj = kobject_create_and_add("mce", &edac_class->kset.kobj); + if (!mce_kobj) { + printk(KERN_ERR "Error creating a mce kset.\n"); + err = -ENOMEM; + goto err_mce_kobj; + } + + for (i = 0; i < ARRAY_SIZE(sysfs_attrs); i++) { + err = sysfs_create_file(mce_kobj, &sysfs_attrs[i]->attr); + if (err) { + printk(KERN_ERR "Error creating %s in sysfs.\n", + sysfs_attrs[i]->attr.name); + goto err_sysfs_create; + } + } + return 0; + +err_sysfs_create: + while (i-- >= 0) + sysfs_remove_file(mce_kobj, &sysfs_attrs[i]->attr); + + kobject_del(mce_kobj); + +err_mce_kobj: + edac_put_sysfs_class(); + + return err; +} + +static void __exit edac_exit_mce_inject(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sysfs_attrs); i++) + sysfs_remove_file(mce_kobj, &sysfs_attrs[i]->attr); + + kobject_del(mce_kobj); + + edac_put_sysfs_class(); +} + +module_init(edac_init_mce_inject); +module_exit(edac_exit_mce_inject); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Borislav Petkov <borislav.petkov@amd.com>"); +MODULE_AUTHOR("AMD Inc."); +MODULE_DESCRIPTION("MCE injection facility for testing MCE decoding"); |