text: // 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 <linux/extable.h>
  #include <linux/pgtable.h>
  #include <asm/mmu.h>
  #include <asm/mce.h>
  #include <asm/machdep.h>
  #include <asm/pte-walk.h>
  #include <asm/sstep.h>
  #include <asm/exception-64s.h>
  #include <asm/extable.h>
  #include <asm/inst.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, pte;
  	unsigned int shift;
  	unsigned long pfn, flags;
  	struct mm_struct *mm;
  
  	if (user_mode(regs))
  		mm = current->mm;
  	else
  		mm = &init_mm;
  
  	local_irq_save(flags);
  	ptep = __find_linux_pte(mm->pgd, addr, NULL, &shift);
  	if (!ptep) {
  		pfn = ULONG_MAX;
  		goto out;
  	}
  	pte = READ_ONCE(*ptep);
  
  	if (!pte_present(pte) || pte_special(pte)) {
  		pfn = ULONG_MAX;
  		goto out;
  	}
  
  	if (shift <= PAGE_SHIFT)
  		pfn = pte_pfn(pte);
  	else {
  		unsigned long rpnmask = (1ul << shift) - PAGE_SIZE;
  		pfn = pte_pfn(__pte(pte_val(pte) | (addr & rpnmask)));
  	}
  out:
  	local_irq_restore(flags);
  	return pfn;
  }
  
  /* 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 } };
  
  static const struct mce_ierror_table mce_p10_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, 0x0000000008080000, true,
    MCE_ERROR_TYPE_USER,MCE_USER_ERROR_SCV, MCE_ECLASS_SOFTWARE,
    MCE_INITIATOR_CPU,  MCE_SEV_WARNING, 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, 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 const struct mce_derror_table mce_p10_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 },
  { 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_phys(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
  	 */
  	struct ppc_inst 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 = ppc_inst_read((struct ppc_inst *)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:
  			if (local_paca->in_mce == 1)
  				slb_save_contents(local_paca->mce_faulty_slbs);
  			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 (local_paca->in_mce == 1)
  				slb_save_contents(local_paca->mce_faulty_slbs);
  			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_phys(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,
  				struct mce_error_info *mce_err)
  {
  	long handled = 0;
  
  	mce_common_process_ue(regs, mce_err);
  	if (mce_err->ignore_event)
  		return 1;
  
  	/*
  	 * 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, &mce_err);
  
  	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);
  }
  
  long __machine_check_early_realmode_p10(struct pt_regs *regs)
  {
  	return mce_handle_error(regs, mce_p10_derror_table, mce_p10_ierror_table);
  }
  