xref: /openbmc/linux/mm/damon/reclaim.c (revision f6d73b12ca9fd3b1c29a6a725cd751b972c740cf)
143b0536cSSeongJae Park // SPDX-License-Identifier: GPL-2.0
243b0536cSSeongJae Park /*
343b0536cSSeongJae Park  * DAMON-based page reclamation
443b0536cSSeongJae Park  *
543b0536cSSeongJae Park  * Author: SeongJae Park <sj@kernel.org>
643b0536cSSeongJae Park  */
743b0536cSSeongJae Park 
843b0536cSSeongJae Park #define pr_fmt(fmt) "damon-reclaim: " fmt
943b0536cSSeongJae Park 
1043b0536cSSeongJae Park #include <linux/damon.h>
11e6aff38bSChristophe JAILLET #include <linux/kstrtox.h>
1243b0536cSSeongJae Park #include <linux/module.h>
1343b0536cSSeongJae Park 
14fdfc119cSSeongJae Park #include "modules-common.h"
15fdfc119cSSeongJae Park 
1643b0536cSSeongJae Park #ifdef MODULE_PARAM_PREFIX
1743b0536cSSeongJae Park #undef MODULE_PARAM_PREFIX
1843b0536cSSeongJae Park #endif
1943b0536cSSeongJae Park #define MODULE_PARAM_PREFIX "damon_reclaim."
2043b0536cSSeongJae Park 
2143b0536cSSeongJae Park /*
2243b0536cSSeongJae Park  * Enable or disable DAMON_RECLAIM.
2343b0536cSSeongJae Park  *
2443b0536cSSeongJae Park  * You can enable DAMON_RCLAIM by setting the value of this parameter as ``Y``.
2543b0536cSSeongJae Park  * Setting it as ``N`` disables DAMON_RECLAIM.  Note that DAMON_RECLAIM could
2643b0536cSSeongJae Park  * do no real monitoring and reclamation due to the watermarks-based activation
2743b0536cSSeongJae Park  * condition.  Refer to below descriptions for the watermarks parameter for
2843b0536cSSeongJae Park  * this.
2943b0536cSSeongJae Park  */
3043b0536cSSeongJae Park static bool enabled __read_mostly;
3143b0536cSSeongJae Park 
3243b0536cSSeongJae Park /*
33e035c280SSeongJae Park  * Make DAMON_RECLAIM reads the input parameters again, except ``enabled``.
34e035c280SSeongJae Park  *
35e035c280SSeongJae Park  * Input parameters that updated while DAMON_RECLAIM is running are not applied
36e035c280SSeongJae Park  * by default.  Once this parameter is set as ``Y``, DAMON_RECLAIM reads values
37e035c280SSeongJae Park  * of parametrs except ``enabled`` again.  Once the re-reading is done, this
38e035c280SSeongJae Park  * parameter is set as ``N``.  If invalid parameters are found while the
39e035c280SSeongJae Park  * re-reading, DAMON_RECLAIM will be disabled.
40e035c280SSeongJae Park  */
41e035c280SSeongJae Park static bool commit_inputs __read_mostly;
42e035c280SSeongJae Park module_param(commit_inputs, bool, 0600);
43e035c280SSeongJae Park 
44e035c280SSeongJae Park /*
4543b0536cSSeongJae Park  * Time threshold for cold memory regions identification in microseconds.
4643b0536cSSeongJae Park  *
4743b0536cSSeongJae Park  * If a memory region is not accessed for this or longer time, DAMON_RECLAIM
4843b0536cSSeongJae Park  * identifies the region as cold, and reclaims.  120 seconds by default.
4943b0536cSSeongJae Park  */
5043b0536cSSeongJae Park static unsigned long min_age __read_mostly = 120000000;
5143b0536cSSeongJae Park module_param(min_age, ulong, 0600);
5243b0536cSSeongJae Park 
53a9d57c73SSeongJae Park static struct damos_quota damon_reclaim_quota = {
54a9d57c73SSeongJae Park 	/* use up to 10 ms time, reclaim up to 128 MiB per 1 sec by default */
55a9d57c73SSeongJae Park 	.ms = 10,
56a9d57c73SSeongJae Park 	.sz = 128 * 1024 * 1024,
57a9d57c73SSeongJae Park 	.reset_interval = 1000,
58a9d57c73SSeongJae Park 	/* Within the quota, page out older regions first. */
59a9d57c73SSeongJae Park 	.weight_sz = 0,
60a9d57c73SSeongJae Park 	.weight_nr_accesses = 0,
61a9d57c73SSeongJae Park 	.weight_age = 1
62a9d57c73SSeongJae Park };
63a9d57c73SSeongJae Park DEFINE_DAMON_MODULES_DAMOS_QUOTAS(damon_reclaim_quota);
6443b0536cSSeongJae Park 
6581f8f57fSYang Yingliang static struct damos_watermarks damon_reclaim_wmarks = {
6634f47ea6SSeongJae Park 	.metric = DAMOS_WMARK_FREE_MEM_RATE,
6734f47ea6SSeongJae Park 	.interval = 5000000,	/* 5 seconds */
6834f47ea6SSeongJae Park 	.high = 500,		/* 50 percent */
6934f47ea6SSeongJae Park 	.mid = 400,		/* 40 percent */
7034f47ea6SSeongJae Park 	.low = 200,		/* 20 percent */
7134f47ea6SSeongJae Park };
7234f47ea6SSeongJae Park DEFINE_DAMON_MODULES_WMARKS_PARAMS(damon_reclaim_wmarks);
7343b0536cSSeongJae Park 
748c341ae3SSeongJae Park static struct damon_attrs damon_reclaim_mon_attrs = {
75fdfc119cSSeongJae Park 	.sample_interval = 5000,	/* 5 ms */
76fdfc119cSSeongJae Park 	.aggr_interval = 100000,	/* 100 ms */
778c341ae3SSeongJae Park 	.ops_update_interval = 0,
788c341ae3SSeongJae Park 	.min_nr_regions = 10,
798c341ae3SSeongJae Park 	.max_nr_regions = 1000,
808c341ae3SSeongJae Park };
81fdfc119cSSeongJae Park DEFINE_DAMON_MODULES_MON_ATTRS_PARAMS(damon_reclaim_mon_attrs);
8243b0536cSSeongJae Park 
8343b0536cSSeongJae Park /*
8443b0536cSSeongJae Park  * Start of the target memory region in physical address.
8543b0536cSSeongJae Park  *
8643b0536cSSeongJae Park  * The start physical address of memory region that DAMON_RECLAIM will do work
8743b0536cSSeongJae Park  * against.  By default, biggest System RAM is used as the region.
8843b0536cSSeongJae Park  */
8943b0536cSSeongJae Park static unsigned long monitor_region_start __read_mostly;
9043b0536cSSeongJae Park module_param(monitor_region_start, ulong, 0600);
9143b0536cSSeongJae Park 
9243b0536cSSeongJae Park /*
9343b0536cSSeongJae Park  * End of the target memory region in physical address.
9443b0536cSSeongJae Park  *
9543b0536cSSeongJae Park  * The end physical address of memory region that DAMON_RECLAIM will do work
9643b0536cSSeongJae Park  * against.  By default, biggest System RAM is used as the region.
9743b0536cSSeongJae Park  */
9843b0536cSSeongJae Park static unsigned long monitor_region_end __read_mostly;
9943b0536cSSeongJae Park module_param(monitor_region_end, ulong, 0600);
10043b0536cSSeongJae Park 
10143b0536cSSeongJae Park /*
10266d9faecSSeongJae Park  * Skip anonymous pages reclamation.
10366d9faecSSeongJae Park  *
10466d9faecSSeongJae Park  * If this parameter is set as ``Y``, DAMON_RECLAIM does not reclaim anonymous
10566d9faecSSeongJae Park  * pages.  By default, ``N``.
10666d9faecSSeongJae Park  */
10766d9faecSSeongJae Park static bool skip_anon __read_mostly;
10866d9faecSSeongJae Park module_param(skip_anon, bool, 0600);
10966d9faecSSeongJae Park 
11066d9faecSSeongJae Park /*
11143b0536cSSeongJae Park  * PID of the DAMON thread
11243b0536cSSeongJae Park  *
11343b0536cSSeongJae Park  * If DAMON_RECLAIM is enabled, this becomes the PID of the worker thread.
11443b0536cSSeongJae Park  * Else, -1.
11543b0536cSSeongJae Park  */
11643b0536cSSeongJae Park static int kdamond_pid __read_mostly = -1;
11743b0536cSSeongJae Park module_param(kdamond_pid, int, 0400);
11843b0536cSSeongJae Park 
119b71f3ea8SSeongJae Park static struct damos_stat damon_reclaim_stat;
120b71f3ea8SSeongJae Park DEFINE_DAMON_MODULES_DAMOS_STATS_PARAMS(damon_reclaim_stat,
121b71f3ea8SSeongJae Park 		reclaim_tried_regions, reclaimed_regions, quota_exceeds);
12260e52e7cSSeongJae Park 
12343b0536cSSeongJae Park static struct damon_ctx *ctx;
12443b0536cSSeongJae Park static struct damon_target *target;
12543b0536cSSeongJae Park 
damon_reclaim_new_scheme(void)12643b0536cSSeongJae Park static struct damos *damon_reclaim_new_scheme(void)
12743b0536cSSeongJae Park {
128f5a79d7cSYajun Deng 	struct damos_access_pattern pattern = {
129f5a79d7cSYajun Deng 		/* Find regions having PAGE_SIZE or larger size */
130f5a79d7cSYajun Deng 		.min_sz_region = PAGE_SIZE,
131f5a79d7cSYajun Deng 		.max_sz_region = ULONG_MAX,
132f5a79d7cSYajun Deng 		/* and not accessed at all */
133f5a79d7cSYajun Deng 		.min_nr_accesses = 0,
134f5a79d7cSYajun Deng 		.max_nr_accesses = 0,
135f5a79d7cSYajun Deng 		/* for min_age or more micro-seconds */
1368c341ae3SSeongJae Park 		.min_age_region = min_age /
1378c341ae3SSeongJae Park 			damon_reclaim_mon_attrs.aggr_interval,
138f5a79d7cSYajun Deng 		.max_age_region = UINT_MAX,
139f5a79d7cSYajun Deng 	};
140f5a79d7cSYajun Deng 
141f5a79d7cSYajun Deng 	return damon_new_scheme(
142f5a79d7cSYajun Deng 			&pattern,
14343b0536cSSeongJae Park 			/* page out those, as soon as found */
14443b0536cSSeongJae Park 			DAMOS_PAGEOUT,
145*973739c9SSeongJae Park 			/* for each aggregation interval */
146*973739c9SSeongJae Park 			0,
14743b0536cSSeongJae Park 			/* under the quota. */
148a9d57c73SSeongJae Park 			&damon_reclaim_quota,
14943b0536cSSeongJae Park 			/* (De)activate this according to the watermarks. */
15034f47ea6SSeongJae Park 			&damon_reclaim_wmarks);
15143b0536cSSeongJae Park }
15243b0536cSSeongJae Park 
damon_reclaim_copy_quota_status(struct damos_quota * dst,struct damos_quota * src)1539cad9a2eSSeongJae Park static void damon_reclaim_copy_quota_status(struct damos_quota *dst,
1549cad9a2eSSeongJae Park 		struct damos_quota *src)
1559cad9a2eSSeongJae Park {
1569cad9a2eSSeongJae Park 	dst->total_charged_sz = src->total_charged_sz;
1579cad9a2eSSeongJae Park 	dst->total_charged_ns = src->total_charged_ns;
1589cad9a2eSSeongJae Park 	dst->charged_sz = src->charged_sz;
1599cad9a2eSSeongJae Park 	dst->charged_from = src->charged_from;
1609cad9a2eSSeongJae Park 	dst->charge_target_from = src->charge_target_from;
1619cad9a2eSSeongJae Park 	dst->charge_addr_from = src->charge_addr_from;
1629cad9a2eSSeongJae Park }
1639cad9a2eSSeongJae Park 
damon_reclaim_apply_parameters(void)164e035c280SSeongJae Park static int damon_reclaim_apply_parameters(void)
16543b0536cSSeongJae Park {
1669cad9a2eSSeongJae Park 	struct damos *scheme, *old_scheme;
16766d9faecSSeongJae Park 	struct damos_filter *filter;
168e035c280SSeongJae Park 	int err = 0;
16943b0536cSSeongJae Park 
1708c341ae3SSeongJae Park 	err = damon_set_attrs(ctx, &damon_reclaim_mon_attrs);
17143b0536cSSeongJae Park 	if (err)
17243b0536cSSeongJae Park 		return err;
17343b0536cSSeongJae Park 
174e035c280SSeongJae Park 	/* Will be freed by next 'damon_set_schemes()' below */
175e035c280SSeongJae Park 	scheme = damon_reclaim_new_scheme();
176e035c280SSeongJae Park 	if (!scheme)
177e035c280SSeongJae Park 		return -ENOMEM;
1789cad9a2eSSeongJae Park 	if (!list_empty(&ctx->schemes)) {
1799cad9a2eSSeongJae Park 		damon_for_each_scheme(old_scheme, ctx)
1809cad9a2eSSeongJae Park 			damon_reclaim_copy_quota_status(&scheme->quota,
1819cad9a2eSSeongJae Park 					&old_scheme->quota);
1829cad9a2eSSeongJae Park 	}
18366d9faecSSeongJae Park 	if (skip_anon) {
18466d9faecSSeongJae Park 		filter = damos_new_filter(DAMOS_FILTER_TYPE_ANON, true);
18566d9faecSSeongJae Park 		if (!filter) {
18666d9faecSSeongJae Park 			/* Will be freed by next 'damon_set_schemes()' below */
18766d9faecSSeongJae Park 			damon_destroy_scheme(scheme);
18866d9faecSSeongJae Park 			return -ENOMEM;
18966d9faecSSeongJae Park 		}
19066d9faecSSeongJae Park 		damos_add_filter(scheme, filter);
19166d9faecSSeongJae Park 	}
192cc713520SKaixu Xia 	damon_set_schemes(ctx, &scheme, 1);
193e035c280SSeongJae Park 
194233f0b31SKaixu Xia 	return damon_set_region_biggest_system_ram_default(target,
195233f0b31SKaixu Xia 					&monitor_region_start,
196233f0b31SKaixu Xia 					&monitor_region_end);
19743b0536cSSeongJae Park }
198e035c280SSeongJae Park 
damon_reclaim_turn(bool on)199e035c280SSeongJae Park static int damon_reclaim_turn(bool on)
200e035c280SSeongJae Park {
201e035c280SSeongJae Park 	int err;
202e035c280SSeongJae Park 
203e035c280SSeongJae Park 	if (!on) {
204e035c280SSeongJae Park 		err = damon_stop(&ctx, 1);
205e035c280SSeongJae Park 		if (!err)
206e035c280SSeongJae Park 			kdamond_pid = -1;
207e035c280SSeongJae Park 		return err;
208e035c280SSeongJae Park 	}
209e035c280SSeongJae Park 
210e035c280SSeongJae Park 	err = damon_reclaim_apply_parameters();
21143b0536cSSeongJae Park 	if (err)
212e035c280SSeongJae Park 		return err;
21343b0536cSSeongJae Park 
2148b9b0d33SSeongJae Park 	err = damon_start(&ctx, 1, true);
215e035c280SSeongJae Park 	if (err)
216e035c280SSeongJae Park 		return err;
21743b0536cSSeongJae Park 	kdamond_pid = ctx->kdamond->pid;
21843b0536cSSeongJae Park 	return 0;
21943b0536cSSeongJae Park }
22043b0536cSSeongJae Park 
damon_reclaim_enabled_store(const char * val,const struct kernel_param * kp)221d79905c7SSeongJae Park static int damon_reclaim_enabled_store(const char *val,
222059342d1SHailong Tu 		const struct kernel_param *kp)
223059342d1SHailong Tu {
22404e98764SSeongJae Park 	bool is_enabled = enabled;
22504e98764SSeongJae Park 	bool enable;
22604e98764SSeongJae Park 	int err;
227059342d1SHailong Tu 
228e6aff38bSChristophe JAILLET 	err = kstrtobool(val, &enable);
22904e98764SSeongJae Park 	if (err)
23004e98764SSeongJae Park 		return err;
231059342d1SHailong Tu 
23204e98764SSeongJae Park 	if (is_enabled == enable)
233059342d1SHailong Tu 		return 0;
23404e98764SSeongJae Park 
23504e98764SSeongJae Park 	/* Called before init function.  The function will handle this. */
23604e98764SSeongJae Park 	if (!ctx)
23704e98764SSeongJae Park 		goto set_param_out;
23804e98764SSeongJae Park 
23904e98764SSeongJae Park 	err = damon_reclaim_turn(enable);
24004e98764SSeongJae Park 	if (err)
24104e98764SSeongJae Park 		return err;
24204e98764SSeongJae Park 
24304e98764SSeongJae Park set_param_out:
24404e98764SSeongJae Park 	enabled = enable;
24504e98764SSeongJae Park 	return err;
246059342d1SHailong Tu }
247059342d1SHailong Tu 
248059342d1SHailong Tu static const struct kernel_param_ops enabled_param_ops = {
249d79905c7SSeongJae Park 	.set = damon_reclaim_enabled_store,
250059342d1SHailong Tu 	.get = param_get_bool,
251059342d1SHailong Tu };
252059342d1SHailong Tu 
253059342d1SHailong Tu module_param_cb(enabled, &enabled_param_ops, &enabled, 0600);
254059342d1SHailong Tu MODULE_PARM_DESC(enabled,
255059342d1SHailong Tu 	"Enable or disable DAMON_RECLAIM (default: disabled)");
256059342d1SHailong Tu 
damon_reclaim_handle_commit_inputs(void)257f25ab3bdSSeongJae Park static int damon_reclaim_handle_commit_inputs(void)
258f25ab3bdSSeongJae Park {
259f25ab3bdSSeongJae Park 	int err;
260f25ab3bdSSeongJae Park 
261f25ab3bdSSeongJae Park 	if (!commit_inputs)
262f25ab3bdSSeongJae Park 		return 0;
263f25ab3bdSSeongJae Park 
264f25ab3bdSSeongJae Park 	err = damon_reclaim_apply_parameters();
265f25ab3bdSSeongJae Park 	commit_inputs = false;
266f25ab3bdSSeongJae Park 	return err;
267f25ab3bdSSeongJae Park }
268f25ab3bdSSeongJae Park 
damon_reclaim_after_aggregation(struct damon_ctx * c)26960e52e7cSSeongJae Park static int damon_reclaim_after_aggregation(struct damon_ctx *c)
27060e52e7cSSeongJae Park {
27160e52e7cSSeongJae Park 	struct damos *s;
27260e52e7cSSeongJae Park 
27360e52e7cSSeongJae Park 	/* update the stats parameter */
274b71f3ea8SSeongJae Park 	damon_for_each_scheme(s, c)
275b71f3ea8SSeongJae Park 		damon_reclaim_stat = s->stat;
276e035c280SSeongJae Park 
277f25ab3bdSSeongJae Park 	return damon_reclaim_handle_commit_inputs();
278e035c280SSeongJae Park }
279e035c280SSeongJae Park 
damon_reclaim_after_wmarks_check(struct damon_ctx * c)280e035c280SSeongJae Park static int damon_reclaim_after_wmarks_check(struct damon_ctx *c)
281e035c280SSeongJae Park {
282f25ab3bdSSeongJae Park 	return damon_reclaim_handle_commit_inputs();
28360e52e7cSSeongJae Park }
28460e52e7cSSeongJae Park 
damon_reclaim_init(void)28543b0536cSSeongJae Park static int __init damon_reclaim_init(void)
28643b0536cSSeongJae Park {
2877ae2c17fSSeongJae Park 	int err = damon_modules_new_paddr_ctx_target(&ctx, &target);
28843b0536cSSeongJae Park 
2897ae2c17fSSeongJae Park 	if (err)
2907ae2c17fSSeongJae Park 		return err;
2914d69c345SSeongJae Park 
292e035c280SSeongJae Park 	ctx->callback.after_wmarks_check = damon_reclaim_after_wmarks_check;
29360e52e7cSSeongJae Park 	ctx->callback.after_aggregation = damon_reclaim_after_aggregation;
29443b0536cSSeongJae Park 
29504e98764SSeongJae Park 	/* 'enabled' has set before this function, probably via command line */
29604e98764SSeongJae Park 	if (enabled)
29704e98764SSeongJae Park 		err = damon_reclaim_turn(true);
29829492829SSeongJae Park 
29904e98764SSeongJae Park 	return err;
30043b0536cSSeongJae Park }
30143b0536cSSeongJae Park 
30243b0536cSSeongJae Park module_init(damon_reclaim_init);
303