blob: cee6f976b2a09454ac97d4c404b058759a0c53a2 [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 <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include "syscfg/syscfg.h"
#include "os/os.h"
#include "nimble/ble.h"
#include "nimble/hci_common.h"
#include "nimble/transport.h"
#include "controller/ble_ll.h"
#include "controller/ble_ll_pdu.h"
#include "controller/ble_ll_conn.h"
#include "controller/ble_ll_hci.h"
#include "controller/ble_ll_scan.h"
#include "controller/ble_ll_whitelist.h"
#include "controller/ble_ll_sched.h"
#include "controller/ble_ll_ctrl.h"
#include "controller/ble_ll_resolv.h"
#include "controller/ble_ll_adv.h"
#include "controller/ble_ll_trace.h"
#include "controller/ble_ll_rfmgmt.h"
#include "controller/ble_ll_tmr.h"
#include "controller/ble_phy.h"
#include "controller/ble_ll_utils.h"
#include "ble_ll_conn_priv.h"
#include "ble_ll_ctrl_priv.h"
#include "ble_ll_priv.h"
#if MYNEWT_PKG_apache_mynewt_nimble__nimble_transport_common_hci_ipc
#include <nimble/transport/hci_ipc.h>
#endif
#if (BLETEST_THROUGHPUT_TEST == 1)
extern void bletest_completed_pkt(uint16_t handle);
#endif
#if MYNEWT_VAL(BLE_LL_CONN_STRICT_SCHED)
struct ble_ll_conn_sm *g_ble_ll_conn_css_ref;
#endif
/* XXX TODO
* 1) I think if we are initiating and we already have a connection with
* a device that we will still try and connect to it. Fix this.
* -> This is true. There are a couple things to do
* i) When a connection create is issued, if we already are connected
* deny it. BLE ERROR = 0x0B (ACL connection exists).
* ii) If we receive an advertisement while initiating and want to send
* a connect request to the device, make sure we dont have it.
* iii) I think I need to do something like this: I am initiating and
* advertising. Suppose the device I want to connect to sends me a connect
* request because I am advertising? What happens to connection? Deal
* with this!
*
* 2) Make sure we check incoming data packets for size and all that. You
* know, supported octets and all that. For both rx and tx.
*
* 3) Make sure we are setting the schedule end time properly for both peripheral
* and central. We should just set this to the end of the connection event.
* We might want to guarantee a IFS time as well since the next event needs
* to be scheduled prior to the start of the event to account for the time it
* takes to get a frame ready (which is pretty much the IFS time).
*
* 4) looks like the current code will allow the 1st packet in a
* connection to extend past the end of the allocated connection end
* time. That is not good. Need to deal with that. Need to extend connection
* end time.
*
* 6) Use error code 0x3E correctly! Connection failed to establish. If you
* read the LE connection complete event, it says that if the connection
* fails to be established that the connection complete event gets sent to
* the host that issued the create connection. Need to resolve this.
*
* 7) How does peer address get set if we are using whitelist? Look at filter
* policy and make sure you are doing this correctly.
*
* 8) Right now I use a fixed definition for required slots. CHange this.
*
* 10) See what connection state machine elements are purely central and
* purely peripheral. We can make a union of them.
*
* 11) Not sure I am dealing with the connection terminate timeout perfectly.
* I may extend a connection event too long although if it is always in terms
* of connection events I am probably fine. Checking at end that the next
* connection event will occur past terminate timeould would be fine.
*
* 12) When a peripheral receives a data packet in a connection it has to send a
* response. Well, it should. If this packet will overrun the next scheduled
* event, what should we do? Transmit anyway? Not transmit? For now, we just
* transmit.
*
* 32kHz crystal
* 1) When scheduling, I need to make sure I have time between
* this one and the next. Should I deal with this in the sched. Or
* is this basically accounted for given a slot? I really just need to
* make sure everything is over N ticks before the next sched start!
* Just add to end time?
*
* 2) I think one way to handle the problem of losing up to a microsecond
* every time we call ble_ll_conn_next_event in a loop is to do everything by
* keeping track of last anchor point. Would need last anchor usecs too. I guess
* we could also keep last anchor usecs as a uint32 or something and when we
* do the next event keep track of the residual using a different ticks to
* usecs calculation. Not sure.
*/
/*
* XXX: How should we deal with a late connection event? We need to determine
* what we want to do under the following cases:
* 1) The current connection event has not ended but a schedule item starts
*/
/* Global LL connection parameters */
struct ble_ll_conn_global_params g_ble_ll_conn_params;
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL) || MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
/* This is a dummy structure we use for the empty PDU */
struct ble_ll_empty_pdu
{
struct os_mbuf om;
struct os_mbuf_pkthdr pkt_hdr;
struct ble_mbuf_hdr ble_hdr;
};
/* We cannot have more than 254 connections given our current implementation */
#if (MYNEWT_VAL(BLE_MAX_CONNECTIONS) >= 255)
#error "Maximum # of connections is 254"
#endif
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
/* Global connection complete event. Used when initiating */
uint8_t *g_ble_ll_conn_comp_ev;
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV_SYNC_TRANSFER)
/* Global default sync transfer params */
struct ble_ll_conn_sync_transfer_params g_ble_ll_conn_sync_transfer_params;
#endif
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
struct ble_ll_conn_create_sm g_ble_ll_conn_create_sm;
#endif
/* Pointer to current connection */
struct ble_ll_conn_sm *g_ble_ll_conn_cur_sm;
/* Connection state machine array */
struct ble_ll_conn_sm g_ble_ll_conn_sm[MYNEWT_VAL(BLE_MAX_CONNECTIONS)];
/* List of active connections */
struct ble_ll_conn_active_list g_ble_ll_conn_active_list;
/* List of free connections */
struct ble_ll_conn_free_list g_ble_ll_conn_free_list;
#if MYNEWT_VAL(BLE_LL_CONN_STRICT_SCHED)
static uint16_t g_ble_ll_conn_css_next_slot = BLE_LL_CONN_CSS_NO_SLOT;
struct ble_ll_conn_css_list g_ble_ll_conn_css_list;
#endif
STATS_SECT_START(ble_ll_conn_stats)
STATS_SECT_ENTRY(cant_set_sched)
STATS_SECT_ENTRY(conn_ev_late)
STATS_SECT_ENTRY(wfr_expirations)
STATS_SECT_ENTRY(handle_not_found)
STATS_SECT_ENTRY(no_conn_sm)
STATS_SECT_ENTRY(no_free_conn_sm)
STATS_SECT_ENTRY(rx_data_pdu_no_conn)
STATS_SECT_ENTRY(rx_data_pdu_bad_aa)
STATS_SECT_ENTRY(periph_rxd_bad_conn_req_params)
STATS_SECT_ENTRY(periph_ce_failures)
STATS_SECT_ENTRY(data_pdu_rx_dup)
STATS_SECT_ENTRY(data_pdu_txg)
STATS_SECT_ENTRY(data_pdu_txf)
STATS_SECT_ENTRY(conn_req_txd)
STATS_SECT_ENTRY(l2cap_enqueued)
STATS_SECT_ENTRY(rx_ctrl_pdus)
STATS_SECT_ENTRY(rx_l2cap_pdus)
STATS_SECT_ENTRY(rx_l2cap_bytes)
STATS_SECT_ENTRY(rx_malformed_ctrl_pdus)
STATS_SECT_ENTRY(rx_bad_llid)
STATS_SECT_ENTRY(tx_ctrl_pdus)
STATS_SECT_ENTRY(tx_ctrl_bytes)
STATS_SECT_ENTRY(tx_l2cap_pdus)
STATS_SECT_ENTRY(tx_l2cap_bytes)
STATS_SECT_ENTRY(tx_empty_pdus)
STATS_SECT_ENTRY(mic_failures)
STATS_SECT_ENTRY(sched_start_in_idle)
STATS_SECT_ENTRY(sched_end_in_idle)
STATS_SECT_ENTRY(conn_event_while_tmo)
STATS_SECT_END
STATS_SECT_DECL(ble_ll_conn_stats) ble_ll_conn_stats;
STATS_NAME_START(ble_ll_conn_stats)
STATS_NAME(ble_ll_conn_stats, cant_set_sched)
STATS_NAME(ble_ll_conn_stats, conn_ev_late)
STATS_NAME(ble_ll_conn_stats, wfr_expirations)
STATS_NAME(ble_ll_conn_stats, handle_not_found)
STATS_NAME(ble_ll_conn_stats, no_conn_sm)
STATS_NAME(ble_ll_conn_stats, no_free_conn_sm)
STATS_NAME(ble_ll_conn_stats, rx_data_pdu_no_conn)
STATS_NAME(ble_ll_conn_stats, rx_data_pdu_bad_aa)
STATS_NAME(ble_ll_conn_stats, periph_rxd_bad_conn_req_params)
STATS_NAME(ble_ll_conn_stats, periph_ce_failures)
STATS_NAME(ble_ll_conn_stats, data_pdu_rx_dup)
STATS_NAME(ble_ll_conn_stats, data_pdu_txg)
STATS_NAME(ble_ll_conn_stats, data_pdu_txf)
STATS_NAME(ble_ll_conn_stats, conn_req_txd)
STATS_NAME(ble_ll_conn_stats, l2cap_enqueued)
STATS_NAME(ble_ll_conn_stats, rx_ctrl_pdus)
STATS_NAME(ble_ll_conn_stats, rx_l2cap_pdus)
STATS_NAME(ble_ll_conn_stats, rx_l2cap_bytes)
STATS_NAME(ble_ll_conn_stats, rx_malformed_ctrl_pdus)
STATS_NAME(ble_ll_conn_stats, rx_bad_llid)
STATS_NAME(ble_ll_conn_stats, tx_ctrl_pdus)
STATS_NAME(ble_ll_conn_stats, tx_ctrl_bytes)
STATS_NAME(ble_ll_conn_stats, tx_l2cap_pdus)
STATS_NAME(ble_ll_conn_stats, tx_l2cap_bytes)
STATS_NAME(ble_ll_conn_stats, tx_empty_pdus)
STATS_NAME(ble_ll_conn_stats, mic_failures)
STATS_NAME(ble_ll_conn_stats, sched_start_in_idle)
STATS_NAME(ble_ll_conn_stats, sched_end_in_idle)
STATS_NAME(ble_ll_conn_stats, conn_event_while_tmo)
STATS_NAME_END(ble_ll_conn_stats)
static void ble_ll_conn_event_end(struct ble_npl_event *ev);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_CTRL_TO_HOST_FLOW_CONTROL)
struct ble_ll_conn_cth_flow {
bool enabled;
uint16_t max_buffers;
uint16_t num_buffers;
};
static struct ble_ll_conn_cth_flow g_ble_ll_conn_cth_flow;
static struct ble_npl_event g_ble_ll_conn_cth_flow_error_ev;
static bool
ble_ll_conn_cth_flow_is_enabled(void)
{
return g_ble_ll_conn_cth_flow.enabled;
}
static bool
ble_ll_conn_cth_flow_alloc_credit(struct ble_ll_conn_sm *connsm)
{
struct ble_ll_conn_cth_flow *cth = &g_ble_ll_conn_cth_flow;
os_sr_t sr;
OS_ENTER_CRITICAL(sr);
if (!cth->num_buffers) {
OS_EXIT_CRITICAL(sr);
return false;
}
connsm->cth_flow_pending++;
cth->num_buffers--;
OS_EXIT_CRITICAL(sr);
return true;
}
static void
ble_ll_conn_cth_flow_free_credit(struct ble_ll_conn_sm *connsm, uint16_t credits)
{
struct ble_ll_conn_cth_flow *cth = &g_ble_ll_conn_cth_flow;
os_sr_t sr;
OS_ENTER_CRITICAL(sr);
/*
* It's not quite clear what we should do if host gives back more credits
* that we have allocated. For now let's just set invalid values back to
* sane values and continue.
*/
cth->num_buffers += credits;
if (cth->num_buffers > cth->max_buffers) {
cth->num_buffers = cth->max_buffers;
}
if (connsm->cth_flow_pending < credits) {
connsm->cth_flow_pending = 0;
} else {
connsm->cth_flow_pending -= credits;
}
OS_EXIT_CRITICAL(sr);
}
static void
ble_ll_conn_cth_flow_error_fn(struct ble_npl_event *ev)
{
struct ble_hci_ev *hci_ev;
struct ble_hci_ev_command_complete *hci_ev_cp;
uint16_t opcode;
hci_ev = ble_transport_alloc_evt(0);
if (!hci_ev) {
/* Not much we can do anyway... */
return;
}
/*
* We are here in case length of HCI_Host_Number_Of_Completed_Packets was
* invalid. We will send an error back to host and we can only hope host is
* reasonable and will do some actions to recover, e.g. it should disconnect
* all connections to guarantee that all credits are back in pool and we're
* back in sync (although spec does not really say what should happen).
*/
opcode = BLE_HCI_OP(BLE_HCI_OGF_CTLR_BASEBAND,
BLE_HCI_OCF_CB_HOST_NUM_COMP_PKTS);
hci_ev->opcode = BLE_HCI_EVCODE_COMMAND_COMPLETE;
hci_ev->length = sizeof(*hci_ev_cp);
hci_ev_cp = (void *)hci_ev->data;
hci_ev_cp->num_packets = BLE_LL_CFG_NUM_HCI_CMD_PKTS;
hci_ev_cp->opcode = htole16(opcode);
hci_ev_cp->status = BLE_ERR_INV_HCI_CMD_PARMS;
ble_ll_hci_event_send(hci_ev);
}
void
ble_ll_conn_cth_flow_set_buffers(uint16_t num_buffers)
{
BLE_LL_ASSERT(num_buffers);
g_ble_ll_conn_cth_flow.max_buffers = num_buffers;
g_ble_ll_conn_cth_flow.num_buffers = num_buffers;
}
bool
ble_ll_conn_cth_flow_enable(bool enabled)
{
struct ble_ll_conn_cth_flow *cth = &g_ble_ll_conn_cth_flow;
if (cth->enabled == enabled) {
return true;
}
if (!SLIST_EMPTY(&g_ble_ll_conn_active_list)) {
return false;
}
cth->enabled = enabled;
return true;
}
void
ble_ll_conn_cth_flow_process_cmd(const uint8_t *cmdbuf)
{
const struct ble_hci_cmd *cmd;
const struct ble_hci_cb_host_num_comp_pkts_cp *cp;
struct ble_ll_conn_sm *connsm;
int i;
cmd = (const void *)cmdbuf;
cp = (const void *)cmd->data;
if (cmd->length != sizeof(cp->handles) + cp->handles * sizeof(cp->h[0])) {
ble_ll_event_add(&g_ble_ll_conn_cth_flow_error_ev);
return;
}
for (i = 0; i < cp->handles; i++) {
/*
* It's probably ok that we do not have active connection with given
* handle - this can happen if disconnection already happened in LL but
* host sent credits back before processing disconnection event. In such
* case we can simply ignore command for that connection since credits
* are returned by LL already.
*/
connsm = ble_ll_conn_find_by_handle(cp->h[i].handle);
if (connsm) {
ble_ll_conn_cth_flow_free_credit(connsm, cp->h[i].count);
}
}
}
#endif
#if MYNEWT_VAL(BLE_LL_CONN_STRICT_SCHED)
static void
ble_ll_conn_css_update_list(struct ble_ll_conn_sm *connsm)
{
struct ble_ll_conn_sm *e;
struct ble_ll_conn_sm *e_last;
struct ble_ll_conn_sm *e_insert_after = NULL;
bool found_to_insert = false;
if (SLIST_FIRST(&g_ble_ll_conn_css_list) == connsm) {
SLIST_REMOVE_HEAD(&g_ble_ll_conn_css_list, css_sle);
} else {
e_last = NULL;
SLIST_FOREACH(e, &g_ble_ll_conn_css_list, css_sle) {
if (e == connsm) {
SLIST_NEXT(e_last, css_sle) = SLIST_NEXT(e, css_sle);
break;
}
e_last = e;
}
}
if (SLIST_EMPTY(&g_ble_ll_conn_css_list)) {
SLIST_INSERT_HEAD(&g_ble_ll_conn_css_list, connsm, css_sle);
return;
}
e_last = NULL;
SLIST_FOREACH(e, &g_ble_ll_conn_css_list, css_sle) {
if (e->css_slot_idx > connsm->css_slot_idx) {
found_to_insert = true;
e_insert_after = e_last;
break;
}
e_last = e;
}
if (found_to_insert) {
if (e_insert_after) {
SLIST_INSERT_AFTER(e_last, connsm, css_sle);
} else {
SLIST_INSERT_HEAD(&g_ble_ll_conn_css_list, connsm, css_sle);
}
} else {
SLIST_INSERT_AFTER(e_last, connsm, css_sle);
}
}
void
ble_ll_conn_css_set_next_slot(uint16_t slot_idx)
{
g_ble_ll_conn_css_next_slot = slot_idx;
}
uint16_t
ble_ll_conn_css_get_next_slot(void)
{
struct ble_ll_conn_sm *connsm;
uint16_t slot_idx = 0;
if (g_ble_ll_conn_css_next_slot != BLE_LL_CONN_CSS_NO_SLOT) {
return g_ble_ll_conn_css_next_slot;
}
/* CSS connections are sorted in active conn list so just need to find 1st
* free value.
*/
SLIST_FOREACH(connsm, &g_ble_ll_conn_css_list, css_sle) {
if ((connsm->css_slot_idx != slot_idx) &&
(connsm->css_slot_idx_pending != slot_idx)) {
break;
}
slot_idx++;
}
if (slot_idx >= ble_ll_sched_css_get_period_slots()) {
slot_idx = BLE_LL_CONN_CSS_NO_SLOT;
}
return slot_idx;
}
int
ble_ll_conn_css_is_slot_busy(uint16_t slot_idx)
{
struct ble_ll_conn_sm *connsm;
if (g_ble_ll_conn_css_next_slot == slot_idx) {
return 1;
}
SLIST_FOREACH(connsm, &g_ble_ll_conn_css_list, css_sle) {
if ((connsm->css_slot_idx == slot_idx) ||
(connsm->css_slot_idx_pending == slot_idx)) {
return 1;
}
}
return 0;
}
int
ble_ll_conn_css_move(struct ble_ll_conn_sm *connsm, uint16_t slot_idx)
{
int16_t slot_diff;
uint32_t offset;
int rc;
/* Assume connsm and slot_idx are valid */
BLE_LL_ASSERT(connsm->conn_state != BLE_LL_CONN_STATE_IDLE);
BLE_LL_ASSERT(connsm->conn_role == BLE_LL_CONN_ROLE_CENTRAL);
BLE_LL_ASSERT((slot_idx < ble_ll_sched_css_get_period_slots()) ||
(slot_idx != BLE_LL_CONN_CSS_NO_SLOT));
slot_diff = slot_idx - connsm->css_slot_idx;
if (slot_diff < 0) {
slot_diff += ble_ll_sched_css_get_period_slots();
}
offset = slot_diff * ble_ll_sched_css_get_slot_us() / BLE_LL_CONN_ITVL_USECS;
if (offset >= 0xffff) {
return -1;
}
rc = ble_ll_conn_move_anchor(connsm, offset);
if (!rc) {
connsm->css_slot_idx_pending = slot_idx;
}
return rc;
}
#endif
struct ble_ll_conn_sm *
ble_ll_conn_find_by_peer_addr(const uint8_t *addr, uint8_t addr_type)
{
struct ble_ll_conn_sm *connsm;
SLIST_FOREACH(connsm, &g_ble_ll_conn_active_list, act_sle) {
if (!memcmp(&connsm->peer_addr, addr, BLE_DEV_ADDR_LEN) &&
!((connsm->peer_addr_type ^ addr_type) & 1)) {
return connsm;
}
}
return NULL;
}
#if MYNEWT_VAL(BLE_LL_PHY)
static inline int
ble_ll_conn_phy_should_update(uint8_t pref_mask, uint8_t curr_mask)
{
#if MYNEWT_VAL(BLE_LL_CONN_PHY_PREFER_2M)
/* Should change to 2M if preferred, but not active */
if ((pref_mask & BLE_PHY_MASK_2M) && (curr_mask != BLE_PHY_MASK_2M)) {
return 1;
}
#endif
/* Should change to active phy is not preferred */
if ((curr_mask & pref_mask) == 0) {
return 1;
}
return 0;
}
int
ble_ll_conn_phy_update_if_needed(struct ble_ll_conn_sm *connsm)
{
if (!ble_ll_conn_phy_should_update(connsm->phy_data.pref_mask_tx,
CONN_CUR_TX_PHY_MASK(connsm)) &&
!ble_ll_conn_phy_should_update(connsm->phy_data.pref_mask_rx,
CONN_CUR_RX_PHY_MASK(connsm))) {
return -1;
}
connsm->phy_data.pref_mask_tx_req = connsm->phy_data.pref_mask_tx;
connsm->phy_data.pref_mask_rx_req = connsm->phy_data.pref_mask_rx;
ble_ll_ctrl_proc_start(connsm, BLE_LL_CTRL_PROC_PHY_UPDATE, NULL);
return 0;
}
#endif
void
ble_ll_conn_itvl_to_ticks(uint32_t itvl, uint32_t *itvl_ticks,
uint8_t *itvl_usecs)
{
*itvl_ticks = ble_ll_tmr_u2t_r(itvl * BLE_LL_CONN_ITVL_USECS, itvl_usecs);
}
/**
* Get the event buffer allocated to send the connection complete event
* when we are initiating.
*
* @return uint8_t*
*/
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
static uint8_t *
ble_ll_init_get_conn_comp_ev(void)
{
uint8_t *evbuf;
evbuf = g_ble_ll_conn_comp_ev;
BLE_LL_ASSERT(evbuf != NULL);
g_ble_ll_conn_comp_ev = NULL;
return evbuf;
}
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
/**
* Called to determine if the received PDU is an empty PDU or not.
*/
static int
ble_ll_conn_is_empty_pdu(uint8_t *rxbuf)
{
int rc;
uint8_t llid;
llid = rxbuf[0] & BLE_LL_DATA_HDR_LLID_MASK;
if ((llid == BLE_LL_LLID_DATA_FRAG) && (rxbuf[1] == 0)) {
rc = 1;
} else {
rc = 0;
}
return rc;
}
#endif
/**
* Called to return the currently running connection state machine end time.
* Always called when interrupts are disabled.
*
* @return int 0: s1 is not least recently used. 1: s1 is least recently used
*/
int
ble_ll_conn_is_lru(struct ble_ll_conn_sm *s1, struct ble_ll_conn_sm *s2)
{
int rc;
/* Set time that we last serviced the schedule */
if (LL_TMR_LT(s1->last_scheduled, s2->last_scheduled)) {
rc = 1;
} else {
rc = 0;
}
return rc;
}
/**
* Called to return the currently running connection state machine end time.
* Always called when interrupts are disabled.
*
* @return uint32_t
*/
uint32_t
ble_ll_conn_get_ce_end_time(void)
{
uint32_t ce_end_time;
if (g_ble_ll_conn_cur_sm) {
ce_end_time = g_ble_ll_conn_cur_sm->ce_end_time;
} else {
ce_end_time = ble_ll_tmr_get();
}
return ce_end_time;
}
/**
* Called when connection state machine needs to halt. This function will:
* -> Disable the PHY, which will prevent any transmit/receive interrupts.
* -> Disable the wait for response timer, if running.
* -> Remove the connection state machine from the scheduler.
* -> Sets the Link Layer state to standby.
* -> Sets the current state machine to NULL.
*
* NOTE: the ordering of these function calls is important! We have to stop
* the PHY and remove the schedule item before we can set the state to
* standby and set the current state machine pointer to NULL.
*/
static void
ble_ll_conn_halt(void)
{
ble_phy_disable();
ble_ll_state_set(BLE_LL_STATE_STANDBY);
g_ble_ll_conn_cur_sm = NULL;
}
/**
* Called when the current connection state machine is no longer being used.
*/
static void
ble_ll_conn_current_sm_over(struct ble_ll_conn_sm *connsm)
{
ble_ll_conn_halt();
/*
* NOTE: the connection state machine may be NULL if we are calling
* this when we are ending the connection. In that case, there is no
* need to post to the LL the connection event end event
*/
if (connsm) {
ble_ll_event_add(&connsm->conn_ev_end);
}
}
/**
* Given a handle, find an active connection matching the handle
*
* @param handle
*
* @return struct ble_ll_conn_sm*
*/
struct ble_ll_conn_sm *
ble_ll_conn_find_by_handle(uint16_t handle)
{
struct ble_ll_conn_sm *connsm;
connsm = NULL;
if ((handle != 0) && (handle <= MYNEWT_VAL(BLE_MAX_CONNECTIONS))) {
connsm = &g_ble_ll_conn_sm[handle - 1];
if (connsm->conn_state == BLE_LL_CONN_STATE_IDLE) {
connsm = NULL;
}
}
return connsm;
}
/**
* Get a connection state machine.
*/
struct ble_ll_conn_sm *
ble_ll_conn_sm_get(void)
{
struct ble_ll_conn_sm *connsm;
connsm = STAILQ_FIRST(&g_ble_ll_conn_free_list);
if (connsm) {
STAILQ_REMOVE_HEAD(&g_ble_ll_conn_free_list, free_stqe);
} else {
STATS_INC(ble_ll_conn_stats, no_free_conn_sm);
}
return connsm;
}
static uint8_t
ble_ll_conn_calc_dci_csa1(struct ble_ll_conn_sm *conn)
{
uint8_t curchan;
uint8_t remap_index;
uint8_t bitpos;
/* Get next unmapped channel */
curchan = conn->last_unmapped_chan + conn->hop_inc;
if (curchan > BLE_PHY_NUM_DATA_CHANS) {
curchan -= BLE_PHY_NUM_DATA_CHANS;
}
/* Save unmapped channel */
conn->last_unmapped_chan = curchan;
/* Is this a valid channel? */
bitpos = 1 << (curchan & 0x07);
if (conn->chan_map[curchan >> 3] & bitpos) {
return curchan;
}
/* Calculate remap index */
remap_index = curchan % conn->chan_map_used;
return ble_ll_utils_chan_map_remap(conn->chan_map, remap_index);
}
/**
* Determine data channel index to be used for the upcoming/current
* connection event
*
* @param conn
* @param latency Used only for CSA #1
*
* @return uint8_t
*/
uint8_t
ble_ll_conn_calc_dci(struct ble_ll_conn_sm *conn, uint16_t latency)
{
uint8_t index;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CSA2)
if (conn->flags.csa2) {
return ble_ll_utils_dci_csa2(conn->event_cntr, conn->channel_id,
conn->chan_map_used, conn->chan_map);
}
#endif
index = conn->data_chan_index;
while (latency > 0) {
index = ble_ll_conn_calc_dci_csa1(conn);
latency--;
}
return index;
}
/**
* Called when we are in the connection state and the wait for response timer
* fires off.
*
* Context: Interrupt
*/
void
ble_ll_conn_wfr_timer_exp(void)
{
struct ble_ll_conn_sm *connsm;
connsm = g_ble_ll_conn_cur_sm;
ble_ll_conn_current_sm_over(connsm);
STATS_INC(ble_ll_conn_stats, wfr_expirations);
}
/**
* Callback for peripheral when it transmits a data pdu and the connection event
* ends after the transmission.
*
* Context: Interrupt
*
* @param sch
*
*/
static void
ble_ll_conn_wait_txend(void *arg)
{
struct ble_ll_conn_sm *connsm;
connsm = (struct ble_ll_conn_sm *)arg;
ble_ll_conn_current_sm_over(connsm);
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
static void
ble_ll_conn_start_rx_encrypt(void *arg)
{
struct ble_ll_conn_sm *connsm;
connsm = (struct ble_ll_conn_sm *)arg;
connsm->flags.encrypted = 1;
ble_phy_encrypt_enable(connsm->enc_data.enc_block.cipher_text);
ble_phy_encrypt_iv_set(connsm->enc_data.iv);
ble_phy_encrypt_counter_set(connsm->enc_data.rx_pkt_cntr,
!CONN_IS_CENTRAL(connsm));
}
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
static void
ble_ll_conn_start_rx_unencrypt(void *arg)
{
struct ble_ll_conn_sm *connsm;
connsm = (struct ble_ll_conn_sm *)arg;
connsm->flags.encrypted = 0;
ble_phy_encrypt_disable();
}
#endif
static void
ble_ll_conn_txend_encrypt(void *arg)
{
struct ble_ll_conn_sm *connsm;
connsm = (struct ble_ll_conn_sm *)arg;
connsm->flags.encrypted = 1;
ble_ll_conn_current_sm_over(connsm);
}
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
static void
ble_ll_conn_rxend_unencrypt(void *arg)
{
struct ble_ll_conn_sm *connsm;
connsm = (struct ble_ll_conn_sm *)arg;
connsm->flags.encrypted = 0;
ble_ll_conn_current_sm_over(connsm);
}
#endif
static void
ble_ll_conn_continue_rx_encrypt(void *arg)
{
struct ble_ll_conn_sm *connsm;
connsm = (struct ble_ll_conn_sm *)arg;
ble_phy_encrypt_counter_set(connsm->enc_data.rx_pkt_cntr,
!CONN_IS_CENTRAL(connsm));
}
#endif
/**
* Returns the cputime of the next scheduled item on the scheduler list or
* when the current connection will start its next interval (whichever is
* earlier). This API is called when determining at what time we should end
* the current connection event. The current connection event must end before
* the next scheduled item. However, the current connection itself is not
* in the scheduler list! Thus, we need to calculate the time at which the
* next connection will start (the schedule start time; not the anchor point)
* and not overrun it.
*
* Context: Interrupt
*
* @param connsm
*
* @return uint32_t
*/
static uint32_t
ble_ll_conn_get_next_sched_time(struct ble_ll_conn_sm *connsm)
{
uint32_t ce_end;
uint32_t next_sched_time;
uint8_t rem_us;
/* Calculate time at which next connection event will start */
/* NOTE: We dont care if this time is tick short. */
ce_end = connsm->anchor_point + connsm->conn_itvl_ticks -
g_ble_ll_sched_offset_ticks;
rem_us = connsm->anchor_point_usecs;
ble_ll_tmr_add_u(&ce_end, &rem_us, connsm->conn_itvl_usecs);
ce_end -= ble_ll_tmr_u2t_up(MYNEWT_VAL(BLE_LL_CONN_EVENT_END_MARGIN));
if (connsm->max_ce_len_ticks) {
if (LL_TMR_LT(connsm->anchor_point + connsm->max_ce_len_ticks, ce_end)) {
ce_end = connsm->anchor_point + connsm->max_ce_len_ticks;
}
}
if (ble_ll_sched_next_time(&next_sched_time)) {
if (LL_TMR_LT(next_sched_time, ce_end)) {
ce_end = next_sched_time;
}
}
return ce_end;
}
/**
* Called to check if certain connection state machine flags have been
* set.
*
* @param connsm
*/
static void
ble_ll_conn_chk_csm_flags(struct ble_ll_conn_sm *connsm)
{
uint8_t update_status;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
if (connsm->flags.encrypt_ltk_req) {
/*
* Send Long term key request event to host. If masked, we need to
* send a REJECT_IND.
*/
if (ble_ll_hci_ev_ltk_req(connsm)) {
ble_ll_ctrl_reject_ind_send(connsm, BLE_LL_CTRL_ENC_REQ,
BLE_ERR_PINKEY_MISSING);
}
connsm->flags.encrypt_ltk_req = 0;
}
#endif
/*
* There are two cases where this flag gets set:
* 1) A connection update procedure was started and the event counter
* has passed the instant.
* 2) We successfully sent the reject reason.
*/
if (connsm->flags.conn_update_host_w4event) {
update_status = BLE_ERR_SUCCESS;
if (IS_PENDING_CTRL_PROC(connsm, BLE_LL_CTRL_PROC_CONN_UPDATE)) {
ble_ll_ctrl_proc_stop(connsm, BLE_LL_CTRL_PROC_CONN_UPDATE);
} else {
if (IS_PENDING_CTRL_PROC(connsm, BLE_LL_CTRL_PROC_CONN_PARAM_REQ)) {
ble_ll_ctrl_proc_stop(connsm, BLE_LL_CTRL_PROC_CONN_PARAM_REQ);
update_status = connsm->reject_reason;
}
}
ble_ll_hci_ev_conn_update(connsm, update_status);
connsm->flags.conn_update_host_w4event = 0;
}
/* Check if we need to send PHY update complete event */
#if MYNEWT_VAL(BLE_LL_PHY)
if (connsm->flags.phy_update_host_w4event) {
if (!ble_ll_hci_ev_phy_update(connsm, BLE_ERR_SUCCESS)) {
/* Sent event. Clear flag */
connsm->flags.phy_update_host_w4event = 0;
}
}
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
if (connsm->flags.subrate_ind_txd) {
ble_ll_conn_subrate_set(connsm, &connsm->subrate_trans);
connsm->subrate_trans.subrate_factor = 0;
ble_ll_ctrl_proc_stop(connsm, BLE_LL_CTRL_PROC_SUBRATE_UPDATE);
connsm->flags.subrate_ind_txd = 0;
connsm->flags.subrate_host_req = 0;
}
#endif /* BLE_LL_CTRL_SUBRATE_IND */
#endif /* BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE */
}
/**
* Called when we want to send a data channel pdu inside a connection event.
*
* Context: interrupt
*
* @param connsm
*
* @return int 0: success; otherwise failure to transmit
*/
static uint16_t
ble_ll_conn_adjust_pyld_len(struct ble_ll_conn_sm *connsm, uint16_t pyld_len)
{
uint16_t max_pyld_len;
uint16_t ret;
#if MYNEWT_VAL(BLE_LL_PHY)
uint8_t phy_mode;
if (connsm->phy_tx_transition) {
phy_mode = ble_ll_phy_to_phy_mode(connsm->phy_tx_transition,
connsm->phy_data.pref_opts);
} else {
phy_mode = connsm->phy_data.tx_phy_mode;
}
max_pyld_len = ble_ll_pdu_max_tx_octets_get(connsm->eff_max_tx_time,
phy_mode);
#else
max_pyld_len = ble_ll_pdu_max_tx_octets_get(connsm->eff_max_tx_time,
BLE_PHY_MODE_1M);
#endif
ret = pyld_len;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
if (connsm->flags.encrypted) {
max_pyld_len -= BLE_LL_DATA_MIC_LEN;
}
#endif
if (ret > connsm->eff_max_tx_octets) {
ret = connsm->eff_max_tx_octets;
}
if (ret > max_pyld_len) {
ret = max_pyld_len;
}
return ret;
}
static int
ble_ll_conn_tx_pdu(struct ble_ll_conn_sm *connsm)
{
int rc;
uint8_t md;
uint8_t hdr_byte;
uint8_t end_transition;
uint8_t cur_txlen;
uint16_t next_txlen;
uint16_t cur_offset;
uint16_t pktlen;
uint32_t next_event_time;
uint32_t ticks;
struct os_mbuf *m;
struct ble_mbuf_hdr *ble_hdr;
struct os_mbuf_pkthdr *pkthdr = NULL;
struct os_mbuf_pkthdr *nextpkthdr;
struct ble_ll_empty_pdu empty_pdu;
ble_phy_tx_end_func txend_func;
int tx_phy_mode;
uint8_t llid;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
int is_ctrl;
uint8_t opcode;
#endif
/* For compiler warnings... */
ble_hdr = NULL;
m = NULL;
md = 0;
hdr_byte = BLE_LL_LLID_DATA_FRAG;
if (connsm->flags.terminate_ind_rxd) {
/* We just received terminate indication.
* Just send empty packet as an ACK
*/
connsm->flags.empty_pdu_txd = 1;
goto conn_tx_pdu;
}
/*
* We need to check if we are retrying a pdu or if there is a pdu on
* the transmit queue.
*/
pkthdr = STAILQ_FIRST(&connsm->conn_txq);
if (!connsm->cur_tx_pdu && !connsm->flags.empty_pdu_txd && !pkthdr) {
connsm->flags.empty_pdu_txd = 1;
goto conn_tx_pdu;
}
/*
* If we dont have a pdu we have previously transmitted, take it off
* the connection transmit queue
*/
cur_offset = 0;
if (!connsm->cur_tx_pdu && !connsm->flags.empty_pdu_txd) {
/* Convert packet header to mbuf */
m = OS_MBUF_PKTHDR_TO_MBUF(pkthdr);
nextpkthdr = STAILQ_NEXT(pkthdr, omp_next);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
/*
* If we are encrypting, we are only allowed to send certain
* kinds of LL control PDU's. If none is enqueued, send empty pdu!
*
* In Slave role, we are allowed to send unencrypted packets until
* LL_ENC_RSP is sent.
*/
if (((connsm->enc_data.enc_state > CONN_ENC_S_ENCRYPTED) &&
CONN_IS_CENTRAL(connsm)) ||
((connsm->enc_data.enc_state > CONN_ENC_S_ENC_RSP_TO_BE_SENT) &&
CONN_IS_PERIPHERAL(connsm))) {
if (!ble_ll_ctrl_enc_allowed_pdu_tx(pkthdr)) {
connsm->flags.empty_pdu_txd = 1;
goto conn_tx_pdu;
}
/*
* We will allow a next packet if it itself is allowed or we are
* a peripheral and we are sending the START_ENC_RSP. The central has
* to wait to receive the START_ENC_RSP from the peripheral before
* packets can be let go.
*/
if (nextpkthdr && !ble_ll_ctrl_enc_allowed_pdu_tx(nextpkthdr)
&& (CONN_IS_CENTRAL(connsm) ||
!ble_ll_ctrl_is_start_enc_rsp(m))) {
nextpkthdr = NULL;
}
}
#endif
/* Take packet off queue*/
STAILQ_REMOVE_HEAD(&connsm->conn_txq, omp_next);
ble_hdr = BLE_MBUF_HDR_PTR(m);
/*
* We dequeued new packet for transmission.
* If this is a data PDU we need to calculate payload length we can send
* over current PHY. Effectively, this determines fragmentation of packet
* into PDUs.
* If this is a control PDU we send complete PDU as only data PDU can be
* fragmented. We assume that checks (i.e. if remote supports such PDU)
* were already performed before putting packet on queue.
*/
llid = ble_hdr->txinfo.hdr_byte & BLE_LL_DATA_HDR_LLID_MASK;
pktlen = pkthdr->omp_len;
if (llid == BLE_LL_LLID_CTRL) {
cur_txlen = pktlen;
ble_ll_ctrl_tx_start(connsm, m);
} else {
cur_txlen = ble_ll_conn_adjust_pyld_len(connsm, pktlen);
}
ble_hdr->txinfo.pyld_len = cur_txlen;
/* NOTE: header was set when first enqueued */
hdr_byte = ble_hdr->txinfo.hdr_byte;
connsm->cur_tx_pdu = m;
} else {
nextpkthdr = pkthdr;
if (connsm->cur_tx_pdu) {
m = connsm->cur_tx_pdu;
ble_hdr = BLE_MBUF_HDR_PTR(m);
pktlen = OS_MBUF_PKTLEN(m);
cur_txlen = ble_hdr->txinfo.pyld_len;
cur_offset = ble_hdr->txinfo.offset;
if (cur_offset == 0) {
hdr_byte = ble_hdr->txinfo.hdr_byte & BLE_LL_DATA_HDR_LLID_MASK;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
if (connsm->enc_data.enc_state > CONN_ENC_S_ENCRYPTED) {
/* We will allow a next packet if it itself is allowed */
pkthdr = OS_MBUF_PKTHDR(connsm->cur_tx_pdu);
if (nextpkthdr && !ble_ll_ctrl_enc_allowed_pdu_tx(nextpkthdr)
&& (CONN_IS_CENTRAL(connsm) ||
!ble_ll_ctrl_is_start_enc_rsp(connsm->cur_tx_pdu))) {
nextpkthdr = NULL;
}
}
#endif
} else {
/* Empty PDU here. NOTE: header byte gets set later */
pktlen = 0;
cur_txlen = 0;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
if (connsm->enc_data.enc_state > CONN_ENC_S_ENCRYPTED) {
/* We will allow a next packet if it itself is allowed */
if (nextpkthdr && !ble_ll_ctrl_enc_allowed_pdu_tx(nextpkthdr)) {
nextpkthdr = NULL;
}
}
#endif
}
}
/*
* Set the more data data flag if we have more data to send and we
* have not been asked to terminate
*/
if (nextpkthdr || ((cur_offset + cur_txlen) < pktlen)) {
/* Get next event time */
next_event_time = ble_ll_conn_get_next_sched_time(connsm);
/* XXX: TODO: need to check this with phy update procedure. There are
limitations if we have started update */
/*
* Dont bother to set the MD bit if we cannot do the following:
* -> wait IFS, send the current frame.
* -> wait IFS, receive a maximum size frame.
* -> wait IFS, send the next frame.
* -> wait IFS, receive a maximum size frame.
*
* For peripheral:
* -> wait IFS, send current frame.
* -> wait IFS, receive maximum size frame.
* -> wait IFS, send next frame.
*/
if ((cur_offset + cur_txlen) < pktlen) {
next_txlen = pktlen - (cur_offset + cur_txlen);
} else {
next_txlen = connsm->eff_max_tx_octets;
}
if (next_txlen > connsm->eff_max_tx_octets) {
next_txlen = connsm->eff_max_tx_octets;
}
/*
* XXX: this calculation is based on using the current time
* and assuming the transmission will occur an IFS time from
* now. This is not the most accurate especially if we have
* received a frame and we are replying to it.
*/
#if MYNEWT_VAL(BLE_LL_PHY)
tx_phy_mode = connsm->phy_data.tx_phy_mode;
#else
tx_phy_mode = BLE_PHY_MODE_1M;
#endif
ticks = (BLE_LL_IFS * 3) + connsm->ota_max_rx_time +
ble_ll_pdu_us(next_txlen, tx_phy_mode) +
ble_ll_pdu_us(cur_txlen, tx_phy_mode);
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
if (connsm->conn_role == BLE_LL_CONN_ROLE_CENTRAL) {
ticks += (BLE_LL_IFS + connsm->ota_max_rx_time);
}
#endif
ticks = ble_ll_tmr_u2t(ticks);
if (LL_TMR_LT(ble_ll_tmr_get() + ticks, next_event_time)) {
md = 1;
}
}
/* If we send an empty PDU we need to initialize the header */
conn_tx_pdu:
if (connsm->flags.empty_pdu_txd) {
/*
* This looks strange, but we dont use the data pointer in the mbuf
* when we have an empty pdu.
*/
m = (struct os_mbuf *)&empty_pdu;
m->om_data = (uint8_t *)&empty_pdu;
m->om_data += BLE_MBUF_MEMBLOCK_OVERHEAD;
ble_hdr = &empty_pdu.ble_hdr;
ble_hdr->txinfo.flags = 0;
ble_hdr->txinfo.offset = 0;
ble_hdr->txinfo.pyld_len = 0;
}
/* Set tx seqnum */
if (connsm->tx_seqnum) {
hdr_byte |= BLE_LL_DATA_HDR_SN_MASK;
}
/* If we have more data, set the bit */
if (md) {
hdr_byte |= BLE_LL_DATA_HDR_MD_MASK;
}
/* Set NESN (next expected sequence number) bit */
if (connsm->next_exp_seqnum) {
hdr_byte |= BLE_LL_DATA_HDR_NESN_MASK;
}
/* Set the header byte in the outgoing frame */
ble_hdr->txinfo.hdr_byte = hdr_byte;
/*
* If we are a peripheral, check to see if this transmission will end the
* connection event. We will end the connection event if we have
* received a valid frame with the more data bit set to 0 and we dont
* have more data.
*
* XXX: for a peripheral, we dont check to see if we can:
* -> wait IFS, rx frame from central (either big or small).
* -> wait IFS, send empty pdu or next pdu.
*
* We could do this. Now, we just keep going and hope that we dont
* overrun next scheduled item.
*/
if ((connsm->flags.terminate_ind_rxd) ||
(CONN_IS_PERIPHERAL(connsm) && (md == 0) &&
(connsm->cons_rxd_bad_crc == 0) &&
((connsm->last_rxd_hdr_byte & BLE_LL_DATA_HDR_MD_MASK) == 0) &&
!ble_ll_ctrl_is_terminate_ind(hdr_byte, m->om_data[0]))) {
/* We will end the connection event */
end_transition = BLE_PHY_TRANSITION_NONE;
txend_func = ble_ll_conn_wait_txend;
} else {
/* Wait for a response here */
end_transition = BLE_PHY_TRANSITION_TX_RX;
txend_func = NULL;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
llid = ble_hdr->txinfo.hdr_byte & BLE_LL_DATA_HDR_LLID_MASK;
if (llid == BLE_LL_LLID_CTRL) {
is_ctrl = 1;
opcode = m->om_data[0];
} else {
is_ctrl = 0;
opcode = 0;
}
if (is_ctrl && (opcode == BLE_LL_CTRL_START_ENC_RSP)) {
/*
* Both central and peripheral send the START_ENC_RSP encrypted and receive
* encrypted
*/
connsm->flags.encrypted = 1;
connsm->enc_data.tx_encrypted = 1;
ble_phy_encrypt_enable(connsm->enc_data.enc_block.cipher_text);
ble_phy_encrypt_iv_set(connsm->enc_data.iv);
ble_phy_encrypt_counter_set(connsm->enc_data.tx_pkt_cntr,
CONN_IS_CENTRAL(connsm));
if (txend_func == NULL) {
txend_func = ble_ll_conn_continue_rx_encrypt;
}
} else if (is_ctrl && (opcode == BLE_LL_CTRL_START_ENC_REQ)) {
/*
* Only the peripheral sends this and it gets sent unencrypted but
* we receive encrypted
*/
connsm->flags.encrypted = 0;
connsm->enc_data.enc_state = CONN_ENC_S_START_ENC_RSP_WAIT;
connsm->enc_data.tx_encrypted = 0;
ble_phy_encrypt_disable();
if (txend_func == NULL) {
txend_func = ble_ll_conn_start_rx_encrypt;
} else {
txend_func = ble_ll_conn_txend_encrypt;
}
} else if (is_ctrl && (opcode == BLE_LL_CTRL_PAUSE_ENC_RSP)) {
/*
* The peripheral sends the PAUSE_ENC_RSP encrypted. The central sends
* it unencrypted (note that link was already set unencrypted).
*/
switch (connsm->conn_role) {
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
case BLE_LL_CONN_ROLE_CENTRAL:
connsm->flags.encrypted = 0;
connsm->enc_data.enc_state = CONN_ENC_S_PAUSED;
connsm->enc_data.tx_encrypted = 0;
ble_phy_encrypt_disable();
break;
#endif
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
case BLE_LL_CONN_ROLE_PERIPHERAL:
connsm->flags.encrypted = 1;
connsm->enc_data.tx_encrypted = 1;
ble_phy_encrypt_enable(connsm->enc_data.enc_block.cipher_text);
ble_phy_encrypt_iv_set(connsm->enc_data.iv);
ble_phy_encrypt_counter_set(connsm->enc_data.tx_pkt_cntr,
CONN_IS_CENTRAL(connsm));
if (txend_func == NULL) {
txend_func = ble_ll_conn_start_rx_unencrypt;
} else {
txend_func = ble_ll_conn_rxend_unencrypt;
}
break;
#endif
default:
BLE_LL_ASSERT(0);
break;
}
} else {
/* If encrypted set packet counter */
if (connsm->flags.encrypted) {
connsm->enc_data.tx_encrypted = 1;
ble_phy_encrypt_counter_set(connsm->enc_data.tx_pkt_cntr,
CONN_IS_CENTRAL(connsm));
if (txend_func == NULL) {
txend_func = ble_ll_conn_continue_rx_encrypt;
}
}
}
#endif
/* Set transmit end callback */
ble_phy_set_txend_cb(txend_func, connsm);
rc = ble_phy_tx(ble_ll_tx_mbuf_pducb, m, end_transition);
if (!rc) {
/* Log transmit on connection state */
cur_txlen = ble_hdr->txinfo.pyld_len;
ble_ll_trace_u32x2(BLE_LL_TRACE_ID_CONN_TX, cur_txlen,
ble_hdr->txinfo.offset);
/* Set last transmitted MD bit */
connsm->flags.last_txd_md = md;
/* Increment packets transmitted */
if (connsm->flags.empty_pdu_txd) {
if (connsm->flags.terminate_ind_rxd) {
connsm->flags.terminate_ind_rxd_acked = 1;
}
STATS_INC(ble_ll_conn_stats, tx_empty_pdus);
} else if ((hdr_byte & BLE_LL_DATA_HDR_LLID_MASK) == BLE_LL_LLID_CTRL) {
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
connsm->has_nonempty_pdu = 1;
#endif
STATS_INC(ble_ll_conn_stats, tx_ctrl_pdus);
STATS_INCN(ble_ll_conn_stats, tx_ctrl_bytes, cur_txlen);
} else {
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
connsm->has_nonempty_pdu = 1;
#endif
STATS_INC(ble_ll_conn_stats, tx_l2cap_pdus);
STATS_INCN(ble_ll_conn_stats, tx_l2cap_bytes, cur_txlen);
}
}
return rc;
}
/**
* Schedule callback for start of connection event.
*
* Context: Interrupt
*
* @param sch
*
* @return int 0: scheduled item is still running. 1: schedule item is done.
*/
static int
ble_ll_conn_event_start_cb(struct ble_ll_sched_item *sch)
{
int rc = 0;
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
uint32_t usecs;
#endif
uint32_t start;
struct ble_ll_conn_sm *connsm;
/* XXX: note that we can extend end time here if we want. Look at this */
/* Set current connection state machine */
connsm = (struct ble_ll_conn_sm *)sch->cb_arg;
g_ble_ll_conn_cur_sm = connsm;
BLE_LL_ASSERT(connsm);
/* In rare cases 1st connection event is fired before LL finished processing
* new connection. In such case just skip this connection event and LL will
* reschedule to next connection event.
*/
if (connsm->conn_state == BLE_LL_CONN_STATE_IDLE) {
ble_ll_conn_current_sm_over(connsm);
return BLE_LL_SCHED_STATE_DONE;
}
/* Log connection event start */
ble_ll_trace_u32(BLE_LL_TRACE_ID_CONN_EV_START, connsm->conn_handle);
/* Disable whitelisting as connections do not use it */
ble_ll_whitelist_disable();
/* Set LL state */
ble_ll_state_set(BLE_LL_STATE_CONNECTION);
/* Set channel */
ble_phy_setchan(connsm->data_chan_index, connsm->access_addr,
connsm->crcinit);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
ble_phy_resolv_list_disable();
#endif
#if MYNEWT_VAL(BLE_LL_PHY)
ble_phy_mode_set(connsm->phy_data.tx_phy_mode, connsm->phy_data.rx_phy_mode);
#endif
/* Set the power */
ble_ll_tx_power_set(g_ble_ll_tx_power);
switch (connsm->conn_role) {
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
case BLE_LL_CONN_ROLE_CENTRAL:
/* Set start time of transmission */
start = sch->start_time + g_ble_ll_sched_offset_ticks;
rc = ble_phy_tx_set_start_time(start, sch->remainder);
if (!rc) {
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
if (connsm->flags.encrypted) {
ble_phy_encrypt_enable(connsm->enc_data.enc_block.cipher_text);
ble_phy_encrypt_iv_set(connsm->enc_data.iv);
ble_phy_encrypt_counter_set(connsm->enc_data.tx_pkt_cntr, 1);
} else {
ble_phy_encrypt_disable();
}
#endif
rc = ble_ll_conn_tx_pdu(connsm);
if (!rc) {
rc = BLE_LL_SCHED_STATE_RUNNING;
} else {
/* Inform LL task of connection event end */
rc = BLE_LL_SCHED_STATE_DONE;
}
} else {
STATS_INC(ble_ll_conn_stats, conn_ev_late);
rc = BLE_LL_SCHED_STATE_DONE;
}
break;
#endif
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
case BLE_LL_CONN_ROLE_PERIPHERAL:
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
if (connsm->flags.encrypted) {
ble_phy_encrypt_enable(connsm->enc_data.enc_block.cipher_text);
ble_phy_encrypt_iv_set(connsm->enc_data.iv);
ble_phy_encrypt_counter_set(connsm->enc_data.rx_pkt_cntr, 1);
} else {
ble_phy_encrypt_disable();
}
#endif
/* XXX: what is this really for the peripheral? */
start = sch->start_time + g_ble_ll_sched_offset_ticks;
rc = ble_phy_rx_set_start_time(start, sch->remainder);
if (rc) {
/* End the connection event as we have no more buffers */
STATS_INC(ble_ll_conn_stats, periph_ce_failures);
rc = BLE_LL_SCHED_STATE_DONE;
} else {
/*
* Set flag that tells peripheral to set last anchor point if a packet
* has been received.
*/
connsm->flags.periph_set_last_anchor = 1;
/*
* Set the wait for response time. The anchor point is when we
* expect the central to start transmitting. Worst-case, we expect
* to hear a reply within the anchor point plus:
* -> current tx window size
* -> current window widening amount (includes +/- 16 usec jitter)
* -> Amount of time it takes to detect packet start.
* -> Some extra time (16 usec) to insure timing is OK
*/
/*
* For the 32 kHz crystal, the amount of usecs we have to wait
* is not from the anchor point; we have to account for the time
* from when the receiver is enabled until the anchor point. The
* time we start before the anchor point is this:
* -> current window widening.
* -> up to one 32 kHz tick since we discard remainder.
* -> Up to one tick since the usecs to ticks calc can be off
* by up to one tick.
* NOTES:
* 1) the 61 we add is for the two ticks mentioned above.
* 2) The address rx time and jitter is accounted for in the
* phy function
*/
usecs = connsm->periph_cur_tx_win_usecs + 61 +
(2 * connsm->periph_cur_window_widening);
ble_phy_wfr_enable(BLE_PHY_WFR_ENABLE_RX, 0, usecs);
/* Set next wakeup time to connection event end time */
rc = BLE_LL_SCHED_STATE_RUNNING;
}
break;
#endif
default:
BLE_LL_ASSERT(0);
break;
}
if (rc == BLE_LL_SCHED_STATE_DONE) {
ble_ll_conn_current_sm_over(connsm);
}
/* Set time that we last serviced the schedule */
connsm->last_scheduled = ble_ll_tmr_get();
return rc;
}
/**
* Called to determine if the device is allowed to send the next pdu in the
* connection event. This will always return 'true' if we are a peripheral. If we
* are a central, we must be able to send the next fragment and get a minimum
* sized response from the peripheral.
*
* Context: Interrupt context (rx end isr).
*
* @param connsm
* @param begtime Time at which IFS before pdu transmission starts
*
* @return int 0: not allowed to send 1: allowed to send
*/
static int
ble_ll_conn_can_send_next_pdu(struct ble_ll_conn_sm *connsm, uint32_t begtime,
uint32_t add_usecs)
{
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
int rc;
uint16_t rem_bytes;
uint32_t ticks;
uint32_t usecs;
uint32_t next_sched_time;
struct os_mbuf *txpdu;
struct os_mbuf_pkthdr *pkthdr;
struct ble_mbuf_hdr *txhdr;
uint32_t allowed_usecs;
int tx_phy_mode;
#if MYNEWT_VAL(BLE_LL_PHY)
tx_phy_mode = connsm->phy_data.tx_phy_mode;
#else
tx_phy_mode = BLE_PHY_MODE_1M;
#endif
rc = 1;
if (connsm->conn_role == BLE_LL_CONN_ROLE_CENTRAL) {
/* Get next scheduled item time */
next_sched_time = ble_ll_conn_get_next_sched_time(connsm);
txpdu = connsm->cur_tx_pdu;
if (!txpdu) {
pkthdr = STAILQ_FIRST(&connsm->conn_txq);
if (pkthdr) {
txpdu = OS_MBUF_PKTHDR_TO_MBUF(pkthdr);
}
} else {
pkthdr = OS_MBUF_PKTHDR(txpdu);
}
/* XXX: TODO: need to check this with phy update procedure. There are
limitations if we have started update */
if (txpdu) {
txhdr = BLE_MBUF_HDR_PTR(txpdu);
rem_bytes = pkthdr->omp_len - txhdr->txinfo.offset;
if (rem_bytes > connsm->eff_max_tx_octets) {
rem_bytes = connsm->eff_max_tx_octets;
}
usecs = ble_ll_pdu_us(rem_bytes, tx_phy_mode);
} else {
/* We will send empty pdu (just a LL header) */
usecs = ble_ll_pdu_us(0, tx_phy_mode);
}
usecs += (BLE_LL_IFS * 2) + connsm->ota_max_rx_time;
ticks = (uint32_t)(next_sched_time - begtime);
allowed_usecs = ble_ll_tmr_t2u(ticks);
if ((usecs + add_usecs) >= allowed_usecs) {
rc = 0;
}
}
return rc;
#else
return 1;
#endif
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_PING)
/**
* Callback for the Authenticated payload timer. This function is called
* when the authenticated payload timer expires. When the authenticated
* payload timeout expires, we should
* -> Send the authenticated payload timeout event.
* -> Start the LE ping procedure.
* -> Restart the timer.
*
* @param arg
*/
void
ble_ll_conn_auth_pyld_timer_cb(struct ble_npl_event *ev)
{
struct ble_ll_conn_sm *connsm;
connsm = (struct ble_ll_conn_sm *)ble_npl_event_get_arg(ev);
ble_ll_auth_pyld_tmo_event_send(connsm);
ble_ll_ctrl_proc_start(connsm, BLE_LL_CTRL_PROC_LE_PING, NULL);
ble_ll_conn_auth_pyld_timer_start(connsm);
}
/**
* Start (or restart) the authenticated payload timer
*
* @param connsm
*/
void
ble_ll_conn_auth_pyld_timer_start(struct ble_ll_conn_sm *connsm)
{
int32_t tmo;
/* Timeout in is in 10 msec units */
tmo = (int32_t)BLE_LL_CONN_AUTH_PYLD_OS_TMO(connsm->auth_pyld_tmo);
ble_npl_callout_reset(&connsm->auth_pyld_timer, tmo);
}
#endif
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
static void
ble_ll_conn_central_common_init(struct ble_ll_conn_sm *connsm)
{
/* Set central role */
connsm->conn_role = BLE_LL_CONN_ROLE_CENTRAL;
/* Set default ce parameters */
/*
* XXX: for now, we need twice the transmit window as our calculations
* for the transmit window offset could be off.
*/
connsm->tx_win_size = BLE_LL_CONN_TX_WIN_MIN + 1;
connsm->tx_win_off = 0;
connsm->central_sca = BLE_LL_SCA_ENUM;
/* Hop increment is a random value between 5 and 16. */
connsm->hop_inc = (ble_ll_rand() % 12) + 5;
/* Set channel map to map requested by host */
connsm->chan_map_used = g_ble_ll_data.chan_map_used;
memcpy(connsm->chan_map, g_ble_ll_data.chan_map, BLE_LL_CHAN_MAP_LEN);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
connsm->acc_subrate_min = g_ble_ll_conn_params.acc_subrate_min;
connsm->acc_subrate_max = g_ble_ll_conn_params.acc_subrate_max;
connsm->acc_max_latency = g_ble_ll_conn_params.acc_max_latency;
connsm->acc_cont_num = g_ble_ll_conn_params.acc_cont_num;
connsm->acc_supervision_tmo = g_ble_ll_conn_params.acc_supervision_tmo;
#endif
/* Calculate random access address and crc initialization value */
connsm->access_addr = ble_ll_utils_calc_aa();
connsm->crcinit = ble_ll_rand() & 0xffffff;
/* Set initial schedule callback */
connsm->conn_sch.sched_cb = ble_ll_conn_event_start_cb;
}
/**
* Called when a create connection command has been received. This initializes
* a connection state machine in the central role.
*
* NOTE: Must be called before the state machine is started
*
* @param connsm
* @param hcc
*/
void
ble_ll_conn_central_init(struct ble_ll_conn_sm *connsm,
struct ble_ll_conn_create_scan *cc_scan,
struct ble_ll_conn_create_params *cc_params)
{
ble_ll_conn_central_common_init(connsm);
connsm->own_addr_type = cc_scan->own_addr_type;
memcpy(&connsm->peer_addr, &cc_scan->peer_addr, BLE_DEV_ADDR_LEN);
connsm->peer_addr_type = cc_scan->peer_addr_type;
connsm->conn_itvl = cc_params->conn_itvl;
connsm->conn_itvl_ticks = cc_params->conn_itvl_ticks;
connsm->conn_itvl_usecs = cc_params->conn_itvl_usecs;
connsm->periph_latency = cc_params->conn_latency;
connsm->supervision_tmo = cc_params->supervision_timeout;
connsm->max_ce_len_ticks = ble_ll_tmr_u2t_up(cc_params->max_ce_len * BLE_LL_CONN_CE_USECS);
}
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_DATA_LEN_EXT)
int
ble_ll_conn_set_data_len(struct ble_ll_conn_sm *connsm,
uint16_t tx_octets, uint16_t tx_time,
uint16_t rx_octets, uint16_t rx_time)
{
int init_dle = 0;
/* Note: octets/time shall be checked by caller! */
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY)
/* Keep original values requested by host since we may want to recalculate
* after PHY changes between coded and uncoded.
*/
connsm->host_req_max_tx_time = tx_time;
connsm->host_req_max_rx_time = rx_time;
/* If peer does not support coded, we cannot use value larger than 2120us */
if (!ble_ll_conn_rem_feature_check(connsm, BLE_LL_FEAT_LE_CODED_PHY)) {
tx_time = MIN(tx_time, BLE_LL_CONN_SUPP_TIME_MAX_UNCODED);
rx_time = MIN(rx_time, BLE_LL_CONN_SUPP_TIME_MAX_UNCODED);
}
#endif
if (connsm->max_tx_time != tx_time) {
connsm->max_tx_time = tx_time;
init_dle = 1;
}
if (connsm->max_tx_octets != tx_octets) {
connsm->max_tx_octets = tx_octets;
init_dle = 1;
}
if (rx_time && (connsm->max_rx_time != rx_time)) {
connsm->max_rx_time = rx_time;
init_dle = 1;
}
if (rx_octets && (connsm->max_rx_octets != rx_octets)) {
connsm->max_rx_octets = rx_octets;
init_dle = 1;
}
if (init_dle) {
ble_ll_ctrl_initiate_dle(connsm, false);
}
return 0;
}
#endif
#if MYNEWT_VAL(BLE_LL_PHY)
static void
ble_ll_conn_set_phy(struct ble_ll_conn_sm *connsm, int tx_phy, int rx_phy)
{
struct ble_ll_conn_phy_data *phy_data = &connsm->phy_data;
phy_data->rx_phy_mode = ble_ll_phy_to_phy_mode(rx_phy,
BLE_HCI_LE_PHY_CODED_ANY);
phy_data->cur_rx_phy = rx_phy;
phy_data->tx_phy_mode = ble_ll_phy_to_phy_mode(tx_phy,
BLE_HCI_LE_PHY_CODED_ANY);
phy_data->cur_tx_phy = tx_phy;
}
static void
ble_ll_conn_init_phy(struct ble_ll_conn_sm *connsm, int phy)
{
struct ble_ll_conn_global_params *conngp;
/* Always initialize symmetric PHY - controller can change this later */
ble_ll_conn_set_phy(connsm, phy, phy);
/* Update data length management to match initial PHY */
conngp = &g_ble_ll_conn_params;
connsm->max_tx_octets = conngp->conn_init_max_tx_octets;
connsm->max_rx_octets = conngp->supp_max_rx_octets;
if (phy == BLE_PHY_CODED) {
connsm->max_tx_time = conngp->conn_init_max_tx_time_coded;
connsm->max_rx_time = BLE_LL_CONN_SUPP_TIME_MAX_CODED;
connsm->rem_max_tx_time = BLE_LL_CONN_SUPP_TIME_MIN_CODED;
connsm->rem_max_rx_time = BLE_LL_CONN_SUPP_TIME_MIN_CODED;
/* Assume peer does support coded */
ble_ll_conn_rem_feature_add(connsm, BLE_LL_FEAT_LE_CODED_PHY);
} else {
connsm->max_tx_time = conngp->conn_init_max_tx_time_uncoded;
connsm->max_rx_time = BLE_LL_CONN_SUPP_TIME_MAX_UNCODED;
connsm->rem_max_tx_time = BLE_LL_CONN_SUPP_TIME_MIN_UNCODED;
connsm->rem_max_rx_time = BLE_LL_CONN_SUPP_TIME_MIN_UNCODED;
}
connsm->eff_max_tx_time = connsm->rem_max_tx_time;
connsm->eff_max_rx_time = connsm->rem_max_rx_time;
connsm->rem_max_tx_octets = BLE_LL_CONN_SUPP_BYTES_MIN;
connsm->rem_max_rx_octets = BLE_LL_CONN_SUPP_BYTES_MIN;
connsm->eff_max_tx_octets = BLE_LL_CONN_SUPP_BYTES_MIN;
connsm->eff_max_rx_octets = BLE_LL_CONN_SUPP_BYTES_MIN;
}
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
static void
ble_ll_conn_create_set_params(struct ble_ll_conn_sm *connsm, uint8_t phy)
{
struct ble_ll_conn_create_params *cc_params;
cc_params = &g_ble_ll_conn_create_sm.params[phy - 1];
connsm->periph_latency = cc_params->conn_latency;
connsm->supervision_tmo = cc_params->supervision_timeout;
connsm->conn_itvl = cc_params->conn_itvl;
connsm->conn_itvl_ticks = cc_params->conn_itvl_ticks;
connsm->conn_itvl_usecs = cc_params->conn_itvl_usecs;
}
#endif
#endif
static void
ble_ll_conn_set_csa(struct ble_ll_conn_sm *connsm, bool chsel)
{
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CSA2)
if (chsel) {
connsm->flags.csa2 = 1;
connsm->channel_id = ((connsm->access_addr & 0xffff0000) >> 16) ^
(connsm->access_addr & 0x0000ffff);
/* calculate the next data channel */
connsm->data_chan_index = ble_ll_conn_calc_dci(connsm, 0);
return;
}
#endif
connsm->last_unmapped_chan = 0;
/* calculate the next data channel */
connsm->data_chan_index = ble_ll_conn_calc_dci(connsm, 1);
}
/**
* Create a new connection state machine. This is done once per
* connection when the HCI command "create connection" is issued to the
* controller or when a peripheral receives a connect request.
*
* Context: Link Layer task
*
* @param connsm
*/
void
ble_ll_conn_sm_new(struct ble_ll_conn_sm *connsm)
{
struct ble_ll_conn_global_params *conn_params;
/* Reset following elements */
memset(&connsm->flags, 0, sizeof(connsm->flags));
connsm->event_cntr = 0;
connsm->conn_state = BLE_LL_CONN_STATE_IDLE;
connsm->disconnect_reason = 0;
connsm->rxd_disconnect_reason = 0;
connsm->conn_features = BLE_LL_CONN_INITIAL_FEATURES;
memset(connsm->remote_features, 0, sizeof(connsm->remote_features));
connsm->vers_nr = 0;
connsm->comp_id = 0;
connsm->sub_vers_nr = 0;
connsm->reject_reason = BLE_ERR_SUCCESS;
connsm->conn_rssi = BLE_LL_CONN_UNKNOWN_RSSI;
connsm->inita_identity_used = 0;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
connsm->subrate_base_event = 0;
connsm->subrate_factor = 1;
connsm->cont_num = 0;
connsm->cont_num_left = 0;
connsm->has_nonempty_pdu = 0;
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV_SYNC_TRANSFER)
connsm->sync_transfer_sync_timeout = g_ble_ll_conn_sync_transfer_params.sync_timeout_us;
connsm->sync_transfer_mode = g_ble_ll_conn_sync_transfer_params.mode;
connsm->sync_transfer_skip = g_ble_ll_conn_sync_transfer_params.max_skip;
#endif
/* XXX: TODO set these based on PHY that started connection */
#if MYNEWT_VAL(BLE_LL_PHY)
connsm->phy_data.cur_tx_phy = BLE_PHY_1M;
connsm->phy_data.cur_rx_phy = BLE_PHY_1M;
connsm->phy_data.tx_phy_mode = BLE_PHY_MODE_1M;
connsm->phy_data.rx_phy_mode = BLE_PHY_MODE_1M;
connsm->phy_data.pref_mask_tx_req = 0;
connsm->phy_data.pref_mask_rx_req = 0;
connsm->phy_data.pref_mask_tx = g_ble_ll_data.ll_pref_tx_phys;
connsm->phy_data.pref_mask_rx = g_ble_ll_data.ll_pref_rx_phys;
connsm->phy_data.pref_opts = 0;
connsm->phy_tx_transition = 0;
#endif
/* Reset current control procedure */
connsm->cur_ctrl_proc = BLE_LL_CTRL_PROC_IDLE;
connsm->pending_ctrl_procs = 0;
/*
* Set handle in connection update procedure to 0. If the handle
* is non-zero it means that the host initiated the connection
* parameter update request and the rest of the parameters are valid.
*/
connsm->conn_param_req.handle = 0;
/* Connection end event */
ble_npl_event_init(&connsm->conn_ev_end, ble_ll_conn_event_end, connsm);
/* Initialize transmit queue and ack/flow control elements */
STAILQ_INIT(&connsm->conn_txq);
connsm->cur_tx_pdu = NULL;
connsm->tx_seqnum = 0;
connsm->next_exp_seqnum = 0;
connsm->cons_rxd_bad_crc = 0;
connsm->last_rxd_sn = 1;
connsm->completed_pkts = 0;
/* initialize data length mgmt */
conn_params = &g_ble_ll_conn_params;
connsm->max_tx_octets = conn_params->conn_init_max_tx_octets;
connsm->max_rx_octets = conn_params->supp_max_rx_octets;
connsm->max_tx_time = conn_params->conn_init_max_tx_time;
connsm->max_rx_time = conn_params->supp_max_rx_time;
connsm->rem_max_tx_time = BLE_LL_CONN_SUPP_TIME_MIN;
connsm->rem_max_rx_time = BLE_LL_CONN_SUPP_TIME_MIN;
connsm->eff_max_tx_time = BLE_LL_CONN_SUPP_TIME_MIN;
connsm->eff_max_rx_time = BLE_LL_CONN_SUPP_TIME_MIN;
connsm->ota_max_rx_time = BLE_LL_CONN_SUPP_TIME_MIN;
connsm->rem_max_tx_octets = BLE_LL_CONN_SUPP_BYTES_MIN;
connsm->rem_max_rx_octets = BLE_LL_CONN_SUPP_BYTES_MIN;
connsm->eff_max_tx_octets = BLE_LL_CONN_SUPP_BYTES_MIN;
connsm->eff_max_rx_octets = BLE_LL_CONN_SUPP_BYTES_MIN;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY)
connsm->host_req_max_tx_time = 0;
connsm->host_req_max_rx_time = 0;
#endif
/* Reset encryption data */
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
memset(&connsm->enc_data, 0, sizeof(struct ble_ll_conn_enc_data));
connsm->enc_data.enc_state = CONN_ENC_S_UNENCRYPTED;
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_PING)
connsm->auth_pyld_tmo = BLE_LL_CONN_DEF_AUTH_PYLD_TMO;
connsm->flags.le_ping_supp = 1;
#endif
/* Add to list of active connections */
SLIST_INSERT_HEAD(&g_ble_ll_conn_active_list, connsm, act_sle);
#if MYNEWT_VAL(BLE_LL_CONN_STRICT_SCHED)
if (ble_ll_sched_css_is_enabled() &&
(connsm->conn_role == BLE_LL_CONN_ROLE_CENTRAL)) {
ble_ll_conn_css_update_list(connsm);
}
#endif
}
void
ble_ll_conn_update_eff_data_len(struct ble_ll_conn_sm *connsm)
{
int ota_max_rx_time_calc = 0;
int send_event;
uint16_t eff_time;
uint16_t eff_bytes;
uint16_t ota_time;
uint8_t phy_mode;
/* Assume no event sent */
send_event = 0;
/* See if effective times have changed */
eff_time = MIN(connsm->rem_max_tx_time, connsm->max_rx_time);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY)
if (connsm->phy_data.cur_rx_phy == BLE_PHY_CODED) {
eff_time = MAX(eff_time, BLE_LL_CONN_SUPP_TIME_MIN_CODED);
}
#endif
if (eff_time != connsm->eff_max_rx_time) {
connsm->eff_max_rx_time = eff_time;
ota_max_rx_time_calc = 1;
send_event = 1;
}
eff_time = MIN(connsm->rem_max_rx_time, connsm->max_tx_time);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY)
if (connsm->phy_data.cur_tx_phy == BLE_PHY_CODED) {
eff_time = MAX(eff_time, BLE_LL_CONN_SUPP_TIME_MIN_CODED);
}
#endif
if (eff_time != connsm->eff_max_tx_time) {
connsm->eff_max_tx_time = eff_time;
send_event = 1;
}
eff_bytes = MIN(connsm->rem_max_tx_octets, connsm->max_rx_octets);
if (eff_bytes != connsm->eff_max_rx_octets) {
connsm->eff_max_rx_octets = eff_bytes;
ota_max_rx_time_calc = 1;
send_event = 1;
}
eff_bytes = MIN(connsm->rem_max_rx_octets, connsm->max_tx_octets);
if (eff_bytes != connsm->eff_max_tx_octets) {
connsm->eff_max_tx_octets = eff_bytes;
send_event = 1;
}
/* If effective rx octets and/or time value changes, we need to calculate
* actual OTA max rx time, i.e. lesser of effective max rx time and rx time
* of PDU containing max rx octets of payload. This is then used to calculate
* connection events timings.
*/
if (ota_max_rx_time_calc) {
#if MYNEWT_VAL(BLE_LL_PHY)
phy_mode = ble_ll_phy_to_phy_mode(connsm->phy_data.cur_rx_phy,
BLE_HCI_LE_PHY_CODED_S8_PREF);
#else
phy_mode = BLE_PHY_MODE_1M;
#endif
ota_time = ble_ll_pdu_us(connsm->eff_max_rx_octets, phy_mode);
connsm->ota_max_rx_time = MIN(ota_time, connsm->eff_max_rx_time);
}
if (send_event) {
ble_ll_hci_ev_datalen_chg(connsm);
}
}
/**
* Called when a connection is terminated
*
* Context: Link Layer task.
*
* @param connsm
* @param ble_err
*/
void
ble_ll_conn_end(struct ble_ll_conn_sm *connsm, uint8_t ble_err)
{
struct os_mbuf *m;
struct os_mbuf_pkthdr *pkthdr;
os_sr_t sr;
/* Remove scheduler events just in case */
ble_ll_sched_rmv_elem(&connsm->conn_sch);
/* In case of the supervision timeout we shall make sure
* that there is no ongoing connection event. It could happen
* because we scheduled connection event before checking connection timeout.
* If connection event managed to start, let us drop it.
*/
OS_ENTER_CRITICAL(sr);
if (g_ble_ll_conn_cur_sm == connsm) {
ble_ll_conn_halt();
STATS_INC(ble_ll_conn_stats, conn_event_while_tmo);
}
OS_EXIT_CRITICAL(sr);
/* Stop any control procedures that might be running */
ble_npl_callout_stop(&connsm->ctrl_proc_rsp_timer);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_PING)
ble_npl_callout_stop(&connsm->auth_pyld_timer);
#endif
/* Remove from the active connection list */
SLIST_REMOVE(&g_ble_ll_conn_active_list, connsm, ble_ll_conn_sm, act_sle);
#if MYNEWT_VAL(BLE_LL_CONN_STRICT_SCHED)
if (ble_ll_sched_css_is_enabled() &&
(connsm->conn_role == BLE_LL_CONN_ROLE_CENTRAL)) {
/* If current connection was reference for CSS, we need to find another
* one. It does not matter which one we'll pick.
*/
OS_ENTER_CRITICAL(sr);
SLIST_REMOVE(&g_ble_ll_conn_css_list, connsm, ble_ll_conn_sm, css_sle);
if (connsm == g_ble_ll_conn_css_ref) {
g_ble_ll_conn_css_ref = SLIST_FIRST(&g_ble_ll_conn_css_list);
}
OS_EXIT_CRITICAL(sr);
}
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_CTRL_TO_HOST_FLOW_CONTROL)
ble_ll_conn_cth_flow_free_credit(connsm, connsm->cth_flow_pending);
#endif
/* Free the current transmit pdu if there is one. */
if (connsm->cur_tx_pdu) {
os_mbuf_free_chain(connsm->cur_tx_pdu);
connsm->cur_tx_pdu = NULL;
}
/* Free all packets on transmit queue */
while (1) {
/* Get mbuf pointer from packet header pointer */
pkthdr = STAILQ_FIRST(&connsm->conn_txq);
if (!pkthdr) {
break;
}
STAILQ_REMOVE_HEAD(&connsm->conn_txq, omp_next);
m = (struct os_mbuf *)((uint8_t *)pkthdr - sizeof(struct os_mbuf));
os_mbuf_free_chain(m);
}
/* Make sure events off queue */
ble_ll_event_remove(&connsm->conn_ev_end);
/* Connection state machine is now idle */
connsm->conn_state = BLE_LL_CONN_STATE_IDLE;
/*
* If we have features and there's pending HCI command, send an event before
* disconnection event so it does make sense to host.
*/
if (connsm->flags.features_host_req &&
connsm->flags.features_rxd) {
ble_ll_hci_ev_rd_rem_used_feat(connsm, BLE_ERR_SUCCESS);
connsm->flags.features_host_req = 0;
}
/*
* If there is still pending read features request HCI command, send an
* event to complete it.
*/
if (connsm->flags.features_host_req) {
ble_ll_hci_ev_rd_rem_used_feat(connsm, ble_err);
connsm->flags.features_host_req = 0;
}
/*
* We need to send a disconnection complete event. Connection Complete for
* canceling connection creation is sent from LE Create Connection Cancel
* Command handler.
*
* If the ble error is "success" it means that the reset command was
* received and we should not send an event.
*/
if (ble_err && (ble_err != BLE_ERR_UNK_CONN_ID ||
connsm->flags.terminate_ind_rxd)) {
ble_ll_disconn_comp_event_send(connsm, ble_err);
}
/* Put connection state machine back on free list */
STAILQ_INSERT_TAIL(&g_ble_ll_conn_free_list, connsm, free_stqe);
/* Log connection end */
ble_ll_trace_u32x3(BLE_LL_TRACE_ID_CONN_END, connsm->conn_handle,
connsm->event_cntr, (uint32_t)ble_err);
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV_SYNC_TRANSFER)
void
ble_ll_conn_get_anchor(struct ble_ll_conn_sm *connsm, uint16_t conn_event,
uint32_t *anchor, uint8_t *anchor_usecs)
{
uint32_t itvl;
itvl = (connsm->conn_itvl * BLE_LL_CONN_ITVL_USECS);
*anchor = connsm->anchor_point;
*anchor_usecs = connsm->anchor_point_usecs;
if ((int16_t)(conn_event - connsm->event_cntr) < 0) {
itvl *= connsm->event_cntr - conn_event;
ble_ll_tmr_sub(anchor, anchor_usecs, itvl);
} else {
itvl *= conn_event - connsm->event_cntr;
ble_ll_tmr_add(anchor, anchor_usecs, itvl);
}
}
#endif
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
int
ble_ll_conn_move_anchor(struct ble_ll_conn_sm *connsm, uint16_t offset)
{
BLE_LL_ASSERT(connsm->conn_role == BLE_LL_CONN_ROLE_CENTRAL);
if (IS_PENDING_CTRL_PROC(connsm, BLE_LL_CTRL_PROC_CONN_PARAM_REQ) ||
IS_PENDING_CTRL_PROC(connsm, BLE_LL_CTRL_PROC_CONN_UPDATE)) {
return -1;
}
connsm->conn_update_anchor_offset_req = offset;
ble_ll_ctrl_proc_start(connsm, BLE_LL_CTRL_PROC_CONN_UPDATE, NULL);
return 0;
}
#endif
/**
* Called to move to the next connection event.
*
* Context: Link Layer task.
*
* @param connsm
*
* @return int
*/
static int
ble_ll_conn_next_event(struct ble_ll_conn_sm *connsm)
{
uint32_t conn_itvl_us;
uint32_t ce_duration;
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
uint32_t cur_ww;
uint32_t max_ww;
#endif
struct ble_ll_conn_upd_req *upd;
uint8_t skip_anchor_calc = 0;
uint32_t usecs;
uint8_t use_periph_latency;
uint16_t base_event_cntr;
uint16_t next_event_cntr;
uint8_t next_is_subrated;
uint16_t subrate_factor;
uint16_t event_cntr_diff;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
struct ble_ll_conn_subrate_params *cstp;
uint16_t trans_next_event_cntr;
uint16_t subrate_conn_upd_event_cntr;
#endif
#if MYNEWT_VAL(BLE_LL_CONN_STRICT_SCHED)
uint8_t anchor_calc_for_css = 0;
#endif
/* XXX: deal with connection request procedure here as well */
ble_ll_conn_chk_csm_flags(connsm);
/* If unable to start terminate procedure, start it now */
if (connsm->disconnect_reason && !connsm->flags.terminate_started) {
ble_ll_ctrl_terminate_start(connsm);
}
if (connsm->flags.terminate_started && CONN_IS_PERIPHERAL(connsm)) {
/* Some of the devices waits whole connection interval to ACK our
* TERMINATE_IND sent as a Slave. Since we are here it means we are still waiting for ACK.
* Make sure we catch it in next connection event.
*/
connsm->periph_latency = 0;
}
next_is_subrated = 1;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
base_event_cntr = connsm->subrate_base_event;
subrate_factor = connsm->subrate_factor;
/* We need to restore remaining continuation events counter if a non-empty
* PDU was txd/rxd in this connection event. Also we need to set counter to
* 0 in case there was no valid PDU at subrated event, since we should not
* use continuation events in such case (i.e. ignore any valid PDUs prior
* to subrated event).
*
* Note that has_nonempty_pdu flag is also cleared here since LL may move to
* next connection event due to scheduling conflict and there will be no
* start callback for new event.
*/
if (connsm->has_nonempty_pdu) {
connsm->cont_num_left = connsm->cont_num;
connsm->has_nonempty_pdu = 0;
} else if (connsm->event_cntr == connsm->subrate_base_event) {
connsm->cont_num_left = 0;
}
if (connsm->cont_num_left > 0) {
connsm->cont_num_left--;
next_is_subrated = 0;
}
#else
base_event_cntr = connsm->event_cntr;
subrate_factor = 1;
#endif
/*
* XXX: TODO Probably want to add checks to see if we need to start
* a control procedure here as an instant may have prevented us from
* starting one.
*/
/*
* XXX TODO: I think this is technically incorrect. We can allow peripheral
* latency if we are doing one of these updates as long as we
* know that the central has received the ACK to the PDU that set
* the instant
*/
/* Set event counter to the next connection event that we will tx/rx in */
use_periph_latency = next_is_subrated &&
connsm->flags.periph_use_latency &&
!connsm->flags.conn_update_sched &&
!connsm->flags.phy_update_sched &&
!connsm->flags.chanmap_update_sched &&
connsm->flags.pkt_rxd;
if (next_is_subrated) {
next_event_cntr = base_event_cntr + subrate_factor;
if (use_periph_latency) {
next_event_cntr += subrate_factor * connsm->periph_latency;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
/* If we are in subrate transition mode, we should also listen on
* subrated connection events based on new parameters.
*/
if (connsm->flags.subrate_trans) {
BLE_LL_ASSERT(CONN_IS_CENTRAL(connsm));
cstp = &connsm->subrate_trans;
trans_next_event_cntr = cstp->subrate_base_event;
while (INT16_LTE(trans_next_event_cntr, connsm->event_cntr)) {
trans_next_event_cntr += cstp->subrate_factor;
}
cstp->subrate_base_event = trans_next_event_cntr;
if (INT16_LT(trans_next_event_cntr, next_event_cntr)) {
next_event_cntr = trans_next_event_cntr;
next_is_subrated = 0;
}
}
#endif
} else {
next_event_cntr = connsm->event_cntr + 1;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
/* If connection update is scheduled, peripheral shall listen at instant
* and one connection event before instant regardless of subrating.
*/
if (CONN_IS_PERIPHERAL(connsm) &&
connsm->flags.conn_update_sched &&
(connsm->subrate_factor > 1)) {
subrate_conn_upd_event_cntr = connsm->conn_update_req.instant - 1;
if (connsm->event_cntr == subrate_conn_upd_event_cntr) {
subrate_conn_upd_event_cntr++;
}
if (INT16_GT(next_event_cntr, subrate_conn_upd_event_cntr)) {
next_event_cntr = subrate_conn_upd_event_cntr;
next_is_subrated = 0;
}
}
/* Set next connection event as a subrate base event if that connection
* event is a subrated event, this simplifies calculations later.
* Note that according to spec base event should only be changed on
* wrap-around, but since we only use this value internally we can use any
* valid value.
*/
if (next_is_subrated ||
(connsm->subrate_base_event +
connsm->subrate_factor == next_event_cntr)) {
connsm->subrate_base_event = next_event_cntr;
}
#endif
event_cntr_diff = next_event_cntr - connsm->event_cntr;
BLE_LL_ASSERT(event_cntr_diff > 0);
connsm->event_cntr = next_event_cntr;
#if MYNEWT_VAL(BLE_LL_CONN_STRICT_SCHED)
if (ble_ll_sched_css_is_enabled() &&
connsm->conn_role == BLE_LL_CONN_ROLE_CENTRAL) {
connsm->css_period_idx += event_cntr_diff;
/* If this is non-reference connection, we calculate anchor point from
* reference connection instead of using connection interval. This is
* to make sure connections do not drift over time.
*/
if (g_ble_ll_conn_css_ref != connsm) {
anchor_calc_for_css = 1;
skip_anchor_calc = 1;
}
}
#endif
if (!skip_anchor_calc) {
/* Calculate next anchor point for connection.
* We can use pre-calculated values for one interval if latency is 1.
*/
if (event_cntr_diff == 1) {
connsm->anchor_point += connsm->conn_itvl_ticks;
ble_ll_tmr_add_u(&connsm->anchor_point, &connsm->anchor_point_usecs,
connsm->conn_itvl_usecs);
} else {
conn_itvl_us = connsm->conn_itvl * BLE_LL_CONN_ITVL_USECS;
ble_ll_tmr_add(&connsm->anchor_point, &connsm->anchor_point_usecs,
conn_itvl_us * event_cntr_diff);
}
}
/*
* If a connection update has been scheduled and the event counter
* is now equal to the instant, we need to adjust the start of the
* connection by the the transmit window offset. We also copy in the
* update parameters as they now should take effect.
*/
if (connsm->flags.conn_update_sched &&
(connsm->event_cntr == connsm->conn_update_req.instant)) {
/* Set flag so we send connection update event */
upd = &connsm->conn_update_req;
if (CONN_IS_CENTRAL(connsm) ||
(CONN_IS_PERIPHERAL(connsm) &&
IS_PENDING_CTRL_PROC(connsm, BLE_LL_CTRL_PROC_CONN_PARAM_REQ)) ||
(connsm->conn_itvl != upd->interval) ||
(connsm->periph_latency != upd->latency) ||
(connsm->supervision_tmo != upd->timeout)) {
connsm->flags.conn_update_host_w4event = 1;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
if (connsm->conn_itvl != upd->interval) {
connsm->subrate_base_event = connsm->event_cntr;
connsm->subrate_factor = 1;
connsm->cont_num = 0;
}
#endif
#if MYNEWT_VAL(BLE_LL_CONN_STRICT_SCHED)
if (ble_ll_sched_css_is_enabled() &&
connsm->conn_role == BLE_LL_CONN_ROLE_CENTRAL) {
BLE_LL_ASSERT(connsm->css_slot_idx_pending !=
BLE_LL_CONN_CSS_NO_SLOT);
/* If we are moving to an earlier slot, we are effectively skipping
* one period.
*/
if (connsm->css_slot_idx_pending < connsm->css_slot_idx) {
connsm->css_period_idx++;
}
connsm->css_slot_idx = connsm->css_slot_idx_pending;
connsm->css_slot_idx_pending = BLE_LL_CONN_CSS_NO_SLOT;
ble_ll_conn_css_update_list(connsm);
if (anchor_calc_for_css) {
ble_ll_sched_css_set_conn_anchor(connsm);
anchor_calc_for_css = 0;
}
#if MYNEWT_VAL(BLE_LL_HCI_VS_CONN_STRICT_SCHED)
ble_ll_hci_ev_send_vs_css_slot_changed(connsm->conn_handle,
connsm->css_slot_idx);
#endif
}
#endif
connsm->supervision_tmo = upd->timeout;
connsm->periph_latency = upd->latency;
connsm->tx_win_size = upd->winsize;
connsm->periph_cur_tx_win_usecs =
connsm->tx_win_size * BLE_LL_CONN_TX_WIN_USECS;
connsm->tx_win_off = upd->winoffset;
connsm->conn_itvl = upd->interval;
ble_ll_conn_itvl_to_ticks(connsm->conn_itvl, &connsm->conn_itvl_ticks,
&connsm->conn_itvl_usecs);
if (connsm->conn_param_req.handle != 0) {
connsm->max_ce_len_ticks = ble_ll_tmr_u2t_up(connsm->conn_param_req.max_ce_len * BLE_LL_CONN_CE_USECS);
connsm->conn_param_req.handle = 0;
}
if (upd->winoffset != 0) {
usecs = upd->winoffset * BLE_LL_CONN_ITVL_USECS;
ble_ll_tmr_add(&connsm->anchor_point, &connsm->anchor_point_usecs,
usecs);
}
/* Reset the starting point of the connection supervision timeout */
connsm->last_rxd_pdu_cputime = connsm->anchor_point;
/* Reset update scheduled flag */
connsm->flags.conn_update_sched = 0;
}
/*
* If there is a channel map request pending and we have reached the
* instant, change to new channel map. Note there is a special case here.
* If we received a channel map update with an instant equal to the event
* counter, when we get here the event counter has already been
* incremented by 1. That is why we do a signed comparison and change to
* new channel map once the event counter equals or has passed channel
* map update instant.
*/
if (connsm->flags.chanmap_update_sched &&
((int16_t)(connsm->chanmap_instant - connsm->event_cntr) <= 0)) {
/* XXX: there is a chance that the control packet is still on
* the queue of the central. This means that we never successfully
* transmitted update request. Would end up killing connection
on peripheral side. Could ignore it or see if still enqueued. */
connsm->chan_map_used =
ble_ll_utils_chan_map_used_get(connsm->req_chanmap);
memcpy(connsm->chan_map, connsm->req_chanmap, BLE_LL_CHAN_MAP_LEN);
connsm->flags.chanmap_update_sched = 0;
ble_ll_ctrl_proc_stop(connsm, BLE_LL_CTRL_PROC_CHAN_MAP_UPD);
/* XXX: host could have resent channel map command. Need to
check to make sure we dont have to restart! */
}
#if MYNEWT_VAL(BLE_LL_CONN_STRICT_SCHED)
if (anchor_calc_for_css) {
ble_ll_sched_css_set_conn_anchor(connsm);
}
#endif
#if MYNEWT_VAL(BLE_LL_PHY)
if (connsm->flags.phy_update_sched &&
(connsm->event_cntr == connsm->phy_instant)) {
/* Set cur phy to new phy */
if (connsm->phy_data.new_tx_phy) {
connsm->phy_data.cur_tx_phy = connsm->phy_data.new_tx_phy;
connsm->phy_data.tx_phy_mode =
ble_ll_phy_to_phy_mode(connsm->phy_data.cur_tx_phy,
connsm->phy_data.pref_opts);
}
if (connsm->phy_data.new_rx_phy) {
connsm->phy_data.cur_rx_phy = connsm->phy_data.new_rx_phy;
connsm->phy_data.rx_phy_mode =
ble_ll_phy_to_phy_mode(connsm->phy_data.cur_rx_phy,
connsm->phy_data.pref_opts);
}
/* Clear flags and set flag to send event at next instant */
connsm->flags.phy_update_sched = 0;
connsm->flags.phy_update_host_w4event = 1;
ble_ll_ctrl_phy_update_proc_complete(connsm);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY)
/* Recalculate effective connection parameters */
ble_ll_conn_update_eff_data_len(connsm);
/*
* If PHY in either direction was changed to coded, we assume that peer
* does support LE Coded PHY even if features were not exchanged yet.
* This means that MaxRxTime can be updated to supported max and we need
* initiate DLE to notify peer about the change.
*/
if (((connsm->phy_data.cur_tx_phy == BLE_PHY_CODED) ||
(connsm->phy_data.cur_rx_phy == BLE_PHY_CODED)) &&
!ble_ll_conn_rem_feature_check(connsm, BLE_LL_FEAT_LE_CODED_PHY)) {
ble_ll_conn_rem_feature_add(connsm, BLE_LL_FEAT_LE_CODED_PHY);
connsm->max_rx_time = BLE_LL_CONN_SUPP_TIME_MAX_CODED;
ble_ll_ctrl_initiate_dle(connsm, false);
}
#endif
}
#endif
/* Calculate data channel index of next connection event */
connsm->data_chan_index = ble_ll_conn_calc_dci(connsm, event_cntr_diff);
/*
* If we are trying to terminate connection, check if next wake time is
* passed the termination timeout. If so, no need to continue with
* connection as we will time out anyway.
*/
if (connsm->flags.terminate_started) {
if ((int32_t)(connsm->terminate_timeout - connsm->anchor_point) <= 0) {
return -1;
}
}
/*
* Calculate ce end time. For a peripheral, we need to add window widening
* and the transmit window if we still have one.
*/
#if MYNEWT_VAL(BLE_LL_CONN_STRICT_SCHED)
/* If css is enabled, use slot duration instead of conn_init_slots for
* reservation.
*/
if (ble_ll_sched_css_is_enabled() &&
connsm->conn_role == BLE_LL_CONN_ROLE_CENTRAL) {
ce_duration = ble_ll_tmr_u2t(ble_ll_sched_css_get_slot_us());
} else {
ce_duration = ble_ll_tmr_u2t(MYNEWT_VAL(BLE_LL_CONN_INIT_SLOTS) *
BLE_LL_SCHED_USECS_PER_SLOT);
}
#else
ce_duration = ble_ll_tmr_u2t(MYNEWT_VAL(BLE_LL_CONN_INIT_SLOTS) *
BLE_LL_SCHED_USECS_PER_SLOT);
#endif
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
if (connsm->conn_role == BLE_LL_CONN_ROLE_PERIPHERAL) {
cur_ww = ble_ll_utils_calc_window_widening(connsm->anchor_point,
connsm->last_anchor_point,
connsm->central_sca);
max_ww = (connsm->conn_itvl * (BLE_LL_CONN_ITVL_USECS/2)) - BLE_LL_IFS;
if (cur_ww >= max_ww) {
return -1;
}
cur_ww += BLE_LL_JITTER_USECS;
connsm->periph_cur_window_widening = cur_ww;
ce_duration += ble_ll_tmr_u2t(cur_ww +
connsm->periph_cur_tx_win_usecs);
}
#endif
ce_duration -= g_ble_ll_sched_offset_ticks;
connsm->ce_end_time = connsm->anchor_point + ce_duration;
return 0;
}
/**
* Called when a connection has been created. This function will
* -> Set the connection state to created.
* -> Start the connection supervision timer
* -> Set the Link Layer state to connection.
* -> Send a connection complete event.
*
* See Section 4.5.2 Vol 6 Part B
*
* Context: Link Layer
*
* @param connsm
*
* @ return 0: connection NOT created. 1: connection created
*/
static int
ble_ll_conn_created(struct ble_ll_conn_sm *connsm, struct ble_mbuf_hdr *rxhdr)
{
int rc;
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
uint8_t *evbuf;
#endif
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
uint32_t usecs;
#endif
/* XXX: TODO this assumes we received in 1M phy */
/* Set state to created */
connsm->conn_state = BLE_LL_CONN_STATE_CREATED;
/* Clear packet received flag */
connsm->flags.pkt_rxd = 0;
/* Consider time created the last scheduled time */
connsm->last_scheduled = ble_ll_tmr_get();
/*
* Set the last rxd pdu time since this is where we want to start the
* supervision timer from.
*/
connsm->last_rxd_pdu_cputime = connsm->last_scheduled;
/*
* Set first connection event time. If peripheral the endtime is the receive end
* time of the connect request. The actual connection starts 1.25 msecs plus
* the transmit window offset from the end of the connection request.
*/
rc = 1;
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
if (connsm->conn_role == BLE_LL_CONN_ROLE_PERIPHERAL) {
/*
* With a 32.768 kHz crystal we dont care about the remaining usecs
* when setting last anchor point. The only thing last anchor is used
* for is to calculate window widening. The effect of this is
* negligible.
*/
connsm->last_anchor_point = rxhdr->beg_cputime;
usecs = rxhdr->rem_usecs + 1250 +
(connsm->tx_win_off * BLE_LL_CONN_TX_WIN_USECS) +
ble_ll_pdu_us(BLE_CONNECT_REQ_LEN,
rxhdr->rxinfo.phy_mode);
if (rxhdr->rxinfo.channel < BLE_PHY_NUM_DATA_CHANS) {
switch (rxhdr->rxinfo.phy) {
case BLE_PHY_1M:
case BLE_PHY_2M:
usecs += 1250;
break;
case BLE_PHY_CODED:
usecs += 2500;
break;
default:
BLE_LL_ASSERT(0);
break;
}
}
/* Anchor point is cputime. */
connsm->anchor_point = rxhdr->beg_cputime;
connsm->anchor_point_usecs = 0;
ble_ll_tmr_add(&connsm->anchor_point, &connsm->anchor_point_usecs,
usecs);
connsm->periph_cur_tx_win_usecs =
connsm->tx_win_size * BLE_LL_CONN_TX_WIN_USECS;
connsm->ce_end_time = connsm->anchor_point +
ble_ll_tmr_u2t(MYNEWT_VAL(BLE_LL_CONN_INIT_SLOTS) *
BLE_LL_SCHED_USECS_PER_SLOT +
connsm->periph_cur_tx_win_usecs) + 1;
/* Start the scheduler for the first connection event */
while (ble_ll_sched_conn_periph_new(connsm)) {
if (ble_ll_conn_next_event(connsm)) {
STATS_INC(ble_ll_conn_stats, cant_set_sched);
rc = 0;
break;
}
}
}
#endif
/* Send connection complete event to inform host of connection */
if (rc) {
#if MYNEWT_VAL(BLE_LL_PHY) && MYNEWT_VAL(BLE_LL_CONN_PHY_INIT_UPDATE)
/*
* If we have default phy preferences and they are different than
* the current PHY's in use, start update procedure.
*/
/*
* XXX: should we attempt to start this without knowing if
* the other side can support it?
*/
if (!ble_ll_conn_phy_update_if_needed(connsm)) {
connsm->flags.phy_update_self_initiated = 1;
}
#endif
switch (connsm->conn_role) {
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
case BLE_LL_CONN_ROLE_CENTRAL:
#if MYNEWT_VAL(BLE_LL_CONN_STRICT_SCHED)
if (ble_ll_sched_css_is_enabled()) {
ble_ll_sched_css_update_anchor(connsm);
ble_ll_conn_css_set_next_slot(BLE_LL_CONN_CSS_NO_SLOT);
}
#endif
evbuf = ble_ll_init_get_conn_comp_ev();
ble_ll_conn_comp_event_send(connsm, BLE_ERR_SUCCESS, evbuf, NULL);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CSA2)
ble_ll_hci_ev_le_csa(connsm);
#endif
/*
* Initiate features exchange
*
* XXX we do this only as a central as it was observed that sending
* LL_PERIPH_FEATURE_REQ after connection breaks some recent iPhone
* models; for peripheral just assume central will initiate features xchg
* if it has some additional features to use.
*/
ble_ll_ctrl_proc_start(connsm, BLE_LL_CTRL_PROC_FEATURE_XCHG,
NULL);
break;
#endif
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
case BLE_LL_CONN_ROLE_PERIPHERAL:
ble_ll_adv_send_conn_comp_ev(connsm, rxhdr);
break;
#endif
default:
BLE_LL_ASSERT(0);
break;
}
}
return rc;
}
/**
* Called upon end of connection event
*
* Context: Link-layer task
*
* @param void *arg Pointer to connection state machine
*
*/
static void
ble_ll_conn_event_end(struct ble_npl_event *ev)
{
uint8_t ble_err;
uint32_t tmo;
struct ble_ll_conn_sm *connsm;
ble_ll_rfmgmt_release();
/* Better be a connection state machine! */
connsm = (struct ble_ll_conn_sm *)ble_npl_event_get_arg(ev);
BLE_LL_ASSERT(connsm);
/* Log event end */
ble_ll_trace_u32x2(BLE_LL_TRACE_ID_CONN_EV_END, connsm->conn_handle,
connsm->event_cntr);
ble_ll_scan_chk_resume();
/* If we have transmitted the terminate IND successfully, we are done */
if ((connsm->flags.terminate_ind_txd) ||
(connsm->flags.terminate_ind_rxd &&
connsm->flags.terminate_ind_rxd_acked)) {
if (connsm->flags.terminate_ind_txd) {
ble_err = BLE_ERR_CONN_TERM_LOCAL;
} else {
/* Make sure the disconnect reason is valid! */
ble_err = connsm->rxd_disconnect_reason;
if (ble_err == 0) {
ble_err = BLE_ERR_REM_USER_CONN_TERM;
}
}
ble_ll_conn_end(connsm, ble_err);
return;
}
/* Remove any connection end events that might be enqueued */
ble_ll_event_remove(&connsm->conn_ev_end);
/*
* If we have received a packet, we can set the current transmit window
* usecs to 0 since we dont need to listen in the transmit window.
*/
if (connsm->flags.pkt_rxd) {
connsm->periph_cur_tx_win_usecs = 0;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_PING)
/*
* If we are encrypted and have passed the authenticated payload timeout
* we need to send an event to tell the host. Unfortunately, I think we
* need one of these per connection and we have to set this timer
* fairly accurately. So we need to another event in the connection.
* This sucks.
*
* The way this works is that whenever the timer expires it just gets reset
* and we send the autheticated payload timeout event. Note that this timer
* should run even when encryption is paused.
* XXX: what should be here? Was there code here that got deleted?
*/
#endif
/* Move to next connection event */
if (ble_ll_conn_next_event(connsm)) {
ble_ll_conn_end(connsm, BLE_ERR_CONN_TERM_LOCAL);
return;
}
/* Reset "per connection event" variables */
connsm->cons_rxd_bad_crc = 0;
connsm->flags.pkt_rxd = 0;
/* See if we need to start any control procedures */
ble_ll_ctrl_chk_proc_start(connsm);
/* Set initial schedule callback */
connsm->conn_sch.sched_cb = ble_ll_conn_event_start_cb;
/* XXX: I think all this fine for when we do connection updates, but
we may want to force the first event to be scheduled. Not sure */
/* Schedule the next connection event */
while (ble_ll_sched_conn_reschedule(connsm)) {
if (ble_ll_conn_next_event(connsm)) {
ble_ll_conn_end(connsm, BLE_ERR_CONN_TERM_LOCAL);
return;
}
}
/*
* This is definitely not perfect but hopefully will be fine in regards to
* the specification. We check the supervision timer at connection event
* end. If the next connection event is going to start past the supervision
* timeout we end the connection here. I guess this goes against the spec
* in two ways:
* 1) We are actually causing a supervision timeout before the time
* specified. However, this is really a moot point because the supervision
* timeout would have expired before we could possibly receive a packet.
* 2) We may end the supervision timeout a bit later than specified as
* we only check this at event end and a bad CRC could cause us to continue
* the connection event longer than the supervision timeout. Given that two
* bad CRC's consecutively ends the connection event, I dont regard this as
* a big deal but it could cause a slightly longer supervision timeout.
*/
if (connsm->conn_state == BLE_LL_CONN_STATE_CREATED) {
tmo = (uint32_t)connsm->conn_itvl * BLE_LL_CONN_ITVL_USECS * 6UL;
ble_err = BLE_ERR_CONN_ESTABLISHMENT;
} else {
tmo = connsm->supervision_tmo * BLE_HCI_CONN_SPVN_TMO_UNITS * 1000UL;
ble_err = BLE_ERR_CONN_SPVN_TMO;
}
/* XXX: Convert to ticks to usecs calculation instead??? */
tmo = ble_ll_tmr_u2t(tmo);
if ((int32_t)(connsm->anchor_point - connsm->last_rxd_pdu_cputime) >= tmo) {
ble_ll_conn_end(connsm, ble_err);
return;
}
/* If we have completed packets, send an event */
ble_ll_conn_num_comp_pkts_event_send(connsm);
/* If we have features and there's pending HCI command, send an event */
if (connsm->flags.features_host_req &&
connsm->flags.features_rxd) {
ble_ll_hci_ev_rd_rem_used_feat(connsm, BLE_ERR_SUCCESS);
connsm->flags.features_host_req = 0;
}
}
/**
* Update the connection request PDU with the address type and address of
* advertiser we are going to send connect request to.
*
* @param m
* @param adva
* @param addr_type Address type of ADVA from received advertisement.
* @param inita
* @param inita_type Address type of INITA from received advertisement.
* @param txoffset The tx window offset for this connection
*/
void
ble_ll_conn_prepare_connect_ind(struct ble_ll_conn_sm *connsm,
struct ble_ll_scan_pdu_data *pdu_data,
struct ble_ll_scan_addr_data *addrd,
uint8_t channel)
{
uint8_t hdr;
uint8_t *addr;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
struct ble_ll_resolv_entry *rl;
#endif
hdr = BLE_ADV_PDU_TYPE_CONNECT_IND;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CSA2)
/* We need CSA2 bit only for legacy connect */
if (channel >= BLE_PHY_NUM_DATA_CHANS) {
hdr |= BLE_ADV_PDU_HDR_CHSEL;
}
#endif
if (addrd->adva_type) {
/* Set random address */
hdr |= BLE_ADV_PDU_HDR_RXADD_MASK;
}
if (addrd->targeta) {
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
if (addrd->targeta_resolved) {
if (connsm->own_addr_type > BLE_OWN_ADDR_RANDOM) {
/* If TargetA was resolved we should reply with a different RPA
* in InitA (see Core 5.3, Vol 6, Part B, 6.4).
*/
BLE_LL_ASSERT(addrd->rpa_index >= 0);
rl = &g_ble_ll_resolv_list[addrd->rpa_index];
hdr |= BLE_ADV_PDU_HDR_TXADD_RAND;
ble_ll_resolv_get_priv_addr(rl, 1, pdu_data->inita);
} else {
/* Host does not want us to use RPA so use identity */
if ((connsm->own_addr_type & 1) == 0) {
memcpy(pdu_data->inita, g_dev_addr, BLE_DEV_ADDR_LEN);
} else {
hdr |= BLE_ADV_PDU_HDR_TXADD_RAND;
memcpy(pdu_data->inita, g_random_addr, BLE_DEV_ADDR_LEN);
}
}
} else {
memcpy(pdu_data->inita, addrd->targeta, BLE_DEV_ADDR_LEN);
if (addrd->targeta_type) {
hdr |= BLE_ADV_PDU_HDR_TXADD_RAND;
}
}
#else
memcpy(pdu_data->inita, addrd->targeta, BLE_DEV_ADDR_LEN);
if (addrd->targeta_type) {
hdr |= BLE_ADV_PDU_HDR_TXADD_RAND;
}
#endif
} else {
/* Get pointer to our device address */
if ((connsm->own_addr_type & 1) == 0) {
addr = g_dev_addr;
} else {
hdr |= BLE_ADV_PDU_HDR_TXADD_RAND;
addr = g_random_addr;
}
/* XXX: do this ahead of time? Calculate the local rpa I mean */
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
if (connsm->own_addr_type > BLE_HCI_ADV_OWN_ADDR_RANDOM) {
if (addrd->rpa_index >= 0) {
/* We are using RPA and advertiser was on our resolving list, so
* we'll use RPA to reply (see Core 5.3, Vol 6, Part B, 6.4).
*/
rl = &g_ble_ll_resolv_list[addrd->rpa_index];
if (rl->rl_has_local) {
hdr |= BLE_ADV_PDU_HDR_TXADD_RAND;
ble_ll_resolv_get_priv_addr(rl, 1, pdu_data->inita);
addr = NULL;
}
} else if (ble_ll_resolv_local_rpa_get(connsm->own_addr_type & 1,
pdu_data->inita) == 0) {
hdr |= BLE_ADV_PDU_HDR_TXADD_RAND;
addr = NULL;
}
}
#endif
if (addr) {
memcpy(pdu_data->inita, addr, BLE_DEV_ADDR_LEN);
/* Identity address used */
connsm->inita_identity_used = 1;
}
}
memcpy(pdu_data->adva, addrd->adva, BLE_DEV_ADDR_LEN);
pdu_data->hdr_byte = hdr;
}
uint8_t
ble_ll_conn_tx_connect_ind_pducb(uint8_t *dptr, void *pducb_arg, uint8_t *hdr_byte)
{
struct ble_ll_conn_sm *connsm;
struct ble_ll_scan_pdu_data *pdu_data;
connsm = pducb_arg;
/*
* pdu_data was prepared just before starting TX and is expected to be
* still valid here
*/
pdu_data = ble_ll_scan_get_pdu_data();
memcpy(dptr, pdu_data->inita, BLE_DEV_ADDR_LEN);
memcpy(dptr + BLE_DEV_ADDR_LEN, pdu_data->adva, BLE_DEV_ADDR_LEN);
dptr += 2 * BLE_DEV_ADDR_LEN;
put_le32(dptr, connsm->access_addr);
dptr[4] = (uint8_t)connsm->crcinit;
dptr[5] = (uint8_t)(connsm->crcinit >> 8);
dptr[6] = (uint8_t)(connsm->crcinit >> 16);
dptr[7] = connsm->tx_win_size;
put_le16(dptr + 8, connsm->tx_win_off);
put_le16(dptr + 10, connsm->conn_itvl);
put_le16(dptr + 12, connsm->periph_latency);
put_le16(dptr + 14, connsm->supervision_tmo);
memcpy(dptr + 16, &connsm->chan_map, BLE_LL_CHAN_MAP_LEN);
dptr[21] = connsm->hop_inc | (connsm->central_sca << 5);
*hdr_byte = pdu_data->hdr_byte;
return 34;
}
/**
* Called when a schedule item overlaps the currently running connection
* event. This generally should not happen, but if it does we stop the
* current connection event to let the schedule item run.
*
* NOTE: the phy has been disabled as well as the wfr timer before this is
* called.
*/
void
ble_ll_conn_event_halt(void)
{
ble_ll_state_set(BLE_LL_STATE_STANDBY);
if (g_ble_ll_conn_cur_sm) {
g_ble_ll_conn_cur_sm->flags.pkt_rxd = 0;
ble_ll_event_add(&g_ble_ll_conn_cur_sm->conn_ev_end);
g_ble_ll_conn_cur_sm = NULL;
}
}
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
int
ble_ll_conn_send_connect_req(struct os_mbuf *rxpdu,
struct ble_ll_scan_addr_data *addrd,
uint8_t ext)
{
struct ble_ll_conn_sm *connsm;
struct ble_mbuf_hdr *rxhdr;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
uint8_t phy;
#endif
int rc;
connsm = g_ble_ll_conn_create_sm.connsm;
rxhdr = BLE_MBUF_HDR_PTR(rxpdu);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (ext) {
#if MYNEWT_VAL(BLE_LL_PHY)
phy = rxhdr->rxinfo.phy;
#else
phy = BLE_PHY_1M;
#endif
ble_ll_conn_create_set_params(connsm, phy);
}
#endif
if (ble_ll_sched_conn_central_new(connsm, rxhdr, 0)) {
return -1;
}
ble_ll_conn_prepare_connect_ind(connsm, ble_ll_scan_get_pdu_data(), addrd,
rxhdr->rxinfo.channel);
ble_phy_set_txend_cb(NULL, NULL);
rc = ble_phy_tx(ble_ll_conn_tx_connect_ind_pducb, connsm,
ext ? BLE_PHY_TRANSITION_TX_RX : BLE_PHY_TRANSITION_NONE);
if (rc) {
ble_ll_conn_send_connect_req_cancel();
return -1;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV) && MYNEWT_VAL(BLE_LL_PHY)
if (ext) {
ble_ll_conn_init_phy(connsm, phy);
}
#endif
return 0;
}
void
ble_ll_conn_send_connect_req_cancel(void)
{
struct ble_ll_conn_sm *connsm;
connsm = g_ble_ll_conn_create_sm.connsm;
ble_ll_sched_rmv_elem(&connsm->conn_sch);
}
static void
ble_ll_conn_central_start(uint8_t phy, uint8_t csa,
struct ble_ll_scan_addr_data *addrd, uint8_t *targeta)
{
struct ble_ll_conn_sm *connsm;
connsm = g_ble_ll_conn_create_sm.connsm;
g_ble_ll_conn_create_sm.connsm = NULL;
connsm->peer_addr_type = addrd->adv_addr_type;
memcpy(connsm->peer_addr, addrd->adv_addr, 6);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
if (addrd->adva_resolved) {
BLE_LL_ASSERT(addrd->rpa_index >= 0);
connsm->peer_addr_resolved = 1;
ble_ll_resolv_set_peer_rpa(addrd->rpa_index, addrd->adva);
ble_ll_scan_set_peer_rpa(addrd->adva);
} else {
connsm->peer_addr_resolved = 0;
}
if (addrd->targeta_resolved) {
BLE_LL_ASSERT(addrd->rpa_index >= 0);
BLE_LL_ASSERT(targeta);
}
#endif
ble_ll_conn_set_csa(connsm, csa);
#if MYNEWT_VAL(BLE_LL_PHY)
ble_ll_conn_init_phy(connsm, phy);
#endif
ble_ll_conn_created(connsm, NULL);
}
void
ble_ll_conn_created_on_legacy(struct os_mbuf *rxpdu,
struct ble_ll_scan_addr_data *addrd,
uint8_t *targeta)
{
uint8_t *rxbuf;
uint8_t csa;
rxbuf = rxpdu->om_data;
csa = rxbuf[0] & BLE_ADV_PDU_HDR_CHSEL_MASK;
ble_ll_conn_central_start(BLE_PHY_1M, csa, addrd, targeta);
}
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
void
ble_ll_conn_created_on_aux(struct os_mbuf *rxpdu,
struct ble_ll_scan_addr_data *addrd,
uint8_t *targeta)
{
#if MYNEWT_VAL(BLE_LL_PHY)
struct ble_mbuf_hdr *rxhdr;
#endif
uint8_t phy;
#if MYNEWT_VAL(BLE_LL_PHY)
rxhdr = BLE_MBUF_HDR_PTR(rxpdu);
phy = rxhdr->rxinfo.phy;
#else
phy = BLE_PHY_1M;
#endif
ble_ll_conn_central_start(phy, 1, addrd, targeta);
}
#endif
#endif /* BLE_LL_CFG_FEAT_LL_EXT_ADV */
/**
* Function called when a timeout has occurred for a connection. There are
* two types of timeouts: a connection supervision timeout and control
* procedure timeout.
*
* Context: Link Layer task
*
* @param connsm
* @param ble_err
*/
void
ble_ll_conn_timeout(struct ble_ll_conn_sm *connsm, uint8_t ble_err)
{
int was_current;
os_sr_t sr;
was_current = 0;
OS_ENTER_CRITICAL(sr);
if (g_ble_ll_conn_cur_sm == connsm) {
ble_ll_conn_current_sm_over(NULL);
was_current = 1;
}
OS_EXIT_CRITICAL(sr);
/* Check if we need to resume scanning */
if (was_current) {
ble_ll_scan_chk_resume();
}
ble_ll_conn_end(connsm, ble_err);
}
/**
* Called when a data channel PDU has started that matches the access
* address of the current connection. Note that the CRC of the PDU has not
* been checked yet.
*
* Context: Interrupt
*
* @param rxhdr
*/
int
ble_ll_conn_rx_isr_start(struct ble_mbuf_hdr *rxhdr, uint32_t aa)
{
struct ble_ll_conn_sm *connsm;
/*
* Disable wait for response timer since we receive a response. We dont
* care if this is the response we were waiting for or not; the code
* called at receive end will deal with ending the connection event
* if needed
*/
connsm = g_ble_ll_conn_cur_sm;
if (connsm) {
/* Double check access address. Better match connection state machine */
if (aa != connsm->access_addr) {
STATS_INC(ble_ll_conn_stats, rx_data_pdu_bad_aa);
ble_ll_state_set(BLE_LL_STATE_STANDBY);
ble_ll_event_add(&connsm->conn_ev_end);
g_ble_ll_conn_cur_sm = NULL;
return -1;
}
/* Set connection handle in mbuf header */
rxhdr->rxinfo.handle = connsm->conn_handle;
/* Set flag denoting we have received a packet in connection event */
connsm->flags.pkt_rxd = 1;
/* Connection is established */
connsm->conn_state = BLE_LL_CONN_STATE_ESTABLISHED;
/* Set anchor point (and last) if 1st rxd frame in connection event */
if (connsm->flags.periph_set_last_anchor) {
connsm->flags.periph_set_last_anchor = 0;
connsm->last_anchor_point = rxhdr->beg_cputime;
connsm->anchor_point = connsm->last_anchor_point;
connsm->anchor_point_usecs = rxhdr->rem_usecs;
}
}
return 1;
}
/**
* Called from the Link Layer task when a data PDU has been received
*
* Context: Link layer task
*
* @param rxpdu Pointer to received pdu
* @param rxpdu Pointer to ble mbuf header of received pdu
*/
void
ble_ll_conn_rx_data_pdu(struct os_mbuf *rxpdu, struct ble_mbuf_hdr *hdr)
{
uint8_t hdr_byte;
uint8_t rxd_sn;
uint8_t *rxbuf;
uint8_t llid;
uint16_t acl_len;
uint16_t acl_hdr;
struct ble_ll_conn_sm *connsm;
/* Packets with invalid CRC are not sent to LL */
BLE_LL_ASSERT(BLE_MBUF_HDR_CRC_OK(hdr));
/* XXX: there is a chance that the connection was thrown away and
re-used before processing packets here. Fix this. */
/* We better have a connection state machine */
connsm = ble_ll_conn_find_by_handle(hdr->rxinfo.handle);
if (!connsm) {
STATS_INC(ble_ll_conn_stats, no_conn_sm);
goto conn_rx_data_pdu_end;
}
/* Check state machine */
ble_ll_conn_chk_csm_flags(connsm);
/* Validate rx data pdu */
rxbuf = rxpdu->om_data;
hdr_byte = rxbuf[0];
acl_len = rxbuf[1];
llid = hdr_byte & BLE_LL_DATA_HDR_LLID_MASK;
rxd_sn = hdr_byte & BLE_LL_DATA_HDR_SN_MASK;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
if (BLE_MBUF_HDR_MIC_FAILURE(hdr)) {
/* MIC failure is expected on retransmissions since packet counter does
* not match, so we simply ignore retransmitted PDU with MIC failure as
* they do not have proper decrypted contents.
*/
if (rxd_sn != connsm->last_rxd_sn) {
STATS_INC(ble_ll_conn_stats, mic_failures);
ble_ll_conn_timeout(connsm, BLE_ERR_CONN_TERM_MIC);
}
goto conn_rx_data_pdu_end;
}
#endif
/*
* Check that the LLID and payload length are reasonable.
* Empty payload is only allowed for LLID == 01b.
* */
if ((llid == 0) || ((acl_len == 0) && (llid != BLE_LL_LLID_DATA_FRAG))) {
STATS_INC(ble_ll_conn_stats, rx_bad_llid);
goto conn_rx_data_pdu_end;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
/* Check if PDU is allowed when encryption is started. If not,
* terminate connection.
*
* Reference: Core 5.0, Vol 6, Part B, 5.1.3.1
*/
if ((connsm->enc_data.enc_state > CONN_ENC_S_PAUSE_ENC_RSP_WAIT &&
CONN_IS_CENTRAL(connsm)) ||
(connsm->enc_data.enc_state >= CONN_ENC_S_ENC_RSP_TO_BE_SENT &&
CONN_IS_PERIPHERAL(connsm))) {
if (!ble_ll_ctrl_enc_allowed_pdu_rx(rxpdu)) {
ble_ll_conn_timeout(connsm, BLE_ERR_CONN_TERM_MIC);
goto conn_rx_data_pdu_end;
}
}
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_PING)
/*
* Reset authenticated payload timeout if valid MIC. NOTE: we dont
* check the MIC failure bit as that would have terminated the
* connection
*/
if ((connsm->enc_data.enc_state == CONN_ENC_S_ENCRYPTED) &&
connsm->flags.le_ping_supp && (acl_len != 0)) {
ble_ll_conn_auth_pyld_timer_start(connsm);
}
#endif
/* Update RSSI */
connsm->conn_rssi = hdr->rxinfo.rssi - ble_ll_rx_gain();
/*
* If we are a peripheral, we can only start to use peripheral latency
* once we have received a NESN of 1 from the central
*/
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
if (connsm->conn_role == BLE_LL_CONN_ROLE_PERIPHERAL) {
if (hdr_byte & BLE_LL_DATA_HDR_NESN_MASK) {
connsm->flags.periph_use_latency = 1;
}
}
#endif
/*
* Discard the received PDU if the sequence number is the same
* as the last received sequence number
*/
if (rxd_sn == connsm->last_rxd_sn) {
STATS_INC(ble_ll_conn_stats, data_pdu_rx_dup);
goto conn_rx_data_pdu_end;
}
/* Update last rxd sn */
connsm->last_rxd_sn = rxd_sn;
/* No need to do anything if empty pdu */
if ((llid == BLE_LL_LLID_DATA_FRAG) && (acl_len == 0)) {
goto conn_rx_data_pdu_end;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
connsm->has_nonempty_pdu = 1;
#endif
if (llid == BLE_LL_LLID_CTRL) {
/* Process control frame */
STATS_INC(ble_ll_conn_stats, rx_ctrl_pdus);
if (ble_ll_ctrl_rx_pdu(connsm, rxpdu)) {
STATS_INC(ble_ll_conn_stats, rx_malformed_ctrl_pdus);
}
} else {
/* Count # of received l2cap frames and byes */
STATS_INC(ble_ll_conn_stats, rx_l2cap_pdus);
STATS_INCN(ble_ll_conn_stats, rx_l2cap_bytes, acl_len);
/* NOTE: there should be at least two bytes available */
BLE_LL_ASSERT(OS_MBUF_LEADINGSPACE(rxpdu) >= 2);
os_mbuf_prepend(rxpdu, 2);
rxbuf = rxpdu->om_data;
acl_hdr = (llid << 12) | connsm->conn_handle;
put_le16(rxbuf, acl_hdr);
put_le16(rxbuf + 2, acl_len);
ble_transport_to_hs_acl(rxpdu);
}
/* NOTE: we dont free the mbuf since we handed it off! */
return;
/* Free buffer */
conn_rx_data_pdu_end:
#if MYNEWT_PKG_apache_mynewt_nimble__nimble_transport_common_hci_ipc
if (hdr->rxinfo.flags & BLE_MBUF_HDR_F_CONN_CREDIT_INT) {
hci_ipc_put(HCI_IPC_TYPE_ACL);
}
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_CTRL_TO_HOST_FLOW_CONTROL)
/* Need to give credit back if we allocated one for this PDU */
if (hdr->rxinfo.flags & BLE_MBUF_HDR_F_CONN_CREDIT) {
ble_ll_conn_cth_flow_free_credit(connsm, 1);
}
#endif
os_mbuf_free_chain(rxpdu);
}
/**
* Called when a packet has been received while in the connection state.
*
* Context: Interrupt
*
* @param rxpdu
* @param crcok
*
* @return int
* < 0: Disable the phy after reception.
* == 0: Success. Do not disable the PHY.
* > 0: Do not disable PHY as that has already been done.
*/
int
ble_ll_conn_rx_isr_end(uint8_t *rxbuf, struct ble_mbuf_hdr *rxhdr)
{
int rc;
uint8_t hdr_byte;
uint8_t hdr_sn;
uint8_t hdr_nesn;
uint8_t conn_sn;
uint8_t conn_nesn;
uint8_t reply = 0;
uint16_t rem_bytes;
uint8_t opcode = 0;
uint8_t rx_pyld_len;
uint32_t begtime;
uint32_t add_usecs;
struct os_mbuf *txpdu;
struct ble_ll_conn_sm *connsm;
struct os_mbuf *rxpdu = NULL;
struct ble_mbuf_hdr *txhdr;
int rx_phy_mode;
bool alloc_rxpdu = true;
rc = -1;
connsm = g_ble_ll_conn_cur_sm;
/* Retrieve the header and payload length */
hdr_byte = rxbuf[0];
rx_pyld_len = rxbuf[1];
/*
* No need to alloc rxpdu for packets with invalid CRC, we would throw them
* away instantly from LL anyway.
*/
if (!BLE_MBUF_HDR_CRC_OK(rxhdr)) {
alloc_rxpdu = false;
}
#if MYNEWT_PKG_apache_mynewt_nimble__nimble_transport_common_hci_ipc
/* If IPC transport is used, make sure there is buffer available on app side
* for this PDU. We'll just nak in LL if there are no free buffers.
*/
if (alloc_rxpdu && BLE_LL_LLID_IS_DATA(hdr_byte) && (rx_pyld_len > 0)) {
if (hci_ipc_get(HCI_IPC_TYPE_ACL)) {
rxhdr->rxinfo.flags |= BLE_MBUF_HDR_F_CONN_CREDIT_INT;
} else {
alloc_rxpdu = false;
}
}
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_CTRL_TO_HOST_FLOW_CONTROL)
/*
* If flow control is enabled, we need to have credit available for each
* non-empty data packet that LL may send to host. If there are no credits
* available, we don't need to allocate buffer for this packet so LL will
* nak it.
*/
if (alloc_rxpdu && ble_ll_conn_cth_flow_is_enabled() &&
BLE_LL_LLID_IS_DATA(hdr_byte) && (rx_pyld_len > 0)) {
if (ble_ll_conn_cth_flow_alloc_credit(connsm)) {
rxhdr->rxinfo.flags |= BLE_MBUF_HDR_F_CONN_CREDIT;
} else {
#if MYNEWT_PKG_apache_mynewt_nimble__nimble_transport_common_hci_ipc
/* Need to return app buffer to pool since we won't use it */
hci_ipc_put(HCI_IPC_TYPE_ACL);
#endif
alloc_rxpdu = false;
}
}
#endif
/*
* We need to attempt to allocate a buffer here. The reason we do this
* now is that we should not ack the packet if we have no receive
* buffers available. We want to free up our transmit PDU if it was
* acked, but we should not ack the received frame if we cant hand it up.
* NOTE: we hand up empty pdu's to the LL task!
*/
if (alloc_rxpdu) {
rxpdu = ble_ll_rxpdu_alloc(rx_pyld_len + BLE_LL_PDU_HDR_LEN);
}
/*
* We should have a current connection state machine. If we dont, we just
* hand the packet to the higher layer to count it.
*/
if (!connsm) {
STATS_INC(ble_ll_conn_stats, rx_data_pdu_no_conn);
goto conn_exit;
}
/*
* Calculate the end time of the received PDU. NOTE: this looks strange
* but for the 32768 crystal we add the time it takes to send the packet
* to the 'additional usecs' field to save some calculations.
*/
begtime = rxhdr->beg_cputime;
#if MYNEWT_VAL(BLE_LL_PHY)
rx_phy_mode = connsm->phy_data.rx_phy_mode;
#else
rx_phy_mode = BLE_PHY_MODE_1M;
#endif
add_usecs = rxhdr->rem_usecs +
ble_ll_pdu_us(rx_pyld_len, rx_phy_mode);
/*
* Check the packet CRC. A connection event can continue even if the
* received PDU does not pass the CRC check. If we receive two consecutive
* CRC errors we end the connection event.
*/
if (!BLE_MBUF_HDR_CRC_OK(rxhdr)) {
/*
* Increment # of consecutively received CRC errors. If more than
* one we will end the connection event.
*/
++connsm->cons_rxd_bad_crc;
if (connsm->cons_rxd_bad_crc >= 2) {
reply = 0;
} else {
switch (connsm->conn_role) {
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
case BLE_LL_CONN_ROLE_CENTRAL:
reply = connsm->flags.last_txd_md;
break;
#endif
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
case BLE_LL_CONN_ROLE_PERIPHERAL:
/* A peripheral always responds with a packet */
reply = 1;
break;
#endif
default:
BLE_LL_ASSERT(0);
break;
}
}
} else {
/* Reset consecutively received bad crcs (since this one was good!) */
connsm->cons_rxd_bad_crc = 0;
/* Set last valid received pdu time (resets supervision timer) */
connsm->last_rxd_pdu_cputime = begtime + ble_ll_tmr_u2t(add_usecs);
/* Set last received header byte */
connsm->last_rxd_hdr_byte = hdr_byte;
if (BLE_LL_LLID_IS_CTRL(hdr_byte)) {
opcode = rxbuf[2];
}
/*
* If SN bit from header does not match NESN in connection, this is
* a resent PDU and should be ignored.
*/
hdr_sn = hdr_byte & BLE_LL_DATA_HDR_SN_MASK;
conn_nesn = connsm->next_exp_seqnum;
if (rxpdu && ((hdr_sn && conn_nesn) || (!hdr_sn && !conn_nesn))) {
connsm->next_exp_seqnum ^= 1;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
if (connsm->flags.encrypted && !ble_ll_conn_is_empty_pdu(rxbuf)) {
++connsm->enc_data.rx_pkt_cntr;
}
#endif
}
ble_ll_trace_u32x2(BLE_LL_TRACE_ID_CONN_RX, connsm->tx_seqnum,
!!(hdr_byte & BLE_LL_DATA_HDR_NESN_MASK));
/*
* Check NESN bit from header. If same as tx seq num, the transmission
* is acknowledged. Otherwise we need to resend this PDU.
*/
if (connsm->flags.empty_pdu_txd || connsm->cur_tx_pdu) {
hdr_nesn = hdr_byte & BLE_LL_DATA_HDR_NESN_MASK;
conn_sn = connsm->tx_seqnum;
if ((hdr_nesn && conn_sn) || (!hdr_nesn && !conn_sn)) {
/* We did not get an ACK. Must retry the PDU */
STATS_INC(ble_ll_conn_stats, data_pdu_txf);
} else {
/* Transmit success */
connsm->tx_seqnum ^= 1;
STATS_INC(ble_ll_conn_stats, data_pdu_txg);
/* If we transmitted the empty pdu, clear flag */
if (connsm->flags.empty_pdu_txd) {
connsm->flags.empty_pdu_txd = 0;
goto chk_rx_terminate_ind;
}
/*
* Determine if we should remove packet from queue or if there
* are more fragments to send.
*/
txpdu = connsm->cur_tx_pdu;
if (txpdu) {
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
if (connsm->enc_data.tx_encrypted) {
++connsm->enc_data.tx_pkt_cntr;
}
#endif
txhdr = BLE_MBUF_HDR_PTR(txpdu);
if ((txhdr->txinfo.hdr_byte & BLE_LL_DATA_HDR_LLID_MASK)
== BLE_LL_LLID_CTRL) {
connsm->cur_tx_pdu = NULL;
/* Note: the mbuf is freed by this call */
rc = ble_ll_ctrl_tx_done(txpdu, connsm);
if (rc) {
/* Means we transmitted a TERMINATE_IND */
goto conn_exit;
} else {
goto chk_rx_terminate_ind;
}
}
/* Increment offset based on number of bytes sent */
txhdr->txinfo.offset += txhdr->txinfo.pyld_len;
if (txhdr->txinfo.offset >= OS_MBUF_PKTLEN(txpdu)) {
/* If l2cap pdu, increment # of completed packets */
if (txhdr->txinfo.pyld_len != 0) {
#if (BLETEST_THROUGHPUT_TEST == 1)
bletest_completed_pkt(connsm->conn_handle);
#endif
++connsm->completed_pkts;
if (connsm->completed_pkts > 2) {
ble_ll_event_add(&g_ble_ll_data.ll_comp_pkt_ev);
}
}
os_mbuf_free_chain(txpdu);
connsm->cur_tx_pdu = NULL;
} else {
rem_bytes = OS_MBUF_PKTLEN(txpdu) - txhdr->txinfo.offset;
/* Adjust payload for max TX time and octets */
#if MYNEWT_VAL(BLE_LL_PHY)
if (BLE_LL_LLID_IS_CTRL(hdr_byte) &&
CONN_IS_PERIPHERAL(connsm) &&
(opcode == BLE_LL_CTRL_PHY_UPDATE_IND)) {
connsm->phy_tx_transition =
ble_ll_ctrl_phy_tx_transition_get(rxbuf[3]);
}
#endif
rem_bytes = ble_ll_conn_adjust_pyld_len(connsm, rem_bytes);
txhdr->txinfo.pyld_len = rem_bytes;
}
}
}
}
/* Should we continue connection event? */
/* If this is a TERMINATE_IND, we have to reply */
chk_rx_terminate_ind:
/* If we received a terminate IND, we must set some flags */
if (BLE_LL_LLID_IS_CTRL(hdr_byte) &&
(opcode == BLE_LL_CTRL_TERMINATE_IND) &&
(rx_pyld_len == (1 + BLE_LL_CTRL_TERMINATE_IND_LEN))) {
connsm->flags.terminate_ind_rxd = 1;
connsm->rxd_disconnect_reason = rxbuf[3];
}
switch (connsm->conn_role) {
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
case BLE_LL_CONN_ROLE_CENTRAL:
reply = connsm->flags.last_txd_md || (hdr_byte & BLE_LL_DATA_HDR_MD_MASK);
break;
#endif
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
case BLE_LL_CONN_ROLE_PERIPHERAL:
/* A peripheral always replies */
reply = 1;
break;
#endif
default:
BLE_LL_ASSERT(0);
break;
}
}
/* If reply flag set, send data pdu and continue connection event */
rc = -1;
if (rx_pyld_len && connsm->flags.encrypted) {
rx_pyld_len += BLE_LL_DATA_MIC_LEN;
}
if (reply && ble_ll_conn_can_send_next_pdu(connsm, begtime, add_usecs)) {
rc = ble_ll_conn_tx_pdu(connsm);
}
conn_exit:
/* Copy the received pdu and hand it up */
if (rxpdu) {
ble_phy_rxpdu_copy(rxbuf, rxpdu);
ble_ll_rx_pdu_in(rxpdu);
}
/* Send link layer a connection end event if over */
if (rc) {
ble_ll_conn_current_sm_over(connsm);
}
return rc;
}
/**
* Called to adjust payload length to fit into max effective octets and TX time
* on current PHY.
*/
/**
* Called to enqueue a packet on the transmit queue of a connection. Should
* only be called by the controller.
*
* Context: Link Layer
*
*
* @param connsm
* @param om
*/
void
ble_ll_conn_enqueue_pkt(struct ble_ll_conn_sm *connsm, struct os_mbuf *om,
uint8_t hdr_byte, uint16_t length)
{
os_sr_t sr;
struct os_mbuf_pkthdr *pkthdr;
struct ble_mbuf_hdr *ble_hdr;
int lifo;
/* Set mbuf length and packet length if a control PDU */
if (hdr_byte == BLE_LL_LLID_CTRL) {
om->om_len = length;
OS_MBUF_PKTHDR(om)->omp_len = length;
}
/* Set BLE transmit header */
ble_hdr = BLE_MBUF_HDR_PTR(om);
ble_hdr->txinfo.flags = 0;
ble_hdr->txinfo.offset = 0;
ble_hdr->txinfo.hdr_byte = hdr_byte;
/*
* Initial payload length is calculate when packet is dequeued, there's no
* need to do this now.
*/
lifo = 0;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
if (connsm->enc_data.enc_state > CONN_ENC_S_ENCRYPTED) {
uint8_t llid;
/*
* If this is one of the following types we need to insert it at
* head of queue.
*/
llid = ble_hdr->txinfo.hdr_byte & BLE_LL_DATA_HDR_LLID_MASK;
if (llid == BLE_LL_LLID_CTRL) {
switch (om->om_data[0]) {
case BLE_LL_CTRL_TERMINATE_IND:
case BLE_LL_CTRL_REJECT_IND:
case BLE_LL_CTRL_REJECT_IND_EXT:
case BLE_LL_CTRL_START_ENC_REQ:
case BLE_LL_CTRL_START_ENC_RSP:
lifo = 1;
break;
case BLE_LL_CTRL_PAUSE_ENC_RSP:
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
if (connsm->conn_role == BLE_LL_CONN_ROLE_CENTRAL) {
lifo = 1;
}
#endif
break;
case BLE_LL_CTRL_ENC_REQ:
case BLE_LL_CTRL_ENC_RSP:
/* If encryption has been paused, we don't want to send any packets from the
* TX queue, as they would go unencrypted.
*/
if (connsm->enc_data.enc_state == CONN_ENC_S_PAUSED) {
lifo = 1;
}
break;
default:
break;
}
}
}
#endif
/* Add to transmit queue for the connection */
pkthdr = OS_MBUF_PKTHDR(om);
OS_ENTER_CRITICAL(sr);
if (lifo) {
STAILQ_INSERT_HEAD(&connsm->conn_txq, pkthdr, omp_next);
} else {
STAILQ_INSERT_TAIL(&connsm->conn_txq, pkthdr, omp_next);
}
OS_EXIT_CRITICAL(sr);
}
/**
* Data packet from host.
*
* Context: Link Layer task
*
* @param om
* @param handle
* @param length
*
* @return int
*/
void
ble_ll_conn_tx_pkt_in(struct os_mbuf *om, uint16_t handle, uint16_t length)
{
uint8_t hdr_byte;
uint16_t conn_handle;
uint16_t pb;
struct ble_ll_conn_sm *connsm;
/* See if we have an active matching connection handle */
conn_handle = handle & 0x0FFF;
connsm = ble_ll_conn_find_by_handle(conn_handle);
if (connsm) {
/* Construct LL header in buffer (NOTE: pb already checked) */
pb = handle & 0x3000;
if (pb == 0) {
hdr_byte = BLE_LL_LLID_DATA_START;
} else {
hdr_byte = BLE_LL_LLID_DATA_FRAG;
}
/* Add to total l2cap pdus enqueue */
STATS_INC(ble_ll_conn_stats, l2cap_enqueued);
/* Clear flags field in BLE header */
ble_ll_conn_enqueue_pkt(connsm, om, hdr_byte, length);
} else {
/* No connection found! */
STATS_INC(ble_ll_conn_stats, handle_not_found);
os_mbuf_free_chain(om);
}
}
#endif
void
ble_ll_conn_chan_map_update(void)
{
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
struct ble_ll_conn_sm *connsm;
/* Perform channel map update */
SLIST_FOREACH(connsm, &g_ble_ll_conn_active_list, act_sle) {
if (connsm->conn_role == BLE_LL_CONN_ROLE_CENTRAL) {
ble_ll_ctrl_proc_start(connsm, BLE_LL_CTRL_PROC_CHAN_MAP_UPD, NULL);
}
}
#endif
}
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL) || MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
/**
* Called when a device has received a connect request while advertising and
* the connect request has passed the advertising filter policy and is for
* us. This will start a connection in the peripheral role assuming that we dont
* already have a connection with this device and that the connect request
* parameters are valid.
*
* Context: Link Layer
*
* @param rxbuf Pointer to received Connect Request PDU
*
* @return 0: connection not started; 1 connecton started
*/
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
int
ble_ll_conn_periph_start(uint8_t *rxbuf, uint8_t pat, struct ble_mbuf_hdr *rxhdr,
bool force_csa2)
{
int rc;
uint32_t temp;
uint32_t crcinit;
uint8_t *inita;
uint8_t *dptr;
struct ble_ll_conn_sm *connsm;
/* Ignore the connection request if we are already connected*/
inita = rxbuf + BLE_LL_PDU_HDR_LEN;
SLIST_FOREACH(connsm, &g_ble_ll_conn_active_list, act_sle) {
if (!memcmp(&connsm->peer_addr, inita, BLE_DEV_ADDR_LEN)) {
if (rxbuf[0] & BLE_ADV_PDU_HDR_TXADD_MASK) {
if (connsm->peer_addr_type & 1) {
return 0;
}
} else {
if ((connsm->peer_addr_type & 1) == 0) {
return 0;
}
}
}
}
/* Allocate a connection. If none available, dont do anything */
connsm = ble_ll_conn_sm_get();
if (connsm == NULL) {
return 0;
}
/* Set the pointer at the start of the connection data */
dptr = rxbuf + BLE_LL_CONN_REQ_ADVA_OFF + BLE_DEV_ADDR_LEN;
/* Set connection state machine information */
connsm->access_addr = get_le32(dptr);
crcinit = dptr[6];
crcinit = (crcinit << 8) | dptr[5];
crcinit = (crcinit << 8) | dptr[4];
connsm->crcinit = crcinit;
connsm->tx_win_size = dptr[7];
connsm->tx_win_off = get_le16(dptr + 8);
connsm->conn_itvl = get_le16(dptr + 10);
connsm->periph_latency = get_le16(dptr + 12);
connsm->supervision_tmo = get_le16(dptr + 14);
memcpy(&connsm->chan_map, dptr + 16, BLE_LL_CHAN_MAP_LEN);
connsm->hop_inc = dptr[21] & 0x1F;
connsm->central_sca = dptr[21] >> 5;
/* Error check parameters */
if ((connsm->tx_win_off > connsm->conn_itvl) ||
(connsm->conn_itvl < BLE_HCI_CONN_ITVL_MIN) ||
(connsm->conn_itvl > BLE_HCI_CONN_ITVL_MAX) ||
(connsm->tx_win_size < BLE_LL_CONN_TX_WIN_MIN) ||
(connsm->periph_latency > BLE_LL_CONN_PERIPH_LATENCY_MAX) ||
(connsm->hop_inc < 5) || (connsm->hop_inc > 16)) {
goto err_periph_start;
}
/* Slave latency cannot cause a supervision timeout */
temp = (connsm->periph_latency + 1) * (connsm->conn_itvl * 2) *
BLE_LL_CONN_ITVL_USECS;
if ((connsm->supervision_tmo * 10000) <= temp ) {
goto err_periph_start;
}
/*
* The transmit window must be less than or equal to the lesser of 10
* msecs or the connection interval minus 1.25 msecs.
*/
temp = connsm->conn_itvl - 1;
if (temp > 8) {
temp = 8;
}
if (connsm->tx_win_size > temp) {
goto err_periph_start;
}
/* Set the address of device that we are connecting with */
memcpy(&connsm->peer_addr, inita, BLE_DEV_ADDR_LEN);
connsm->peer_addr_type = pat;
/* Calculate number of used channels; make sure it meets min requirement */
connsm->chan_map_used = ble_ll_utils_chan_map_used_get(connsm->chan_map);
if (connsm->chan_map_used < 2) {
goto err_periph_start;
}
ble_ll_conn_itvl_to_ticks(connsm->conn_itvl, &connsm->conn_itvl_ticks,
&connsm->conn_itvl_usecs);
/* Start the connection state machine */
connsm->conn_role = BLE_LL_CONN_ROLE_PERIPHERAL;
ble_ll_conn_sm_new(connsm);
#if MYNEWT_VAL(BLE_LL_PHY)
/* Use the same PHY as we received CONNECT_REQ on */
ble_ll_conn_init_phy(connsm, rxhdr->rxinfo.phy);
#endif
ble_ll_conn_set_csa(connsm,
force_csa2 || (rxbuf[0] & BLE_ADV_PDU_HDR_CHSEL_MASK));
/* Set initial schedule callback */
connsm->conn_sch.sched_cb = ble_ll_conn_event_start_cb;
rc = ble_ll_conn_created(connsm, rxhdr);
if (!rc) {
SLIST_REMOVE(&g_ble_ll_conn_active_list, connsm, ble_ll_conn_sm, act_sle);
STAILQ_INSERT_TAIL(&g_ble_ll_conn_free_list, connsm, free_stqe);
}
return rc;
err_periph_start:
STAILQ_INSERT_TAIL(&g_ble_ll_conn_free_list, connsm, free_stqe);
STATS_INC(ble_ll_conn_stats, periph_rxd_bad_conn_req_params);
return 0;
}
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
int
ble_ll_conn_subrate_req_hci(struct ble_ll_conn_sm *connsm,
struct ble_ll_conn_subrate_req_params *srp)
{
uint32_t t1, t2;
if ((srp->subrate_min < 0x0001) || (srp->subrate_min > 0x01f4) ||
(srp->subrate_max < 0x0001) || (srp->subrate_max > 0x01f4) ||
(srp->max_latency > 0x01f3) || (srp->cont_num > 0x01f3) ||
(srp->supervision_tmo < 0x000a) || (srp->supervision_tmo > 0x0c80)) {
return -EINVAL;
}
if (srp->subrate_max * (srp->max_latency + 1) > 500) {
return -EINVAL;
}
t1 = connsm->conn_itvl * srp->subrate_max * (srp->max_latency + 1) *
BLE_LL_CONN_ITVL_USECS;
t2 = srp->supervision_tmo * BLE_HCI_CONN_SPVN_TMO_UNITS * 1000 / 2;
if (t1 > t2) {
return -EINVAL;
}
if (srp->subrate_max < srp->subrate_min) {
return -EINVAL;
}
if (srp->cont_num >= srp->subrate_max) {
return -EINVAL;
}
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
if ((connsm->conn_role == BLE_LL_CONN_ROLE_CENTRAL) &&
!ble_ll_conn_rem_feature_check(connsm,
BLE_LL_FEAT_CONN_SUBRATING_HOST)) {
return -ENOTSUP;
}
#endif
if (connsm->cur_ctrl_proc == BLE_LL_CTRL_PROC_CONN_PARAM_REQ) {
return -EBUSY;
}
switch (connsm->conn_role) {
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
case BLE_LL_CONN_ROLE_CENTRAL:
connsm->subrate_trans.subrate_factor = srp->subrate_max;
connsm->subrate_trans.subrate_base_event = connsm->event_cntr;
connsm->subrate_trans.periph_latency = srp->max_latency;
connsm->subrate_trans.cont_num = srp->cont_num;
connsm->subrate_trans.supervision_tmo = srp->supervision_tmo;
connsm->flags.subrate_host_req = 1;
ble_ll_ctrl_proc_start(connsm, BLE_LL_CTRL_PROC_SUBRATE_UPDATE, NULL);
break;
#endif
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
case BLE_LL_CONN_ROLE_PERIPHERAL:
connsm->subrate_req = *srp;
connsm->flags.subrate_host_req = 1;
ble_ll_ctrl_proc_start(connsm, BLE_LL_CTRL_PROC_SUBRATE_REQ, NULL);
break;
#endif
default:
BLE_LL_ASSERT(0);
}
return 0;
}
int
ble_ll_conn_subrate_req_llcp(struct ble_ll_conn_sm *connsm,
struct ble_ll_conn_subrate_req_params *srp)
{
BLE_LL_ASSERT(connsm->conn_role == BLE_LL_CONN_ROLE_CENTRAL);
if ((srp->subrate_min < 0x0001) || (srp->subrate_min > 0x01f4) ||
(srp->subrate_max < 0x0001) || (srp->subrate_max > 0x01f4) ||
(srp->max_latency > 0x01f3) || (srp->cont_num > 0x01f3) ||
(srp->supervision_tmo < 0x000a) || (srp->supervision_tmo > 0x0c80)) {
return -EINVAL;
}
if (connsm->cur_ctrl_proc == BLE_LL_CTRL_PROC_CONN_PARAM_REQ) {
return -EBUSY;
}
if ((srp->max_latency > connsm->acc_max_latency) ||
(srp->supervision_tmo > connsm->acc_supervision_tmo) ||
(srp->subrate_max < connsm->acc_subrate_min) ||
(srp->subrate_min > connsm->acc_subrate_max) ||
((connsm->conn_itvl * BLE_LL_CONN_ITVL_USECS * srp->subrate_min *
(srp->max_latency + 1)) * 2 >= srp->supervision_tmo *
BLE_HCI_CONN_SPVN_TMO_UNITS * 1000)) {
return -EINVAL;
}
connsm->subrate_trans.subrate_factor = MIN(connsm->acc_subrate_max,
srp->subrate_max);
connsm->subrate_trans.subrate_base_event = connsm->event_cntr;
connsm->subrate_trans.periph_latency = MIN(connsm->acc_max_latency,
srp->max_latency);
connsm->subrate_trans.cont_num = MIN(MAX(connsm->acc_cont_num,
srp->cont_num),
connsm->subrate_trans.subrate_factor - 1);
connsm->subrate_trans.supervision_tmo = MIN(connsm->supervision_tmo,
srp->supervision_tmo);
ble_ll_ctrl_proc_start(connsm, BLE_LL_CTRL_PROC_SUBRATE_UPDATE, NULL);
return 0;
}
void
ble_ll_conn_subrate_set(struct ble_ll_conn_sm *connsm,
struct ble_ll_conn_subrate_params *sp)
{
int16_t event_cntr_diff;
int16_t subrate_events_diff;
uint8_t send_ev;
/* Assume parameters were checked by caller */
send_ev = connsm->flags.subrate_host_req ||
(connsm->subrate_factor != sp->subrate_factor) ||
(connsm->periph_latency != sp->periph_latency) ||
(connsm->cont_num != sp->cont_num) ||
(connsm->supervision_tmo != sp->supervision_tmo);
connsm->subrate_factor = sp->subrate_factor;
connsm->subrate_base_event = sp->subrate_base_event;
connsm->periph_latency = sp->periph_latency;
connsm->cont_num = sp->cont_num;
connsm->supervision_tmo = sp->supervision_tmo;
/* Let's update subrate base event to "latest" one */
event_cntr_diff = connsm->event_cntr - connsm->subrate_base_event;
subrate_events_diff = event_cntr_diff / connsm->subrate_factor;
connsm->subrate_base_event += connsm->subrate_factor * subrate_events_diff;
if (send_ev) {
ble_ll_hci_ev_subrate_change(connsm, 0);
}
}
#endif
#define MAX_TIME_UNCODED(_maxbytes) \
ble_ll_pdu_us(_maxbytes + BLE_LL_DATA_MIC_LEN, BLE_PHY_MODE_1M);
#define MAX_TIME_CODED(_maxbytes) \
ble_ll_pdu_us(_maxbytes + BLE_LL_DATA_MIC_LEN, BLE_PHY_MODE_CODED_125KBPS);
/**
* Called to reset the connection module. When this function is called the
* scheduler has been stopped and the phy has been disabled. The LL should
* be in the standby state.
*
* Context: Link Layer task
*/
void
ble_ll_conn_module_reset(void)
{
uint8_t max_phy_pyld;
uint16_t maxbytes;
struct ble_ll_conn_sm *connsm;
struct ble_ll_conn_global_params *conn_params;
/* Kill the current one first (if one is running) */
if (g_ble_ll_conn_cur_sm) {
connsm = g_ble_ll_conn_cur_sm;
g_ble_ll_conn_cur_sm = NULL;
ble_ll_conn_end(connsm, BLE_ERR_SUCCESS);
}
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
/* Free the global connection complete event if there is one */
if (g_ble_ll_conn_comp_ev) {
ble_transport_free(g_ble_ll_conn_comp_ev);
g_ble_ll_conn_comp_ev = NULL;
}
/* Reset connection we are attempting to create */
g_ble_ll_conn_create_sm.connsm = NULL;
#endif
/* Now go through and end all the connections */
while (1) {
connsm = SLIST_FIRST(&g_ble_ll_conn_active_list);
if (!connsm) {
break;
}
ble_ll_conn_end(connsm, BLE_ERR_SUCCESS);
}
/* Get the maximum supported PHY PDU size from the PHY */
max_phy_pyld = ble_phy_max_data_pdu_pyld();
/* Configure the global LL parameters */
conn_params = &g_ble_ll_conn_params;
maxbytes = MIN(MYNEWT_VAL(BLE_LL_SUPP_MAX_RX_BYTES), max_phy_pyld);
conn_params->supp_max_rx_octets = maxbytes;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY)
conn_params->supp_max_rx_time = MAX_TIME_CODED(maxbytes);
#else
conn_params->supp_max_rx_time = MAX_TIME_UNCODED(maxbytes);
#endif
maxbytes = MIN(MYNEWT_VAL(BLE_LL_SUPP_MAX_TX_BYTES), max_phy_pyld);
conn_params->supp_max_tx_octets = maxbytes;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY)
conn_params->supp_max_tx_time = MAX_TIME_CODED(maxbytes);
#else
conn_params->supp_max_tx_time = MAX_TIME_UNCODED(maxbytes);
#endif
maxbytes = MIN(MYNEWT_VAL(BLE_LL_CONN_INIT_MAX_TX_BYTES), max_phy_pyld);
conn_params->conn_init_max_tx_octets = maxbytes;
conn_params->conn_init_max_tx_time = MAX_TIME_UNCODED(maxbytes);
conn_params->conn_init_max_tx_time_uncoded = MAX_TIME_UNCODED(maxbytes);
conn_params->conn_init_max_tx_time_coded = MAX_TIME_CODED(maxbytes);
conn_params->sugg_tx_octets = BLE_LL_CONN_SUPP_BYTES_MIN;
conn_params->sugg_tx_time = BLE_LL_CONN_SUPP_TIME_MIN;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
conn_params->acc_subrate_min = 0x0001;
conn_params->acc_subrate_max = 0x0001;
conn_params->acc_max_latency = 0x0000;
conn_params->acc_cont_num = 0x0000;
conn_params->acc_supervision_tmo = 0x0c80;
#endif
/* Reset statistics */
STATS_RESET(ble_ll_conn_stats);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV_SYNC_TRANSFER)
/* reset default sync transfer params */
g_ble_ll_conn_sync_transfer_params.max_skip = 0;
g_ble_ll_conn_sync_transfer_params.mode = 0;
g_ble_ll_conn_sync_transfer_params.sync_timeout_us = 0;
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_CTRL_TO_HOST_FLOW_CONTROL)
g_ble_ll_conn_cth_flow.enabled = false;
g_ble_ll_conn_cth_flow.max_buffers = 1;
g_ble_ll_conn_cth_flow.num_buffers = 1;
#endif
#if MYNEWT_VAL(BLE_LL_CONN_STRICT_SCHED)
g_ble_ll_conn_css_next_slot = BLE_LL_CONN_CSS_NO_SLOT;
#endif
}
/* Initialize the connection module */
void
ble_ll_conn_module_init(void)
{
int rc;
uint16_t i;
struct ble_ll_conn_sm *connsm;
/* Initialize list of active connections */
SLIST_INIT(&g_ble_ll_conn_active_list);
STAILQ_INIT(&g_ble_ll_conn_free_list);
#if MYNEWT_VAL(BLE_LL_CONN_STRICT_SCHED)
SLIST_INIT(&g_ble_ll_conn_css_list);
#endif
/*
* Take all the connections off the free memory pool and add them to
* the free connection list, assigning handles in linear order. Note:
* the specification allows a handle of zero; we just avoid using it.
*/
connsm = &g_ble_ll_conn_sm[0];
for (i = 0; i < MYNEWT_VAL(BLE_MAX_CONNECTIONS); ++i) {
memset(connsm, 0, sizeof(struct ble_ll_conn_sm));
connsm->conn_handle = i + 1;
STAILQ_INSERT_TAIL(&g_ble_ll_conn_free_list, connsm, free_stqe);
/* Initialize fixed schedule elements */
connsm->conn_sch.sched_type = BLE_LL_SCHED_TYPE_CONN;
connsm->conn_sch.cb_arg = connsm;
ble_ll_ctrl_init_conn_sm(connsm);
++connsm;
}
/* Register connection statistics */
rc = stats_init_and_reg(STATS_HDR(ble_ll_conn_stats),
STATS_SIZE_INIT_PARMS(ble_ll_conn_stats, STATS_SIZE_32),
STATS_NAME_INIT_PARMS(ble_ll_conn_stats),
"ble_ll_conn");
BLE_LL_ASSERT(rc == 0);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_CTRL_TO_HOST_FLOW_CONTROL)
ble_npl_event_init(&g_ble_ll_conn_cth_flow_error_ev,
ble_ll_conn_cth_flow_error_fn, NULL);
#endif
/* Call reset to finish reset of initialization */
ble_ll_conn_module_reset();
}
#endif