]> Pileus Git - ~andy/linux/blobdiff - drivers/edac/amd64_edac.c
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jbarnes...
[~andy/linux] / drivers / edac / amd64_edac.c
index 3b6c421531cf23590ab33a014ba93410845220ca..858fe603722361f4292052952a738f3f9cc4a03e 100644 (file)
@@ -1,4 +1,5 @@
 #include "amd64_edac.h"
+#include <asm/k8.h>
 
 static struct edac_pci_ctl_info *amd64_ctl_pci;
 
@@ -753,13 +754,13 @@ static void amd64_cpu_display_info(struct amd64_pvt *pvt)
 static enum edac_type amd64_determine_edac_cap(struct amd64_pvt *pvt)
 {
        int bit;
-       enum dev_type edac_cap = EDAC_NONE;
+       enum dev_type edac_cap = EDAC_FLAG_NONE;
 
        bit = (boot_cpu_data.x86 > 0xf || pvt->ext_model >= OPTERON_CPU_REV_F)
                ? 19
                : 17;
 
-       if (pvt->dclr0 >> BIT(bit))
+       if (pvt->dclr0 & BIT(bit))
                edac_cap = EDAC_FLAG_SECDED;
 
        return edac_cap;
@@ -1268,7 +1269,7 @@ static int f10_early_channel_count(struct amd64_pvt *pvt)
        if (channels == 0)
                channels = 1;
 
-       debugf0("DIMM count= %d\n", channels);
+       debugf0("MCT channel count: %d\n", channels);
 
        return channels;
 
@@ -2965,7 +2966,12 @@ static int amd64_check_ecc_enabled(struct amd64_pvt *pvt)
                                "    Use of the override can cause "
                                "unknown side effects.\n");
                        ret = -ENODEV;
-               }
+               } else
+                       /*
+                        * enable further driver loading if ECC enable is
+                        * overridden.
+                        */
+                       ret = 0;
        } else {
                amd64_printk(KERN_INFO,
                        "ECC is enabled by BIOS, Proceeding "
@@ -2978,3 +2984,376 @@ static int amd64_check_ecc_enabled(struct amd64_pvt *pvt)
        return ret;
 }
 
+struct mcidev_sysfs_attribute sysfs_attrs[ARRAY_SIZE(amd64_dbg_attrs) +
+                                         ARRAY_SIZE(amd64_inj_attrs) +
+                                         1];
+
+struct mcidev_sysfs_attribute terminator = { .attr = { .name = NULL } };
+
+static void amd64_set_mc_sysfs_attributes(struct mem_ctl_info *mci)
+{
+       unsigned int i = 0, j = 0;
+
+       for (; i < ARRAY_SIZE(amd64_dbg_attrs); i++)
+               sysfs_attrs[i] = amd64_dbg_attrs[i];
+
+       for (j = 0; j < ARRAY_SIZE(amd64_inj_attrs); j++, i++)
+               sysfs_attrs[i] = amd64_inj_attrs[j];
+
+       sysfs_attrs[i] = terminator;
+
+       mci->mc_driver_sysfs_attributes = sysfs_attrs;
+}
+
+static void amd64_setup_mci_misc_attributes(struct mem_ctl_info *mci)
+{
+       struct amd64_pvt *pvt = mci->pvt_info;
+
+       mci->mtype_cap          = MEM_FLAG_DDR2 | MEM_FLAG_RDDR2;
+       mci->edac_ctl_cap       = EDAC_FLAG_NONE;
+
+       if (pvt->nbcap & K8_NBCAP_SECDED)
+               mci->edac_ctl_cap |= EDAC_FLAG_SECDED;
+
+       if (pvt->nbcap & K8_NBCAP_CHIPKILL)
+               mci->edac_ctl_cap |= EDAC_FLAG_S4ECD4ED;
+
+       mci->edac_cap           = amd64_determine_edac_cap(pvt);
+       mci->mod_name           = EDAC_MOD_STR;
+       mci->mod_ver            = EDAC_AMD64_VERSION;
+       mci->ctl_name           = get_amd_family_name(pvt->mc_type_index);
+       mci->dev_name           = pci_name(pvt->dram_f2_ctl);
+       mci->ctl_page_to_phys   = NULL;
+
+       /* IMPORTANT: Set the polling 'check' function in this module */
+       mci->edac_check         = amd64_check;
+
+       /* memory scrubber interface */
+       mci->set_sdram_scrub_rate = amd64_set_scrub_rate;
+       mci->get_sdram_scrub_rate = amd64_get_scrub_rate;
+}
+
+/*
+ * Init stuff for this DRAM Controller device.
+ *
+ * Due to a hardware feature on Fam10h CPUs, the Enable Extended Configuration
+ * Space feature MUST be enabled on ALL Processors prior to actually reading
+ * from the ECS registers. Since the loading of the module can occur on any
+ * 'core', and cores don't 'see' all the other processors ECS data when the
+ * others are NOT enabled. Our solution is to first enable ECS access in this
+ * routine on all processors, gather some data in a amd64_pvt structure and
+ * later come back in a finish-setup function to perform that final
+ * initialization. See also amd64_init_2nd_stage() for that.
+ */
+static int amd64_probe_one_instance(struct pci_dev *dram_f2_ctl,
+                                   int mc_type_index)
+{
+       struct amd64_pvt *pvt = NULL;
+       int err = 0, ret;
+
+       ret = -ENOMEM;
+       pvt = kzalloc(sizeof(struct amd64_pvt), GFP_KERNEL);
+       if (!pvt)
+               goto err_exit;
+
+       pvt->mc_node_id = get_node_id(dram_f2_ctl);
+
+       pvt->dram_f2_ctl        = dram_f2_ctl;
+       pvt->ext_model          = boot_cpu_data.x86_model >> 4;
+       pvt->mc_type_index      = mc_type_index;
+       pvt->ops                = family_ops(mc_type_index);
+       pvt->old_mcgctl         = 0;
+
+       /*
+        * We have the dram_f2_ctl device as an argument, now go reserve its
+        * sibling devices from the PCI system.
+        */
+       ret = -ENODEV;
+       err = amd64_reserve_mc_sibling_devices(pvt, mc_type_index);
+       if (err)
+               goto err_free;
+
+       ret = -EINVAL;
+       err = amd64_check_ecc_enabled(pvt);
+       if (err)
+               goto err_put;
+
+       /*
+        * Key operation here: setup of HW prior to performing ops on it. Some
+        * setup is required to access ECS data. After this is performed, the
+        * 'teardown' function must be called upon error and normal exit paths.
+        */
+       if (boot_cpu_data.x86 >= 0x10)
+               amd64_setup(pvt);
+
+       /*
+        * Save the pointer to the private data for use in 2nd initialization
+        * stage
+        */
+       pvt_lookup[pvt->mc_node_id] = pvt;
+
+       return 0;
+
+err_put:
+       amd64_free_mc_sibling_devices(pvt);
+
+err_free:
+       kfree(pvt);
+
+err_exit:
+       return ret;
+}
+
+/*
+ * This is the finishing stage of the init code. Needs to be performed after all
+ * MCs' hardware have been prepped for accessing extended config space.
+ */
+static int amd64_init_2nd_stage(struct amd64_pvt *pvt)
+{
+       int node_id = pvt->mc_node_id;
+       struct mem_ctl_info *mci;
+       int ret, err = 0;
+
+       amd64_read_mc_registers(pvt);
+
+       ret = -ENODEV;
+       if (pvt->ops->probe_valid_hardware) {
+               err = pvt->ops->probe_valid_hardware(pvt);
+               if (err)
+                       goto err_exit;
+       }
+
+       /*
+        * We need to determine how many memory channels there are. Then use
+        * that information for calculating the size of the dynamic instance
+        * tables in the 'mci' structure
+        */
+       pvt->channel_count = pvt->ops->early_channel_count(pvt);
+       if (pvt->channel_count < 0)
+               goto err_exit;
+
+       ret = -ENOMEM;
+       mci = edac_mc_alloc(0, CHIPSELECT_COUNT, pvt->channel_count, node_id);
+       if (!mci)
+               goto err_exit;
+
+       mci->pvt_info = pvt;
+
+       mci->dev = &pvt->dram_f2_ctl->dev;
+       amd64_setup_mci_misc_attributes(mci);
+
+       if (amd64_init_csrows(mci))
+               mci->edac_cap = EDAC_FLAG_NONE;
+
+       amd64_enable_ecc_error_reporting(mci);
+       amd64_set_mc_sysfs_attributes(mci);
+
+       ret = -ENODEV;
+       if (edac_mc_add_mc(mci)) {
+               debugf1("failed edac_mc_add_mc()\n");
+               goto err_add_mc;
+       }
+
+       mci_lookup[node_id] = mci;
+       pvt_lookup[node_id] = NULL;
+       return 0;
+
+err_add_mc:
+       edac_mc_free(mci);
+
+err_exit:
+       debugf0("failure to init 2nd stage: ret=%d\n", ret);
+
+       amd64_restore_ecc_error_reporting(pvt);
+
+       if (boot_cpu_data.x86 > 0xf)
+               amd64_teardown(pvt);
+
+       amd64_free_mc_sibling_devices(pvt);
+
+       kfree(pvt_lookup[pvt->mc_node_id]);
+       pvt_lookup[node_id] = NULL;
+
+       return ret;
+}
+
+
+static int __devinit amd64_init_one_instance(struct pci_dev *pdev,
+                                const struct pci_device_id *mc_type)
+{
+       int ret = 0;
+
+       debugf0("(MC node=%d,mc_type='%s')\n", get_node_id(pdev),
+               get_amd_family_name(mc_type->driver_data));
+
+       ret = pci_enable_device(pdev);
+       if (ret < 0)
+               ret = -EIO;
+       else
+               ret = amd64_probe_one_instance(pdev, mc_type->driver_data);
+
+       if (ret < 0)
+               debugf0("ret=%d\n", ret);
+
+       return ret;
+}
+
+static void __devexit amd64_remove_one_instance(struct pci_dev *pdev)
+{
+       struct mem_ctl_info *mci;
+       struct amd64_pvt *pvt;
+
+       /* Remove from EDAC CORE tracking list */
+       mci = edac_mc_del_mc(&pdev->dev);
+       if (!mci)
+               return;
+
+       pvt = mci->pvt_info;
+
+       amd64_restore_ecc_error_reporting(pvt);
+
+       if (boot_cpu_data.x86 > 0xf)
+               amd64_teardown(pvt);
+
+       amd64_free_mc_sibling_devices(pvt);
+
+       kfree(pvt);
+       mci->pvt_info = NULL;
+
+       mci_lookup[pvt->mc_node_id] = NULL;
+
+       /* Free the EDAC CORE resources */
+       edac_mc_free(mci);
+}
+
+/*
+ * This table is part of the interface for loading drivers for PCI devices. The
+ * PCI core identifies what devices are on a system during boot, and then
+ * inquiry this table to see if this driver is for a given device found.
+ */
+static const struct pci_device_id amd64_pci_table[] __devinitdata = {
+       {
+               .vendor         = PCI_VENDOR_ID_AMD,
+               .device         = PCI_DEVICE_ID_AMD_K8_NB_MEMCTL,
+               .subvendor      = PCI_ANY_ID,
+               .subdevice      = PCI_ANY_ID,
+               .class          = 0,
+               .class_mask     = 0,
+               .driver_data    = K8_CPUS
+       },
+       {
+               .vendor         = PCI_VENDOR_ID_AMD,
+               .device         = PCI_DEVICE_ID_AMD_10H_NB_DRAM,
+               .subvendor      = PCI_ANY_ID,
+               .subdevice      = PCI_ANY_ID,
+               .class          = 0,
+               .class_mask     = 0,
+               .driver_data    = F10_CPUS
+       },
+       {
+               .vendor         = PCI_VENDOR_ID_AMD,
+               .device         = PCI_DEVICE_ID_AMD_11H_NB_DRAM,
+               .subvendor      = PCI_ANY_ID,
+               .subdevice      = PCI_ANY_ID,
+               .class          = 0,
+               .class_mask     = 0,
+               .driver_data    = F11_CPUS
+       },
+       {0, }
+};
+MODULE_DEVICE_TABLE(pci, amd64_pci_table);
+
+static struct pci_driver amd64_pci_driver = {
+       .name           = EDAC_MOD_STR,
+       .probe          = amd64_init_one_instance,
+       .remove         = __devexit_p(amd64_remove_one_instance),
+       .id_table       = amd64_pci_table,
+};
+
+static void amd64_setup_pci_device(void)
+{
+       struct mem_ctl_info *mci;
+       struct amd64_pvt *pvt;
+
+       if (amd64_ctl_pci)
+               return;
+
+       mci = mci_lookup[0];
+       if (mci) {
+
+               pvt = mci->pvt_info;
+               amd64_ctl_pci =
+                       edac_pci_create_generic_ctl(&pvt->dram_f2_ctl->dev,
+                                                   EDAC_MOD_STR);
+
+               if (!amd64_ctl_pci) {
+                       pr_warning("%s(): Unable to create PCI control\n",
+                                  __func__);
+
+                       pr_warning("%s(): PCI error report via EDAC not set\n",
+                                  __func__);
+                       }
+       }
+}
+
+static int __init amd64_edac_init(void)
+{
+       int nb, err = -ENODEV;
+
+       edac_printk(KERN_INFO, EDAC_MOD_STR, EDAC_AMD64_VERSION "\n");
+
+       opstate_init();
+
+       if (cache_k8_northbridges() < 0)
+               goto err_exit;
+
+       err = pci_register_driver(&amd64_pci_driver);
+       if (err)
+               return err;
+
+       /*
+        * At this point, the array 'pvt_lookup[]' contains pointers to alloc'd
+        * amd64_pvt structs. These will be used in the 2nd stage init function
+        * to finish initialization of the MC instances.
+        */
+       for (nb = 0; nb < num_k8_northbridges; nb++) {
+               if (!pvt_lookup[nb])
+                       continue;
+
+               err = amd64_init_2nd_stage(pvt_lookup[nb]);
+               if (err)
+                       goto err_2nd_stage;
+       }
+
+       amd64_setup_pci_device();
+
+       return 0;
+
+err_2nd_stage:
+       debugf0("2nd stage failed\n");
+
+err_exit:
+       pci_unregister_driver(&amd64_pci_driver);
+
+       return err;
+}
+
+static void __exit amd64_edac_exit(void)
+{
+       if (amd64_ctl_pci)
+               edac_pci_release_generic_ctl(amd64_ctl_pci);
+
+       pci_unregister_driver(&amd64_pci_driver);
+}
+
+module_init(amd64_edac_init);
+module_exit(amd64_edac_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("SoftwareBitMaker: Doug Thompson, "
+               "Dave Peterson, Thayne Harbaugh");
+MODULE_DESCRIPTION("MC support for AMD64 memory controllers - "
+               EDAC_AMD64_VERSION);
+
+module_param(edac_op_state, int, 0444);
+MODULE_PARM_DESC(edac_op_state, "EDAC Error Reporting state: 0=Poll,1=NMI");