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!