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.
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 | 1 + 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, 587 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
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 db72b96f9c8c..a5f867de6bd6 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 b8719f47428e..e2c0dcbfd02d 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)
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 5cea4d4458f3..b2eaba38cc68 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 8c1470f06740..7812e773aa0c 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; +}
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 | 1 + kernel/bpf/wakeup_source_iter.c | 103 ++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 kernel/bpf/wakeup_source_iter.c
diff --git a/kernel/bpf/Makefile b/kernel/bpf/Makefile index 232cbc97434d..6a479982469a 100644 --- a/kernel/bpf/Makefile +++ b/kernel/bpf/Makefile @@ -56,6 +56,7 @@ obj-$(CONFIG_BPF_SYSCALL) += kmem_cache_iter.o ifeq ($(CONFIG_DMA_SHARED_BUFFER),y) obj-$(CONFIG_BPF_SYSCALL) += dmabuf_iter.o endif +obj-$(CONFIG_BPF_SYSCALL) += wakeup_source_iter.o
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..b8719f47428e --- /dev/null +++ b/kernel/bpf/wakeup_source_iter.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2025 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);
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..5cea4d4458f3 --- /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) 2025 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..8c1470f06740 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/wakeup_source_iter.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 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; +}
Hi Samuel,
kernel test robot noticed the following build errors:
[auto build test ERROR on bpf-next/master] [also build test ERROR on bpf/master shuah-kselftest/next shuah-kselftest/fixes linus/master v6.18 next-20251204] [cannot apply to bpf-next/net] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Samuel-Wu/bpf-Add-wakeup_sour... base: https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git master patch link: https://lore.kernel.org/r/20251204025003.3162056-2-wusamuel%40google.com patch subject: [PATCH v1 1/4] bpf: Add wakeup_source iterator config: um-randconfig-001-20251205 (https://download.01.org/0day-ci/archive/20251205/202512050548.YLgRm659-lkp@i...) compiler: gcc-14 (Debian 14.2.0-19) 14.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251205/202512050548.YLgRm659-lkp@i...)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot lkp@intel.com | Closes: https://lore.kernel.org/oe-kbuild-all/202512050548.YLgRm659-lkp@intel.com/
All errors (new ones prefixed by >>):
kernel/bpf/wakeup_source_iter.c: In function 'wakeup_source_iter_seq_start': kernel/bpf/wakeup_source_iter.c:20:20: error: implicit declaration of function 'wakeup_sources_read_lock'; did you mean 'wakeup_source_register'? [-Wimplicit-function-declaration] 20 | *srcuidx = wakeup_sources_read_lock(); | ^~~~~~~~~~~~~~~~~~~~~~~~ | wakeup_source_register kernel/bpf/wakeup_source_iter.c:22:14: error: implicit declaration of function 'wakeup_sources_walk_start'; did you mean 'wakeup_source_register'? [-Wimplicit-function-declaration] 22 | ws = wakeup_sources_walk_start(); | ^~~~~~~~~~~~~~~~~~~~~~~~~ | wakeup_source_register
kernel/bpf/wakeup_source_iter.c:22:12: error: assignment to 'struct wakeup_source *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
22 | ws = wakeup_sources_walk_start(); | ^ kernel/bpf/wakeup_source_iter.c:24:22: error: implicit declaration of function 'wakeup_sources_walk_next' [-Wimplicit-function-declaration] 24 | ws = wakeup_sources_walk_next(ws); | ^~~~~~~~~~~~~~~~~~~~~~~~ kernel/bpf/wakeup_source_iter.c:24:20: error: assignment to 'struct wakeup_source *' from 'int' makes pointer from integer without a cast [-Wint-conversion] 24 | ws = wakeup_sources_walk_next(ws); | ^ kernel/bpf/wakeup_source_iter.c: In function 'wakeup_source_iter_seq_next':
kernel/bpf/wakeup_source_iter.c:35:16: error: returning 'int' from a function with return type 'void *' makes pointer from integer without a cast [-Wint-conversion]
35 | return wakeup_sources_walk_next(ws); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ kernel/bpf/wakeup_source_iter.c: In function 'wakeup_source_iter_seq_stop': kernel/bpf/wakeup_source_iter.c:43:17: error: implicit declaration of function 'wakeup_sources_read_unlock' [-Wimplicit-function-declaration] 43 | wakeup_sources_read_unlock(*srcuidx); | ^~~~~~~~~~~~~~~~~~~~~~~~~~
vim +22 kernel/bpf/wakeup_source_iter.c
13 14 static void *wakeup_source_iter_seq_start(struct seq_file *seq, loff_t *pos) 15 { 16 int *srcuidx = seq->private; 17 struct wakeup_source *ws; 18 loff_t i; 19 20 *srcuidx = wakeup_sources_read_lock(); 21
22 ws = wakeup_sources_walk_start();
23 for (i = 0; ws && i < *pos; i++) 24 ws = wakeup_sources_walk_next(ws); 25 26 return ws; 27 } 28 29 static void *wakeup_source_iter_seq_next(struct seq_file *seq, void *v, loff_t *pos) 30 { 31 struct wakeup_source *ws = v; 32 33 ++*pos; 34
35 return wakeup_sources_walk_next(ws);
36 } 37
Hi Samuel,
kernel test robot noticed the following build errors:
[auto build test ERROR on bpf-next/master] [also build test ERROR on bpf/master shuah-kselftest/next shuah-kselftest/fixes linus/master v6.18 next-20251204] [cannot apply to bpf-next/net] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Samuel-Wu/bpf-Add-wakeup_sour... base: https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git master patch link: https://lore.kernel.org/r/20251204025003.3162056-2-wusamuel%40google.com patch subject: [PATCH v1 1/4] bpf: Add wakeup_source iterator config: um-randconfig-r052-20251205 (https://download.01.org/0day-ci/archive/20251205/202512051019.IljjrKRb-lkp@i...) compiler: gcc-13 (Debian 13.3.0-16) 13.3.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251205/202512051019.IljjrKRb-lkp@i...)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot lkp@intel.com | Closes: https://lore.kernel.org/oe-kbuild-all/202512051019.IljjrKRb-lkp@intel.com/
All error/warnings (new ones prefixed by >>):
kernel/bpf/wakeup_source_iter.c: In function 'wakeup_source_iter_seq_start':
kernel/bpf/wakeup_source_iter.c:20:20: error: implicit declaration of function 'wakeup_sources_read_lock'; did you mean 'wakeup_source_register'? [-Werror=implicit-function-declaration]
20 | *srcuidx = wakeup_sources_read_lock(); | ^~~~~~~~~~~~~~~~~~~~~~~~ | wakeup_source_register
kernel/bpf/wakeup_source_iter.c:22:14: error: implicit declaration of function 'wakeup_sources_walk_start'; did you mean 'wakeup_source_register'? [-Werror=implicit-function-declaration]
22 | ws = wakeup_sources_walk_start(); | ^~~~~~~~~~~~~~~~~~~~~~~~~ | wakeup_source_register
kernel/bpf/wakeup_source_iter.c:22:12: warning: assignment to 'struct wakeup_source *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
22 | ws = wakeup_sources_walk_start(); | ^
kernel/bpf/wakeup_source_iter.c:24:22: error: implicit declaration of function 'wakeup_sources_walk_next' [-Werror=implicit-function-declaration]
24 | ws = wakeup_sources_walk_next(ws); | ^~~~~~~~~~~~~~~~~~~~~~~~ kernel/bpf/wakeup_source_iter.c:24:20: warning: assignment to 'struct wakeup_source *' from 'int' makes pointer from integer without a cast [-Wint-conversion] 24 | ws = wakeup_sources_walk_next(ws); | ^ kernel/bpf/wakeup_source_iter.c: In function 'wakeup_source_iter_seq_next':
kernel/bpf/wakeup_source_iter.c:35:16: warning: returning 'int' from a function with return type 'void *' makes pointer from integer without a cast [-Wint-conversion]
35 | return wakeup_sources_walk_next(ws); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ kernel/bpf/wakeup_source_iter.c: In function 'wakeup_source_iter_seq_stop':
kernel/bpf/wakeup_source_iter.c:43:17: error: implicit declaration of function 'wakeup_sources_read_unlock' [-Werror=implicit-function-declaration]
43 | wakeup_sources_read_unlock(*srcuidx); | ^~~~~~~~~~~~~~~~~~~~~~~~~~ cc1: some warnings being treated as errors
vim +20 kernel/bpf/wakeup_source_iter.c
13 14 static void *wakeup_source_iter_seq_start(struct seq_file *seq, loff_t *pos) 15 { 16 int *srcuidx = seq->private; 17 struct wakeup_source *ws; 18 loff_t i; 19
20 *srcuidx = wakeup_sources_read_lock();
21
22 ws = wakeup_sources_walk_start();
23 for (i = 0; ws && i < *pos; i++)
24 ws = wakeup_sources_walk_next(ws);
25 26 return ws; 27 } 28 29 static void *wakeup_source_iter_seq_next(struct seq_file *seq, void *v, loff_t *pos) 30 { 31 struct wakeup_source *ws = v; 32 33 ++*pos; 34
35 return wakeup_sources_walk_next(ws);
36 } 37 38 static void wakeup_source_iter_seq_stop(struct seq_file *seq, void *v) 39 { 40 int *srcuidx = seq->private; 41 42 if (*srcuidx >= 0)
43 wakeup_sources_read_unlock(*srcuidx);
44 *srcuidx = -1; 45 } 46
linux-kselftest-mirror@lists.linaro.org