| From ce357e210354e2c932fc3bb6891a0a48dddcc54a Mon Sep 17 00:00:00 2001 |
| From: Yazen Ghannam <yazen.ghannam@amd.com> |
| Date: Fri, 27 Jul 2018 16:40:09 -0500 |
| Subject: x86/mce: Handle varying MCA bank counts |
| |
| [ Upstream commit 006c077041dc73b9490fffc4c6af5befe0687110 ] |
| |
| 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 |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| arch/x86/kernel/cpu/mce/core.c | 22 +++++++--------------- |
| arch/x86/kernel/cpu/mce/inject.c | 14 +++++++------- |
| 2 files changed, 14 insertions(+), 22 deletions(-) |
| |
| diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c |
| index 0d47306cec7ae..9e6a94c208e01 100644 |
| --- a/arch/x86/kernel/cpu/mce/core.c |
| +++ b/arch/x86/kernel/cpu/mce/core.c |
| @@ -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; |
| @@ -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; |
| } |
| @@ -2489,6 +2479,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); |
| |
| diff --git a/arch/x86/kernel/cpu/mce/inject.c b/arch/x86/kernel/cpu/mce/inject.c |
| index 8492ef7d90150..3f82afd0f46f2 100644 |
| --- a/arch/x86/kernel/cpu/mce/inject.c |
| +++ b/arch/x86/kernel/cpu/mce/inject.c |
| @@ -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 |
| |
| @@ -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; |
| } |
| |
| @@ -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) |
| -- |
| 2.20.1 |
| |