diff options
Diffstat (limited to 'circuitpython/devices/ble_hci/common-hal/_bleio/hci.c')
-rw-r--r-- | circuitpython/devices/ble_hci/common-hal/_bleio/hci.c | 805 |
1 files changed, 805 insertions, 0 deletions
diff --git a/circuitpython/devices/ble_hci/common-hal/_bleio/hci.c b/circuitpython/devices/ble_hci/common-hal/_bleio/hci.c new file mode 100644 index 0000000..ec16635 --- /dev/null +++ b/circuitpython/devices/ble_hci/common-hal/_bleio/hci.c @@ -0,0 +1,805 @@ +// This file is derived from the ArduinoBLE library. Its header is below. +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "att.h" +#include "hci.h" + +#include "py/obj.h" +#include "py/mperrno.h" +#include "py/runtime.h" + +// Zephyr include files to define HCI communication values and structs. +#include "hci_include/hci.h" +#include "hci_include/hci_err.h" +#include "hci_include/l2cap_internal.h" + +#include <string.h> + +#include "py/mphal.h" // ***************************** +#include "supervisor/shared/tick.h" +#include "shared-bindings/_bleio/__init__.h" +#include "common-hal/_bleio/Adapter.h" +#include "shared-bindings/microcontroller/__init__.h" + +// Set to 1 for extensive HCI packet logging. +#define HCI_DEBUG 0 + +// HCI H4 protocol packet types: first byte in the packet. +#define H4_CMD 0x01 +#define H4_ACL 0x02 +#define H4_SCO 0x03 +#define H4_EVT 0x04 + +#define CTS_TIMEOUT_MSECS (1000) +#define RESPONSE_TIMEOUT_MSECS (1000) + +// These are the headers of the full packets that are sent over the serial interface. +// They all have a one-byte type-field at the front, one of the H4_xxx packet types. + +typedef struct __attribute__ ((packed)) { + uint8_t pkt_type; + uint16_t opcode; + uint8_t param_len; + uint8_t params[]; +} h4_hci_cmd_pkt_t; + +#define ACL_DATA_PB_FIRST_NON_FLUSH 0 +#define ACL_DATA_PB_MIDDLE 1 +#define ACL_DATA_PB_FIRST_FLUSH 2 +#define ACL_DATA_PB_FULL 3 + +typedef struct __attribute__ ((packed)) { + uint8_t pkt_type; + uint16_t handle : 12; + uint8_t pb : 2; // Packet boundary flag: ACL_DATA_PB values. + uint8_t bc : 2; // Broadcast flag: always 0b00 for BLE. + uint16_t data_len; // length of data[] in this packet. + uint8_t data[]; +} h4_hci_acl_pkt_t; + +// The ACL data in an h4_hci_acl_pkt_t may be fragmented across +// multiple ACL_DATA packets, and need to be recombined. This is the +// structure of the combined packet or the first fragment. +typedef struct __attribute__ ((packed)) { + uint16_t acl_data_len; // Length of acl_data. Does not include this header. + uint16_t cid; // Channel ID. + uint8_t acl_data[]; // Length is acl_data_len of full packet. +} acl_data_t; + +typedef struct __attribute__ ((packed)) { + uint8_t pkt_type; + uint8_t evt; + uint8_t param_len; + uint8_t params[]; +} h4_hci_evt_pkt_t; + + +////////////////////////////////////////////////////////////////////// +// Static storage: + +// FIX size +#define RX_BUFFER_SIZE (3 + 255) +#define ACL_DATA_BUFFER_SIZE (255) + +STATIC uint8_t rx_buffer[RX_BUFFER_SIZE]; +STATIC size_t rx_idx; + +STATIC uint8_t acl_data_buffer[ACL_DATA_BUFFER_SIZE]; +STATIC size_t acl_data_len; + +STATIC size_t num_command_packets_allowed; +STATIC volatile size_t pending_pkt; + +// Results from parsing a command response packet. +STATIC bool cmd_response_received; +STATIC uint16_t cmd_response_opcode; +STATIC uint8_t cmd_response_status; +STATIC size_t cmd_response_len; +STATIC uint8_t *cmd_response_data; + +STATIC volatile bool hci_poll_in_progress = false; + + +////////////////////////////////////////////////////////////////////// + +#if HCI_DEBUG +#include "hci_debug.c" +#endif // HCI_DEBUG + +STATIC void process_acl_data_pkt(uint8_t pkt_len, uint8_t pkt_data[]) { + h4_hci_acl_pkt_t *pkt = (h4_hci_acl_pkt_t *)pkt_data; + + if (pkt->pb != ACL_DATA_PB_MIDDLE) { + // This is the start of a fragmented acl_data packet or is a full packet. + memcpy(acl_data_buffer, pkt->data, pkt->data_len); + acl_data_len = pkt->data_len; + } else { + // This is a middle or end fragment of acl data. + // Append to the accumulated data so far. + memcpy(&acl_data_buffer[acl_data_len], pkt->data, pkt->data_len); + acl_data_len += pkt->data_len; + } + + acl_data_t *acl = (acl_data_t *)&acl_data_buffer; + if (acl_data_len != sizeof(acl) + acl->acl_data_len) { + // We don't have the full packet yet. + return; + } + + if (acl->cid == BT_L2CAP_CID_ATT) { + att_process_data(pkt->handle, acl->acl_data_len, acl->acl_data); + } + // } else if (aclHdr->cid == BT_L2CAP_CID_LE_SIG) { + // L2CAPSignaling.handleData(aclHdr->handle & 0x0fff, aclHdr->len, &rx_buffer[1 + sizeof(HCIACLHdr)]); + // } else { + // struct __attribute__ ((packed)) { + // uint8_t op; + // uint8_t id; + // uint16_t length; + // uint16_t reason; + // uint16_t localCid; + // uint16_t remoteCid; + // } l2capRejectCid= { 0x01, 0x00, 0x006, 0x0002, aclHdr->cid, 0x0000 }; + + // sendAclPkt(aclHdr->handle & 0x0fff, 0x0005, sizeof(l2capRejectCid), &l2capRejectCid); + // } +} + +// Process number of completed packets. Reduce number of pending packets by reported +// number of completed. +STATIC void process_num_comp_pkts(uint16_t handle, uint16_t num_pkts) { + if (num_pkts && pending_pkt > num_pkts) { + pending_pkt -= num_pkts; + } else { + pending_pkt = 0; + } +} + +STATIC void process_evt_pkt(size_t pkt_len, uint8_t pkt_data[]) { + h4_hci_evt_pkt_t *pkt = (h4_hci_evt_pkt_t *)pkt_data; + + switch (pkt->evt) { + case BT_HCI_EVT_DISCONN_COMPLETE: { + struct bt_hci_evt_disconn_complete *disconn_complete = + (struct bt_hci_evt_disconn_complete *)pkt->params; + (void)disconn_complete; + + att_remove_connection(disconn_complete->handle, disconn_complete->reason); + // FIX L2CAPSignaling.removeConnection(disconn_complete->handle, disconn_complete->reason); + break; + } + + case BT_HCI_EVT_CMD_COMPLETE: { + struct cmd_complete_with_status { + struct bt_hci_evt_cmd_complete cmd_complete; + struct bt_hci_evt_cc_status cc_status; + } __packed; + + struct cmd_complete_with_status *evt = (struct cmd_complete_with_status *)pkt->params; + + num_command_packets_allowed = evt->cmd_complete.ncmd; + + cmd_response_received = true; + cmd_response_opcode = evt->cmd_complete.opcode; + cmd_response_status = evt->cc_status.status; + // All the bytes following cmd_complete, -including- the status byte, which is + // included in all the _bt_hci_rp_* structs. + cmd_response_data = (uint8_t *)&evt->cc_status; + // Includes status byte. + cmd_response_len = pkt->param_len - sizeof_field(struct cmd_complete_with_status, cmd_complete); + + break; + } + + case BT_HCI_EVT_CMD_STATUS: { + struct bt_hci_evt_cmd_status *evt = (struct bt_hci_evt_cmd_status *)pkt->params; + + num_command_packets_allowed = evt->ncmd; + + cmd_response_received = true; + cmd_response_opcode = evt->opcode; + cmd_response_status = evt->status; + cmd_response_data = NULL; + cmd_response_len = 0; + + break; + } + + case BT_HCI_EVT_NUM_COMPLETED_PACKETS: { + struct bt_hci_evt_num_completed_packets *evt = + (struct bt_hci_evt_num_completed_packets *)pkt->params; + + // Start at zero-th pair: (conn handle, num completed packets). + struct bt_hci_handle_count *handle_and_count = &(evt->h[0]); + for (uint8_t i = 0; i < evt->num_handles; i++) { + process_num_comp_pkts(handle_and_count->handle, handle_and_count->count); + handle_and_count++; + } + break; + } + + case BT_HCI_EVT_LE_META_EVENT: { + struct bt_hci_evt_le_meta_event *meta_evt = (struct bt_hci_evt_le_meta_event *)pkt->params; + uint8_t *le_evt = pkt->params + sizeof (struct bt_hci_evt_le_meta_event); + + if (meta_evt->subevent == BT_HCI_EVT_LE_CONN_COMPLETE) { + // Advertising stops when connection occurs. + // We don't tell the adapter to stop, because stopping advertising + // when it's already stopped seems to exercise a bug in the ESP32 HCI code: + // It doesn't return a response. + bleio_adapter_advertising_was_stopped(&common_hal_bleio_adapter_obj); + + struct bt_hci_evt_le_conn_complete *le_conn_complete = + (struct bt_hci_evt_le_conn_complete *)le_evt; + + if (le_conn_complete->status == BT_HCI_ERR_SUCCESS) { + att_add_connection( + le_conn_complete->handle, + le_conn_complete->role, + &le_conn_complete->peer_addr, + le_conn_complete->interval, + le_conn_complete->latency, + le_conn_complete->supv_timeout, + le_conn_complete->clock_accuracy); + + } + } else if (meta_evt->subevent == BT_HCI_EVT_LE_ADVERTISING_REPORT) { + struct bt_hci_evt_le_advertising_info *le_advertising_info = + (struct bt_hci_evt_le_advertising_info *)le_evt; + if (le_advertising_info->evt_type == BT_HCI_ADV_DIRECT_IND) { + // FIX + // last byte is RSSI + // GAP.handleLeAdvertisingReport(leAdvertisingReport->type, + // leAdvertisingReport->peerBdaddrType, + // leAdvertisingReport->peerBdaddr, + // leAdvertisingReport->eirLength, + // leAdvertisingReport->eirData, + // rssi); //FIX, don't separate + + } + } + break; + } + + default: + #if HCI_DEBUG + mp_printf(&mp_plat_print, "process_evt_pkt: Unknown event: %02x\n"); + #endif + break; + } +} + +void bleio_hci_reset(void) { + rx_idx = 0; + pending_pkt = 0; + hci_poll_in_progress = false; + bleio_att_reset(); +} + +hci_result_t hci_poll_for_incoming_pkt(void) { + common_hal_mcu_disable_interrupts(); + if (hci_poll_in_progress) { + common_hal_mcu_enable_interrupts(); + return HCI_OK; + } + hci_poll_in_progress = true; + common_hal_mcu_enable_interrupts(); + + // Assert RTS low to say we're ready to read data. + common_hal_digitalio_digitalinout_set_value(common_hal_bleio_adapter_obj.rts_digitalinout, false); + + int errcode = 0; + bool packet_is_complete = false; + + // Read bytes until we run out. There may be more than one packet in the input buffer. + while (!packet_is_complete && + common_hal_busio_uart_rx_characters_available(common_hal_bleio_adapter_obj.hci_uart) > 0) { + + // Read just one character a a time, so we don't accidentally get part of a second + // packet. + size_t num_read = + common_hal_busio_uart_read(common_hal_bleio_adapter_obj.hci_uart, rx_buffer + rx_idx, 1, &errcode); + if (num_read == 0) { + return HCI_OK; + } + if (errcode) { + if (errcode == EAGAIN) { + continue; + } + hci_poll_in_progress = false; + mp_printf(&mp_plat_print, "HCI_READ_ERROR, errcode: %x\n", errcode); + return HCI_READ_ERROR; + } + rx_idx++; + if (rx_idx >= sizeof(rx_buffer)) { + // Incoming packet is too large. Should not happen. + return HCI_READ_ERROR; + } + + switch (rx_buffer[0]) { + case H4_ACL: + if (rx_idx >= sizeof(h4_hci_acl_pkt_t)) { + const size_t total_len = + sizeof(h4_hci_acl_pkt_t) + ((h4_hci_acl_pkt_t *)rx_buffer)->data_len; + if (rx_idx == total_len) { + packet_is_complete = true; + } + if (rx_idx > total_len) { + return HCI_PACKET_SIZE_ERROR; + } + } + break; + + case H4_EVT: + if (rx_idx >= sizeof(h4_hci_evt_pkt_t)) { + const size_t total_len = + sizeof(h4_hci_evt_pkt_t) + ((h4_hci_evt_pkt_t *)rx_buffer)->param_len; + if (rx_idx == total_len) { + packet_is_complete = true; + } + if (rx_idx > total_len) { + return HCI_PACKET_SIZE_ERROR; + } + } + break; + + default: + // Unknown or bad packet type. Start over. + rx_idx = 0; + break; + } + } // end while + + if (packet_is_complete) { + // Stop incoming data while processing packet. + common_hal_digitalio_digitalinout_set_value(common_hal_bleio_adapter_obj.rts_digitalinout, true); + size_t pkt_len = rx_idx; + + // Reset for next packet. + rx_idx = 0; + packet_is_complete = false; + + switch (rx_buffer[0]) { + case H4_ACL: + #if HCI_DEBUG + dump_acl_pkt(false, pkt_len, rx_buffer); + #endif + process_acl_data_pkt(pkt_len, rx_buffer); + break; + + case H4_EVT: + #if HCI_DEBUG + dump_evt_pkt(false, pkt_len, rx_buffer); + #endif + process_evt_pkt(pkt_len, rx_buffer); + break; + + default: + #if HCI_DEBUG + mp_printf(&mp_plat_print, "Unknown HCI packet type: %d\n", rx_buffer[0]); + #endif + break; + } + + // Let incoming bytes flow again. + common_hal_digitalio_digitalinout_set_value(common_hal_bleio_adapter_obj.rts_digitalinout, false); + } + + // All done with this batch. Hold off receiving bytes until we're ready again. + ///common_hal_digitalio_digitalinout_set_value(common_hal_bleio_adapter_obj.rts_digitalinout, true); + hci_poll_in_progress = false; + return HCI_OK; +} + + +STATIC hci_result_t write_pkt(uint8_t *buffer, size_t len) { + // Wait for CTS to go low before writing to HCI adapter. + uint64_t start = supervisor_ticks_ms64(); + + while (common_hal_digitalio_digitalinout_get_value(common_hal_bleio_adapter_obj.cts_digitalinout)) { + RUN_BACKGROUND_TASKS; + if (supervisor_ticks_ms64() - start > CTS_TIMEOUT_MSECS) { + return HCI_WRITE_TIMEOUT; + } + } + + int errcode = 0; + common_hal_busio_uart_write(common_hal_bleio_adapter_obj.hci_uart, buffer, len, &errcode); + if (errcode) { + return HCI_WRITE_ERROR; + } + + return HCI_OK; +} + +STATIC hci_result_t send_command(uint16_t opcode, uint8_t params_len, void *params) { + uint8_t cmd_pkt_len = sizeof(h4_hci_cmd_pkt_t) + params_len; + uint8_t tx_buffer[cmd_pkt_len]; + + // cmd header is at the beginning of tx_buffer + h4_hci_cmd_pkt_t *cmd_pkt = (h4_hci_cmd_pkt_t *)tx_buffer; + cmd_pkt->pkt_type = H4_CMD; + cmd_pkt->opcode = opcode; + cmd_pkt->param_len = params_len; + + memcpy(cmd_pkt->params, params, params_len); + + #if HCI_DEBUG + dump_cmd_pkt(true, sizeof(tx_buffer), tx_buffer); + #endif + + int result = write_pkt(tx_buffer, cmd_pkt_len); + if (result != HCI_OK) { + return result; + } + + cmd_response_received = false; + + // Wait for a response. Note that other packets may be received that are not + // command responses. + uint64_t start = supervisor_ticks_ms64(); + while (supervisor_ticks_ms64() - start < RESPONSE_TIMEOUT_MSECS) { + // RUN_BACKGROUND_TASKS includes hci_poll_for_incoming_pkt(); + RUN_BACKGROUND_TASKS; + if (cmd_response_received && cmd_response_opcode == opcode) { + // If this is definitely a response to the command that was sent, + // return the status value, which will will be + // BT_HCI_ERR_SUCCESS (0x00) if the command succeeded, + // or a BT_HCI_ERR_x value (> 0x00) if there was a problem. + return cmd_response_status; + } + } + + // No I/O error, but no response sent back in time. + return HCI_RESPONSE_TIMEOUT; +} + +hci_result_t hci_send_acl_pkt(uint16_t handle, uint8_t cid, uint16_t data_len, uint8_t *data) { + // Wait for all backlogged packets to finish. + while (pending_pkt >= common_hal_bleio_adapter_obj.max_acl_num_buffers) { + // RUN_BACKGROUND_TASKS includes hci_poll_for_incoming_pkt(); + RUN_BACKGROUND_TASKS; + } + + // buf_len is size of entire packet including header. + const size_t buf_len = sizeof(h4_hci_acl_pkt_t) + sizeof(acl_data_t) + data_len; + uint8_t tx_buffer[buf_len]; + + h4_hci_acl_pkt_t *acl_pkt = (h4_hci_acl_pkt_t *)tx_buffer; + acl_data_t *acl_data = (acl_data_t *)acl_pkt->data; + acl_pkt->pkt_type = H4_ACL; + acl_pkt->handle = handle; + acl_pkt->pb = ACL_DATA_PB_FIRST_FLUSH; + acl_pkt->bc = 0; + acl_pkt->data_len = (uint16_t)(sizeof(acl_data_t) + data_len); + acl_data->acl_data_len = data_len; + acl_data->cid = cid; + + memcpy(&acl_data->acl_data, data, data_len); + + #if HCI_DEBUG + dump_acl_pkt(true, buf_len, tx_buffer); + #endif + + pending_pkt++; + + int errcode = 0; + common_hal_busio_uart_write(common_hal_bleio_adapter_obj.hci_uart, tx_buffer, buf_len, &errcode); + if (errcode) { + return HCI_WRITE_ERROR; + } + return HCI_OK; +} + +hci_result_t hci_reset(void) { + return send_command(BT_HCI_OP_RESET, 0, NULL); +} + +hci_result_t hci_read_local_version(uint8_t *hci_version, uint16_t *hci_revision, uint8_t *lmp_version, uint16_t *manufacturer, uint16_t *lmp_subversion) { + hci_result_t result = send_command(BT_HCI_OP_READ_LOCAL_VERSION_INFO, 0, NULL); + if (result == HCI_OK) { + struct bt_hci_rp_read_local_version_info *response = + (struct bt_hci_rp_read_local_version_info *)cmd_response_data; + *hci_version = response->hci_version; + *hci_revision = response->hci_revision; + *lmp_version = response->lmp_version; + *manufacturer = response->manufacturer; + *lmp_subversion = response->lmp_subversion; + } + + return result; +} + +hci_result_t hci_read_bd_addr(bt_addr_t *addr) { + int result = send_command(BT_HCI_OP_READ_BD_ADDR, 0, NULL); + if (result == HCI_OK) { + struct bt_hci_rp_read_bd_addr *response = (struct bt_hci_rp_read_bd_addr *)cmd_response_data; + memcpy(addr->val, response->bdaddr.val, sizeof_field(bt_addr_t, val)); + } + + return result; +} + +hci_result_t hci_read_rssi(uint16_t handle, int *rssi) { + int result = send_command(BT_HCI_OP_READ_RSSI, sizeof(handle), &handle); + if (result == HCI_OK) { + struct bt_hci_rp_read_rssi *response = (struct bt_hci_rp_read_rssi *)cmd_response_data; + *rssi = response->rssi; + } + + return result; +} + +hci_result_t hci_set_event_mask(uint64_t event_mask) { + return send_command(BT_HCI_OP_SET_EVENT_MASK, sizeof(event_mask), &event_mask); +} + +hci_result_t hci_le_read_buffer_size(uint16_t *le_max_len, uint8_t *le_max_num) { + int result = send_command(BT_HCI_OP_LE_READ_BUFFER_SIZE, 0, NULL); + if (result == HCI_OK) { + struct bt_hci_rp_le_read_buffer_size *response = + (struct bt_hci_rp_le_read_buffer_size *)cmd_response_data; + *le_max_len = response->le_max_len; + *le_max_num = response->le_max_num; + } + + return result; +} + +hci_result_t hci_read_buffer_size(uint16_t *acl_max_len, uint8_t *sco_max_len, uint16_t *acl_max_num, uint16_t *sco_max_num) { + int result = send_command(BT_HCI_OP_READ_BUFFER_SIZE, 0, NULL); + if (result == HCI_OK) { + struct bt_hci_rp_read_buffer_size *response = + (struct bt_hci_rp_read_buffer_size *)cmd_response_data; + *acl_max_len = response->acl_max_len; + *sco_max_len = response->sco_max_len; + *acl_max_num = response->acl_max_num; + *sco_max_num = response->sco_max_num; + } + + return result; +} + +hci_result_t hci_le_set_random_address(uint8_t addr[6]) { + return send_command(BT_HCI_OP_LE_SET_RANDOM_ADDRESS, 6, addr); +} + +hci_result_t hci_le_set_advertising_parameters(uint16_t min_interval, uint16_t max_interval, uint8_t type, uint8_t own_addr_type, bt_addr_le_t *direct_addr, uint8_t channel_map, uint8_t filter_policy) { + struct bt_hci_cp_le_set_adv_param params = { + .min_interval = min_interval, + .max_interval = max_interval, + .type = type, + .own_addr_type = own_addr_type, + // .direct_addr set below. + .channel_map = channel_map, + .filter_policy = filter_policy, + }; + params.direct_addr.type = direct_addr->type; + memcpy(params.direct_addr.a.val, direct_addr->a.val, sizeof(params.direct_addr.a.val)); + + return send_command(BT_HCI_OP_LE_SET_ADV_PARAM, sizeof(params), ¶ms); +} + +hci_result_t hci_le_set_extended_advertising_parameters(uint8_t handle, uint16_t props, uint32_t prim_min_interval, uint32_t prim_max_interval, uint8_t prim_channel_map, uint8_t own_addr_type, bt_addr_le_t *peer_addr, uint8_t filter_policy, int8_t tx_power, uint8_t prim_adv_phy, uint8_t sec_adv_max_skip, uint8_t sec_adv_phy, uint8_t sid, uint8_t scan_req_notify_enable) { + struct bt_hci_cp_le_set_ext_adv_param params = { + .handle = handle, + .props = props, + // .prim_min_interval and .prim_max_interval set below + .prim_channel_map = prim_channel_map, + .own_addr_type = own_addr_type, + // .peer_addr set below. + .tx_power = tx_power, + .sec_adv_max_skip = sec_adv_max_skip, + .sec_adv_phy = sec_adv_phy, + .sid = sid, + .scan_req_notify_enable = scan_req_notify_enable, + }; + // Assumes little-endian. + memcpy(params.prim_min_interval, (void *)&prim_min_interval, + sizeof_field(struct bt_hci_cp_le_set_ext_adv_param, prim_min_interval)); + memcpy(params.prim_max_interval, (void *)&prim_max_interval, + sizeof_field(struct bt_hci_cp_le_set_ext_adv_param, prim_max_interval)); + memcpy(params.peer_addr.a.val, peer_addr->a.val, sizeof_field(bt_addr_le_t, a.val)); + return send_command(BT_HCI_OP_LE_SET_EXT_ADV_PARAM, sizeof(params), ¶ms); +} + +hci_result_t hci_le_read_maximum_advertising_data_length(uint16_t *max_adv_data_len) { + int result = send_command(BT_HCI_OP_LE_READ_MAX_ADV_DATA_LEN, 0, NULL); + if (result == HCI_OK) { + struct bt_hci_rp_le_read_max_adv_data_len *response = + (struct bt_hci_rp_le_read_max_adv_data_len *)cmd_response_data; + *max_adv_data_len = response->max_adv_data_len; + } + + return result; +} + +hci_result_t hci_le_read_local_supported_features(uint8_t features[8]) { + int result = send_command(BT_HCI_OP_LE_READ_LOCAL_FEATURES, 0, NULL); + if (result == HCI_OK) { + struct bt_hci_rp_le_read_local_features *response = + (struct bt_hci_rp_le_read_local_features *)cmd_response_data; + memcpy(features, response->features, + sizeof_field(struct bt_hci_rp_le_read_local_features, features)); + } + + return result; +} + +hci_result_t hci_le_set_advertising_data(uint8_t len, uint8_t data[]) { + struct bt_hci_cp_le_set_adv_data params = { + // Zero out unused data bytes. + .data = { 0 }, + }; + + params.len = len; + memcpy(params.data, data, len); + + // All data bytes are sent even if some are unused. + return send_command(BT_HCI_OP_LE_SET_ADV_DATA, sizeof(params), ¶ms); +} + +hci_result_t hci_le_set_extended_advertising_data(uint8_t handle, uint8_t op, uint8_t frag_pref, uint8_t len, uint8_t data[]) { + const uint8_t max_len = sizeof_field(struct bt_hci_cp_le_set_ext_adv_data, data); + uint8_t valid_len = MIN(len, max_len); + struct bt_hci_cp_le_set_ext_adv_data params = { + .handle = handle, + .op = op, + .frag_pref = frag_pref, + .len = valid_len, + }; + memcpy(params.data, data, valid_len); + return send_command(BT_HCI_OP_LE_SET_EXT_ADV_DATA, sizeof(params) - (max_len - valid_len), ¶ms); +} + + +hci_result_t hci_le_set_scan_response_data(uint8_t len, uint8_t data[]) { + struct bt_hci_cp_le_set_scan_rsp_data params = { + // Zero out unused data bytes. + .data = { 0 }, + }; + params.len = len; + memcpy(params.data, data, len); + + // All data bytes are sent even if some are unused. + return send_command(BT_HCI_OP_LE_SET_SCAN_RSP_DATA, sizeof(params), ¶ms); +} + +hci_result_t hci_le_set_advertising_enable(uint8_t enable) { + return send_command(BT_HCI_OP_LE_SET_ADV_ENABLE, sizeof(enable), &enable); +} + +hci_result_t hci_le_set_extended_advertising_enable(uint8_t enable, uint8_t set_num, uint8_t handle[], uint16_t duration[], uint8_t max_ext_adv_evts[]) { + uint8_t params[sizeof(struct bt_hci_cp_le_set_ext_adv_enable) + + set_num * (sizeof(struct bt_hci_ext_adv_set))]; + struct bt_hci_cp_le_set_ext_adv_enable *params_p = (struct bt_hci_cp_le_set_ext_adv_enable *)¶ms; + params_p->enable = enable; + params_p->set_num = set_num; + for (size_t i = 0; i < set_num; i++) { + params_p->s[i].handle = handle[i]; + params_p->s[i].duration = duration[i]; + params_p->s[i].max_ext_adv_evts = max_ext_adv_evts[i]; + } + + return send_command(BT_HCI_OP_LE_SET_EXT_ADV_ENABLE, sizeof(params), ¶ms); +} + +hci_result_t hci_le_set_scan_parameters(uint8_t scan_type, uint16_t interval, uint16_t window, uint8_t addr_type, uint8_t filter_policy) { + struct bt_hci_cp_le_set_scan_param params = { + .scan_type = scan_type, + .interval = interval, + .window = window, + .addr_type = addr_type, + .filter_policy = filter_policy, + }; + + return send_command(BT_HCI_OP_LE_SET_SCAN_PARAM, sizeof(params), ¶ms); +} + +hci_result_t hci_le_set_scan_enable(uint8_t enable, uint8_t filter_dup) { + struct bt_hci_cp_le_set_scan_enable params = { + .enable = enable, + .filter_dup = filter_dup, + }; + + return send_command(BT_HCI_OP_LE_SET_SCAN_ENABLE, sizeof(params), ¶ms); +} + +hci_result_t hci_le_create_conn(uint16_t scan_interval, uint16_t scan_window, uint8_t filter_policy, bt_addr_le_t *peer_addr, uint8_t own_addr_type, uint16_t conn_interval_min, uint16_t conn_interval_max, uint16_t conn_latency, uint16_t supervision_timeout, uint16_t min_ce_len, uint16_t max_ce_len) { + struct bt_hci_cp_le_create_conn params = { + .scan_interval = scan_interval, + .scan_window = scan_window, + .filter_policy = filter_policy, + // .peer_addr is set below + .own_addr_type = own_addr_type, + .conn_interval_min = conn_interval_min, + .conn_interval_max = conn_interval_max, + .conn_latency = conn_latency, + .supervision_timeout = supervision_timeout, + .min_ce_len = min_ce_len, + .max_ce_len = max_ce_len, + }; + params.peer_addr.type = peer_addr->type; + memcpy(params.peer_addr.a.val, peer_addr->a.val, sizeof(params.peer_addr.a.val)); + + return send_command(BT_HCI_OP_LE_CREATE_CONN, sizeof(params), ¶ms); +} + +hci_result_t hci_le_cancel_conn(void) { + return send_command(BT_HCI_OP_CONNECT_CANCEL, 0, NULL); +} + +hci_result_t hci_le_conn_update(uint16_t handle, uint16_t conn_interval_min, uint16_t conn_interval_max, uint16_t conn_latency, uint16_t supervision_timeout) { + struct hci_cp_le_conn_update params = { + .handle = handle, + .conn_interval_min = conn_interval_min, + .conn_interval_max = conn_interval_max, + .conn_latency = conn_latency, + .supervision_timeout = supervision_timeout, + .min_ce_len = 4, + .max_ce_len = 6, + }; + + return send_command(BT_HCI_OP_LE_CONN_UPDATE, sizeof(params), ¶ms); +} + +hci_result_t hci_disconnect(uint16_t handle) { + struct bt_hci_cp_disconnect params = { + .handle = handle, + .reason = BT_HCI_ERR_REMOTE_USER_TERM_CONN, + }; + + return send_command(BT_HCI_OP_DISCONNECT, sizeof(params), ¶ms); +} + +void hci_check_error(hci_result_t result) { + switch (result) { + case HCI_OK: + return; + + case HCI_RESPONSE_TIMEOUT: + mp_raise_bleio_BluetoothError(translate("Timeout waiting for HCI response")); + return; + + case HCI_WRITE_TIMEOUT: + mp_raise_bleio_BluetoothError(translate("Timeout waiting to write HCI request")); + return; + + case HCI_READ_ERROR: + mp_raise_bleio_BluetoothError(translate("Error reading from HCI adapter")); + return; + + case HCI_WRITE_ERROR: + mp_raise_bleio_BluetoothError(translate("Error writing to HCI adapter")); + return; + + case HCI_PACKET_SIZE_ERROR: + mp_raise_RuntimeError(translate("HCI packet size mismatch")); + return; + + case HCI_ATT_ERROR: + mp_raise_RuntimeError(translate("Error in ATT protocol code")); + return; + + default: + // Should be an HCI status error, > 0. + if (result > 0) { + mp_raise_bleio_BluetoothError(translate("HCI status error: %02x"), result); + } else { + mp_raise_bleio_BluetoothError(translate("Unknown hci_result_t: %d"), result); + } + return; + } +} |