mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-08-20 23:55:22 +00:00
Added GATT Object Transfer Service implementation. Signed-off-by: Kamil Piszczek <Kamil.Piszczek@nordicsemi.no>
216 lines
4.7 KiB
C
216 lines
4.7 KiB
C
/*
|
|
* Copyright (c) 2020 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/types.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <sys/printk.h>
|
|
#include <sys/byteorder.h>
|
|
#include <zephyr.h>
|
|
#include <init.h>
|
|
|
|
#include <net/buf.h>
|
|
|
|
#include "ots_l2cap_internal.h"
|
|
|
|
#include <logging/log.h>
|
|
|
|
LOG_MODULE_DECLARE(bt_ots, CONFIG_BT_OTS_LOG_LEVEL);
|
|
|
|
/* 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
|
|
|
|
/* Maximum size of TX buffer and its payload. */
|
|
#define MAX_TX_BUF_SIZE 256
|
|
#define MAX_TX_BUF_PAYLOAD_SIZE (MAX_TX_BUF_SIZE - BT_L2CAP_CHAN_SEND_RESERVE)
|
|
|
|
NET_BUF_POOL_FIXED_DEFINE(ot_chan_tx_pool, 1, MAX_TX_BUF_SIZE, NULL);
|
|
|
|
/* 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, MAX_TX_BUF_PAYLOAD_SIZE);
|
|
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_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;
|
|
}
|
|
|
|
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 void l2cap_status(struct bt_l2cap_chan *chan, atomic_t *status)
|
|
{
|
|
LOG_DBG("Channel %p status %u", chan, *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)
|
|
{
|
|
LOG_DBG("Channel %p disconnected", chan);
|
|
}
|
|
|
|
static const struct bt_l2cap_chan_ops l2cap_ops = {
|
|
.sent = l2cap_sent,
|
|
.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 int l2cap_accept(struct bt_conn *conn, struct bt_l2cap_chan **chan)
|
|
{
|
|
struct bt_gatt_ots_l2cap *l2cap_ctx;
|
|
|
|
LOG_DBG("Incoming conn %p", conn);
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&channels, l2cap_ctx, node) {
|
|
if (l2cap_ctx->ot_chan.chan.conn) {
|
|
continue;
|
|
}
|
|
|
|
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(const struct device *arg)
|
|
{
|
|
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;
|
|
}
|
|
|
|
SYS_INIT(bt_gatt_ots_l2cap_init, APPLICATION,
|
|
CONFIG_APPLICATION_INIT_PRIORITY);
|