| /* |
| * Copyright (c) 2020 - 2022 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/types.h> |
| #include <stddef.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <zephyr/sys/printk.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/init.h> |
| |
| #include <zephyr/net/buf.h> |
| |
| #include "ots_l2cap_internal.h" |
| |
| #include <zephyr/logging/log.h> |
| |
| /* This l2cap is the only OTS-file in use for OTC. |
| * If only OTC is used, the OTS log module must be registered here. |
| */ |
| #if defined(CONFIG_BT_OTS) |
| LOG_MODULE_DECLARE(bt_ots, CONFIG_BT_OTS_LOG_LEVEL); |
| #elif IS_ENABLED(CONFIG_BT_OTS_CLIENT) |
| LOG_MODULE_REGISTER(bt_ots, CONFIG_BT_OTS_LOG_LEVEL); |
| #endif |
| |
| /* According to BLE specification Assigned Numbers that are used in the |
| * Logical Link Control for protocol/service multiplexers. |
| */ |
| #define BT_GATT_OTS_L2CAP_PSM 0x0025 |
| |
| |
| NET_BUF_POOL_FIXED_DEFINE(ot_chan_tx_pool, 1, |
| BT_L2CAP_SDU_BUF_SIZE(CONFIG_BT_OTS_L2CAP_CHAN_TX_MTU), 8, |
| NULL); |
| |
| #if (CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU > BT_L2CAP_SDU_RX_MTU) |
| NET_BUF_POOL_FIXED_DEFINE(ot_chan_rx_pool, 1, CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU, 8, |
| NULL); |
| #endif |
| |
| /* List of Object Transfer Channels. */ |
| static sys_slist_t channels; |
| |
| static int ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx) |
| { |
| int ret; |
| struct net_buf *buf; |
| uint32_t len; |
| |
| /* Calculate maximum length of data chunk. */ |
| len = MIN(l2cap_ctx->ot_chan.tx.mtu, CONFIG_BT_OTS_L2CAP_CHAN_TX_MTU); |
| len = MIN(len, l2cap_ctx->tx.len - l2cap_ctx->tx.len_sent); |
| |
| /* Prepare buffer for sending. */ |
| buf = net_buf_alloc(&ot_chan_tx_pool, K_FOREVER); |
| net_buf_reserve(buf, BT_L2CAP_SDU_CHAN_SEND_RESERVE); |
| net_buf_add_mem(buf, &l2cap_ctx->tx.data[l2cap_ctx->tx.len_sent], len); |
| |
| ret = bt_l2cap_chan_send(&l2cap_ctx->ot_chan.chan, buf); |
| if (ret < 0) { |
| LOG_ERR("Unable to send data over CoC: %d", ret); |
| net_buf_unref(buf); |
| |
| return -ENOEXEC; |
| } |
| |
| /* Mark that L2CAP TX was accepted. */ |
| l2cap_ctx->tx.len_sent += len; |
| |
| LOG_DBG("Sending TX chunk with %d bytes on L2CAP CoC", len); |
| |
| return 0; |
| } |
| |
| #if (CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU > BT_L2CAP_SDU_RX_MTU) |
| static struct net_buf *l2cap_alloc_buf(struct bt_l2cap_chan *chan) |
| { |
| LOG_DBG("Channel %p allocating buffer", chan); |
| |
| return net_buf_alloc(&ot_chan_rx_pool, K_FOREVER); |
| } |
| #endif |
| |
| |
| static void l2cap_sent(struct bt_l2cap_chan *chan) |
| { |
| struct bt_gatt_ots_l2cap *l2cap_ctx; |
| |
| LOG_DBG("Outgoing data channel %p transmitted", chan); |
| |
| l2cap_ctx = CONTAINER_OF(chan, struct bt_gatt_ots_l2cap, ot_chan); |
| |
| /* Ongoing TX - sending next chunk. */ |
| if (l2cap_ctx->tx.len != l2cap_ctx->tx.len_sent) { |
| ots_l2cap_send(l2cap_ctx); |
| |
| return; |
| } |
| |
| /* TX completed - notify upper layers and clean up. */ |
| memset(&l2cap_ctx->tx, 0, sizeof(l2cap_ctx->tx)); |
| |
| LOG_DBG("Scheduled TX on L2CAP CoC is complete"); |
| |
| if (l2cap_ctx->tx_done) { |
| l2cap_ctx->tx_done(l2cap_ctx, chan->conn); |
| } |
| } |
| |
| static int l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) |
| { |
| struct bt_gatt_ots_l2cap *l2cap_ctx; |
| |
| LOG_DBG("Incoming data channel %p received", chan); |
| |
| l2cap_ctx = CONTAINER_OF(chan, struct bt_gatt_ots_l2cap, ot_chan); |
| |
| if (!l2cap_ctx->rx_done) { |
| return -ENODEV; |
| } |
| |
| return l2cap_ctx->rx_done(l2cap_ctx, chan->conn, buf); |
| } |
| |
| static void l2cap_status(struct bt_l2cap_chan *chan, atomic_t *status) |
| { |
| LOG_DBG("Channel %p status %lu", chan, atomic_get(status)); |
| } |
| |
| static void l2cap_connected(struct bt_l2cap_chan *chan) |
| { |
| LOG_DBG("Channel %p connected", chan); |
| } |
| |
| static void l2cap_disconnected(struct bt_l2cap_chan *chan) |
| { |
| struct bt_gatt_ots_l2cap *l2cap_ctx; |
| |
| LOG_DBG("Channel %p disconnected", chan); |
| |
| l2cap_ctx = CONTAINER_OF(chan, struct bt_gatt_ots_l2cap, ot_chan); |
| |
| if (l2cap_ctx->closed) { |
| l2cap_ctx->closed(l2cap_ctx, chan->conn); |
| } |
| } |
| |
| static const struct bt_l2cap_chan_ops l2cap_ops = { |
| #if (CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU > BT_L2CAP_SDU_RX_MTU) |
| .alloc_buf = l2cap_alloc_buf, |
| #endif |
| .sent = l2cap_sent, |
| .recv = l2cap_recv, |
| .status = l2cap_status, |
| .connected = l2cap_connected, |
| .disconnected = l2cap_disconnected, |
| }; |
| |
| static inline void l2cap_chan_init(struct bt_l2cap_le_chan *chan) |
| { |
| chan->rx.mtu = CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU; |
| chan->chan.ops = &l2cap_ops; |
| |
| LOG_DBG("RX MTU set to %u", chan->rx.mtu); |
| } |
| |
| static struct bt_gatt_ots_l2cap *find_free_l2cap_ctx(void) |
| { |
| struct bt_gatt_ots_l2cap *l2cap_ctx; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER(&channels, l2cap_ctx, node) { |
| if (l2cap_ctx->ot_chan.chan.conn) { |
| continue; |
| } |
| |
| return l2cap_ctx; |
| } |
| |
| return NULL; |
| } |
| |
| static int l2cap_accept(struct bt_conn *conn, struct bt_l2cap_chan **chan) |
| { |
| struct bt_gatt_ots_l2cap *l2cap_ctx; |
| |
| LOG_DBG("Incoming conn %p", (void *)conn); |
| |
| l2cap_ctx = find_free_l2cap_ctx(); |
| if (l2cap_ctx) { |
| l2cap_chan_init(&l2cap_ctx->ot_chan); |
| memset(&l2cap_ctx->tx, 0, sizeof(l2cap_ctx->tx)); |
| |
| *chan = &l2cap_ctx->ot_chan.chan; |
| |
| return 0; |
| } |
| |
| return -ENOMEM; |
| } |
| |
| static struct bt_l2cap_server l2cap_server = { |
| .psm = BT_GATT_OTS_L2CAP_PSM, |
| .accept = l2cap_accept, |
| }; |
| |
| static int bt_gatt_ots_l2cap_init(void) |
| { |
| int err; |
| |
| sys_slist_init(&channels); |
| |
| err = bt_l2cap_server_register(&l2cap_server); |
| if (err) { |
| LOG_ERR("Unable to register OTS PSM"); |
| return err; |
| } |
| |
| LOG_DBG("Initialized OTS L2CAP"); |
| |
| return 0; |
| } |
| |
| bool bt_gatt_ots_l2cap_is_open(struct bt_gatt_ots_l2cap *l2cap_ctx, |
| struct bt_conn *conn) |
| { |
| return (l2cap_ctx->ot_chan.chan.conn == conn); |
| } |
| |
| int bt_gatt_ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx, |
| uint8_t *data, uint32_t len) |
| { |
| int err; |
| |
| if (l2cap_ctx->tx.len != 0) { |
| LOG_ERR("L2CAP TX in progress"); |
| |
| return -EAGAIN; |
| } |
| |
| l2cap_ctx->tx.data = data; |
| l2cap_ctx->tx.len = len; |
| |
| LOG_DBG("Starting TX on L2CAP CoC with %d byte packet", len); |
| |
| err = ots_l2cap_send(l2cap_ctx); |
| if (err) { |
| LOG_ERR("Unable to send data over CoC: %d", err); |
| |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| int bt_gatt_ots_l2cap_register(struct bt_gatt_ots_l2cap *l2cap_ctx) |
| { |
| sys_slist_append(&channels, &l2cap_ctx->node); |
| |
| return 0; |
| } |
| |
| int bt_gatt_ots_l2cap_unregister(struct bt_gatt_ots_l2cap *l2cap_ctx) |
| { |
| sys_slist_find_and_remove(&channels, &l2cap_ctx->node); |
| |
| return 0; |
| } |
| |
| /* Similar to l2cap_accept(), but for the client side */ |
| int bt_gatt_ots_l2cap_connect(struct bt_conn *conn, |
| struct bt_gatt_ots_l2cap **l2cap_ctx) |
| { |
| int err; |
| struct bt_gatt_ots_l2cap *ctx; |
| |
| if (!conn) { |
| LOG_WRN("Invalid Connection"); |
| return -ENOTCONN; |
| } |
| |
| if (!l2cap_ctx) { |
| LOG_WRN("Invalid context"); |
| return -EINVAL; |
| } |
| |
| *l2cap_ctx = NULL; |
| |
| ctx = find_free_l2cap_ctx(); |
| if (!ctx) { |
| return -ENOMEM; |
| } |
| |
| l2cap_chan_init(&ctx->ot_chan); |
| (void)memset(&ctx->tx, 0, sizeof(ctx->tx)); |
| |
| LOG_DBG("Connecting L2CAP CoC"); |
| err = bt_l2cap_chan_connect(conn, &ctx->ot_chan.chan, BT_GATT_OTS_L2CAP_PSM); |
| if (err) { |
| LOG_WRN("Unable to connect to psm %u (err %d)", BT_GATT_OTS_L2CAP_PSM, err); |
| } else { |
| LOG_DBG("L2CAP connection pending"); |
| *l2cap_ctx = ctx; |
| } |
| |
| return err; |
| } |
| |
| int bt_gatt_ots_l2cap_disconnect(struct bt_gatt_ots_l2cap *l2cap_ctx) |
| { |
| return bt_l2cap_chan_disconnect(&l2cap_ctx->ot_chan.chan); |
| } |
| |
| SYS_INIT(bt_gatt_ots_l2cap_init, APPLICATION, |
| CONFIG_APPLICATION_INIT_PRIORITY); |