xref: /openbmc/linux/mm/damon/paddr.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
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