Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 93 additions & 1 deletion doc/connectivity/bluetooth/api/l2cap.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,101 @@ listen to, the required security level ``sec_level``, and the callback
``accept`` which is called to authorize incoming connection requests and
allocate channel instances.

Creating a simple L2CAP server
-----------------------------

The documentation above describes the APIs but does not show a minimal flow to create a working L2CAP server. The steps are:

1. Register a server using :c:func:`bt_l2cap_server_register` and provide an ``accept`` callback.
2. Prepare a channel instance (for example :c:type:`bt_l2cap_le_chan`) whose ``chan.ops`` points to a :c:type:`bt_l2cap_chan_ops` struct.
3. In the ``accept`` callback assign the channel instance to ``*chan`` to accept the incoming connection.
4. Implement the callbacks in :c:type:`bt_l2cap_chan_ops` such as ``recv``, ``connected`` and ``disconnected`` to handle data and connection events.

A minimal skeleton example follows (fill in allocation and error handling as needed):

.. code-block:: c

/* Minimal L2CAP server example (skeleton) */

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/l2cap.h>
#include <zephyr/sys/printk.h>

static int my_chan_recv(struct bt_l2cap_chan *chan, struct net_buf *buf);
static void my_chan_connected(struct bt_l2cap_chan *chan);
static void my_chan_disconnected(struct bt_l2cap_chan *chan);

/* Define channel operations */
static struct bt_l2cap_chan_ops my_chan_ops = {
.recv = my_chan_recv,
.connected = my_chan_connected,
.disconnected = my_chan_disconnected,
};

/* Channel instance (one per connection). Allocate dynamically if you need many connections. */
static struct bt_l2cap_le_chan my_chan = {
.chan.ops = &my_chan_ops,
};

/* Accept callback: called on an incoming connection request. */
static int my_server_accept(struct bt_conn *conn, struct bt_l2cap_chan **chan)
{
printk("L2CAP accept from %p\n", conn);

/* Assign our channel instance (or dynamically allocate a new one) */
*chan = &my_chan.chan;

/* return 0 to accept, or negative errno to reject */
return 0;
}

static void my_chan_connected(struct bt_l2cap_chan *chan)
{
printk("L2CAP channel connected\n");
}

static void my_chan_disconnected(struct bt_l2cap_chan *chan)
{
printk("L2CAP channel disconnected\n");
}

static int my_chan_recv(struct bt_l2cap_chan *chan, struct net_buf *buf)
{
printk("L2CAP received %u bytes\n", buf->len);
/* Process data here; returning 0 tells the stack to free the buffer.
* If you need to process asynchronously, return -EINPROGRESS and later
* call bt_l2cap_chan_recv_complete(chan, buf) when done (then you must
* manage buf ownership yourself).
*/
return 0;
}

static struct bt_l2cap_server my_server = {
.psm = BT_L2CAP_PSM_DYNAMIC, /* or a fixed PSM value */
.sec_level = BT_SECURITY_LOW,
.accept = my_server_accept,
};

/* Call this after Bluetooth is enabled to register the server */
static int register_my_server(void)
{
int err = bt_l2cap_server_register(&my_server);
if (err) {
printk("Failed to register L2CAP server: %d\n", err);
return err;
}
printk("L2CAP server registered\n");
return 0;
}

Notes
~~~~~
* For multiple concurrent connections allocate channel instances dynamically (k_malloc or pool) instead of reusing a single ``my_chan``.
* See the test reference implementation for a fuller example: ``tests/bsim/bluetooth/host/l2cap/credits/src/main.c``.

Client channels can be initiated with use of :c:func:`bt_l2cap_chan_connect`
API and can be disconnected with the :c:func:`bt_l2cap_chan_disconnect` API.
Note that the later can also disconnect channel instances created by servers.
Note that the latter can also disconnect channel instances created by servers.

API Reference
*************
Expand Down
Loading