i7core_edac: scrubbing fixups
Get a more reliable DCLK value from DMI, name the SCRUBINTERVAL mask and guard against potential overflow in the scrub rate computations. Signed-off-by: Nils Carlson <nils.carlson@ericsson.com>
This commit is contained in:
parent
168eb34ded
commit
535e9c78e1
|
@ -31,6 +31,7 @@
|
||||||
#include <linux/pci_ids.h>
|
#include <linux/pci_ids.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
|
#include <linux/dmi.h>
|
||||||
#include <linux/edac.h>
|
#include <linux/edac.h>
|
||||||
#include <linux/mmzone.h>
|
#include <linux/mmzone.h>
|
||||||
#include <linux/smp.h>
|
#include <linux/smp.h>
|
||||||
|
@ -107,6 +108,7 @@ MODULE_PARM_DESC(use_pci_fixup, "Enable PCI fixup to seek for hidden devices");
|
||||||
|
|
||||||
#define MC_SCRUB_CONTROL 0x4c
|
#define MC_SCRUB_CONTROL 0x4c
|
||||||
#define STARTSCRUB (1 << 24)
|
#define STARTSCRUB (1 << 24)
|
||||||
|
#define SCRUBINTERVAL_MASK 0xffffff
|
||||||
|
|
||||||
#define MC_COR_ECC_CNT_0 0x80
|
#define MC_COR_ECC_CNT_0 0x80
|
||||||
#define MC_COR_ECC_CNT_1 0x84
|
#define MC_COR_ECC_CNT_1 0x84
|
||||||
|
@ -275,6 +277,9 @@ struct i7core_pvt {
|
||||||
/* Count indicator to show errors not got */
|
/* Count indicator to show errors not got */
|
||||||
unsigned mce_overrun;
|
unsigned mce_overrun;
|
||||||
|
|
||||||
|
/* DCLK Frequency used for computing scrub rate */
|
||||||
|
int dclk_freq;
|
||||||
|
|
||||||
/* Struct to control EDAC polling */
|
/* Struct to control EDAC polling */
|
||||||
struct edac_pci_ctl_info *i7core_pci;
|
struct edac_pci_ctl_info *i7core_pci;
|
||||||
};
|
};
|
||||||
|
@ -1952,6 +1957,112 @@ static struct notifier_block i7_mce_dec = {
|
||||||
.notifier_call = i7core_mce_check_error,
|
.notifier_call = i7core_mce_check_error,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct memdev_dmi_entry {
|
||||||
|
u8 type;
|
||||||
|
u8 length;
|
||||||
|
u16 handle;
|
||||||
|
u16 phys_mem_array_handle;
|
||||||
|
u16 mem_err_info_handle;
|
||||||
|
u16 total_width;
|
||||||
|
u16 data_width;
|
||||||
|
u16 size;
|
||||||
|
u8 form;
|
||||||
|
u8 device_set;
|
||||||
|
u8 device_locator;
|
||||||
|
u8 bank_locator;
|
||||||
|
u8 memory_type;
|
||||||
|
u16 type_detail;
|
||||||
|
u16 speed;
|
||||||
|
u8 manufacturer;
|
||||||
|
u8 serial_number;
|
||||||
|
u8 asset_tag;
|
||||||
|
u8 part_number;
|
||||||
|
u8 attributes;
|
||||||
|
u32 extended_size;
|
||||||
|
u16 conf_mem_clk_speed;
|
||||||
|
} __attribute__((__packed__));
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Decode the DRAM Clock Frequency, be paranoid, make sure that all
|
||||||
|
* memory devices show the same speed, and if they don't then consider
|
||||||
|
* all speeds to be invalid.
|
||||||
|
*/
|
||||||
|
static void decode_dclk(const struct dmi_header *dh, void *_dclk_freq)
|
||||||
|
{
|
||||||
|
int *dclk_freq = _dclk_freq;
|
||||||
|
u16 dmi_mem_clk_speed;
|
||||||
|
|
||||||
|
if (*dclk_freq == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (dh->type == DMI_ENTRY_MEM_DEVICE) {
|
||||||
|
struct memdev_dmi_entry *memdev_dmi_entry =
|
||||||
|
(struct memdev_dmi_entry *)dh;
|
||||||
|
unsigned long conf_mem_clk_speed_offset =
|
||||||
|
(unsigned long)&memdev_dmi_entry->conf_mem_clk_speed -
|
||||||
|
(unsigned long)&memdev_dmi_entry->type;
|
||||||
|
unsigned long speed_offset =
|
||||||
|
(unsigned long)&memdev_dmi_entry->speed -
|
||||||
|
(unsigned long)&memdev_dmi_entry->type;
|
||||||
|
|
||||||
|
/* Check that a DIMM is present */
|
||||||
|
if (memdev_dmi_entry->size == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pick the configured speed if it's available, otherwise
|
||||||
|
* pick the DIMM speed, or we don't have a speed.
|
||||||
|
*/
|
||||||
|
if (memdev_dmi_entry->length > conf_mem_clk_speed_offset) {
|
||||||
|
dmi_mem_clk_speed =
|
||||||
|
memdev_dmi_entry->conf_mem_clk_speed;
|
||||||
|
} else if (memdev_dmi_entry->length > speed_offset) {
|
||||||
|
dmi_mem_clk_speed = memdev_dmi_entry->speed;
|
||||||
|
} else {
|
||||||
|
*dclk_freq = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*dclk_freq == 0) {
|
||||||
|
/* First pass, speed was 0 */
|
||||||
|
if (dmi_mem_clk_speed > 0) {
|
||||||
|
/* Set speed if a valid speed is read */
|
||||||
|
*dclk_freq = dmi_mem_clk_speed;
|
||||||
|
} else {
|
||||||
|
/* Otherwise we don't have a valid speed */
|
||||||
|
*dclk_freq = -1;
|
||||||
|
}
|
||||||
|
} else if (*dclk_freq > 0 &&
|
||||||
|
*dclk_freq != dmi_mem_clk_speed) {
|
||||||
|
/*
|
||||||
|
* If we have a speed, check that all DIMMS are the same
|
||||||
|
* speed, otherwise set the speed as invalid.
|
||||||
|
*/
|
||||||
|
*dclk_freq = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The default DCLK frequency is used as a fallback if we
|
||||||
|
* fail to find anything reliable in the DMI. The value
|
||||||
|
* is taken straight from the datasheet.
|
||||||
|
*/
|
||||||
|
#define DEFAULT_DCLK_FREQ 800
|
||||||
|
|
||||||
|
static int get_dclk_freq(void)
|
||||||
|
{
|
||||||
|
int dclk_freq = 0;
|
||||||
|
|
||||||
|
dmi_walk(decode_dclk, (void *)&dclk_freq);
|
||||||
|
|
||||||
|
if (dclk_freq < 1)
|
||||||
|
return DEFAULT_DCLK_FREQ;
|
||||||
|
|
||||||
|
return dclk_freq;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* set_sdram_scrub_rate This routine sets byte/sec bandwidth scrub rate
|
* set_sdram_scrub_rate This routine sets byte/sec bandwidth scrub rate
|
||||||
* to hardware according to SCRUBINTERVAL formula
|
* to hardware according to SCRUBINTERVAL formula
|
||||||
|
@ -1961,8 +2072,6 @@ static int set_sdram_scrub_rate(struct mem_ctl_info *mci, u32 new_bw)
|
||||||
{
|
{
|
||||||
struct i7core_pvt *pvt = mci->pvt_info;
|
struct i7core_pvt *pvt = mci->pvt_info;
|
||||||
struct pci_dev *pdev;
|
struct pci_dev *pdev;
|
||||||
const u32 cache_line_size = 64;
|
|
||||||
const u32 freq_dclk = 800*1000000;
|
|
||||||
u32 dw_scrub;
|
u32 dw_scrub;
|
||||||
u32 dw_ssr;
|
u32 dw_ssr;
|
||||||
|
|
||||||
|
@ -1977,18 +2086,28 @@ static int set_sdram_scrub_rate(struct mem_ctl_info *mci, u32 new_bw)
|
||||||
/* Prepare to disable petrol scrub */
|
/* Prepare to disable petrol scrub */
|
||||||
dw_scrub &= ~STARTSCRUB;
|
dw_scrub &= ~STARTSCRUB;
|
||||||
/* Stop the patrol scrub engine */
|
/* Stop the patrol scrub engine */
|
||||||
write_and_test(pdev, MC_SCRUB_CONTROL, dw_scrub & ~0x00ffffff);
|
write_and_test(pdev, MC_SCRUB_CONTROL,
|
||||||
|
dw_scrub & ~SCRUBINTERVAL_MASK);
|
||||||
|
|
||||||
/* Get current status of scrub rate and set bit to disable */
|
/* Get current status of scrub rate and set bit to disable */
|
||||||
pci_read_config_dword(pdev, MC_SSRCONTROL, &dw_ssr);
|
pci_read_config_dword(pdev, MC_SSRCONTROL, &dw_ssr);
|
||||||
dw_ssr &= ~SSR_MODE_MASK;
|
dw_ssr &= ~SSR_MODE_MASK;
|
||||||
dw_ssr |= SSR_MODE_DISABLE;
|
dw_ssr |= SSR_MODE_DISABLE;
|
||||||
} else {
|
} else {
|
||||||
|
const int cache_line_size = 64;
|
||||||
|
const u32 freq_dclk_mhz = pvt->dclk_freq;
|
||||||
|
unsigned long long scrub_interval;
|
||||||
/*
|
/*
|
||||||
* Translate the desired scrub rate to a register value and
|
* Translate the desired scrub rate to a register value and
|
||||||
* program the cooresponding register value.
|
* program the corresponding register value.
|
||||||
*/
|
*/
|
||||||
dw_scrub = 0x00ffffff & (cache_line_size * freq_dclk / new_bw);
|
scrub_interval = (unsigned long long)freq_dclk_mhz *
|
||||||
|
cache_line_size * 1000000 / new_bw;
|
||||||
|
|
||||||
|
if (!scrub_interval || scrub_interval > SCRUBINTERVAL_MASK)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
dw_scrub = SCRUBINTERVAL_MASK & scrub_interval;
|
||||||
|
|
||||||
/* Start the patrol scrub engine */
|
/* Start the patrol scrub engine */
|
||||||
pci_write_config_dword(pdev, MC_SCRUB_CONTROL,
|
pci_write_config_dword(pdev, MC_SCRUB_CONTROL,
|
||||||
|
@ -2015,7 +2134,8 @@ static int get_sdram_scrub_rate(struct mem_ctl_info *mci)
|
||||||
struct i7core_pvt *pvt = mci->pvt_info;
|
struct i7core_pvt *pvt = mci->pvt_info;
|
||||||
struct pci_dev *pdev;
|
struct pci_dev *pdev;
|
||||||
const u32 cache_line_size = 64;
|
const u32 cache_line_size = 64;
|
||||||
const u32 freq_dclk = 800*1000000;
|
const u32 freq_dclk_mhz = pvt->dclk_freq;
|
||||||
|
unsigned long long scrub_rate;
|
||||||
u32 scrubval;
|
u32 scrubval;
|
||||||
|
|
||||||
/* Get data from the MC register, function 2 */
|
/* Get data from the MC register, function 2 */
|
||||||
|
@ -2027,12 +2147,14 @@ static int get_sdram_scrub_rate(struct mem_ctl_info *mci)
|
||||||
pci_read_config_dword(pdev, MC_SCRUB_CONTROL, &scrubval);
|
pci_read_config_dword(pdev, MC_SCRUB_CONTROL, &scrubval);
|
||||||
|
|
||||||
/* Mask highest 8-bits to 0 */
|
/* Mask highest 8-bits to 0 */
|
||||||
scrubval &= 0x00ffffff;
|
scrubval &= SCRUBINTERVAL_MASK;
|
||||||
if (!scrubval)
|
if (!scrubval)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* Calculate scrub rate value into byte/sec bandwidth */
|
/* Calculate scrub rate value into byte/sec bandwidth */
|
||||||
return 0xffffffff & (cache_line_size * freq_dclk / (u64) scrubval);
|
scrub_rate = (unsigned long long)freq_dclk_mhz *
|
||||||
|
1000000 * cache_line_size / scrubval;
|
||||||
|
return (int)scrub_rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void enable_sdram_scrub_setting(struct mem_ctl_info *mci)
|
static void enable_sdram_scrub_setting(struct mem_ctl_info *mci)
|
||||||
|
@ -2204,6 +2326,9 @@ static int i7core_register_mci(struct i7core_dev *i7core_dev)
|
||||||
/* allocating generic PCI control info */
|
/* allocating generic PCI control info */
|
||||||
i7core_pci_ctl_create(pvt);
|
i7core_pci_ctl_create(pvt);
|
||||||
|
|
||||||
|
/* DCLK for scrub rate setting */
|
||||||
|
pvt->dclk_freq = get_dclk_freq();
|
||||||
|
|
||||||
atomic_notifier_chain_register(&x86_mce_decoder_chain, &i7_mce_dec);
|
atomic_notifier_chain_register(&x86_mce_decoder_chain, &i7_mce_dec);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
Loading…
Reference in New Issue