Re: [RFC PATCH] powerpc/mm/thp: Make page table walk safe against thp split/collapse
From: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Date: 2015-03-23 23:36:24
On Mon, 2015-03-23 at 20:30 +0530, Aneesh Kumar K.V wrote:
-static inline pte_t *lookup_linux_ptep(pgd_t *pgdir, unsigned long hva,
+static inline pte_t lookup_linux_pte(pgd_t *pgdir, unsigned long hva,
unsigned long *pte_sizep)
{
pte_t *ptep;
unsigned long ps = *pte_sizep;
unsigned int shift;
+ unsigned long flags;
+ local_irq_save(flags);
ptep = find_linux_pte_or_hugepte(pgdir, hva, &shift);
if (!ptep)
- return NULL;
+ goto err_out;
if (shift)
*pte_sizep = 1ul << shift;
else
*pte_sizep = PAGE_SIZE;
if (ps > *pte_sizep)
- return NULL;
+ goto err_out;
- return ptep;
+ local_irq_restore(flags);
+ return *ptep;
+err_out:
+ local_irq_restore(flags);
+ return __pte(0);
}Doesn't the above go backward vs. your explanation ? IE. You capture the PTE *value* inside the lock then drop the lock and have no check for splitting or anything, causing you to potentially return a stale PTE. You assume the caller will check for splitting ? It doesn't ...
quoted hunk ↗ jump to hunk
#endif /* __ASSEMBLY__ */diff --git a/arch/powerpc/kernel/eeh.c b/arch/powerpc/kernel/eeh.c index 3b2252e7731b..c2b44f250824 100644 --- a/arch/powerpc/kernel/eeh.c +++ b/arch/powerpc/kernel/eeh.c@@ -326,17 +326,21 @@ void eeh_slot_error_detail(struct eeh_pe *pe, int severity) static inline unsigned long eeh_token_to_phys(unsigned long token) { pte_t *ptep; - unsigned long pa; + unsigned long pa, flags; int hugepage_shift; /* * We won't find hugepages here, iomem */ + local_irq_save(flags); ptep = find_linux_pte_or_hugepte(init_mm.pgd, token, &hugepage_shift); - if (!ptep) + if (!ptep) { + local_irq_restore(flags); return token; + } WARN_ON(hugepage_shift); pa = pte_pfn(*ptep) << PAGE_SHIFT; + local_irq_restore(flags);
Isn't think the same as lookup_linux_pte ? Can't you just use that and keep WARN_ON(psize != PAGE_SIZE) ?
quoted hunk ↗ jump to hunk
return pa | (token & (PAGE_SIZE-1)); }diff --git a/arch/powerpc/kernel/io-workarounds.c b/arch/powerpc/kernel/io-workarounds.c index 24b968f8e4d8..bd958d93fd38 100644 --- a/arch/powerpc/kernel/io-workarounds.c +++ b/arch/powerpc/kernel/io-workarounds.c@@ -65,6 +65,7 @@ struct iowa_bus *iowa_mem_find_bus(const PCI_IO_ADDR addr) if (token && token <= iowa_bus_count) bus = &iowa_busses[token - 1]; else { + unsigned long flags; unsigned long vaddr, paddr; pte_t *ptep;@@ -72,6 +73,7 @@ struct iowa_bus *iowa_mem_find_bus(const PCI_IO_ADDR addr) if (vaddr < PHB_IO_BASE || vaddr >= PHB_IO_END) return NULL; + local_irq_save(flags); ptep = find_linux_pte_or_hugepte(init_mm.pgd, vaddr, &hugepage_shift); if (ptep == NULL)@@ -83,6 +85,7 @@ struct iowa_bus *iowa_mem_find_bus(const PCI_IO_ADDR addr) WARN_ON(hugepage_shift); paddr = pte_pfn(*ptep) << PAGE_SHIFT; } + local_irq_restore(flags); bus = iowa_pci_find(vaddr, paddr);
Same.
quoted hunk ↗ jump to hunk
if (bus == NULL)diff --git a/arch/powerpc/kvm/book3s_64_mmu_hv.c b/arch/powerpc/kvm/book3s_64_mmu_hv.c index 534acb3c6c3d..2dccaa6a1113 100644 --- a/arch/powerpc/kvm/book3s_64_mmu_hv.c +++ b/arch/powerpc/kvm/book3s_64_mmu_hv.c@@ -537,14 +537,15 @@ int kvmppc_book3s_hv_page_fault(struct kvm_run *run, struct kvm_vcpu *vcpu, } /* if the guest wants write access, see if that is OK */ if (!writing && hpte_is_writable(r)) { + unsigned long flags; unsigned int hugepage_shift; pte_t *ptep, pte; /* * We need to protect against page table destruction - * while looking up and updating the pte. + * hugepage split and collapse. */ - rcu_read_lock_sched(); + local_irq_save(flags); ptep = find_linux_pte_or_hugepte(current->mm->pgd, hva, &hugepage_shift); if (ptep) {@@ -553,7 +554,7 @@ int kvmppc_book3s_hv_page_fault(struct kvm_run *run, struct kvm_vcpu *vcpu, if (pte_write(pte)) write_ok = 1; } - rcu_read_unlock_sched(); + local_irq_restore(flags); } }
Ack.
quoted hunk ↗ jump to hunk
diff --git a/arch/powerpc/kvm/book3s_hv_rm_mmu.c b/arch/powerpc/kvm/book3s_hv_rm_mmu.c index 625407e4d3b0..b0ad673b35cf 100644 --- a/arch/powerpc/kvm/book3s_hv_rm_mmu.c +++ b/arch/powerpc/kvm/book3s_hv_rm_mmu.c@@ -24,14 +24,19 @@ /* Translate address of a vmalloc'd thing to a linear map address */ static void *real_vmalloc_addr(void *x) { + unsigned long flags; unsigned long addr = (unsigned long) x; pte_t *p; + local_irq_save(flags); p = find_linux_pte_or_hugepte(swapper_pg_dir, addr, NULL); - if (!p || !pte_present(*p)) + if (!p || !pte_present(*p)) { + local_irq_restore(flags); return NULL; + } /* assume we don't have huge pages in vmalloc space... */ addr = (pte_pfn(*p) << PAGE_SHIFT) | (addr & ~PAGE_MASK); + local_irq_restore(flags); return __va(addr); }
This is called in real mode, I don't like the debug stuff that can happen inside local_irq_* in that context. Can you either pounce that to the higher level callers (and so not do it in real mode at all) or use a lower level variant ? (I prefer going for the callers).
quoted hunk ↗ jump to hunk
@@ -134,20 +139,27 @@ static void remove_revmap_chain(struct kvm *kvm, long pte_index, static pte_t lookup_linux_pte_and_update(pgd_t *pgdir, unsigned long hva, int writing, unsigned long *pte_sizep) { - pte_t *ptep; + pte_t *ptep, pte; + unsigned long flags; unsigned long ps = *pte_sizep; unsigned int hugepage_shift; + local_irq_save(flags); ptep = find_linux_pte_or_hugepte(pgdir, hva, &hugepage_shift); if (!ptep) - return __pte(0); + goto err_out; if (hugepage_shift) *pte_sizep = 1ul << hugepage_shift; else *pte_sizep = PAGE_SIZE; if (ps > *pte_sizep) - return __pte(0); - return kvmppc_read_update_linux_pte(ptep, writing, hugepage_shift); + goto err_out; + pte = kvmppc_read_update_linux_pte(ptep, writing, hugepage_shift); + local_irq_restore(flags); + return pte; +err_out: + local_irq_restore(flags); + return __pte(0); }
Ditto
quoted hunk ↗ jump to hunk
static inline void unlock_hpte(__be64 *hpte, unsigned long hpte_v)@@ -211,6 +223,10 @@ long kvmppc_do_h_enter(struct kvm *kvm, unsigned long flags, /* Look up the Linux PTE for the backing page */ pte_size = psize; + /* + * If we had a page table table change after lookup, we would + * retry via mmu_notifier_retry. + */ pte = lookup_linux_pte_and_update(pgdir, hva, writing, &pte_size); if (pte_present(pte) && !pte_protnone(pte)) { if (writing && !pte_write(pte))diff --git a/arch/powerpc/kvm/e500_mmu_host.c b/arch/powerpc/kvm/e500_mmu_host.c index cc536d4a75ef..b50778eee650 100644 --- a/arch/powerpc/kvm/e500_mmu_host.c +++ b/arch/powerpc/kvm/e500_mmu_host.c@@ -335,7 +335,7 @@ static inline int kvmppc_e500_shadow_map(struct kvmppc_vcpu_e500 *vcpu_e500, unsigned long mmu_seq; struct kvm *kvm = vcpu_e500->vcpu.kvm; unsigned long tsize_pages = 0; - pte_t *ptep; + pte_t pte; unsigned int wimg = 0; pgd_t *pgdir;@@ -468,9 +468,16 @@ static inline int kvmppc_e500_shadow_map(struct kvmppc_vcpu_e500 *vcpu_e500, pgdir = vcpu_e500->vcpu.arch.pgdir; - ptep = lookup_linux_ptep(pgdir, hva, &tsize_pages); - if (pte_present(*ptep)) - wimg = (*ptep >> PTE_WIMGE_SHIFT) & MAS2_WIMGE_MASK; + /* + * We are just looking at the wimg bits, so we don't + * care much about the trans splitting bit. + * How about hugepage collapse ? the pfn will change, + * may be we should hold mmap_sem ?. We also don't + * have a page reference count here . + */ + pte = lookup_linux_pte(pgdir, hva, &tsize_pages); + if (pte_present(pte)) + wimg = (pte >> PTE_WIMGE_SHIFT) & MAS2_WIMGE_MASK; else { if (printk_ratelimit()) pr_err("%s: pte not present: gfn %lx, pfn %lx\n",diff --git a/arch/powerpc/mm/hugetlbpage.c b/arch/powerpc/mm/hugetlbpage.c index 7e408bfc7948..92f01649450a 100644 --- a/arch/powerpc/mm/hugetlbpage.c +++ b/arch/powerpc/mm/hugetlbpage.c@@ -108,8 +108,17 @@ int pgd_huge(pgd_t pgd) pte_t *huge_pte_offset(struct mm_struct *mm, unsigned long addr) { - /* Only called for hugetlbfs pages, hence can ignore THP */ - return find_linux_pte_or_hugepte(mm->pgd, addr, NULL); + unsigned long flags; + pte_t *ptep; + /* + * Only called for hugetlbfs pages, hence can ignore THP + * The save and restore are there only for avoiding the warning + * in find_linux_pte_or_hugepte(). + */ + local_irq_save(flags); + ptep = find_linux_pte_or_hugepte(mm->pgd, addr, NULL); + local_irq_restore(flags); + return ptep; } static int __hugepte_alloc(struct mm_struct *mm, hugepd_t *hpdp,@@ -681,28 +690,35 @@ void hugetlb_free_pgd_range(struct mmu_gather *tlb, } while (addr = next, addr != end); } +/* + * We are holding mmap_sem, so a parallel huge page collapse cannot run. + * To prevent hugepage split, disable irq. + */ struct page * follow_huge_addr(struct mm_struct *mm, unsigned long address, int write) { pte_t *ptep; struct page *page; unsigned shift; - unsigned long mask; + unsigned long mask, flags; /* * Transparent hugepages are handled by generic code. We can skip them * here. */ + local_irq_save(flags); ptep = find_linux_pte_or_hugepte(mm->pgd, address, &shift); /* Verify it is a huge page else bail. */ - if (!ptep || !shift || pmd_trans_huge(*(pmd_t *)ptep)) + if (!ptep || !shift || pmd_trans_huge(*(pmd_t *)ptep)) { + local_irq_restore(flags); return ERR_PTR(-EINVAL); - + } mask = (1UL << shift) - 1; page = pte_page(*ptep); if (page) page += (address & mask) / PAGE_SIZE; + local_irq_restore(flags); return page; }@@ -949,6 +965,8 @@ void flush_dcache_icache_hugepage(struct page *page) * * So long as we atomically load page table pointers we are safe against teardown, * we can follow the address down to the the page and take a ref on it. + * Should be called with irq disabled. If is is splitting hugepage, we will return + * NULL; */ pte_t *find_linux_pte_or_hugepte(pgd_t *pgdir, unsigned long ea, unsigned *shift)@@ -963,6 +981,11 @@ pte_t *find_linux_pte_or_hugepte(pgd_t *pgdir, unsigned long ea, unsigned *shift if (shift) *shift = 0; + if (!irqs_disabled()) {
Might want to use the arch_* variant for similar "beware of generic debug stuff in real mode" reasons...
quoted hunk ↗ jump to hunk
+ pr_info("%s called with irq enabled\n", __func__); + dump_stack(); + } + pgdp = pgdir + pgd_index(ea); pgd = ACCESS_ONCE(*pgdp); /*diff --git a/arch/powerpc/perf/callchain.c b/arch/powerpc/perf/callchain.c index 2396dda282cd..173e4abc0c56 100644 --- a/arch/powerpc/perf/callchain.c +++ b/arch/powerpc/perf/callchain.c@@ -125,6 +125,7 @@ static int read_user_stack_slow(void __user *ptr, void *ret, int nb) if (!pgdir) return -EFAULT; + /* FIXME!! I guess we come with irq disabled here ? */
Not that I know of, that's not guaranteed.
ptep = find_linux_pte_or_hugepte(pgdir, addr, &shift); if (!shift) shift = PAGE_SHIFT;