Add tests for TX timestamping
v5:
- Add comments & rename send_extra* test data fields
- Rename tester-utils.h -> tester.h
- Use int, not struct so_timestamping for setting SO_TIMESTAMPING,
as CI seems to be running old environment without the struct.
v4:
- Drop BT_NO_ERRQUEUE_POLL test and lib #define
v3:
- BT_NO_ERRQUEUE_POLL experimental flag enable in tests
- Drop tester cmdline patch as it's unrelated
v2:
- L2CAP LE Client tests
- SCO TX timestamping test
- Fix emulator bthost L2CAP LE credits send/recv
- Fix emulator SCO send pkts
- BT_NO_ERRQUEUE_POLL test
- Tester command-line option -n
Pauli Virtanen (7):
lib: add BT_SCM_ERROR
iso-tester: Add tests for TX timestamping
l2cap-tester: Add test for TX timestamping
btdev: set nonzero SCO mtu & max pkt
sco-tester: add TX timestamping test
bthost: handle client L2CAP conn in LE credit based mode
l2cap-tester: add tests for LE Client read/write/tx-timestamping
emulator/btdev.c | 11 ++-
emulator/bthost.c | 205 +++++++++++++++++++++++++++++++++++++------
lib/bluetooth.h | 1 +
tools/iso-tester.c | 182 +++++++++++++++++++++++++++++++++++---
tools/l2cap-tester.c | 148 +++++++++++++++++++++++++++++--
tools/sco-tester.c | 96 +++++++++++++++++++-
tools/tester.h | 163 ++++++++++++++++++++++++++++++++++
7 files changed, 760 insertions(+), 46 deletions(-)
create mode 100644 tools/tester.h
--
2.44.0
Set nonzero max pkt count, so that kernel can transmit data.
The request & accept/reject flow is not emulated yet.
---
emulator/btdev.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/emulator/btdev.c b/emulator/btdev.c
index 0ad6b2793..a63136fad 100644
--- a/emulator/btdev.c
+++ b/emulator/btdev.c
@@ -148,6 +148,8 @@ struct btdev {
uint8_t feat_page_2[8];
uint16_t acl_mtu;
uint16_t acl_max_pkt;
+ uint16_t sco_mtu;
+ uint16_t sco_max_pkt;
uint16_t iso_mtu;
uint16_t iso_max_pkt;
uint8_t country_code;
@@ -653,9 +655,9 @@ static int cmd_read_buffer_size(struct btdev *dev, const void *data,
rsp.status = BT_HCI_ERR_SUCCESS;
rsp.acl_mtu = cpu_to_le16(dev->acl_mtu);
- rsp.sco_mtu = 0;
+ rsp.sco_mtu = cpu_to_le16(dev->sco_mtu);
rsp.acl_max_pkt = cpu_to_le16(dev->acl_max_pkt);
- rsp.sco_max_pkt = cpu_to_le16(0);
+ rsp.sco_max_pkt = cpu_to_le16(dev->sco_max_pkt);
cmd_complete(dev, BT_HCI_CMD_READ_BUFFER_SIZE, &rsp, sizeof(rsp));
@@ -2764,6 +2766,8 @@ static int cmd_enhanced_setup_sync_conn_complete(struct btdev *dev,
goto done;
}
+ /* TODO: HCI_Connection_Request connection flow */
+
cc.status = BT_HCI_ERR_SUCCESS;
memcpy(cc.bdaddr, conn->link->dev->bdaddr, 6);
@@ -7173,6 +7177,9 @@ struct btdev *btdev_create(enum btdev_type type, uint16_t id)
btdev->acl_mtu = 192;
btdev->acl_max_pkt = 1;
+ btdev->sco_mtu = 72;
+ btdev->sco_max_pkt = 1;
+
btdev->iso_mtu = 251;
btdev->iso_max_pkt = 1;
btdev->big_handle = 0xff;
--
2.44.0
Add TX timestamping test utilities in new tools/tester.h, so that they
can be shared between testers.
Add tests:
ISO Send - TX Timestamping
ISO Send - TX Sched Timestamping
ISO Send - TX Msg Timestamping
---
tools/iso-tester.c | 182 ++++++++++++++++++++++++++++++++++++++++++---
tools/tester.h | 163 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 335 insertions(+), 10 deletions(-)
create mode 100644 tools/tester.h
diff --git a/tools/iso-tester.c b/tools/iso-tester.c
index 60afef301..14a2e7d46 100644
--- a/tools/iso-tester.c
+++ b/tools/iso-tester.c
@@ -18,6 +18,9 @@
#include <poll.h>
#include <stdbool.h>
+#include <linux/errqueue.h>
+#include <linux/net_tstamp.h>
+
#include <glib.h>
#include "lib/bluetooth.h"
@@ -34,6 +37,8 @@
#include "src/shared/util.h"
#include "src/shared/queue.h"
+#include "tester.h"
+
#define QOS_IO(_interval, _latency, _sdu, _phy, _rtn) \
{ \
.interval = _interval, \
@@ -462,11 +467,12 @@ struct test_data {
uint16_t handle;
uint16_t acl_handle;
struct queue *io_queue;
- unsigned int io_id[2];
+ unsigned int io_id[3];
uint8_t client_num;
int step;
bool reconnect;
bool suspending;
+ struct tx_tstamp_data tx_ts;
};
struct iso_client_data {
@@ -487,6 +493,22 @@ struct iso_client_data {
size_t base_len;
bool listen_bind;
bool pa_bind;
+
+ /* Enable SO_TIMESTAMPING with these flags */
+ uint32_t so_timestamping;
+
+ /* Enable SO_TIMESTAMPING using CMSG instead of setsockopt() */
+ bool cmsg_timestamping;
+
+ /* Number of additional packets to send, before SO_TIMESTAMPING.
+ * Used to test kernel timestamp TX queue logic.
+ */
+ unsigned int repeat_send_pre_ts;
+
+ /* Number of additional packets to send, after SO_TIMESTAMPING.
+ * Used for testing TX timestamping OPT_ID.
+ */
+ unsigned int repeat_send;
};
typedef bool (*iso_defer_accept_t)(struct test_data *data, GIOChannel *io);
@@ -677,15 +699,14 @@ static void io_free(void *data)
static void test_data_free(void *test_data)
{
struct test_data *data = test_data;
+ unsigned int i;
if (data->io_queue)
queue_destroy(data->io_queue, io_free);
- if (data->io_id[0] > 0)
- g_source_remove(data->io_id[0]);
-
- if (data->io_id[1] > 0)
- g_source_remove(data->io_id[1]);
+ for (i = 0; i < ARRAY_SIZE(data->io_id); ++i)
+ if (data->io_id[i] > 0)
+ g_source_remove(data->io_id[i]);
free(data);
}
@@ -987,6 +1008,38 @@ static const struct iso_client_data connect_16_2_1_send = {
.send = &send_16_2_1,
};
+static const struct iso_client_data connect_send_tx_timestamping = {
+ .qos = QOS_16_2_1,
+ .expect_err = 0,
+ .send = &send_16_2_1,
+ .so_timestamping = (SOF_TIMESTAMPING_SOFTWARE |
+ SOF_TIMESTAMPING_OPT_ID |
+ SOF_TIMESTAMPING_TX_SOFTWARE),
+ .repeat_send = 1,
+ .repeat_send_pre_ts = 2,
+};
+
+static const struct iso_client_data connect_send_tx_sched_timestamping = {
+ .qos = QOS_16_2_1,
+ .expect_err = 0,
+ .send = &send_16_2_1,
+ .so_timestamping = (SOF_TIMESTAMPING_SOFTWARE |
+ SOF_TIMESTAMPING_TX_SOFTWARE |
+ SOF_TIMESTAMPING_OPT_TSONLY |
+ SOF_TIMESTAMPING_TX_SCHED),
+ .repeat_send = 1,
+};
+
+static const struct iso_client_data connect_send_tx_cmsg_timestamping = {
+ .qos = QOS_16_2_1,
+ .expect_err = 0,
+ .send = &send_16_2_1,
+ .so_timestamping = (SOF_TIMESTAMPING_SOFTWARE |
+ SOF_TIMESTAMPING_TX_SOFTWARE),
+ .repeat_send = 1,
+ .cmsg_timestamping = true,
+};
+
static const struct iso_client_data listen_16_2_1_recv = {
.qos = QOS_16_2_1,
.expect_err = 0,
@@ -1410,14 +1463,17 @@ static void bthost_recv_data(const void *buf, uint16_t len, void *user_data)
struct test_data *data = user_data;
const struct iso_client_data *isodata = data->test_data;
+ --data->step;
+
tester_print("Client received %u bytes of data", len);
if (isodata->send && (isodata->send->iov_len != len ||
memcmp(isodata->send->iov_base, buf, len))) {
if (!isodata->recv->iov_base)
tester_test_failed();
- } else
+ } else if (!data->step) {
tester_test_passed();
+ }
}
static void bthost_iso_disconnected(void *user_data)
@@ -2058,17 +2114,93 @@ static void iso_recv(struct test_data *data, GIOChannel *io)
data->io_id[0] = g_io_add_watch(io, G_IO_IN, iso_recv_data, data);
}
-static void iso_send(struct test_data *data, GIOChannel *io)
+static gboolean iso_recv_errqueue(GIOChannel *io, GIOCondition cond,
+ gpointer user_data)
{
+ struct test_data *data = user_data;
const struct iso_client_data *isodata = data->test_data;
- ssize_t ret;
+ int sk = g_io_channel_unix_get_fd(io);
+ int err;
+
+ data->step--;
+
+ err = tx_tstamp_recv(&data->tx_ts, sk, isodata->send->iov_len);
+ if (err > 0)
+ return TRUE;
+ else if (!err && !data->step)
+ tester_test_passed();
+ else
+ tester_test_failed();
+
+ data->io_id[2] = 0;
+ return FALSE;
+}
+
+static void iso_tx_timestamping(struct test_data *data, GIOChannel *io)
+{
+ const struct iso_client_data *isodata = data->test_data;
+ int so = isodata->so_timestamping;
int sk;
+ int err;
+ unsigned int count;
+
+ if (!(isodata->so_timestamping & SOF_TIMESTAMPING_TX_RECORD_MASK))
+ return;
+
+ tester_print("Enabling TX timestamping");
+
+ tx_tstamp_init(&data->tx_ts, isodata->so_timestamping);
+
+ for (count = 0; count < isodata->repeat_send + 1; ++count)
+ data->step += tx_tstamp_expect(&data->tx_ts);
sk = g_io_channel_unix_get_fd(io);
+ data->io_id[2] = g_io_add_watch(io, G_IO_ERR, iso_recv_errqueue, data);
+
+ if (isodata->cmsg_timestamping)
+ so &= ~SOF_TIMESTAMPING_TX_RECORD_MASK;
+
+ err = setsockopt(sk, SOL_SOCKET, SO_TIMESTAMPING, &so, sizeof(so));
+ if (err < 0) {
+ tester_warn("setsockopt SO_TIMESTAMPING: %s (%d)",
+ strerror(errno), errno);
+ tester_test_failed();
+ return;
+ }
+}
+
+static void iso_send_data(struct test_data *data, GIOChannel *io)
+{
+ const struct iso_client_data *isodata = data->test_data;
+ char control[CMSG_SPACE(sizeof(uint32_t))];
+ struct msghdr msg = {
+ .msg_iov = (struct iovec *)isodata->send,
+ .msg_iovlen = 1,
+ };
+ struct cmsghdr *cmsg;
+ ssize_t ret;
+ int sk;
+
tester_print("Writing %zu bytes of data", isodata->send->iov_len);
- ret = writev(sk, isodata->send, 1);
+ sk = g_io_channel_unix_get_fd(io);
+
+ if (isodata->cmsg_timestamping) {
+ memset(control, 0, sizeof(control));
+ msg.msg_control = control;
+ msg.msg_controllen = sizeof(control);
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SO_TIMESTAMPING;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(uint32_t));
+
+ *((uint32_t *)CMSG_DATA(cmsg)) = (isodata->so_timestamping &
+ SOF_TIMESTAMPING_TX_RECORD_MASK);
+ }
+
+ ret = sendmsg(sk, &msg, 0);
if (ret < 0 || isodata->send->iov_len != (size_t) ret) {
tester_warn("Failed to write %zu bytes: %s (%d)",
isodata->send->iov_len, strerror(errno), errno);
@@ -2076,6 +2208,22 @@ static void iso_send(struct test_data *data, GIOChannel *io)
return;
}
+ data->step++;
+}
+
+static void iso_send(struct test_data *data, GIOChannel *io)
+{
+ const struct iso_client_data *isodata = data->test_data;
+ unsigned int count;
+
+ for (count = 0; count < isodata->repeat_send_pre_ts; ++count)
+ iso_send_data(data, io);
+
+ iso_tx_timestamping(data, io);
+
+ for (count = 0; count < isodata->repeat_send + 1; ++count)
+ iso_send_data(data, io);
+
if (isodata->bcast) {
tester_test_passed();
return;
@@ -3197,6 +3345,20 @@ int main(int argc, char *argv[])
test_iso("ISO Send - Success", &connect_16_2_1_send, setup_powered,
test_connect);
+ /* Test basic TX timestamping */
+ test_iso("ISO Send - TX Timestamping", &connect_send_tx_timestamping,
+ setup_powered, test_connect);
+
+ /* Test schedule-time TX timestamps are emitted */
+ test_iso("ISO Send - TX Sched Timestamping",
+ &connect_send_tx_sched_timestamping, setup_powered,
+ test_connect);
+
+ /* Test TX timestamping with flags set via per-packet CMSG */
+ test_iso("ISO Send - TX CMSG Timestamping",
+ &connect_send_tx_cmsg_timestamping, setup_powered,
+ test_connect);
+
test_iso("ISO Receive - Success", &listen_16_2_1_recv, setup_powered,
test_listen);
diff --git a/tools/tester.h b/tools/tester.h
new file mode 100644
index 000000000..617de842e
--- /dev/null
+++ b/tools/tester.h
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2022 Intel Corporation.
+ *
+ */
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <linux/errqueue.h>
+#include <linux/net_tstamp.h>
+
+#include <glib.h>
+
+#define SEC_NSEC(_t) ((_t) * 1000000000LL)
+#define TS_NSEC(_ts) (SEC_NSEC((_ts)->tv_sec) + (_ts)->tv_nsec)
+
+struct tx_tstamp_data {
+ struct {
+ uint32_t id;
+ uint32_t type;
+ } expect[16];
+ unsigned int pos;
+ unsigned int count;
+ unsigned int sent;
+ uint32_t so_timestamping;
+};
+
+static inline void tx_tstamp_init(struct tx_tstamp_data *data,
+ uint32_t so_timestamping)
+{
+ memset(data, 0, sizeof(*data));
+ memset(data->expect, 0xff, sizeof(data->expect));
+
+ data->so_timestamping = so_timestamping;
+}
+
+static inline int tx_tstamp_expect(struct tx_tstamp_data *data)
+{
+ unsigned int pos = data->count;
+ int steps;
+
+ if (data->so_timestamping & SOF_TIMESTAMPING_TX_SCHED) {
+ g_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));
+ data->expect[pos].type = SCM_TSTAMP_SND;
+ data->expect[pos].id = data->sent;
+ pos++;
+ }
+
+ data->sent++;
+
+ steps = pos - data->count;
+ data->count = pos;
+ return steps;
+}
+
+static inline int tx_tstamp_recv(struct tx_tstamp_data *data, int sk, int len)
+{
+ unsigned char control[512];
+ ssize_t ret;
+ char buf[1024];
+ struct msghdr msg;
+ struct iovec iov;
+ struct cmsghdr *cmsg;
+ struct scm_timestamping *tss = NULL;
+ struct sock_extended_err *serr = NULL;
+ struct timespec now;
+
+ iov.iov_base = buf;
+ iov.iov_len = sizeof(buf);
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = control;
+ msg.msg_controllen = sizeof(control);
+
+ ret = recvmsg(sk, &msg, MSG_ERRQUEUE);
+ if (ret < 0) {
+ tester_warn("Failed to read from errqueue: %s (%d)",
+ strerror(errno), errno);
+ return -EINVAL;
+ }
+
+ if (data->so_timestamping & SOF_TIMESTAMPING_OPT_TSONLY) {
+ if (ret != 0) {
+ tester_warn("Packet copied back to errqueue");
+ return -EINVAL;
+ }
+ } else if (len > ret) {
+ tester_warn("Packet not copied back to errqueue: %zd", ret);
+ return -EINVAL;
+ }
+
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
+ cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_TIMESTAMPING) {
+ tss = (void *)CMSG_DATA(cmsg);
+ } else if (cmsg->cmsg_level == SOL_BLUETOOTH &&
+ cmsg->cmsg_type == BT_SCM_ERROR) {
+ serr = (void *)CMSG_DATA(cmsg);
+ }
+ }
+
+ if (!tss) {
+ tester_warn("SCM_TIMESTAMPING not found");
+ return -EINVAL;
+ }
+
+ if (!serr) {
+ tester_warn("BT_SCM_ERROR not found");
+ return -EINVAL;
+ }
+
+ if (serr->ee_errno != ENOMSG ||
+ serr->ee_origin != SO_EE_ORIGIN_TIMESTAMPING) {
+ tester_warn("BT_SCM_ERROR wrong for timestamping");
+ return -EINVAL;
+ }
+
+ clock_gettime(CLOCK_REALTIME, &now);
+
+ if (TS_NSEC(&now) < TS_NSEC(tss->ts) ||
+ TS_NSEC(&now) > TS_NSEC(tss->ts) + SEC_NSEC(10)) {
+ tester_warn("nonsense in timestamp");
+ return -EINVAL;
+ }
+
+ if (data->pos >= data->count) {
+ tester_warn("Too many timestamps");
+ return -EINVAL;
+ }
+
+ if ((data->so_timestamping & SOF_TIMESTAMPING_OPT_ID) &&
+ serr->ee_data != data->expect[data->pos].id) {
+ tester_warn("Bad timestamp id %u", serr->ee_data);
+ return -EINVAL;
+ }
+
+ if (serr->ee_info != data->expect[data->pos].type) {
+ tester_warn("Bad timestamp type %u", serr->ee_info);
+ return -EINVAL;
+ }
+
+ tester_print("Got valid TX timestamp %u", data->pos);
+
+ ++data->pos;
+
+ return data->count - data->pos;
+}
--
2.44.0
Add test:
SCO CVSD Send - TX Timestamping
---
tools/sco-tester.c | 96 ++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 93 insertions(+), 3 deletions(-)
diff --git a/tools/sco-tester.c b/tools/sco-tester.c
index ecc65e092..d146a6937 100644
--- a/tools/sco-tester.c
+++ b/tools/sco-tester.c
@@ -31,6 +31,8 @@
#include "src/shared/mgmt.h"
#include "src/shared/util.h"
+#include "tester.h"
+
struct test_data {
const void *test_data;
struct mgmt *mgmt;
@@ -38,15 +40,24 @@ struct test_data {
struct hciemu *hciemu;
enum hciemu_type hciemu_type;
unsigned int io_id;
+ unsigned int err_io_id;
int sk;
bool disable_esco;
bool enable_codecs;
+ int step;
+ struct tx_tstamp_data tx_ts;
};
struct sco_client_data {
int expect_err;
const uint8_t *send_data;
uint16_t data_len;
+
+ /* Enable SO_TIMESTAMPING with these flags */
+ uint32_t so_timestamping;
+
+ /* Number of additional packets to send. */
+ unsigned int repeat_send;
};
static void print_debug(const char *str, void *user_data)
@@ -227,8 +238,10 @@ static void test_data_free(void *test_data)
break; \
user->hciemu_type = HCIEMU_TYPE_BREDRLE; \
user->io_id = 0; \
+ user->err_io_id = 0; \
user->sk = -1; \
user->test_data = data; \
+ user->step = 0; \
user->disable_esco = _disable_esco; \
user->enable_codecs = _enable_codecs; \
tester_add_full(name, data, \
@@ -265,6 +278,16 @@ static const struct sco_client_data connect_send_success = {
.send_data = data
};
+static const struct sco_client_data connect_send_tx_timestamping = {
+ .expect_err = 0,
+ .data_len = sizeof(data),
+ .send_data = data,
+ .so_timestamping = (SOF_TIMESTAMPING_SOFTWARE |
+ SOF_TIMESTAMPING_OPT_ID |
+ SOF_TIMESTAMPING_TX_SOFTWARE),
+ .repeat_send = 2,
+};
+
static void client_connectable_complete(uint16_t opcode, uint8_t status,
const void *param, uint8_t len,
void *user_data)
@@ -595,6 +618,59 @@ static int connect_sco_sock(struct test_data *data, int sk)
return 0;
}
+static gboolean recv_errqueue(GIOChannel *io, GIOCondition cond,
+ gpointer 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 err;
+
+ data->step--;
+
+ err = tx_tstamp_recv(&data->tx_ts, sk, scodata->data_len);
+ if (err > 0)
+ return TRUE;
+ else if (!err && !data->step)
+ tester_test_passed();
+ else
+ tester_test_failed();
+
+ data->err_io_id = 0;
+ return FALSE;
+}
+
+static void sco_tx_timestamping(struct test_data *data, GIOChannel *io)
+{
+ const struct sco_client_data *scodata = data->test_data;
+ int so = scodata->so_timestamping;
+ int sk;
+ int err;
+ unsigned int count;
+
+ if (!(scodata->so_timestamping & SOF_TIMESTAMPING_TX_RECORD_MASK))
+ return;
+
+ sk = g_io_channel_unix_get_fd(io);
+
+ tester_print("Enabling TX timestamping");
+
+ tx_tstamp_init(&data->tx_ts, scodata->so_timestamping);
+
+ for (count = 0; count < scodata->repeat_send + 1; ++count)
+ data->step += tx_tstamp_expect(&data->tx_ts);
+
+ err = setsockopt(sk, SOL_SOCKET, SO_TIMESTAMPING, &so, sizeof(so));
+ if (err < 0) {
+ tester_warn("setsockopt SO_TIMESTAMPING: %s (%d)",
+ strerror(errno), errno);
+ tester_test_failed();
+ return;
+ }
+
+ data->err_io_id = g_io_add_watch(io, G_IO_ERR, recv_errqueue, data);
+}
+
static gboolean sco_connect_cb(GIOChannel *io, GIOCondition cond,
gpointer user_data)
{
@@ -619,10 +695,20 @@ static gboolean sco_connect_cb(GIOChannel *io, GIOCondition cond,
if (scodata->send_data) {
ssize_t ret;
+ unsigned int count;
+
+ data->step = 0;
- tester_print("Writing %u bytes of data", scodata->data_len);
+ sco_tx_timestamping(data, io);
- ret = write(sk, scodata->send_data, scodata->data_len);
+ tester_print("Writing %u*%u bytes of data",
+ scodata->repeat_send + 1, scodata->data_len);
+
+ for (count = 0; count < scodata->repeat_send + 1; ++count) {
+ ret = write(sk, scodata->send_data, scodata->data_len);
+ if (scodata->data_len != ret)
+ break;
+ }
if (scodata->data_len != ret) {
tester_warn("Failed to write %u bytes: %zu %s (%d)",
scodata->data_len, ret, strerror(errno),
@@ -633,7 +719,7 @@ static gboolean sco_connect_cb(GIOChannel *io, GIOCondition cond,
if (-err != scodata->expect_err)
tester_test_failed();
- else
+ else if (!data->step)
tester_test_passed();
return FALSE;
@@ -869,6 +955,10 @@ int main(int argc, char *argv[])
test_sco("SCO CVSD Send - Success", &connect_send_success,
setup_powered, test_connect);
+ test_sco("SCO CVSD Send - TX Timestamping",
+ &connect_send_tx_timestamping,
+ setup_powered, test_connect);
+
test_offload_sco("Basic SCO Get Socket Option - Offload - Success",
NULL, setup_powered, test_codecs_getsockopt);
--
2.44.0
Allow bthost hooks to receive data from L2CAP LE credit based
connections. Handle LE credit header when receiving, and reassemble
received SDU.
Handle L2CAP LE credit header also in bthost_send_cid.
---
emulator/bthost.c | 205 ++++++++++++++++++++++++++++++++++++++++------
1 file changed, 180 insertions(+), 25 deletions(-)
diff --git a/emulator/bthost.c b/emulator/bthost.c
index 8c40fce90..8616bf715 100644
--- a/emulator/bthost.c
+++ b/emulator/bthost.c
@@ -162,11 +162,21 @@ struct btconn {
void *recv_data;
};
+enum l2cap_mode {
+ L2CAP_MODE_OTHER,
+ L2CAP_MODE_LE_CRED,
+ L2CAP_MODE_LE_ENH_CRED,
+};
+
struct l2conn {
+ struct l2conn *next;
uint16_t scid;
uint16_t dcid;
uint16_t psm;
- struct l2conn *next;
+ enum l2cap_mode mode;
+ uint16_t data_len;
+ uint16_t recv_len;
+ void *recv_data;
};
struct rcconn {
@@ -275,6 +285,7 @@ struct bthost *bthost_create(void)
static void l2conn_free(struct l2conn *conn)
{
+ free(conn->recv_data);
free(conn);
}
@@ -360,6 +371,7 @@ static struct l2conn *bthost_add_l2cap_conn(struct bthost *bthost,
l2conn->psm = psm;
l2conn->scid = scid;
l2conn->dcid = dcid;
+ l2conn->mode = L2CAP_MODE_OTHER;
l2conn->next = conn->l2conns;
conn->l2conns = l2conn;
@@ -415,6 +427,19 @@ static struct l2conn *btconn_find_l2cap_conn_by_scid(struct btconn *conn,
return NULL;
}
+static struct l2conn *btconn_find_l2cap_conn_by_dcid(struct btconn *conn,
+ uint16_t dcid)
+{
+ struct l2conn *l2conn;
+
+ for (l2conn = conn->l2conns; l2conn != NULL; l2conn = l2conn->next) {
+ if (l2conn->dcid == dcid)
+ return l2conn;
+ }
+
+ return NULL;
+}
+
static struct l2cap_conn_cb_data *bthost_find_l2cap_cb_by_psm(
struct bthost *bthost, uint16_t psm)
{
@@ -609,14 +634,24 @@ static void send_iov(struct bthost *bthost, uint16_t handle, uint16_t cid,
}
static void send_acl(struct bthost *bthost, uint16_t handle, uint16_t cid,
- const void *data, uint16_t len)
+ bool sdu_hdr, const void *data, uint16_t len)
{
- struct iovec iov;
+ struct iovec iov[2];
+ uint16_t sdu;
+ int num = 0;
+
+ if (sdu_hdr) {
+ sdu = cpu_to_le16(len);
+ iov[num].iov_base = &sdu;
+ iov[num].iov_len = sizeof(sdu);
+ num++;
+ }
- iov.iov_base = (void *) data;
- iov.iov_len = len;
+ iov[num].iov_base = (void *) data;
+ iov[num].iov_len = len;
+ num++;
- send_iov(bthost, handle, cid, &iov, 1);
+ send_iov(bthost, handle, cid, iov, num);
}
static uint8_t l2cap_sig_send(struct bthost *bthost, struct btconn *conn,
@@ -711,12 +746,19 @@ void bthost_send_cid(struct bthost *bthost, uint16_t handle, uint16_t cid,
const void *data, uint16_t len)
{
struct btconn *conn;
+ struct l2conn *l2conn;
+ bool sdu_hdr = false;
conn = bthost_find_conn(bthost, handle);
if (!conn)
return;
- send_acl(bthost, handle, cid, data, len);
+ l2conn = btconn_find_l2cap_conn_by_dcid(conn, cid);
+ if (l2conn && (l2conn->mode == L2CAP_MODE_LE_CRED ||
+ l2conn->mode == L2CAP_MODE_LE_ENH_CRED))
+ sdu_hdr = true;
+
+ send_acl(bthost, handle, cid, sdu_hdr, data, len);
}
void bthost_send_cid_v(struct bthost *bthost, uint16_t handle, uint16_t cid,
@@ -1779,7 +1821,7 @@ static void rfcomm_sabm_send(struct bthost *bthost, struct btconn *conn,
cmd.length = RFCOMM_LEN8(0);
cmd.fcs = rfcomm_fcs2((uint8_t *)&cmd);
- send_acl(bthost, conn->handle, l2conn->dcid, &cmd, sizeof(cmd));
+ send_acl(bthost, conn->handle, l2conn->dcid, false, &cmd, sizeof(cmd));
}
static bool l2cap_conn_rsp(struct bthost *bthost, struct btconn *conn,
@@ -2110,6 +2152,7 @@ static bool l2cap_le_conn_req(struct bthost *bthost, struct btconn *conn,
uint8_t ident, const void *data, uint16_t len)
{
const struct bt_l2cap_pdu_le_conn_req *req = data;
+ struct l2cap_conn_cb_data *cb_data;
struct bt_l2cap_pdu_le_conn_rsp rsp;
uint16_t psm;
@@ -2124,7 +2167,8 @@ static bool l2cap_le_conn_req(struct bthost *bthost, struct btconn *conn,
rsp.mps = 23;
rsp.credits = 1;
- if (bthost_find_l2cap_cb_by_psm(bthost, psm))
+ cb_data = bthost_find_l2cap_cb_by_psm(bthost, psm);
+ if (cb_data)
rsp.dcid = cpu_to_le16(conn->next_cid++);
else
rsp.result = cpu_to_le16(0x0002); /* PSM Not Supported */
@@ -2132,6 +2176,20 @@ static bool l2cap_le_conn_req(struct bthost *bthost, struct btconn *conn,
l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_LE_CONN_RSP, ident, &rsp,
sizeof(rsp));
+ if (!rsp.result) {
+ struct l2conn *l2conn;
+
+ l2conn = bthost_add_l2cap_conn(bthost, conn,
+ le16_to_cpu(rsp.dcid),
+ le16_to_cpu(req->scid),
+ le16_to_cpu(psm));
+ l2conn->mode = L2CAP_MODE_LE_CRED;
+
+ if (cb_data && l2conn->psm == cb_data->psm && cb_data->func)
+ cb_data->func(conn->handle, l2conn->dcid,
+ cb_data->user_data);
+ }
+
return true;
}
@@ -2139,11 +2197,14 @@ static bool l2cap_le_conn_rsp(struct bthost *bthost, struct btconn *conn,
uint8_t ident, const void *data, uint16_t len)
{
const struct bt_l2cap_pdu_le_conn_rsp *rsp = data;
+ struct l2conn *l2conn;
if (len < sizeof(*rsp))
return false;
/* TODO add L2CAP connection before with proper PSM */
- bthost_add_l2cap_conn(bthost, conn, 0, le16_to_cpu(rsp->dcid), 0);
+ l2conn = bthost_add_l2cap_conn(bthost, conn, 0,
+ le16_to_cpu(rsp->dcid), 0);
+ l2conn->mode = L2CAP_MODE_LE_CRED;
return true;
}
@@ -2196,16 +2257,19 @@ static bool l2cap_ecred_conn_rsp(struct bthost *bthost, struct btconn *conn,
uint16_t scid[5];
} __attribute__ ((packed)) *rsp = data;
int num_scid, i;
+ struct l2conn *l2conn;
if (len < sizeof(*rsp))
return false;
num_scid = len / sizeof(*rsp->scid);
- for (i = 0; i < num_scid; i++)
+ for (i = 0; i < num_scid; i++) {
/* TODO add L2CAP connection before with proper PSM */
- bthost_add_l2cap_conn(bthost, conn, 0,
+ l2conn = bthost_add_l2cap_conn(bthost, conn, 0,
le16_to_cpu(rsp->scid[i]), 0);
+ l2conn->mode = L2CAP_MODE_LE_ENH_CRED;
+ }
return true;
@@ -2333,7 +2397,7 @@ static void rfcomm_ua_send(struct bthost *bthost, struct btconn *conn,
cmd.length = RFCOMM_LEN8(0);
cmd.fcs = rfcomm_fcs2((uint8_t *)&cmd);
- send_acl(bthost, conn->handle, l2conn->dcid, &cmd, sizeof(cmd));
+ send_acl(bthost, conn->handle, l2conn->dcid, false, &cmd, sizeof(cmd));
}
static void rfcomm_dm_send(struct bthost *bthost, struct btconn *conn,
@@ -2347,7 +2411,7 @@ static void rfcomm_dm_send(struct bthost *bthost, struct btconn *conn,
cmd.length = RFCOMM_LEN8(0);
cmd.fcs = rfcomm_fcs2((uint8_t *)&cmd);
- send_acl(bthost, conn->handle, l2conn->dcid, &cmd, sizeof(cmd));
+ send_acl(bthost, conn->handle, l2conn->dcid, false, &cmd, sizeof(cmd));
}
static void rfcomm_sabm_recv(struct bthost *bthost, struct btconn *conn,
@@ -2636,12 +2700,97 @@ static void process_rfcomm(struct bthost *bthost, struct btconn *conn,
}
}
+static void append_l2conn_data(struct bthost *bthost, struct l2conn *conn,
+ const void *data, uint16_t len)
+{
+ if (!conn->recv_data) {
+ bthost_debug(bthost, "Unexpected L2CAP SDU data: sCID 0x%4.4x ",
+ conn->scid);
+ return;
+ }
+
+ if (conn->recv_len + len > conn->data_len) {
+ bthost_debug(bthost, "Unexpected L2CAP SDU data: sCID 0x%4.4x ",
+ conn->scid);
+ return;
+ }
+
+ memcpy(conn->recv_data + conn->recv_len, data, len);
+ conn->recv_len += len;
+
+ bthost_debug(bthost, "L2CAP SDU data: %u/%u bytes", conn->recv_len,
+ conn->data_len);
+}
+
+static void free_l2conn_data(struct l2conn *conn)
+{
+ free(conn->recv_data);
+ conn->recv_data = NULL;
+ conn->recv_len = 0;
+ conn->data_len = 0;
+}
+
+static void new_l2conn_data(struct bthost *bthost, struct l2conn *conn,
+ uint16_t len)
+{
+ free(conn->recv_data);
+ conn->recv_data = malloc(len);
+ conn->recv_len = 0;
+ conn->data_len = len;
+}
+
+static bool process_l2cap_conn(struct bthost *bthost, struct btconn *conn,
+ struct l2conn *l2conn, struct iovec *data)
+{
+ struct bt_l2cap_pdu_le_flowctl_creds creds;
+ uint16_t sdu;
+
+ if (!l2conn)
+ return true;
+
+ switch (l2conn->mode) {
+ case L2CAP_MODE_LE_CRED:
+ case L2CAP_MODE_LE_ENH_CRED:
+ break;
+ case L2CAP_MODE_OTHER:
+ return true;
+ }
+
+ /* Credit-based flow control */
+
+ creds.cid = cpu_to_le16(l2conn->scid);
+ creds.credits = cpu_to_le16(1);
+ l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_LE_FLOWCTL_CREDS, 0,
+ &creds, sizeof(creds));
+
+ if (!l2conn->data_len) {
+ if (!util_iov_pull_le16(data, &sdu)) {
+ free_l2conn_data(l2conn);
+ bthost_debug(bthost, "L2CAP invalid SDU");
+ return false;
+ }
+ new_l2conn_data(bthost, l2conn, sdu);
+ }
+
+ append_l2conn_data(bthost, l2conn, data->iov_base, data->iov_len);
+
+ if (l2conn->recv_len < l2conn->data_len)
+ return false; /* SDU incomplete */
+
+ l2conn->data_len = 0;
+ data->iov_base = l2conn->recv_data;
+ data->iov_len = l2conn->recv_len;
+
+ return true;
+}
+
static void process_l2cap(struct bthost *bthost, struct btconn *conn,
- const void *data, uint16_t len)
+ const void *buf, uint16_t len)
{
- const struct bt_l2cap_hdr *l2_hdr = data;
+ const struct bt_l2cap_hdr *l2_hdr = buf;
struct cid_hook *hook;
struct l2conn *l2conn;
+ struct iovec data;
uint16_t cid, l2_len;
l2_len = le16_to_cpu(l2_hdr->len);
@@ -2654,31 +2803,37 @@ static void process_l2cap(struct bthost *bthost, struct btconn *conn,
bthost_debug(bthost, "L2CAP data: %u bytes", l2_len);
cid = le16_to_cpu(l2_hdr->cid);
+ l2conn = btconn_find_l2cap_conn_by_scid(conn, cid);
+
+ data.iov_base = (void *)l2_hdr->data;
+ data.iov_len = l2_len;
+
+ if (!process_l2cap_conn(bthost, conn, l2conn, &data))
+ return;
hook = find_cid_hook(conn, cid);
if (hook) {
- hook->func(l2_hdr->data, l2_len, hook->user_data);
+ hook->func(data.iov_base, data.iov_len, hook->user_data);
return;
}
switch (cid) {
case 0x0001:
- l2cap_sig(bthost, conn, l2_hdr->data, l2_len);
+ l2cap_sig(bthost, conn, data.iov_base, data.iov_len);
break;
case 0x0005:
- l2cap_le_sig(bthost, conn, l2_hdr->data, l2_len);
+ l2cap_le_sig(bthost, conn, data.iov_base, data.iov_len);
break;
case 0x0006:
- smp_data(conn->smp_data, l2_hdr->data, l2_len);
+ smp_data(conn->smp_data, data.iov_base, data.iov_len);
break;
case 0x0007:
- smp_bredr_data(conn->smp_data, l2_hdr->data, l2_len);
+ smp_bredr_data(conn->smp_data, data.iov_base, data.iov_len);
break;
default:
- l2conn = btconn_find_l2cap_conn_by_scid(conn, cid);
if (l2conn && l2conn->psm == 0x0003)
- process_rfcomm(bthost, conn, l2conn, l2_hdr->data,
- l2_len);
+ process_rfcomm(bthost, conn, l2conn, data.iov_base,
+ data.iov_len);
else
bthost_debug(bthost,
"Packet for unknown CID 0x%04x (%u)",
@@ -3494,7 +3649,7 @@ void bthost_send_rfcomm_data(struct bthost *bthost, uint16_t handle,
}
uih_frame[uih_len - 1] = rfcomm_fcs((void *)hdr);
- send_acl(bthost, handle, rcconn->scid, uih_frame, uih_len);
+ send_acl(bthost, handle, rcconn->scid, false, uih_frame, uih_len);
free(uih_frame);
}
--
2.44.0
Add tests:
L2CAP LE Client - Read Success
L2CAP LE Client - Write Success
L2CAP LE Client - TX Timestamping
---
tools/l2cap-tester.c | 41 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 40 insertions(+), 1 deletion(-)
diff --git a/tools/l2cap-tester.c b/tools/l2cap-tester.c
index 375ff767b..02d1571d2 100644
--- a/tools/l2cap-tester.c
+++ b/tools/l2cap-tester.c
@@ -487,6 +487,30 @@ static const struct l2cap_data le_client_connect_timeout_test_1 = {
.timeout = 1,
};
+static const struct l2cap_data le_client_connect_read_success_test = {
+ .client_psm = 0x0080,
+ .server_psm = 0x0080,
+ .read_data = l2_data,
+ .data_len = sizeof(l2_data),
+};
+
+static const struct l2cap_data le_client_connect_write_success_test = {
+ .client_psm = 0x0080,
+ .server_psm = 0x0080,
+ .write_data = l2_data,
+ .data_len = sizeof(l2_data),
+};
+
+static const struct l2cap_data le_client_connect_tx_timestamping_test = {
+ .client_psm = 0x0080,
+ .server_psm = 0x0080,
+ .write_data = l2_data,
+ .data_len = sizeof(l2_data),
+ .so_timestamping = (SOF_TIMESTAMPING_SOFTWARE |
+ SOF_TIMESTAMPING_OPT_ID |
+ SOF_TIMESTAMPING_TX_SOFTWARE),
+};
+
static const struct l2cap_data le_client_connect_adv_success_test_1 = {
.client_psm = 0x0080,
.server_psm = 0x0080,
@@ -1082,6 +1106,8 @@ static gboolean client_received_data(GIOChannel *io, GIOCondition cond,
char buf[1024];
int sk;
+ tester_debug("Client received data");
+
sk = g_io_channel_unix_get_fd(io);
if (read(sk, buf, l2data->data_len) != l2data->data_len) {
tester_warn("Unable to read %u bytes", l2data->data_len);
@@ -1126,6 +1152,8 @@ static void bthost_received_data(const void *buf, uint16_t len,
struct test_data *data = tester_get_data();
const struct l2cap_data *l2data = data->test_data;
+ tester_debug("BTHost received data: %u bytes", len);
+
--data->step;
if (len != l2data->data_len) {
@@ -1314,7 +1342,7 @@ static gboolean l2cap_connect_cb(GIOChannel *io, GIOCondition cond,
goto failed;
}
- tester_print("Successfully connected");
+ tester_print("Successfully connected to CID 0x%04x", data->dcid);
if (!check_mtu(data, sk)) {
tester_test_failed();
@@ -1506,6 +1534,8 @@ static void client_l2cap_connect_cb(uint16_t handle, uint16_t cid,
{
struct test_data *data = user_data;
+ tester_debug("Client connect CID 0x%04x handle 0x%04x", cid, handle);
+
data->dcid = cid;
data->handle = handle;
}
@@ -2431,6 +2461,15 @@ int main(int argc, char *argv[])
test_l2cap_le("L2CAP LE Client - Timeout",
&le_client_connect_timeout_test_1,
setup_powered_client, test_connect_timeout);
+ test_l2cap_le("L2CAP LE Client - Read Success",
+ &le_client_connect_read_success_test,
+ setup_powered_client, test_connect);
+ test_l2cap_le("L2CAP LE Client - Write Success",
+ &le_client_connect_write_success_test,
+ setup_powered_client, test_connect);
+ test_l2cap_le("L2CAP LE Client - TX Timestamping",
+ &le_client_connect_tx_timestamping_test,
+ setup_powered_client, test_connect);
test_l2cap_le("L2CAP LE Client, Direct Advertising - Success",
&le_client_connect_adv_success_test_1,
setup_powered_client, test_connect);
--
2.44.0
Add test
L2CAP BR/EDR Client - TX Timestamping
---
tools/l2cap-tester.c | 107 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 102 insertions(+), 5 deletions(-)
diff --git a/tools/l2cap-tester.c b/tools/l2cap-tester.c
index 461f2c27c..375ff767b 100644
--- a/tools/l2cap-tester.c
+++ b/tools/l2cap-tester.c
@@ -30,6 +30,9 @@
#include "src/shared/tester.h"
#include "src/shared/mgmt.h"
+#include "src/shared/util.h"
+
+#include "tester.h"
struct test_data {
const void *test_data;
@@ -38,12 +41,15 @@ struct test_data {
struct hciemu *hciemu;
enum hciemu_type hciemu_type;
unsigned int io_id;
+ unsigned int err_io_id;
uint16_t handle;
uint16_t scid;
uint16_t dcid;
int sk;
int sk2;
bool host_disconnected;
+ int step;
+ struct tx_tstamp_data tx_ts;
};
struct l2cap_data {
@@ -86,6 +92,12 @@ struct l2cap_data {
bool defer;
bool shut_sock_wr;
+
+ /* Enable SO_TIMESTAMPING with these flags */
+ uint32_t so_timestamping;
+
+ /* Number of additional packets to send. */
+ unsigned int repeat_send;
};
static void print_debug(const char *str, void *user_data)
@@ -226,6 +238,11 @@ static void test_post_teardown(const void *test_data)
data->io_id = 0;
}
+ if (data->err_io_id > 0) {
+ g_source_remove(data->err_io_id);
+ data->err_io_id = 0;
+ }
+
hciemu_unref(data->hciemu);
data->hciemu = NULL;
}
@@ -245,6 +262,7 @@ static void test_data_free(void *test_data)
break; \
user->hciemu_type = HCIEMU_TYPE_BREDR; \
user->io_id = 0; \
+ user->err_io_id = 0; \
user->test_data = data; \
tester_add_full(name, data, \
test_pre_setup, setup, func, NULL, \
@@ -259,6 +277,7 @@ static void test_data_free(void *test_data)
break; \
user->hciemu_type = HCIEMU_TYPE_LE; \
user->io_id = 0; \
+ user->err_io_id = 0; \
user->test_data = data; \
tester_add_full(name, data, \
test_pre_setup, setup, func, NULL, \
@@ -321,6 +340,17 @@ static const struct l2cap_data client_connect_write_success_test = {
.data_len = sizeof(l2_data),
};
+static const struct l2cap_data client_connect_tx_timestamping_test = {
+ .client_psm = 0x1001,
+ .server_psm = 0x1001,
+ .write_data = l2_data,
+ .data_len = sizeof(l2_data),
+ .so_timestamping = (SOF_TIMESTAMPING_SOFTWARE |
+ SOF_TIMESTAMPING_OPT_ID |
+ SOF_TIMESTAMPING_TX_SOFTWARE),
+ .repeat_send = 2,
+};
+
static const struct l2cap_data client_connect_shut_wr_success_test = {
.client_psm = 0x1001,
.server_psm = 0x1001,
@@ -1096,6 +1126,8 @@ static void bthost_received_data(const void *buf, uint16_t len,
struct test_data *data = tester_get_data();
const struct l2cap_data *l2data = data->test_data;
+ --data->step;
+
if (len != l2data->data_len) {
tester_test_failed();
return;
@@ -1103,7 +1135,7 @@ static void bthost_received_data(const void *buf, uint16_t len,
if (memcmp(buf, l2data->write_data, l2data->data_len))
tester_test_failed();
- else
+ else if (!data->step)
tester_test_passed();
}
@@ -1207,6 +1239,59 @@ static bool check_mtu(struct test_data *data, int sk)
return true;
}
+static gboolean recv_errqueue(GIOChannel *io, GIOCondition cond,
+ gpointer 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 err;
+
+ data->step--;
+
+ err = tx_tstamp_recv(&data->tx_ts, sk, l2data->data_len);
+ if (err > 0)
+ return TRUE;
+ else if (!err && !data->step)
+ tester_test_passed();
+ else
+ tester_test_failed();
+
+ data->err_io_id = 0;
+ return FALSE;
+}
+
+static void l2cap_tx_timestamping(struct test_data *data, GIOChannel *io)
+{
+ const struct l2cap_data *l2data = data->test_data;
+ int so = l2data->so_timestamping;
+ int sk;
+ int err;
+ unsigned int count;
+
+ if (!(l2data->so_timestamping & SOF_TIMESTAMPING_TX_RECORD_MASK))
+ return;
+
+ sk = g_io_channel_unix_get_fd(io);
+
+ tester_print("Enabling TX timestamping");
+
+ tx_tstamp_init(&data->tx_ts, l2data->so_timestamping);
+
+ for (count = 0; count < l2data->repeat_send + 1; ++count)
+ data->step += tx_tstamp_expect(&data->tx_ts);
+
+ err = setsockopt(sk, SOL_SOCKET, SO_TIMESTAMPING, &so, sizeof(so));
+ if (err < 0) {
+ tester_warn("setsockopt SO_TIMESTAMPING: %s (%d)",
+ strerror(errno), errno);
+ tester_test_failed();
+ return;
+ }
+
+ data->err_io_id = g_io_add_watch(io, G_IO_ERR, recv_errqueue, data);
+}
+
static gboolean l2cap_connect_cb(GIOChannel *io, GIOCondition cond,
gpointer user_data)
{
@@ -1249,15 +1334,23 @@ static gboolean l2cap_connect_cb(GIOChannel *io, GIOCondition cond,
} else if (l2data->write_data) {
struct bthost *bthost;
ssize_t ret;
+ unsigned int count;
+
+ data->step = 0;
bthost = hciemu_client_get_host(data->hciemu);
bthost_add_cid_hook(bthost, data->handle, data->dcid,
bthost_received_data, NULL);
- ret = write(sk, l2data->write_data, l2data->data_len);
- if (ret != l2data->data_len) {
- tester_warn("Unable to write all data");
- tester_test_failed();
+ l2cap_tx_timestamping(data, io);
+
+ for (count = 0; count < l2data->repeat_send + 1; ++count) {
+ ret = write(sk, l2data->write_data, l2data->data_len);
+ if (ret != l2data->data_len) {
+ tester_warn("Unable to write all data");
+ tester_test_failed();
+ }
+ ++data->step;
}
return FALSE;
@@ -2280,6 +2373,10 @@ int main(int argc, char *argv[])
&client_connect_write_success_test,
setup_powered_client, test_connect);
+ test_l2cap_bredr("L2CAP BR/EDR Client - TX Timestamping",
+ &client_connect_tx_timestamping_test,
+ setup_powered_client, test_connect);
+
test_l2cap_bredr("L2CAP BR/EDR Client - Invalid PSM 1",
&client_connect_nval_psm_test_1,
setup_powered_client, test_connect);
--
2.44.0