Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions include/linux/mm.h
Original file line number Diff line number Diff line change
Expand Up @@ -3695,14 +3695,7 @@ static inline void __pagetable_free(struct ptdesc *pt)
__free_pages(page, compound_order(page));
}

#ifdef CONFIG_ASYNC_KERNEL_PGTABLE_FREE
void pagetable_free_kernel(struct ptdesc *pt);
#else
static inline void pagetable_free_kernel(struct ptdesc *pt)
{
__pagetable_free(pt);
}
#endif
/**
* pagetable_free - Free pagetables
* @pt: The page table descriptor
Expand Down
35 changes: 30 additions & 5 deletions mm/pagewalk.c
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ int walk_page_range(struct mm_struct *mm, unsigned long start,
* Note: Be careful to walk the kernel pages tables, the caller may be need to
* take other effective approaches (mmap lock may be insufficient) to prevent
* the intermediate kernel page tables belonging to the specified address range
* from being freed (e.g. memory hot-remove).
* from being freed (e.g. memory hot-remove, vmap huge page promotion).
*/
int walk_kernel_page_table_range(unsigned long start, unsigned long end,
const struct mm_walk_ops *ops, pgd_t *pgd, void *private)
Expand All @@ -643,7 +643,7 @@ int walk_kernel_page_table_range(unsigned long start, unsigned long end,
* Use this function to walk the kernel page tables locklessly. It should be
* guaranteed that the caller has exclusive access over the range they are
* operating on - that there should be no concurrent access, for example,
* changing permissions for vmalloc objects.
* changing permissions for vmalloc objects, or vmap huge page promotion.
*/
int walk_kernel_page_table_range_lockless(unsigned long start, unsigned long end,
const struct mm_walk_ops *ops, pgd_t *pgd, void *private)
Expand Down Expand Up @@ -692,9 +692,34 @@ int walk_page_range_debug(struct mm_struct *mm, unsigned long start,
};

/* For convenience, we allow traversal of kernel mappings. */
if (mm == &init_mm)
return walk_kernel_page_table_range(start, end, ops,
pgd, private);
if (mm == &init_mm) {
unsigned long addr = start;

/*
* Walk in bounded chunks so the RCU read lock is never held
* across the whole kernel address space. A kernel page table
* freed via pagetable_free_kernel() stays valid until the walk
* that may have observed it drops the lock; releasing the lock
* between chunks is safe as no page table pointer is held
* across the gap.
*/
while (addr < end) {
unsigned long next = min(end, ALIGN(addr + 1, PGDIR_SIZE));
int err;

rcu_read_lock();
err = walk_kernel_page_table_range(addr, next, ops,
pgd, private);
rcu_read_unlock();
if (err)
return err;

addr = next;
cond_resched();
}
return 0;
}

if (start >= end || !walk.mm)
return -EINVAL;
if (!check_ops_safe(ops))
Expand Down
22 changes: 21 additions & 1 deletion mm/pgtable-generic.c
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,13 @@ pte_t *pte_offset_map_lock(struct mm_struct *mm, pmd_t *pmd,
goto again;
}

static void kernel_pgtable_free_rcu(struct rcu_head *head)
{
struct ptdesc *pt = container_of(head, struct ptdesc, pt_rcu_head);

__pagetable_free(pt);
}

#ifdef CONFIG_ASYNC_KERNEL_PGTABLE_FREE
static void kernel_pgtable_work_func(struct work_struct *work);

Expand All @@ -434,8 +441,15 @@ static void kernel_pgtable_work_func(struct work_struct *work)
spin_unlock(&kernel_pgtable_work.lock);

iommu_sva_invalidate_kva_range(PAGE_OFFSET, TLB_FLUSH_ALL);

/*
* Debug walkers (ptdump) may walk ranges they do not own and race this
* free, so they walk under rcu_read_lock(). Free after a grace period:
* a walker either already saw the cleared PMD, or keeps the page alive
* until it drops the RCU lock.
*/
list_for_each_entry_safe(pt, next, &page_list, pt_list)
__pagetable_free(pt);
call_rcu(&pt->pt_rcu_head, kernel_pgtable_free_rcu);
}

void pagetable_free_kernel(struct ptdesc *pt)
Expand All @@ -446,4 +460,10 @@ void pagetable_free_kernel(struct ptdesc *pt)

schedule_work(&kernel_pgtable_work.work);
}
#else
void pagetable_free_kernel(struct ptdesc *pt)
{
/* Defer the free by a grace period; see kernel_pgtable_work_func(). */
call_rcu(&pt->pt_rcu_head, kernel_pgtable_free_rcu);
}
#endif
2 changes: 0 additions & 2 deletions mm/ptdump.c
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,11 @@ void ptdump_walk_pgd(struct ptdump_state *st, struct mm_struct *mm, pgd_t *pgd)
const struct ptdump_range *range = st->range;

get_online_mems();
mmap_write_lock(mm);
while (range->start != range->end) {
walk_page_range_debug(mm, range->start, range->end,
&ptdump_ops, pgd, st);
range++;
}
mmap_write_unlock(mm);
put_online_mems();

/* Flush out the last page */
Expand Down