mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-28 04:45:12 +07:00
192f0f8e9d
Notable changes: - Removal of the NPU DMA code, used by the out-of-tree Nvidia driver, as well as some other functions only used by drivers that haven't (yet?) made it upstream. - A fix for a bug in our handling of hardware watchpoints (eg. perf record -e mem: ...) which could lead to register corruption and kernel crashes. - Enable HAVE_ARCH_HUGE_VMAP, which allows us to use large pages for vmalloc when using the Radix MMU. - A large but incremental rewrite of our exception handling code to use gas macros rather than multiple levels of nested CPP macros. And the usual small fixes, cleanups and improvements. Thanks to: Alastair D'Silva, Alexey Kardashevskiy, Andreas Schwab, Aneesh Kumar K.V, Anju T Sudhakar, Anton Blanchard, Arnd Bergmann, Athira Rajeev, Cédric Le Goater, Christian Lamparter, Christophe Leroy, Christophe Lombard, Christoph Hellwig, Daniel Axtens, Denis Efremov, Enrico Weigelt, Frederic Barrat, Gautham R. Shenoy, Geert Uytterhoeven, Geliang Tang, Gen Zhang, Greg Kroah-Hartman, Greg Kurz, Gustavo Romero, Krzysztof Kozlowski, Madhavan Srinivasan, Masahiro Yamada, Mathieu Malaterre, Michael Neuling, Nathan Lynch, Naveen N. Rao, Nicholas Piggin, Nishad Kamdar, Oliver O'Halloran, Qian Cai, Ravi Bangoria, Sachin Sant, Sam Bobroff, Satheesh Rajendran, Segher Boessenkool, Shaokun Zhang, Shawn Anastasio, Stewart Smith, Suraj Jitindar Singh, Thiago Jung Bauermann, YueHaibing. -----BEGIN PGP SIGNATURE----- iQIcBAABAgAGBQJdKVoLAAoJEFHr6jzI4aWA0kIP/A6shIbbE7H5W2hFrqt/PPPK 3+VrvPKbOFF+W6hcE/RgSZmEnUo0svdNjHUd/eMfFS1vb/uRt2QDdrsHUNNwURQL M2mcLXFwYpnjSjb/XMgDbHpAQxjeGfTdYLonUIejN7Rk8KQUeLyKQ3SBn6kfMc46 DnUUcPcjuRGaETUmVuZZ4e40ZWbJp8PKDrSJOuUrTPXMaK5ciNbZk5mCWXGbYl6G BMQAyv4ld/417rNTjBEP/T2foMJtioAt4W6mtlgdkOTdIEZnFU67nNxDBthNSu2c 95+I+/sML4KOp1R4yhqLSLIDDbc3bg3c99hLGij0d948z3bkSZ8bwnPaUuy70C4v U8rvl/+N6C6H3DgSsPE/Gnkd8DnudqWY8nULc+8p3fXljGwww6/Qgt+6yCUn8BdW WgixkSjKgjDmzTw8trIUNEqORrTVle7cM2hIyIK2Q5T4kWzNQxrLZ/x/3wgoYjUa 1KwIzaRo5JKZ9D3pJnJ5U+knE2/90rJIyfcp0W6ygyJsWKi2GNmq1eN3sKOw0IxH Tg86RENIA/rEMErNOfP45sLteMuTR7of7peCG3yumIOZqsDVYAzerpvtSgip2cvK aG+9HcYlBFOOOF9Dabi8GXsTBLXLfwiyjjLSpA9eXPwW8KObgiNfTZa7ujjTPvis 4mk9oukFTFUpfhsMmI3T =3dBZ -----END PGP SIGNATURE----- Merge tag 'powerpc-5.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux Pull powerpc updates from Michael Ellerman: "Notable changes: - Removal of the NPU DMA code, used by the out-of-tree Nvidia driver, as well as some other functions only used by drivers that haven't (yet?) made it upstream. - A fix for a bug in our handling of hardware watchpoints (eg. perf record -e mem: ...) which could lead to register corruption and kernel crashes. - Enable HAVE_ARCH_HUGE_VMAP, which allows us to use large pages for vmalloc when using the Radix MMU. - A large but incremental rewrite of our exception handling code to use gas macros rather than multiple levels of nested CPP macros. And the usual small fixes, cleanups and improvements. Thanks to: Alastair D'Silva, Alexey Kardashevskiy, Andreas Schwab, Aneesh Kumar K.V, Anju T Sudhakar, Anton Blanchard, Arnd Bergmann, Athira Rajeev, Cédric Le Goater, Christian Lamparter, Christophe Leroy, Christophe Lombard, Christoph Hellwig, Daniel Axtens, Denis Efremov, Enrico Weigelt, Frederic Barrat, Gautham R. Shenoy, Geert Uytterhoeven, Geliang Tang, Gen Zhang, Greg Kroah-Hartman, Greg Kurz, Gustavo Romero, Krzysztof Kozlowski, Madhavan Srinivasan, Masahiro Yamada, Mathieu Malaterre, Michael Neuling, Nathan Lynch, Naveen N. Rao, Nicholas Piggin, Nishad Kamdar, Oliver O'Halloran, Qian Cai, Ravi Bangoria, Sachin Sant, Sam Bobroff, Satheesh Rajendran, Segher Boessenkool, Shaokun Zhang, Shawn Anastasio, Stewart Smith, Suraj Jitindar Singh, Thiago Jung Bauermann, YueHaibing" * tag 'powerpc-5.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux: (163 commits) powerpc/powernv/idle: Fix restore of SPRN_LDBAR for POWER9 stop state. powerpc/eeh: Handle hugepages in ioremap space ocxl: Update for AFU descriptor template version 1.1 powerpc/boot: pass CONFIG options in a simpler and more robust way powerpc/boot: add {get, put}_unaligned_be32 to xz_config.h powerpc/irq: Don't WARN continuously in arch_local_irq_restore() powerpc/module64: Use symbolic instructions names. powerpc/module32: Use symbolic instructions names. powerpc: Move PPC_HA() PPC_HI() and PPC_LO() to ppc-opcode.h powerpc/module64: Fix comment in R_PPC64_ENTRY handling powerpc/boot: Add lzo support for uImage powerpc/boot: Add lzma support for uImage powerpc/boot: don't force gzipped uImage powerpc/8xx: Add microcode patch to move SMC parameter RAM. powerpc/8xx: Use IO accessors in microcode programming. powerpc/8xx: replace #ifdefs by IS_ENABLED() in microcode.c powerpc/8xx: refactor programming of microcode CPM params. powerpc/8xx: refactor printing of microcode patch name. powerpc/8xx: Refactor microcode write powerpc/8xx: refactor writing of CPM microcode arrays ...
633 lines
20 KiB
C
633 lines
20 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Machine check exception handling CPU-side for power7 and power8
|
|
*
|
|
* Copyright 2013 IBM Corporation
|
|
* Author: Mahesh Salgaonkar <mahesh@linux.vnet.ibm.com>
|
|
*/
|
|
|
|
#undef DEBUG
|
|
#define pr_fmt(fmt) "mce_power: " fmt
|
|
|
|
#include <linux/types.h>
|
|
#include <linux/ptrace.h>
|
|
#include <asm/mmu.h>
|
|
#include <asm/mce.h>
|
|
#include <asm/machdep.h>
|
|
#include <asm/pgtable.h>
|
|
#include <asm/pte-walk.h>
|
|
#include <asm/sstep.h>
|
|
#include <asm/exception-64s.h>
|
|
|
|
/*
|
|
* Convert an address related to an mm to a PFN. NOTE: we are in real
|
|
* mode, we could potentially race with page table updates.
|
|
*/
|
|
unsigned long addr_to_pfn(struct pt_regs *regs, unsigned long addr)
|
|
{
|
|
pte_t *ptep;
|
|
unsigned long flags;
|
|
struct mm_struct *mm;
|
|
|
|
if (user_mode(regs))
|
|
mm = current->mm;
|
|
else
|
|
mm = &init_mm;
|
|
|
|
local_irq_save(flags);
|
|
if (mm == current->mm)
|
|
ptep = find_current_mm_pte(mm->pgd, addr, NULL, NULL);
|
|
else
|
|
ptep = find_init_mm_pte(addr, NULL);
|
|
local_irq_restore(flags);
|
|
if (!ptep || pte_special(*ptep))
|
|
return ULONG_MAX;
|
|
return pte_pfn(*ptep);
|
|
}
|
|
|
|
/* flush SLBs and reload */
|
|
#ifdef CONFIG_PPC_BOOK3S_64
|
|
void flush_and_reload_slb(void)
|
|
{
|
|
/* Invalidate all SLBs */
|
|
slb_flush_all_realmode();
|
|
|
|
#ifdef CONFIG_KVM_BOOK3S_HANDLER
|
|
/*
|
|
* If machine check is hit when in guest or in transition, we will
|
|
* only flush the SLBs and continue.
|
|
*/
|
|
if (get_paca()->kvm_hstate.in_guest)
|
|
return;
|
|
#endif
|
|
if (early_radix_enabled())
|
|
return;
|
|
|
|
/*
|
|
* This probably shouldn't happen, but it may be possible it's
|
|
* called in early boot before SLB shadows are allocated.
|
|
*/
|
|
if (!get_slb_shadow())
|
|
return;
|
|
|
|
slb_restore_bolted_realmode();
|
|
}
|
|
#endif
|
|
|
|
static void flush_erat(void)
|
|
{
|
|
#ifdef CONFIG_PPC_BOOK3S_64
|
|
if (!early_cpu_has_feature(CPU_FTR_ARCH_300)) {
|
|
flush_and_reload_slb();
|
|
return;
|
|
}
|
|
#endif
|
|
asm volatile(PPC_ISA_3_0_INVALIDATE_ERAT : : :"memory");
|
|
}
|
|
|
|
#define MCE_FLUSH_SLB 1
|
|
#define MCE_FLUSH_TLB 2
|
|
#define MCE_FLUSH_ERAT 3
|
|
|
|
static int mce_flush(int what)
|
|
{
|
|
#ifdef CONFIG_PPC_BOOK3S_64
|
|
if (what == MCE_FLUSH_SLB) {
|
|
flush_and_reload_slb();
|
|
return 1;
|
|
}
|
|
#endif
|
|
if (what == MCE_FLUSH_ERAT) {
|
|
flush_erat();
|
|
return 1;
|
|
}
|
|
if (what == MCE_FLUSH_TLB) {
|
|
tlbiel_all();
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define SRR1_MC_LOADSTORE(srr1) ((srr1) & PPC_BIT(42))
|
|
|
|
struct mce_ierror_table {
|
|
unsigned long srr1_mask;
|
|
unsigned long srr1_value;
|
|
bool nip_valid; /* nip is a valid indicator of faulting address */
|
|
unsigned int error_type;
|
|
unsigned int error_subtype;
|
|
unsigned int error_class;
|
|
unsigned int initiator;
|
|
unsigned int severity;
|
|
bool sync_error;
|
|
};
|
|
|
|
static const struct mce_ierror_table mce_p7_ierror_table[] = {
|
|
{ 0x00000000001c0000, 0x0000000000040000, true,
|
|
MCE_ERROR_TYPE_UE, MCE_UE_ERROR_IFETCH, MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000000001c0000, 0x0000000000080000, true,
|
|
MCE_ERROR_TYPE_SLB, MCE_SLB_ERROR_PARITY, MCE_ECLASS_HARD_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000000001c0000, 0x00000000000c0000, true,
|
|
MCE_ERROR_TYPE_SLB, MCE_SLB_ERROR_MULTIHIT, MCE_ECLASS_SOFT_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0x00000000001c0000, 0x0000000000100000, true,
|
|
MCE_ERROR_TYPE_SLB, MCE_SLB_ERROR_INDETERMINATE, /* BOTH */
|
|
MCE_ECLASS_SOFT_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0x00000000001c0000, 0x0000000000140000, true,
|
|
MCE_ERROR_TYPE_TLB, MCE_TLB_ERROR_MULTIHIT, MCE_ECLASS_SOFT_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0x00000000001c0000, 0x0000000000180000, true,
|
|
MCE_ERROR_TYPE_UE, MCE_UE_ERROR_PAGE_TABLE_WALK_IFETCH, MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000000001c0000, 0x00000000001c0000, true,
|
|
MCE_ERROR_TYPE_UE, MCE_UE_ERROR_IFETCH, MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0, 0, 0, 0, 0, 0, 0 } };
|
|
|
|
static const struct mce_ierror_table mce_p8_ierror_table[] = {
|
|
{ 0x00000000081c0000, 0x0000000000040000, true,
|
|
MCE_ERROR_TYPE_UE, MCE_UE_ERROR_IFETCH, MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000000081c0000, 0x0000000000080000, true,
|
|
MCE_ERROR_TYPE_SLB, MCE_SLB_ERROR_PARITY, MCE_ECLASS_HARD_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000000081c0000, 0x00000000000c0000, true,
|
|
MCE_ERROR_TYPE_SLB, MCE_SLB_ERROR_MULTIHIT, MCE_ECLASS_SOFT_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0x00000000081c0000, 0x0000000000100000, true,
|
|
MCE_ERROR_TYPE_ERAT, MCE_ERAT_ERROR_MULTIHIT, MCE_ECLASS_SOFT_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0x00000000081c0000, 0x0000000000140000, true,
|
|
MCE_ERROR_TYPE_TLB, MCE_TLB_ERROR_MULTIHIT, MCE_ECLASS_SOFT_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0x00000000081c0000, 0x0000000000180000, true,
|
|
MCE_ERROR_TYPE_UE, MCE_UE_ERROR_PAGE_TABLE_WALK_IFETCH,
|
|
MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000000081c0000, 0x00000000001c0000, true,
|
|
MCE_ERROR_TYPE_UE, MCE_UE_ERROR_IFETCH, MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000000081c0000, 0x0000000008000000, true,
|
|
MCE_ERROR_TYPE_LINK, MCE_LINK_ERROR_IFETCH_TIMEOUT, MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000000081c0000, 0x0000000008040000, true,
|
|
MCE_ERROR_TYPE_LINK,MCE_LINK_ERROR_PAGE_TABLE_WALK_IFETCH_TIMEOUT,
|
|
MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0, 0, 0, 0, 0, 0, 0 } };
|
|
|
|
static const struct mce_ierror_table mce_p9_ierror_table[] = {
|
|
{ 0x00000000081c0000, 0x0000000000040000, true,
|
|
MCE_ERROR_TYPE_UE, MCE_UE_ERROR_IFETCH, MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000000081c0000, 0x0000000000080000, true,
|
|
MCE_ERROR_TYPE_SLB, MCE_SLB_ERROR_PARITY, MCE_ECLASS_HARD_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000000081c0000, 0x00000000000c0000, true,
|
|
MCE_ERROR_TYPE_SLB, MCE_SLB_ERROR_MULTIHIT, MCE_ECLASS_SOFT_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0x00000000081c0000, 0x0000000000100000, true,
|
|
MCE_ERROR_TYPE_ERAT, MCE_ERAT_ERROR_MULTIHIT, MCE_ECLASS_SOFT_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0x00000000081c0000, 0x0000000000140000, true,
|
|
MCE_ERROR_TYPE_TLB, MCE_TLB_ERROR_MULTIHIT, MCE_ECLASS_SOFT_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0x00000000081c0000, 0x0000000000180000, true,
|
|
MCE_ERROR_TYPE_UE, MCE_UE_ERROR_PAGE_TABLE_WALK_IFETCH, MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000000081c0000, 0x00000000001c0000, true,
|
|
MCE_ERROR_TYPE_RA, MCE_RA_ERROR_IFETCH_FOREIGN, MCE_ECLASS_SOFTWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000000081c0000, 0x0000000008000000, true,
|
|
MCE_ERROR_TYPE_LINK, MCE_LINK_ERROR_IFETCH_TIMEOUT, MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000000081c0000, 0x0000000008040000, true,
|
|
MCE_ERROR_TYPE_LINK,MCE_LINK_ERROR_PAGE_TABLE_WALK_IFETCH_TIMEOUT,
|
|
MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000000081c0000, 0x00000000080c0000, true,
|
|
MCE_ERROR_TYPE_RA, MCE_RA_ERROR_IFETCH, MCE_ECLASS_SOFTWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000000081c0000, 0x0000000008100000, true,
|
|
MCE_ERROR_TYPE_RA, MCE_RA_ERROR_PAGE_TABLE_WALK_IFETCH, MCE_ECLASS_SOFTWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000000081c0000, 0x0000000008140000, false,
|
|
MCE_ERROR_TYPE_RA, MCE_RA_ERROR_STORE, MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_FATAL, false }, /* ASYNC is fatal */
|
|
{ 0x00000000081c0000, 0x0000000008180000, false,
|
|
MCE_ERROR_TYPE_LINK,MCE_LINK_ERROR_STORE_TIMEOUT,
|
|
MCE_INITIATOR_CPU, MCE_SEV_FATAL, false }, /* ASYNC is fatal */
|
|
{ 0x00000000081c0000, 0x00000000081c0000, true, MCE_ECLASS_HARDWARE,
|
|
MCE_ERROR_TYPE_RA, MCE_RA_ERROR_PAGE_TABLE_WALK_IFETCH_FOREIGN,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0, 0, 0, 0, 0, 0, 0 } };
|
|
|
|
struct mce_derror_table {
|
|
unsigned long dsisr_value;
|
|
bool dar_valid; /* dar is a valid indicator of faulting address */
|
|
unsigned int error_type;
|
|
unsigned int error_subtype;
|
|
unsigned int error_class;
|
|
unsigned int initiator;
|
|
unsigned int severity;
|
|
bool sync_error;
|
|
};
|
|
|
|
static const struct mce_derror_table mce_p7_derror_table[] = {
|
|
{ 0x00008000, false,
|
|
MCE_ERROR_TYPE_UE, MCE_UE_ERROR_LOAD_STORE, MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00004000, true,
|
|
MCE_ERROR_TYPE_UE, MCE_UE_ERROR_PAGE_TABLE_WALK_LOAD_STORE,
|
|
MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000800, true,
|
|
MCE_ERROR_TYPE_ERAT, MCE_ERAT_ERROR_MULTIHIT, MCE_ECLASS_SOFT_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0x00000400, true,
|
|
MCE_ERROR_TYPE_TLB, MCE_TLB_ERROR_MULTIHIT, MCE_ECLASS_SOFT_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0x00000080, true,
|
|
MCE_ERROR_TYPE_SLB, MCE_SLB_ERROR_MULTIHIT, MCE_ECLASS_SOFT_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0x00000100, true,
|
|
MCE_ERROR_TYPE_SLB, MCE_SLB_ERROR_PARITY, MCE_ECLASS_HARD_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000040, true,
|
|
MCE_ERROR_TYPE_SLB, MCE_SLB_ERROR_INDETERMINATE, /* BOTH */
|
|
MCE_ECLASS_HARD_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0, false, 0, 0, 0, 0, 0 } };
|
|
|
|
static const struct mce_derror_table mce_p8_derror_table[] = {
|
|
{ 0x00008000, false,
|
|
MCE_ERROR_TYPE_UE, MCE_UE_ERROR_LOAD_STORE, MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00004000, true,
|
|
MCE_ERROR_TYPE_UE, MCE_UE_ERROR_PAGE_TABLE_WALK_LOAD_STORE,
|
|
MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00002000, true,
|
|
MCE_ERROR_TYPE_LINK, MCE_LINK_ERROR_LOAD_TIMEOUT, MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00001000, true,
|
|
MCE_ERROR_TYPE_LINK, MCE_LINK_ERROR_PAGE_TABLE_WALK_LOAD_STORE_TIMEOUT,
|
|
MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000800, true,
|
|
MCE_ERROR_TYPE_ERAT, MCE_ERAT_ERROR_MULTIHIT, MCE_ECLASS_SOFT_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0x00000400, true,
|
|
MCE_ERROR_TYPE_TLB, MCE_TLB_ERROR_MULTIHIT, MCE_ECLASS_SOFT_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0x00000200, true,
|
|
MCE_ERROR_TYPE_ERAT, MCE_ERAT_ERROR_MULTIHIT, /* SECONDARY ERAT */
|
|
MCE_ECLASS_SOFT_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0x00000080, true,
|
|
MCE_ERROR_TYPE_SLB, MCE_SLB_ERROR_MULTIHIT, /* Before PARITY */
|
|
MCE_ECLASS_SOFT_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0x00000100, true,
|
|
MCE_ERROR_TYPE_SLB, MCE_SLB_ERROR_PARITY, MCE_ECLASS_HARD_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0, false, 0, 0, 0, 0, 0 } };
|
|
|
|
static const struct mce_derror_table mce_p9_derror_table[] = {
|
|
{ 0x00008000, false,
|
|
MCE_ERROR_TYPE_UE, MCE_UE_ERROR_LOAD_STORE, MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00004000, true,
|
|
MCE_ERROR_TYPE_UE, MCE_UE_ERROR_PAGE_TABLE_WALK_LOAD_STORE,
|
|
MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00002000, true,
|
|
MCE_ERROR_TYPE_LINK, MCE_LINK_ERROR_LOAD_TIMEOUT, MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00001000, true,
|
|
MCE_ERROR_TYPE_LINK, MCE_LINK_ERROR_PAGE_TABLE_WALK_LOAD_STORE_TIMEOUT,
|
|
MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000800, true,
|
|
MCE_ERROR_TYPE_ERAT, MCE_ERAT_ERROR_MULTIHIT, MCE_ECLASS_SOFT_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0x00000400, true,
|
|
MCE_ERROR_TYPE_TLB, MCE_TLB_ERROR_MULTIHIT, MCE_ECLASS_SOFT_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0x00000200, false,
|
|
MCE_ERROR_TYPE_USER, MCE_USER_ERROR_TLBIE, MCE_ECLASS_SOFTWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0x00000080, true,
|
|
MCE_ERROR_TYPE_SLB, MCE_SLB_ERROR_MULTIHIT, /* Before PARITY */
|
|
MCE_ECLASS_SOFT_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_WARNING, true },
|
|
{ 0x00000100, true,
|
|
MCE_ERROR_TYPE_SLB, MCE_SLB_ERROR_PARITY, MCE_ECLASS_HARD_INDETERMINATE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000040, true,
|
|
MCE_ERROR_TYPE_RA, MCE_RA_ERROR_LOAD, MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000020, false,
|
|
MCE_ERROR_TYPE_RA, MCE_RA_ERROR_PAGE_TABLE_WALK_LOAD_STORE,
|
|
MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000010, false,
|
|
MCE_ERROR_TYPE_RA, MCE_RA_ERROR_PAGE_TABLE_WALK_LOAD_STORE_FOREIGN,
|
|
MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0x00000008, false,
|
|
MCE_ERROR_TYPE_RA, MCE_RA_ERROR_LOAD_STORE_FOREIGN, MCE_ECLASS_HARDWARE,
|
|
MCE_INITIATOR_CPU, MCE_SEV_SEVERE, true },
|
|
{ 0, false, 0, 0, 0, 0, 0 } };
|
|
|
|
static int mce_find_instr_ea_and_pfn(struct pt_regs *regs, uint64_t *addr,
|
|
uint64_t *phys_addr)
|
|
{
|
|
/*
|
|
* Carefully look at the NIP to determine
|
|
* the instruction to analyse. Reading the NIP
|
|
* in real-mode is tricky and can lead to recursive
|
|
* faults
|
|
*/
|
|
int instr;
|
|
unsigned long pfn, instr_addr;
|
|
struct instruction_op op;
|
|
struct pt_regs tmp = *regs;
|
|
|
|
pfn = addr_to_pfn(regs, regs->nip);
|
|
if (pfn != ULONG_MAX) {
|
|
instr_addr = (pfn << PAGE_SHIFT) + (regs->nip & ~PAGE_MASK);
|
|
instr = *(unsigned int *)(instr_addr);
|
|
if (!analyse_instr(&op, &tmp, instr)) {
|
|
pfn = addr_to_pfn(regs, op.ea);
|
|
*addr = op.ea;
|
|
*phys_addr = (pfn << PAGE_SHIFT);
|
|
return 0;
|
|
}
|
|
/*
|
|
* analyse_instr() might fail if the instruction
|
|
* is not a load/store, although this is unexpected
|
|
* for load/store errors or if we got the NIP
|
|
* wrong
|
|
*/
|
|
}
|
|
*addr = 0;
|
|
return -1;
|
|
}
|
|
|
|
static int mce_handle_ierror(struct pt_regs *regs,
|
|
const struct mce_ierror_table table[],
|
|
struct mce_error_info *mce_err, uint64_t *addr,
|
|
uint64_t *phys_addr)
|
|
{
|
|
uint64_t srr1 = regs->msr;
|
|
int handled = 0;
|
|
int i;
|
|
|
|
*addr = 0;
|
|
|
|
for (i = 0; table[i].srr1_mask; i++) {
|
|
if ((srr1 & table[i].srr1_mask) != table[i].srr1_value)
|
|
continue;
|
|
|
|
/* attempt to correct the error */
|
|
switch (table[i].error_type) {
|
|
case MCE_ERROR_TYPE_SLB:
|
|
handled = mce_flush(MCE_FLUSH_SLB);
|
|
break;
|
|
case MCE_ERROR_TYPE_ERAT:
|
|
handled = mce_flush(MCE_FLUSH_ERAT);
|
|
break;
|
|
case MCE_ERROR_TYPE_TLB:
|
|
handled = mce_flush(MCE_FLUSH_TLB);
|
|
break;
|
|
}
|
|
|
|
/* now fill in mce_error_info */
|
|
mce_err->error_type = table[i].error_type;
|
|
mce_err->error_class = table[i].error_class;
|
|
switch (table[i].error_type) {
|
|
case MCE_ERROR_TYPE_UE:
|
|
mce_err->u.ue_error_type = table[i].error_subtype;
|
|
break;
|
|
case MCE_ERROR_TYPE_SLB:
|
|
mce_err->u.slb_error_type = table[i].error_subtype;
|
|
break;
|
|
case MCE_ERROR_TYPE_ERAT:
|
|
mce_err->u.erat_error_type = table[i].error_subtype;
|
|
break;
|
|
case MCE_ERROR_TYPE_TLB:
|
|
mce_err->u.tlb_error_type = table[i].error_subtype;
|
|
break;
|
|
case MCE_ERROR_TYPE_USER:
|
|
mce_err->u.user_error_type = table[i].error_subtype;
|
|
break;
|
|
case MCE_ERROR_TYPE_RA:
|
|
mce_err->u.ra_error_type = table[i].error_subtype;
|
|
break;
|
|
case MCE_ERROR_TYPE_LINK:
|
|
mce_err->u.link_error_type = table[i].error_subtype;
|
|
break;
|
|
}
|
|
mce_err->sync_error = table[i].sync_error;
|
|
mce_err->severity = table[i].severity;
|
|
mce_err->initiator = table[i].initiator;
|
|
if (table[i].nip_valid) {
|
|
*addr = regs->nip;
|
|
if (mce_err->sync_error &&
|
|
table[i].error_type == MCE_ERROR_TYPE_UE) {
|
|
unsigned long pfn;
|
|
|
|
if (get_paca()->in_mce < MAX_MCE_DEPTH) {
|
|
pfn = addr_to_pfn(regs, regs->nip);
|
|
if (pfn != ULONG_MAX) {
|
|
*phys_addr =
|
|
(pfn << PAGE_SHIFT);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return handled;
|
|
}
|
|
|
|
mce_err->error_type = MCE_ERROR_TYPE_UNKNOWN;
|
|
mce_err->error_class = MCE_ECLASS_UNKNOWN;
|
|
mce_err->severity = MCE_SEV_SEVERE;
|
|
mce_err->initiator = MCE_INITIATOR_CPU;
|
|
mce_err->sync_error = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mce_handle_derror(struct pt_regs *regs,
|
|
const struct mce_derror_table table[],
|
|
struct mce_error_info *mce_err, uint64_t *addr,
|
|
uint64_t *phys_addr)
|
|
{
|
|
uint64_t dsisr = regs->dsisr;
|
|
int handled = 0;
|
|
int found = 0;
|
|
int i;
|
|
|
|
*addr = 0;
|
|
|
|
for (i = 0; table[i].dsisr_value; i++) {
|
|
if (!(dsisr & table[i].dsisr_value))
|
|
continue;
|
|
|
|
/* attempt to correct the error */
|
|
switch (table[i].error_type) {
|
|
case MCE_ERROR_TYPE_SLB:
|
|
if (mce_flush(MCE_FLUSH_SLB))
|
|
handled = 1;
|
|
break;
|
|
case MCE_ERROR_TYPE_ERAT:
|
|
if (mce_flush(MCE_FLUSH_ERAT))
|
|
handled = 1;
|
|
break;
|
|
case MCE_ERROR_TYPE_TLB:
|
|
if (mce_flush(MCE_FLUSH_TLB))
|
|
handled = 1;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Attempt to handle multiple conditions, but only return
|
|
* one. Ensure uncorrectable errors are first in the table
|
|
* to match.
|
|
*/
|
|
if (found)
|
|
continue;
|
|
|
|
/* now fill in mce_error_info */
|
|
mce_err->error_type = table[i].error_type;
|
|
mce_err->error_class = table[i].error_class;
|
|
switch (table[i].error_type) {
|
|
case MCE_ERROR_TYPE_UE:
|
|
mce_err->u.ue_error_type = table[i].error_subtype;
|
|
break;
|
|
case MCE_ERROR_TYPE_SLB:
|
|
mce_err->u.slb_error_type = table[i].error_subtype;
|
|
break;
|
|
case MCE_ERROR_TYPE_ERAT:
|
|
mce_err->u.erat_error_type = table[i].error_subtype;
|
|
break;
|
|
case MCE_ERROR_TYPE_TLB:
|
|
mce_err->u.tlb_error_type = table[i].error_subtype;
|
|
break;
|
|
case MCE_ERROR_TYPE_USER:
|
|
mce_err->u.user_error_type = table[i].error_subtype;
|
|
break;
|
|
case MCE_ERROR_TYPE_RA:
|
|
mce_err->u.ra_error_type = table[i].error_subtype;
|
|
break;
|
|
case MCE_ERROR_TYPE_LINK:
|
|
mce_err->u.link_error_type = table[i].error_subtype;
|
|
break;
|
|
}
|
|
mce_err->sync_error = table[i].sync_error;
|
|
mce_err->severity = table[i].severity;
|
|
mce_err->initiator = table[i].initiator;
|
|
if (table[i].dar_valid)
|
|
*addr = regs->dar;
|
|
else if (mce_err->sync_error &&
|
|
table[i].error_type == MCE_ERROR_TYPE_UE) {
|
|
/*
|
|
* We do a maximum of 4 nested MCE calls, see
|
|
* kernel/exception-64s.h
|
|
*/
|
|
if (get_paca()->in_mce < MAX_MCE_DEPTH)
|
|
mce_find_instr_ea_and_pfn(regs, addr, phys_addr);
|
|
}
|
|
found = 1;
|
|
}
|
|
|
|
if (found)
|
|
return handled;
|
|
|
|
mce_err->error_type = MCE_ERROR_TYPE_UNKNOWN;
|
|
mce_err->error_class = MCE_ECLASS_UNKNOWN;
|
|
mce_err->severity = MCE_SEV_SEVERE;
|
|
mce_err->initiator = MCE_INITIATOR_CPU;
|
|
mce_err->sync_error = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long mce_handle_ue_error(struct pt_regs *regs)
|
|
{
|
|
long handled = 0;
|
|
|
|
/*
|
|
* On specific SCOM read via MMIO we may get a machine check
|
|
* exception with SRR0 pointing inside opal. If that is the
|
|
* case OPAL may have recovery address to re-read SCOM data in
|
|
* different way and hence we can recover from this MC.
|
|
*/
|
|
|
|
if (ppc_md.mce_check_early_recovery) {
|
|
if (ppc_md.mce_check_early_recovery(regs))
|
|
handled = 1;
|
|
}
|
|
return handled;
|
|
}
|
|
|
|
static long mce_handle_error(struct pt_regs *regs,
|
|
const struct mce_derror_table dtable[],
|
|
const struct mce_ierror_table itable[])
|
|
{
|
|
struct mce_error_info mce_err = { 0 };
|
|
uint64_t addr, phys_addr = ULONG_MAX;
|
|
uint64_t srr1 = regs->msr;
|
|
long handled;
|
|
|
|
if (SRR1_MC_LOADSTORE(srr1))
|
|
handled = mce_handle_derror(regs, dtable, &mce_err, &addr,
|
|
&phys_addr);
|
|
else
|
|
handled = mce_handle_ierror(regs, itable, &mce_err, &addr,
|
|
&phys_addr);
|
|
|
|
if (!handled && mce_err.error_type == MCE_ERROR_TYPE_UE)
|
|
handled = mce_handle_ue_error(regs);
|
|
|
|
save_mce_event(regs, handled, &mce_err, regs->nip, addr, phys_addr);
|
|
|
|
return handled;
|
|
}
|
|
|
|
long __machine_check_early_realmode_p7(struct pt_regs *regs)
|
|
{
|
|
/* P7 DD1 leaves top bits of DSISR undefined */
|
|
regs->dsisr &= 0x0000ffff;
|
|
|
|
return mce_handle_error(regs, mce_p7_derror_table, mce_p7_ierror_table);
|
|
}
|
|
|
|
long __machine_check_early_realmode_p8(struct pt_regs *regs)
|
|
{
|
|
return mce_handle_error(regs, mce_p8_derror_table, mce_p8_ierror_table);
|
|
}
|
|
|
|
long __machine_check_early_realmode_p9(struct pt_regs *regs)
|
|
{
|
|
/*
|
|
* On POWER9 DD2.1 and below, it's possible to get a machine check
|
|
* caused by a paste instruction where only DSISR bit 25 is set. This
|
|
* will result in the MCE handler seeing an unknown event and the kernel
|
|
* crashing. An MCE that occurs like this is spurious, so we don't need
|
|
* to do anything in terms of servicing it. If there is something that
|
|
* needs to be serviced, the CPU will raise the MCE again with the
|
|
* correct DSISR so that it can be serviced properly. So detect this
|
|
* case and mark it as handled.
|
|
*/
|
|
if (SRR1_MC_LOADSTORE(regs->msr) && regs->dsisr == 0x02000000)
|
|
return 1;
|
|
|
|
return mce_handle_error(regs, mce_p9_derror_table, mce_p9_ierror_table);
|
|
}
|