| /* 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_ll.h" |
| #include "bluetooth_le.h" |
| #include "btle_hci_int.h" |
| #include "util.h" |
| #include "console.h" |
| #include "radio.h" |
| #include "radio_test.h" |
| #include "task.h" |
| #include "timer.h" |
| |
| #ifdef CONFIG_BLUETOOTH_LL_DEBUG |
| |
| #define CPUTS(outstr) cputs(CC_BLUETOOTH_LL, outstr) |
| #define CPRINTS(format, args...) cprints(CC_BLUETOOTH_LL, format, ## args) |
| #define CPRINTF(format, args...) cprintf(CC_BLUETOOTH_LL, format, ## args) |
| |
| #else /* CONFIG_BLUETOOTH_LL_DEBUG */ |
| |
| #define CPUTS(outstr) |
| #define CPRINTS(format, args...) |
| #define CPRINTF(format, args...) |
| |
| #endif /* CONFIG_BLUETOOTH_LL_DEBUG */ |
| |
| /* Link Layer */ |
| |
| enum ll_state_t ll_state = UNINITIALIZED; |
| |
| static struct hciLeSetAdvParams ll_adv_params; |
| static struct hciLeSetScanParams ll_scan_params; |
| static int ll_adv_interval_us; |
| static int ll_adv_timeout_us; |
| |
| static struct ble_pdu ll_adv_pdu; |
| static struct ble_pdu ll_scan_rsp_pdu; |
| static struct ble_pdu tx_packet_1; |
| static struct ble_pdu *packet_tb_sent; |
| static struct ble_connection_params conn_params; |
| static int connection_initialized; |
| static struct remapping_table remap_table; |
| |
| static uint64_t receive_time, last_receive_time; |
| static uint8_t num_consecutive_failures; |
| |
| static uint32_t tx_end, tx_rsp_end, time_of_connect_req; |
| struct ble_pdu ll_rcv_packet; |
| static uint32_t ll_conn_events; |
| static uint32_t errors_recovered; |
| |
| int ll_power; |
| uint8_t is_first_data_packet; |
| |
| static uint64_t ll_random_address = 0xC5BADBADBAD1; /* Uninitialized */ |
| static uint64_t ll_public_address = 0xC5BADBADBADF; /* Uninitialized */ |
| static uint8_t ll_channel_map[5] = {0xff, 0xff, 0xff, 0xff, 0x1f}; |
| |
| static uint8_t ll_filter_duplicates; |
| |
| int ll_pseudo_rand(int max_plus_one) |
| { |
| static uint32_t lfsr = 0x55555; |
| int lsb = lfsr & 1; |
| |
| lfsr = lfsr >> 1; |
| if (lsb) |
| lfsr ^= 0x80020003; /* Bits 32, 22, 2, 1 */ |
| return lfsr % max_plus_one; |
| } |
| |
| uint8_t ll_set_tx_power(uint8_t *params) |
| { |
| /* Add checking */ |
| ll_power = params[0]; |
| return HCI_SUCCESS; |
| } |
| |
| uint8_t ll_read_tx_power(void) |
| { |
| return ll_power; |
| } |
| |
| /* LE Information */ |
| uint8_t ll_read_buffer_size(uint8_t *return_params) |
| { |
| return_params[0] = LL_MAX_DATA_PACKET_LENGTH & 0xff; |
| return_params[1] = (LL_MAX_DATA_PACKET_LENGTH >> 8) & 0xff; |
| return_params[2] = LL_MAX_DATA_PACKETS; |
| return HCI_SUCCESS; |
| } |
| |
| uint8_t ll_read_local_supported_features(uint8_t *return_params) |
| { |
| uint64_t supported_features = LL_SUPPORTED_FEATURES; |
| |
| memcpy(return_params, &supported_features, sizeof(supported_features)); |
| return HCI_SUCCESS; |
| } |
| |
| uint8_t ll_read_supported_states(uint8_t *return_params) |
| { |
| uint64_t supported_states = LL_SUPPORTED_STATES; |
| |
| memcpy(return_params, &supported_states, sizeof(supported_states)); |
| return HCI_SUCCESS; |
| } |
| |
| uint8_t ll_set_host_channel_classification(uint8_t *params) |
| { |
| memcpy(ll_channel_map, params, sizeof(ll_channel_map)); |
| return HCI_SUCCESS; |
| } |
| |
| /* Advertising */ |
| uint8_t ll_set_scan_response_data(uint8_t *params) |
| { |
| if (params[0] > BLE_MAX_ADV_PAYLOAD_OCTETS) |
| return HCI_ERR_Invalid_HCI_Command_Parameters; |
| |
| if (ll_state == ADVERTISING) |
| return HCI_ERR_Controller_Busy; |
| |
| memcpy(&ll_scan_rsp_pdu.payload[BLUETOOTH_ADDR_OCTETS], ¶ms[1], |
| params[0]); |
| ll_scan_rsp_pdu.header.adv.length = params[0] + BLUETOOTH_ADDR_OCTETS; |
| |
| return HCI_SUCCESS; |
| } |
| |
| uint8_t ll_set_adv_data(uint8_t *params) |
| { |
| if (params[0] > BLE_MAX_ADV_PAYLOAD_OCTETS) |
| return HCI_ERR_Invalid_HCI_Command_Parameters; |
| |
| if (ll_state == ADVERTISING) |
| return HCI_ERR_Controller_Busy; |
| |
| /* Skip the address */ |
| memcpy(&ll_adv_pdu.payload[BLUETOOTH_ADDR_OCTETS], ¶ms[1], |
| params[0]); |
| ll_adv_pdu.header.adv.length = params[0] + BLUETOOTH_ADDR_OCTETS; |
| |
| return HCI_SUCCESS; |
| } |
| |
| uint8_t ll_reset(void) |
| { |
| ll_state = UNINITIALIZED; |
| radio_disable(); |
| |
| ble_radio_clear_white_list(); |
| |
| return HCI_SUCCESS; |
| } |
| |
| static uint8_t ll_state_change_request(enum ll_state_t next_state) |
| { |
| /* Initialize the radio if it hasn't been initialized */ |
| if (ll_state == UNINITIALIZED) { |
| if (ble_radio_init(BLE_ADV_ACCESS_ADDRESS, BLE_ADV_CRCINIT) |
| != EC_SUCCESS) |
| return HCI_ERR_Hardware_Failure; |
| ll_state = STANDBY; |
| } |
| |
| /* Only change states when the link layer is in STANDBY */ |
| if (next_state != STANDBY && ll_state != STANDBY) |
| return HCI_ERR_Controller_Busy; |
| |
| ll_state = next_state; |
| |
| return HCI_SUCCESS; |
| } |
| |
| uint8_t ll_set_advertising_enable(uint8_t *params) |
| { |
| uint8_t rv; |
| |
| if (params[0]) { |
| rv = ll_state_change_request(ADVERTISING); |
| if (rv == HCI_SUCCESS) |
| task_wake(TASK_ID_BLE_LL); |
| } else { |
| rv = ll_state_change_request(STANDBY); |
| } |
| |
| return rv; |
| } |
| |
| uint8_t ll_set_scan_enable(uint8_t *params) |
| { |
| uint8_t rv; |
| |
| if (params[0]) { |
| ll_filter_duplicates = params[1]; |
| rv = ll_state_change_request(SCANNING); |
| if (rv == HCI_SUCCESS) |
| task_wake(TASK_ID_BLE_LL); |
| } else { |
| rv = ll_state_change_request(STANDBY); |
| } |
| |
| return HCI_SUCCESS; |
| } |
| |
| void set_empty_data_packet(struct ble_pdu *pdu) |
| { |
| /* LLID == 1 means incomplete or empty data packet */ |
| pdu->header.data.llid = 1; |
| pdu->header.data.nesn = 1; |
| pdu->header.data.sn = 0; |
| pdu->header.data.md = 0; |
| pdu->header.data.length = 0; |
| pdu->header_type_adv = 0; |
| } |
| |
| /* Connection state */ |
| |
| /** |
| * This function serves to take data from a CONNECT_REQ packet and copy it |
| * into a struct, conn_params, which defines the parameter of the connection. |
| * It also fills a remapping table, another essential element of the link |
| * layer connection. |
| */ |
| uint8_t initialize_connection(void) |
| { |
| int cur_offset = 0, i = 0; |
| uint8_t final_octet = 0; |
| uint8_t remap_arr[5]; |
| uint8_t *payload_start = (uint8_t *)(ll_rcv_packet.payload); |
| |
| num_consecutive_failures = 0; |
| |
| /* Copy data into the appropriate portions of memory */ |
| memcpy((uint8_t *)&(conn_params.init_a), |
| payload_start, CONNECT_REQ_INITA_LEN); |
| cur_offset += CONNECT_REQ_INITA_LEN; |
| |
| memcpy((uint8_t *)&(conn_params.adv_a), |
| payload_start+cur_offset, CONNECT_REQ_ADVA_LEN); |
| cur_offset += CONNECT_REQ_ADVA_LEN; |
| |
| memcpy(&(conn_params.access_addr), |
| payload_start+cur_offset, CONNECT_REQ_ACCESS_ADDR_LEN); |
| cur_offset += CONNECT_REQ_ACCESS_ADDR_LEN; |
| |
| conn_params.crc_init_val = 0; |
| memcpy(&(conn_params.crc_init_val), |
| payload_start+cur_offset, CONNECT_REQ_CRC_INIT_VAL_LEN); |
| cur_offset += CONNECT_REQ_CRC_INIT_VAL_LEN; |
| |
| memcpy(&(conn_params.win_size), |
| payload_start+cur_offset, CONNECT_REQ_WIN_SIZE_LEN); |
| cur_offset += CONNECT_REQ_WIN_SIZE_LEN; |
| |
| memcpy(&(conn_params.win_offset), |
| payload_start+cur_offset, CONNECT_REQ_WIN_OFFSET_LEN); |
| cur_offset += CONNECT_REQ_WIN_OFFSET_LEN; |
| |
| memcpy(&(conn_params.interval), |
| payload_start+cur_offset, CONNECT_REQ_INTERVAL_LEN); |
| cur_offset += CONNECT_REQ_INTERVAL_LEN; |
| |
| memcpy(&(conn_params.latency), |
| payload_start+cur_offset, CONNECT_REQ_LATENCY_LEN); |
| cur_offset += CONNECT_REQ_LATENCY_LEN; |
| |
| memcpy(&(conn_params.timeout), |
| payload_start+cur_offset, CONNECT_REQ_TIMEOUT_LEN); |
| cur_offset += CONNECT_REQ_TIMEOUT_LEN; |
| |
| conn_params.channel_map = 0; |
| memcpy(&(conn_params.channel_map), |
| payload_start+cur_offset, CONNECT_REQ_CHANNEL_MAP_LEN); |
| cur_offset += CONNECT_REQ_CHANNEL_MAP_LEN; |
| |
| memcpy(&final_octet, payload_start+cur_offset, |
| CONNECT_REQ_HOP_INCREMENT_AND_SCA_LEN); |
| |
| /* last 5 bits of final_octet: */ |
| conn_params.hop_increment = final_octet & 0x1f; |
| /* first 3 bits of final_octet: */ |
| conn_params.sleep_clock_accuracy = (final_octet & 0xe0) >> 5; |
| |
| /* Set up channel mapping table */ |
| for (i = 0; i < 5; ++i) |
| remap_arr[i] = *(((uint8_t *)&(conn_params.channel_map))+i); |
| fill_remapping_table(&remap_table, remap_arr, |
| conn_params.hop_increment); |
| |
| /* Calculate transmission window parameters */ |
| conn_params.transmitWindowSize = conn_params.win_size * 1250; |
| conn_params.transmitWindowOffset = conn_params.win_offset * 1250; |
| conn_params.connInterval = conn_params.interval * 1250; |
| /* The following two lines convert ms -> microseconds */ |
| conn_params.connSlaveLatency = 1000 * conn_params.latency; |
| conn_params.connSupervisionTimeout = 10000 * conn_params.timeout; |
| /* All these times are in microseconds! */ |
| |
| /* Check for common transmission errors */ |
| if (conn_params.hop_increment < 5 || conn_params.hop_increment > 16) { |
| for (i = 0; i < 5; ++i) |
| CPRINTF("ERROR!! ILLEGAL HOP_INCREMENT!!\n"); |
| return HCI_ERR_Invalid_LMP_Parameters; |
| } |
| |
| is_first_data_packet = 1; |
| return HCI_SUCCESS; |
| } |
| |
| /* White List */ |
| uint8_t ll_clear_white_list(void) |
| { |
| if (ble_radio_clear_white_list() == EC_SUCCESS) |
| return HCI_SUCCESS; |
| else |
| return HCI_ERR_Hardware_Failure; |
| } |
| |
| uint8_t ll_read_white_list_size(uint8_t *return_params) |
| { |
| if (ble_radio_read_white_list_size(return_params) == EC_SUCCESS) |
| return HCI_SUCCESS; |
| else |
| return HCI_ERR_Hardware_Failure; |
| } |
| |
| uint8_t ll_add_device_to_white_list(uint8_t *params) |
| { |
| if (ble_radio_add_device_to_white_list(¶ms[1], params[0]) == |
| EC_SUCCESS) |
| return HCI_SUCCESS; |
| else |
| return HCI_ERR_Host_Rejected_Due_To_Limited_Resources; |
| } |
| |
| uint8_t ll_remove_device_from_white_list(uint8_t *params) |
| { |
| if (ble_radio_remove_device_from_white_list(¶ms[1], params[0]) == |
| EC_SUCCESS) |
| return HCI_SUCCESS; |
| else |
| return HCI_ERR_Hardware_Failure; |
| } |
| |
| /* Connections */ |
| uint8_t ll_read_remote_used_features(uint8_t *params) |
| { |
| uint16_t handle = params[0] | (((uint16_t)params[1]) << 8); |
| |
| CPRINTS("Read remote used features for handle %d", handle); |
| /* Check handle */ |
| return HCI_SUCCESS; |
| } |
| |
| /* RF PHY Testing */ |
| static int ll_test_packets; |
| |
| uint8_t ll_receiver_test(uint8_t *params) |
| { |
| int rv; |
| |
| ll_test_packets = 0; |
| |
| /* See if the link layer is busy */ |
| rv = ll_state_change_request(TEST_RX); |
| if (rv) |
| return rv; |
| |
| rv = ble_test_rx_init(params[0]); |
| if (rv) |
| return rv; |
| |
| CPRINTS("Start Rx test"); |
| task_wake(TASK_ID_BLE_LL); |
| |
| return HCI_SUCCESS; |
| } |
| |
| uint8_t ll_transmitter_test(uint8_t *params) |
| { |
| int rv; |
| |
| ll_test_packets = 0; |
| |
| /* See if the link layer is busy */ |
| rv = ll_state_change_request(TEST_TX); |
| if (rv) |
| return rv; |
| |
| rv = ble_test_tx_init(params[0], params[1], params[2]); |
| if (rv) |
| return rv; |
| |
| CPRINTS("Start Tx test"); |
| task_wake(TASK_ID_BLE_LL); |
| |
| return HCI_SUCCESS; |
| } |
| |
| uint8_t ll_test_end(uint8_t *return_params) |
| { |
| CPRINTS("End (%d packets)", ll_test_packets); |
| |
| ble_test_stop(); |
| |
| if (ll_state == TEST_RX) { |
| return_params[0] = ll_test_packets & 0xff; |
| return_params[1] = (ll_test_packets >> 8); |
| ll_test_packets = 0; |
| } else { |
| return_params[0] = 0; |
| return_params[1] = 0; |
| ll_test_packets = 0; |
| } |
| return ll_reset(); |
| } |
| |
| uint8_t ll_set_random_address(uint8_t *params) |
| { |
| /* No checking. The host should know the rules. */ |
| memcpy(&ll_random_address, params, |
| sizeof(struct hciLeSetRandomAddress)); |
| return HCI_SUCCESS; |
| } |
| |
| uint8_t ll_set_scan_params(uint8_t *params) |
| { |
| if (ll_state == SCANNING) |
| return HCI_ERR_Controller_Busy; |
| |
| memcpy(&ll_scan_params, params, sizeof(struct hciLeSetScanParams)); |
| |
| return HCI_SUCCESS; |
| } |
| |
| uint8_t ll_set_advertising_params(uint8_t *params) |
| { |
| if (ll_state == ADVERTISING) |
| return HCI_ERR_Controller_Busy; |
| |
| memcpy(&ll_adv_params, params, sizeof(struct hciLeSetAdvParams)); |
| |
| switch (ll_adv_params.advType) { |
| case BLE_ADV_HEADER_PDU_TYPE_ADV_NONCONN_IND: |
| case BLE_ADV_HEADER_PDU_TYPE_ADV_SCAN_IND: |
| if (ll_adv_params.advIntervalMin < |
| (100000 / LL_ADV_INTERVAL_UNIT_US)) /* 100ms */ |
| return HCI_ERR_Invalid_HCI_Command_Parameters; |
| /* Fall through */ |
| case BLE_ADV_HEADER_PDU_TYPE_ADV_IND: |
| if (ll_adv_params.advIntervalMin > ll_adv_params.advIntervalMax) |
| return HCI_ERR_Invalid_HCI_Command_Parameters; |
| if (ll_adv_params.advIntervalMin < |
| (20000 / LL_ADV_INTERVAL_UNIT_US) || /* 20ms */ |
| ll_adv_params.advIntervalMax > |
| (10240000 / LL_ADV_INTERVAL_UNIT_US)) /* 10.24s */ |
| return HCI_ERR_Invalid_HCI_Command_Parameters; |
| ll_adv_interval_us = (((ll_adv_params.advIntervalMin + |
| ll_adv_params.advIntervalMax) / 2) * |
| LL_ADV_INTERVAL_UNIT_US); |
| /* Don't time out */ |
| ll_adv_timeout_us = -1; |
| break; |
| case BLE_ADV_HEADER_PDU_TYPE_ADV_DIRECT_IND: |
| ll_adv_interval_us = LL_ADV_DIRECT_INTERVAL_US; |
| ll_adv_timeout_us = LL_ADV_DIRECT_TIMEOUT_US; |
| break; |
| default: |
| return HCI_ERR_Invalid_HCI_Command_Parameters; |
| } |
| |
| /* Initialize the ADV PDU */ |
| ll_adv_pdu.header_type_adv = 1; |
| ll_adv_pdu.header.adv.type = ll_adv_params.advType; |
| ll_adv_pdu.header.adv.txaddr = ll_adv_params.useRandomAddress; |
| |
| if (ll_adv_params.useRandomAddress) |
| memcpy(ll_adv_pdu.payload, &ll_random_address, |
| BLUETOOTH_ADDR_OCTETS); |
| else |
| memcpy(ll_adv_pdu.payload, &ll_public_address, |
| BLUETOOTH_ADDR_OCTETS); |
| |
| if (ll_adv_params.advType == BLE_ADV_HEADER_PDU_TYPE_ADV_DIRECT_IND) { |
| ll_adv_pdu.header.adv.rxaddr = |
| ll_adv_params.directRandomAddress; |
| memcpy(&ll_adv_pdu.payload[BLUETOOTH_ADDR_OCTETS], |
| ll_adv_params.directAddr, |
| sizeof(ll_adv_params.directAddr)); |
| ll_adv_pdu.header.adv.length = 12; |
| } else { |
| ll_adv_pdu.header.adv.rxaddr = 0; |
| } |
| |
| /* All other types get data from SetAdvertisingData */ |
| |
| /* Initialize the Scan Rsp PDU */ |
| ll_scan_rsp_pdu.header_type_adv = 1; |
| ll_scan_rsp_pdu.header.adv.type = BLE_ADV_HEADER_PDU_TYPE_SCAN_RSP; |
| ll_scan_rsp_pdu.header.adv.txaddr = ll_adv_params.useRandomAddress; |
| |
| if (ll_adv_params.useRandomAddress) |
| memcpy(ll_scan_rsp_pdu.payload, &ll_random_address, |
| BLUETOOTH_ADDR_OCTETS); |
| else |
| memcpy(ll_scan_rsp_pdu.payload, &ll_public_address, |
| BLUETOOTH_ADDR_OCTETS); |
| |
| ll_scan_rsp_pdu.header.adv.rxaddr = 0; |
| |
| return HCI_SUCCESS; |
| } |
| |
| static uint32_t tx_end, rsp_end, tx_rsp_end; |
| struct ble_pdu ll_rcv_packet; |
| |
| /** |
| * Advertises packet that has already been generated on given channel. |
| * |
| * This function also processes any incoming scan requests. |
| * |
| * @param chan The channel on which to advertise. |
| * @returns EC_SUCCESS on packet reception, otherwise error. |
| */ |
| int ble_ll_adv(int chan) |
| { |
| int 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; |
| |
| ble_tx(&ll_adv_pdu); |
| |
| while (!RADIO_DONE) |
| ; |
| |
| tx_end = get_time().le.lo; |
| |
| if (ll_adv_pdu.header.adv.type == |
| BLE_ADV_HEADER_PDU_TYPE_ADV_NONCONN_IND) |
| return EC_SUCCESS; |
| |
| rv = ble_rx(&ll_rcv_packet, 16000, 1); |
| |
| if (rv != EC_SUCCESS) |
| return rv; |
| |
| while (!RADIO_DONE) |
| ; |
| |
| tx_rsp_end = get_time().le.lo; |
| |
| /* Check for valid responses */ |
| switch (ll_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 ((ll_adv_pdu.header.adv.type != |
| BLE_ADV_HEADER_PDU_TYPE_ADV_IND && |
| ll_adv_pdu.header.adv.type != |
| BLE_ADV_HEADER_PDU_TYPE_ADV_SCAN_IND) || |
| /* The advertising address needs to match */ |
| (memcmp(&ll_rcv_packet.payload[BLUETOOTH_ADDR_OCTETS], |
| &ll_adv_pdu.payload[0], BLUETOOTH_ADDR_OCTETS))) { |
| /* Don't send the scan response */ |
| radio_disable(); |
| return rv; |
| } |
| break; |
| case BLE_ADV_HEADER_PDU_TYPE_CONNECT_REQ: |
| /* Don't send a scan response */ |
| radio_disable(); |
| /* Connecting is only allowed for ADV_IND and ADV_DIRECT_IND */ |
| if (ll_adv_pdu.header.adv.type != |
| BLE_ADV_HEADER_PDU_TYPE_ADV_IND && |
| ll_adv_pdu.header.adv.type != |
| BLE_ADV_HEADER_PDU_TYPE_ADV_DIRECT_IND) |
| return rv; |
| /* The advertising address needs to match */ |
| if (memcmp(&ll_rcv_packet.payload[BLUETOOTH_ADDR_OCTETS], |
| &ll_adv_pdu.payload[0], BLUETOOTH_ADDR_OCTETS)) |
| return rv; |
| /* The InitAddr address needs to match for ADV_DIRECT_IND */ |
| if (ll_adv_pdu.header.adv.type == |
| BLE_ADV_HEADER_PDU_TYPE_ADV_DIRECT_IND && |
| memcmp(&ll_adv_pdu.payload[BLUETOOTH_ADDR_OCTETS], |
| &ll_rcv_packet.payload[0], BLUETOOTH_ADDR_OCTETS)) |
| return rv; |
| |
| /* Mark time that connect was received */ |
| time_of_connect_req = NRF51_TIMER_CC(0, 1); |
| |
| /* |
| * Enter connection state upon receiving |
| * a connect request packet |
| */ |
| ll_state = CONNECTION; |
| |
| return rv; |
| break; |
| default: /* Unhandled response packet */ |
| radio_disable(); |
| return rv; |
| break; |
| } |
| |
| CPRINTF("ADV %u Response %u %u\n", tx_end, rsp_end, tx_rsp_end); |
| |
| return rv; |
| } |
| |
| int ble_ll_adv_event(void) |
| { |
| int chan_idx; |
| int rv; |
| |
| for (chan_idx = 0; chan_idx < 3; chan_idx++) { |
| if (ll_adv_params.advChannelMap & BIT(chan_idx)) { |
| rv = ble_ll_adv(chan_idx + 37); |
| if (rv != EC_SUCCESS) |
| return rv; |
| } |
| } |
| |
| return rv; |
| } |
| |
| |
| void print_connection_state(void) |
| { |
| CPRINTF("vvvvvvvvvvvvvvvvvvvCONNECTION STATEvvvvvvvvvvvvvvvvvvv\n"); |
| CPRINTF("Number of connections events processed: %d\n", ll_conn_events); |
| CPRINTF("Recovered from %d bad receives.\n", errors_recovered); |
| CPRINTF("Access addr(hex): %x\n", conn_params.access_addr); |
| CPRINTF("win_size(hex): %x\n", conn_params.win_size); |
| CPRINTF("win_offset(hex): %x\n", conn_params.win_offset); |
| CPRINTF("interval(hex): %x\n", conn_params.interval); |
| CPRINTF("latency(hex): %x\n", conn_params.latency); |
| CPRINTF("timeout(hex): %x\n", conn_params.timeout); |
| CPRINTF("channel_map(hex): %lx\n", conn_params.channel_map); |
| CPRINTF("hop(hex): %x\n", conn_params.hop_increment); |
| CPRINTF("SCA(hex): %x\n", conn_params.sleep_clock_accuracy); |
| CPRINTF("transmitWindowOffset: %d\n", conn_params.transmitWindowOffset); |
| CPRINTF("connInterval: %d\n", conn_params.connInterval); |
| CPRINTF("transmitWindowSize: %d\n", conn_params.transmitWindowSize); |
| CPRINTF("^^^^^^^^^^^^^^^^^^^CONNECTION STATE^^^^^^^^^^^^^^^^^^^\n"); |
| } |
| |
| int connected_communicate(void) |
| { |
| int rv; |
| long sleep_time; |
| int offset = 0; |
| uint64_t listen_time; |
| uint8_t comm_channel = get_next_data_channel(&remap_table); |
| |
| if (num_consecutive_failures > 0) { |
| ble_radio_init(conn_params.access_addr, |
| conn_params.crc_init_val); |
| NRF51_RADIO_FREQUENCY = |
| NRF51_RADIO_FREQUENCY_VAL(chan2freq(comm_channel)); |
| NRF51_RADIO_DATAWHITEIV = comm_channel; |
| listen_time = last_receive_time + conn_params.connInterval |
| - get_time().val + conn_params.transmitWindowSize; |
| |
| /* |
| * This listens for 1.25 times the expected amount |
| * of time. This is a margin of error. This line is |
| * only called when a connection has failed (a missed |
| * packet). The slave and the master could have |
| * missed this packet due to a disagreement on when |
| * the packet should have arrived. We listen for |
| * slightly longer than expected in the case that |
| * there was a timing disagreement. |
| */ |
| rv = ble_rx(&ll_rcv_packet, |
| listen_time + (listen_time >> 2), 0); |
| } else { |
| if (!is_first_data_packet) { |
| sleep_time = receive_time + |
| conn_params.connInterval - get_time().val; |
| /* |
| * The time slept is 31/32 (96.875%) of the calculated |
| * required sleep time because the code to receive |
| * packets requires time to set up. |
| */ |
| usleep(sleep_time - (sleep_time >> 5)); |
| } else { |
| last_receive_time = time_of_connect_req; |
| sleep_time = TRANSMIT_WINDOW_OFFSET_CONSTANT + |
| conn_params.transmitWindowOffset + |
| time_of_connect_req - get_time().val; |
| if (sleep_time >= 0) { |
| /* |
| * Radio is on for longer than needed for first |
| * packet to make sure that it is received. |
| */ |
| usleep(sleep_time - (sleep_time >> 2)); |
| } else { |
| return EC_ERROR_TIMEOUT; |
| } |
| } |
| |
| ble_radio_init(conn_params.access_addr, |
| conn_params.crc_init_val); |
| NRF51_RADIO_FREQUENCY = |
| NRF51_RADIO_FREQUENCY_VAL(chan2freq(comm_channel)); |
| NRF51_RADIO_DATAWHITEIV = comm_channel; |
| |
| /* |
| * Timing the transmit window is very hard to do when the code |
| * executing has actual effect on the timing. To combat this, |
| * the radio starts a little early, and terminates when the |
| * window normally should. The variable 'offset' represents |
| * how early the window opens in microseconds. |
| */ |
| if (!is_first_data_packet) |
| offset = last_receive_time + conn_params.connInterval |
| - get_time().val; |
| else |
| offset = 0; |
| |
| rv = ble_rx(&ll_rcv_packet, |
| offset + conn_params.transmitWindowSize, |
| 0); |
| } |
| |
| /* |
| * The radio shortcuts have been set up so that transmission |
| * occurs automatically after receiving. The radio just needs |
| * to know where to find the packet to be sent. |
| */ |
| NRF51_RADIO_PACKETPTR = (uint32_t)packet_tb_sent; |
| |
| receive_time = NRF51_TIMER_CC(0, 1); |
| if (rv != EC_SUCCESS) |
| receive_time = last_receive_time + conn_params.connInterval; |
| |
| while (!RADIO_DONE) |
| ; |
| |
| last_receive_time = receive_time; |
| is_first_data_packet = 0; |
| |
| return rv; |
| } |
| |
| static uint32_t ll_adv_events; |
| static timestamp_t deadline; |
| static uint32_t start, end; |
| |
| void bluetooth_ll_task(void) |
| { |
| uint64_t last_rx_time = 0; |
| CPRINTS("LL task init"); |
| |
| while (1) { |
| switch (ll_state) { |
| case ADVERTISING: |
| |
| if (deadline.val == 0) { |
| CPRINTS("ADV @%p", &ll_adv_pdu); |
| deadline.val = get_time().val + |
| (uint32_t)ll_adv_timeout_us; |
| ll_adv_events = 0; |
| } |
| |
| ble_ll_adv_event(); |
| ll_adv_events++; |
| |
| if (ll_state == CONNECTION) { |
| receive_time = 0; |
| break; |
| } |
| /* sleep for 0-10ms */ |
| usleep(ll_adv_interval_us + ll_pseudo_rand(10000)); |
| |
| if (get_time().val > deadline.val) { |
| ll_state = STANDBY; |
| break; |
| } |
| break; |
| case STANDBY: |
| deadline.val = 0; |
| CPRINTS("Standby %d events", ll_adv_events); |
| ll_adv_events = 0; |
| ll_conn_events = 0; |
| task_wait_event(-1); |
| connection_initialized = 0; |
| errors_recovered = 0; |
| break; |
| case TEST_RX: |
| if (ble_test_rx() == HCI_SUCCESS) |
| ll_test_packets++; |
| /* Packets come every 625us, sleep to save power */ |
| usleep(300); |
| break; |
| case TEST_TX: |
| start = get_time().le.lo; |
| ble_test_tx(); |
| ll_test_packets++; |
| end = get_time().le.lo; |
| usleep(625 - 82 - (end-start)); /* 625us */ |
| break; |
| case UNINITIALIZED: |
| ble_radio_init(BLE_ADV_ACCESS_ADDRESS, BLE_ADV_CRCINIT); |
| ll_adv_events = 0; |
| task_wait_event(-1); |
| connection_initialized = 0; |
| packet_tb_sent = &tx_packet_1; |
| set_empty_data_packet(&tx_packet_1); |
| break; |
| case CONNECTION: |
| if (!connection_initialized) { |
| if (initialize_connection() != HCI_SUCCESS) { |
| ll_state = STANDBY; |
| break; |
| } |
| connection_initialized = 1; |
| last_rx_time = NRF51_TIMER_CC(0, 1); |
| } |
| |
| if (connected_communicate() == EC_SUCCESS) { |
| if (num_consecutive_failures > 0) |
| ++errors_recovered; |
| num_consecutive_failures = 0; |
| last_rx_time = get_time().val; |
| } else { |
| num_consecutive_failures++; |
| if ((get_time().val - last_rx_time) > |
| conn_params.connSupervisionTimeout) { |
| |
| ll_state = STANDBY; |
| CPRINTF("EXITING CONNECTION STATE " |
| "DUE TO TIMEOUT.\n"); |
| } |
| } |
| ++ll_conn_events; |
| |
| if (ll_state == STANDBY) { |
| CPRINTF("Exiting connection state/Entering " |
| "Standby state after %d connections ", |
| "events\n", ll_conn_events); |
| print_connection_state(); |
| } |
| break; |
| default: |
| CPRINTS("Unhandled State ll_state = %d", ll_state); |
| ll_state = UNINITIALIZED; |
| task_wait_event(-1); |
| } |
| } |
| } |
| |