All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/2] Bluetooth: L2CAP: Add definitions for Enhanced Credit Based Mode
@ 2020-01-16 21:27 Luiz Augusto von Dentz
  2020-01-16 21:27 ` [PATCH 2/2] Bluetooth: L2CAP: Add initial code " Luiz Augusto von Dentz
  2020-01-23 17:50 ` [PATCH 1/2] Bluetooth: L2CAP: Add definitions " Marcel Holtmann
  0 siblings, 2 replies; 14+ messages in thread
From: Luiz Augusto von Dentz @ 2020-01-16 21:27 UTC (permalink / raw)
  To: linux-bluetooth

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

This introduces the definitions for the new L2CAP mode called Enhanced
Credit Based Mode.

Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
---
 include/net/bluetooth/l2cap.h | 41 +++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index 093aedebdf0c..b9ea88cfd2d2 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -119,6 +119,11 @@ struct l2cap_conninfo {
 #define L2CAP_LE_CONN_REQ	0x14
 #define L2CAP_LE_CONN_RSP	0x15
 #define L2CAP_LE_CREDITS	0x16
+#define L2CAP_ECRED_CONN_REQ	0x17
+#define L2CAP_ECRED_CONN_RSP	0x18
+#define L2CAP_ECRED_RECONF_REQ	0x19
+#define L2CAP_ECRED_RECONF_RSP	0x1a
+
 
 /* L2CAP extended feature mask */
 #define L2CAP_FEAT_FLOWCTL	0x00000001
@@ -360,6 +365,8 @@ struct l2cap_conf_rfc {
  */
 #define L2CAP_MODE_LE_FLOWCTL	0x80
 
+#define L2CAP_MODE_EXT_FLOWCTL	0x81
+
 struct l2cap_conf_efs {
 	__u8	id;
 	__u8	stype;
@@ -483,6 +490,39 @@ struct l2cap_le_credits {
 	__le16     credits;
 } __packed;
 
+#define L2CAP_ECRED_MIN_MTU		64
+#define L2CAP_ECRED_MIN_MPS		64
+
+struct l2cap_ecred_conn_req {
+	__le16 psm;
+	__le16 mtu;
+	__le16 mps;
+	__le16 credits;
+	__le16 scid[0];
+} __packed;
+
+struct l2cap_ecred_conn_rsp {
+	__le16 mtu;
+	__le16 mps;
+	__le16 credits;
+	__le16 result;
+	__le16 dcid[0];
+};
+
+struct l2cap_ecred_reconf_req {
+	__le16 mtu;
+	__le16 mps;
+	__le16 scid[0];
+} __packed;
+
+#define L2CAP_RECONF_SUCCESS		0x0000
+#define L2CAP_RECONF_INVALID_MTU	0x0001
+#define L2CAP_RECONF_INVALID_MPS	0x0002
+
+struct l2cap_ecred_reconf_rsp {
+	__le16 result;
+} __packed;
+
 /* ----- L2CAP channels and connections ----- */
 struct l2cap_seq_list {
 	__u16	head;
@@ -724,6 +764,7 @@ enum {
 	FLAG_EFS_ENABLE,
 	FLAG_DEFER_SETUP,
 	FLAG_LE_CONN_REQ_SENT,
+	FLAG_ECRED_CONN_REQ_SENT,
 	FLAG_PENDING_SECURITY,
 	FLAG_HOLD_HCI_CONN,
 };
-- 
2.21.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH 2/2] Bluetooth: L2CAP: Add initial code for Enhanced Credit Based Mode
  2020-01-16 21:27 [PATCH 1/2] Bluetooth: L2CAP: Add definitions for Enhanced Credit Based Mode Luiz Augusto von Dentz
@ 2020-01-16 21:27 ` Luiz Augusto von Dentz
  2020-01-23 18:00   ` Marcel Holtmann
  2020-01-23 17:50 ` [PATCH 1/2] Bluetooth: L2CAP: Add definitions " Marcel Holtmann
  1 sibling, 1 reply; 14+ messages in thread
From: Luiz Augusto von Dentz @ 2020-01-16 21:27 UTC (permalink / raw)
  To: linux-bluetooth

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

This adds the initial code for Enhanced Credit Based Mode which
introduces a new socket mode called L2CAP_MODE_EXT_FLOWCTL, which for
the most part work the same as L2CAP_MODE_LE_FLOWCTL but uses different
PDUs to setup the connections and also works over BR/EDR.

Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
---
 include/net/bluetooth/l2cap.h |   4 +
 net/bluetooth/l2cap_core.c    | 510 +++++++++++++++++++++++++++++++++-
 net/bluetooth/l2cap_sock.c    |  39 +--
 3 files changed, 521 insertions(+), 32 deletions(-)

diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index b9ea88cfd2d2..11ae4c93354b 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -295,6 +295,8 @@ struct l2cap_conn_rsp {
 #define L2CAP_CR_LE_ENCRYPTION		0x0008
 #define L2CAP_CR_LE_INVALID_SCID	0x0009
 #define L2CAP_CR_LE_SCID_IN_USE		0X000A
+#define L2CAP_CR_LE_UNACCEPT_PARAMS	0X000B
+#define L2CAP_CR_LE_INVALID_PARAMS	0X000C
 
 /* connect/create channel status */
 #define L2CAP_CS_NO_INFO	0x0000
@@ -964,6 +966,7 @@ void l2cap_cleanup_sockets(void);
 bool l2cap_is_socket(struct socket *sock);
 
 void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan);
+void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan);
 void __l2cap_connect_rsp_defer(struct l2cap_chan *chan);
 
 int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm);
@@ -973,6 +976,7 @@ struct l2cap_chan *l2cap_chan_create(void);
 void l2cap_chan_close(struct l2cap_chan *chan, int reason);
 int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
 		       bdaddr_t *dst, u8 dst_type);
+int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu);
 int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len);
 void l2cap_chan_busy(struct l2cap_chan *chan, int busy);
 int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator);
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index 195459a1e53e..a528526cffac 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -532,6 +532,17 @@ static void l2cap_le_flowctl_init(struct l2cap_chan *chan, u16 tx_credits)
 	skb_queue_head_init(&chan->tx_q);
 }
 
+static void l2cap_ecred_init(struct l2cap_chan *chan, u16 tx_credits)
+{
+	l2cap_le_flowctl_init(chan, tx_credits);
+
+	/* L2CAP implementations shall support a minimum MPS of 64 octets */
+	if (chan->mps < L2CAP_ECRED_MIN_MPS) {
+		chan->mps = L2CAP_ECRED_MIN_MPS;
+		chan->rx_credits = (chan->imtu / chan->mps) + 1;
+	}
+}
+
 void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
 {
 	BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn,
@@ -638,6 +649,7 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
 		break;
 
 	case L2CAP_MODE_LE_FLOWCTL:
+	case L2CAP_MODE_EXT_FLOWCTL:
 		skb_queue_purge(&chan->tx_q);
 		break;
 
@@ -1260,6 +1272,17 @@ static void l2cap_move_done(struct l2cap_chan *chan)
 	}
 }
 
+static bool l2cap_mode_ready(struct l2cap_chan *chan)
+{
+	switch (chan->mode) {
+	case L2CAP_MODE_LE_FLOWCTL:
+	case L2CAP_MODE_EXT_FLOWCTL:
+		return chan->tx_credits ? true : false;
+	}
+
+	return true;
+}
+
 static void l2cap_chan_ready(struct l2cap_chan *chan)
 {
 	/* The channel may have already been flagged as connected in
@@ -1273,7 +1296,7 @@ static void l2cap_chan_ready(struct l2cap_chan *chan)
 	chan->conf_state = 0;
 	__clear_chan_timer(chan);
 
-	if (chan->mode == L2CAP_MODE_LE_FLOWCTL && !chan->tx_credits)
+	if (!l2cap_mode_ready(chan))
 		chan->ops->suspend(chan);
 
 	chan->state = BT_CONNECTED;
@@ -1306,6 +1329,31 @@ static void l2cap_le_connect(struct l2cap_chan *chan)
 		       sizeof(req), &req);
 }
 
+static void l2cap_ecred_connect(struct l2cap_chan *chan)
+{
+	struct l2cap_conn *conn = chan->conn;
+	struct {
+		struct l2cap_ecred_conn_req req;
+		__le16 scid;
+	} __packed pdu;
+
+	if (test_and_set_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags))
+		return;
+
+	l2cap_ecred_init(chan, 0);
+
+	pdu.req.psm     = chan->psm;
+	pdu.req.mtu     = cpu_to_le16(chan->imtu);
+	pdu.req.mps     = cpu_to_le16(chan->mps);
+	pdu.req.credits = cpu_to_le16(chan->rx_credits);
+	pdu.scid        = cpu_to_le16(chan->scid);
+
+	chan->ident = l2cap_get_ident(conn);
+
+	l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_CONN_REQ,
+		       sizeof(pdu), &pdu);
+}
+
 static void l2cap_le_start(struct l2cap_chan *chan)
 {
 	struct l2cap_conn *conn = chan->conn;
@@ -1318,8 +1366,12 @@ static void l2cap_le_start(struct l2cap_chan *chan)
 		return;
 	}
 
-	if (chan->state == BT_CONNECT)
-		l2cap_le_connect(chan);
+	if (chan->state == BT_CONNECT) {
+		if (chan->mode == L2CAP_MODE_EXT_FLOWCTL)
+			l2cap_ecred_connect(chan);
+		else
+			l2cap_le_connect(chan);
+	}
 }
 
 static void l2cap_start_connection(struct l2cap_chan *chan)
@@ -2505,6 +2557,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
 
 	switch (chan->mode) {
 	case L2CAP_MODE_LE_FLOWCTL:
+	case L2CAP_MODE_EXT_FLOWCTL:
 		/* Check outgoing MTU */
 		if (len > chan->omtu)
 			return -EMSGSIZE;
@@ -3773,6 +3826,42 @@ void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan)
 		       &rsp);
 }
 
+void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
+{
+	struct l2cap_ecred_conn_rsp rsp;
+	struct l2cap_conn *conn = chan->conn;
+	u16 ident = chan->ident;
+	int i = 0;
+
+	if (!ident) {
+		return;
+	}
+
+	BT_DBG("chan %p ident %d", chan, ident);
+
+	rsp.mtu     = cpu_to_le16(chan->imtu);
+	rsp.mps     = cpu_to_le16(chan->mps);
+	rsp.credits = cpu_to_le16(chan->rx_credits);
+	rsp.result  = cpu_to_le16(L2CAP_CR_LE_SUCCESS);
+
+	mutex_lock(&conn->chan_lock);
+
+	list_for_each_entry(chan, &conn->chan_l, list) {
+		if (chan->ident != ident)
+			continue;
+
+		/* Reset ident so only one response is sent */
+		chan->ident = 0;
+
+		/* Include all channels pending with the same ident */
+		rsp.dcid[i++] = cpu_to_le16(chan->scid);
+	}
+
+	mutex_unlock(&conn->chan_lock);
+
+	l2cap_send_cmd(conn, ident, L2CAP_ECRED_CONN_RSP, sizeof(rsp), &rsp);
+}
+
 void __l2cap_connect_rsp_defer(struct l2cap_chan *chan)
 {
 	struct l2cap_conn_rsp rsp;
@@ -5714,6 +5803,347 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn,
 	return 0;
 }
 
+static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
+				       struct l2cap_cmd_hdr *cmd, u16 cmd_len,
+				       u8 *data)
+{
+	struct l2cap_ecred_conn_req *req = (void *) data;
+	struct {
+		struct l2cap_ecred_conn_rsp rsp;
+		__le16 dcid[5];
+	} __packed pdu;
+	struct l2cap_chan *chan, *pchan;
+	u16 credits, mtu, mps;
+	__le16 psm;
+	u8 result, len = 0;
+	int i;
+	bool defer = false;
+
+	if (cmd_len < sizeof(*req))
+		return -EPROTO;
+
+	mtu  = __le16_to_cpu(req->mtu);
+	mps  = __le16_to_cpu(req->mps);
+
+	if (mtu < L2CAP_ECRED_MIN_MTU || mps < L2CAP_ECRED_MIN_MPS) {
+		result = L2CAP_CR_LE_UNACCEPT_PARAMS;
+		goto response;
+	}
+
+	psm  = req->psm;
+	credits = 0;
+
+	BT_DBG("psm 0x%2.2x mtu %u mps %u", __le16_to_cpu(psm), mtu, mps);
+
+	memset(&pdu, 0, sizeof(pdu));
+
+	/* Check if we have socket listening on psm */
+	pchan = l2cap_global_chan_by_psm(BT_LISTEN, psm, &conn->hcon->src,
+					 &conn->hcon->dst, LE_LINK);
+	if (!pchan) {
+		result = L2CAP_CR_LE_BAD_PSM;
+		goto response;
+	}
+
+	mutex_lock(&conn->chan_lock);
+	l2cap_chan_lock(pchan);
+
+	if (!smp_sufficient_security(conn->hcon, pchan->sec_level,
+				     SMP_ALLOW_STK)) {
+		result = L2CAP_CR_LE_AUTHENTICATION;
+		goto unlock;
+	}
+
+	result = L2CAP_CR_LE_SUCCESS;
+
+	for (i = 0, cmd_len -= sizeof(req); cmd_len >= sizeof(u16);
+	     i++, cmd_len -= sizeof(u16)) {
+		u16 scid = __le16_to_cpu(req->scid[i]);
+
+		BT_DBG("scid[%d] 0x%4.4x", i, scid);
+
+		pdu.dcid[i] = 0x0000;
+		len += sizeof(*pdu.dcid);
+
+		/* Check for valid dynamic CID range */
+		if (scid < L2CAP_CID_DYN_START || scid > L2CAP_CID_LE_DYN_END) {
+			result = L2CAP_CR_LE_INVALID_SCID;
+			continue;
+		}
+
+		/* Check if we already have channel with that dcid */
+		if (__l2cap_get_chan_by_dcid(conn, scid)) {
+			result = L2CAP_CR_LE_SCID_IN_USE;
+			continue;
+		}
+
+		chan = pchan->ops->new_connection(pchan);
+		if (!chan) {
+			result = L2CAP_CR_LE_NO_MEM;
+			continue;
+		}
+
+		bacpy(&chan->src, &conn->hcon->src);
+		bacpy(&chan->dst, &conn->hcon->dst);
+		chan->src_type = bdaddr_src_type(conn->hcon);
+		chan->dst_type = bdaddr_dst_type(conn->hcon);
+		chan->psm  = psm;
+		chan->dcid = scid;
+		chan->omtu = mtu;
+		chan->remote_mps = mps;
+
+		__l2cap_chan_add(conn, chan);
+
+		l2cap_ecred_init(chan, __le16_to_cpu(req->credits));
+
+		/* Init response */
+		if (!pdu.rsp.credits) {
+			pdu.rsp.mtu = cpu_to_le16(chan->imtu);
+			pdu.rsp.mps = cpu_to_le16(chan->mps);
+			pdu.rsp.credits = cpu_to_le16(chan->rx_credits);
+		}
+
+		pdu.dcid[i] = cpu_to_le16(chan->scid);
+
+		__set_chan_timer(chan, chan->ops->get_sndtimeo(chan));
+
+		chan->ident = cmd->ident;
+
+		if (test_bit(FLAG_DEFER_SETUP, &chan->flags)) {
+			l2cap_state_change(chan, BT_CONNECT2);
+			defer = true;
+			chan->ops->defer(chan);
+		} else {
+			l2cap_chan_ready(chan);
+		}
+	}
+
+unlock:
+	l2cap_chan_unlock(pchan);
+	mutex_unlock(&conn->chan_lock);
+	l2cap_chan_put(pchan);
+
+response:
+	pdu.rsp.result = cpu_to_le16(result);
+
+	if (defer)
+		return 0;
+
+	l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_CONN_RSP,
+		       sizeof(pdu.rsp) + len, &pdu);
+
+	return 0;
+}
+
+static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
+				       struct l2cap_cmd_hdr *cmd, u16 cmd_len,
+				       u8 *data)
+{
+	struct l2cap_ecred_conn_rsp *rsp = (void *) data;
+	struct hci_conn *hcon = conn->hcon;
+	u16 mtu, mps, credits, result;
+	struct l2cap_chan *chan;
+	int err = 0, sec_level;
+	int i = 0;
+
+	if (cmd_len < sizeof(*rsp))
+		return -EPROTO;
+
+	mtu     = __le16_to_cpu(rsp->mtu);
+	mps     = __le16_to_cpu(rsp->mps);
+	credits = __le16_to_cpu(rsp->credits);
+	result  = __le16_to_cpu(rsp->result);
+
+	BT_DBG("mtu %u mps %u credits %u result 0x%4.4x", mtu, mps, credits,
+	       result);
+
+	mutex_lock(&conn->chan_lock);
+
+	cmd_len -= sizeof(*rsp);
+
+	list_for_each_entry(chan, &conn->chan_l, list) {
+		u16 dcid;
+
+		if (chan->ident != cmd->ident ||
+		    chan->mode != L2CAP_MODE_EXT_FLOWCTL ||
+		    chan->state == BT_CONNECTED)
+			continue;
+
+		l2cap_chan_lock(chan);
+
+		/* Check that there is a dcid for each pending channel */
+		if (cmd_len < sizeof(dcid)) {
+			l2cap_chan_del(chan, ECONNREFUSED);
+			l2cap_chan_unlock(chan);
+			continue;
+		}
+
+		dcid = __le16_to_cpu(rsp->dcid[i++]);
+		cmd_len -= sizeof(u16);
+
+		BT_DBG("dcid[%d] 0x%4.4x", i, dcid);
+
+		/* Check if dcid is already in use */
+		if (dcid && __l2cap_get_chan_by_dcid(conn, dcid)) {
+			/* If a device receives a
+			 * L2CAP_CREDIT_BASED_CONNECTION_RSP packet with an
+			 * already-assigned Destination CID, then both the
+			 * original channel and the new channel shall be
+			 * immediately discarded and not used.
+			 */
+			l2cap_chan_del(chan, ECONNREFUSED);
+			l2cap_chan_unlock(chan);
+			chan = __l2cap_get_chan_by_dcid(conn, dcid);
+			l2cap_chan_lock(chan);
+			l2cap_chan_del(chan, ECONNRESET);
+			l2cap_chan_unlock(chan);
+			continue;
+		}
+
+		switch (result) {
+		case L2CAP_CR_LE_AUTHENTICATION:
+		case L2CAP_CR_LE_ENCRYPTION:
+			/* If we already have MITM protection we can't do
+			 * anything.
+			 */
+			if (hcon->sec_level > BT_SECURITY_MEDIUM) {
+				l2cap_chan_del(chan, ECONNREFUSED);
+				break;
+			}
+
+			sec_level = hcon->sec_level + 1;
+			if (chan->sec_level < sec_level)
+				chan->sec_level = sec_level;
+
+			/* We'll need to send a new Connect Request */
+			clear_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags);
+
+			smp_conn_security(hcon, chan->sec_level);
+			break;
+
+		case L2CAP_CR_LE_BAD_PSM:
+			l2cap_chan_del(chan, ECONNREFUSED);
+			break;
+
+		default:
+			/* If dcid was not set it means channels was refused */
+			if (!dcid) {
+				l2cap_chan_del(chan, ECONNREFUSED);
+				break;
+			}
+
+			chan->ident = 0;
+			chan->dcid = dcid;
+			chan->omtu = mtu;
+			chan->remote_mps = mps;
+			chan->tx_credits = credits;
+			l2cap_chan_ready(chan);
+			break;
+		}
+
+		l2cap_chan_unlock(chan);
+	}
+
+	mutex_unlock(&conn->chan_lock);
+
+	return err;
+}
+
+static inline int l2cap_ecred_reconf_req(struct l2cap_conn *conn,
+					 struct l2cap_cmd_hdr *cmd, u16 cmd_len,
+					 u8 *data)
+{
+	struct l2cap_ecred_reconf_req *req = (void *) data;
+	struct l2cap_ecred_reconf_rsp rsp;
+	u16 mtu, mps, result;
+	struct l2cap_chan *chan;
+	int i;
+
+	if (cmd_len < sizeof(*req)) {
+		result = L2CAP_CR_LE_INVALID_PARAMS;
+		goto respond;
+	}
+
+	mtu = __le16_to_cpu(req->mtu);
+	mps = __le16_to_cpu(req->mps);
+
+	BT_DBG("mtu %u mps %u", mtu, mps);
+
+	if (mtu < L2CAP_ECRED_MIN_MTU) {
+		result = L2CAP_RECONF_INVALID_MTU;
+		goto respond;
+	}
+
+	if (mps < L2CAP_ECRED_MIN_MPS) {
+		result = L2CAP_RECONF_INVALID_MPS;
+		goto respond;
+	}
+
+	result = L2CAP_RECONF_SUCCESS;
+
+	for (i = 0, cmd_len -= sizeof(*req); cmd_len < sizeof(u16);
+	     i++, cmd_len -= sizeof(u16)) {
+		u16 scid;
+
+		scid = __le16_to_cpu(req->scid[i]);
+		if (!scid)
+			return -EPROTO;
+
+		chan = __l2cap_get_chan_by_dcid(conn, scid);
+		if (!chan)
+			continue;
+
+		/* If the MTU value is decreased for any of the included
+		 * channels, then the receiver shall disconnect all
+		 * included channels.
+		 */
+		if (chan->omtu > mtu) {
+			BT_ERR("chan %p decreased MTU %u -> %u", chan,
+			       chan->omtu, mtu);
+			result = L2CAP_RECONF_INVALID_MTU;
+		}
+
+		chan->omtu = mtu;
+		chan->remote_mps = mps;
+	}
+
+respond:
+	rsp.result = cpu_to_le16(result);
+
+	l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_RECONF_RSP, sizeof(rsp),
+		       &rsp);
+
+	return 0;
+}
+
+static inline int l2cap_ecred_reconf_rsp(struct l2cap_conn *conn,
+					 struct l2cap_cmd_hdr *cmd, u16 cmd_len,
+					 u8 *data)
+{
+	struct l2cap_chan *chan;
+	struct l2cap_ecred_conn_rsp *rsp = (void *) data;
+	u16 result;
+
+	if (cmd_len < sizeof(*rsp))
+		return -EPROTO;
+
+	result = __le16_to_cpu(rsp->result);
+
+	BT_DBG("result 0x%4.4x", rsp->result);
+
+	if (!result)
+		return 0;
+
+	list_for_each_entry(chan, &conn->chan_l, list) {
+		if (chan->ident != cmd->ident)
+			continue;
+
+		l2cap_chan_del(chan, ECONNRESET);
+	}
+
+	return 0;
+}
+
 static inline int l2cap_le_command_rej(struct l2cap_conn *conn,
 				       struct l2cap_cmd_hdr *cmd, u16 cmd_len,
 				       u8 *data)
@@ -5769,6 +6199,22 @@ static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn,
 		err = l2cap_le_credits(conn, cmd, cmd_len, data);
 		break;
 
+	case L2CAP_ECRED_CONN_REQ:
+		err = l2cap_ecred_conn_req(conn, cmd, cmd_len, data);
+		break;
+
+	case L2CAP_ECRED_CONN_RSP:
+		err = l2cap_ecred_conn_rsp(conn, cmd, cmd_len, data);
+		break;
+
+	case L2CAP_ECRED_RECONF_REQ:
+		err = l2cap_ecred_reconf_req(conn, cmd, cmd_len, data);
+		break;
+
+	case L2CAP_ECRED_RECONF_RSP:
+		err = l2cap_ecred_reconf_rsp(conn, cmd, cmd_len, data);
+		break;
+
 	case L2CAP_DISCONN_REQ:
 		err = l2cap_disconnect_req(conn, cmd, cmd_len, data);
 		break;
@@ -6814,11 +7260,13 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
 	struct l2cap_le_credits pkt;
 	u16 return_credits;
 
-	return_credits = ((chan->imtu / chan->mps) + 1) - chan->rx_credits;
+	return_credits = (chan->imtu / chan->mps) + 1;
 
-	if (!return_credits)
+	if (chan->rx_credits >= return_credits)
 		return;
 
+	return_credits -= chan->rx_credits;
+
 	BT_DBG("chan %p returning %u credits to sender", chan, return_credits);
 
 	chan->rx_credits += return_credits;
@@ -6831,7 +7279,7 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
 	l2cap_send_cmd(conn, chan->ident, L2CAP_LE_CREDITS, sizeof(pkt), &pkt);
 }
 
-static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
+static int l2cap_ecred_recv(struct l2cap_chan *chan, struct sk_buff *skb)
 {
 	int err;
 
@@ -6846,7 +7294,7 @@ static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
 	return err;
 }
 
-static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
+static int l2cap_ecred_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
 {
 	int err;
 
@@ -6894,7 +7342,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
 		}
 
 		if (skb->len == sdu_len)
-			return l2cap_le_recv(chan, skb);
+			return l2cap_ecred_recv(chan, skb);
 
 		chan->sdu = skb;
 		chan->sdu_len = sdu_len;
@@ -6926,7 +7374,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
 	skb = NULL;
 
 	if (chan->sdu->len == chan->sdu_len) {
-		err = l2cap_le_recv(chan, chan->sdu);
+		err = l2cap_ecred_recv(chan, chan->sdu);
 		if (!err) {
 			chan->sdu = NULL;
 			chan->sdu_last_frag = NULL;
@@ -6987,7 +7435,8 @@ static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid,
 
 	switch (chan->mode) {
 	case L2CAP_MODE_LE_FLOWCTL:
-		if (l2cap_le_data_rcv(chan, skb) < 0)
+	case L2CAP_MODE_EXT_FLOWCTL:
+		if (l2cap_ecred_data_rcv(chan, skb) < 0)
 			goto drop;
 
 		goto done;
@@ -7214,8 +7663,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
 	struct hci_dev *hdev;
 	int err;
 
-	BT_DBG("%pMR -> %pMR (type %u) psm 0x%2.2x", &chan->src, dst,
-	       dst_type, __le16_to_cpu(psm));
+	BT_DBG("%pMR -> %pMR (type %u) psm 0x%4.4x mode 0x%2.2x", &chan->src,
+	       dst, dst_type, __le16_to_cpu(psm), chan->mode);
 
 	hdev = hci_get_route(dst, &chan->src, chan->src_type);
 	if (!hdev)
@@ -7244,6 +7693,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
 		break;
 	case L2CAP_MODE_LE_FLOWCTL:
 		break;
+	case L2CAP_MODE_EXT_FLOWCTL:
+		break;
 	case L2CAP_MODE_ERTM:
 	case L2CAP_MODE_STREAMING:
 		if (!disable_ertm)
@@ -7368,6 +7819,38 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
 }
 EXPORT_SYMBOL_GPL(l2cap_chan_connect);
 
+static void l2cap_ecred_reconfigure(struct l2cap_chan *chan)
+{
+	struct l2cap_conn *conn = chan->conn;
+	struct {
+		struct l2cap_ecred_reconf_req req;
+		__le16 scid;
+	} pdu;
+
+	pdu.req.mtu = cpu_to_le16(chan->imtu);
+	pdu.req.mps = cpu_to_le16(chan->mps);
+	pdu.scid    = cpu_to_le16(chan->scid);
+
+	chan->ident = l2cap_get_ident(conn);
+
+	l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_RECONF_REQ,
+		       sizeof(pdu), &pdu);
+}
+
+int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu)
+{
+	if (chan->imtu > mtu)
+		return -EINVAL;
+
+	BT_DBG("chan %p mtu 0x%4.4x", chan, mtu);
+
+	chan->imtu = mtu;
+
+	l2cap_ecred_reconfigure(chan);
+
+	return 0;
+}
+
 /* ---- L2CAP interface with lower layer (HCI) ---- */
 
 int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr)
@@ -7579,7 +8062,8 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
 			else
 				__set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
 		} else if (chan->state == BT_CONNECT2 &&
-			   chan->mode != L2CAP_MODE_LE_FLOWCTL) {
+			   !(chan->mode == L2CAP_MODE_EXT_FLOWCTL ||
+			    chan->mode == L2CAP_MODE_LE_FLOWCTL)) {
 			struct l2cap_conn_rsp rsp;
 			__u16 res, stat;
 
diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index a7be8b59b3c2..12c557f7f40f 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -232,7 +232,7 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr,
 			return -EINVAL;
 	}
 
-	if (chan->psm && bdaddr_type_is_le(chan->src_type))
+	if (chan->psm && bdaddr_type_is_le(chan->src_type) && !chan->mode)
 		chan->mode = L2CAP_MODE_LE_FLOWCTL;
 
 	err = l2cap_chan_connect(chan, la.l2_psm, __le16_to_cpu(la.l2_cid),
@@ -273,6 +273,7 @@ static int l2cap_sock_listen(struct socket *sock, int backlog)
 	switch (chan->mode) {
 	case L2CAP_MODE_BASIC:
 	case L2CAP_MODE_LE_FLOWCTL:
+	case L2CAP_MODE_EXT_FLOWCTL:
 		break;
 	case L2CAP_MODE_ERTM:
 	case L2CAP_MODE_STREAMING:
@@ -408,16 +409,6 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
 
 	switch (optname) {
 	case L2CAP_OPTIONS:
-		/* LE sockets should use BT_SNDMTU/BT_RCVMTU, but since
-		 * legacy ATT code depends on getsockopt for
-		 * L2CAP_OPTIONS we need to let this pass.
-		 */
-		if (bdaddr_type_is_le(chan->src_type) &&
-		    chan->scid != L2CAP_CID_ATT) {
-			err = -EINVAL;
-			break;
-		}
-
 		memset(&opts, 0, sizeof(opts));
 		opts.imtu     = chan->imtu;
 		opts.omtu     = chan->omtu;
@@ -427,6 +418,8 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
 		opts.max_tx   = chan->max_tx;
 		opts.txwin_size = chan->tx_win;
 
+		BT_DBG("mode 0x%2.2x", chan->mode);
+
 		len = min_t(unsigned int, len, sizeof(opts));
 		if (copy_to_user(optval, (char *) &opts, len))
 			err = -EFAULT;
@@ -643,11 +636,6 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname,
 
 	switch (optname) {
 	case L2CAP_OPTIONS:
-		if (bdaddr_type_is_le(chan->src_type)) {
-			err = -EINVAL;
-			break;
-		}
-
 		if (sk->sk_state == BT_CONNECTED) {
 			err = -EINVAL;
 			break;
@@ -680,6 +668,7 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname,
 		chan->mode = opts.mode;
 		switch (chan->mode) {
 		case L2CAP_MODE_LE_FLOWCTL:
+		case L2CAP_MODE_EXT_FLOWCTL:
 			break;
 		case L2CAP_MODE_BASIC:
 			clear_bit(CONF_STATE2_DEVICE, &chan->conf_state);
@@ -694,6 +683,8 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname,
 			break;
 		}
 
+		BT_DBG("mode 0x%2.2x", chan->mode);
+
 		chan->imtu = opts.imtu;
 		chan->omtu = opts.omtu;
 		chan->fcs  = opts.fcs;
@@ -926,7 +917,8 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
 			break;
 		}
 
-		if (sk->sk_state == BT_CONNECTED) {
+		if (chan->mode == L2CAP_MODE_LE_FLOWCTL &&
+		    sk->sk_state == BT_CONNECTED) {
 			err = -EISCONN;
 			break;
 		}
@@ -936,7 +928,12 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
 			break;
 		}
 
-		chan->imtu = opt;
+		if (chan->mode == L2CAP_MODE_EXT_FLOWCTL &&
+		    sk->sk_state == BT_CONNECTED)
+			err = l2cap_chan_reconfigure(chan, opt);
+		else
+			chan->imtu = opt;
+
 		break;
 
 	default:
@@ -991,7 +988,11 @@ static int l2cap_sock_recvmsg(struct socket *sock, struct msghdr *msg,
 
 	if (sk->sk_state == BT_CONNECT2 && test_bit(BT_SK_DEFER_SETUP,
 						    &bt_sk(sk)->flags)) {
-		if (bdaddr_type_is_le(pi->chan->src_type)) {
+		if (pi->chan->mode == L2CAP_MODE_EXT_FLOWCTL) {
+			sk->sk_state = BT_CONNECTED;
+			pi->chan->state = BT_CONNECTED;
+			__l2cap_ecred_conn_rsp_defer(pi->chan);
+		} if (bdaddr_type_is_le(pi->chan->src_type)) {
 			sk->sk_state = BT_CONNECTED;
 			pi->chan->state = BT_CONNECTED;
 			__l2cap_le_connect_rsp_defer(pi->chan);
-- 
2.21.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH 1/2] Bluetooth: L2CAP: Add definitions for Enhanced Credit Based Mode
  2020-01-16 21:27 [PATCH 1/2] Bluetooth: L2CAP: Add definitions for Enhanced Credit Based Mode Luiz Augusto von Dentz
  2020-01-16 21:27 ` [PATCH 2/2] Bluetooth: L2CAP: Add initial code " Luiz Augusto von Dentz
@ 2020-01-23 17:50 ` Marcel Holtmann
  1 sibling, 0 replies; 14+ messages in thread
From: Marcel Holtmann @ 2020-01-23 17:50 UTC (permalink / raw)
  To: Luiz Augusto von Dentz; +Cc: linux-bluetooth

Hi Luiz,

> This introduces the definitions for the new L2CAP mode called Enhanced
> Credit Based Mode.
> 
> Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
> ---
> include/net/bluetooth/l2cap.h | 41 +++++++++++++++++++++++++++++++++++
> 1 file changed, 41 insertions(+)
> 
> diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
> index 093aedebdf0c..b9ea88cfd2d2 100644
> --- a/include/net/bluetooth/l2cap.h
> +++ b/include/net/bluetooth/l2cap.h
> @@ -119,6 +119,11 @@ struct l2cap_conninfo {
> #define L2CAP_LE_CONN_REQ	0x14
> #define L2CAP_LE_CONN_RSP	0x15
> #define L2CAP_LE_CREDITS	0x16
> +#define L2CAP_ECRED_CONN_REQ	0x17
> +#define L2CAP_ECRED_CONN_RSP	0x18
> +#define L2CAP_ECRED_RECONF_REQ	0x19
> +#define L2CAP_ECRED_RECONF_RSP	0x1a
> +
> 

can you please get rid of this extra empty line.

> /* L2CAP extended feature mask */
> #define L2CAP_FEAT_FLOWCTL	0x00000001
> @@ -360,6 +365,8 @@ struct l2cap_conf_rfc {
>  */
> #define L2CAP_MODE_LE_FLOWCTL	0x80
> 
> +#define L2CAP_MODE_EXT_FLOWCTL	0x81
> +

And here just don’t add another one. Just put it after L2CAP_MODE_LE_FLOWCTL

> struct l2cap_conf_efs {
> 	__u8	id;
> 	__u8	stype;
> @@ -483,6 +490,39 @@ struct l2cap_le_credits {
> 	__le16     credits;
> } __packed;
> 
> +#define L2CAP_ECRED_MIN_MTU		64
> +#define L2CAP_ECRED_MIN_MPS		64
> +
> +struct l2cap_ecred_conn_req {
> +	__le16 psm;
> +	__le16 mtu;
> +	__le16 mps;
> +	__le16 credits;
> +	__le16 scid[0];
> +} __packed;
> +
> +struct l2cap_ecred_conn_rsp {
> +	__le16 mtu;
> +	__le16 mps;
> +	__le16 credits;
> +	__le16 result;
> +	__le16 dcid[0];
> +};
> +
> +struct l2cap_ecred_reconf_req {
> +	__le16 mtu;
> +	__le16 mps;
> +	__le16 scid[0];
> +} __packed;
> +
> +#define L2CAP_RECONF_SUCCESS		0x0000
> +#define L2CAP_RECONF_INVALID_MTU	0x0001
> +#define L2CAP_RECONF_INVALID_MPS	0x0002
> +
> +struct l2cap_ecred_reconf_rsp {
> +	__le16 result;
> +} __packed;
> +
> /* ----- L2CAP channels and connections ----- */
> struct l2cap_seq_list {
> 	__u16	head;
> @@ -724,6 +764,7 @@ enum {
> 	FLAG_EFS_ENABLE,
> 	FLAG_DEFER_SETUP,
> 	FLAG_LE_CONN_REQ_SENT,
> +	FLAG_ECRED_CONN_REQ_SENT,
> 	FLAG_PENDING_SECURITY,
> 	FLAG_HOLD_HCI_CONN,

The rest looks good.

Regards

Marcel


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] Bluetooth: L2CAP: Add initial code for Enhanced Credit Based Mode
  2020-01-16 21:27 ` [PATCH 2/2] Bluetooth: L2CAP: Add initial code " Luiz Augusto von Dentz
@ 2020-01-23 18:00   ` Marcel Holtmann
  2020-01-23 23:29     ` Luiz Augusto von Dentz
  0 siblings, 1 reply; 14+ messages in thread
From: Marcel Holtmann @ 2020-01-23 18:00 UTC (permalink / raw)
  To: Luiz Augusto von Dentz; +Cc: linux-bluetooth

Hi Luiz,

> This adds the initial code for Enhanced Credit Based Mode which
> introduces a new socket mode called L2CAP_MODE_EXT_FLOWCTL, which for
> the most part work the same as L2CAP_MODE_LE_FLOWCTL but uses different
> PDUs to setup the connections and also works over BR/EDR.
> 
> Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
> ---
> include/net/bluetooth/l2cap.h |   4 +
> net/bluetooth/l2cap_core.c    | 510 +++++++++++++++++++++++++++++++++-
> net/bluetooth/l2cap_sock.c    |  39 +--
> 3 files changed, 521 insertions(+), 32 deletions(-)
> 
> diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
> index b9ea88cfd2d2..11ae4c93354b 100644
> --- a/include/net/bluetooth/l2cap.h
> +++ b/include/net/bluetooth/l2cap.h
> @@ -295,6 +295,8 @@ struct l2cap_conn_rsp {
> #define L2CAP_CR_LE_ENCRYPTION		0x0008
> #define L2CAP_CR_LE_INVALID_SCID	0x0009
> #define L2CAP_CR_LE_SCID_IN_USE		0X000A
> +#define L2CAP_CR_LE_UNACCEPT_PARAMS	0X000B
> +#define L2CAP_CR_LE_INVALID_PARAMS	0X000C
> 
> /* connect/create channel status */
> #define L2CAP_CS_NO_INFO	0x0000
> @@ -964,6 +966,7 @@ void l2cap_cleanup_sockets(void);
> bool l2cap_is_socket(struct socket *sock);
> 
> void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan);
> +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan);
> void __l2cap_connect_rsp_defer(struct l2cap_chan *chan);
> 
> int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm);
> @@ -973,6 +976,7 @@ struct l2cap_chan *l2cap_chan_create(void);
> void l2cap_chan_close(struct l2cap_chan *chan, int reason);
> int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> 		       bdaddr_t *dst, u8 dst_type);
> +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu);
> int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len);
> void l2cap_chan_busy(struct l2cap_chan *chan, int busy);
> int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator);
> diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
> index 195459a1e53e..a528526cffac 100644
> --- a/net/bluetooth/l2cap_core.c
> +++ b/net/bluetooth/l2cap_core.c
> @@ -532,6 +532,17 @@ static void l2cap_le_flowctl_init(struct l2cap_chan *chan, u16 tx_credits)
> 	skb_queue_head_init(&chan->tx_q);
> }
> 
> +static void l2cap_ecred_init(struct l2cap_chan *chan, u16 tx_credits)
> +{
> +	l2cap_le_flowctl_init(chan, tx_credits);
> +
> +	/* L2CAP implementations shall support a minimum MPS of 64 octets */
> +	if (chan->mps < L2CAP_ECRED_MIN_MPS) {
> +		chan->mps = L2CAP_ECRED_MIN_MPS;
> +		chan->rx_credits = (chan->imtu / chan->mps) + 1;
> +	}
> +}
> +
> void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
> {
> 	BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn,
> @@ -638,6 +649,7 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
> 		break;
> 
> 	case L2CAP_MODE_LE_FLOWCTL:
> +	case L2CAP_MODE_EXT_FLOWCTL:
> 		skb_queue_purge(&chan->tx_q);
> 		break;
> 
> @@ -1260,6 +1272,17 @@ static void l2cap_move_done(struct l2cap_chan *chan)
> 	}
> }
> 
> +static bool l2cap_mode_ready(struct l2cap_chan *chan)
> +{
> +	switch (chan->mode) {
> +	case L2CAP_MODE_LE_FLOWCTL:
> +	case L2CAP_MODE_EXT_FLOWCTL:
> +		return chan->tx_credits ? true : false;
> +	}
> +
> +	return true;
> +}
> +
> static void l2cap_chan_ready(struct l2cap_chan *chan)
> {
> 	/* The channel may have already been flagged as connected in
> @@ -1273,7 +1296,7 @@ static void l2cap_chan_ready(struct l2cap_chan *chan)
> 	chan->conf_state = 0;
> 	__clear_chan_timer(chan);
> 
> -	if (chan->mode == L2CAP_MODE_LE_FLOWCTL && !chan->tx_credits)
> +	if (!l2cap_mode_ready(chan))
> 		chan->ops->suspend(chan);

	switch (chan->mode) {
	case ..
	case ..
		if (!chan->tx_credits)
			chan->ops->suspend(chan);
		break;
	}

> 
> 	chan->state = BT_CONNECTED;
> @@ -1306,6 +1329,31 @@ static void l2cap_le_connect(struct l2cap_chan *chan)
> 		       sizeof(req), &req);
> }
> 
> +static void l2cap_ecred_connect(struct l2cap_chan *chan)
> +{
> +	struct l2cap_conn *conn = chan->conn;
> +	struct {
> +		struct l2cap_ecred_conn_req req;
> +		__le16 scid;
> +	} __packed pdu;
> +
> +	if (test_and_set_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags))
> +		return;
> +
> +	l2cap_ecred_init(chan, 0);
> +
> +	pdu.req.psm     = chan->psm;
> +	pdu.req.mtu     = cpu_to_le16(chan->imtu);
> +	pdu.req.mps     = cpu_to_le16(chan->mps);
> +	pdu.req.credits = cpu_to_le16(chan->rx_credits);
> +	pdu.scid        = cpu_to_le16(chan->scid);
> +
> +	chan->ident = l2cap_get_ident(conn);
> +
> +	l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_CONN_REQ,
> +		       sizeof(pdu), &pdu);
> +}
> +
> static void l2cap_le_start(struct l2cap_chan *chan)
> {
> 	struct l2cap_conn *conn = chan->conn;
> @@ -1318,8 +1366,12 @@ static void l2cap_le_start(struct l2cap_chan *chan)
> 		return;
> 	}
> 
> -	if (chan->state == BT_CONNECT)
> -		l2cap_le_connect(chan);
> +	if (chan->state == BT_CONNECT) {
> +		if (chan->mode == L2CAP_MODE_EXT_FLOWCTL)
> +			l2cap_ecred_connect(chan);
> +		else
> +			l2cap_le_connect(chan);
> +	}
> }
> 
> static void l2cap_start_connection(struct l2cap_chan *chan)
> @@ -2505,6 +2557,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
> 
> 	switch (chan->mode) {
> 	case L2CAP_MODE_LE_FLOWCTL:
> +	case L2CAP_MODE_EXT_FLOWCTL:
> 		/* Check outgoing MTU */
> 		if (len > chan->omtu)
> 			return -EMSGSIZE;
> @@ -3773,6 +3826,42 @@ void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan)
> 		       &rsp);
> }
> 
> +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
> +{
> +	struct l2cap_ecred_conn_rsp rsp;
> +	struct l2cap_conn *conn = chan->conn;
> +	u16 ident = chan->ident;
> +	int i = 0;
> +
> +	if (!ident) {
> +		return;
> +	}
> +

No { } here.

> +	BT_DBG("chan %p ident %d", chan, ident);
> +
> +	rsp.mtu     = cpu_to_le16(chan->imtu);
> +	rsp.mps     = cpu_to_le16(chan->mps);
> +	rsp.credits = cpu_to_le16(chan->rx_credits);
> +	rsp.result  = cpu_to_le16(L2CAP_CR_LE_SUCCESS);
> +
> +	mutex_lock(&conn->chan_lock);
> +
> +	list_for_each_entry(chan, &conn->chan_l, list) {
> +		if (chan->ident != ident)
> +			continue;
> +
> +		/* Reset ident so only one response is sent */
> +		chan->ident = 0;
> +
> +		/* Include all channels pending with the same ident */
> +		rsp.dcid[i++] = cpu_to_le16(chan->scid);

This doesn’t work. The sizeof(rsp) has no dcid in there. You are overflowing into conn struct here.

> +	}
> +
> +	mutex_unlock(&conn->chan_lock);
> +
> +	l2cap_send_cmd(conn, ident, L2CAP_ECRED_CONN_RSP, sizeof(rsp), &rsp);
> +}
> +
> void __l2cap_connect_rsp_defer(struct l2cap_chan *chan)
> {
> 	struct l2cap_conn_rsp rsp;
> @@ -5714,6 +5803,347 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn,
> 	return 0;
> }
> 
> +static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
> +				       struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> +				       u8 *data)
> +{
> +	struct l2cap_ecred_conn_req *req = (void *) data;
> +	struct {
> +		struct l2cap_ecred_conn_rsp rsp;
> +		__le16 dcid[5];
> +	} __packed pdu;
> +	struct l2cap_chan *chan, *pchan;
> +	u16 credits, mtu, mps;
> +	__le16 psm;
> +	u8 result, len = 0;
> +	int i;
> +	bool defer = false;
> +
> +	if (cmd_len < sizeof(*req))
> +		return -EPROTO;
> +
> +	mtu  = __le16_to_cpu(req->mtu);
> +	mps  = __le16_to_cpu(req->mps);
> +
> +	if (mtu < L2CAP_ECRED_MIN_MTU || mps < L2CAP_ECRED_MIN_MPS) {
> +		result = L2CAP_CR_LE_UNACCEPT_PARAMS;
> +		goto response;
> +	}
> +
> +	psm  = req->psm;
> +	credits = 0;
> +
> +	BT_DBG("psm 0x%2.2x mtu %u mps %u", __le16_to_cpu(psm), mtu, mps);
> +
> +	memset(&pdu, 0, sizeof(pdu));
> +
> +	/* Check if we have socket listening on psm */
> +	pchan = l2cap_global_chan_by_psm(BT_LISTEN, psm, &conn->hcon->src,
> +					 &conn->hcon->dst, LE_LINK);
> +	if (!pchan) {
> +		result = L2CAP_CR_LE_BAD_PSM;
> +		goto response;
> +	}
> +
> +	mutex_lock(&conn->chan_lock);
> +	l2cap_chan_lock(pchan);
> +
> +	if (!smp_sufficient_security(conn->hcon, pchan->sec_level,
> +				     SMP_ALLOW_STK)) {
> +		result = L2CAP_CR_LE_AUTHENTICATION;
> +		goto unlock;
> +	}
> +
> +	result = L2CAP_CR_LE_SUCCESS;
> +
> +	for (i = 0, cmd_len -= sizeof(req); cmd_len >= sizeof(u16);
> +	     i++, cmd_len -= sizeof(u16)) {

Can we simplify the loop statement or switch to while statement?

> +		u16 scid = __le16_to_cpu(req->scid[i]);
> +
> +		BT_DBG("scid[%d] 0x%4.4x", i, scid);
> +
> +		pdu.dcid[i] = 0x0000;
> +		len += sizeof(*pdu.dcid);
> +
> +		/* Check for valid dynamic CID range */
> +		if (scid < L2CAP_CID_DYN_START || scid > L2CAP_CID_LE_DYN_END) {
> +			result = L2CAP_CR_LE_INVALID_SCID;
> +			continue;
> +		}
> +
> +		/* Check if we already have channel with that dcid */
> +		if (__l2cap_get_chan_by_dcid(conn, scid)) {
> +			result = L2CAP_CR_LE_SCID_IN_USE;
> +			continue;
> +		}
> +
> +		chan = pchan->ops->new_connection(pchan);
> +		if (!chan) {
> +			result = L2CAP_CR_LE_NO_MEM;
> +			continue;
> +		}
> +
> +		bacpy(&chan->src, &conn->hcon->src);
> +		bacpy(&chan->dst, &conn->hcon->dst);
> +		chan->src_type = bdaddr_src_type(conn->hcon);
> +		chan->dst_type = bdaddr_dst_type(conn->hcon);
> +		chan->psm  = psm;
> +		chan->dcid = scid;
> +		chan->omtu = mtu;
> +		chan->remote_mps = mps;
> +
> +		__l2cap_chan_add(conn, chan);
> +
> +		l2cap_ecred_init(chan, __le16_to_cpu(req->credits));
> +
> +		/* Init response */
> +		if (!pdu.rsp.credits) {
> +			pdu.rsp.mtu = cpu_to_le16(chan->imtu);
> +			pdu.rsp.mps = cpu_to_le16(chan->mps);
> +			pdu.rsp.credits = cpu_to_le16(chan->rx_credits);
> +		}
> +
> +		pdu.dcid[i] = cpu_to_le16(chan->scid);
> +
> +		__set_chan_timer(chan, chan->ops->get_sndtimeo(chan));
> +
> +		chan->ident = cmd->ident;
> +
> +		if (test_bit(FLAG_DEFER_SETUP, &chan->flags)) {
> +			l2cap_state_change(chan, BT_CONNECT2);
> +			defer = true;
> +			chan->ops->defer(chan);
> +		} else {
> +			l2cap_chan_ready(chan);
> +		}
> +	}
> +
> +unlock:
> +	l2cap_chan_unlock(pchan);
> +	mutex_unlock(&conn->chan_lock);
> +	l2cap_chan_put(pchan);
> +
> +response:
> +	pdu.rsp.result = cpu_to_le16(result);
> +
> +	if (defer)
> +		return 0;
> +
> +	l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_CONN_RSP,
> +		       sizeof(pdu.rsp) + len, &pdu);
> +
> +	return 0;
> +}
> +
> +static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
> +				       struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> +				       u8 *data)
> +{
> +	struct l2cap_ecred_conn_rsp *rsp = (void *) data;
> +	struct hci_conn *hcon = conn->hcon;
> +	u16 mtu, mps, credits, result;
> +	struct l2cap_chan *chan;
> +	int err = 0, sec_level;
> +	int i = 0;
> +
> +	if (cmd_len < sizeof(*rsp))
> +		return -EPROTO;
> +
> +	mtu     = __le16_to_cpu(rsp->mtu);
> +	mps     = __le16_to_cpu(rsp->mps);
> +	credits = __le16_to_cpu(rsp->credits);
> +	result  = __le16_to_cpu(rsp->result);
> +
> +	BT_DBG("mtu %u mps %u credits %u result 0x%4.4x", mtu, mps, credits,
> +	       result);
> +
> +	mutex_lock(&conn->chan_lock);
> +
> +	cmd_len -= sizeof(*rsp);
> +
> +	list_for_each_entry(chan, &conn->chan_l, list) {
> +		u16 dcid;
> +
> +		if (chan->ident != cmd->ident ||
> +		    chan->mode != L2CAP_MODE_EXT_FLOWCTL ||
> +		    chan->state == BT_CONNECTED)
> +			continue;
> +
> +		l2cap_chan_lock(chan);
> +
> +		/* Check that there is a dcid for each pending channel */
> +		if (cmd_len < sizeof(dcid)) {
> +			l2cap_chan_del(chan, ECONNREFUSED);
> +			l2cap_chan_unlock(chan);
> +			continue;
> +		}
> +
> +		dcid = __le16_to_cpu(rsp->dcid[i++]);
> +		cmd_len -= sizeof(u16);
> +
> +		BT_DBG("dcid[%d] 0x%4.4x", i, dcid);
> +
> +		/* Check if dcid is already in use */
> +		if (dcid && __l2cap_get_chan_by_dcid(conn, dcid)) {
> +			/* If a device receives a
> +			 * L2CAP_CREDIT_BASED_CONNECTION_RSP packet with an
> +			 * already-assigned Destination CID, then both the
> +			 * original channel and the new channel shall be
> +			 * immediately discarded and not used.
> +			 */
> +			l2cap_chan_del(chan, ECONNREFUSED);
> +			l2cap_chan_unlock(chan);
> +			chan = __l2cap_get_chan_by_dcid(conn, dcid);
> +			l2cap_chan_lock(chan);
> +			l2cap_chan_del(chan, ECONNRESET);
> +			l2cap_chan_unlock(chan);
> +			continue;
> +		}
> +
> +		switch (result) {
> +		case L2CAP_CR_LE_AUTHENTICATION:
> +		case L2CAP_CR_LE_ENCRYPTION:
> +			/* If we already have MITM protection we can't do
> +			 * anything.
> +			 */
> +			if (hcon->sec_level > BT_SECURITY_MEDIUM) {
> +				l2cap_chan_del(chan, ECONNREFUSED);
> +				break;
> +			}
> +
> +			sec_level = hcon->sec_level + 1;
> +			if (chan->sec_level < sec_level)
> +				chan->sec_level = sec_level;
> +
> +			/* We'll need to send a new Connect Request */
> +			clear_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags);
> +
> +			smp_conn_security(hcon, chan->sec_level);
> +			break;
> +
> +		case L2CAP_CR_LE_BAD_PSM:
> +			l2cap_chan_del(chan, ECONNREFUSED);
> +			break;
> +
> +		default:
> +			/* If dcid was not set it means channels was refused */
> +			if (!dcid) {
> +				l2cap_chan_del(chan, ECONNREFUSED);
> +				break;
> +			}
> +
> +			chan->ident = 0;
> +			chan->dcid = dcid;
> +			chan->omtu = mtu;
> +			chan->remote_mps = mps;
> +			chan->tx_credits = credits;
> +			l2cap_chan_ready(chan);
> +			break;
> +		}
> +
> +		l2cap_chan_unlock(chan);
> +	}
> +
> +	mutex_unlock(&conn->chan_lock);
> +
> +	return err;
> +}
> +
> +static inline int l2cap_ecred_reconf_req(struct l2cap_conn *conn,
> +					 struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> +					 u8 *data)
> +{
> +	struct l2cap_ecred_reconf_req *req = (void *) data;
> +	struct l2cap_ecred_reconf_rsp rsp;
> +	u16 mtu, mps, result;
> +	struct l2cap_chan *chan;
> +	int i;
> +
> +	if (cmd_len < sizeof(*req)) {
> +		result = L2CAP_CR_LE_INVALID_PARAMS;
> +		goto respond;
> +	}
> +
> +	mtu = __le16_to_cpu(req->mtu);
> +	mps = __le16_to_cpu(req->mps);
> +
> +	BT_DBG("mtu %u mps %u", mtu, mps);
> +
> +	if (mtu < L2CAP_ECRED_MIN_MTU) {
> +		result = L2CAP_RECONF_INVALID_MTU;
> +		goto respond;
> +	}
> +
> +	if (mps < L2CAP_ECRED_MIN_MPS) {
> +		result = L2CAP_RECONF_INVALID_MPS;
> +		goto respond;
> +	}
> +
> +	result = L2CAP_RECONF_SUCCESS;
> +
> +	for (i = 0, cmd_len -= sizeof(*req); cmd_len < sizeof(u16);
> +	     i++, cmd_len -= sizeof(u16)) {

Same comment as above.

> +		u16 scid;
> +
> +		scid = __le16_to_cpu(req->scid[i]);
> +		if (!scid)
> +			return -EPROTO;
> +
> +		chan = __l2cap_get_chan_by_dcid(conn, scid);
> +		if (!chan)
> +			continue;
> +
> +		/* If the MTU value is decreased for any of the included
> +		 * channels, then the receiver shall disconnect all
> +		 * included channels.
> +		 */
> +		if (chan->omtu > mtu) {
> +			BT_ERR("chan %p decreased MTU %u -> %u", chan,
> +			       chan->omtu, mtu);
> +			result = L2CAP_RECONF_INVALID_MTU;
> +		}
> +
> +		chan->omtu = mtu;
> +		chan->remote_mps = mps;
> +	}
> +
> +respond:
> +	rsp.result = cpu_to_le16(result);
> +
> +	l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_RECONF_RSP, sizeof(rsp),
> +		       &rsp);
> +
> +	return 0;
> +}
> +
> +static inline int l2cap_ecred_reconf_rsp(struct l2cap_conn *conn,
> +					 struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> +					 u8 *data)
> +{
> +	struct l2cap_chan *chan;
> +	struct l2cap_ecred_conn_rsp *rsp = (void *) data;
> +	u16 result;
> +
> +	if (cmd_len < sizeof(*rsp))
> +		return -EPROTO;
> +
> +	result = __le16_to_cpu(rsp->result);
> +
> +	BT_DBG("result 0x%4.4x", rsp->result);
> +
> +	if (!result)
> +		return 0;
> +
> +	list_for_each_entry(chan, &conn->chan_l, list) {
> +		if (chan->ident != cmd->ident)
> +			continue;
> +
> +		l2cap_chan_del(chan, ECONNRESET);
> +	}
> +
> +	return 0;
> +}
> +
> static inline int l2cap_le_command_rej(struct l2cap_conn *conn,
> 				       struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> 				       u8 *data)
> @@ -5769,6 +6199,22 @@ static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn,
> 		err = l2cap_le_credits(conn, cmd, cmd_len, data);
> 		break;
> 
> +	case L2CAP_ECRED_CONN_REQ:
> +		err = l2cap_ecred_conn_req(conn, cmd, cmd_len, data);
> +		break;
> +
> +	case L2CAP_ECRED_CONN_RSP:
> +		err = l2cap_ecred_conn_rsp(conn, cmd, cmd_len, data);
> +		break;
> +
> +	case L2CAP_ECRED_RECONF_REQ:
> +		err = l2cap_ecred_reconf_req(conn, cmd, cmd_len, data);
> +		break;
> +
> +	case L2CAP_ECRED_RECONF_RSP:
> +		err = l2cap_ecred_reconf_rsp(conn, cmd, cmd_len, data);
> +		break;
> +
> 	case L2CAP_DISCONN_REQ:
> 		err = l2cap_disconnect_req(conn, cmd, cmd_len, data);
> 		break;
> @@ -6814,11 +7260,13 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
> 	struct l2cap_le_credits pkt;
> 	u16 return_credits;
> 
> -	return_credits = ((chan->imtu / chan->mps) + 1) - chan->rx_credits;
> +	return_credits = (chan->imtu / chan->mps) + 1;
> 
> -	if (!return_credits)
> +	if (chan->rx_credits >= return_credits)
> 		return;
> 
> +	return_credits -= chan->rx_credits;
> +
> 	BT_DBG("chan %p returning %u credits to sender", chan, return_credits);
> 
> 	chan->rx_credits += return_credits;
> @@ -6831,7 +7279,7 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
> 	l2cap_send_cmd(conn, chan->ident, L2CAP_LE_CREDITS, sizeof(pkt), &pkt);
> }
> 
> -static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
> +static int l2cap_ecred_recv(struct l2cap_chan *chan, struct sk_buff *skb)
> {
> 	int err;
> 
> @@ -6846,7 +7294,7 @@ static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
> 	return err;
> }
> 
> -static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> +static int l2cap_ecred_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> {
> 	int err;
> 
> @@ -6894,7 +7342,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> 		}
> 
> 		if (skb->len == sdu_len)
> -			return l2cap_le_recv(chan, skb);
> +			return l2cap_ecred_recv(chan, skb);
> 
> 		chan->sdu = skb;
> 		chan->sdu_len = sdu_len;
> @@ -6926,7 +7374,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> 	skb = NULL;
> 
> 	if (chan->sdu->len == chan->sdu_len) {
> -		err = l2cap_le_recv(chan, chan->sdu);
> +		err = l2cap_ecred_recv(chan, chan->sdu);
> 		if (!err) {
> 			chan->sdu = NULL;
> 			chan->sdu_last_frag = NULL;
> @@ -6987,7 +7435,8 @@ static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid,
> 
> 	switch (chan->mode) {
> 	case L2CAP_MODE_LE_FLOWCTL:
> -		if (l2cap_le_data_rcv(chan, skb) < 0)
> +	case L2CAP_MODE_EXT_FLOWCTL:
> +		if (l2cap_ecred_data_rcv(chan, skb) < 0)
> 			goto drop;
> 
> 		goto done;
> @@ -7214,8 +7663,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> 	struct hci_dev *hdev;
> 	int err;
> 
> -	BT_DBG("%pMR -> %pMR (type %u) psm 0x%2.2x", &chan->src, dst,
> -	       dst_type, __le16_to_cpu(psm));
> +	BT_DBG("%pMR -> %pMR (type %u) psm 0x%4.4x mode 0x%2.2x", &chan->src,
> +	       dst, dst_type, __le16_to_cpu(psm), chan->mode);
> 
> 	hdev = hci_get_route(dst, &chan->src, chan->src_type);
> 	if (!hdev)
> @@ -7244,6 +7693,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> 		break;
> 	case L2CAP_MODE_LE_FLOWCTL:
> 		break;
> +	case L2CAP_MODE_EXT_FLOWCTL:
> +		break;

No need to do another break. Just put it under LE_FLOWCTL.

> 	case L2CAP_MODE_ERTM:
> 	case L2CAP_MODE_STREAMING:
> 		if (!disable_ertm)
> @@ -7368,6 +7819,38 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> }
> EXPORT_SYMBOL_GPL(l2cap_chan_connect);
> 
> +static void l2cap_ecred_reconfigure(struct l2cap_chan *chan)
> +{
> +	struct l2cap_conn *conn = chan->conn;
> +	struct {
> +		struct l2cap_ecred_reconf_req req;
> +		__le16 scid;
> +	} pdu;
> +
> +	pdu.req.mtu = cpu_to_le16(chan->imtu);
> +	pdu.req.mps = cpu_to_le16(chan->mps);
> +	pdu.scid    = cpu_to_le16(chan->scid);
> +
> +	chan->ident = l2cap_get_ident(conn);
> +
> +	l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_RECONF_REQ,
> +		       sizeof(pdu), &pdu);
> +}
> +
> +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu)
> +{
> +	if (chan->imtu > mtu)
> +		return -EINVAL;
> +
> +	BT_DBG("chan %p mtu 0x%4.4x", chan, mtu);
> +
> +	chan->imtu = mtu;
> +
> +	l2cap_ecred_reconfigure(chan);
> +
> +	return 0;
> +}
> +
> /* ---- L2CAP interface with lower layer (HCI) ---- */
> 
> int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr)
> @@ -7579,7 +8062,8 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
> 			else
> 				__set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
> 		} else if (chan->state == BT_CONNECT2 &&
> -			   chan->mode != L2CAP_MODE_LE_FLOWCTL) {
> +			   !(chan->mode == L2CAP_MODE_EXT_FLOWCTL ||
> +			    chan->mode == L2CAP_MODE_LE_FLOWCTL)) {

Please double check that this line is correctly aligned after the (.

> 			struct l2cap_conn_rsp rsp;
> 			__u16 res, stat;
> 
> diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
> index a7be8b59b3c2..12c557f7f40f 100644
> --- a/net/bluetooth/l2cap_sock.c
> +++ b/net/bluetooth/l2cap_sock.c
> @@ -232,7 +232,7 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr,
> 			return -EINVAL;
> 	}
> 
> -	if (chan->psm && bdaddr_type_is_le(chan->src_type))
> +	if (chan->psm && bdaddr_type_is_le(chan->src_type) && !chan->mode)
> 		chan->mode = L2CAP_MODE_LE_FLOWCTL;
> 
> 	err = l2cap_chan_connect(chan, la.l2_psm, __le16_to_cpu(la.l2_cid),
> @@ -273,6 +273,7 @@ static int l2cap_sock_listen(struct socket *sock, int backlog)
> 	switch (chan->mode) {
> 	case L2CAP_MODE_BASIC:
> 	case L2CAP_MODE_LE_FLOWCTL:
> +	case L2CAP_MODE_EXT_FLOWCTL:
> 		break;
> 	case L2CAP_MODE_ERTM:
> 	case L2CAP_MODE_STREAMING:
> @@ -408,16 +409,6 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
> 
> 	switch (optname) {
> 	case L2CAP_OPTIONS:
> -		/* LE sockets should use BT_SNDMTU/BT_RCVMTU, but since
> -		 * legacy ATT code depends on getsockopt for
> -		 * L2CAP_OPTIONS we need to let this pass.
> -		 */
> -		if (bdaddr_type_is_le(chan->src_type) &&
> -		    chan->scid != L2CAP_CID_ATT) {
> -			err = -EINVAL;
> -			break;
> -		}
> -

Should this be a separate patch. How do we keep this legacy behavior.

> 		memset(&opts, 0, sizeof(opts));
> 		opts.imtu     = chan->imtu;
> 		opts.omtu     = chan->omtu;
> @@ -427,6 +418,8 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
> 		opts.max_tx   = chan->max_tx;
> 		opts.txwin_size = chan->tx_win;
> 
> +		BT_DBG("mode 0x%2.2x", chan->mode);
> +
> 		len = min_t(unsigned int, len, sizeof(opts));
> 		if (copy_to_user(optval, (char *) &opts, len))
> 			err = -EFAULT;
> @@ -643,11 +636,6 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname,
> 
> 	switch (optname) {
> 	case L2CAP_OPTIONS:
> -		if (bdaddr_type_is_le(chan->src_type)) {
> -			err = -EINVAL;
> -			break;
> -		}
> -
> 		if (sk->sk_state == BT_CONNECTED) {
> 			err = -EINVAL;
> 			break;
> @@ -680,6 +668,7 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname,
> 		chan->mode = opts.mode;
> 		switch (chan->mode) {
> 		case L2CAP_MODE_LE_FLOWCTL:
> +		case L2CAP_MODE_EXT_FLOWCTL:
> 			break;
> 		case L2CAP_MODE_BASIC:
> 			clear_bit(CONF_STATE2_DEVICE, &chan->conf_state);
> @@ -694,6 +683,8 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname,
> 			break;
> 		}
> 
> +		BT_DBG("mode 0x%2.2x", chan->mode);
> +
> 		chan->imtu = opts.imtu;
> 		chan->omtu = opts.omtu;
> 		chan->fcs  = opts.fcs;
> @@ -926,7 +917,8 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
> 			break;
> 		}
> 
> -		if (sk->sk_state == BT_CONNECTED) {
> +		if (chan->mode == L2CAP_MODE_LE_FLOWCTL &&
> +		    sk->sk_state == BT_CONNECTED) {
> 			err = -EISCONN;
> 			break;
> 		}
> @@ -936,7 +928,12 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
> 			break;
> 		}
> 
> -		chan->imtu = opt;
> +		if (chan->mode == L2CAP_MODE_EXT_FLOWCTL &&
> +		    sk->sk_state == BT_CONNECTED)
> +			err = l2cap_chan_reconfigure(chan, opt);
> +		else
> +			chan->imtu = opt;
> +
> 		break;
> 
> 	default:
> @@ -991,7 +988,11 @@ static int l2cap_sock_recvmsg(struct socket *sock, struct msghdr *msg,
> 
> 	if (sk->sk_state == BT_CONNECT2 && test_bit(BT_SK_DEFER_SETUP,
> 						    &bt_sk(sk)->flags)) {
> -		if (bdaddr_type_is_le(pi->chan->src_type)) {
> +		if (pi->chan->mode == L2CAP_MODE_EXT_FLOWCTL) {
> +			sk->sk_state = BT_CONNECTED;
> +			pi->chan->state = BT_CONNECTED;
> +			__l2cap_ecred_conn_rsp_defer(pi->chan);
> +		} if (bdaddr_type_is_le(pi->chan->src_type)) {
> 			sk->sk_state = BT_CONNECTED;
> 			pi->chan->state = BT_CONNECTED;
> 			__l2cap_le_connect_rsp_defer(pi->chan);

Regards

Marcel


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] Bluetooth: L2CAP: Add initial code for Enhanced Credit Based Mode
  2020-01-23 18:00   ` Marcel Holtmann
@ 2020-01-23 23:29     ` Luiz Augusto von Dentz
  2020-01-24 16:39       ` Marcel Holtmann
  0 siblings, 1 reply; 14+ messages in thread
From: Luiz Augusto von Dentz @ 2020-01-23 23:29 UTC (permalink / raw)
  To: Marcel Holtmann; +Cc: linux-bluetooth

Hi Marcel,

On Thu, Jan 23, 2020 at 10:00 AM Marcel Holtmann <marcel@holtmann.org> wrote:
>
> Hi Luiz,
>
> > This adds the initial code for Enhanced Credit Based Mode which
> > introduces a new socket mode called L2CAP_MODE_EXT_FLOWCTL, which for
> > the most part work the same as L2CAP_MODE_LE_FLOWCTL but uses different
> > PDUs to setup the connections and also works over BR/EDR.
> >
> > Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
> > ---
> > include/net/bluetooth/l2cap.h |   4 +
> > net/bluetooth/l2cap_core.c    | 510 +++++++++++++++++++++++++++++++++-
> > net/bluetooth/l2cap_sock.c    |  39 +--
> > 3 files changed, 521 insertions(+), 32 deletions(-)
> >
> > diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
> > index b9ea88cfd2d2..11ae4c93354b 100644
> > --- a/include/net/bluetooth/l2cap.h
> > +++ b/include/net/bluetooth/l2cap.h
> > @@ -295,6 +295,8 @@ struct l2cap_conn_rsp {
> > #define L2CAP_CR_LE_ENCRYPTION                0x0008
> > #define L2CAP_CR_LE_INVALID_SCID      0x0009
> > #define L2CAP_CR_LE_SCID_IN_USE               0X000A
> > +#define L2CAP_CR_LE_UNACCEPT_PARAMS  0X000B
> > +#define L2CAP_CR_LE_INVALID_PARAMS   0X000C
> >
> > /* connect/create channel status */
> > #define L2CAP_CS_NO_INFO      0x0000
> > @@ -964,6 +966,7 @@ void l2cap_cleanup_sockets(void);
> > bool l2cap_is_socket(struct socket *sock);
> >
> > void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan);
> > +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan);
> > void __l2cap_connect_rsp_defer(struct l2cap_chan *chan);
> >
> > int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm);
> > @@ -973,6 +976,7 @@ struct l2cap_chan *l2cap_chan_create(void);
> > void l2cap_chan_close(struct l2cap_chan *chan, int reason);
> > int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> >                      bdaddr_t *dst, u8 dst_type);
> > +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu);
> > int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len);
> > void l2cap_chan_busy(struct l2cap_chan *chan, int busy);
> > int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator);
> > diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
> > index 195459a1e53e..a528526cffac 100644
> > --- a/net/bluetooth/l2cap_core.c
> > +++ b/net/bluetooth/l2cap_core.c
> > @@ -532,6 +532,17 @@ static void l2cap_le_flowctl_init(struct l2cap_chan *chan, u16 tx_credits)
> >       skb_queue_head_init(&chan->tx_q);
> > }
> >
> > +static void l2cap_ecred_init(struct l2cap_chan *chan, u16 tx_credits)
> > +{
> > +     l2cap_le_flowctl_init(chan, tx_credits);
> > +
> > +     /* L2CAP implementations shall support a minimum MPS of 64 octets */
> > +     if (chan->mps < L2CAP_ECRED_MIN_MPS) {
> > +             chan->mps = L2CAP_ECRED_MIN_MPS;
> > +             chan->rx_credits = (chan->imtu / chan->mps) + 1;
> > +     }
> > +}
> > +
> > void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
> > {
> >       BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn,
> > @@ -638,6 +649,7 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
> >               break;
> >
> >       case L2CAP_MODE_LE_FLOWCTL:
> > +     case L2CAP_MODE_EXT_FLOWCTL:
> >               skb_queue_purge(&chan->tx_q);
> >               break;
> >
> > @@ -1260,6 +1272,17 @@ static void l2cap_move_done(struct l2cap_chan *chan)
> >       }
> > }
> >
> > +static bool l2cap_mode_ready(struct l2cap_chan *chan)
> > +{
> > +     switch (chan->mode) {
> > +     case L2CAP_MODE_LE_FLOWCTL:
> > +     case L2CAP_MODE_EXT_FLOWCTL:
> > +             return chan->tx_credits ? true : false;
> > +     }
> > +
> > +     return true;
> > +}
> > +
> > static void l2cap_chan_ready(struct l2cap_chan *chan)
> > {
> >       /* The channel may have already been flagged as connected in
> > @@ -1273,7 +1296,7 @@ static void l2cap_chan_ready(struct l2cap_chan *chan)
> >       chan->conf_state = 0;
> >       __clear_chan_timer(chan);
> >
> > -     if (chan->mode == L2CAP_MODE_LE_FLOWCTL && !chan->tx_credits)
> > +     if (!l2cap_mode_ready(chan))
> >               chan->ops->suspend(chan);
>
>         switch (chan->mode) {
>         case ..
>         case ..
>                 if (!chan->tx_credits)
>                         chan->ops->suspend(chan);
>                 break;
>         }
>
> >
> >       chan->state = BT_CONNECTED;
> > @@ -1306,6 +1329,31 @@ static void l2cap_le_connect(struct l2cap_chan *chan)
> >                      sizeof(req), &req);
> > }
> >
> > +static void l2cap_ecred_connect(struct l2cap_chan *chan)
> > +{
> > +     struct l2cap_conn *conn = chan->conn;
> > +     struct {
> > +             struct l2cap_ecred_conn_req req;
> > +             __le16 scid;
> > +     } __packed pdu;
> > +
> > +     if (test_and_set_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags))
> > +             return;
> > +
> > +     l2cap_ecred_init(chan, 0);
> > +
> > +     pdu.req.psm     = chan->psm;
> > +     pdu.req.mtu     = cpu_to_le16(chan->imtu);
> > +     pdu.req.mps     = cpu_to_le16(chan->mps);
> > +     pdu.req.credits = cpu_to_le16(chan->rx_credits);
> > +     pdu.scid        = cpu_to_le16(chan->scid);
> > +
> > +     chan->ident = l2cap_get_ident(conn);
> > +
> > +     l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_CONN_REQ,
> > +                    sizeof(pdu), &pdu);
> > +}
> > +
> > static void l2cap_le_start(struct l2cap_chan *chan)
> > {
> >       struct l2cap_conn *conn = chan->conn;
> > @@ -1318,8 +1366,12 @@ static void l2cap_le_start(struct l2cap_chan *chan)
> >               return;
> >       }
> >
> > -     if (chan->state == BT_CONNECT)
> > -             l2cap_le_connect(chan);
> > +     if (chan->state == BT_CONNECT) {
> > +             if (chan->mode == L2CAP_MODE_EXT_FLOWCTL)
> > +                     l2cap_ecred_connect(chan);
> > +             else
> > +                     l2cap_le_connect(chan);
> > +     }
> > }
> >
> > static void l2cap_start_connection(struct l2cap_chan *chan)
> > @@ -2505,6 +2557,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
> >
> >       switch (chan->mode) {
> >       case L2CAP_MODE_LE_FLOWCTL:
> > +     case L2CAP_MODE_EXT_FLOWCTL:
> >               /* Check outgoing MTU */
> >               if (len > chan->omtu)
> >                       return -EMSGSIZE;
> > @@ -3773,6 +3826,42 @@ void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan)
> >                      &rsp);
> > }
> >
> > +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
> > +{
> > +     struct l2cap_ecred_conn_rsp rsp;
> > +     struct l2cap_conn *conn = chan->conn;
> > +     u16 ident = chan->ident;
> > +     int i = 0;
> > +
> > +     if (!ident) {
> > +             return;
> > +     }
> > +
>
> No { } here.
>
> > +     BT_DBG("chan %p ident %d", chan, ident);
> > +
> > +     rsp.mtu     = cpu_to_le16(chan->imtu);
> > +     rsp.mps     = cpu_to_le16(chan->mps);
> > +     rsp.credits = cpu_to_le16(chan->rx_credits);
> > +     rsp.result  = cpu_to_le16(L2CAP_CR_LE_SUCCESS);
> > +
> > +     mutex_lock(&conn->chan_lock);
> > +
> > +     list_for_each_entry(chan, &conn->chan_l, list) {
> > +             if (chan->ident != ident)
> > +                     continue;
> > +
> > +             /* Reset ident so only one response is sent */
> > +             chan->ident = 0;
> > +
> > +             /* Include all channels pending with the same ident */
> > +             rsp.dcid[i++] = cpu_to_le16(chan->scid);
>
> This doesn’t work. The sizeof(rsp) has no dcid in there. You are overflowing into conn struct here.
>
> > +     }
> > +
> > +     mutex_unlock(&conn->chan_lock);
> > +
> > +     l2cap_send_cmd(conn, ident, L2CAP_ECRED_CONN_RSP, sizeof(rsp), &rsp);
> > +}
> > +
> > void __l2cap_connect_rsp_defer(struct l2cap_chan *chan)
> > {
> >       struct l2cap_conn_rsp rsp;
> > @@ -5714,6 +5803,347 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn,
> >       return 0;
> > }
> >
> > +static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
> > +                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> > +                                    u8 *data)
> > +{
> > +     struct l2cap_ecred_conn_req *req = (void *) data;
> > +     struct {
> > +             struct l2cap_ecred_conn_rsp rsp;
> > +             __le16 dcid[5];
> > +     } __packed pdu;
> > +     struct l2cap_chan *chan, *pchan;
> > +     u16 credits, mtu, mps;
> > +     __le16 psm;
> > +     u8 result, len = 0;
> > +     int i;
> > +     bool defer = false;
> > +
> > +     if (cmd_len < sizeof(*req))
> > +             return -EPROTO;
> > +
> > +     mtu  = __le16_to_cpu(req->mtu);
> > +     mps  = __le16_to_cpu(req->mps);
> > +
> > +     if (mtu < L2CAP_ECRED_MIN_MTU || mps < L2CAP_ECRED_MIN_MPS) {
> > +             result = L2CAP_CR_LE_UNACCEPT_PARAMS;
> > +             goto response;
> > +     }
> > +
> > +     psm  = req->psm;
> > +     credits = 0;
> > +
> > +     BT_DBG("psm 0x%2.2x mtu %u mps %u", __le16_to_cpu(psm), mtu, mps);
> > +
> > +     memset(&pdu, 0, sizeof(pdu));
> > +
> > +     /* Check if we have socket listening on psm */
> > +     pchan = l2cap_global_chan_by_psm(BT_LISTEN, psm, &conn->hcon->src,
> > +                                      &conn->hcon->dst, LE_LINK);
> > +     if (!pchan) {
> > +             result = L2CAP_CR_LE_BAD_PSM;
> > +             goto response;
> > +     }
> > +
> > +     mutex_lock(&conn->chan_lock);
> > +     l2cap_chan_lock(pchan);
> > +
> > +     if (!smp_sufficient_security(conn->hcon, pchan->sec_level,
> > +                                  SMP_ALLOW_STK)) {
> > +             result = L2CAP_CR_LE_AUTHENTICATION;
> > +             goto unlock;
> > +     }
> > +
> > +     result = L2CAP_CR_LE_SUCCESS;
> > +
> > +     for (i = 0, cmd_len -= sizeof(req); cmd_len >= sizeof(u16);
> > +          i++, cmd_len -= sizeof(u16)) {
>
> Can we simplify the loop statement or switch to while statement?
>
> > +             u16 scid = __le16_to_cpu(req->scid[i]);
> > +
> > +             BT_DBG("scid[%d] 0x%4.4x", i, scid);
> > +
> > +             pdu.dcid[i] = 0x0000;
> > +             len += sizeof(*pdu.dcid);
> > +
> > +             /* Check for valid dynamic CID range */
> > +             if (scid < L2CAP_CID_DYN_START || scid > L2CAP_CID_LE_DYN_END) {
> > +                     result = L2CAP_CR_LE_INVALID_SCID;
> > +                     continue;
> > +             }
> > +
> > +             /* Check if we already have channel with that dcid */
> > +             if (__l2cap_get_chan_by_dcid(conn, scid)) {
> > +                     result = L2CAP_CR_LE_SCID_IN_USE;
> > +                     continue;
> > +             }
> > +
> > +             chan = pchan->ops->new_connection(pchan);
> > +             if (!chan) {
> > +                     result = L2CAP_CR_LE_NO_MEM;
> > +                     continue;
> > +             }
> > +
> > +             bacpy(&chan->src, &conn->hcon->src);
> > +             bacpy(&chan->dst, &conn->hcon->dst);
> > +             chan->src_type = bdaddr_src_type(conn->hcon);
> > +             chan->dst_type = bdaddr_dst_type(conn->hcon);
> > +             chan->psm  = psm;
> > +             chan->dcid = scid;
> > +             chan->omtu = mtu;
> > +             chan->remote_mps = mps;
> > +
> > +             __l2cap_chan_add(conn, chan);
> > +
> > +             l2cap_ecred_init(chan, __le16_to_cpu(req->credits));
> > +
> > +             /* Init response */
> > +             if (!pdu.rsp.credits) {
> > +                     pdu.rsp.mtu = cpu_to_le16(chan->imtu);
> > +                     pdu.rsp.mps = cpu_to_le16(chan->mps);
> > +                     pdu.rsp.credits = cpu_to_le16(chan->rx_credits);
> > +             }
> > +
> > +             pdu.dcid[i] = cpu_to_le16(chan->scid);
> > +
> > +             __set_chan_timer(chan, chan->ops->get_sndtimeo(chan));
> > +
> > +             chan->ident = cmd->ident;
> > +
> > +             if (test_bit(FLAG_DEFER_SETUP, &chan->flags)) {
> > +                     l2cap_state_change(chan, BT_CONNECT2);
> > +                     defer = true;
> > +                     chan->ops->defer(chan);
> > +             } else {
> > +                     l2cap_chan_ready(chan);
> > +             }
> > +     }
> > +
> > +unlock:
> > +     l2cap_chan_unlock(pchan);
> > +     mutex_unlock(&conn->chan_lock);
> > +     l2cap_chan_put(pchan);
> > +
> > +response:
> > +     pdu.rsp.result = cpu_to_le16(result);
> > +
> > +     if (defer)
> > +             return 0;
> > +
> > +     l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_CONN_RSP,
> > +                    sizeof(pdu.rsp) + len, &pdu);
> > +
> > +     return 0;
> > +}
> > +
> > +static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
> > +                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> > +                                    u8 *data)
> > +{
> > +     struct l2cap_ecred_conn_rsp *rsp = (void *) data;
> > +     struct hci_conn *hcon = conn->hcon;
> > +     u16 mtu, mps, credits, result;
> > +     struct l2cap_chan *chan;
> > +     int err = 0, sec_level;
> > +     int i = 0;
> > +
> > +     if (cmd_len < sizeof(*rsp))
> > +             return -EPROTO;
> > +
> > +     mtu     = __le16_to_cpu(rsp->mtu);
> > +     mps     = __le16_to_cpu(rsp->mps);
> > +     credits = __le16_to_cpu(rsp->credits);
> > +     result  = __le16_to_cpu(rsp->result);
> > +
> > +     BT_DBG("mtu %u mps %u credits %u result 0x%4.4x", mtu, mps, credits,
> > +            result);
> > +
> > +     mutex_lock(&conn->chan_lock);
> > +
> > +     cmd_len -= sizeof(*rsp);
> > +
> > +     list_for_each_entry(chan, &conn->chan_l, list) {
> > +             u16 dcid;
> > +
> > +             if (chan->ident != cmd->ident ||
> > +                 chan->mode != L2CAP_MODE_EXT_FLOWCTL ||
> > +                 chan->state == BT_CONNECTED)
> > +                     continue;
> > +
> > +             l2cap_chan_lock(chan);
> > +
> > +             /* Check that there is a dcid for each pending channel */
> > +             if (cmd_len < sizeof(dcid)) {
> > +                     l2cap_chan_del(chan, ECONNREFUSED);
> > +                     l2cap_chan_unlock(chan);
> > +                     continue;
> > +             }
> > +
> > +             dcid = __le16_to_cpu(rsp->dcid[i++]);
> > +             cmd_len -= sizeof(u16);
> > +
> > +             BT_DBG("dcid[%d] 0x%4.4x", i, dcid);
> > +
> > +             /* Check if dcid is already in use */
> > +             if (dcid && __l2cap_get_chan_by_dcid(conn, dcid)) {
> > +                     /* If a device receives a
> > +                      * L2CAP_CREDIT_BASED_CONNECTION_RSP packet with an
> > +                      * already-assigned Destination CID, then both the
> > +                      * original channel and the new channel shall be
> > +                      * immediately discarded and not used.
> > +                      */
> > +                     l2cap_chan_del(chan, ECONNREFUSED);
> > +                     l2cap_chan_unlock(chan);
> > +                     chan = __l2cap_get_chan_by_dcid(conn, dcid);
> > +                     l2cap_chan_lock(chan);
> > +                     l2cap_chan_del(chan, ECONNRESET);
> > +                     l2cap_chan_unlock(chan);
> > +                     continue;
> > +             }
> > +
> > +             switch (result) {
> > +             case L2CAP_CR_LE_AUTHENTICATION:
> > +             case L2CAP_CR_LE_ENCRYPTION:
> > +                     /* If we already have MITM protection we can't do
> > +                      * anything.
> > +                      */
> > +                     if (hcon->sec_level > BT_SECURITY_MEDIUM) {
> > +                             l2cap_chan_del(chan, ECONNREFUSED);
> > +                             break;
> > +                     }
> > +
> > +                     sec_level = hcon->sec_level + 1;
> > +                     if (chan->sec_level < sec_level)
> > +                             chan->sec_level = sec_level;
> > +
> > +                     /* We'll need to send a new Connect Request */
> > +                     clear_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags);
> > +
> > +                     smp_conn_security(hcon, chan->sec_level);
> > +                     break;
> > +
> > +             case L2CAP_CR_LE_BAD_PSM:
> > +                     l2cap_chan_del(chan, ECONNREFUSED);
> > +                     break;
> > +
> > +             default:
> > +                     /* If dcid was not set it means channels was refused */
> > +                     if (!dcid) {
> > +                             l2cap_chan_del(chan, ECONNREFUSED);
> > +                             break;
> > +                     }
> > +
> > +                     chan->ident = 0;
> > +                     chan->dcid = dcid;
> > +                     chan->omtu = mtu;
> > +                     chan->remote_mps = mps;
> > +                     chan->tx_credits = credits;
> > +                     l2cap_chan_ready(chan);
> > +                     break;
> > +             }
> > +
> > +             l2cap_chan_unlock(chan);
> > +     }
> > +
> > +     mutex_unlock(&conn->chan_lock);
> > +
> > +     return err;
> > +}
> > +
> > +static inline int l2cap_ecred_reconf_req(struct l2cap_conn *conn,
> > +                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> > +                                      u8 *data)
> > +{
> > +     struct l2cap_ecred_reconf_req *req = (void *) data;
> > +     struct l2cap_ecred_reconf_rsp rsp;
> > +     u16 mtu, mps, result;
> > +     struct l2cap_chan *chan;
> > +     int i;
> > +
> > +     if (cmd_len < sizeof(*req)) {
> > +             result = L2CAP_CR_LE_INVALID_PARAMS;
> > +             goto respond;
> > +     }
> > +
> > +     mtu = __le16_to_cpu(req->mtu);
> > +     mps = __le16_to_cpu(req->mps);
> > +
> > +     BT_DBG("mtu %u mps %u", mtu, mps);
> > +
> > +     if (mtu < L2CAP_ECRED_MIN_MTU) {
> > +             result = L2CAP_RECONF_INVALID_MTU;
> > +             goto respond;
> > +     }
> > +
> > +     if (mps < L2CAP_ECRED_MIN_MPS) {
> > +             result = L2CAP_RECONF_INVALID_MPS;
> > +             goto respond;
> > +     }
> > +
> > +     result = L2CAP_RECONF_SUCCESS;
> > +
> > +     for (i = 0, cmd_len -= sizeof(*req); cmd_len < sizeof(u16);
> > +          i++, cmd_len -= sizeof(u16)) {
>
> Same comment as above.
>
> > +             u16 scid;
> > +
> > +             scid = __le16_to_cpu(req->scid[i]);
> > +             if (!scid)
> > +                     return -EPROTO;
> > +
> > +             chan = __l2cap_get_chan_by_dcid(conn, scid);
> > +             if (!chan)
> > +                     continue;
> > +
> > +             /* If the MTU value is decreased for any of the included
> > +              * channels, then the receiver shall disconnect all
> > +              * included channels.
> > +              */
> > +             if (chan->omtu > mtu) {
> > +                     BT_ERR("chan %p decreased MTU %u -> %u", chan,
> > +                            chan->omtu, mtu);
> > +                     result = L2CAP_RECONF_INVALID_MTU;
> > +             }
> > +
> > +             chan->omtu = mtu;
> > +             chan->remote_mps = mps;
> > +     }
> > +
> > +respond:
> > +     rsp.result = cpu_to_le16(result);
> > +
> > +     l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_RECONF_RSP, sizeof(rsp),
> > +                    &rsp);
> > +
> > +     return 0;
> > +}
> > +
> > +static inline int l2cap_ecred_reconf_rsp(struct l2cap_conn *conn,
> > +                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> > +                                      u8 *data)
> > +{
> > +     struct l2cap_chan *chan;
> > +     struct l2cap_ecred_conn_rsp *rsp = (void *) data;
> > +     u16 result;
> > +
> > +     if (cmd_len < sizeof(*rsp))
> > +             return -EPROTO;
> > +
> > +     result = __le16_to_cpu(rsp->result);
> > +
> > +     BT_DBG("result 0x%4.4x", rsp->result);
> > +
> > +     if (!result)
> > +             return 0;
> > +
> > +     list_for_each_entry(chan, &conn->chan_l, list) {
> > +             if (chan->ident != cmd->ident)
> > +                     continue;
> > +
> > +             l2cap_chan_del(chan, ECONNRESET);
> > +     }
> > +
> > +     return 0;
> > +}
> > +
> > static inline int l2cap_le_command_rej(struct l2cap_conn *conn,
> >                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >                                      u8 *data)
> > @@ -5769,6 +6199,22 @@ static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn,
> >               err = l2cap_le_credits(conn, cmd, cmd_len, data);
> >               break;
> >
> > +     case L2CAP_ECRED_CONN_REQ:
> > +             err = l2cap_ecred_conn_req(conn, cmd, cmd_len, data);
> > +             break;
> > +
> > +     case L2CAP_ECRED_CONN_RSP:
> > +             err = l2cap_ecred_conn_rsp(conn, cmd, cmd_len, data);
> > +             break;
> > +
> > +     case L2CAP_ECRED_RECONF_REQ:
> > +             err = l2cap_ecred_reconf_req(conn, cmd, cmd_len, data);
> > +             break;
> > +
> > +     case L2CAP_ECRED_RECONF_RSP:
> > +             err = l2cap_ecred_reconf_rsp(conn, cmd, cmd_len, data);
> > +             break;
> > +
> >       case L2CAP_DISCONN_REQ:
> >               err = l2cap_disconnect_req(conn, cmd, cmd_len, data);
> >               break;
> > @@ -6814,11 +7260,13 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
> >       struct l2cap_le_credits pkt;
> >       u16 return_credits;
> >
> > -     return_credits = ((chan->imtu / chan->mps) + 1) - chan->rx_credits;
> > +     return_credits = (chan->imtu / chan->mps) + 1;
> >
> > -     if (!return_credits)
> > +     if (chan->rx_credits >= return_credits)
> >               return;
> >
> > +     return_credits -= chan->rx_credits;
> > +
> >       BT_DBG("chan %p returning %u credits to sender", chan, return_credits);
> >
> >       chan->rx_credits += return_credits;
> > @@ -6831,7 +7279,7 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
> >       l2cap_send_cmd(conn, chan->ident, L2CAP_LE_CREDITS, sizeof(pkt), &pkt);
> > }
> >
> > -static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
> > +static int l2cap_ecred_recv(struct l2cap_chan *chan, struct sk_buff *skb)
> > {
> >       int err;
> >
> > @@ -6846,7 +7294,7 @@ static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
> >       return err;
> > }
> >
> > -static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> > +static int l2cap_ecred_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> > {
> >       int err;
> >
> > @@ -6894,7 +7342,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> >               }
> >
> >               if (skb->len == sdu_len)
> > -                     return l2cap_le_recv(chan, skb);
> > +                     return l2cap_ecred_recv(chan, skb);
> >
> >               chan->sdu = skb;
> >               chan->sdu_len = sdu_len;
> > @@ -6926,7 +7374,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> >       skb = NULL;
> >
> >       if (chan->sdu->len == chan->sdu_len) {
> > -             err = l2cap_le_recv(chan, chan->sdu);
> > +             err = l2cap_ecred_recv(chan, chan->sdu);
> >               if (!err) {
> >                       chan->sdu = NULL;
> >                       chan->sdu_last_frag = NULL;
> > @@ -6987,7 +7435,8 @@ static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid,
> >
> >       switch (chan->mode) {
> >       case L2CAP_MODE_LE_FLOWCTL:
> > -             if (l2cap_le_data_rcv(chan, skb) < 0)
> > +     case L2CAP_MODE_EXT_FLOWCTL:
> > +             if (l2cap_ecred_data_rcv(chan, skb) < 0)
> >                       goto drop;
> >
> >               goto done;
> > @@ -7214,8 +7663,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> >       struct hci_dev *hdev;
> >       int err;
> >
> > -     BT_DBG("%pMR -> %pMR (type %u) psm 0x%2.2x", &chan->src, dst,
> > -            dst_type, __le16_to_cpu(psm));
> > +     BT_DBG("%pMR -> %pMR (type %u) psm 0x%4.4x mode 0x%2.2x", &chan->src,
> > +            dst, dst_type, __le16_to_cpu(psm), chan->mode);
> >
> >       hdev = hci_get_route(dst, &chan->src, chan->src_type);
> >       if (!hdev)
> > @@ -7244,6 +7693,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> >               break;
> >       case L2CAP_MODE_LE_FLOWCTL:
> >               break;
> > +     case L2CAP_MODE_EXT_FLOWCTL:
> > +             break;
>
> No need to do another break. Just put it under LE_FLOWCTL.
>
> >       case L2CAP_MODE_ERTM:
> >       case L2CAP_MODE_STREAMING:
> >               if (!disable_ertm)
> > @@ -7368,6 +7819,38 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> > }
> > EXPORT_SYMBOL_GPL(l2cap_chan_connect);
> >
> > +static void l2cap_ecred_reconfigure(struct l2cap_chan *chan)
> > +{
> > +     struct l2cap_conn *conn = chan->conn;
> > +     struct {
> > +             struct l2cap_ecred_reconf_req req;
> > +             __le16 scid;
> > +     } pdu;
> > +
> > +     pdu.req.mtu = cpu_to_le16(chan->imtu);
> > +     pdu.req.mps = cpu_to_le16(chan->mps);
> > +     pdu.scid    = cpu_to_le16(chan->scid);
> > +
> > +     chan->ident = l2cap_get_ident(conn);
> > +
> > +     l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_RECONF_REQ,
> > +                    sizeof(pdu), &pdu);
> > +}
> > +
> > +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu)
> > +{
> > +     if (chan->imtu > mtu)
> > +             return -EINVAL;
> > +
> > +     BT_DBG("chan %p mtu 0x%4.4x", chan, mtu);
> > +
> > +     chan->imtu = mtu;
> > +
> > +     l2cap_ecred_reconfigure(chan);
> > +
> > +     return 0;
> > +}
> > +
> > /* ---- L2CAP interface with lower layer (HCI) ---- */
> >
> > int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr)
> > @@ -7579,7 +8062,8 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
> >                       else
> >                               __set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
> >               } else if (chan->state == BT_CONNECT2 &&
> > -                        chan->mode != L2CAP_MODE_LE_FLOWCTL) {
> > +                        !(chan->mode == L2CAP_MODE_EXT_FLOWCTL ||
> > +                         chan->mode == L2CAP_MODE_LE_FLOWCTL)) {
>
> Please double check that this line is correctly aligned after the (.
>
> >                       struct l2cap_conn_rsp rsp;
> >                       __u16 res, stat;
> >
> > diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
> > index a7be8b59b3c2..12c557f7f40f 100644
> > --- a/net/bluetooth/l2cap_sock.c
> > +++ b/net/bluetooth/l2cap_sock.c
> > @@ -232,7 +232,7 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr,
> >                       return -EINVAL;
> >       }
> >
> > -     if (chan->psm && bdaddr_type_is_le(chan->src_type))
> > +     if (chan->psm && bdaddr_type_is_le(chan->src_type) && !chan->mode)
> >               chan->mode = L2CAP_MODE_LE_FLOWCTL;
> >
> >       err = l2cap_chan_connect(chan, la.l2_psm, __le16_to_cpu(la.l2_cid),
> > @@ -273,6 +273,7 @@ static int l2cap_sock_listen(struct socket *sock, int backlog)
> >       switch (chan->mode) {
> >       case L2CAP_MODE_BASIC:
> >       case L2CAP_MODE_LE_FLOWCTL:
> > +     case L2CAP_MODE_EXT_FLOWCTL:
> >               break;
> >       case L2CAP_MODE_ERTM:
> >       case L2CAP_MODE_STREAMING:
> > @@ -408,16 +409,6 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
> >
> >       switch (optname) {
> >       case L2CAP_OPTIONS:
> > -             /* LE sockets should use BT_SNDMTU/BT_RCVMTU, but since
> > -              * legacy ATT code depends on getsockopt for
> > -              * L2CAP_OPTIONS we need to let this pass.
> > -              */
> > -             if (bdaddr_type_is_le(chan->src_type) &&
> > -                 chan->scid != L2CAP_CID_ATT) {
> > -                     err = -EINVAL;
> > -                     break;
> > -             }
> > -
>
> Should this be a separate patch. How do we keep this legacy behavior.

I kind have forgotten to fix this one, I guess we will need a new
option in order to read the mode without using the old L2CAP_OPTIONS
then, or do you have anything against introducing yet another option?

> >               memset(&opts, 0, sizeof(opts));
> >               opts.imtu     = chan->imtu;
> >               opts.omtu     = chan->omtu;
> > @@ -427,6 +418,8 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
> >               opts.max_tx   = chan->max_tx;
> >               opts.txwin_size = chan->tx_win;
> >
> > +             BT_DBG("mode 0x%2.2x", chan->mode);
> > +
> >               len = min_t(unsigned int, len, sizeof(opts));
> >               if (copy_to_user(optval, (char *) &opts, len))
> >                       err = -EFAULT;
> > @@ -643,11 +636,6 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname,
> >
> >       switch (optname) {
> >       case L2CAP_OPTIONS:
> > -             if (bdaddr_type_is_le(chan->src_type)) {
> > -                     err = -EINVAL;
> > -                     break;
> > -             }
> > -
> >               if (sk->sk_state == BT_CONNECTED) {
> >                       err = -EINVAL;
> >                       break;
> > @@ -680,6 +668,7 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname,
> >               chan->mode = opts.mode;
> >               switch (chan->mode) {
> >               case L2CAP_MODE_LE_FLOWCTL:
> > +             case L2CAP_MODE_EXT_FLOWCTL:
> >                       break;
> >               case L2CAP_MODE_BASIC:
> >                       clear_bit(CONF_STATE2_DEVICE, &chan->conf_state);
> > @@ -694,6 +683,8 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname,
> >                       break;
> >               }
> >
> > +             BT_DBG("mode 0x%2.2x", chan->mode);
> > +
> >               chan->imtu = opts.imtu;
> >               chan->omtu = opts.omtu;
> >               chan->fcs  = opts.fcs;
> > @@ -926,7 +917,8 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
> >                       break;
> >               }
> >
> > -             if (sk->sk_state == BT_CONNECTED) {
> > +             if (chan->mode == L2CAP_MODE_LE_FLOWCTL &&
> > +                 sk->sk_state == BT_CONNECTED) {
> >                       err = -EISCONN;
> >                       break;
> >               }
> > @@ -936,7 +928,12 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
> >                       break;
> >               }
> >
> > -             chan->imtu = opt;
> > +             if (chan->mode == L2CAP_MODE_EXT_FLOWCTL &&
> > +                 sk->sk_state == BT_CONNECTED)
> > +                     err = l2cap_chan_reconfigure(chan, opt);
> > +             else
> > +                     chan->imtu = opt;
> > +
> >               break;
> >
> >       default:
> > @@ -991,7 +988,11 @@ static int l2cap_sock_recvmsg(struct socket *sock, struct msghdr *msg,
> >
> >       if (sk->sk_state == BT_CONNECT2 && test_bit(BT_SK_DEFER_SETUP,
> >                                                   &bt_sk(sk)->flags)) {
> > -             if (bdaddr_type_is_le(pi->chan->src_type)) {
> > +             if (pi->chan->mode == L2CAP_MODE_EXT_FLOWCTL) {
> > +                     sk->sk_state = BT_CONNECTED;
> > +                     pi->chan->state = BT_CONNECTED;
> > +                     __l2cap_ecred_conn_rsp_defer(pi->chan);
> > +             } if (bdaddr_type_is_le(pi->chan->src_type)) {
> >                       sk->sk_state = BT_CONNECTED;
> >                       pi->chan->state = BT_CONNECTED;
> >                       __l2cap_le_connect_rsp_defer(pi->chan);
>
> Regards
>
> Marcel
>


-- 
Luiz Augusto von Dentz

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] Bluetooth: L2CAP: Add initial code for Enhanced Credit Based Mode
  2020-01-23 23:29     ` Luiz Augusto von Dentz
@ 2020-01-24 16:39       ` Marcel Holtmann
  2020-01-24 19:13         ` Luiz Augusto von Dentz
  0 siblings, 1 reply; 14+ messages in thread
From: Marcel Holtmann @ 2020-01-24 16:39 UTC (permalink / raw)
  To: Luiz Augusto von Dentz; +Cc: linux-bluetooth

Hi Luiz,

>>> This adds the initial code for Enhanced Credit Based Mode which
>>> introduces a new socket mode called L2CAP_MODE_EXT_FLOWCTL, which for
>>> the most part work the same as L2CAP_MODE_LE_FLOWCTL but uses different
>>> PDUs to setup the connections and also works over BR/EDR.
>>> 
>>> Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
>>> ---
>>> include/net/bluetooth/l2cap.h |   4 +
>>> net/bluetooth/l2cap_core.c    | 510 +++++++++++++++++++++++++++++++++-
>>> net/bluetooth/l2cap_sock.c    |  39 +--
>>> 3 files changed, 521 insertions(+), 32 deletions(-)
>>> 
>>> diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
>>> index b9ea88cfd2d2..11ae4c93354b 100644
>>> --- a/include/net/bluetooth/l2cap.h
>>> +++ b/include/net/bluetooth/l2cap.h
>>> @@ -295,6 +295,8 @@ struct l2cap_conn_rsp {
>>> #define L2CAP_CR_LE_ENCRYPTION                0x0008
>>> #define L2CAP_CR_LE_INVALID_SCID      0x0009
>>> #define L2CAP_CR_LE_SCID_IN_USE               0X000A
>>> +#define L2CAP_CR_LE_UNACCEPT_PARAMS  0X000B
>>> +#define L2CAP_CR_LE_INVALID_PARAMS   0X000C
>>> 
>>> /* connect/create channel status */
>>> #define L2CAP_CS_NO_INFO      0x0000
>>> @@ -964,6 +966,7 @@ void l2cap_cleanup_sockets(void);
>>> bool l2cap_is_socket(struct socket *sock);
>>> 
>>> void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan);
>>> +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan);
>>> void __l2cap_connect_rsp_defer(struct l2cap_chan *chan);
>>> 
>>> int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm);
>>> @@ -973,6 +976,7 @@ struct l2cap_chan *l2cap_chan_create(void);
>>> void l2cap_chan_close(struct l2cap_chan *chan, int reason);
>>> int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
>>>                     bdaddr_t *dst, u8 dst_type);
>>> +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu);
>>> int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len);
>>> void l2cap_chan_busy(struct l2cap_chan *chan, int busy);
>>> int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator);
>>> diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
>>> index 195459a1e53e..a528526cffac 100644
>>> --- a/net/bluetooth/l2cap_core.c
>>> +++ b/net/bluetooth/l2cap_core.c
>>> @@ -532,6 +532,17 @@ static void l2cap_le_flowctl_init(struct l2cap_chan *chan, u16 tx_credits)
>>>      skb_queue_head_init(&chan->tx_q);
>>> }
>>> 
>>> +static void l2cap_ecred_init(struct l2cap_chan *chan, u16 tx_credits)
>>> +{
>>> +     l2cap_le_flowctl_init(chan, tx_credits);
>>> +
>>> +     /* L2CAP implementations shall support a minimum MPS of 64 octets */
>>> +     if (chan->mps < L2CAP_ECRED_MIN_MPS) {
>>> +             chan->mps = L2CAP_ECRED_MIN_MPS;
>>> +             chan->rx_credits = (chan->imtu / chan->mps) + 1;
>>> +     }
>>> +}
>>> +
>>> void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
>>> {
>>>      BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn,
>>> @@ -638,6 +649,7 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
>>>              break;
>>> 
>>>      case L2CAP_MODE_LE_FLOWCTL:
>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>              skb_queue_purge(&chan->tx_q);
>>>              break;
>>> 
>>> @@ -1260,6 +1272,17 @@ static void l2cap_move_done(struct l2cap_chan *chan)
>>>      }
>>> }
>>> 
>>> +static bool l2cap_mode_ready(struct l2cap_chan *chan)
>>> +{
>>> +     switch (chan->mode) {
>>> +     case L2CAP_MODE_LE_FLOWCTL:
>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>> +             return chan->tx_credits ? true : false;
>>> +     }
>>> +
>>> +     return true;
>>> +}
>>> +
>>> static void l2cap_chan_ready(struct l2cap_chan *chan)
>>> {
>>>      /* The channel may have already been flagged as connected in
>>> @@ -1273,7 +1296,7 @@ static void l2cap_chan_ready(struct l2cap_chan *chan)
>>>      chan->conf_state = 0;
>>>      __clear_chan_timer(chan);
>>> 
>>> -     if (chan->mode == L2CAP_MODE_LE_FLOWCTL && !chan->tx_credits)
>>> +     if (!l2cap_mode_ready(chan))
>>>              chan->ops->suspend(chan);
>> 
>>        switch (chan->mode) {
>>        case ..
>>        case ..
>>                if (!chan->tx_credits)
>>                        chan->ops->suspend(chan);
>>                break;
>>        }
>> 
>>> 
>>>      chan->state = BT_CONNECTED;
>>> @@ -1306,6 +1329,31 @@ static void l2cap_le_connect(struct l2cap_chan *chan)
>>>                     sizeof(req), &req);
>>> }
>>> 
>>> +static void l2cap_ecred_connect(struct l2cap_chan *chan)
>>> +{
>>> +     struct l2cap_conn *conn = chan->conn;
>>> +     struct {
>>> +             struct l2cap_ecred_conn_req req;
>>> +             __le16 scid;
>>> +     } __packed pdu;
>>> +
>>> +     if (test_and_set_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags))
>>> +             return;
>>> +
>>> +     l2cap_ecred_init(chan, 0);
>>> +
>>> +     pdu.req.psm     = chan->psm;
>>> +     pdu.req.mtu     = cpu_to_le16(chan->imtu);
>>> +     pdu.req.mps     = cpu_to_le16(chan->mps);
>>> +     pdu.req.credits = cpu_to_le16(chan->rx_credits);
>>> +     pdu.scid        = cpu_to_le16(chan->scid);
>>> +
>>> +     chan->ident = l2cap_get_ident(conn);
>>> +
>>> +     l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_CONN_REQ,
>>> +                    sizeof(pdu), &pdu);
>>> +}
>>> +
>>> static void l2cap_le_start(struct l2cap_chan *chan)
>>> {
>>>      struct l2cap_conn *conn = chan->conn;
>>> @@ -1318,8 +1366,12 @@ static void l2cap_le_start(struct l2cap_chan *chan)
>>>              return;
>>>      }
>>> 
>>> -     if (chan->state == BT_CONNECT)
>>> -             l2cap_le_connect(chan);
>>> +     if (chan->state == BT_CONNECT) {
>>> +             if (chan->mode == L2CAP_MODE_EXT_FLOWCTL)
>>> +                     l2cap_ecred_connect(chan);
>>> +             else
>>> +                     l2cap_le_connect(chan);
>>> +     }
>>> }
>>> 
>>> static void l2cap_start_connection(struct l2cap_chan *chan)
>>> @@ -2505,6 +2557,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
>>> 
>>>      switch (chan->mode) {
>>>      case L2CAP_MODE_LE_FLOWCTL:
>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>              /* Check outgoing MTU */
>>>              if (len > chan->omtu)
>>>                      return -EMSGSIZE;
>>> @@ -3773,6 +3826,42 @@ void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan)
>>>                     &rsp);
>>> }
>>> 
>>> +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
>>> +{
>>> +     struct l2cap_ecred_conn_rsp rsp;
>>> +     struct l2cap_conn *conn = chan->conn;
>>> +     u16 ident = chan->ident;
>>> +     int i = 0;
>>> +
>>> +     if (!ident) {
>>> +             return;
>>> +     }
>>> +
>> 
>> No { } here.
>> 
>>> +     BT_DBG("chan %p ident %d", chan, ident);
>>> +
>>> +     rsp.mtu     = cpu_to_le16(chan->imtu);
>>> +     rsp.mps     = cpu_to_le16(chan->mps);
>>> +     rsp.credits = cpu_to_le16(chan->rx_credits);
>>> +     rsp.result  = cpu_to_le16(L2CAP_CR_LE_SUCCESS);
>>> +
>>> +     mutex_lock(&conn->chan_lock);
>>> +
>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
>>> +             if (chan->ident != ident)
>>> +                     continue;
>>> +
>>> +             /* Reset ident so only one response is sent */
>>> +             chan->ident = 0;
>>> +
>>> +             /* Include all channels pending with the same ident */
>>> +             rsp.dcid[i++] = cpu_to_le16(chan->scid);
>> 
>> This doesn’t work. The sizeof(rsp) has no dcid in there. You are overflowing into conn struct here.
>> 
>>> +     }
>>> +
>>> +     mutex_unlock(&conn->chan_lock);
>>> +
>>> +     l2cap_send_cmd(conn, ident, L2CAP_ECRED_CONN_RSP, sizeof(rsp), &rsp);
>>> +}
>>> +
>>> void __l2cap_connect_rsp_defer(struct l2cap_chan *chan)
>>> {
>>>      struct l2cap_conn_rsp rsp;
>>> @@ -5714,6 +5803,347 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn,
>>>      return 0;
>>> }
>>> 
>>> +static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
>>> +                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>> +                                    u8 *data)
>>> +{
>>> +     struct l2cap_ecred_conn_req *req = (void *) data;
>>> +     struct {
>>> +             struct l2cap_ecred_conn_rsp rsp;
>>> +             __le16 dcid[5];
>>> +     } __packed pdu;
>>> +     struct l2cap_chan *chan, *pchan;
>>> +     u16 credits, mtu, mps;
>>> +     __le16 psm;
>>> +     u8 result, len = 0;
>>> +     int i;
>>> +     bool defer = false;
>>> +
>>> +     if (cmd_len < sizeof(*req))
>>> +             return -EPROTO;
>>> +
>>> +     mtu  = __le16_to_cpu(req->mtu);
>>> +     mps  = __le16_to_cpu(req->mps);
>>> +
>>> +     if (mtu < L2CAP_ECRED_MIN_MTU || mps < L2CAP_ECRED_MIN_MPS) {
>>> +             result = L2CAP_CR_LE_UNACCEPT_PARAMS;
>>> +             goto response;
>>> +     }
>>> +
>>> +     psm  = req->psm;
>>> +     credits = 0;
>>> +
>>> +     BT_DBG("psm 0x%2.2x mtu %u mps %u", __le16_to_cpu(psm), mtu, mps);
>>> +
>>> +     memset(&pdu, 0, sizeof(pdu));
>>> +
>>> +     /* Check if we have socket listening on psm */
>>> +     pchan = l2cap_global_chan_by_psm(BT_LISTEN, psm, &conn->hcon->src,
>>> +                                      &conn->hcon->dst, LE_LINK);
>>> +     if (!pchan) {
>>> +             result = L2CAP_CR_LE_BAD_PSM;
>>> +             goto response;
>>> +     }
>>> +
>>> +     mutex_lock(&conn->chan_lock);
>>> +     l2cap_chan_lock(pchan);
>>> +
>>> +     if (!smp_sufficient_security(conn->hcon, pchan->sec_level,
>>> +                                  SMP_ALLOW_STK)) {
>>> +             result = L2CAP_CR_LE_AUTHENTICATION;
>>> +             goto unlock;
>>> +     }
>>> +
>>> +     result = L2CAP_CR_LE_SUCCESS;
>>> +
>>> +     for (i = 0, cmd_len -= sizeof(req); cmd_len >= sizeof(u16);
>>> +          i++, cmd_len -= sizeof(u16)) {
>> 
>> Can we simplify the loop statement or switch to while statement?
>> 
>>> +             u16 scid = __le16_to_cpu(req->scid[i]);
>>> +
>>> +             BT_DBG("scid[%d] 0x%4.4x", i, scid);
>>> +
>>> +             pdu.dcid[i] = 0x0000;
>>> +             len += sizeof(*pdu.dcid);
>>> +
>>> +             /* Check for valid dynamic CID range */
>>> +             if (scid < L2CAP_CID_DYN_START || scid > L2CAP_CID_LE_DYN_END) {
>>> +                     result = L2CAP_CR_LE_INVALID_SCID;
>>> +                     continue;
>>> +             }
>>> +
>>> +             /* Check if we already have channel with that dcid */
>>> +             if (__l2cap_get_chan_by_dcid(conn, scid)) {
>>> +                     result = L2CAP_CR_LE_SCID_IN_USE;
>>> +                     continue;
>>> +             }
>>> +
>>> +             chan = pchan->ops->new_connection(pchan);
>>> +             if (!chan) {
>>> +                     result = L2CAP_CR_LE_NO_MEM;
>>> +                     continue;
>>> +             }
>>> +
>>> +             bacpy(&chan->src, &conn->hcon->src);
>>> +             bacpy(&chan->dst, &conn->hcon->dst);
>>> +             chan->src_type = bdaddr_src_type(conn->hcon);
>>> +             chan->dst_type = bdaddr_dst_type(conn->hcon);
>>> +             chan->psm  = psm;
>>> +             chan->dcid = scid;
>>> +             chan->omtu = mtu;
>>> +             chan->remote_mps = mps;
>>> +
>>> +             __l2cap_chan_add(conn, chan);
>>> +
>>> +             l2cap_ecred_init(chan, __le16_to_cpu(req->credits));
>>> +
>>> +             /* Init response */
>>> +             if (!pdu.rsp.credits) {
>>> +                     pdu.rsp.mtu = cpu_to_le16(chan->imtu);
>>> +                     pdu.rsp.mps = cpu_to_le16(chan->mps);
>>> +                     pdu.rsp.credits = cpu_to_le16(chan->rx_credits);
>>> +             }
>>> +
>>> +             pdu.dcid[i] = cpu_to_le16(chan->scid);
>>> +
>>> +             __set_chan_timer(chan, chan->ops->get_sndtimeo(chan));
>>> +
>>> +             chan->ident = cmd->ident;
>>> +
>>> +             if (test_bit(FLAG_DEFER_SETUP, &chan->flags)) {
>>> +                     l2cap_state_change(chan, BT_CONNECT2);
>>> +                     defer = true;
>>> +                     chan->ops->defer(chan);
>>> +             } else {
>>> +                     l2cap_chan_ready(chan);
>>> +             }
>>> +     }
>>> +
>>> +unlock:
>>> +     l2cap_chan_unlock(pchan);
>>> +     mutex_unlock(&conn->chan_lock);
>>> +     l2cap_chan_put(pchan);
>>> +
>>> +response:
>>> +     pdu.rsp.result = cpu_to_le16(result);
>>> +
>>> +     if (defer)
>>> +             return 0;
>>> +
>>> +     l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_CONN_RSP,
>>> +                    sizeof(pdu.rsp) + len, &pdu);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
>>> +                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>> +                                    u8 *data)
>>> +{
>>> +     struct l2cap_ecred_conn_rsp *rsp = (void *) data;
>>> +     struct hci_conn *hcon = conn->hcon;
>>> +     u16 mtu, mps, credits, result;
>>> +     struct l2cap_chan *chan;
>>> +     int err = 0, sec_level;
>>> +     int i = 0;
>>> +
>>> +     if (cmd_len < sizeof(*rsp))
>>> +             return -EPROTO;
>>> +
>>> +     mtu     = __le16_to_cpu(rsp->mtu);
>>> +     mps     = __le16_to_cpu(rsp->mps);
>>> +     credits = __le16_to_cpu(rsp->credits);
>>> +     result  = __le16_to_cpu(rsp->result);
>>> +
>>> +     BT_DBG("mtu %u mps %u credits %u result 0x%4.4x", mtu, mps, credits,
>>> +            result);
>>> +
>>> +     mutex_lock(&conn->chan_lock);
>>> +
>>> +     cmd_len -= sizeof(*rsp);
>>> +
>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
>>> +             u16 dcid;
>>> +
>>> +             if (chan->ident != cmd->ident ||
>>> +                 chan->mode != L2CAP_MODE_EXT_FLOWCTL ||
>>> +                 chan->state == BT_CONNECTED)
>>> +                     continue;
>>> +
>>> +             l2cap_chan_lock(chan);
>>> +
>>> +             /* Check that there is a dcid for each pending channel */
>>> +             if (cmd_len < sizeof(dcid)) {
>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
>>> +                     l2cap_chan_unlock(chan);
>>> +                     continue;
>>> +             }
>>> +
>>> +             dcid = __le16_to_cpu(rsp->dcid[i++]);
>>> +             cmd_len -= sizeof(u16);
>>> +
>>> +             BT_DBG("dcid[%d] 0x%4.4x", i, dcid);
>>> +
>>> +             /* Check if dcid is already in use */
>>> +             if (dcid && __l2cap_get_chan_by_dcid(conn, dcid)) {
>>> +                     /* If a device receives a
>>> +                      * L2CAP_CREDIT_BASED_CONNECTION_RSP packet with an
>>> +                      * already-assigned Destination CID, then both the
>>> +                      * original channel and the new channel shall be
>>> +                      * immediately discarded and not used.
>>> +                      */
>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
>>> +                     l2cap_chan_unlock(chan);
>>> +                     chan = __l2cap_get_chan_by_dcid(conn, dcid);
>>> +                     l2cap_chan_lock(chan);
>>> +                     l2cap_chan_del(chan, ECONNRESET);
>>> +                     l2cap_chan_unlock(chan);
>>> +                     continue;
>>> +             }
>>> +
>>> +             switch (result) {
>>> +             case L2CAP_CR_LE_AUTHENTICATION:
>>> +             case L2CAP_CR_LE_ENCRYPTION:
>>> +                     /* If we already have MITM protection we can't do
>>> +                      * anything.
>>> +                      */
>>> +                     if (hcon->sec_level > BT_SECURITY_MEDIUM) {
>>> +                             l2cap_chan_del(chan, ECONNREFUSED);
>>> +                             break;
>>> +                     }
>>> +
>>> +                     sec_level = hcon->sec_level + 1;
>>> +                     if (chan->sec_level < sec_level)
>>> +                             chan->sec_level = sec_level;
>>> +
>>> +                     /* We'll need to send a new Connect Request */
>>> +                     clear_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags);
>>> +
>>> +                     smp_conn_security(hcon, chan->sec_level);
>>> +                     break;
>>> +
>>> +             case L2CAP_CR_LE_BAD_PSM:
>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
>>> +                     break;
>>> +
>>> +             default:
>>> +                     /* If dcid was not set it means channels was refused */
>>> +                     if (!dcid) {
>>> +                             l2cap_chan_del(chan, ECONNREFUSED);
>>> +                             break;
>>> +                     }
>>> +
>>> +                     chan->ident = 0;
>>> +                     chan->dcid = dcid;
>>> +                     chan->omtu = mtu;
>>> +                     chan->remote_mps = mps;
>>> +                     chan->tx_credits = credits;
>>> +                     l2cap_chan_ready(chan);
>>> +                     break;
>>> +             }
>>> +
>>> +             l2cap_chan_unlock(chan);
>>> +     }
>>> +
>>> +     mutex_unlock(&conn->chan_lock);
>>> +
>>> +     return err;
>>> +}
>>> +
>>> +static inline int l2cap_ecred_reconf_req(struct l2cap_conn *conn,
>>> +                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>> +                                      u8 *data)
>>> +{
>>> +     struct l2cap_ecred_reconf_req *req = (void *) data;
>>> +     struct l2cap_ecred_reconf_rsp rsp;
>>> +     u16 mtu, mps, result;
>>> +     struct l2cap_chan *chan;
>>> +     int i;
>>> +
>>> +     if (cmd_len < sizeof(*req)) {
>>> +             result = L2CAP_CR_LE_INVALID_PARAMS;
>>> +             goto respond;
>>> +     }
>>> +
>>> +     mtu = __le16_to_cpu(req->mtu);
>>> +     mps = __le16_to_cpu(req->mps);
>>> +
>>> +     BT_DBG("mtu %u mps %u", mtu, mps);
>>> +
>>> +     if (mtu < L2CAP_ECRED_MIN_MTU) {
>>> +             result = L2CAP_RECONF_INVALID_MTU;
>>> +             goto respond;
>>> +     }
>>> +
>>> +     if (mps < L2CAP_ECRED_MIN_MPS) {
>>> +             result = L2CAP_RECONF_INVALID_MPS;
>>> +             goto respond;
>>> +     }
>>> +
>>> +     result = L2CAP_RECONF_SUCCESS;
>>> +
>>> +     for (i = 0, cmd_len -= sizeof(*req); cmd_len < sizeof(u16);
>>> +          i++, cmd_len -= sizeof(u16)) {
>> 
>> Same comment as above.
>> 
>>> +             u16 scid;
>>> +
>>> +             scid = __le16_to_cpu(req->scid[i]);
>>> +             if (!scid)
>>> +                     return -EPROTO;
>>> +
>>> +             chan = __l2cap_get_chan_by_dcid(conn, scid);
>>> +             if (!chan)
>>> +                     continue;
>>> +
>>> +             /* If the MTU value is decreased for any of the included
>>> +              * channels, then the receiver shall disconnect all
>>> +              * included channels.
>>> +              */
>>> +             if (chan->omtu > mtu) {
>>> +                     BT_ERR("chan %p decreased MTU %u -> %u", chan,
>>> +                            chan->omtu, mtu);
>>> +                     result = L2CAP_RECONF_INVALID_MTU;
>>> +             }
>>> +
>>> +             chan->omtu = mtu;
>>> +             chan->remote_mps = mps;
>>> +     }
>>> +
>>> +respond:
>>> +     rsp.result = cpu_to_le16(result);
>>> +
>>> +     l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_RECONF_RSP, sizeof(rsp),
>>> +                    &rsp);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static inline int l2cap_ecred_reconf_rsp(struct l2cap_conn *conn,
>>> +                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>> +                                      u8 *data)
>>> +{
>>> +     struct l2cap_chan *chan;
>>> +     struct l2cap_ecred_conn_rsp *rsp = (void *) data;
>>> +     u16 result;
>>> +
>>> +     if (cmd_len < sizeof(*rsp))
>>> +             return -EPROTO;
>>> +
>>> +     result = __le16_to_cpu(rsp->result);
>>> +
>>> +     BT_DBG("result 0x%4.4x", rsp->result);
>>> +
>>> +     if (!result)
>>> +             return 0;
>>> +
>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
>>> +             if (chan->ident != cmd->ident)
>>> +                     continue;
>>> +
>>> +             l2cap_chan_del(chan, ECONNRESET);
>>> +     }
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> static inline int l2cap_le_command_rej(struct l2cap_conn *conn,
>>>                                     struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>                                     u8 *data)
>>> @@ -5769,6 +6199,22 @@ static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn,
>>>              err = l2cap_le_credits(conn, cmd, cmd_len, data);
>>>              break;
>>> 
>>> +     case L2CAP_ECRED_CONN_REQ:
>>> +             err = l2cap_ecred_conn_req(conn, cmd, cmd_len, data);
>>> +             break;
>>> +
>>> +     case L2CAP_ECRED_CONN_RSP:
>>> +             err = l2cap_ecred_conn_rsp(conn, cmd, cmd_len, data);
>>> +             break;
>>> +
>>> +     case L2CAP_ECRED_RECONF_REQ:
>>> +             err = l2cap_ecred_reconf_req(conn, cmd, cmd_len, data);
>>> +             break;
>>> +
>>> +     case L2CAP_ECRED_RECONF_RSP:
>>> +             err = l2cap_ecred_reconf_rsp(conn, cmd, cmd_len, data);
>>> +             break;
>>> +
>>>      case L2CAP_DISCONN_REQ:
>>>              err = l2cap_disconnect_req(conn, cmd, cmd_len, data);
>>>              break;
>>> @@ -6814,11 +7260,13 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
>>>      struct l2cap_le_credits pkt;
>>>      u16 return_credits;
>>> 
>>> -     return_credits = ((chan->imtu / chan->mps) + 1) - chan->rx_credits;
>>> +     return_credits = (chan->imtu / chan->mps) + 1;
>>> 
>>> -     if (!return_credits)
>>> +     if (chan->rx_credits >= return_credits)
>>>              return;
>>> 
>>> +     return_credits -= chan->rx_credits;
>>> +
>>>      BT_DBG("chan %p returning %u credits to sender", chan, return_credits);
>>> 
>>>      chan->rx_credits += return_credits;
>>> @@ -6831,7 +7279,7 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
>>>      l2cap_send_cmd(conn, chan->ident, L2CAP_LE_CREDITS, sizeof(pkt), &pkt);
>>> }
>>> 
>>> -static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
>>> +static int l2cap_ecred_recv(struct l2cap_chan *chan, struct sk_buff *skb)
>>> {
>>>      int err;
>>> 
>>> @@ -6846,7 +7294,7 @@ static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>      return err;
>>> }
>>> 
>>> -static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
>>> +static int l2cap_ecred_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
>>> {
>>>      int err;
>>> 
>>> @@ -6894,7 +7342,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>              }
>>> 
>>>              if (skb->len == sdu_len)
>>> -                     return l2cap_le_recv(chan, skb);
>>> +                     return l2cap_ecred_recv(chan, skb);
>>> 
>>>              chan->sdu = skb;
>>>              chan->sdu_len = sdu_len;
>>> @@ -6926,7 +7374,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>      skb = NULL;
>>> 
>>>      if (chan->sdu->len == chan->sdu_len) {
>>> -             err = l2cap_le_recv(chan, chan->sdu);
>>> +             err = l2cap_ecred_recv(chan, chan->sdu);
>>>              if (!err) {
>>>                      chan->sdu = NULL;
>>>                      chan->sdu_last_frag = NULL;
>>> @@ -6987,7 +7435,8 @@ static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid,
>>> 
>>>      switch (chan->mode) {
>>>      case L2CAP_MODE_LE_FLOWCTL:
>>> -             if (l2cap_le_data_rcv(chan, skb) < 0)
>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>> +             if (l2cap_ecred_data_rcv(chan, skb) < 0)
>>>                      goto drop;
>>> 
>>>              goto done;
>>> @@ -7214,8 +7663,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
>>>      struct hci_dev *hdev;
>>>      int err;
>>> 
>>> -     BT_DBG("%pMR -> %pMR (type %u) psm 0x%2.2x", &chan->src, dst,
>>> -            dst_type, __le16_to_cpu(psm));
>>> +     BT_DBG("%pMR -> %pMR (type %u) psm 0x%4.4x mode 0x%2.2x", &chan->src,
>>> +            dst, dst_type, __le16_to_cpu(psm), chan->mode);
>>> 
>>>      hdev = hci_get_route(dst, &chan->src, chan->src_type);
>>>      if (!hdev)
>>> @@ -7244,6 +7693,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
>>>              break;
>>>      case L2CAP_MODE_LE_FLOWCTL:
>>>              break;
>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>> +             break;
>> 
>> No need to do another break. Just put it under LE_FLOWCTL.
>> 
>>>      case L2CAP_MODE_ERTM:
>>>      case L2CAP_MODE_STREAMING:
>>>              if (!disable_ertm)
>>> @@ -7368,6 +7819,38 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
>>> }
>>> EXPORT_SYMBOL_GPL(l2cap_chan_connect);
>>> 
>>> +static void l2cap_ecred_reconfigure(struct l2cap_chan *chan)
>>> +{
>>> +     struct l2cap_conn *conn = chan->conn;
>>> +     struct {
>>> +             struct l2cap_ecred_reconf_req req;
>>> +             __le16 scid;
>>> +     } pdu;
>>> +
>>> +     pdu.req.mtu = cpu_to_le16(chan->imtu);
>>> +     pdu.req.mps = cpu_to_le16(chan->mps);
>>> +     pdu.scid    = cpu_to_le16(chan->scid);
>>> +
>>> +     chan->ident = l2cap_get_ident(conn);
>>> +
>>> +     l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_RECONF_REQ,
>>> +                    sizeof(pdu), &pdu);
>>> +}
>>> +
>>> +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu)
>>> +{
>>> +     if (chan->imtu > mtu)
>>> +             return -EINVAL;
>>> +
>>> +     BT_DBG("chan %p mtu 0x%4.4x", chan, mtu);
>>> +
>>> +     chan->imtu = mtu;
>>> +
>>> +     l2cap_ecred_reconfigure(chan);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> /* ---- L2CAP interface with lower layer (HCI) ---- */
>>> 
>>> int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr)
>>> @@ -7579,7 +8062,8 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
>>>                      else
>>>                              __set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
>>>              } else if (chan->state == BT_CONNECT2 &&
>>> -                        chan->mode != L2CAP_MODE_LE_FLOWCTL) {
>>> +                        !(chan->mode == L2CAP_MODE_EXT_FLOWCTL ||
>>> +                         chan->mode == L2CAP_MODE_LE_FLOWCTL)) {
>> 
>> Please double check that this line is correctly aligned after the (.
>> 
>>>                      struct l2cap_conn_rsp rsp;
>>>                      __u16 res, stat;
>>> 
>>> diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
>>> index a7be8b59b3c2..12c557f7f40f 100644
>>> --- a/net/bluetooth/l2cap_sock.c
>>> +++ b/net/bluetooth/l2cap_sock.c
>>> @@ -232,7 +232,7 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr,
>>>                      return -EINVAL;
>>>      }
>>> 
>>> -     if (chan->psm && bdaddr_type_is_le(chan->src_type))
>>> +     if (chan->psm && bdaddr_type_is_le(chan->src_type) && !chan->mode)
>>>              chan->mode = L2CAP_MODE_LE_FLOWCTL;
>>> 
>>>      err = l2cap_chan_connect(chan, la.l2_psm, __le16_to_cpu(la.l2_cid),
>>> @@ -273,6 +273,7 @@ static int l2cap_sock_listen(struct socket *sock, int backlog)
>>>      switch (chan->mode) {
>>>      case L2CAP_MODE_BASIC:
>>>      case L2CAP_MODE_LE_FLOWCTL:
>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>              break;
>>>      case L2CAP_MODE_ERTM:
>>>      case L2CAP_MODE_STREAMING:
>>> @@ -408,16 +409,6 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
>>> 
>>>      switch (optname) {
>>>      case L2CAP_OPTIONS:
>>> -             /* LE sockets should use BT_SNDMTU/BT_RCVMTU, but since
>>> -              * legacy ATT code depends on getsockopt for
>>> -              * L2CAP_OPTIONS we need to let this pass.
>>> -              */
>>> -             if (bdaddr_type_is_le(chan->src_type) &&
>>> -                 chan->scid != L2CAP_CID_ATT) {
>>> -                     err = -EINVAL;
>>> -                     break;
>>> -             }
>>> -
>> 
>> Should this be a separate patch. How do we keep this legacy behavior.
> 
> I kind have forgotten to fix this one, I guess we will need a new
> option in order to read the mode without using the old L2CAP_OPTIONS
> then, or do you have anything against introducing yet another option?

so what is the problem here? Is this because L2CAP by itself can not tell if enhanced credit mode is supported by the peer?

Do you actually want to create a new mode socket options with defined values by us to allow selected the mode of operation?

Regards

Marcel


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] Bluetooth: L2CAP: Add initial code for Enhanced Credit Based Mode
  2020-01-24 16:39       ` Marcel Holtmann
@ 2020-01-24 19:13         ` Luiz Augusto von Dentz
  2020-01-25  7:33           ` Marcel Holtmann
  0 siblings, 1 reply; 14+ messages in thread
From: Luiz Augusto von Dentz @ 2020-01-24 19:13 UTC (permalink / raw)
  To: Marcel Holtmann; +Cc: linux-bluetooth

Hi Marcel,

On Fri, Jan 24, 2020 at 8:39 AM Marcel Holtmann <marcel@holtmann.org> wrote:
>
> Hi Luiz,
>
> >>> This adds the initial code for Enhanced Credit Based Mode which
> >>> introduces a new socket mode called L2CAP_MODE_EXT_FLOWCTL, which for
> >>> the most part work the same as L2CAP_MODE_LE_FLOWCTL but uses different
> >>> PDUs to setup the connections and also works over BR/EDR.
> >>>
> >>> Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
> >>> ---
> >>> include/net/bluetooth/l2cap.h |   4 +
> >>> net/bluetooth/l2cap_core.c    | 510 +++++++++++++++++++++++++++++++++-
> >>> net/bluetooth/l2cap_sock.c    |  39 +--
> >>> 3 files changed, 521 insertions(+), 32 deletions(-)
> >>>
> >>> diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
> >>> index b9ea88cfd2d2..11ae4c93354b 100644
> >>> --- a/include/net/bluetooth/l2cap.h
> >>> +++ b/include/net/bluetooth/l2cap.h
> >>> @@ -295,6 +295,8 @@ struct l2cap_conn_rsp {
> >>> #define L2CAP_CR_LE_ENCRYPTION                0x0008
> >>> #define L2CAP_CR_LE_INVALID_SCID      0x0009
> >>> #define L2CAP_CR_LE_SCID_IN_USE               0X000A
> >>> +#define L2CAP_CR_LE_UNACCEPT_PARAMS  0X000B
> >>> +#define L2CAP_CR_LE_INVALID_PARAMS   0X000C
> >>>
> >>> /* connect/create channel status */
> >>> #define L2CAP_CS_NO_INFO      0x0000
> >>> @@ -964,6 +966,7 @@ void l2cap_cleanup_sockets(void);
> >>> bool l2cap_is_socket(struct socket *sock);
> >>>
> >>> void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan);
> >>> +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan);
> >>> void __l2cap_connect_rsp_defer(struct l2cap_chan *chan);
> >>>
> >>> int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm);
> >>> @@ -973,6 +976,7 @@ struct l2cap_chan *l2cap_chan_create(void);
> >>> void l2cap_chan_close(struct l2cap_chan *chan, int reason);
> >>> int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> >>>                     bdaddr_t *dst, u8 dst_type);
> >>> +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu);
> >>> int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len);
> >>> void l2cap_chan_busy(struct l2cap_chan *chan, int busy);
> >>> int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator);
> >>> diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
> >>> index 195459a1e53e..a528526cffac 100644
> >>> --- a/net/bluetooth/l2cap_core.c
> >>> +++ b/net/bluetooth/l2cap_core.c
> >>> @@ -532,6 +532,17 @@ static void l2cap_le_flowctl_init(struct l2cap_chan *chan, u16 tx_credits)
> >>>      skb_queue_head_init(&chan->tx_q);
> >>> }
> >>>
> >>> +static void l2cap_ecred_init(struct l2cap_chan *chan, u16 tx_credits)
> >>> +{
> >>> +     l2cap_le_flowctl_init(chan, tx_credits);
> >>> +
> >>> +     /* L2CAP implementations shall support a minimum MPS of 64 octets */
> >>> +     if (chan->mps < L2CAP_ECRED_MIN_MPS) {
> >>> +             chan->mps = L2CAP_ECRED_MIN_MPS;
> >>> +             chan->rx_credits = (chan->imtu / chan->mps) + 1;
> >>> +     }
> >>> +}
> >>> +
> >>> void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
> >>> {
> >>>      BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn,
> >>> @@ -638,6 +649,7 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
> >>>              break;
> >>>
> >>>      case L2CAP_MODE_LE_FLOWCTL:
> >>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>              skb_queue_purge(&chan->tx_q);
> >>>              break;
> >>>
> >>> @@ -1260,6 +1272,17 @@ static void l2cap_move_done(struct l2cap_chan *chan)
> >>>      }
> >>> }
> >>>
> >>> +static bool l2cap_mode_ready(struct l2cap_chan *chan)
> >>> +{
> >>> +     switch (chan->mode) {
> >>> +     case L2CAP_MODE_LE_FLOWCTL:
> >>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>> +             return chan->tx_credits ? true : false;
> >>> +     }
> >>> +
> >>> +     return true;
> >>> +}
> >>> +
> >>> static void l2cap_chan_ready(struct l2cap_chan *chan)
> >>> {
> >>>      /* The channel may have already been flagged as connected in
> >>> @@ -1273,7 +1296,7 @@ static void l2cap_chan_ready(struct l2cap_chan *chan)
> >>>      chan->conf_state = 0;
> >>>      __clear_chan_timer(chan);
> >>>
> >>> -     if (chan->mode == L2CAP_MODE_LE_FLOWCTL && !chan->tx_credits)
> >>> +     if (!l2cap_mode_ready(chan))
> >>>              chan->ops->suspend(chan);
> >>
> >>        switch (chan->mode) {
> >>        case ..
> >>        case ..
> >>                if (!chan->tx_credits)
> >>                        chan->ops->suspend(chan);
> >>                break;
> >>        }
> >>
> >>>
> >>>      chan->state = BT_CONNECTED;
> >>> @@ -1306,6 +1329,31 @@ static void l2cap_le_connect(struct l2cap_chan *chan)
> >>>                     sizeof(req), &req);
> >>> }
> >>>
> >>> +static void l2cap_ecred_connect(struct l2cap_chan *chan)
> >>> +{
> >>> +     struct l2cap_conn *conn = chan->conn;
> >>> +     struct {
> >>> +             struct l2cap_ecred_conn_req req;
> >>> +             __le16 scid;
> >>> +     } __packed pdu;
> >>> +
> >>> +     if (test_and_set_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags))
> >>> +             return;
> >>> +
> >>> +     l2cap_ecred_init(chan, 0);
> >>> +
> >>> +     pdu.req.psm     = chan->psm;
> >>> +     pdu.req.mtu     = cpu_to_le16(chan->imtu);
> >>> +     pdu.req.mps     = cpu_to_le16(chan->mps);
> >>> +     pdu.req.credits = cpu_to_le16(chan->rx_credits);
> >>> +     pdu.scid        = cpu_to_le16(chan->scid);
> >>> +
> >>> +     chan->ident = l2cap_get_ident(conn);
> >>> +
> >>> +     l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_CONN_REQ,
> >>> +                    sizeof(pdu), &pdu);
> >>> +}
> >>> +
> >>> static void l2cap_le_start(struct l2cap_chan *chan)
> >>> {
> >>>      struct l2cap_conn *conn = chan->conn;
> >>> @@ -1318,8 +1366,12 @@ static void l2cap_le_start(struct l2cap_chan *chan)
> >>>              return;
> >>>      }
> >>>
> >>> -     if (chan->state == BT_CONNECT)
> >>> -             l2cap_le_connect(chan);
> >>> +     if (chan->state == BT_CONNECT) {
> >>> +             if (chan->mode == L2CAP_MODE_EXT_FLOWCTL)
> >>> +                     l2cap_ecred_connect(chan);
> >>> +             else
> >>> +                     l2cap_le_connect(chan);
> >>> +     }
> >>> }
> >>>
> >>> static void l2cap_start_connection(struct l2cap_chan *chan)
> >>> @@ -2505,6 +2557,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
> >>>
> >>>      switch (chan->mode) {
> >>>      case L2CAP_MODE_LE_FLOWCTL:
> >>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>              /* Check outgoing MTU */
> >>>              if (len > chan->omtu)
> >>>                      return -EMSGSIZE;
> >>> @@ -3773,6 +3826,42 @@ void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan)
> >>>                     &rsp);
> >>> }
> >>>
> >>> +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
> >>> +{
> >>> +     struct l2cap_ecred_conn_rsp rsp;
> >>> +     struct l2cap_conn *conn = chan->conn;
> >>> +     u16 ident = chan->ident;
> >>> +     int i = 0;
> >>> +
> >>> +     if (!ident) {
> >>> +             return;
> >>> +     }
> >>> +
> >>
> >> No { } here.
> >>
> >>> +     BT_DBG("chan %p ident %d", chan, ident);
> >>> +
> >>> +     rsp.mtu     = cpu_to_le16(chan->imtu);
> >>> +     rsp.mps     = cpu_to_le16(chan->mps);
> >>> +     rsp.credits = cpu_to_le16(chan->rx_credits);
> >>> +     rsp.result  = cpu_to_le16(L2CAP_CR_LE_SUCCESS);
> >>> +
> >>> +     mutex_lock(&conn->chan_lock);
> >>> +
> >>> +     list_for_each_entry(chan, &conn->chan_l, list) {
> >>> +             if (chan->ident != ident)
> >>> +                     continue;
> >>> +
> >>> +             /* Reset ident so only one response is sent */
> >>> +             chan->ident = 0;
> >>> +
> >>> +             /* Include all channels pending with the same ident */
> >>> +             rsp.dcid[i++] = cpu_to_le16(chan->scid);
> >>
> >> This doesn’t work. The sizeof(rsp) has no dcid in there. You are overflowing into conn struct here.
> >>
> >>> +     }
> >>> +
> >>> +     mutex_unlock(&conn->chan_lock);
> >>> +
> >>> +     l2cap_send_cmd(conn, ident, L2CAP_ECRED_CONN_RSP, sizeof(rsp), &rsp);
> >>> +}
> >>> +
> >>> void __l2cap_connect_rsp_defer(struct l2cap_chan *chan)
> >>> {
> >>>      struct l2cap_conn_rsp rsp;
> >>> @@ -5714,6 +5803,347 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn,
> >>>      return 0;
> >>> }
> >>>
> >>> +static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
> >>> +                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >>> +                                    u8 *data)
> >>> +{
> >>> +     struct l2cap_ecred_conn_req *req = (void *) data;
> >>> +     struct {
> >>> +             struct l2cap_ecred_conn_rsp rsp;
> >>> +             __le16 dcid[5];
> >>> +     } __packed pdu;
> >>> +     struct l2cap_chan *chan, *pchan;
> >>> +     u16 credits, mtu, mps;
> >>> +     __le16 psm;
> >>> +     u8 result, len = 0;
> >>> +     int i;
> >>> +     bool defer = false;
> >>> +
> >>> +     if (cmd_len < sizeof(*req))
> >>> +             return -EPROTO;
> >>> +
> >>> +     mtu  = __le16_to_cpu(req->mtu);
> >>> +     mps  = __le16_to_cpu(req->mps);
> >>> +
> >>> +     if (mtu < L2CAP_ECRED_MIN_MTU || mps < L2CAP_ECRED_MIN_MPS) {
> >>> +             result = L2CAP_CR_LE_UNACCEPT_PARAMS;
> >>> +             goto response;
> >>> +     }
> >>> +
> >>> +     psm  = req->psm;
> >>> +     credits = 0;
> >>> +
> >>> +     BT_DBG("psm 0x%2.2x mtu %u mps %u", __le16_to_cpu(psm), mtu, mps);
> >>> +
> >>> +     memset(&pdu, 0, sizeof(pdu));
> >>> +
> >>> +     /* Check if we have socket listening on psm */
> >>> +     pchan = l2cap_global_chan_by_psm(BT_LISTEN, psm, &conn->hcon->src,
> >>> +                                      &conn->hcon->dst, LE_LINK);
> >>> +     if (!pchan) {
> >>> +             result = L2CAP_CR_LE_BAD_PSM;
> >>> +             goto response;
> >>> +     }
> >>> +
> >>> +     mutex_lock(&conn->chan_lock);
> >>> +     l2cap_chan_lock(pchan);
> >>> +
> >>> +     if (!smp_sufficient_security(conn->hcon, pchan->sec_level,
> >>> +                                  SMP_ALLOW_STK)) {
> >>> +             result = L2CAP_CR_LE_AUTHENTICATION;
> >>> +             goto unlock;
> >>> +     }
> >>> +
> >>> +     result = L2CAP_CR_LE_SUCCESS;
> >>> +
> >>> +     for (i = 0, cmd_len -= sizeof(req); cmd_len >= sizeof(u16);
> >>> +          i++, cmd_len -= sizeof(u16)) {
> >>
> >> Can we simplify the loop statement or switch to while statement?
> >>
> >>> +             u16 scid = __le16_to_cpu(req->scid[i]);
> >>> +
> >>> +             BT_DBG("scid[%d] 0x%4.4x", i, scid);
> >>> +
> >>> +             pdu.dcid[i] = 0x0000;
> >>> +             len += sizeof(*pdu.dcid);
> >>> +
> >>> +             /* Check for valid dynamic CID range */
> >>> +             if (scid < L2CAP_CID_DYN_START || scid > L2CAP_CID_LE_DYN_END) {
> >>> +                     result = L2CAP_CR_LE_INVALID_SCID;
> >>> +                     continue;
> >>> +             }
> >>> +
> >>> +             /* Check if we already have channel with that dcid */
> >>> +             if (__l2cap_get_chan_by_dcid(conn, scid)) {
> >>> +                     result = L2CAP_CR_LE_SCID_IN_USE;
> >>> +                     continue;
> >>> +             }
> >>> +
> >>> +             chan = pchan->ops->new_connection(pchan);
> >>> +             if (!chan) {
> >>> +                     result = L2CAP_CR_LE_NO_MEM;
> >>> +                     continue;
> >>> +             }
> >>> +
> >>> +             bacpy(&chan->src, &conn->hcon->src);
> >>> +             bacpy(&chan->dst, &conn->hcon->dst);
> >>> +             chan->src_type = bdaddr_src_type(conn->hcon);
> >>> +             chan->dst_type = bdaddr_dst_type(conn->hcon);
> >>> +             chan->psm  = psm;
> >>> +             chan->dcid = scid;
> >>> +             chan->omtu = mtu;
> >>> +             chan->remote_mps = mps;
> >>> +
> >>> +             __l2cap_chan_add(conn, chan);
> >>> +
> >>> +             l2cap_ecred_init(chan, __le16_to_cpu(req->credits));
> >>> +
> >>> +             /* Init response */
> >>> +             if (!pdu.rsp.credits) {
> >>> +                     pdu.rsp.mtu = cpu_to_le16(chan->imtu);
> >>> +                     pdu.rsp.mps = cpu_to_le16(chan->mps);
> >>> +                     pdu.rsp.credits = cpu_to_le16(chan->rx_credits);
> >>> +             }
> >>> +
> >>> +             pdu.dcid[i] = cpu_to_le16(chan->scid);
> >>> +
> >>> +             __set_chan_timer(chan, chan->ops->get_sndtimeo(chan));
> >>> +
> >>> +             chan->ident = cmd->ident;
> >>> +
> >>> +             if (test_bit(FLAG_DEFER_SETUP, &chan->flags)) {
> >>> +                     l2cap_state_change(chan, BT_CONNECT2);
> >>> +                     defer = true;
> >>> +                     chan->ops->defer(chan);
> >>> +             } else {
> >>> +                     l2cap_chan_ready(chan);
> >>> +             }
> >>> +     }
> >>> +
> >>> +unlock:
> >>> +     l2cap_chan_unlock(pchan);
> >>> +     mutex_unlock(&conn->chan_lock);
> >>> +     l2cap_chan_put(pchan);
> >>> +
> >>> +response:
> >>> +     pdu.rsp.result = cpu_to_le16(result);
> >>> +
> >>> +     if (defer)
> >>> +             return 0;
> >>> +
> >>> +     l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_CONN_RSP,
> >>> +                    sizeof(pdu.rsp) + len, &pdu);
> >>> +
> >>> +     return 0;
> >>> +}
> >>> +
> >>> +static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
> >>> +                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >>> +                                    u8 *data)
> >>> +{
> >>> +     struct l2cap_ecred_conn_rsp *rsp = (void *) data;
> >>> +     struct hci_conn *hcon = conn->hcon;
> >>> +     u16 mtu, mps, credits, result;
> >>> +     struct l2cap_chan *chan;
> >>> +     int err = 0, sec_level;
> >>> +     int i = 0;
> >>> +
> >>> +     if (cmd_len < sizeof(*rsp))
> >>> +             return -EPROTO;
> >>> +
> >>> +     mtu     = __le16_to_cpu(rsp->mtu);
> >>> +     mps     = __le16_to_cpu(rsp->mps);
> >>> +     credits = __le16_to_cpu(rsp->credits);
> >>> +     result  = __le16_to_cpu(rsp->result);
> >>> +
> >>> +     BT_DBG("mtu %u mps %u credits %u result 0x%4.4x", mtu, mps, credits,
> >>> +            result);
> >>> +
> >>> +     mutex_lock(&conn->chan_lock);
> >>> +
> >>> +     cmd_len -= sizeof(*rsp);
> >>> +
> >>> +     list_for_each_entry(chan, &conn->chan_l, list) {
> >>> +             u16 dcid;
> >>> +
> >>> +             if (chan->ident != cmd->ident ||
> >>> +                 chan->mode != L2CAP_MODE_EXT_FLOWCTL ||
> >>> +                 chan->state == BT_CONNECTED)
> >>> +                     continue;
> >>> +
> >>> +             l2cap_chan_lock(chan);
> >>> +
> >>> +             /* Check that there is a dcid for each pending channel */
> >>> +             if (cmd_len < sizeof(dcid)) {
> >>> +                     l2cap_chan_del(chan, ECONNREFUSED);
> >>> +                     l2cap_chan_unlock(chan);
> >>> +                     continue;
> >>> +             }
> >>> +
> >>> +             dcid = __le16_to_cpu(rsp->dcid[i++]);
> >>> +             cmd_len -= sizeof(u16);
> >>> +
> >>> +             BT_DBG("dcid[%d] 0x%4.4x", i, dcid);
> >>> +
> >>> +             /* Check if dcid is already in use */
> >>> +             if (dcid && __l2cap_get_chan_by_dcid(conn, dcid)) {
> >>> +                     /* If a device receives a
> >>> +                      * L2CAP_CREDIT_BASED_CONNECTION_RSP packet with an
> >>> +                      * already-assigned Destination CID, then both the
> >>> +                      * original channel and the new channel shall be
> >>> +                      * immediately discarded and not used.
> >>> +                      */
> >>> +                     l2cap_chan_del(chan, ECONNREFUSED);
> >>> +                     l2cap_chan_unlock(chan);
> >>> +                     chan = __l2cap_get_chan_by_dcid(conn, dcid);
> >>> +                     l2cap_chan_lock(chan);
> >>> +                     l2cap_chan_del(chan, ECONNRESET);
> >>> +                     l2cap_chan_unlock(chan);
> >>> +                     continue;
> >>> +             }
> >>> +
> >>> +             switch (result) {
> >>> +             case L2CAP_CR_LE_AUTHENTICATION:
> >>> +             case L2CAP_CR_LE_ENCRYPTION:
> >>> +                     /* If we already have MITM protection we can't do
> >>> +                      * anything.
> >>> +                      */
> >>> +                     if (hcon->sec_level > BT_SECURITY_MEDIUM) {
> >>> +                             l2cap_chan_del(chan, ECONNREFUSED);
> >>> +                             break;
> >>> +                     }
> >>> +
> >>> +                     sec_level = hcon->sec_level + 1;
> >>> +                     if (chan->sec_level < sec_level)
> >>> +                             chan->sec_level = sec_level;
> >>> +
> >>> +                     /* We'll need to send a new Connect Request */
> >>> +                     clear_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags);
> >>> +
> >>> +                     smp_conn_security(hcon, chan->sec_level);
> >>> +                     break;
> >>> +
> >>> +             case L2CAP_CR_LE_BAD_PSM:
> >>> +                     l2cap_chan_del(chan, ECONNREFUSED);
> >>> +                     break;
> >>> +
> >>> +             default:
> >>> +                     /* If dcid was not set it means channels was refused */
> >>> +                     if (!dcid) {
> >>> +                             l2cap_chan_del(chan, ECONNREFUSED);
> >>> +                             break;
> >>> +                     }
> >>> +
> >>> +                     chan->ident = 0;
> >>> +                     chan->dcid = dcid;
> >>> +                     chan->omtu = mtu;
> >>> +                     chan->remote_mps = mps;
> >>> +                     chan->tx_credits = credits;
> >>> +                     l2cap_chan_ready(chan);
> >>> +                     break;
> >>> +             }
> >>> +
> >>> +             l2cap_chan_unlock(chan);
> >>> +     }
> >>> +
> >>> +     mutex_unlock(&conn->chan_lock);
> >>> +
> >>> +     return err;
> >>> +}
> >>> +
> >>> +static inline int l2cap_ecred_reconf_req(struct l2cap_conn *conn,
> >>> +                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >>> +                                      u8 *data)
> >>> +{
> >>> +     struct l2cap_ecred_reconf_req *req = (void *) data;
> >>> +     struct l2cap_ecred_reconf_rsp rsp;
> >>> +     u16 mtu, mps, result;
> >>> +     struct l2cap_chan *chan;
> >>> +     int i;
> >>> +
> >>> +     if (cmd_len < sizeof(*req)) {
> >>> +             result = L2CAP_CR_LE_INVALID_PARAMS;
> >>> +             goto respond;
> >>> +     }
> >>> +
> >>> +     mtu = __le16_to_cpu(req->mtu);
> >>> +     mps = __le16_to_cpu(req->mps);
> >>> +
> >>> +     BT_DBG("mtu %u mps %u", mtu, mps);
> >>> +
> >>> +     if (mtu < L2CAP_ECRED_MIN_MTU) {
> >>> +             result = L2CAP_RECONF_INVALID_MTU;
> >>> +             goto respond;
> >>> +     }
> >>> +
> >>> +     if (mps < L2CAP_ECRED_MIN_MPS) {
> >>> +             result = L2CAP_RECONF_INVALID_MPS;
> >>> +             goto respond;
> >>> +     }
> >>> +
> >>> +     result = L2CAP_RECONF_SUCCESS;
> >>> +
> >>> +     for (i = 0, cmd_len -= sizeof(*req); cmd_len < sizeof(u16);
> >>> +          i++, cmd_len -= sizeof(u16)) {
> >>
> >> Same comment as above.
> >>
> >>> +             u16 scid;
> >>> +
> >>> +             scid = __le16_to_cpu(req->scid[i]);
> >>> +             if (!scid)
> >>> +                     return -EPROTO;
> >>> +
> >>> +             chan = __l2cap_get_chan_by_dcid(conn, scid);
> >>> +             if (!chan)
> >>> +                     continue;
> >>> +
> >>> +             /* If the MTU value is decreased for any of the included
> >>> +              * channels, then the receiver shall disconnect all
> >>> +              * included channels.
> >>> +              */
> >>> +             if (chan->omtu > mtu) {
> >>> +                     BT_ERR("chan %p decreased MTU %u -> %u", chan,
> >>> +                            chan->omtu, mtu);
> >>> +                     result = L2CAP_RECONF_INVALID_MTU;
> >>> +             }
> >>> +
> >>> +             chan->omtu = mtu;
> >>> +             chan->remote_mps = mps;
> >>> +     }
> >>> +
> >>> +respond:
> >>> +     rsp.result = cpu_to_le16(result);
> >>> +
> >>> +     l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_RECONF_RSP, sizeof(rsp),
> >>> +                    &rsp);
> >>> +
> >>> +     return 0;
> >>> +}
> >>> +
> >>> +static inline int l2cap_ecred_reconf_rsp(struct l2cap_conn *conn,
> >>> +                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >>> +                                      u8 *data)
> >>> +{
> >>> +     struct l2cap_chan *chan;
> >>> +     struct l2cap_ecred_conn_rsp *rsp = (void *) data;
> >>> +     u16 result;
> >>> +
> >>> +     if (cmd_len < sizeof(*rsp))
> >>> +             return -EPROTO;
> >>> +
> >>> +     result = __le16_to_cpu(rsp->result);
> >>> +
> >>> +     BT_DBG("result 0x%4.4x", rsp->result);
> >>> +
> >>> +     if (!result)
> >>> +             return 0;
> >>> +
> >>> +     list_for_each_entry(chan, &conn->chan_l, list) {
> >>> +             if (chan->ident != cmd->ident)
> >>> +                     continue;
> >>> +
> >>> +             l2cap_chan_del(chan, ECONNRESET);
> >>> +     }
> >>> +
> >>> +     return 0;
> >>> +}
> >>> +
> >>> static inline int l2cap_le_command_rej(struct l2cap_conn *conn,
> >>>                                     struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >>>                                     u8 *data)
> >>> @@ -5769,6 +6199,22 @@ static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn,
> >>>              err = l2cap_le_credits(conn, cmd, cmd_len, data);
> >>>              break;
> >>>
> >>> +     case L2CAP_ECRED_CONN_REQ:
> >>> +             err = l2cap_ecred_conn_req(conn, cmd, cmd_len, data);
> >>> +             break;
> >>> +
> >>> +     case L2CAP_ECRED_CONN_RSP:
> >>> +             err = l2cap_ecred_conn_rsp(conn, cmd, cmd_len, data);
> >>> +             break;
> >>> +
> >>> +     case L2CAP_ECRED_RECONF_REQ:
> >>> +             err = l2cap_ecred_reconf_req(conn, cmd, cmd_len, data);
> >>> +             break;
> >>> +
> >>> +     case L2CAP_ECRED_RECONF_RSP:
> >>> +             err = l2cap_ecred_reconf_rsp(conn, cmd, cmd_len, data);
> >>> +             break;
> >>> +
> >>>      case L2CAP_DISCONN_REQ:
> >>>              err = l2cap_disconnect_req(conn, cmd, cmd_len, data);
> >>>              break;
> >>> @@ -6814,11 +7260,13 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
> >>>      struct l2cap_le_credits pkt;
> >>>      u16 return_credits;
> >>>
> >>> -     return_credits = ((chan->imtu / chan->mps) + 1) - chan->rx_credits;
> >>> +     return_credits = (chan->imtu / chan->mps) + 1;
> >>>
> >>> -     if (!return_credits)
> >>> +     if (chan->rx_credits >= return_credits)
> >>>              return;
> >>>
> >>> +     return_credits -= chan->rx_credits;
> >>> +
> >>>      BT_DBG("chan %p returning %u credits to sender", chan, return_credits);
> >>>
> >>>      chan->rx_credits += return_credits;
> >>> @@ -6831,7 +7279,7 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
> >>>      l2cap_send_cmd(conn, chan->ident, L2CAP_LE_CREDITS, sizeof(pkt), &pkt);
> >>> }
> >>>
> >>> -static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>> +static int l2cap_ecred_recv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>> {
> >>>      int err;
> >>>
> >>> @@ -6846,7 +7294,7 @@ static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>      return err;
> >>> }
> >>>
> >>> -static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>> +static int l2cap_ecred_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>> {
> >>>      int err;
> >>>
> >>> @@ -6894,7 +7342,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>              }
> >>>
> >>>              if (skb->len == sdu_len)
> >>> -                     return l2cap_le_recv(chan, skb);
> >>> +                     return l2cap_ecred_recv(chan, skb);
> >>>
> >>>              chan->sdu = skb;
> >>>              chan->sdu_len = sdu_len;
> >>> @@ -6926,7 +7374,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>      skb = NULL;
> >>>
> >>>      if (chan->sdu->len == chan->sdu_len) {
> >>> -             err = l2cap_le_recv(chan, chan->sdu);
> >>> +             err = l2cap_ecred_recv(chan, chan->sdu);
> >>>              if (!err) {
> >>>                      chan->sdu = NULL;
> >>>                      chan->sdu_last_frag = NULL;
> >>> @@ -6987,7 +7435,8 @@ static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid,
> >>>
> >>>      switch (chan->mode) {
> >>>      case L2CAP_MODE_LE_FLOWCTL:
> >>> -             if (l2cap_le_data_rcv(chan, skb) < 0)
> >>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>> +             if (l2cap_ecred_data_rcv(chan, skb) < 0)
> >>>                      goto drop;
> >>>
> >>>              goto done;
> >>> @@ -7214,8 +7663,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> >>>      struct hci_dev *hdev;
> >>>      int err;
> >>>
> >>> -     BT_DBG("%pMR -> %pMR (type %u) psm 0x%2.2x", &chan->src, dst,
> >>> -            dst_type, __le16_to_cpu(psm));
> >>> +     BT_DBG("%pMR -> %pMR (type %u) psm 0x%4.4x mode 0x%2.2x", &chan->src,
> >>> +            dst, dst_type, __le16_to_cpu(psm), chan->mode);
> >>>
> >>>      hdev = hci_get_route(dst, &chan->src, chan->src_type);
> >>>      if (!hdev)
> >>> @@ -7244,6 +7693,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> >>>              break;
> >>>      case L2CAP_MODE_LE_FLOWCTL:
> >>>              break;
> >>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>> +             break;
> >>
> >> No need to do another break. Just put it under LE_FLOWCTL.
> >>
> >>>      case L2CAP_MODE_ERTM:
> >>>      case L2CAP_MODE_STREAMING:
> >>>              if (!disable_ertm)
> >>> @@ -7368,6 +7819,38 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> >>> }
> >>> EXPORT_SYMBOL_GPL(l2cap_chan_connect);
> >>>
> >>> +static void l2cap_ecred_reconfigure(struct l2cap_chan *chan)
> >>> +{
> >>> +     struct l2cap_conn *conn = chan->conn;
> >>> +     struct {
> >>> +             struct l2cap_ecred_reconf_req req;
> >>> +             __le16 scid;
> >>> +     } pdu;
> >>> +
> >>> +     pdu.req.mtu = cpu_to_le16(chan->imtu);
> >>> +     pdu.req.mps = cpu_to_le16(chan->mps);
> >>> +     pdu.scid    = cpu_to_le16(chan->scid);
> >>> +
> >>> +     chan->ident = l2cap_get_ident(conn);
> >>> +
> >>> +     l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_RECONF_REQ,
> >>> +                    sizeof(pdu), &pdu);
> >>> +}
> >>> +
> >>> +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu)
> >>> +{
> >>> +     if (chan->imtu > mtu)
> >>> +             return -EINVAL;
> >>> +
> >>> +     BT_DBG("chan %p mtu 0x%4.4x", chan, mtu);
> >>> +
> >>> +     chan->imtu = mtu;
> >>> +
> >>> +     l2cap_ecred_reconfigure(chan);
> >>> +
> >>> +     return 0;
> >>> +}
> >>> +
> >>> /* ---- L2CAP interface with lower layer (HCI) ---- */
> >>>
> >>> int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr)
> >>> @@ -7579,7 +8062,8 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
> >>>                      else
> >>>                              __set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
> >>>              } else if (chan->state == BT_CONNECT2 &&
> >>> -                        chan->mode != L2CAP_MODE_LE_FLOWCTL) {
> >>> +                        !(chan->mode == L2CAP_MODE_EXT_FLOWCTL ||
> >>> +                         chan->mode == L2CAP_MODE_LE_FLOWCTL)) {
> >>
> >> Please double check that this line is correctly aligned after the (.
> >>
> >>>                      struct l2cap_conn_rsp rsp;
> >>>                      __u16 res, stat;
> >>>
> >>> diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
> >>> index a7be8b59b3c2..12c557f7f40f 100644
> >>> --- a/net/bluetooth/l2cap_sock.c
> >>> +++ b/net/bluetooth/l2cap_sock.c
> >>> @@ -232,7 +232,7 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr,
> >>>                      return -EINVAL;
> >>>      }
> >>>
> >>> -     if (chan->psm && bdaddr_type_is_le(chan->src_type))
> >>> +     if (chan->psm && bdaddr_type_is_le(chan->src_type) && !chan->mode)
> >>>              chan->mode = L2CAP_MODE_LE_FLOWCTL;
> >>>
> >>>      err = l2cap_chan_connect(chan, la.l2_psm, __le16_to_cpu(la.l2_cid),
> >>> @@ -273,6 +273,7 @@ static int l2cap_sock_listen(struct socket *sock, int backlog)
> >>>      switch (chan->mode) {
> >>>      case L2CAP_MODE_BASIC:
> >>>      case L2CAP_MODE_LE_FLOWCTL:
> >>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>              break;
> >>>      case L2CAP_MODE_ERTM:
> >>>      case L2CAP_MODE_STREAMING:
> >>> @@ -408,16 +409,6 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
> >>>
> >>>      switch (optname) {
> >>>      case L2CAP_OPTIONS:
> >>> -             /* LE sockets should use BT_SNDMTU/BT_RCVMTU, but since
> >>> -              * legacy ATT code depends on getsockopt for
> >>> -              * L2CAP_OPTIONS we need to let this pass.
> >>> -              */
> >>> -             if (bdaddr_type_is_le(chan->src_type) &&
> >>> -                 chan->scid != L2CAP_CID_ATT) {
> >>> -                     err = -EINVAL;
> >>> -                     break;
> >>> -             }
> >>> -
> >>
> >> Should this be a separate patch. How do we keep this legacy behavior.
> >
> > I kind have forgotten to fix this one, I guess we will need a new
> > option in order to read the mode without using the old L2CAP_OPTIONS
> > then, or do you have anything against introducing yet another option?
>
> so what is the problem here? Is this because L2CAP by itself can not tell if enhanced credit mode is supported by the peer?

Yep, afaik there is no means to detect the support transparently,
otherwise could always prefer to use EXT_FLOWCTL instead of
LE_FLOWCTL, it seems at the point we introduced LE_FLOWCTL option we
had always assumed there would not be any mode one could use when the
address type is LE.

> Do you actually want to create a new mode socket options with defined values by us to allow selected the mode of operation?

I woud assume that is the only safe option here, though what I did was
to enable L2CAP_OPTIONS to work with LE addresses which I assume is
safe as previously it would cause an error no one would be using it.

-- 
Luiz Augusto von Dentz

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] Bluetooth: L2CAP: Add initial code for Enhanced Credit Based Mode
  2020-01-24 19:13         ` Luiz Augusto von Dentz
@ 2020-01-25  7:33           ` Marcel Holtmann
  2020-01-27  6:28             ` Luiz Augusto von Dentz
  0 siblings, 1 reply; 14+ messages in thread
From: Marcel Holtmann @ 2020-01-25  7:33 UTC (permalink / raw)
  To: Luiz Augusto von Dentz; +Cc: linux-bluetooth

Hi Luiz,

>>>>> This adds the initial code for Enhanced Credit Based Mode which
>>>>> introduces a new socket mode called L2CAP_MODE_EXT_FLOWCTL, which for
>>>>> the most part work the same as L2CAP_MODE_LE_FLOWCTL but uses different
>>>>> PDUs to setup the connections and also works over BR/EDR.
>>>>> 
>>>>> Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
>>>>> ---
>>>>> include/net/bluetooth/l2cap.h |   4 +
>>>>> net/bluetooth/l2cap_core.c    | 510 +++++++++++++++++++++++++++++++++-
>>>>> net/bluetooth/l2cap_sock.c    |  39 +--
>>>>> 3 files changed, 521 insertions(+), 32 deletions(-)
>>>>> 
>>>>> diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
>>>>> index b9ea88cfd2d2..11ae4c93354b 100644
>>>>> --- a/include/net/bluetooth/l2cap.h
>>>>> +++ b/include/net/bluetooth/l2cap.h
>>>>> @@ -295,6 +295,8 @@ struct l2cap_conn_rsp {
>>>>> #define L2CAP_CR_LE_ENCRYPTION                0x0008
>>>>> #define L2CAP_CR_LE_INVALID_SCID      0x0009
>>>>> #define L2CAP_CR_LE_SCID_IN_USE               0X000A
>>>>> +#define L2CAP_CR_LE_UNACCEPT_PARAMS  0X000B
>>>>> +#define L2CAP_CR_LE_INVALID_PARAMS   0X000C
>>>>> 
>>>>> /* connect/create channel status */
>>>>> #define L2CAP_CS_NO_INFO      0x0000
>>>>> @@ -964,6 +966,7 @@ void l2cap_cleanup_sockets(void);
>>>>> bool l2cap_is_socket(struct socket *sock);
>>>>> 
>>>>> void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan);
>>>>> +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan);
>>>>> void __l2cap_connect_rsp_defer(struct l2cap_chan *chan);
>>>>> 
>>>>> int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm);
>>>>> @@ -973,6 +976,7 @@ struct l2cap_chan *l2cap_chan_create(void);
>>>>> void l2cap_chan_close(struct l2cap_chan *chan, int reason);
>>>>> int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
>>>>>                    bdaddr_t *dst, u8 dst_type);
>>>>> +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu);
>>>>> int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len);
>>>>> void l2cap_chan_busy(struct l2cap_chan *chan, int busy);
>>>>> int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator);
>>>>> diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
>>>>> index 195459a1e53e..a528526cffac 100644
>>>>> --- a/net/bluetooth/l2cap_core.c
>>>>> +++ b/net/bluetooth/l2cap_core.c
>>>>> @@ -532,6 +532,17 @@ static void l2cap_le_flowctl_init(struct l2cap_chan *chan, u16 tx_credits)
>>>>>     skb_queue_head_init(&chan->tx_q);
>>>>> }
>>>>> 
>>>>> +static void l2cap_ecred_init(struct l2cap_chan *chan, u16 tx_credits)
>>>>> +{
>>>>> +     l2cap_le_flowctl_init(chan, tx_credits);
>>>>> +
>>>>> +     /* L2CAP implementations shall support a minimum MPS of 64 octets */
>>>>> +     if (chan->mps < L2CAP_ECRED_MIN_MPS) {
>>>>> +             chan->mps = L2CAP_ECRED_MIN_MPS;
>>>>> +             chan->rx_credits = (chan->imtu / chan->mps) + 1;
>>>>> +     }
>>>>> +}
>>>>> +
>>>>> void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
>>>>> {
>>>>>     BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn,
>>>>> @@ -638,6 +649,7 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
>>>>>             break;
>>>>> 
>>>>>     case L2CAP_MODE_LE_FLOWCTL:
>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>             skb_queue_purge(&chan->tx_q);
>>>>>             break;
>>>>> 
>>>>> @@ -1260,6 +1272,17 @@ static void l2cap_move_done(struct l2cap_chan *chan)
>>>>>     }
>>>>> }
>>>>> 
>>>>> +static bool l2cap_mode_ready(struct l2cap_chan *chan)
>>>>> +{
>>>>> +     switch (chan->mode) {
>>>>> +     case L2CAP_MODE_LE_FLOWCTL:
>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>> +             return chan->tx_credits ? true : false;
>>>>> +     }
>>>>> +
>>>>> +     return true;
>>>>> +}
>>>>> +
>>>>> static void l2cap_chan_ready(struct l2cap_chan *chan)
>>>>> {
>>>>>     /* The channel may have already been flagged as connected in
>>>>> @@ -1273,7 +1296,7 @@ static void l2cap_chan_ready(struct l2cap_chan *chan)
>>>>>     chan->conf_state = 0;
>>>>>     __clear_chan_timer(chan);
>>>>> 
>>>>> -     if (chan->mode == L2CAP_MODE_LE_FLOWCTL && !chan->tx_credits)
>>>>> +     if (!l2cap_mode_ready(chan))
>>>>>             chan->ops->suspend(chan);
>>>> 
>>>>       switch (chan->mode) {
>>>>       case ..
>>>>       case ..
>>>>               if (!chan->tx_credits)
>>>>                       chan->ops->suspend(chan);
>>>>               break;
>>>>       }
>>>> 
>>>>> 
>>>>>     chan->state = BT_CONNECTED;
>>>>> @@ -1306,6 +1329,31 @@ static void l2cap_le_connect(struct l2cap_chan *chan)
>>>>>                    sizeof(req), &req);
>>>>> }
>>>>> 
>>>>> +static void l2cap_ecred_connect(struct l2cap_chan *chan)
>>>>> +{
>>>>> +     struct l2cap_conn *conn = chan->conn;
>>>>> +     struct {
>>>>> +             struct l2cap_ecred_conn_req req;
>>>>> +             __le16 scid;
>>>>> +     } __packed pdu;
>>>>> +
>>>>> +     if (test_and_set_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags))
>>>>> +             return;
>>>>> +
>>>>> +     l2cap_ecred_init(chan, 0);
>>>>> +
>>>>> +     pdu.req.psm     = chan->psm;
>>>>> +     pdu.req.mtu     = cpu_to_le16(chan->imtu);
>>>>> +     pdu.req.mps     = cpu_to_le16(chan->mps);
>>>>> +     pdu.req.credits = cpu_to_le16(chan->rx_credits);
>>>>> +     pdu.scid        = cpu_to_le16(chan->scid);
>>>>> +
>>>>> +     chan->ident = l2cap_get_ident(conn);
>>>>> +
>>>>> +     l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_CONN_REQ,
>>>>> +                    sizeof(pdu), &pdu);
>>>>> +}
>>>>> +
>>>>> static void l2cap_le_start(struct l2cap_chan *chan)
>>>>> {
>>>>>     struct l2cap_conn *conn = chan->conn;
>>>>> @@ -1318,8 +1366,12 @@ static void l2cap_le_start(struct l2cap_chan *chan)
>>>>>             return;
>>>>>     }
>>>>> 
>>>>> -     if (chan->state == BT_CONNECT)
>>>>> -             l2cap_le_connect(chan);
>>>>> +     if (chan->state == BT_CONNECT) {
>>>>> +             if (chan->mode == L2CAP_MODE_EXT_FLOWCTL)
>>>>> +                     l2cap_ecred_connect(chan);
>>>>> +             else
>>>>> +                     l2cap_le_connect(chan);
>>>>> +     }
>>>>> }
>>>>> 
>>>>> static void l2cap_start_connection(struct l2cap_chan *chan)
>>>>> @@ -2505,6 +2557,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
>>>>> 
>>>>>     switch (chan->mode) {
>>>>>     case L2CAP_MODE_LE_FLOWCTL:
>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>             /* Check outgoing MTU */
>>>>>             if (len > chan->omtu)
>>>>>                     return -EMSGSIZE;
>>>>> @@ -3773,6 +3826,42 @@ void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan)
>>>>>                    &rsp);
>>>>> }
>>>>> 
>>>>> +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
>>>>> +{
>>>>> +     struct l2cap_ecred_conn_rsp rsp;
>>>>> +     struct l2cap_conn *conn = chan->conn;
>>>>> +     u16 ident = chan->ident;
>>>>> +     int i = 0;
>>>>> +
>>>>> +     if (!ident) {
>>>>> +             return;
>>>>> +     }
>>>>> +
>>>> 
>>>> No { } here.
>>>> 
>>>>> +     BT_DBG("chan %p ident %d", chan, ident);
>>>>> +
>>>>> +     rsp.mtu     = cpu_to_le16(chan->imtu);
>>>>> +     rsp.mps     = cpu_to_le16(chan->mps);
>>>>> +     rsp.credits = cpu_to_le16(chan->rx_credits);
>>>>> +     rsp.result  = cpu_to_le16(L2CAP_CR_LE_SUCCESS);
>>>>> +
>>>>> +     mutex_lock(&conn->chan_lock);
>>>>> +
>>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
>>>>> +             if (chan->ident != ident)
>>>>> +                     continue;
>>>>> +
>>>>> +             /* Reset ident so only one response is sent */
>>>>> +             chan->ident = 0;
>>>>> +
>>>>> +             /* Include all channels pending with the same ident */
>>>>> +             rsp.dcid[i++] = cpu_to_le16(chan->scid);
>>>> 
>>>> This doesn’t work. The sizeof(rsp) has no dcid in there. You are overflowing into conn struct here.
>>>> 
>>>>> +     }
>>>>> +
>>>>> +     mutex_unlock(&conn->chan_lock);
>>>>> +
>>>>> +     l2cap_send_cmd(conn, ident, L2CAP_ECRED_CONN_RSP, sizeof(rsp), &rsp);
>>>>> +}
>>>>> +
>>>>> void __l2cap_connect_rsp_defer(struct l2cap_chan *chan)
>>>>> {
>>>>>     struct l2cap_conn_rsp rsp;
>>>>> @@ -5714,6 +5803,347 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn,
>>>>>     return 0;
>>>>> }
>>>>> 
>>>>> +static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
>>>>> +                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>>> +                                    u8 *data)
>>>>> +{
>>>>> +     struct l2cap_ecred_conn_req *req = (void *) data;
>>>>> +     struct {
>>>>> +             struct l2cap_ecred_conn_rsp rsp;
>>>>> +             __le16 dcid[5];
>>>>> +     } __packed pdu;
>>>>> +     struct l2cap_chan *chan, *pchan;
>>>>> +     u16 credits, mtu, mps;
>>>>> +     __le16 psm;
>>>>> +     u8 result, len = 0;
>>>>> +     int i;
>>>>> +     bool defer = false;
>>>>> +
>>>>> +     if (cmd_len < sizeof(*req))
>>>>> +             return -EPROTO;
>>>>> +
>>>>> +     mtu  = __le16_to_cpu(req->mtu);
>>>>> +     mps  = __le16_to_cpu(req->mps);
>>>>> +
>>>>> +     if (mtu < L2CAP_ECRED_MIN_MTU || mps < L2CAP_ECRED_MIN_MPS) {
>>>>> +             result = L2CAP_CR_LE_UNACCEPT_PARAMS;
>>>>> +             goto response;
>>>>> +     }
>>>>> +
>>>>> +     psm  = req->psm;
>>>>> +     credits = 0;
>>>>> +
>>>>> +     BT_DBG("psm 0x%2.2x mtu %u mps %u", __le16_to_cpu(psm), mtu, mps);
>>>>> +
>>>>> +     memset(&pdu, 0, sizeof(pdu));
>>>>> +
>>>>> +     /* Check if we have socket listening on psm */
>>>>> +     pchan = l2cap_global_chan_by_psm(BT_LISTEN, psm, &conn->hcon->src,
>>>>> +                                      &conn->hcon->dst, LE_LINK);
>>>>> +     if (!pchan) {
>>>>> +             result = L2CAP_CR_LE_BAD_PSM;
>>>>> +             goto response;
>>>>> +     }
>>>>> +
>>>>> +     mutex_lock(&conn->chan_lock);
>>>>> +     l2cap_chan_lock(pchan);
>>>>> +
>>>>> +     if (!smp_sufficient_security(conn->hcon, pchan->sec_level,
>>>>> +                                  SMP_ALLOW_STK)) {
>>>>> +             result = L2CAP_CR_LE_AUTHENTICATION;
>>>>> +             goto unlock;
>>>>> +     }
>>>>> +
>>>>> +     result = L2CAP_CR_LE_SUCCESS;
>>>>> +
>>>>> +     for (i = 0, cmd_len -= sizeof(req); cmd_len >= sizeof(u16);
>>>>> +          i++, cmd_len -= sizeof(u16)) {
>>>> 
>>>> Can we simplify the loop statement or switch to while statement?
>>>> 
>>>>> +             u16 scid = __le16_to_cpu(req->scid[i]);
>>>>> +
>>>>> +             BT_DBG("scid[%d] 0x%4.4x", i, scid);
>>>>> +
>>>>> +             pdu.dcid[i] = 0x0000;
>>>>> +             len += sizeof(*pdu.dcid);
>>>>> +
>>>>> +             /* Check for valid dynamic CID range */
>>>>> +             if (scid < L2CAP_CID_DYN_START || scid > L2CAP_CID_LE_DYN_END) {
>>>>> +                     result = L2CAP_CR_LE_INVALID_SCID;
>>>>> +                     continue;
>>>>> +             }
>>>>> +
>>>>> +             /* Check if we already have channel with that dcid */
>>>>> +             if (__l2cap_get_chan_by_dcid(conn, scid)) {
>>>>> +                     result = L2CAP_CR_LE_SCID_IN_USE;
>>>>> +                     continue;
>>>>> +             }
>>>>> +
>>>>> +             chan = pchan->ops->new_connection(pchan);
>>>>> +             if (!chan) {
>>>>> +                     result = L2CAP_CR_LE_NO_MEM;
>>>>> +                     continue;
>>>>> +             }
>>>>> +
>>>>> +             bacpy(&chan->src, &conn->hcon->src);
>>>>> +             bacpy(&chan->dst, &conn->hcon->dst);
>>>>> +             chan->src_type = bdaddr_src_type(conn->hcon);
>>>>> +             chan->dst_type = bdaddr_dst_type(conn->hcon);
>>>>> +             chan->psm  = psm;
>>>>> +             chan->dcid = scid;
>>>>> +             chan->omtu = mtu;
>>>>> +             chan->remote_mps = mps;
>>>>> +
>>>>> +             __l2cap_chan_add(conn, chan);
>>>>> +
>>>>> +             l2cap_ecred_init(chan, __le16_to_cpu(req->credits));
>>>>> +
>>>>> +             /* Init response */
>>>>> +             if (!pdu.rsp.credits) {
>>>>> +                     pdu.rsp.mtu = cpu_to_le16(chan->imtu);
>>>>> +                     pdu.rsp.mps = cpu_to_le16(chan->mps);
>>>>> +                     pdu.rsp.credits = cpu_to_le16(chan->rx_credits);
>>>>> +             }
>>>>> +
>>>>> +             pdu.dcid[i] = cpu_to_le16(chan->scid);
>>>>> +
>>>>> +             __set_chan_timer(chan, chan->ops->get_sndtimeo(chan));
>>>>> +
>>>>> +             chan->ident = cmd->ident;
>>>>> +
>>>>> +             if (test_bit(FLAG_DEFER_SETUP, &chan->flags)) {
>>>>> +                     l2cap_state_change(chan, BT_CONNECT2);
>>>>> +                     defer = true;
>>>>> +                     chan->ops->defer(chan);
>>>>> +             } else {
>>>>> +                     l2cap_chan_ready(chan);
>>>>> +             }
>>>>> +     }
>>>>> +
>>>>> +unlock:
>>>>> +     l2cap_chan_unlock(pchan);
>>>>> +     mutex_unlock(&conn->chan_lock);
>>>>> +     l2cap_chan_put(pchan);
>>>>> +
>>>>> +response:
>>>>> +     pdu.rsp.result = cpu_to_le16(result);
>>>>> +
>>>>> +     if (defer)
>>>>> +             return 0;
>>>>> +
>>>>> +     l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_CONN_RSP,
>>>>> +                    sizeof(pdu.rsp) + len, &pdu);
>>>>> +
>>>>> +     return 0;
>>>>> +}
>>>>> +
>>>>> +static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
>>>>> +                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>>> +                                    u8 *data)
>>>>> +{
>>>>> +     struct l2cap_ecred_conn_rsp *rsp = (void *) data;
>>>>> +     struct hci_conn *hcon = conn->hcon;
>>>>> +     u16 mtu, mps, credits, result;
>>>>> +     struct l2cap_chan *chan;
>>>>> +     int err = 0, sec_level;
>>>>> +     int i = 0;
>>>>> +
>>>>> +     if (cmd_len < sizeof(*rsp))
>>>>> +             return -EPROTO;
>>>>> +
>>>>> +     mtu     = __le16_to_cpu(rsp->mtu);
>>>>> +     mps     = __le16_to_cpu(rsp->mps);
>>>>> +     credits = __le16_to_cpu(rsp->credits);
>>>>> +     result  = __le16_to_cpu(rsp->result);
>>>>> +
>>>>> +     BT_DBG("mtu %u mps %u credits %u result 0x%4.4x", mtu, mps, credits,
>>>>> +            result);
>>>>> +
>>>>> +     mutex_lock(&conn->chan_lock);
>>>>> +
>>>>> +     cmd_len -= sizeof(*rsp);
>>>>> +
>>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
>>>>> +             u16 dcid;
>>>>> +
>>>>> +             if (chan->ident != cmd->ident ||
>>>>> +                 chan->mode != L2CAP_MODE_EXT_FLOWCTL ||
>>>>> +                 chan->state == BT_CONNECTED)
>>>>> +                     continue;
>>>>> +
>>>>> +             l2cap_chan_lock(chan);
>>>>> +
>>>>> +             /* Check that there is a dcid for each pending channel */
>>>>> +             if (cmd_len < sizeof(dcid)) {
>>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
>>>>> +                     l2cap_chan_unlock(chan);
>>>>> +                     continue;
>>>>> +             }
>>>>> +
>>>>> +             dcid = __le16_to_cpu(rsp->dcid[i++]);
>>>>> +             cmd_len -= sizeof(u16);
>>>>> +
>>>>> +             BT_DBG("dcid[%d] 0x%4.4x", i, dcid);
>>>>> +
>>>>> +             /* Check if dcid is already in use */
>>>>> +             if (dcid && __l2cap_get_chan_by_dcid(conn, dcid)) {
>>>>> +                     /* If a device receives a
>>>>> +                      * L2CAP_CREDIT_BASED_CONNECTION_RSP packet with an
>>>>> +                      * already-assigned Destination CID, then both the
>>>>> +                      * original channel and the new channel shall be
>>>>> +                      * immediately discarded and not used.
>>>>> +                      */
>>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
>>>>> +                     l2cap_chan_unlock(chan);
>>>>> +                     chan = __l2cap_get_chan_by_dcid(conn, dcid);
>>>>> +                     l2cap_chan_lock(chan);
>>>>> +                     l2cap_chan_del(chan, ECONNRESET);
>>>>> +                     l2cap_chan_unlock(chan);
>>>>> +                     continue;
>>>>> +             }
>>>>> +
>>>>> +             switch (result) {
>>>>> +             case L2CAP_CR_LE_AUTHENTICATION:
>>>>> +             case L2CAP_CR_LE_ENCRYPTION:
>>>>> +                     /* If we already have MITM protection we can't do
>>>>> +                      * anything.
>>>>> +                      */
>>>>> +                     if (hcon->sec_level > BT_SECURITY_MEDIUM) {
>>>>> +                             l2cap_chan_del(chan, ECONNREFUSED);
>>>>> +                             break;
>>>>> +                     }
>>>>> +
>>>>> +                     sec_level = hcon->sec_level + 1;
>>>>> +                     if (chan->sec_level < sec_level)
>>>>> +                             chan->sec_level = sec_level;
>>>>> +
>>>>> +                     /* We'll need to send a new Connect Request */
>>>>> +                     clear_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags);
>>>>> +
>>>>> +                     smp_conn_security(hcon, chan->sec_level);
>>>>> +                     break;
>>>>> +
>>>>> +             case L2CAP_CR_LE_BAD_PSM:
>>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
>>>>> +                     break;
>>>>> +
>>>>> +             default:
>>>>> +                     /* If dcid was not set it means channels was refused */
>>>>> +                     if (!dcid) {
>>>>> +                             l2cap_chan_del(chan, ECONNREFUSED);
>>>>> +                             break;
>>>>> +                     }
>>>>> +
>>>>> +                     chan->ident = 0;
>>>>> +                     chan->dcid = dcid;
>>>>> +                     chan->omtu = mtu;
>>>>> +                     chan->remote_mps = mps;
>>>>> +                     chan->tx_credits = credits;
>>>>> +                     l2cap_chan_ready(chan);
>>>>> +                     break;
>>>>> +             }
>>>>> +
>>>>> +             l2cap_chan_unlock(chan);
>>>>> +     }
>>>>> +
>>>>> +     mutex_unlock(&conn->chan_lock);
>>>>> +
>>>>> +     return err;
>>>>> +}
>>>>> +
>>>>> +static inline int l2cap_ecred_reconf_req(struct l2cap_conn *conn,
>>>>> +                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>>> +                                      u8 *data)
>>>>> +{
>>>>> +     struct l2cap_ecred_reconf_req *req = (void *) data;
>>>>> +     struct l2cap_ecred_reconf_rsp rsp;
>>>>> +     u16 mtu, mps, result;
>>>>> +     struct l2cap_chan *chan;
>>>>> +     int i;
>>>>> +
>>>>> +     if (cmd_len < sizeof(*req)) {
>>>>> +             result = L2CAP_CR_LE_INVALID_PARAMS;
>>>>> +             goto respond;
>>>>> +     }
>>>>> +
>>>>> +     mtu = __le16_to_cpu(req->mtu);
>>>>> +     mps = __le16_to_cpu(req->mps);
>>>>> +
>>>>> +     BT_DBG("mtu %u mps %u", mtu, mps);
>>>>> +
>>>>> +     if (mtu < L2CAP_ECRED_MIN_MTU) {
>>>>> +             result = L2CAP_RECONF_INVALID_MTU;
>>>>> +             goto respond;
>>>>> +     }
>>>>> +
>>>>> +     if (mps < L2CAP_ECRED_MIN_MPS) {
>>>>> +             result = L2CAP_RECONF_INVALID_MPS;
>>>>> +             goto respond;
>>>>> +     }
>>>>> +
>>>>> +     result = L2CAP_RECONF_SUCCESS;
>>>>> +
>>>>> +     for (i = 0, cmd_len -= sizeof(*req); cmd_len < sizeof(u16);
>>>>> +          i++, cmd_len -= sizeof(u16)) {
>>>> 
>>>> Same comment as above.
>>>> 
>>>>> +             u16 scid;
>>>>> +
>>>>> +             scid = __le16_to_cpu(req->scid[i]);
>>>>> +             if (!scid)
>>>>> +                     return -EPROTO;
>>>>> +
>>>>> +             chan = __l2cap_get_chan_by_dcid(conn, scid);
>>>>> +             if (!chan)
>>>>> +                     continue;
>>>>> +
>>>>> +             /* If the MTU value is decreased for any of the included
>>>>> +              * channels, then the receiver shall disconnect all
>>>>> +              * included channels.
>>>>> +              */
>>>>> +             if (chan->omtu > mtu) {
>>>>> +                     BT_ERR("chan %p decreased MTU %u -> %u", chan,
>>>>> +                            chan->omtu, mtu);
>>>>> +                     result = L2CAP_RECONF_INVALID_MTU;
>>>>> +             }
>>>>> +
>>>>> +             chan->omtu = mtu;
>>>>> +             chan->remote_mps = mps;
>>>>> +     }
>>>>> +
>>>>> +respond:
>>>>> +     rsp.result = cpu_to_le16(result);
>>>>> +
>>>>> +     l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_RECONF_RSP, sizeof(rsp),
>>>>> +                    &rsp);
>>>>> +
>>>>> +     return 0;
>>>>> +}
>>>>> +
>>>>> +static inline int l2cap_ecred_reconf_rsp(struct l2cap_conn *conn,
>>>>> +                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>>> +                                      u8 *data)
>>>>> +{
>>>>> +     struct l2cap_chan *chan;
>>>>> +     struct l2cap_ecred_conn_rsp *rsp = (void *) data;
>>>>> +     u16 result;
>>>>> +
>>>>> +     if (cmd_len < sizeof(*rsp))
>>>>> +             return -EPROTO;
>>>>> +
>>>>> +     result = __le16_to_cpu(rsp->result);
>>>>> +
>>>>> +     BT_DBG("result 0x%4.4x", rsp->result);
>>>>> +
>>>>> +     if (!result)
>>>>> +             return 0;
>>>>> +
>>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
>>>>> +             if (chan->ident != cmd->ident)
>>>>> +                     continue;
>>>>> +
>>>>> +             l2cap_chan_del(chan, ECONNRESET);
>>>>> +     }
>>>>> +
>>>>> +     return 0;
>>>>> +}
>>>>> +
>>>>> static inline int l2cap_le_command_rej(struct l2cap_conn *conn,
>>>>>                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>>>                                    u8 *data)
>>>>> @@ -5769,6 +6199,22 @@ static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn,
>>>>>             err = l2cap_le_credits(conn, cmd, cmd_len, data);
>>>>>             break;
>>>>> 
>>>>> +     case L2CAP_ECRED_CONN_REQ:
>>>>> +             err = l2cap_ecred_conn_req(conn, cmd, cmd_len, data);
>>>>> +             break;
>>>>> +
>>>>> +     case L2CAP_ECRED_CONN_RSP:
>>>>> +             err = l2cap_ecred_conn_rsp(conn, cmd, cmd_len, data);
>>>>> +             break;
>>>>> +
>>>>> +     case L2CAP_ECRED_RECONF_REQ:
>>>>> +             err = l2cap_ecred_reconf_req(conn, cmd, cmd_len, data);
>>>>> +             break;
>>>>> +
>>>>> +     case L2CAP_ECRED_RECONF_RSP:
>>>>> +             err = l2cap_ecred_reconf_rsp(conn, cmd, cmd_len, data);
>>>>> +             break;
>>>>> +
>>>>>     case L2CAP_DISCONN_REQ:
>>>>>             err = l2cap_disconnect_req(conn, cmd, cmd_len, data);
>>>>>             break;
>>>>> @@ -6814,11 +7260,13 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
>>>>>     struct l2cap_le_credits pkt;
>>>>>     u16 return_credits;
>>>>> 
>>>>> -     return_credits = ((chan->imtu / chan->mps) + 1) - chan->rx_credits;
>>>>> +     return_credits = (chan->imtu / chan->mps) + 1;
>>>>> 
>>>>> -     if (!return_credits)
>>>>> +     if (chan->rx_credits >= return_credits)
>>>>>             return;
>>>>> 
>>>>> +     return_credits -= chan->rx_credits;
>>>>> +
>>>>>     BT_DBG("chan %p returning %u credits to sender", chan, return_credits);
>>>>> 
>>>>>     chan->rx_credits += return_credits;
>>>>> @@ -6831,7 +7279,7 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
>>>>>     l2cap_send_cmd(conn, chan->ident, L2CAP_LE_CREDITS, sizeof(pkt), &pkt);
>>>>> }
>>>>> 
>>>>> -static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>> +static int l2cap_ecred_recv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>> {
>>>>>     int err;
>>>>> 
>>>>> @@ -6846,7 +7294,7 @@ static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>     return err;
>>>>> }
>>>>> 
>>>>> -static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>> +static int l2cap_ecred_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>> {
>>>>>     int err;
>>>>> 
>>>>> @@ -6894,7 +7342,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>             }
>>>>> 
>>>>>             if (skb->len == sdu_len)
>>>>> -                     return l2cap_le_recv(chan, skb);
>>>>> +                     return l2cap_ecred_recv(chan, skb);
>>>>> 
>>>>>             chan->sdu = skb;
>>>>>             chan->sdu_len = sdu_len;
>>>>> @@ -6926,7 +7374,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>     skb = NULL;
>>>>> 
>>>>>     if (chan->sdu->len == chan->sdu_len) {
>>>>> -             err = l2cap_le_recv(chan, chan->sdu);
>>>>> +             err = l2cap_ecred_recv(chan, chan->sdu);
>>>>>             if (!err) {
>>>>>                     chan->sdu = NULL;
>>>>>                     chan->sdu_last_frag = NULL;
>>>>> @@ -6987,7 +7435,8 @@ static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid,
>>>>> 
>>>>>     switch (chan->mode) {
>>>>>     case L2CAP_MODE_LE_FLOWCTL:
>>>>> -             if (l2cap_le_data_rcv(chan, skb) < 0)
>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>> +             if (l2cap_ecred_data_rcv(chan, skb) < 0)
>>>>>                     goto drop;
>>>>> 
>>>>>             goto done;
>>>>> @@ -7214,8 +7663,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
>>>>>     struct hci_dev *hdev;
>>>>>     int err;
>>>>> 
>>>>> -     BT_DBG("%pMR -> %pMR (type %u) psm 0x%2.2x", &chan->src, dst,
>>>>> -            dst_type, __le16_to_cpu(psm));
>>>>> +     BT_DBG("%pMR -> %pMR (type %u) psm 0x%4.4x mode 0x%2.2x", &chan->src,
>>>>> +            dst, dst_type, __le16_to_cpu(psm), chan->mode);
>>>>> 
>>>>>     hdev = hci_get_route(dst, &chan->src, chan->src_type);
>>>>>     if (!hdev)
>>>>> @@ -7244,6 +7693,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
>>>>>             break;
>>>>>     case L2CAP_MODE_LE_FLOWCTL:
>>>>>             break;
>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>> +             break;
>>>> 
>>>> No need to do another break. Just put it under LE_FLOWCTL.
>>>> 
>>>>>     case L2CAP_MODE_ERTM:
>>>>>     case L2CAP_MODE_STREAMING:
>>>>>             if (!disable_ertm)
>>>>> @@ -7368,6 +7819,38 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
>>>>> }
>>>>> EXPORT_SYMBOL_GPL(l2cap_chan_connect);
>>>>> 
>>>>> +static void l2cap_ecred_reconfigure(struct l2cap_chan *chan)
>>>>> +{
>>>>> +     struct l2cap_conn *conn = chan->conn;
>>>>> +     struct {
>>>>> +             struct l2cap_ecred_reconf_req req;
>>>>> +             __le16 scid;
>>>>> +     } pdu;
>>>>> +
>>>>> +     pdu.req.mtu = cpu_to_le16(chan->imtu);
>>>>> +     pdu.req.mps = cpu_to_le16(chan->mps);
>>>>> +     pdu.scid    = cpu_to_le16(chan->scid);
>>>>> +
>>>>> +     chan->ident = l2cap_get_ident(conn);
>>>>> +
>>>>> +     l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_RECONF_REQ,
>>>>> +                    sizeof(pdu), &pdu);
>>>>> +}
>>>>> +
>>>>> +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu)
>>>>> +{
>>>>> +     if (chan->imtu > mtu)
>>>>> +             return -EINVAL;
>>>>> +
>>>>> +     BT_DBG("chan %p mtu 0x%4.4x", chan, mtu);
>>>>> +
>>>>> +     chan->imtu = mtu;
>>>>> +
>>>>> +     l2cap_ecred_reconfigure(chan);
>>>>> +
>>>>> +     return 0;
>>>>> +}
>>>>> +
>>>>> /* ---- L2CAP interface with lower layer (HCI) ---- */
>>>>> 
>>>>> int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr)
>>>>> @@ -7579,7 +8062,8 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
>>>>>                     else
>>>>>                             __set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
>>>>>             } else if (chan->state == BT_CONNECT2 &&
>>>>> -                        chan->mode != L2CAP_MODE_LE_FLOWCTL) {
>>>>> +                        !(chan->mode == L2CAP_MODE_EXT_FLOWCTL ||
>>>>> +                         chan->mode == L2CAP_MODE_LE_FLOWCTL)) {
>>>> 
>>>> Please double check that this line is correctly aligned after the (.
>>>> 
>>>>>                     struct l2cap_conn_rsp rsp;
>>>>>                     __u16 res, stat;
>>>>> 
>>>>> diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
>>>>> index a7be8b59b3c2..12c557f7f40f 100644
>>>>> --- a/net/bluetooth/l2cap_sock.c
>>>>> +++ b/net/bluetooth/l2cap_sock.c
>>>>> @@ -232,7 +232,7 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr,
>>>>>                     return -EINVAL;
>>>>>     }
>>>>> 
>>>>> -     if (chan->psm && bdaddr_type_is_le(chan->src_type))
>>>>> +     if (chan->psm && bdaddr_type_is_le(chan->src_type) && !chan->mode)
>>>>>             chan->mode = L2CAP_MODE_LE_FLOWCTL;
>>>>> 
>>>>>     err = l2cap_chan_connect(chan, la.l2_psm, __le16_to_cpu(la.l2_cid),
>>>>> @@ -273,6 +273,7 @@ static int l2cap_sock_listen(struct socket *sock, int backlog)
>>>>>     switch (chan->mode) {
>>>>>     case L2CAP_MODE_BASIC:
>>>>>     case L2CAP_MODE_LE_FLOWCTL:
>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>             break;
>>>>>     case L2CAP_MODE_ERTM:
>>>>>     case L2CAP_MODE_STREAMING:
>>>>> @@ -408,16 +409,6 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
>>>>> 
>>>>>     switch (optname) {
>>>>>     case L2CAP_OPTIONS:
>>>>> -             /* LE sockets should use BT_SNDMTU/BT_RCVMTU, but since
>>>>> -              * legacy ATT code depends on getsockopt for
>>>>> -              * L2CAP_OPTIONS we need to let this pass.
>>>>> -              */
>>>>> -             if (bdaddr_type_is_le(chan->src_type) &&
>>>>> -                 chan->scid != L2CAP_CID_ATT) {
>>>>> -                     err = -EINVAL;
>>>>> -                     break;
>>>>> -             }
>>>>> -
>>>> 
>>>> Should this be a separate patch. How do we keep this legacy behavior.
>>> 
>>> I kind have forgotten to fix this one, I guess we will need a new
>>> option in order to read the mode without using the old L2CAP_OPTIONS
>>> then, or do you have anything against introducing yet another option?
>> 
>> so what is the problem here? Is this because L2CAP by itself can not tell if enhanced credit mode is supported by the peer?
> 
> Yep, afaik there is no means to detect the support transparently,
> otherwise could always prefer to use EXT_FLOWCTL instead of
> LE_FLOWCTL, it seems at the point we introduced LE_FLOWCTL option we
> had always assumed there would not be any mode one could use when the
> address type is LE.
> 
>> Do you actually want to create a new mode socket options with defined values by us to allow selected the mode of operation?
> 
> I woud assume that is the only safe option here, though what I did was
> to enable L2CAP_OPTIONS to work with LE addresses which I assume is
> safe as previously it would cause an error no one would be using it.

that is what I was afraid of. So can we use BT_SNDMTU and BT_RCVMTU and then only a mode property is missing? Or do we have other options that we want to configure.

For the multi-connect case, I was thinking that we use something similar to DEFER_SETUP. If we set a DEFER_SETUP on an outgoing socket, then you can call connect() on a socket and succeeds. However no POLLIN | POLLOUT would be signaled. When calling a connect() without that option set, then all sockets become ready at the same time on success.

Local CID assignment however must be done by the time connect() succeeds to allow identification. That order / indentation of L2CAP channels is another fun question. What happens if we get 4 GATT sockets, who decides what parts run where?

Regards

Marcel


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] Bluetooth: L2CAP: Add initial code for Enhanced Credit Based Mode
  2020-01-25  7:33           ` Marcel Holtmann
@ 2020-01-27  6:28             ` Luiz Augusto von Dentz
  2020-01-27 12:10               ` Marcel Holtmann
  0 siblings, 1 reply; 14+ messages in thread
From: Luiz Augusto von Dentz @ 2020-01-27  6:28 UTC (permalink / raw)
  To: Marcel Holtmann; +Cc: linux-bluetooth

Hi Marcel

On Fri, Jan 24, 2020 at 11:33 PM Marcel Holtmann <marcel@holtmann.org> wrote:
>
> Hi Luiz,
>
> >>>>> This adds the initial code for Enhanced Credit Based Mode which
> >>>>> introduces a new socket mode called L2CAP_MODE_EXT_FLOWCTL, which for
> >>>>> the most part work the same as L2CAP_MODE_LE_FLOWCTL but uses different
> >>>>> PDUs to setup the connections and also works over BR/EDR.
> >>>>>
> >>>>> Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
> >>>>> ---
> >>>>> include/net/bluetooth/l2cap.h |   4 +
> >>>>> net/bluetooth/l2cap_core.c    | 510 +++++++++++++++++++++++++++++++++-
> >>>>> net/bluetooth/l2cap_sock.c    |  39 +--
> >>>>> 3 files changed, 521 insertions(+), 32 deletions(-)
> >>>>>
> >>>>> diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
> >>>>> index b9ea88cfd2d2..11ae4c93354b 100644
> >>>>> --- a/include/net/bluetooth/l2cap.h
> >>>>> +++ b/include/net/bluetooth/l2cap.h
> >>>>> @@ -295,6 +295,8 @@ struct l2cap_conn_rsp {
> >>>>> #define L2CAP_CR_LE_ENCRYPTION                0x0008
> >>>>> #define L2CAP_CR_LE_INVALID_SCID      0x0009
> >>>>> #define L2CAP_CR_LE_SCID_IN_USE               0X000A
> >>>>> +#define L2CAP_CR_LE_UNACCEPT_PARAMS  0X000B
> >>>>> +#define L2CAP_CR_LE_INVALID_PARAMS   0X000C
> >>>>>
> >>>>> /* connect/create channel status */
> >>>>> #define L2CAP_CS_NO_INFO      0x0000
> >>>>> @@ -964,6 +966,7 @@ void l2cap_cleanup_sockets(void);
> >>>>> bool l2cap_is_socket(struct socket *sock);
> >>>>>
> >>>>> void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan);
> >>>>> +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan);
> >>>>> void __l2cap_connect_rsp_defer(struct l2cap_chan *chan);
> >>>>>
> >>>>> int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm);
> >>>>> @@ -973,6 +976,7 @@ struct l2cap_chan *l2cap_chan_create(void);
> >>>>> void l2cap_chan_close(struct l2cap_chan *chan, int reason);
> >>>>> int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> >>>>>                    bdaddr_t *dst, u8 dst_type);
> >>>>> +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu);
> >>>>> int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len);
> >>>>> void l2cap_chan_busy(struct l2cap_chan *chan, int busy);
> >>>>> int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator);
> >>>>> diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
> >>>>> index 195459a1e53e..a528526cffac 100644
> >>>>> --- a/net/bluetooth/l2cap_core.c
> >>>>> +++ b/net/bluetooth/l2cap_core.c
> >>>>> @@ -532,6 +532,17 @@ static void l2cap_le_flowctl_init(struct l2cap_chan *chan, u16 tx_credits)
> >>>>>     skb_queue_head_init(&chan->tx_q);
> >>>>> }
> >>>>>
> >>>>> +static void l2cap_ecred_init(struct l2cap_chan *chan, u16 tx_credits)
> >>>>> +{
> >>>>> +     l2cap_le_flowctl_init(chan, tx_credits);
> >>>>> +
> >>>>> +     /* L2CAP implementations shall support a minimum MPS of 64 octets */
> >>>>> +     if (chan->mps < L2CAP_ECRED_MIN_MPS) {
> >>>>> +             chan->mps = L2CAP_ECRED_MIN_MPS;
> >>>>> +             chan->rx_credits = (chan->imtu / chan->mps) + 1;
> >>>>> +     }
> >>>>> +}
> >>>>> +
> >>>>> void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
> >>>>> {
> >>>>>     BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn,
> >>>>> @@ -638,6 +649,7 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
> >>>>>             break;
> >>>>>
> >>>>>     case L2CAP_MODE_LE_FLOWCTL:
> >>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>>>             skb_queue_purge(&chan->tx_q);
> >>>>>             break;
> >>>>>
> >>>>> @@ -1260,6 +1272,17 @@ static void l2cap_move_done(struct l2cap_chan *chan)
> >>>>>     }
> >>>>> }
> >>>>>
> >>>>> +static bool l2cap_mode_ready(struct l2cap_chan *chan)
> >>>>> +{
> >>>>> +     switch (chan->mode) {
> >>>>> +     case L2CAP_MODE_LE_FLOWCTL:
> >>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>>> +             return chan->tx_credits ? true : false;
> >>>>> +     }
> >>>>> +
> >>>>> +     return true;
> >>>>> +}
> >>>>> +
> >>>>> static void l2cap_chan_ready(struct l2cap_chan *chan)
> >>>>> {
> >>>>>     /* The channel may have already been flagged as connected in
> >>>>> @@ -1273,7 +1296,7 @@ static void l2cap_chan_ready(struct l2cap_chan *chan)
> >>>>>     chan->conf_state = 0;
> >>>>>     __clear_chan_timer(chan);
> >>>>>
> >>>>> -     if (chan->mode == L2CAP_MODE_LE_FLOWCTL && !chan->tx_credits)
> >>>>> +     if (!l2cap_mode_ready(chan))
> >>>>>             chan->ops->suspend(chan);
> >>>>
> >>>>       switch (chan->mode) {
> >>>>       case ..
> >>>>       case ..
> >>>>               if (!chan->tx_credits)
> >>>>                       chan->ops->suspend(chan);
> >>>>               break;
> >>>>       }
> >>>>
> >>>>>
> >>>>>     chan->state = BT_CONNECTED;
> >>>>> @@ -1306,6 +1329,31 @@ static void l2cap_le_connect(struct l2cap_chan *chan)
> >>>>>                    sizeof(req), &req);
> >>>>> }
> >>>>>
> >>>>> +static void l2cap_ecred_connect(struct l2cap_chan *chan)
> >>>>> +{
> >>>>> +     struct l2cap_conn *conn = chan->conn;
> >>>>> +     struct {
> >>>>> +             struct l2cap_ecred_conn_req req;
> >>>>> +             __le16 scid;
> >>>>> +     } __packed pdu;
> >>>>> +
> >>>>> +     if (test_and_set_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags))
> >>>>> +             return;
> >>>>> +
> >>>>> +     l2cap_ecred_init(chan, 0);
> >>>>> +
> >>>>> +     pdu.req.psm     = chan->psm;
> >>>>> +     pdu.req.mtu     = cpu_to_le16(chan->imtu);
> >>>>> +     pdu.req.mps     = cpu_to_le16(chan->mps);
> >>>>> +     pdu.req.credits = cpu_to_le16(chan->rx_credits);
> >>>>> +     pdu.scid        = cpu_to_le16(chan->scid);
> >>>>> +
> >>>>> +     chan->ident = l2cap_get_ident(conn);
> >>>>> +
> >>>>> +     l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_CONN_REQ,
> >>>>> +                    sizeof(pdu), &pdu);
> >>>>> +}
> >>>>> +
> >>>>> static void l2cap_le_start(struct l2cap_chan *chan)
> >>>>> {
> >>>>>     struct l2cap_conn *conn = chan->conn;
> >>>>> @@ -1318,8 +1366,12 @@ static void l2cap_le_start(struct l2cap_chan *chan)
> >>>>>             return;
> >>>>>     }
> >>>>>
> >>>>> -     if (chan->state == BT_CONNECT)
> >>>>> -             l2cap_le_connect(chan);
> >>>>> +     if (chan->state == BT_CONNECT) {
> >>>>> +             if (chan->mode == L2CAP_MODE_EXT_FLOWCTL)
> >>>>> +                     l2cap_ecred_connect(chan);
> >>>>> +             else
> >>>>> +                     l2cap_le_connect(chan);
> >>>>> +     }
> >>>>> }
> >>>>>
> >>>>> static void l2cap_start_connection(struct l2cap_chan *chan)
> >>>>> @@ -2505,6 +2557,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
> >>>>>
> >>>>>     switch (chan->mode) {
> >>>>>     case L2CAP_MODE_LE_FLOWCTL:
> >>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>>>             /* Check outgoing MTU */
> >>>>>             if (len > chan->omtu)
> >>>>>                     return -EMSGSIZE;
> >>>>> @@ -3773,6 +3826,42 @@ void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan)
> >>>>>                    &rsp);
> >>>>> }
> >>>>>
> >>>>> +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
> >>>>> +{
> >>>>> +     struct l2cap_ecred_conn_rsp rsp;
> >>>>> +     struct l2cap_conn *conn = chan->conn;
> >>>>> +     u16 ident = chan->ident;
> >>>>> +     int i = 0;
> >>>>> +
> >>>>> +     if (!ident) {
> >>>>> +             return;
> >>>>> +     }
> >>>>> +
> >>>>
> >>>> No { } here.
> >>>>
> >>>>> +     BT_DBG("chan %p ident %d", chan, ident);
> >>>>> +
> >>>>> +     rsp.mtu     = cpu_to_le16(chan->imtu);
> >>>>> +     rsp.mps     = cpu_to_le16(chan->mps);
> >>>>> +     rsp.credits = cpu_to_le16(chan->rx_credits);
> >>>>> +     rsp.result  = cpu_to_le16(L2CAP_CR_LE_SUCCESS);
> >>>>> +
> >>>>> +     mutex_lock(&conn->chan_lock);
> >>>>> +
> >>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
> >>>>> +             if (chan->ident != ident)
> >>>>> +                     continue;
> >>>>> +
> >>>>> +             /* Reset ident so only one response is sent */
> >>>>> +             chan->ident = 0;
> >>>>> +
> >>>>> +             /* Include all channels pending with the same ident */
> >>>>> +             rsp.dcid[i++] = cpu_to_le16(chan->scid);
> >>>>
> >>>> This doesn’t work. The sizeof(rsp) has no dcid in there. You are overflowing into conn struct here.
> >>>>
> >>>>> +     }
> >>>>> +
> >>>>> +     mutex_unlock(&conn->chan_lock);
> >>>>> +
> >>>>> +     l2cap_send_cmd(conn, ident, L2CAP_ECRED_CONN_RSP, sizeof(rsp), &rsp);
> >>>>> +}
> >>>>> +
> >>>>> void __l2cap_connect_rsp_defer(struct l2cap_chan *chan)
> >>>>> {
> >>>>>     struct l2cap_conn_rsp rsp;
> >>>>> @@ -5714,6 +5803,347 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn,
> >>>>>     return 0;
> >>>>> }
> >>>>>
> >>>>> +static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
> >>>>> +                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >>>>> +                                    u8 *data)
> >>>>> +{
> >>>>> +     struct l2cap_ecred_conn_req *req = (void *) data;
> >>>>> +     struct {
> >>>>> +             struct l2cap_ecred_conn_rsp rsp;
> >>>>> +             __le16 dcid[5];
> >>>>> +     } __packed pdu;
> >>>>> +     struct l2cap_chan *chan, *pchan;
> >>>>> +     u16 credits, mtu, mps;
> >>>>> +     __le16 psm;
> >>>>> +     u8 result, len = 0;
> >>>>> +     int i;
> >>>>> +     bool defer = false;
> >>>>> +
> >>>>> +     if (cmd_len < sizeof(*req))
> >>>>> +             return -EPROTO;
> >>>>> +
> >>>>> +     mtu  = __le16_to_cpu(req->mtu);
> >>>>> +     mps  = __le16_to_cpu(req->mps);
> >>>>> +
> >>>>> +     if (mtu < L2CAP_ECRED_MIN_MTU || mps < L2CAP_ECRED_MIN_MPS) {
> >>>>> +             result = L2CAP_CR_LE_UNACCEPT_PARAMS;
> >>>>> +             goto response;
> >>>>> +     }
> >>>>> +
> >>>>> +     psm  = req->psm;
> >>>>> +     credits = 0;
> >>>>> +
> >>>>> +     BT_DBG("psm 0x%2.2x mtu %u mps %u", __le16_to_cpu(psm), mtu, mps);
> >>>>> +
> >>>>> +     memset(&pdu, 0, sizeof(pdu));
> >>>>> +
> >>>>> +     /* Check if we have socket listening on psm */
> >>>>> +     pchan = l2cap_global_chan_by_psm(BT_LISTEN, psm, &conn->hcon->src,
> >>>>> +                                      &conn->hcon->dst, LE_LINK);
> >>>>> +     if (!pchan) {
> >>>>> +             result = L2CAP_CR_LE_BAD_PSM;
> >>>>> +             goto response;
> >>>>> +     }
> >>>>> +
> >>>>> +     mutex_lock(&conn->chan_lock);
> >>>>> +     l2cap_chan_lock(pchan);
> >>>>> +
> >>>>> +     if (!smp_sufficient_security(conn->hcon, pchan->sec_level,
> >>>>> +                                  SMP_ALLOW_STK)) {
> >>>>> +             result = L2CAP_CR_LE_AUTHENTICATION;
> >>>>> +             goto unlock;
> >>>>> +     }
> >>>>> +
> >>>>> +     result = L2CAP_CR_LE_SUCCESS;
> >>>>> +
> >>>>> +     for (i = 0, cmd_len -= sizeof(req); cmd_len >= sizeof(u16);
> >>>>> +          i++, cmd_len -= sizeof(u16)) {
> >>>>
> >>>> Can we simplify the loop statement or switch to while statement?
> >>>>
> >>>>> +             u16 scid = __le16_to_cpu(req->scid[i]);
> >>>>> +
> >>>>> +             BT_DBG("scid[%d] 0x%4.4x", i, scid);
> >>>>> +
> >>>>> +             pdu.dcid[i] = 0x0000;
> >>>>> +             len += sizeof(*pdu.dcid);
> >>>>> +
> >>>>> +             /* Check for valid dynamic CID range */
> >>>>> +             if (scid < L2CAP_CID_DYN_START || scid > L2CAP_CID_LE_DYN_END) {
> >>>>> +                     result = L2CAP_CR_LE_INVALID_SCID;
> >>>>> +                     continue;
> >>>>> +             }
> >>>>> +
> >>>>> +             /* Check if we already have channel with that dcid */
> >>>>> +             if (__l2cap_get_chan_by_dcid(conn, scid)) {
> >>>>> +                     result = L2CAP_CR_LE_SCID_IN_USE;
> >>>>> +                     continue;
> >>>>> +             }
> >>>>> +
> >>>>> +             chan = pchan->ops->new_connection(pchan);
> >>>>> +             if (!chan) {
> >>>>> +                     result = L2CAP_CR_LE_NO_MEM;
> >>>>> +                     continue;
> >>>>> +             }
> >>>>> +
> >>>>> +             bacpy(&chan->src, &conn->hcon->src);
> >>>>> +             bacpy(&chan->dst, &conn->hcon->dst);
> >>>>> +             chan->src_type = bdaddr_src_type(conn->hcon);
> >>>>> +             chan->dst_type = bdaddr_dst_type(conn->hcon);
> >>>>> +             chan->psm  = psm;
> >>>>> +             chan->dcid = scid;
> >>>>> +             chan->omtu = mtu;
> >>>>> +             chan->remote_mps = mps;
> >>>>> +
> >>>>> +             __l2cap_chan_add(conn, chan);
> >>>>> +
> >>>>> +             l2cap_ecred_init(chan, __le16_to_cpu(req->credits));
> >>>>> +
> >>>>> +             /* Init response */
> >>>>> +             if (!pdu.rsp.credits) {
> >>>>> +                     pdu.rsp.mtu = cpu_to_le16(chan->imtu);
> >>>>> +                     pdu.rsp.mps = cpu_to_le16(chan->mps);
> >>>>> +                     pdu.rsp.credits = cpu_to_le16(chan->rx_credits);
> >>>>> +             }
> >>>>> +
> >>>>> +             pdu.dcid[i] = cpu_to_le16(chan->scid);
> >>>>> +
> >>>>> +             __set_chan_timer(chan, chan->ops->get_sndtimeo(chan));
> >>>>> +
> >>>>> +             chan->ident = cmd->ident;
> >>>>> +
> >>>>> +             if (test_bit(FLAG_DEFER_SETUP, &chan->flags)) {
> >>>>> +                     l2cap_state_change(chan, BT_CONNECT2);
> >>>>> +                     defer = true;
> >>>>> +                     chan->ops->defer(chan);
> >>>>> +             } else {
> >>>>> +                     l2cap_chan_ready(chan);
> >>>>> +             }
> >>>>> +     }
> >>>>> +
> >>>>> +unlock:
> >>>>> +     l2cap_chan_unlock(pchan);
> >>>>> +     mutex_unlock(&conn->chan_lock);
> >>>>> +     l2cap_chan_put(pchan);
> >>>>> +
> >>>>> +response:
> >>>>> +     pdu.rsp.result = cpu_to_le16(result);
> >>>>> +
> >>>>> +     if (defer)
> >>>>> +             return 0;
> >>>>> +
> >>>>> +     l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_CONN_RSP,
> >>>>> +                    sizeof(pdu.rsp) + len, &pdu);
> >>>>> +
> >>>>> +     return 0;
> >>>>> +}
> >>>>> +
> >>>>> +static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
> >>>>> +                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >>>>> +                                    u8 *data)
> >>>>> +{
> >>>>> +     struct l2cap_ecred_conn_rsp *rsp = (void *) data;
> >>>>> +     struct hci_conn *hcon = conn->hcon;
> >>>>> +     u16 mtu, mps, credits, result;
> >>>>> +     struct l2cap_chan *chan;
> >>>>> +     int err = 0, sec_level;
> >>>>> +     int i = 0;
> >>>>> +
> >>>>> +     if (cmd_len < sizeof(*rsp))
> >>>>> +             return -EPROTO;
> >>>>> +
> >>>>> +     mtu     = __le16_to_cpu(rsp->mtu);
> >>>>> +     mps     = __le16_to_cpu(rsp->mps);
> >>>>> +     credits = __le16_to_cpu(rsp->credits);
> >>>>> +     result  = __le16_to_cpu(rsp->result);
> >>>>> +
> >>>>> +     BT_DBG("mtu %u mps %u credits %u result 0x%4.4x", mtu, mps, credits,
> >>>>> +            result);
> >>>>> +
> >>>>> +     mutex_lock(&conn->chan_lock);
> >>>>> +
> >>>>> +     cmd_len -= sizeof(*rsp);
> >>>>> +
> >>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
> >>>>> +             u16 dcid;
> >>>>> +
> >>>>> +             if (chan->ident != cmd->ident ||
> >>>>> +                 chan->mode != L2CAP_MODE_EXT_FLOWCTL ||
> >>>>> +                 chan->state == BT_CONNECTED)
> >>>>> +                     continue;
> >>>>> +
> >>>>> +             l2cap_chan_lock(chan);
> >>>>> +
> >>>>> +             /* Check that there is a dcid for each pending channel */
> >>>>> +             if (cmd_len < sizeof(dcid)) {
> >>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
> >>>>> +                     l2cap_chan_unlock(chan);
> >>>>> +                     continue;
> >>>>> +             }
> >>>>> +
> >>>>> +             dcid = __le16_to_cpu(rsp->dcid[i++]);
> >>>>> +             cmd_len -= sizeof(u16);
> >>>>> +
> >>>>> +             BT_DBG("dcid[%d] 0x%4.4x", i, dcid);
> >>>>> +
> >>>>> +             /* Check if dcid is already in use */
> >>>>> +             if (dcid && __l2cap_get_chan_by_dcid(conn, dcid)) {
> >>>>> +                     /* If a device receives a
> >>>>> +                      * L2CAP_CREDIT_BASED_CONNECTION_RSP packet with an
> >>>>> +                      * already-assigned Destination CID, then both the
> >>>>> +                      * original channel and the new channel shall be
> >>>>> +                      * immediately discarded and not used.
> >>>>> +                      */
> >>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
> >>>>> +                     l2cap_chan_unlock(chan);
> >>>>> +                     chan = __l2cap_get_chan_by_dcid(conn, dcid);
> >>>>> +                     l2cap_chan_lock(chan);
> >>>>> +                     l2cap_chan_del(chan, ECONNRESET);
> >>>>> +                     l2cap_chan_unlock(chan);
> >>>>> +                     continue;
> >>>>> +             }
> >>>>> +
> >>>>> +             switch (result) {
> >>>>> +             case L2CAP_CR_LE_AUTHENTICATION:
> >>>>> +             case L2CAP_CR_LE_ENCRYPTION:
> >>>>> +                     /* If we already have MITM protection we can't do
> >>>>> +                      * anything.
> >>>>> +                      */
> >>>>> +                     if (hcon->sec_level > BT_SECURITY_MEDIUM) {
> >>>>> +                             l2cap_chan_del(chan, ECONNREFUSED);
> >>>>> +                             break;
> >>>>> +                     }
> >>>>> +
> >>>>> +                     sec_level = hcon->sec_level + 1;
> >>>>> +                     if (chan->sec_level < sec_level)
> >>>>> +                             chan->sec_level = sec_level;
> >>>>> +
> >>>>> +                     /* We'll need to send a new Connect Request */
> >>>>> +                     clear_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags);
> >>>>> +
> >>>>> +                     smp_conn_security(hcon, chan->sec_level);
> >>>>> +                     break;
> >>>>> +
> >>>>> +             case L2CAP_CR_LE_BAD_PSM:
> >>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
> >>>>> +                     break;
> >>>>> +
> >>>>> +             default:
> >>>>> +                     /* If dcid was not set it means channels was refused */
> >>>>> +                     if (!dcid) {
> >>>>> +                             l2cap_chan_del(chan, ECONNREFUSED);
> >>>>> +                             break;
> >>>>> +                     }
> >>>>> +
> >>>>> +                     chan->ident = 0;
> >>>>> +                     chan->dcid = dcid;
> >>>>> +                     chan->omtu = mtu;
> >>>>> +                     chan->remote_mps = mps;
> >>>>> +                     chan->tx_credits = credits;
> >>>>> +                     l2cap_chan_ready(chan);
> >>>>> +                     break;
> >>>>> +             }
> >>>>> +
> >>>>> +             l2cap_chan_unlock(chan);
> >>>>> +     }
> >>>>> +
> >>>>> +     mutex_unlock(&conn->chan_lock);
> >>>>> +
> >>>>> +     return err;
> >>>>> +}
> >>>>> +
> >>>>> +static inline int l2cap_ecred_reconf_req(struct l2cap_conn *conn,
> >>>>> +                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >>>>> +                                      u8 *data)
> >>>>> +{
> >>>>> +     struct l2cap_ecred_reconf_req *req = (void *) data;
> >>>>> +     struct l2cap_ecred_reconf_rsp rsp;
> >>>>> +     u16 mtu, mps, result;
> >>>>> +     struct l2cap_chan *chan;
> >>>>> +     int i;
> >>>>> +
> >>>>> +     if (cmd_len < sizeof(*req)) {
> >>>>> +             result = L2CAP_CR_LE_INVALID_PARAMS;
> >>>>> +             goto respond;
> >>>>> +     }
> >>>>> +
> >>>>> +     mtu = __le16_to_cpu(req->mtu);
> >>>>> +     mps = __le16_to_cpu(req->mps);
> >>>>> +
> >>>>> +     BT_DBG("mtu %u mps %u", mtu, mps);
> >>>>> +
> >>>>> +     if (mtu < L2CAP_ECRED_MIN_MTU) {
> >>>>> +             result = L2CAP_RECONF_INVALID_MTU;
> >>>>> +             goto respond;
> >>>>> +     }
> >>>>> +
> >>>>> +     if (mps < L2CAP_ECRED_MIN_MPS) {
> >>>>> +             result = L2CAP_RECONF_INVALID_MPS;
> >>>>> +             goto respond;
> >>>>> +     }
> >>>>> +
> >>>>> +     result = L2CAP_RECONF_SUCCESS;
> >>>>> +
> >>>>> +     for (i = 0, cmd_len -= sizeof(*req); cmd_len < sizeof(u16);
> >>>>> +          i++, cmd_len -= sizeof(u16)) {
> >>>>
> >>>> Same comment as above.
> >>>>
> >>>>> +             u16 scid;
> >>>>> +
> >>>>> +             scid = __le16_to_cpu(req->scid[i]);
> >>>>> +             if (!scid)
> >>>>> +                     return -EPROTO;
> >>>>> +
> >>>>> +             chan = __l2cap_get_chan_by_dcid(conn, scid);
> >>>>> +             if (!chan)
> >>>>> +                     continue;
> >>>>> +
> >>>>> +             /* If the MTU value is decreased for any of the included
> >>>>> +              * channels, then the receiver shall disconnect all
> >>>>> +              * included channels.
> >>>>> +              */
> >>>>> +             if (chan->omtu > mtu) {
> >>>>> +                     BT_ERR("chan %p decreased MTU %u -> %u", chan,
> >>>>> +                            chan->omtu, mtu);
> >>>>> +                     result = L2CAP_RECONF_INVALID_MTU;
> >>>>> +             }
> >>>>> +
> >>>>> +             chan->omtu = mtu;
> >>>>> +             chan->remote_mps = mps;
> >>>>> +     }
> >>>>> +
> >>>>> +respond:
> >>>>> +     rsp.result = cpu_to_le16(result);
> >>>>> +
> >>>>> +     l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_RECONF_RSP, sizeof(rsp),
> >>>>> +                    &rsp);
> >>>>> +
> >>>>> +     return 0;
> >>>>> +}
> >>>>> +
> >>>>> +static inline int l2cap_ecred_reconf_rsp(struct l2cap_conn *conn,
> >>>>> +                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >>>>> +                                      u8 *data)
> >>>>> +{
> >>>>> +     struct l2cap_chan *chan;
> >>>>> +     struct l2cap_ecred_conn_rsp *rsp = (void *) data;
> >>>>> +     u16 result;
> >>>>> +
> >>>>> +     if (cmd_len < sizeof(*rsp))
> >>>>> +             return -EPROTO;
> >>>>> +
> >>>>> +     result = __le16_to_cpu(rsp->result);
> >>>>> +
> >>>>> +     BT_DBG("result 0x%4.4x", rsp->result);
> >>>>> +
> >>>>> +     if (!result)
> >>>>> +             return 0;
> >>>>> +
> >>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
> >>>>> +             if (chan->ident != cmd->ident)
> >>>>> +                     continue;
> >>>>> +
> >>>>> +             l2cap_chan_del(chan, ECONNRESET);
> >>>>> +     }
> >>>>> +
> >>>>> +     return 0;
> >>>>> +}
> >>>>> +
> >>>>> static inline int l2cap_le_command_rej(struct l2cap_conn *conn,
> >>>>>                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >>>>>                                    u8 *data)
> >>>>> @@ -5769,6 +6199,22 @@ static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn,
> >>>>>             err = l2cap_le_credits(conn, cmd, cmd_len, data);
> >>>>>             break;
> >>>>>
> >>>>> +     case L2CAP_ECRED_CONN_REQ:
> >>>>> +             err = l2cap_ecred_conn_req(conn, cmd, cmd_len, data);
> >>>>> +             break;
> >>>>> +
> >>>>> +     case L2CAP_ECRED_CONN_RSP:
> >>>>> +             err = l2cap_ecred_conn_rsp(conn, cmd, cmd_len, data);
> >>>>> +             break;
> >>>>> +
> >>>>> +     case L2CAP_ECRED_RECONF_REQ:
> >>>>> +             err = l2cap_ecred_reconf_req(conn, cmd, cmd_len, data);
> >>>>> +             break;
> >>>>> +
> >>>>> +     case L2CAP_ECRED_RECONF_RSP:
> >>>>> +             err = l2cap_ecred_reconf_rsp(conn, cmd, cmd_len, data);
> >>>>> +             break;
> >>>>> +
> >>>>>     case L2CAP_DISCONN_REQ:
> >>>>>             err = l2cap_disconnect_req(conn, cmd, cmd_len, data);
> >>>>>             break;
> >>>>> @@ -6814,11 +7260,13 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
> >>>>>     struct l2cap_le_credits pkt;
> >>>>>     u16 return_credits;
> >>>>>
> >>>>> -     return_credits = ((chan->imtu / chan->mps) + 1) - chan->rx_credits;
> >>>>> +     return_credits = (chan->imtu / chan->mps) + 1;
> >>>>>
> >>>>> -     if (!return_credits)
> >>>>> +     if (chan->rx_credits >= return_credits)
> >>>>>             return;
> >>>>>
> >>>>> +     return_credits -= chan->rx_credits;
> >>>>> +
> >>>>>     BT_DBG("chan %p returning %u credits to sender", chan, return_credits);
> >>>>>
> >>>>>     chan->rx_credits += return_credits;
> >>>>> @@ -6831,7 +7279,7 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
> >>>>>     l2cap_send_cmd(conn, chan->ident, L2CAP_LE_CREDITS, sizeof(pkt), &pkt);
> >>>>> }
> >>>>>
> >>>>> -static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>> +static int l2cap_ecred_recv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>> {
> >>>>>     int err;
> >>>>>
> >>>>> @@ -6846,7 +7294,7 @@ static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>>     return err;
> >>>>> }
> >>>>>
> >>>>> -static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>> +static int l2cap_ecred_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>> {
> >>>>>     int err;
> >>>>>
> >>>>> @@ -6894,7 +7342,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>>             }
> >>>>>
> >>>>>             if (skb->len == sdu_len)
> >>>>> -                     return l2cap_le_recv(chan, skb);
> >>>>> +                     return l2cap_ecred_recv(chan, skb);
> >>>>>
> >>>>>             chan->sdu = skb;
> >>>>>             chan->sdu_len = sdu_len;
> >>>>> @@ -6926,7 +7374,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>>     skb = NULL;
> >>>>>
> >>>>>     if (chan->sdu->len == chan->sdu_len) {
> >>>>> -             err = l2cap_le_recv(chan, chan->sdu);
> >>>>> +             err = l2cap_ecred_recv(chan, chan->sdu);
> >>>>>             if (!err) {
> >>>>>                     chan->sdu = NULL;
> >>>>>                     chan->sdu_last_frag = NULL;
> >>>>> @@ -6987,7 +7435,8 @@ static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid,
> >>>>>
> >>>>>     switch (chan->mode) {
> >>>>>     case L2CAP_MODE_LE_FLOWCTL:
> >>>>> -             if (l2cap_le_data_rcv(chan, skb) < 0)
> >>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>>> +             if (l2cap_ecred_data_rcv(chan, skb) < 0)
> >>>>>                     goto drop;
> >>>>>
> >>>>>             goto done;
> >>>>> @@ -7214,8 +7663,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> >>>>>     struct hci_dev *hdev;
> >>>>>     int err;
> >>>>>
> >>>>> -     BT_DBG("%pMR -> %pMR (type %u) psm 0x%2.2x", &chan->src, dst,
> >>>>> -            dst_type, __le16_to_cpu(psm));
> >>>>> +     BT_DBG("%pMR -> %pMR (type %u) psm 0x%4.4x mode 0x%2.2x", &chan->src,
> >>>>> +            dst, dst_type, __le16_to_cpu(psm), chan->mode);
> >>>>>
> >>>>>     hdev = hci_get_route(dst, &chan->src, chan->src_type);
> >>>>>     if (!hdev)
> >>>>> @@ -7244,6 +7693,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> >>>>>             break;
> >>>>>     case L2CAP_MODE_LE_FLOWCTL:
> >>>>>             break;
> >>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>>> +             break;
> >>>>
> >>>> No need to do another break. Just put it under LE_FLOWCTL.
> >>>>
> >>>>>     case L2CAP_MODE_ERTM:
> >>>>>     case L2CAP_MODE_STREAMING:
> >>>>>             if (!disable_ertm)
> >>>>> @@ -7368,6 +7819,38 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> >>>>> }
> >>>>> EXPORT_SYMBOL_GPL(l2cap_chan_connect);
> >>>>>
> >>>>> +static void l2cap_ecred_reconfigure(struct l2cap_chan *chan)
> >>>>> +{
> >>>>> +     struct l2cap_conn *conn = chan->conn;
> >>>>> +     struct {
> >>>>> +             struct l2cap_ecred_reconf_req req;
> >>>>> +             __le16 scid;
> >>>>> +     } pdu;
> >>>>> +
> >>>>> +     pdu.req.mtu = cpu_to_le16(chan->imtu);
> >>>>> +     pdu.req.mps = cpu_to_le16(chan->mps);
> >>>>> +     pdu.scid    = cpu_to_le16(chan->scid);
> >>>>> +
> >>>>> +     chan->ident = l2cap_get_ident(conn);
> >>>>> +
> >>>>> +     l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_RECONF_REQ,
> >>>>> +                    sizeof(pdu), &pdu);
> >>>>> +}
> >>>>> +
> >>>>> +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu)
> >>>>> +{
> >>>>> +     if (chan->imtu > mtu)
> >>>>> +             return -EINVAL;
> >>>>> +
> >>>>> +     BT_DBG("chan %p mtu 0x%4.4x", chan, mtu);
> >>>>> +
> >>>>> +     chan->imtu = mtu;
> >>>>> +
> >>>>> +     l2cap_ecred_reconfigure(chan);
> >>>>> +
> >>>>> +     return 0;
> >>>>> +}
> >>>>> +
> >>>>> /* ---- L2CAP interface with lower layer (HCI) ---- */
> >>>>>
> >>>>> int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr)
> >>>>> @@ -7579,7 +8062,8 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
> >>>>>                     else
> >>>>>                             __set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
> >>>>>             } else if (chan->state == BT_CONNECT2 &&
> >>>>> -                        chan->mode != L2CAP_MODE_LE_FLOWCTL) {
> >>>>> +                        !(chan->mode == L2CAP_MODE_EXT_FLOWCTL ||
> >>>>> +                         chan->mode == L2CAP_MODE_LE_FLOWCTL)) {
> >>>>
> >>>> Please double check that this line is correctly aligned after the (.
> >>>>
> >>>>>                     struct l2cap_conn_rsp rsp;
> >>>>>                     __u16 res, stat;
> >>>>>
> >>>>> diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
> >>>>> index a7be8b59b3c2..12c557f7f40f 100644
> >>>>> --- a/net/bluetooth/l2cap_sock.c
> >>>>> +++ b/net/bluetooth/l2cap_sock.c
> >>>>> @@ -232,7 +232,7 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr,
> >>>>>                     return -EINVAL;
> >>>>>     }
> >>>>>
> >>>>> -     if (chan->psm && bdaddr_type_is_le(chan->src_type))
> >>>>> +     if (chan->psm && bdaddr_type_is_le(chan->src_type) && !chan->mode)
> >>>>>             chan->mode = L2CAP_MODE_LE_FLOWCTL;
> >>>>>
> >>>>>     err = l2cap_chan_connect(chan, la.l2_psm, __le16_to_cpu(la.l2_cid),
> >>>>> @@ -273,6 +273,7 @@ static int l2cap_sock_listen(struct socket *sock, int backlog)
> >>>>>     switch (chan->mode) {
> >>>>>     case L2CAP_MODE_BASIC:
> >>>>>     case L2CAP_MODE_LE_FLOWCTL:
> >>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>>>             break;
> >>>>>     case L2CAP_MODE_ERTM:
> >>>>>     case L2CAP_MODE_STREAMING:
> >>>>> @@ -408,16 +409,6 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
> >>>>>
> >>>>>     switch (optname) {
> >>>>>     case L2CAP_OPTIONS:
> >>>>> -             /* LE sockets should use BT_SNDMTU/BT_RCVMTU, but since
> >>>>> -              * legacy ATT code depends on getsockopt for
> >>>>> -              * L2CAP_OPTIONS we need to let this pass.
> >>>>> -              */
> >>>>> -             if (bdaddr_type_is_le(chan->src_type) &&
> >>>>> -                 chan->scid != L2CAP_CID_ATT) {
> >>>>> -                     err = -EINVAL;
> >>>>> -                     break;
> >>>>> -             }
> >>>>> -
> >>>>
> >>>> Should this be a separate patch. How do we keep this legacy behavior.
> >>>
> >>> I kind have forgotten to fix this one, I guess we will need a new
> >>> option in order to read the mode without using the old L2CAP_OPTIONS
> >>> then, or do you have anything against introducing yet another option?
> >>
> >> so what is the problem here? Is this because L2CAP by itself can not tell if enhanced credit mode is supported by the peer?
> >
> > Yep, afaik there is no means to detect the support transparently,
> > otherwise could always prefer to use EXT_FLOWCTL instead of
> > LE_FLOWCTL, it seems at the point we introduced LE_FLOWCTL option we
> > had always assumed there would not be any mode one could use when the
> > address type is LE.
> >
> >> Do you actually want to create a new mode socket options with defined values by us to allow selected the mode of operation?
> >
> > I woud assume that is the only safe option here, though what I did was
> > to enable L2CAP_OPTIONS to work with LE addresses which I assume is
> > safe as previously it would cause an error no one would be using it.
>
> that is what I was afraid of. So can we use BT_SNDMTU and BT_RCVMTU and then only a mode property is missing? Or do we have other options that we want to configure.

Not sure I follow, do you prefer chaning an existing option to add a
new field? I though having an option to set the mode would be better
but perhaps you want to combine it something else, well maybe some
sort of QOS interface, the problem is that the field are quite
different depending on the mode so Im afraid that would take a lot
more time to define.

> For the multi-connect case, I was thinking that we use something similar to DEFER_SETUP. If we set a DEFER_SETUP on an outgoing socket, then you can call connect() on a socket and succeeds. However no POLLIN | POLLOUT would be signaled. When calling a connect() without that option set, then all sockets become ready at the same time on success.

As initiator I don't think we have a good match in the socket API as
it only return one fd at time, so currently it still doing the
connection one by one, what I though we could do is to delay a little
to group the request in a single L2CAP request, thought that creates
an extra latency for initiating the connection and can be racy.
Another option would be to allow bind to bind to multiple sockets at
once, so when you issue the connect in one socket it would connect all
socket bound.

> Local CID assignment however must be done by the time connect() succeeds to allow identification. That order / indentation of L2CAP channels is another fun question. What happens if we get 4 GATT sockets, who decides what parts run where?

For GATT we keep all sockets internally as a pool of channels to use,
for the listening socket I went with the assumption that if one of the
sockets would fails all the remaining would fail as well, again I
tried not to introduce artificial delays since we could get into weird
situation where only 1 socket accepts the connection but the
remaining, though defer setup is kind a special case already so we
could perhaps wait for each and every socket to respond, but one of
those takes too long and the request times out then all the other
would time ou as well since we don't respond before that.

-- 
Luiz Augusto von Dentz

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] Bluetooth: L2CAP: Add initial code for Enhanced Credit Based Mode
  2020-01-27  6:28             ` Luiz Augusto von Dentz
@ 2020-01-27 12:10               ` Marcel Holtmann
  2020-02-20 23:42                 ` Luiz Augusto von Dentz
  0 siblings, 1 reply; 14+ messages in thread
From: Marcel Holtmann @ 2020-01-27 12:10 UTC (permalink / raw)
  To: Luiz Augusto von Dentz; +Cc: linux-bluetooth

Hi Luiz,

>>>>>>> This adds the initial code for Enhanced Credit Based Mode which
>>>>>>> introduces a new socket mode called L2CAP_MODE_EXT_FLOWCTL, which for
>>>>>>> the most part work the same as L2CAP_MODE_LE_FLOWCTL but uses different
>>>>>>> PDUs to setup the connections and also works over BR/EDR.
>>>>>>> 
>>>>>>> Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
>>>>>>> ---
>>>>>>> include/net/bluetooth/l2cap.h |   4 +
>>>>>>> net/bluetooth/l2cap_core.c    | 510 +++++++++++++++++++++++++++++++++-
>>>>>>> net/bluetooth/l2cap_sock.c    |  39 +--
>>>>>>> 3 files changed, 521 insertions(+), 32 deletions(-)
>>>>>>> 
>>>>>>> diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
>>>>>>> index b9ea88cfd2d2..11ae4c93354b 100644
>>>>>>> --- a/include/net/bluetooth/l2cap.h
>>>>>>> +++ b/include/net/bluetooth/l2cap.h
>>>>>>> @@ -295,6 +295,8 @@ struct l2cap_conn_rsp {
>>>>>>> #define L2CAP_CR_LE_ENCRYPTION                0x0008
>>>>>>> #define L2CAP_CR_LE_INVALID_SCID      0x0009
>>>>>>> #define L2CAP_CR_LE_SCID_IN_USE               0X000A
>>>>>>> +#define L2CAP_CR_LE_UNACCEPT_PARAMS  0X000B
>>>>>>> +#define L2CAP_CR_LE_INVALID_PARAMS   0X000C
>>>>>>> 
>>>>>>> /* connect/create channel status */
>>>>>>> #define L2CAP_CS_NO_INFO      0x0000
>>>>>>> @@ -964,6 +966,7 @@ void l2cap_cleanup_sockets(void);
>>>>>>> bool l2cap_is_socket(struct socket *sock);
>>>>>>> 
>>>>>>> void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan);
>>>>>>> +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan);
>>>>>>> void __l2cap_connect_rsp_defer(struct l2cap_chan *chan);
>>>>>>> 
>>>>>>> int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm);
>>>>>>> @@ -973,6 +976,7 @@ struct l2cap_chan *l2cap_chan_create(void);
>>>>>>> void l2cap_chan_close(struct l2cap_chan *chan, int reason);
>>>>>>> int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
>>>>>>>                   bdaddr_t *dst, u8 dst_type);
>>>>>>> +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu);
>>>>>>> int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len);
>>>>>>> void l2cap_chan_busy(struct l2cap_chan *chan, int busy);
>>>>>>> int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator);
>>>>>>> diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
>>>>>>> index 195459a1e53e..a528526cffac 100644
>>>>>>> --- a/net/bluetooth/l2cap_core.c
>>>>>>> +++ b/net/bluetooth/l2cap_core.c
>>>>>>> @@ -532,6 +532,17 @@ static void l2cap_le_flowctl_init(struct l2cap_chan *chan, u16 tx_credits)
>>>>>>>    skb_queue_head_init(&chan->tx_q);
>>>>>>> }
>>>>>>> 
>>>>>>> +static void l2cap_ecred_init(struct l2cap_chan *chan, u16 tx_credits)
>>>>>>> +{
>>>>>>> +     l2cap_le_flowctl_init(chan, tx_credits);
>>>>>>> +
>>>>>>> +     /* L2CAP implementations shall support a minimum MPS of 64 octets */
>>>>>>> +     if (chan->mps < L2CAP_ECRED_MIN_MPS) {
>>>>>>> +             chan->mps = L2CAP_ECRED_MIN_MPS;
>>>>>>> +             chan->rx_credits = (chan->imtu / chan->mps) + 1;
>>>>>>> +     }
>>>>>>> +}
>>>>>>> +
>>>>>>> void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
>>>>>>> {
>>>>>>>    BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn,
>>>>>>> @@ -638,6 +649,7 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
>>>>>>>            break;
>>>>>>> 
>>>>>>>    case L2CAP_MODE_LE_FLOWCTL:
>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>>>            skb_queue_purge(&chan->tx_q);
>>>>>>>            break;
>>>>>>> 
>>>>>>> @@ -1260,6 +1272,17 @@ static void l2cap_move_done(struct l2cap_chan *chan)
>>>>>>>    }
>>>>>>> }
>>>>>>> 
>>>>>>> +static bool l2cap_mode_ready(struct l2cap_chan *chan)
>>>>>>> +{
>>>>>>> +     switch (chan->mode) {
>>>>>>> +     case L2CAP_MODE_LE_FLOWCTL:
>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>>> +             return chan->tx_credits ? true : false;
>>>>>>> +     }
>>>>>>> +
>>>>>>> +     return true;
>>>>>>> +}
>>>>>>> +
>>>>>>> static void l2cap_chan_ready(struct l2cap_chan *chan)
>>>>>>> {
>>>>>>>    /* The channel may have already been flagged as connected in
>>>>>>> @@ -1273,7 +1296,7 @@ static void l2cap_chan_ready(struct l2cap_chan *chan)
>>>>>>>    chan->conf_state = 0;
>>>>>>>    __clear_chan_timer(chan);
>>>>>>> 
>>>>>>> -     if (chan->mode == L2CAP_MODE_LE_FLOWCTL && !chan->tx_credits)
>>>>>>> +     if (!l2cap_mode_ready(chan))
>>>>>>>            chan->ops->suspend(chan);
>>>>>> 
>>>>>>      switch (chan->mode) {
>>>>>>      case ..
>>>>>>      case ..
>>>>>>              if (!chan->tx_credits)
>>>>>>                      chan->ops->suspend(chan);
>>>>>>              break;
>>>>>>      }
>>>>>> 
>>>>>>> 
>>>>>>>    chan->state = BT_CONNECTED;
>>>>>>> @@ -1306,6 +1329,31 @@ static void l2cap_le_connect(struct l2cap_chan *chan)
>>>>>>>                   sizeof(req), &req);
>>>>>>> }
>>>>>>> 
>>>>>>> +static void l2cap_ecred_connect(struct l2cap_chan *chan)
>>>>>>> +{
>>>>>>> +     struct l2cap_conn *conn = chan->conn;
>>>>>>> +     struct {
>>>>>>> +             struct l2cap_ecred_conn_req req;
>>>>>>> +             __le16 scid;
>>>>>>> +     } __packed pdu;
>>>>>>> +
>>>>>>> +     if (test_and_set_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags))
>>>>>>> +             return;
>>>>>>> +
>>>>>>> +     l2cap_ecred_init(chan, 0);
>>>>>>> +
>>>>>>> +     pdu.req.psm     = chan->psm;
>>>>>>> +     pdu.req.mtu     = cpu_to_le16(chan->imtu);
>>>>>>> +     pdu.req.mps     = cpu_to_le16(chan->mps);
>>>>>>> +     pdu.req.credits = cpu_to_le16(chan->rx_credits);
>>>>>>> +     pdu.scid        = cpu_to_le16(chan->scid);
>>>>>>> +
>>>>>>> +     chan->ident = l2cap_get_ident(conn);
>>>>>>> +
>>>>>>> +     l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_CONN_REQ,
>>>>>>> +                    sizeof(pdu), &pdu);
>>>>>>> +}
>>>>>>> +
>>>>>>> static void l2cap_le_start(struct l2cap_chan *chan)
>>>>>>> {
>>>>>>>    struct l2cap_conn *conn = chan->conn;
>>>>>>> @@ -1318,8 +1366,12 @@ static void l2cap_le_start(struct l2cap_chan *chan)
>>>>>>>            return;
>>>>>>>    }
>>>>>>> 
>>>>>>> -     if (chan->state == BT_CONNECT)
>>>>>>> -             l2cap_le_connect(chan);
>>>>>>> +     if (chan->state == BT_CONNECT) {
>>>>>>> +             if (chan->mode == L2CAP_MODE_EXT_FLOWCTL)
>>>>>>> +                     l2cap_ecred_connect(chan);
>>>>>>> +             else
>>>>>>> +                     l2cap_le_connect(chan);
>>>>>>> +     }
>>>>>>> }
>>>>>>> 
>>>>>>> static void l2cap_start_connection(struct l2cap_chan *chan)
>>>>>>> @@ -2505,6 +2557,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
>>>>>>> 
>>>>>>>    switch (chan->mode) {
>>>>>>>    case L2CAP_MODE_LE_FLOWCTL:
>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>>>            /* Check outgoing MTU */
>>>>>>>            if (len > chan->omtu)
>>>>>>>                    return -EMSGSIZE;
>>>>>>> @@ -3773,6 +3826,42 @@ void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan)
>>>>>>>                   &rsp);
>>>>>>> }
>>>>>>> 
>>>>>>> +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
>>>>>>> +{
>>>>>>> +     struct l2cap_ecred_conn_rsp rsp;
>>>>>>> +     struct l2cap_conn *conn = chan->conn;
>>>>>>> +     u16 ident = chan->ident;
>>>>>>> +     int i = 0;
>>>>>>> +
>>>>>>> +     if (!ident) {
>>>>>>> +             return;
>>>>>>> +     }
>>>>>>> +
>>>>>> 
>>>>>> No { } here.
>>>>>> 
>>>>>>> +     BT_DBG("chan %p ident %d", chan, ident);
>>>>>>> +
>>>>>>> +     rsp.mtu     = cpu_to_le16(chan->imtu);
>>>>>>> +     rsp.mps     = cpu_to_le16(chan->mps);
>>>>>>> +     rsp.credits = cpu_to_le16(chan->rx_credits);
>>>>>>> +     rsp.result  = cpu_to_le16(L2CAP_CR_LE_SUCCESS);
>>>>>>> +
>>>>>>> +     mutex_lock(&conn->chan_lock);
>>>>>>> +
>>>>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
>>>>>>> +             if (chan->ident != ident)
>>>>>>> +                     continue;
>>>>>>> +
>>>>>>> +             /* Reset ident so only one response is sent */
>>>>>>> +             chan->ident = 0;
>>>>>>> +
>>>>>>> +             /* Include all channels pending with the same ident */
>>>>>>> +             rsp.dcid[i++] = cpu_to_le16(chan->scid);
>>>>>> 
>>>>>> This doesn’t work. The sizeof(rsp) has no dcid in there. You are overflowing into conn struct here.
>>>>>> 
>>>>>>> +     }
>>>>>>> +
>>>>>>> +     mutex_unlock(&conn->chan_lock);
>>>>>>> +
>>>>>>> +     l2cap_send_cmd(conn, ident, L2CAP_ECRED_CONN_RSP, sizeof(rsp), &rsp);
>>>>>>> +}
>>>>>>> +
>>>>>>> void __l2cap_connect_rsp_defer(struct l2cap_chan *chan)
>>>>>>> {
>>>>>>>    struct l2cap_conn_rsp rsp;
>>>>>>> @@ -5714,6 +5803,347 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn,
>>>>>>>    return 0;
>>>>>>> }
>>>>>>> 
>>>>>>> +static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
>>>>>>> +                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>>>>> +                                    u8 *data)
>>>>>>> +{
>>>>>>> +     struct l2cap_ecred_conn_req *req = (void *) data;
>>>>>>> +     struct {
>>>>>>> +             struct l2cap_ecred_conn_rsp rsp;
>>>>>>> +             __le16 dcid[5];
>>>>>>> +     } __packed pdu;
>>>>>>> +     struct l2cap_chan *chan, *pchan;
>>>>>>> +     u16 credits, mtu, mps;
>>>>>>> +     __le16 psm;
>>>>>>> +     u8 result, len = 0;
>>>>>>> +     int i;
>>>>>>> +     bool defer = false;
>>>>>>> +
>>>>>>> +     if (cmd_len < sizeof(*req))
>>>>>>> +             return -EPROTO;
>>>>>>> +
>>>>>>> +     mtu  = __le16_to_cpu(req->mtu);
>>>>>>> +     mps  = __le16_to_cpu(req->mps);
>>>>>>> +
>>>>>>> +     if (mtu < L2CAP_ECRED_MIN_MTU || mps < L2CAP_ECRED_MIN_MPS) {
>>>>>>> +             result = L2CAP_CR_LE_UNACCEPT_PARAMS;
>>>>>>> +             goto response;
>>>>>>> +     }
>>>>>>> +
>>>>>>> +     psm  = req->psm;
>>>>>>> +     credits = 0;
>>>>>>> +
>>>>>>> +     BT_DBG("psm 0x%2.2x mtu %u mps %u", __le16_to_cpu(psm), mtu, mps);
>>>>>>> +
>>>>>>> +     memset(&pdu, 0, sizeof(pdu));
>>>>>>> +
>>>>>>> +     /* Check if we have socket listening on psm */
>>>>>>> +     pchan = l2cap_global_chan_by_psm(BT_LISTEN, psm, &conn->hcon->src,
>>>>>>> +                                      &conn->hcon->dst, LE_LINK);
>>>>>>> +     if (!pchan) {
>>>>>>> +             result = L2CAP_CR_LE_BAD_PSM;
>>>>>>> +             goto response;
>>>>>>> +     }
>>>>>>> +
>>>>>>> +     mutex_lock(&conn->chan_lock);
>>>>>>> +     l2cap_chan_lock(pchan);
>>>>>>> +
>>>>>>> +     if (!smp_sufficient_security(conn->hcon, pchan->sec_level,
>>>>>>> +                                  SMP_ALLOW_STK)) {
>>>>>>> +             result = L2CAP_CR_LE_AUTHENTICATION;
>>>>>>> +             goto unlock;
>>>>>>> +     }
>>>>>>> +
>>>>>>> +     result = L2CAP_CR_LE_SUCCESS;
>>>>>>> +
>>>>>>> +     for (i = 0, cmd_len -= sizeof(req); cmd_len >= sizeof(u16);
>>>>>>> +          i++, cmd_len -= sizeof(u16)) {
>>>>>> 
>>>>>> Can we simplify the loop statement or switch to while statement?
>>>>>> 
>>>>>>> +             u16 scid = __le16_to_cpu(req->scid[i]);
>>>>>>> +
>>>>>>> +             BT_DBG("scid[%d] 0x%4.4x", i, scid);
>>>>>>> +
>>>>>>> +             pdu.dcid[i] = 0x0000;
>>>>>>> +             len += sizeof(*pdu.dcid);
>>>>>>> +
>>>>>>> +             /* Check for valid dynamic CID range */
>>>>>>> +             if (scid < L2CAP_CID_DYN_START || scid > L2CAP_CID_LE_DYN_END) {
>>>>>>> +                     result = L2CAP_CR_LE_INVALID_SCID;
>>>>>>> +                     continue;
>>>>>>> +             }
>>>>>>> +
>>>>>>> +             /* Check if we already have channel with that dcid */
>>>>>>> +             if (__l2cap_get_chan_by_dcid(conn, scid)) {
>>>>>>> +                     result = L2CAP_CR_LE_SCID_IN_USE;
>>>>>>> +                     continue;
>>>>>>> +             }
>>>>>>> +
>>>>>>> +             chan = pchan->ops->new_connection(pchan);
>>>>>>> +             if (!chan) {
>>>>>>> +                     result = L2CAP_CR_LE_NO_MEM;
>>>>>>> +                     continue;
>>>>>>> +             }
>>>>>>> +
>>>>>>> +             bacpy(&chan->src, &conn->hcon->src);
>>>>>>> +             bacpy(&chan->dst, &conn->hcon->dst);
>>>>>>> +             chan->src_type = bdaddr_src_type(conn->hcon);
>>>>>>> +             chan->dst_type = bdaddr_dst_type(conn->hcon);
>>>>>>> +             chan->psm  = psm;
>>>>>>> +             chan->dcid = scid;
>>>>>>> +             chan->omtu = mtu;
>>>>>>> +             chan->remote_mps = mps;
>>>>>>> +
>>>>>>> +             __l2cap_chan_add(conn, chan);
>>>>>>> +
>>>>>>> +             l2cap_ecred_init(chan, __le16_to_cpu(req->credits));
>>>>>>> +
>>>>>>> +             /* Init response */
>>>>>>> +             if (!pdu.rsp.credits) {
>>>>>>> +                     pdu.rsp.mtu = cpu_to_le16(chan->imtu);
>>>>>>> +                     pdu.rsp.mps = cpu_to_le16(chan->mps);
>>>>>>> +                     pdu.rsp.credits = cpu_to_le16(chan->rx_credits);
>>>>>>> +             }
>>>>>>> +
>>>>>>> +             pdu.dcid[i] = cpu_to_le16(chan->scid);
>>>>>>> +
>>>>>>> +             __set_chan_timer(chan, chan->ops->get_sndtimeo(chan));
>>>>>>> +
>>>>>>> +             chan->ident = cmd->ident;
>>>>>>> +
>>>>>>> +             if (test_bit(FLAG_DEFER_SETUP, &chan->flags)) {
>>>>>>> +                     l2cap_state_change(chan, BT_CONNECT2);
>>>>>>> +                     defer = true;
>>>>>>> +                     chan->ops->defer(chan);
>>>>>>> +             } else {
>>>>>>> +                     l2cap_chan_ready(chan);
>>>>>>> +             }
>>>>>>> +     }
>>>>>>> +
>>>>>>> +unlock:
>>>>>>> +     l2cap_chan_unlock(pchan);
>>>>>>> +     mutex_unlock(&conn->chan_lock);
>>>>>>> +     l2cap_chan_put(pchan);
>>>>>>> +
>>>>>>> +response:
>>>>>>> +     pdu.rsp.result = cpu_to_le16(result);
>>>>>>> +
>>>>>>> +     if (defer)
>>>>>>> +             return 0;
>>>>>>> +
>>>>>>> +     l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_CONN_RSP,
>>>>>>> +                    sizeof(pdu.rsp) + len, &pdu);
>>>>>>> +
>>>>>>> +     return 0;
>>>>>>> +}
>>>>>>> +
>>>>>>> +static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
>>>>>>> +                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>>>>> +                                    u8 *data)
>>>>>>> +{
>>>>>>> +     struct l2cap_ecred_conn_rsp *rsp = (void *) data;
>>>>>>> +     struct hci_conn *hcon = conn->hcon;
>>>>>>> +     u16 mtu, mps, credits, result;
>>>>>>> +     struct l2cap_chan *chan;
>>>>>>> +     int err = 0, sec_level;
>>>>>>> +     int i = 0;
>>>>>>> +
>>>>>>> +     if (cmd_len < sizeof(*rsp))
>>>>>>> +             return -EPROTO;
>>>>>>> +
>>>>>>> +     mtu     = __le16_to_cpu(rsp->mtu);
>>>>>>> +     mps     = __le16_to_cpu(rsp->mps);
>>>>>>> +     credits = __le16_to_cpu(rsp->credits);
>>>>>>> +     result  = __le16_to_cpu(rsp->result);
>>>>>>> +
>>>>>>> +     BT_DBG("mtu %u mps %u credits %u result 0x%4.4x", mtu, mps, credits,
>>>>>>> +            result);
>>>>>>> +
>>>>>>> +     mutex_lock(&conn->chan_lock);
>>>>>>> +
>>>>>>> +     cmd_len -= sizeof(*rsp);
>>>>>>> +
>>>>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
>>>>>>> +             u16 dcid;
>>>>>>> +
>>>>>>> +             if (chan->ident != cmd->ident ||
>>>>>>> +                 chan->mode != L2CAP_MODE_EXT_FLOWCTL ||
>>>>>>> +                 chan->state == BT_CONNECTED)
>>>>>>> +                     continue;
>>>>>>> +
>>>>>>> +             l2cap_chan_lock(chan);
>>>>>>> +
>>>>>>> +             /* Check that there is a dcid for each pending channel */
>>>>>>> +             if (cmd_len < sizeof(dcid)) {
>>>>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
>>>>>>> +                     l2cap_chan_unlock(chan);
>>>>>>> +                     continue;
>>>>>>> +             }
>>>>>>> +
>>>>>>> +             dcid = __le16_to_cpu(rsp->dcid[i++]);
>>>>>>> +             cmd_len -= sizeof(u16);
>>>>>>> +
>>>>>>> +             BT_DBG("dcid[%d] 0x%4.4x", i, dcid);
>>>>>>> +
>>>>>>> +             /* Check if dcid is already in use */
>>>>>>> +             if (dcid && __l2cap_get_chan_by_dcid(conn, dcid)) {
>>>>>>> +                     /* If a device receives a
>>>>>>> +                      * L2CAP_CREDIT_BASED_CONNECTION_RSP packet with an
>>>>>>> +                      * already-assigned Destination CID, then both the
>>>>>>> +                      * original channel and the new channel shall be
>>>>>>> +                      * immediately discarded and not used.
>>>>>>> +                      */
>>>>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
>>>>>>> +                     l2cap_chan_unlock(chan);
>>>>>>> +                     chan = __l2cap_get_chan_by_dcid(conn, dcid);
>>>>>>> +                     l2cap_chan_lock(chan);
>>>>>>> +                     l2cap_chan_del(chan, ECONNRESET);
>>>>>>> +                     l2cap_chan_unlock(chan);
>>>>>>> +                     continue;
>>>>>>> +             }
>>>>>>> +
>>>>>>> +             switch (result) {
>>>>>>> +             case L2CAP_CR_LE_AUTHENTICATION:
>>>>>>> +             case L2CAP_CR_LE_ENCRYPTION:
>>>>>>> +                     /* If we already have MITM protection we can't do
>>>>>>> +                      * anything.
>>>>>>> +                      */
>>>>>>> +                     if (hcon->sec_level > BT_SECURITY_MEDIUM) {
>>>>>>> +                             l2cap_chan_del(chan, ECONNREFUSED);
>>>>>>> +                             break;
>>>>>>> +                     }
>>>>>>> +
>>>>>>> +                     sec_level = hcon->sec_level + 1;
>>>>>>> +                     if (chan->sec_level < sec_level)
>>>>>>> +                             chan->sec_level = sec_level;
>>>>>>> +
>>>>>>> +                     /* We'll need to send a new Connect Request */
>>>>>>> +                     clear_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags);
>>>>>>> +
>>>>>>> +                     smp_conn_security(hcon, chan->sec_level);
>>>>>>> +                     break;
>>>>>>> +
>>>>>>> +             case L2CAP_CR_LE_BAD_PSM:
>>>>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
>>>>>>> +                     break;
>>>>>>> +
>>>>>>> +             default:
>>>>>>> +                     /* If dcid was not set it means channels was refused */
>>>>>>> +                     if (!dcid) {
>>>>>>> +                             l2cap_chan_del(chan, ECONNREFUSED);
>>>>>>> +                             break;
>>>>>>> +                     }
>>>>>>> +
>>>>>>> +                     chan->ident = 0;
>>>>>>> +                     chan->dcid = dcid;
>>>>>>> +                     chan->omtu = mtu;
>>>>>>> +                     chan->remote_mps = mps;
>>>>>>> +                     chan->tx_credits = credits;
>>>>>>> +                     l2cap_chan_ready(chan);
>>>>>>> +                     break;
>>>>>>> +             }
>>>>>>> +
>>>>>>> +             l2cap_chan_unlock(chan);
>>>>>>> +     }
>>>>>>> +
>>>>>>> +     mutex_unlock(&conn->chan_lock);
>>>>>>> +
>>>>>>> +     return err;
>>>>>>> +}
>>>>>>> +
>>>>>>> +static inline int l2cap_ecred_reconf_req(struct l2cap_conn *conn,
>>>>>>> +                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>>>>> +                                      u8 *data)
>>>>>>> +{
>>>>>>> +     struct l2cap_ecred_reconf_req *req = (void *) data;
>>>>>>> +     struct l2cap_ecred_reconf_rsp rsp;
>>>>>>> +     u16 mtu, mps, result;
>>>>>>> +     struct l2cap_chan *chan;
>>>>>>> +     int i;
>>>>>>> +
>>>>>>> +     if (cmd_len < sizeof(*req)) {
>>>>>>> +             result = L2CAP_CR_LE_INVALID_PARAMS;
>>>>>>> +             goto respond;
>>>>>>> +     }
>>>>>>> +
>>>>>>> +     mtu = __le16_to_cpu(req->mtu);
>>>>>>> +     mps = __le16_to_cpu(req->mps);
>>>>>>> +
>>>>>>> +     BT_DBG("mtu %u mps %u", mtu, mps);
>>>>>>> +
>>>>>>> +     if (mtu < L2CAP_ECRED_MIN_MTU) {
>>>>>>> +             result = L2CAP_RECONF_INVALID_MTU;
>>>>>>> +             goto respond;
>>>>>>> +     }
>>>>>>> +
>>>>>>> +     if (mps < L2CAP_ECRED_MIN_MPS) {
>>>>>>> +             result = L2CAP_RECONF_INVALID_MPS;
>>>>>>> +             goto respond;
>>>>>>> +     }
>>>>>>> +
>>>>>>> +     result = L2CAP_RECONF_SUCCESS;
>>>>>>> +
>>>>>>> +     for (i = 0, cmd_len -= sizeof(*req); cmd_len < sizeof(u16);
>>>>>>> +          i++, cmd_len -= sizeof(u16)) {
>>>>>> 
>>>>>> Same comment as above.
>>>>>> 
>>>>>>> +             u16 scid;
>>>>>>> +
>>>>>>> +             scid = __le16_to_cpu(req->scid[i]);
>>>>>>> +             if (!scid)
>>>>>>> +                     return -EPROTO;
>>>>>>> +
>>>>>>> +             chan = __l2cap_get_chan_by_dcid(conn, scid);
>>>>>>> +             if (!chan)
>>>>>>> +                     continue;
>>>>>>> +
>>>>>>> +             /* If the MTU value is decreased for any of the included
>>>>>>> +              * channels, then the receiver shall disconnect all
>>>>>>> +              * included channels.
>>>>>>> +              */
>>>>>>> +             if (chan->omtu > mtu) {
>>>>>>> +                     BT_ERR("chan %p decreased MTU %u -> %u", chan,
>>>>>>> +                            chan->omtu, mtu);
>>>>>>> +                     result = L2CAP_RECONF_INVALID_MTU;
>>>>>>> +             }
>>>>>>> +
>>>>>>> +             chan->omtu = mtu;
>>>>>>> +             chan->remote_mps = mps;
>>>>>>> +     }
>>>>>>> +
>>>>>>> +respond:
>>>>>>> +     rsp.result = cpu_to_le16(result);
>>>>>>> +
>>>>>>> +     l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_RECONF_RSP, sizeof(rsp),
>>>>>>> +                    &rsp);
>>>>>>> +
>>>>>>> +     return 0;
>>>>>>> +}
>>>>>>> +
>>>>>>> +static inline int l2cap_ecred_reconf_rsp(struct l2cap_conn *conn,
>>>>>>> +                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>>>>> +                                      u8 *data)
>>>>>>> +{
>>>>>>> +     struct l2cap_chan *chan;
>>>>>>> +     struct l2cap_ecred_conn_rsp *rsp = (void *) data;
>>>>>>> +     u16 result;
>>>>>>> +
>>>>>>> +     if (cmd_len < sizeof(*rsp))
>>>>>>> +             return -EPROTO;
>>>>>>> +
>>>>>>> +     result = __le16_to_cpu(rsp->result);
>>>>>>> +
>>>>>>> +     BT_DBG("result 0x%4.4x", rsp->result);
>>>>>>> +
>>>>>>> +     if (!result)
>>>>>>> +             return 0;
>>>>>>> +
>>>>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
>>>>>>> +             if (chan->ident != cmd->ident)
>>>>>>> +                     continue;
>>>>>>> +
>>>>>>> +             l2cap_chan_del(chan, ECONNRESET);
>>>>>>> +     }
>>>>>>> +
>>>>>>> +     return 0;
>>>>>>> +}
>>>>>>> +
>>>>>>> static inline int l2cap_le_command_rej(struct l2cap_conn *conn,
>>>>>>>                                   struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>>>>>                                   u8 *data)
>>>>>>> @@ -5769,6 +6199,22 @@ static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn,
>>>>>>>            err = l2cap_le_credits(conn, cmd, cmd_len, data);
>>>>>>>            break;
>>>>>>> 
>>>>>>> +     case L2CAP_ECRED_CONN_REQ:
>>>>>>> +             err = l2cap_ecred_conn_req(conn, cmd, cmd_len, data);
>>>>>>> +             break;
>>>>>>> +
>>>>>>> +     case L2CAP_ECRED_CONN_RSP:
>>>>>>> +             err = l2cap_ecred_conn_rsp(conn, cmd, cmd_len, data);
>>>>>>> +             break;
>>>>>>> +
>>>>>>> +     case L2CAP_ECRED_RECONF_REQ:
>>>>>>> +             err = l2cap_ecred_reconf_req(conn, cmd, cmd_len, data);
>>>>>>> +             break;
>>>>>>> +
>>>>>>> +     case L2CAP_ECRED_RECONF_RSP:
>>>>>>> +             err = l2cap_ecred_reconf_rsp(conn, cmd, cmd_len, data);
>>>>>>> +             break;
>>>>>>> +
>>>>>>>    case L2CAP_DISCONN_REQ:
>>>>>>>            err = l2cap_disconnect_req(conn, cmd, cmd_len, data);
>>>>>>>            break;
>>>>>>> @@ -6814,11 +7260,13 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
>>>>>>>    struct l2cap_le_credits pkt;
>>>>>>>    u16 return_credits;
>>>>>>> 
>>>>>>> -     return_credits = ((chan->imtu / chan->mps) + 1) - chan->rx_credits;
>>>>>>> +     return_credits = (chan->imtu / chan->mps) + 1;
>>>>>>> 
>>>>>>> -     if (!return_credits)
>>>>>>> +     if (chan->rx_credits >= return_credits)
>>>>>>>            return;
>>>>>>> 
>>>>>>> +     return_credits -= chan->rx_credits;
>>>>>>> +
>>>>>>>    BT_DBG("chan %p returning %u credits to sender", chan, return_credits);
>>>>>>> 
>>>>>>>    chan->rx_credits += return_credits;
>>>>>>> @@ -6831,7 +7279,7 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
>>>>>>>    l2cap_send_cmd(conn, chan->ident, L2CAP_LE_CREDITS, sizeof(pkt), &pkt);
>>>>>>> }
>>>>>>> 
>>>>>>> -static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>> +static int l2cap_ecred_recv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>> {
>>>>>>>    int err;
>>>>>>> 
>>>>>>> @@ -6846,7 +7294,7 @@ static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>>    return err;
>>>>>>> }
>>>>>>> 
>>>>>>> -static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>> +static int l2cap_ecred_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>> {
>>>>>>>    int err;
>>>>>>> 
>>>>>>> @@ -6894,7 +7342,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>>            }
>>>>>>> 
>>>>>>>            if (skb->len == sdu_len)
>>>>>>> -                     return l2cap_le_recv(chan, skb);
>>>>>>> +                     return l2cap_ecred_recv(chan, skb);
>>>>>>> 
>>>>>>>            chan->sdu = skb;
>>>>>>>            chan->sdu_len = sdu_len;
>>>>>>> @@ -6926,7 +7374,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>>    skb = NULL;
>>>>>>> 
>>>>>>>    if (chan->sdu->len == chan->sdu_len) {
>>>>>>> -             err = l2cap_le_recv(chan, chan->sdu);
>>>>>>> +             err = l2cap_ecred_recv(chan, chan->sdu);
>>>>>>>            if (!err) {
>>>>>>>                    chan->sdu = NULL;
>>>>>>>                    chan->sdu_last_frag = NULL;
>>>>>>> @@ -6987,7 +7435,8 @@ static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid,
>>>>>>> 
>>>>>>>    switch (chan->mode) {
>>>>>>>    case L2CAP_MODE_LE_FLOWCTL:
>>>>>>> -             if (l2cap_le_data_rcv(chan, skb) < 0)
>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>>> +             if (l2cap_ecred_data_rcv(chan, skb) < 0)
>>>>>>>                    goto drop;
>>>>>>> 
>>>>>>>            goto done;
>>>>>>> @@ -7214,8 +7663,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
>>>>>>>    struct hci_dev *hdev;
>>>>>>>    int err;
>>>>>>> 
>>>>>>> -     BT_DBG("%pMR -> %pMR (type %u) psm 0x%2.2x", &chan->src, dst,
>>>>>>> -            dst_type, __le16_to_cpu(psm));
>>>>>>> +     BT_DBG("%pMR -> %pMR (type %u) psm 0x%4.4x mode 0x%2.2x", &chan->src,
>>>>>>> +            dst, dst_type, __le16_to_cpu(psm), chan->mode);
>>>>>>> 
>>>>>>>    hdev = hci_get_route(dst, &chan->src, chan->src_type);
>>>>>>>    if (!hdev)
>>>>>>> @@ -7244,6 +7693,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
>>>>>>>            break;
>>>>>>>    case L2CAP_MODE_LE_FLOWCTL:
>>>>>>>            break;
>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>>> +             break;
>>>>>> 
>>>>>> No need to do another break. Just put it under LE_FLOWCTL.
>>>>>> 
>>>>>>>    case L2CAP_MODE_ERTM:
>>>>>>>    case L2CAP_MODE_STREAMING:
>>>>>>>            if (!disable_ertm)
>>>>>>> @@ -7368,6 +7819,38 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
>>>>>>> }
>>>>>>> EXPORT_SYMBOL_GPL(l2cap_chan_connect);
>>>>>>> 
>>>>>>> +static void l2cap_ecred_reconfigure(struct l2cap_chan *chan)
>>>>>>> +{
>>>>>>> +     struct l2cap_conn *conn = chan->conn;
>>>>>>> +     struct {
>>>>>>> +             struct l2cap_ecred_reconf_req req;
>>>>>>> +             __le16 scid;
>>>>>>> +     } pdu;
>>>>>>> +
>>>>>>> +     pdu.req.mtu = cpu_to_le16(chan->imtu);
>>>>>>> +     pdu.req.mps = cpu_to_le16(chan->mps);
>>>>>>> +     pdu.scid    = cpu_to_le16(chan->scid);
>>>>>>> +
>>>>>>> +     chan->ident = l2cap_get_ident(conn);
>>>>>>> +
>>>>>>> +     l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_RECONF_REQ,
>>>>>>> +                    sizeof(pdu), &pdu);
>>>>>>> +}
>>>>>>> +
>>>>>>> +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu)
>>>>>>> +{
>>>>>>> +     if (chan->imtu > mtu)
>>>>>>> +             return -EINVAL;
>>>>>>> +
>>>>>>> +     BT_DBG("chan %p mtu 0x%4.4x", chan, mtu);
>>>>>>> +
>>>>>>> +     chan->imtu = mtu;
>>>>>>> +
>>>>>>> +     l2cap_ecred_reconfigure(chan);
>>>>>>> +
>>>>>>> +     return 0;
>>>>>>> +}
>>>>>>> +
>>>>>>> /* ---- L2CAP interface with lower layer (HCI) ---- */
>>>>>>> 
>>>>>>> int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr)
>>>>>>> @@ -7579,7 +8062,8 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
>>>>>>>                    else
>>>>>>>                            __set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
>>>>>>>            } else if (chan->state == BT_CONNECT2 &&
>>>>>>> -                        chan->mode != L2CAP_MODE_LE_FLOWCTL) {
>>>>>>> +                        !(chan->mode == L2CAP_MODE_EXT_FLOWCTL ||
>>>>>>> +                         chan->mode == L2CAP_MODE_LE_FLOWCTL)) {
>>>>>> 
>>>>>> Please double check that this line is correctly aligned after the (.
>>>>>> 
>>>>>>>                    struct l2cap_conn_rsp rsp;
>>>>>>>                    __u16 res, stat;
>>>>>>> 
>>>>>>> diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
>>>>>>> index a7be8b59b3c2..12c557f7f40f 100644
>>>>>>> --- a/net/bluetooth/l2cap_sock.c
>>>>>>> +++ b/net/bluetooth/l2cap_sock.c
>>>>>>> @@ -232,7 +232,7 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr,
>>>>>>>                    return -EINVAL;
>>>>>>>    }
>>>>>>> 
>>>>>>> -     if (chan->psm && bdaddr_type_is_le(chan->src_type))
>>>>>>> +     if (chan->psm && bdaddr_type_is_le(chan->src_type) && !chan->mode)
>>>>>>>            chan->mode = L2CAP_MODE_LE_FLOWCTL;
>>>>>>> 
>>>>>>>    err = l2cap_chan_connect(chan, la.l2_psm, __le16_to_cpu(la.l2_cid),
>>>>>>> @@ -273,6 +273,7 @@ static int l2cap_sock_listen(struct socket *sock, int backlog)
>>>>>>>    switch (chan->mode) {
>>>>>>>    case L2CAP_MODE_BASIC:
>>>>>>>    case L2CAP_MODE_LE_FLOWCTL:
>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>>>            break;
>>>>>>>    case L2CAP_MODE_ERTM:
>>>>>>>    case L2CAP_MODE_STREAMING:
>>>>>>> @@ -408,16 +409,6 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
>>>>>>> 
>>>>>>>    switch (optname) {
>>>>>>>    case L2CAP_OPTIONS:
>>>>>>> -             /* LE sockets should use BT_SNDMTU/BT_RCVMTU, but since
>>>>>>> -              * legacy ATT code depends on getsockopt for
>>>>>>> -              * L2CAP_OPTIONS we need to let this pass.
>>>>>>> -              */
>>>>>>> -             if (bdaddr_type_is_le(chan->src_type) &&
>>>>>>> -                 chan->scid != L2CAP_CID_ATT) {
>>>>>>> -                     err = -EINVAL;
>>>>>>> -                     break;
>>>>>>> -             }
>>>>>>> -
>>>>>> 
>>>>>> Should this be a separate patch. How do we keep this legacy behavior.
>>>>> 
>>>>> I kind have forgotten to fix this one, I guess we will need a new
>>>>> option in order to read the mode without using the old L2CAP_OPTIONS
>>>>> then, or do you have anything against introducing yet another option?
>>>> 
>>>> so what is the problem here? Is this because L2CAP by itself can not tell if enhanced credit mode is supported by the peer?
>>> 
>>> Yep, afaik there is no means to detect the support transparently,
>>> otherwise could always prefer to use EXT_FLOWCTL instead of
>>> LE_FLOWCTL, it seems at the point we introduced LE_FLOWCTL option we
>>> had always assumed there would not be any mode one could use when the
>>> address type is LE.
>>> 
>>>> Do you actually want to create a new mode socket options with defined values by us to allow selected the mode of operation?
>>> 
>>> I woud assume that is the only safe option here, though what I did was
>>> to enable L2CAP_OPTIONS to work with LE addresses which I assume is
>>> safe as previously it would cause an error no one would be using it.
>> 
>> that is what I was afraid of. So can we use BT_SNDMTU and BT_RCVMTU and then only a mode property is missing? Or do we have other options that we want to configure.
> 
> Not sure I follow, do you prefer chaning an existing option to add a
> new field? I though having an option to set the mode would be better
> but perhaps you want to combine it something else, well maybe some
> sort of QOS interface, the problem is that the field are quite
> different depending on the mode so Im afraid that would take a lot
> more time to define.

Let me ask this differently. What options do we want to be able to configure if we deprecate L2CAP_OPTIONS?

Anyhow, maybe we first introduce a debugfs option that says attempt_ecred_mode_first and only when the attempt fails fall back to credit based flow control mode.

Btw. do we have at least a supported features bit in L2CAP hinting that ecred mode is supported over BR/EDR. It would be good to have some way of knowing it for BR/EDR ahead of time.

>> For the multi-connect case, I was thinking that we use something similar to DEFER_SETUP. If we set a DEFER_SETUP on an outgoing socket, then you can call connect() on a socket and succeeds. However no POLLIN | POLLOUT would be signaled. When calling a connect() without that option set, then all sockets become ready at the same time on success.
> 
> As initiator I don't think we have a good match in the socket API as
> it only return one fd at time, so currently it still doing the
> connection one by one, what I though we could do is to delay a little
> to group the request in a single L2CAP request, thought that creates
> an extra latency for initiating the connection and can be racy.
> Another option would be to allow bind to bind to multiple sockets at
> once, so when you issue the connect in one socket it would connect all
> socket bound.

Let me try to explain this in pseudo code.

	fd1 = socket();
	setsockopt(fd1, BT_DEFER_SETUP);
	connect(fd1);
	0 = poll(fd1);

	fd2 = socket();
	setsockopt(fd2, BT_DEFER_SETUP);
	connect(fd2);
	0 = poll(fd2);

	fd3 = socket();
	connect(fd3);
	POLLOUT = poll(fd1);
	POLLOUT = poll(fd2);
	POLLOUT = poll(fd3);

Essentially all the connect() call with BT_DEFER_SETUP will succeed, but POLLOUT will not signal. So you have the fd, but you can not write to it. When we have the connect() without BT_DEFER_SETUP set, then L2CAP will connect all channels at the same time and assign them to the given sockets. And now all fds become POLLOUT.

>> Local CID assignment however must be done by the time connect() succeeds to allow identification. That order / indentation of L2CAP channels is another fun question. What happens if we get 4 GATT sockets, who decides what parts run where?
> 
> For GATT we keep all sockets internally as a pool of channels to use,
> for the listening socket I went with the assumption that if one of the
> sockets would fails all the remaining would fail as well, again I
> tried not to introduce artificial delays since we could get into weird
> situation where only 1 socket accepts the connection but the
> remaining, though defer setup is kind a special case already so we
> could perhaps wait for each and every socket to respond, but one of
> those takes too long and the request times out then all the other
> would time ou as well since we don't respond before that.

With the above, there would be no wait. For the incoming connections case it is no different then it is right now. The connection is ready and the accept() call just distributes the fd. Setting BT_DEFER_SETUP is the tricky one with a listening socket. But in the end, if you read() from one of the sockets all fd become active, same as you close() one, all will be closed. So you just decide once to accept or reject the connection. And since accept() call are all sequential, we could just delay signaling the other accept() until we got the read() / close() call.

	fd = socket();
	listen(fd);
	setsockopt(fd, BT_DEFER_SETUP);

	<accept incoming connection>

	fd1 = accept(fd);
	read(fd1);
	fd2 = accept(fd);
	fd3 = accept(fd);

	<reject incoming connection>

	fd1 = accept(fd);
	close(fd1);

If you do it without BT_DEFER_SETUP it is business as usual.

	fd = socket();
	listen(fd);

	fd1 = accept(fd);
	fd2 = accept(fd);
	fd3 = accept(fd);

Regards

Marcel


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] Bluetooth: L2CAP: Add initial code for Enhanced Credit Based Mode
  2020-01-27 12:10               ` Marcel Holtmann
@ 2020-02-20 23:42                 ` Luiz Augusto von Dentz
  2020-02-21  8:14                   ` Marcel Holtmann
  0 siblings, 1 reply; 14+ messages in thread
From: Luiz Augusto von Dentz @ 2020-02-20 23:42 UTC (permalink / raw)
  To: Marcel Holtmann; +Cc: linux-bluetooth

Hi Marcel,

On Mon, Jan 27, 2020 at 4:10 AM Marcel Holtmann <marcel@holtmann.org> wrote:
>
> Hi Luiz,
>
> >>>>>>> This adds the initial code for Enhanced Credit Based Mode which
> >>>>>>> introduces a new socket mode called L2CAP_MODE_EXT_FLOWCTL, which for
> >>>>>>> the most part work the same as L2CAP_MODE_LE_FLOWCTL but uses different
> >>>>>>> PDUs to setup the connections and also works over BR/EDR.
> >>>>>>>
> >>>>>>> Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
> >>>>>>> ---
> >>>>>>> include/net/bluetooth/l2cap.h |   4 +
> >>>>>>> net/bluetooth/l2cap_core.c    | 510 +++++++++++++++++++++++++++++++++-
> >>>>>>> net/bluetooth/l2cap_sock.c    |  39 +--
> >>>>>>> 3 files changed, 521 insertions(+), 32 deletions(-)
> >>>>>>>
> >>>>>>> diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
> >>>>>>> index b9ea88cfd2d2..11ae4c93354b 100644
> >>>>>>> --- a/include/net/bluetooth/l2cap.h
> >>>>>>> +++ b/include/net/bluetooth/l2cap.h
> >>>>>>> @@ -295,6 +295,8 @@ struct l2cap_conn_rsp {
> >>>>>>> #define L2CAP_CR_LE_ENCRYPTION                0x0008
> >>>>>>> #define L2CAP_CR_LE_INVALID_SCID      0x0009
> >>>>>>> #define L2CAP_CR_LE_SCID_IN_USE               0X000A
> >>>>>>> +#define L2CAP_CR_LE_UNACCEPT_PARAMS  0X000B
> >>>>>>> +#define L2CAP_CR_LE_INVALID_PARAMS   0X000C
> >>>>>>>
> >>>>>>> /* connect/create channel status */
> >>>>>>> #define L2CAP_CS_NO_INFO      0x0000
> >>>>>>> @@ -964,6 +966,7 @@ void l2cap_cleanup_sockets(void);
> >>>>>>> bool l2cap_is_socket(struct socket *sock);
> >>>>>>>
> >>>>>>> void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan);
> >>>>>>> +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan);
> >>>>>>> void __l2cap_connect_rsp_defer(struct l2cap_chan *chan);
> >>>>>>>
> >>>>>>> int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm);
> >>>>>>> @@ -973,6 +976,7 @@ struct l2cap_chan *l2cap_chan_create(void);
> >>>>>>> void l2cap_chan_close(struct l2cap_chan *chan, int reason);
> >>>>>>> int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> >>>>>>>                   bdaddr_t *dst, u8 dst_type);
> >>>>>>> +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu);
> >>>>>>> int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len);
> >>>>>>> void l2cap_chan_busy(struct l2cap_chan *chan, int busy);
> >>>>>>> int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator);
> >>>>>>> diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
> >>>>>>> index 195459a1e53e..a528526cffac 100644
> >>>>>>> --- a/net/bluetooth/l2cap_core.c
> >>>>>>> +++ b/net/bluetooth/l2cap_core.c
> >>>>>>> @@ -532,6 +532,17 @@ static void l2cap_le_flowctl_init(struct l2cap_chan *chan, u16 tx_credits)
> >>>>>>>    skb_queue_head_init(&chan->tx_q);
> >>>>>>> }
> >>>>>>>
> >>>>>>> +static void l2cap_ecred_init(struct l2cap_chan *chan, u16 tx_credits)
> >>>>>>> +{
> >>>>>>> +     l2cap_le_flowctl_init(chan, tx_credits);
> >>>>>>> +
> >>>>>>> +     /* L2CAP implementations shall support a minimum MPS of 64 octets */
> >>>>>>> +     if (chan->mps < L2CAP_ECRED_MIN_MPS) {
> >>>>>>> +             chan->mps = L2CAP_ECRED_MIN_MPS;
> >>>>>>> +             chan->rx_credits = (chan->imtu / chan->mps) + 1;
> >>>>>>> +     }
> >>>>>>> +}
> >>>>>>> +
> >>>>>>> void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
> >>>>>>> {
> >>>>>>>    BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn,
> >>>>>>> @@ -638,6 +649,7 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
> >>>>>>>            break;
> >>>>>>>
> >>>>>>>    case L2CAP_MODE_LE_FLOWCTL:
> >>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>>>>>            skb_queue_purge(&chan->tx_q);
> >>>>>>>            break;
> >>>>>>>
> >>>>>>> @@ -1260,6 +1272,17 @@ static void l2cap_move_done(struct l2cap_chan *chan)
> >>>>>>>    }
> >>>>>>> }
> >>>>>>>
> >>>>>>> +static bool l2cap_mode_ready(struct l2cap_chan *chan)
> >>>>>>> +{
> >>>>>>> +     switch (chan->mode) {
> >>>>>>> +     case L2CAP_MODE_LE_FLOWCTL:
> >>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>>>>> +             return chan->tx_credits ? true : false;
> >>>>>>> +     }
> >>>>>>> +
> >>>>>>> +     return true;
> >>>>>>> +}
> >>>>>>> +
> >>>>>>> static void l2cap_chan_ready(struct l2cap_chan *chan)
> >>>>>>> {
> >>>>>>>    /* The channel may have already been flagged as connected in
> >>>>>>> @@ -1273,7 +1296,7 @@ static void l2cap_chan_ready(struct l2cap_chan *chan)
> >>>>>>>    chan->conf_state = 0;
> >>>>>>>    __clear_chan_timer(chan);
> >>>>>>>
> >>>>>>> -     if (chan->mode == L2CAP_MODE_LE_FLOWCTL && !chan->tx_credits)
> >>>>>>> +     if (!l2cap_mode_ready(chan))
> >>>>>>>            chan->ops->suspend(chan);
> >>>>>>
> >>>>>>      switch (chan->mode) {
> >>>>>>      case ..
> >>>>>>      case ..
> >>>>>>              if (!chan->tx_credits)
> >>>>>>                      chan->ops->suspend(chan);
> >>>>>>              break;
> >>>>>>      }
> >>>>>>
> >>>>>>>
> >>>>>>>    chan->state = BT_CONNECTED;
> >>>>>>> @@ -1306,6 +1329,31 @@ static void l2cap_le_connect(struct l2cap_chan *chan)
> >>>>>>>                   sizeof(req), &req);
> >>>>>>> }
> >>>>>>>
> >>>>>>> +static void l2cap_ecred_connect(struct l2cap_chan *chan)
> >>>>>>> +{
> >>>>>>> +     struct l2cap_conn *conn = chan->conn;
> >>>>>>> +     struct {
> >>>>>>> +             struct l2cap_ecred_conn_req req;
> >>>>>>> +             __le16 scid;
> >>>>>>> +     } __packed pdu;
> >>>>>>> +
> >>>>>>> +     if (test_and_set_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags))
> >>>>>>> +             return;
> >>>>>>> +
> >>>>>>> +     l2cap_ecred_init(chan, 0);
> >>>>>>> +
> >>>>>>> +     pdu.req.psm     = chan->psm;
> >>>>>>> +     pdu.req.mtu     = cpu_to_le16(chan->imtu);
> >>>>>>> +     pdu.req.mps     = cpu_to_le16(chan->mps);
> >>>>>>> +     pdu.req.credits = cpu_to_le16(chan->rx_credits);
> >>>>>>> +     pdu.scid        = cpu_to_le16(chan->scid);
> >>>>>>> +
> >>>>>>> +     chan->ident = l2cap_get_ident(conn);
> >>>>>>> +
> >>>>>>> +     l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_CONN_REQ,
> >>>>>>> +                    sizeof(pdu), &pdu);
> >>>>>>> +}
> >>>>>>> +
> >>>>>>> static void l2cap_le_start(struct l2cap_chan *chan)
> >>>>>>> {
> >>>>>>>    struct l2cap_conn *conn = chan->conn;
> >>>>>>> @@ -1318,8 +1366,12 @@ static void l2cap_le_start(struct l2cap_chan *chan)
> >>>>>>>            return;
> >>>>>>>    }
> >>>>>>>
> >>>>>>> -     if (chan->state == BT_CONNECT)
> >>>>>>> -             l2cap_le_connect(chan);
> >>>>>>> +     if (chan->state == BT_CONNECT) {
> >>>>>>> +             if (chan->mode == L2CAP_MODE_EXT_FLOWCTL)
> >>>>>>> +                     l2cap_ecred_connect(chan);
> >>>>>>> +             else
> >>>>>>> +                     l2cap_le_connect(chan);
> >>>>>>> +     }
> >>>>>>> }
> >>>>>>>
> >>>>>>> static void l2cap_start_connection(struct l2cap_chan *chan)
> >>>>>>> @@ -2505,6 +2557,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
> >>>>>>>
> >>>>>>>    switch (chan->mode) {
> >>>>>>>    case L2CAP_MODE_LE_FLOWCTL:
> >>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>>>>>            /* Check outgoing MTU */
> >>>>>>>            if (len > chan->omtu)
> >>>>>>>                    return -EMSGSIZE;
> >>>>>>> @@ -3773,6 +3826,42 @@ void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan)
> >>>>>>>                   &rsp);
> >>>>>>> }
> >>>>>>>
> >>>>>>> +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
> >>>>>>> +{
> >>>>>>> +     struct l2cap_ecred_conn_rsp rsp;
> >>>>>>> +     struct l2cap_conn *conn = chan->conn;
> >>>>>>> +     u16 ident = chan->ident;
> >>>>>>> +     int i = 0;
> >>>>>>> +
> >>>>>>> +     if (!ident) {
> >>>>>>> +             return;
> >>>>>>> +     }
> >>>>>>> +
> >>>>>>
> >>>>>> No { } here.
> >>>>>>
> >>>>>>> +     BT_DBG("chan %p ident %d", chan, ident);
> >>>>>>> +
> >>>>>>> +     rsp.mtu     = cpu_to_le16(chan->imtu);
> >>>>>>> +     rsp.mps     = cpu_to_le16(chan->mps);
> >>>>>>> +     rsp.credits = cpu_to_le16(chan->rx_credits);
> >>>>>>> +     rsp.result  = cpu_to_le16(L2CAP_CR_LE_SUCCESS);
> >>>>>>> +
> >>>>>>> +     mutex_lock(&conn->chan_lock);
> >>>>>>> +
> >>>>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
> >>>>>>> +             if (chan->ident != ident)
> >>>>>>> +                     continue;
> >>>>>>> +
> >>>>>>> +             /* Reset ident so only one response is sent */
> >>>>>>> +             chan->ident = 0;
> >>>>>>> +
> >>>>>>> +             /* Include all channels pending with the same ident */
> >>>>>>> +             rsp.dcid[i++] = cpu_to_le16(chan->scid);
> >>>>>>
> >>>>>> This doesn’t work. The sizeof(rsp) has no dcid in there. You are overflowing into conn struct here.
> >>>>>>
> >>>>>>> +     }
> >>>>>>> +
> >>>>>>> +     mutex_unlock(&conn->chan_lock);
> >>>>>>> +
> >>>>>>> +     l2cap_send_cmd(conn, ident, L2CAP_ECRED_CONN_RSP, sizeof(rsp), &rsp);
> >>>>>>> +}
> >>>>>>> +
> >>>>>>> void __l2cap_connect_rsp_defer(struct l2cap_chan *chan)
> >>>>>>> {
> >>>>>>>    struct l2cap_conn_rsp rsp;
> >>>>>>> @@ -5714,6 +5803,347 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn,
> >>>>>>>    return 0;
> >>>>>>> }
> >>>>>>>
> >>>>>>> +static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
> >>>>>>> +                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >>>>>>> +                                    u8 *data)
> >>>>>>> +{
> >>>>>>> +     struct l2cap_ecred_conn_req *req = (void *) data;
> >>>>>>> +     struct {
> >>>>>>> +             struct l2cap_ecred_conn_rsp rsp;
> >>>>>>> +             __le16 dcid[5];
> >>>>>>> +     } __packed pdu;
> >>>>>>> +     struct l2cap_chan *chan, *pchan;
> >>>>>>> +     u16 credits, mtu, mps;
> >>>>>>> +     __le16 psm;
> >>>>>>> +     u8 result, len = 0;
> >>>>>>> +     int i;
> >>>>>>> +     bool defer = false;
> >>>>>>> +
> >>>>>>> +     if (cmd_len < sizeof(*req))
> >>>>>>> +             return -EPROTO;
> >>>>>>> +
> >>>>>>> +     mtu  = __le16_to_cpu(req->mtu);
> >>>>>>> +     mps  = __le16_to_cpu(req->mps);
> >>>>>>> +
> >>>>>>> +     if (mtu < L2CAP_ECRED_MIN_MTU || mps < L2CAP_ECRED_MIN_MPS) {
> >>>>>>> +             result = L2CAP_CR_LE_UNACCEPT_PARAMS;
> >>>>>>> +             goto response;
> >>>>>>> +     }
> >>>>>>> +
> >>>>>>> +     psm  = req->psm;
> >>>>>>> +     credits = 0;
> >>>>>>> +
> >>>>>>> +     BT_DBG("psm 0x%2.2x mtu %u mps %u", __le16_to_cpu(psm), mtu, mps);
> >>>>>>> +
> >>>>>>> +     memset(&pdu, 0, sizeof(pdu));
> >>>>>>> +
> >>>>>>> +     /* Check if we have socket listening on psm */
> >>>>>>> +     pchan = l2cap_global_chan_by_psm(BT_LISTEN, psm, &conn->hcon->src,
> >>>>>>> +                                      &conn->hcon->dst, LE_LINK);
> >>>>>>> +     if (!pchan) {
> >>>>>>> +             result = L2CAP_CR_LE_BAD_PSM;
> >>>>>>> +             goto response;
> >>>>>>> +     }
> >>>>>>> +
> >>>>>>> +     mutex_lock(&conn->chan_lock);
> >>>>>>> +     l2cap_chan_lock(pchan);
> >>>>>>> +
> >>>>>>> +     if (!smp_sufficient_security(conn->hcon, pchan->sec_level,
> >>>>>>> +                                  SMP_ALLOW_STK)) {
> >>>>>>> +             result = L2CAP_CR_LE_AUTHENTICATION;
> >>>>>>> +             goto unlock;
> >>>>>>> +     }
> >>>>>>> +
> >>>>>>> +     result = L2CAP_CR_LE_SUCCESS;
> >>>>>>> +
> >>>>>>> +     for (i = 0, cmd_len -= sizeof(req); cmd_len >= sizeof(u16);
> >>>>>>> +          i++, cmd_len -= sizeof(u16)) {
> >>>>>>
> >>>>>> Can we simplify the loop statement or switch to while statement?
> >>>>>>
> >>>>>>> +             u16 scid = __le16_to_cpu(req->scid[i]);
> >>>>>>> +
> >>>>>>> +             BT_DBG("scid[%d] 0x%4.4x", i, scid);
> >>>>>>> +
> >>>>>>> +             pdu.dcid[i] = 0x0000;
> >>>>>>> +             len += sizeof(*pdu.dcid);
> >>>>>>> +
> >>>>>>> +             /* Check for valid dynamic CID range */
> >>>>>>> +             if (scid < L2CAP_CID_DYN_START || scid > L2CAP_CID_LE_DYN_END) {
> >>>>>>> +                     result = L2CAP_CR_LE_INVALID_SCID;
> >>>>>>> +                     continue;
> >>>>>>> +             }
> >>>>>>> +
> >>>>>>> +             /* Check if we already have channel with that dcid */
> >>>>>>> +             if (__l2cap_get_chan_by_dcid(conn, scid)) {
> >>>>>>> +                     result = L2CAP_CR_LE_SCID_IN_USE;
> >>>>>>> +                     continue;
> >>>>>>> +             }
> >>>>>>> +
> >>>>>>> +             chan = pchan->ops->new_connection(pchan);
> >>>>>>> +             if (!chan) {
> >>>>>>> +                     result = L2CAP_CR_LE_NO_MEM;
> >>>>>>> +                     continue;
> >>>>>>> +             }
> >>>>>>> +
> >>>>>>> +             bacpy(&chan->src, &conn->hcon->src);
> >>>>>>> +             bacpy(&chan->dst, &conn->hcon->dst);
> >>>>>>> +             chan->src_type = bdaddr_src_type(conn->hcon);
> >>>>>>> +             chan->dst_type = bdaddr_dst_type(conn->hcon);
> >>>>>>> +             chan->psm  = psm;
> >>>>>>> +             chan->dcid = scid;
> >>>>>>> +             chan->omtu = mtu;
> >>>>>>> +             chan->remote_mps = mps;
> >>>>>>> +
> >>>>>>> +             __l2cap_chan_add(conn, chan);
> >>>>>>> +
> >>>>>>> +             l2cap_ecred_init(chan, __le16_to_cpu(req->credits));
> >>>>>>> +
> >>>>>>> +             /* Init response */
> >>>>>>> +             if (!pdu.rsp.credits) {
> >>>>>>> +                     pdu.rsp.mtu = cpu_to_le16(chan->imtu);
> >>>>>>> +                     pdu.rsp.mps = cpu_to_le16(chan->mps);
> >>>>>>> +                     pdu.rsp.credits = cpu_to_le16(chan->rx_credits);
> >>>>>>> +             }
> >>>>>>> +
> >>>>>>> +             pdu.dcid[i] = cpu_to_le16(chan->scid);
> >>>>>>> +
> >>>>>>> +             __set_chan_timer(chan, chan->ops->get_sndtimeo(chan));
> >>>>>>> +
> >>>>>>> +             chan->ident = cmd->ident;
> >>>>>>> +
> >>>>>>> +             if (test_bit(FLAG_DEFER_SETUP, &chan->flags)) {
> >>>>>>> +                     l2cap_state_change(chan, BT_CONNECT2);
> >>>>>>> +                     defer = true;
> >>>>>>> +                     chan->ops->defer(chan);
> >>>>>>> +             } else {
> >>>>>>> +                     l2cap_chan_ready(chan);
> >>>>>>> +             }
> >>>>>>> +     }
> >>>>>>> +
> >>>>>>> +unlock:
> >>>>>>> +     l2cap_chan_unlock(pchan);
> >>>>>>> +     mutex_unlock(&conn->chan_lock);
> >>>>>>> +     l2cap_chan_put(pchan);
> >>>>>>> +
> >>>>>>> +response:
> >>>>>>> +     pdu.rsp.result = cpu_to_le16(result);
> >>>>>>> +
> >>>>>>> +     if (defer)
> >>>>>>> +             return 0;
> >>>>>>> +
> >>>>>>> +     l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_CONN_RSP,
> >>>>>>> +                    sizeof(pdu.rsp) + len, &pdu);
> >>>>>>> +
> >>>>>>> +     return 0;
> >>>>>>> +}
> >>>>>>> +
> >>>>>>> +static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
> >>>>>>> +                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >>>>>>> +                                    u8 *data)
> >>>>>>> +{
> >>>>>>> +     struct l2cap_ecred_conn_rsp *rsp = (void *) data;
> >>>>>>> +     struct hci_conn *hcon = conn->hcon;
> >>>>>>> +     u16 mtu, mps, credits, result;
> >>>>>>> +     struct l2cap_chan *chan;
> >>>>>>> +     int err = 0, sec_level;
> >>>>>>> +     int i = 0;
> >>>>>>> +
> >>>>>>> +     if (cmd_len < sizeof(*rsp))
> >>>>>>> +             return -EPROTO;
> >>>>>>> +
> >>>>>>> +     mtu     = __le16_to_cpu(rsp->mtu);
> >>>>>>> +     mps     = __le16_to_cpu(rsp->mps);
> >>>>>>> +     credits = __le16_to_cpu(rsp->credits);
> >>>>>>> +     result  = __le16_to_cpu(rsp->result);
> >>>>>>> +
> >>>>>>> +     BT_DBG("mtu %u mps %u credits %u result 0x%4.4x", mtu, mps, credits,
> >>>>>>> +            result);
> >>>>>>> +
> >>>>>>> +     mutex_lock(&conn->chan_lock);
> >>>>>>> +
> >>>>>>> +     cmd_len -= sizeof(*rsp);
> >>>>>>> +
> >>>>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
> >>>>>>> +             u16 dcid;
> >>>>>>> +
> >>>>>>> +             if (chan->ident != cmd->ident ||
> >>>>>>> +                 chan->mode != L2CAP_MODE_EXT_FLOWCTL ||
> >>>>>>> +                 chan->state == BT_CONNECTED)
> >>>>>>> +                     continue;
> >>>>>>> +
> >>>>>>> +             l2cap_chan_lock(chan);
> >>>>>>> +
> >>>>>>> +             /* Check that there is a dcid for each pending channel */
> >>>>>>> +             if (cmd_len < sizeof(dcid)) {
> >>>>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
> >>>>>>> +                     l2cap_chan_unlock(chan);
> >>>>>>> +                     continue;
> >>>>>>> +             }
> >>>>>>> +
> >>>>>>> +             dcid = __le16_to_cpu(rsp->dcid[i++]);
> >>>>>>> +             cmd_len -= sizeof(u16);
> >>>>>>> +
> >>>>>>> +             BT_DBG("dcid[%d] 0x%4.4x", i, dcid);
> >>>>>>> +
> >>>>>>> +             /* Check if dcid is already in use */
> >>>>>>> +             if (dcid && __l2cap_get_chan_by_dcid(conn, dcid)) {
> >>>>>>> +                     /* If a device receives a
> >>>>>>> +                      * L2CAP_CREDIT_BASED_CONNECTION_RSP packet with an
> >>>>>>> +                      * already-assigned Destination CID, then both the
> >>>>>>> +                      * original channel and the new channel shall be
> >>>>>>> +                      * immediately discarded and not used.
> >>>>>>> +                      */
> >>>>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
> >>>>>>> +                     l2cap_chan_unlock(chan);
> >>>>>>> +                     chan = __l2cap_get_chan_by_dcid(conn, dcid);
> >>>>>>> +                     l2cap_chan_lock(chan);
> >>>>>>> +                     l2cap_chan_del(chan, ECONNRESET);
> >>>>>>> +                     l2cap_chan_unlock(chan);
> >>>>>>> +                     continue;
> >>>>>>> +             }
> >>>>>>> +
> >>>>>>> +             switch (result) {
> >>>>>>> +             case L2CAP_CR_LE_AUTHENTICATION:
> >>>>>>> +             case L2CAP_CR_LE_ENCRYPTION:
> >>>>>>> +                     /* If we already have MITM protection we can't do
> >>>>>>> +                      * anything.
> >>>>>>> +                      */
> >>>>>>> +                     if (hcon->sec_level > BT_SECURITY_MEDIUM) {
> >>>>>>> +                             l2cap_chan_del(chan, ECONNREFUSED);
> >>>>>>> +                             break;
> >>>>>>> +                     }
> >>>>>>> +
> >>>>>>> +                     sec_level = hcon->sec_level + 1;
> >>>>>>> +                     if (chan->sec_level < sec_level)
> >>>>>>> +                             chan->sec_level = sec_level;
> >>>>>>> +
> >>>>>>> +                     /* We'll need to send a new Connect Request */
> >>>>>>> +                     clear_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags);
> >>>>>>> +
> >>>>>>> +                     smp_conn_security(hcon, chan->sec_level);
> >>>>>>> +                     break;
> >>>>>>> +
> >>>>>>> +             case L2CAP_CR_LE_BAD_PSM:
> >>>>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
> >>>>>>> +                     break;
> >>>>>>> +
> >>>>>>> +             default:
> >>>>>>> +                     /* If dcid was not set it means channels was refused */
> >>>>>>> +                     if (!dcid) {
> >>>>>>> +                             l2cap_chan_del(chan, ECONNREFUSED);
> >>>>>>> +                             break;
> >>>>>>> +                     }
> >>>>>>> +
> >>>>>>> +                     chan->ident = 0;
> >>>>>>> +                     chan->dcid = dcid;
> >>>>>>> +                     chan->omtu = mtu;
> >>>>>>> +                     chan->remote_mps = mps;
> >>>>>>> +                     chan->tx_credits = credits;
> >>>>>>> +                     l2cap_chan_ready(chan);
> >>>>>>> +                     break;
> >>>>>>> +             }
> >>>>>>> +
> >>>>>>> +             l2cap_chan_unlock(chan);
> >>>>>>> +     }
> >>>>>>> +
> >>>>>>> +     mutex_unlock(&conn->chan_lock);
> >>>>>>> +
> >>>>>>> +     return err;
> >>>>>>> +}
> >>>>>>> +
> >>>>>>> +static inline int l2cap_ecred_reconf_req(struct l2cap_conn *conn,
> >>>>>>> +                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >>>>>>> +                                      u8 *data)
> >>>>>>> +{
> >>>>>>> +     struct l2cap_ecred_reconf_req *req = (void *) data;
> >>>>>>> +     struct l2cap_ecred_reconf_rsp rsp;
> >>>>>>> +     u16 mtu, mps, result;
> >>>>>>> +     struct l2cap_chan *chan;
> >>>>>>> +     int i;
> >>>>>>> +
> >>>>>>> +     if (cmd_len < sizeof(*req)) {
> >>>>>>> +             result = L2CAP_CR_LE_INVALID_PARAMS;
> >>>>>>> +             goto respond;
> >>>>>>> +     }
> >>>>>>> +
> >>>>>>> +     mtu = __le16_to_cpu(req->mtu);
> >>>>>>> +     mps = __le16_to_cpu(req->mps);
> >>>>>>> +
> >>>>>>> +     BT_DBG("mtu %u mps %u", mtu, mps);
> >>>>>>> +
> >>>>>>> +     if (mtu < L2CAP_ECRED_MIN_MTU) {
> >>>>>>> +             result = L2CAP_RECONF_INVALID_MTU;
> >>>>>>> +             goto respond;
> >>>>>>> +     }
> >>>>>>> +
> >>>>>>> +     if (mps < L2CAP_ECRED_MIN_MPS) {
> >>>>>>> +             result = L2CAP_RECONF_INVALID_MPS;
> >>>>>>> +             goto respond;
> >>>>>>> +     }
> >>>>>>> +
> >>>>>>> +     result = L2CAP_RECONF_SUCCESS;
> >>>>>>> +
> >>>>>>> +     for (i = 0, cmd_len -= sizeof(*req); cmd_len < sizeof(u16);
> >>>>>>> +          i++, cmd_len -= sizeof(u16)) {
> >>>>>>
> >>>>>> Same comment as above.
> >>>>>>
> >>>>>>> +             u16 scid;
> >>>>>>> +
> >>>>>>> +             scid = __le16_to_cpu(req->scid[i]);
> >>>>>>> +             if (!scid)
> >>>>>>> +                     return -EPROTO;
> >>>>>>> +
> >>>>>>> +             chan = __l2cap_get_chan_by_dcid(conn, scid);
> >>>>>>> +             if (!chan)
> >>>>>>> +                     continue;
> >>>>>>> +
> >>>>>>> +             /* If the MTU value is decreased for any of the included
> >>>>>>> +              * channels, then the receiver shall disconnect all
> >>>>>>> +              * included channels.
> >>>>>>> +              */
> >>>>>>> +             if (chan->omtu > mtu) {
> >>>>>>> +                     BT_ERR("chan %p decreased MTU %u -> %u", chan,
> >>>>>>> +                            chan->omtu, mtu);
> >>>>>>> +                     result = L2CAP_RECONF_INVALID_MTU;
> >>>>>>> +             }
> >>>>>>> +
> >>>>>>> +             chan->omtu = mtu;
> >>>>>>> +             chan->remote_mps = mps;
> >>>>>>> +     }
> >>>>>>> +
> >>>>>>> +respond:
> >>>>>>> +     rsp.result = cpu_to_le16(result);
> >>>>>>> +
> >>>>>>> +     l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_RECONF_RSP, sizeof(rsp),
> >>>>>>> +                    &rsp);
> >>>>>>> +
> >>>>>>> +     return 0;
> >>>>>>> +}
> >>>>>>> +
> >>>>>>> +static inline int l2cap_ecred_reconf_rsp(struct l2cap_conn *conn,
> >>>>>>> +                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >>>>>>> +                                      u8 *data)
> >>>>>>> +{
> >>>>>>> +     struct l2cap_chan *chan;
> >>>>>>> +     struct l2cap_ecred_conn_rsp *rsp = (void *) data;
> >>>>>>> +     u16 result;
> >>>>>>> +
> >>>>>>> +     if (cmd_len < sizeof(*rsp))
> >>>>>>> +             return -EPROTO;
> >>>>>>> +
> >>>>>>> +     result = __le16_to_cpu(rsp->result);
> >>>>>>> +
> >>>>>>> +     BT_DBG("result 0x%4.4x", rsp->result);
> >>>>>>> +
> >>>>>>> +     if (!result)
> >>>>>>> +             return 0;
> >>>>>>> +
> >>>>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
> >>>>>>> +             if (chan->ident != cmd->ident)
> >>>>>>> +                     continue;
> >>>>>>> +
> >>>>>>> +             l2cap_chan_del(chan, ECONNRESET);
> >>>>>>> +     }
> >>>>>>> +
> >>>>>>> +     return 0;
> >>>>>>> +}
> >>>>>>> +
> >>>>>>> static inline int l2cap_le_command_rej(struct l2cap_conn *conn,
> >>>>>>>                                   struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >>>>>>>                                   u8 *data)
> >>>>>>> @@ -5769,6 +6199,22 @@ static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn,
> >>>>>>>            err = l2cap_le_credits(conn, cmd, cmd_len, data);
> >>>>>>>            break;
> >>>>>>>
> >>>>>>> +     case L2CAP_ECRED_CONN_REQ:
> >>>>>>> +             err = l2cap_ecred_conn_req(conn, cmd, cmd_len, data);
> >>>>>>> +             break;
> >>>>>>> +
> >>>>>>> +     case L2CAP_ECRED_CONN_RSP:
> >>>>>>> +             err = l2cap_ecred_conn_rsp(conn, cmd, cmd_len, data);
> >>>>>>> +             break;
> >>>>>>> +
> >>>>>>> +     case L2CAP_ECRED_RECONF_REQ:
> >>>>>>> +             err = l2cap_ecred_reconf_req(conn, cmd, cmd_len, data);
> >>>>>>> +             break;
> >>>>>>> +
> >>>>>>> +     case L2CAP_ECRED_RECONF_RSP:
> >>>>>>> +             err = l2cap_ecred_reconf_rsp(conn, cmd, cmd_len, data);
> >>>>>>> +             break;
> >>>>>>> +
> >>>>>>>    case L2CAP_DISCONN_REQ:
> >>>>>>>            err = l2cap_disconnect_req(conn, cmd, cmd_len, data);
> >>>>>>>            break;
> >>>>>>> @@ -6814,11 +7260,13 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
> >>>>>>>    struct l2cap_le_credits pkt;
> >>>>>>>    u16 return_credits;
> >>>>>>>
> >>>>>>> -     return_credits = ((chan->imtu / chan->mps) + 1) - chan->rx_credits;
> >>>>>>> +     return_credits = (chan->imtu / chan->mps) + 1;
> >>>>>>>
> >>>>>>> -     if (!return_credits)
> >>>>>>> +     if (chan->rx_credits >= return_credits)
> >>>>>>>            return;
> >>>>>>>
> >>>>>>> +     return_credits -= chan->rx_credits;
> >>>>>>> +
> >>>>>>>    BT_DBG("chan %p returning %u credits to sender", chan, return_credits);
> >>>>>>>
> >>>>>>>    chan->rx_credits += return_credits;
> >>>>>>> @@ -6831,7 +7279,7 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
> >>>>>>>    l2cap_send_cmd(conn, chan->ident, L2CAP_LE_CREDITS, sizeof(pkt), &pkt);
> >>>>>>> }
> >>>>>>>
> >>>>>>> -static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>>>> +static int l2cap_ecred_recv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>>>> {
> >>>>>>>    int err;
> >>>>>>>
> >>>>>>> @@ -6846,7 +7294,7 @@ static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>>>>    return err;
> >>>>>>> }
> >>>>>>>
> >>>>>>> -static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>>>> +static int l2cap_ecred_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>>>> {
> >>>>>>>    int err;
> >>>>>>>
> >>>>>>> @@ -6894,7 +7342,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>>>>            }
> >>>>>>>
> >>>>>>>            if (skb->len == sdu_len)
> >>>>>>> -                     return l2cap_le_recv(chan, skb);
> >>>>>>> +                     return l2cap_ecred_recv(chan, skb);
> >>>>>>>
> >>>>>>>            chan->sdu = skb;
> >>>>>>>            chan->sdu_len = sdu_len;
> >>>>>>> @@ -6926,7 +7374,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>>>>    skb = NULL;
> >>>>>>>
> >>>>>>>    if (chan->sdu->len == chan->sdu_len) {
> >>>>>>> -             err = l2cap_le_recv(chan, chan->sdu);
> >>>>>>> +             err = l2cap_ecred_recv(chan, chan->sdu);
> >>>>>>>            if (!err) {
> >>>>>>>                    chan->sdu = NULL;
> >>>>>>>                    chan->sdu_last_frag = NULL;
> >>>>>>> @@ -6987,7 +7435,8 @@ static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid,
> >>>>>>>
> >>>>>>>    switch (chan->mode) {
> >>>>>>>    case L2CAP_MODE_LE_FLOWCTL:
> >>>>>>> -             if (l2cap_le_data_rcv(chan, skb) < 0)
> >>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>>>>> +             if (l2cap_ecred_data_rcv(chan, skb) < 0)
> >>>>>>>                    goto drop;
> >>>>>>>
> >>>>>>>            goto done;
> >>>>>>> @@ -7214,8 +7663,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> >>>>>>>    struct hci_dev *hdev;
> >>>>>>>    int err;
> >>>>>>>
> >>>>>>> -     BT_DBG("%pMR -> %pMR (type %u) psm 0x%2.2x", &chan->src, dst,
> >>>>>>> -            dst_type, __le16_to_cpu(psm));
> >>>>>>> +     BT_DBG("%pMR -> %pMR (type %u) psm 0x%4.4x mode 0x%2.2x", &chan->src,
> >>>>>>> +            dst, dst_type, __le16_to_cpu(psm), chan->mode);
> >>>>>>>
> >>>>>>>    hdev = hci_get_route(dst, &chan->src, chan->src_type);
> >>>>>>>    if (!hdev)
> >>>>>>> @@ -7244,6 +7693,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> >>>>>>>            break;
> >>>>>>>    case L2CAP_MODE_LE_FLOWCTL:
> >>>>>>>            break;
> >>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>>>>> +             break;
> >>>>>>
> >>>>>> No need to do another break. Just put it under LE_FLOWCTL.
> >>>>>>
> >>>>>>>    case L2CAP_MODE_ERTM:
> >>>>>>>    case L2CAP_MODE_STREAMING:
> >>>>>>>            if (!disable_ertm)
> >>>>>>> @@ -7368,6 +7819,38 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> >>>>>>> }
> >>>>>>> EXPORT_SYMBOL_GPL(l2cap_chan_connect);
> >>>>>>>
> >>>>>>> +static void l2cap_ecred_reconfigure(struct l2cap_chan *chan)
> >>>>>>> +{
> >>>>>>> +     struct l2cap_conn *conn = chan->conn;
> >>>>>>> +     struct {
> >>>>>>> +             struct l2cap_ecred_reconf_req req;
> >>>>>>> +             __le16 scid;
> >>>>>>> +     } pdu;
> >>>>>>> +
> >>>>>>> +     pdu.req.mtu = cpu_to_le16(chan->imtu);
> >>>>>>> +     pdu.req.mps = cpu_to_le16(chan->mps);
> >>>>>>> +     pdu.scid    = cpu_to_le16(chan->scid);
> >>>>>>> +
> >>>>>>> +     chan->ident = l2cap_get_ident(conn);
> >>>>>>> +
> >>>>>>> +     l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_RECONF_REQ,
> >>>>>>> +                    sizeof(pdu), &pdu);
> >>>>>>> +}
> >>>>>>> +
> >>>>>>> +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu)
> >>>>>>> +{
> >>>>>>> +     if (chan->imtu > mtu)
> >>>>>>> +             return -EINVAL;
> >>>>>>> +
> >>>>>>> +     BT_DBG("chan %p mtu 0x%4.4x", chan, mtu);
> >>>>>>> +
> >>>>>>> +     chan->imtu = mtu;
> >>>>>>> +
> >>>>>>> +     l2cap_ecred_reconfigure(chan);
> >>>>>>> +
> >>>>>>> +     return 0;
> >>>>>>> +}
> >>>>>>> +
> >>>>>>> /* ---- L2CAP interface with lower layer (HCI) ---- */
> >>>>>>>
> >>>>>>> int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr)
> >>>>>>> @@ -7579,7 +8062,8 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
> >>>>>>>                    else
> >>>>>>>                            __set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
> >>>>>>>            } else if (chan->state == BT_CONNECT2 &&
> >>>>>>> -                        chan->mode != L2CAP_MODE_LE_FLOWCTL) {
> >>>>>>> +                        !(chan->mode == L2CAP_MODE_EXT_FLOWCTL ||
> >>>>>>> +                         chan->mode == L2CAP_MODE_LE_FLOWCTL)) {
> >>>>>>
> >>>>>> Please double check that this line is correctly aligned after the (.
> >>>>>>
> >>>>>>>                    struct l2cap_conn_rsp rsp;
> >>>>>>>                    __u16 res, stat;
> >>>>>>>
> >>>>>>> diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
> >>>>>>> index a7be8b59b3c2..12c557f7f40f 100644
> >>>>>>> --- a/net/bluetooth/l2cap_sock.c
> >>>>>>> +++ b/net/bluetooth/l2cap_sock.c
> >>>>>>> @@ -232,7 +232,7 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr,
> >>>>>>>                    return -EINVAL;
> >>>>>>>    }
> >>>>>>>
> >>>>>>> -     if (chan->psm && bdaddr_type_is_le(chan->src_type))
> >>>>>>> +     if (chan->psm && bdaddr_type_is_le(chan->src_type) && !chan->mode)
> >>>>>>>            chan->mode = L2CAP_MODE_LE_FLOWCTL;
> >>>>>>>
> >>>>>>>    err = l2cap_chan_connect(chan, la.l2_psm, __le16_to_cpu(la.l2_cid),
> >>>>>>> @@ -273,6 +273,7 @@ static int l2cap_sock_listen(struct socket *sock, int backlog)
> >>>>>>>    switch (chan->mode) {
> >>>>>>>    case L2CAP_MODE_BASIC:
> >>>>>>>    case L2CAP_MODE_LE_FLOWCTL:
> >>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>>>>>            break;
> >>>>>>>    case L2CAP_MODE_ERTM:
> >>>>>>>    case L2CAP_MODE_STREAMING:
> >>>>>>> @@ -408,16 +409,6 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
> >>>>>>>
> >>>>>>>    switch (optname) {
> >>>>>>>    case L2CAP_OPTIONS:
> >>>>>>> -             /* LE sockets should use BT_SNDMTU/BT_RCVMTU, but since
> >>>>>>> -              * legacy ATT code depends on getsockopt for
> >>>>>>> -              * L2CAP_OPTIONS we need to let this pass.
> >>>>>>> -              */
> >>>>>>> -             if (bdaddr_type_is_le(chan->src_type) &&
> >>>>>>> -                 chan->scid != L2CAP_CID_ATT) {
> >>>>>>> -                     err = -EINVAL;
> >>>>>>> -                     break;
> >>>>>>> -             }
> >>>>>>> -
> >>>>>>
> >>>>>> Should this be a separate patch. How do we keep this legacy behavior.
> >>>>>
> >>>>> I kind have forgotten to fix this one, I guess we will need a new
> >>>>> option in order to read the mode without using the old L2CAP_OPTIONS
> >>>>> then, or do you have anything against introducing yet another option?
> >>>>
> >>>> so what is the problem here? Is this because L2CAP by itself can not tell if enhanced credit mode is supported by the peer?
> >>>
> >>> Yep, afaik there is no means to detect the support transparently,
> >>> otherwise could always prefer to use EXT_FLOWCTL instead of
> >>> LE_FLOWCTL, it seems at the point we introduced LE_FLOWCTL option we
> >>> had always assumed there would not be any mode one could use when the
> >>> address type is LE.
> >>>
> >>>> Do you actually want to create a new mode socket options with defined values by us to allow selected the mode of operation?
> >>>
> >>> I woud assume that is the only safe option here, though what I did was
> >>> to enable L2CAP_OPTIONS to work with LE addresses which I assume is
> >>> safe as previously it would cause an error no one would be using it.
> >>
> >> that is what I was afraid of. So can we use BT_SNDMTU and BT_RCVMTU and then only a mode property is missing? Or do we have other options that we want to configure.
> >
> > Not sure I follow, do you prefer chaning an existing option to add a
> > new field? I though having an option to set the mode would be better
> > but perhaps you want to combine it something else, well maybe some
> > sort of QOS interface, the problem is that the field are quite
> > different depending on the mode so Im afraid that would take a lot
> > more time to define.
>
> Let me ask this differently. What options do we want to be able to configure if we deprecate L2CAP_OPTIONS?

Coming back to this perhaps we could infer the mode if in case there
are multiple connection being made, the only question is if the spec
actually allow connection with the lagacy command if the remote side
is listen on PSM that is for ECRED mode like EATT, if it does require
maybe we can use BT_DEFER_SETUP to trigger it but I don't really like
reusing BT_DEFER_SETUP like that since it was introduced for a very
different purpose it may confuse people.

> Anyhow, maybe we first introduce a debugfs option that says attempt_ecred_mode_first and only when the attempt fails fall back to credit based flow control mode.

I was thinking in something like the following so we only use the new
mode if it is enabled, well provided we can remove it later:

https://gist.github.com/Vudentz/35ca4a39b58e64e8ecbde3ff6ada2d61

> Btw. do we have at least a supported features bit in L2CAP hinting that ecred mode is supported over BR/EDR. It would be good to have some way of knowing it for BR/EDR ahead of time.

I don't remember it being added, but yes it would make a lot of sense
to have it when using over BR/EDR.

> >> For the multi-connect case, I was thinking that we use something similar to DEFER_SETUP. If we set a DEFER_SETUP on an outgoing socket, then you can call connect() on a socket and succeeds. However no POLLIN | POLLOUT would be signaled. When calling a connect() without that option set, then all sockets become ready at the same time on success.
> >
> > As initiator I don't think we have a good match in the socket API as
> > it only return one fd at time, so currently it still doing the
> > connection one by one, what I though we could do is to delay a little
> > to group the request in a single L2CAP request, thought that creates
> > an extra latency for initiating the connection and can be racy.
> > Another option would be to allow bind to bind to multiple sockets at
> > once, so when you issue the connect in one socket it would connect all
> > socket bound.
>
> Let me try to explain this in pseudo code.
>
>         fd1 = socket();
>         setsockopt(fd1, BT_DEFER_SETUP);
>         connect(fd1);
>         0 = poll(fd1);
>
>         fd2 = socket();
>         setsockopt(fd2, BT_DEFER_SETUP);
>         connect(fd2);
>         0 = poll(fd2);
>
>         fd3 = socket();
>         connect(fd3);
>         POLLOUT = poll(fd1);
>         POLLOUT = poll(fd2);
>         POLLOUT = poll(fd3);
>
> Essentially all the connect() call with BT_DEFER_SETUP will succeed, but POLLOUT will not signal. So you have the fd, but you can not write to it. When we have the connect() without BT_DEFER_SETUP set, then L2CAP will connect all channels at the same time and assign them to the given sockets. And now all fds become POLLOUT.

Should work, btw couldn't we just use bind for the first 2 sockets?
That imo would make things a little cleaner since we only do connect
when it is really ready to connect and we don't have to even consider
poll until then, that said we would need to group these sockets either
way so we need to do this per processs, actually the order would be:

fd# = socket()
bind(fd#)
setsockopt(fd#, BT_DEFER_SETUP)

Since bind sets the address then on setsockopt we can check if there
is anything else pending to be connected for the same address and then
just proceed.

> >> Local CID assignment however must be done by the time connect() succeeds to allow identification. That order / indentation of L2CAP channels is another fun question. What happens if we get 4 GATT sockets, who decides what parts run where?
> >
> > For GATT we keep all sockets internally as a pool of channels to use,
> > for the listening socket I went with the assumption that if one of the
> > sockets would fails all the remaining would fail as well, again I
> > tried not to introduce artificial delays since we could get into weird
> > situation where only 1 socket accepts the connection but the
> > remaining, though defer setup is kind a special case already so we
> > could perhaps wait for each and every socket to respond, but one of
> > those takes too long and the request times out then all the other
> > would time ou as well since we don't respond before that.
>
> With the above, there would be no wait. For the incoming connections case it is no different then it is right now. The connection is ready and the accept() call just distributes the fd. Setting BT_DEFER_SETUP is the tricky one with a listening socket. But in the end, if you read() from one of the sockets all fd become active, same as you close() one, all will be closed. So you just decide once to accept or reject the connection. And since accept() call are all sequential, we could just delay signaling the other accept() until we got the read() / close() call.
>
>         fd = socket();
>         listen(fd);
>         setsockopt(fd, BT_DEFER_SETUP);
>
>         <accept incoming connection>
>
>         fd1 = accept(fd);
>         read(fd1);
>         fd2 = accept(fd);
>         fd3 = accept(fd);
>
>         <reject incoming connection>
>
>         fd1 = accept(fd);
>         close(fd1);
>
> If you do it without BT_DEFER_SETUP it is business as usual.
>
>         fd = socket();
>         listen(fd);
>
>         fd1 = accept(fd);
>         fd2 = accept(fd);
>         fd3 = accept(fd);
>
> Regards
>
> Marcel
>


-- 
Luiz Augusto von Dentz

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] Bluetooth: L2CAP: Add initial code for Enhanced Credit Based Mode
  2020-02-20 23:42                 ` Luiz Augusto von Dentz
@ 2020-02-21  8:14                   ` Marcel Holtmann
  2020-02-21 18:26                     ` Luiz Augusto von Dentz
  0 siblings, 1 reply; 14+ messages in thread
From: Marcel Holtmann @ 2020-02-21  8:14 UTC (permalink / raw)
  To: Luiz Augusto von Dentz; +Cc: linux-bluetooth

Hi Luiz,

>>>>>>>>> This adds the initial code for Enhanced Credit Based Mode which
>>>>>>>>> introduces a new socket mode called L2CAP_MODE_EXT_FLOWCTL, which for
>>>>>>>>> the most part work the same as L2CAP_MODE_LE_FLOWCTL but uses different
>>>>>>>>> PDUs to setup the connections and also works over BR/EDR.
>>>>>>>>> 
>>>>>>>>> Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
>>>>>>>>> ---
>>>>>>>>> include/net/bluetooth/l2cap.h |   4 +
>>>>>>>>> net/bluetooth/l2cap_core.c    | 510 +++++++++++++++++++++++++++++++++-
>>>>>>>>> net/bluetooth/l2cap_sock.c    |  39 +--
>>>>>>>>> 3 files changed, 521 insertions(+), 32 deletions(-)
>>>>>>>>> 
>>>>>>>>> diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
>>>>>>>>> index b9ea88cfd2d2..11ae4c93354b 100644
>>>>>>>>> --- a/include/net/bluetooth/l2cap.h
>>>>>>>>> +++ b/include/net/bluetooth/l2cap.h
>>>>>>>>> @@ -295,6 +295,8 @@ struct l2cap_conn_rsp {
>>>>>>>>> #define L2CAP_CR_LE_ENCRYPTION                0x0008
>>>>>>>>> #define L2CAP_CR_LE_INVALID_SCID      0x0009
>>>>>>>>> #define L2CAP_CR_LE_SCID_IN_USE               0X000A
>>>>>>>>> +#define L2CAP_CR_LE_UNACCEPT_PARAMS  0X000B
>>>>>>>>> +#define L2CAP_CR_LE_INVALID_PARAMS   0X000C
>>>>>>>>> 
>>>>>>>>> /* connect/create channel status */
>>>>>>>>> #define L2CAP_CS_NO_INFO      0x0000
>>>>>>>>> @@ -964,6 +966,7 @@ void l2cap_cleanup_sockets(void);
>>>>>>>>> bool l2cap_is_socket(struct socket *sock);
>>>>>>>>> 
>>>>>>>>> void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan);
>>>>>>>>> +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan);
>>>>>>>>> void __l2cap_connect_rsp_defer(struct l2cap_chan *chan);
>>>>>>>>> 
>>>>>>>>> int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm);
>>>>>>>>> @@ -973,6 +976,7 @@ struct l2cap_chan *l2cap_chan_create(void);
>>>>>>>>> void l2cap_chan_close(struct l2cap_chan *chan, int reason);
>>>>>>>>> int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
>>>>>>>>>                  bdaddr_t *dst, u8 dst_type);
>>>>>>>>> +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu);
>>>>>>>>> int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len);
>>>>>>>>> void l2cap_chan_busy(struct l2cap_chan *chan, int busy);
>>>>>>>>> int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator);
>>>>>>>>> diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
>>>>>>>>> index 195459a1e53e..a528526cffac 100644
>>>>>>>>> --- a/net/bluetooth/l2cap_core.c
>>>>>>>>> +++ b/net/bluetooth/l2cap_core.c
>>>>>>>>> @@ -532,6 +532,17 @@ static void l2cap_le_flowctl_init(struct l2cap_chan *chan, u16 tx_credits)
>>>>>>>>>   skb_queue_head_init(&chan->tx_q);
>>>>>>>>> }
>>>>>>>>> 
>>>>>>>>> +static void l2cap_ecred_init(struct l2cap_chan *chan, u16 tx_credits)
>>>>>>>>> +{
>>>>>>>>> +     l2cap_le_flowctl_init(chan, tx_credits);
>>>>>>>>> +
>>>>>>>>> +     /* L2CAP implementations shall support a minimum MPS of 64 octets */
>>>>>>>>> +     if (chan->mps < L2CAP_ECRED_MIN_MPS) {
>>>>>>>>> +             chan->mps = L2CAP_ECRED_MIN_MPS;
>>>>>>>>> +             chan->rx_credits = (chan->imtu / chan->mps) + 1;
>>>>>>>>> +     }
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
>>>>>>>>> {
>>>>>>>>>   BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn,
>>>>>>>>> @@ -638,6 +649,7 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
>>>>>>>>>           break;
>>>>>>>>> 
>>>>>>>>>   case L2CAP_MODE_LE_FLOWCTL:
>>>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>>>>>           skb_queue_purge(&chan->tx_q);
>>>>>>>>>           break;
>>>>>>>>> 
>>>>>>>>> @@ -1260,6 +1272,17 @@ static void l2cap_move_done(struct l2cap_chan *chan)
>>>>>>>>>   }
>>>>>>>>> }
>>>>>>>>> 
>>>>>>>>> +static bool l2cap_mode_ready(struct l2cap_chan *chan)
>>>>>>>>> +{
>>>>>>>>> +     switch (chan->mode) {
>>>>>>>>> +     case L2CAP_MODE_LE_FLOWCTL:
>>>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>>>>> +             return chan->tx_credits ? true : false;
>>>>>>>>> +     }
>>>>>>>>> +
>>>>>>>>> +     return true;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> static void l2cap_chan_ready(struct l2cap_chan *chan)
>>>>>>>>> {
>>>>>>>>>   /* The channel may have already been flagged as connected in
>>>>>>>>> @@ -1273,7 +1296,7 @@ static void l2cap_chan_ready(struct l2cap_chan *chan)
>>>>>>>>>   chan->conf_state = 0;
>>>>>>>>>   __clear_chan_timer(chan);
>>>>>>>>> 
>>>>>>>>> -     if (chan->mode == L2CAP_MODE_LE_FLOWCTL && !chan->tx_credits)
>>>>>>>>> +     if (!l2cap_mode_ready(chan))
>>>>>>>>>           chan->ops->suspend(chan);
>>>>>>>> 
>>>>>>>>     switch (chan->mode) {
>>>>>>>>     case ..
>>>>>>>>     case ..
>>>>>>>>             if (!chan->tx_credits)
>>>>>>>>                     chan->ops->suspend(chan);
>>>>>>>>             break;
>>>>>>>>     }
>>>>>>>> 
>>>>>>>>> 
>>>>>>>>>   chan->state = BT_CONNECTED;
>>>>>>>>> @@ -1306,6 +1329,31 @@ static void l2cap_le_connect(struct l2cap_chan *chan)
>>>>>>>>>                  sizeof(req), &req);
>>>>>>>>> }
>>>>>>>>> 
>>>>>>>>> +static void l2cap_ecred_connect(struct l2cap_chan *chan)
>>>>>>>>> +{
>>>>>>>>> +     struct l2cap_conn *conn = chan->conn;
>>>>>>>>> +     struct {
>>>>>>>>> +             struct l2cap_ecred_conn_req req;
>>>>>>>>> +             __le16 scid;
>>>>>>>>> +     } __packed pdu;
>>>>>>>>> +
>>>>>>>>> +     if (test_and_set_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags))
>>>>>>>>> +             return;
>>>>>>>>> +
>>>>>>>>> +     l2cap_ecred_init(chan, 0);
>>>>>>>>> +
>>>>>>>>> +     pdu.req.psm     = chan->psm;
>>>>>>>>> +     pdu.req.mtu     = cpu_to_le16(chan->imtu);
>>>>>>>>> +     pdu.req.mps     = cpu_to_le16(chan->mps);
>>>>>>>>> +     pdu.req.credits = cpu_to_le16(chan->rx_credits);
>>>>>>>>> +     pdu.scid        = cpu_to_le16(chan->scid);
>>>>>>>>> +
>>>>>>>>> +     chan->ident = l2cap_get_ident(conn);
>>>>>>>>> +
>>>>>>>>> +     l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_CONN_REQ,
>>>>>>>>> +                    sizeof(pdu), &pdu);
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> static void l2cap_le_start(struct l2cap_chan *chan)
>>>>>>>>> {
>>>>>>>>>   struct l2cap_conn *conn = chan->conn;
>>>>>>>>> @@ -1318,8 +1366,12 @@ static void l2cap_le_start(struct l2cap_chan *chan)
>>>>>>>>>           return;
>>>>>>>>>   }
>>>>>>>>> 
>>>>>>>>> -     if (chan->state == BT_CONNECT)
>>>>>>>>> -             l2cap_le_connect(chan);
>>>>>>>>> +     if (chan->state == BT_CONNECT) {
>>>>>>>>> +             if (chan->mode == L2CAP_MODE_EXT_FLOWCTL)
>>>>>>>>> +                     l2cap_ecred_connect(chan);
>>>>>>>>> +             else
>>>>>>>>> +                     l2cap_le_connect(chan);
>>>>>>>>> +     }
>>>>>>>>> }
>>>>>>>>> 
>>>>>>>>> static void l2cap_start_connection(struct l2cap_chan *chan)
>>>>>>>>> @@ -2505,6 +2557,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
>>>>>>>>> 
>>>>>>>>>   switch (chan->mode) {
>>>>>>>>>   case L2CAP_MODE_LE_FLOWCTL:
>>>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>>>>>           /* Check outgoing MTU */
>>>>>>>>>           if (len > chan->omtu)
>>>>>>>>>                   return -EMSGSIZE;
>>>>>>>>> @@ -3773,6 +3826,42 @@ void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan)
>>>>>>>>>                  &rsp);
>>>>>>>>> }
>>>>>>>>> 
>>>>>>>>> +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
>>>>>>>>> +{
>>>>>>>>> +     struct l2cap_ecred_conn_rsp rsp;
>>>>>>>>> +     struct l2cap_conn *conn = chan->conn;
>>>>>>>>> +     u16 ident = chan->ident;
>>>>>>>>> +     int i = 0;
>>>>>>>>> +
>>>>>>>>> +     if (!ident) {
>>>>>>>>> +             return;
>>>>>>>>> +     }
>>>>>>>>> +
>>>>>>>> 
>>>>>>>> No { } here.
>>>>>>>> 
>>>>>>>>> +     BT_DBG("chan %p ident %d", chan, ident);
>>>>>>>>> +
>>>>>>>>> +     rsp.mtu     = cpu_to_le16(chan->imtu);
>>>>>>>>> +     rsp.mps     = cpu_to_le16(chan->mps);
>>>>>>>>> +     rsp.credits = cpu_to_le16(chan->rx_credits);
>>>>>>>>> +     rsp.result  = cpu_to_le16(L2CAP_CR_LE_SUCCESS);
>>>>>>>>> +
>>>>>>>>> +     mutex_lock(&conn->chan_lock);
>>>>>>>>> +
>>>>>>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
>>>>>>>>> +             if (chan->ident != ident)
>>>>>>>>> +                     continue;
>>>>>>>>> +
>>>>>>>>> +             /* Reset ident so only one response is sent */
>>>>>>>>> +             chan->ident = 0;
>>>>>>>>> +
>>>>>>>>> +             /* Include all channels pending with the same ident */
>>>>>>>>> +             rsp.dcid[i++] = cpu_to_le16(chan->scid);
>>>>>>>> 
>>>>>>>> This doesn’t work. The sizeof(rsp) has no dcid in there. You are overflowing into conn struct here.
>>>>>>>> 
>>>>>>>>> +     }
>>>>>>>>> +
>>>>>>>>> +     mutex_unlock(&conn->chan_lock);
>>>>>>>>> +
>>>>>>>>> +     l2cap_send_cmd(conn, ident, L2CAP_ECRED_CONN_RSP, sizeof(rsp), &rsp);
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> void __l2cap_connect_rsp_defer(struct l2cap_chan *chan)
>>>>>>>>> {
>>>>>>>>>   struct l2cap_conn_rsp rsp;
>>>>>>>>> @@ -5714,6 +5803,347 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn,
>>>>>>>>>   return 0;
>>>>>>>>> }
>>>>>>>>> 
>>>>>>>>> +static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
>>>>>>>>> +                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>>>>>>> +                                    u8 *data)
>>>>>>>>> +{
>>>>>>>>> +     struct l2cap_ecred_conn_req *req = (void *) data;
>>>>>>>>> +     struct {
>>>>>>>>> +             struct l2cap_ecred_conn_rsp rsp;
>>>>>>>>> +             __le16 dcid[5];
>>>>>>>>> +     } __packed pdu;
>>>>>>>>> +     struct l2cap_chan *chan, *pchan;
>>>>>>>>> +     u16 credits, mtu, mps;
>>>>>>>>> +     __le16 psm;
>>>>>>>>> +     u8 result, len = 0;
>>>>>>>>> +     int i;
>>>>>>>>> +     bool defer = false;
>>>>>>>>> +
>>>>>>>>> +     if (cmd_len < sizeof(*req))
>>>>>>>>> +             return -EPROTO;
>>>>>>>>> +
>>>>>>>>> +     mtu  = __le16_to_cpu(req->mtu);
>>>>>>>>> +     mps  = __le16_to_cpu(req->mps);
>>>>>>>>> +
>>>>>>>>> +     if (mtu < L2CAP_ECRED_MIN_MTU || mps < L2CAP_ECRED_MIN_MPS) {
>>>>>>>>> +             result = L2CAP_CR_LE_UNACCEPT_PARAMS;
>>>>>>>>> +             goto response;
>>>>>>>>> +     }
>>>>>>>>> +
>>>>>>>>> +     psm  = req->psm;
>>>>>>>>> +     credits = 0;
>>>>>>>>> +
>>>>>>>>> +     BT_DBG("psm 0x%2.2x mtu %u mps %u", __le16_to_cpu(psm), mtu, mps);
>>>>>>>>> +
>>>>>>>>> +     memset(&pdu, 0, sizeof(pdu));
>>>>>>>>> +
>>>>>>>>> +     /* Check if we have socket listening on psm */
>>>>>>>>> +     pchan = l2cap_global_chan_by_psm(BT_LISTEN, psm, &conn->hcon->src,
>>>>>>>>> +                                      &conn->hcon->dst, LE_LINK);
>>>>>>>>> +     if (!pchan) {
>>>>>>>>> +             result = L2CAP_CR_LE_BAD_PSM;
>>>>>>>>> +             goto response;
>>>>>>>>> +     }
>>>>>>>>> +
>>>>>>>>> +     mutex_lock(&conn->chan_lock);
>>>>>>>>> +     l2cap_chan_lock(pchan);
>>>>>>>>> +
>>>>>>>>> +     if (!smp_sufficient_security(conn->hcon, pchan->sec_level,
>>>>>>>>> +                                  SMP_ALLOW_STK)) {
>>>>>>>>> +             result = L2CAP_CR_LE_AUTHENTICATION;
>>>>>>>>> +             goto unlock;
>>>>>>>>> +     }
>>>>>>>>> +
>>>>>>>>> +     result = L2CAP_CR_LE_SUCCESS;
>>>>>>>>> +
>>>>>>>>> +     for (i = 0, cmd_len -= sizeof(req); cmd_len >= sizeof(u16);
>>>>>>>>> +          i++, cmd_len -= sizeof(u16)) {
>>>>>>>> 
>>>>>>>> Can we simplify the loop statement or switch to while statement?
>>>>>>>> 
>>>>>>>>> +             u16 scid = __le16_to_cpu(req->scid[i]);
>>>>>>>>> +
>>>>>>>>> +             BT_DBG("scid[%d] 0x%4.4x", i, scid);
>>>>>>>>> +
>>>>>>>>> +             pdu.dcid[i] = 0x0000;
>>>>>>>>> +             len += sizeof(*pdu.dcid);
>>>>>>>>> +
>>>>>>>>> +             /* Check for valid dynamic CID range */
>>>>>>>>> +             if (scid < L2CAP_CID_DYN_START || scid > L2CAP_CID_LE_DYN_END) {
>>>>>>>>> +                     result = L2CAP_CR_LE_INVALID_SCID;
>>>>>>>>> +                     continue;
>>>>>>>>> +             }
>>>>>>>>> +
>>>>>>>>> +             /* Check if we already have channel with that dcid */
>>>>>>>>> +             if (__l2cap_get_chan_by_dcid(conn, scid)) {
>>>>>>>>> +                     result = L2CAP_CR_LE_SCID_IN_USE;
>>>>>>>>> +                     continue;
>>>>>>>>> +             }
>>>>>>>>> +
>>>>>>>>> +             chan = pchan->ops->new_connection(pchan);
>>>>>>>>> +             if (!chan) {
>>>>>>>>> +                     result = L2CAP_CR_LE_NO_MEM;
>>>>>>>>> +                     continue;
>>>>>>>>> +             }
>>>>>>>>> +
>>>>>>>>> +             bacpy(&chan->src, &conn->hcon->src);
>>>>>>>>> +             bacpy(&chan->dst, &conn->hcon->dst);
>>>>>>>>> +             chan->src_type = bdaddr_src_type(conn->hcon);
>>>>>>>>> +             chan->dst_type = bdaddr_dst_type(conn->hcon);
>>>>>>>>> +             chan->psm  = psm;
>>>>>>>>> +             chan->dcid = scid;
>>>>>>>>> +             chan->omtu = mtu;
>>>>>>>>> +             chan->remote_mps = mps;
>>>>>>>>> +
>>>>>>>>> +             __l2cap_chan_add(conn, chan);
>>>>>>>>> +
>>>>>>>>> +             l2cap_ecred_init(chan, __le16_to_cpu(req->credits));
>>>>>>>>> +
>>>>>>>>> +             /* Init response */
>>>>>>>>> +             if (!pdu.rsp.credits) {
>>>>>>>>> +                     pdu.rsp.mtu = cpu_to_le16(chan->imtu);
>>>>>>>>> +                     pdu.rsp.mps = cpu_to_le16(chan->mps);
>>>>>>>>> +                     pdu.rsp.credits = cpu_to_le16(chan->rx_credits);
>>>>>>>>> +             }
>>>>>>>>> +
>>>>>>>>> +             pdu.dcid[i] = cpu_to_le16(chan->scid);
>>>>>>>>> +
>>>>>>>>> +             __set_chan_timer(chan, chan->ops->get_sndtimeo(chan));
>>>>>>>>> +
>>>>>>>>> +             chan->ident = cmd->ident;
>>>>>>>>> +
>>>>>>>>> +             if (test_bit(FLAG_DEFER_SETUP, &chan->flags)) {
>>>>>>>>> +                     l2cap_state_change(chan, BT_CONNECT2);
>>>>>>>>> +                     defer = true;
>>>>>>>>> +                     chan->ops->defer(chan);
>>>>>>>>> +             } else {
>>>>>>>>> +                     l2cap_chan_ready(chan);
>>>>>>>>> +             }
>>>>>>>>> +     }
>>>>>>>>> +
>>>>>>>>> +unlock:
>>>>>>>>> +     l2cap_chan_unlock(pchan);
>>>>>>>>> +     mutex_unlock(&conn->chan_lock);
>>>>>>>>> +     l2cap_chan_put(pchan);
>>>>>>>>> +
>>>>>>>>> +response:
>>>>>>>>> +     pdu.rsp.result = cpu_to_le16(result);
>>>>>>>>> +
>>>>>>>>> +     if (defer)
>>>>>>>>> +             return 0;
>>>>>>>>> +
>>>>>>>>> +     l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_CONN_RSP,
>>>>>>>>> +                    sizeof(pdu.rsp) + len, &pdu);
>>>>>>>>> +
>>>>>>>>> +     return 0;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
>>>>>>>>> +                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>>>>>>> +                                    u8 *data)
>>>>>>>>> +{
>>>>>>>>> +     struct l2cap_ecred_conn_rsp *rsp = (void *) data;
>>>>>>>>> +     struct hci_conn *hcon = conn->hcon;
>>>>>>>>> +     u16 mtu, mps, credits, result;
>>>>>>>>> +     struct l2cap_chan *chan;
>>>>>>>>> +     int err = 0, sec_level;
>>>>>>>>> +     int i = 0;
>>>>>>>>> +
>>>>>>>>> +     if (cmd_len < sizeof(*rsp))
>>>>>>>>> +             return -EPROTO;
>>>>>>>>> +
>>>>>>>>> +     mtu     = __le16_to_cpu(rsp->mtu);
>>>>>>>>> +     mps     = __le16_to_cpu(rsp->mps);
>>>>>>>>> +     credits = __le16_to_cpu(rsp->credits);
>>>>>>>>> +     result  = __le16_to_cpu(rsp->result);
>>>>>>>>> +
>>>>>>>>> +     BT_DBG("mtu %u mps %u credits %u result 0x%4.4x", mtu, mps, credits,
>>>>>>>>> +            result);
>>>>>>>>> +
>>>>>>>>> +     mutex_lock(&conn->chan_lock);
>>>>>>>>> +
>>>>>>>>> +     cmd_len -= sizeof(*rsp);
>>>>>>>>> +
>>>>>>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
>>>>>>>>> +             u16 dcid;
>>>>>>>>> +
>>>>>>>>> +             if (chan->ident != cmd->ident ||
>>>>>>>>> +                 chan->mode != L2CAP_MODE_EXT_FLOWCTL ||
>>>>>>>>> +                 chan->state == BT_CONNECTED)
>>>>>>>>> +                     continue;
>>>>>>>>> +
>>>>>>>>> +             l2cap_chan_lock(chan);
>>>>>>>>> +
>>>>>>>>> +             /* Check that there is a dcid for each pending channel */
>>>>>>>>> +             if (cmd_len < sizeof(dcid)) {
>>>>>>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
>>>>>>>>> +                     l2cap_chan_unlock(chan);
>>>>>>>>> +                     continue;
>>>>>>>>> +             }
>>>>>>>>> +
>>>>>>>>> +             dcid = __le16_to_cpu(rsp->dcid[i++]);
>>>>>>>>> +             cmd_len -= sizeof(u16);
>>>>>>>>> +
>>>>>>>>> +             BT_DBG("dcid[%d] 0x%4.4x", i, dcid);
>>>>>>>>> +
>>>>>>>>> +             /* Check if dcid is already in use */
>>>>>>>>> +             if (dcid && __l2cap_get_chan_by_dcid(conn, dcid)) {
>>>>>>>>> +                     /* If a device receives a
>>>>>>>>> +                      * L2CAP_CREDIT_BASED_CONNECTION_RSP packet with an
>>>>>>>>> +                      * already-assigned Destination CID, then both the
>>>>>>>>> +                      * original channel and the new channel shall be
>>>>>>>>> +                      * immediately discarded and not used.
>>>>>>>>> +                      */
>>>>>>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
>>>>>>>>> +                     l2cap_chan_unlock(chan);
>>>>>>>>> +                     chan = __l2cap_get_chan_by_dcid(conn, dcid);
>>>>>>>>> +                     l2cap_chan_lock(chan);
>>>>>>>>> +                     l2cap_chan_del(chan, ECONNRESET);
>>>>>>>>> +                     l2cap_chan_unlock(chan);
>>>>>>>>> +                     continue;
>>>>>>>>> +             }
>>>>>>>>> +
>>>>>>>>> +             switch (result) {
>>>>>>>>> +             case L2CAP_CR_LE_AUTHENTICATION:
>>>>>>>>> +             case L2CAP_CR_LE_ENCRYPTION:
>>>>>>>>> +                     /* If we already have MITM protection we can't do
>>>>>>>>> +                      * anything.
>>>>>>>>> +                      */
>>>>>>>>> +                     if (hcon->sec_level > BT_SECURITY_MEDIUM) {
>>>>>>>>> +                             l2cap_chan_del(chan, ECONNREFUSED);
>>>>>>>>> +                             break;
>>>>>>>>> +                     }
>>>>>>>>> +
>>>>>>>>> +                     sec_level = hcon->sec_level + 1;
>>>>>>>>> +                     if (chan->sec_level < sec_level)
>>>>>>>>> +                             chan->sec_level = sec_level;
>>>>>>>>> +
>>>>>>>>> +                     /* We'll need to send a new Connect Request */
>>>>>>>>> +                     clear_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags);
>>>>>>>>> +
>>>>>>>>> +                     smp_conn_security(hcon, chan->sec_level);
>>>>>>>>> +                     break;
>>>>>>>>> +
>>>>>>>>> +             case L2CAP_CR_LE_BAD_PSM:
>>>>>>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
>>>>>>>>> +                     break;
>>>>>>>>> +
>>>>>>>>> +             default:
>>>>>>>>> +                     /* If dcid was not set it means channels was refused */
>>>>>>>>> +                     if (!dcid) {
>>>>>>>>> +                             l2cap_chan_del(chan, ECONNREFUSED);
>>>>>>>>> +                             break;
>>>>>>>>> +                     }
>>>>>>>>> +
>>>>>>>>> +                     chan->ident = 0;
>>>>>>>>> +                     chan->dcid = dcid;
>>>>>>>>> +                     chan->omtu = mtu;
>>>>>>>>> +                     chan->remote_mps = mps;
>>>>>>>>> +                     chan->tx_credits = credits;
>>>>>>>>> +                     l2cap_chan_ready(chan);
>>>>>>>>> +                     break;
>>>>>>>>> +             }
>>>>>>>>> +
>>>>>>>>> +             l2cap_chan_unlock(chan);
>>>>>>>>> +     }
>>>>>>>>> +
>>>>>>>>> +     mutex_unlock(&conn->chan_lock);
>>>>>>>>> +
>>>>>>>>> +     return err;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +static inline int l2cap_ecred_reconf_req(struct l2cap_conn *conn,
>>>>>>>>> +                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>>>>>>> +                                      u8 *data)
>>>>>>>>> +{
>>>>>>>>> +     struct l2cap_ecred_reconf_req *req = (void *) data;
>>>>>>>>> +     struct l2cap_ecred_reconf_rsp rsp;
>>>>>>>>> +     u16 mtu, mps, result;
>>>>>>>>> +     struct l2cap_chan *chan;
>>>>>>>>> +     int i;
>>>>>>>>> +
>>>>>>>>> +     if (cmd_len < sizeof(*req)) {
>>>>>>>>> +             result = L2CAP_CR_LE_INVALID_PARAMS;
>>>>>>>>> +             goto respond;
>>>>>>>>> +     }
>>>>>>>>> +
>>>>>>>>> +     mtu = __le16_to_cpu(req->mtu);
>>>>>>>>> +     mps = __le16_to_cpu(req->mps);
>>>>>>>>> +
>>>>>>>>> +     BT_DBG("mtu %u mps %u", mtu, mps);
>>>>>>>>> +
>>>>>>>>> +     if (mtu < L2CAP_ECRED_MIN_MTU) {
>>>>>>>>> +             result = L2CAP_RECONF_INVALID_MTU;
>>>>>>>>> +             goto respond;
>>>>>>>>> +     }
>>>>>>>>> +
>>>>>>>>> +     if (mps < L2CAP_ECRED_MIN_MPS) {
>>>>>>>>> +             result = L2CAP_RECONF_INVALID_MPS;
>>>>>>>>> +             goto respond;
>>>>>>>>> +     }
>>>>>>>>> +
>>>>>>>>> +     result = L2CAP_RECONF_SUCCESS;
>>>>>>>>> +
>>>>>>>>> +     for (i = 0, cmd_len -= sizeof(*req); cmd_len < sizeof(u16);
>>>>>>>>> +          i++, cmd_len -= sizeof(u16)) {
>>>>>>>> 
>>>>>>>> Same comment as above.
>>>>>>>> 
>>>>>>>>> +             u16 scid;
>>>>>>>>> +
>>>>>>>>> +             scid = __le16_to_cpu(req->scid[i]);
>>>>>>>>> +             if (!scid)
>>>>>>>>> +                     return -EPROTO;
>>>>>>>>> +
>>>>>>>>> +             chan = __l2cap_get_chan_by_dcid(conn, scid);
>>>>>>>>> +             if (!chan)
>>>>>>>>> +                     continue;
>>>>>>>>> +
>>>>>>>>> +             /* If the MTU value is decreased for any of the included
>>>>>>>>> +              * channels, then the receiver shall disconnect all
>>>>>>>>> +              * included channels.
>>>>>>>>> +              */
>>>>>>>>> +             if (chan->omtu > mtu) {
>>>>>>>>> +                     BT_ERR("chan %p decreased MTU %u -> %u", chan,
>>>>>>>>> +                            chan->omtu, mtu);
>>>>>>>>> +                     result = L2CAP_RECONF_INVALID_MTU;
>>>>>>>>> +             }
>>>>>>>>> +
>>>>>>>>> +             chan->omtu = mtu;
>>>>>>>>> +             chan->remote_mps = mps;
>>>>>>>>> +     }
>>>>>>>>> +
>>>>>>>>> +respond:
>>>>>>>>> +     rsp.result = cpu_to_le16(result);
>>>>>>>>> +
>>>>>>>>> +     l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_RECONF_RSP, sizeof(rsp),
>>>>>>>>> +                    &rsp);
>>>>>>>>> +
>>>>>>>>> +     return 0;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +static inline int l2cap_ecred_reconf_rsp(struct l2cap_conn *conn,
>>>>>>>>> +                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>>>>>>> +                                      u8 *data)
>>>>>>>>> +{
>>>>>>>>> +     struct l2cap_chan *chan;
>>>>>>>>> +     struct l2cap_ecred_conn_rsp *rsp = (void *) data;
>>>>>>>>> +     u16 result;
>>>>>>>>> +
>>>>>>>>> +     if (cmd_len < sizeof(*rsp))
>>>>>>>>> +             return -EPROTO;
>>>>>>>>> +
>>>>>>>>> +     result = __le16_to_cpu(rsp->result);
>>>>>>>>> +
>>>>>>>>> +     BT_DBG("result 0x%4.4x", rsp->result);
>>>>>>>>> +
>>>>>>>>> +     if (!result)
>>>>>>>>> +             return 0;
>>>>>>>>> +
>>>>>>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
>>>>>>>>> +             if (chan->ident != cmd->ident)
>>>>>>>>> +                     continue;
>>>>>>>>> +
>>>>>>>>> +             l2cap_chan_del(chan, ECONNRESET);
>>>>>>>>> +     }
>>>>>>>>> +
>>>>>>>>> +     return 0;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> static inline int l2cap_le_command_rej(struct l2cap_conn *conn,
>>>>>>>>>                                  struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>>>>>>>                                  u8 *data)
>>>>>>>>> @@ -5769,6 +6199,22 @@ static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn,
>>>>>>>>>           err = l2cap_le_credits(conn, cmd, cmd_len, data);
>>>>>>>>>           break;
>>>>>>>>> 
>>>>>>>>> +     case L2CAP_ECRED_CONN_REQ:
>>>>>>>>> +             err = l2cap_ecred_conn_req(conn, cmd, cmd_len, data);
>>>>>>>>> +             break;
>>>>>>>>> +
>>>>>>>>> +     case L2CAP_ECRED_CONN_RSP:
>>>>>>>>> +             err = l2cap_ecred_conn_rsp(conn, cmd, cmd_len, data);
>>>>>>>>> +             break;
>>>>>>>>> +
>>>>>>>>> +     case L2CAP_ECRED_RECONF_REQ:
>>>>>>>>> +             err = l2cap_ecred_reconf_req(conn, cmd, cmd_len, data);
>>>>>>>>> +             break;
>>>>>>>>> +
>>>>>>>>> +     case L2CAP_ECRED_RECONF_RSP:
>>>>>>>>> +             err = l2cap_ecred_reconf_rsp(conn, cmd, cmd_len, data);
>>>>>>>>> +             break;
>>>>>>>>> +
>>>>>>>>>   case L2CAP_DISCONN_REQ:
>>>>>>>>>           err = l2cap_disconnect_req(conn, cmd, cmd_len, data);
>>>>>>>>>           break;
>>>>>>>>> @@ -6814,11 +7260,13 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
>>>>>>>>>   struct l2cap_le_credits pkt;
>>>>>>>>>   u16 return_credits;
>>>>>>>>> 
>>>>>>>>> -     return_credits = ((chan->imtu / chan->mps) + 1) - chan->rx_credits;
>>>>>>>>> +     return_credits = (chan->imtu / chan->mps) + 1;
>>>>>>>>> 
>>>>>>>>> -     if (!return_credits)
>>>>>>>>> +     if (chan->rx_credits >= return_credits)
>>>>>>>>>           return;
>>>>>>>>> 
>>>>>>>>> +     return_credits -= chan->rx_credits;
>>>>>>>>> +
>>>>>>>>>   BT_DBG("chan %p returning %u credits to sender", chan, return_credits);
>>>>>>>>> 
>>>>>>>>>   chan->rx_credits += return_credits;
>>>>>>>>> @@ -6831,7 +7279,7 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
>>>>>>>>>   l2cap_send_cmd(conn, chan->ident, L2CAP_LE_CREDITS, sizeof(pkt), &pkt);
>>>>>>>>> }
>>>>>>>>> 
>>>>>>>>> -static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>>>> +static int l2cap_ecred_recv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>>>> {
>>>>>>>>>   int err;
>>>>>>>>> 
>>>>>>>>> @@ -6846,7 +7294,7 @@ static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>>>>   return err;
>>>>>>>>> }
>>>>>>>>> 
>>>>>>>>> -static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>>>> +static int l2cap_ecred_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>>>> {
>>>>>>>>>   int err;
>>>>>>>>> 
>>>>>>>>> @@ -6894,7 +7342,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>>>>           }
>>>>>>>>> 
>>>>>>>>>           if (skb->len == sdu_len)
>>>>>>>>> -                     return l2cap_le_recv(chan, skb);
>>>>>>>>> +                     return l2cap_ecred_recv(chan, skb);
>>>>>>>>> 
>>>>>>>>>           chan->sdu = skb;
>>>>>>>>>           chan->sdu_len = sdu_len;
>>>>>>>>> @@ -6926,7 +7374,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>>>>   skb = NULL;
>>>>>>>>> 
>>>>>>>>>   if (chan->sdu->len == chan->sdu_len) {
>>>>>>>>> -             err = l2cap_le_recv(chan, chan->sdu);
>>>>>>>>> +             err = l2cap_ecred_recv(chan, chan->sdu);
>>>>>>>>>           if (!err) {
>>>>>>>>>                   chan->sdu = NULL;
>>>>>>>>>                   chan->sdu_last_frag = NULL;
>>>>>>>>> @@ -6987,7 +7435,8 @@ static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid,
>>>>>>>>> 
>>>>>>>>>   switch (chan->mode) {
>>>>>>>>>   case L2CAP_MODE_LE_FLOWCTL:
>>>>>>>>> -             if (l2cap_le_data_rcv(chan, skb) < 0)
>>>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>>>>> +             if (l2cap_ecred_data_rcv(chan, skb) < 0)
>>>>>>>>>                   goto drop;
>>>>>>>>> 
>>>>>>>>>           goto done;
>>>>>>>>> @@ -7214,8 +7663,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
>>>>>>>>>   struct hci_dev *hdev;
>>>>>>>>>   int err;
>>>>>>>>> 
>>>>>>>>> -     BT_DBG("%pMR -> %pMR (type %u) psm 0x%2.2x", &chan->src, dst,
>>>>>>>>> -            dst_type, __le16_to_cpu(psm));
>>>>>>>>> +     BT_DBG("%pMR -> %pMR (type %u) psm 0x%4.4x mode 0x%2.2x", &chan->src,
>>>>>>>>> +            dst, dst_type, __le16_to_cpu(psm), chan->mode);
>>>>>>>>> 
>>>>>>>>>   hdev = hci_get_route(dst, &chan->src, chan->src_type);
>>>>>>>>>   if (!hdev)
>>>>>>>>> @@ -7244,6 +7693,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
>>>>>>>>>           break;
>>>>>>>>>   case L2CAP_MODE_LE_FLOWCTL:
>>>>>>>>>           break;
>>>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>>>>> +             break;
>>>>>>>> 
>>>>>>>> No need to do another break. Just put it under LE_FLOWCTL.
>>>>>>>> 
>>>>>>>>>   case L2CAP_MODE_ERTM:
>>>>>>>>>   case L2CAP_MODE_STREAMING:
>>>>>>>>>           if (!disable_ertm)
>>>>>>>>> @@ -7368,6 +7819,38 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
>>>>>>>>> }
>>>>>>>>> EXPORT_SYMBOL_GPL(l2cap_chan_connect);
>>>>>>>>> 
>>>>>>>>> +static void l2cap_ecred_reconfigure(struct l2cap_chan *chan)
>>>>>>>>> +{
>>>>>>>>> +     struct l2cap_conn *conn = chan->conn;
>>>>>>>>> +     struct {
>>>>>>>>> +             struct l2cap_ecred_reconf_req req;
>>>>>>>>> +             __le16 scid;
>>>>>>>>> +     } pdu;
>>>>>>>>> +
>>>>>>>>> +     pdu.req.mtu = cpu_to_le16(chan->imtu);
>>>>>>>>> +     pdu.req.mps = cpu_to_le16(chan->mps);
>>>>>>>>> +     pdu.scid    = cpu_to_le16(chan->scid);
>>>>>>>>> +
>>>>>>>>> +     chan->ident = l2cap_get_ident(conn);
>>>>>>>>> +
>>>>>>>>> +     l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_RECONF_REQ,
>>>>>>>>> +                    sizeof(pdu), &pdu);
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu)
>>>>>>>>> +{
>>>>>>>>> +     if (chan->imtu > mtu)
>>>>>>>>> +             return -EINVAL;
>>>>>>>>> +
>>>>>>>>> +     BT_DBG("chan %p mtu 0x%4.4x", chan, mtu);
>>>>>>>>> +
>>>>>>>>> +     chan->imtu = mtu;
>>>>>>>>> +
>>>>>>>>> +     l2cap_ecred_reconfigure(chan);
>>>>>>>>> +
>>>>>>>>> +     return 0;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> /* ---- L2CAP interface with lower layer (HCI) ---- */
>>>>>>>>> 
>>>>>>>>> int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr)
>>>>>>>>> @@ -7579,7 +8062,8 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
>>>>>>>>>                   else
>>>>>>>>>                           __set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
>>>>>>>>>           } else if (chan->state == BT_CONNECT2 &&
>>>>>>>>> -                        chan->mode != L2CAP_MODE_LE_FLOWCTL) {
>>>>>>>>> +                        !(chan->mode == L2CAP_MODE_EXT_FLOWCTL ||
>>>>>>>>> +                         chan->mode == L2CAP_MODE_LE_FLOWCTL)) {
>>>>>>>> 
>>>>>>>> Please double check that this line is correctly aligned after the (.
>>>>>>>> 
>>>>>>>>>                   struct l2cap_conn_rsp rsp;
>>>>>>>>>                   __u16 res, stat;
>>>>>>>>> 
>>>>>>>>> diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
>>>>>>>>> index a7be8b59b3c2..12c557f7f40f 100644
>>>>>>>>> --- a/net/bluetooth/l2cap_sock.c
>>>>>>>>> +++ b/net/bluetooth/l2cap_sock.c
>>>>>>>>> @@ -232,7 +232,7 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr,
>>>>>>>>>                   return -EINVAL;
>>>>>>>>>   }
>>>>>>>>> 
>>>>>>>>> -     if (chan->psm && bdaddr_type_is_le(chan->src_type))
>>>>>>>>> +     if (chan->psm && bdaddr_type_is_le(chan->src_type) && !chan->mode)
>>>>>>>>>           chan->mode = L2CAP_MODE_LE_FLOWCTL;
>>>>>>>>> 
>>>>>>>>>   err = l2cap_chan_connect(chan, la.l2_psm, __le16_to_cpu(la.l2_cid),
>>>>>>>>> @@ -273,6 +273,7 @@ static int l2cap_sock_listen(struct socket *sock, int backlog)
>>>>>>>>>   switch (chan->mode) {
>>>>>>>>>   case L2CAP_MODE_BASIC:
>>>>>>>>>   case L2CAP_MODE_LE_FLOWCTL:
>>>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>>>>>           break;
>>>>>>>>>   case L2CAP_MODE_ERTM:
>>>>>>>>>   case L2CAP_MODE_STREAMING:
>>>>>>>>> @@ -408,16 +409,6 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
>>>>>>>>> 
>>>>>>>>>   switch (optname) {
>>>>>>>>>   case L2CAP_OPTIONS:
>>>>>>>>> -             /* LE sockets should use BT_SNDMTU/BT_RCVMTU, but since
>>>>>>>>> -              * legacy ATT code depends on getsockopt for
>>>>>>>>> -              * L2CAP_OPTIONS we need to let this pass.
>>>>>>>>> -              */
>>>>>>>>> -             if (bdaddr_type_is_le(chan->src_type) &&
>>>>>>>>> -                 chan->scid != L2CAP_CID_ATT) {
>>>>>>>>> -                     err = -EINVAL;
>>>>>>>>> -                     break;
>>>>>>>>> -             }
>>>>>>>>> -
>>>>>>>> 
>>>>>>>> Should this be a separate patch. How do we keep this legacy behavior.
>>>>>>> 
>>>>>>> I kind have forgotten to fix this one, I guess we will need a new
>>>>>>> option in order to read the mode without using the old L2CAP_OPTIONS
>>>>>>> then, or do you have anything against introducing yet another option?
>>>>>> 
>>>>>> so what is the problem here? Is this because L2CAP by itself can not tell if enhanced credit mode is supported by the peer?
>>>>> 
>>>>> Yep, afaik there is no means to detect the support transparently,
>>>>> otherwise could always prefer to use EXT_FLOWCTL instead of
>>>>> LE_FLOWCTL, it seems at the point we introduced LE_FLOWCTL option we
>>>>> had always assumed there would not be any mode one could use when the
>>>>> address type is LE.
>>>>> 
>>>>>> Do you actually want to create a new mode socket options with defined values by us to allow selected the mode of operation?
>>>>> 
>>>>> I woud assume that is the only safe option here, though what I did was
>>>>> to enable L2CAP_OPTIONS to work with LE addresses which I assume is
>>>>> safe as previously it would cause an error no one would be using it.
>>>> 
>>>> that is what I was afraid of. So can we use BT_SNDMTU and BT_RCVMTU and then only a mode property is missing? Or do we have other options that we want to configure.
>>> 
>>> Not sure I follow, do you prefer chaning an existing option to add a
>>> new field? I though having an option to set the mode would be better
>>> but perhaps you want to combine it something else, well maybe some
>>> sort of QOS interface, the problem is that the field are quite
>>> different depending on the mode so Im afraid that would take a lot
>>> more time to define.
>> 
>> Let me ask this differently. What options do we want to be able to configure if we deprecate L2CAP_OPTIONS?
> 
> Coming back to this perhaps we could infer the mode if in case there
> are multiple connection being made, the only question is if the spec
> actually allow connection with the lagacy command if the remote side
> is listen on PSM that is for ECRED mode like EATT, if it does require
> maybe we can use BT_DEFER_SETUP to trigger it but I don't really like
> reusing BT_DEFER_SETUP like that since it was introduced for a very
> different purpose it may confuse people.

you still haven’t answered my question. What kind of options do we want the socket user to configure?

>> Anyhow, maybe we first introduce a debugfs option that says attempt_ecred_mode_first and only when the attempt fails fall back to credit based flow control mode.
> 
> I was thinking in something like the following so we only use the new
> mode if it is enabled, well provided we can remove it later:
> 
> https://gist.github.com/Vudentz/35ca4a39b58e64e8ecbde3ff6ada2d61

That looks fine to me.

>> Btw. do we have at least a supported features bit in L2CAP hinting that ecred mode is supported over BR/EDR. It would be good to have some way of knowing it for BR/EDR ahead of time.
> 
> I don't remember it being added, but yes it would make a lot of sense
> to have it when using over BR/EDR.

If not, then we should file an erratum for it.

>>>> For the multi-connect case, I was thinking that we use something similar to DEFER_SETUP. If we set a DEFER_SETUP on an outgoing socket, then you can call connect() on a socket and succeeds. However no POLLIN | POLLOUT would be signaled. When calling a connect() without that option set, then all sockets become ready at the same time on success.
>>> 
>>> As initiator I don't think we have a good match in the socket API as
>>> it only return one fd at time, so currently it still doing the
>>> connection one by one, what I though we could do is to delay a little
>>> to group the request in a single L2CAP request, thought that creates
>>> an extra latency for initiating the connection and can be racy.
>>> Another option would be to allow bind to bind to multiple sockets at
>>> once, so when you issue the connect in one socket it would connect all
>>> socket bound.
>> 
>> Let me try to explain this in pseudo code.
>> 
>>        fd1 = socket();
>>        setsockopt(fd1, BT_DEFER_SETUP);
>>        connect(fd1);
>>        0 = poll(fd1);
>> 
>>        fd2 = socket();
>>        setsockopt(fd2, BT_DEFER_SETUP);
>>        connect(fd2);
>>        0 = poll(fd2);
>> 
>>        fd3 = socket();
>>        connect(fd3);
>>        POLLOUT = poll(fd1);
>>        POLLOUT = poll(fd2);
>>        POLLOUT = poll(fd3);
>> 
>> Essentially all the connect() call with BT_DEFER_SETUP will succeed, but POLLOUT will not signal. So you have the fd, but you can not write to it. When we have the connect() without BT_DEFER_SETUP set, then L2CAP will connect all channels at the same time and assign them to the given sockets. And now all fds become POLLOUT.
> 
> Should work, btw couldn't we just use bind for the first 2 sockets?
> That imo would make things a little cleaner since we only do connect
> when it is really ready to connect and we don't have to even consider
> poll until then, that said we would need to group these sockets either
> way so we need to do this per processs, actually the order would be:
> 
> fd# = socket()
> bind(fd#)
> setsockopt(fd#, BT_DEFER_SETUP)
> 
> Since bind sets the address then on setsockopt we can check if there
> is anything else pending to be connected for the same address and then
> just proceed.

With bind() you set the local address. With connect() you select the remote address. I don’t see how bind() only would get us there.

And the poll() part is not a problem btw. We should be doing asynchronous write/send anyway. It is just that we have been a bit lazy with some of the protocol implementation and execute write/send right away without checking with poll if the socket is actually writable. If we fix that then this will be all dead simple. It is no different than an asynchronous connect() anyway. There you have to wait for poll() as well. I really doubt we are using blocking connect() calls. And if we do, we need to fix that.

Regards

Marcel


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] Bluetooth: L2CAP: Add initial code for Enhanced Credit Based Mode
  2020-02-21  8:14                   ` Marcel Holtmann
@ 2020-02-21 18:26                     ` Luiz Augusto von Dentz
  2020-02-22  9:44                       ` Marcel Holtmann
  0 siblings, 1 reply; 14+ messages in thread
From: Luiz Augusto von Dentz @ 2020-02-21 18:26 UTC (permalink / raw)
  To: Marcel Holtmann; +Cc: linux-bluetooth

Hi Marcel,

On Fri, Feb 21, 2020 at 12:14 AM Marcel Holtmann <marcel@holtmann.org> wrote:
>
> Hi Luiz,
>
> >>>>>>>>> This adds the initial code for Enhanced Credit Based Mode which
> >>>>>>>>> introduces a new socket mode called L2CAP_MODE_EXT_FLOWCTL, which for
> >>>>>>>>> the most part work the same as L2CAP_MODE_LE_FLOWCTL but uses different
> >>>>>>>>> PDUs to setup the connections and also works over BR/EDR.
> >>>>>>>>>
> >>>>>>>>> Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
> >>>>>>>>> ---
> >>>>>>>>> include/net/bluetooth/l2cap.h |   4 +
> >>>>>>>>> net/bluetooth/l2cap_core.c    | 510 +++++++++++++++++++++++++++++++++-
> >>>>>>>>> net/bluetooth/l2cap_sock.c    |  39 +--
> >>>>>>>>> 3 files changed, 521 insertions(+), 32 deletions(-)
> >>>>>>>>>
> >>>>>>>>> diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
> >>>>>>>>> index b9ea88cfd2d2..11ae4c93354b 100644
> >>>>>>>>> --- a/include/net/bluetooth/l2cap.h
> >>>>>>>>> +++ b/include/net/bluetooth/l2cap.h
> >>>>>>>>> @@ -295,6 +295,8 @@ struct l2cap_conn_rsp {
> >>>>>>>>> #define L2CAP_CR_LE_ENCRYPTION                0x0008
> >>>>>>>>> #define L2CAP_CR_LE_INVALID_SCID      0x0009
> >>>>>>>>> #define L2CAP_CR_LE_SCID_IN_USE               0X000A
> >>>>>>>>> +#define L2CAP_CR_LE_UNACCEPT_PARAMS  0X000B
> >>>>>>>>> +#define L2CAP_CR_LE_INVALID_PARAMS   0X000C
> >>>>>>>>>
> >>>>>>>>> /* connect/create channel status */
> >>>>>>>>> #define L2CAP_CS_NO_INFO      0x0000
> >>>>>>>>> @@ -964,6 +966,7 @@ void l2cap_cleanup_sockets(void);
> >>>>>>>>> bool l2cap_is_socket(struct socket *sock);
> >>>>>>>>>
> >>>>>>>>> void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan);
> >>>>>>>>> +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan);
> >>>>>>>>> void __l2cap_connect_rsp_defer(struct l2cap_chan *chan);
> >>>>>>>>>
> >>>>>>>>> int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm);
> >>>>>>>>> @@ -973,6 +976,7 @@ struct l2cap_chan *l2cap_chan_create(void);
> >>>>>>>>> void l2cap_chan_close(struct l2cap_chan *chan, int reason);
> >>>>>>>>> int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> >>>>>>>>>                  bdaddr_t *dst, u8 dst_type);
> >>>>>>>>> +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu);
> >>>>>>>>> int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len);
> >>>>>>>>> void l2cap_chan_busy(struct l2cap_chan *chan, int busy);
> >>>>>>>>> int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator);
> >>>>>>>>> diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
> >>>>>>>>> index 195459a1e53e..a528526cffac 100644
> >>>>>>>>> --- a/net/bluetooth/l2cap_core.c
> >>>>>>>>> +++ b/net/bluetooth/l2cap_core.c
> >>>>>>>>> @@ -532,6 +532,17 @@ static void l2cap_le_flowctl_init(struct l2cap_chan *chan, u16 tx_credits)
> >>>>>>>>>   skb_queue_head_init(&chan->tx_q);
> >>>>>>>>> }
> >>>>>>>>>
> >>>>>>>>> +static void l2cap_ecred_init(struct l2cap_chan *chan, u16 tx_credits)
> >>>>>>>>> +{
> >>>>>>>>> +     l2cap_le_flowctl_init(chan, tx_credits);
> >>>>>>>>> +
> >>>>>>>>> +     /* L2CAP implementations shall support a minimum MPS of 64 octets */
> >>>>>>>>> +     if (chan->mps < L2CAP_ECRED_MIN_MPS) {
> >>>>>>>>> +             chan->mps = L2CAP_ECRED_MIN_MPS;
> >>>>>>>>> +             chan->rx_credits = (chan->imtu / chan->mps) + 1;
> >>>>>>>>> +     }
> >>>>>>>>> +}
> >>>>>>>>> +
> >>>>>>>>> void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
> >>>>>>>>> {
> >>>>>>>>>   BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn,
> >>>>>>>>> @@ -638,6 +649,7 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
> >>>>>>>>>           break;
> >>>>>>>>>
> >>>>>>>>>   case L2CAP_MODE_LE_FLOWCTL:
> >>>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>>>>>>>           skb_queue_purge(&chan->tx_q);
> >>>>>>>>>           break;
> >>>>>>>>>
> >>>>>>>>> @@ -1260,6 +1272,17 @@ static void l2cap_move_done(struct l2cap_chan *chan)
> >>>>>>>>>   }
> >>>>>>>>> }
> >>>>>>>>>
> >>>>>>>>> +static bool l2cap_mode_ready(struct l2cap_chan *chan)
> >>>>>>>>> +{
> >>>>>>>>> +     switch (chan->mode) {
> >>>>>>>>> +     case L2CAP_MODE_LE_FLOWCTL:
> >>>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>>>>>>> +             return chan->tx_credits ? true : false;
> >>>>>>>>> +     }
> >>>>>>>>> +
> >>>>>>>>> +     return true;
> >>>>>>>>> +}
> >>>>>>>>> +
> >>>>>>>>> static void l2cap_chan_ready(struct l2cap_chan *chan)
> >>>>>>>>> {
> >>>>>>>>>   /* The channel may have already been flagged as connected in
> >>>>>>>>> @@ -1273,7 +1296,7 @@ static void l2cap_chan_ready(struct l2cap_chan *chan)
> >>>>>>>>>   chan->conf_state = 0;
> >>>>>>>>>   __clear_chan_timer(chan);
> >>>>>>>>>
> >>>>>>>>> -     if (chan->mode == L2CAP_MODE_LE_FLOWCTL && !chan->tx_credits)
> >>>>>>>>> +     if (!l2cap_mode_ready(chan))
> >>>>>>>>>           chan->ops->suspend(chan);
> >>>>>>>>
> >>>>>>>>     switch (chan->mode) {
> >>>>>>>>     case ..
> >>>>>>>>     case ..
> >>>>>>>>             if (!chan->tx_credits)
> >>>>>>>>                     chan->ops->suspend(chan);
> >>>>>>>>             break;
> >>>>>>>>     }
> >>>>>>>>
> >>>>>>>>>
> >>>>>>>>>   chan->state = BT_CONNECTED;
> >>>>>>>>> @@ -1306,6 +1329,31 @@ static void l2cap_le_connect(struct l2cap_chan *chan)
> >>>>>>>>>                  sizeof(req), &req);
> >>>>>>>>> }
> >>>>>>>>>
> >>>>>>>>> +static void l2cap_ecred_connect(struct l2cap_chan *chan)
> >>>>>>>>> +{
> >>>>>>>>> +     struct l2cap_conn *conn = chan->conn;
> >>>>>>>>> +     struct {
> >>>>>>>>> +             struct l2cap_ecred_conn_req req;
> >>>>>>>>> +             __le16 scid;
> >>>>>>>>> +     } __packed pdu;
> >>>>>>>>> +
> >>>>>>>>> +     if (test_and_set_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags))
> >>>>>>>>> +             return;
> >>>>>>>>> +
> >>>>>>>>> +     l2cap_ecred_init(chan, 0);
> >>>>>>>>> +
> >>>>>>>>> +     pdu.req.psm     = chan->psm;
> >>>>>>>>> +     pdu.req.mtu     = cpu_to_le16(chan->imtu);
> >>>>>>>>> +     pdu.req.mps     = cpu_to_le16(chan->mps);
> >>>>>>>>> +     pdu.req.credits = cpu_to_le16(chan->rx_credits);
> >>>>>>>>> +     pdu.scid        = cpu_to_le16(chan->scid);
> >>>>>>>>> +
> >>>>>>>>> +     chan->ident = l2cap_get_ident(conn);
> >>>>>>>>> +
> >>>>>>>>> +     l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_CONN_REQ,
> >>>>>>>>> +                    sizeof(pdu), &pdu);
> >>>>>>>>> +}
> >>>>>>>>> +
> >>>>>>>>> static void l2cap_le_start(struct l2cap_chan *chan)
> >>>>>>>>> {
> >>>>>>>>>   struct l2cap_conn *conn = chan->conn;
> >>>>>>>>> @@ -1318,8 +1366,12 @@ static void l2cap_le_start(struct l2cap_chan *chan)
> >>>>>>>>>           return;
> >>>>>>>>>   }
> >>>>>>>>>
> >>>>>>>>> -     if (chan->state == BT_CONNECT)
> >>>>>>>>> -             l2cap_le_connect(chan);
> >>>>>>>>> +     if (chan->state == BT_CONNECT) {
> >>>>>>>>> +             if (chan->mode == L2CAP_MODE_EXT_FLOWCTL)
> >>>>>>>>> +                     l2cap_ecred_connect(chan);
> >>>>>>>>> +             else
> >>>>>>>>> +                     l2cap_le_connect(chan);
> >>>>>>>>> +     }
> >>>>>>>>> }
> >>>>>>>>>
> >>>>>>>>> static void l2cap_start_connection(struct l2cap_chan *chan)
> >>>>>>>>> @@ -2505,6 +2557,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
> >>>>>>>>>
> >>>>>>>>>   switch (chan->mode) {
> >>>>>>>>>   case L2CAP_MODE_LE_FLOWCTL:
> >>>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>>>>>>>           /* Check outgoing MTU */
> >>>>>>>>>           if (len > chan->omtu)
> >>>>>>>>>                   return -EMSGSIZE;
> >>>>>>>>> @@ -3773,6 +3826,42 @@ void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan)
> >>>>>>>>>                  &rsp);
> >>>>>>>>> }
> >>>>>>>>>
> >>>>>>>>> +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
> >>>>>>>>> +{
> >>>>>>>>> +     struct l2cap_ecred_conn_rsp rsp;
> >>>>>>>>> +     struct l2cap_conn *conn = chan->conn;
> >>>>>>>>> +     u16 ident = chan->ident;
> >>>>>>>>> +     int i = 0;
> >>>>>>>>> +
> >>>>>>>>> +     if (!ident) {
> >>>>>>>>> +             return;
> >>>>>>>>> +     }
> >>>>>>>>> +
> >>>>>>>>
> >>>>>>>> No { } here.
> >>>>>>>>
> >>>>>>>>> +     BT_DBG("chan %p ident %d", chan, ident);
> >>>>>>>>> +
> >>>>>>>>> +     rsp.mtu     = cpu_to_le16(chan->imtu);
> >>>>>>>>> +     rsp.mps     = cpu_to_le16(chan->mps);
> >>>>>>>>> +     rsp.credits = cpu_to_le16(chan->rx_credits);
> >>>>>>>>> +     rsp.result  = cpu_to_le16(L2CAP_CR_LE_SUCCESS);
> >>>>>>>>> +
> >>>>>>>>> +     mutex_lock(&conn->chan_lock);
> >>>>>>>>> +
> >>>>>>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
> >>>>>>>>> +             if (chan->ident != ident)
> >>>>>>>>> +                     continue;
> >>>>>>>>> +
> >>>>>>>>> +             /* Reset ident so only one response is sent */
> >>>>>>>>> +             chan->ident = 0;
> >>>>>>>>> +
> >>>>>>>>> +             /* Include all channels pending with the same ident */
> >>>>>>>>> +             rsp.dcid[i++] = cpu_to_le16(chan->scid);
> >>>>>>>>
> >>>>>>>> This doesn’t work. The sizeof(rsp) has no dcid in there. You are overflowing into conn struct here.
> >>>>>>>>
> >>>>>>>>> +     }
> >>>>>>>>> +
> >>>>>>>>> +     mutex_unlock(&conn->chan_lock);
> >>>>>>>>> +
> >>>>>>>>> +     l2cap_send_cmd(conn, ident, L2CAP_ECRED_CONN_RSP, sizeof(rsp), &rsp);
> >>>>>>>>> +}
> >>>>>>>>> +
> >>>>>>>>> void __l2cap_connect_rsp_defer(struct l2cap_chan *chan)
> >>>>>>>>> {
> >>>>>>>>>   struct l2cap_conn_rsp rsp;
> >>>>>>>>> @@ -5714,6 +5803,347 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn,
> >>>>>>>>>   return 0;
> >>>>>>>>> }
> >>>>>>>>>
> >>>>>>>>> +static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
> >>>>>>>>> +                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >>>>>>>>> +                                    u8 *data)
> >>>>>>>>> +{
> >>>>>>>>> +     struct l2cap_ecred_conn_req *req = (void *) data;
> >>>>>>>>> +     struct {
> >>>>>>>>> +             struct l2cap_ecred_conn_rsp rsp;
> >>>>>>>>> +             __le16 dcid[5];
> >>>>>>>>> +     } __packed pdu;
> >>>>>>>>> +     struct l2cap_chan *chan, *pchan;
> >>>>>>>>> +     u16 credits, mtu, mps;
> >>>>>>>>> +     __le16 psm;
> >>>>>>>>> +     u8 result, len = 0;
> >>>>>>>>> +     int i;
> >>>>>>>>> +     bool defer = false;
> >>>>>>>>> +
> >>>>>>>>> +     if (cmd_len < sizeof(*req))
> >>>>>>>>> +             return -EPROTO;
> >>>>>>>>> +
> >>>>>>>>> +     mtu  = __le16_to_cpu(req->mtu);
> >>>>>>>>> +     mps  = __le16_to_cpu(req->mps);
> >>>>>>>>> +
> >>>>>>>>> +     if (mtu < L2CAP_ECRED_MIN_MTU || mps < L2CAP_ECRED_MIN_MPS) {
> >>>>>>>>> +             result = L2CAP_CR_LE_UNACCEPT_PARAMS;
> >>>>>>>>> +             goto response;
> >>>>>>>>> +     }
> >>>>>>>>> +
> >>>>>>>>> +     psm  = req->psm;
> >>>>>>>>> +     credits = 0;
> >>>>>>>>> +
> >>>>>>>>> +     BT_DBG("psm 0x%2.2x mtu %u mps %u", __le16_to_cpu(psm), mtu, mps);
> >>>>>>>>> +
> >>>>>>>>> +     memset(&pdu, 0, sizeof(pdu));
> >>>>>>>>> +
> >>>>>>>>> +     /* Check if we have socket listening on psm */
> >>>>>>>>> +     pchan = l2cap_global_chan_by_psm(BT_LISTEN, psm, &conn->hcon->src,
> >>>>>>>>> +                                      &conn->hcon->dst, LE_LINK);
> >>>>>>>>> +     if (!pchan) {
> >>>>>>>>> +             result = L2CAP_CR_LE_BAD_PSM;
> >>>>>>>>> +             goto response;
> >>>>>>>>> +     }
> >>>>>>>>> +
> >>>>>>>>> +     mutex_lock(&conn->chan_lock);
> >>>>>>>>> +     l2cap_chan_lock(pchan);
> >>>>>>>>> +
> >>>>>>>>> +     if (!smp_sufficient_security(conn->hcon, pchan->sec_level,
> >>>>>>>>> +                                  SMP_ALLOW_STK)) {
> >>>>>>>>> +             result = L2CAP_CR_LE_AUTHENTICATION;
> >>>>>>>>> +             goto unlock;
> >>>>>>>>> +     }
> >>>>>>>>> +
> >>>>>>>>> +     result = L2CAP_CR_LE_SUCCESS;
> >>>>>>>>> +
> >>>>>>>>> +     for (i = 0, cmd_len -= sizeof(req); cmd_len >= sizeof(u16);
> >>>>>>>>> +          i++, cmd_len -= sizeof(u16)) {
> >>>>>>>>
> >>>>>>>> Can we simplify the loop statement or switch to while statement?
> >>>>>>>>
> >>>>>>>>> +             u16 scid = __le16_to_cpu(req->scid[i]);
> >>>>>>>>> +
> >>>>>>>>> +             BT_DBG("scid[%d] 0x%4.4x", i, scid);
> >>>>>>>>> +
> >>>>>>>>> +             pdu.dcid[i] = 0x0000;
> >>>>>>>>> +             len += sizeof(*pdu.dcid);
> >>>>>>>>> +
> >>>>>>>>> +             /* Check for valid dynamic CID range */
> >>>>>>>>> +             if (scid < L2CAP_CID_DYN_START || scid > L2CAP_CID_LE_DYN_END) {
> >>>>>>>>> +                     result = L2CAP_CR_LE_INVALID_SCID;
> >>>>>>>>> +                     continue;
> >>>>>>>>> +             }
> >>>>>>>>> +
> >>>>>>>>> +             /* Check if we already have channel with that dcid */
> >>>>>>>>> +             if (__l2cap_get_chan_by_dcid(conn, scid)) {
> >>>>>>>>> +                     result = L2CAP_CR_LE_SCID_IN_USE;
> >>>>>>>>> +                     continue;
> >>>>>>>>> +             }
> >>>>>>>>> +
> >>>>>>>>> +             chan = pchan->ops->new_connection(pchan);
> >>>>>>>>> +             if (!chan) {
> >>>>>>>>> +                     result = L2CAP_CR_LE_NO_MEM;
> >>>>>>>>> +                     continue;
> >>>>>>>>> +             }
> >>>>>>>>> +
> >>>>>>>>> +             bacpy(&chan->src, &conn->hcon->src);
> >>>>>>>>> +             bacpy(&chan->dst, &conn->hcon->dst);
> >>>>>>>>> +             chan->src_type = bdaddr_src_type(conn->hcon);
> >>>>>>>>> +             chan->dst_type = bdaddr_dst_type(conn->hcon);
> >>>>>>>>> +             chan->psm  = psm;
> >>>>>>>>> +             chan->dcid = scid;
> >>>>>>>>> +             chan->omtu = mtu;
> >>>>>>>>> +             chan->remote_mps = mps;
> >>>>>>>>> +
> >>>>>>>>> +             __l2cap_chan_add(conn, chan);
> >>>>>>>>> +
> >>>>>>>>> +             l2cap_ecred_init(chan, __le16_to_cpu(req->credits));
> >>>>>>>>> +
> >>>>>>>>> +             /* Init response */
> >>>>>>>>> +             if (!pdu.rsp.credits) {
> >>>>>>>>> +                     pdu.rsp.mtu = cpu_to_le16(chan->imtu);
> >>>>>>>>> +                     pdu.rsp.mps = cpu_to_le16(chan->mps);
> >>>>>>>>> +                     pdu.rsp.credits = cpu_to_le16(chan->rx_credits);
> >>>>>>>>> +             }
> >>>>>>>>> +
> >>>>>>>>> +             pdu.dcid[i] = cpu_to_le16(chan->scid);
> >>>>>>>>> +
> >>>>>>>>> +             __set_chan_timer(chan, chan->ops->get_sndtimeo(chan));
> >>>>>>>>> +
> >>>>>>>>> +             chan->ident = cmd->ident;
> >>>>>>>>> +
> >>>>>>>>> +             if (test_bit(FLAG_DEFER_SETUP, &chan->flags)) {
> >>>>>>>>> +                     l2cap_state_change(chan, BT_CONNECT2);
> >>>>>>>>> +                     defer = true;
> >>>>>>>>> +                     chan->ops->defer(chan);
> >>>>>>>>> +             } else {
> >>>>>>>>> +                     l2cap_chan_ready(chan);
> >>>>>>>>> +             }
> >>>>>>>>> +     }
> >>>>>>>>> +
> >>>>>>>>> +unlock:
> >>>>>>>>> +     l2cap_chan_unlock(pchan);
> >>>>>>>>> +     mutex_unlock(&conn->chan_lock);
> >>>>>>>>> +     l2cap_chan_put(pchan);
> >>>>>>>>> +
> >>>>>>>>> +response:
> >>>>>>>>> +     pdu.rsp.result = cpu_to_le16(result);
> >>>>>>>>> +
> >>>>>>>>> +     if (defer)
> >>>>>>>>> +             return 0;
> >>>>>>>>> +
> >>>>>>>>> +     l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_CONN_RSP,
> >>>>>>>>> +                    sizeof(pdu.rsp) + len, &pdu);
> >>>>>>>>> +
> >>>>>>>>> +     return 0;
> >>>>>>>>> +}
> >>>>>>>>> +
> >>>>>>>>> +static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
> >>>>>>>>> +                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >>>>>>>>> +                                    u8 *data)
> >>>>>>>>> +{
> >>>>>>>>> +     struct l2cap_ecred_conn_rsp *rsp = (void *) data;
> >>>>>>>>> +     struct hci_conn *hcon = conn->hcon;
> >>>>>>>>> +     u16 mtu, mps, credits, result;
> >>>>>>>>> +     struct l2cap_chan *chan;
> >>>>>>>>> +     int err = 0, sec_level;
> >>>>>>>>> +     int i = 0;
> >>>>>>>>> +
> >>>>>>>>> +     if (cmd_len < sizeof(*rsp))
> >>>>>>>>> +             return -EPROTO;
> >>>>>>>>> +
> >>>>>>>>> +     mtu     = __le16_to_cpu(rsp->mtu);
> >>>>>>>>> +     mps     = __le16_to_cpu(rsp->mps);
> >>>>>>>>> +     credits = __le16_to_cpu(rsp->credits);
> >>>>>>>>> +     result  = __le16_to_cpu(rsp->result);
> >>>>>>>>> +
> >>>>>>>>> +     BT_DBG("mtu %u mps %u credits %u result 0x%4.4x", mtu, mps, credits,
> >>>>>>>>> +            result);
> >>>>>>>>> +
> >>>>>>>>> +     mutex_lock(&conn->chan_lock);
> >>>>>>>>> +
> >>>>>>>>> +     cmd_len -= sizeof(*rsp);
> >>>>>>>>> +
> >>>>>>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
> >>>>>>>>> +             u16 dcid;
> >>>>>>>>> +
> >>>>>>>>> +             if (chan->ident != cmd->ident ||
> >>>>>>>>> +                 chan->mode != L2CAP_MODE_EXT_FLOWCTL ||
> >>>>>>>>> +                 chan->state == BT_CONNECTED)
> >>>>>>>>> +                     continue;
> >>>>>>>>> +
> >>>>>>>>> +             l2cap_chan_lock(chan);
> >>>>>>>>> +
> >>>>>>>>> +             /* Check that there is a dcid for each pending channel */
> >>>>>>>>> +             if (cmd_len < sizeof(dcid)) {
> >>>>>>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
> >>>>>>>>> +                     l2cap_chan_unlock(chan);
> >>>>>>>>> +                     continue;
> >>>>>>>>> +             }
> >>>>>>>>> +
> >>>>>>>>> +             dcid = __le16_to_cpu(rsp->dcid[i++]);
> >>>>>>>>> +             cmd_len -= sizeof(u16);
> >>>>>>>>> +
> >>>>>>>>> +             BT_DBG("dcid[%d] 0x%4.4x", i, dcid);
> >>>>>>>>> +
> >>>>>>>>> +             /* Check if dcid is already in use */
> >>>>>>>>> +             if (dcid && __l2cap_get_chan_by_dcid(conn, dcid)) {
> >>>>>>>>> +                     /* If a device receives a
> >>>>>>>>> +                      * L2CAP_CREDIT_BASED_CONNECTION_RSP packet with an
> >>>>>>>>> +                      * already-assigned Destination CID, then both the
> >>>>>>>>> +                      * original channel and the new channel shall be
> >>>>>>>>> +                      * immediately discarded and not used.
> >>>>>>>>> +                      */
> >>>>>>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
> >>>>>>>>> +                     l2cap_chan_unlock(chan);
> >>>>>>>>> +                     chan = __l2cap_get_chan_by_dcid(conn, dcid);
> >>>>>>>>> +                     l2cap_chan_lock(chan);
> >>>>>>>>> +                     l2cap_chan_del(chan, ECONNRESET);
> >>>>>>>>> +                     l2cap_chan_unlock(chan);
> >>>>>>>>> +                     continue;
> >>>>>>>>> +             }
> >>>>>>>>> +
> >>>>>>>>> +             switch (result) {
> >>>>>>>>> +             case L2CAP_CR_LE_AUTHENTICATION:
> >>>>>>>>> +             case L2CAP_CR_LE_ENCRYPTION:
> >>>>>>>>> +                     /* If we already have MITM protection we can't do
> >>>>>>>>> +                      * anything.
> >>>>>>>>> +                      */
> >>>>>>>>> +                     if (hcon->sec_level > BT_SECURITY_MEDIUM) {
> >>>>>>>>> +                             l2cap_chan_del(chan, ECONNREFUSED);
> >>>>>>>>> +                             break;
> >>>>>>>>> +                     }
> >>>>>>>>> +
> >>>>>>>>> +                     sec_level = hcon->sec_level + 1;
> >>>>>>>>> +                     if (chan->sec_level < sec_level)
> >>>>>>>>> +                             chan->sec_level = sec_level;
> >>>>>>>>> +
> >>>>>>>>> +                     /* We'll need to send a new Connect Request */
> >>>>>>>>> +                     clear_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags);
> >>>>>>>>> +
> >>>>>>>>> +                     smp_conn_security(hcon, chan->sec_level);
> >>>>>>>>> +                     break;
> >>>>>>>>> +
> >>>>>>>>> +             case L2CAP_CR_LE_BAD_PSM:
> >>>>>>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
> >>>>>>>>> +                     break;
> >>>>>>>>> +
> >>>>>>>>> +             default:
> >>>>>>>>> +                     /* If dcid was not set it means channels was refused */
> >>>>>>>>> +                     if (!dcid) {
> >>>>>>>>> +                             l2cap_chan_del(chan, ECONNREFUSED);
> >>>>>>>>> +                             break;
> >>>>>>>>> +                     }
> >>>>>>>>> +
> >>>>>>>>> +                     chan->ident = 0;
> >>>>>>>>> +                     chan->dcid = dcid;
> >>>>>>>>> +                     chan->omtu = mtu;
> >>>>>>>>> +                     chan->remote_mps = mps;
> >>>>>>>>> +                     chan->tx_credits = credits;
> >>>>>>>>> +                     l2cap_chan_ready(chan);
> >>>>>>>>> +                     break;
> >>>>>>>>> +             }
> >>>>>>>>> +
> >>>>>>>>> +             l2cap_chan_unlock(chan);
> >>>>>>>>> +     }
> >>>>>>>>> +
> >>>>>>>>> +     mutex_unlock(&conn->chan_lock);
> >>>>>>>>> +
> >>>>>>>>> +     return err;
> >>>>>>>>> +}
> >>>>>>>>> +
> >>>>>>>>> +static inline int l2cap_ecred_reconf_req(struct l2cap_conn *conn,
> >>>>>>>>> +                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >>>>>>>>> +                                      u8 *data)
> >>>>>>>>> +{
> >>>>>>>>> +     struct l2cap_ecred_reconf_req *req = (void *) data;
> >>>>>>>>> +     struct l2cap_ecred_reconf_rsp rsp;
> >>>>>>>>> +     u16 mtu, mps, result;
> >>>>>>>>> +     struct l2cap_chan *chan;
> >>>>>>>>> +     int i;
> >>>>>>>>> +
> >>>>>>>>> +     if (cmd_len < sizeof(*req)) {
> >>>>>>>>> +             result = L2CAP_CR_LE_INVALID_PARAMS;
> >>>>>>>>> +             goto respond;
> >>>>>>>>> +     }
> >>>>>>>>> +
> >>>>>>>>> +     mtu = __le16_to_cpu(req->mtu);
> >>>>>>>>> +     mps = __le16_to_cpu(req->mps);
> >>>>>>>>> +
> >>>>>>>>> +     BT_DBG("mtu %u mps %u", mtu, mps);
> >>>>>>>>> +
> >>>>>>>>> +     if (mtu < L2CAP_ECRED_MIN_MTU) {
> >>>>>>>>> +             result = L2CAP_RECONF_INVALID_MTU;
> >>>>>>>>> +             goto respond;
> >>>>>>>>> +     }
> >>>>>>>>> +
> >>>>>>>>> +     if (mps < L2CAP_ECRED_MIN_MPS) {
> >>>>>>>>> +             result = L2CAP_RECONF_INVALID_MPS;
> >>>>>>>>> +             goto respond;
> >>>>>>>>> +     }
> >>>>>>>>> +
> >>>>>>>>> +     result = L2CAP_RECONF_SUCCESS;
> >>>>>>>>> +
> >>>>>>>>> +     for (i = 0, cmd_len -= sizeof(*req); cmd_len < sizeof(u16);
> >>>>>>>>> +          i++, cmd_len -= sizeof(u16)) {
> >>>>>>>>
> >>>>>>>> Same comment as above.
> >>>>>>>>
> >>>>>>>>> +             u16 scid;
> >>>>>>>>> +
> >>>>>>>>> +             scid = __le16_to_cpu(req->scid[i]);
> >>>>>>>>> +             if (!scid)
> >>>>>>>>> +                     return -EPROTO;
> >>>>>>>>> +
> >>>>>>>>> +             chan = __l2cap_get_chan_by_dcid(conn, scid);
> >>>>>>>>> +             if (!chan)
> >>>>>>>>> +                     continue;
> >>>>>>>>> +
> >>>>>>>>> +             /* If the MTU value is decreased for any of the included
> >>>>>>>>> +              * channels, then the receiver shall disconnect all
> >>>>>>>>> +              * included channels.
> >>>>>>>>> +              */
> >>>>>>>>> +             if (chan->omtu > mtu) {
> >>>>>>>>> +                     BT_ERR("chan %p decreased MTU %u -> %u", chan,
> >>>>>>>>> +                            chan->omtu, mtu);
> >>>>>>>>> +                     result = L2CAP_RECONF_INVALID_MTU;
> >>>>>>>>> +             }
> >>>>>>>>> +
> >>>>>>>>> +             chan->omtu = mtu;
> >>>>>>>>> +             chan->remote_mps = mps;
> >>>>>>>>> +     }
> >>>>>>>>> +
> >>>>>>>>> +respond:
> >>>>>>>>> +     rsp.result = cpu_to_le16(result);
> >>>>>>>>> +
> >>>>>>>>> +     l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_RECONF_RSP, sizeof(rsp),
> >>>>>>>>> +                    &rsp);
> >>>>>>>>> +
> >>>>>>>>> +     return 0;
> >>>>>>>>> +}
> >>>>>>>>> +
> >>>>>>>>> +static inline int l2cap_ecred_reconf_rsp(struct l2cap_conn *conn,
> >>>>>>>>> +                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >>>>>>>>> +                                      u8 *data)
> >>>>>>>>> +{
> >>>>>>>>> +     struct l2cap_chan *chan;
> >>>>>>>>> +     struct l2cap_ecred_conn_rsp *rsp = (void *) data;
> >>>>>>>>> +     u16 result;
> >>>>>>>>> +
> >>>>>>>>> +     if (cmd_len < sizeof(*rsp))
> >>>>>>>>> +             return -EPROTO;
> >>>>>>>>> +
> >>>>>>>>> +     result = __le16_to_cpu(rsp->result);
> >>>>>>>>> +
> >>>>>>>>> +     BT_DBG("result 0x%4.4x", rsp->result);
> >>>>>>>>> +
> >>>>>>>>> +     if (!result)
> >>>>>>>>> +             return 0;
> >>>>>>>>> +
> >>>>>>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
> >>>>>>>>> +             if (chan->ident != cmd->ident)
> >>>>>>>>> +                     continue;
> >>>>>>>>> +
> >>>>>>>>> +             l2cap_chan_del(chan, ECONNRESET);
> >>>>>>>>> +     }
> >>>>>>>>> +
> >>>>>>>>> +     return 0;
> >>>>>>>>> +}
> >>>>>>>>> +
> >>>>>>>>> static inline int l2cap_le_command_rej(struct l2cap_conn *conn,
> >>>>>>>>>                                  struct l2cap_cmd_hdr *cmd, u16 cmd_len,
> >>>>>>>>>                                  u8 *data)
> >>>>>>>>> @@ -5769,6 +6199,22 @@ static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn,
> >>>>>>>>>           err = l2cap_le_credits(conn, cmd, cmd_len, data);
> >>>>>>>>>           break;
> >>>>>>>>>
> >>>>>>>>> +     case L2CAP_ECRED_CONN_REQ:
> >>>>>>>>> +             err = l2cap_ecred_conn_req(conn, cmd, cmd_len, data);
> >>>>>>>>> +             break;
> >>>>>>>>> +
> >>>>>>>>> +     case L2CAP_ECRED_CONN_RSP:
> >>>>>>>>> +             err = l2cap_ecred_conn_rsp(conn, cmd, cmd_len, data);
> >>>>>>>>> +             break;
> >>>>>>>>> +
> >>>>>>>>> +     case L2CAP_ECRED_RECONF_REQ:
> >>>>>>>>> +             err = l2cap_ecred_reconf_req(conn, cmd, cmd_len, data);
> >>>>>>>>> +             break;
> >>>>>>>>> +
> >>>>>>>>> +     case L2CAP_ECRED_RECONF_RSP:
> >>>>>>>>> +             err = l2cap_ecred_reconf_rsp(conn, cmd, cmd_len, data);
> >>>>>>>>> +             break;
> >>>>>>>>> +
> >>>>>>>>>   case L2CAP_DISCONN_REQ:
> >>>>>>>>>           err = l2cap_disconnect_req(conn, cmd, cmd_len, data);
> >>>>>>>>>           break;
> >>>>>>>>> @@ -6814,11 +7260,13 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
> >>>>>>>>>   struct l2cap_le_credits pkt;
> >>>>>>>>>   u16 return_credits;
> >>>>>>>>>
> >>>>>>>>> -     return_credits = ((chan->imtu / chan->mps) + 1) - chan->rx_credits;
> >>>>>>>>> +     return_credits = (chan->imtu / chan->mps) + 1;
> >>>>>>>>>
> >>>>>>>>> -     if (!return_credits)
> >>>>>>>>> +     if (chan->rx_credits >= return_credits)
> >>>>>>>>>           return;
> >>>>>>>>>
> >>>>>>>>> +     return_credits -= chan->rx_credits;
> >>>>>>>>> +
> >>>>>>>>>   BT_DBG("chan %p returning %u credits to sender", chan, return_credits);
> >>>>>>>>>
> >>>>>>>>>   chan->rx_credits += return_credits;
> >>>>>>>>> @@ -6831,7 +7279,7 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
> >>>>>>>>>   l2cap_send_cmd(conn, chan->ident, L2CAP_LE_CREDITS, sizeof(pkt), &pkt);
> >>>>>>>>> }
> >>>>>>>>>
> >>>>>>>>> -static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>>>>>> +static int l2cap_ecred_recv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>>>>>> {
> >>>>>>>>>   int err;
> >>>>>>>>>
> >>>>>>>>> @@ -6846,7 +7294,7 @@ static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>>>>>>   return err;
> >>>>>>>>> }
> >>>>>>>>>
> >>>>>>>>> -static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>>>>>> +static int l2cap_ecred_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>>>>>> {
> >>>>>>>>>   int err;
> >>>>>>>>>
> >>>>>>>>> @@ -6894,7 +7342,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>>>>>>           }
> >>>>>>>>>
> >>>>>>>>>           if (skb->len == sdu_len)
> >>>>>>>>> -                     return l2cap_le_recv(chan, skb);
> >>>>>>>>> +                     return l2cap_ecred_recv(chan, skb);
> >>>>>>>>>
> >>>>>>>>>           chan->sdu = skb;
> >>>>>>>>>           chan->sdu_len = sdu_len;
> >>>>>>>>> @@ -6926,7 +7374,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
> >>>>>>>>>   skb = NULL;
> >>>>>>>>>
> >>>>>>>>>   if (chan->sdu->len == chan->sdu_len) {
> >>>>>>>>> -             err = l2cap_le_recv(chan, chan->sdu);
> >>>>>>>>> +             err = l2cap_ecred_recv(chan, chan->sdu);
> >>>>>>>>>           if (!err) {
> >>>>>>>>>                   chan->sdu = NULL;
> >>>>>>>>>                   chan->sdu_last_frag = NULL;
> >>>>>>>>> @@ -6987,7 +7435,8 @@ static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid,
> >>>>>>>>>
> >>>>>>>>>   switch (chan->mode) {
> >>>>>>>>>   case L2CAP_MODE_LE_FLOWCTL:
> >>>>>>>>> -             if (l2cap_le_data_rcv(chan, skb) < 0)
> >>>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>>>>>>> +             if (l2cap_ecred_data_rcv(chan, skb) < 0)
> >>>>>>>>>                   goto drop;
> >>>>>>>>>
> >>>>>>>>>           goto done;
> >>>>>>>>> @@ -7214,8 +7663,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> >>>>>>>>>   struct hci_dev *hdev;
> >>>>>>>>>   int err;
> >>>>>>>>>
> >>>>>>>>> -     BT_DBG("%pMR -> %pMR (type %u) psm 0x%2.2x", &chan->src, dst,
> >>>>>>>>> -            dst_type, __le16_to_cpu(psm));
> >>>>>>>>> +     BT_DBG("%pMR -> %pMR (type %u) psm 0x%4.4x mode 0x%2.2x", &chan->src,
> >>>>>>>>> +            dst, dst_type, __le16_to_cpu(psm), chan->mode);
> >>>>>>>>>
> >>>>>>>>>   hdev = hci_get_route(dst, &chan->src, chan->src_type);
> >>>>>>>>>   if (!hdev)
> >>>>>>>>> @@ -7244,6 +7693,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> >>>>>>>>>           break;
> >>>>>>>>>   case L2CAP_MODE_LE_FLOWCTL:
> >>>>>>>>>           break;
> >>>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>>>>>>> +             break;
> >>>>>>>>
> >>>>>>>> No need to do another break. Just put it under LE_FLOWCTL.
> >>>>>>>>
> >>>>>>>>>   case L2CAP_MODE_ERTM:
> >>>>>>>>>   case L2CAP_MODE_STREAMING:
> >>>>>>>>>           if (!disable_ertm)
> >>>>>>>>> @@ -7368,6 +7819,38 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
> >>>>>>>>> }
> >>>>>>>>> EXPORT_SYMBOL_GPL(l2cap_chan_connect);
> >>>>>>>>>
> >>>>>>>>> +static void l2cap_ecred_reconfigure(struct l2cap_chan *chan)
> >>>>>>>>> +{
> >>>>>>>>> +     struct l2cap_conn *conn = chan->conn;
> >>>>>>>>> +     struct {
> >>>>>>>>> +             struct l2cap_ecred_reconf_req req;
> >>>>>>>>> +             __le16 scid;
> >>>>>>>>> +     } pdu;
> >>>>>>>>> +
> >>>>>>>>> +     pdu.req.mtu = cpu_to_le16(chan->imtu);
> >>>>>>>>> +     pdu.req.mps = cpu_to_le16(chan->mps);
> >>>>>>>>> +     pdu.scid    = cpu_to_le16(chan->scid);
> >>>>>>>>> +
> >>>>>>>>> +     chan->ident = l2cap_get_ident(conn);
> >>>>>>>>> +
> >>>>>>>>> +     l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_RECONF_REQ,
> >>>>>>>>> +                    sizeof(pdu), &pdu);
> >>>>>>>>> +}
> >>>>>>>>> +
> >>>>>>>>> +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu)
> >>>>>>>>> +{
> >>>>>>>>> +     if (chan->imtu > mtu)
> >>>>>>>>> +             return -EINVAL;
> >>>>>>>>> +
> >>>>>>>>> +     BT_DBG("chan %p mtu 0x%4.4x", chan, mtu);
> >>>>>>>>> +
> >>>>>>>>> +     chan->imtu = mtu;
> >>>>>>>>> +
> >>>>>>>>> +     l2cap_ecred_reconfigure(chan);
> >>>>>>>>> +
> >>>>>>>>> +     return 0;
> >>>>>>>>> +}
> >>>>>>>>> +
> >>>>>>>>> /* ---- L2CAP interface with lower layer (HCI) ---- */
> >>>>>>>>>
> >>>>>>>>> int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr)
> >>>>>>>>> @@ -7579,7 +8062,8 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
> >>>>>>>>>                   else
> >>>>>>>>>                           __set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
> >>>>>>>>>           } else if (chan->state == BT_CONNECT2 &&
> >>>>>>>>> -                        chan->mode != L2CAP_MODE_LE_FLOWCTL) {
> >>>>>>>>> +                        !(chan->mode == L2CAP_MODE_EXT_FLOWCTL ||
> >>>>>>>>> +                         chan->mode == L2CAP_MODE_LE_FLOWCTL)) {
> >>>>>>>>
> >>>>>>>> Please double check that this line is correctly aligned after the (.
> >>>>>>>>
> >>>>>>>>>                   struct l2cap_conn_rsp rsp;
> >>>>>>>>>                   __u16 res, stat;
> >>>>>>>>>
> >>>>>>>>> diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
> >>>>>>>>> index a7be8b59b3c2..12c557f7f40f 100644
> >>>>>>>>> --- a/net/bluetooth/l2cap_sock.c
> >>>>>>>>> +++ b/net/bluetooth/l2cap_sock.c
> >>>>>>>>> @@ -232,7 +232,7 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr,
> >>>>>>>>>                   return -EINVAL;
> >>>>>>>>>   }
> >>>>>>>>>
> >>>>>>>>> -     if (chan->psm && bdaddr_type_is_le(chan->src_type))
> >>>>>>>>> +     if (chan->psm && bdaddr_type_is_le(chan->src_type) && !chan->mode)
> >>>>>>>>>           chan->mode = L2CAP_MODE_LE_FLOWCTL;
> >>>>>>>>>
> >>>>>>>>>   err = l2cap_chan_connect(chan, la.l2_psm, __le16_to_cpu(la.l2_cid),
> >>>>>>>>> @@ -273,6 +273,7 @@ static int l2cap_sock_listen(struct socket *sock, int backlog)
> >>>>>>>>>   switch (chan->mode) {
> >>>>>>>>>   case L2CAP_MODE_BASIC:
> >>>>>>>>>   case L2CAP_MODE_LE_FLOWCTL:
> >>>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
> >>>>>>>>>           break;
> >>>>>>>>>   case L2CAP_MODE_ERTM:
> >>>>>>>>>   case L2CAP_MODE_STREAMING:
> >>>>>>>>> @@ -408,16 +409,6 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
> >>>>>>>>>
> >>>>>>>>>   switch (optname) {
> >>>>>>>>>   case L2CAP_OPTIONS:
> >>>>>>>>> -             /* LE sockets should use BT_SNDMTU/BT_RCVMTU, but since
> >>>>>>>>> -              * legacy ATT code depends on getsockopt for
> >>>>>>>>> -              * L2CAP_OPTIONS we need to let this pass.
> >>>>>>>>> -              */
> >>>>>>>>> -             if (bdaddr_type_is_le(chan->src_type) &&
> >>>>>>>>> -                 chan->scid != L2CAP_CID_ATT) {
> >>>>>>>>> -                     err = -EINVAL;
> >>>>>>>>> -                     break;
> >>>>>>>>> -             }
> >>>>>>>>> -
> >>>>>>>>
> >>>>>>>> Should this be a separate patch. How do we keep this legacy behavior.
> >>>>>>>
> >>>>>>> I kind have forgotten to fix this one, I guess we will need a new
> >>>>>>> option in order to read the mode without using the old L2CAP_OPTIONS
> >>>>>>> then, or do you have anything against introducing yet another option?
> >>>>>>
> >>>>>> so what is the problem here? Is this because L2CAP by itself can not tell if enhanced credit mode is supported by the peer?
> >>>>>
> >>>>> Yep, afaik there is no means to detect the support transparently,
> >>>>> otherwise could always prefer to use EXT_FLOWCTL instead of
> >>>>> LE_FLOWCTL, it seems at the point we introduced LE_FLOWCTL option we
> >>>>> had always assumed there would not be any mode one could use when the
> >>>>> address type is LE.
> >>>>>
> >>>>>> Do you actually want to create a new mode socket options with defined values by us to allow selected the mode of operation?
> >>>>>
> >>>>> I woud assume that is the only safe option here, though what I did was
> >>>>> to enable L2CAP_OPTIONS to work with LE addresses which I assume is
> >>>>> safe as previously it would cause an error no one would be using it.
> >>>>
> >>>> that is what I was afraid of. So can we use BT_SNDMTU and BT_RCVMTU and then only a mode property is missing? Or do we have other options that we want to configure.
> >>>
> >>> Not sure I follow, do you prefer chaning an existing option to add a
> >>> new field? I though having an option to set the mode would be better
> >>> but perhaps you want to combine it something else, well maybe some
> >>> sort of QOS interface, the problem is that the field are quite
> >>> different depending on the mode so Im afraid that would take a lot
> >>> more time to define.
> >>
> >> Let me ask this differently. What options do we want to be able to configure if we deprecate L2CAP_OPTIONS?
> >
> > Coming back to this perhaps we could infer the mode if in case there
> > are multiple connection being made, the only question is if the spec
> > actually allow connection with the lagacy command if the remote side
> > is listen on PSM that is for ECRED mode like EATT, if it does require
> > maybe we can use BT_DEFER_SETUP to trigger it but I don't really like
> > reusing BT_DEFER_SETUP like that since it was introduced for a very
> > different purpose it may confuse people.
>
> you still haven’t answered my question. What kind of options do we want the socket user to configure?

The current solution is using just the mode so it could detect if the
PSM is intended to use the new signalling or not, the spec don't seem
to be very clear if connection made to the PSM shall only accept the
new signalling or not, in theory I guess it doesn't since the PSM
domain is global connecting with legacy signalling should work as
well, it just don't allow connecting multiple sockets at once.

So my thingking is, if the there are pending l2cap_chan with
DEFER_SETUP set then use the new command, otherwise stick to the old
one, so the new signalling commands would only really be used when
there is more than one socket connecting, how about that?

> >> Anyhow, maybe we first introduce a debugfs option that says attempt_ecred_mode_first and only when the attempt fails fall back to credit based flow control mode.
> >
> > I was thinking in something like the following so we only use the new
> > mode if it is enabled, well provided we can remove it later:
> >
> > https://gist.github.com/Vudentz/35ca4a39b58e64e8ecbde3ff6ada2d61
>
> That looks fine to me.
>
> >> Btw. do we have at least a supported features bit in L2CAP hinting that ecred mode is supported over BR/EDR. It would be good to have some way of knowing it for BR/EDR ahead of time.
> >
> > I don't remember it being added, but yes it would make a lot of sense
> > to have it when using over BR/EDR.
>
> If not, then we should file an erratum for it.
>
> >>>> For the multi-connect case, I was thinking that we use something similar to DEFER_SETUP. If we set a DEFER_SETUP on an outgoing socket, then you can call connect() on a socket and succeeds. However no POLLIN | POLLOUT would be signaled. When calling a connect() without that option set, then all sockets become ready at the same time on success.
> >>>
> >>> As initiator I don't think we have a good match in the socket API as
> >>> it only return one fd at time, so currently it still doing the
> >>> connection one by one, what I though we could do is to delay a little
> >>> to group the request in a single L2CAP request, thought that creates
> >>> an extra latency for initiating the connection and can be racy.
> >>> Another option would be to allow bind to bind to multiple sockets at
> >>> once, so when you issue the connect in one socket it would connect all
> >>> socket bound.
> >>
> >> Let me try to explain this in pseudo code.
> >>
> >>        fd1 = socket();
> >>        setsockopt(fd1, BT_DEFER_SETUP);
> >>        connect(fd1);
> >>        0 = poll(fd1);
> >>
> >>        fd2 = socket();
> >>        setsockopt(fd2, BT_DEFER_SETUP);
> >>        connect(fd2);
> >>        0 = poll(fd2);
> >>
> >>        fd3 = socket();
> >>        connect(fd3);
> >>        POLLOUT = poll(fd1);
> >>        POLLOUT = poll(fd2);
> >>        POLLOUT = poll(fd3);
> >>
> >> Essentially all the connect() call with BT_DEFER_SETUP will succeed, but POLLOUT will not signal. So you have the fd, but you can not write to it. When we have the connect() without BT_DEFER_SETUP set, then L2CAP will connect all channels at the same time and assign them to the given sockets. And now all fds become POLLOUT.
> >
> > Should work, btw couldn't we just use bind for the first 2 sockets?
> > That imo would make things a little cleaner since we only do connect
> > when it is really ready to connect and we don't have to even consider
> > poll until then, that said we would need to group these sockets either
> > way so we need to do this per processs, actually the order would be:
> >
> > fd# = socket()
> > bind(fd#)
> > setsockopt(fd#, BT_DEFER_SETUP)
> >
> > Since bind sets the address then on setsockopt we can check if there
> > is anything else pending to be connected for the same address and then
> > just proceed.
>
> With bind() you set the local address. With connect() you select the remote address. I don’t see how bind() only would get us there.
>
> And the poll() part is not a problem btw. We should be doing asynchronous write/send anyway. It is just that we have been a bit lazy with some of the protocol implementation and execute write/send right away without checking with poll if the socket is actually writable. If we fix that then this will be all dead simple. It is no different than an asynchronous connect() anyway. There you have to wait for poll() as well. I really doubt we are using blocking connect() calls. And if we do, we need to fix that.

You are right, here is a draft implementing this (not tested):

https://gist.github.com/Vudentz/5f4e34358f7a76518c054cfcd6bbf4fd

-- 
Luiz Augusto von Dentz

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] Bluetooth: L2CAP: Add initial code for Enhanced Credit Based Mode
  2020-02-21 18:26                     ` Luiz Augusto von Dentz
@ 2020-02-22  9:44                       ` Marcel Holtmann
  0 siblings, 0 replies; 14+ messages in thread
From: Marcel Holtmann @ 2020-02-22  9:44 UTC (permalink / raw)
  To: Luiz Augusto von Dentz; +Cc: linux-bluetooth

Hi Luiz,

>>>>>>>>>>> This adds the initial code for Enhanced Credit Based Mode which
>>>>>>>>>>> introduces a new socket mode called L2CAP_MODE_EXT_FLOWCTL, which for
>>>>>>>>>>> the most part work the same as L2CAP_MODE_LE_FLOWCTL but uses different
>>>>>>>>>>> PDUs to setup the connections and also works over BR/EDR.
>>>>>>>>>>> 
>>>>>>>>>>> Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
>>>>>>>>>>> ---
>>>>>>>>>>> include/net/bluetooth/l2cap.h |   4 +
>>>>>>>>>>> net/bluetooth/l2cap_core.c    | 510 +++++++++++++++++++++++++++++++++-
>>>>>>>>>>> net/bluetooth/l2cap_sock.c    |  39 +--
>>>>>>>>>>> 3 files changed, 521 insertions(+), 32 deletions(-)
>>>>>>>>>>> 
>>>>>>>>>>> diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
>>>>>>>>>>> index b9ea88cfd2d2..11ae4c93354b 100644
>>>>>>>>>>> --- a/include/net/bluetooth/l2cap.h
>>>>>>>>>>> +++ b/include/net/bluetooth/l2cap.h
>>>>>>>>>>> @@ -295,6 +295,8 @@ struct l2cap_conn_rsp {
>>>>>>>>>>> #define L2CAP_CR_LE_ENCRYPTION                0x0008
>>>>>>>>>>> #define L2CAP_CR_LE_INVALID_SCID      0x0009
>>>>>>>>>>> #define L2CAP_CR_LE_SCID_IN_USE               0X000A
>>>>>>>>>>> +#define L2CAP_CR_LE_UNACCEPT_PARAMS  0X000B
>>>>>>>>>>> +#define L2CAP_CR_LE_INVALID_PARAMS   0X000C
>>>>>>>>>>> 
>>>>>>>>>>> /* connect/create channel status */
>>>>>>>>>>> #define L2CAP_CS_NO_INFO      0x0000
>>>>>>>>>>> @@ -964,6 +966,7 @@ void l2cap_cleanup_sockets(void);
>>>>>>>>>>> bool l2cap_is_socket(struct socket *sock);
>>>>>>>>>>> 
>>>>>>>>>>> void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan);
>>>>>>>>>>> +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan);
>>>>>>>>>>> void __l2cap_connect_rsp_defer(struct l2cap_chan *chan);
>>>>>>>>>>> 
>>>>>>>>>>> int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm);
>>>>>>>>>>> @@ -973,6 +976,7 @@ struct l2cap_chan *l2cap_chan_create(void);
>>>>>>>>>>> void l2cap_chan_close(struct l2cap_chan *chan, int reason);
>>>>>>>>>>> int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
>>>>>>>>>>>                 bdaddr_t *dst, u8 dst_type);
>>>>>>>>>>> +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu);
>>>>>>>>>>> int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len);
>>>>>>>>>>> void l2cap_chan_busy(struct l2cap_chan *chan, int busy);
>>>>>>>>>>> int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator);
>>>>>>>>>>> diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
>>>>>>>>>>> index 195459a1e53e..a528526cffac 100644
>>>>>>>>>>> --- a/net/bluetooth/l2cap_core.c
>>>>>>>>>>> +++ b/net/bluetooth/l2cap_core.c
>>>>>>>>>>> @@ -532,6 +532,17 @@ static void l2cap_le_flowctl_init(struct l2cap_chan *chan, u16 tx_credits)
>>>>>>>>>>>  skb_queue_head_init(&chan->tx_q);
>>>>>>>>>>> }
>>>>>>>>>>> 
>>>>>>>>>>> +static void l2cap_ecred_init(struct l2cap_chan *chan, u16 tx_credits)
>>>>>>>>>>> +{
>>>>>>>>>>> +     l2cap_le_flowctl_init(chan, tx_credits);
>>>>>>>>>>> +
>>>>>>>>>>> +     /* L2CAP implementations shall support a minimum MPS of 64 octets */
>>>>>>>>>>> +     if (chan->mps < L2CAP_ECRED_MIN_MPS) {
>>>>>>>>>>> +             chan->mps = L2CAP_ECRED_MIN_MPS;
>>>>>>>>>>> +             chan->rx_credits = (chan->imtu / chan->mps) + 1;
>>>>>>>>>>> +     }
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
>>>>>>>>>>> {
>>>>>>>>>>>  BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn,
>>>>>>>>>>> @@ -638,6 +649,7 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
>>>>>>>>>>>          break;
>>>>>>>>>>> 
>>>>>>>>>>>  case L2CAP_MODE_LE_FLOWCTL:
>>>>>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>>>>>>>          skb_queue_purge(&chan->tx_q);
>>>>>>>>>>>          break;
>>>>>>>>>>> 
>>>>>>>>>>> @@ -1260,6 +1272,17 @@ static void l2cap_move_done(struct l2cap_chan *chan)
>>>>>>>>>>>  }
>>>>>>>>>>> }
>>>>>>>>>>> 
>>>>>>>>>>> +static bool l2cap_mode_ready(struct l2cap_chan *chan)
>>>>>>>>>>> +{
>>>>>>>>>>> +     switch (chan->mode) {
>>>>>>>>>>> +     case L2CAP_MODE_LE_FLOWCTL:
>>>>>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>>>>>>> +             return chan->tx_credits ? true : false;
>>>>>>>>>>> +     }
>>>>>>>>>>> +
>>>>>>>>>>> +     return true;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> static void l2cap_chan_ready(struct l2cap_chan *chan)
>>>>>>>>>>> {
>>>>>>>>>>>  /* The channel may have already been flagged as connected in
>>>>>>>>>>> @@ -1273,7 +1296,7 @@ static void l2cap_chan_ready(struct l2cap_chan *chan)
>>>>>>>>>>>  chan->conf_state = 0;
>>>>>>>>>>>  __clear_chan_timer(chan);
>>>>>>>>>>> 
>>>>>>>>>>> -     if (chan->mode == L2CAP_MODE_LE_FLOWCTL && !chan->tx_credits)
>>>>>>>>>>> +     if (!l2cap_mode_ready(chan))
>>>>>>>>>>>          chan->ops->suspend(chan);
>>>>>>>>>> 
>>>>>>>>>>    switch (chan->mode) {
>>>>>>>>>>    case ..
>>>>>>>>>>    case ..
>>>>>>>>>>            if (!chan->tx_credits)
>>>>>>>>>>                    chan->ops->suspend(chan);
>>>>>>>>>>            break;
>>>>>>>>>>    }
>>>>>>>>>> 
>>>>>>>>>>> 
>>>>>>>>>>>  chan->state = BT_CONNECTED;
>>>>>>>>>>> @@ -1306,6 +1329,31 @@ static void l2cap_le_connect(struct l2cap_chan *chan)
>>>>>>>>>>>                 sizeof(req), &req);
>>>>>>>>>>> }
>>>>>>>>>>> 
>>>>>>>>>>> +static void l2cap_ecred_connect(struct l2cap_chan *chan)
>>>>>>>>>>> +{
>>>>>>>>>>> +     struct l2cap_conn *conn = chan->conn;
>>>>>>>>>>> +     struct {
>>>>>>>>>>> +             struct l2cap_ecred_conn_req req;
>>>>>>>>>>> +             __le16 scid;
>>>>>>>>>>> +     } __packed pdu;
>>>>>>>>>>> +
>>>>>>>>>>> +     if (test_and_set_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags))
>>>>>>>>>>> +             return;
>>>>>>>>>>> +
>>>>>>>>>>> +     l2cap_ecred_init(chan, 0);
>>>>>>>>>>> +
>>>>>>>>>>> +     pdu.req.psm     = chan->psm;
>>>>>>>>>>> +     pdu.req.mtu     = cpu_to_le16(chan->imtu);
>>>>>>>>>>> +     pdu.req.mps     = cpu_to_le16(chan->mps);
>>>>>>>>>>> +     pdu.req.credits = cpu_to_le16(chan->rx_credits);
>>>>>>>>>>> +     pdu.scid        = cpu_to_le16(chan->scid);
>>>>>>>>>>> +
>>>>>>>>>>> +     chan->ident = l2cap_get_ident(conn);
>>>>>>>>>>> +
>>>>>>>>>>> +     l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_CONN_REQ,
>>>>>>>>>>> +                    sizeof(pdu), &pdu);
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> static void l2cap_le_start(struct l2cap_chan *chan)
>>>>>>>>>>> {
>>>>>>>>>>>  struct l2cap_conn *conn = chan->conn;
>>>>>>>>>>> @@ -1318,8 +1366,12 @@ static void l2cap_le_start(struct l2cap_chan *chan)
>>>>>>>>>>>          return;
>>>>>>>>>>>  }
>>>>>>>>>>> 
>>>>>>>>>>> -     if (chan->state == BT_CONNECT)
>>>>>>>>>>> -             l2cap_le_connect(chan);
>>>>>>>>>>> +     if (chan->state == BT_CONNECT) {
>>>>>>>>>>> +             if (chan->mode == L2CAP_MODE_EXT_FLOWCTL)
>>>>>>>>>>> +                     l2cap_ecred_connect(chan);
>>>>>>>>>>> +             else
>>>>>>>>>>> +                     l2cap_le_connect(chan);
>>>>>>>>>>> +     }
>>>>>>>>>>> }
>>>>>>>>>>> 
>>>>>>>>>>> static void l2cap_start_connection(struct l2cap_chan *chan)
>>>>>>>>>>> @@ -2505,6 +2557,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
>>>>>>>>>>> 
>>>>>>>>>>>  switch (chan->mode) {
>>>>>>>>>>>  case L2CAP_MODE_LE_FLOWCTL:
>>>>>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>>>>>>>          /* Check outgoing MTU */
>>>>>>>>>>>          if (len > chan->omtu)
>>>>>>>>>>>                  return -EMSGSIZE;
>>>>>>>>>>> @@ -3773,6 +3826,42 @@ void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan)
>>>>>>>>>>>                 &rsp);
>>>>>>>>>>> }
>>>>>>>>>>> 
>>>>>>>>>>> +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
>>>>>>>>>>> +{
>>>>>>>>>>> +     struct l2cap_ecred_conn_rsp rsp;
>>>>>>>>>>> +     struct l2cap_conn *conn = chan->conn;
>>>>>>>>>>> +     u16 ident = chan->ident;
>>>>>>>>>>> +     int i = 0;
>>>>>>>>>>> +
>>>>>>>>>>> +     if (!ident) {
>>>>>>>>>>> +             return;
>>>>>>>>>>> +     }
>>>>>>>>>>> +
>>>>>>>>>> 
>>>>>>>>>> No { } here.
>>>>>>>>>> 
>>>>>>>>>>> +     BT_DBG("chan %p ident %d", chan, ident);
>>>>>>>>>>> +
>>>>>>>>>>> +     rsp.mtu     = cpu_to_le16(chan->imtu);
>>>>>>>>>>> +     rsp.mps     = cpu_to_le16(chan->mps);
>>>>>>>>>>> +     rsp.credits = cpu_to_le16(chan->rx_credits);
>>>>>>>>>>> +     rsp.result  = cpu_to_le16(L2CAP_CR_LE_SUCCESS);
>>>>>>>>>>> +
>>>>>>>>>>> +     mutex_lock(&conn->chan_lock);
>>>>>>>>>>> +
>>>>>>>>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
>>>>>>>>>>> +             if (chan->ident != ident)
>>>>>>>>>>> +                     continue;
>>>>>>>>>>> +
>>>>>>>>>>> +             /* Reset ident so only one response is sent */
>>>>>>>>>>> +             chan->ident = 0;
>>>>>>>>>>> +
>>>>>>>>>>> +             /* Include all channels pending with the same ident */
>>>>>>>>>>> +             rsp.dcid[i++] = cpu_to_le16(chan->scid);
>>>>>>>>>> 
>>>>>>>>>> This doesn’t work. The sizeof(rsp) has no dcid in there. You are overflowing into conn struct here.
>>>>>>>>>> 
>>>>>>>>>>> +     }
>>>>>>>>>>> +
>>>>>>>>>>> +     mutex_unlock(&conn->chan_lock);
>>>>>>>>>>> +
>>>>>>>>>>> +     l2cap_send_cmd(conn, ident, L2CAP_ECRED_CONN_RSP, sizeof(rsp), &rsp);
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> void __l2cap_connect_rsp_defer(struct l2cap_chan *chan)
>>>>>>>>>>> {
>>>>>>>>>>>  struct l2cap_conn_rsp rsp;
>>>>>>>>>>> @@ -5714,6 +5803,347 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn,
>>>>>>>>>>>  return 0;
>>>>>>>>>>> }
>>>>>>>>>>> 
>>>>>>>>>>> +static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
>>>>>>>>>>> +                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>>>>>>>>> +                                    u8 *data)
>>>>>>>>>>> +{
>>>>>>>>>>> +     struct l2cap_ecred_conn_req *req = (void *) data;
>>>>>>>>>>> +     struct {
>>>>>>>>>>> +             struct l2cap_ecred_conn_rsp rsp;
>>>>>>>>>>> +             __le16 dcid[5];
>>>>>>>>>>> +     } __packed pdu;
>>>>>>>>>>> +     struct l2cap_chan *chan, *pchan;
>>>>>>>>>>> +     u16 credits, mtu, mps;
>>>>>>>>>>> +     __le16 psm;
>>>>>>>>>>> +     u8 result, len = 0;
>>>>>>>>>>> +     int i;
>>>>>>>>>>> +     bool defer = false;
>>>>>>>>>>> +
>>>>>>>>>>> +     if (cmd_len < sizeof(*req))
>>>>>>>>>>> +             return -EPROTO;
>>>>>>>>>>> +
>>>>>>>>>>> +     mtu  = __le16_to_cpu(req->mtu);
>>>>>>>>>>> +     mps  = __le16_to_cpu(req->mps);
>>>>>>>>>>> +
>>>>>>>>>>> +     if (mtu < L2CAP_ECRED_MIN_MTU || mps < L2CAP_ECRED_MIN_MPS) {
>>>>>>>>>>> +             result = L2CAP_CR_LE_UNACCEPT_PARAMS;
>>>>>>>>>>> +             goto response;
>>>>>>>>>>> +     }
>>>>>>>>>>> +
>>>>>>>>>>> +     psm  = req->psm;
>>>>>>>>>>> +     credits = 0;
>>>>>>>>>>> +
>>>>>>>>>>> +     BT_DBG("psm 0x%2.2x mtu %u mps %u", __le16_to_cpu(psm), mtu, mps);
>>>>>>>>>>> +
>>>>>>>>>>> +     memset(&pdu, 0, sizeof(pdu));
>>>>>>>>>>> +
>>>>>>>>>>> +     /* Check if we have socket listening on psm */
>>>>>>>>>>> +     pchan = l2cap_global_chan_by_psm(BT_LISTEN, psm, &conn->hcon->src,
>>>>>>>>>>> +                                      &conn->hcon->dst, LE_LINK);
>>>>>>>>>>> +     if (!pchan) {
>>>>>>>>>>> +             result = L2CAP_CR_LE_BAD_PSM;
>>>>>>>>>>> +             goto response;
>>>>>>>>>>> +     }
>>>>>>>>>>> +
>>>>>>>>>>> +     mutex_lock(&conn->chan_lock);
>>>>>>>>>>> +     l2cap_chan_lock(pchan);
>>>>>>>>>>> +
>>>>>>>>>>> +     if (!smp_sufficient_security(conn->hcon, pchan->sec_level,
>>>>>>>>>>> +                                  SMP_ALLOW_STK)) {
>>>>>>>>>>> +             result = L2CAP_CR_LE_AUTHENTICATION;
>>>>>>>>>>> +             goto unlock;
>>>>>>>>>>> +     }
>>>>>>>>>>> +
>>>>>>>>>>> +     result = L2CAP_CR_LE_SUCCESS;
>>>>>>>>>>> +
>>>>>>>>>>> +     for (i = 0, cmd_len -= sizeof(req); cmd_len >= sizeof(u16);
>>>>>>>>>>> +          i++, cmd_len -= sizeof(u16)) {
>>>>>>>>>> 
>>>>>>>>>> Can we simplify the loop statement or switch to while statement?
>>>>>>>>>> 
>>>>>>>>>>> +             u16 scid = __le16_to_cpu(req->scid[i]);
>>>>>>>>>>> +
>>>>>>>>>>> +             BT_DBG("scid[%d] 0x%4.4x", i, scid);
>>>>>>>>>>> +
>>>>>>>>>>> +             pdu.dcid[i] = 0x0000;
>>>>>>>>>>> +             len += sizeof(*pdu.dcid);
>>>>>>>>>>> +
>>>>>>>>>>> +             /* Check for valid dynamic CID range */
>>>>>>>>>>> +             if (scid < L2CAP_CID_DYN_START || scid > L2CAP_CID_LE_DYN_END) {
>>>>>>>>>>> +                     result = L2CAP_CR_LE_INVALID_SCID;
>>>>>>>>>>> +                     continue;
>>>>>>>>>>> +             }
>>>>>>>>>>> +
>>>>>>>>>>> +             /* Check if we already have channel with that dcid */
>>>>>>>>>>> +             if (__l2cap_get_chan_by_dcid(conn, scid)) {
>>>>>>>>>>> +                     result = L2CAP_CR_LE_SCID_IN_USE;
>>>>>>>>>>> +                     continue;
>>>>>>>>>>> +             }
>>>>>>>>>>> +
>>>>>>>>>>> +             chan = pchan->ops->new_connection(pchan);
>>>>>>>>>>> +             if (!chan) {
>>>>>>>>>>> +                     result = L2CAP_CR_LE_NO_MEM;
>>>>>>>>>>> +                     continue;
>>>>>>>>>>> +             }
>>>>>>>>>>> +
>>>>>>>>>>> +             bacpy(&chan->src, &conn->hcon->src);
>>>>>>>>>>> +             bacpy(&chan->dst, &conn->hcon->dst);
>>>>>>>>>>> +             chan->src_type = bdaddr_src_type(conn->hcon);
>>>>>>>>>>> +             chan->dst_type = bdaddr_dst_type(conn->hcon);
>>>>>>>>>>> +             chan->psm  = psm;
>>>>>>>>>>> +             chan->dcid = scid;
>>>>>>>>>>> +             chan->omtu = mtu;
>>>>>>>>>>> +             chan->remote_mps = mps;
>>>>>>>>>>> +
>>>>>>>>>>> +             __l2cap_chan_add(conn, chan);
>>>>>>>>>>> +
>>>>>>>>>>> +             l2cap_ecred_init(chan, __le16_to_cpu(req->credits));
>>>>>>>>>>> +
>>>>>>>>>>> +             /* Init response */
>>>>>>>>>>> +             if (!pdu.rsp.credits) {
>>>>>>>>>>> +                     pdu.rsp.mtu = cpu_to_le16(chan->imtu);
>>>>>>>>>>> +                     pdu.rsp.mps = cpu_to_le16(chan->mps);
>>>>>>>>>>> +                     pdu.rsp.credits = cpu_to_le16(chan->rx_credits);
>>>>>>>>>>> +             }
>>>>>>>>>>> +
>>>>>>>>>>> +             pdu.dcid[i] = cpu_to_le16(chan->scid);
>>>>>>>>>>> +
>>>>>>>>>>> +             __set_chan_timer(chan, chan->ops->get_sndtimeo(chan));
>>>>>>>>>>> +
>>>>>>>>>>> +             chan->ident = cmd->ident;
>>>>>>>>>>> +
>>>>>>>>>>> +             if (test_bit(FLAG_DEFER_SETUP, &chan->flags)) {
>>>>>>>>>>> +                     l2cap_state_change(chan, BT_CONNECT2);
>>>>>>>>>>> +                     defer = true;
>>>>>>>>>>> +                     chan->ops->defer(chan);
>>>>>>>>>>> +             } else {
>>>>>>>>>>> +                     l2cap_chan_ready(chan);
>>>>>>>>>>> +             }
>>>>>>>>>>> +     }
>>>>>>>>>>> +
>>>>>>>>>>> +unlock:
>>>>>>>>>>> +     l2cap_chan_unlock(pchan);
>>>>>>>>>>> +     mutex_unlock(&conn->chan_lock);
>>>>>>>>>>> +     l2cap_chan_put(pchan);
>>>>>>>>>>> +
>>>>>>>>>>> +response:
>>>>>>>>>>> +     pdu.rsp.result = cpu_to_le16(result);
>>>>>>>>>>> +
>>>>>>>>>>> +     if (defer)
>>>>>>>>>>> +             return 0;
>>>>>>>>>>> +
>>>>>>>>>>> +     l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_CONN_RSP,
>>>>>>>>>>> +                    sizeof(pdu.rsp) + len, &pdu);
>>>>>>>>>>> +
>>>>>>>>>>> +     return 0;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
>>>>>>>>>>> +                                    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>>>>>>>>> +                                    u8 *data)
>>>>>>>>>>> +{
>>>>>>>>>>> +     struct l2cap_ecred_conn_rsp *rsp = (void *) data;
>>>>>>>>>>> +     struct hci_conn *hcon = conn->hcon;
>>>>>>>>>>> +     u16 mtu, mps, credits, result;
>>>>>>>>>>> +     struct l2cap_chan *chan;
>>>>>>>>>>> +     int err = 0, sec_level;
>>>>>>>>>>> +     int i = 0;
>>>>>>>>>>> +
>>>>>>>>>>> +     if (cmd_len < sizeof(*rsp))
>>>>>>>>>>> +             return -EPROTO;
>>>>>>>>>>> +
>>>>>>>>>>> +     mtu     = __le16_to_cpu(rsp->mtu);
>>>>>>>>>>> +     mps     = __le16_to_cpu(rsp->mps);
>>>>>>>>>>> +     credits = __le16_to_cpu(rsp->credits);
>>>>>>>>>>> +     result  = __le16_to_cpu(rsp->result);
>>>>>>>>>>> +
>>>>>>>>>>> +     BT_DBG("mtu %u mps %u credits %u result 0x%4.4x", mtu, mps, credits,
>>>>>>>>>>> +            result);
>>>>>>>>>>> +
>>>>>>>>>>> +     mutex_lock(&conn->chan_lock);
>>>>>>>>>>> +
>>>>>>>>>>> +     cmd_len -= sizeof(*rsp);
>>>>>>>>>>> +
>>>>>>>>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
>>>>>>>>>>> +             u16 dcid;
>>>>>>>>>>> +
>>>>>>>>>>> +             if (chan->ident != cmd->ident ||
>>>>>>>>>>> +                 chan->mode != L2CAP_MODE_EXT_FLOWCTL ||
>>>>>>>>>>> +                 chan->state == BT_CONNECTED)
>>>>>>>>>>> +                     continue;
>>>>>>>>>>> +
>>>>>>>>>>> +             l2cap_chan_lock(chan);
>>>>>>>>>>> +
>>>>>>>>>>> +             /* Check that there is a dcid for each pending channel */
>>>>>>>>>>> +             if (cmd_len < sizeof(dcid)) {
>>>>>>>>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
>>>>>>>>>>> +                     l2cap_chan_unlock(chan);
>>>>>>>>>>> +                     continue;
>>>>>>>>>>> +             }
>>>>>>>>>>> +
>>>>>>>>>>> +             dcid = __le16_to_cpu(rsp->dcid[i++]);
>>>>>>>>>>> +             cmd_len -= sizeof(u16);
>>>>>>>>>>> +
>>>>>>>>>>> +             BT_DBG("dcid[%d] 0x%4.4x", i, dcid);
>>>>>>>>>>> +
>>>>>>>>>>> +             /* Check if dcid is already in use */
>>>>>>>>>>> +             if (dcid && __l2cap_get_chan_by_dcid(conn, dcid)) {
>>>>>>>>>>> +                     /* If a device receives a
>>>>>>>>>>> +                      * L2CAP_CREDIT_BASED_CONNECTION_RSP packet with an
>>>>>>>>>>> +                      * already-assigned Destination CID, then both the
>>>>>>>>>>> +                      * original channel and the new channel shall be
>>>>>>>>>>> +                      * immediately discarded and not used.
>>>>>>>>>>> +                      */
>>>>>>>>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
>>>>>>>>>>> +                     l2cap_chan_unlock(chan);
>>>>>>>>>>> +                     chan = __l2cap_get_chan_by_dcid(conn, dcid);
>>>>>>>>>>> +                     l2cap_chan_lock(chan);
>>>>>>>>>>> +                     l2cap_chan_del(chan, ECONNRESET);
>>>>>>>>>>> +                     l2cap_chan_unlock(chan);
>>>>>>>>>>> +                     continue;
>>>>>>>>>>> +             }
>>>>>>>>>>> +
>>>>>>>>>>> +             switch (result) {
>>>>>>>>>>> +             case L2CAP_CR_LE_AUTHENTICATION:
>>>>>>>>>>> +             case L2CAP_CR_LE_ENCRYPTION:
>>>>>>>>>>> +                     /* If we already have MITM protection we can't do
>>>>>>>>>>> +                      * anything.
>>>>>>>>>>> +                      */
>>>>>>>>>>> +                     if (hcon->sec_level > BT_SECURITY_MEDIUM) {
>>>>>>>>>>> +                             l2cap_chan_del(chan, ECONNREFUSED);
>>>>>>>>>>> +                             break;
>>>>>>>>>>> +                     }
>>>>>>>>>>> +
>>>>>>>>>>> +                     sec_level = hcon->sec_level + 1;
>>>>>>>>>>> +                     if (chan->sec_level < sec_level)
>>>>>>>>>>> +                             chan->sec_level = sec_level;
>>>>>>>>>>> +
>>>>>>>>>>> +                     /* We'll need to send a new Connect Request */
>>>>>>>>>>> +                     clear_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags);
>>>>>>>>>>> +
>>>>>>>>>>> +                     smp_conn_security(hcon, chan->sec_level);
>>>>>>>>>>> +                     break;
>>>>>>>>>>> +
>>>>>>>>>>> +             case L2CAP_CR_LE_BAD_PSM:
>>>>>>>>>>> +                     l2cap_chan_del(chan, ECONNREFUSED);
>>>>>>>>>>> +                     break;
>>>>>>>>>>> +
>>>>>>>>>>> +             default:
>>>>>>>>>>> +                     /* If dcid was not set it means channels was refused */
>>>>>>>>>>> +                     if (!dcid) {
>>>>>>>>>>> +                             l2cap_chan_del(chan, ECONNREFUSED);
>>>>>>>>>>> +                             break;
>>>>>>>>>>> +                     }
>>>>>>>>>>> +
>>>>>>>>>>> +                     chan->ident = 0;
>>>>>>>>>>> +                     chan->dcid = dcid;
>>>>>>>>>>> +                     chan->omtu = mtu;
>>>>>>>>>>> +                     chan->remote_mps = mps;
>>>>>>>>>>> +                     chan->tx_credits = credits;
>>>>>>>>>>> +                     l2cap_chan_ready(chan);
>>>>>>>>>>> +                     break;
>>>>>>>>>>> +             }
>>>>>>>>>>> +
>>>>>>>>>>> +             l2cap_chan_unlock(chan);
>>>>>>>>>>> +     }
>>>>>>>>>>> +
>>>>>>>>>>> +     mutex_unlock(&conn->chan_lock);
>>>>>>>>>>> +
>>>>>>>>>>> +     return err;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +static inline int l2cap_ecred_reconf_req(struct l2cap_conn *conn,
>>>>>>>>>>> +                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>>>>>>>>> +                                      u8 *data)
>>>>>>>>>>> +{
>>>>>>>>>>> +     struct l2cap_ecred_reconf_req *req = (void *) data;
>>>>>>>>>>> +     struct l2cap_ecred_reconf_rsp rsp;
>>>>>>>>>>> +     u16 mtu, mps, result;
>>>>>>>>>>> +     struct l2cap_chan *chan;
>>>>>>>>>>> +     int i;
>>>>>>>>>>> +
>>>>>>>>>>> +     if (cmd_len < sizeof(*req)) {
>>>>>>>>>>> +             result = L2CAP_CR_LE_INVALID_PARAMS;
>>>>>>>>>>> +             goto respond;
>>>>>>>>>>> +     }
>>>>>>>>>>> +
>>>>>>>>>>> +     mtu = __le16_to_cpu(req->mtu);
>>>>>>>>>>> +     mps = __le16_to_cpu(req->mps);
>>>>>>>>>>> +
>>>>>>>>>>> +     BT_DBG("mtu %u mps %u", mtu, mps);
>>>>>>>>>>> +
>>>>>>>>>>> +     if (mtu < L2CAP_ECRED_MIN_MTU) {
>>>>>>>>>>> +             result = L2CAP_RECONF_INVALID_MTU;
>>>>>>>>>>> +             goto respond;
>>>>>>>>>>> +     }
>>>>>>>>>>> +
>>>>>>>>>>> +     if (mps < L2CAP_ECRED_MIN_MPS) {
>>>>>>>>>>> +             result = L2CAP_RECONF_INVALID_MPS;
>>>>>>>>>>> +             goto respond;
>>>>>>>>>>> +     }
>>>>>>>>>>> +
>>>>>>>>>>> +     result = L2CAP_RECONF_SUCCESS;
>>>>>>>>>>> +
>>>>>>>>>>> +     for (i = 0, cmd_len -= sizeof(*req); cmd_len < sizeof(u16);
>>>>>>>>>>> +          i++, cmd_len -= sizeof(u16)) {
>>>>>>>>>> 
>>>>>>>>>> Same comment as above.
>>>>>>>>>> 
>>>>>>>>>>> +             u16 scid;
>>>>>>>>>>> +
>>>>>>>>>>> +             scid = __le16_to_cpu(req->scid[i]);
>>>>>>>>>>> +             if (!scid)
>>>>>>>>>>> +                     return -EPROTO;
>>>>>>>>>>> +
>>>>>>>>>>> +             chan = __l2cap_get_chan_by_dcid(conn, scid);
>>>>>>>>>>> +             if (!chan)
>>>>>>>>>>> +                     continue;
>>>>>>>>>>> +
>>>>>>>>>>> +             /* If the MTU value is decreased for any of the included
>>>>>>>>>>> +              * channels, then the receiver shall disconnect all
>>>>>>>>>>> +              * included channels.
>>>>>>>>>>> +              */
>>>>>>>>>>> +             if (chan->omtu > mtu) {
>>>>>>>>>>> +                     BT_ERR("chan %p decreased MTU %u -> %u", chan,
>>>>>>>>>>> +                            chan->omtu, mtu);
>>>>>>>>>>> +                     result = L2CAP_RECONF_INVALID_MTU;
>>>>>>>>>>> +             }
>>>>>>>>>>> +
>>>>>>>>>>> +             chan->omtu = mtu;
>>>>>>>>>>> +             chan->remote_mps = mps;
>>>>>>>>>>> +     }
>>>>>>>>>>> +
>>>>>>>>>>> +respond:
>>>>>>>>>>> +     rsp.result = cpu_to_le16(result);
>>>>>>>>>>> +
>>>>>>>>>>> +     l2cap_send_cmd(conn, cmd->ident, L2CAP_ECRED_RECONF_RSP, sizeof(rsp),
>>>>>>>>>>> +                    &rsp);
>>>>>>>>>>> +
>>>>>>>>>>> +     return 0;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +static inline int l2cap_ecred_reconf_rsp(struct l2cap_conn *conn,
>>>>>>>>>>> +                                      struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>>>>>>>>> +                                      u8 *data)
>>>>>>>>>>> +{
>>>>>>>>>>> +     struct l2cap_chan *chan;
>>>>>>>>>>> +     struct l2cap_ecred_conn_rsp *rsp = (void *) data;
>>>>>>>>>>> +     u16 result;
>>>>>>>>>>> +
>>>>>>>>>>> +     if (cmd_len < sizeof(*rsp))
>>>>>>>>>>> +             return -EPROTO;
>>>>>>>>>>> +
>>>>>>>>>>> +     result = __le16_to_cpu(rsp->result);
>>>>>>>>>>> +
>>>>>>>>>>> +     BT_DBG("result 0x%4.4x", rsp->result);
>>>>>>>>>>> +
>>>>>>>>>>> +     if (!result)
>>>>>>>>>>> +             return 0;
>>>>>>>>>>> +
>>>>>>>>>>> +     list_for_each_entry(chan, &conn->chan_l, list) {
>>>>>>>>>>> +             if (chan->ident != cmd->ident)
>>>>>>>>>>> +                     continue;
>>>>>>>>>>> +
>>>>>>>>>>> +             l2cap_chan_del(chan, ECONNRESET);
>>>>>>>>>>> +     }
>>>>>>>>>>> +
>>>>>>>>>>> +     return 0;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> static inline int l2cap_le_command_rej(struct l2cap_conn *conn,
>>>>>>>>>>>                                 struct l2cap_cmd_hdr *cmd, u16 cmd_len,
>>>>>>>>>>>                                 u8 *data)
>>>>>>>>>>> @@ -5769,6 +6199,22 @@ static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn,
>>>>>>>>>>>          err = l2cap_le_credits(conn, cmd, cmd_len, data);
>>>>>>>>>>>          break;
>>>>>>>>>>> 
>>>>>>>>>>> +     case L2CAP_ECRED_CONN_REQ:
>>>>>>>>>>> +             err = l2cap_ecred_conn_req(conn, cmd, cmd_len, data);
>>>>>>>>>>> +             break;
>>>>>>>>>>> +
>>>>>>>>>>> +     case L2CAP_ECRED_CONN_RSP:
>>>>>>>>>>> +             err = l2cap_ecred_conn_rsp(conn, cmd, cmd_len, data);
>>>>>>>>>>> +             break;
>>>>>>>>>>> +
>>>>>>>>>>> +     case L2CAP_ECRED_RECONF_REQ:
>>>>>>>>>>> +             err = l2cap_ecred_reconf_req(conn, cmd, cmd_len, data);
>>>>>>>>>>> +             break;
>>>>>>>>>>> +
>>>>>>>>>>> +     case L2CAP_ECRED_RECONF_RSP:
>>>>>>>>>>> +             err = l2cap_ecred_reconf_rsp(conn, cmd, cmd_len, data);
>>>>>>>>>>> +             break;
>>>>>>>>>>> +
>>>>>>>>>>>  case L2CAP_DISCONN_REQ:
>>>>>>>>>>>          err = l2cap_disconnect_req(conn, cmd, cmd_len, data);
>>>>>>>>>>>          break;
>>>>>>>>>>> @@ -6814,11 +7260,13 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
>>>>>>>>>>>  struct l2cap_le_credits pkt;
>>>>>>>>>>>  u16 return_credits;
>>>>>>>>>>> 
>>>>>>>>>>> -     return_credits = ((chan->imtu / chan->mps) + 1) - chan->rx_credits;
>>>>>>>>>>> +     return_credits = (chan->imtu / chan->mps) + 1;
>>>>>>>>>>> 
>>>>>>>>>>> -     if (!return_credits)
>>>>>>>>>>> +     if (chan->rx_credits >= return_credits)
>>>>>>>>>>>          return;
>>>>>>>>>>> 
>>>>>>>>>>> +     return_credits -= chan->rx_credits;
>>>>>>>>>>> +
>>>>>>>>>>>  BT_DBG("chan %p returning %u credits to sender", chan, return_credits);
>>>>>>>>>>> 
>>>>>>>>>>>  chan->rx_credits += return_credits;
>>>>>>>>>>> @@ -6831,7 +7279,7 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
>>>>>>>>>>>  l2cap_send_cmd(conn, chan->ident, L2CAP_LE_CREDITS, sizeof(pkt), &pkt);
>>>>>>>>>>> }
>>>>>>>>>>> 
>>>>>>>>>>> -static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>>>>>> +static int l2cap_ecred_recv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>>>>>> {
>>>>>>>>>>>  int err;
>>>>>>>>>>> 
>>>>>>>>>>> @@ -6846,7 +7294,7 @@ static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>>>>>>  return err;
>>>>>>>>>>> }
>>>>>>>>>>> 
>>>>>>>>>>> -static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>>>>>> +static int l2cap_ecred_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>>>>>> {
>>>>>>>>>>>  int err;
>>>>>>>>>>> 
>>>>>>>>>>> @@ -6894,7 +7342,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>>>>>>          }
>>>>>>>>>>> 
>>>>>>>>>>>          if (skb->len == sdu_len)
>>>>>>>>>>> -                     return l2cap_le_recv(chan, skb);
>>>>>>>>>>> +                     return l2cap_ecred_recv(chan, skb);
>>>>>>>>>>> 
>>>>>>>>>>>          chan->sdu = skb;
>>>>>>>>>>>          chan->sdu_len = sdu_len;
>>>>>>>>>>> @@ -6926,7 +7374,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
>>>>>>>>>>>  skb = NULL;
>>>>>>>>>>> 
>>>>>>>>>>>  if (chan->sdu->len == chan->sdu_len) {
>>>>>>>>>>> -             err = l2cap_le_recv(chan, chan->sdu);
>>>>>>>>>>> +             err = l2cap_ecred_recv(chan, chan->sdu);
>>>>>>>>>>>          if (!err) {
>>>>>>>>>>>                  chan->sdu = NULL;
>>>>>>>>>>>                  chan->sdu_last_frag = NULL;
>>>>>>>>>>> @@ -6987,7 +7435,8 @@ static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid,
>>>>>>>>>>> 
>>>>>>>>>>>  switch (chan->mode) {
>>>>>>>>>>>  case L2CAP_MODE_LE_FLOWCTL:
>>>>>>>>>>> -             if (l2cap_le_data_rcv(chan, skb) < 0)
>>>>>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>>>>>>> +             if (l2cap_ecred_data_rcv(chan, skb) < 0)
>>>>>>>>>>>                  goto drop;
>>>>>>>>>>> 
>>>>>>>>>>>          goto done;
>>>>>>>>>>> @@ -7214,8 +7663,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
>>>>>>>>>>>  struct hci_dev *hdev;
>>>>>>>>>>>  int err;
>>>>>>>>>>> 
>>>>>>>>>>> -     BT_DBG("%pMR -> %pMR (type %u) psm 0x%2.2x", &chan->src, dst,
>>>>>>>>>>> -            dst_type, __le16_to_cpu(psm));
>>>>>>>>>>> +     BT_DBG("%pMR -> %pMR (type %u) psm 0x%4.4x mode 0x%2.2x", &chan->src,
>>>>>>>>>>> +            dst, dst_type, __le16_to_cpu(psm), chan->mode);
>>>>>>>>>>> 
>>>>>>>>>>>  hdev = hci_get_route(dst, &chan->src, chan->src_type);
>>>>>>>>>>>  if (!hdev)
>>>>>>>>>>> @@ -7244,6 +7693,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
>>>>>>>>>>>          break;
>>>>>>>>>>>  case L2CAP_MODE_LE_FLOWCTL:
>>>>>>>>>>>          break;
>>>>>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>>>>>>> +             break;
>>>>>>>>>> 
>>>>>>>>>> No need to do another break. Just put it under LE_FLOWCTL.
>>>>>>>>>> 
>>>>>>>>>>>  case L2CAP_MODE_ERTM:
>>>>>>>>>>>  case L2CAP_MODE_STREAMING:
>>>>>>>>>>>          if (!disable_ertm)
>>>>>>>>>>> @@ -7368,6 +7819,38 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
>>>>>>>>>>> }
>>>>>>>>>>> EXPORT_SYMBOL_GPL(l2cap_chan_connect);
>>>>>>>>>>> 
>>>>>>>>>>> +static void l2cap_ecred_reconfigure(struct l2cap_chan *chan)
>>>>>>>>>>> +{
>>>>>>>>>>> +     struct l2cap_conn *conn = chan->conn;
>>>>>>>>>>> +     struct {
>>>>>>>>>>> +             struct l2cap_ecred_reconf_req req;
>>>>>>>>>>> +             __le16 scid;
>>>>>>>>>>> +     } pdu;
>>>>>>>>>>> +
>>>>>>>>>>> +     pdu.req.mtu = cpu_to_le16(chan->imtu);
>>>>>>>>>>> +     pdu.req.mps = cpu_to_le16(chan->mps);
>>>>>>>>>>> +     pdu.scid    = cpu_to_le16(chan->scid);
>>>>>>>>>>> +
>>>>>>>>>>> +     chan->ident = l2cap_get_ident(conn);
>>>>>>>>>>> +
>>>>>>>>>>> +     l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_RECONF_REQ,
>>>>>>>>>>> +                    sizeof(pdu), &pdu);
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu)
>>>>>>>>>>> +{
>>>>>>>>>>> +     if (chan->imtu > mtu)
>>>>>>>>>>> +             return -EINVAL;
>>>>>>>>>>> +
>>>>>>>>>>> +     BT_DBG("chan %p mtu 0x%4.4x", chan, mtu);
>>>>>>>>>>> +
>>>>>>>>>>> +     chan->imtu = mtu;
>>>>>>>>>>> +
>>>>>>>>>>> +     l2cap_ecred_reconfigure(chan);
>>>>>>>>>>> +
>>>>>>>>>>> +     return 0;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> /* ---- L2CAP interface with lower layer (HCI) ---- */
>>>>>>>>>>> 
>>>>>>>>>>> int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr)
>>>>>>>>>>> @@ -7579,7 +8062,8 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
>>>>>>>>>>>                  else
>>>>>>>>>>>                          __set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
>>>>>>>>>>>          } else if (chan->state == BT_CONNECT2 &&
>>>>>>>>>>> -                        chan->mode != L2CAP_MODE_LE_FLOWCTL) {
>>>>>>>>>>> +                        !(chan->mode == L2CAP_MODE_EXT_FLOWCTL ||
>>>>>>>>>>> +                         chan->mode == L2CAP_MODE_LE_FLOWCTL)) {
>>>>>>>>>> 
>>>>>>>>>> Please double check that this line is correctly aligned after the (.
>>>>>>>>>> 
>>>>>>>>>>>                  struct l2cap_conn_rsp rsp;
>>>>>>>>>>>                  __u16 res, stat;
>>>>>>>>>>> 
>>>>>>>>>>> diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
>>>>>>>>>>> index a7be8b59b3c2..12c557f7f40f 100644
>>>>>>>>>>> --- a/net/bluetooth/l2cap_sock.c
>>>>>>>>>>> +++ b/net/bluetooth/l2cap_sock.c
>>>>>>>>>>> @@ -232,7 +232,7 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr,
>>>>>>>>>>>                  return -EINVAL;
>>>>>>>>>>>  }
>>>>>>>>>>> 
>>>>>>>>>>> -     if (chan->psm && bdaddr_type_is_le(chan->src_type))
>>>>>>>>>>> +     if (chan->psm && bdaddr_type_is_le(chan->src_type) && !chan->mode)
>>>>>>>>>>>          chan->mode = L2CAP_MODE_LE_FLOWCTL;
>>>>>>>>>>> 
>>>>>>>>>>>  err = l2cap_chan_connect(chan, la.l2_psm, __le16_to_cpu(la.l2_cid),
>>>>>>>>>>> @@ -273,6 +273,7 @@ static int l2cap_sock_listen(struct socket *sock, int backlog)
>>>>>>>>>>>  switch (chan->mode) {
>>>>>>>>>>>  case L2CAP_MODE_BASIC:
>>>>>>>>>>>  case L2CAP_MODE_LE_FLOWCTL:
>>>>>>>>>>> +     case L2CAP_MODE_EXT_FLOWCTL:
>>>>>>>>>>>          break;
>>>>>>>>>>>  case L2CAP_MODE_ERTM:
>>>>>>>>>>>  case L2CAP_MODE_STREAMING:
>>>>>>>>>>> @@ -408,16 +409,6 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
>>>>>>>>>>> 
>>>>>>>>>>>  switch (optname) {
>>>>>>>>>>>  case L2CAP_OPTIONS:
>>>>>>>>>>> -             /* LE sockets should use BT_SNDMTU/BT_RCVMTU, but since
>>>>>>>>>>> -              * legacy ATT code depends on getsockopt for
>>>>>>>>>>> -              * L2CAP_OPTIONS we need to let this pass.
>>>>>>>>>>> -              */
>>>>>>>>>>> -             if (bdaddr_type_is_le(chan->src_type) &&
>>>>>>>>>>> -                 chan->scid != L2CAP_CID_ATT) {
>>>>>>>>>>> -                     err = -EINVAL;
>>>>>>>>>>> -                     break;
>>>>>>>>>>> -             }
>>>>>>>>>>> -
>>>>>>>>>> 
>>>>>>>>>> Should this be a separate patch. How do we keep this legacy behavior.
>>>>>>>>> 
>>>>>>>>> I kind have forgotten to fix this one, I guess we will need a new
>>>>>>>>> option in order to read the mode without using the old L2CAP_OPTIONS
>>>>>>>>> then, or do you have anything against introducing yet another option?
>>>>>>>> 
>>>>>>>> so what is the problem here? Is this because L2CAP by itself can not tell if enhanced credit mode is supported by the peer?
>>>>>>> 
>>>>>>> Yep, afaik there is no means to detect the support transparently,
>>>>>>> otherwise could always prefer to use EXT_FLOWCTL instead of
>>>>>>> LE_FLOWCTL, it seems at the point we introduced LE_FLOWCTL option we
>>>>>>> had always assumed there would not be any mode one could use when the
>>>>>>> address type is LE.
>>>>>>> 
>>>>>>>> Do you actually want to create a new mode socket options with defined values by us to allow selected the mode of operation?
>>>>>>> 
>>>>>>> I woud assume that is the only safe option here, though what I did was
>>>>>>> to enable L2CAP_OPTIONS to work with LE addresses which I assume is
>>>>>>> safe as previously it would cause an error no one would be using it.
>>>>>> 
>>>>>> that is what I was afraid of. So can we use BT_SNDMTU and BT_RCVMTU and then only a mode property is missing? Or do we have other options that we want to configure.
>>>>> 
>>>>> Not sure I follow, do you prefer chaning an existing option to add a
>>>>> new field? I though having an option to set the mode would be better
>>>>> but perhaps you want to combine it something else, well maybe some
>>>>> sort of QOS interface, the problem is that the field are quite
>>>>> different depending on the mode so Im afraid that would take a lot
>>>>> more time to define.
>>>> 
>>>> Let me ask this differently. What options do we want to be able to configure if we deprecate L2CAP_OPTIONS?
>>> 
>>> Coming back to this perhaps we could infer the mode if in case there
>>> are multiple connection being made, the only question is if the spec
>>> actually allow connection with the lagacy command if the remote side
>>> is listen on PSM that is for ECRED mode like EATT, if it does require
>>> maybe we can use BT_DEFER_SETUP to trigger it but I don't really like
>>> reusing BT_DEFER_SETUP like that since it was introduced for a very
>>> different purpose it may confuse people.
>> 
>> you still haven’t answered my question. What kind of options do we want the socket user to configure?
> 
> The current solution is using just the mode so it could detect if the
> PSM is intended to use the new signalling or not, the spec don't seem
> to be very clear if connection made to the PSM shall only accept the
> new signalling or not, in theory I guess it doesn't since the PSM
> domain is global connecting with legacy signalling should work as
> well, it just don't allow connecting multiple sockets at once.

sounds like this spec was rushed out the door. It is full of holes.

> So my thingking is, if the there are pending l2cap_chan with
> DEFER_SETUP set then use the new command, otherwise stick to the old
> one, so the new signalling commands would only really be used when
> there is more than one socket connecting, how about that?

I assumed the ECRED mode had the advantage of MTU increase capability. So I think it might be actually useful even with a single CID.

>>>> Anyhow, maybe we first introduce a debugfs option that says attempt_ecred_mode_first and only when the attempt fails fall back to credit based flow control mode.
>>> 
>>> I was thinking in something like the following so we only use the new
>>> mode if it is enabled, well provided we can remove it later:
>>> 
>>> https://gist.github.com/Vudentz/35ca4a39b58e64e8ecbde3ff6ada2d61
>> 
>> That looks fine to me.
>> 
>>>> Btw. do we have at least a supported features bit in L2CAP hinting that ecred mode is supported over BR/EDR. It would be good to have some way of knowing it for BR/EDR ahead of time.
>>> 
>>> I don't remember it being added, but yes it would make a lot of sense
>>> to have it when using over BR/EDR.
>> 
>> If not, then we should file an erratum for it.
>> 
>>>>>> For the multi-connect case, I was thinking that we use something similar to DEFER_SETUP. If we set a DEFER_SETUP on an outgoing socket, then you can call connect() on a socket and succeeds. However no POLLIN | POLLOUT would be signaled. When calling a connect() without that option set, then all sockets become ready at the same time on success.
>>>>> 
>>>>> As initiator I don't think we have a good match in the socket API as
>>>>> it only return one fd at time, so currently it still doing the
>>>>> connection one by one, what I though we could do is to delay a little
>>>>> to group the request in a single L2CAP request, thought that creates
>>>>> an extra latency for initiating the connection and can be racy.
>>>>> Another option would be to allow bind to bind to multiple sockets at
>>>>> once, so when you issue the connect in one socket it would connect all
>>>>> socket bound.
>>>> 
>>>> Let me try to explain this in pseudo code.
>>>> 
>>>>       fd1 = socket();
>>>>       setsockopt(fd1, BT_DEFER_SETUP);
>>>>       connect(fd1);
>>>>       0 = poll(fd1);
>>>> 
>>>>       fd2 = socket();
>>>>       setsockopt(fd2, BT_DEFER_SETUP);
>>>>       connect(fd2);
>>>>       0 = poll(fd2);
>>>> 
>>>>       fd3 = socket();
>>>>       connect(fd3);
>>>>       POLLOUT = poll(fd1);
>>>>       POLLOUT = poll(fd2);
>>>>       POLLOUT = poll(fd3);
>>>> 
>>>> Essentially all the connect() call with BT_DEFER_SETUP will succeed, but POLLOUT will not signal. So you have the fd, but you can not write to it. When we have the connect() without BT_DEFER_SETUP set, then L2CAP will connect all channels at the same time and assign them to the given sockets. And now all fds become POLLOUT.
>>> 
>>> Should work, btw couldn't we just use bind for the first 2 sockets?
>>> That imo would make things a little cleaner since we only do connect
>>> when it is really ready to connect and we don't have to even consider
>>> poll until then, that said we would need to group these sockets either
>>> way so we need to do this per processs, actually the order would be:
>>> 
>>> fd# = socket()
>>> bind(fd#)
>>> setsockopt(fd#, BT_DEFER_SETUP)
>>> 
>>> Since bind sets the address then on setsockopt we can check if there
>>> is anything else pending to be connected for the same address and then
>>> just proceed.
>> 
>> With bind() you set the local address. With connect() you select the remote address. I don’t see how bind() only would get us there.
>> 
>> And the poll() part is not a problem btw. We should be doing asynchronous write/send anyway. It is just that we have been a bit lazy with some of the protocol implementation and execute write/send right away without checking with poll if the socket is actually writable. If we fix that then this will be all dead simple. It is no different than an asynchronous connect() anyway. There you have to wait for poll() as well. I really doubt we are using blocking connect() calls. And if we do, we need to fix that.
> 
> You are right, here is a draft implementing this (not tested):
> 
> https://gist.github.com/Vudentz/5f4e34358f7a76518c054cfcd6bbf4fd

This doesn’t look right. I think this will be all a bit more complicated. Also the recursive function call will not actually work.

Regards

Marcel


^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2020-02-22  9:44 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-01-16 21:27 [PATCH 1/2] Bluetooth: L2CAP: Add definitions for Enhanced Credit Based Mode Luiz Augusto von Dentz
2020-01-16 21:27 ` [PATCH 2/2] Bluetooth: L2CAP: Add initial code " Luiz Augusto von Dentz
2020-01-23 18:00   ` Marcel Holtmann
2020-01-23 23:29     ` Luiz Augusto von Dentz
2020-01-24 16:39       ` Marcel Holtmann
2020-01-24 19:13         ` Luiz Augusto von Dentz
2020-01-25  7:33           ` Marcel Holtmann
2020-01-27  6:28             ` Luiz Augusto von Dentz
2020-01-27 12:10               ` Marcel Holtmann
2020-02-20 23:42                 ` Luiz Augusto von Dentz
2020-02-21  8:14                   ` Marcel Holtmann
2020-02-21 18:26                     ` Luiz Augusto von Dentz
2020-02-22  9:44                       ` Marcel Holtmann
2020-01-23 17:50 ` [PATCH 1/2] Bluetooth: L2CAP: Add definitions " Marcel Holtmann

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.