/*
* Copyright (c) 2016 Nordic Semiconductor ASA
* Copyright (c) 2016 Vinayak Kariappa Chettimada
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <soc.h>
#include <device.h>
#include <clock_control.h>
#include <bluetooth/hci.h>
#include <misc/util.h>
#include "cpu.h"
#include "rand.h"
#include "ecb.h"
#include "ccm.h"
#include "radio.h"
#include "mem.h"
#include "memq.h"
#include "mayfly.h"
#include "util.h"
#include "ticker.h"
#include "pdu.h"
#include "ctrl.h"
#include "ctrl_internal.h"
#include "config.h"
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLUETOOTH_DEBUG_HCI_DRIVER)
#include <bluetooth/log.h>
#include "debug.h"
#define RADIO_PREAMBLE_TO_ADDRESS_US 40
#define RADIO_HCTO_US (150 + 2 + 2 + \
RADIO_PREAMBLE_TO_ADDRESS_US)
#define RADIO_CONN_EVENTS(x, y) ((uint16_t)((x) / (y)))
#define RADIO_TICKER_JITTER_US 16
#define RADIO_TICKER_START_PART_US 300
#define RADIO_TICKER_XTAL_OFFSET_US 1200
#define RADIO_TICKER_PREEMPT_PART_US 0
#define RADIO_TICKER_PREEMPT_PART_MIN_US 0
#define RADIO_TICKER_PREEMPT_PART_MAX_US RADIO_TICKER_XTAL_OFFSET_US
#if defined(CONFIG_BLUETOOTH_CONTROLLER_CONN_RSSI)
#define RADIO_RSSI_SAMPLE_COUNT 10
#define RADIO_RSSI_THRESHOLD 4
#endif /* CONFIG_BLUETOOTH_CONTROLLER_CONN_RSSI */
#define RADIO_IRK_COUNT_MAX 8
#define XTAL_ADVANCED 1
#define SCHED_ADVANCED 1
#define SILENT_CONNECTION 0
#define RADIO_PHY_ADV 0
#define RADIO_PHY_CONN 0
enum role {
ROLE_NONE,
ROLE_ADV,
ROLE_OBS,
ROLE_SLAVE,
ROLE_MASTER,
};
enum state {
STATE_NONE,
STATE_RX,
STATE_TX,
STATE_CLOSE,
STATE_STOP,
STATE_ABORT,
};
struct advertiser {
struct shdr hdr;
uint8_t chl_map:3;
uint8_t chl_map_current:3;
uint8_t filter_policy:2;
uint8_t filter_enable_bitmask;
uint8_t filter_addr_type_bitmask;
uint8_t filter_bdaddr[8][BDADDR_SIZE];
struct radio_adv_data adv_data;
struct radio_adv_data scan_data;
struct connection *conn;
};
struct observer {
struct shdr hdr;
uint8_t scan_type:1;
uint8_t scan_state:1;
uint8_t scan_channel:2;
uint8_t filter_policy:2;
uint8_t adv_addr_type:1;
uint8_t init_addr_type:1;
uint8_t adv_addr[BDADDR_SIZE];
uint8_t init_addr[BDADDR_SIZE];
uint32_t ticks_window;
uint8_t filter_enable_bitmask;
uint8_t filter_addr_type_bitmask;
uint8_t filter_bdaddr[8][BDADDR_SIZE];
uint16_t conn_interval;
uint16_t conn_latency;
uint16_t conn_timeout;
uint32_t ticks_conn_slot;
struct connection *conn;
uint32_t win_offset_us;
};
static struct {
struct device *hf_clock;
uint32_t ticks_anchor;
uint32_t remainder_anchor;
uint8_t volatile ticker_id_prepare;
uint8_t volatile ticker_id_event;
uint8_t volatile ticker_id_stop;
enum role volatile role;
enum state state;
uint8_t filter_enable_bitmask;
uint8_t filter_addr_type_bitmask;
uint8_t filter_bdaddr[8][BDADDR_SIZE];
uint8_t nirk;
uint8_t irk[RADIO_IRK_COUNT_MAX][16];
struct advertiser advertiser;
struct observer observer;
void *conn_pool;
void *conn_free;
uint8_t connection_count;
struct connection *conn_curr;
uint8_t packet_counter;
uint8_t crc_expire;
uint8_t data_channel_map[5];
uint8_t data_channel_count;
uint8_t sca;
#if defined(CONFIG_BLUETOOTH_CONTROLLER_DATA_LENGTH)
/* DLE global settings */
uint16_t default_tx_octets;
uint16_t default_tx_time;
#endif /* CONFIG_BLUETOOTH_CONTROLLER_DATA_LENGTH */
/** @todo below members to be made role specific and quota managed for
* Rx-es.
*/
/* Advertiser, Observer, and Connections Rx data pool */
void *pkt_rx_data_pool;
void *pkt_rx_data_free;
uint16_t packet_data_octets_max;
uint16_t packet_rx_data_pool_size;
uint16_t packet_rx_data_size;
uint8_t packet_rx_data_count;
/* Free queue Rx data buffers */
struct radio_pdu_node_rx **packet_rx;
uint8_t packet_rx_count;
uint8_t volatile packet_rx_last;
uint8_t packet_rx_acquire;
/* Controller to Host event-cum-data queue */
void *link_rx_pool;
void *link_rx_free;
void *link_rx_head;
void *volatile link_rx_tail;
uint8_t link_rx_data_quota;
/* Connections common Tx ctrl and data pool */
void *pkt_tx_ctrl_pool;
void *pkt_tx_ctrl_free;
void *pkt_tx_data_pool;
void *pkt_tx_data_free;
uint16_t packet_tx_data_size;
/* Host to Controller Tx, and Controller to Host Num complete queue */
struct pdu_data_q_tx *pkt_tx;
struct pdu_data_q_tx *pkt_release;
uint8_t packet_tx_count;
uint8_t volatile packet_tx_first;
uint8_t packet_tx_last;
uint8_t packet_release_first;
uint8_t volatile packet_release_last;
uint16_t fc_handle[TRIPLE_BUFFER_SIZE];
uint8_t volatile fc_req;
uint8_t fc_ack;
uint8_t fc_ena;
uint32_t ticks_active_to_start;
struct connection *conn_upd;
} _radio;
static uint16_t const gc_lookup_ppm[] = { 500, 250, 150, 100, 75, 50, 30, 20 };
static void common_init(void);
static void ticker_success_assert(uint32_t status, void *params);
static void ticker_stop_adv_assert(uint32_t status, void *params);
static void ticker_stop_obs_assert(uint32_t status, void *params);
static void ticker_update_adv_assert(uint32_t status, void *params);
static void ticker_update_slave_assert(uint32_t status, void *params);
static void event_inactive(uint32_t ticks_at_expire, uint32_t remainder,
uint16_t lazy, void *context);
static void adv_setup(void);
static void event_adv(uint32_t ticks_at_expire, uint32_t remainder,
uint16_t lazy, void *context);
static void event_obs(uint32_t ticks_at_expire, uint32_t remainder,
uint16_t lazy, void *context);
static void event_slave_prepare(uint32_t ticks_at_expire,
uint32_t remainder, uint16_t lazy,
void *context);
static void event_slave(uint32_t ticks_at_expire, uint32_t remainder,
uint16_t lazy, void *context);
static void event_master_prepare(uint32_t ticks_at_expire,
uint32_t remainder, uint16_t lazy,
void *context);
static void event_master(uint32_t ticks_at_expire, uint32_t remainder,
uint16_t lazy, void *context);
static void rx_packet_set(struct connection *conn,
struct pdu_data *pdu_data_rx);
static void tx_packet_set(struct connection *conn,
struct pdu_data *pdu_data_tx);
static void prepare_pdu_data_tx(struct connection *conn,
struct pdu_data **pdu_data_tx);
static void packet_rx_allocate(uint8_t max);
#if defined(CONFIG_BLUETOOTH_CONTROLLER_DATA_LENGTH)
static uint8_t packet_rx_acquired_count_get(void);
#endif /* CONFIG_BLUETOOTH_CONTROLLER_DATA_LENGTH */
static struct radio_pdu_node_rx *packet_rx_reserve_get(uint8_t count);
static void packet_rx_enqueue(void);
static void packet_tx_enqueue(uint8_t max);
static struct pdu_data *empty_tx_enqueue(struct connection *conn);
static void ctrl_tx_enqueue(struct connection *conn,
struct radio_pdu_node_tx *node_tx);
static void pdu_node_tx_release(uint16_t handle,
struct radio_pdu_node_tx *node_tx);
static void connection_release(struct connection *conn);
static uint32_t conn_update(struct connection *conn,
struct pdu_data *pdu_data_rx);
static uint32_t is_peer_compatible(struct connection *conn);
static uint32_t conn_update_req(struct connection *conn);
static uint32_t channel_map_update(struct connection *conn,
struct pdu_data *pdu_data_rx);
static void enc_req_reused_send(struct connection *conn,
struct radio_pdu_node_tx *node_tx);
static void terminate_ind_rx_enqueue(struct connection *conn, uint8_t reason);
static void enc_rsp_send(struct connection *conn);
static void start_enc_rsp_send(struct connection *conn,
struct pdu_data *pdu_ctrl_tx);
static void unknown_rsp_send(struct connection *conn, uint8_t type);
static void feature_rsp_send(struct connection *conn);
static void pause_enc_rsp_send(struct connection *conn);
static void version_ind_send(struct connection *conn);
#if defined(CONFIG_BLUETOOTH_CONTROLLER_LE_PING)
static void ping_resp_send(struct connection *conn);
#endif /* CONFIG_BLUETOOTH_CONTROLLER_LE_PING */
static void reject_ind_ext_send(struct connection *conn,
uint8_t reject_opcode,
uint8_t error_code);
#if defined(CONFIG_BLUETOOTH_CONTROLLER_DATA_LENGTH)
static void length_resp_send(struct connection *conn,
uint16_t eff_rx_octets,
uint16_t eff_tx_octets);
#endif /* CONFIG_BLUETOOTH_CONTROLLER_DATA_LENGTH */
static uint32_t role_disable(uint8_t ticker_id_primary,
uint8_t ticker_id_stop);
static void rx_fc_lock(uint16_t handle);
/*****************************************************************************
*RADIO
****************************************************************************/
uint32_t radio_init(void *hf_clock, uint8_t sca, uint8_t connection_count_max,
uint8_t rx_count_max, uint8_t tx_count_max,
uint16_t packet_data_octets_max,
uint16_t packet_tx_data_size, uint8_t *mem_radio,
uint16_t mem_size)
{
uint32_t retcode;
uint8_t *mem_radio_end;
/* intialise hf_clock device to use in prepare */
_radio.hf_clock = hf_clock;
/* initialise SCA */
_radio.sca = sca;
/* initialised radio mem end variable */
mem_radio_end = mem_radio + mem_size;
/* initialise connection context memory */
_radio.connection_count = connection_count_max;
_radio.conn_pool = mem_radio;
mem_radio += (sizeof(struct connection) * _radio.connection_count);
/* initialise rx and tx queue counts */
/* additional for pdu to NACK or receive empty PDU,
* 1 scan resp and 1* ctrl event.
*/
rx_count_max += 3;
/* additional pdu to send enc_req ctrl pdu */
tx_count_max += 1;
_radio.packet_rx_count = (rx_count_max + 1);
_radio.packet_tx_count = (tx_count_max + 1);
_radio.link_rx_data_quota = rx_count_max;
/* initialise rx queue memory */
_radio.packet_rx = (void *)mem_radio;
mem_radio +=
(sizeof(struct radio_pdu_node_rx *)*_radio.packet_rx_count);
/* initialise tx queue memory */
_radio.pkt_tx = (void *)mem_radio;
mem_radio += (sizeof(struct pdu_data_q_tx) * _radio.packet_tx_count);
/* initialise tx release queue memory */
_radio.pkt_release = (void *)mem_radio;
mem_radio += (sizeof(struct pdu_data_q_tx) * _radio.packet_tx_count);
/* initialise rx memory size and count */
_radio.packet_data_octets_max = packet_data_octets_max;
if ((RADIO_ACPDU_SIZE_MAX + 1) <
(offsetof(struct pdu_data, payload) +
_radio.packet_data_octets_max)) {
_radio.packet_rx_data_pool_size =
(MROUND(offsetof(struct radio_pdu_node_rx, pdu_data) +
offsetof(struct pdu_data, payload) +
_radio.packet_data_octets_max) * rx_count_max);
} else {
_radio.packet_rx_data_pool_size =
(MROUND(offsetof(struct radio_pdu_node_rx, pdu_data) +
(RADIO_ACPDU_SIZE_MAX + 1)) * rx_count_max);
}
_radio.packet_rx_data_size = PACKET_RX_DATA_SIZE_MIN;
_radio.packet_rx_data_count = (_radio.packet_rx_data_pool_size /
_radio.packet_rx_data_size);
/* initialise rx data pool memory */
_radio.pkt_rx_data_pool = mem_radio;
mem_radio += _radio.packet_rx_data_pool_size;
/* initialise rx link pool memory */
_radio.link_rx_pool = mem_radio;
mem_radio += (sizeof(void *) * 2 * (_radio.packet_rx_count +
_radio.connection_count));
/* initialise tx ctrl pool memory */
_radio.pkt_tx_ctrl_pool = mem_radio;
mem_radio += PACKET_TX_CTRL_SIZE_MIN * PACKET_MEM_COUNT_TX_CTRL;
/* initialise tx data memory size and count */
_radio.packet_tx_data_size =
MROUND(offsetof(struct radio_pdu_node_tx, pdu_data) +
offsetof(struct pdu_data, payload) +
packet_tx_data_size);
/* initialise tx data pool memory */
_radio.pkt_tx_data_pool = mem_radio;
mem_radio += (_radio.packet_tx_data_size * tx_count_max);
/* check for sufficient memory allocation for stack
* configuration.
*/
retcode = (mem_radio - mem_radio_end);
if (retcode) {
return (retcode + mem_size);
}
/* enable connection handle based on-off flow control feature.
* This is a simple flow control to rx data only on one selected
* connection handle.
* TODO: replace this feature with host-to-controller flowcontrol
* implementation/design.
*/
_radio.fc_ena = 1;
/* memory allocations */
common_init();
return retcode;
}
void ctrl_reset(void)
{
uint16_t conn_handle;
/* disable advertiser events */
role_disable(RADIO_TICKER_ID_ADV, RADIO_TICKER_ID_ADV_STOP);
/* disable oberver events */
role_disable(RADIO_TICKER_ID_OBS, RADIO_TICKER_ID_OBS_STOP);
/* disable connection events */
for (conn_handle = 0; conn_handle < _radio.connection_count;
conn_handle++) {
role_disable(RADIO_TICKER_ID_FIRST_CONNECTION + conn_handle,
TICKER_NULL);
}
/* reset controller context members */
_radio.filter_enable_bitmask = 0;
_radio.nirk = 0;
_radio.advertiser.conn = NULL;
_radio.observer.conn = NULL;
_radio.packet_rx_data_size = PACKET_RX_DATA_SIZE_MIN;
_radio.packet_rx_data_count = (_radio.packet_rx_data_pool_size /
_radio.packet_rx_data_size);
_radio.packet_rx_last = 0;
_radio.packet_rx_acquire = 0;
_radio.link_rx_data_quota = _radio.packet_rx_count - 1;
_radio.packet_tx_first = 0;
_radio.packet_tx_last = 0;
_radio.packet_release_first = 0;
_radio.packet_release_last = 0;
/* memory allocations */
common_init();
}
static void common_init(void)
{
void *link;
/* initialise connection pool. */
if (_radio.connection_count) {
mem_init(_radio.conn_pool, CONNECTION_T_SIZE,
_radio.connection_count,
&_radio.conn_free);
} else {
_radio.conn_free = NULL;
}
/* initialise rx pool. */
mem_init(_radio.pkt_rx_data_pool,
_radio.packet_rx_data_size,
_radio.packet_rx_data_count,
&_radio.pkt_rx_data_free);
/* initialise rx link pool. */
mem_init(_radio.link_rx_pool, (sizeof(void *) * 2),
(_radio.packet_rx_count + _radio.connection_count),
&_radio.link_rx_free);
/* initialise ctrl tx pool. */
mem_init(_radio.pkt_tx_ctrl_pool, PACKET_TX_CTRL_SIZE_MIN,
PACKET_MEM_COUNT_TX_CTRL, &_radio.pkt_tx_ctrl_free);
/* initialise data tx pool. */
mem_init(_radio.pkt_tx_data_pool, _radio.packet_tx_data_size,
(_radio.packet_tx_count - 1), &_radio.pkt_tx_data_free);
/* initialise the event-cum-data memq */
link = mem_acquire(&_radio.link_rx_free);
LL_ASSERT(link);
memq_init(link, &_radio.link_rx_head, (void *)&_radio.link_rx_tail);
/* initialise advertiser channel map */
_radio.advertiser.chl_map = 0x07;
/* initialise connection channel map */
_radio.data_channel_map[0] = 0xFF;
_radio.data_channel_map[1] = 0xFF;
_radio.data_channel_map[2] = 0xFF;
_radio.data_channel_map[3] = 0xFF;
_radio.data_channel_map[4] = 0x1F;
_radio.data_channel_count = 37;
#if defined(CONFIG_BLUETOOTH_CONTROLLER_DATA_LENGTH)
/* Initialize the DLE defaults */
_radio.default_tx_octets = RADIO_LL_LENGTH_OCTETS_RX_MIN;
_radio.default_tx_time = RADIO_LL_LENGTH_TIME_RX_MIN;
#endif /* CONFIG_BLUETOOTH_CONTROLLER_DATA_LENGTH */
/* allocate the rx queue */
packet_rx_allocate(0xFF);
}
static inline void isr_radio_state_tx(void)
{
_radio.state = STATE_RX;
radio_switch_complete_and_tx();
radio_tmr_hcto_configure(radio_tmr_end_get() +
RADIO_RX_CHAIN_DELAY_US + RADIO_HCTO_US -
RADIO_TX_CHAIN_DELAY_US);
switch (_radio.role) {
case ROLE_ADV:
radio_pkt_rx_set(radio_pkt_scratch_get());
/* assert if radio packet ptr is not set and radio started rx */
LL_ASSERT(!radio_is_ready());
if (_radio.advertiser.filter_policy && _radio.nirk) {
radio_ar_configure(_radio.nirk, _radio.irk);
}
radio_tmr_end_capture();
break;
case ROLE_OBS:
radio_pkt_rx_set(_radio.packet_rx[_radio.packet_rx_last]->
pdu_data);
/* assert if radio packet ptr is not set and radio started rx */
LL_ASSERT(!radio_is_ready());
radio_rssi_measure();
break;
case ROLE_MASTER:
#if defined(CONFIG_BLUETOOTH_CONTROLLER_CONN_RSSI)
if (_radio.packet_counter == 0) {
radio_rssi_measure();
}
#endif /* CONFIG_BLUETOOTH_CONTROLLER_CONN_RSSI */
/* fall thru */
case ROLE_SLAVE:
rx_packet_set(_radio.conn_curr, (struct pdu_data *)_radio.
packet_rx[_radio.packet_rx_last]->pdu_data);
/* assert if radio packet ptr is not set and radio started rx */
LL_ASSERT(!radio_is_ready());
radio_tmr_end_capture();
/* Route the tx packet to respective connections */
/* TODO: use timebox for tx enqueue (instead of 1 packet
* that is routed, which may not be for the current connection)
* try to route as much tx packet in queue into corresponding
* connection's tx list.
*/
packet_tx_enqueue(1);
break;
case ROLE_NONE:
default:
LL_ASSERT(0);
break;
}
}
static inline uint32_t isr_rx_adv(uint8_t devmatch_ok, uint8_t irkmatch_ok,
uint8_t irkmatch_id, uint8_t rssi_ready)
{
struct pdu_adv *pdu_adv, *_pdu_adv;
struct radio_pdu_node_rx *radio_pdu_node_rx;
pdu_adv = (struct pdu_adv *)radio_pkt_scratch_get();
if ((pdu_adv->type == PDU_ADV_TYPE_SCAN_REQ) &&
(pdu_adv->len == sizeof(struct pdu_adv_payload_scan_req)) &&
(((_radio.advertiser.filter_policy & 0x01) == 0) ||
(devmatch_ok) || (irkmatch_ok)) &&
(1 /** @todo own addr match check */)) {
_radio.state = STATE_CLOSE;
radio_switch_complete_and_disable();
/* TODO use rssi_ready to generate proprietary scan_req event */
ARG_UNUSED(rssi_ready);
/* use the latest scan data, if any */
if (_radio.advertiser.scan_data.first != _radio.
advertiser.scan_data.last) {
uint8_t first;
first = _radio.advertiser.scan_data.first + 1;
if (first == DOUBLE_BUFFER_SIZE) {
first = 0;
}
_radio.advertiser.scan_data.first = first;
}
radio_pkt_tx_set(&_radio.advertiser.scan_data.
data[_radio.advertiser.scan_data.first][0]);
return 0;
} else if ((pdu_adv->type == PDU_ADV_TYPE_CONNECT_REQ) &&
(pdu_adv->len == sizeof(struct pdu_adv_payload_connect_req)) &&
(((_radio.advertiser.filter_policy & 0x02) == 0) ||
(devmatch_ok) || (irkmatch_ok)) &&
(1 /** @todo own addr match check */) &&
((_radio.fc_ena == 0) || (_radio.fc_req == _radio.fc_ack)) &&
(_radio.advertiser.conn)) {
struct connection *conn;
uint32_t ticker_status;
uint32_t ticks_slot_offset;
uint32_t conn_interval_us;
struct pdu_data *pdu_data;
struct radio_le_conn_cmplt *radio_le_conn_cmplt;
radio_pdu_node_rx = packet_rx_reserve_get(3);
if (radio_pdu_node_rx == 0) {
return 1;
}
_radio.state = STATE_STOP;
radio_disable();
/* acquire the slave context from advertiser */
conn = _radio.advertiser.conn;
_radio.advertiser.conn = NULL;
/* Populate the slave context */
conn->handle = mem_index_get(conn, _radio.conn_pool,
CONNECTION_T_SIZE);
memcpy(&conn->crc_init[0],
&pdu_adv->payload.connect_req.lldata.crc_init[0],
3);
memcpy(&conn->access_addr[0],
&pdu_adv->payload.connect_req.lldata.access_addr[0],
4);
memcpy(&conn->data_channel_map[0],
&pdu_adv->payload.connect_req.lldata.channel_map[0],
sizeof(conn->data_channel_map));
conn->data_channel_count =
util_ones_count_get(&conn->data_channel_map[0],
sizeof(conn->data_channel_map));
conn->data_channel_hop =
pdu_adv->payload.connect_req.lldata.hop;
conn->conn_interval =
pdu_adv->payload.connect_req.lldata.interval;
conn_interval_us =
pdu_adv->payload.connect_req.lldata.interval * 1250;
conn->latency =
pdu_adv->payload.connect_req.lldata.latency;
memcpy((void *)&conn->role.slave.force, &conn->access_addr[0],
sizeof(conn->role.slave.force));
conn->supervision_reload =
RADIO_CONN_EVENTS((pdu_adv->payload.connect_req.lldata.timeout
* 10 * 1000), conn_interval_us);
conn->procedure_reload = RADIO_CONN_EVENTS((40 * 1000 * 1000),
conn_interval_us);
#if defined(CONFIG_BLUETOOTH_CONTROLLER_LE_PING)
/* APTO in no. of connection events */
conn->apto_reload = RADIO_CONN_EVENTS((30 * 1000 * 1000),
conn_interval_us);
/* Dispatch LE Ping PDU 6 connection events (that peer would
* listen to) before 30s timeout
* TODO: "peer listens to" is greater than 30s due to latency
*/
conn->appto_reload = (conn->apto_reload > (conn->latency + 6)) ?
(conn->apto_reload - (conn->latency + 6)) :
conn->apto_reload;
#endif /* CONFIG_BLUETOOTH_CONTROLLER_LE_PING */
/* Prepare the rx packet structure */
radio_pdu_node_rx->hdr.handle = conn->handle;
radio_pdu_node_rx->hdr.type = NODE_RX_TYPE_CONNECTION;
/* prepare connection complete structure */
pdu_data = (struct pdu_data *)radio_pdu_node_rx->pdu_data;
radio_le_conn_cmplt =
(struct radio_le_conn_cmplt *)&pdu_data->payload;
radio_le_conn_cmplt->status = 0x00;
radio_le_conn_cmplt->role = 0x01;
radio_le_conn_cmplt->peer_addr_type = pdu_adv->tx_addr;
memcpy(&radio_le_conn_cmplt->peer_addr[0],
&pdu_adv->payload.connect_req.init_addr[0],
BDADDR_SIZE);
radio_le_conn_cmplt->own_addr_type = pdu_adv->rx_addr;
memcpy(&radio_le_conn_cmplt->own_addr[0],
&pdu_adv->payload.connect_req.adv_addr[0], BDADDR_SIZE);
radio_le_conn_cmplt->peer_irk_index = irkmatch_id;
radio_le_conn_cmplt->interval =
pdu_adv->payload.connect_req.lldata.interval;
radio_le_conn_cmplt->latency =
pdu_adv->payload.connect_req.lldata.latency;
radio_le_conn_cmplt->timeout =
pdu_adv->payload.connect_req.lldata.timeout;
radio_le_conn_cmplt->mca =
pdu_adv->payload.connect_req.lldata.sca;
/* enqueue connection complete structure into queue */
rx_fc_lock(conn->handle);
packet_rx_enqueue();
/* calculate the window widening */
conn->role.slave.sca = pdu_adv->payload.connect_req.lldata.sca;
conn->role.slave.window_widening_periodic_us =
(((gc_lookup_ppm[_radio.sca] +
gc_lookup_ppm[conn->role.slave.sca]) *
conn_interval_us) + (1000000 - 1)) / 1000000;
conn->role.slave.window_widening_max_us =
(conn_interval_us >> 1) - 150;
conn->role.slave.window_size_event_us =
pdu_adv->payload.connect_req.lldata.win_size * 1250;
conn->role.slave.window_size_prepare_us = 0;
/* calculate slave slot */
conn->hdr.ticks_slot =
TICKER_US_TO_TICKS(RADIO_TICKER_START_PART_US +
RADIO_RX_READY_DELAY_US + 328 +
328 + 150);
conn->hdr.ticks_active_to_start = _radio.ticks_active_to_start;
conn->hdr.ticks_xtal_to_start =
TICKER_US_TO_TICKS(RADIO_TICKER_XTAL_OFFSET_US);
conn->hdr.ticks_preempt_to_start =
TICKER_US_TO_TICKS(RADIO_TICKER_PREEMPT_PART_MIN_US);
ticks_slot_offset =
(conn->hdr.ticks_active_to_start <
conn->hdr.ticks_xtal_to_start) ?
conn->hdr.ticks_xtal_to_start :
conn->hdr.ticks_active_to_start;
conn_interval_us -=
conn->role.slave.window_widening_periodic_us;
/* Stop Advertiser */
ticker_status = ticker_stop(RADIO_TICKER_INSTANCE_ID_RADIO,
RADIO_TICKER_USER_ID_WORKER,
RADIO_TICKER_ID_ADV,
ticker_stop_adv_assert,
(void *)__LINE__);
ticker_stop_adv_assert(ticker_status, (void *)__LINE__);
/* Stop Direct Adv Stopper */
_pdu_adv = (struct pdu_adv *)&_radio.advertiser.adv_data.data
[_radio.advertiser.adv_data.first][0];
if (_pdu_adv->type == PDU_ADV_TYPE_DIRECT_IND) {
/* Advertiser stop can expire while here in this ISR.
* Deferred attempt to stop can fail as it would have
* expired, hence ignore failure.
*/
ticker_stop(RADIO_TICKER_INSTANCE_ID_RADIO,
RADIO_TICKER_USER_ID_WORKER,
RADIO_TICKER_ID_ADV_STOP, NULL, NULL);
}
/* Start Slave */
ticker_status = ticker_start(RADIO_TICKER_INSTANCE_ID_RADIO,
RADIO_TICKER_USER_ID_WORKER,
RADIO_TICKER_ID_FIRST_CONNECTION
+ conn->handle, (_radio.ticks_anchor - ticks_slot_offset),
TICKER_US_TO_TICKS(radio_tmr_end_get() -
RADIO_TX_CHAIN_DELAY_US + (((uint64_t) pdu_adv->
payload.connect_req.lldata.win_offset + 1) * 1250) -
RADIO_RX_READY_DELAY_US - (RADIO_TICKER_JITTER_US << 1)),
TICKER_US_TO_TICKS(conn_interval_us),
TICKER_REMAINDER(conn_interval_us), TICKER_NULL_LAZY,
(ticks_slot_offset + conn->hdr.ticks_slot),
event_slave_prepare, conn, ticker_success_assert,
(void *)__LINE__);
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
(ticker_status == TICKER_STATUS_BUSY));
return 0;
}
return 1;
}
static inline uint32_t isr_rx_obs(uint8_t irkmatch_id, uint8_t rssi_ready)
{
struct pdu_adv *pdu_adv_rx;
struct radio_pdu_node_rx *radio_pdu_node_rx;
radio_pdu_node_rx = packet_rx_reserve_get(3);
if (radio_pdu_node_rx == 0) {
return 1;
}
pdu_adv_rx = (struct pdu_adv *)
_radio.packet_rx[_radio.packet_rx_last]->pdu_data;
/* Initiator */
if ((_radio.observer.conn) && ((_radio.fc_ena == 0) ||
(_radio.fc_req == _radio.fc_ack)) &&
(((pdu_adv_rx->type == PDU_ADV_TYPE_ADV_IND) &&
(((_radio.observer.filter_policy & 0x01) != 0) ||
((_radio.observer.adv_addr_type == pdu_adv_rx->tx_addr) &&
(memcmp(&_radio.observer.adv_addr[0],
&pdu_adv_rx->payload.adv_ind.addr[0],
BDADDR_SIZE) == 0)))) ||
((pdu_adv_rx->type == PDU_ADV_TYPE_DIRECT_IND) &&
(/* allow directed adv packets addressed to this device */
((_radio.observer.init_addr_type == pdu_adv_rx->rx_addr) &&
(memcmp(&_radio.observer.init_addr[0],
&pdu_adv_rx->payload.direct_ind.init_addr[0],
BDADDR_SIZE) == 0)) ||
/* allow directed adv packets where initiator address
* is resolvable private address
*/
(((_radio.observer.filter_policy & 0x02) != 0) &&
(pdu_adv_rx->rx_addr != 0) &&
((pdu_adv_rx->payload.direct_ind.init_addr[5] & 0xc0) == 0x40))))) &&
((radio_tmr_end_get() + 502) <
TICKER_TICKS_TO_US(_radio.observer.hdr.ticks_slot))) {
struct connection *conn;
struct pdu_adv *pdu_adv_tx;
struct pdu_data *pdu_data;
struct radio_le_conn_cmplt
*radio_le_conn_cmplt;
uint32_t ticker_status;
uint32_t ticks_slot_offset;
uint32_t conn_interval_us;
uint32_t conn_space_us;
_radio.state = STATE_STOP;
/* acquire the master context from observer */
conn = _radio.observer.conn;
_radio.observer.conn = NULL;
/* Tx the connect request packet */
pdu_adv_tx = (struct pdu_adv *)radio_pkt_scratch_get();
pdu_adv_tx->type = PDU_ADV_TYPE_CONNECT_REQ;
pdu_adv_tx->tx_addr = _radio.observer.init_addr_type;
pdu_adv_tx->rx_addr = pdu_adv_rx->tx_addr;
pdu_adv_tx->len = sizeof(struct pdu_adv_payload_connect_req);
memcpy(&pdu_adv_tx->payload.connect_req.init_addr[0],
&_radio.observer.init_addr[0], BDADDR_SIZE);
memcpy(&pdu_adv_tx->payload.connect_req.adv_addr[0],
&pdu_adv_rx->payload.adv_ind.addr[0], BDADDR_SIZE);
memcpy(&pdu_adv_tx->payload.connect_req.lldata.
access_addr[0], &conn->access_addr[0], 4);
memcpy(&pdu_adv_tx->payload.connect_req.lldata.crc_init[0],
&conn->crc_init[0], 3);
pdu_adv_tx->payload.connect_req.lldata. win_size = 1;
conn_interval_us =
(uint32_t)_radio.observer.conn_interval * 1250;
if (_radio.observer.win_offset_us == 0) {
conn_space_us = radio_tmr_end_get() -
RADIO_TX_CHAIN_DELAY_US + 502 + 1250 -
RADIO_TX_READY_DELAY_US;
pdu_adv_tx->payload.connect_req.lldata.win_offset = 0;
} else {
conn_space_us = _radio.observer. win_offset_us;
while ((conn_space_us & ((uint32_t)1 << 31)) ||
(conn_space_us < (radio_tmr_end_get() -
RADIO_TX_CHAIN_DELAY_US +
502 + 1250 -
RADIO_TX_READY_DELAY_US))) {
conn_space_us += conn_interval_us;
}
pdu_adv_tx->payload.connect_req.lldata. win_offset =
(conn_space_us - radio_tmr_end_get() +
RADIO_TX_CHAIN_DELAY_US - 502 - 1250 +
RADIO_TX_READY_DELAY_US) / 1250;
}
pdu_adv_tx->payload.connect_req.lldata.interval =
_radio.observer.conn_interval;
pdu_adv_tx->payload.connect_req.lldata.latency =
_radio.observer.conn_latency;
pdu_adv_tx->payload.connect_req.lldata.timeout =
_radio.observer.conn_timeout;
memcpy(&pdu_adv_tx->payload.connect_req.lldata.channel_map[0],
&conn->data_channel_map[0],
sizeof(pdu_adv_tx->payload.connect_req.lldata.channel_map));
pdu_adv_tx->payload.connect_req.lldata.hop =
conn->data_channel_hop;
pdu_adv_tx->payload.connect_req.lldata.sca = _radio.sca;
radio_switch_complete_and_disable();
radio_pkt_tx_set(pdu_adv_tx);
/* assert if radio packet ptr is not set and radio started tx */
LL_ASSERT(!radio_is_ready());
radio_tmr_end_capture();
/* block CPU so that there is no CRC error on pdu tx,
* this is only needed if we want the CPU to sleep.
* while(!radio_has_disabled())
* {cpu_sleep();}
* radio_status_reset();
*/
/* Populate the master context */
conn->handle = mem_index_get(conn, _radio.conn_pool,
CONNECTION_T_SIZE);
/* Prepare the rx packet structure */
radio_pdu_node_rx->hdr.handle = conn->handle;
radio_pdu_node_rx->hdr.type = NODE_RX_TYPE_CONNECTION;
/* prepare connection complete structure */
pdu_data = (struct pdu_data *)radio_pdu_node_rx->pdu_data;
radio_le_conn_cmplt =
(struct radio_le_conn_cmplt *)&pdu_data->payload;
radio_le_conn_cmplt->status = 0x00;
radio_le_conn_cmplt->role = 0x00;
radio_le_conn_cmplt->peer_addr_type = pdu_adv_tx->rx_addr;
memcpy(&radio_le_conn_cmplt->peer_addr[0],
&pdu_adv_tx->payload. connect_req.adv_addr[0],
BDADDR_SIZE);
radio_le_conn_cmplt->own_addr_type = pdu_adv_tx->tx_addr;
memcpy(&radio_le_conn_cmplt->own_addr[0],
&pdu_adv_tx->payload. connect_req.init_addr[0],
BDADDR_SIZE);
radio_le_conn_cmplt->peer_irk_index = irkmatch_id;
radio_le_conn_cmplt->interval = _radio.observer.conn_interval;
radio_le_conn_cmplt->latency = _radio.observer. conn_latency;
radio_le_conn_cmplt->timeout = _radio.observer.conn_timeout;
radio_le_conn_cmplt->mca =
pdu_adv_tx->payload.connect_req.lldata.sca;
/* enqueue connection complete structure into queue */
rx_fc_lock(conn->handle);
packet_rx_enqueue();
/* Calculate master slot */
conn->hdr.ticks_slot = _radio.observer.ticks_conn_slot;
conn->hdr.ticks_active_to_start = _radio.ticks_active_to_start;
conn->hdr.ticks_xtal_to_start =
TICKER_US_TO_TICKS(RADIO_TICKER_XTAL_OFFSET_US);
conn->hdr.ticks_preempt_to_start =
TICKER_US_TO_TICKS(RADIO_TICKER_PREEMPT_PART_MIN_US);
ticks_slot_offset =
(conn->hdr. ticks_active_to_start <
conn->hdr.ticks_xtal_to_start) ?
conn->hdr.ticks_xtal_to_start :
conn->hdr.ticks_active_to_start;
/* Stop Observer */
ticker_status = ticker_stop(RADIO_TICKER_INSTANCE_ID_RADIO,
RADIO_TICKER_USER_ID_WORKER,
RADIO_TICKER_ID_OBS,
ticker_stop_obs_assert,
(void *)__LINE__);
ticker_stop_obs_assert(ticker_status, (void *)__LINE__);
/* Observer stop can expire while here in this ISR.
* Deferred attempt to stop can fail as it would have
* expired, hence ignore failure.
*/
ticker_stop(RADIO_TICKER_INSTANCE_ID_RADIO,
RADIO_TICKER_USER_ID_WORKER,
RADIO_TICKER_ID_OBS_STOP, NULL, NULL);
/* Start master */
ticker_status =
ticker_start(RADIO_TICKER_INSTANCE_ID_RADIO,
RADIO_TICKER_USER_ID_WORKER,
RADIO_TICKER_ID_FIRST_CONNECTION +
conn->handle,
(_radio.ticks_anchor - ticks_slot_offset),
TICKER_US_TO_TICKS(conn_space_us),
TICKER_US_TO_TICKS(conn_interval_us),
TICKER_REMAINDER(conn_interval_us),
TICKER_NULL_LAZY,
(ticks_slot_offset + conn->hdr.ticks_slot),
event_master_prepare, conn,
ticker_success_assert, (void *)__LINE__);
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
(ticker_status == TICKER_STATUS_BUSY));
return 0;
}
/* Active scanner */
else if (((pdu_adv_rx->type == PDU_ADV_TYPE_ADV_IND) ||
(pdu_adv_rx->type == PDU_ADV_TYPE_SCAN_IND)) &&
(_radio.observer.scan_type != 0) &&
(_radio.observer.conn == 0)) {
struct pdu_adv *pdu_adv_tx;
/* save the RSSI value */
((uint8_t *)pdu_adv_rx)[offsetof(struct pdu_adv, payload) +
pdu_adv_rx->len] =
(rssi_ready) ? (radio_rssi_get() & 0x7F) : 0x7F;
/* save the adv packet */
radio_pdu_node_rx->hdr.handle = 0xffff;
radio_pdu_node_rx->hdr.type = NODE_RX_TYPE_REPORT;
packet_rx_enqueue();
/* prepare the scan request packet */
pdu_adv_tx = (struct pdu_adv *)radio_pkt_scratch_get();
pdu_adv_tx->type = PDU_ADV_TYPE_SCAN_REQ;
pdu_adv_tx->tx_addr = _radio.observer.init_addr_type;
pdu_adv_tx->rx_addr = pdu_adv_rx->tx_addr;
pdu_adv_tx->len = sizeof(struct pdu_adv_payload_scan_req);
memcpy(&pdu_adv_tx->payload.scan_req.scan_addr[0],
&_radio.observer.init_addr[0], BDADDR_SIZE);
memcpy(&pdu_adv_tx->payload.scan_req.adv_addr[0],
&pdu_adv_rx->payload.adv_ind.addr[0], BDADDR_SIZE);
/* switch scanner state to active */
_radio.observer.scan_state = 1;
_radio.state = STATE_TX;
radio_pkt_tx_set(pdu_adv_tx);
radio_switch_complete_and_rx();
radio_tmr_end_capture();
return 0;
}
/* Passive scanner or scan responses */
else if (((pdu_adv_rx->type == PDU_ADV_TYPE_ADV_IND) ||
((pdu_adv_rx->type == PDU_ADV_TYPE_DIRECT_IND) &&
(/* allow directed adv packets addressed to this device */
((_radio.observer.init_addr_type == pdu_adv_rx->rx_addr) &&
(memcmp(&_radio.observer.init_addr[0],
&pdu_adv_rx->payload.direct_ind.init_addr[0],
BDADDR_SIZE) == 0)) ||
/* allow directed adv packets where initiator address
* is resolvable private address
*/
(((_radio.observer.filter_policy & 0x02) != 0) &&
(pdu_adv_rx->rx_addr != 0) &&
((pdu_adv_rx->payload.direct_ind.init_addr[5] & 0xc0) == 0x40)))) ||
(pdu_adv_rx->type == PDU_ADV_TYPE_NONCONN_IND) ||
(pdu_adv_rx->type == PDU_ADV_TYPE_SCAN_IND) ||
((pdu_adv_rx->type == PDU_ADV_TYPE_SCAN_RESP) &&
(_radio.observer.scan_state != 0))) &&
(pdu_adv_rx->len != 0) && (!_radio.observer.conn)) {
/* save the RSSI value */
((uint8_t *)pdu_adv_rx)[offsetof(struct pdu_adv, payload) +
pdu_adv_rx->len] =
(rssi_ready) ? (radio_rssi_get() & 0x7f) : 0x7f;
/* save the scan response packet */
radio_pdu_node_rx->hdr.handle = 0xffff;
radio_pdu_node_rx->hdr.type = NODE_RX_TYPE_REPORT;
packet_rx_enqueue();
}
/* invalid PDU */
else {
/* ignore and close this rx/tx chain ( code below ) */
return 1;
}
return 1;
}
static inline uint8_t isr_rx_conn_pkt_ack(struct pdu_data *pdu_data_tx,
struct radio_pdu_node_tx **node_tx)
{
uint8_t terminate = 0;
switch (pdu_data_tx->payload.llctrl.opcode) {
case PDU_DATA_LLCTRL_TYPE_TERMINATE_IND:
_radio.state = STATE_CLOSE;
radio_disable();
/* assert if radio packet ptr is not set and radio started tx */
LL_ASSERT(!radio_is_ready());
terminate_ind_rx_enqueue(_radio.conn_curr,
(pdu_data_tx->payload.llctrl.ctrldata.terminate_ind.
error_code == 0x13) ? 0x16 :
pdu_data_tx->payload.llctrl.ctrldata.terminate_ind.
error_code);
/* Ack received, hence terminate */
terminate = 1;
break;
case PDU_DATA_LLCTRL_TYPE_ENC_REQ:
/* things from master stored for session key calculation */
memcpy(&_radio.conn_curr->llcp.encryption.skd[0],
&pdu_data_tx->payload.llctrl.ctrldata.enc_req.skdm[0],
8);
memcpy(&_radio.conn_curr->ccm_rx.iv[0],
&pdu_data_tx->payload.llctrl.ctrldata.enc_req.ivm[0],
4);
/* pause data packet tx */
_radio.conn_curr->pause_tx = 1;
/* Start Procedure Timeout (this will not replace terminate
* procedure which always gets place before any packets
* going out, hence safe by design).
*/
_radio.conn_curr->procedure_expire =
_radio.conn_curr->procedure_reload;
break;
case PDU_