blob: bac7f02e6a6c07c45d858fada234a17236e1e918 [file] [log] [blame]
/*
* 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 <assert.h>
#include <errno.h>
#include <string.h>
#include "sysinit/sysinit.h"
#include "syscfg/syscfg.h"
#include "stats/stats.h"
#include "host/ble_hs.h"
#if MYNEWT_VAL(BLE_ISO_BROADCAST_SOURCE)
#include "audio/ble_audio_broadcast_source.h"
#endif
#include "ble_hs_priv.h"
#include "ble_iso_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) + \
MYNEWT_VAL(BLE_TRANSPORT_EVT_DISCARDABLE_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;
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)
{
#ifdef 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
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_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);
#endif
rc = ble_gap_init();
SYSINIT_PANIC_ASSERT(rc == 0);
#if NIMBLE_BLE_CONNECT
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
#if MYNEWT_VAL(BLE_ISO)
rc = ble_iso_init();
SYSINIT_PANIC_ASSERT(rc == 0);
#if MYNEWT_VAL(BLE_ISO_BROADCAST_SOURCE)
rc = ble_audio_broadcast_init();
SYSINIT_PANIC_ASSERT(rc == 0);
#endif
#endif
#if MYNEWT_VAL(BLE_AUDIO_MAX_CODEC_RECORDS)
rc = ble_audio_codec_init();
SYSINIT_PANIC_ASSERT(rc == 0);
#endif
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);
}
int
ble_transport_to_hs_iso_impl(struct os_mbuf *om)
{
#if MYNEWT_VAL(BLE_ISO_BROADCAST_SINK)
return ble_iso_rx_data(om, NULL);
#else
os_mbuf_free_chain(om);
return 0;
#endif
}
void
ble_transport_hs_init(void)
{
ble_hs_init();
}