/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #include #include #include #include "sysinit/sysinit.h" #include "nimble_syscfg.h" #include "stats/stats.h" #include "host/ble_hs.h" #include "ble_hs_priv.h" #include "nimble/nimble_npl.h" #ifndef MYNEWT #include "nimble/nimble_port.h" #endif #define BLE_HS_HCI_EVT_COUNT MYNEWT_VAL(BLE_TRANSPORT_EVT_COUNT) static void ble_hs_event_rx_hci_ev(struct ble_npl_event *ev); #if NIMBLE_BLE_CONNECT static void ble_hs_event_tx_notify(struct ble_npl_event *ev); #endif static void ble_hs_event_reset(struct ble_npl_event *ev); static void ble_hs_event_start_stage1(struct ble_npl_event *ev); static void ble_hs_event_start_stage2(struct ble_npl_event *ev); static void ble_hs_timer_sched(int32_t ticks_from_now); struct os_mempool ble_hs_hci_ev_pool; static os_membuf_t ble_hs_hci_os_event_buf[ OS_MEMPOOL_SIZE(BLE_HS_HCI_EVT_COUNT, sizeof (struct ble_npl_event)) ]; /** OS event - triggers tx of pending notifications and indications. */ static struct ble_npl_event ble_hs_ev_tx_notifications; /** OS event - triggers a full reset. */ static struct ble_npl_event ble_hs_ev_reset; static struct ble_npl_event ble_hs_ev_start_stage1; static struct ble_npl_event ble_hs_ev_start_stage2; static struct ble_npl_event ble_hs_ev_user; uint8_t ble_hs_sync_state; uint8_t ble_hs_enabled_state; static int ble_hs_reset_reason; #define BLE_HS_SYNC_RETRY_TIMEOUT_MS 100 /* ms */ static void *ble_hs_parent_task; /** * Handles unresponsive timeouts and periodic retries in case of resource * shortage. */ static struct ble_npl_callout ble_hs_timer; /* Shared queue that the host uses for work items. */ static struct ble_npl_eventq *ble_hs_evq; static struct ble_mqueue ble_hs_rx_q; static struct ble_npl_mutex ble_hs_mutex; /** These values keep track of required ATT and GATT resources counts. They * increase as services are added, and are read when the ATT server and GATT * server are started. */ uint16_t ble_hs_max_attrs; uint16_t ble_hs_max_services; uint16_t ble_hs_max_client_configs; #if MYNEWT_VAL(BLE_HS_DEBUG) static uint8_t ble_hs_dbg_mutex_locked; #endif STATS_SECT_DECL(ble_hs_stats) ble_hs_stats; STATS_NAME_START(ble_hs_stats) STATS_NAME(ble_hs_stats, conn_create) STATS_NAME(ble_hs_stats, conn_delete) STATS_NAME(ble_hs_stats, hci_cmd) STATS_NAME(ble_hs_stats, hci_event) STATS_NAME(ble_hs_stats, hci_invalid_ack) STATS_NAME(ble_hs_stats, hci_unknown_event) STATS_NAME(ble_hs_stats, hci_timeout) STATS_NAME(ble_hs_stats, reset) STATS_NAME(ble_hs_stats, sync) STATS_NAME(ble_hs_stats, pvcy_add_entry) STATS_NAME(ble_hs_stats, pvcy_add_entry_fail) STATS_NAME_END(ble_hs_stats) struct ble_npl_eventq * ble_hs_evq_get(void) { return ble_hs_evq; } void ble_hs_evq_set(struct ble_npl_eventq *evq) { ble_hs_evq = evq; } #if MYNEWT_VAL(BLE_HS_DEBUG) int ble_hs_locked_by_cur_task(void) { #if MYNEWT struct os_task *owner; if (!ble_npl_os_started()) { return ble_hs_dbg_mutex_locked; } owner = ble_hs_mutex.mu.mu_owner; return owner != NULL && owner == os_sched_get_current_task(); #else return 1; #endif } #endif /** * Indicates whether the host's parent task is currently running. */ int ble_hs_is_parent_task(void) { return !ble_npl_os_started() || ble_npl_get_current_task_id() == ble_hs_parent_task; } /** * Locks the BLE host mutex. Nested locks allowed. */ void ble_hs_lock_nested(void) { int rc; #if MYNEWT_VAL(BLE_HS_DEBUG) if (!ble_npl_os_started()) { ble_hs_dbg_mutex_locked = 1; return; } #endif rc = ble_npl_mutex_pend(&ble_hs_mutex, 0xffffffff); BLE_HS_DBG_ASSERT_EVAL(rc == 0 || rc == OS_NOT_STARTED); } /** * Unlocks the BLE host mutex. Nested locks allowed. */ void ble_hs_unlock_nested(void) { int rc; #if MYNEWT_VAL(BLE_HS_DEBUG) if (!ble_npl_os_started()) { ble_hs_dbg_mutex_locked = 0; return; } #endif rc = ble_npl_mutex_release(&ble_hs_mutex); BLE_HS_DBG_ASSERT_EVAL(rc == 0 || rc == OS_NOT_STARTED); } /** * Locks the BLE host mutex. Nested locks not allowed. */ void ble_hs_lock(void) { BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task()); #if MYNEWT_VAL(BLE_HS_DEBUG) if (!ble_npl_os_started()) { BLE_HS_DBG_ASSERT(!ble_hs_dbg_mutex_locked); } #endif ble_hs_lock_nested(); } /** * Unlocks the BLE host mutex. Nested locks not allowed. */ void ble_hs_unlock(void) { #if MYNEWT_VAL(BLE_HS_DEBUG) if (!ble_npl_os_started()) { BLE_HS_DBG_ASSERT(ble_hs_dbg_mutex_locked); } #endif ble_hs_unlock_nested(); } void ble_hs_process_rx_data_queue(void) { struct os_mbuf *om; while ((om = ble_mqueue_get(&ble_hs_rx_q)) != NULL) { ble_hs_hci_evt_acl_process(om); } } static int ble_hs_wakeup_tx_conn(struct ble_hs_conn *conn) { struct os_mbuf_pkthdr *omp; struct os_mbuf *om; int rc; while ((omp = STAILQ_FIRST(&conn->bhc_tx_q)) != NULL) { STAILQ_REMOVE_HEAD(&conn->bhc_tx_q, omp_next); om = OS_MBUF_PKTHDR_TO_MBUF(omp); rc = ble_hs_hci_acl_tx_now(conn, &om); if (rc == BLE_HS_EAGAIN) { /* Controller is at capacity. This packet will be the first to * get transmitted next time around. */ STAILQ_INSERT_HEAD(&conn->bhc_tx_q, OS_MBUF_PKTHDR(om), omp_next); return BLE_HS_EAGAIN; } } return 0; } /** * Schedules the transmission of all queued ACL data packets to the controller. */ void ble_hs_wakeup_tx(void) { struct ble_hs_conn *conn; int rc; ble_hs_lock(); /* If there is a connection with a partially transmitted packet, it has to * be serviced first. The controller is waiting for the remainder so it * can reassemble it. */ for (conn = ble_hs_conn_first(); conn != NULL; conn = SLIST_NEXT(conn, bhc_next)) { if (conn->bhc_flags & BLE_HS_CONN_F_TX_FRAG) { rc = ble_hs_wakeup_tx_conn(conn); if (rc != 0) { goto done; } break; } } /* For each connection, transmit queued packets until there are no more * packets to send or the controller's buffers are exhausted. */ for (conn = ble_hs_conn_first(); conn != NULL; conn = SLIST_NEXT(conn, bhc_next)) { rc = ble_hs_wakeup_tx_conn(conn); if (rc != 0) { goto done; } } done: ble_hs_unlock(); } static void ble_hs_clear_rx_queue(void) { struct os_mbuf *om; while ((om = ble_mqueue_get(&ble_hs_rx_q)) != NULL) { os_mbuf_free_chain(om); } } int ble_hs_is_enabled(void) { return ble_hs_enabled_state == BLE_HS_ENABLED_STATE_ON; } int ble_hs_synced(void) { return ble_hs_sync_state == BLE_HS_SYNC_STATE_GOOD; } static int ble_hs_sync(void) { ble_npl_time_t retry_tmo_ticks; int rc; /* Set the sync state to "bringup." This allows the parent task to send * the startup sequence to the controller. No other tasks are allowed to * send any commands. */ ble_hs_sync_state = BLE_HS_SYNC_STATE_BRINGUP; rc = ble_hs_startup_go(); if (rc == 0) { ble_hs_sync_state = BLE_HS_SYNC_STATE_GOOD; } else { ble_hs_sync_state = BLE_HS_SYNC_STATE_BAD; } retry_tmo_ticks = ble_npl_time_ms_to_ticks32(BLE_HS_SYNC_RETRY_TIMEOUT_MS); ble_hs_timer_sched(retry_tmo_ticks); if (rc == 0) { rc = ble_hs_misc_restore_irks(); if (rc != 0) { BLE_HS_LOG(INFO, "Failed to restore IRKs from store; status=%d\n", rc); } if (ble_hs_cfg.sync_cb != NULL) { ble_hs_cfg.sync_cb(); } STATS_INC(ble_hs_stats, sync); } return rc; } static int ble_hs_reset(void) { int rc; STATS_INC(ble_hs_stats, reset); ble_hs_sync_state = 0; ble_hs_clear_rx_queue(); /* Clear adverising and scanning states. */ ble_gap_reset_state(ble_hs_reset_reason); /* Clear configured addresses. */ ble_hs_id_reset(); if (ble_hs_cfg.reset_cb != NULL && ble_hs_reset_reason != 0) { ble_hs_cfg.reset_cb(ble_hs_reset_reason); } ble_hs_reset_reason = 0; rc = ble_hs_sync(); return rc; } /** * Called when the host timer expires. Handles unresponsive timeouts and * periodic retries in case of resource shortage. */ static void ble_hs_timer_exp(struct ble_npl_event *ev) { int32_t ticks_until_next; switch (ble_hs_sync_state) { case BLE_HS_SYNC_STATE_GOOD: #if NIMBLE_BLE_CONNECT ticks_until_next = ble_gattc_timer(); ble_hs_timer_sched(ticks_until_next); ticks_until_next = ble_l2cap_sig_timer(); ble_hs_timer_sched(ticks_until_next); ticks_until_next = ble_sm_timer(); ble_hs_timer_sched(ticks_until_next); ticks_until_next = ble_hs_conn_timer(); ble_hs_timer_sched(ticks_until_next); #endif ticks_until_next = ble_gap_timer(); ble_hs_timer_sched(ticks_until_next); break; case BLE_HS_SYNC_STATE_BAD: ble_hs_reset(); break; case BLE_HS_SYNC_STATE_BRINGUP: default: /* The timer should not be set in this state. */ assert(0); break; } } static void ble_hs_timer_reset(uint32_t ticks) { int rc; if (!ble_hs_is_enabled()) { ble_npl_callout_stop(&ble_hs_timer); } else { rc = ble_npl_callout_reset(&ble_hs_timer, ticks); BLE_HS_DBG_ASSERT_EVAL(rc == 0); } } static void ble_hs_timer_sched(int32_t ticks_from_now) { ble_npl_time_t abs_time; if (ticks_from_now == BLE_HS_FOREVER) { return; } /* Reset timer if it is not currently scheduled or if the specified time is * sooner than the previous expiration time. */ abs_time = ble_npl_time_get() + ticks_from_now; if (!ble_npl_callout_is_active(&ble_hs_timer) || ((ble_npl_stime_t)(abs_time - ble_npl_callout_get_ticks(&ble_hs_timer))) < 0) { ble_hs_timer_reset(ticks_from_now); } } void ble_hs_timer_resched(void) { /* Reschedule the timer to run immediately. The timer callback will query * each module for an up-to-date expiration time. */ ble_hs_timer_reset(0); } static void ble_hs_sched_start_stage2(void) { ble_npl_eventq_put((struct ble_npl_eventq *)ble_hs_evq_get(), &ble_hs_ev_start_stage2); } void ble_hs_sched_start(void) { #ifdef MYNEWT ble_npl_eventq_put((struct ble_npl_eventq *)os_eventq_dflt_get(), &ble_hs_ev_start_stage1); #else ble_npl_eventq_put(nimble_port_get_dflt_eventq(), &ble_hs_ev_start_stage1); #endif } static void ble_hs_event_rx_hci_ev(struct ble_npl_event *ev) { struct ble_hci_ev *hci_ev; int rc; hci_ev = ble_npl_event_get_arg(ev); rc = os_memblock_put(&ble_hs_hci_ev_pool, ev); BLE_HS_DBG_ASSERT_EVAL(rc == 0); ble_hs_hci_evt_process(hci_ev); } #if NIMBLE_BLE_CONNECT static void ble_hs_event_tx_notify(struct ble_npl_event *ev) { ble_gatts_tx_notifications(); } #endif void __attribute__((weak)) ble_app_user_evt(struct ble_npl_event *ev) { return; } void ble_app_user_evt_post(void) { ble_npl_eventq_put(ble_hs_evq, &ble_hs_ev_user); } static void ble_hs_event_rx_data(struct ble_npl_event *ev) { ble_hs_process_rx_data_queue(); } static void ble_hs_event_reset(struct ble_npl_event *ev) { ble_hs_reset(); } /** * Implements the first half of the start process. This just enqueues another * event on the host parent task's event queue. * * Starting is done in two stages to allow the application time to configure * the event queue to use after system initialization but before the host * starts. */ static void ble_hs_event_start_stage1(struct ble_npl_event *ev) { ble_hs_sched_start_stage2(); } /** * Implements the second half of the start process. This actually starts the * host. * * Starting is done in two stages to allow the application time to configure * the event queue to use after system initialization but before the host * starts. */ static void ble_hs_event_start_stage2(struct ble_npl_event *ev) { int rc; rc = ble_hs_start(); assert(rc == 0); } void ble_hs_enqueue_hci_event(uint8_t *hci_evt) { struct ble_npl_event *ev; ev = os_memblock_get(&ble_hs_hci_ev_pool); if (ev == NULL) { ble_transport_free(hci_evt); } else { ble_npl_event_init(ev, ble_hs_event_rx_hci_ev, hci_evt); ble_npl_eventq_put(ble_hs_evq, ev); } } /** * Schedules for all pending notifications and indications to be sent in the * host parent task. */ void ble_hs_notifications_sched(void) { #if !MYNEWT_VAL(BLE_HS_REQUIRE_OS) if (!ble_npl_os_started()) { ble_gatts_tx_notifications(); return; } #endif ble_npl_eventq_put(ble_hs_evq, &ble_hs_ev_tx_notifications); } void ble_hs_sched_reset(int reason) { BLE_HS_DBG_ASSERT(ble_hs_reset_reason == 0); ble_hs_reset_reason = reason; ble_npl_eventq_put(ble_hs_evq, &ble_hs_ev_reset); } void ble_hs_hw_error(uint8_t hw_code) { ble_hs_sched_reset(BLE_HS_HW_ERR(hw_code)); } int ble_hs_start(void) { int rc; ble_hs_lock(); switch (ble_hs_enabled_state) { case BLE_HS_ENABLED_STATE_ON: rc = BLE_HS_EALREADY; break; case BLE_HS_ENABLED_STATE_STOPPING: rc = BLE_HS_EBUSY; break; case BLE_HS_ENABLED_STATE_OFF: ble_hs_enabled_state = BLE_HS_ENABLED_STATE_ON; rc = 0; break; default: assert(0); rc = BLE_HS_EUNKNOWN; break; } ble_hs_unlock(); if (rc != 0) { return rc; } ble_hs_parent_task = ble_npl_get_current_task_id(); #if MYNEWT_VAL(SELFTEST) /* Stop the timer just in case the host was already running (e.g., unit * tests). */ ble_npl_callout_stop(&ble_hs_timer); #endif ble_npl_callout_init(&ble_hs_timer, ble_hs_evq, ble_hs_timer_exp, NULL); #if NIMBLE_BLE_CONNECT rc = ble_gatts_start(); if (rc != 0) { return rc; } #endif ble_hs_sync(); return 0; } /** * Called when a data packet is received from the controller. This function * consumes the supplied mbuf, regardless of the outcome. * * @param om The incoming data packet, beginning with the * HCI ACL data header. * * @return 0 on success; nonzero on failure. */ static int ble_hs_rx_data(struct os_mbuf *om, void *arg) { int rc; /* If flow control is enabled, mark this packet with its corresponding * connection handle. */ ble_hs_flow_track_data_mbuf(om); rc = ble_mqueue_put(&ble_hs_rx_q, ble_hs_evq, om); if (rc != 0) { os_mbuf_free_chain(om); return BLE_HS_EOS; } return 0; } /** * Enqueues an ACL data packet for transmission. This function consumes the * supplied mbuf, regardless of the outcome. * * @param om The outgoing data packet, beginning with the * HCI ACL data header. * * @return 0 on success; nonzero on failure. */ int ble_hs_tx_data(struct os_mbuf *om) { return ble_transport_to_ll_acl(om); } void ble_hs_init(void) { int rc; /* Ensure this function only gets called by sysinit. */ SYSINIT_ASSERT_ACTIVE(); /* Create memory pool of OS events */ rc = os_mempool_init(&ble_hs_hci_ev_pool, BLE_HS_HCI_EVT_COUNT, sizeof (struct ble_npl_event), ble_hs_hci_os_event_buf, "ble_hs_hci_ev_pool"); SYSINIT_PANIC_ASSERT(rc == 0); /* These get initialized here to allow unit tests to run without a zeroed * bss. */ ble_hs_reset_reason = 0; ble_hs_enabled_state = BLE_HS_ENABLED_STATE_OFF; #if NIMBLE_BLE_CONNECT ble_npl_event_init(&ble_hs_ev_tx_notifications, ble_hs_event_tx_notify, NULL); #endif ble_npl_event_init(&ble_hs_ev_reset, ble_hs_event_reset, NULL); ble_npl_event_init(&ble_hs_ev_start_stage1, ble_hs_event_start_stage1, NULL); ble_npl_event_init(&ble_hs_ev_start_stage2, ble_hs_event_start_stage2, NULL); ble_npl_event_init(&ble_hs_ev_user, ble_app_user_evt, NULL); ble_hs_hci_init(); rc = ble_hs_conn_init(); SYSINIT_PANIC_ASSERT(rc == 0); #if MYNEWT_VAL(BLE_PERIODIC_ADV) rc = ble_hs_periodic_sync_init(); SYSINIT_PANIC_ASSERT(rc == 0); #endif #if NIMBLE_BLE_CONNECT rc = ble_l2cap_init(); SYSINIT_PANIC_ASSERT(rc == 0); rc = ble_att_init(); SYSINIT_PANIC_ASSERT(rc == 0); rc = ble_att_svr_init(); SYSINIT_PANIC_ASSERT(rc == 0); rc = ble_gattc_init(); SYSINIT_PANIC_ASSERT(rc == 0); rc = ble_gatts_init(); SYSINIT_PANIC_ASSERT(rc == 0); #endif rc = ble_gap_init(); SYSINIT_PANIC_ASSERT(rc == 0); ble_hs_stop_init(); ble_mqueue_init(&ble_hs_rx_q, ble_hs_event_rx_data, NULL); rc = stats_init_and_reg( STATS_HDR(ble_hs_stats), STATS_SIZE_INIT_PARMS(ble_hs_stats, STATS_SIZE_32), STATS_NAME_INIT_PARMS(ble_hs_stats), "ble_hs"); SYSINIT_PANIC_ASSERT(rc == 0); rc = ble_npl_mutex_init(&ble_hs_mutex); SYSINIT_PANIC_ASSERT(rc == 0); #if MYNEWT_VAL(BLE_HS_DEBUG) ble_hs_dbg_mutex_locked = 0; #endif #ifdef MYNEWT ble_hs_evq_set((struct ble_npl_eventq *)os_eventq_dflt_get()); #else ble_hs_evq_set(nimble_port_get_dflt_eventq()); #endif /* Enqueue the start event to the default event queue. Using the default * queue ensures the event won't run until the end of main(). This allows * the application to configure this package in the meantime. */ #if MYNEWT_VAL(BLE_HS_AUTO_START) #ifdef MYNEWT ble_npl_eventq_put((struct ble_npl_eventq *)os_eventq_dflt_get(), &ble_hs_ev_start_stage1); #else ble_npl_eventq_put(nimble_port_get_dflt_eventq(), &ble_hs_ev_start_stage1); #endif #endif } /* Transport APIs for HS side */ int ble_transport_to_hs_evt_impl(void *buf) { return ble_hs_hci_rx_evt(buf, NULL); } int ble_transport_to_hs_acl_impl(struct os_mbuf *om) { return ble_hs_rx_data(om, NULL); } void ble_transport_hs_init(void) { ble_hs_init(); }