diff --git a/nimble/host/include/host/ble_hs.h b/nimble/host/include/host/ble_hs.h index 71fc2bfb4..6c2acfd4d 100644 --- a/nimble/host/include/host/ble_hs.h +++ b/nimble/host/include/host/ble_hs.h @@ -94,6 +94,7 @@ extern "C" { #define BLE_HS_ESTORE_FAIL 28 #define BLE_HS_EPREEMPTED 29 #define BLE_HS_EDISABLED 30 +#define BLE_HS_ESTALLED 31 /** Error base for ATT errors */ #define BLE_HS_ERR_ATT_BASE 0x100 diff --git a/nimble/host/include/host/ble_l2cap.h b/nimble/host/include/host/ble_l2cap.h index c20734eed..644bd9d0d 100644 --- a/nimble/host/include/host/ble_l2cap.h +++ b/nimble/host/include/host/ble_l2cap.h @@ -75,6 +75,7 @@ struct ble_hs_conn; #define BLE_L2CAP_EVENT_COC_DISCONNECTED 1 #define BLE_L2CAP_EVENT_COC_ACCEPT 2 #define BLE_L2CAP_EVENT_COC_DATA_RECEIVED 3 +#define BLE_L2CAP_EVENT_COC_TX_UNSTALLED 4 typedef void ble_l2cap_sig_update_fn(uint16_t conn_handle, int status, void *arg); @@ -173,6 +174,28 @@ struct ble_l2cap_event { /** The mbuf with received SDU. */ struct os_mbuf *sdu_rx; } receive; + + /** + * Represents tx_unstalled data. Valid for the following event + * types: + * o BLE_L2CAP_EVENT_COC_TX_UNSTALLED + */ + struct { + /** Connection handle of the relevant connection */ + uint16_t conn_handle; + + /** The L2CAP channel of the relevant L2CAP connection. */ + struct ble_l2cap_chan *chan; + + /** + * The status of the send attempt which was stalled due to + * lack of credits; This can be non zero only if there + * is an issue with memory allocation for following SDU fragments. + * In such a case last SDU has been partially sent to peer device + * and it is up to application to decide how to handle it. + */ + int status; + } tx_unstalled; }; }; diff --git a/nimble/host/src/ble_l2cap_coc.c b/nimble/host/src/ble_l2cap_coc.c index 22d26d86b..bc597f936 100644 --- a/nimble/host/src/ble_l2cap_coc.c +++ b/nimble/host/src/ble_l2cap_coc.c @@ -316,6 +316,23 @@ ble_l2cap_coc_cleanup_chan(struct ble_l2cap_chan *chan) os_mbuf_free_chain(chan->coc_tx.sdu); } +static void +ble_l2cap_event_coc_unstalled(struct ble_l2cap_chan *chan, int status) +{ + struct ble_l2cap_event event = { }; + + if (!chan->cb) { + return; + } + + event.type = BLE_L2CAP_EVENT_COC_TX_UNSTALLED; + event.tx_unstalled.conn_handle = chan->conn_handle; + event.tx_unstalled.chan = chan; + event.tx_unstalled.status = status; + + chan->cb(&event, chan->cb_arg); +} + static int ble_l2cap_coc_continue_tx(struct ble_l2cap_chan *chan) { @@ -402,16 +419,30 @@ ble_l2cap_coc_continue_tx(struct ble_l2cap_chan *chan) os_mbuf_free_chain(tx->sdu); tx->sdu = 0; tx->data_offset = 0; + if (tx->flags & BLE_L2CAP_COC_FLAG_STALLED) { + ble_l2cap_event_coc_unstalled(chan, 0); + tx->flags &= ~BLE_L2CAP_COC_FLAG_STALLED; + } break; } } + if (tx->sdu) { + /* Not complete SDU sent, wait for credits */ + tx->flags |= BLE_L2CAP_COC_FLAG_STALLED; + return BLE_HS_ESTALLED; + } + return 0; failed: os_mbuf_free_chain(tx->sdu); tx->sdu = NULL; os_mbuf_free_chain(txom); + if (tx->flags & BLE_L2CAP_COC_FLAG_STALLED) { + ble_l2cap_event_coc_unstalled(chan, rc); + tx->flags &= ~BLE_L2CAP_COC_FLAG_STALLED; + } return rc; } diff --git a/nimble/host/src/ble_l2cap_coc_priv.h b/nimble/host/src/ble_l2cap_coc_priv.h index dc9821402..0a1a97b77 100644 --- a/nimble/host/src/ble_l2cap_coc_priv.h +++ b/nimble/host/src/ble_l2cap_coc_priv.h @@ -35,11 +35,14 @@ extern "C" { struct ble_l2cap_chan; +#define BLE_L2CAP_COC_FLAG_STALLED 0x01 + struct ble_l2cap_coc_endpoint { + struct os_mbuf *sdu; uint16_t mtu; uint16_t credits; uint16_t data_offset; - struct os_mbuf *sdu; + uint8_t flags; }; struct ble_l2cap_coc_srv { diff --git a/nimble/host/test/src/ble_l2cap_test.c b/nimble/host/test/src/ble_l2cap_test.c index 6fbbe7714..49769155b 100644 --- a/nimble/host/test/src/ble_l2cap_test.c +++ b/nimble/host/test/src/ble_l2cap_test.c @@ -736,6 +736,9 @@ ble_l2cap_test_event(struct ble_l2cap_event *event, void *arg) OS_MBUF_PKTLEN(event->receive.sdu_rx)); TEST_ASSERT(memcmp(sdu_rx->om_data, ev->data, ev->data_len) == 0); return 0; + case BLE_L2CAP_EVENT_COC_TX_UNSTALLED: + /* TODO Add tests for this */ + return 0; default: return 0; }