I am writing an application in C using SiliconLabs Simplicity IDE for an EFR32MG21 board that implements Bluetooth 5.0. My application sends data between devices using L2CAP connections over channels. One of the devices uses a CSR8675 Bluetooth chip that only supports Bluetooth spec 4.1.

I am trying to understand what I should use as the parameters to the LE CREDIT BASED CONNECTION REQUEST, LE CREDIT BASED CONNECTION RESPONSE, and LE FLOW CONTROL CREDIT signaling commands to get the fastest data rate.

The way the 4.1 & 5.0 specs read to me is that the credits are the number of LE-frames a device can send to another device. Got it. For 4.1, the LE-Frames can hold up to 23 bytes (27 - 4 byte header). So, since there's one credit for each LE-frame, I would think the maximum value of the MTU (maximum transferrable unit, aka SDU) parameter would be the combination of the lengths of the data in these LE-frames and that there'd be one LE-frame per MPS (Maximum PDU Size). However, the spec says that the MPS value in the LE CREDIT BASED CONNECTION REQUEST command can range from 23 to 65533 bytes. Can an MPS be bigger than the payload size of an LE-frame?

I understand that an MTU (maximum transferrable unit, aka SDU) parameter can be greater than 23. That makes sense since the MTU is the total length of all the LE-frames' payloads. But I don't understand why the 4.1 spec says that MPS can be greater than 23.

I inspected my data over the air and saw that, when I set my MPS to something greater than 23, the link layer broke that up into packets of 23 (27 - 4). It appeared as if it was breaking up the MPS packets that were the fragments of my MTU/SDU.

Help!

Thanks in advance!

I have tried different values for my MPS, MTU, and credits. I once tried to set my MPS to the size of the BT 5.0 device I'm using, 247 bytes. This partially worked but eventually an error was returned to me when I tried to transmit data and my connection was dropped. The error was L2CAP_WRONG_STATE.

With an MPS of 247 bytes, every time I send one I use 11 credits (my LE-frame has 23 bytes of payload). I think I am subtracting my credits and sending (waiting on) new ones accordingly.

I am surprised this even partially worked because, again, I don't understand why MPS can be set to something greater than the payload of an LE-frame.

1

There are 1 best solutions below

3
Emil On

Bluetooth 4.2 introduced LE Data Length extension, which is a feature at the Link Layer. Previously, the Link Layer was limited to only be able to send 27 bytes per over-the-air packet. With this new extension, this limit can be increased up to 251 bytes. Note that this is purely a performance feature and does not at all affect any code, features or limitations running on the host side. L2CAP PDUs up to 65535 bytes have always been, and are still, supported, in BLE (as long as the MTU or MPS is big enough).

With L2CAP CoC, the minimum unit at the host side is called a K-frame (called LE-frame in earlier versions). A K-frame is limited by the MPS (Maximum PDU payload Size). The application however sends and receives data in SDUs (Service Data Units). These are limited by the MTU (Maximum Transmission Unit). The L2CAP layer on the host side splits up an SDU into one or more K-frames, with the idea that these smaller frames can then be interleaved with other data, in case the SDU is very big.

As you say, MPS may be up to 65533 bytes (which is 2 bytes less than 65535 - this is to make space for the 2-byte SDU length header, since the PDU Length in the Basic L2CAP Header (which is present in all L2CAP PDUs) covers both the 2-byte SDU length header field and the information payload). In order to support such big K-frames, there is a feature in L2CAP called Fragmentation and Recombination. The L2CAP chapter, section 1.1 describes it as follows:

  • Fragmentation and Recombination

Some Controllers may have limited transmission capabilities and may require fragment sizes different from those created by L2CAP segmentation. Therefore layers below L2CAP may further fragment and recombine L2CAP PDUs to create fragments which fit each layer’s capabilities. During transmission of an L2CAP PDU, many different levels of fragmentation and recombination may occur in both peer devices.

The HCI driver or Controller may fragment L2CAP PDUs to honor packet size constraints of a Host Controller Interface transport scheme. This results in HCI ACL Data packet payloads carrying start and continuation fragments of the L2CAP PDU. Similarly the Controller may fragment L2CAP PDUs to map them into Controller packets. This may result in Controller packet payloads carrying start and continuation fragments of the L2CAP PDU.

Each layer of the protocol stack may pass on different sized fragments of L2CAP PDUs, and the size of fragments created by a layer may be different in each peer device. However the PDU is fragmented within the stack, the receiving L2CAP entity still recombines the fragments to obtain the original L2CAP PDU.

Section 1.4 describes Fragmentation as follows:

A procedure used to split L2CAP PDUs to smaller parts, named fragments, appropriate for delivery to the lower layer transport. Although described within the L2CAP layer, fragmentation may actually occur in an HCI Host driver, and/or in a Controller, to accommodate the L2CAP PDU transport to HCI ACL Data packet or Controller packet sizes. Fragmentation of PDUs may be applied in all L2CAP modes.

So yes, K-frames can be split up into multiple Link Layer PDUs. This is exactly how normal GATT works as well when the ATT_MTU is large.

For the best actual performance, you should take the following into account:

  • Every SDU sent requires a 2 byte length header.
  • Every K-frame adds a 4 byte Basic L2CAP Header.
  • After a K-frame has been split up to Link Layer packets, note that one packet (usually the last) might be smaller than the other. Avoid small packets since these come with relatively huge overheads.
  • Overhead of sending L2CAP_FLOW_CONTROL_CREDIT_IND packets.

So, if you just have a big stream of bytes that you need to transfer and don't care about response time when interleaving with other streams, then use the maximum possible MPS, maximum possible MTU, and send as few SDUs as possible. If you send an SDU containing 10 kB that fits in one K-frame, that will just cost one credit. Let the receiver send back L2CAP_FLOW_CONTROL_CREDIT_IND as infrequent as possible, but before the sender runs out, to avoid stalling the sender. There is no need to set a "small" MPS such as 23 or 247 just to try to match your Link Layer maximum PDU size, if you have a lot of data to send. Rather the opposite, if you send data that uses 4 Link Layer PDUs and these fit in one K-frame, only one 4 byte Basic L2CAP Header is needed instead of 4*4=16 bytes.

Just note that in case of a e.g. a GATT, SMP or L2CAP Connection parameter update request is sent and a very big K-frame is being sent, the response cannot be sent before the K-frame has completed. But the response must be received within 30 seconds per the standard, otherwise the link may be terminated.

Also, make sure that whenever you offer credits, make sure you can actually handle receiving as much data you have given credits for, without blowing up the memory.

In general all guidelines for reaching high throughput over GATT also applies for L2CAP CoC, since the same underlying technology is used. This means you should utilize LE Data Length extension if available, use tuned connection parameters, and use as big packets as possible to avoid the relatively large overhead of sending small packets.

I have tried different values for my MPS, MTU, and credits. I once tried to set my MPS to the size of the BT 5.0 device I'm using, 247 bytes. This partially worked but eventually an error was returned to me when I tried to transmit data and my connection was dropped. The error was L2CAP_WRONG_STATE.

You should debug this. Probably some bug in your code.

With an MPS of 247 bytes, every time I send one I use 11 credits (my LE-frame has 23 bytes of payload). I think I am subtracting my credits and sending (waiting on) new ones accordingly.

If you have MPS set to 247, then you should also be able to send K-frames of that size, and hence need only 1 credit for such a frame.