mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-09-18 23:09:29 +02:00
Merge tag 'mm-hotfixes-stable-2026-08-06-18-44' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm
Pull MM fixes from Andrew Morton:
"17 hotfixes. 15 are cc:stable. 16 are for MM.
There's a patch series from Lorenzo "mm: fix UAF caused by race
between ptdump and vmap pgtable freeing" which addresses a quite old
bug in the ptdump code.
And another series also from Lorenzo which fixes a four year old bug
in the huge_zero_folio handling.
A series from SJ fixes a few possible divide-by-zero issues which
Sashiko sniffed out. And a series which fixes handling of the
commit_inputs parameters.
The remainder are singletons, please see their changelogs for details"
* tag 'mm-hotfixes-stable-2026-08-06-18-44' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm:
mm/damon: adjust isolated pages stat for DAMOS_MIGRATE_{HOT,COLD}
mm/damon/ops-common: putback folios on invalid migrate nid
mm/huge_memory: initialise workingset state before folio split
mm/page_table_check: skip special zero mappings
mm/damon/lru_sort: skip damon_call() if ctx has not started
mm/damon/reclaim: skip damon_call() if ctx has not started
mm/damon/lru_sort: error out for >10000 active_mem_bp
samples/damon/mtier: error out for zero quota goal target values
mailmap: map old addresses to Danila Tikhonov
mm/huge_memory: separate out CONFIG_PERSISTENT_HUGE_ZERO_FOLIO logic
mm/huge_memory: fix huge_zero_pfn race
MAINTAINERS: update address for Brendan Jackman
mm/filemap: __filemap_add_folio() restore index before retrying
microblaze: restore the page alignment of swapper_pg_dir
arm64: remove redundant concurrent ptdump UAF mitigation
mm/ptdump: always stabilise against page table freeing using init_mm
mm/vmalloc: acquire init_mm lock on huge vmap to avoid ptdump UAF
This commit is contained in:
@@ -171,6 +171,7 @@ Boris Brezillon <bbrezillon@kernel.org> <b.brezillon@overkiz.com>
|
||||
Boris Brezillon <bbrezillon@kernel.org> <boris.brezillon@bootlin.com>
|
||||
Boris Brezillon <bbrezillon@kernel.org> <boris.brezillon@free-electrons.com>
|
||||
Brendan Higgins <brendan.higgins@linux.dev> <brendanhiggins@google.com>
|
||||
Brendan Jackman <brendan.jackman@linux.dev> <jackmanb@google.com>
|
||||
Brian Avery <b.avery@hp.com>
|
||||
Brian Cain <bcain@kernel.org> <brian.cain@oss.qualcomm.com>
|
||||
Brian Cain <bcain@kernel.org> <bcain@quicinc.com>
|
||||
@@ -233,6 +234,8 @@ Daniel Lezcano <daniel.lezcano@kernel.org> <daniel.lezcano@linexp.org>
|
||||
Daniel Lezcano <daniel.lezcano@kernel.org> <dlezcano@fr.ibm.com>
|
||||
Daniel Thompson <danielt@kernel.org> <daniel.thompson@linaro.org>
|
||||
Daniele Alessandrelli <daniele.alessandrelli@gmail.com> <daniele.alessandrelli@intel.com>
|
||||
Danila Tikhonov <danila@mainlining.org> <danila@jiaxyga.com>
|
||||
Danila Tikhonov <danila@mainlining.org> <JIaxyga@protonmail.com>
|
||||
Danilo Krummrich <dakr@kernel.org> <dakr@redhat.com>
|
||||
David Brownell <david-b@pacbell.net>
|
||||
David Collins <quic_collinsd@quicinc.com> <collinsd@codeaurora.org>
|
||||
|
||||
+1
-1
@@ -17152,7 +17152,7 @@ M: Andrew Morton <akpm@linux-foundation.org>
|
||||
M: Vlastimil Babka <vbabka@kernel.org>
|
||||
R: Suren Baghdasaryan <surenb@google.com>
|
||||
R: Michal Hocko <mhocko@suse.com>
|
||||
R: Brendan Jackman <jackmanb@google.com>
|
||||
R: Brendan Jackman <brendan.jackman@linux.dev>
|
||||
R: Johannes Weiner <hannes@cmpxchg.org>
|
||||
R: Zi Yan <ziy@nvidia.com>
|
||||
L: linux-mm@kvack.org
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
|
||||
#include <linux/ptdump.h>
|
||||
|
||||
DECLARE_STATIC_KEY_FALSE(arm64_ptdump_lock_key);
|
||||
|
||||
#ifdef CONFIG_PTDUMP
|
||||
|
||||
#include <linux/mm_types.h>
|
||||
|
||||
+4
-39
@@ -49,8 +49,6 @@
|
||||
#define NO_CONT_MAPPINGS BIT(1)
|
||||
#define NO_EXEC_MAPPINGS BIT(2) /* assumes FEAT_HPDS is not used */
|
||||
|
||||
DEFINE_STATIC_KEY_FALSE(arm64_ptdump_lock_key);
|
||||
|
||||
u64 kimage_voffset __ro_after_init;
|
||||
EXPORT_SYMBOL(kimage_voffset);
|
||||
|
||||
@@ -1864,8 +1862,7 @@ int pmd_clear_huge(pmd_t *pmdp)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int __pmd_free_pte_page(pmd_t *pmdp, unsigned long addr,
|
||||
bool acquire_mmap_lock)
|
||||
int pmd_free_pte_page(pmd_t *pmdp, unsigned long addr)
|
||||
{
|
||||
pte_t *table;
|
||||
pmd_t pmd;
|
||||
@@ -1877,25 +1874,13 @@ static int __pmd_free_pte_page(pmd_t *pmdp, unsigned long addr,
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* See comment in pud_free_pmd_page for static key logic */
|
||||
table = pte_offset_kernel(pmdp, addr);
|
||||
pmd_clear(pmdp);
|
||||
__flush_tlb_kernel_pgtable(addr);
|
||||
if (static_branch_unlikely(&arm64_ptdump_lock_key) && acquire_mmap_lock) {
|
||||
mmap_read_lock(&init_mm);
|
||||
mmap_read_unlock(&init_mm);
|
||||
}
|
||||
|
||||
pte_free_kernel(NULL, table);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int pmd_free_pte_page(pmd_t *pmdp, unsigned long addr)
|
||||
{
|
||||
/* If ptdump is walking the pagetables, acquire init_mm.mmap_lock */
|
||||
return __pmd_free_pte_page(pmdp, addr, /* acquire_mmap_lock = */ true);
|
||||
}
|
||||
|
||||
int pud_free_pmd_page(pud_t *pudp, unsigned long addr)
|
||||
{
|
||||
pmd_t *table;
|
||||
@@ -1911,36 +1896,16 @@ int pud_free_pmd_page(pud_t *pudp, unsigned long addr)
|
||||
}
|
||||
|
||||
table = pmd_offset(pudp, addr);
|
||||
|
||||
/*
|
||||
* Our objective is to prevent ptdump from reading a PMD table which has
|
||||
* been freed. In this race, if pud_free_pmd_page observes the key on
|
||||
* (which got flipped by ptdump) then the mmap lock sequence here will,
|
||||
* as a result of the mmap write lock/unlock sequence in ptdump, give
|
||||
* us the correct synchronization. If not, this means that ptdump has
|
||||
* yet not started walking the pagetables - the sequence of barriers
|
||||
* issued by __flush_tlb_kernel_pgtable() guarantees that ptdump will
|
||||
* observe an empty PUD.
|
||||
*/
|
||||
pud_clear(pudp);
|
||||
__flush_tlb_kernel_pgtable(addr);
|
||||
if (static_branch_unlikely(&arm64_ptdump_lock_key)) {
|
||||
mmap_read_lock(&init_mm);
|
||||
mmap_read_unlock(&init_mm);
|
||||
}
|
||||
|
||||
pmdp = table;
|
||||
next = addr;
|
||||
end = addr + PUD_SIZE;
|
||||
do {
|
||||
if (pmd_present(pmdp_get(pmdp)))
|
||||
/*
|
||||
* PMD has been isolated, so ptdump won't see it. No
|
||||
* need to acquire init_mm.mmap_lock.
|
||||
*/
|
||||
__pmd_free_pte_page(pmdp, next, /* acquire_mmap_lock = */ false);
|
||||
pmd_free_pte_page(pmdp, next);
|
||||
} while (pmdp++, next += PMD_SIZE, next != end);
|
||||
|
||||
pud_clear(pudp);
|
||||
__flush_tlb_kernel_pgtable(addr);
|
||||
pmd_free(NULL, table);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -283,13 +283,6 @@ void note_page_flush(struct ptdump_state *pt_st)
|
||||
note_page(pt_st, 0, -1, pte_val(pte_zero));
|
||||
}
|
||||
|
||||
static void arm64_ptdump_walk_pgd(struct ptdump_state *st, struct mm_struct *mm)
|
||||
{
|
||||
static_branch_inc(&arm64_ptdump_lock_key);
|
||||
ptdump_walk_pgd(st, mm, NULL);
|
||||
static_branch_dec(&arm64_ptdump_lock_key);
|
||||
}
|
||||
|
||||
void ptdump_walk(struct seq_file *s, struct ptdump_info *info)
|
||||
{
|
||||
unsigned long end = ~0UL;
|
||||
@@ -318,7 +311,7 @@ void ptdump_walk(struct seq_file *s, struct ptdump_info *info)
|
||||
}
|
||||
};
|
||||
|
||||
arm64_ptdump_walk_pgd(&st.ptdump, info->mm);
|
||||
ptdump_walk_pgd(&st.ptdump, info->mm, NULL);
|
||||
}
|
||||
|
||||
static void __init ptdump_initialize(void)
|
||||
@@ -360,7 +353,7 @@ bool ptdump_check_wx(void)
|
||||
}
|
||||
};
|
||||
|
||||
arm64_ptdump_walk_pgd(&st.ptdump, &init_mm);
|
||||
ptdump_walk_pgd(&st.ptdump, &init_mm, NULL);
|
||||
|
||||
if (st.wx_pages || st.uxn_pages) {
|
||||
pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found, %lu non-UXN pages found\n",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
#include <asm/processor.h>
|
||||
|
||||
.section .data
|
||||
/* The MMU requires a page aligned page directory. */
|
||||
.align 12
|
||||
.global swapper_pg_dir
|
||||
swapper_pg_dir:
|
||||
.space PAGE_SIZE
|
||||
|
||||
@@ -621,6 +621,7 @@ static inline void mmap_read_unlock(struct mm_struct *mm)
|
||||
|
||||
DEFINE_GUARD(mmap_read_lock, struct mm_struct *,
|
||||
mmap_read_lock(_T), mmap_read_unlock(_T))
|
||||
DEFINE_GUARD_COND(mmap_read_lock, _try, mmap_read_trylock(_T))
|
||||
|
||||
static inline void mmap_read_unlock_non_owner(struct mm_struct *mm)
|
||||
{
|
||||
|
||||
+8
-5
@@ -233,6 +233,8 @@ static int damon_lru_sort_add_quota_goals(struct damos *hot_scheme,
|
||||
|
||||
if (!active_mem_bp)
|
||||
return 0;
|
||||
if (10000 < active_mem_bp)
|
||||
return -EINVAL;
|
||||
goal = damos_new_quota_goal(DAMOS_QUOTA_ACTIVE_MEM_BP, active_mem_bp);
|
||||
if (!goal)
|
||||
return -ENOMEM;
|
||||
@@ -349,6 +351,8 @@ static int damon_lru_sort_commit_inputs_fn(void *arg)
|
||||
return damon_lru_sort_apply_parameters();
|
||||
}
|
||||
|
||||
static bool damon_lru_sort_damon_has_started;
|
||||
|
||||
static int damon_lru_sort_commit_inputs_store(const char *val,
|
||||
const struct kernel_param *kp)
|
||||
{
|
||||
@@ -369,11 +373,8 @@ static int damon_lru_sort_commit_inputs_store(const char *val,
|
||||
if (!commit_inputs_request)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Skip damon_call() if ctx is not initialized to avoid
|
||||
* NULL pointer dereference.
|
||||
*/
|
||||
if (!ctx)
|
||||
/* Skip damon_call() if ctx has not successfully started. */
|
||||
if (!damon_lru_sort_damon_has_started)
|
||||
return -EINVAL;
|
||||
|
||||
err = damon_call(ctx, &control);
|
||||
@@ -424,6 +425,8 @@ static int damon_lru_sort_turn(bool on)
|
||||
err = damon_start(&ctx, 1, true);
|
||||
if (err)
|
||||
return err;
|
||||
if (!damon_lru_sort_damon_has_started)
|
||||
damon_lru_sort_damon_has_started = true;
|
||||
return damon_call(ctx, &call_control);
|
||||
}
|
||||
|
||||
|
||||
+12
-1
@@ -375,6 +375,8 @@ keep:
|
||||
while (!list_empty(folio_list)) {
|
||||
folio = lru_to_folio(folio_list);
|
||||
list_del(&folio->lru);
|
||||
node_stat_sub_folio(folio, NR_ISOLATED_ANON +
|
||||
folio_is_file_lru(folio));
|
||||
folio_putback_lru(folio);
|
||||
}
|
||||
|
||||
@@ -392,8 +394,17 @@ unsigned long damon_migrate_pages(struct list_head *folio_list, int target_nid)
|
||||
return nr_migrated;
|
||||
|
||||
if (target_nid < 0 || target_nid >= MAX_NUMNODES ||
|
||||
!node_state(target_nid, N_MEMORY))
|
||||
!node_state(target_nid, N_MEMORY)) {
|
||||
while (!list_empty(folio_list)) {
|
||||
struct folio *folio = lru_to_folio(folio_list);
|
||||
|
||||
list_del(&folio->lru);
|
||||
node_stat_sub_folio(folio, NR_ISOLATED_ANON +
|
||||
folio_is_file_lru(folio));
|
||||
folio_putback_lru(folio);
|
||||
}
|
||||
return nr_migrated;
|
||||
}
|
||||
|
||||
noreclaim_flag = memalloc_noreclaim_save();
|
||||
|
||||
|
||||
@@ -350,6 +350,8 @@ static unsigned long damon_pa_migrate(struct damon_region *r,
|
||||
|
||||
if (!folio_isolate_lru(folio))
|
||||
goto put_folio;
|
||||
node_stat_add_folio(folio, NR_ISOLATED_ANON +
|
||||
folio_is_file_lru(folio));
|
||||
list_add(&folio->lru, &folio_list);
|
||||
put_folio:
|
||||
addr += folio_size(folio);
|
||||
|
||||
+6
-5
@@ -276,6 +276,8 @@ static int damon_reclaim_commit_inputs_fn(void *arg)
|
||||
return damon_reclaim_apply_parameters();
|
||||
}
|
||||
|
||||
static bool damon_reclaim_damon_has_started;
|
||||
|
||||
static int damon_reclaim_commit_inputs_store(const char *val,
|
||||
const struct kernel_param *kp)
|
||||
{
|
||||
@@ -296,11 +298,8 @@ static int damon_reclaim_commit_inputs_store(const char *val,
|
||||
if (!commit_inputs_request)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Skip damon_call() if ctx is not initialized to avoid
|
||||
* NULL pointer dereference.
|
||||
*/
|
||||
if (!ctx)
|
||||
/* Skip damon_call() if ctx has not successfully started. */
|
||||
if (!damon_reclaim_damon_has_started)
|
||||
return -EINVAL;
|
||||
|
||||
err = damon_call(ctx, &control);
|
||||
@@ -347,6 +346,8 @@ static int damon_reclaim_turn(bool on)
|
||||
err = damon_start(&ctx, 1, true);
|
||||
if (err)
|
||||
return err;
|
||||
if (!damon_reclaim_damon_has_started)
|
||||
damon_reclaim_damon_has_started = true;
|
||||
return damon_call(ctx, &call_control);
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -649,7 +649,8 @@ static void damos_va_migrate_dests_add(struct folio *folio,
|
||||
isolate:
|
||||
if (!folio_isolate_lru(folio))
|
||||
return;
|
||||
|
||||
node_stat_add_folio(folio, NR_ISOLATED_ANON +
|
||||
folio_is_file_lru(folio));
|
||||
list_add(&folio->lru, &migration_lists[i]);
|
||||
}
|
||||
|
||||
|
||||
@@ -931,6 +931,12 @@ unlock:
|
||||
|
||||
if (!xas_nomem(&xas, gfp))
|
||||
break;
|
||||
|
||||
/*
|
||||
* Lock has been dropped: start again with the original index
|
||||
* and order (but now with the memory reserved by xas_nomem()).
|
||||
*/
|
||||
xas_set_order(&xas, index, forder);
|
||||
}
|
||||
|
||||
if (xas_error(&xas))
|
||||
|
||||
+119
-74
@@ -41,6 +41,7 @@
|
||||
#include <linux/pgalloc.h>
|
||||
#include <linux/pgalloc_tag.h>
|
||||
#include <linux/pagewalk.h>
|
||||
#include <linux/cleanup.h>
|
||||
|
||||
#include <asm/tlb.h>
|
||||
#include "internal.h"
|
||||
@@ -77,9 +78,15 @@ static unsigned long deferred_split_scan(struct shrinker *shrink,
|
||||
struct shrink_control *sc);
|
||||
static bool split_underused_thp = true;
|
||||
|
||||
static atomic_t huge_zero_refcount;
|
||||
#define HUGE_ZERO_UNSET_PFN (~0UL)
|
||||
struct folio *huge_zero_folio __read_mostly;
|
||||
unsigned long huge_zero_pfn __read_mostly = ~0UL;
|
||||
unsigned long huge_zero_pfn __read_mostly = HUGE_ZERO_UNSET_PFN;
|
||||
#ifndef CONFIG_PERSISTENT_HUGE_ZERO_FOLIO
|
||||
static atomic_t huge_zero_refcount;
|
||||
static DEFINE_SPINLOCK(huge_zero_lock);
|
||||
static struct shrinker *huge_zero_folio_shrinker;
|
||||
#endif
|
||||
|
||||
unsigned long huge_anon_orders_always __read_mostly;
|
||||
unsigned long huge_anon_orders_madvise __read_mostly;
|
||||
unsigned long huge_anon_orders_inherit __read_mostly;
|
||||
@@ -221,33 +228,74 @@ unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma,
|
||||
return orders;
|
||||
}
|
||||
|
||||
static bool get_huge_zero_folio(void)
|
||||
static struct folio *alloc_huge_zero_folio(void)
|
||||
{
|
||||
struct folio *zero_folio;
|
||||
retry:
|
||||
if (likely(atomic_inc_not_zero(&huge_zero_refcount)))
|
||||
return true;
|
||||
|
||||
zero_folio = folio_alloc((GFP_TRANSHUGE | __GFP_ZERO | __GFP_ZEROTAGS) &
|
||||
~__GFP_MOVABLE,
|
||||
HPAGE_PMD_ORDER);
|
||||
if (!zero_folio) {
|
||||
count_vm_event(THP_ZERO_PAGE_ALLOC_FAILED);
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
/* Ensure zero folio won't have large_rmappable flag set. */
|
||||
folio_clear_large_rmappable(zero_folio);
|
||||
preempt_disable();
|
||||
if (cmpxchg(&huge_zero_folio, NULL, zero_folio)) {
|
||||
preempt_enable();
|
||||
folio_clear_large_rmappable(zero_folio); /* Explicitly not rmappable. */
|
||||
return zero_folio;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PERSISTENT_HUGE_ZERO_FOLIO
|
||||
static int __init huge_zero_init(void)
|
||||
{
|
||||
huge_zero_folio = alloc_huge_zero_folio();
|
||||
if (!huge_zero_folio) {
|
||||
pr_warn("Allocating persistent huge zero folio failed\n");
|
||||
} else {
|
||||
huge_zero_pfn = folio_pfn(huge_zero_folio);
|
||||
count_vm_event(THP_ZERO_PAGE_ALLOC);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __init huge_zero_shrinker_exit(void)
|
||||
{
|
||||
}
|
||||
|
||||
struct folio *mm_get_huge_zero_folio(struct mm_struct *mm)
|
||||
{
|
||||
return huge_zero_folio;
|
||||
}
|
||||
|
||||
void mm_put_huge_zero_folio(struct mm_struct *mm)
|
||||
{
|
||||
}
|
||||
#else
|
||||
static bool get_huge_zero_folio(void)
|
||||
{
|
||||
struct folio *zero_folio;
|
||||
|
||||
/* Paired with atomic_set_release(). */
|
||||
if (likely(atomic_inc_not_zero(&huge_zero_refcount)))
|
||||
return true;
|
||||
|
||||
zero_folio = alloc_huge_zero_folio();
|
||||
if (unlikely(!zero_folio))
|
||||
return false;
|
||||
|
||||
/* Paired with critical section in shrink_huge_zero_folio_scan(). */
|
||||
spin_lock(&huge_zero_lock);
|
||||
if (huge_zero_folio) {
|
||||
/* Somebody else already installed it. */
|
||||
atomic_inc(&huge_zero_refcount);
|
||||
spin_unlock(&huge_zero_lock);
|
||||
folio_put(zero_folio);
|
||||
goto retry;
|
||||
return true;
|
||||
}
|
||||
WRITE_ONCE(huge_zero_folio, zero_folio);
|
||||
WRITE_ONCE(huge_zero_pfn, folio_pfn(zero_folio));
|
||||
/* Paired with atomic_inc_not_zero(). +1 for shrinker pin. */
|
||||
atomic_set_release(&huge_zero_refcount, 2);
|
||||
spin_unlock(&huge_zero_lock);
|
||||
|
||||
/* We take additional reference here. It will be put back by shrinker */
|
||||
atomic_set(&huge_zero_refcount, 2);
|
||||
preempt_enable();
|
||||
count_vm_event(THP_ZERO_PAGE_ALLOC);
|
||||
return true;
|
||||
}
|
||||
@@ -258,14 +306,59 @@ static void put_huge_zero_folio(void)
|
||||
* Counter should never go to zero here. Only shrinker can put
|
||||
* last reference.
|
||||
*/
|
||||
BUG_ON(atomic_dec_and_test(&huge_zero_refcount));
|
||||
WARN_ON_ONCE(atomic_dec_and_test(&huge_zero_refcount));
|
||||
}
|
||||
|
||||
static unsigned long shrink_huge_zero_folio_count(struct shrinker *shrink,
|
||||
struct shrink_control *sc)
|
||||
{
|
||||
/* we can free zero page only if last reference remains */
|
||||
return atomic_read(&huge_zero_refcount) == 1 ? HPAGE_PMD_NR : 0;
|
||||
}
|
||||
|
||||
static unsigned long shrink_huge_zero_folio_scan(struct shrinker *shrink,
|
||||
struct shrink_control *sc)
|
||||
{
|
||||
struct folio *zero_folio;
|
||||
|
||||
/* Paired with critical section in get_huge_zero_folio(). */
|
||||
scoped_guard(spinlock, &huge_zero_lock) {
|
||||
/* Paired with atomic_inc_not_zero() in get_huge_zero_folio(). */
|
||||
if (atomic_cmpxchg(&huge_zero_refcount, 1, 0) != 1)
|
||||
return 0;
|
||||
|
||||
zero_folio = huge_zero_folio;
|
||||
VM_WARN_ON_ONCE(!zero_folio);
|
||||
WRITE_ONCE(huge_zero_folio, NULL);
|
||||
WRITE_ONCE(huge_zero_pfn, HUGE_ZERO_UNSET_PFN);
|
||||
}
|
||||
|
||||
folio_put(zero_folio);
|
||||
return HPAGE_PMD_NR;
|
||||
}
|
||||
|
||||
static int __init huge_zero_init(void)
|
||||
{
|
||||
huge_zero_folio_shrinker = shrinker_alloc(0, "thp-zero");
|
||||
if (!huge_zero_folio_shrinker) {
|
||||
shrinker_free(deferred_split_shrinker);
|
||||
list_lru_destroy(&deferred_split_lru);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
huge_zero_folio_shrinker->count_objects = shrink_huge_zero_folio_count;
|
||||
huge_zero_folio_shrinker->scan_objects = shrink_huge_zero_folio_scan;
|
||||
shrinker_register(huge_zero_folio_shrinker);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __init huge_zero_shrinker_exit(void)
|
||||
{
|
||||
shrinker_free(huge_zero_folio_shrinker);
|
||||
}
|
||||
|
||||
struct folio *mm_get_huge_zero_folio(struct mm_struct *mm)
|
||||
{
|
||||
if (IS_ENABLED(CONFIG_PERSISTENT_HUGE_ZERO_FOLIO))
|
||||
return huge_zero_folio;
|
||||
|
||||
if (mm_flags_test(MMF_HUGE_ZERO_FOLIO, mm))
|
||||
return READ_ONCE(huge_zero_folio);
|
||||
|
||||
@@ -280,35 +373,10 @@ struct folio *mm_get_huge_zero_folio(struct mm_struct *mm)
|
||||
|
||||
void mm_put_huge_zero_folio(struct mm_struct *mm)
|
||||
{
|
||||
if (IS_ENABLED(CONFIG_PERSISTENT_HUGE_ZERO_FOLIO))
|
||||
return;
|
||||
|
||||
if (mm_flags_test(MMF_HUGE_ZERO_FOLIO, mm))
|
||||
put_huge_zero_folio();
|
||||
}
|
||||
|
||||
static unsigned long shrink_huge_zero_folio_count(struct shrinker *shrink,
|
||||
struct shrink_control *sc)
|
||||
{
|
||||
/* we can free zero page only if last reference remains */
|
||||
return atomic_read(&huge_zero_refcount) == 1 ? HPAGE_PMD_NR : 0;
|
||||
}
|
||||
|
||||
static unsigned long shrink_huge_zero_folio_scan(struct shrinker *shrink,
|
||||
struct shrink_control *sc)
|
||||
{
|
||||
if (atomic_cmpxchg(&huge_zero_refcount, 1, 0) == 1) {
|
||||
struct folio *zero_folio = xchg(&huge_zero_folio, NULL);
|
||||
BUG_ON(zero_folio == NULL);
|
||||
WRITE_ONCE(huge_zero_pfn, ~0UL);
|
||||
folio_put(zero_folio);
|
||||
return HPAGE_PMD_NR;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct shrinker *huge_zero_folio_shrinker;
|
||||
#endif /* CONFIG_PERSISTENT_HUGE_ZERO_FOLIO */
|
||||
|
||||
#ifdef CONFIG_SYSFS
|
||||
static ssize_t enabled_show(struct kobject *kobj,
|
||||
@@ -972,39 +1040,14 @@ static int __init thp_shrinker_init(void)
|
||||
deferred_split_shrinker->scan_objects = deferred_split_scan;
|
||||
shrinker_register(deferred_split_shrinker);
|
||||
|
||||
if (IS_ENABLED(CONFIG_PERSISTENT_HUGE_ZERO_FOLIO)) {
|
||||
/*
|
||||
* Bump the reference of the huge_zero_folio and do not
|
||||
* initialize the shrinker.
|
||||
*
|
||||
* huge_zero_folio will always be NULL on failure. We assume
|
||||
* that get_huge_zero_folio() will most likely not fail as
|
||||
* thp_shrinker_init() is invoked early on during boot.
|
||||
*/
|
||||
if (!get_huge_zero_folio())
|
||||
pr_warn("Allocating persistent huge zero folio failed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
huge_zero_folio_shrinker = shrinker_alloc(0, "thp-zero");
|
||||
if (!huge_zero_folio_shrinker) {
|
||||
shrinker_free(deferred_split_shrinker);
|
||||
list_lru_destroy(&deferred_split_lru);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
huge_zero_folio_shrinker->count_objects = shrink_huge_zero_folio_count;
|
||||
huge_zero_folio_shrinker->scan_objects = shrink_huge_zero_folio_scan;
|
||||
shrinker_register(huge_zero_folio_shrinker);
|
||||
|
||||
return 0;
|
||||
return huge_zero_init();
|
||||
}
|
||||
|
||||
static void __init thp_shrinker_exit(void)
|
||||
{
|
||||
shrinker_free(huge_zero_folio_shrinker);
|
||||
shrinker_free(deferred_split_shrinker);
|
||||
list_lru_destroy(&deferred_split_lru);
|
||||
huge_zero_shrinker_exit();
|
||||
}
|
||||
|
||||
static int __init hugepage_init(void)
|
||||
@@ -4033,7 +4076,7 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
|
||||
gfp_t gfp;
|
||||
|
||||
mapping = folio->mapping;
|
||||
min_order = mapping_min_folio_order(folio->mapping);
|
||||
min_order = mapping_min_folio_order(mapping);
|
||||
if (new_order < min_order) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
@@ -4047,6 +4090,8 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
|
||||
goto out;
|
||||
}
|
||||
|
||||
mapping_set_update(&xas, mapping);
|
||||
|
||||
if (split_type == SPLIT_TYPE_UNIFORM) {
|
||||
xas_set_order(&xas, folio->index, new_order);
|
||||
xas_split_alloc(&xas, folio, old_order, gfp);
|
||||
|
||||
+16
-4
@@ -151,18 +151,29 @@ void __page_table_check_pte_clear(struct mm_struct *mm, unsigned long addr,
|
||||
if (&init_mm == mm)
|
||||
return;
|
||||
|
||||
if (pte_user_accessible_page(mm, addr, pte))
|
||||
if (pte_user_accessible_page(mm, addr, pte) && !pte_special(pte))
|
||||
page_table_check_clear(pte_pfn(pte), PAGE_SIZE >> PAGE_SHIFT);
|
||||
}
|
||||
EXPORT_SYMBOL(__page_table_check_pte_clear);
|
||||
|
||||
static inline bool page_table_check_huge_zero_pmd(pmd_t pmd)
|
||||
{
|
||||
unsigned long pfn = pmd_pfn(pmd);
|
||||
|
||||
if (!pfn_valid(pfn))
|
||||
return false;
|
||||
|
||||
return is_huge_zero_folio(page_folio(pfn_to_page(pfn)));
|
||||
}
|
||||
|
||||
void __page_table_check_pmd_clear(struct mm_struct *mm, unsigned long addr,
|
||||
pmd_t pmd)
|
||||
{
|
||||
if (&init_mm == mm)
|
||||
return;
|
||||
|
||||
if (pmd_user_accessible_page(mm, addr, pmd))
|
||||
if (pmd_user_accessible_page(mm, addr, pmd) &&
|
||||
!page_table_check_huge_zero_pmd(pmd))
|
||||
page_table_check_clear(pmd_pfn(pmd), PMD_SIZE >> PAGE_SHIFT);
|
||||
}
|
||||
EXPORT_SYMBOL(__page_table_check_pmd_clear);
|
||||
@@ -208,7 +219,7 @@ void __page_table_check_ptes_set(struct mm_struct *mm, unsigned long addr,
|
||||
|
||||
for (i = 0; i < nr; i++)
|
||||
__page_table_check_pte_clear(mm, addr + PAGE_SIZE * i, ptep_get(ptep + i));
|
||||
if (pte_user_accessible_page(mm, addr, pte))
|
||||
if (pte_user_accessible_page(mm, addr, pte) && !pte_special(pte))
|
||||
page_table_check_set(pte_pfn(pte), nr, pte_write(pte));
|
||||
}
|
||||
EXPORT_SYMBOL(__page_table_check_ptes_set);
|
||||
@@ -238,7 +249,8 @@ void __page_table_check_pmds_set(struct mm_struct *mm, unsigned long addr,
|
||||
|
||||
for (i = 0; i < nr; i++)
|
||||
__page_table_check_pmd_clear(mm, addr + PMD_SIZE * i, *(pmdp + i));
|
||||
if (pmd_user_accessible_page(mm, addr, pmd))
|
||||
if (pmd_user_accessible_page(mm, addr, pmd) &&
|
||||
!page_table_check_huge_zero_pmd(pmd))
|
||||
page_table_check_set(pmd_pfn(pmd), stride * nr, pmd_write(pmd));
|
||||
}
|
||||
EXPORT_SYMBOL(__page_table_check_pmds_set);
|
||||
|
||||
+21
-15
@@ -678,6 +678,8 @@ int walk_kernel_page_table_range_lockless(unsigned long start, unsigned long end
|
||||
* will also not lock the PTEs for the pte_entry() callback.
|
||||
*
|
||||
* This is for debugging purposes ONLY.
|
||||
*
|
||||
* The mmap write lock must be held.
|
||||
*/
|
||||
int walk_page_range_debug(struct mm_struct *mm, unsigned long start,
|
||||
unsigned long end, const struct mm_walk_ops *ops,
|
||||
@@ -691,25 +693,29 @@ int walk_page_range_debug(struct mm_struct *mm, unsigned long start,
|
||||
.no_vma = true
|
||||
};
|
||||
|
||||
/* For convenience, we allow traversal of kernel mappings. */
|
||||
if (mm == &init_mm)
|
||||
return walk_kernel_page_table_range(start, end, ops,
|
||||
pgd, private);
|
||||
if (start >= end || !walk.mm)
|
||||
/*
|
||||
* When walking userland page tables, an mmap write lock must be held to
|
||||
* account for munmap() downgrading to an mmap read lock when tearing
|
||||
* down page tables.
|
||||
*
|
||||
* When walking kernel page tables, an mmap write lock must also be held
|
||||
* to account for page table freeing on vmap huge page mapping.
|
||||
*/
|
||||
mmap_assert_write_locked(mm);
|
||||
/*
|
||||
* x86, arm64 ptdump allow walks of efi mm's and x86 ptdump allows walks
|
||||
* of arbitrary mm's.
|
||||
*
|
||||
* However, they both must also hold the init_mm lock to account for
|
||||
* concurrent kernel page table freeing.
|
||||
*/
|
||||
mmap_assert_write_locked(&init_mm);
|
||||
|
||||
if (start >= end)
|
||||
return -EINVAL;
|
||||
if (!check_ops_safe(ops))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* The mmap lock protects the page walker from changes to the page
|
||||
* tables during the walk. However a read lock is insufficient to
|
||||
* protect those areas which don't have a VMA as munmap() detaches
|
||||
* the VMAs before downgrading to a read lock and actually tearing
|
||||
* down PTEs/page tables. In which case, the mmap write lock should
|
||||
* be held.
|
||||
*/
|
||||
mmap_assert_write_locked(mm);
|
||||
|
||||
return walk_pgd_range(start, end, &walk);
|
||||
}
|
||||
|
||||
|
||||
@@ -178,11 +178,18 @@ void ptdump_walk_pgd(struct ptdump_state *st, struct mm_struct *mm, pgd_t *pgd)
|
||||
|
||||
get_online_mems();
|
||||
mmap_write_lock(mm);
|
||||
/* To stabilise kernel page tables we must hold the init_mm lock too. */
|
||||
if (mm != &init_mm)
|
||||
mmap_write_lock_nested(&init_mm, SINGLE_DEPTH_NESTING);
|
||||
|
||||
while (range->start != range->end) {
|
||||
walk_page_range_debug(mm, range->start, range->end,
|
||||
&ptdump_ops, pgd, st);
|
||||
range++;
|
||||
}
|
||||
|
||||
if (mm != &init_mm)
|
||||
mmap_write_unlock(&init_mm);
|
||||
mmap_write_unlock(mm);
|
||||
put_online_mems();
|
||||
|
||||
|
||||
+31
-9
@@ -43,6 +43,7 @@
|
||||
#include <asm/tlbflush.h>
|
||||
#include <asm/shmparam.h>
|
||||
#include <linux/page_owner.h>
|
||||
#include <linux/cleanup.h>
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include <trace/events/vmalloc.h>
|
||||
@@ -158,10 +159,21 @@ static int vmap_try_huge_pmd(pmd_t *pmd, unsigned long addr, unsigned long end,
|
||||
if (!IS_ALIGNED(phys_addr, PMD_SIZE))
|
||||
return 0;
|
||||
|
||||
if (pmd_present(*pmd) && !pmd_free_pte_page(pmd, addr))
|
||||
return 0;
|
||||
if (!pmd_present(*pmd))
|
||||
return pmd_set_huge(pmd, phys_addr, prot);
|
||||
|
||||
return pmd_set_huge(pmd, phys_addr, prot);
|
||||
/*
|
||||
* Acquire the mmap read lock to exclude ptdump, which walks
|
||||
* kernel page tables it does not own under the mmap write lock.
|
||||
*
|
||||
* Concurrent read lock holders are safe: each exclusively owns
|
||||
* the range it operates on and cannot reach this page table.
|
||||
*/
|
||||
scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) {
|
||||
if (!pmd_free_pte_page(pmd, addr))
|
||||
return 0;
|
||||
return pmd_set_huge(pmd, phys_addr, prot);
|
||||
}
|
||||
}
|
||||
|
||||
static int vmap_pmd_range(pud_t *pud, unsigned long addr, unsigned long end,
|
||||
@@ -210,10 +222,15 @@ static int vmap_try_huge_pud(pud_t *pud, unsigned long addr, unsigned long end,
|
||||
if (!IS_ALIGNED(phys_addr, PUD_SIZE))
|
||||
return 0;
|
||||
|
||||
if (pud_present(*pud) && !pud_free_pmd_page(pud, addr))
|
||||
return 0;
|
||||
if (!pud_present(*pud))
|
||||
return pud_set_huge(pud, phys_addr, prot);
|
||||
|
||||
return pud_set_huge(pud, phys_addr, prot);
|
||||
/* See comment in vmap_try_huge_pmd(). */
|
||||
scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) {
|
||||
if (!pud_free_pmd_page(pud, addr))
|
||||
return 0;
|
||||
return pud_set_huge(pud, phys_addr, prot);
|
||||
}
|
||||
}
|
||||
|
||||
static int vmap_pud_range(p4d_t *p4d, unsigned long addr, unsigned long end,
|
||||
@@ -262,10 +279,15 @@ static int vmap_try_huge_p4d(p4d_t *p4d, unsigned long addr, unsigned long end,
|
||||
if (!IS_ALIGNED(phys_addr, P4D_SIZE))
|
||||
return 0;
|
||||
|
||||
if (p4d_present(*p4d) && !p4d_free_pud_page(p4d, addr))
|
||||
return 0;
|
||||
if (!p4d_present(*p4d))
|
||||
return p4d_set_huge(p4d, phys_addr, prot);
|
||||
|
||||
return p4d_set_huge(p4d, phys_addr, prot);
|
||||
/* See comment in vmap_try_huge_pmd(). */
|
||||
scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) {
|
||||
if (!p4d_free_pud_page(p4d, addr))
|
||||
return 0;
|
||||
return p4d_set_huge(p4d, phys_addr, prot);
|
||||
}
|
||||
}
|
||||
|
||||
static int vmap_p4d_range(pgd_t *pgd, unsigned long addr, unsigned long end,
|
||||
|
||||
@@ -156,6 +156,9 @@ static struct damon_ctx *damon_sample_mtier_build_ctx(bool promote)
|
||||
if (!scheme)
|
||||
goto free_out;
|
||||
damon_set_schemes(ctx, &scheme, 1);
|
||||
/* zero target value causes division by zero in damos_quota_store() */
|
||||
if (!node0_mem_used_bp || !node0_mem_free_bp)
|
||||
goto free_out;
|
||||
quota_goal = damos_new_quota_goal(
|
||||
promote ? DAMOS_QUOTA_NODE_MEM_USED_BP :
|
||||
DAMOS_QUOTA_NODE_MEM_FREE_BP,
|
||||
|
||||
Reference in New Issue
Block a user