From: Bastien Nocera <hadess@hadess.net>
To: Pauli Virtanen <pav@iki.fi>, linux-bluetooth@vger.kernel.org
Subject: Re: [PATCH BlueZ v3 09/20] test-runner: use virtio-serial for implementing -u device forwarding
Date: Mon, 30 Mar 2026 14:30:52 +0200 [thread overview]
Message-ID: <e0edff5ac18ef0181ff9fc7a009d21fb10fbfb54.camel@hadess.net> (raw)
In-Reply-To: <34a731c7f20b905dc4b60e66a8c31e5bf3284017.1774214693.git.pav@iki.fi>
On Sun, 2026-03-22 at 23:29 +0200, Pauli Virtanen wrote:
> Using pci-serial to forward eg. btvirt sockets is unreliable, as qemu
> or
> kernel seems to be sometimes dropping part of the sent data or insert
> spurious \0 bytes, leading to sporadic errors like:
>
> kernel: Bluetooth: hci0: command 0x0c52 tx timeout
> kernel: Bluetooth: hci0: Opcode 0x0c52 failed: -110
> btvirt: packet error, unknown type: 0
>
> This appears to occur most often when host system is under load, e.g.
> due to multiple test-runners running at the same time. The problem
> is
> not specific to btvirt, but seems to be in the qemu serial device
> layer
> vs. kernel interaction.
>
> Change test-runner to use virtserialport to forward the btvirt
> connection inside the VM, as virtio-serial doesn't appear to have
> these
> problems.
>
> Since it's not a TTY device, we have to do vport <-> tty-with-hci-
> ldisc
> forwarding of the data in test-runner, so this becomes a bit more
> involved.
> ---
> Makefile.tools | 2 +
> configure.ac | 9 ++
> tools/test-runner.c | 300 +++++++++++++++++++++++++++++++++---------
> --
> 3 files changed, 241 insertions(+), 70 deletions(-)
>
> diff --git a/Makefile.tools b/Makefile.tools
> index f6de2e685..6e30a535f 100644
> --- a/Makefile.tools
> +++ b/Makefile.tools
> @@ -321,6 +321,8 @@ tools_btgatt_server_SOURCES = tools/btgatt-
> server.c src/uuid-helper.c
> tools_btgatt_server_LDADD = src/libshared-mainloop.la \
> lib/libbluetooth-
> internal.la
>
> +tools_test_runner_LDADD = $(OPENPTY_LIBS)
> +
> tools_rctest_LDADD = lib/libbluetooth-internal.la
>
> tools_l2test_LDADD = lib/libbluetooth-internal.la
> diff --git a/configure.ac b/configure.ac
> index 52de7d665..3bc1f5c44 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -251,6 +251,15 @@ AC_ARG_ENABLE(tools, AS_HELP_STRING([--disable-
> tools],
> [disable Bluetooth tools]),
> [enable_tools=${enableval}])
> AM_CONDITIONAL(TOOLS, test "${enable_tools}" != "no")
>
> +openpty_libs=
> +if (test "${enable_tools}" != "no"); then
> + AC_CHECK_FUNCS([openpty], [openpty_libs=],
> + [AC_CHECK_LIB([util], [openpty], [openpty_libs=-lutil],
> + [AC_CHECK_LIB([bsd], [openpty], [openpty_libs=-lbsd],
> + [AC_MSG_ERROR([openpty not found])])])])
Why check for -lbsd? The openpty(3) man page just says "-lutil". Is
this maybe copy/pasted from software that can also run on BSD?
> +fi
> +AC_SUBST(OPENPTY_LIBS, [${openpty_libs}])
> +
> AC_ARG_ENABLE(monitor, AS_HELP_STRING([--disable-monitor],
> [disable Bluetooth monitor]),
> [enable_monitor=${enableval}])
> AM_CONDITIONAL(MONITOR, test "${enable_monitor}" != "no")
> diff --git a/tools/test-runner.c b/tools/test-runner.c
> index b3e0b0cfe..576313b79 100644
> --- a/tools/test-runner.c
> +++ b/tools/test-runner.c
> @@ -24,6 +24,9 @@
> #include <getopt.h>
> #include <poll.h>
> #include <limits.h>
> +#include <dirent.h>
> +#include <pty.h>
> +#include <stdint.h>
> #include <sys/wait.h>
> #include <sys/stat.h>
> #include <sys/types.h>
> @@ -306,7 +309,7 @@ static void start_qemu(void)
> testargs);
>
> argv = alloca(sizeof(qemu_argv) +
> - (sizeof(char *) * (6 + (num_devs * 4))) +
> + (sizeof(char *) * (8 + (num_devs * 4))) +
> (sizeof(char *) * (usb_dev ? 4 : 0)) +
> (sizeof(char *) * num_extra_opts));
> memcpy(argv, qemu_argv, sizeof(qemu_argv));
> @@ -330,14 +333,17 @@ static void start_qemu(void)
> argv[pos++] = "-append";
> argv[pos++] = (char *) cmdline;
>
> + argv[pos++] = "-device";
> + argv[pos++] = "virtio-serial";
> +
> for (i = 0; i < num_devs; i++) {
> char *chrdev, *serdev;
>
> chrdev = alloca(48 + strlen(device_path));
> sprintf(chrdev, "socket,path=%s,id=bt%d",
> device_path, i);
>
> - serdev = alloca(48);
> - sprintf(serdev, "pci-serial,chardev=bt%d", i);
> + serdev = alloca(64);
> + sprintf(serdev,
> "virtserialport,chardev=bt%d,name=bt.%d", i, i);
>
> argv[pos++] = "-chardev";
> argv[pos++] = chrdev;
> @@ -360,65 +366,12 @@ static void start_qemu(void)
> execve(argv[0], argv, qemu_envp);
> }
>
> -static int open_serial(const char *path)
> -{
> - struct termios ti;
> - int fd, saved_ldisc, ldisc = N_HCI;
> -
> - fd = open(path, O_RDWR | O_NOCTTY);
> - if (fd < 0) {
> - perror("Failed to open serial port");
> - return -1;
> - }
> -
> - if (tcflush(fd, TCIOFLUSH) < 0) {
> - perror("Failed to flush serial port");
> - close(fd);
> - return -1;
> - }
> -
> - if (ioctl(fd, TIOCGETD, &saved_ldisc) < 0) {
> - perror("Failed get serial line discipline");
> - close(fd);
> - return -1;
> - }
> -
> - /* Switch TTY to raw mode */
> - memset(&ti, 0, sizeof(ti));
> - cfmakeraw(&ti);
> -
> - ti.c_cflag |= (B115200 | CLOCAL | CREAD);
> -
> - /* Set flow control */
> - ti.c_cflag |= CRTSCTS;
> -
> - if (tcsetattr(fd, TCSANOW, &ti) < 0) {
> - perror("Failed to set serial port settings");
> - close(fd);
> - return -1;
> - }
> -
> - if (ioctl(fd, TIOCSETD, &ldisc) < 0) {
> - perror("Failed set serial line discipline");
> - close(fd);
> - return -1;
> - }
> -
> - printf("Switched line discipline from %d to %d\n",
> saved_ldisc, ldisc);
> -
> - return fd;
> -}
> -
> -static int attach_proto(const char *path, unsigned int proto,
> +static int attach_proto(int fd, unsigned int proto,
> unsigned int
> mandatory_flags,
> unsigned int optional_flags)
> {
> unsigned int flags = mandatory_flags | optional_flags;
> - int fd, dev_id;
> -
> - fd = open_serial(path);
> - if (fd < 0)
> - return -1;
> + int dev_id;
>
> if (ioctl(fd, HCIUARTSETFLAGS, flags) < 0) {
> if (errno == EINVAL) {
> @@ -895,13 +848,222 @@ static int start_audio_server(pid_t pids[2])
> return 0;
> }
>
> +static bool find_attach_dev(char path[PATH_MAX])
> +{
> + const char *vport_path = "/sys/class/virtio-ports";
> + struct dirent *entry;
> + DIR *dir;
> +
> + dir = opendir(vport_path);
> + if (!dir)
> + return false;
> +
> + while ((entry = readdir(dir)) != NULL) {
> + FILE *f;
> + char buf[64];
> + size_t size;
> +
> + snprintf(path, PATH_MAX, "%s/%s/name", vport_path,
> + entr
> y->d_name);
> + f = fopen(path, "r");
> + if (!f)
> + continue;
> +
> + size = fread(buf, 1, sizeof(buf) - 1, f);
> + buf[size] = 0;
> +
> + fclose(f);
> +
> + if (strncmp(buf, "bt.", 3) == 0) {
> + snprintf(path, PATH_MAX, "/dev/%s", entry-
> >d_name);
> + closedir(dir);
> + return true;
> + }
> + }
> +
> + closedir(dir);
> + return false;
> +}
> +
> +static void copy_fd_bidi(int src, int dst)
> +{
> + fd_set rfds, wfds;
> + int fd[2] = { src, dst };
> + uint8_t buf[2][4096];
> + size_t size[2] = { 0, 0 };
> + size_t pos[2] = { 0, 0 };
> + int i, ret;
> +
> + /* Simple copying of data src <-> dst to both directions */
> +
> + for (i = 0; i < 2; ++i) {
> + int flags = fcntl(fd[i], F_GETFL);
> +
> + if (fcntl(fd[i], F_SETFL, flags | O_NONBLOCK) < 0) {
> + perror("fcntl");
> + goto error;
> + }
> + }
> +
> + while (1) {
> + FD_ZERO(&rfds);
> + FD_ZERO(&wfds);
> +
> + for (i = 0; i < 2; ++i) {
> + if (size[i])
> + FD_SET(fd[i], &wfds);
> + else
> + FD_SET(fd[1 - i], &rfds);
> + }
> +
> + ret = select(FD_SETSIZE, &rfds, &wfds, NULL, NULL);
> + if (ret < 0) {
> + if (errno == EINTR)
> + continue;
> + perror("select");
> + goto error;
> + }
> +
> + for (i = 0; i < 2; ++i) {
> + ssize_t s;
> +
> + if (!size[i] && FD_ISSET(fd[1 - i], &rfds))
> {
> + s = read(fd[1 - i], buf[i],
> sizeof(buf[i]));
> + if (s >= 0) {
> + size[i] = s;
> + pos[i] = 0;
> + } else if (errno == EINTR) {
> + /* ok */
> + } else {
> + perror("read");
> + goto error;
> + }
> +
> + }
> +
> + if (size[i]) {
> + s = write(fd[i], buf[i] + pos[i],
> size[i]);
> + if (s >= 0) {
> + size[i] -= s;
> + pos[i] += s;
> + } else if (errno == EINTR || errno
> == EAGAIN
> + || errno ==
> EWOULDBLOCK) {
> + /* ok */
> + } else {
> + perror("write");
> + goto error;
> + }
> + }
> + }
> + }
> + return;
> +
> +error:
> + fprintf(stderr, "Bluetooth controller forward terminated
> with error\n");
> + exit(1);
> +}
> +
> +static int start_controller_forward(const char *path, pid_t
> *controller_pid)
> +{
> + struct termios ti;
> + pid_t pid;
> + int src = -1, dst = -1, fd = -1;
> + int ret, saved_ldisc, ldisc = N_HCI;
> +
> + /* virtio-serial ports cannot be used for HCI line disciple,
> so
> + * openpty() serial device and forward data to/from it.
> + */
> +
> + src = open(path, O_RDWR);
> + if (src < 0)
> + goto error;
> +
> + /* Raw mode TTY */
> + memset(&ti, 0, sizeof(ti));
> + cfmakeraw(&ti);
> + ti.c_cflag |= B115200 | CLOCAL | CREAD;
> +
> + /* With flow control */
> + ti.c_cflag |= CRTSCTS;
> +
> + ret = openpty(&dst, &fd, NULL, &ti, NULL);
> + if (ret < 0)
> + goto error;
> +
> + if (ioctl(fd, TIOCGETD, &saved_ldisc) < 0) {
> + perror("Failed get serial line discipline");
> + goto error;
> + }
> +
> + if (ioctl(fd, TIOCSETD, &ldisc) < 0) {
> + perror("Failed set serial line discipline");
> + goto error;
> + }
> +
> + printf("Switched line discipline from %d to %d\n",
> saved_ldisc, ldisc);
> +
> + pid = fork();
> + if (pid < 0) {
> + perror("Failed to fork new process");
> + goto error;
> + } else if (pid == 0) {
> + close(fd);
> + copy_fd_bidi(src, dst);
> + exit(0);
> + }
> +
> + *controller_pid = pid;
> +
> + close(src);
> + close(dst);
> + return fd;
> +
> +error:
> + if (src >= 0)
> + close(src);
> + if (dst >= 0)
> + close(dst);
> + if (fd >= 0)
> + close(fd);
> + return -1;
> +}
> +
> +static int attach_controller(pid_t *controller_pid)
> +{
> + unsigned int basic_flags, extra_flags;
> + char path[PATH_MAX];
> + int fd;
> +
> + *controller_pid = -1;
> +
> + if (!find_attach_dev(path)) {
> + printf("Failed to find Bluetooth controller
> virtio\n");
> + return -1;
> + }
> +
> + printf("Forwarding Bluetooth controller from %s\n", path);
> +
> + fd = start_controller_forward(path, controller_pid);
> + if (fd < 0) {
> + printf("Failed to forward Bluetooth controller\n");
> + return -1;
> + }
> +
> + basic_flags = (1 << HCI_UART_RESET_ON_INIT);
> + extra_flags = (1 << HCI_UART_VND_DETECT);
> +
> + printf("Attaching Bluetooth controller\n");
> +
> + return attach_proto(fd, HCI_UART_H4, basic_flags,
> extra_flags);
> +}
> +
> static void run_command(char *cmdname, char *home)
> {
> char *argv[9], *envp[3];
> int pos = 0, idx = 0;
> int serial_fd;
> pid_t pid, dbus_pid, daemon_pid, monitor_pid, emulator_pid,
> - dbus_session_pid, audio_pid[2];
> + dbus_session_pid, audio_pid[2], controller_pid;
> int i;
>
> if (!home) {
> @@ -910,18 +1072,11 @@ static void run_command(char *cmdname, char
> *home)
> }
>
> if (num_devs) {
> - const char *node = "/dev/ttyS1";
> - unsigned int basic_flags, extra_flags;
> -
> - printf("Attaching BR/EDR controller to %s\n", node);
> -
> - basic_flags = (1 << HCI_UART_RESET_ON_INIT);
> - extra_flags = (1 << HCI_UART_VND_DETECT);
> -
> - serial_fd = attach_proto(node, HCI_UART_H4,
> basic_flags,
> -
> extra_flags);
> - } else
> + serial_fd = attach_controller(&controller_pid);
> + } else {
> serial_fd = -1;
> + controller_pid = -1;
> + }
>
> if (start_dbus) {
> create_dbus_system_conf();
> @@ -1063,6 +1218,11 @@ start_next:
> monitor_pid = -1;
> }
>
> + if (corpse == controller_pid) {
> + printf("Controller terminated\n");
> + controller_pid = -1;
> + }
> +
> for (i = 0; i < 2; ++i) {
> if (corpse == audio_pid[i]) {
> printf("Audio server %d
> terminated\n", i);
next prev parent reply other threads:[~2026-03-30 12:30 UTC|newest]
Thread overview: 31+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-22 21:29 [PATCH BlueZ v3 00/20] Functional/integration testing Pauli Virtanen
2026-03-22 21:29 ` [PATCH BlueZ v3 01/20] emulator: btvirt: check pkt lengths, don't get stuck on malformed Pauli Virtanen
2026-03-22 22:38 ` Functional/integration testing bluez.test.bot
2026-03-22 21:29 ` [PATCH BlueZ v3 02/20] emulator: btvirt: allow specifying where server unix sockets are made Pauli Virtanen
2026-03-30 12:30 ` Bastien Nocera
2026-03-30 14:01 ` Luiz Augusto von Dentz
2026-03-30 14:33 ` Bastien Nocera
2026-03-22 21:29 ` [PATCH BlueZ v3 03/20] emulator: btvirt: support SCO data packets Pauli Virtanen
2026-03-22 21:29 ` [PATCH BlueZ v3 04/20] emulator: btdev: clear more state on Reset Pauli Virtanen
2026-03-22 21:29 ` [PATCH BlueZ v3 05/20] test-runner: enable path argument for --unix Pauli Virtanen
2026-03-22 21:29 ` [PATCH BlueZ v3 06/20] test-runner: Add -o/--option option Pauli Virtanen
2026-03-22 21:29 ` [PATCH BlueZ v3 07/20] test-runner: allow source tree root for -k Pauli Virtanen
2026-03-22 21:29 ` [PATCH BlueZ v3 08/20] doc: enable CONFIG_VIRTIO_CONSOLE in tester config Pauli Virtanen
2026-03-22 21:29 ` [PATCH BlueZ v3 09/20] test-runner: use virtio-serial for implementing -u device forwarding Pauli Virtanen
2026-03-24 20:24 ` Luiz Augusto von Dentz
2026-03-24 21:00 ` Pauli Virtanen
2026-03-30 12:30 ` Bastien Nocera [this message]
2026-03-22 21:29 ` [PATCH BlueZ v3 10/20] doc: enable KVM paravirtualization & clock support in tester kernel config Pauli Virtanen
2026-03-22 21:30 ` [PATCH BlueZ v3 11/20] doc: add functional/integration testing documentation Pauli Virtanen
2026-03-22 21:30 ` [PATCH BlueZ v3 12/20] test: add functional/integration testing framework Pauli Virtanen
2026-03-22 21:30 ` [PATCH BlueZ v3 13/20] test: functional: add Pipewire-using audio streaming tests Pauli Virtanen
2026-03-22 21:30 ` [PATCH BlueZ v3 14/20] test: functional: add --btmon option to start btmon Pauli Virtanen
2026-03-22 21:31 ` [PATCH BlueZ v3 15/20] build: add functional testing target Pauli Virtanen
2026-03-22 21:31 ` [PATCH BlueZ v3 16/20] test: functional: impose Python code formatting Pauli Virtanen
2026-03-22 21:31 ` [PATCH BlueZ v3 17/20] test: functional: add option for building kernel image first Pauli Virtanen
2026-03-22 21:31 ` [PATCH BlueZ v3 18/20] test: functional: add custom Agent1 implementation for testing Pauli Virtanen
2026-03-22 21:31 ` [PATCH BlueZ v3 19/20] test: functional: warn on kernel warning/bug messages Pauli Virtanen
2026-03-22 21:31 ` [PATCH BlueZ v3 20/20] test: functional: add basic obex file transfer tests Pauli Virtanen
2026-03-30 12:30 ` [PATCH BlueZ v3 00/20] Functional/integration testing Bastien Nocera
2026-03-30 12:48 ` Pauli Virtanen
2026-03-30 13:04 ` Bastien Nocera
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=e0edff5ac18ef0181ff9fc7a009d21fb10fbfb54.camel@hadess.net \
--to=hadess@hadess.net \
--cc=linux-bluetooth@vger.kernel.org \
--cc=pav@iki.fi \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox