This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

CC2640R2F: Connecting to BLE 4.2 device with DLE support causes max receive PDU size to be used, regardless of MAX_PDU_SIZE

Part Number: CC2640R2F
Other Parts Discussed in Thread: CC2640

OVERVIEW

When an iPhone 7 connects to our device, our app seems to automatically send over a LL_LENGTH_REQ control packet as its first order of business.  The packet says that our transmit packet size and interval is L2CAP standard, but our receive packet size is the maximum for data length extension.   The iPhone 7 responds with a LL_LENGTH_RSP saying that it can support the highest Tx and Rx data lengths for DLE (data length extension).  This results in our receive buffers being resized to be unneccessarily large, eating up a lot of heap.  The LL_LENGTH_REQ packet is still sent connecting to an iPhone 6s (which supports BLE 4.2 but not data length extension), but it's rejected by the 6s with an "unknown opcode" control packet, which doesn't cause our app adjust the buffer sizes.  It appears that it isn't the iPhone 7 itself that's the issue but the fact that it supports DLE.  Ultimately what we want to do is prevent this length control packet from being sent, to "intercept" it before it's sent and modify its value, or to (if possible) otherwise figure out some way of setting our own maximum Rx rate and packet size.

DETAILS:

We've been dealing with an issue for the last few months involving connecting to the iPhone 7.  By using HEAPMGR_METRICS and HEAPMGR_PROFILER, we determined that the cause of this issue was heap failure, which is why simply adding more code was reducing the available memory for heap.  When heapmgr.h is out of heap, the allocation function returns false and fails to allocate the designated object.  This can result in the connection dropping or becoming unresponsive.  We tuned some of our stack sizes to avoid this issue, leaving us with just enough memory to connect to an iPhone 7.  When connecting to an iPhone 7, we found that HEAPMGR_MEMMAX, which indicates the highest point of heap allocation, will be about 1000 greater than it would be after an iPhone 6 connection.

Investigating further, it turns out that the objects on the heap that were taking up the most space were buffers created through a call to osal_bm_alloc in osal_bufmgr.c.  The buffers are of size 265 (a little bigger once they make it to the heap), and four of them are always active during an iPhone 7 connection if the heap has sufficient space for them.  If there isn't enough space for them, the app continually tries to create them, causing constant heap failures if there isn't enough heap.  When the connection terminates, any existing buffers are freed.

When I connect with iPhone 6s instead of an iPhone 7, these buffers are created, but they're of size 41 rather than 265.  41 - 27 (minimum LCAP packet size) = a buffer header of size 14, and 265 - 14 = 251, which is the maximum data length extension packet size.  So, these buffers appear to be packet receive buffers.  However, setting MAX_PDU_SIZE to 27 doesn't seem to have any effect on receive buffer size when iPhone 7 connects.  Similarly, setting MAX_NUM_PDU to a different value doesn't seem to have an effect on the number of buffers spawned.  I'm not sure if I've disabled data length extension.  In my build_config.opt, I have all -DV42_FEATURES defines commented out for now just to be sure, but it doesn't seem to help.  In the comments of build_config.opt, it says "EXT_DATA_LEN_CFG - Enable the Extended Data Length Feature in the Controller (always enabled)."  Does this mean there's no way to turn it off?  Or does it mean that there's no way to disable it during runtime if it's defined?

I also see in factory_config.opt, which is run by the build, the line "-DBLE_V42_FEATURES=V42_FEATURES+PRIVACY_1_2_CFG+EXT_DATA_LEN_CFG".  (this file has a warning not to change it.)  Would this define be making DLE always enabled? 

If DLE is used, the documentation says that the connection should start with a packet MTU of 23 (resulting in a packet size of 27 when L2CAP header is included) and a connection interval of 328us, and changes to those connection parameters must be negotiated between the master and slave.

When our application receives GAP_LINK_ESTABLISHED_EVENT, one of the actions we perform is to call GATT_ExchangeMTU.  We send a clientRxMTU of 23, the maximum L2CAP MTU size.  We receive an ATT_EXCHANGE_MTU_RSP with a serverRxMTU of 23, which if I'm understanding the CC2640 developer's guide correctly (http://www.ti.com/lit/ug/swru393d/swru393d.pdf, 5.5.2.1), this indicates that the MTU size should be 23 for that connection.  We don't receive an ATT_MTU_UPDATED_EVENT, I assume because the MTU hasn't changed from the default values, and thus the connection MTU hasn't updated.  This behavior is the same regardless of being on iPhone 7 or 6s.

However, before the ATT_EXCHANGE_MTU_RSP is received, we also get a HCI_BLE_DATA_LENGTH_CHANGE_EVENT.  This event happens before any of the 265-sized buffers are created, and it occurs whether or not the exchange MTU procedure is initiated.  This event isn't generated when we connect to an iPhone 6s.  Reading the values from the event, we get:

maxTxOctets: 27

maxTxReceiveTime: 328

maxRxOctets: 251

maxRxReceiveTime: 2120

Looking at a sniffer log, I found a LL_LENGTH_REQ and LL_LENGTH_RSP packet right after the connection started. (I had overlooked this before because apparently SmartRF Packet Sniffer 2.18.1.0 doesn't automatically label LL_LENGTH_REQ and LL_LENGTH_RSP packets; these were just shown as "Control" packets and I had to double click on the packet and select "Packet Details" to see the data, in case anyone else runs into this).  The LL_LENGTH_REQ sent from us had the same values as in the HCI_BLE_DATA_LENGTH_CHANGE_EVENT, and the iPhone 7 responded with the maximum possible Tx and Rx packet sizes.  This results in the values listed above, since the minimum mutual values for Tx and Rx are used.  This packet is still sent when connecting to an iPhone 6s.  6s supports BLE 4.2, but not data length extension, and it responds with an "unknown opcode" to the control packet.  The receive buffers remain at the L2CAP packet size during the connection.

Using the Modules view in CCS and setting a breakpoint on the function llSetupLenCtrlPacket(), I found that this is called almost immediately after the initial Rx buffers are set up (tracked with llCreateRxBuffer), very soon after GAP_LINK_ESTABLISHED_EVENT.

So, the issue here seems to rest in the outgoing values of the LL_LENGTH_REQ packet, and we can't seem to find any way to control these values using defines or constants.

Sorry about the long post here; this is something we've really been trying to crack since the additional heap usage by iPhone 7 is restricting us from adding new features.  Any help or ideas are greatly appreciated!

  • Team,

    Can you please look at the following issue that our customer is having for CC2640R2 on the iphone7.

    Very Respectfully,
    Omid
  • Thanks, Omid!

    We've managed to get this working, by tracing back the origin of the values used to populate the LL_LENGTH_REQ packet.  In memory, the L2CAP values are followed by two copies of the DLE values:

    1B 00 48 01 FB 00 48 08 FB 00 48 08 (23, 328, 251, 2120, 251, 2120)

    So, we scan the memory range that these show up in and replace them at the initialization of our application task, making it:

    1B 00 48 01 1B 00 48 01 1B 00 48 01 (23, 328, 23, 328, 23, 328)

    Here's the code.  Of course, modifying internal variables used by the stack at runtime is done at your own risk.  I think that replacing the DLE values with the L2CAP values should be fine in this case, since the L2CAP values are valid for a connection which is using data length extension, and the connection is initially configured to use these values.  You should make sure that the memory range used corresponds to your configuration, and that these values actually appear in that memory range in the correct format.

    // The range of bytes in memory to scan

    #define MIN_DLE_BYTES_ADDR 0x20000000

    #define MAX_DLE_BYTES_ADDR 0x2000FFFF

    #define DLE_BYTES 0x084800FB

    #define L2CAP_BYTES 0x0148001B

    inline bool fixDleBytes( void ) {

       // Scan through the memory range the bytes are found in

       for (int* i = (int*)MIN_DLE_BYTES_ADDR; i <= (int*)MAX_DLE_BYTES_ADDR; i++) {

           // Make sure we have the exact sequence of bytes

           if (*i == L2CAP_BYTES && *(i+1) == DLE_BYTES && *(i+2) == DLE_BYTES) {

               // Replace max DLE values with L2CAP values

               *(i+1) = L2CAP_BYTES;

               *(i+2) = L2CAP_BYTES;

               return TRUE;

           }

       }

       return FALSE;

    }

    Of course, this is pretty non-ideal and it would be nice to have a more accessible way to limit packet data length.

     

  • Hello,

    You can override the Controller (LL) defaults used to respond to the min/max octets/time in the LL_LENGTH_REQ / LL_LENGTH_RSP with the HCI_EXT_SetMaxDataLenCmd vendor extension command. See the usage in the SW Dev Guide section for DLE:
    software-dl.ti.com/.../index.html

    Also take note that the ATT_MTU size (derived from MAX_PDU_SIZE) is not directly tied to the LL PDU size, although to transfer data efficiently, the ATT_MTU should be at set to the same size as the LL PDU.

    If you are sending Notifications and/or Indications, you do not need to alloc the full ATT_MTU size, however, you cannot exceed the max MTU size when you send the data.

    Best wishes
  • JXS,

    Thank you so much for taking the time to assist my customer!

    Respectfully,

    Omid

  • Hi JXS, somehow we overlooked this function when looking at all these API headers. (also I hadn't found that documentation you linked in all my google searches, it will be very useful for future reference.) I can't verify this just yet due to some issues that are preventing us from connecting a debugger, but I will accept this as working!

    Thank you both for your help!