Add Kconfig configuration to conditionally enable LE Ping feature in the Controller. This will save CPU time, flash and RAM, if this feature is not desired. Change-id: I5fbbdbe8f45ac01c9b0d7b11e002a0d1db4d272e Signed-off-by: Vinayak Chettimada <vinayak.kariappa.chettimada@nordicsemi.no>
7889 lines
218 KiB
C
7889 lines
218 KiB
C
/*
|
|
* Copyright (c) 2016 Nordic Semiconductor ASA
|
|
* Copyright (c) 2016 Vinayak Kariappa Chettimada
|
|
*
|
|
* Licensed 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 <stddef.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include <misc/util.h>
|
|
#include <device.h>
|
|
#include <clock_control.h>
|
|
|
|
#include "hal_work.h"
|
|
|
|
#include "defines.h"
|
|
#include "cpu.h"
|
|
#include "work.h"
|
|
#include "rand.h"
|
|
#include "ticker.h"
|
|
#include "mem.h"
|
|
#include "memq.h"
|
|
#include "util.h"
|
|
#include "ecb.h"
|
|
#include "ccm.h"
|
|
#include "radio.h"
|
|
#include "pdu.h"
|
|
#include "ctrl_internal.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
|
|
|
|
#define RADIO_RSSI_SAMPLE_COUNT 10
|
|
#define RADIO_RSSI_THRESHOLD 4
|
|
|
|
#define RADIO_IRK_COUNT_MAX 8
|
|
|
|
#define XTAL_ADVANCED 1
|
|
#define SCHED_ADVANCED 1
|
|
#define FAST_ENC_PROCEDURE 0
|
|
#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;
|
|
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;
|
|
|
|
/* DLE global settings */
|
|
uint16_t default_tx_octets;
|
|
uint16_t default_tx_time;
|
|
|
|
/** @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_pool_size;
|
|
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 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);
|
|
static uint8_t packet_rx_acquired_count_get(void);
|
|
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);
|
|
static void length_resp_send(struct connection *conn,
|
|
uint16_t eff_rx_octets,
|
|
uint16_t eff_tx_octets);
|
|
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, 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 = (struct radio_pdu_node_rx **)mem_radio;
|
|
mem_radio +=
|
|
(sizeof(struct radio_pdu_node_rx *)*_radio.packet_rx_count);
|
|
|
|
/* initialise tx queue memory */
|
|
_radio.pkt_tx = (struct pdu_data_q_tx *)mem_radio;
|
|
mem_radio += (sizeof(struct pdu_data_q_tx) * _radio.packet_tx_count);
|
|
|
|
/* initialise tx release queue memory */
|
|
_radio.pkt_release = (struct pdu_data_q_tx *)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 =
|
|
(ALIGN4(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 =
|
|
(ALIGN4(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 =
|
|
ALIGN4(offsetof(struct radio_pdu_node_tx, pdu_data) +
|
|
offsetof(struct pdu_data, payload) +
|
|
_radio.packet_data_octets_max);
|
|
_radio.packet_tx_data_pool_size =
|
|
(_radio.packet_tx_data_size * tx_count_max);
|
|
|
|
/* initialise tx data pool memory */
|
|
_radio.pkt_tx_data_pool = mem_radio;
|
|
mem_radio += _radio.packet_tx_data_pool_size;
|
|
|
|
/* 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;
|
|
|
|
/* Initialize the DLE defaults */
|
|
_radio.default_tx_octets = RADIO_LL_LENGTH_OCTETS_RX_MIN;
|
|
_radio.default_tx_time = RADIO_LL_LENGTH_TIME_RX_MIN;
|
|
|
|
/* 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 (_radio.packet_counter == 0) {
|
|
radio_rssi_measure();
|
|
}
|
|
/* 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 */
|
|
rssi_ready = rssi_ready; /* unused for now */
|
|
|
|
/* 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)
|
|
conn->apto_reload = RADIO_CONN_EVENTS((30 * 1000 * 1000),
|
|
conn_interval_us);
|
|
conn->appto_reload =
|
|
(conn->apto_reload > (conn->latency + 2)) ?
|
|
(conn->apto_reload - (conn->latency + 2)) :
|
|
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_success_assert, (void *)__LINE__);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
|
|
/* 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) {
|
|
ticker_status =
|
|
ticker_stop(
|
|
RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER,
|
|
RADIO_TICKER_ID_ADV_STOP,
|
|
0, /* @todo ticker_success_assert */
|
|
0 /* @todo (void *) __LINE__*/);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
}
|
|
|
|
/* 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 and start Master */
|
|
ticker_status = ticker_stop(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER,
|
|
RADIO_TICKER_ID_OBS,
|
|
ticker_success_assert,
|
|
(void *)__LINE__);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
ticker_status = ticker_stop(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER,
|
|
RADIO_TICKER_ID_OBS_STOP,
|
|
0, /* @todo ticker_success_assert */
|
|
0 /* @todo (void *) __LINE__ */);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
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_DATA_LLCTRL_TYPE_ENC_RSP:
|
|
/* pause data packet tx */
|
|
_radio.conn_curr->pause_tx = 1;
|
|
break;
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_START_ENC_REQ:
|
|
/* Nothing to do.
|
|
* Remember that we may have received encrypted START_ENC_RSP
|
|
* alongwith this tx ack at this point in time.
|
|
*/
|
|
break;
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_REQ:
|
|
/* pause data packet tx */
|
|
_radio.conn_curr->pause_tx = 1;
|
|
|
|
/* key refresh */
|
|
_radio.conn_curr->refresh = 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_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP:
|
|
if (_radio.role == ROLE_MASTER) {
|
|
/* reused tx-ed PDU and send enc req */
|
|
enc_req_reused_send(_radio.conn_curr, *node_tx);
|
|
|
|
/* dont release ctrl PDU memory */
|
|
*node_tx = NULL;
|
|
} else {
|
|
/* pause data packet tx */
|
|
_radio.conn_curr->pause_tx = 1;
|
|
}
|
|
break;
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_REJECT_IND:
|
|
/* resume data packet rx and tx */
|
|
_radio.conn_curr->pause_rx = 0;
|
|
_radio.conn_curr->pause_tx = 0;
|
|
|
|
/* Procedure complete */
|
|
_radio.conn_curr->procedure_expire = 0;
|
|
break;
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_LENGTH_REQ:
|
|
if ((_radio.conn_curr->llcp_length.req !=
|
|
_radio.conn_curr->llcp_length.ack) &&
|
|
(_radio.conn_curr->llcp_length.state ==
|
|
LLCP_LENGTH_STATE_ACK_WAIT)){
|
|
/* pause data packet tx */
|
|
_radio.conn_curr->pause_tx = 1;
|
|
|
|
/* wait for response */
|
|
_radio.conn_curr->llcp_length.state =
|
|
LLCP_LENGTH_STATE_RSP_WAIT;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* Do nothing for other ctrl packet ack */
|
|
break;
|
|
}
|
|
|
|
return terminate;
|
|
}
|
|
|
|
static inline struct radio_pdu_node_tx *
|
|
isr_rx_conn_pkt_release(struct radio_pdu_node_tx *node_tx)
|
|
{
|
|
_radio.conn_curr->packet_tx_head_len = 0;
|
|
_radio.conn_curr->packet_tx_head_offset = 0;
|
|
|
|
/* release */
|
|
if (_radio.conn_curr->pkt_tx_head == _radio.conn_curr->pkt_tx_ctrl) {
|
|
if (node_tx) {
|
|
_radio.conn_curr->pkt_tx_ctrl =
|
|
_radio.conn_curr->pkt_tx_ctrl->next;
|
|
_radio.conn_curr->pkt_tx_head =
|
|
_radio.conn_curr->pkt_tx_ctrl;
|
|
if (_radio.conn_curr->pkt_tx_ctrl ==
|
|
_radio.conn_curr->pkt_tx_data) {
|
|
_radio.conn_curr->pkt_tx_ctrl = NULL;
|
|
}
|
|
|
|
mem_release(node_tx, &_radio. pkt_tx_ctrl_free);
|
|
}
|
|
} else {
|
|
if (_radio.conn_curr->pkt_tx_head ==
|
|
_radio.conn_curr->pkt_tx_data) {
|
|
_radio.conn_curr->pkt_tx_data =
|
|
_radio.conn_curr->pkt_tx_data->next;
|
|
}
|
|
_radio.conn_curr->pkt_tx_head =
|
|
_radio.conn_curr->pkt_tx_head->next;
|
|
|
|
return node_tx;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static inline void
|
|
isr_rx_conn_pkt_ctrl_rej(struct radio_pdu_node_rx *radio_pdu_node_rx,
|
|
uint8_t *rx_enqueue)
|
|
{
|
|
/* reset ctrl procedure */
|
|
_radio.conn_curr->llcp_ack = _radio.conn_curr->llcp_req;
|
|
|
|
switch (_radio.conn_curr->llcp_type) {
|
|
case LLCP_CONNECTION_UPDATE:
|
|
LL_ASSERT(_radio.conn_upd == _radio.conn_curr);
|
|
|
|
/* reset mutex */
|
|
_radio.conn_upd = NULL;
|
|
|
|
/* update to next ticks offsets */
|
|
if (_radio.conn_curr->role.slave.role != 0) {
|
|
_radio.conn_curr->role.slave.ticks_to_offset =
|
|
_radio.conn_curr->llcp.connection_update.
|
|
ticks_to_offset_next;
|
|
}
|
|
|
|
/* conn param req procedure, if any, is complete */
|
|
_radio.conn_curr->procedure_expire = 0;
|
|
|
|
/* enqueue the reject ind ext */
|
|
if (!_radio.conn_curr->llcp.connection_update.is_internal) {
|
|
struct radio_le_conn_update_cmplt
|
|
*radio_le_conn_update_cmplt;
|
|
struct pdu_data *pdu_data_rx;
|
|
|
|
radio_pdu_node_rx->hdr.type = NODE_RX_TYPE_CONN_UPDATE;
|
|
|
|
/* prepare connection update complete structure */
|
|
pdu_data_rx =
|
|
(struct pdu_data *)radio_pdu_node_rx->pdu_data;
|
|
radio_le_conn_update_cmplt =
|
|
(struct radio_le_conn_update_cmplt *)
|
|
&pdu_data_rx->payload;
|
|
radio_le_conn_update_cmplt->status = 0x00;
|
|
radio_le_conn_update_cmplt->interval =
|
|
_radio.conn_curr->conn_interval;
|
|
radio_le_conn_update_cmplt->latency =
|
|
_radio.conn_curr->latency;
|
|
radio_le_conn_update_cmplt->timeout =
|
|
_radio.conn_curr->supervision_reload *
|
|
_radio.conn_curr->conn_interval * 125 / 1000;
|
|
|
|
*rx_enqueue = 1;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
LL_ASSERT(0);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
static inline uint8_t isr_rx_conn_pkt_ctrl_dle(struct pdu_data *pdu_data_rx,
|
|
uint8_t *rx_enqueue)
|
|
{
|
|
uint16_t eff_rx_octets;
|
|
uint16_t eff_tx_octets;
|
|
uint8_t nack = 0;
|
|
|
|
eff_rx_octets = _radio.conn_curr->max_rx_octets;
|
|
eff_tx_octets = _radio.conn_curr->max_tx_octets;
|
|
|
|
if (/* Local idle, and peer request; complete the peer procedure
|
|
* with response.
|
|
*/
|
|
((_radio.conn_curr->llcp_length.req ==
|
|
_radio.conn_curr->llcp_length.ack) &&
|
|
(PDU_DATA_LLCTRL_TYPE_LENGTH_REQ ==
|
|
pdu_data_rx->payload.llctrl.opcode)) ||
|
|
/* or Local has requested... */
|
|
((_radio.conn_curr->llcp_length.req !=
|
|
_radio.conn_curr->llcp_length.ack) &&
|
|
/* and Local request, and peer request; override with peer
|
|
* procedure, and complete the peer procedure with response.
|
|
*/
|
|
(((LLCP_LENGTH_STATE_REQ == _radio.conn_curr->llcp_length.state) &&
|
|
(PDU_DATA_LLCTRL_TYPE_LENGTH_REQ ==
|
|
pdu_data_rx->payload.llctrl.opcode)) ||
|
|
/* and Local wait, and peer response; complete the
|
|
* local procedure.
|
|
*/
|
|
((LLCP_LENGTH_STATE_RSP_WAIT ==
|
|
_radio.conn_curr->llcp_length.state) &&
|
|
(PDU_DATA_LLCTRL_TYPE_LENGTH_RSP ==
|
|
pdu_data_rx->payload.llctrl.opcode))))) {
|
|
|
|
struct pdu_data_llctrl_length_req_rsp *lr;
|
|
|
|
lr = (struct pdu_data_llctrl_length_req_rsp *)
|
|
&pdu_data_rx->payload.llctrl.ctrldata.length_req;
|
|
|
|
/* use the minimal of our default_tx_octets and
|
|
* peer max_rx_octets
|
|
*/
|
|
eff_tx_octets = min(lr->max_rx_octets,
|
|
_radio.conn_curr->default_tx_octets);
|
|
|
|
/* use the minimal of our max supported and
|
|
* peer max_tx_octets
|
|
*/
|
|
eff_rx_octets = min(lr->max_tx_octets,
|
|
RADIO_LL_LENGTH_OCTETS_RX_MAX);
|
|
|
|
/* check if change in rx octets */
|
|
if (eff_rx_octets != _radio.conn_curr->max_rx_octets) {
|
|
uint16_t free_count_rx;
|
|
|
|
free_count_rx = packet_rx_acquired_count_get()
|
|
+ mem_free_count_get(_radio.pkt_rx_data_free);
|
|
LL_ASSERT(free_count_rx <= 0xFF);
|
|
|
|
if (_radio.packet_rx_data_count == free_count_rx) {
|
|
|
|
/* accept the effective tx */
|
|
_radio.conn_curr->max_tx_octets = eff_tx_octets;
|
|
|
|
/* trigger or retain the ctrl procedure so as
|
|
* to resize the rx buffers.
|
|
*/
|
|
_radio.conn_curr->llcp_length.rx_octets =
|
|
eff_rx_octets;
|
|
_radio.conn_curr->llcp_length.tx_octets =
|
|
eff_tx_octets;
|
|
_radio.conn_curr->llcp_length.ack =
|
|
(_radio.conn_curr->llcp_length.req - 1);
|
|
_radio.conn_curr->llcp_length.state =
|
|
LLCP_LENGTH_STATE_RESIZE;
|
|
|
|
/* close the current connection event, so as
|
|
* to perform rx octet change.
|
|
*/
|
|
_radio.state = STATE_CLOSE;
|
|
} else {
|
|
nack = 1;
|
|
}
|
|
} else {
|
|
/* resume data packet tx */
|
|
_radio.conn_curr->pause_tx = 0;
|
|
|
|
/* accept the effective tx */
|
|
_radio.conn_curr->max_tx_octets = eff_tx_octets;
|
|
|
|
/* Procedure complete */
|
|
_radio.conn_curr->llcp_length.ack =
|
|
_radio.conn_curr->llcp_length.req;
|
|
_radio.conn_curr->procedure_expire = 0;
|
|
|
|
/* prepare event params */
|
|
lr->max_rx_octets = eff_rx_octets;
|
|
lr->max_rx_time = ((eff_rx_octets + 14) << 3);
|
|
lr->max_tx_octets = eff_tx_octets;
|
|
lr->max_tx_time = ((eff_tx_octets + 14) << 3);
|
|
|
|
/* Enqueue data length change event (with no change in
|
|
* rx length happened), safe to enqueue rx.
|
|
*/
|
|
*rx_enqueue = 1;
|
|
}
|
|
} else {
|
|
LL_ASSERT(0);
|
|
}
|
|
|
|
if ((PDU_DATA_LLCTRL_TYPE_LENGTH_REQ ==
|
|
pdu_data_rx->payload.llctrl.opcode) && !nack) {
|
|
length_resp_send(_radio.conn_curr, eff_rx_octets,
|
|
eff_tx_octets);
|
|
}
|
|
|
|
return nack;
|
|
}
|
|
|
|
static inline uint8_t
|
|
isr_rx_conn_pkt_ctrl(struct radio_pdu_node_rx *radio_pdu_node_rx,
|
|
uint8_t *rx_enqueue)
|
|
{
|
|
struct pdu_data *pdu_data_rx;
|
|
uint8_t nack = 0;
|
|
|
|
pdu_data_rx = (struct pdu_data *)radio_pdu_node_rx->pdu_data;
|
|
switch (pdu_data_rx->payload.llctrl.opcode) {
|
|
case PDU_DATA_LLCTRL_TYPE_CONN_UPDATE_REQ:
|
|
if (conn_update(_radio.conn_curr, pdu_data_rx) == 0) {
|
|
/* conn param req procedure, if any, is complete */
|
|
_radio.conn_curr->procedure_expire = 0;
|
|
} else {
|
|
_radio.conn_curr->llcp_terminate.reason_peer = 0x28;
|
|
}
|
|
break;
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_CHANNEL_MAP_REQ:
|
|
if (channel_map_update(_radio.conn_curr, pdu_data_rx)) {
|
|
_radio.conn_curr->llcp_terminate.reason_peer = 0x28;
|
|
}
|
|
break;
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_TERMINATE_IND:
|
|
/* Ack and then terminate */
|
|
_radio.conn_curr->llcp_terminate.reason_peer =
|
|
pdu_data_rx->payload.llctrl.ctrldata.terminate_ind.error_code;
|
|
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_rx->payload.llctrl.ctrldata.enc_req.skdm[0],
|
|
8);
|
|
memcpy(&_radio.conn_curr->ccm_rx.iv[0],
|
|
&pdu_data_rx->payload.llctrl.ctrldata.enc_req.ivm[0], 4);
|
|
|
|
/* pause rx data packets */
|
|
_radio.conn_curr->pause_rx = 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;
|
|
|
|
/* TODO: remove this code block.
|
|
* test peer master for overlapping contrl procedure.
|
|
*/
|
|
#if 0
|
|
if (_radio.conn_curr->llcp_version.tx == 0) {
|
|
_radio.conn_curr->llcp_version.tx = 1;
|
|
|
|
version_ind_send(_radio.conn_curr);
|
|
}
|
|
#endif
|
|
|
|
#if !!FAST_ENC_PROCEDURE
|
|
/* TODO BT Spec. text: may finalize the sending of additional
|
|
* data channel PDUs queued in the controller.
|
|
*/
|
|
enc_rsp_send(_radio.conn_curr);
|
|
#endif
|
|
|
|
/* enqueue the enc req */
|
|
*rx_enqueue = 1;
|
|
break;
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_ENC_RSP:
|
|
/* things sent by slave stored for session key calculation */
|
|
memcpy(&_radio.conn_curr->llcp.encryption.skd[8],
|
|
&pdu_data_rx->payload.llctrl.ctrldata.enc_rsp.skds[0],
|
|
8);
|
|
memcpy(&_radio.conn_curr->ccm_rx.iv[4],
|
|
&pdu_data_rx->payload.llctrl.ctrldata.enc_rsp.ivs[0],
|
|
4);
|
|
|
|
/* pause rx data packets */
|
|
_radio.conn_curr->pause_rx = 1;
|
|
break;
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_START_ENC_REQ:
|
|
LL_ASSERT(_radio.conn_curr->llcp_req ==
|
|
_radio.conn_curr->llcp_ack);
|
|
|
|
/* start enc rsp to be scheduled in master prepare */
|
|
_radio.conn_curr->llcp_type = LLCP_ENCRYPTION;
|
|
_radio.conn_curr->llcp_ack--;
|
|
break;
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_START_ENC_RSP:
|
|
if (_radio.role == ROLE_SLAVE) {
|
|
#if !FAST_ENC_PROCEDURE
|
|
LL_ASSERT(_radio.conn_curr->llcp_req ==
|
|
_radio.conn_curr->llcp_ack);
|
|
|
|
/* start enc rsp to be scheduled in slave prepare */
|
|
_radio.conn_curr->llcp_type = LLCP_ENCRYPTION;
|
|
_radio.conn_curr->llcp_ack--;
|
|
#else
|
|
/* enable transmit encryption */
|
|
_radio.conn_curr->enc_tx = 1;
|
|
|
|
start_enc_rsp_send(_radio.conn_curr, 0);
|
|
|
|
/* resume data packet rx and tx */
|
|
_radio.conn_curr->pause_rx = 0;
|
|
_radio.conn_curr->pause_tx = 0;
|
|
#endif
|
|
} else {
|
|
/* resume data packet rx and tx */
|
|
_radio.conn_curr->pause_rx = 0;
|
|
_radio.conn_curr->pause_tx = 0;
|
|
}
|
|
|
|
/* enqueue the start enc resp (encryption change/refresh) */
|
|
if (_radio.conn_curr->refresh) {
|
|
_radio.conn_curr->refresh = 0;
|
|
|
|
/* key refresh event */
|
|
radio_pdu_node_rx->hdr.type = NODE_RX_TYPE_ENC_REFRESH;
|
|
}
|
|
*rx_enqueue = 1;
|
|
|
|
/* Procedure complete */
|
|
_radio.conn_curr->procedure_expire = 0;
|
|
break;
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_FEATURE_REQ:
|
|
case PDU_DATA_LLCTRL_TYPE_SLAVE_FEATURE_REQ:
|
|
/* AND the feature set to get Feature USED */
|
|
_radio.conn_curr->llcp_features &=
|
|
pdu_data_rx->payload.llctrl.ctrldata.feature_req.features[0];
|
|
|
|
feature_rsp_send(_radio.conn_curr);
|
|
break;
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_FEATURE_RSP:
|
|
/* AND the feature set to get Feature USED */
|
|
_radio.conn_curr->llcp_features &=
|
|
pdu_data_rx->payload.llctrl.ctrldata.feature_rsp.features[0];
|
|
|
|
/* enqueue the feature resp */
|
|
*rx_enqueue = 1;
|
|
|
|
/* Procedure complete */
|
|
_radio.conn_curr->procedure_expire = 0;
|
|
break;
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_REQ:
|
|
pause_enc_rsp_send(_radio.conn_curr);
|
|
|
|
/* pause data packet rx */
|
|
_radio.conn_curr->pause_rx = 1;
|
|
|
|
/* key refresh */
|
|
_radio.conn_curr->refresh = 1;
|
|
|
|
/* disable receive encryption */
|
|
_radio.conn_curr->enc_rx = 0;
|
|
break;
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP:
|
|
if (_radio.role == ROLE_MASTER) {
|
|
/* reply with pause enc rsp */
|
|
pause_enc_rsp_send(_radio.conn_curr);
|
|
|
|
/* disable receive encryption */
|
|
_radio.conn_curr->enc_rx = 0;
|
|
}
|
|
|
|
/* pause data packet rx */
|
|
_radio.conn_curr->pause_rx = 1;
|
|
|
|
/* disable transmit encryption */
|
|
_radio.conn_curr->enc_tx = 0;
|
|
break;
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_VERSION_IND:
|
|
_radio.conn_curr->llcp_version.version_number =
|
|
pdu_data_rx->payload.llctrl.ctrldata.
|
|
version_ind.version_number;
|
|
_radio.conn_curr->llcp_version. company_id =
|
|
pdu_data_rx->payload.llctrl.ctrldata.version_ind.company_id;
|
|
_radio.conn_curr->llcp_version.sub_version_number =
|
|
pdu_data_rx->payload.llctrl.ctrldata.version_ind.sub_version_number;
|
|
|
|
if ((_radio.conn_curr->llcp_version.tx != 0) &&
|
|
(_radio.conn_curr->llcp_version.rx == 0)) {
|
|
/* enqueue the version ind */
|
|
*rx_enqueue = 1;
|
|
|
|
/* Procedure complete */
|
|
_radio.conn_curr->procedure_expire = 0;
|
|
}
|
|
|
|
_radio.conn_curr->llcp_version.rx = 1;
|
|
|
|
if (_radio.conn_curr->llcp_version.tx == 0) {
|
|
_radio.conn_curr->llcp_version.tx = 1;
|
|
|
|
version_ind_send(_radio.conn_curr);
|
|
}
|
|
break;
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_REJECT_IND:
|
|
/* resume data packet rx and tx */
|
|
_radio.conn_curr->pause_rx = 0;
|
|
_radio.conn_curr->pause_tx = 0;
|
|
|
|
/* Procedure complete */
|
|
_radio.conn_curr->procedure_expire = 0;
|
|
|
|
/* enqueue the reject ind */
|
|
*rx_enqueue = 1;
|
|
break;
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ:
|
|
/* connection update or params req in progress
|
|
* 1. if connection update in progress, then both master and
|
|
* slave ignore this param req (in out impl. we assert,
|
|
* see below assert).
|
|
* 2. if connection param req to be initiated, slave drop
|
|
* initiation and respond to this req, master ignore this
|
|
* param req and continue to initiate.
|
|
* 3. if connection param rsp waited for, slave drop waiting
|
|
* and respond to this req, master ignore this param req and
|
|
* master continue waiting.
|
|
*/
|
|
|
|
/* no ctrl procedures in progress or master req while slave
|
|
* waiting resp.
|
|
*/
|
|
if (((_radio.conn_curr->llcp_req == _radio.conn_curr->llcp_ack) &&
|
|
(_radio.conn_upd == 0)) ||
|
|
((_radio.conn_curr->llcp_req != _radio.conn_curr->llcp_ack) &&
|
|
(_radio.conn_curr->role.slave.role != 0) &&
|
|
(_radio.conn_curr == _radio.conn_upd) &&
|
|
(_radio.conn_curr->llcp_type == LLCP_CONNECTION_UPDATE) &&
|
|
((_radio.conn_curr->llcp.connection_update.state ==
|
|
LLCP_CONN_STATE_INITIATE) ||
|
|
(_radio.conn_curr->llcp.connection_update.state ==
|
|
LLCP_CONN_STATE_REQ) ||
|
|
(_radio.conn_curr->llcp.connection_update.state ==
|
|
LLCP_CONN_STATE_RSP_WAIT)))) {
|
|
/* set mutex */
|
|
if (_radio.conn_upd == 0) {
|
|
_radio.conn_upd = _radio.conn_curr;
|
|
}
|
|
|
|
/* resp to be generated by app, for now save
|
|
* parameters
|
|
*/
|
|
_radio.conn_curr->llcp.connection_update.interval =
|
|
pdu_data_rx->payload.llctrl.ctrldata.conn_param_req.interval_min;
|
|
_radio.conn_curr->llcp.connection_update.latency =
|
|
pdu_data_rx->payload.llctrl.ctrldata.conn_param_req.latency;
|
|
_radio.conn_curr->llcp.connection_update.timeout =
|
|
pdu_data_rx->payload.llctrl.ctrldata.conn_param_req.timeout;
|
|
_radio.conn_curr->llcp.connection_update.preferred_periodicity =
|
|
pdu_data_rx->payload.llctrl.ctrldata.conn_param_req.preferred_periodicity;
|
|
_radio.conn_curr->llcp.connection_update.instant =
|
|
pdu_data_rx->payload.llctrl.ctrldata.conn_param_req.
|
|
reference_conn_event_count;
|
|
_radio.conn_curr->llcp.connection_update.offset0 =
|
|
pdu_data_rx->payload.llctrl.ctrldata.conn_param_req.offset0;
|
|
_radio.conn_curr->llcp.connection_update.offset1 =
|
|
pdu_data_rx->payload.llctrl.ctrldata.conn_param_req.offset1;
|
|
_radio.conn_curr->llcp.connection_update.offset2 =
|
|
pdu_data_rx->payload.llctrl.ctrldata.conn_param_req.offset2;
|
|
_radio.conn_curr->llcp.connection_update.offset3 =
|
|
pdu_data_rx->payload.llctrl.ctrldata.conn_param_req.offset3;
|
|
_radio.conn_curr->llcp.connection_update.offset4 =
|
|
pdu_data_rx->payload.llctrl.ctrldata.conn_param_req.offset4;
|
|
_radio.conn_curr->llcp.connection_update.offset5 =
|
|
pdu_data_rx->payload.llctrl.ctrldata.conn_param_req.offset5;
|
|
|
|
/* enqueue the conn param req, if parameters changed,
|
|
* else respond
|
|
*/
|
|
if ((_radio.conn_curr->llcp.connection_update.interval !=
|
|
_radio.conn_curr->conn_interval) ||
|
|
(_radio.conn_curr->llcp.connection_update.latency
|
|
!= _radio.conn_curr->latency) ||
|
|
(_radio.conn_curr->llcp.connection_update.timeout !=
|
|
(_radio.conn_curr->conn_interval *
|
|
_radio.conn_curr->supervision_reload * 125 / 1000))) {
|
|
*rx_enqueue = 1;
|
|
|
|
_radio.conn_curr->llcp.connection_update.state =
|
|
LLCP_CONN_STATE_APP_WAIT;
|
|
_radio.conn_curr->llcp.connection_update.is_internal = 0;
|
|
_radio.conn_curr->llcp_type = LLCP_CONNECTION_UPDATE;
|
|
_radio.conn_curr->llcp_ack--;
|
|
} else {
|
|
_radio.conn_curr->llcp.connection_update.win_size = 1;
|
|
_radio.conn_curr->llcp.connection_update.win_offset_us = 0;
|
|
_radio.conn_curr->llcp.connection_update.state =
|
|
LLCP_CONN_STATE_RSP;
|
|
_radio.conn_curr->llcp.connection_update.is_internal = 0;
|
|
|
|
_radio.conn_curr->llcp_type = LLCP_CONNECTION_UPDATE;
|
|
_radio.conn_curr->llcp_ack--;
|
|
}
|
|
}
|
|
/* master in conn update procedure, master in any state we
|
|
* ignore this req
|
|
*/
|
|
else if ((_radio.conn_curr->llcp_req !=
|
|
_radio.conn_curr->llcp_ack) &&
|
|
(_radio.conn_curr->role.master.role == 0) &&
|
|
(_radio.conn_curr == _radio.conn_upd) &&
|
|
(_radio.conn_curr->llcp_type == LLCP_CONNECTION_UPDATE)) {
|
|
/* ignore this req, as master continue initiating or
|
|
* waiting for resp
|
|
*/
|
|
}
|
|
/* no ctrl procedure in this connection, but conn update mutex
|
|
* set (another connection update in progress), hence reject
|
|
* this req.
|
|
*/
|
|
else if (_radio.conn_curr->llcp_req ==
|
|
_radio.conn_curr->llcp_ack) {
|
|
reject_ind_ext_send(_radio.conn_curr,
|
|
PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ,
|
|
/* @todo use correct error_code */
|
|
0x20);
|
|
} else {
|
|
/* different transaction collision */
|
|
/* 1. conn update in progress, instant not reached */
|
|
/* 2. some other ctrl procedure in progress */
|
|
|
|
LL_ASSERT(0);
|
|
}
|
|
break;
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_CONN_PARAM_RSP:
|
|
/* @todo send conn_update req */
|
|
break;
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_REJECT_IND_EXT:
|
|
if (_radio.conn_curr->llcp_req != _radio.conn_curr->llcp_ack) {
|
|
isr_rx_conn_pkt_ctrl_rej(radio_pdu_node_rx, rx_enqueue);
|
|
|
|
} else {
|
|
/* By spec. slave shall not generate a conn update
|
|
* complete on reject from master.
|
|
*/
|
|
LL_ASSERT(_radio.conn_curr->role.slave.role);
|
|
}
|
|
break;
|
|
|
|
#if defined(CONFIG_BLUETOOTH_CONTROLLER_LE_PING)
|
|
case PDU_DATA_LLCTRL_TYPE_PING_REQ:
|
|
ping_resp_send(_radio.conn_curr);
|
|
break;
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_PING_RSP:
|
|
/* Procedure complete */
|
|
_radio.conn_curr->procedure_expire = 0;
|
|
break;
|
|
#endif /* CONFIG_BLUETOOTH_CONTROLLER_LE_PING */
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP:
|
|
if (_radio.conn_curr->llcp_req != _radio.conn_curr->llcp_ack) {
|
|
/* reset ctrl procedure */
|
|
_radio.conn_curr->llcp_ack = _radio.conn_curr->llcp_req;
|
|
|
|
switch (_radio.conn_curr->llcp_type) {
|
|
default:
|
|
LL_ASSERT(0);
|
|
break;
|
|
}
|
|
} else if (_radio.conn_curr->llcp_length.req !=
|
|
_radio.conn_curr->llcp_length.ack) {
|
|
/* Procedure complete */
|
|
_radio.conn_curr->procedure_expire = 0;
|
|
_radio.conn_curr->llcp_length.ack =
|
|
_radio.conn_curr->llcp_length.req;
|
|
|
|
/* resume data packet tx */
|
|
_radio.conn_curr->pause_tx = 0;
|
|
|
|
/* propagate the data length procedure to
|
|
* host
|
|
*/
|
|
*rx_enqueue = 1;
|
|
} else {
|
|
struct pdu_data_llctrl *llctrl;
|
|
|
|
llctrl = (struct pdu_data_llctrl *)
|
|
&pdu_data_rx->payload.llctrl;
|
|
switch (llctrl->ctrldata.unknown_rsp.type) {
|
|
|
|
#if defined(CONFIG_BLUETOOTH_CONTROLLER_LE_PING)
|
|
case PDU_DATA_LLCTRL_TYPE_PING_REQ:
|
|
/* unknown rsp to LE Ping Req completes the
|
|
* procedure; nothing to do here.
|
|
*/
|
|
break;
|
|
#endif /* CONFIG_BLUETOOTH_CONTROLLER_LE_PING */
|
|
|
|
default:
|
|
/* enqueue the error and let HCI handle it */
|
|
*rx_enqueue = 1;
|
|
break;
|
|
}
|
|
|
|
/* Procedure complete */
|
|
_radio.conn_curr->procedure_expire = 0;
|
|
}
|
|
break;
|
|
|
|
case PDU_DATA_LLCTRL_TYPE_LENGTH_RSP:
|
|
case PDU_DATA_LLCTRL_TYPE_LENGTH_REQ:
|
|
nack = isr_rx_conn_pkt_ctrl_dle(pdu_data_rx, rx_enqueue);
|
|
break;
|
|
|
|
default:
|
|
unknown_rsp_send(_radio.conn_curr,
|
|
pdu_data_rx->payload.llctrl.opcode);
|
|
break;
|
|
}
|
|
|
|
return nack;
|
|
}
|
|
|
|
static inline uint32_t
|
|
isr_rx_conn_pkt(struct radio_pdu_node_rx *radio_pdu_node_rx,
|
|
struct radio_pdu_node_tx **tx_release, uint8_t *rx_enqueue)
|
|
{
|
|
struct pdu_data *pdu_data_rx;
|
|
struct pdu_data *pdu_data_tx;
|
|
uint8_t terminate = 0;
|
|
uint8_t nack = 0;
|
|
|
|
/* Reset CRC expiry counter */
|
|
_radio.crc_expire = 0;
|
|
|
|
/* Ack for transmitted data */
|
|
pdu_data_rx = (struct pdu_data *)radio_pdu_node_rx->pdu_data;
|
|
if (pdu_data_rx->nesn != _radio.conn_curr->sn) {
|
|
|
|
/* Increment serial number */
|
|
_radio.conn_curr->sn++;
|
|
|
|
if (_radio.conn_curr->empty == 0) {
|
|
struct radio_pdu_node_tx *node_tx;
|
|
uint8_t pdu_data_tx_len, pdu_data_tx_ll_id;
|
|
|
|
node_tx = _radio.conn_curr->pkt_tx_head;
|
|
pdu_data_tx = (struct pdu_data *)
|
|
(node_tx->pdu_data +
|
|
_radio.conn_curr->packet_tx_head_offset);
|
|
|
|
pdu_data_tx_len = pdu_data_tx->len;
|
|
pdu_data_tx_ll_id = pdu_data_tx->ll_id;
|
|
|
|
if (pdu_data_tx_len != 0) {
|
|
/* if encrypted increment tx counter */
|
|
if (_radio.conn_curr->enc_tx) {
|
|
_radio.conn_curr->ccm_tx.counter++;
|
|
}
|
|
|
|
/* process ctrl packet on tx cmplt */
|
|
if (pdu_data_tx_ll_id == PDU_DATA_LLID_CTRL) {
|
|
terminate =
|
|
isr_rx_conn_pkt_ack(pdu_data_tx,
|
|
&node_tx);
|
|
}
|
|
}
|
|
|
|
_radio.conn_curr->packet_tx_head_offset += pdu_data_tx_len;
|
|
if (_radio.conn_curr->packet_tx_head_offset ==
|
|
_radio.conn_curr->packet_tx_head_len) {
|
|
*tx_release = isr_rx_conn_pkt_release(node_tx);
|
|
}
|
|
} else {
|
|
_radio.conn_curr->empty = 0;
|
|
}
|
|
}
|
|
|
|
/* local initiated disconnect procedure completed */
|
|
if (terminate) {
|
|
connection_release(_radio.conn_curr);
|
|
_radio.conn_curr = NULL;
|
|
|
|
return terminate;
|
|
}
|
|
|
|
/* process received data */
|
|
if ((pdu_data_rx->sn == _radio.conn_curr->nesn) &&
|
|
/* check so that we will NEVER use the rx buffer reserved for empty
|
|
* packet and internal control enqueue
|
|
*/
|
|
(packet_rx_reserve_get(3) != 0) &&
|
|
((_radio.fc_ena == 0) ||
|
|
((_radio.link_rx_head == _radio.link_rx_tail) &&
|
|
(_radio.fc_req == _radio.fc_ack)) ||
|
|
((_radio.link_rx_head != _radio.link_rx_tail) &&
|
|
(_radio.fc_req != _radio.fc_ack) &&
|
|
(((_radio.fc_req == 0) &&
|
|
(_radio.fc_handle[TRIPLE_BUFFER_SIZE - 1] ==
|
|
_radio.conn_curr->handle)) ||
|
|
((_radio.fc_req != 0) &&
|
|
(_radio.fc_handle[_radio.fc_req - 1] ==
|
|
_radio.conn_curr->handle)))))) {
|
|
uint8_t ccm_rx_increment = 0;
|
|
|
|
if (pdu_data_rx->len != 0) {
|
|
/* If required, wait for CCM to finish
|
|
*/
|
|
if (_radio.conn_curr->enc_rx) {
|
|
uint32_t done;
|
|
|
|
done = radio_ccm_is_done();
|
|
LL_ASSERT(done);
|
|
|
|
ccm_rx_increment = 1;
|
|
}
|
|
|
|
/* MIC Failure Check or data rx during pause */
|
|
if (((_radio.conn_curr->enc_rx) &&
|
|
!radio_ccm_mic_is_valid()) ||
|
|
((_radio.conn_curr->pause_rx) &&
|
|
(pdu_data_rx->ll_id != PDU_DATA_LLID_CTRL))) {
|
|
_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,
|
|
0x3d);
|
|
|
|
connection_release(_radio.conn_curr);
|
|
_radio.conn_curr = NULL;
|
|
|
|
return 1; /* terminated */
|
|
}
|
|
|
|
#if defined(CONFIG_BLUETOOTH_CONTROLLER_LE_PING)
|
|
/* stop authenticated payload (pre) timeout */
|
|
_radio.conn_curr->appto_expire = 0;
|
|
_radio.conn_curr->apto_expire = 0;
|
|
#endif /* CONFIG_BLUETOOTH_CONTROLLER_LE_PING */
|
|
|
|
switch (pdu_data_rx->ll_id) {
|
|
case PDU_DATA_LLID_DATA_CONTINUE:
|
|
case PDU_DATA_LLID_DATA_START:
|
|
/* enqueue data packet */
|
|
*rx_enqueue = 1;
|
|
break;
|
|
|
|
case PDU_DATA_LLID_CTRL:
|
|
nack = isr_rx_conn_pkt_ctrl(radio_pdu_node_rx,
|
|
rx_enqueue);
|
|
break;
|
|
case PDU_DATA_LLID_RESV:
|
|
default:
|
|
LL_ASSERT(0);
|
|
break;
|
|
}
|
|
|
|
#if defined(CONFIG_BLUETOOTH_CONTROLLER_LE_PING)
|
|
} else if ((_radio.conn_curr->enc_rx) ||
|
|
(_radio.conn_curr->pause_rx)) {
|
|
/* start authenticated payload (pre) timeout */
|
|
if (_radio.conn_curr->apto_expire == 0) {
|
|
_radio.conn_curr->appto_expire =
|
|
_radio.conn_curr->appto_reload;
|
|
_radio.conn_curr->apto_expire =
|
|
_radio.conn_curr->apto_reload;
|
|
}
|
|
#endif /* CONFIG_BLUETOOTH_CONTROLLER_LE_PING */
|
|
|
|
}
|
|
|
|
if (!nack) {
|
|
_radio.conn_curr->nesn++;
|
|
|
|
if (ccm_rx_increment) {
|
|
_radio.conn_curr->ccm_rx.counter++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void isr_rx_conn(uint8_t crc_ok, uint8_t trx_done,
|
|
uint8_t rssi_ready)
|
|
{
|
|
struct radio_pdu_node_rx *radio_pdu_node_rx;
|
|
struct radio_pdu_node_tx *tx_release = NULL;
|
|
uint8_t is_empty_pdu_tx_retry;
|
|
struct pdu_data *pdu_data_rx;
|
|
struct pdu_data *pdu_data_tx;
|
|
uint8_t rx_enqueue = 0;
|
|
uint8_t crc_close = 0;
|
|
|
|
#if defined(CONFIG_BLUETOOTH_CONTROLLER_PROFILE_ISR)
|
|
static uint8_t s_lmax;
|
|
static uint8_t s_lmin = (uint8_t) -1;
|
|
static uint8_t s_lprv;
|
|
static uint8_t s_max;
|
|
static uint8_t s_min = (uint8_t) -1;
|
|
static uint8_t s_prv;
|
|
uint32_t sample;
|
|
uint8_t latency, elapsed, prv;
|
|
uint8_t chg = 0;
|
|
#endif /* CONFIG_BLUETOOTH_CONTROLLER_PROFILE_ISR */
|
|
|
|
/* Collect RSSI for connection */
|
|
if (_radio.packet_counter == 0) {
|
|
if (rssi_ready) {
|
|
uint8_t rssi = radio_rssi_get();
|
|
|
|
_radio.conn_curr->rssi_latest = rssi;
|
|
|
|
if (((_radio.conn_curr->rssi_reported - rssi) & 0xFF) >
|
|
RADIO_RSSI_THRESHOLD) {
|
|
if (_radio.conn_curr->rssi_sample_count) {
|
|
_radio.conn_curr->rssi_sample_count--;
|
|
}
|
|
} else {
|
|
_radio.conn_curr->rssi_sample_count =
|
|
RADIO_RSSI_SAMPLE_COUNT;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Increment packet counter for this connection event */
|
|
_radio.packet_counter++;
|
|
|
|
/* received data packet */
|
|
radio_pdu_node_rx = _radio.packet_rx[_radio.packet_rx_last];
|
|
radio_pdu_node_rx->hdr.type = NODE_RX_TYPE_DC_PDU;
|
|
|
|
if (crc_ok) {
|
|
uint32_t terminate;
|
|
|
|
terminate = isr_rx_conn_pkt(radio_pdu_node_rx, &tx_release,
|
|
&rx_enqueue);
|
|
if (terminate) {
|
|
goto isr_rx_conn_exit;
|
|
}
|
|
} else {
|
|
/* Start CRC error countdown, if not already started */
|
|
if (_radio.crc_expire == 0) {
|
|
_radio.crc_expire = 2;
|
|
}
|
|
|
|
/* Check crc error countdown expiry */
|
|
_radio.crc_expire--;
|
|
crc_close = (_radio.crc_expire == 0);
|
|
}
|
|
|
|
/* prepare transmit packet */
|
|
is_empty_pdu_tx_retry = _radio.conn_curr->empty;
|
|
prepare_pdu_data_tx(_radio.conn_curr, &pdu_data_tx);
|
|
|
|
/* silent connection */
|
|
if (SILENT_CONNECTION) {
|
|
/* slave silent, enter/be in supervision timeout */
|
|
if (_radio.packet_counter == 0) {
|
|
_radio.packet_counter = 0xFF;
|
|
}
|
|
|
|
/* master silent, hence avoid slave drift compensation, and
|
|
* close slave if no tx packets
|
|
*/
|
|
if (!trx_done) {
|
|
/* avoid slave drift compensation if first packet
|
|
* missed
|
|
*/
|
|
if (_radio.packet_counter == 1) {
|
|
_radio.packet_counter = 0xFF;
|
|
}
|
|
|
|
/* no Rx-ed packet and none to Tx, close event */
|
|
if ((_radio.conn_curr->empty) &&
|
|
(pdu_data_tx->md == 0)) {
|
|
_radio.state = STATE_CLOSE;
|
|
radio_disable();
|
|
|
|
goto isr_rx_conn_exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Decide on event continuation and hence Radio Shorts to use */
|
|
pdu_data_rx = (struct pdu_data *)radio_pdu_node_rx->pdu_data;
|
|
_radio.state = ((_radio.state == STATE_CLOSE) || (crc_close) ||
|
|
((crc_ok) && (pdu_data_rx->md == 0) &&
|
|
(pdu_data_tx->len == 0)) ||
|
|
(_radio.conn_curr->llcp_terminate.reason_peer != 0)) ?
|
|
STATE_CLOSE : STATE_TX;
|
|
|
|
if (_radio.state == STATE_CLOSE) {
|
|
/* Event close for master */
|
|
if (_radio.role == ROLE_MASTER) {
|
|
_radio.conn_curr->empty = is_empty_pdu_tx_retry;
|
|
|
|
radio_disable();
|
|
|
|
goto isr_rx_conn_exit;
|
|
}
|
|
/* Event close for slave */
|
|
else {
|
|
radio_switch_complete_and_disable();
|
|
}
|
|
} else { /* if (_radio.state == STATE_TX) */
|
|
|
|
radio_switch_complete_and_rx();
|
|
radio_tmr_end_capture();
|
|
}
|
|
|
|
/* fill sn and nesn */
|
|
pdu_data_tx->sn = _radio.conn_curr->sn;
|
|
pdu_data_tx->nesn = _radio.conn_curr->nesn;
|
|
|
|
/* setup the radio tx packet buffer */
|
|
tx_packet_set(_radio.conn_curr, pdu_data_tx);
|
|
|
|
isr_rx_conn_exit:
|
|
|
|
#if defined(CONFIG_BLUETOOTH_CONTROLLER_PROFILE_ISR)
|
|
/* get the ISR latency sample */
|
|
sample = radio_tmr_sample_get();
|
|
|
|
/* sample the packet timer again, use it to calculate ISR execution time
|
|
* and use it in profiling event
|
|
*/
|
|
radio_tmr_sample();
|
|
|
|
#endif /* CONFIG_BLUETOOTH_CONTROLLER_PROFILE_ISR */
|
|
|
|
/* release tx node and generate event for num complete */
|
|
if (tx_release) {
|
|
pdu_node_tx_release(_radio.conn_curr->handle, tx_release);
|
|
}
|
|
|
|
/* enqueue any rx packet/event towards application */
|
|
if (rx_enqueue) {
|
|
/* set data flow control lock on currently rx-ed connection */
|
|
rx_fc_lock(_radio.conn_curr->handle);
|
|
|
|
/* set the connection handle and enqueue */
|
|
radio_pdu_node_rx->hdr.handle = _radio.conn_curr->handle;
|
|
packet_rx_enqueue();
|
|
}
|
|
|
|
#if defined(CONFIG_BLUETOOTH_CONTROLLER_PROFILE_ISR)
|
|
/* calculate the elapsed time in us since on-air radio packet end
|
|
* to ISR entry
|
|
*/
|
|
latency = sample - radio_tmr_end_get();
|
|
|
|
/* check changes in min, avg and max of latency */
|
|
if (latency > s_lmax) {
|
|
s_lmax = latency;
|
|
chg = 1;
|
|
}
|
|
if (latency < s_lmin) {
|
|
s_lmin = latency;
|
|
chg = 1;
|
|
}
|
|
|
|
/* check for +/- 1us change */
|
|
prv = ((uint16_t)s_lprv + latency) >> 1;
|
|
if (prv != s_lprv) {
|
|
s_lprv = latency;
|
|
chg = 1;
|
|
}
|
|
|
|
/* calculate the elapsed time in us since ISR entry */
|
|
elapsed = radio_tmr_sample_get() - sample;
|
|
|
|
/* check changes in min, avg and max */
|
|
if (elapsed > s_max) {
|
|
s_max = elapsed;
|
|
chg = 1;
|
|
}
|
|
|
|
if (elapsed < s_min) {
|
|
s_min = elapsed;
|
|
chg = 1;
|
|
}
|
|
|
|
/* check for +/- 1us change */
|
|
prv = ((uint16_t)s_prv + elapsed) >> 1;
|
|
if (prv != s_prv) {
|
|
s_prv = elapsed;
|
|
chg = 1;
|
|
}
|
|
|
|
/* generate event if any change */
|
|
if (chg) {
|
|
/* NOTE: enqueue only if rx buffer available, else ignore */
|
|
radio_pdu_node_rx = packet_rx_reserve_get(2);
|
|
if (radio_pdu_node_rx) {
|
|
radio_pdu_node_rx->hdr.handle = 0xFFFF;
|
|
radio_pdu_node_rx->hdr.type = NODE_RX_TYPE_PROFILE;
|
|
pdu_data_rx = (struct pdu_data *)
|
|
radio_pdu_node_rx->pdu_data;
|
|
pdu_data_rx->payload.profile.lcur = latency;
|
|
pdu_data_rx->payload.profile.lmin = s_lmin;
|
|
pdu_data_rx->payload.profile.lmax = s_lmax;
|
|
pdu_data_rx->payload.profile.cur = elapsed;
|
|
pdu_data_rx->payload.profile.min = s_min;
|
|
pdu_data_rx->payload.profile.max = s_max;
|
|
packet_rx_enqueue();
|
|
}
|
|
}
|
|
#endif /* CONFIG_BLUETOOTH_CONTROLLER_PROFILE_ISR */
|
|
|
|
return;
|
|
}
|
|
|
|
static inline void isr_radio_state_rx(uint8_t trx_done, uint8_t crc_ok,
|
|
uint8_t devmatch_ok, uint8_t irkmatch_ok,
|
|
uint8_t irkmatch_id, uint8_t rssi_ready)
|
|
{
|
|
uint32_t err;
|
|
|
|
if (!((trx_done) || ((SILENT_CONNECTION) &&
|
|
(_radio.role == ROLE_SLAVE)))) {
|
|
_radio.state = STATE_CLOSE;
|
|
radio_disable();
|
|
return;
|
|
}
|
|
|
|
switch (_radio.role) {
|
|
case ROLE_ADV:
|
|
if (crc_ok) {
|
|
err = isr_rx_adv(devmatch_ok, irkmatch_ok,
|
|
irkmatch_id, rssi_ready);
|
|
} else {
|
|
err = 1;
|
|
}
|
|
if (err) {
|
|
_radio.state = STATE_CLOSE;
|
|
radio_disable();
|
|
}
|
|
break;
|
|
|
|
case ROLE_OBS:
|
|
if ((crc_ok) &&
|
|
(((_radio.observer.filter_policy & 0x01) == 0) ||
|
|
(devmatch_ok) || (irkmatch_ok))) {
|
|
err = isr_rx_obs(irkmatch_id, rssi_ready);
|
|
} else {
|
|
err = 1;
|
|
}
|
|
if (err) {
|
|
_radio.state = STATE_CLOSE;
|
|
radio_disable();
|
|
/* switch scanner state to idle */
|
|
_radio.observer.scan_state = 0;
|
|
}
|
|
break;
|
|
|
|
case ROLE_SLAVE:
|
|
case ROLE_MASTER:
|
|
isr_rx_conn(crc_ok, trx_done, rssi_ready);
|
|
break;
|
|
|
|
case ROLE_NONE:
|
|
default:
|
|
LL_ASSERT(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static inline uint32_t isr_close_adv(void)
|
|
{
|
|
uint32_t dont_close = 0;
|
|
|
|
if ((_radio.state == STATE_CLOSE) &&
|
|
(_radio.advertiser.chl_map_current != 0)) {
|
|
dont_close = 1;
|
|
|
|
adv_setup();
|
|
|
|
_radio.state = STATE_TX;
|
|
|
|
radio_tx_enable();
|
|
|
|
radio_tmr_end_capture();
|
|
} else {
|
|
struct pdu_adv *pdu_adv;
|
|
|
|
radio_filter_disable();
|
|
|
|
pdu_adv =
|
|
(struct pdu_adv *)
|
|
&_radio.advertiser.adv_data.data[_radio.advertiser.adv_data.first][0];
|
|
if ((_radio.state == STATE_CLOSE) &&
|
|
(pdu_adv->type != PDU_ADV_TYPE_DIRECT_IND)) {
|
|
uint32_t ticker_status;
|
|
uint8_t random_delay;
|
|
|
|
/** @todo use random 0-10 */
|
|
random_delay = 10;
|
|
|
|
ticker_status =
|
|
ticker_update(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER,
|
|
RADIO_TICKER_ID_ADV,
|
|
TICKER_US_TO_TICKS(random_delay * 1000),
|
|
0, 0, 0, 0, 0,
|
|
ticker_success_assert,
|
|
(void *)__LINE__);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
}
|
|
}
|
|
|
|
return dont_close;
|
|
}
|
|
|
|
static inline uint32_t isr_close_obs(void)
|
|
{
|
|
uint32_t dont_close = 0;
|
|
|
|
if (_radio.state == STATE_CLOSE) {
|
|
dont_close = 1;
|
|
|
|
radio_pkt_rx_set(_radio.packet_rx[_radio.packet_rx_last]->pdu_data);
|
|
radio_switch_complete_and_tx();
|
|
radio_rssi_measure();
|
|
|
|
if (_radio.observer.filter_policy && _radio.nirk) {
|
|
radio_ar_configure(_radio.nirk, _radio.irk);
|
|
}
|
|
|
|
_radio.state = STATE_RX;
|
|
|
|
radio_rx_enable();
|
|
|
|
radio_tmr_end_capture();
|
|
} else {
|
|
uint32_t ticker_status;
|
|
|
|
radio_filter_disable();
|
|
|
|
if (_radio.state == STATE_ABORT) {
|
|
ticker_status =
|
|
ticker_stop(
|
|
RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER,
|
|
RADIO_TICKER_ID_OBS_STOP,
|
|
0 /** @todo ticker_success_assert */,
|
|
0 /** @todo (void *) __LINE__ */);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
}
|
|
}
|
|
|
|
return dont_close;
|
|
}
|
|
|
|
static inline void isr_close_conn(void)
|
|
{
|
|
uint16_t ticks_drift_plus;
|
|
uint16_t ticks_drift_minus;
|
|
uint16_t latency_event;
|
|
uint16_t elapsed_event;
|
|
uint16_t lazy;
|
|
uint8_t force;
|
|
|
|
/* Local initiated terminate happened */
|
|
if (_radio.conn_curr == 0) {
|
|
return;
|
|
}
|
|
|
|
/* Remote Initiated terminate happened in this event for Slave */
|
|
if ((_radio.role == ROLE_SLAVE) &&
|
|
(_radio.conn_curr->llcp_terminate.reason_peer != 0)) {
|
|
terminate_ind_rx_enqueue(_radio.conn_curr,
|
|
_radio.conn_curr->llcp_terminate.reason_peer);
|
|
|
|
connection_release(_radio.conn_curr);
|
|
_radio.conn_curr = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
ticks_drift_plus = 0;
|
|
ticks_drift_minus = 0;
|
|
latency_event = _radio.conn_curr->latency_event;
|
|
elapsed_event = latency_event + 1;
|
|
|
|
/* calculate drift if anchor point sync-ed */
|
|
if ((_radio.packet_counter != 0) && ((!SILENT_CONNECTION) ||
|
|
(_radio.packet_counter != 0xFF))) {
|
|
if (_radio.role == ROLE_SLAVE) {
|
|
uint32_t start_to_address_actual_us;
|
|
uint32_t start_to_address_expected_us;
|
|
uint32_t window_widening_event_us;
|
|
|
|
/* calculate the drift in ticks */
|
|
start_to_address_actual_us = radio_tmr_aa_get();
|
|
window_widening_event_us =
|
|
_radio.conn_curr->role.slave.window_widening_event_us;
|
|
start_to_address_expected_us =
|
|
(RADIO_TICKER_JITTER_US << 1) +
|
|
RADIO_PREAMBLE_TO_ADDRESS_US +
|
|
window_widening_event_us;
|
|
if (start_to_address_actual_us <=
|
|
start_to_address_expected_us) {
|
|
ticks_drift_plus =
|
|
TICKER_US_TO_TICKS(window_widening_event_us);
|
|
ticks_drift_minus =
|
|
TICKER_US_TO_TICKS((uint64_t)(start_to_address_expected_us -
|
|
start_to_address_actual_us));
|
|
} else {
|
|
ticks_drift_plus =
|
|
TICKER_US_TO_TICKS(start_to_address_actual_us);
|
|
ticks_drift_minus =
|
|
TICKER_US_TO_TICKS((RADIO_TICKER_JITTER_US << 1) +
|
|
RADIO_PREAMBLE_TO_ADDRESS_US);
|
|
}
|
|
|
|
|
|
/* Reset window widening, as anchor point sync-ed */
|
|
_radio.conn_curr->role.slave.window_widening_event_us = 0;
|
|
_radio.conn_curr->role.slave.window_size_event_us = 0;
|
|
|
|
/* apply latency if no more data */
|
|
_radio.conn_curr->latency_event = _radio.conn_curr->latency;
|
|
if (_radio.conn_curr->pkt_tx_head) {
|
|
struct pdu_data *pdu_data_tx;
|
|
|
|
pdu_data_tx = (struct pdu_data *)
|
|
_radio.conn_curr->pkt_tx_head->pdu_data;
|
|
if (pdu_data_tx->len ||
|
|
_radio.conn_curr->packet_tx_head_offset) {
|
|
_radio.conn_curr->latency_event = 0;
|
|
}
|
|
}
|
|
} else {
|
|
/* Reset connection failed to establish procedure */
|
|
_radio.conn_curr->role.master.connect_expire = 0;
|
|
}
|
|
|
|
/* Reset supervision counter */
|
|
_radio.conn_curr->supervision_expire = 0;
|
|
}
|
|
|
|
/* Remote Initiated terminate happened in previous event for Master */
|
|
else if ((_radio.role == ROLE_MASTER) &&
|
|
(_radio.conn_curr->llcp_terminate.reason_peer != 0)) {
|
|
terminate_ind_rx_enqueue(_radio.conn_curr,
|
|
_radio.conn_curr->llcp_terminate.reason_peer);
|
|
|
|
connection_release(_radio.conn_curr);
|
|
_radio.conn_curr = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
/* If master, check connection failed to establish */
|
|
else if ((_radio.role == ROLE_MASTER) &&
|
|
(_radio.conn_curr->role.master.connect_expire != 0)) {
|
|
if (_radio.conn_curr->role.master.connect_expire >
|
|
elapsed_event) {
|
|
_radio.conn_curr->role.master.connect_expire -= elapsed_event;
|
|
} else {
|
|
terminate_ind_rx_enqueue(_radio.conn_curr, 0x3e);
|
|
|
|
connection_release(_radio.conn_curr);
|
|
_radio.conn_curr = NULL;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* if anchor point not sync-ed, start supervision timeout, and break
|
|
* latency if any.
|
|
*/
|
|
else {
|
|
/* Start supervision timeout, if not started already */
|
|
if (_radio.conn_curr->supervision_expire == 0) {
|
|
_radio.conn_curr->supervision_expire =
|
|
_radio.conn_curr->supervision_reload;
|
|
}
|
|
}
|
|
|
|
/* check supervision timeout */
|
|
force = 0;
|
|
if (_radio.conn_curr->supervision_expire != 0) {
|
|
if (_radio.conn_curr->supervision_expire > elapsed_event) {
|
|
_radio.conn_curr->supervision_expire -= elapsed_event;
|
|
|
|
/* break latency */
|
|
_radio.conn_curr->latency_event = 0;
|
|
|
|
/* Force both master and slave when close to
|
|
* supervision timeout.
|
|
*/
|
|
if (_radio.conn_curr->supervision_expire <= 6) {
|
|
force = 1;
|
|
}
|
|
/* use randomness to force slave role when anchor
|
|
* points are being missed.
|
|
*/
|
|
else if (_radio.role == ROLE_SLAVE) {
|
|
if (latency_event != 0) {
|
|
force = 1;
|
|
} else {
|
|
force = _radio.conn_curr->role.slave.force & 0x01;
|
|
|
|
/* rotate force bits */
|
|
_radio.conn_curr->role.slave.force >>= 1;
|
|
if (force) {
|
|
_radio.conn_curr->role.slave.force |=
|
|
((uint32_t)1 << 31);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
terminate_ind_rx_enqueue(_radio.conn_curr, 0x08);
|
|
|
|
connection_release(_radio.conn_curr);
|
|
_radio.conn_curr = NULL;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* check procedure timeout */
|
|
if (_radio.conn_curr->procedure_expire != 0) {
|
|
if (_radio.conn_curr->procedure_expire > elapsed_event) {
|
|
_radio.conn_curr->procedure_expire -= elapsed_event;
|
|
} else {
|
|
terminate_ind_rx_enqueue(_radio.conn_curr, 0x22);
|
|
|
|
connection_release(_radio.conn_curr);
|
|
_radio.conn_curr = NULL;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_BLUETOOTH_CONTROLLER_LE_PING)
|
|
/* check apto */
|
|
if (_radio.conn_curr->apto_expire != 0) {
|
|
if (_radio.conn_curr->apto_expire > elapsed_event) {
|
|
_radio.conn_curr->apto_expire -= elapsed_event;
|
|
} else {
|
|
struct radio_pdu_node_rx *radio_pdu_node_rx;
|
|
|
|
_radio.conn_curr->apto_expire = 0;
|
|
|
|
/* Prepare the rx packet structure */
|
|
radio_pdu_node_rx = packet_rx_reserve_get(2);
|
|
LL_ASSERT(radio_pdu_node_rx);
|
|
|
|
radio_pdu_node_rx->hdr.handle = _radio.conn_curr->handle;
|
|
radio_pdu_node_rx->hdr.type = NODE_RX_TYPE_APTO;
|
|
|
|
/* enqueue apto event into rx queue */
|
|
packet_rx_enqueue();
|
|
}
|
|
}
|
|
|
|
/* check appto */
|
|
if (_radio.conn_curr->appto_expire != 0) {
|
|
if (_radio.conn_curr->appto_expire > elapsed_event) {
|
|
_radio.conn_curr->appto_expire -= elapsed_event;
|
|
} else {
|
|
_radio.conn_curr->appto_expire = 0;
|
|
|
|
if ((_radio.conn_curr->procedure_expire == 0) &&
|
|
(_radio.conn_curr->llcp_req ==
|
|
_radio.conn_curr->llcp_ack)) {
|
|
_radio.conn_curr->llcp_type = LLCP_PING;
|
|
_radio.conn_curr->llcp_ack--;
|
|
}
|
|
}
|
|
}
|
|
#endif /* CONFIG_BLUETOOTH_CONTROLLER_LE_PING */
|
|
|
|
/* generate RSSI event */
|
|
if (_radio.conn_curr->rssi_sample_count == 0) {
|
|
struct radio_pdu_node_rx *radio_pdu_node_rx;
|
|
struct pdu_data *pdu_data_rx;
|
|
|
|
radio_pdu_node_rx = packet_rx_reserve_get(2);
|
|
if (radio_pdu_node_rx) {
|
|
_radio.conn_curr->rssi_reported =
|
|
_radio.conn_curr->rssi_latest;
|
|
_radio.conn_curr->rssi_sample_count =
|
|
RADIO_RSSI_SAMPLE_COUNT;
|
|
|
|
/* Prepare the rx packet structure */
|
|
radio_pdu_node_rx->hdr.handle =
|
|
_radio.conn_curr->handle;
|
|
radio_pdu_node_rx->hdr.type = NODE_RX_TYPE_RSSI;
|
|
|
|
/* prepare connection RSSI structure */
|
|
pdu_data_rx = (struct pdu_data *)
|
|
radio_pdu_node_rx->pdu_data;
|
|
pdu_data_rx->payload.rssi =
|
|
_radio.conn_curr->rssi_reported;
|
|
|
|
/* enqueue connection RSSI structure into queue */
|
|
packet_rx_enqueue();
|
|
}
|
|
}
|
|
|
|
/* break latency based on ctrl procedure pending */
|
|
if ((_radio.conn_curr->llcp_ack != _radio.conn_curr->llcp_req) &&
|
|
((_radio.conn_curr->llcp_type == LLCP_CONNECTION_UPDATE) ||
|
|
(_radio.conn_curr->llcp_type == LLCP_CHANNEL_MAP))) {
|
|
_radio.conn_curr->latency_event = 0;
|
|
}
|
|
|
|
/* check if latency needs update */
|
|
lazy = 0;
|
|
if ((force) || (latency_event != _radio.conn_curr->latency_event)) {
|
|
lazy = _radio.conn_curr->latency_event + 1;
|
|
}
|
|
|
|
if ((ticks_drift_plus != 0) || (ticks_drift_minus != 0) ||
|
|
(lazy != 0) || (force != 0)) {
|
|
uint32_t ticker_status;
|
|
|
|
ticker_status =
|
|
ticker_update(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER,
|
|
RADIO_TICKER_ID_FIRST_CONNECTION +
|
|
_radio.conn_curr->handle,
|
|
ticks_drift_plus, ticks_drift_minus, 0, 0,
|
|
lazy, force,
|
|
0 /** @todo ticker_success_assert */,
|
|
0 /** @todo (void *) __LINE__ */);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
}
|
|
}
|
|
|
|
static inline void isr_radio_state_close(void)
|
|
{
|
|
uint32_t dont_close = 0;
|
|
|
|
switch (_radio.role) {
|
|
case ROLE_ADV:
|
|
dont_close = isr_close_adv();
|
|
break;
|
|
|
|
case ROLE_OBS:
|
|
dont_close = isr_close_obs();
|
|
break;
|
|
|
|
case ROLE_SLAVE:
|
|
case ROLE_MASTER:
|
|
isr_close_conn();
|
|
break;
|
|
|
|
case ROLE_NONE:
|
|
default:
|
|
LL_ASSERT(0);
|
|
break;
|
|
}
|
|
|
|
if (dont_close) {
|
|
return;
|
|
}
|
|
|
|
_radio.role = ROLE_NONE;
|
|
_radio.state = STATE_NONE;
|
|
_radio.ticker_id_event = 0;
|
|
|
|
radio_tmr_stop();
|
|
|
|
event_inactive(0, 0, 0, 0);
|
|
|
|
clock_control_off(_radio.hf_clock, NULL);
|
|
|
|
work_enable(WORK_TICKER_JOB0_IRQ);
|
|
|
|
DEBUG_RADIO_CLOSE(0);
|
|
}
|
|
|
|
static void isr(void)
|
|
{
|
|
uint8_t trx_done;
|
|
uint8_t crc_ok;
|
|
uint8_t devmatch_ok;
|
|
uint8_t irkmatch_ok;
|
|
uint8_t irkmatch_id;
|
|
uint8_t rssi_ready;
|
|
|
|
DEBUG_RADIO_ISR(1);
|
|
|
|
/* Read radio status and events */
|
|
trx_done = radio_is_done();
|
|
if (trx_done) {
|
|
|
|
#if defined(CONFIG_BLUETOOTH_CONTROLLER_PROFILE_ISR)
|
|
/* sample the packet timer here, use it to calculate ISR latency
|
|
* and generate the profiling event at the end of the ISR.
|
|
*/
|
|
radio_tmr_sample();
|
|
#endif /* CONFIG_BLUETOOTH_CONTROLLER_PROFILE_ISR */
|
|
|
|
crc_ok = radio_crc_is_valid();
|
|
devmatch_ok = radio_filter_has_match();
|
|
irkmatch_ok = radio_ar_has_match();
|
|
irkmatch_id = radio_ar_match_get();
|
|
rssi_ready = radio_rssi_is_ready();
|
|
} else {
|
|
crc_ok = devmatch_ok = irkmatch_ok = rssi_ready = 0;
|
|
irkmatch_id = 0xFF;
|
|
}
|
|
|
|
/* Clear radio status and events */
|
|
radio_status_reset();
|
|
radio_tmr_status_reset();
|
|
radio_filter_status_reset();
|
|
radio_ar_status_reset();
|
|
radio_rssi_status_reset();
|
|
|
|
switch (_radio.state) {
|
|
case STATE_TX:
|
|
isr_radio_state_tx();
|
|
break;
|
|
|
|
case STATE_RX:
|
|
isr_radio_state_rx(trx_done, crc_ok, devmatch_ok, irkmatch_ok,
|
|
irkmatch_id, rssi_ready);
|
|
break;
|
|
|
|
case STATE_ABORT:
|
|
case STATE_STOP:
|
|
case STATE_CLOSE:
|
|
isr_radio_state_close();
|
|
break;
|
|
|
|
case STATE_NONE:
|
|
/* Ignore Duplicate Radio Disabled IRQ due to forced stop
|
|
* using Radio Disable task.
|
|
*/
|
|
break;
|
|
|
|
default:
|
|
LL_ASSERT(0);
|
|
break;
|
|
}
|
|
|
|
DEBUG_RADIO_ISR(0);
|
|
}
|
|
|
|
#if (WORK_TICKER_WORKER0_IRQ_PRIORITY == WORK_TICKER_JOB0_IRQ_PRIORITY)
|
|
static void ticker_job_disable(uint32_t status, void *op_context)
|
|
{
|
|
ARG_UNUSED(status);
|
|
ARG_UNUSED(op_context);
|
|
|
|
if (_radio.state != STATE_NONE) {
|
|
work_disable(WORK_TICKER_JOB0_IRQ);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void ticker_if_done(uint32_t status, void *ops_context)
|
|
{
|
|
*((uint32_t volatile *)ops_context) = status;
|
|
}
|
|
|
|
static void ticker_success_assert(uint32_t status, void *params)
|
|
{
|
|
ARG_UNUSED(params);
|
|
|
|
LL_ASSERT(status == TICKER_STATUS_SUCCESS);
|
|
}
|
|
|
|
static void work_radio_active(void *params)
|
|
{
|
|
static uint8_t s_active;
|
|
|
|
if ((uint32_t)params) {
|
|
if (s_active++) {
|
|
return;
|
|
}
|
|
|
|
DEBUG_RADIO_ACTIVE(1);
|
|
|
|
radio_active_callback(1);
|
|
} else {
|
|
LL_ASSERT(s_active);
|
|
|
|
if (--s_active) {
|
|
return;
|
|
}
|
|
|
|
DEBUG_RADIO_ACTIVE(0);
|
|
|
|
radio_active_callback(0);
|
|
}
|
|
}
|
|
|
|
static void event_active(uint32_t ticks_at_expire, uint32_t remainder,
|
|
uint16_t lazy, void *context)
|
|
{
|
|
static struct work s_work_radio_active = { 0, 0, 0,
|
|
WORK_TICKER_WORKER0_IRQ, (work_fp) work_radio_active,
|
|
(void *)1};
|
|
uint32_t retval;
|
|
|
|
ARG_UNUSED(ticks_at_expire);
|
|
ARG_UNUSED(remainder);
|
|
ARG_UNUSED(lazy);
|
|
ARG_UNUSED(context);
|
|
|
|
retval = work_schedule(&s_work_radio_active, 0);
|
|
LL_ASSERT(!retval);
|
|
}
|
|
|
|
static void work_radio_inactive(void *params)
|
|
{
|
|
ARG_UNUSED(params);
|
|
|
|
work_radio_active(0);
|
|
|
|
DEBUG_RADIO_CLOSE(0);
|
|
}
|
|
|
|
static void event_inactive(uint32_t ticks_at_expire, uint32_t remainder,
|
|
uint16_t lazy, void *context)
|
|
{
|
|
static struct work s_work_radio_inactive = { 0, 0, 0,
|
|
WORK_TICKER_WORKER0_IRQ, (work_fp) work_radio_inactive, 0};
|
|
uint32_t retval;
|
|
|
|
ARG_UNUSED(ticks_at_expire);
|
|
ARG_UNUSED(remainder);
|
|
ARG_UNUSED(lazy);
|
|
ARG_UNUSED(context);
|
|
|
|
retval = work_schedule(&s_work_radio_inactive, 0);
|
|
LL_ASSERT(!retval);
|
|
}
|
|
|
|
static void work_xtal_start(void *params)
|
|
{
|
|
ARG_UNUSED(params);
|
|
|
|
/* turn on 16MHz clock, non-blocking mode. */
|
|
clock_control_on(_radio.hf_clock, NULL);
|
|
}
|
|
|
|
static void event_xtal(uint32_t ticks_at_expire, uint32_t remainder,
|
|
uint16_t lazy, void *context)
|
|
{
|
|
static struct work s_work_xtal_start = { 0, 0, 0,
|
|
WORK_TICKER_WORKER0_IRQ, (work_fp) work_xtal_start, 0 };
|
|
uint32_t retval;
|
|
|
|
ARG_UNUSED(ticks_at_expire);
|
|
ARG_UNUSED(remainder);
|
|
ARG_UNUSED(lazy);
|
|
ARG_UNUSED(context);
|
|
|
|
retval = work_schedule(&s_work_xtal_start, 0);
|
|
LL_ASSERT(!retval);
|
|
}
|
|
|
|
static void work_xtal_stop(void *params)
|
|
{
|
|
ARG_UNUSED(params);
|
|
|
|
clock_control_off(_radio.hf_clock, NULL);
|
|
|
|
DEBUG_RADIO_CLOSE(0);
|
|
}
|
|
|
|
#if XTAL_ADVANCED
|
|
static void work_xtal_retain(uint8_t retain)
|
|
{
|
|
static uint8_t s_xtal_retained;
|
|
|
|
if (retain) {
|
|
if (!s_xtal_retained) {
|
|
static struct work s_work_xtal_start = { 0, 0, 0,
|
|
WORK_TICKER_WORKER0_IRQ,
|
|
(work_fp) work_xtal_start, 0 };
|
|
uint32_t retval;
|
|
|
|
s_xtal_retained = 1;
|
|
|
|
retval = work_schedule(&s_work_xtal_start, 0);
|
|
LL_ASSERT(!retval);
|
|
}
|
|
} else {
|
|
if (s_xtal_retained) {
|
|
static struct work s_work_xtal_stop = { 0, 0, 0,
|
|
WORK_TICKER_WORKER0_IRQ,
|
|
(work_fp) work_xtal_stop, 0 };
|
|
uint32_t retval;
|
|
|
|
s_xtal_retained = 0;
|
|
|
|
retval = work_schedule(&s_work_xtal_stop, 0);
|
|
LL_ASSERT(!retval);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void prepare_reduced(uint32_t status, void *op_context)
|
|
{
|
|
/* It is acceptable that ticker_update will fail, if ticker is stopped;
|
|
* for example, obs ticker is stopped on connection estblishment but
|
|
* is also preempted.
|
|
*/
|
|
if (status == 0) {
|
|
struct shdr *hdr = (struct shdr *)op_context;
|
|
|
|
hdr->ticks_xtal_to_start |= ((uint32_t)1 << 31);
|
|
}
|
|
}
|
|
|
|
static void prepare_normal(uint32_t status, void *op_context)
|
|
{
|
|
/* It is acceptable that ticker_update will fail, if ticker is stopped;
|
|
* for example, obs ticker is stopped on connection estblishment but
|
|
* is also preempted.
|
|
*/
|
|
if (status == 0) {
|
|
struct shdr *hdr = (struct shdr *)op_context;
|
|
|
|
hdr->ticks_xtal_to_start &= ~((uint32_t)1 << 31);
|
|
}
|
|
}
|
|
|
|
static void prepare_normal_set(struct shdr *hdr,
|
|
uint8_t ticker_user_id,
|
|
uint8_t ticker_id)
|
|
{
|
|
if (hdr->ticks_xtal_to_start & ((uint32_t)1 << 31)) {
|
|
uint32_t ticker_status;
|
|
uint32_t ticks_prepare_to_start =
|
|
(hdr->ticks_active_to_start >
|
|
hdr->ticks_preempt_to_start) ? hdr->
|
|
ticks_active_to_start : hdr->ticks_preempt_to_start;
|
|
uint32_t ticks_drift_minus =
|
|
(hdr->ticks_xtal_to_start & (~((uint32_t)1 << 31))) -
|
|
ticks_prepare_to_start;
|
|
|
|
ticker_status =
|
|
ticker_update(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
ticker_user_id,
|
|
ticker_id, 0, ticks_drift_minus,
|
|
ticks_drift_minus, 0, 0, 0,
|
|
prepare_normal, hdr);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
}
|
|
}
|
|
|
|
#if (RADIO_TICKER_PREEMPT_PART_US <= RADIO_TICKER_PREEMPT_PART_MIN_US)
|
|
static uint32_t preempt_calc(struct shdr *hdr, uint8_t ticker_id,
|
|
uint32_t ticks_at_expire)
|
|
{
|
|
uint32_t diff =
|
|
ticker_ticks_diff_get(ticker_ticks_now_get(), ticks_at_expire);
|
|
|
|
diff += 3;
|
|
if (diff > TICKER_US_TO_TICKS(RADIO_TICKER_START_PART_US)) {
|
|
work_xtal_retain(0);
|
|
|
|
prepare_normal_set(hdr, RADIO_TICKER_USER_ID_WORKER, ticker_id);
|
|
|
|
diff += hdr->ticks_preempt_to_start;
|
|
if (diff <
|
|
TICKER_US_TO_TICKS(RADIO_TICKER_PREEMPT_PART_MAX_US)) {
|
|
hdr->ticks_preempt_to_start = diff;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/** @brief This function decides to start (additional call) xtal ahead of next
|
|
* ticker, if next ticker is close to current ticker expire.
|
|
*
|
|
* @note This function also detects if two tickers of same interval are drifting
|
|
* close and issues a conn param req or does a conn update.
|
|
*
|
|
* @todo Detect drift for overlapping tickers.
|
|
*/
|
|
static void work_xtal_stop_calc(void *params)
|
|
{
|
|
uint32_t volatile ticker_status;
|
|
uint8_t ticker_id;
|
|
uint32_t ticks_current;
|
|
uint32_t ticks_to_expire;
|
|
|
|
ticker_id = 0xff;
|
|
ticks_to_expire = 0;
|
|
ticker_status =
|
|
ticker_next_slot_get(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_JOB, &ticker_id,
|
|
&ticks_current, &ticks_to_expire,
|
|
ticker_if_done, (void *)&ticker_status);
|
|
|
|
while (ticker_status == TICKER_STATUS_BUSY) {
|
|
ticker_job_sched(RADIO_TICKER_INSTANCE_ID_RADIO);
|
|
}
|
|
|
|
LL_ASSERT(ticker_status == TICKER_STATUS_SUCCESS);
|
|
|
|
if ((ticker_id != 0xff) &&
|
|
(ticks_to_expire < TICKER_US_TO_TICKS(10000))) {
|
|
work_xtal_retain(1);
|
|
|
|
if (ticker_id >= RADIO_TICKER_ID_ADV) {
|
|
#if SCHED_ADVANCED
|
|
uint8_t ticker_id_current = ((uint32_t)params & 0xff);
|
|
struct connection *conn_curr = NULL;
|
|
#endif
|
|
uint32_t ticks_prepare_to_start;
|
|
struct connection *conn = NULL;
|
|
struct shdr *hdr = NULL;
|
|
|
|
/* Select the role's scheduling header */
|
|
if (ticker_id >= RADIO_TICKER_ID_FIRST_CONNECTION) {
|
|
conn = mem_get(_radio.conn_pool,
|
|
CONNECTION_T_SIZE,
|
|
(ticker_id -
|
|
RADIO_TICKER_ID_FIRST_CONNECTION));
|
|
hdr = &conn->hdr;
|
|
} else if (ticker_id == RADIO_TICKER_ID_ADV) {
|
|
hdr = &_radio.advertiser.hdr;
|
|
} else if (ticker_id == RADIO_TICKER_ID_OBS) {
|
|
hdr = &_radio.observer.hdr;
|
|
} else {
|
|
LL_ASSERT(0);
|
|
}
|
|
|
|
/* compensate for reduced next ticker's prepare or
|
|
* reduce next ticker's prepare.
|
|
*/
|
|
ticks_prepare_to_start =
|
|
(hdr->ticks_active_to_start >
|
|
hdr->ticks_preempt_to_start) ?
|
|
hdr->ticks_active_to_start :
|
|
hdr->ticks_preempt_to_start;
|
|
if ((hdr->ticks_xtal_to_start & ((uint32_t)1 << 31)) != 0) {
|
|
ticks_to_expire -= ((hdr->ticks_xtal_to_start &
|
|
(~((uint32_t)1 << 31))) -
|
|
ticks_prepare_to_start);
|
|
} else {
|
|
/* Postpone the primary because we dont have
|
|
* to start xtal.
|
|
*/
|
|
if (hdr->ticks_xtal_to_start >
|
|
ticks_prepare_to_start) {
|
|
uint32_t ticks_drift_plus =
|
|
hdr->ticks_xtal_to_start -
|
|
ticks_prepare_to_start;
|
|
|
|
ticker_status =
|
|
ticker_update(
|
|
RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_JOB,
|
|
ticker_id,
|
|
ticks_drift_plus, 0,
|
|
0, ticks_drift_plus,
|
|
0, 0,
|
|
prepare_reduced,
|
|
hdr);
|
|
LL_ASSERT((TICKER_STATUS_SUCCESS ==
|
|
ticker_status) ||
|
|
(TICKER_STATUS_BUSY ==
|
|
ticker_status));
|
|
}
|
|
}
|
|
|
|
#if SCHED_ADVANCED
|
|
if (ticker_id_current >= RADIO_TICKER_ID_FIRST_CONNECTION) {
|
|
/* compensate the current ticker for reduced
|
|
* prepare.
|
|
*/
|
|
conn_curr =
|
|
mem_get(_radio.conn_pool,
|
|
CONNECTION_T_SIZE,
|
|
(ticker_id_current -
|
|
RADIO_TICKER_ID_FIRST_CONNECTION));
|
|
ticks_prepare_to_start =
|
|
(conn_curr->hdr.ticks_active_to_start >
|
|
conn_curr->hdr.ticks_preempt_to_start) ?
|
|
conn_curr->hdr.ticks_active_to_start :
|
|
conn_curr->hdr.ticks_preempt_to_start;
|
|
if ((conn_curr->hdr.ticks_xtal_to_start &
|
|
((uint32_t)1 << 31)) != 0) {
|
|
ticks_to_expire +=
|
|
((conn_curr->hdr.ticks_xtal_to_start &
|
|
(~((uint32_t)1 << 31))) -
|
|
ticks_prepare_to_start);
|
|
}
|
|
}
|
|
|
|
/* auto conn param req or conn update procedure to
|
|
* avoid connection collisions.
|
|
*/
|
|
if ((conn) && (conn_curr) &&
|
|
(conn_curr->conn_interval == conn->conn_interval)) {
|
|
uint32_t ticks_conn_interval =
|
|
TICKER_US_TO_TICKS(conn->conn_interval * 1250);
|
|
|
|
/* remove laziness, if any, from
|
|
* ticks_to_expire.
|
|
*/
|
|
while (ticks_to_expire > ticks_conn_interval) {
|
|
ticks_to_expire -= ticks_conn_interval;
|
|
}
|
|
|
|
/* if next ticker close to this ticker, send
|
|
* conn param req.
|
|
*/
|
|
if ((conn_curr->role.slave.role != 0) &&
|
|
(conn->role.master.role == 0) &&
|
|
(ticks_to_expire <
|
|
(TICKER_US_TO_TICKS(RADIO_TICKER_XTAL_OFFSET_US +
|
|
625)
|
|
+ conn_curr->hdr.ticks_slot))) {
|
|
uint32_t status;
|
|
|
|
status = conn_update_req(conn_curr);
|
|
if ((status == 2) &&
|
|
(conn->llcp_version.rx)) {
|
|
conn_update_req(conn);
|
|
}
|
|
} else if ((conn_curr->role.master.role == 0) &&
|
|
(conn->role.slave.role != 0) &&
|
|
(ticks_to_expire <
|
|
(TICKER_US_TO_TICKS(RADIO_TICKER_XTAL_OFFSET_US +
|
|
625) +
|
|
conn_curr->hdr.ticks_slot))) {
|
|
uint32_t status;
|
|
|
|
status = conn_update_req(conn);
|
|
if ((status == 2) &&
|
|
(conn_curr->llcp_version.rx)) {
|
|
conn_update_req(conn_curr);
|
|
}
|
|
}
|
|
}
|
|
#endif /* SCHED_ADVANCED */
|
|
}
|
|
} else {
|
|
work_xtal_retain(0);
|
|
|
|
if ((ticker_id != 0xff) && (ticker_id >= RADIO_TICKER_ID_ADV)) {
|
|
struct shdr *hdr = NULL;
|
|
|
|
/* Select the role's scheduling header */
|
|
if (ticker_id >= RADIO_TICKER_ID_FIRST_CONNECTION) {
|
|
struct connection *conn;
|
|
|
|
conn = mem_get(_radio.conn_pool,
|
|
CONNECTION_T_SIZE,
|
|
(ticker_id -
|
|
RADIO_TICKER_ID_FIRST_CONNECTION));
|
|
hdr = &conn->hdr;
|
|
} else if (ticker_id == RADIO_TICKER_ID_ADV) {
|
|
hdr = &_radio.advertiser.hdr;
|
|
} else if (ticker_id == RADIO_TICKER_ID_OBS) {
|
|
hdr = &_radio.observer.hdr;
|
|
} else {
|
|
LL_ASSERT(0);
|
|
}
|
|
|
|
/* Use normal prepare */
|
|
prepare_normal_set(hdr, RADIO_TICKER_USER_ID_JOB,
|
|
ticker_id);
|
|
}
|
|
}
|
|
}
|
|
#endif /* XTAL_ADVANCED */
|
|
|
|
#if SCHED_ADVANCED
|
|
static void sched_after_master_free_slot_get(uint8_t user_id,
|
|
uint32_t ticks_slot_abs,
|
|
uint32_t *ticks_anchor,
|
|
uint32_t *us_offset)
|
|
{
|
|
uint8_t ticker_id;
|
|
uint8_t ticker_id_prev;
|
|
uint32_t ticks_to_expire;
|
|
uint32_t ticks_to_expire_prev;
|
|
uint32_t ticks_slot_prev_abs;
|
|
|
|
ticker_id = ticker_id_prev = 0xff;
|
|
ticks_to_expire = ticks_to_expire_prev = *us_offset = 0;
|
|
ticks_slot_prev_abs = 0;
|
|
while (1) {
|
|
uint32_t volatile ticker_status;
|
|
struct connection *conn;
|
|
|
|
ticker_status =
|
|
ticker_next_slot_get(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
user_id, &ticker_id, ticks_anchor,
|
|
&ticks_to_expire, ticker_if_done,
|
|
(void *)&ticker_status);
|
|
|
|
while (ticker_status == TICKER_STATUS_BUSY) {
|
|
ticker_job_sched(RADIO_TICKER_INSTANCE_ID_RADIO);
|
|
}
|
|
|
|
LL_ASSERT(ticker_status == TICKER_STATUS_SUCCESS);
|
|
|
|
if (ticker_id == 0xff) {
|
|
break;
|
|
}
|
|
|
|
if (ticker_id < RADIO_TICKER_ID_FIRST_CONNECTION) {
|
|
continue;
|
|
}
|
|
|
|
conn = mem_get(_radio.conn_pool, CONNECTION_T_SIZE,
|
|
(ticker_id - RADIO_TICKER_ID_FIRST_CONNECTION));
|
|
if ((conn) && (conn->role.master.role == 0)) {
|
|
uint32_t ticks_to_expire_normal = ticks_to_expire;
|
|
|
|
if (conn->hdr.ticks_xtal_to_start & ((uint32_t)1 << 31)) {
|
|
uint32_t ticks_prepare_to_start =
|
|
(conn->hdr.ticks_active_to_start >
|
|
conn->hdr.ticks_preempt_to_start) ?
|
|
conn->hdr.ticks_active_to_start :
|
|
conn->hdr.ticks_preempt_to_start;
|
|
|
|
ticks_to_expire_normal -=
|
|
((conn->hdr.ticks_xtal_to_start &
|
|
(~((uint32_t)1 << 31))) -
|
|
ticks_prepare_to_start);
|
|
}
|
|
|
|
if ((ticker_id_prev != 0xFF) &&
|
|
(ticker_ticks_diff_get(ticks_to_expire_normal,
|
|
ticks_to_expire_prev) >
|
|
(ticks_slot_prev_abs + ticks_slot_abs +
|
|
TICKER_US_TO_TICKS(RADIO_TICKER_JITTER_US << 2)))) {
|
|
break;
|
|
}
|
|
|
|
ticker_id_prev = ticker_id;
|
|
ticks_to_expire_prev = ticks_to_expire_normal;
|
|
ticks_slot_prev_abs =
|
|
TICKER_US_TO_TICKS(RADIO_TICKER_XTAL_OFFSET_US) +
|
|
conn->hdr.ticks_slot;
|
|
}
|
|
}
|
|
|
|
if (ticker_id_prev != 0xff) {
|
|
*us_offset = TICKER_TICKS_TO_US(ticks_to_expire_prev +
|
|
ticks_slot_prev_abs) +
|
|
(RADIO_TICKER_JITTER_US << 1);
|
|
}
|
|
}
|
|
|
|
static void sched_after_master_free_offset_get(uint16_t conn_interval,
|
|
uint32_t ticks_slot,
|
|
uint32_t ticks_anchor,
|
|
uint32_t *win_offset_us)
|
|
{
|
|
uint32_t ticks_anchor_offset = ticks_anchor;
|
|
|
|
sched_after_master_free_slot_get(RADIO_TICKER_USER_ID_JOB,
|
|
(TICKER_US_TO_TICKS(RADIO_TICKER_XTAL_OFFSET_US) +
|
|
ticks_slot), &ticks_anchor_offset,
|
|
win_offset_us);
|
|
|
|
if (ticks_anchor_offset != ticks_anchor) {
|
|
*win_offset_us +=
|
|
TICKER_TICKS_TO_US(ticker_ticks_diff_get(ticks_anchor_offset,
|
|
ticks_anchor));
|
|
}
|
|
|
|
if ((*win_offset_us & ((uint32_t)1 << 31)) == 0) {
|
|
uint32_t conn_interval_us = conn_interval * 1250;
|
|
|
|
while (*win_offset_us > conn_interval_us) {
|
|
*win_offset_us -= conn_interval_us;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void work_sched_after_master_free_offset_get(void *params)
|
|
{
|
|
sched_after_master_free_offset_get(_radio.observer.conn_interval,
|
|
_radio.observer.ticks_conn_slot,
|
|
(uint32_t)params,
|
|
&_radio.observer.win_offset_us);
|
|
}
|
|
|
|
static void work_sched_win_offset_use(void *params)
|
|
{
|
|
struct connection *conn = (struct connection *)params;
|
|
uint16_t win_offset;
|
|
|
|
sched_after_master_free_offset_get(conn->conn_interval,
|
|
conn->hdr.ticks_slot,
|
|
conn->llcp.connection_update.ticks_ref,
|
|
&conn->llcp.connection_update.win_offset_us);
|
|
|
|
win_offset = conn->llcp.connection_update.win_offset_us / 1250;
|
|
memcpy(conn->llcp.connection_update.pdu_win_offset, &win_offset,
|
|
sizeof(uint16_t));
|
|
}
|
|
|
|
static void sched_free_win_offset_calc(struct connection *conn_curr,
|
|
uint8_t is_select,
|
|
uint32_t *ticks_to_offset_next,
|
|
uint16_t conn_interval,
|
|
uint8_t *offset_max,
|
|
uint8_t *win_offset)
|
|
{
|
|
uint32_t ticks_prepare_reduced = 0;
|
|
uint32_t ticks_anchor;
|
|
uint32_t ticks_anchor_prev;
|
|
uint32_t ticks_to_expire_prev;
|
|
uint32_t ticks_to_expire;
|
|
uint32_t ticks_slot_prev_abs;
|
|
uint8_t ticker_id;
|
|
uint8_t ticker_id_prev;
|
|
uint8_t ticker_id_other;
|
|
uint8_t offset_index;
|
|
uint16_t _win_offset;
|
|
|
|
if (conn_curr->hdr.ticks_xtal_to_start & ((uint32_t)1 << 31)) {
|
|
uint32_t ticks_prepare_to_start =
|
|
(conn_curr->hdr.ticks_active_to_start >
|
|
conn_curr->hdr.ticks_preempt_to_start) ?
|
|
conn_curr->hdr.ticks_active_to_start :
|
|
conn_curr->hdr.ticks_preempt_to_start;
|
|
|
|
ticks_prepare_reduced = ((conn_curr->hdr.ticks_xtal_to_start &
|
|
(~((uint32_t)1 << 31))) -
|
|
ticks_prepare_to_start);
|
|
}
|
|
|
|
ticker_id = ticker_id_prev = ticker_id_other = 0xFF;
|
|
ticks_to_expire = ticks_to_expire_prev = ticks_anchor =
|
|
ticks_anchor_prev = offset_index = _win_offset = 0;
|
|
ticks_slot_prev_abs = 0;
|
|
do {
|
|
uint32_t volatile ticker_status;
|
|
struct connection *conn;
|
|
|
|
ticker_status =
|
|
ticker_next_slot_get(
|
|
RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_JOB,
|
|
&ticker_id, &ticks_anchor,
|
|
&ticks_to_expire, ticker_if_done,
|
|
(void *)&ticker_status);
|
|
|
|
while (ticker_status == TICKER_STATUS_BUSY) {
|
|
ticker_job_sched(RADIO_TICKER_INSTANCE_ID_RADIO);
|
|
}
|
|
|
|
LL_ASSERT(ticker_status == TICKER_STATUS_SUCCESS);
|
|
|
|
if (ticker_id == 0xff) {
|
|
break;
|
|
}
|
|
|
|
if ((ticker_id_prev != 0xff) &&
|
|
(ticks_anchor != ticks_anchor_prev)) {
|
|
LL_ASSERT(0);
|
|
}
|
|
|
|
if (ticker_id < RADIO_TICKER_ID_ADV) {
|
|
continue;
|
|
}
|
|
|
|
if (ticker_id < RADIO_TICKER_ID_FIRST_CONNECTION) {
|
|
/* non conn role found which could have preempted a
|
|
* conn role, hence do not consider this free space
|
|
* and any further as free slot for offset,
|
|
*/
|
|
ticker_id_other = ticker_id;
|
|
continue;
|
|
}
|
|
|
|
if (ticker_id_other != 0xFF) {
|
|
break;
|
|
}
|
|
|
|
conn = mem_get(_radio.conn_pool, CONNECTION_T_SIZE,
|
|
(ticker_id - RADIO_TICKER_ID_FIRST_CONNECTION));
|
|
|
|
if ((conn != conn_curr) && ((is_select) ||
|
|
(conn->role.master.role == 0))) {
|
|
uint32_t ticks_to_expire_normal =
|
|
ticks_to_expire + ticks_prepare_reduced;
|
|
|
|
if (conn->hdr.ticks_xtal_to_start &
|
|
((uint32_t)1 << 31)) {
|
|
uint32_t ticks_prepare_to_start =
|
|
(conn->hdr.ticks_active_to_start >
|
|
conn->hdr.ticks_preempt_to_start) ?
|
|
conn->hdr.ticks_active_to_start :
|
|
conn->hdr.ticks_preempt_to_start;
|
|
|
|
ticks_to_expire_normal -=
|
|
((conn->hdr.ticks_xtal_to_start &
|
|
(~((uint32_t)1 << 31))) -
|
|
ticks_prepare_to_start);
|
|
}
|
|
|
|
if (*ticks_to_offset_next < ticks_to_expire_normal) {
|
|
if (ticks_to_expire_prev < *ticks_to_offset_next) {
|
|
ticks_to_expire_prev =
|
|
*ticks_to_offset_next;
|
|
}
|
|
|
|
while ((offset_index < *offset_max) &&
|
|
(ticker_ticks_diff_get(ticks_to_expire_normal,
|
|
ticks_to_expire_prev) >=
|
|
(ticks_slot_prev_abs +
|
|
TICKER_US_TO_TICKS(RADIO_TICKER_XTAL_OFFSET_US +
|
|
625 + 1250) +
|
|
conn->hdr.ticks_slot))) {
|
|
_win_offset =
|
|
TICKER_TICKS_TO_US(ticks_to_expire_prev +
|
|
ticks_slot_prev_abs) / 1250;
|
|
if (_win_offset >= conn_interval) {
|
|
ticks_to_expire_prev = 0;
|
|
|
|
break;
|
|
}
|
|
|
|
memcpy(win_offset +
|
|
(sizeof(uint16_t) * offset_index),
|
|
&_win_offset, sizeof(uint16_t));
|
|
offset_index++;
|
|
|
|
ticks_to_expire_prev +=
|
|
TICKER_US_TO_TICKS(1250);
|
|
}
|
|
|
|
*ticks_to_offset_next = ticks_to_expire_prev;
|
|
|
|
if (_win_offset >= conn_interval) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
ticks_anchor_prev = ticks_anchor;
|
|
ticker_id_prev = ticker_id;
|
|
ticks_to_expire_prev = ticks_to_expire_normal;
|
|
ticks_slot_prev_abs =
|
|
TICKER_US_TO_TICKS(RADIO_TICKER_XTAL_OFFSET_US +
|
|
625 + 1250) +
|
|
conn->hdr.ticks_slot;
|
|
}
|
|
} while (offset_index < *offset_max);
|
|
|
|
if (ticker_id == 0xFF) {
|
|
if (ticks_to_expire_prev < *ticks_to_offset_next) {
|
|
ticks_to_expire_prev = *ticks_to_offset_next;
|
|
}
|
|
|
|
while (offset_index < *offset_max) {
|
|
_win_offset =
|
|
TICKER_TICKS_TO_US(ticks_to_expire_prev +
|
|
ticks_slot_prev_abs) / 1250;
|
|
if (_win_offset >= conn_interval) {
|
|
ticks_to_expire_prev = 0;
|
|
|
|
break;
|
|
}
|
|
|
|
memcpy(win_offset + (sizeof(uint16_t) * offset_index),
|
|
&_win_offset, sizeof(uint16_t));
|
|
offset_index++;
|
|
|
|
ticks_to_expire_prev += TICKER_US_TO_TICKS(1250);
|
|
}
|
|
|
|
*ticks_to_offset_next = ticks_to_expire_prev;
|
|
}
|
|
|
|
*offset_max = offset_index;
|
|
}
|
|
|
|
static void work_sched_free_win_offset_calc(void *params)
|
|
{
|
|
struct connection *conn = (struct connection *)params;
|
|
uint32_t ticks_to_offset_default = 0;
|
|
uint32_t *ticks_to_offset_next;
|
|
uint8_t offset_max = 6;
|
|
|
|
ticks_to_offset_next = &ticks_to_offset_default;
|
|
|
|
if (conn->role.slave.role != 0) {
|
|
conn->llcp.connection_update.ticks_to_offset_next =
|
|
conn->role.slave.ticks_to_offset;
|
|
|
|
ticks_to_offset_next =
|
|
&conn->llcp.connection_update.ticks_to_offset_next;
|
|
}
|
|
|
|
sched_free_win_offset_calc(conn, 0, ticks_to_offset_next,
|
|
conn->llcp.connection_update.interval,
|
|
&offset_max,
|
|
(uint8_t *)conn->llcp.connection_update.pdu_win_offset);
|
|
}
|
|
|
|
static void work_sched_win_offset_select(void *params)
|
|
{
|
|
#define OFFSET_S_MAX 6
|
|
#define OFFSET_M_MAX 6
|
|
struct connection *conn = (struct connection *)params;
|
|
uint32_t ticks_to_offset;
|
|
uint16_t win_offset_m[OFFSET_M_MAX];
|
|
uint8_t offset_m_max = OFFSET_M_MAX;
|
|
uint16_t win_offset_s;
|
|
uint8_t offset_index_s = 0;
|
|
|
|
ticks_to_offset =
|
|
TICKER_US_TO_TICKS(conn->llcp.connection_update.offset0 * 1250);
|
|
|
|
sched_free_win_offset_calc(conn, 1, &ticks_to_offset,
|
|
conn->llcp.connection_update.interval,
|
|
&offset_m_max, (uint8_t *)&win_offset_m[0]);
|
|
|
|
while (offset_index_s < OFFSET_S_MAX) {
|
|
uint8_t offset_index_m = 0;
|
|
|
|
memcpy((uint8_t *)&win_offset_s,
|
|
((uint8_t *)&conn->llcp.connection_update.offset0 +
|
|
(sizeof(uint16_t) * offset_index_s)), sizeof(uint16_t));
|
|
|
|
while (offset_index_m < offset_m_max) {
|
|
if ((win_offset_s != 0xffff) &&
|
|
(win_offset_s == win_offset_m[offset_index_m])) {
|
|
break;
|
|
}
|
|
|
|
offset_index_m++;
|
|
}
|
|
|
|
if (offset_index_m < offset_m_max) {
|
|
break;
|
|
}
|
|
|
|
offset_index_s++;
|
|
}
|
|
|
|
if (offset_index_s < OFFSET_S_MAX) {
|
|
conn->llcp.connection_update.win_offset_us =
|
|
win_offset_s * 1250;
|
|
memcpy(conn->llcp.connection_update.pdu_win_offset,
|
|
&win_offset_s, sizeof(uint16_t));
|
|
} else {
|
|
struct pdu_data *pdu_ctrl_tx;
|
|
|
|
/* procedure request acked */
|
|
conn->llcp_ack = conn->llcp_req;
|
|
|
|
/* reset mutex */
|
|
_radio.conn_upd = NULL;
|
|
|
|
/* send reject_ind_ext */
|
|
pdu_ctrl_tx = (struct pdu_data *)
|
|
((uint8_t *)conn->llcp.connection_update.pdu_win_offset -
|
|
offsetof(struct pdu_data,
|
|
payload.llctrl.ctrldata.conn_update_req.win_offset));
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_tx->len =
|
|
offsetof(struct pdu_data_llctrl, ctrldata) +
|
|
sizeof(struct pdu_data_llctrl_reject_ind_ext);
|
|
pdu_ctrl_tx->payload.llctrl.opcode =
|
|
PDU_DATA_LLCTRL_TYPE_REJECT_IND_EXT;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.reject_ind_ext.
|
|
reject_opcode = PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.reject_ind_ext.
|
|
error_code = 0x20; /* Unsupported parameter value */
|
|
}
|
|
}
|
|
#endif /* SCHED_ADVANCED */
|
|
|
|
static void work_radio_stop(void *params)
|
|
{
|
|
enum state state = (enum state)((uint32_t)params & 0xff);
|
|
uint32_t radio_used;
|
|
|
|
LL_ASSERT((state == STATE_STOP) || (state == STATE_ABORT));
|
|
|
|
radio_used = ((_radio.state != STATE_NONE) &&
|
|
(_radio.state != STATE_STOP) &&
|
|
(_radio.state != STATE_ABORT));
|
|
if (radio_used || !radio_is_idle()) {
|
|
if (radio_used) {
|
|
_radio.state = state;
|
|
}
|
|
|
|
/** @todo try designing so as to not to abort tx packet */
|
|
radio_disable();
|
|
}
|
|
}
|
|
|
|
static void event_stop(uint32_t ticks_at_expire, uint32_t remainder,
|
|
uint16_t lazy, void *context)
|
|
{
|
|
static struct work s_work_radio_stop = { 0, 0, 0,
|
|
WORK_TICKER_WORKER0_IRQ, (work_fp) work_radio_stop, 0 };
|
|
uint32_t retval;
|
|
|
|
ARG_UNUSED(ticks_at_expire);
|
|
ARG_UNUSED(remainder);
|
|
ARG_UNUSED(lazy);
|
|
|
|
/* Radio state requested (stop or abort) stored in context is supplied
|
|
* in params.
|
|
*/
|
|
s_work_radio_stop.params = context;
|
|
|
|
/* Stop Radio Tx/Rx */
|
|
retval = work_schedule(&s_work_radio_stop, 0);
|
|
LL_ASSERT(!retval);
|
|
}
|
|
|
|
static void event_common_prepare(uint32_t ticks_at_expire,
|
|
uint32_t remainder,
|
|
uint32_t *ticks_xtal_to_start,
|
|
uint32_t *ticks_active_to_start,
|
|
uint32_t ticks_preempt_to_start,
|
|
uint8_t ticker_id,
|
|
ticker_timeout_func ticker_timeout_fp,
|
|
void *context)
|
|
{
|
|
uint32_t ticker_status;
|
|
uint32_t _ticks_xtal_to_start = *ticks_xtal_to_start;
|
|
uint32_t _ticks_active_to_start = *ticks_active_to_start;
|
|
uint32_t ticks_to_start;
|
|
|
|
/* in case this event is short prepare, xtal to start duration will be
|
|
* active to start duration.
|
|
*/
|
|
if (_ticks_xtal_to_start & ((uint32_t)1 << 31)) {
|
|
_ticks_xtal_to_start =
|
|
(_ticks_active_to_start > ticks_preempt_to_start) ?
|
|
_ticks_active_to_start :
|
|
ticks_preempt_to_start;
|
|
}
|
|
|
|
/* decide whether its XTAL start or active event that is the current
|
|
* execution context and accordingly setup the ticker for the other
|
|
* event (XTAL or active event). These are oneshot ticker.
|
|
*/
|
|
if (_ticks_active_to_start < _ticks_xtal_to_start) {
|
|
uint32_t ticks_to_active;
|
|
|
|
/* XTAL is before Active */
|
|
ticks_to_active = _ticks_xtal_to_start - _ticks_active_to_start;
|
|
ticks_to_start = _ticks_xtal_to_start;
|
|
|
|
ticker_status =
|
|
ticker_start(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER,
|
|
RADIO_TICKER_ID_MARKER_0, ticks_at_expire,
|
|
ticks_to_active, TICKER_NULL_PERIOD,
|
|
TICKER_NULL_REMAINDER, TICKER_NULL_LAZY,
|
|
TICKER_NULL_SLOT, event_active, 0,
|
|
ticker_success_assert, (void *)__LINE__);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
|
|
event_xtal(0, 0, 0, 0);
|
|
} else if (_ticks_active_to_start > _ticks_xtal_to_start) {
|
|
uint32_t ticks_to_xtal;
|
|
|
|
/* Active is before XTAL */
|
|
ticks_to_xtal = _ticks_active_to_start - _ticks_xtal_to_start;
|
|
ticks_to_start = _ticks_active_to_start;
|
|
|
|
event_active(0, 0, 0, 0);
|
|
|
|
ticker_status =
|
|
ticker_start(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER,
|
|
RADIO_TICKER_ID_MARKER_0, ticks_at_expire,
|
|
ticks_to_xtal, TICKER_NULL_PERIOD,
|
|
TICKER_NULL_REMAINDER, TICKER_NULL_LAZY,
|
|
TICKER_NULL_SLOT, event_xtal, 0,
|
|
ticker_success_assert, (void *)__LINE__);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
} else {
|
|
/* Active and XTAL are at the same time,
|
|
* no ticker required to be setup.
|
|
*/
|
|
ticks_to_start = _ticks_xtal_to_start;
|
|
|
|
event_active(0, 0, 0, 0);
|
|
event_xtal(0, 0, 0, 0);
|
|
}
|
|
|
|
/* remember the remainder to be used in pkticker */
|
|
_radio.remainder_anchor = remainder;
|
|
|
|
/* setup the start ticker */
|
|
ticker_status =
|
|
ticker_start(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER, RADIO_TICKER_ID_EVENT,
|
|
ticks_at_expire, ticks_to_start,
|
|
TICKER_NULL_PERIOD, TICKER_NULL_REMAINDER,
|
|
TICKER_NULL_LAZY, TICKER_NULL_SLOT,
|
|
ticker_timeout_fp, context, ticker_success_assert,
|
|
(void *)__LINE__);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
|
|
#define RADIO_DEFERRED_PREEMPT 0
|
|
#if RADIO_DEFERRED_PREEMPT
|
|
/* setup pre-empt ticker if any running state present */
|
|
if (_radio.state != STATE_NONE) {
|
|
ticker_status =
|
|
ticker_start(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER,
|
|
RADIO_TICKER_ID_PRE_EMPT, ticks_at_expire,
|
|
(ticks_to_start - conn->hdr.ticks_preempt_to_start),
|
|
TICKER_NULL_PERIOD, TICKER_NULL_REMAINDER,
|
|
TICKER_NULL_LAZY, TICKER_NULL_SLOT,
|
|
event_stop, (void *)STATE_ABORT,
|
|
ticker_success_assert, (void *)__LINE__);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
}
|
|
#else
|
|
event_stop(0, 0, 0, (void *)STATE_ABORT);
|
|
#endif
|
|
#undef RADIO_DEFERRED_PREEMPT
|
|
|
|
/** Handle change in _ticks_active_to_start */
|
|
if (_radio.ticks_active_to_start != _ticks_active_to_start) {
|
|
uint32_t ticks_to_start_new =
|
|
((_radio.ticks_active_to_start <
|
|
(*ticks_xtal_to_start & ~(((uint32_t)1 << 31)))) ?
|
|
(*ticks_xtal_to_start & ~(((uint32_t)1 << 31))) :
|
|
_radio.ticks_active_to_start);
|
|
|
|
*ticks_active_to_start = _radio.ticks_active_to_start;
|
|
|
|
if ((*ticks_xtal_to_start) & ((uint32_t)1 << 31)) {
|
|
*ticks_xtal_to_start &= ~(((uint32_t)1 << 31));
|
|
}
|
|
|
|
/* drift the primary as required due to active line change */
|
|
ticker_status =
|
|
ticker_update(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER, ticker_id,
|
|
ticks_to_start, ticks_to_start_new,
|
|
ticks_to_start_new, ticks_to_start, 0, 0,
|
|
ticker_success_assert, (void *)__LINE__);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
}
|
|
|
|
/* route all packets queued for connections */
|
|
packet_tx_enqueue(0xFF);
|
|
|
|
/* calc whether xtal needs to be retained after this event */
|
|
#if XTAL_ADVANCED
|
|
{
|
|
static struct work s_work_xtal_stop_calc = {
|
|
0, 0, 0, WORK_TICKER_JOB0_IRQ,
|
|
(work_fp) work_xtal_stop_calc, 0 };
|
|
uint32_t retval;
|
|
|
|
s_work_xtal_stop_calc.params = (void *)(uint32_t)ticker_id;
|
|
|
|
retval = work_schedule(&s_work_xtal_stop_calc, 1);
|
|
LL_ASSERT(!retval);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static uint8_t channel_calc(uint8_t *channel_use, uint8_t hop,
|
|
uint16_t latency, uint8_t *channel_map,
|
|
uint8_t channel_count)
|
|
{
|
|
uint8_t channel_next;
|
|
|
|
channel_next = ((*channel_use) + (hop * (1 + latency))) % 37;
|
|
*channel_use = channel_next;
|
|
|
|
if ((channel_map[channel_next >> 3] & (1 << (channel_next % 8))) == 0) {
|
|
uint8_t channel_index;
|
|
uint8_t byte_count;
|
|
|
|
channel_index = channel_next % channel_count;
|
|
channel_next = 0;
|
|
|
|
byte_count = 5;
|
|
while (byte_count--) {
|
|
uint8_t bite;
|
|
uint8_t bit_count;
|
|
|
|
bite = *channel_map;
|
|
bit_count = 8;
|
|
while (bit_count--) {
|
|
if (bite & 0x01) {
|
|
if (channel_index == 0) {
|
|
break;
|
|
}
|
|
channel_index--;
|
|
}
|
|
channel_next++;
|
|
bite >>= 1;
|
|
}
|
|
|
|
if (bit_count < 8) {
|
|
break;
|
|
}
|
|
|
|
channel_map++;
|
|
}
|
|
} else {
|
|
/* channel can be used, return it */
|
|
}
|
|
|
|
return channel_next;
|
|
}
|
|
|
|
static void channel_set(uint32_t channel)
|
|
{
|
|
switch (channel) {
|
|
case 37:
|
|
radio_freq_chnl_set(2);
|
|
break;
|
|
|
|
case 38:
|
|
radio_freq_chnl_set(26);
|
|
break;
|
|
|
|
case 39:
|
|
radio_freq_chnl_set(80);
|
|
break;
|
|
|
|
default:
|
|
if (channel < 11) {
|
|
radio_freq_chnl_set(4 + (2 * channel));
|
|
} else if (channel < 40) {
|
|
radio_freq_chnl_set(28 + (2 * (channel - 11)));
|
|
} else {
|
|
LL_ASSERT(0);
|
|
}
|
|
break;
|
|
}
|
|
|
|
radio_whiten_iv_set(channel);
|
|
}
|
|
|
|
/** @brief Prepare access address as per BT Spec.
|
|
*
|
|
* - It shall have no more than six consecutive zeros or ones.
|
|
* - It shall not be the advertising channel packets' Access Address.
|
|
* - It shall not be a sequence that differs from the advertising channel
|
|
* packets Access Address by only one bit.
|
|
* - It shall not have all four octets equal.
|
|
* - It shall have no more than 24 transitions.
|
|
* - It shall have a minimum of two transitions in the most significant six
|
|
* bits.
|
|
*/
|
|
static uint32_t access_addr_get(void)
|
|
{
|
|
uint32_t access_addr;
|
|
uint8_t bit_idx;
|
|
uint8_t transitions;
|
|
uint8_t consecutive_cnt;
|
|
uint8_t consecutive_bit;
|
|
|
|
rand_get(sizeof(uint32_t), (uint8_t *)&access_addr);
|
|
|
|
bit_idx = 31;
|
|
transitions = 0;
|
|
consecutive_cnt = 1;
|
|
consecutive_bit = (access_addr >> bit_idx) & 0x01;
|
|
while (bit_idx--) {
|
|
uint8_t bit;
|
|
|
|
bit = (access_addr >> bit_idx) & 0x01;
|
|
if (bit == consecutive_bit) {
|
|
consecutive_cnt++;
|
|
} else {
|
|
consecutive_cnt = 1;
|
|
consecutive_bit = bit;
|
|
transitions++;
|
|
}
|
|
|
|
/* It shall have no more than six consecutive zeros or ones. */
|
|
/* It shall have a minimum of two transitions in the most
|
|
* significant six bits.
|
|
*/
|
|
if ((consecutive_cnt > 6)
|
|
|| ((bit_idx < 28) && (transitions < 2))) {
|
|
if (consecutive_bit) {
|
|
consecutive_bit = 0;
|
|
access_addr &= ~(1 << bit_idx);
|
|
} else {
|
|
consecutive_bit = 1;
|
|
access_addr |= (1 << bit_idx);
|
|
}
|
|
|
|
consecutive_cnt = 1;
|
|
transitions++;
|
|
}
|
|
|
|
/* It shall have no more than 24 transitions */
|
|
if (transitions > 24) {
|
|
if (consecutive_bit) {
|
|
access_addr &= ~((1 << (bit_idx + 1)) - 1);
|
|
} else {
|
|
access_addr |= ((1 << (bit_idx + 1)) - 1);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** @todo proper access address calculations
|
|
* It shall not be the advertising channel packets Access Address.
|
|
* It shall not be a sequence that differs from the advertising channel
|
|
* packets Access Address by only one bit.
|
|
* It shall not have all four octets equal.
|
|
*/
|
|
|
|
return access_addr;
|
|
}
|
|
|
|
static void adv_obs_conn_configure(uint8_t phy)
|
|
{
|
|
radio_reset();
|
|
radio_phy_set(phy);
|
|
radio_tx_power_set(0);
|
|
radio_tmr_tifs_set(150);
|
|
radio_isr_set(isr);
|
|
}
|
|
|
|
static void adv_obs_configure(uint8_t phy)
|
|
{
|
|
uint32_t aa = 0x8e89bed6;
|
|
|
|
adv_obs_conn_configure(phy);
|
|
radio_aa_set((uint8_t *)&aa);
|
|
radio_pkt_configure(phy, 6, 37);
|
|
radio_crc_configure(((0x5bUL) | ((0x06UL) << 8) | ((0x00UL) << 16)),
|
|
0x555555);
|
|
}
|
|
|
|
void radio_event_adv_prepare(uint32_t ticks_at_expire, uint32_t remainder,
|
|
uint16_t lazy, void *context)
|
|
{
|
|
ARG_UNUSED(lazy);
|
|
ARG_UNUSED(context);
|
|
|
|
DEBUG_RADIO_PREPARE_A(1);
|
|
|
|
_radio.ticker_id_prepare = RADIO_TICKER_ID_ADV;
|
|
|
|
event_common_prepare(ticks_at_expire, remainder,
|
|
&_radio.advertiser.hdr.ticks_xtal_to_start,
|
|
&_radio.advertiser.hdr.ticks_active_to_start,
|
|
_radio.advertiser.hdr.ticks_preempt_to_start,
|
|
RADIO_TICKER_ID_ADV, event_adv, 0);
|
|
|
|
DEBUG_RADIO_PREPARE_A(0);
|
|
}
|
|
|
|
static void adv_setup(void)
|
|
{
|
|
uint8_t bitmap;
|
|
uint8_t channel;
|
|
|
|
/* Use latest adv packet */
|
|
if (_radio.advertiser.adv_data.first !=
|
|
_radio.advertiser.adv_data.last) {
|
|
uint8_t first;
|
|
|
|
first = _radio.advertiser.adv_data.first + 1;
|
|
if (first == DOUBLE_BUFFER_SIZE) {
|
|
first = 0;
|
|
}
|
|
_radio.advertiser.adv_data.first = first;
|
|
}
|
|
|
|
radio_pkt_tx_set(&_radio.advertiser.adv_data.data
|
|
[_radio.advertiser.adv_data.first][0]);
|
|
radio_switch_complete_and_rx();
|
|
|
|
bitmap = _radio.advertiser.chl_map_current;
|
|
channel = 0;
|
|
while ((bitmap & 0x01) == 0) {
|
|
channel++;
|
|
bitmap >>= 1;
|
|
}
|
|
_radio.advertiser.chl_map_current &=
|
|
(_radio.advertiser.chl_map_current - 1);
|
|
|
|
channel_set(37 + channel);
|
|
}
|
|
|
|
static void event_adv(uint32_t ticks_at_expire, uint32_t remainder,
|
|
uint16_t lazy, void *context)
|
|
{
|
|
ARG_UNUSED(remainder);
|
|
ARG_UNUSED(lazy);
|
|
ARG_UNUSED(context);
|
|
|
|
DEBUG_RADIO_START_A(1);
|
|
|
|
LL_ASSERT(_radio.role == ROLE_NONE);
|
|
LL_ASSERT(_radio.ticker_id_prepare == RADIO_TICKER_ID_ADV);
|
|
|
|
/** @todo check if XTAL is started,
|
|
* options 1: abort Radio Start,
|
|
* 2: wait for XTAL start.
|
|
*/
|
|
|
|
_radio.role = ROLE_ADV;
|
|
_radio.state = STATE_TX;
|
|
_radio.ticker_id_prepare = 0;
|
|
_radio.ticker_id_event = RADIO_TICKER_ID_ADV;
|
|
_radio.ticks_anchor = ticks_at_expire;
|
|
|
|
adv_obs_configure(RADIO_PHY_ADV);
|
|
|
|
_radio.advertiser.chl_map_current = _radio.advertiser.chl_map;
|
|
adv_setup();
|
|
|
|
/* Setup Radio Filter */
|
|
if (_radio.advertiser.filter_policy) {
|
|
radio_filter_configure(_radio.advertiser.filter_enable_bitmask,
|
|
_radio.advertiser.filter_addr_type_bitmask,
|
|
(uint8_t *)_radio.advertiser.filter_bdaddr);
|
|
}
|
|
|
|
radio_tmr_start(1,
|
|
ticks_at_expire +
|
|
TICKER_US_TO_TICKS(RADIO_TICKER_START_PART_US),
|
|
_radio.remainder_anchor);
|
|
radio_tmr_end_capture();
|
|
|
|
#if (XTAL_ADVANCED && (RADIO_TICKER_PREEMPT_PART_US \
|
|
<= RADIO_TICKER_PREEMPT_PART_MIN_US))
|
|
/* check if preempt to start has changed */
|
|
if (preempt_calc(&_radio.advertiser.hdr, RADIO_TICKER_ID_ADV,
|
|
ticks_at_expire) != 0) {
|
|
_radio.state = STATE_STOP;
|
|
radio_disable();
|
|
} else
|
|
#endif
|
|
/* Ticker Job Silence */
|
|
#if (WORK_TICKER_WORKER0_IRQ_PRIORITY == WORK_TICKER_JOB0_IRQ_PRIORITY)
|
|
{
|
|
uint32_t ticker_status;
|
|
|
|
ticker_status =
|
|
ticker_job_idle_get(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER,
|
|
ticker_job_disable, 0);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
}
|
|
#endif
|
|
|
|
DEBUG_RADIO_START_A(0);
|
|
}
|
|
|
|
void event_adv_stop(uint32_t ticks_at_expire, uint32_t remainder,
|
|
uint16_t lazy, void *context)
|
|
{
|
|
uint32_t ticker_status;
|
|
struct radio_pdu_node_rx *radio_pdu_node_rx;
|
|
struct pdu_data *pdu_data_rx;
|
|
struct radio_le_conn_cmplt *radio_le_conn_cmplt;
|
|
|
|
ARG_UNUSED(ticks_at_expire);
|
|
ARG_UNUSED(remainder);
|
|
ARG_UNUSED(lazy);
|
|
ARG_UNUSED(context);
|
|
|
|
/* Stop Direct Adv */
|
|
ticker_status =
|
|
ticker_stop(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER, RADIO_TICKER_ID_ADV,
|
|
ticker_success_assert, (void *)__LINE__);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
/** @todo synchronize stopping of scanner, i.e. pre-event and event
|
|
* needs to complete
|
|
*/
|
|
/* below lines are temporary */
|
|
ticker_status = ticker_stop(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER,
|
|
RADIO_TICKER_ID_MARKER_0,
|
|
ticker_success_assert, (void *)__LINE__);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
ticker_status = ticker_stop(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER, RADIO_TICKER_ID_EVENT,
|
|
ticker_success_assert, (void *)__LINE__);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
|
|
/* Prepare the rx packet structure */
|
|
radio_pdu_node_rx = packet_rx_reserve_get(1);
|
|
LL_ASSERT(radio_pdu_node_rx);
|
|
|
|
/** Connection handle */
|
|
radio_pdu_node_rx->hdr.handle = 0xffff;
|
|
/** @todo */
|
|
radio_pdu_node_rx->hdr.type = NODE_RX_TYPE_CONNECTION;
|
|
|
|
/* prepare connection complete structure */
|
|
pdu_data_rx = (struct pdu_data *)radio_pdu_node_rx->pdu_data;
|
|
radio_le_conn_cmplt =
|
|
(struct radio_le_conn_cmplt *)&pdu_data_rx->payload;
|
|
memset(radio_le_conn_cmplt, 0x00, sizeof(struct radio_le_conn_cmplt));
|
|
radio_le_conn_cmplt->status = 0x3c;
|
|
|
|
/* enqueue connection complete structure into queue */
|
|
packet_rx_enqueue();
|
|
}
|
|
|
|
static void event_obs_prepare(uint32_t ticks_at_expire, uint32_t remainder,
|
|
uint16_t lazy, void *context)
|
|
{
|
|
ARG_UNUSED(lazy);
|
|
ARG_UNUSED(context);
|
|
|
|
DEBUG_RADIO_PREPARE_O(1);
|
|
|
|
_radio.ticker_id_prepare = RADIO_TICKER_ID_OBS;
|
|
|
|
event_common_prepare(ticks_at_expire, remainder,
|
|
&_radio.observer.hdr.ticks_xtal_to_start,
|
|
&_radio.observer.hdr.ticks_active_to_start,
|
|
_radio.observer.hdr.ticks_preempt_to_start,
|
|
RADIO_TICKER_ID_OBS, event_obs, 0);
|
|
|
|
#if SCHED_ADVANCED
|
|
/* calc next group in us for the anchor where first connection event
|
|
* to be placed
|
|
*/
|
|
if (_radio.observer.conn) {
|
|
static struct work _work_sched_after_master_free_offset_get = {
|
|
0, 0, 0, WORK_TICKER_JOB0_IRQ,
|
|
(work_fp) work_sched_after_master_free_offset_get, 0 };
|
|
uint32_t ticks_at_expire_normal = ticks_at_expire;
|
|
uint32_t retval;
|
|
|
|
if (_radio.observer.hdr.ticks_xtal_to_start & ((uint32_t)1 << 31)) {
|
|
uint32_t ticks_prepare_to_start =
|
|
(_radio.observer.hdr.ticks_active_to_start >
|
|
_radio.observer.hdr.ticks_preempt_to_start) ?
|
|
_radio.observer.hdr.ticks_active_to_start :
|
|
_radio.observer.hdr.ticks_preempt_to_start;
|
|
|
|
ticks_at_expire_normal -=
|
|
((_radio.observer.hdr.ticks_xtal_to_start &
|
|
(~((uint32_t)1 << 31))) -
|
|
ticks_prepare_to_start);
|
|
}
|
|
|
|
_work_sched_after_master_free_offset_get.params =
|
|
(void *)ticks_at_expire_normal;
|
|
|
|
retval = work_schedule(&_work_sched_after_master_free_offset_get,
|
|
1);
|
|
LL_ASSERT(!retval);
|
|
}
|
|
#endif
|
|
|
|
DEBUG_RADIO_PREPARE_O(0);
|
|
}
|
|
|
|
static void event_obs(uint32_t ticks_at_expire, uint32_t remainder,
|
|
uint16_t lazy, void *context)
|
|
{
|
|
uint32_t ticker_status;
|
|
|
|
ARG_UNUSED(remainder);
|
|
ARG_UNUSED(lazy);
|
|
ARG_UNUSED(context);
|
|
|
|
DEBUG_RADIO_START_O(1);
|
|
|
|
LL_ASSERT(_radio.role == ROLE_NONE);
|
|
LL_ASSERT(_radio.ticker_id_prepare == RADIO_TICKER_ID_OBS);
|
|
|
|
/** @todo check if XTAL is started, options 1: abort Radio Start,
|
|
* 2: wait for XTAL start
|
|
*/
|
|
_radio.role = ROLE_OBS;
|
|
_radio.state = STATE_RX;
|
|
_radio.ticker_id_prepare = 0;
|
|
_radio.ticker_id_event = RADIO_TICKER_ID_OBS;
|
|
_radio.ticks_anchor = ticks_at_expire;
|
|
_radio.observer.scan_state = 0;
|
|
|
|
adv_obs_configure(RADIO_PHY_ADV);
|
|
|
|
channel_set(37 + _radio.observer.scan_channel++);
|
|
if (_radio.observer.scan_channel == 3) {
|
|
_radio.observer.scan_channel = 0;
|
|
}
|
|
|
|
radio_pkt_rx_set(_radio.packet_rx[_radio.packet_rx_last]->pdu_data);
|
|
radio_switch_complete_and_tx();
|
|
radio_rssi_measure();
|
|
|
|
/* Setup Radio Filter */
|
|
if (_radio.observer.filter_policy) {
|
|
radio_filter_configure(_radio.observer.filter_enable_bitmask,
|
|
_radio.observer.filter_addr_type_bitmask,
|
|
(uint8_t *)_radio.observer.filter_bdaddr);
|
|
|
|
if (_radio.nirk) {
|
|
radio_ar_configure(_radio.nirk, _radio.irk);
|
|
}
|
|
}
|
|
|
|
radio_tmr_start(0,
|
|
ticks_at_expire +
|
|
TICKER_US_TO_TICKS(RADIO_TICKER_START_PART_US),
|
|
_radio.remainder_anchor);
|
|
radio_tmr_end_capture();
|
|
|
|
#if (XTAL_ADVANCED && (RADIO_TICKER_PREEMPT_PART_US\
|
|
<= RADIO_TICKER_PREEMPT_PART_MIN_US))
|
|
/* check if preempt to start has changed */
|
|
if (preempt_calc(&_radio.observer.hdr, RADIO_TICKER_ID_OBS,
|
|
ticks_at_expire) != 0) {
|
|
_radio.state = STATE_STOP;
|
|
radio_disable();
|
|
} else
|
|
#endif
|
|
{
|
|
/* start window close timeout */
|
|
ticker_status =
|
|
ticker_start(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER,
|
|
RADIO_TICKER_ID_OBS_STOP, ticks_at_expire,
|
|
_radio.observer.ticks_window +
|
|
TICKER_US_TO_TICKS(RADIO_TICKER_START_PART_US),
|
|
TICKER_NULL_PERIOD, TICKER_NULL_REMAINDER,
|
|
TICKER_NULL_LAZY, TICKER_NULL_SLOT,
|
|
event_stop, (void *)STATE_STOP,
|
|
ticker_success_assert, (void *)__LINE__);
|
|
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
|
|
/* Ticker Job Silence */
|
|
#if (WORK_TICKER_WORKER0_IRQ_PRIORITY == WORK_TICKER_JOB0_IRQ_PRIORITY)
|
|
{
|
|
uint32_t ticker_status;
|
|
|
|
ticker_status =
|
|
ticker_job_idle_get(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER,
|
|
ticker_job_disable, 0);
|
|
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
DEBUG_RADIO_START_O(0);
|
|
}
|
|
|
|
static inline void event_conn_update_st_init(struct connection *conn,
|
|
uint16_t event_counter,
|
|
struct pdu_data *pdu_ctrl_tx,
|
|
uint32_t ticks_at_expire,
|
|
struct work *work_sched_offset,
|
|
work_fp fp_work_select_or_use)
|
|
{
|
|
/* move to in progress */
|
|
conn->llcp.connection_update.state = LLCP_CONN_STATE_INPROG;
|
|
|
|
/* set instant */
|
|
conn->llcp.connection_update.instant =
|
|
event_counter + conn->latency + 6;
|
|
|
|
/* place the conn update req packet as next in tx queue */
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_tx->len = offsetof(struct pdu_data_llctrl, ctrldata) +
|
|
sizeof(struct pdu_data_llctrl_conn_update_req);
|
|
pdu_ctrl_tx->payload.llctrl.opcode =
|
|
PDU_DATA_LLCTRL_TYPE_CONN_UPDATE_REQ;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_update_req.win_size =
|
|
conn->llcp.connection_update.win_size;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_update_req.
|
|
win_offset = conn->llcp.connection_update.win_offset_us / 1250;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_update_req.interval =
|
|
conn->llcp.connection_update.interval;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_update_req.latency =
|
|
conn->llcp.connection_update.latency;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_update_req.timeout =
|
|
conn->llcp.connection_update.timeout;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_update_req.instant =
|
|
conn->llcp.connection_update.instant;
|
|
|
|
#if SCHED_ADVANCED
|
|
{
|
|
uint32_t retval;
|
|
|
|
/* calculate window offset that places the connection in the
|
|
* next available slot after existing masters.
|
|
*/
|
|
conn->llcp.connection_update.ticks_ref = ticks_at_expire;
|
|
if (conn->hdr.ticks_xtal_to_start & ((uint32_t)1 << 31)) {
|
|
uint32_t ticks_prepare_to_start =
|
|
(conn->hdr.ticks_active_to_start >
|
|
conn->hdr.ticks_preempt_to_start) ?
|
|
conn->hdr.ticks_active_to_start :
|
|
conn->hdr.ticks_preempt_to_start;
|
|
|
|
conn->llcp.connection_update.ticks_ref -=
|
|
((conn->hdr.ticks_xtal_to_start &
|
|
(~((uint32_t)1 << 31))) -
|
|
ticks_prepare_to_start);
|
|
}
|
|
|
|
conn->llcp.connection_update.pdu_win_offset = (uint16_t *)
|
|
&pdu_ctrl_tx->payload.llctrl.ctrldata.conn_update_req.win_offset;
|
|
|
|
work_sched_offset->fp = fp_work_select_or_use;
|
|
work_sched_offset->params = (void *)conn;
|
|
|
|
retval = work_schedule(work_sched_offset, 1);
|
|
LL_ASSERT(!retval);
|
|
}
|
|
#else
|
|
ARG_UNUSED(ticks_at_expire);
|
|
ARG_UNUSED(work_sched_offset);
|
|
ARG_UNUSED(fp_work_select_or_use);
|
|
#endif
|
|
}
|
|
|
|
static inline void event_conn_update_st_req(struct connection *conn,
|
|
uint16_t event_counter,
|
|
struct pdu_data *pdu_ctrl_tx,
|
|
uint32_t ticks_at_expire,
|
|
struct work *work_sched_offset)
|
|
{
|
|
/* move to wait for conn_update/rsp/rej */
|
|
conn->llcp.connection_update.state = LLCP_CONN_STATE_RSP_WAIT;
|
|
|
|
/* place the conn param req packet as next in tx queue */
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_tx->len = offsetof(struct pdu_data_llctrl, ctrldata) +
|
|
sizeof(struct pdu_data_llctrl_conn_param_req);
|
|
pdu_ctrl_tx->payload.llctrl.opcode =
|
|
PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.interval_min =
|
|
conn->llcp.connection_update.interval;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.interval_max =
|
|
conn->llcp.connection_update.interval;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.latency =
|
|
conn->llcp.connection_update.latency;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.timeout =
|
|
conn->llcp.connection_update.timeout;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.preferred_periodicity = 0;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.reference_conn_event_count = event_counter;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.offset0 = 0x0000;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.offset1 = 0xffff;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.offset2 = 0xffff;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.offset3 = 0xffff;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.offset4 = 0xffff;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.offset5 = 0xffff;
|
|
|
|
/* Start Procedure Timeout */
|
|
conn->procedure_expire = conn->procedure_reload;
|
|
|
|
#if SCHED_ADVANCED
|
|
{
|
|
uint32_t retval;
|
|
|
|
conn->llcp.connection_update.ticks_ref = ticks_at_expire;
|
|
if (conn->hdr.ticks_xtal_to_start & ((uint32_t)1 << 31)) {
|
|
uint32_t ticks_prepare_to_start =
|
|
(conn->hdr.ticks_active_to_start >
|
|
conn->hdr.ticks_preempt_to_start) ?
|
|
conn->hdr.ticks_active_to_start :
|
|
conn->hdr.ticks_preempt_to_start;
|
|
|
|
conn->llcp.connection_update.ticks_ref -=
|
|
((conn->hdr.ticks_xtal_to_start &
|
|
(~((uint32_t)1 << 31))) -
|
|
ticks_prepare_to_start);
|
|
}
|
|
|
|
conn->llcp.connection_update.pdu_win_offset = (uint16_t *)
|
|
&pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.offset0;
|
|
|
|
work_sched_offset->fp = work_sched_free_win_offset_calc;
|
|
work_sched_offset->params = (void *)conn;
|
|
|
|
retval = work_schedule(work_sched_offset, 1);
|
|
LL_ASSERT(!retval);
|
|
}
|
|
#else
|
|
ARG_UNUSED(ticks_at_expire);
|
|
ARG_UNUSED(work_sched_offset);
|
|
#endif
|
|
}
|
|
|
|
static inline void event_conn_update_st_rsp(struct connection *conn,
|
|
struct pdu_data *pdu_ctrl_tx)
|
|
{
|
|
/* procedure request acked */
|
|
conn->llcp_ack = conn->llcp_req;
|
|
|
|
/* reset mutex */
|
|
_radio.conn_upd = NULL;
|
|
|
|
/** @todo REJECT_IND_EXT */
|
|
|
|
/* place the conn param rsp packet as next in tx queue */
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_tx->len = offsetof(struct pdu_data_llctrl, ctrldata) +
|
|
sizeof(struct pdu_data_llctrl_conn_param_rsp);
|
|
pdu_ctrl_tx->payload.llctrl.opcode =
|
|
PDU_DATA_LLCTRL_TYPE_CONN_PARAM_RSP;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.interval_min =
|
|
conn->llcp.connection_update.interval;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.interval_max =
|
|
conn->llcp.connection_update.interval;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.latency =
|
|
conn->llcp.connection_update.latency;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.timeout =
|
|
conn->llcp.connection_update.timeout;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.preferred_periodicity =
|
|
conn->llcp.connection_update.preferred_periodicity;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.reference_conn_event_count =
|
|
conn->llcp.connection_update.instant;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.offset0 =
|
|
conn->llcp.connection_update.offset0;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.offset1 =
|
|
conn->llcp.connection_update.offset1;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.offset2 =
|
|
conn->llcp.connection_update.offset2;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.offset3 =
|
|
conn->llcp.connection_update.offset3;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.offset4 =
|
|
conn->llcp.connection_update.offset4;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.conn_param_req.offset5 =
|
|
conn->llcp.connection_update.offset5;
|
|
}
|
|
|
|
static inline uint32_t event_conn_update_prep(struct connection *conn,
|
|
uint16_t event_counter,
|
|
uint32_t ticks_at_expire)
|
|
{
|
|
struct connection *conn_upd;
|
|
uint16_t instant_latency;
|
|
|
|
conn_upd = _radio.conn_upd;
|
|
|
|
/* set mutex */
|
|
if (!conn_upd) {
|
|
_radio.conn_upd = conn;
|
|
}
|
|
|
|
instant_latency =
|
|
((event_counter - conn->llcp.connection_update.instant) &
|
|
0xffff);
|
|
if (conn->llcp.connection_update.state) {
|
|
if (((conn_upd == 0) || (conn_upd == conn)) &&
|
|
(conn->llcp.connection_update.state !=
|
|
LLCP_CONN_STATE_APP_WAIT) &&
|
|
(conn->llcp.connection_update.state !=
|
|
LLCP_CONN_STATE_RSP_WAIT)) {
|
|
#if SCHED_ADVANCED
|
|
static struct work gs_work_sched_offset = {
|
|
0, 0, 0, WORK_TICKER_JOB0_IRQ, 0, 0 };
|
|
work_fp fp_work_select_or_use;
|
|
#endif
|
|
struct radio_pdu_node_tx *node_tx;
|
|
struct pdu_data *pdu_ctrl_tx;
|
|
uint8_t state;
|
|
|
|
node_tx = mem_acquire(&_radio.pkt_tx_ctrl_free);
|
|
if (!node_tx) {
|
|
return 1;
|
|
}
|
|
|
|
pdu_ctrl_tx = (struct pdu_data *)node_tx->pdu_data;
|
|
|
|
#if SCHED_ADVANCED
|
|
fp_work_select_or_use = work_sched_win_offset_use;
|
|
#endif
|
|
state = conn->llcp.connection_update.state;
|
|
if ((state == LLCP_CONN_STATE_RSP) &&
|
|
(conn->role.master.role == 0)) {
|
|
state = LLCP_CONN_STATE_INITIATE;
|
|
#if SCHED_ADVANCED
|
|
fp_work_select_or_use =
|
|
work_sched_win_offset_select;
|
|
#endif
|
|
}
|
|
|
|
switch (state) {
|
|
case LLCP_CONN_STATE_INITIATE:
|
|
if (conn->role.master.role == 0) {
|
|
#if SCHED_ADVANCED
|
|
event_conn_update_st_init(conn,
|
|
event_counter,
|
|
pdu_ctrl_tx,
|
|
ticks_at_expire,
|
|
&gs_work_sched_offset,
|
|
fp_work_select_or_use);
|
|
#else
|
|
event_conn_update_st_init(conn,
|
|
event_counter,
|
|
pdu_ctrl_tx,
|
|
ticks_at_expire,
|
|
NULL,
|
|
NULL);
|
|
#endif
|
|
break;
|
|
}
|
|
/* fall thru if slave */
|
|
|
|
case LLCP_CONN_STATE_REQ:
|
|
#if SCHED_ADVANCED
|
|
event_conn_update_st_req(conn,
|
|
event_counter,
|
|
pdu_ctrl_tx,
|
|
ticks_at_expire,
|
|
&gs_work_sched_offset);
|
|
#else
|
|
event_conn_update_st_req(conn,
|
|
event_counter,
|
|
pdu_ctrl_tx,
|
|
ticks_at_expire,
|
|
NULL);
|
|
#endif
|
|
break;
|
|
|
|
case LLCP_CONN_STATE_RSP:
|
|
event_conn_update_st_rsp(conn, pdu_ctrl_tx);
|
|
break;
|
|
|
|
default:
|
|
LL_ASSERT(0);
|
|
break;
|
|
}
|
|
|
|
ctrl_tx_enqueue(conn, node_tx);
|
|
}
|
|
} else if (instant_latency <= 0x7FFF) {
|
|
struct radio_pdu_node_rx *radio_pdu_node_rx;
|
|
struct pdu_data *pdu_data_rx;
|
|
struct radio_le_conn_update_cmplt *radio_le_conn_update_cmplt;
|
|
uint32_t ticker_status;
|
|
uint32_t conn_interval_us;
|
|
uint32_t periodic_us;
|
|
uint32_t ticks_win_offset;
|
|
uint32_t ticks_slot_offset;
|
|
uint16_t conn_interval_old;
|
|
uint16_t conn_interval_new;
|
|
uint16_t latency;
|
|
uint32_t work_was_enabled;
|
|
|
|
/* procedure request acked */
|
|
conn->llcp_ack = conn->llcp_req;
|
|
|
|
/* Reset ticker_id_prepare as role is not continued further
|
|
* due to conn update at this event.
|
|
*/
|
|
_radio.ticker_id_prepare = 0;
|
|
|
|
/* reset mutex */
|
|
if (_radio.conn_upd == conn) {
|
|
_radio.conn_upd = NULL;
|
|
}
|
|
|
|
/* Prepare the rx packet structure */
|
|
if ((conn->llcp.connection_update.interval !=
|
|
conn->conn_interval) ||
|
|
(conn->llcp.connection_update.latency != conn->latency) ||
|
|
(conn->llcp.connection_update.timeout !=
|
|
(conn->conn_interval * conn->supervision_reload * 125 / 1000))) {
|
|
radio_pdu_node_rx = packet_rx_reserve_get(2);
|
|
LL_ASSERT(radio_pdu_node_rx);
|
|
|
|
radio_pdu_node_rx->hdr.handle = conn->handle;
|
|
radio_pdu_node_rx->hdr.type = NODE_RX_TYPE_CONN_UPDATE;
|
|
|
|
/* prepare connection update complete structure */
|
|
pdu_data_rx =
|
|
(struct pdu_data *)radio_pdu_node_rx->pdu_data;
|
|
radio_le_conn_update_cmplt =
|
|
(struct radio_le_conn_update_cmplt *)
|
|
&pdu_data_rx->payload;
|
|
radio_le_conn_update_cmplt->status = 0x00;
|
|
radio_le_conn_update_cmplt->interval =
|
|
conn->llcp.connection_update.interval;
|
|
radio_le_conn_update_cmplt->latency =
|
|
conn->llcp.connection_update.latency;
|
|
radio_le_conn_update_cmplt->timeout =
|
|
conn->llcp.connection_update.timeout;
|
|
|
|
/* enqueue connection update complete structure
|
|
* into queue.
|
|
*/
|
|
packet_rx_enqueue();
|
|
}
|
|
|
|
/* restore to normal prepare */
|
|
if (conn->hdr.ticks_xtal_to_start & ((uint32_t)1 << 31)) {
|
|
uint32_t ticks_prepare_to_start =
|
|
(conn->hdr.ticks_active_to_start >
|
|
conn->hdr.ticks_preempt_to_start) ?
|
|
conn->hdr.ticks_active_to_start :
|
|
conn->hdr.ticks_preempt_to_start;
|
|
|
|
conn->hdr.ticks_xtal_to_start &= ~((uint32_t)1 << 31);
|
|
ticks_at_expire -= (conn->hdr.ticks_xtal_to_start -
|
|
ticks_prepare_to_start);
|
|
}
|
|
|
|
/* compensate for instant_latency due to laziness */
|
|
conn_interval_old = instant_latency * conn->conn_interval;
|
|
latency = conn_interval_old /
|
|
conn->llcp.connection_update.interval;
|
|
conn_interval_new = latency *
|
|
conn->llcp.connection_update.interval;
|
|
if (conn_interval_new > conn_interval_old) {
|
|
ticks_at_expire +=
|
|
TICKER_US_TO_TICKS((conn_interval_new -
|
|
conn_interval_old) * 1250);
|
|
} else {
|
|
ticks_at_expire -=
|
|
TICKER_US_TO_TICKS((conn_interval_old -
|
|
conn_interval_new) * 1250);
|
|
}
|
|
conn->latency_prepare -= (instant_latency - latency);
|
|
|
|
/* calculate the offset, window widening and interval */
|
|
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->llcp.connection_update.interval * 1250;
|
|
periodic_us = conn_interval_us;
|
|
if (conn->role.slave.role != 0) {
|
|
conn->role.slave.window_widening_prepare_us -=
|
|
conn->role.slave.window_widening_periodic_us *
|
|
instant_latency;
|
|
|
|
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_prepare_us =
|
|
conn->llcp.connection_update.win_size * 1250;
|
|
conn->role.slave.ticks_to_offset = 0;
|
|
|
|
conn->role.slave.window_widening_prepare_us +=
|
|
conn->role.slave.window_widening_periodic_us *
|
|
latency;
|
|
if (conn->role.slave.window_widening_prepare_us >
|
|
conn->role.slave.window_widening_max_us) {
|
|
conn->role.slave.window_widening_prepare_us =
|
|
conn->role.slave.window_widening_max_us;
|
|
}
|
|
|
|
ticks_at_expire -=
|
|
TICKER_US_TO_TICKS(conn->role.slave.window_widening_periodic_us *
|
|
latency);
|
|
ticks_win_offset =
|
|
TICKER_US_TO_TICKS((conn->llcp.connection_update.win_offset_us /
|
|
1250) * 1250);
|
|
periodic_us -=
|
|
conn->role.slave.window_widening_periodic_us;
|
|
|
|
if (conn->llcp.connection_update.is_internal == 2) {
|
|
conn_update_req(conn);
|
|
}
|
|
} else {
|
|
ticks_win_offset =
|
|
TICKER_US_TO_TICKS(conn->llcp.connection_update.win_offset_us);
|
|
}
|
|
conn->conn_interval = conn->llcp.connection_update.interval;
|
|
conn->latency = conn->llcp.connection_update.latency;
|
|
conn->supervision_reload =
|
|
RADIO_CONN_EVENTS((conn->llcp.connection_update.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)
|
|
conn->apto_reload = RADIO_CONN_EVENTS((30 * 1000 * 1000),
|
|
conn_interval_us);
|
|
conn->appto_reload =
|
|
(conn->apto_reload > (conn->latency + 2)) ?
|
|
(conn->apto_reload - (conn->latency + 2)) :
|
|
conn->apto_reload;
|
|
#endif /* CONFIG_BLUETOOTH_CONTROLLER_LE_PING */
|
|
|
|
if (!conn->llcp.connection_update.is_internal) {
|
|
conn->supervision_expire = 0;
|
|
}
|
|
|
|
/* disable ticker job, in order to chain stop and start
|
|
* to avoid RTC being stopped if no tickers active.
|
|
*/
|
|
work_was_enabled = work_is_enabled(WORK_TICKER_JOB0_IRQ);
|
|
work_disable(WORK_TICKER_JOB0_IRQ);
|
|
|
|
/* start slave/master with new timings */
|
|
ticker_status =
|
|
ticker_stop(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER,
|
|
RADIO_TICKER_ID_FIRST_CONNECTION +
|
|
conn->handle, ticker_success_assert,
|
|
(void *)__LINE__);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
ticker_status =
|
|
ticker_start(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER,
|
|
RADIO_TICKER_ID_FIRST_CONNECTION +
|
|
conn->handle,
|
|
ticks_at_expire, ticks_win_offset,
|
|
TICKER_US_TO_TICKS(periodic_us),
|
|
TICKER_REMAINDER(periodic_us),
|
|
TICKER_NULL_LAZY,
|
|
(ticks_slot_offset + conn->hdr.ticks_slot),
|
|
(conn->role.slave.role != 0) ?
|
|
event_slave_prepare : event_master_prepare,
|
|
conn, ticker_success_assert,
|
|
(void *)__LINE__);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
|
|
/* enable ticker job, if disabled in this function */
|
|
if (work_was_enabled) {
|
|
work_enable(WORK_TICKER_JOB0_IRQ);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static inline void event_ch_map_prep(struct connection *conn,
|
|
uint16_t event_counter)
|
|
{
|
|
if (conn->llcp.channel_map.initiate) {
|
|
struct radio_pdu_node_tx *node_tx;
|
|
|
|
node_tx = mem_acquire(&_radio.pkt_tx_ctrl_free);
|
|
if (node_tx) {
|
|
struct pdu_data *pdu_ctrl_tx =
|
|
(struct pdu_data *)node_tx->pdu_data;
|
|
|
|
/* reset initiate flag */
|
|
conn->llcp.channel_map.initiate = 0;
|
|
|
|
/* set instant */
|
|
conn->llcp.channel_map.instant =
|
|
event_counter + conn->latency + 6;
|
|
|
|
/* place the channel map req packet as next in
|
|
* tx queue
|
|
*/
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_tx->len = offsetof(struct pdu_data_llctrl,
|
|
ctrldata) +
|
|
sizeof(struct pdu_data_llctrl_channel_map_req);
|
|
pdu_ctrl_tx->payload.llctrl.opcode =
|
|
PDU_DATA_LLCTRL_TYPE_CHANNEL_MAP_REQ;
|
|
memcpy(&pdu_ctrl_tx->payload.llctrl.
|
|
ctrldata.channel_map_req.chm[0],
|
|
&conn->llcp.channel_map.chm[0],
|
|
sizeof(pdu_ctrl_tx->payload.
|
|
llctrl.ctrldata.channel_map_req.chm));
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.channel_map_req.instant =
|
|
conn->llcp.channel_map.instant;
|
|
|
|
ctrl_tx_enqueue(conn, node_tx);
|
|
}
|
|
} else
|
|
if (((event_counter - conn->llcp.channel_map.instant) & 0xFFFF)
|
|
<= 0x7FFF) {
|
|
/* procedure request acked */
|
|
conn->llcp_ack = conn->llcp_req;
|
|
|
|
/* copy to active channel map */
|
|
memcpy(&conn->data_channel_map[0],
|
|
&conn->llcp.channel_map.chm[0],
|
|
sizeof(conn->data_channel_map));
|
|
conn->data_channel_count =
|
|
util_ones_count_get(&conn->data_channel_map[0],
|
|
sizeof(conn->data_channel_map));
|
|
}
|
|
|
|
}
|
|
|
|
static inline void event_enc_prep(struct connection *conn)
|
|
{
|
|
struct radio_pdu_node_tx *node_tx;
|
|
|
|
node_tx = mem_acquire(&_radio.pkt_tx_ctrl_free);
|
|
if (node_tx) {
|
|
struct pdu_data *pdu_ctrl_tx =
|
|
(struct pdu_data *)node_tx->pdu_data;
|
|
|
|
/* procedure request acked */
|
|
conn->llcp_ack = conn->llcp_req;
|
|
|
|
/* master sends encrypted enc start rsp in control priority */
|
|
if (conn->role.master.role == 0) {
|
|
/* calc the Session Key */
|
|
ecb_encrypt(&conn->llcp.encryption.ltk[0],
|
|
&conn->llcp.encryption.skd[0],
|
|
0, &conn->ccm_rx.key[0]);
|
|
|
|
/* copy the Session Key */
|
|
memcpy(&conn->ccm_tx.key[0], &conn->ccm_rx.key[0],
|
|
sizeof(conn->ccm_tx.key));
|
|
|
|
/* copy the IV */
|
|
memcpy(&conn->ccm_tx.iv[0], &conn->ccm_rx.iv[0],
|
|
sizeof(conn->ccm_tx.iv));
|
|
|
|
/* initialise counter */
|
|
conn->ccm_rx.counter = 0;
|
|
conn->ccm_tx.counter = 0;
|
|
|
|
/* set direction: slave to master = 0,
|
|
* master to slave = 1
|
|
*/
|
|
conn->ccm_rx.direction = 0;
|
|
conn->ccm_tx.direction = 1;
|
|
|
|
/* enable receive and transmit encryption */
|
|
conn->enc_rx = 1;
|
|
conn->enc_tx = 1;
|
|
|
|
/* send enc start resp */
|
|
start_enc_rsp_send(conn, pdu_ctrl_tx);
|
|
}
|
|
/* slave send reject ind or start enc req at control priority */
|
|
else if (!conn->pause_tx || conn->refresh) {
|
|
/* ll ctrl packet */
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
|
|
/* place the reject ind packet as next in tx queue */
|
|
if (conn->llcp.encryption.error_code) {
|
|
pdu_ctrl_tx->len =
|
|
offsetof(struct pdu_data_llctrl, ctrldata) +
|
|
sizeof(struct pdu_data_llctrl_reject_ind);
|
|
pdu_ctrl_tx->payload.llctrl.opcode =
|
|
PDU_DATA_LLCTRL_TYPE_REJECT_IND;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.reject_ind.error_code =
|
|
conn->llcp.encryption.error_code;
|
|
|
|
conn->llcp.encryption.error_code = 0;
|
|
}
|
|
/* place the start enc req packet as next in tx queue */
|
|
else {
|
|
#if !FAST_ENC_PROCEDURE
|
|
/* TODO BT Spec. text: may finalize the sending
|
|
* of additional data channel PDUs queued in the
|
|
* controller.
|
|
*/
|
|
enc_rsp_send(conn);
|
|
#endif
|
|
|
|
/* calc the Session Key */
|
|
ecb_encrypt(&conn->llcp.encryption.ltk[0],
|
|
&conn->llcp.encryption.skd[0], 0,
|
|
&conn->ccm_rx.key[0]);
|
|
|
|
/* copy the Session Key */
|
|
memcpy(&conn->ccm_tx.key[0],
|
|
&conn->ccm_rx.key[0],
|
|
sizeof(conn->ccm_tx.key));
|
|
|
|
/* copy the IV */
|
|
memcpy(&conn->ccm_tx.iv[0], &conn->ccm_rx.iv[0],
|
|
sizeof(conn->ccm_tx.iv));
|
|
|
|
/* initialise counter */
|
|
conn->ccm_rx.counter = 0;
|
|
conn->ccm_tx.counter = 0;
|
|
|
|
/* set direction: slave to master = 0,
|
|
* master to slave = 1
|
|
*/
|
|
conn->ccm_rx.direction = 1;
|
|
conn->ccm_tx.direction = 0;
|
|
|
|
/* enable receive encryption (transmit turned
|
|
* on when start enc resp from master is
|
|
* received)
|
|
*/
|
|
conn->enc_rx = 1;
|
|
|
|
/* prepare the start enc req */
|
|
pdu_ctrl_tx->len =
|
|
offsetof(struct pdu_data_llctrl,
|
|
ctrldata);
|
|
pdu_ctrl_tx->payload.llctrl.opcode =
|
|
PDU_DATA_LLCTRL_TYPE_START_ENC_REQ;
|
|
}
|
|
} else {
|
|
#if !FAST_ENC_PROCEDURE
|
|
/* enable transmit encryption */
|
|
_radio.conn_curr->enc_tx = 1;
|
|
|
|
start_enc_rsp_send(_radio.conn_curr, 0);
|
|
|
|
/* resume data packet rx and tx */
|
|
_radio.conn_curr->pause_rx = 0;
|
|
_radio.conn_curr->pause_tx = 0;
|
|
#else
|
|
/* Fast Enc implementation shall have enqueued the
|
|
* start enc rsp in the radio ISR itself, we should
|
|
* not get here.
|
|
*/
|
|
LL_ASSERT(0);
|
|
#endif
|
|
}
|
|
|
|
ctrl_tx_enqueue(conn, node_tx);
|
|
}
|
|
}
|
|
|
|
static inline void event_fex_prep(struct connection *conn)
|
|
{
|
|
struct radio_pdu_node_tx *node_tx;
|
|
|
|
node_tx = mem_acquire(&_radio.pkt_tx_ctrl_free);
|
|
if (node_tx) {
|
|
struct pdu_data *pdu_ctrl_tx =
|
|
(struct pdu_data *)node_tx->pdu_data;
|
|
|
|
/* procedure request acked */
|
|
conn->llcp_ack = conn->llcp_req;
|
|
|
|
/* use initial feature bitmap */
|
|
conn->llcp_features = RADIO_BLE_FEATURES;
|
|
|
|
/* place the feature exchange req packet as next in tx queue */
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_tx->len = offsetof(struct pdu_data_llctrl, ctrldata) +
|
|
sizeof(struct pdu_data_llctrl_feature_req);
|
|
pdu_ctrl_tx->payload.llctrl.opcode =
|
|
(conn->role.master.role == 0) ?
|
|
PDU_DATA_LLCTRL_TYPE_FEATURE_REQ :
|
|
PDU_DATA_LLCTRL_TYPE_SLAVE_FEATURE_REQ;
|
|
memset(&pdu_ctrl_tx->payload.llctrl.ctrldata.feature_req.features[0],
|
|
0x00,
|
|
sizeof(pdu_ctrl_tx->payload.llctrl.ctrldata.feature_req.features));
|
|
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.feature_req.features[0] =
|
|
conn->llcp_features;
|
|
|
|
ctrl_tx_enqueue(conn, node_tx);
|
|
|
|
/* Start Procedure Timeout (@todo this shall not replace
|
|
* terminate procedure)
|
|
*/
|
|
conn->procedure_expire = conn->procedure_reload;
|
|
}
|
|
|
|
}
|
|
|
|
static inline void event_vex_prep(struct connection *conn)
|
|
{
|
|
|
|
if (conn->llcp_version.tx == 0) {
|
|
struct radio_pdu_node_tx *node_tx;
|
|
|
|
node_tx = mem_acquire(&_radio.pkt_tx_ctrl_free);
|
|
if (node_tx) {
|
|
struct pdu_data *pdu_ctrl_tx =
|
|
(struct pdu_data *)node_tx->pdu_data;
|
|
|
|
/* procedure request acked */
|
|
conn->llcp_ack = conn->llcp_req;
|
|
|
|
/* set version ind tx-ed flag */
|
|
conn->llcp_version.tx = 1;
|
|
|
|
/* place the version ind packet as next in tx queue */
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_tx->len = offsetof(struct pdu_data_llctrl,
|
|
ctrldata) +
|
|
sizeof(struct pdu_data_llctrl_version_ind);
|
|
pdu_ctrl_tx->payload.llctrl.opcode =
|
|
PDU_DATA_LLCTRL_TYPE_VERSION_IND;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.version_ind.version_number =
|
|
RADIO_BLE_VERSION_NUMBER;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.version_ind.company_id =
|
|
RADIO_BLE_COMPANY_ID;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.version_ind.sub_version_number =
|
|
RADIO_BLE_SUB_VERSION_NUMBER;
|
|
|
|
ctrl_tx_enqueue(conn, node_tx);
|
|
|
|
/* Start Procedure Timeout (@todo this shall not
|
|
* replace terminate procedure)
|
|
*/
|
|
conn->procedure_expire = conn->procedure_reload;
|
|
}
|
|
} else if (conn->llcp_version.rx != 0) {
|
|
struct radio_pdu_node_rx *radio_pdu_node_rx;
|
|
struct pdu_data *pdu_ctrl_rx;
|
|
|
|
/* procedure request acked */
|
|
conn->llcp_ack = conn->llcp_req;
|
|
|
|
/* Prepare the rx packet structure */
|
|
radio_pdu_node_rx = packet_rx_reserve_get(2);
|
|
LL_ASSERT(radio_pdu_node_rx);
|
|
|
|
radio_pdu_node_rx->hdr.handle = conn->handle;
|
|
radio_pdu_node_rx->hdr.type = NODE_RX_TYPE_DC_PDU;
|
|
|
|
/* prepare version ind structure */
|
|
pdu_ctrl_rx = (struct pdu_data *)radio_pdu_node_rx->pdu_data;
|
|
pdu_ctrl_rx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_rx->len = offsetof(struct pdu_data_llctrl, ctrldata) +
|
|
sizeof(struct pdu_data_llctrl_version_ind);
|
|
pdu_ctrl_rx->payload.llctrl.opcode =
|
|
PDU_DATA_LLCTRL_TYPE_VERSION_IND;
|
|
pdu_ctrl_rx->payload.llctrl.ctrldata.version_ind.version_number =
|
|
conn->llcp_version.version_number;
|
|
pdu_ctrl_rx->payload.llctrl.ctrldata.version_ind.company_id =
|
|
conn->llcp_version.company_id;
|
|
pdu_ctrl_rx->payload.llctrl.ctrldata.version_ind.sub_version_number =
|
|
conn->llcp_version.sub_version_number;
|
|
|
|
/* enqueue version ind structure into rx queue */
|
|
packet_rx_enqueue();
|
|
} else {
|
|
/* tx-ed but no rx, and new request placed */
|
|
LL_ASSERT(0);
|
|
}
|
|
|
|
}
|
|
|
|
#if defined(CONFIG_BLUETOOTH_CONTROLLER_LE_PING)
|
|
static inline void event_ping_prep(struct connection *conn)
|
|
{
|
|
struct radio_pdu_node_tx *node_tx;
|
|
|
|
node_tx = mem_acquire(&_radio.pkt_tx_ctrl_free);
|
|
if (node_tx) {
|
|
struct pdu_data *pdu_ctrl_tx =
|
|
(struct pdu_data *)node_tx->pdu_data;
|
|
|
|
/* procedure request acked */
|
|
conn->llcp_ack = conn->llcp_req;
|
|
|
|
/* place the ping req packet as next in tx queue */
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_tx->len = offsetof(struct pdu_data_llctrl, ctrldata);
|
|
pdu_ctrl_tx->payload.llctrl.opcode =
|
|
PDU_DATA_LLCTRL_TYPE_PING_REQ;
|
|
|
|
ctrl_tx_enqueue(conn, node_tx);
|
|
|
|
/* Start Procedure Timeout (@todo this shall not replace
|
|
* terminate procedure)
|
|
*/
|
|
conn->procedure_expire = conn->procedure_reload;
|
|
}
|
|
|
|
}
|
|
#endif /* CONFIG_BLUETOOTH_CONTROLLER_LE_PING */
|
|
|
|
static inline void event_len_prep(struct connection *conn)
|
|
{
|
|
switch (conn->llcp_length.state) {
|
|
case LLCP_LENGTH_STATE_REQ:
|
|
{
|
|
struct pdu_data_llctrl_length_req_rsp *lr;
|
|
struct radio_pdu_node_tx *node_tx;
|
|
struct pdu_data *pdu_ctrl_tx;
|
|
uint16_t free_count_rx;
|
|
|
|
free_count_rx = packet_rx_acquired_count_get() +
|
|
mem_free_count_get(_radio.pkt_rx_data_free);
|
|
LL_ASSERT(free_count_rx <= 0xFF);
|
|
|
|
if (_radio.packet_rx_data_count != free_count_rx) {
|
|
break;
|
|
}
|
|
|
|
node_tx = mem_acquire(&_radio.pkt_tx_ctrl_free);
|
|
if (!node_tx) {
|
|
break;
|
|
}
|
|
|
|
/* wait for resp before completing the procedure */
|
|
conn->llcp_length.state = LLCP_LENGTH_STATE_ACK_WAIT;
|
|
|
|
/* set the default tx octets to requested value */
|
|
conn->default_tx_octets = conn->llcp_length.tx_octets;
|
|
|
|
/* place the length req packet as next in tx queue */
|
|
pdu_ctrl_tx = (struct pdu_data *) node_tx->pdu_data;
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_tx->len = offsetof(struct pdu_data_llctrl, ctrldata) +
|
|
sizeof(struct pdu_data_llctrl_length_req_rsp);
|
|
pdu_ctrl_tx->payload.llctrl.opcode =
|
|
PDU_DATA_LLCTRL_TYPE_LENGTH_REQ;
|
|
|
|
lr = (struct pdu_data_llctrl_length_req_rsp *)
|
|
&pdu_ctrl_tx->payload.llctrl.ctrldata.length_req;
|
|
lr->max_rx_octets = RADIO_LL_LENGTH_OCTETS_RX_MAX;
|
|
lr->max_rx_time = ((RADIO_LL_LENGTH_OCTETS_RX_MAX + 14) << 3);
|
|
lr->max_tx_octets = conn->default_tx_octets;
|
|
lr->max_tx_time = ((conn->default_tx_octets + 14) << 3);
|
|
|
|
ctrl_tx_enqueue(conn, node_tx);
|
|
|
|
/* Start Procedure Timeout (@todo this shall not replace
|
|
* terminate procedure).
|
|
*/
|
|
conn->procedure_expire = conn->procedure_reload;
|
|
}
|
|
break;
|
|
|
|
case LLCP_LENGTH_STATE_RESIZE:
|
|
{
|
|
struct pdu_data_llctrl_length_req_rsp *lr;
|
|
struct radio_pdu_node_rx *node_rx;
|
|
struct pdu_data *pdu_ctrl_rx;
|
|
uint16_t packet_rx_data_size;
|
|
uint16_t free_count_conn;
|
|
uint16_t free_count_rx;
|
|
|
|
/* Ensure the rx pool is not in use.
|
|
* This is important to be able to re-size the pool
|
|
* ensuring there is no chance that an operation on
|
|
* the pool is pre-empted causing memory corruption.
|
|
*/
|
|
free_count_rx = packet_rx_acquired_count_get() +
|
|
mem_free_count_get(_radio.pkt_rx_data_free);
|
|
LL_ASSERT(free_count_rx <= 0xFF);
|
|
|
|
if (_radio.packet_rx_data_count != free_count_rx) {
|
|
/** TODO another role instance has obtained
|
|
* memory from rx pool.
|
|
*/
|
|
LL_ASSERT(0);
|
|
}
|
|
|
|
/* Procedure complete */
|
|
conn->llcp_length.ack = conn->llcp_length.req;
|
|
conn->procedure_expire = 0;
|
|
|
|
/* resume data packet tx */
|
|
_radio.conn_curr->pause_tx = 0;
|
|
|
|
/* Use the new rx octets in the connection */
|
|
conn->max_rx_octets = conn->llcp_length.rx_octets;
|
|
|
|
/** TODO This design is exception as memory initialization
|
|
* and allocation is done in radio context here, breaking the
|
|
* rule that the rx buffers are allocated in application
|
|
* context.
|
|
* Design mem_* such that mem_init could interrupt mem_acquire,
|
|
* when the pool is full?
|
|
*/
|
|
free_count_conn = mem_free_count_get(_radio.conn_free);
|
|
if (_radio.advertiser.conn) {
|
|
free_count_conn++;
|
|
}
|
|
if (_radio.observer.conn) {
|
|
free_count_conn++;
|
|
}
|
|
packet_rx_data_size = ALIGN4(offsetof(struct radio_pdu_node_rx,
|
|
pdu_data) +
|
|
offsetof(struct pdu_data,
|
|
payload) +
|
|
conn->max_rx_octets);
|
|
/* Resize to lower or higher size if this is the only active
|
|
* connection, or resize to only higher sizes as there may be
|
|
* other connections using the current size.
|
|
*/
|
|
if (((free_count_conn + 1) == _radio.connection_count) ||
|
|
(packet_rx_data_size > _radio.packet_rx_data_size)) {
|
|
/* as rx mem is to be re-sized, release acquired
|
|
* memq link.
|
|
*/
|
|
while (_radio.packet_rx_acquire !=
|
|
_radio.packet_rx_last) {
|
|
|
|
struct radio_pdu_node_rx *node_rx;
|
|
|
|
if (_radio.packet_rx_acquire == 0) {
|
|
_radio.packet_rx_acquire =
|
|
_radio.packet_rx_count - 1;
|
|
} else {
|
|
_radio.packet_rx_acquire -= 1;
|
|
}
|
|
|
|
node_rx = _radio.packet_rx[
|
|
_radio.packet_rx_acquire];
|
|
mem_release(node_rx->hdr.onion.link,
|
|
&_radio.link_rx_free);
|
|
|
|
LL_ASSERT(_radio.link_rx_data_quota <
|
|
(_radio.packet_rx_count - 1));
|
|
_radio.link_rx_data_quota++;
|
|
|
|
/* no need to release node_rx as we mem_init
|
|
* later down in code.
|
|
*/
|
|
}
|
|
|
|
/* calculate the new rx node size and new count */
|
|
if (conn->max_rx_octets < (RADIO_ACPDU_SIZE_MAX + 1)) {
|
|
_radio.packet_rx_data_size =
|
|
ALIGN4(offsetof(struct radio_pdu_node_rx,
|
|
pdu_data) +
|
|
(RADIO_ACPDU_SIZE_MAX + 1));
|
|
} else {
|
|
_radio.packet_rx_data_size =
|
|
packet_rx_data_size;
|
|
}
|
|
_radio.packet_rx_data_count =
|
|
_radio.packet_rx_data_pool_size /
|
|
_radio.packet_rx_data_size;
|
|
LL_ASSERT(_radio.packet_rx_data_count);
|
|
|
|
/* re-size (re-init) the free rx pool */
|
|
mem_init(_radio.pkt_rx_data_pool,
|
|
_radio.packet_rx_data_size,
|
|
_radio.packet_rx_data_count,
|
|
&_radio.pkt_rx_data_free);
|
|
|
|
/* allocate the rx queue include one extra for
|
|
* generating event in following lines.
|
|
*/
|
|
packet_rx_allocate(4);
|
|
}
|
|
|
|
/* Prepare the rx packet structure */
|
|
node_rx = packet_rx_reserve_get(2);
|
|
LL_ASSERT(node_rx);
|
|
node_rx->hdr.handle = conn->handle;
|
|
node_rx->hdr.type = NODE_RX_TYPE_DC_PDU;
|
|
|
|
/* prepare version ind structure */
|
|
pdu_ctrl_rx = (struct pdu_data *) node_rx->pdu_data;
|
|
pdu_ctrl_rx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_rx->len = offsetof(struct pdu_data_llctrl,
|
|
ctrldata) +
|
|
sizeof(struct pdu_data_llctrl_length_req_rsp);
|
|
pdu_ctrl_rx->payload.llctrl.opcode =
|
|
PDU_DATA_LLCTRL_TYPE_LENGTH_RSP;
|
|
|
|
lr = (struct pdu_data_llctrl_length_req_rsp *)
|
|
&pdu_ctrl_rx->payload.llctrl.ctrldata.length_req;
|
|
lr->max_rx_octets = conn->max_rx_octets;
|
|
lr->max_rx_time = ((conn->max_rx_octets + 14) << 3);
|
|
lr->max_tx_octets = conn->max_tx_octets;
|
|
lr->max_tx_time = ((conn->max_tx_octets + 14) << 3);
|
|
|
|
/* enqueue version ind structure into rx queue */
|
|
packet_rx_enqueue();
|
|
}
|
|
break;
|
|
|
|
case LLCP_LENGTH_STATE_ACK_WAIT:
|
|
case LLCP_LENGTH_STATE_RSP_WAIT:
|
|
/* no nothing */
|
|
break;
|
|
|
|
default:
|
|
LL_ASSERT(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void event_connection_prepare(uint32_t ticks_at_expire,
|
|
uint32_t remainder, uint16_t lazy,
|
|
struct connection *conn)
|
|
{
|
|
uint16_t event_counter;
|
|
|
|
_radio.ticker_id_prepare =
|
|
RADIO_TICKER_ID_FIRST_CONNECTION + conn->handle;
|
|
|
|
/* Calc window widening */
|
|
if (conn->role.slave.role != 0) {
|
|
conn->role.slave.window_widening_prepare_us +=
|
|
conn->role.slave.window_widening_periodic_us * (lazy + 1);
|
|
if (conn->role.slave.window_widening_prepare_us >
|
|
conn->role.slave.window_widening_max_us) {
|
|
conn->role.slave.window_widening_prepare_us =
|
|
conn->role.slave.window_widening_max_us;
|
|
}
|
|
}
|
|
|
|
/* save the latency for use in event */
|
|
conn->latency_prepare += lazy;
|
|
|
|
/* calc current event counter value */
|
|
event_counter = conn->event_counter + conn->latency_prepare;
|
|
|
|
/* check if procedure is requested */
|
|
if (conn->llcp_ack != conn->llcp_req) {
|
|
/* Stop previous event, to avoid Radio DMA corrupting the
|
|
* rx queue
|
|
*/
|
|
event_stop(0, 0, 0, (void *)STATE_ABORT);
|
|
|
|
switch (conn->llcp_type) {
|
|
case LLCP_CONNECTION_UPDATE:
|
|
if (event_conn_update_prep(conn, event_counter,
|
|
ticks_at_expire) == 0) {
|
|
return;
|
|
}
|
|
break;
|
|
case LLCP_CHANNEL_MAP:
|
|
event_ch_map_prep(conn, event_counter);
|
|
break;
|
|
|
|
case LLCP_ENCRYPTION:
|
|
event_enc_prep(conn);
|
|
break;
|
|
|
|
case LLCP_FEATURE_EXCHANGE:
|
|
event_fex_prep(conn);
|
|
break;
|
|
|
|
case LLCP_VERSION_EXCHANGE:
|
|
event_vex_prep(conn);
|
|
break;
|
|
|
|
#if defined(CONFIG_BLUETOOTH_CONTROLLER_LE_PING)
|
|
case LLCP_PING:
|
|
event_ping_prep(conn);
|
|
break;
|
|
#endif /* CONFIG_BLUETOOTH_CONTROLLER_LE_PING */
|
|
|
|
default:
|
|
LL_ASSERT(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Terminate Procedure Request */
|
|
if (conn->llcp_terminate.ack != conn->llcp_terminate.req) {
|
|
struct radio_pdu_node_tx *node_tx;
|
|
|
|
/* Stop previous event, to avoid Radio DMA corrupting the rx
|
|
* queue
|
|
*/
|
|
event_stop(0, 0, 0, (void *)STATE_ABORT);
|
|
|
|
node_tx = mem_acquire(&_radio.pkt_tx_ctrl_free);
|
|
if (node_tx) {
|
|
struct pdu_data *pdu_ctrl_tx =
|
|
(struct pdu_data *)node_tx->pdu_data;
|
|
|
|
/* Terminate Procedure acked */
|
|
conn->llcp_terminate.ack = conn->llcp_terminate.req;
|
|
|
|
/* place the terminate ind packet in tx queue */
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_tx->len =
|
|
offsetof(struct pdu_data_llctrl, ctrldata) +
|
|
sizeof(struct pdu_data_llctrl_terminate_ind);
|
|
pdu_ctrl_tx->payload.llctrl.opcode =
|
|
PDU_DATA_LLCTRL_TYPE_TERMINATE_IND;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.terminate_ind.
|
|
error_code = conn->llcp_terminate.reason_own;
|
|
|
|
ctrl_tx_enqueue(conn, node_tx);
|
|
|
|
/* Terminate Procedure timeout is started, will
|
|
* replace any other timeout running
|
|
*/
|
|
conn->procedure_expire = conn->procedure_reload;
|
|
}
|
|
}
|
|
|
|
/* check if procedure is requested */
|
|
if (conn->llcp_length.ack != conn->llcp_length.req) {
|
|
/* Stop previous event, to avoid Radio DMA corrupting the
|
|
* rx queue
|
|
*/
|
|
event_stop(0, 0, 0, (void *)STATE_ABORT);
|
|
|
|
/* handle DLU state machine */
|
|
event_len_prep(conn);
|
|
}
|
|
|
|
/* Setup XTAL startup and radio active events */
|
|
event_common_prepare(ticks_at_expire, remainder,
|
|
&conn->hdr.ticks_xtal_to_start,
|
|
&conn->hdr.ticks_active_to_start,
|
|
conn->hdr.ticks_preempt_to_start,
|
|
(RADIO_TICKER_ID_FIRST_CONNECTION + conn->handle),
|
|
(conn->role.slave.role != 0) ? event_slave : event_master,
|
|
conn);
|
|
|
|
/* store the next event counter value */
|
|
conn->event_counter = event_counter + 1;
|
|
}
|
|
|
|
static void connection_configure(struct connection *conn)
|
|
{
|
|
uint8_t phy;
|
|
|
|
phy = RADIO_PHY_CONN;
|
|
adv_obs_conn_configure(phy);
|
|
radio_aa_set(conn->access_addr);
|
|
radio_crc_configure(((0x5bUL) | ((0x06UL) << 8) | ((0x00UL) << 16)),
|
|
(((uint32_t)conn->crc_init[2] << 16) |
|
|
((uint32_t)conn->crc_init[1] << 8) |
|
|
((uint32_t)conn->crc_init[0])));
|
|
}
|
|
|
|
static void event_slave_prepare(uint32_t ticks_at_expire, uint32_t remainder,
|
|
uint16_t lazy, void *context)
|
|
{
|
|
DEBUG_RADIO_PREPARE_S(1);
|
|
|
|
event_connection_prepare(ticks_at_expire, remainder, lazy, context);
|
|
|
|
DEBUG_RADIO_PREPARE_S(0);
|
|
}
|
|
|
|
static void event_slave(uint32_t ticks_at_expire, uint32_t remainder,
|
|
uint16_t lazy, void *context)
|
|
{
|
|
struct connection *conn;
|
|
uint8_t data_channel_use;
|
|
uint32_t remainder_us;
|
|
|
|
ARG_UNUSED(remainder);
|
|
ARG_UNUSED(lazy);
|
|
|
|
DEBUG_RADIO_START_S(1);
|
|
|
|
LL_ASSERT(_radio.role == ROLE_NONE);
|
|
|
|
conn = (struct connection *)context;
|
|
LL_ASSERT(_radio.ticker_id_prepare ==
|
|
(RADIO_TICKER_ID_FIRST_CONNECTION + conn->handle));
|
|
|
|
_radio.role = ROLE_SLAVE;
|
|
_radio.state = STATE_RX;
|
|
_radio.ticker_id_prepare = 0;
|
|
_radio.ticker_id_event =
|
|
(RADIO_TICKER_ID_FIRST_CONNECTION + conn->handle);
|
|
_radio.ticks_anchor = ticks_at_expire;
|
|
_radio.packet_counter = 0;
|
|
_radio.crc_expire = 0;
|
|
|
|
_radio.conn_curr = conn;
|
|
|
|
conn->latency_event = conn->latency_prepare;
|
|
conn->latency_prepare = 0;
|
|
|
|
connection_configure(conn);
|
|
|
|
rx_packet_set(conn, (struct pdu_data *)
|
|
_radio.packet_rx[_radio.packet_rx_last]->pdu_data);
|
|
|
|
radio_switch_complete_and_tx();
|
|
radio_rssi_measure();
|
|
|
|
/* Setup Radio Channel */
|
|
data_channel_use = channel_calc(&conn->data_channel_use,
|
|
conn->data_channel_hop,
|
|
conn->latency_event,
|
|
&conn->data_channel_map[0],
|
|
conn->data_channel_count);
|
|
channel_set(data_channel_use);
|
|
|
|
/* current window widening */
|
|
conn->role.slave.window_widening_event_us +=
|
|
conn->role.slave.window_widening_prepare_us;
|
|
conn->role.slave.window_widening_prepare_us = 0;
|
|
if (conn->role.slave.window_widening_event_us >
|
|
conn->role.slave.window_widening_max_us) {
|
|
conn->role.slave.window_widening_event_us =
|
|
conn->role.slave.window_widening_max_us;
|
|
}
|
|
|
|
/* current window size */
|
|
conn->role.slave.window_size_event_us +=
|
|
conn->role.slave.window_size_prepare_us;
|
|
conn->role.slave.window_size_prepare_us = 0;
|
|
|
|
remainder_us =
|
|
radio_tmr_start(0, ticks_at_expire +
|
|
TICKER_US_TO_TICKS(RADIO_TICKER_START_PART_US),
|
|
_radio.remainder_anchor);
|
|
radio_tmr_aa_capture();
|
|
radio_tmr_hcto_configure(0 + remainder_us + RADIO_RX_READY_DELAY_US +
|
|
(conn->role.slave.window_widening_event_us << 1) +
|
|
/* +/- 16 us of BLE jitter plus own implementation drift unit of 30.51 us. */
|
|
(RADIO_TICKER_JITTER_US << 2) +
|
|
RADIO_PREAMBLE_TO_ADDRESS_US +
|
|
conn->role.slave.window_size_event_us);
|
|
radio_tmr_end_capture();
|
|
|
|
#if (XTAL_ADVANCED && (RADIO_TICKER_PREEMPT_PART_US \
|
|
<= RADIO_TICKER_PREEMPT_PART_MIN_US))
|
|
/* check if preempt to start has changed */
|
|
if (preempt_calc(&conn->hdr, (RADIO_TICKER_ID_FIRST_CONNECTION +
|
|
conn->handle), ticks_at_expire) != 0) {
|
|
_radio.state = STATE_STOP;
|
|
radio_disable();
|
|
} else
|
|
#endif
|
|
|
|
/* Ticker Job Silence */
|
|
#if (WORK_TICKER_WORKER0_IRQ_PRIORITY == WORK_TICKER_JOB0_IRQ_PRIORITY)
|
|
{
|
|
uint32_t ticker_status;
|
|
|
|
ticker_status =
|
|
ticker_job_idle_get(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER,
|
|
ticker_job_disable, 0);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
}
|
|
#endif
|
|
|
|
/* Route the tx packet to respective connections */
|
|
packet_tx_enqueue(2);
|
|
|
|
DEBUG_RADIO_START_S(0);
|
|
}
|
|
|
|
static void event_master_prepare(uint32_t ticks_at_expire, uint32_t remainder,
|
|
uint16_t lazy, void *context)
|
|
{
|
|
DEBUG_RADIO_PREPARE_M(1);
|
|
|
|
event_connection_prepare(ticks_at_expire, remainder, lazy, context);
|
|
|
|
DEBUG_RADIO_PREPARE_M(0);
|
|
}
|
|
|
|
static void event_master(uint32_t ticks_at_expire, uint32_t remainder,
|
|
uint16_t lazy, void *context)
|
|
{
|
|
struct connection *conn;
|
|
struct pdu_data *pdu_data_tx;
|
|
uint8_t data_channel_use;
|
|
|
|
ARG_UNUSED(remainder);
|
|
ARG_UNUSED(lazy);
|
|
|
|
DEBUG_RADIO_START_M(1);
|
|
|
|
LL_ASSERT(_radio.role == ROLE_NONE);
|
|
|
|
conn = (struct connection *)context;
|
|
LL_ASSERT(_radio.ticker_id_prepare ==
|
|
(RADIO_TICKER_ID_FIRST_CONNECTION + conn->handle));
|
|
|
|
_radio.role = ROLE_MASTER;
|
|
_radio.state = STATE_TX;
|
|
_radio.ticker_id_prepare = 0;
|
|
_radio.ticker_id_event =
|
|
(RADIO_TICKER_ID_FIRST_CONNECTION + conn->handle);
|
|
_radio.ticks_anchor = ticks_at_expire;
|
|
_radio.packet_counter = 0;
|
|
_radio.crc_expire = 0;
|
|
|
|
_radio.conn_curr = conn;
|
|
|
|
conn->latency_event = conn->latency_prepare;
|
|
conn->latency_prepare = 0;
|
|
|
|
/* Route the tx packet to respective connections */
|
|
packet_tx_enqueue(2);
|
|
|
|
/* prepare transmit packet */
|
|
prepare_pdu_data_tx(conn, &pdu_data_tx);
|
|
|
|
pdu_data_tx->sn = conn->sn;
|
|
pdu_data_tx->nesn = conn->nesn;
|
|
|
|
connection_configure(conn);
|
|
|
|
tx_packet_set(conn, pdu_data_tx);
|
|
radio_switch_complete_and_rx();
|
|
|
|
/* Setup Radio Channel */
|
|
data_channel_use = channel_calc(&conn->data_channel_use,
|
|
conn->data_channel_hop,
|
|
conn->latency_event,
|
|
&conn->data_channel_map[0],
|
|
conn->data_channel_count);
|
|
channel_set(data_channel_use);
|
|
|
|
/* normal connection! */
|
|
#if SILENT_CONNECTION
|
|
if ((!conn->empty) || (pdu_data_tx->md) ||
|
|
((conn->supervision_expire != 0) &&
|
|
(conn->supervision_expire <= 6)) ||
|
|
((conn->role.master.connect_expire != 0) &&
|
|
(conn->role.master.connect_expire <= 6)))
|
|
#endif
|
|
{
|
|
radio_tmr_start(1, ticks_at_expire +
|
|
TICKER_US_TO_TICKS(RADIO_TICKER_START_PART_US),
|
|
_radio.remainder_anchor);
|
|
radio_tmr_end_capture();
|
|
}
|
|
#if SILENT_CONNECTION
|
|
/* silent connection! */
|
|
else {
|
|
uint32_t remainder_us;
|
|
|
|
/* start in RX state */
|
|
_radio.state = STATE_RX;
|
|
_radio.packet_counter = 0xFF;
|
|
|
|
rx_packet_set(conn, (struct pdu_data *)_radio.
|
|
packet_rx[_radio.packet_rx_last]->pdu_data);
|
|
radio_switch_complete_and_tx();
|
|
|
|
/* setup pkticker and hcto */
|
|
remainder_us =
|
|
radio_tmr_start(0, ticks_at_expire +
|
|
TICKER_US_TO_TICKS(RADIO_TICKER_START_PART_US),
|
|
_radio.remainder_anchor);
|
|
radio_tmr_aa_capture();
|
|
#define QUICK_FIX_EXTRA_WINDOW 230
|
|
radio_tmr_hcto_configure(0 + remainder_us +
|
|
RADIO_TX_READY_DELAY_US + 230 +
|
|
RADIO_PREAMBLE_TO_ADDRESS_US +
|
|
QUICK_FIX_EXTRA_WINDOW);
|
|
#undef QUICK_FIX_EXTRA_WINDOW
|
|
}
|
|
#endif
|
|
|
|
#if (XTAL_ADVANCED && (RADIO_TICKER_PREEMPT_PART_US \
|
|
<= RADIO_TICKER_PREEMPT_PART_MIN_US))
|
|
/* check if preempt to start has changed */
|
|
if (0 !=
|
|
preempt_calc(&conn->hdr, (RADIO_TICKER_ID_FIRST_CONNECTION +
|
|
conn->handle), ticks_at_expire)) {
|
|
_radio.state = STATE_STOP;
|
|
radio_disable();
|
|
} else
|
|
#endif
|
|
|
|
/* Ticker Job Silence */
|
|
#if (WORK_TICKER_WORKER0_IRQ_PRIORITY == WORK_TICKER_JOB0_IRQ_PRIORITY)
|
|
{
|
|
uint32_t ticker_status;
|
|
|
|
ticker_status =
|
|
ticker_job_idle_get(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER,
|
|
ticker_job_disable, 0);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
}
|
|
#endif
|
|
|
|
DEBUG_RADIO_START_M(0);
|
|
}
|
|
|
|
static void rx_packet_set(struct connection *conn, struct pdu_data *pdu_data_rx)
|
|
{
|
|
uint8_t phy;
|
|
|
|
phy = RADIO_PHY_CONN;
|
|
if (conn->enc_rx) {
|
|
radio_pkt_configure(phy, 8, (conn->max_rx_octets + 4));
|
|
|
|
radio_pkt_rx_set(radio_ccm_rx_pkt_set(&conn->ccm_rx,
|
|
pdu_data_rx));
|
|
} else {
|
|
radio_pkt_configure(phy, 8, conn->max_rx_octets);
|
|
|
|
radio_pkt_rx_set(pdu_data_rx);
|
|
}
|
|
}
|
|
|
|
static void tx_packet_set(struct connection *conn,
|
|
struct pdu_data *pdu_data_tx)
|
|
{
|
|
uint8_t phy;
|
|
|
|
phy = RADIO_PHY_CONN;
|
|
if (conn->enc_tx) {
|
|
radio_pkt_configure(phy, 8, (conn->max_tx_octets + 4));
|
|
|
|
radio_pkt_tx_set(radio_ccm_tx_pkt_set(&conn->ccm_tx,
|
|
pdu_data_tx));
|
|
} else {
|
|
radio_pkt_configure(phy, 8, conn->max_tx_octets);
|
|
|
|
radio_pkt_tx_set(pdu_data_tx);
|
|
}
|
|
}
|
|
|
|
static void prepare_pdu_data_tx(struct connection *conn,
|
|
struct pdu_data **pdu_data_tx)
|
|
{
|
|
struct pdu_data *_pdu_data_tx;
|
|
|
|
/*@FIXME: assign before checking first 3 conditions */
|
|
_pdu_data_tx = (struct pdu_data *)conn->pkt_tx_head->pdu_data;
|
|
|
|
if ((conn->empty != 0) || /* empty packet */
|
|
/* no ctrl or data packet */
|
|
(conn->pkt_tx_head == 0) ||
|
|
/* data tx paused, only control packets allowed */
|
|
((conn->pause_tx) && (_pdu_data_tx != 0) &&
|
|
(_pdu_data_tx->len != 0) &&
|
|
((_pdu_data_tx->ll_id != PDU_DATA_LLID_CTRL) ||
|
|
((conn->role.master.role == 0) &&
|
|
(((conn->refresh == 0) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode !=
|
|
PDU_DATA_LLCTRL_TYPE_TERMINATE_IND) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode != PDU_DATA_LLCTRL_TYPE_START_ENC_RSP) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode != PDU_DATA_LLCTRL_TYPE_REJECT_IND) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode != PDU_DATA_LLCTRL_TYPE_REJECT_IND_EXT)) ||
|
|
((conn->refresh != 0) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode != PDU_DATA_LLCTRL_TYPE_TERMINATE_IND) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode != PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode != PDU_DATA_LLCTRL_TYPE_ENC_REQ) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode != PDU_DATA_LLCTRL_TYPE_START_ENC_RSP) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode != PDU_DATA_LLCTRL_TYPE_REJECT_IND) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode != PDU_DATA_LLCTRL_TYPE_REJECT_IND_EXT)))) ||
|
|
((conn->role.slave.role != 0) &&
|
|
(((conn->refresh == 0) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode != PDU_DATA_LLCTRL_TYPE_TERMINATE_IND) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode != PDU_DATA_LLCTRL_TYPE_START_ENC_REQ) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode != PDU_DATA_LLCTRL_TYPE_START_ENC_RSP) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode != PDU_DATA_LLCTRL_TYPE_REJECT_IND) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode != PDU_DATA_LLCTRL_TYPE_REJECT_IND_EXT)) ||
|
|
((conn->refresh != 0) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode != PDU_DATA_LLCTRL_TYPE_TERMINATE_IND) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode != PDU_DATA_LLCTRL_TYPE_ENC_RSP) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode != PDU_DATA_LLCTRL_TYPE_START_ENC_REQ) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode != PDU_DATA_LLCTRL_TYPE_START_ENC_RSP) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode != PDU_DATA_LLCTRL_TYPE_REJECT_IND) &&
|
|
(_pdu_data_tx->payload.llctrl.opcode != PDU_DATA_LLCTRL_TYPE_REJECT_IND_EXT))))))) {
|
|
_pdu_data_tx = empty_tx_enqueue(conn);
|
|
} else {
|
|
_pdu_data_tx =
|
|
(struct pdu_data *)(conn->pkt_tx_head->pdu_data +
|
|
conn->packet_tx_head_offset);
|
|
|
|
if (!conn->packet_tx_head_len) {
|
|
conn->packet_tx_head_len = _pdu_data_tx->len;
|
|
}
|
|
|
|
if (conn->packet_tx_head_offset) {
|
|
_pdu_data_tx->ll_id = PDU_DATA_LLID_DATA_CONTINUE;
|
|
}
|
|
|
|
_pdu_data_tx->len = conn->packet_tx_head_len -
|
|
conn->packet_tx_head_offset;
|
|
_pdu_data_tx->md = 0;
|
|
|
|
if (_pdu_data_tx->len > conn->max_tx_octets) {
|
|
_pdu_data_tx->len = conn->max_tx_octets;
|
|
_pdu_data_tx->md = 1;
|
|
}
|
|
|
|
if (conn->pkt_tx_head->next) {
|
|
_pdu_data_tx->md = 1;
|
|
}
|
|
}
|
|
|
|
_pdu_data_tx->rfu0 = 0;
|
|
_pdu_data_tx->resv = 0;
|
|
|
|
*pdu_data_tx = _pdu_data_tx;
|
|
}
|
|
|
|
static void packet_rx_allocate(uint8_t max)
|
|
{
|
|
uint8_t acquire;
|
|
|
|
if (max > _radio.link_rx_data_quota) {
|
|
max = _radio.link_rx_data_quota;
|
|
}
|
|
|
|
acquire = _radio.packet_rx_acquire + 1;
|
|
if (acquire == _radio.packet_rx_count) {
|
|
acquire = 0;
|
|
}
|
|
|
|
while ((max--) && (acquire != _radio.packet_rx_last)) {
|
|
void *link;
|
|
struct radio_pdu_node_rx *radio_pdu_node_rx;
|
|
|
|
link = mem_acquire(&_radio.link_rx_free);
|
|
if (!link) {
|
|
break;
|
|
}
|
|
|
|
radio_pdu_node_rx = mem_acquire(&_radio.pkt_rx_data_free);
|
|
if (!radio_pdu_node_rx) {
|
|
mem_release(link, &_radio.link_rx_free);
|
|
break;
|
|
}
|
|
|
|
radio_pdu_node_rx->hdr.onion.link = link;
|
|
|
|
_radio.packet_rx[_radio.packet_rx_acquire] = radio_pdu_node_rx;
|
|
_radio.packet_rx_acquire = acquire;
|
|
|
|
acquire = _radio.packet_rx_acquire + 1;
|
|
if (acquire == _radio.packet_rx_count) {
|
|
acquire = 0;
|
|
}
|
|
|
|
_radio.link_rx_data_quota--;
|
|
}
|
|
}
|
|
|
|
static uint8_t packet_rx_acquired_count_get(void)
|
|
{
|
|
if (_radio.packet_rx_acquire >=
|
|
_radio.packet_rx_last) {
|
|
return (_radio.packet_rx_acquire -
|
|
_radio.packet_rx_last);
|
|
} else {
|
|
return (_radio.packet_rx_count -
|
|
_radio.packet_rx_last +
|
|
_radio.packet_rx_acquire);
|
|
}
|
|
}
|
|
|
|
static struct radio_pdu_node_rx *packet_rx_reserve_get(uint8_t count)
|
|
{
|
|
struct radio_pdu_node_rx *radio_pdu_node_rx;
|
|
|
|
if (_radio.packet_rx_last > _radio.packet_rx_acquire) {
|
|
if (count >
|
|
((_radio.packet_rx_count - _radio.packet_rx_last) +
|
|
_radio.packet_rx_acquire)) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
if (count >
|
|
(_radio.packet_rx_acquire - _radio.packet_rx_last)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
radio_pdu_node_rx = _radio.packet_rx[_radio.packet_rx_last];
|
|
radio_pdu_node_rx->hdr.type = NODE_RX_TYPE_DC_PDU;
|
|
|
|
return radio_pdu_node_rx;
|
|
}
|
|
|
|
static void event_callback(void)
|
|
{
|
|
static struct work work_event_callback = { 0, 0, 0,
|
|
WORK_TICKER_JOB0_IRQ, (work_fp) radio_event_callback, 0 };
|
|
|
|
work_schedule(&work_event_callback, 1);
|
|
}
|
|
|
|
static void packet_rx_enqueue(void)
|
|
{
|
|
void *link;
|
|
struct radio_pdu_node_rx *radio_pdu_node_rx;
|
|
uint8_t last;
|
|
|
|
LL_ASSERT(_radio.packet_rx_last != _radio.packet_rx_acquire);
|
|
|
|
/* Remember the rx node and acquired link mem */
|
|
radio_pdu_node_rx = _radio.packet_rx[_radio.packet_rx_last];
|
|
link = radio_pdu_node_rx->hdr.onion.link;
|
|
|
|
/* serialize release queue with rx queue by storing reference to last
|
|
* element in release queue
|
|
*/
|
|
radio_pdu_node_rx->hdr.onion.packet_release_last =
|
|
_radio.packet_release_last;
|
|
|
|
/* dequeue from acquired rx queue */
|
|
last = _radio.packet_rx_last + 1;
|
|
if (last == _radio.packet_rx_count) {
|
|
last = 0;
|
|
}
|
|
_radio.packet_rx_last = last;
|
|
|
|
/* Enqueue into event-cum-data queue */
|
|
link = memq_enqueue(radio_pdu_node_rx, link,
|
|
(void *)&_radio.link_rx_tail);
|
|
LL_ASSERT(link);
|
|
|
|
/* callback to trigger application action */
|
|
event_callback();
|
|
}
|
|
|
|
static void packet_tx_enqueue(uint8_t max)
|
|
{
|
|
while ((max--) && (_radio.packet_tx_first != _radio.packet_tx_last)) {
|
|
struct pdu_data_q_tx *pdu_data_q_tx;
|
|
struct radio_pdu_node_tx *node_tx_new;
|
|
struct connection *conn;
|
|
uint8_t first;
|
|
|
|
pdu_data_q_tx = &_radio.pkt_tx[_radio.packet_tx_first];
|
|
node_tx_new = pdu_data_q_tx->node_tx;
|
|
node_tx_new->next = NULL;
|
|
conn = mem_get(_radio.conn_pool, CONNECTION_T_SIZE,
|
|
pdu_data_q_tx->handle);
|
|
|
|
if (conn->handle == pdu_data_q_tx->handle) {
|
|
if (conn->pkt_tx_data == 0) {
|
|
conn->pkt_tx_data = node_tx_new;
|
|
|
|
if (conn->pkt_tx_head == 0) {
|
|
conn->pkt_tx_head = node_tx_new;
|
|
conn->pkt_tx_last = NULL;
|
|
}
|
|
}
|
|
|
|
if (conn->pkt_tx_last) {
|
|
conn->pkt_tx_last->next = node_tx_new;
|
|
}
|
|
|
|
conn->pkt_tx_last = node_tx_new;
|
|
} else {
|
|
struct pdu_data *pdu_data_tx;
|
|
|
|
pdu_data_tx = (struct pdu_data *)node_tx_new->pdu_data;
|
|
|
|
/* By setting it resv, when app gets num cmplt, no
|
|
* num cmplt is counted, but the buffer is released
|
|
*/
|
|
pdu_data_tx->ll_id = PDU_DATA_LLID_RESV;
|
|
|
|
pdu_node_tx_release(pdu_data_q_tx->handle, node_tx_new);
|
|
}
|
|
|
|
first = _radio.packet_tx_first + 1;
|
|
if (first == _radio.packet_tx_count) {
|
|
first = 0;
|
|
}
|
|
_radio.packet_tx_first = first;
|
|
}
|
|
}
|
|
|
|
static struct pdu_data *empty_tx_enqueue(struct connection *conn)
|
|
{
|
|
struct pdu_data *pdu_data_tx;
|
|
|
|
conn->empty = 1;
|
|
|
|
pdu_data_tx = (struct pdu_data *)radio_pkt_empty_get();
|
|
pdu_data_tx->ll_id = PDU_DATA_LLID_DATA_CONTINUE;
|
|
pdu_data_tx->len = 0;
|
|
if (conn->pkt_tx_head) {
|
|
pdu_data_tx->md = 1;
|
|
} else {
|
|
pdu_data_tx->md = 0;
|
|
}
|
|
|
|
return pdu_data_tx;
|
|
}
|
|
|
|
static void ctrl_tx_enqueue(struct connection *conn,
|
|
struct radio_pdu_node_tx *node_tx)
|
|
{
|
|
/* check if a packet was tx-ed and not acked by peer */
|
|
if (
|
|
/* An explicit empty PDU is not enqueued */
|
|
(conn->empty == 0) &&
|
|
/* and data/ctrl packet is in the head */
|
|
(conn->pkt_tx_head) && (
|
|
/* data PDU tx is not paused */
|
|
(conn->pause_tx == 0) ||
|
|
/* or ctrl PDU already at head */
|
|
(conn->pkt_tx_head == conn->pkt_tx_ctrl))) {
|
|
/* data or ctrl may have been transmitted once, but not acked
|
|
* by peer, hence place this new ctrl after head
|
|
*/
|
|
|
|
/* if data transmited once, keep it at head of the tx list,
|
|
* as we will insert a ctrl after it, hence advance the
|
|
* data pointer
|
|
*/
|
|
if (conn->pkt_tx_head == conn->pkt_tx_data) {
|
|
conn->pkt_tx_data = conn->pkt_tx_data->next;
|
|
}
|
|
|
|
/* if no ctrl packet already queued, new ctrl added will be
|
|
* the ctrl pointer and is inserted after head.
|
|
*/
|
|
if (conn->pkt_tx_ctrl == 0) {
|
|
node_tx->next = conn->pkt_tx_head->next;
|
|
conn->pkt_tx_head->next = node_tx;
|
|
conn->pkt_tx_ctrl = node_tx;
|
|
} else {
|
|
/* TODO support for more than 2 pending ctrl packets. */
|
|
LL_ASSERT(conn->pkt_tx_ctrl->next == conn->pkt_tx_data);
|
|
|
|
node_tx->next = conn->pkt_tx_ctrl->next;
|
|
conn->pkt_tx_ctrl->next = node_tx;
|
|
}
|
|
} else {
|
|
/* No packet needing ACK. */
|
|
|
|
/* If first ctrl packet then add it as head else add it to the
|
|
* tail of the ctrl packets.
|
|
*/
|
|
if (!conn->pkt_tx_ctrl) {
|
|
node_tx->next = conn->pkt_tx_head;
|
|
conn->pkt_tx_head = node_tx;
|
|
conn->pkt_tx_ctrl = node_tx;
|
|
} else {
|
|
/* TODO support for more than 2 pending ctrl packets. */
|
|
LL_ASSERT(conn->pkt_tx_ctrl->next == conn->pkt_tx_data);
|
|
|
|
node_tx->next = conn->pkt_tx_ctrl->next;
|
|
conn->pkt_tx_ctrl->next = node_tx;
|
|
}
|
|
}
|
|
|
|
/* Update last pointer if ctrl added at end of tx list */
|
|
if (node_tx->next == 0) {
|
|
conn->pkt_tx_last = node_tx;
|
|
}
|
|
}
|
|
|
|
static void pdu_node_tx_release(uint16_t handle,
|
|
struct radio_pdu_node_tx *node_tx)
|
|
{
|
|
uint8_t last;
|
|
|
|
last = _radio.packet_release_last + 1;
|
|
if (last == _radio.packet_tx_count) {
|
|
last = 0;
|
|
}
|
|
|
|
LL_ASSERT(last != _radio.packet_release_first);
|
|
|
|
/* Enqueue app mem for release */
|
|
_radio.pkt_release[_radio.packet_release_last].handle = handle;
|
|
_radio.pkt_release[_radio.packet_release_last].node_tx = node_tx;
|
|
_radio.packet_release_last = last;
|
|
|
|
/* callback to trigger application action */
|
|
event_callback();
|
|
}
|
|
|
|
static void connection_release(struct connection *conn)
|
|
{
|
|
uint32_t ticker_status;
|
|
|
|
/* Enable Ticker Job, we are in a radio event which disabled it if
|
|
* worker0 and job0 priority where same.
|
|
*/
|
|
work_enable(WORK_TICKER_JOB0_IRQ);
|
|
|
|
/** @todo correctly stop tickers ensuring crystal and radio active are
|
|
* placed in right states
|
|
*/
|
|
|
|
/* Stop Master/Slave role ticker */
|
|
ticker_status =
|
|
ticker_stop(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER,
|
|
(RADIO_TICKER_ID_FIRST_CONNECTION + conn->handle),
|
|
ticker_success_assert, (void *)__LINE__);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
|
|
/* Stop Marker 0 and event single-shot tickers */
|
|
if ((_radio.state == STATE_ABORT) &&
|
|
(_radio.ticker_id_prepare == (RADIO_TICKER_ID_FIRST_CONNECTION +
|
|
conn->handle))) {
|
|
ticker_status =
|
|
ticker_stop(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER,
|
|
RADIO_TICKER_ID_MARKER_0,
|
|
ticker_success_assert, (void *)__LINE__);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
ticker_status =
|
|
ticker_stop(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_WORKER,
|
|
RADIO_TICKER_ID_EVENT,
|
|
ticker_success_assert, (void *)__LINE__);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
}
|
|
|
|
/* flush and release, data packet before ctrl */
|
|
while ((conn->pkt_tx_head != conn->pkt_tx_ctrl) &&
|
|
(conn->pkt_tx_head != conn->pkt_tx_data)) {
|
|
struct radio_pdu_node_tx *node_tx;
|
|
struct pdu_data *pdu_data_tx;
|
|
|
|
/* By setting it resv, when app gets num cmplt, no num cmplt
|
|
* is counted, but the buffer is released
|
|
*/
|
|
node_tx = conn->pkt_tx_head;
|
|
pdu_data_tx = (struct pdu_data *)node_tx->pdu_data;
|
|
pdu_data_tx->ll_id = PDU_DATA_LLID_RESV;
|
|
|
|
conn->pkt_tx_head = conn->pkt_tx_head->next;
|
|
|
|
pdu_node_tx_release(conn->handle, node_tx);
|
|
}
|
|
|
|
/* flush and release, ctrl packet before data */
|
|
while ((conn->pkt_tx_head) &&
|
|
(conn->pkt_tx_head != conn->pkt_tx_data)) {
|
|
void *release;
|
|
|
|
release = conn->pkt_tx_head;
|
|
conn->pkt_tx_head = conn->pkt_tx_head->next;
|
|
conn->pkt_tx_ctrl = conn->pkt_tx_head;
|
|
|
|
mem_release(release, &_radio.pkt_tx_ctrl_free);
|
|
}
|
|
conn->pkt_tx_ctrl = NULL;
|
|
|
|
/* flush and release, rest of data */
|
|
while (conn->pkt_tx_head) {
|
|
struct radio_pdu_node_tx *node_tx;
|
|
struct pdu_data *pdu_data_tx;
|
|
|
|
/* By setting it resv, when app gets num cmplt, no num cmplt
|
|
* is counted, but the buffer is released
|
|
*/
|
|
node_tx = conn->pkt_tx_head;
|
|
pdu_data_tx = (struct pdu_data *)node_tx->pdu_data;
|
|
pdu_data_tx->ll_id = PDU_DATA_LLID_RESV;
|
|
|
|
conn->pkt_tx_head = conn->pkt_tx_head->next;
|
|
conn->pkt_tx_data = conn->pkt_tx_head;
|
|
|
|
pdu_node_tx_release(conn->handle, node_tx);
|
|
}
|
|
|
|
conn->handle = 0xffff;
|
|
|
|
/* reset mutex */
|
|
if (_radio.conn_upd == conn) {
|
|
_radio.conn_upd = NULL;
|
|
}
|
|
}
|
|
|
|
static void terminate_ind_rx_enqueue(struct connection *conn, uint8_t reason)
|
|
{
|
|
struct radio_pdu_node_rx *radio_pdu_node_rx;
|
|
void *link;
|
|
|
|
/* Prepare the rx packet structure */
|
|
radio_pdu_node_rx =
|
|
(struct radio_pdu_node_rx *)&conn->llcp_terminate.radio_pdu_node_rx;
|
|
LL_ASSERT(radio_pdu_node_rx->hdr.onion.link);
|
|
|
|
radio_pdu_node_rx->hdr.handle = conn->handle;
|
|
radio_pdu_node_rx->hdr.type = NODE_RX_TYPE_TERMINATE;
|
|
*((uint8_t *)radio_pdu_node_rx->pdu_data) = reason;
|
|
|
|
/* Get the link mem reserved in the connection context */
|
|
link = radio_pdu_node_rx->hdr.onion.link;
|
|
|
|
/* Serialize release queue with rx queue by storing reference to
|
|
* last element in release queue
|
|
*/
|
|
radio_pdu_node_rx->hdr.onion.packet_release_last =
|
|
_radio.packet_release_last;
|
|
|
|
/* Enqueue into event-cum-data queue */
|
|
link = memq_enqueue(radio_pdu_node_rx, link,
|
|
(void *)&_radio.link_rx_tail);
|
|
LL_ASSERT(link);
|
|
|
|
/* callback to trigger application action */
|
|
event_callback();
|
|
}
|
|
|
|
static uint32_t conn_update(struct connection *conn,
|
|
struct pdu_data *pdu_data_rx)
|
|
{
|
|
if (((pdu_data_rx->payload.llctrl.ctrldata.conn_update_req.instant -
|
|
conn->event_counter) & 0xFFFF) > 0x7FFF) {
|
|
return 1;
|
|
}
|
|
|
|
LL_ASSERT((conn->llcp_req == conn->llcp_ack) ||
|
|
((conn->llcp_type == LLCP_CONNECTION_UPDATE) &&
|
|
(conn->llcp.connection_update.state ==
|
|
LLCP_CONN_STATE_RSP_WAIT)));
|
|
|
|
/* set mutex, if only not already set. As a master the mutex shall
|
|
* be set, but a slave we accept it as new 'set' of mutex.
|
|
*/
|
|
if (_radio.conn_upd == 0) {
|
|
LL_ASSERT(conn->role.slave.role != 0);
|
|
|
|
_radio.conn_upd = conn;
|
|
}
|
|
|
|
conn->llcp.connection_update.win_size =
|
|
pdu_data_rx->payload.llctrl.ctrldata.conn_update_req.win_size;
|
|
conn->llcp.connection_update.win_offset_us =
|
|
pdu_data_rx->payload.llctrl.ctrldata.conn_update_req.win_offset *
|
|
1250;
|
|
conn->llcp.connection_update.interval =
|
|
pdu_data_rx->payload.llctrl.ctrldata.conn_update_req.interval;
|
|
conn->llcp.connection_update.latency =
|
|
pdu_data_rx->payload.llctrl.ctrldata.conn_update_req.latency;
|
|
conn->llcp.connection_update.timeout =
|
|
pdu_data_rx->payload.llctrl.ctrldata.conn_update_req.timeout;
|
|
conn->llcp.connection_update.instant =
|
|
pdu_data_rx->payload.llctrl.ctrldata.conn_update_req.instant;
|
|
conn->llcp.connection_update.state = LLCP_CONN_STATE_INPROG;
|
|
conn->llcp.connection_update.is_internal = 0;
|
|
|
|
conn->llcp_type = LLCP_CONNECTION_UPDATE;
|
|
conn->llcp_ack--;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t is_peer_compatible(struct connection *conn)
|
|
{
|
|
return ((conn->llcp_version.rx) &&
|
|
(conn->llcp_version.version_number >= RADIO_BLE_VERSION_NUMBER) &&
|
|
(conn->llcp_version.company_id == RADIO_BLE_COMPANY_ID) &&
|
|
(conn->llcp_version.sub_version_number >= RADIO_BLE_SUB_VERSION_NUMBER));
|
|
}
|
|
|
|
static uint32_t conn_update_req(struct connection *conn)
|
|
{
|
|
if (conn->llcp_req != conn->llcp_ack) {
|
|
return 1;
|
|
}
|
|
|
|
if ((conn->role.master.role == 0) || (is_peer_compatible(conn))) {
|
|
/** Perform slave intiated conn param req */
|
|
conn->llcp.connection_update.win_size = 1;
|
|
conn->llcp.connection_update.win_offset_us = 0;
|
|
conn->llcp.connection_update.interval = conn->conn_interval;
|
|
conn->llcp.connection_update.latency = conn->latency;
|
|
conn->llcp.connection_update.timeout = conn->conn_interval *
|
|
conn->supervision_reload * 125 / 1000;
|
|
/* conn->llcp.connection_update.instant = 0; */
|
|
conn->llcp.connection_update.state =
|
|
(conn->role.master.role == 0) ?
|
|
LLCP_CONN_STATE_INITIATE : LLCP_CONN_STATE_REQ;
|
|
conn->llcp.connection_update.is_internal = 1;
|
|
|
|
conn->llcp_type = LLCP_CONNECTION_UPDATE;
|
|
conn->llcp_ack--;
|
|
|
|
return 0;
|
|
}
|
|
|
|
return 2;
|
|
}
|
|
|
|
static uint32_t channel_map_update(struct connection *conn,
|
|
struct pdu_data *pdu_data_rx)
|
|
{
|
|
if (((pdu_data_rx->payload.llctrl.ctrldata.channel_map_req.instant -
|
|
conn->event_counter) & 0xffff) > 0x7fff) {
|
|
return 1;
|
|
}
|
|
|
|
LL_ASSERT(conn->llcp_req == conn->llcp_ack);
|
|
|
|
memcpy(&conn->llcp.channel_map.chm[0],
|
|
&pdu_data_rx->payload.llctrl.ctrldata.channel_map_req.chm[0],
|
|
sizeof(conn->llcp.channel_map.chm));
|
|
conn->llcp.channel_map.instant =
|
|
pdu_data_rx->payload.llctrl.ctrldata.channel_map_req.instant;
|
|
conn->llcp.channel_map.initiate = 0;
|
|
|
|
conn->llcp_type = LLCP_CHANNEL_MAP;
|
|
conn->llcp_ack--;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void enc_req_reused_send(struct connection *conn,
|
|
struct radio_pdu_node_tx *node_tx)
|
|
{
|
|
struct pdu_data *pdu_ctrl_tx;
|
|
|
|
pdu_ctrl_tx = (struct pdu_data *)node_tx->pdu_data;
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_tx->len = offsetof(struct pdu_data_llctrl, ctrldata)
|
|
+ sizeof(struct pdu_data_llctrl_enc_req);
|
|
pdu_ctrl_tx->payload.llctrl.opcode = PDU_DATA_LLCTRL_TYPE_ENC_REQ;
|
|
memcpy(&pdu_ctrl_tx->payload.llctrl.ctrldata.enc_req.rand[0],
|
|
&conn->llcp.encryption.rand[0],
|
|
sizeof(pdu_ctrl_tx->payload.llctrl.ctrldata.enc_req.rand));
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.enc_req.ediv[0] =
|
|
conn->llcp.encryption.ediv[0];
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.enc_req.ediv[1] =
|
|
conn->llcp.encryption.ediv[1];
|
|
/** @todo */
|
|
memset(&pdu_ctrl_tx->payload.llctrl.ctrldata.enc_req.skdm[0], 0xcc,
|
|
sizeof(pdu_ctrl_tx->payload.llctrl.ctrldata.enc_req.skdm));
|
|
/** @todo */
|
|
memset(&pdu_ctrl_tx->payload.llctrl.ctrldata.enc_req.ivm[0], 0xdd,
|
|
sizeof(pdu_ctrl_tx->payload.llctrl.ctrldata.enc_req.ivm));
|
|
}
|
|
|
|
static void enc_rsp_send(struct connection *conn)
|
|
{
|
|
struct radio_pdu_node_tx *node_tx;
|
|
struct pdu_data *pdu_ctrl_tx;
|
|
|
|
/* acquire tx mem */
|
|
node_tx = mem_acquire(&_radio.pkt_tx_ctrl_free);
|
|
LL_ASSERT(node_tx);
|
|
|
|
pdu_ctrl_tx = (struct pdu_data *)node_tx->pdu_data;
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_tx->len = offsetof(struct pdu_data_llctrl, ctrldata)
|
|
+ sizeof(struct pdu_data_llctrl_enc_rsp);
|
|
pdu_ctrl_tx->payload.llctrl.opcode = PDU_DATA_LLCTRL_TYPE_ENC_RSP;
|
|
/** @todo */
|
|
memset(&pdu_ctrl_tx->payload.llctrl.ctrldata.enc_rsp.skds[0], 0xaa,
|
|
sizeof(pdu_ctrl_tx->payload.llctrl.ctrldata.enc_rsp.skds));
|
|
/** @todo */
|
|
memset(&pdu_ctrl_tx->payload.llctrl.ctrldata.enc_rsp.ivs[0], 0xbb,
|
|
sizeof(pdu_ctrl_tx->payload.llctrl.ctrldata.enc_rsp.ivs));
|
|
|
|
/* things from slave stored for session key calculation */
|
|
memcpy(&conn->llcp.encryption.skd[8],
|
|
&pdu_ctrl_tx->payload.llctrl.ctrldata.enc_rsp.skds[0], 8);
|
|
memcpy(&conn->ccm_rx.iv[4],
|
|
&pdu_ctrl_tx->payload.llctrl.ctrldata.enc_rsp.ivs[0], 4);
|
|
|
|
ctrl_tx_enqueue(conn, node_tx);
|
|
}
|
|
|
|
static void start_enc_rsp_send(struct connection *conn,
|
|
struct pdu_data *pdu_ctrl_tx)
|
|
{
|
|
struct radio_pdu_node_tx *node_tx = NULL;
|
|
|
|
if (!pdu_ctrl_tx) {
|
|
/* acquire tx mem */
|
|
node_tx = mem_acquire(&_radio.pkt_tx_ctrl_free);
|
|
LL_ASSERT(node_tx);
|
|
|
|
pdu_ctrl_tx = (struct pdu_data *)node_tx->pdu_data;
|
|
}
|
|
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_tx->len = offsetof(struct pdu_data_llctrl, ctrldata);
|
|
pdu_ctrl_tx->payload.llctrl.opcode =
|
|
PDU_DATA_LLCTRL_TYPE_START_ENC_RSP;
|
|
|
|
if (node_tx) {
|
|
ctrl_tx_enqueue(conn, node_tx);
|
|
}
|
|
}
|
|
|
|
static void unknown_rsp_send(struct connection *conn, uint8_t type)
|
|
{
|
|
struct radio_pdu_node_tx *node_tx;
|
|
struct pdu_data *pdu_ctrl_tx;
|
|
|
|
/* acquire tx mem */
|
|
node_tx = mem_acquire(&_radio.pkt_tx_ctrl_free);
|
|
LL_ASSERT(node_tx);
|
|
|
|
pdu_ctrl_tx = (struct pdu_data *)node_tx->pdu_data;
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_tx->len = offsetof(struct pdu_data_llctrl, ctrldata)
|
|
+ sizeof(struct pdu_data_llctrl_unknown_rsp);
|
|
pdu_ctrl_tx->payload.llctrl.opcode = PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.unknown_rsp.type = type;
|
|
|
|
ctrl_tx_enqueue(conn, node_tx);
|
|
}
|
|
|
|
static void feature_rsp_send(struct connection *conn)
|
|
{
|
|
struct radio_pdu_node_tx *node_tx;
|
|
struct pdu_data *pdu_ctrl_tx;
|
|
|
|
/* acquire tx mem */
|
|
node_tx = mem_acquire(&_radio.pkt_tx_ctrl_free);
|
|
LL_ASSERT(node_tx);
|
|
|
|
pdu_ctrl_tx = (struct pdu_data *)node_tx->pdu_data;
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_tx->len = offsetof(struct pdu_data_llctrl, ctrldata) +
|
|
sizeof(struct pdu_data_llctrl_feature_rsp);
|
|
pdu_ctrl_tx->payload.llctrl.opcode = PDU_DATA_LLCTRL_TYPE_FEATURE_RSP;
|
|
memset(&pdu_ctrl_tx->payload.llctrl.ctrldata.feature_rsp.features[0],
|
|
0x00,
|
|
sizeof(pdu_ctrl_tx->payload.llctrl.ctrldata.feature_rsp.features));
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.feature_rsp.features[0] =
|
|
conn->llcp_features;
|
|
|
|
ctrl_tx_enqueue(conn, node_tx);
|
|
}
|
|
|
|
static void pause_enc_rsp_send(struct connection *conn)
|
|
{
|
|
struct radio_pdu_node_tx *node_tx;
|
|
struct pdu_data *pdu_ctrl_tx;
|
|
|
|
/* acquire tx mem */
|
|
node_tx = mem_acquire(&_radio.pkt_tx_ctrl_free);
|
|
LL_ASSERT(node_tx);
|
|
|
|
pdu_ctrl_tx = (struct pdu_data *)node_tx->pdu_data;
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_tx->len = offsetof(struct pdu_data_llctrl, ctrldata);
|
|
pdu_ctrl_tx->payload.llctrl.opcode =
|
|
PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP;
|
|
|
|
ctrl_tx_enqueue(conn, node_tx);
|
|
}
|
|
|
|
static void version_ind_send(struct connection *conn)
|
|
{
|
|
struct radio_pdu_node_tx *node_tx;
|
|
struct pdu_data *pdu_ctrl_tx;
|
|
|
|
/* acquire tx mem */
|
|
node_tx = mem_acquire(&_radio.pkt_tx_ctrl_free);
|
|
LL_ASSERT(node_tx);
|
|
|
|
pdu_ctrl_tx = (struct pdu_data *)node_tx->pdu_data;
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_tx->len = offsetof(struct pdu_data_llctrl, ctrldata) +
|
|
sizeof(struct pdu_data_llctrl_version_ind);
|
|
pdu_ctrl_tx->payload.llctrl.opcode = PDU_DATA_LLCTRL_TYPE_VERSION_IND;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.version_ind.version_number =
|
|
RADIO_BLE_VERSION_NUMBER;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.version_ind.company_id =
|
|
RADIO_BLE_COMPANY_ID;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.version_ind.sub_version_number =
|
|
RADIO_BLE_SUB_VERSION_NUMBER;
|
|
|
|
ctrl_tx_enqueue(conn, node_tx);
|
|
|
|
/* Apple work-around, add empty packet before version_ind */
|
|
empty_tx_enqueue(conn);
|
|
}
|
|
|
|
#if defined(CONFIG_BLUETOOTH_CONTROLLER_LE_PING)
|
|
static void ping_resp_send(struct connection *conn)
|
|
{
|
|
struct radio_pdu_node_tx *node_tx;
|
|
struct pdu_data *pdu_ctrl_tx;
|
|
|
|
/* acquire tx mem */
|
|
node_tx = mem_acquire(&_radio.pkt_tx_ctrl_free);
|
|
LL_ASSERT(node_tx);
|
|
|
|
pdu_ctrl_tx = (struct pdu_data *)node_tx->pdu_data;
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_tx->len = offsetof(struct pdu_data_llctrl, ctrldata);
|
|
pdu_ctrl_tx->payload.llctrl.opcode = PDU_DATA_LLCTRL_TYPE_PING_RSP;
|
|
|
|
ctrl_tx_enqueue(conn, node_tx);
|
|
}
|
|
#endif /* CONFIG_BLUETOOTH_CONTROLLER_LE_PING */
|
|
|
|
static void reject_ind_ext_send(struct connection *conn,
|
|
uint8_t reject_opcode, uint8_t error_code)
|
|
{
|
|
struct radio_pdu_node_tx *node_tx;
|
|
struct pdu_data *pdu_ctrl_tx;
|
|
|
|
/* acquire tx mem */
|
|
node_tx = mem_acquire(&_radio.pkt_tx_ctrl_free);
|
|
LL_ASSERT(node_tx);
|
|
|
|
pdu_ctrl_tx = (struct pdu_data *)node_tx->pdu_data;
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_tx->len = offsetof(struct pdu_data_llctrl, ctrldata) +
|
|
sizeof(struct pdu_data_llctrl_reject_ind_ext);
|
|
pdu_ctrl_tx->payload.llctrl.opcode =
|
|
PDU_DATA_LLCTRL_TYPE_REJECT_IND_EXT;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.reject_ind_ext.reject_opcode =
|
|
reject_opcode;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.reject_ind_ext.error_code =
|
|
error_code;
|
|
|
|
ctrl_tx_enqueue(conn, node_tx);
|
|
}
|
|
|
|
static void length_resp_send(struct connection *conn, uint16_t eff_rx_octets,
|
|
uint16_t eff_tx_octets)
|
|
{
|
|
struct radio_pdu_node_tx *node_tx;
|
|
struct pdu_data *pdu_ctrl_tx;
|
|
|
|
node_tx = mem_acquire(&_radio.pkt_tx_ctrl_free);
|
|
LL_ASSERT(node_tx);
|
|
|
|
pdu_ctrl_tx = (struct pdu_data *) node_tx->pdu_data;
|
|
pdu_ctrl_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_ctrl_tx->len = offsetof(struct pdu_data_llctrl, ctrldata) +
|
|
sizeof(struct pdu_data_llctrl_length_req_rsp);
|
|
pdu_ctrl_tx->payload.llctrl.opcode =
|
|
PDU_DATA_LLCTRL_TYPE_LENGTH_RSP;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.length_rsp.max_rx_octets =
|
|
eff_rx_octets;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.length_rsp.max_rx_time =
|
|
((eff_rx_octets + 14) << 3);
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.length_rsp.max_tx_octets =
|
|
eff_tx_octets;
|
|
pdu_ctrl_tx->payload.llctrl.ctrldata.length_rsp.max_tx_time =
|
|
((eff_tx_octets + 14) << 3);
|
|
|
|
ctrl_tx_enqueue(conn, node_tx);
|
|
}
|
|
|
|
void radio_ticks_active_to_start_set(uint32_t ticks_active_to_start)
|
|
{
|
|
_radio.ticks_active_to_start = ticks_active_to_start;
|
|
}
|
|
|
|
struct radio_adv_data *radio_adv_data_get(void)
|
|
{
|
|
return &_radio.advertiser.adv_data;
|
|
}
|
|
|
|
struct radio_adv_data *radio_scan_data_get(void)
|
|
{
|
|
return &_radio.advertiser.scan_data;
|
|
}
|
|
|
|
void radio_filter_clear(void)
|
|
{
|
|
_radio.filter_enable_bitmask = 0;
|
|
_radio.filter_addr_type_bitmask = 0;
|
|
}
|
|
|
|
uint32_t radio_filter_add(uint8_t addr_type, uint8_t *addr)
|
|
{
|
|
if (_radio.filter_enable_bitmask != 0xFF) {
|
|
uint8_t index;
|
|
|
|
for (index = 0;
|
|
(_radio.filter_enable_bitmask & (1 << index));
|
|
index++) {
|
|
}
|
|
_radio.filter_enable_bitmask |= (1 << index);
|
|
_radio.filter_addr_type_bitmask |=
|
|
((addr_type & 0x01) << index);
|
|
memcpy(&_radio.filter_bdaddr[index][0], addr, BDADDR_SIZE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
uint32_t radio_filter_remove(uint8_t addr_type, uint8_t *addr)
|
|
{
|
|
uint8_t index;
|
|
|
|
if (!_radio.filter_enable_bitmask) {
|
|
return 1;
|
|
}
|
|
|
|
index = 8;
|
|
while (index--) {
|
|
if ((_radio.filter_enable_bitmask & BIT(index)) &&
|
|
(((_radio.filter_addr_type_bitmask >> index) & 0x01) ==
|
|
(addr_type & 0x01)) &&
|
|
!memcmp(_radio.filter_bdaddr[index], addr, BDADDR_SIZE)) {
|
|
_radio.filter_enable_bitmask &= ~BIT(index);
|
|
_radio.filter_addr_type_bitmask &= ~BIT(index);
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void radio_irk_clear(void)
|
|
{
|
|
_radio.nirk = 0;
|
|
}
|
|
|
|
uint32_t radio_irk_add(uint8_t *irk)
|
|
{
|
|
if (_radio.nirk >= RADIO_IRK_COUNT_MAX) {
|
|
return 1;
|
|
}
|
|
|
|
memcpy(&_radio.irk[_radio.nirk][0], irk, 16);
|
|
_radio.nirk++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct connection *connection_get(uint16_t handle)
|
|
{
|
|
struct connection *conn;
|
|
|
|
if (handle < _radio.connection_count) {
|
|
conn = mem_get(_radio.conn_pool, CONNECTION_T_SIZE, handle);
|
|
if ((conn) && (conn->handle == handle)) {
|
|
return conn;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void role_active_disable(uint8_t ticker_id_stop,
|
|
uint32_t ticks_xtal_to_start,
|
|
uint32_t ticks_active_to_start)
|
|
{
|
|
static struct work s_work_radio_inactive = { 0, 0, 0,
|
|
WORK_TICKER_WORKER0_IRQ, (work_fp) work_radio_inactive, 0 };
|
|
uint32_t volatile ticker_status_event;
|
|
|
|
/* Step 2: Is caller before Event? Stop Event */
|
|
ticker_status_event =
|
|
ticker_stop(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_APP, RADIO_TICKER_ID_EVENT,
|
|
ticker_if_done, (void *)&ticker_status_event);
|
|
|
|
if (ticker_status_event == TICKER_STATUS_BUSY) {
|
|
work_enable(WORK_TICKER_JOB0_IRQ);
|
|
|
|
LL_ASSERT(ticker_status_event != TICKER_STATUS_BUSY);
|
|
}
|
|
|
|
if (ticker_status_event == TICKER_STATUS_SUCCESS) {
|
|
static struct work s_work_xtal_stop = { 0, 0, 0,
|
|
WORK_TICKER_WORKER0_IRQ, (work_fp) work_xtal_stop, 0 };
|
|
uint32_t volatile ticker_status_pre_event;
|
|
|
|
/* Step 2.1: Is caller between Primary and Marker0?
|
|
* Stop the Marker0 event
|
|
*/
|
|
ticker_status_pre_event =
|
|
ticker_stop(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_APP,
|
|
RADIO_TICKER_ID_MARKER_0,
|
|
ticker_if_done,
|
|
(void *)&ticker_status_pre_event);
|
|
|
|
if (ticker_status_pre_event == TICKER_STATUS_BUSY) {
|
|
work_enable(WORK_TICKER_JOB0_IRQ);
|
|
|
|
LL_ASSERT(ticker_status_event != TICKER_STATUS_BUSY);
|
|
}
|
|
|
|
if (ticker_status_pre_event == TICKER_STATUS_SUCCESS) {
|
|
/* Step 2.1.1: Check and deassert Radio Active or XTAL
|
|
* start
|
|
*/
|
|
if (ticks_active_to_start > ticks_xtal_to_start) {
|
|
uint32_t retval;
|
|
|
|
/* radio active asserted, handle deasserting
|
|
* here
|
|
*/
|
|
retval = work_schedule(&s_work_radio_inactive,
|
|
0);
|
|
LL_ASSERT(!retval);
|
|
} else {
|
|
uint32_t retval;
|
|
|
|
/* XTAL started, handle XTAL stop here */
|
|
retval = work_schedule(&s_work_xtal_stop, 0);
|
|
LL_ASSERT(!retval);
|
|
}
|
|
} else if (ticker_status_pre_event == TICKER_STATUS_FAILURE) {
|
|
uint32_t retval;
|
|
|
|
/* Step 2.1.2: Deassert Radio Active and XTAL start */
|
|
|
|
/* radio active asserted, handle deasserting here */
|
|
retval = work_schedule(&s_work_radio_inactive, 0);
|
|
LL_ASSERT(!retval);
|
|
|
|
/* XTAL started, handle XTAL stop here */
|
|
retval = work_schedule(&s_work_xtal_stop, 0);
|
|
LL_ASSERT(!retval);
|
|
} else {
|
|
LL_ASSERT(0);
|
|
}
|
|
} else if (ticker_status_event == TICKER_STATUS_FAILURE) {
|
|
uint32_t volatile ticker_status_stop;
|
|
|
|
/* Step 3: Caller inside Event, handle graceful stop of Event
|
|
* (role dependent)
|
|
*/
|
|
/* Stop ticker "may" be in use for direct adv or observer,
|
|
* hence stop may fail if ticker not used.
|
|
*/
|
|
ticker_status_stop =
|
|
ticker_stop(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_APP, ticker_id_stop,
|
|
ticker_if_done,
|
|
(void *)&ticker_status_stop);
|
|
|
|
if (ticker_status_stop == TICKER_STATUS_BUSY) {
|
|
work_enable(WORK_TICKER_JOB0_IRQ);
|
|
|
|
LL_ASSERT(ticker_status_event != TICKER_STATUS_BUSY);
|
|
}
|
|
|
|
LL_ASSERT((ticker_status_stop == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status_stop == TICKER_STATUS_FAILURE));
|
|
|
|
if (_radio.role != ROLE_NONE) {
|
|
static struct work s_work_radio_stop = { 0, 0, 0,
|
|
WORK_TICKER_WORKER0_IRQ,
|
|
(work_fp) work_radio_stop, 0 };
|
|
uint32_t retval;
|
|
|
|
/* Radio state STOP is supplied in params */
|
|
s_work_radio_stop.params = (void *)STATE_STOP;
|
|
|
|
/* Stop Radio Tx/Rx */
|
|
retval = work_schedule(&s_work_radio_stop, 0);
|
|
LL_ASSERT(!retval);
|
|
|
|
/* wait for radio ISR to exit */
|
|
while (_radio.role != ROLE_NONE) {
|
|
cpu_sleep();
|
|
}
|
|
}
|
|
} else {
|
|
LL_ASSERT(0);
|
|
}
|
|
|
|
}
|
|
|
|
static uint32_t role_disable(uint8_t ticker_id_primary,
|
|
uint8_t ticker_id_stop)
|
|
{
|
|
uint32_t volatile ticker_status;
|
|
uint32_t ticks_xtal_to_start = 0;
|
|
uint32_t ticks_active_to_start = 0;
|
|
|
|
switch (ticker_id_primary) {
|
|
case RADIO_TICKER_ID_ADV:
|
|
ticks_xtal_to_start =
|
|
_radio.advertiser.hdr.ticks_xtal_to_start;
|
|
ticks_active_to_start =
|
|
_radio.advertiser.hdr.ticks_active_to_start;
|
|
break;
|
|
|
|
case RADIO_TICKER_ID_OBS:
|
|
ticks_xtal_to_start =
|
|
_radio.observer.hdr.ticks_xtal_to_start;
|
|
ticks_active_to_start =
|
|
_radio.observer.hdr.ticks_active_to_start;
|
|
break;
|
|
default:
|
|
if (ticker_id_primary >= RADIO_TICKER_ID_FIRST_CONNECTION) {
|
|
struct connection *conn;
|
|
uint16_t conn_handle;
|
|
|
|
conn_handle = ticker_id_primary -
|
|
RADIO_TICKER_ID_FIRST_CONNECTION;
|
|
conn = connection_get(conn_handle);
|
|
if (!conn) {
|
|
return 1;
|
|
}
|
|
|
|
ticks_xtal_to_start =
|
|
conn->hdr.ticks_xtal_to_start;
|
|
ticks_active_to_start =
|
|
conn->hdr.ticks_active_to_start;
|
|
} else {
|
|
BT_ASSERT(0);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Step 1: Is Primary started? Stop the Primary ticker */
|
|
ticker_status =
|
|
ticker_stop(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_APP,
|
|
ticker_id_primary, ticker_if_done,
|
|
(void *)&ticker_status);
|
|
|
|
if (ticker_status == TICKER_STATUS_BUSY) {
|
|
/* if inside our event, enable Job. */
|
|
if (_radio.ticker_id_event == ticker_id_primary) {
|
|
work_enable(WORK_TICKER_JOB0_IRQ);
|
|
}
|
|
|
|
/** @todo design to avoid this wait */
|
|
while (ticker_status == TICKER_STATUS_BUSY) {
|
|
cpu_sleep();
|
|
}
|
|
}
|
|
|
|
if (ticker_status != TICKER_STATUS_SUCCESS) {
|
|
return 1;
|
|
}
|
|
|
|
/* Inside our event, gracefully handle XTAL and Radio actives */
|
|
if ((_radio.ticker_id_prepare == ticker_id_primary)
|
|
|| (_radio.ticker_id_event == ticker_id_primary)) {
|
|
|
|
role_active_disable(ticker_id_stop,
|
|
ticks_xtal_to_start, ticks_active_to_start);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t radio_adv_enable(uint16_t interval, uint8_t chl_map,
|
|
uint8_t filter_policy)
|
|
{
|
|
struct connection *conn;
|
|
uint32_t volatile ticker_status;
|
|
uint32_t ticks_slot_offset;
|
|
struct pdu_adv *pdu_adv;
|
|
|
|
pdu_adv = (struct pdu_adv *)
|
|
&_radio.advertiser.adv_data.data[_radio.advertiser.adv_data.last][0];
|
|
|
|
if ((pdu_adv->type == PDU_ADV_TYPE_ADV_IND) ||
|
|
(pdu_adv->type == PDU_ADV_TYPE_DIRECT_IND)) {
|
|
void *link;
|
|
|
|
if (_radio.advertiser.conn) {
|
|
return 1;
|
|
}
|
|
|
|
link = mem_acquire(&_radio.link_rx_free);
|
|
if (!link) {
|
|
return 1;
|
|
}
|
|
|
|
conn = mem_acquire(&_radio.conn_free);
|
|
if (!conn) {
|
|
mem_release(link, &_radio.link_rx_free);
|
|
|
|
return 1;
|
|
}
|
|
|
|
conn->handle = 0xFFFF;
|
|
conn->llcp_features = RADIO_BLE_FEATURES;
|
|
conn->data_channel_use = 0;
|
|
conn->event_counter = 0;
|
|
conn->latency_prepare = 0;
|
|
conn->latency_event = 0;
|
|
conn->default_tx_octets = _radio.default_tx_octets;
|
|
conn->max_tx_octets = RADIO_LL_LENGTH_OCTETS_RX_MIN;
|
|
conn->max_rx_octets = RADIO_LL_LENGTH_OCTETS_RX_MIN;
|
|
conn->role.slave.role = 1;
|
|
conn->role.slave.latency_cancel = 0;
|
|
conn->role.slave.window_widening_prepare_us = 0;
|
|
conn->role.slave.window_widening_event_us = 0;
|
|
conn->role.slave.ticks_to_offset = 0;
|
|
conn->supervision_expire = 6;
|
|
conn->procedure_expire = 0;
|
|
|
|
#if defined(CONFIG_BLUETOOTH_CONTROLLER_LE_PING)
|
|
conn->apto_expire = 0;
|
|
conn->appto_expire = 0;
|
|
#endif /* CONFIG_BLUETOOTH_CONTROLLER_LE_PING */
|
|
|
|
conn->llcp_req = 0;
|
|
conn->llcp_ack = 0;
|
|
conn->llcp_version.tx = 0;
|
|
conn->llcp_version.rx = 0;
|
|
conn->llcp_terminate.req = 0;
|
|
conn->llcp_terminate.ack = 0;
|
|
conn->llcp_terminate.reason_peer = 0;
|
|
conn->llcp_terminate.radio_pdu_node_rx.hdr.onion.link = link;
|
|
conn->llcp_length.req = 0;
|
|
conn->llcp_length.ack = 0;
|
|
conn->sn = 0;
|
|
conn->nesn = 0;
|
|
conn->pause_rx = 0;
|
|
conn->pause_tx = 0;
|
|
conn->enc_rx = 0;
|
|
conn->enc_tx = 0;
|
|
conn->refresh = 0;
|
|
conn->empty = 0;
|
|
conn->pkt_tx_head = NULL;
|
|
conn->pkt_tx_ctrl = NULL;
|
|
conn->pkt_tx_data = NULL;
|
|
conn->pkt_tx_last = NULL;
|
|
conn->packet_tx_head_len = 0;
|
|
conn->packet_tx_head_offset = 0;
|
|
conn->rssi_latest = 0x7F;
|
|
conn->rssi_reported = 0x7F;
|
|
conn->rssi_sample_count = 0;
|
|
|
|
_radio.advertiser.conn = conn;
|
|
} else {
|
|
conn = NULL;
|
|
}
|
|
|
|
_radio.advertiser.chl_map = chl_map;
|
|
_radio.advertiser.filter_policy = filter_policy;
|
|
if (filter_policy) {
|
|
_radio.advertiser.filter_addr_type_bitmask =
|
|
_radio.filter_addr_type_bitmask;
|
|
memcpy(&_radio.advertiser.filter_bdaddr[0][0],
|
|
&_radio.filter_bdaddr[0][0],
|
|
sizeof(_radio.advertiser.filter_bdaddr));
|
|
_radio.advertiser.filter_enable_bitmask =
|
|
_radio.filter_enable_bitmask;
|
|
}
|
|
|
|
_radio.advertiser.hdr.ticks_active_to_start =
|
|
_radio.ticks_active_to_start;
|
|
_radio.advertiser.hdr.ticks_xtal_to_start =
|
|
TICKER_US_TO_TICKS(RADIO_TICKER_XTAL_OFFSET_US);
|
|
_radio.advertiser.hdr.ticks_preempt_to_start =
|
|
TICKER_US_TO_TICKS(RADIO_TICKER_PREEMPT_PART_MIN_US);
|
|
_radio.advertiser.hdr.ticks_slot =
|
|
TICKER_US_TO_TICKS(RADIO_TICKER_START_PART_US +
|
|
/* Max. chain is ADV_IND + SCAN_REQ + SCAN_RESP */
|
|
((376 + 150 + 176 + 150 + 376) * 3));
|
|
|
|
ticks_slot_offset =
|
|
(_radio.advertiser.hdr.ticks_active_to_start <
|
|
_radio.advertiser.hdr.ticks_xtal_to_start) ?
|
|
_radio.advertiser.hdr.ticks_xtal_to_start :
|
|
_radio.advertiser.hdr.ticks_active_to_start;
|
|
|
|
if (pdu_adv->type == PDU_ADV_TYPE_DIRECT_IND) {
|
|
uint32_t ticks_now = ticker_ticks_now_get();
|
|
|
|
ticker_status =
|
|
ticker_start(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_APP,
|
|
RADIO_TICKER_ID_ADV, ticks_now, 0,
|
|
(ticks_slot_offset +
|
|
_radio.advertiser.hdr.ticks_slot),
|
|
TICKER_NULL_REMAINDER, TICKER_NULL_LAZY,
|
|
(ticks_slot_offset +
|
|
_radio.advertiser.hdr.ticks_slot),
|
|
radio_event_adv_prepare, 0,
|
|
ticker_if_done, (void *)&ticker_status);
|
|
|
|
/** @todo design to avoid this wait */
|
|
while (ticker_status == TICKER_STATUS_BUSY) {
|
|
cpu_sleep();
|
|
}
|
|
|
|
if (ticker_status != TICKER_STATUS_SUCCESS) {
|
|
goto failure_cleanup;
|
|
}
|
|
|
|
ticker_status =
|
|
ticker_start(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_APP,
|
|
RADIO_TICKER_ID_ADV_STOP, ticks_now,
|
|
TICKER_US_TO_TICKS((uint64_t) (1280 * 1000) +
|
|
RADIO_TICKER_XTAL_OFFSET_US),
|
|
TICKER_NULL_PERIOD, TICKER_NULL_REMAINDER,
|
|
TICKER_NULL_LAZY, TICKER_NULL_SLOT,
|
|
event_adv_stop, 0, ticker_if_done,
|
|
(void *)&ticker_status);
|
|
} else {
|
|
ticker_status =
|
|
ticker_start(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_APP,
|
|
RADIO_TICKER_ID_ADV,
|
|
ticker_ticks_now_get(), 0,
|
|
TICKER_US_TO_TICKS((uint64_t) interval * 625),
|
|
TICKER_NULL_REMAINDER, TICKER_NULL_LAZY,
|
|
(ticks_slot_offset +
|
|
_radio.advertiser.hdr.ticks_slot),
|
|
radio_event_adv_prepare, 0, ticker_if_done,
|
|
(void *)&ticker_status);
|
|
}
|
|
|
|
/** @todo design to avoid this wait */
|
|
while (ticker_status == TICKER_STATUS_BUSY) {
|
|
cpu_sleep();
|
|
}
|
|
|
|
if (ticker_status == TICKER_STATUS_SUCCESS) {
|
|
return 0;
|
|
}
|
|
|
|
failure_cleanup:
|
|
|
|
if (conn) {
|
|
mem_release(conn->llcp_terminate.radio_pdu_node_rx.hdr.
|
|
onion.link, &_radio.link_rx_free);
|
|
mem_release(conn, &_radio.conn_free);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
uint32_t radio_adv_disable(void)
|
|
{
|
|
uint32_t status;
|
|
|
|
status = role_disable(RADIO_TICKER_ID_ADV,
|
|
RADIO_TICKER_ID_ADV_STOP);
|
|
if (!status) {
|
|
struct connection *conn;
|
|
|
|
conn = _radio.advertiser.conn;
|
|
if (conn) {
|
|
_radio.advertiser.conn = NULL;
|
|
|
|
mem_release(conn->llcp_terminate.radio_pdu_node_rx.hdr.onion.link,
|
|
&_radio.link_rx_free);
|
|
mem_release(conn, &_radio.conn_free);
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
uint32_t radio_scan_enable(uint8_t scan_type, uint8_t init_addr_type,
|
|
uint8_t *init_addr, uint16_t interval,
|
|
uint16_t window, uint8_t filter_policy)
|
|
{
|
|
uint32_t volatile ticker_status;
|
|
uint32_t ticks_anchor;
|
|
uint32_t us_offset;
|
|
uint32_t ticks_interval;
|
|
uint32_t ticks_slot_offset;
|
|
|
|
_radio.observer.scan_type = scan_type;
|
|
_radio.observer.init_addr_type = init_addr_type;
|
|
memcpy(&_radio.observer.init_addr[0], init_addr, BDADDR_SIZE);
|
|
_radio.observer.ticks_window =
|
|
TICKER_US_TO_TICKS((uint64_t) window * 625);
|
|
_radio.observer.filter_policy = filter_policy;
|
|
if (filter_policy) {
|
|
_radio.observer.filter_addr_type_bitmask =
|
|
_radio.filter_addr_type_bitmask;
|
|
memcpy(&_radio.observer.filter_bdaddr[0][0],
|
|
&_radio.filter_bdaddr[0][0],
|
|
sizeof(_radio.advertiser.filter_bdaddr));
|
|
_radio.observer.filter_enable_bitmask =
|
|
_radio.filter_enable_bitmask;
|
|
}
|
|
|
|
_radio.observer.hdr.ticks_active_to_start =
|
|
_radio.ticks_active_to_start;
|
|
_radio.observer.hdr.ticks_xtal_to_start =
|
|
TICKER_US_TO_TICKS(RADIO_TICKER_XTAL_OFFSET_US);
|
|
_radio.observer.hdr.ticks_preempt_to_start =
|
|
TICKER_US_TO_TICKS(RADIO_TICKER_PREEMPT_PART_MIN_US);
|
|
_radio.observer.hdr.ticks_slot = _radio.observer.ticks_window;
|
|
|
|
ticks_interval = TICKER_US_TO_TICKS((uint64_t) interval * 625);
|
|
if (_radio.observer.hdr.ticks_slot >
|
|
(ticks_interval -
|
|
TICKER_US_TO_TICKS(RADIO_TICKER_XTAL_OFFSET_US))) {
|
|
_radio.observer.hdr.ticks_slot =
|
|
(ticks_interval -
|
|
TICKER_US_TO_TICKS(RADIO_TICKER_XTAL_OFFSET_US));
|
|
}
|
|
|
|
ticks_slot_offset =
|
|
(_radio.observer.hdr.ticks_active_to_start <
|
|
_radio.observer.hdr.ticks_xtal_to_start) ?
|
|
_radio.observer.hdr.ticks_xtal_to_start :
|
|
_radio.observer.hdr.ticks_active_to_start;
|
|
|
|
ticks_anchor = ticker_ticks_now_get();
|
|
|
|
if ((_radio.observer.conn) || !SCHED_ADVANCED) {
|
|
us_offset = 0;
|
|
}
|
|
#if SCHED_ADVANCED
|
|
else {
|
|
sched_after_master_free_slot_get(RADIO_TICKER_USER_ID_APP,
|
|
(ticks_slot_offset +
|
|
_radio.observer.hdr.ticks_slot),
|
|
&ticks_anchor, &us_offset);
|
|
}
|
|
#endif
|
|
|
|
ticker_status =
|
|
ticker_start(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_APP, RADIO_TICKER_ID_OBS,
|
|
(ticks_anchor + TICKER_US_TO_TICKS(us_offset)), 0,
|
|
ticks_interval,
|
|
TICKER_REMAINDER((uint64_t) interval * 625),
|
|
TICKER_NULL_LAZY,
|
|
(ticks_slot_offset +
|
|
_radio.observer.hdr.ticks_slot),
|
|
event_obs_prepare, 0, ticker_if_done,
|
|
(void *)&ticker_status);
|
|
|
|
/** @todo design to avoid this wait */
|
|
while (ticker_status == TICKER_STATUS_BUSY) {
|
|
cpu_sleep();
|
|
}
|
|
|
|
return ((ticker_status == TICKER_STATUS_SUCCESS) ? 0 : 1);
|
|
}
|
|
|
|
uint32_t radio_scan_disable(void)
|
|
{
|
|
uint32_t status;
|
|
|
|
status = role_disable(RADIO_TICKER_ID_OBS,
|
|
RADIO_TICKER_ID_OBS_STOP);
|
|
if (!status) {
|
|
struct connection *conn;
|
|
|
|
conn = _radio.observer.conn;
|
|
if (conn) {
|
|
_radio.observer.conn = NULL;
|
|
|
|
mem_release(conn->llcp_terminate.
|
|
radio_pdu_node_rx.hdr.onion.link,
|
|
&_radio.link_rx_free);
|
|
mem_release(conn, &_radio.conn_free);
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
uint32_t radio_connect_enable(uint8_t adv_addr_type, uint8_t *adv_addr,
|
|
uint16_t interval, uint16_t latency,
|
|
uint16_t timeout)
|
|
{
|
|
void *link;
|
|
struct connection *conn;
|
|
uint32_t access_addr;
|
|
uint32_t conn_interval_us;
|
|
|
|
if (_radio.observer.conn) {
|
|
return 1;
|
|
}
|
|
|
|
link = mem_acquire(&_radio.link_rx_free);
|
|
if (!link) {
|
|
return 1;
|
|
}
|
|
|
|
conn = mem_acquire(&_radio.conn_free);
|
|
if (!conn) {
|
|
mem_release(link, &_radio.link_rx_free);
|
|
|
|
return 1;
|
|
}
|
|
|
|
radio_scan_disable();
|
|
|
|
_radio.observer.adv_addr_type = adv_addr_type;
|
|
memcpy(&_radio.observer.adv_addr[0], adv_addr, BDADDR_SIZE);
|
|
_radio.observer.conn_interval = interval;
|
|
_radio.observer.conn_latency = latency;
|
|
_radio.observer.conn_timeout = timeout;
|
|
_radio.observer.ticks_conn_slot =
|
|
TICKER_US_TO_TICKS(RADIO_TICKER_START_PART_US +
|
|
RADIO_TX_READY_DELAY_US + 328 + 328 + 150);
|
|
|
|
conn->handle = 0xFFFF;
|
|
conn->llcp_features = RADIO_BLE_FEATURES;
|
|
access_addr = access_addr_get();
|
|
memcpy(&conn->access_addr[0], &access_addr, sizeof(conn->access_addr));
|
|
memcpy(&conn->crc_init[0], &conn, 3);
|
|
memcpy(&conn->data_channel_map[0], &_radio.data_channel_map[0],
|
|
sizeof(conn->data_channel_map));
|
|
conn->data_channel_count = _radio.data_channel_count;
|
|
conn->data_channel_hop = 6;
|
|
conn->data_channel_use = 0;
|
|
conn->event_counter = 0;
|
|
conn->conn_interval = _radio.observer.conn_interval;
|
|
conn->latency_prepare = 0;
|
|
conn->latency_event = 0;
|
|
conn->latency = _radio.observer.conn_latency;
|
|
conn->default_tx_octets = _radio.default_tx_octets;
|
|
conn->max_tx_octets = RADIO_LL_LENGTH_OCTETS_RX_MIN;
|
|
conn->max_rx_octets = RADIO_LL_LENGTH_OCTETS_RX_MIN;
|
|
conn->role.master.role = 0;
|
|
conn->role.master.connect_expire = 6;
|
|
conn_interval_us =
|
|
(uint32_t)_radio.observer.conn_interval * 1250;
|
|
conn->supervision_reload =
|
|
RADIO_CONN_EVENTS((_radio.observer.conn_timeout * 10 * 1000),
|
|
conn_interval_us);
|
|
conn->supervision_expire = 0;
|
|
conn->procedure_reload =
|
|
RADIO_CONN_EVENTS((40 * 1000 * 1000), conn_interval_us);
|
|
conn->procedure_expire = 0;
|
|
|
|
#if defined(CONFIG_BLUETOOTH_CONTROLLER_LE_PING)
|
|
conn->apto_reload =
|
|
RADIO_CONN_EVENTS((30 * 1000 * 1000), conn_interval_us);
|
|
conn->apto_expire = 0;
|
|
conn->appto_reload = (conn->apto_reload > (conn->latency + 2)) ?
|
|
(conn->apto_reload - (conn->latency + 2)) : conn->apto_reload;
|
|
conn->appto_expire = 0;
|
|
#endif /* CONFIG_BLUETOOTH_CONTROLLER_LE_PING */
|
|
|
|
conn->llcp_req = 0;
|
|
conn->llcp_ack = 0;
|
|
conn->llcp_version.tx = 0;
|
|
conn->llcp_version.rx = 0;
|
|
conn->llcp_terminate.req = 0;
|
|
conn->llcp_terminate.ack = 0;
|
|
conn->llcp_terminate.reason_peer = 0;
|
|
conn->llcp_terminate.radio_pdu_node_rx.hdr.onion.link = link;
|
|
conn->llcp_length.req = 0;
|
|
conn->llcp_length.ack = 0;
|
|
conn->sn = 0;
|
|
conn->nesn = 0;
|
|
conn->pause_rx = 0;
|
|
conn->pause_tx = 0;
|
|
conn->enc_rx = 0;
|
|
conn->enc_tx = 0;
|
|
conn->refresh = 0;
|
|
conn->empty = 0;
|
|
conn->pkt_tx_head = NULL;
|
|
conn->pkt_tx_ctrl = NULL;
|
|
conn->pkt_tx_data = NULL;
|
|
conn->pkt_tx_last = NULL;
|
|
conn->packet_tx_head_len = 0;
|
|
conn->packet_tx_head_offset = 0;
|
|
conn->rssi_latest = 0x7F;
|
|
conn->rssi_reported = 0x7F;
|
|
conn->rssi_sample_count = 0;
|
|
|
|
_radio.observer.conn = conn;
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t radio_connect_disable(void)
|
|
{
|
|
uint32_t status;
|
|
|
|
if (_radio.observer.conn == 0) {
|
|
return 1;
|
|
}
|
|
|
|
status = radio_scan_disable();
|
|
|
|
return status;
|
|
}
|
|
|
|
uint32_t radio_conn_update(uint16_t handle, uint8_t cmd, uint8_t status,
|
|
uint16_t interval, uint16_t latency,
|
|
uint16_t timeout)
|
|
{
|
|
struct connection *conn;
|
|
|
|
ARG_UNUSED(status);
|
|
|
|
conn = connection_get(handle);
|
|
if ((!conn) ||
|
|
((conn->llcp_req != conn->llcp_ack) &&
|
|
((conn->llcp_type != LLCP_CONNECTION_UPDATE) ||
|
|
(conn->llcp.connection_update.state !=
|
|
LLCP_CONN_STATE_APP_WAIT)))) {
|
|
if ((conn) && (conn->llcp_type == LLCP_CONNECTION_UPDATE)) {
|
|
/* controller busy (mockup requirement) */
|
|
return 2;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
conn->llcp.connection_update.win_size = 1;
|
|
conn->llcp.connection_update.win_offset_us = 0;
|
|
conn->llcp.connection_update.interval = interval;
|
|
conn->llcp.connection_update.latency = latency;
|
|
conn->llcp.connection_update.timeout = timeout;
|
|
/* conn->llcp.connection_update.instant = 0; */
|
|
conn->llcp.connection_update.state = cmd + 1;
|
|
conn->llcp.connection_update.is_internal = 0;
|
|
|
|
conn->llcp_type = LLCP_CONNECTION_UPDATE;
|
|
conn->llcp_req++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t radio_chm_update(uint8_t *chm)
|
|
{
|
|
uint8_t instance;
|
|
|
|
memcpy(&_radio.data_channel_map[0], chm,
|
|
sizeof(_radio.data_channel_map));
|
|
_radio.data_channel_count =
|
|
util_ones_count_get(&_radio.data_channel_map[0],
|
|
sizeof(_radio.data_channel_map));
|
|
|
|
instance = _radio.connection_count;
|
|
while (instance--) {
|
|
struct connection *conn;
|
|
|
|
conn = connection_get(instance);
|
|
if (!conn || conn->role.slave.role) {
|
|
continue;
|
|
}
|
|
|
|
if (conn->llcp_req != conn->llcp_ack) {
|
|
return 1;
|
|
}
|
|
|
|
memcpy(&conn->llcp.channel_map.chm[0], chm,
|
|
sizeof(conn->llcp.channel_map.chm));
|
|
/* conn->llcp.channel_map.instant = 0; */
|
|
conn->llcp.channel_map.initiate = 1;
|
|
|
|
conn->llcp_type = LLCP_CHANNEL_MAP;
|
|
conn->llcp_req++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t radio_chm_get(uint16_t handle, uint8_t *chm)
|
|
{
|
|
struct connection *conn;
|
|
|
|
conn = connection_get(handle);
|
|
if (!conn) {
|
|
return 1;
|
|
}
|
|
|
|
/** @todo make reading context-safe */
|
|
memcpy(chm, conn->data_channel_map, sizeof(conn->data_channel_map));
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t radio_enc_req_send(uint16_t handle, uint8_t *rand, uint8_t *ediv,
|
|
uint8_t *ltk)
|
|
{
|
|
struct connection *conn;
|
|
struct radio_pdu_node_tx *node_tx;
|
|
|
|
conn = connection_get(handle);
|
|
if (!conn) {
|
|
return 1;
|
|
}
|
|
|
|
node_tx = radio_tx_mem_acquire();
|
|
if (node_tx) {
|
|
struct pdu_data *pdu_data_tx;
|
|
|
|
pdu_data_tx = (struct pdu_data *)node_tx->pdu_data;
|
|
|
|
memcpy(&conn->llcp.encryption.ltk[0], ltk,
|
|
sizeof(conn->llcp.encryption.ltk));
|
|
|
|
if ((conn->enc_rx == 0) && (conn->enc_tx == 0)) {
|
|
pdu_data_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_data_tx->len = offsetof(struct pdu_data_llctrl,
|
|
ctrldata) +
|
|
sizeof(struct pdu_data_llctrl_enc_req);
|
|
pdu_data_tx->payload.llctrl.opcode =
|
|
PDU_DATA_LLCTRL_TYPE_ENC_REQ;
|
|
memcpy(&pdu_data_tx->payload.llctrl.ctrldata.
|
|
enc_req.rand[0], rand,
|
|
sizeof(pdu_data_tx->payload.llctrl.ctrldata.
|
|
enc_req.rand));
|
|
pdu_data_tx->payload.llctrl.ctrldata.enc_req.ediv[0] =
|
|
ediv[0];
|
|
pdu_data_tx->payload.llctrl.ctrldata.enc_req.ediv[1] =
|
|
ediv[1];
|
|
memset(&pdu_data_tx->payload.llctrl.ctrldata.enc_req.
|
|
skdm[0], 0xcc,
|
|
/** @todo */
|
|
sizeof(pdu_data_tx->payload.llctrl.ctrldata.
|
|
enc_req.skdm));
|
|
memset(&pdu_data_tx->payload.llctrl.ctrldata.enc_req.
|
|
ivm[0], 0xdd,
|
|
/** @todo */
|
|
sizeof(pdu_data_tx->payload.llctrl.ctrldata.
|
|
enc_req.ivm));
|
|
} else if ((conn->enc_rx != 0) && (conn->enc_tx != 0)) {
|
|
memcpy(&conn->llcp.encryption.rand[0], rand,
|
|
sizeof(conn->llcp.encryption.rand));
|
|
|
|
conn->llcp.encryption.ediv[0] = ediv[0];
|
|
conn->llcp.encryption.ediv[1] = ediv[1];
|
|
|
|
pdu_data_tx->ll_id = PDU_DATA_LLID_CTRL;
|
|
pdu_data_tx->len =
|
|
offsetof(struct pdu_data_llctrl, ctrldata);
|
|
pdu_data_tx->payload.llctrl.opcode =
|
|
PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_REQ;
|
|
} else {
|
|
radio_tx_mem_release(node_tx);
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (radio_tx_mem_enqueue(handle, node_tx)) {
|
|
radio_tx_mem_release(node_tx);
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
uint32_t radio_start_enc_req_send(uint16_t handle, uint8_t error_code,
|
|
uint8_t const *const ltk)
|
|
{
|
|
struct connection *conn;
|
|
|
|
conn = connection_get(handle);
|
|
if (!conn) {
|
|
return 1;
|
|
}
|
|
|
|
if (error_code) {
|
|
if (conn->refresh == 0) {
|
|
if (conn->llcp_req != conn->llcp_ack) {
|
|
return 1;
|
|
}
|
|
|
|
conn->llcp.encryption.error_code = error_code;
|
|
|
|
conn->llcp_type = LLCP_ENCRYPTION;
|
|
conn->llcp_req++;
|
|
} else {
|
|
if (conn->llcp_terminate.ack !=
|
|
conn->llcp_terminate.req) {
|
|
return 1;
|
|
}
|
|
|
|
conn->llcp_terminate.reason_own = error_code;
|
|
|
|
conn->llcp_terminate.req++;
|
|
}
|
|
} else {
|
|
memcpy(&conn->llcp.encryption.ltk[0], ltk,
|
|
sizeof(conn->llcp.encryption.ltk));
|
|
|
|
if (conn->llcp_req != conn->llcp_ack) {
|
|
return 1;
|
|
}
|
|
|
|
conn->llcp.encryption.error_code = 0;
|
|
|
|
conn->llcp_type = LLCP_ENCRYPTION;
|
|
conn->llcp_req++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t radio_feature_req_send(uint16_t handle)
|
|
{
|
|
struct connection *conn;
|
|
|
|
conn = connection_get(handle);
|
|
if (!conn || (conn->llcp_req != conn->llcp_ack)) {
|
|
return 1;
|
|
}
|
|
|
|
conn->llcp_type = LLCP_FEATURE_EXCHANGE;
|
|
conn->llcp_req++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t radio_version_ind_send(uint16_t handle)
|
|
{
|
|
struct connection *conn;
|
|
|
|
conn = connection_get(handle);
|
|
if (!conn || (conn->llcp_req != conn->llcp_ack)) {
|
|
return 1;
|
|
}
|
|
|
|
conn->llcp_type = LLCP_VERSION_EXCHANGE;
|
|
conn->llcp_req++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t radio_terminate_ind_send(uint16_t handle, uint8_t reason)
|
|
{
|
|
struct connection *conn;
|
|
|
|
conn = connection_get(handle);
|
|
if (!conn || (conn->llcp_terminate.ack != conn->llcp_terminate.req)) {
|
|
return 1;
|
|
}
|
|
|
|
conn->llcp_terminate.reason_own = reason;
|
|
|
|
conn->llcp_terminate.req++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t radio_length_req_send(uint16_t handle, uint16_t tx_octets)
|
|
{
|
|
struct connection *conn;
|
|
|
|
conn = connection_get(handle);
|
|
if (!conn || (conn->llcp_req != conn->llcp_ack) ||
|
|
(conn->llcp_length.req != conn->llcp_length.ack)) {
|
|
return 1;
|
|
}
|
|
|
|
conn->llcp_length.state = LLCP_LENGTH_STATE_REQ;
|
|
conn->llcp_length.tx_octets = tx_octets;
|
|
conn->llcp_length.req++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void radio_length_default_get(uint16_t *max_tx_octets, uint16_t *max_tx_time)
|
|
{
|
|
*max_tx_octets = _radio.default_tx_octets;
|
|
*max_tx_time = _radio.default_tx_time;
|
|
}
|
|
|
|
uint32_t radio_length_default_set(uint16_t max_tx_octets, uint16_t max_tx_time)
|
|
{
|
|
if (max_tx_octets > RADIO_LL_LENGTH_OCTETS_RX_MAX ||
|
|
max_tx_time > RADIO_LL_LENGTH_TIME_RX_MAX) {
|
|
|
|
return 1;
|
|
}
|
|
|
|
_radio.default_tx_octets = max_tx_octets;
|
|
_radio.default_tx_time = max_tx_time;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void radio_length_max_get(uint16_t *max_tx_octets, uint16_t *max_tx_time,
|
|
uint16_t *max_rx_octets, uint16_t *max_rx_time)
|
|
{
|
|
*max_tx_octets = RADIO_LL_LENGTH_OCTETS_RX_MAX;
|
|
*max_tx_time = RADIO_LL_LENGTH_TIME_RX_MAX;
|
|
*max_rx_octets = RADIO_LL_LENGTH_OCTETS_RX_MAX;
|
|
*max_rx_time = RADIO_LL_LENGTH_TIME_RX_MAX;
|
|
}
|
|
|
|
static uint8_t tx_cmplt_get(uint16_t *handle, uint8_t *first, uint8_t last)
|
|
{
|
|
uint8_t _first;
|
|
uint8_t cmplt;
|
|
|
|
_first = *first;
|
|
if (_first == last) {
|
|
return 0;
|
|
}
|
|
|
|
cmplt = 0;
|
|
*handle = _radio.pkt_release[_first].handle;
|
|
do {
|
|
struct radio_pdu_node_tx *node_tx;
|
|
struct pdu_data *pdu_data_tx;
|
|
|
|
if (*handle != _radio.pkt_release[_first].handle) {
|
|
break;
|
|
}
|
|
|
|
node_tx = _radio.pkt_release[_first].node_tx;
|
|
/*@FIXME: assign before first 3 if conditions */
|
|
pdu_data_tx = (struct pdu_data *)node_tx->pdu_data;
|
|
if ((!node_tx) || (node_tx == (struct radio_pdu_node_tx *)1) ||
|
|
((((uint32_t)node_tx & ~(0x00000003)) != 0) &&
|
|
(pdu_data_tx) && (pdu_data_tx->len != 0) &&
|
|
((pdu_data_tx->ll_id == PDU_DATA_LLID_DATA_START) ||
|
|
(pdu_data_tx->ll_id == PDU_DATA_LLID_DATA_CONTINUE)))) {
|
|
|
|
/* data packet, hence count num cmplt */
|
|
_radio.pkt_release[_first].node_tx =
|
|
(struct radio_pdu_node_tx *)1;
|
|
|
|
cmplt++;
|
|
} else {
|
|
/* ctrl packet, hence not num cmplt */
|
|
_radio.pkt_release[_first].node_tx =
|
|
(struct radio_pdu_node_tx *)2;
|
|
}
|
|
|
|
if (((uint32_t)node_tx & ~(0x00000003)) != 0) {
|
|
mem_release(node_tx, &_radio.pkt_tx_data_free);
|
|
}
|
|
|
|
_first = _first + 1;
|
|
if (_first == _radio.packet_tx_count) {
|
|
_first = 0;
|
|
}
|
|
|
|
} while (_first != last);
|
|
|
|
*first = _first;
|
|
|
|
return cmplt;
|
|
}
|
|
|
|
uint8_t radio_rx_get(struct radio_pdu_node_rx **radio_pdu_node_rx,
|
|
uint16_t *handle)
|
|
{
|
|
uint8_t cmplt;
|
|
|
|
cmplt = 0;
|
|
if (_radio.link_rx_head != _radio.link_rx_tail) {
|
|
struct radio_pdu_node_rx *_radio_pdu_node_rx;
|
|
|
|
_radio_pdu_node_rx = *((void **)_radio.link_rx_head + 1);
|
|
|
|
cmplt = tx_cmplt_get(handle,
|
|
&_radio.packet_release_first,
|
|
_radio_pdu_node_rx->hdr.onion.
|
|
packet_release_last);
|
|
if (!cmplt) {
|
|
uint16_t handle;
|
|
uint8_t first, cmplt_prev, cmplt_curr;
|
|
|
|
first = _radio.packet_release_first;
|
|
cmplt_curr = 0;
|
|
do {
|
|
cmplt_prev = cmplt_curr;
|
|
cmplt_curr = tx_cmplt_get(&handle, &first,
|
|
_radio.packet_release_last);
|
|
} while ((cmplt_prev != 0) ||
|
|
(cmplt_prev != cmplt_curr));
|
|
|
|
*radio_pdu_node_rx = _radio_pdu_node_rx;
|
|
} else {
|
|
*radio_pdu_node_rx = NULL;
|
|
}
|
|
} else {
|
|
cmplt = tx_cmplt_get(handle, &_radio.packet_release_first,
|
|
_radio.packet_release_last);
|
|
|
|
*radio_pdu_node_rx = NULL;
|
|
}
|
|
|
|
return cmplt;
|
|
}
|
|
|
|
void radio_rx_dequeue(void)
|
|
{
|
|
struct radio_pdu_node_rx *radio_pdu_node_rx = NULL;
|
|
void *link;
|
|
|
|
link = memq_dequeue(_radio.link_rx_tail, &_radio.link_rx_head,
|
|
(void **)&radio_pdu_node_rx);
|
|
LL_ASSERT(link);
|
|
|
|
mem_release(link, &_radio.link_rx_free);
|
|
|
|
switch (radio_pdu_node_rx->hdr.type) {
|
|
case NODE_RX_TYPE_DC_PDU:
|
|
case NODE_RX_TYPE_REPORT:
|
|
case NODE_RX_TYPE_CONNECTION:
|
|
case NODE_RX_TYPE_CONN_UPDATE:
|
|
case NODE_RX_TYPE_ENC_REFRESH:
|
|
case NODE_RX_TYPE_APTO:
|
|
case NODE_RX_TYPE_RSSI:
|
|
|
|
#if defined(CONFIG_BLUETOOTH_CONTROLLER_PROFILE_ISR)
|
|
case NODE_RX_TYPE_PROFILE:
|
|
#endif /* CONFIG_BLUETOOTH_CONTROLLER_PROFILE_ISR */
|
|
|
|
/* release data link credit quota */
|
|
LL_ASSERT(_radio.link_rx_data_quota <
|
|
(_radio.packet_rx_count - 1));
|
|
|
|
_radio.link_rx_data_quota++;
|
|
break;
|
|
|
|
case NODE_RX_TYPE_TERMINATE:
|
|
/* did not use data link quota */
|
|
break;
|
|
|
|
default:
|
|
LL_ASSERT(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void radio_rx_mem_release(struct radio_pdu_node_rx **radio_pdu_node_rx)
|
|
{
|
|
struct radio_pdu_node_rx *_radio_pdu_node_rx;
|
|
struct connection *conn;
|
|
|
|
_radio_pdu_node_rx = *radio_pdu_node_rx;
|
|
while (_radio_pdu_node_rx) {
|
|
struct radio_pdu_node_rx *_radio_pdu_node_rx_free;
|
|
|
|
_radio_pdu_node_rx_free = _radio_pdu_node_rx;
|
|
_radio_pdu_node_rx = _radio_pdu_node_rx->hdr.onion.next;
|
|
|
|
switch (_radio_pdu_node_rx_free->hdr.type) {
|
|
case NODE_RX_TYPE_DC_PDU:
|
|
case NODE_RX_TYPE_REPORT:
|
|
case NODE_RX_TYPE_CONNECTION:
|
|
case NODE_RX_TYPE_CONN_UPDATE:
|
|
case NODE_RX_TYPE_ENC_REFRESH:
|
|
case NODE_RX_TYPE_APTO:
|
|
case NODE_RX_TYPE_RSSI:
|
|
|
|
#if defined(CONFIG_BLUETOOTH_CONTROLLER_PROFILE_ISR)
|
|
case NODE_RX_TYPE_PROFILE:
|
|
#endif /* CONFIG_BLUETOOTH_CONTROLLER_PROFILE_ISR */
|
|
|
|
mem_release(_radio_pdu_node_rx_free,
|
|
&_radio.pkt_rx_data_free);
|
|
break;
|
|
|
|
case NODE_RX_TYPE_TERMINATE:
|
|
conn = mem_get(_radio.conn_pool, CONNECTION_T_SIZE,
|
|
_radio_pdu_node_rx_free->hdr.handle);
|
|
|
|
mem_release(conn, &_radio.conn_free);
|
|
break;
|
|
|
|
default:
|
|
LL_ASSERT(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
*radio_pdu_node_rx = _radio_pdu_node_rx;
|
|
|
|
packet_rx_allocate(0xff);
|
|
}
|
|
|
|
static void rx_fc_lock(uint16_t handle)
|
|
{
|
|
if (_radio.fc_req == _radio.fc_ack) {
|
|
uint8_t req;
|
|
|
|
_radio.fc_handle[_radio.fc_req] = handle;
|
|
req = _radio.fc_req + 1;
|
|
if (req == TRIPLE_BUFFER_SIZE) {
|
|
req = 0;
|
|
}
|
|
_radio.fc_req = req;
|
|
}
|
|
}
|
|
|
|
uint8_t do_radio_rx_fc_set(uint16_t handle, uint8_t req,
|
|
uint8_t ack)
|
|
{
|
|
if (req == ack) {
|
|
if (_radio.link_rx_head == _radio.link_rx_tail) {
|
|
uint8_t ack1 = ack;
|
|
|
|
if (ack1 == 0) {
|
|
ack1 = TRIPLE_BUFFER_SIZE;
|
|
}
|
|
_radio.fc_handle[--ack1] = handle;
|
|
_radio.fc_ack = ack1;
|
|
|
|
/* check if ISR updated FC by changing fc_req */
|
|
if (req != _radio.fc_req) {
|
|
_radio.fc_ack = ack;
|
|
|
|
return 1;
|
|
}
|
|
} else {
|
|
return 1;
|
|
}
|
|
} else if (((req == 0) &&
|
|
(_radio.fc_handle[TRIPLE_BUFFER_SIZE - 1] != handle)) ||
|
|
((req != 0) && (_radio.fc_handle[req - 1] != handle))) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint8_t radio_rx_fc_set(uint16_t handle, uint8_t fc)
|
|
{
|
|
if (_radio.fc_ena) {
|
|
uint8_t req = _radio.fc_req;
|
|
uint8_t ack = _radio.fc_ack;
|
|
|
|
if (fc) {
|
|
if (handle != 0xffff) {
|
|
return do_radio_rx_fc_set(handle, req, ack);
|
|
}
|
|
} else if ((_radio.link_rx_head == _radio.link_rx_tail) &&
|
|
(req != ack)
|
|
) {
|
|
_radio.fc_ack = req;
|
|
|
|
if ((_radio.link_rx_head != _radio.link_rx_tail) &&
|
|
(req == _radio.fc_req)) {
|
|
_radio.fc_ack = ack;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint8_t radio_rx_fc_get(uint16_t *handle)
|
|
{
|
|
uint8_t req = _radio.fc_req;
|
|
uint8_t ack = _radio.fc_ack;
|
|
|
|
if (req != ack) {
|
|
if (handle) {
|
|
*handle = _radio.fc_handle[ack];
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct radio_pdu_node_tx *radio_tx_mem_acquire(void)
|
|
{
|
|
return mem_acquire(&_radio.pkt_tx_data_free);
|
|
}
|
|
|
|
void radio_tx_mem_release(struct radio_pdu_node_tx *node_tx)
|
|
{
|
|
mem_release(node_tx, &_radio.pkt_tx_data_free);
|
|
}
|
|
|
|
static void ticker_op_latency_cancelled(uint32_t ticker_status,
|
|
void *params)
|
|
{
|
|
struct connection *conn;
|
|
|
|
LL_ASSERT(ticker_status == TICKER_STATUS_SUCCESS);
|
|
|
|
conn = (struct connection *)params;
|
|
conn->role.slave.latency_cancel = 0;
|
|
}
|
|
|
|
uint32_t radio_tx_mem_enqueue(uint16_t handle,
|
|
struct radio_pdu_node_tx *node_tx)
|
|
{
|
|
uint8_t last;
|
|
struct connection *conn;
|
|
struct pdu_data *pdu_data;
|
|
|
|
last = _radio.packet_tx_last + 1;
|
|
if (last == _radio.packet_tx_count) {
|
|
last = 0;
|
|
}
|
|
|
|
pdu_data = (struct pdu_data *)node_tx->pdu_data;
|
|
conn = connection_get(handle);
|
|
if ((last == _radio.packet_tx_first) || (conn == 0) ||
|
|
(pdu_data->len > _radio.packet_data_octets_max)) {
|
|
return 1;
|
|
}
|
|
|
|
_radio.pkt_tx[_radio.packet_tx_last].handle = handle;
|
|
_radio.pkt_tx[_radio.packet_tx_last]. node_tx = node_tx;
|
|
_radio.packet_tx_last = last;
|
|
|
|
/* break slave latency */
|
|
if ((conn->role.slave.role != 0) && (conn->latency_event != 0) &&
|
|
(conn->role.slave.latency_cancel == 0)) {
|
|
uint32_t ticker_status;
|
|
|
|
conn->role.slave.latency_cancel = 1;
|
|
|
|
ticker_status = ticker_update(RADIO_TICKER_INSTANCE_ID_RADIO,
|
|
RADIO_TICKER_USER_ID_APP,
|
|
RADIO_TICKER_ID_FIRST_CONNECTION +
|
|
conn->handle, 0, 0, 0, 0, 1, 0,
|
|
ticker_op_latency_cancelled,
|
|
(void *)conn);
|
|
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
|
|
(ticker_status == TICKER_STATUS_BUSY));
|
|
}
|
|
|
|
return 0;
|
|
}
|