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