* Altera Arria10 L2 cache and On-Chip RAM ECC handling. (Thor Thayer)

* Remove ad-hoc buffering of MCE records in sb_edac and i7core_edac. (Tony Luck)
 
 * Do not register sb_edac with pci_register_driver(). (Tony Luck)
 
 * Add support for Skylake to ie31200_edac. (Jason Baron)
 
 * Do not register amd64_edac with pci_register_driver(). (Borislav Petkov)
 
 + the usual round of cleanups and fixes all over the place.
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQIcBAABAgAGBQJXOaHaAAoJEBLB8Bhh3lVKRdoP/jDewaX4GAxgcKaK9Kx7VjMe
 i42E/7syh5iLoyFgZ1hUjvqiQHN4RyWloUYyNHbNxGS9uXCMXIUoJQd2FjOY442s
 oeEaoMCfZsJLzKQti3fUPK9GugZ57BSZxfn6NlJPpyG4FxSirOcZFCxGaNuyqqrh
 0M+gFvbWfZcVDLE0FI5CUYZWRxsk//+pLM4ZkQZFA8Eo1tGVc3r0zEEAlL/uEMDW
 P0ATvRHNUeib9YQGTdSD7cNUpRX4SX+T8lDUiaVm+tHL5ES3b4rayMBdbVMrahfc
 Gnxu9GtO5gWXEY6QDDpOx+VkbfFmDFodx833psJ3MVD8evEFHHdinkgDaptLrrV6
 92ZDKR5s3W6tKXkcmGuExrtc17UgjLcRZCebXbv+5FJlVrslzvQ9ESOTRyiZBrCD
 ZpFi2TZhpYU1uEWuoBZCbWNXW2pcSt7/bQ9bYUvfrvNfgPzPnblubJuVKRcfY0WB
 x2vj0PNnckpoPRvskV7GEe0Y/JISzAxBQUK6XO+GJgMgz5M+23SEaSVU5yeyf26e
 x/yD5yImQGN84AxfRMMvbR2JvpVLN3vdFWtWigneht160erVA/Qgw5Gcrpw53tZr
 zPD3fGaetrNgS8BvJAy/c6xHV1j6A1RGCGThy8Ivqep9WD90a5JhR1NRjZp6m8sf
 aB0A1zTP7e+hRFkZGahO
 =ud2P
 -----END PGP SIGNATURE-----

Merge tag 'edac_for_4.7' of git://git.kernel.org/pub/scm/linux/kernel/git/bp/bp

Pull EDAC updates from Borislav Petkov:
 "It was pretty busy in EDAC land this time:

   - Altera Arria10 L2 cache and On-Chip RAM ECC handling (Thor Thayer)

   - Remove ad-hoc buffering of MCE records in sb_edac and i7core_edac
     (Tony Luck)

   - Do not register sb_edac with pci_register_driver() (Tony Luck)

   - Add support for Skylake to ie31200_edac (Jason Baron)

   - Do not register amd64_edac with pci_register_driver() (Borislav
     Petkov)

  ... plus the usual round of cleanups and fixes all over the place"

* tag 'edac_for_4.7' of git://git.kernel.org/pub/scm/linux/kernel/git/bp/bp: (25 commits)
  EDAC, amd64_edac: Drop pci_register_driver() use
  EDAC, ie31200_edac: Add Skylake support
  EDAC, sb_edac: Use cpu family/model in driver detection
  EDAC, i7core: Remove double buffering of error records
  EDAC, amd64_edac: Issue driver banner only on success
  ARM: socfpga: Initialize Arria10 OCRAM ECC on startup
  EDAC: Increment correct counter in edac_inc_ue_error()
  EDAC, sb_edac: Remove double buffering of error records
  EDAC: Fix used after kfree() error in edac_unregister_sysfs()
  EDAC, altera: Avoid unused function warnings
  EDAC, altera: Remove useless casts
  ARM: socfpga: Enable Arria10 OCRAM ECC on startup
  EDAC, altera: Add Arria10 OCRAM ECC support
  Documentation: dt: socfpga: Add Altera Arria10 OCRAM binding
  EDAC, altera: Make OCRAM ECC dependency check generic
  EDAC, altera: Add register offset for ECC Enable
  EDAC, altera: Extract error inject operations to a struct fops
  ARM: socfpga: Enable Arria10 L2 cache ECC on startup
  EDAC, altera: Add Arria10 L2 Cache ECC handling
  Documentation, dt, socfpga: Add Altera Arria10 L2 cache binding
  ...
This commit is contained in:
Linus Torvalds 2016-05-16 18:44:39 -07:00
commit 1cc3880a3c
15 changed files with 898 additions and 451 deletions

View File

@ -3,6 +3,7 @@ This driver uses the EDAC framework to implement the SOCFPGA ECC Manager.
The ECC Manager counts and corrects single bit errors and counts/handles The ECC Manager counts and corrects single bit errors and counts/handles
double bit errors which are uncorrectable. double bit errors which are uncorrectable.
Cyclone5 and Arria5 ECC Manager
Required Properties: Required Properties:
- compatible : Should be "altr,socfpga-ecc-manager" - compatible : Should be "altr,socfpga-ecc-manager"
- #address-cells: must be 1 - #address-cells: must be 1
@ -47,3 +48,52 @@ Example:
interrupts = <0 178 1>, <0 179 1>; interrupts = <0 178 1>, <0 179 1>;
}; };
}; };
Arria10 SoCFPGA ECC Manager
The Arria10 SoC ECC Manager handles the IRQs for each peripheral
in a shared register instead of individual IRQs like the Cyclone5
and Arria5. Therefore the device tree is different as well.
Required Properties:
- compatible : Should be "altr,socfpga-a10-ecc-manager"
- altr,sysgr-syscon : phandle to Arria10 System Manager Block
containing the ECC manager registers.
- #address-cells: must be 1
- #size-cells: must be 1
- interrupts : Should be single bit error interrupt, then double bit error
interrupt. Note the rising edge type.
- ranges : standard definition, should translate from local addresses
Subcomponents:
L2 Cache ECC
Required Properties:
- compatible : Should be "altr,socfpga-a10-l2-ecc"
- reg : Address and size for ECC error interrupt clear registers.
On-Chip RAM ECC
Required Properties:
- compatible : Should be "altr,socfpga-a10-ocram-ecc"
- reg : Address and size for ECC block registers.
Example:
eccmgr: eccmgr@ffd06000 {
compatible = "altr,socfpga-a10-ecc-manager";
altr,sysmgr-syscon = <&sysmgr>;
#address-cells = <1>;
#size-cells = <1>;
interrupts = <0 2 IRQ_TYPE_LEVEL_HIGH>,
<0 0 IRQ_TYPE_LEVEL_HIGH>;
ranges;
l2-ecc@ffd06010 {
compatible = "altr,socfpga-a10-l2-ecc";
reg = <0xffd06010 0x4>;
};
ocram-ecc@ff8c3000 {
compatible = "altr,socfpga-a10-ocram-ecc";
reg = <0xff8c3000 0x90>;
};
};

View File

@ -38,6 +38,8 @@ extern void socfpga_init_clocks(void);
extern void socfpga_sysmgr_init(void); extern void socfpga_sysmgr_init(void);
void socfpga_init_l2_ecc(void); void socfpga_init_l2_ecc(void);
void socfpga_init_ocram_ecc(void); void socfpga_init_ocram_ecc(void);
void socfpga_init_arria10_l2_ecc(void);
void socfpga_init_arria10_ocram_ecc(void);
extern void __iomem *sys_manager_base_addr; extern void __iomem *sys_manager_base_addr;
extern void __iomem *rst_manager_base_addr; extern void __iomem *rst_manager_base_addr;

View File

@ -17,6 +17,20 @@
#include <linux/of_platform.h> #include <linux/of_platform.h>
#include <linux/of_address.h> #include <linux/of_address.h>
#include "core.h"
/* A10 System Manager L2 ECC Control register */
#define A10_MPU_CTRL_L2_ECC_OFST 0x0
#define A10_MPU_CTRL_L2_ECC_EN BIT(0)
/* A10 System Manager Global IRQ Mask register */
#define A10_SYSMGR_ECC_INTMASK_CLR_OFST 0x98
#define A10_SYSMGR_ECC_INTMASK_CLR_L2 BIT(0)
/* A10 System Manager L2 ECC IRQ Clear register */
#define A10_SYSMGR_MPU_CLEAR_L2_ECC_OFST 0xA8
#define A10_SYSMGR_MPU_CLEAR_L2_ECC (BIT(31) | BIT(15))
void socfpga_init_l2_ecc(void) void socfpga_init_l2_ecc(void)
{ {
struct device_node *np; struct device_node *np;
@ -39,3 +53,38 @@ void socfpga_init_l2_ecc(void)
writel(0x01, mapped_l2_edac_addr); writel(0x01, mapped_l2_edac_addr);
iounmap(mapped_l2_edac_addr); iounmap(mapped_l2_edac_addr);
} }
void socfpga_init_arria10_l2_ecc(void)
{
struct device_node *np;
void __iomem *mapped_l2_edac_addr;
/* Find the L2 EDAC device tree node */
np = of_find_compatible_node(NULL, NULL, "altr,socfpga-a10-l2-ecc");
if (!np) {
pr_err("Unable to find socfpga-a10-l2-ecc in dtb\n");
return;
}
mapped_l2_edac_addr = of_iomap(np, 0);
of_node_put(np);
if (!mapped_l2_edac_addr) {
pr_err("Unable to find L2 ECC mapping in dtb\n");
return;
}
if (!sys_manager_base_addr) {
pr_err("System Mananger not mapped for L2 ECC\n");
goto exit;
}
/* Clear any pending IRQs */
writel(A10_SYSMGR_MPU_CLEAR_L2_ECC, (sys_manager_base_addr +
A10_SYSMGR_MPU_CLEAR_L2_ECC_OFST));
/* Enable ECC */
writel(A10_SYSMGR_ECC_INTMASK_CLR_L2, sys_manager_base_addr +
A10_SYSMGR_ECC_INTMASK_CLR_OFST);
writel(A10_MPU_CTRL_L2_ECC_EN, mapped_l2_edac_addr +
A10_MPU_CTRL_L2_ECC_OFST);
exit:
iounmap(mapped_l2_edac_addr);
}

View File

@ -13,12 +13,15 @@
* You should have received a copy of the GNU General Public License along with * You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>. * this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <linux/delay.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/genalloc.h> #include <linux/genalloc.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of_address.h> #include <linux/of_address.h>
#include <linux/of_platform.h> #include <linux/of_platform.h>
#include "core.h"
#define ALTR_OCRAM_CLEAR_ECC 0x00000018 #define ALTR_OCRAM_CLEAR_ECC 0x00000018
#define ALTR_OCRAM_ECC_EN 0x00000019 #define ALTR_OCRAM_ECC_EN 0x00000019
@ -47,3 +50,133 @@ void socfpga_init_ocram_ecc(void)
iounmap(mapped_ocr_edac_addr); iounmap(mapped_ocr_edac_addr);
} }
/* Arria10 OCRAM Section */
#define ALTR_A10_ECC_CTRL_OFST 0x08
#define ALTR_A10_OCRAM_ECC_EN_CTL (BIT(1) | BIT(0))
#define ALTR_A10_ECC_INITA BIT(16)
#define ALTR_A10_ECC_INITSTAT_OFST 0x0C
#define ALTR_A10_ECC_INITCOMPLETEA BIT(0)
#define ALTR_A10_ECC_INITCOMPLETEB BIT(8)
#define ALTR_A10_ECC_ERRINTEN_OFST 0x10
#define ALTR_A10_ECC_SERRINTEN BIT(0)
#define ALTR_A10_ECC_INTSTAT_OFST 0x20
#define ALTR_A10_ECC_SERRPENA BIT(0)
#define ALTR_A10_ECC_DERRPENA BIT(8)
#define ALTR_A10_ECC_ERRPENA_MASK (ALTR_A10_ECC_SERRPENA | \
ALTR_A10_ECC_DERRPENA)
/* ECC Manager Defines */
#define A10_SYSMGR_ECC_INTMASK_SET_OFST 0x94
#define A10_SYSMGR_ECC_INTMASK_CLR_OFST 0x98
#define A10_SYSMGR_ECC_INTMASK_OCRAM BIT(1)
#define ALTR_A10_ECC_INIT_WATCHDOG_10US 10000
static inline void ecc_set_bits(u32 bit_mask, void __iomem *ioaddr)
{
u32 value = readl(ioaddr);
value |= bit_mask;
writel(value, ioaddr);
}
static inline void ecc_clear_bits(u32 bit_mask, void __iomem *ioaddr)
{
u32 value = readl(ioaddr);
value &= ~bit_mask;
writel(value, ioaddr);
}
static inline int ecc_test_bits(u32 bit_mask, void __iomem *ioaddr)
{
u32 value = readl(ioaddr);
return (value & bit_mask) ? 1 : 0;
}
/*
* This function uses the memory initialization block in the Arria10 ECC
* controller to initialize/clear the entire memory data and ECC data.
*/
static int altr_init_memory_port(void __iomem *ioaddr)
{
int limit = ALTR_A10_ECC_INIT_WATCHDOG_10US;
ecc_set_bits(ALTR_A10_ECC_INITA, (ioaddr + ALTR_A10_ECC_CTRL_OFST));
while (limit--) {
if (ecc_test_bits(ALTR_A10_ECC_INITCOMPLETEA,
(ioaddr + ALTR_A10_ECC_INITSTAT_OFST)))
break;
udelay(1);
}
if (limit < 0)
return -EBUSY;
/* Clear any pending ECC interrupts */
writel(ALTR_A10_ECC_ERRPENA_MASK,
(ioaddr + ALTR_A10_ECC_INTSTAT_OFST));
return 0;
}
void socfpga_init_arria10_ocram_ecc(void)
{
struct device_node *np;
int ret = 0;
void __iomem *ecc_block_base;
if (!sys_manager_base_addr) {
pr_err("SOCFPGA: sys-mgr is not initialized\n");
return;
}
/* Find the OCRAM EDAC device tree node */
np = of_find_compatible_node(NULL, NULL, "altr,socfpga-a10-ocram-ecc");
if (!np) {
pr_err("Unable to find socfpga-a10-ocram-ecc\n");
return;
}
/* Map the ECC Block */
ecc_block_base = of_iomap(np, 0);
of_node_put(np);
if (!ecc_block_base) {
pr_err("Unable to map OCRAM ECC block\n");
return;
}
/* Disable ECC */
writel(ALTR_A10_OCRAM_ECC_EN_CTL,
sys_manager_base_addr + A10_SYSMGR_ECC_INTMASK_SET_OFST);
ecc_clear_bits(ALTR_A10_ECC_SERRINTEN,
(ecc_block_base + ALTR_A10_ECC_ERRINTEN_OFST));
ecc_clear_bits(ALTR_A10_OCRAM_ECC_EN_CTL,
(ecc_block_base + ALTR_A10_ECC_CTRL_OFST));
/* Ensure all writes complete */
wmb();
/* Use HW initialization block to initialize memory for ECC */
ret = altr_init_memory_port(ecc_block_base);
if (ret) {
pr_err("ECC: cannot init OCRAM PORTA memory\n");
goto exit;
}
/* Enable ECC */
ecc_set_bits(ALTR_A10_OCRAM_ECC_EN_CTL,
(ecc_block_base + ALTR_A10_ECC_CTRL_OFST));
ecc_set_bits(ALTR_A10_ECC_SERRINTEN,
(ecc_block_base + ALTR_A10_ECC_ERRINTEN_OFST));
writel(ALTR_A10_OCRAM_ECC_EN_CTL,
sys_manager_base_addr + A10_SYSMGR_ECC_INTMASK_CLR_OFST);
/* Ensure all writes complete */
wmb();
exit:
iounmap(ecc_block_base);
}

View File

@ -66,6 +66,16 @@ static void __init socfpga_init_irq(void)
socfpga_init_ocram_ecc(); socfpga_init_ocram_ecc();
} }
static void __init socfpga_arria10_init_irq(void)
{
irqchip_init();
socfpga_sysmgr_init();
if (IS_ENABLED(CONFIG_EDAC_ALTERA_L2C))
socfpga_init_arria10_l2_ecc();
if (IS_ENABLED(CONFIG_EDAC_ALTERA_OCRAM))
socfpga_init_arria10_ocram_ecc();
}
static void socfpga_cyclone5_restart(enum reboot_mode mode, const char *cmd) static void socfpga_cyclone5_restart(enum reboot_mode mode, const char *cmd)
{ {
u32 temp; u32 temp;
@ -113,7 +123,7 @@ static const char *altera_a10_dt_match[] = {
DT_MACHINE_START(SOCFPGA_A10, "Altera SOCFPGA Arria10") DT_MACHINE_START(SOCFPGA_A10, "Altera SOCFPGA Arria10")
.l2c_aux_val = 0, .l2c_aux_val = 0,
.l2c_aux_mask = ~0, .l2c_aux_mask = ~0,
.init_irq = socfpga_init_irq, .init_irq = socfpga_arria10_init_irq,
.restart = socfpga_arria10_restart, .restart = socfpga_arria10_restart,
.dt_compat = altera_a10_dt_match, .dt_compat = altera_a10_dt_match,
MACHINE_END MACHINE_END

View File

@ -378,12 +378,11 @@ config EDAC_ALTERA
config EDAC_ALTERA_L2C config EDAC_ALTERA_L2C
bool "Altera L2 Cache ECC" bool "Altera L2 Cache ECC"
depends on EDAC_ALTERA=y depends on EDAC_ALTERA=y && CACHE_L2X0
select CACHE_L2X0
help help
Support for error detection and correction on the Support for error detection and correction on the
Altera L2 cache Memory for Altera SoCs. This option Altera L2 cache Memory for Altera SoCs. This option
requires L2 cache so it will force that selection. requires L2 cache.
config EDAC_ALTERA_OCRAM config EDAC_ALTERA_OCRAM
bool "Altera On-Chip RAM ECC" bool "Altera On-Chip RAM ECC"

View File

@ -24,6 +24,7 @@
#include <linux/interrupt.h> #include <linux/interrupt.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/mfd/syscon.h> #include <linux/mfd/syscon.h>
#include <linux/of_address.h>
#include <linux/of_platform.h> #include <linux/of_platform.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/regmap.h> #include <linux/regmap.h>
@ -78,27 +79,6 @@ static const struct altr_sdram_prv_data a10_data = {
.ue_set_mask = A10_DIAGINT_TDERRA_MASK, .ue_set_mask = A10_DIAGINT_TDERRA_MASK,
}; };
/************************** EDAC Device Defines **************************/
/* OCRAM ECC Management Group Defines */
#define ALTR_MAN_GRP_OCRAM_ECC_OFFSET 0x04
#define ALTR_OCR_ECC_EN BIT(0)
#define ALTR_OCR_ECC_INJS BIT(1)
#define ALTR_OCR_ECC_INJD BIT(2)
#define ALTR_OCR_ECC_SERR BIT(3)
#define ALTR_OCR_ECC_DERR BIT(4)
/* L2 ECC Management Group Defines */
#define ALTR_MAN_GRP_L2_ECC_OFFSET 0x00
#define ALTR_L2_ECC_EN BIT(0)
#define ALTR_L2_ECC_INJS BIT(1)
#define ALTR_L2_ECC_INJD BIT(2)
#define ALTR_UE_TRIGGER_CHAR 'U' /* Trigger for UE */
#define ALTR_TRIGGER_READ_WRD_CNT 32 /* Line size x 4 */
#define ALTR_TRIG_OCRAM_BYTE_SIZE 128 /* Line size x 4 */
#define ALTR_TRIG_L2C_BYTE_SIZE 4096 /* Full Page */
/*********************** EDAC Memory Controller Functions ****************/ /*********************** EDAC Memory Controller Functions ****************/
/* The SDRAM controller uses the EDAC Memory Controller framework. */ /* The SDRAM controller uses the EDAC Memory Controller framework. */
@ -252,8 +232,8 @@ static unsigned long get_total_mem(void)
} }
static const struct of_device_id altr_sdram_ctrl_of_match[] = { static const struct of_device_id altr_sdram_ctrl_of_match[] = {
{ .compatible = "altr,sdram-edac", .data = (void *)&c5_data}, { .compatible = "altr,sdram-edac", .data = &c5_data},
{ .compatible = "altr,sdram-edac-a10", .data = (void *)&a10_data}, { .compatible = "altr,sdram-edac-a10", .data = &a10_data},
{}, {},
}; };
MODULE_DEVICE_TABLE(of, altr_sdram_ctrl_of_match); MODULE_DEVICE_TABLE(of, altr_sdram_ctrl_of_match);
@ -570,28 +550,8 @@ module_platform_driver(altr_edac_driver);
const struct edac_device_prv_data ocramecc_data; const struct edac_device_prv_data ocramecc_data;
const struct edac_device_prv_data l2ecc_data; const struct edac_device_prv_data l2ecc_data;
const struct edac_device_prv_data a10_ocramecc_data;
struct edac_device_prv_data { const struct edac_device_prv_data a10_l2ecc_data;
int (*setup)(struct platform_device *pdev, void __iomem *base);
int ce_clear_mask;
int ue_clear_mask;
char dbgfs_name[20];
void * (*alloc_mem)(size_t size, void **other);
void (*free_mem)(void *p, size_t size, void *other);
int ecc_enable_mask;
int ce_set_mask;
int ue_set_mask;
int trig_alloc_sz;
};
struct altr_edac_device_dev {
void __iomem *base;
int sb_irq;
int db_irq;
const struct edac_device_prv_data *data;
struct dentry *debugfs_dir;
char *edac_dev_name;
};
static irqreturn_t altr_edac_device_handler(int irq, void *dev_id) static irqreturn_t altr_edac_device_handler(int irq, void *dev_id)
{ {
@ -665,8 +625,9 @@ static ssize_t altr_edac_device_trig(struct file *file,
if (ACCESS_ONCE(ptemp[i])) if (ACCESS_ONCE(ptemp[i]))
result = -1; result = -1;
/* Toggle Error bit (it is latched), leave ECC enabled */ /* Toggle Error bit (it is latched), leave ECC enabled */
writel(error_mask, drvdata->base); writel(error_mask, (drvdata->base + priv->set_err_ofst));
writel(priv->ecc_enable_mask, drvdata->base); writel(priv->ecc_enable_mask, (drvdata->base +
priv->set_err_ofst));
ptemp[i] = i; ptemp[i] = i;
} }
/* Ensure it has been written out */ /* Ensure it has been written out */
@ -694,6 +655,16 @@ static const struct file_operations altr_edac_device_inject_fops = {
.llseek = generic_file_llseek, .llseek = generic_file_llseek,
}; };
static ssize_t altr_edac_a10_device_trig(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos);
static const struct file_operations altr_edac_a10_device_inject_fops = {
.open = simple_open,
.write = altr_edac_a10_device_trig,
.llseek = generic_file_llseek,
};
static void altr_create_edacdev_dbgfs(struct edac_device_ctl_info *edac_dci, static void altr_create_edacdev_dbgfs(struct edac_device_ctl_info *edac_dci,
const struct edac_device_prv_data *priv) const struct edac_device_prv_data *priv)
{ {
@ -708,17 +679,18 @@ static void altr_create_edacdev_dbgfs(struct edac_device_ctl_info *edac_dci,
if (!edac_debugfs_create_file(priv->dbgfs_name, S_IWUSR, if (!edac_debugfs_create_file(priv->dbgfs_name, S_IWUSR,
drvdata->debugfs_dir, edac_dci, drvdata->debugfs_dir, edac_dci,
&altr_edac_device_inject_fops)) priv->inject_fops))
debugfs_remove_recursive(drvdata->debugfs_dir); debugfs_remove_recursive(drvdata->debugfs_dir);
} }
static const struct of_device_id altr_edac_device_of_match[] = { static const struct of_device_id altr_edac_device_of_match[] = {
#ifdef CONFIG_EDAC_ALTERA_L2C #ifdef CONFIG_EDAC_ALTERA_L2C
{ .compatible = "altr,socfpga-l2-ecc", .data = (void *)&l2ecc_data }, { .compatible = "altr,socfpga-l2-ecc", .data = &l2ecc_data },
{ .compatible = "altr,socfpga-a10-l2-ecc", .data = &a10_l2ecc_data },
#endif #endif
#ifdef CONFIG_EDAC_ALTERA_OCRAM #ifdef CONFIG_EDAC_ALTERA_OCRAM
{ .compatible = "altr,socfpga-ocram-ecc", { .compatible = "altr,socfpga-ocram-ecc", .data = &ocramecc_data },
.data = (void *)&ocramecc_data }, { .compatible = "altr,socfpga-a10-ocram-ecc", .data = &a10_ocramecc_data },
#endif #endif
{}, {},
}; };
@ -789,7 +761,7 @@ static int altr_edac_device_probe(struct platform_device *pdev)
/* Check specific dependencies for the module */ /* Check specific dependencies for the module */
if (drvdata->data->setup) { if (drvdata->data->setup) {
res = drvdata->data->setup(pdev, drvdata->base); res = drvdata->data->setup(drvdata);
if (res) if (res)
goto fail1; goto fail1;
} }
@ -856,6 +828,25 @@ module_platform_driver(altr_edac_device_driver);
/*********************** OCRAM EDAC Device Functions *********************/ /*********************** OCRAM EDAC Device Functions *********************/
#ifdef CONFIG_EDAC_ALTERA_OCRAM #ifdef CONFIG_EDAC_ALTERA_OCRAM
/*
* Test for memory's ECC dependencies upon entry because platform specific
* startup should have initialized the memory and enabled the ECC.
* Can't turn on ECC here because accessing un-initialized memory will
* cause CE/UE errors possibly causing an ABORT.
*/
static int altr_check_ecc_deps(struct altr_edac_device_dev *device)
{
void __iomem *base = device->base;
const struct edac_device_prv_data *prv = device->data;
if (readl(base + prv->ecc_en_ofst) & prv->ecc_enable_mask)
return 0;
edac_printk(KERN_ERR, EDAC_DEVICE,
"%s: No ECC present or ECC disabled.\n",
device->edac_dev_name);
return -ENODEV;
}
static void *ocram_alloc_mem(size_t size, void **other) static void *ocram_alloc_mem(size_t size, void **other)
{ {
@ -891,36 +882,53 @@ static void ocram_free_mem(void *p, size_t size, void *other)
gen_pool_free((struct gen_pool *)other, (u32)p, size); gen_pool_free((struct gen_pool *)other, (u32)p, size);
} }
/* static irqreturn_t altr_edac_a10_ecc_irq(struct altr_edac_device_dev *dci,
* altr_ocram_check_deps() bool sberr)
* Test for OCRAM cache ECC dependencies upon entry because
* platform specific startup should have initialized the
* On-Chip RAM memory and enabled the ECC.
* Can't turn on ECC here because accessing un-initialized
* memory will cause CE/UE errors possibly causing an ABORT.
*/
static int altr_ocram_check_deps(struct platform_device *pdev,
void __iomem *base)
{ {
if (readl(base) & ALTR_OCR_ECC_EN) void __iomem *base = dci->base;
return 0;
edac_printk(KERN_ERR, EDAC_DEVICE, if (sberr) {
"OCRAM: No ECC present or ECC disabled.\n"); writel(ALTR_A10_ECC_SERRPENA,
return -ENODEV; base + ALTR_A10_ECC_INTSTAT_OFST);
edac_device_handle_ce(dci->edac_dev, 0, 0, dci->edac_dev_name);
} else {
writel(ALTR_A10_ECC_DERRPENA,
base + ALTR_A10_ECC_INTSTAT_OFST);
edac_device_handle_ue(dci->edac_dev, 0, 0, dci->edac_dev_name);
panic("\nEDAC:ECC_DEVICE[Uncorrectable errors]\n");
}
return IRQ_HANDLED;
} }
const struct edac_device_prv_data ocramecc_data = { const struct edac_device_prv_data ocramecc_data = {
.setup = altr_ocram_check_deps, .setup = altr_check_ecc_deps,
.ce_clear_mask = (ALTR_OCR_ECC_EN | ALTR_OCR_ECC_SERR), .ce_clear_mask = (ALTR_OCR_ECC_EN | ALTR_OCR_ECC_SERR),
.ue_clear_mask = (ALTR_OCR_ECC_EN | ALTR_OCR_ECC_DERR), .ue_clear_mask = (ALTR_OCR_ECC_EN | ALTR_OCR_ECC_DERR),
.dbgfs_name = "altr_ocram_trigger", .dbgfs_name = "altr_ocram_trigger",
.alloc_mem = ocram_alloc_mem, .alloc_mem = ocram_alloc_mem,
.free_mem = ocram_free_mem, .free_mem = ocram_free_mem,
.ecc_enable_mask = ALTR_OCR_ECC_EN, .ecc_enable_mask = ALTR_OCR_ECC_EN,
.ecc_en_ofst = ALTR_OCR_ECC_REG_OFFSET,
.ce_set_mask = (ALTR_OCR_ECC_EN | ALTR_OCR_ECC_INJS), .ce_set_mask = (ALTR_OCR_ECC_EN | ALTR_OCR_ECC_INJS),
.ue_set_mask = (ALTR_OCR_ECC_EN | ALTR_OCR_ECC_INJD), .ue_set_mask = (ALTR_OCR_ECC_EN | ALTR_OCR_ECC_INJD),
.set_err_ofst = ALTR_OCR_ECC_REG_OFFSET,
.trig_alloc_sz = ALTR_TRIG_OCRAM_BYTE_SIZE, .trig_alloc_sz = ALTR_TRIG_OCRAM_BYTE_SIZE,
.inject_fops = &altr_edac_device_inject_fops,
};
const struct edac_device_prv_data a10_ocramecc_data = {
.setup = altr_check_ecc_deps,
.ce_clear_mask = ALTR_A10_ECC_SERRPENA,
.ue_clear_mask = ALTR_A10_ECC_DERRPENA,
.irq_status_mask = A10_SYSMGR_ECC_INTSTAT_OCRAM,
.dbgfs_name = "altr_ocram_trigger",
.ecc_enable_mask = ALTR_A10_OCRAM_ECC_EN_CTL,
.ecc_en_ofst = ALTR_A10_ECC_CTRL_OFST,
.ce_set_mask = ALTR_A10_ECC_TSERRA,
.ue_set_mask = ALTR_A10_ECC_TDERRA,
.set_err_ofst = ALTR_A10_ECC_INTTEST_OFST,
.ecc_irq_handler = altr_edac_a10_ecc_irq,
.inject_fops = &altr_edac_a10_device_inject_fops,
}; };
#endif /* CONFIG_EDAC_ALTERA_OCRAM */ #endif /* CONFIG_EDAC_ALTERA_OCRAM */
@ -966,10 +974,13 @@ static void l2_free_mem(void *p, size_t size, void *other)
* Bail if ECC is not enabled. * Bail if ECC is not enabled.
* Note that L2 Cache Enable is forced at build time. * Note that L2 Cache Enable is forced at build time.
*/ */
static int altr_l2_check_deps(struct platform_device *pdev, static int altr_l2_check_deps(struct altr_edac_device_dev *device)
void __iomem *base)
{ {
if (readl(base) & ALTR_L2_ECC_EN) void __iomem *base = device->base;
const struct edac_device_prv_data *prv = device->data;
if ((readl(base) & prv->ecc_enable_mask) ==
prv->ecc_enable_mask)
return 0; return 0;
edac_printk(KERN_ERR, EDAC_DEVICE, edac_printk(KERN_ERR, EDAC_DEVICE,
@ -977,6 +988,24 @@ static int altr_l2_check_deps(struct platform_device *pdev,
return -ENODEV; return -ENODEV;
} }
static irqreturn_t altr_edac_a10_l2_irq(struct altr_edac_device_dev *dci,
bool sberr)
{
if (sberr) {
regmap_write(dci->edac->ecc_mgr_map,
A10_SYSGMR_MPU_CLEAR_L2_ECC_OFST,
A10_SYSGMR_MPU_CLEAR_L2_ECC_SB);
edac_device_handle_ce(dci->edac_dev, 0, 0, dci->edac_dev_name);
} else {
regmap_write(dci->edac->ecc_mgr_map,
A10_SYSGMR_MPU_CLEAR_L2_ECC_OFST,
A10_SYSGMR_MPU_CLEAR_L2_ECC_MB);
edac_device_handle_ue(dci->edac_dev, 0, 0, dci->edac_dev_name);
panic("\nEDAC:ECC_DEVICE[Uncorrectable errors]\n");
}
return IRQ_HANDLED;
}
const struct edac_device_prv_data l2ecc_data = { const struct edac_device_prv_data l2ecc_data = {
.setup = altr_l2_check_deps, .setup = altr_l2_check_deps,
.ce_clear_mask = 0, .ce_clear_mask = 0,
@ -987,11 +1016,252 @@ const struct edac_device_prv_data l2ecc_data = {
.ecc_enable_mask = ALTR_L2_ECC_EN, .ecc_enable_mask = ALTR_L2_ECC_EN,
.ce_set_mask = (ALTR_L2_ECC_EN | ALTR_L2_ECC_INJS), .ce_set_mask = (ALTR_L2_ECC_EN | ALTR_L2_ECC_INJS),
.ue_set_mask = (ALTR_L2_ECC_EN | ALTR_L2_ECC_INJD), .ue_set_mask = (ALTR_L2_ECC_EN | ALTR_L2_ECC_INJD),
.set_err_ofst = ALTR_L2_ECC_REG_OFFSET,
.trig_alloc_sz = ALTR_TRIG_L2C_BYTE_SIZE, .trig_alloc_sz = ALTR_TRIG_L2C_BYTE_SIZE,
.inject_fops = &altr_edac_device_inject_fops,
};
const struct edac_device_prv_data a10_l2ecc_data = {
.setup = altr_l2_check_deps,
.ce_clear_mask = ALTR_A10_L2_ECC_SERR_CLR,
.ue_clear_mask = ALTR_A10_L2_ECC_MERR_CLR,
.irq_status_mask = A10_SYSMGR_ECC_INTSTAT_L2,
.dbgfs_name = "altr_l2_trigger",
.alloc_mem = l2_alloc_mem,
.free_mem = l2_free_mem,
.ecc_enable_mask = ALTR_A10_L2_ECC_EN_CTL,
.ce_set_mask = ALTR_A10_L2_ECC_CE_INJ_MASK,
.ue_set_mask = ALTR_A10_L2_ECC_UE_INJ_MASK,
.set_err_ofst = ALTR_A10_L2_ECC_INJ_OFST,
.ecc_irq_handler = altr_edac_a10_l2_irq,
.trig_alloc_sz = ALTR_TRIG_L2C_BYTE_SIZE,
.inject_fops = &altr_edac_device_inject_fops,
}; };
#endif /* CONFIG_EDAC_ALTERA_L2C */ #endif /* CONFIG_EDAC_ALTERA_L2C */
/********************* Arria10 EDAC Device Functions *************************/
/*
* The Arria10 EDAC Device Functions differ from the Cyclone5/Arria5
* because 2 IRQs are shared among the all ECC peripherals. The ECC
* manager manages the IRQs and the children.
* Based on xgene_edac.c peripheral code.
*/
static ssize_t altr_edac_a10_device_trig(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct edac_device_ctl_info *edac_dci = file->private_data;
struct altr_edac_device_dev *drvdata = edac_dci->pvt_info;
const struct edac_device_prv_data *priv = drvdata->data;
void __iomem *set_addr = (drvdata->base + priv->set_err_ofst);
unsigned long flags;
u8 trig_type;
if (!user_buf || get_user(trig_type, user_buf))
return -EFAULT;
local_irq_save(flags);
if (trig_type == ALTR_UE_TRIGGER_CHAR)
writel(priv->ue_set_mask, set_addr);
else
writel(priv->ce_set_mask, set_addr);
/* Ensure the interrupt test bits are set */
wmb();
local_irq_restore(flags);
return count;
}
static irqreturn_t altr_edac_a10_irq_handler(int irq, void *dev_id)
{
irqreturn_t rc = IRQ_NONE;
struct altr_arria10_edac *edac = dev_id;
struct altr_edac_device_dev *dci;
int irq_status;
bool sberr = (irq == edac->sb_irq) ? 1 : 0;
int sm_offset = sberr ? A10_SYSMGR_ECC_INTSTAT_SERR_OFST :
A10_SYSMGR_ECC_INTSTAT_DERR_OFST;
regmap_read(edac->ecc_mgr_map, sm_offset, &irq_status);
if ((irq != edac->sb_irq) && (irq != edac->db_irq)) {
WARN_ON(1);
} else {
list_for_each_entry(dci, &edac->a10_ecc_devices, next) {
if (irq_status & dci->data->irq_status_mask)
rc = dci->data->ecc_irq_handler(dci, sberr);
}
}
return rc;
}
static int altr_edac_a10_device_add(struct altr_arria10_edac *edac,
struct device_node *np)
{
struct edac_device_ctl_info *dci;
struct altr_edac_device_dev *altdev;
char *ecc_name = (char *)np->name;
struct resource res;
int edac_idx;
int rc = 0;
const struct edac_device_prv_data *prv;
/* Get matching node and check for valid result */
const struct of_device_id *pdev_id =
of_match_node(altr_edac_device_of_match, np);
if (IS_ERR_OR_NULL(pdev_id))
return -ENODEV;
/* Get driver specific data for this EDAC device */
prv = pdev_id->data;
if (IS_ERR_OR_NULL(prv))
return -ENODEV;
if (!devres_open_group(edac->dev, altr_edac_a10_device_add, GFP_KERNEL))
return -ENOMEM;
rc = of_address_to_resource(np, 0, &res);
if (rc < 0) {
edac_printk(KERN_ERR, EDAC_DEVICE,
"%s: no resource address\n", ecc_name);
goto err_release_group;
}
edac_idx = edac_device_alloc_index();
dci = edac_device_alloc_ctl_info(sizeof(*altdev), ecc_name,
1, ecc_name, 1, 0, NULL, 0,
edac_idx);
if (!dci) {
edac_printk(KERN_ERR, EDAC_DEVICE,
"%s: Unable to allocate EDAC device\n", ecc_name);
rc = -ENOMEM;
goto err_release_group;
}
altdev = dci->pvt_info;
dci->dev = edac->dev;
altdev->edac_dev_name = ecc_name;
altdev->edac_idx = edac_idx;
altdev->edac = edac;
altdev->edac_dev = dci;
altdev->data = prv;
altdev->ddev = *edac->dev;
dci->dev = &altdev->ddev;
dci->ctl_name = "Altera ECC Manager";
dci->mod_name = ecc_name;
dci->dev_name = ecc_name;
altdev->base = devm_ioremap_resource(edac->dev, &res);
if (IS_ERR(altdev->base)) {
rc = PTR_ERR(altdev->base);
goto err_release_group1;
}
/* Check specific dependencies for the module */
if (altdev->data->setup) {
rc = altdev->data->setup(altdev);
if (rc)
goto err_release_group1;
}
rc = edac_device_add_device(dci);
if (rc) {
dev_err(edac->dev, "edac_device_add_device failed\n");
rc = -ENOMEM;
goto err_release_group1;
}
altr_create_edacdev_dbgfs(dci, prv);
list_add(&altdev->next, &edac->a10_ecc_devices);
devres_remove_group(edac->dev, altr_edac_a10_device_add);
return 0;
err_release_group1:
edac_device_free_ctl_info(dci);
err_release_group:
edac_printk(KERN_ALERT, EDAC_DEVICE, "%s: %d\n", __func__, __LINE__);
devres_release_group(edac->dev, NULL);
edac_printk(KERN_ERR, EDAC_DEVICE,
"%s:Error setting up EDAC device: %d\n", ecc_name, rc);
return rc;
}
static int altr_edac_a10_probe(struct platform_device *pdev)
{
struct altr_arria10_edac *edac;
struct device_node *child;
int rc;
edac = devm_kzalloc(&pdev->dev, sizeof(*edac), GFP_KERNEL);
if (!edac)
return -ENOMEM;
edac->dev = &pdev->dev;
platform_set_drvdata(pdev, edac);
INIT_LIST_HEAD(&edac->a10_ecc_devices);
edac->ecc_mgr_map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
"altr,sysmgr-syscon");
if (IS_ERR(edac->ecc_mgr_map)) {
edac_printk(KERN_ERR, EDAC_DEVICE,
"Unable to get syscon altr,sysmgr-syscon\n");
return PTR_ERR(edac->ecc_mgr_map);
}
edac->sb_irq = platform_get_irq(pdev, 0);
rc = devm_request_irq(&pdev->dev, edac->sb_irq,
altr_edac_a10_irq_handler,
IRQF_SHARED, dev_name(&pdev->dev), edac);
if (rc) {
edac_printk(KERN_ERR, EDAC_DEVICE, "No SBERR IRQ resource\n");
return rc;
}
edac->db_irq = platform_get_irq(pdev, 1);
rc = devm_request_irq(&pdev->dev, edac->db_irq,
altr_edac_a10_irq_handler,
IRQF_SHARED, dev_name(&pdev->dev), edac);
if (rc) {
edac_printk(KERN_ERR, EDAC_DEVICE, "No DBERR IRQ resource\n");
return rc;
}
for_each_child_of_node(pdev->dev.of_node, child) {
if (!of_device_is_available(child))
continue;
if (of_device_is_compatible(child, "altr,socfpga-a10-l2-ecc"))
altr_edac_a10_device_add(edac, child);
else if (of_device_is_compatible(child,
"altr,socfpga-a10-ocram-ecc"))
altr_edac_a10_device_add(edac, child);
}
return 0;
}
static const struct of_device_id altr_edac_a10_of_match[] = {
{ .compatible = "altr,socfpga-a10-ecc-manager" },
{},
};
MODULE_DEVICE_TABLE(of, altr_edac_a10_of_match);
static struct platform_driver altr_edac_a10_driver = {
.probe = altr_edac_a10_probe,
.driver = {
.name = "socfpga_a10_ecc_manager",
.of_match_table = altr_edac_a10_of_match,
},
};
module_platform_driver(altr_edac_a10_driver);
MODULE_LICENSE("GPL v2"); MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Thor Thayer"); MODULE_AUTHOR("Thor Thayer");
MODULE_DESCRIPTION("EDAC Driver for Altera Memories"); MODULE_DESCRIPTION("EDAC Driver for Altera Memories");

View File

@ -195,4 +195,132 @@ struct altr_sdram_mc_data {
const struct altr_sdram_prv_data *data; const struct altr_sdram_prv_data *data;
}; };
/************************** EDAC Device Defines **************************/
/***** General Device Trigger Defines *****/
#define ALTR_UE_TRIGGER_CHAR 'U' /* Trigger for UE */
#define ALTR_TRIGGER_READ_WRD_CNT 32 /* Line size x 4 */
#define ALTR_TRIG_OCRAM_BYTE_SIZE 128 /* Line size x 4 */
#define ALTR_TRIG_L2C_BYTE_SIZE 4096 /* Full Page */
/******* Cyclone5 and Arria5 Defines *******/
/* OCRAM ECC Management Group Defines */
#define ALTR_MAN_GRP_OCRAM_ECC_OFFSET 0x04
#define ALTR_OCR_ECC_REG_OFFSET 0x00
#define ALTR_OCR_ECC_EN BIT(0)
#define ALTR_OCR_ECC_INJS BIT(1)
#define ALTR_OCR_ECC_INJD BIT(2)
#define ALTR_OCR_ECC_SERR BIT(3)
#define ALTR_OCR_ECC_DERR BIT(4)
/* L2 ECC Management Group Defines */
#define ALTR_MAN_GRP_L2_ECC_OFFSET 0x00
#define ALTR_L2_ECC_REG_OFFSET 0x00
#define ALTR_L2_ECC_EN BIT(0)
#define ALTR_L2_ECC_INJS BIT(1)
#define ALTR_L2_ECC_INJD BIT(2)
/* Arria10 General ECC Block Module Defines */
#define ALTR_A10_ECC_CTRL_OFST 0x08
#define ALTR_A10_ECC_EN BIT(0)
#define ALTR_A10_ECC_INITA BIT(16)
#define ALTR_A10_ECC_INITB BIT(24)
#define ALTR_A10_ECC_INITSTAT_OFST 0x0C
#define ALTR_A10_ECC_INITCOMPLETEA BIT(0)
#define ALTR_A10_ECC_INITCOMPLETEB BIT(8)
#define ALTR_A10_ECC_ERRINTEN_OFST 0x10
#define ALTR_A10_ECC_SERRINTEN BIT(0)
#define ALTR_A10_ECC_INTSTAT_OFST 0x20
#define ALTR_A10_ECC_SERRPENA BIT(0)
#define ALTR_A10_ECC_DERRPENA BIT(8)
#define ALTR_A10_ECC_ERRPENA_MASK (ALTR_A10_ECC_SERRPENA | \
ALTR_A10_ECC_DERRPENA)
#define ALTR_A10_ECC_SERRPENB BIT(16)
#define ALTR_A10_ECC_DERRPENB BIT(24)
#define ALTR_A10_ECC_ERRPENB_MASK (ALTR_A10_ECC_SERRPENB | \
ALTR_A10_ECC_DERRPENB)
#define ALTR_A10_ECC_INTTEST_OFST 0x24
#define ALTR_A10_ECC_TSERRA BIT(0)
#define ALTR_A10_ECC_TDERRA BIT(8)
/* ECC Manager Defines */
#define A10_SYSMGR_ECC_INTMASK_SET_OFST 0x94
#define A10_SYSMGR_ECC_INTMASK_CLR_OFST 0x98
#define A10_SYSMGR_ECC_INTMASK_OCRAM BIT(1)
#define A10_SYSMGR_ECC_INTSTAT_SERR_OFST 0x9C
#define A10_SYSMGR_ECC_INTSTAT_DERR_OFST 0xA0
#define A10_SYSMGR_ECC_INTSTAT_L2 BIT(0)
#define A10_SYSMGR_ECC_INTSTAT_OCRAM BIT(1)
#define A10_SYSGMR_MPU_CLEAR_L2_ECC_OFST 0xA8
#define A10_SYSGMR_MPU_CLEAR_L2_ECC_SB BIT(15)
#define A10_SYSGMR_MPU_CLEAR_L2_ECC_MB BIT(31)
/* Arria 10 L2 ECC Management Group Defines */
#define ALTR_A10_L2_ECC_CTL_OFST 0x0
#define ALTR_A10_L2_ECC_EN_CTL BIT(0)
#define ALTR_A10_L2_ECC_STATUS 0xFFD060A4
#define ALTR_A10_L2_ECC_STAT_OFST 0xA4
#define ALTR_A10_L2_ECC_SERR_PEND BIT(0)
#define ALTR_A10_L2_ECC_MERR_PEND BIT(0)
#define ALTR_A10_L2_ECC_CLR_OFST 0x4
#define ALTR_A10_L2_ECC_SERR_CLR BIT(15)
#define ALTR_A10_L2_ECC_MERR_CLR BIT(31)
#define ALTR_A10_L2_ECC_INJ_OFST ALTR_A10_L2_ECC_CTL_OFST
#define ALTR_A10_L2_ECC_CE_INJ_MASK 0x00000101
#define ALTR_A10_L2_ECC_UE_INJ_MASK 0x00010101
/* Arria 10 OCRAM ECC Management Group Defines */
#define ALTR_A10_OCRAM_ECC_EN_CTL (BIT(1) | BIT(0))
struct altr_edac_device_dev;
struct edac_device_prv_data {
int (*setup)(struct altr_edac_device_dev *device);
int ce_clear_mask;
int ue_clear_mask;
int irq_status_mask;
char dbgfs_name[20];
void * (*alloc_mem)(size_t size, void **other);
void (*free_mem)(void *p, size_t size, void *other);
int ecc_enable_mask;
int ecc_en_ofst;
int ce_set_mask;
int ue_set_mask;
int set_err_ofst;
irqreturn_t (*ecc_irq_handler)(struct altr_edac_device_dev *dci,
bool sb);
int trig_alloc_sz;
const struct file_operations *inject_fops;
};
struct altr_edac_device_dev {
struct list_head next;
void __iomem *base;
int sb_irq;
int db_irq;
const struct edac_device_prv_data *data;
struct dentry *debugfs_dir;
char *edac_dev_name;
struct altr_arria10_edac *edac;
struct edac_device_ctl_info *edac_dev;
struct device ddev;
int edac_idx;
};
struct altr_arria10_edac {
struct device *dev;
struct regmap *ecc_mgr_map;
int sb_irq;
int db_irq;
struct list_head a10_ecc_devices;
};
#endif /* #ifndef _ALTERA_EDAC_H */ #endif /* #ifndef _ALTERA_EDAC_H */

View File

@ -15,11 +15,6 @@ module_param(ecc_enable_override, int, 0644);
static struct msr __percpu *msrs; static struct msr __percpu *msrs;
/*
* count successfully initialized driver instances for setup_pci_device()
*/
static atomic_t drv_instances = ATOMIC_INIT(0);
/* Per-node stuff */ /* Per-node stuff */
static struct ecc_settings **ecc_stngs; static struct ecc_settings **ecc_stngs;
@ -1918,7 +1913,7 @@ static struct amd64_family_type family_types[] = {
[K8_CPUS] = { [K8_CPUS] = {
.ctl_name = "K8", .ctl_name = "K8",
.f1_id = PCI_DEVICE_ID_AMD_K8_NB_ADDRMAP, .f1_id = PCI_DEVICE_ID_AMD_K8_NB_ADDRMAP,
.f3_id = PCI_DEVICE_ID_AMD_K8_NB_MISC, .f2_id = PCI_DEVICE_ID_AMD_K8_NB_MEMCTL,
.ops = { .ops = {
.early_channel_count = k8_early_channel_count, .early_channel_count = k8_early_channel_count,
.map_sysaddr_to_csrow = k8_map_sysaddr_to_csrow, .map_sysaddr_to_csrow = k8_map_sysaddr_to_csrow,
@ -1928,7 +1923,7 @@ static struct amd64_family_type family_types[] = {
[F10_CPUS] = { [F10_CPUS] = {
.ctl_name = "F10h", .ctl_name = "F10h",
.f1_id = PCI_DEVICE_ID_AMD_10H_NB_MAP, .f1_id = PCI_DEVICE_ID_AMD_10H_NB_MAP,
.f3_id = PCI_DEVICE_ID_AMD_10H_NB_MISC, .f2_id = PCI_DEVICE_ID_AMD_10H_NB_DRAM,
.ops = { .ops = {
.early_channel_count = f1x_early_channel_count, .early_channel_count = f1x_early_channel_count,
.map_sysaddr_to_csrow = f1x_map_sysaddr_to_csrow, .map_sysaddr_to_csrow = f1x_map_sysaddr_to_csrow,
@ -1938,7 +1933,7 @@ static struct amd64_family_type family_types[] = {
[F15_CPUS] = { [F15_CPUS] = {
.ctl_name = "F15h", .ctl_name = "F15h",
.f1_id = PCI_DEVICE_ID_AMD_15H_NB_F1, .f1_id = PCI_DEVICE_ID_AMD_15H_NB_F1,
.f3_id = PCI_DEVICE_ID_AMD_15H_NB_F3, .f2_id = PCI_DEVICE_ID_AMD_15H_NB_F2,
.ops = { .ops = {
.early_channel_count = f1x_early_channel_count, .early_channel_count = f1x_early_channel_count,
.map_sysaddr_to_csrow = f1x_map_sysaddr_to_csrow, .map_sysaddr_to_csrow = f1x_map_sysaddr_to_csrow,
@ -1948,7 +1943,7 @@ static struct amd64_family_type family_types[] = {
[F15_M30H_CPUS] = { [F15_M30H_CPUS] = {
.ctl_name = "F15h_M30h", .ctl_name = "F15h_M30h",
.f1_id = PCI_DEVICE_ID_AMD_15H_M30H_NB_F1, .f1_id = PCI_DEVICE_ID_AMD_15H_M30H_NB_F1,
.f3_id = PCI_DEVICE_ID_AMD_15H_M30H_NB_F3, .f2_id = PCI_DEVICE_ID_AMD_15H_M30H_NB_F2,
.ops = { .ops = {
.early_channel_count = f1x_early_channel_count, .early_channel_count = f1x_early_channel_count,
.map_sysaddr_to_csrow = f1x_map_sysaddr_to_csrow, .map_sysaddr_to_csrow = f1x_map_sysaddr_to_csrow,
@ -1958,7 +1953,7 @@ static struct amd64_family_type family_types[] = {
[F15_M60H_CPUS] = { [F15_M60H_CPUS] = {
.ctl_name = "F15h_M60h", .ctl_name = "F15h_M60h",
.f1_id = PCI_DEVICE_ID_AMD_15H_M60H_NB_F1, .f1_id = PCI_DEVICE_ID_AMD_15H_M60H_NB_F1,
.f3_id = PCI_DEVICE_ID_AMD_15H_M60H_NB_F3, .f2_id = PCI_DEVICE_ID_AMD_15H_M60H_NB_F2,
.ops = { .ops = {
.early_channel_count = f1x_early_channel_count, .early_channel_count = f1x_early_channel_count,
.map_sysaddr_to_csrow = f1x_map_sysaddr_to_csrow, .map_sysaddr_to_csrow = f1x_map_sysaddr_to_csrow,
@ -1968,7 +1963,7 @@ static struct amd64_family_type family_types[] = {
[F16_CPUS] = { [F16_CPUS] = {
.ctl_name = "F16h", .ctl_name = "F16h",
.f1_id = PCI_DEVICE_ID_AMD_16H_NB_F1, .f1_id = PCI_DEVICE_ID_AMD_16H_NB_F1,
.f3_id = PCI_DEVICE_ID_AMD_16H_NB_F3, .f2_id = PCI_DEVICE_ID_AMD_16H_NB_F2,
.ops = { .ops = {
.early_channel_count = f1x_early_channel_count, .early_channel_count = f1x_early_channel_count,
.map_sysaddr_to_csrow = f1x_map_sysaddr_to_csrow, .map_sysaddr_to_csrow = f1x_map_sysaddr_to_csrow,
@ -1978,7 +1973,7 @@ static struct amd64_family_type family_types[] = {
[F16_M30H_CPUS] = { [F16_M30H_CPUS] = {
.ctl_name = "F16h_M30h", .ctl_name = "F16h_M30h",
.f1_id = PCI_DEVICE_ID_AMD_16H_M30H_NB_F1, .f1_id = PCI_DEVICE_ID_AMD_16H_M30H_NB_F1,
.f3_id = PCI_DEVICE_ID_AMD_16H_M30H_NB_F3, .f2_id = PCI_DEVICE_ID_AMD_16H_M30H_NB_F2,
.ops = { .ops = {
.early_channel_count = f1x_early_channel_count, .early_channel_count = f1x_early_channel_count,
.map_sysaddr_to_csrow = f1x_map_sysaddr_to_csrow, .map_sysaddr_to_csrow = f1x_map_sysaddr_to_csrow,
@ -2227,13 +2222,13 @@ static inline void decode_bus_error(int node_id, struct mce *m)
} }
/* /*
* Use pvt->F2 which contains the F2 CPU PCI device to get the related * Use pvt->F3 which contains the F3 CPU PCI device to get the related
* F1 (AddrMap) and F3 (Misc) devices. Return negative value on error. * F1 (AddrMap) and F2 (Dct) devices. Return negative value on error.
*/ */
static int reserve_mc_sibling_devs(struct amd64_pvt *pvt, u16 f1_id, u16 f3_id) static int reserve_mc_sibling_devs(struct amd64_pvt *pvt, u16 f1_id, u16 f2_id)
{ {
/* Reserve the ADDRESS MAP Device */ /* Reserve the ADDRESS MAP Device */
pvt->F1 = pci_get_related_function(pvt->F2->vendor, f1_id, pvt->F2); pvt->F1 = pci_get_related_function(pvt->F3->vendor, f1_id, pvt->F3);
if (!pvt->F1) { if (!pvt->F1) {
amd64_err("error address map device not found: " amd64_err("error address map device not found: "
"vendor %x device 0x%x (broken BIOS?)\n", "vendor %x device 0x%x (broken BIOS?)\n",
@ -2241,15 +2236,15 @@ static int reserve_mc_sibling_devs(struct amd64_pvt *pvt, u16 f1_id, u16 f3_id)
return -ENODEV; return -ENODEV;
} }
/* Reserve the MISC Device */ /* Reserve the DCT Device */
pvt->F3 = pci_get_related_function(pvt->F2->vendor, f3_id, pvt->F2); pvt->F2 = pci_get_related_function(pvt->F3->vendor, f2_id, pvt->F3);
if (!pvt->F3) { if (!pvt->F2) {
pci_dev_put(pvt->F1); pci_dev_put(pvt->F1);
pvt->F1 = NULL; pvt->F1 = NULL;
amd64_err("error F3 device not found: " amd64_err("error F2 device not found: "
"vendor %x device 0x%x (broken BIOS?)\n", "vendor %x device 0x%x (broken BIOS?)\n",
PCI_VENDOR_ID_AMD, f3_id); PCI_VENDOR_ID_AMD, f2_id);
return -ENODEV; return -ENODEV;
} }
@ -2263,7 +2258,7 @@ static int reserve_mc_sibling_devs(struct amd64_pvt *pvt, u16 f1_id, u16 f3_id)
static void free_mc_sibling_devs(struct amd64_pvt *pvt) static void free_mc_sibling_devs(struct amd64_pvt *pvt)
{ {
pci_dev_put(pvt->F1); pci_dev_put(pvt->F1);
pci_dev_put(pvt->F3); pci_dev_put(pvt->F2);
} }
/* /*
@ -2778,14 +2773,14 @@ static const struct attribute_group *amd64_edac_attr_groups[] = {
NULL NULL
}; };
static int init_one_instance(struct pci_dev *F2) static int init_one_instance(unsigned int nid)
{ {
struct amd64_pvt *pvt = NULL; struct pci_dev *F3 = node_to_amd_nb(nid)->misc;
struct amd64_family_type *fam_type = NULL; struct amd64_family_type *fam_type = NULL;
struct mem_ctl_info *mci = NULL; struct mem_ctl_info *mci = NULL;
struct edac_mc_layer layers[2]; struct edac_mc_layer layers[2];
struct amd64_pvt *pvt = NULL;
int err = 0, ret; int err = 0, ret;
u16 nid = amd_pci_dev_to_node_id(F2);
ret = -ENOMEM; ret = -ENOMEM;
pvt = kzalloc(sizeof(struct amd64_pvt), GFP_KERNEL); pvt = kzalloc(sizeof(struct amd64_pvt), GFP_KERNEL);
@ -2793,7 +2788,7 @@ static int init_one_instance(struct pci_dev *F2)
goto err_ret; goto err_ret;
pvt->mc_node_id = nid; pvt->mc_node_id = nid;
pvt->F2 = F2; pvt->F3 = F3;
ret = -EINVAL; ret = -EINVAL;
fam_type = per_family_init(pvt); fam_type = per_family_init(pvt);
@ -2801,7 +2796,7 @@ static int init_one_instance(struct pci_dev *F2)
goto err_free; goto err_free;
ret = -ENODEV; ret = -ENODEV;
err = reserve_mc_sibling_devs(pvt, fam_type->f1_id, fam_type->f3_id); err = reserve_mc_sibling_devs(pvt, fam_type->f1_id, fam_type->f2_id);
if (err) if (err)
goto err_free; goto err_free;
@ -2836,7 +2831,7 @@ static int init_one_instance(struct pci_dev *F2)
goto err_siblings; goto err_siblings;
mci->pvt_info = pvt; mci->pvt_info = pvt;
mci->pdev = &pvt->F2->dev; mci->pdev = &pvt->F3->dev;
setup_mci_misc_attrs(mci, fam_type); setup_mci_misc_attrs(mci, fam_type);
@ -2855,8 +2850,6 @@ static int init_one_instance(struct pci_dev *F2)
amd_register_ecc_decoder(decode_bus_error); amd_register_ecc_decoder(decode_bus_error);
atomic_inc(&drv_instances);
return 0; return 0;
err_add_mc: err_add_mc:
@ -2872,19 +2865,11 @@ err_ret:
return ret; return ret;
} }
static int probe_one_instance(struct pci_dev *pdev, static int probe_one_instance(unsigned int nid)
const struct pci_device_id *mc_type)
{ {
u16 nid = amd_pci_dev_to_node_id(pdev);
struct pci_dev *F3 = node_to_amd_nb(nid)->misc; struct pci_dev *F3 = node_to_amd_nb(nid)->misc;
struct ecc_settings *s; struct ecc_settings *s;
int ret = 0; int ret;
ret = pci_enable_device(pdev);
if (ret < 0) {
edac_dbg(0, "ret=%d\n", ret);
return -EIO;
}
ret = -ENOMEM; ret = -ENOMEM;
s = kzalloc(sizeof(struct ecc_settings), GFP_KERNEL); s = kzalloc(sizeof(struct ecc_settings), GFP_KERNEL);
@ -2905,7 +2890,7 @@ static int probe_one_instance(struct pci_dev *pdev,
goto err_enable; goto err_enable;
} }
ret = init_one_instance(pdev); ret = init_one_instance(nid);
if (ret < 0) { if (ret < 0) {
amd64_err("Error probing instance: %d\n", nid); amd64_err("Error probing instance: %d\n", nid);
restore_ecc_error_reporting(s, nid, F3); restore_ecc_error_reporting(s, nid, F3);
@ -2921,19 +2906,18 @@ err_out:
return ret; return ret;
} }
static void remove_one_instance(struct pci_dev *pdev) static void remove_one_instance(unsigned int nid)
{ {
struct mem_ctl_info *mci;
struct amd64_pvt *pvt;
u16 nid = amd_pci_dev_to_node_id(pdev);
struct pci_dev *F3 = node_to_amd_nb(nid)->misc; struct pci_dev *F3 = node_to_amd_nb(nid)->misc;
struct ecc_settings *s = ecc_stngs[nid]; struct ecc_settings *s = ecc_stngs[nid];
struct mem_ctl_info *mci;
struct amd64_pvt *pvt;
mci = find_mci_by_dev(&pdev->dev); mci = find_mci_by_dev(&F3->dev);
WARN_ON(!mci); WARN_ON(!mci);
/* Remove from EDAC CORE tracking list */ /* Remove from EDAC CORE tracking list */
mci = edac_mc_del_mc(&pdev->dev); mci = edac_mc_del_mc(&F3->dev);
if (!mci) if (!mci)
return; return;
@ -2957,31 +2941,6 @@ static void remove_one_instance(struct pci_dev *pdev)
edac_mc_free(mci); 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[] = {
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_K8_NB_MEMCTL) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_10H_NB_DRAM) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_15H_NB_F2) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_15H_M30H_NB_F2) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_15H_M60H_NB_F2) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_16H_NB_F2) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_16H_M30H_NB_F2) },
{0, }
};
MODULE_DEVICE_TABLE(pci, amd64_pci_table);
static struct pci_driver amd64_pci_driver = {
.name = EDAC_MOD_STR,
.probe = probe_one_instance,
.remove = remove_one_instance,
.id_table = amd64_pci_table,
.driver.probe_type = PROBE_FORCE_SYNCHRONOUS,
};
static void setup_pci_device(void) static void setup_pci_device(void)
{ {
struct mem_ctl_info *mci; struct mem_ctl_info *mci;
@ -3005,8 +2964,7 @@ static void setup_pci_device(void)
static int __init amd64_edac_init(void) static int __init amd64_edac_init(void)
{ {
int err = -ENODEV; int err = -ENODEV;
int i;
printk(KERN_INFO "AMD64 EDAC driver v%s\n", EDAC_AMD64_VERSION);
opstate_init(); opstate_init();
@ -3022,13 +2980,14 @@ static int __init amd64_edac_init(void)
if (!msrs) if (!msrs)
goto err_free; goto err_free;
err = pci_register_driver(&amd64_pci_driver); for (i = 0; i < amd_nb_num(); i++)
if (err) if (probe_one_instance(i)) {
goto err_pci; /* unwind properly */
while (--i >= 0)
remove_one_instance(i);
err = -ENODEV; goto err_pci;
if (!atomic_read(&drv_instances)) }
goto err_no_instances;
setup_pci_device(); setup_pci_device();
@ -3036,10 +2995,9 @@ static int __init amd64_edac_init(void)
amd64_err("%s on 32-bit is unsupported. USE AT YOUR OWN RISK!\n", EDAC_MOD_STR); amd64_err("%s on 32-bit is unsupported. USE AT YOUR OWN RISK!\n", EDAC_MOD_STR);
#endif #endif
return 0; printk(KERN_INFO "AMD64 EDAC driver v%s\n", EDAC_AMD64_VERSION);
err_no_instances: return 0;
pci_unregister_driver(&amd64_pci_driver);
err_pci: err_pci:
msrs_free(msrs); msrs_free(msrs);
@ -3055,10 +3013,13 @@ err_ret:
static void __exit amd64_edac_exit(void) static void __exit amd64_edac_exit(void)
{ {
int i;
if (pci_ctl) if (pci_ctl)
edac_pci_release_generic_ctl(pci_ctl); edac_pci_release_generic_ctl(pci_ctl);
pci_unregister_driver(&amd64_pci_driver); for (i = 0; i < amd_nb_num(); i++)
remove_one_instance(i);
kfree(ecc_stngs); kfree(ecc_stngs);
ecc_stngs = NULL; ecc_stngs = NULL;

View File

@ -422,7 +422,7 @@ struct low_ops {
struct amd64_family_type { struct amd64_family_type {
const char *ctl_name; const char *ctl_name;
u16 f1_id, f3_id; u16 f1_id, f2_id;
struct low_ops ops; struct low_ops ops;
}; };

View File

@ -923,7 +923,7 @@ static void edac_inc_ue_error(struct mem_ctl_info *mci,
mci->ue_mc += count; mci->ue_mc += count;
if (!enable_per_layer_report) { if (!enable_per_layer_report) {
mci->ce_noinfo_count += count; mci->ue_noinfo_count += count;
return; return;
} }

View File

@ -998,11 +998,12 @@ void edac_remove_sysfs_mci_device(struct mem_ctl_info *mci)
void edac_unregister_sysfs(struct mem_ctl_info *mci) void edac_unregister_sysfs(struct mem_ctl_info *mci)
{ {
struct bus_type *bus = mci->bus;
const char *name = mci->bus->name; const char *name = mci->bus->name;
edac_dbg(1, "Unregistering device %s\n", dev_name(&mci->dev)); edac_dbg(1, "Unregistering device %s\n", dev_name(&mci->dev));
device_unregister(&mci->dev); device_unregister(&mci->dev);
bus_unregister(mci->bus); bus_unregister(bus);
kfree(name); kfree(name);
} }

View File

@ -271,16 +271,6 @@ struct i7core_pvt {
bool is_registered, enable_scrub; bool is_registered, enable_scrub;
/* Fifo double buffers */
struct mce mce_entry[MCE_LOG_LEN];
struct mce mce_outentry[MCE_LOG_LEN];
/* Fifo in/out counters */
unsigned mce_in, mce_out;
/* Count indicator to show errors not got */
unsigned mce_overrun;
/* DCLK Frequency used for computing scrub rate */ /* DCLK Frequency used for computing scrub rate */
int dclk_freq; int dclk_freq;
@ -1792,56 +1782,15 @@ static void i7core_mce_output_error(struct mem_ctl_info *mci,
* i7core_check_error Retrieve and process errors reported by the * i7core_check_error Retrieve and process errors reported by the
* hardware. Called by the Core module. * hardware. Called by the Core module.
*/ */
static void i7core_check_error(struct mem_ctl_info *mci) static void i7core_check_error(struct mem_ctl_info *mci, struct mce *m)
{ {
struct i7core_pvt *pvt = mci->pvt_info; struct i7core_pvt *pvt = mci->pvt_info;
int i;
unsigned count = 0;
struct mce *m;
/* i7core_mce_output_error(mci, m);
* MCE first step: Copy all mce errors into a temporary buffer
* We use a double buffering here, to reduce the risk of
* losing an error.
*/
smp_rmb();
count = (pvt->mce_out + MCE_LOG_LEN - pvt->mce_in)
% MCE_LOG_LEN;
if (!count)
goto check_ce_error;
m = pvt->mce_outentry;
if (pvt->mce_in + count > MCE_LOG_LEN) {
unsigned l = MCE_LOG_LEN - pvt->mce_in;
memcpy(m, &pvt->mce_entry[pvt->mce_in], sizeof(*m) * l);
smp_wmb();
pvt->mce_in = 0;
count -= l;
m += l;
}
memcpy(m, &pvt->mce_entry[pvt->mce_in], sizeof(*m) * count);
smp_wmb();
pvt->mce_in += count;
smp_rmb();
if (pvt->mce_overrun) {
i7core_printk(KERN_ERR, "Lost %d memory errors\n",
pvt->mce_overrun);
smp_wmb();
pvt->mce_overrun = 0;
}
/*
* MCE second step: parse errors and display
*/
for (i = 0; i < count; i++)
i7core_mce_output_error(mci, &pvt->mce_outentry[i]);
/* /*
* Now, let's increment CE error counts * Now, let's increment CE error counts
*/ */
check_ce_error:
if (!pvt->is_registered) if (!pvt->is_registered)
i7core_udimm_check_mc_ecc_err(mci); i7core_udimm_check_mc_ecc_err(mci);
else else
@ -1849,12 +1798,8 @@ check_ce_error:
} }
/* /*
* i7core_mce_check_error Replicates mcelog routine to get errors * Check that logging is enabled and that this is the right type
* This routine simply queues mcelog errors, and * of error for us to handle.
* return. The error itself should be handled later
* by i7core_check_error.
* WARNING: As this routine should be called at NMI time, extra care should
* be taken to avoid deadlocks, and to be as fast as possible.
*/ */
static int i7core_mce_check_error(struct notifier_block *nb, unsigned long val, static int i7core_mce_check_error(struct notifier_block *nb, unsigned long val,
void *data) void *data)
@ -1882,21 +1827,7 @@ static int i7core_mce_check_error(struct notifier_block *nb, unsigned long val,
if (mce->bank != 8) if (mce->bank != 8)
return NOTIFY_DONE; return NOTIFY_DONE;
smp_rmb(); i7core_check_error(mci, mce);
if ((pvt->mce_out + 1) % MCE_LOG_LEN == pvt->mce_in) {
smp_wmb();
pvt->mce_overrun++;
return NOTIFY_DONE;
}
/* Copy memory error at the ringbuffer */
memcpy(&pvt->mce_entry[pvt->mce_out], mce, sizeof(*mce));
smp_wmb();
pvt->mce_out = (pvt->mce_out + 1) % MCE_LOG_LEN;
/* Handle fatal errors immediately */
if (mce->mcgstatus & 1)
i7core_check_error(mci);
/* Advise mcelog that the errors were handled */ /* Advise mcelog that the errors were handled */
return NOTIFY_STOP; return NOTIFY_STOP;
@ -2243,8 +2174,6 @@ static int i7core_register_mci(struct i7core_dev *i7core_dev)
get_dimm_config(mci); get_dimm_config(mci);
/* record ptr to the generic device */ /* record ptr to the generic device */
mci->pdev = &i7core_dev->pdev[0]->dev; mci->pdev = &i7core_dev->pdev[0]->dev;
/* Set the function pointer to an actual operation function */
mci->edac_check = i7core_check_error;
/* Enable scrubrate setting */ /* Enable scrubrate setting */
if (pvt->enable_scrub) if (pvt->enable_scrub)

View File

@ -17,6 +17,7 @@
* 015c: Xeon E3-1200 v2/3rd Gen Core processor DRAM Controller * 015c: Xeon E3-1200 v2/3rd Gen Core processor DRAM Controller
* 0c04: Xeon E3-1200 v3/4th Gen Core Processor DRAM Controller * 0c04: Xeon E3-1200 v3/4th Gen Core Processor DRAM Controller
* 0c08: Xeon E3-1200 v3 Processor DRAM Controller * 0c08: Xeon E3-1200 v3 Processor DRAM Controller
* 1918: Xeon E3-1200 v5 Skylake Host Bridge/DRAM Registers
* *
* Based on Intel specification: * Based on Intel specification:
* http://www.intel.com/content/dam/www/public/us/en/documents/datasheets/xeon-e3-1200v3-vol-2-datasheet.pdf * http://www.intel.com/content/dam/www/public/us/en/documents/datasheets/xeon-e3-1200v3-vol-2-datasheet.pdf
@ -55,6 +56,7 @@
#define PCI_DEVICE_ID_INTEL_IE31200_HB_5 0x015c #define PCI_DEVICE_ID_INTEL_IE31200_HB_5 0x015c
#define PCI_DEVICE_ID_INTEL_IE31200_HB_6 0x0c04 #define PCI_DEVICE_ID_INTEL_IE31200_HB_6 0x0c04
#define PCI_DEVICE_ID_INTEL_IE31200_HB_7 0x0c08 #define PCI_DEVICE_ID_INTEL_IE31200_HB_7 0x0c08
#define PCI_DEVICE_ID_INTEL_IE31200_HB_8 0x1918
#define IE31200_DIMMS 4 #define IE31200_DIMMS 4
#define IE31200_RANKS 8 #define IE31200_RANKS 8
@ -105,8 +107,11 @@
* 1 Multiple Bit Error Status (MERRSTS) * 1 Multiple Bit Error Status (MERRSTS)
* 0 Correctable Error Status (CERRSTS) * 0 Correctable Error Status (CERRSTS)
*/ */
#define IE31200_C0ECCERRLOG 0x40c8 #define IE31200_C0ECCERRLOG 0x40c8
#define IE31200_C1ECCERRLOG 0x44c8 #define IE31200_C1ECCERRLOG 0x44c8
#define IE31200_C0ECCERRLOG_SKL 0x4048
#define IE31200_C1ECCERRLOG_SKL 0x4448
#define IE31200_ECCERRLOG_CE BIT(0) #define IE31200_ECCERRLOG_CE BIT(0)
#define IE31200_ECCERRLOG_UE BIT(1) #define IE31200_ECCERRLOG_UE BIT(1)
#define IE31200_ECCERRLOG_RANK_BITS GENMASK_ULL(28, 27) #define IE31200_ECCERRLOG_RANK_BITS GENMASK_ULL(28, 27)
@ -123,17 +128,28 @@
#define IE31200_CAPID0_DDPCD BIT(6) #define IE31200_CAPID0_DDPCD BIT(6)
#define IE31200_CAPID0_ECC BIT(1) #define IE31200_CAPID0_ECC BIT(1)
#define IE31200_MAD_DIMM_0_OFFSET 0x5004 #define IE31200_MAD_DIMM_0_OFFSET 0x5004
#define IE31200_MAD_DIMM_SIZE GENMASK_ULL(7, 0) #define IE31200_MAD_DIMM_0_OFFSET_SKL 0x500C
#define IE31200_MAD_DIMM_A_RANK BIT(17) #define IE31200_MAD_DIMM_SIZE GENMASK_ULL(7, 0)
#define IE31200_MAD_DIMM_A_WIDTH BIT(19) #define IE31200_MAD_DIMM_A_RANK BIT(17)
#define IE31200_MAD_DIMM_A_RANK_SHIFT 17
#define IE31200_MAD_DIMM_A_RANK_SKL BIT(10)
#define IE31200_MAD_DIMM_A_RANK_SKL_SHIFT 10
#define IE31200_MAD_DIMM_A_WIDTH BIT(19)
#define IE31200_MAD_DIMM_A_WIDTH_SHIFT 19
#define IE31200_MAD_DIMM_A_WIDTH_SKL GENMASK_ULL(9, 8)
#define IE31200_MAD_DIMM_A_WIDTH_SKL_SHIFT 8
#define IE31200_PAGES(n) (n << (28 - PAGE_SHIFT)) /* Skylake reports 1GB increments, everything else is 256MB */
#define IE31200_PAGES(n, skl) \
(n << (28 + (2 * skl) - PAGE_SHIFT))
static int nr_channels; static int nr_channels;
struct ie31200_priv { struct ie31200_priv {
void __iomem *window; void __iomem *window;
void __iomem *c0errlog;
void __iomem *c1errlog;
}; };
enum ie31200_chips { enum ie31200_chips {
@ -157,9 +173,9 @@ static const struct ie31200_dev_info ie31200_devs[] = {
}; };
struct dimm_data { struct dimm_data {
u8 size; /* in 256MB multiples */ u8 size; /* in multiples of 256MB, except Skylake is 1GB */
u8 dual_rank : 1, u8 dual_rank : 1,
x16_width : 1; /* 0 means x8 width */ x16_width : 2; /* 0 means x8 width */
}; };
static int how_many_channels(struct pci_dev *pdev) static int how_many_channels(struct pci_dev *pdev)
@ -197,11 +213,10 @@ static bool ecc_capable(struct pci_dev *pdev)
return true; return true;
} }
static int eccerrlog_row(int channel, u64 log) static int eccerrlog_row(u64 log)
{ {
int rank = ((log & IE31200_ECCERRLOG_RANK_BITS) >> return ((log & IE31200_ECCERRLOG_RANK_BITS) >>
IE31200_ECCERRLOG_RANK_SHIFT); IE31200_ECCERRLOG_RANK_SHIFT);
return rank | (channel * IE31200_RANKS_PER_CHANNEL);
} }
static void ie31200_clear_error_info(struct mem_ctl_info *mci) static void ie31200_clear_error_info(struct mem_ctl_info *mci)
@ -219,7 +234,6 @@ static void ie31200_get_and_clear_error_info(struct mem_ctl_info *mci,
{ {
struct pci_dev *pdev; struct pci_dev *pdev;
struct ie31200_priv *priv = mci->pvt_info; struct ie31200_priv *priv = mci->pvt_info;
void __iomem *window = priv->window;
pdev = to_pci_dev(mci->pdev); pdev = to_pci_dev(mci->pdev);
@ -232,9 +246,9 @@ static void ie31200_get_and_clear_error_info(struct mem_ctl_info *mci,
if (!(info->errsts & IE31200_ERRSTS_BITS)) if (!(info->errsts & IE31200_ERRSTS_BITS))
return; return;
info->eccerrlog[0] = lo_hi_readq(window + IE31200_C0ECCERRLOG); info->eccerrlog[0] = lo_hi_readq(priv->c0errlog);
if (nr_channels == 2) if (nr_channels == 2)
info->eccerrlog[1] = lo_hi_readq(window + IE31200_C1ECCERRLOG); info->eccerrlog[1] = lo_hi_readq(priv->c1errlog);
pci_read_config_word(pdev, IE31200_ERRSTS, &info->errsts2); pci_read_config_word(pdev, IE31200_ERRSTS, &info->errsts2);
@ -245,10 +259,10 @@ static void ie31200_get_and_clear_error_info(struct mem_ctl_info *mci,
* should be UE info. * should be UE info.
*/ */
if ((info->errsts ^ info->errsts2) & IE31200_ERRSTS_BITS) { if ((info->errsts ^ info->errsts2) & IE31200_ERRSTS_BITS) {
info->eccerrlog[0] = lo_hi_readq(window + IE31200_C0ECCERRLOG); info->eccerrlog[0] = lo_hi_readq(priv->c0errlog);
if (nr_channels == 2) if (nr_channels == 2)
info->eccerrlog[1] = info->eccerrlog[1] =
lo_hi_readq(window + IE31200_C1ECCERRLOG); lo_hi_readq(priv->c1errlog);
} }
ie31200_clear_error_info(mci); ie31200_clear_error_info(mci);
@ -274,14 +288,14 @@ static void ie31200_process_error_info(struct mem_ctl_info *mci,
if (log & IE31200_ECCERRLOG_UE) { if (log & IE31200_ECCERRLOG_UE) {
edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 1, edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 1,
0, 0, 0, 0, 0, 0,
eccerrlog_row(channel, log), eccerrlog_row(log),
channel, -1, channel, -1,
"ie31200 UE", ""); "ie31200 UE", "");
} else if (log & IE31200_ECCERRLOG_CE) { } else if (log & IE31200_ECCERRLOG_CE) {
edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, 1, edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, 1,
0, 0, 0, 0,
IE31200_ECCERRLOG_SYNDROME(log), IE31200_ECCERRLOG_SYNDROME(log),
eccerrlog_row(channel, log), eccerrlog_row(log),
channel, -1, channel, -1,
"ie31200 CE", ""); "ie31200 CE", "");
} }
@ -326,6 +340,33 @@ static void __iomem *ie31200_map_mchbar(struct pci_dev *pdev)
return window; return window;
} }
static void __skl_populate_dimm_info(struct dimm_data *dd, u32 addr_decode,
int chan)
{
dd->size = (addr_decode >> (chan << 4)) & IE31200_MAD_DIMM_SIZE;
dd->dual_rank = (addr_decode & (IE31200_MAD_DIMM_A_RANK_SKL << (chan << 4))) ? 1 : 0;
dd->x16_width = ((addr_decode & (IE31200_MAD_DIMM_A_WIDTH_SKL << (chan << 4))) >>
(IE31200_MAD_DIMM_A_WIDTH_SKL_SHIFT + (chan << 4)));
}
static void __populate_dimm_info(struct dimm_data *dd, u32 addr_decode,
int chan)
{
dd->size = (addr_decode >> (chan << 3)) & IE31200_MAD_DIMM_SIZE;
dd->dual_rank = (addr_decode & (IE31200_MAD_DIMM_A_RANK << chan)) ? 1 : 0;
dd->x16_width = (addr_decode & (IE31200_MAD_DIMM_A_WIDTH << chan)) ? 1 : 0;
}
static void populate_dimm_info(struct dimm_data *dd, u32 addr_decode, int chan,
bool skl)
{
if (skl)
__skl_populate_dimm_info(dd, addr_decode, chan);
else
__populate_dimm_info(dd, addr_decode, chan);
}
static int ie31200_probe1(struct pci_dev *pdev, int dev_idx) static int ie31200_probe1(struct pci_dev *pdev, int dev_idx)
{ {
int i, j, ret; int i, j, ret;
@ -334,7 +375,8 @@ static int ie31200_probe1(struct pci_dev *pdev, int dev_idx)
struct dimm_data dimm_info[IE31200_CHANNELS][IE31200_DIMMS_PER_CHANNEL]; struct dimm_data dimm_info[IE31200_CHANNELS][IE31200_DIMMS_PER_CHANNEL];
void __iomem *window; void __iomem *window;
struct ie31200_priv *priv; struct ie31200_priv *priv;
u32 addr_decode; u32 addr_decode, mad_offset;
bool skl = (pdev->device == PCI_DEVICE_ID_INTEL_IE31200_HB_8);
edac_dbg(0, "MC:\n"); edac_dbg(0, "MC:\n");
@ -363,7 +405,10 @@ static int ie31200_probe1(struct pci_dev *pdev, int dev_idx)
edac_dbg(3, "MC: init mci\n"); edac_dbg(3, "MC: init mci\n");
mci->pdev = &pdev->dev; mci->pdev = &pdev->dev;
mci->mtype_cap = MEM_FLAG_DDR3; if (skl)
mci->mtype_cap = MEM_FLAG_DDR4;
else
mci->mtype_cap = MEM_FLAG_DDR3;
mci->edac_ctl_cap = EDAC_FLAG_SECDED; mci->edac_ctl_cap = EDAC_FLAG_SECDED;
mci->edac_cap = EDAC_FLAG_SECDED; mci->edac_cap = EDAC_FLAG_SECDED;
mci->mod_name = EDAC_MOD_STR; mci->mod_name = EDAC_MOD_STR;
@ -374,19 +419,24 @@ static int ie31200_probe1(struct pci_dev *pdev, int dev_idx)
mci->ctl_page_to_phys = NULL; mci->ctl_page_to_phys = NULL;
priv = mci->pvt_info; priv = mci->pvt_info;
priv->window = window; priv->window = window;
if (skl) {
priv->c0errlog = window + IE31200_C0ECCERRLOG_SKL;
priv->c1errlog = window + IE31200_C1ECCERRLOG_SKL;
mad_offset = IE31200_MAD_DIMM_0_OFFSET_SKL;
} else {
priv->c0errlog = window + IE31200_C0ECCERRLOG;
priv->c1errlog = window + IE31200_C1ECCERRLOG;
mad_offset = IE31200_MAD_DIMM_0_OFFSET;
}
/* populate DIMM info */ /* populate DIMM info */
for (i = 0; i < IE31200_CHANNELS; i++) { for (i = 0; i < IE31200_CHANNELS; i++) {
addr_decode = readl(window + IE31200_MAD_DIMM_0_OFFSET + addr_decode = readl(window + mad_offset +
(i * 4)); (i * 4));
edac_dbg(0, "addr_decode: 0x%x\n", addr_decode); edac_dbg(0, "addr_decode: 0x%x\n", addr_decode);
for (j = 0; j < IE31200_DIMMS_PER_CHANNEL; j++) { for (j = 0; j < IE31200_DIMMS_PER_CHANNEL; j++) {
dimm_info[i][j].size = (addr_decode >> (j * 8)) & populate_dimm_info(&dimm_info[i][j], addr_decode, j,
IE31200_MAD_DIMM_SIZE; skl);
dimm_info[i][j].dual_rank = (addr_decode &
(IE31200_MAD_DIMM_A_RANK << j)) ? 1 : 0;
dimm_info[i][j].x16_width = (addr_decode &
(IE31200_MAD_DIMM_A_WIDTH << j)) ? 1 : 0;
edac_dbg(0, "size: 0x%x, rank: %d, width: %d\n", edac_dbg(0, "size: 0x%x, rank: %d, width: %d\n",
dimm_info[i][j].size, dimm_info[i][j].size,
dimm_info[i][j].dual_rank, dimm_info[i][j].dual_rank,
@ -405,7 +455,7 @@ static int ie31200_probe1(struct pci_dev *pdev, int dev_idx)
struct dimm_info *dimm; struct dimm_info *dimm;
unsigned long nr_pages; unsigned long nr_pages;
nr_pages = IE31200_PAGES(dimm_info[j][i].size); nr_pages = IE31200_PAGES(dimm_info[j][i].size, skl);
if (nr_pages == 0) if (nr_pages == 0)
continue; continue;
@ -417,7 +467,10 @@ static int ie31200_probe1(struct pci_dev *pdev, int dev_idx)
dimm->nr_pages = nr_pages; dimm->nr_pages = nr_pages;
edac_dbg(0, "set nr pages: 0x%lx\n", nr_pages); edac_dbg(0, "set nr pages: 0x%lx\n", nr_pages);
dimm->grain = 8; /* just a guess */ dimm->grain = 8; /* just a guess */
dimm->mtype = MEM_DDR3; if (skl)
dimm->mtype = MEM_DDR4;
else
dimm->mtype = MEM_DDR3;
dimm->dtype = DEV_UNKNOWN; dimm->dtype = DEV_UNKNOWN;
dimm->edac_mode = EDAC_UNKNOWN; dimm->edac_mode = EDAC_UNKNOWN;
} }
@ -426,7 +479,10 @@ static int ie31200_probe1(struct pci_dev *pdev, int dev_idx)
dimm->nr_pages = nr_pages; dimm->nr_pages = nr_pages;
edac_dbg(0, "set nr pages: 0x%lx\n", nr_pages); edac_dbg(0, "set nr pages: 0x%lx\n", nr_pages);
dimm->grain = 8; /* same guess */ dimm->grain = 8; /* same guess */
dimm->mtype = MEM_DDR3; if (skl)
dimm->mtype = MEM_DDR4;
else
dimm->mtype = MEM_DDR3;
dimm->dtype = DEV_UNKNOWN; dimm->dtype = DEV_UNKNOWN;
dimm->edac_mode = EDAC_UNKNOWN; dimm->edac_mode = EDAC_UNKNOWN;
} }
@ -500,6 +556,9 @@ static const struct pci_device_id ie31200_pci_tbl[] = {
{ {
PCI_VEND_DEV(INTEL, IE31200_HB_7), PCI_ANY_ID, PCI_ANY_ID, 0, 0, PCI_VEND_DEV(INTEL, IE31200_HB_7), PCI_ANY_ID, PCI_ANY_ID, 0, 0,
IE31200}, IE31200},
{
PCI_VEND_DEV(INTEL, IE31200_HB_8), PCI_ANY_ID, PCI_ANY_ID, 0, 0,
IE31200},
{ {
0, 0,
} /* 0 terminated list. */ } /* 0 terminated list. */

View File

@ -21,6 +21,8 @@
#include <linux/smp.h> #include <linux/smp.h>
#include <linux/bitmap.h> #include <linux/bitmap.h>
#include <linux/math64.h> #include <linux/math64.h>
#include <linux/mod_devicetable.h>
#include <asm/cpu_device_id.h>
#include <asm/processor.h> #include <asm/processor.h>
#include <asm/mce.h> #include <asm/mce.h>
@ -28,8 +30,6 @@
/* Static vars */ /* Static vars */
static LIST_HEAD(sbridge_edac_list); static LIST_HEAD(sbridge_edac_list);
static DEFINE_MUTEX(sbridge_edac_lock);
static int probed;
/* /*
* Alter this version for the module when modifications are made * Alter this version for the module when modifications are made
@ -364,16 +364,6 @@ struct sbridge_pvt {
bool is_mirrored, is_lockstep, is_close_pg; bool is_mirrored, is_lockstep, is_close_pg;
bool is_chan_hash; bool is_chan_hash;
/* Fifo double buffers */
struct mce mce_entry[MCE_LOG_LEN];
struct mce mce_outentry[MCE_LOG_LEN];
/* Fifo in/out counters */
unsigned mce_in, mce_out;
/* Count indicator to show errors not got */
unsigned mce_overrun;
/* Memory description */ /* Memory description */
u64 tolm, tohm; u64 tolm, tohm;
struct knl_pvt knl; struct knl_pvt knl;
@ -662,18 +652,6 @@ static const struct pci_id_table pci_dev_descr_broadwell_table[] = {
{0,} /* 0 terminated list. */ {0,} /* 0 terminated list. */
}; };
/*
* pci_device_id table for which devices we are looking for
*/
static const struct pci_device_id sbridge_pci_tbl[] = {
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_SBRIDGE_IMC_HA0)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_IBRIDGE_IMC_HA0_TA)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_HASWELL_IMC_HA0)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_BROADWELL_IMC_HA0)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_KNL_IMC_SAD0)},
{0,} /* 0 terminated list. */
};
/**************************************************************************** /****************************************************************************
Ancillary status routines Ancillary status routines
@ -3097,63 +3075,8 @@ err_parsing:
} }
/* /*
* sbridge_check_error Retrieve and process errors reported by the * Check that logging is enabled and that this is the right type
* hardware. Called by the Core module. * of error for us to handle.
*/
static void sbridge_check_error(struct mem_ctl_info *mci)
{
struct sbridge_pvt *pvt = mci->pvt_info;
int i;
unsigned count = 0;
struct mce *m;
/*
* MCE first step: Copy all mce errors into a temporary buffer
* We use a double buffering here, to reduce the risk of
* loosing an error.
*/
smp_rmb();
count = (pvt->mce_out + MCE_LOG_LEN - pvt->mce_in)
% MCE_LOG_LEN;
if (!count)
return;
m = pvt->mce_outentry;
if (pvt->mce_in + count > MCE_LOG_LEN) {
unsigned l = MCE_LOG_LEN - pvt->mce_in;
memcpy(m, &pvt->mce_entry[pvt->mce_in], sizeof(*m) * l);
smp_wmb();
pvt->mce_in = 0;
count -= l;
m += l;
}
memcpy(m, &pvt->mce_entry[pvt->mce_in], sizeof(*m) * count);
smp_wmb();
pvt->mce_in += count;
smp_rmb();
if (pvt->mce_overrun) {
sbridge_printk(KERN_ERR, "Lost %d memory errors\n",
pvt->mce_overrun);
smp_wmb();
pvt->mce_overrun = 0;
}
/*
* MCE second step: parse errors and display
*/
for (i = 0; i < count; i++)
sbridge_mce_output_error(mci, &pvt->mce_outentry[i]);
}
/*
* sbridge_mce_check_error Replicates mcelog routine to get errors
* This routine simply queues mcelog errors, and
* return. The error itself should be handled later
* by sbridge_check_error.
* WARNING: As this routine should be called at NMI time, extra care should
* be taken to avoid deadlocks, and to be as fast as possible.
*/ */
static int sbridge_mce_check_error(struct notifier_block *nb, unsigned long val, static int sbridge_mce_check_error(struct notifier_block *nb, unsigned long val,
void *data) void *data)
@ -3198,21 +3121,7 @@ static int sbridge_mce_check_error(struct notifier_block *nb, unsigned long val,
"%u APIC %x\n", mce->cpuvendor, mce->cpuid, "%u APIC %x\n", mce->cpuvendor, mce->cpuid,
mce->time, mce->socketid, mce->apicid); mce->time, mce->socketid, mce->apicid);
smp_rmb(); sbridge_mce_output_error(mci, mce);
if ((pvt->mce_out + 1) % MCE_LOG_LEN == pvt->mce_in) {
smp_wmb();
pvt->mce_overrun++;
return NOTIFY_DONE;
}
/* Copy memory error at the ringbuffer */
memcpy(&pvt->mce_entry[pvt->mce_out], mce, sizeof(*mce));
smp_wmb();
pvt->mce_out = (pvt->mce_out + 1) % MCE_LOG_LEN;
/* Handle fatal errors immediately */
if (mce->mcgstatus & 1)
sbridge_check_error(mci);
/* Advice mcelog that the error were handled */ /* Advice mcelog that the error were handled */
return NOTIFY_STOP; return NOTIFY_STOP;
@ -3298,9 +3207,6 @@ static int sbridge_register_mci(struct sbridge_dev *sbridge_dev, enum type type)
mci->dev_name = pci_name(pdev); mci->dev_name = pci_name(pdev);
mci->ctl_page_to_phys = NULL; mci->ctl_page_to_phys = NULL;
/* Set the function pointer to an actual operation function */
mci->edac_check = sbridge_check_error;
pvt->info.type = type; pvt->info.type = type;
switch (type) { switch (type) {
case IVY_BRIDGE: case IVY_BRIDGE:
@ -3448,62 +3354,40 @@ fail0:
return rc; return rc;
} }
#define ICPU(model, table) \
{ X86_VENDOR_INTEL, 6, model, 0, (unsigned long)&table }
/* Order here must match "enum type" */
static const struct x86_cpu_id sbridge_cpuids[] = {
ICPU(0x2d, pci_dev_descr_sbridge_table), /* SANDY_BRIDGE */
ICPU(0x3e, pci_dev_descr_ibridge_table), /* IVY_BRIDGE */
ICPU(0x3f, pci_dev_descr_haswell_table), /* HASWELL */
ICPU(0x4f, pci_dev_descr_broadwell_table), /* BROADWELL */
ICPU(0x57, pci_dev_descr_knl_table), /* KNIGHTS_LANDING */
{ }
};
MODULE_DEVICE_TABLE(x86cpu, sbridge_cpuids);
/* /*
* sbridge_probe Probe for ONE instance of device to see if it is * sbridge_probe Get all devices and register memory controllers
* present. * present.
* return: * return:
* 0 for FOUND a device * 0 for FOUND a device
* < 0 for error code * < 0 for error code
*/ */
static int sbridge_probe(struct pci_dev *pdev, const struct pci_device_id *id) static int sbridge_probe(const struct x86_cpu_id *id)
{ {
int rc = -ENODEV; int rc = -ENODEV;
u8 mc, num_mc = 0; u8 mc, num_mc = 0;
struct sbridge_dev *sbridge_dev; struct sbridge_dev *sbridge_dev;
enum type type = SANDY_BRIDGE; struct pci_id_table *ptable = (struct pci_id_table *)id->driver_data;
/* get the pci devices we want to reserve for our use */ /* get the pci devices we want to reserve for our use */
mutex_lock(&sbridge_edac_lock); rc = sbridge_get_all_devices(&num_mc, ptable);
/*
* All memory controllers are allocated at the first pass.
*/
if (unlikely(probed >= 1)) {
mutex_unlock(&sbridge_edac_lock);
return -ENODEV;
}
probed++;
switch (pdev->device) {
case PCI_DEVICE_ID_INTEL_IBRIDGE_IMC_HA0_TA:
rc = sbridge_get_all_devices(&num_mc,
pci_dev_descr_ibridge_table);
type = IVY_BRIDGE;
break;
case PCI_DEVICE_ID_INTEL_SBRIDGE_IMC_HA0:
rc = sbridge_get_all_devices(&num_mc,
pci_dev_descr_sbridge_table);
type = SANDY_BRIDGE;
break;
case PCI_DEVICE_ID_INTEL_HASWELL_IMC_HA0:
rc = sbridge_get_all_devices(&num_mc,
pci_dev_descr_haswell_table);
type = HASWELL;
break;
case PCI_DEVICE_ID_INTEL_BROADWELL_IMC_HA0:
rc = sbridge_get_all_devices(&num_mc,
pci_dev_descr_broadwell_table);
type = BROADWELL;
break;
case PCI_DEVICE_ID_INTEL_KNL_IMC_SAD0:
rc = sbridge_get_all_devices_knl(&num_mc,
pci_dev_descr_knl_table);
type = KNIGHTS_LANDING;
break;
}
if (unlikely(rc < 0)) { if (unlikely(rc < 0)) {
edac_dbg(0, "couldn't get all devices for 0x%x\n", pdev->device); edac_dbg(0, "couldn't get all devices\n");
goto fail0; goto fail0;
} }
@ -3514,14 +3398,13 @@ static int sbridge_probe(struct pci_dev *pdev, const struct pci_device_id *id)
mc, mc + 1, num_mc); mc, mc + 1, num_mc);
sbridge_dev->mc = mc++; sbridge_dev->mc = mc++;
rc = sbridge_register_mci(sbridge_dev, type); rc = sbridge_register_mci(sbridge_dev, id - sbridge_cpuids);
if (unlikely(rc < 0)) if (unlikely(rc < 0))
goto fail1; goto fail1;
} }
sbridge_printk(KERN_INFO, "%s\n", SBRIDGE_REVISION); sbridge_printk(KERN_INFO, "%s\n", SBRIDGE_REVISION);
mutex_unlock(&sbridge_edac_lock);
return 0; return 0;
fail1: fail1:
@ -3530,74 +3413,47 @@ fail1:
sbridge_put_all_devices(); sbridge_put_all_devices();
fail0: fail0:
mutex_unlock(&sbridge_edac_lock);
return rc; return rc;
} }
/* /*
* sbridge_remove destructor for one instance of device * sbridge_remove cleanup
* *
*/ */
static void sbridge_remove(struct pci_dev *pdev) static void sbridge_remove(void)
{ {
struct sbridge_dev *sbridge_dev; struct sbridge_dev *sbridge_dev;
edac_dbg(0, "\n"); edac_dbg(0, "\n");
/*
* 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
* is a Nehalem or upper design. However, due to the way several PCI
* devices are grouped together to provide MC functionality, we need
* to use a different method for releasing the devices
*/
mutex_lock(&sbridge_edac_lock);
if (unlikely(!probed)) {
mutex_unlock(&sbridge_edac_lock);
return;
}
list_for_each_entry(sbridge_dev, &sbridge_edac_list, list) list_for_each_entry(sbridge_dev, &sbridge_edac_list, list)
sbridge_unregister_mci(sbridge_dev); sbridge_unregister_mci(sbridge_dev);
/* Release PCI resources */ /* Release PCI resources */
sbridge_put_all_devices(); sbridge_put_all_devices();
probed--;
mutex_unlock(&sbridge_edac_lock);
} }
MODULE_DEVICE_TABLE(pci, sbridge_pci_tbl);
/*
* sbridge_driver pci_driver structure for this module
*
*/
static struct pci_driver sbridge_driver = {
.name = "sbridge_edac",
.probe = sbridge_probe,
.remove = sbridge_remove,
.id_table = sbridge_pci_tbl,
};
/* /*
* sbridge_init Module entry function * sbridge_init Module entry function
* Try to initialize this module for its devices * Try to initialize this module for its devices
*/ */
static int __init sbridge_init(void) static int __init sbridge_init(void)
{ {
int pci_rc; const struct x86_cpu_id *id;
int rc;
edac_dbg(2, "\n"); edac_dbg(2, "\n");
id = x86_match_cpu(sbridge_cpuids);
if (!id)
return -ENODEV;
/* Ensure that the OPSTATE is set correctly for POLL or NMI */ /* Ensure that the OPSTATE is set correctly for POLL or NMI */
opstate_init(); opstate_init();
pci_rc = pci_register_driver(&sbridge_driver); rc = sbridge_probe(id);
if (pci_rc >= 0) {
if (rc >= 0) {
mce_register_decode_chain(&sbridge_mce_dec); mce_register_decode_chain(&sbridge_mce_dec);
if (get_edac_report_status() == EDAC_REPORTING_DISABLED) if (get_edac_report_status() == EDAC_REPORTING_DISABLED)
sbridge_printk(KERN_WARNING, "Loading driver, error reporting disabled.\n"); sbridge_printk(KERN_WARNING, "Loading driver, error reporting disabled.\n");
@ -3605,9 +3461,9 @@ static int __init sbridge_init(void)
} }
sbridge_printk(KERN_ERR, "Failed to register device with error %d.\n", sbridge_printk(KERN_ERR, "Failed to register device with error %d.\n",
pci_rc); rc);
return pci_rc; return rc;
} }
/* /*
@ -3617,7 +3473,7 @@ static int __init sbridge_init(void)
static void __exit sbridge_exit(void) static void __exit sbridge_exit(void)
{ {
edac_dbg(2, "\n"); edac_dbg(2, "\n");
pci_unregister_driver(&sbridge_driver); sbridge_remove();
mce_unregister_decode_chain(&sbridge_mce_dec); mce_unregister_decode_chain(&sbridge_mce_dec);
} }