diff mbox series

[BlueZ,v2] tools: add BPF timestamping tests

Message ID 632807049a6b64e11103b95163ffa5de8f18a4ed.1743846534.git.pav@iki.fi
State New
Headers show
Series [BlueZ,v2] tools: add BPF timestamping tests | expand

Commit Message

Pauli Virtanen April 5, 2025, 9:49 a.m. UTC
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
---

Notes:
    v2:
    - automake: nodist, BUILD_SOURCES, CLEANFILES, silence output
    - fix return type of tx_tstamp_bpf_process
    - separate timestamp tracking for socket & BPF, add test enabling both
    - match BPF tskey handling to the current plan

 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       | 264 ++++++++++++++++++++++++++++++++++++-------
 11 files changed, 531 insertions(+), 54 deletions(-)
 create mode 100644 tools/tester-bpf.c
 create mode 100644 tools/tester-bpf.h

Comments

bluez.test.bot@gmail.com April 5, 2025, 11:13 a.m. UTC | #1
This is automated email and please do not reply to this email!

Dear submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=950146

---Test result---

Test Summary:
CheckPatch                    PENDING   0.20 seconds
GitLint                       PENDING   0.19 seconds
BuildEll                      PASS      20.99 seconds
BluezMake                     PASS      1631.60 seconds
MakeCheck                     PASS      13.98 seconds
MakeDistcheck                 PASS      163.79 seconds
CheckValgrind                 PASS      224.03 seconds
CheckSmatch                   WARNING   294.82 seconds
bluezmakeextell               PASS      103.18 seconds
IncrementalBuild              PENDING   0.18 seconds
ScanBuild                     PASS      886.40 seconds

Details
##############################
Test: CheckPatch - PENDING
Desc: Run checkpatch.pl script
Output:

##############################
Test: GitLint - PENDING
Desc: Run gitlint
Output:

##############################
Test: CheckSmatch - WARNING
Desc: Run smatch tool with source
Output:
tools/sco-tester.c: note: in included file:./lib/bluetooth.h:232:15: warning: array of flexible structures./lib/bluetooth.h:237:31: warning: array of flexible structures
##############################
Test: IncrementalBuild - PENDING
Desc: Incremental build with the patches in the series
Output:



---
Regards,
Linux Bluetooth
Luiz Augusto von Dentz April 7, 2025, 1:44 p.m. UTC | #2
Hi Pauli,

On Sat, Apr 5, 2025 at 5:50 AM Pauli Virtanen <pav@iki.fi> wrote:
>
> 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
> ---
>
> Notes:
>     v2:
>     - automake: nodist, BUILD_SOURCES, CLEANFILES, silence output
>     - fix return type of tx_tstamp_bpf_process
>     - separate timestamp tracking for socket & BPF, add test enabling both
>     - match BPF tskey handling to the current plan
>
>  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       | 264 ++++++++++++++++++++++++++++++++++++-------

Might be a good idea to split into separate patches, e.g. enable BPF
build then introduce tester-bpf, test-runner followed by the new
tests.

>  11 files changed, 531 insertions(+), 54 deletions(-)
>  create mode 100644 tools/tester-bpf.c
>  create mode 100644 tools/tester-bpf.h
>
> diff --git a/Makefile.tools b/Makefile.tools
> index e60c31b1d..75bd3daaf 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 2ea727256..6f09e248f 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 <time.h>
> -               #include <linux/errqueue.h>
> -               #include <linux/net_tstamp.h>]])
> +       AC_CHECK_DECLS([SOF_TIMESTAMPING_TX_COMPLETION, SCM_TSTAMP_COMPLETION],
> +               [], [], [[#include <time.h>
> +                       #include <linux/errqueue.h>
> +                       #include <linux/net_tstamp.h>]])
> +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 423a9379c..09fb4b248 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 "<name>"
>
> +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 099eddc79..70e345c52 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 350775fdd..858321730 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 41ef62578..350823a01 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 650f8bab3..0b234b37b 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 1d770330c..84c0f90ad 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 000000000..dcea6cc87
> --- /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 <bpf/bpf_helpers.h>
> +#include <bpf/bpf_tracing.h>
> +#include <bpf/bpf_core_read.h>
> +
> +#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 000000000..1b3d06bc8
> --- /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 4e7d7226b..16816fb6e 100644
> --- a/tools/tester.h
> +++ b/tools/tester.h
> @@ -11,13 +11,22 @@
>  #include <stdlib.h>
>  #include <stdint.h>
>  #include <time.h>
> +#include <fcntl.h>
> +#include <sys/stat.h>
>  #include <sys/socket.h>
>  #include <linux/errqueue.h>
>  #include <linux/net_tstamp.h>
>
>  #include <glib.h>
>
> -#define SEC_NSEC(_t)  ((_t) * 1000000000LL)
> +#ifdef HAVE_BPF
> +#include <linux/bpf.h>
> +#include <bpf/libbpf.h>
> +#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;
> -       }
> -
> -       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 (serr->ee_info == data->expect[i].type) {
> -                       data->expect[i].type = 0xffff;
> -                       break;
> -               }
> -       }
> -       if (i == data->count) {
> -               tester_warn("Bad timestamp type %u", serr->ee_info);
> -               return -EINVAL;
> -       }
> -
> -       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;
> -       }
> -
> -       tester_print("Got valid TX timestamp %u (type %u, id %u)", i,
> -                                               serr->ee_info, serr->ee_data);
> -
> -       ++data->pos;
> -
> -       return data->count - data->pos;
> +       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;
> +       }
> +
> +       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;
> +       }
> +
> +       data->skel = tester_bpf__open_and_load();
> +       if (!data->skel)
> +               goto fail;
> +
> +       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 (tester_bpf__attach(data->skel)) {
> +               tester_warn("attach failed");
> +               goto fail;
> +       }
> +
> +       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;
> +       }
> +
> +       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;
> +       }
> +
> +       flag = 0;
> +       if (setsockopt(sk, SOL_CUSTOM_TESTER, 0, &flag, sizeof(flag))) {
> +               tester_warn("BPF setsockopt failed");
> +               goto fail;
> +       }
> +
> +       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
> +
> --
> 2.49.0
>
>
diff mbox series

Patch

diff --git a/Makefile.tools b/Makefile.tools
index e60c31b1d..75bd3daaf 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 2ea727256..6f09e248f 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 <time.h>
-		#include <linux/errqueue.h>
-		#include <linux/net_tstamp.h>]])
+	AC_CHECK_DECLS([SOF_TIMESTAMPING_TX_COMPLETION, SCM_TSTAMP_COMPLETION],
+		[], [], [[#include <time.h>
+			#include <linux/errqueue.h>
+			#include <linux/net_tstamp.h>]])
+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 423a9379c..09fb4b248 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 "<name>"
 
+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 099eddc79..70e345c52 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 350775fdd..858321730 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 41ef62578..350823a01 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 650f8bab3..0b234b37b 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 1d770330c..84c0f90ad 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 000000000..dcea6cc87
--- /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 <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_core_read.h>
+
+#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 000000000..1b3d06bc8
--- /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 4e7d7226b..16816fb6e 100644
--- a/tools/tester.h
+++ b/tools/tester.h
@@ -11,13 +11,22 @@ 
 #include <stdlib.h>
 #include <stdint.h>
 #include <time.h>
+#include <fcntl.h>
+#include <sys/stat.h>
 #include <sys/socket.h>
 #include <linux/errqueue.h>
 #include <linux/net_tstamp.h>
 
 #include <glib.h>
 
-#define SEC_NSEC(_t)  ((_t) * 1000000000LL)
+#ifdef HAVE_BPF
+#include <linux/bpf.h>
+#include <bpf/libbpf.h>
+#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;
-	}
-
-	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 (serr->ee_info == data->expect[i].type) {
-			data->expect[i].type = 0xffff;
-			break;
-		}
-	}
-	if (i == data->count) {
-		tester_warn("Bad timestamp type %u", serr->ee_info);
-		return -EINVAL;
-	}
-
-	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;
-	}
-
-	tester_print("Got valid TX timestamp %u (type %u, id %u)", i,
-						serr->ee_info, serr->ee_data);
-
-	++data->pos;
-
-	return data->count - data->pos;
+	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;
+	}
+
+	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;
+	}
+
+	data->skel = tester_bpf__open_and_load();
+	if (!data->skel)
+		goto fail;
+
+	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 (tester_bpf__attach(data->skel)) {
+		tester_warn("attach failed");
+		goto fail;
+	}
+
+	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;
+	}
+
+	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;
+	}
+
+	flag = 0;
+	if (setsockopt(sk, SOL_CUSTOM_TESTER, 0, &flag, sizeof(flag))) {
+		tester_warn("BPF setsockopt failed");
+		goto fail;
+	}
+
+	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
+