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: Szymon Janc <szymon.janc@codecoup.pl>
This commit is contained in:
Szymon Janc 2021-09-17 20:33:53 +02:00 committed by Carles Cufí
commit c589994dc0
2 changed files with 164 additions and 0 deletions

View file

@ -177,6 +177,10 @@ struct bt_l2cap_le_chan {
* @ref BT_L2CAP_SDU_RX_MTU by the stack. * @ref BT_L2CAP_SDU_RX_MTU by the stack.
*/ */
struct bt_l2cap_le_endpoint rx; struct bt_l2cap_le_endpoint rx;
/** Pending RX MTU on ECFC reconfigure, used internally by stack */
uint16_t pending_rx_mtu;
/** Channel Transmission Endpoint */ /** Channel Transmission Endpoint */
struct bt_l2cap_le_endpoint tx; struct bt_l2cap_le_endpoint tx;
/** Channel Transmission queue */ /** Channel Transmission queue */
@ -316,6 +320,16 @@ struct bt_l2cap_chan_ops {
* references to the channel object. * references to the channel object.
*/ */
void (*released)(struct bt_l2cap_chan *chan); 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 /** @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, int bt_l2cap_ecred_chan_connect(struct bt_conn *conn,
struct bt_l2cap_chan **chans, uint16_t psm); 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 /** @brief Connect L2CAP channel
* *
* Connect L2CAP channel by PSM, once the connection is completed channel * Connect L2CAP channel by PSM, once the connection is completed channel

View file

@ -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++) { for (int i = 0; i < chan_count; i++) {
BT_L2CAP_LE_CHAN(chans[i])->tx.mtu = mtu; BT_L2CAP_LE_CHAN(chans[i])->tx.mtu = mtu;
BT_L2CAP_LE_CHAN(chans[i])->tx.mps = mps; 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); BT_DBG("mtu %u mps %u", mtu, mps);
@ -1311,6 +1315,36 @@ response:
l2cap_send(conn, BT_L2CAP_CID_LE_SIG, buf); 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) */ #endif /* defined(CONFIG_BT_L2CAP_ECRED) */
static struct bt_l2cap_le_chan *l2cap_remove_rx_cid(struct bt_conn *conn, 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: case BT_L2CAP_ECRED_RECONF_REQ:
le_ecred_reconf_req(l2cap, hdr->ident, buf); le_ecred_reconf_req(l2cap, hdr->ident, buf);
break; break;
case BT_L2CAP_ECRED_RECONF_RSP:
le_ecred_reconf_rsp(l2cap, hdr->ident, buf);
break;
#endif /* defined(CONFIG_BT_L2CAP_ECRED) */ #endif /* defined(CONFIG_BT_L2CAP_ECRED) */
#else #else
case BT_L2CAP_CMD_REJECT: case BT_L2CAP_CMD_REJECT:
@ -2566,6 +2603,105 @@ fail:
return err; 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) */ #endif /* defined(CONFIG_BT_L2CAP_ECRED) */
int bt_l2cap_chan_connect(struct bt_conn *conn, struct bt_l2cap_chan *chan, int bt_l2cap_chan_connect(struct bt_conn *conn, struct bt_l2cap_chan *chan,