1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20 #include "securec.h"
21 #include "syscfg/syscfg.h"
22 #include "nimble/ble_hci_trans.h"
23 #include "ble_hs_priv.h"
24
25 #if MYNEWT_VAL(BLE_HS_FLOW_CTRL)
26
27 #define BLE_HS_FLOW_ITVL_TICKS \
28 ble_npl_time_ms_to_ticks32(MYNEWT_VAL(BLE_HS_FLOW_CTRL_ITVL))
29
30 /**
31 * The number of freed buffers since the most-recent
32 * number-of-completed-packets event was sent. This is used to determine if an
33 * immediate event transmission is required.
34 */
35 static uint16_t ble_hs_flow_num_completed_pkts;
36
37 /** Periodically sends number-of-completed-packets events. */
38 static struct ble_npl_callout ble_hs_flow_timer;
39
40 static ble_npl_event_fn ble_hs_flow_event_cb;
41
42 static struct ble_npl_event ble_hs_flow_ev;
43
ble_hs_flow_tx_num_comp_pkts(void)44 static int ble_hs_flow_tx_num_comp_pkts(void)
45 {
46 uint8_t buf[
47 sizeof(struct ble_hci_cb_host_num_comp_pkts_cp) +
48 sizeof(struct ble_hci_cb_host_num_comp_pkts_entry)
49 ];
50 struct ble_hci_cb_host_num_comp_pkts_cp *cmd = (void *) buf;
51 struct ble_hs_conn *conn;
52 int rc;
53 BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task());
54
55 /* For each connection with completed packets, send a separate
56 * host-number-of-completed-packets command.
57 */
58 for (conn = ble_hs_conn_first();
59 conn != NULL;
60 conn = SLIST_NEXT(conn, bhc_next)) {
61 if (conn->bhc_completed_pkts > 0) {
62 /* Only specify one connection per command. */
63 /* could combine this in single HCI command */
64 cmd->handles = 1;
65 /* Append entry for this connection. */
66 cmd->h[0].handle = htole16(conn->bhc_handle);
67 cmd->h[0].count = htole16(conn->bhc_completed_pkts);
68 conn->bhc_completed_pkts = 0;
69 /* The host-number-of-completed-packets command does not elicit a
70 * response from the controller, so don't use the normal blocking
71 * HCI API when sending it.
72 */
73 rc = ble_hs_hci_cmd_send_buf(BLE_HCI_OP(BLE_HCI_OGF_CTLR_BASEBAND,
74 BLE_HCI_OCF_CB_HOST_NUM_COMP_PKTS),
75 buf, sizeof(buf));
76 if (rc != 0) {
77 return rc;
78 }
79 }
80 }
81
82 return 0;
83 }
84
ble_hs_flow_event_cb(struct ble_npl_event * ev)85 static void ble_hs_flow_event_cb(struct ble_npl_event *ev)
86 {
87 int rc;
88 ble_hs_lock();
89
90 if (ble_hs_flow_num_completed_pkts > 0) {
91 rc = ble_hs_flow_tx_num_comp_pkts();
92 if (rc != 0) {
93 ble_hs_sched_reset(rc);
94 }
95
96 ble_hs_flow_num_completed_pkts = 0;
97 }
98
99 ble_hs_unlock();
100 }
101
ble_hs_flow_inc_completed_pkts(struct ble_hs_conn * conn)102 static void ble_hs_flow_inc_completed_pkts(struct ble_hs_conn *conn)
103 {
104 uint16_t num_free;
105 int rc;
106 BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task());
107 conn->bhc_completed_pkts++;
108 ble_hs_flow_num_completed_pkts++;
109 if (ble_hs_flow_num_completed_pkts > MYNEWT_VAL(BLE_ACL_BUF_COUNT)) {
110 ble_hs_sched_reset(BLE_HS_ECONTROLLER);
111 return;
112 }
113
114 /* If the number of free buffers is at or below the configured threshold,
115 * send an immediate number-of-copmleted-packets event.
116 */
117 num_free = MYNEWT_VAL(BLE_ACL_BUF_COUNT) - ble_hs_flow_num_completed_pkts;
118 if (num_free <= MYNEWT_VAL(BLE_HS_FLOW_CTRL_THRESH)) {
119 ble_npl_eventq_put(ble_hs_evq_get(), &ble_hs_flow_ev);
120 ble_npl_callout_stop(&ble_hs_flow_timer);
121 } else if (ble_hs_flow_num_completed_pkts == 1) {
122 rc = ble_npl_callout_reset(&ble_hs_flow_timer, BLE_HS_FLOW_ITVL_TICKS);
123 BLE_HS_DBG_ASSERT_EVAL(rc == 0);
124 }
125 }
126
ble_hs_flow_acl_free(struct os_mempool_ext * mpe,void * data,void * arg)127 static os_error_t ble_hs_flow_acl_free(struct os_mempool_ext *mpe, void *data, void *arg)
128 {
129 struct ble_hs_conn *conn;
130 const struct os_mbuf *om;
131 uint16_t conn_handle;
132 int rc;
133 om = data;
134 /* An ACL data packet must be a single mbuf, and it must contain the
135 * corresponding connection handle in its user header.
136 */
137 assert(OS_MBUF_IS_PKTHDR(om));
138 assert(OS_MBUF_USRHDR_LEN(om) >= sizeof conn_handle);
139 /* Copy the connection handle out of the mbuf. */
140 memcpy_s(&conn_handle, sizeof(conn_handle), OS_MBUF_USRHDR(om), sizeof conn_handle);
141 /* Free the mbuf back to its pool. */
142 rc = os_memblock_put_from_cb(&mpe->mpe_mp, data);
143 if (rc != 0) {
144 return rc;
145 }
146
147 /* Allow nested locks - there are too many places where acl buffers can get
148 * freed.
149 */
150 ble_hs_lock_nested();
151 conn = ble_hs_conn_find(conn_handle);
152 if (conn != NULL) {
153 ble_hs_flow_inc_completed_pkts(conn);
154 }
155
156 ble_hs_unlock_nested();
157 return 0;
158 }
159 #endif /* MYNEWT_VAL(BLE_HS_FLOW_CTRL) */
160
ble_hs_flow_connection_broken(uint16_t conn_handle)161 void ble_hs_flow_connection_broken(uint16_t conn_handle)
162 {
163 #if MYNEWT_VAL(BLE_HS_FLOW_CTRL) && \
164 MYNEWT_VAL(BLE_HS_FLOW_CTRL_TX_ON_DISCONNECT)
165 ble_hs_lock();
166 ble_hs_flow_tx_num_comp_pkts();
167 ble_hs_unlock();
168 #endif
169 }
170
171 /**
172 * Fills the user header of an incoming data packet. On function return, the
173 * header contains the connection handle associated with the sender.
174 *
175 * If flow control is disabled, this function is a no-op.
176 */
ble_hs_flow_fill_acl_usrhdr(struct os_mbuf * om)177 void ble_hs_flow_fill_acl_usrhdr(struct os_mbuf *om)
178 {
179 #if MYNEWT_VAL(BLE_HS_FLOW_CTRL)
180 const struct hci_data_hdr *hdr;
181 uint16_t *conn_handle;
182 BLE_HS_DBG_ASSERT(OS_MBUF_USRHDR_LEN(om) >= sizeof * conn_handle);
183 conn_handle = OS_MBUF_USRHDR(om);
184 hdr = (void *)om->om_data;
185 *conn_handle = BLE_HCI_DATA_HANDLE(hdr->hdh_handle_pb_bc);
186 #endif
187 }
188
189 /**
190 * Sends the HCI commands to the controller required for enabling host flow
191 * control.
192 *
193 * If flow control is disabled, this function is a no-op.
194 */
ble_hs_flow_startup(void)195 int ble_hs_flow_startup(void)
196 {
197 #if MYNEWT_VAL(BLE_HS_FLOW_CTRL)
198 struct ble_hci_cb_ctlr_to_host_fc_cp enable_cmd;
199 struct ble_hci_cb_host_buf_size_cp buf_size_cmd = {
200 .acl_data_len = htole16(MYNEWT_VAL(BLE_ACL_BUF_SIZE)),
201 .acl_num = htole16(MYNEWT_VAL(BLE_ACL_BUF_COUNT)),
202 };
203 int rc;
204 ble_npl_event_init(&ble_hs_flow_ev, ble_hs_flow_event_cb, NULL);
205 /* Assume failure. */
206 ble_hci_trans_set_acl_free_cb(NULL, NULL);
207 #if MYNEWT_VAL(SELFTEST)
208 ble_npl_callout_stop(&ble_hs_flow_timer);
209 #endif
210 enable_cmd.enable = BLE_HCI_CTLR_TO_HOST_FC_ACL;
211 rc = ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_CTLR_BASEBAND,
212 BLE_HCI_OCF_CB_SET_CTLR_TO_HOST_FC),
213 &enable_cmd, sizeof(enable_cmd), NULL, 0);
214 if (rc != 0) {
215 return rc;
216 }
217
218 rc = ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_CTLR_BASEBAND,
219 BLE_HCI_OCF_CB_HOST_BUF_SIZE),
220 &buf_size_cmd, sizeof(buf_size_cmd), NULL, 0);
221 if (rc != 0) {
222 enable_cmd.enable = BLE_HCI_CTLR_TO_HOST_FC_OFF;
223 ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_CTLR_BASEBAND,
224 BLE_HCI_OCF_CB_SET_CTLR_TO_HOST_FC),
225 &enable_cmd, sizeof(enable_cmd), NULL, 0);
226 return rc;
227 }
228
229 /* Flow control successfully enabled. */
230 ble_hs_flow_num_completed_pkts = 0;
231 ble_hci_trans_set_acl_free_cb(ble_hs_flow_acl_free, NULL);
232 ble_npl_callout_init(&ble_hs_flow_timer, ble_hs_evq_get(),
233 ble_hs_flow_event_cb, NULL);
234 #endif
235 return 0;
236 }
237
ble_hs_flow_deinit(void)238 int ble_hs_flow_deinit(void)
239 {
240 #if MYNEWT_VAL(BLE_HS_FLOW_CTRL)
241 ble_npl_callout_deinit(&ble_hs_flow_timer);
242 #endif
243 return 0;
244 }