trouble icon indicating copy to clipboard operation
trouble copied to clipboard

ESP32 MCUs - MTU must be set to 255

Open lure23 opened this issue 10 months ago • 7 comments

We've fixed this for the examples but downstream code using trouble can still drive into the ditch if it uses MTU other than 255.

The Bluetooth 4.2 spec states that MTU is:

[MTU] unsigned integer between 23 and 517

  • [ ] Track https://github.com/esp-rs/esp-hal/issues/2984 and once/if completed, consider removing the fixed 255 consts.rs, allowing applications to suggest whichever MTU they'd prefer
  • [ ] Test with a wide range of ESP32 MCUs and MTU values (the legal range, 23..517) that at least the examples work

lure23 avatar Feb 16 '25 15:02 lure23

We still don't support configuring ble_acl_buf_size but I had a brief look at the issue here

The controller returns an error for HCI_Host_Buffer_Size if the requested host_acl_data_packet_len is less than what is configured but you can set larger values.

That's probably because with the configured ble_acl_buf_size the controller would like to exchange packets up to that size.

(i.e. Maximum length (in octets) of the data portion of each HCI ACL Data packet that the Host is able to accept.)

It should not be less than what the controller reports here: https://github.com/embassy-rs/trouble/blame/953b76eda6258d5682e02c3306c729a793f89b01/host/src/host.rs#L921-L927

[MTU] unsigned integer between 23 and 517

I think this is what client and server negotiate via ATT_EXCHANGE_MTU_REQ and ATT_EXCHANGE_MTU_RES which is not the same

bjoernQ avatar Apr 14 '25 15:04 bjoernQ

Hi @bjoernQ ,

Just a pass by bluetooth chip raw recruit, and you are correct.

Host_ACL_Data_Packet_Length in HCI_Host_Buffer_Size command should generally greater than LE_ACL_Data_Packet_Length returned in HCI_LE_Read_Buffer_Size event.

And it has nothing to do with the MTU, MTU is an concept with the host part, it's used by the GATT profile when any packet is assembling and is prior to L2CAP (ACL packet) handling.

Huckies avatar Apr 17 '25 03:04 Huckies

Thanks for the clarification!

bjoernQ avatar Apr 17 '25 06:04 bjoernQ

I think on the trouble side, there is a mismatch where the Host_ACL_Data_Packet_Length is set to the MTU of gatt/l2cap, because it doesn't buffer the ACL packets. It just reads 1 ACL data at a time, pulls out the l2cap/gatt part and put that into an inbound queue for a channel.

So when issuing the HCI_Host_Buffer_Size, it uses the MTU of gatt/l2cap and length of that queue, which seems wrong to me now in light of the above comments, and it would be wasteful to use large buffers if the gatt/l2cap MTU is lower.

Maybe it should be using size '255' and len '1', because it doesn't do any buffering? 🤔

lulf avatar Apr 17 '25 20:04 lulf

Setting len to 1 makes sense, but have size set to 255 looks unreasonable to me.

Host_ACL_Data_Packet_Length being set to equal or greater than MTU value isn't the problem, it just need to have a lower limit (which is no smaller than LE_ACL_Data_Packet_Length).

Let's look into this case:

The Controller returns LE_ACL_Data_Packet_Length = 251. A LE link exchanged MTU to 517 before the discovery procedure begins, and the GATT Server is a keyboard with quite a long report map characteristic, let's say 1024 octets.

  • The GATT Client will issue single ATT_READ_REQ and multiple ATT_READ_BLOB_REQ transactions to retrieve the whole characteristic value, this is done by the ATT profile and the GATT Server replies with ATT_READ_RSP and ATT_READ_BLOB_RSP, whose payload length can be up to 514 (517-3) octets;
  • In GATT Client, for each 517 octets long ACL packets, when transmitted from Controller to Host, it is splitted into three HCI packets in 251+251+19 octets because the ACL buffer size on Controller side, note there is some HCI header overhead there;
  • In GATT Client, the Host side HCI will have the packet reassembled into one 517 octets long ACL packet before sending it up to the stack.

It is obvious now to allow the MTU being set to 517, you will have a local buffer with capacity ≥ 517 octets, and it makes no sense not declaring it to the controller.

Huckies avatar Apr 18 '25 03:04 Huckies

Setting len to 1 makes sense, but have size set to 255 looks unreasonable to me.

Host_ACL_Data_Packet_Length being set to equal or greater than MTU value isn't the problem, it just need to have a lower limit (which is no smaller than LE_ACL_Data_Packet_Length).

Let's look into this case:

The Controller returns LE_ACL_Data_Packet_Length = 251. A LE link exchanged MTU to 517 before the discovery procedure begins, and the GATT Server is a keyboard with quite a long report map characteristic, let's say 1024 octets.

* The GATT Client will issue single `ATT_READ_REQ` and multiple `ATT_READ_BLOB_REQ` transactions to retrieve the whole characteristic value, this is done by the ATT profile and the GATT Server replies with `ATT_READ_RSP` and `ATT_READ_BLOB_RSP`, whose payload length can be up to 514 (517-3) octets;

* In GATT Client, for each 517 octets long ACL packets, when transmitted from Controller to Host, it is splitted into three HCI packets in 251+251+19 octets because the ACL buffer size on Controller side, note there is some HCI header overhead there;

* In GATT Client, the Host side HCI will have the packet reassembled into one 517 octets long ACL packet before sending it up to the stack.

It is obvious now to allow the MTU being set to 517, you will have a local buffer with capacity ≥ 517 octets, and it makes no sense not declaring it to the controller.

Thanks for the example, it makes sense to report a larger capacity if available.

The source of my confusion here I think stems from the fact that the host 'rx loop' that reads the ACL data uses a buffer on the stack of fixed size 255 to read ACL data, events etc into. And then based on that, if it's gatt/l2cap it will allocate the buffer and copy into that or reassemble into the existing buffer. Problem is that today the gatt/l2cap pool can have buffers smaller than < 255 (i.e. if the app knows it will use a att mtu of 42).

So the options then could be on of these perhaps:

  • Make that stack buffer bigger and/or allocatable and set host buffer size based on this (must be >= than controller buffer size as you stated).
  • Always allocate a buffer from the pool no matter what. "Wastes" allocating a buffer for event data, but maybe that's OK... Also means that we could avoid a copy, but would need to offset correctly into the ACL data...
  • Allocate a single buffer from the pool at startup and use that for reading acl data, and do the copy as today. This would at least make sure the buffer sizes are consistent.

I will explore these options as part of some refactoring I'm doing on the buffer allocation to allow an external allocator, so it will hopefully end up in a better state.

lulf avatar Apr 18 '25 08:04 lulf

After reading the latest BT6.0 spec for 2 hours (what else do you do on Easter vacation...?), I concluded that a stack-allocated HCI RX buffer of 255 bytes is sufficient (excluding HCI overhead).

The HCI layer and related buffers act solely as a link between controller and host (and vice versa). The specification defines three types of HCI packets:

  • Command Packet (host to controller): minimum controller buffer size is 255 bytes (+3 bytes HCI overhead)
  • Event Packet (controller to host): minimum host buffer size is 255 bytes (+2 bytes HCI overhead)
  • ACL Data Packet (both directions): minimum buffer size is 27 bytes (+4 bytes HCI overhead)

Buffer size requirements of upper-layer protocols (L2CAP, ATT, etc.) should not affect HCI buffer sizing.

What value for Host_ACL_Data_Packet_Length should we send in the HCI_Host_Buffer_Size command? For event packets we must provide a buffer of atleast 255 bytes (excluding HCI overhead). Since we use the same buffer for all HCI packet types, we can also specify 255 as the value for Host_ACL_Data_Packet_Length. Using any larger buffer seems unnecessary, as the maximum expected ACL Data Packet size bounded is the 251-byte DLE limit.

timokroeger avatar Apr 18 '25 13:04 timokroeger