* Re: [PATCH net-next] docs: exclude driver and netdevsim bugs
From: Andrew Lunn @ 2026-06-03 19:25 UTC (permalink / raw)
To: Jakub Kicinski
Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms, johannes,
corbet, skhan, workflows, linux-doc
In-Reply-To: <20260603162943.2406080-1-kuba@kernel.org>
> +Additionally, netdev does not consider bugs to be ``net``-worthy
> +if they fulfill **all** of the following criteria:
> + - bug is in a hardware device driver;
> + - bug is either a missing error handling or is part of the error handling flow;
> + - bug was discovered by a static analysis / AI tool;
> + - bug was triggered/observed only with kernel changes or fault injection.
> +Fixes for such bugs should default to ``net-next`` and should **not** contain
> +a Fixes tag. Networking or driver maintainers may redirect such fixes to ``net``
> +at their discretion if they consider the condition to be relevant enough.
I would also stress what the stable rules say:
It must either fix a real bug that bothers people or ...
Many of the bug fixes we are currently getting don't meet this
criteria, so are net-next material.
Andrew
^ permalink raw reply
* Re: [PATCH v2 02/18] perf test: Add workload-ctl option
From: Arnaldo Carvalho de Melo @ 2026-06-03 19:22 UTC (permalink / raw)
To: James Clark
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Namhyung Kim, Jiri Olsa,
Ian Rogers, Amir Ayupov, Jonathan Corbet, Shuah Khan,
Paschalis Mpeis, coresight, linux-perf-users, linux-kernel,
Arnaldo Carvalho de Melo, linux-doc
In-Reply-To: <aiB-w5V1oQl4a0Sy@x1>
On Wed, Jun 03, 2026 at 04:21:44PM -0300, Arnaldo Carvalho de Melo wrote:
> On Tue, Jun 02, 2026 at 03:26:44PM +0100, James Clark wrote:
> > Add a --workload-ctl=fifo:ctl-fifo[,ack-fifo] option for 'perf test
> > -w'. When set, run_workload() opens the named FIFO, writes enable before
> > invoking the builtin workload, writes disable before returning, and
> > waits for ack responses when an ack FIFO is provided to ensure that the
> > workload doesn't run until the events are enabled.
> >
> > This can be used to limit the scope of the recording to only the
> > workload execution and avoid recording Perf setup and teardown code if
> > Perf record is started with events disabled (-D 1).
>
> I see no mention to the equivalent in 'perf record', from its man page:
Nevermind, I should've read it completely :-\
- Arnaldo
> ----------------------------------------------------------------------
> --control=fifo:ctl-fifo[,ack-fifo]::
> --control=fd:ctl-fd[,ack-fd]::
> ctl-fifo / ack-fifo are opened and used as ctl-fd / ack-fd as follows.
> Listen on ctl-fd descriptor for command to control measurement.
>
> Available commands:
>
> - 'enable' : enable events
> - 'disable' : disable events
> - 'enable name' : enable event 'name'
> - 'disable name' : disable event 'name'
> - 'snapshot' : AUX area tracing snapshot).
> - 'stop' : stop perf record
> - 'ping' : ping
> - 'evlist [-v|-g|-F] : display all events
>
> -F Show just the sample frequency used for each event.
> -v Show all fields.
> -g Show event group information.
> ----------------------------------------------------------------------
>
> Can this be shared code?
>
> - Arnaldo
>
> > Assisted-by: Codex:GPT-5.5
> > Signed-off-by: James Clark <james.clark@linaro.org>
> > ---
> > tools/perf/Documentation/perf-test.txt | 6 ++
> > tools/perf/tests/builtin-test.c | 184 ++++++++++++++++++++++++++++++++-
> > 2 files changed, 188 insertions(+), 2 deletions(-)
> >
> > diff --git a/tools/perf/Documentation/perf-test.txt b/tools/perf/Documentation/perf-test.txt
> > index 32da0d1fa86a..1faf30d4a7be 100644
> > --- a/tools/perf/Documentation/perf-test.txt
> > +++ b/tools/perf/Documentation/perf-test.txt
> > @@ -69,3 +69,9 @@ OPTIONS
> >
> > --list-workloads::
> > List the available workloads to use with -w/--workload.
> > +
> > +--workload-ctl=fifo:ctl-fifo[,ack-fifo]::
> > + Write 'enable' to ctl-fifo before running the workload and 'disable'
> > + before returning. If ack-fifo is provided, the workload runner waits for
> > + an 'ack' response after each command. This scopes the recording to only
> > + the workload if used with 'perf record -D 1 --control ...'.
> > diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c
> > index f2c135891477..d5df3efdce3b 100644
> > --- a/tools/perf/tests/builtin-test.c
> > +++ b/tools/perf/tests/builtin-test.c
> > @@ -50,6 +50,7 @@ static bool sequential;
> > static unsigned int runs_per_test = 1;
> > const char *dso_to_test;
> > const char *test_objdump_path = "objdump";
> > +static const char *workload_control;
> >
> > /*
> > * List of architecture specific tests. Not a weak symbol as the array length is
> > @@ -161,6 +162,11 @@ static struct test_workload *workloads[] = {
> > #endif
> > };
> >
> > +struct workload_control {
> > + int ctl_fd;
> > + int ack_fd;
> > +};
> > +
> > #define workloads__for_each(workload) \
> > for (unsigned i = 0; i < ARRAY_SIZE(workloads) && ({ workload = workloads[i]; 1; }); i++)
> >
> > @@ -711,13 +717,185 @@ static int workloads__fprintf_list(FILE *fp)
> > return printed;
> > }
> >
> > +static int perf_control_open_fifo(struct workload_control *ctl, const char *str)
> > +{
> > + char *s, *p;
> > + int ret;
> > +
> > + if (strncmp(str, "fifo:", 5))
> > + return -EINVAL;
> > +
> > + str += 5;
> > + if (!*str || *str == ',')
> > + return -EINVAL;
> > +
> > + s = strdup(str);
> > + if (!s)
> > + return -ENOMEM;
> > +
> > + p = strchr(s, ',');
> > + if (p)
> > + *p = '\0';
> > +
> > + ctl->ctl_fd = open(s, O_WRONLY | O_CLOEXEC);
> > + if (ctl->ctl_fd < 0) {
> > + ret = -errno;
> > + pr_err("Failed to open workload control FIFO '%s': %m\n", s);
> > + free(s);
> > + return ret;
> > + }
> > +
> > + if (p && *++p) {
> > + ctl->ack_fd = open(p, O_RDONLY | O_CLOEXEC);
> > + if (ctl->ack_fd < 0) {
> > + ret = -errno;
> > + pr_err("Failed to open workload control ack FIFO '%s': %m\n", p);
> > + close(ctl->ctl_fd);
> > + ctl->ctl_fd = -1;
> > + free(s);
> > + return ret;
> > + }
> > + }
> > +
> > + free(s);
> > + return 0;
> > +}
> > +
> > +static int perf_control_open(struct workload_control *ctl)
> > +{
> > + int ret;
> > +
> > + if (!workload_control)
> > + return 0;
> > +
> > + ret = perf_control_open_fifo(ctl, workload_control);
> > +
> > + if (ret == -EINVAL) {
> > + pr_err("Unsupported workload control spec '%s', expected fifo:ctl-fifo[,ack-fifo]\n",
> > + workload_control);
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static void perf_control_close(struct workload_control *ctl)
> > +{
> > + if (ctl->ctl_fd >= 0) {
> > + close(ctl->ctl_fd);
> > + ctl->ctl_fd = -1;
> > + }
> > + if (ctl->ack_fd >= 0) {
> > + close(ctl->ack_fd);
> > + ctl->ack_fd = -1;
> > + }
> > +}
> > +
> > +static int perf_control_write_cmd(int fd, const char *cmd)
> > +{
> > + size_t len = strlen(cmd);
> > + ssize_t ret;
> > +
> > + while (len) {
> > + ret = write(fd, cmd, len);
> > + if (ret < 0) {
> > + if (errno == EINTR)
> > + continue;
> > + pr_err("Failed to write perf control command '%s': %m\n", cmd);
> > + return -1;
> > + }
> > +
> > + if (!ret) {
> > + pr_err("Failed to write perf control command '%s': short write\n", cmd);
> > + return -1;
> > + }
> > +
> > + cmd += ret;
> > + len -= ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int perf_control_read_ack(int fd)
> > +{
> > + char buf[16];
> > + ssize_t ret;
> > +
> > + do {
> > + ret = read(fd, buf, sizeof(buf) - 1);
> > + } while (ret < 0 && errno == EINTR);
> > +
> > + if (ret < 0) {
> > + pr_err("Failed to read perf control ack: %m\n");
> > + return -1;
> > + }
> > +
> > + if (!ret) {
> > + pr_err("Unexpected EOF while reading perf control ack\n");
> > + return -1;
> > + }
> > +
> > + buf[ret] = '\0';
> > + for (ssize_t i = 0; i < ret; i++) {
> > + if (buf[i] == '\n' || buf[i] == '\0') {
> > + buf[i] = '\0';
> > + break;
> > + }
> > + }
> > +
> > + if (strcmp(buf, "ack")) {
> > + pr_err("Unexpected perf control ack: %s\n", buf);
> > + return -1;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int perf_control_send(struct workload_control *ctl, const char *cmd)
> > +{
> > + if (ctl->ctl_fd < 0)
> > + return 0;
> > +
> > + if (perf_control_write_cmd(ctl->ctl_fd, cmd))
> > + return -1;
> > +
> > + if (ctl->ack_fd >= 0 && perf_control_read_ack(ctl->ack_fd))
> > + return -1;
> > +
> > + return 0;
> > +}
> > +
> > static int run_workload(const char *work, int argc, const char **argv)
> > {
> > struct test_workload *twl;
> >
> > workloads__for_each(twl) {
> > - if (!strcmp(twl->name, work))
> > - return twl->func(argc, argv);
> > + struct workload_control ctl = {
> > + .ctl_fd = -1,
> > + .ack_fd = -1,
> > + };
> > + int control_ret, ret;
> > +
> > + if (strcmp(twl->name, work))
> > + continue;
> > +
> > + ret = perf_control_open(&ctl);
> > + if (ret)
> > + return ret;
> > +
> > + if (perf_control_send(&ctl, "enable\n")) {
> > + perf_control_close(&ctl);
> > + return -1;
> > + }
> > +
> > + ret = twl->func(argc, argv);
> > +
> > + control_ret = perf_control_send(&ctl, "disable\n");
> > + perf_control_close(&ctl);
> > + if (control_ret)
> > + return -1;
> > +
> > + return ret;
> > }
> >
> > pr_info("No workload found: %s\n", work);
> > @@ -799,6 +977,8 @@ int cmd_test(int argc, const char **argv)
> > OPT_UINTEGER('r', "runs-per-test", &runs_per_test,
> > "Run each test the given number of times, default 1"),
> > OPT_STRING('w', "workload", &workload, "work", "workload to run for testing, use '--list-workloads' to list the available ones."),
> > + OPT_STRING(0, "workload-ctl", &workload_control, "fifo:ctl-fifo[,ack-fifo]",
> > + "Write enable to the fifo just before running the workload and disable after, with optional ack from ack-fifo"),
> > OPT_BOOLEAN(0, "list-workloads", &list_workloads, "List the available builtin workloads to use with -w/--workload"),
> > OPT_STRING(0, "dso", &dso_to_test, "dso", "dso to test"),
> > OPT_STRING(0, "objdump", &test_objdump_path, "path",
> >
> > --
> > 2.34.1
^ permalink raw reply
* Re: [PATCH v2 02/18] perf test: Add workload-ctl option
From: Arnaldo Carvalho de Melo @ 2026-06-03 19:21 UTC (permalink / raw)
To: James Clark
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Namhyung Kim, Jiri Olsa,
Ian Rogers, Amir Ayupov, Jonathan Corbet, Shuah Khan,
Paschalis Mpeis, coresight, linux-perf-users, linux-kernel,
Arnaldo Carvalho de Melo, linux-doc
In-Reply-To: <20260602-james-cs-context-tracking-fix-v2-2-85b5ce6f55c6@linaro.org>
On Tue, Jun 02, 2026 at 03:26:44PM +0100, James Clark wrote:
> Add a --workload-ctl=fifo:ctl-fifo[,ack-fifo] option for 'perf test
> -w'. When set, run_workload() opens the named FIFO, writes enable before
> invoking the builtin workload, writes disable before returning, and
> waits for ack responses when an ack FIFO is provided to ensure that the
> workload doesn't run until the events are enabled.
>
> This can be used to limit the scope of the recording to only the
> workload execution and avoid recording Perf setup and teardown code if
> Perf record is started with events disabled (-D 1).
I see no mention to the equivalent in 'perf record', from its man page:
----------------------------------------------------------------------
--control=fifo:ctl-fifo[,ack-fifo]::
--control=fd:ctl-fd[,ack-fd]::
ctl-fifo / ack-fifo are opened and used as ctl-fd / ack-fd as follows.
Listen on ctl-fd descriptor for command to control measurement.
Available commands:
- 'enable' : enable events
- 'disable' : disable events
- 'enable name' : enable event 'name'
- 'disable name' : disable event 'name'
- 'snapshot' : AUX area tracing snapshot).
- 'stop' : stop perf record
- 'ping' : ping
- 'evlist [-v|-g|-F] : display all events
-F Show just the sample frequency used for each event.
-v Show all fields.
-g Show event group information.
----------------------------------------------------------------------
Can this be shared code?
- Arnaldo
> Assisted-by: Codex:GPT-5.5
> Signed-off-by: James Clark <james.clark@linaro.org>
> ---
> tools/perf/Documentation/perf-test.txt | 6 ++
> tools/perf/tests/builtin-test.c | 184 ++++++++++++++++++++++++++++++++-
> 2 files changed, 188 insertions(+), 2 deletions(-)
>
> diff --git a/tools/perf/Documentation/perf-test.txt b/tools/perf/Documentation/perf-test.txt
> index 32da0d1fa86a..1faf30d4a7be 100644
> --- a/tools/perf/Documentation/perf-test.txt
> +++ b/tools/perf/Documentation/perf-test.txt
> @@ -69,3 +69,9 @@ OPTIONS
>
> --list-workloads::
> List the available workloads to use with -w/--workload.
> +
> +--workload-ctl=fifo:ctl-fifo[,ack-fifo]::
> + Write 'enable' to ctl-fifo before running the workload and 'disable'
> + before returning. If ack-fifo is provided, the workload runner waits for
> + an 'ack' response after each command. This scopes the recording to only
> + the workload if used with 'perf record -D 1 --control ...'.
> diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c
> index f2c135891477..d5df3efdce3b 100644
> --- a/tools/perf/tests/builtin-test.c
> +++ b/tools/perf/tests/builtin-test.c
> @@ -50,6 +50,7 @@ static bool sequential;
> static unsigned int runs_per_test = 1;
> const char *dso_to_test;
> const char *test_objdump_path = "objdump";
> +static const char *workload_control;
>
> /*
> * List of architecture specific tests. Not a weak symbol as the array length is
> @@ -161,6 +162,11 @@ static struct test_workload *workloads[] = {
> #endif
> };
>
> +struct workload_control {
> + int ctl_fd;
> + int ack_fd;
> +};
> +
> #define workloads__for_each(workload) \
> for (unsigned i = 0; i < ARRAY_SIZE(workloads) && ({ workload = workloads[i]; 1; }); i++)
>
> @@ -711,13 +717,185 @@ static int workloads__fprintf_list(FILE *fp)
> return printed;
> }
>
> +static int perf_control_open_fifo(struct workload_control *ctl, const char *str)
> +{
> + char *s, *p;
> + int ret;
> +
> + if (strncmp(str, "fifo:", 5))
> + return -EINVAL;
> +
> + str += 5;
> + if (!*str || *str == ',')
> + return -EINVAL;
> +
> + s = strdup(str);
> + if (!s)
> + return -ENOMEM;
> +
> + p = strchr(s, ',');
> + if (p)
> + *p = '\0';
> +
> + ctl->ctl_fd = open(s, O_WRONLY | O_CLOEXEC);
> + if (ctl->ctl_fd < 0) {
> + ret = -errno;
> + pr_err("Failed to open workload control FIFO '%s': %m\n", s);
> + free(s);
> + return ret;
> + }
> +
> + if (p && *++p) {
> + ctl->ack_fd = open(p, O_RDONLY | O_CLOEXEC);
> + if (ctl->ack_fd < 0) {
> + ret = -errno;
> + pr_err("Failed to open workload control ack FIFO '%s': %m\n", p);
> + close(ctl->ctl_fd);
> + ctl->ctl_fd = -1;
> + free(s);
> + return ret;
> + }
> + }
> +
> + free(s);
> + return 0;
> +}
> +
> +static int perf_control_open(struct workload_control *ctl)
> +{
> + int ret;
> +
> + if (!workload_control)
> + return 0;
> +
> + ret = perf_control_open_fifo(ctl, workload_control);
> +
> + if (ret == -EINVAL) {
> + pr_err("Unsupported workload control spec '%s', expected fifo:ctl-fifo[,ack-fifo]\n",
> + workload_control);
> + }
> +
> + return ret;
> +}
> +
> +static void perf_control_close(struct workload_control *ctl)
> +{
> + if (ctl->ctl_fd >= 0) {
> + close(ctl->ctl_fd);
> + ctl->ctl_fd = -1;
> + }
> + if (ctl->ack_fd >= 0) {
> + close(ctl->ack_fd);
> + ctl->ack_fd = -1;
> + }
> +}
> +
> +static int perf_control_write_cmd(int fd, const char *cmd)
> +{
> + size_t len = strlen(cmd);
> + ssize_t ret;
> +
> + while (len) {
> + ret = write(fd, cmd, len);
> + if (ret < 0) {
> + if (errno == EINTR)
> + continue;
> + pr_err("Failed to write perf control command '%s': %m\n", cmd);
> + return -1;
> + }
> +
> + if (!ret) {
> + pr_err("Failed to write perf control command '%s': short write\n", cmd);
> + return -1;
> + }
> +
> + cmd += ret;
> + len -= ret;
> + }
> +
> + return 0;
> +}
> +
> +static int perf_control_read_ack(int fd)
> +{
> + char buf[16];
> + ssize_t ret;
> +
> + do {
> + ret = read(fd, buf, sizeof(buf) - 1);
> + } while (ret < 0 && errno == EINTR);
> +
> + if (ret < 0) {
> + pr_err("Failed to read perf control ack: %m\n");
> + return -1;
> + }
> +
> + if (!ret) {
> + pr_err("Unexpected EOF while reading perf control ack\n");
> + return -1;
> + }
> +
> + buf[ret] = '\0';
> + for (ssize_t i = 0; i < ret; i++) {
> + if (buf[i] == '\n' || buf[i] == '\0') {
> + buf[i] = '\0';
> + break;
> + }
> + }
> +
> + if (strcmp(buf, "ack")) {
> + pr_err("Unexpected perf control ack: %s\n", buf);
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +static int perf_control_send(struct workload_control *ctl, const char *cmd)
> +{
> + if (ctl->ctl_fd < 0)
> + return 0;
> +
> + if (perf_control_write_cmd(ctl->ctl_fd, cmd))
> + return -1;
> +
> + if (ctl->ack_fd >= 0 && perf_control_read_ack(ctl->ack_fd))
> + return -1;
> +
> + return 0;
> +}
> +
> static int run_workload(const char *work, int argc, const char **argv)
> {
> struct test_workload *twl;
>
> workloads__for_each(twl) {
> - if (!strcmp(twl->name, work))
> - return twl->func(argc, argv);
> + struct workload_control ctl = {
> + .ctl_fd = -1,
> + .ack_fd = -1,
> + };
> + int control_ret, ret;
> +
> + if (strcmp(twl->name, work))
> + continue;
> +
> + ret = perf_control_open(&ctl);
> + if (ret)
> + return ret;
> +
> + if (perf_control_send(&ctl, "enable\n")) {
> + perf_control_close(&ctl);
> + return -1;
> + }
> +
> + ret = twl->func(argc, argv);
> +
> + control_ret = perf_control_send(&ctl, "disable\n");
> + perf_control_close(&ctl);
> + if (control_ret)
> + return -1;
> +
> + return ret;
> }
>
> pr_info("No workload found: %s\n", work);
> @@ -799,6 +977,8 @@ int cmd_test(int argc, const char **argv)
> OPT_UINTEGER('r', "runs-per-test", &runs_per_test,
> "Run each test the given number of times, default 1"),
> OPT_STRING('w', "workload", &workload, "work", "workload to run for testing, use '--list-workloads' to list the available ones."),
> + OPT_STRING(0, "workload-ctl", &workload_control, "fifo:ctl-fifo[,ack-fifo]",
> + "Write enable to the fifo just before running the workload and disable after, with optional ack from ack-fifo"),
> OPT_BOOLEAN(0, "list-workloads", &list_workloads, "List the available builtin workloads to use with -w/--workload"),
> OPT_STRING(0, "dso", &dso_to_test, "dso", "dso to test"),
> OPT_STRING(0, "objdump", &test_objdump_path, "path",
>
> --
> 2.34.1
^ permalink raw reply
* Re: [RFC PATCH 0/5] mm, swap: Virtual Swap Space (Swap Table Edition)
From: Yosry Ahmed @ 2026-06-03 19:00 UTC (permalink / raw)
To: Nhat Pham
Cc: kasong, Liam.Howlett, akpm, apopple, axelrasmussen, baohua,
baolin.wang, bhe, byungchul, cgroups, chengming.zhou, chrisl,
corbet, david, dev.jain, gourry, hannes, hughd, jannh,
joshua.hahnjy, lance.yang, lenb, linux-doc, linux-kernel,
linux-mm, linux-pm, lorenzo.stoakes, matthew.brost, mhocko,
muchun.song, npache, pavel, peterx, peterz, pfalcato, rafael,
rakie.kim, roman.gushchin, rppt, ryan.roberts, shakeel.butt,
shikemeng, surenb, tglx, vbabka, weixugc, ying.huang, yosry.ahmed,
yuanchu, zhengqi.arch, ziy, kernel-team, riel, haowenchao22
In-Reply-To: <CAKEwX=MZQJLHNNU0tUqnihdhdPdVd19KhC-HtJxfbQ_d8OezzQ@mail.gmail.com>
> > > I don't like that the code bifurcates for vswap vs. normal swap entries
> > > though. Not sure if this is an issue that can be fixed with proper
> > > abstractions to hide it, or if the design needs modifications. I was
> > > honestly really hoping we don't end up with this. I was hoping that the
> > > physical swap device no longer uses a full swap table and all, and
> > > everything goes through vswap.
> > >
> > > I hoping that if redirection isn't needed (e.g. zswap is disabled),
> > > vswap can directly encode the physical swap slot so that the reverse
> > > mapping isn't needed -- so we avoid the overhead without keeping the
> > > physical swap device using a fully-fledged swap table.
> >
> > Can you expand on "vswap can directly encode the physical swap slot"?
> > I'm not sure I follow here.
> >
> > >
> > > All that being said, perhaps I am too out of touch with the code to
> > > realize it's simply not possible.
> > >
> > > Honestly, if the main reason we can't have a single swap table for vswap
> > > is saving 8 bytes on the reverse mapping, it sounds like a weak-ish
> > > argument, even if we can't optimize the reverse mapping away. But maybe
> > > I am also out of touch with RAM prices :)
> >
> > In terms of the space overhead I do agree, FWIW :)
> >
> > I think the other concern is the indirection overhead with going
> > through the xarray for every swap operation, hence the per-CPU vswap
> > cluster lookup caching idea:
> >
> > https://lore.kernel.org/all/20260505153854.1612033-23-nphamcs@gmail.com/
> >
> > >
> > > I at least hope that, the current design is not painting us into a
> > > corner (e.g. through userspace interfaces), and we can still achieve a
> > > vswap-for-all implementation in the future (maybe that's what you have
> > > in mind already?).
> >
> > That's still my plan. Operationally speaking, I want to make this
> > completely transparent to users, with minimal to no performance
> > overhead.
>
> I do want to add that, even without achieving this, the current design
> already enables a lot of use cases. I think it is a good compromise to
> maintain both virtual and directly mapped physical swap entries for
> now, and revisit the conversation of whether we can afford a mandatory
> vswap layer once all the optimizations have been done :)
>
> We should strive to simplify the codebase, and it will naturally
> happen when the original overhead concern is no longer there. A
> swap-related example: a few years ago, everyone thought swap slot
> cache was needed. But then, Kairui optimized the swap allocator's lock
> contention issue away, and that swap slot cache is suddenly redundant.
> That finally allowed us to get rid of it. Similar thing happened (or
> is happening?) with the SWP_SYNCHRONOUS_IO swapcache-skipping
> heuristics.
I agree, I just want to make sure we have a line of sight (or at least
no blockers) to having a unified vswap layer in the future.
^ permalink raw reply
* Re: [RFC PATCH 0/5] mm, swap: Virtual Swap Space (Swap Table Edition)
From: Yosry Ahmed @ 2026-06-03 18:58 UTC (permalink / raw)
To: Nhat Pham
Cc: kasong, Liam.Howlett, akpm, apopple, axelrasmussen, baohua,
baolin.wang, bhe, byungchul, cgroups, chengming.zhou, chrisl,
corbet, david, dev.jain, gourry, hannes, hughd, jannh,
joshua.hahnjy, lance.yang, lenb, linux-doc, linux-kernel,
linux-mm, linux-pm, lorenzo.stoakes, matthew.brost, mhocko,
muchun.song, npache, pavel, peterx, peterz, pfalcato, rafael,
rakie.kim, roman.gushchin, rppt, ryan.roberts, shakeel.butt,
shikemeng, surenb, tglx, vbabka, weixugc, ying.huang, yosry.ahmed,
yuanchu, zhengqi.arch, ziy, kernel-team, riel, haowenchao22
In-Reply-To: <CAKEwX=MWX9KkSFAoN4xEMg3b+gZUN9=yd7rirAWG5NOBf26eAg@mail.gmail.com>
> > I assume the main reason here is to avoid the extra overhead if
> > everything uses vswap, which would mainly be the reverse mapping
> > overhead? I guess there's also some simplicity that comes from reusing
> > the swap info infra as a whole, including the swap table.
>
> Yeah it helps a lot that we don't have to rewrite the whole allocator
> and swap entry reference counting logic again :)
I specifically meant using a full swap info thing for the physical swap
device even when it's behind vswap. That seems like an overkill, and we
don't need things like the swap entry reference coutning. We probably
just need a bitmap and a reverse mapping.
So I am assuming the main reason why we are not doing that (at least for
now) is simplicity?
> >
> > I don't like that the code bifurcates for vswap vs. normal swap entries
> > though. Not sure if this is an issue that can be fixed with proper
> > abstractions to hide it, or if the design needs modifications. I was
> > honestly really hoping we don't end up with this. I was hoping that the
> > physical swap device no longer uses a full swap table and all, and
> > everything goes through vswap.
> >
> > I hoping that if redirection isn't needed (e.g. zswap is disabled),
> > vswap can directly encode the physical swap slot so that the reverse
> > mapping isn't needed -- so we avoid the overhead without keeping the
> > physical swap device using a fully-fledged swap table.
>
> Can you expand on "vswap can directly encode the physical swap slot"?
> I'm not sure I follow here.
I meant that if redirection is not needed (e.g. zswap is disabled), then
instead of having a vswap device pointing at a physical swap device, we
can just the data (e.g. phyiscal swap slot) in the vswap device
directly. Then we don't need a full swap info thing and swap table for
the physical swap device.
This directly ties into my question above, about why we have a
fully-fledged swap info thing for the physical swap device when using
vswap.
> >
> > All that being said, perhaps I am too out of touch with the code to
> > realize it's simply not possible.
> >
> > Honestly, if the main reason we can't have a single swap table for vswap
> > is saving 8 bytes on the reverse mapping, it sounds like a weak-ish
> > argument, even if we can't optimize the reverse mapping away. But maybe
> > I am also out of touch with RAM prices :)
>
> In terms of the space overhead I do agree, FWIW :)
>
> I think the other concern is the indirection overhead with going
> through the xarray for every swap operation, hence the per-CPU vswap
> cluster lookup caching idea:
>
> https://lore.kernel.org/all/20260505153854.1612033-23-nphamcs@gmail.com/
Right, but we should already avoid the xarray with the swap table
design, right? We just have one swap table pointing to another
essentially?
> >
> > I at least hope that, the current design is not painting us into a
> > corner (e.g. through userspace interfaces), and we can still achieve a
> > vswap-for-all implementation in the future (maybe that's what you have
> > in mind already?).
>
> That's still my plan. Operationally speaking, I want to make this
> completely transparent to users, with minimal to no performance
> overhead.
So if CONFIG_VSWAP is set all swap devices are vswap by default, right?
Would it help with testing if it's controlled by a boot param?
>
> The next action item is to optimize for vswap-on-fast-swapfile case -
> that was Kairui's main concerns regarding performance. I spent a lot
> of time perfing and fixing issues for this case in v6. The issues with
> the most egregious effects and simplest fix (vswap-less
> swap-cache-only check for e.g) are already fixed in this new design,
> and eventually I will move the rest (lookup caching) and more to here.
So is the end goal to have vswap be the default rather than a special
swap device? It would certainly help to include some details about that.
> >
> > Aside from the swap code, the only sticking point for me is the logic
> > bifurcation in zswap. Why does zswap need to handle vswap vs. not vswap?
> > I thought the point of the design is to use vswap when zswap is used,
> > and otherwise use a normal swap table. In a way, one of the goals is to
> > make zswap a first class swap citizen, but it doesn't seem like we are
> > achieving that?
>
> We already have all the machinery to make zswap completely
> independent. Right now, if you use vswap, you'll skip the zswap's
> internal xarray entirely, and just store a zswap entry in the virtual
> swap cluster's vtable.
>
> I just haven't removed the old code for 2 reasons:
>
> 1. Reduce the delta on this RFC, to ease the burden for reviewers (and
> definitely not because I'm lazy :P)
>
> 2. The only other practical reason is so that we can let users compile
> with !CONFIG_VSWAP and still uses zswap on top of the old swapfile
> setup during the transition/experimentation period for now.
>
> But logically and conceptually speaking, there is no reason I can come
> up with to use zswap on without vswap. The CPU indirection overhead is
> already partially there (since zswap uses an xarray) and further
> optimized (cluster loopup caching etc.), as well as the space overhead
> (vswap replaces the zswap xarray). I actually wrote a whole paragraph
> about how we should always go for vswap if we're using zswap, but then
> decide to remove it since there's no code for it yet.
>
> If folks like it, what I can do is have CONFIG_ZSWAP depends on
> CONFIG_VSWAP, removes all the non-vswap logic, and call it a day? :)
> Then, on the swap allocation side, if vswap allocation fail and zswap
> writeback is disabled, we can error out early.
Hmm maybe we can keep it around for now and do that after vswap
stabilizes? It ultimately depend on how much complexity we maintain by
allowing both.
I think another problem is 32-bit, technically zswap can be used on
32-bit now, right? So vswap not supporitng 32-bit is a problem.
General question (for both zswap and general swap code), would a boot
param make implementation simpler? Right now we seem to key off the swap
device having the "vswap" flag, would it help if it was a runtime
constant?
^ permalink raw reply
* Re: [PATCH v3 2/4] mm/zswap: Implement proactive writeback
From: Yosry Ahmed @ 2026-06-03 18:54 UTC (permalink / raw)
To: Nhat Pham
Cc: Hao Jia, akpm, tj, hannes, shakeel.butt, mhocko, mkoutny,
chengming.zhou, muchun.song, roman.gushchin, cgroups, linux-mm,
linux-kernel, linux-doc, Hao Jia
In-Reply-To: <CAKEwX=OeFsFPdazWFf0tsKTOi3DneX3fCcreEkzwb=BOR_37hA@mail.gmail.com>
> > > I suspect not a lot of people invokes the shrink_memcg() synchronous
> > > path in zswap store though. Setting zswap.max is hard (as it involves
> > > guessing compression ratio ahead of time) and induces quite a bit of
> > > overhead (obj_cgroup_may_zswap() does a force flush for every store if
> > > you set zswap.max to a value other than 0 and max).
> >
> > Yeah I agree, but I just wanna make sure we don't completely kill
> > performance in case anyone is actually doing that.
> >
> > Regarding the force flush, it's unfortunate that we need to rely on a
> > stat for internal limit checking. It might make sense to use page
> > counters here, which already support hierarchical charging and
> > whatnot. We'll need something similar to the objcg approach to charge
> > sub-pages (or maybe just reuse that somehow?).
>
> Conceptually, I've moved towards not capping zswap.max, and using it
> only as a binary knob for enablement/disablement :)
>
> It's consuming the same resources it saves, so if we cap memory.max
> (and swap.max until we have vswap), then we already have isolation.
> The split between uncompressed/compressed should then be dynamically
> determined by workload characteristics (working set size, access
> pattern, compressibility) and service's SLO, and reclaimers
> (preferably proactive reclaimers) will drive us towards the optimal
> split/equilibrium.
Yeah I guess we can wait until someone actually has a use case for a
non-binary zswap.max and complains about performance.
^ permalink raw reply
* Re: [PATCH v3 2/4] mm/zswap: Implement proactive writeback
From: Nhat Pham @ 2026-06-03 18:51 UTC (permalink / raw)
To: Yosry Ahmed
Cc: Hao Jia, akpm, tj, hannes, shakeel.butt, mhocko, mkoutny,
chengming.zhou, muchun.song, roman.gushchin, cgroups, linux-mm,
linux-kernel, linux-doc, Hao Jia
In-Reply-To: <CAO9r8zO0GjTeY2+j_AjfTQ8qYOmHezZAV44f=aPgrjJcdtO9rQ@mail.gmail.com>
On Wed, Jun 3, 2026 at 11:43 AM Yosry Ahmed <yosry@kernel.org> wrote:
>
> On Wed, Jun 3, 2026 at 11:34 AM Nhat Pham <nphamcs@gmail.com> wrote:
> >
> > On Wed, Jun 3, 2026 at 11:26 AM Yosry Ahmed <yosry@kernel.org> wrote:
> > >
> > > > > > Is the main difference that we are scanning in batches here? I think we
> > > > > > can have shrink_memcg() do that too. If anything, it might make the
> > > > > > shrinker more efficient. Over-reclaim is ofc a concern, and especially
> > > > > > in the zswap_store() path as the overhead can be noticeable. Maybe we
> > > > > > can parameterize the batch size based on the code path.
> > > > > >
> > > > > > Nhat, what do you think?
> > > > >
> > > > > Nhat, since we now have the referenced-based second chance algorithm,
> > > > > should we consider doing batch writeback for shrink_memcg() as well?
> > > >
> > > > I just take a look at shrink_memcg() and realized it's reclaiming one
> > > > page at a time. Thanks for the reminder - I hated it.
> > > >
> > > > Please batchify it if it makes your life easier :) We don't reclaim
> > > > "just one page/object" anywhere else in the reclaim path, Sure, it
> > > > adds a bit of latency to zswap_store() if we reached cgroup limit, but
> > > > IMHO if we hit zswap.max limit at zswap_store() time, that is already
> > > > the slowest of slow path that you should have avoided with proactive
> > > > reclaim/zswap shrinker in the first place. And, setting zswap.max does
> > > > not make sense to me in the first place (I can write a whole essay
> > > > about it).
> > >
> > > Should we batchify shrink_memcg() from the shrinker and background
> > > writeback, but leave the synchronous zswap_store() path to reclaim one
> > > page for this series at least to avoid potential regressions?
> > >
> > > I think this change specifically needs more intensive testing (vs the
> > > other code paths).
> >
> > I'm fine with having shrink_memcg() takes a batch_size argument for now :)
> >
> > I suspect not a lot of people invokes the shrink_memcg() synchronous
> > path in zswap store though. Setting zswap.max is hard (as it involves
> > guessing compression ratio ahead of time) and induces quite a bit of
> > overhead (obj_cgroup_may_zswap() does a force flush for every store if
> > you set zswap.max to a value other than 0 and max).
>
> Yeah I agree, but I just wanna make sure we don't completely kill
> performance in case anyone is actually doing that.
>
> Regarding the force flush, it's unfortunate that we need to rely on a
> stat for internal limit checking. It might make sense to use page
> counters here, which already support hierarchical charging and
> whatnot. We'll need something similar to the objcg approach to charge
> sub-pages (or maybe just reuse that somehow?).
Conceptually, I've moved towards not capping zswap.max, and using it
only as a binary knob for enablement/disablement :)
It's consuming the same resources it saves, so if we cap memory.max
(and swap.max until we have vswap), then we already have isolation.
The split between uncompressed/compressed should then be dynamically
determined by workload characteristics (working set size, access
pattern, compressibility) and service's SLO, and reclaimers
(preferably proactive reclaimers) will drive us towards the optimal
split/equilibrium.
^ permalink raw reply
* Re: [PATCH v3 2/4] mm/zswap: Implement proactive writeback
From: Yosry Ahmed @ 2026-06-03 18:43 UTC (permalink / raw)
To: Nhat Pham
Cc: Hao Jia, akpm, tj, hannes, shakeel.butt, mhocko, mkoutny,
chengming.zhou, muchun.song, roman.gushchin, cgroups, linux-mm,
linux-kernel, linux-doc, Hao Jia
In-Reply-To: <CAKEwX=NQUqqrM9vdYE2KhWEZx-YwPc7YPhfz7xaBrGVDf824bA@mail.gmail.com>
On Wed, Jun 3, 2026 at 11:34 AM Nhat Pham <nphamcs@gmail.com> wrote:
>
> On Wed, Jun 3, 2026 at 11:26 AM Yosry Ahmed <yosry@kernel.org> wrote:
> >
> > > > > Is the main difference that we are scanning in batches here? I think we
> > > > > can have shrink_memcg() do that too. If anything, it might make the
> > > > > shrinker more efficient. Over-reclaim is ofc a concern, and especially
> > > > > in the zswap_store() path as the overhead can be noticeable. Maybe we
> > > > > can parameterize the batch size based on the code path.
> > > > >
> > > > > Nhat, what do you think?
> > > >
> > > > Nhat, since we now have the referenced-based second chance algorithm,
> > > > should we consider doing batch writeback for shrink_memcg() as well?
> > >
> > > I just take a look at shrink_memcg() and realized it's reclaiming one
> > > page at a time. Thanks for the reminder - I hated it.
> > >
> > > Please batchify it if it makes your life easier :) We don't reclaim
> > > "just one page/object" anywhere else in the reclaim path, Sure, it
> > > adds a bit of latency to zswap_store() if we reached cgroup limit, but
> > > IMHO if we hit zswap.max limit at zswap_store() time, that is already
> > > the slowest of slow path that you should have avoided with proactive
> > > reclaim/zswap shrinker in the first place. And, setting zswap.max does
> > > not make sense to me in the first place (I can write a whole essay
> > > about it).
> >
> > Should we batchify shrink_memcg() from the shrinker and background
> > writeback, but leave the synchronous zswap_store() path to reclaim one
> > page for this series at least to avoid potential regressions?
> >
> > I think this change specifically needs more intensive testing (vs the
> > other code paths).
>
> I'm fine with having shrink_memcg() takes a batch_size argument for now :)
>
> I suspect not a lot of people invokes the shrink_memcg() synchronous
> path in zswap store though. Setting zswap.max is hard (as it involves
> guessing compression ratio ahead of time) and induces quite a bit of
> overhead (obj_cgroup_may_zswap() does a force flush for every store if
> you set zswap.max to a value other than 0 and max).
Yeah I agree, but I just wanna make sure we don't completely kill
performance in case anyone is actually doing that.
Regarding the force flush, it's unfortunate that we need to rely on a
stat for internal limit checking. It might make sense to use page
counters here, which already support hierarchical charging and
whatnot. We'll need something similar to the objcg approach to charge
sub-pages (or maybe just reuse that somehow?).
^ permalink raw reply
* Re: [PATCH v3 2/4] mm/zswap: Implement proactive writeback
From: Nhat Pham @ 2026-06-03 18:34 UTC (permalink / raw)
To: Yosry Ahmed
Cc: Hao Jia, akpm, tj, hannes, shakeel.butt, mhocko, mkoutny,
chengming.zhou, muchun.song, roman.gushchin, cgroups, linux-mm,
linux-kernel, linux-doc, Hao Jia
In-Reply-To: <CAO9r8zMBUMXy_bkeT8z+M=dXayU=6VGEw+-HmfDWR2fyJy=z+A@mail.gmail.com>
On Wed, Jun 3, 2026 at 11:26 AM Yosry Ahmed <yosry@kernel.org> wrote:
>
> > > > Is the main difference that we are scanning in batches here? I think we
> > > > can have shrink_memcg() do that too. If anything, it might make the
> > > > shrinker more efficient. Over-reclaim is ofc a concern, and especially
> > > > in the zswap_store() path as the overhead can be noticeable. Maybe we
> > > > can parameterize the batch size based on the code path.
> > > >
> > > > Nhat, what do you think?
> > >
> > > Nhat, since we now have the referenced-based second chance algorithm,
> > > should we consider doing batch writeback for shrink_memcg() as well?
> >
> > I just take a look at shrink_memcg() and realized it's reclaiming one
> > page at a time. Thanks for the reminder - I hated it.
> >
> > Please batchify it if it makes your life easier :) We don't reclaim
> > "just one page/object" anywhere else in the reclaim path, Sure, it
> > adds a bit of latency to zswap_store() if we reached cgroup limit, but
> > IMHO if we hit zswap.max limit at zswap_store() time, that is already
> > the slowest of slow path that you should have avoided with proactive
> > reclaim/zswap shrinker in the first place. And, setting zswap.max does
> > not make sense to me in the first place (I can write a whole essay
> > about it).
>
> Should we batchify shrink_memcg() from the shrinker and background
> writeback, but leave the synchronous zswap_store() path to reclaim one
> page for this series at least to avoid potential regressions?
>
> I think this change specifically needs more intensive testing (vs the
> other code paths).
I'm fine with having shrink_memcg() takes a batch_size argument for now :)
I suspect not a lot of people invokes the shrink_memcg() synchronous
path in zswap store though. Setting zswap.max is hard (as it involves
guessing compression ratio ahead of time) and induces quite a bit of
overhead (obj_cgroup_may_zswap() does a force flush for every store if
you set zswap.max to a value other than 0 and max).
^ permalink raw reply
* Re: [PATCH v3 2/4] mm/zswap: Implement proactive writeback
From: Yosry Ahmed @ 2026-06-03 18:26 UTC (permalink / raw)
To: Nhat Pham
Cc: Hao Jia, akpm, tj, hannes, shakeel.butt, mhocko, mkoutny,
chengming.zhou, muchun.song, roman.gushchin, cgroups, linux-mm,
linux-kernel, linux-doc, Hao Jia
In-Reply-To: <CAKEwX=PoBZ4ci30tKHQXs1o9=NDpPrtbe7RxxZTbnzVJf74ZYQ@mail.gmail.com>
> > > Is the main difference that we are scanning in batches here? I think we
> > > can have shrink_memcg() do that too. If anything, it might make the
> > > shrinker more efficient. Over-reclaim is ofc a concern, and especially
> > > in the zswap_store() path as the overhead can be noticeable. Maybe we
> > > can parameterize the batch size based on the code path.
> > >
> > > Nhat, what do you think?
> >
> > Nhat, since we now have the referenced-based second chance algorithm,
> > should we consider doing batch writeback for shrink_memcg() as well?
>
> I just take a look at shrink_memcg() and realized it's reclaiming one
> page at a time. Thanks for the reminder - I hated it.
>
> Please batchify it if it makes your life easier :) We don't reclaim
> "just one page/object" anywhere else in the reclaim path, Sure, it
> adds a bit of latency to zswap_store() if we reached cgroup limit, but
> IMHO if we hit zswap.max limit at zswap_store() time, that is already
> the slowest of slow path that you should have avoided with proactive
> reclaim/zswap shrinker in the first place. And, setting zswap.max does
> not make sense to me in the first place (I can write a whole essay
> about it).
Should we batchify shrink_memcg() from the shrinker and background
writeback, but leave the synchronous zswap_store() path to reclaim one
page for this series at least to avoid potential regressions?
I think this change specifically needs more intensive testing (vs the
other code paths).
^ permalink raw reply
* Re: [PATCH v3 2/4] mm/zswap: Implement proactive writeback
From: Nhat Pham @ 2026-06-03 18:23 UTC (permalink / raw)
To: Hao Jia
Cc: Yosry Ahmed, akpm, tj, hannes, shakeel.butt, mhocko, mkoutny,
chengming.zhou, muchun.song, roman.gushchin, cgroups, linux-mm,
linux-kernel, linux-doc, Hao Jia
In-Reply-To: <ea2c1323-1440-e927-f14a-0eac54a245bf@gmail.com>
On Wed, Jun 3, 2026 at 4:27 AM Hao Jia <jiahao.kernel@gmail.com> wrote:
>
>
>
> On 2026/5/30 09:37, Yosry Ahmed wrote:
> > On Tue, May 26, 2026 at 07:45:59PM +0800, Hao Jia wrote:
> >> From: Hao Jia <jiahao1@lixiang.com>
> >>
> >> Zswap currently writes back pages to backing swap reactively, triggered
> >> either by the shrinker or when the pool reaches its size limit. There is
> >> no mechanism to control the amount of writeback for a specific memory
> >> cgroup. However, users may want to proactively write back zswap pages,
> >> e.g., to free up memory for other applications or to prepare for
> >> memory-intensive workloads.
> >>
> >> Introduce a "zswap_writeback_only" key to the memory.reclaim cgroup
> >> interface. When specified, this key bypasses standard memory reclaim
> >> and exclusively performs proactive zswap writeback up to the requested
> >> budget. If omitted, the default reclaim behavior remains unchanged.
> >>
> >> Example usage:
> >> # Write back 100MB of pages from zswap to the backing swap
> >> echo "100M zswap_writeback_only" > memory.reclaim
> >>
> >> Note that the actual amount written back may be less than requested due
> >> to the zswap second-chance algorithm: referenced entries are rotated on
> >> the LRU on the first encounter and only written back on a second pass.
> >> If fewer bytes are written back than requested, -EAGAIN is returned,
> >> matching the existing memory.reclaim semantics.
> >>
> >> Internally, extend user_proactive_reclaim() to parse the new
> >> "zswap_writeback_only" token and invoke the dedicated handler. Add
> >> zswap_proactive_writeback() to walk the target memcg subtree via the
> >> per-memcg writeback cursor, draining per-node zswap LRUs through
> >> list_lru_walk_one() with the shrink_memcg_cb() callback.
> >>
> >> Suggested-by: Yosry Ahmed <yosry@kernel.org>
> >> Suggested-by: Nhat Pham <nphamcs@gmail.com>
> >> Signed-off-by: Hao Jia <jiahao1@lixiang.com>
> >> ---
> >> Documentation/admin-guide/cgroup-v2.rst | 18 +++-
> >> Documentation/admin-guide/mm/zswap.rst | 11 +-
> >> include/linux/zswap.h | 7 ++
> >> mm/vmscan.c | 14 +++
> >> mm/zswap.c | 138 ++++++++++++++++++++++++
> >> 5 files changed, 185 insertions(+), 3 deletions(-)
> >>
> >> diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
> >> index 6efd0095ed99..6564abf0dec5 100644
> >> --- a/Documentation/admin-guide/cgroup-v2.rst
> >> +++ b/Documentation/admin-guide/cgroup-v2.rst
> >> @@ -1425,9 +1425,10 @@ PAGE_SIZE multiple when read back.
> >>
> >> The following nested keys are defined.
> >>
> >> - ========== ================================
> >> + ==================== ==================================================
> >> swappiness Swappiness value to reclaim with
> >> - ========== ================================
> >> + zswap_writeback_only Only perform proactive zswap writeback
> >> + ==================== ==================================================
> >>
> >> Specifying a swappiness value instructs the kernel to perform
> >> the reclaim with that swappiness value. Note that this has the
> >> @@ -1437,6 +1438,19 @@ The following nested keys are defined.
> >> The valid range for swappiness is [0-200, max], setting
> >> swappiness=max exclusively reclaims anonymous memory.
> >>
> >> + The zswap_writeback_only key skips ordinary memory reclaim and
> >> + writes back pages from zswap to the backing swap device until
> >> + the requested amount has been written or no further candidates
> >> + are found. This is useful to proactively offload cold pages from
> >> + the zswap pool to the swap device. It is only available if
> >> + zswap writeback is enabled. zswap_writeback_only cannot be combined
> >> + with swappiness; specifying both returns -EINVAL.
> >> +
> >> + Example::
> >> +
> >> + # Write back up to 100MB of pages from zswap to the backing swap
> >> + echo "100M zswap_writeback_only" > memory.reclaim
> >
> >
> > memcg folks need to chime in about the interface here. An alternative
> > would be a separate interface (e.g. memory.zswap.do_writeback or
> > memory.zswap.writeback.reclaim or sth).
> >
> >> diff --git a/mm/zswap.c b/mm/zswap.c
> >> index 73e64a635690..7bcbf788f634 100644
> >> --- a/mm/zswap.c
> >> +++ b/mm/zswap.c
> >> @@ -1679,6 +1679,144 @@ int zswap_load(struct folio *folio)
> >> return 0;
> >> }
> >>
> >> +/*
> >> + * Maximum LRU scan limit:
> >> + * number of entries to scan per page of remaining budget.
> >> + */
> >> +#define ZSWAP_PROACTIVE_WB_SCAN_RATIO 16UL
> >> +/*
> >> + * Batch size for proactive writeback:
> >> + * - As the per-memcg writeback target in the outer memcg loop.
> >> + * - As the per-walk budget passed to list_lru_walk_one().
> >> + */
> >> +#define ZSWAP_PROACTIVE_WB_BATCH 128UL
> >> +
> >> +/*
> >> + * Walk the per-node LRUs of @memcg to write back up to @nr_to_write pages.
> >> + * Returns the number of pages written back, or -ENOENT if @memcg is a
> >> + * zombie or has writeback disabled.
> >> + */
> >> +static long zswap_proactive_shrink_memcg(struct mem_cgroup *memcg,
> >> + unsigned long nr_to_write)
> >> +{
> >> + unsigned long nr_written = 0;
> >> + int nid;
> >> +
> >> + if (!mem_cgroup_zswap_writeback_enabled(memcg))
> >> + return -ENOENT;
> >> +
> >> + if (!mem_cgroup_online(memcg))
> >> + return -ENOENT;
> >> +
> >> + for_each_node_state(nid, N_NORMAL_MEMORY) {
> >> + bool encountered_page_in_swapcache = false;
> >> + unsigned long nr_to_scan, nr_scanned = 0;
> >> +
> >> + /*
> >> + * Cap by LRU length: bounds rewalks when referenced
> >> + * entries keep rotating to the tail.
> >> + */
> >> + nr_to_scan = list_lru_count_one(&zswap_list_lru, nid, memcg);
> >> + if (!nr_to_scan)
> >> + continue;
> >> +
> >> + /*
> >> + * Cap by SCAN_RATIO * remaining budget: bounds scan cost
> >> + * to the remaining writeback budget.
> >> + */
> >> + nr_to_scan = min(nr_to_scan,
> >> + (nr_to_write - nr_written) * ZSWAP_PROACTIVE_WB_SCAN_RATIO);
> >> +
> >> + while (nr_scanned < nr_to_scan) {
> >> + unsigned long nr_to_walk = min(ZSWAP_PROACTIVE_WB_BATCH,
> >> + nr_to_scan - nr_scanned);
> >> +
> >> + if (signal_pending(current))
> >> + return nr_written;
> >> +
> >> + /*
> >> + * Account for the committed budget rather than the walker's
> >> + * actual delta. If the list is emptied concurrently, the
> >> + * walker visits nothing and nr_scanned would never advance.
> >> + */
> >> + nr_scanned += nr_to_walk;
> >> +
> >> + nr_written += list_lru_walk_one(&zswap_list_lru, nid, memcg,
> >> + &shrink_memcg_cb,
> >> + &encountered_page_in_swapcache,
> >> + &nr_to_walk);
> >> +
> >> + if (nr_written >= nr_to_write)
> >> + return nr_written;
> >> + if (encountered_page_in_swapcache)
> >> + break;
> >> +
> >> + cond_resched();
> >> + }
> >> + }
> >> +
> >> + return nr_written;
> >> +}
> >> +
> >> +int zswap_proactive_writeback(struct mem_cgroup *memcg,
> >> + unsigned long nr_to_writeback)
> >> +{
> >> + struct mem_cgroup *iter_memcg;
> >> + unsigned long nr_written = 0;
> >> + int failures = 0, attempts = 0;
> >> +
> >> + if (!memcg)
> >> + return -EINVAL;
> >> + if (!nr_to_writeback)
> >> + return 0;
> >> +
> >> + /*
> >> + * Writeback will be aborted with -EAGAIN if we encounter
> >> + * the following MAX_RECLAIM_RETRIES times:
> >> + * - No writeback-candidate memcgs found in a subtree walk.
> >> + * - A writeback-candidate memcg wrote back zero pages.
> >> + */
> >> + while (nr_written < nr_to_writeback) {
> >> + unsigned long batch_size;
> >> + long shrunk;
> >> +
> >> + if (signal_pending(current))
> >> + return -EINTR;
> >> +
> >> + iter_memcg = zswap_mem_cgroup_iter(memcg);
> >> +
> >> + if (!iter_memcg) {
> >> + /*
> >> + * Continue without incrementing failures if we found
> >> + * candidate memcgs in the last subtree walk.
> >> + */
> >> + if (!attempts && ++failures == MAX_RECLAIM_RETRIES)
> >> + return -EAGAIN;
> >> + attempts = 0;
> >> + continue;
> >> + }
> >> +
> >> + batch_size = min(nr_to_writeback - nr_written,
> >> + ZSWAP_PROACTIVE_WB_BATCH);
> >> + shrunk = zswap_proactive_shrink_memcg(iter_memcg, batch_size);
> >> + mem_cgroup_put(iter_memcg);
> >> +
> >> + /* Writeback-disabled or offline: skip without counting. */
> >> + if (shrunk == -ENOENT)
> >> + continue;
> >> +
> >> + ++attempts;
> >> + if (shrunk > 0)
> >> + nr_written += shrunk;
> >> + else if (++failures == MAX_RECLAIM_RETRIES)
> >> + return -EAGAIN;
> >> +
> >> + cond_resched();
> >> + }
> >> +
> >> + return 0;
> >> +}
> >> +
> >
> > There is a lot of copy+paste from shrink_worker() and shrink_memcg()
> > here. We really should be able to reuse shrink_memcg().
> >
>
> I will do some consolidation and code reuse in the next version.
>
> > Is the main difference that we are scanning in batches here? I think we
> > can have shrink_memcg() do that too. If anything, it might make the
> > shrinker more efficient. Over-reclaim is ofc a concern, and especially
> > in the zswap_store() path as the overhead can be noticeable. Maybe we
> > can parameterize the batch size based on the code path.
> >
> > Nhat, what do you think?
>
> Nhat, since we now have the referenced-based second chance algorithm,
> should we consider doing batch writeback for shrink_memcg() as well?
I just take a look at shrink_memcg() and realized it's reclaiming one
page at a time. Thanks for the reminder - I hated it.
Please batchify it if it makes your life easier :) We don't reclaim
"just one page/object" anywhere else in the reclaim path, Sure, it
adds a bit of latency to zswap_store() if we reached cgroup limit, but
IMHO if we hit zswap.max limit at zswap_store() time, that is already
the slowest of slow path that you should have avoided with proactive
reclaim/zswap shrinker in the first place. And, setting zswap.max does
not make sense to me in the first place (I can write a whole essay
about it).
^ permalink raw reply
* Re: [PATCH v7 00/13] liveupdate: Remove limits on sessions and files
From: Mike Rapoport @ 2026-06-03 18:16 UTC (permalink / raw)
To: linux-kselftest, shuah, akpm, linux-mm, skhan, linux-doc,
linux-kernel, corbet, dmatlack, kexec, pratyush, skhawaja, graf,
Pasha Tatashin
In-Reply-To: <20260603154402.468928-1-pasha.tatashin@soleen.com>
On Wed, 03 Jun 2026 15:43:49 +0000, Pasha Tatashin wrote:
> liveupdate: Remove limits on sessions and files
>
> Hi all,
>
> This series removes the fixed limits on the number of files that can
> be preserved within a single session, and the total number of sessions
> managed by the Live Update Orchestrator (LUO).
>
> [...]
Applied to next branch of liveupdate/linux.git tree, thanks!
[01/13] liveupdate: change file_set->count type to u64 for type safety
commit: 81fbb909ec07868415f6b2269922c8d1cc6a215a
[02/13] liveupdate: avoid mixing cleanup guards with goto in luo_session_retrieve_fd
commit: 6af06e11bd48bdefaf9381f6ff0bd65b1e5d98ab
[03/13] liveupdate: centralize state management into struct luo_ser
commit: d376e4b55c9a0adb3e701c7eaff21d9ba655a1c6
[04/13] liveupdate: register luo_ser as KHO subtree
commit: cf071b3536df76a2a75b83ca1fe8c043824352c3
[05/13] liveupdate: Extract luo_file_deserialize_one helper
commit: 51b71af922a7145e63fdc0cab075d681ecd89e4a
[06/13] liveupdate: Extract luo_session_deserialize_one helper
commit: be9d10d167652e11283cd07c7daf187222808db1
[07/13] kho: add support for linked-block serialization
commit: 0349ff2887059112ce06831ab29aec47a2a7285a
[08/13] liveupdate: defer session block allocation and physical address setting
commit: b5a58a922e6f2f9f40faddd8e0e1fe3ce0ea9c56
[09/13] liveupdate: Remove limit on the number of sessions
commit: 2a441a14c2c03b39d1c89438dd28cef9d8fa57d5
[10/13] liveupdate: Remove limit on the number of files per session
commit: 1d1153097f4dd417e2ea00404edec9fbd1d88f28
[11/13] selftests/liveupdate: Test session and file limit removal
commit: 5ba3f30643cbdd79fb82e525aa1ca55b62fcc7ac
[12/13] selftests/liveupdate: Add stress-sessions kexec test
commit: 3432292fb9130191dca57953941f7ae3888d52d8
[13/13] selftests/liveupdate: Add stress-files kexec test
commit: 46429a15a6dfe522880d5085f1f6999357758872
tree: https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux
branch: next
--
Sincerely yours,
Mike.
^ permalink raw reply
* Re: [PATCH v2 08/18] perf test cs-etm: Replace memcpy test with raw dump stress test
From: Leo Yan @ 2026-06-03 18:16 UTC (permalink / raw)
To: James Clark
Cc: Suzuki K Poulose, Mike Leach, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Amir Ayupov, Jonathan Corbet,
Shuah Khan, Paschalis Mpeis, coresight, linux-perf-users,
linux-kernel, Arnaldo Carvalho de Melo, linux-doc
In-Reply-To: <882922c0-042a-4108-bc2f-cec443d0db9f@linaro.org>
On Wed, Jun 03, 2026 at 05:11:30PM +0100, James Clark wrote:
[...]
> > I am not sure how we can map 2MiB trace data to 50MiB+ raw dump. This
>
> Why not? Decoding it is roughly equal to decompressing it, and with that
> amount of trace the small differences in compressibility average out and you
> do get the same amount every time. I think if we got less than half the
> amount expected then it would be worth investigating.
TBH, I don't know how to calculate decompressing size. Seems to me this
is a heuristics value.
Maybe Mike could help to confirm if this works or not.
[...]
> That would work, but that wouldn't be as thorough. The first thing it prints
> is " CoreSight .* Trace data: size .* bytes". If it stops working half way
> through or prints nothing then the test will still pass.
>
> The reason I wanted to add a stress test is because all of the other tests
> have been reduced to just a few kb of trace so we have nothing that opens a
> file with a more reasonable amount of data.
>
> I suppose with your suggestion we'd still check the exit code, but that's
> about it.
Is it possible to locate the end of raw dump with a specific parttern? like:
Idx:36061; ID:1a; I_IGNORE : Ignore.
Idx:36062; ID:1a; I_IGNORE : Ignore.
Idx:36063; ID:1a; I_IGNORE : Ignore.
Idx:36064; ID:1a; I_IGNORE : Ignore.
Idx:36065; ID:1a; I_IGNORE : Ignore.
Idx:36066; ID:1a; I_IGNORE : Ignore.
Idx:36067; ID:1a; I_IGNORE : Ignore.
Idx:36068; ID:1a; I_IGNORE : Ignore.
Idx:36069; ID:1a; I_IGNORE : Ignore.
Idx:36070; ID:1a; I_IGNORE : Ignore.
Idx:36071; ID:1a; I_IGNORE : Ignore.
Idx:36072; ID:1a; I_IGNORE : Ignore.
Idx:36073; ID:1a; I_IGNORE : Ignore.
Idx:36074; ID:1a; I_IGNORE : Ignore.
Idx:36075; ID:1a; I_IGNORE : Ignore.
Idx:36076; ID:1a; I_IGNORE : Ignore.
Idx:36077; ID:1a; I_IGNORE : Ignore.
Idx:36078; ID:1a; I_IGNORE : Ignore.
Idx:36079; ID:1a; I_IGNORE : Ignore.
Idx:36080; ID:1a; I_IGNORE : Ignore.
Idx:36081; ID:1a; I_IGNORE : Ignore.
Idx:36082; ID:1a; I_IGNORE : Ignore.
Idx:36083; ID:1a; I_IGNORE : Ignore.
Idx:36084; ID:1a; I_IGNORE : Ignore.
Idx:36085; ID:1a; I_IGNORE : Ignore.
Idx:36086; ID:1a; I_IGNORE : Ignore.
Idx:36087; ID:1a; I_IGNORE : Ignore.
Idx:36088; ID:1a; I_IGNORE : Ignore.
Idx:36089; ID:1a; I_IGNORE : Ignore.
Idx:36090; ID:1a; I_IGNORE : Ignore.
Idx:36091; ID:1a; I_IGNORE : Ignore.
Idx:36092; ID:1a; I_IGNORE : Ignore.
Idx:36093; ID:1a; I_IGNORE : Ignore.
Idx:36094; ID:1a; I_IGNORE : Ignore.
Idx:36095; ID:1a; I_IGNORE : Ignore.
Thanks,
Leo
^ permalink raw reply
* Re: [PATCH v3 2/4] mm/zswap: Implement proactive writeback
From: Nhat Pham @ 2026-06-03 18:14 UTC (permalink / raw)
To: Yosry Ahmed
Cc: Hao Jia, Johannes Weiner, akpm, tj, shakeel.butt, mhocko, mkoutny,
chengming.zhou, muchun.song, roman.gushchin, cgroups, linux-mm,
linux-kernel, linux-doc, Hao Jia
In-Reply-To: <aiBqzOtEv5iAC_qC@google.com>
On Wed, Jun 3, 2026 at 10:58 AM Yosry Ahmed <yosry@kernel.org> wrote:
>
> On Wed, Jun 03, 2026 at 07:22:36PM +0800, Hao Jia wrote:
> >
> >
> > On 2026/5/30 09:40, Yosry Ahmed wrote:
> > > On Fri, May 29, 2026 at 12:58:09PM -0700, Nhat Pham wrote:
> > > > On Tue, May 26, 2026 at 4:46 AM Hao Jia <jiahao.kernel@gmail.com> wrote:
> > > > >
> > > > > From: Hao Jia <jiahao1@lixiang.com>
> > > > >
> > > > > Zswap currently writes back pages to backing swap reactively, triggered
> > > > > either by the shrinker or when the pool reaches its size limit. There is
> > > > > no mechanism to control the amount of writeback for a specific memory
> > > > > cgroup. However, users may want to proactively write back zswap pages,
> > > > > e.g., to free up memory for other applications or to prepare for
> > > > > memory-intensive workloads.
> > > > >
> > > > > Introduce a "zswap_writeback_only" key to the memory.reclaim cgroup
> > > > > interface. When specified, this key bypasses standard memory reclaim
> > > > > and exclusively performs proactive zswap writeback up to the requested
> > > > > budget. If omitted, the default reclaim behavior remains unchanged.
> > > > >
> > > > > Example usage:
> > > > > # Write back 100MB of pages from zswap to the backing swap
> > > > > echo "100M zswap_writeback_only" > memory.reclaim
> > > >
> > > > Hmmm, so this 100MB is the pre-compression size? i.e if this 100 MB
> > > > compresses to 25 MB, then you're only freeing 25 MB?
> > > >
> > > > I'm ok-ish with this, but can you document it?
> > >
> > > That's a good point. I think pre-compressed size doesn't make sense to
> > > be honest. We should care about how much memory we are actually trying
> > > to save by doing writeback here.
> > >
> > > The pre-compressed size is only useful in determining the blast radius,
> > > how many actual pages are going to have slower page faults now. But
> > > then, I don't think there's a reasonable way for userspace to decide
> > > that.
> > >
> > > I understand passing in the compressed size is tricky because we need to
> > > keep track of the size of the compressed pages we end up writing back,
> > > but it should be doable.
> >
> > Agreed. Using pre-compressed size is probably easier to implement. IIRC,
> > interfaces like ZRAM writeback_limit are also calculated using the
> > pre-compressed size.
> >
> > I'll clarify this in the documentation in the next version.
> >
> > >
> > > If we really want pre-compressed size here, then yes we need to make it
> > > very clear, and I vote that we use a separate interface in this case
> > > because memory.reclaim having different meanings for the amount of
> > > memory written to it is extremely counter-intuitive.
> > >
> > Agree. This would indeed break the semantics of memory.reclaim. I will use a
> > separate interface for proactive writeback in the next version.
>
> But doesn't it make more sense to specify the compressed size, which is
> ultimately the amount of memory you actually want to reclaim.
>
I personally prefer compressed size to pre-compressed size. That's
kinda what user cares about, no?
One thing we can do is let users prescribe a compressed size, but
internally, we can multiply that by the average compression ratio.
That gives us a guesstimate of how many pages we need to reclaim, and
you can follow the rest of your implementation as is (perhaps with
short-circuit when we reach the goal with fewer pages reclaimed).
^ permalink raw reply
* Re: [PATCH v2 18/18] perf test cs-etm: Move existing tests to coresight folder
From: Leo Yan @ 2026-06-03 18:02 UTC (permalink / raw)
To: James Clark
Cc: Suzuki K Poulose, Mike Leach, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Amir Ayupov, Jonathan Corbet,
Shuah Khan, Paschalis Mpeis, coresight, linux-perf-users,
linux-kernel, Arnaldo Carvalho de Melo, linux-doc
In-Reply-To: <20260602-james-cs-context-tracking-fix-v2-18-85b5ce6f55c6@linaro.org>
On Tue, Jun 02, 2026 at 03:27:00PM +0100, James Clark wrote:
> There is a subfolder for Coresight tests so might as well keep them all
> in here.
Now we only have two shell test for CoreSight (I might add one for
callchain test), seems to me it is more meaningful to move Arm specific
tests into a central place (like tools/perf/tests/shell/arm/) for
easier maintainence.
Anyway, this patch is fine as well:
Reviewed-by: Leo Yan <leo.yan@arm.com>
^ permalink raw reply
* Re: [PATCH v3 2/4] mm/zswap: Implement proactive writeback
From: Yosry Ahmed @ 2026-06-03 17:58 UTC (permalink / raw)
To: Hao Jia, Nhat Pham, Johannes Weiner
Cc: akpm, tj, shakeel.butt, mhocko, mkoutny, chengming.zhou,
muchun.song, roman.gushchin, cgroups, linux-mm, linux-kernel,
linux-doc, Hao Jia
In-Reply-To: <6deeaea7-3cd1-4403-29fc-d2dc55c297f8@gmail.com>
On Wed, Jun 03, 2026 at 07:22:36PM +0800, Hao Jia wrote:
>
>
> On 2026/5/30 09:40, Yosry Ahmed wrote:
> > On Fri, May 29, 2026 at 12:58:09PM -0700, Nhat Pham wrote:
> > > On Tue, May 26, 2026 at 4:46 AM Hao Jia <jiahao.kernel@gmail.com> wrote:
> > > >
> > > > From: Hao Jia <jiahao1@lixiang.com>
> > > >
> > > > Zswap currently writes back pages to backing swap reactively, triggered
> > > > either by the shrinker or when the pool reaches its size limit. There is
> > > > no mechanism to control the amount of writeback for a specific memory
> > > > cgroup. However, users may want to proactively write back zswap pages,
> > > > e.g., to free up memory for other applications or to prepare for
> > > > memory-intensive workloads.
> > > >
> > > > Introduce a "zswap_writeback_only" key to the memory.reclaim cgroup
> > > > interface. When specified, this key bypasses standard memory reclaim
> > > > and exclusively performs proactive zswap writeback up to the requested
> > > > budget. If omitted, the default reclaim behavior remains unchanged.
> > > >
> > > > Example usage:
> > > > # Write back 100MB of pages from zswap to the backing swap
> > > > echo "100M zswap_writeback_only" > memory.reclaim
> > >
> > > Hmmm, so this 100MB is the pre-compression size? i.e if this 100 MB
> > > compresses to 25 MB, then you're only freeing 25 MB?
> > >
> > > I'm ok-ish with this, but can you document it?
> >
> > That's a good point. I think pre-compressed size doesn't make sense to
> > be honest. We should care about how much memory we are actually trying
> > to save by doing writeback here.
> >
> > The pre-compressed size is only useful in determining the blast radius,
> > how many actual pages are going to have slower page faults now. But
> > then, I don't think there's a reasonable way for userspace to decide
> > that.
> >
> > I understand passing in the compressed size is tricky because we need to
> > keep track of the size of the compressed pages we end up writing back,
> > but it should be doable.
>
> Agreed. Using pre-compressed size is probably easier to implement. IIRC,
> interfaces like ZRAM writeback_limit are also calculated using the
> pre-compressed size.
>
> I'll clarify this in the documentation in the next version.
>
> >
> > If we really want pre-compressed size here, then yes we need to make it
> > very clear, and I vote that we use a separate interface in this case
> > because memory.reclaim having different meanings for the amount of
> > memory written to it is extremely counter-intuitive.
> >
> Agree. This would indeed break the semantics of memory.reclaim. I will use a
> separate interface for proactive writeback in the next version.
But doesn't it make more sense to specify the compressed size, which is
ultimately the amount of memory you actually want to reclaim.
Johannes, Nhat, WDYT?
^ permalink raw reply
* Re: [PATCH v3 2/4] mm/zswap: Implement proactive writeback
From: Yosry Ahmed @ 2026-06-03 17:55 UTC (permalink / raw)
To: Hao Jia
Cc: akpm, tj, hannes, shakeel.butt, mhocko, mkoutny, nphamcs,
chengming.zhou, muchun.song, roman.gushchin, cgroups, linux-mm,
linux-kernel, linux-doc, Hao Jia
In-Reply-To: <ea2c1323-1440-e927-f14a-0eac54a245bf@gmail.com>
> > Is the main difference that we are scanning in batches here? I think we
> > can have shrink_memcg() do that too. If anything, it might make the
> > shrinker more efficient. Over-reclaim is ofc a concern, and especially
> > in the zswap_store() path as the overhead can be noticeable. Maybe we
> > can parameterize the batch size based on the code path.
> >
> > Nhat, what do you think?
>
> Nhat, since we now have the referenced-based second chance algorithm, should
> we consider doing batch writeback for shrink_memcg() as well?
>
> Of course, we could pass a parameter to control whether batch writeback is
> needed, so as to preserve the original behavior of shrink_memcg().
Yeah probably best to parameterize the batch size and keep the current
behavior of the shrinker for this series. We can play with using batches
for the shrinker later.
>
> Thanks,
> Hao
^ permalink raw reply
* Re: [PATCH v3 1/4] mm/zswap: Make shrink_worker writeback cursor per-memcg
From: Yosry Ahmed @ 2026-06-03 17:53 UTC (permalink / raw)
To: Hao Jia
Cc: akpm, tj, hannes, shakeel.butt, mhocko, mkoutny, nphamcs,
chengming.zhou, muchun.song, roman.gushchin, cgroups, linux-mm,
linux-kernel, linux-doc, Hao Jia
In-Reply-To: <c7870fe2-3588-79db-cbfb-bd6a2b78f594@gmail.com>
On Wed, Jun 03, 2026 at 11:02:54AM +0800, Hao Jia wrote:
>
>
> On 2026/6/3 07:19, Yosry Ahmed wrote:
> > > > > > > Proactive writeback also wants a similar per-memcg cursor that is
> > > > > > > scoped to the specified memcg, so that repeated invocations against
> > > > > > > the same memcg make forward progress across its descendant memcgs
> > > > > > > instead of restarting from the first child memcg each time.
> > > > > >
> > > > > > Is this a problem in practice?
> > > > > >
> > > > > > Is the concern the overhead of scanning memcgs repeatedly, or lack of
> > > > > > fairness? I wonder if we should just do writeback in batches from all
> > > > > > memcgs, similar to how reclaim does it, then evaluate at the end if we
> > > > > > need to start over?
> > > > > >
> > > > >
> > > > > Not using a per-cgroup cursor will cause issues for "repeated small-budget
> > > > > calls" cases. For example, repeatedly triggering a 2MB writeback might
> > > > > result in only writing back pages from the first few child memcgs every
> > > > > time. In the worst-case scenario (where the writeback amount is less than
> > > > > WB_BATCH), it might only ever write back from the first child memcg.
> > > >
> > > > Right, so a fairness concern?
> > > >
> > > > I wonder if we should just reclaim a batch from each memcg, then check
> > > > if we reached the goal, otherwise start over. If the batch size is small
> > > > enough that should work?
> > >
> > > Even with a small batch size, for small writeback requests triggered by
> > > user-space (e.g., 2MB, which is batch size * N), it might still repeatedly
> > > write back from only the first N child memcgs.
> >
> > Yes, I understand, I am asking if this is a problem in practice. For
> > this to be a problem we'd need to trigger small writeback requests and
> > have many memcgs.
> >
> > > This could cause the user-space agent to prematurely give up on zswap
> > > writeback.
> >
> > Why? The kernel should not return before trying to writeback from all
> > memcgs. If we scan the first N child memcgs and did not writeback
> > enough, we should keep going, right?
> >
>
> Yes, this issue is not caused by the kernel, but rather by our user-space
> agent itself.
>
> For instance, suppose a parent memcg has two children, memcg1 and memcg2,
> each with 200MB of zswap (100MB inactive). Triggering proactive writeback on
> the parent memcg will exhaust memcg1's inactive zswap pages. After that,
> even though memcg2 still has plenty of inactive zswap pages, it will
> continue to write back memcg1's active zswap pages. Writing back active
> zswap pages causes the user-space agent to prematurely abort the writeback
> because it detects that certain memcg metrics have exceeded predefined
> thresholds.
This will only happen if the reclaim size is smaller than the batch
size, right? Otherwise the kernel should reclaim more or less equally
from both memcgs?
> Of course, real-world scenarios are much more complex, and this kind of case
> is extremely rare in our environment.
>
> That being said, your suggestion of using the global lock for the per-memcg
> cursors makes the writeback fairer and would resolve these corner cases.
Right, but I'd rather not do per-memcg cursors at all if we can avoid
it. Will using batches help make reclaim fair over all memcgs without a
cursor?
We can always add the cursor later if needed.
^ permalink raw reply
* Re: [PATCH v2 17/18] perf test cs-etm: Speed up disassembly test
From: Leo Yan @ 2026-06-03 17:50 UTC (permalink / raw)
To: James Clark
Cc: Suzuki K Poulose, Mike Leach, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Amir Ayupov, Jonathan Corbet,
Shuah Khan, Paschalis Mpeis, coresight, linux-perf-users,
linux-kernel, Arnaldo Carvalho de Melo, linux-doc
In-Reply-To: <20260602-james-cs-context-tracking-fix-v2-17-85b5ce6f55c6@linaro.org>
On Tue, Jun 02, 2026 at 03:26:59PM +0100, James Clark wrote:
> We can use exit snapshot to limit the amount of trace to decode here
> too. Also each call to objdump is quite expensive on kcore so limit it
> to 2 samples instead of 30. We only want to see if there is no data at
> all.
>
> Signed-off-by: James Clark <james.clark@linaro.org>
Reviewed-by: Leo Yan <leo.yan@arm.com>
^ permalink raw reply
* Re: [PATCH v2 16/18] perf test cs-etm: Add all branch instructions to test
From: Leo Yan @ 2026-06-03 17:49 UTC (permalink / raw)
To: James Clark
Cc: Suzuki K Poulose, Mike Leach, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Amir Ayupov, Jonathan Corbet,
Shuah Khan, Paschalis Mpeis, coresight, linux-perf-users,
linux-kernel, Arnaldo Carvalho de Melo, linux-doc
In-Reply-To: <20260602-james-cs-context-tracking-fix-v2-16-85b5ce6f55c6@linaro.org>
On Tue, Jun 02, 2026 at 03:26:58PM +0100, James Clark wrote:
> If we reduce the number of samples searched to speed up the test, then
> there will be less chance of hitting one of these branches. Extend the
> regex to cover all branches so the test will always pass.
>
> Signed-off-by: James Clark <james.clark@linaro.org>
Reviewed-by: Leo Yan <leo.yan@arm.com>
^ permalink raw reply
* [RFC PATCH v2 6/6] kcov: add recursion guard and documentation for kcov-dataflow
From: Yunseong Kim @ 2026-06-03 17:43 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Dmitry Vyukov,
Andrey Konovalov, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, Nicolas Schier,
Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Jonathan Corbet, Shuah Khan
Cc: Yunseong Kim, linux-kernel, kasan-dev, llvm, linux-kbuild,
rust-for-linux, workflows, linux-doc, Yunseong Kim
In-Reply-To: <20260603-kcov-dataflow-next-20260603-v2-0-fee0939de2c4@est.tech>
Add a per-task recursion guard to kcov_df_write() using the high bit of
kcov_dataflow_seq. This prevents infinite recursion when
CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL is enabled: functions called by the
callback itself (copy_from_kernel_nofault, xadd helpers) are also
instrumented and would re-enter kcov_df_write() without this guard.
The guard uses the sequence counter's bit 31 as a re-entrancy flag.
The low 24 bits (used for TLV record sequence numbers) are unaffected.
Also:
- Exclude kcov.o, extable.o, softirq.o from dataflow instrumentation
(same pattern as KCOV_INSTRUMENT exclusions)
- Add Documentation/dev-tools/kcov-dataflow.rst with:
- Prerequisites and Kconfig options
- Per-module instrumentation instructions
- Complete C example for data collection
- Ring buffer format specification
- Ioctl interface reference
- Fork interception example for child process tracing
- Rust module support via post-compilation pipeline
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
Documentation/dev-tools/kcov-dataflow.rst | 282 ++++++++++++++++++++++++++++++
kernel/Makefile | 3 +
kernel/kcov.c | 14 +-
3 files changed, 298 insertions(+), 1 deletion(-)
diff --git a/Documentation/dev-tools/kcov-dataflow.rst b/Documentation/dev-tools/kcov-dataflow.rst
new file mode 100644
index 000000000000..5941df9f29e6
--- /dev/null
+++ b/Documentation/dev-tools/kcov-dataflow.rst
@@ -0,0 +1,282 @@
+KCOV-Dataflow: function argument and return value extraction
+=============================================================
+
+KCOV-Dataflow captures function arguments and return values — including
+automatic struct field decomposition — at instrumented kernel function
+boundaries. It provides per-task, lock-free ring buffers accessible via
+``mmap()``, enabling data-flow-aware fuzzing and post-mortem contract
+verification.
+
+Unlike KCOV's ``trace-pc`` which reports *which* code executed,
+KCOV-Dataflow reports *what values* were passed and returned. This is
+a completely separate device from ``/sys/kernel/debug/kcov``.
+
+Prerequisites
+-------------
+
+KCOV-Dataflow requires Clang/LLVM with the ``dataflow-args`` and
+``dataflow-ret`` SanitizerCoverage extensions. Standard (unpatched)
+compilers will not expose these Kconfig options.
+
+To enable KCOV-Dataflow, configure the kernel with::
+
+ CONFIG_KCOV=y
+ CONFIG_KCOV_DATAFLOW_ARGS=y
+ CONFIG_KCOV_DATAFLOW_RET=y
+
+Optional: instrument the entire kernel (significant overhead)::
+
+ CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL=y
+
+Coverage data becomes accessible once debugfs is mounted::
+
+ mount -t debugfs none /sys/kernel/debug
+
+Per-module instrumentation
+--------------------------
+
+To instrument a specific module, add to its Makefile::
+
+ KCOV_DATAFLOW_my_module.o := y
+
+For example, to instrument the Android binder driver::
+
+ # drivers/android/Makefile
+ KCOV_DATAFLOW_binder.o := y
+ KCOV_DATAFLOW_binder_alloc.o := y
+
+For Rust modules, add to the crate's Makefile::
+
+ # drivers/android/binder/Makefile
+ KCOV_DATAFLOW := y
+
+To instrument an entire directory, set the variable without a filename::
+
+ # fs/Makefile
+ KCOV_DATAFLOW := y
+
+The build system automatically adds the required compiler flags
+(``-fsanitize-coverage=dataflow-args,dataflow-ret -g``).
+
+Data collection
+---------------
+
+The following program demonstrates how to collect function argument and
+return value data for a single syscall:
+
+.. code-block:: c
+
+ #include <stdio.h>
+ #include <stdint.h>
+ #include <stdlib.h>
+ #include <sys/types.h>
+ #include <sys/ioctl.h>
+ #include <sys/mman.h>
+ #include <unistd.h>
+ #include <fcntl.h>
+
+ #define KCOV_DF_INIT_TRACE _IOR('d', 1, unsigned long)
+ #define KCOV_DF_ENABLE _IO('d', 100)
+ #define KCOV_DF_DISABLE _IO('d', 101)
+ #define BUF_SIZE (64 << 10) /* 64K words = 512KB */
+
+ int main(void)
+ {
+ int fd;
+ uint64_t *buf, n, i;
+
+ fd = open("/sys/kernel/debug/kcov_dataflow", O_RDWR);
+ if (fd == -1)
+ perror("open"), exit(1);
+
+ /* Allocate buffer (size in u64 words). */
+ if (ioctl(fd, KCOV_DF_INIT_TRACE, BUF_SIZE))
+ perror("ioctl(INIT)"), exit(1);
+
+ /* Map the buffer into user space. */
+ buf = (uint64_t *)mmap(NULL, BUF_SIZE * sizeof(uint64_t),
+ PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (buf == MAP_FAILED)
+ perror("mmap"), exit(1);
+
+ /* Enable data-flow collection for this task. */
+ if (ioctl(fd, KCOV_DF_ENABLE, 0))
+ perror("ioctl(ENABLE)"), exit(1);
+
+ /* Reset counter. */
+ __atomic_store_n(&buf[0], 0, __ATOMIC_RELAXED);
+
+ /* === Trigger syscall(s) here === */
+ read(-1, NULL, 0);
+
+ /* Read how many words were written. */
+ n = __atomic_load_n(&buf[0], __ATOMIC_RELAXED);
+
+ /* Parse TLV records. */
+ i = 1;
+ while (i < n) {
+ uint64_t type_seq = buf[i];
+ uint64_t pc = buf[i + 1];
+ uint64_t meta = buf[i + 2];
+ uint32_t type = (type_seq >> 28) & 0xF;
+ uint32_t seq = type_seq & 0x00FFFFFF;
+ uint32_t arg_idx = (meta >> 56) & 0xFF;
+ uint32_t size = (meta >> 48) & 0xFF;
+
+ printf("[%s] seq=%u pc=0x%lx arg_idx=%u size=%u val=0x%lx\n",
+ type == 0xE ? "ENTRY" : "RET",
+ seq, pc, arg_idx, size, buf[i + 3]);
+ i += 4; /* minimum record size: 3 header + 1 value */
+ }
+
+ if (ioctl(fd, KCOV_DF_DISABLE, 0))
+ perror("ioctl(DISABLE)"), exit(1);
+
+ munmap(buf, BUF_SIZE * sizeof(uint64_t));
+ close(fd);
+ return 0;
+ }
+
+Ring buffer format
+------------------
+
+The buffer is an array of ``u64`` words::
+
+ buf[0]: atomic counter — total words written
+
+Each record occupies 3 + N words:
+
++--------+------------------+---------------------------------------------+
+| Offset | Field | Description |
++========+==================+=============================================+
+| 0 | type_and_seq | bits[31:28] = 0xE (entry) or 0xF (return), |
+| | | bits[23:0] = per-task sequence number |
++--------+------------------+---------------------------------------------+
+| 1 | pc | Instrumented function address |
++--------+------------------+---------------------------------------------+
+| 2 | meta | bits[63:56] = arg_idx (0 for return), |
+| | | bits[55:48] = size in bytes, |
+| | | bits[47:0] = raw pointer value |
++--------+------------------+---------------------------------------------+
+| 3..N | field_val[0..N] | Struct field values or single scalar |
++--------+------------------+---------------------------------------------+
+
+Magic values:
+
+- ``0xBADADD85``: field read failed (pointer was invalid/freed/poisoned)
+
+Safety
+------
+
+- Callbacks are ``notrace``, ``__no_sanitize_coverage``, ``noinline``
+ to prevent recursion.
+- All pointer reads use ``copy_from_kernel_nofault()`` — survives
+ freed, poisoned, or unmapped memory.
+- An ``in_task()`` guard rejects calls from hardirq/softirq/NMI context,
+ preventing reentrant buffer corruption.
+- No ``printk`` or allocation in the data path.
+- When not enabled for a task, overhead is a single boolean check.
+
+Ioctl interface
+---------------
+
++---------------------+----------------------------+---------------------------+
+| Command | Value | Description |
++=====================+============================+===========================+
+| KCOV_DF_INIT_TRACE | ``_IOR('d', 1, unsigned | Allocate buffer |
+| | long)`` | (size in u64 words) |
++---------------------+----------------------------+---------------------------+
+| KCOV_DF_ENABLE | ``_IO('d', 100)`` | Start collection for |
+| | | current task |
++---------------------+----------------------------+---------------------------+
+| KCOV_DF_DISABLE | ``_IO('d', 101)`` | Stop collection |
++---------------------+----------------------------+---------------------------+
+
+Compatibility
+-------------
+
+KCOV-Dataflow is completely independent from legacy KCOV:
+
+- Separate device: ``/sys/kernel/debug/kcov_dataflow``
+- Separate ioctl namespace (``'d'`` vs ``'c'``)
+- Separate per-task buffer
+- Both can be used simultaneously without interference
+- syzkaller and other KCOV users are unaffected
+
+Rust module support
+-------------------
+
+Rust kernel modules are supported via a post-compilation pipeline::
+
+ rustc --emit=llvm-ir -g module.rs
+ opt -passes=sancov-module \
+ -sanitizer-coverage-dataflow-args \
+ -sanitizer-coverage-dataflow-ret module.ll -S -o module_inst.ll
+ llc -filetype=obj module_inst.ll -o module.o
+
+This is the good method for capturing Rust function arguments at runtime.
+
+
+Tracing child processes (fork interception)
+-------------------------------------------
+
+KCOV-Dataflow is per-task: after ``fork()``, the child does not inherit
+the enabled state. To trace child processes, re-enable on the inherited
+file descriptor in the child before ``exec()``. The ``mmap``'d buffer is
+shared (``MAP_SHARED``), so both parent and child write to the same ring
+buffer atomically.
+
+.. code-block:: c
+
+ #include <stdio.h>
+ #include <stdint.h>
+ #include <stdlib.h>
+ #include <sys/ioctl.h>
+ #include <sys/mman.h>
+ #include <sys/wait.h>
+ #include <unistd.h>
+ #include <fcntl.h>
+
+ #define KCOV_DF_INIT_TRACE _IOR('d', 1, unsigned long)
+ #define KCOV_DF_ENABLE _IO('d', 100)
+ #define KCOV_DF_DISABLE _IO('d', 101)
+ #define BUF_SIZE (64 << 10)
+
+ int main(int argc, char **argv)
+ {
+ int fd = open("/sys/kernel/debug/kcov_dataflow", O_RDWR);
+ ioctl(fd, KCOV_DF_INIT_TRACE, BUF_SIZE);
+ uint64_t *buf = mmap(NULL, BUF_SIZE * 8,
+ PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+ /* Enable for parent task */
+ ioctl(fd, KCOV_DF_ENABLE, 0);
+ __atomic_store_n(&buf[0], 0, __ATOMIC_RELAXED);
+
+ pid_t pid = fork();
+ if (pid == 0) {
+ /* Child: re-enable on inherited fd.
+ * The shared mmap buffer receives records from both tasks.
+ */
+ ioctl(fd, KCOV_DF_ENABLE, 0);
+ execvp(argv[1], &argv[1]);
+ _exit(1);
+ }
+
+ waitpid(pid, NULL, 0);
+ ioctl(fd, KCOV_DF_DISABLE, 0);
+
+ uint64_t n = __atomic_load_n(&buf[0], __ATOMIC_RELAXED);
+ printf("Captured %lu words from parent + child\n", n);
+
+ munmap(buf, BUF_SIZE * 8);
+ close(fd);
+ return 0;
+ }
+
+Note: the child's ``ioctl(fd, KCOV_DF_ENABLE)`` will fail if the parent
+has not yet called ``KCOV_DF_DISABLE``, because only one task can be
+associated with a descriptor at a time. For true multi-process tracing,
+open a separate ``kcov_dataflow`` fd per child, or disable in the parent
+before the child enables (as shown above — the parent is blocked in
+``waitpid`` so it generates no records during that time anyway).
diff --git a/kernel/Makefile b/kernel/Makefile
index 1e1a31673577..9c56421c5390 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -37,6 +37,7 @@ KCOV_INSTRUMENT_extable.o := n
KCOV_INSTRUMENT_stacktrace.o := n
# Don't self-instrument.
KCOV_INSTRUMENT_kcov.o := n
+KCOV_DATAFLOW_kcov.o := n
# If sanitizers detect any issues in kcov, it may lead to recursion
# via printk, etc.
KASAN_SANITIZE_kcov.o := n
@@ -207,3 +208,5 @@ $(obj)/kheaders.md5: $(obj)/kheaders-srclist FORCE
$(call filechk,kheaders_md5sum)
clean-files := kheaders.md5 kheaders-srclist kheaders-objlist
+KCOV_DATAFLOW_extable.o := n
+KCOV_DATAFLOW_softirq.o := n
diff --git a/kernel/kcov.c b/kernel/kcov.c
index 373b8034ca5c..8d9d5e33549f 100644
--- a/kernel/kcov.c
+++ b/kernel/kcov.c
@@ -413,6 +413,16 @@ kcov_df_write(u64 type_marker, u64 pc, u64 meta, void *ptr,
if (!in_task())
return;
+ /*
+ * Prevent recursion: functions called by this callback
+ * (copy_from_kernel_nofault, xadd helpers) may be instrumented
+ * with INSTRUMENT_ALL. Use a per-task guard via the sequence
+ * counter's high bit.
+ */
+ if (t->kcov_dataflow_seq & (1U << 31))
+ return;
+ t->kcov_dataflow_seq |= (1U << 31);
+
area = (u64 *)t->kcov_df_area;
if (!area)
return;
@@ -449,7 +459,7 @@ kcov_df_write(u64 type_marker, u64 pc, u64 meta, void *ptr,
if (KCOV_DF_IS_ERR(ptr)) {
for (i = 0; i < num_fields; i++)
area[pos + 3 + i] = KCOV_DF_MAGIC_BAD;
- return;
+ goto out;
}
for (i = 0; i < num_fields; i++) {
u64 off, sz, val = KCOV_DF_MAGIC_BAD;
@@ -469,6 +479,8 @@ kcov_df_write(u64 type_marker, u64 pc, u64 meta, void *ptr,
area[pos + 3 + i] = val;
}
}
+out:
+ t->kcov_dataflow_seq &= ~(1U << 31);
}
#ifdef CONFIG_KCOV_DATAFLOW_ARGS
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v2 5/6] kcov: add interrupt context guard to kcov_df_write()
From: Yunseong Kim @ 2026-06-03 17:43 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Dmitry Vyukov,
Andrey Konovalov, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, Nicolas Schier,
Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Jonathan Corbet, Shuah Khan
Cc: Yunseong Kim, linux-kernel, kasan-dev, llvm, linux-kbuild,
rust-for-linux, workflows, linux-doc, Yunseong Kim
In-Reply-To: <20260603-kcov-dataflow-next-20260603-v2-0-fee0939de2c4@est.tech>
The KCOV-Dataflow write path (kcov_df_write) only checks
t->kcov_df_enabled before writing to the shared ring buffer. Unlike
the standard KCOV check_kcov_mode() which rejects interrupt context,
kcov_df_write() has no such protection. This means instrumented code
running in hardirq, softirq, or NMI context that interrupts a task
mid-write can re-enter kcov_df_write(), causing:
- Data corruption in the ring buffer (interleaved records)
- Out-of-order sequence counter increments
- Potential faults from nested pointer dereferences
Add an in_task() check to reject calls from non-task context, matching
the safety model of the standard KCOV tracing path.
Also suppress -Wmissing-prototypes in the eight_args_c test module
Makefile, as the exported test functions intentionally lack a shared
header.
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
kernel/kcov.c | 4 ++++
tools/kcov-dataflow/eight_args_c/Makefile | 1 +
2 files changed, 5 insertions(+)
diff --git a/kernel/kcov.c b/kernel/kcov.c
index d3c9c0efe961..373b8034ca5c 100644
--- a/kernel/kcov.c
+++ b/kernel/kcov.c
@@ -409,6 +409,10 @@ kcov_df_write(u64 type_marker, u64 pc, u64 meta, void *ptr,
if (!t->kcov_df_enabled)
return;
+ /* Reject calls from hardirq/softirq/NMI to prevent reentrant corruption. */
+ if (!in_task())
+ return;
+
area = (u64 *)t->kcov_df_area;
if (!area)
return;
diff --git a/tools/kcov-dataflow/eight_args_c/Makefile b/tools/kcov-dataflow/eight_args_c/Makefile
index de35bb541f07..038775b49435 100644
--- a/tools/kcov-dataflow/eight_args_c/Makefile
+++ b/tools/kcov-dataflow/eight_args_c/Makefile
@@ -1,2 +1,3 @@
obj-m := eight_args_mod.o
KCOV_DATAFLOW_eight_args_mod.o := y
+ccflags-y += -Wno-missing-prototypes
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v2 4/6] tools/kcov-dataflow: add userspace consumer and test modules
From: Yunseong Kim @ 2026-06-03 17:43 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Dmitry Vyukov,
Andrey Konovalov, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, Nicolas Schier,
Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Jonathan Corbet, Shuah Khan
Cc: Yunseong Kim, linux-kernel, kasan-dev, llvm, linux-kbuild,
rust-for-linux, workflows, linux-doc, Yunseong Kim
In-Reply-To: <20260603-kcov-dataflow-next-20260603-v2-0-fee0939de2c4@est.tech>
Add tools/kcov-dataflow/ with:
- trigger.c: userspace consumer that opens /sys/kernel/debug/kcov_dataflow,
mmaps the buffer, enables recording, triggers a kernel path, and dumps
the captured TLV records.
- kcov-view.py: visualization tool that parses and pretty-prints the
binary TLV buffer with struct field expansion and symbol resolution.
- eight_args_c/eight_args_mod.c: stress test with 1-8 argument functions
verifying correct capture of register and stack-passed arguments.
- eight_args_rust/eight_args_rust.rs: Rust equivalent of the 8-argument
stress test, verifying Rust module dataflow support.
- deep_module/deep_chain_mod.c: 10-level deep call chain demonstrating
taint propagation tracking across function boundaries.
Sample kcov-view.py output (C):
func2+0x0 [eight_args_mod](arg[0]=0x11, arg[1]=0x22)
ret = 0x33
func8+0x0 [eight_args_mod](arg[0]=0x11, .., arg[7]=0x88)
ret = 0x264
Sample kcov-view.py output (Rust):
rfunc2+0x0 [eight_args_rust](arg[0]=0x11, arg[1]=0x22)
ret = 0x33
rfunc8+0x0 [eight_args_rust](arg[0]=0x11, .., arg[7]=0x88)
ret = 0x264
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
tools/kcov-dataflow/.gitignore | 12 +
tools/kcov-dataflow/deep_module/Makefile | 2 +
tools/kcov-dataflow/deep_module/deep_chain_mod.c | 224 +++++++++++++++++
tools/kcov-dataflow/eight_args_c/Makefile | 2 +
tools/kcov-dataflow/eight_args_c/eight_args_mod.c | 95 +++++++
tools/kcov-dataflow/eight_args_rust/Makefile | 2 +
.../eight_args_rust/eight_args_rust.rs | 114 +++++++++
tools/kcov-dataflow/kcov-view.py | 272 +++++++++++++++++++++
tools/kcov-dataflow/trigger.c | 125 ++++++++++
9 files changed, 848 insertions(+)
diff --git a/tools/kcov-dataflow/.gitignore b/tools/kcov-dataflow/.gitignore
new file mode 100644
index 000000000000..1f35df8fbd07
--- /dev/null
+++ b/tools/kcov-dataflow/.gitignore
@@ -0,0 +1,12 @@
+# Built binaries
+test_mock
+test_mock_binary
+trigger
+*.o
+*.ko
+*.mod
+*.mod.c
+Module.symvers
+modules.order
+.module-common.o
+*.ll
diff --git a/tools/kcov-dataflow/deep_module/Makefile b/tools/kcov-dataflow/deep_module/Makefile
new file mode 100644
index 000000000000..6afed580dc9a
--- /dev/null
+++ b/tools/kcov-dataflow/deep_module/Makefile
@@ -0,0 +1,2 @@
+obj-m := deep_chain_mod.o
+KCOV_DATAFLOW_deep_chain_mod.o := y
diff --git a/tools/kcov-dataflow/deep_module/deep_chain_mod.c b/tools/kcov-dataflow/deep_module/deep_chain_mod.c
new file mode 100644
index 000000000000..786e23c5d213
--- /dev/null
+++ b/tools/kcov-dataflow/deep_module/deep_chain_mod.c
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * deep_chain_mod.c - Demonstrates kcov_dataflow tracing through 10 nested
+ * function calls. An attacker-controlled "offset" value propagates from
+ * the entry point through transformations until it causes an OOB write
+ * in the deepest function.
+ *
+ * Call chain:
+ * entry_handler → parse_request → validate_header → extract_payload →
+ * transform_data → apply_filter → compute_index → lookup_slot →
+ * write_slot → commit_write (BUG: OOB here)
+ */
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/delay.h>
+
+/* Simulated protocol structures */
+struct request_header {
+ u32 magic;
+ u32 version;
+ u32 payload_offset; /* ← attacker controls this */
+ u32 payload_size;
+};
+
+struct payload {
+ u64 session_id;
+ u32 transform_key;
+ u32 filter_mask;
+ u8 data[32];
+};
+
+struct slot_table {
+ u32 num_slots;
+ u64 slots[8]; /* only 8 slots! */
+};
+
+static struct proc_dir_entry *proc_deep;
+
+/* === 10 nested functions: deepest first === */
+
+/* Function 10 (DEEPEST): The vulnerable write */
+static noinline int commit_write(struct slot_table *table, u32 index, u64 value)
+{
+ /* BUG: no bounds check on index — if index >= 8, OOB write */
+ table->slots[index] = value;
+ return 0;
+}
+
+/* Function 9 */
+static noinline int write_slot(struct slot_table *table, u32 slot_idx,
+ u64 session_id)
+{
+ u64 combined = session_id ^ (u64)slot_idx;
+
+ return commit_write(table, slot_idx, combined);
+}
+
+/* Function 8 */
+static noinline u32 lookup_slot(struct slot_table *table, u32 computed_idx)
+{
+ /* Pass through — in real code this might do hash lookup */
+ u32 final_idx = computed_idx % 16; /* BUG: should be % 8 */
+
+ write_slot(table, final_idx, 0xDEADC0DE00000000ULL | final_idx);
+ return final_idx;
+}
+
+/* Function 7 */
+static noinline u32 compute_index(u32 transform_result, u32 filter_output)
+{
+ /* Combines two values into an index */
+ return (transform_result + filter_output) & 0xF; /* 0-15, but table has 8 */
+}
+
+/* Function 6 */
+static noinline u32 apply_filter(struct payload *pl, u32 transformed_val)
+{
+ u32 filtered = transformed_val & pl->filter_mask;
+
+ return filtered >> 1;
+}
+
+/* Function 5 */
+static noinline u32 transform_data(struct payload *pl, u32 raw_offset)
+{
+ /* Transforms the offset using the payload's key */
+ return raw_offset * pl->transform_key;
+}
+
+/* Function 4 */
+static noinline struct payload *extract_payload(void *buf, u32 offset, u32 size)
+{
+ /* In real code: validates and extracts payload from buffer */
+ return (struct payload *)((u8 *)buf + offset);
+}
+
+/* Function 3 */
+static noinline int validate_header(struct request_header *hdr)
+{
+ if (hdr->magic != 0x50524F54) /* "PROT" */
+ return -1;
+ if (hdr->version > 2)
+ return -1;
+ /* BUG: doesn't validate payload_offset bounds! */
+ return 0;
+}
+
+/* Function 2 */
+static noinline int parse_request(void *buf, u32 buf_size,
+ struct request_header **out_hdr,
+ struct payload **out_payload)
+{
+ struct request_header *hdr = (struct request_header *)buf;
+
+ if (validate_header(hdr) < 0)
+ return -1;
+
+ *out_hdr = hdr;
+ *out_payload = extract_payload(buf, hdr->payload_offset, hdr->payload_size);
+ return 0;
+}
+
+/* Function 1 (ENTRY): The syscall handler */
+static noinline int entry_handler(void *user_buf, u32 user_size)
+{
+ struct request_header *hdr;
+ struct payload *pl;
+ struct slot_table *table;
+ u32 transformed, filtered, index, slot;
+
+ if (parse_request(user_buf, user_size, &hdr, &pl) < 0)
+ return -1;
+
+ table = kzalloc(sizeof(*table), GFP_KERNEL);
+ if (!table)
+ return -ENOMEM;
+ table->num_slots = 8;
+
+ /* The tainted data flow:
+ * hdr->payload_offset → extract_payload → pl
+ * pl->transform_key + payload_offset → transform_data → transformed
+ * transformed + pl->filter_mask → apply_filter → filtered
+ * transformed + filtered → compute_index → index
+ * index → lookup_slot → slot (% 16, should be % 8)
+ * slot → write_slot → commit_write (OOB if slot >= 8)
+ */
+ transformed = transform_data(pl, hdr->payload_offset);
+ filtered = apply_filter(pl, transformed);
+ index = compute_index(transformed, filtered);
+ slot = lookup_slot(table, index);
+
+ pr_info("deep_chain: slot=%u (OOB if >= 8)\n", slot);
+
+ kfree(table);
+ return 0;
+}
+
+/* Trigger: constructs a malicious request that causes index=12 (OOB) */
+static ssize_t deep_trigger_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ u8 *buf;
+ struct request_header *hdr;
+ struct payload *pl;
+
+ buf = kzalloc(256, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /* Craft malicious request */
+ hdr = (struct request_header *)buf;
+ hdr->magic = 0x50524F54; /* valid magic */
+ hdr->version = 1; /* valid version */
+ hdr->payload_offset = 16; /* offset to payload (valid position) */
+ hdr->payload_size = sizeof(struct payload);
+
+ /* Craft payload that will produce OOB index */
+ pl = (struct payload *)(buf + 16);
+ pl->session_id = 0xAAAABBBBCCCCDDDDULL;
+ pl->transform_key = 3; /* multiplier */
+ pl->filter_mask = 0xFFFFFFFF; /* no filtering */
+ memcpy(pl->data, "ATTACKER_PAYLOAD_DATA!!!", 24);
+
+ /*
+ * Trace: payload_offset=16, transform_key=3
+ * transformed = 16 * 3 = 48
+ * filtered = (48 & 0xFFFFFFFF) >> 1 = 24
+ * index = (48 + 24) & 0xF = 72 & 0xF = 8
+ * lookup_slot: final_idx = 8 % 16 = 8 ← OOB! (table has slots[0..7])
+ */
+
+ pr_info("deep_chain: triggering 10-deep call chain with offset=%u\n",
+ hdr->payload_offset);
+
+ entry_handler(buf, 256);
+
+ kfree(buf);
+ return count;
+}
+
+static const struct proc_ops deep_proc_ops = {
+ .proc_write = deep_trigger_write,
+};
+
+static int __init deep_chain_init(void)
+{
+ proc_deep = proc_create("deep_trigger", 0200, NULL, &deep_proc_ops);
+ if (!proc_deep)
+ return -ENOMEM;
+ pr_info("deep_chain_mod: loaded. echo x > /proc/deep_trigger\n");
+ return 0;
+}
+
+static void __exit deep_chain_exit(void)
+{
+ proc_remove(proc_deep);
+}
+
+module_init(deep_chain_init);
+module_exit(deep_chain_exit);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("10-deep call chain for kcov_dataflow visualization");
diff --git a/tools/kcov-dataflow/eight_args_c/Makefile b/tools/kcov-dataflow/eight_args_c/Makefile
new file mode 100644
index 000000000000..de35bb541f07
--- /dev/null
+++ b/tools/kcov-dataflow/eight_args_c/Makefile
@@ -0,0 +1,2 @@
+obj-m := eight_args_mod.o
+KCOV_DATAFLOW_eight_args_mod.o := y
diff --git a/tools/kcov-dataflow/eight_args_c/eight_args_mod.c b/tools/kcov-dataflow/eight_args_c/eight_args_mod.c
new file mode 100644
index 000000000000..660b27033756
--- /dev/null
+++ b/tools/kcov-dataflow/eight_args_c/eight_args_mod.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * eight_args_mod.c - Verify kcov_dataflow captures 1 through 8 argument functions.
+ */
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("KCOV dataflow 8-argument stress test module");
+
+noinline u64 func1(u64 a1)
+{
+ return a1;
+}
+EXPORT_SYMBOL(func1);
+
+noinline u64 func2(u64 a1, u64 a2)
+{
+ return a1 + a2;
+}
+EXPORT_SYMBOL(func2);
+
+noinline u64 func3(u64 a1, u64 a2, u64 a3)
+{
+ return a1 + a2 + a3;
+}
+EXPORT_SYMBOL(func3);
+
+noinline u64 func4(u64 a1, u64 a2, u64 a3, u64 a4)
+{
+ return a1 + a2 + a3 + a4;
+}
+EXPORT_SYMBOL(func4);
+
+noinline u64 func5(u64 a1, u64 a2, u64 a3, u64 a4, u64 a5)
+{
+ return a1 + a2 + a3 + a4 + a5;
+}
+EXPORT_SYMBOL(func5);
+
+noinline u64 func6(u64 a1, u64 a2, u64 a3, u64 a4, u64 a5, u64 a6)
+{
+ return a1 + a2 + a3 + a4 + a5 + a6;
+}
+EXPORT_SYMBOL(func6);
+
+noinline u64 func7(u64 a1, u64 a2, u64 a3, u64 a4, u64 a5, u64 a6,
+ u64 a7)
+{
+ return a1 + a2 + a3 + a4 + a5 + a6 + a7;
+}
+EXPORT_SYMBOL(func7);
+
+noinline u64 func8(u64 a1, u64 a2, u64 a3, u64 a4, u64 a5, u64 a6,
+ u64 a7, u64 a8)
+{
+ return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8;
+}
+EXPORT_SYMBOL(func8);
+
+static ssize_t trigger_write(struct file *f, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ pr_info("func1(0x11)=0x%llx\n", func1(0x11));
+ pr_info("func2(0x11,0x22)=0x%llx\n", func2(0x11, 0x22));
+ pr_info("func3(0x11,0x22,0x33)=0x%llx\n",
+ func3(0x11, 0x22, 0x33));
+ pr_info("func4(0x11,..,0x44)=0x%llx\n",
+ func4(0x11, 0x22, 0x33, 0x44));
+ pr_info("func5(0x11,..,0x55)=0x%llx\n",
+ func5(0x11, 0x22, 0x33, 0x44, 0x55));
+ pr_info("func6(0x11,..,0x66)=0x%llx\n",
+ func6(0x11, 0x22, 0x33, 0x44, 0x55, 0x66));
+ pr_info("func7(0x11,..,0x77)=0x%llx\n",
+ func7(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77));
+ pr_info("func8(0x11,..,0x88)=0x%llx\n",
+ func8(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88));
+ return count;
+}
+
+static const struct proc_ops ops = { .proc_write = trigger_write };
+
+static int __init init_mod(void)
+{
+ proc_create("test_args", 0200, NULL, &ops);
+ return 0;
+}
+
+static void __exit exit_mod(void)
+{
+ remove_proc_entry("test_args", NULL);
+}
+
+module_init(init_mod);
+module_exit(exit_mod);
diff --git a/tools/kcov-dataflow/eight_args_rust/Makefile b/tools/kcov-dataflow/eight_args_rust/Makefile
new file mode 100644
index 000000000000..8881d369e670
--- /dev/null
+++ b/tools/kcov-dataflow/eight_args_rust/Makefile
@@ -0,0 +1,2 @@
+obj-m := eight_args_rust.o
+KCOV_DATAFLOW_eight_args_rust.o := y
diff --git a/tools/kcov-dataflow/eight_args_rust/eight_args_rust.rs b/tools/kcov-dataflow/eight_args_rust/eight_args_rust.rs
new file mode 100644
index 000000000000..11bbe1449eaf
--- /dev/null
+++ b/tools/kcov-dataflow/eight_args_rust/eight_args_rust.rs
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0
+//! Verify kcov_dataflow captures 1-arg through 8-arg functions.
+//! Write to /sys/kernel/debug/test_args_rust to trigger all 8.
+#![allow(missing_docs)]
+
+use kernel::prelude::*;
+use kernel::c_str;
+
+module! {
+ type: ArgsModule,
+ name: "eight_args_rust",
+ authors: ["kcov-dataflow"],
+ description: "1-8 arg verification",
+ license: "GPL",
+}
+
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc1(a1: u64) -> u64 { a1 }
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc2(a1: u64, a2: u64) -> u64 { a1 + a2 }
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc3(a1: u64, a2: u64, a3: u64) -> u64 {
+ a1 + a2 + a3
+}
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc4(a1: u64, a2: u64, a3: u64, a4: u64) -> u64 {
+ a1 + a2 + a3 + a4
+}
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc5(
+ a1: u64, a2: u64, a3: u64, a4: u64, a5: u64,
+) -> u64 {
+ a1 + a2 + a3 + a4 + a5
+}
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc6(
+ a1: u64, a2: u64, a3: u64, a4: u64, a5: u64, a6: u64,
+) -> u64 {
+ a1 + a2 + a3 + a4 + a5 + a6
+}
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc7(
+ a1: u64, a2: u64, a3: u64, a4: u64, a5: u64, a6: u64, a7: u64,
+) -> u64 {
+ a1 + a2 + a3 + a4 + a5 + a6 + a7
+}
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc8(
+ a1: u64, a2: u64, a3: u64, a4: u64, a5: u64, a6: u64, a7: u64,
+ a8: u64,
+) -> u64 {
+ a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8
+}
+
+unsafe extern "C" fn write_handler(
+ _file: *mut kernel::bindings::file,
+ _buf: *const core::ffi::c_char,
+ count: usize,
+ _ppos: *mut kernel::bindings::loff_t,
+) -> kernel::ffi::c_long {
+ let r1 = rfunc1(0x11);
+ pr_info!("rfunc1: ret=0x{:x}\n", r1);
+ let r2 = rfunc2(0x11, 0x22);
+ pr_info!("rfunc2: ret=0x{:x}\n", r2);
+ let r3 = rfunc3(0x11, 0x22, 0x33);
+ pr_info!("rfunc3: ret=0x{:x}\n", r3);
+ let r4 = rfunc4(0x11, 0x22, 0x33, 0x44);
+ pr_info!("rfunc4: ret=0x{:x}\n", r4);
+ let r5 = rfunc5(0x11, 0x22, 0x33, 0x44, 0x55);
+ pr_info!("rfunc5: ret=0x{:x}\n", r5);
+ let r6 = rfunc6(0x11, 0x22, 0x33, 0x44, 0x55, 0x66);
+ pr_info!("rfunc6: ret=0x{:x}\n", r6);
+ let r7 = rfunc7(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77);
+ pr_info!("rfunc7: ret=0x{:x}\n", r7);
+ let r8 = rfunc8(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88);
+ pr_info!("rfunc8: ret=0x{:x}\n", r8);
+ count as kernel::ffi::c_long
+}
+
+#[repr(transparent)]
+struct SyncFops(kernel::bindings::file_operations);
+unsafe impl Sync for SyncFops {}
+
+static FOPS: SyncFops = SyncFops(kernel::bindings::file_operations {
+ write: Some(unsafe { core::mem::transmute(write_handler as *const ()) }),
+ ..unsafe { core::mem::zeroed() }
+});
+
+struct ArgsModule { d: *mut kernel::bindings::dentry }
+
+impl kernel::Module for ArgsModule {
+ fn init(_module: &'static ThisModule) -> Result<Self> {
+ let d = unsafe {
+ kernel::bindings::debugfs_create_file_unsafe(
+ c_str!("test_args_rust").as_char_ptr(),
+ 0o222, core::ptr::null_mut(), core::ptr::null_mut(), &FOPS.0,
+ )
+ };
+ Ok(Self { d })
+ }
+}
+impl Drop for ArgsModule {
+ fn drop(&mut self) { unsafe { kernel::bindings::debugfs_remove(self.d) }; }
+}
+unsafe impl Send for ArgsModule {}
+unsafe impl Sync for ArgsModule {}
diff --git a/tools/kcov-dataflow/kcov-view.py b/tools/kcov-dataflow/kcov-view.py
new file mode 100755
index 000000000000..70acb5474f5e
--- /dev/null
+++ b/tools/kcov-dataflow/kcov-view.py
@@ -0,0 +1,272 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+kcov-view.py - Merged KCOV + KCOV_DATAFLOW viewer
+
+Reads both /sys/kernel/debug/kcov (PC trace) and /sys/kernel/debug/kcov_dataflow
+(args/ret), correlates by PC, and produces a human-readable call trace with
+argument values and struct field expansion.
+
+Usage (inside guest or with appropriate permissions):
+ python3 kcov-view.py <trigger_command>
+
+Example:
+ python3 kcov-view.py "echo x > /proc/uaf_trigger"
+
+Output:
+ func+0x0 [module]
+ → a(arg[0]=0x1, arg[1]=0x2, arg[2]=0x3, arg[3]=struct{.f[0]=1, .f[1]=2, .f[2]=3})
+ ← ret = struct{.f[0]=1, .f[1]=2, .f[2]=3}
+ → a(arg[0]=0x0, arg[1]=0x0, arg[2]=0x1, arg[3]=NULL)
+ ← ret = 0x0
+"""
+import os, sys, struct, mmap, fcntl, subprocess, re, ctypes
+from collections import defaultdict
+
+# Ioctl definitions (x86_64)
+KCOV_INIT_TRACE = 0x80086301 # _IOR('c', 1, unsigned long)
+KCOV_ENABLE = 0x6364 # _IO('c', 100)
+KCOV_DISABLE = 0x6365 # _IO('c', 101)
+KCOV_TRACE_PC = 0
+
+KCOV_DF_INIT_TRACE = 0x80086401 # _IOR('d', 1, unsigned long)
+KCOV_DF_ENABLE = 0x6464 # _IO('d', 100)
+KCOV_DF_DISABLE = 0x6465 # _IO('d', 101)
+
+BUF_SIZE = 65536 # 65536 * 8 = 512KB = 128 pages (page-aligned)
+
+# Load kallsyms for symbolization
+def load_kallsyms():
+ syms = {}
+ try:
+ with open("/proc/kallsyms") as f:
+ for line in f:
+ parts = line.split()
+ if len(parts) >= 3:
+ addr = int(parts[0], 16)
+ name = parts[2]
+ mod = parts[3].strip("[]") if len(parts) > 3 else ""
+ syms[addr] = (name, mod)
+ except:
+ pass
+ return syms
+
+def symbolize(pc, syms):
+ """Find nearest symbol <= pc"""
+ best_addr = 0
+ best_name = f"0x{pc:x}"
+ best_mod = ""
+ for addr, (name, mod) in syms.items():
+ if addr <= pc and addr > best_addr:
+ best_addr = addr
+ best_name = name
+ best_mod = mod
+ offset = pc - best_addr
+ if best_mod:
+ return f"{best_name}+0x{offset:x} [{best_mod}]"
+ return f"{best_name}+0x{offset:x}"
+
+def parse_dataflow(buf, n):
+ """Parse TLV records from kcov_dataflow buffer into a list of events."""
+ events = []
+ i = 1
+ while i <= n and i < BUF_SIZE:
+ hdr = buf[i]
+ typ = hdr & 0xF0000000
+ seq = hdr & 0x00FFFFFF
+
+ if typ not in (0xE0000000, 0xF0000000):
+ i += 1
+ continue
+
+ pc = buf[i + 1]
+ meta = buf[i + 2]
+ i += 3
+
+ # Collect field values
+ fields = []
+ while i <= n and i < BUF_SIZE:
+ v = buf[i]
+ vtype = v & 0xF0000000
+ if vtype == 0xE0000000 or vtype == 0xF0000000:
+ break
+ fields.append(v)
+ i += 1
+
+ if typ == 0xE0000000:
+ arg_idx = (meta >> 56) & 0xFF
+ arg_sz = (meta >> 48) & 0xFF
+ ptr = meta & 0xFFFFFFFFFFFF
+ events.append({
+ "type": "entry", "seq": seq, "pc": pc,
+ "arg_idx": arg_idx, "arg_size": arg_sz,
+ "ptr": ptr, "fields": fields
+ })
+ else:
+ ret_sz = (meta >> 48) & 0xFF
+ ptr = meta & 0xFFFFFFFFFFFF
+ events.append({
+ "type": "ret", "seq": seq, "pc": pc,
+ "ret_size": ret_sz, "ptr": ptr, "fields": fields
+ })
+ return events
+
+def format_value(val):
+ if val == 0xBADADD85:
+ return "FAULT"
+ if val == 0:
+ return "0"
+ return f"0x{val:x}"
+
+def format_entry(ev):
+ """Format an entry event as a function argument."""
+ if len(ev["fields"]) > 1:
+ # Struct: multiple fields
+ flds = ", ".join(f".f[{i}]={format_value(v)}" for i, v in enumerate(ev["fields"]))
+ return f"struct{{{flds}}}"
+ elif len(ev["fields"]) == 1:
+ v = ev["fields"][0]
+ if v == 0 and ev["ptr"] == 0:
+ return "NULL"
+ return format_value(v)
+ return format_value(ev["ptr"])
+
+def merge_and_display(pc_trace, df_events, syms):
+ """Display dataflow events with symbolization."""
+ print("\n╔═══════════════════════════════════════════════════════════╗")
+ print("║ Merged KCOV Coverage + Dataflow View ║")
+ print("╚═══════════════════════════════════════════════════════════╝\n")
+
+ if not df_events:
+ print(" (no dataflow events captured)")
+ return
+
+ # Group events into calls: consecutive entries for same PC followed by a ret
+ calls = []
+ current_args = []
+ current_pc = None
+
+ for ev in df_events:
+ if ev["type"] == "entry":
+ if current_pc is not None and ev["pc"] != current_pc:
+ calls.append({"pc": current_pc, "args": current_args, "ret": None})
+ current_args = []
+ current_pc = ev["pc"]
+ current_args.append(ev)
+ elif ev["type"] == "ret":
+ if current_pc == ev["pc"]:
+ calls.append({"pc": current_pc, "args": current_args, "ret": ev})
+ current_args = []
+ current_pc = None
+ else:
+ if current_args:
+ calls.append({"pc": current_pc, "args": current_args, "ret": None})
+ current_args = []
+ calls.append({"pc": ev["pc"], "args": [], "ret": ev})
+ current_pc = None
+
+ if current_args:
+ calls.append({"pc": current_pc, "args": current_args, "ret": None})
+
+ for call in calls:
+ sym = symbolize(call["pc"], syms)
+ args_parts = []
+ for a in call["args"]:
+ idx = a["arg_idx"]
+ if len(a["fields"]) > 1:
+ flds = ", ".join(f".f[{i}]={format_value(v)}" for i, v in enumerate(a["fields"]))
+ args_parts.append(f"arg[{idx}]=struct{{{flds}}}")
+ elif len(a["fields"]) == 1:
+ args_parts.append(f"arg[{idx}]={format_value(a['fields'][0])}")
+ else:
+ args_parts.append(f"arg[{idx}]=?")
+
+ print(f" → {sym}({', '.join(args_parts)})")
+
+ if call["ret"]:
+ r = call["ret"]
+ if len(r["fields"]) > 1:
+ flds = ", ".join(f".f[{i}]={format_value(v)}" for i, v in enumerate(r["fields"]))
+ print(f" ← ret = struct{{{flds}}}")
+ elif len(r["fields"]) == 1:
+ print(f" ← ret = {format_value(r['fields'][0])}")
+ print()
+
+def main():
+ if len(sys.argv) < 2:
+ print(f"Usage: {sys.argv[0]} <trigger_command>")
+ print(f"Example: {sys.argv[0]} 'echo x > /proc/uaf_trigger'")
+ sys.exit(1)
+
+ trigger_cmd = sys.argv[1]
+ syms = load_kallsyms()
+
+ # Setup ctypes mmap
+ libc = ctypes.CDLL("libc.so.6", use_errno=True)
+ libc.mmap.restype = ctypes.c_void_p
+ libc.mmap.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_int,
+ ctypes.c_int, ctypes.c_int, ctypes.c_long]
+ PROT_RW = 0x3 # PROT_READ | PROT_WRITE
+ MAP_SHARED = 0x01
+
+ # Open both devices
+ kcov_fd = -1
+ df_fd = -1
+ kcov_arr = None
+ df_arr = None
+
+ # Legacy kcov (PC trace) - skip for now, use kallsyms for symbolization
+ kcov_arr = None
+
+ # Dataflow device - required
+ df_fd = os.open("/sys/kernel/debug/kcov_dataflow", os.O_RDWR)
+ fcntl.ioctl(df_fd, KCOV_DF_INIT_TRACE, BUF_SIZE)
+ df_ptr = libc.mmap(None, BUF_SIZE * 8, PROT_RW, MAP_SHARED, df_fd, 0)
+ if df_ptr == ctypes.c_void_p(-1).value:
+ print("Error: kcov_dataflow mmap failed")
+ sys.exit(1)
+ df_arr = (ctypes.c_uint64 * BUF_SIZE).from_address(df_ptr)
+
+ # Enable both
+ if kcov_arr:
+ fcntl.ioctl(kcov_fd, KCOV_ENABLE, KCOV_TRACE_PC)
+ kcov_arr[0] = 0
+
+ fcntl.ioctl(df_fd, KCOV_DF_ENABLE, 0)
+ df_arr[0] = 0
+
+ # Trigger - must happen in THIS process (kcov_dataflow is per-task)
+ if ">" in trigger_cmd:
+ target = trigger_cmd.split(">")[-1].strip()
+ else:
+ target = trigger_cmd
+ try:
+ fd_t = os.open(target, os.O_WRONLY)
+ os.write(fd_t, b"x")
+ os.close(fd_t)
+ except Exception as e:
+ print(f"Trigger failed: {e}")
+
+ # Read results
+ pc_trace = []
+ if kcov_arr:
+ n_pcs = kcov_arr[0]
+ for i in range(1, min(int(n_pcs) + 1, BUF_SIZE)):
+ pc_trace.append(kcov_arr[i])
+ fcntl.ioctl(kcov_fd, KCOV_DISABLE, 0)
+
+ n_df = int(df_arr[0])
+ df_raw = [int(df_arr[i]) for i in range(min(n_df + 10, BUF_SIZE))]
+ fcntl.ioctl(df_fd, KCOV_DF_DISABLE, 0)
+
+ # Parse and display
+ df_events = parse_dataflow(df_raw, int(n_df))
+ merge_and_display(pc_trace, df_events, syms)
+
+ # Cleanup
+ if kcov_arr:
+ os.close(kcov_fd)
+ os.close(df_fd)
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/kcov-dataflow/trigger.c b/tools/kcov-dataflow/trigger.c
new file mode 100644
index 000000000000..7fa7b4414770
--- /dev/null
+++ b/tools/kcov-dataflow/trigger.c
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * trigger.c - Uses /sys/kernel/debug/kcov_dataflow to capture
+ * function args/ret TLV records. Completely independent from legacy kcov.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+#define KCOV_DF_INIT_TRACE _IOR('d', 1, unsigned long)
+#define KCOV_DF_ENABLE _IO('d', 100)
+#define KCOV_DF_DISABLE _IO('d', 101)
+
+#define COVER_SIZE (64 * 1024) /* 64K u64 words = 512KB */
+
+static void dump_buffer(uint64_t *cover, uint64_t n)
+{
+ uint64_t i = 1;
+
+ printf("=== KCOV Dataflow TLV Dump (%lu words) ===\n", n);
+ while (i <= n && i < COVER_SIZE) {
+ uint64_t hdr = cover[i];
+ uint64_t type = hdr & 0xF0000000ULL;
+ uint64_t seq = hdr & 0x00FFFFFFULL;
+ uint64_t pc = cover[i + 1];
+ uint64_t meta = cover[i + 2];
+
+ if (type == 0xE0000000ULL) {
+ uint32_t arg_idx = (meta >> 56) & 0xFF;
+ uint32_t arg_sz = (meta >> 48) & 0xFF;
+ uint64_t ptr = meta & 0xFFFFFFFFFFFFULL;
+
+ printf("[ENTRY] seq=%lu pc=0x%lx arg[%u](%u) ptr=0x%lx\n",
+ seq, pc, arg_idx, arg_sz, ptr);
+ } else if (type == 0xF0000000ULL) {
+ uint32_t ret_sz = (meta >> 48) & 0xFF;
+ uint64_t ptr = meta & 0xFFFFFFFFFFFFULL;
+
+ printf("[RET] seq=%lu pc=0x%lx ret(%u) ptr=0x%lx\n",
+ seq, pc, ret_sz, ptr);
+ } else {
+ i++;
+ continue;
+ }
+
+ /* Print field values */
+ i += 3;
+ while (i <= n && i < COVER_SIZE) {
+ uint64_t next = cover[i];
+ uint64_t next_type = next & 0xF0000000ULL;
+
+ if (next_type == 0xE0000000ULL || next_type == 0xF0000000ULL)
+ break;
+ if (next == 0xBADADD85ULL)
+ printf(" val = FAULT\n");
+ else
+ printf(" val = 0x%lx\n", next);
+ i++;
+ }
+ }
+ printf("=== Done ===\n");
+}
+
+int main(int argc, char **argv)
+{
+ const char *trigger_path = "/proc/uaf_trigger";
+ int fd, tfd;
+ uint64_t *cover;
+ uint64_t n;
+
+ if (argc > 1)
+ trigger_path = argv[1];
+
+ fd = open("/sys/kernel/debug/kcov_dataflow", O_RDWR);
+ if (fd < 0) {
+ perror("open kcov_dataflow");
+ return 1;
+ }
+
+ if (ioctl(fd, KCOV_DF_INIT_TRACE, COVER_SIZE)) {
+ perror("KCOV_DF_INIT_TRACE");
+ close(fd);
+ return 1;
+ }
+
+ cover = mmap(NULL, COVER_SIZE * sizeof(uint64_t),
+ PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (cover == MAP_FAILED) {
+ perror("mmap");
+ close(fd);
+ return 1;
+ }
+
+ if (ioctl(fd, KCOV_DF_ENABLE, 0)) {
+ perror("KCOV_DF_ENABLE");
+ munmap(cover, COVER_SIZE * sizeof(uint64_t));
+ close(fd);
+ return 1;
+ }
+
+ /* Reset */
+ __atomic_store_n(&cover[0], 0, __ATOMIC_RELAXED);
+
+ /* Trigger */
+ tfd = open(trigger_path, O_WRONLY);
+ if (tfd >= 0) {
+ write(tfd, "x", 1);
+ close(tfd);
+ }
+
+ n = __atomic_load_n(&cover[0], __ATOMIC_RELAXED);
+
+ ioctl(fd, KCOV_DF_DISABLE, 0);
+
+ dump_buffer(cover, n);
+
+ munmap(cover, COVER_SIZE * sizeof(uint64_t));
+ close(fd);
+ return 0;
+}
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v2 3/6] kcov: add CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL and NO_INLINE
From: Yunseong Kim @ 2026-06-03 17:43 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Dmitry Vyukov,
Andrey Konovalov, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, Nicolas Schier,
Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Jonathan Corbet, Shuah Khan
Cc: Yunseong Kim, linux-kernel, kasan-dev, llvm, linux-kbuild,
rust-for-linux, workflows, linux-doc, Yunseong Kim
In-Reply-To: <20260603-kcov-dataflow-next-20260603-v2-0-fee0939de2c4@est.tech>
Add two Kconfig options for global dataflow instrumentation control:
- CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL: instruments all kernel objects
with dataflow tracing by default (mirrors CONFIG_KCOV_INSTRUMENT_ALL).
Individual files can opt out with: KCOV_DATAFLOW_file.o := n
- CONFIG_KCOV_DATAFLOW_NO_INLINE: adds -fno-inline to instrumented files
for complete argument visibility (default y). Setting to n allows
global enablement without stack overflow or BUILD_BUG_ON failures.
Overhead with INSTRUMENT_ALL (NO_INLINE=n, KASAN baseline):
.text: +9.5%, .data: +44%, boot: +71%, syscall: +133%
Comparable to KASAN (+100-200%) and acceptable for fuzzing kernels.
rust/Makefile: opt out core.o from dataflow (same as KCOV_INSTRUMENT).
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
lib/Kconfig.debug | 23 ++++++++++++++++++++++-
rust/Makefile | 1 +
2 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index abd1a94589aa..3b952b6361a8 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2261,7 +2261,6 @@ config KCOV_SELFTEST
On test failure, causes the kernel to panic. Recommended to be
enabled, ensuring critical functionality works as intended.
-
config KCOV_DATAFLOW_ARGS
bool "Enable KCOV dataflow: function argument capture"
depends on KCOV
@@ -2283,6 +2282,28 @@ config KCOV_DATAFLOW_RET
metadata, recording individual field values at runtime.
Enable per-module with: KCOV_DATAFLOW_file.o := y in the Makefile.
Requires clang with -fsanitize-coverage=dataflow-ret support.
+
+config KCOV_DATAFLOW_INSTRUMENT_ALL
+ bool "Instrument all code with KCOV dataflow by default"
+ depends on KCOV_DATAFLOW_ARGS || KCOV_DATAFLOW_RET
+ help
+ If enabled, all kernel objects are compiled with dataflow
+ instrumentation (like CONFIG_KCOV_INSTRUMENT_ALL for basic KCOV).
+ Individual files can opt out with: KCOV_DATAFLOW_file.o := n
+ Increases compile time and binary size significantly.
+ Suitable for fuzzing and security auditing kernels.
+
+config KCOV_DATAFLOW_NO_INLINE
+ bool "Disable inlining for dataflow-instrumented files"
+ depends on KCOV_DATAFLOW_ARGS || KCOV_DATAFLOW_RET
+ default y
+ help
+ Adds -fno-inline to dataflow-instrumented files for complete
+ argument visibility. Without this, inlined functions will not
+ have their arguments captured individually.
+ Disabling allows global enablement with lower overhead at the
+ cost of missing inlined function traces.
+
config DEBUG_AID_FOR_SYZBOT
bool "Additional debug code for syzbot"
default n
diff --git a/rust/Makefile b/rust/Makefile
index b9e9f512cec3..d122a65226dc 100644
--- a/rust/Makefile
+++ b/rust/Makefile
@@ -656,6 +656,7 @@ ifneq ($(or $(CONFIG_X86_64),$(CONFIG_X86_32)),)
$(obj)/core.o: scripts/target.json
endif
KCOV_INSTRUMENT_core.o := n
+KCOV_DATAFLOW_core.o := n
$(obj)/compiler_builtins.o: private skip_gendwarfksyms = 1
$(obj)/compiler_builtins.o: private rustc_objcopy = -w -W '__*'
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v2 2/6] kcov: add build system support for dataflow instrumentation
From: Yunseong Kim @ 2026-06-03 17:43 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Dmitry Vyukov,
Andrey Konovalov, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, Nicolas Schier,
Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Jonathan Corbet, Shuah Khan
Cc: Yunseong Kim, linux-kernel, kasan-dev, llvm, linux-kbuild,
rust-for-linux, workflows, linux-doc, Yunseong Kim
In-Reply-To: <20260603-kcov-dataflow-next-20260603-v2-0-fee0939de2c4@est.tech>
Add CFLAGS_KCOV_DATAFLOW and RUSTFLAGS_KCOV_DATAFLOW exports to
scripts/Makefile.kcov, containing:
-fsanitize-coverage=dataflow-args,dataflow-ret -g
(with optional -fno-inline via CONFIG_KCOV_DATAFLOW_NO_INLINE)
scripts/Makefile.lib applies these flags when a module's Makefile sets:
KCOV_DATAFLOW_file.o := y (per-file)
KCOV_DATAFLOW := y (per-directory)
Also supports CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL for global enablement.
The flags are only applied to kernel objects (same guard as basic KCOV).
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
scripts/Makefile.kcov | 6 ++++++
scripts/Makefile.lib | 7 +++++++
2 files changed, 13 insertions(+)
diff --git a/scripts/Makefile.kcov b/scripts/Makefile.kcov
index 78305a84ba9d..101173fe194b 100644
--- a/scripts/Makefile.kcov
+++ b/scripts/Makefile.kcov
@@ -2,10 +2,16 @@
kcov-flags-y += -fsanitize-coverage=trace-pc
kcov-flags-$(CONFIG_KCOV_ENABLE_COMPARISONS) += -fsanitize-coverage=trace-cmp
+# KCOV dataflow: trace function args and return values
+kcov-dataflow-flags-y := -fsanitize-coverage=dataflow-args,dataflow-ret -g
+kcov-dataflow-flags-$(CONFIG_KCOV_DATAFLOW_NO_INLINE) += -fno-inline
+
kcov-rflags-y += -Cpasses=sancov-module
kcov-rflags-y += -Cllvm-args=-sanitizer-coverage-level=3
kcov-rflags-y += -Cllvm-args=-sanitizer-coverage-trace-pc
kcov-rflags-$(CONFIG_KCOV_ENABLE_COMPARISONS) += -Cllvm-args=-sanitizer-coverage-trace-compares
export CFLAGS_KCOV := $(kcov-flags-y)
+export CFLAGS_KCOV_DATAFLOW := $(kcov-dataflow-flags-y)
+export RUSTFLAGS_KCOV_DATAFLOW := -Cpasses=sancov-module -Cllvm-args=-sanitizer-coverage-level=3 -Cllvm-args=-sanitizer-coverage-dataflow-args -Cllvm-args=-sanitizer-coverage-dataflow-ret -Cdebuginfo=2
export RUSTFLAGS_KCOV := $(kcov-rflags-y)
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index 80e127c75a93..519bf651cdcf 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -88,6 +88,13 @@ _c_flags += $(if $(patsubst n%,, \
_rust_flags += $(if $(patsubst n%,, \
$(KCOV_INSTRUMENT_$(target-stem).o)$(KCOV_INSTRUMENT)$(if $(is-kernel-object),$(CONFIG_KCOV_INSTRUMENT_ALL))), \
$(RUSTFLAGS_KCOV))
+# KCOV dataflow: per-file opt-in or global via CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL
+_c_flags += $(if $(patsubst n%,, \
+ $(KCOV_DATAFLOW_$(target-stem).o)$(KCOV_DATAFLOW)$(if $(is-kernel-object),$(CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL))), \
+ $(CFLAGS_KCOV_DATAFLOW))
+_rust_flags += $(if $(patsubst n%,, \
+ $(KCOV_DATAFLOW_$(target-stem).o)$(KCOV_DATAFLOW)$(if $(is-kernel-object),$(CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL))), \
+ $(RUSTFLAGS_KCOV_DATAFLOW))
endif
#
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox