Logical Link Control and Adaptation Layer Protocol (L2CAP)¶
The L2CAP layer sits on top of the HCI layer on the host side and transfers data between the upper layers of the host (GAP, GATT, SM, application) and the link layer. This layer is responsible for protocol multiplexing capability, segmentation, and reassembly operation for data exchanged between the host and the protocol stack. All data from the host or application goes through and is encapsulated in an L2CAP packet. L2CAP permits higher-level protocols (such as GATT and SM) and applications to transmit and receive upper layer data packets (L2CAP service data units, SDU) up to 64KB long. See Figure 66. for more information.
Note
The actual L2CAP payload size is limited by the amount of memory available on the specific device being implemented. L2CAP also permits per-channel flow control and retransmission.
General L2CAP Terminology¶
Term |
Description |
---|---|
L2CAP channel |
The logical connection between two endpoints in peer devices, characterized by their Channel Identifiers (CIDs) |
SDU or L2CAP SDU |
Service Data Unit: a packet of data that L2CAP exchanges with the upper layer and transports transparently over an L2CAP channel using the procedures specified in this document. This is the raw payload from the host/app, and does not include L2CAP headers. |
PDU or L2CAP PDU |
Protocol Data Unit: a packet of data containing L2CAP protocol information fields, control information, and/or upper layer information data. This packet includes L2CAP headers. A single SDU may be split across multiple PDUs. |
Maximum Transmission Unit (MTU) |
The maximum size of payload data, in octets, that the upper layer entity can accept (that is, the MTU corresponds to the maximum SDU size). Note: This is different than ATT_MTU. |
Maximum PDU Payload Size (MPS) |
The maximum size of payload data in octets that the L2CAP layer entity can accept (that is, the MPS corresponds to the maximum PDU payload size). |
Credit |
The number of LE-frames that the device can receive. Credits may range between 1 and 65535, and are used as a flow control mechanism between devices. |
L2CAP Basic Header |
L2CAP protocol information that is prepended to each PDU. This includes CID and length |
Protocol/Service Multiplexer (PSM) |
A two octet field that is used to define the interpretation of L2CAP channel data. There are both dynamic and fixed PSMs. Fixed PSMs are assigned by the SIG, while dynamic PSMs may be discovered by GATT. |
Fragmentation/Recombination |
Fragmentation is the process of breaking down L2CAP PDUs into smaller pieces for the controller to send out. Recombination is the process of the controller reassembling fragments into complete L2CAP PDUs. Fragmentation/Recombination is performed by the controller and is based on the LE Data Length Extension feature. Fragmentation/Recombination operations are transparent to L2CAP. |
Segmentation/Reassembly |
Segmentation is the process of breaking a single L2CAP SDU up multiple L2CAP packets called SDU segments. Reassembly in the inverse of this operation on the receive side. Each segment is encapsulated in a proper L2CAP header. Both segmentation and reassembly is handled by the L2CAP layer and transparent to lower and higher layers. |
Important
The max SDU size supported by the stack is L2CAP_SDU_SIZE
.
L2CAP Modes of Operation¶
The BLE5-Stack supports two different modes of operation of the L2CAP layer.
Basic L2CAP Mode
LE Credit Based Flow Control Mode
Note that the L2CAP section is shared for BR/EDR, BR/EDR/LE (dual mode), and LE only controller implementations. The BLE5-Stack controller implementation is LE only, thus only the modes above are relevant.
L2CAP Channels¶
There are three types of channels in L2CAP:
Connection-oriented
Connectionless data
L2CAP signaling
Each endpoint of an L2CAP channel is referred to by a channel identifier (CID). See the Channel Identifiers section ([Vol 3], Part A, Section 2.1) of the Bluetooth Core Specifications Version 5.3 for more details on L2CAP Channel Identifiers. Connectionless channels are not supported over the LE-U controller, and thus are not used by the BLE5-Stack.
Channels can be divided into fixed and dynamic channels.
Fixed Channels¶
Fixed channels perform a specific L2CAP function, and use CIDs between 0x0001 and 0x003F. The characteristics of each fixed channel (such as MTU) are defined on a per channel basis.
Tip
Higher level protocols such as ATT may enforce their own MTU, which is different than the L2CAP MTU.
The relevant CIDs that are available for use by the stack or application are listed below.
CID |
Description |
Usage |
0x0004 |
Attribute Protocol (ATT) |
Sending ATT information |
0x0005 |
LE Signaling Channel |
Sending L2CAP commands |
0x0006 |
Security Manager Protocol (SMP) |
Sending pairing/security information |
0x0040-0x007F |
Dynamically Allocated |
LE Credit Based Flow control packets |
For example, data exchanged over the GATT protocol uses channel 0x0004, SMP (pairing and security) uses 0x0006, and the LE signaling channel uses 0x0005. The ATT, SMP, and signaling channels are not directly accessible via the application as they are used by their associated Host layers. Put another way, when calling a GATT related API, it handles the necessary encapsulation into an L2CAP packet. The signaling channel is used for L2CAP connection parameter update procedure, establishing LE credit based connections on the dynamic channels, and exchanging credits.
Dynamically Allocated Channels¶
A dynamically allocated CID is allocated to identify the logical link and the local endpoint. The local endpoint must be in the range from 0x0040 to 0xFFFF. This endpoint is used in the connection-orientated L2CAP channels described in the following section. Credit Based Flow Control mode is used by the L2CAP layer for Connection-Oriented Channels. These dynamically assigned channels are accessible and managed directly at the application layer. This means that the application is responsible for defining its own protocol on top of L2CAP CoC. L2CAP channels are bidirectional and are analogous to sockets.
When a channel is dynamically allocated, it must have the following parameters set:
PSM
MTU
CID
Fixed PSMs are defined by the Bluetooth SIG, these range between 0x0001 and 0x007F. Dynamic PSMs range between 0x0080 and 0x00FF. PSMs may be fixed on GATT server devices, while GATT clients shall obtain PSMs from the GATT service.
L2CAP Frame types¶
There are two frame types that are used by the BLE5-Stack. These are the Basic frame which is used by the fixed channels in basic mode, and the LE information frame which is used by the dynamic channels in LE credit based flow control mode. L2CAP handles framing of the SDU data from the host or application, but it is important to keep the protocol overhead of each frame in mind as it will affect how much application data ends up in a PDU.
The contents of the L2CAP frames are defined in Vol 3, Part A. The LE information frame is defined in section 3.4 and the basic frame is defined in section 3.1. The headers sizes are summarized here for reference.
L2CAP Frame Type |
Header Size (octets) |
Header contents |
Basic frame |
4 |
Length: 2 octets CID: 2 octets |
LE Information Frame |
6 |
Length: 2 octets CID: 2 octets SDU length: 2 octets |
Fragmentation/Recombination¶
From an L2CAP perspective, all packets are delivered to and received from the controller as complete packets. This means that fragmentation/recombination (if enabled by LE Data Length Extension) is performed by the controller and not visible to L2CAP. See LE Data Length Extension (DLE) for more information.
When fragmentation is used, larger packets are split into multiple link layer packets and reassembled by the link layer of the peer device. The picture below shows this relationship.
Note
The DLE PDU is negotiated by LL_LEN_REQ
and LL_LEN_RSP
. DLE PDU is NOT the same
as L2CAP PDU. For more information regarding how to change the DLE PDU, see
LE Data Length Extension (DLE).
Segmentation/Reassembly¶
When operating in Basic Mode, L2CAP will not perform any segmentation or reassembly. However, when operating in LE Credit Based Flow Control mode on a dynamic channel (CoC) segmentation and reassembly at the L2CAP layer may occur.
Configuring L2CAP¶
Build Configuration¶
The L2CAP CoC dynamic channels by default is not enabled in the projects. It can be enabled by the following methods:
SysConfig tool: For projects that support SysConfig tool, you can enable
L2CAP CoC dynamic channels by checking L2CAP Connection Oriented Channels
box.
Please refer to Figure 113. for BLE5-Stack feature overview.
build_config.opt: For projects that do not support SysConfig tool, you can enable L2CAP CoC dynamic channels by adding
-DV41_FEATURES=L2CAP_COC_CFG
tobuild_config.opt
file.
Runtime Configuration¶
Tip
This is a bit of a misnomer because generally these parameters are set
via #define
but they are in fact initialized dynamically at the time
of the stack boot. In this case, runtime means that they not fixed by the
protocol stack library and may be changed by the user.
These L2CAP parameters are passed into the BLE5-Stack at initialization via the
.opt
file. These include:
L2CAP_NUM_PSM
: Number of Protocol/Service multiplexersL2CAP_NUM_CO_CHANNELS
: Number of allowed dynamic CoCs.MAX_PDU_SIZE
: Max PDU buffer size that the controller acceptsMAX_NUM_PDU
: Number of L2CAP TX PDU buffers in the controllerFor more information for how to change the above parameters, please see Stack Configurations
L2CAP MTU¶
As mentioned in General L2CAP Terminology, the L2CAP MTU is the maximum payload that can be processed by the L2CAP layer. The MTU size is the largest SDU that L2CAP will accept.
However, the MTU used by L2CAP will vary depending on the mode and channel type
Signaling channel will use the
L2CAP_SIG_MTU_SIZE
Fixed channel packets are limited by
MAX_PDU_SIZE - L2CAP_HDR_SIZE
CoC packets depend on the PSM’s MTU and are limited by
L2CAP_SDU_SIZE
For fixed channel MTU is defined by a higher level protocol such as ATT.
On dynamic connection oriented channels, the MTU is bound by the minimum of
L2CAP_SDU_SIZE
and the peer’s supported MTU.
RAM Considerations¶
Care must be taken with respect to RAM when enabling these features as they will consume heap memory. Additionally, since L2CAP is responsible for encapsulating packets from higher layers, the higher level protocols may have requirements on how L2CAP is configured.
L2CAP Frame Type |
Heap Allocation |
Alloc time |
|
sizeof(l2capPsm_t)*L2CAP_NUM_PSM |
L2CAP init |
|
sizeof(l2capChannel_t)* (MAX_NUM_BLE_CONNS + L2CAP_NUM_CO_CHANNELS) |
L2CAP Init |
|
sizeof( l2capCoChannel_t ) |
Channel creation |
|
Depends on application usage, can be up to MAX_PDU_SIZE*MAX_NUM_PDU in TX case. In the RX case it depends on RX throughput |
Runtime |
|
See above |
Runtime |
The two parameters are only used by dynamic connection oriented channels,
and do not need to be considered if the feature is not used. MAX_PDU_SIZE
and MAX_NUM_PDU
as they do affect both the fixed channels used by ATT and
SM as well as the dynamic connection oriented channels.
When using connection oriented channels, L2CAP will allocate space for each TX segment of the SDU before passing to the controller. The size is based on the max packet size that is negotiated by the controller.
On RX, L2CAP will allocate the size of the entire SDU on receiving the first packet based on the length field in the header.
For signaling commands on the fixed channels, L2CAP will allocate the memory
for the packet itself, based on L2CAP_SIG_MTU_SIZE
which is the MTU of the
signaling channel.
Tip
For data packet payloads, the higher level protocol (ATT, SM, Application)
is responsible for allocating using L2CAP_bm_alloc(...)
. In the case
of ATT and SM, this is done transparent to the user. For CoC SDUs, the user
owns the memory associated with the payload.
Controller to Host Flow Control¶
As mentioned above, MAX_NUM_PDU
defines the max number of TX packets that
can be queued up to the controller at a time. Attempting to send more packets
will result in a failure code being returned by the high level API and the
packet not being queued up by the controller.
The application may not know how many packets are queued up at a given time so it is best to always check return codes. Additionally, the application can increase the efficiency at which is queues packets up by registering for flow control notifications from the L2CAP layer.
This can be done with L2CAP_RegisterFlowCtrlTask(). When enabled,
the API will notify the application with L2CAP_NUM_CTRL_DATA_PKT_EVT
with
the number of data packets that are available for sending. This event is
triggered each time a new buffer becomes available.
Connection Oriented Channels Example¶
The BLE5-Stack provides APIs to create L2CAP CoC channels to transfer bidirectional data between two Bluetooth Low Energy devices supporting this feature. Following are the steps and generic code example which are needed to create L2CAP CoC channels communication using basic_ble framework.
Enable L2CAP CoC Feature, please refer to Build Configuration
Define APP_L2CAP_SPSM. Since this constant needs to get accessed from several files, this definition should be placed in app_main.h.
1//The valid range for SPSM is 0x0080 ~ 0x00FF, 0x0080 is used as an example. 2#define APP_L2CAP_SPSM 0x0080
Create file app_l2cap.c and add it to the project –> app folder. This file will contain most of the changes needed for using L2CAP CoC Channels. Therefore, create a new file app_l2cap.c inside the app folder of the basic_ble project. The file should contain the following header.
1/****************************************************************************** 2 3@file app_l2cap.c 4 5@brief This file contains the L2CAP functionality 6 7Group: WCS, BTS 8$Target Device: DEVICES $ 9 10****************************************************************************** 11$Release Name: PACKAGE NAME $ 12$Release Date: PACKAGE RELEASE DATE $ 13*****************************************************************************/ 14 15//***************************************************************************** 16//! Includes 17//***************************************************************************** 18#include <stdio.h> 19#include <string.h> 20#include "ti_ble_config.h" 21#include <ti/bleapp/ble_app_util/inc/bleapputil_api.h> 22#include <ti/bleapp/menu_module/menu_module.h> 23#include <app_main.h> 24 25//***************************************************************************** 26//! Defines 27//***************************************************************************** 28 29//***************************************************************************** 30//! Globals 31//***************************************************************************** 32 33//***************************************************************************** 34//! Functions 35//*****************************************************************************
Register Simplified Protocol/Service Multiplexer (SPSM). SPSM registration is needed for both central and peripheral devices. The basic_ble example can be configured to both, central and peripheral, but the following code will be executed in both scenarios. Place this function in app_l2cap.c.
1//***************************************************************************** 2//! Functions 3//***************************************************************************** 4 5/********************************************************************* 6 * @fn L2cap_start 7 * 8 * @brief This function is called after stack initialization, 9 * the purpose of this function is to initialize and 10 * register the L2CAP CoC functionality. 11 * 12 * @return SUCCESS, errorInfo 13 */ 14bStatus_t L2cap_start( void ) 15{ 16 bStatus_t status = SUCCESS; 17 18 // Register the handlers 19 status = BLEAppUtil_registerEventHandler( &data_L2CACAP_SIGNAL_Handler ); 20 status = BLEAppUtil_registerEventHandler( &data_L2CACAP_DATA_Handler ); 21 22 // Setup SPSM 23 l2capPsm_t psm; 24 l2capPsmInfo_t psmInfo; 25 26 if (L2CAP_PsmInfo(APP_L2CAP_SPSM, &psmInfo) == INVALIDPARAMETER) 27 { 28 // Prepare the PSM parameters 29 psm.initPeerCredits = 0xFFFF; //give peer maximum credit 30 psm.maxNumChannels = MAX_NUM_BLE_CONNS; 31 psm.mtu = MAX_PDU_SIZE; 32 psm.peerCreditThreshold = 0; 33 psm.pfnVerifySecCB = NULL; 34 psm.psm = APP_L2CAP_SPSM; 35 psm.taskId = ICall_getLocalMsgEntityId(ICALL_SERVICE_CLASS_BLE_MSG, BLEAppUtil_getSelfEntity()); 36 37 // Register SPSM with L2CAP task 38 status = L2CAP_RegisterPsm(&psm); 39 } 40 else 41 { 42 // L2CAP setup failed 43 status = FAILURE; 44 } 45 46 // Return status value 47 return( status ); 48}
Note
This function also contains the event handler registrations, which will be explained later.
Call L2cap_start from the
App_StackInitDoneHandler
in file app_main.c.1void App_StackInitDoneHandler(gapDeviceInitDoneEvent_t *deviceInitDoneData) 2{ 3 ... 4 5 #if defined( V41_FEATURES ) && ( V41_FEATURES & ( L2CAP_COC_CFG ) ) 6 status = L2cap_start(); 7 if(status != SUCCESS) 8 { 9 // Call Error Handler 10 } 11 #endif
Central or peripheral device sends L2CAP LE Credit Based Connection Request. This request can be done at the end of the BLEAPPUTIL_LINK_ESTABLISHED_EVENT.
1void Connection_ConnEventHandler(uint32 event, BLEAppUtil_msgHdr_t *pMsgData) 2{ 3 ... 4 5 case BLEAPPUTIL_LINK_ESTABLISHED_EVENT: 6 { 7 ... 8 9 // Send out L2CAP_LE_CREDIT_BASED_CONNECTION_REQ, only one side needs to do this 10 L2CAP_ConnectReq(gapEstMsg->connectionHandle, APP_L2CAP_SPSM, APP_L2CAP_SPSM); 11 12 break; 13 }
Note
Both central and peripheral device can request to establish L2CAP CoC channels. As long as one of the device requests, the link establishment request will be sent. There is no need to add this part of the code for both devices, so here it is only added conditionally for the central device.
Once the L2CAP credit based connection establishment is successful, the BLE5-Stack will send
BLEAPPUTIL_L2CAP_SIGNAL_TYPE
through the application data module with opcodeBLEAPPUTIL_L2CAP_CHANNEL_ESTABLISHED_EVT
for both central and peripheral devices. Therefore, we will implement a function to handle L2CAP messages in app_l2cap.c and register the event handler accordingly.1//***************************************************************************** 2//! Globals 3//***************************************************************************** 4 5... 6 7// Events handlers struct, contains the handlers and event masks 8// of the application data module 9BLEAppUtil_EventHandler_t data_L2CACAP_SIGNAL_Handler = 10{ 11 .handlerType = BLEAPPUTIL_L2CAP_SIGNAL_TYPE, 12 .pEventHandler = L2CAP_SIGNAL_EventHandler, 13 .eventMask = BLEAPPUTIL_L2CAP_CHANNEL_ESTABLISHED_EVT | 14 BLEAPPUTIL_L2CAP_CHANNEL_TERMINATED_EVT | 15 BLEAPPUTIL_L2CAP_SEND_SDU_DONE_EVT 16}; 17 18//***************************************************************************** 19//! Functions 20//***************************************************************************** 21 22... 23 24bStatus_t L2cap_start( void ) 25{ 26 ... 27 28 // Register the handlers 29 status = BLEAppUtil_registerEventHandler( &data_L2CACAP_SIGNAL_Handler ); 30 31 ...
1//***************************************************************************** 2//! Globals 3//***************************************************************************** 4 5... 6 7static void L2CAP_SIGNAL_EventHandler(uint32 event, BLEAppUtil_msgHdr_t *pMsgData); 8 9... 10 11// Holds the connection ID 12static uint16_t cocCID; 13 14//***************************************************************************** 15//! Functions 16//***************************************************************************** 17 18... 19 20/********************************************************************* 21 * @fn L2CAP_SIGNAL_EventHandler 22 * 23 * @brief The purpose of this function is to handle L2CAP signal 24 * events which were registered in 25 * @ref BLEAppUtil_registerEventHandler 26 * 27 * @param event - message event. 28 * @param pMsgData - pointer to message data. 29 * 30 * @return none 31 */ 32static void L2CAP_SIGNAL_EventHandler(uint32 event, BLEAppUtil_msgHdr_t *pMsgData) 33{ 34 l2capSignalEvent_t *l2capMsg = ( l2capSignalEvent_t * )pMsgData; 35 36 switch (event) 37 { 38 case BLEAPPUTIL_L2CAP_CHANNEL_ESTABLISHED_EVT: 39 { 40 l2capChannelEstEvt_t *pEstEvt = &(l2capMsg->cmd.channelEstEvt); 41 42 if (l2capMsg->connHandle != LINKDB_CONNHANDLE_INVALID && l2capMsg->connHandle < MAX_NUM_BLE_CONNS) 43 { 44 // Successfully establish link over L2CAP 45 // Extract the CID and store in the application layer 46 // This will be useful when sending data over L2CAP channels 47 cocCID = pEstEvt->CID; 48 } 49 else 50 { 51 // Could not establish an L2CAP link 52 } 53 54 // Give max credits to the other side 55 L2CAP_FlowCtrlCredit(pEstEvt->CID, 0xFFFF); 56 57 // Send test data 58 Application_sendL2capData(); 59 } 60 break; 61 62 case BLEAPPUTIL_L2CAP_SEND_SDU_DONE_EVT: 63 { 64 if (l2capMsg->hdr.status == SUCCESS) 65 { 66 // Successfully sending data over L2CAP 67 } 68 } 69 break; 70 71 case BLEAPPUTIL_L2CAP_CHANNEL_TERMINATED_EVT: 72 { 73 // L2CAP channel has been terminated 74 } 75 break; 76 77 default: 78 break; 79 } 80}
Note
Both peripheral and central devices need to exchange L2CAP Credits. After the link is established, devices can distribute credit using
L2CAP_FlowCtrlCredit
as shown in the listing above.Send data using L2CAP CoC channel. In order to send data over L2CAP, L2CAP_SendSDU() is needed. Therefore, we will implement a function Application_sendL2capData in app_l2cap.c and executed it for the
BLEAPPUTIL_L2CAP_CHANNEL_ESTABLISHED_EVT
. After successfully sending data over the air, the BLE5-Stack will sendBLEAPPUTIL_L2CAP_SIGNAL_TYPE
through the application data module with opcodeBLEAPPUTIL_L2CAP_SEND_SDU_DONE_EVT
.1//***************************************************************************** 2//! Functions 3//***************************************************************************** 4 5... 6 7uint8_t appData[] = "Test Data!"; 8 9bStatus_t Application_sendL2capData(void) 10{ 11 l2capPacket_t pkt; 12 bStatus_t status = SUCCESS; 13 14 // Tell L2CAP the desired Channel ID 15 pkt.CID = cocCID; 16 17 // Allocate space for payload 18 pkt.pPayload = L2CAP_bm_alloc(sizeof(appData)); 19 20 /* Copy payload data */ 21 memcpy(pkt.pPayload, appData, sizeof(appData)); 22 23 if (pkt.pPayload != NULL) 24 { 25 pkt.len = (sizeof(appData)); 26 27 // Print transmit data to serial terminal, expected data is /0 terminated 28 MenuModule_printf(11, 0, "L2CAP Data TX - %s", pkt.pPayload); 29 30 // Send packet 31 status = L2CAP_SendSDU(&pkt); 32 33 // Check that the packet was sent 34 if (SUCCESS != status) 35 { 36 // If SDU wasn't sent, free 37 BM_free(pkt.pPayload); 38 } 39 } 40 else 41 { 42 status = bleMemAllocError; 43 } 44 45 return (status); 46}
Process received data over L2CAP. When the device receives data from L2CAP, the BLE5-Stack will send
BLEAPPUTIL_L2CAP_DATA_TYPE
to the application data module. Therefore, we will implement a function to handle L2CAP data in app_l2cap.c and register the event handler accordingly.1//***************************************************************************** 2//! Globals 3//***************************************************************************** 4 5... 6 7BLEAppUtil_EventHandler_t data_L2CACAP_DATA_Handler = 8{ 9 .handlerType = BLEAPPUTIL_L2CAP_DATA_TYPE, 10 .pEventHandler = L2CAP_DATA_EventHandler 11}; 12 13//***************************************************************************** 14//! Functions 15//***************************************************************************** 16 17... 18 19bStatus_t L2cap_start( void ) 20{ 21 ... 22 23 status = BLEAppUtil_registerEventHandler( &data_L2CACAP_DATA_Handler ); 24 25 ...
1//***************************************************************************** 2//! Globals 3//***************************************************************************** 4 5... 6 7static void L2CAP_DATA_EventHandler(uint32 event, BLEAppUtil_msgHdr_t *pMsgData); 8 9//***************************************************************************** 10//! Functions 11//***************************************************************************** 12 13... 14 15/********************************************************************* 16 * @fn L2CAP_DATA_EventHandler 17 * 18 * @brief The purpose of this function is to handle L2CAP data 19 * events that rise from the GAP and were registered in 20 * @ref BLEAppUtil_RegisterGAPEvent 21 * 22 * @param event - message event. 23 * @param pMsgData - pointer to message data. 24 * 25 * @return none 26 */ 27static void L2CAP_DATA_EventHandler(uint32 event, BLEAppUtil_msgHdr_t *pMsgData) 28{ 29 if (!pMsgData) 30 { 31 // Caller needs to figure out by himself that pMsg is NULL 32 return; 33 } 34 35 l2capDataEvent_t *l2capDataEventData = ( l2capDataEvent_t * )pMsgData; 36 37 // Print received data to serial terminal, expected data is /0 terminated 38 MenuModule_printf(13, 0, "L2CAP Data RX - %s", l2capDataEventData->pkt.pPayload); 39}
The Figure 67. shows a sample connection and data exchange process between central and peripheral device using a L2CAP connection-oriented channel in LE Credit Based Flow Control Mode.