2024-02-20 23:01:07 +02:00
|
|
|
/* Spa Bluez5 ISO I/O */
|
|
|
|
|
/* SPDX-FileCopyrightText: Copyright © 2024 Pauli Virtanen */
|
|
|
|
|
/* SPDX-License-Identifier: MIT */
|
|
|
|
|
|
|
|
|
|
#ifndef SPA_BLUEZ5_BT_LATENCY_H
|
|
|
|
|
#define SPA_BLUEZ5_BT_LATENCY_H
|
|
|
|
|
|
2025-03-22 14:18:58 +02:00
|
|
|
#include <time.h>
|
2024-02-20 23:01:07 +02:00
|
|
|
#include <sys/socket.h>
|
|
|
|
|
#include <linux/net_tstamp.h>
|
|
|
|
|
#include <linux/errqueue.h>
|
|
|
|
|
#include <linux/sockios.h>
|
|
|
|
|
|
|
|
|
|
#include <spa/utils/defs.h>
|
2026-01-11 14:25:23 +02:00
|
|
|
#include <spa/utils/ratelimit.h>
|
2024-02-20 23:01:07 +02:00
|
|
|
#include <spa/support/log.h>
|
|
|
|
|
|
2024-05-26 12:46:31 +03:00
|
|
|
#include "defs.h"
|
2024-02-20 23:01:07 +02:00
|
|
|
#include "rate-control.h"
|
|
|
|
|
|
|
|
|
|
/* New kernel API */
|
|
|
|
|
#ifndef BT_SCM_ERROR
|
|
|
|
|
#define BT_SCM_ERROR 0x04
|
|
|
|
|
#endif
|
2024-05-26 12:46:31 +03:00
|
|
|
|
|
|
|
|
#define NEW_SOF_TIMESTAMPING_TX_COMPLETION (1 << 18)
|
|
|
|
|
#define NEW_SCM_TSTAMP_COMPLETION (SCM_TSTAMP_ACK + 1)
|
2024-02-20 23:01:07 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Bluetooth latency tracking.
|
|
|
|
|
*/
|
|
|
|
|
struct spa_bt_latency
|
|
|
|
|
{
|
|
|
|
|
uint64_t value;
|
|
|
|
|
struct spa_bt_ptp ptp;
|
|
|
|
|
bool valid;
|
2024-05-26 12:46:31 +03:00
|
|
|
bool enabled;
|
|
|
|
|
uint32_t queue;
|
|
|
|
|
uint32_t kernel_queue;
|
|
|
|
|
size_t unsent;
|
2024-02-20 23:01:07 +02:00
|
|
|
|
|
|
|
|
struct {
|
2026-01-11 14:25:23 +02:00
|
|
|
struct {
|
|
|
|
|
int64_t send;
|
|
|
|
|
uint32_t pos;
|
|
|
|
|
size_t size;
|
|
|
|
|
bool snd;
|
|
|
|
|
bool completion;
|
|
|
|
|
} pending[64];
|
2024-02-20 23:01:07 +02:00
|
|
|
uint32_t pos;
|
|
|
|
|
int64_t prev_tx;
|
2026-01-11 14:25:23 +02:00
|
|
|
struct spa_ratelimit rate_limit;
|
2024-02-20 23:01:07 +02:00
|
|
|
} impl;
|
|
|
|
|
};
|
|
|
|
|
|
2024-05-26 12:46:31 +03:00
|
|
|
static inline void spa_bt_latency_init(struct spa_bt_latency *lat, struct spa_bt_transport *transport,
|
2024-02-20 23:01:07 +02:00
|
|
|
uint32_t period, struct spa_log *log)
|
|
|
|
|
{
|
2024-05-26 12:46:31 +03:00
|
|
|
int so_timestamping = (NEW_SOF_TIMESTAMPING_TX_COMPLETION | SOF_TIMESTAMPING_TX_SOFTWARE |
|
|
|
|
|
SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_OPT_ID | SOF_TIMESTAMPING_OPT_TSONLY);
|
2024-02-20 23:01:07 +02:00
|
|
|
int res;
|
|
|
|
|
|
|
|
|
|
spa_zero(*lat);
|
|
|
|
|
|
2026-01-11 14:25:23 +02:00
|
|
|
lat->impl.rate_limit.interval = 5 * 60 * SPA_NSEC_PER_SEC;
|
|
|
|
|
lat->impl.rate_limit.burst = 8;
|
|
|
|
|
|
2024-05-26 12:46:31 +03:00
|
|
|
if (!transport->device->adapter->tx_timestamping_supported)
|
2024-02-20 23:01:07 +02:00
|
|
|
return;
|
|
|
|
|
|
2024-05-26 12:46:31 +03:00
|
|
|
res = setsockopt(transport->fd, SOL_SOCKET, SO_TIMESTAMPING, &so_timestamping, sizeof(so_timestamping));
|
2024-02-20 23:01:07 +02:00
|
|
|
if (res < 0) {
|
2024-05-26 12:46:31 +03:00
|
|
|
spa_log_info(log, "setsockopt(SO_TIMESTAMPING) failed (kernel feature not enabled?): %d (%m)", errno);
|
2024-02-20 23:01:07 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Flush errqueue on start */
|
|
|
|
|
do {
|
2024-05-26 12:46:31 +03:00
|
|
|
res = recv(transport->fd, NULL, 0, MSG_ERRQUEUE | MSG_DONTWAIT | MSG_TRUNC);
|
2024-02-20 23:01:07 +02:00
|
|
|
} while (res == 0);
|
|
|
|
|
|
|
|
|
|
spa_bt_ptp_init(&lat->ptp, period, period / 2);
|
2024-05-26 12:46:31 +03:00
|
|
|
|
|
|
|
|
lat->enabled = true;
|
2024-02-20 23:01:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline void spa_bt_latency_reset(struct spa_bt_latency *lat)
|
|
|
|
|
{
|
|
|
|
|
lat->value = 0;
|
|
|
|
|
lat->valid = false;
|
|
|
|
|
spa_bt_ptp_init(&lat->ptp, lat->ptp.period, lat->ptp.period / 2);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-11 14:25:23 +02:00
|
|
|
static inline void spa_bt_latency_clear_pending(struct spa_bt_latency *lat, unsigned int i,
|
|
|
|
|
bool snd, bool completion)
|
|
|
|
|
{
|
|
|
|
|
i = i % SPA_N_ELEMENTS(lat->impl.pending);
|
|
|
|
|
|
|
|
|
|
if (snd && lat->impl.pending[i].snd) {
|
|
|
|
|
if (lat->kernel_queue)
|
|
|
|
|
lat->kernel_queue--;
|
|
|
|
|
|
|
|
|
|
lat->impl.pending[i].snd = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (completion && lat->impl.pending[i].completion) {
|
|
|
|
|
if (lat->unsent > lat->impl.pending[i].size)
|
|
|
|
|
lat->unsent -= lat->impl.pending[i].size;
|
|
|
|
|
else
|
|
|
|
|
lat->unsent = 0;
|
|
|
|
|
|
|
|
|
|
if (lat->queue > 0)
|
|
|
|
|
lat->queue--;
|
|
|
|
|
if (!lat->queue)
|
|
|
|
|
lat->unsent = 0;
|
|
|
|
|
|
|
|
|
|
lat->impl.pending[i].completion = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-26 12:46:31 +03:00
|
|
|
static inline ssize_t spa_bt_send(int fd, const void *buf, size_t size,
|
|
|
|
|
struct spa_bt_latency *lat, uint64_t now)
|
2024-02-20 23:01:07 +02:00
|
|
|
{
|
2024-05-26 12:46:31 +03:00
|
|
|
ssize_t res = send(fd, buf, size, MSG_DONTWAIT | MSG_NOSIGNAL);
|
2024-02-20 23:01:07 +02:00
|
|
|
|
2024-05-26 12:46:31 +03:00
|
|
|
if (!lat || !lat->enabled)
|
|
|
|
|
return res;
|
2024-02-20 23:01:07 +02:00
|
|
|
|
2024-05-26 12:46:31 +03:00
|
|
|
if (res >= 0) {
|
2026-01-11 14:25:23 +02:00
|
|
|
uint32_t i = lat->impl.pos % SPA_N_ELEMENTS(lat->impl.pending);
|
2024-05-26 12:46:31 +03:00
|
|
|
|
2026-01-11 14:25:23 +02:00
|
|
|
spa_bt_latency_clear_pending(lat, i, true, true);
|
|
|
|
|
lat->impl.pending[i].send = now;
|
|
|
|
|
lat->impl.pending[i].size = size;
|
|
|
|
|
lat->impl.pending[i].pos = lat->impl.pos % UINT16_MAX;
|
|
|
|
|
lat->impl.pending[i].snd = true;
|
|
|
|
|
lat->impl.pending[i].completion = true;
|
|
|
|
|
|
|
|
|
|
lat->impl.pos++;
|
2024-05-26 12:46:31 +03:00
|
|
|
lat->queue++;
|
|
|
|
|
lat->kernel_queue++;
|
|
|
|
|
lat->unsent += size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return res;
|
2024-02-20 23:01:07 +02:00
|
|
|
}
|
|
|
|
|
|
2026-01-11 14:25:23 +02:00
|
|
|
static inline int spa_bt_latency_recv_errqueue(struct spa_bt_latency *lat, int fd, int64_t now,
|
|
|
|
|
struct spa_log *log)
|
2024-02-20 23:01:07 +02:00
|
|
|
{
|
2025-07-11 17:56:44 +03:00
|
|
|
union {
|
|
|
|
|
char buf[CMSG_SPACE(32 * sizeof(struct scm_timestamping))];
|
|
|
|
|
struct cmsghdr align;
|
2024-02-20 23:01:07 +02:00
|
|
|
} control;
|
2026-01-11 14:25:23 +02:00
|
|
|
unsigned int i;
|
2024-02-20 23:01:07 +02:00
|
|
|
|
2024-05-26 12:46:31 +03:00
|
|
|
if (!lat->enabled)
|
2024-02-20 23:01:07 +02:00
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
struct iovec data = {
|
|
|
|
|
.iov_base = NULL,
|
|
|
|
|
.iov_len = 0
|
|
|
|
|
};
|
|
|
|
|
struct msghdr msg = {
|
|
|
|
|
.msg_iov = &data,
|
|
|
|
|
.msg_iovlen = 1,
|
2025-07-11 17:56:44 +03:00
|
|
|
.msg_control = control.buf,
|
|
|
|
|
.msg_controllen = sizeof(control.buf),
|
2024-02-20 23:01:07 +02:00
|
|
|
};
|
|
|
|
|
struct cmsghdr *cmsg;
|
2026-01-11 14:25:23 +02:00
|
|
|
bool have_tss = false, have_serr = false;
|
|
|
|
|
struct scm_timestamping tss;
|
|
|
|
|
struct sock_extended_err serr;
|
2024-02-20 23:01:07 +02:00
|
|
|
int res;
|
|
|
|
|
|
|
|
|
|
res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT);
|
|
|
|
|
if (res < 0) {
|
|
|
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
|
|
|
|
break;
|
|
|
|
|
return -errno;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
|
2026-01-11 14:25:23 +02:00
|
|
|
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING) {
|
|
|
|
|
memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss));
|
|
|
|
|
have_tss = true;
|
|
|
|
|
} else if (cmsg->cmsg_level == SOL_BLUETOOTH && cmsg->cmsg_type == BT_SCM_ERROR) {
|
|
|
|
|
memcpy(&serr, CMSG_DATA(cmsg), sizeof(serr));
|
|
|
|
|
have_serr = true;
|
|
|
|
|
} else {
|
2024-02-20 23:01:07 +02:00
|
|
|
continue;
|
2026-01-11 14:25:23 +02:00
|
|
|
}
|
2024-02-20 23:01:07 +02:00
|
|
|
}
|
|
|
|
|
|
2026-01-11 14:25:23 +02:00
|
|
|
if (!have_tss || !have_serr || serr.ee_errno != ENOMSG || serr.ee_origin != SO_EE_ORIGIN_TIMESTAMPING)
|
|
|
|
|
continue;
|
2024-05-26 12:46:31 +03:00
|
|
|
|
2026-01-11 14:25:23 +02:00
|
|
|
uint32_t tx_pos = serr.ee_data % SPA_N_ELEMENTS(lat->impl.pending);
|
|
|
|
|
|
|
|
|
|
if (serr.ee_data % UINT16_MAX != lat->impl.pending[tx_pos].pos) {
|
|
|
|
|
spa_log_debug(log, "fd:%d latency[%u] bad value %u", fd, tx_pos, serr.ee_data);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (serr.ee_info) {
|
2024-05-26 12:46:31 +03:00
|
|
|
case SCM_TSTAMP_SND:
|
2026-01-11 14:25:23 +02:00
|
|
|
spa_bt_latency_clear_pending(lat, tx_pos, true, false);
|
2024-02-20 23:01:07 +02:00
|
|
|
continue;
|
2024-05-26 12:46:31 +03:00
|
|
|
case NEW_SCM_TSTAMP_COMPLETION:
|
2026-01-11 14:25:23 +02:00
|
|
|
if (!lat->impl.pending[tx_pos].completion)
|
|
|
|
|
continue;
|
2024-05-26 12:46:31 +03:00
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2024-02-20 23:01:07 +02:00
|
|
|
|
2026-01-11 14:25:23 +02:00
|
|
|
struct timespec *ts = &tss.ts[0];
|
2024-02-20 23:01:07 +02:00
|
|
|
int64_t tx_time = SPA_TIMESPEC_TO_NSEC(ts);
|
|
|
|
|
|
2026-01-11 14:25:23 +02:00
|
|
|
lat->value = tx_time - lat->impl.pending[tx_pos].send;
|
2024-02-20 23:01:07 +02:00
|
|
|
if (lat->impl.prev_tx && tx_time > lat->impl.prev_tx)
|
|
|
|
|
spa_bt_ptp_update(&lat->ptp, lat->value, tx_time - lat->impl.prev_tx);
|
|
|
|
|
|
|
|
|
|
lat->impl.prev_tx = tx_time;
|
|
|
|
|
|
2026-01-11 14:25:23 +02:00
|
|
|
spa_bt_latency_clear_pending(lat, tx_pos, false, true);
|
2024-05-26 12:46:31 +03:00
|
|
|
|
2024-02-20 23:01:07 +02:00
|
|
|
spa_log_trace(log, "fd:%d latency[%d] nsec:%"PRIu64" range:%d..%d ms",
|
|
|
|
|
fd, tx_pos, lat->value,
|
|
|
|
|
(int)(spa_bt_ptp_valid(&lat->ptp) ? lat->ptp.min / SPA_NSEC_PER_MSEC : -1),
|
|
|
|
|
(int)(spa_bt_ptp_valid(&lat->ptp) ? lat->ptp.max / SPA_NSEC_PER_MSEC : -1));
|
|
|
|
|
} while (true);
|
|
|
|
|
|
2026-01-11 14:25:23 +02:00
|
|
|
/* Clear too old pending latencies. Controllers (eg. Intel AX210, Realtek 8761CU)
|
|
|
|
|
* have known firmware bugs where they fail to report ISO packet completions. This
|
|
|
|
|
* will cause completion timestamps to be missing, so we should try to recover
|
|
|
|
|
* from this. (Kernel as of v6.18 will eventually stop sending though as it will
|
|
|
|
|
* think buffers are full.)
|
|
|
|
|
*/
|
|
|
|
|
for (i = 0; i < SPA_N_ELEMENTS(lat->impl.pending); ++i) {
|
|
|
|
|
if (lat->impl.pending[i].snd || lat->impl.pending[i].completion) {
|
|
|
|
|
if (lat->impl.pending[i].send + SPA_NSEC_PER_SEC < now) {
|
|
|
|
|
int suppressed;
|
|
|
|
|
|
|
|
|
|
if ((suppressed = spa_ratelimit_test(&lat->impl.rate_limit, now)) >= 0)
|
|
|
|
|
spa_log_warn(log, "Missing completion reports for packet (%d suppressed): "
|
|
|
|
|
"Bluetooth adapter firmware bug?", suppressed);
|
|
|
|
|
|
|
|
|
|
spa_log_trace(log, "fd:%d latency[%u] too late", fd, i);
|
|
|
|
|
spa_bt_latency_clear_pending(lat, i, true, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-20 23:01:07 +02:00
|
|
|
lat->valid = spa_bt_ptp_valid(&lat->ptp);
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline void spa_bt_latency_flush(struct spa_bt_latency *lat, int fd, struct spa_log *log)
|
|
|
|
|
{
|
|
|
|
|
int so_timestamping = 0;
|
|
|
|
|
|
2024-05-26 12:46:31 +03:00
|
|
|
if (!lat->enabled)
|
|
|
|
|
return;
|
|
|
|
|
|
2024-02-20 23:01:07 +02:00
|
|
|
/* Disable timestamping and flush errqueue */
|
|
|
|
|
setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &so_timestamping, sizeof(so_timestamping));
|
2026-01-11 14:25:23 +02:00
|
|
|
spa_bt_latency_recv_errqueue(lat, fd, 0, log);
|
2024-02-20 23:01:07 +02:00
|
|
|
|
2024-05-26 12:46:31 +03:00
|
|
|
lat->enabled = false;
|
2024-02-20 23:01:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|