From: "Aneesh Kumar K.V" aneesh.kumar@linux.ibm.com
[ Upstream commit d9101bfa6adc831bda8836c4d774820553c14942 ]
__find_linux_mm_pte() returns a page table entry pointer after walking the page table without holding locks. To make it safe against a THP split and/or collapse, we disable interrupts around the lockless page table walk. However we need to keep interrupts disabled as long as we use the page table entry pointer that is returned.
Fix addr_to_pfn() to do that.
Fixes: ba41e1e1ccb9 ("powerpc/mce: Hookup derror (load/store) UE errors") Signed-off-by: Aneesh Kumar K.V aneesh.kumar@linux.ibm.com [mpe: Rearrange code slightly and tweak change log wording] Signed-off-by: Michael Ellerman mpe@ellerman.id.au Link: https://lore.kernel.org/r/20190918145328.28602-1-aneesh.kumar@linux.ibm.com Signed-off-by: Sasha Levin sashal@kernel.org
Signed-off-by: Yang Yingliang yangyingliang@huawei.com --- arch/powerpc/kernel/mce_power.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/arch/powerpc/kernel/mce_power.c b/arch/powerpc/kernel/mce_power.c index 37a110b..ecb3750 100644 --- a/arch/powerpc/kernel/mce_power.c +++ b/arch/powerpc/kernel/mce_power.c @@ -40,7 +40,7 @@ static unsigned long addr_to_pfn(struct pt_regs *regs, unsigned long addr) { pte_t *ptep; unsigned int shift; - unsigned long flags; + unsigned long pfn, flags; struct mm_struct *mm;
if (user_mode(regs)) @@ -50,18 +50,22 @@ static unsigned long addr_to_pfn(struct pt_regs *regs, unsigned long addr)
local_irq_save(flags); ptep = __find_linux_pte(mm->pgd, addr, NULL, &shift); - local_irq_restore(flags);
- if (!ptep || pte_special(*ptep)) - return ULONG_MAX; + if (!ptep || pte_special(*ptep)) { + pfn = ULONG_MAX; + goto out; + }
- if (shift > PAGE_SHIFT) { + if (shift <= PAGE_SHIFT) + pfn = pte_pfn(*ptep); + else { unsigned long rpnmask = (1ul << shift) - PAGE_SIZE; - - return pte_pfn(__pte(pte_val(*ptep) | (addr & rpnmask))); + pfn = pte_pfn(__pte(pte_val(*ptep) | (addr & rpnmask))); }
- return pte_pfn(*ptep); +out: + local_irq_restore(flags); + return pfn; }
/* flush SLBs and reload */