Commit cb2ea25e authored by Szymon Janc's avatar Szymon Janc Committed by Christopher Friedt
Browse files

bluetooth: Add support for reconfiguring L2CAP channels



This allows application to increase channel's MTU and (in some cases)
MPS. When channel gets reconfigured dedicated callback is called to
inform application.

Signed-off-by: default avatarSzymon Janc <szymon.janc@codecoup.pl>
parent 50d357d7
Loading
Loading
Loading
Loading
+28 −0
Original line number Diff line number Diff line
@@ -177,6 +177,10 @@ struct bt_l2cap_le_chan {
	 *  @ref BT_L2CAP_SDU_RX_MTU by the stack.
	 */
	struct bt_l2cap_le_endpoint	rx;

	/** Pending RX MTU on ECFC reconfigure, used internally by stack */
	uint16_t pending_rx_mtu;

	/** Channel Transmission Endpoint */
	struct bt_l2cap_le_endpoint	tx;
	/** Channel Transmission queue */
@@ -316,6 +320,16 @@ struct bt_l2cap_chan_ops {
	 * references to the channel object.
	 */
	void (*released)(struct bt_l2cap_chan *chan);

	/** @brief Channel reconfigured callback
	 *
	 *  If this callback is provided it will be called whenever peer or
	 *  local device requested reconfiguration. Application may check
	 *  updated MTU and MPS values by inspecting chan->le endpoints.
	 *
	 *  @param chan The channel which was reconfigured
	 */
	void (*reconfigured)(struct bt_l2cap_chan *chan);
};

/** @def BT_L2CAP_CHAN_SEND_RESERVE
@@ -414,6 +428,20 @@ int bt_l2cap_br_server_register(struct bt_l2cap_server *server);
int bt_l2cap_ecred_chan_connect(struct bt_conn *conn,
				struct bt_l2cap_chan **chans, uint16_t psm);

/** @brief Reconfigure Enhanced Credit Based L2CAP channels
 *
 *  Reconfigure up to 5 L2CAP channels. Channels must be from the same bt_conn.
 *  Once reconfiguration is completed each channel reconfigured() callback will
 *  be called. MTU cannot be decreased on any of provided channels.
 *
 *  @param chans Array of channel objects. Null-terminated. Elements after the
 *               first 5 are silently ignored.
 *  @param mtu Channel MTU to reconfigure to.
 *
 *  @return 0 in case of success or negative value in case of error.
 */
int bt_l2cap_ecred_chan_reconfigure(struct bt_l2cap_chan **chans, uint16_t mtu);

/** @brief Connect L2CAP channel
 *
 *  Connect L2CAP channel by PSM, once the connection is completed channel
+136 −0
Original line number Diff line number Diff line
@@ -1298,6 +1298,10 @@ static void le_ecred_reconf_req(struct bt_l2cap *l2cap, uint8_t ident,
	for (int i = 0; i < chan_count; i++) {
		BT_L2CAP_LE_CHAN(chans[i])->tx.mtu = mtu;
		BT_L2CAP_LE_CHAN(chans[i])->tx.mps = mps;

		if (chans[i]->ops->reconfigured) {
			chans[i]->ops->reconfigured(chans[i]);
		}
	}

	BT_DBG("mtu %u mps %u", mtu, mps);
@@ -1311,6 +1315,36 @@ response:

	l2cap_send(conn, BT_L2CAP_CID_LE_SIG, buf);
}

static void le_ecred_reconf_rsp(struct bt_l2cap *l2cap, uint8_t ident,
				struct net_buf *buf)
{
	struct bt_conn *conn = l2cap->chan.chan.conn;
	struct bt_l2cap_ecred_reconf_rsp *rsp;
	struct bt_l2cap_le_chan *ch;
	uint16_t result;

	if (buf->len < sizeof(*rsp)) {
		BT_ERR("Too small ecred reconf rsp packet size");
		return;
	}

	rsp = net_buf_pull_mem(buf, sizeof(*rsp));
	result = sys_le16_to_cpu(rsp->result);

	while ((ch = l2cap_lookup_ident(conn, ident))) {
		if (result == BT_L2CAP_LE_SUCCESS) {
			ch->rx.mtu = ch->pending_rx_mtu;
		}

		ch->pending_rx_mtu = 0;
		ch->chan.ident = 0U;

		if (ch->chan.ops->reconfigured) {
			ch->chan.ops->reconfigured(&ch->chan);
		}
	}
}
#endif /* defined(CONFIG_BT_L2CAP_ECRED) */

static struct bt_l2cap_le_chan *l2cap_remove_rx_cid(struct bt_conn *conn,
@@ -2013,6 +2047,9 @@ static int l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf)
	case BT_L2CAP_ECRED_RECONF_REQ:
		le_ecred_reconf_req(l2cap, hdr->ident, buf);
		break;
	case BT_L2CAP_ECRED_RECONF_RSP:
		le_ecred_reconf_rsp(l2cap, hdr->ident, buf);
		break;
#endif /* defined(CONFIG_BT_L2CAP_ECRED) */
#else
	case BT_L2CAP_CMD_REJECT:
@@ -2566,6 +2603,105 @@ fail:

	return err;
}

static struct bt_l2cap_le_chan *l2cap_find_pending_reconf(struct bt_conn *conn)
{
	struct bt_l2cap_chan *chan;

	SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) {
		if (BT_L2CAP_LE_CHAN(chan)->pending_rx_mtu) {
			return BT_L2CAP_LE_CHAN(chan);
		}
	}

	return NULL;
}

int bt_l2cap_ecred_chan_reconfigure(struct bt_l2cap_chan **chans, uint16_t mtu)
{
	struct bt_l2cap_ecred_reconf_req *req;
	struct bt_conn *conn = NULL;
	struct bt_l2cap_le_chan *ch;
	struct net_buf *buf;
	uint8_t ident;
	int i;

	BT_DBG("chans %p mtu 0x%04x", chans, mtu);

	if (!chans) {
		return -EINVAL;
	}

	for (i = 0; i < L2CAP_ECRED_CHAN_MAX; i++) {
		if (!chans[i]) {
			break;
		}

		/* validate that all channels are from same connection */
		if (conn) {
			if (conn != chans[i]->conn) {
				return -EINVAL;
			}
		} else {
			conn = chans[i]->conn;
		}

		/* validate MTU is not decreased */
		if (mtu < BT_L2CAP_LE_CHAN(chans[i])->rx.mtu) {
			return -EINVAL;
		}
	}

	if (i == 0) {
		return -EINVAL;
	}

	if (!conn) {
		return -ENOTCONN;
	}

	if (conn->type != BT_CONN_TYPE_LE) {
		return -EINVAL;
	}

	/* allow only 1 request at time */
	if (l2cap_find_pending_reconf(conn)) {
		return -EBUSY;
	}

	ident = get_ident();

	buf = l2cap_create_le_sig_pdu(NULL, BT_L2CAP_ECRED_RECONF_REQ,
				      ident,
				      sizeof(*req) + (i * sizeof(uint16_t)));
	if (!buf) {
		return -ENOMEM;
	}

	req = net_buf_add(buf, sizeof(*req));
	req->mtu = sys_cpu_to_le16(mtu);

	/* MPS shall not be bigger than MTU + BT_L2CAP_SDU_HDR_SIZE
	 * as the remaining bytes cannot be used.
	 */
	req->mps = sys_cpu_to_le16(MIN(mtu + BT_L2CAP_SDU_HDR_SIZE,
				       BT_L2CAP_RX_MTU));

	for (int j = 0; j < i; j++) {
		ch = BT_L2CAP_LE_CHAN(chans[j]);

		ch->chan.ident = ident;
		ch->pending_rx_mtu = mtu;

		net_buf_add_le16(buf, ch->rx.cid);
	};

	/* we use first channel for sending and timeouting */
	l2cap_chan_send_req(chans[0], buf, L2CAP_CONN_TIMEOUT);

	return 0;
}

#endif /* defined(CONFIG_BT_L2CAP_ECRED) */

int bt_l2cap_chan_connect(struct bt_conn *conn, struct bt_l2cap_chan *chan,