| /* |
| * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <inttypes.h> |
| |
| #include "cyw43_btbus.h" |
| #include "cyw43_ll.h" |
| #include "cyw43_config.h" |
| #include "cybt_shared_bus_driver.h" |
| |
| #include "cyw43_btfw_43439.h" |
| |
| #if CYW43_USE_HEX_BTFW |
| extern const char brcm_patch_version[]; |
| extern const uint8_t brcm_patchram_buf[]; |
| extern const int brcm_patch_ram_length; |
| #endif |
| |
| #define BTSDIO_FW_READY_POLLING_INTERVAL_MS (1) |
| #define BTSDIO_BT_AWAKE_POLLING_INTERVAL_MS (1) |
| |
| #define BTSDIO_FW_READY_POLLING_RETRY_COUNT (300) |
| #define BTSDIO_FW_AWAKE_POLLING_RETRY_COUNT (300) |
| |
| #define BTSDIO_FWBUF_OPER_DELAY_US (250) |
| #define BTFW_WAIT_TIME_MS (150) |
| |
| #define CYBT_DEBUG 0 |
| #define CYBT_VDEBUG 0 |
| |
| #if CYBT_DEBUG |
| #define cybt_debug(format,args...) printf("%d.%d: " format, (int)cyw43_hal_ticks_ms() / 1000, (int)cyw43_hal_ticks_ms() % 1000, ## args) |
| #else |
| #define cybt_debug(format, ...) ((void)0) |
| #endif |
| #define cybt_printf(format, args...) printf("%d.%d: " format, (int)cyw43_hal_ticks_ms() / 1000, (int)cyw43_hal_ticks_ms() % 1000, ## args) |
| |
| #define ROUNDUP(x, a) ((((x) + ((a) - 1)) / (a)) * (a)) |
| #define ROUNDDN(x, a) ((x) & ~((a) - 1)) |
| #define ISALIGNED(a, x) (((uint32_t)(a) & ((x) - 1)) == 0) |
| |
| #define CIRC_BUF_CNT(in, out) (((in) - (out)) & ((BTSDIO_FWBUF_SIZE)-1)) |
| #define CIRC_BUF_SPACE(in, out) CIRC_BUF_CNT((out), ((in) + 4)) |
| |
| typedef enum { |
| HCI_PACKET_TYPE_IGNORE = 0x00, |
| HCI_PACKET_TYPE_COMMAND = 0x01, |
| HCI_PACKET_TYPE_ACL = 0x02, |
| HCI_PACKET_TYPE_SCO = 0x03, |
| HCI_PACKET_TYPE_EVENT = 0x04, |
| HCI_PACKET_TYPE_DIAG = 0x07, |
| HCI_PACKET_TYPE_LOOPBACK = 0xFF |
| } hci_packet_type_t; |
| |
| static cybt_result_t cybt_fw_download_prepare(uint8_t **p_write_buf, uint8_t **p_hex_buf) { |
| *p_write_buf = NULL; |
| *p_hex_buf = NULL; |
| |
| *p_write_buf = cyw43_malloc(BTFW_DOWNLOAD_BLK_SIZE + BTFW_SD_ALIGN); |
| if (NULL == *p_write_buf) { |
| return CYBT_ERR_OUT_OF_MEMORY; |
| } |
| |
| *p_hex_buf = cyw43_malloc(BTFW_MAX_STR_LEN); |
| if (NULL == *p_hex_buf) { |
| cyw43_free(*p_write_buf); |
| return CYBT_ERR_OUT_OF_MEMORY; |
| } |
| |
| return CYBT_SUCCESS; |
| } |
| |
| static cybt_result_t cybt_fw_download_finish(uint8_t *p_write_buf, uint8_t *p_hex_buf) { |
| if (p_write_buf) { |
| cyw43_free(p_write_buf); |
| } |
| |
| if (p_hex_buf) { |
| cyw43_free(p_hex_buf); |
| } |
| |
| return CYBT_SUCCESS; |
| } |
| |
| static cybt_result_t cybt_wait_bt_ready(uint32_t max_polling_times) { |
| cyw43_delay_ms(BTFW_WAIT_TIME_MS); |
| do { |
| if (cybt_ready()) { |
| return CYBT_SUCCESS; |
| } |
| cyw43_delay_ms(BTSDIO_FW_READY_POLLING_INTERVAL_MS); |
| } while (max_polling_times--); |
| return CYBT_ERR_TIMEOUT; |
| } |
| |
| static cybt_result_t cybt_wait_bt_awake(uint32_t max_polling_times) { |
| do { |
| if (cybt_awake()) { |
| return CYBT_SUCCESS; |
| } |
| cyw43_delay_ms(BTSDIO_BT_AWAKE_POLLING_INTERVAL_MS); |
| } while (max_polling_times--); |
| return CYBT_ERR_TIMEOUT; |
| } |
| |
| int cyw43_btbus_init(cyw43_ll_t *self) { |
| cybt_result_t ret; |
| |
| uint8_t *p_write_buf = NULL; |
| uint8_t *p_hex_buf = NULL; |
| |
| cybt_sharedbus_driver_init(self); |
| |
| ret = cybt_fw_download_prepare(&p_write_buf, &p_hex_buf); |
| if (CYBT_SUCCESS != ret) { |
| cybt_printf("Could not allocate memory\n"); |
| return ret; |
| } |
| |
| cybt_debug("cybt_fw_download\n"); |
| const uint8_t *fw_data_buf; |
| uint32_t fw_data_len; |
| #if CYW43_USE_HEX_BTFW |
| cybt_printf("CYW43_USE_HEX_BTFW is true\n"); |
| #ifndef NDEBUG |
| cybt_printf("BT FW download, version = %s\n", brcm_patch_version); |
| #endif |
| fw_data_len = brcm_patch_ram_length; |
| fw_data_buf = brcm_patchram_buf; |
| #else |
| fw_data_len = cyw43_btfw_43439_len; |
| fw_data_buf = cyw43_btfw_43439; |
| #endif |
| ret = cybt_fw_download(fw_data_buf, |
| fw_data_len, |
| p_write_buf, |
| p_hex_buf |
| ); |
| |
| cybt_debug("cybt_fw_download_finish\n"); |
| cybt_fw_download_finish(p_write_buf, p_hex_buf); |
| |
| if (CYBT_SUCCESS != ret) { |
| cybt_printf("hci_open(): FW download failed (0x%x)\n", ret); |
| return CYBT_ERR_HCI_INIT_FAILED; |
| } |
| |
| cybt_debug("// cybt_wait_bt_ready\n"); |
| ret = cybt_wait_bt_ready(BTSDIO_FW_READY_POLLING_RETRY_COUNT); |
| assert(ret == CYBT_SUCCESS); |
| if (CYBT_SUCCESS == ret) { |
| cybt_debug("hci_open(): FW download successfully\n"); |
| } else { |
| cybt_printf("hci_open(): Failed to download FW\n"); |
| return CYBT_ERR_HCI_INIT_FAILED; |
| } |
| |
| ret = cybt_init_buffer(); |
| assert(ret == 0); |
| if (ret != 0) { |
| return ret; |
| } |
| ret = cybt_wait_bt_awake(BTSDIO_FW_AWAKE_POLLING_RETRY_COUNT); |
| assert(ret == 0); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| cybt_set_host_ready(); |
| cybt_toggle_bt_intr(); |
| |
| return CYBT_SUCCESS; |
| } |
| |
| #if CYBT_VDEBUG |
| static void dump_bytes(const uint8_t *bptr, uint32_t len) { |
| unsigned int i = 0; |
| |
| for (i = 0; i < len; i++) { |
| if ((i & 0x07) == 0) { |
| printf("\n "); |
| } |
| printf("0x%02x", bptr[i]); |
| if (i != (len-1)) { |
| printf(", "); |
| } else { |
| } |
| } |
| printf("\n"); |
| } |
| #endif |
| |
| static cybt_result_t cybt_hci_write_buf(const uint8_t *p_data, uint32_t length) { |
| cybt_result_t ret_result = CYBT_SUCCESS; |
| cybt_fw_membuf_index_t fw_membuf_info = {0}; |
| |
| assert(ISALIGNED(p_data, 4)); |
| if (!ISALIGNED(p_data, 4)) { |
| cybt_printf("cybt_hci_write_hdr: buffer not aligned\n"); |
| return CYBT_ERR_BADARG; |
| } |
| |
| // total length including header |
| length = ROUNDUP(length, 4); |
| cybt_get_bt_buf_index(&fw_membuf_info); |
| uint32_t buf_space = CIRC_BUF_SPACE(fw_membuf_info.host2bt_in_val, fw_membuf_info.host2bt_out_val); |
| assert(length <= buf_space); // queue full? |
| if (length > buf_space) { |
| return CYBT_ERR_QUEUE_FULL; |
| } |
| |
| if (fw_membuf_info.host2bt_in_val + length <= BTSDIO_FWBUF_SIZE) { |
| // Don't need to wrap circular buf |
| cybt_debug("cybt_hci_write_hdr: 1-round write, len = %" PRId32 "\n", length); |
| cybt_mem_write_idx(H2B_BUF_ADDR_IDX, fw_membuf_info.host2bt_in_val, p_data, length); |
| fw_membuf_info.host2bt_in_val += length; |
| } else { |
| // Need to wrap circular buf |
| uint32_t first_write_len = BTSDIO_FWBUF_SIZE - fw_membuf_info.host2bt_in_val; |
| if (first_write_len >= 4) { |
| cybt_mem_write_idx(H2B_BUF_ADDR_IDX, fw_membuf_info.host2bt_in_val, p_data, first_write_len); |
| fw_membuf_info.host2bt_in_val += first_write_len; |
| } else { |
| first_write_len = 0; |
| } |
| uint32_t second_write_len = length - first_write_len; |
| cybt_debug("cybt_hci_write_hdr: 2-round write, 1st_len = %" PRId32 ", 2nd_len = %" PRId32 "\n", first_write_len, |
| second_write_len); |
| if (second_write_len > 0) { |
| cybt_mem_write_idx(H2B_BUF_ADDR_IDX, 0, p_data + first_write_len, second_write_len); |
| fw_membuf_info.host2bt_in_val += second_write_len; |
| } |
| } |
| |
| // Update circular buf pointer |
| const uint32_t new_h2b_in_val = fw_membuf_info.host2bt_in_val & (BTSDIO_FWBUF_SIZE - 1); |
| cybt_reg_write_idx(H2B_BUF_IN_ADDR_IDX, new_h2b_in_val); |
| |
| cybt_toggle_bt_intr(); |
| return ret_result; |
| } |
| |
| static cybt_result_t cybt_hci_read(uint8_t *p_data, uint32_t *p_length) { |
| cybt_result_t ret_result = CYBT_SUCCESS; |
| uint32_t fw_b2h_buf_count; |
| uint32_t new_b2h_out_val; |
| cybt_fw_membuf_index_t fw_membuf_info = {0}; |
| static uint32_t available = 0; |
| |
| assert(ISALIGNED(p_data, 4)); |
| if (!ISALIGNED(p_data, 4)) { |
| assert(false); |
| cybt_printf("cybt_hci_read: buffer not aligned\n"); |
| return CYBT_ERR_BADARG; |
| } |
| |
| uint32_t read_len = ROUNDUP(*p_length, 4); |
| |
| cybt_get_bt_buf_index(&fw_membuf_info); |
| fw_b2h_buf_count = CIRC_BUF_CNT(fw_membuf_info.bt2host_in_val, |
| fw_membuf_info.bt2host_out_val); |
| cybt_debug("cybt_hci_read: bt2host_in_val=%lu bt2host_out_val=%lu fw_b2h_buf_count=%ld\n", |
| fw_membuf_info.bt2host_in_val, fw_membuf_info.bt2host_out_val, fw_b2h_buf_count); |
| if (fw_b2h_buf_count < available) { |
| cybt_printf("error: cybt_hci_read buffer overflow fw_b2h_buf_count=%ld available=%lu\n", fw_b2h_buf_count, |
| available); |
| cybt_printf("error: cybt_hci_read bt2host_in_val=%lu bt2host_out_val=%lu\n", fw_membuf_info.bt2host_in_val, |
| fw_membuf_info.bt2host_out_val); |
| panic("cyw43 buffer overflow"); |
| } |
| |
| // No space in buffer |
| if (fw_b2h_buf_count == 0) { |
| *p_length = 0; |
| } else { |
| if (read_len > fw_b2h_buf_count) { |
| read_len = fw_b2h_buf_count; |
| } |
| |
| if (fw_membuf_info.bt2host_out_val + read_len <= BTSDIO_FWBUF_SIZE) { |
| // Don't need to wrap the circular buf |
| cybt_debug("cybt_hci_read: 1-round read, len = %" PRId32 "\n", read_len); |
| cybt_mem_read_idx(B2H_BUF_ADDR_IDX, fw_membuf_info.bt2host_out_val, p_data, read_len); |
| fw_membuf_info.bt2host_out_val += read_len; |
| } else { |
| // Need to wrap the circular buf |
| uint32_t first_read_len = BTSDIO_FWBUF_SIZE - fw_membuf_info.bt2host_out_val; |
| if (first_read_len >= 4) { |
| cybt_mem_read_idx(B2H_BUF_ADDR_IDX, fw_membuf_info.bt2host_out_val, p_data, first_read_len); |
| fw_membuf_info.bt2host_out_val += first_read_len; |
| } else { |
| first_read_len = 0; |
| } |
| uint32_t second_read_len = read_len - first_read_len; |
| cybt_debug("cybt_hci_read: 2-round read, 1st_len = %" PRId32 ", 2nd_len = %" PRId32 "\n", first_read_len, |
| second_read_len); |
| if (second_read_len > 0) { |
| cybt_mem_read_idx(B2H_BUF_ADDR_IDX, 0, p_data + first_read_len, second_read_len); |
| fw_membuf_info.bt2host_out_val += second_read_len; |
| } |
| } |
| available = fw_b2h_buf_count - read_len; // remember amount available to check for buffer overflow |
| |
| // Update pointer |
| new_b2h_out_val = fw_membuf_info.bt2host_out_val & (BTSDIO_FWBUF_SIZE - 1); |
| cybt_debug("cybt_hci_read new b2h_out = %" PRId32 "\n", new_b2h_out_val); |
| cybt_reg_write_idx(B2H_BUF_OUT_ADDR_IDX, new_b2h_out_val); |
| |
| // in case the real length is less than the requested one |
| *p_length = read_len; |
| } |
| cybt_toggle_bt_intr(); |
| return ret_result; |
| } |
| |
| static void cybt_bus_request(void) { |
| CYW43_THREAD_ENTER |
| // todo: Handle failure |
| cybt_result_t err = cybt_set_bt_awake(true); |
| assert(err == 0); |
| err = cybt_wait_bt_awake(BTSDIO_FW_AWAKE_POLLING_RETRY_COUNT); |
| assert(err == 0); |
| (void) err; |
| } |
| |
| static void cybt_bus_release(void) { |
| // mutex if using wifi |
| CYW43_THREAD_EXIT |
| } |
| |
| // Send the buffer which includes space for a 4 byte header at the start |
| // The last byte of the header should already be set to the packet type |
| int cyw43_btbus_write(uint8_t *buf, uint32_t size) { |
| uint16_t cmd_len = 0; |
| |
| // The size of the buffer should include a 4 byte header at the start |
| cmd_len = size - 4; //in BTSDIO, cmd_len does not include header length |
| |
| // Create payload starting with required headers |
| // Format: Cmd Len B0, Cmd Len B1, Cmd Len B2, HCI pckt type, Data |
| buf[0] = (uint8_t) (cmd_len & 0xFF); |
| buf[1] = (uint8_t) ((cmd_len & 0xFF00) >> 8); |
| buf[2] = 0; |
| |
| cybt_bus_request(); |
| |
| cybt_debug("cyw43_btbus_write: %d\n", cmd_len); |
| #if CYBT_VDEBUG |
| dump_bytes(buf, size); // dump header and data |
| #endif |
| |
| cybt_hci_write_buf(buf, size); |
| cybt_bus_release(); |
| |
| return 0; |
| } |
| |
| static bool cybt_hci_read_packet(uint8_t *buf, uint32_t max_buf_size, uint32_t *size) { |
| uint32_t total_read_len = 0; |
| uint32_t read_len = 0; |
| cybt_result_t bt_result; |
| |
| // Read the header into the first 4 bytes of the buffer |
| read_len = 4; //3 bytes BTSDIO packet length + 1 bytes PTI |
| bt_result = cybt_hci_read(buf, &read_len); |
| |
| if (bt_result != CYBT_SUCCESS) { |
| *size = 0; |
| cybt_printf("cybt_hci_read_packet: error %d", bt_result); |
| return true; |
| } |
| |
| if (read_len == 0) { |
| // No data is read from SPI |
| *size = 0; |
| cybt_debug("cybt_hci_read_packet: no data\n"); |
| return true; |
| } |
| |
| uint32_t hci_read_len = ((buf[2] << 16) & 0xFFFF00) | ((buf[1] << 8) & 0xFF00) | (buf[0] & 0xFF); |
| if (hci_read_len > max_buf_size - 4) { |
| *size = 0; |
| cybt_printf("cybt_hci_read_packet: too much data len %" PRId32"\n", hci_read_len); |
| assert(false); |
| return false; |
| } |
| total_read_len = hci_read_len; |
| |
| // Read the packet data after the header |
| cybt_debug("cybt_hci_read_packet: packet type 0x%" PRIx8 " len %" PRId32 "\n", buf[3], hci_read_len); |
| bt_result = cybt_hci_read(buf + 4, &total_read_len); |
| if (bt_result != CYBT_SUCCESS) { |
| *size = 0; |
| cybt_printf("cybt_hci_read_packet: read failed\n"); |
| assert(false); |
| return false; |
| } |
| |
| // Might read more because of alignment |
| if (total_read_len >= hci_read_len) { |
| assert(total_read_len == ROUNDUP(hci_read_len, 4)); // check if we're losing data? |
| *size = hci_read_len + 4; |
| } else { |
| assert(total_read_len > 0); |
| *size = total_read_len + 4; |
| cybt_printf("cybt_hci_read_packet: failed to read all data %lu < %lu\n", total_read_len, hci_read_len); |
| //assert(false); |
| return true; |
| } |
| |
| cybt_debug("cybt_hci_read_packet: %ld\n", *size); |
| #if CYBT_VDEBUG |
| dump_bytes(buf, *size); |
| #endif |
| |
| return true; |
| } |
| |
| // Reads the hci packet prepended with 4 byte header. The last header byte is the packet type |
| int cyw43_btbus_read(uint8_t *buf, uint32_t max_buf_size, uint32_t *size) { |
| cybt_bus_request(); |
| bool result = cybt_hci_read_packet(buf, max_buf_size, size); |
| cybt_bus_release(); |
| return result ? 0 : -1; |
| } |