forked from mirrors/linux
		
	Bluetooth: Add basic LE L2CAP connect request receiving support
This patch adds the necessary boiler plate code to handle receiving L2CAP connect requests over LE and respond to them with a proper connect response. Signed-off-by: Johan Hedberg <johan.hedberg@intel.com> Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
This commit is contained in:
		
							parent
							
								
									791d60f71a
								
							
						
					
					
						commit
						27e2d4c8d2
					
				
					 2 changed files with 153 additions and 0 deletions
				
			
		|  | @ -846,6 +846,7 @@ int l2cap_init_sockets(void); | ||||||
| void l2cap_cleanup_sockets(void); | 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_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); | ||||||
|  |  | ||||||
|  | @ -617,6 +617,29 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err) | ||||||
| 	return; | 	return; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void l2cap_chan_le_connect_reject(struct l2cap_chan *chan) | ||||||
|  | { | ||||||
|  | 	struct l2cap_conn *conn = chan->conn; | ||||||
|  | 	struct l2cap_le_conn_rsp rsp; | ||||||
|  | 	u16 result; | ||||||
|  | 
 | ||||||
|  | 	if (test_bit(FLAG_DEFER_SETUP, &chan->flags)) | ||||||
|  | 		result = L2CAP_CR_AUTHORIZATION; | ||||||
|  | 	else | ||||||
|  | 		result = L2CAP_CR_BAD_PSM; | ||||||
|  | 
 | ||||||
|  | 	l2cap_state_change(chan, BT_DISCONN); | ||||||
|  | 
 | ||||||
|  | 	rsp.dcid    = cpu_to_le16(chan->scid); | ||||||
|  | 	rsp.mtu     = cpu_to_le16(chan->imtu); | ||||||
|  | 	rsp.mps     = __constant_cpu_to_le16(L2CAP_LE_DEFAULT_MPS); | ||||||
|  | 	rsp.credits = __constant_cpu_to_le16(L2CAP_LE_MAX_CREDITS); | ||||||
|  | 	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; | ||||||
|  | @ -663,6 +686,8 @@ 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) | ||||||
|  | 				l2cap_chan_le_connect_reject(chan); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		l2cap_chan_del(chan, reason); | 		l2cap_chan_del(chan, reason); | ||||||
|  | @ -3641,6 +3666,23 @@ static int l2cap_build_conf_rsp(struct l2cap_chan *chan, void *data, | ||||||
| 	return ptr - data; | 	return ptr - data; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan) | ||||||
|  | { | ||||||
|  | 	struct l2cap_le_conn_rsp rsp; | ||||||
|  | 	struct l2cap_conn *conn = chan->conn; | ||||||
|  | 
 | ||||||
|  | 	BT_DBG("chan %p", chan); | ||||||
|  | 
 | ||||||
|  | 	rsp.dcid    = cpu_to_le16(chan->scid); | ||||||
|  | 	rsp.mtu     = cpu_to_le16(chan->imtu); | ||||||
|  | 	rsp.mps     = __constant_cpu_to_le16(L2CAP_LE_DEFAULT_MPS); | ||||||
|  | 	rsp.credits = __constant_cpu_to_le16(L2CAP_LE_MAX_CREDITS); | ||||||
|  | 	rsp.result  = __constant_cpu_to_le16(L2CAP_CR_SUCCESS); | ||||||
|  | 
 | ||||||
|  | 	l2cap_send_cmd(conn, chan->ident, L2CAP_LE_CONN_RSP, sizeof(rsp), | ||||||
|  | 		       &rsp); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| 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; | ||||||
|  | @ -5382,6 +5424,113 @@ static inline int l2cap_bredr_sig_cmd(struct l2cap_conn *conn, | ||||||
| 	return err; | 	return err; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static int l2cap_le_connect_req(struct l2cap_conn *conn, | ||||||
|  | 				struct l2cap_cmd_hdr *cmd, u16 cmd_len, | ||||||
|  | 				u8 *data) | ||||||
|  | { | ||||||
|  | 	struct l2cap_le_conn_req *req = (struct l2cap_le_conn_req *) data; | ||||||
|  | 	struct l2cap_le_conn_rsp rsp; | ||||||
|  | 	struct l2cap_chan *chan, *pchan; | ||||||
|  | 	u16 dcid, scid, mtu, mps; | ||||||
|  | 	__le16 psm; | ||||||
|  | 	u8 result; | ||||||
|  | 
 | ||||||
|  | 	if (cmd_len != sizeof(*req)) | ||||||
|  | 		return -EPROTO; | ||||||
|  | 
 | ||||||
|  | 	scid = __le16_to_cpu(req->scid); | ||||||
|  | 	mtu  = __le16_to_cpu(req->mtu); | ||||||
|  | 	mps  = __le16_to_cpu(req->mps); | ||||||
|  | 	psm  = req->psm; | ||||||
|  | 	dcid = 0; | ||||||
|  | 
 | ||||||
|  | 	if (mtu < 23 || mps < 23) | ||||||
|  | 		return -EPROTO; | ||||||
|  | 
 | ||||||
|  | 	BT_DBG("psm 0x%2.2x scid 0x%4.4x mtu %u mps %u", __le16_to_cpu(psm), | ||||||
|  | 	       scid, mtu, mps); | ||||||
|  | 
 | ||||||
|  | 	/* 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_BAD_PSM; | ||||||
|  | 		chan = NULL; | ||||||
|  | 		goto response; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	mutex_lock(&conn->chan_lock); | ||||||
|  | 	l2cap_chan_lock(pchan); | ||||||
|  | 
 | ||||||
|  | 	if (!smp_sufficient_security(conn->hcon, pchan->sec_level)) { | ||||||
|  | 		result = L2CAP_CR_AUTHENTICATION; | ||||||
|  | 		chan = NULL; | ||||||
|  | 		goto response_unlock; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* Check if we already have channel with that dcid */ | ||||||
|  | 	if (__l2cap_get_chan_by_dcid(conn, scid)) { | ||||||
|  | 		result = L2CAP_CR_NO_MEM; | ||||||
|  | 		chan = NULL; | ||||||
|  | 		goto response_unlock; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	chan = pchan->ops->new_connection(pchan); | ||||||
|  | 	if (!chan) { | ||||||
|  | 		result = L2CAP_CR_NO_MEM; | ||||||
|  | 		goto response_unlock; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	bacpy(&chan->src, &conn->hcon->src); | ||||||
|  | 	bacpy(&chan->dst, &conn->hcon->dst); | ||||||
|  | 	chan->src_type = bdaddr_type(conn->hcon, conn->hcon->src_type); | ||||||
|  | 	chan->dst_type = bdaddr_type(conn->hcon, conn->hcon->dst_type); | ||||||
|  | 	chan->psm  = psm; | ||||||
|  | 	chan->dcid = scid; | ||||||
|  | 	chan->omtu = mtu; | ||||||
|  | 	chan->remote_mps = mps; | ||||||
|  | 
 | ||||||
|  | 	__l2cap_chan_add(conn, chan); | ||||||
|  | 	dcid = 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); | ||||||
|  | 		result = L2CAP_CR_PEND; | ||||||
|  | 		chan->ops->defer(chan); | ||||||
|  | 	} else { | ||||||
|  | 		l2cap_chan_ready(chan); | ||||||
|  | 		result = L2CAP_CR_SUCCESS; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | response_unlock: | ||||||
|  | 	l2cap_chan_unlock(pchan); | ||||||
|  | 	mutex_unlock(&conn->chan_lock); | ||||||
|  | 
 | ||||||
|  | 	if (result == L2CAP_CR_PEND) | ||||||
|  | 		return 0; | ||||||
|  | 
 | ||||||
|  | response: | ||||||
|  | 	if (chan) { | ||||||
|  | 		rsp.mtu = cpu_to_le16(chan->imtu); | ||||||
|  | 		rsp.mps = __constant_cpu_to_le16(L2CAP_LE_DEFAULT_MPS); | ||||||
|  | 	} else { | ||||||
|  | 		rsp.mtu = 0; | ||||||
|  | 		rsp.mps = 0; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	rsp.dcid    = cpu_to_le16(dcid); | ||||||
|  | 	rsp.credits = __constant_cpu_to_le16(L2CAP_LE_MAX_CREDITS); | ||||||
|  | 	rsp.result  = cpu_to_le16(result); | ||||||
|  | 
 | ||||||
|  | 	l2cap_send_cmd(conn, cmd->ident, L2CAP_LE_CONN_RSP, sizeof(rsp), &rsp); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn, | static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn, | ||||||
| 				   struct l2cap_cmd_hdr *cmd, u16 cmd_len, | 				   struct l2cap_cmd_hdr *cmd, u16 cmd_len, | ||||||
| 				   u8 *data) | 				   u8 *data) | ||||||
|  | @ -5400,6 +5549,9 @@ static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn, | ||||||
| 		l2cap_le_connect_rsp(conn, cmd, cmd_len, data); | 		l2cap_le_connect_rsp(conn, cmd, cmd_len, data); | ||||||
| 		return 0; | 		return 0; | ||||||
| 
 | 
 | ||||||
|  | 	case L2CAP_LE_CONN_REQ: | ||||||
|  | 		return l2cap_le_connect_req(conn, cmd, cmd_len, data); | ||||||
|  | 
 | ||||||
| 	default: | 	default: | ||||||
| 		BT_ERR("Unknown LE signaling command 0x%2.2x", cmd->code); | 		BT_ERR("Unknown LE signaling command 0x%2.2x", cmd->code); | ||||||
| 		return -EINVAL; | 		return -EINVAL; | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Johan Hedberg
						Johan Hedberg