* [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel
@ 2026-10-22 15:46 Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 01/27] tools/test-runner: Do not report filesystems already mounted Luiz Augusto von Dentz
` (27 more replies)
0 siblings, 28 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
This makes test-runner run the kernel testers in a User Mode Linux guest
as well as with QEMU/KVM, and with -T on a simulated clock: btvirt is the
guest's time travel calendar, so timers and timeouts take no real time
and timing is the same on every run. btvirt can also serve controllers to
UML guests as vhost-user virtio devices sharing the calendar, e.g. for
firmware simulators.
Time travel made test races and busy loops visible, fixed first in the
series, and showed that GLib's main loop reads the clock for every source
on every iteration, a system call through the host under UML, so the
testers now run on the mainloop instead.
Wall clock time in seconds, same kernel version and machine, all tests
passing, single runs:
tester tests KVM UML UML -T UML -T
(GLib) (mainloop)
mgmt-tester 514 78.2 94.0 42.6 20.3
l2cap-tester 111 9.2 10.2 10.2 5.6
iso-tester 141 8.1 11.7 16.3 7.6
sco-tester 30 4.6 3.1 3.6 2.0
rfcomm-tester 11 3.6 1.5 2.6 1.5
smp-tester 8 3.6 1.5 1.5 1.0
ioctl-tester 28 4.1 2.1 3.1 1.5
userchan-tester 4 2.6 1.0 1.5 1.0
bnep-tester 1 2.6 1.0 1.0 1.0
doc/tester.config is split into tester.kvm.config and tester.uml.config,
both building every Bluetooth driver, so a single UML kernel can verify
the drivers and run the testers.
Usage, with a UML kernel built from doc/tester.uml.config:
tools/test-runner -T -k <linux>/linux -- tools/mgmt-tester
Luiz Augusto von Dentz (27):
tools/test-runner: Do not report filesystems already mounted
shared/tester: Time out teardown
mgmt-tester: Wait for LL Privacy setup to complete
iso-tester: Fix leaking socket watches
l2cap-tester: Fix race in LE Client - Close socket 2
iso-tester: Fix busy waiting in PAST tests
doc: Enable IPv6 in tester.config
6lowpan-tester: Find the interface of the controller
emulator: Remove -T option from btvirt
doc: Add btvirt documentation
emulator: Add vhost-user backend
emulator: Add vhost-user virtio server to btvirt
emulator: Add time travel calendar to btvirt
emulator: Add virtio console and time travel client to btvirt
tools/test-runner: Add User Mode Linux support
doc: Add virtio documentation
shared/mainloop: Add idle callbacks
shared/timeout: Run timeouts of 0 once idle
shared/tester: Remove GLib dependency
emulator/hciemu: Remove GLib dependency
tools: Build testers that do not need GLib with the mainloop
shared/mainloop: Add deferred callbacks
tools: Add socket watches for testers without GLib
tools: Run sco-tester, rfcomm-tester and 6lowpan-tester on the
mainloop
tools: Run l2cap-tester and iso-tester on the mainloop
doc/virtio: Update measurements of the testers
doc: Split tester.config for QEMU and User Mode Linux
Makefile.am | 5 +-
Makefile.tools | 47 +-
doc/btvirt.rst | 189 ++++++
doc/test-functional.rst | 4 +-
doc/test-runner.rst | 150 ++++-
doc/tester.config | 118 +---
doc/tester.kvm.config | 144 +++++
doc/tester.uml.config | 120 ++++
doc/virtio.rst | 412 +++++++++++++
emulator/hciemu.c | 122 ++--
emulator/main.c | 116 +++-
emulator/timeout.c | 102 ++++
emulator/timetravel.c | 743 ++++++++++++++++++++++++
emulator/timetravel.h | 55 ++
emulator/ttclient.c | 313 ++++++++++
emulator/ttclient.h | 50 ++
emulator/vconsole.c | 701 +++++++++++++++++++++++
emulator/vconsole.h | 26 +
emulator/vhost.c | 1018 +++++++++++++++++++++++++++++++++
emulator/vhost.h | 67 +++
emulator/virtio.c | 356 ++++++++++++
emulator/virtio.h | 20 +
src/shared/mainloop-ell.c | 12 +
src/shared/mainloop-glib.c | 12 +
src/shared/mainloop.c | 177 +++++-
src/shared/mainloop.h | 4 +
src/shared/tester.c | 306 +++++++---
src/shared/timeout-glib.c | 10 +-
src/shared/timeout-mainloop.c | 9 +-
tools/6lowpan-tester.c | 111 +++-
tools/bnep-tester.c | 7 -
tools/ioctl-tester.c | 6 +-
tools/iso-tester.c | 358 +++++++-----
tools/l2cap-tester.c | 347 +++++------
tools/mesh-tester.c | 1 -
tools/mgmt-tester.c | 204 ++++++-
tools/rfcomm-tester.c | 105 ++--
tools/sco-tester.c | 108 ++--
tools/smp-tester.c | 30 +-
tools/test-runner.c | 404 ++++++++++++-
tools/tester-io.c | 400 +++++++++++++
tools/tester-io.h | 52 ++
tools/tester.h | 8 +-
tools/userchan-tester.c | 1 -
44 files changed, 6721 insertions(+), 829 deletions(-)
create mode 100644 doc/btvirt.rst
mode change 100644 => 120000 doc/tester.config
create mode 100644 doc/tester.kvm.config
create mode 100644 doc/tester.uml.config
create mode 100644 doc/virtio.rst
create mode 100644 emulator/timeout.c
create mode 100644 emulator/timetravel.c
create mode 100644 emulator/timetravel.h
create mode 100644 emulator/ttclient.c
create mode 100644 emulator/ttclient.h
create mode 100644 emulator/vconsole.c
create mode 100644 emulator/vconsole.h
create mode 100644 emulator/vhost.c
create mode 100644 emulator/vhost.h
create mode 100644 emulator/virtio.c
create mode 100644 emulator/virtio.h
create mode 100644 tools/tester-io.c
create mode 100644 tools/tester-io.h
--
2.56.0
^ permalink raw reply [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 01/27] tools/test-runner: Do not report filesystems already mounted
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 20:02 ` Run the testers under User Mode Linux with time travel bluez.test.bot
2026-10-22 15:46 ` [PATCH BlueZ v1 02/27] shared/tester: Time out teardown Luiz Augusto von Dentz
` (26 subsequent siblings)
27 siblings, 1 reply; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
With CONFIG_DEVTMPFS_MOUNT the kernel mounts devtmpfs itself, so
mounting it again fails with EBUSY, reported as an error on every run:
Mounting devtmpfs to /dev
Failed to mount filesystem: Device or resource busy
Only report other failures.
Assisted-by: Claude:claude-opus-5-5
---
tools/test-runner.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/tools/test-runner.c b/tools/test-runner.c
index 674ce072d959..21de3a50da5e 100644
--- a/tools/test-runner.c
+++ b/tools/test-runner.c
@@ -254,11 +254,14 @@ static void prepare_sandbox(void)
printf("Mounting %s to %s\n", mount_table[i].fstype,
mount_table[i].target);
+ /* The kernel may have mounted it already, e.g. devtmpfs
+ * with CONFIG_DEVTMPFS_MOUNT.
+ */
if (mount(mount_table[i].fstype,
mount_table[i].target,
mount_table[i].fstype,
mount_table[i].flags,
- mount_table[i].options) < 0)
+ mount_table[i].options) < 0 && errno != EBUSY)
perror("Failed to mount filesystem");
}
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 02/27] shared/tester: Time out teardown
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 01/27] tools/test-runner: Do not report filesystems already mounted Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 03/27] mgmt-tester: Wait for LL Privacy setup to complete Luiz Augusto von Dentz
` (25 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
A teardown with no timeout blocks every following test if what it waits
for never happens, e.g. mgmt-tester waiting for the removal of an index
the kernel never added since the controller failed to initialize:
Set Low Energy on 6.2 - SCI Setting - test timed out
Set Low Energy on 6.2 - SCI Setting - teardown
Give teardown the timeout of the test, but at least 30 seconds, as it
may have to wait for the kernel to time out commands still sent to a
controller being removed, 2 seconds each. When it expires report the
test as timed out and do not run the remaining tests: the callbacks of
the test may still complete later, and would be taken for those of the
test running then.
Assisted-by: Claude:claude-opus-5-5
---
src/shared/tester.c | 66 ++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 62 insertions(+), 4 deletions(-)
diff --git a/src/shared/tester.c b/src/shared/tester.c
index eb2e923fd790..4065545793aa 100644
--- a/src/shared/tester.c
+++ b/src/shared/tester.c
@@ -423,6 +423,55 @@ static int tester_summarize(void)
return failed;
}
+static gboolean done_callback(gpointer user_data);
+
+static bool teardown_timeout(gpointer user_data)
+{
+ struct test_case *test = user_data;
+
+ test->timeout_id = 0;
+
+ if (!test_current || test_current->data != test)
+ return FALSE;
+
+ /* Give up waiting, e.g. for an index that was never added to be
+ * removed, rather than blocking for good. Its callbacks may still
+ * complete later, and would be taken for those of the test running
+ * then, so do not run the remaining tests.
+ */
+ test->result = TEST_RESULT_TIMED_OUT;
+ test->stage = TEST_STAGE_INVALID;
+
+ print_progress(test->name, COLOR_RED, "teardown timed out");
+ tester_warn("Not running the remaining tests");
+
+ while (test_current->next)
+ test_current = test_current->next;
+
+ g_idle_add(done_callback, test);
+
+ return FALSE;
+}
+
+/* Teardown may have to wait for the kernel to time out commands still sent
+ * to a controller being removed, 2 seconds each, so only give up on a
+ * teardown that clearly does not complete.
+ */
+#define TEARDOWN_TIMEOUT 30
+
+static void teardown_timeout_start(struct test_case *test)
+{
+ if (test->timeout_id > 0) {
+ timeout_remove(test->timeout_id);
+ test->timeout_id = 0;
+ }
+
+ if (test->timeout > 0)
+ test->timeout_id = timeout_add_seconds(
+ MAX(test->timeout, TEARDOWN_TIMEOUT),
+ teardown_timeout, test, NULL);
+}
+
static gboolean teardown_callback(gpointer user_data)
{
struct test_case *test = user_data;
@@ -430,6 +479,8 @@ static gboolean teardown_callback(gpointer user_data)
test->teardown_id = 0;
test->stage = TEST_STAGE_TEARDOWN;
+ teardown_timeout_start(test);
+
print_progress(test->name, COLOR_MAGENTA, "teardown");
test->teardown_func(test->test_data);
@@ -629,10 +680,7 @@ void tester_setup_failed(void)
test->result = TEST_RESULT_FAILED;
test->stage = TEST_STAGE_POST_TEARDOWN;
- if (test->timeout_id > 0) {
- timeout_remove(test->timeout_id);
- test->timeout_id = 0;
- }
+ teardown_timeout_start(test);
print_progress(test->name, COLOR_RED, "setup failed");
print_progress(test->name, COLOR_MAGENTA, "teardown");
@@ -745,6 +793,11 @@ void tester_post_teardown_complete(void)
if (test->stage != TEST_STAGE_POST_TEARDOWN)
return;
+ if (test->timeout_id > 0) {
+ timeout_remove(test->timeout_id);
+ test->timeout_id = 0;
+ }
+
print_progress(test->name, COLOR_MAGENTA, "teardown complete");
g_idle_add(done_callback, test);
@@ -762,6 +815,11 @@ void tester_post_teardown_failed(void)
if (test->stage != TEST_STAGE_POST_TEARDOWN)
return;
+ if (test->timeout_id > 0) {
+ timeout_remove(test->timeout_id);
+ test->timeout_id = 0;
+ }
+
print_progress(test->name, COLOR_RED, "teardown failed");
g_idle_add(done_callback, test);
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 03/27] mgmt-tester: Wait for LL Privacy setup to complete
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 01/27] tools/test-runner: Do not report filesystems already mounted Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 02/27] shared/tester: Time out teardown Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 04/27] iso-tester: Fix leaking socket watches Luiz Augusto von Dentz
` (24 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
The LL Privacy setups send Power On, Add Device, Set Device Flags and Add
Advertising without waiting for their replies, so setup may complete
while they are still pending, even twice, and the test then gets their
events, e.g. the Device Flags Changed of Add Device instead of the one
of Set Device Flags.
Make each of them a setup condition, and once all are met wait for the
controller to settle, i.e. no HCI command for 100 ms, as the kernel
keeps programming the accept and resolving lists after replying, which
a test may otherwise take for its own commands.
This shows with time travel, where computing takes no time:
LL Privacy, before: 13/27 passed
LL Privacy, after: 27/27 passed
Assisted-by: Claude:claude-opus-5-5
---
tools/mgmt-tester.c | 201 +++++++++++++++++++++++++++++++++++++++++---
1 file changed, 190 insertions(+), 11 deletions(-)
diff --git a/tools/mgmt-tester.c b/tools/mgmt-tester.c
index 631c906f3aa8..abfe57dad0c8 100644
--- a/tools/mgmt-tester.c
+++ b/tools/mgmt-tester.c
@@ -76,6 +76,8 @@ struct test_data {
struct queue *expect_hci_q;
int unmet_conditions;
int unmet_setup_conditions;
+ bool setup_settle;
+ unsigned int settle_id;
int sk;
struct simult_discovery simult;
};
@@ -119,6 +121,11 @@ static void test_post_teardown(const void *test_data)
simult_discovery_cleanup(data);
+ if (data->settle_id) {
+ timeout_remove(data->settle_id);
+ data->settle_id = 0;
+ }
+
if (data->sk >= 0)
close(data->sk);
@@ -554,6 +561,50 @@ static void test_add_setup_condition(struct test_data *data)
data->unmet_setup_conditions);
}
+/* Time without HCI commands after which the controller is considered
+ * settled, see test_setup_settle().
+ */
+#define SETTLE_TIMEOUT 100
+
+static bool settle_timeout(void *user_data)
+{
+ struct test_data *data = user_data;
+
+ data->settle_id = 0;
+ hciemu_clear_central_post_command_hooks(data->hciemu);
+
+ tester_print("Controller settled");
+ tester_setup_complete();
+
+ return false;
+}
+
+static void settle_command(uint16_t opcode, const void *param,
+ uint8_t length, void *user_data)
+{
+ struct test_data *data = user_data;
+
+ if (data->settle_id)
+ timeout_remove(data->settle_id);
+
+ data->settle_id = timeout_add(SETTLE_TIMEOUT, settle_timeout, data,
+ NULL);
+}
+
+/* The kernel may still program the controller, e.g. its accept and
+ * resolving lists, after replying to the setup commands, so wait for the
+ * controller to be idle for a while, or a test may see the setup commands
+ * instead of its own.
+ */
+static void test_setup_settle(struct test_data *data)
+{
+ tester_print("Waiting for the controller to settle");
+
+ hciemu_add_central_post_command_hook(data->hciemu, settle_command,
+ data);
+ settle_command(0, NULL, 0, data);
+}
+
static void test_setup_condition_complete(struct test_data *data)
{
data->unmet_setup_conditions--;
@@ -564,6 +615,11 @@ static void test_setup_condition_complete(struct test_data *data)
if (data->unmet_setup_conditions > 0)
return;
+ if (data->setup_settle) {
+ test_setup_settle(data);
+ return;
+ }
+
tester_setup_complete();
}
@@ -6624,6 +6680,18 @@ static void setup_powered_callback(uint8_t status, uint16_t length,
setup_bthost();
}
+/* Powered on as one of several setup commands, which completes once all
+ * of them are, not just the client setup started from here.
+ */
+static void setup_powered_condition_callback(uint8_t status, uint16_t length,
+ const void *param, void *user_data)
+{
+ setup_powered_callback(status, length, param, user_data);
+
+ if (status == MGMT_STATUS_SUCCESS)
+ test_setup_condition_complete(tester_get_data());
+}
+
static void setup_class(const void *test_data)
{
struct test_data *data = tester_get_data();
@@ -11526,6 +11594,8 @@ static void setup_add_device_callback(uint8_t status, uint16_t length,
}
tester_print("New Device is Added");
+
+ test_setup_condition_complete(tester_get_data());
}
static void setup_remove_device_callback(uint8_t status, uint16_t length,
@@ -11559,6 +11629,17 @@ static void setup_add_adv_callback(uint8_t status, uint16_t length,
rp->instance);
}
+/* Last of several setup commands, see setup_powered_condition_callback() */
+static void setup_add_advertising_condition_callback(uint8_t status,
+ uint16_t length, const void *param,
+ void *user_data)
+{
+ setup_add_advertising_callback(status, length, param, user_data);
+
+ if (status == MGMT_STATUS_SUCCESS)
+ test_setup_condition_complete(tester_get_data());
+}
+
static void setup_add_adv_callback_adv(uint8_t status, uint16_t length,
const void *param, void *user_data)
{
@@ -11582,7 +11663,7 @@ static void setup_add_adv_callback_adv(uint8_t status, uint16_t length,
mgmt_send(data->mgmt, MGMT_OP_ADD_ADVERTISING, data->mgmt_index,
sizeof(adv_param), adv_param,
- setup_add_advertising_callback,
+ setup_add_advertising_condition_callback,
NULL, NULL);
}
@@ -11591,17 +11672,22 @@ static void setup_ll_privacy_set_flags_1(const void *test_data)
struct test_data *data = tester_get_data();
unsigned char param[] = { 0x01 };
+ data->setup_settle = true;
+
mgmt_send(data->mgmt, MGMT_OP_LOAD_IRKS, data->mgmt_index,
sizeof(load_irks_le_public_param_1),
load_irks_le_public_param_1,
setup_load_irks_callback, NULL, NULL);
/* Set Powered On */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_POWERED, data->mgmt_index,
sizeof(param), param,
- setup_powered_callback, NULL, NULL);
+ setup_powered_condition_callback,
+ NULL, NULL);
/* Add Device 1 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_1),
add_device_le_public_param_1,
@@ -11617,6 +11703,8 @@ static void setup_set_device_flags_callback(uint8_t status, uint16_t length,
}
tester_print("Device Flags are set");
+
+ test_setup_condition_complete(tester_get_data());
}
static void setup_ll_privacy_add_2(const void *test_data)
@@ -11624,6 +11712,8 @@ static void setup_ll_privacy_add_2(const void *test_data)
struct test_data *data = tester_get_data();
unsigned char param[] = { 0x01 };
+ data->setup_settle = true;
+
/* Add IRKs of Device1 and Device2 */
mgmt_send(data->mgmt, MGMT_OP_LOAD_IRKS, data->mgmt_index,
sizeof(load_irks_2_devices_param),
@@ -11631,16 +11721,20 @@ static void setup_ll_privacy_add_2(const void *test_data)
setup_load_irks_callback, NULL, NULL);
/* Set Powered On */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_POWERED, data->mgmt_index,
sizeof(param), param,
- setup_powered_callback, NULL, NULL);
+ setup_powered_condition_callback,
+ NULL, NULL);
/* Add Device 1 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_1),
add_device_le_public_param_1,
setup_add_device_callback, NULL, NULL);
/* Device 1 Flags */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_DEVICE_FLAGS, data->mgmt_index,
sizeof(set_device_flags_param_1),
set_device_flags_param_1,
@@ -11653,6 +11747,8 @@ static void setup_ll_privacy_set_flags_3(const void *test_data)
struct test_data *data = tester_get_data();
unsigned char param[] = { 0x01 };
+ data->setup_settle = true;
+
/* Add IRKs of Device1 and Device2 */
mgmt_send(data->mgmt, MGMT_OP_LOAD_IRKS, data->mgmt_index,
sizeof(load_irks_2_devices_param),
@@ -11660,17 +11756,21 @@ static void setup_ll_privacy_set_flags_3(const void *test_data)
setup_load_irks_callback, NULL, NULL);
/* Set Powered On */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_POWERED, data->mgmt_index,
sizeof(param), param,
- setup_powered_callback, NULL, NULL);
+ setup_powered_condition_callback,
+ NULL, NULL);
/* Add Device 1 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_1),
add_device_le_public_param_1,
setup_add_device_callback, NULL, NULL);
/* Device 1 Flags */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_DEVICE_FLAGS, data->mgmt_index,
sizeof(set_device_flags_param_1),
set_device_flags_param_1,
@@ -11678,6 +11778,7 @@ static void setup_ll_privacy_set_flags_3(const void *test_data)
NULL);
/* Add Device 2 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_2),
add_device_le_public_param_2,
@@ -11698,6 +11799,8 @@ static void setup_ll_privacy_3_devices(const void *test_data)
struct test_data *data = tester_get_data();
unsigned char param[] = { 0x01 };
+ data->setup_settle = true;
+
/* Add IRKs of 4 Devices */
mgmt_send(data->mgmt, MGMT_OP_LOAD_IRKS, data->mgmt_index,
sizeof(load_irks_4_devices_param),
@@ -11705,12 +11808,14 @@ static void setup_ll_privacy_3_devices(const void *test_data)
setup_load_irks_callback, NULL, NULL);
/* Add Device 1 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_1),
add_device_le_public_param_1,
setup_add_device_callback, NULL, NULL);
/* Device 1 Flags */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_DEVICE_FLAGS, data->mgmt_index,
sizeof(set_device_flags_param_1),
set_device_flags_param_1,
@@ -11718,12 +11823,14 @@ static void setup_ll_privacy_3_devices(const void *test_data)
NULL);
/* Add Device 2 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_2),
add_device_le_public_param_2,
setup_add_device_callback, NULL, NULL);
/* Device 2 Flags */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_DEVICE_FLAGS, data->mgmt_index,
sizeof(set_device_flags_param_2),
set_device_flags_param_2,
@@ -11731,21 +11838,25 @@ static void setup_ll_privacy_3_devices(const void *test_data)
NULL);
/* Add Device 3 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_3),
add_device_le_public_param_3,
setup_add_device_callback, NULL, NULL);
/* Device 3 Flags */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_DEVICE_FLAGS, data->mgmt_index,
sizeof(set_device_flags_param_3),
set_device_flags_param_3,
setup_set_device_flags_callback, NULL,
NULL);
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_POWERED, data->mgmt_index,
sizeof(param), param,
- setup_powered_callback, NULL, NULL);
+ setup_powered_condition_callback,
+ NULL, NULL);
}
static void setup_ll_privacy_set_flags_4(const void *test_data)
@@ -11753,6 +11864,8 @@ static void setup_ll_privacy_set_flags_4(const void *test_data)
struct test_data *data = tester_get_data();
unsigned char param[] = { 0x01 };
+ data->setup_settle = true;
+
hciemu_set_central_le_rl_len(data->hciemu, 4);
/* Add IRKs of 4 Devices */
@@ -11762,12 +11875,14 @@ static void setup_ll_privacy_set_flags_4(const void *test_data)
setup_load_irks_callback, NULL, NULL);
/* Add Device 1 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_1),
add_device_le_public_param_1,
setup_add_device_callback, NULL, NULL);
/* Device 1 Flags */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_DEVICE_FLAGS, data->mgmt_index,
sizeof(set_device_flags_param_1),
set_device_flags_param_1,
@@ -11775,12 +11890,14 @@ static void setup_ll_privacy_set_flags_4(const void *test_data)
NULL);
/* Add Device 2 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_2),
add_device_le_public_param_2,
setup_add_device_callback, NULL, NULL);
/* Device 2 Flags */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_DEVICE_FLAGS, data->mgmt_index,
sizeof(set_device_flags_param_2),
set_device_flags_param_2,
@@ -11788,12 +11905,14 @@ static void setup_ll_privacy_set_flags_4(const void *test_data)
NULL);
/* Add Device 3 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_3),
add_device_le_public_param_3,
setup_add_device_callback, NULL, NULL);
/* Device 3 Flags */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_DEVICE_FLAGS, data->mgmt_index,
sizeof(set_device_flags_param_3),
set_device_flags_param_3,
@@ -11801,14 +11920,17 @@ static void setup_ll_privacy_set_flags_4(const void *test_data)
NULL);
/* Add Device 4 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_4),
add_device_le_public_param_4,
setup_add_device_callback, NULL, NULL);
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_POWERED, data->mgmt_index,
sizeof(param), param,
- setup_powered_callback, NULL, NULL);
+ setup_powered_condition_callback,
+ NULL, NULL);
}
static void setup_ll_privacy_add_3(const void *test_data)
@@ -11816,6 +11938,8 @@ static void setup_ll_privacy_add_3(const void *test_data)
struct test_data *data = tester_get_data();
unsigned char param[] = { 0x01 };
+ data->setup_settle = true;
+
hciemu_set_central_le_al_len(data->hciemu, 2);
/* Add IRKs of 3 Devices */
@@ -11825,17 +11949,21 @@ static void setup_ll_privacy_add_3(const void *test_data)
setup_load_irks_callback, NULL, NULL);
/* Set Powered On */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_POWERED, data->mgmt_index,
sizeof(param), param,
- setup_powered_callback, NULL, NULL);
+ setup_powered_condition_callback,
+ NULL, NULL);
/* Add Device 1 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_1),
add_device_le_public_param_1,
setup_add_device_callback, NULL, NULL);
/* Device 1 Flags */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_DEVICE_FLAGS, data->mgmt_index,
sizeof(set_device_flags_param_1),
set_device_flags_param_1,
@@ -11843,12 +11971,14 @@ static void setup_ll_privacy_add_3(const void *test_data)
NULL);
/* Add Device 2 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_2),
add_device_le_public_param_2,
setup_add_device_callback, NULL, NULL);
/* Device 2 Flags */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_DEVICE_FLAGS, data->mgmt_index,
sizeof(set_device_flags_param_2),
set_device_flags_param_2,
@@ -11863,9 +11993,13 @@ static void setup_ll_privacy_device2_discovry(const void *test_data)
unsigned char param[] = { 0x01 };
uint8_t disc_param[] = { 0x06 };
+ data->setup_settle = true;
+
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_POWERED, data->mgmt_index,
sizeof(param), param,
- setup_powered_callback, NULL, NULL);
+ setup_powered_condition_callback,
+ NULL, NULL);
/* Load IRKs */
mgmt_send(data->mgmt, MGMT_OP_LOAD_IRKS, data->mgmt_index,
sizeof(load_irks_2_devices_param),
@@ -11873,11 +12007,13 @@ static void setup_ll_privacy_device2_discovry(const void *test_data)
setup_load_irks_callback, NULL, NULL);
/* Load Device1 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_1),
add_device_le_public_param_1,
setup_add_device_callback, NULL, NULL);
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_2),
add_device_le_public_param_2,
@@ -11906,6 +12042,8 @@ static void setup_ll_privacy_add_4(const void *test_data)
unsigned char param[] = { 0x01 };
unsigned char adv_param[sizeof(*cp) + TESTER_ADD_ADV_DATA_LEN];
+ data->setup_settle = true;
+
cp = (struct mgmt_cp_add_advertising *) adv_param;
setup_add_adv_param(cp, 1);
@@ -11926,6 +12064,8 @@ static void setup_ll_privacy_set_flags_5(const void *test_data)
unsigned char param[] = { 0x01 };
unsigned char adv_param[sizeof(*cp) + TESTER_ADD_ADV_DATA_LEN];
+ data->setup_settle = true;
+
cp = (struct mgmt_cp_add_advertising *) adv_param;
setup_add_adv_param(cp, 1);
@@ -11941,11 +12081,13 @@ static void setup_ll_privacy_set_flags_5(const void *test_data)
NULL, NULL, NULL);
/* Add Advertising Instance */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_ADVERTISING, data->mgmt_index,
sizeof(adv_param), adv_param,
setup_add_adv_callback_adv, NULL, NULL);
/* Add Device 1 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_1),
add_device_le_public_param_1,
@@ -11959,6 +12101,8 @@ static void setup_ll_privacy_set_flags_6(const void *test_data)
unsigned char param[] = { 0x01 };
unsigned char adv_param[sizeof(*cp) + TESTER_ADD_ADV_DATA_LEN];
+ data->setup_settle = true;
+
cp = (struct mgmt_cp_add_advertising *) adv_param;
setup_add_adv_param(cp, 1);
@@ -11974,17 +12118,20 @@ static void setup_ll_privacy_set_flags_6(const void *test_data)
NULL, NULL, NULL);
/* Add Advertising Instance 1 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_ADVERTISING, data->mgmt_index,
sizeof(adv_param), adv_param,
setup_add_adv_callback_adv, NULL, NULL);
/* Add Device 2 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_2),
add_device_le_public_param_2,
setup_add_device_callback, NULL, NULL);
/* Device 2 Flags */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_DEVICE_FLAGS, data->mgmt_index,
sizeof(set_device_flags_param_2),
set_device_flags_param_2,
@@ -11992,6 +12139,7 @@ static void setup_ll_privacy_set_flags_6(const void *test_data)
NULL);
/* Add Device 1 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_1),
add_device_le_public_param_1,
@@ -12005,6 +12153,8 @@ static void setup_ll_privacy_adv_3_devices(const void *test_data)
unsigned char param[] = { 0x01 };
unsigned char adv_param[sizeof(*cp) + TESTER_ADD_ADV_DATA_LEN];
+ data->setup_settle = true;
+
cp = (struct mgmt_cp_add_advertising *) adv_param;
setup_add_adv_param(cp, 1);
@@ -12019,12 +12169,14 @@ static void setup_ll_privacy_adv_3_devices(const void *test_data)
setup_load_irks_callback, NULL, NULL);
/* Add Device 1 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_1),
add_device_le_public_param_1,
setup_add_device_callback, NULL, NULL);
/* Device 1 Flags */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_DEVICE_FLAGS, data->mgmt_index,
sizeof(set_device_flags_param_1),
set_device_flags_param_1,
@@ -12032,12 +12184,14 @@ static void setup_ll_privacy_adv_3_devices(const void *test_data)
NULL);
/* Add Device 2 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_2),
add_device_le_public_param_2,
setup_add_device_callback, NULL, NULL);
/* Device 2 Flags */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_DEVICE_FLAGS, data->mgmt_index,
sizeof(set_device_flags_param_2),
set_device_flags_param_2,
@@ -12045,21 +12199,25 @@ static void setup_ll_privacy_adv_3_devices(const void *test_data)
NULL);
/* Add Device 3 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_3),
add_device_le_public_param_3,
setup_add_device_callback, NULL, NULL);
/* Device 3 Flags */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_DEVICE_FLAGS, data->mgmt_index,
sizeof(set_device_flags_param_3),
set_device_flags_param_3,
setup_set_device_flags_callback, NULL,
NULL);
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_POWERED, data->mgmt_index,
sizeof(param), param,
- setup_powered_callback, NULL, NULL);
+ setup_powered_condition_callback,
+ NULL, NULL);
}
static void setup_ll_privacy_adv_1_device_2_advs(const void *test_data)
@@ -12069,6 +12227,8 @@ static void setup_ll_privacy_adv_1_device_2_advs(const void *test_data)
unsigned char param[] = { 0x01 };
unsigned char adv_param[sizeof(*cp) + TESTER_ADD_ADV_DATA_LEN];
+ data->setup_settle = true;
+
cp = (struct mgmt_cp_add_advertising *) adv_param;
setup_add_adv_param(cp, 1);
@@ -12083,12 +12243,14 @@ static void setup_ll_privacy_adv_1_device_2_advs(const void *test_data)
NULL, NULL, NULL);
/* Add Device 1 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_1),
add_device_le_public_param_1,
setup_add_device_callback, NULL, NULL);
/* Device 1 Flags */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_DEVICE_FLAGS, data->mgmt_index,
sizeof(set_device_flags_param_1),
set_device_flags_param_1,
@@ -12096,18 +12258,21 @@ static void setup_ll_privacy_adv_1_device_2_advs(const void *test_data)
NULL);
/* Add Device 2 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_2),
add_device_le_public_param_2,
setup_add_device_callback, NULL, NULL);
/* Device 2 Flags */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_DEVICE_FLAGS, data->mgmt_index,
sizeof(set_device_flags_param_2),
set_device_flags_param_2,
setup_set_device_flags_callback, NULL,
NULL);
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_ADVERTISING, data->mgmt_index,
sizeof(adv_param), adv_param,
setup_add_adv_callback_adv, NULL, NULL);
@@ -12133,6 +12298,7 @@ static void setup_add_2_advertisings(const void *test_data)
sizeof(param), param,
NULL, NULL, NULL);
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_ADVERTISING, data->mgmt_index,
sizeof(adv_param), adv_param,
setup_add_adv_callback_adv,
@@ -12155,6 +12321,7 @@ static void setup_add_2_advertisings_no_power(const void *test_data)
sizeof(param), ¶m,
NULL, NULL, NULL);
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_ADVERTISING, data->mgmt_index,
sizeof(adv_param), adv_param,
setup_add_adv_callback_adv,
@@ -12167,6 +12334,8 @@ static void setup_ll_privacy_enable_powered(const void *test_data)
unsigned char param[] = { 0x01 };
struct bthost *bthost;
+ data->setup_settle = true;
+
bthost = hciemu_client_get_host(data->hciemu);
bthost_set_scan_params(bthost, 0x01, 0x00, 0x00);
bthost_set_scan_enable(bthost, 0x01);
@@ -12184,6 +12353,8 @@ static void setup_ll_privacy_add_adv(const void *test_data)
unsigned char set_adv_param[] = { 0x02 };
struct bthost *bthost;
+ data->setup_settle = true;
+
/* Setup bthost to enable the scan */
bthost = hciemu_client_get_host(data->hciemu);
bthost_set_scan_params(bthost, 0x01, 0x00, 0x00);
@@ -12216,15 +12387,20 @@ static void setup_ll_privacy_add_device(const void *test_data)
struct test_data *data = tester_get_data();
unsigned char param[] = { 0x01 };
+ data->setup_settle = true;
+
mgmt_send(data->mgmt, MGMT_OP_LOAD_IRKS, data->mgmt_index,
sizeof(load_irks_le_public_param_1),
load_irks_le_public_param_1,
setup_load_irks_callback, NULL, NULL);
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_POWERED, data->mgmt_index,
sizeof(param), param,
- setup_powered_callback, NULL, NULL);
+ setup_powered_condition_callback,
+ NULL, NULL);
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_le_public_param_1),
add_device_le_public_param_1,
@@ -13583,15 +13759,18 @@ static void setup_suspend_resume_success_3(const void *test_data)
unsigned char param[] = { 0x01 };
/* Add Device 1 */
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_ADD_DEVICE, data->mgmt_index,
sizeof(add_device_success_param_3),
add_device_success_param_3,
setup_add_device_callback,
NULL, NULL);
+ test_add_setup_condition(data);
mgmt_send(data->mgmt, MGMT_OP_SET_POWERED, data->mgmt_index,
sizeof(param), param,
- setup_powered_callback, NULL, NULL);
+ setup_powered_condition_callback,
+ NULL, NULL);
}
static void test_suspend_resume_success_3(const void *test_data)
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 04/27] iso-tester: Fix leaking socket watches
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (2 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 03/27] mgmt-tester: Wait for LL Privacy setup to complete Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 05/27] l2cap-tester: Fix race in LE Client - Close socket 2 Luiz Augusto von Dentz
` (23 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
iso_recv_data() clears io_id[0] on entry, but returns TRUE to keep its
watch while more data is expected, so the watch can no longer be
removed, and the watches with ids are only removed when the test data is
freed at exit. Either way the watched socket stays open past the test,
and once it hangs up, as the controller is removed, it wakes up the main
loop over and over for every following test, as G_IO_IN does not
include G_IO_HUP.
Keep the id while the watch is kept and remove the watches on post
teardown. This busy loop is costly under User Mode Linux time travel,
where computing takes no simulated time, e.g. the PAST Receiver tests
took about 7 seconds each, with -T:
iso-tester before 49.2 s
after 16.3 s
Assisted-by: Claude:claude-opus-5-5
---
tools/iso-tester.c | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/tools/iso-tester.c b/tools/iso-tester.c
index fe27eaaf34bf..d610b3c32c51 100644
--- a/tools/iso-tester.c
+++ b/tools/iso-tester.c
@@ -707,10 +707,22 @@ static void io_free(void *data)
static void test_post_teardown(const void *test_data)
{
struct test_data *data = tester_get_data();
+ unsigned int i;
if (data->io_queue)
queue_remove_all(data->io_queue, NULL, NULL, io_free);
+ /* Remove the watches with the test, as the sockets they keep open
+ * hang up once the controller is removed, which would wake up the
+ * main loop over and over for the following tests.
+ */
+ for (i = 0; i < ARRAY_SIZE(data->io_id); ++i) {
+ if (data->io_id[i] > 0) {
+ g_source_remove(data->io_id[i]);
+ data->io_id[i] = 0;
+ }
+ }
+
mgmt_send(data->mgmt, MGMT_OP_SET_EXP_FEATURE, MGMT_INDEX_NONE,
sizeof(reset_iso_socket_param), reset_iso_socket_param,
NULL, NULL, NULL);
@@ -2542,9 +2554,11 @@ static gboolean iso_recv_data(GIOChannel *io, GIOCondition cond,
if (memcmp(buf, isodata->recv->iov_base, ret))
tester_test_failed();
- else if (data->step)
+ else if (data->step) {
+ /* Keep the watch, and its id so teardown can remove it */
+ data->io_id[0] = g_source_get_id(g_main_current_source());
return TRUE;
- else if (isodata->disconnect)
+ } else if (isodata->disconnect)
iso_shutdown(data, io);
else if (isodata->terminate)
iso_terminate(data, io);
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 05/27] l2cap-tester: Fix race in LE Client - Close socket 2
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (3 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 04/27] iso-tester: Fix leaking socket watches Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 06/27] iso-tester: Fix busy waiting in PAST tests Luiz Augusto von Dentz
` (22 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
The test closes the socket once the scan is disabled, expecting the
kernel to cancel the connection attempt with LE Create Connection
Cancel, but the kernel only sends LE Create Connection after disabling
the scan, so whether there is an attempt to cancel depends on which runs
first. Close the socket once LE Create Connection was sent instead.
This shows under User Mode Linux time travel, where the test always
closed the socket too early.
Assisted-by: Claude:claude-opus-5-5
---
tools/l2cap-tester.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/tools/l2cap-tester.c b/tools/l2cap-tester.c
index f2d3e56b8761..c2643b4182b0 100644
--- a/tools/l2cap-tester.c
+++ b/tools/l2cap-tester.c
@@ -2769,7 +2769,17 @@ static void test_close_socket_scan_disabled(void)
if (l2data == &le_client_close_socket_test_1)
g_idle_add(test_close_socket_1_part_3, NULL);
- else if (l2data == &le_client_close_socket_test_2)
+}
+
+/* The kernel creates the connection once the scan is disabled, close the
+ * socket only then, so it has a connection attempt to cancel.
+ */
+static void test_close_socket_create_conn(void)
+{
+ struct test_data *data = tester_get_data();
+ const struct l2cap_data *l2data = data->test_data;
+
+ if (l2data == &le_client_close_socket_test_2)
g_idle_add(test_close_socket_2_part_3, NULL);
}
@@ -2793,6 +2803,8 @@ static void test_close_socket_router(uint16_t opcode, const void *param,
test_close_socket_scan_enabled();
else
test_close_socket_scan_disabled();
+ } else if (opcode == BT_HCI_CMD_LE_CREATE_CONN) {
+ test_close_socket_create_conn();
} else if (opcode == BT_HCI_CMD_LE_CREATE_CONN_CANCEL) {
test_close_socket_conn_cancel();
}
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 06/27] iso-tester: Fix busy waiting in PAST tests
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (4 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 05/27] l2cap-tester: Fix race in LE Client - Close socket 2 Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 07/27] doc: Enable IPv6 in tester.config Luiz Augusto von Dentz
` (21 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
iso_pollout() kept its watch while the connection to the PAST receiver
was missing, but the socket is writable all along, so the main loop was
woken up until the connection was established. Under User Mode Linux
time travel, where computing takes no simulated time, the PAST Info
tests took about 400 seconds each.
Drop the watch instead and send from iso_new_conn() once the connection
is there.
Assisted-by: Claude:claude-opus-5-5
---
tools/iso-tester.c | 25 ++++++++++++++++++++++---
1 file changed, 22 insertions(+), 3 deletions(-)
diff --git a/tools/iso-tester.c b/tools/iso-tester.c
index d610b3c32c51..7149232d17f4 100644
--- a/tools/iso-tester.c
+++ b/tools/iso-tester.c
@@ -471,6 +471,7 @@ struct test_data {
uint16_t handle;
uint16_t acl_handle;
struct queue *io_queue;
+ GIOChannel *past_io;
unsigned int io_id[4];
uint8_t client_num;
int step;
@@ -712,6 +713,11 @@ static void test_post_teardown(const void *test_data)
if (data->io_queue)
queue_remove_all(data->io_queue, NULL, NULL, io_free);
+ if (data->past_io) {
+ g_io_channel_unref(data->past_io);
+ data->past_io = NULL;
+ }
+
/* Remove the watches with the test, as the sockets they keep open
* hang up once the controller is removed, which would wake up the
* main loop over and over for the following tests.
@@ -1716,6 +1722,8 @@ static void bthost_iso_disconnected(void *user_data)
data->handle = 0x0000;
}
+static void iso_send(struct test_data *data, GIOChannel *io);
+
static void iso_new_conn(uint16_t handle, void *user_data)
{
struct test_data *data = user_data;
@@ -1727,6 +1735,12 @@ static void iso_new_conn(uint16_t handle, void *user_data)
data->handle = handle;
+ if (data->past_io) {
+ iso_send(data, data->past_io);
+ g_io_channel_unref(data->past_io);
+ data->past_io = NULL;
+ }
+
host = hciemu_client_get_host(data->hciemu);
bthost_add_iso_hook(host, data->handle, bthost_recv_data, data,
bthost_iso_disconnected);
@@ -2703,11 +2717,16 @@ static gboolean iso_pollout(GIOChannel *io, GIOCondition cond,
const struct iso_client_data *isodata = data->test_data;
unsigned int count;
- if (isodata->past && !data->handle)
- return TRUE;
-
data->io_id[0] = 0;
+ /* Send once the connection to the PAST receiver is there, see
+ * iso_new_conn(), rather than waking up for good until then.
+ */
+ if (isodata->past && !data->handle) {
+ data->past_io = g_io_channel_ref(io);
+ return FALSE;
+ }
+
tester_print("POLLOUT event received");
for (count = 0; count < isodata->repeat_send_pre_ts; ++count)
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 07/27] doc: Enable IPv6 in tester.config
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (5 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 06/27] iso-tester: Fix busy waiting in PAST tests Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 08/27] 6lowpan-tester: Find the interface of the controller Luiz Augusto von Dentz
` (20 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
6LoWPAN over Bluetooth needs IPv6, which tester.config relied on the
default configuration for: without it 6lowpan_enable does not show up
in debugfs and every 6lowpan-tester test fails, as with the User Mode
Linux defconfig.
Assisted-by: Claude:claude-opus-5-5
---
doc/tester.config | 1 +
1 file changed, 1 insertion(+)
diff --git a/doc/tester.config b/doc/tester.config
index 34f1530e174c..58b7c592335c 100644
--- a/doc/tester.config
+++ b/doc/tester.config
@@ -15,6 +15,7 @@ CONFIG_PTP_1588_CLOCK_VMCLOCK=y
CONFIG_NET=y
CONFIG_INET=y
+CONFIG_IPV6=y
CONFIG_NET_9P=y
CONFIG_NET_9P_VIRTIO=y
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 08/27] 6lowpan-tester: Find the interface of the controller
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (6 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 07/27] doc: Enable IPv6 in tester.config Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 09/27] emulator: Remove -T option from btvirt Luiz Augusto von Dentz
` (19 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
The tests open bt0, but the interface of a previous test may still be
there, removed by the kernel from a work, in which case the interface of
the current test gets another name and the test waits for packets on the
wrong one. Use the most recent interface of the controller of the test.
This shows under User Mode Linux time travel, where the receive tests
always got the interface of the previous test.
Assisted-by: Claude:claude-opus-5-5
---
tools/6lowpan-tester.c | 62 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 61 insertions(+), 1 deletion(-)
diff --git a/tools/6lowpan-tester.c b/tools/6lowpan-tester.c
index a8faf6eb5976..e407684a9e4b 100644
--- a/tools/6lowpan-tester.c
+++ b/tools/6lowpan-tester.c
@@ -22,6 +22,7 @@
#include <net/if.h>
#include <linux/if_packet.h>
#include <sys/ioctl.h>
+#include <dirent.h>
#include <glib.h>
@@ -590,14 +591,73 @@ done:
return FALSE;
}
+/* Find the interface of the controller of this test, the most recent one,
+ * as the interface of a previous test may still be there, which can also
+ * make the name of this one other than bt0.
+ */
+static bool find_iface(struct test_data *data, char *ifname, size_t len)
+{
+ char path[PATH_MAX], link[PATH_MAX], hci[16];
+ struct dirent *entry;
+ int best = -1;
+ DIR *dir;
+
+ snprintf(hci, sizeof(hci), "hci%u", data->mgmt_index);
+
+ dir = opendir("/sys/class/net");
+ if (!dir)
+ return false;
+
+ while ((entry = readdir(dir))) {
+ const char *name;
+ ssize_t n;
+ int index;
+
+ if (strncmp(entry->d_name, "bt", 2))
+ continue;
+
+ snprintf(path, sizeof(path), "/sys/class/net/%s/device",
+ entry->d_name);
+
+ n = readlink(path, link, sizeof(link) - 1);
+ if (n < 0)
+ continue;
+
+ link[n] = '\0';
+ name = strrchr(link, '/');
+ name = name ? name + 1 : link;
+
+ if (strcmp(name, hci))
+ continue;
+
+ if (strlen(entry->d_name) >= len)
+ continue;
+
+ index = if_nametoindex(entry->d_name);
+ if (index > best) {
+ best = index;
+ memcpy(ifname, entry->d_name,
+ strlen(entry->d_name) + 1);
+ }
+ }
+
+ closedir(dir);
+
+ return best >= 0;
+}
+
static gboolean client_open_iface(gpointer user_data)
{
struct test_data *data = user_data;
const struct client_data *cdata = data->test_data;
+ char ifname[IFNAMSIZ];
data->io_id = 0;
- data->packet_fd = open_iface("bt0", cdata->sk_type);
+ data->packet_fd = -1;
+
+ if (find_iface(data, ifname, sizeof(ifname)))
+ data->packet_fd = open_iface(ifname, cdata->sk_type);
if (data->packet_fd < 0) {
tester_print("Wait for interface...");
data->io_id = g_timeout_add(500, client_open_iface, data);
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 09/27] emulator: Remove -T option from btvirt
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (7 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 08/27] 6lowpan-tester: Find the interface of the controller Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 10/27] doc: Add btvirt documentation Luiz Augusto von Dentz
` (18 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
The AMP test controllers were removed, but -T was left in the help text
and the getopt string without a handler, so btvirt -T exits with an
error.
Fixes: 1474c11618bc ("build: Remove AMP related functionality")
Assisted-by: Claude:claude-opus-5-5
---
emulator/main.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/emulator/main.c b/emulator/main.c
index b0e295b17609..61c2f0d5c33d 100644
--- a/emulator/main.c
+++ b/emulator/main.c
@@ -58,7 +58,6 @@ static void usage(void)
"\t BR/EDR/LE controllers created with\n"
"\t -l, -s and -t:\n"
"\t 5.0, 5.2, 6.0 or 6.2 (default)\n"
- "\t-T[num] Number of test AMP controllers\n"
"\t-h, --help Show help options\n");
}
@@ -141,7 +140,7 @@ int main(int argc, char *argv[])
for (;;) {
int opt;
- opt = getopt_long(argc, argv, "dSs::t::l::LBAC:U::T::vh",
+ opt = getopt_long(argc, argv, "dSs::t::l::LBAC:U::vh",
main_options, NULL);
if (opt < 0)
break;
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 10/27] doc: Add btvirt documentation
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (8 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 09/27] emulator: Remove -T option from btvirt Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 11/27] emulator: Add vhost-user backend Luiz Augusto von Dentz
` (17 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Document the btvirt options, the ways it exposes the emulated
controllers and which of them share the emulated air interface.
Assisted-by: Claude:claude-opus-5-5
---
Makefile.am | 1 +
doc/btvirt.rst | 153 +++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 154 insertions(+)
create mode 100644 doc/btvirt.rst
diff --git a/Makefile.am b/Makefile.am
index f742458d113e..26884a54ea06 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -494,6 +494,7 @@ EXTRA_DIST += SECURITY.md
EXTRA_DIST += doc/assigned-numbers.rst doc/supported-features.txt \
doc/test-coverage.txt \
doc/test-runner.rst \
+ doc/btvirt.rst \
doc/test-functional.rst \
doc/functional-testing.rst \
doc/functional-a2dp.rst \
diff --git a/doc/btvirt.rst b/doc/btvirt.rst
new file mode 100644
index 000000000000..2e016406a4fc
--- /dev/null
+++ b/doc/btvirt.rst
@@ -0,0 +1,153 @@
+======
+btvirt
+======
+
+-----------------------------
+Bluetooth controller emulator
+-----------------------------
+
+:Version: BlueZ
+:Copyright: Free use of this software is granted under the terms of the GNU
+ Lesser General Public Licenses (LGPL).
+:Date: October 2026
+:Manual section: 1
+:Manual group: Linux System Administration
+
+SYNOPSIS
+========
+
+**btvirt** [*OPTIONS*]
+
+DESCRIPTION
+===========
+
+**btvirt(1)** emulates Bluetooth controllers in software. It is used to test
+the kernel Bluetooth subsystem and BlueZ without real hardware, most notably
+by **test-runner(1)** and **test-functional(1)**.
+
+The controllers are implemented by the BlueZ emulator (``emulator/btdev.c``)
+and speak HCI. They can be exposed to a Bluetooth stack in several ways:
+
+- as local virtual controllers through ``/dev/vhci`` (**-l**), which then
+ appear as ``hciN`` on the machine running btvirt;
+- as H:4 streams on Unix socket servers (**-s**) or a TCP server (**-t**),
+ one controller per client connection;
+- as an H:4 stream on a pseudo terminal (**-S**).
+
+All controllers created by one btvirt process share an emulated air
+interface: they can discover, connect to and exchange data with each other,
+regardless of how each one is exposed. Controllers in different btvirt
+processes cannot see each other, except for the test LE controllers created
+with **-U**, see below.
+
+btvirt runs until it receives **SIGINT** or **SIGTERM**.
+
+OPTIONS
+=======
+
+At least one of **-l**, **-s**, **-t**, **-S** or **-U** must be given.
+Optional arguments must be attached to the option, as in **-l2**,
+**-t45551** or **--server=/tmp/bt**.
+
+-l[num], --local[=num] Create *num* local virtual controllers through
+ ``/dev/vhci`` (default: 1). Requires access to
+ ``/dev/vhci``. The controller type is selected
+ with **-L**, **-B**, **-A** or **-C**.
+
+-s[path], --server[=path] Create Unix socket servers in the directory
+ *path* (default: ``/tmp``). Each client that
+ connects gets a new controller of the server's
+ type, attached as an H:4 stream. The following
+ sockets are created:
+
+ ``bt-server-bredrle``
+ BR/EDR/LE controllers, of the version given
+ with **-C**
+ ``bt-server-bredr``
+ BR/EDR-only controllers
+ ``bt-server-le``
+ LE-only controllers
+ ``bt-server-amp``
+ AMP controllers
+ ``bt-server-mon``
+ monitor socket, no controller is created
+
+-t[port], --tcp[=port] Create a TCP server on *port* (default: 45550).
+ Each client that connects gets a new BR/EDR/LE
+ controller, of the version given with **-C**,
+ attached as an H:4 stream. The server listens on
+ 127.0.0.1 only.
+
+-S, --serial Create a pseudo terminal with a BR/EDR/LE
+ controller attached as an H:4 stream. The path of
+ the terminal is printed on startup; attach it
+ with **btattach(1)**.
+
+-U[num], --letest[=num] Create *num* test LE controllers (default: 1)
+ through ``/dev/vhci``. Unlike the other
+ controllers, these exchange radio traffic with
+ test LE controllers in other btvirt processes,
+ using UDP broadcasts on port 45023.
+
+-L, --le Make the controllers created with **-l** LE-only.
+
+-B, --bredr Make the controllers created with **-l**
+ BR/EDR-only.
+
+-A, --amp Make the controllers created with **-l** AMP
+ controllers.
+
+-C version, --core=version Core Specification version of the BR/EDR/LE
+ controllers created with **-l**, **-s** (on
+ ``bt-server-bredrle``) and **-t**. Supported
+ values are ``5.0``, ``5.2``, ``6.0`` and ``6.2``
+ (default).
+
+-d, --debug Enable debug output for the **-l** and **-s**
+ controllers.
+
+-v, --version Show version.
+
+-h, --help Show help options.
+
+VENDOR COMMANDS
+===============
+
+Controllers created with **-l** answer two vendor commands, so the kernel
+treats them as supporting the corresponding extensions:
+
+- opcode ``0xfc10``: emulator-specific vendor command;
+- opcode ``0xfc1e``: Microsoft vendor extension (MSFT).
+
+EXAMPLES
+========
+
+Create two local controllers, which show up as ``hciN`` and can connect to
+each other::
+
+ $ sudo btvirt -l2
+
+Serve controllers to virtual machines, one per connecting VM, the way
+**test-functional(1)** does::
+
+ $ btvirt --server=/tmp/bt
+ $ test-runner -u/tmp/bt/bt-server-bredrle -k bzImage -- ...
+
+Emulate a Bluetooth 5.2 controller over TCP::
+
+ $ btvirt -C 5.2 -t
+
+RESOURCES
+=========
+
+http://www.bluez.org
+
+REPORTING BUGS
+==============
+
+linux-bluetooth@vger.kernel.org
+
+SEE ALSO
+========
+
+**test-runner(1)**, **test-functional(1)**, **btattach(1)**
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 11/27] emulator: Add vhost-user backend
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (9 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 10/27] doc: Add btvirt documentation Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 12/27] emulator: Add vhost-user virtio server to btvirt Luiz Augusto von Dentz
` (16 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Add a vhost-user back-end on top of the mainloop, so emulated devices
can be provided to virtual machines as virtio devices. It supports the
split virtqueue layout, the configuration space and kick/call file
descriptors.
Offer VHOST_USER_PROTOCOL_F_SLAVE_REQ and
VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS, so the guest sends kicks as
VHOST_USER_VRING_KICK messages and is notified with
VHOST_USER_SLAVE_VRING_CALL requests. User Mode Linux with external time
travel requires this, as it does not handle call file descriptors while
it waits for the time controller. The front-end only acknowledges a
notification after the time controller answered it, so a wait callback
lets the user serve it in the meantime.
Messages are read without blocking and may arrive in several parts, so
one connection cannot hold up the others or the calendar, and
vhost_process() handles them for a time travel calendar before it
advances time; a nested call for a connection already being processed
returns right away, so only the outer one removes it on failure. A
can_notify callback holds notifications to the driver back until
vhost_flush_notify(), for a back-end that may only notify while the
calendar lets it run.
Assisted-by: Claude:claude-opus-5-5
---
Makefile.tools | 3 +-
emulator/vhost.c | 1018 ++++++++++++++++++++++++++++++++++++++++++++++
emulator/vhost.h | 67 +++
3 files changed, 1087 insertions(+), 1 deletion(-)
create mode 100644 emulator/vhost.c
create mode 100644 emulator/vhost.h
diff --git a/Makefile.tools b/Makefile.tools
index 2914bf1c0391..cb6c01e5e0d9 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -102,7 +102,8 @@ emulator_btvirt_SOURCES = emulator/main.c monitor/bt.h \
emulator/bthost.h emulator/bthost.c \
emulator/smp.c \
emulator/phy.h emulator/phy.c \
- emulator/le.h emulator/le.c
+ emulator/le.h emulator/le.c \
+ emulator/vhost.h emulator/vhost.c
emulator_btvirt_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la
emulator_b1ee_SOURCES = emulator/b1ee.c
diff --git a/emulator/vhost.c b/emulator/vhost.c
new file mode 100644
index 000000000000..a0b79c6bc02a
--- /dev/null
+++ b/emulator/vhost.c
@@ -0,0 +1,1018 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2026 Intel Corporation
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/epoll.h>
+#include <poll.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+
+#include "src/shared/mainloop.h"
+#include "src/shared/util.h"
+
+#include "vhost.h"
+
+/* vhost-user messages, see docs/interop/vhost-user.rst in QEMU */
+#define VHOST_USER_GET_FEATURES 1
+#define VHOST_USER_SET_FEATURES 2
+#define VHOST_USER_SET_OWNER 3
+#define VHOST_USER_RESET_OWNER 4
+#define VHOST_USER_SET_MEM_TABLE 5
+#define VHOST_USER_SET_VRING_NUM 8
+#define VHOST_USER_SET_VRING_ADDR 9
+#define VHOST_USER_SET_VRING_BASE 10
+#define VHOST_USER_GET_VRING_BASE 11
+#define VHOST_USER_SET_VRING_KICK 12
+#define VHOST_USER_SET_VRING_CALL 13
+#define VHOST_USER_SET_VRING_ERR 14
+#define VHOST_USER_GET_PROTOCOL_FEATURES 15
+#define VHOST_USER_SET_PROTOCOL_FEATURES 16
+#define VHOST_USER_SET_VRING_ENABLE 18
+#define VHOST_USER_SET_SLAVE_REQ_FD 21
+#define VHOST_USER_GET_CONFIG 24
+#define VHOST_USER_SET_CONFIG 25
+#define VHOST_USER_VRING_KICK 35
+
+/* Back-end to front-end requests */
+#define VHOST_USER_SLAVE_VRING_CALL 4
+
+#define VHOST_USER_VERSION 0x1
+#define VHOST_USER_FLAG_REPLY (1 << 2)
+#define VHOST_USER_FLAG_NEED_REPLY (1 << 3)
+
+#define VHOST_USER_VRING_INDEX_MASK 0xff
+#define VHOST_USER_VRING_NOFD_MASK (1 << 8)
+
+#define VHOST_USER_F_PROTOCOL_FEATURES 30
+#define VHOST_USER_PROTOCOL_F_SLAVE_REQ 5
+#define VHOST_USER_PROTOCOL_F_CONFIG 9
+#define VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS 14
+
+/* How long to wait for the front-end to acknowledge a notification */
+#define VHOST_USER_REPLY_TIMEOUT 5000
+
+#define VIRTIO_F_VERSION_1 32
+
+#define VRING_DESC_F_NEXT 1
+#define VRING_DESC_F_WRITE 2
+#define VRING_DESC_F_INDIRECT 4
+
+#define VHOST_USER_MAX_FDS 8
+#define VHOST_USER_MAX_REGIONS 8
+#define VHOST_USER_MAX_QUEUES 8
+#define VHOST_USER_MAX_CONFIG 256
+
+struct vhost_hdr {
+ uint32_t request;
+ uint32_t flags;
+ uint32_t size;
+} __attribute__((packed));
+
+struct vhost_vring_state {
+ uint32_t index;
+ uint32_t num;
+} __attribute__((packed));
+
+struct vhost_vring_addr {
+ uint32_t index;
+ uint32_t flags;
+ uint64_t desc;
+ uint64_t used;
+ uint64_t avail;
+ uint64_t log;
+} __attribute__((packed));
+
+struct vhost_region {
+ uint64_t guest_addr;
+ uint64_t size;
+ uint64_t user_addr;
+ uint64_t mmap_offset;
+} __attribute__((packed));
+
+struct vhost_mem {
+ uint32_t num;
+ uint32_t padding;
+ struct vhost_region regions[VHOST_USER_MAX_REGIONS];
+} __attribute__((packed));
+
+struct vhost_config {
+ uint32_t offset;
+ uint32_t size;
+ uint32_t flags;
+ uint8_t payload[VHOST_USER_MAX_CONFIG];
+} __attribute__((packed));
+
+struct vhost_msg {
+ struct vhost_hdr hdr;
+ union {
+ uint64_t u64;
+ struct vhost_vring_state state;
+ struct vhost_vring_addr addr;
+ struct vhost_mem mem;
+ struct vhost_config config;
+ } payload;
+} __attribute__((packed));
+
+struct vring_desc {
+ uint64_t addr;
+ uint32_t len;
+ uint16_t flags;
+ uint16_t next;
+} __attribute__((packed));
+
+struct vring_avail {
+ uint16_t flags;
+ uint16_t idx;
+ uint16_t ring[];
+} __attribute__((packed));
+
+struct vring_used_elem {
+ uint32_t id;
+ uint32_t len;
+} __attribute__((packed));
+
+struct vring_used {
+ uint16_t flags;
+ uint16_t idx;
+ struct vring_used_elem ring[];
+} __attribute__((packed));
+
+struct region {
+ uint64_t guest_addr;
+ uint64_t user_addr;
+ uint64_t size;
+ void *map;
+ size_t map_len;
+ uint8_t *base;
+};
+
+struct queue {
+ unsigned int num;
+ struct vring_desc *desc;
+ struct vring_avail *avail;
+ struct vring_used *used;
+ uint16_t last_avail;
+ int kick_fd;
+ int call_fd;
+ bool enabled;
+ /* A notification is held back by the can_notify callback */
+ bool notify_pending;
+};
+
+struct vhost {
+ int fd;
+ const struct vhost_ops *ops;
+ void *user_data;
+ uint64_t features;
+ uint64_t protocol_features;
+ int slave_fd;
+ struct region regions[VHOST_USER_MAX_REGIONS];
+ unsigned int num_regions;
+ struct queue queues[VHOST_USER_MAX_QUEUES];
+ /* Message being received, which may arrive in several parts */
+ struct vhost_msg rx;
+ size_t rx_len;
+ int rx_fds[VHOST_USER_MAX_FDS];
+ unsigned int rx_num_fds;
+ bool processing;
+ bool freeing;
+ vhost_debug_func_t debug_callback;
+ void *debug_data;
+};
+
+static void vu_debug(struct vhost *vu, const char *format, ...)
+{
+ char str[128];
+ va_list ap;
+
+ if (!vu->debug_callback)
+ return;
+
+ va_start(ap, format);
+ vsnprintf(str, sizeof(str), format, ap);
+ va_end(ap);
+
+ vu->debug_callback(str, vu->debug_data);
+}
+
+static void *gpa_to_va(struct vhost *vu, uint64_t addr, uint64_t len)
+{
+ unsigned int i;
+
+ for (i = 0; i < vu->num_regions; i++) {
+ struct region *r = &vu->regions[i];
+
+ if (addr >= r->guest_addr && len <= r->size &&
+ addr - r->guest_addr <= r->size - len)
+ return r->base + (addr - r->guest_addr);
+ }
+
+ return NULL;
+}
+
+static void *uva_to_va(struct vhost *vu, uint64_t addr)
+{
+ unsigned int i;
+
+ for (i = 0; i < vu->num_regions; i++) {
+ struct region *r = &vu->regions[i];
+
+ if (addr >= r->user_addr && addr - r->user_addr < r->size)
+ return r->base + (addr - r->user_addr);
+ }
+
+ return NULL;
+}
+
+static void unmap_regions(struct vhost *vu)
+{
+ unsigned int i;
+
+ for (i = 0; i < vu->num_regions; i++)
+ munmap(vu->regions[i].map, vu->regions[i].map_len);
+
+ vu->num_regions = 0;
+}
+
+static void close_queue_fds(struct queue *q)
+{
+ if (q->kick_fd >= 0) {
+ mainloop_remove_fd(q->kick_fd);
+ close(q->kick_fd);
+ q->kick_fd = -1;
+ }
+
+ if (q->call_fd >= 0) {
+ close(q->call_fd);
+ q->call_fd = -1;
+ }
+}
+
+static void reset_queue(struct queue *q)
+{
+ close_queue_fds(q);
+ q->desc = NULL;
+ q->avail = NULL;
+ q->used = NULL;
+ q->num = 0;
+ q->last_avail = 0;
+ q->enabled = false;
+}
+
+static bool queue_ready(struct queue *q)
+{
+ return q->enabled && q->desc && q->avail && q->used && q->num;
+}
+
+/* Send a VRING_CALL request on the back-end channel and wait for the
+ * front-end to acknowledge it. While waiting, the wait callback lets the
+ * caller serve the front-end, e.g. as its time travel calendar.
+ */
+static void notify_inband(struct vhost *vu, unsigned int index)
+{
+ struct vhost_msg msg = {
+ .hdr.request = VHOST_USER_SLAVE_VRING_CALL,
+ .hdr.flags = VHOST_USER_VERSION | VHOST_USER_FLAG_NEED_REPLY,
+ .hdr.size = sizeof(msg.payload.state),
+ .payload.state.index = index,
+ };
+ struct pollfd pfd = { .fd = vu->slave_fd, .events = POLLIN };
+ unsigned int waited = 0;
+
+ if (write(vu->slave_fd, &msg, sizeof(msg.hdr) + msg.hdr.size) < 0) {
+ vu_debug(vu, "queue %u: notification failed", index);
+ return;
+ }
+
+ while (waited < VHOST_USER_REPLY_TIMEOUT) {
+ int ret = poll(&pfd, 1, 1);
+
+ if (ret > 0) {
+ if (read(vu->slave_fd, &msg, sizeof(msg.hdr) +
+ sizeof(msg.payload.u64)) < 0)
+ vu_debug(vu, "queue %u: no reply", index);
+ return;
+ }
+
+ if (ret < 0 && errno != EINTR)
+ break;
+
+ if (vu->ops->wait)
+ vu->ops->wait(vu, vu->user_data);
+
+ waited++;
+ }
+
+ vu_debug(vu, "queue %u: notification not acknowledged", index);
+}
+
+static void notify_driver(struct vhost *vu, unsigned int index)
+{
+ struct queue *q = &vu->queues[index];
+ uint64_t val = 1;
+
+ if (vu->ops->can_notify && !vu->ops->can_notify(vu, vu->user_data)) {
+ q->notify_pending = true;
+ return;
+ }
+
+ q->notify_pending = false;
+
+ if (q->call_fd >= 0) {
+ if (write(q->call_fd, &val, sizeof(val)) < 0)
+ vu_debug(vu, "queue %u: notification failed", index);
+ return;
+ }
+
+ if (vu->slave_fd >= 0 && (vu->protocol_features &
+ (1ULL << VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS)))
+ notify_inband(vu, index);
+}
+
+static void kick_callback(int fd, uint32_t events, void *user_data)
+{
+ struct vhost *vu = user_data;
+ unsigned int i;
+ uint64_t val;
+
+ if (read(fd, &val, sizeof(val)) < 0)
+ return;
+
+ for (i = 0; i < vu->ops->num_queues; i++) {
+ if (vu->queues[i].kick_fd != fd)
+ continue;
+
+ if (vu->ops->queue_notify)
+ vu->ops->queue_notify(vu, i, vu->user_data);
+ break;
+ }
+}
+
+/* Return the descriptor chain head of the next available buffer */
+static int next_avail(struct queue *q)
+{
+ uint16_t head;
+
+ if (!queue_ready(q))
+ return -1;
+
+ if (q->last_avail == le16_to_cpu(q->avail->idx))
+ return -1;
+
+ __sync_synchronize();
+
+ head = le16_to_cpu(q->avail->ring[q->last_avail % q->num]);
+ if (head >= q->num)
+ return -1;
+
+ q->last_avail++;
+
+ return head;
+}
+
+static void add_used(struct vhost *vu, unsigned int index, uint16_t head,
+ uint32_t len)
+{
+ struct queue *q = &vu->queues[index];
+ uint16_t idx = le16_to_cpu(q->used->idx);
+
+ q->used->ring[idx % q->num].id = cpu_to_le32(head);
+ q->used->ring[idx % q->num].len = cpu_to_le32(len);
+
+ __sync_synchronize();
+
+ q->used->idx = cpu_to_le16(idx + 1);
+
+ notify_driver(vu, index);
+}
+
+ssize_t vhost_pop(struct vhost *vu, unsigned int index,
+ void *buf, size_t len)
+{
+ struct queue *q;
+ unsigned int count = 0;
+ size_t offset = 0;
+ int head, i;
+
+ if (index >= vu->ops->num_queues)
+ return -1;
+
+ q = &vu->queues[index];
+
+ head = next_avail(q);
+ if (head < 0)
+ return 0;
+
+ for (i = head;; i = le16_to_cpu(q->desc[i].next)) {
+ struct vring_desc *d = &q->desc[i];
+ uint32_t dlen = le32_to_cpu(d->len);
+ void *data;
+
+ if (le16_to_cpu(d->flags) & (VRING_DESC_F_WRITE |
+ VRING_DESC_F_INDIRECT) ||
+ dlen > len - offset || ++count > q->num) {
+ vu_debug(vu, "queue %u: invalid buffer", index);
+ add_used(vu, index, head, 0);
+ return -1;
+ }
+
+ data = gpa_to_va(vu, le64_to_cpu(d->addr), dlen);
+ if (!data) {
+ vu_debug(vu, "queue %u: invalid address", index);
+ add_used(vu, index, head, 0);
+ return -1;
+ }
+
+ memcpy(buf + offset, data, dlen);
+ offset += dlen;
+
+ if (!(le16_to_cpu(d->flags) & VRING_DESC_F_NEXT))
+ break;
+
+ if (le16_to_cpu(d->next) >= q->num) {
+ add_used(vu, index, head, 0);
+ return -1;
+ }
+ }
+
+ add_used(vu, index, head, 0);
+
+ return offset;
+}
+
+bool vhost_push(struct vhost *vu, unsigned int index,
+ const struct iovec *iov, int iovcnt)
+{
+ struct queue *q;
+ size_t total = 0, len, offset = 0, space = 0;
+ unsigned int count = 0;
+ uint16_t saved;
+ int head, i, v = 0;
+
+ if (index >= vu->ops->num_queues)
+ return false;
+
+ q = &vu->queues[index];
+
+ for (i = 0; i < iovcnt; i++)
+ total += iov[i].iov_len;
+
+ len = total;
+ saved = q->last_avail;
+
+ head = next_avail(q);
+ if (head < 0)
+ return false;
+
+ /* Check that the whole packet fits before writing it */
+ for (i = head;; i = le16_to_cpu(q->desc[i].next)) {
+ struct vring_desc *d = &q->desc[i];
+ uint16_t flags = le16_to_cpu(d->flags);
+
+ if (!(flags & VRING_DESC_F_WRITE) ||
+ (flags & VRING_DESC_F_INDIRECT) ||
+ ++count > q->num)
+ goto invalid;
+
+ space += le32_to_cpu(d->len);
+
+ if (!(le16_to_cpu(d->flags) & VRING_DESC_F_NEXT))
+ break;
+
+ if (le16_to_cpu(d->next) >= q->num)
+ goto invalid;
+ }
+
+ if (space < len) {
+ vu_debug(vu, "queue %u: %zu bytes do not fit in %zu",
+ index, total, space);
+ /* Leave the buffer to the driver, the packet is dropped */
+ q->last_avail = saved;
+ return false;
+ }
+
+ for (i = head; len; i = le16_to_cpu(q->desc[i].next)) {
+ struct vring_desc *d = &q->desc[i];
+ uint32_t dlen = le32_to_cpu(d->len);
+ uint8_t *data;
+ size_t done = 0;
+
+ data = gpa_to_va(vu, le64_to_cpu(d->addr), dlen);
+ if (!data)
+ goto invalid;
+
+ while (done < dlen && v < iovcnt) {
+ size_t n = MIN(dlen - done, iov[v].iov_len - offset);
+
+ memcpy(data + done, iov[v].iov_base + offset, n);
+ done += n;
+ offset += n;
+ len -= n;
+
+ if (offset == iov[v].iov_len) {
+ offset = 0;
+ v++;
+ }
+ }
+
+ if (!len)
+ break;
+ }
+
+ add_used(vu, index, head, total);
+
+ return true;
+
+invalid:
+ vu_debug(vu, "queue %u: invalid buffer", index);
+ add_used(vu, index, head, 0);
+ return false;
+}
+
+static int send_reply(struct vhost *vu, struct vhost_msg *msg,
+ size_t size)
+{
+ msg->hdr.flags = VHOST_USER_VERSION | VHOST_USER_FLAG_REPLY;
+ msg->hdr.size = size;
+
+ if (write(vu->fd, msg, sizeof(msg->hdr) + size) < 0)
+ return -errno;
+
+ return 0;
+}
+
+static int send_u64(struct vhost *vu, struct vhost_msg *msg,
+ uint64_t val)
+{
+ msg->payload.u64 = val;
+
+ return send_reply(vu, msg, sizeof(msg->payload.u64));
+}
+
+static int set_mem_table(struct vhost *vu, struct vhost_msg *msg,
+ int *fds, unsigned int num_fds)
+{
+ unsigned int i;
+
+ if (msg->payload.mem.num > VHOST_USER_MAX_REGIONS ||
+ msg->payload.mem.num != num_fds)
+ return -EINVAL;
+
+ unmap_regions(vu);
+
+ for (i = 0; i < msg->payload.mem.num; i++) {
+ struct vhost_region *m = &msg->payload.mem.regions[i];
+ struct region *r = &vu->regions[i];
+
+ r->map_len = m->size + m->mmap_offset;
+ r->map = mmap(NULL, r->map_len, PROT_READ | PROT_WRITE,
+ MAP_SHARED, fds[i], 0);
+ if (r->map == MAP_FAILED) {
+ vu_debug(vu, "failed to map region %u: %s", i,
+ strerror(errno));
+ unmap_regions(vu);
+ return -errno;
+ }
+
+ r->guest_addr = m->guest_addr;
+ r->user_addr = m->user_addr;
+ r->size = m->size;
+ r->base = r->map + m->mmap_offset;
+ vu->num_regions++;
+ }
+
+ return 0;
+}
+
+static int set_vring_fd(struct vhost *vu, struct vhost_msg *msg,
+ int *fds, unsigned int num_fds)
+{
+ unsigned int index = msg->payload.u64 & VHOST_USER_VRING_INDEX_MASK;
+ bool nofd = msg->payload.u64 & VHOST_USER_VRING_NOFD_MASK;
+ struct queue *q;
+ int fd = -1;
+
+ if (index >= vu->ops->num_queues)
+ return -EINVAL;
+
+ if (!nofd) {
+ if (num_fds != 1)
+ return -EINVAL;
+ fd = fds[0];
+ fds[0] = -1;
+ }
+
+ q = &vu->queues[index];
+
+ switch (msg->hdr.request) {
+ case VHOST_USER_SET_VRING_KICK:
+ if (q->kick_fd >= 0) {
+ mainloop_remove_fd(q->kick_fd);
+ close(q->kick_fd);
+ }
+ q->kick_fd = fd;
+ if (fd >= 0 && mainloop_add_fd(fd, EPOLLIN, kick_callback,
+ vu, NULL) < 0)
+ return -EIO;
+ /* Without VHOST_USER_F_PROTOCOL_FEATURES queues are enabled
+ * once they have a kick file descriptor.
+ */
+ if (!(vu->features & (1ULL << VHOST_USER_F_PROTOCOL_FEATURES)))
+ q->enabled = true;
+ break;
+ case VHOST_USER_SET_VRING_CALL:
+ if (q->call_fd >= 0)
+ close(q->call_fd);
+ q->call_fd = fd;
+ break;
+ default:
+ if (fd >= 0)
+ close(fd);
+ break;
+ }
+
+ return 0;
+}
+
+static int get_config(struct vhost *vu, struct vhost_msg *msg)
+{
+ struct vhost_config *c = &msg->payload.config;
+ uint32_t size = c->size;
+
+ if (size > VHOST_USER_MAX_CONFIG)
+ return -EINVAL;
+
+ memset(c->payload, 0, size);
+
+ if (c->offset < vu->ops->config_len)
+ memcpy(c->payload, vu->ops->config + c->offset,
+ MIN(size, vu->ops->config_len - c->offset));
+
+ return send_reply(vu, msg, offsetof(struct vhost_config,
+ payload) + size);
+}
+
+static int handle_msg(struct vhost *vu, struct vhost_msg *msg,
+ int *fds, unsigned int num_fds)
+{
+ struct queue *q;
+ unsigned int index;
+
+ vu_debug(vu, "request %u size %u", msg->hdr.request, msg->hdr.size);
+
+ switch (msg->hdr.request) {
+ case VHOST_USER_GET_FEATURES:
+ return send_u64(vu, msg, vu->ops->features |
+ (1ULL << VIRTIO_F_VERSION_1) |
+ (1ULL << VHOST_USER_F_PROTOCOL_FEATURES));
+ case VHOST_USER_SET_FEATURES:
+ vu->features = msg->payload.u64;
+ return 0;
+ case VHOST_USER_GET_PROTOCOL_FEATURES:
+ return send_u64(vu, msg,
+ (1ULL << VHOST_USER_PROTOCOL_F_CONFIG) |
+ (1ULL << VHOST_USER_PROTOCOL_F_SLAVE_REQ) |
+ (1ULL << VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS));
+ case VHOST_USER_SET_PROTOCOL_FEATURES:
+ vu->protocol_features = msg->payload.u64;
+ return 0;
+ case VHOST_USER_SET_OWNER:
+ return 0;
+ case VHOST_USER_RESET_OWNER:
+ for (index = 0; index < vu->ops->num_queues; index++)
+ reset_queue(&vu->queues[index]);
+ unmap_regions(vu);
+ return 0;
+ case VHOST_USER_SET_MEM_TABLE:
+ return set_mem_table(vu, msg, fds, num_fds);
+ case VHOST_USER_SET_VRING_KICK:
+ case VHOST_USER_SET_VRING_CALL:
+ case VHOST_USER_SET_VRING_ERR:
+ return set_vring_fd(vu, msg, fds, num_fds);
+ case VHOST_USER_SET_SLAVE_REQ_FD:
+ if (num_fds != 1)
+ return -EINVAL;
+ if (vu->slave_fd >= 0)
+ close(vu->slave_fd);
+ vu->slave_fd = fds[0];
+ fds[0] = -1;
+ return 0;
+ case VHOST_USER_GET_CONFIG:
+ return get_config(vu, msg);
+ case VHOST_USER_SET_CONFIG:
+ return 0;
+ }
+
+ index = msg->payload.state.index;
+ if (index >= vu->ops->num_queues)
+ return -EINVAL;
+
+ q = &vu->queues[index];
+
+ switch (msg->hdr.request) {
+ case VHOST_USER_SET_VRING_NUM:
+ if (!msg->payload.state.num ||
+ msg->payload.state.num > 32768 ||
+ msg->payload.state.num &
+ (msg->payload.state.num - 1))
+ return -EINVAL;
+ q->num = msg->payload.state.num;
+ return 0;
+ case VHOST_USER_SET_VRING_BASE:
+ q->last_avail = msg->payload.state.num;
+ return 0;
+ case VHOST_USER_GET_VRING_BASE:
+ /* The driver stops the queue */
+ q->enabled = false;
+ close_queue_fds(q);
+ msg->payload.state.num = q->last_avail;
+ return send_reply(vu, msg, sizeof(msg->payload.state));
+ case VHOST_USER_SET_VRING_ADDR:
+ q->desc = uva_to_va(vu, msg->payload.addr.desc);
+ q->avail = uva_to_va(vu, msg->payload.addr.avail);
+ q->used = uva_to_va(vu, msg->payload.addr.used);
+ if (!q->desc || !q->avail || !q->used)
+ return -EINVAL;
+ return 0;
+ case VHOST_USER_VRING_KICK:
+ if (vu->ops->queue_notify)
+ vu->ops->queue_notify(vu, index, vu->user_data);
+ return 0;
+ case VHOST_USER_SET_VRING_ENABLE:
+ q->enabled = msg->payload.state.num;
+ if (q->enabled && vu->ops->queue_notify)
+ vu->ops->queue_notify(vu, index, vu->user_data);
+ return 0;
+ }
+
+ vu_debug(vu, "unsupported request %u", msg->hdr.request);
+
+ return -EOPNOTSUPP;
+}
+
+static void close_rx_fds(struct vhost *vu)
+{
+ unsigned int i;
+
+ for (i = 0; i < vu->rx_num_fds; i++)
+ close(vu->rx_fds[i]);
+
+ vu->rx_num_fds = 0;
+}
+
+/* Receive the next part of the message without blocking. Returns 0 once
+ * the message is complete, with its file descriptors moved to fds.
+ */
+static int recv_msg(struct vhost *vu, struct vhost_msg *msg,
+ int *fds, unsigned int *num_fds)
+{
+ char control[CMSG_SPACE(sizeof(int) * VHOST_USER_MAX_FDS)];
+ size_t hdr_len = sizeof(vu->rx.hdr);
+ struct iovec iov;
+ struct msghdr mh = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = control,
+ .msg_controllen = sizeof(control),
+ };
+ struct cmsghdr *cmsg;
+ size_t total;
+ ssize_t len;
+
+ if (vu->rx_len < hdr_len)
+ total = hdr_len;
+ else
+ total = hdr_len + vu->rx.hdr.size;
+
+ iov.iov_base = (uint8_t *) &vu->rx + vu->rx_len;
+ iov.iov_len = total - vu->rx_len;
+
+ len = recvmsg(vu->fd, &mh, MSG_CMSG_CLOEXEC | MSG_DONTWAIT);
+ if (len < 0 && (errno == EAGAIN || errno == EINTR))
+ return -EAGAIN;
+ if (len <= 0)
+ return -EIO;
+
+ for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) {
+ unsigned int n, i;
+ int *cfds;
+
+ if (cmsg->cmsg_level != SOL_SOCKET ||
+ cmsg->cmsg_type != SCM_RIGHTS)
+ continue;
+
+ n = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+ cfds = (int *) CMSG_DATA(cmsg);
+
+ for (i = 0; i < n; i++) {
+ if (vu->rx_num_fds < VHOST_USER_MAX_FDS)
+ vu->rx_fds[vu->rx_num_fds++] = cfds[i];
+ else
+ close(cfds[i]);
+ }
+ }
+
+ vu->rx_len += len;
+
+ if (vu->rx_len == hdr_len && vu->rx.hdr.size > sizeof(vu->rx.payload))
+ return -EINVAL;
+
+ if (vu->rx_len < hdr_len || vu->rx_len < hdr_len + vu->rx.hdr.size)
+ return -EAGAIN;
+
+ memcpy(msg, &vu->rx, vu->rx_len);
+ memcpy(fds, vu->rx_fds, vu->rx_num_fds * sizeof(int));
+ *num_fds = vu->rx_num_fds;
+
+ memset(&vu->rx, 0, sizeof(vu->rx));
+ vu->rx_len = 0;
+ vu->rx_num_fds = 0;
+
+ return 0;
+}
+
+static bool process_msg(struct vhost *vu, bool *failed)
+{
+ struct vhost_msg msg;
+ int fds[VHOST_USER_MAX_FDS];
+ unsigned int num_fds, i;
+ bool need_reply;
+ int err;
+
+ memset(&msg, 0, sizeof(msg));
+
+ err = recv_msg(vu, &msg, fds, &num_fds);
+ if (err == -EAGAIN)
+ return true;
+ if (err < 0) {
+ *failed = true;
+ return false;
+ }
+
+ need_reply = msg.hdr.flags & VHOST_USER_FLAG_NEED_REPLY;
+
+ err = handle_msg(vu, &msg, fds, num_fds);
+
+ /* File descriptors taken over are set to -1, the memory mappings
+ * keep the memory referenced.
+ */
+ for (i = 0; i < num_fds; i++) {
+ if (fds[i] >= 0)
+ close(fds[i]);
+ }
+
+ if (need_reply && msg.hdr.request != VHOST_USER_GET_FEATURES &&
+ msg.hdr.request != VHOST_USER_GET_PROTOCOL_FEATURES &&
+ msg.hdr.request != VHOST_USER_GET_VRING_BASE &&
+ msg.hdr.request != VHOST_USER_GET_CONFIG)
+ send_u64(vu, &msg, err < 0 ? 1 : 0);
+
+ if (err < 0)
+ vu_debug(vu, "request %u failed: %s", msg.hdr.request,
+ strerror(-err));
+
+ return true;
+}
+
+/* Handle the pending messages. A message may trigger the calendar, which
+ * processes the connections before advancing time, this one included, so
+ * a nested call returns right away and leaves its messages to the outer
+ * one, which alone may remove the connection, once done with it.
+ */
+static void process_msgs(struct vhost *vu)
+{
+ bool failed = false;
+ int avail;
+
+ if (vu->fd < 0 || vu->processing)
+ return;
+
+ vu->processing = true;
+
+ while (process_msg(vu, &failed)) {
+ if (ioctl(vu->fd, FIONREAD, &avail) < 0 || avail <= 0)
+ break;
+ }
+
+ vu->processing = false;
+
+ if (failed)
+ mainloop_remove_fd(vu->fd);
+}
+
+static void read_callback(int fd, uint32_t events, void *user_data)
+{
+ struct vhost *vu = user_data;
+
+ if (events & (EPOLLERR | EPOLLHUP)) {
+ mainloop_remove_fd(vu->fd);
+ return;
+ }
+
+ process_msgs(vu);
+}
+
+void vhost_flush_notify(struct vhost *vu)
+{
+ unsigned int i;
+
+ for (i = 0; i < vu->ops->num_queues; i++) {
+ if (vu->queues[i].notify_pending)
+ notify_driver(vu, i);
+ }
+}
+
+void vhost_process(struct vhost *vu)
+{
+ process_msgs(vu);
+}
+
+static void vu_destroy(void *user_data)
+{
+ struct vhost *vu = user_data;
+ unsigned int i;
+
+ for (i = 0; i < VHOST_USER_MAX_QUEUES; i++)
+ reset_queue(&vu->queues[i]);
+
+ unmap_regions(vu);
+ close_rx_fds(vu);
+
+ if (vu->slave_fd >= 0) {
+ close(vu->slave_fd);
+ vu->slave_fd = -1;
+ }
+
+ close(vu->fd);
+ vu->fd = -1;
+
+ if (!vu->freeing && vu->ops->disconnect)
+ vu->ops->disconnect(vu, vu->user_data);
+}
+
+struct vhost *vhost_new(int fd, const struct vhost_ops *ops,
+ void *user_data)
+{
+ struct vhost *vu;
+ unsigned int i;
+
+ if (ops->num_queues > VHOST_USER_MAX_QUEUES ||
+ ops->config_len > VHOST_USER_MAX_CONFIG)
+ return NULL;
+
+ vu = new0(struct vhost, 1);
+ vu->fd = fd;
+ vu->slave_fd = -1;
+ vu->ops = ops;
+ vu->user_data = user_data;
+
+ for (i = 0; i < VHOST_USER_MAX_QUEUES; i++) {
+ vu->queues[i].kick_fd = -1;
+ vu->queues[i].call_fd = -1;
+ }
+
+ if (mainloop_add_fd(fd, EPOLLIN, read_callback, vu, vu_destroy) < 0) {
+ free(vu);
+ return NULL;
+ }
+
+ return vu;
+}
+
+void vhost_free(struct vhost *vu)
+{
+ if (!vu)
+ return;
+
+ vu->freeing = true;
+
+ if (vu->fd >= 0)
+ mainloop_remove_fd(vu->fd);
+
+ free(vu);
+}
+
+void vhost_set_debug(struct vhost *vu,
+ vhost_debug_func_t callback, void *user_data)
+{
+ vu->debug_callback = callback;
+ vu->debug_data = user_data;
+}
diff --git a/emulator/vhost.h b/emulator/vhost.h
new file mode 100644
index 000000000000..a5ec92f9594c
--- /dev/null
+++ b/emulator/vhost.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2026 Intel Corporation
+ *
+ *
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+
+struct vhost;
+
+struct vhost_ops {
+ /* Device specific virtio feature bits, VERSION_1 is always offered */
+ uint64_t features;
+ unsigned int num_queues;
+ /* Device configuration space, read with VHOST_USER_GET_CONFIG */
+ const void *config;
+ size_t config_len;
+ /* The driver made buffers available on a queue */
+ void (*queue_notify)(struct vhost *vu, unsigned int index,
+ void *user_data);
+ /* The connection was closed */
+ void (*disconnect)(struct vhost *vu, void *user_data);
+ /* Called repeatedly while waiting for the front-end to acknowledge
+ * an in-band notification
+ */
+ void (*wait)(struct vhost *vu, void *user_data);
+ /* If set and returning false, notifications to the driver are held
+ * back until vhost_flush_notify()
+ */
+ bool (*can_notify)(struct vhost *vu, void *user_data);
+};
+
+struct vhost *vhost_new(int fd, const struct vhost_ops *ops,
+ void *user_data);
+void vhost_free(struct vhost *vu);
+
+/* Send the notifications held back by can_notify */
+void vhost_flush_notify(struct vhost *vu);
+
+/* Process the messages pending on the connection without blocking */
+void vhost_process(struct vhost *vu);
+
+typedef void (*vhost_debug_func_t)(const char *str, void *user_data);
+void vhost_set_debug(struct vhost *vu,
+ vhost_debug_func_t callback, void *user_data);
+
+/* Read the next buffer the driver made available on a driver-to-device
+ * queue into buf. Returns the number of bytes read, 0 if no buffer is
+ * available or -1 on error.
+ */
+ssize_t vhost_pop(struct vhost *vu, unsigned int index,
+ void *buf, size_t len);
+
+/* Write the data into the next buffer the driver made available on a
+ * device-to-driver queue and notify the driver. Returns false if no buffer
+ * is available or the data does not fit.
+ */
+bool vhost_push(struct vhost *vu, unsigned int index,
+ const struct iovec *iov, int iovcnt);
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 12/27] emulator: Add vhost-user virtio server to btvirt
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (10 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 11/27] emulator: Add vhost-user backend Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 13/27] emulator: Add time travel calendar " Luiz Augusto von Dentz
` (15 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Add the -V option, which creates a vhost-user server socket on which
each connection gets a new controller, provided to the guest as a virtio
Bluetooth device. This is how User Mode Linux guests, which can only
attach external devices through vhost-user, get emulated controllers.
Packets for the driver are queued until it posts receive buffers, since
the Linux driver keeps only one posted.
Assisted-by: Claude:claude-opus-5-5
---
Makefile.tools | 3 +-
doc/btvirt.rst | 26 +++-
emulator/main.c | 27 +++-
emulator/virtio.c | 323 ++++++++++++++++++++++++++++++++++++++++++++++
emulator/virtio.h | 20 +++
5 files changed, 391 insertions(+), 8 deletions(-)
create mode 100644 emulator/virtio.c
create mode 100644 emulator/virtio.h
diff --git a/Makefile.tools b/Makefile.tools
index cb6c01e5e0d9..6b2adb2f6556 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -103,7 +103,8 @@ emulator_btvirt_SOURCES = emulator/main.c monitor/bt.h \
emulator/smp.c \
emulator/phy.h emulator/phy.c \
emulator/le.h emulator/le.c \
- emulator/vhost.h emulator/vhost.c
+ emulator/vhost.h emulator/vhost.c \
+ emulator/virtio.h emulator/virtio.c
emulator_btvirt_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la
emulator_b1ee_SOURCES = emulator/b1ee.c
diff --git a/doc/btvirt.rst b/doc/btvirt.rst
index 2e016406a4fc..c8e2b0be3cc2 100644
--- a/doc/btvirt.rst
+++ b/doc/btvirt.rst
@@ -32,6 +32,8 @@ and speak HCI. They can be exposed to a Bluetooth stack in several ways:
appear as ``hciN`` on the machine running btvirt;
- as H:4 streams on Unix socket servers (**-s**) or a TCP server (**-t**),
one controller per client connection;
+- as virtio Bluetooth devices on a vhost-user server (**-V**), one
+ controller per connection, for virtual machines;
- as an H:4 stream on a pseudo terminal (**-S**).
All controllers created by one btvirt process share an emulated air
@@ -45,7 +47,8 @@ btvirt runs until it receives **SIGINT** or **SIGTERM**.
OPTIONS
=======
-At least one of **-l**, **-s**, **-t**, **-S** or **-U** must be given.
+At least one of **-l**, **-s**, **-t**, **-V**, **-S** or **-U** must be
+given.
Optional arguments must be attached to the option, as in **-l2**,
**-t45551** or **--server=/tmp/bt**.
@@ -78,6 +81,14 @@ Optional arguments must be attached to the option, as in **-l2**,
attached as an H:4 stream. The server listens on
127.0.0.1 only.
+-V[path], --vhost[=path] Create a vhost-user server socket
+ ``bt-vhost-bredrle`` in the directory *path*
+ (default: ``/tmp``). Each connection gets a new
+ BR/EDR/LE controller, of the version given with
+ **-C**, provided to the guest as a virtio
+ Bluetooth device. See **virtio** for how to
+ attach it to QEMU or User Mode Linux guests.
+
-S, --serial Create a pseudo terminal with a BR/EDR/LE
controller attached as an H:4 stream. The path of
the terminal is printed on startup; attach it
@@ -99,12 +110,12 @@ Optional arguments must be attached to the option, as in **-l2**,
-C version, --core=version Core Specification version of the BR/EDR/LE
controllers created with **-l**, **-s** (on
- ``bt-server-bredrle``) and **-t**. Supported
+ ``bt-server-bredrle``), **-t** and **-V**. Supported
values are ``5.0``, ``5.2``, ``6.0`` and ``6.2``
(default).
--d, --debug Enable debug output for the **-l** and **-s**
- controllers.
+-d, --debug Enable debug output for the **-l**, **-s** and
+ **-V** controllers.
-v, --version Show version.
@@ -133,6 +144,13 @@ Serve controllers to virtual machines, one per connecting VM, the way
$ btvirt --server=/tmp/bt
$ test-runner -u/tmp/bt/bt-server-bredrle -k bzImage -- ...
+Provide controllers to User Mode Linux guests as virtio Bluetooth
+devices, two from the same socket::
+
+ $ btvirt -V/tmp/bt
+ $ linux ... virtio_uml.device=/tmp/bt/bt-vhost-bredrle:40 \
+ virtio_uml.device=/tmp/bt/bt-vhost-bredrle:40
+
Emulate a Bluetooth 5.2 controller over TCP::
$ btvirt -C 5.2 -t
diff --git a/emulator/main.c b/emulator/main.c
index 61c2f0d5c33d..b448b6c00723 100644
--- a/emulator/main.c
+++ b/emulator/main.c
@@ -27,6 +27,7 @@
#include "btdev.h"
#include "server.h"
#include "vhci.h"
+#include "virtio.h"
#include "le.h"
static void signal_callback(int signum, void *user_data)
@@ -49,6 +50,8 @@ static void usage(void)
"\t-S Create local serial port\n"
"\t-s[path=/tmp] Create local server sockets\n"
"\t-t[port=45550] Create a TCP server\n"
+ "\t-V[path=/tmp] Create local vhost-user virtio\n"
+ "\t server socket\n"
"\t-l[num] Number of local controllers\n"
"\t-L Create LE only controller\n"
"\t-U[num] Number of test LE controllers\n"
@@ -56,7 +59,7 @@ static void usage(void)
"\t-A Create AMP controller\n"
"\t-C, --core=<version> Core Specification version of the\n"
"\t BR/EDR/LE controllers created with\n"
- "\t -l, -s and -t:\n"
+ "\t -l, -s, -t and -V:\n"
"\t 5.0, 5.2, 6.0 or 6.2 (default)\n"
"\t-h, --help Show help options\n");
}
@@ -66,6 +69,7 @@ static const struct option main_options[] = {
{ "serial", no_argument, NULL, 'S' },
{ "server", optional_argument, NULL, 's' },
{ "tcp", optional_argument, NULL, 't' },
+ { "vhost", optional_argument, NULL, 'V' },
{ "local", optional_argument, NULL, 'l' },
{ "le", no_argument, NULL, 'L' },
{ "bredr", no_argument, NULL, 'B' },
@@ -126,6 +130,7 @@ int main(int argc, char *argv[])
bool server_enabled = false;
const char *server_path = "/tmp";
uint16_t tcp_port = 0;
+ const char *vhost_path = NULL;
bool serial_enabled = false;
int letest_count = 0;
int vhci_count = 0;
@@ -140,7 +145,7 @@ int main(int argc, char *argv[])
for (;;) {
int opt;
- opt = getopt_long(argc, argv, "dSs::t::l::LBAC:U::vh",
+ opt = getopt_long(argc, argv, "dSs::t::V::l::LBAC:U::vh",
main_options, NULL);
if (opt < 0)
break;
@@ -163,6 +168,9 @@ int main(int argc, char *argv[])
else
tcp_port = 45550;
break;
+ case 'V':
+ vhost_path = optarg ? optarg : "/tmp";
+ break;
case 'l':
if (optarg)
vhci_count = atoi(optarg);
@@ -209,7 +217,7 @@ int main(int argc, char *argv[])
type = bredrle_type;
if (letest_count < 1 && vhci_count < 1 && !server_enabled &&
- !tcp_port && !serial_enabled) {
+ !tcp_port && !serial_enabled && !vhost_path) {
fprintf(stderr, "No emulator specified\n");
return EXIT_FAILURE;
}
@@ -306,5 +314,18 @@ int main(int argc, char *argv[])
fprintf(stderr, "Listening TCP on 127.0.0.1:%d\n", tcp_port);
}
+ if (vhost_path) {
+ struct vtio *vtio;
+ char path[PATH_MAX];
+
+ snprintf(path, sizeof(path), "%s/%s", vhost_path,
+ "bt-vhost-bredrle");
+ vtio = vtio_open(bredrle_type, path);
+ if (!vtio)
+ fprintf(stderr, "Failed to open vhost-user server\n");
+ else if (debug_enabled)
+ vtio_set_debug(vtio, server_debug, "vhost");
+ }
+
return mainloop_run_with_signal(signal_callback, NULL);
}
diff --git a/emulator/virtio.c b/emulator/virtio.c
new file mode 100644
index 000000000000..47889027075c
--- /dev/null
+++ b/emulator/virtio.c
@@ -0,0 +1,323 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2026 Intel Corporation
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "src/shared/mainloop.h"
+#include "src/shared/queue.h"
+#include "src/shared/util.h"
+
+#include "btdev.h"
+#include "vhost.h"
+#include "virtio.h"
+
+/* Virtio device ID 40, see include/uapi/linux/virtio_bt.h in Linux */
+#define VTIO_F_MSFT_EXT 1
+
+#define VTIO_VQ_TX 0
+#define VTIO_VQ_RX 1
+#define VTIO_NUM_VQS 2
+
+#define VTIO_MSFT_OPCODE 0xfc1e
+
+/* The Linux driver posts receive buffers of this size */
+#define VTIO_MAX_PKT 1000
+
+struct vtio_config {
+ uint8_t type;
+ uint16_t vendor;
+ uint16_t msft_opcode;
+} __attribute__((packed));
+
+struct vtio {
+ enum btdev_type type;
+ int fd;
+ struct queue *clients;
+ vtio_debug_func_t debug_callback;
+ void *debug_data;
+};
+
+struct client {
+ struct vtio *vtio;
+ struct vhost *vu;
+ struct btdev *btdev;
+ /* Packets for the driver, waiting for receive buffers */
+ struct queue *pending;
+};
+
+struct packet {
+ size_t len;
+ uint8_t data[];
+};
+
+static struct vtio_config config;
+
+static void vtio_debug(struct vtio *vtio, const char *format, ...)
+{
+ char str[128];
+ va_list ap;
+
+ if (!vtio || !vtio->debug_callback)
+ return;
+
+ va_start(ap, format);
+ vsnprintf(str, sizeof(str), format, ap);
+ va_end(ap);
+
+ vtio->debug_callback(str, vtio->debug_data);
+}
+
+static void client_free(void *data)
+{
+ struct client *client = data;
+
+ vhost_free(client->vu);
+ btdev_destroy(client->btdev);
+ queue_destroy(client->pending, free);
+ free(client);
+}
+
+static void flush_pending(struct client *client)
+{
+ struct packet *pkt;
+
+ while ((pkt = queue_peek_head(client->pending))) {
+ struct iovec iov = { pkt->data, pkt->len };
+
+ if (!vhost_push(client->vu, VTIO_VQ_RX, &iov, 1))
+ break;
+
+ queue_pop_head(client->pending);
+ free(pkt);
+ }
+}
+
+static void btdev_send(const struct iovec *iov, int iovlen, void *user_data)
+{
+ struct client *client = user_data;
+ struct packet *pkt;
+ size_t len = 0;
+ int i;
+
+ for (i = 0; i < iovlen; i++)
+ len += iov[i].iov_len;
+
+ if (len > VTIO_MAX_PKT) {
+ vtio_debug(client->vtio, "dropping %zu bytes packet", len);
+ return;
+ }
+
+ pkt = malloc(sizeof(*pkt) + len);
+ if (!pkt)
+ return;
+
+ pkt->len = 0;
+ for (i = 0; i < iovlen; i++) {
+ memcpy(pkt->data + pkt->len, iov[i].iov_base, iov[i].iov_len);
+ pkt->len += iov[i].iov_len;
+ }
+
+ /* The driver may have no receive buffer posted, e.g. for back to
+ * back events, so keep the packet until it posts one.
+ */
+ queue_push_tail(client->pending, pkt);
+ flush_pending(client);
+}
+
+static void queue_notify(struct vhost *vu, unsigned int index,
+ void *user_data)
+{
+ struct client *client = user_data;
+ uint8_t buf[VTIO_MAX_PKT + 4];
+ ssize_t len;
+
+ switch (index) {
+ case VTIO_VQ_TX:
+ while ((len = vhost_pop(vu, index, buf, sizeof(buf)))) {
+ if (len > 0)
+ btdev_receive_h4(client->btdev, buf, len);
+ }
+ break;
+ case VTIO_VQ_RX:
+ flush_pending(client);
+ break;
+ }
+}
+
+static void disconnect(struct vhost *vu, void *user_data)
+{
+ struct client *client = user_data;
+
+ vtio_debug(client->vtio, "client disconnected");
+
+ /* vu is being torn down by the mainloop and freed by client_free */
+ if (client->vtio)
+ queue_remove(client->vtio->clients, client);
+
+ client_free(client);
+}
+
+static const struct vhost_ops vhost_ops = {
+ .features = 1ULL << VTIO_F_MSFT_EXT,
+ .num_queues = VTIO_NUM_VQS,
+ .config = &config,
+ .config_len = sizeof(config),
+ .queue_notify = queue_notify,
+ .disconnect = disconnect,
+};
+
+static void vu_debug(const char *str, void *user_data)
+{
+ struct client *client = user_data;
+
+ vtio_debug(client->vtio, "vhost-user: %s", str);
+}
+
+static void accept_callback(int fd, uint32_t events, void *user_data)
+{
+ struct vtio *vtio = user_data;
+ struct client *client;
+ int nfd;
+
+ if (events & (EPOLLERR | EPOLLHUP)) {
+ mainloop_remove_fd(vtio->fd);
+ return;
+ }
+
+ nfd = accept(vtio->fd, NULL, NULL);
+ if (nfd < 0)
+ return;
+
+ client = new0(struct client, 1);
+ client->vtio = vtio;
+ client->pending = queue_new();
+
+ client->btdev = btdev_create(vtio->type, 0x42);
+ if (!client->btdev)
+ goto fail;
+
+ btdev_set_msft_opcode(client->btdev, VTIO_MSFT_OPCODE);
+ btdev_set_send_handler(client->btdev, btdev_send, client);
+
+ client->vu = vhost_new(nfd, &vhost_ops, client);
+ if (!client->vu)
+ goto fail;
+
+ vhost_set_debug(client->vu, vu_debug, client);
+
+ queue_push_tail(vtio->clients, client);
+
+ vtio_debug(vtio, "client connected");
+
+ return;
+
+fail:
+ close(nfd);
+ btdev_destroy(client->btdev);
+ queue_destroy(client->pending, free);
+ free(client);
+}
+
+static void detach_client(void *data)
+{
+ struct client *client = data;
+
+ client->vtio = NULL;
+}
+
+static void vtio_destroy(void *user_data)
+{
+ struct vtio *vtio = user_data;
+
+ queue_destroy(vtio->clients, detach_client);
+ close(vtio->fd);
+ free(vtio);
+}
+
+struct vtio *vtio_open(enum btdev_type type, const char *path)
+{
+ struct vtio *vtio;
+ struct sockaddr_un addr;
+
+ if (strlen(path) >= sizeof(addr.sun_path))
+ return NULL;
+
+ config.msft_opcode = cpu_to_le16(VTIO_MSFT_OPCODE);
+
+ vtio = new0(struct vtio, 1);
+ vtio->type = type;
+ vtio->clients = queue_new();
+
+ vtio->fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ if (vtio->fd < 0) {
+ perror("Failed to open vhost-user socket");
+ goto fail;
+ }
+
+ unlink(path);
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ memcpy(addr.sun_path, path, strlen(path));
+
+ if (bind(vtio->fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ perror("Failed to bind vhost-user socket");
+ goto fail;
+ }
+
+ if (listen(vtio->fd, 5) < 0) {
+ perror("Failed to listen on vhost-user socket");
+ goto fail;
+ }
+
+ if (mainloop_add_fd(vtio->fd, EPOLLIN, accept_callback, vtio,
+ vtio_destroy) < 0)
+ goto fail;
+
+ return vtio;
+
+fail:
+ if (vtio->fd >= 0)
+ close(vtio->fd);
+ queue_destroy(vtio->clients, NULL);
+ free(vtio);
+ return NULL;
+}
+
+void vtio_close(struct vtio *vtio)
+{
+ if (!vtio)
+ return;
+
+ mainloop_remove_fd(vtio->fd);
+}
+
+bool vtio_set_debug(struct vtio *vtio,
+ vtio_debug_func_t callback, void *user_data)
+{
+ if (!vtio)
+ return false;
+
+ vtio->debug_callback = callback;
+ vtio->debug_data = user_data;
+
+ return true;
+}
diff --git a/emulator/virtio.h b/emulator/virtio.h
new file mode 100644
index 000000000000..1e1b44f0006a
--- /dev/null
+++ b/emulator/virtio.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2026 Intel Corporation
+ *
+ *
+ */
+
+#include <stdbool.h>
+
+struct vtio;
+
+struct vtio *vtio_open(enum btdev_type type, const char *path);
+void vtio_close(struct vtio *vtio);
+
+typedef void (*vtio_debug_func_t)(const char *str, void *user_data);
+bool vtio_set_debug(struct vtio *vtio,
+ vtio_debug_func_t callback, void *user_data);
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 13/27] emulator: Add time travel calendar to btvirt
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (11 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 12/27] emulator: Add vhost-user virtio server to btvirt Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 14/27] emulator: Add virtio console and time travel client " Luiz Augusto von Dentz
` (14 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Add the --time option, which makes btvirt the time travel calendar of
User Mode Linux guests started with time-travel=ext, so they share a
simulated clock with btvirt without an external time controller. Time
only advances when every guest waits, and the timers of the emulated
controllers run on the simulated clock. A guest runs exactly at the
time it requested, and GET answers with the current time, which a guest
takes as the time of an interrupt.
The calendar only uses messages. UML uses the file descriptors of the
START acknowledgment without checking how many it received, so send a
shared memory it rejects and /dev/null.
In lock-step, send FREE_UNTIL with the next event of any other client
or timer before RUN, so a guest does not ask before each of its own
events, about four round trips each, and lower it when an earlier event
shows up. A guest sends UPDATE before each virtio notification, but does
not wait for it to be handled, so take its time from UPDATE and stop it
there before acknowledging: it may not run ahead of what the
notification causes, such as an interrupt from the device.
Add tt_set_skew() to let time advance up to a skew beyond the earliest
running guest, and a hold callback for pacing: while held, time advances
no faster than real time, and no FREE_UNTIL is given so pacing can stop
a guest.
Assisted-by: Claude:claude-opus-5-5
---
Makefile.tools | 4 +-
doc/btvirt.rst | 22 +-
emulator/main.c | 27 +-
emulator/timeout.c | 102 ++++++
emulator/timetravel.c | 743 ++++++++++++++++++++++++++++++++++++++++++
emulator/timetravel.h | 55 ++++
emulator/virtio.c | 33 ++
7 files changed, 981 insertions(+), 5 deletions(-)
create mode 100644 emulator/timeout.c
create mode 100644 emulator/timetravel.c
create mode 100644 emulator/timetravel.h
diff --git a/Makefile.tools b/Makefile.tools
index 6b2adb2f6556..bc92bc02b83d 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -104,7 +104,9 @@ emulator_btvirt_SOURCES = emulator/main.c monitor/bt.h \
emulator/phy.h emulator/phy.c \
emulator/le.h emulator/le.c \
emulator/vhost.h emulator/vhost.c \
- emulator/virtio.h emulator/virtio.c
+ emulator/virtio.h emulator/virtio.c \
+ emulator/timetravel.h emulator/timetravel.c \
+ emulator/timeout.c
emulator_btvirt_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la
emulator_b1ee_SOURCES = emulator/b1ee.c
diff --git a/doc/btvirt.rst b/doc/btvirt.rst
index c8e2b0be3cc2..16ef55dbb7a0 100644
--- a/doc/btvirt.rst
+++ b/doc/btvirt.rst
@@ -47,8 +47,8 @@ btvirt runs until it receives **SIGINT** or **SIGTERM**.
OPTIONS
=======
-At least one of **-l**, **-s**, **-t**, **-V**, **-S** or **-U** must be
-given.
+At least one of **-l**, **-s**, **-t**, **-V**, **-S**, **-U** or
+**--time** must be given.
Optional arguments must be attached to the option, as in **-l2**,
**-t45551** or **--server=/tmp/bt**.
@@ -89,6 +89,17 @@ Optional arguments must be attached to the option, as in **-l2**,
Bluetooth device. See **virtio** for how to
attach it to QEMU or User Mode Linux guests.
+--time[=path] Create a time travel calendar socket ``bt-time``
+ in the directory *path* (default: ``/tmp``) for
+ User Mode Linux guests started with
+ ``time-travel=ext:<path>/bt-time``. The guests
+ and btvirt then share a simulated clock: time
+ only advances when every guest waits, and the
+ timers of the emulated controllers run on it.
+ Notifications of **-V** controllers are
+ exchanged in-band, so they are ordered with
+ the guests' clocks.
+
-S, --serial Create a pseudo terminal with a BR/EDR/LE
controller attached as an H:4 stream. The path of
the terminal is printed on startup; attach it
@@ -151,6 +162,13 @@ devices, two from the same socket::
$ linux ... virtio_uml.device=/tmp/bt/bt-vhost-bredrle:40 \
virtio_uml.device=/tmp/bt/bt-vhost-bredrle:40
+Run the same guests on a simulated clock shared with btvirt::
+
+ $ btvirt --time=/tmp/bt -V/tmp/bt
+ $ linux ... time-travel=ext:/tmp/bt/bt-time \
+ virtio_uml.device=/tmp/bt/bt-vhost-bredrle:40 \
+ virtio_uml.device=/tmp/bt/bt-vhost-bredrle:40
+
Emulate a Bluetooth 5.2 controller over TCP::
$ btvirt -C 5.2 -t
diff --git a/emulator/main.c b/emulator/main.c
index b448b6c00723..a3b9456bb642 100644
--- a/emulator/main.c
+++ b/emulator/main.c
@@ -28,6 +28,7 @@
#include "server.h"
#include "vhci.h"
#include "virtio.h"
+#include "timetravel.h"
#include "le.h"
static void signal_callback(int signum, void *user_data)
@@ -52,6 +53,8 @@ static void usage(void)
"\t-t[port=45550] Create a TCP server\n"
"\t-V[path=/tmp] Create local vhost-user virtio\n"
"\t server socket\n"
+ "\t--time[=path=/tmp] Create time travel calendar socket\n"
+ "\t for User Mode Linux guests\n"
"\t-l[num] Number of local controllers\n"
"\t-L Create LE only controller\n"
"\t-U[num] Number of test LE controllers\n"
@@ -70,6 +73,7 @@ static const struct option main_options[] = {
{ "server", optional_argument, NULL, 's' },
{ "tcp", optional_argument, NULL, 't' },
{ "vhost", optional_argument, NULL, 'V' },
+ { "time", optional_argument, NULL, 'I' },
{ "local", optional_argument, NULL, 'l' },
{ "le", no_argument, NULL, 'L' },
{ "bredr", no_argument, NULL, 'B' },
@@ -131,6 +135,7 @@ int main(int argc, char *argv[])
const char *server_path = "/tmp";
uint16_t tcp_port = 0;
const char *vhost_path = NULL;
+ const char *time_path = NULL;
bool serial_enabled = false;
int letest_count = 0;
int vhci_count = 0;
@@ -145,7 +150,7 @@ int main(int argc, char *argv[])
for (;;) {
int opt;
- opt = getopt_long(argc, argv, "dSs::t::V::l::LBAC:U::vh",
+ opt = getopt_long(argc, argv, "dSs::t::V::I::l::LBAC:U::vh",
main_options, NULL);
if (opt < 0)
break;
@@ -168,6 +173,9 @@ int main(int argc, char *argv[])
else
tcp_port = 45550;
break;
+ case 'I':
+ time_path = optarg ? optarg : "/tmp";
+ break;
case 'V':
vhost_path = optarg ? optarg : "/tmp";
break;
@@ -217,13 +225,28 @@ int main(int argc, char *argv[])
type = bredrle_type;
if (letest_count < 1 && vhci_count < 1 && !server_enabled &&
- !tcp_port && !serial_enabled && !vhost_path) {
+ !tcp_port && !serial_enabled && !vhost_path &&
+ !time_path) {
fprintf(stderr, "No emulator specified\n");
return EXIT_FAILURE;
}
printf("Bluetooth emulator ver %s\n", VERSION);
+ /* Before any controller, so their timers use the simulated clock */
+ if (time_path) {
+ char path[PATH_MAX];
+
+ snprintf(path, sizeof(path), "%s/%s", time_path, "bt-time");
+ if (!tt_open(path)) {
+ fprintf(stderr, "Failed to open time travel socket\n");
+ return EXIT_FAILURE;
+ }
+
+ if (debug_enabled)
+ tt_set_debug(server_debug, "time");
+ }
+
for (i = 0; i < letest_count; i++) {
struct bt_le *le;
diff --git a/emulator/timeout.c b/emulator/timeout.c
new file mode 100644
index 000000000000..772cbfa4d4ce
--- /dev/null
+++ b/emulator/timeout.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2026 Intel Corporation
+ *
+ *
+ */
+
+/* Timeouts for btvirt: on the simulated clock of the time travel calendar
+ * when it is enabled, otherwise on the mainloop like
+ * src/shared/timeout-mainloop.c.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include "src/shared/mainloop.h"
+#include "src/shared/util.h"
+#include "src/shared/timeout.h"
+
+#include "timetravel.h"
+
+/* Calendar timer IDs are kept apart from the mainloop ones */
+#define TT_TIMEOUT_ID 0x80000000U
+
+struct timeout_data {
+ int id;
+ timeout_func_t func;
+ timeout_destroy_func_t destroy;
+ unsigned int timeout;
+ void *user_data;
+};
+
+static void timeout_callback(int id, void *user_data)
+{
+ struct timeout_data *data = user_data;
+
+ if (data->func(data->user_data) &&
+ !mainloop_modify_timeout(data->id, data->timeout))
+ return;
+
+ mainloop_remove_timeout(data->id);
+}
+
+static void timeout_destroy(void *user_data)
+{
+ struct timeout_data *data = user_data;
+
+ if (data->destroy)
+ data->destroy(data->user_data);
+
+ free(data);
+}
+
+unsigned int timeout_add(unsigned int timeout, timeout_func_t func,
+ void *user_data, timeout_destroy_func_t destroy)
+{
+ struct timeout_data *data;
+
+ if (tt_enabled())
+ return TT_TIMEOUT_ID | tt_timeout_add(timeout, func, user_data,
+ destroy);
+
+ data = new0(struct timeout_data, 1);
+ data->func = func;
+ data->user_data = user_data;
+ data->timeout = timeout;
+ data->destroy = destroy;
+
+ data->id = mainloop_add_timeout(timeout, timeout_callback, data,
+ timeout_destroy);
+ if (data->id < 0) {
+ free(data);
+ return 0;
+ }
+
+ return (unsigned int) data->id;
+}
+
+void timeout_remove(unsigned int id)
+{
+ if (!id)
+ return;
+
+ if (id & TT_TIMEOUT_ID) {
+ tt_timeout_remove(id & ~TT_TIMEOUT_ID);
+ return;
+ }
+
+ mainloop_remove_timeout((int) id);
+}
+
+unsigned int timeout_add_seconds(unsigned int timeout, timeout_func_t func,
+ void *user_data, timeout_destroy_func_t destroy)
+{
+ return timeout_add(timeout * 1000, func, user_data, destroy);
+}
diff --git a/emulator/timetravel.c b/emulator/timetravel.c
new file mode 100644
index 000000000000..c1938ed23bdc
--- /dev/null
+++ b/emulator/timetravel.c
@@ -0,0 +1,743 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2026 Intel Corporation
+ *
+ *
+ */
+
+#define _GNU_SOURCE
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/epoll.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "src/shared/mainloop.h"
+#include "src/shared/queue.h"
+#include "src/shared/util.h"
+
+#include "timetravel.h"
+
+/* Protocol of include/uapi/linux/um_timetravel.h in Linux */
+#define TT_ACK 0
+#define TT_START 1
+#define TT_REQUEST 2
+#define TT_WAIT 3
+#define TT_GET 4
+#define TT_UPDATE 5
+#define TT_RUN 6
+#define TT_FREE_UNTIL 7
+#define TT_GET_TOD 8
+#define TT_BROADCAST 9
+
+#define TT_SHM_SIZE 4096
+
+struct tt_msg {
+ uint32_t op;
+ uint32_t seq;
+ uint64_t time;
+} __attribute__((packed));
+
+struct tt_client {
+ int fd;
+ uint16_t id;
+ bool waiting;
+ bool has_request;
+ uint64_t request;
+ /* Time the client runs at, as far as known */
+ uint64_t time;
+ /* Time the running client may run until without asking, 0 if none */
+ uint64_t free_until;
+};
+
+struct tt_flush {
+ tt_flush_func_t func;
+ void *user_data;
+};
+
+struct tt_timer {
+ unsigned int id;
+ uint64_t time;
+ uint64_t interval;
+ tt_timeout_func_t func;
+ tt_destroy_func_t destroy;
+ void *user_data;
+};
+
+static struct {
+ int fd;
+ struct queue *clients;
+ struct queue *timers;
+ uint64_t now;
+ uint64_t skew;
+ uint64_t tod;
+ uint16_t next_id;
+ unsigned int next_timer;
+ bool scheduling;
+ struct queue *flushes;
+ tt_hold_func_t hold;
+ void *hold_data;
+ /* While held, time advances no faster than real time from here */
+ bool held;
+ uint64_t held_sim;
+ uint64_t held_wall;
+ int pace_id;
+ tt_debug_func_t debug;
+ void *debug_data;
+} tt = { .fd = -1 };
+
+static void tt_debug(const char *format, ...)
+{
+ char str[128];
+ va_list ap;
+
+ if (!tt.debug)
+ return;
+
+ va_start(ap, format);
+ vsnprintf(str, sizeof(str), format, ap);
+ va_end(ap);
+
+ tt.debug(str, tt.debug_data);
+}
+
+static void send_msg(struct tt_client *client, uint32_t op, uint32_t seq,
+ uint64_t time)
+{
+ struct tt_msg msg = { .op = op, .seq = seq, .time = time };
+
+ if (write(client->fd, &msg, sizeof(msg)) < 0)
+ tt_debug("client %u: write failed", client->id);
+}
+
+/* The ACK to START may carry a shared memory and a log file descriptor.
+ * This calendar only uses messages, but UML uses the descriptors it
+ * receives without checking how many it got, so send a shared memory it
+ * rejects (version 0) and /dev/null.
+ */
+static void send_start_ack(struct tt_client *client, uint32_t seq)
+{
+ struct tt_msg msg = { .op = TT_ACK, .seq = seq, .time = client->id };
+ char control[CMSG_SPACE(sizeof(int) * 2)];
+ struct iovec iov = { &msg, sizeof(msg) };
+ struct msghdr mh = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = control,
+ .msg_controllen = sizeof(control),
+ };
+ struct cmsghdr *cmsg;
+ int fds[2];
+
+ fds[0] = memfd_create("bt-time-shm", MFD_CLOEXEC);
+ fds[1] = open("/dev/null", O_WRONLY | O_CLOEXEC);
+ if (fds[0] < 0 || fds[1] < 0 || ftruncate(fds[0], TT_SHM_SIZE) < 0) {
+ tt_debug("client %u: no file descriptors for START",
+ client->id);
+ send_msg(client, TT_ACK, seq, client->id);
+ goto done;
+ }
+
+ memset(control, 0, sizeof(control));
+ cmsg = CMSG_FIRSTHDR(&mh);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(fds));
+ memcpy(CMSG_DATA(cmsg), fds, sizeof(fds));
+
+ if (sendmsg(client->fd, &mh, 0) < 0)
+ tt_debug("client %u: sendmsg failed", client->id);
+
+done:
+ if (fds[0] >= 0)
+ close(fds[0]);
+ if (fds[1] >= 0)
+ close(fds[1]);
+}
+
+static void find_running(void *data, void *user_data)
+{
+ struct tt_client *client = data;
+ uint64_t *min = user_data;
+
+ if (!client->waiting && client->time < *min)
+ *min = client->time;
+}
+
+static void find_next_client(void *data, void *user_data)
+{
+ struct tt_client *client = data;
+ struct tt_client **next = user_data;
+
+ if (!client->waiting || !client->has_request)
+ return;
+
+ if (!*next || client->request < (*next)->request)
+ *next = client;
+}
+
+static void find_next_timer(void *data, void *user_data)
+{
+ struct tt_timer *timer = data;
+ struct tt_timer **next = user_data;
+
+ if (!*next || timer->time < (*next)->time)
+ *next = timer;
+}
+
+static void timer_free(void *data)
+{
+ struct tt_timer *timer = data;
+
+ if (timer->destroy)
+ timer->destroy(timer->user_data);
+
+ free(timer);
+}
+
+static void fire_timer(struct tt_timer *timer)
+{
+ if (timer->func(timer->user_data)) {
+ /* The callback may have removed the timer */
+ if (queue_find(tt.timers, NULL, timer)) {
+ timer->time = tt.now + timer->interval;
+ return;
+ }
+ }
+
+ if (queue_remove(tt.timers, timer))
+ timer_free(timer);
+}
+
+static void run_flush(void *data, void *user_data)
+{
+ struct tt_flush *flush = data;
+
+ flush->func(flush->user_data);
+}
+
+static void schedule(void);
+
+struct free_limit {
+ struct tt_client *client;
+ uint64_t time;
+};
+
+static void find_free_limit(void *data, void *user_data)
+{
+ struct tt_client *client = data;
+ struct free_limit *limit = user_data;
+
+ if (client == limit->client)
+ return;
+
+ /* A client that has not waited yet may request any time */
+ if (!client->waiting)
+ limit->time = MIN(limit->time, tt.now);
+ else if (client->has_request)
+ limit->time = MIN(limit->time, client->request);
+}
+
+static void find_timer_limit(void *data, void *user_data)
+{
+ struct tt_timer *timer = data;
+ uint64_t *limit = user_data;
+
+ *limit = MIN(*limit, timer->time);
+}
+
+/* How far a client may run without asking: up to the earliest event of any
+ * other client or timer. Only in lock-step, and not while time may be
+ * paced, as pacing must be able to stop the client.
+ */
+static uint64_t free_until(struct tt_client *client)
+{
+ struct free_limit limit = { client, UINT64_MAX };
+
+ if (tt.skew || tt.hold)
+ return 0;
+
+ queue_foreach(tt.clients, find_free_limit, &limit);
+ queue_foreach(tt.timers, find_timer_limit, &limit.time);
+
+ return limit.time;
+}
+
+/* Lower the limit of a running client when an earlier event appeared. This
+ * happens in response to a client, e.g. while it waits for its virtio
+ * notification to be acknowledged, so it receives the new limit before it
+ * runs further.
+ */
+static void update_free_until(void *data, void *user_data)
+{
+ struct tt_client *client = data;
+ uint64_t limit;
+
+ if (client->waiting || !client->free_until)
+ return;
+
+ limit = free_until(client);
+ if (limit >= client->free_until)
+ return;
+
+ client->free_until = limit;
+ send_msg(client, TT_FREE_UNTIL, 0, limit);
+}
+
+static uint64_t wall_now(void)
+{
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+
+ return (uint64_t) ts.tv_sec * 1000000000ULL + ts.tv_nsec;
+}
+
+static void pace_callback(int id, void *user_data)
+{
+ mainloop_remove_timeout(id);
+ tt.pace_id = 0;
+ schedule();
+}
+
+/* Limit of time while held: real time elapsed since the hold started */
+static uint64_t hold_limit(void)
+{
+ bool hold = tt.hold && tt.hold(tt.hold_data);
+
+ if (!hold) {
+ tt.held = false;
+ return UINT64_MAX;
+ }
+
+ if (!tt.held) {
+ tt.held = true;
+ tt.held_sim = tt.now;
+ tt.held_wall = wall_now();
+ }
+
+ return tt.held_sim + (wall_now() - tt.held_wall);
+}
+
+/* Schedule again when real time catches up with the next event */
+static void pace(uint64_t next, uint64_t limit)
+{
+ unsigned int msec = (next - limit) / 1000000ULL + 1;
+
+ if (tt.pace_id > 0)
+ return;
+
+ tt.pace_id = mainloop_add_timeout(MIN(msec, 100U), pace_callback,
+ NULL, NULL);
+}
+
+/* Run whichever is due first, a timer of this process or the client with
+ * the earliest request. In lock-step (no skew) time only advances when
+ * every client waits. With a skew, time may advance up to that much beyond
+ * the earliest running client, so clients run their own events in parallel
+ * while others compute, which takes no simulated time.
+ */
+static void schedule(void)
+{
+ if (tt.scheduling)
+ return;
+
+ tt.scheduling = true;
+
+ while (1) {
+ struct tt_client *client = NULL;
+ struct tt_timer *timer = NULL;
+ uint64_t running = UINT64_MAX;
+ uint64_t limit = UINT64_MAX;
+ uint64_t hold;
+ uint64_t next;
+
+ queue_foreach(tt.flushes, run_flush, NULL);
+
+ if (queue_isempty(tt.clients))
+ break;
+
+ queue_foreach(tt.clients, find_running, &running);
+ if (running != UINT64_MAX) {
+ if (!tt.skew) {
+ queue_foreach(tt.clients, update_free_until,
+ NULL);
+ break;
+ }
+ limit = running + tt.skew;
+ }
+
+ hold = hold_limit();
+
+ queue_foreach(tt.clients, find_next_client, &client);
+ queue_foreach(tt.timers, find_next_timer, &timer);
+
+ if (timer && (!client || timer->time <= client->request)) {
+ next = timer->time;
+ client = NULL;
+ } else if (client) {
+ next = client->request;
+ timer = NULL;
+ } else {
+ break;
+ }
+
+ if (next > limit)
+ break;
+
+ if (next > hold) {
+ pace(next, hold);
+ break;
+ }
+
+ if (next > tt.now)
+ tt.now = next;
+
+ if (timer) {
+ fire_timer(timer);
+ continue;
+ }
+
+ client->waiting = false;
+ client->has_request = false;
+ client->time = next;
+
+ /* Let the client run until the next event of another one,
+ * instead of asking for each of its own events.
+ */
+ client->free_until = free_until(client);
+ if (client->free_until)
+ send_msg(client, TT_FREE_UNTIL, 0, client->free_until);
+
+ send_msg(client, TT_RUN, 0, next);
+ }
+
+ tt.scheduling = false;
+}
+
+void tt_schedule(void)
+{
+ if (tt_enabled())
+ schedule();
+}
+
+static void handle_msg(struct tt_client *client, struct tt_msg *msg)
+{
+ switch (msg->op) {
+ case TT_ACK:
+ /* ACK of a RUN */
+ return;
+ case TT_START:
+ client->id = tt.next_id++;
+ client->time = tt.now;
+ tt_debug("client %u started", client->id);
+ send_start_ack(client, msg->seq);
+ return;
+ case TT_REQUEST:
+ client->has_request = true;
+ /* The guest must run exactly at the time of its event */
+ client->request = msg->time;
+ send_msg(client, TT_ACK, msg->seq, 0);
+ break;
+ case TT_WAIT:
+ client->waiting = true;
+ client->free_until = 0;
+ send_msg(client, TT_ACK, msg->seq, 0);
+ break;
+ case TT_GET:
+ /* The guest takes it as the time of an interrupt: the current
+ * time, which no guest is ahead of.
+ */
+ client->time = MAX(client->time, tt.now);
+ send_msg(client, TT_ACK, msg->seq, client->time);
+ return;
+ case TT_UPDATE:
+ if (msg->time > client->time)
+ client->time = msg->time;
+ /* In lock-step the running client sets the time, which may be
+ * ahead of the calendar when it runs until a FREE_UNTIL.
+ */
+ if (!tt.skew && !client->waiting && client->time > tt.now)
+ tt.now = client->time;
+ /* The guest sends UPDATE before a virtio notification, which
+ * it does not wait for, and runs on meanwhile. Stop it at this
+ * time before acknowledging, so it cannot run ahead of what
+ * the notification causes, e.g. the device's interrupt.
+ */
+ if (client->free_until && client->free_until > client->time) {
+ client->free_until = client->time;
+ send_msg(client, TT_FREE_UNTIL, 0, client->time);
+ }
+ send_msg(client, TT_ACK, msg->seq, 0);
+ break;
+ case TT_GET_TOD:
+ send_msg(client, TT_ACK, msg->seq, tt.tod + tt.now);
+ return;
+ default:
+ tt_debug("client %u: unsupported op %u", client->id, msg->op);
+ send_msg(client, TT_ACK, msg->seq, 0);
+ return;
+ }
+}
+
+/* Returns 1 if a message was handled, 0 if none is pending and -1 if the
+ * connection failed.
+ */
+static int read_msg(struct tt_client *client)
+{
+ struct tt_msg msg;
+ ssize_t len;
+
+ len = recv(client->fd, &msg, sizeof(msg), MSG_DONTWAIT);
+ if (len < 0 && (errno == EAGAIN || errno == EINTR))
+ return 0;
+
+ if (len != sizeof(msg)) {
+ tt_debug("client %u: read %zd", client->id, len);
+ return -1;
+ }
+
+ handle_msg(client, &msg);
+
+ return 1;
+}
+
+static void client_destroy(void *data)
+{
+ struct tt_client *client = data;
+
+ tt_debug("client %u disconnected", client->id);
+
+ queue_remove(tt.clients, client);
+ close(client->fd);
+ free(client);
+
+ /* Clients and back-ends are destroyed in any order on shutdown, so
+ * only schedule while the calendar is open.
+ */
+ if (tt.fd >= 0)
+ schedule();
+}
+
+static void client_callback(int fd, uint32_t events, void *user_data)
+{
+ struct tt_client *client = user_data;
+
+ int ret;
+
+ if (events & (EPOLLERR | EPOLLHUP)) {
+ mainloop_remove_fd(client->fd);
+ return;
+ }
+
+ /* Process everything that is pending before scheduling, messages
+ * may also have been processed already by tt_process().
+ */
+ while ((ret = read_msg(client)) > 0)
+ ;
+
+ if (ret < 0) {
+ mainloop_remove_fd(client->fd);
+ return;
+ }
+
+ schedule();
+}
+
+static void process_client(void *data, void *user_data)
+{
+ struct tt_client *client = data;
+
+ while (read_msg(client) > 0)
+ ;
+}
+
+void tt_process(void)
+{
+ queue_foreach(tt.clients, process_client, NULL);
+}
+
+static void accept_callback(int fd, uint32_t events, void *user_data)
+{
+ struct tt_client *client;
+ int nfd;
+
+ if (events & (EPOLLERR | EPOLLHUP)) {
+ mainloop_remove_fd(fd);
+ return;
+ }
+
+ nfd = accept(fd, NULL, NULL);
+ if (nfd < 0)
+ return;
+
+ client = new0(struct tt_client, 1);
+ client->fd = nfd;
+
+ if (mainloop_add_fd(nfd, EPOLLIN, client_callback, client,
+ client_destroy) < 0) {
+ close(nfd);
+ free(client);
+ return;
+ }
+
+ queue_push_tail(tt.clients, client);
+}
+
+static void tt_destroy(void *user_data)
+{
+ close(tt.fd);
+ tt.fd = -1;
+}
+
+bool tt_open(const char *path)
+{
+ struct sockaddr_un addr;
+ struct timespec ts;
+
+ if (tt.fd >= 0 || strlen(path) >= sizeof(addr.sun_path))
+ return false;
+
+ tt.fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ if (tt.fd < 0)
+ return false;
+
+ unlink(path);
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ memcpy(addr.sun_path, path, strlen(path));
+
+ if (bind(tt.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0 ||
+ listen(tt.fd, 5) < 0)
+ goto fail;
+
+ if (mainloop_add_fd(tt.fd, EPOLLIN, accept_callback, NULL,
+ tt_destroy) < 0)
+ goto fail;
+
+ clock_gettime(CLOCK_REALTIME, &ts);
+ tt.tod = (uint64_t) ts.tv_sec * 1000000000ULL + ts.tv_nsec;
+ tt.clients = queue_new();
+ tt.timers = queue_new();
+ tt.next_id = 1;
+
+ return true;
+
+fail:
+ close(tt.fd);
+ tt.fd = -1;
+ return false;
+}
+
+void tt_close(void)
+{
+ if (tt.fd >= 0)
+ mainloop_remove_fd(tt.fd);
+}
+
+bool tt_enabled(void)
+{
+ return tt.fd >= 0;
+}
+
+uint64_t tt_now(void)
+{
+ return tt.now;
+}
+
+void tt_set_skew(unsigned int msec)
+{
+ tt.skew = (uint64_t) msec * 1000000ULL;
+}
+
+void tt_set_hold(tt_hold_func_t func, void *user_data)
+{
+ tt.hold = func;
+ tt.hold_data = user_data;
+}
+
+static bool match_flush(const void *data, const void *match_data)
+{
+ const struct tt_flush *flush = data;
+ const struct tt_flush *match = match_data;
+
+ return flush->func == match->func &&
+ flush->user_data == match->user_data;
+}
+
+void tt_remove_flush(tt_flush_func_t func, void *user_data)
+{
+ struct tt_flush match = { .func = func, .user_data = user_data };
+
+ free(queue_remove_if(tt.flushes, match_flush, &match));
+}
+
+void tt_add_flush(tt_flush_func_t func, void *user_data)
+{
+ struct tt_flush *flush;
+
+ if (!tt.flushes)
+ tt.flushes = queue_new();
+
+ flush = new0(struct tt_flush, 1);
+ flush->func = func;
+ flush->user_data = user_data;
+
+ queue_push_tail(tt.flushes, flush);
+}
+
+unsigned int tt_timeout_add(unsigned int msec, tt_timeout_func_t func,
+ void *user_data, tt_destroy_func_t destroy)
+{
+ struct tt_timer *timer;
+
+ timer = new0(struct tt_timer, 1);
+ timer->id = ++tt.next_timer;
+ timer->interval = (uint64_t) msec * 1000000ULL;
+ timer->time = tt.now + timer->interval;
+ timer->func = func;
+ timer->user_data = user_data;
+ timer->destroy = destroy;
+
+ queue_push_tail(tt.timers, timer);
+
+ return timer->id;
+}
+
+static bool match_timer(const void *data, const void *match_data)
+{
+ const struct tt_timer *timer = data;
+
+ return timer->id == PTR_TO_UINT(match_data);
+}
+
+void tt_timeout_remove(unsigned int id)
+{
+ struct tt_timer *timer;
+
+ timer = queue_remove_if(tt.timers, match_timer, UINT_TO_PTR(id));
+ if (timer)
+ timer_free(timer);
+}
+
+void tt_set_debug(tt_debug_func_t callback, void *user_data)
+{
+ tt.debug = callback;
+ tt.debug_data = user_data;
+}
diff --git a/emulator/timetravel.h b/emulator/timetravel.h
new file mode 100644
index 000000000000..45b9d4ef69f2
--- /dev/null
+++ b/emulator/timetravel.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2026 Intel Corporation
+ *
+ *
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+
+/* Time travel calendar for User Mode Linux (time-travel=ext) guests */
+
+bool tt_open(const char *path);
+void tt_close(void);
+bool tt_enabled(void);
+
+/* Let time advance up to msec beyond the earliest running guest, so guests
+ * run in parallel; 0 (default) is lock-step.
+ */
+void tt_set_skew(unsigned int msec);
+
+/* Current simulated time in nanoseconds */
+uint64_t tt_now(void);
+
+/* Called before advancing time, to process pending device input */
+typedef void (*tt_flush_func_t)(void *user_data);
+void tt_add_flush(tt_flush_func_t func, void *user_data);
+void tt_remove_flush(tt_flush_func_t func, void *user_data);
+
+/* While the hold function returns true, time advances no faster than real
+ * time. Used while a peer outside the simulation, e.g. a test driver, has
+ * not asked anything of the guests, so they don't run ahead of it.
+ */
+typedef bool (*tt_hold_func_t)(void *user_data);
+void tt_set_hold(tt_hold_func_t func, void *user_data);
+
+/* Advance time if every guest waits, e.g. after processing device input */
+void tt_schedule(void);
+
+/* Process pending calendar messages without advancing time, used while
+ * waiting for a guest that may itself be waiting for the calendar.
+ */
+void tt_process(void);
+
+typedef bool (*tt_timeout_func_t)(void *user_data);
+typedef void (*tt_destroy_func_t)(void *user_data);
+unsigned int tt_timeout_add(unsigned int msec, tt_timeout_func_t func,
+ void *user_data, tt_destroy_func_t destroy);
+void tt_timeout_remove(unsigned int id);
+
+typedef void (*tt_debug_func_t)(const char *str, void *user_data);
+void tt_set_debug(tt_debug_func_t callback, void *user_data);
diff --git a/emulator/virtio.c b/emulator/virtio.c
index 47889027075c..34c8f5d3f93c 100644
--- a/emulator/virtio.c
+++ b/emulator/virtio.c
@@ -27,6 +27,7 @@
#include "btdev.h"
#include "vhost.h"
+#include "timetravel.h"
#include "virtio.h"
/* Virtio device ID 40, see include/uapi/linux/virtio_bt.h in Linux */
@@ -160,6 +161,34 @@ static void queue_notify(struct vhost *vu, unsigned int index,
flush_pending(client);
break;
}
+
+ /* The guest may wait for the calendar now */
+ tt_schedule();
+}
+
+/* The guest acknowledges in-band notifications only after its calendar
+ * requests are answered, so keep serving the calendar meanwhile.
+ */
+static void vhost_wait(struct vhost *vu, void *user_data)
+{
+ tt_process();
+}
+
+static void flush_client(void *data, void *user_data)
+{
+ struct client *client = data;
+
+ vhost_process(client->vu);
+}
+
+/* Process guest notifications sent before the guest waited, so time does
+ * not advance past them.
+ */
+static void flush(void *user_data)
+{
+ struct vtio *vtio = user_data;
+
+ queue_foreach(vtio->clients, flush_client, NULL);
}
static void disconnect(struct vhost *vu, void *user_data)
@@ -182,6 +211,7 @@ static const struct vhost_ops vhost_ops = {
.config_len = sizeof(config),
.queue_notify = queue_notify,
.disconnect = disconnect,
+ .wait = vhost_wait,
};
static void vu_debug(const char *str, void *user_data)
@@ -247,6 +277,7 @@ static void vtio_destroy(void *user_data)
{
struct vtio *vtio = user_data;
+ tt_remove_flush(flush, vtio);
queue_destroy(vtio->clients, detach_client);
close(vtio->fd);
free(vtio);
@@ -292,6 +323,8 @@ struct vtio *vtio_open(enum btdev_type type, const char *path)
vtio_destroy) < 0)
goto fail;
+ tt_add_flush(flush, vtio);
+
return vtio;
fail:
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 14/27] emulator: Add virtio console and time travel client to btvirt
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (12 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 13/27] emulator: Add time travel calendar " Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 15/27] tools/test-runner: Add User Mode Linux support Luiz Augusto von Dentz
` (13 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Add --console, a vhost-user virtio console with named ports, each
relayed to a Unix socket <path>-<name>, so a test driver outside the
guest, such as pytest-bluezenv, can reach User Mode Linux guests the way
it uses virtio-serial ports under QEMU. While the driver has no request
outstanding on a sync port, time is paced to real time so guests do not
run ahead of it.
Add --time-client, so the console relay takes part in an external time
travel calendar, e.g. one simulating the controllers: it keeps a request
one quantum (100 us) ahead and only injects input while the calendar
lets it run, as guests may run ahead of the calendar's current time with
FREE_UNTIL. Add --time-skew for btvirt's own calendar.
Assisted-by: Claude:claude-opus-5-5
---
Makefile.tools | 2 +
emulator/main.c | 69 ++++-
emulator/ttclient.c | 313 ++++++++++++++++++++
emulator/ttclient.h | 50 ++++
emulator/vconsole.c | 701 ++++++++++++++++++++++++++++++++++++++++++++
emulator/vconsole.h | 26 ++
6 files changed, 1158 insertions(+), 3 deletions(-)
create mode 100644 emulator/ttclient.c
create mode 100644 emulator/ttclient.h
create mode 100644 emulator/vconsole.c
create mode 100644 emulator/vconsole.h
diff --git a/Makefile.tools b/Makefile.tools
index bc92bc02b83d..304dbf135e98 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -106,6 +106,8 @@ emulator_btvirt_SOURCES = emulator/main.c monitor/bt.h \
emulator/vhost.h emulator/vhost.c \
emulator/virtio.h emulator/virtio.c \
emulator/timetravel.h emulator/timetravel.c \
+ emulator/vconsole.h emulator/vconsole.c \
+ emulator/ttclient.h emulator/ttclient.c \
emulator/timeout.c
emulator_btvirt_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la
diff --git a/emulator/main.c b/emulator/main.c
index a3b9456bb642..794d381764b5 100644
--- a/emulator/main.c
+++ b/emulator/main.c
@@ -29,6 +29,8 @@
#include "vhci.h"
#include "virtio.h"
#include "timetravel.h"
+#include "vconsole.h"
+#include "ttclient.h"
#include "le.h"
static void signal_callback(int signum, void *user_data)
@@ -55,6 +57,14 @@ static void usage(void)
"\t server socket\n"
"\t--time[=path=/tmp] Create time travel calendar socket\n"
"\t for User Mode Linux guests\n"
+ "\t--time-skew=<msec> Let guests run up to msec apart\n"
+ "\t (default: 0, lock-step)\n"
+ "\t--time-client=<id>:<socket>\n"
+ "\t Inject console input as client of a\n"
+ "\t time travel calendar\n"
+ "\t--console=<path>:<name>[=sync]...\n"
+ "\t Create vhost-user virtio console with\n"
+ "\t named ports relayed to <path>-<name>\n"
"\t-l[num] Number of local controllers\n"
"\t-L Create LE only controller\n"
"\t-U[num] Number of test LE controllers\n"
@@ -74,6 +84,9 @@ static const struct option main_options[] = {
{ "tcp", optional_argument, NULL, 't' },
{ "vhost", optional_argument, NULL, 'V' },
{ "time", optional_argument, NULL, 'I' },
+ { "console", required_argument, NULL, 'O' },
+ { "time-skew", required_argument, NULL, 'K' },
+ { "time-client", required_argument, NULL, 'J' },
{ "local", optional_argument, NULL, 'l' },
{ "le", no_argument, NULL, 'L' },
{ "bredr", no_argument, NULL, 'B' },
@@ -136,6 +149,10 @@ int main(int argc, char *argv[])
uint16_t tcp_port = 0;
const char *vhost_path = NULL;
const char *time_path = NULL;
+ const char *consoles[16];
+ int console_count = 0;
+ unsigned int time_skew = 0;
+ const char *time_client = NULL;
bool serial_enabled = false;
int letest_count = 0;
int vhci_count = 0;
@@ -150,8 +167,9 @@ int main(int argc, char *argv[])
for (;;) {
int opt;
- opt = getopt_long(argc, argv, "dSs::t::V::I::l::LBAC:U::vh",
- main_options, NULL);
+ opt = getopt_long(argc, argv,
+ "dSs::t::V::I::O:K:J:l::LBAC:U::vh",
+ main_options, NULL);
if (opt < 0)
break;
@@ -173,6 +191,19 @@ int main(int argc, char *argv[])
else
tcp_port = 45550;
break;
+ case 'J':
+ time_client = optarg;
+ break;
+ case 'K':
+ time_skew = atoi(optarg);
+ break;
+ case 'O':
+ if (console_count == ARRAY_SIZE(consoles)) {
+ fprintf(stderr, "Too many consoles\n");
+ return EXIT_FAILURE;
+ }
+ consoles[console_count++] = optarg;
+ break;
case 'I':
time_path = optarg ? optarg : "/tmp";
break;
@@ -226,7 +257,7 @@ int main(int argc, char *argv[])
if (letest_count < 1 && vhci_count < 1 && !server_enabled &&
!tcp_port && !serial_enabled && !vhost_path &&
- !time_path) {
+ !time_path && !console_count) {
fprintf(stderr, "No emulator specified\n");
return EXIT_FAILURE;
}
@@ -243,6 +274,8 @@ int main(int argc, char *argv[])
return EXIT_FAILURE;
}
+ tt_set_skew(time_skew);
+
if (debug_enabled)
tt_set_debug(server_debug, "time");
}
@@ -337,6 +370,36 @@ int main(int argc, char *argv[])
fprintf(stderr, "Listening TCP on 127.0.0.1:%d\n", tcp_port);
}
+ for (i = 0; i < console_count; i++) {
+ struct vcon *vcon;
+
+ vcon = vcon_open(consoles[i]);
+ if (!vcon)
+ return EXIT_FAILURE;
+
+ if (debug_enabled)
+ vcon_set_debug(vcon, server_debug, "console");
+ }
+
+ if (time_client) {
+ char *end;
+ unsigned long id = strtoul(time_client, &end, 0);
+
+ if (*end != ':' || !end[1]) {
+ fprintf(stderr, "Invalid time client: %s\n",
+ time_client);
+ return EXIT_FAILURE;
+ }
+
+ if (debug_enabled)
+ ttc_set_debug(server_debug, "time-client");
+
+ if (!ttc_open(end + 1, id, 0)) {
+ fprintf(stderr, "Failed to connect to %s\n", end + 1);
+ return EXIT_FAILURE;
+ }
+ }
+
if (vhost_path) {
struct vtio *vtio;
char path[PATH_MAX];
diff --git a/emulator/ttclient.c b/emulator/ttclient.c
new file mode 100644
index 000000000000..c134b5be9af0
--- /dev/null
+++ b/emulator/ttclient.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2026 Intel Corporation
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "src/shared/mainloop.h"
+#include "src/shared/util.h"
+
+#include "ttclient.h"
+
+/* Protocol of include/uapi/linux/um_timetravel.h in Linux */
+#define TT_ACK 0
+#define TT_START 1
+#define TT_REQUEST 2
+#define TT_WAIT 3
+#define TT_RUN 6
+#define TT_FREE_UNTIL 7
+#define TT_BROADCAST 9
+
+struct tt_msg {
+ uint32_t op;
+ uint32_t seq;
+ uint64_t time;
+} __attribute__((packed));
+
+static struct {
+ int fd;
+ uint32_t seq;
+ uint64_t now;
+ uint64_t quantum;
+ bool running;
+ int pace_id;
+ ttc_run_func_t run;
+ void *run_data;
+ ttc_pace_func_t pace;
+ void *pace_data;
+ ttc_debug_func_t debug;
+ void *debug_data;
+} ttc = { .fd = -1 };
+
+static void ttc_debug(const char *format, ...)
+{
+ char str[128];
+ va_list ap;
+
+ if (!ttc.debug)
+ return;
+
+ va_start(ap, format);
+ vsnprintf(str, sizeof(str), format, ap);
+ va_end(ap);
+
+ ttc.debug(str, ttc.debug_data);
+}
+
+/* Read a message, closing any file descriptors it carries (shared memory
+ * offered with the START acknowledgment, which this client does not use).
+ */
+static bool read_msg(struct tt_msg *msg)
+{
+ char control[CMSG_SPACE(sizeof(int) * 2)];
+ struct iovec iov = { msg, sizeof(*msg) };
+ struct msghdr mh = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = control,
+ .msg_controllen = sizeof(control),
+ };
+ struct cmsghdr *cmsg;
+ ssize_t len;
+
+ do {
+ len = recvmsg(ttc.fd, &mh, MSG_CMSG_CLOEXEC);
+ } while (len < 0 && errno == EINTR);
+
+ for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) {
+ int fds[2];
+ unsigned int i, n;
+
+ if (cmsg->cmsg_level != SOL_SOCKET ||
+ cmsg->cmsg_type != SCM_RIGHTS)
+ continue;
+
+ n = MIN((cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int), 2U);
+ memcpy(fds, CMSG_DATA(cmsg), n * sizeof(int));
+ for (i = 0; i < n; i++)
+ close(fds[i]);
+ }
+
+ return len == sizeof(*msg);
+}
+
+static void send_msg(uint32_t op, uint32_t seq, uint64_t time)
+{
+ struct tt_msg msg = { .op = op, .seq = seq, .time = time };
+
+ if (write(ttc.fd, &msg, sizeof(msg)) < 0)
+ ttc_debug("write failed: %s", strerror(errno));
+}
+
+/* Messages the calendar may send at any time */
+static void handle_async(struct tt_msg *msg)
+{
+
+ switch (msg->op) {
+ case TT_FREE_UNTIL:
+ case TT_BROADCAST:
+ send_msg(TT_ACK, msg->seq, 0);
+ break;
+ case TT_RUN:
+ ttc.now = msg->time;
+ ttc.running = true;
+ send_msg(TT_ACK, msg->seq, 0);
+ break;
+ }
+}
+
+/* Send a request and wait for its acknowledgment */
+static bool request(uint32_t op, uint64_t time, uint64_t *ack_time)
+{
+ uint32_t seq = ++ttc.seq;
+ struct tt_msg msg;
+
+ send_msg(op, seq, time);
+
+ while (read_msg(&msg)) {
+ if (msg.op == TT_ACK && msg.seq == seq) {
+ if (ack_time)
+ *ack_time = msg.time;
+ return true;
+ }
+
+ handle_async(&msg);
+ }
+
+ ttc_debug("calendar connection lost");
+ return false;
+}
+
+static void run(void);
+
+void ttc_process(void)
+{
+ struct tt_msg msg;
+
+ if (ttc.fd < 0)
+ return;
+
+ /* Only messages the calendar may send while the client runs */
+ while (recv(ttc.fd, &msg, sizeof(msg), MSG_DONTWAIT | MSG_PEEK) ==
+ sizeof(msg)) {
+ if (msg.op != TT_FREE_UNTIL && msg.op != TT_BROADCAST)
+ break;
+
+ if (!read_msg(&msg))
+ break;
+
+ handle_async(&msg);
+ }
+}
+
+/* Ask to run again one quantum later and let the guests run until then */
+static void wait_next(void)
+{
+ if (!request(TT_REQUEST, ttc.now + ttc.quantum, NULL) ||
+ !request(TT_WAIT, (uint64_t) -1, NULL)) {
+ mainloop_remove_fd(ttc.fd);
+ return;
+ }
+
+ ttc.running = false;
+}
+
+static void pace_callback(int id, void *user_data)
+{
+ mainloop_remove_timeout(id);
+ ttc.pace_id = 0;
+ wait_next();
+}
+
+static void run(void)
+{
+ if (ttc.run)
+ ttc.run(ttc.run_data);
+
+ /* Without anything asked of the guests, hold time for one quantum
+ * of real time, so it advances no faster than real time.
+ */
+ if (ttc.pace && ttc.pace(ttc.pace_data)) {
+ ttc.pace_id = mainloop_add_timeout(
+ MAX(ttc.quantum / 1000000ULL, 1ULL),
+ pace_callback, NULL, NULL);
+ if (ttc.pace_id > 0)
+ return;
+ }
+
+ wait_next();
+}
+
+static void read_callback(int fd, uint32_t events, void *user_data)
+{
+ struct tt_msg msg;
+
+ if ((events & (EPOLLERR | EPOLLHUP)) || !read_msg(&msg)) {
+ ttc_debug("calendar disconnected");
+ mainloop_remove_fd(fd);
+ return;
+ }
+
+ handle_async(&msg);
+
+ if (msg.op == TT_RUN)
+ run();
+}
+
+static void ttc_destroy(void *user_data)
+{
+ if (ttc.pace_id > 0)
+ mainloop_remove_timeout(ttc.pace_id);
+
+ close(ttc.fd);
+ ttc.fd = -1;
+ ttc.running = false;
+}
+
+bool ttc_open(const char *path, unsigned int id, unsigned int quantum_us)
+{
+ struct sockaddr_un addr;
+ uint64_t ack;
+
+ if (ttc.fd >= 0 || strlen(path) >= sizeof(addr.sun_path))
+ return false;
+
+ ttc.fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ if (ttc.fd < 0)
+ return false;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ memcpy(addr.sun_path, path, strlen(path));
+
+ if (connect(ttc.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0)
+ goto fail;
+
+ ttc.quantum = (uint64_t) (quantum_us ? quantum_us : 100) * 1000ULL;
+
+ if (!request(TT_START, id, &ack))
+ goto fail;
+
+ ttc_debug("started, id %u", (unsigned int) (ack & 0xffff));
+
+ if (mainloop_add_fd(ttc.fd, EPOLLIN, read_callback, NULL,
+ ttc_destroy) < 0)
+ goto fail;
+
+ /* A client runs once started */
+ ttc.running = true;
+ run();
+
+ return true;
+
+fail:
+ close(ttc.fd);
+ ttc.fd = -1;
+ return false;
+}
+
+bool ttc_enabled(void)
+{
+ return ttc.fd >= 0;
+}
+
+bool ttc_running(void)
+{
+ return ttc.fd < 0 || ttc.running;
+}
+
+void ttc_set_run(ttc_run_func_t func, void *user_data)
+{
+ ttc.run = func;
+ ttc.run_data = user_data;
+}
+
+void ttc_set_pace(ttc_pace_func_t func, void *user_data)
+{
+ ttc.pace = func;
+ ttc.pace_data = user_data;
+}
+
+void ttc_set_debug(ttc_debug_func_t callback, void *user_data)
+{
+ ttc.debug = callback;
+ ttc.debug_data = user_data;
+}
diff --git a/emulator/ttclient.h b/emulator/ttclient.h
new file mode 100644
index 000000000000..237a42cc6db9
--- /dev/null
+++ b/emulator/ttclient.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2026 Intel Corporation
+ *
+ *
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+
+/* Time travel client: takes part in the scheduling of a calendar (the time
+ * controller of User Mode Linux guests started with time-travel=ext), so
+ * that input from outside the simulation is injected at simulated times
+ * no guest has run past.
+ *
+ * The client keeps a request one quantum ahead of its time, so the
+ * calendar never lets guests run (or run ahead with FREE_UNTIL) beyond
+ * it, and only runs when the calendar grants it. Requesting the current
+ * time instead is not safe: with FREE_UNTIL, guests may already be past
+ * the calendar's current time.
+ */
+
+/* Quantum in microseconds, default (0) 100 */
+bool ttc_open(const char *path, unsigned int id, unsigned int quantum_us);
+bool ttc_enabled(void);
+
+/* The calendar granted the client to run: injecting events is safe */
+bool ttc_running(void);
+
+/* Answer pending calendar messages, e.g. while waiting for a guest that
+ * itself waits for the calendar
+ */
+void ttc_process(void);
+
+/* Called each time the client runs */
+typedef void (*ttc_run_func_t)(void *user_data);
+void ttc_set_run(ttc_run_func_t func, void *user_data);
+
+/* While the pace function returns true, the client lets the guests' time
+ * advance no faster than real time, e.g. while a test driver outside the
+ * simulation has not asked anything of the guests.
+ */
+typedef bool (*ttc_pace_func_t)(void *user_data);
+void ttc_set_pace(ttc_pace_func_t func, void *user_data);
+
+typedef void (*ttc_debug_func_t)(const char *str, void *user_data);
+void ttc_set_debug(ttc_debug_func_t callback, void *user_data);
diff --git a/emulator/vconsole.c b/emulator/vconsole.c
new file mode 100644
index 000000000000..7d288458a759
--- /dev/null
+++ b/emulator/vconsole.c
@@ -0,0 +1,701 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2026 Intel Corporation
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "src/shared/mainloop.h"
+#include "src/shared/queue.h"
+#include "src/shared/util.h"
+
+#include "vhost.h"
+#include "timetravel.h"
+#include "ttclient.h"
+#include "vconsole.h"
+
+/* Virtio console, see include/uapi/linux/virtio_console.h in Linux */
+#define VCON_F_MULTIPORT 1
+
+#define VCON_DEVICE_READY 0
+#define VCON_DEVICE_ADD 1
+#define VCON_PORT_READY 3
+#define VCON_PORT_OPEN 6
+#define VCON_PORT_NAME 7
+
+/* Queues: port 0 receive/transmit, control receive/transmit, then a
+ * receive/transmit pair for each further port.
+ */
+#define VCON_CTRL_RX 2
+#define VCON_CTRL_TX 3
+
+#define VCON_MAX_PORTS 3
+#define VCON_MAX_NAME 64
+/* The Linux driver posts receive buffers of a page */
+#define VCON_CHUNK 4096
+/* Data from the guest kept while no peer is connected */
+#define VCON_MAX_BACKLOG (1024 * 1024)
+
+struct vcon_config {
+ uint16_t cols;
+ uint16_t rows;
+ uint32_t max_nr_ports;
+ uint32_t emerg_wr;
+} __attribute__((packed));
+
+struct vcon_ctrl {
+ uint32_t id;
+ uint16_t event;
+ uint16_t value;
+} __attribute__((packed));
+
+struct packet {
+ size_t len;
+ uint8_t data[];
+};
+
+struct port {
+ struct vcon *vcon;
+ unsigned int id;
+ char name[VCON_MAX_NAME];
+ bool sync;
+ int listen_fd;
+ int client_fd;
+ /* Data for the guest, waiting for receive buffers */
+ struct queue *to_guest;
+ /* Data from the guest, waiting for a peer */
+ struct queue *to_peer;
+ size_t to_peer_len;
+ /* The peer sent a request the guest has not answered yet */
+ bool outstanding;
+ /* The driver discards data of ports nobody opened in the guest */
+ bool guest_open;
+};
+
+struct vcon {
+ char path[108];
+ int fd;
+ struct vhost *vu;
+ struct vhost_ops ops;
+ struct vcon_config config;
+ struct port ports[VCON_MAX_PORTS];
+ unsigned int num_ports;
+ /* Control messages waiting for receive buffers */
+ struct queue *ctrl;
+ vcon_debug_func_t debug_callback;
+ void *debug_data;
+};
+
+static struct queue *vcons;
+
+static void vcon_debug(struct vcon *vcon, const char *format, ...)
+{
+ char str[160];
+ va_list ap;
+
+ if (!vcon->debug_callback)
+ return;
+
+ va_start(ap, format);
+ vsnprintf(str, sizeof(str), format, ap);
+ va_end(ap);
+
+ vcon->debug_callback(str, vcon->debug_data);
+}
+
+static unsigned int rx_queue(unsigned int port)
+{
+ return port ? 2 + 2 * port : 0;
+}
+
+static struct packet *packet_new(const void *data, size_t len)
+{
+ struct packet *pkt;
+
+ pkt = malloc(sizeof(*pkt) + len);
+ if (!pkt)
+ return NULL;
+
+ pkt->len = len;
+ memcpy(pkt->data, data, len);
+
+ return pkt;
+}
+
+/* Push pending packets to a receive queue while it has buffers */
+static void flush_queue(struct vcon *vcon, struct queue *pending,
+ unsigned int index)
+{
+ struct packet *pkt;
+
+ if (!vcon->vu || queue_isempty(pending))
+ return;
+
+ /* As time travel client, only inject while the calendar lets it run,
+ * so no guest has run past that time.
+ */
+ if (!ttc_running())
+ return;
+
+ while ((pkt = queue_peek_head(pending))) {
+ struct iovec iov = { pkt->data, pkt->len };
+
+ if (!vhost_push(vcon->vu, index, &iov, 1))
+ break;
+
+
+ queue_pop_head(pending);
+ free(pkt);
+ }
+}
+
+static void send_ctrl(struct vcon *vcon, unsigned int id, uint16_t event,
+ uint16_t value, const char *name)
+{
+ uint8_t buf[sizeof(struct vcon_ctrl) + VCON_MAX_NAME];
+ struct vcon_ctrl *ctrl = (void *) buf;
+ size_t len = sizeof(*ctrl);
+ struct packet *pkt;
+
+ ctrl->id = cpu_to_le32(id);
+ ctrl->event = cpu_to_le16(event);
+ ctrl->value = cpu_to_le16(value);
+
+ if (name) {
+ memcpy(buf + len, name, strlen(name));
+ len += strlen(name);
+ }
+
+ pkt = packet_new(buf, len);
+ if (!pkt)
+ return;
+
+ queue_push_tail(vcon->ctrl, pkt);
+ flush_queue(vcon, vcon->ctrl, VCON_CTRL_RX);
+}
+
+static void flush_port(struct port *port)
+{
+ if (port->guest_open)
+ flush_queue(port->vcon, port->to_guest, rx_queue(port->id));
+}
+
+static void handle_ctrl(struct vcon *vcon, const struct vcon_ctrl *ctrl)
+{
+ unsigned int id = le32_to_cpu(ctrl->id);
+ struct port *port;
+ unsigned int i;
+
+ switch (le16_to_cpu(ctrl->event)) {
+ case VCON_DEVICE_READY:
+ for (i = 0; i < vcon->num_ports; i++)
+ send_ctrl(vcon, i, VCON_DEVICE_ADD, 1, NULL);
+ break;
+ case VCON_PORT_READY:
+ if (id >= vcon->num_ports)
+ break;
+ send_ctrl(vcon, id, VCON_PORT_NAME, 1, vcon->ports[id].name);
+ /* Data from the guest is kept until a peer connects */
+ send_ctrl(vcon, id, VCON_PORT_OPEN, 1, NULL);
+ break;
+ case VCON_PORT_OPEN:
+ if (id >= vcon->num_ports)
+ break;
+ port = &vcon->ports[id];
+ port->guest_open = le16_to_cpu(ctrl->value);
+ vcon_debug(vcon, "%s: guest %s", port->name,
+ port->guest_open ? "opened" : "closed");
+ flush_port(port);
+ break;
+ }
+}
+
+static void write_peer(struct port *port, const uint8_t *data, size_t len)
+{
+ while (len) {
+ ssize_t n = write(port->client_fd, data, len);
+
+ if (n < 0)
+ return;
+
+ data += n;
+ len -= n;
+ }
+}
+
+static void from_guest(struct port *port, const uint8_t *data, size_t len)
+{
+ struct packet *pkt;
+
+ /* The guest answered */
+ port->outstanding = false;
+
+ if (port->client_fd >= 0) {
+ write_peer(port, data, len);
+ return;
+ }
+
+ /* Answers of a sync port belong to the peer that asked, other ports
+ * keep the guest's output (e.g. logs) until a peer connects.
+ */
+ if (port->sync || port->to_peer_len + len > VCON_MAX_BACKLOG)
+ return;
+
+ pkt = packet_new(data, len);
+ if (!pkt)
+ return;
+
+ queue_push_tail(port->to_peer, pkt);
+ port->to_peer_len += len;
+}
+
+static void queue_notify(struct vhost *vu, unsigned int index,
+ void *user_data)
+{
+ struct vcon *vcon = user_data;
+ static uint8_t buf[65536];
+ unsigned int i;
+ ssize_t len;
+
+ if (index == VCON_CTRL_TX) {
+ while ((len = vhost_pop(vu, index, buf, sizeof(buf)))) {
+ if (len >= (ssize_t) sizeof(struct vcon_ctrl))
+ handle_ctrl(vcon, (void *) buf);
+ }
+ goto done;
+ }
+
+ if (index == VCON_CTRL_RX) {
+ flush_queue(vcon, vcon->ctrl, VCON_CTRL_RX);
+ goto done;
+ }
+
+ for (i = 0; i < vcon->num_ports; i++) {
+ struct port *port = &vcon->ports[i];
+
+ if (index == rx_queue(i)) {
+ flush_port(port);
+ break;
+ }
+
+ if (index == rx_queue(i) + 1) {
+ while ((len = vhost_pop(vu, index, buf, sizeof(buf)))) {
+ if (len > 0)
+ from_guest(port, buf, len);
+ }
+ break;
+ }
+ }
+
+done:
+ tt_schedule();
+}
+
+static void run_vcon(void *data, void *user_data)
+{
+ struct vcon *vcon = data;
+ unsigned int i;
+
+ if (!vcon->vu)
+ return;
+
+ flush_queue(vcon, vcon->ctrl, VCON_CTRL_RX);
+
+ for (i = 0; i < vcon->num_ports; i++)
+ flush_port(&vcon->ports[i]);
+
+ vhost_flush_notify(vcon->vu);
+}
+
+/* The calendar lets the time travel client run: deliver what is pending */
+static void run(void *user_data)
+{
+ queue_foreach(vcons, run_vcon, NULL);
+}
+
+static bool can_notify(struct vhost *vu, void *user_data)
+{
+ return ttc_running();
+}
+
+static void vhost_wait(struct vhost *vu, void *user_data)
+{
+ tt_process();
+ ttc_process();
+}
+
+static void disconnect(struct vhost *vu, void *user_data)
+{
+ struct vcon *vcon = user_data;
+ unsigned int i;
+
+ vcon_debug(vcon, "guest disconnected");
+
+ vhost_free(vcon->vu);
+ vcon->vu = NULL;
+ queue_remove_all(vcon->ctrl, NULL, NULL, free);
+
+ for (i = 0; i < vcon->num_ports; i++)
+ vcon->ports[i].guest_open = false;
+
+ tt_schedule();
+}
+
+static void vu_debug(const char *str, void *user_data)
+{
+ struct vcon *vcon = user_data;
+
+ vcon_debug(vcon, "vhost-user: %s", str);
+}
+
+static void accept_guest(int fd, uint32_t events, void *user_data)
+{
+ struct vcon *vcon = user_data;
+ int nfd;
+
+ if (events & (EPOLLERR | EPOLLHUP)) {
+ mainloop_remove_fd(fd);
+ return;
+ }
+
+ nfd = accept(fd, NULL, NULL);
+ if (nfd < 0)
+ return;
+
+ if (vcon->vu) {
+ vcon_debug(vcon, "already serving a guest");
+ close(nfd);
+ return;
+ }
+
+ vcon->vu = vhost_new(nfd, &vcon->ops, vcon);
+ if (!vcon->vu) {
+ close(nfd);
+ return;
+ }
+
+ vhost_set_debug(vcon->vu, vu_debug, vcon);
+
+ vcon_debug(vcon, "guest connected");
+}
+
+static void peer_read(int fd, uint32_t events, void *user_data)
+{
+ struct port *port = user_data;
+ uint8_t buf[VCON_CHUNK];
+ struct packet *pkt;
+ ssize_t len;
+
+ len = read(fd, buf, sizeof(buf));
+ if (len <= 0) {
+ mainloop_remove_fd(fd);
+ return;
+ }
+
+ pkt = packet_new(buf, len);
+ if (!pkt)
+ return;
+
+ if (port->sync)
+ port->outstanding = true;
+
+ queue_push_tail(port->to_guest, pkt);
+ flush_port(port);
+
+ tt_schedule();
+}
+
+static void peer_destroy(void *user_data)
+{
+ struct port *port = user_data;
+
+ vcon_debug(port->vcon, "%s: peer disconnected", port->name);
+
+ close(port->client_fd);
+ port->client_fd = -1;
+ port->outstanding = false;
+
+ /* Requests of the peer that left are not delivered anymore */
+ queue_remove_all(port->to_guest, NULL, NULL, free);
+
+ tt_schedule();
+}
+
+static void accept_peer(int fd, uint32_t events, void *user_data)
+{
+ struct port *port = user_data;
+ struct packet *pkt;
+ int nfd;
+
+ if (events & (EPOLLERR | EPOLLHUP)) {
+ mainloop_remove_fd(fd);
+ return;
+ }
+
+ nfd = accept(fd, NULL, NULL);
+ if (nfd < 0)
+ return;
+
+ if (port->client_fd >= 0) {
+ close(nfd);
+ return;
+ }
+
+ port->client_fd = nfd;
+
+ if (mainloop_add_fd(nfd, EPOLLIN, peer_read, port, peer_destroy) < 0) {
+ close(nfd);
+ port->client_fd = -1;
+ return;
+ }
+
+ vcon_debug(port->vcon, "%s: peer connected", port->name);
+
+ while ((pkt = queue_pop_head(port->to_peer))) {
+ write_peer(port, pkt->data, pkt->len);
+ free(pkt);
+ }
+
+ port->to_peer_len = 0;
+}
+
+static int listen_unix(const char *path)
+{
+ struct sockaddr_un addr;
+ int fd;
+
+ if (strlen(path) >= sizeof(addr.sun_path))
+ return -1;
+
+ fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ if (fd < 0)
+ return -1;
+
+ unlink(path);
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ memcpy(addr.sun_path, path, strlen(path));
+
+ if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0 ||
+ listen(fd, 5) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+static void sync_state(void *data, void *user_data)
+{
+ struct vcon *vcon = data;
+ unsigned int *state = user_data;
+ unsigned int i;
+
+ for (i = 0; i < vcon->num_ports; i++) {
+ struct port *port = &vcon->ports[i];
+
+ if (!port->sync || port->client_fd < 0)
+ continue;
+
+ state[0]++;
+ if (port->outstanding)
+ state[1]++;
+ }
+}
+
+/* Hold time while a peer is connected to a sync port but has no request
+ * outstanding on any of them: it is between requests, so the guests must
+ * not run ahead of it, time then advances at most at real time.
+ */
+static bool hold(void *user_data)
+{
+ unsigned int state[2] = { 0, 0 };
+
+ queue_foreach(vcons, sync_state, state);
+
+ return state[0] && !state[1];
+}
+
+static void flush(void *user_data)
+{
+ struct vcon *vcon = user_data;
+
+ if (vcon->vu)
+ vhost_process(vcon->vu);
+}
+
+static bool parse_spec(struct vcon *vcon, const char *spec)
+{
+ char *str, *tok, *save = NULL;
+ bool ok = false;
+
+ str = strdup(spec);
+ if (!str)
+ return false;
+
+ tok = strtok_r(str, ":", &save);
+ if (!tok || strlen(tok) >= sizeof(vcon->path))
+ goto done;
+
+ memcpy(vcon->path, tok, strlen(tok) + 1);
+
+ while ((tok = strtok_r(NULL, ":", &save))) {
+ struct port *port;
+ char *opt;
+
+ if (vcon->num_ports == VCON_MAX_PORTS)
+ goto done;
+
+ port = &vcon->ports[vcon->num_ports];
+
+ opt = strchr(tok, '=');
+ if (opt) {
+ *opt++ = '\0';
+ if (strcmp(opt, "sync"))
+ goto done;
+ port->sync = true;
+ }
+
+ if (!*tok || strlen(tok) >= sizeof(port->name))
+ goto done;
+
+ memcpy(port->name, tok, strlen(tok) + 1);
+ port->id = vcon->num_ports++;
+ }
+
+ ok = vcon->num_ports > 0;
+
+done:
+ free(str);
+ return ok;
+}
+
+struct vcon *vcon_open(const char *spec)
+{
+ struct vcon *vcon;
+ unsigned int i;
+
+ vcon = new0(struct vcon, 1);
+ vcon->fd = -1;
+
+ for (i = 0; i < VCON_MAX_PORTS; i++) {
+ vcon->ports[i].listen_fd = -1;
+ vcon->ports[i].client_fd = -1;
+ }
+
+ if (!parse_spec(vcon, spec)) {
+ free(vcon);
+ return NULL;
+ }
+
+ vcon->ctrl = queue_new();
+ vcon->config.max_nr_ports = cpu_to_le32(vcon->num_ports);
+ vcon->ops.features = 1ULL << VCON_F_MULTIPORT;
+ vcon->ops.num_queues = 2 + 2 * vcon->num_ports;
+ vcon->ops.config = &vcon->config;
+ vcon->ops.config_len = sizeof(vcon->config);
+ vcon->ops.queue_notify = queue_notify;
+ vcon->ops.disconnect = disconnect;
+ vcon->ops.wait = vhost_wait;
+ vcon->ops.can_notify = can_notify;
+
+ vcon->fd = listen_unix(vcon->path);
+ if (vcon->fd < 0 || mainloop_add_fd(vcon->fd, EPOLLIN, accept_guest,
+ vcon, NULL) < 0)
+ goto fail;
+
+ for (i = 0; i < vcon->num_ports; i++) {
+ struct port *port = &vcon->ports[i];
+ char path[sizeof(vcon->path) + VCON_MAX_NAME + 1];
+
+ port->vcon = vcon;
+ port->to_guest = queue_new();
+ port->to_peer = queue_new();
+
+ snprintf(path, sizeof(path), "%s-%s", vcon->path, port->name);
+ port->listen_fd = listen_unix(path);
+ if (port->listen_fd < 0 || mainloop_add_fd(port->listen_fd,
+ EPOLLIN, accept_peer, port, NULL) < 0)
+ goto fail;
+ }
+
+ if (!vcons) {
+ vcons = queue_new();
+ tt_set_hold(hold, NULL);
+ ttc_set_pace(hold, NULL);
+ ttc_set_run(run, NULL);
+ }
+
+ queue_push_tail(vcons, vcon);
+ tt_add_flush(flush, vcon);
+
+ return vcon;
+
+fail:
+ fprintf(stderr, "Failed to open console %s\n", vcon->path);
+ vcon_close(vcon);
+ return NULL;
+}
+
+void vcon_close(struct vcon *vcon)
+{
+ unsigned int i;
+
+ if (!vcon)
+ return;
+
+ queue_remove(vcons, vcon);
+
+ for (i = 0; i < vcon->num_ports; i++) {
+ struct port *port = &vcon->ports[i];
+
+ if (port->client_fd >= 0)
+ mainloop_remove_fd(port->client_fd);
+ if (port->listen_fd >= 0) {
+ mainloop_remove_fd(port->listen_fd);
+ close(port->listen_fd);
+ }
+ queue_destroy(port->to_guest, free);
+ queue_destroy(port->to_peer, free);
+ }
+
+ vhost_free(vcon->vu);
+
+ if (vcon->fd >= 0) {
+ mainloop_remove_fd(vcon->fd);
+ close(vcon->fd);
+ }
+
+ tt_remove_flush(flush, vcon);
+ queue_destroy(vcon->ctrl, free);
+ free(vcon);
+}
+
+bool vcon_set_debug(struct vcon *vcon, vcon_debug_func_t callback,
+ void *user_data)
+{
+ if (!vcon)
+ return false;
+
+ vcon->debug_callback = callback;
+ vcon->debug_data = user_data;
+
+ return true;
+}
diff --git a/emulator/vconsole.h b/emulator/vconsole.h
new file mode 100644
index 000000000000..bfebe395005f
--- /dev/null
+++ b/emulator/vconsole.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2026 Intel Corporation
+ *
+ *
+ */
+
+#include <stdbool.h>
+
+struct vcon;
+
+/* Serve a virtio console with named ports on the vhost-user socket path.
+ * Each port is relayed to a Unix socket "<path>-<name>" for a peer outside
+ * the guest. spec is "<path>:<name>[=sync]:<name>[=sync]...", a sync port
+ * carries requests from the peer: time is held while the peer has none
+ * outstanding on any sync port.
+ */
+struct vcon *vcon_open(const char *spec);
+void vcon_close(struct vcon *vcon);
+
+typedef void (*vcon_debug_func_t)(const char *str, void *user_data);
+bool vcon_set_debug(struct vcon *vcon, vcon_debug_func_t callback,
+ void *user_data);
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 15/27] tools/test-runner: Add User Mode Linux support
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (13 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 14/27] emulator: Add virtio console and time travel client " Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 16/27] doc: Add virtio documentation Luiz Augusto von Dentz
` (12 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Accept a User Mode Linux kernel with -k, detected by its .uml.setup.init
section, and run it directly with the host filesystem as root and
test-runner as init, as is done with QEMU. Options given with -o are
passed as kernel parameters, e.g. virtio_uml.device=<socket>:<id> for
devices served from the host. The guest powers off at the end, as
rebooting User Mode Linux starts it again, and mounts the coverage
directory with hostfs instead of 9p.
Add -T, --time-travel, which starts btvirt --time as time travel
calendar and joins the guest to it, so the same kernel runs with or
without simulated time by adding -T. The calendar is stopped when the
guest exits. The kernel needs CONFIG_UML_TIME_TRAVEL_SUPPORT, checked
with its --help, which only lists time-travel=ext with it.
Wall clock time of the testers, all tests passing, same kernel version
and machine, User Mode Linux with seccomp:
tester tests KVM UML UML -T
mgmt-tester 514 83.6 111.6 42.6
l2cap-tester 111 9.6 13.7 10.2
iso-tester 141 13.7 24.3 16.3
sco-tester 30 5.1 4.1 3.6
rfcomm-tester 11 3.6 2.0 2.6
smp-tester 8 4.1 1.5 1.5
ioctl-tester 28 4.1 2.6 3.1
userchan-tester 4 2.6 1.5 1.5
bnep-tester 1 2.6 1.0 1.0
Assisted-by: Claude:claude-opus-5-5
---
doc/test-runner.rst | 46 ++++-
tools/test-runner.c | 399 ++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 431 insertions(+), 14 deletions(-)
diff --git a/doc/test-runner.rst b/doc/test-runner.rst
index 52178244fce8..02d69b06f54c 100644
--- a/doc/test-runner.rst
+++ b/doc/test-runner.rst
@@ -26,9 +26,12 @@ OPTIONS
:-P/--pcie=<qemu_args>: Provide PCIe device
:-q/--qemu=<path>: QEMU binary
:-H/--qemu-host-cpu: Use host CPU (requires KVM support)
-:-k/--kernel=<image>: Kernel image (bzImage)
+:-k/--kernel=<image>: Kernel image (bzImage) or User Mode Linux binary
:-F/--virtiofs[=<path>]: Path to virtiofsd, or no to disable virtio-fs
-:-o/--option=<opt>: Additional argument passed to QEMU
+:-T/--time-travel: Run a User Mode Linux kernel on simulated time, with
+ btvirt as time travel calendar
+:-o/--option=<opt>: Additional argument passed to QEMU, or kernel
+ parameter with User Mode Linux
:-h/--help: Show help options
ARCHITECTURE
@@ -134,6 +137,28 @@ additional channels between the launcher and the guest are added, for
example by **test-functional(1)**, which attaches its own virtio-serial
ports and a writable shared directory this way.
+User Mode Linux
+---------------
+
+When ``-k`` is a User Mode Linux (UML) binary, recognized by its
+``.uml.setup.init`` section, test-runner runs it directly instead of QEMU,
+with the host filesystem mounted as root through hostfs, and powers it off
+at the end since rebooting UML starts it again. The kernel needs
+``CONFIG_HOSTFS``, and ``CONFIG_VIRTIO_UML`` for virtio devices.
+
+``-o`` then adds kernel parameters, which is how UML attaches devices served
+from the host, e.g. ``virtio_uml.device=<socket>:40`` for a virtio
+Bluetooth controller, or joins an external simulated clock with
+``time-travel=ext:<socket>``. See `virtio.rst <virtio.rst>`_. ``-u``,
+``-U``, ``-P``, ``-q`` and ``-H`` need QEMU.
+
+With ``-T``, test-runner starts **btvirt(1)** ``--time`` as time travel
+calendar and runs the guest on its simulated clock: time only advances when
+the guest waits, so it skips idle time and timeouts take no real time, while
+the guest sees the same timings on every run. This needs a kernel with
+``CONFIG_UML_TIME_TRAVEL_SUPPORT``, which test-runner checks; without
+``-T`` the same kernel runs on real time.
+
Kernel
======
@@ -385,3 +410,20 @@ exits.
Note that unlike the other modes, PCIe-passthrough boots the guest with ACPI
and APIC enabled, as these are required for device enumeration and MSI
interrupt delivery.
+
+Running mgmt-tester under User Mode Linux
+-----------------------------------------
+
+.. code-block::
+
+ $ make ARCH=um defconfig
+ $ scripts/kconfig/merge_config.sh -m .config <bluez>/doc/tester.config
+ $ make ARCH=um olddefconfig linux
+ $ tools/test-runner -k <linux>/linux -- tools/mgmt-tester
+
+On simulated time, with a kernel that also has
+``CONFIG_UML_TIME_TRAVEL_SUPPORT=y``:
+
+.. code-block::
+
+ $ tools/test-runner -T -k <linux>/linux -- tools/mgmt-tester
diff --git a/tools/test-runner.c b/tools/test-runner.c
index 21de3a50da5e..8be4bfb09265 100644
--- a/tools/test-runner.c
+++ b/tools/test-runner.c
@@ -43,6 +43,8 @@
#include "bluetooth/hci_lib.h"
#include "tools/hciattach.h"
+#include <elf.h>
+
#ifndef WAIT_ANY
#define WAIT_ANY (-1)
#endif
@@ -64,6 +66,7 @@ static bool start_dbus_session;
static bool start_daemon = false;
static bool start_monitor = false;
static bool qemu_host_cpu = false;
+static bool time_travel;
static int num_devs = 0;
static int num_emulator = 0;
static const char *device_path = "/tmp/bt-server-bredr";
@@ -155,9 +158,79 @@ static const char *kernel_table[] = {
"arch/x86/boot/bzImage",
"vmlinux",
"arch/x86/boot/vmlinux",
+ "linux",
NULL
};
+/* A User Mode Linux kernel is an executable with the .uml.setup.init
+ * section, through which it registers its command line options.
+ */
+static bool is_uml_kernel(const char *path)
+{
+ Elf64_Ehdr ehdr;
+ Elf64_Shdr *shdrs = NULL;
+ char *names = NULL;
+ bool found = false;
+ size_t size;
+ FILE *fp;
+ int i;
+
+ fp = fopen(path, "re");
+ if (!fp)
+ return false;
+
+ if (fread(&ehdr, sizeof(ehdr), 1, fp) != 1 ||
+ memcmp(ehdr.e_ident, ELFMAG, SELFMAG) ||
+ ehdr.e_ident[EI_CLASS] != ELFCLASS64 ||
+ ehdr.e_shentsize != sizeof(Elf64_Shdr) ||
+ ehdr.e_shstrndx >= ehdr.e_shnum)
+ goto done;
+
+ shdrs = calloc(ehdr.e_shnum, sizeof(*shdrs));
+ if (!shdrs || fseek(fp, ehdr.e_shoff, SEEK_SET) ||
+ fread(shdrs, sizeof(*shdrs), ehdr.e_shnum, fp) !=
+ ehdr.e_shnum)
+ goto done;
+
+ size = shdrs[ehdr.e_shstrndx].sh_size;
+ names = calloc(1, size + 1);
+ if (!names || fseek(fp, shdrs[ehdr.e_shstrndx].sh_offset, SEEK_SET) ||
+ fread(names, size, 1, fp) != 1)
+ goto done;
+
+ for (i = 0; i < ehdr.e_shnum; i++) {
+ if (shdrs[i].sh_name < size &&
+ !strcmp(names + shdrs[i].sh_name,
+ ".uml.setup.init")) {
+ found = true;
+ break;
+ }
+ }
+
+done:
+ free(names);
+ free(shdrs);
+ fclose(fp);
+ return found;
+}
+
+static bool is_uml_guest(void)
+{
+ char cmdline[CMDLINE_MAX];
+ bool uml = false;
+ FILE *fp;
+
+ fp = fopen("/proc/cmdline", "re");
+ if (!fp)
+ return false;
+
+ if (fgets(cmdline, sizeof(cmdline), fp))
+ uml = strstr(cmdline, "TESTUML=1") != NULL;
+
+ fclose(fp);
+ return uml;
+}
+
static bool find_kernel(const char *root, char path[PATH_MAX])
{
struct stat st;
@@ -615,6 +688,12 @@ static void coverage_mount(const char *dir)
printf("Mounting %s writable for coverage\n", dir);
+ if (is_uml_guest()) {
+ if (mount("none", dir, "hostfs", 0, dir) < 0)
+ perror("Failed to mount coverage directory");
+ return;
+ }
+
if (mount(COVERAGE_TAG, dir, "9p", 0,
"trans=virtio,version=9p2000.L") < 0)
perror("Failed to mount coverage directory");
@@ -952,6 +1031,254 @@ static int start_qemu(void)
return WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE;
}
+static const char *find_btvirt(void);
+
+#define TT_DIR_MAX 32
+
+/* A User Mode Linux kernel built with time travel support lists the
+ * external mode, time-travel=ext, in its help.
+ */
+static bool uml_has_time_travel(void)
+{
+ char *line = NULL;
+ bool found = false;
+ size_t size = 0;
+ int fds[2];
+ FILE *fp;
+ pid_t pid;
+
+ if (pipe(fds) < 0)
+ return false;
+
+ pid = fork();
+ if (pid < 0) {
+ close(fds[0]);
+ close(fds[1]);
+ return false;
+ }
+
+ if (pid == 0) {
+ dup2(fds[1], STDOUT_FILENO);
+ close(fds[0]);
+ close(fds[1]);
+ execl(kernel_image, kernel_image, "--help", NULL);
+ exit(EXIT_FAILURE);
+ }
+
+ close(fds[1]);
+
+ fp = fdopen(fds[0], "r");
+ if (!fp) {
+ close(fds[0]);
+ waitpid(pid, NULL, 0);
+ return false;
+ }
+
+ while (getline(&line, &size, fp) > 0) {
+ if (strstr(line, "time-travel=ext"))
+ found = true;
+ }
+
+ free(line);
+ fclose(fp);
+ waitpid(pid, NULL, 0);
+
+ return found;
+}
+
+/* Start btvirt as time travel calendar for the guest, in a new directory
+ * for its socket. Returns the pid of btvirt, or -1 on failure.
+ */
+static pid_t start_calendar(char dir[TT_DIR_MAX])
+{
+ const char *btvirt;
+ char opt[PATH_MAX], sock[PATH_MAX];
+ struct stat st;
+ pid_t pid;
+ int i;
+
+ btvirt = find_btvirt();
+ if (!btvirt) {
+ fprintf(stderr, "Failed to locate btvirt binary\n");
+ return -1;
+ }
+
+ snprintf(dir, TT_DIR_MAX, "/tmp/test-runner-tt.XXXXXX");
+ if (!mkdtemp(dir)) {
+ perror("Failed to create time travel directory");
+ return -1;
+ }
+
+ snprintf(opt, sizeof(opt), "--time=%s", dir);
+ snprintf(sock, sizeof(sock), "%s/bt-time", dir);
+
+ printf("Starting time travel calendar %s %s\n", btvirt, opt);
+
+ pid = fork();
+ if (pid < 0) {
+ perror("Failed to fork new process");
+ rmdir(dir);
+ return -1;
+ }
+
+ if (pid == 0) {
+ execl(btvirt, btvirt, opt, NULL);
+ exit(EXIT_FAILURE);
+ }
+
+ for (i = 0; i < 100; i++) {
+ if (!stat(sock, &st))
+ return pid;
+
+ if (waitpid(pid, NULL, WNOHANG) == pid)
+ break;
+
+ usleep(50000);
+ }
+
+ fprintf(stderr, "Time travel calendar did not start\n");
+ kill(pid, SIGTERM);
+ waitpid(pid, NULL, 0);
+ rmdir(dir);
+
+ return -1;
+}
+
+static void stop_calendar(pid_t pid, const char *dir)
+{
+ char sock[PATH_MAX];
+
+ kill(pid, SIGTERM);
+ waitpid(pid, NULL, 0);
+
+ snprintf(sock, sizeof(sock), "%s/bt-time", dir);
+ unlink(sock);
+ rmdir(dir);
+}
+
+/* Run a User Mode Linux kernel with the host filesystem as root and this
+ * binary as init, as start_qemu() does for QEMU. Extra options (-o) are
+ * kernel parameters, e.g. time-travel=ext:<socket> or
+ * virtio_uml.device=<socket>:<id> for devices served from the host.
+ */
+static int start_uml(void)
+{
+ char cwd[PATH_MAX/2], initcmd[PATH_MAX], testargs[PATH_MAX];
+ char dir[TT_DIR_MAX];
+ pid_t calendar = -1, pid;
+ int status = EXIT_FAILURE << 8;
+ char *argv_strv[EXTRA_OPT_MAX + 64];
+ struct strv _cleanup_(strv_cleanup) argv = { argv_strv,
+ ARRAY_SIZE(argv_strv) };
+ int i, pos;
+
+ if (!getcwd(cwd, sizeof(cwd)))
+ strcat(cwd, "/");
+
+ if (own_binary[0] == '/')
+ snprintf(initcmd, sizeof(initcmd), "%s", own_binary);
+ else
+ snprintf(initcmd, sizeof(initcmd), "%s/%s", cwd, own_binary);
+
+ pos = snprintf(testargs, sizeof(testargs), "%s", test_argv[0]);
+
+ for (i = 1; i < test_argc; i++) {
+ int len = sizeof(testargs) - pos;
+ int n = snprintf(testargs + pos, len, " %s", test_argv[i]);
+
+ if (n < 0 || n >= len) {
+ fprintf(stderr, "Buffer overflow detected in "
+ "testargs\n");
+ return EXIT_FAILURE;
+ }
+
+ pos += n;
+ }
+
+ strv_append(&argv, "%s", kernel_image);
+ strv_append(&argv, "mem=512M");
+ /* UML keeps its PID file in uml_dir, ~/.uml by default */
+ strv_append(&argv, "uml_dir=/tmp");
+ /* No block device, UML would otherwise try to open root_fs */
+ strv_append(&argv, "ubd0r=/dev/null");
+ strv_append(&argv, "root=/dev/root");
+ strv_append(&argv, "rootfstype=hostfs");
+ strv_append(&argv, "hostfs=/");
+ strv_append(&argv, "ro");
+ strv_append(&argv, "quiet");
+ /* Run the guest's processes with seccomp instead of ptrace */
+ strv_append(&argv, "seccomp=on");
+ strv_append(&argv, "con=null");
+ strv_append(&argv, "con0=fd:0,fd:1");
+ strv_append(&argv, "TESTUML=1");
+ strv_append(&argv, "init=%s", initcmd);
+ strv_append(&argv, "TESTHOME=%s", cwd);
+ strv_append(&argv, "TESTDBUS=%u", start_dbus);
+ strv_append(&argv, "TESTDAEMON=%u", start_daemon);
+ strv_append(&argv, "TESTDBUSSESSION=%u", start_dbus_session);
+ strv_append(&argv, "XDG_RUNTIME_DIR=/run/user/0");
+ strv_append(&argv, "TESTMONITOR=%u", start_monitor);
+ strv_append(&argv, "TESTEMULATOR=%u", num_emulator);
+ strv_append(&argv, "TESTDEVS=0");
+ strv_append(&argv, "TESTAUTO=%u", run_auto);
+ strv_append(&argv, "TESTAUDIO='%s'", audio_server ? audio_server : "");
+ strv_append(&argv, "TESTARGS='%s'", testargs);
+
+ for (i = 0; i < num_extra_opts; ++i)
+ strv_append(&argv, "%s", extra_opts[i]);
+
+ if (time_travel) {
+ calendar = start_calendar(dir);
+ if (calendar < 0)
+ return EXIT_FAILURE;
+
+ strv_append(&argv, "time-travel=ext:%s/bt-time", dir);
+ }
+
+ if (STRV_ERROR(argv)) {
+ fprintf(stderr, "Failed to build argument list\n");
+ goto done;
+ }
+
+ /* Exec directly if there is no calendar to stop afterwards */
+ if (!time_travel) {
+ execve(argv.strv[0], argv.strv, qemu_envp);
+ perror("Failed to start User Mode Linux");
+ return EXIT_FAILURE;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ perror("Failed to fork new process");
+ goto done;
+ }
+
+ if (pid == 0) {
+ execve(argv.strv[0], argv.strv, qemu_envp);
+ perror("Failed to start User Mode Linux");
+ exit(EXIT_FAILURE);
+ }
+
+ qemu_pid = pid;
+
+ signal(SIGINT, qemu_signal);
+ signal(SIGTERM, qemu_signal);
+ signal(SIGHUP, qemu_signal);
+
+ while (waitpid(pid, &status, 0) < 0) {
+ if (errno != EINTR)
+ break;
+ }
+
+ qemu_pid = -1;
+
+done:
+ if (calendar > 0)
+ stop_calendar(calendar, dir);
+
+ return WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE;
+}
+
static int open_serial(const char *path)
{
struct termios ti;
@@ -1328,6 +1655,20 @@ static const char *btvirt_table[] = {
NULL
};
+static const char *find_btvirt(void)
+{
+ int i;
+
+ for (i = 0; btvirt_table[i]; i++) {
+ struct stat st;
+
+ if (!stat(btvirt_table[i], &st))
+ return btvirt_table[i];
+ }
+
+ return NULL;
+}
+
static pid_t start_btvirt(const char *home, int num)
{
const char *btvirt = NULL;
@@ -1818,9 +2159,14 @@ static void usage(void)
"\t-P, --pcie <qemu_args> Provide PCIe device\n"
"\t-q, --qemu <path> QEMU binary\n"
"\t-H, --qemu-host-cpu Use host CPU (requires KVM support)\n"
- "\t-k, --kernel <image> Kernel bzImage or source tree path\n"
+ "\t-k, --kernel <image> Kernel bzImage, User Mode Linux\n"
+ "\t binary or source tree path\n"
"\t-F, --virtiofs[=<path>] Virtiofsd path or 'no'\n"
- "\t-o, --option <opt> Additional argument passed to QEMU\n"
+ "\t-T, --time-travel Run User Mode Linux on simulated\n"
+ "\t time, with btvirt as calendar\n"
+ "\t-o, --option <opt> Additional argument passed to QEMU,\n"
+ "\t or kernel parameter with User\n"
+ "\t Mode Linux\n"
"\t-h, --help Show help options\n");
}
@@ -1841,6 +2187,7 @@ static const struct option main_options[] = {
{ "pcie", required_argument, NULL, 'P' },
{ "option", required_argument, NULL, 'o' },
{ "virtiofs", optional_argument, NULL, 'F' },
+ { "time-travel", no_argument, NULL, 'T' },
{ "version", no_argument, NULL, 'v' },
{ "help", no_argument, NULL, 'h' },
{ }
@@ -1848,7 +2195,7 @@ static const struct option main_options[] = {
int main(int argc, char *argv[])
{
- char kernel_path[PATH_MAX];
+ static char kernel_path[PATH_MAX];
bool virtiofs_auto = true;
if (getpid() == 1 && getppid() == 0) {
@@ -1856,14 +2203,15 @@ int main(int argc, char *argv[])
run_tests();
sync();
- reboot(RB_AUTOBOOT);
+ /* Rebooting User Mode Linux starts it again */
+ reboot(is_uml_guest() ? RB_POWER_OFF : RB_AUTOBOOT);
return EXIT_SUCCESS;
}
for (;;) {
int opt;
- opt = getopt_long(argc, argv, "au::bdsl::mq:Hk:A::U:P:o:F::vh",
+ opt = getopt_long(argc, argv, "au::bdsl::mq:Hk:A::U:P:o:F::Tvh",
main_options, NULL);
if (opt < 0)
break;
@@ -1918,6 +2266,9 @@ int main(int argc, char *argv[])
}
extra_opts[num_extra_opts++] = optarg;
break;
+ case 'T':
+ time_travel = true;
+ break;
case 'F':
virtiofs_auto = false;
if (optarg) {
@@ -1957,6 +2308,37 @@ int main(int argc, char *argv[])
test_argv = argv + optind;
test_argc = argc - optind;
+ if (!find_kernel(kernel_image, kernel_path)) {
+ fprintf(stderr, "No kernel image found\n");
+ return EXIT_FAILURE;
+ }
+
+ kernel_image = kernel_path;
+
+ if (is_uml_kernel(kernel_image)) {
+ if (num_devs || usb_dev || pcie_dev || qemu_binary ||
+ qemu_host_cpu) {
+ fprintf(stderr, "-u, -U, -P, -q and -H need QEMU, not "
+ "User Mode Linux\n");
+ return EXIT_FAILURE;
+ }
+
+ if (time_travel && !uml_has_time_travel()) {
+ fprintf(stderr, "%s has no time travel support "
+ "(CONFIG_UML_TIME_TRAVEL_SUPPORT)\n",
+ kernel_image);
+ return EXIT_FAILURE;
+ }
+
+ printf("Using User Mode Linux kernel %s\n", kernel_image);
+ return start_uml();
+ }
+
+ if (time_travel) {
+ fprintf(stderr, "-T needs a User Mode Linux kernel\n");
+ return EXIT_FAILURE;
+ }
+
if (!qemu_binary) {
qemu_binary = find_qemu();
if (!qemu_binary) {
@@ -1965,13 +2347,6 @@ int main(int argc, char *argv[])
}
}
- if (!find_kernel(kernel_image, kernel_path)) {
- fprintf(stderr, "No kernel image found\n");
- return EXIT_FAILURE;
- }
-
- kernel_image = kernel_path;
-
printf("Using QEMU binary %s\n", qemu_binary);
printf("Using kernel image %s\n", kernel_image);
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 16/27] doc: Add virtio documentation
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (14 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 15/27] tools/test-runner: Add User Mode Linux support Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 17/27] shared/mainloop: Add idle callbacks Luiz Augusto von Dentz
` (11 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Document the virtio Bluetooth device as implemented by the Linux driver
and what a vhost-user backend has to provide to serve controllers to
virtual machines, including User Mode Linux guests with time travel.
Explain how time travel works, with diagrams of the two clocks, the
calendar protocol, lock-step scheduling, FREE_UNTIL and how a virtio
notification ends it, and why busy-waiting is costly under it, and
compare KVM and User Mode Linux with the wall clock time of the kernel
testers with and without test-runner -T.
Assisted-by: Claude:claude-opus-5-5
---
Makefile.am | 1 +
doc/virtio.rst | 406 +++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 407 insertions(+)
create mode 100644 doc/virtio.rst
diff --git a/Makefile.am b/Makefile.am
index 26884a54ea06..453cc7c584e4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -495,6 +495,7 @@ EXTRA_DIST += doc/assigned-numbers.rst doc/supported-features.txt \
doc/test-coverage.txt \
doc/test-runner.rst \
doc/btvirt.rst \
+ doc/virtio.rst \
doc/test-functional.rst \
doc/functional-testing.rst \
doc/functional-a2dp.rst \
diff --git a/doc/virtio.rst b/doc/virtio.rst
new file mode 100644
index 000000000000..e1148a50c797
--- /dev/null
+++ b/doc/virtio.rst
@@ -0,0 +1,406 @@
+======
+virtio
+======
+
+------------------------------------------------------------
+Virtio Bluetooth device and vhost-user controller backends
+------------------------------------------------------------
+
+Introduction
+============
+
+The virtio Bluetooth device (device ID 40) carries HCI packets between
+a guest kernel and a Bluetooth controller provided by the host. The Linux
+driver is ``drivers/bluetooth/virtio_bt.c``; it registers the device as an
+``hciN`` with bus type ``HCI_VIRTIO``, without any attach step such as
+**btattach(1)**.
+
+The controller behind the device can be anything that speaks HCI: an
+emulator, a firmware simulation or a proxy to real hardware. When the
+controller runs as a separate host process, the device is provided through
+the vhost-user protocol: the guest's virtio transport talks to the process
+over a Unix socket.
+
+This document describes the device as implemented by Linux and what a
+vhost-user backend has to provide, as **btvirt(1)** does with **-V**; see
+`btvirt support`_.
+
+Why virtio and vhost-user
+=========================
+
+The two are separate layers:
+
+- **virtio** is the device: how HCI packets are framed in virtqueues and
+ which configuration the guest reads.
+- **vhost-user** is the transport between the guest and the process that
+ implements the device.
+
+QEMU guests can get a controller without either of them, through an emulated
+serial port and **btattach(1)**; this is what **test-runner(1)** **-u** does.
+User Mode Linux (UML) has no serial device that can connect to a Unix socket;
+its only way to attach an externally implemented device is ``virtio_uml``,
+which speaks vhost-user. On UML, vhost-user virtio is therefore the
+natural way to provide controllers.
+
+It is also required for deterministic simulation. With UML time travel
+(``time-travel=ext``), the guest's clock is driven by an external time
+controller, and all inputs to the guest have to be scheduled through it.
+Data arriving on a plain file descriptor, such as a serial line, is not; a
+vhost-user backend can coordinate every message with the time controller
+(see `Time travel`_).
+
+Device
+======
+
+Feature bits
+------------
+
+The device must offer ``VIRTIO_F_VERSION_1``; the Linux driver refuses
+devices without it. The device specific feature bits are
+(``include/uapi/linux/virtio_bt.h``):
+
+.. list-table::
+ :header-rows: 1
+ :widths: auto
+
+ * - Bit
+ - Name
+ - Meaning
+
+ * - 0
+ - ``VIRTIO_BT_F_VND_HCI``
+ - The ``vendor`` configuration field is valid; the driver applies the
+ vendor specific setup (see `Configuration`_).
+
+ * - 1
+ - ``VIRTIO_BT_F_MSFT_EXT``
+ - The controller supports the Microsoft vendor extension, using the
+ opcode in ``msft_opcode``.
+
+ * - 2
+ - ``VIRTIO_BT_F_AOSP_EXT``
+ - The controller supports the Android (AOSP) vendor extension.
+
+ * - 3
+ - ``VIRTIO_BT_F_CONFIG_V2``
+ - The configuration uses the ``virtio_bt_config_v2`` layout.
+
+Configuration
+-------------
+
+Version 1 layout, packed:
+
+.. code-block::
+
+ struct virtio_bt_config {
+ __u8 type; /* 0: primary controller */
+ __u16 vendor;
+ __u16 msft_opcode;
+ } __attribute__((packed));
+
+Version 2 layout, used if ``VIRTIO_BT_F_CONFIG_V2`` is negotiated:
+
+.. code-block::
+
+ struct virtio_bt_config_v2 {
+ __u8 type;
+ __u8 alignment;
+ __u16 vendor;
+ __u16 msft_opcode;
+ };
+
+``type`` must be ``VIRTIO_BT_CONFIG_TYPE_PRIMARY`` (0); the driver rejects
+other values.
+
+``vendor`` selects the driver's setup and quirks when ``VIRTIO_BT_F_VND_HCI``
+is negotiated:
+
+.. list-table::
+ :header-rows: 1
+ :widths: auto
+
+ * - Value
+ - Vendor
+ - Driver behavior
+
+ * - 0
+ - none
+ - generic controller
+
+ * - 1
+ - Zephyr
+ - manufacturer 1521, Zephyr setup and set-address commands
+
+ * - 2
+ - Intel
+ - manufacturer 2, Intel setup and set-address commands
+
+ * - 3
+ - Realtek
+ - manufacturer 93, Realtek setup
+
+A backend emulating a generic controller should not offer
+``VIRTIO_BT_F_VND_HCI``, or should set ``vendor`` to 0.
+
+Virtqueues
+----------
+
+.. list-table::
+ :header-rows: 1
+ :widths: auto
+
+ * - Index
+ - Name
+ - Direction
+
+ * - 0
+ - tx
+ - guest to device: HCI commands, ACL, SCO and ISO data
+
+ * - 1
+ - rx
+ - device to guest: HCI events, ACL, SCO and ISO data
+
+Each buffer holds exactly one HCI packet in H:4 format: the packet type byte
+(``0x01`` command, ``0x02`` ACL, ``0x03`` SCO, ``0x04`` event, ``0x05`` ISO)
+followed by the packet. There is no other header.
+
+The Linux driver keeps a single receive buffer of 1000 bytes posted on the rx
+queue and posts the next one only after it has processed a packet. A backend
+must therefore expect to find no rx buffer available when it has several
+packets to deliver back to back (for example an advertising report followed
+by its scan response report), and has to queue them until the guest posts a
+buffer, instead of dropping them. Packets longer than the posted buffer are
+rejected by the driver.
+
+The driver performs an HCI Reset when the device is opened, so the backend
+must answer it within the kernel's HCI command timeout.
+
+vhost-user backend
+==================
+
+A backend implements the vhost-user protocol as the slave (back-end) side, on
+a Unix socket that the guest's VMM (QEMU, or the UML kernel itself) connects
+to. Beyond the base protocol it must support:
+
+- ``VHOST_USER_F_PROTOCOL_FEATURES``;
+- protocol feature ``VHOST_USER_PROTOCOL_F_CONFIG``, so the guest can read
+ the configuration with ``VHOST_USER_GET_CONFIG``;
+- two virtqueues, tx and rx as above.
+
+For time travel, it should also support ``VHOST_USER_PROTOCOL_F_SLAVE_REQ``
+and ``VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS``, see `Time travel`_.
+
+The backend reads packets from the tx queue and passes them to its
+controller, and writes the controller's packets into rx buffers and notifies
+the guest.
+
+Attaching to a guest
+====================
+
+UML
+---
+
+Pass one ``virtio_uml.device`` parameter per controller, with the backend's
+socket and the virtio Bluetooth device ID::
+
+ linux ... virtio_uml.device=/tmp/bt-0:40 virtio_uml.device=/tmp/bt-1:40
+
+The UML kernel needs ``CONFIG_VIRTIO_UML`` and ``CONFIG_BT_VIRTIO``.
+
+QEMU
+----
+
+QEMU has no dedicated vhost-user Bluetooth device, but its generic
+``vhost-user-test-device-pci`` attaches a backend by device ID. The guest
+memory must be shared, for example with a ``memory-backend-memfd`` and
+``share=on``, as **test-runner(1)** sets up for virtio-fs::
+
+ -chardev socket,id=bt0,path=/tmp/bt-vhost-bredrle
+ -device vhost-user-test-device-pci,chardev=bt0,virtio-id=40,num_vqs=2,config_size=5
+
+Time travel
+===========
+
+With ``time-travel=ext:[<id>:]<socket>``, the UML kernel's time only advances
+when an external time controller allows it, and every process taking part in
+the simulation connects to the same controller using the protocol in
+``include/uapi/linux/um_timetravel.h``. A controller backend that takes part
+in the simulation:
+
+- takes part in the scheduling of the time controller (calendar), so its
+ own events (such as radio activity) happen at simulated times; btvirt
+ does this by being the calendar itself;
+- uses in-band notifications, which ``virtio_uml`` enables when both
+ ``VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS`` and
+ ``VHOST_USER_PROTOCOL_F_SLAVE_REQ`` are negotiated. Kicks and calls are then
+ messages on the vhost-user sockets instead of eventfds or pipes. This is
+ required: a call file descriptor is a regular interrupt, which UML does not
+ handle while it waits for the time controller, whereas on a back-end
+ request it asks the time controller to run at the current time before
+ acknowledging it. The backend therefore has to keep serving the time
+ controller while it waits for the acknowledgment.
+
+Packet timing, controller timeouts and the guest's HCI command timeouts are
+then all measured on the same simulated clock, and runs are reproducible.
+
+btvirt support
+==============
+
+**btvirt(1)** **-V** creates a vhost-user server socket,
+``bt-vhost-bredrle``. Each connection gets a new BR/EDR/LE controller that
+shares btvirt's emulated air, like the Unix socket servers of **-s**, so one
+socket path can be given to several devices. Packets for the driver are
+queued until it posts receive buffers.
+
+With **--time**, btvirt is also the guests' time travel calendar and
+offers in-band notifications, see `Time travel`_.
+
+User Mode Linux and KVM
+=======================
+
+**test-runner(1)** runs guests under QEMU, normally with KVM. A User Mode
+Linux guest with btvirt as calendar differs in where the controllers run and
+which clock everything follows.
+
+With QEMU and KVM, the guest's clock follows the host's real time; the
+controllers are reached over emulated serial ports (**btvirt(1)** **-s**) or
+are inside the guest (``/dev/vhci``, used by the kernel testers)::
+
+ host real time
+ ┌──────────────────────────────────────────────────────────────┐
+ │ btvirt -s │
+ │ └ bt-server-bredrle ── H:4 ──┐ │
+ │ qemu (KVM) │ │
+ │ ┌ guest ─────────────────────┴───────────────────────────┐ │
+ │ │ kernel ── hci_uart (btattach) ── hciN │ │
+ │ │ bluetoothd, bluetoothctl │ │
+ │ │ or: mgmt-tester ── hciemu ── /dev/vhci ── hciN │ │
+ │ └────────────────────────────────────────────────────────┘ │
+ └──────────────────────────────────────────────────────────────┘
+
+With User Mode Linux and time travel, the guests run as host processes, but
+their clocks only advance when btvirt, as calendar, lets them; the
+controllers are virtio devices served by btvirt, and their timers run on
+the same simulated clock::
+
+ host simulated time
+ ┌──────────────────────────────────────────────────────────────┐
+ │ btvirt --time -V │
+ │ ├ bt-time ─────────── calendar ───────┬─────────────┐ │
+ │ └ bt-vhost-bredrle ── vhost-user ──┬──┼─────────┐ │ │
+ │ │ │ │ │ │
+ │ ┌ linux (UML) ─────────────────────┴──┴┐ ┌─────┴───┴──┐ │
+ │ │ time-travel=ext:.../bt-time │ │ linux (UML)│ │
+ │ │ virtio_uml ── virtio_bt ── hciN │ │ ... │ │
+ │ │ bluetoothd, bluetoothctl │ │ │ │
+ │ └──────────────────────────────────────┘ └────────────┘ │
+ └──────────────────────────────────────────────────────────────┘
+
+How time travel works
+---------------------
+
+Two clocks matter: the host's real time, which flows continuously, and the
+simulated time of the guests, which only advances when every participant
+waits and then jumps straight to the next event. Computing takes no
+simulated time, and idle time takes no real time::
+
+ simulated time
+ ▲
+ 250 ms┤ ┌──────── computing: real time passes,
+ │ │ simulated time stands still
+ │ │
+ │ │ ◀──────── idle until the next timer:
+ 10 ms┤ ┌──────────┘ skipped, no real time passes
+ │ │
+ 0 ms┼───────────┘
+ └──────────────────────────────────────────────▶ real time
+
+A guest joins the calendar with ``time-travel=ext:<socket>`` and talks to it
+with the messages of ``include/uapi/linux/um_timetravel.h``. When it has
+nothing left to do before its next timer, it asks to run again at that time
+and waits; the calendar answers with RUN once every other participant waits
+too and no earlier event is due::
+
+ guest calendar (btvirt)
+ │ REQUEST(t=10 ms) next timer │
+ ├────────────────────────────────────▶│
+ │◀──────────────────────────── ACK ───┤
+ │ WAIT nothing to do │
+ ├────────────────────────────────────▶│ all others wait,
+ │ │ earliest event: 10 ms
+ │◀─────────────────────── RUN(10 ms) ─┤
+ │ runs its timer at 10 ms │
+
+The calendar always runs the earliest event, a guest's request or one of
+btvirt's own timers, e.g. those of the emulated controllers, so every
+participant sees the same order of events on every run (lock-step)::
+
+ guest A ──run── WAIT ·············· RUN(t1) ──run── WAIT ·····
+ guest B ──run──────── WAIT ·························· RUN(t2) ─
+ btvirt timer(t0)
+ time ─────────────────t0────────t1─────────────────t2─────▶
+ (no real time passes while all wait)
+
+Asking before each timer costs about four messages per event. With
+``FREE_UNTIL``, the calendar instead tells a guest how far it may run on its
+own, up to the next event of any other participant, so a guest only waits
+when it has to::
+
+ guest A ── RUN(t1) + FREE_UNTIL(t3) ─▶ runs its own events at
+ t1 < ta < tb < t3 without any message, then WAITs for t3
+ guest B ············ request at t3 ·············· RUN(t3) ──▶
+ time ────t1─────ta──────tb──────────────────────t3────────▶
+
+A virtio notification (kick) from the guest to btvirt may cause an event for
+it at the current time, e.g. the controller's reply. The guest sends its
+time (UPDATE) before each kick, and btvirt lowers its limit to that time
+before acknowledging it, so the guest cannot run past the reply::
+
+ guest ─ UPDATE(tk) ─ kick ─┐ stopped at tk ┌─ IRQ at tk ──▶
+ btvirt FREE_UNTIL(tk) ─┘ reply event ───┘
+ time ───────────────────tk──────────────────────────────▶
+
+Busy-waiting does not work under time travel: as computing takes no
+simulated time, a loop waiting for time to pass only advances by the 256 ns
+UML charges for each system call. A main loop woken up over and over by a
+socket it does not handle, e.g. one that hung up while only ``G_IO_IN`` is
+watched, then needs tens of thousands of iterations for a 250 ms timer.
+
+Which tests benefit
+-------------------
+
+Wall clock time of the kernel testers with **test-runner(1)**, same kernel
+version and machine, User Mode Linux with seccomp, with and without
+**-T**, all tests passing::
+
+ tester tests KVM UML UML -T
+ mgmt-tester 514 83.6 111.6 42.6
+ l2cap-tester 111 9.6 13.7 10.2
+ iso-tester 141 13.7 24.3 16.3
+ sco-tester 30 5.1 4.1 3.6
+ rfcomm-tester 11 3.6 2.0 2.6
+ smp-tester 8 4.1 1.5 1.5
+ ioctl-tester 28 4.1 2.6 3.1
+ userchan-tester 4 2.6 1.5 1.5
+ bnep-tester 1 2.6 1.0 1.0
+
+UML runs the guest's system calls and context switches considerably slower
+than KVM, as each one goes through the host, so testers exchanging many
+small messages are slower without time travel. With it, tests that wait,
+like most of mgmt-tester, take no real time for timers and timeouts. Small
+testers are faster under UML, which starts in a fraction of a second.
+
+Time travel helps most when tests wait: timers, timeouts and idle time take
+no real time, and the timing is the same on every run. It also lets
+controllers outside the guests, such as btvirt's or a firmware simulation,
+share the guests' clock, as in **test-functional(1)**.
+
+SEE ALSO
+========
+
+**btvirt(1)**, **test-runner(1)**, **test-functional(1)**, **btattach(1)**
+
+- vhost-user protocol: ``docs/interop/vhost-user.rst`` in the QEMU sources
+- ``drivers/bluetooth/virtio_bt.c`` and
+ ``include/uapi/linux/virtio_bt.h`` in the Linux sources
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 17/27] shared/mainloop: Add idle callbacks
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (15 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 16/27] doc: Add virtio documentation Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 18/27] shared/timeout: Run timeouts of 0 once idle Luiz Augusto von Dentz
` (10 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Add mainloop_add_idle(), whose callback runs once no file descriptor is
ready, after everything pending was handled, as GLib's idle sources do.
It is armed again with mainloop_modify_timeout() with 0 and removed with
mainloop_remove_timeout(), its id being above the file descriptors.
A timeout of 0 cannot be used for this: mainloop_add_timeout() leaves it
disarmed, and a short timer would run before pending events.
Assisted-by: Claude:claude-opus-5-5
---
src/shared/mainloop-ell.c | 6 ++
src/shared/mainloop-glib.c | 6 ++
src/shared/mainloop.c | 156 ++++++++++++++++++++++++++++++++++++-
src/shared/mainloop.h | 2 +
4 files changed, 169 insertions(+), 1 deletion(-)
diff --git a/src/shared/mainloop-ell.c b/src/shared/mainloop-ell.c
index 1aec7fad9e96..1dda9c9ef761 100644
--- a/src/shared/mainloop-ell.c
+++ b/src/shared/mainloop-ell.c
@@ -100,6 +100,12 @@ int mainloop_add_timeout(unsigned int msec, mainloop_timeout_func callback,
return -ENOSYS;
}
+int mainloop_add_idle(mainloop_timeout_func callback, void *user_data,
+ mainloop_destroy_func destroy)
+{
+ return -EOPNOTSUPP;
+}
+
int mainloop_modify_timeout(int fd, unsigned int msec)
{
return -ENOSYS;
diff --git a/src/shared/mainloop-glib.c b/src/shared/mainloop-glib.c
index 042119baf1ad..971562610e02 100644
--- a/src/shared/mainloop-glib.c
+++ b/src/shared/mainloop-glib.c
@@ -94,6 +94,12 @@ int mainloop_add_timeout(unsigned int msec, mainloop_timeout_func callback,
return -ENOSYS;
}
+int mainloop_add_idle(mainloop_timeout_func callback, void *user_data,
+ mainloop_destroy_func destroy)
+{
+ return -EOPNOTSUPP;
+}
+
int mainloop_modify_timeout(int fd, unsigned int msec)
{
return -ENOSYS;
diff --git a/src/shared/mainloop.c b/src/shared/mainloop.c
index b3ec0f2b1773..652436be944d 100644
--- a/src/shared/mainloop.c
+++ b/src/shared/mainloop.c
@@ -19,6 +19,7 @@
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
+#include <stdbool.h>
#include <string.h>
#include <signal.h>
#include <sys/signalfd.h>
@@ -88,6 +89,10 @@ void mainloop_exit_failure(void)
epoll_terminate = 1;
}
+static bool idle_pending(void);
+static void run_idles(void);
+static void remove_all_idles(void);
+
int mainloop_run(void)
{
unsigned int i;
@@ -96,10 +101,16 @@ int mainloop_run(void)
struct epoll_event events[MAX_EPOLL_EVENTS];
int n, nfds;
- nfds = epoll_wait(epoll_fd, events, MAX_EPOLL_EVENTS, -1);
+ nfds = epoll_wait(epoll_fd, events, MAX_EPOLL_EVENTS,
+ idle_pending() ? 0 : -1);
if (nfds < 0)
continue;
+ if (!nfds) {
+ run_idles();
+ continue;
+ }
+
for (n = 0; n < nfds; n++) {
uint64_t id = events[n].data.u64;
int fd = id % MAX_MAINLOOP_ENTRIES;
@@ -129,6 +140,8 @@ int mainloop_run(void)
}
}
+ remove_all_idles();
+
close(epoll_fd);
epoll_fd = 0;
@@ -309,8 +322,138 @@ int mainloop_add_timeout(unsigned int msec, mainloop_timeout_func callback,
return data->fd;
}
+/* Idle callbacks run when no file descriptor is ready, after everything
+ * pending was handled, as with GLib. Their ids are above the file
+ * descriptors, so the timeout functions tell them apart.
+ */
+#define IDLE_ID_BASE 0x10000
+
+struct idle_data {
+ int id;
+ bool armed;
+ bool removed;
+ mainloop_timeout_func callback;
+ mainloop_destroy_func destroy;
+ void *user_data;
+ struct idle_data *next;
+};
+
+static struct idle_data *idle_list;
+static int idle_next_id = IDLE_ID_BASE;
+
+static struct idle_data *find_idle(int id)
+{
+ struct idle_data *idle;
+
+ for (idle = idle_list; idle; idle = idle->next) {
+ if (idle->id == id && !idle->removed)
+ return idle;
+ }
+
+ return NULL;
+}
+
+static bool idle_pending(void)
+{
+ struct idle_data *idle;
+
+ for (idle = idle_list; idle; idle = idle->next) {
+ if (idle->armed && !idle->removed)
+ return true;
+ }
+
+ return false;
+}
+
+static void free_removed_idles(void)
+{
+ struct idle_data **prev = &idle_list;
+
+ while (*prev) {
+ struct idle_data *idle = *prev;
+
+ if (!idle->removed) {
+ prev = &idle->next;
+ continue;
+ }
+
+ *prev = idle->next;
+
+ if (idle->destroy)
+ idle->destroy(idle->user_data);
+
+ free(idle);
+ }
+}
+
+static void remove_all_idles(void)
+{
+ struct idle_data *idle;
+
+ for (idle = idle_list; idle; idle = idle->next)
+ idle->removed = true;
+
+ free_removed_idles();
+}
+
+/* Run the idle callbacks armed before this pass, once each */
+static void run_idles(void)
+{
+ struct idle_data *idle;
+ int last = idle_next_id;
+
+ for (idle = idle_list; idle; idle = idle->next) {
+ if (idle->removed || !idle->armed || idle->id >= last)
+ continue;
+
+ idle->armed = false;
+ idle->callback(idle->id, idle->user_data);
+ }
+
+ free_removed_idles();
+}
+
+/* Run callback once the mainloop is idle, again after
+ * mainloop_modify_timeout() with 0; removed with mainloop_remove_timeout().
+ */
+int mainloop_add_idle(mainloop_timeout_func callback, void *user_data,
+ mainloop_destroy_func destroy)
+{
+ struct idle_data *idle, **last;
+
+ if (!callback)
+ return -EINVAL;
+
+ idle = malloc(sizeof(*idle));
+ if (!idle)
+ return -ENOMEM;
+
+ memset(idle, 0, sizeof(*idle));
+ idle->id = idle_next_id++;
+ idle->armed = true;
+ idle->callback = callback;
+ idle->destroy = destroy;
+ idle->user_data = user_data;
+
+ for (last = &idle_list; *last; last = &(*last)->next)
+ ;
+ *last = idle;
+
+ return idle->id;
+}
+
int mainloop_modify_timeout(int id, unsigned int msec)
{
+ if (id >= IDLE_ID_BASE) {
+ struct idle_data *idle = find_idle(id);
+
+ if (!idle)
+ return -ENOENT;
+
+ idle->armed = true;
+ return 0;
+ }
+
if (msec > 0) {
if (timeout_set(id, msec) < 0)
return -EIO;
@@ -324,5 +467,16 @@ int mainloop_modify_timeout(int id, unsigned int msec)
int mainloop_remove_timeout(int id)
{
+ if (id >= IDLE_ID_BASE) {
+ struct idle_data *idle = find_idle(id);
+
+ if (!idle)
+ return -ENOENT;
+
+ /* Freed after the current pass, which may be running it */
+ idle->removed = true;
+ return 0;
+ }
+
return mainloop_remove_fd(id);
}
diff --git a/src/shared/mainloop.h b/src/shared/mainloop.h
index fb02dd34109b..2dd07ce03818 100644
--- a/src/shared/mainloop.h
+++ b/src/shared/mainloop.h
@@ -33,6 +33,8 @@ int mainloop_remove_fd(int fd);
int mainloop_add_timeout(unsigned int msec, mainloop_timeout_func callback,
void *user_data, mainloop_destroy_func destroy);
int mainloop_modify_timeout(int fd, unsigned int msec);
+int mainloop_add_idle(mainloop_timeout_func callback, void *user_data,
+ mainloop_destroy_func destroy);
int mainloop_remove_timeout(int id);
int mainloop_set_signal(sigset_t *mask, mainloop_signal_func callback,
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 18/27] shared/timeout: Run timeouts of 0 once idle
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (16 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 17/27] shared/mainloop: Add idle callbacks Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 19/27] shared/tester: Remove GLib dependency Luiz Augusto von Dentz
` (9 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
With the mainloop backend timeout_add() with 0 never runs, as a timer
of 0 is disarmed, e.g. bt_hci's deferred event filter update. Use an
idle callback for it instead.
With GLib it runs at default priority, before pending events, so run it
at idle priority, as with the mainloop backend, which is what deferring
to the next iteration means.
Assisted-by: Claude:claude-opus-5-5
---
src/shared/timeout-glib.c | 10 +++++++++-
src/shared/timeout-mainloop.c | 9 ++++++++-
2 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/src/shared/timeout-glib.c b/src/shared/timeout-glib.c
index 3268d480c3a7..549e3fa5822b 100644
--- a/src/shared/timeout-glib.c
+++ b/src/shared/timeout-glib.c
@@ -52,7 +52,15 @@ unsigned int timeout_add(unsigned int timeout, timeout_func_t func,
data->destroy = destroy;
data->user_data = user_data;
- id = g_timeout_add_full(G_PRIORITY_DEFAULT, timeout, timeout_callback,
+ /* A timeout of 0 runs once nothing else is pending, as with the
+ * mainloop backend.
+ */
+ if (timeout)
+ id = g_timeout_add_full(G_PRIORITY_DEFAULT, timeout,
+ timeout_callback, data,
+ timeout_destroy);
+ else
+ id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, timeout_callback,
data, timeout_destroy);
if (!id)
g_free(data);
diff --git a/src/shared/timeout-mainloop.c b/src/shared/timeout-mainloop.c
index 9be803cda020..e6f9c5e826b3 100644
--- a/src/shared/timeout-mainloop.c
+++ b/src/shared/timeout-mainloop.c
@@ -54,7 +54,14 @@ unsigned int timeout_add(unsigned int timeout, timeout_func_t func,
data->timeout = timeout;
data->destroy = destroy;
- data->id = mainloop_add_timeout(timeout, timeout_callback, data,
+ /* A timeout of 0 runs as soon as the mainloop gets to it, as with the
+ * other backends, while mainloop_add_timeout() leaves it disarmed.
+ */
+ if (timeout)
+ data->id = mainloop_add_timeout(timeout, timeout_callback, data,
+ timeout_destroy);
+ else
+ data->id = mainloop_add_idle(timeout_callback, data,
timeout_destroy);
if (data->id < 0) {
free(data);
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 19/27] shared/tester: Remove GLib dependency
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (17 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 18/27] shared/timeout: Run timeouts of 0 once idle Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 20/27] emulator/hciemu: " Luiz Augusto von Dentz
` (8 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Use timeout_add() with 0 for the idle callbacks, struct queue for the
tests, CLOCK_MONOTONIC for the execution times and getopt_long() for the
options, and build it into libshared-mainloop as well, so testers can
run on the mainloop instead of GLib.
GLib's main loop reads the clock for every source on every iteration,
which under User Mode Linux is a system call through the host, about 6
us each: it was 78% of the system calls of mgmt-tester, 1.2 million in
one run.
Assisted-by: Claude:claude-opus-5-5
---
Makefile.am | 3 +-
src/shared/tester.c | 250 +++++++++++++++++++++++++++-----------------
2 files changed, 156 insertions(+), 97 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index 453cc7c584e4..f8821a109b49 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -272,7 +272,8 @@ src_libshared_mainloop_la_SOURCES = $(shared_sources) \
src/shared/timeout-mainloop.c \
src/shared/mainloop.h src/shared/mainloop.c \
src/shared/mainloop-notify.h \
- src/shared/mainloop-notify.c
+ src/shared/mainloop-notify.c \
+ src/shared/tester.c
src_libshared_mainloop_la_LDFLAGS = $(AM_LDFLAGS)
src_libshared_mainloop_la_CFLAGS = $(AM_CFLAGS)
diff --git a/src/shared/tester.c b/src/shared/tester.c
index 4065545793aa..72b280a1f3d5 100644
--- a/src/shared/tester.c
+++ b/src/shared/tester.c
@@ -20,11 +20,11 @@
#include <stdlib.h>
#include <string.h>
#include <signal.h>
+#include <getopt.h>
+#include <time.h>
#include <sys/signalfd.h>
#include <sys/socket.h>
-#include <glib.h>
-
#include "bluetooth/bluetooth.h"
#include "bluetooth/hci.h"
@@ -38,6 +38,7 @@
#include "src/shared/tester.h"
#include "src/shared/log.h"
#include "src/shared/timeout.h"
+#include "src/shared/queue.h"
#define COLOR_OFF "\x1B[0m"
#define COLOR_BLACK "\x1B[0;30m"
@@ -90,8 +91,8 @@ struct test_case {
tester_data_func_t teardown_func;
tester_data_func_t post_teardown_func;
tester_data_func_t io_complete_func;
- gdouble start_time;
- gdouble end_time;
+ double start_time;
+ double end_time;
unsigned int timeout;
unsigned int timeout_id;
unsigned int teardown_id;
@@ -101,15 +102,16 @@ struct test_case {
static char *tester_name;
-static GList *test_list;
-static GList *test_current;
-static GTimer *test_timer;
+static struct queue *test_list;
+static const struct queue_entry *test_current;
+static double test_timer_start;
+static double test_timer_stop;
-static gboolean option_version = FALSE;
-static gboolean option_quiet = FALSE;
-static gboolean option_debug = FALSE;
-static gboolean option_monitor = FALSE;
-static gboolean option_list = FALSE;
+static bool option_version;
+static bool option_quiet;
+static bool option_debug;
+static bool option_monitor;
+static bool option_list;
static const char *option_prefix = NULL;
static const char *option_string = NULL;
@@ -126,7 +128,20 @@ struct monitor_l2cap_hdr {
uint16_t psm;
} __attribute__((packed));
-static void test_destroy(gpointer data)
+/* Seconds since tester_run(), on the clock the timeouts use */
+static double test_timer_elapsed(void)
+{
+ struct timespec ts;
+
+ if (test_timer_stop)
+ return test_timer_stop - test_timer_start;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+
+ return ts.tv_sec + ts.tv_nsec / 1e9 - test_timer_start;
+}
+
+static void test_destroy(void *data)
{
struct test_case *test = data;
@@ -134,7 +149,7 @@ static void test_destroy(gpointer data)
timeout_remove(test->timeout_id);
if (test->teardown_id > 0)
- g_source_remove(test->teardown_id);
+ timeout_remove(test->teardown_id);
if (test->destroy)
test->destroy(test->user_data);
@@ -285,7 +300,8 @@ void tester_add_full(const char *name, const void *test_data,
if (!test_func)
return;
- if (option_prefix && !g_str_has_prefix(name, option_prefix)) {
+ if (option_prefix && strncmp(name, option_prefix,
+ strlen(option_prefix))) {
if (destroy)
destroy(user_data);
return;
@@ -338,7 +354,7 @@ void tester_add_full(const char *name, const void *test_data,
test->destroy = destroy;
test->user_data = user_data;
- test_list = g_list_append(test_list, test);
+ queue_push_tail(test_list, test);
}
void tester_add(const char *name, const void *test_data,
@@ -371,17 +387,17 @@ void *tester_get_data(void)
static int tester_summarize(void)
{
unsigned int not_run = 0, passed = 0, failed = 0;
- gdouble execution_time;
- GList *list;
+ double execution_time;
+ const struct queue_entry *list;
tester_log("");
print_text(COLOR_HIGHLIGHT, "");
print_text(COLOR_HIGHLIGHT, "Test Summary");
print_text(COLOR_HIGHLIGHT, "------------");
- for (list = g_list_first(test_list); list; list = g_list_next(list)) {
+ for (list = queue_get_entries(test_list); list; list = list->next) {
struct test_case *test = list->data;
- gdouble exec_time;
+ double exec_time;
exec_time = test->end_time - test->start_time;
@@ -417,22 +433,22 @@ static int tester_summarize(void)
(float) passed * 100 / (not_run + passed + failed) : 0,
failed, not_run);
- execution_time = g_timer_elapsed(test_timer, NULL);
+ execution_time = test_timer_elapsed();
tester_log("Overall execution time: %.3g seconds", execution_time);
return failed;
}
-static gboolean done_callback(gpointer user_data);
+static bool done_callback(void *user_data);
-static bool teardown_timeout(gpointer user_data)
+static bool teardown_timeout(void *user_data)
{
struct test_case *test = user_data;
test->timeout_id = 0;
if (!test_current || test_current->data != test)
- return FALSE;
+ return false;
/* Give up waiting, e.g. for an index that was never added to be
* removed, rather than blocking for good. Its callbacks may still
@@ -448,9 +464,9 @@ static bool teardown_timeout(gpointer user_data)
while (test_current->next)
test_current = test_current->next;
- g_idle_add(done_callback, test);
+ timeout_add(0, done_callback, test, NULL);
- return FALSE;
+ return false;
}
/* Teardown may have to wait for the kernel to time out commands still sent
@@ -472,7 +488,7 @@ static void teardown_timeout_start(struct test_case *test)
teardown_timeout, test, NULL);
}
-static gboolean teardown_callback(gpointer user_data)
+static bool teardown_callback(void *user_data)
{
struct test_case *test = user_data;
@@ -488,24 +504,24 @@ static gboolean teardown_callback(gpointer user_data)
VALGRIND_DO_ADDED_LEAK_CHECK;
#endif
- return FALSE;
+ return false;
}
-static bool test_timeout(gpointer user_data)
+static bool test_timeout(void *user_data)
{
struct test_case *test = user_data;
test->timeout_id = 0;
if (!test_current)
- return FALSE;
+ return false;
test->result = TEST_RESULT_TIMED_OUT;
print_progress(test->name, COLOR_RED, "test timed out");
- g_idle_add(teardown_callback, test);
+ timeout_add(0, teardown_callback, test, NULL);
- return FALSE;
+ return false;
}
static void next_test_case(void)
@@ -513,12 +529,12 @@ static void next_test_case(void)
struct test_case *test;
if (test_current)
- test_current = g_list_next(test_current);
+ test_current = test_current->next;
else
- test_current = test_list;
+ test_current = queue_get_entries(test_list);
if (!test_current) {
- g_timer_stop(test_timer);
+ test_timer_stop = test_timer_elapsed() + test_timer_start;
mainloop_quit();
return;
@@ -529,7 +545,7 @@ static void next_test_case(void)
tester_log("");
print_progress(test->name, COLOR_BLACK, "init");
- test->start_time = g_timer_elapsed(test_timer, NULL);
+ test->start_time = test_timer_elapsed();
if (test->timeout > 0)
test->timeout_id = timeout_add_seconds(test->timeout,
@@ -541,7 +557,7 @@ static void next_test_case(void)
test->pre_setup_func(test->test_data);
}
-static gboolean setup_callback(gpointer user_data)
+static bool setup_callback(void *user_data)
{
struct test_case *test = user_data;
@@ -550,10 +566,10 @@ static gboolean setup_callback(gpointer user_data)
print_progress(test->name, COLOR_BLUE, "setup");
test->setup_func(test->test_data);
- return FALSE;
+ return false;
}
-static gboolean run_callback(gpointer user_data)
+static bool run_callback(void *user_data)
{
struct test_case *test = user_data;
@@ -562,19 +578,19 @@ static gboolean run_callback(gpointer user_data)
print_progress(test->name, COLOR_BLACK, "run");
test->test_func(test->test_data);
- return FALSE;
+ return false;
}
-static gboolean done_callback(gpointer user_data)
+static bool done_callback(void *user_data)
{
struct test_case *test = user_data;
- test->end_time = g_timer_elapsed(test_timer, NULL);
+ test->end_time = test_timer_elapsed();
print_progress(test->name, COLOR_BLACK, "done");
next_test_case();
- return FALSE;
+ return false;
}
void tester_pre_setup_complete(void)
@@ -589,7 +605,7 @@ void tester_pre_setup_complete(void)
if (test->stage != TEST_STAGE_PRE_SETUP)
return;
- g_idle_add(setup_callback, test);
+ timeout_add(0, setup_callback, test, NULL);
}
void tester_pre_setup_failed(void)
@@ -613,7 +629,7 @@ void tester_pre_setup_failed(void)
print_progress(test->name, COLOR_RED, "pre setup failed");
- g_idle_add(done_callback, test);
+ timeout_add(0, done_callback, test, NULL);
}
void tester_pre_setup_abort(void)
@@ -635,7 +651,7 @@ void tester_pre_setup_abort(void)
print_progress(test->name, COLOR_YELLOW, "not run");
- g_idle_add(done_callback, test);
+ timeout_add(0, done_callback, test, NULL);
}
bool tester_pre_setup_skip_by_default(void)
@@ -662,7 +678,7 @@ void tester_setup_complete(void)
print_progress(test->name, COLOR_BLUE, "setup complete");
- g_idle_add(run_callback, test);
+ timeout_add(0, run_callback, test, NULL);
}
void tester_setup_failed(void)
@@ -729,7 +745,7 @@ static void test_result(enum test_result result)
if (test->teardown_id > 0)
return;
- test->teardown_id = g_idle_add(teardown_callback, test);
+ test->teardown_id = timeout_add(0, teardown_callback, test, NULL);
}
void tester_test_passed(void)
@@ -800,7 +816,7 @@ void tester_post_teardown_complete(void)
print_progress(test->name, COLOR_MAGENTA, "teardown complete");
- g_idle_add(done_callback, test);
+ timeout_add(0, done_callback, test, NULL);
}
void tester_post_teardown_failed(void)
@@ -822,16 +838,19 @@ void tester_post_teardown_failed(void)
print_progress(test->name, COLOR_RED, "teardown failed");
- g_idle_add(done_callback, test);
+ timeout_add(0, done_callback, test, NULL);
}
-static gboolean start_tester(gpointer user_data)
+static bool start_tester(void *user_data)
{
- test_timer = g_timer_new();
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ test_timer_start = ts.tv_sec + ts.tv_nsec / 1e9;
next_test_case();
- return FALSE;
+ return false;
}
struct wait_data {
@@ -841,7 +860,7 @@ struct wait_data {
void *user_data;
};
-static gboolean wait_callback(gpointer user_data)
+static bool wait_callback(void *user_data)
{
struct wait_data *wait = user_data;
struct test_case *test = wait->test;
@@ -851,7 +870,7 @@ static gboolean wait_callback(gpointer user_data)
if (wait->seconds > 0) {
print_progress(test->name, COLOR_BLACK, "%u seconds left",
wait->seconds);
- return TRUE;
+ return true;
}
print_progress(test->name, COLOR_BLACK, "waiting done");
@@ -860,7 +879,7 @@ static gboolean wait_callback(gpointer user_data)
free(wait);
- return FALSE;
+ return false;
}
void tester_wait(unsigned int seconds, tester_wait_func_t func,
@@ -883,7 +902,7 @@ void tester_wait(unsigned int seconds, tester_wait_func_t func,
wait->func = func;
wait->user_data = user_data;
- g_timeout_add(1000, wait_callback, wait);
+ timeout_add(1000, wait_callback, wait, NULL);
print_progress(test->name, COLOR_BLACK, "waiting %u seconds", seconds);
}
@@ -905,53 +924,88 @@ static void signal_callback(int signum, void *user_data)
bool tester_use_quiet(void)
{
- return option_quiet == TRUE ? true : false;
+ return option_quiet;
}
bool tester_use_debug(void)
{
- return option_debug == TRUE ? true : false;
+ return option_debug;
}
-static GOptionEntry options[] = {
- { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version,
- "Show version information and exit" },
- { "quiet", 'q', 0, G_OPTION_ARG_NONE, &option_quiet,
- "Run tests without logging" },
- { "debug", 'd', 0, G_OPTION_ARG_NONE, &option_debug,
- "Run tests with debug output" },
- { "monitor", 'm', 0, G_OPTION_ARG_NONE, &option_monitor,
- "Enable monitor output" },
- { "list", 'l', 0, G_OPTION_ARG_NONE, &option_list,
- "Only list the tests to be run" },
- { "prefix", 'p', 0, G_OPTION_ARG_STRING, &option_prefix,
- "Run tests matching provided prefix" },
- { "string", 's', 0, G_OPTION_ARG_STRING, &option_string,
- "Run tests matching provided string" },
- { NULL },
+static const struct option options[] = {
+ { "version", no_argument, NULL, 'v' },
+ { "quiet", no_argument, NULL, 'q' },
+ { "debug", no_argument, NULL, 'd' },
+ { "monitor", no_argument, NULL, 'm' },
+ { "list", no_argument, NULL, 'l' },
+ { "prefix", required_argument, NULL, 'p' },
+ { "string", required_argument, NULL, 's' },
+ { "help", no_argument, NULL, 'h' },
+ { }
};
+static void usage(const char *name)
+{
+ printf("Usage:\n"
+ "\t%s [options]\n"
+ "Options:\n"
+ "\t-v, --version Show version information and exit\n"
+ "\t-q, --quiet Run tests without logging\n"
+ "\t-d, --debug Run tests with debug output\n"
+ "\t-m, --monitor Enable monitor output\n"
+ "\t-l, --list Only list the tests to be run\n"
+ "\t-p, --prefix=<prefix> Run tests matching provided prefix\n"
+ "\t-s, --string=<string> Run tests matching provided string\n"
+ "\t-h, --help Show help options\n", name);
+}
+
void tester_init(int *argc, char ***argv)
{
- GOptionContext *context;
- GError *error = NULL;
+ int opt, i;
- context = g_option_context_new(NULL);
- g_option_context_add_main_entries(context, options, NULL);
+ for (;;) {
+ opt = getopt_long(*argc, *argv, "vqdmlp:s:h", options, NULL);
+ if (opt < 0)
+ break;
- if (g_option_context_parse(context, argc, argv, &error) == FALSE) {
- if (error != NULL) {
- g_printerr("%s\n", error->message);
- g_error_free(error);
- } else
- g_printerr("An unknown error occurred\n");
- exit(1);
+ switch (opt) {
+ case 'v':
+ option_version = true;
+ break;
+ case 'q':
+ option_quiet = true;
+ break;
+ case 'd':
+ option_debug = true;
+ break;
+ case 'm':
+ option_monitor = true;
+ break;
+ case 'l':
+ option_list = true;
+ break;
+ case 'p':
+ option_prefix = optarg;
+ break;
+ case 's':
+ option_string = optarg;
+ break;
+ case 'h':
+ usage((*argv)[0]);
+ exit(EXIT_SUCCESS);
+ default:
+ exit(EXIT_FAILURE);
+ }
}
- g_option_context_free(context);
+ /* Leave the program name and the arguments that are not options */
+ for (i = optind; i < *argc; i++)
+ (*argv)[i - optind + 1] = (*argv)[i];
+ *argc -= optind - 1;
+ (*argv)[*argc] = NULL;
- if (option_version == TRUE) {
- g_print("%s\n", VERSION);
+ if (option_version) {
+ printf("%s\n", VERSION);
exit(EXIT_SUCCESS);
}
@@ -963,7 +1017,7 @@ void tester_init(int *argc, char ***argv)
else
tester_name = strdup(++tester_name);
- test_list = NULL;
+ test_list = queue_new();
test_current = NULL;
}
@@ -1029,7 +1083,8 @@ static bool test_io_send(struct io *io, void *user_data)
tester_monitor('<', 0x0004, 0x0000, iov->iov_base, len);
- g_assert_cmpint(len, ==, iov->iov_len);
+ if (len != (ssize_t) iov->iov_len)
+ abort();
if (!test->iovcnt && test->io_complete_func) {
test->io_complete_func(test->test_data);
@@ -1053,7 +1108,8 @@ static bool test_io_recv(struct io *io, void *user_data)
len = read(fd, buf, sizeof(buf));
- g_assert(len > 0);
+ if (len <= 0)
+ abort();
tester_monitor('>', 0x0004, 0x0000, buf, len);
@@ -1067,9 +1123,11 @@ static bool test_io_recv(struct io *io, void *user_data)
tester_monitor('!', 0x0004, 0x0000, iov->iov_base,
iov->iov_len);
- g_assert_cmpint(len, ==, iov->iov_len);
+ if (len != (ssize_t) iov->iov_len)
+ abort();
- g_assert(memcmp(buf, iov->iov_base, len) == 0);
+ if (memcmp(buf, iov->iov_base, len))
+ abort();
if (test->iovcnt)
io_set_write_handler(io, test_io_send, NULL, NULL);
@@ -1160,13 +1218,13 @@ int tester_run(void)
return EXIT_SUCCESS;
}
- g_idle_add(start_tester, NULL);
+ timeout_add(0, start_tester, NULL, NULL);
mainloop_run_with_signal(signal_callback, NULL);
ret = tester_summarize();
- g_list_free_full(test_list, test_destroy);
+ queue_destroy(test_list, test_destroy);
if (option_monitor)
bt_log_close();
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 20/27] emulator/hciemu: Remove GLib dependency
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (18 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 19/27] shared/tester: Remove GLib dependency Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 21/27] tools: Build testers that do not need GLib with the mainloop Luiz Augusto von Dentz
` (7 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Use struct io for the sockets between the emulated controllers and
hosts, and timeout_add() with 0 for the deferred host start and the
flushing of client events, so hciemu works with any mainloop backend.
Assisted-by: Claude:claude-opus-5-5
---
emulator/hciemu.c | 122 +++++++++++++++++-----------------------------
1 file changed, 45 insertions(+), 77 deletions(-)
diff --git a/emulator/hciemu.c b/emulator/hciemu.c
index 48ed63b413f4..2f544f319bf2 100644
--- a/emulator/hciemu.c
+++ b/emulator/hciemu.c
@@ -24,8 +24,6 @@
#include <sys/socket.h>
#include <sys/ioctl.h>
-#include <glib.h>
-
#include "bluetooth/bluetooth.h"
#include "bluetooth/hci.h"
@@ -35,14 +33,16 @@
#include "emulator/bthost.h"
#include "src/shared/util.h"
#include "src/shared/queue.h"
+#include "src/shared/io.h"
+#include "src/shared/timeout.h"
#include "emulator/hciemu.h"
struct hciemu_client {
struct bthost *host;
struct btdev *dev;
- guint start_source;
- guint host_source;
- guint source;
+ unsigned int start_source;
+ struct io *host_io;
+ struct io *io;
int sock[2];
};
@@ -124,11 +124,11 @@ static void client_command_callback(uint16_t opcode,
static void writev_callback(const struct iovec *iov, int iovlen,
void *user_data)
{
- GIOChannel *channel = user_data;
+ struct io *io = user_data;
ssize_t written;
int fd;
- fd = g_io_channel_unix_get_fd(channel);
+ fd = io_get_fd(io);
written = writev(fd, iov, iovlen);
if (written < 0) {
@@ -162,99 +162,69 @@ static void writev_callback(const struct iovec *iov, int iovlen,
}
}
-static gboolean receive_bthost(GIOChannel *channel, GIOCondition condition,
- gpointer user_data)
+static bool receive_bthost(struct io *io, void *user_data)
{
struct bthost *bthost = user_data;
unsigned char buf[4096];
ssize_t len;
- int fd;
- if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP))
- return FALSE;
-
- fd = g_io_channel_unix_get_fd(channel);
-
- len = read(fd, buf, sizeof(buf));
+ len = read(io_get_fd(io), buf, sizeof(buf));
if (len < 0)
- return FALSE;
+ return false;
bthost_receive_h4(bthost, buf, len);
- return TRUE;
+ return true;
}
-static guint create_source_bthost(int fd, struct bthost *bthost)
+static struct io *create_io_bthost(int fd, struct bthost *bthost)
{
- GIOChannel *channel;
- guint source;
+ struct io *io;
- channel = g_io_channel_unix_new(fd);
+ io = io_new(fd);
+ io_set_close_on_destroy(io, true);
- g_io_channel_set_close_on_unref(channel, TRUE);
- g_io_channel_set_encoding(channel, NULL, NULL);
- g_io_channel_set_buffered(channel, FALSE);
+ bthost_set_send_handler(bthost, writev_callback, io);
- bthost_set_send_handler(bthost, writev_callback, channel);
+ io_set_read_handler(io, receive_bthost, bthost, NULL);
- source = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT,
- G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
- receive_bthost, bthost, NULL);
-
- g_io_channel_unref(channel);
-
- return source;
+ return io;
}
-static gboolean receive_btdev(GIOChannel *channel, GIOCondition condition,
- gpointer user_data)
+static bool receive_btdev(struct io *io, void *user_data)
{
struct btdev *btdev = user_data;
unsigned char buf[4096];
ssize_t len;
- int fd;
- if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP))
- return FALSE;
-
- fd = g_io_channel_unix_get_fd(channel);
-
- len = read(fd, buf, sizeof(buf));
+ len = read(io_get_fd(io), buf, sizeof(buf));
if (len < 0) {
if (errno == EAGAIN || errno == EINTR)
- return TRUE;
+ return true;
- return FALSE;
+ return false;
}
if (len < 1)
- return FALSE;
+ return false;
btdev_receive_h4(btdev, buf, len);
- return TRUE;
+ return true;
}
-static guint create_source_btdev(int fd, struct btdev *btdev)
+static struct io *create_io_btdev(int fd, struct btdev *btdev)
{
- GIOChannel *channel;
- guint source;
+ struct io *io;
- channel = g_io_channel_unix_new(fd);
+ io = io_new(fd);
+ io_set_close_on_destroy(io, true);
- g_io_channel_set_close_on_unref(channel, TRUE);
- g_io_channel_set_encoding(channel, NULL, NULL);
- g_io_channel_set_buffered(channel, FALSE);
+ btdev_set_send_handler(btdev, writev_callback, io);
- btdev_set_send_handler(btdev, writev_callback, channel);
+ io_set_read_handler(io, receive_btdev, btdev, NULL);
- source = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT,
- G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
- receive_btdev, btdev, NULL);
-
- g_io_channel_unref(channel);
-
- return source;
+ return io;
}
static bool create_vhci(struct hciemu *hciemu)
@@ -318,7 +288,7 @@ struct bthost *hciemu_client_get_host(struct hciemu *hciemu)
return hciemu_client_host(client);
}
-static gboolean start_host(gpointer user_data)
+static bool start_host(void *user_data)
{
struct hciemu_client *client = user_data;
@@ -326,18 +296,17 @@ static gboolean start_host(gpointer user_data)
bthost_start(client->host);
- return FALSE;
+ return false;
}
static void hciemu_client_destroy(void *data)
{
struct hciemu_client *client = data;
- if (client->start_source)
- g_source_remove(client->start_source);
+ timeout_remove(client->start_source);
- g_source_remove(client->host_source);
- g_source_remove(client->source);
+ io_destroy(client->host_io);
+ io_destroy(client->io);
bthost_destroy(client->host);
btdev_destroy(client->dev);
@@ -385,9 +354,9 @@ static struct hciemu_client *hciemu_client_new(struct hciemu *hciemu,
client->sock[0] = sv[0];
client->sock[1] = sv[1];
- client->source = create_source_btdev(sv[0], client->dev);
- client->host_source = create_source_bthost(sv[1], client->host);
- client->start_source = g_idle_add(start_host, client);
+ client->io = create_io_btdev(sv[0], client->dev);
+ client->host_io = create_io_bthost(sv[1], client->host);
+ client->start_source = timeout_add(0, start_host, client, NULL);
btdev_get_mtu(client->dev, &acl_mtu, NULL, &iso_mtu);
bthost_set_acl_mtu(client->host, acl_mtu);
@@ -512,8 +481,7 @@ void hciemu_unref(struct hciemu *hciemu)
queue_destroy(hciemu->post_command_hooks, destroy_command_hook);
queue_destroy(hciemu->clients, hciemu_client_destroy);
- if (hciemu->flush_id)
- g_source_remove(hciemu->flush_id);
+ timeout_remove(hciemu->flush_id);
vhci_close(hciemu->vhci);
@@ -853,7 +821,7 @@ static bool client_is_pending(const void *data, const void *match_data)
struct hciemu_client *client = (struct hciemu_client *)data;
int used, i;
- if (!client->source || !client->host_source)
+ if (!client->io || !client->host_io)
return false;
for (i = 0; i < 2; ++i) {
@@ -866,12 +834,12 @@ static bool client_is_pending(const void *data, const void *match_data)
return false;
}
-static gboolean flush_client_events(gpointer user_data)
+static bool flush_client_events(void *user_data)
{
struct hciemu *hciemu = user_data;
if (queue_find(hciemu->clients, client_is_pending, NULL))
- return TRUE;
+ return true;
hciemu->flush_id = 0;
@@ -879,7 +847,7 @@ static gboolean flush_client_events(gpointer user_data)
if (hciemu->vhci)
vhci_pause_input(hciemu->vhci, false);
- return FALSE;
+ return false;
}
void hciemu_flush_client_events(struct hciemu *hciemu)
@@ -889,5 +857,5 @@ void hciemu_flush_client_events(struct hciemu *hciemu)
util_debug(hciemu->debug_callback, hciemu->debug_data, "vhci: pause");
vhci_pause_input(hciemu->vhci, true);
- hciemu->flush_id = g_idle_add(flush_client_events, hciemu);
+ hciemu->flush_id = timeout_add(0, flush_client_events, hciemu, NULL);
}
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 21/27] tools: Build testers that do not need GLib with the mainloop
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (19 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 20/27] emulator/hciemu: " Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 22/27] shared/mainloop: Add deferred callbacks Luiz Augusto von Dentz
` (6 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Remove the remaining GLib uses of mgmt-tester, smp-tester, bnep-tester,
ioctl-tester, userchan-tester, mesh-tester and hci-tester, unused watch
ids and g_assert() mostly, and link them with libshared-mainloop, which
does not read the clock on every iteration.
mgmt-tester under User Mode Linux with test-runner -T:
GLib 42.6 s
mainloop 20.3 s
Assisted-by: Claude:claude-opus-5-5
---
Makefile.tools | 14 +++++++-------
tools/bnep-tester.c | 7 -------
tools/ioctl-tester.c | 6 ++----
tools/mesh-tester.c | 1 -
tools/mgmt-tester.c | 3 +--
tools/smp-tester.c | 30 +++++++++++++-----------------
tools/tester.h | 8 ++++----
tools/userchan-tester.c | 1 -
8 files changed, 27 insertions(+), 43 deletions(-)
diff --git a/Makefile.tools b/Makefile.tools
index 304dbf135e98..9f0dd897c607 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -136,7 +136,7 @@ tools_mgmt_tester_SOURCES = tools/mgmt-tester.c monitor/bt.h \
emulator/bthost.h emulator/bthost.c \
emulator/smp.c
tools_mgmt_tester_LDADD = lib/libbluetooth-internal.la \
- src/libshared-glib.la $(GLIB_LIBS)
+ src/libshared-mainloop.la
tools_mesh_tester_SOURCES = tools/mesh-tester.c monitor/bt.h \
emulator/hciemu.h emulator/hciemu.c \
@@ -145,7 +145,7 @@ tools_mesh_tester_SOURCES = tools/mesh-tester.c monitor/bt.h \
emulator/bthost.h emulator/bthost.c \
emulator/smp.c
tools_mesh_tester_LDADD = lib/libbluetooth-internal.la \
- src/libshared-glib.la $(GLIB_LIBS)
+ src/libshared-mainloop.la
tools_l2cap_tester_SOURCES = tools/l2cap-tester.c tools/tester.h monitor/bt.h \
emulator/hciemu.h emulator/hciemu.c \
@@ -172,7 +172,7 @@ tools_bnep_tester_SOURCES = tools/bnep-tester.c monitor/bt.h \
emulator/bthost.h emulator/bthost.c \
emulator/smp.c
tools_bnep_tester_LDADD = lib/libbluetooth-internal.la \
- src/libshared-glib.la $(GLIB_LIBS)
+ src/libshared-mainloop.la
tools_smp_tester_SOURCES = tools/smp-tester.c monitor/bt.h \
emulator/hciemu.h emulator/hciemu.c \
@@ -181,7 +181,7 @@ tools_smp_tester_SOURCES = tools/smp-tester.c monitor/bt.h \
emulator/bthost.h emulator/bthost.c \
emulator/smp.c
tools_smp_tester_LDADD = lib/libbluetooth-internal.la \
- src/libshared-glib.la $(GLIB_LIBS)
+ src/libshared-mainloop.la
tools_gap_tester_SOURCES = tools/gap-tester.c monitor/bt.h \
emulator/hciemu.h emulator/hciemu.c \
@@ -204,7 +204,7 @@ tools_sco_tester_LDADD = lib/libbluetooth-internal.la \
src/libshared-glib.la $(GLIB_LIBS)
tools_hci_tester_SOURCES = tools/hci-tester.c monitor/bt.h
-tools_hci_tester_LDADD = src/libshared-glib.la $(GLIB_LIBS)
+tools_hci_tester_LDADD = src/libshared-mainloop.la
tools_userchan_tester_SOURCES = tools/userchan-tester.c monitor/bt.h \
emulator/hciemu.h emulator/hciemu.c \
@@ -213,7 +213,7 @@ tools_userchan_tester_SOURCES = tools/userchan-tester.c monitor/bt.h \
emulator/bthost.h emulator/bthost.c \
emulator/smp.c
tools_userchan_tester_LDADD = lib/libbluetooth-internal.la \
- src/libshared-glib.la $(GLIB_LIBS)
+ src/libshared-mainloop.la
tools_iso_tester_SOURCES = tools/iso-tester.c tools/tester.h monitor/bt.h \
emulator/hciemu.h emulator/hciemu.c \
@@ -231,7 +231,7 @@ tools_ioctl_tester_SOURCES = tools/ioctl-tester.c monitor/bt.h \
emulator/bthost.h emulator/bthost.c \
emulator/smp.c
tools_ioctl_tester_LDADD = lib/libbluetooth-internal.la \
- src/libshared-glib.la $(GLIB_LIBS)
+ src/libshared-mainloop.la
tools_6lowpan_tester_SOURCES = tools/6lowpan-tester.c monitor/bt.h \
emulator/hciemu.h emulator/hciemu.c \
diff --git a/tools/bnep-tester.c b/tools/bnep-tester.c
index 1c53d024c0f8..8c7382b815c4 100644
--- a/tools/bnep-tester.c
+++ b/tools/bnep-tester.c
@@ -18,7 +18,6 @@
#include <errno.h>
#include <stdbool.h>
-#include <glib.h>
#include "bluetooth/bluetooth.h"
#include "bluetooth/bnep.h"
@@ -37,7 +36,6 @@ struct test_data {
struct hciemu *hciemu;
enum hciemu_type hciemu_type;
const void *test_data;
- unsigned int io_id;
uint16_t conn_handle;
};
@@ -198,10 +196,6 @@ static void test_post_teardown(const void *test_data)
{
struct test_data *data = tester_get_data();
- if (data->io_id > 0) {
- g_source_remove(data->io_id);
- data->io_id = 0;
- }
hciemu_unref(data->hciemu);
data->hciemu = NULL;
@@ -288,7 +282,6 @@ static void test_basic(const void *test_data)
break; \
user->hciemu_type = HCIEMU_TYPE_BREDR; \
user->test_data = data; \
- user->io_id = 0; \
tester_add_full(name, data, \
test_pre_setup, setup, func, NULL, \
test_post_teardown, 2, user, test_data_free); \
diff --git a/tools/ioctl-tester.c b/tools/ioctl-tester.c
index e957b0d01bfa..d9623d8f796c 100644
--- a/tools/ioctl-tester.c
+++ b/tools/ioctl-tester.c
@@ -20,7 +20,7 @@
#include <sys/ioctl.h>
#include <sys/socket.h>
-#include <glib.h>
+#include <assert.h>
#include "bluetooth/bluetooth.h"
#include "bluetooth/mgmt.h"
@@ -50,8 +50,6 @@ struct test_data {
uint16_t handle;
uint16_t acl_handle;
- GIOChannel *io;
- unsigned int io_id[2];
int step;
bool reconnect;
@@ -782,7 +780,7 @@ static void test_ioctl_common(const void *test_data)
}
} else {
if (ioctl_data->param_len) {
- g_assert(req_len >= ioctl_data->param_len);
+ assert(req_len >= ioctl_data->param_len);
req_len = ioctl_data->param_len;
}
memcpy(req, ioctl_data->param, req_len);
diff --git a/tools/mesh-tester.c b/tools/mesh-tester.c
index 4412bfdb727e..896cfd1f208f 100644
--- a/tools/mesh-tester.c
+++ b/tools/mesh-tester.c
@@ -21,7 +21,6 @@
#include <errno.h>
#include <unistd.h>
-#include <glib.h>
#include "bluetooth/bluetooth.h"
#include "bluetooth/hci.h"
diff --git a/tools/mgmt-tester.c b/tools/mgmt-tester.c
index abfe57dad0c8..58dac1317623 100644
--- a/tools/mgmt-tester.c
+++ b/tools/mgmt-tester.c
@@ -22,7 +22,6 @@
#include <errno.h>
#include <unistd.h>
-#include <glib.h>
#include "bluetooth/bluetooth.h"
#include "bluetooth/hci.h"
@@ -12599,7 +12598,7 @@ static void test_remove_device(const void *test_data)
static bool hook_delay_cmd(const void *data, uint16_t len, void *user_data)
{
tester_print("Delaying emulator response...");
- g_usleep(250000);
+ usleep(250000);
tester_print("Delaying emulator response... Done.");
return true;
}
diff --git a/tools/smp-tester.c b/tools/smp-tester.c
index 581f3a233704..29027d98a852 100644
--- a/tools/smp-tester.c
+++ b/tools/smp-tester.c
@@ -18,7 +18,7 @@
#include <stdbool.h>
#include <sys/socket.h>
-#include <glib.h>
+#include <assert.h>
#include "bluetooth/bluetooth.h"
#include "bluetooth/hci.h"
@@ -31,6 +31,7 @@
#include "src/shared/crypto.h"
#include "src/shared/ecc.h"
#include "src/shared/tester.h"
+#include "src/shared/util.h"
#include "src/shared/mgmt.h"
#define SMP_CID 0x0006
@@ -41,7 +42,6 @@ struct test_data {
uint16_t mgmt_index;
struct hciemu *hciemu;
enum hciemu_type hciemu_type;
- unsigned int io_id;
uint8_t ia[6];
uint8_t ia_type;
uint8_t ra[6];
@@ -229,10 +229,6 @@ static void test_post_teardown(const void *test_data)
{
struct test_data *data = tester_get_data();
- if (data->io_id > 0) {
- g_source_remove(data->io_id);
- data->io_id = 0;
- }
if (data->crypto) {
bt_crypto_unref(data->crypto);
@@ -293,7 +289,7 @@ static const struct smp_req_rsp nval_req_1[] = {
static const struct smp_data smp_server_nval_req_1_test = {
.req = nval_req_1,
- .req_count = G_N_ELEMENTS(nval_req_1),
+ .req_count = ARRAY_SIZE(nval_req_1),
};
static const uint8_t smp_nval_req_2[7] = { 0x01 };
@@ -306,7 +302,7 @@ static const struct smp_req_rsp srv_nval_req_1[] = {
static const struct smp_data smp_server_nval_req_2_test = {
.req = srv_nval_req_1,
- .req_count = G_N_ELEMENTS(srv_nval_req_1),
+ .req_count = ARRAY_SIZE(srv_nval_req_1),
};
static const uint8_t smp_nval_req_3[] = { 0x01, 0xff };
@@ -319,7 +315,7 @@ static const struct smp_req_rsp srv_nval_req_2[] = {
static const struct smp_data smp_server_nval_req_3_test = {
.req = srv_nval_req_2,
- .req_count = G_N_ELEMENTS(srv_nval_req_2),
+ .req_count = ARRAY_SIZE(srv_nval_req_2),
};
static const uint8_t smp_basic_req_1[] = { 0x01, /* Pairing Request */
@@ -353,7 +349,7 @@ static const struct smp_req_rsp srv_basic_req_1[] = {
static const struct smp_data smp_server_basic_req_1_test = {
.req = srv_basic_req_1,
- .req_count = G_N_ELEMENTS(srv_basic_req_1),
+ .req_count = ARRAY_SIZE(srv_basic_req_1),
};
static const struct smp_req_rsp cli_basic_req_1[] = {
@@ -367,7 +363,7 @@ static const struct smp_req_rsp cli_basic_req_1[] = {
static const struct smp_data smp_client_basic_req_1_test = {
.req = cli_basic_req_1,
- .req_count = G_N_ELEMENTS(cli_basic_req_1),
+ .req_count = ARRAY_SIZE(cli_basic_req_1),
};
static const uint8_t smp_basic_req_2[] = { 0x01, /* Pairing Request */
@@ -390,7 +386,7 @@ static const struct smp_req_rsp cli_basic_req_2[] = {
static const struct smp_data smp_client_basic_req_2_test = {
.req = cli_basic_req_2,
- .req_count = G_N_ELEMENTS(cli_basic_req_1),
+ .req_count = ARRAY_SIZE(cli_basic_req_1),
.mitm = true,
};
@@ -429,7 +425,7 @@ static const struct smp_req_rsp cli_sc_req_1[] = {
static const struct smp_data smp_client_sc_req_1_test = {
.req = cli_sc_req_1,
- .req_count = G_N_ELEMENTS(cli_sc_req_1),
+ .req_count = ARRAY_SIZE(cli_sc_req_1),
.sc = true,
};
@@ -455,7 +451,7 @@ static const struct smp_req_rsp cli_sc_req_2[] = {
static const struct smp_data smp_client_sc_req_2_test = {
.req = cli_sc_req_2,
- .req_count = G_N_ELEMENTS(cli_sc_req_2),
+ .req_count = ARRAY_SIZE(cli_sc_req_2),
.sc = true,
};
@@ -549,16 +545,16 @@ static const void *get_pdu(const uint8_t *pdu, size_t len)
uint8_t opcode = pdu[0];
static uint8_t buf[65];
- g_assert(len > 0);
+ assert(len > 0);
switch (opcode) {
case 0x01: /* Pairing Request */
- g_assert(len <= sizeof(data->preq));
+ assert(len <= sizeof(data->preq));
memset(data->preq, 0, sizeof(data->preq));
memcpy(data->preq, pdu, len);
break;
case 0x02: /* Pairing Response */
- g_assert(len <= sizeof(data->prsp));
+ assert(len <= sizeof(data->prsp));
memset(data->prsp, 0, sizeof(data->prsp));
memcpy(data->prsp, pdu, len);
break;
diff --git a/tools/tester.h b/tools/tester.h
index 9df600f900ad..2728a54e5c03 100644
--- a/tools/tester.h
+++ b/tools/tester.h
@@ -20,7 +20,7 @@
#include <net/if.h>
#include <sys/ioctl.h>
-#include <glib.h>
+#include <assert.h>
#define SEC_NSEC(_t) ((_t) * 1000000000LL)
#define TS_NSEC(_ts) (SEC_NSEC((_ts)->tv_sec) + (_ts)->tv_nsec)
@@ -65,21 +65,21 @@ static inline int tx_tstamp_expect(struct tx_tstamp_data *data, size_t len)
data->sent += len - 1;
if (data->so_timestamping & SOF_TIMESTAMPING_TX_SCHED) {
- g_assert(pos < ARRAY_SIZE(data->expect));
+ assert(pos < ARRAY_SIZE(data->expect));
data->expect[pos].type = SCM_TSTAMP_SCHED;
data->expect[pos].id = data->sent;
pos++;
}
if (data->so_timestamping & SOF_TIMESTAMPING_TX_SOFTWARE) {
- g_assert(pos < ARRAY_SIZE(data->expect));
+ assert(pos < ARRAY_SIZE(data->expect));
data->expect[pos].type = SCM_TSTAMP_SND;
data->expect[pos].id = data->sent;
pos++;
}
if (data->so_timestamping & SOF_TIMESTAMPING_TX_COMPLETION) {
- g_assert(pos < ARRAY_SIZE(data->expect));
+ assert(pos < ARRAY_SIZE(data->expect));
data->expect[pos].type = SCM_TSTAMP_COMPLETION;
data->expect[pos].id = data->sent;
pos++;
diff --git a/tools/userchan-tester.c b/tools/userchan-tester.c
index 9164fc33f5d9..febc28371501 100644
--- a/tools/userchan-tester.c
+++ b/tools/userchan-tester.c
@@ -18,7 +18,6 @@
#include <errno.h>
#include <stdbool.h>
-#include <glib.h>
#include "bluetooth/bluetooth.h"
#include "bluetooth/hci.h"
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 22/27] shared/mainloop: Add deferred callbacks
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (20 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 21/27] tools: Build testers that do not need GLib with the mainloop Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 23/27] tools: Add socket watches for testers without GLib Luiz Augusto von Dentz
` (5 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Add mainloop_add_deferred(), whose callback runs once the descriptors
ready at the time were handled, before the idle callbacks, like a GLib
source of default priority dispatched after those added before it.
Assisted-by: Claude:claude-opus-5-5
---
src/shared/mainloop-ell.c | 6 ++++++
src/shared/mainloop-glib.c | 6 ++++++
src/shared/mainloop.c | 41 ++++++++++++++++++++++++++++----------
src/shared/mainloop.h | 2 ++
4 files changed, 45 insertions(+), 10 deletions(-)
diff --git a/src/shared/mainloop-ell.c b/src/shared/mainloop-ell.c
index 1dda9c9ef761..873950743f52 100644
--- a/src/shared/mainloop-ell.c
+++ b/src/shared/mainloop-ell.c
@@ -106,6 +106,12 @@ int mainloop_add_idle(mainloop_timeout_func callback, void *user_data,
return -EOPNOTSUPP;
}
+int mainloop_add_deferred(mainloop_timeout_func callback, void *user_data,
+ mainloop_destroy_func destroy)
+{
+ return -EOPNOTSUPP;
+}
+
int mainloop_modify_timeout(int fd, unsigned int msec)
{
return -ENOSYS;
diff --git a/src/shared/mainloop-glib.c b/src/shared/mainloop-glib.c
index 971562610e02..705dbd3bec63 100644
--- a/src/shared/mainloop-glib.c
+++ b/src/shared/mainloop-glib.c
@@ -100,6 +100,12 @@ int mainloop_add_idle(mainloop_timeout_func callback, void *user_data,
return -EOPNOTSUPP;
}
+int mainloop_add_deferred(mainloop_timeout_func callback, void *user_data,
+ mainloop_destroy_func destroy)
+{
+ return -EOPNOTSUPP;
+}
+
int mainloop_modify_timeout(int fd, unsigned int msec)
{
return -ENOSYS;
diff --git a/src/shared/mainloop.c b/src/shared/mainloop.c
index 652436be944d..653f22db70e6 100644
--- a/src/shared/mainloop.c
+++ b/src/shared/mainloop.c
@@ -90,7 +90,7 @@ void mainloop_exit_failure(void)
}
static bool idle_pending(void);
-static void run_idles(void);
+static void run_idles(bool deferred);
static void remove_all_idles(void);
int mainloop_run(void)
@@ -107,7 +107,8 @@ int mainloop_run(void)
continue;
if (!nfds) {
- run_idles();
+ run_idles(true);
+ run_idles(false);
continue;
}
@@ -123,6 +124,8 @@ int mainloop_run(void)
data->callback(data->fd, events[n].events,
data->user_data);
}
+
+ run_idles(true);
}
for (i = 0; i < MAX_MAINLOOP_ENTRIES; i++) {
@@ -330,6 +333,7 @@ int mainloop_add_timeout(unsigned int msec, mainloop_timeout_func callback,
struct idle_data {
int id;
+ bool deferred;
bool armed;
bool removed;
mainloop_timeout_func callback;
@@ -396,14 +400,15 @@ static void remove_all_idles(void)
free_removed_idles();
}
-/* Run the idle callbacks armed before this pass, once each */
-static void run_idles(void)
+/* Run the idle or deferred callbacks armed before this pass, once each */
+static void run_idles(bool deferred)
{
struct idle_data *idle;
int last = idle_next_id;
for (idle = idle_list; idle; idle = idle->next) {
- if (idle->removed || !idle->armed || idle->id >= last)
+ if (idle->removed || !idle->armed || idle->id >= last ||
+ idle->deferred != deferred)
continue;
idle->armed = false;
@@ -413,11 +418,8 @@ static void run_idles(void)
free_removed_idles();
}
-/* Run callback once the mainloop is idle, again after
- * mainloop_modify_timeout() with 0; removed with mainloop_remove_timeout().
- */
-int mainloop_add_idle(mainloop_timeout_func callback, void *user_data,
- mainloop_destroy_func destroy)
+static int add_idle(bool deferred, mainloop_timeout_func callback,
+ void *user_data, mainloop_destroy_func destroy)
{
struct idle_data *idle, **last;
@@ -430,6 +432,7 @@ int mainloop_add_idle(mainloop_timeout_func callback, void *user_data,
memset(idle, 0, sizeof(*idle));
idle->id = idle_next_id++;
+ idle->deferred = deferred;
idle->armed = true;
idle->callback = callback;
idle->destroy = destroy;
@@ -442,6 +445,24 @@ int mainloop_add_idle(mainloop_timeout_func callback, void *user_data,
return idle->id;
}
+/* Run callback once the mainloop is idle, again after
+ * mainloop_modify_timeout() with 0; removed with mainloop_remove_timeout().
+ */
+int mainloop_add_idle(mainloop_timeout_func callback, void *user_data,
+ mainloop_destroy_func destroy)
+{
+ return add_idle(false, callback, user_data, destroy);
+}
+
+/* Run callback after the events ready now were handled, before the idle
+ * callbacks, like a source of default priority added last with GLib.
+ */
+int mainloop_add_deferred(mainloop_timeout_func callback, void *user_data,
+ mainloop_destroy_func destroy)
+{
+ return add_idle(true, callback, user_data, destroy);
+}
+
int mainloop_modify_timeout(int id, unsigned int msec)
{
if (id >= IDLE_ID_BASE) {
diff --git a/src/shared/mainloop.h b/src/shared/mainloop.h
index 2dd07ce03818..e7bc470c53d4 100644
--- a/src/shared/mainloop.h
+++ b/src/shared/mainloop.h
@@ -35,6 +35,8 @@ int mainloop_add_timeout(unsigned int msec, mainloop_timeout_func callback,
int mainloop_modify_timeout(int fd, unsigned int msec);
int mainloop_add_idle(mainloop_timeout_func callback, void *user_data,
mainloop_destroy_func destroy);
+int mainloop_add_deferred(mainloop_timeout_func callback, void *user_data,
+ mainloop_destroy_func destroy);
int mainloop_remove_timeout(int id);
int mainloop_set_signal(sigset_t *mask, mainloop_signal_func callback,
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 23/27] tools: Add socket watches for testers without GLib
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (21 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 22/27] shared/mainloop: Add deferred callbacks Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 24/27] tools: Run sco-tester, rfcomm-tester and 6lowpan-tester on the mainloop Luiz Augusto von Dentz
` (4 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Add tester_io, watches on file descriptors on top of the mainloop for
the testers using GIOChannel watches, keeping their semantics: several
watches per descriptor holding a reference to it, the descriptor closed
with the last reference if requested, tester_io_close() reported as
TESTER_IO_NVAL, and the watches called after the other ready descriptors
in the order they were added, which the testers rely on to see the
events of the emulated controllers first.
Unlike GLib, a hang up nobody watches for drops the watches instead of
waking up the mainloop for good, and an error nobody watches for, e.g. a
connection attempt timing out, is polled again after a millisecond,
which also lets time advance under User Mode Linux time travel.
Assisted-by: Claude:claude-opus-5-5
---
tools/tester-io.c | 400 ++++++++++++++++++++++++++++++++++++++++++++++
tools/tester-io.h | 52 ++++++
2 files changed, 452 insertions(+)
create mode 100644 tools/tester-io.c
create mode 100644 tools/tester-io.h
diff --git a/tools/tester-io.c b/tools/tester-io.c
new file mode 100644
index 000000000000..e9675225682b
--- /dev/null
+++ b/tools/tester-io.c
@@ -0,0 +1,400 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2026 Intel Corporation
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "src/shared/util.h"
+#include "src/shared/queue.h"
+#include "src/shared/mainloop.h"
+#include "src/shared/timeout.h"
+#include "tools/tester-io.h"
+
+/* Watch ids have the high bit set, so tester_remove_source() can tell them
+ * from timeout ids.
+ */
+#define WATCH_ID_BIT 0x80000000u
+
+struct tester_io {
+ int ref_count;
+ int fd;
+ bool close_on_unref;
+ bool registered;
+ bool dispatching;
+ bool scheduled;
+ unsigned int backoff_id;
+ uint32_t pending;
+ struct queue *watches;
+};
+
+struct watch {
+ unsigned int id;
+ struct tester_io *io;
+ uint32_t cond;
+ tester_io_func_t func;
+ void *user_data;
+ bool removed;
+};
+
+/* The tester_io with watches, in the order of their first watch */
+static struct queue *ios;
+static int dispatch_id;
+static unsigned int next_id;
+static unsigned int current_id;
+
+struct tester_io *tester_io_new(int fd)
+{
+ struct tester_io *io;
+
+ io = new0(struct tester_io, 1);
+ io->ref_count = 1;
+ io->fd = fd;
+ io->watches = queue_new();
+
+ return io;
+}
+
+struct tester_io *tester_io_ref(struct tester_io *io)
+{
+ if (io)
+ io->ref_count++;
+
+ return io;
+}
+
+void tester_io_unref(struct tester_io *io)
+{
+ if (!io || --io->ref_count > 0)
+ return;
+
+ queue_destroy(io->watches, free);
+
+ if (io->close_on_unref && io->fd >= 0)
+ close(io->fd);
+
+ free(io);
+}
+
+int tester_io_get_fd(struct tester_io *io)
+{
+ return io ? io->fd : -1;
+}
+
+void tester_io_set_close_on_unref(struct tester_io *io, bool close_on_unref)
+{
+ if (io)
+ io->close_on_unref = close_on_unref;
+}
+
+static void collect_cond(void *data, void *user_data)
+{
+ struct watch *watch = data;
+ uint32_t *events = user_data;
+
+ if (!watch->removed)
+ *events |= watch->cond;
+}
+
+static void io_event(int fd, uint32_t events, void *user_data);
+
+static bool match_registered_fd(const void *data, const void *match_data)
+{
+ const struct tester_io *io = data;
+
+ return io->registered && io->fd == PTR_TO_INT(match_data);
+}
+
+static void unregister(struct tester_io *io)
+{
+ if (!io->registered)
+ return;
+
+ io->registered = false;
+ mainloop_remove_fd(io->fd);
+}
+
+/* Poll for what the watches want, except while their callbacks are
+ * pending, and stop polling without any.
+ */
+static void update_events(struct tester_io *io)
+{
+ uint32_t events = 0;
+
+ queue_foreach(io->watches, collect_cond, &events);
+ events &= ~TESTER_IO_NVAL;
+
+ if (queue_isempty(io->watches) || io->scheduled || io->backoff_id ||
+ io->fd < 0) {
+ unregister(io);
+ return;
+ }
+
+ if (!io->registered) {
+ if (mainloop_add_fd(io->fd, events, io_event, io, NULL) < 0) {
+ struct tester_io *stale;
+
+ /* The descriptor of another io was closed directly,
+ * and the same number was given to this one.
+ */
+ stale = queue_find(ios, match_registered_fd,
+ INT_TO_PTR(io->fd));
+ if (!stale || stale == io)
+ return;
+
+ unregister(stale);
+
+ if (mainloop_add_fd(io->fd, events, io_event, io,
+ NULL) < 0)
+ return;
+ }
+
+ io->registered = true;
+ return;
+ }
+
+ mainloop_modify_fd(io->fd, events);
+}
+
+static bool match_removed(const void *data, const void *match_data)
+{
+ const struct watch *watch = data;
+
+ return watch->removed;
+}
+
+/* Drops the removed watches and their references to io. The caller holds
+ * a reference of its own, so io is freed by the caller's unref at most.
+ */
+static void cleanup(struct tester_io *io)
+{
+ struct watch *watch;
+
+ while ((watch = queue_remove_if(io->watches, match_removed, NULL))) {
+ free(watch);
+ io->ref_count--;
+ }
+
+ if (queue_isempty(io->watches)) {
+ io->scheduled = false;
+ io->pending = 0;
+ timeout_remove(io->backoff_id);
+ io->backoff_id = 0;
+ queue_remove(ios, io);
+ }
+
+ update_events(io);
+}
+
+static bool backoff_done(void *user_data)
+{
+ struct tester_io *io = user_data;
+
+ io->backoff_id = 0;
+ update_events(io);
+
+ return false;
+}
+
+static void remove_watch(void *data, void *user_data)
+{
+ struct watch *watch = data;
+
+ watch->removed = true;
+}
+
+static void dispatch(struct tester_io *io)
+{
+ const struct queue_entry *entry;
+ unsigned int last = next_id;
+ uint32_t events = io->pending;
+ uint32_t wanted = 0;
+
+ io->scheduled = false;
+ io->pending = 0;
+
+ tester_io_ref(io);
+ io->dispatching = true;
+
+ queue_foreach(io->watches, collect_cond, &wanted);
+
+ /* Watches added by a callback wait for the next event */
+ for (entry = queue_get_entries(io->watches); entry;
+ entry = entry->next) {
+ struct watch *watch = entry->data;
+
+ if (watch->removed || (watch->id & ~WATCH_ID_BIT) > last ||
+ !(events & watch->cond))
+ continue;
+
+ current_id = watch->id;
+
+ if (!watch->func(io, events & watch->cond, watch->user_data))
+ watch->removed = true;
+
+ current_id = 0;
+ }
+
+ /* A hang up nobody watches for would be reported over and over,
+ * waking up the mainloop for good: drop the watches instead, as with
+ * a closed descriptor. An error may be transient, e.g. a connection
+ * attempt timing out, so keep watching after one, as GLib does.
+ */
+ if (events & (TESTER_IO_HUP | TESTER_IO_NVAL) & ~wanted)
+ queue_foreach(io->watches, remove_watch, NULL);
+ else if (events & TESTER_IO_ERR & ~wanted && !io->backoff_id)
+ /* Polling would report the error right away again, so look
+ * again a bit later, which lets simulated time advance too.
+ */
+ io->backoff_id = timeout_add(1, backoff_done, io, NULL);
+
+ if (io->fd < 0)
+ queue_foreach(io->watches, remove_watch, NULL);
+
+ io->dispatching = false;
+ cleanup(io);
+ tester_io_unref(io);
+}
+
+static bool match_scheduled(const void *data, const void *match_data)
+{
+ const struct tester_io *io = data;
+
+ return io->scheduled;
+}
+
+static void dispatch_all(int id, void *user_data)
+{
+ struct tester_io *io;
+
+ mainloop_remove_timeout(id);
+ dispatch_id = 0;
+
+ while ((io = queue_find(ios, match_scheduled, NULL)))
+ dispatch(io);
+}
+
+/* Like GLib, which dispatches the ready sources in the order they were
+ * added, call the watches after the other descriptors ready at the same
+ * time, e.g. those of the emulated controllers, were handled, in the order
+ * of the first watch of each descriptor, and before idle callbacks: stop
+ * polling and dispatch once the ready descriptors were handled.
+ */
+static void schedule_dispatch(struct tester_io *io, uint32_t events)
+{
+ io->pending |= events;
+
+ if (io->scheduled)
+ return;
+
+ io->scheduled = true;
+ unregister(io);
+
+ if (!dispatch_id)
+ dispatch_id = mainloop_add_deferred(dispatch_all, NULL, NULL);
+}
+
+static void io_event(int fd, uint32_t events, void *user_data)
+{
+ struct tester_io *io = user_data;
+
+ schedule_dispatch(io, events);
+}
+
+unsigned int tester_io_add_watch(struct tester_io *io, uint32_t cond,
+ tester_io_func_t func, void *user_data)
+{
+ struct watch *watch;
+
+ if (!io || !func)
+ return 0;
+
+ if (!ios)
+ ios = queue_new();
+
+ watch = new0(struct watch, 1);
+ watch->id = ++next_id | WATCH_ID_BIT;
+ watch->io = tester_io_ref(io);
+ watch->cond = cond;
+ watch->func = func;
+ watch->user_data = user_data;
+
+ if (queue_isempty(io->watches))
+ queue_push_tail(ios, io);
+
+ queue_push_tail(io->watches, watch);
+ update_events(io);
+
+ return watch->id;
+}
+
+void tester_io_close(struct tester_io *io)
+{
+ if (!io || io->fd < 0)
+ return;
+
+ unregister(io);
+ close(io->fd);
+ io->fd = -1;
+
+ if (!queue_isempty(io->watches))
+ schedule_dispatch(io, TESTER_IO_NVAL);
+}
+
+static bool match_watch_id(const void *data, const void *match_data)
+{
+ const struct watch *watch = data;
+
+ return watch->id == PTR_TO_UINT(match_data) && !watch->removed;
+}
+
+static bool match_io_watch(const void *data, const void *match_data)
+{
+ const struct tester_io *io = data;
+
+ return queue_find(io->watches, match_watch_id, match_data);
+}
+
+unsigned int tester_current_source(void)
+{
+ return current_id;
+}
+
+void tester_remove_source(unsigned int id)
+{
+ struct tester_io *io;
+ struct watch *watch;
+
+ if (!id)
+ return;
+
+ if (!(id & WATCH_ID_BIT)) {
+ timeout_remove(id);
+ return;
+ }
+
+ io = queue_find(ios, match_io_watch, UINT_TO_PTR(id));
+ if (!io)
+ return;
+
+ watch = queue_find(io->watches, match_watch_id, UINT_TO_PTR(id));
+ watch->removed = true;
+
+ /* Cleaned up once the callbacks are done */
+ if (io->dispatching)
+ return;
+
+ tester_io_ref(io);
+ cleanup(io);
+ tester_io_unref(io);
+}
diff --git a/tools/tester-io.h b/tools/tester-io.h
new file mode 100644
index 000000000000..848251702cf0
--- /dev/null
+++ b/tools/tester-io.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2026 Intel Corporation
+ *
+ *
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <poll.h>
+#include <sys/epoll.h>
+
+/* Watches on file descriptors for the testers, in the way of GLib's
+ * GIOChannel watches, on top of the mainloop: several watches per file
+ * descriptor, each holding a reference to it, and the file descriptor
+ * closed with the last reference if requested. Like GLib, watches are
+ * called after the other ready descriptors were handled.
+ */
+
+#define TESTER_IO_IN EPOLLIN
+#define TESTER_IO_OUT EPOLLOUT
+#define TESTER_IO_ERR EPOLLERR
+#define TESTER_IO_HUP EPOLLHUP
+/* Reported once the descriptor was closed with tester_io_close() */
+#define TESTER_IO_NVAL POLLNVAL
+
+struct tester_io;
+
+typedef bool (*tester_io_func_t)(struct tester_io *io, uint32_t cond,
+ void *user_data);
+
+struct tester_io *tester_io_new(int fd);
+struct tester_io *tester_io_ref(struct tester_io *io);
+void tester_io_unref(struct tester_io *io);
+int tester_io_get_fd(struct tester_io *io);
+void tester_io_set_close_on_unref(struct tester_io *io, bool close_on_unref);
+
+/* Closes the descriptor, which the watches see as TESTER_IO_NVAL */
+void tester_io_close(struct tester_io *io);
+
+/* The watch is called while one of cond is set, until it returns false */
+unsigned int tester_io_add_watch(struct tester_io *io, uint32_t cond,
+ tester_io_func_t func, void *user_data);
+
+/* Removes a watch, or a timeout added with timeout_add() */
+void tester_remove_source(unsigned int id);
+
+/* The id of the watch being called, 0 outside of a watch */
+unsigned int tester_current_source(void);
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 24/27] tools: Run sco-tester, rfcomm-tester and 6lowpan-tester on the mainloop
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (22 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 23/27] tools: Add socket watches for testers without GLib Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 25/27] tools: Run l2cap-tester and iso-tester " Luiz Augusto von Dentz
` (3 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Replace their GIOChannel watches with tester_io and GLib timeouts with
timeout_add(), and link them with libshared-mainloop. rfcomm-tester
closes its socket with tester_io_close(), as the mainloop cannot tell
that a descriptor was closed.
Assisted-by: Claude:claude-opus-5-5
---
Makefile.tools | 15 +++---
tools/6lowpan-tester.c | 51 ++++++++++---------
tools/rfcomm-tester.c | 105 ++++++++++++++++++++-------------------
tools/sco-tester.c | 108 +++++++++++++++++++++--------------------
4 files changed, 148 insertions(+), 131 deletions(-)
diff --git a/Makefile.tools b/Makefile.tools
index 9f0dd897c607..5a04987ea3ea 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -156,14 +156,15 @@ tools_l2cap_tester_SOURCES = tools/l2cap-tester.c tools/tester.h monitor/bt.h \
tools_l2cap_tester_LDADD = lib/libbluetooth-internal.la \
src/libshared-glib.la $(GLIB_LIBS)
-tools_rfcomm_tester_SOURCES = tools/rfcomm-tester.c monitor/bt.h \
+tools_rfcomm_tester_SOURCES = tools/rfcomm-tester.c \
+ tools/tester-io.h tools/tester-io.c monitor/bt.h \
emulator/hciemu.h emulator/hciemu.c \
emulator/vhci.h emulator/vhci.c \
emulator/btdev.h emulator/btdev.c \
emulator/bthost.h emulator/bthost.c \
emulator/smp.c
tools_rfcomm_tester_LDADD = lib/libbluetooth-internal.la \
- src/libshared-glib.la $(GLIB_LIBS)
+ src/libshared-mainloop.la
tools_bnep_tester_SOURCES = tools/bnep-tester.c monitor/bt.h \
emulator/hciemu.h emulator/hciemu.c \
@@ -194,14 +195,15 @@ tools_gap_tester_LDADD = lib/libbluetooth-internal.la \
src/libshared-glib.la \
$(GLIB_LIBS) $(DBUS_LIBS)
-tools_sco_tester_SOURCES = tools/sco-tester.c tools/tester.h monitor/bt.h \
+tools_sco_tester_SOURCES = tools/sco-tester.c \
+ tools/tester-io.h tools/tester-io.c tools/tester.h monitor/bt.h \
emulator/hciemu.h emulator/hciemu.c \
emulator/vhci.h emulator/vhci.c \
emulator/btdev.h emulator/btdev.c \
emulator/bthost.h emulator/bthost.c \
emulator/smp.c
tools_sco_tester_LDADD = lib/libbluetooth-internal.la \
- src/libshared-glib.la $(GLIB_LIBS)
+ src/libshared-mainloop.la
tools_hci_tester_SOURCES = tools/hci-tester.c monitor/bt.h
tools_hci_tester_LDADD = src/libshared-mainloop.la
@@ -233,14 +235,15 @@ tools_ioctl_tester_SOURCES = tools/ioctl-tester.c monitor/bt.h \
tools_ioctl_tester_LDADD = lib/libbluetooth-internal.la \
src/libshared-mainloop.la
-tools_6lowpan_tester_SOURCES = tools/6lowpan-tester.c monitor/bt.h \
+tools_6lowpan_tester_SOURCES = tools/6lowpan-tester.c \
+ tools/tester-io.h tools/tester-io.c monitor/bt.h \
emulator/hciemu.h emulator/hciemu.c \
emulator/vhci.h emulator/vhci.c \
emulator/btdev.h emulator/btdev.c \
emulator/bthost.h emulator/bthost.c \
emulator/smp.c
tools_6lowpan_tester_LDADD = lib/libbluetooth-internal.la \
- src/libshared-glib.la $(GLIB_LIBS)
+ src/libshared-mainloop.la
endif
if TOOLS
diff --git a/tools/6lowpan-tester.c b/tools/6lowpan-tester.c
index e407684a9e4b..3bb537f1ceaf 100644
--- a/tools/6lowpan-tester.c
+++ b/tools/6lowpan-tester.c
@@ -22,9 +22,10 @@
#include <net/if.h>
#include <linux/if_packet.h>
#include <sys/ioctl.h>
-#include <dirent.h>
-#include <glib.h>
+#include <assert.h>
+#include <limits.h>
+#include <dirent.h>
#include "bluetooth/bluetooth.h"
#include "bluetooth/mgmt.h"
@@ -34,6 +35,8 @@
#include "emulator/hciemu.h"
#include "src/shared/tester.h"
+#include "src/shared/timeout.h"
+#include "tools/tester-io.h"
#include "src/shared/mgmt.h"
#include "src/shared/util.h"
@@ -272,7 +275,7 @@ static void test_post_teardown(const void *test_data)
int ret;
if (data->io_id > 0) {
- g_source_remove(data->io_id);
+ tester_remove_source(data->io_id);
data->io_id = 0;
}
@@ -531,7 +534,7 @@ error:
return -EIO;
}
-static gboolean client_do_disconnect(gpointer user_data)
+static bool client_do_disconnect(void *user_data)
{
struct test_data *data = user_data;
const uint8_t *client_bdaddr;
@@ -549,11 +552,11 @@ static gboolean client_do_disconnect(gpointer user_data)
if (write_6lowpan("6lowpan_control", "disconnect %s 0", addr))
tester_test_failed();
- return FALSE;
+ return false;
}
-static gboolean recv_iface_packet(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool recv_iface_packet(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = user_data;
const struct client_data *cdata = data->test_data;
@@ -561,10 +564,10 @@ static gboolean recv_iface_packet(GIOChannel *io, GIOCondition cond,
int fd;
ssize_t ret;
- if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
+ if (cond & (TESTER_IO_ERR | TESTER_IO_HUP | TESTER_IO_NVAL))
goto done;
- fd = g_io_channel_unix_get_fd(io);
+ fd = tester_io_get_fd(io);
ret = recv(fd, buf, sizeof(buf), 0);
if (ret < 0) {
tester_print("recv failed");
@@ -575,9 +578,9 @@ static gboolean recv_iface_packet(GIOChannel *io, GIOCondition cond,
tester_print("Recv %d bytes", (int)ret);
if (ret != cdata->send_expect_data_len)
- return TRUE;
+ return true;
if (memcmp(buf, cdata->send_expect_data, ret))
- return TRUE;
+ return true;
tester_print("Received sent packet");
@@ -588,7 +591,7 @@ static gboolean recv_iface_packet(GIOChannel *io, GIOCondition cond,
done:
data->io_id = 0;
- return FALSE;
+ return false;
}
/* Find the interface of the controller of this test, the most recent one,
@@ -646,7 +649,7 @@ static bool find_iface(struct test_data *data, char *ifname, size_t len)
return best >= 0;
}
-static gboolean client_open_iface(gpointer user_data)
+static bool client_open_iface(void *user_data)
{
struct test_data *data = user_data;
const struct client_data *cdata = data->test_data;
@@ -660,30 +663,30 @@ static gboolean client_open_iface(gpointer user_data)
data->packet_fd = open_iface(ifname, cdata->sk_type);
if (data->packet_fd < 0) {
tester_print("Wait for interface...");
- data->io_id = g_timeout_add(500, client_open_iface, data);
- return FALSE;
+ data->io_id = timeout_add(500, client_open_iface, data, NULL);
+ return false;
}
if (cdata->send_data) {
struct bthost *bthost;
- GIOChannel *io;
+ struct tester_io *io;
bthost = hciemu_client_get_host(data->hciemu);
- io = g_io_channel_unix_new(data->packet_fd);
- data->io_id = g_io_add_watch(io,
- G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- recv_iface_packet, data);
- g_io_channel_unref(io);
+ io = tester_io_new(data->packet_fd);
+ data->io_id = tester_io_add_watch(io,
+ TESTER_IO_IN | TESTER_IO_ERR | TESTER_IO_HUP |
+ TESTER_IO_NVAL, recv_iface_packet, data);
+ tester_io_unref(io);
tester_debug("Send %u bytes", cdata->send_data_len);
bthost_send_cid(bthost, data->handle, data->dcid,
cdata->send_data, cdata->send_data_len);
} else if (cdata->disconnect) {
- data->io_id = g_idle_add(client_do_disconnect, data);
+ data->io_id = timeout_add(0, client_do_disconnect, data, NULL);
}
- return FALSE;
+ return false;
}
static void client_l2cap_connect_cb(uint16_t handle, uint16_t cid,
@@ -713,7 +716,7 @@ static void client_l2cap_connect_cb(uint16_t handle, uint16_t cid,
/* Wait until kernel handles L2CAP connect RSP */
if (cdata->send_data || cdata->disconnect)
- data->io_id = g_idle_add(client_open_iface, data);
+ data->io_id = timeout_add(0, client_open_iface, data, NULL);
if (cdata->disable_on_connect) {
if (write_6lowpan("6lowpan_enable", "0"))
diff --git a/tools/rfcomm-tester.c b/tools/rfcomm-tester.c
index b7f7d9ebd794..998d698c1bb2 100644
--- a/tools/rfcomm-tester.c
+++ b/tools/rfcomm-tester.c
@@ -18,7 +18,7 @@
#include <errno.h>
#include <stdbool.h>
-#include <glib.h>
+#include <assert.h>
#include "bluetooth/bluetooth.h"
#include "bluetooth/rfcomm.h"
@@ -29,6 +29,8 @@
#include "emulator/hciemu.h"
#include "src/shared/tester.h"
+#include "src/shared/timeout.h"
+#include "tools/tester-io.h"
#include "src/shared/mgmt.h"
#include "src/shared/util.h"
@@ -38,7 +40,7 @@ struct test_data {
struct hciemu *hciemu;
enum hciemu_type hciemu_type;
const void *test_data;
- GIOChannel *io;
+ struct tester_io *io;
unsigned int io_id;
uint16_t conn_handle;
uint16_t send_len;
@@ -204,12 +206,12 @@ static void test_post_teardown(const void *test_data)
struct test_data *data = tester_get_data();
if (data->io_id > 0) {
- g_source_remove(data->io_id);
+ tester_remove_source(data->io_id);
data->io_id = 0;
}
if (data->io)
- g_io_channel_unref(data->io);
+ tester_io_unref(data->io);
hciemu_unref(data->hciemu);
data->hciemu = NULL;
@@ -432,8 +434,8 @@ static int connect_rfcomm_sock(int sk, const bdaddr_t *bdaddr, uint8_t channel)
return 0;
}
-static gboolean client_received_data(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool client_received_data(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
const struct rfcomm_client_data *cli = data->test_data;
@@ -441,7 +443,7 @@ static gboolean client_received_data(GIOChannel *io, GIOCondition cond,
ssize_t ret;
char buf[248];
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
ret = read(sk, buf, cli->data_len);
if (cli->data_len != ret) {
@@ -457,24 +459,24 @@ static gboolean client_received_data(GIOChannel *io, GIOCondition cond,
return false;
}
-static gboolean rc_write_data(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool rc_write_data(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = user_data;
const struct rfcomm_client_data *cli = data->test_data;
int sk;
ssize_t ret;
- if (cond & G_IO_NVAL)
+ if (cond & TESTER_IO_NVAL)
return false;
- if (cond & (G_IO_HUP | G_IO_ERR))
+ if (cond & (TESTER_IO_HUP | TESTER_IO_ERR))
goto done;
tester_print("Writing %u bytes of data",
cli->data_len - data->send_len);
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
ret = write(sk, cli->send_data + data->send_len,
cli->data_len - data->send_len);
@@ -495,8 +497,8 @@ done:
return false;
}
-static gboolean rc_connect_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool rc_connect_cb(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
const struct rfcomm_client_data *cli = data->test_data;
@@ -507,7 +509,7 @@ static gboolean rc_connect_cb(GIOChannel *io, GIOCondition cond,
data->io_id = 0;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &sk_err, &len) < 0)
err = -errno;
@@ -520,12 +522,15 @@ static gboolean rc_connect_cb(GIOChannel *io, GIOCondition cond,
}
if (cli->send_data) {
- data->io = g_io_channel_ref(io);
- cond = G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL;
- data->io_id = g_io_add_watch(io, cond, rc_write_data, data);
+ data->io = tester_io_ref(io);
+ cond = TESTER_IO_OUT | TESTER_IO_HUP | TESTER_IO_ERR |
+ TESTER_IO_NVAL;
+ data->io_id = tester_io_add_watch(io, cond, rc_write_data,
+ data);
return false;
} else if (cli->read_data) {
- g_io_add_watch(io, G_IO_IN, client_received_data, NULL);
+ tester_io_add_watch(io, TESTER_IO_IN, client_received_data,
+ NULL);
bthost_send_rfcomm_data(hciemu_client_get_host(data->hciemu),
data->conn_handle,
cli->client_channel,
@@ -543,8 +548,8 @@ static gboolean rc_connect_cb(GIOChannel *io, GIOCondition cond,
return false;
}
-static gboolean rc_close_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool rc_close_cb(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
@@ -585,10 +590,10 @@ static void client_hook_func(const void *data, uint16_t len,
if (cli->data_len != test_data->recv_len) {
if (cli->data_len != test_data->send_len)
- test_data->io_id = g_io_add_watch(test_data->io,
- G_IO_OUT | G_IO_HUP |
- G_IO_ERR | G_IO_NVAL,
- rc_write_data, test_data);
+ test_data->io_id = tester_io_add_watch(test_data->io,
+ TESTER_IO_OUT | TESTER_IO_HUP |
+ TESTER_IO_ERR | TESTER_IO_NVAL,
+ rc_write_data, test_data);
return;
}
@@ -637,7 +642,7 @@ static void test_connect(const void *test_data)
struct bthost *bthost = hciemu_client_get_host(data->hciemu);
const struct rfcomm_client_data *cli = data->test_data;
const uint8_t *client_addr, *central_addr;
- GIOChannel *io;
+ struct tester_io *io;
int sk;
bthost_add_l2cap_server(bthost, 0x0003, NULL, NULL, NULL);
@@ -656,25 +661,26 @@ static void test_connect(const void *test_data)
return;
}
- io = g_io_channel_unix_new(sk);
+ io = tester_io_new(sk);
tester_print("Connect in progress %d", sk);
if (cli->close) {
- data->io_id = g_io_add_watch(io, G_IO_NVAL, rc_close_cb, NULL);
- close(sk);
+ data->io_id = tester_io_add_watch(io, TESTER_IO_NVAL,
+ rc_close_cb, NULL);
+ tester_io_close(io);
tester_print("Close socket %d", sk);
} else {
- g_io_channel_set_close_on_unref(io, TRUE);
- data->io_id = g_io_add_watch(io, G_IO_OUT, rc_connect_cb,
- NULL);
+ tester_io_set_close_on_unref(io, true);
+ data->io_id = tester_io_add_watch(io, TESTER_IO_OUT,
+ rc_connect_cb, NULL);
}
- g_io_channel_unref(io);
+ tester_io_unref(io);
}
-static gboolean server_received_data(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool server_received_data(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
const struct rfcomm_server_data *srv = data->test_data;
@@ -682,7 +688,7 @@ static gboolean server_received_data(GIOChannel *io, GIOCondition cond,
ssize_t ret;
int sk;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
ret = read(sk, buf, srv->data_len);
if (ret != srv->data_len) {
@@ -698,8 +704,8 @@ static gboolean server_received_data(GIOChannel *io, GIOCondition cond,
return false;
}
-static gboolean rfcomm_listen_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool rfcomm_listen_cb(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
const struct rfcomm_server_data *srv = data->test_data;
@@ -707,7 +713,7 @@ static gboolean rfcomm_listen_cb(GIOChannel *io, GIOCondition cond,
data->io_id = 0;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
new_sk = accept(sk, NULL, NULL);
if (new_sk < 0) {
@@ -725,15 +731,15 @@ static gboolean rfcomm_listen_cb(GIOChannel *io, GIOCondition cond,
close(new_sk);
return false;
} else if (srv->read_data) {
- GIOChannel *new_io;
+ struct tester_io *new_io;
- new_io = g_io_channel_unix_new(new_sk);
- g_io_channel_set_close_on_unref(new_io, TRUE);
+ new_io = tester_io_new(new_sk);
+ tester_io_set_close_on_unref(new_io, true);
- data->io_id = g_io_add_watch(new_io, G_IO_IN,
+ data->io_id = tester_io_add_watch(new_io, TESTER_IO_IN,
server_received_data, NULL);
- g_io_channel_unref(new_io);
+ tester_io_unref(new_io);
return false;
}
@@ -786,7 +792,7 @@ static void test_server(const void *test_data)
const struct rfcomm_server_data *srv = data->test_data;
const uint8_t *central_addr;
struct bthost *bthost;
- GIOChannel *io;
+ struct tester_io *io;
int sk;
central_addr = hciemu_get_central_bdaddr(data->hciemu);
@@ -805,11 +811,12 @@ static void test_server(const void *test_data)
return;
}
- io = g_io_channel_unix_new(sk);
- g_io_channel_set_close_on_unref(io, TRUE);
+ io = tester_io_new(sk);
+ tester_io_set_close_on_unref(io, true);
- data->io_id = g_io_add_watch(io, G_IO_IN, rfcomm_listen_cb, NULL);
- g_io_channel_unref(io);
+ data->io_id = tester_io_add_watch(io, TESTER_IO_IN, rfcomm_listen_cb,
+ NULL);
+ tester_io_unref(io);
tester_print("Listening for connections");
diff --git a/tools/sco-tester.c b/tools/sco-tester.c
index fca99a91c20f..3604e10f0647 100644
--- a/tools/sco-tester.c
+++ b/tools/sco-tester.c
@@ -18,7 +18,7 @@
#include <poll.h>
#include <stdbool.h>
-#include <glib.h>
+#include <assert.h>
#include "bluetooth/bluetooth.h"
#include "bluetooth/sco.h"
@@ -29,6 +29,8 @@
#include "emulator/hciemu.h"
#include "src/shared/tester.h"
+#include "src/shared/timeout.h"
+#include "tools/tester-io.h"
#include "src/shared/mgmt.h"
#include "src/shared/util.h"
@@ -260,7 +262,7 @@ static void test_data_free(void *test_data)
struct test_data *data = test_data;
if (data->io_id > 0)
- g_source_remove(data->io_id);
+ tester_remove_source(data->io_id);
free(data);
}
@@ -850,29 +852,29 @@ static int connect_sco_sock(struct test_data *data, int sk)
return 0;
}
-static gboolean recv_errqueue(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool recv_errqueue(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = user_data;
const struct sco_client_data *scodata = data->test_data;
- int sk = g_io_channel_unix_get_fd(io);
+ int sk = tester_io_get_fd(io);
int err;
data->step--;
err = tx_tstamp_recv(&data->tx_ts, sk, scodata->data_len);
if (err > 0)
- return TRUE;
+ return true;
else if (err)
tester_test_failed();
else if (!data->step)
tester_test_passed();
data->err_io_id = 0;
- return FALSE;
+ return false;
}
-static void sco_tx_timestamping(struct test_data *data, GIOChannel *io)
+static void sco_tx_timestamping(struct test_data *data, struct tester_io *io)
{
const struct sco_client_data *scodata = data->test_data;
int so = scodata->so_timestamping;
@@ -883,7 +885,7 @@ static void sco_tx_timestamping(struct test_data *data, GIOChannel *io)
if (!(scodata->so_timestamping & TS_TX_RECORD_MASK))
return;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
tester_print("Enabling TX timestamping");
@@ -900,11 +902,12 @@ static void sco_tx_timestamping(struct test_data *data, GIOChannel *io)
return;
}
- data->err_io_id = g_io_add_watch(io, G_IO_ERR, recv_errqueue, data);
+ data->err_io_id = tester_io_add_watch(io, TESTER_IO_ERR, recv_errqueue,
+ data);
}
-static gboolean sock_received_data(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool sock_received_data(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
const struct sco_client_data *scodata = data->test_data;
@@ -913,20 +916,20 @@ static gboolean sock_received_data(GIOChannel *io, GIOCondition cond,
int sk;
ssize_t len;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
len = recv_tstamp(sk, buf, sizeof(buf), tstamp);
if (len < 0) {
tester_warn("Unable to read: %s (%d)", strerror(errno), errno);
tester_test_failed();
- return FALSE;
+ return false;
}
tester_debug("read: %d", (int)len);
if (len != scodata->data_len) {
tester_test_failed();
- return FALSE;
+ return false;
}
--data->step;
@@ -937,35 +940,35 @@ static gboolean sock_received_data(GIOChannel *io, GIOCondition cond,
else if (!data->step)
tester_test_passed();
else
- return TRUE;
+ return true;
- return FALSE;
+ return false;
}
-static void sco_recv_data(struct test_data *data, GIOChannel *io)
+static void sco_recv_data(struct test_data *data, struct tester_io *io)
{
const struct sco_client_data *scodata = data->test_data;
data->step = 0;
- if (rx_timestamping_init(g_io_channel_unix_get_fd(io),
+ if (rx_timestamping_init(tester_io_get_fd(io),
scodata->so_timestamping))
return;
- g_io_add_watch(io, G_IO_IN, sock_received_data, NULL);
+ tester_io_add_watch(io, TESTER_IO_IN, sock_received_data, NULL);
++data->step;
}
-static gboolean sco_connect(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool sco_connect(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
const struct sco_client_data *scodata = data->test_data;
int err, sk_err, sk;
socklen_t len = sizeof(sk_err);
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &sk_err, &len) < 0)
err = -errno;
@@ -1003,7 +1006,7 @@ static gboolean sco_connect(GIOChannel *io, GIOCondition cond,
}
/* Don't close the socket until all data is sent */
- g_io_channel_set_close_on_unref(io, FALSE);
+ tester_io_set_close_on_unref(io, false);
}
if (scodata->shutdown) {
@@ -1016,24 +1019,24 @@ static gboolean sco_connect(GIOChannel *io, GIOCondition cond,
else if (!data->step)
tester_test_passed();
- return FALSE;
+ return false;
}
-static gboolean sco_connect_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool sco_connect_cb(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
data->io_id = 0;
sco_connect(io, cond, user_data);
- return FALSE;
+ return false;
}
static void test_connect(const void *test_data)
{
struct test_data *data = tester_get_data();
const struct sco_client_data *scodata = data->test_data;
- GIOChannel *io;
+ struct tester_io *io;
int sk;
sk = create_sco_sock(data);
@@ -1056,12 +1059,13 @@ static void test_connect(const void *test_data)
data->sk = sk;
- io = g_io_channel_unix_new(sk);
- g_io_channel_set_close_on_unref(io, TRUE);
+ io = tester_io_new(sk);
+ tester_io_set_close_on_unref(io, true);
- data->io_id = g_io_add_watch(io, G_IO_OUT, sco_connect_cb, NULL);
+ data->io_id = tester_io_add_watch(io, TESTER_IO_OUT, sco_connect_cb,
+ NULL);
- g_io_channel_unref(io);
+ tester_io_unref(io);
tester_print("Connect in progress");
}
@@ -1155,7 +1159,7 @@ end:
static bool hook_delay_evt(const void *msg, uint16_t len, void *user_data)
{
tester_print("Delaying emulator response...");
- g_usleep(500000);
+ usleep(500000);
tester_print("Delaying emulator response... Done.");
return true;
}
@@ -1259,7 +1263,7 @@ static bool hook_simult_disc(const void *msg, uint16_t len, void *user_data)
static bool hook_delay_cmd(const void *data, uint16_t len, void *user_data)
{
tester_print("Delaying emulator response...");
- g_usleep(250000);
+ usleep(250000);
tester_print("Delaying emulator response... Done.");
return true;
}
@@ -1377,13 +1381,13 @@ fail:
return err;
}
-static bool sco_defer_accept(struct test_data *data, GIOChannel *io)
+static bool sco_defer_accept(struct test_data *data, struct tester_io *io)
{
int sk;
char c;
struct pollfd pfd;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
memset(&pfd, 0, sizeof(pfd));
pfd.fd = sk;
@@ -1406,20 +1410,20 @@ static bool sco_defer_accept(struct test_data *data, GIOChannel *io)
return true;
}
-static gboolean sco_accept_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool sco_accept_cb(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
const struct sco_client_data *scodata = data->test_data;
int sk, new_sk;
- gboolean ret;
- GIOChannel *new_io;
+ bool ret;
+ struct tester_io *new_io;
data->io_id = 0;
tester_debug("New connection");
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
new_sk = accept(sk, NULL, NULL);
if (new_sk < 0) {
@@ -1427,12 +1431,12 @@ static gboolean sco_accept_cb(GIOChannel *io, GIOCondition cond,
return false;
}
- new_io = g_io_channel_unix_new(new_sk);
- g_io_channel_set_close_on_unref(new_io, TRUE);
+ new_io = tester_io_new(new_sk);
+ tester_io_set_close_on_unref(new_io, true);
if (scodata->defer) {
if (scodata->expect_err < 0) {
- g_io_channel_unref(new_io);
+ tester_io_unref(new_io);
tester_test_passed();
return false;
}
@@ -1446,16 +1450,16 @@ static gboolean sco_accept_cb(GIOChannel *io, GIOCondition cond,
ret = sco_connect(new_io, cond, user_data);
- g_io_channel_unref(new_io);
+ tester_io_unref(new_io);
return ret;
}
-static void setup_listen(struct test_data *data, GIOFunc func)
+static void setup_listen(struct test_data *data, tester_io_func_t func)
{
struct hciemu_client *client;
struct bthost *host;
int sk;
- GIOChannel *io;
+ struct tester_io *io;
sk = listen_sco_sock(data);
if (sk < 0) {
@@ -1466,11 +1470,11 @@ static void setup_listen(struct test_data *data, GIOFunc func)
return;
}
- io = g_io_channel_unix_new(sk);
- g_io_channel_set_close_on_unref(io, TRUE);
+ io = tester_io_new(sk);
+ tester_io_set_close_on_unref(io, true);
- data->io_id = g_io_add_watch(io, G_IO_IN, func, NULL);
- g_io_channel_unref(io);
+ data->io_id = tester_io_add_watch(io, TESTER_IO_IN, func, NULL);
+ tester_io_unref(io);
tester_print("Listen in progress");
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 25/27] tools: Run l2cap-tester and iso-tester on the mainloop
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (23 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 24/27] tools: Run sco-tester, rfcomm-tester and 6lowpan-tester on the mainloop Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 26/27] doc/virtio: Update measurements of the testers Luiz Augusto von Dentz
` (2 subsequent siblings)
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Replace their GIOChannel watches with tester_io and GLib timeouts with
timeout_add(), and link them with libshared-mainloop, which unlike GLib
does not read the clock on every iteration.
Under User Mode Linux with test-runner -T:
l2cap-tester 10.2 s -> 5.6 s
iso-tester 16.3 s -> 7.6 s
Assisted-by: Claude:claude-opus-5-5
---
Makefile.tools | 10 +-
tools/iso-tester.c | 337 ++++++++++++++++++++++---------------------
tools/l2cap-tester.c | 333 ++++++++++++++++++++++--------------------
3 files changed, 355 insertions(+), 325 deletions(-)
diff --git a/Makefile.tools b/Makefile.tools
index 5a04987ea3ea..3a376b3d00c6 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -147,14 +147,15 @@ tools_mesh_tester_SOURCES = tools/mesh-tester.c monitor/bt.h \
tools_mesh_tester_LDADD = lib/libbluetooth-internal.la \
src/libshared-mainloop.la
-tools_l2cap_tester_SOURCES = tools/l2cap-tester.c tools/tester.h monitor/bt.h \
+tools_l2cap_tester_SOURCES = tools/l2cap-tester.c \
+ tools/tester-io.h tools/tester-io.c tools/tester.h monitor/bt.h \
emulator/hciemu.h emulator/hciemu.c \
emulator/vhci.h emulator/vhci.c \
emulator/btdev.h emulator/btdev.c \
emulator/bthost.h emulator/bthost.c \
emulator/smp.c
tools_l2cap_tester_LDADD = lib/libbluetooth-internal.la \
- src/libshared-glib.la $(GLIB_LIBS)
+ src/libshared-mainloop.la
tools_rfcomm_tester_SOURCES = tools/rfcomm-tester.c \
tools/tester-io.h tools/tester-io.c monitor/bt.h \
@@ -217,14 +218,15 @@ tools_userchan_tester_SOURCES = tools/userchan-tester.c monitor/bt.h \
tools_userchan_tester_LDADD = lib/libbluetooth-internal.la \
src/libshared-mainloop.la
-tools_iso_tester_SOURCES = tools/iso-tester.c tools/tester.h monitor/bt.h \
+tools_iso_tester_SOURCES = tools/iso-tester.c \
+ tools/tester-io.h tools/tester-io.c tools/tester.h monitor/bt.h \
emulator/hciemu.h emulator/hciemu.c \
emulator/vhci.h emulator/vhci.c \
emulator/btdev.h emulator/btdev.c \
emulator/bthost.h emulator/bthost.c \
emulator/smp.c
tools_iso_tester_LDADD = lib/libbluetooth-internal.la \
- src/libshared-glib.la $(GLIB_LIBS)
+ src/libshared-mainloop.la
tools_ioctl_tester_SOURCES = tools/ioctl-tester.c monitor/bt.h \
emulator/hciemu.h emulator/hciemu.c \
diff --git a/tools/iso-tester.c b/tools/iso-tester.c
index 7149232d17f4..333c894ea06e 100644
--- a/tools/iso-tester.c
+++ b/tools/iso-tester.c
@@ -22,7 +22,7 @@
#include <linux/errqueue.h>
#include <linux/net_tstamp.h>
-#include <glib.h>
+#include <assert.h>
#include "bluetooth/bluetooth.h"
#include "bluetooth/iso.h"
@@ -35,6 +35,8 @@
#include "emulator/hciemu.h"
#include "src/shared/tester.h"
+#include "src/shared/timeout.h"
+#include "tools/tester-io.h"
#include "src/shared/mgmt.h"
#include "src/shared/util.h"
#include "src/shared/queue.h"
@@ -471,7 +473,7 @@ struct test_data {
uint16_t handle;
uint16_t acl_handle;
struct queue *io_queue;
- GIOChannel *past_io;
+ struct tester_io *past_io;
unsigned int io_id[4];
uint8_t client_num;
int step;
@@ -525,8 +527,9 @@ struct iso_client_data {
unsigned int repeat_send;
};
-typedef bool (*iso_defer_accept_t)(struct test_data *data, GIOChannel *io,
- uint8_t num, GIOFunc func);
+typedef bool (*iso_defer_accept_t)(struct test_data *data, struct tester_io *io,
+ uint8_t num,
+ tester_io_func_t func);
static void mgmt_debug(const char *str, void *user_data)
{
@@ -700,9 +703,9 @@ static void test_pre_setup(const void *test_data)
static void io_free(void *data)
{
- GIOChannel *io = data;
+ struct tester_io *io = data;
- g_io_channel_unref(io);
+ tester_io_unref(io);
}
static void test_post_teardown(const void *test_data)
@@ -713,10 +716,8 @@ static void test_post_teardown(const void *test_data)
if (data->io_queue)
queue_remove_all(data->io_queue, NULL, NULL, io_free);
- if (data->past_io) {
- g_io_channel_unref(data->past_io);
- data->past_io = NULL;
- }
+ tester_io_unref(data->past_io);
+ data->past_io = NULL;
/* Remove the watches with the test, as the sockets they keep open
* hang up once the controller is removed, which would wake up the
@@ -724,7 +725,7 @@ static void test_post_teardown(const void *test_data)
*/
for (i = 0; i < ARRAY_SIZE(data->io_id); ++i) {
if (data->io_id[i] > 0) {
- g_source_remove(data->io_id[i]);
+ tester_remove_source(data->io_id[i]);
data->io_id[i] = 0;
}
}
@@ -747,7 +748,7 @@ static void test_data_free(void *test_data)
for (i = 0; i < ARRAY_SIZE(data->io_id); ++i)
if (data->io_id[i] > 0)
- g_source_remove(data->io_id[i]);
+ tester_remove_source(data->io_id[i]);
free(data);
}
@@ -1722,7 +1723,7 @@ static void bthost_iso_disconnected(void *user_data)
data->handle = 0x0000;
}
-static void iso_send(struct test_data *data, GIOChannel *io);
+static void iso_send(struct test_data *data, struct tester_io *io);
static void iso_new_conn(uint16_t handle, void *user_data)
{
@@ -1737,7 +1738,7 @@ static void iso_new_conn(uint16_t handle, void *user_data)
if (data->past_io) {
iso_send(data, data->past_io);
- g_io_channel_unref(data->past_io);
+ tester_io_unref(data->past_io);
data->past_io = NULL;
}
@@ -2382,22 +2383,23 @@ static bool check_bcast_qos(const struct bt_iso_qos *qos1,
}
static void test_connect(const void *test_data);
-static gboolean iso_connect_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data);
-static gboolean iso_accept_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data);
-static bool iso_defer_accept_bcast(struct test_data *data, GIOChannel *io,
- uint8_t num, GIOFunc func);
+static bool iso_connect_cb(struct tester_io *io, uint32_t cond,
+ void *user_data);
+static bool iso_accept_cb(struct tester_io *io, uint32_t cond,
+ void *user_data);
+static bool iso_defer_accept_bcast(struct test_data *data, struct tester_io *io,
+ uint8_t num,
+ tester_io_func_t func);
-static gboolean iso_disconnected(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool iso_disconnected(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = user_data;
const struct iso_client_data *isodata = data->test_data;
data->io_id[0] = 0;
- if (cond & G_IO_HUP) {
+ if (cond & TESTER_IO_HUP) {
if (!isodata->bcast && data->handle)
tester_test_failed();
@@ -2411,34 +2413,35 @@ static gboolean iso_disconnected(GIOChannel *io, GIOCondition cond,
if (!isodata->server)
test_connect(data->test_data);
else {
- GIOChannel *parent =
+ struct tester_io *parent =
queue_peek_head(data->io_queue);
data->step++;
if (!iso_defer_accept_bcast(data,
- g_io_channel_ref(parent), 0,
+ tester_io_ref(parent), 0,
iso_accept_cb))
- g_io_channel_unref(parent);
+ tester_io_unref(parent);
}
- return FALSE;
+ return false;
}
tester_test_passed();
} else
tester_test_failed();
- return FALSE;
+ return false;
}
-static void iso_shutdown(struct test_data *data, GIOChannel *io)
+static void iso_shutdown(struct test_data *data, struct tester_io *io)
{
int sk;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
- data->io_id[0] = g_io_add_watch(io, G_IO_HUP, iso_disconnected, data);
+ data->io_id[0] = tester_io_add_watch(io, TESTER_IO_HUP,
+ iso_disconnected, data);
/* Shutdown using SHUT_WR as SHUT_RDWR cause the socket to HUP
* immediately instead of waiting for Disconnect Complete event.
@@ -2448,12 +2451,13 @@ static void iso_shutdown(struct test_data *data, GIOChannel *io)
tester_print("Disconnecting...");
}
-static void iso_terminate(struct test_data *data, GIOChannel *io)
+static void iso_terminate(struct test_data *data, struct tester_io *io)
{
struct bthost *host;
/* Setup watcher to check if fd is closed properly after termination */
- data->io_id[0] = g_io_add_watch(io, G_IO_HUP, iso_disconnected, data);
+ data->io_id[0] = tester_io_add_watch(io, TESTER_IO_HUP,
+ iso_disconnected, data);
tester_print("Terminating...");
@@ -2463,12 +2467,12 @@ static void iso_terminate(struct test_data *data, GIOChannel *io)
bthost_terminate_big(host, BT_HCI_ERR_LOCAL_HOST_TERM);
}
-static gboolean iso_recv_data(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool iso_recv_data(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = user_data;
const struct iso_client_data *isodata = data->test_data;
- int sk = g_io_channel_unix_get_fd(io);
+ int sk = tester_io_get_fd(io);
unsigned char control[256];
ssize_t ret;
char buf[1024];
@@ -2491,7 +2495,7 @@ static gboolean iso_recv_data(GIOChannel *io, GIOCondition cond,
tester_warn("Failed to read %zu bytes: %s (%d)",
isodata->recv->iov_len, strerror(errno), errno);
tester_test_failed();
- return FALSE;
+ return false;
}
if (isodata->pkt_status) {
@@ -2520,7 +2524,7 @@ static gboolean iso_recv_data(GIOChannel *io, GIOCondition cond,
} else
tester_test_passed();
- return FALSE;
+ return false;
}
if (isodata->pkt_seqnum) {
@@ -2550,28 +2554,28 @@ static gboolean iso_recv_data(GIOChannel *io, GIOCondition cond,
tester_warn("isodata->pkt_seqnum 0x%2.2x != 0x%2.2x "
"pkt_seqnum", pkt_seqnum, data->seqnum);
tester_test_failed();
- return FALSE;
+ return false;
}
}
if (rx_timestamp_check(&msg, isodata->so_timestamping, 1000) < 0) {
tester_test_failed();
- return FALSE;
+ return false;
}
if (data->step) {
data->step--;
} else {
tester_test_failed();
- return FALSE;
+ return false;
}
if (memcmp(buf, isodata->recv->iov_base, ret))
tester_test_failed();
else if (data->step) {
/* Keep the watch, and its id so teardown can remove it */
- data->io_id[0] = g_source_get_id(g_main_current_source());
- return TRUE;
+ data->io_id[0] = tester_current_source();
+ return true;
} else if (isodata->disconnect)
iso_shutdown(data, io);
else if (isodata->terminate)
@@ -2579,10 +2583,10 @@ static gboolean iso_recv_data(GIOChannel *io, GIOCondition cond,
else
tester_test_passed();
- return FALSE;
+ return false;
}
-static void iso_recv(struct test_data *data, GIOChannel *io)
+static void iso_recv(struct test_data *data, struct tester_io *io)
{
const struct iso_client_data *isodata = data->test_data;
struct bthost *host;
@@ -2597,7 +2601,7 @@ static void iso_recv(struct test_data *data, GIOChannel *io)
return;
}
- if (rx_timestamping_init(g_io_channel_unix_get_fd(io),
+ if (rx_timestamping_init(tester_io_get_fd(io),
isodata->so_timestamping))
return;
@@ -2610,32 +2614,33 @@ static void iso_recv(struct test_data *data, GIOChannel *io)
data->step++;
}
- data->io_id[0] = g_io_add_watch(io, G_IO_IN, iso_recv_data, data);
+ data->io_id[0] = tester_io_add_watch(io, TESTER_IO_IN, iso_recv_data,
+ data);
}
-static gboolean iso_recv_errqueue(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool iso_recv_errqueue(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = user_data;
const struct iso_client_data *isodata = data->test_data;
- int sk = g_io_channel_unix_get_fd(io);
+ int sk = tester_io_get_fd(io);
int err;
data->step--;
err = tx_tstamp_recv(&data->tx_ts, sk, isodata->send->iov_len);
if (err > 0)
- return TRUE;
+ return true;
else if (err)
tester_test_failed();
else if (!data->step)
tester_test_passed();
data->io_id[2] = 0;
- return FALSE;
+ return false;
}
-static void iso_tx_timestamping(struct test_data *data, GIOChannel *io)
+static void iso_tx_timestamping(struct test_data *data, struct tester_io *io)
{
const struct iso_client_data *isodata = data->test_data;
int so = isodata->so_timestamping;
@@ -2653,9 +2658,10 @@ static void iso_tx_timestamping(struct test_data *data, GIOChannel *io)
for (count = 0; count < isodata->repeat_send + 1; ++count)
data->step += tx_tstamp_expect(&data->tx_ts, 0);
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
- data->io_id[2] = g_io_add_watch(io, G_IO_ERR, iso_recv_errqueue, data);
+ data->io_id[2] = tester_io_add_watch(io, TESTER_IO_ERR,
+ iso_recv_errqueue, data);
if (isodata->cmsg_timestamping)
so &= ~TS_TX_RECORD_MASK;
@@ -2669,7 +2675,7 @@ static void iso_tx_timestamping(struct test_data *data, GIOChannel *io)
}
}
-static void iso_send_data(struct test_data *data, GIOChannel *io)
+static void iso_send_data(struct test_data *data, struct tester_io *io)
{
const struct iso_client_data *isodata = data->test_data;
char control[CMSG_SPACE(sizeof(uint32_t))];
@@ -2683,7 +2689,7 @@ static void iso_send_data(struct test_data *data, GIOChannel *io)
tester_print("Writing %zu bytes of data", isodata->send->iov_len);
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
if (isodata->cmsg_timestamping) {
memset(control, 0, sizeof(control));
@@ -2710,8 +2716,8 @@ static void iso_send_data(struct test_data *data, GIOChannel *io)
data->step++;
}
-static gboolean iso_pollout(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool iso_pollout(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = user_data;
const struct iso_client_data *isodata = data->test_data;
@@ -2723,8 +2729,8 @@ static gboolean iso_pollout(GIOChannel *io, GIOCondition cond,
* iso_new_conn(), rather than waking up for good until then.
*/
if (isodata->past && !data->handle) {
- data->past_io = g_io_channel_ref(io);
- return FALSE;
+ data->past_io = tester_io_ref(io);
+ return false;
}
tester_print("POLLOUT event received");
@@ -2739,20 +2745,21 @@ static gboolean iso_pollout(GIOChannel *io, GIOCondition cond,
if (isodata->bcast) {
if (isodata->past)
- return FALSE;
+ return false;
tester_test_passed();
- return FALSE;
+ return false;
}
if (isodata->recv)
iso_recv(data, io);
- return FALSE;
+ return false;
}
-static void iso_send(struct test_data *data, GIOChannel *io)
+static void iso_send(struct test_data *data, struct tester_io *io)
{
- data->io_id[0] = g_io_add_watch(io, G_IO_OUT, iso_pollout, data);
+ data->io_id[0] = tester_io_add_watch(io, TESTER_IO_OUT, iso_pollout,
+ data);
}
static bool hook_set_event_mask(const void *msg, uint16_t len, void *user_data)
@@ -2873,8 +2880,8 @@ static bool check_rpa(struct test_data *data, int sk)
return true;
}
-static gboolean iso_connect(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool iso_connect(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
const struct iso_client_data *isodata = data->test_data;
@@ -2884,7 +2891,7 @@ static gboolean iso_connect(GIOChannel *io, GIOCondition cond,
bool ret = true;
uint8_t base[BASE_MAX_LENGTH] = {0};
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
len = sizeof(qos);
memset(&qos, 0, len);
@@ -2895,7 +2902,7 @@ static gboolean iso_connect(GIOChannel *io, GIOCondition cond,
strerror(errno), errno);
data->step = 0;
tester_test_failed();
- return FALSE;
+ return false;
}
if (!isodata->bcast) {
@@ -2911,7 +2918,7 @@ static gboolean iso_connect(GIOChannel *io, GIOCondition cond,
tester_warn("Unexpected QoS parameter");
data->step = 0;
tester_test_failed();
- return FALSE;
+ return false;
}
if (isodata->bcast && isodata->server && isodata->base) {
@@ -2923,7 +2930,7 @@ static gboolean iso_connect(GIOChannel *io, GIOCondition cond,
strerror(errno), errno);
data->step = 0;
tester_test_failed();
- return FALSE;
+ return false;
}
if (len != isodata->base_len ||
@@ -2931,7 +2938,7 @@ static gboolean iso_connect(GIOChannel *io, GIOCondition cond,
tester_warn("Unexpected BASE");
data->step = 0;
tester_test_failed();
- return FALSE;
+ return false;
}
}
@@ -2950,7 +2957,7 @@ static gboolean iso_connect(GIOChannel *io, GIOCondition cond,
strerror(errno), errno);
data->step = 0;
tester_test_failed();
- return FALSE;
+ return false;
}
if (olen != sizeof(addr)) {
@@ -2958,14 +2965,14 @@ static gboolean iso_connect(GIOChannel *io, GIOCondition cond,
olen, sizeof(addr));
data->step = 0;
tester_test_failed();
- return FALSE;
+ return false;
}
if (addr.bc.bc_sid > 0x0f) {
tester_warn("Invalid SID: %d", addr.bc.bc_sid);
data->step = 0;
tester_test_failed();
- return FALSE;
+ return false;
}
tester_print("SID: 0x%02x", addr.bc.bc_sid);
@@ -2984,7 +2991,7 @@ static gboolean iso_connect(GIOChannel *io, GIOCondition cond,
if (!check_rpa(data, sk)) {
data->step = 0;
tester_test_failed();
- return FALSE;
+ return false;
}
tester_print("Successfully connected");
@@ -3012,11 +3019,11 @@ static gboolean iso_connect(GIOChannel *io, GIOCondition cond,
tester_test_passed();
}
- return FALSE;
+ return false;
}
-static gboolean iso_connect_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool iso_connect_cb(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
@@ -3025,8 +3032,8 @@ static gboolean iso_connect_cb(GIOChannel *io, GIOCondition cond,
return iso_connect(io, cond, user_data);
}
-static gboolean iso_connect2_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool iso_connect2_cb(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
@@ -3106,11 +3113,11 @@ static int connect_deferred(int sk)
}
static void setup_connect_many(struct test_data *data, uint8_t n, uint8_t *num,
- GIOFunc *func)
+ tester_io_func_t *func)
{
const struct iso_client_data *isodata = data->test_data;
int sk[256];
- GIOChannel *io;
+ struct tester_io *io;
unsigned int i;
for (i = 0; i < n; ++i) {
@@ -3126,10 +3133,11 @@ static void setup_connect_many(struct test_data *data, uint8_t n, uint8_t *num,
}
for (i = 0; i < n; ++i) {
- io = g_io_channel_unix_new(sk[i]);
- g_io_channel_set_close_on_unref(io, TRUE);
+ io = tester_io_new(sk[i]);
+ tester_io_set_close_on_unref(io, true);
- data->io_id[num[i]] = g_io_add_watch(io, G_IO_OUT, func[i],
+ data->io_id[num[i]] = tester_io_add_watch(io, TESTER_IO_OUT,
+ func[i],
NULL);
if (data->io_queue)
@@ -3140,7 +3148,7 @@ static void setup_connect_many(struct test_data *data, uint8_t n, uint8_t *num,
*/
queue_push_tail(data->io_queue, io);
else
- g_io_channel_unref(io);
+ tester_io_unref(io);
tester_print("Connect %d in progress", num[i]);
@@ -3148,7 +3156,8 @@ static void setup_connect_many(struct test_data *data, uint8_t n, uint8_t *num,
}
}
-static void setup_connect(struct test_data *data, uint8_t num, GIOFunc func)
+static void setup_connect(struct test_data *data, uint8_t num,
+ tester_io_func_t func)
{
return setup_connect_many(data, 1, &num, &func);
}
@@ -3158,7 +3167,7 @@ static void test_connect(const void *test_data)
struct test_data *data = tester_get_data();
const struct iso_client_data *isodata = test_data;
uint8_t n = 0;
- GIOFunc func[2];
+ tester_io_func_t func[2];
uint8_t num[2] = {0, 1};
func[n++] = iso_connect_cb;
@@ -3320,7 +3329,7 @@ fail:
return err;
}
-static gboolean test_listen_past(gpointer user_data)
+static bool test_listen_past(void *user_data)
{
struct test_data *data = user_data;
struct bthost *host;
@@ -3332,15 +3341,15 @@ static gboolean test_listen_past(gpointer user_data)
host = hciemu_client_get_host(data->hciemu);
bthost_past_set_info(host, data->acl_handle);
- return FALSE;
+ return false;
}
static void setup_listen_many(struct test_data *data, uint8_t n, uint8_t *num,
- GIOFunc *func)
+ tester_io_func_t *func)
{
const struct iso_client_data *isodata = data->test_data;
int sk[256];
- GIOChannel *io;
+ struct tester_io *io;
unsigned int i;
for (i = 0; i < n; ++i) {
@@ -3353,13 +3362,13 @@ static void setup_listen_many(struct test_data *data, uint8_t n, uint8_t *num,
return;
}
- io = g_io_channel_unix_new(sk[i]);
- g_io_channel_set_close_on_unref(io, TRUE);
+ io = tester_io_new(sk[i]);
+ tester_io_set_close_on_unref(io, true);
- data->io_id[num[i]] = g_io_add_watch(io, G_IO_IN,
+ data->io_id[num[i]] = tester_io_add_watch(io, TESTER_IO_IN,
func[i], NULL);
- g_io_channel_unref(io);
+ tester_io_unref(io);
tester_print("Listen %d in progress", num[i]);
@@ -3391,25 +3400,27 @@ static void setup_listen_many(struct test_data *data, uint8_t n, uint8_t *num,
/* Wait for listen to take effect before initiating PAST
* procedure.
*/
- g_assert(data->io_id[3] == 0);
- data->io_id[3] = g_timeout_add(250, test_listen_past, data);
+ assert(data->io_id[3] == 0);
+ data->io_id[3] = timeout_add(250, test_listen_past, data, NULL);
}
}
-static void setup_listen(struct test_data *data, uint8_t num, GIOFunc func)
+static void setup_listen(struct test_data *data, uint8_t num,
+ tester_io_func_t func)
{
return setup_listen_many(data, 1, &num, &func);
}
-static bool iso_defer_accept_bcast(struct test_data *data, GIOChannel *io,
- uint8_t num, GIOFunc func)
+static bool iso_defer_accept_bcast(struct test_data *data, struct tester_io *io,
+ uint8_t num,
+ tester_io_func_t func)
{
int sk;
char c;
const struct iso_client_data *isodata = data->test_data;
struct sockaddr_iso *addr = NULL;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
if (isodata->pa_bind) {
addr = malloc(sizeof(*addr) + sizeof(*addr->iso_bc));
@@ -3442,20 +3453,21 @@ static bool iso_defer_accept_bcast(struct test_data *data, GIOChannel *io,
if (data->io_queue)
queue_push_tail(data->io_queue, io);
- data->io_id[num] = g_io_add_watch(io, G_IO_IN,
+ data->io_id[num] = tester_io_add_watch(io, TESTER_IO_IN,
func, NULL);
return true;
}
-static bool iso_defer_accept_ucast(struct test_data *data, GIOChannel *io,
- uint8_t num, GIOFunc func)
+static bool iso_defer_accept_ucast(struct test_data *data, struct tester_io *io,
+ uint8_t num,
+ tester_io_func_t func)
{
int sk;
char c;
struct pollfd pfd;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
memset(&pfd, 0, sizeof(pfd));
pfd.fd = sk;
@@ -3479,25 +3491,26 @@ static bool iso_defer_accept_ucast(struct test_data *data, GIOChannel *io,
if (data->io_queue)
queue_push_tail(data->io_queue, io);
- data->io_id[num] = g_io_add_watch(io, G_IO_OUT,
+ data->io_id[num] = tester_io_add_watch(io, TESTER_IO_OUT,
func, NULL);
return true;
}
-static gboolean iso_accept(GIOChannel *io, GIOCondition cond,
- gpointer user_data, uint8_t num, GIOFunc func)
+static bool iso_accept(struct tester_io *io, uint32_t cond,
+ void *user_data, uint8_t num,
+ tester_io_func_t func)
{
struct test_data *data = tester_get_data();
const struct iso_client_data *isodata = data->test_data;
int sk, new_sk;
- gboolean ret;
- GIOChannel *new_io;
+ bool ret;
+ struct tester_io *new_io;
iso_defer_accept_t iso_defer_accept = isodata->bcast ?
iso_defer_accept_bcast :
iso_defer_accept_ucast;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
new_sk = accept(sk, NULL, NULL);
if (new_sk < 0) {
@@ -3505,12 +3518,12 @@ static gboolean iso_accept(GIOChannel *io, GIOCondition cond,
return false;
}
- new_io = g_io_channel_unix_new(new_sk);
- g_io_channel_set_close_on_unref(new_io, TRUE);
+ new_io = tester_io_new(new_sk);
+ tester_io_set_close_on_unref(new_io, true);
if (isodata->defer) {
if (isodata->expect_err < 0) {
- g_io_channel_unref(new_io);
+ tester_io_unref(new_io);
tester_test_passed();
return false;
}
@@ -3519,13 +3532,13 @@ static gboolean iso_accept(GIOChannel *io, GIOCondition cond,
iso_connect(new_io, cond, user_data);
if (!data->step) {
- g_io_channel_unref(new_io);
+ tester_io_unref(new_io);
return false;
}
/* Return if connection has already been accepted */
if (queue_find(data->io_queue, NULL, io)) {
- g_io_channel_unref(new_io);
+ tester_io_unref(new_io);
return false;
}
}
@@ -3533,7 +3546,7 @@ static gboolean iso_accept(GIOChannel *io, GIOCondition cond,
if (!iso_defer_accept(data, new_io, num, func)) {
tester_warn("Unable to accept deferred setup");
tester_test_failed();
- g_io_channel_unref(new_io);
+ tester_io_unref(new_io);
}
return false;
}
@@ -3566,12 +3579,12 @@ static gboolean iso_accept(GIOChannel *io, GIOCondition cond,
ret = iso_connect(new_io, cond, user_data);
- g_io_channel_unref(new_io);
+ tester_io_unref(new_io);
return ret;
}
-static gboolean iso_accept_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool iso_accept_cb(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
const struct iso_client_data *isodata = data->test_data;
@@ -3584,8 +3597,8 @@ static gboolean iso_accept_cb(GIOChannel *io, GIOCondition cond,
return iso_accept(io, cond, user_data, 0, iso_connect_cb);
}
-static gboolean iso_accept2_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool iso_accept2_cb(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
@@ -3605,13 +3618,13 @@ static void test_connect2(const void *test_data)
{
struct test_data *data = tester_get_data();
uint8_t num[2] = {0, 1};
- GIOFunc funcs[2] = {iso_connect_cb, iso_connect2_cb};
+ tester_io_func_t funcs[2] = {iso_connect_cb, iso_connect2_cb};
setup_connect_many(data, 2, num, funcs);
}
-static gboolean iso_connect2_seq_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool iso_connect2_seq_cb(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
@@ -3629,7 +3642,7 @@ static void test_connect2_seq(const void *test_data)
setup_connect(data, 0, iso_connect2_seq_cb);
}
-static gboolean test_connect2_busy_done(gpointer user_data)
+static bool test_connect2_busy_done(void *user_data)
{
struct test_data *data = tester_get_data();
@@ -3637,18 +3650,18 @@ static gboolean test_connect2_busy_done(gpointer user_data)
if (data->io_id[0] > 0) {
/* First connection still exists */
- g_source_remove(data->io_id[0]);
+ tester_remove_source(data->io_id[0]);
data->io_id[0] = 0;
tester_test_passed();
} else {
tester_test_failed();
}
- return FALSE;
+ return false;
}
-static gboolean iso_connect_cb_busy_disc(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool iso_connect_cb_busy_disc(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
@@ -3656,11 +3669,11 @@ static gboolean iso_connect_cb_busy_disc(GIOChannel *io, GIOCondition cond,
tester_print("Disconnected 1");
tester_test_failed();
- return FALSE;
+ return false;
}
-static gboolean iso_connect_cb_busy_2(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool iso_connect_cb_busy_2(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
int err, sk_err, sk;
@@ -3668,7 +3681,7 @@ static gboolean iso_connect_cb_busy_2(GIOChannel *io, GIOCondition cond,
data->io_id[1] = 0;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
len = sizeof(sk_err);
@@ -3681,22 +3694,22 @@ static gboolean iso_connect_cb_busy_2(GIOChannel *io, GIOCondition cond,
if (err == -EBUSY && data->io_id[0] > 0) {
/* Wait in case first connection still gets disconnected */
- data->io_id[1] = g_timeout_add(250, test_connect2_busy_done,
- data);
+ data->io_id[1] = timeout_add(250, test_connect2_busy_done,
+ data, NULL);
} else {
tester_test_failed();
}
- return FALSE;
+ return false;
}
-static gboolean iso_connect_cb_busy(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool iso_connect_cb_busy(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
/* First connection shall not be disconnected */
- data->io_id[0] = g_io_add_watch(io, G_IO_ERR | G_IO_HUP,
+ data->io_id[0] = tester_io_add_watch(io, TESTER_IO_ERR | TESTER_IO_HUP,
iso_connect_cb_busy_disc, data);
/* Second connect shall fail since CIG is now busy */
@@ -3712,8 +3725,8 @@ static void test_connect2_busy(const void *test_data)
setup_connect(data, 0, iso_connect_cb_busy);
}
-static gboolean iso_connect_close_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool iso_connect_close_cb(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = user_data;
@@ -3725,7 +3738,7 @@ static gboolean iso_connect_close_cb(GIOChannel *io, GIOCondition cond,
if (!data->step)
tester_test_passed();
- return FALSE;
+ return false;
}
static bool hook_remove_cig(const void *msg, uint16_t len, void *user_data)
@@ -3745,7 +3758,7 @@ static void test_connect_close(const void *test_data)
{
struct test_data *data = tester_get_data();
int sk;
- GIOChannel *io;
+ struct tester_io *io;
data->step = 2;
@@ -3757,31 +3770,33 @@ static void test_connect_close(const void *test_data)
if (sk < 0)
return;
- io = g_io_channel_unix_new(sk);
- g_io_channel_set_close_on_unref(io, TRUE);
- data->io_id[0] = g_io_add_watch(io, G_IO_HUP, iso_connect_close_cb,
+ io = tester_io_new(sk);
+ tester_io_set_close_on_unref(io, true);
+ data->io_id[0] = tester_io_add_watch(io, TESTER_IO_HUP,
+ iso_connect_close_cb,
data);
shutdown(sk, SHUT_RDWR);
- g_io_channel_unref(io);
+ tester_io_unref(io);
}
-static gboolean iso_connect_wait_close_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool iso_connect_wait_close_cb(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
int sk;
tester_print("Connected");
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
- data->io_id[0] = g_io_add_watch(io, G_IO_HUP, iso_connect_close_cb,
+ data->io_id[0] = tester_io_add_watch(io, TESTER_IO_HUP,
+ iso_connect_close_cb,
data);
shutdown(sk, SHUT_RDWR);
- return FALSE;
+ return false;
}
static void test_connect_wait_close(const void *test_data)
@@ -3848,7 +3863,7 @@ static void test_past(const void *test_data)
{
struct test_data *data = tester_get_data();
uint8_t num[1] = { 1 };
- GIOFunc funcs[1] = { iso_accept2_cb };
+ tester_io_func_t funcs[1] = { iso_accept2_cb };
if (data->client_num > 1)
setup_listen_many(data, 1, num, funcs);
@@ -3868,7 +3883,7 @@ static void test_bcast2(const void *test_data)
{
struct test_data *data = tester_get_data();
uint8_t num[2] = {0, 1};
- GIOFunc funcs[2] = {iso_connect_cb, iso_connect2_cb};
+ tester_io_func_t funcs[2] = {iso_connect_cb, iso_connect2_cb};
setup_connect_many(data, 2, num, funcs);
}
@@ -3877,7 +3892,7 @@ static void test_bcast2_reconn(const void *test_data)
{
struct test_data *data = tester_get_data();
uint8_t num[2] = {0, 1};
- GIOFunc funcs[2] = {iso_connect_cb, iso_connect2_cb};
+ tester_io_func_t funcs[2] = {iso_connect_cb, iso_connect2_cb};
data->io_queue = queue_new();
@@ -3896,7 +3911,7 @@ static void test_bcast_recv2(const void *test_data)
{
struct test_data *data = tester_get_data();
uint8_t num[2] = {0, 1};
- GIOFunc funcs[2] = {iso_accept_cb, iso_accept2_cb};
+ tester_io_func_t funcs[2] = {iso_accept_cb, iso_accept2_cb};
setup_listen_many(data, 2, num, funcs);
}
@@ -3924,7 +3939,7 @@ static void test_bcast_recv2_defer(const void *test_data)
{
struct test_data *data = tester_get_data();
uint8_t num[2] = {0, 1};
- GIOFunc funcs[2] = {iso_accept_cb, iso_accept2_cb};
+ tester_io_func_t funcs[2] = {iso_accept_cb, iso_accept2_cb};
data->step = 2;
diff --git a/tools/l2cap-tester.c b/tools/l2cap-tester.c
index c2643b4182b0..4002ca561db8 100644
--- a/tools/l2cap-tester.c
+++ b/tools/l2cap-tester.c
@@ -18,7 +18,7 @@
#include <poll.h>
#include <stdbool.h>
-#include <glib.h>
+#include <assert.h>
#include "bluetooth/bluetooth.h"
#include "bluetooth/l2cap.h"
@@ -29,6 +29,8 @@
#include "emulator/hciemu.h"
#include "src/shared/tester.h"
+#include "src/shared/timeout.h"
+#include "tools/tester-io.h"
#include "src/shared/mgmt.h"
#include "src/shared/queue.h"
#include "src/shared/util.h"
@@ -271,7 +273,7 @@ static void test_pre_setup(const void *test_data)
static void io_channel_unref(void *data)
{
- g_io_channel_unref(data);
+ tester_io_unref(data);
}
static void test_post_teardown(const void *test_data)
@@ -279,12 +281,12 @@ static void test_post_teardown(const void *test_data)
struct test_data *data = tester_get_data();
if (data->io_id > 0) {
- g_source_remove(data->io_id);
+ tester_remove_source(data->io_id);
data->io_id = 0;
}
if (data->err_io_id > 0) {
- g_source_remove(data->err_io_id);
+ tester_remove_source(data->err_io_id);
data->err_io_id = 0;
}
@@ -1798,8 +1800,8 @@ static void received_data(struct test_data *tdata, const void *buf,
iov.iov_len = 0;
}
-static gboolean sock_received_data(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool sock_received_data(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
const struct l2cap_data *l2data = data->test_data;
@@ -1808,21 +1810,21 @@ static gboolean sock_received_data(GIOChannel *io, GIOCondition cond,
int sk;
ssize_t len;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
len = recv_tstamp(sk, buf, sizeof(buf), tstamp);
if (len < 0) {
tester_warn("Unable to read: %s (%d)", strerror(errno), errno);
tester_test_failed();
- return FALSE;
+ return false;
}
received_data(data, buf, len, l2data->read_data, l2data->data_len);
if (data->step)
- return TRUE;
+ return true;
- return FALSE;
+ return false;
}
static void bthost_received_data(const void *buf, uint16_t len,
@@ -1834,8 +1836,8 @@ static void bthost_received_data(const void *buf, uint16_t len,
received_data(data, buf, len, l2data->write_data, l2data->data_len);
}
-static gboolean socket_closed_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool socket_closed_cb(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
const struct l2cap_data *l2data = data->test_data;
@@ -1846,19 +1848,19 @@ static gboolean socket_closed_cb(GIOChannel *io, GIOCondition cond,
if (l2data->shut_sock_wr) {
/* if socket is closed using SHUT_WR, L2CAP disconnection
- * response must be received first before G_IO_HUP event.
+ * response must be received first before TESTER_IO_HUP event.
*/
if (data->host_disconnected)
tester_test_passed();
else {
- tester_warn("G_IO_HUP received before receiving L2CAP disconnection");
+ tester_warn("TESTER_IO_HUP received before receiving L2CAP disconnection");
tester_test_failed();
}
}
data->io_id = 0;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &sk_err, &len) < 0)
err = -errno;
@@ -1874,7 +1876,7 @@ static gboolean socket_closed_cb(GIOChannel *io, GIOCondition cond,
else
tester_test_failed();
- return FALSE;
+ return false;
}
static bool check_mtu(struct test_data *data, int sk)
@@ -1923,7 +1925,7 @@ static bool check_mtu(struct test_data *data, int sk)
return true;
}
-static gboolean check_phy_and_sec(gpointer args)
+static bool check_phy_and_sec(void *args)
{
int sk = PTR_TO_INT(args);
struct test_data *data = tester_get_data();
@@ -1947,7 +1949,7 @@ static gboolean check_phy_and_sec(gpointer args)
tester_print("phy 0x%08x != 0x%08x", l2data->phy, data->phys);
/* Retry */
- return TRUE;
+ return true;
}
tester_print("Checking security level...");
@@ -1965,7 +1967,7 @@ static gboolean check_phy_and_sec(gpointer args)
l2data->change_sec_level);
/* Retry */
- return TRUE;
+ return true;
}
data->step--;
@@ -1973,7 +1975,7 @@ static gboolean check_phy_and_sec(gpointer args)
done:
shutdown(sk, SHUT_WR);
- return FALSE;
+ return false;
}
static int check_phys_and_sec(struct test_data *data, int sk)
@@ -2031,7 +2033,7 @@ static int check_phys_and_sec(struct test_data *data, int sk)
if (changed) {
/* Wait for the change */
data->step++;
- g_timeout_add(50, check_phy_and_sec, INT_TO_PTR(sk));
+ timeout_add(50, check_phy_and_sec, INT_TO_PTR(sk), NULL);
return -EINPROGRESS;
}
@@ -2039,29 +2041,29 @@ static int check_phys_and_sec(struct test_data *data, int sk)
return 0;
}
-static gboolean recv_errqueue(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool recv_errqueue(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = user_data;
const struct l2cap_data *l2data = data->test_data;
- int sk = g_io_channel_unix_get_fd(io);
+ int sk = tester_io_get_fd(io);
int err;
data->step--;
err = tx_tstamp_recv(&data->tx_ts, sk, l2data->data_len);
if (err > 0)
- return TRUE;
+ return true;
else if (err)
tester_test_failed();
else if (!data->step)
tester_test_passed();
data->err_io_id = 0;
- return FALSE;
+ return false;
}
-static void l2cap_tx_timestamping(struct test_data *data, GIOChannel *io)
+static void l2cap_tx_timestamping(struct test_data *data, struct tester_io *io)
{
const struct l2cap_data *l2data = data->test_data;
int so = l2data->so_timestamping;
@@ -2072,7 +2074,7 @@ static void l2cap_tx_timestamping(struct test_data *data, GIOChannel *io)
if (!(l2data->so_timestamping & TS_TX_RECORD_MASK))
return;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
tester_print("Enabling TX timestamping");
@@ -2090,7 +2092,8 @@ static void l2cap_tx_timestamping(struct test_data *data, GIOChannel *io)
return;
}
- data->err_io_id = g_io_add_watch(io, G_IO_ERR, recv_errqueue, data);
+ data->err_io_id = tester_io_add_watch(io, TESTER_IO_ERR, recv_errqueue,
+ data);
}
static int l2cap_send(int sk, const void *data, size_t len, uint16_t mtu)
@@ -2115,7 +2118,7 @@ static int l2cap_send(int sk, const void *data, size_t len, uint16_t mtu)
return total;
}
-static void l2cap_read_data(struct test_data *data, GIOChannel *io,
+static void l2cap_read_data(struct test_data *data, struct tester_io *io,
uint16_t cid)
{
const struct l2cap_data *l2data = data->test_data;
@@ -2125,12 +2128,12 @@ static void l2cap_read_data(struct test_data *data, GIOChannel *io,
data->step = 0;
- if (rx_timestamping_init(g_io_channel_unix_get_fd(io),
+ if (rx_timestamping_init(tester_io_get_fd(io),
l2data->so_timestamping))
return;
bthost = hciemu_client_get_host(data->hciemu);
- g_io_add_watch(io, G_IO_IN, sock_received_data, NULL);
+ tester_io_add_watch(io, TESTER_IO_IN, sock_received_data, NULL);
len = MIN(iov.iov_len, data->l2o.imtu);
@@ -2144,7 +2147,7 @@ static void l2cap_read_data(struct test_data *data, GIOChannel *io,
++data->step;
}
-static void l2cap_write_data(struct test_data *data, GIOChannel *io,
+static void l2cap_write_data(struct test_data *data, struct tester_io *io,
uint16_t cid)
{
const struct l2cap_data *l2data = data->test_data;
@@ -2154,7 +2157,7 @@ static void l2cap_write_data(struct test_data *data, GIOChannel *io,
unsigned int count;
socklen_t len = sizeof(size);
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
data->step = 0;
@@ -2185,8 +2188,8 @@ static void l2cap_write_data(struct test_data *data, GIOChannel *io,
}
}
-static gboolean l2cap_connect_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool l2cap_connect_cb(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
const struct l2cap_data *l2data = data->test_data;
@@ -2195,7 +2198,7 @@ static gboolean l2cap_connect_cb(GIOChannel *io, GIOCondition cond,
data->io_id = 0;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &sk_err, &len) < 0)
err = -errno;
@@ -2211,30 +2214,31 @@ static gboolean l2cap_connect_cb(GIOChannel *io, GIOCondition cond,
if (!check_mtu(data, sk)) {
tester_test_failed();
- return FALSE;
+ return false;
}
err = check_phys_and_sec(data, sk);
if (err < 0) {
if (err == -EINPROGRESS) {
- g_io_add_watch(io, G_IO_HUP, socket_closed_cb, NULL);
- return FALSE;
+ tester_io_add_watch(io, TESTER_IO_HUP, socket_closed_cb,
+ NULL);
+ return false;
}
tester_test_failed();
- return FALSE;
+ return false;
}
if (l2data->read_data) {
l2cap_read_data(data, io, data->dcid);
- return FALSE;
+ return false;
} else if (l2data->write_data) {
l2cap_write_data(data, io, data->dcid);
- return FALSE;
+ return false;
} else if (l2data->shut_sock_wr) {
- g_io_add_watch(io, G_IO_HUP, socket_closed_cb, NULL);
+ tester_io_add_watch(io, TESTER_IO_HUP, socket_closed_cb, NULL);
shutdown(sk, SHUT_WR);
- return FALSE;
+ return false;
}
failed:
@@ -2243,7 +2247,7 @@ failed:
else
tester_test_passed();
- return FALSE;
+ return false;
}
static int create_l2cap_sock(struct test_data *data, uint16_t psm,
@@ -2434,7 +2438,7 @@ static void test_connect(const void *test_data)
{
struct test_data *data = tester_get_data();
const struct l2cap_data *l2data = data->test_data;
- GIOChannel *io;
+ struct tester_io *io;
int sk;
if (l2data->server_psm) {
@@ -2485,12 +2489,13 @@ static void test_connect(const void *test_data)
return;
}
- io = g_io_channel_unix_new(sk);
- g_io_channel_set_close_on_unref(io, TRUE);
+ io = tester_io_new(sk);
+ tester_io_set_close_on_unref(io, true);
- data->io_id = g_io_add_watch(io, G_IO_OUT, l2cap_connect_cb, NULL);
+ data->io_id = tester_io_add_watch(io, TESTER_IO_OUT, l2cap_connect_cb,
+ NULL);
- g_io_channel_unref(io);
+ tester_io_unref(io);
tester_print("Connect in progress");
}
@@ -2499,7 +2504,7 @@ static void test_connect_close(const void *test_data)
{
struct test_data *data = tester_get_data();
const struct l2cap_data *l2data = data->test_data;
- GIOChannel *io;
+ struct tester_io *io;
int sk;
sk = create_l2cap_sock(data, 0, l2data->cid, l2data->sec_level,
@@ -2519,10 +2524,11 @@ static void test_connect_close(const void *test_data)
return;
}
- io = g_io_channel_unix_new(sk);
- g_io_channel_set_close_on_unref(io, TRUE);
- data->io_id = g_io_add_watch(io, G_IO_HUP, socket_closed_cb, NULL);
- g_io_channel_unref(io);
+ io = tester_io_new(sk);
+ tester_io_set_close_on_unref(io, true);
+ data->io_id = tester_io_add_watch(io, TESTER_IO_HUP, socket_closed_cb,
+ NULL);
+ tester_io_unref(io);
shutdown(sk, SHUT_RDWR);
}
@@ -2531,7 +2537,7 @@ static void test_connect_timeout(const void *test_data)
{
struct test_data *data = tester_get_data();
const struct l2cap_data *l2data = data->test_data;
- GIOChannel *io;
+ struct tester_io *io;
int sk;
struct timeval sndto;
socklen_t len;
@@ -2565,10 +2571,11 @@ static void test_connect_timeout(const void *test_data)
return;
}
- io = g_io_channel_unix_new(sk);
- g_io_channel_set_close_on_unref(io, TRUE);
- data->io_id = g_io_add_watch(io, G_IO_HUP, socket_closed_cb, NULL);
- g_io_channel_unref(io);
+ io = tester_io_new(sk);
+ tester_io_set_close_on_unref(io, true);
+ data->io_id = tester_io_add_watch(io, TESTER_IO_HUP, socket_closed_cb,
+ NULL);
+ tester_io_unref(io);
}
static void test_connect_reject(const void *test_data)
@@ -2593,12 +2600,13 @@ static void test_connect_reject(const void *test_data)
close(sk);
}
-static int connect_socket(const uint8_t *client_bdaddr, GIOFunc connect_cb,
+static int connect_socket(const uint8_t *client_bdaddr,
+ tester_io_func_t connect_cb,
bool defer)
{
struct test_data *data = tester_get_data();
const struct l2cap_data *l2data = data->test_data;
- GIOChannel *io;
+ struct tester_io *io;
int sk;
sk = create_l2cap_sock(data, 0, l2data->cid, l2data->sec_level,
@@ -2634,12 +2642,13 @@ static int connect_socket(const uint8_t *client_bdaddr, GIOFunc connect_cb,
}
if (connect_cb) {
- io = g_io_channel_unix_new(sk);
- g_io_channel_set_close_on_unref(io, TRUE);
+ io = tester_io_new(sk);
+ tester_io_set_close_on_unref(io, true);
- data->io_id = g_io_add_watch(io, G_IO_OUT, connect_cb, NULL);
+ data->io_id = tester_io_add_watch(io, TESTER_IO_OUT, connect_cb,
+ NULL);
- g_io_channel_unref(io);
+ tester_io_unref(io);
}
tester_print("Connect in progress, sk = %d %s", sk,
@@ -2648,7 +2657,7 @@ static int connect_socket(const uint8_t *client_bdaddr, GIOFunc connect_cb,
return sk;
}
-static gboolean test_close_socket_1_part_3(gpointer arg)
+static bool test_close_socket_1_part_3(void *arg)
{
struct test_data *data = tester_get_data();
@@ -2657,20 +2666,20 @@ static gboolean test_close_socket_1_part_3(gpointer arg)
if (data->sk != -1) {
tester_print("Error - scan was not enabled yet");
tester_test_failed();
- return FALSE;
+ return false;
}
if (hciemu_get_central_le_scan_enable(data->hciemu)) {
tester_print("Delayed check whether scann is off failed");
tester_test_failed();
- return FALSE;
+ return false;
}
tester_test_passed();
- return FALSE;
+ return false;
}
-static gboolean test_close_socket_1_part_2(gpointer args)
+static bool test_close_socket_1_part_2(void *args)
{
struct test_data *data = tester_get_data();
int sk = data->sk;
@@ -2684,7 +2693,7 @@ static gboolean test_close_socket_1_part_2(gpointer args)
if (!hciemu_get_central_le_scan_enable(data->hciemu)) {
tester_print("Error - should be still scanning");
tester_test_failed();
- return FALSE;
+ return false;
}
/* Calling close() should remove device from accept list, and stop
@@ -2693,15 +2702,15 @@ static gboolean test_close_socket_1_part_2(gpointer args)
if (close(sk) < 0) {
tester_print("Error when closing socket");
tester_test_failed();
- return FALSE;
+ return false;
}
data->sk = -1;
/* tester_test_passed will be called when scan is stopped. */
- return FALSE;
+ return false;
}
-static gboolean test_close_socket_2_part_3(gpointer arg)
+static bool test_close_socket_2_part_3(void *arg)
{
struct test_data *data = tester_get_data();
int sk = data->sk;
@@ -2711,7 +2720,7 @@ static gboolean test_close_socket_2_part_3(gpointer arg)
if (hciemu_get_central_le_scan_enable(data->hciemu)) {
tester_print("Error - should no longer scan");
tester_test_failed();
- return FALSE;
+ return false;
}
/* Calling close() should eventually cause CMD_LE_CREATE_CONN_CANCEL */
@@ -2719,11 +2728,11 @@ static gboolean test_close_socket_2_part_3(gpointer arg)
if (err < 0) {
tester_print("Error when closing socket");
tester_test_failed();
- return FALSE;
+ return false;
}
/* CMD_LE_CREATE_CONN_CANCEL will trigger test pass. */
- return FALSE;
+ return false;
}
static bool test_close_socket_cc_hook(const void *data, uint16_t len,
@@ -2732,7 +2741,7 @@ static bool test_close_socket_cc_hook(const void *data, uint16_t len,
return false;
}
-static gboolean test_close_socket_2_part_2(gpointer arg)
+static bool test_close_socket_2_part_2(void *arg)
{
struct test_data *data = tester_get_data();
struct bthost *bthost = hciemu_client_get_host(data->hciemu);
@@ -2748,7 +2757,7 @@ static gboolean test_close_socket_2_part_2(gpointer arg)
*/
bthost_set_adv_enable(bthost, 0x01);
bthost_set_adv_enable(bthost, 0x00);
- return FALSE;
+ return false;
}
static void test_close_socket_scan_enabled(void)
@@ -2757,9 +2766,9 @@ static void test_close_socket_scan_enabled(void)
const struct l2cap_data *l2data = data->test_data;
if (l2data == &le_client_close_socket_test_1)
- g_idle_add(test_close_socket_1_part_2, NULL);
+ timeout_add(0, test_close_socket_1_part_2, NULL, NULL);
else if (l2data == &le_client_close_socket_test_2)
- g_idle_add(test_close_socket_2_part_2, NULL);
+ timeout_add(0, test_close_socket_2_part_2, NULL, NULL);
}
static void test_close_socket_scan_disabled(void)
@@ -2768,7 +2777,7 @@ static void test_close_socket_scan_disabled(void)
const struct l2cap_data *l2data = data->test_data;
if (l2data == &le_client_close_socket_test_1)
- g_idle_add(test_close_socket_1_part_3, NULL);
+ timeout_add(0, test_close_socket_1_part_3, NULL, NULL);
}
/* The kernel creates the connection once the scan is disabled, close the
@@ -2780,7 +2789,7 @@ static void test_close_socket_create_conn(void)
const struct l2cap_data *l2data = data->test_data;
if (l2data == &le_client_close_socket_test_2)
- g_idle_add(test_close_socket_2_part_3, NULL);
+ timeout_add(0, test_close_socket_2_part_3, NULL, NULL);
}
static void test_close_socket_conn_cancel(void)
@@ -2828,8 +2837,8 @@ static void test_close_socket(const void *test_data)
}
static uint8_t test_2_connect_cb_cnt;
-static gboolean test_2_connect_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool test_2_connect_cb(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
const struct l2cap_data *l2data = data->test_data;
@@ -2838,7 +2847,7 @@ static gboolean test_2_connect_cb(GIOChannel *io, GIOCondition cond,
data->io_id = 0;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &sk_err, &len) < 0)
err = -errno;
@@ -2848,7 +2857,7 @@ static gboolean test_2_connect_cb(GIOChannel *io, GIOCondition cond,
if (err < 0) {
tester_warn("Connect failed: %s (%d)", strerror(-err), -err);
tester_test_failed();
- return FALSE;
+ return false;
}
tester_print("Successfully connected");
@@ -2865,16 +2874,16 @@ static gboolean test_2_connect_cb(GIOChannel *io, GIOCondition cond,
tester_test_passed();
}
- return FALSE;
+ return false;
}
-static gboolean enable_advertising(gpointer args)
+static bool enable_advertising(void *args)
{
struct test_data *data = tester_get_data();
struct bthost *bthost = hciemu_client_get_host(data->hciemu);
bthost_set_adv_enable(bthost, 0x01);
- return FALSE;
+ return false;
}
static void test_connect_2_part_2(void)
@@ -2891,7 +2900,7 @@ static void test_connect_2_part_2(void)
close(data->sk);
}
- g_idle_add(enable_advertising, NULL);
+ timeout_add(0, enable_advertising, NULL, NULL);
}
static uint8_t test_scan_enable_counter;
@@ -2907,7 +2916,7 @@ static void test_connect_2_router(uint16_t opcode, const void *param,
if (test_scan_enable_counter == 1)
test_connect_2_part_2();
else if (test_scan_enable_counter == 2)
- g_idle_add(enable_advertising, NULL);
+ timeout_add(0, enable_advertising, NULL, NULL);
}
}
@@ -2942,26 +2951,27 @@ static void test_connect_2(const void *test_data)
defer);
}
-static gboolean watch_no_track_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool watch_no_track_cb(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
- int sk = g_io_channel_unix_get_fd(io);
+ int sk = tester_io_get_fd(io);
tester_print("Ready sk = %d", sk);
- return FALSE;
+ return false;
}
static int watch_and_close_sk(int sk)
{
- GIOChannel *io;
+ struct tester_io *io;
if (sk < 0)
return sk;
- io = g_io_channel_unix_new(sk);
- g_io_add_watch(io, G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ io = tester_io_new(sk);
+ tester_io_add_watch(io,
+ TESTER_IO_OUT | TESTER_IO_ERR | TESTER_IO_HUP | TESTER_IO_NVAL,
watch_no_track_cb, NULL);
- g_io_channel_unref(io);
+ tester_io_unref(io);
return sk;
}
@@ -3028,8 +3038,8 @@ static void test_connect_ext_defer_limit(const void *test_data)
}
}
-static gboolean l2cap_accept_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool l2cap_accept_cb(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
const struct l2cap_data *l2data = data->test_data;
@@ -3037,46 +3047,47 @@ static gboolean l2cap_accept_cb(GIOChannel *io, GIOCondition cond,
data->io_id = 0;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
if (!check_mtu(data, sk)) {
tester_test_failed();
- return FALSE;
+ return false;
}
err = check_phys_and_sec(data, sk);
if (err < 0) {
if (err == -EINPROGRESS) {
- g_io_add_watch(io, G_IO_HUP, socket_closed_cb, NULL);
- return FALSE;
+ tester_io_add_watch(io, TESTER_IO_HUP, socket_closed_cb,
+ NULL);
+ return false;
}
tester_test_failed();
- return FALSE;
+ return false;
}
if (l2data->read_data) {
l2cap_read_data(data, io, data->dcid);
- return FALSE;
+ return false;
} else if (l2data->write_data) {
l2cap_write_data(data, io, data->scid);
- return FALSE;
+ return false;
}
tester_print("Successfully connected");
tester_test_passed();
- return FALSE;
+ return false;
}
-static bool defer_accept(struct test_data *data, GIOChannel *io,
- GIOFunc accept_cb)
+static bool defer_accept(struct test_data *data, struct tester_io *io,
+ tester_io_func_t accept_cb)
{
int sk;
char c;
struct pollfd pfd;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
memset(&pfd, 0, sizeof(pfd));
pfd.fd = sk;
@@ -3095,17 +3106,18 @@ static bool defer_accept(struct test_data *data, GIOChannel *io,
}
if (accept_cb)
- data->io_id = g_io_add_watch(io, G_IO_OUT, accept_cb, NULL);
+ data->io_id = tester_io_add_watch(io, TESTER_IO_OUT, accept_cb,
+ NULL);
- g_io_channel_unref(io);
+ tester_io_unref(io);
tester_print("Accept deferred setup sk = %d", sk);
return true;
}
-static gboolean l2cap_listen_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static bool l2cap_listen_cb(struct tester_io *io, uint32_t cond,
+ void *user_data)
{
struct test_data *data = tester_get_data();
const struct l2cap_data *l2data = data->test_data;
@@ -3113,31 +3125,31 @@ static gboolean l2cap_listen_cb(GIOChannel *io, GIOCondition cond,
data->io_id = 0;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
new_sk = accept(sk, NULL, NULL);
if (new_sk < 0) {
tester_warn("accept failed: %s (%u)", strerror(errno), errno);
tester_test_failed();
- return FALSE;
+ return false;
}
tester_print("Accept sk = %d", new_sk);
- io = g_io_channel_unix_new(new_sk);
- g_io_channel_set_close_on_unref(io, TRUE);
+ io = tester_io_new(new_sk);
+ tester_io_set_close_on_unref(io, true);
if (l2data->defer) {
if (l2data->expect_err < 0) {
- g_io_channel_unref(io);
- return FALSE;
+ tester_io_unref(io);
+ return false;
}
if (!defer_accept(data, io, l2cap_accept_cb)) {
tester_warn("Unable to accept deferred setup");
tester_test_failed();
}
- return FALSE;
+ return false;
}
return l2cap_accept_cb(io, cond, user_data);
@@ -3243,14 +3255,14 @@ static void send_req_new_conn(uint16_t handle, void *user_data)
}
static void start_test_server(const void *test_data, bthost_new_conn_cb conn_cb,
- GIOFunc listen_cb)
+ tester_io_func_t listen_cb)
{
struct test_data *data = tester_get_data();
const struct l2cap_data *l2data = data->test_data;
const uint8_t *central_bdaddr;
uint8_t addr_type;
struct bthost *bthost;
- GIOChannel *io;
+ struct tester_io *io;
int sk;
if (l2data->server_psm || l2data->cid) {
@@ -3281,11 +3293,12 @@ static void start_test_server(const void *test_data, bthost_new_conn_cb conn_cb,
return;
}
- io = g_io_channel_unix_new(sk);
- g_io_channel_set_close_on_unref(io, TRUE);
+ io = tester_io_new(sk);
+ tester_io_set_close_on_unref(io, true);
- data->io_id = g_io_add_watch(io, G_IO_IN, listen_cb, NULL);
- g_io_channel_unref(io);
+ data->io_id = tester_io_add_watch(io, TESTER_IO_IN, listen_cb,
+ NULL);
+ tester_io_unref(io);
tester_print("Listening for connections");
}
@@ -3313,39 +3326,39 @@ static void test_server(const void *test_data)
start_test_server(test_data, send_req_new_conn, l2cap_listen_cb);
}
-static gboolean ext_flowctl_accept_2_close_1_listen_cb(GIOChannel *io,
- GIOCondition cond, gpointer user_data)
+static bool ext_flowctl_accept_2_close_1_listen_cb(struct tester_io *io,
+ uint32_t cond, void *user_data)
{
struct test_data *data = tester_get_data();
int sk, new_sk;
int err = 0;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
new_sk = accept(sk, NULL, NULL);
if (err < 0) {
tester_warn("accept failed: %s (%u)", strerror(errno), errno);
tester_test_failed();
- return FALSE;
+ return false;
}
- if (!defer_accept(data, g_io_channel_unix_new(new_sk), NULL))
+ if (!defer_accept(data, tester_io_new(new_sk), NULL))
goto fail;
close(new_sk);
if (data->step--)
- return TRUE;
+ return true;
tester_test_passed();
data->io_id = 0;
- return FALSE;
+ return false;
fail:
tester_test_failed();
close(new_sk);
- return FALSE;
+ return false;
}
static void test_ext_flowctl_server_accept_2_close_1(const void *test_data)
@@ -3357,14 +3370,14 @@ static void test_ext_flowctl_server_accept_2_close_1(const void *test_data)
ext_flowctl_accept_2_close_1_listen_cb);
}
-static gboolean ext_flowctl_nval_conn_req_ready_cb(gpointer ptr)
+static bool ext_flowctl_nval_conn_req_ready_cb(void *ptr)
{
struct test_data *data = tester_get_data();
const struct queue_entry *entry;
- GIOFunc cb = l2cap_accept_cb;
+ tester_io_func_t cb = l2cap_accept_cb;
if (data->io_id) {
- g_source_remove(data->io_id);
+ tester_remove_source(data->io_id);
data->io_id = 0;
}
@@ -3375,10 +3388,10 @@ static gboolean ext_flowctl_nval_conn_req_ready_cb(gpointer ptr)
entry = queue_get_entries(data->io_channels);
for (; entry; entry = entry->next) {
- GIOChannel *io = entry->data;
+ struct tester_io *io = entry->data;
- if (!defer_accept(data, g_io_channel_ref(io), cb)) {
- g_io_channel_unref(io);
+ if (!defer_accept(data, tester_io_ref(io), cb)) {
+ tester_io_unref(io);
tester_warn("Unable to accept deferred setup");
tester_test_failed();
break;
@@ -3388,39 +3401,39 @@ static gboolean ext_flowctl_nval_conn_req_ready_cb(gpointer ptr)
cb = NULL;
}
- return FALSE;
+ return false;
}
-static gboolean ext_flowctl_nval_conn_req_listen_cb(GIOChannel *io,
- GIOCondition cond, gpointer user_data)
+static bool ext_flowctl_nval_conn_req_listen_cb(struct tester_io *io,
+ uint32_t cond, void *user_data)
{
struct test_data *data = tester_get_data();
int sk, new_sk;
- GIOChannel *new_io;
+ struct tester_io *new_io;
if (!data->step)
- return FALSE;
+ return false;
- sk = g_io_channel_unix_get_fd(io);
+ sk = tester_io_get_fd(io);
new_sk = accept(sk, NULL, NULL);
if (new_sk < 0) {
tester_warn("accept failed: %s (%u)", strerror(errno), errno);
tester_test_failed();
- return FALSE;
+ return false;
}
tester_print("Accept sk = %d", new_sk);
- new_io = g_io_channel_unix_new(new_sk);
- g_io_channel_set_close_on_unref(new_io, TRUE);
+ new_io = tester_io_new(new_sk);
+ tester_io_set_close_on_unref(new_io, true);
if (!data->io_channels)
data->io_channels = queue_new();
queue_push_tail(data->io_channels, new_io);
if (data->err_io_id) {
- g_source_remove(data->err_io_id);
+ tester_remove_source(data->err_io_id);
data->err_io_id = 0;
}
@@ -3429,13 +3442,13 @@ static gboolean ext_flowctl_nval_conn_req_listen_cb(GIOChannel *io,
if (!data->step) {
data->io_id = 0;
ext_flowctl_nval_conn_req_ready_cb(NULL);
- return FALSE;
+ return false;
}
- data->err_io_id = g_timeout_add(1000,
- ext_flowctl_nval_conn_req_ready_cb, NULL);
+ data->err_io_id = timeout_add(1000,
+ ext_flowctl_nval_conn_req_ready_cb, NULL, NULL);
- return TRUE;
+ return true;
}
static void ext_flowctl_nval_conn_req_conn_cb(uint16_t handle, void *user_data)
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 26/27] doc/virtio: Update measurements of the testers
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (24 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 25/27] tools: Run l2cap-tester and iso-tester " Luiz Augusto von Dentz
@ 2026-10-22 15:46 ` Luiz Augusto von Dentz
2026-10-22 15:47 ` [PATCH BlueZ v1 27/27] doc: Split tester.config for QEMU and User Mode Linux Luiz Augusto von Dentz
2026-10-29 18:40 ` [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel patchwork-bot+bluetooth
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Update the wall clock time of the kernel testers, now that they run on
the mainloop, which unlike GLib does not read the clock, a system call
through the host under User Mode Linux, on every iteration.
Assisted-by: Claude:claude-opus-5-5
---
doc/virtio.rst | 22 ++++++++++++++--------
1 file changed, 14 insertions(+), 8 deletions(-)
diff --git a/doc/virtio.rst b/doc/virtio.rst
index e1148a50c797..36a90e01de4b 100644
--- a/doc/virtio.rst
+++ b/doc/virtio.rst
@@ -375,14 +375,14 @@ version and machine, User Mode Linux with seccomp, with and without
**-T**, all tests passing::
tester tests KVM UML UML -T
- mgmt-tester 514 83.6 111.6 42.6
- l2cap-tester 111 9.6 13.7 10.2
- iso-tester 141 13.7 24.3 16.3
- sco-tester 30 5.1 4.1 3.6
- rfcomm-tester 11 3.6 2.0 2.6
- smp-tester 8 4.1 1.5 1.5
- ioctl-tester 28 4.1 2.6 3.1
- userchan-tester 4 2.6 1.5 1.5
+ mgmt-tester 514 78.2 94.0 20.3
+ l2cap-tester 111 9.2 10.2 5.6
+ iso-tester 141 8.1 11.7 7.6
+ sco-tester 30 4.6 3.1 2.0
+ rfcomm-tester 11 3.6 1.5 1.5
+ smp-tester 8 3.6 1.5 1.0
+ ioctl-tester 28 4.1 2.1 1.5
+ userchan-tester 4 2.6 1.0 1.0
bnep-tester 1 2.6 1.0 1.0
UML runs the guest's system calls and context switches considerably slower
@@ -391,6 +391,12 @@ small messages are slower without time travel. With it, tests that wait,
like most of mgmt-tester, take no real time for timers and timeouts. Small
testers are faster under UML, which starts in a fraction of a second.
+Under UML, reading the clock is a system call through the host too, about
+6 us against 0.02 us with KVM, where the vDSO answers it. GLib's main loop
+reads it for every source on every iteration, so testers run on the
+mainloop of ``src/shared/mainloop.c`` instead, which waits with
+``epoll_wait()`` and one ``timerfd`` per timer and does not read the clock.
+
Time travel helps most when tests wait: timers, timeouts and idle time take
no real time, and the timing is the same on every run. It also lets
controllers outside the guests, such as btvirt's or a firmware simulation,
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH BlueZ v1 27/27] doc: Split tester.config for QEMU and User Mode Linux
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (25 preceding siblings ...)
2026-10-22 15:46 ` [PATCH BlueZ v1 26/27] doc/virtio: Update measurements of the testers Luiz Augusto von Dentz
@ 2026-10-22 15:47 ` Luiz Augusto von Dentz
2026-10-29 18:40 ` [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel patchwork-bot+bluetooth
27 siblings, 0 replies; 30+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-22 15:47 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Rename tester.config to tester.kvm.config, keeping tester.config as a
link to it for existing users, and add tester.uml.config for a User Mode
Linux kernel, which uses hostfs and virtio_uml instead of the QEMU
devices and needs time travel support for test-runner -T.
Both build every Bluetooth protocol and device driver, with the buses
and subsystems they depend on and COMPILE_TEST, so a tester kernel also
verifies that they all build, and a single User Mode Linux kernel can
verify the drivers and run the testers. CONFIG_BT_HS, which no longer
exists, is removed. CONFIG_BT_INTEL_PCIE depends on ACPI, which User Mode
Linux does not have, unless built with COMPILE_TEST in kernels allowing
it.
Document each configuration in test-runner.
Assisted-by: Claude:claude-opus-5-5
---
doc/test-functional.rst | 4 +-
doc/test-runner.rst | 118 +++++++++++++++++++++++++++-----
doc/tester.config | 119 +--------------------------------
doc/tester.kvm.config | 144 ++++++++++++++++++++++++++++++++++++++++
doc/tester.uml.config | 120 +++++++++++++++++++++++++++++++++
5 files changed, 370 insertions(+), 135 deletions(-)
mode change 100644 => 120000 doc/tester.config
create mode 100644 doc/tester.kvm.config
create mode 100644 doc/tester.uml.config
diff --git a/doc/test-functional.rst b/doc/test-functional.rst
index e2fcf34138b3..917653bfef5a 100644
--- a/doc/test-functional.rst
+++ b/doc/test-functional.rst
@@ -90,7 +90,7 @@ The following additional options apply:
``--kernel=<image>``
Kernel image (or built Linux source tree root) to
- use. See **test-runner(1)** and `tester.config` for required
+ use. See **test-runner(1)** and `tester.kvm.config` for required
kernel config.
If not provided, value from `FUNCTIONAL_TESTING_KERNEL`
@@ -300,7 +300,7 @@ Simplest setup is
.. code-block::
- cp ../bluez/doc/tester.config .config
+ cp ../bluez/doc/tester.kvm.config .config
make olddefconfig
make -j8
diff --git a/doc/test-runner.rst b/doc/test-runner.rst
index 02d69b06f54c..e59a2dd141aa 100644
--- a/doc/test-runner.rst
+++ b/doc/test-runner.rst
@@ -143,8 +143,8 @@ User Mode Linux
When ``-k`` is a User Mode Linux (UML) binary, recognized by its
``.uml.setup.init`` section, test-runner runs it directly instead of QEMU,
with the host filesystem mounted as root through hostfs, and powers it off
-at the end since rebooting UML starts it again. The kernel needs
-``CONFIG_HOSTFS``, and ``CONFIG_VIRTIO_UML`` for virtio devices.
+at the end since rebooting UML starts it again. See `User Mode Linux kernel`_
+for its configuration.
``-o`` then adds kernel parameters, which is how UML attaches devices served
from the host, e.g. ``virtio_uml.device=<socket>:40`` for a virtio
@@ -162,16 +162,50 @@ the guest sees the same timings on every run. This needs a kernel with
Kernel
======
-The test-runner tool requires a kernel that is at least build with these
-minimal options for a successful boot. These options should be installed as
-.config in the kernel source directory followed by:
+Two configuration fragments with everything the testers need are provided:
+``doc/tester.kvm.config`` for a kernel run with QEMU and KVM, and
+``doc/tester.uml.config`` for a User Mode Linux kernel. ``doc/tester.config``
+is the same as ``doc/tester.kvm.config``. The options they contain are
+described below.
+
+Both also build every Bluetooth protocol and device driver into the kernel,
+with the buses and subsystems they depend on, so a tester kernel verifies
+that they all build, even those the testers do not use, and those for other
+platforms with ``CONFIG_COMPILE_TEST``:
.. code-block::
- make olddefconfig
+ CONFIG_COMPILE_TEST=y
+ CONFIG_USB_SUPPORT=y
+ CONFIG_USB=y
+ CONFIG_MMC=y
+ CONFIG_SERIAL_DEV_BUS=y
+ CONFIG_SERIAL_DEV_CTRL_TTYPORT=y
+ CONFIG_GPIOLIB=y
+ CONFIG_PM=y
+ CONFIG_NVMEM=y
+ CONFIG_POWER_SEQUENCING=y
+ CONFIG_HID=y
+ CONFIG_NEW_LEDS=y
+ CONFIG_LEDS_CLASS=y
-After that a default kernel with the required options can be built. More
-option (like the Bluetooth subsystem) can be enabled on top of this.
+QEMU and KVM kernel
+-------------------
+
+With QEMU, the kernel is a bzImage, and test-runner reaches the guest
+through virtio devices: the console, the shared host filesystem
+(virtio-fs or 9p) and the emulated serial ports of ``-u``. Build it with
+``doc/tester.kvm.config`` installed as .config in the kernel source
+directory:
+
+.. code-block::
+
+ cp <bluez>/doc/tester.kvm.config .config
+ make olddefconfig
+ make
+
+The minimal options for a successful boot are the following, on top of
+which more options, like the Bluetooth subsystem, can be enabled:
.. code-block::
@@ -192,7 +226,7 @@ option (like the Bluetooth subsystem) can be enabled on top of this.
CONFIG_SERIAL_8250=y
CONFIG_SERIAL_8250_CONSOLE=y
- CONFIG_SERIAL_8250_PCI=yCONFIG_DEBUG_KERNEL=y
+ CONFIG_SERIAL_8250_PCI=y
CONFIG_SERIAL_8250_NR_UARTS=4
CONFIG_TMPFS=y
@@ -205,6 +239,60 @@ option (like the Bluetooth subsystem) can be enabled on top of this.
Filesystem passthrough uses virtio-fs when ``virtiofsd`` is installed on the
host, otherwise 9p. Use ``-Fno`` for kernels without ``CONFIG_VIRTIO_FS``.
+The controllers that can be passed through to the guest, see the USB and
+PCIe passthrough examples, also need the host controller drivers of
+``doc/tester.kvm.config``, and ``CONFIG_PCI`` and ``CONFIG_ACPI`` for the
+PCIe ones.
+
+User Mode Linux kernel
+----------------------
+
+A User Mode Linux kernel is a program run on the host, built with
+``ARCH=um``, and has no PCI, USB or serial devices: the host filesystem is
+mounted with hostfs, controllers are emulated inside the guest through
+``/dev/vhci`` or served from the host as virtio devices with
+``virtio_uml``, and **-T** needs time travel support. Build it from the
+default configuration with ``doc/tester.uml.config`` merged in:
+
+.. code-block::
+
+ make ARCH=um defconfig
+ scripts/kconfig/merge_config.sh -m .config <bluez>/doc/tester.uml.config
+ make ARCH=um olddefconfig
+ make ARCH=um linux
+
+The resulting ``linux`` binary is given to test-runner with ``-k``. The
+options specific to User Mode Linux are the following, with the Bluetooth
+and debugging ones below in addition:
+
+.. code-block::
+
+ CONFIG_HOSTFS=y
+ CONFIG_VIRTIO_UML=y
+ CONFIG_UML_PCI_OVER_VIRTIO=y
+ CONFIG_UML_TIME_TRAVEL_SUPPORT=y
+
+ CONFIG_NET=y
+ CONFIG_INET=y
+ CONFIG_IPV6=y
+
+ CONFIG_TMPFS=y
+ CONFIG_DEVTMPFS=y
+ CONFIG_DEBUG_FS=y
+
+ CONFIG_BT_HCIVHCI=y
+ CONFIG_BT_VIRTIO=y
+
+The User Mode Linux default configuration does not enable IPv6, which
+6lowpan-tester needs. ``CONFIG_UML_PCI_OVER_VIRTIO`` provides the PCI bus
+for the PCI drivers to build. User Mode Linux has no ACPI, so
+``CONFIG_BT_INTEL_PCIE`` is only built with ``CONFIG_COMPILE_TEST``, in
+kernels where it does not depend on ACPI otherwise.
+
+As it builds every driver and runs the testers, a single User Mode Linux
+kernel can verify both; a QEMU kernel is only needed for what QEMU
+provides, such as passing controllers through to the guest.
+
Bluetooth
---------
@@ -268,7 +356,8 @@ options may be useful:
Other
-----
-For tests requiring accurate time inside the VM, possible with KVM:
+For tests requiring accurate time inside the VM, possible with KVM, part of
+``doc/tester.kvm.config``:
.. code-block::
@@ -414,15 +503,14 @@ interrupt delivery.
Running mgmt-tester under User Mode Linux
-----------------------------------------
+With a kernel built from ``doc/tester.uml.config``, see
+`User Mode Linux kernel`_:
+
.. code-block::
- $ make ARCH=um defconfig
- $ scripts/kconfig/merge_config.sh -m .config <bluez>/doc/tester.config
- $ make ARCH=um olddefconfig linux
$ tools/test-runner -k <linux>/linux -- tools/mgmt-tester
-On simulated time, with a kernel that also has
-``CONFIG_UML_TIME_TRAVEL_SUPPORT=y``:
+On simulated time:
.. code-block::
diff --git a/doc/tester.config b/doc/tester.config
deleted file mode 100644
index 58b7c592335c..000000000000
--- a/doc/tester.config
+++ /dev/null
@@ -1,118 +0,0 @@
-CONFIG_PCI=y
-CONFIG_VIRTIO=y
-CONFIG_VIRTIO_PCI=y
-CONFIG_VIRTIO_CONSOLE=y
-CONFIG_VIRTIO_FS=y
-CONFIG_FUSE_FS=y
-
-CONFIG_HYPERVISOR_GUEST=y
-CONFIG_PARAVIRT=y
-CONFIG_KVM_GUEST=y
-
-CONFIG_PTP_1588_CLOCK=y
-CONFIG_PTP_1588_CLOCK_KVM=y
-CONFIG_PTP_1588_CLOCK_VMCLOCK=y
-
-CONFIG_NET=y
-CONFIG_INET=y
-CONFIG_IPV6=y
-
-CONFIG_NET_9P=y
-CONFIG_NET_9P_VIRTIO=y
-
-CONFIG_9P_FS=y
-CONFIG_9P_FS_POSIX_ACL=y
-
-CONFIG_SERIAL_8250=y
-CONFIG_SERIAL_8250_CONSOLE=y
-CONFIG_SERIAL_8250_PCI=y
-CONFIG_SERIAL_8250_NR_UARTS=4
-
-CONFIG_TMPFS=y
-CONFIG_TMPFS_POSIX_ACL=y
-CONFIG_TMPFS_XATTR=y
-
-CONFIG_DEVTMPFS=y
-CONFIG_DEBUG_FS=y
-
-CONFIG_BT=y
-CONFIG_BT_BREDR=y
-CONFIG_BT_RFCOMM=y
-CONFIG_BT_BNEP=y
-CONFIG_BT_HIDP=y
-CONFIG_BT_LE=y
-CONFIG_BT_HS=y
-CONFIG_BT_MSFTEXT=y
-CONFIG_BT_AOSPEXT=y
-CONFIG_BT_FEATURE_DEBUG=y
-
-CONFIG_CRYPTO_AES=y
-CONFIG_CRYPTO_CMAC=y
-CONFIG_CRYPTO_USER_API=y
-CONFIG_CRYPTO_USER_API_HASH=y
-CONFIG_CRYPTO_USER_API_SKCIPHER=y
-
-CONFIG_UNIX=y
-
-CONFIG_UHID=y
-CONFIG_HIDRAW=y
-
-CONFIG_DEBUG_KERNEL=y
-CONFIG_LOCKDEP_SUPPORT=y
-CONFIG_DEBUG_SPINLOCK=y
-CONFIG_DEBUG_LOCK_ALLOC=y
-CONFIG_DEBUG_ATOMIC_SLEEP=y
-CONFIG_PROVE_LOCKING=y
-CONFIG_PROVE_RCU=y
-CONFIG_LOCKDEP=y
-CONFIG_DEBUG_MUTEXES=y
-CONFIG_KASAN=y
-
-CONFIG_USB=y
-CONFIG_USB_XHCI_HCD=y
-CONFIG_USB_XHCI_PLATFORM=y
-
-CONFIG_SERIAL_DEV_BUS=y
-
-CONFIG_6LOWPAN=y
-CONFIG_6LOWPAN_DEBUGFS=y
-CONFIG_BT_6LOWPAN=y
-CONFIG_PACKET=y
-
-#
-# Bluetooth device drivers
-#
-CONFIG_BT_INTEL=y
-CONFIG_BT_BCM=y
-CONFIG_BT_RTL=y
-CONFIG_BT_QCA=y
-CONFIG_BT_MTK=y
-CONFIG_BT_HCIBTUSB=y
-CONFIG_BT_HCIBTUSB_POLL_SYNC=y
-CONFIG_BT_HCIBTUSB_BCM=y
-CONFIG_BT_HCIBTUSB_MTK=y
-CONFIG_BT_HCIBTUSB_RTL=y
-CONFIG_BT_HCIUART=y
-CONFIG_BT_HCIUART_SERDEV=y
-CONFIG_BT_HCIUART_H4=y
-CONFIG_BT_HCIUART_BCSP=y
-CONFIG_BT_HCIUART_ATH3K=y
-CONFIG_BT_HCIUART_LL=y
-CONFIG_BT_HCIUART_3WIRE=y
-CONFIG_BT_HCIUART_QCA=y
-CONFIG_BT_HCIUART_AG6XX=y
-CONFIG_BT_HCIUART_MRVL=y
-CONFIG_BT_HCIUART_AML=y
-CONFIG_BT_HCIBCM203X=y
-CONFIG_BT_HCIBCM4377=y
-CONFIG_BT_HCIBPA10X=y
-CONFIG_BT_HCIBFUSB=y
-CONFIG_BT_HCIVHCI=y
-CONFIG_BT_MRVL=y
-CONFIG_BT_ATH3K=y
-CONFIG_BT_MTKSDIO=y
-CONFIG_BT_MTKUART=y
-CONFIG_BT_VIRTIO=y
-CONFIG_BT_NXPUART=y
-CONFIG_BT_INTEL_PCIE=y
-# end of Bluetooth device drivers
diff --git a/doc/tester.config b/doc/tester.config
new file mode 120000
index 000000000000..9f68726afd0a
--- /dev/null
+++ b/doc/tester.config
@@ -0,0 +1 @@
+tester.kvm.config
\ No newline at end of file
diff --git a/doc/tester.kvm.config b/doc/tester.kvm.config
new file mode 100644
index 000000000000..d8066f78aade
--- /dev/null
+++ b/doc/tester.kvm.config
@@ -0,0 +1,144 @@
+CONFIG_PCI=y
+CONFIG_ACPI=y
+CONFIG_VIRTIO=y
+CONFIG_VIRTIO_PCI=y
+CONFIG_VIRTIO_CONSOLE=y
+CONFIG_VIRTIO_FS=y
+CONFIG_FUSE_FS=y
+
+CONFIG_HYPERVISOR_GUEST=y
+CONFIG_PARAVIRT=y
+CONFIG_KVM_GUEST=y
+
+CONFIG_PTP_1588_CLOCK=y
+CONFIG_PTP_1588_CLOCK_KVM=y
+CONFIG_PTP_1588_CLOCK_VMCLOCK=y
+
+CONFIG_NET=y
+CONFIG_INET=y
+CONFIG_IPV6=y
+
+CONFIG_NET_9P=y
+CONFIG_NET_9P_VIRTIO=y
+
+CONFIG_9P_FS=y
+CONFIG_9P_FS_POSIX_ACL=y
+
+CONFIG_SERIAL_8250=y
+CONFIG_SERIAL_8250_CONSOLE=y
+CONFIG_SERIAL_8250_PCI=y
+CONFIG_SERIAL_8250_NR_UARTS=4
+
+CONFIG_TMPFS=y
+CONFIG_TMPFS_POSIX_ACL=y
+CONFIG_TMPFS_XATTR=y
+
+CONFIG_DEVTMPFS=y
+CONFIG_DEBUG_FS=y
+
+CONFIG_BT=y
+CONFIG_BT_BREDR=y
+CONFIG_BT_RFCOMM=y
+CONFIG_BT_RFCOMM_TTY=y
+CONFIG_BT_BNEP=y
+CONFIG_BT_BNEP_MC_FILTER=y
+CONFIG_BT_BNEP_PROTO_FILTER=y
+CONFIG_BT_HIDP=y
+CONFIG_BT_LE=y
+CONFIG_BT_LE_L2CAP_ECRED=y
+CONFIG_BT_LEDS=y
+CONFIG_BT_MSFTEXT=y
+CONFIG_BT_AOSPEXT=y
+CONFIG_BT_DEBUGFS=y
+CONFIG_BT_SELFTEST=y
+CONFIG_BT_SELFTEST_ECDH=y
+CONFIG_BT_SELFTEST_SMP=y
+CONFIG_BT_FEATURE_DEBUG=y
+
+CONFIG_CRYPTO_AES=y
+CONFIG_CRYPTO_CMAC=y
+CONFIG_CRYPTO_USER_API=y
+CONFIG_CRYPTO_USER_API_HASH=y
+CONFIG_CRYPTO_USER_API_SKCIPHER=y
+
+CONFIG_UNIX=y
+
+CONFIG_HID=y
+CONFIG_UHID=y
+CONFIG_HIDRAW=y
+
+CONFIG_NEW_LEDS=y
+CONFIG_LEDS_CLASS=y
+
+CONFIG_DEBUG_KERNEL=y
+CONFIG_LOCKDEP_SUPPORT=y
+CONFIG_DEBUG_SPINLOCK=y
+CONFIG_DEBUG_LOCK_ALLOC=y
+CONFIG_DEBUG_ATOMIC_SLEEP=y
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_LOCKDEP=y
+CONFIG_DEBUG_MUTEXES=y
+CONFIG_KASAN=y
+
+CONFIG_6LOWPAN=y
+CONFIG_6LOWPAN_DEBUGFS=y
+CONFIG_BT_6LOWPAN=y
+CONFIG_PACKET=y
+
+#
+# Buses and subsystems the Bluetooth device drivers depend on
+#
+CONFIG_COMPILE_TEST=y
+CONFIG_USB_SUPPORT=y
+CONFIG_USB=y
+CONFIG_MMC=y
+CONFIG_SERIAL_DEV_BUS=y
+CONFIG_SERIAL_DEV_CTRL_TTYPORT=y
+CONFIG_GPIOLIB=y
+CONFIG_PM=y
+CONFIG_NVMEM=y
+CONFIG_POWER_SEQUENCING=y
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_XHCI_PLATFORM=y
+
+#
+# Bluetooth device drivers
+#
+CONFIG_BT_HCIBTUSB=y
+CONFIG_BT_HCIBTUSB_POLL_SYNC=y
+CONFIG_BT_HCIBTUSB_BCM=y
+CONFIG_BT_HCIBTUSB_MTK=y
+CONFIG_BT_HCIBTUSB_RTL=y
+CONFIG_BT_HCIBTUSB_QCOM=y
+CONFIG_BT_HCIBTSDIO=y
+CONFIG_BT_HCIUART=y
+CONFIG_BT_HCIUART_SERDEV=y
+CONFIG_BT_HCIUART_H4=y
+CONFIG_BT_HCIUART_NOKIA=y
+CONFIG_BT_HCIUART_BCSP=y
+CONFIG_BT_HCIUART_ATH3K=y
+CONFIG_BT_HCIUART_LL=y
+CONFIG_BT_HCIUART_3WIRE=y
+CONFIG_BT_HCIUART_INTEL=y
+CONFIG_BT_HCIUART_BCM=y
+CONFIG_BT_HCIUART_RTL=y
+CONFIG_BT_HCIUART_QCA=y
+CONFIG_BT_HCIUART_AG6XX=y
+CONFIG_BT_HCIUART_MRVL=y
+CONFIG_BT_HCIUART_AML=y
+CONFIG_BT_HCIBCM203X=y
+CONFIG_BT_HCIBCM4377=y
+CONFIG_BT_HCIBPA10X=y
+CONFIG_BT_HCIBFUSB=y
+CONFIG_BT_HCIVHCI=y
+CONFIG_BT_MRVL=y
+CONFIG_BT_MRVL_SDIO=y
+CONFIG_BT_ATH3K=y
+CONFIG_BT_MTKSDIO=y
+CONFIG_BT_MTKUART=y
+CONFIG_BT_VIRTIO=y
+CONFIG_BT_NXPUART=y
+CONFIG_BT_INTEL_PCIE=y
+CONFIG_BT_QCOMSMD=y
+# end of Bluetooth device drivers
diff --git a/doc/tester.uml.config b/doc/tester.uml.config
new file mode 100644
index 000000000000..f7ff4eb64b45
--- /dev/null
+++ b/doc/tester.uml.config
@@ -0,0 +1,120 @@
+CONFIG_HOSTFS=y
+CONFIG_VIRTIO_UML=y
+CONFIG_UML_PCI_OVER_VIRTIO=y
+CONFIG_UML_TIME_TRAVEL_SUPPORT=y
+
+CONFIG_NET=y
+CONFIG_INET=y
+CONFIG_IPV6=y
+
+CONFIG_TMPFS=y
+CONFIG_TMPFS_POSIX_ACL=y
+CONFIG_TMPFS_XATTR=y
+
+CONFIG_DEVTMPFS=y
+CONFIG_DEBUG_FS=y
+
+CONFIG_BT=y
+CONFIG_BT_BREDR=y
+CONFIG_BT_RFCOMM=y
+CONFIG_BT_RFCOMM_TTY=y
+CONFIG_BT_BNEP=y
+CONFIG_BT_BNEP_MC_FILTER=y
+CONFIG_BT_BNEP_PROTO_FILTER=y
+CONFIG_BT_HIDP=y
+CONFIG_BT_LE=y
+CONFIG_BT_LE_L2CAP_ECRED=y
+CONFIG_BT_LEDS=y
+CONFIG_BT_MSFTEXT=y
+CONFIG_BT_AOSPEXT=y
+CONFIG_BT_DEBUGFS=y
+CONFIG_BT_SELFTEST=y
+CONFIG_BT_SELFTEST_ECDH=y
+CONFIG_BT_SELFTEST_SMP=y
+CONFIG_BT_FEATURE_DEBUG=y
+
+CONFIG_CRYPTO_AES=y
+CONFIG_CRYPTO_CMAC=y
+CONFIG_CRYPTO_USER_API=y
+CONFIG_CRYPTO_USER_API_HASH=y
+CONFIG_CRYPTO_USER_API_SKCIPHER=y
+
+CONFIG_UNIX=y
+
+CONFIG_HID=y
+CONFIG_UHID=y
+CONFIG_HIDRAW=y
+
+CONFIG_NEW_LEDS=y
+CONFIG_LEDS_CLASS=y
+
+CONFIG_DEBUG_KERNEL=y
+CONFIG_LOCKDEP_SUPPORT=y
+CONFIG_DEBUG_SPINLOCK=y
+CONFIG_DEBUG_LOCK_ALLOC=y
+CONFIG_DEBUG_ATOMIC_SLEEP=y
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_LOCKDEP=y
+CONFIG_DEBUG_MUTEXES=y
+CONFIG_KASAN=y
+
+CONFIG_6LOWPAN=y
+CONFIG_6LOWPAN_DEBUGFS=y
+CONFIG_BT_6LOWPAN=y
+CONFIG_PACKET=y
+
+#
+# Buses and subsystems the Bluetooth device drivers depend on
+#
+CONFIG_COMPILE_TEST=y
+CONFIG_USB_SUPPORT=y
+CONFIG_USB=y
+CONFIG_MMC=y
+CONFIG_SERIAL_DEV_BUS=y
+CONFIG_SERIAL_DEV_CTRL_TTYPORT=y
+CONFIG_GPIOLIB=y
+CONFIG_PM=y
+CONFIG_NVMEM=y
+CONFIG_POWER_SEQUENCING=y
+
+#
+# Bluetooth device drivers
+#
+CONFIG_BT_HCIBTUSB=y
+CONFIG_BT_HCIBTUSB_POLL_SYNC=y
+CONFIG_BT_HCIBTUSB_BCM=y
+CONFIG_BT_HCIBTUSB_MTK=y
+CONFIG_BT_HCIBTUSB_RTL=y
+CONFIG_BT_HCIBTUSB_QCOM=y
+CONFIG_BT_HCIBTSDIO=y
+CONFIG_BT_HCIUART=y
+CONFIG_BT_HCIUART_SERDEV=y
+CONFIG_BT_HCIUART_H4=y
+CONFIG_BT_HCIUART_NOKIA=y
+CONFIG_BT_HCIUART_BCSP=y
+CONFIG_BT_HCIUART_ATH3K=y
+CONFIG_BT_HCIUART_LL=y
+CONFIG_BT_HCIUART_3WIRE=y
+CONFIG_BT_HCIUART_INTEL=y
+CONFIG_BT_HCIUART_BCM=y
+CONFIG_BT_HCIUART_RTL=y
+CONFIG_BT_HCIUART_QCA=y
+CONFIG_BT_HCIUART_AG6XX=y
+CONFIG_BT_HCIUART_MRVL=y
+CONFIG_BT_HCIUART_AML=y
+CONFIG_BT_HCIBCM203X=y
+CONFIG_BT_HCIBCM4377=y
+CONFIG_BT_HCIBPA10X=y
+CONFIG_BT_HCIBFUSB=y
+CONFIG_BT_HCIVHCI=y
+CONFIG_BT_MRVL=y
+CONFIG_BT_MRVL_SDIO=y
+CONFIG_BT_ATH3K=y
+CONFIG_BT_MTKSDIO=y
+CONFIG_BT_MTKUART=y
+CONFIG_BT_VIRTIO=y
+CONFIG_BT_NXPUART=y
+CONFIG_BT_QCOMSMD=y
+CONFIG_BT_INTEL_PCIE=y
+# end of Bluetooth device drivers
--
2.56.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* RE: Run the testers under User Mode Linux with time travel
2026-10-22 15:46 ` [PATCH BlueZ v1 01/27] tools/test-runner: Do not report filesystems already mounted Luiz Augusto von Dentz
@ 2026-10-22 20:02 ` bluez.test.bot
0 siblings, 0 replies; 30+ messages in thread
From: bluez.test.bot @ 2026-10-22 20:02 UTC (permalink / raw)
To: linux-bluetooth, luiz.dentz
[-- Attachment #1: Type: text/plain, Size: 6880 bytes --]
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/series/1191414/
---Test result---
Test Summary:
CheckPatch FAIL 18.23 seconds
GitLint FAIL 9.50 seconds
BuildEll PASS 19.55 seconds
BluezMake PASS 301.82 seconds
MakeCheck PASS 14.53 seconds
MakeDistcheck PASS 144.54 seconds
CheckValgrind PASS 237.04 seconds
CheckSmatch WARNING 298.70 seconds
bluezmakeextell PASS 96.09 seconds
TestFunctional PASS 1154.77 seconds
IncrementalBuild PASS 635.83 seconds
ScanBuild PASS 1186.64 seconds
Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script
Output:
[BlueZ,v1,11/27] emulator: Add vhost-user backend
WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#258: FILE: emulator/vhost.c:87:
+} __attribute__((packed));
WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#263: FILE: emulator/vhost.c:92:
+} __attribute__((packed));
WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#272: FILE: emulator/vhost.c:101:
+} __attribute__((packed));
WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#279: FILE: emulator/vhost.c:108:
+} __attribute__((packed));
WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#285: FILE: emulator/vhost.c:114:
+} __attribute__((packed));
WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#292: FILE: emulator/vhost.c:121:
+} __attribute__((packed));
WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#303: FILE: emulator/vhost.c:132:
+} __attribute__((packed));
WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#310: FILE: emulator/vhost.c:139:
+} __attribute__((packed));
WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#316: FILE: emulator/vhost.c:145:
+} __attribute__((packed));
WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#321: FILE: emulator/vhost.c:150:
+} __attribute__((packed));
WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#327: FILE: emulator/vhost.c:156:
+} __attribute__((packed));
/home/runner/work/bluez/bluez/src/patch/14908110.patch total: 0 errors, 11 warnings, 1094 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/home/runner/work/bluez/bluez/src/patch/14908110.patch has style problems, please review.
NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
[BlueZ,v1,12/27] emulator: Add vhost-user virtio server to btvirt
WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#368: FILE: emulator/virtio.c:48:
+} __attribute__((packed));
/home/runner/work/bluez/bluez/src/patch/14908117.patch total: 0 errors, 1 warnings, 491 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/home/runner/work/bluez/bluez/src/patch/14908117.patch has style problems, please review.
NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
[BlueZ,v1,13/27] emulator: Add time travel calendar to btvirt
WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#474: FILE: emulator/timetravel.c:54:
+} __attribute__((packed));
/home/runner/work/bluez/bluez/src/patch/14908111.patch total: 0 errors, 1 warnings, 1088 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/home/runner/work/bluez/bluez/src/patch/14908111.patch has style problems, please review.
NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
[BlueZ,v1,14/27] emulator: Add virtio console and time travel client to btvirt
WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#345: FILE: emulator/ttclient.c:43:
+} __attribute__((packed));
WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#737: FILE: emulator/vconsole.c:60:
+} __attribute__((packed));
WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#743: FILE: emulator/vconsole.c:66:
+} __attribute__((packed));
/home/runner/work/bluez/bluez/src/patch/14908112.patch total: 0 errors, 3 warnings, 1221 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/home/runner/work/bluez/bluez/src/patch/14908112.patch has style problems, please review.
NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
##############################
Test: GitLint - FAIL
Desc: Run gitlint
Output:
[BlueZ,v1,24/27] tools: Run sco-tester, rfcomm-tester and 6lowpan-tester on the mainloop
1: T1 Title exceeds max length (88>80): "[BlueZ,v1,24/27] tools: Run sco-tester, rfcomm-tester and 6lowpan-tester on the mainloop"
##############################
Test: CheckSmatch - WARNING
Desc: Run smatch tool with source
Output:
tools/sco-tester.c: note: in included file:./lib/bluetooth/bluetooth.h:232:15: warning: array of flexible structures./lib/bluetooth/bluetooth.h:237:31: warning: array of flexible structures
https://github.com/bluez/bluez/pull/2701
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
` (26 preceding siblings ...)
2026-10-22 15:47 ` [PATCH BlueZ v1 27/27] doc: Split tester.config for QEMU and User Mode Linux Luiz Augusto von Dentz
@ 2026-10-29 18:40 ` patchwork-bot+bluetooth
27 siblings, 0 replies; 30+ messages in thread
From: patchwork-bot+bluetooth @ 2026-10-29 18:40 UTC (permalink / raw)
To: Luiz Augusto von Dentz; +Cc: linux-bluetooth
Hello:
This series was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
On Thu, 22 Oct 2026 11:46:33 -0400 you wrote:
> From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
>
> This makes test-runner run the kernel testers in a User Mode Linux guest
> as well as with QEMU/KVM, and with -T on a simulated clock: btvirt is the
> guest's time travel calendar, so timers and timeouts take no real time
> and timing is the same on every run. btvirt can also serve controllers to
> UML guests as vhost-user virtio devices sharing the calendar, e.g. for
> firmware simulators.
>
> [...]
Here is the summary with links:
- [BlueZ,v1,01/27] tools/test-runner: Do not report filesystems already mounted
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=01a2794c5b5e
- [BlueZ,v1,02/27] shared/tester: Time out teardown
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=d32cf1e77713
- [BlueZ,v1,03/27] mgmt-tester: Wait for LL Privacy setup to complete
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=a2b5b7c7b756
- [BlueZ,v1,04/27] iso-tester: Fix leaking socket watches
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=9cd93aa69d97
- [BlueZ,v1,05/27] l2cap-tester: Fix race in LE Client - Close socket 2
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=a10a29556ce8
- [BlueZ,v1,06/27] iso-tester: Fix busy waiting in PAST tests
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=9b3856a85336
- [BlueZ,v1,07/27] doc: Enable IPv6 in tester.config
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=2362c4224ba9
- [BlueZ,v1,08/27] 6lowpan-tester: Find the interface of the controller
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=6cc3ff01132d
- [BlueZ,v1,09/27] emulator: Remove -T option from btvirt
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=ba940f323890
- [BlueZ,v1,10/27] doc: Add btvirt documentation
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=703331bde0eb
- [BlueZ,v1,11/27] emulator: Add vhost-user backend
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=1ce41b67dd13
- [BlueZ,v1,12/27] emulator: Add vhost-user virtio server to btvirt
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=11199825bfdc
- [BlueZ,v1,13/27] emulator: Add time travel calendar to btvirt
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=d7ef45305b15
- [BlueZ,v1,14/27] emulator: Add virtio console and time travel client to btvirt
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=faca01db81d6
- [BlueZ,v1,15/27] tools/test-runner: Add User Mode Linux support
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=a7e9a42a791a
- [BlueZ,v1,16/27] doc: Add virtio documentation
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=fe2d32320fba
- [BlueZ,v1,17/27] shared/mainloop: Add idle callbacks
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=43593026bf84
- [BlueZ,v1,18/27] shared/timeout: Run timeouts of 0 once idle
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=3bce4ad01a01
- [BlueZ,v1,19/27] shared/tester: Remove GLib dependency
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=941e0a26f1f9
- [BlueZ,v1,20/27] emulator/hciemu: Remove GLib dependency
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=60eda0019a8b
- [BlueZ,v1,21/27] tools: Build testers that do not need GLib with the mainloop
(no matching commit)
- [BlueZ,v1,22/27] shared/mainloop: Add deferred callbacks
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=a2b4b1308069
- [BlueZ,v1,23/27] tools: Add socket watches for testers without GLib
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=b0c80a96d0a2
- [BlueZ,v1,24/27] tools: Run sco-tester, rfcomm-tester and 6lowpan-tester on the mainloop
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=e37f17751c75
- [BlueZ,v1,25/27] tools: Run l2cap-tester and iso-tester on the mainloop
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=477642f6b5fa
- [BlueZ,v1,26/27] doc/virtio: Update measurements of the testers
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=14bd6ab65f4a
- [BlueZ,v1,27/27] doc: Split tester.config for QEMU and User Mode Linux
(no matching commit)
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 30+ messages in thread
end of thread, other threads:[~2026-10-29 18:40 UTC | newest]
Thread overview: 30+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 01/27] tools/test-runner: Do not report filesystems already mounted Luiz Augusto von Dentz
2026-10-22 20:02 ` Run the testers under User Mode Linux with time travel bluez.test.bot
2026-10-22 15:46 ` [PATCH BlueZ v1 02/27] shared/tester: Time out teardown Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 03/27] mgmt-tester: Wait for LL Privacy setup to complete Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 04/27] iso-tester: Fix leaking socket watches Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 05/27] l2cap-tester: Fix race in LE Client - Close socket 2 Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 06/27] iso-tester: Fix busy waiting in PAST tests Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 07/27] doc: Enable IPv6 in tester.config Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 08/27] 6lowpan-tester: Find the interface of the controller Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 09/27] emulator: Remove -T option from btvirt Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 10/27] doc: Add btvirt documentation Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 11/27] emulator: Add vhost-user backend Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 12/27] emulator: Add vhost-user virtio server to btvirt Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 13/27] emulator: Add time travel calendar " Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 14/27] emulator: Add virtio console and time travel client " Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 15/27] tools/test-runner: Add User Mode Linux support Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 16/27] doc: Add virtio documentation Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 17/27] shared/mainloop: Add idle callbacks Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 18/27] shared/timeout: Run timeouts of 0 once idle Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 19/27] shared/tester: Remove GLib dependency Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 20/27] emulator/hciemu: " Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 21/27] tools: Build testers that do not need GLib with the mainloop Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 22/27] shared/mainloop: Add deferred callbacks Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 23/27] tools: Add socket watches for testers without GLib Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 24/27] tools: Run sco-tester, rfcomm-tester and 6lowpan-tester on the mainloop Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 25/27] tools: Run l2cap-tester and iso-tester " Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 26/27] doc/virtio: Update measurements of the testers Luiz Augusto von Dentz
2026-10-22 15:47 ` [PATCH BlueZ v1 27/27] doc: Split tester.config for QEMU and User Mode Linux Luiz Augusto von Dentz
2026-10-29 18:40 ` [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel patchwork-bot+bluetooth
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox