From d9941add9fabc426e2babaea586907ffc3e6c665 Mon Sep 17 00:00:00 2001 From: Tedd Ho-Jeong An Date: Tue, 10 Mar 2020 10:00:58 -0700 Subject: [PATCH 1/2] workflow: Add workflow files This patch adds workflow files for ci: [sync.yml] - runs every 30 mins. - sync repo with upstream repo and rebase workflow branch to tip of master. - creates PR after reading patches from patchwork.kernel.org [ci.yml] - Tests the following checks: - checkpatch - gitlint - make - make check [code_scan.yml] - Static code checker: Coverity and Clang - Coverity: Submit the result to the coverity website - Clang Code Scan: Send email with result file to the internal team To simplify the history, new change will amend to this patch without creating new patch. --- .github/workflows/ci.yml | 25 +++++++++++++++++++ .github/workflows/code_scan.yml | 26 ++++++++++++++++++++ .github/workflows/sync.yml | 43 +++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/code_scan.yml create mode 100644 .github/workflows/sync.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..a3a54d1a1b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: CI + +on: [pull_request] + +jobs: + ci: + runs-on: ubuntu-latest + name: CI for Pull Request + steps: + - name: Checkout the source code + uses: actions/checkout@v3 + with: + path: src/src + + - name: CI + uses: tedd-an/bzcafe@main + with: + task: ci + base_folder: src + space: user + github_token: ${{ secrets.ACTION_TOKEN }} + email_token: ${{ secrets.EMAIL_TOKEN }} + patchwork_token: ${{ secrets.PATCHWORK_TOKEN }} + patchwork_user: ${{ secrets.PATCHWORK_USER }} + diff --git a/.github/workflows/code_scan.yml b/.github/workflows/code_scan.yml new file mode 100644 index 0000000000..181d08c32d --- /dev/null +++ b/.github/workflows/code_scan.yml @@ -0,0 +1,26 @@ +name: Code Scan + +on: + schedule: + - cron: "40 7 * * FRI" + +jobs: + code-scan: + runs-on: ubuntu-latest + steps: + - name: Checkout the source + uses: actions/checkout@v2 + with: + fetch-depth: 0 + path: src + - name: Code Scan + uses: BluezTestBot/action-code-scan@main + with: + src_path: src + github_token: ${{ secrets.GITHUB_TOKEN }} + email_token: ${{ secrets.EMAIL_TOKEN }} + - uses: actions/upload-artifact@v2 + with: + name: scan_report + path: scan_report.tar.gz + diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml new file mode 100644 index 0000000000..d935cca9fa --- /dev/null +++ b/.github/workflows/sync.yml @@ -0,0 +1,43 @@ +name: Sync + +on: + schedule: + - cron: "*/30 * * * *" + +jobs: + sync_repo: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + ref: master + + - name: Sync Repo + uses: tedd-an/bzcafe@main + with: + task: sync + upstream_repo: 'https://git.kernel.org/pub/scm/bluetooth/bluez.git' + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Cleanup PR + uses: tedd-an/bzcafe@main + with: + task: cleanup + github_token: ${{ secrets.ACTION_TOKEN }} + + sync_patchwork: + needs: sync_repo + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Sync Patchwork + uses: tedd-an/bzcafe@main + with: + task: patchwork + space: user + github_token: ${{ secrets.ACTION_TOKEN }} + email_token: ${{ secrets.EMAIL_TOKEN }} + patchwork_token: ${{ secrets.PATCHWORK_TOKEN }} + patchwork_user: ${{ secrets.PATCHWORK_USER }} + From 48d9c346b9c857e3e50a45d16b47aaf2c8b86d19 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 5 Apr 2025 12:49:20 +0300 Subject: [PATCH 2/2] tools: add BPF timestamping tests Add some tests for BPF timestamping on Bluetooth sockets. These require additional tester kernel config, and at build time the vmlinux image. Add cgroup mount to test-runner. Add documentation to tester config for this. Add tests: ISO Send - TX BPF Timestamping ISO Send - TX BPF + Socket Timestamping --- Makefile.tools | 39 +++++++ configure.ac | 36 ++++++- doc/test-runner.rst | 28 ++++- doc/tester.config | 8 ++ tools/iso-tester.c | 97 ++++++++++++++++- tools/l2cap-tester.c | 2 +- tools/sco-tester.c | 2 +- tools/test-runner.c | 1 + tools/tester-bpf.c | 101 ++++++++++++++++++ tools/tester-bpf.h | 7 ++ tools/tester.h | 240 +++++++++++++++++++++++++++++++++++++------ 11 files changed, 519 insertions(+), 42 deletions(-) create mode 100644 tools/tester-bpf.c create mode 100644 tools/tester-bpf.h diff --git a/Makefile.tools b/Makefile.tools index e60c31b1d9..75bd3daafa 100644 --- a/Makefile.tools +++ b/Makefile.tools @@ -144,6 +144,8 @@ tools_l2cap_tester_SOURCES = tools/l2cap-tester.c tools/tester.h monitor/bt.h \ emulator/smp.c tools_l2cap_tester_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) +tools_l2cap_tester_CPPFLAGS = $(AM_CPPFLAGS) $(GLIB_CFLAGS) +nodist_tools_l2cap_tester_SOURCES = tools_rfcomm_tester_SOURCES = tools/rfcomm-tester.c monitor/bt.h \ emulator/hciemu.h emulator/hciemu.c \ @@ -191,6 +193,8 @@ tools_sco_tester_SOURCES = tools/sco-tester.c tools/tester.h monitor/bt.h \ emulator/smp.c tools_sco_tester_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) +tools_sco_tester_CPPFLAGS = $(AM_CPPFLAGS) $(GLIB_CFLAGS) +nodist_tools_sco_tester_SOURCES = tools_hci_tester_SOURCES = tools/hci-tester.c monitor/bt.h tools_hci_tester_LDADD = src/libshared-glib.la $(GLIB_LIBS) @@ -212,6 +216,8 @@ tools_iso_tester_SOURCES = tools/iso-tester.c tools/tester.h monitor/bt.h \ emulator/smp.c tools_iso_tester_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) +tools_iso_tester_CPPFLAGS = $(AM_CPPFLAGS) $(GLIB_CFLAGS) +nodist_tools_iso_tester_SOURCES = tools_ioctl_tester_SOURCES = tools/ioctl-tester.c monitor/bt.h \ emulator/hciemu.h emulator/hciemu.c \ @@ -221,6 +227,39 @@ tools_ioctl_tester_SOURCES = tools/ioctl-tester.c monitor/bt.h \ emulator/smp.c tools_ioctl_tester_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) + +if TESTING_BPF +tools/vmlinux.h: $(BPF_VMLINUX) + $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ + bpftool btf dump file $(BPF_VMLINUX) format c > $@.new && \ + mv -f $@.new $@ + +tools/tester-bpf.o: $(srcdir)/tools/tester-bpf.c tools/vmlinux.h + $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ + $(CLANG_BPF) -Wall -Werror -Os -g --target=bpf -Itools -c -o $@ $< + +tools/tester-skel.h: tools/tester-bpf.o + $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ + bpftool gen skeleton $< > $@.new && \ + mv -f $@.new $@ + +BPF_BUILT_SOURCES = $(builddir)/tools/tester-skel.h $(builddir)/tools/vmlinux.h + +nodist_tools_sco_tester_SOURCES += $(BPF_BUILT_SOURCES) +nodist_tools_iso_tester_SOURCES += $(BPF_BUILT_SOURCES) +nodist_tools_l2cap_tester_SOURCES += $(BPF_BUILT_SOURCES) +BUILT_SOURCES += $(BPF_BUILT_SOURCES) +CLEANFILES += $(BPF_BUILT_SOURCES) + +tools_sco_tester_CPPFLAGS += -I$(builddir)/tools $(LIBBPF_CFLAGS) +tools_iso_tester_CPPFLAGS += -I$(builddir)/tools $(LIBBPF_CFLAGS) +tools_l2cap_tester_CPPFLAGS += -I$(builddir)/tools $(LIBBPF_CFLAGS) + +tools_sco_tester_LDADD += $(LIBBPF_LIBS) +tools_iso_tester_LDADD += $(LIBBPF_LIBS) +tools_l2cap_tester_LDADD += $(LIBBPF_LIBS) +endif + endif if TOOLS diff --git a/configure.ac b/configure.ac index 2ea7272562..6f09e248f9 100644 --- a/configure.ac +++ b/configure.ac @@ -390,10 +390,38 @@ AC_ARG_ENABLE(testing, AS_HELP_STRING([--enable-testing], AM_CONDITIONAL(TESTING, test "${enable_testing}" = "yes") if (test "${enable_testing}" = "yes"); then - AC_CHECK_DECLS([SOF_TIMESTAMPING_TX_COMPLETION, SCM_TSTAMP_COMPLETION], - [], [], [[#include - #include - #include ]]) + AC_CHECK_DECLS([SOF_TIMESTAMPING_TX_COMPLETION, SCM_TSTAMP_COMPLETION], + [], [], [[#include + #include + #include ]]) +fi + +AC_ARG_ENABLE(testing-bpf, AS_HELP_STRING([--enable-testing-bpf[=PATH/TO/VMLINUX]], + [enable BPF testing tools]), + [enable_testing_bpf=yes; enable_testing_bpf_arg=${enableval}], + [enable_bpf=no]) +AM_CONDITIONAL(TESTING_BPF, test "${enable_testing_bpf}" = "yes") + +if (test "${enable_testing_bpf}" = "yes"); then + AC_ARG_VAR(CLANG_BPF, [CLANG compiler (for BPF)]) + AC_ARG_VAR(BPFTOOL, [bpftool]) + AC_ARG_VAR(BPF_VMLINUX, [vmlinux image to use for BPF testing]) + AC_PATH_PROG([CLANG_BPF], [clang], "no") + if (test "${CLANG_BPF}" == "no"); then + AC_MSG_ERROR([clang for BPF missing]) + fi + AC_PATH_PROG([BPFTOOL], [bpftool], "no") + if (test "${BPFTOOL}" == "no"); then + AC_MSG_ERROR([bpftool missing]) + fi + PKG_CHECK_MODULES(LIBBPF, libbpf >= 1.4, [], [AC_MSG_ERROR([libbpf missing])]) + if (test "${enable_testing_bpf_arg}" != "yes"); then + BPF_VMLINUX=${enable_testing_bpf_arg} + elif (test "${BPF_VMLINUX}" = ""); then + BPF_VMLINUX=/sys/kernel/btf/vmlinux + fi + AC_MSG_NOTICE([using BPF_VMLINUX=${BPF_VMLINUX} for BPF testing]) + AC_DEFINE(HAVE_BPF, 1, [Define to 1 if bpf testing is required]) fi AC_ARG_ENABLE(experimental, AS_HELP_STRING([--enable-experimental], diff --git a/doc/test-runner.rst b/doc/test-runner.rst index 423a9379c0..09fb4b2480 100644 --- a/doc/test-runner.rst +++ b/doc/test-runner.rst @@ -91,8 +91,8 @@ Bluetooth CONFIG_UHID=y -Lock debuging -------------- +Lock debugging +-------------- To catch locking related issues the following set of kernel config options may be useful: @@ -110,6 +110,21 @@ options may be useful: CONFIG_DEBUG_MUTEXES=y CONFIG_KASAN=y +BPF testing +----------- + +For BPF related tests: + +.. code-block:: + + CONFIG_BPF=y + CONFIG_BPF_SYSCALL=y + CONFIG_BPF_JIT=y + CONFIG_CGROUPS=y + CONFIG_CGROUP_BPF=y + CONFIG_DEBUG_INFO_DWARF5=y + CONFIG_DEBUG_INFO_BTF=y + EXAMPLES ======== @@ -127,6 +142,15 @@ Running a specific test of mgmt-tester $ tools/test-runner -k /pathto/bzImage -- tools/mgmt-tester -s "" +Compiling and running BPF tests +------------------------------- + +.. code-block:: + + $ ./configure --enable-testing --enable-testing-bpf=/home/me/linux/vmlinux + $ make + $ tools/test-runner -k /home/me/linux/arch/x86_64/boot/bzImage -- tools/iso-tester -s BPF + Running bluetoothctl with emulated controller --------------------------------------------- diff --git a/doc/tester.config b/doc/tester.config index 099eddc79b..70e345c526 100644 --- a/doc/tester.config +++ b/doc/tester.config @@ -57,3 +57,11 @@ CONFIG_PROVE_RCU=y CONFIG_LOCKDEP=y CONFIG_DEBUG_MUTEXES=y CONFIG_KASAN=y + +CONFIG_BPF=y +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +CONFIG_CGROUPS=y +CONFIG_CGROUP_BPF=y +CONFIG_DEBUG_INFO_DWARF5=y +CONFIG_DEBUG_INFO_BTF=y diff --git a/tools/iso-tester.c b/tools/iso-tester.c index 350775fddc..8583217309 100644 --- a/tools/iso-tester.c +++ b/tools/iso-tester.c @@ -471,12 +471,13 @@ struct test_data { uint16_t handle; uint16_t acl_handle; struct queue *io_queue; - unsigned int io_id[4]; + unsigned int io_id[5]; uint8_t client_num; int step; bool reconnect; bool suspending; struct tx_tstamp_data tx_ts; + struct tx_tstamp_data bpf_tx_ts; }; struct iso_client_data { @@ -517,6 +518,9 @@ struct iso_client_data { /* Disable BT_POLL_ERRQUEUE before enabling TX timestamping */ bool no_poll_errqueue; + + /* Enable BPF TX timestamping */ + bool bpf_ts; }; typedef bool (*iso_defer_accept_t)(struct test_data *data, GIOChannel *io, @@ -697,6 +701,13 @@ static void test_pre_setup(const void *test_data) return; } +#ifndef HAVE_BPF + if (isodata && isodata->bpf_ts) { + if (tester_pre_setup_skip_by_default()) + return; + } +#endif + data->mgmt = mgmt_new_default(); if (!data->mgmt) { tester_warn("Failed to setup management interface"); @@ -738,6 +749,9 @@ static void test_post_teardown(const void *test_data) NULL, NULL, NULL); } + tx_tstamp_teardown(&data->tx_ts); + tx_tstamp_teardown(&data->bpf_tx_ts); + hciemu_unref(data->hciemu); data->hciemu = NULL; } @@ -776,7 +790,7 @@ static void test_data_free(void *test_data) user->accept_reason = reason; \ tester_add_full(name, data, \ test_pre_setup, setup, func, NULL, \ - test_post_teardown, 2, user, test_data_free); \ + test_post_teardown, 3, user, test_data_free); \ } while (0) #define test_iso(name, data, setup, func) \ @@ -1094,6 +1108,29 @@ static const struct iso_client_data connect_send_tx_no_poll_timestamping = { .no_poll_errqueue = true, }; +static const struct iso_client_data connect_send_tx_bpf_timestamping = { + .qos = QOS_16_2_1, + .expect_err = 0, + .send = &send_16_2_1, + .so_timestamping = 0, + .repeat_send = 1, + .repeat_send_pre_ts = 2, + .bpf_ts = true, +}; + +static const struct iso_client_data connect_send_tx_bpf_sk_timestamping = { + .qos = QOS_16_2_1, + .expect_err = 0, + .send = &send_16_2_1, + .so_timestamping = (SOF_TIMESTAMPING_SOFTWARE | + SOF_TIMESTAMPING_OPT_ID | + SOF_TIMESTAMPING_TX_SOFTWARE | + SOF_TIMESTAMPING_TX_COMPLETION), + .repeat_send = 1, + .repeat_send_pre_ts = 2, + .bpf_ts = true, +}; + static const struct iso_client_data listen_16_2_1_recv = { .qos = QOS_16_2_1, .expect_err = 0, @@ -2254,6 +2291,24 @@ static gboolean iso_fail_errqueue(GIOChannel *io, GIOCondition cond, return FALSE; } +static gboolean iso_bpf_io(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct test_data *data = user_data; + int err; + + err = tx_tstamp_bpf_process(&data->bpf_tx_ts, &data->step); + if (err > 0) + return TRUE; + else if (err) + tester_test_failed(); + else if (!data->step) + tester_test_passed(); + + data->io_id[4] = 0; + return FALSE; +} + static gboolean iso_timer_errqueue(gpointer user_data) { struct test_data *data = user_data; @@ -2281,18 +2336,40 @@ static void iso_tx_timestamping(struct test_data *data, GIOChannel *io) int err; unsigned int count; - if (!(isodata->so_timestamping & TS_TX_RECORD_MASK)) + if (!(isodata->so_timestamping & TS_TX_RECORD_MASK) && !isodata->bpf_ts) return; tester_print("Enabling TX timestamping"); - tx_tstamp_init(&data->tx_ts, isodata->so_timestamping, false); + tx_tstamp_init(&data->tx_ts, isodata->so_timestamping, false, false); + tx_tstamp_init(&data->bpf_tx_ts, isodata->so_timestamping, false, true); - for (count = 0; count < isodata->repeat_send + 1; ++count) + for (count = 0; count < isodata->repeat_send + 1; ++count) { data->step += tx_tstamp_expect(&data->tx_ts, 0); + if (isodata->bpf_ts) + data->step += tx_tstamp_expect(&data->bpf_tx_ts, 0); + } sk = g_io_channel_unix_get_fd(io); + if (isodata->bpf_ts) { + GIOChannel *bpf_io; + + err = tx_tstamp_bpf_start(&data->bpf_tx_ts, sk); + if (err < 0) { + tester_warn("BPF timestamping failed: %s (%d)", + strerror(-err), err); + tester_test_failed(); + return; + } + + bpf_io = g_io_channel_unix_new(err); + data->io_id[4] = g_io_add_watch(bpf_io, + G_IO_IN | G_IO_ERR | G_IO_HUP, + iso_bpf_io, data); + g_io_channel_unref(bpf_io); + } + if (isodata->no_poll_errqueue) { uint32_t flag = 0; @@ -2393,6 +2470,8 @@ static void iso_send(struct test_data *data, GIOChannel *io) for (count = 0; count < isodata->repeat_send + 1; ++count) iso_send_data(data, io); + g_io_channel_set_close_on_unref(io, FALSE); + if (isodata->bcast) { tester_test_passed(); return; @@ -3647,6 +3726,14 @@ int main(int argc, char *argv[]) &connect_send_tx_no_poll_timestamping, setup_powered, test_connect); + /* Test TX timestamping using BPF */ + test_iso("ISO Send - TX BPF Timestamping", + &connect_send_tx_bpf_timestamping, setup_powered, + test_connect); + test_iso("ISO Send - TX BPF + Socket Timestamping", + &connect_send_tx_bpf_sk_timestamping, setup_powered, + test_connect); + test_iso("ISO Receive - Success", &listen_16_2_1_recv, setup_powered, test_listen); diff --git a/tools/l2cap-tester.c b/tools/l2cap-tester.c index 41ef625783..350823a01b 100644 --- a/tools/l2cap-tester.c +++ b/tools/l2cap-tester.c @@ -1382,7 +1382,7 @@ static void l2cap_tx_timestamping(struct test_data *data, GIOChannel *io) tester_print("Enabling TX timestamping"); tx_tstamp_init(&data->tx_ts, l2data->so_timestamping, - l2data->sock_type == SOCK_STREAM); + l2data->sock_type == SOCK_STREAM, false); for (count = 0; count < l2data->repeat_send + 1; ++count) data->step += tx_tstamp_expect(&data->tx_ts, l2data->data_len); diff --git a/tools/sco-tester.c b/tools/sco-tester.c index 650f8bab3d..0b234b37ba 100644 --- a/tools/sco-tester.c +++ b/tools/sco-tester.c @@ -767,7 +767,7 @@ static void sco_tx_timestamping(struct test_data *data, GIOChannel *io) tester_print("Enabling TX timestamping"); - tx_tstamp_init(&data->tx_ts, scodata->so_timestamping, false); + tx_tstamp_init(&data->tx_ts, scodata->so_timestamping, false, false); for (count = 0; count < scodata->repeat_send + 1; ++count) data->step += tx_tstamp_expect(&data->tx_ts, 0); diff --git a/tools/test-runner.c b/tools/test-runner.c index 1d770330ce..84c0f90adc 100644 --- a/tools/test-runner.c +++ b/tools/test-runner.c @@ -127,6 +127,7 @@ static const struct { { "tmpfs", "/run", "mode=0755", MS_NOSUID|MS_NODEV|MS_STRICTATIME }, { "tmpfs", "/tmp", NULL, 0 }, { "debugfs", "/sys/kernel/debug", NULL, 0 }, + { "cgroup2", "/sys/fs/cgroup", NULL, 0 }, { } }; diff --git a/tools/tester-bpf.c b/tools/tester-bpf.c new file mode 100644 index 0000000000..dcea6cc879 --- /dev/null +++ b/tools/tester-bpf.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2025 Pauli Virtanen + * + */ + +#include "vmlinux.h" + +#include +#include +#include + +#ifndef AF_BLUETOOTH +#define AF_BLUETOOTH 31 +#endif + +#ifndef SOL_SOCKET +#define SOL_SOCKET 1 +#endif + +#include "tester-bpf.h" + +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 256 * 1024); +} tx_tstamp_events SEC(".maps"); + +static inline void tx_tstamp_event_emit(__u32 type, __u32 tskey) +{ + struct tx_tstamp_event *event; + + event = bpf_ringbuf_reserve(&tx_tstamp_events, sizeof(*event), 0); + if (!event) + return; + + event->type = type; + event->id = tskey; + event->nsec = bpf_ktime_get_ns(); + + bpf_ringbuf_submit(event, 0); +} + +SEC("sockops") +int skops_sockopt(struct bpf_sock_ops *skops) +{ + struct bpf_sock *bpf_sk = skops->sk; + struct bpf_sock_ops_kern *skops_kern; + struct skb_shared_info *shinfo; + const struct sk_buff *skb; + + if (!bpf_sk) + return 1; + + if (skops->family != AF_BLUETOOTH) + return 1; + + skops_kern = bpf_cast_to_kern_ctx(skops); + skb = skops_kern->skb; + shinfo = bpf_core_cast(skb->head + skb->end, struct skb_shared_info); + + switch (skops->op) { + case BPF_SOCK_OPS_TSTAMP_SENDMSG_CB: + bpf_sock_ops_enable_tx_tstamp(skops_kern, 0); + break; + case BPF_SOCK_OPS_TSTAMP_SCHED_CB: + tx_tstamp_event_emit(SCM_TSTAMP_SCHED, shinfo->tskey); + break; + case BPF_SOCK_OPS_TSTAMP_SND_SW_CB: + tx_tstamp_event_emit(SCM_TSTAMP_SND, shinfo->tskey); + break; + case BPF_SOCK_OPS_TSTAMP_ACK_CB: + tx_tstamp_event_emit(SCM_TSTAMP_ACK, shinfo->tskey); + break; + case BPF_SOCK_OPS_TSTAMP_COMPLETION_CB: + tx_tstamp_event_emit(SCM_TSTAMP_COMPLETION, shinfo->tskey); + break; + } + + return 1; +} + +SEC("cgroup/setsockopt") +int _setsockopt(struct bpf_sockopt *ctx) +{ + if (ctx->level == SOL_CUSTOM_TESTER) { + int flag = SK_BPF_CB_TX_TIMESTAMPING; + + bpf_setsockopt(ctx->sk, SOL_SOCKET, + SK_BPF_CB_FLAGS, &flag, sizeof(flag)); + + ctx->optlen = -1; + return 1; + } + + return 1; +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/tester-bpf.h b/tools/tester-bpf.h new file mode 100644 index 0000000000..1b3d06bc80 --- /dev/null +++ b/tools/tester-bpf.h @@ -0,0 +1,7 @@ +struct tx_tstamp_event { + __u32 type; + __u32 id; + __u64 nsec; +}; + +#define SOL_CUSTOM_TESTER 0x89abcdef diff --git a/tools/tester.h b/tools/tester.h index 4e7d7226be..16816fb6e0 100644 --- a/tools/tester.h +++ b/tools/tester.h @@ -11,13 +11,22 @@ #include #include #include +#include +#include #include #include #include #include -#define SEC_NSEC(_t) ((_t) * 1000000000LL) +#ifdef HAVE_BPF +#include +#include +#include "tester-bpf.h" +#include "tester-skel.h" +#endif + +#define SEC_NSEC(_t) ((_t) * 1000000000ULL) #define TS_NSEC(_ts) (SEC_NSEC((_ts)->tv_sec) + (_ts)->tv_nsec) #if !HAVE_DECL_SOF_TIMESTAMPING_TX_COMPLETION @@ -39,16 +48,24 @@ struct tx_tstamp_data { unsigned int sent; uint32_t so_timestamping; bool stream; + bool bpf; +#ifdef HAVE_BPF + struct tester_bpf *skel; + struct ring_buffer *buf; + int cgroup_fd; + int bpf_err; +#endif }; static inline void tx_tstamp_init(struct tx_tstamp_data *data, - uint32_t so_timestamping, bool stream) + uint32_t so_timestamping, bool stream, bool bpf) { memset(data, 0, sizeof(*data)); memset(data->expect, 0xff, sizeof(data->expect)); data->so_timestamping = so_timestamping; data->stream = stream; + data->bpf = bpf; } static inline int tx_tstamp_expect(struct tx_tstamp_data *data, size_t len) @@ -59,6 +76,21 @@ static inline int tx_tstamp_expect(struct tx_tstamp_data *data, size_t len) if (data->stream && len) data->sent += len - 1; + if (data->bpf) { + bool have_tskey = + data->so_timestamping & SOF_TIMESTAMPING_OPT_ID && + data->so_timestamping & SOF_TIMESTAMPING_TX_RECORD_MASK; + + g_assert(pos + 2 <= ARRAY_SIZE(data->expect)); + data->expect[pos].type = SCM_TSTAMP_SND; + data->expect[pos].id = have_tskey ? data->sent : 0; + pos++; + data->expect[pos].type = SCM_TSTAMP_COMPLETION; + data->expect[pos].id = have_tskey ? data->sent : 0; + pos++; + goto done; + } + if (data->so_timestamping & SOF_TIMESTAMPING_TX_SCHED) { g_assert(pos < ARRAY_SIZE(data->expect)); data->expect[pos].type = SCM_TSTAMP_SCHED; @@ -80,6 +112,7 @@ static inline int tx_tstamp_expect(struct tx_tstamp_data *data, size_t len) pos++; } +done: if (!data->stream || len) data->sent++; @@ -88,6 +121,51 @@ static inline int tx_tstamp_expect(struct tx_tstamp_data *data, size_t len) return steps; } +static inline int tx_tstamp_validate(struct tx_tstamp_data *data, + const char *source, uint32_t type, uint32_t id, + uint64_t nsec, uint64_t now) +{ + unsigned int i; + + if (now < nsec || now > nsec + SEC_NSEC(10)) { + tester_warn("nonsense in timestamp"); + return -EINVAL; + } + + if (data->pos >= data->count) { + tester_warn("Too many timestamps"); + return -EINVAL; + } + + /* Find first unreceived timestamp of the right type */ + for (i = 0; i < data->count; ++i) { + if (data->expect[i].type >= 0xffff) + continue; + + if (type == data->expect[i].type) { + data->expect[i].type = 0xffff; + break; + } + } + if (i == data->count) { + tester_warn("Bad timestamp type %u", type); + return -EINVAL; + } + + if ((data->so_timestamping & SOF_TIMESTAMPING_OPT_ID || data->bpf) && + id != data->expect[i].id) { + tester_warn("Bad timestamp id %u", id); + return -EINVAL; + } + + tester_print("Got valid %s TX timestamp %u (type %u, id %u)", + source, i, type, id); + + ++data->pos; + + return data->count - data->pos; +} + static inline int tx_tstamp_recv(struct tx_tstamp_data *data, int sk, int len) { unsigned char control[512]; @@ -99,7 +177,6 @@ static inline int tx_tstamp_recv(struct tx_tstamp_data *data, int sk, int len) struct scm_timestamping *tss = NULL; struct sock_extended_err *serr = NULL; struct timespec now; - unsigned int i; iov.iov_base = buf; iov.iov_len = sizeof(buf); @@ -159,42 +236,147 @@ static inline int tx_tstamp_recv(struct tx_tstamp_data *data, int sk, int len) clock_gettime(CLOCK_REALTIME, &now); - if (TS_NSEC(&now) < TS_NSEC(tss->ts) || - TS_NSEC(&now) > TS_NSEC(tss->ts) + SEC_NSEC(10)) { - tester_warn("nonsense in timestamp"); - return -EINVAL; + return tx_tstamp_validate(data, "socket", serr->ee_info, serr->ee_data, + TS_NSEC(tss->ts), TS_NSEC(&now)); +} + + +#ifdef HAVE_BPF + +static inline int tx_tstamp_event_handler(void *ctx, void *buf, size_t size) +{ + struct tx_tstamp_data *data = ctx; + struct tx_tstamp_event *event = buf; + struct timespec now; + + if (size < sizeof(*event)) { + tester_warn("Bad BPF event"); + return -EIO; } - if (data->pos >= data->count) { - tester_warn("Too many timestamps"); - return -EINVAL; + clock_gettime(CLOCK_MONOTONIC, &now); + + data->bpf_err = tx_tstamp_validate(data, "BPF", event->type, event->id, + event->nsec, TS_NSEC(&now)); + return data->bpf_err; +} + +static inline int tx_tstamp_bpf_start(struct tx_tstamp_data *data, int sk) +{ + int flag; + + data->cgroup_fd = open("/sys/fs/cgroup", O_RDONLY); + if (data->cgroup_fd < 0) { + tester_warn("opening cgroup failed"); + goto fail; } - /* Find first unreceived timestamp of the right type */ - for (i = 0; i < data->count; ++i) { - if (data->expect[i].type >= 0xffff) - continue; + data->skel = tester_bpf__open_and_load(); + if (!data->skel) + goto fail; - if (serr->ee_info == data->expect[i].type) { - data->expect[i].type = 0xffff; - break; - } + data->buf = ring_buffer__new( + bpf_map__fd(data->skel->maps.tx_tstamp_events), + tx_tstamp_event_handler, data, NULL); + if (!data->buf) { + tester_warn("ringbuffer failed"); + goto fail; } - if (i == data->count) { - tester_warn("Bad timestamp type %u", serr->ee_info); - return -EINVAL; + + if (tester_bpf__attach(data->skel)) { + tester_warn("attach failed"); + goto fail; } - if ((data->so_timestamping & SOF_TIMESTAMPING_OPT_ID) && - serr->ee_data != data->expect[i].id) { - tester_warn("Bad timestamp id %u", serr->ee_data); - return -EINVAL; + data->skel->links.skops_sockopt = + bpf_program__attach_cgroup(data->skel->progs.skops_sockopt, + data->cgroup_fd); + if (!data->skel->links.skops_sockopt) { + tester_warn("BPF sockops attach cgroup failed"); + goto fail; } - tester_print("Got valid TX timestamp %u (type %u, id %u)", i, - serr->ee_info, serr->ee_data); + data->skel->links._setsockopt = + bpf_program__attach_cgroup(data->skel->progs._setsockopt, + data->cgroup_fd); + if (!data->skel->links._setsockopt) { + tester_warn("BPF setsockopt attach cgroup failed"); + goto fail; + } - ++data->pos; + flag = 0; + if (setsockopt(sk, SOL_CUSTOM_TESTER, 0, &flag, sizeof(flag))) { + tester_warn("BPF setsockopt failed"); + goto fail; + } - return data->count - data->pos; + tester_print("BPF test program attached"); + return ring_buffer__epoll_fd(data->buf); + +fail: + if (data->buf) + ring_buffer__free(data->buf); + if (data->skel) + tester_bpf__destroy(data->skel); + if (data->cgroup_fd > 0) + close(data->cgroup_fd); + data->buf = NULL; + data->skel = NULL; + data->cgroup_fd = 0; + return -EIO; +} + +static inline int tx_tstamp_bpf_process(struct tx_tstamp_data *data, int *step) +{ + int err; + + err = ring_buffer__consume(data->buf); + if (err < 0) { + data->bpf_err = err; + } else if (step) { + if (*step >= err) + *step -= err; + else + data->bpf_err = -E2BIG; + } + + return data->bpf_err; } + +static inline void tx_tstamp_teardown(struct tx_tstamp_data *data) +{ + if (data->skel) + tester_bpf__detach(data->skel); + if (data->cgroup_fd > 0) + close(data->cgroup_fd); + if (data->buf) + ring_buffer__free(data->buf); + if (data->skel) { + tester_bpf__destroy(data->skel); + tester_print("BPF test program removed"); + } + + data->buf = NULL; + data->skel = NULL; + data->cgroup_fd = 0; +} + +#else + +static inline int tx_tstamp_bpf_start(struct tx_tstamp_data *data, int sk) +{ + tester_warn("Tester compiled without BPF"); + return -EOPNOTSUPP; +} + +static inline int tx_tstamp_bpf_process(struct tx_tstamp_data *data, int *step) +{ + return false; +} + +static inline void tx_tstamp_teardown(struct tx_tstamp_data *data) +{ +} + +#endif +