/* * This file is part of the MicroPython project, http://micropython.org/ * * The MIT License (MIT) * * Copyright (c) 2019 Damien P. George * Copyright (c) 2020 Jim Mussared * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include "py/mperrno.h" #include "py/mphal.h" #include "py/runtime.h" #include "extmod/modbluetooth.h" #include "mpbthciport.h" #include "rtc.h" #include "rfcore.h" #if defined(STM32WB) #include "stm32wbxx_ll_ipcc.h" #if MICROPY_PY_BLUETOOTH #if MICROPY_BLUETOOTH_NIMBLE // For mp_bluetooth_nimble_hci_uart_wfi #include "nimble/nimble_npl.h" #else #error "STM32WB must use NimBLE." #endif #if !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS #error "STM32WB must use synchronous BLE events." #endif #endif #define DEBUG_printf(...) // printf("rfcore: " __VA_ARGS__) // Define to 1 to print traces of HCI packets #define HCI_TRACE (0) #define IPCC_CH_BLE (LL_IPCC_CHANNEL_1) // BLE HCI command and response #define IPCC_CH_SYS (LL_IPCC_CHANNEL_2) // system HCI command and response #define IPCC_CH_MM (LL_IPCC_CHANNEL_4) // release buffer #define IPCC_CH_HCI_ACL (LL_IPCC_CHANNEL_6) // HCI ACL outgoing data #define OGF_CTLR_BASEBAND (0x03) #define OCF_CB_RESET (0x03) #define OCF_CB_SET_EVENT_MASK2 (0x63) #define OGF_VENDOR (0x3f) #define OCF_WRITE_CONFIG (0x0c) #define OCF_SET_TX_POWER (0x0f) #define OCF_BLE_INIT (0x66) #define OCF_C2_FLASH_ERASE_ACTIVITY (0x69) #define OCF_C2_SET_FLASH_ACTIVITY_CONTROL (0x73) #define HCI_OPCODE(ogf, ocf) ((ogf) << 10 | (ocf)) #define HCI_KIND_BT_CMD (0x01) // ...? #define HCI_KIND_BT_ACL (0x02) // #define HCI_KIND_BT_EVENT (0x04) // #define HCI_KIND_VENDOR_RESPONSE (0x11) #define HCI_KIND_VENDOR_EVENT (0x12) #define HCI_EVENT_COMMAND_COMPLETE (0x0E) // #define HCI_EVENT_COMMAND_STATUS (0x0F) // #define HCI_EVENT_NUMBER_OF_COMPLETED_PACKETS (0x13) // ()* #define SYS_ACK_TIMEOUT_MS (250) #define BLE_ACK_TIMEOUT_MS (250) // AN5185 #define MAGIC_FUS_ACTIVE 0xA94656B9 // AN5289 #define MAGIC_IPCC_MEM_INCORRECT 0x3DE96F61 volatile bool hci_acl_cmd_pending = false; typedef struct _tl_list_node_t { volatile struct _tl_list_node_t *next; volatile struct _tl_list_node_t *prev; uint8_t body[0]; } tl_list_node_t; typedef struct _parse_hci_info_t { rfcore_ble_msg_callback_t cb_fun; void *cb_env; bool was_hci_reset_evt; } parse_hci_info_t; // Version // [0:3] = Build - 0: Untracked - 15:Released - x: Tracked version // [4:7] = branch - 0: Mass Market - x: ... // [8:15] = Subversion // [16:23] = Version minor // [24:31] = Version major // Memory Size // [0:7] = Flash (Number of 4k sectors) // [8:15] = Reserved (Shall be set to 0 - may be used as flash extension) // [16:23] = SRAM2b (Number of 1k sectors) // [24:31] = SRAM2a (Number of 1k sectors) typedef union __attribute__((packed)) _ipcc_device_info_table_t { struct { uint32_t table_state; uint8_t reserved0; uint8_t last_fus_state; uint8_t last_ws_state; uint8_t ws_type; uint32_t safeboot_version; uint32_t fus_version; uint32_t fus_memorysize; uint32_t ws_version; uint32_t ws_memorysize; uint32_t ws_ble_info; uint32_t ws_thread_info; uint32_t reserved1; uint64_t uid64; uint16_t device_id; uint16_t pad; } fus; struct { uint32_t safeboot_version; uint32_t fus_version; uint32_t fus_memorysize; uint32_t fus_info; uint32_t fw_version; uint32_t fw_memorysize; uint32_t fw_infostack; uint32_t fw_reserved; } ws; } ipcc_device_info_table_t; typedef struct __attribute__((packed)) _ipcc_ble_table_t { uint8_t *pcmd_buffer; uint8_t *pcs_buffer; tl_list_node_t *pevt_queue; uint8_t *phci_acl_data_buffer; } ipcc_ble_table_t; // msg // [0:7] = cmd/evt // [8:31] = Reserved typedef struct __attribute__((packed)) _ipcc_sys_table_t { uint8_t *pcmd_buffer; tl_list_node_t *sys_queue; } ipcc_sys_table_t; typedef struct __attribute__((packed)) _ipcc_mem_manager_table_t { uint8_t *spare_ble_buffer; uint8_t *spare_sys_buffer; uint8_t *blepool; uint32_t blepoolsize; tl_list_node_t *pevt_free_buffer_queue; uint8_t *traces_evt_pool; uint32_t tracespoolsize; } ipcc_mem_manager_table_t; typedef struct __attribute__((packed)) _ipcc_ref_table_t { ipcc_device_info_table_t *p_device_info_table; ipcc_ble_table_t *p_ble_table; void *p_thread_table; ipcc_sys_table_t *p_sys_table; ipcc_mem_manager_table_t *p_mem_manager_table; void *p_traces_table; void *p_mac_802_15_4_table; void *p_zigbee_table; void *p_lld_tests_table; void *p_lld_ble_table; } ipcc_ref_table_t; // The stm32wb55xg.ld script puts .bss.ipcc_mem_* into SRAM2A and .bss_ipcc_membuf_* into SRAM2B. // It also leaves 64 bytes at the start of SRAM2A for the ref table. static ipcc_device_info_table_t ipcc_mem_dev_info_tab; // mem1 static ipcc_ble_table_t ipcc_mem_ble_tab; // mem1 static ipcc_sys_table_t ipcc_mem_sys_tab; // mem1 static ipcc_mem_manager_table_t ipcc_mem_memmgr_tab; // mem1 static uint8_t ipcc_membuf_sys_cmd_buf[272]; // mem2 static tl_list_node_t ipcc_mem_sys_queue; // mem1 static tl_list_node_t ipcc_mem_memmgr_free_buf_queue; // mem1 static uint8_t ipcc_membuf_memmgr_ble_spare_evt_buf[272]; // mem2 static uint8_t ipcc_membuf_memmgr_sys_spare_evt_buf[272]; // mem2 static uint8_t ipcc_membuf_memmgr_evt_pool[6 * 272]; // mem2 static uint8_t ipcc_membuf_ble_cmd_buf[272]; // mem2 static uint8_t ipcc_membuf_ble_cs_buf[272]; // mem2 static tl_list_node_t ipcc_mem_ble_evt_queue; // mem1 static uint8_t ipcc_membuf_ble_hci_acl_data_buf[272]; // mem2 /******************************************************************************/ // Transport layer linked list static void tl_list_init(volatile tl_list_node_t *n) { n->next = n; n->prev = n; } static volatile tl_list_node_t *tl_list_unlink(volatile tl_list_node_t *n) { volatile tl_list_node_t *next = n->next; volatile tl_list_node_t *prev = n->prev; prev->next = next; next->prev = prev; return next; } static void tl_list_append(volatile tl_list_node_t *head, volatile tl_list_node_t *n) { n->next = head; n->prev = head->prev; head->prev->next = n; head->prev = n; } /******************************************************************************/ // IPCC interface static volatile ipcc_ref_table_t *get_buffer_table(void) { // The IPCCDBA option bytes must not be changed without // making a corresponding change to the linker script. return (volatile ipcc_ref_table_t *)(SRAM2A_BASE + LL_FLASH_GetIPCCBufferAddr() * 4); } void ipcc_init(uint32_t irq_pri) { DEBUG_printf("ipcc_init\n"); // Setup buffer table pointers volatile ipcc_ref_table_t *tab = get_buffer_table(); tab->p_device_info_table = &ipcc_mem_dev_info_tab; tab->p_ble_table = &ipcc_mem_ble_tab; tab->p_sys_table = &ipcc_mem_sys_tab; tab->p_mem_manager_table = &ipcc_mem_memmgr_tab; // Start IPCC peripheral __HAL_RCC_IPCC_CLK_ENABLE(); // Enable receive IRQ on the BLE channel. LL_C1_IPCC_EnableIT_RXO(IPCC); LL_C1_IPCC_DisableReceiveChannel(IPCC, LL_IPCC_CHANNEL_1 | LL_IPCC_CHANNEL_2 | LL_IPCC_CHANNEL_3 | LL_IPCC_CHANNEL_4 | LL_IPCC_CHANNEL_5 | LL_IPCC_CHANNEL_6); LL_C1_IPCC_EnableReceiveChannel(IPCC, IPCC_CH_BLE); NVIC_SetPriority(IPCC_C1_RX_IRQn, irq_pri); HAL_NVIC_EnableIRQ(IPCC_C1_RX_IRQn); // Device info table will be populated by FUS/WS on CPU2 boot. // Populate system table tl_list_init(&ipcc_mem_sys_queue); ipcc_mem_sys_tab.pcmd_buffer = ipcc_membuf_sys_cmd_buf; ipcc_mem_sys_tab.sys_queue = &ipcc_mem_sys_queue; // Populate memory manager table tl_list_init(&ipcc_mem_memmgr_free_buf_queue); ipcc_mem_memmgr_tab.spare_ble_buffer = ipcc_membuf_memmgr_ble_spare_evt_buf; ipcc_mem_memmgr_tab.spare_sys_buffer = ipcc_membuf_memmgr_sys_spare_evt_buf; ipcc_mem_memmgr_tab.blepool = ipcc_membuf_memmgr_evt_pool; ipcc_mem_memmgr_tab.blepoolsize = sizeof(ipcc_membuf_memmgr_evt_pool); ipcc_mem_memmgr_tab.pevt_free_buffer_queue = &ipcc_mem_memmgr_free_buf_queue; ipcc_mem_memmgr_tab.traces_evt_pool = NULL; ipcc_mem_memmgr_tab.tracespoolsize = 0; // Populate BLE table tl_list_init(&ipcc_mem_ble_evt_queue); ipcc_mem_ble_tab.pcmd_buffer = ipcc_membuf_ble_cmd_buf; ipcc_mem_ble_tab.pcs_buffer = ipcc_membuf_ble_cs_buf; ipcc_mem_ble_tab.pevt_queue = &ipcc_mem_ble_evt_queue; ipcc_mem_ble_tab.phci_acl_data_buffer = ipcc_membuf_ble_hci_acl_data_buf; } /******************************************************************************/ // Transport layer HCI interface // The WS firmware doesn't support OCF_CB_SET_EVENT_MASK2, and fails with: // v1.8.0.0.4 (and below): HCI_EVENT_COMMAND_COMPLETE with a non-zero status // v1.9.0.0.4 (and above): HCI_EVENT_COMMAND_STATUS with a non-zero status // In either case we detect the failure response and inject this response // instead (which is HCI_EVENT_COMMAND_COMPLETE for OCF_CB_SET_EVENT_MASK2 // with status=0). static const uint8_t set_event_event_mask2_fix_payload[] = { 0x04, 0x0e, 0x04, 0x01, 0x63, 0x0c, 0x00 }; static size_t tl_parse_hci_msg(const uint8_t *buf, parse_hci_info_t *parse) { const char *info; #if HCI_TRACE int applied_set_event_event_mask2_fix = 0; #endif size_t len; switch (buf[0]) { case HCI_KIND_BT_ACL: { info = "HCI_ACL"; len = 5 + buf[3] + (buf[4] << 8); if (parse != NULL) { parse->cb_fun(parse->cb_env, buf, len); } break; } case HCI_KIND_BT_EVENT: { info = "HCI_EVT"; // Acknowledgment of a pending ACL request, allow another one to be sent. if (buf[1] == HCI_EVENT_NUMBER_OF_COMPLETED_PACKETS) { hci_acl_cmd_pending = false; } len = 3 + buf[2]; if (parse != NULL) { if (buf[1] == HCI_EVENT_COMMAND_COMPLETE && len == 7) { uint16_t opcode = (buf[5] << 8) | buf[4]; uint8_t status = buf[6]; if (opcode == HCI_OPCODE(OGF_CTLR_BASEBAND, OCF_CB_SET_EVENT_MASK2) && status != 0) { // For WS firmware v1.8.0.0.4 and below. Reply with the "everything OK" payload. parse->cb_fun(parse->cb_env, set_event_event_mask2_fix_payload, sizeof(set_event_event_mask2_fix_payload)); #if HCI_TRACE applied_set_event_event_mask2_fix = 18; #endif break; // Don't send the original payload. } if (opcode == HCI_OPCODE(OGF_CTLR_BASEBAND, OCF_CB_RESET) && status == 0) { // Controller acknowledged reset command. // This will trigger setting the MAC address. parse->was_hci_reset_evt = true; } } if (buf[1] == HCI_EVENT_COMMAND_STATUS && len == 7) { uint16_t opcode = (buf[6] << 8) | buf[5]; uint8_t status = buf[3]; if (opcode == HCI_OPCODE(OGF_CTLR_BASEBAND, OCF_CB_SET_EVENT_MASK2) && status != 0) { // For WS firmware v1.9.0.0.4 and higher. Reply with the "everything OK" payload. parse->cb_fun(parse->cb_env, set_event_event_mask2_fix_payload, sizeof(set_event_event_mask2_fix_payload)); #if HCI_TRACE applied_set_event_event_mask2_fix = 19; #endif break; // Don't send the original payload. } } parse->cb_fun(parse->cb_env, buf, len); } break; } case HCI_KIND_VENDOR_RESPONSE: { // assert(buf[1] == 0x0e); info = "VEND_RESP"; len = 3 + buf[2]; // ??? // uint16_t cmd = buf[4] | buf[5] << 8; // uint8_t status = buf[6]; break; } case HCI_KIND_VENDOR_EVENT: { // assert(buf[1] == 0xff); info = "VEND_EVT"; len = 3 + buf[2]; // ??? // uint16_t evt = buf[3] | buf[4] << 8; break; } default: info = "HCI_UNKNOWN"; len = 0; break; } #if HCI_TRACE printf("[% 8d] <%s(%02x", mp_hal_ticks_ms(), info, buf[0]); for (int i = 1; i < len; ++i) { printf(":%02x", buf[i]); } printf(")"); if (parse && parse->was_hci_reset_evt) { printf(" (reset)"); } if (applied_set_event_event_mask2_fix) { printf(" (mask2 fix %d)", applied_set_event_event_mask2_fix); } printf("\n"); #else (void)info; #endif return len; } static size_t tl_process_msg(volatile tl_list_node_t *head, unsigned int ch, parse_hci_info_t *parse) { volatile tl_list_node_t *cur = head->next; bool added_to_free_queue = false; size_t len = 0; while (cur != head) { len += tl_parse_hci_msg((uint8_t *)cur->body, parse); volatile tl_list_node_t *next = tl_list_unlink(cur); // If this node is allocated from the memmgr event pool, then place it into the free buffer. if ((uint8_t *)cur >= ipcc_membuf_memmgr_evt_pool && (uint8_t *)cur < ipcc_membuf_memmgr_evt_pool + sizeof(ipcc_membuf_memmgr_evt_pool)) { // Wait for C2 to indicate that it has finished using the free buffer, // so that we can link the newly-freed memory in to this buffer. // If waiting is needed then it is typically between 5 and 20 microseconds. while (LL_C1_IPCC_IsActiveFlag_CHx(IPCC, IPCC_CH_MM)) { } // Place memory back in free pool. tl_list_append(&ipcc_mem_memmgr_free_buf_queue, cur); added_to_free_queue = true; } cur = next; } if (added_to_free_queue) { // Notify change in free pool. LL_C1_IPCC_SetFlag_CHx(IPCC, IPCC_CH_MM); } return len; } // Only call this when IRQs are disabled on this channel. static size_t tl_check_msg(volatile tl_list_node_t *head, unsigned int ch, parse_hci_info_t *parse) { size_t len = 0; if (LL_C2_IPCC_IsActiveFlag_CHx(IPCC, ch)) { // Process new data. len = tl_process_msg(head, ch, parse); // Clear receive channel (allows RF core to send more data to us). LL_C1_IPCC_ClearFlag_CHx(IPCC, ch); if (ch == IPCC_CH_BLE) { // Re-enable IRQs for BLE now that we've cleared the flag. LL_C1_IPCC_EnableReceiveChannel(IPCC, IPCC_CH_BLE); } } return len; } static void tl_hci_cmd(uint8_t *cmd, unsigned int ch, uint8_t hdr, uint16_t opcode, const uint8_t *buf, size_t len) { tl_list_node_t *n = (tl_list_node_t *)cmd; n->next = NULL; n->prev = NULL; cmd[8] = hdr; cmd[9] = opcode; cmd[10] = opcode >> 8; cmd[11] = len; memcpy(&cmd[12], buf, len); #if HCI_TRACE printf("[% 8d] >HCI(", mp_hal_ticks_ms()); for (int i = 0; i < len + 4; ++i) { printf(":%02x", cmd[i + 8]); } printf(")\n"); #endif // Indicate that this channel is ready. LL_C1_IPCC_SetFlag_CHx(IPCC, ch); } static ssize_t tl_sys_wait_ack(const uint8_t *buf, mp_int_t timeout_ms) { uint32_t t0 = mp_hal_ticks_ms(); timeout_ms = MAX(SYS_ACK_TIMEOUT_MS, timeout_ms); // C2 will clear this bit to acknowledge the request. while (LL_C1_IPCC_IsActiveFlag_CHx(IPCC, IPCC_CH_SYS)) { if (mp_hal_ticks_ms() - t0 > timeout_ms) { printf("tl_sys_wait_ack: timeout\n"); return -MP_ETIMEDOUT; } } // C1-to-C2 bit cleared, so process the response (just get the length, do // not parse any further). return (ssize_t)tl_parse_hci_msg(buf, NULL); } static ssize_t tl_sys_hci_cmd_resp(uint16_t opcode, const uint8_t *buf, size_t len, mp_int_t timeout_ms) { tl_hci_cmd(ipcc_membuf_sys_cmd_buf, IPCC_CH_SYS, 0x10, opcode, buf, len); return tl_sys_wait_ack(ipcc_membuf_sys_cmd_buf, timeout_ms); } static int tl_ble_wait_resp(void) { uint32_t t0 = mp_hal_ticks_ms(); while (!LL_C2_IPCC_IsActiveFlag_CHx(IPCC, IPCC_CH_BLE)) { if (mp_hal_ticks_ms() - t0 > BLE_ACK_TIMEOUT_MS) { printf("tl_ble_wait_resp: timeout\n"); return -MP_ETIMEDOUT; } } // C2 set IPCC flag -- process the data, clear the flag, and re-enable IRQs. tl_check_msg(&ipcc_mem_ble_evt_queue, IPCC_CH_BLE, NULL); return 0; } // Synchronously send a BLE command. static void tl_ble_hci_cmd_resp(uint16_t opcode, const uint8_t *buf, size_t len) { // Poll for completion rather than wait for IRQ->scheduler. LL_C1_IPCC_DisableReceiveChannel(IPCC, IPCC_CH_BLE); tl_hci_cmd(ipcc_membuf_ble_cmd_buf, IPCC_CH_BLE, HCI_KIND_BT_CMD, opcode, buf, len); tl_ble_wait_resp(); } /******************************************************************************/ // RF core interface void rfcore_init(void) { DEBUG_printf("rfcore_init\n"); // Ensure LSE is running rtc_init_finalise(); // In case we're waking from deepsleep, enforce core synchronisation __HAL_RCC_HSEM_CLK_ENABLE(); while (LL_HSEM_1StepLock(HSEM, CFG_HW_PWR_STANDBY_SEMID)) { } // Select LSE as RF wakeup source RCC->CSR = (RCC->CSR & ~RCC_CSR_RFWKPSEL) | 1 << RCC_CSR_RFWKPSEL_Pos; // Initialise IPCC and shared memory structures ipcc_init(IRQ_PRI_SDIO); // When the device is out of standby, it is required to use the EXTI mechanism to wakeup CPU2 LL_C2_EXTI_EnableEvent_32_63(LL_EXTI_LINE_41); LL_EXTI_EnableRisingTrig_32_63(LL_EXTI_LINE_41); LL_HSEM_ReleaseLock(HSEM, CFG_HW_PWR_STANDBY_SEMID, 0); // Boot the second core __SEV(); __WFE(); PWR->CR4 |= PWR_CR4_C2BOOT; } static const struct { uint8_t *pBleBufferAddress; // unused uint32_t BleBufferSize; // unused uint16_t NumAttrRecord; uint16_t NumAttrServ; uint16_t AttrValueArrSize; uint8_t NumOfLinks; uint8_t ExtendedPacketLengthEnable; uint8_t PrWriteListSize; uint8_t MblockCount; uint16_t AttMtu; uint16_t SlaveSca; uint8_t MasterSca; uint8_t LsSource; uint32_t MaxConnEventLength; uint16_t HsStartupTime; uint8_t ViterbiEnable; uint8_t LlOnly; uint8_t HwVersion; } ble_init_params = { 0, // pBleBufferAddress 0, // BleBufferSize MICROPY_HW_RFCORE_BLE_NUM_GATT_ATTRIBUTES, MICROPY_HW_RFCORE_BLE_NUM_GATT_SERVICES, MICROPY_HW_RFCORE_BLE_ATT_VALUE_ARRAY_SIZE, MICROPY_HW_RFCORE_BLE_NUM_LINK, MICROPY_HW_RFCORE_BLE_DATA_LENGTH_EXTENSION, MICROPY_HW_RFCORE_BLE_PREPARE_WRITE_LIST_SIZE, MICROPY_HW_RFCORE_BLE_MBLOCK_COUNT, MICROPY_HW_RFCORE_BLE_MAX_ATT_MTU, MICROPY_HW_RFCORE_BLE_SLAVE_SCA, MICROPY_HW_RFCORE_BLE_MASTER_SCA, MICROPY_HW_RFCORE_BLE_LSE_SOURCE, MICROPY_HW_RFCORE_BLE_MAX_CONN_EVENT_LENGTH, MICROPY_HW_RFCORE_BLE_HSE_STARTUP_TIME, MICROPY_HW_RFCORE_BLE_VITERBI_MODE, MICROPY_HW_RFCORE_BLE_LL_ONLY, 0, // HwVersion }; void rfcore_ble_init(void) { DEBUG_printf("rfcore_ble_init\n"); // Configure and reset the BLE controller. if (!rfcore_ble_reset()) { // ble init can fail if core2 has previously locked up. Reset HSI & rfcore to retry. LL_RCC_HSI_Disable(); mp_hal_delay_ms(100); LL_RCC_HSI_Enable(); rfcore_init(); rfcore_ble_reset(); } // Enable PES rather than SEM7 to moderate flash access between the cores. uint8_t buf = 0; // FLASH_ACTIVITY_CONTROL_PES tl_sys_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_C2_SET_FLASH_ACTIVITY_CONTROL), &buf, 1, 0); } bool rfcore_ble_reset(void) { DEBUG_printf("rfcore_ble_reset\n"); // Clear any outstanding messages from ipcc_init. tl_check_msg(&ipcc_mem_sys_queue, IPCC_CH_SYS, NULL); // Configure and reset the BLE controller. int ret = tl_sys_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_BLE_INIT), (const uint8_t *)&ble_init_params, sizeof(ble_init_params), 500); if (ret == -MP_ETIMEDOUT) { return false; } tl_ble_hci_cmd_resp(HCI_OPCODE(0x03, 0x0003), NULL, 0); return true; } void rfcore_ble_hci_cmd(size_t len, const uint8_t *src) { DEBUG_printf("rfcore_ble_hci_cmd\n"); #if HCI_TRACE printf("[% 8d] >HCI_CMD(%02x", mp_hal_ticks_ms(), src[0]); for (int i = 1; i < len; ++i) { printf(":%02x", src[i]); } printf(")\n"); #endif tl_list_node_t *n; uint32_t ch; if (src[0] == HCI_KIND_BT_CMD) { n = (tl_list_node_t *)&ipcc_membuf_ble_cmd_buf[0]; ch = IPCC_CH_BLE; } else if (src[0] == HCI_KIND_BT_ACL) { n = (tl_list_node_t *)&ipcc_membuf_ble_hci_acl_data_buf[0]; ch = IPCC_CH_HCI_ACL; // Give the previous ACL command up to 100ms to complete. mp_uint_t timeout_start_ticks_ms = mp_hal_ticks_ms(); while (hci_acl_cmd_pending) { if (mp_hal_ticks_ms() - timeout_start_ticks_ms > 100) { break; } #if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE mp_bluetooth_nimble_hci_uart_wfi(); #endif } // Prevent sending another command until this one returns with HCI_EVENT_COMMAND_{COMPLETE,STATUS}. hci_acl_cmd_pending = true; } else { printf("** UNEXPECTED HCI HDR: 0x%02x **\n", src[0]); return; } n->next = NULL; n->prev = NULL; memcpy(n->body, src, len); // IPCC indicate. LL_C1_IPCC_SetFlag_CHx(IPCC, ch); } size_t rfcore_ble_check_msg(rfcore_ble_msg_callback_t cb, void *env) { parse_hci_info_t parse = { cb, env, false }; size_t len = tl_check_msg(&ipcc_mem_ble_evt_queue, IPCC_CH_BLE, &parse); // Intercept HCI_Reset events and reconfigure the controller following the reset if (parse.was_hci_reset_evt) { uint8_t buf[8]; buf[0] = 0; // config offset buf[1] = 6; // config length mp_hal_get_mac(MP_HAL_MAC_BDADDR, &buf[2]); #define SWAP_UINT8(a, b) { uint8_t temp = a; a = b; b = temp; \ } SWAP_UINT8(buf[2], buf[7]); SWAP_UINT8(buf[3], buf[6]); SWAP_UINT8(buf[4], buf[5]); tl_ble_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_WRITE_CONFIG), buf, 8); // set BDADDR } return len; } // "level" is 0x00-0x1f, ranging from -40 dBm to +6 dBm (not linear). void rfcore_ble_set_txpower(uint8_t level) { uint8_t buf[2] = { 0x00, level }; tl_ble_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_SET_TX_POWER), buf, 2); } void rfcore_start_flash_erase(void) { uint8_t buf = 1; // ERASE_ACTIVITY_ON tl_sys_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_C2_FLASH_ERASE_ACTIVITY), &buf, 1, 0); } void rfcore_end_flash_erase(void) { uint8_t buf = 0; // ERASE_ACTIVITY_OFF tl_sys_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_C2_FLASH_ERASE_ACTIVITY), &buf, 1, 0); } /******************************************************************************/ // IPCC IRQ Handlers void IPCC_C1_TX_IRQHandler(void) { IRQ_ENTER(IPCC_C1_TX_IRQn); IRQ_EXIT(IPCC_C1_TX_IRQn); } void IPCC_C1_RX_IRQHandler(void) { IRQ_ENTER(IPCC_C1_RX_IRQn); DEBUG_printf("IPCC_C1_RX_IRQHandler\n"); if (LL_C2_IPCC_IsActiveFlag_CHx(IPCC, IPCC_CH_BLE)) { // Disable this IRQ until the incoming data is processed (in tl_check_msg). LL_C1_IPCC_DisableReceiveChannel(IPCC, IPCC_CH_BLE); #if MICROPY_PY_BLUETOOTH // Queue up the scheduler to process UART data and run events. mp_bluetooth_hci_poll_now(); #endif } IRQ_EXIT(IPCC_C1_RX_IRQn); } /******************************************************************************/ // MicroPython bindings static mp_obj_t rfcore_status(void) { return mp_obj_new_int_from_uint(ipcc_mem_dev_info_tab.fus.table_state); } MP_DEFINE_CONST_FUN_OBJ_0(rfcore_status_obj, rfcore_status); static mp_obj_t get_version_tuple(uint32_t data) { mp_obj_t items[] = { MP_OBJ_NEW_SMALL_INT(data >> 24), MP_OBJ_NEW_SMALL_INT(data >> 16 & 0xFF), MP_OBJ_NEW_SMALL_INT(data >> 8 & 0xFF), MP_OBJ_NEW_SMALL_INT(data >> 4 & 0xF), MP_OBJ_NEW_SMALL_INT(data & 0xF) }; return mp_obj_new_tuple(5, items); } static mp_obj_t rfcore_fw_version(mp_obj_t fw_id_in) { if (ipcc_mem_dev_info_tab.fus.table_state == MAGIC_IPCC_MEM_INCORRECT) { mp_raise_OSError(MP_EINVAL); } mp_int_t fw_id = mp_obj_get_int(fw_id_in); bool fus_active = ipcc_mem_dev_info_tab.fus.table_state == MAGIC_FUS_ACTIVE; uint32_t v; if (fw_id == 0) { // FUS v = fus_active ? ipcc_mem_dev_info_tab.fus.fus_version : ipcc_mem_dev_info_tab.ws.fus_version; } else { // WS v = fus_active ? ipcc_mem_dev_info_tab.fus.ws_version : ipcc_mem_dev_info_tab.ws.fw_version; } return get_version_tuple(v); } MP_DEFINE_CONST_FUN_OBJ_1(rfcore_fw_version_obj, rfcore_fw_version); static mp_obj_t rfcore_sys_hci(size_t n_args, const mp_obj_t *args) { if (ipcc_mem_dev_info_tab.fus.table_state == MAGIC_IPCC_MEM_INCORRECT) { mp_raise_OSError(MP_EINVAL); } mp_int_t ogf = mp_obj_get_int(args[0]); mp_int_t ocf = mp_obj_get_int(args[1]); mp_buffer_info_t bufinfo = {0}; mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ); mp_int_t timeout_ms = 0; if (n_args >= 4) { timeout_ms = mp_obj_get_int(args[3]); } ssize_t len = tl_sys_hci_cmd_resp(HCI_OPCODE(ogf, ocf), bufinfo.buf, bufinfo.len, timeout_ms); if (len < 0) { mp_raise_OSError(-len); } return mp_obj_new_bytes(ipcc_membuf_sys_cmd_buf, len); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(rfcore_sys_hci_obj, 3, 4, rfcore_sys_hci); #endif // defined(STM32WB)