From c589994dc0717b7cb39280caab53ef5377326625 Mon Sep 17 00:00:00 2001 From: Szymon Janc Date: Fri, 17 Sep 2021 20:33:53 +0200 Subject: [PATCH] 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 --- include/bluetooth/l2cap.h | 28 +++++++ subsys/bluetooth/host/l2cap.c | 136 ++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) diff --git a/include/bluetooth/l2cap.h b/include/bluetooth/l2cap.h index 8b973dd5786..7f5aa877492 100644 --- a/include/bluetooth/l2cap.h +++ b/include/bluetooth/l2cap.h @@ -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 diff --git a/subsys/bluetooth/host/l2cap.c b/subsys/bluetooth/host/l2cap.c index 08e57cffdee..b9b1e486705 100644 --- a/subsys/bluetooth/host/l2cap.c +++ b/subsys/bluetooth/host/l2cap.c @@ -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,