damon_commit_ctx() does its holistic parameter set validation while applying the new parameter in the set one by one. If it finds a parameter is invalid, because some invalid parameters may already be committed (it is called "commit" but not atomic and irreversable), it stops the running DAMON context. The callers of the function therefore have to validate the parameters before calling it. Because the function already embeds holistic validation, DAMON_SYSFS reuses it in a safe way. It creates a test-purpose context that is not running but mimics the running one, and calls damon_commit_ctx() against the test purpose context. If it succeeds, the parameters are considered valid, and a real damon_commit_ctx() call against the running context is made with those. Other callers such as DAMON_RECLAIM and DAMON_LRU_SORT do not expose full parameters to users. For efficiency, they validate only the known set of parameters. The efficiency gain is arguably small and doubtful, though. Meanwhile the maintenance overhead of the multiple different validations is clearly high. We actually found and fixed a few bugs in the class. Update damon_commit_ctx() to embed DAMON_SYSFS' safe and holistic validation approach. Callers can simply call damon_commit_ctx() without worrying if their parameters are invalid. Note that damon_commit_ctx() can still cause an unexpected stop of the running context, if internal memory allocation fails. It is arguably unlikely since those internal allocations are too small to fail, but theoretically possible. It should also be better addressed, but not necessarily a blocker of this small and incremental improvement effort. Signed-off-by: SJ Park --- mm/damon/core.c | 61 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index 871c6f5257c9e..018dd5ff80324 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -1664,20 +1664,7 @@ static int damon_commit_probes(struct damon_ctx *dst, struct damon_ctx *src) return 0; } -/** - * damon_commit_ctx() - Commit parameters of a DAMON context to another. - * @dst: The commit destination DAMON context. - * @src: The commit source DAMON context. - * - * This function copies user-specified parameters from @src to @dst and update - * the internal status and results accordingly. Users should use this function - * for context-level parameters update of running context, instead of manual - * in-place updates. - * - * This function should be called from parameters-update safe context, like - * damon_call(). - */ -int damon_commit_ctx(struct damon_ctx *dst, struct damon_ctx *src) +static int __damon_commit_ctx(struct damon_ctx *dst, struct damon_ctx *src) { int err; struct damos *scheme; @@ -1732,6 +1719,52 @@ int damon_commit_ctx(struct damon_ctx *dst, struct damon_ctx *src) return 0; } +static struct damon_ctx *damon_new_test_ctx(struct damon_ctx *dst) +{ + struct damon_ctx *test_ctx; + int err; + + test_ctx = damon_new_ctx(); + if (!test_ctx) + return NULL; + err = __damon_commit_ctx(test_ctx, dst); + if (err) { + damon_destroy_ctx(test_ctx); + return NULL; + } + return test_ctx; +} + +/** + * damon_commit_ctx() - Commit parameters of a DAMON context to another. + * @dst: The commit destination DAMON context. + * @src: The commit source DAMON context. + * + * This function copies user-specified parameters from @src to @dst and update + * the internal status and results accordingly. Users should use this function + * for context-level parameters update of running context, instead of manual + * in-place updates. + * + * This function should be called from parameters-update safe context, like + * damon_call(). + */ +int damon_commit_ctx(struct damon_ctx *dst, struct damon_ctx *src) +{ + struct damon_ctx *test_ctx; + int err; + + test_ctx = damon_new_test_ctx(dst); + if (!test_ctx) + return -ENOMEM; + err = __damon_commit_ctx(test_ctx, src); + if (err) + goto out; + err = __damon_commit_ctx(dst, src); +out: + damon_destroy_ctx(test_ctx); + return err; +} + /** * damon_nr_running_ctxs() - Return number of currently running contexts. */ -- 2.47.3 damon_start() and damon_commit_ctx() are two main DAMON core API functions for setting whole DAMON parameters. While damon_commit_ctx() does holistic parameters testing, damon_start() just believes the caller validated the whole thing. Embed the holistic parameter check that is already in damon_commit_ctx() into damon_start(). After this change, the callers can safely call damon_start() without validating the parameters. Signed-off-by: SJ Park --- mm/damon/core.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index 018dd5ff80324..ca301abcb9ec5 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -1865,6 +1865,8 @@ static int __damon_start(struct damon_ctx *ctx) return err; } +static int __damon_commit_ctx(struct damon_ctx *dst, struct damon_ctx *src); + /** * damon_start() - Starts the monitorings for a given group of contexts. * @ctxs: an array of the pointers for contexts to start monitoring @@ -1886,8 +1888,16 @@ int damon_start(struct damon_ctx **ctxs, int nr_ctxs, bool exclusive) int err = 0; for (i = 0; i < nr_ctxs; i++) { - if (!is_power_of_2(ctxs[i]->min_region_sz)) - return -EINVAL; + struct damon_ctx *test_ctx; + + test_ctx = damon_new_ctx(); + if (!test_ctx) + return -ENOMEM; + + err = __damon_commit_ctx(test_ctx, ctxs[i]); + damon_destroy_ctx(test_ctx); + if (err) + return err; } mutex_lock(&damon_lock); -- 2.47.3 DAMON sysfs interface does parameters validation-purpose damon_commit_ctx() calls for parameters update. Now the same logic is embedded inside damon_commit_ctx() itself. Hence, the validation in DAMON sysfs interface is just an unnecessary duplicate. Remove it. Signed-off-by: SJ Park --- mm/damon/sysfs.c | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/mm/damon/sysfs.c b/mm/damon/sysfs.c index 6710b6d019bf5..e666dddf1feba 100644 --- a/mm/damon/sysfs.c +++ b/mm/damon/sysfs.c @@ -2098,26 +2098,6 @@ static int damon_sysfs_apply_inputs(struct damon_ctx *ctx, static struct damon_ctx *damon_sysfs_build_ctx( struct damon_sysfs_context *sys_ctx); -/* - * Return a new damon_ctx for testing new parameters to commit. - */ -static struct damon_ctx *damon_sysfs_new_test_ctx( - struct damon_ctx *running_ctx) -{ - struct damon_ctx *test_ctx; - int err; - - test_ctx = damon_new_ctx(); - if (!test_ctx) - return NULL; - err = damon_commit_ctx(test_ctx, running_ctx); - if (err) { - damon_destroy_ctx(test_ctx); - return NULL; - } - return test_ctx; -} - /* * damon_sysfs_commit_input() - Commit user inputs to a running kdamond. * @kdamond: The kobject wrapper for the associated kdamond. @@ -2127,7 +2107,7 @@ static struct damon_ctx *damon_sysfs_new_test_ctx( static int damon_sysfs_commit_input(void *data) { struct damon_sysfs_kdamond *kdamond = data; - struct damon_ctx *param_ctx, *test_ctx; + struct damon_ctx *param_ctx; int err; if (!damon_sysfs_kdamond_running(kdamond)) @@ -2139,17 +2119,7 @@ static int damon_sysfs_commit_input(void *data) param_ctx = damon_sysfs_build_ctx(kdamond->contexts->contexts_arr[0]); if (IS_ERR(param_ctx)) return PTR_ERR(param_ctx); - test_ctx = damon_sysfs_new_test_ctx(kdamond->damon_ctx); - if (!test_ctx) { - damon_destroy_ctx(param_ctx); - return -ENOMEM; - } - err = damon_commit_ctx(test_ctx, param_ctx); - if (err) - goto out; err = damon_commit_ctx(kdamond->damon_ctx, param_ctx); -out: - damon_destroy_ctx(test_ctx); damon_destroy_ctx(param_ctx); return err; } -- 2.47.3 DAMON_RECLAIM validates the user input for min_region_sz. The same validation is done inside damon_start() and damon_commit_ctx(). Remove the duplicate. Signed-off-by: SJ Park --- mm/damon/reclaim.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/mm/damon/reclaim.c b/mm/damon/reclaim.c index 11b70d0a9a6f0..6469b25cc34f9 100644 --- a/mm/damon/reclaim.c +++ b/mm/damon/reclaim.c @@ -208,11 +208,6 @@ static int damon_reclaim_apply_parameters(void) param_ctx->addr_unit = addr_unit; param_ctx->min_region_sz = max(DAMON_MIN_REGION_SZ / addr_unit, 1); - if (!is_power_of_2(param_ctx->min_region_sz)) { - err = -EINVAL; - goto out; - } - if (!damon_reclaim_mon_attrs.aggr_interval) { err = -EINVAL; goto out; -- 2.47.3 DAMON_LRU_SORT validates the user input for min_region_sz. The same validation is done inside damon_start() and damon_commit_ctx(). Remove the unnecessary duplicate. Signed-off-by: SJ Park --- mm/damon/lru_sort.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/mm/damon/lru_sort.c b/mm/damon/lru_sort.c index 32f41491b726b..2dd0cd0d26273 100644 --- a/mm/damon/lru_sort.c +++ b/mm/damon/lru_sort.c @@ -284,11 +284,6 @@ static int damon_lru_sort_apply_parameters(void) param_ctx->addr_unit = addr_unit; param_ctx->min_region_sz = max(DAMON_MIN_REGION_SZ / addr_unit, 1); - if (!is_power_of_2(param_ctx->min_region_sz)) { - err = -EINVAL; - goto out; - } - if (!damon_lru_sort_mon_attrs.sample_interval) { err = -EINVAL; goto out; -- 2.47.3 The kernel doc comment of damon_region clearly specifies every region should have positive size. But it is unclear who should verify it. damon_set_regions() is the recommended DAMON core function for setting regions from the callers, and has the verification. Update the comment to clarify the callers should be ok to pass any values for region addresses, as long as they use damon_set_regions(). Signed-off-by: SJ Park --- include/linux/damon.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/linux/damon.h b/include/linux/damon.h index 616bdf0954b52..2661231c0ae82 100644 --- a/include/linux/damon.h +++ b/include/linux/damon.h @@ -49,7 +49,8 @@ struct damon_size_range { * @list: List head for siblings. * @age: Age of this region. * - * For any use case, @ar should be non-zero positive size. + * For any use case, @ar should be non-zero positive size. damon_set_regions() + * does the validation. * * @nr_accesses is reset to zero for every &damon_attrs->aggr_interval and be * increased for every &damon_attrs->sample_interval if an access to the region -- 2.47.3 damon_set_region_system_rams() validates user inputs to avoid creating a negative size region. But DAMON core avoids zero size, too. The check is incomplete. The complete check is done inside damon_set_regions(), which is eventually called from damon_set_region_system_rams_default(). Drop the incomplete and unnecessary check. Signed-off-by: SJ Park --- mm/damon/core.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index ca301abcb9ec5..3c5a46e1c079a 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -3846,9 +3846,6 @@ int damon_set_region_system_rams_default(struct damon_target *t, { struct damon_addr_range addr_range; - if (*start > *end) - return -EINVAL; - if (!*start && !*end && !damon_find_system_rams_range(start, end, addr_unit)) return -EINVAL; -- 2.47.3 DAMON_SYSFS validates user inputs for monitoring target regions to disallow negative size regions. DAMON core assumes only positive size regions, though. The validation is incomplete. Fortunately damon_set_regions(), which is eventually used by DAMON_SYSFS, does the complete validation. Remove the incomplete and unnecessary validation. Signed-off-by: SJ Park --- mm/damon/sysfs.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/mm/damon/sysfs.c b/mm/damon/sysfs.c index e666dddf1feba..b65651498e0d1 100644 --- a/mm/damon/sysfs.c +++ b/mm/damon/sysfs.c @@ -1987,9 +1987,6 @@ static int damon_sysfs_set_regions(struct damon_target *t, struct damon_sysfs_region *sys_region = sysfs_regions->regions_arr[i]; - if (sys_region->ar.start > sys_region->ar.end) - goto out; - ranges[i].start = sys_region->ar.start; ranges[i].end = sys_region->ar.end; if (i == 0) -- 2.47.3