Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 84084a31 authored by Mat Martineau's avatar Mat Martineau Committed by Gustavo F. Padovan
Browse files

Bluetooth: Perform L2CAP SDU reassembly without copying data



Use sk_buff fragment capabilities to link together incoming skbs
instead of allocating a new skb for reassembly and copying.

The new reassembly code works equally well for ERTM and streaming
mode, so there is now one reassembly function instead of two.

Signed-off-by: default avatarMat Martineau <mathewm@codeaurora.org>
Signed-off-by: default avatarGustavo F. Padovan <padovan@profusion.mobi>
parent 5b668eb3
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -354,8 +354,8 @@ struct l2cap_chan {
	__u8		retry_count;
	__u8		num_acked;
	__u16		sdu_len;
	__u16		partial_sdu_len;
	struct sk_buff	*sdu;
	struct sk_buff	*sdu_last_frag;

	__u8		remote_tx_win;
	__u8		remote_max_tx;
@@ -448,7 +448,6 @@ enum {
#define L2CAP_CONF_MAX_CONF_RSP 2

enum {
	CONN_SAR_SDU,
	CONN_SREJ_SENT,
	CONN_WAIT_F,
	CONN_SREJ_ACT,
+80 −163
Original line number Diff line number Diff line
@@ -3128,102 +3128,104 @@ static int l2cap_add_to_srej_queue(struct l2cap_chan *chan, struct sk_buff *skb,
	return 0;
}

static int l2cap_ertm_reassembly_sdu(struct l2cap_chan *chan, struct sk_buff *skb, u16 control)
static void append_skb_frag(struct sk_buff *skb,
			struct sk_buff *new_frag, struct sk_buff **last_frag)
{
	struct sk_buff *_skb;
	int err;
	/* skb->len reflects data in skb as well as all fragments
	 * skb->data_len reflects only data in fragments
	 */
	if (!skb_has_frag_list(skb))
		skb_shinfo(skb)->frag_list = new_frag;

	new_frag->next = NULL;

	(*last_frag)->next = new_frag;
	*last_frag = new_frag;

	skb->len += new_frag->len;
	skb->data_len += new_frag->len;
	skb->truesize += new_frag->truesize;
}

static int l2cap_reassemble_sdu(struct l2cap_chan *chan, struct sk_buff *skb, u16 control)
{
	int err = -EINVAL;

	switch (control & L2CAP_CTRL_SAR) {
	case L2CAP_SDU_UNSEGMENTED:
		if (test_bit(CONN_SAR_SDU, &chan->conn_state))
			goto drop;
		if (chan->sdu)
			break;

		return chan->ops->recv(chan->data, skb);
		err = chan->ops->recv(chan->data, skb);
		break;

	case L2CAP_SDU_START:
		if (test_bit(CONN_SAR_SDU, &chan->conn_state))
			goto drop;
		if (chan->sdu)
			break;

		chan->sdu_len = get_unaligned_le16(skb->data);
		skb_pull(skb, 2);

		if (chan->sdu_len > chan->imtu)
			goto disconnect;

		chan->sdu = bt_skb_alloc(chan->sdu_len, GFP_ATOMIC);
		if (!chan->sdu)
			return -ENOMEM;
		if (chan->sdu_len > chan->imtu) {
			err = -EMSGSIZE;
			break;
		}

		/* pull sdu_len bytes only after alloc, because of Local Busy
		 * condition we have to be sure that this will be executed
		 * only once, i.e., when alloc does not fail */
		skb_pull(skb, 2);
		if (skb->len >= chan->sdu_len)
			break;

		memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len);
		chan->sdu = skb;
		chan->sdu_last_frag = skb;

		set_bit(CONN_SAR_SDU, &chan->conn_state);
		chan->partial_sdu_len = skb->len;
		skb = NULL;
		err = 0;
		break;

	case L2CAP_SDU_CONTINUE:
		if (!test_bit(CONN_SAR_SDU, &chan->conn_state))
			goto disconnect;

		if (!chan->sdu)
			goto disconnect;
			break;

		chan->partial_sdu_len += skb->len;
		if (chan->partial_sdu_len > chan->sdu_len)
			goto drop;
		append_skb_frag(chan->sdu, skb,
				&chan->sdu_last_frag);
		skb = NULL;

		memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len);
		if (chan->sdu->len >= chan->sdu_len)
			break;

		err = 0;
		break;

	case L2CAP_SDU_END:
		if (!test_bit(CONN_SAR_SDU, &chan->conn_state))
			goto disconnect;

		if (!chan->sdu)
			goto disconnect;

		chan->partial_sdu_len += skb->len;

		if (chan->partial_sdu_len > chan->imtu)
			goto drop;
			break;

		if (chan->partial_sdu_len != chan->sdu_len)
			goto drop;
		append_skb_frag(chan->sdu, skb,
				&chan->sdu_last_frag);
		skb = NULL;

		memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len);
		if (chan->sdu->len != chan->sdu_len)
			break;

		_skb = skb_clone(chan->sdu, GFP_ATOMIC);
		if (!_skb) {
			return -ENOMEM;
		}
		err = chan->ops->recv(chan->data, chan->sdu);

		err = chan->ops->recv(chan->data, _skb);
		if (err < 0) {
			kfree_skb(_skb);
			return err;
		if (!err) {
			/* Reassembly complete */
			chan->sdu = NULL;
			chan->sdu_last_frag = NULL;
			chan->sdu_len = 0;
		}

		clear_bit(CONN_SAR_SDU, &chan->conn_state);

		kfree_skb(chan->sdu);
		break;
	}

	if (err) {
		kfree_skb(skb);
	return 0;

drop:
		kfree_skb(chan->sdu);
		chan->sdu = NULL;
		chan->sdu_last_frag = NULL;
		chan->sdu_len = 0;
	}

disconnect:
	l2cap_send_disconn_req(chan->conn, chan, ECONNRESET);
	kfree_skb(skb);
	return 0;
	return err;
}

static void l2cap_ertm_enter_local_busy(struct l2cap_chan *chan)
@@ -3277,99 +3279,6 @@ void l2cap_chan_busy(struct l2cap_chan *chan, int busy)
	}
}

static int l2cap_streaming_reassembly_sdu(struct l2cap_chan *chan, struct sk_buff *skb, u16 control)
{
	struct sk_buff *_skb;
	int err = -EINVAL;

	/*
	 * TODO: We have to notify the userland if some data is lost with the
	 * Streaming Mode.
	 */

	switch (control & L2CAP_CTRL_SAR) {
	case L2CAP_SDU_UNSEGMENTED:
		if (test_bit(CONN_SAR_SDU, &chan->conn_state)) {
			kfree_skb(chan->sdu);
			break;
		}

		err = chan->ops->recv(chan->data, skb);
		if (!err)
			return 0;

		break;

	case L2CAP_SDU_START:
		if (test_bit(CONN_SAR_SDU, &chan->conn_state)) {
			kfree_skb(chan->sdu);
			break;
		}

		chan->sdu_len = get_unaligned_le16(skb->data);
		skb_pull(skb, 2);

		if (chan->sdu_len > chan->imtu) {
			err = -EMSGSIZE;
			break;
		}

		chan->sdu = bt_skb_alloc(chan->sdu_len, GFP_ATOMIC);
		if (!chan->sdu) {
			err = -ENOMEM;
			break;
		}

		memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len);

		set_bit(CONN_SAR_SDU, &chan->conn_state);
		chan->partial_sdu_len = skb->len;
		err = 0;
		break;

	case L2CAP_SDU_CONTINUE:
		if (!test_bit(CONN_SAR_SDU, &chan->conn_state))
			break;

		memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len);

		chan->partial_sdu_len += skb->len;
		if (chan->partial_sdu_len > chan->sdu_len)
			kfree_skb(chan->sdu);
		else
			err = 0;

		break;

	case L2CAP_SDU_END:
		if (!test_bit(CONN_SAR_SDU, &chan->conn_state))
			break;

		memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len);

		clear_bit(CONN_SAR_SDU, &chan->conn_state);
		chan->partial_sdu_len += skb->len;

		if (chan->partial_sdu_len > chan->imtu)
			goto drop;

		if (chan->partial_sdu_len == chan->sdu_len) {
			_skb = skb_clone(chan->sdu, GFP_ATOMIC);
			err = chan->ops->recv(chan->data, _skb);
			if (err < 0)
				kfree_skb(_skb);
		}
		err = 0;

drop:
		kfree_skb(chan->sdu);
		break;
	}

	kfree_skb(skb);
	return err;
}

static void l2cap_check_srej_gap(struct l2cap_chan *chan, u8 tx_seq)
{
	struct sk_buff *skb;
@@ -3384,7 +3293,7 @@ static void l2cap_check_srej_gap(struct l2cap_chan *chan, u8 tx_seq)

		skb = skb_dequeue(&chan->srej_q);
		control = bt_cb(skb)->sar << L2CAP_CTRL_SAR_SHIFT;
		err = l2cap_ertm_reassembly_sdu(chan, skb, control);
		err = l2cap_reassemble_sdu(chan, skb, control);

		if (err < 0) {
			l2cap_send_disconn_req(chan->conn, chan, ECONNRESET);
@@ -3544,7 +3453,7 @@ static inline int l2cap_data_channel_iframe(struct l2cap_chan *chan, u16 rx_cont
		return 0;
	}

	err = l2cap_ertm_reassembly_sdu(chan, skb, rx_control);
	err = l2cap_reassemble_sdu(chan, skb, rx_control);
	chan->buffer_seq = (chan->buffer_seq + 1) % 64;
	if (err < 0) {
		l2cap_send_disconn_req(chan->conn, chan, ECONNRESET);
@@ -3860,12 +3769,20 @@ static inline int l2cap_data_channel(struct l2cap_conn *conn, u16 cid, struct sk

		tx_seq = __get_txseq(control);

		if (chan->expected_tx_seq == tx_seq)
			chan->expected_tx_seq = (chan->expected_tx_seq + 1) % 64;
		else
		if (chan->expected_tx_seq != tx_seq) {
			/* Frame(s) missing - must discard partial SDU */
			kfree_skb(chan->sdu);
			chan->sdu = NULL;
			chan->sdu_last_frag = NULL;
			chan->sdu_len = 0;

			/* TODO: Notify userland of missing data */
		}

		chan->expected_tx_seq = (tx_seq + 1) % 64;

		l2cap_streaming_reassembly_sdu(chan, skb, control);
		if (l2cap_reassemble_sdu(chan, skb, control) == -EMSGSIZE)
			l2cap_send_disconn_req(chan->conn, chan, ECONNRESET);

		goto done;