* [PATCH bpf-next v3 0/3] Add wakeup_source iterators
@ 2026-01-24 1:21 Samuel Wu
2026-01-24 1:21 ` [PATCH bpf-next v3 1/3] PM: wakeup: Handle empty list in wakeup_sources_walk Samuel Wu
` (2 more replies)
0 siblings, 3 replies; 11+ messages in thread
From: Samuel Wu @ 2026-01-24 1:21 UTC (permalink / raw)
To: Rafael J. Wysocki, Len Brown, Pavel Machek, Greg Kroah-Hartman,
Danilo Krummrich, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman, Song Liu,
Yonghong Song, John Fastabend, KP Singh, Stanislav Fomichev,
Hao Luo, Jiri Olsa, Shuah Khan
Cc: Samuel Wu, kernel-team, linux-kernel, linux-pm, bpf, linux-kselftest
This patch series introduces BPF iterators for wakeup_source, enabling
BPF programs to efficiently traverse a device's wakeup sources.
Currently, inspecting wakeup sources typically involves reading interfaces
like /sys/class/wakeup/* or debugfs. The repeated syscalls to query the
sysfs nodes is inefficient, as there can be hundreds of wakeup_sources, and
each wakeup source have multiple stats, with one sysfs node per stat.
debugfs is unstable and insecure.
The iterators utilize pre-existing wakeup_sources_walk_* functions to
traverse over the SRCU that backs the list of wakeup_sources.
Changes in v3:
- Update wakeup_sources_walk_start() to handle an empty list per bpf-ci
- Simplify read of a struct's field in BPF program selftest per Andrii
- Drop open coded iterators for wakeup_sources
- Fix condition from !get_ws_iter_stream to get_ws_iter_stream in selftest
- Read event_count instead of wakeup_count in selftest
- v2 link: https://lore.kernel.org/all/20260108225523.3268383-1-wusamuel@google.com/
Changes in v2:
- Guard BPF Makefile with CONFIG_PM_SLEEP to fix build errors
- Update copyright from 2025 to 2026
- v1 link: https://lore.kernel.org/all/20251204025003.3162056-1-wusamuel@google.com/
Samuel Wu (3):
PM: wakeup: Handle empty list in wakeup_sources_walk
bpf: Add wakeup_source iterator
selftests/bpf: Add tests for wakeup_sources
drivers/base/power/wakeup.c | 4 +-
kernel/bpf/Makefile | 3 +
kernel/bpf/wakeup_source_iter.c | 103 +++++++
tools/testing/selftests/bpf/config | 1 +
.../bpf/prog_tests/wakeup_source_iter.c | 281 ++++++++++++++++++
.../selftests/bpf/progs/wakeup_source_iter.c | 60 ++++
6 files changed, 449 insertions(+), 3 deletions(-)
create mode 100644 kernel/bpf/wakeup_source_iter.c
create mode 100644 tools/testing/selftests/bpf/prog_tests/wakeup_source_iter.c
create mode 100644 tools/testing/selftests/bpf/progs/wakeup_source_iter.c
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH bpf-next v3 1/3] PM: wakeup: Handle empty list in wakeup_sources_walk 2026-01-24 1:21 [PATCH bpf-next v3 0/3] Add wakeup_source iterators Samuel Wu @ 2026-01-24 1:21 ` Samuel Wu 2026-01-27 16:15 ` Rafael J. Wysocki 2026-01-24 1:21 ` [PATCH bpf-next v3 2/3] bpf: Add wakeup_source iterator Samuel Wu 2026-01-24 1:21 ` [PATCH bpf-next v3 3/3] selftests/bpf: Add tests for wakeup_sources Samuel Wu 2 siblings, 1 reply; 11+ messages in thread From: Samuel Wu @ 2026-01-24 1:21 UTC (permalink / raw) To: Rafael J. Wysocki, Len Brown, Pavel Machek, Greg Kroah-Hartman, Danilo Krummrich, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song, John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa, Shuah Khan Cc: Samuel Wu, kernel-team, linux-kernel, linux-pm, bpf, linux-kselftest In the case of an empty wakeup_sources list, wakeup_sources_walk_start() will return an invalid but non-NULL address. This also affects wrappers of the aforementioned function, like for_each_wakeup_source(). This patch updates wakeup_sources_walk_start() to return NULL in case of an empty list. Signed-off-by: Samuel Wu <wusamuel@google.com> --- drivers/base/power/wakeup.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index 1e1a0e7eeac5..e69033d16fba 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -275,9 +275,7 @@ EXPORT_SYMBOL_GPL(wakeup_sources_read_unlock); */ struct wakeup_source *wakeup_sources_walk_start(void) { - struct list_head *ws_head = &wakeup_sources; - - return list_entry_rcu(ws_head->next, struct wakeup_source, entry); + return list_first_or_null_rcu(&wakeup_sources, struct wakeup_source, entry); } EXPORT_SYMBOL_GPL(wakeup_sources_walk_start); -- 2.52.0.457.g6b5491de43-goog ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v3 1/3] PM: wakeup: Handle empty list in wakeup_sources_walk 2026-01-24 1:21 ` [PATCH bpf-next v3 1/3] PM: wakeup: Handle empty list in wakeup_sources_walk Samuel Wu @ 2026-01-27 16:15 ` Rafael J. Wysocki 2026-02-02 20:23 ` Samuel Wu 0 siblings, 1 reply; 11+ messages in thread From: Rafael J. Wysocki @ 2026-01-27 16:15 UTC (permalink / raw) To: Samuel Wu Cc: Rafael J. Wysocki, Len Brown, Pavel Machek, Greg Kroah-Hartman, Danilo Krummrich, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song, John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa, Shuah Khan, kernel-team, linux-kernel, linux-pm, bpf, linux-kselftest On Sat, Jan 24, 2026 at 2:22 AM Samuel Wu <wusamuel@google.com> wrote: > > In the case of an empty wakeup_sources list, wakeup_sources_walk_start() > will return an invalid but non-NULL address. This also affects wrappers > of the aforementioned function, like for_each_wakeup_source(). > > This patch updates wakeup_sources_walk_start() to return NULL in case of > an empty list. > > Signed-off-by: Samuel Wu <wusamuel@google.com> > --- > drivers/base/power/wakeup.c | 4 +--- > 1 file changed, 1 insertion(+), 3 deletions(-) > > diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c > index 1e1a0e7eeac5..e69033d16fba 100644 > --- a/drivers/base/power/wakeup.c > +++ b/drivers/base/power/wakeup.c > @@ -275,9 +275,7 @@ EXPORT_SYMBOL_GPL(wakeup_sources_read_unlock); > */ > struct wakeup_source *wakeup_sources_walk_start(void) > { > - struct list_head *ws_head = &wakeup_sources; > - > - return list_entry_rcu(ws_head->next, struct wakeup_source, entry); > + return list_first_or_null_rcu(&wakeup_sources, struct wakeup_source, entry); > } > EXPORT_SYMBOL_GPL(wakeup_sources_walk_start); > > -- This looks like a fix for the for_each_wakeup_source() users. I can apply it without the next two patches. ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v3 1/3] PM: wakeup: Handle empty list in wakeup_sources_walk 2026-01-27 16:15 ` Rafael J. Wysocki @ 2026-02-02 20:23 ` Samuel Wu 2026-02-02 20:40 ` Rafael J. Wysocki 0 siblings, 1 reply; 11+ messages in thread From: Samuel Wu @ 2026-02-02 20:23 UTC (permalink / raw) To: Rafael J. Wysocki Cc: Len Brown, Pavel Machek, Greg Kroah-Hartman, Danilo Krummrich, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song, John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa, Shuah Khan, kernel-team, linux-kernel, linux-pm, bpf, linux-kselftest On Tue, Jan 27, 2026 at 8:15 AM Rafael J. Wysocki <rafael@kernel.org> wrote: > > On Sat, Jan 24, 2026 at 2:22 AM Samuel Wu <wusamuel@google.com> wrote: > > > > In the case of an empty wakeup_sources list, wakeup_sources_walk_start() > > will return an invalid but non-NULL address. This also affects wrappers > > of the aforementioned function, like for_each_wakeup_source(). > > > > This patch updates wakeup_sources_walk_start() to return NULL in case of > > an empty list. > > > > Signed-off-by: Samuel Wu <wusamuel@google.com> > > --- > > drivers/base/power/wakeup.c | 4 +--- > > 1 file changed, 1 insertion(+), 3 deletions(-) > > > > diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c > > index 1e1a0e7eeac5..e69033d16fba 100644 > > --- a/drivers/base/power/wakeup.c > > +++ b/drivers/base/power/wakeup.c > > @@ -275,9 +275,7 @@ EXPORT_SYMBOL_GPL(wakeup_sources_read_unlock); > > */ > > struct wakeup_source *wakeup_sources_walk_start(void) > > { > > - struct list_head *ws_head = &wakeup_sources; > > - > > - return list_entry_rcu(ws_head->next, struct wakeup_source, entry); > > + return list_first_or_null_rcu(&wakeup_sources, struct wakeup_source, entry); > > } > > EXPORT_SYMBOL_GPL(wakeup_sources_walk_start); > > > > -- > > This looks like a fix for the for_each_wakeup_source() users. > > I can apply it without the next two patches. Yes please. Feel free to apply this patch independently, as it can exist as a standalone fix independent of the subsequent two patches in the series. Thanks! ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v3 1/3] PM: wakeup: Handle empty list in wakeup_sources_walk 2026-02-02 20:23 ` Samuel Wu @ 2026-02-02 20:40 ` Rafael J. Wysocki 0 siblings, 0 replies; 11+ messages in thread From: Rafael J. Wysocki @ 2026-02-02 20:40 UTC (permalink / raw) To: Samuel Wu Cc: Rafael J. Wysocki, Len Brown, Pavel Machek, Greg Kroah-Hartman, Danilo Krummrich, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song, John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa, Shuah Khan, kernel-team, linux-kernel, linux-pm, bpf, linux-kselftest On Mon, Feb 2, 2026 at 9:23 PM Samuel Wu <wusamuel@google.com> wrote: > > On Tue, Jan 27, 2026 at 8:15 AM Rafael J. Wysocki <rafael@kernel.org> wrote: > > > > On Sat, Jan 24, 2026 at 2:22 AM Samuel Wu <wusamuel@google.com> wrote: > > > > > > In the case of an empty wakeup_sources list, wakeup_sources_walk_start() > > > will return an invalid but non-NULL address. This also affects wrappers > > > of the aforementioned function, like for_each_wakeup_source(). > > > > > > This patch updates wakeup_sources_walk_start() to return NULL in case of > > > an empty list. > > > > > > Signed-off-by: Samuel Wu <wusamuel@google.com> > > > --- > > > drivers/base/power/wakeup.c | 4 +--- > > > 1 file changed, 1 insertion(+), 3 deletions(-) > > > > > > diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c > > > index 1e1a0e7eeac5..e69033d16fba 100644 > > > --- a/drivers/base/power/wakeup.c > > > +++ b/drivers/base/power/wakeup.c > > > @@ -275,9 +275,7 @@ EXPORT_SYMBOL_GPL(wakeup_sources_read_unlock); > > > */ > > > struct wakeup_source *wakeup_sources_walk_start(void) > > > { > > > - struct list_head *ws_head = &wakeup_sources; > > > - > > > - return list_entry_rcu(ws_head->next, struct wakeup_source, entry); > > > + return list_first_or_null_rcu(&wakeup_sources, struct wakeup_source, entry); > > > } > > > EXPORT_SYMBOL_GPL(wakeup_sources_walk_start); > > > > > > -- > > > > This looks like a fix for the for_each_wakeup_source() users. > > > > I can apply it without the next two patches. > > Yes please. Feel free to apply this patch independently, as it can > exist as a standalone fix independent of the subsequent two patches in > the series. OK, applied as 6.20/7.0 material, thanks! ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH bpf-next v3 2/3] bpf: Add wakeup_source iterator 2026-01-24 1:21 [PATCH bpf-next v3 0/3] Add wakeup_source iterators Samuel Wu 2026-01-24 1:21 ` [PATCH bpf-next v3 1/3] PM: wakeup: Handle empty list in wakeup_sources_walk Samuel Wu @ 2026-01-24 1:21 ` Samuel Wu 2026-01-24 1:50 ` bot+bpf-ci 2026-01-24 1:21 ` [PATCH bpf-next v3 3/3] selftests/bpf: Add tests for wakeup_sources Samuel Wu 2 siblings, 1 reply; 11+ messages in thread From: Samuel Wu @ 2026-01-24 1:21 UTC (permalink / raw) To: Rafael J. Wysocki, Len Brown, Pavel Machek, Greg Kroah-Hartman, Danilo Krummrich, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song, John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa, Shuah Khan Cc: Samuel Wu, kernel-team, linux-kernel, linux-pm, bpf, linux-kselftest Add a BPF iterator for traversing through wakeup_sources. Setup iterators to traverse through a SRCUs of wakeup_sources. This is a more elegant and efficient traversal than going through the options today, such as at /sys/class/wakeup, or through debugfs. Signed-off-by: Samuel Wu <wusamuel@google.com> --- kernel/bpf/Makefile | 3 + kernel/bpf/wakeup_source_iter.c | 103 ++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 kernel/bpf/wakeup_source_iter.c diff --git a/kernel/bpf/Makefile b/kernel/bpf/Makefile index 79cf22860a99..1259373298e1 100644 --- a/kernel/bpf/Makefile +++ b/kernel/bpf/Makefile @@ -66,6 +66,9 @@ obj-$(CONFIG_BPF_SYSCALL) += kmem_cache_iter.o ifeq ($(CONFIG_DMA_SHARED_BUFFER),y) obj-$(CONFIG_BPF_SYSCALL) += dmabuf_iter.o endif +ifeq ($(CONFIG_PM_SLEEP),y) +obj-$(CONFIG_BPF_SYSCALL) += wakeup_source_iter.o +endif CFLAGS_REMOVE_percpu_freelist.o = $(CC_FLAGS_FTRACE) CFLAGS_REMOVE_bpf_lru_list.o = $(CC_FLAGS_FTRACE) diff --git a/kernel/bpf/wakeup_source_iter.c b/kernel/bpf/wakeup_source_iter.c new file mode 100644 index 000000000000..ab83d212a1f9 --- /dev/null +++ b/kernel/bpf/wakeup_source_iter.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2026 Google LLC */ +#include <linux/bpf.h> +#include <linux/btf_ids.h> +#include <linux/kernel.h> +#include <linux/pm_wakeup.h> +#include <linux/seq_file.h> + +struct bpf_iter__wakeup_source { + __bpf_md_ptr(struct bpf_iter_meta *, meta); + __bpf_md_ptr(struct wakeup_source *, wakeup_source); +}; + +static void *wakeup_source_iter_seq_start(struct seq_file *seq, loff_t *pos) +{ + int *srcuidx = seq->private; + struct wakeup_source *ws; + loff_t i; + + *srcuidx = wakeup_sources_read_lock(); + + ws = wakeup_sources_walk_start(); + for (i = 0; ws && i < *pos; i++) + ws = wakeup_sources_walk_next(ws); + + return ws; +} + +static void *wakeup_source_iter_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + struct wakeup_source *ws = v; + + ++*pos; + + return wakeup_sources_walk_next(ws); +} + +static void wakeup_source_iter_seq_stop(struct seq_file *seq, void *v) +{ + int *srcuidx = seq->private; + + if (*srcuidx >= 0) + wakeup_sources_read_unlock(*srcuidx); + *srcuidx = -1; +} + +static int __wakeup_source_seq_show(struct seq_file *seq, void *v, bool in_stop) +{ + struct bpf_iter_meta meta = { + .seq = seq, + }; + struct bpf_iter__wakeup_source ctx = { + .meta = &meta, + .wakeup_source = v, + }; + struct bpf_prog *prog = bpf_iter_get_info(&meta, in_stop); + + if (prog) + return bpf_iter_run_prog(prog, &ctx); + + return 0; +} + +static int wakeup_source_iter_seq_show(struct seq_file *seq, void *v) +{ + return __wakeup_source_seq_show(seq, v, false); +} + +static const struct seq_operations wakeup_source_iter_seq_ops = { + .start = wakeup_source_iter_seq_start, + .next = wakeup_source_iter_seq_next, + .stop = wakeup_source_iter_seq_stop, + .show = wakeup_source_iter_seq_show, +}; + +static const struct bpf_iter_seq_info wakeup_source_iter_seq_info = { + .seq_ops = &wakeup_source_iter_seq_ops, + .seq_priv_size = sizeof(int), +}; + +static struct bpf_iter_reg bpf_wakeup_source_reg_info = { + .target = "wakeup_source", + .ctx_arg_info_size = 1, + .ctx_arg_info = { + { + offsetof(struct bpf_iter__wakeup_source, wakeup_source), + PTR_TO_BTF_ID_OR_NULL + }, + }, + .seq_info = &wakeup_source_iter_seq_info, +}; + +DEFINE_BPF_ITER_FUNC(wakeup_source, struct bpf_iter_meta *meta, + struct wakeup_source *wakeup_source) +BTF_ID_LIST_SINGLE(bpf_wakeup_source_btf_id, struct, wakeup_source) + +static int __init wakeup_source_iter_init(void) +{ + bpf_wakeup_source_reg_info.ctx_arg_info[0].btf_id = bpf_wakeup_source_btf_id[0]; + return bpf_iter_reg_target(&bpf_wakeup_source_reg_info); +} + +late_initcall(wakeup_source_iter_init); -- 2.52.0.457.g6b5491de43-goog ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v3 2/3] bpf: Add wakeup_source iterator 2026-01-24 1:21 ` [PATCH bpf-next v3 2/3] bpf: Add wakeup_source iterator Samuel Wu @ 2026-01-24 1:50 ` bot+bpf-ci 2026-02-02 20:33 ` Samuel Wu 0 siblings, 1 reply; 11+ messages in thread From: bot+bpf-ci @ 2026-01-24 1:50 UTC (permalink / raw) To: wusamuel, rafael, lenb, pavel, gregkh, dakr, ast, daniel, andrii, martin.lau, eddyz87, song, yonghong.song, john.fastabend, kpsingh, sdf, haoluo, jolsa, shuah Cc: wusamuel, kernel-team, linux-kernel, linux-pm, bpf, linux-kselftest, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai [-- Attachment #1: Type: text/plain, Size: 1548 bytes --] > diff --git a/kernel/bpf/wakeup_source_iter.c b/kernel/bpf/wakeup_source_iter.c > new file mode 100644 > index 000000000000..ab83d212a1f9 > --- /dev/null > +++ b/kernel/bpf/wakeup_source_iter.c [ ... ] > +static void wakeup_source_iter_seq_stop(struct seq_file *seq, void *v) > +{ > + int *srcuidx = seq->private; > + > + if (*srcuidx >= 0) > + wakeup_sources_read_unlock(*srcuidx); > + *srcuidx = -1; > +} Should this function call __wakeup_source_seq_show(seq, v, true) when v is NULL to notify the BPF program that iteration has completed? Looking at other BPF iterators like map_iter, prog_iter, link_iter, task_iter, and kmem_cache_iter, they all invoke the BPF program with in_stop=true when the stop callback receives a NULL pointer. For example, map_iter does: static void bpf_map_seq_stop(struct seq_file *seq, void *v) { if (!v) (void)__bpf_map_seq_show(seq, v, true); ... } The bpf_seq_read() function in kernel/bpf/bpf_iter.c has this comment at the stop label: "/* bpf program called if !p */", which suggests this callback is expected. The __wakeup_source_seq_show() function already accepts an in_stop parameter but is never called with true. Without this callback, BPF programs using this iterator cannot detect when iteration has finished. --- AI reviewed your patch. Please fix the bug or email reply why it's not a bug. See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md CI run summary: https://github.com/kernel-patches/bpf/actions/runs/21306880863 ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v3 2/3] bpf: Add wakeup_source iterator 2026-01-24 1:50 ` bot+bpf-ci @ 2026-02-02 20:33 ` Samuel Wu 0 siblings, 0 replies; 11+ messages in thread From: Samuel Wu @ 2026-02-02 20:33 UTC (permalink / raw) To: bot+bpf-ci Cc: rafael, lenb, pavel, gregkh, dakr, ast, daniel, andrii, martin.lau, eddyz87, song, yonghong.song, john.fastabend, kpsingh, sdf, haoluo, jolsa, shuah, kernel-team, linux-kernel, linux-pm, bpf, linux-kselftest, martin.lau, clm, ihor.solodrai On Fri, Jan 23, 2026 at 5:50 PM <bot+bpf-ci@kernel.org> wrote: > > > diff --git a/kernel/bpf/wakeup_source_iter.c b/kernel/bpf/wakeup_source_iter.c > > new file mode 100644 > > index 000000000000..ab83d212a1f9 > > --- /dev/null > > +++ b/kernel/bpf/wakeup_source_iter.c > > [ ... ] > > > +static void wakeup_source_iter_seq_stop(struct seq_file *seq, void *v) > > +{ > > + int *srcuidx = seq->private; > > + > > + if (*srcuidx >= 0) > > + wakeup_sources_read_unlock(*srcuidx); > > + *srcuidx = -1; > > +} > > Should this function call __wakeup_source_seq_show(seq, v, true) when v is > NULL to notify the BPF program that iteration has completed? My understanding is that above code is invoked as a N+1 case after iterating through a list of size N. If desired behavior is to do additional cleanup not already covered by _seq_stop() (e.g. printing closing footers), then v == NULL should be handled. However, there is no such need for wakeup_source_iter at the moment. > > Looking at other BPF iterators like map_iter, prog_iter, link_iter, > task_iter, and kmem_cache_iter, they all invoke the BPF program with > in_stop=true when the stop callback receives a NULL pointer. For example, > map_iter does: > > static void bpf_map_seq_stop(struct seq_file *seq, void *v) > { > if (!v) > (void)__bpf_map_seq_show(seq, v, true); > ... > } > > The bpf_seq_read() function in kernel/bpf/bpf_iter.c has this comment at > the stop label: "/* bpf program called if !p */", which suggests this > callback is expected. > > The __wakeup_source_seq_show() function already accepts an in_stop > parameter but is never called with true. Without this callback, BPF > programs using this iterator cannot detect when iteration has finished. > If I understand correctly, this is not mandatory. For a straightforward iterator like this one, I'd like to keep it simple and not unnecessarily handle this case. > > --- > AI reviewed your patch. Please fix the bug or email reply why it's not a bug. > See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md > > CI run summary: https://github.com/kernel-patches/bpf/actions/runs/21306880863 ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH bpf-next v3 3/3] selftests/bpf: Add tests for wakeup_sources 2026-01-24 1:21 [PATCH bpf-next v3 0/3] Add wakeup_source iterators Samuel Wu 2026-01-24 1:21 ` [PATCH bpf-next v3 1/3] PM: wakeup: Handle empty list in wakeup_sources_walk Samuel Wu 2026-01-24 1:21 ` [PATCH bpf-next v3 2/3] bpf: Add wakeup_source iterator Samuel Wu @ 2026-01-24 1:21 ` Samuel Wu 2026-01-24 1:50 ` bot+bpf-ci 2 siblings, 1 reply; 11+ messages in thread From: Samuel Wu @ 2026-01-24 1:21 UTC (permalink / raw) To: Rafael J. Wysocki, Len Brown, Pavel Machek, Greg Kroah-Hartman, Danilo Krummrich, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song, John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa, Shuah Khan Cc: Samuel Wu, kernel-team, linux-kernel, linux-pm, bpf, linux-kselftest Sets up the framework to test wakeup_sources iterators using BPF, and adds a few basic tests. Adds several helper functions that for grabbing and releasing a wakelock, abstracting out key functions to setup a framework for testing wakeup_sources. Additionally, adds 3 tests: 1. check_active_count: Checks that stats related to active_count are properly set after several lock/unlock cycles 2. check_sleep_times: Checks that time accounting related to sleep are properly calculated 3. check_no_infinite_reads: Checks that the iterator traversal returns NULL at the end Signed-off-by: Samuel Wu <wusamuel@google.com> --- tools/testing/selftests/bpf/config | 1 + .../bpf/prog_tests/wakeup_source_iter.c | 281 ++++++++++++++++++ .../selftests/bpf/progs/wakeup_source_iter.c | 60 ++++ 3 files changed, 342 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/wakeup_source_iter.c create mode 100644 tools/testing/selftests/bpf/progs/wakeup_source_iter.c diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config index 558839e3c185..c12c5e04b81f 100644 --- a/tools/testing/selftests/bpf/config +++ b/tools/testing/selftests/bpf/config @@ -111,6 +111,7 @@ CONFIG_IP6_NF_IPTABLES=y CONFIG_IP6_NF_FILTER=y CONFIG_NF_NAT=y CONFIG_PACKET=y +CONFIG_PM_WAKELOCKS=y CONFIG_RC_CORE=y CONFIG_SAMPLES=y CONFIG_SAMPLE_LIVEPATCH=m diff --git a/tools/testing/selftests/bpf/prog_tests/wakeup_source_iter.c b/tools/testing/selftests/bpf/prog_tests/wakeup_source_iter.c new file mode 100644 index 000000000000..c8a38717e284 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/wakeup_source_iter.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Google LLC */ + +#include <test_progs.h> +#include <bpf/libbpf.h> +#include "wakeup_source_iter.skel.h" + +#include <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + + +/* Sleep for 10ms to ensure active time is > 0 after converting ns to ms*/ +#define TEST_SLEEP_US 10000 +#define TEST_SLEEP_MS (TEST_SLEEP_US / 1000) +#define WAKEUP_SOURCE_NAME_LEN 32 + +static const char test_ws_name[] = "bpf_selftest_ws"; +static bool test_ws_created; + +/* + * Creates a new wakeup source by writing to /sys/power/wake_lock. + * This lock persists until explicitly unlocked. + */ +static int lock_ws(const char *name) +{ + int fd; + ssize_t bytes; + + fd = open("/sys/power/wake_lock", O_WRONLY); + if (!ASSERT_OK_FD(fd, "open /sys/power/wake_lock")) + return -1; + + bytes = write(fd, name, strlen(name)); + close(fd); + if (!ASSERT_EQ(bytes, strlen(name), "write to wake_lock")) + return -1; + + return 0; +} + +/* + * Destroys the ws by writing the same name to /sys/power/wake_unlock. + */ +static void unlock_ws(const char *name) +{ + int fd; + + fd = open("/sys/power/wake_unlock", O_WRONLY); + if (!ASSERT_OK_FD(fd, "open /sys/power/wake_unlock")) + goto cleanup; + + write(fd, name, strlen(name)); + +cleanup: + if (fd) + close(fd); +} + +/* + * Setups for testing ws iterators. Will run once prior to suite of tests. + */ +static int setup_test_ws(void) +{ + if (lock_ws(test_ws_name)) + return -1; + test_ws_created = true; + + return 0; +} + +/* + * Tears down and cleanups testing ws iterators. WIll run once after the suite + * of tests. + */ +static void teardown_test_ws(void) +{ + if (!test_ws_created) + return; + unlock_ws(test_ws_name); + test_ws_created = false; +} + +struct WakeupSourceInfo { + char name[WAKEUP_SOURCE_NAME_LEN]; + unsigned long active_count; + long active_time_ms; + unsigned long event_count; + unsigned long expire_count; + long last_change_ms; + long max_time_ms; + long prevent_sleep_time_ms; + long total_time_ms; + unsigned long wakeup_count; +}; + +/* + * Reads and parses one wakeup_source record from the iterator file. + * A record is a single space-delimited line. + * Returns true on success, false on EOF. Asserts internally on errors. + */ +static bool read_ws_info(FILE *iter_file, struct WakeupSourceInfo *ws_info, + char **line) +{ + size_t linesize; + int items; + + if (getline(line, &linesize, iter_file) == -1) + return false; + + (*line)[strcspn(*line, "\n")] = 0; + + items = sscanf(*line, "%s %lu %ld %lu %lu %ld %ld %ld %ld %lu", + ws_info->name, &ws_info->active_count, + &ws_info->active_time_ms, &ws_info->event_count, + &ws_info->expire_count, &ws_info->last_change_ms, + &ws_info->max_time_ms, &ws_info->prevent_sleep_time_ms, + &ws_info->total_time_ms, &ws_info->wakeup_count); + + if (!ASSERT_EQ(items, 10, "read wakeup source info")) + return false; + + if (!ASSERT_LT(strlen(ws_info->name), WAKEUP_SOURCE_NAME_LEN, + "name length")) + return false; + + return true; +} + +static int get_ws_iter_stream(struct wakeup_source_iter *skel, int *iter_fd, + FILE **iter_file) +{ + *iter_fd = bpf_iter_create( + bpf_link__fd(skel->links.wakeup_source_collector)); + if (!ASSERT_OK_FD(*iter_fd, "iter_create")) + return -1; + + *iter_file = fdopen(*iter_fd, "r"); + if (!ASSERT_OK_PTR(*iter_file, "fdopen")) + return -1; + + return 0; +} + +static void subtest_ws_iter_check_active_count(struct wakeup_source_iter *skel) +{ + static const char subtest_ws_name[] = "bpf_selftest_ws_active_count"; + const int lock_unlock_cycles = 5; + struct WakeupSourceInfo ws_info; + char *line = NULL; + bool found_ws = false; + FILE *iter_file = NULL; + int iter_fd = -1; + int i; + + for (i = 0; i < lock_unlock_cycles; i++) { + if (!ASSERT_OK(lock_ws(subtest_ws_name), "lock_ws")) + goto cleanup; + unlock_ws(subtest_ws_name); + } + + if (get_ws_iter_stream(skel, &iter_fd, &iter_file)) + goto cleanup; + + while (read_ws_info(iter_file, &ws_info, &line)) { + if (strcmp(ws_info.name, subtest_ws_name) == 0) { + found_ws = true; + ASSERT_EQ(ws_info.active_count, lock_unlock_cycles, + "active_count check"); + ASSERT_EQ(ws_info.event_count, lock_unlock_cycles, + "event_count check"); + break; + } + } + + ASSERT_TRUE(found_ws, "found active_count test ws"); + + free(line); +cleanup: + if (iter_file) + fclose(iter_file); + else if (iter_fd >= 0) + close(iter_fd); +} + +static void subtest_ws_iter_check_sleep_times(struct wakeup_source_iter *skel) +{ + bool found_test_ws = false; + struct WakeupSourceInfo ws_info; + char *line = NULL; + FILE *iter_file; + int iter_fd; + + if (get_ws_iter_stream(skel, &iter_fd, &iter_file)) + goto cleanup; + + while (read_ws_info(iter_file, &ws_info, &line)) { + if (strcmp(ws_info.name, test_ws_name) == 0) { + found_test_ws = true; + ASSERT_GT(ws_info.last_change_ms, 0, + "Expected non-zero last change"); + ASSERT_GE(ws_info.active_time_ms, TEST_SLEEP_MS, + "Expected active time >= TEST_SLEEP_MS"); + ASSERT_GE(ws_info.max_time_ms, TEST_SLEEP_MS, + "Expected max time >= TEST_SLEEP_MS"); + ASSERT_GE(ws_info.total_time_ms, TEST_SLEEP_MS, + "Expected total time >= TEST_SLEEP_MS"); + break; + } + } + + ASSERT_TRUE(found_test_ws, "found_test_ws"); + + free(line); +cleanup: + if (iter_file) + fclose(iter_file); + else if (iter_fd >= 0) + close(iter_fd); +} + +static void subtest_ws_iter_check_no_infinite_reads( + struct wakeup_source_iter *skel) +{ + int iter_fd; + char buf[256]; + + iter_fd = bpf_iter_create(bpf_link__fd(skel->links.wakeup_source_collector)); + if (!ASSERT_OK_FD(iter_fd, "iter_create")) + return; + + while (read(iter_fd, buf, sizeof(buf)) > 0) + ; + + /* Final read should return 0 */ + ASSERT_EQ(read(iter_fd, buf, sizeof(buf)), 0, "read"); + + close(iter_fd); +} + +void test_wakeup_source_iter(void) +{ + struct wakeup_source_iter *skel = NULL; + + if (geteuid() != 0) { + fprintf(stderr, + "Skipping wakeup_source_iter test, requires root\n"); + test__skip(); + return; + } + + skel = wakeup_source_iter__open_and_load(); + if (!ASSERT_OK_PTR(skel, "wakeup_source_iter__open_and_load")) + return; + + if (!ASSERT_OK(setup_test_ws(), "setup_test_ws")) + goto destroy; + + if (!ASSERT_OK(wakeup_source_iter__attach(skel), "skel_attach")) + goto destroy; + + /* + * Sleep on O(ms) to ensure that time stats' resolution isn't lost when + * converting from ns to ms + */ + usleep(TEST_SLEEP_US); + + if (test__start_subtest("active_count")) + subtest_ws_iter_check_active_count(skel); + if (test__start_subtest("sleep_times")) + subtest_ws_iter_check_sleep_times(skel); + if (test__start_subtest("no_infinite_reads")) + subtest_ws_iter_check_no_infinite_reads(skel); + +destroy: + teardown_test_ws(); + wakeup_source_iter__destroy(skel); +} diff --git a/tools/testing/selftests/bpf/progs/wakeup_source_iter.c b/tools/testing/selftests/bpf/progs/wakeup_source_iter.c new file mode 100644 index 000000000000..eb19569e4424 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/wakeup_source_iter.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Google LLC */ +#include <vmlinux.h> +#include <bpf/bpf_core_read.h> +#include <bpf/bpf_helpers.h> + +#define NSEC_PER_MS 1000000UL +#define WAKEUP_SOURCE_NAME_LEN 32 + +char _license[] SEC("license") = "GPL"; + +SEC("iter/wakeup_source") +int wakeup_source_collector(struct bpf_iter__wakeup_source *ctx) +{ + const struct wakeup_source *ws = ctx->wakeup_source; + struct seq_file *seq = ctx->meta->seq; + char name[WAKEUP_SOURCE_NAME_LEN] = {'\0'}; + const char *pname; + bool active, autosleep_enable; + s64 active_time, curr_time, max_time, prevent_sleep_time, total_time; + + if (!ws) + return 0; + + active = BPF_CORE_READ_BITFIELD_PROBED(ws, active); + autosleep_enable = BPF_CORE_READ_BITFIELD_PROBED(ws, autosleep_enabled); + if (bpf_core_read(&pname, sizeof(pname), &ws->name) || + bpf_probe_read_kernel_str(name, sizeof(name), pname) < 0) + return 0; + + active_time = 0; + curr_time = bpf_ktime_get_ns(); + max_time = ws->max_time; + prevent_sleep_time = ws->prevent_sleep_time; + total_time = ws->total_time; + + if (active) { + active_time = curr_time - ws->last_time; + total_time += active_time; + if (active_time > max_time) + max_time = active_time; + if (autosleep_enable) + prevent_sleep_time += + curr_time - ws->start_prevent_time; + } + + BPF_SEQ_PRINTF(seq, + "%s %lu %ld %lu %lu %ld %ld %ld %ld %lu\n", + name, + ws->active_count, + active_time / NSEC_PER_MS, + ws->event_count, + ws->expire_count, + ws->last_time / NSEC_PER_MS, + max_time / NSEC_PER_MS, + prevent_sleep_time / NSEC_PER_MS, + total_time / NSEC_PER_MS, + ws->wakeup_count); + return 0; +} -- 2.52.0.457.g6b5491de43-goog ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v3 3/3] selftests/bpf: Add tests for wakeup_sources 2026-01-24 1:21 ` [PATCH bpf-next v3 3/3] selftests/bpf: Add tests for wakeup_sources Samuel Wu @ 2026-01-24 1:50 ` bot+bpf-ci 2026-02-02 20:27 ` Samuel Wu 0 siblings, 1 reply; 11+ messages in thread From: bot+bpf-ci @ 2026-01-24 1:50 UTC (permalink / raw) To: wusamuel, rafael, lenb, pavel, gregkh, dakr, ast, daniel, andrii, martin.lau, eddyz87, song, yonghong.song, john.fastabend, kpsingh, sdf, haoluo, jolsa, shuah Cc: wusamuel, kernel-team, linux-kernel, linux-pm, bpf, linux-kselftest, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai [-- Attachment #1: Type: text/plain, Size: 2071 bytes --] > diff --git a/tools/testing/selftests/bpf/prog_tests/wakeup_source_iter.c b/tools/testing/selftests/bpf/prog_tests/wakeup_source_iter.c > new file mode 100644 > index 000000000000..c8a38717e284 > --- /dev/null > +++ b/tools/testing/selftests/bpf/prog_tests/wakeup_source_iter.c [ ... ] > +static int get_ws_iter_stream(struct wakeup_source_iter *skel, int *iter_fd, > + FILE **iter_file) > +{ > + *iter_fd = bpf_iter_create( > + bpf_link__fd(skel->links.wakeup_source_collector)); > + if (!ASSERT_OK_FD(*iter_fd, "iter_create")) > + return -1; > + > + *iter_file = fdopen(*iter_fd, "r"); > + if (!ASSERT_OK_PTR(*iter_file, "fdopen")) > + return -1; > + > + return 0; > +} If bpf_iter_create() fails above, get_ws_iter_stream() returns -1 without writing to *iter_file. > +static void subtest_ws_iter_check_active_count(struct wakeup_source_iter *skel) > +{ [ ... ] > + char *line = NULL; > + bool found_ws = false; > + FILE *iter_file = NULL; > + int iter_fd = -1; This function correctly initializes iter_file and iter_fd before calling get_ws_iter_stream(). [ ... ] > +} > + > +static void subtest_ws_iter_check_sleep_times(struct wakeup_source_iter *skel) > +{ > + bool found_test_ws = false; > + struct WakeupSourceInfo ws_info; > + char *line = NULL; > + FILE *iter_file; > + int iter_fd; In contrast to subtest_ws_iter_check_active_count(), iter_file and iter_fd are not initialized here. > + > + if (get_ws_iter_stream(skel, &iter_fd, &iter_file)) > + goto cleanup; [ ... ] > +cleanup: > + if (iter_file) > + fclose(iter_file); > + else if (iter_fd >= 0) > + close(iter_fd); > +} If get_ws_iter_stream() fails at bpf_iter_create(), can this access uninitialized iter_file and iter_fd? The cleanup code would then evaluate garbage values, potentially calling fclose() with an invalid pointer. --- AI reviewed your patch. Please fix the bug or email reply why it's not a bug. See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md CI run summary: https://github.com/kernel-patches/bpf/actions/runs/21306880863 ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v3 3/3] selftests/bpf: Add tests for wakeup_sources 2026-01-24 1:50 ` bot+bpf-ci @ 2026-02-02 20:27 ` Samuel Wu 0 siblings, 0 replies; 11+ messages in thread From: Samuel Wu @ 2026-02-02 20:27 UTC (permalink / raw) To: bot+bpf-ci Cc: rafael, lenb, pavel, gregkh, dakr, ast, daniel, andrii, martin.lau, eddyz87, song, yonghong.song, john.fastabend, kpsingh, sdf, haoluo, jolsa, shuah, kernel-team, linux-kernel, linux-pm, bpf, linux-kselftest, martin.lau, clm, ihor.solodrai On Fri, Jan 23, 2026 at 5:50 PM <bot+bpf-ci@kernel.org> wrote: > > > diff --git a/tools/testing/selftests/bpf/prog_tests/wakeup_source_iter.c b/tools/testing/selftests/bpf/prog_tests/wakeup_source_iter.c > > new file mode 100644 > > index 000000000000..c8a38717e284 > > --- /dev/null > > +++ b/tools/testing/selftests/bpf/prog_tests/wakeup_source_iter.c > > [ ... ] > > > +static int get_ws_iter_stream(struct wakeup_source_iter *skel, int *iter_fd, > > + FILE **iter_file) > > +{ > > + *iter_fd = bpf_iter_create( > > + bpf_link__fd(skel->links.wakeup_source_collector)); > > + if (!ASSERT_OK_FD(*iter_fd, "iter_create")) > > + return -1; > > + > > + *iter_file = fdopen(*iter_fd, "r"); > > + if (!ASSERT_OK_PTR(*iter_file, "fdopen")) > > + return -1; > > + > > + return 0; > > +} > > If bpf_iter_create() fails above, get_ws_iter_stream() returns -1 without > writing to *iter_file. > This should be fine, if the BPF iter file isn't created, we will not write to that file. > > +static void subtest_ws_iter_check_active_count(struct wakeup_source_iter *skel) > > +{ > > [ ... ] > > > + char *line = NULL; > > + bool found_ws = false; > > + FILE *iter_file = NULL; > > + int iter_fd = -1; > > This function correctly initializes iter_file and iter_fd before calling > get_ws_iter_stream(). > > [ ... ] > > > +} > > + > > +static void subtest_ws_iter_check_sleep_times(struct wakeup_source_iter *skel) > > +{ > > + bool found_test_ws = false; > > + struct WakeupSourceInfo ws_info; > > + char *line = NULL; > > + FILE *iter_file; > > + int iter_fd; > > In contrast to subtest_ws_iter_check_active_count(), iter_file and iter_fd > are not initialized here. > Ack, this is important and I can address it in the v4 of the patch. > > + > > + if (get_ws_iter_stream(skel, &iter_fd, &iter_file)) > > + goto cleanup; > > [ ... ] > > > +cleanup: > > + if (iter_file) > > + fclose(iter_file); > > + else if (iter_fd >= 0) > > + close(iter_fd); > > +} > > If get_ws_iter_stream() fails at bpf_iter_create(), can this access > uninitialized iter_file and iter_fd? The cleanup code would then evaluate > garbage values, potentially calling fclose() with an invalid pointer. > Correct, and this issue will be fixed when the variables are initialized as pointed out in the previous comment. > > --- > AI reviewed your patch. Please fix the bug or email reply why it's not a bug. > See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md > > CI run summary: https://github.com/kernel-patches/bpf/actions/runs/21306880863 ^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-02-02 20:40 UTC | newest] Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-01-24 1:21 [PATCH bpf-next v3 0/3] Add wakeup_source iterators Samuel Wu 2026-01-24 1:21 ` [PATCH bpf-next v3 1/3] PM: wakeup: Handle empty list in wakeup_sources_walk Samuel Wu 2026-01-27 16:15 ` Rafael J. Wysocki 2026-02-02 20:23 ` Samuel Wu 2026-02-02 20:40 ` Rafael J. Wysocki 2026-01-24 1:21 ` [PATCH bpf-next v3 2/3] bpf: Add wakeup_source iterator Samuel Wu 2026-01-24 1:50 ` bot+bpf-ci 2026-02-02 20:33 ` Samuel Wu 2026-01-24 1:21 ` [PATCH bpf-next v3 3/3] selftests/bpf: Add tests for wakeup_sources Samuel Wu 2026-01-24 1:50 ` bot+bpf-ci 2026-02-02 20:27 ` Samuel Wu
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®