|
11 | 11 | #include <errno.h>
|
12 | 12 | #include <zephyr/sys/atomic.h>
|
13 | 13 | #include <zephyr/sys/byteorder.h>
|
| 14 | +#include <zephyr/sys/check.h> |
14 | 15 | #include <zephyr/sys/iterable_sections.h>
|
15 | 16 | #include <zephyr/sys/util.h>
|
16 | 17 | #include <zephyr/sys/crc.h>
|
17 | 18 |
|
18 | 19 | #include <zephyr/bluetooth/hci.h>
|
19 | 20 | #include <zephyr/bluetooth/bluetooth.h>
|
20 | 21 | #include <zephyr/bluetooth/conn.h>
|
| 22 | +#include <zephyr/bluetooth/classic/l2cap_br.h> |
21 | 23 |
|
22 | 24 | #include "host/buf_view.h"
|
23 | 25 | #include "host/hci_core.h"
|
@@ -75,6 +77,7 @@ LOG_MODULE_REGISTER(bt_l2cap_br, CONFIG_BT_L2CAP_LOG_LEVEL);
|
75 | 77 | #define L2CAP_BR_CFG_TIMEOUT K_SECONDS(4)
|
76 | 78 | #define L2CAP_BR_DISCONN_TIMEOUT K_SECONDS(1)
|
77 | 79 | #define L2CAP_BR_CONN_TIMEOUT K_SECONDS(40)
|
| 80 | +#define L2CAP_BR_ECHO_TIMEOUT K_SECONDS(30) |
78 | 81 |
|
79 | 82 | #define L2CAP_FEAT_FC_MASK BIT(0)
|
80 | 83 | #define L2CAP_FEAT_RET_MASK BIT(1)
|
@@ -228,6 +231,8 @@ struct bt_l2cap_br {
|
228 | 231 |
|
229 | 232 | static struct bt_l2cap_br bt_l2cap_br_pool[CONFIG_BT_MAX_CONN];
|
230 | 233 |
|
| 234 | +static sys_slist_t bt_l2cap_br_echo_cbs = SYS_SLIST_STATIC_INIT(&bt_l2cap_br_echo_cbs); |
| 235 | + |
231 | 236 | struct bt_l2cap_chan *bt_l2cap_br_lookup_rx_cid(struct bt_conn *conn,
|
232 | 237 | uint16_t cid)
|
233 | 238 | {
|
@@ -343,6 +348,7 @@ static void l2cap_br_rtx_timeout(struct k_work *work)
|
343 | 348 | if (chan->rx.cid == BT_L2CAP_CID_BR_SIG) {
|
344 | 349 | LOG_DBG("Skip BR/EDR signalling channel ");
|
345 | 350 | atomic_clear_bit(chan->flags, L2CAP_FLAG_SIG_INFO_PENDING);
|
| 351 | + chan->ident = 0; |
346 | 352 | return;
|
347 | 353 | }
|
348 | 354 |
|
@@ -4521,6 +4527,7 @@ static void l2cap_br_disconnected(struct bt_l2cap_chan *chan)
|
4521 | 4527 | * so this should always succeed.
|
4522 | 4528 | */
|
4523 | 4529 | (void)k_work_cancel_delayable(&br_chan->rtx_work);
|
| 4530 | + br_chan->ident = 0; |
4524 | 4531 | }
|
4525 | 4532 | }
|
4526 | 4533 |
|
@@ -4820,6 +4827,40 @@ static void l2cap_br_reject_rsp(struct bt_l2cap_br *l2cap, uint8_t ident, struct
|
4820 | 4827 | } while (chan != NULL);
|
4821 | 4828 | }
|
4822 | 4829 |
|
| 4830 | +static void l2cap_br_echo_req(struct bt_l2cap_br *l2cap, uint8_t ident, struct net_buf *buf) |
| 4831 | +{ |
| 4832 | + struct bt_conn *conn = l2cap->chan.chan.conn; |
| 4833 | + struct bt_l2cap_br_echo_cb *callback; |
| 4834 | + |
| 4835 | + SYS_SLIST_FOR_EACH_CONTAINER(&bt_l2cap_br_echo_cbs, callback, _node) { |
| 4836 | + if (callback->req) { |
| 4837 | + callback->req(conn, ident, buf); |
| 4838 | + } |
| 4839 | + } |
| 4840 | +} |
| 4841 | + |
| 4842 | +static void l2cap_br_echo_rsp(struct bt_l2cap_br *l2cap, uint8_t ident, struct net_buf *buf) |
| 4843 | +{ |
| 4844 | + struct bt_conn *conn = l2cap->chan.chan.conn; |
| 4845 | + struct bt_l2cap_br_echo_cb *callback; |
| 4846 | + |
| 4847 | + if (ident != l2cap->chan.ident) { |
| 4848 | + LOG_WRN("ident mismatch (%u != %u)!", l2cap->chan.ident, ident); |
| 4849 | + goto failed; |
| 4850 | + } |
| 4851 | + |
| 4852 | + SYS_SLIST_FOR_EACH_CONTAINER(&bt_l2cap_br_echo_cbs, callback, _node) { |
| 4853 | + if (callback->rsp) { |
| 4854 | + callback->rsp(conn, buf); |
| 4855 | + } |
| 4856 | + } |
| 4857 | + |
| 4858 | +failed: |
| 4859 | + l2cap->chan.ident = 0; |
| 4860 | + /* Release RTX work since got the response */ |
| 4861 | + k_work_cancel_delayable(&l2cap->chan.rtx_work); |
| 4862 | +} |
| 4863 | + |
4823 | 4864 | static void l2cap_br_sig_handle(struct bt_l2cap_br *l2cap, struct bt_l2cap_sig_hdr *hdr,
|
4824 | 4865 | struct net_buf *buf)
|
4825 | 4866 | {
|
@@ -4858,6 +4899,12 @@ static void l2cap_br_sig_handle(struct bt_l2cap_br *l2cap, struct bt_l2cap_sig_h
|
4858 | 4899 | case BT_L2CAP_CMD_REJECT:
|
4859 | 4900 | l2cap_br_reject_rsp(l2cap, hdr->ident, buf);
|
4860 | 4901 | break;
|
| 4902 | + case BT_L2CAP_ECHO_REQ: |
| 4903 | + l2cap_br_echo_req(l2cap, hdr->ident, buf); |
| 4904 | + break; |
| 4905 | + case BT_L2CAP_ECHO_RSP: |
| 4906 | + l2cap_br_echo_rsp(l2cap, hdr->ident, buf); |
| 4907 | + break; |
4861 | 4908 | default:
|
4862 | 4909 | LOG_WRN("Unknown/Unsupported L2CAP PDU code 0x%02x", hdr->code);
|
4863 | 4910 | l2cap_br_send_reject(l2cap->chan.chan.conn, hdr->ident, BT_L2CAP_REJ_NOT_UNDERSTOOD,
|
@@ -6003,3 +6050,133 @@ void bt_l2cap_br_init(void)
|
6003 | 6050 | bt_avrcp_init();
|
6004 | 6051 | }
|
6005 | 6052 | }
|
| 6053 | + |
| 6054 | +int bt_l2cap_br_echo_cb_register(struct bt_l2cap_br_echo_cb *cb) |
| 6055 | +{ |
| 6056 | + CHECKIF(cb == NULL) { |
| 6057 | + return -EINVAL; |
| 6058 | + } |
| 6059 | + |
| 6060 | + if (sys_slist_find(&bt_l2cap_br_echo_cbs, &cb->_node, NULL)) { |
| 6061 | + return -EEXIST; |
| 6062 | + } |
| 6063 | + |
| 6064 | + sys_slist_append(&bt_l2cap_br_echo_cbs, &cb->_node); |
| 6065 | + |
| 6066 | + return 0; |
| 6067 | +} |
| 6068 | + |
| 6069 | +int bt_l2cap_br_echo_cb_unregister(struct bt_l2cap_br_echo_cb *cb) |
| 6070 | +{ |
| 6071 | + CHECKIF(cb == NULL) { |
| 6072 | + return -EINVAL; |
| 6073 | + } |
| 6074 | + |
| 6075 | + if (!sys_slist_find_and_remove(&bt_l2cap_br_echo_cbs, &cb->_node)) { |
| 6076 | + return -ENOENT; |
| 6077 | + } |
| 6078 | + |
| 6079 | + return 0; |
| 6080 | +} |
| 6081 | + |
| 6082 | +int bt_l2cap_br_echo_req(struct bt_conn *conn, struct net_buf *buf) |
| 6083 | +{ |
| 6084 | + struct bt_l2cap_chan *chan; |
| 6085 | + struct bt_l2cap_sig_hdr *hdr; |
| 6086 | + int err; |
| 6087 | + |
| 6088 | + if ((conn == NULL) || (buf == NULL)) { |
| 6089 | + return -EINVAL; |
| 6090 | + } |
| 6091 | + |
| 6092 | + LOG_DBG("ACL conn %p buf %p len %u", conn, buf, buf->len); |
| 6093 | + |
| 6094 | + if (buf->ref != 1) { |
| 6095 | + LOG_WRN("Expecting 1 ref, got %d", buf->ref); |
| 6096 | + return -EINVAL; |
| 6097 | + } |
| 6098 | + |
| 6099 | + if (buf->len >= (L2CAP_BR_MIN_MTU - sizeof(*hdr))) { |
| 6100 | + LOG_ERR("attempt to send %u bytes on %u MTU chan", buf->len, L2CAP_BR_MIN_MTU); |
| 6101 | + return -EMSGSIZE; |
| 6102 | + } |
| 6103 | + |
| 6104 | + if (net_buf_headroom(buf) < BT_L2CAP_BR_ECHO_REQ_RESERVE) { |
| 6105 | + /* Call `net_buf_reserve(buf, BT_L2CAP_BR_ECHO_REQ_RESERVE)` |
| 6106 | + * when allocating buffers intended for bt_l2cap_br_echo_req(). |
| 6107 | + */ |
| 6108 | + LOG_ERR("Not enough headroom in buf %p", buf); |
| 6109 | + return -EINVAL; |
| 6110 | + } |
| 6111 | + |
| 6112 | + chan = bt_l2cap_br_lookup_rx_cid(conn, BT_L2CAP_CID_BR_SIG); |
| 6113 | + if (chan == NULL) { |
| 6114 | + LOG_ERR("Signaling Channel %u not found", BT_L2CAP_CID_BR_SIG); |
| 6115 | + return -ENOTCONN; |
| 6116 | + } |
| 6117 | + |
| 6118 | + if (BR_CHAN(chan)->ident) { |
| 6119 | + LOG_ERR("Waiting for ECHO RSP"); |
| 6120 | + return -EBUSY; |
| 6121 | + } |
| 6122 | + |
| 6123 | + hdr = net_buf_push(buf, sizeof(*hdr)); |
| 6124 | + |
| 6125 | + hdr->code = BT_L2CAP_ECHO_REQ; |
| 6126 | + hdr->ident = l2cap_br_get_ident(); |
| 6127 | + hdr->len = sys_cpu_to_le16(buf->len - sizeof(*hdr)); |
| 6128 | + |
| 6129 | + /* Set the ident for the signaling request */ |
| 6130 | + BR_CHAN(chan)->ident = hdr->ident; |
| 6131 | + |
| 6132 | + err = bt_l2cap_br_send_cb(conn, BT_L2CAP_CID_BR_SIG, buf, NULL, NULL); |
| 6133 | + if (err == 0) { |
| 6134 | + k_work_reschedule(&BR_CHAN(chan)->rtx_work, L2CAP_BR_ECHO_TIMEOUT); |
| 6135 | + } |
| 6136 | + |
| 6137 | + return err; |
| 6138 | +} |
| 6139 | + |
| 6140 | +int bt_l2cap_br_echo_rsp(struct bt_conn *conn, uint8_t identifier, struct net_buf *buf) |
| 6141 | +{ |
| 6142 | + struct bt_l2cap_chan *chan; |
| 6143 | + struct bt_l2cap_sig_hdr *hdr; |
| 6144 | + |
| 6145 | + if ((conn == NULL) || (buf == NULL) || (identifier == 0)) { |
| 6146 | + return -EINVAL; |
| 6147 | + } |
| 6148 | + |
| 6149 | + LOG_DBG("ACL conn %p buf %p len %u", conn, buf, buf->len); |
| 6150 | + |
| 6151 | + if (buf->ref != 1) { |
| 6152 | + LOG_WRN("Expecting 1 ref, got %d", buf->ref); |
| 6153 | + return -EINVAL; |
| 6154 | + } |
| 6155 | + |
| 6156 | + if (buf->len >= (L2CAP_BR_MIN_MTU - sizeof(*hdr))) { |
| 6157 | + LOG_ERR("attempt to send %u bytes on %u MTU chan", buf->len, L2CAP_BR_MIN_MTU); |
| 6158 | + return -EMSGSIZE; |
| 6159 | + } |
| 6160 | + |
| 6161 | + if (net_buf_headroom(buf) < BT_L2CAP_BR_ECHO_RSP_RESERVE) { |
| 6162 | + /* Call `net_buf_reserve(buf, BT_L2CAP_BR_ECHO_RSP_RESERVE)` |
| 6163 | + * when allocating buffers intended for bt_l2cap_br_echo_rsp(). |
| 6164 | + */ |
| 6165 | + LOG_ERR("Not enough headroom in buf %p", buf); |
| 6166 | + return -EINVAL; |
| 6167 | + } |
| 6168 | + |
| 6169 | + chan = bt_l2cap_br_lookup_rx_cid(conn, BT_L2CAP_CID_BR_SIG); |
| 6170 | + if (chan == NULL) { |
| 6171 | + LOG_ERR("Signaling Channel %u not found", BT_L2CAP_CID_BR_SIG); |
| 6172 | + return -ENOTCONN; |
| 6173 | + } |
| 6174 | + |
| 6175 | + hdr = net_buf_push(buf, sizeof(*hdr)); |
| 6176 | + |
| 6177 | + hdr->code = BT_L2CAP_ECHO_RSP; |
| 6178 | + hdr->ident = identifier; |
| 6179 | + hdr->len = sys_cpu_to_le16(buf->len - sizeof(*hdr)); |
| 6180 | + |
| 6181 | + return bt_l2cap_br_send_cb(conn, BT_L2CAP_CID_BR_SIG, buf, NULL, NULL); |
| 6182 | +} |
0 commit comments