1a28397beSSeongJae Park // SPDX-License-Identifier: GPL-2.0
2a28397beSSeongJae Park /*
3a28397beSSeongJae Park * DAMON Primitives for The Physical Address Space
4a28397beSSeongJae Park *
5a28397beSSeongJae Park * Author: SeongJae Park <sj@kernel.org>
6a28397beSSeongJae Park */
7a28397beSSeongJae Park
8a28397beSSeongJae Park #define pr_fmt(fmt) "damon-pa: " fmt
9a28397beSSeongJae Park
10a28397beSSeongJae Park #include <linux/mmu_notifier.h>
11a28397beSSeongJae Park #include <linux/page_idle.h>
12a28397beSSeongJae Park #include <linux/pagemap.h>
13a28397beSSeongJae Park #include <linux/rmap.h>
1457223ac2SSeongJae Park #include <linux/swap.h>
15a28397beSSeongJae Park
1657223ac2SSeongJae Park #include "../internal.h"
17f7d911c3SSeongJae Park #include "ops-common.h"
18a28397beSSeongJae Park
__damon_pa_mkold(struct folio * folio,struct vm_area_struct * vma,unsigned long addr,void * arg)192f031c6fSMatthew Wilcox (Oracle) static bool __damon_pa_mkold(struct folio *folio, struct vm_area_struct *vma,
20a28397beSSeongJae Park unsigned long addr, void *arg)
21a28397beSSeongJae Park {
222f031c6fSMatthew Wilcox (Oracle) DEFINE_FOLIO_VMA_WALK(pvmw, folio, vma, addr, 0);
23a28397beSSeongJae Park
24a28397beSSeongJae Park while (page_vma_mapped_walk(&pvmw)) {
25a28397beSSeongJae Park addr = pvmw.address;
26a28397beSSeongJae Park if (pvmw.pte)
27c11d34faSRyan Roberts damon_ptep_mkold(pvmw.pte, vma, addr);
28a28397beSSeongJae Park else
29c11d34faSRyan Roberts damon_pmdp_mkold(pvmw.pmd, vma, addr);
30a28397beSSeongJae Park }
31a28397beSSeongJae Park return true;
32a28397beSSeongJae Park }
33a28397beSSeongJae Park
damon_pa_mkold(unsigned long paddr)34a28397beSSeongJae Park static void damon_pa_mkold(unsigned long paddr)
35a28397beSSeongJae Park {
3607bb1fbaSKefeng Wang struct folio *folio = damon_get_folio(PHYS_PFN(paddr));
37a28397beSSeongJae Park struct rmap_walk_control rwc = {
38a28397beSSeongJae Park .rmap_one = __damon_pa_mkold,
392f031c6fSMatthew Wilcox (Oracle) .anon_lock = folio_lock_anon_vma_read,
40a28397beSSeongJae Park };
41a28397beSSeongJae Park bool need_lock;
42a28397beSSeongJae Park
4307bb1fbaSKefeng Wang if (!folio)
44a28397beSSeongJae Park return;
45a28397beSSeongJae Park
466d42dba3SMatthew Wilcox (Oracle) if (!folio_mapped(folio) || !folio_raw_mapping(folio)) {
476d42dba3SMatthew Wilcox (Oracle) folio_set_idle(folio);
48a28397beSSeongJae Park goto out;
49a28397beSSeongJae Park }
50a28397beSSeongJae Park
516d42dba3SMatthew Wilcox (Oracle) need_lock = !folio_test_anon(folio) || folio_test_ksm(folio);
526d42dba3SMatthew Wilcox (Oracle) if (need_lock && !folio_trylock(folio))
53a28397beSSeongJae Park goto out;
54a28397beSSeongJae Park
552f031c6fSMatthew Wilcox (Oracle) rmap_walk(folio, &rwc);
56a28397beSSeongJae Park
57a28397beSSeongJae Park if (need_lock)
586d42dba3SMatthew Wilcox (Oracle) folio_unlock(folio);
59a28397beSSeongJae Park
60a28397beSSeongJae Park out:
616d42dba3SMatthew Wilcox (Oracle) folio_put(folio);
62a28397beSSeongJae Park }
63a28397beSSeongJae Park
__damon_pa_prepare_access_check(struct damon_region * r)648ef4d5caSKaixu Xia static void __damon_pa_prepare_access_check(struct damon_region *r)
65a28397beSSeongJae Park {
66a28397beSSeongJae Park r->sampling_addr = damon_rand(r->ar.start, r->ar.end);
67a28397beSSeongJae Park
68a28397beSSeongJae Park damon_pa_mkold(r->sampling_addr);
69a28397beSSeongJae Park }
70a28397beSSeongJae Park
damon_pa_prepare_access_checks(struct damon_ctx * ctx)71cdeed009SXin Hao static void damon_pa_prepare_access_checks(struct damon_ctx *ctx)
72a28397beSSeongJae Park {
73a28397beSSeongJae Park struct damon_target *t;
74a28397beSSeongJae Park struct damon_region *r;
75a28397beSSeongJae Park
76a28397beSSeongJae Park damon_for_each_target(t, ctx) {
77a28397beSSeongJae Park damon_for_each_region(r, t)
788ef4d5caSKaixu Xia __damon_pa_prepare_access_check(r);
79a28397beSSeongJae Park }
80a28397beSSeongJae Park }
81a28397beSSeongJae Park
__damon_pa_young(struct folio * folio,struct vm_area_struct * vma,unsigned long addr,void * arg)822f031c6fSMatthew Wilcox (Oracle) static bool __damon_pa_young(struct folio *folio, struct vm_area_struct *vma,
83a28397beSSeongJae Park unsigned long addr, void *arg)
84a28397beSSeongJae Park {
85b0c0e744SSeongJae Park bool *accessed = arg;
86c8423186SMatthew Wilcox (Oracle) DEFINE_FOLIO_VMA_WALK(pvmw, folio, vma, addr, 0);
87a28397beSSeongJae Park
88b0c0e744SSeongJae Park *accessed = false;
89a28397beSSeongJae Park while (page_vma_mapped_walk(&pvmw)) {
90a28397beSSeongJae Park addr = pvmw.address;
91a28397beSSeongJae Park if (pvmw.pte) {
92c33c7948SRyan Roberts *accessed = pte_young(ptep_get(pvmw.pte)) ||
93c8423186SMatthew Wilcox (Oracle) !folio_test_idle(folio) ||
94a28397beSSeongJae Park mmu_notifier_test_young(vma->vm_mm, addr);
95a28397beSSeongJae Park } else {
96a28397beSSeongJae Park #ifdef CONFIG_TRANSPARENT_HUGEPAGE
97*e7ee3f97SLevi Yun *accessed = pmd_young(pmdp_get(pvmw.pmd)) ||
98c8423186SMatthew Wilcox (Oracle) !folio_test_idle(folio) ||
99a28397beSSeongJae Park mmu_notifier_test_young(vma->vm_mm, addr);
100a28397beSSeongJae Park #else
101a28397beSSeongJae Park WARN_ON_ONCE(1);
102a28397beSSeongJae Park #endif /* CONFIG_TRANSPARENT_HUGEPAGE */
103a28397beSSeongJae Park }
104b0c0e744SSeongJae Park if (*accessed) {
105a28397beSSeongJae Park page_vma_mapped_walk_done(&pvmw);
106a28397beSSeongJae Park break;
107a28397beSSeongJae Park }
108a28397beSSeongJae Park }
109a28397beSSeongJae Park
110a28397beSSeongJae Park /* If accessed, stop walking */
111b0c0e744SSeongJae Park return *accessed == false;
112a28397beSSeongJae Park }
113a28397beSSeongJae Park
damon_pa_young(unsigned long paddr,unsigned long * folio_sz)114af40e35aSSeongJae Park static bool damon_pa_young(unsigned long paddr, unsigned long *folio_sz)
115a28397beSSeongJae Park {
11607bb1fbaSKefeng Wang struct folio *folio = damon_get_folio(PHYS_PFN(paddr));
117b0c0e744SSeongJae Park bool accessed = false;
118a28397beSSeongJae Park struct rmap_walk_control rwc = {
119b0c0e744SSeongJae Park .arg = &accessed,
120a28397beSSeongJae Park .rmap_one = __damon_pa_young,
1212f031c6fSMatthew Wilcox (Oracle) .anon_lock = folio_lock_anon_vma_read,
122a28397beSSeongJae Park };
123a28397beSSeongJae Park bool need_lock;
124a28397beSSeongJae Park
12507bb1fbaSKefeng Wang if (!folio)
126a28397beSSeongJae Park return false;
127a28397beSSeongJae Park
128c8423186SMatthew Wilcox (Oracle) if (!folio_mapped(folio) || !folio_raw_mapping(folio)) {
129c8423186SMatthew Wilcox (Oracle) if (folio_test_idle(folio))
130b0c0e744SSeongJae Park accessed = false;
131a28397beSSeongJae Park else
132b0c0e744SSeongJae Park accessed = true;
133a28397beSSeongJae Park goto out;
134a28397beSSeongJae Park }
135a28397beSSeongJae Park
136c8423186SMatthew Wilcox (Oracle) need_lock = !folio_test_anon(folio) || folio_test_ksm(folio);
13770307b0eSKefeng Wang if (need_lock && !folio_trylock(folio))
13870307b0eSKefeng Wang goto out;
139a28397beSSeongJae Park
1402f031c6fSMatthew Wilcox (Oracle) rmap_walk(folio, &rwc);
141a28397beSSeongJae Park
142a28397beSSeongJae Park if (need_lock)
143c8423186SMatthew Wilcox (Oracle) folio_unlock(folio);
144a28397beSSeongJae Park
145a28397beSSeongJae Park out:
146397b0c3aSSeongJae Park *folio_sz = folio_size(folio);
147751688b8SSeongJae Park folio_put(folio);
148b0c0e744SSeongJae Park return accessed;
149a28397beSSeongJae Park }
150a28397beSSeongJae Park
__damon_pa_check_access(struct damon_region * r)15109876ae7SKaixu Xia static void __damon_pa_check_access(struct damon_region *r)
152a28397beSSeongJae Park {
153a28397beSSeongJae Park static unsigned long last_addr;
154af40e35aSSeongJae Park static unsigned long last_folio_sz = PAGE_SIZE;
155a28397beSSeongJae Park static bool last_accessed;
156a28397beSSeongJae Park
157a28397beSSeongJae Park /* If the region is in the last checked page, reuse the result */
158af40e35aSSeongJae Park if (ALIGN_DOWN(last_addr, last_folio_sz) ==
159af40e35aSSeongJae Park ALIGN_DOWN(r->sampling_addr, last_folio_sz)) {
160a28397beSSeongJae Park if (last_accessed)
161a28397beSSeongJae Park r->nr_accesses++;
162a28397beSSeongJae Park return;
163a28397beSSeongJae Park }
164a28397beSSeongJae Park
165af40e35aSSeongJae Park last_accessed = damon_pa_young(r->sampling_addr, &last_folio_sz);
166a28397beSSeongJae Park if (last_accessed)
167a28397beSSeongJae Park r->nr_accesses++;
168a28397beSSeongJae Park
169a28397beSSeongJae Park last_addr = r->sampling_addr;
170a28397beSSeongJae Park }
171a28397beSSeongJae Park
damon_pa_check_accesses(struct damon_ctx * ctx)172cdeed009SXin Hao static unsigned int damon_pa_check_accesses(struct damon_ctx *ctx)
173a28397beSSeongJae Park {
174a28397beSSeongJae Park struct damon_target *t;
175a28397beSSeongJae Park struct damon_region *r;
176a28397beSSeongJae Park unsigned int max_nr_accesses = 0;
177a28397beSSeongJae Park
178a28397beSSeongJae Park damon_for_each_target(t, ctx) {
179a28397beSSeongJae Park damon_for_each_region(r, t) {
18009876ae7SKaixu Xia __damon_pa_check_access(r);
181a28397beSSeongJae Park max_nr_accesses = max(r->nr_accesses, max_nr_accesses);
182a28397beSSeongJae Park }
183a28397beSSeongJae Park }
184a28397beSSeongJae Park
185a28397beSSeongJae Park return max_nr_accesses;
186a28397beSSeongJae Park }
187a28397beSSeongJae Park
__damos_pa_filter_out(struct damos_filter * filter,struct folio * folio)18818250e78SSeongJae Park static bool __damos_pa_filter_out(struct damos_filter *filter,
18907bb1fbaSKefeng Wang struct folio *folio)
19018250e78SSeongJae Park {
19118250e78SSeongJae Park bool matched = false;
19218250e78SSeongJae Park struct mem_cgroup *memcg;
19318250e78SSeongJae Park
19418250e78SSeongJae Park switch (filter->type) {
19518250e78SSeongJae Park case DAMOS_FILTER_TYPE_ANON:
19607bb1fbaSKefeng Wang matched = folio_test_anon(folio);
19718250e78SSeongJae Park break;
19818250e78SSeongJae Park case DAMOS_FILTER_TYPE_MEMCG:
19918250e78SSeongJae Park rcu_read_lock();
20007bb1fbaSKefeng Wang memcg = folio_memcg_check(folio);
20118250e78SSeongJae Park if (!memcg)
20218250e78SSeongJae Park matched = false;
20318250e78SSeongJae Park else
20418250e78SSeongJae Park matched = filter->memcg_id == mem_cgroup_id(memcg);
20518250e78SSeongJae Park rcu_read_unlock();
20618250e78SSeongJae Park break;
20718250e78SSeongJae Park default:
20818250e78SSeongJae Park break;
20918250e78SSeongJae Park }
21018250e78SSeongJae Park
21118250e78SSeongJae Park return matched == filter->matching;
21218250e78SSeongJae Park }
21318250e78SSeongJae Park
21418250e78SSeongJae Park /*
21518250e78SSeongJae Park * damos_pa_filter_out - Return true if the page should be filtered out.
21618250e78SSeongJae Park */
damos_pa_filter_out(struct damos * scheme,struct folio * folio)21707bb1fbaSKefeng Wang static bool damos_pa_filter_out(struct damos *scheme, struct folio *folio)
21818250e78SSeongJae Park {
21918250e78SSeongJae Park struct damos_filter *filter;
22018250e78SSeongJae Park
22118250e78SSeongJae Park damos_for_each_filter(filter, scheme) {
22207bb1fbaSKefeng Wang if (__damos_pa_filter_out(filter, folio))
22318250e78SSeongJae Park return true;
22418250e78SSeongJae Park }
22518250e78SSeongJae Park return false;
22618250e78SSeongJae Park }
22718250e78SSeongJae Park
damon_pa_pageout(struct damon_region * r,struct damos * s)22818250e78SSeongJae Park static unsigned long damon_pa_pageout(struct damon_region *r, struct damos *s)
22957223ac2SSeongJae Park {
2300e92c2eeSSeongJae Park unsigned long addr, applied;
23107bb1fbaSKefeng Wang LIST_HEAD(folio_list);
23257223ac2SSeongJae Park
23357223ac2SSeongJae Park for (addr = r->ar.start; addr < r->ar.end; addr += PAGE_SIZE) {
23407bb1fbaSKefeng Wang struct folio *folio = damon_get_folio(PHYS_PFN(addr));
23557223ac2SSeongJae Park
23607bb1fbaSKefeng Wang if (!folio)
23757223ac2SSeongJae Park continue;
23857223ac2SSeongJae Park
239dd411433SKefeng Wang if (damos_pa_filter_out(s, folio))
240dd411433SKefeng Wang goto put_folio;
24118250e78SSeongJae Park
24207bb1fbaSKefeng Wang folio_clear_referenced(folio);
24307bb1fbaSKefeng Wang folio_test_clear_young(folio);
244dd411433SKefeng Wang if (!folio_isolate_lru(folio))
245dd411433SKefeng Wang goto put_folio;
2463f98c9a6Sandrew.yang if (folio_test_unevictable(folio))
24707bb1fbaSKefeng Wang folio_putback_lru(folio);
2483f98c9a6Sandrew.yang else
24907bb1fbaSKefeng Wang list_add(&folio->lru, &folio_list);
250dd411433SKefeng Wang put_folio:
25107bb1fbaSKefeng Wang folio_put(folio);
25257223ac2SSeongJae Park }
25307bb1fbaSKefeng Wang applied = reclaim_pages(&folio_list);
25457223ac2SSeongJae Park cond_resched();
2550e92c2eeSSeongJae Park return applied * PAGE_SIZE;
25657223ac2SSeongJae Park }
25757223ac2SSeongJae Park
damon_pa_mark_accessed_or_deactivate(struct damon_region * r,struct damos * s,bool mark_accessed)2588193321aSSeongJae Park static inline unsigned long damon_pa_mark_accessed_or_deactivate(
25918250e78SSeongJae Park struct damon_region *r, struct damos *s, bool mark_accessed)
2608cdcc532SSeongJae Park {
2618cdcc532SSeongJae Park unsigned long addr, applied = 0;
2628cdcc532SSeongJae Park
2638cdcc532SSeongJae Park for (addr = r->ar.start; addr < r->ar.end; addr += PAGE_SIZE) {
26407bb1fbaSKefeng Wang struct folio *folio = damon_get_folio(PHYS_PFN(addr));
2658cdcc532SSeongJae Park
26607bb1fbaSKefeng Wang if (!folio)
2678cdcc532SSeongJae Park continue;
26818250e78SSeongJae Park
269b6993be2SKefeng Wang if (damos_pa_filter_out(s, folio))
270b6993be2SKefeng Wang goto put_folio;
27118250e78SSeongJae Park
2728193321aSSeongJae Park if (mark_accessed)
273f70da5eeSVishal Moola (Oracle) folio_mark_accessed(folio);
2748193321aSSeongJae Park else
2755a9e3474SVishal Moola (Oracle) folio_deactivate(folio);
276f70da5eeSVishal Moola (Oracle) applied += folio_nr_pages(folio);
277b6993be2SKefeng Wang put_folio:
278dd52a61dSSeongJae Park folio_put(folio);
27999cdc2cdSSeongJae Park }
28099cdc2cdSSeongJae Park return applied * PAGE_SIZE;
28199cdc2cdSSeongJae Park }
28299cdc2cdSSeongJae Park
damon_pa_mark_accessed(struct damon_region * r,struct damos * s)28318250e78SSeongJae Park static unsigned long damon_pa_mark_accessed(struct damon_region *r,
28418250e78SSeongJae Park struct damos *s)
2858193321aSSeongJae Park {
28618250e78SSeongJae Park return damon_pa_mark_accessed_or_deactivate(r, s, true);
2878193321aSSeongJae Park }
2888193321aSSeongJae Park
damon_pa_deactivate_pages(struct damon_region * r,struct damos * s)28918250e78SSeongJae Park static unsigned long damon_pa_deactivate_pages(struct damon_region *r,
29018250e78SSeongJae Park struct damos *s)
2918193321aSSeongJae Park {
29218250e78SSeongJae Park return damon_pa_mark_accessed_or_deactivate(r, s, false);
2938193321aSSeongJae Park }
2948193321aSSeongJae Park
damon_pa_apply_scheme(struct damon_ctx * ctx,struct damon_target * t,struct damon_region * r,struct damos * scheme)2950e93e8bfSSeongJae Park static unsigned long damon_pa_apply_scheme(struct damon_ctx *ctx,
2960e93e8bfSSeongJae Park struct damon_target *t, struct damon_region *r,
2970e93e8bfSSeongJae Park struct damos *scheme)
2980e93e8bfSSeongJae Park {
2990e93e8bfSSeongJae Park switch (scheme->action) {
3000e93e8bfSSeongJae Park case DAMOS_PAGEOUT:
30118250e78SSeongJae Park return damon_pa_pageout(r, scheme);
3028cdcc532SSeongJae Park case DAMOS_LRU_PRIO:
30318250e78SSeongJae Park return damon_pa_mark_accessed(r, scheme);
30499cdc2cdSSeongJae Park case DAMOS_LRU_DEPRIO:
30518250e78SSeongJae Park return damon_pa_deactivate_pages(r, scheme);
306f82e70e2SSeongJae Park case DAMOS_STAT:
307f82e70e2SSeongJae Park break;
3080e93e8bfSSeongJae Park default:
309f82e70e2SSeongJae Park /* DAMOS actions that not yet supported by 'paddr'. */
3100e93e8bfSSeongJae Park break;
3110e93e8bfSSeongJae Park }
3120e93e8bfSSeongJae Park return 0;
3130e93e8bfSSeongJae Park }
3140e93e8bfSSeongJae Park
damon_pa_scheme_score(struct damon_ctx * context,struct damon_target * t,struct damon_region * r,struct damos * scheme)315cdeed009SXin Hao static int damon_pa_scheme_score(struct damon_ctx *context,
316cdeed009SXin Hao struct damon_target *t, struct damon_region *r,
317cdeed009SXin Hao struct damos *scheme)
318198f0f4cSSeongJae Park {
319198f0f4cSSeongJae Park switch (scheme->action) {
320198f0f4cSSeongJae Park case DAMOS_PAGEOUT:
321e3e486e6SKaixu Xia return damon_cold_score(context, r, scheme);
3228cdcc532SSeongJae Park case DAMOS_LRU_PRIO:
3238cdcc532SSeongJae Park return damon_hot_score(context, r, scheme);
32499cdc2cdSSeongJae Park case DAMOS_LRU_DEPRIO:
325e3e486e6SKaixu Xia return damon_cold_score(context, r, scheme);
326198f0f4cSSeongJae Park default:
327198f0f4cSSeongJae Park break;
328198f0f4cSSeongJae Park }
329198f0f4cSSeongJae Park
330198f0f4cSSeongJae Park return DAMOS_MAX_SCORE;
331198f0f4cSSeongJae Park }
332198f0f4cSSeongJae Park
damon_pa_initcall(void)3337752925fSSeongJae Park static int __init damon_pa_initcall(void)
3347752925fSSeongJae Park {
3357752925fSSeongJae Park struct damon_operations ops = {
3367752925fSSeongJae Park .id = DAMON_OPS_PADDR,
3377752925fSSeongJae Park .init = NULL,
3387752925fSSeongJae Park .update = NULL,
3397752925fSSeongJae Park .prepare_access_checks = damon_pa_prepare_access_checks,
3407752925fSSeongJae Park .check_accesses = damon_pa_check_accesses,
3417752925fSSeongJae Park .reset_aggregated = NULL,
34285104056SSeongJae Park .target_valid = NULL,
3437752925fSSeongJae Park .cleanup = NULL,
3447752925fSSeongJae Park .apply_scheme = damon_pa_apply_scheme,
3457752925fSSeongJae Park .get_scheme_score = damon_pa_scheme_score,
3467752925fSSeongJae Park };
3477752925fSSeongJae Park
3487752925fSSeongJae Park return damon_register_ops(&ops);
3497752925fSSeongJae Park };
3507752925fSSeongJae Park
3517752925fSSeongJae Park subsys_initcall(damon_pa_initcall);
352