/* * Intel 5400 class Memory Controllers kernel module (Seaburg) * * This file may be distributed under the terms of the * GNU General Public License. * * Copyright (c) 2008 by: * Ben Woodard <[email protected]> * Mauro Carvalho Chehab * * Red Hat Inc. https://www.redhat.com * * Forked and adapted from the i5000_edac driver which was * written by Douglas Thompson Linux Networx <[email protected]> * * This module is based on the following document: * * Intel 5400 Chipset Memory Controller Hub (MCH) - Datasheet * http://developer.intel.com/design/chipsets/datashts/313070.htm * * This Memory Controller manages DDR2 FB-DIMMs. It has 2 branches, each with * 2 channels operating in lockstep no-mirror mode. Each channel can have up to * 4 dimm's, each with up to 8GB. * */ #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_module.h" /* * Alter this version for the I5400 module when modifications are made */ #define I5400_REVISION … #define EDAC_MOD_STR … #define i5400_printk(level, fmt, arg...) … #define i5400_mc_printk(mci, level, fmt, arg...) … /* Limits for i5400 */ #define MAX_BRANCHES … #define CHANNELS_PER_BRANCH … #define DIMMS_PER_CHANNEL … #define MAX_CHANNELS … /* Device 16, * Function 0: System Address * Function 1: Memory Branch Map, Control, Errors Register * Function 2: FSB Error Registers * * All 3 functions of Device 16 (0,1,2) share the SAME DID and * uses PCI_DEVICE_ID_INTEL_5400_ERR for device 16 (0,1,2), * PCI_DEVICE_ID_INTEL_5400_FBD0 and PCI_DEVICE_ID_INTEL_5400_FBD1 * for device 21 (0,1). */ /* OFFSETS for Function 0 */ #define AMBASE … #define MAXCH … #define MAXDIMMPERCH … /* OFFSETS for Function 1 */ #define TOLM … #define REDMEMB … #define REC_ECC_LOCATOR_ODD(x) … #define MIR0 … #define MIR1 … #define AMIR0 … #define AMIR1 … /* Fatal error registers */ #define FERR_FAT_FBD … #define FERR_FAT_FBDCHAN … #define NERR_FAT_FBD … #define FERR_NF_FBD … /* Non-fatal error register */ #define NERR_NF_FBD … /* Enable error mask */ #define EMASK_FBD … #define ERR0_FBD … #define ERR1_FBD … #define ERR2_FBD … #define MCERR_FBD … /* No OFFSETS for Device 16 Function 2 */ /* * Device 21, * Function 0: Memory Map Branch 0 * * Device 22, * Function 0: Memory Map Branch 1 */ /* OFFSETS for Function 0 */ #define AMBPRESENT_0 … #define AMBPRESENT_1 … #define MTR0 … #define MTR1 … #define MTR2 … #define MTR3 … /* OFFSETS for Function 1 */ #define NRECFGLOG … #define RECFGLOG … #define NRECMEMA … #define NRECMEMB … #define NRECFB_DIMMA … #define NRECFB_DIMMB … #define NRECFB_DIMMC … #define NRECFB_DIMMD … #define NRECFB_DIMME … #define NRECFB_DIMMF … #define REDMEMA … #define RECMEMA … #define RECMEMB … #define RECFB_DIMMA … #define RECFB_DIMMB … #define RECFB_DIMMC … #define RECFB_DIMMD … #define RECFB_DIMME … #define RECFB_DIMMF … /* * Error indicator bits and masks * Error masks are according with Table 5-17 of i5400 datasheet */ enum error_mask { … }; /* * Names to translate bit error into something useful */ static const char *error_name[] = …; /* Fatal errors */ #define ERROR_FAT_MASK … /* Correctable errors */ #define ERROR_NF_CORRECTABLE … #define ERROR_NF_DIMM_SPARE … #define ERROR_NF_SPD_PROTOCOL … #define ERROR_NF_NORTH_CRC … /* Recoverable errors */ #define ERROR_NF_RECOVERABLE … /* uncorrectable errors */ #define ERROR_NF_UNCORRECTABLE … /* mask to all non-fatal errors */ #define ERROR_NF_MASK … /* * Define error masks for the several registers */ /* Enable all fatal and non fatal errors */ #define ENABLE_EMASK_ALL … /* mask for fatal error registers */ #define FERR_FAT_MASK … /* masks for non-fatal error register */ static inline int to_nf_mask(unsigned int mask) { return (mask & EMASK_M29) | (mask >> 3); }; static inline int from_nf_ferr(unsigned int mask) { return (mask & EMASK_M29) | /* Bit 28 */ (mask & ((1 << 28) - 1) << 3); /* Bits 0 to 27 */ }; #define FERR_NF_MASK … #define FERR_NF_CORRECTABLE … #define FERR_NF_DIMM_SPARE … #define FERR_NF_SPD_PROTOCOL … #define FERR_NF_NORTH_CRC … #define FERR_NF_RECOVERABLE … #define FERR_NF_UNCORRECTABLE … /* * Defines to extract the various fields from the * MTRx - Memory Technology Registers */ #define MTR_DIMMS_PRESENT(mtr) … #define MTR_DIMMS_ETHROTTLE(mtr) … #define MTR_DRAM_WIDTH(mtr) … #define MTR_DRAM_BANKS(mtr) … #define MTR_DRAM_BANKS_ADDR_BITS(mtr) … #define MTR_DIMM_RANK(mtr) … #define MTR_DIMM_RANK_ADDR_BITS(mtr) … #define MTR_DIMM_ROWS(mtr) … #define MTR_DIMM_ROWS_ADDR_BITS(mtr) … #define MTR_DIMM_COLS(mtr) … #define MTR_DIMM_COLS_ADDR_BITS(mtr) … /* This applies to FERR_NF_FB-DIMM as well as FERR_FAT_FB-DIMM */ static inline int extract_fbdchan_indx(u32 x) { … } /* Device name and register DID (Device ID) */ struct i5400_dev_info { … }; /* Table of devices attributes supported by this driver */ static const struct i5400_dev_info i5400_devs[] = …; struct i5400_dimm_info { … }; /* driver private data structure */ struct i5400_pvt { … }; /* I5400 MCH error information retrieved from Hardware */ struct i5400_error_info { … }; /* note that nrec_rdwr changed from NRECMEMA to NRECMEMB between the 5000 and 5400 better to use an inline function than a macro in this case */ static inline int nrec_bank(struct i5400_error_info *info) { … } static inline int nrec_rank(struct i5400_error_info *info) { … } static inline int nrec_buf_id(struct i5400_error_info *info) { … } static inline int nrec_rdwr(struct i5400_error_info *info) { … } /* This applies to both NREC and REC string so it can be used with nrec_rdwr and rec_rdwr */ static inline const char *rdwr_str(int rdwr) { … } static inline int nrec_cas(struct i5400_error_info *info) { … } static inline int nrec_ras(struct i5400_error_info *info) { … } static inline int rec_bank(struct i5400_error_info *info) { … } static inline int rec_rank(struct i5400_error_info *info) { … } static inline int rec_rdwr(struct i5400_error_info *info) { … } static inline int rec_cas(struct i5400_error_info *info) { … } static inline int rec_ras(struct i5400_error_info *info) { … } static struct edac_pci_ctl_info *i5400_pci; /* * i5400_get_error_info Retrieve the hardware error information from * the hardware and cache it in the 'info' * structure */ static void i5400_get_error_info(struct mem_ctl_info *mci, struct i5400_error_info *info) { … } /* * i5400_proccess_non_recoverable_info(struct mem_ctl_info *mci, * struct i5400_error_info *info, * int handle_errors); * * handle the Intel FATAL and unrecoverable errors, if any */ static void i5400_proccess_non_recoverable_info(struct mem_ctl_info *mci, struct i5400_error_info *info, unsigned long allErrors) { … } /* * i5400_process_fatal_error_info(struct mem_ctl_info *mci, * struct i5400_error_info *info, * int handle_errors); * * handle the Intel NON-FATAL errors, if any */ static void i5400_process_nonfatal_error_info(struct mem_ctl_info *mci, struct i5400_error_info *info) { … } /* * i5400_process_error_info Process the error info that is * in the 'info' structure, previously retrieved from hardware */ static void i5400_process_error_info(struct mem_ctl_info *mci, struct i5400_error_info *info) { … } /* * i5400_clear_error Retrieve any error from the hardware * but do NOT process that error. * Used for 'clearing' out of previous errors * Called by the Core module. */ static void i5400_clear_error(struct mem_ctl_info *mci) { … } /* * i5400_check_error Retrieve and process errors reported by the * hardware. Called by the Core module. */ static void i5400_check_error(struct mem_ctl_info *mci) { … } /* * i5400_put_devices 'put' all the devices that we have * reserved via 'get' */ static void i5400_put_devices(struct mem_ctl_info *mci) { … } /* * i5400_get_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 */ static int i5400_get_devices(struct mem_ctl_info *mci, int dev_idx) { … } /* * determine_amb_present * * the information is contained in DIMMS_PER_CHANNEL different * registers determining which of the DIMMS_PER_CHANNEL requires * knowing which channel is in question * * 2 branches, each with 2 channels * b0_ambpresent0 for channel '0' * b0_ambpresent1 for channel '1' * b1_ambpresent0 for channel '2' * b1_ambpresent1 for channel '3' */ static int determine_amb_present_reg(struct i5400_pvt *pvt, int channel) { … } /* * determine_mtr(pvt, dimm, channel) * * return the proper MTR register as determine by the dimm and desired channel */ static int determine_mtr(struct i5400_pvt *pvt, int dimm, int channel) { … } /* */ static void decode_mtr(int slot_row, u16 mtr) { … } static void handle_channel(struct i5400_pvt *pvt, int dimm, int channel, struct i5400_dimm_info *dinfo) { … } /* * calculate_dimm_size * * also will output a DIMM matrix map, if debug is enabled, for viewing * how the DIMMs are populated */ static void calculate_dimm_size(struct i5400_pvt *pvt) { … } /* * i5400_get_mc_regs read in the necessary registers and * cache locally * * Fills in the private data members */ static void i5400_get_mc_regs(struct mem_ctl_info *mci) { … } /* * i5400_init_dimms Initialize the 'dimms' table within * the mci control structure with the * addressing of memory. * * return: * 0 success * 1 no actual memory found on this MC */ static int i5400_init_dimms(struct mem_ctl_info *mci) { … } /* * i5400_enable_error_reporting * Turn on the memory reporting features of the hardware */ static void i5400_enable_error_reporting(struct mem_ctl_info *mci) { … } /* * i5400_probe1 Probe for ONE instance of device to see if it is * present. * return: * 0 for FOUND a device * < 0 for error code */ static int i5400_probe1(struct pci_dev *pdev, int dev_idx) { … } /* * i5400_init_one constructor for one instance of device * * returns: * negative on error * count (>= 0) */ static int i5400_init_one(struct pci_dev *pdev, const struct pci_device_id *id) { … } /* * i5400_remove_one destructor for one instance of device * */ static void i5400_remove_one(struct pci_dev *pdev) { … } /* * pci_device_id table for which devices we are looking for * * The "E500P" device is the first device supported. */ static const struct pci_device_id i5400_pci_tbl[] = …; MODULE_DEVICE_TABLE(pci, i5400_pci_tbl); /* * i5400_driver pci_driver structure for this module * */ static struct pci_driver i5400_driver = …; /* * i5400_init Module entry function * Try to initialize this module for its devices */ static int __init i5400_init(void) { … } /* * i5400_exit() Module exit function * Unregister the driver */ static void __exit i5400_exit(void) { … } module_init(…) …; module_exit(i5400_exit); MODULE_LICENSE(…) …; MODULE_AUTHOR(…) …; MODULE_AUTHOR(…) …; MODULE_AUTHOR(…) …; MODULE_DESCRIPTION(…) …; module_param(edac_op_state, int, 0444); MODULE_PARM_DESC(…) …;