From a16e7a2971d5da85e1c03ee782c4d0ccedc09fb5 Mon Sep 17 00:00:00 2001 From: Christopher Collins Date: Mon, 2 Oct 2017 15:02:27 -0700 Subject: [PATCH] BLE Host - Allow GAP to be preempted. Privacy requires this functionality. An entry can only be added to the controller's resolving list when there are no active advertising, create connection, or discovery procedures. We ensure the entry is successfully added by preempting GAP first. When preemption occurs, all active GAP procedures are aborted, and no new ones can be started until the `ble_gap_preempt_done()` function is called. X-Original-Commit: 47d7bc44777eec68d87c45673e75f59bbba1f9f9 --- nimble/host/include/host/ble_hs.h | 1 + nimble/host/src/ble_gap.c | 151 ++++++++++++++++++++++++++++-- nimble/host/src/ble_gap_priv.h | 3 + 3 files changed, 149 insertions(+), 6 deletions(-) diff --git a/nimble/host/include/host/ble_hs.h b/nimble/host/include/host/ble_hs.h index 85885bd4a..da0468bcb 100644 --- a/nimble/host/include/host/ble_hs.h +++ b/nimble/host/include/host/ble_hs.h @@ -76,6 +76,7 @@ struct os_event; #define BLE_HS_EENCRYPT_KEY_SZ 26 #define BLE_HS_ESTORE_CAP 27 #define BLE_HS_ESTORE_FAIL 28 +#define BLE_HS_EPREEMPTED 29 #define BLE_HS_ERR_ATT_BASE 0x100 /* 256 */ #define BLE_HS_ATT_ERR(x) ((x) ? BLE_HS_ERR_ATT_BASE + (x) : 0) diff --git a/nimble/host/src/ble_gap.c b/nimble/host/src/ble_gap.c index ce307be77..e870cd922 100644 --- a/nimble/host/src/ble_gap.c +++ b/nimble/host/src/ble_gap.c @@ -44,6 +44,16 @@ * For (2), the procedure result is indicated by an application-configured * callback. The callback is executed when the procedure completes. * + * The GAP is always in one of two states: + * 1. Free + * 2. Preempted + * + * While GAP is in the free state, new procedures can be started at will. + * While GAP is in the preempted state, no new procedures are allowed. The + * host sets GAP to the preempted state when it needs to ensure no ongoing + * procedures, a condition required for some HCI commands to succeed. The host + * must take care to take GAP out of the preempted state as soon as possible. + * * Notes on thread-safety: * 1. The ble_hs mutex must always be unlocked when an application callback is * executed. The purpose of this requirement is to allow callbacks to @@ -100,8 +110,13 @@ struct ble_gap_master_state { ble_gap_event_fn *cb; void *cb_arg; - union { + /** + * Indicates the type of master procedure that was preempted, or + * BLE_GAP_OP_NULL if no procedure was preempted. + */ + uint8_t preempted_op; + union { struct { uint8_t using_wl:1; uint8_t our_addr_type:2; @@ -113,12 +128,9 @@ struct ble_gap_master_state { uint8_t extended:1; } disc; }; - - }; static bssnz_t struct ble_gap_master_state ble_gap_master; - #if MYNEWT_VAL(BLE_MESH) struct ble_gap_mesh_state { ble_gap_event_fn *cb; @@ -141,6 +153,9 @@ static bssnz_t struct { unsigned our_addr_type:2; ble_gap_event_fn *cb; void *cb_arg; + + /** Set to 1 if advertising was preempted. */ + uint8_t preempted:1; } ble_gap_slave; struct ble_gap_update_entry { @@ -599,6 +614,15 @@ ble_gap_call_conn_event_cb(struct ble_gap_event *event, uint16_t conn_handle) return 0; } +static bool +ble_gap_is_preempted(void) +{ + BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task()); + + return ble_gap_slave.preempted || + ble_gap_master.preempted_op != BLE_GAP_OP_NULL; +} + static void ble_gap_master_reset_state(void) { @@ -638,6 +662,7 @@ ble_gap_master_extract_state(struct ble_gap_master_state *out_state, if (reset_state) { ble_gap_master_reset_state(); + ble_gap_master.preempted_op = BLE_GAP_OP_NULL; } ble_hs_unlock(); @@ -727,7 +752,7 @@ ble_gap_is_extended_disc(void) static void ble_gap_disc_report(void *desc) { - struct ble_gap_master_state state;; + struct ble_gap_master_state state; struct ble_gap_event event; memset(&event, 0, sizeof event); @@ -1317,7 +1342,11 @@ ble_gap_rx_conn_complete(struct hci_le_conn_complete *evt) if (ble_gap_master_in_progress()) { if (evt->status == BLE_ERR_UNK_CONN_ID) { /* Connect procedure successfully cancelled. */ - ble_gap_master_connect_cancelled(); + if (ble_gap_master.preempted_op == BLE_GAP_OP_M_CONN) { + ble_gap_master_failed(BLE_HS_EPREEMPTED); + } else { + ble_gap_master_connect_cancelled(); + } } else { ble_gap_master_failed(BLE_HS_HCI_ERR(evt->status)); } @@ -2240,6 +2269,11 @@ ble_gap_adv_start(uint8_t own_addr_type, const ble_addr_t *direct_addr, } } + if (ble_gap_is_preempted()) { + rc = BLE_HS_EPREEMPTED; + goto done; + } + rc = ble_hs_id_use_addr(own_addr_type); if (rc != 0) { goto done; @@ -2740,6 +2774,10 @@ ble_gap_disc_ext_validate(uint8_t own_addr_type) return BLE_HS_EALREADY; } + if (ble_gap_is_preempted()) { + return BLE_HS_EPREEMPTED; + } + return 0; } #endif @@ -3237,6 +3275,11 @@ ble_gap_ext_connect(uint8_t own_addr_type, const ble_addr_t *peer_addr, goto done; } + if (ble_gap_is_preempted()) { + rc = BLE_HS_EPREEMPTED; + goto done; + } + if (!ble_hs_conn_can_alloc()) { rc = BLE_HS_ENOMEM; goto done; @@ -3387,6 +3430,11 @@ ble_gap_connect(uint8_t own_addr_type, const ble_addr_t *peer_addr, goto done; } + if (ble_gap_is_preempted()) { + rc = BLE_HS_EPREEMPTED; + goto done; + } + if (!ble_hs_conn_can_alloc()) { rc = BLE_HS_ENOMEM; goto done; @@ -4301,6 +4349,97 @@ ble_gap_mtu_event(uint16_t conn_handle, uint16_t cid, uint16_t mtu) ble_gap_call_conn_event_cb(&event, conn_handle); } +/***************************************************************************** + * $preempt * + *****************************************************************************/ + +/** + * Aborts all active GAP procedures and prevents new ones from being started. + * This function is used to ensure an idle GAP so that the controller's + * resolving list can be modified. When done accessing the resolving list, the + * caller must call `ble_gap_preempt_done()` to permit new GAP procedures. + * + * On preemption, all aborted GAP procedures are reported with a status or + * reason code of BLE_HS_EPREEMPTED. An attempt to initiate a new GAP + * procedure during preemption fails with a return code of BLE_HS_EPREEMPTED. + */ +void +ble_gap_preempt(void) +{ + int rc; + + ble_hs_lock(); + + BLE_HS_DBG_ASSERT(!ble_gap_is_preempted()); + + rc = ble_gap_adv_stop_no_lock(); + if (rc == 0) { + ble_gap_slave.preempted = 1; + } + + rc = ble_gap_conn_cancel_no_lock(); + if (rc == 0) { + ble_gap_master.preempted_op = BLE_GAP_OP_M_CONN; + } + + rc = ble_gap_disc_cancel_no_lock(); + if (rc == 0) { + ble_gap_master.preempted_op = BLE_GAP_OP_M_DISC; + } + + ble_hs_unlock(); +} + +/** + * Takes GAP out of the preempted state, allowing new GAP procedures to be + * initiaited. This function should only be called after a call to + * `ble_gap_preempt()`. + */ +void +ble_gap_preempt_done(void) +{ + struct ble_gap_event event; + ble_gap_event_fn *master_cb; + ble_gap_event_fn *slave_cb; + void *master_arg; + void *slave_arg; + int disc_preempted; + int adv_preempted; + + adv_preempted = 0; + disc_preempted = 0; + + ble_hs_lock(); + + if (ble_gap_slave.preempted) { + ble_gap_slave.preempted = 0; + adv_preempted = 1; + slave_cb = ble_gap_slave.cb; + slave_arg = ble_gap_slave.cb_arg; + } + + if (ble_gap_master.preempted_op == BLE_GAP_OP_M_DISC) { + ble_gap_master.preempted_op = BLE_GAP_OP_NULL; + disc_preempted = 1; + master_cb = ble_gap_master.cb; + master_arg = ble_gap_master.cb_arg; + } + + ble_hs_unlock(); + + if (adv_preempted) { + event.type = BLE_GAP_EVENT_ADV_COMPLETE; + event.adv_complete.reason = BLE_HS_EPREEMPTED; + ble_gap_call_event_cb(&event, slave_cb, slave_arg); + } + + if (disc_preempted) { + event.type = BLE_GAP_EVENT_DISC_COMPLETE; + event.disc_complete.reason = BLE_HS_EPREEMPTED; + ble_gap_call_event_cb(&event, master_cb, master_arg); + } +} + /***************************************************************************** * $init * *****************************************************************************/ diff --git a/nimble/host/src/ble_gap_priv.h b/nimble/host/src/ble_gap_priv.h index 88e62ca29..f90c1a01d 100644 --- a/nimble/host/src/ble_gap_priv.h +++ b/nimble/host/src/ble_gap_priv.h @@ -104,6 +104,9 @@ void ble_gap_identity_event(uint16_t conn_handle); int ble_gap_repeat_pairing_event(const struct ble_gap_repeat_pairing *rp); int ble_gap_master_in_progress(void); +void ble_gap_preempt(void); +void ble_gap_preempt_done(void); + void ble_gap_conn_broken(uint16_t conn_handle, int reason); int32_t ble_gap_timer(void);