Skip to content

Commit

Permalink
x86/mce: Handle varying MCA bank counts
Browse files Browse the repository at this point in the history
Linux reads MCG_CAP[Count] to find the number of MCA banks visible to a
CPU. Currently, this number is the same for all CPUs and a warning is
shown if there is a difference. The number of banks is overwritten with
the MCG_CAP[Count] value of each following CPU that boots.

According to the Intel SDM and AMD APM, the MCG_CAP[Count] value gives
the number of banks that are available to a "processor implementation".
The AMD BKDGs/PPRs further clarify that this value is per core. This
value has historically been the same for every core in the system, but
that is not an architectural requirement.

Future AMD systems may have different MCG_CAP[Count] values per core,
so the assumption that all CPUs will have the same MCG_CAP[Count] value
will no longer be valid.

Also, the first CPU to boot will allocate the struct mce_banks[] array
using the number of banks based on its MCG_CAP[Count] value. The machine
check handler and other functions use the global number of banks to
iterate and index into the mce_banks[] array. So it's possible to use an
out-of-bounds index on an asymmetric system where a following CPU sees a
MCG_CAP[Count] value greater than its predecessors.

Thus, allocate the mce_banks[] array to the maximum number of banks.
This will avoid the potential out-of-bounds index since the value of
mca_cfg.banks is capped to MAX_NR_BANKS.

Set the value of mca_cfg.banks equal to the max of the previous value
and the value for the current CPU. This way mca_cfg.banks will always
represent the max number of banks detected on any CPU in the system.

This will ensure that all CPUs will access all the banks that are
visible to them. A CPU that can access fewer than the max number of
banks will find the registers of the extra banks to be read-as-zero.

Furthermore, print the resulting number of MCA banks in use. Do this in
mcheck_late_init() so that the final value is printed after all CPUs
have been initialized.

Finally, get bank count from target CPU when doing injection with mce-inject
module.

 [ bp: Remove out-of-bounds example, passify and cleanup commit message. ]

Signed-off-by: Yazen Ghannam <yazen.ghannam@amd.com>
Signed-off-by: Borislav Petkov <bp@suse.de>
Cc: "H. Peter Anvin" <hpa@zytor.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: linux-edac <linux-edac@vger.kernel.org>
Cc: Pu Wen <puwen@hygon.cn>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Tony Luck <tony.luck@intel.com>
Cc: Vishal Verma <vishal.l.verma@intel.com>
Cc: x86-ml <x86@kernel.org>
Link: https://lkml.kernel.org/r/20180727214009.78289-1-Yazen.Ghannam@amd.com
  • Loading branch information
Yazen Ghannam authored and Borislav Petkov committed Mar 27, 2019
1 parent f19501a commit 006c077
Show file tree
Hide file tree
Showing 2 changed files with 14 additions and 22 deletions.
22 changes: 7 additions & 15 deletions arch/x86/kernel/cpu/mce/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -1481,13 +1481,12 @@ EXPORT_SYMBOL_GPL(mce_notify_irq);
static int __mcheck_cpu_mce_banks_init(void)
{
int i;
u8 num_banks = mca_cfg.banks;

mce_banks = kcalloc(num_banks, sizeof(struct mce_bank), GFP_KERNEL);
mce_banks = kcalloc(MAX_NR_BANKS, sizeof(struct mce_bank), GFP_KERNEL);
if (!mce_banks)
return -ENOMEM;

for (i = 0; i < num_banks; i++) {
for (i = 0; i < MAX_NR_BANKS; i++) {
struct mce_bank *b = &mce_banks[i];

b->ctl = -1ULL;
Expand All @@ -1501,28 +1500,19 @@ static int __mcheck_cpu_mce_banks_init(void)
*/
static int __mcheck_cpu_cap_init(void)
{
unsigned b;
u64 cap;
u8 b;

rdmsrl(MSR_IA32_MCG_CAP, cap);

b = cap & MCG_BANKCNT_MASK;
if (!mca_cfg.banks)
pr_info("CPU supports %d MCE banks\n", b);

if (b > MAX_NR_BANKS) {
pr_warn("Using only %u machine check banks out of %u\n",
MAX_NR_BANKS, b);
if (WARN_ON_ONCE(b > MAX_NR_BANKS))
b = MAX_NR_BANKS;
}

/* Don't support asymmetric configurations today */
WARN_ON(mca_cfg.banks != 0 && b != mca_cfg.banks);
mca_cfg.banks = b;
mca_cfg.banks = max(mca_cfg.banks, b);

if (!mce_banks) {
int err = __mcheck_cpu_mce_banks_init();

if (err)
return err;
}
Expand Down Expand Up @@ -2481,6 +2471,8 @@ EXPORT_SYMBOL_GPL(mcsafe_key);

static int __init mcheck_late_init(void)
{
pr_info("Using %d MCE banks\n", mca_cfg.banks);

if (mca_cfg.recovery)
static_branch_inc(&mcsafe_key);

Expand Down
14 changes: 7 additions & 7 deletions arch/x86/kernel/cpu/mce/inject.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@
static struct mce i_mce;
static struct dentry *dfs_inj;

static u8 n_banks;

#define MAX_FLAG_OPT_SIZE 4
#define NBCFG 0x44

Expand Down Expand Up @@ -570,9 +568,15 @@ static void do_inject(void)
static int inj_bank_set(void *data, u64 val)
{
struct mce *m = (struct mce *)data;
u8 n_banks;
u64 cap;

/* Get bank count on target CPU so we can handle non-uniform values. */
rdmsrl_on_cpu(m->extcpu, MSR_IA32_MCG_CAP, &cap);
n_banks = cap & MCG_BANKCNT_MASK;

if (val >= n_banks) {
pr_err("Non-existent MCE bank: %llu\n", val);
pr_err("MCA bank %llu non-existent on CPU%d\n", val, m->extcpu);
return -EINVAL;
}

Expand Down Expand Up @@ -665,10 +669,6 @@ static struct dfs_node {
static int __init debugfs_init(void)
{
unsigned int i;
u64 cap;

rdmsrl(MSR_IA32_MCG_CAP, cap);
n_banks = cap & MCG_BANKCNT_MASK;

dfs_inj = debugfs_create_dir("mce-inject", NULL);
if (!dfs_inj)
Expand Down

0 comments on commit 006c077

Please sign in to comment.