Linux bluetooth development
 help / color / mirror / Atom feed
From: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
To: linux-bluetooth@vger.kernel.org
Subject: [PATCH BlueZ vRFC 6/7] monitor: Add ISO link details to the analyze mode
Date: Fri,  2 Oct 2026 15:27:58 -0400	[thread overview]
Message-ID: <20261002192759.3071262-6-luiz.dentz@gmail.com> (raw)
In-Reply-To: <20261002192759.3071262-1-luiz.dentz@gmail.com>

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

Print the link parameters of CIS and BIS connections along with the last
LE Read ISO Link Quality counters:

  Found LE-BIS connection with handle 2560
        ...
        Link: ISO interval 30.00 msec NSE 12 BN 3 PTO 0 IRC 4 latency 28880 usec
        ...
        Link quality: 1 reads
          TX unacked 140 flushed 9 last subevent 31 retransmitted 210
          RX CRC errors 402 unreceived 12 duplicated 3

With a burst number above one the SDUs of an ISO interval are delivered
together, so pace them by the interval they belong to rather than by the
SDU interval, which reported most of them as jitter. On the BIS above:

-       RX jitter: 13.05 msec (max 45.73 msec, late 2556)
+       RX jitter: 0.68 msec (max 30.00 msec, late 0)

Assisted-by: OpenCode:claude-opus-5.5
---
 monitor/analyze.c | 73 +++++++++++++++++++++++++++++++++++++++++++++--
 monitor/packet.c  | 20 +++++++++++--
 monitor/packet.h  |  2 ++
 3 files changed, 89 insertions(+), 6 deletions(-)

diff --git a/monitor/analyze.c b/monitor/analyze.c
index 3a11590a0061..2b5e3087197c 100644
--- a/monitor/analyze.c
+++ b/monitor/analyze.c
@@ -109,6 +109,9 @@ struct hci_conn {
 	struct packet_loss rx_loss;
 	struct packet_jitter rx_jitter;
 	uint8_t sco_rate;
+	size_t lq_num;
+	struct bt_hci_rsp_le_read_iso_link_quality lq;
+	char iso_link[96];
 };
 
 struct hci_conn_tx {
@@ -382,9 +385,26 @@ static void conn_destroy(void *data)
 	print_stats(&conn->rx, "RX");
 	print_stats(&conn->tx, "TX");
 
+	if (conn->iso_link[0])
+		print_field("Link: %s", conn->iso_link);
+
 	packet_loss_print(&conn->rx_loss, "RX");
 	packet_jitter_print(&conn->rx_jitter, "RX");
 
+	if (conn->lq_num) {
+		print_field("Link quality: %zu reads", conn->lq_num);
+		print_field("  TX unacked %u flushed %u last subevent %u "
+				"retransmitted %u",
+				le32_to_cpu(conn->lq.tx_unacked_packets),
+				le32_to_cpu(conn->lq.tx_flushed_packets),
+				le32_to_cpu(conn->lq.tx_last_subevent_packets),
+				le32_to_cpu(conn->lq.retransmitted_packets));
+		print_field("  RX CRC errors %u unreceived %u duplicated %u",
+				le32_to_cpu(conn->lq.crc_error_packets),
+				le32_to_cpu(conn->lq.rx_unreceived_packets),
+				le32_to_cpu(conn->lq.duplicated_packets));
+	}
+
 	if (conn->setup_seen) {
 		print_field("Connected: #%lu", conn->frame_connected);
 		if (conn->terminated) {
@@ -902,6 +922,24 @@ static void rsp_read_bd_addr(struct hci_dev *dev, struct timeval *tv,
 	memcpy(dev->bdaddr, rsp->bdaddr, 6);
 }
 
+static void rsp_le_read_iso_link_quality(struct hci_dev *dev,
+					const void *data, uint16_t size)
+{
+	const struct bt_hci_rsp_le_read_iso_link_quality *rsp = data;
+	struct hci_conn *conn;
+
+	if (size < sizeof(*rsp) || rsp->status)
+		return;
+
+	conn = conn_lookup(dev, le16_to_cpu(rsp->handle));
+	if (!conn)
+		return;
+
+	/* The counters are cumulative so the last read has them all */
+	conn->lq = *rsp;
+	conn->lq_num++;
+}
+
 static void evt_cmd_complete(struct hci_dev *dev, struct timeval *tv,
 					const void *data, uint16_t size)
 {
@@ -919,6 +957,9 @@ static void evt_cmd_complete(struct hci_dev *dev, struct timeval *tv,
 	case BT_HCI_CMD_READ_BD_ADDR:
 		rsp_read_bd_addr(dev, tv, data, size);
 		break;
+	case BT_HCI_CMD_LE_READ_ISO_LINK_QUALITY:
+		rsp_le_read_iso_link_quality(dev, data, size);
+		break;
 	}
 }
 
@@ -1080,9 +1121,22 @@ static void evt_sync_conn_complete(struct hci_dev *dev, struct timeval *tv,
 static void set_iso_interval(struct hci_conn *conn, uint16_t interval,
 								uint8_t bn)
 {
-	if (bn)
-		conn->rx_jitter.interval = (uint32_t)le16_to_cpu(interval) *
-								1250 / bn;
+	if (!bn)
+		return;
+
+	conn->rx_jitter.interval = (uint32_t)le16_to_cpu(interval) * 1250 / bn;
+	conn->rx_jitter.bn = bn;
+}
+
+static void set_bis_link(struct hci_conn *conn, uint16_t interval,
+				uint8_t nse, uint8_t bn, uint8_t pto,
+				uint8_t irc, const uint8_t *latency)
+{
+	snprintf(conn->iso_link, sizeof(conn->iso_link), "ISO interval "
+			"%u.%02u msec NSE %u BN %u PTO %u IRC %u latency %u "
+			"usec", le16_to_cpu(interval) * 125 / 100,
+			le16_to_cpu(interval) * 125 % 100, nse, bn, pto, irc,
+			get_le24(latency));
 }
 
 static void evt_le_cis_established(struct hci_dev *dev, struct timeval *tv,
@@ -1110,6 +1164,15 @@ static void evt_le_cis_established(struct hci_dev *dev, struct timeval *tv,
 
 	/* Only the Peripheral gets a CIS Request for the handle */
 	set_iso_interval(conn, evt->interval, link ? evt->c_bn : evt->p_bn);
+
+	snprintf(conn->iso_link, sizeof(conn->iso_link), "ISO interval "
+			"%u.%02u msec NSE %u BN %u/%u FT %u/%u latency %u/%u "
+			"usec (C/P)",
+			le16_to_cpu(evt->interval) * 125 / 100,
+			le16_to_cpu(evt->interval) * 125 % 100, evt->nse,
+			evt->c_bn, evt->p_bn, evt->c_ft, evt->p_ft,
+			get_le24(evt->c_latency),
+			get_le24(evt->p_latency));
 }
 
 static void evt_le_cis_req(struct hci_dev *dev, struct timeval *tv,
@@ -1152,6 +1215,8 @@ static void evt_le_big_complete(struct hci_dev *dev, struct timeval *tv,
 			conn->setup_seen = true;
 			conn->frame_connected = frame;
 			set_iso_interval(conn, evt->interval, evt->bn);
+			set_bis_link(conn, evt->interval, evt->nse, evt->bn,
+					evt->pto, evt->irc, evt->latency);
 		}
 	}
 }
@@ -1179,6 +1244,8 @@ static void evt_le_big_sync_established(struct hci_dev *dev, struct timeval *tv,
 			conn->setup_seen = true;
 			conn->frame_connected = frame;
 			set_iso_interval(conn, evt->interval, evt->bn);
+			set_bis_link(conn, evt->interval, evt->nse, evt->bn,
+					evt->pto, evt->irc, evt->latency);
 		}
 	}
 }
diff --git a/monitor/packet.c b/monitor/packet.c
index 1e177d0abd3d..c21c79f75971 100644
--- a/monitor/packet.c
+++ b/monitor/packet.c
@@ -12161,7 +12161,7 @@ static void jitter_add(struct packet_jitter *jitter, struct timeval *tv,
 							int64_t expected)
 {
 	struct timeval delta;
-	int64_t arrival, d;
+	int64_t arrival, d, period;
 
 	timersub(tv, &jitter->prev_tv, &delta);
 	jitter->prev_tv = *tv;
@@ -12177,7 +12177,8 @@ static void jitter_add(struct packet_jitter *jitter, struct timeval *tv,
 	jitter->last_dev = d;
 
 	/* A sample arriving an interval or more later than expected */
-	if (jitter->interval && d >= jitter->interval)
+	period = (int64_t)jitter->interval * (jitter->bn > 1 ? jitter->bn : 1);
+	if (jitter->interval && d >= period)
 		jitter->late++;
 
 	if (d < 0)
@@ -12212,6 +12213,7 @@ void packet_jitter_add_sn(struct packet_jitter *jitter, struct timeval *tv,
 	if (!jitter->have_prev) {
 		jitter->prev_tv = *tv;
 		jitter->prev_sn = sn;
+		jitter->ext_sn = sn;
 		jitter->have_prev = true;
 		return;
 	}
@@ -12221,7 +12223,18 @@ void packet_jitter_add_sn(struct packet_jitter *jitter, struct timeval *tv,
 		return;
 
 	jitter->prev_sn = sn;
-	jitter_add(jitter, tv, (int64_t)diff * jitter->interval);
+	jitter->ext_sn += diff;
+
+	/*
+	 * With a burst number above one the samples of an ISO interval are
+	 * delivered together, so pace them by the interval they belong to.
+	 */
+	if (jitter->bn > 1)
+		jitter_add(jitter, tv, (int64_t)(jitter->ext_sn / jitter->bn -
+				(jitter->ext_sn - diff) / jitter->bn) *
+				jitter->interval * jitter->bn);
+	else
+		jitter_add(jitter, tv, (int64_t)diff * jitter->interval);
 }
 
 void packet_jitter_add_ts(struct packet_jitter *jitter, struct timeval *tv,
@@ -13617,6 +13630,7 @@ static void set_iso_interval(uint16_t handle, uint16_t interval, uint8_t bn)
 		return;
 
 	conn->rx_jitter.interval = (uint32_t)le16_to_cpu(interval) * 1250 / bn;
+	conn->rx_jitter.bn = bn;
 }
 
 static void le_cis_established_evt(struct timeval *tv, uint16_t index,
diff --git a/monitor/packet.h b/monitor/packet.h
index 85b7f2ff762c..77921af39263 100644
--- a/monitor/packet.h
+++ b/monitor/packet.h
@@ -59,6 +59,8 @@ struct packet_loss {
 
 struct packet_jitter {
 	uint32_t interval;	/* Nominal interval in usec, 0 if unknown */
+	uint8_t bn;		/* Samples delivered together, 0 if 1 */
+	uint64_t ext_sn;	/* Extended sequence number */
 	bool have_prev;
 	struct timeval prev_tv;
 	uint32_t prev_ts;
-- 
2.55.0


  parent reply	other threads:[~2026-10-02 19:28 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-02 19:27 [PATCH BlueZ vRFC 1/7] monitor: Add audio quality statistics for ISO streams Luiz Augusto von Dentz
2026-10-02 19:27 ` [PATCH BlueZ vRFC 2/7] monitor/analyze: Fix a new connection for each BIS or eSCO packet Luiz Augusto von Dentz
2026-10-02 19:27 ` [PATCH BlueZ vRFC 3/7] monitor/analyze: Fix the size of SCO packets Luiz Augusto von Dentz
2026-10-02 19:27 ` [PATCH BlueZ vRFC 4/7] monitor/analyze: Fix the type of synchronous connections Luiz Augusto von Dentz
2026-10-02 19:27 ` [PATCH BlueZ vRFC 5/7] monitor: Add audio quality statistics for SCO data Luiz Augusto von Dentz
2026-10-02 19:27 ` Luiz Augusto von Dentz [this message]
2026-10-02 19:27 ` [PATCH BlueZ vRFC 7/7] monitor: Add audio quality statistics for A2DP streams Luiz Augusto von Dentz
2026-10-02 22:07 ` [BlueZ,vRFC,1/7] monitor: Add audio quality statistics for ISO streams bluez.test.bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261002192759.3071262-6-luiz.dentz@gmail.com \
    --to=luiz.dentz@gmail.com \
    --cc=linux-bluetooth@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox