summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Wilson <chris@chris-wilson.co.uk>2016-10-15 09:54:01 +0100
committerChris Wilson <chris@chris-wilson.co.uk>2017-03-27 14:27:27 +0100
commit37064f5b4d3f1bf88785424264f6630a457afe95 (patch)
treefa69c35779cf403b845616fa679968a2634b439d
parentc02ed2e75ef4c74e41e421acb4ef1494671585e8 (diff)
mm/vmalloc: Replace opencoded 4-level page walkers
Rather than open-code the intricacies of walking the 4-level page tables, use the generic page table walker apply_to_page_range() instead. The important change is that it now cleans up after an unsuccessful insertion and propagates the correct error. The current failure may lead to a WARN if we encounter ENOMEM in one vmap_pte_range() and later retry with the same page range. WARNING: CPU: 0 PID: 605 at mm/vmalloc.c:136 vmap_page_range_noflush+0x2c1/0x340 i.e. WARN_ON(!pte_none(*pte)) v2: Don't convert the vunmap code over to apply_to_page_range() as it may try to allocate during atomic sections, such as exiting a task: [ 9.837563] [<ffffffff810519b0>] pte_alloc_one_kernel+0x10/0x20 [ 9.837568] [<ffffffff811a7486>] __pte_alloc_kernel+0x16/0xa0 [ 9.837572] [<ffffffff811aaa76>] apply_to_page_range+0x3f6/0x460 [ 9.837576] [<ffffffff811b8888>] free_unmap_vmap_area_noflush+0x28/0x40 [ 9.837579] [<ffffffff811b9dcd>] remove_vm_area+0x4d/0x60 [ 9.837582] [<ffffffff811b9e09>] __vunmap+0x29/0x130 [ 9.837585] [<ffffffff811b9f7d>] vfree+0x3d/0x90 [ 9.837589] [<ffffffff8107ace6>] put_task_stack+0x76/0x130 References: https://bugs.freedesktop.org/show_bug.cgi?id=98269 Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: David Rientjes <rientjes@google.com> Cc: Vladimir Davydov <vdavydov.dev@gmail.com> Cc: Johannes Weiner <hannes@cmpxchg.org> Cc: Mel Gorman <mgorman@techsingularity.net> Cc: Wang Xiaoqiang <wangxq10@lzu.edu.cn> Cc: Jerome Marchand <jmarchan@redhat.com> Cc: Joonsoo Kim <iamjoonsoo.kim@lge.com> Cc: linux-mm@kvack.org
-rw-r--r--mm/vmalloc.c108
1 files changed, 26 insertions, 82 deletions
diff --git a/mm/vmalloc.c b/mm/vmalloc.c
index 0b057628a7ba..8cc8a7a40296 100644
--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c
@@ -133,80 +133,27 @@ static void vunmap_page_range(unsigned long addr, unsigned long end)
} while (pgd++, addr = next, addr != end);
}
-static int vmap_pte_range(pmd_t *pmd, unsigned long addr,
- unsigned long end, pgprot_t prot, struct page **pages, int *nr)
-{
- pte_t *pte;
-
- /*
- * nr is a running index into the array which helps higher level
- * callers keep track of where we're up to.
- */
-
- pte = pte_alloc_kernel(pmd, addr);
- if (!pte)
- return -ENOMEM;
- do {
- struct page *page = pages[*nr];
-
- if (WARN_ON(!pte_none(*pte)))
- return -EBUSY;
- if (WARN_ON(!page))
- return -ENOMEM;
- set_pte_at(&init_mm, addr, pte, mk_pte(page, prot));
- (*nr)++;
- } while (pte++, addr += PAGE_SIZE, addr != end);
- return 0;
-}
+struct vmap_page {
+ pgprot_t prot;
+ struct page **pages;
+ unsigned long count;
+};
-static int vmap_pmd_range(pud_t *pud, unsigned long addr,
- unsigned long end, pgprot_t prot, struct page **pages, int *nr)
+static int vmap_page(pte_t *pte, pgtable_t token,
+ unsigned long addr, void *data)
{
- pmd_t *pmd;
- unsigned long next;
-
- pmd = pmd_alloc(&init_mm, pud, addr);
- if (!pmd)
- return -ENOMEM;
- do {
- next = pmd_addr_end(addr, end);
- if (vmap_pte_range(pmd, addr, next, prot, pages, nr))
- return -ENOMEM;
- } while (pmd++, addr = next, addr != end);
- return 0;
-}
+ struct vmap_page *v = data;
+ struct page *page;
-static int vmap_pud_range(p4d_t *p4d, unsigned long addr,
- unsigned long end, pgprot_t prot, struct page **pages, int *nr)
-{
- pud_t *pud;
- unsigned long next;
+ if (WARN_ON(!pte_none(*pte)))
+ return -EBUSY;
- pud = pud_alloc(&init_mm, p4d, addr);
- if (!pud)
+ page = v->pages[v->count];
+ if (WARN_ON(!page))
return -ENOMEM;
- do {
- next = pud_addr_end(addr, end);
- if (vmap_pmd_range(pud, addr, next, prot, pages, nr))
- return -ENOMEM;
- } while (pud++, addr = next, addr != end);
- return 0;
-}
-static int vmap_p4d_range(pgd_t *pgd, unsigned long addr,
- unsigned long end, pgprot_t prot, struct page **pages, int *nr)
-{
- p4d_t *p4d;
- unsigned long next;
-
- p4d = p4d_alloc(&init_mm, pgd, addr);
- if (!p4d)
- return -ENOMEM;
- do {
- next = p4d_addr_end(addr, end);
- if (vmap_pud_range(p4d, addr, next, prot, pages, nr))
- return -ENOMEM;
- } while (p4d++, addr = next, addr != end);
+ set_pte_at(&init_mm, addr, pte, mk_pte(page, v->prot));
+ v->count++;
return 0;
}
@@ -219,22 +166,19 @@ static int vmap_p4d_range(pgd_t *pgd, unsigned long addr,
static int vmap_page_range_noflush(unsigned long start, unsigned long end,
pgprot_t prot, struct page **pages)
{
- pgd_t *pgd;
- unsigned long next;
- unsigned long addr = start;
- int err = 0;
- int nr = 0;
+ struct vmap_page v = { prot, pages };
+ int err;
- BUG_ON(addr >= end);
- pgd = pgd_offset_k(addr);
- do {
- next = pgd_addr_end(addr, end);
- err = vmap_p4d_range(pgd, addr, next, prot, pages, &nr);
- if (err)
- return err;
- } while (pgd++, addr = next, addr != end);
+ if ((end - start) >> PAGE_SHIFT > INT_MAX)
+ return -EINVAL;
+
+ err = apply_to_page_range(&init_mm, start, end - start, vmap_page, &v);
+ if (unlikely(err)) {
+ vunmap_page_range(start, start + (v.count << PAGE_SHIFT));
+ return err;
+ }
- return nr;
+ return v.count;
}
static int vmap_page_range(unsigned long start, unsigned long end,