diff options
Diffstat (limited to 'firmware/bluetooth_rxtx/le_phy.c')
-rw-r--r-- | firmware/bluetooth_rxtx/le_phy.c | 869 |
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 +} |