MYNEWT-339 BLE host - Retry GATT on mbuf exhaust.

If a GATT transmission fails due to mbuf exhaustion, the procedure is
marked as stalled.  The host attempts to resume stalled procedures once
per second (configurable: BLE_GATT_RESUME_RATE).


Project: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/commit/984cebdf
Tree: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/tree/984cebdf
Diff: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/diff/984cebdf

Branch: refs/heads/develop
Commit: 984cebdff768945a0cd5dee55c0cc66b94a27899
Parents: d13fe65
Author: Christopher Collins <ccoll...@apache.org>
Authored: Thu Nov 17 16:06:34 2016 -0800
Committer: Christopher Collins <ccoll...@apache.org>
Committed: Fri Nov 18 17:31:56 2016 -0800

----------------------------------------------------------------------
 net/nimble/host/src/ble_att.c          |   4 +-
 net/nimble/host/src/ble_att_cmd_priv.h |   2 +
 net/nimble/host/src/ble_gatt_priv.h    |   6 +
 net/nimble/host/src/ble_gattc.c        | 763 ++++++++++++++++++++--------
 net/nimble/host/syscfg.yml             |  13 +-
 5 files changed, 583 insertions(+), 205 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/984cebdf/net/nimble/host/src/ble_att.c
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_att.c b/net/nimble/host/src/ble_att.c
index 5b23074..0e3f00d 100644
--- a/net/nimble/host/src/ble_att.c
+++ b/net/nimble/host/src/ble_att.c
@@ -147,8 +147,8 @@ void
 ble_att_conn_chan_find(uint16_t conn_handle, struct ble_hs_conn **out_conn,
                        struct ble_l2cap_chan **out_chan)
 {
-    ble_hs_misc_conn_chan_find(conn_handle, BLE_L2CAP_CID_ATT,
-                               out_conn, out_chan);
+    ble_hs_misc_conn_chan_find_reqd(conn_handle, BLE_L2CAP_CID_ATT,
+                                    out_conn, out_chan);
 }
 
 void

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/984cebdf/net/nimble/host/src/ble_att_cmd_priv.h
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_att_cmd_priv.h 
b/net/nimble/host/src/ble_att_cmd_priv.h
index 836a3ee..f954340 100644
--- a/net/nimble/host/src/ble_att_cmd_priv.h
+++ b/net/nimble/host/src/ble_att_cmd_priv.h
@@ -139,6 +139,8 @@ struct ble_att_read_type_rsp {
 } __attribute__((packed));
 
 #define BLE_ATT_READ_TYPE_ADATA_BASE_SZ     2
+#define BLE_ATT_READ_TYPE_ADATA_SZ_16       6
+#define BLE_ATT_READ_TYPE_ADATA_SZ_128      20
 
 /**
  * | Parameter                          | Size (octets)     |

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/984cebdf/net/nimble/host/src/ble_gatt_priv.h
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_gatt_priv.h 
b/net/nimble/host/src/ble_gatt_priv.h
index 110ced8..2325120 100644
--- a/net/nimble/host/src/ble_gatt_priv.h
+++ b/net/nimble/host/src/ble_gatt_priv.h
@@ -20,6 +20,7 @@
 #ifndef H_BLE_GATT_PRIV_
 #define H_BLE_GATT_PRIV_
 
+#include "syscfg/syscfg.h"
 #include "stats/stats.h"
 #include "host/ble_gatt.h"
 #ifdef __cplusplus
@@ -98,6 +99,11 @@ struct ble_gatts_conn {
 };
 
 /*** @client. */
+
+/** Convert the resume rate from milliseconds to OS ticks. */
+#define BLE_GATT_RESUME_RATE_TICKS                \
+    (MYNEWT_VAL(BLE_GATT_RESUME_RATE) * OS_TICKS_PER_SEC / 1000)
+
 int ble_gattc_locked_by_cur_task(void);
 void ble_gatts_indicate_fail_notconn(uint16_t conn_handle);
 

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/984cebdf/net/nimble/host/src/ble_gattc.c
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_gattc.c b/net/nimble/host/src/ble_gattc.c
index 1e93841..857530b 100644
--- a/net/nimble/host/src/ble_gattc.c
+++ b/net/nimble/host/src/ble_gattc.c
@@ -64,7 +64,11 @@
  * $definitions / declarations                                               *
  *****************************************************************************/
 
-#define BLE_GATT_UNRESPONSIVE_TIMEOUT           (30 * OS_TICKS_PER_SEC)
+/**
+ * The maximum time to wait for a single ATT response.  The spec defines this
+ * as the ATT transaction time (Vol. 3, Part F, 3.3.3)
+ */
+#define BLE_GATTC_UNRESPONSIVE_TIMEOUT          (30 * OS_TICKS_PER_SEC)
 
 #define BLE_GATT_OP_NONE                        UINT8_MAX
 #define BLE_GATT_OP_MTU                         0
@@ -84,6 +88,9 @@
 #define BLE_GATT_OP_INDICATE                    14
 #define BLE_GATT_OP_MAX                         15
 
+/** Procedure stalled due to resource exhaustion. */
+#define BLE_GATTC_PROC_F_STALLED                0x01
+
 /** Represents an in-progress GATT procedure. */
 struct ble_gattc_proc {
     STAILQ_ENTRY(ble_gattc_proc) next;
@@ -91,6 +98,7 @@ struct ble_gattc_proc {
     uint32_t exp_os_ticks;
     uint16_t conn_handle;
     uint8_t op;
+    uint8_t flags;
 
     union {
         struct {
@@ -152,6 +160,9 @@ struct ble_gattc_proc {
         } read;
 
         struct {
+            uint8_t chr_uuid[16];
+            uint16_t start_handle;
+            uint16_t end_handle;
             ble_gatt_attr_fn *cb;
             void *cb_arg;
         } read_uuid;
@@ -164,6 +175,8 @@ struct ble_gattc_proc {
         } read_long;
 
         struct {
+            uint16_t handles[MYNEWT_VAL(BLE_GATT_READ_MAX_ATTRS)];
+            uint8_t num_handles;
             ble_gatt_attr_fn *cb;
             void *cb_arg;
         } read_mult;
@@ -238,6 +251,37 @@ static ble_gattc_err_fn * const 
ble_gattc_err_dispatch[BLE_GATT_OP_MAX] = {
     [BLE_GATT_OP_INDICATE]          = ble_gattc_indicate_err,
 };
 
+typedef int ble_gattc_resume_fn(struct ble_gattc_proc *proc);
+
+static ble_gattc_resume_fn ble_gattc_disc_all_svcs_resume;
+static ble_gattc_resume_fn ble_gattc_disc_svc_uuid_resume;
+static ble_gattc_resume_fn ble_gattc_find_inc_svcs_resume;
+static ble_gattc_resume_fn ble_gattc_disc_all_chrs_resume;
+static ble_gattc_resume_fn ble_gattc_disc_chr_uuid_resume;
+static ble_gattc_resume_fn ble_gattc_disc_all_dscs_resume;
+static ble_gattc_resume_fn ble_gattc_read_long_resume;
+static ble_gattc_resume_fn ble_gattc_write_long_resume;
+static ble_gattc_resume_fn ble_gattc_write_reliable_resume;
+
+static ble_gattc_resume_fn * const
+ble_gattc_resume_dispatch[BLE_GATT_OP_MAX] = {
+    [BLE_GATT_OP_MTU]               = NULL,
+    [BLE_GATT_OP_DISC_ALL_SVCS]     = ble_gattc_disc_all_svcs_resume,
+    [BLE_GATT_OP_DISC_SVC_UUID]     = ble_gattc_disc_svc_uuid_resume,
+    [BLE_GATT_OP_FIND_INC_SVCS]     = ble_gattc_find_inc_svcs_resume,
+    [BLE_GATT_OP_DISC_ALL_CHRS]     = ble_gattc_disc_all_chrs_resume,
+    [BLE_GATT_OP_DISC_CHR_UUID]     = ble_gattc_disc_chr_uuid_resume,
+    [BLE_GATT_OP_DISC_ALL_DSCS]     = ble_gattc_disc_all_dscs_resume,
+    [BLE_GATT_OP_READ]              = NULL,
+    [BLE_GATT_OP_READ_UUID]         = NULL,
+    [BLE_GATT_OP_READ_LONG]         = ble_gattc_read_long_resume,
+    [BLE_GATT_OP_READ_MULT]         = NULL,
+    [BLE_GATT_OP_WRITE]             = NULL,
+    [BLE_GATT_OP_WRITE_LONG]        = ble_gattc_write_long_resume,
+    [BLE_GATT_OP_WRITE_RELIABLE]    = ble_gattc_write_reliable_resume,
+    [BLE_GATT_OP_INDICATE]          = NULL,
+};
+
 /**
  * Receive functions - these handle specific incoming responses and apply them
  * to the appropriate active GATT procedure.
@@ -326,6 +370,11 @@ static struct os_mempool ble_gattc_proc_pool;
 /* The list of active GATT client procedures. */
 static struct ble_gattc_proc_list ble_gattc_procs;
 
+/* The time when we should attempt to resume stalled procedures, in OS ticks.
+ * A value of 0 indicates no stalled procedures.
+ */
+static os_time_t ble_gattc_resume_at;
+
 /* Statistics. */
 STATS_SECT_DECL(ble_gattc_stats) ble_gattc_stats;
 STATS_NAME_START(ble_gattc_stats)
@@ -644,20 +693,67 @@ ble_gattc_proc_insert(struct ble_gattc_proc *proc)
 }
 
 static void
-ble_gattc_proc_set_timer(struct ble_gattc_proc *proc)
+ble_gattc_proc_set_exp_timer(struct ble_gattc_proc *proc)
+{
+    proc->exp_os_ticks = os_time_get() + BLE_GATTC_UNRESPONSIVE_TIMEOUT;
+}
+
+static void
+ble_gattc_proc_set_resume_timer(struct ble_gattc_proc *proc)
 {
-    proc->exp_os_ticks = os_time_get() + BLE_GATT_UNRESPONSIVE_TIMEOUT;
-    ble_hs_timer_resched();
+    proc->flags |= BLE_GATTC_PROC_F_STALLED;
+
+    /* Don't overwrite resume time if it is already set; piggyback on it
+     * instead.
+     */
+    if (ble_gattc_resume_at == 0) {
+        ble_gattc_resume_at = os_time_get() + BLE_GATT_RESUME_RATE_TICKS;
+
+        /* A value of 0 indicates the timer is unset.  Disambiguate this. */
+        if (ble_gattc_resume_at == 0) {
+            ble_gattc_resume_at++;
+        }
+    }
 }
 
 static void
 ble_gattc_process_status(struct ble_gattc_proc *proc, int status)
 {
-    if (status == 0) {
-        ble_gattc_proc_set_timer(proc);
+    switch (status) {
+    case 0:
+        if (!(proc->flags & BLE_GATTC_PROC_F_STALLED)) {
+            ble_gattc_proc_set_exp_timer(proc);
+        }
+
         ble_gattc_proc_insert(proc);
-    } else {
+        ble_hs_timer_resched();
+        break;
+
+    default:
         ble_gattc_proc_free(proc);
+        break;
+    }
+}
+
+/**
+ * Processes the return code that results from an attempt to resume a
+ * procedure.  If the resume attempt failed due to memory exhaustion at a lower
+ * layer, the procedure is marked as stalled but still in progress.  Otherwise,
+ * the resume error code is unmodified.
+ */
+static int
+ble_gattc_process_resume_status(struct ble_gattc_proc *proc, int status)
+{
+    switch (status) {
+    case 0:
+        return 0;
+
+    case BLE_HS_ENOMEM:
+        ble_gattc_proc_set_resume_timer(proc);
+        return 0;
+
+    default:
+        return status;
     }
 }
 
@@ -666,7 +762,7 @@ ble_gattc_process_status(struct ble_gattc_proc *proc, int 
status)
  *****************************************************************************/
 
 /**
- * Retrieves the dispatch entry with the specified op code.
+ * Retrieves the error dispatch entry with the specified op code.
  */
 static ble_gattc_err_fn *
 ble_gattc_err_dispatch_get(uint8_t op)
@@ -676,6 +772,23 @@ ble_gattc_err_dispatch_get(uint8_t op)
 }
 
 /**
+ * Retrieves the error dispatch entry with the specified op code.
+ */
+static ble_gattc_resume_fn *
+ble_gattc_resume_dispatch_get(uint8_t op)
+{
+    BLE_HS_DBG_ASSERT(op < BLE_GATT_OP_MAX);
+    return ble_gattc_resume_dispatch[op];
+}
+
+typedef int ble_gattc_match_fn(struct ble_gattc_proc *proc, void *arg);
+
+struct ble_gattc_criteria_conn_op {
+    uint16_t conn_handle;
+    uint8_t op;
+};
+
+/**
  * Tests if a proc entry fits the specified criteria.
  *
  * @param proc                  The procedure to test.
@@ -686,78 +799,91 @@ ble_gattc_err_dispatch_get(uint8_t op)
  * @return                      1 if the proc matches; 0 otherwise.
  */
 static int
-ble_gattc_proc_matches(struct ble_gattc_proc *proc, uint16_t conn_handle,
-                       uint8_t op)
+ble_gattc_proc_matches_conn_op(struct ble_gattc_proc *proc, void *arg)
 {
-    if (conn_handle != proc->conn_handle) {
+    const struct ble_gattc_criteria_conn_op *criteria;
+
+    criteria = arg;
+
+    if (criteria->conn_handle != proc->conn_handle) {
         return 0;
     }
 
-    if (op != proc->op && op != BLE_GATT_OP_NONE) {
+    if (criteria->op != proc->op && criteria->op != BLE_GATT_OP_NONE) {
         return 0;
     }
 
     return 1;
 }
 
-static struct ble_gattc_proc *
-ble_gattc_extract(uint16_t conn_handle, uint8_t op)
+struct ble_gattc_criteria_exp {
+    os_time_t now;
+    int32_t next_exp_in;
+};
+
+static int
+ble_gattc_proc_matches_expired(struct ble_gattc_proc *proc, void *arg)
 {
-    struct ble_gattc_proc *proc;
-    struct ble_gattc_proc *prev;
+    struct ble_gattc_criteria_exp *criteria;
+    int32_t time_diff;
 
-    /* Only the parent task is allowed to remove entries from the list. */
-    BLE_HS_DBG_ASSERT(ble_hs_is_parent_task());
+    criteria = arg;
 
-    ble_hs_lock();
+    time_diff = proc->exp_os_ticks - criteria->now;
 
-    prev = NULL;
-    STAILQ_FOREACH(proc, &ble_gattc_procs, next) {
-        if (ble_gattc_proc_matches(proc, conn_handle, op)) {
-            if (prev == NULL) {
-                STAILQ_REMOVE_HEAD(&ble_gattc_procs, next);
-            } else {
-                STAILQ_REMOVE_AFTER(&ble_gattc_procs, prev, next);
-            }
-            break;
-        }
-        prev = proc;
+    if (time_diff <= 0) {
+        /* Procedure is expired. */
+        return 1;
     }
 
-    ble_hs_unlock();
-
-    return proc;
+    /* Procedure isn't expired; determine if it is the next to expire. */
+    if (time_diff < criteria->next_exp_in) {
+        criteria->next_exp_in = time_diff;
+    }
+    return 0;
 }
 
+struct ble_gattc_criteria_conn_rx_entry {
+    uint16_t conn_handle;
+    const void *rx_entries;
+    int num_rx_entries;
+    const void *matching_rx_entry;
+};
+
 static int
-ble_gattc_conn_op_matches(struct ble_gattc_proc *proc, uint16_t conn_handle,
-                          uint8_t op)
+ble_gattc_proc_matches_conn_rx_entry(struct ble_gattc_proc *proc, void *arg)
 {
-    if (conn_handle != BLE_HS_CONN_HANDLE_NONE &&
-        conn_handle != proc->conn_handle) {
+    struct ble_gattc_criteria_conn_rx_entry *criteria;
 
-        return 0;
-    }
+    criteria = arg;
+
+    if (criteria->conn_handle != BLE_HS_CONN_HANDLE_NONE &&
+        criteria->conn_handle != proc->conn_handle) {
 
-    if (op != BLE_GATT_OP_NONE && op != proc->op) {
         return 0;
     }
 
+    /* Entry matches; indicate corresponding rx entry. */
+    criteria->matching_rx_entry = ble_gattc_rx_entry_find(
+        proc->op, criteria->rx_entries, criteria->num_rx_entries);
+
     return 1;
 }
 
 static void
-ble_gattc_extract_by_conn_op(uint16_t conn_handle, uint8_t op,
-                             struct ble_gattc_proc_list *dst_list)
+ble_gattc_extract(ble_gattc_match_fn *cb, void *arg, int max_procs,
+                  struct ble_gattc_proc_list *dst_list)
 {
     struct ble_gattc_proc *proc;
     struct ble_gattc_proc *prev;
     struct ble_gattc_proc *next;
+    int num_extracted;
 
     /* Only the parent task is allowed to remove entries from the list. */
     BLE_HS_DBG_ASSERT(ble_hs_is_parent_task());
 
     STAILQ_INIT(dst_list);
+    num_extracted = 0;
 
     ble_hs_lock();
 
@@ -766,13 +892,20 @@ ble_gattc_extract_by_conn_op(uint16_t conn_handle, 
uint8_t op,
     while (proc != NULL) {
         next = STAILQ_NEXT(proc, next);
 
-        if (ble_gattc_conn_op_matches(proc, conn_handle, op)) {
+        if (cb(proc, arg)) {
             if (prev == NULL) {
                 STAILQ_REMOVE_HEAD(&ble_gattc_procs, next);
             } else {
                 STAILQ_REMOVE_AFTER(&ble_gattc_procs, prev, next);
             }
             STAILQ_INSERT_TAIL(dst_list, proc, next);
+
+            if (max_procs > 0) {
+                num_extracted++;
+                if (num_extracted >= max_procs) {
+                    break;
+                }
+            }
         } else {
             prev = proc;
         }
@@ -783,6 +916,48 @@ ble_gattc_extract_by_conn_op(uint16_t conn_handle, uint8_t 
op,
     ble_hs_unlock();
 }
 
+static struct ble_gattc_proc *
+ble_gattc_extract_one(ble_gattc_match_fn *cb, void *arg)
+{
+    struct ble_gattc_proc_list dst_list;
+
+    ble_gattc_extract(cb, arg, 1, &dst_list);
+    return STAILQ_FIRST(&dst_list);
+}
+
+static void
+ble_gattc_extract_by_conn_op(uint16_t conn_handle, uint8_t op,
+                             struct ble_gattc_proc_list *dst_list)
+{
+    struct ble_gattc_criteria_conn_op criteria;
+
+    criteria.conn_handle = conn_handle;
+    criteria.op = op;
+
+    ble_gattc_extract(ble_gattc_proc_matches_conn_op, &criteria, 0, dst_list);
+}
+
+static struct ble_gattc_proc *
+ble_gattc_extract_first_by_conn_op(uint16_t conn_handle, uint8_t op)
+{
+    struct ble_gattc_proc_list dst_list;
+
+    ble_gattc_extract_by_conn_op(conn_handle, op, &dst_list);
+    return STAILQ_FIRST(&dst_list);
+}
+
+static int
+ble_gattc_proc_matches_stalled(struct ble_gattc_proc *proc, void *unused)
+{
+    return proc->flags & BLE_GATTC_PROC_F_STALLED;
+}
+
+static void
+ble_gattc_extract_stalled(struct ble_gattc_proc_list *dst_list)
+{
+    ble_gattc_extract(ble_gattc_proc_matches_stalled, NULL, 0, dst_list);
+}
+
 /**
  * @return                      The number of ticks until the next expiration
  *                                  occurs.
@@ -790,88 +965,33 @@ ble_gattc_extract_by_conn_op(uint16_t conn_handle, 
uint8_t op,
 static int32_t
 ble_gattc_extract_expired(struct ble_gattc_proc_list *dst_list)
 {
-    struct ble_gattc_proc *proc;
-    struct ble_gattc_proc *prev;
-    struct ble_gattc_proc *next;
-    uint32_t now;
-    int32_t next_exp_in;
-    int32_t time_diff;
+    struct ble_gattc_criteria_exp criteria;
 
-    /* Only the parent task is allowed to remove entries from the list. */
-    BLE_HS_DBG_ASSERT(ble_hs_is_parent_task());
+    criteria.now = os_time_get();
+    criteria.next_exp_in = BLE_HS_FOREVER;
 
-    now = os_time_get();
     STAILQ_INIT(dst_list);
+    ble_gattc_extract(ble_gattc_proc_matches_expired, &criteria, 0, dst_list);
 
-    /* Assume each event is either expired or has infinite duration. */
-    next_exp_in = BLE_HS_FOREVER;
-
-    ble_hs_lock();
-
-    prev = NULL;
-    proc = STAILQ_FIRST(&ble_gattc_procs);
-    while (proc != NULL) {
-        next = STAILQ_NEXT(proc, next);
-
-        time_diff = proc->exp_os_ticks - now;
-        if (time_diff <= 0) {
-            /* Procedure has expired; move it to the destination list. */
-            if (prev == NULL) {
-                STAILQ_REMOVE_HEAD(&ble_gattc_procs, next);
-            } else {
-                STAILQ_REMOVE_AFTER(&ble_gattc_procs, prev, next);
-            }
-            STAILQ_INSERT_TAIL(dst_list, proc, next);
-        } else {
-            if (time_diff < next_exp_in) {
-                next_exp_in = time_diff;
-            }
-        }
-
-        prev = proc;
-        proc = next;
-    }
-
-    ble_hs_unlock();
-
-    return next_exp_in;
+    return criteria.next_exp_in;
 }
 
 static struct ble_gattc_proc *
 ble_gattc_extract_with_rx_entry(uint16_t conn_handle,
-                                const void *rx_entries, int num_entries,
+                                const void *rx_entries, int num_rx_entries,
                                 const void **out_rx_entry)
 {
+    struct ble_gattc_criteria_conn_rx_entry criteria;
     struct ble_gattc_proc *proc;
-    struct ble_gattc_proc *prev;
-    const void *rx_entry;
-
-    /* Only the parent task is allowed to remove entries from the list. */
-    BLE_HS_DBG_ASSERT(ble_hs_is_parent_task());
 
-    ble_hs_lock();
+    criteria.conn_handle = conn_handle;
+    criteria.rx_entries = rx_entries;
+    criteria.num_rx_entries = num_rx_entries;
+    criteria.matching_rx_entry = NULL;
 
-    prev = NULL;
-    STAILQ_FOREACH(proc, &ble_gattc_procs, next) {
-        if (proc->conn_handle == conn_handle) {
-            rx_entry = ble_gattc_rx_entry_find(proc->op, rx_entries,
-                                               num_entries);
-            if (rx_entry != NULL) {
-                if (prev == NULL) {
-                    STAILQ_REMOVE_HEAD(&ble_gattc_procs, next);
-                } else {
-                    STAILQ_REMOVE_AFTER(&ble_gattc_procs, prev, next);
-                }
-
-                *out_rx_entry = rx_entry;
-                break;
-            }
-        }
-
-        prev = proc;
-    }
-
-    ble_hs_unlock();
+    proc = ble_gattc_extract_one(ble_gattc_proc_matches_conn_rx_entry,
+                                 &criteria);
+    *out_rx_entry = criteria.matching_rx_entry;
 
     return proc;
 }
@@ -925,6 +1045,50 @@ ble_gattc_fail_procs(uint16_t conn_handle, uint8_t op, 
int status)
     }
 }
 
+static void
+ble_gattc_resume_procs(void)
+{
+    struct ble_gattc_proc_list stall_list;
+    struct ble_gattc_proc *proc;
+    ble_gattc_resume_fn *resume_cb;
+    int rc;
+
+    /* Cancel resume timer since it is being serviced. */
+    ble_gattc_resume_at = 0;
+
+    ble_gattc_extract_stalled(&stall_list);
+
+    STAILQ_FOREACH(proc, &stall_list, next) {
+        resume_cb = ble_gattc_resume_dispatch_get(proc->op);
+        BLE_HS_DBG_ASSERT(resume_cb != NULL);
+
+        proc->flags &= ~BLE_GATTC_PROC_F_STALLED;
+        rc = resume_cb(proc);
+        ble_gattc_process_status(proc, rc);
+    }
+}
+
+static int32_t
+ble_gattc_ticks_until_resume(void)
+{
+    os_time_t now;
+    int32_t diff;
+
+    /* Resume timer not set. */
+    if (ble_gattc_resume_at == 0) {
+        return BLE_HS_FOREVER;
+    }
+
+    now = os_time_get();
+    diff = ble_gattc_resume_at - now;
+    if (diff <= 0) {
+        /* Timer already expired; resume immediately. */
+        return 0;
+    }
+
+    return diff;
+}
+
 /**
  * Times out expired GATT client procedures.
  *
@@ -936,6 +1100,7 @@ ble_gattc_timer(void)
 {
     struct ble_gattc_proc_list exp_list;
     struct ble_gattc_proc *proc;
+    int32_t ticks_until_resume;
     int32_t ticks_until_exp;
 
     /* Remove timed-out procedures from the main list and insert them into a
@@ -953,7 +1118,16 @@ ble_gattc_timer(void)
         ble_gattc_proc_free(proc);
     }
 
-    return ticks_until_exp;
+    /* If there are stalled procedures, the GATT client will need to wake up to
+     * resume them.
+     */
+    ticks_until_resume = ble_gattc_ticks_until_resume();
+    if (ticks_until_resume == 0) {
+        ble_gattc_resume_procs();
+        ticks_until_resume = ble_gattc_ticks_until_resume();
+    }
+
+    return min(ticks_until_exp, ticks_until_resume);
 }
 
 /**
@@ -1021,6 +1195,27 @@ ble_gattc_mtu_err(struct ble_gattc_proc *proc, int 
status, uint16_t att_handle)
     ble_gattc_mtu_cb(proc, status, att_handle, 0);
 }
 
+static int
+ble_gattc_mtu_tx(struct ble_gattc_proc *proc)
+{
+    struct ble_att_mtu_cmd req;
+    struct ble_l2cap_chan *chan;
+    struct ble_hs_conn *conn;
+    int rc;
+
+    ble_hs_lock();
+    ble_att_conn_chan_find(proc->conn_handle, &conn, &chan);
+    req.bamc_mtu = chan->blc_my_mtu;
+    ble_hs_unlock();
+
+    rc = ble_att_clt_tx_mtu(proc->conn_handle, &req);
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
+
 /**
  * Initiates GATT procedure: Exchange MTU.
  *
@@ -1036,10 +1231,7 @@ ble_gattc_mtu_err(struct ble_gattc_proc *proc, int 
status, uint16_t att_handle)
 int
 ble_gattc_exchange_mtu(uint16_t conn_handle, ble_gatt_mtu_fn *cb, void *cb_arg)
 {
-    struct ble_att_mtu_cmd req;
     struct ble_gattc_proc *proc;
-    struct ble_l2cap_chan *chan;
-    struct ble_hs_conn *conn;
     int rc;
 
     STATS_INC(ble_gattc_stats, mtu);
@@ -1057,12 +1249,7 @@ ble_gattc_exchange_mtu(uint16_t conn_handle, 
ble_gatt_mtu_fn *cb, void *cb_arg)
 
     ble_gattc_log_proc_init("exchange mtu\n");
 
-    ble_hs_lock();
-    ble_att_conn_chan_find(proc->conn_handle, &conn, &chan);
-    req.bamc_mtu = chan->blc_my_mtu;
-    ble_hs_unlock();
-
-    rc = ble_att_clt_tx_mtu(proc->conn_handle, &req);
+    rc = ble_gattc_mtu_tx(proc);
     if (rc != 0) {
         goto done;
     }
@@ -1117,7 +1304,7 @@ ble_gattc_disc_all_svcs_cb(struct ble_gattc_proc *proc,
  * Triggers a pending transmit for the specified discover-all-services proc.
  */
 static int
-ble_gattc_disc_all_svcs_go(struct ble_gattc_proc *proc, int cb_on_err)
+ble_gattc_disc_all_svcs_tx(struct ble_gattc_proc *proc)
 {
     struct ble_att_read_group_type_req req;
     uint8_t uuid128[16];
@@ -1131,11 +1318,23 @@ ble_gattc_disc_all_svcs_go(struct ble_gattc_proc *proc, 
int cb_on_err)
     req.bagq_start_handle = proc->disc_all_svcs.prev_handle + 1;
     req.bagq_end_handle = 0xffff;
     rc = ble_att_clt_tx_read_group_type(proc->conn_handle, &req, uuid128);
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
 
+static int
+ble_gattc_disc_all_svcs_resume(struct ble_gattc_proc *proc)
+{
+    int status;
+    int rc;
+
+    status = ble_gattc_disc_all_svcs_tx(proc);
+    rc = ble_gattc_process_resume_status(proc, status);
     if (rc != 0) {
-        if (cb_on_err) {
-            ble_gattc_disc_all_svcs_cb(proc, rc, 0, NULL);
-        }
+        ble_gattc_disc_all_svcs_cb(proc, rc, 0, NULL);
         return rc;
     }
 
@@ -1238,7 +1437,7 @@ ble_gattc_disc_all_svcs_rx_complete(struct ble_gattc_proc 
*proc, int status)
     }
 
     /* Send follow-up request. */
-    rc = ble_gattc_disc_all_svcs_go(proc, 1);
+    rc = ble_gattc_disc_all_svcs_resume(proc);
     if (rc != 0) {
         return BLE_HS_EDONE;
     }
@@ -1283,7 +1482,7 @@ ble_gattc_disc_all_svcs(uint16_t conn_handle, 
ble_gatt_disc_svc_fn *cb,
 
     ble_gattc_log_proc_init("discover all services\n");
 
-    rc = ble_gattc_disc_all_svcs_go(proc, 0);
+    rc = ble_gattc_disc_all_svcs_tx(proc);
     if (rc != 0) {
         goto done;
     }
@@ -1338,7 +1537,7 @@ ble_gattc_disc_svc_uuid_cb(struct ble_gattc_proc *proc, 
int status,
  * Triggers a pending transmit for the specified discover-service-by-uuid proc.
  */
 static int
-ble_gattc_disc_svc_uuid_go(struct ble_gattc_proc *proc, int cb_on_err)
+ble_gattc_disc_svc_uuid_tx(struct ble_gattc_proc *proc)
 {
     struct ble_att_find_type_value_req req;
     int rc;
@@ -1352,9 +1551,22 @@ ble_gattc_disc_svc_uuid_go(struct ble_gattc_proc *proc, 
int cb_on_err)
     rc = ble_att_clt_tx_find_type_value(proc->conn_handle, &req,
                                         proc->disc_svc_uuid.service_uuid, 16);
     if (rc != 0) {
-        if (cb_on_err) {
-            ble_gattc_disc_svc_uuid_cb(proc, rc, 0, NULL);
-        }
+        return rc;
+    }
+
+    return 0;
+}
+
+static int
+ble_gattc_disc_svc_uuid_resume(struct ble_gattc_proc *proc)
+{
+    int status;
+    int rc;
+
+    status = ble_gattc_disc_svc_uuid_tx(proc);
+    rc = ble_gattc_process_resume_status(proc, status);
+    if (rc != 0) {
+        ble_gattc_disc_svc_uuid_cb(proc, rc, 0, NULL);
         return rc;
     }
 
@@ -1439,7 +1651,7 @@ ble_gattc_disc_svc_uuid_rx_complete(struct ble_gattc_proc 
*proc, int status)
     }
 
     /* Send follow-up request. */
-    rc = ble_gattc_disc_svc_uuid_go(proc, 1);
+    rc = ble_gattc_disc_svc_uuid_resume(proc);
     if (rc != 0) {
         return BLE_HS_EDONE;
     }
@@ -1488,7 +1700,7 @@ ble_gattc_disc_svc_by_uuid(uint16_t conn_handle, const 
void *svc_uuid128,
 
     ble_gattc_log_disc_svc_uuid(proc);
 
-    rc = ble_gattc_disc_svc_uuid_go(proc, 0);
+    rc = ble_gattc_disc_svc_uuid_tx(proc);
     if (rc != 0) {
         goto done;
     }
@@ -1543,7 +1755,7 @@ ble_gattc_find_inc_svcs_cb(struct ble_gattc_proc *proc, 
int status,
  * Triggers a pending transmit for the specified find-included-services proc.
  */
 static int
-ble_gattc_find_inc_svcs_go(struct ble_gattc_proc *proc, int cb_on_err)
+ble_gattc_find_inc_svcs_tx(struct ble_gattc_proc *proc)
 {
     struct ble_att_read_type_req read_type_req;
     struct ble_att_read_req read_req;
@@ -1563,16 +1775,31 @@ ble_gattc_find_inc_svcs_go(struct ble_gattc_proc *proc, 
int cb_on_err)
 
         rc = ble_att_clt_tx_read_type(proc->conn_handle,
                                       &read_type_req, uuid128);
+        if (rc != 0) {
+            return rc;
+        }
     } else {
         /* Read the UUID of the previously found service. */
         read_req.barq_handle = proc->find_inc_svcs.cur_start;
         rc = ble_att_clt_tx_read(proc->conn_handle, &read_req);
+        if (rc != 0) {
+            return rc;
+        }
     }
 
+    return 0;
+}
+
+static int
+ble_gattc_find_inc_svcs_resume(struct ble_gattc_proc *proc)
+{
+    int status;
+    int rc;
+
+    status = ble_gattc_find_inc_svcs_tx(proc);
+    rc = ble_gattc_process_resume_status(proc, status);
     if (rc != 0) {
-        if (cb_on_err) {
-            ble_gattc_find_inc_svcs_cb(proc, rc, 0, NULL);
-        }
+        ble_gattc_find_inc_svcs_cb(proc, rc, 0, NULL);
         return rc;
     }
 
@@ -1646,7 +1873,7 @@ ble_gattc_find_inc_svcs_rx_read_rsp(struct ble_gattc_proc 
*proc, int status,
     /* Proceed to the next service. */
     proc->find_inc_svcs.cur_start = 0;
     proc->find_inc_svcs.cur_end = 0;
-    rc = ble_gattc_find_inc_svcs_go(proc, 1);
+    rc = ble_gattc_find_inc_svcs_resume(proc);
     if (rc != 0) {
         goto err;
     }
@@ -1757,7 +1984,7 @@ ble_gattc_find_inc_svcs_rx_complete(struct ble_gattc_proc 
*proc, int status)
     }
 
     /* Send follow-up request. */
-    rc = ble_gattc_find_inc_svcs_go(proc, 1);
+    rc = ble_gattc_find_inc_svcs_resume(proc);
     if (rc != 0) {
         return BLE_HS_EDONE;
     }
@@ -1809,7 +2036,7 @@ ble_gattc_find_inc_svcs(uint16_t conn_handle, uint16_t 
start_handle,
 
     ble_gattc_log_find_inc_svcs(proc);
 
-    rc = ble_gattc_find_inc_svcs_go(proc, 0);
+    rc = ble_gattc_find_inc_svcs_tx(proc);
     if (rc != 0) {
         goto done;
     }
@@ -1864,7 +2091,7 @@ ble_gattc_disc_all_chrs_cb(struct ble_gattc_proc *proc, 
int status,
  * proc.
  */
 static int
-ble_gattc_disc_all_chrs_go(struct ble_gattc_proc *proc, int cb_on_err)
+ble_gattc_disc_all_chrs_tx(struct ble_gattc_proc *proc)
 {
     struct ble_att_read_type_req req;
     uint8_t uuid128[16];
@@ -1879,11 +2106,23 @@ ble_gattc_disc_all_chrs_go(struct ble_gattc_proc *proc, 
int cb_on_err)
     req.batq_end_handle = proc->disc_all_chrs.end_handle;
 
     rc = ble_att_clt_tx_read_type(proc->conn_handle, &req, uuid128);
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
+
+static int
+ble_gattc_disc_all_chrs_resume(struct ble_gattc_proc *proc)
+{
+    int status;
+    int rc;
 
+    status = ble_gattc_disc_all_chrs_tx(proc);
+    rc = ble_gattc_process_resume_status(proc, status);
     if (rc != 0) {
-        if (cb_on_err) {
-            ble_gattc_disc_all_chrs_cb(proc, rc, 0, NULL);
-        }
+        ble_gattc_disc_all_chrs_cb(proc, rc, 0, NULL);
         return rc;
     }
 
@@ -1989,7 +2228,7 @@ ble_gattc_disc_all_chrs_rx_complete(struct ble_gattc_proc 
*proc, int status)
     }
 
     /* Send follow-up request. */
-    rc = ble_gattc_disc_all_chrs_go(proc, 1);
+    rc = ble_gattc_disc_all_chrs_resume(proc);
     if (rc != 0) {
         return BLE_HS_EDONE;
     }
@@ -2041,7 +2280,7 @@ ble_gattc_disc_all_chrs(uint16_t conn_handle, uint16_t 
start_handle,
 
     ble_gattc_log_disc_all_chrs(proc);
 
-    rc = ble_gattc_disc_all_chrs_go(proc, 0);
+    rc = ble_gattc_disc_all_chrs_tx(proc);
     if (rc != 0) {
         goto done;
     }
@@ -2096,7 +2335,7 @@ ble_gattc_disc_chr_uuid_cb(struct ble_gattc_proc *proc, 
int status,
  * discover-characteristic-by-uuid proc.
  */
 static int
-ble_gattc_disc_chr_uuid_go(struct ble_gattc_proc *proc, int cb_on_err)
+ble_gattc_disc_chr_uuid_tx(struct ble_gattc_proc *proc)
 {
     struct ble_att_read_type_req req;
     uint8_t uuid128[16];
@@ -2111,11 +2350,23 @@ ble_gattc_disc_chr_uuid_go(struct ble_gattc_proc *proc, 
int cb_on_err)
     req.batq_end_handle = proc->disc_chr_uuid.end_handle;
 
     rc = ble_att_clt_tx_read_type(proc->conn_handle, &req, uuid128);
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
 
+static int
+ble_gattc_disc_chr_uuid_resume(struct ble_gattc_proc *proc)
+{
+    int status;
+    int rc;
+
+    status = ble_gattc_disc_chr_uuid_tx(proc);
+    rc = ble_gattc_process_resume_status(proc, status);
     if (rc != 0) {
-        if (cb_on_err) {
-            ble_gattc_disc_chr_uuid_cb(proc, rc, 0, NULL);
-        }
+        ble_gattc_disc_chr_uuid_cb(proc, rc, 0, NULL);
         return rc;
     }
 
@@ -2232,7 +2483,7 @@ ble_gattc_disc_chr_uuid_rx_complete(struct ble_gattc_proc 
*proc, int status)
     }
 
     /* Send follow-up request. */
-    rc = ble_gattc_disc_chr_uuid_go(proc, 1);
+    rc = ble_gattc_disc_chr_uuid_resume(proc);
     if (rc != 0) {
         return BLE_HS_EDONE;
     }
@@ -2287,7 +2538,7 @@ ble_gattc_disc_chrs_by_uuid(uint16_t conn_handle, 
uint16_t start_handle,
 
     ble_gattc_log_disc_chr_uuid(proc);
 
-    rc = ble_gattc_disc_chr_uuid_go(proc, 0);
+    rc = ble_gattc_disc_chr_uuid_tx(proc);
     if (rc != 0) {
         goto done;
     }
@@ -2342,7 +2593,7 @@ ble_gattc_disc_all_dscs_cb(struct ble_gattc_proc *proc, 
int status,
  * Triggers a pending transmit for the specified discover-all-descriptors proc.
  */
 static int
-ble_gattc_disc_all_dscs_go(struct ble_gattc_proc *proc, int cb_on_err)
+ble_gattc_disc_all_dscs_tx(struct ble_gattc_proc *proc)
 {
     struct ble_att_find_info_req req;
     int rc;
@@ -2354,9 +2605,22 @@ ble_gattc_disc_all_dscs_go(struct ble_gattc_proc *proc, 
int cb_on_err)
 
     rc = ble_att_clt_tx_find_info(proc->conn_handle, &req);
     if (rc != 0) {
-        if (cb_on_err) {
-            ble_gattc_disc_all_dscs_cb(proc, rc, 0, NULL);
-        }
+        return rc;
+    }
+
+    return 0;
+}
+
+static int
+ble_gattc_disc_all_dscs_resume(struct ble_gattc_proc *proc)
+{
+    int status;
+    int rc;
+
+    status = ble_gattc_disc_all_dscs_tx(proc);
+    rc = ble_gattc_process_resume_status(proc, status);
+    if (rc != 0) {
+        ble_gattc_disc_all_dscs_cb(proc, rc, 0, NULL);
         return rc;
     }
 
@@ -2439,7 +2703,7 @@ ble_gattc_disc_all_dscs_rx_complete(struct ble_gattc_proc 
*proc, int status)
     }
 
     /* Send follow-up request. */
-    rc = ble_gattc_disc_all_dscs_go(proc, 1);
+    rc = ble_gattc_disc_all_dscs_resume(proc);
     if (rc != 0) {
         return BLE_HS_EDONE;
     }
@@ -2493,7 +2757,7 @@ ble_gattc_disc_all_dscs(uint16_t conn_handle, uint16_t 
chr_val_handle,
 
     ble_gattc_log_disc_all_dscs(proc);
 
-    rc = ble_gattc_disc_all_dscs_go(proc, 0);
+    rc = ble_gattc_disc_all_dscs_tx(proc);
     if (rc != 0) {
         goto done;
     }
@@ -2580,6 +2844,21 @@ ble_gattc_read_rx_read_rsp(struct ble_gattc_proc *proc, 
int status,
     return BLE_HS_EDONE;
 }
 
+static int
+ble_gattc_read_tx(struct ble_gattc_proc *proc)
+{
+    struct ble_att_read_req req;
+    int rc;
+
+    req.barq_handle = proc->read.handle;
+    rc = ble_att_clt_tx_read(proc->conn_handle, &req);
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
+
 /**
  * Initiates GATT procedure: Read Characteristic Value.
  *
@@ -2602,7 +2881,6 @@ ble_gattc_read(uint16_t conn_handle, uint16_t attr_handle,
 #endif
 
     struct ble_gattc_proc *proc;
-    struct ble_att_read_req req;
     int rc;
 
     STATS_INC(ble_gattc_stats, read);
@@ -2620,9 +2898,7 @@ ble_gattc_read(uint16_t conn_handle, uint16_t attr_handle,
     proc->read.cb_arg = cb_arg;
 
     ble_gattc_log_read(attr_handle);
-
-    req.barq_handle = attr_handle;
-    rc = ble_att_clt_tx_read(proc->conn_handle, &req);
+    rc = ble_gattc_read_tx(proc);
     if (rc != 0) {
         goto done;
     }
@@ -2710,7 +2986,6 @@ ble_gattc_read_uuid_rx_adata(struct ble_gattc_proc *proc,
     } else {
         rc = 0;
     }
-
     rc = ble_gattc_read_uuid_cb(proc, rc, 0, &attr);
 
     /* Free the attribute mbuf if the application has not consumed it. */
@@ -2744,6 +3019,24 @@ ble_gattc_read_uuid_rx_complete(struct ble_gattc_proc 
*proc, int status)
     return BLE_HS_EDONE;
 }
 
+static int
+ble_gattc_read_uuid_tx(struct ble_gattc_proc *proc)
+{
+    struct ble_att_read_type_req req;
+    int rc;
+
+    req.batq_start_handle = proc->read_uuid.start_handle;
+    req.batq_end_handle = proc->read_uuid.end_handle;
+
+    rc = ble_att_clt_tx_read_type(proc->conn_handle, &req,
+                                  proc->read_uuid.chr_uuid);
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
+
 /**
  * Initiates GATT procedure: Read Using Characteristic UUID.
  *
@@ -2769,7 +3062,6 @@ ble_gattc_read_by_uuid(uint16_t conn_handle, uint16_t 
start_handle,
     return BLE_HS_ENOTSUP;
 #endif
 
-    struct ble_att_read_type_req req;
     struct ble_gattc_proc *proc;
     int rc;
 
@@ -2783,14 +3075,14 @@ ble_gattc_read_by_uuid(uint16_t conn_handle, uint16_t 
start_handle,
 
     proc->op = BLE_GATT_OP_READ_UUID;
     proc->conn_handle = conn_handle;
+    memcpy(proc->read_uuid.chr_uuid, uuid128, 16);
+    proc->read_uuid.start_handle = start_handle;
+    proc->read_uuid.end_handle = end_handle;
     proc->read_uuid.cb = cb;
     proc->read_uuid.cb_arg = cb_arg;
 
     ble_gattc_log_read_uuid(start_handle, end_handle, uuid128);
-
-    req.batq_start_handle = start_handle;
-    req.batq_end_handle = end_handle;
-    rc = ble_att_clt_tx_read_type(conn_handle, &req, uuid128);
+    rc = ble_gattc_read_uuid_tx(proc);
     if (rc != 0) {
         goto done;
     }
@@ -2844,7 +3136,7 @@ ble_gattc_read_long_cb(struct ble_gattc_proc *proc, int 
status,
  * Triggers a pending transmit for the specified read-long-characteristic proc.
  */
 static int
-ble_gattc_read_long_go(struct ble_gattc_proc *proc, int cb_on_err)
+ble_gattc_read_long_tx(struct ble_gattc_proc *proc)
 {
     struct ble_att_read_blob_req blob_req;
     struct ble_att_read_req read_req;
@@ -2855,16 +3147,31 @@ ble_gattc_read_long_go(struct ble_gattc_proc *proc, int 
cb_on_err)
     if (proc->read_long.offset == 0) {
         read_req.barq_handle = proc->read_long.handle;
         rc = ble_att_clt_tx_read(proc->conn_handle, &read_req);
+        if (rc != 0) {
+            return rc;
+        }
     } else {
         blob_req.babq_handle = proc->read_long.handle;
         blob_req.babq_offset = proc->read_long.offset;
         rc = ble_att_clt_tx_read_blob(proc->conn_handle, &blob_req);
+        if (rc != 0) {
+            return rc;
+        }
     }
 
+    return 0;
+}
+
+static int
+ble_gattc_read_long_resume(struct ble_gattc_proc *proc)
+{
+    int status;
+    int rc;
+
+    status = ble_gattc_read_long_tx(proc);
+    rc = ble_gattc_process_resume_status(proc, status);
     if (rc != 0) {
-        if (cb_on_err) {
-            ble_gattc_read_long_cb(proc, rc, 0, NULL);
-        }
+        ble_gattc_read_long_cb(proc, rc, 0, NULL);
         return rc;
     }
 
@@ -2929,7 +3236,7 @@ ble_gattc_read_long_rx_read_rsp(struct ble_gattc_proc 
*proc, int status,
 
     /* Send follow-up request. */
     proc->read_long.offset += data_len;
-    rc = ble_gattc_read_long_go(proc, 1);
+    rc = ble_gattc_read_long_resume(proc);
     if (rc != 0) {
         return BLE_HS_EDONE;
     }
@@ -2978,7 +3285,7 @@ ble_gattc_read_long(uint16_t conn_handle, uint16_t handle,
 
     ble_gattc_log_read_long(proc);
 
-    rc = ble_gattc_read_long_go(proc, 0);
+    rc = ble_gattc_read_long_tx(proc);
     if (rc != 0) {
         goto done;
     }
@@ -3054,6 +3361,20 @@ ble_gattc_read_mult_err(struct ble_gattc_proc *proc, int 
status,
     ble_gattc_read_mult_cb(proc, status, att_handle, NULL);
 }
 
+static int
+ble_gattc_read_mult_tx(struct ble_gattc_proc *proc)
+{
+    int rc;
+
+    rc = ble_att_clt_tx_read_mult(proc->conn_handle, proc->read_mult.handles,
+                                  proc->read_mult.num_handles);
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
+
 /**
  * Initiates GATT procedure: Read Multiple Characteristic Values.
  *
@@ -3080,8 +3401,15 @@ ble_gattc_read_mult(uint16_t conn_handle, const uint16_t 
*handles,
     struct ble_gattc_proc *proc;
     int rc;
 
+    proc = NULL;
+
     STATS_INC(ble_gattc_stats, read_mult);
 
+    if (num_handles > MYNEWT_VAL(BLE_GATT_READ_MAX_ATTRS)) {
+        rc = BLE_HS_EINVAL;
+        goto done;
+    }
+
     proc = ble_gattc_proc_alloc();
     if (proc == NULL) {
         rc = BLE_HS_ENOMEM;
@@ -3090,12 +3418,13 @@ ble_gattc_read_mult(uint16_t conn_handle, const 
uint16_t *handles,
 
     proc->op = BLE_GATT_OP_READ_MULT;
     proc->conn_handle = conn_handle;
+    memcpy(proc->read_mult.handles, handles, num_handles * sizeof *handles);
+    proc->read_mult.num_handles = num_handles;
     proc->read_mult.cb = cb;
     proc->read_mult.cb_arg = cb_arg;
 
     ble_gattc_log_read_mult(handles, num_handles);
-
-    rc = ble_att_clt_tx_read_mult(conn_handle, handles, num_handles);
+    rc = ble_gattc_read_mult_tx(proc);
     if (rc != 0) {
         goto done;
     }
@@ -3373,7 +3702,7 @@ ble_gattc_write_long_cb(struct ble_gattc_proc *proc, int 
status,
  * write-long-characteristic-value proc.
  */
 static int
-ble_gattc_write_long_go(struct ble_gattc_proc *proc, int cb_on_err)
+ble_gattc_write_long_tx(struct ble_gattc_proc *proc)
 {
     struct ble_att_prep_write_cmd prep_req;
     struct ble_att_exec_write_req exec_req;
@@ -3386,8 +3715,7 @@ ble_gattc_write_long_go(struct ble_gattc_proc *proc, int 
cb_on_err)
 
     om = NULL;
 
-    max_sz = ble_att_mtu(proc->conn_handle) -
-             BLE_ATT_PREP_WRITE_CMD_BASE_SZ;
+    max_sz = ble_att_mtu(proc->conn_handle) - BLE_ATT_PREP_WRITE_CMD_BASE_SZ;
     if (max_sz <= 0) {
         /* Not connected. */
         rc = BLE_HS_ENOTCONN;
@@ -3430,12 +3758,23 @@ ble_gattc_write_long_go(struct ble_gattc_proc *proc, 
int cb_on_err)
 
 done:
     os_mbuf_free_chain(om);
+    return rc;
+}
+
+static int
+ble_gattc_write_long_resume(struct ble_gattc_proc *proc)
+{
+    int status;
+    int rc;
 
-    if (rc != 0 && cb_on_err) {
+    status = ble_gattc_write_long_tx(proc);
+    rc = ble_gattc_process_resume_status(proc, status);
+    if (rc != 0) {
         ble_gattc_write_long_cb(proc, rc, 0);
+        return rc;
     }
 
-    return rc;
+    return 0;
 }
 
 /**
@@ -3525,7 +3864,7 @@ ble_gattc_write_long_rx_prep(struct ble_gattc_proc *proc,
 
     /* Send follow-up request. */
     proc->write_long.attr.offset += OS_MBUF_PKTLEN(om);
-    rc = ble_gattc_write_long_go(proc, 1);
+    rc = ble_gattc_write_long_resume(proc);
     if (rc != 0) {
         goto err;
     }
@@ -3608,7 +3947,7 @@ ble_gattc_write_long(uint16_t conn_handle, uint16_t 
attr_handle,
 
     ble_gattc_log_write_long(proc);
 
-    rc = ble_gattc_write_long_go(proc, 0);
+    rc = ble_gattc_write_long_tx(proc);
     if (rc != 0) {
         goto done;
     }
@@ -3667,7 +4006,7 @@ ble_gattc_write_reliable_cb(struct ble_gattc_proc *proc, 
int status,
  * write-reliable-characteristic-value proc.
  */
 static int
-ble_gattc_write_reliable_go(struct ble_gattc_proc *proc, int cb_on_err)
+ble_gattc_write_reliable_tx(struct ble_gattc_proc *proc)
 {
     struct ble_att_prep_write_cmd prep_req;
     struct ble_att_exec_write_req exec_req;
@@ -3725,12 +4064,23 @@ ble_gattc_write_reliable_go(struct ble_gattc_proc 
*proc, int cb_on_err)
 
 done:
     os_mbuf_free_chain(om);
+    return rc;
+}
+
+static int
+ble_gattc_write_reliable_resume(struct ble_gattc_proc *proc)
+{
+    int status;
+    int rc;
 
-    if (rc != 0 && cb_on_err) {
+    status = ble_gattc_write_reliable_tx(proc);
+    rc = ble_gattc_process_resume_status(proc, status);
+    if (rc != 0) {
         ble_gattc_write_reliable_cb(proc, rc, 0);
+        return rc;
     }
 
-    return rc;
+    return 0;
 }
 
 /**
@@ -3812,7 +4162,7 @@ ble_gattc_write_reliable_rx_prep(struct ble_gattc_proc 
*proc,
         attr->offset = 0;
         proc->write_reliable.cur_attr++;
     }
-    rc = ble_gattc_write_reliable_go(proc, 1);
+    rc = ble_gattc_write_reliable_resume(proc);
     if (rc != 0) {
         goto err;
     }
@@ -3901,8 +4251,7 @@ ble_gattc_write_reliable(uint16_t conn_handle,
     }
 
     ble_gattc_log_write_reliable(proc);
-
-    rc = ble_gattc_write_reliable_go(proc, 1);
+    rc = ble_gattc_write_reliable_tx(proc);
     if (rc != 0) {
         goto done;
     }
@@ -4187,7 +4536,7 @@ ble_gattc_rx_err(uint16_t conn_handle, struct 
ble_att_error_rsp *rsp)
     struct ble_gattc_proc *proc;
     ble_gattc_err_fn *err_cb;
 
-    proc = ble_gattc_extract(conn_handle, BLE_GATT_OP_NONE);
+    proc = ble_gattc_extract_first_by_conn_op(conn_handle, BLE_GATT_OP_NONE);
     if (proc != NULL) {
         err_cb = ble_gattc_err_dispatch_get(proc->op);
         if (err_cb != NULL) {
@@ -4207,7 +4556,7 @@ ble_gattc_rx_mtu(uint16_t conn_handle, int status, 
uint16_t chan_mtu)
 {
     struct ble_gattc_proc *proc;
 
-    proc = ble_gattc_extract(conn_handle, BLE_GATT_OP_MTU);
+    proc = ble_gattc_extract_first_by_conn_op(conn_handle, BLE_GATT_OP_MTU);
     if (proc != NULL) {
         ble_gattc_mtu_cb(proc, status, 0, chan_mtu);
         ble_gattc_process_status(proc, BLE_HS_EDONE);
@@ -4229,7 +4578,8 @@ ble_gattc_rx_find_info_idata(uint16_t conn_handle,
     struct ble_gattc_proc *proc;
     int rc;
 
-    proc = ble_gattc_extract(conn_handle, BLE_GATT_OP_DISC_ALL_DSCS);
+    proc = ble_gattc_extract_first_by_conn_op(conn_handle,
+                                              BLE_GATT_OP_DISC_ALL_DSCS);
     if (proc != NULL) {
         rc = ble_gattc_disc_all_dscs_rx_idata(proc, idata);
         ble_gattc_process_status(proc, rc);
@@ -4250,7 +4600,8 @@ ble_gattc_rx_find_info_complete(uint16_t conn_handle, int 
status)
     struct ble_gattc_proc *proc;
     int rc;
 
-    proc = ble_gattc_extract(conn_handle, BLE_GATT_OP_DISC_ALL_DSCS);
+    proc = ble_gattc_extract_first_by_conn_op(conn_handle,
+                                              BLE_GATT_OP_DISC_ALL_DSCS);
     if (proc != NULL) {
         rc = ble_gattc_disc_all_dscs_rx_complete(proc, status);
         ble_gattc_process_status(proc, rc);
@@ -4272,7 +4623,8 @@ ble_gattc_rx_find_type_value_hinfo(uint16_t conn_handle,
     struct ble_gattc_proc *proc;
     int rc;
 
-    proc = ble_gattc_extract(conn_handle, BLE_GATT_OP_DISC_SVC_UUID);
+    proc = ble_gattc_extract_first_by_conn_op(conn_handle,
+                                              BLE_GATT_OP_DISC_SVC_UUID);
     if (proc != NULL) {
         rc = ble_gattc_disc_svc_uuid_rx_hinfo(proc, hinfo);
         ble_gattc_process_status(proc, rc);
@@ -4293,7 +4645,8 @@ ble_gattc_rx_find_type_value_complete(uint16_t 
conn_handle, int status)
     struct ble_gattc_proc *proc;
     int rc;
 
-    proc = ble_gattc_extract(conn_handle, BLE_GATT_OP_DISC_SVC_UUID);
+    proc = ble_gattc_extract_first_by_conn_op(conn_handle,
+                                              BLE_GATT_OP_DISC_SVC_UUID);
     if (proc != NULL) {
         rc = ble_gattc_disc_svc_uuid_rx_complete(proc, status);
         ble_gattc_process_status(proc, rc);
@@ -4364,7 +4717,8 @@ ble_gattc_rx_read_group_type_adata(uint16_t conn_handle,
     struct ble_gattc_proc *proc;
     int rc;
 
-    proc = ble_gattc_extract(conn_handle, BLE_GATT_OP_DISC_ALL_SVCS);
+    proc = ble_gattc_extract_first_by_conn_op(conn_handle,
+                                              BLE_GATT_OP_DISC_ALL_SVCS);
     if (proc != NULL) {
         rc = ble_gattc_disc_all_svcs_rx_adata(proc, adata);
         ble_gattc_process_status(proc, rc);
@@ -4385,7 +4739,8 @@ ble_gattc_rx_read_group_type_complete(uint16_t 
conn_handle, int status)
     struct ble_gattc_proc *proc;
     int rc;
 
-    proc = ble_gattc_extract(conn_handle, BLE_GATT_OP_DISC_ALL_SVCS);
+    proc = ble_gattc_extract_first_by_conn_op(conn_handle,
+                                              BLE_GATT_OP_DISC_ALL_SVCS);
     if (proc != NULL) {
         rc = ble_gattc_disc_all_svcs_rx_complete(proc, status);
         ble_gattc_process_status(proc, rc);
@@ -4431,7 +4786,8 @@ ble_gattc_rx_read_blob_rsp(uint16_t conn_handle, int 
status,
     struct ble_gattc_proc *proc;
     int rc;
 
-    proc = ble_gattc_extract(conn_handle, BLE_GATT_OP_READ_LONG);
+    proc = ble_gattc_extract_first_by_conn_op(conn_handle,
+                                              BLE_GATT_OP_READ_LONG);
     if (proc != NULL) {
         rc = ble_gattc_read_long_rx_read_rsp(proc, status, om);
         ble_gattc_process_status(proc, rc);
@@ -4452,7 +4808,8 @@ ble_gattc_rx_read_mult_rsp(uint16_t conn_handle, int 
status,
 
     struct ble_gattc_proc *proc;
 
-    proc = ble_gattc_extract(conn_handle, BLE_GATT_OP_READ_MULT);
+    proc = ble_gattc_extract_first_by_conn_op(conn_handle,
+                                              BLE_GATT_OP_READ_MULT);
     if (proc != NULL) {
         ble_gattc_read_mult_cb(proc, status, 0, om);
         ble_gattc_process_status(proc, BLE_HS_EDONE);
@@ -4472,7 +4829,8 @@ ble_gattc_rx_write_rsp(uint16_t conn_handle)
 
     struct ble_gattc_proc *proc;
 
-    proc = ble_gattc_extract(conn_handle, BLE_GATT_OP_WRITE);
+    proc = ble_gattc_extract_first_by_conn_op(conn_handle,
+                                              BLE_GATT_OP_WRITE);
     if (proc != NULL) {
         ble_gattc_write_cb(proc, 0, 0);
         ble_gattc_process_status(proc, BLE_HS_EDONE);
@@ -4541,7 +4899,8 @@ ble_gattc_rx_indicate_rsp(uint16_t conn_handle)
 
     struct ble_gattc_proc *proc;
 
-    proc = ble_gattc_extract(conn_handle, BLE_GATT_OP_INDICATE);
+    proc = ble_gattc_extract_first_by_conn_op(conn_handle,
+                                              BLE_GATT_OP_INDICATE);
     if (proc != NULL) {
         ble_gattc_indicate_rx_rsp(proc);
         ble_gattc_process_status(proc, BLE_HS_EDONE);

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/984cebdf/net/nimble/host/syscfg.yml
----------------------------------------------------------------------
diff --git a/net/nimble/host/syscfg.yml b/net/nimble/host/syscfg.yml
index ad8ab7a..2223c56 100644
--- a/net/nimble/host/syscfg.yml
+++ b/net/nimble/host/syscfg.yml
@@ -132,14 +132,25 @@ syscfg.defs:
         value: 1
 
     # GATT options.
+    BLE_GATT_READ_MAX_ATTRS:
+        description: >
+            The maximum number of attributes that can be read with a single
+            GATT Read Multiple Characteristic Values procedure.
+        value: 8
     BLE_GATT_WRITE_MAX_ATTRS:
         description: >
             The maximum number of attributes that can be written with a single
             GATT Reliable Write procedure.
         value: 4
     BLE_GATT_MAX_PROCS:
-        description: 'TBD'
+        description: >
+            The maximum number of concurrent client GATT procedures.
         value: 4
+    BLE_GATT_RESUME_RATE:
+        description: >
+            The rate to periodically resume GATT procedures that have stalled
+            due to memory exhaustion.  Units are milliseconds.
+        value: 1000
 
     # Supported server ATT commands.
     BLE_ATT_SVR_FIND_INFO:

Reply via email to