Thread (4 messages) flat view 4 messages, 3 authors, 2026-03-03
STALE166d

[PATCH BlueZ v2 2/2] bthost: Add segmentation support for L2CAP LE-(E)CRED mode

From: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
Date: 2026-02-26 20:03:47
Subsystem: the rest · Maintainer: Linus Torvalds

From: Luiz Augusto von Dentz <redacted>

This fixes the following tests since the kernel now attempts to
check if a segment length is bigger than the MPS:

L2CAP LE Client - Read 32k Success - run
  Connect in progress
  Client connect CID 0x0040 handle 0x0001
  Successfully connected to CID 0x0040
Bluetooth: Too big LE L2CAP MPS: len 672 > 188

L2CAP LE Client - RX Timestamping 32k - run
  Connect in progress
  Client connect CID 0x0040 handle 0x0001
  Successfully connected to CID 0x0040
Bluetooth: Too big LE L2CAP MPS: len 672 > 188
---
 emulator/bthost.c | 53 ++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 41 insertions(+), 12 deletions(-)
diff --git a/emulator/bthost.c b/emulator/bthost.c
index 53b12f828675..b913c8015416 100644
--- a/emulator/bthost.c
+++ b/emulator/bthost.c
@@ -22,6 +22,7 @@
 #include <endian.h>
 #include <errno.h>
 #include <stdbool.h>
+#include <sys/param.h>
 
 #include "bluetooth/bluetooth.h"
 
@@ -185,6 +186,8 @@ struct l2conn {
 	uint16_t scid;
 	uint16_t dcid;
 	uint16_t psm;
+	uint16_t rx_mps;
+	uint16_t tx_mps;
 	uint16_t rx_credits;
 	uint16_t tx_credits;
 	enum l2cap_mode mode;
@@ -748,14 +751,14 @@ 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,
-				bool sdu_hdr, const void *data, uint16_t len)
+			uint16_t sdu_len, const void *data, uint16_t len)
 {
 	struct iovec iov[2];
 	uint16_t sdu;
 	int num = 0;
 
-	if (sdu_hdr) {
-		sdu = cpu_to_le16(len);
+	if (sdu_len) {
+		sdu = cpu_to_le16(sdu_len);
 		iov[num].iov_base = &sdu;
 		iov[num].iov_len = sizeof(sdu);
 		num++;
@@ -885,18 +888,42 @@ void bthost_send_cid(struct bthost *bthost, uint16_t handle, uint16_t cid,
 {
 	struct btconn *conn;
 	struct l2conn *l2conn;
-	bool sdu_hdr = false;
+	struct iovec iov = {
+		.iov_base = (void *) data,
+		.iov_len = len,
+	};
 
 	conn = bthost_find_conn(bthost, handle);
 	if (!conn)
 		return;
 
 	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);
+	/* Segment SDU in case of LE (Enhanced) Credit Based Flow Control */
+	if (l2conn && (l2conn->mode == L2CAP_MODE_LE_CRED ||
+			l2conn->mode == L2CAP_MODE_LE_ENH_CRED)) {
+		uint16_t sdu_len = len;
+		uint16_t slen;
+		int i;
+
+		for (i = 0; iov.iov_len; i++) {
+			if (sdu_len)
+				slen = MIN(iov.iov_len,
+					l2conn->tx_mps - sizeof(sdu_len));
+			else
+				slen = MIN(iov.iov_len, l2conn->tx_mps);
+
+			send_acl(bthost, handle, cid, sdu_len,
+					util_iov_pull_mem(&iov, slen), slen);
+
+			if (sdu_len)
+				sdu_len = 0;
+		}
+
+		return;
+	}
+
+	send_acl(bthost, handle, cid, 0, data, len);
 }
 
 void bthost_send_cid_v(struct bthost *bthost, uint16_t handle, uint16_t cid,
@@ -2104,7 +2131,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, false, &cmd, sizeof(cmd));
+	send_acl(bthost, conn->handle, l2conn->dcid, 0, &cmd, sizeof(cmd));
 }
 
 static bool l2cap_conn_rsp(struct bthost *bthost, struct btconn *conn,
@@ -2501,6 +2528,8 @@ static bool l2cap_le_conn_req(struct bthost *bthost, struct btconn *conn,
 							le16_to_cpu(req->scid),
 							le16_to_cpu(psm));
 		l2conn->mode = L2CAP_MODE_LE_CRED;
+		l2conn->rx_mps = le16_to_cpu(rsp.mps);
+		l2conn->tx_mps = le16_to_cpu(req->mps);
 		l2conn->rx_credits = le16_to_cpu(rsp.credits);
 		l2conn->tx_credits = le16_to_cpu(req->credits);
 
@@ -2749,7 +2778,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, false, &cmd, sizeof(cmd));
+	send_acl(bthost, conn->handle, l2conn->dcid, 0, &cmd, sizeof(cmd));
 }
 
 static void rfcomm_dm_send(struct bthost *bthost, struct btconn *conn,
@@ -2763,7 +2792,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, false, &cmd, sizeof(cmd));
+	send_acl(bthost, conn->handle, l2conn->dcid, 0, &cmd, sizeof(cmd));
 }
 
 static void rfcomm_sabm_recv(struct bthost *bthost, struct btconn *conn,
@@ -4199,7 +4228,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, false, uih_frame, uih_len);
+	send_acl(bthost, handle, rcconn->scid, 0, uih_frame, uih_len);
 
 	free(uih_frame);
 }
-- 
2.52.0
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help