// SPDX-License-Identifier: GPL-2.0
/*
* DAMON Debugfs Interface
*
* Author: SeongJae Park <sj@kernel.org>
*/
#define pr_fmt(fmt) "damon-dbgfs: " fmt
#include <linux/damon.h>
#include <linux/debugfs.h>
#include <linux/file.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/page_idle.h>
#include <linux/slab.h>
#define DAMON_DBGFS_DEPRECATION_NOTICE \
"DAMON debugfs interface is deprecated, so users should move " \
"to DAMON_SYSFS. If you cannot, please report your usecase to " \
"damon@lists.linux.dev and linux-mm@kvack.org.\n"
static struct damon_ctx **dbgfs_ctxs;
static int dbgfs_nr_ctxs;
static struct dentry **dbgfs_dirs;
static DEFINE_MUTEX(damon_dbgfs_lock);
static void damon_dbgfs_warn_deprecation(void)
{
pr_warn_once(DAMON_DBGFS_DEPRECATION_NOTICE);
}
/*
* Returns non-empty string on success, negative error code otherwise.
*/
static char *user_input_str(const char __user *buf, size_t count, loff_t *ppos)
{
char *kbuf;
ssize_t ret;
/* We do not accept continuous write */
if (*ppos)
return ERR_PTR(-EINVAL);
kbuf = kmalloc(count + 1, GFP_KERNEL | __GFP_NOWARN);
if (!kbuf)
return ERR_PTR(-ENOMEM);
ret = simple_write_to_buffer(kbuf, count + 1, ppos, buf, count);
if (ret != count) {
kfree(kbuf);
return ERR_PTR(-EIO);
}
kbuf[ret] = '\0';
return kbuf;
}
static ssize_t dbgfs_attrs_read(struct file *file,
char __user *buf, size_t count, loff_t *ppos)
{
struct damon_ctx *ctx = file->private_data;
char kbuf[128];
int ret;
mutex_lock(&ctx->kdamond_lock);
ret = scnprintf(kbuf, ARRAY_SIZE(kbuf), "%lu %lu %lu %lu %lu\n",
ctx->attrs.sample_interval, ctx->attrs.aggr_interval,
ctx->attrs.ops_update_interval,
ctx->attrs.min_nr_regions, ctx->attrs.max_nr_regions);
mutex_unlock(&ctx->kdamond_lock);
return simple_read_from_buffer(buf, count, ppos, kbuf, ret);
}
static ssize_t dbgfs_attrs_write(struct file *file,
const char __user *buf, size_t count, loff_t *ppos)
{
struct damon_ctx *ctx = file->private_data;
struct damon_attrs attrs;
char *kbuf;
ssize_t ret;
kbuf = user_input_str(buf, count, ppos);
if (IS_ERR(kbuf))
return PTR_ERR(kbuf);
if (sscanf(kbuf, "%lu %lu %lu %lu %lu",
&attrs.sample_interval, &attrs.aggr_interval,
&attrs.ops_update_interval,
&attrs.min_nr_regions,
&attrs.max_nr_regions) != 5) {
ret = -EINVAL;
goto out;
}
mutex_lock(&ctx->kdamond_lock);
if (ctx->kdamond) {
ret = -EBUSY;
goto unlock_out;
}
ret = damon_set_attrs(ctx, &attrs);
if (!ret)
ret = count;
unlock_out:
mutex_unlock(&ctx->kdamond_lock);
out:
kfree(kbuf);
return ret;
}
/*
* Return corresponding dbgfs' scheme action value (int) for the given
* damos_action if the given damos_action value is valid and supported by
* dbgfs, negative error code otherwise.
*/
static int damos_action_to_dbgfs_scheme_action(enum damos_action action)
{
switch (action) {
case DAMOS_WILLNEED:
return 0;
case DAMOS_COLD:
return 1;
case DAMOS_PAGEOUT:
return 2;
case DAMOS_HUGEPAGE:
return 3;
case DAMOS_NOHUGEPAGE:
return 4;
case DAMOS_STAT:
return 5;
default:
return -EINVAL;
}
}
static ssize_t sprint_schemes(struct damon_ctx *c, char *buf, ssize_t len)
{
struct damos *s;
int written = 0;
int rc;
damon_for_each_scheme(s, c) {
rc = scnprintf(&buf[written], len - written,
"%lu %lu %u %u %u %u %d %lu %lu %lu %u %u %u %d %lu %lu %lu %lu %lu %lu %lu %lu %lu\n",
s->pattern.min_sz_region,
s->pattern.max_sz_region,
s