forked from mirrors/linux
		
	Bluetooth: L2CAP: Add initial code for Enhanced Credit Based Mode
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> Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
This commit is contained in:
		
							parent
							
								
									145720963b
								
							
						
					
					
						commit
						15f02b9105
					
				
					 3 changed files with 552 additions and 20 deletions
				
			
		| 
						 | 
					@ -294,6 +294,8 @@ struct l2cap_conn_rsp {
 | 
				
			||||||
#define L2CAP_CR_LE_ENCRYPTION		0x0008
 | 
					#define L2CAP_CR_LE_ENCRYPTION		0x0008
 | 
				
			||||||
#define L2CAP_CR_LE_INVALID_SCID	0x0009
 | 
					#define L2CAP_CR_LE_INVALID_SCID	0x0009
 | 
				
			||||||
#define L2CAP_CR_LE_SCID_IN_USE		0X000A
 | 
					#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 */
 | 
					/* connect/create channel status */
 | 
				
			||||||
#define L2CAP_CS_NO_INFO	0x0000
 | 
					#define L2CAP_CS_NO_INFO	0x0000
 | 
				
			||||||
| 
						 | 
					@ -962,6 +964,7 @@ void l2cap_cleanup_sockets(void);
 | 
				
			||||||
bool l2cap_is_socket(struct socket *sock);
 | 
					bool l2cap_is_socket(struct socket *sock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan);
 | 
					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);
 | 
					void __l2cap_connect_rsp_defer(struct l2cap_chan *chan);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm);
 | 
					int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm);
 | 
				
			||||||
| 
						 | 
					@ -971,6 +974,7 @@ struct l2cap_chan *l2cap_chan_create(void);
 | 
				
			||||||
void l2cap_chan_close(struct l2cap_chan *chan, int reason);
 | 
					void l2cap_chan_close(struct l2cap_chan *chan, int reason);
 | 
				
			||||||
int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
 | 
					int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
 | 
				
			||||||
		       bdaddr_t *dst, u8 dst_type);
 | 
							       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);
 | 
					int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len);
 | 
				
			||||||
void l2cap_chan_busy(struct l2cap_chan *chan, int busy);
 | 
					void l2cap_chan_busy(struct l2cap_chan *chan, int busy);
 | 
				
			||||||
int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator);
 | 
					int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -535,6 +535,17 @@ static void l2cap_le_flowctl_init(struct l2cap_chan *chan, u16 tx_credits)
 | 
				
			||||||
	skb_queue_head_init(&chan->tx_q);
 | 
						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)
 | 
					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,
 | 
						BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn,
 | 
				
			||||||
| 
						 | 
					@ -641,6 +652,7 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	case L2CAP_MODE_LE_FLOWCTL:
 | 
						case L2CAP_MODE_LE_FLOWCTL:
 | 
				
			||||||
 | 
						case L2CAP_MODE_EXT_FLOWCTL:
 | 
				
			||||||
		skb_queue_purge(&chan->tx_q);
 | 
							skb_queue_purge(&chan->tx_q);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -707,6 +719,27 @@ static void l2cap_chan_le_connect_reject(struct l2cap_chan *chan)
 | 
				
			||||||
		       &rsp);
 | 
							       &rsp);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void l2cap_chan_ecred_connect_reject(struct l2cap_chan *chan)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct l2cap_conn *conn = chan->conn;
 | 
				
			||||||
 | 
						struct l2cap_ecred_conn_rsp rsp;
 | 
				
			||||||
 | 
						u16 result;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (test_bit(FLAG_DEFER_SETUP, &chan->flags))
 | 
				
			||||||
 | 
							result = L2CAP_CR_LE_AUTHORIZATION;
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
							result = L2CAP_CR_LE_BAD_PSM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						l2cap_state_change(chan, BT_DISCONN);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						memset(&rsp, 0, sizeof(rsp));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rsp.result  = cpu_to_le16(result);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						l2cap_send_cmd(conn, chan->ident, L2CAP_LE_CONN_RSP, sizeof(rsp),
 | 
				
			||||||
 | 
							       &rsp);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void l2cap_chan_connect_reject(struct l2cap_chan *chan)
 | 
					static void l2cap_chan_connect_reject(struct l2cap_chan *chan)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct l2cap_conn *conn = chan->conn;
 | 
						struct l2cap_conn *conn = chan->conn;
 | 
				
			||||||
| 
						 | 
					@ -752,8 +785,16 @@ void l2cap_chan_close(struct l2cap_chan *chan, int reason)
 | 
				
			||||||
		if (chan->chan_type == L2CAP_CHAN_CONN_ORIENTED) {
 | 
							if (chan->chan_type == L2CAP_CHAN_CONN_ORIENTED) {
 | 
				
			||||||
			if (conn->hcon->type == ACL_LINK)
 | 
								if (conn->hcon->type == ACL_LINK)
 | 
				
			||||||
				l2cap_chan_connect_reject(chan);
 | 
									l2cap_chan_connect_reject(chan);
 | 
				
			||||||
			else if (conn->hcon->type == LE_LINK)
 | 
								else if (conn->hcon->type == LE_LINK) {
 | 
				
			||||||
				l2cap_chan_le_connect_reject(chan);
 | 
									switch (chan->mode) {
 | 
				
			||||||
 | 
									case L2CAP_MODE_LE_FLOWCTL:
 | 
				
			||||||
 | 
										l2cap_chan_le_connect_reject(chan);
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
									case L2CAP_MODE_EXT_FLOWCTL:
 | 
				
			||||||
 | 
										l2cap_chan_ecred_connect_reject(chan);
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		l2cap_chan_del(chan, reason);
 | 
							l2cap_chan_del(chan, reason);
 | 
				
			||||||
| 
						 | 
					@ -1276,8 +1317,13 @@ static void l2cap_chan_ready(struct l2cap_chan *chan)
 | 
				
			||||||
	chan->conf_state = 0;
 | 
						chan->conf_state = 0;
 | 
				
			||||||
	__clear_chan_timer(chan);
 | 
						__clear_chan_timer(chan);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (chan->mode == L2CAP_MODE_LE_FLOWCTL && !chan->tx_credits)
 | 
						switch (chan->mode) {
 | 
				
			||||||
		chan->ops->suspend(chan);
 | 
						case L2CAP_MODE_LE_FLOWCTL:
 | 
				
			||||||
 | 
						case L2CAP_MODE_EXT_FLOWCTL:
 | 
				
			||||||
 | 
							if (!chan->tx_credits)
 | 
				
			||||||
 | 
								chan->ops->suspend(chan);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	chan->state = BT_CONNECTED;
 | 
						chan->state = BT_CONNECTED;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1309,6 +1355,31 @@ static void l2cap_le_connect(struct l2cap_chan *chan)
 | 
				
			||||||
		       sizeof(req), &req);
 | 
							       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)
 | 
					static void l2cap_le_start(struct l2cap_chan *chan)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct l2cap_conn *conn = chan->conn;
 | 
						struct l2cap_conn *conn = chan->conn;
 | 
				
			||||||
| 
						 | 
					@ -1321,8 +1392,12 @@ static void l2cap_le_start(struct l2cap_chan *chan)
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (chan->state == BT_CONNECT)
 | 
						if (chan->state == BT_CONNECT) {
 | 
				
			||||||
		l2cap_le_connect(chan);
 | 
							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)
 | 
					static void l2cap_start_connection(struct l2cap_chan *chan)
 | 
				
			||||||
| 
						 | 
					@ -2508,6 +2583,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch (chan->mode) {
 | 
						switch (chan->mode) {
 | 
				
			||||||
	case L2CAP_MODE_LE_FLOWCTL:
 | 
						case L2CAP_MODE_LE_FLOWCTL:
 | 
				
			||||||
 | 
						case L2CAP_MODE_EXT_FLOWCTL:
 | 
				
			||||||
		/* Check outgoing MTU */
 | 
							/* Check outgoing MTU */
 | 
				
			||||||
		if (len > chan->omtu)
 | 
							if (len > chan->omtu)
 | 
				
			||||||
			return -EMSGSIZE;
 | 
								return -EMSGSIZE;
 | 
				
			||||||
| 
						 | 
					@ -3776,6 +3852,45 @@ void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan)
 | 
				
			||||||
		       &rsp);
 | 
							       &rsp);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct {
 | 
				
			||||||
 | 
							struct l2cap_ecred_conn_rsp rsp;
 | 
				
			||||||
 | 
							__le16 dcid[5];
 | 
				
			||||||
 | 
						} __packed pdu;
 | 
				
			||||||
 | 
						struct l2cap_conn *conn = chan->conn;
 | 
				
			||||||
 | 
						u16 ident = chan->ident;
 | 
				
			||||||
 | 
						int i = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!ident)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						BT_DBG("chan %p ident %d", chan, ident);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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.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 */
 | 
				
			||||||
 | 
							pdu.dcid[i++] = cpu_to_le16(chan->scid);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mutex_unlock(&conn->chan_lock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						l2cap_send_cmd(conn, ident, L2CAP_ECRED_CONN_RSP,
 | 
				
			||||||
 | 
								sizeof(pdu.rsp) + i * sizeof(__le16), &pdu);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void __l2cap_connect_rsp_defer(struct l2cap_chan *chan)
 | 
					void __l2cap_connect_rsp_defer(struct l2cap_chan *chan)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct l2cap_conn_rsp rsp;
 | 
						struct l2cap_conn_rsp rsp;
 | 
				
			||||||
| 
						 | 
					@ -5718,6 +5833,351 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn,
 | 
				
			||||||
	return 0;
 | 
						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, num_scid;
 | 
				
			||||||
 | 
						bool defer = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (cmd_len < sizeof(*req) || cmd_len - sizeof(*req) % sizeof(u16)) {
 | 
				
			||||||
 | 
							result = L2CAP_CR_LE_INVALID_PARAMS;
 | 
				
			||||||
 | 
							goto response;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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;
 | 
				
			||||||
 | 
						cmd_len -= sizeof(req);
 | 
				
			||||||
 | 
						num_scid = cmd_len / sizeof(u16);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (i = 0; i < num_scid; i++) {
 | 
				
			||||||
 | 
							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, num_scid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (cmd_len < sizeof(*req) || cmd_len - sizeof(*req) % sizeof(u16)) {
 | 
				
			||||||
 | 
							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;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cmd_len -= sizeof(*req);
 | 
				
			||||||
 | 
						num_scid = cmd_len / sizeof(u16);
 | 
				
			||||||
 | 
						result = L2CAP_RECONF_SUCCESS;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (i = 0; i < num_scid; i++) {
 | 
				
			||||||
 | 
							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,
 | 
					static inline int l2cap_le_command_rej(struct l2cap_conn *conn,
 | 
				
			||||||
				       struct l2cap_cmd_hdr *cmd, u16 cmd_len,
 | 
									       struct l2cap_cmd_hdr *cmd, u16 cmd_len,
 | 
				
			||||||
				       u8 *data)
 | 
									       u8 *data)
 | 
				
			||||||
| 
						 | 
					@ -5773,6 +6233,22 @@ static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn,
 | 
				
			||||||
		err = l2cap_le_credits(conn, cmd, cmd_len, data);
 | 
							err = l2cap_le_credits(conn, cmd, cmd_len, data);
 | 
				
			||||||
		break;
 | 
							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:
 | 
						case L2CAP_DISCONN_REQ:
 | 
				
			||||||
		err = l2cap_disconnect_req(conn, cmd, cmd_len, data);
 | 
							err = l2cap_disconnect_req(conn, cmd, cmd_len, data);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
| 
						 | 
					@ -6815,11 +7291,13 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
 | 
				
			||||||
	struct l2cap_le_credits pkt;
 | 
						struct l2cap_le_credits pkt;
 | 
				
			||||||
	u16 return_credits;
 | 
						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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return_credits -= chan->rx_credits;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	BT_DBG("chan %p returning %u credits to sender", chan, return_credits);
 | 
						BT_DBG("chan %p returning %u credits to sender", chan, return_credits);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	chan->rx_credits += return_credits;
 | 
						chan->rx_credits += return_credits;
 | 
				
			||||||
| 
						 | 
					@ -6832,7 +7310,7 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
 | 
				
			||||||
	l2cap_send_cmd(conn, chan->ident, L2CAP_LE_CREDITS, sizeof(pkt), &pkt);
 | 
						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;
 | 
						int err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6847,7 +7325,7 @@ static int l2cap_le_recv(struct l2cap_chan *chan, struct sk_buff *skb)
 | 
				
			||||||
	return err;
 | 
						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;
 | 
						int err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6895,7 +7373,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (skb->len == sdu_len)
 | 
							if (skb->len == sdu_len)
 | 
				
			||||||
			return l2cap_le_recv(chan, skb);
 | 
								return l2cap_ecred_recv(chan, skb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		chan->sdu = skb;
 | 
							chan->sdu = skb;
 | 
				
			||||||
		chan->sdu_len = sdu_len;
 | 
							chan->sdu_len = sdu_len;
 | 
				
			||||||
| 
						 | 
					@ -6927,7 +7405,7 @@ static int l2cap_le_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
 | 
				
			||||||
	skb = NULL;
 | 
						skb = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (chan->sdu->len == chan->sdu_len) {
 | 
						if (chan->sdu->len == chan->sdu_len) {
 | 
				
			||||||
		err = l2cap_le_recv(chan, chan->sdu);
 | 
							err = l2cap_ecred_recv(chan, chan->sdu);
 | 
				
			||||||
		if (!err) {
 | 
							if (!err) {
 | 
				
			||||||
			chan->sdu = NULL;
 | 
								chan->sdu = NULL;
 | 
				
			||||||
			chan->sdu_last_frag = NULL;
 | 
								chan->sdu_last_frag = NULL;
 | 
				
			||||||
| 
						 | 
					@ -6988,7 +7466,8 @@ static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch (chan->mode) {
 | 
						switch (chan->mode) {
 | 
				
			||||||
	case L2CAP_MODE_LE_FLOWCTL:
 | 
						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 drop;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		goto done;
 | 
							goto done;
 | 
				
			||||||
| 
						 | 
					@ -7215,8 +7694,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
 | 
				
			||||||
	struct hci_dev *hdev;
 | 
						struct hci_dev *hdev;
 | 
				
			||||||
	int err;
 | 
						int err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	BT_DBG("%pMR -> %pMR (type %u) psm 0x%2.2x", &chan->src, dst,
 | 
						BT_DBG("%pMR -> %pMR (type %u) psm 0x%4.4x mode 0x%2.2x", &chan->src,
 | 
				
			||||||
	       dst_type, __le16_to_cpu(psm));
 | 
						       dst, dst_type, __le16_to_cpu(psm), chan->mode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	hdev = hci_get_route(dst, &chan->src, chan->src_type);
 | 
						hdev = hci_get_route(dst, &chan->src, chan->src_type);
 | 
				
			||||||
	if (!hdev)
 | 
						if (!hdev)
 | 
				
			||||||
| 
						 | 
					@ -7244,6 +7723,7 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
 | 
				
			||||||
	case L2CAP_MODE_BASIC:
 | 
						case L2CAP_MODE_BASIC:
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	case L2CAP_MODE_LE_FLOWCTL:
 | 
						case L2CAP_MODE_LE_FLOWCTL:
 | 
				
			||||||
 | 
						case L2CAP_MODE_EXT_FLOWCTL:
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	case L2CAP_MODE_ERTM:
 | 
						case L2CAP_MODE_ERTM:
 | 
				
			||||||
	case L2CAP_MODE_STREAMING:
 | 
						case L2CAP_MODE_STREAMING:
 | 
				
			||||||
| 
						 | 
					@ -7369,6 +7849,38 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL_GPL(l2cap_chan_connect);
 | 
					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) ---- */
 | 
					/* ---- L2CAP interface with lower layer (HCI) ---- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr)
 | 
					int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr)
 | 
				
			||||||
| 
						 | 
					@ -7580,7 +8092,8 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
 | 
				
			||||||
			else
 | 
								else
 | 
				
			||||||
				__set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
 | 
									__set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
 | 
				
			||||||
		} else if (chan->state == BT_CONNECT2 &&
 | 
							} 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;
 | 
								struct l2cap_conn_rsp rsp;
 | 
				
			||||||
			__u16 res, stat;
 | 
								__u16 res, stat;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -232,7 +232,7 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr,
 | 
				
			||||||
			return -EINVAL;
 | 
								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;
 | 
							chan->mode = L2CAP_MODE_LE_FLOWCTL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = l2cap_chan_connect(chan, la.l2_psm, __le16_to_cpu(la.l2_cid),
 | 
						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) {
 | 
						switch (chan->mode) {
 | 
				
			||||||
	case L2CAP_MODE_BASIC:
 | 
						case L2CAP_MODE_BASIC:
 | 
				
			||||||
	case L2CAP_MODE_LE_FLOWCTL:
 | 
						case L2CAP_MODE_LE_FLOWCTL:
 | 
				
			||||||
 | 
						case L2CAP_MODE_EXT_FLOWCTL:
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	case L2CAP_MODE_ERTM:
 | 
						case L2CAP_MODE_ERTM:
 | 
				
			||||||
	case L2CAP_MODE_STREAMING:
 | 
						case L2CAP_MODE_STREAMING:
 | 
				
			||||||
| 
						 | 
					@ -427,6 +428,8 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
 | 
				
			||||||
		opts.max_tx   = chan->max_tx;
 | 
							opts.max_tx   = chan->max_tx;
 | 
				
			||||||
		opts.txwin_size = chan->tx_win;
 | 
							opts.txwin_size = chan->tx_win;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							BT_DBG("mode 0x%2.2x", chan->mode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		len = min_t(unsigned int, len, sizeof(opts));
 | 
							len = min_t(unsigned int, len, sizeof(opts));
 | 
				
			||||||
		if (copy_to_user(optval, (char *) &opts, len))
 | 
							if (copy_to_user(optval, (char *) &opts, len))
 | 
				
			||||||
			err = -EFAULT;
 | 
								err = -EFAULT;
 | 
				
			||||||
| 
						 | 
					@ -707,6 +710,8 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname,
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							BT_DBG("mode 0x%2.2x", chan->mode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		chan->imtu = opts.imtu;
 | 
							chan->imtu = opts.imtu;
 | 
				
			||||||
		chan->omtu = opts.omtu;
 | 
							chan->omtu = opts.omtu;
 | 
				
			||||||
		chan->fcs  = opts.fcs;
 | 
							chan->fcs  = opts.fcs;
 | 
				
			||||||
| 
						 | 
					@ -939,7 +944,8 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (sk->sk_state == BT_CONNECTED) {
 | 
							if (chan->mode == L2CAP_MODE_LE_FLOWCTL &&
 | 
				
			||||||
 | 
							    sk->sk_state == BT_CONNECTED) {
 | 
				
			||||||
			err = -EISCONN;
 | 
								err = -EISCONN;
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -949,7 +955,12 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
 | 
				
			||||||
			break;
 | 
								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;
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
| 
						 | 
					@ -1004,7 +1015,11 @@ static int l2cap_sock_recvmsg(struct socket *sock, struct msghdr *msg,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (sk->sk_state == BT_CONNECT2 && test_bit(BT_SK_DEFER_SETUP,
 | 
						if (sk->sk_state == BT_CONNECT2 && test_bit(BT_SK_DEFER_SETUP,
 | 
				
			||||||
						    &bt_sk(sk)->flags)) {
 | 
											    &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;
 | 
								sk->sk_state = BT_CONNECTED;
 | 
				
			||||||
			pi->chan->state = BT_CONNECTED;
 | 
								pi->chan->state = BT_CONNECTED;
 | 
				
			||||||
			__l2cap_le_connect_rsp_defer(pi->chan);
 | 
								__l2cap_le_connect_rsp_defer(pi->chan);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue