BLE host: Check HCI handle on ack.
This check exposed a blatant bug in our handling of a remote connection
parameter request event. The code was immediately transmitting HCI packets in
the wrong context, rather than reserving HCI tx slots.
diff --git a/net/nimble/host/src/ble_gap.c b/net/nimble/host/src/ble_gap.c
index 091e597..9a11094 100644
--- a/net/nimble/host/src/ble_gap.c
+++ b/net/nimble/host/src/ble_gap.c
@@ -171,6 +171,7 @@
SLIST_ENTRY(ble_gap_update_entry) next;
struct ble_gap_upd_params params;
uint16_t conn_handle;
+ uint8_t reject_reason;
uint8_t state;
uint8_t hci_handle;
};
@@ -1317,6 +1318,7 @@
{
assert(ble_gap_wl.op == BLE_GAP_OP_W_SET);
assert(ble_gap_wl.state == BLE_GAP_STATE_W_ADD);
+ assert(ble_gap_wl.hci_handle == ack->bha_hci_handle);
ble_gap_wl.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
@@ -1371,6 +1373,7 @@
{
assert(ble_gap_wl.op == BLE_GAP_OP_W_SET);
assert(ble_gap_wl.state == BLE_GAP_STATE_W_CLEAR);
+ assert(ble_gap_wl.hci_handle == ack->bha_hci_handle);
ble_gap_wl.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
@@ -1459,6 +1462,8 @@
static void
ble_gap_adv_ack_disable(struct ble_hci_ack *ack, void *arg)
{
+ assert(ble_gap_slave.hci_handle == ack->bha_hci_handle);
+
ble_gap_slave.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
if (ack->bha_status == 0) {
@@ -1602,6 +1607,8 @@
static void
ble_gap_adv_ack(struct ble_hci_ack *ack, void *arg)
{
+ assert(ble_gap_slave.hci_handle == ack->bha_hci_handle);
+
ble_gap_slave.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
if (ack->bha_status != 0) {
@@ -1641,8 +1648,6 @@
uint8_t rsp_data[BLE_HCI_MAX_SCAN_RSP_DATA_LEN] = { 0 }; /* XXX */
int rc;
- ble_gap_slave.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
-
ble_hci_sched_set_ack_cb(ble_gap_adv_ack, NULL);
rc = host_hci_cmd_le_set_scan_rsp_data(rsp_data, sizeof rsp_data);
if (rc != 0) {
@@ -1725,6 +1730,8 @@
{
int8_t power_level;
+ assert(ble_gap_slave.hci_handle == ack->bha_hci_handle);
+
ble_gap_slave.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
if (ack->bha_status != 0) {
@@ -1762,13 +1769,11 @@
{
int rc;
- ble_gap_slave.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
-
ble_hci_sched_set_ack_cb(ble_gap_adv_power_ack, NULL);
rc = host_hci_cmd_read_adv_pwr();
if (rc != 0) {
ble_gap_call_slave_cb(BLE_GAP_EVENT_ADV_FAILURE,
- BLE_HS_HCI_ERR(rc), 1);
+ BLE_HS_HCI_ERR(rc), 1);
return 1;
}
@@ -1993,6 +1998,7 @@
{
assert(ble_gap_master.op == BLE_GAP_OP_M_DISC);
assert(ble_gap_master.state == BLE_GAP_STATE_M_DISC_DISABLE);
+ assert(ble_gap_master.hci_handle == ack->bha_hci_handle);
ble_gap_master.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
@@ -2035,6 +2041,7 @@
{
assert(ble_gap_master.op == BLE_GAP_OP_M_DISC);
assert(ble_gap_master.state == BLE_GAP_STATE_M_DISC_ENABLE);
+ assert(ble_gap_master.hci_handle == ack->bha_hci_handle);
ble_gap_master.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
@@ -2076,6 +2083,7 @@
{
assert(ble_gap_master.op == BLE_GAP_OP_M_DISC);
assert(ble_gap_master.state == BLE_GAP_STATE_M_DISC_PARAMS);
+ assert(ble_gap_master.hci_handle == ack->bha_hci_handle);
ble_gap_master.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
@@ -2193,6 +2201,7 @@
{
assert(ble_gap_master.op == BLE_GAP_OP_M_CONN);
assert(ble_gap_master.state == BLE_GAP_STATE_M_UNACKED);
+ assert(ble_gap_master.hci_handle == ack->bha_hci_handle);
ble_gap_master.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
@@ -2470,6 +2479,7 @@
entry = arg;
assert(entry->state == BLE_GAP_STATE_U_NEG_REPLY);
+ assert(entry->hci_handle == ack->bha_hci_handle);
SLIST_REMOVE(&ble_gap_update_entries, entry,
ble_gap_update_entry, next);
@@ -2492,6 +2502,7 @@
entry = arg;
assert(entry->state == BLE_GAP_STATE_U_REPLY);
+ assert(entry->hci_handle == ack->bha_hci_handle);
entry->hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
@@ -2509,6 +2520,68 @@
}
}
+static int
+ble_gap_tx_param_pos_reply(void *arg)
+{
+ struct hci_conn_param_reply pos_reply;
+ struct ble_gap_update_entry *entry;
+ struct ble_gap_snapshot snap;
+ int snap_rc;
+ int rc;
+
+ entry = arg;
+
+ pos_reply.handle = entry->conn_handle;
+ pos_reply.conn_itvl_min = entry->params.itvl_min;
+ pos_reply.conn_itvl_max = entry->params.itvl_max;
+ pos_reply.conn_latency = entry->params.latency;
+ pos_reply.supervision_timeout = entry->params.supervision_timeout;
+ pos_reply.min_ce_len = entry->params.min_ce_len;
+ pos_reply.max_ce_len = entry->params.max_ce_len;
+
+ ble_hci_sched_set_ack_cb(ble_gap_param_reply_ack, entry);
+
+ rc = host_hci_cmd_le_conn_param_reply(&pos_reply);
+ if (rc != 0) {
+ rc = BLE_HS_HCI_ERR(rc);
+ snap_rc = ble_gap_find_snapshot(entry->conn_handle, &snap);
+ if (snap_rc == 0) {
+ ble_gap_call_conn_cb(BLE_GAP_EVENT_CONN_UPDATE_REQ, rc, &snap,
+ NULL, NULL);
+ }
+ }
+
+ return rc;
+}
+
+static int
+ble_gap_tx_param_neg_reply(void *arg)
+{
+ struct hci_conn_param_neg_reply neg_reply;
+ struct ble_gap_update_entry *entry;
+ struct ble_gap_snapshot snap;
+ int snap_rc;
+ int rc;
+
+ entry = arg;
+
+ neg_reply.handle = entry->conn_handle;
+ neg_reply.reason = entry->reject_reason;
+
+ ble_hci_sched_set_ack_cb(ble_gap_param_neg_reply_ack, entry);
+ rc = host_hci_cmd_le_conn_param_neg_reply(&neg_reply);
+ if (rc != 0) {
+ rc = BLE_HS_HCI_ERR(rc);
+ snap_rc = ble_gap_find_snapshot(entry->conn_handle, &snap);
+ if (snap_rc == 0) {
+ ble_gap_call_conn_cb(BLE_GAP_EVENT_CONN_UPDATE_REQ, rc, &snap,
+ NULL, NULL);
+ }
+ }
+
+ return rc;
+}
+
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
@@ -2520,10 +2593,8 @@
return;
#endif
- struct hci_conn_param_neg_reply neg_reply;
struct ble_gap_upd_params peer_params;
struct ble_gap_update_entry *entry;
- struct hci_conn_param_reply pos_reply;
struct ble_gap_snapshot snap;
int rc;
@@ -2552,41 +2623,37 @@
peer_params.min_ce_len = 0;
peer_params.max_ce_len = 0;
- entry = ble_gap_update_entry_alloc(evt->connection_handle,
- &peer_params,
- BLE_GAP_STATE_U_REPLY);
+ entry = ble_gap_update_entry_alloc(evt->connection_handle, &peer_params,
+ BLE_GAP_STATE_U_REPLY);
if (entry == NULL) {
/* Out of memory; reject. */
+ /* XXX: Our negative response should indicate which connection handle
+ * it applies to, but we don't have anywhere to store this information.
+ * For now, just send a connection handle of 0 and hope the peer can
+ * sort it out.
+ */
rc = BLE_ERR_MEM_CAPACITY;
} else {
rc = ble_gap_call_conn_cb(BLE_GAP_EVENT_CONN_UPDATE_REQ, 0, &snap,
&entry->params, &peer_params);
+ if (rc != 0) {
+ entry->reject_reason = rc;
+ }
}
if (rc == 0) {
- pos_reply.handle = entry->conn_handle;
- pos_reply.conn_itvl_min = entry->params.itvl_min;
- pos_reply.conn_itvl_max = entry->params.itvl_max;
- pos_reply.conn_latency = entry->params.latency;
- pos_reply.supervision_timeout = entry->params.supervision_timeout;
- pos_reply.min_ce_len = entry->params.min_ce_len;
- pos_reply.max_ce_len = entry->params.max_ce_len;
-
- ble_hci_sched_set_ack_cb(ble_gap_param_reply_ack, entry);
-
- rc = host_hci_cmd_le_conn_param_reply(&pos_reply);
+ ble_hci_sched_enqueue(ble_gap_tx_param_pos_reply, entry,
+ &entry->hci_handle);
}
if (rc != 0) {
- neg_reply.handle = evt->connection_handle;
- neg_reply.reason = rc;
-
if (entry != NULL) {
entry->state = BLE_GAP_STATE_U_NEG_REPLY;
+ ble_hci_sched_enqueue(ble_gap_tx_param_neg_reply, entry,
+ &entry->hci_handle);
+ } else {
+ ble_hci_sched_enqueue(ble_gap_tx_param_neg_reply, NULL, NULL);
}
-
- ble_hci_sched_set_ack_cb(ble_gap_param_neg_reply_ack, entry);
- host_hci_cmd_le_conn_param_neg_reply(&neg_reply);
}
if (entry != NULL) {
@@ -2609,6 +2676,7 @@
entry = arg;
assert(entry->state == BLE_GAP_STATE_U_UPDATE);
+ assert(entry->hci_handle == ack->bha_hci_handle);
ble_gap_master.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;