Files
linux-stable-mirror/mm/damon/paddr.c
T
SJ ParkandAndrew Morton 14afcf67dc MAINTAINERS: s/SeongJae/SJ/
My legal and preferred first names are SeongJae and SJ, respectively.  I
was using the legal name for commits and tags, while using the preferred
name for conversations.  It sometimes confuses people including myself. 
Consistently use the preferred name.

Together remove copyright notes on files.  Those are only confusing for
people who are not familiar with the law.  Meanwhile, we can infer the
information in a better way from git logs and public information.

Link: https://lore.kernel.org/20260630013820.143366-1-sj@kernel.org
Signed-off-by: SJ Park <sj@kernel.org>
Acked-by: Lorenzo Stoakes <ljs@kernel.org>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Cc: Liam R. Howlett <liam@infradead.org>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Mike Rapoport <rppt@kernel.org>
Cc: Suren Baghdasaryan <surenb@google.com>
Cc: Vlastimil Babka <vbabka@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2026-07-09 15:48:53 -07:00

457 lines
11 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* DAMON Code for The Physical Address Space
*/
#define pr_fmt(fmt) "damon-pa: " fmt
#include <linux/mmu_notifier.h>
#include <linux/page_idle.h>
#include <linux/pagemap.h>
#include <linux/rmap.h>
#include <linux/swap.h>
#include <linux/memory-tiers.h>
#include <linux/mm_inline.h>
#include "../internal.h"
#include "ops-common.h"
static phys_addr_t damon_pa_phys_addr(
unsigned long addr, unsigned long addr_unit)
{
return (phys_addr_t)addr * addr_unit;
}
static unsigned long damon_pa_core_addr(
phys_addr_t pa, unsigned long addr_unit)
{
/*
* Use div_u64() for avoiding linking errors related with __udivdi3,
* __aeabi_uldivmod, or similar problems. This should also improve the
* performance optimization (read div_u64() comment for the detail).
*/
if (sizeof(pa) == 8 && sizeof(addr_unit) == 4)
return div_u64(pa, addr_unit);
return pa / addr_unit;
}
static void damon_pa_mkold(phys_addr_t paddr)
{
struct folio *folio = damon_get_folio(PHYS_PFN(paddr));
if (!folio)
return;
damon_folio_mkold(folio);
folio_put(folio);
}
static void __damon_pa_prepare_access_check(struct damon_region *r,
struct damon_ctx *ctx)
{
r->sampling_addr = damon_rand(ctx, r->ar.start, r->ar.end);
damon_pa_mkold(damon_pa_phys_addr(r->sampling_addr, ctx->addr_unit));
}
static void damon_pa_prepare_access_checks(struct damon_ctx *ctx)
{
struct damon_target *t;
struct damon_region *r;
damon_for_each_target(t, ctx) {
damon_for_each_region(r, t)
__damon_pa_prepare_access_check(r, ctx);
}
}
static bool damon_pa_young(phys_addr_t paddr, unsigned long *folio_sz)
{
struct folio *folio = damon_get_folio(PHYS_PFN(paddr));
bool accessed;
if (!folio)
return false;
accessed = damon_folio_young(folio);
*folio_sz = folio_size(folio);
folio_put(folio);
return accessed;
}
static void __damon_pa_check_access(struct damon_region *r,
struct damon_attrs *attrs, unsigned long addr_unit)
{
static phys_addr_t last_addr;
static unsigned long last_folio_sz = PAGE_SIZE;
static bool last_accessed;
phys_addr_t sampling_addr = damon_pa_phys_addr(
r->sampling_addr, addr_unit);
/* If the region is in the last checked page, reuse the result */
if (ALIGN_DOWN(last_addr, last_folio_sz) ==
ALIGN_DOWN(sampling_addr, last_folio_sz)) {
damon_update_region_access_rate(r, last_accessed, attrs);
return;
}
last_accessed = damon_pa_young(sampling_addr, &last_folio_sz);
damon_update_region_access_rate(r, last_accessed, attrs);
last_addr = sampling_addr;
}
static unsigned int damon_pa_check_accesses(struct damon_ctx *ctx)
{
struct damon_target *t;
struct damon_region *r;
unsigned int max_nr_accesses = 0;
damon_for_each_target(t, ctx) {
damon_for_each_region(r, t) {
__damon_pa_check_access(
r, &ctx->attrs, ctx->addr_unit);
max_nr_accesses = max(r->nr_accesses, max_nr_accesses);
}
}
return max_nr_accesses;
}
static bool damon_pa_filter_match(struct damon_filter *filter,
struct folio *folio)
{
bool matched = false;
struct mem_cgroup *memcg;
switch (filter->type) {
case DAMON_FILTER_TYPE_ANON:
if (!folio) {
matched = false;
break;
}
matched = folio_test_anon(folio);
break;
case DAMON_FILTER_TYPE_MEMCG:
if (!folio) {
matched = false;
break;
}
rcu_read_lock();
memcg = folio_memcg_check(folio);
if (!memcg)
matched = false;
else
matched = filter->memcg_id == mem_cgroup_id(memcg);
rcu_read_unlock();
break;
default:
break;
}
return matched == filter->matching;
}
static bool damon_pa_filter_pass(phys_addr_t pa, struct folio *folio,
struct damon_probe *p)
{
struct damon_filter *f;
bool pass = true;
damon_for_each_filter(f, p) {
if (damon_pa_filter_match(f, folio)) {
pass = f->allow;
break;
}
pass = !f->allow;
}
return pass;
}
static void damon_pa_apply_probes(struct damon_ctx *ctx)
{
struct damon_target *t;
struct damon_region *r;
struct damon_probe *p;
damon_for_each_target(t, ctx) {
damon_for_each_region(r, t) {
int i = 0;
phys_addr_t pa;
struct folio *folio;
pa = damon_pa_phys_addr(r->sampling_addr,
ctx->addr_unit);
folio = damon_get_folio(PHYS_PFN(pa));
damon_for_each_probe(p, ctx) {
if (damon_pa_filter_pass(pa, folio, p))
r->probe_hits[i]++;
i++;
}
if (folio)
folio_put(folio);
}
}
}
/*
* damos_pa_filter_out - Return true if the page should be filtered out.
*/
static bool damos_pa_filter_out(struct damos *scheme, struct folio *folio)
{
struct damos_filter *filter;
if (scheme->core_filters_allowed)
return false;
damos_for_each_ops_filter(filter, scheme) {
if (damos_folio_filter_match(filter, folio))
return !filter->allow;
}
return scheme->ops_filters_default_reject;
}
static bool damon_pa_invalid_damos_folio(struct folio *folio, struct damos *s)
{
if (!folio)
return true;
if (folio == s->last_applied) {
folio_put(folio);
return true;
}
return false;
}
static unsigned long damon_pa_pageout(struct damon_region *r,
unsigned long addr_unit, struct damos *s,
unsigned long *sz_filter_passed)
{
phys_addr_t addr, applied;
LIST_HEAD(folio_list);
bool install_young_filter = true;
struct damos_filter *filter;
struct folio *folio = NULL;
/* check access in page level again by default */
damos_for_each_ops_filter(filter, s) {
if (filter->type == DAMOS_FILTER_TYPE_YOUNG) {
install_young_filter = false;
break;
}
}
if (install_young_filter) {
filter = damos_new_filter(
DAMOS_FILTER_TYPE_YOUNG, true, false);
if (!filter)
return 0;
damos_add_filter(s, filter);
}
addr = damon_pa_phys_addr(r->ar.start, addr_unit);
while (addr < damon_pa_phys_addr(r->ar.end, addr_unit)) {
folio = damon_get_folio(PHYS_PFN(addr));
if (damon_pa_invalid_damos_folio(folio, s)) {
addr += PAGE_SIZE;
continue;
}
if (damos_pa_filter_out(s, folio))
goto put_folio;
else
*sz_filter_passed += folio_size(folio) / addr_unit;
folio_clear_referenced(folio);
folio_test_clear_young(folio);
if (!folio_isolate_lru(folio))
goto put_folio;
if (folio_test_unevictable(folio))
folio_putback_lru(folio);
else
list_add(&folio->lru, &folio_list);
put_folio:
addr += folio_size(folio);
folio_put(folio);
}
if (install_young_filter)
damos_destroy_filter(filter);
applied = reclaim_pages(&folio_list);
cond_resched();
s->last_applied = folio;
return damon_pa_core_addr(applied * PAGE_SIZE, addr_unit);
}
static inline unsigned long damon_pa_de_activate(
struct damon_region *r, unsigned long addr_unit,
struct damos *s, bool activate,
unsigned long *sz_filter_passed)
{
phys_addr_t addr, applied = 0;
struct folio *folio = NULL;
addr = damon_pa_phys_addr(r->ar.start, addr_unit);
while (addr < damon_pa_phys_addr(r->ar.end, addr_unit)) {
folio = damon_get_folio(PHYS_PFN(addr));
if (damon_pa_invalid_damos_folio(folio, s)) {
addr += PAGE_SIZE;
continue;
}
if (damos_pa_filter_out(s, folio))
goto put_folio;
else
*sz_filter_passed += folio_size(folio) / addr_unit;
if (activate)
folio_activate(folio);
else
folio_deactivate(folio);
applied += folio_nr_pages(folio);
put_folio:
addr += folio_size(folio);
folio_put(folio);
}
s->last_applied = folio;
return damon_pa_core_addr(applied * PAGE_SIZE, addr_unit);
}
static unsigned long damon_pa_activate_pages(struct damon_region *r,
unsigned long addr_unit, struct damos *s,
unsigned long *sz_filter_passed)
{
return damon_pa_de_activate(r, addr_unit, s, true, sz_filter_passed);
}
static unsigned long damon_pa_deactivate_pages(struct damon_region *r,
unsigned long addr_unit, struct damos *s,
unsigned long *sz_filter_passed)
{
return damon_pa_de_activate(r, addr_unit, s, false, sz_filter_passed);
}
static unsigned long damon_pa_migrate(struct damon_region *r,
unsigned long addr_unit, struct damos *s,
unsigned long *sz_filter_passed)
{
phys_addr_t addr, applied;
LIST_HEAD(folio_list);
struct folio *folio = NULL;
addr = damon_pa_phys_addr(r->ar.start, addr_unit);
while (addr < damon_pa_phys_addr(r->ar.end, addr_unit)) {
folio = damon_get_folio(PHYS_PFN(addr));
if (damon_pa_invalid_damos_folio(folio, s)) {
addr += PAGE_SIZE;
continue;
}
if (damos_pa_filter_out(s, folio))
goto put_folio;
else
*sz_filter_passed += folio_size(folio) / addr_unit;
if (!folio_isolate_lru(folio))
goto put_folio;
list_add(&folio->lru, &folio_list);
put_folio:
addr += folio_size(folio);
folio_put(folio);
}
applied = damon_migrate_pages(&folio_list, s->target_nid);
cond_resched();
s->last_applied = folio;
return damon_pa_core_addr(applied * PAGE_SIZE, addr_unit);
}
static unsigned long damon_pa_stat(struct damon_region *r,
unsigned long addr_unit, struct damos *s,
unsigned long *sz_filter_passed)
{
phys_addr_t addr;
struct folio *folio = NULL;
if (!damos_ops_has_filter(s))
return 0;
addr = damon_pa_phys_addr(r->ar.start, addr_unit);
while (addr < damon_pa_phys_addr(r->ar.end, addr_unit)) {
folio = damon_get_folio(PHYS_PFN(addr));
if (damon_pa_invalid_damos_folio(folio, s)) {
addr += PAGE_SIZE;
continue;
}
if (!damos_pa_filter_out(s, folio))
*sz_filter_passed += folio_size(folio) / addr_unit;
addr += folio_size(folio);
folio_put(folio);
}
s->last_applied = folio;
return 0;
}
static unsigned long damon_pa_apply_scheme(struct damon_ctx *ctx,
struct damon_target *t, struct damon_region *r,
struct damos *scheme, unsigned long *sz_filter_passed)
{
unsigned long aunit = ctx->addr_unit;
switch (scheme->action) {
case DAMOS_PAGEOUT:
return damon_pa_pageout(r, aunit, scheme, sz_filter_passed);
case DAMOS_LRU_PRIO:
return damon_pa_activate_pages(r, aunit, scheme,
sz_filter_passed);
case DAMOS_LRU_DEPRIO:
return damon_pa_deactivate_pages(r, aunit, scheme,
sz_filter_passed);
case DAMOS_MIGRATE_HOT:
case DAMOS_MIGRATE_COLD:
return damon_pa_migrate(r, aunit, scheme, sz_filter_passed);
case DAMOS_STAT:
return damon_pa_stat(r, aunit, scheme, sz_filter_passed);
default:
/* DAMOS actions that not yet supported by 'paddr'. */
break;
}
return 0;
}
static int damon_pa_scheme_score(struct damon_ctx *context,
struct damon_region *r, struct damos *scheme)
{
switch (scheme->action) {
case DAMOS_PAGEOUT:
return damon_cold_score(context, r, scheme);
case DAMOS_LRU_PRIO:
return damon_hot_score(context, r, scheme);
case DAMOS_LRU_DEPRIO:
return damon_cold_score(context, r, scheme);
case DAMOS_MIGRATE_HOT:
return damon_hot_score(context, r, scheme);
case DAMOS_MIGRATE_COLD:
return damon_cold_score(context, r, scheme);
default:
break;
}
return DAMOS_MAX_SCORE;
}
static int __init damon_pa_initcall(void)
{
struct damon_operations ops = {
.id = DAMON_OPS_PADDR,
.init = NULL,
.update = NULL,
.prepare_access_checks = damon_pa_prepare_access_checks,
.check_accesses = damon_pa_check_accesses,
.apply_probes = damon_pa_apply_probes,
.target_valid = NULL,
.apply_scheme = damon_pa_apply_scheme,
.get_scheme_score = damon_pa_scheme_score,
};
return damon_register_ops(&ops);
};
subsys_initcall(damon_pa_initcall);