* [PATCH bpf-next v2 0/4] Add wakeup_source iterators
@ 2026-01-08 22:55 Samuel Wu
2026-01-08 22:55 ` [PATCH bpf-next v2 1/4] bpf: Add wakeup_source iterator Samuel Wu
` (4 more replies)
0 siblings, 5 replies; 11+ messages in thread
From: Samuel Wu @ 2026-01-08 22:55 UTC (permalink / raw)
To: 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, 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.
This series implements two types of iterators:
1. Standard BPF Iterator: Allows creating a BPF link to iterate over
wakeup sources
2. Open-coded Iterator: Enables the use of wakeup_source iterators directly
within BPF programs
Both iterators utilize pre-existing APIs wakeup_sources_walk_* to traverse
over the SRCU that backs the list of wakeup_sources.
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 (4):
bpf: Add wakeup_source iterator
bpf: Open coded BPF for wakeup_sources
selftests/bpf: Add tests for wakeup_sources
selftests/bpf: Open coded BPF wakeup_sources test
kernel/bpf/Makefile | 3 +
kernel/bpf/helpers.c | 3 +
kernel/bpf/wakeup_source_iter.c | 137 ++++++++
.../testing/selftests/bpf/bpf_experimental.h | 5 +
tools/testing/selftests/bpf/config | 1 +
.../bpf/prog_tests/wakeup_source_iter.c | 323 ++++++++++++++++++
.../selftests/bpf/progs/wakeup_source_iter.c | 117 +++++++
7 files changed, 589 insertions(+)
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 v2 1/4] bpf: Add wakeup_source iterator
2026-01-08 22:55 [PATCH bpf-next v2 0/4] Add wakeup_source iterators Samuel Wu
@ 2026-01-08 22:55 ` Samuel Wu
2026-01-08 23:21 ` bot+bpf-ci
2026-01-08 22:55 ` [PATCH bpf-next v2 2/4] bpf: Open coded BPF for wakeup_sources Samuel Wu
` (3 subsequent siblings)
4 siblings, 1 reply; 11+ messages in thread
From: Samuel Wu @ 2026-01-08 22:55 UTC (permalink / raw)
To: 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, 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
* [PATCH bpf-next v2 2/4] bpf: Open coded BPF for wakeup_sources
2026-01-08 22:55 [PATCH bpf-next v2 0/4] Add wakeup_source iterators Samuel Wu
2026-01-08 22:55 ` [PATCH bpf-next v2 1/4] bpf: Add wakeup_source iterator Samuel Wu
@ 2026-01-08 22:55 ` Samuel Wu
2026-01-08 23:21 ` bot+bpf-ci
2026-01-08 22:55 ` [PATCH bpf-next v2 3/4] selftests/bpf: Add tests " Samuel Wu
` (2 subsequent siblings)
4 siblings, 1 reply; 11+ messages in thread
From: Samuel Wu @ 2026-01-08 22:55 UTC (permalink / raw)
To: 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, bpf, linux-kselftest
Add open coded BPF iterators for wakeup_sources, which opens up more
options for BPF programs that need to traverse through wakeup_sources.
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
kernel/bpf/helpers.c | 3 +++
kernel/bpf/wakeup_source_iter.c | 34 +++++++++++++++++++++++++++++++++
2 files changed, 37 insertions(+)
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 9eaa4185e0a7..ca34d7614c3a 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -4518,6 +4518,9 @@ BTF_ID_FLAGS(func, bpf_iter_dmabuf_new, KF_ITER_NEW | KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_iter_dmabuf_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_iter_dmabuf_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)
#endif
+BTF_ID_FLAGS(func, bpf_iter_wakeup_source_new, KF_ITER_NEW | KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_iter_wakeup_source_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_iter_wakeup_source_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)
BTF_ID_FLAGS(func, __bpf_trap)
BTF_ID_FLAGS(func, bpf_strcmp);
BTF_ID_FLAGS(func, bpf_strcasecmp);
diff --git a/kernel/bpf/wakeup_source_iter.c b/kernel/bpf/wakeup_source_iter.c
index ab83d212a1f9..149baecfe436 100644
--- a/kernel/bpf/wakeup_source_iter.c
+++ b/kernel/bpf/wakeup_source_iter.c
@@ -90,6 +90,40 @@ static struct bpf_iter_reg bpf_wakeup_source_reg_info = {
.seq_info = &wakeup_source_iter_seq_info,
};
+struct bpf_iter_wakeup_source {
+ struct wakeup_source *ws;
+ int srcuidx;
+};
+
+__bpf_kfunc_start_defs();
+
+__bpf_kfunc int bpf_iter_wakeup_source_new(struct bpf_iter_wakeup_source *it)
+{
+ it->srcuidx = wakeup_sources_read_lock();
+ it->ws = wakeup_sources_walk_start();
+
+ return 0;
+}
+
+__bpf_kfunc struct wakeup_source *bpf_iter_wakeup_source_next(struct bpf_iter_wakeup_source *it)
+{
+ struct wakeup_source *prev = it->ws;
+
+ if (!prev)
+ return NULL;
+
+ it->ws = wakeup_sources_walk_next(it->ws);
+
+ return prev;
+}
+
+__bpf_kfunc void bpf_iter_wakeup_source_destroy(struct bpf_iter_wakeup_source *it)
+{
+ wakeup_sources_read_unlock(it->srcuidx);
+}
+
+__bpf_kfunc_end_defs();
+
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)
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH bpf-next v2 3/4] selftests/bpf: Add tests for wakeup_sources
2026-01-08 22:55 [PATCH bpf-next v2 0/4] Add wakeup_source iterators Samuel Wu
2026-01-08 22:55 ` [PATCH bpf-next v2 1/4] bpf: Add wakeup_source iterator Samuel Wu
2026-01-08 22:55 ` [PATCH bpf-next v2 2/4] bpf: Open coded BPF for wakeup_sources Samuel Wu
@ 2026-01-08 22:55 ` Samuel Wu
2026-01-09 23:31 ` Andrii Nakryiko
2026-01-08 22:55 ` [PATCH bpf-next v2 4/4] selftests/bpf: Open coded BPF wakeup_sources test Samuel Wu
2026-01-09 23:37 ` [PATCH bpf-next v2 0/4] Add wakeup_source iterators Andrii Nakryiko
4 siblings, 1 reply; 11+ messages in thread
From: Samuel Wu @ 2026-01-08 22:55 UTC (permalink / raw)
To: 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, 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 | 70 +++++
3 files changed, 352 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..31729f11585e
--- /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.wakeup_count, lock_unlock_cycles,
+ "wakeup_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..9a377fd28f4e
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/wakeup_source_iter.c
@@ -0,0 +1,70 @@
+// 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;
+ u64 active_count, event_count, expire_count, wakeup_count;
+ s64 active_time, curr_time, last_change_time, max_time,
+ prevent_sleep_time, start_prevent_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 ||
+ bpf_core_read(&active_count, sizeof(active_count), &ws->active_count) ||
+ bpf_core_read(&event_count, sizeof(event_count), &ws->event_count) ||
+ bpf_core_read(&expire_count, sizeof(expire_count), &ws->expire_count) ||
+ bpf_core_read(&last_change_time, sizeof(last_change_time), &ws->last_time) ||
+ bpf_core_read(&max_time, sizeof(max_time), &ws->max_time) ||
+ bpf_core_read(
+ &prevent_sleep_time, sizeof(prevent_sleep_time), &ws->prevent_sleep_time) ||
+ bpf_core_read(
+ &start_prevent_time, sizeof(start_prevent_time), &ws->start_prevent_time) ||
+ bpf_core_read(&total_time, sizeof(total_time), &ws->total_time) ||
+ bpf_core_read(&wakeup_count, sizeof(wakeup_count), &ws->wakeup_count))
+ return 0;
+
+
+ curr_time = bpf_ktime_get_ns();
+ active_time = 0;
+ if (active) {
+ active_time = curr_time - last_change_time;
+ total_time += active_time;
+ if (active_time > max_time)
+ max_time = active_time;
+ if (autosleep_enable)
+ prevent_sleep_time += curr_time - start_prevent_time;
+
+ }
+
+ BPF_SEQ_PRINTF(seq,
+ "%s %lu %ld %lu %lu %ld %ld %ld %ld %lu\n",
+ name,
+ active_count,
+ active_time / NSEC_PER_MS,
+ event_count,
+ expire_count,
+ last_change_time / NSEC_PER_MS,
+ max_time / NSEC_PER_MS,
+ prevent_sleep_time / NSEC_PER_MS,
+ total_time / NSEC_PER_MS,
+ wakeup_count);
+ return 0;
+}
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH bpf-next v2 4/4] selftests/bpf: Open coded BPF wakeup_sources test
2026-01-08 22:55 [PATCH bpf-next v2 0/4] Add wakeup_source iterators Samuel Wu
` (2 preceding siblings ...)
2026-01-08 22:55 ` [PATCH bpf-next v2 3/4] selftests/bpf: Add tests " Samuel Wu
@ 2026-01-08 22:55 ` Samuel Wu
2026-01-09 23:37 ` [PATCH bpf-next v2 0/4] Add wakeup_source iterators Andrii Nakryiko
4 siblings, 0 replies; 11+ messages in thread
From: Samuel Wu @ 2026-01-08 22:55 UTC (permalink / raw)
To: 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, bpf, linux-kselftest
This commit introduces a new selftest for the BPF wakeup_source iterator
to verify the functionality of open-coded iteration.
The test adds:
- A new BPF map `test_ws_hash` to track iterated wakeup source names.
- A BPF program `iter_ws_for_each` that iterates over wakeup sources and
updates the `test_ws_hash` map with the names of found sources.
- A new subtest `subtest_ws_iter_check_open_coded` to trigger the BPF
program and assert that the expected wakeup sources are marked in the
map.
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
.../testing/selftests/bpf/bpf_experimental.h | 5 ++
.../bpf/prog_tests/wakeup_source_iter.c | 42 +++++++++++++++++
.../selftests/bpf/progs/wakeup_source_iter.c | 47 +++++++++++++++++++
3 files changed, 94 insertions(+)
diff --git a/tools/testing/selftests/bpf/bpf_experimental.h b/tools/testing/selftests/bpf/bpf_experimental.h
index 2cd9165c7348..e532999b91ca 100644
--- a/tools/testing/selftests/bpf/bpf_experimental.h
+++ b/tools/testing/selftests/bpf/bpf_experimental.h
@@ -598,6 +598,11 @@ extern void bpf_iter_dmabuf_destroy(struct bpf_iter_dmabuf *it) __weak __ksym;
extern int bpf_cgroup_read_xattr(struct cgroup *cgroup, const char *name__str,
struct bpf_dynptr *value_p) __weak __ksym;
+struct bpf_iter_wakeup_source;
+extern int bpf_iter_wakeup_source_new(struct bpf_iter_wakeup_source *it) __weak __ksym;
+extern struct wakeup_source *bpf_iter_wakeup_source_next(
+ struct bpf_iter_wakeup_source *it) __weak __ksym;
+extern void bpf_iter_wakeup_source_destroy(struct bpf_iter_wakeup_source *it) __weak __ksym;
#define PREEMPT_BITS 8
#define SOFTIRQ_BITS 8
diff --git a/tools/testing/selftests/bpf/prog_tests/wakeup_source_iter.c b/tools/testing/selftests/bpf/prog_tests/wakeup_source_iter.c
index 31729f11585e..7b4d4cb25fd0 100644
--- a/tools/testing/selftests/bpf/prog_tests/wakeup_source_iter.c
+++ b/tools/testing/selftests/bpf/prog_tests/wakeup_source_iter.c
@@ -241,9 +241,37 @@ static void subtest_ws_iter_check_no_infinite_reads(
close(iter_fd);
}
+static void subtest_ws_iter_check_open_coded(struct wakeup_source_iter *skel,
+ int map_fd)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, topts);
+ char key[WAKEUP_SOURCE_NAME_LEN] = {0};
+ int err, fd;
+ bool found = false;
+
+ fd = bpf_program__fd(skel->progs.iter_ws_for_each);
+
+ err = bpf_prog_test_run_opts(fd, &topts);
+ if (!ASSERT_OK(err, "test_run_opts err"))
+ return;
+ if (!ASSERT_OK(topts.retval, "test_run_opts retval"))
+ return;
+
+ strncpy(key, test_ws_name, WAKEUP_SOURCE_NAME_LEN - 1);
+
+ if (!ASSERT_OK(bpf_map_lookup_elem(map_fd, key, &found),
+ "lookup test_ws_name"))
+ return;
+
+ ASSERT_TRUE(found, "found test ws via bpf_for_each");
+}
+
void test_wakeup_source_iter(void)
{
struct wakeup_source_iter *skel = NULL;
+ int map_fd;
+ const bool found_val = false;
+ char key[WAKEUP_SOURCE_NAME_LEN] = {0};
if (geteuid() != 0) {
fprintf(stderr,
@@ -256,6 +284,17 @@ void test_wakeup_source_iter(void)
if (!ASSERT_OK_PTR(skel, "wakeup_source_iter__open_and_load"))
return;
+ map_fd = bpf_map__fd(skel->maps.test_ws_hash);
+ if (!ASSERT_OK_FD(map_fd, "map_fd"))
+ goto destroy_skel;
+
+ /* Copy test name to key buffer, ensuring it's zero-padded */
+ strncpy(key, test_ws_name, WAKEUP_SOURCE_NAME_LEN - 1);
+
+ if (!ASSERT_OK(bpf_map_update_elem(map_fd, key, &found_val, BPF_ANY),
+ "insert test_ws_name"))
+ goto destroy_skel;
+
if (!ASSERT_OK(setup_test_ws(), "setup_test_ws"))
goto destroy;
@@ -274,8 +313,11 @@ void test_wakeup_source_iter(void)
subtest_ws_iter_check_sleep_times(skel);
if (test__start_subtest("no_infinite_reads"))
subtest_ws_iter_check_no_infinite_reads(skel);
+ if (test__start_subtest("open_coded"))
+ subtest_ws_iter_check_open_coded(skel, map_fd);
destroy:
teardown_test_ws();
+destroy_skel:
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
index 9a377fd28f4e..a95718dadcc7 100644
--- a/tools/testing/selftests/bpf/progs/wakeup_source_iter.c
+++ b/tools/testing/selftests/bpf/progs/wakeup_source_iter.c
@@ -9,6 +9,13 @@
char _license[] SEC("license") = "GPL";
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(key_size, WAKEUP_SOURCE_NAME_LEN);
+ __type(value, bool);
+ __uint(max_entries, 5);
+} test_ws_hash SEC(".maps");
+
SEC("iter/wakeup_source")
int wakeup_source_collector(struct bpf_iter__wakeup_source *ctx)
{
@@ -68,3 +75,43 @@ int wakeup_source_collector(struct bpf_iter__wakeup_source *ctx)
wakeup_count);
return 0;
}
+
+SEC("syscall")
+int iter_ws_for_each(const void *ctx)
+{
+ struct wakeup_source *ws;
+
+ bpf_for_each(wakeup_source, ws) {
+ char name[WAKEUP_SOURCE_NAME_LEN];
+ const char *pname;
+ bool *found;
+ long len;
+ int i;
+
+ if (bpf_core_read(&pname, sizeof(pname), &ws->name))
+ return 1;
+
+ if (!pname)
+ continue;
+
+ len = bpf_probe_read_kernel_str(name, sizeof(name), pname);
+ if (len < 0)
+ return 1;
+
+ /*
+ * Clear the remainder of the buffer to ensure a stable key for
+ * the map lookup.
+ */
+ bpf_for(i, len, WAKEUP_SOURCE_NAME_LEN)
+ name[i] = 0;
+
+ found = bpf_map_lookup_elem(&test_ws_hash, name);
+ if (found) {
+ bool t = true;
+
+ bpf_map_update_elem(&test_ws_hash, name, &t, BPF_EXIST);
+ }
+ }
+
+ return 0;
+}
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v2 1/4] bpf: Add wakeup_source iterator
2026-01-08 22:55 ` [PATCH bpf-next v2 1/4] bpf: Add wakeup_source iterator Samuel Wu
@ 2026-01-08 23:21 ` bot+bpf-ci
0 siblings, 0 replies; 11+ messages in thread
From: bot+bpf-ci @ 2026-01-08 23:21 UTC (permalink / raw)
To: wusamuel, ast, daniel, andrii, martin.lau, eddyz87, song,
yonghong.song, john.fastabend, kpsingh, sdf, haoluo, jolsa,
shuah
Cc: wusamuel, kernel-team, linux-kernel, bpf, linux-kselftest, ast,
andrii, daniel, martin.lau, eddyz87, yonghong.song, clm,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 1959 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_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;
> +}
What happens when the wakeup_sources list is empty?
wakeup_sources_walk_start() uses list_entry_rcu(ws_head->next, ...) which
does not check for an empty list. When the list is empty, ws_head->next
points back to ws_head itself, so list_entry_rcu() computes a pointer to
memory before the static list head, resulting in an invalid pointer.
The loop condition (ws && i < *pos) does not execute when *pos is 0,
so this invalid pointer is returned directly. Unlike the existing debugfs
implementation wakeup_sources_stats_seq_start() which uses
list_for_each_entry_rcu_locked() and properly returns NULL for an empty
list, this function returns a non-NULL invalid pointer.
The BPF iterator framework in bpf_seq_read() checks if (!p) goto stop,
but since the invalid pointer is non-NULL, this check passes and the
show callback is invoked with the invalid pointer. Could this pass
garbage memory to the BPF program?
The for_each_wakeup_source() macro might mask this issue because
wakeup_sources_walk_next() on this invalid pointer will return NULL,
but the seq_file framework calls show() before next(), so the first
invocation receives the 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/20835022582
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v2 2/4] bpf: Open coded BPF for wakeup_sources
2026-01-08 22:55 ` [PATCH bpf-next v2 2/4] bpf: Open coded BPF for wakeup_sources Samuel Wu
@ 2026-01-08 23:21 ` bot+bpf-ci
0 siblings, 0 replies; 11+ messages in thread
From: bot+bpf-ci @ 2026-01-08 23:21 UTC (permalink / raw)
To: wusamuel, ast, daniel, andrii, martin.lau, eddyz87, song,
yonghong.song, john.fastabend, kpsingh, sdf, haoluo, jolsa,
shuah
Cc: wusamuel, kernel-team, linux-kernel, bpf, linux-kselftest, ast,
andrii, daniel, martin.lau, eddyz87, yonghong.song, clm,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2073 bytes --]
> diff --git a/kernel/bpf/wakeup_source_iter.c b/kernel/bpf/wakeup_source_iter.c
> index ab83d212a1f9..149baecfe436 100644
> --- a/kernel/bpf/wakeup_source_iter.c
> +++ b/kernel/bpf/wakeup_source_iter.c
[ ... ]
> +__bpf_kfunc int bpf_iter_wakeup_source_new(struct bpf_iter_wakeup_source *it)
> +{
> + it->srcuidx = wakeup_sources_read_lock();
> + it->ws = wakeup_sources_walk_start();
> +
> + return 0;
> +}
> +
> +__bpf_kfunc struct wakeup_source *bpf_iter_wakeup_source_next(struct bpf_iter_wakeup_source *it)
> +{
> + struct wakeup_source *prev = it->ws;
> +
> + if (!prev)
> + return NULL;
> +
> + it->ws = wakeup_sources_walk_next(it->ws);
> +
> + return prev;
> +}
When the wakeup_sources list is empty, wakeup_sources_walk_start() returns
a bogus pointer via list_entry_rcu()/container_of() on the list head itself,
rather than NULL. The check "if (!prev)" at line 112 will not catch this
bogus pointer since it is non-NULL.
Does this return a bogus pointer to the BPF program when the wakeup_sources
list is empty? If so, the BPF program could then access invalid memory when
it tries to read fields of the returned wakeup_source.
For reference, wakeup_sources_walk_start() in drivers/base/power/wakeup.c:
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);
}
When wakeup_sources is empty, ws_head->next == ws_head, so this returns
container_of(ws_head, struct wakeup_source, entry) which points to memory
before the wakeup_sources variable rather than a valid wakeup_source struct.
Other BPF open-coded iterators like bpf_iter_dmabuf handle this differently
by initializing to NULL and getting the first element lazily in _next().
Would a similar approach work here?
---
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/20835022582
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v2 3/4] selftests/bpf: Add tests for wakeup_sources
2026-01-08 22:55 ` [PATCH bpf-next v2 3/4] selftests/bpf: Add tests " Samuel Wu
@ 2026-01-09 23:31 ` Andrii Nakryiko
2026-01-21 23:28 ` Samuel Wu
0 siblings, 1 reply; 11+ messages in thread
From: Andrii Nakryiko @ 2026-01-09 23:31 UTC (permalink / raw)
To: Samuel Wu
Cc: 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, bpf, linux-kselftest
On Thu, Jan 8, 2026 at 2:56 PM Samuel Wu <wusamuel@google.com> wrote:
>
> 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 | 70 +++++
> 3 files changed, 352 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..31729f11585e
> --- /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.wakeup_count, lock_unlock_cycles,
> + "wakeup_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..9a377fd28f4e
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/wakeup_source_iter.c
> @@ -0,0 +1,70 @@
> +// 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;
> + u64 active_count, event_count, expire_count, wakeup_count;
> + s64 active_time, curr_time, last_change_time, max_time,
> + prevent_sleep_time, start_prevent_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 ||
> + bpf_core_read(&active_count, sizeof(active_count), &ws->active_count) ||
> + bpf_core_read(&event_count, sizeof(event_count), &ws->event_count) ||
> + bpf_core_read(&expire_count, sizeof(expire_count), &ws->expire_count) ||
> + bpf_core_read(&last_change_time, sizeof(last_change_time), &ws->last_time) ||
> + bpf_core_read(&max_time, sizeof(max_time), &ws->max_time) ||
> + bpf_core_read(
> + &prevent_sleep_time, sizeof(prevent_sleep_time), &ws->prevent_sleep_time) ||
> + bpf_core_read(
> + &start_prevent_time, sizeof(start_prevent_time), &ws->start_prevent_time) ||
> + bpf_core_read(&total_time, sizeof(total_time), &ws->total_time) ||
> + bpf_core_read(&wakeup_count, sizeof(wakeup_count), &ws->wakeup_count))
> + return 0;
you really don't need to do this for types returned from iterators,
try just accessing ws->start_prevent_time (and any other field)
directly
> +
> +
> + curr_time = bpf_ktime_get_ns();
> + active_time = 0;
> + if (active) {
> + active_time = curr_time - last_change_time;
> + total_time += active_time;
> + if (active_time > max_time)
> + max_time = active_time;
> + if (autosleep_enable)
> + prevent_sleep_time += curr_time - start_prevent_time;
> +
> + }
> +
> + BPF_SEQ_PRINTF(seq,
> + "%s %lu %ld %lu %lu %ld %ld %ld %ld %lu\n",
> + name,
> + active_count,
> + active_time / NSEC_PER_MS,
> + event_count,
> + expire_count,
> + last_change_time / NSEC_PER_MS,
> + max_time / NSEC_PER_MS,
> + prevent_sleep_time / NSEC_PER_MS,
> + total_time / NSEC_PER_MS,
> + wakeup_count);
> + return 0;
> +}
> --
> 2.52.0.457.g6b5491de43-goog
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v2 0/4] Add wakeup_source iterators
2026-01-08 22:55 [PATCH bpf-next v2 0/4] Add wakeup_source iterators Samuel Wu
` (3 preceding siblings ...)
2026-01-08 22:55 ` [PATCH bpf-next v2 4/4] selftests/bpf: Open coded BPF wakeup_sources test Samuel Wu
@ 2026-01-09 23:37 ` Andrii Nakryiko
2026-01-21 23:27 ` Samuel Wu
4 siblings, 1 reply; 11+ messages in thread
From: Andrii Nakryiko @ 2026-01-09 23:37 UTC (permalink / raw)
To: Samuel Wu
Cc: 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, bpf, linux-kselftest
On Thu, Jan 8, 2026 at 2:56 PM Samuel Wu <wusamuel@google.com> wrote:
>
> 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.
>
> This series implements two types of iterators:
> 1. Standard BPF Iterator: Allows creating a BPF link to iterate over
> wakeup sources
> 2. Open-coded Iterator: Enables the use of wakeup_source iterators directly
> within BPF programs
>
> Both iterators utilize pre-existing APIs wakeup_sources_walk_* to traverse
> over the SRCU that backs the list of wakeup_sources.
One reason to add either open-coded iterator or iterator program is
when you need to work with some kernel object (i.e., if there is some
additional API that takes that object and somehow manipulates it) or
if traversal logic is involved in terms of synchronization primitives
necessary.
In your case neither concern seems to apply. Looking at
wakeup_sources_walk_* helpers, it's just a simple linked list
traversal and struct wakeup_source has plenty of plain data fields of
interest, if I understand correctly. You probably are not intending to
allow locking it or manipulate wakeirq, is that right?
So I'd say you should do this using bpf_for() generic loop and
bpf_core_cast() helper. The only problem is that wakeup_sources global
variable is static, so it's not that easy to get its memory address
(to start iteration). Maybe look into working around that first?
pw-bot: cr
>
> 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 (4):
> bpf: Add wakeup_source iterator
> bpf: Open coded BPF for wakeup_sources
> selftests/bpf: Add tests for wakeup_sources
> selftests/bpf: Open coded BPF wakeup_sources test
>
> kernel/bpf/Makefile | 3 +
> kernel/bpf/helpers.c | 3 +
> kernel/bpf/wakeup_source_iter.c | 137 ++++++++
> .../testing/selftests/bpf/bpf_experimental.h | 5 +
> tools/testing/selftests/bpf/config | 1 +
> .../bpf/prog_tests/wakeup_source_iter.c | 323 ++++++++++++++++++
> .../selftests/bpf/progs/wakeup_source_iter.c | 117 +++++++
> 7 files changed, 589 insertions(+)
> 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
* Re: [PATCH bpf-next v2 0/4] Add wakeup_source iterators
2026-01-09 23:37 ` [PATCH bpf-next v2 0/4] Add wakeup_source iterators Andrii Nakryiko
@ 2026-01-21 23:27 ` Samuel Wu
0 siblings, 0 replies; 11+ messages in thread
From: Samuel Wu @ 2026-01-21 23:27 UTC (permalink / raw)
To: Andrii Nakryiko
Cc: 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, bpf, linux-kselftest
On Fri, Jan 9, 2026 at 3:37 PM Andrii Nakryiko
<andrii.nakryiko@gmail.com> wrote:
>
> On Thu, Jan 8, 2026 at 2:56 PM Samuel Wu <wusamuel@google.com> wrote:
> >
> > 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.
> >
> > This series implements two types of iterators:
> > 1. Standard BPF Iterator: Allows creating a BPF link to iterate over
> > wakeup sources
> > 2. Open-coded Iterator: Enables the use of wakeup_source iterators directly
> > within BPF programs
> >
> > Both iterators utilize pre-existing APIs wakeup_sources_walk_* to traverse
> > over the SRCU that backs the list of wakeup_sources.
>
> One reason to add either open-coded iterator or iterator program is
> when you need to work with some kernel object (i.e., if there is some
> additional API that takes that object and somehow manipulates it) or
> if traversal logic is involved in terms of synchronization primitives
> necessary.
>
> In your case neither concern seems to apply. Looking at
> wakeup_sources_walk_* helpers, it's just a simple linked list
> traversal and struct wakeup_source has plenty of plain data fields of
> interest, if I understand correctly. You probably are not intending to
> allow locking it or manipulate wakeirq, is that right?
>
Correct.
> So I'd say you should do this using bpf_for() generic loop and
> bpf_core_cast() helper. The only problem is that wakeup_sources global
> variable is static, so it's not that easy to get its memory address
> (to start iteration). Maybe look into working around that first?
>
> pw-bot: cr
>
I'm thinking I'll have to drop the open-coded iterators patches in the
v3 patchset, and revisit this in a later patchset when we have a clear
use case for it. I'm seeing now that open-coded iterators support was
added around kernel 6.6, so backwards compatibility will be
nontrivial.
>
> >
> > 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 (4):
> > bpf: Add wakeup_source iterator
> > bpf: Open coded BPF for wakeup_sources
> > selftests/bpf: Add tests for wakeup_sources
> > selftests/bpf: Open coded BPF wakeup_sources test
> >
> > kernel/bpf/Makefile | 3 +
> > kernel/bpf/helpers.c | 3 +
> > kernel/bpf/wakeup_source_iter.c | 137 ++++++++
> > .../testing/selftests/bpf/bpf_experimental.h | 5 +
> > tools/testing/selftests/bpf/config | 1 +
> > .../bpf/prog_tests/wakeup_source_iter.c | 323 ++++++++++++++++++
> > .../selftests/bpf/progs/wakeup_source_iter.c | 117 +++++++
> > 7 files changed, 589 insertions(+)
> > 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
* Re: [PATCH bpf-next v2 3/4] selftests/bpf: Add tests for wakeup_sources
2026-01-09 23:31 ` Andrii Nakryiko
@ 2026-01-21 23:28 ` Samuel Wu
0 siblings, 0 replies; 11+ messages in thread
From: Samuel Wu @ 2026-01-21 23:28 UTC (permalink / raw)
To: Andrii Nakryiko
Cc: 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, bpf, linux-kselftest
On Fri, Jan 9, 2026 at 3:32 PM Andrii Nakryiko
<andrii.nakryiko@gmail.com> wrote:
>
> On Thu, Jan 8, 2026 at 2:56 PM Samuel Wu <wusamuel@google.com> wrote:
> >
> > 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 | 70 +++++
> > 3 files changed, 352 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..31729f11585e
> > --- /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.wakeup_count, lock_unlock_cycles,
> > + "wakeup_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..9a377fd28f4e
> > --- /dev/null
> > +++ b/tools/testing/selftests/bpf/progs/wakeup_source_iter.c
> > @@ -0,0 +1,70 @@
> > +// 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;
> > + u64 active_count, event_count, expire_count, wakeup_count;
> > + s64 active_time, curr_time, last_change_time, max_time,
> > + prevent_sleep_time, start_prevent_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 ||
> > + bpf_core_read(&active_count, sizeof(active_count), &ws->active_count) ||
> > + bpf_core_read(&event_count, sizeof(event_count), &ws->event_count) ||
> > + bpf_core_read(&expire_count, sizeof(expire_count), &ws->expire_count) ||
> > + bpf_core_read(&last_change_time, sizeof(last_change_time), &ws->last_time) ||
> > + bpf_core_read(&max_time, sizeof(max_time), &ws->max_time) ||
> > + bpf_core_read(
> > + &prevent_sleep_time, sizeof(prevent_sleep_time), &ws->prevent_sleep_time) ||
> > + bpf_core_read(
> > + &start_prevent_time, sizeof(start_prevent_time), &ws->start_prevent_time) ||
> > + bpf_core_read(&total_time, sizeof(total_time), &ws->total_time) ||
> > + bpf_core_read(&wakeup_count, sizeof(wakeup_count), &ws->wakeup_count))
> > + return 0;
>
> you really don't need to do this for types returned from iterators,
> try just accessing ws->start_prevent_time (and any other field)
> directly
>
Thanks for reviewing Andrii. That is much cleaner. I'll address this
and the bot's comments in v3.
> > +
> > +
> > + curr_time = bpf_ktime_get_ns();
> > + active_time = 0;
> > + if (active) {
> > + active_time = curr_time - last_change_time;
> > + total_time += active_time;
> > + if (active_time > max_time)
> > + max_time = active_time;
> > + if (autosleep_enable)
> > + prevent_sleep_time += curr_time - start_prevent_time;
> > +
> > + }
> > +
> > + BPF_SEQ_PRINTF(seq,
> > + "%s %lu %ld %lu %lu %ld %ld %ld %ld %lu\n",
> > + name,
> > + active_count,
> > + active_time / NSEC_PER_MS,
> > + event_count,
> > + expire_count,
> > + last_change_time / NSEC_PER_MS,
> > + max_time / NSEC_PER_MS,
> > + prevent_sleep_time / NSEC_PER_MS,
> > + total_time / NSEC_PER_MS,
> > + wakeup_count);
> > + return 0;
> > +}
> > --
> > 2.52.0.457.g6b5491de43-goog
> >
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-01-21 23:28 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-01-08 22:55 [PATCH bpf-next v2 0/4] Add wakeup_source iterators Samuel Wu
2026-01-08 22:55 ` [PATCH bpf-next v2 1/4] bpf: Add wakeup_source iterator Samuel Wu
2026-01-08 23:21 ` bot+bpf-ci
2026-01-08 22:55 ` [PATCH bpf-next v2 2/4] bpf: Open coded BPF for wakeup_sources Samuel Wu
2026-01-08 23:21 ` bot+bpf-ci
2026-01-08 22:55 ` [PATCH bpf-next v2 3/4] selftests/bpf: Add tests " Samuel Wu
2026-01-09 23:31 ` Andrii Nakryiko
2026-01-21 23:28 ` Samuel Wu
2026-01-08 22:55 ` [PATCH bpf-next v2 4/4] selftests/bpf: Open coded BPF wakeup_sources test Samuel Wu
2026-01-09 23:37 ` [PATCH bpf-next v2 0/4] Add wakeup_source iterators Andrii Nakryiko
2026-01-21 23: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®