/*
|
* 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 <syscfg/syscfg.h>
|
|
#if MYNEWT_VAL(BLE_LL_ROLE_OBSERVER) && MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
|
|
#include <stdint.h>
|
#include <stdlib.h>
|
#include <string.h>
|
#include <assert.h>
|
#include "os/os.h"
|
#include "nimble/ble.h"
|
#include "nimble/hci_common.h"
|
#include "controller/ble_phy.h"
|
#include "controller/ble_hw.h"
|
#include "controller/ble_ll.h"
|
#include "controller/ble_ll_sched.h"
|
#include "controller/ble_ll_scan.h"
|
#include "controller/ble_ll_scan_aux.h"
|
#include "controller/ble_ll_hci.h"
|
#include "controller/ble_ll_whitelist.h"
|
#include "controller/ble_ll_resolv.h"
|
#include "controller/ble_ll_sync.h"
|
|
#define BLE_LL_SCAN_AUX_F_AUX_ADV 0x0001
|
#define BLE_LL_SCAN_AUX_F_AUX_CHAIN 0x0002
|
#define BLE_LL_SCAN_AUX_F_MATCHED 0x0004
|
#define BLE_LL_SCAN_AUX_F_W4_SCAN_RSP 0x0008
|
#define BLE_LL_SCAN_AUX_F_SCANNED 0x0010
|
#define BLE_LL_SCAN_AUX_F_HAS_ADVA 0x0020
|
#define BLE_LL_SCAN_AUX_F_HAS_TARGETA 0x0040
|
#define BLE_LL_SCAN_AUX_F_HAS_ADI 0x0080
|
#define BLE_LL_SCAN_AUX_F_RESOLVED_ADVA 0x0100
|
#define BLE_LL_SCAN_AUX_F_RESOLVED_TARGETA 0x0200
|
#define BLE_LL_SCAN_AUX_F_CONNECTABLE 0x0400
|
#define BLE_LL_SCAN_AUX_F_W4_CONNECT_RSP 0x0800
|
|
#define BLE_LL_SCAN_AUX_H_SENT_ANY 0x01
|
#define BLE_LL_SCAN_AUX_H_DONE 0x02
|
#define BLE_LL_SCAN_AUX_H_TRUNCATED 0x04
|
|
struct ble_ll_scan_aux_data {
|
uint16_t flags;
|
uint8_t hci_state;
|
|
uint8_t scan_type;
|
|
uint8_t pri_phy;
|
uint8_t sec_phy;
|
uint8_t chan;
|
uint8_t offset_unit : 1;
|
uint32_t aux_ptr;
|
struct ble_ll_sched_item sch;
|
struct ble_npl_event break_ev;
|
struct ble_hci_ev *hci_ev;
|
|
uint16_t adi;
|
|
uint8_t adva[6];
|
uint8_t targeta[6];
|
uint8_t adva_type : 1;
|
uint8_t targeta_type : 1;
|
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
int8_t rpa_index;
|
#endif
|
};
|
|
#define AUX_MEMPOOL_SIZE (OS_MEMPOOL_SIZE( \
|
MYNEWT_VAL(BLE_LL_SCAN_AUX_SEGMENT_CNT), \
|
sizeof(struct ble_ll_scan_aux_data)))
|
|
static os_membuf_t aux_data_mem[AUX_MEMPOOL_SIZE];
|
static struct os_mempool aux_data_pool;
|
|
static struct ble_ll_scan_aux_data *aux_data_current;
|
|
static void ble_ll_hci_ev_send_ext_adv_truncated_report(struct ble_ll_scan_aux_data *aux);
|
|
static inline uint8_t *
|
ble_ll_scan_aux_get_own_addr(void)
|
{
|
uint8_t own_addr_type;
|
|
own_addr_type = ble_ll_scan_get_own_addr_type() & 1;
|
|
return ble_ll_get_our_devaddr(own_addr_type);
|
}
|
|
static int
|
ble_ll_scan_aux_sched_cb(struct ble_ll_sched_item *sch)
|
{
|
struct ble_ll_scan_aux_data *aux = sch->cb_arg;
|
uint32_t wfr_us;
|
#if BLE_LL_BT5_PHY_SUPPORTED
|
uint8_t phy_mode;
|
#endif
|
uint8_t lls;
|
int rc;
|
|
BLE_LL_ASSERT(aux);
|
|
lls = ble_ll_state_get();
|
BLE_LL_ASSERT(lls == BLE_LL_STATE_STANDBY);
|
|
rc = ble_phy_setchan(aux->chan, BLE_ACCESS_ADDR_ADV, BLE_LL_CRCINIT_ADV);
|
BLE_LL_ASSERT(rc == 0);
|
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
|
ble_phy_encrypt_disable();
|
#endif
|
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
if (ble_ll_resolv_enabled()) {
|
ble_phy_resolv_list_enable();
|
} else {
|
ble_phy_resolv_list_disable();
|
}
|
#endif
|
|
#if BLE_LL_BT5_PHY_SUPPORTED
|
phy_mode = ble_ll_phy_to_phy_mode(aux->sec_phy, BLE_HCI_LE_PHY_CODED_ANY);
|
ble_phy_mode_set(phy_mode, phy_mode);
|
#endif
|
|
rc = ble_phy_rx_set_start_time(sch->start_time + g_ble_ll_sched_offset_ticks,
|
sch->remainder);
|
if (rc != 0 && rc != BLE_PHY_ERR_RX_LATE) {
|
ble_ll_scan_aux_break(aux);
|
return BLE_LL_SCHED_STATE_DONE;
|
}
|
|
/* Keep listening even if we are late, we may still receive something */
|
|
if (ble_ll_scan_get_filt_policy() & 1) {
|
ble_ll_whitelist_enable();
|
} else {
|
ble_ll_whitelist_disable();
|
}
|
|
wfr_us = aux->offset_unit ? 300 : 30;
|
ble_phy_wfr_enable(BLE_PHY_WFR_ENABLE_RX, 0, wfr_us);
|
|
aux_data_current = aux;
|
|
ble_ll_state_set(BLE_LL_STATE_SCAN_AUX);
|
|
return BLE_LL_SCHED_STATE_RUNNING;
|
}
|
|
static struct ble_ll_scan_aux_data *
|
ble_ll_scan_aux_alloc(void)
|
{
|
struct ble_ll_scan_aux_data *aux;
|
|
aux = os_memblock_get(&aux_data_pool);
|
if (!aux) {
|
return NULL;
|
}
|
|
memset(aux, 0, sizeof(*aux));
|
|
aux->sch.sched_cb = ble_ll_scan_aux_sched_cb;
|
aux->sch.sched_type = BLE_LL_SCHED_TYPE_SCAN_AUX;
|
aux->sch.cb_arg = aux;
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
aux->rpa_index = -1;
|
#endif
|
|
return aux;
|
}
|
|
static void
|
ble_ll_scan_aux_free(struct ble_ll_scan_aux_data *aux)
|
{
|
BLE_LL_ASSERT(!aux->sch.enqueued);
|
BLE_LL_ASSERT(aux->hci_ev == NULL);
|
BLE_LL_ASSERT((aux->hci_state & BLE_LL_SCAN_AUX_H_DONE) ||
|
!(aux->hci_state & BLE_LL_SCAN_AUX_H_SENT_ANY));
|
|
os_memblock_put(&aux_data_pool, aux);
|
}
|
|
static void
|
ble_ll_scan_aux_update_scan_backoff(struct ble_ll_scan_aux_data *aux)
|
{
|
if (!(aux->flags & BLE_LL_SCAN_AUX_F_W4_SCAN_RSP) &&
|
!(aux->flags & BLE_LL_SCAN_AUX_F_SCANNED)) {
|
return;
|
}
|
|
if ((aux->hci_state & BLE_LL_SCAN_AUX_H_DONE) &&
|
!(aux->hci_state & BLE_LL_SCAN_AUX_H_TRUNCATED)) {
|
ble_ll_scan_backoff_update(1);
|
} else {
|
ble_ll_scan_backoff_update(0);
|
}
|
}
|
|
static inline bool
|
ble_ll_scan_aux_need_truncation(struct ble_ll_scan_aux_data *aux)
|
{
|
return (aux->hci_state & BLE_LL_SCAN_AUX_H_SENT_ANY) &&
|
!(aux->hci_state & BLE_LL_SCAN_AUX_H_DONE);
|
}
|
|
static struct ble_hci_ev *
|
ble_ll_hci_ev_alloc_ext_adv_report_for_aux(struct ble_ll_scan_addr_data *addrd,
|
struct ble_ll_scan_aux_data *aux)
|
{
|
struct ble_hci_ev_le_subev_ext_adv_rpt *hci_subev;
|
struct ext_adv_report *report;
|
struct ble_hci_ev *hci_ev;
|
|
hci_ev = ble_transport_alloc_evt(1);
|
if (!hci_ev) {
|
return NULL;
|
}
|
|
hci_ev->opcode = BLE_HCI_EVCODE_LE_META;
|
hci_ev->length = sizeof(*hci_subev) + sizeof(*report);
|
|
hci_subev = (void *)hci_ev->data;
|
hci_subev->subev_code = BLE_HCI_LE_SUBEV_EXT_ADV_RPT;
|
hci_subev->num_reports = 1;
|
|
report = hci_subev->reports;
|
|
memset(report, 0, sizeof(*report));
|
|
report->evt_type = 0;
|
if (addrd->adva) {
|
report->addr_type = addrd->adv_addr_type;
|
memcpy(report->addr, addrd->adv_addr, 6);
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
if (addrd->adva_resolved) {
|
report->addr_type += 2;
|
}
|
#endif
|
} else {
|
report->addr_type = 0xff;
|
}
|
report->pri_phy = aux->pri_phy;
|
report->sec_phy = aux->sec_phy;
|
report->sid = aux->adi >> 12;
|
report->tx_power = 0x7f;
|
report->rssi = 0x7f;
|
report->periodic_itvl = 0;
|
if (addrd->targeta) {
|
report->evt_type |= BLE_HCI_ADV_DIRECT_MASK;
|
report->dir_addr_type = addrd->targeta_type;
|
memcpy(report->dir_addr, addrd->targeta, 6);
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
if (addrd->targeta_resolved) {
|
report->dir_addr_type += 2;
|
}
|
#endif
|
}
|
report->data_len = 0;
|
|
return hci_ev;
|
}
|
|
static struct ble_hci_ev *
|
ble_ll_hci_ev_dup_ext_adv_report(struct ble_hci_ev *hci_ev_src)
|
{
|
struct ble_hci_ev_le_subev_ext_adv_rpt *hci_subev;
|
struct ext_adv_report *report;
|
struct ble_hci_ev *hci_ev;
|
|
hci_ev = ble_transport_alloc_evt(1);
|
if (!hci_ev) {
|
return NULL;
|
}
|
|
memcpy(hci_ev, hci_ev_src, sizeof(*hci_ev) + sizeof(*hci_subev) +
|
sizeof(*report));
|
hci_ev->length = sizeof(*hci_subev) + sizeof(*report);
|
|
hci_subev = (void *)hci_ev->data;
|
|
report = hci_subev->reports;
|
report->data_len = 0;
|
|
return hci_ev;
|
}
|
|
static void
|
ble_ll_hci_ev_update_ext_adv_report_from_aux(struct ble_hci_ev *hci_ev,
|
struct os_mbuf *rxpdu,
|
struct ble_mbuf_hdr *rxhdr)
|
{
|
struct ble_hci_ev_le_subev_ext_adv_rpt *hci_subev;
|
struct ext_adv_report *report;
|
uint8_t adv_mode;
|
uint8_t eh_len;
|
uint8_t eh_flags;
|
uint8_t *eh_data;
|
uint8_t *rxbuf;
|
|
hci_subev = (void *)hci_ev->data;
|
report = hci_subev->reports;
|
rxbuf = rxpdu->om_data;
|
|
adv_mode = rxbuf[2] >> 6;
|
eh_len = rxbuf[2] & 0x3f;
|
eh_flags = rxbuf[3];
|
eh_data = &rxbuf[4];
|
|
report->evt_type |= adv_mode;
|
if (rxhdr->rxinfo.flags & BLE_MBUF_HDR_F_SCAN_RSP_RXD) {
|
report->evt_type |= BLE_HCI_ADV_SCAN_MASK | BLE_HCI_ADV_SCAN_RSP_MASK;
|
}
|
report->sec_phy = rxhdr->rxinfo.phy;
|
|
/* Strip PDU header and ext header, leave only AD */
|
os_mbuf_adj(rxpdu, 3 + eh_len);
|
|
/*
|
* We only care about SyncInfo and TxPower so don't bother parsing if they
|
* are not present, just set to 'unknown' values.
|
*/
|
if ((eh_len == 0) || !(eh_flags & ((1 << BLE_LL_EXT_ADV_SYNC_INFO_BIT) |
|
(1 << BLE_LL_EXT_ADV_TX_POWER_BIT)))) {
|
report->periodic_itvl = 0;
|
report->tx_power = 0x7f;
|
return;
|
}
|
/* Now parse extended header... */
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_ADVA_BIT)) {
|
eh_data += BLE_LL_EXT_ADV_ADVA_SIZE;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_TARGETA_BIT)) {
|
eh_data += BLE_LL_EXT_ADV_TARGETA_SIZE;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_CTE_INFO_BIT)) {
|
eh_data += BLE_LL_EXT_ADV_CTE_INFO_SIZE;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_DATA_INFO_BIT)) {
|
eh_data += BLE_LL_EXT_ADV_DATA_INFO_SIZE;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_AUX_PTR_BIT)) {
|
eh_data += BLE_LL_EXT_ADV_AUX_PTR_SIZE;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_SYNC_INFO_BIT)) {
|
report->periodic_itvl = get_le16(eh_data + 2);
|
eh_data += BLE_LL_EXT_ADV_SYNC_INFO_SIZE;
|
} else {
|
report->periodic_itvl = 0;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_TX_POWER_BIT)) {
|
report->tx_power = *eh_data;
|
} else {
|
report->tx_power = 0x7f;
|
}
|
}
|
|
static void
|
ble_ll_hci_ev_update_ext_adv_report_from_ext(struct ble_hci_ev *hci_ev,
|
struct os_mbuf *rxpdu,
|
struct ble_mbuf_hdr *rxhdr)
|
{
|
struct ble_mbuf_hdr_rxinfo *rxinfo = &rxhdr->rxinfo;
|
struct ble_hci_ev_le_subev_ext_adv_rpt *hci_subev;
|
struct ext_adv_report *report;
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
struct ble_ll_resolv_entry *rl;
|
#endif
|
uint8_t pdu_hdr;
|
uint8_t adv_mode;
|
uint8_t eh_len;
|
uint8_t eh_flags;
|
uint8_t *eh_data;
|
uint8_t *rxbuf;
|
|
rxbuf = rxpdu->om_data;
|
|
pdu_hdr = rxbuf[0];
|
adv_mode = rxbuf[2] >> 6;
|
eh_len = rxbuf[2] & 0x3f;
|
eh_flags = rxbuf[3];
|
eh_data = &rxbuf[4];
|
|
hci_ev->opcode = BLE_HCI_EVCODE_LE_META;
|
hci_ev->length = sizeof(*hci_subev) + sizeof(*report);
|
|
hci_subev = (void *)hci_ev->data;
|
hci_subev->subev_code = BLE_HCI_LE_SUBEV_EXT_ADV_RPT;
|
hci_subev->num_reports = 1;
|
|
report = hci_subev->reports;
|
|
memset(report, 0, sizeof(*report));
|
|
report->evt_type = adv_mode;
|
|
report->pri_phy = rxinfo->phy;
|
report->sec_phy = 0;
|
report->sid = 0xff;
|
report->rssi = rxhdr->rxinfo.rssi;
|
report->periodic_itvl = 0;
|
report->data_len = 0;
|
|
/* Now parse extended header... */
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_ADVA_BIT)) {
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
if (rxinfo->rpa_index >= 0) {
|
rl = &g_ble_ll_resolv_list[rxinfo->rpa_index];
|
report->addr_type = rl->rl_addr_type + 2;
|
memcpy(report->addr, rl->rl_identity_addr, 6);
|
} else {
|
report->addr_type = !!(pdu_hdr & BLE_ADV_PDU_HDR_TXADD_MASK);
|
memcpy(report->addr, eh_data, 6);
|
}
|
#else
|
report->addr_type = !!(pdu_hdr & BLE_ADV_PDU_HDR_TXADD_MASK);
|
memcpy(report->addr, eh_data, 6);
|
#endif
|
eh_data += BLE_LL_EXT_ADV_ADVA_SIZE;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_TARGETA_BIT)) {
|
report->evt_type |= BLE_HCI_ADV_DIRECT_MASK;
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
if (rxinfo->flags & BLE_MBUF_HDR_F_TARGETA_RESOLVED) {
|
report->dir_addr_type = (ble_ll_scan_get_own_addr_type() & 1) + 2;
|
memcpy(report->dir_addr, ble_ll_scan_aux_get_own_addr(), 6);
|
} else {
|
report->dir_addr_type = !!(pdu_hdr & BLE_ADV_PDU_HDR_RXADD_MASK);
|
memcpy(report->dir_addr, eh_data, 6);
|
}
|
#else
|
report->dir_addr_type = !!(pdu_hdr & BLE_ADV_PDU_HDR_RXADD_MASK);
|
memcpy(report->dir_addr, eh_data, 6);
|
#endif
|
eh_data += BLE_LL_EXT_ADV_TARGETA_SIZE;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_CTE_INFO_BIT)) {
|
eh_data += BLE_LL_EXT_ADV_CTE_INFO_SIZE;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_DATA_INFO_BIT)) {
|
eh_data += BLE_LL_EXT_ADV_DATA_INFO_SIZE;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_AUX_PTR_BIT)) {
|
eh_data += BLE_LL_EXT_ADV_AUX_PTR_SIZE;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_SYNC_INFO_BIT)) {
|
eh_data += BLE_LL_EXT_ADV_SYNC_INFO_SIZE;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_TX_POWER_BIT)) {
|
report->tx_power = *eh_data;
|
} else {
|
report->tx_power = 0x7f;
|
}
|
|
/* Strip PDU header and ext header, leave only AD */
|
os_mbuf_adj(rxpdu, 3 + eh_len);
|
|
}
|
|
static void
|
ble_ll_hci_ev_send_ext_adv_truncated_report(struct ble_ll_scan_aux_data *aux)
|
{
|
struct ble_hci_ev_le_subev_ext_adv_rpt *hci_subev;
|
struct ext_adv_report *report;
|
struct ble_hci_ev *hci_ev;
|
|
if (!ble_ll_hci_is_le_event_enabled(BLE_HCI_LE_SUBEV_EXT_ADV_RPT)) {
|
return;
|
}
|
|
hci_ev = aux->hci_ev;
|
aux->hci_ev = NULL;
|
|
BLE_LL_ASSERT(hci_ev);
|
|
hci_subev = (void *)hci_ev->data;
|
report = hci_subev->reports;
|
report->evt_type |= BLE_HCI_ADV_DATA_STATUS_TRUNCATED;
|
|
ble_ll_hci_event_send(hci_ev);
|
|
aux->hci_state |= BLE_LL_SCAN_AUX_H_DONE | BLE_LL_SCAN_AUX_H_TRUNCATED;
|
}
|
|
static int
|
ble_ll_hci_ev_send_ext_adv_report(struct os_mbuf *rxpdu,
|
struct ble_mbuf_hdr *rxhdr,
|
struct ble_hci_ev **hci_ev)
|
{
|
struct ble_mbuf_hdr_rxinfo *rxinfo = &rxhdr->rxinfo;
|
struct ble_hci_ev_le_subev_ext_adv_rpt *hci_subev;
|
struct ble_hci_ev *hci_ev_next;
|
struct ext_adv_report *report;
|
bool truncated = false;
|
int max_data_len;
|
int data_len;
|
int offset;
|
|
data_len = OS_MBUF_PKTLEN(rxpdu);
|
max_data_len = BLE_LL_MAX_EVT_LEN -
|
sizeof(**hci_ev) - sizeof(*hci_subev) - sizeof(*report);
|
offset = 0;
|
hci_ev_next = NULL;
|
|
do {
|
hci_subev = (void *)(*hci_ev)->data;
|
report = hci_subev->reports;
|
|
report->rssi = rxinfo->rssi;
|
|
report->data_len = min(max_data_len, data_len - offset);
|
os_mbuf_copydata(rxpdu, offset, report->data_len, report->data);
|
(*hci_ev)->length += report->data_len;
|
|
offset += report->data_len;
|
|
/*
|
* We need another event if either there are still some data left in
|
* this PDU or scan for next aux is scheduled.
|
*/
|
if ((offset < data_len) ||
|
(rxinfo->flags & BLE_MBUF_HDR_F_AUX_PTR_WAIT)) {
|
hci_ev_next = ble_ll_hci_ev_dup_ext_adv_report(*hci_ev);
|
if (hci_ev_next) {
|
report->evt_type |= BLE_HCI_ADV_DATA_STATUS_INCOMPLETE;
|
} else {
|
report->evt_type |= BLE_HCI_ADV_DATA_STATUS_TRUNCATED;
|
}
|
}
|
|
switch (report->evt_type & BLE_HCI_ADV_DATA_STATUS_MASK) {
|
case BLE_HCI_ADV_DATA_STATUS_TRUNCATED:
|
truncated = true;
|
/* no break */
|
case BLE_HCI_ADV_DATA_STATUS_COMPLETE:
|
BLE_LL_ASSERT(!hci_ev_next);
|
break;
|
case BLE_HCI_ADV_DATA_STATUS_INCOMPLETE:
|
BLE_LL_ASSERT(hci_ev_next);
|
break;
|
default:
|
BLE_LL_ASSERT(0);
|
}
|
|
ble_ll_hci_event_send(*hci_ev);
|
|
*hci_ev = hci_ev_next;
|
hci_ev_next = NULL;
|
} while ((offset < data_len) && *hci_ev);
|
|
return truncated ? -1 : 0;
|
}
|
|
|
static int
|
ble_ll_hci_ev_send_ext_adv_report_for_aux(struct os_mbuf *rxpdu,
|
struct ble_mbuf_hdr *rxhdr,
|
struct ble_ll_scan_aux_data *aux,
|
struct ble_ll_scan_addr_data *addrd)
|
{
|
struct ble_hci_ev *hci_ev;
|
int rc;
|
|
if (!ble_ll_hci_is_le_event_enabled(BLE_HCI_LE_SUBEV_EXT_ADV_RPT)) {
|
return -1;
|
}
|
|
/*
|
* We need to always keep one event allocated in aux to be able to truncate
|
* data properly in case of an error. If there is no event in aux it means
|
* this is first event and we can silently ignore in case of an error.
|
*/
|
if (aux->hci_ev) {
|
hci_ev = aux->hci_ev;
|
aux->hci_ev = NULL;
|
} else {
|
hci_ev = ble_ll_hci_ev_alloc_ext_adv_report_for_aux(addrd, aux);
|
if (!hci_ev) {
|
return -1;
|
}
|
}
|
|
ble_ll_hci_ev_update_ext_adv_report_from_aux(hci_ev, rxpdu, rxhdr);
|
|
rc = ble_ll_hci_ev_send_ext_adv_report(rxpdu, rxhdr, &hci_ev);
|
if (rc < 0) {
|
BLE_LL_ASSERT(!hci_ev);
|
aux->hci_state = BLE_LL_SCAN_AUX_H_DONE | BLE_LL_SCAN_AUX_H_TRUNCATED;
|
} else if (hci_ev) {
|
aux->hci_state = BLE_LL_SCAN_AUX_H_SENT_ANY;
|
} else {
|
aux->hci_state = BLE_LL_SCAN_AUX_H_DONE;
|
}
|
|
aux->hci_ev = hci_ev;
|
|
return rc;
|
}
|
|
static void
|
ble_ll_hci_ev_send_ext_adv_report_for_ext(struct os_mbuf *rxpdu,
|
struct ble_mbuf_hdr *rxhdr)
|
{
|
struct ble_hci_ev *hci_ev;
|
|
if (!ble_ll_hci_is_le_event_enabled(BLE_HCI_LE_SUBEV_EXT_ADV_RPT)) {
|
return;
|
}
|
|
hci_ev = ble_transport_alloc_evt(1);
|
if (!hci_ev) {
|
return;
|
}
|
|
ble_ll_hci_ev_update_ext_adv_report_from_ext(hci_ev, rxpdu, rxhdr);
|
|
ble_ll_hci_ev_send_ext_adv_report(rxpdu, rxhdr, &hci_ev);
|
|
BLE_LL_ASSERT(!hci_ev);
|
}
|
|
static void
|
ble_ll_scan_aux_break_ev(struct ble_npl_event *ev)
|
{
|
struct ble_ll_scan_aux_data *aux = ble_npl_event_get_arg(ev);
|
|
BLE_LL_ASSERT(aux);
|
|
if (ble_ll_scan_aux_need_truncation(aux)) {
|
ble_ll_hci_ev_send_ext_adv_truncated_report(aux);
|
}
|
|
ble_ll_scan_aux_update_scan_backoff(aux);
|
|
ble_ll_scan_aux_free(aux);
|
ble_ll_scan_chk_resume();
|
}
|
|
void
|
ble_ll_scan_aux_break(struct ble_ll_scan_aux_data *aux)
|
{
|
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
|
if (aux->flags & BLE_LL_SCAN_AUX_F_W4_CONNECT_RSP) {
|
ble_ll_conn_send_connect_req_cancel();
|
}
|
#endif
|
|
ble_npl_event_init(&aux->break_ev, ble_ll_scan_aux_break_ev, aux);
|
ble_ll_event_send(&aux->break_ev);
|
}
|
|
static int
|
ble_ll_scan_aux_parse_aux_ptr(struct ble_ll_scan_aux_data *aux,
|
uint32_t aux_ptr, uint32_t *offset_us)
|
{
|
uint8_t offset_unit;
|
uint32_t offset;
|
uint8_t chan;
|
uint8_t phy;
|
|
phy = (aux_ptr >> 21) & 0x07;
|
switch (phy) {
|
case 0:
|
phy = BLE_PHY_1M;
|
break;
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_2M_PHY)
|
case 1:
|
phy = BLE_PHY_2M;
|
break;
|
#endif
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY)
|
case 2:
|
phy = BLE_PHY_CODED;
|
break;
|
#endif
|
default:
|
return -1;
|
}
|
|
chan = aux_ptr & 0x3f;
|
if (chan >= BLE_PHY_NUM_DATA_CHANS) {
|
return -1;
|
}
|
|
offset = 30 * ((aux_ptr >> 8) & 0x1fff);
|
if ((aux_ptr >> 7) & 0x01) {
|
offset *= 10;
|
offset_unit = 1;
|
} else {
|
offset_unit = 0;
|
}
|
|
if (offset < BLE_LL_MAFS) {
|
return -1;
|
}
|
|
aux->sec_phy = phy;
|
aux->chan = chan;
|
aux->offset_unit = offset_unit;
|
|
*offset_us = offset;
|
|
return 0;
|
}
|
|
int
|
ble_ll_scan_aux_sched(struct ble_ll_scan_aux_data *aux, uint32_t pdu_time,
|
uint8_t pdu_time_rem, uint32_t aux_ptr)
|
{
|
uint32_t offset_us;
|
int rc;
|
|
rc = ble_ll_scan_aux_parse_aux_ptr(aux, aux_ptr, &offset_us);
|
if (rc < 0) {
|
return -1;
|
}
|
|
rc = ble_ll_sched_scan_aux(&aux->sch, pdu_time, pdu_time_rem, offset_us);
|
if (rc < 0) {
|
return -1;
|
}
|
|
return 0;
|
}
|
|
int
|
ble_ll_scan_aux_rx_isr_start(uint8_t pdu_type, struct ble_mbuf_hdr *rxhdr)
|
{
|
struct ble_ll_scan_aux_data *aux;
|
|
BLE_LL_ASSERT(aux_data_current);
|
aux = aux_data_current;
|
|
if (aux->flags & BLE_LL_SCAN_AUX_F_W4_CONNECT_RSP) {
|
if (pdu_type != BLE_ADV_PDU_TYPE_AUX_CONNECT_RSP) {
|
aux_data_current = NULL;
|
ble_ll_scan_aux_break(aux);
|
ble_ll_state_set(BLE_LL_STATE_STANDBY);
|
return -1;
|
}
|
return 0;
|
}
|
|
if (pdu_type != BLE_ADV_PDU_TYPE_ADV_EXT_IND) {
|
aux_data_current = NULL;
|
ble_ll_scan_aux_break(aux);
|
ble_ll_state_set(BLE_LL_STATE_STANDBY);
|
return -1;
|
}
|
|
/*
|
* Prepare TX transition when doing active scanning and receiving 1st PDU
|
* since we may want to send AUX_SCAN_REQ.
|
*/
|
if ((aux->scan_type != BLE_SCAN_TYPE_PASSIVE) &&
|
!(aux->flags & BLE_LL_SCAN_AUX_F_AUX_ADV)) {
|
return 1;
|
}
|
|
return 0;
|
}
|
|
static int
|
ble_ll_scan_aux_parse_to_aux_data(struct ble_ll_scan_aux_data *aux,
|
uint8_t *rxbuf, struct ble_mbuf_hdr *rxhdr)
|
{
|
uint8_t pdu_hdr;
|
uint8_t pdu_len;
|
uint8_t adv_mode;
|
uint8_t eh_len;
|
uint8_t eh_flags;
|
uint8_t *eh_data;
|
|
pdu_hdr = rxbuf[0];
|
pdu_len = rxbuf[1];
|
|
/* PDU without at least Extended Header Length is invalid */
|
if (pdu_len == 0) {
|
return -1;
|
}
|
|
/* Mark as AUX received on 1st PDU, then as CHAIN received on subsequent */
|
if (aux->flags & BLE_LL_SCAN_AUX_F_AUX_ADV) {
|
aux->flags |= BLE_LL_SCAN_AUX_F_AUX_CHAIN;
|
} else {
|
aux->flags |= BLE_LL_SCAN_AUX_F_AUX_ADV;
|
}
|
|
adv_mode = rxbuf[2] >> 6;
|
eh_len = rxbuf[2] & 0x3f;
|
|
/* Only AUX_CHAIN_IND is valid without Extended Header */
|
if (eh_len == 0) {
|
if (!(aux->flags & BLE_LL_SCAN_AUX_F_AUX_CHAIN) || adv_mode) {
|
return -1;
|
}
|
return 0;
|
}
|
|
eh_flags = rxbuf[3];
|
eh_data = &rxbuf[4];
|
|
/* Now parse extended header... */
|
|
/* AdvA is only valid in 1st PDU, ignore in AUX_CHAIN_IND */
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_ADVA_BIT)) {
|
if (!(aux->flags & BLE_LL_SCAN_AUX_F_AUX_CHAIN)) {
|
memcpy(aux->adva, eh_data, 6);
|
aux->adva_type = !!(pdu_hdr & BLE_ADV_PDU_HDR_TXADD_MASK);
|
aux->flags |= BLE_LL_SCAN_AUX_F_HAS_ADVA;
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
aux->rpa_index = ble_hw_resolv_list_match();
|
#endif
|
}
|
eh_data += BLE_LL_EXT_ADV_ADVA_SIZE;
|
}
|
|
/* TargetA is only valid in 1st PDU, ignore in AUX_CHAIN_IND */
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_TARGETA_BIT)) {
|
if (!(aux->flags & BLE_LL_SCAN_AUX_F_AUX_CHAIN)) {
|
memcpy(aux->targeta, eh_data, 6);
|
aux->targeta_type = !!(pdu_hdr & BLE_ADV_PDU_HDR_RXADD_MASK);
|
aux->flags |= BLE_LL_SCAN_AUX_F_HAS_TARGETA;
|
}
|
eh_data += BLE_LL_EXT_ADV_TARGETA_SIZE;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_CTE_INFO_BIT)) {
|
eh_data += BLE_LL_EXT_ADV_CTE_INFO_SIZE;
|
}
|
|
/*
|
* ADI handling is a bit convoluted....
|
* ADI is mandatory in ADV_EXT_IND with AuxPtr and is also mandatory in PDU
|
* if included in superior PDU. This implies that each AUX_CHAIN shall have
|
* ADI. However... AUX_SCAN_RSP does not need to have ADI, so if there's no
|
* ADI in AUX_SCAN_RSP we allow it and clear corresponding flag to skip ADI
|
* checks on subsequent PDUs.
|
*/
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_DATA_INFO_BIT)) {
|
if (aux->flags & BLE_LL_SCAN_AUX_F_HAS_ADI) {
|
if (get_le16(eh_data) != aux->adi) {
|
return -1;
|
}
|
}
|
eh_data += BLE_LL_EXT_ADV_DATA_INFO_SIZE;
|
} else if (aux->flags & BLE_LL_SCAN_AUX_F_HAS_ADI) {
|
if (rxhdr->rxinfo.flags & BLE_MBUF_HDR_F_SCAN_RSP_RXD) {
|
aux->flags &= ~BLE_LL_SCAN_AUX_F_HAS_ADI;
|
} else {
|
return -1;
|
}
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_AUX_PTR_BIT)) {
|
aux->aux_ptr = get_le24(eh_data);
|
rxhdr->rxinfo.flags |= BLE_MBUF_HDR_F_AUX_PTR_WAIT;
|
}
|
|
return 0;
|
}
|
|
static int
|
ble_ll_scan_aux_ext_parse(uint8_t *rxbuf, struct ble_ll_scan_addr_data *addrd,
|
uint16_t *adi, uint32_t *aux_ptr)
|
{
|
uint8_t pdu_hdr;
|
uint8_t pdu_len;
|
uint8_t adv_mode;
|
uint8_t eh_len;
|
uint8_t eh_flags;
|
uint8_t *eh_data;
|
|
pdu_hdr = rxbuf[0];
|
pdu_len = rxbuf[1];
|
|
if (pdu_len == 0) {
|
return -1;
|
}
|
|
adv_mode = rxbuf[2] >> 6;
|
eh_len = rxbuf[2] & 0x3f;
|
|
if ((adv_mode == 3) || (eh_len == 0)) {
|
return -1;
|
}
|
|
eh_flags = rxbuf[3];
|
eh_data = &rxbuf[4];
|
|
/* ADV_EXT_IND with AuxPtr but without ADI is invalid */
|
if ((eh_flags & (1 << BLE_LL_EXT_ADV_AUX_PTR_BIT)) &&
|
!(eh_flags & (1 << BLE_LL_EXT_ADV_DATA_INFO_BIT))) {
|
return -1;
|
}
|
|
/* ADV_EXT_IND without either AdvA or AuxPtr is not valid */
|
if (!(eh_flags & ((1 << BLE_LL_EXT_ADV_ADVA_BIT) |
|
(1 << BLE_LL_EXT_ADV_AUX_PTR_BIT)))) {
|
return -1;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_ADVA_BIT)) {
|
addrd->adva = eh_data;
|
addrd->adva_type = !!(pdu_hdr & BLE_ADV_PDU_HDR_TXADD_RAND);
|
eh_data += BLE_LL_EXT_ADV_ADVA_SIZE;
|
} else {
|
addrd->adva = NULL;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_TARGETA_BIT)) {
|
addrd->targeta = eh_data;
|
addrd->targeta_type = !!(pdu_hdr & BLE_ADV_PDU_HDR_RXADD_RAND);
|
eh_data += BLE_LL_EXT_ADV_TARGETA_SIZE;
|
} else {
|
addrd->targeta = NULL;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_CTE_INFO_BIT)) {
|
eh_data += BLE_LL_EXT_ADV_CTE_INFO_SIZE;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_DATA_INFO_BIT)) {
|
*adi = get_le16(eh_data);
|
eh_data += BLE_LL_EXT_ADV_DATA_INFO_SIZE;
|
} else {
|
*adi = 0;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_AUX_PTR_BIT)) {
|
*aux_ptr = get_le24(eh_data);
|
} else {
|
*aux_ptr = 0;
|
}
|
|
return 0;
|
}
|
|
static void
|
ble_ll_scan_aux_update_rxinfo_from_addrd(struct ble_ll_scan_addr_data *addrd,
|
struct ble_mbuf_hdr_rxinfo *rxinfo)
|
{
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
rxinfo->rpa_index = addrd->rpa_index;
|
if (addrd->adva_resolved) {
|
rxinfo->flags |= BLE_MBUF_HDR_F_RESOLVED;
|
}
|
if (addrd->targeta_resolved) {
|
rxinfo->flags |= BLE_MBUF_HDR_F_TARGETA_RESOLVED;
|
}
|
#endif
|
}
|
|
static void
|
ble_ll_scan_aux_update_aux_data_from_addrd(struct ble_ll_scan_addr_data *addrd,
|
struct ble_ll_scan_aux_data *aux)
|
{
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
aux->rpa_index = addrd->rpa_index;
|
if (addrd->adva_resolved) {
|
aux->flags |= BLE_LL_SCAN_AUX_F_RESOLVED_ADVA;
|
}
|
if (addrd->targeta_resolved) {
|
aux->flags |= BLE_LL_SCAN_AUX_F_RESOLVED_TARGETA;
|
}
|
#endif
|
}
|
|
int
|
ble_ll_scan_aux_rx_isr_end_on_ext(struct ble_ll_scan_sm *scansm,
|
struct os_mbuf *rxpdu)
|
{
|
struct ble_mbuf_hdr *rxhdr = BLE_MBUF_HDR_PTR(rxpdu);
|
struct ble_mbuf_hdr_rxinfo *rxinfo = &rxhdr->rxinfo;
|
struct ble_ll_scan_addr_data addrd;
|
struct ble_ll_scan_aux_data *aux;
|
uint8_t *rxbuf;
|
uint32_t aux_ptr;
|
uint16_t adi;
|
bool do_match;
|
int rc;
|
|
rxbuf = rxpdu->om_data;
|
|
rc = ble_ll_scan_aux_ext_parse(rxbuf, &addrd, &adi, &aux_ptr);
|
if (rc < 0) {
|
return -1;
|
}
|
|
/* We can filter based on ext PDU alone if both AdvA and TargetA are present
|
* or there's no AuxPtr. Otherwise, we need to wait for aux PDU to filter.
|
*/
|
do_match = !aux_ptr || (addrd.adva && addrd.targeta);
|
if (do_match) {
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
addrd.rpa_index = ble_hw_resolv_list_match();
|
#endif
|
rc = ble_ll_scan_rx_filter(ble_ll_scan_get_own_addr_type(),
|
ble_ll_scan_get_filt_policy(),
|
&addrd, NULL);
|
if (rc < 0) {
|
return -1;
|
}
|
|
/* Don't care about initiator here, there are no AdvA and TargetA
|
* in connectable ADV_EXT_IND so no filtering to do.
|
*/
|
|
if (!aux_ptr) {
|
/* We do not allocate aux_data for ADV_EXT_IND without AuxPtr so
|
* need to pass match data in rxinfo.
|
*/
|
ble_ll_scan_aux_update_rxinfo_from_addrd(&addrd, rxinfo);
|
}
|
}
|
|
if (aux_ptr) {
|
aux = ble_ll_scan_aux_alloc();
|
if (!aux) {
|
return -1;
|
}
|
|
aux->scan_type = scansm->scanp->scan_type;
|
|
aux->pri_phy = rxinfo->phy;
|
aux->aux_ptr = aux_ptr;
|
|
if (addrd.adva) {
|
memcpy(aux->adva, addrd.adva, 6);
|
aux->adva_type = addrd.adva_type;
|
aux->flags |= BLE_LL_SCAN_AUX_F_HAS_ADVA;
|
}
|
|
if (addrd.targeta) {
|
memcpy(aux->targeta, addrd.targeta, 6);
|
aux->targeta_type = addrd.targeta_type;
|
aux->flags |= BLE_LL_SCAN_AUX_F_HAS_TARGETA;
|
}
|
|
aux->adi = adi;
|
aux->flags |= BLE_LL_SCAN_AUX_F_HAS_ADI;
|
|
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
|
if (aux->scan_type == BLE_SCAN_TYPE_INITIATE) {
|
aux->flags |= BLE_LL_SCAN_AUX_F_CONNECTABLE;
|
}
|
#endif
|
|
if (do_match) {
|
aux->flags |= BLE_LL_SCAN_AUX_F_MATCHED;
|
ble_ll_scan_aux_update_aux_data_from_addrd(&addrd, aux);
|
} else if (addrd.adva) {
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
/* If ext PDU has AdvA, we need to store rpa_index to be able to
|
* reuse it for filtering when done on aux PDU.
|
*/
|
aux->rpa_index = ble_hw_resolv_list_match();
|
#endif
|
}
|
|
rxinfo->user_data = aux;
|
}
|
|
return 0;
|
}
|
|
void
|
ble_ll_scan_aux_pkt_in_on_ext(struct os_mbuf *rxpdu,
|
struct ble_mbuf_hdr *rxhdr)
|
{
|
struct ble_mbuf_hdr_rxinfo *rxinfo = &rxhdr->rxinfo;
|
struct ble_ll_scan_aux_data *aux = rxinfo->user_data;
|
int rc;
|
|
if (rxinfo->flags & BLE_MBUF_HDR_F_IGNORED) {
|
BLE_LL_ASSERT(!aux);
|
return;
|
}
|
|
if (!aux) {
|
ble_ll_hci_ev_send_ext_adv_report_for_ext(rxpdu, rxhdr);
|
return;
|
}
|
|
BLE_LL_ASSERT(aux->aux_ptr);
|
|
rc = ble_ll_scan_aux_sched(aux, rxhdr->beg_cputime, rxhdr->rem_usecs,
|
aux->aux_ptr);
|
if (rc < 0) {
|
ble_ll_scan_aux_free(aux);
|
}
|
}
|
|
static uint8_t
|
ble_ll_scan_aux_scan_req_tx_pdu_cb(uint8_t *dptr, void *arg, uint8_t *hdr_byte)
|
{
|
struct ble_ll_scan_aux_data *aux = arg;
|
uint8_t *scana;
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
struct ble_ll_resolv_entry *rl;
|
uint8_t rpa[BLE_DEV_ADDR_LEN];
|
#endif
|
uint8_t hb;
|
|
hb = BLE_ADV_PDU_TYPE_SCAN_REQ;
|
|
/* ScanA */
|
if (ble_ll_scan_get_own_addr_type() & 0x01) {
|
hb |= BLE_ADV_PDU_HDR_TXADD_RAND;
|
scana = g_random_addr;
|
} else {
|
scana = g_dev_addr;
|
}
|
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
if (ble_ll_scan_get_own_addr_type() & 0x02) {
|
if (aux->rpa_index >=0) {
|
rl = &g_ble_ll_resolv_list[aux->rpa_index];
|
} else {
|
rl = NULL;
|
}
|
|
/*
|
* If device is on RL and we have local IRK, we use RPA generated using
|
* that IRK as ScanA. Otherwise we use NRPA as ScanA to prevent our
|
* device from being tracked when doing an active scan
|
* ref: Core 5.2, Vol 6, Part B, section 6.3)
|
*/
|
if (rl && rl->rl_has_local) {
|
ble_ll_resolv_get_priv_addr(rl, 1, rpa);
|
scana = rpa;
|
} else {
|
scana = ble_ll_get_scan_nrpa();
|
}
|
|
hb |= BLE_ADV_PDU_HDR_TXADD_RAND;
|
}
|
#endif
|
memcpy(dptr, scana, BLE_DEV_ADDR_LEN);
|
|
/* AdvA */
|
if (aux->adva_type) {
|
hb |= BLE_ADV_PDU_HDR_RXADD_RAND;
|
}
|
memcpy(dptr + BLE_DEV_ADDR_LEN, aux->adva, BLE_DEV_ADDR_LEN);
|
|
*hdr_byte = hb;
|
|
return BLE_DEV_ADDR_LEN * 2;
|
}
|
|
static bool
|
ble_ll_scan_aux_send_scan_req(struct ble_ll_scan_aux_data *aux,
|
struct ble_ll_scan_addr_data *addrd)
|
{
|
int rc;
|
|
/* Check if we already scanned this device successfully */
|
if (ble_ll_scan_have_rxd_scan_rsp(addrd->adv_addr, addrd->adv_addr_type,
|
true, aux->adi)) {
|
return false;
|
}
|
|
/* We want to send a request, see if backoff allows us */
|
if (ble_ll_scan_backoff_kick() != 0) {
|
return false;
|
}
|
|
/* TODO perhaps we should check if scan req+rsp won't overlap with scheduler
|
* item (old code did it), but for now let's just scan and we will be
|
* interrupted if scheduler kicks in.
|
*/
|
|
rc = ble_phy_tx(ble_ll_scan_aux_scan_req_tx_pdu_cb, aux,
|
BLE_PHY_TRANSITION_TX_RX);
|
if (rc) {
|
return false;
|
}
|
|
return true;
|
}
|
|
int
|
ble_ll_scan_aux_rx_isr_end(struct os_mbuf *rxpdu, uint8_t crcok)
|
{
|
struct ble_ll_scan_addr_data addrd;
|
struct ble_mbuf_hdr_rxinfo *rxinfo;
|
struct ble_ll_scan_aux_data *aux;
|
struct ble_mbuf_hdr *rxhdr;
|
uint8_t scan_filt_policy;
|
uint8_t scan_ok;
|
uint8_t adv_mode;
|
uint8_t *rxbuf;
|
int rc;
|
|
BLE_LL_ASSERT(aux_data_current);
|
aux = aux_data_current;
|
aux_data_current = NULL;
|
|
if (rxpdu == NULL) {
|
ble_ll_scan_aux_break(aux);
|
goto done;
|
}
|
|
rxhdr = BLE_MBUF_HDR_PTR(rxpdu);
|
rxinfo = &rxhdr->rxinfo;
|
rxinfo->user_data = aux;
|
|
if (!crcok) {
|
rxinfo->flags |= BLE_MBUF_HDR_F_IGNORED;
|
goto done;
|
}
|
|
rxbuf = rxpdu->om_data;
|
|
if (aux->flags & BLE_LL_SCAN_AUX_F_W4_CONNECT_RSP) {
|
aux->flags &= ~BLE_LL_SCAN_AUX_F_W4_CONNECT_RSP;
|
rxinfo->flags |= BLE_MBUF_HDR_F_CONNECT_RSP_RXD;
|
goto done;
|
}
|
|
if (aux->flags & BLE_LL_SCAN_AUX_F_W4_SCAN_RSP) {
|
aux->flags &= ~BLE_LL_SCAN_AUX_F_W4_SCAN_RSP;
|
aux->flags |= BLE_LL_SCAN_AUX_F_SCANNED;
|
rxinfo->flags |= BLE_MBUF_HDR_F_SCAN_RSP_RXD;
|
}
|
|
rc = ble_ll_scan_aux_parse_to_aux_data(aux, rxbuf, rxhdr);
|
if (rc < 0) {
|
rxinfo->flags |= BLE_MBUF_HDR_F_IGNORED;
|
goto done;
|
}
|
|
if (aux->flags & BLE_LL_SCAN_AUX_F_MATCHED) {
|
goto done;
|
}
|
|
/* We do filtering on either ADV_EXT_IND or AUX_ADV_IND so we should not be
|
* here when processing AUX_CHAIN_IND.
|
*/
|
BLE_LL_ASSERT(!(aux->flags & BLE_LL_SCAN_AUX_F_AUX_CHAIN));
|
|
if (aux->flags & BLE_LL_SCAN_AUX_F_HAS_ADVA) {
|
addrd.adva = aux->adva;
|
addrd.adva_type = aux->adva_type;
|
} else {
|
addrd.adva = NULL;
|
}
|
|
if (aux->flags & BLE_LL_SCAN_AUX_F_HAS_TARGETA) {
|
addrd.targeta = aux->targeta;
|
addrd.targeta_type = aux->targeta_type;
|
} else {
|
addrd.targeta = NULL;
|
}
|
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
addrd.rpa_index = aux->rpa_index;
|
#endif
|
|
scan_filt_policy = ble_ll_scan_get_filt_policy();
|
|
rc = ble_ll_scan_rx_filter(ble_ll_scan_get_own_addr_type(),
|
scan_filt_policy, &addrd, &scan_ok);
|
if (rc < 0) {
|
rxinfo->flags |= BLE_MBUF_HDR_F_IGNORED;
|
/*
|
* XXX hack warning
|
* Even if PDU was not allowed by current scan filter policy, we should
|
* still allow it to sync if SyncInfo is present. Since we do not use
|
* F_DEVMATCH in aux code for its intended purpose, let's use it here to
|
* indicate no match due to scan filter policy.
|
*/
|
if (rc == -2) {
|
rxinfo->flags |= BLE_MBUF_HDR_F_DEVMATCH;
|
}
|
goto done;
|
}
|
|
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
|
if ((aux->scan_type == BLE_SCAN_TYPE_INITIATE) &&
|
!(scan_filt_policy & 0x01)) {
|
rc = ble_ll_scan_rx_check_init(&addrd);
|
if (rc < 0) {
|
rxinfo->flags |= BLE_MBUF_HDR_F_IGNORED;
|
goto done;
|
}
|
}
|
#endif
|
|
aux->flags |= BLE_LL_SCAN_AUX_F_MATCHED;
|
|
ble_ll_scan_aux_update_aux_data_from_addrd(&addrd, aux);
|
|
adv_mode = rxbuf[2] >> 6;
|
|
switch (aux->scan_type) {
|
case BLE_SCAN_TYPE_ACTIVE:
|
if ((adv_mode == BLE_LL_EXT_ADV_MODE_SCAN) && scan_ok &&
|
ble_ll_scan_aux_send_scan_req(aux, &addrd)) {
|
/* AUX_SCAN_REQ sent, keep PHY enabled to continue */
|
aux->flags |= BLE_LL_SCAN_AUX_F_W4_SCAN_RSP;
|
rxinfo->flags |= BLE_MBUF_HDR_F_SCAN_REQ_TXD;
|
aux_data_current = aux;
|
return 0;
|
}
|
break;
|
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
|
case BLE_SCAN_TYPE_INITIATE:
|
if (ble_ll_conn_send_connect_req(rxpdu, &addrd, 1) == 0) {
|
/* AUX_CONNECT_REQ sent, keep PHY enabled to continue */
|
aux->flags |= BLE_LL_SCAN_AUX_F_W4_CONNECT_RSP;
|
rxinfo->flags |= BLE_MBUF_HDR_F_CONNECT_REQ_TXD;
|
aux_data_current = aux;
|
return 0;
|
} else {
|
rxinfo->flags |= BLE_MBUF_HDR_F_IGNORED;
|
}
|
break;
|
#endif
|
default:
|
break;
|
}
|
|
done:
|
/* We are done with this PDU so go to standby and let LL resume if needed */
|
ble_ll_state_set(BLE_LL_STATE_STANDBY);
|
return -1;
|
}
|
|
static void
|
ble_ll_scan_aux_init_addrd_from_aux_data(struct ble_ll_scan_aux_data *aux,
|
struct ble_ll_scan_addr_data *addrd)
|
{
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
struct ble_ll_resolv_entry *rl;
|
#endif
|
|
if (aux->flags & BLE_LL_SCAN_AUX_F_HAS_ADVA) {
|
addrd->adva = aux->adva;
|
addrd->adva_type = aux->adva_type;
|
} else {
|
addrd->adva = NULL;
|
addrd->adva_type = 0;
|
}
|
|
if (aux->flags & BLE_LL_SCAN_AUX_F_HAS_TARGETA) {
|
addrd->targeta = aux->targeta;
|
addrd->targeta_type = aux->targeta_type;
|
} else {
|
addrd->targeta = NULL;
|
addrd->targeta_type = 0;
|
}
|
|
addrd->adv_addr = addrd->adva;
|
addrd->adv_addr_type = addrd->adva_type;
|
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
addrd->rpa_index = aux->rpa_index;
|
|
if (aux->flags & BLE_LL_SCAN_AUX_F_RESOLVED_ADVA) {
|
BLE_LL_ASSERT(aux->rpa_index >= 0);
|
rl = &g_ble_ll_resolv_list[aux->rpa_index];
|
addrd->adv_addr = rl->rl_identity_addr;
|
addrd->adv_addr_type = rl->rl_addr_type;
|
addrd->adva_resolved = 1;
|
} else {
|
addrd->adva_resolved = 0;
|
}
|
|
if (aux->flags & BLE_LL_SCAN_AUX_F_RESOLVED_TARGETA) {
|
addrd->targeta = ble_ll_scan_aux_get_own_addr();
|
addrd->targeta_type = ble_ll_scan_get_own_addr_type() & 1;
|
addrd->targeta_resolved = 1;
|
} else {
|
addrd->targeta_resolved = 0;
|
}
|
#endif
|
}
|
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV)
|
static void
|
ble_ll_scan_aux_sync_check(struct os_mbuf *rxpdu,
|
struct ble_ll_scan_addr_data *addrd)
|
{
|
struct ble_mbuf_hdr *rxhdr = BLE_MBUF_HDR_PTR(rxpdu);
|
uint8_t *rxbuf = rxpdu->om_data;
|
uint8_t adv_mode;
|
uint8_t eh_len;
|
uint8_t eh_flags;
|
uint8_t *eh_data;
|
uint8_t sid;
|
|
adv_mode = rxbuf[2] >> 6;
|
|
if (adv_mode != BLE_LL_EXT_ADV_MODE_NON_CONN) {
|
return;
|
}
|
|
eh_len = rxbuf[2] & 0x3f;
|
|
if (eh_len == 0) {
|
return;
|
}
|
|
eh_flags = rxbuf[3];
|
eh_data = &rxbuf[4];
|
|
/* Need ADI and SyncInfo */
|
if (!(eh_flags & ((1 << BLE_LL_EXT_ADV_SYNC_INFO_BIT) |
|
(1 << BLE_LL_EXT_ADV_DATA_INFO_BIT)))) {
|
return;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_ADVA_BIT)) {
|
eh_data += BLE_LL_EXT_ADV_ADVA_SIZE;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_TARGETA_BIT)) {
|
eh_data += BLE_LL_EXT_ADV_TARGETA_SIZE;
|
}
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_CTE_INFO_BIT)) {
|
eh_data += 1;
|
}
|
|
sid = get_le16(eh_data) >> 12;
|
eh_data += BLE_LL_EXT_ADV_DATA_INFO_SIZE;
|
|
if (eh_flags & (1 << BLE_LL_EXT_ADV_AUX_PTR_BIT)) {
|
eh_data += BLE_LL_EXT_ADV_AUX_PTR_SIZE;
|
}
|
|
ble_ll_sync_info_event(addrd, sid, rxhdr, eh_data);
|
}
|
#endif
|
|
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
|
static int
|
ble_ll_scan_aux_check_connect_rsp(uint8_t *rxbuf,
|
struct ble_ll_scan_pdu_data *pdu_data,
|
struct ble_ll_scan_addr_data *addrd)
|
{
|
uint8_t pdu_hdr;
|
uint8_t pdu_len;
|
uint8_t adv_mode;
|
uint8_t eh_len;
|
uint8_t eh_flags;
|
uint8_t *eh_data;
|
uint8_t adva_type;
|
uint8_t *adva;
|
uint8_t targeta_type;
|
uint8_t *targeta;
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
struct ble_ll_resolv_entry *rl = NULL;
|
#endif
|
|
pdu_hdr = rxbuf[0];
|
pdu_len = rxbuf[1];
|
|
if (pdu_len == 0) {
|
return -1;
|
}
|
|
adv_mode = rxbuf[2] >> 6;
|
eh_len = rxbuf[2] & 0x3f;
|
|
if ((adv_mode != 0) || (eh_len == 0)) {
|
return -1;
|
}
|
|
eh_flags = rxbuf[3];
|
eh_data = &rxbuf[4];
|
|
/* AUX_CONNECT_RSP without AdvA or TargetA is not valid */
|
if (!(eh_flags & ((1 << BLE_LL_EXT_ADV_ADVA_BIT) |
|
(1 << BLE_LL_EXT_ADV_TARGETA_BIT)))) {
|
return -1;
|
}
|
|
adva = eh_data;
|
adva_type = !!(pdu_hdr & BLE_ADV_PDU_HDR_TXADD_RAND);
|
eh_data += BLE_LL_EXT_ADV_ADVA_SIZE;
|
|
targeta = eh_data;
|
targeta_type = !!(pdu_hdr & BLE_ADV_PDU_HDR_RXADD_RAND);
|
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
/* If we have IRK for peer and AdvA is an RPA, we need to check if current
|
* RPA resolves using that IRK. This is to verify AdvA in case RPS changed
|
* due to timeout or AdvA in advertising was an identity address but is an
|
* RPA in AUX_CONNECT_RSP.
|
* Otherwise, it shall be the same as in AUX_CONNECT_REQ.
|
*/
|
if ((addrd->rpa_index >= 0) && ble_ll_is_rpa(adva, adva_type)) {
|
rl = &g_ble_ll_resolv_list[addrd->rpa_index];
|
|
if (!ble_ll_resolv_rpa(adva, rl->rl_peer_irk)) {
|
return -1;
|
}
|
|
addrd->adva_resolved = 1;
|
addrd->adva = adva;
|
addrd->adva_type = adva_type;
|
} else if ((adva_type !=
|
!!(pdu_data->hdr_byte & BLE_ADV_PDU_HDR_RXADD_MASK)) ||
|
(memcmp(adva, pdu_data->adva, 6) != 0)) {
|
return -1;
|
}
|
#else
|
if ((adva_type != !!(pdu_data->hdr_byte & BLE_ADV_PDU_HDR_RXADD_MASK)) ||
|
(memcmp(adva, pdu_data->adva, 6) != 0)) {
|
return -1;
|
}
|
#endif /* BLE_LL_CFG_FEAT_LL_PRIVACY */
|
|
if ((targeta_type != !!(pdu_data->hdr_byte & BLE_ADV_PDU_HDR_TXADD_MASK)) ||
|
(memcmp(targeta, pdu_data->inita, 6) != 0)) {
|
return -1;
|
}
|
|
return 0;
|
}
|
|
static void
|
ble_ll_scan_aux_rx_pkt_in_for_initiator(struct os_mbuf *rxpdu,
|
struct ble_mbuf_hdr *rxhdr)
|
{
|
struct ble_ll_scan_addr_data addrd;
|
struct ble_mbuf_hdr_rxinfo *rxinfo;
|
struct ble_ll_scan_aux_data *aux;
|
|
rxinfo = &rxhdr->rxinfo;
|
aux = rxinfo->user_data;
|
|
if (rxinfo->flags & BLE_MBUF_HDR_F_IGNORED) {
|
ble_ll_scan_aux_free(aux);
|
ble_ll_scan_chk_resume();
|
return;
|
}
|
|
if (!(rxinfo->flags & BLE_MBUF_HDR_F_CONNECT_RSP_RXD)) {
|
BLE_LL_ASSERT(rxinfo->flags & BLE_MBUF_HDR_F_CONNECT_REQ_TXD);
|
/* Waiting for AUX_CONNECT_RSP, do nothing */
|
return;
|
}
|
|
ble_ll_scan_aux_init_addrd_from_aux_data(aux, &addrd);
|
|
if (ble_ll_scan_aux_check_connect_rsp(rxpdu->om_data,
|
ble_ll_scan_get_pdu_data(),
|
&addrd) < 0) {
|
ble_ll_conn_send_connect_req_cancel();
|
ble_ll_scan_aux_free(aux);
|
ble_ll_scan_chk_resume();
|
return;
|
}
|
|
ble_ll_scan_sm_stop(0);
|
ble_ll_conn_created_on_aux(rxpdu, &addrd, aux->targeta);
|
ble_ll_scan_aux_free(aux);
|
}
|
#endif
|
|
void
|
ble_ll_scan_aux_rx_pkt_in(struct os_mbuf *rxpdu, struct ble_mbuf_hdr *rxhdr)
|
{
|
struct ble_ll_scan_addr_data addrd;
|
struct ble_mbuf_hdr_rxinfo *rxinfo;
|
struct ble_ll_scan_aux_data *aux;
|
bool scan_duplicate = false;
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV)
|
bool sync_check = false;
|
#endif
|
int rc;
|
|
rxinfo = &rxhdr->rxinfo;
|
aux = rxinfo->user_data;
|
|
BLE_LL_ASSERT(aux);
|
|
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
|
if (aux->scan_type == BLE_SCAN_TYPE_INITIATE) {
|
ble_ll_scan_aux_rx_pkt_in_for_initiator(rxpdu, rxhdr);
|
return;
|
}
|
#endif
|
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV)
|
sync_check = ble_ll_sync_enabled() &&
|
!(aux->flags & BLE_LL_SCAN_AUX_F_AUX_CHAIN);
|
|
/* PDU was not allowed due to scan filter policy, but we can still try to
|
* sync since separate filter policy is used for this purpose.
|
*/
|
if ((rxinfo->flags & BLE_MBUF_HDR_F_DEVMATCH) && sync_check) {
|
ble_ll_scan_aux_init_addrd_from_aux_data(aux, &addrd);
|
ble_ll_scan_aux_sync_check(rxpdu, &addrd);
|
}
|
#endif
|
|
if (rxinfo->flags & BLE_MBUF_HDR_F_IGNORED) {
|
if (ble_ll_scan_aux_need_truncation(aux)) {
|
ble_ll_hci_ev_send_ext_adv_truncated_report(aux);
|
} else {
|
aux->hci_state |= BLE_LL_SCAN_AUX_H_DONE;
|
}
|
|
ble_ll_scan_aux_update_scan_backoff(aux);
|
}
|
|
if (aux->hci_state & BLE_LL_SCAN_AUX_H_DONE) {
|
ble_ll_scan_aux_free(aux);
|
ble_ll_scan_chk_resume();
|
return;
|
}
|
|
/* Try to schedule scan for subsequent aux asap, if needed */
|
if (rxinfo->flags & BLE_MBUF_HDR_F_AUX_PTR_WAIT) {
|
rc = ble_ll_scan_aux_sched(aux, rxhdr->beg_cputime, rxhdr->rem_usecs,
|
aux->aux_ptr);
|
if (rc < 0) {
|
rxinfo->flags &= ~BLE_MBUF_HDR_F_AUX_PTR_WAIT;
|
}
|
}
|
|
ble_ll_scan_aux_init_addrd_from_aux_data(aux, &addrd);
|
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV)
|
if (sync_check) {
|
ble_ll_scan_aux_sync_check(rxpdu, &addrd);
|
}
|
#endif
|
|
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
|
if (addrd.adva_resolved) {
|
BLE_LL_ASSERT(addrd.rpa_index >= 0);
|
ble_ll_resolv_set_peer_rpa(addrd.rpa_index, addrd.adva);
|
}
|
#endif
|
|
scan_duplicate = ble_ll_scan_get_filt_dups() &&
|
ble_ll_scan_dup_check_ext(addrd.adv_addr_type,
|
addrd.adv_addr, true, aux->adi);
|
if (!scan_duplicate) {
|
rc = ble_ll_hci_ev_send_ext_adv_report_for_aux(rxpdu, rxhdr, aux,
|
&addrd);
|
|
/*
|
* Update duplicates list if report was sent successfully and we are
|
* done with this chain. On error, status is already set to 'done' so
|
* we will cancel aux scan (if any) and stop further processing.
|
* However, if we send AUX_SCAN_REQ for this PDU then need to remove
|
* 'done' as we should continue with scanning after AUX_SCAN_RSP.
|
*/
|
|
if (rc == 0) {
|
if (rxinfo->flags & BLE_MBUF_HDR_F_SCAN_REQ_TXD) {
|
aux->hci_state &= ~BLE_LL_SCAN_AUX_H_DONE;
|
} else if (aux->hci_state & BLE_LL_SCAN_AUX_H_DONE) {
|
BLE_LL_ASSERT(!(rxinfo->flags & BLE_MBUF_HDR_F_AUX_PTR_WAIT));
|
if (ble_ll_scan_get_filt_dups()) {
|
ble_ll_scan_dup_update_ext(addrd.adv_addr_type,
|
addrd.adv_addr, true, aux->adi);
|
}
|
if (aux->flags & BLE_LL_SCAN_AUX_F_SCANNED) {
|
ble_ll_scan_add_scan_rsp_adv(addrd.adv_addr,
|
addrd.adv_addr_type,
|
1, aux->adi);
|
}
|
}
|
}
|
} else {
|
/* This is a duplicate, we don't want to scan it anymore */
|
aux->hci_state |= BLE_LL_SCAN_AUX_H_DONE;
|
}
|
|
/*
|
* If we are done processing this chain and aux scan was not scheduled or
|
* we removed it from scheduler, we can remove aux_data now. Otherwise we
|
* will remove on next pkt_in.
|
*/
|
if ((aux->hci_state & BLE_LL_SCAN_AUX_H_DONE) &&
|
(!(rxinfo->flags & BLE_MBUF_HDR_F_AUX_PTR_WAIT) ||
|
(ble_ll_sched_rmv_elem(&aux->sch) == 0))) {
|
ble_ll_scan_aux_update_scan_backoff(aux);
|
ble_ll_scan_aux_free(aux);
|
}
|
|
ble_ll_scan_chk_resume();
|
}
|
|
void
|
ble_ll_scan_aux_wfr_timer_exp(void)
|
{
|
ble_phy_disable();
|
ble_ll_scan_aux_halt();
|
}
|
|
void
|
ble_ll_scan_aux_halt(void)
|
{
|
struct ble_ll_scan_aux_data *aux = aux_data_current;
|
|
BLE_LL_ASSERT(aux);
|
|
aux_data_current = NULL;
|
|
ble_ll_state_set(BLE_LL_STATE_STANDBY);
|
|
ble_ll_scan_aux_break(aux);
|
}
|
|
void
|
ble_ll_scan_aux_sched_remove(struct ble_ll_sched_item *sch)
|
{
|
ble_ll_scan_aux_break(sch->cb_arg);
|
}
|
|
void
|
ble_ll_scan_aux_init(void)
|
{
|
os_error_t err;
|
|
err = os_mempool_init(&aux_data_pool,
|
MYNEWT_VAL(BLE_LL_SCAN_AUX_SEGMENT_CNT),
|
sizeof(struct ble_ll_scan_aux_data),
|
aux_data_mem, "ble_ll_scan_aux_data_pool");
|
BLE_LL_ASSERT(err == 0);
|
}
|
|
#endif /* BLE_LL_CFG_FEAT_LL_EXT_ADV */
|