538 lines
13 KiB
C
538 lines
13 KiB
C
/* Copyright 2016 The Chromium OS Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "bluetooth_le.h"
|
|
#include "include/bluetooth_le.h"
|
|
#include "console.h"
|
|
#include "ppi.h"
|
|
#include "radio.h"
|
|
#include "registers.h"
|
|
#include "timer.h"
|
|
#include "util.h"
|
|
|
|
#define CPUTS(outstr) cputs(CC_BLUETOOTH_LE, outstr)
|
|
#define CPRINTS(format, args...) cprints(CC_BLUETOOTH_LE, format, ## args)
|
|
#define CPRINTF(format, args...) cprintf(CC_BLUETOOTH_LE, format, ## args)
|
|
|
|
static void ble2nrf_packet(struct ble_pdu *ble_p,
|
|
struct nrf51_ble_packet_t *radio_p)
|
|
{
|
|
if (ble_p->header_type_adv) {
|
|
radio_p->s0 = ble_p->header.adv.type & 0xf;
|
|
radio_p->s0 |= (ble_p->header.adv.txaddr ?
|
|
1 << BLE_ADV_HEADER_TXADD_SHIFT : 0);
|
|
radio_p->s0 |= (ble_p->header.adv.rxaddr ?
|
|
1 << BLE_ADV_HEADER_RXADD_SHIFT : 0);
|
|
radio_p->length = ble_p->header.adv.length & 0x3f; /* 6 bits */
|
|
} else {
|
|
radio_p->s0 = ble_p->header.data.llid & 0x3;
|
|
radio_p->s0 |= (ble_p->header.data.nesn ?
|
|
1 << BLE_DATA_HEADER_NESN_SHIFT : 0);
|
|
radio_p->s0 |= (ble_p->header.data.sn ?
|
|
1 << BLE_DATA_HEADER_SN_SHIFT : 0);
|
|
radio_p->s0 |= (ble_p->header.data.md ?
|
|
1 << BLE_DATA_HEADER_MD_SHIFT : 0);
|
|
radio_p->length = ble_p->header.data.length & 0x1f; /* 5 bits */
|
|
}
|
|
|
|
if (radio_p->length > 0)
|
|
memcpy(radio_p->payload, ble_p->payload, radio_p->length);
|
|
}
|
|
|
|
static void nrf2ble_packet(struct ble_pdu *ble_p,
|
|
struct nrf51_ble_packet_t *radio_p, int type_adv)
|
|
{
|
|
if (type_adv) {
|
|
ble_p->header_type_adv = 1;
|
|
ble_p->header.adv.type = radio_p->s0 & 0xf;
|
|
ble_p->header.adv.txaddr = (radio_p->s0 &
|
|
BIT(BLE_ADV_HEADER_TXADD_SHIFT)) != 0;
|
|
ble_p->header.adv.rxaddr = (radio_p->s0 &
|
|
BIT(BLE_ADV_HEADER_RXADD_SHIFT)) != 0;
|
|
/* Length check? 6-37 Bytes */
|
|
ble_p->header.adv.length = radio_p->length;
|
|
} else {
|
|
ble_p->header_type_adv = 0;
|
|
ble_p->header.data.llid = radio_p->s0 & 0x3;
|
|
ble_p->header.data.nesn = (radio_p->s0 &
|
|
BIT(BLE_DATA_HEADER_NESN_SHIFT)) != 0;
|
|
ble_p->header.data.sn = (radio_p->s0 &
|
|
BIT(BLE_DATA_HEADER_SN_SHIFT)) != 0;
|
|
ble_p->header.data.md = (radio_p->s0 &
|
|
BIT(BLE_DATA_HEADER_MD_SHIFT)) != 0;
|
|
/* Length check? 0-31 Bytes */
|
|
ble_p->header.data.length = radio_p->length;
|
|
}
|
|
|
|
if (radio_p->length > 0)
|
|
memcpy(ble_p->payload, radio_p->payload, radio_p->length);
|
|
}
|
|
|
|
struct ble_pdu adv_packet;
|
|
struct nrf51_ble_packet_t on_air_packet;
|
|
|
|
struct ble_pdu rcv_packet;
|
|
|
|
int ble_radio_init(uint32_t access_address, uint32_t crc_init_val)
|
|
{
|
|
int rv = radio_init(BLE_1MBIT);
|
|
|
|
if (rv)
|
|
return rv;
|
|
NRF51_RADIO_CRCCNF = 3 | NRF51_RADIO_CRCCNF_SKIP_ADDR; /* 3-byte CRC */
|
|
/* x^24 + x^10 + x^9 + x^6 + x^4 + x^3 + x + 1 */
|
|
/* 0x1_0000_0000_0000_0110_0101_1011 */
|
|
NRF51_RADIO_CRCPOLY = 0x100065B;
|
|
|
|
NRF51_RADIO_CRCINIT = crc_init_val;
|
|
|
|
NRF51_RADIO_TXPOWER = NRF51_RADIO_TXPOWER_0_DBM;
|
|
|
|
NRF51_RADIO_BASE0 = access_address << 8;
|
|
NRF51_RADIO_PREFIX0 = access_address >> 24;
|
|
|
|
if (access_address != BLE_ADV_ACCESS_ADDRESS)
|
|
CPRINTF("Initializing radio for data packet.\n");
|
|
|
|
NRF51_RADIO_TXADDRESS = 0;
|
|
NRF51_RADIO_RXADDRESSES = 1;
|
|
NRF51_RADIO_PCNF0 = NRF51_RADIO_PCNF0_ADV_DATA;
|
|
NRF51_RADIO_PCNF1 = NRF51_RADIO_PCNF1_ADV_DATA;
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
static struct nrf51_ble_packet_t tx_packet;
|
|
|
|
static uint32_t tx_end, rsp_end;
|
|
|
|
void ble_tx(struct ble_pdu *pdu)
|
|
{
|
|
uint32_t timeout_time;
|
|
|
|
ble2nrf_packet(pdu, &tx_packet);
|
|
|
|
NRF51_RADIO_PACKETPTR = (uint32_t)&tx_packet;
|
|
NRF51_RADIO_END = NRF51_RADIO_PAYLOAD = NRF51_RADIO_ADDRESS = 0;
|
|
NRF51_RADIO_RXEN = 0;
|
|
NRF51_RADIO_TXEN = 1;
|
|
|
|
timeout_time = get_time().val + RADIO_SETUP_TIMEOUT;
|
|
while (!NRF51_RADIO_READY) {
|
|
if (get_time().val > timeout_time) {
|
|
CPRINTF("ERROR DURING RADIO TX SETUP. TRY AGAIN.\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
timeout_time = get_time().val + RADIO_SETUP_TIMEOUT;
|
|
while (!NRF51_RADIO_END) {
|
|
if (get_time().val > timeout_time) {
|
|
CPRINTF("RADIO DID NOT SHUT DOWN AFTER TX. "
|
|
"RECOMMEND REBOOT.\n");
|
|
return;
|
|
}
|
|
}
|
|
NRF51_RADIO_DISABLE = 1;
|
|
}
|
|
|
|
static struct nrf51_ble_packet_t rx_packet;
|
|
int ble_rx(struct ble_pdu *pdu, int timeout, int adv)
|
|
{
|
|
uint32_t done;
|
|
uint32_t timeout_time;
|
|
int ppi_channel_requested;
|
|
|
|
/* Prevent illegal wait times */
|
|
if (timeout <= 0) {
|
|
NRF51_RADIO_DISABLE = 1;
|
|
return EC_ERROR_TIMEOUT;
|
|
}
|
|
|
|
NRF51_RADIO_PACKETPTR = (uint32_t)&rx_packet;
|
|
NRF51_RADIO_END = NRF51_RADIO_PAYLOAD = NRF51_RADIO_ADDRESS = 0;
|
|
/*
|
|
* These shortcuts cause packet transmission 150 microseconds after
|
|
* packet receive, as is the BTLE standard. See NRF51 manual:
|
|
* section 17.1.12
|
|
*/
|
|
NRF51_RADIO_SHORTS = NRF51_RADIO_SHORTS_READY_START |
|
|
NRF51_RADIO_SHORTS_DISABLED_TXEN |
|
|
NRF51_RADIO_SHORTS_END_DISABLE;
|
|
|
|
/*
|
|
* This creates a shortcut that marks the time
|
|
* that the payload was received by the radio
|
|
* in NRF51_TIMER_CC(0,1)
|
|
*/
|
|
ppi_channel_requested = NRF51_PPI_CH_RADIO_ADDR__TIMER0CC1;
|
|
if (ppi_request_channel(&ppi_channel_requested) == EC_SUCCESS) {
|
|
NRF51_PPI_CHEN |= BIT(ppi_channel_requested);
|
|
NRF51_PPI_CHENSET |= BIT(ppi_channel_requested);
|
|
}
|
|
|
|
|
|
NRF51_RADIO_RXEN = 1;
|
|
|
|
timeout_time = get_time().val + RADIO_SETUP_TIMEOUT;
|
|
while (!NRF51_RADIO_READY) {
|
|
if (get_time().val > timeout_time) {
|
|
CPRINTF("RADIO NOT SET UP IN TIME. TIMING OUT.\n");
|
|
return EC_ERROR_TIMEOUT;
|
|
}
|
|
}
|
|
|
|
timeout_time = get_time().val + timeout;
|
|
do {
|
|
if (get_time().val >= timeout_time) {
|
|
NRF51_RADIO_DISABLE = 1;
|
|
return EC_ERROR_TIMEOUT;
|
|
}
|
|
done = NRF51_RADIO_END;
|
|
} while (!done);
|
|
|
|
rsp_end = get_time().le.lo;
|
|
|
|
if (NRF51_RADIO_CRCSTATUS == 0) {
|
|
CPRINTF("INVALID CRC\n");
|
|
return EC_ERROR_CRC;
|
|
}
|
|
|
|
nrf2ble_packet(pdu, &rx_packet, adv);
|
|
|
|
/*
|
|
* Throw error if radio not yet disabled. Something has
|
|
* gone wrong. May be in an unexpected state.
|
|
*/
|
|
if (NRF51_RADIO_DISABLED != 1)
|
|
return EC_ERROR_UNKNOWN;
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
/* White list handling */
|
|
int ble_radio_clear_white_list(void)
|
|
{
|
|
NRF51_RADIO_DACNF = 0;
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int ble_radio_read_white_list_size(uint8_t *ret_size)
|
|
{
|
|
int i, size = 0;
|
|
uint32_t dacnf = NRF51_RADIO_DACNF;
|
|
|
|
/* Count the bits that are set */
|
|
for (i = 0; i < NRF51_RADIO_DACNF_MAX; i++)
|
|
if (dacnf & NRF51_RADIO_DACNF_ENA(i))
|
|
size++;
|
|
|
|
*ret_size = size;
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int ble_radio_add_device_to_white_list(const uint8_t *addr_ptr, uint8_t rand)
|
|
{
|
|
uint32_t dacnf = NRF51_RADIO_DACNF;
|
|
int i;
|
|
uint32_t aligned;
|
|
|
|
/* Check for duplicates using ble_radio_remove_device? */
|
|
|
|
/* Find a free entry */
|
|
for (i = 0; i < NRF51_RADIO_DACNF_MAX &&
|
|
(dacnf & NRF51_RADIO_DACNF_ENA(i)); i++)
|
|
;
|
|
|
|
if (i == NRF51_RADIO_DACNF_MAX)
|
|
return EC_ERROR_OVERFLOW;
|
|
|
|
memcpy(&aligned, addr_ptr, 4);
|
|
NRF51_RADIO_DAB(i) = aligned;
|
|
memcpy(&aligned, addr_ptr + 4, 2);
|
|
NRF51_RADIO_DAP(i) = aligned;
|
|
|
|
NRF51_RADIO_DACNF = dacnf | NRF51_RADIO_DACNF_ENA(i) |
|
|
(rand ? NRF51_RADIO_DACNF_TXADD(i) : 0);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int ble_radio_remove_device_from_white_list(const uint8_t *addr_ptr,
|
|
uint8_t rand)
|
|
{
|
|
int i, dacnf = NRF51_RADIO_DACNF;
|
|
|
|
/* Find a matching entry */
|
|
for (i = 0; i < NRF51_RADIO_DACNF_MAX; i++) {
|
|
uint32_t dab = NRF51_RADIO_DAB(i), dap = NRF51_RADIO_DAP(i);
|
|
|
|
if ((dacnf & NRF51_RADIO_DACNF_ENA(i)) && /* Enabled */
|
|
/* Rand flag matches */
|
|
(rand == ((dacnf & NRF51_RADIO_DACNF_TXADD(i)) != 0)) &&
|
|
/* Address matches */
|
|
(!memcmp(addr_ptr, &dab, 4)) &&
|
|
(!memcmp(addr_ptr + 4, &dap, 2)))
|
|
break;
|
|
}
|
|
|
|
if (i == NRF51_RADIO_DACNF_MAX) /* Not found is successfully removed */
|
|
return EC_SUCCESS;
|
|
|
|
NRF51_RADIO_DACNF = dacnf & ~((NRF51_RADIO_DACNF_ENA(i)) |
|
|
(rand ? NRF51_RADIO_DACNF_TXADD(i) : 0));
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
|
|
int ble_adv_packet(struct ble_pdu *adv_packet, int chan)
|
|
{
|
|
int done;
|
|
int rv;
|
|
|
|
/* Change channel */
|
|
NRF51_RADIO_FREQUENCY = NRF51_RADIO_FREQUENCY_VAL(chan2freq(chan));
|
|
NRF51_RADIO_DATAWHITEIV = chan;
|
|
|
|
ble_tx(adv_packet);
|
|
|
|
do {
|
|
done = NRF51_RADIO_END;
|
|
} while (!done);
|
|
|
|
tx_end = get_time().le.lo;
|
|
|
|
if (adv_packet->header.adv.type ==
|
|
BLE_ADV_HEADER_PDU_TYPE_ADV_NONCONN_IND)
|
|
return EC_SUCCESS;
|
|
|
|
rv = ble_rx(&rcv_packet, 16000, 1);
|
|
|
|
if (rv != EC_SUCCESS)
|
|
return rv;
|
|
|
|
/* Check for valid responses */
|
|
switch (rcv_packet.header.adv.type) {
|
|
case BLE_ADV_HEADER_PDU_TYPE_SCAN_REQ:
|
|
/* Scan requests are only allowed for ADV_IND and SCAN_IND */
|
|
if (adv_packet->header.adv.type !=
|
|
BLE_ADV_HEADER_PDU_TYPE_ADV_IND &&
|
|
adv_packet->header.adv.type !=
|
|
BLE_ADV_HEADER_PDU_TYPE_ADV_SCAN_IND)
|
|
return rv;
|
|
/* The advertising address needs to match */
|
|
if (memcmp(&rcv_packet.payload[BLUETOOTH_ADDR_OCTETS],
|
|
&adv_packet->payload[0], BLUETOOTH_ADDR_OCTETS))
|
|
return rv;
|
|
break;
|
|
case BLE_ADV_HEADER_PDU_TYPE_CONNECT_REQ:
|
|
/* Connections are only allowed for two types of advertising */
|
|
if (adv_packet->header.adv.type !=
|
|
BLE_ADV_HEADER_PDU_TYPE_ADV_IND &&
|
|
adv_packet->header.adv.type !=
|
|
BLE_ADV_HEADER_PDU_TYPE_ADV_DIRECT_IND)
|
|
return rv;
|
|
/* The advertising address needs to match */
|
|
if (memcmp(&rcv_packet.payload[BLUETOOTH_ADDR_OCTETS],
|
|
&adv_packet->payload[0], BLUETOOTH_ADDR_OCTETS))
|
|
return rv;
|
|
/* The InitAddr needs to match for Directed advertising */
|
|
if (adv_packet->header.adv.type ==
|
|
BLE_ADV_HEADER_PDU_TYPE_ADV_DIRECT_IND &&
|
|
memcmp(&adv_packet->payload[BLUETOOTH_ADDR_OCTETS],
|
|
&rcv_packet.payload[0], BLUETOOTH_ADDR_OCTETS))
|
|
return rv;
|
|
break;
|
|
default: /* Unhandled response packet */
|
|
return rv;
|
|
break;
|
|
}
|
|
|
|
dump_ble_packet(&rcv_packet);
|
|
CPRINTF("tx_end %u Response %u\n", tx_end, rsp_end);
|
|
|
|
return rv;
|
|
}
|
|
|
|
int ble_adv_event(struct ble_pdu *adv_packet)
|
|
{
|
|
int chan;
|
|
int rv;
|
|
|
|
for (chan = 37; chan < 40; chan++) {
|
|
rv = ble_adv_packet(adv_packet, chan);
|
|
if (rv != EC_SUCCESS)
|
|
return rv;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
static void fill_header(struct ble_pdu *adv, int type, int txaddr, int rxaddr)
|
|
{
|
|
adv->header_type_adv = 1;
|
|
adv->header.adv.type = type;
|
|
adv->header.adv.txaddr = txaddr ?
|
|
BLE_ADV_HEADER_RANDOM_ADDR : BLE_ADV_HEADER_PUBLIC_ADDR;
|
|
adv->header.adv.rxaddr = rxaddr ?
|
|
BLE_ADV_HEADER_RANDOM_ADDR : BLE_ADV_HEADER_PUBLIC_ADDR;
|
|
adv->header.adv.length = 0;
|
|
}
|
|
|
|
static int fill_payload(uint8_t *payload, uint64_t addr, int name_length)
|
|
{
|
|
uint8_t *curr;
|
|
|
|
curr = pack_adv_addr(payload, addr);
|
|
curr = pack_adv(curr, name_length, GAP_COMPLETE_NAME,
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrs");
|
|
curr = pack_adv_int(curr, 2, GAP_APPEARANCE,
|
|
GAP_APPEARANCE_HID_KEYBOARD);
|
|
curr = pack_adv_int(curr, 1, GAP_FLAGS,
|
|
GAP_FLAGS_LE_LIM_DISC | GAP_FLAGS_LE_NO_BR_EDR);
|
|
curr = pack_adv_int(curr, 2, GAP_COMP_16_BIT_UUID,
|
|
GATT_SERVICE_HID_UUID);
|
|
|
|
return curr - payload;
|
|
}
|
|
|
|
static void fill_packet(struct ble_pdu *adv, uint64_t addr, int type,
|
|
int name_length)
|
|
{
|
|
fill_header(adv, type, BLE_ADV_HEADER_RANDOM_ADDR,
|
|
BLE_ADV_HEADER_PUBLIC_ADDR);
|
|
|
|
adv->header.adv.length = fill_payload(adv->payload, addr, name_length);
|
|
}
|
|
|
|
static int command_ble_adv(int argc, char **argv)
|
|
{
|
|
int type, length, reps, interval;
|
|
uint64_t addr;
|
|
char *e;
|
|
int i;
|
|
int rv;
|
|
|
|
if (argc < 3 || argc > 5)
|
|
return EC_ERROR_PARAM_COUNT;
|
|
|
|
type = strtoi(argv[1], &e, 0);
|
|
if (*e || type < 0 || (type > 2 && type != 6))
|
|
return EC_ERROR_PARAM1;
|
|
|
|
length = strtoi(argv[2], &e, 0);
|
|
if (*e || length > 32)
|
|
return EC_ERROR_PARAM2;
|
|
|
|
if (argc >= 4) {
|
|
reps = strtoi(argv[3], &e, 0);
|
|
if (*e || reps < 0)
|
|
return EC_ERROR_PARAM3;
|
|
} else {
|
|
reps = 1;
|
|
}
|
|
|
|
if (argc >= 5) {
|
|
interval = strtoi(argv[4], &e, 0);
|
|
if (*e || interval < 0)
|
|
return EC_ERROR_PARAM4;
|
|
} else {
|
|
interval = 100000;
|
|
}
|
|
|
|
if (type == BLE_ADV_HEADER_PDU_TYPE_ADV_DIRECT_IND && length != 12) {
|
|
length = 12;
|
|
CPRINTS("type DIRECT needs to have a length of 12");
|
|
}
|
|
|
|
rv = ble_radio_init(BLE_ADV_ACCESS_ADDRESS, BLE_ADV_CRCINIT);
|
|
|
|
|
|
CPRINTS("ADV @%p", &adv_packet);
|
|
|
|
((uint32_t *)&addr)[0] = 0xA3A2A1A0 | type;
|
|
((uint32_t *)&addr)[1] = BLE_RANDOM_ADDR_MSBS_STATIC << 8 | 0x5A4;
|
|
|
|
fill_packet(&adv_packet, addr, type, length);
|
|
|
|
for (i = 0; i < reps; i++) {
|
|
ble_adv_event(&adv_packet);
|
|
usleep(interval);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(ble_adv, command_ble_adv,
|
|
"type len [reps] [interval = 100000 (100ms)]",
|
|
"Send a BLE packet of type type of length len");
|
|
|
|
static int command_ble_adv_scan(int argc, char **argv)
|
|
{
|
|
int chan, packets, i;
|
|
int addr_lsbyte;
|
|
char *e;
|
|
int rv;
|
|
|
|
if (argc < 2)
|
|
return EC_ERROR_PARAM_COUNT;
|
|
|
|
chan = strtoi(argv[1], &e, 0);
|
|
if (*e || chan < 37 || chan > 39)
|
|
return EC_ERROR_PARAM1;
|
|
|
|
chan = strtoi(argv[1], &e, 0);
|
|
if (*e || chan < 37 || chan > 39)
|
|
return EC_ERROR_PARAM1;
|
|
|
|
if (argc >= 3) {
|
|
packets = strtoi(argv[2], &e, 0);
|
|
if (*e || packets < 0)
|
|
return EC_ERROR_PARAM2;
|
|
} else {
|
|
packets = 1;
|
|
}
|
|
|
|
if (argc >= 4) {
|
|
addr_lsbyte = strtoi(argv[3], &e, 0);
|
|
if (*e || addr_lsbyte > 255)
|
|
return EC_ERROR_PARAM3;
|
|
} else {
|
|
addr_lsbyte = -1;
|
|
}
|
|
|
|
rv = ble_radio_init(BLE_ADV_ACCESS_ADDRESS, BLE_ADV_CRCINIT);
|
|
|
|
/* Change channel */
|
|
NRF51_RADIO_FREQUENCY = NRF51_RADIO_FREQUENCY_VAL(chan2freq(chan));
|
|
NRF51_RADIO_DATAWHITEIV = chan;
|
|
|
|
CPRINTS("ADV Listen");
|
|
if (addr_lsbyte != -1)
|
|
CPRINTS("filtered (%x)", addr_lsbyte);
|
|
|
|
for (i = 0; i < packets; i++) {
|
|
rv = ble_rx(&rcv_packet, 1000000, 1);
|
|
|
|
if (rv == EC_ERROR_TIMEOUT)
|
|
continue;
|
|
|
|
if (addr_lsbyte == -1 || rcv_packet.payload[0] == addr_lsbyte)
|
|
dump_ble_packet(&rcv_packet);
|
|
}
|
|
|
|
rv = radio_disable();
|
|
|
|
CPRINTS("on_air payload rcvd %p", &rx_packet);
|
|
|
|
return rv;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(ble_scan, command_ble_adv_scan,
|
|
"chan [num] [addr0]",
|
|
"Scan for [num] BLE packets on channel chan");
|
|
|