summaryrefslogtreecommitdiff
path: root/firmware/bluetooth_rxtx/le_phy.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/bluetooth_rxtx/le_phy.c')
-rw-r--r--firmware/bluetooth_rxtx/le_phy.c869
1 files changed, 869 insertions, 0 deletions
diff --git a/firmware/bluetooth_rxtx/le_phy.c b/firmware/bluetooth_rxtx/le_phy.c
new file mode 100644
index 0000000..2f4782d
--- /dev/null
+++ b/firmware/bluetooth_rxtx/le_phy.c
@@ -0,0 +1,869 @@
+/*
+ * Copyright 2017 Mike Ryan
+ *
+ * This file is part of Project Ubertooth and is released under the
+ * terms of the GPL. Refer to COPYING for more information.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "ubertooth.h"
+#include "ubertooth_clock.h"
+#include "ubertooth_dma.h"
+#include "ubertooth_usb.h"
+#include "bluetooth_le.h"
+#include "queue.h"
+
+// current time, from timer1
+#define NOW T1TC
+#define USEC(X) ((X)*10)
+#define MSEC(X) ((X)*10000)
+#define SEC(X) ((X)*10000000)
+#define PACKET_DURATION(X) (USEC(40 + (X)->size * 8))
+
+#define ADVERTISING_AA (0x8e89bed6)
+
+///////////////////////
+// time constants
+
+// time for the radio to warmup + some timing slack
+#define RX_WARMUP_TIME USEC(300)
+
+// max inter-frame space between packets in a connection event
+#define IFS_TIMEOUT USEC(300)
+
+// observed connection anchor must be within ANCHOR_EPSILON of
+// calculated anchor
+#define ANCHOR_EPSILON USEC(3)
+
+
+//////////////////////
+// global state
+
+extern le_state_t le; // FIXME - refactor this struct
+volatile uint16_t rf_channel;
+uint8_t le_dma_dest[2];
+
+extern volatile uint8_t mode;
+extern volatile uint8_t requested_mode;
+extern volatile uint16_t le_adv_channel;
+extern volatile int cancel_follow;
+
+////////////////////
+// buffers
+
+// packet buffers live in a pool. a minimum of one buffer is always
+// being used, either waiting to receive or actively receiving a packet
+// (current_rxbuf). once a packet is received, it is placed into the
+// packet queue. the main loop pulls packets from this queue and
+// processes them, and then returns the buffers back to the pool by
+// calling buffer_release()
+
+#define LE_BUFFER_POOL_SIZE 4
+typedef struct _le_rx_t {
+ uint8_t data[2 + 255 + 3]; // header + PDU + CRC
+ unsigned size; // total data length (known after header rx)
+ unsigned pos; // current input byte offset
+ uint32_t timestamp; // timestamp taken after first byte rx
+ unsigned channel; // physical channel
+ uint32_t access_address; // access address
+ int available; // 1 if available, 0 in use
+ int8_t rssi_min, rssi_max; // min and max RSSI observed values
+ int rssi_sum; // running sum of all RSSI values
+} le_rx_t;
+
+// pool of all buffers
+static le_rx_t le_buffer_pool[LE_BUFFER_POOL_SIZE];
+
+// buffer waiting for or actively receiving packet
+static le_rx_t *current_rxbuf = NULL;
+
+// received packets, waiting to be processed
+queue_t packet_queue;
+
+
+/////////////////////
+// connections
+
+// this system is architected so that following multiple connections may
+// be possible in the future. all connection state lives in an le_conn_t
+// struct. at present only one such structure exists. refer to
+// connection event below for how anchors are handled.
+
+typedef struct _le_conn_t {
+ uint32_t access_address;
+ uint32_t crc_init;
+ uint32_t crc_init_reversed;
+
+ uint8_t channel_idx;
+ uint8_t hop_increment;
+ uint32_t conn_interval; // in units of 100 ns
+ uint32_t supervision_timeout; // in units of 100 ns
+
+ uint8_t win_size;
+ uint32_t win_offset; // in units of 100 ns
+
+ le_channel_remapping_t remapping;
+
+ uint32_t last_anchor;
+ int anchor_set;
+ uint32_t last_packet_ts; // used to check supervision timeout
+
+ uint16_t conn_event_counter;
+
+ int channel_map_update_pending;
+ uint16_t channel_map_update_instant;
+ le_channel_remapping_t pending_remapping;
+} le_conn_t;
+le_conn_t conn = { 0, };
+
+// every connection event is tracked using this global le_conn_event_t
+// structure named conn_event. when a packet is observed, anchor is set.
+// the event may close due to receiving two packets, or if a timeout
+// occurs. in both cases, finish_conn_event() is called, which updates
+// the active connection's anchor. opened is set to 1 once the radio is
+// tuned to the data channel for the connection event.
+typedef struct _le_conn_event_t {
+ uint32_t anchor;
+ unsigned num_packets;
+ int opened;
+} le_conn_event_t;
+le_conn_event_t conn_event;
+
+static void reset_conn(void) {
+ memset(&conn, 0, sizeof(conn));
+ conn.access_address = ADVERTISING_AA;
+}
+
+
+//////////////////////
+// code
+
+// pre-declarations for utility stuff
+static void timer1_start(void);
+static void timer1_stop(void);
+static void timer1_set_match(uint32_t match);
+static void timer1_clear_match(void);
+static void timer1_wait_fs_lock(void);
+static void timer1_cancel_fs_lock(void);
+static void blink(int tx, int rx, int usr);
+static void le_dma_init(void);
+static void le_cc2400_strobe_rx(void);
+static void change_channel(void);
+static uint8_t dewhiten_length(unsigned channel, uint8_t data);
+
+// resets the state of all available buffers
+static void buffers_init(void) {
+ int i;
+
+ for (i = 0; i < LE_BUFFER_POOL_SIZE; ++i)
+ le_buffer_pool[i].available = 1;
+}
+
+// clear a buffer for new data
+static void buffer_clear(le_rx_t *buf) {
+ buf->pos = 0;
+ buf->size = 0;
+ memset(buf->data, 0, sizeof(buf->data));
+ buf->rssi_min = INT8_MAX;
+ buf->rssi_max = INT8_MIN;
+ buf->rssi_sum = 0;
+}
+
+// get a packet buffer
+// returns a pointer to a buffer if available
+// returns NULL otherwise
+static le_rx_t *buffer_get(void) {
+ int i;
+
+ for (i = 0; i < LE_BUFFER_POOL_SIZE; ++i) {
+ if (le_buffer_pool[i].available) {
+ le_buffer_pool[i].available = 0;
+ buffer_clear(&le_buffer_pool[i]);
+ return &le_buffer_pool[i];
+ }
+ }
+
+ return NULL;
+}
+
+// release a buffer back to the pool
+static void buffer_release(le_rx_t *buffer) {
+ buffer->available = 1;
+}
+
+// clear a connection event
+static void reset_conn_event(void) {
+ conn_event.num_packets = 0;
+ conn_event.opened = 0;
+}
+
+// finish a connection event
+//
+// 1) update the anchor point (see details below)
+// 2) check if supervision timeout is exceeded
+// 2) setup radio for next packet (data or adv if timeout exceeded)
+//
+// anchor update logic can be summarized thusly:
+// 1) if we received two packets, set the connection anchor to the
+// observed value
+// 2) if we received one packet, see if it's within ANCHOR_EPISLON
+// microseconds if the expected anchor time. if so, it's the master
+// and we can update the anchor
+// 3) if the single packet is a slave or we received zero packets,
+// update the anchor to the estimated value
+//
+// FIXME this code does not properly handle the case where the initial
+// connection transmit window has no received packets
+static void finish_conn_event(void) {
+ uint32_t last_anchor = 0;
+ int last_anchor_set = 0;
+
+ // two packets -- update anchor
+ if (conn_event.num_packets == 2) {
+ last_anchor = conn_event.anchor;
+ last_anchor_set = 1;
+ }
+
+ // if there's one packet, we need to find out if it was the master
+ else if (conn_event.num_packets == 1 && conn.anchor_set) {
+ // calculate the difference between the estimated and observed anchor
+ uint32_t estimated_anchor = conn.last_anchor + conn.conn_interval;
+ uint32_t delta = estimated_anchor - conn_event.anchor;
+ // see whether the observed anchor is within 3 us of the estimate
+ delta += ANCHOR_EPSILON;
+ if (delta < 2 * ANCHOR_EPSILON) {
+ last_anchor = conn_event.anchor;
+ last_anchor_set = 1;
+ }
+ }
+
+ // if we observed a new anchor, set it
+ if (last_anchor_set) {
+ conn.last_anchor = last_anchor;
+ conn.anchor_set = 1;
+ }
+
+ // without a new anchor, estimate the next anchor
+ else if (conn.anchor_set) {
+ conn.last_anchor += conn.conn_interval;
+ }
+
+ else {
+ // FIXME this is totally broken if we receive the slave's packet first
+ conn.last_anchor = conn_event.anchor;
+ conn.last_packet_ts = NOW; // FIXME gross hack
+ }
+
+ // update last packet for supervision timeout
+ if (conn_event.num_packets > 0) {
+ conn.last_packet_ts = NOW;
+ }
+
+ reset_conn_event();
+
+ // increment connection event counter
+ ++conn.conn_event_counter;
+
+ // supervision timeout reached - switch back to advertising
+ if (NOW - conn.last_packet_ts > conn.supervision_timeout) {
+ reset_conn();
+ change_channel();
+ }
+
+ // FIXME - hack to cancel following a connection
+ else if (cancel_follow) {
+ cancel_follow = 0;
+ reset_conn();
+ change_channel();
+ }
+
+ // supervision timeout not reached - hop to next channel
+ else {
+ timer1_set_match(conn.last_anchor + conn.conn_interval - RX_WARMUP_TIME);
+ }
+}
+
+// DMA handler
+// called once per byte. handles all incoming data, but only minimally
+// processes received data. at the end of a packet, it enqueues the
+// received packet, fetches a new buffer, and restarts RX.
+void le_DMA_IRQHandler(void) {
+ unsigned pos;
+ int8_t rssi;
+ uint32_t timestamp = NOW; // sampled early for most accurate measurement
+
+ // channel 0
+ if (DMACIntStat & (1 << 0)) {
+ // terminal count - byte received
+ if (DMACIntTCStat & (1 << 0)) {
+ DMACIntTCClear = (1 << 0);
+
+ // poll RSSI
+ rssi = (int8_t)(cc2400_get(RSSI) >> 8);
+ current_rxbuf->rssi_sum += rssi;
+ if (rssi < current_rxbuf->rssi_min) current_rxbuf->rssi_min = rssi;
+ if (rssi > current_rxbuf->rssi_max) current_rxbuf->rssi_max = rssi;
+
+ // grab byte from DMA buffer
+ pos = current_rxbuf->pos;
+ current_rxbuf->data[pos] = le_dma_dest[pos & 1]; // dirty hack
+ pos += 1;
+ current_rxbuf->pos = pos;
+
+ if (pos == 1) {
+ current_rxbuf->timestamp = timestamp - USEC(8 + 32); // packet starts at preamble
+ current_rxbuf->channel = rf_channel;
+ current_rxbuf->access_address = conn.access_address;
+
+ // data packet received: cancel timeout
+ // new timeout or hop timer will be set at end of packet RX
+ if (btle_channel_index(rf_channel) < 37) {
+ timer1_clear_match();
+ }
+ }
+
+ // get length from header
+ if (pos == 2) {
+ uint8_t length = dewhiten_length(current_rxbuf->channel, current_rxbuf->data[1]);
+ current_rxbuf->size = length + 2 + 3; // two bytes for header and three for CRC
+ }
+
+ // finished packet - state transition
+ if (pos > 2 && pos >= current_rxbuf->size) {
+ // stop the CC2400 before flushing SSP
+ cc2400_strobe(SFSON);
+
+ // stop DMA on this channel and flush SSP
+ DMACC0Config = 0;
+ DMACIntTCClear = (1 << 0); // if we don't clear a second time, data is corrupt
+
+ DIO_SSP_DMACR &= ~SSPDMACR_RXDMAE;
+ while (SSP1SR & SSPSR_RNE) {
+ uint8_t tmp = (uint8_t)DIO_SSP_DR;
+ }
+
+ // TODO error transition on queue_insert
+ queue_insert(&packet_queue, current_rxbuf);
+
+ // track connection events
+ if (btle_channel_index(rf_channel) < 37) {
+ ++conn_event.num_packets;
+
+ // first packet: set connection anchor
+ if (conn_event.num_packets == 1) {
+ conn_event.anchor = current_rxbuf->timestamp;
+ timer1_set_match(NOW + IFS_TIMEOUT); // set a timeout for next packet
+ }
+
+ // second packet: close connection event, and set hop timer
+ else if (conn_event.num_packets == 2) {
+ cc2400_strobe(SRFOFF);
+ current_rxbuf = buffer_get();
+ finish_conn_event();
+ return;
+ }
+ }
+
+ // get a new packet
+ // TODO handle error transition
+ current_rxbuf = buffer_get();
+
+ // restart DMA and SSP
+ le_dma_init();
+ dio_ssp_start();
+
+ // wait for FS_LOCK in background
+ timer1_wait_fs_lock();
+ }
+ }
+
+ // error - transition to error state
+ if (DMACIntErrStat & (1 << 0)) {
+ // TODO error state transition
+ DMACIntErrClr = (1 << 0);
+ }
+ }
+}
+
+static void le_dma_init(void) {
+ int i;
+
+ // DMA linked list items
+ typedef struct {
+ uint32_t src;
+ uint32_t dest;
+ uint32_t next_lli;
+ uint32_t control;
+ } dma_lli;
+ static dma_lli le_dma_lli[2];
+
+ for (i = 0; i < 2; ++i) {
+ le_dma_lli[i].src = (uint32_t)&(DIO_SSP_DR);
+ le_dma_lli[i].dest = (uint32_t)&le_dma_dest[i];
+ le_dma_lli[i].next_lli = (uint32_t)&le_dma_lli[1-i]; // two elements pointing back at each other
+ le_dma_lli[i].control = 1 |
+ (0 << 12) | // source burst size = 1
+ (0 << 15) | // destination burst size = 1
+ (0 << 18) | // source width 8 bits
+ (0 << 21) | // destination width 8 bits
+ DMACCxControl_I; // terminal count interrupt enable
+ }
+
+ // configure DMA channel 0
+ DMACC0SrcAddr = le_dma_lli[0].src;
+ DMACC0DestAddr = le_dma_lli[0].dest;
+ DMACC0LLI = le_dma_lli[0].next_lli;
+ DMACC0Control = le_dma_lli[0].control;
+ DMACC0Config =
+ DIO_SSP_SRC |
+ (0x2 << 11) | // peripheral to memory
+ DMACCxConfig_IE | // allow error interrupts
+ DMACCxConfig_ITC; // allow terminal count interrupts
+}
+
+// initalize USB, SSP, and DMA
+static void le_sys_init(void) {
+ usb_queue_init(); // USB FIFO FIXME replace with safer queue
+ dio_ssp_init(); // init SSP and raise !CS (self-routed GPIO)
+ le_dma_init(); // prepare DMA + interrupts
+ dio_ssp_start(); // enable SSP + DMA
+}
+
+// initialize RF and strobe FSON
+static void le_cc2400_init_rf(void) {
+ u16 grmdm, mdmctrl;
+ uint32_t sync = rbit(conn.access_address);
+
+ mdmctrl = 0x0040; // 250 kHz frequency deviation
+ grmdm = 0x44E1; // un-buffered mode, packet w/ sync word detection
+ // 0 10 00 1 001 11 0 00 0 1
+ // | | | | | +--------> CRC off
+ // | | | | +-----------> sync word: 32 MSB bits of SYNC_WORD
+ // | | | +---------------> 1 preamble byte of 01010101
+ // | | +-----------------> packet mode
+ // | +--------------------> un-buffered mode
+ // +-----------------------> sync error bits: 2
+
+ cc2400_set(MANAND, 0x7ffe);
+ cc2400_set(LMTST, 0x2b22);
+
+ cc2400_set(MDMTST0, 0x124b);
+ // 1 2 4b
+ // 00 0 1 0 0 10 01001011
+ // | | | | | +---------> AFC_DELTA = ??
+ // | | | | +------------> AFC settling = 4 pairs (8 bit preamble)
+ // | | | +--------------> no AFC adjust on packet
+ // | | +----------------> do not invert data
+ // | +------------------> TX IF freq 1 0Hz
+ // +--------------------> PRNG off
+ //
+ // ref: CC2400 datasheet page 67
+ // AFC settling explained page 41/42
+
+ cc2400_set(GRMDM, grmdm);
+
+ cc2400_set(SYNCL, sync & 0xffff);
+ cc2400_set(SYNCH, (sync >> 16) & 0xffff);
+
+ cc2400_set(FSDIV, rf_channel - 1); // 1 MHz IF
+ cc2400_set(MDMCTRL, mdmctrl);
+
+ // XOSC16M should always be stable, but leave this test anyway
+ while (!(cc2400_status() & XOSC16M_STABLE));
+
+ // wait for FS_LOCK in background
+ cc2400_strobe(SFSON);
+ timer1_wait_fs_lock();
+}
+
+// strobe RX and enable PA
+static void le_cc2400_strobe_rx(void) {
+ cc2400_strobe(SRX);
+#ifdef UBERTOOTH_ONE
+ PAEN_SET;
+ HGM_SET;
+#endif
+}
+
+// change channel and init rx
+static void change_channel(void) {
+ uint8_t channel_idx = 0;
+
+ cc2400_strobe(SRFOFF);
+
+ // stop DMA and flush SSP
+ DIO_SSP_DMACR &= ~SSPDMACR_RXDMAE;
+ while (SSP1SR & SSPSR_RNE) {
+ uint8_t tmp = (uint8_t)DIO_SSP_DR;
+ }
+
+ buffer_clear(current_rxbuf);
+ le_dma_init();
+ dio_ssp_start();
+
+ if (conn.access_address == ADVERTISING_AA) {
+ // FIXME
+ switch (le_adv_channel) {
+ case 2402: channel_idx = 37; break;
+ case 2426: channel_idx = 38; break;
+ case 2480: channel_idx = 39; break;
+ default: channel_idx = 37; break;
+ }
+ } else {
+ conn.channel_idx = (conn.channel_idx + conn.hop_increment) % 37;
+ channel_idx = le_map_channel(conn.channel_idx, &conn.remapping);
+ }
+
+ rf_channel = btle_channel_index_to_phys(channel_idx);
+ le_cc2400_init_rf();
+}
+
+///////
+// timer stuff
+
+static void timer1_start(void) {
+ T1TCR = TCR_Counter_Reset;
+ T1PR = 4; // 100 ns
+ T1TCR = TCR_Counter_Enable;
+
+ // set up interrupt handler
+ ISER0 = ISER0_ISE_TIMER1;
+}
+
+static void timer1_stop(void) {
+ T1TCR = TCR_Counter_Reset;
+
+ // clear interrupt handler
+ ICER0 = ICER0_ICE_TIMER1;
+}
+
+static void timer1_set_match(uint32_t match) {
+ T1MR0 = match;
+ T1MCR |= TMCR_MR0I;
+}
+
+static void timer1_clear_match(void) {
+ T1MCR &= ~TMCR_MR0I;
+}
+
+static void timer1_wait_fs_lock(void) {
+ T1MR2 = NOW + USEC(3);
+ T1MCR |= TMCR_MR2I;
+}
+
+static void timer1_cancel_fs_lock(void) {
+ T1MCR &= ~TMCR_MR2I;
+}
+
+void TIMER1_IRQHandler(void) {
+ if (T1IR & TIR_MR0_Interrupt) {
+ // ack the interrupt
+ T1IR = TIR_MR0_Interrupt;
+
+ // channel map update, can be interleaved with connection update
+ if (conn.channel_map_update_pending &&
+ conn.conn_event_counter == conn.channel_map_update_instant) {
+ conn.remapping = conn.pending_remapping;
+ conn.channel_map_update_pending = 0;
+ }
+
+ // new connection event: set timeout and change channel
+ if (!conn_event.opened) {
+ conn_event.opened = 1;
+ // timeout is max packet length + warmup time (slack)
+ timer1_set_match(NOW + USEC(2120) + RX_WARMUP_TIME);
+ change_channel();
+ }
+
+ // timeout: close connection event and set timer for next hop
+ else {
+ finish_conn_event();
+ }
+ }
+
+ // LEDs
+ if (T1IR & TIR_MR1_Interrupt) {
+ T1IR = TIR_MR1_Interrupt;
+ T1MCR &= ~TMCR_MR1I;
+
+ TXLED_CLR;
+ RXLED_CLR;
+ USRLED_CLR;
+ }
+
+ // check FS_LOCK
+ if (T1IR & TIR_MR2_Interrupt) {
+ T1IR = TIR_MR2_Interrupt;
+
+ // if FS is locked, strobe RX and clear interrupt
+ if (cc2400_status() & FS_LOCK) {
+ le_cc2400_strobe_rx();
+ T1MCR &= ~TMCR_MR2I;
+ }
+
+ // if FS is not locked, check again in 3 us
+ else {
+ timer1_wait_fs_lock();
+ }
+ }
+}
+
+static void blink(int tx, int rx, int usr) {
+ if (tx)
+ TXLED_SET;
+ if (rx)
+ RXLED_SET;
+ if (usr)
+ USRLED_SET;
+
+ // blink for 10 ms
+ T1MR1 = NOW + MSEC(10);
+ T1MCR |= TMCR_MR1I;
+}
+
+// helper function to dewhiten length from whitened data (only used
+// during DMA)
+static uint8_t dewhiten_length(unsigned channel, uint8_t data) {
+ unsigned int i, bit;
+ int idx = whitening_index[btle_channel_index(channel)];
+ uint8_t out = 0;
+
+ // length is second byte of packet
+ idx = (idx + 8) % sizeof(whitening);
+
+ for (i = 0; i < 8; ++i) {
+ bit = (data >> (7-i)) & 1;
+ bit ^= whitening[idx];
+ idx = (idx + 1) % sizeof(whitening);
+ out |= bit << i;
+ }
+
+ return out;
+}
+
+// enqueue a packet for USB
+// FIXME this is cribbed from existing code, but does not have enough
+// room for larger LE packets
+static int usb_enqueue_le(le_rx_t *packet) {
+ usb_pkt_rx* f = usb_enqueue();
+
+ // fail if queue is full
+ if (f == NULL) {
+ return 0;
+ }
+
+ f->pkt_type = LE_PACKET;
+
+ f->clkn_high = 0;
+ f->clk100ns = packet->timestamp;
+
+ f->channel = (uint8_t)((packet->channel - 2402) & 0xff);
+ f->rssi_avg = packet->rssi_sum / packet->size;
+ f->rssi_min = packet->rssi_min;
+ f->rssi_max = packet->rssi_max;
+ f->rssi_count = 0;
+
+ memcpy(f->data, &packet->access_address, 4);
+ memcpy(f->data+4, packet->data, DMA_SIZE-4);
+
+ f->status = 0;
+
+ return 1;
+}
+
+static unsigned extract_field(le_rx_t *buf, size_t offset, unsigned size) {
+ unsigned i, ret = 0;
+
+ // this could just be replaced by memcpy... right?
+ for (i = 0; i < size; ++i)
+ ret |= buf->data[offset + i] << (i*8);
+
+ return ret;
+}
+
+static void le_connect_handler(le_rx_t *buf) {
+ uint32_t aa, crc_init;
+ uint32_t win_size, max_win_size;
+
+ if (buf->size != 2 + 6 + 6 + 22 + 3)
+ return;
+
+ // FIXME ugly hack
+ if (cancel_follow)
+ cancel_follow = 0;
+
+ conn.access_address = extract_field(buf, 14, 4);
+ conn.crc_init = extract_field(buf, 18, 3);
+ conn.crc_init_reversed = rbit(conn.crc_init);
+ conn.win_size = extract_field(buf, 21, 1);
+ conn.win_offset = extract_field(buf, 22, 2);
+ conn.conn_interval = extract_field(buf, 24, 2);
+ conn.supervision_timeout = extract_field(buf, 28, 2);
+ conn.hop_increment = extract_field(buf, 35, 1) & 0x1f;
+
+ if (conn.conn_interval < 6 || conn.conn_interval > 3200) {
+ goto err_out;
+ } else {
+ conn.conn_interval *= USEC(1250);
+ }
+
+ // window offset is in range [0, conn_interval]
+ conn.win_offset *= USEC(1250);
+ if (conn.win_offset > conn.conn_interval)
+ goto err_out;
+
+ // win size is in range [1.25 ms, MIN(10 ms, conn_interval - 1.25 ms)]
+ win_size = conn.win_size * USEC(1250);
+ max_win_size = conn.conn_interval - USEC(1250);
+ if (max_win_size > MSEC(10))
+ max_win_size = MSEC(10);
+ if (win_size < USEC(1250) || win_size > max_win_size)
+ goto err_out;
+
+ // The connSupervisionTimeout shall be a multiple of 10 ms in the
+ // range of 100 ms to 32.0 s and it shall be larger than (1 +
+ // connSlaveLatency) * connInterval * 2
+ conn.supervision_timeout *= MSEC(10);
+ if (conn.supervision_timeout < MSEC(100) || conn.supervision_timeout > SEC(32))
+ goto err_out;
+ // TODO handle slave latency
+
+ le_parse_channel_map(&buf->data[30], &conn.remapping);
+ if (conn.remapping.total_channels == 0)
+ goto err_out;
+
+ // cancel RX on advertising channel
+ timer1_cancel_fs_lock();
+
+ reset_conn_event();
+ timer1_set_match(buf->timestamp + PACKET_DURATION(buf) +
+ conn.win_offset + USEC(1250) - RX_WARMUP_TIME);
+ return;
+
+ // error condition: reset conn and return
+err_out:
+ reset_conn();
+}
+
+static void channel_map_update_handler(le_rx_t *buf) {
+ conn.channel_map_update_pending = 1;
+ conn.channel_map_update_instant = extract_field(buf, 8, 2);
+ le_parse_channel_map(&buf->data[3], &conn.pending_remapping);
+}
+
+static void packet_handler(le_rx_t *buf) {
+ // advertising packet
+ if (btle_channel_index(buf->channel) >= 37) {
+ switch (buf->data[0] & 0xf) {
+ // CONNECT_REQ
+ case 0x05:
+ le_connect_handler(buf);
+ break;
+ }
+ }
+
+ // data packet
+ else {
+ // LL control PDU
+ if ((buf->data[0] & 0b11) == 0b11 && buf->data[1] > 0) {
+ switch (buf->data[2]) {
+ // LE_CHANNEL_MAP_REQ -- update channel map
+ case 0x1:
+ if (buf->data[1] == 8)
+ channel_map_update_handler(buf);
+ break;
+ }
+ }
+ }
+
+}
+
+static int filter_match(le_rx_t *buf) {
+ if (!le.target_set)
+ return 1;
+
+ // allow all data channel packets
+ if (btle_channel_index(buf->channel) < 37)
+ return 1;
+
+ switch (buf->data[0] & 0xf) {
+ // ADV_IND, ADV_NONCONN_IND, ADV_SCAN_IND, SCAN_RSP
+ case 0x00:
+ case 0x02:
+ case 0x06:
+ case 0x04:
+ // header + one address
+ if (buf->size < 2 + 6)
+ return 0;
+ return memcmp(&buf->data[2], le.target, 6) == 0;
+ break;
+
+ // ADV_DIRECT_IND, SCAN_REQ, CONNECT_REQ
+ case 0x01:
+ case 0x03:
+ case 0x05:
+ // header + two addresses
+ if (buf->size < 2 + 6 + 6)
+ return 0;
+ return memcmp(&buf->data[2], le.target, 6) == 0 ||
+ memcmp(&buf->data[8], le.target, 6) == 0;
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+void le_phy_main(void) {
+ // disable USB interrupts -- we poll them below
+ // n.b., they should not be enabled but let's be careful
+ ICER0 = ICER0_ICE_USB;
+ // disable clkn and timer0
+ clkn_disable();
+
+ buffers_init();
+ queue_init(&packet_queue);
+ timer1_start();
+
+ current_rxbuf = buffer_get();
+ rf_channel = le_adv_channel; // FIXME
+ conn.access_address = ADVERTISING_AA;
+ le_sys_init();
+ le_cc2400_init_rf();
+
+ cancel_follow = 0;
+
+ while (requested_mode == MODE_BT_FOLLOW_LE) {
+ le_rx_t *packet = NULL;
+ if (queue_remove(&packet_queue, (void **)&packet)) {
+ le_dewhiten(packet->data, packet->size, packet->channel);
+
+ if (filter_match(packet)) {
+ blink(0, 1, 0); // RX LED
+ usb_enqueue_le(packet);
+ packet_handler(packet);
+ }
+
+ buffer_release(packet);
+ }
+
+ // polled USB handling
+ handle_usb(0);
+
+ // XXX maybe LED light show?
+ }
+
+ timer1_stop();
+
+ // reset state
+ RXLED_CLR;
+ TXLED_CLR;
+ USRLED_CLR;
+ clkn_init();
+
+ // TODO kill CC2400
+}