From 0713faaec0cbce21068ca5fc9a608c3729d35dc1 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 4 Mar 2026 10:50:50 +1100 Subject: [PATCH 1/7] rp2: Add Zephyr BLE HCI driver and build integration. Signed-off-by: Andrew Leech --- ports/rp2/CMakeLists.txt | 101 ++- ports/rp2/Makefile | 4 + ports/rp2/boards/RPI_PICO2_W/mpconfigboard.h | 13 + .../mpconfigvariant_zephyr_ble.cmake | 18 + .../mpconfigvariant_zephyr_ble.cmake | 16 + ports/rp2/cyw43_btfw_43439_extern.h | 8 + ports/rp2/cyw43_bthci_uart_wrapper.c | 68 ++ ports/rp2/cyw43_configport.h | 42 +- ports/rp2/cyw43_mp_malloc.h | 37 + ports/rp2/machine_pin_cyw43.c | 35 +- ports/rp2/main.c | 17 +- ports/rp2/memmap_mp_rp2040.ld | 30 + ports/rp2/memmap_mp_rp2350.ld | 29 + ports/rp2/modmachine.c | 4 + ports/rp2/mpconfigport.h | 6 + ports/rp2/mphalport.c | 39 + ports/rp2/mpzephyrport_rp2.c | 697 ++++++++++++++++++ ports/rp2/mpzephyrport_rp2_stub.c | 95 +++ 18 files changed, 1249 insertions(+), 10 deletions(-) create mode 100644 ports/rp2/boards/RPI_PICO2_W/mpconfigvariant_zephyr_ble.cmake create mode 100644 ports/rp2/boards/RPI_PICO_W/mpconfigvariant_zephyr_ble.cmake create mode 100644 ports/rp2/cyw43_btfw_43439_extern.h create mode 100644 ports/rp2/cyw43_bthci_uart_wrapper.c create mode 100644 ports/rp2/cyw43_mp_malloc.h create mode 100644 ports/rp2/mpzephyrport_rp2.c create mode 100644 ports/rp2/mpzephyrport_rp2_stub.c diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 2caa5428f39ec..d8cdb618bbe23 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -84,6 +84,9 @@ endif() list(APPEND GIT_SUBMODULES lib/mbedtls) list(APPEND GIT_SUBMODULES lib/tinyusb) +# Workaround for pico-sdk host toolchain issue, see directory for details +list(APPEND CMAKE_MODULE_PATH "${MICROPY_PORT_DIR}/tools_patch") + # Include component cmake fragments include(${MICROPY_DIR}/py/py.cmake) include(${MICROPY_DIR}/extmod/extmod.cmake) @@ -259,6 +262,13 @@ elseif(PICO_RISCV) ) endif() +# When a board variant explicitly disables threading, override mpconfigport.h default +if(DEFINED MICROPY_PY_THREAD AND NOT MICROPY_PY_THREAD) + target_compile_definitions(${MICROPY_TARGET} PRIVATE + MICROPY_PY_THREAD=0 + ) +endif() + # Use our custom pico_float_micropython float implementation. This is needed for two reasons: # - to fix inf handling in pico-sdk's __wrap___aeabi_fadd(); # - so we can use our own libm functions, to fix inaccuracies in the pico-sdk versions. @@ -325,25 +335,47 @@ if (MICROPY_PY_LWIP) endif() if(MICROPY_PY_BLUETOOTH) + # Add shared HCI infrastructure for all BLE stacks (BTstack, NimBLE, Zephyr) + # Provides common polling, scheduling, and controller interface (WEAK overrides) list(APPEND MICROPY_SOURCE_PORT mpbthciport.c) + target_compile_definitions(${MICROPY_TARGET} PRIVATE MICROPY_PY_BLUETOOTH=1 MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS=1 MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE=1 ) + # Interlock requires threading support for mp_thread_get_state/set_state + if(NOT DEFINED MICROPY_PY_THREAD OR MICROPY_PY_THREAD) + target_compile_definitions(${MICROPY_TARGET} PRIVATE + MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS_WITH_INTERLOCK=1 + MICROPY_PY_BLUETOOTH_SYNC_EVENT_STACK_SIZE=2048 + ) + endif() endif() if (MICROPY_PY_BLUETOOTH_CYW43) target_compile_definitions(${MICROPY_TARGET} PRIVATE CYW43_ENABLE_BLUETOOTH=1 MICROPY_PY_BLUETOOTH_CYW43=1 + CYW43_INCLUDE_LEGACY_F1_OVERFLOW_WORKAROUND_VARIABLES=1 + ) + + # Add CYW43 external pin support (needed for LED and other CYW43 GPIO pins) + list(APPEND MICROPY_SOURCE_PORT + machine_pin_cyw43.c ) if (MICROPY_BLUETOOTH_BTSTACK) + # BTstack uses SPI/SDIO BT transport target_link_libraries(${MICROPY_TARGET} pico_btstack_hci_transport_cyw43 ) endif() + + if (MICROPY_BLUETOOTH_ZEPHYR) + # Zephyr BLE uses CYW43 SPI btbus (same as BTstack) + # HCI transport files added in MICROPY_BLUETOOTH_ZEPHYR section above + endif() endif() if (MICROPY_BLUETOOTH_BTSTACK) @@ -387,6 +419,53 @@ if(MICROPY_BLUETOOTH_NIMBLE) list(APPEND MICROPY_INC_CORE ${NIMBLE_INCLUDE}) endif() +if(MICROPY_BLUETOOTH_ZEPHYR) + list(APPEND GIT_SUBMODULES lib/zephyr) + if(NOT UPDATE_SUBMODULES AND NOT EXISTS ${MICROPY_DIR}/lib/zephyr/subsys/bluetooth/host/hci_core.c) + message(FATAL_ERROR " zephyr not initialized.\n Run 'make BOARD=${MICROPY_BOARD} submodules'") + endif() + + # CYW43 controller functions provided by pico-sdk via WEAK overrides + if (MICROPY_PY_NETWORK_CYW43) + list(APPEND MICROPY_SOURCE_PORT mpzephyrport_rp2.c) # Full CYW43 HCI transport + else() + list(APPEND MICROPY_SOURCE_PORT mpzephyrport_rp2_stub.c) # Minimal HCI stub + endif() + + include(${MICROPY_DIR}/extmod/zephyr_ble/zephyr_ble.cmake) + target_link_libraries(${MICROPY_TARGET} micropy_extmod_zephyr_ble) + + # Link CMSIS headers to Zephyr BLE (provides __enable_irq, __disable_irq, __ISB, __get_PRIMASK) + # These intrinsics are required by Zephyr's arch/arm/asm_inline_gcc.h + if(TARGET cmsis_core_headers) + target_link_libraries(micropy_extmod_zephyr_ble INTERFACE cmsis_core_headers) + endif() + + # Define port-specific macro for cmsis_core.h stub header + target_compile_definitions(micropy_extmod_zephyr_ble INTERFACE __ZEPHYR_BLE_RP2_PORT__) + + get_target_property(ZEPHYR_INCLUDE micropy_extmod_zephyr_ble INTERFACE_INCLUDE_DIRECTORIES) + list(APPEND MICROPY_INC_CORE ${ZEPHYR_INCLUDE}) + + # Add Zephyr BLE sources for QSTR/root pointer scanning + get_target_property(ZEPHYR_BLE_SOURCES micropy_extmod_zephyr_ble INTERFACE_SOURCES) + list(APPEND MICROPY_SOURCE_QSTR ${ZEPHYR_BLE_SOURCES}) + + target_compile_definitions(${MICROPY_TARGET} PRIVATE + MICROPY_BLUETOOTH_ZEPHYR=1 + MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS=1 + ) + + # MICROPY_PY_BLUETOOTH_USE_ZEPHYR_HCI calls mp_bluetooth_hci_controller_init() + # before bt_enable(). For CYW43, HCI transport is initialized via + # bt_hci_transport_setup() which is called by bt_enable(). + if (NOT MICROPY_PY_NETWORK_CYW43) + target_compile_definitions(${MICROPY_TARGET} PRIVATE + MICROPY_PY_BLUETOOTH_USE_ZEPHYR_HCI=1 + ) + endif() +endif() + # tinyusb helper target_include_directories(${MICROPY_TARGET} PRIVATE ${MICROPY_DIR}/shared/tinyusb/ @@ -408,15 +487,33 @@ if (MICROPY_PY_NETWORK_CYW43) ) endif() - list(APPEND MICROPY_SOURCE_PORT - machine_pin_cyw43.c + # Override cyw43_malloc/free to use MicroPython's tracked allocator. + # This is critical because the pico-sdk's default uses C malloc which + # panics with "Out of memory" during BT firmware download. + # The compile definitions are set on cyw43_driver which is an INTERFACE + # library, so they propagate to all targets that link it. + target_compile_definitions(cyw43_driver INTERFACE + cyw43_malloc=m_tracked_calloc_wrapper + cyw43_free=m_tracked_free + ) + # Force-include header with function declarations for the above. + # Use generator expression to only apply to C files, not assembly. + target_compile_options(cyw43_driver INTERFACE + $<$:-include${MICROPY_PORT_DIR}/cyw43_mp_malloc.h> ) + # Link CYW43 WiFi driver (for both BTstack and Zephyr BLE) target_link_libraries(${MICROPY_TARGET} cyw43_driver_picow ) + + # Port dir must come before pico_cyw43_driver/include so MicroPython's + # cyw43_configport.h is found first (the pico-SDK version defines + # CYW43_THREAD_ENTER as a function call requiring cyw43_arch linkage). target_include_directories(${MICROPY_TARGET} PRIVATE + "${MICROPY_PORT_DIR}" ${MICROPY_DIR}/lib/cyw43-driver/ + ${PICO_SDK_PATH}/src/rp2_common/pico_cyw43_driver/include ) endif() diff --git a/ports/rp2/Makefile b/ports/rp2/Makefile index 0ba6c81a006be..889205adc36b0 100644 --- a/ports/rp2/Makefile +++ b/ports/rp2/Makefile @@ -60,6 +60,10 @@ ifdef MICROPY_C_HEAP_SIZE CMAKE_ARGS += -DMICROPY_C_HEAP_SIZE=$(MICROPY_C_HEAP_SIZE) endif +ifdef MICROPY_BLUETOOTH_ZEPHYR +CMAKE_ARGS += -DMICROPY_BLUETOOTH_ZEPHYR=1 +endif + HELP_PICO_SDK_SUBMODULE ?= "\033[1;31mError: pico-sdk submodule is not initialized.\033[0m Run 'make submodules'" HELP_BUILD_ERROR ?= "See \033[1;31mhttps://github.com/micropython/micropython/wiki/Build-Troubleshooting\033[0m" diff --git a/ports/rp2/boards/RPI_PICO2_W/mpconfigboard.h b/ports/rp2/boards/RPI_PICO2_W/mpconfigboard.h index fe688c2803192..eaa34a83a80c1 100644 --- a/ports/rp2/boards/RPI_PICO2_W/mpconfigboard.h +++ b/ports/rp2/boards/RPI_PICO2_W/mpconfigboard.h @@ -7,15 +7,28 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "Pico2W" // CYW43 driver configuration. +// Use #ifndef guards as pico-SDK also defines these +#ifndef CYW43_USE_SPI #define CYW43_USE_SPI (1) +#endif +#ifndef CYW43_LWIP #define CYW43_LWIP (1) +#endif +#ifndef CYW43_GPIO #define CYW43_GPIO (1) +#endif +#ifndef CYW43_SPI_PIO #define CYW43_SPI_PIO (1) +#endif // For debugging mbedtls - also set // Debug level (0-4) 1=warning, 2=info, 3=debug, 4=verbose // #define MODUSSL_MBEDTLS_DEBUG_LEVEL 1 +// BLE UART for CYW43 Bluetooth controller (UART0) +#define MICROPY_HW_BLE_UART_ID (0) +#define MICROPY_HW_BLE_UART_BAUDRATE (115200) + #define MICROPY_HW_PIN_EXT_COUNT CYW43_WL_GPIO_COUNT int mp_hal_is_pin_reserved(int n); diff --git a/ports/rp2/boards/RPI_PICO2_W/mpconfigvariant_zephyr_ble.cmake b/ports/rp2/boards/RPI_PICO2_W/mpconfigvariant_zephyr_ble.cmake new file mode 100644 index 0000000000000..f786f00a5a0a4 --- /dev/null +++ b/ports/rp2/boards/RPI_PICO2_W/mpconfigvariant_zephyr_ble.cmake @@ -0,0 +1,18 @@ +# Zephyr BLE variant for Pico 2 W (cooperative polling, no FreeRTOS) +# Build with: make BOARD=RPI_PICO2_W BOARD_VARIANT=zephyr_ble + +# RP2350 platform (normally set by the default mpconfigvariant.cmake) +set(PICO_PLATFORM "rp2350") + +# Disable threading - use cooperative polling instead of FreeRTOS tasks +set(MICROPY_PY_THREAD OFF) + +# Allocate 16KB C heap for GATT service/UUID allocations +set(MICROPY_C_HEAP_SIZE 16384) + +# Override bluetooth stack selection +set(MICROPY_BLUETOOTH_BTSTACK OFF) +set(MICROPY_BLUETOOTH_ZEPHYR ON) + +# Force fetch picotool from git (version mismatch workaround) +set(PICOTOOL_FORCE_FETCH_FROM_GIT ON) diff --git a/ports/rp2/boards/RPI_PICO_W/mpconfigvariant_zephyr_ble.cmake b/ports/rp2/boards/RPI_PICO_W/mpconfigvariant_zephyr_ble.cmake new file mode 100644 index 0000000000000..a25e1ea75c0b3 --- /dev/null +++ b/ports/rp2/boards/RPI_PICO_W/mpconfigvariant_zephyr_ble.cmake @@ -0,0 +1,16 @@ +# Zephyr BLE variant for Pico W (cooperative polling, no FreeRTOS) +# Build with: make BOARD=RPI_PICO_W BOARD_VARIANT=zephyr_ble +message(STATUS "Loading Zephyr BLE polling variant for Pico W") + +# Disable threading - use cooperative polling instead of FreeRTOS tasks +set(MICROPY_PY_THREAD OFF) + +# Allocate 16KB C heap for GATT service/UUID allocations +set(MICROPY_C_HEAP_SIZE 16384) + +# Override bluetooth stack selection +set(MICROPY_BLUETOOTH_BTSTACK OFF) +set(MICROPY_BLUETOOTH_ZEPHYR ON) + +# Force fetch picotool from git (version mismatch workaround) +set(PICOTOOL_FORCE_FETCH_FROM_GIT ON) diff --git a/ports/rp2/cyw43_btfw_43439_extern.h b/ports/rp2/cyw43_btfw_43439_extern.h new file mode 100644 index 0000000000000..23ecd8aa56d53 --- /dev/null +++ b/ports/rp2/cyw43_btfw_43439_extern.h @@ -0,0 +1,8 @@ +// Stub header to declare BT firmware data as extern +// (actual definition is in cybt_shared_bus.c) +extern const unsigned char cyw43_btfw_43439[]; +extern const unsigned int cyw43_btfw_43439_len; + +// Alias names used in cyw43_bthci_uart.c +#define btfw_data cyw43_btfw_43439 +#define btfw_len cyw43_btfw_43439_len diff --git a/ports/rp2/cyw43_bthci_uart_wrapper.c b/ports/rp2/cyw43_bthci_uart_wrapper.c new file mode 100644 index 0000000000000..802a91f6740ad --- /dev/null +++ b/ports/rp2/cyw43_bthci_uart_wrapper.c @@ -0,0 +1,68 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 MicroPython Contributors + * + * 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. + */ + +// Wrapper to include cyw43_bthci_uart.c with proper CYW43 BT configuration +// +// NOTE: The BT firmware data is defined in cybt_shared_bus.c (part of +// cyw43_driver_picow library). To avoid duplicate definitions, we declare +// the firmware data as extern here and skip including the firmware header. + +// Include MicroPython HAL +#include "py/mphal.h" + +// Force-define CYW43 BT UART transport before any CYW43 includes +#ifdef CYW43_ENABLE_BLUETOOTH_OVER_UART +#undef CYW43_ENABLE_BLUETOOTH_OVER_UART +#endif +#define CYW43_ENABLE_BLUETOOTH_OVER_UART 1 + +// Use stub firmware header that declares data as extern +#define CYW43_BT_FIRMWARE_INCLUDE_FILE "cyw43_btfw_43439_extern.h" + +// Define MAC address index for BDADDR +#ifndef CYW43_HAL_MAC_BDADDR +#define CYW43_HAL_MAC_BDADDR MP_HAL_MAC_BDADDR +#endif + +// Define other BT config that should come from cyw43_configport.h +#ifndef CYW43_PIN_BT_REG_ON +#define CYW43_PIN_BT_REG_ON (0) +#endif +#ifndef CYW43_PIN_BT_CTS +#define CYW43_PIN_BT_CTS (2) +#endif +#ifndef CYW43_PIN_BT_HOST_WAKE +#define CYW43_PIN_BT_HOST_WAKE (3) +#endif +#ifndef CYW43_PIN_BT_DEV_WAKE +#define CYW43_PIN_BT_DEV_WAKE (4) +#endif +#ifndef CYW43_HAL_UART_READCHAR_BLOCKING_WAIT +#define CYW43_HAL_UART_READCHAR_BLOCKING_WAIT mp_event_handle_nowait() +#endif + +// Include the actual implementation +#include "../../lib/cyw43-driver/src/cyw43_bthci_uart.c" diff --git a/ports/rp2/cyw43_configport.h b/ports/rp2/cyw43_configport.h index 2da0b1f8a4982..4027059bfe19c 100644 --- a/ports/rp2/cyw43_configport.h +++ b/ports/rp2/cyw43_configport.h @@ -26,21 +26,43 @@ #ifndef MICROPY_INCLUDED_RP2_CYW43_CONFIGPORT_H #define MICROPY_INCLUDED_RP2_CYW43_CONFIGPORT_H +// Include mpconfig.h first to get MICROPY_BLUETOOTH_ZEPHYR definition +#include "py/mpconfig.h" + #include "extmod/cyw43_config_common.h" +#if MICROPY_BLUETOOTH_ZEPHYR +// Override CYW43_PRINTF to use mp_plat_print which is available during +// early BT init before the Python printer is set up. +#include "py/mpprint.h" +#undef CYW43_PRINTF +#define CYW43_PRINTF(...) mp_printf(&mp_plat_print, __VA_ARGS__) +#endif + +#ifndef CYW43_INCLUDE_LEGACY_F1_OVERFLOW_WORKAROUND_VARIABLES #define CYW43_INCLUDE_LEGACY_F1_OVERFLOW_WORKAROUND_VARIABLES (1) +#endif #define CYW43_WIFI_NVRAM_INCLUDE_FILE "wifi_nvram_43439.h" #define CYW43_SLEEP_MAX (10) // Unclear why rp2 port overrides the default here #define CYW43_USE_OTP_MAC (1) -static inline bool cyw43_poll_is_pending(void) { - return pendsv_is_pending(PENDSV_DISPATCH_CYW43); -} +// cyw43_poll_is_pending is defined as a function in mphalport.c +// (pico-SDK's library needs it as an actual symbol, not just static inline) +bool cyw43_poll_is_pending(void); static inline void cyw43_yield(void) { + #if MICROPY_BLUETOOTH_ZEPHYR + // Use simple busy-wait for IOCTL polling. SPI responses come directly from + // the chip via polling, not from the service task. + uint32_t start = time_us_32(); + while (time_us_32() - start < 1000) { + tight_loop_contents(); + } + #else if (!cyw43_poll_is_pending()) { best_effort_wfe_or_timeout(make_timeout_time_ms(1)); } + #endif } #define CYW43_POST_POLL_HOOK cyw43_post_poll_hook(); @@ -103,4 +125,18 @@ uint cyw43_get_pin_wl(cyw43_pin_index_t pin_id); #define cyw43_free m_tracked_free #endif +// Bluetooth HCI UART configuration (for CYW43 BT controller) +#if CYW43_ENABLE_BLUETOOTH_OVER_UART +#define CYW43_BT_FIRMWARE_INCLUDE_FILE "firmware/cyw43_btfw_43439.h" +#define CYW43_PIN_BT_REG_ON (0) // Internal to CYW43 chip +#define CYW43_PIN_BT_CTS (2) // Internal to CYW43 chip +#define CYW43_PIN_BT_HOST_WAKE (3) // Internal to CYW43 chip +#define CYW43_PIN_BT_DEV_WAKE (4) // Internal to CYW43 chip +#define MICROPY_HW_BLE_UART_ID (0) // UART0 for BT HCI +#define MICROPY_HW_BLE_UART_BAUDRATE (115200) + +// Hook to process events while waiting for UART data during BT init +#define CYW43_HAL_UART_READCHAR_BLOCKING_WAIT CYW43_EVENT_POLL_HOOK +#endif + #endif // MICROPY_INCLUDED_RP2_CYW43_CONFIGPORT_H diff --git a/ports/rp2/cyw43_mp_malloc.h b/ports/rp2/cyw43_mp_malloc.h new file mode 100644 index 0000000000000..f38383f802a15 --- /dev/null +++ b/ports/rp2/cyw43_mp_malloc.h @@ -0,0 +1,37 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024-2026 Andrew Leech + * + * 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. + */ +#ifndef MICROPY_INCLUDED_RP2_CYW43_MP_MALLOC_H +#define MICROPY_INCLUDED_RP2_CYW43_MP_MALLOC_H + +#include + +// Forward declarations for MicroPython tracked memory allocator functions +// used by the CYW43 driver (via CMake compile definitions that redefine +// cyw43_malloc/cyw43_free to these functions). +void *m_tracked_calloc_wrapper(size_t size); +void m_tracked_free(void *ptr); + +#endif // MICROPY_INCLUDED_RP2_CYW43_MP_MALLOC_H diff --git a/ports/rp2/machine_pin_cyw43.c b/ports/rp2/machine_pin_cyw43.c index f79d932f7a535..587f1a72ec78c 100644 --- a/ports/rp2/machine_pin_cyw43.c +++ b/ports/rp2/machine_pin_cyw43.c @@ -30,10 +30,13 @@ #include "py/runtime.h" #include "py/mphal.h" -#if defined(MICROPY_PY_NETWORK_CYW43) && defined(MICROPY_HW_PIN_EXT_COUNT) && defined(CYW43_GPIO) && CYW43_GPIO == 1 +#if (defined(MICROPY_PY_NETWORK_CYW43) || defined(MICROPY_PY_BLUETOOTH_CYW43)) && defined(MICROPY_HW_PIN_EXT_COUNT) && defined(CYW43_GPIO) && CYW43_GPIO == 1 #include "modmachine.h" #include "machine_pin.h" + +#if defined(MICROPY_PY_NETWORK_CYW43) +// WiFi enabled - full implementation with cyw43_state #include "lib/cyw43-driver/src/cyw43.h" void machine_pin_ext_init(void) { @@ -83,4 +86,32 @@ void machine_pin_ext_config(machine_pin_obj_t *self, int mode, int value) { } } -#endif // defined(MICROPY_PY_NETWORK_CYW43) && defined(MICROPY_HW_PIN_EXT_COUNT) && defined(CYW43_GPIO) && CYW43_GPIO == 1 +#else +// WiFi disabled, BT enabled - stub implementations +// CYW43 GPIO pins (like LED) won't work without the WiFi driver's cyw43_state + +void machine_pin_ext_init(void) { +} + +void machine_pin_ext_set(machine_pin_obj_t *self, bool value) { + // Stub: GPIO not available without WiFi driver + (void)self; + (void)value; +} + +bool machine_pin_ext_get(machine_pin_obj_t *self) { + // Stub: GPIO not available without WiFi driver + (void)self; + return false; +} + +void machine_pin_ext_config(machine_pin_obj_t *self, int mode, int value) { + // Stub: GPIO not available without WiFi driver + (void)self; + (void)mode; + (void)value; +} + +#endif // MICROPY_PY_NETWORK_CYW43 + +#endif // (MICROPY_PY_NETWORK_CYW43 || MICROPY_PY_BLUETOOTH_CYW43) && MICROPY_HW_PIN_EXT_COUNT && CYW43_GPIO diff --git a/ports/rp2/main.c b/ports/rp2/main.c index c67f404f61e27..94c0966de7482 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -44,7 +44,9 @@ #include "uart.h" #include "modmachine.h" #include "modrp2.h" +#if MICROPY_BLUETOOTH_BTSTACK || MICROPY_BLUETOOTH_NIMBLE #include "mpbthciport.h" +#endif #include "mpnetworkport.h" #include "genhdr/mpversion.h" #include "mp_usbd.h" @@ -159,7 +161,9 @@ int main(int argc, char **argv) { { cyw43_init(&cyw43_state); cyw43_irq_init(); + #if !MICROPY_BLUETOOTH_ZEPHYR cyw43_post_poll_hook(); // enable the irq + #endif uint8_t buf[8]; memcpy(&buf[0], "PICO", 4); @@ -198,6 +202,12 @@ int main(int argc, char **argv) { #if MICROPY_PY_BLUETOOTH mp_bluetooth_hci_init(); #endif + + #if MICROPY_BLUETOOTH_ZEPHYR && (MICROPY_PY_NETWORK_CYW43 || MICROPY_PY_BLUETOOTH_CYW43) + // For Zephyr BLE, enable CYW43 IRQ after BLE subsystem is initialized + cyw43_post_poll_hook(); + #endif + #if MICROPY_PY_NETWORK mod_network_init(); #endif @@ -247,6 +257,10 @@ int main(int argc, char **argv) { // Hook for resetting anything immediately following a soft reset command. MICROPY_BOARD_START_SOFT_RESET(); + // BLE must be deinitialized before network to ensure clean shutdown + #if MICROPY_PY_BLUETOOTH + mp_bluetooth_deinit(); + #endif #if MICROPY_PY_NETWORK mod_network_deinit(); #endif @@ -255,9 +269,6 @@ int main(int argc, char **argv) { #endif rp2_dma_deinit(); rp2_pio_deinit(); - #if MICROPY_PY_BLUETOOTH - mp_bluetooth_deinit(); - #endif #if MICROPY_PY_MACHINE_PWM machine_pwm_deinit_all(); #endif diff --git a/ports/rp2/memmap_mp_rp2040.ld b/ports/rp2/memmap_mp_rp2040.ld index 2a63aefaeca4c..077ac13f39e51 100644 --- a/ports/rp2/memmap_mp_rp2040.ld +++ b/ports/rp2/memmap_mp_rp2040.ld @@ -171,6 +171,36 @@ SECTIONS *(.jcr) . = ALIGN(4); + + /* Zephyr iterable sections - must be in copied data section */ + /* These symbols are required by Zephyr's TYPE_SECTION_START/END macros */ + . = ALIGN(4); + _net_buf_pool_list_start = .; + KEEP(*(SORT(._net_buf_pool.static.*))); + _net_buf_pool_list_end = .; + + . = ALIGN(4); + _bt_conn_cb_list_start = .; + KEEP(*(SORT(._bt_conn_cb.static.*))); + _bt_conn_cb_list_end = .; + + . = ALIGN(4); + _bt_gatt_service_static_list_start = .; + KEEP(*(SORT(._bt_gatt_service_static.static.*))); + _bt_gatt_service_static_list_end = .; + + . = ALIGN(4); + _bt_l2cap_fixed_chan_list_start = .; + KEEP(*(SORT(._bt_l2cap_fixed_chan.static.*))); + _bt_l2cap_fixed_chan_list_end = .; + + . = ALIGN(4); + _settings_handler_static_list_start = .; + KEEP(*(SORT(._settings_handler_static.static.*))); + _settings_handler_static_list_end = .; + + . = ALIGN(4); + /* All data end */ __data_end__ = .; } > RAM AT> FLASH diff --git a/ports/rp2/memmap_mp_rp2350.ld b/ports/rp2/memmap_mp_rp2350.ld index 5eb85f53d968c..9e99978cb9283 100644 --- a/ports/rp2/memmap_mp_rp2350.ld +++ b/ports/rp2/memmap_mp_rp2350.ld @@ -187,6 +187,35 @@ SECTIONS *(.jcr) . = ALIGN(4); + + /* Zephyr iterable sections - must be in copied data section */ + /* These symbols are required by Zephyr's TYPE_SECTION_START/END macros */ + . = ALIGN(4); + _net_buf_pool_list_start = .; + KEEP(*(SORT(._net_buf_pool.static.*))); + _net_buf_pool_list_end = .; + + . = ALIGN(4); + _bt_conn_cb_list_start = .; + KEEP(*(SORT(._bt_conn_cb.static.*))); + _bt_conn_cb_list_end = .; + + . = ALIGN(4); + _bt_gatt_service_static_list_start = .; + KEEP(*(SORT(._bt_gatt_service_static.static.*))); + _bt_gatt_service_static_list_end = .; + + . = ALIGN(4); + _bt_l2cap_fixed_chan_list_start = .; + KEEP(*(SORT(._bt_l2cap_fixed_chan.static.*))); + _bt_l2cap_fixed_chan_list_end = .; + + . = ALIGN(4); + _settings_handler_static_list_start = .; + KEEP(*(SORT(._settings_handler_static.static.*))); + _settings_handler_static_list_end = .; + + . = ALIGN(4); } > RAM AT> FLASH .tdata : { diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index e5cf703edd3df..1ed6dd842ce5c 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -134,6 +134,10 @@ static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { } static void mp_machine_idle(void) { + // Process scheduled callbacks (e.g. BLE polling) before sleeping. + // This matches the STM32 behavior and ensures timely callback delivery. + mp_handle_pending(MP_HANDLE_PENDING_CALLBACKS_AND_CLEAR_EXCEPTIONS); + MICROPY_INTERNAL_WFE(1); } diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 29d084582bb3f..3c4317152ca78 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -123,6 +123,8 @@ #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) #define MICROPY_SCHEDULER_DEPTH (8) #define MICROPY_SCHEDULER_STATIC_NODES (1) +// Wake the CPU from WFE when a scheduler node is added (e.g. BLE HCI data ready). +#define MICROPY_SCHED_HOOK_SCHEDULED __sev() #ifndef MICROPY_USE_INTERNAL_ERRNO #define MICROPY_USE_INTERNAL_ERRNO (1) #endif @@ -296,6 +298,10 @@ extern void lwip_poll_hook(void); // Bluetooth code only runs in the scheduler, no locking/mutex required. #define MICROPY_PY_BLUETOOTH_ENTER uint32_t atomic_state = 0; #define MICROPY_PY_BLUETOOTH_EXIT (void)atomic_state; +// Enable central mode and GATT client support +#ifndef MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +#define MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE (1) +#endif #endif #ifndef MICROPY_BOARD_STARTUP diff --git a/ports/rp2/mphalport.c b/ports/rp2/mphalport.c index d46360cb95e70..54660bb297cf3 100644 --- a/ports/rp2/mphalport.c +++ b/ports/rp2/mphalport.c @@ -279,6 +279,45 @@ int mp_hal_is_pin_reserved(int n) { #endif } +#if MICROPY_PY_NETWORK_CYW43 +// Check if CYW43 poll is pending. +bool cyw43_poll_is_pending(void) { + return pendsv_is_pending(PENDSV_DISPATCH_CYW43); +} + +// Wait with background processing. +// Required by pico-SDK's cyw43_driver_picow library. +void cyw43_await_background_or_timeout_us(uint32_t timeout_us) { + if (timeout_us <= 1000) { + uint32_t start = mp_hal_ticks_us(); + while (mp_hal_ticks_us() - start < timeout_us) { + mp_event_handle_nowait(); + } + return; + } + mp_event_wait_ms(timeout_us / 1000); +} + +// Wrapper for m_tracked_calloc with malloc-compatible signature. +// Used by CYW43 driver (via compile definition cyw43_malloc=m_tracked_calloc_wrapper) +// because pico-sdk's default cyw43_malloc uses C malloc which has no heap in MicroPython. +void *m_tracked_calloc_wrapper(size_t size) { + return m_tracked_calloc(size, 1); +} + +#if MICROPY_BLUETOOTH_ZEPHYR +// Bluetooth HCI process callback - called by CYW43 driver when BT data is available. +// Schedule HCI processing on the main task where all BLE state access is serialized. +volatile uint32_t cyw43_bt_hci_process_count = 0; +extern void mp_bluetooth_zephyr_port_poll_now(void); +void cyw43_bluetooth_hci_process(void) { + cyw43_bt_hci_process_count++; + mp_bluetooth_zephyr_port_poll_now(); +} +#endif // MICROPY_BLUETOOTH_ZEPHYR + +#endif // MICROPY_PY_NETWORK_CYW43 + void mp_hal_get_random(size_t n, uint8_t *buf) { for (int i = 0; i < n; i += 8) { uint64_t rand64 = get_rand_64(); diff --git a/ports/rp2/mpzephyrport_rp2.c b/ports/rp2/mpzephyrport_rp2.c new file mode 100644 index 0000000000000..edfc6dcf26059 --- /dev/null +++ b/ports/rp2/mpzephyrport_rp2.c @@ -0,0 +1,697 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 MicroPython Contributors + * + * 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. + */ + +// RP2 port integration for Zephyr BLE stack with CYW43 controller + +// Suppress deprecation warnings for bt_buf_get_type() - the function still works +// and we need it for H:4 HCI transport +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +// Include board configuration first to get all defines +#include "py/mpconfig.h" + +#include "py/runtime.h" +#include "py/stream.h" +#include "py/mphal.h" +#include "extmod/modbluetooth.h" +#include "modmachine.h" + +#include + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_ZEPHYR + +// Include our HAL first (uses our macro definitions) +#include "extmod/zephyr_ble/hal/zephyr_ble_hal.h" + +// Undef conflicting macros before including real Zephyr headers +// Our macros are functionally equivalent to Zephyr's but defined without guards +// Pico-SDK KHZ/MHZ vs Zephyr function-like macros +#undef KHZ +#undef MHZ +// Our stubs vs Zephyr util.h +#undef CONTAINER_OF +#undef FLEXIBLE_ARRAY_DECLARE +#include "zephyr/device.h" +#include +#include +#include + +// CYW43 driver for WiFi/BT chip +#if MICROPY_PY_NETWORK_CYW43 +#include "lib/cyw43-driver/src/cyw43.h" + +// Debug: Access to shared bus buffer indices +typedef struct { + uint32_t host2bt_in_val; + uint32_t host2bt_out_val; + uint32_t bt2host_in_val; + uint32_t bt2host_out_val; +} cybt_fw_membuf_index_t; +extern int cybt_get_bt_buf_index(cybt_fw_membuf_index_t *p_buf_index); +#endif + +// Debug output controlled by ZEPHYR_BLE_DEBUG +// Note: debug_printf is NOT thread-safe. Only call from main task context. +// For HCI RX task on core1, use debug_printf_hci_task which is disabled. +// TEMPORARILY enabled for debugging +#define ZEPHYR_BLE_DEBUG_TEMP 0 +#if ZEPHYR_BLE_DEBUG || ZEPHYR_BLE_DEBUG_TEMP +#define debug_printf(...) mp_printf(&mp_plat_print, "mpzephyrport_rp2: " __VA_ARGS__) +#else +#define debug_printf(...) do {} while (0) +#endif +// HCI RX task debug is always disabled to prevent multicore printf races +#define debug_printf_hci_task(...) do {} while (0) +#define error_printf(...) mp_printf(&mp_plat_print, "mpzephyrport_rp2 ERROR: " __VA_ARGS__) + +// CYW43 SPI btbus HCI transport +#if MICROPY_PY_NETWORK_CYW43 + +static volatile bt_hci_recv_t recv_cb = NULL; // Returns int: 0 on success, negative on error +static const struct device *hci_dev = NULL; + +// Buffer for incoming HCI packets (4-byte CYW43 header + max HCI packet) +#define CYW43_HCI_HEADER_SIZE 4 +#define HCI_MAX_PACKET_SIZE 1024 +// IMPORTANT: Must be 4-byte aligned for CYW43 SPI DMA transfers +static uint8_t __attribute__((aligned(4))) hci_rx_buffer[CYW43_HCI_HEADER_SIZE + HCI_MAX_PACKET_SIZE]; + +// ============================================================================ +// HCI Packet Processing (common to FreeRTOS and polling paths) +// ============================================================================ + +// HCI event type counters (for GDB / k_panic inspection only). +// Compiled out in non-debug builds to avoid per-packet overhead. +#if ZEPHYR_BLE_DEBUG +static uint32_t hci_rx_evt_cmd_complete = 0; +static uint32_t hci_rx_evt_cmd_status = 0; +static uint32_t hci_rx_evt_le_meta = 0; +static uint32_t hci_rx_evt_le_adv_report = 0; +static uint32_t hci_rx_evt_le_conn_complete = 0; +static uint32_t hci_rx_evt_le_enh_conn_complete = 0; +static uint32_t hci_rx_evt_disconnect_complete = 0; +static uint32_t hci_rx_evt_other = 0; +static uint32_t hci_rx_acl = 0; +#define HCI_RX_EVT_INC(counter) ((counter)++) +#else +#define HCI_RX_EVT_INC(counter) ((void)0) +#endif + +// Rejection/error counters -- non-static for k_panic debug output. +// Always compiled in since they track error conditions, not per-packet events. +uint32_t hci_rx_rejected_len = 0; +uint32_t hci_rx_rejected_param_len = 0; +uint32_t hci_rx_rejected_oversize = 0; +uint32_t hci_rx_rejected_event = 0; +uint32_t hci_rx_rejected_acl = 0; +uint32_t hci_rx_rejected_type = 0; +uint32_t hci_rx_buf_failed = 0; +uint32_t hci_rx_total_processed = 0; + +// Process a single HCI packet from the given buffer +// Used by both FreeRTOS HCI queue processing and polling fallback +static void process_hci_rx_packet(uint8_t *rx_buf, uint32_t len); + +// ============================================================================ +// FreeRTOS HCI RX Task (Phase 6) +// Uses dedicated task for HCI packet reception when enabled. +// When disabled, uses cooperative polling via soft timer. +// ============================================================================ + +#include "extmod/zephyr_ble/hal/zephyr_ble_poll.h" +#include "extmod/zephyr_ble/hal/zephyr_ble_port.h" + +void mp_bluetooth_zephyr_process_hci_queue(void) { + // No queue in polling mode - HCI packets processed directly +} + +// ============================================================================ + +// Process a single HCI packet from the given buffer +static void process_hci_rx_packet(uint8_t *rx_buf, uint32_t len) { + if (recv_cb == NULL || len <= CYW43_HCI_HEADER_SIZE) { + return; + } + + hci_rx_total_processed++; + + // Extract packet type from CYW43 header (byte 3) + uint8_t pkt_type = rx_buf[3]; + uint8_t *pkt_data = &rx_buf[CYW43_HCI_HEADER_SIZE]; + uint32_t pkt_len = len - CYW43_HCI_HEADER_SIZE; + + // Debug: log CMD_COMPLETE and CMD_STATUS events (important for init) + // NOTE: Disabled - excessive logging during init causes timing issues + #if 0 + if (pkt_type == BT_HCI_H4_EVT && pkt_len >= 2) { + uint8_t evt = pkt_data[0]; + if (evt == 0x0E || evt == 0x0F) { // CMD_COMPLETE or CMD_STATUS + mp_printf(&mp_plat_print, "[HCI_RX] evt=0x%02x len=%lu\n", evt, (unsigned long)pkt_len); + } + } + #endif + + // Validate packet length - HCI event packets should be reasonable size + // Event header is: event_code(1) + param_len(1) + params(param_len) + // Maximum reasonable size is ~255 bytes (param_len is uint8) + if (pkt_len > 260 || pkt_len < 2) { + // Invalid packet length - likely garbage data, skip it + hci_rx_rejected_len++; + return; + } + + // Allocate Zephyr net_buf based on packet type + struct net_buf *buf = NULL; + + switch (pkt_type) { + case BT_HCI_H4_EVT: + if (pkt_len >= 2) { + // Track event types for debugging + uint8_t evt_code = pkt_data[0]; + uint8_t param_len = pkt_data[1]; + + // Validate: param_len should match actual packet length + // pkt_len = event_code(1) + param_len(1) + params(param_len) + if (param_len + 2 != pkt_len) { + // Length mismatch - corrupted packet, skip + hci_rx_rejected_param_len++; + return; + } + + // Validate event code - must be a known HCI event + // Check event code BEFORE size check because different events use + // different buffer pools with different size limits: + // - CMD_COMPLETE (0x0E), CMD_STATUS (0x0F): hci_cmd_pool (larger, ~255 bytes) + // - Other events: hci_rx_pool (CONFIG_BT_BUF_EVT_RX_SIZE = 68) + bool valid_event = false; + bool is_cmd_event = false; + if (evt_code == 0x0E) { + HCI_RX_EVT_INC(hci_rx_evt_cmd_complete); + valid_event = true; + is_cmd_event = true; + } else if (evt_code == 0x0F) { + HCI_RX_EVT_INC(hci_rx_evt_cmd_status); + valid_event = true; + is_cmd_event = true; + } else if (evt_code == 0x3E) { + HCI_RX_EVT_INC(hci_rx_evt_le_meta); + valid_event = true; + // Check LE subevent code (at pkt_data[2] after evt_code and length) + if (pkt_len >= 3) { + uint8_t subevent = pkt_data[2]; + if (subevent == 0x02) { + HCI_RX_EVT_INC(hci_rx_evt_le_adv_report); + } else if (subevent == 0x01) { + HCI_RX_EVT_INC(hci_rx_evt_le_conn_complete); + } else if (subevent == 0x0A) { + HCI_RX_EVT_INC(hci_rx_evt_le_enh_conn_complete); + } + } + } else if (evt_code == 0x05) { + HCI_RX_EVT_INC(hci_rx_evt_disconnect_complete); + HCI_RX_EVT_INC(hci_rx_evt_other); + valid_event = true; + } else if (evt_code == 0x08 || + evt_code == 0x13 || evt_code == 0x1A || + evt_code == 0x04 || evt_code == 0x03) { + HCI_RX_EVT_INC(hci_rx_evt_other); + valid_event = true; + } + // Skip unknown event codes - likely garbage + if (!valid_event) { + hci_rx_rejected_event++; + return; + } + + // Check packet fits in buffer pool + // CMD_COMPLETE/CMD_STATUS use hci_cmd_pool (255 bytes) + // Other events use hci_rx_pool (CONFIG_BT_BUF_EVT_RX_SIZE = 68) + uint32_t max_evt_size = is_cmd_event ? 255 : 68; + if (pkt_len > max_evt_size) { + hci_rx_rejected_oversize++; + return; + } + + buf = bt_buf_get_evt(pkt_data[0], false, K_FOREVER); + } + break; + case BT_HCI_H4_ACL: { + // ACL data: handle(2) + length(2) + data + if (pkt_len < 4) { + hci_rx_rejected_acl++; + return; // Too short + } + // Check declared length matches actual + uint16_t acl_len = pkt_data[2] | (pkt_data[3] << 8); + if (acl_len + 4 != pkt_len) { + hci_rx_rejected_acl++; + return; // Length mismatch + } + // Check fits in buffer + if (pkt_len > CONFIG_BT_BUF_ACL_RX_SIZE + 4) { // data + 4-byte ACL header + hci_rx_rejected_acl++; + return; // Oversized + } + HCI_RX_EVT_INC(hci_rx_acl); + buf = bt_buf_get_rx(BT_BUF_ACL_IN, K_FOREVER); + break; + } + default: + // Unknown packet type - silently ignore (catches garbage H4 types) + hci_rx_rejected_type++; + return; + } + + if (buf == NULL) { + // Don't use mp_printf here - HCI RX task doesn't hold GIL + // Just silently drop the packet + hci_rx_buf_failed++; + return; + } + + // Copy packet data to net_buf and deliver to Zephyr + net_buf_add_mem(buf, pkt_data, pkt_len); + + // Set work queue context flag so priority HCI events (like Number of Completed Packets) + // can directly process TX notify instead of queuing work. This is needed because + // priority events are processed immediately in bt_recv_unsafe(), not via work queue. + extern void mp_bluetooth_zephyr_set_sys_work_q_context(bool in_context); + mp_bluetooth_zephyr_set_sys_work_q_context(true); + + int ret = recv_cb(hci_dev, buf); + + mp_bluetooth_zephyr_set_sys_work_q_context(false); + + if (ret < 0) { + // Error - unref the buffer (no mp_printf from HCI RX task) + net_buf_unref(buf); + } +} + +// HCI packet reception handler - called from shared sched_node via soft timer. +// Strong override of weak default in zephyr_ble_poll.c. +void mp_bluetooth_zephyr_port_run_task(mp_sched_node_t *node) { + (void)node; + + // Early exit if BLE is not active (recv_cb is set by hci_cyw43_open) + // This prevents processing stale Zephyr state after soft reset. + // Also skip during deinit to prevent CYW43 SPI reads on a post-reset controller. + if (recv_cb == NULL || mp_bluetooth_zephyr_shutting_down) { + return; + } + + // Process Zephyr BLE work queues and semaphores + mp_bluetooth_zephyr_poll(); + + // Read directly from CYW43 via shared SPI bus. + // Read and process HCI packets one at a time with work between each. + extern int cyw43_bluetooth_hci_read(uint8_t *buf, uint32_t max_size, uint32_t *len); + extern void mp_bluetooth_zephyr_work_process(void); + + while (1) { + // Check buffer availability before reading HCI data. + if (!mp_bluetooth_zephyr_buffers_available()) { + mp_bluetooth_zephyr_work_process(); + if (!mp_bluetooth_zephyr_buffers_available()) { + // Still no buffers — stop reading, retry next poll. + break; + } + } + + uint32_t len = 0; + int ret = cyw43_bluetooth_hci_read(hci_rx_buffer, sizeof(hci_rx_buffer), &len); + if (ret != 0 || len <= CYW43_HCI_HEADER_SIZE) { + break; // No more packets. + } + + process_hci_rx_packet(hci_rx_buffer, len); + mp_bluetooth_zephyr_work_process(); + } + + // Flush deferred L2CAP recv notifications after all HCI processing is done. + // seg_recv_cb defers the Python IRQ notification to avoid re-entrancy issues; + // it must be flushed here after work_process completes. + extern void mp_bluetooth_zephyr_l2cap_flush_recv_notify(void); + mp_bluetooth_zephyr_l2cap_flush_recv_notify(); + + // Reschedule soft timer for continuous HCI polling. + mp_bluetooth_zephyr_port_poll_in_ms(ZEPHYR_BLE_POLL_INTERVAL_MS); +} + +// Zephyr HCI driver implementation + +// Forward declarations for poll_uart stats (defined later in file) +extern uint32_t poll_uart_count; +extern uint32_t poll_uart_hci_reads; +extern uint32_t poll_uart_cyw43_calls; +extern uint32_t poll_uart_skipped_recursion; +extern uint32_t poll_uart_skipped_no_cb; +extern uint32_t hci_tx_count; +extern uint32_t hci_tx_cmd_count; + +static int hci_cyw43_open(const struct device *dev, bt_hci_recv_t recv) { + debug_printf("hci_cyw43_open called, dev=%p recv=%p\n", dev, recv); + hci_dev = dev; + + // Reset poll_uart counters for fresh start + poll_uart_count = 0; + poll_uart_hci_reads = 0; + poll_uart_cyw43_calls = 0; + poll_uart_skipped_recursion = 0; + poll_uart_skipped_no_cb = 0; + hci_tx_count = 0; + hci_tx_cmd_count = 0; + + // Note: recv_cb is set AFTER bt_hci_transport_setup() to prevent HCI RX + // task from reading SPI bus during BT firmware download + + // Initialize CYW43 BT controller via bt_hci_transport_setup() + // This must be called before any HCI communication can happen + int ret = bt_hci_transport_setup(dev); + if (ret != 0) { + error_printf("bt_hci_transport_setup failed: %d\n", ret); + return ret; + } + + // Flush any stale HCI data from previous session + // This prevents old responses from confusing the new init sequence + extern int cyw43_bluetooth_hci_read(uint8_t *buf, uint32_t max_size, uint32_t *len); + int flush_count = 0; + uint32_t len = 0; + while (cyw43_bluetooth_hci_read(hci_rx_buffer, sizeof(hci_rx_buffer), &len) == 0 && len > 0) { + flush_count++; + len = 0; + if (flush_count > 100) { + break; // Safety limit + } + } + if (flush_count > 0) { + debug_printf("Flushed %d stale HCI packets\n", flush_count); + } + + // Now enable HCI RX by setting callback (HCI RX task is already running) + recv_cb = recv; + + debug_printf("hci_cyw43_open completed\n"); + return 0; +} + +static int hci_cyw43_close(const struct device *dev) { + // Print poll_uart stats (always available) + debug_printf("hci_cyw43_close: poll_uart calls=%lu hci_reads=%lu cyw43_calls=%lu\n", + (unsigned long)poll_uart_count, (unsigned long)poll_uart_hci_reads, + (unsigned long)poll_uart_cyw43_calls); + + recv_cb = NULL; + mp_bluetooth_zephyr_poll_stop_timer(); + + // Teardown the HCI transport to allow clean reinitialization + bt_hci_transport_teardown(dev); + + return 0; +} + +static int hci_cyw43_send(const struct device *dev, struct net_buf *buf) { + (void)dev; + uint8_t buf_type = bt_buf_get_type(buf); + debug_printf("hci_cyw43_send: type=%u len=%u data[0]=0x%02x\n", buf_type, buf->len, buf->len > 0 ? buf->data[0] : 0xFF); + + // Debug: increment TX counter + extern uint32_t hci_tx_count; + extern uint32_t hci_tx_cmd_count; + hci_tx_count++; + + // Map Zephyr buffer type to H:4 packet type + uint8_t pkt_type; + switch (buf_type) { + case BT_BUF_CMD: + hci_tx_cmd_count++; + pkt_type = BT_HCI_H4_CMD; + break; + case BT_BUF_ACL_OUT: + pkt_type = BT_HCI_H4_ACL; + break; + default: + error_printf("Unknown buffer type: %u\n", buf_type); + net_buf_unref(buf); + return -1; + } + + // CYW43 requires 4-byte header: [0,0,0,pkt_type] + packet_data + // Stack-allocate to avoid GC heap pressure on every HCI TX. + // Max size: CYW43_HCI_HEADER_SIZE(4) + CONFIG_BT_BUF_ACL_TX_SIZE(255) = 259. + size_t cyw43_pkt_size = CYW43_HCI_HEADER_SIZE + buf->len; + uint8_t __attribute__((aligned(4))) cyw43_pkt[CYW43_HCI_HEADER_SIZE + CONFIG_BT_BUF_ACL_TX_SIZE]; + + // Build CYW43 packet: 4-byte header + HCI data + memset(cyw43_pkt, 0, CYW43_HCI_HEADER_SIZE); + cyw43_pkt[3] = pkt_type; + memcpy(&cyw43_pkt[CYW43_HCI_HEADER_SIZE], buf->data, buf->len); + + // Write to CYW43 via shared SPI bus + extern int cyw43_bluetooth_hci_write(uint8_t *buf, size_t len); + int ret = cyw43_bluetooth_hci_write(cyw43_pkt, cyw43_pkt_size); + + net_buf_unref(buf); + + if (ret != 0) { + error_printf("cyw43_bluetooth_hci_write failed: %d\n", ret); + return -1; + } + + return 0; +} + +// HCI driver API structure +static const struct bt_hci_driver_api hci_cyw43_api = { + .open = hci_cyw43_open, + .close = hci_cyw43_close, + .send = hci_cyw43_send, +}; + +// Device state (required for device_is_ready()) +static struct device_state hci_device_state = { + .init_res = 0, + .initialized = true, +}; + +// HCI device structure (referenced by Zephyr via DEVICE_DT_GET macro) +// Named __device_dts_ord_0 to match what DEVICE_DT_GET() expands to +// __attribute__((used)) prevents garbage collection with -fdata-sections +__attribute__((used)) +const struct device __device_dts_ord_0 = { + .name = "HCI_CYW43", + .api = &hci_cyw43_api, + .state = &hci_device_state, + .data = NULL, +}; + +// Alias for code that uses the descriptive name +const struct device *const mp_bluetooth_zephyr_hci_dev = &__device_dts_ord_0; + +// CYW43 BT uses shared SPI bus (btbus), no UART HAL needed + +// HCI transport setup (called by BLE host during initialization) +int bt_hci_transport_setup(const struct device *dev) { + (void)dev; + + // Initialize CYW43 BT using shared SPI bus (same as BTstack) + // This ensures WiFi driver is up first, then loads BT firmware + extern int cyw43_bluetooth_hci_init(void); + return cyw43_bluetooth_hci_init(); +} + +// HCI transport teardown +int bt_hci_transport_teardown(const struct device *dev) { + debug_printf("bt_hci_transport_teardown\n"); + (void)dev; + + // CYW43 btbus doesn't have a deinit function - BT state is maintained + // The cyw43_bluetooth_hci_init() is idempotent (checks bt_loaded flag) + return 0; +} + +// Poll HCI from CYW43 SPI - called from k_sem_take() wait loop +// This reads any pending HCI data from the CYW43 chip and passes it to Zephyr +static volatile bool poll_uart_in_progress = false; + +// Debug counters for poll_uart calls (non-static for k_panic debug output) +uint32_t poll_uart_count = 0; +uint32_t poll_uart_hci_reads = 0; +uint32_t hci_tx_count = 0; +uint32_t hci_tx_cmd_count = 0; + +uint32_t mp_bluetooth_zephyr_poll_uart_count(void) { + return poll_uart_count; +} + +uint32_t mp_bluetooth_zephyr_poll_uart_hci_reads(void) { + return poll_uart_hci_reads; +} + +// Debug: track poll_uart entry reasons +uint32_t poll_uart_skipped_recursion = 0; +uint32_t poll_uart_skipped_no_cb = 0; +uint32_t poll_uart_skipped_task = 0; +uint32_t poll_uart_cyw43_calls = 0; + +// Deinitialize Zephyr port - called during ble.active(False) +void mp_bluetooth_zephyr_port_deinit(void) { + // Clean up shared soft timer and sched_node + mp_bluetooth_zephyr_poll_cleanup(); + + // Clear recv_cb since bt_disable() has reset the controller + // On reinit, bt_enable() will set up a fresh HCI transport + recv_cb = NULL; + + // DO NOT reset bt_loaded - the CYW43 BT firmware should stay loaded. + // bt_disable() sends HCI_Reset which resets the controller state. + // On reinit, bt_enable() will send another HCI_Reset to the already-loaded firmware. + // Re-downloading firmware to an already-running controller corrupts its state. + + // Reset the HOST_CTRL register cache in the shared bus driver. + // This is critical because cybt_reg_read() returns cached values for HOST_CTRL_REG_ADDR. + // After bt_disable(), the BT controller state may have changed but the cache is stale. + // This affects wake signaling in cybt_set_bt_awake() used by cybt_bus_request(). + // + // Must reset to SW_RDY (1 << 24) not 0, because: + // - btbus_init sets SW_RDY to tell firmware host is ready + // - cybt_toggle_bt_intr() XORs DATA_VALID based on cached value + // - If cache is 0, the toggle will clear SW_RDY which breaks firmware comms + extern volatile uint32_t host_ctrl_cache_reg; + host_ctrl_cache_reg = (1 << 24); // BTSDIO_REG_SW_RDY_BITMASK + + // Reset state variables for clean re-initialization + poll_uart_in_progress = false; + poll_uart_count = 0; + poll_uart_hci_reads = 0; + hci_tx_count = 0; + hci_tx_cmd_count = 0; + poll_uart_skipped_recursion = 0; + poll_uart_skipped_no_cb = 0; + poll_uart_skipped_task = 0; + poll_uart_cyw43_calls = 0; +} + +void mp_bluetooth_zephyr_poll_uart(void) { + poll_uart_count++; + + // Skip CYW43 SPI reads during BLE deinit unless we're inside k_sem_take's + // wait loop (where HCI transport must work for bt_disable's HCI_RESET). + // After bt_disable sends HCI_RESET, the CYW43 controller enters reset state + // and SPI reads can hang indefinitely, freezing the Pico W. The soft timer + // fires run_task() (not in wait loop) which would trigger this hang. + // k_sem_take calls (in wait loop) are allowed so bt_disable can complete. + if (mp_bluetooth_zephyr_shutting_down && !mp_bluetooth_zephyr_in_wait_loop) { + return; + } + + // Prevent recursion - but allow re-entry from semaphore wait loops + // When mp_bluetooth_zephyr_in_wait_loop is true, we're in k_sem_take() polling + // and need to read HCI to receive the response that will signal the semaphore. + if (poll_uart_in_progress && !mp_bluetooth_zephyr_in_wait_loop) { + poll_uart_skipped_recursion++; + return; + } + if (recv_cb == NULL) { + poll_uart_skipped_no_cb++; + return; + } + + poll_uart_in_progress = true; + + // Process any packets queued by HCI RX task first + // This is critical for timely command credit return + poll_uart_cyw43_calls++; + + // Read HCI packets one at a time with work between each. + // NOTE: This path is only used when HCI RX task is NOT running. + extern int cyw43_bluetooth_hci_read(uint8_t *buf, uint32_t max_size, uint32_t *len); + extern void mp_bluetooth_zephyr_work_process(void); + while (1) { + // Check buffer availability before reading more HCI data. + if (!mp_bluetooth_zephyr_buffers_available()) { + mp_bluetooth_zephyr_work_process(); + if (!mp_bluetooth_zephyr_buffers_available()) { + break; + } + } + + uint32_t len = 0; + int ret = cyw43_bluetooth_hci_read(hci_rx_buffer, sizeof(hci_rx_buffer), &len); + if (ret != 0 || len <= CYW43_HCI_HEADER_SIZE) { + break; // No more packets. + } + + poll_uart_hci_reads++; + process_hci_rx_packet(hci_rx_buffer, len); + mp_bluetooth_zephyr_work_process(); + } + + poll_uart_in_progress = false; +} + +// Strong override: read HCI from CYW43 SPI, process Zephyr work, reschedule. +void mp_bluetooth_hci_poll(void) { + if (mp_bluetooth_is_active()) { + mp_bluetooth_zephyr_poll_uart(); + mp_bluetooth_zephyr_poll(); + mp_bluetooth_hci_poll_in_ms(ZEPHYR_BLE_POLL_INTERVAL_MS); + } +} + +// Strong override: read HCI during k_sem_take wait loops. +// Must call poll_uart directly to drain all pending CYW43 packets. +void mp_bluetooth_zephyr_hci_uart_wfi(void) { + mp_bluetooth_zephyr_poll_uart(); + mp_bluetooth_zephyr_poll(); +} + +#else // !MICROPY_PY_NETWORK_CYW43 + +// Stub implementations for ports without CYW43 +void mp_bluetooth_zephyr_port_init(void) { +} + +void mp_bluetooth_zephyr_port_poll_in_ms(uint32_t ms) { + (void)ms; +} + +#endif // MICROPY_PY_NETWORK_CYW43 + +// Zephyr kernel arch stubs — interrupt control for cooperative scheduler. +#include "hardware/sync.h" + +unsigned int arch_irq_lock(void) { + return save_and_disable_interrupts(); +} + +void arch_irq_unlock(unsigned int key) { + restore_interrupts(key); +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_ZEPHYR diff --git a/ports/rp2/mpzephyrport_rp2_stub.c b/ports/rp2/mpzephyrport_rp2_stub.c new file mode 100644 index 0000000000000..5666589374385 --- /dev/null +++ b/ports/rp2/mpzephyrport_rp2_stub.c @@ -0,0 +1,95 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 MicroPython Contributors + * + * 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. + */ + +// Minimal Zephyr HCI driver for RP2 (UART-based controllers) +// Uses shared H:4 parser from extmod/zephyr_ble/hal/zephyr_ble_h4.c +// The weak poll_uart default handles byte-by-byte UART reading. + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_ZEPHYR + +#include +#include +#include +#include "py/runtime.h" +#include "extmod/zephyr_ble/hal/zephyr_ble_h4.h" + +// HCI open: Initialize transport and register receive callback +static int hci_open(const struct device *dev, bt_hci_recv_t recv) { + (void)dev; + mp_printf(&mp_plat_print, "HCI: hci_open called, recv=%p\n", recv); + mp_bluetooth_zephyr_h4_init(&mp_bluetooth_zephyr_hci_dev, recv); + + // Controller already initialized by mp_bluetooth_hci_controller_init() + mp_printf(&mp_plat_print, "HCI: hci_open completed\n"); + return 0; +} + +// HCI send: Send packet to controller via WEAK overrides +static int hci_send(const struct device *dev, struct net_buf *buf) { + (void)dev; + + // In Zephyr 4.2+, packet type is encoded as H:4 prefix byte at buf->data[0] + uint8_t pkt_type = buf->data[0]; + + mp_printf(&mp_plat_print, "HCI: hci_send type=%u len=%u\n", pkt_type, buf->len); + + // Send via WEAK override (controller driver provides implementation) + extern int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len); + int ret = mp_bluetooth_hci_uart_write(buf->data, buf->len); + + net_buf_unref(buf); + + if (ret != 0) { + mp_printf(&mp_plat_print, "HCI ERROR: uart_write failed: %d\n", ret); + return -1; + } + + return 0; +} + +// HCI close: Shutdown transport +static int hci_close(const struct device *dev) { + (void)dev; + mp_printf(&mp_plat_print, "HCI: hci_close called\n"); + mp_bluetooth_zephyr_h4_deinit(); + return 0; +} + +// HCI driver API structure +static const struct bt_hci_driver_api hci_driver_api = { + .open = hci_open, + .send = hci_send, + .close = hci_close, +}; + +// HCI device structure (referenced by DEVICE_DT_GET macro) +const struct device mp_bluetooth_zephyr_hci_dev = { + .name = "HCI_CYW43", + .data = NULL, + .api = &hci_driver_api, +}; + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_ZEPHYR From edc47838cb07e4048452f01ce479ba533bf5128e Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sun, 19 Apr 2026 23:53:15 +1000 Subject: [PATCH 2/7] rp2: Use shared poll interval define for Zephyr BLE. Signed-off-by: Andrew Leech --- ports/rp2/CMakeLists.txt | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index d8cdb618bbe23..a4a2a53658c43 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -492,15 +492,17 @@ if (MICROPY_PY_NETWORK_CYW43) # panics with "Out of memory" during BT firmware download. # The compile definitions are set on cyw43_driver which is an INTERFACE # library, so they propagate to all targets that link it. - target_compile_definitions(cyw43_driver INTERFACE - cyw43_malloc=m_tracked_calloc_wrapper - cyw43_free=m_tracked_free - ) - # Force-include header with function declarations for the above. - # Use generator expression to only apply to C files, not assembly. - target_compile_options(cyw43_driver INTERFACE - $<$:-include${MICROPY_PORT_DIR}/cyw43_mp_malloc.h> - ) + if(TARGET cyw43_driver) + target_compile_definitions(cyw43_driver INTERFACE + cyw43_malloc=m_tracked_calloc_wrapper + cyw43_free=m_tracked_free + ) + # Force-include header with function declarations for the above. + # Use generator expression to only apply to C files, not assembly. + target_compile_options(cyw43_driver INTERFACE + $<$:-include${MICROPY_PORT_DIR}/cyw43_mp_malloc.h> + ) + endif() # Link CYW43 WiFi driver (for both BTstack and Zephyr BLE) target_link_libraries(${MICROPY_TARGET} From 19df0c7dcad44c0737d7792ae5773e4d5fe3e218 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Thu, 12 Mar 2026 22:48:57 +1100 Subject: [PATCH 3/7] rp2: Add L2CAP flush and re-entrancy guard to port_run_task. Add mp_bluetooth_zephyr_l2cap_flush_recv_notify() call to port_run_task and re-entrancy guard to prevent recursive execution from nested mp_handle_pending() calls. Signed-off-by: Andrew Leech --- ports/rp2/mpzephyrport_rp2.c | 139 +++++++++++++++++++---------------- 1 file changed, 76 insertions(+), 63 deletions(-) diff --git a/ports/rp2/mpzephyrport_rp2.c b/ports/rp2/mpzephyrport_rp2.c index edfc6dcf26059..e9e41a49aad98 100644 --- a/ports/rp2/mpzephyrport_rp2.c +++ b/ports/rp2/mpzephyrport_rp2.c @@ -104,33 +104,26 @@ static uint8_t __attribute__((aligned(4))) hci_rx_buffer[CYW43_HCI_HEADER_SIZE + // HCI Packet Processing (common to FreeRTOS and polling paths) // ============================================================================ -// HCI event type counters (for GDB / k_panic inspection only). -// Compiled out in non-debug builds to avoid per-packet overhead. -#if ZEPHYR_BLE_DEBUG -static uint32_t hci_rx_evt_cmd_complete = 0; -static uint32_t hci_rx_evt_cmd_status = 0; -static uint32_t hci_rx_evt_le_meta = 0; -static uint32_t hci_rx_evt_le_adv_report = 0; -static uint32_t hci_rx_evt_le_conn_complete = 0; -static uint32_t hci_rx_evt_le_enh_conn_complete = 0; -static uint32_t hci_rx_evt_disconnect_complete = 0; -static uint32_t hci_rx_evt_other = 0; -static uint32_t hci_rx_acl = 0; -#define HCI_RX_EVT_INC(counter) ((counter)++) -#else -#define HCI_RX_EVT_INC(counter) ((void)0) -#endif - -// Rejection/error counters -- non-static for k_panic debug output. -// Always compiled in since they track error conditions, not per-packet events. -uint32_t hci_rx_rejected_len = 0; -uint32_t hci_rx_rejected_param_len = 0; -uint32_t hci_rx_rejected_oversize = 0; -uint32_t hci_rx_rejected_event = 0; -uint32_t hci_rx_rejected_acl = 0; -uint32_t hci_rx_rejected_type = 0; -uint32_t hci_rx_buf_failed = 0; -uint32_t hci_rx_total_processed = 0; +// HCI event type counters (for debugging what packets are received) +static volatile uint32_t hci_rx_evt_cmd_complete = 0; // 0x0E +static volatile uint32_t hci_rx_evt_cmd_status = 0; // 0x0F +static volatile uint32_t hci_rx_evt_le_meta = 0; // 0x3E (LE events including ADV_REPORT) +static volatile uint32_t hci_rx_evt_le_adv_report = 0; // 0x3E subevent 0x02 (advertising reports) +static volatile uint32_t hci_rx_evt_le_conn_complete = 0; // 0x3E subevent 0x01 (LE Connection Complete) +static volatile uint32_t hci_rx_evt_le_enh_conn_complete = 0; // 0x3E subevent 0x0A (LE Enhanced Connection Complete) +static volatile uint32_t hci_rx_evt_disconnect_complete = 0; // 0x05 (Disconnection Complete) +static volatile uint32_t hci_rx_evt_other = 0; // Other event codes +static volatile uint32_t hci_rx_acl = 0; // ACL data packets + +// Rejection counters (for debugging validation) - non-static for k_panic debug output +volatile uint32_t hci_rx_rejected_len = 0; // Invalid length +volatile uint32_t hci_rx_rejected_param_len = 0; // param_len mismatch +volatile uint32_t hci_rx_rejected_oversize = 0; // Oversized packet +volatile uint32_t hci_rx_rejected_event = 0; // Unknown event code +volatile uint32_t hci_rx_rejected_acl = 0; // Invalid ACL +volatile uint32_t hci_rx_rejected_type = 0; // Unknown packet type +volatile uint32_t hci_rx_buf_failed = 0; // Buffer alloc failed +volatile uint32_t hci_rx_total_processed = 0; // Total packets processed // Process a single HCI packet from the given buffer // Used by both FreeRTOS HCI queue processing and polling fallback @@ -210,35 +203,39 @@ static void process_hci_rx_packet(uint8_t *rx_buf, uint32_t len) { bool valid_event = false; bool is_cmd_event = false; if (evt_code == 0x0E) { - HCI_RX_EVT_INC(hci_rx_evt_cmd_complete); + hci_rx_evt_cmd_complete++; valid_event = true; is_cmd_event = true; } else if (evt_code == 0x0F) { - HCI_RX_EVT_INC(hci_rx_evt_cmd_status); + hci_rx_evt_cmd_status++; valid_event = true; is_cmd_event = true; } else if (evt_code == 0x3E) { - HCI_RX_EVT_INC(hci_rx_evt_le_meta); + hci_rx_evt_le_meta++; valid_event = true; // Check LE subevent code (at pkt_data[2] after evt_code and length) if (pkt_len >= 3) { uint8_t subevent = pkt_data[2]; if (subevent == 0x02) { - HCI_RX_EVT_INC(hci_rx_evt_le_adv_report); + hci_rx_evt_le_adv_report++; } else if (subevent == 0x01) { - HCI_RX_EVT_INC(hci_rx_evt_le_conn_complete); + // LE Connection Complete + hci_rx_evt_le_conn_complete++; } else if (subevent == 0x0A) { - HCI_RX_EVT_INC(hci_rx_evt_le_enh_conn_complete); + // LE Enhanced Connection Complete + hci_rx_evt_le_enh_conn_complete++; } } } else if (evt_code == 0x05) { - HCI_RX_EVT_INC(hci_rx_evt_disconnect_complete); - HCI_RX_EVT_INC(hci_rx_evt_other); + // Disconnection Complete + hci_rx_evt_disconnect_complete++; + hci_rx_evt_other++; valid_event = true; } else if (evt_code == 0x08 || evt_code == 0x13 || evt_code == 0x1A || evt_code == 0x04 || evt_code == 0x03) { - HCI_RX_EVT_INC(hci_rx_evt_other); + // Other valid events + hci_rx_evt_other++; valid_event = true; } // Skip unknown event codes - likely garbage @@ -271,12 +268,12 @@ static void process_hci_rx_packet(uint8_t *rx_buf, uint32_t len) { hci_rx_rejected_acl++; return; // Length mismatch } - // Check fits in buffer - if (pkt_len > CONFIG_BT_BUF_ACL_RX_SIZE + 4) { // data + 4-byte ACL header + // Check fits in buffer (CONFIG_BT_BUF_ACL_RX_SIZE = 27) + if (pkt_len > 27 + 4) { // 27 data + 4 header hci_rx_rejected_acl++; return; // Oversized } - HCI_RX_EVT_INC(hci_rx_acl); + hci_rx_acl++; buf = bt_buf_get_rx(BT_BUF_ACL_IN, K_FOREVER); break; } @@ -314,9 +311,19 @@ static void process_hci_rx_packet(uint8_t *rx_buf, uint32_t len) { // HCI packet reception handler - called from shared sched_node via soft timer. // Strong override of weak default in zephyr_ble_poll.c. +static volatile bool run_task_in_progress; + void mp_bluetooth_zephyr_port_run_task(mp_sched_node_t *node) { (void)node; + // Guard against re-entrancy. flush_recv_notify calls invoke_irq_handler + // which runs Python code via mp_call_function_2. Between bytecodes of + // the Python IRQ handler, mp_handle_pending() can re-schedule and run + // this function if the soft timer fires during the handler. + if (run_task_in_progress) { + return; + } + // Early exit if BLE is not active (recv_cb is set by hci_cyw43_open) // This prevents processing stale Zephyr state after soft reset. // Also skip during deinit to prevent CYW43 SPI reads on a post-reset controller. @@ -324,6 +331,8 @@ void mp_bluetooth_zephyr_port_run_task(mp_sched_node_t *node) { return; } + run_task_in_progress = true; + // Process Zephyr BLE work queues and semaphores mp_bluetooth_zephyr_poll(); @@ -352,12 +361,16 @@ void mp_bluetooth_zephyr_port_run_task(mp_sched_node_t *node) { mp_bluetooth_zephyr_work_process(); } - // Flush deferred L2CAP recv notifications after all HCI processing is done. - // seg_recv_cb defers the Python IRQ notification to avoid re-entrancy issues; - // it must be flushed here after work_process completes. + // Flush deferred L2CAP recv notification now that all HCI data is + // processed. Keep run_task_in_progress=true during the flush so that + // the Python IRQ handler (invoked via on_l2cap_recv → mp_call_function_2) + // cannot trigger a nested port_run_task via mp_handle_pending() between + // bytecodes. Missed HCI data will be picked up in the next timer cycle. extern void mp_bluetooth_zephyr_l2cap_flush_recv_notify(void); mp_bluetooth_zephyr_l2cap_flush_recv_notify(); + run_task_in_progress = false; + // Reschedule soft timer for continuous HCI polling. mp_bluetooth_zephyr_port_poll_in_ms(ZEPHYR_BLE_POLL_INTERVAL_MS); } @@ -365,13 +378,13 @@ void mp_bluetooth_zephyr_port_run_task(mp_sched_node_t *node) { // Zephyr HCI driver implementation // Forward declarations for poll_uart stats (defined later in file) -extern uint32_t poll_uart_count; -extern uint32_t poll_uart_hci_reads; -extern uint32_t poll_uart_cyw43_calls; -extern uint32_t poll_uart_skipped_recursion; -extern uint32_t poll_uart_skipped_no_cb; -extern uint32_t hci_tx_count; -extern uint32_t hci_tx_cmd_count; +extern volatile uint32_t poll_uart_count; +extern volatile uint32_t poll_uart_hci_reads; +extern volatile uint32_t poll_uart_cyw43_calls; +extern volatile uint32_t poll_uart_skipped_recursion; +extern volatile uint32_t poll_uart_skipped_no_cb; +extern volatile uint32_t hci_tx_count; +extern volatile uint32_t hci_tx_cmd_count; static int hci_cyw43_open(const struct device *dev, bt_hci_recv_t recv) { debug_printf("hci_cyw43_open called, dev=%p recv=%p\n", dev, recv); @@ -441,8 +454,8 @@ static int hci_cyw43_send(const struct device *dev, struct net_buf *buf) { debug_printf("hci_cyw43_send: type=%u len=%u data[0]=0x%02x\n", buf_type, buf->len, buf->len > 0 ? buf->data[0] : 0xFF); // Debug: increment TX counter - extern uint32_t hci_tx_count; - extern uint32_t hci_tx_cmd_count; + extern volatile uint32_t hci_tx_count; + extern volatile uint32_t hci_tx_cmd_count; hci_tx_count++; // Map Zephyr buffer type to H:4 packet type @@ -462,10 +475,9 @@ static int hci_cyw43_send(const struct device *dev, struct net_buf *buf) { } // CYW43 requires 4-byte header: [0,0,0,pkt_type] + packet_data - // Stack-allocate to avoid GC heap pressure on every HCI TX. - // Max size: CYW43_HCI_HEADER_SIZE(4) + CONFIG_BT_BUF_ACL_TX_SIZE(255) = 259. + // Allocate temporary buffer for CYW43 packet format size_t cyw43_pkt_size = CYW43_HCI_HEADER_SIZE + buf->len; - uint8_t __attribute__((aligned(4))) cyw43_pkt[CYW43_HCI_HEADER_SIZE + CONFIG_BT_BUF_ACL_TX_SIZE]; + uint8_t *cyw43_pkt = m_new(uint8_t, cyw43_pkt_size); // Build CYW43 packet: 4-byte header + HCI data memset(cyw43_pkt, 0, CYW43_HCI_HEADER_SIZE); @@ -476,6 +488,7 @@ static int hci_cyw43_send(const struct device *dev, struct net_buf *buf) { extern int cyw43_bluetooth_hci_write(uint8_t *buf, size_t len); int ret = cyw43_bluetooth_hci_write(cyw43_pkt, cyw43_pkt_size); + m_del(uint8_t, cyw43_pkt, cyw43_pkt_size); net_buf_unref(buf); if (ret != 0) { @@ -539,11 +552,11 @@ int bt_hci_transport_teardown(const struct device *dev) { // This reads any pending HCI data from the CYW43 chip and passes it to Zephyr static volatile bool poll_uart_in_progress = false; -// Debug counters for poll_uart calls (non-static for k_panic debug output) -uint32_t poll_uart_count = 0; -uint32_t poll_uart_hci_reads = 0; -uint32_t hci_tx_count = 0; -uint32_t hci_tx_cmd_count = 0; +// Debug counter for poll_uart calls (non-static for k_panic debug output) +volatile uint32_t poll_uart_count = 0; +volatile uint32_t poll_uart_hci_reads = 0; // Successful HCI reads +volatile uint32_t hci_tx_count = 0; // HCI commands/ACL data sent +volatile uint32_t hci_tx_cmd_count = 0; // HCI commands only uint32_t mp_bluetooth_zephyr_poll_uart_count(void) { return poll_uart_count; @@ -554,10 +567,10 @@ uint32_t mp_bluetooth_zephyr_poll_uart_hci_reads(void) { } // Debug: track poll_uart entry reasons -uint32_t poll_uart_skipped_recursion = 0; -uint32_t poll_uart_skipped_no_cb = 0; -uint32_t poll_uart_skipped_task = 0; -uint32_t poll_uart_cyw43_calls = 0; +volatile uint32_t poll_uart_skipped_recursion = 0; +volatile uint32_t poll_uart_skipped_no_cb = 0; +volatile uint32_t poll_uart_skipped_task = 0; +volatile uint32_t poll_uart_cyw43_calls = 0; // Deinitialize Zephyr port - called during ble.active(False) void mp_bluetooth_zephyr_port_deinit(void) { From f123bddbe3981610634f90618d7ff1584313a976 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sun, 19 Apr 2026 23:53:36 +1000 Subject: [PATCH 4/7] rp2: Add L2CAP poll_now flush to port_run_task. Signed-off-by: Andrew Leech --- ports/rp2/mpzephyrport_rp2.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/ports/rp2/mpzephyrport_rp2.c b/ports/rp2/mpzephyrport_rp2.c index e9e41a49aad98..455d44efa4e86 100644 --- a/ports/rp2/mpzephyrport_rp2.c +++ b/ports/rp2/mpzephyrport_rp2.c @@ -334,17 +334,17 @@ void mp_bluetooth_zephyr_port_run_task(mp_sched_node_t *node) { run_task_in_progress = true; // Process Zephyr BLE work queues and semaphores - mp_bluetooth_zephyr_poll(); + bool did_work = mp_bluetooth_zephyr_poll(); // Read directly from CYW43 via shared SPI bus. // Read and process HCI packets one at a time with work between each. extern int cyw43_bluetooth_hci_read(uint8_t *buf, uint32_t max_size, uint32_t *len); - extern void mp_bluetooth_zephyr_work_process(void); + extern bool mp_bluetooth_zephyr_work_process(void); while (1) { // Check buffer availability before reading HCI data. if (!mp_bluetooth_zephyr_buffers_available()) { - mp_bluetooth_zephyr_work_process(); + did_work |= mp_bluetooth_zephyr_work_process(); if (!mp_bluetooth_zephyr_buffers_available()) { // Still no buffers — stop reading, retry next poll. break; @@ -358,7 +358,7 @@ void mp_bluetooth_zephyr_port_run_task(mp_sched_node_t *node) { } process_hci_rx_packet(hci_rx_buffer, len); - mp_bluetooth_zephyr_work_process(); + did_work |= mp_bluetooth_zephyr_work_process(); } // Flush deferred L2CAP recv notification now that all HCI data is @@ -371,8 +371,12 @@ void mp_bluetooth_zephyr_port_run_task(mp_sched_node_t *node) { run_task_in_progress = false; - // Reschedule soft timer for continuous HCI polling. - mp_bluetooth_zephyr_port_poll_in_ms(ZEPHYR_BLE_POLL_INTERVAL_MS); + // Adaptive reschedule: immediate when work was processed, idle interval otherwise. + if (did_work) { + mp_bluetooth_zephyr_port_poll_now(); + } else { + mp_bluetooth_zephyr_port_poll_in_ms(ZEPHYR_BLE_POLL_INTERVAL_MS); + } } // Zephyr HCI driver implementation From f0dc5291395408b4411a6d376c004b54fc73b4ba Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 4 Mar 2026 10:56:31 +1100 Subject: [PATCH 5/7] zephyr: Replace BLE bindings with extmod/zephyr_ble. Replace the native Zephyr port's custom BLE bindings with the shared extmod/zephyr_ble integration layer. This unifies the BLE API across all ports using Zephyr BLE, with the native Zephyr port using Zephyr's own kernel primitives instead of the HAL shim stubs. Includes machine.idle() fix to yield to Zephyr threads, and test fixes for nRF52840 DK BLE multitests. Signed-off-by: Andrew Leech --- extmod/zephyr_ble/hal/zephyr_ble_timer.h | 13 +- ports/zephyr/CMakeLists.txt | 40 +- ports/zephyr/boards/nrf52840dk_nrf52840.conf | 51 +- .../boards/nrf52840dk_nrf52840.overlay.usb | 12 + ports/zephyr/boards/nucleo_wb55rg.conf | 3 + .../boards/xiao_ble_nrf52840_sense.conf | 3 + ports/zephyr/modbluetooth_zephyr.c | 997 ------------------ ports/zephyr/modmachine.c | 2 +- ports/zephyr/mpconfigport.h | 51 +- ports/zephyr/mpzephyrport_ble.c | 168 +++ 10 files changed, 294 insertions(+), 1046 deletions(-) create mode 100644 ports/zephyr/boards/nrf52840dk_nrf52840.overlay.usb delete mode 100644 ports/zephyr/modbluetooth_zephyr.c create mode 100644 ports/zephyr/mpzephyrport_ble.c diff --git a/extmod/zephyr_ble/hal/zephyr_ble_timer.h b/extmod/zephyr_ble/hal/zephyr_ble_timer.h index de5952ccd8b1c..7ec71353119c0 100644 --- a/extmod/zephyr_ble/hal/zephyr_ble_timer.h +++ b/extmod/zephyr_ble/hal/zephyr_ble_timer.h @@ -31,6 +31,11 @@ #include #include +#ifdef __ZEPHYR__ +// Native Zephyr: use real kernel types +#include +#else + // Zephyr k_timer abstraction layer for MicroPython // Uses polling-based timer mechanism (cooperative scheduling) @@ -61,12 +66,14 @@ void k_timer_stop(struct k_timer *timer); // Note: K_MSEC, K_NO_WAIT, K_FOREVER are defined in zephyr_ble_work.h // Note: k_yield() is defined in zephyr_ble_kernel.h -// Called by MicroPython scheduler to process timer callbacks -void mp_bluetooth_zephyr_timer_process(void); - // Check if two timeouts are equal static inline bool K_TIMEOUT_EQ(k_timeout_t a, k_timeout_t b) { return a.ticks == b.ticks; } +#endif // __ZEPHYR__ + +// Called by MicroPython scheduler to process timer callbacks +void mp_bluetooth_zephyr_timer_process(void); + #endif // MICROPY_INCLUDED_EXTMOD_ZEPHYR_BLE_HAL_ZEPHYR_BLE_TIMER_H diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt index 82142de2e084b..f7249f94918a6 100644 --- a/ports/zephyr/CMakeLists.txt +++ b/ports/zephyr/CMakeLists.txt @@ -37,25 +37,6 @@ include(${MICROPY_DIR}/extmod/extmod.cmake) list(APPEND DTS_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/dts) -# Define mpy-cross flags, for use with frozen code. -if(CONFIG_RISCV_ISA_RV32I AND CONFIG_RISCV_ISA_EXT_M AND CONFIG_RISCV_ISA_EXT_C) - set(ARCH_FLAGS "") - set(RV32_EXT "") - if(CONFIG_RISCV_ISA_EXT_ZBA) - list(APPEND RV32_EXT "zba") - endif() - if(CONFIG_RISCV_ISA_EXT_ZCMP) - list(APPEND RV32_EXT "zcmp") - endif() - if(RV32_EXT) - list(JOIN RV32_EXT "," RV32_EXT) - set(ARCH_FLAGS "-march-flags=${RV32_EXT}") - endif() - set(MICROPY_CROSS_FLAGS "-march=rv32imc ${ARCH_FLAGS}") -elseif(CONFIG_RISCV_ISA_RV64I AND CONFIG_RISCV_ISA_EXT_M AND CONFIG_RISCV_ISA_EXT_C) - set(MICROPY_CROSS_FLAGS -march=rv64imc) -endif() - if (CONFIG_MICROPY_FROZEN_MODULES) cmake_path(ABSOLUTE_PATH CONFIG_MICROPY_FROZEN_MANIFEST BASE_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) set(MICROPY_FROZEN_MANIFEST ${CONFIG_MICROPY_FROZEN_MANIFEST}) @@ -68,7 +49,7 @@ set(MICROPY_SOURCE_PORT machine_spi.c machine_pin.c machine_timer.c - modbluetooth_zephyr.c + mpzephyrport_ble.c modsocket.c modzephyr.c modzsensor.c @@ -173,6 +154,25 @@ zephyr_library_compile_definitions( zephyr_library_sources(${MICROPY_SOURCE_QSTR}) zephyr_library_link_libraries(kernel) +if(CONFIG_BT) + set(MICROPY_SOURCE_ZEPHYR_BLE + ${MICROPY_DIR}/extmod/zephyr_ble/modbluetooth_zephyr.c + ) + zephyr_library_sources(${MICROPY_SOURCE_ZEPHYR_BLE}) + list(APPEND MICROPY_SOURCE_QSTR ${MICROPY_SOURCE_ZEPHYR_BLE}) + zephyr_library_include_directories( + ${MICROPY_DIR}/extmod/zephyr_ble/hal + ${ZEPHYR_BASE}/subsys/bluetooth + ) + zephyr_library_compile_definitions( + MICROPY_BLUETOOTH_ZEPHYR=1 + MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS=1 + MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS_WITH_INTERLOCK=1 + MICROPY_PY_BLUETOOTH_SYNC_EVENT_STACK_SIZE=4096 + ZEPHYR_BLE_DEBUG=0 + ) +endif() + include(${MICROPY_DIR}/py/mkrules.cmake) add_dependencies(BUILD_VERSION_HEADER zephyr_generated_headers) diff --git a/ports/zephyr/boards/nrf52840dk_nrf52840.conf b/ports/zephyr/boards/nrf52840dk_nrf52840.conf index dc2ed1c4b49df..72cd68a65b859 100644 --- a/ports/zephyr/boards/nrf52840dk_nrf52840.conf +++ b/ports/zephyr/boards/nrf52840dk_nrf52840.conf @@ -1,18 +1,65 @@ CONFIG_NETWORKING=n + +# USB CDC ACM console +CONFIG_USB_DEVICE_STACK_NEXT=y +CONFIG_USBD_CDC_ACM_CLASS=y + CONFIG_BT=y CONFIG_BT_DEVICE_NAME_DYNAMIC=y CONFIG_BT_GATT_DYNAMIC_DB=y CONFIG_BT_PERIPHERAL=y CONFIG_BT_CENTRAL=y CONFIG_BT_GATT_CLIENT=y -CONFIG_BT_L2CAP_TX_MTU=252 +CONFIG_BT_L2CAP_TX_MTU=512 CONFIG_BT_BUF_ACL_RX_SIZE=256 CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION=n +# L2CAP CoC throughput: enable DLE and increase buffer counts +CONFIG_BT_CTLR_DATA_LENGTH_MAX=251 +CONFIG_BT_BUF_ACL_TX_COUNT=10 +CONFIG_BT_L2CAP_TX_BUF_COUNT=10 +CONFIG_BT_CONN_TX_MAX=10 +CONFIG_BT_CTLR_RX_BUFFERS=3 +CONFIG_BT_BUF_EVT_RX_COUNT=20 +CONFIG_BT_SMP=y +CONFIG_BT_SMP_SC_PAIR_ONLY=n +CONFIG_BT_SMP_ENFORCE_MITM=n +CONFIG_BT_SMP_MIN_ENC_KEY_SIZE=7 +CONFIG_BT_MAX_PAIRED=8 +CONFIG_BT_BONDABLE=y +CONFIG_BT_KEYS_OVERWRITE_OLDEST=y + +# Settings subsystem for bond persistence (required for bonded=true in pairing_complete) +CONFIG_SETTINGS=y +CONFIG_BT_SETTINGS=y +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_NVS=y +CONFIG_BT_L2CAP_DYNAMIC_CHANNEL=y +CONFIG_BT_RX_STACK_SIZE=8192 + +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 + CONFIG_MICROPY_HEAP_SIZE=98304 -CONFIG_MAIN_STACK_SIZE=8192 +CONFIG_MAIN_STACK_SIZE=16384 # CONFIG_DYNAMIC_THREAD=y CONFIG_THREAD_CUSTOM_DATA=y CONFIG_THREAD_MONITOR=y CONFIG_THREAD_STACK_INFO=y + +# ECDH config — use default p256-m driver (needed for SMP Secure Connections) +# Full mbedTLS ECP causes DHKey check failure on nRF52840 native controller +CONFIG_BT_LONG_WQ_STACK_SIZE=4096 + +# SMP/pairing debug via RTT +CONFIG_LOG=y +CONFIG_LOG_BACKEND_RTT=y +CONFIG_LOG_BACKEND_UART=n +CONFIG_USE_SEGGER_RTT=y +CONFIG_LOG_BUFFER_SIZE=8192 +CONFIG_SEGGER_RTT_BUFFER_SIZE_UP=8192 +CONFIG_BT_LOG_LEVEL_WRN=y +CONFIG_BT_SMP_LOG_LEVEL_DBG=y +CONFIG_BT_CONN_LOG_LEVEL_INF=y +CONFIG_BT_L2CAP_LOG_LEVEL_INF=y diff --git a/ports/zephyr/boards/nrf52840dk_nrf52840.overlay.usb b/ports/zephyr/boards/nrf52840dk_nrf52840.overlay.usb new file mode 100644 index 0000000000000..944f6fdca771a --- /dev/null +++ b/ports/zephyr/boards/nrf52840dk_nrf52840.overlay.usb @@ -0,0 +1,12 @@ +/ { + chosen { + /* Use USB CDC ACM as the console. */ + zephyr,console = &cdc_acm_uart0; + }; +}; + +&zephyr_udc0 { + cdc_acm_uart0: cdc_acm_uart0 { + compatible = "zephyr,cdc-acm-uart"; + }; +}; diff --git a/ports/zephyr/boards/nucleo_wb55rg.conf b/ports/zephyr/boards/nucleo_wb55rg.conf index 1c9c2f794cb05..e75fa5fb2d116 100644 --- a/ports/zephyr/boards/nucleo_wb55rg.conf +++ b/ports/zephyr/boards/nucleo_wb55rg.conf @@ -17,6 +17,9 @@ CONFIG_BT_GATT_CLIENT=y CONFIG_BT_L2CAP_TX_MTU=252 CONFIG_BT_BUF_ACL_RX_SIZE=256 CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION=n +CONFIG_BT_SMP=y +CONFIG_BT_BONDABLE=y +CONFIG_BT_L2CAP_DYNAMIC_CHANNEL=y # MicroPython config CONFIG_MICROPY_HEAP_SIZE=131072 diff --git a/ports/zephyr/boards/xiao_ble_nrf52840_sense.conf b/ports/zephyr/boards/xiao_ble_nrf52840_sense.conf index 541ade2af246a..ce2c49e91717d 100644 --- a/ports/zephyr/boards/xiao_ble_nrf52840_sense.conf +++ b/ports/zephyr/boards/xiao_ble_nrf52840_sense.conf @@ -20,6 +20,9 @@ CONFIG_BT_GATT_CLIENT=y CONFIG_BT_L2CAP_TX_MTU=252 CONFIG_BT_BUF_ACL_RX_SIZE=256 CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION=n +CONFIG_BT_SMP=y +CONFIG_BT_BONDABLE=y +CONFIG_BT_L2CAP_DYNAMIC_CHANNEL=y CONFIG_MICROPY_HEAP_SIZE=98304 CONFIG_MAIN_STACK_SIZE=8192 diff --git a/ports/zephyr/modbluetooth_zephyr.c b/ports/zephyr/modbluetooth_zephyr.c deleted file mode 100644 index c1b8ddfd8b404..0000000000000 --- a/ports/zephyr/modbluetooth_zephyr.c +++ /dev/null @@ -1,997 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2019-2021 Damien P. George - * Copyright (c) 2019-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 "py/runtime.h" -#include "py/mperrno.h" -#include "py/mphal.h" - -#if MICROPY_PY_BLUETOOTH - -#include -#include -#include -#include -#include -#include -#include "extmod/modbluetooth.h" - -#define DEBUG_printf(...) // printk("BLE: " __VA_ARGS__) - -#define BLE_HCI_SCAN_ITVL_MIN 0x10 -#define BLE_HCI_SCAN_ITVL_MAX 0xffff -#define BLE_HCI_SCAN_WINDOW_MIN 0x10 -#define BLE_HCI_SCAN_WINDOW_MAX 0xffff - -#define ERRNO_BLUETOOTH_NOT_ACTIVE MP_ENODEV - -#define MP_BLUETOOTH_ZEPHYR_MAX_SERVICES (8) - -/* This masks Permission bits from GATT API */ -#define GATT_PERM_MASK (BT_GATT_PERM_READ | \ - BT_GATT_PERM_READ_AUTHEN | \ - BT_GATT_PERM_READ_ENCRYPT | \ - BT_GATT_PERM_WRITE | \ - BT_GATT_PERM_WRITE_AUTHEN | \ - BT_GATT_PERM_WRITE_ENCRYPT | \ - BT_GATT_PERM_PREPARE_WRITE) - -#define GATT_PERM_ENC_READ_MASK (BT_GATT_PERM_READ_ENCRYPT | \ - BT_GATT_PERM_READ_AUTHEN) - -#define GATT_PERM_ENC_WRITE_MASK (BT_GATT_PERM_WRITE_ENCRYPT | \ - BT_GATT_PERM_WRITE_AUTHEN) - -enum { - MP_BLUETOOTH_ZEPHYR_BLE_STATE_OFF, - MP_BLUETOOTH_ZEPHYR_BLE_STATE_ACTIVE, - MP_BLUETOOTH_ZEPHYR_BLE_STATE_SUSPENDED, -}; - -enum { - MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_INACTIVE, - MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_DEACTIVATING, - MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_ACTIVE, -}; - -union uuid_u { - struct bt_uuid uuid; - struct bt_uuid_16 u16; - struct bt_uuid_32 u32; - struct bt_uuid_128 u128; -}; - -struct add_characteristic { - uint8_t properties; - uint8_t permissions; - const struct bt_uuid *uuid; -}; - -struct add_descriptor { - uint8_t permissions; - const struct bt_uuid *uuid; -}; - -typedef struct _mp_bt_zephyr_conn_t { - struct bt_conn *conn; - struct _mp_bt_zephyr_conn_t *next; -} mp_bt_zephyr_conn_t; - -typedef struct _mp_bluetooth_zephyr_root_pointers_t { - // list of objects to be tracked by the gc - mp_obj_t objs_list; - - // Characteristic (and descriptor) value storage. - mp_gatts_db_t gatts_db; - - // Service definitions. - size_t n_services; - struct bt_gatt_service *services[MP_BLUETOOTH_ZEPHYR_MAX_SERVICES]; - - // active connections - mp_bt_zephyr_conn_t *connections; -} mp_bluetooth_zephyr_root_pointers_t; - -static int mp_bluetooth_zephyr_ble_state; - -#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE -static int mp_bluetooth_zephyr_gap_scan_state; -static struct k_timer mp_bluetooth_zephyr_gap_scan_timer; -static struct bt_le_scan_cb mp_bluetooth_zephyr_gap_scan_cb_struct; -#endif - -static struct bt_data bt_ad_data[8]; -static size_t bt_ad_len = 0; -static struct bt_data bt_sd_data[8]; -static size_t bt_sd_len = 0; - -static mp_bt_zephyr_conn_t *mp_bt_zephyr_next_conn; - -static mp_bt_zephyr_conn_t *mp_bt_zephyr_find_connection(uint8_t conn_handle); -static void mp_bt_zephyr_insert_connection(mp_bt_zephyr_conn_t *connection); -static void mp_bt_zephyr_remove_connection(uint8_t conn_handle); -static void mp_bt_zephyr_connected(struct bt_conn *connected, uint8_t err); -static void mp_bt_zephyr_disconnected(struct bt_conn *disconn, uint8_t reason); -static struct bt_uuid *create_zephyr_uuid(const mp_obj_bluetooth_uuid_t *uuid); -static void gatt_db_add(const struct bt_gatt_attr *pattern, struct bt_gatt_attr *attr, size_t user_data_len); -static void add_service(const struct bt_uuid *u, struct bt_gatt_attr *attr); -static void add_characteristic(struct add_characteristic *ch, struct bt_gatt_attr *attr_chrc, struct bt_gatt_attr *attr_value); -static void add_ccc(struct bt_gatt_attr *attr, struct bt_gatt_attr *attr_desc); -static void add_cep(const struct bt_gatt_attr *attr_chrc, struct bt_gatt_attr *attr_desc); -static void add_descriptor(struct bt_gatt_attr *chrc, struct add_descriptor *d, struct bt_gatt_attr *attr_desc); -static void mp_bt_zephyr_gatt_indicate_done(struct bt_conn *conn, struct bt_gatt_indicate_params *params, uint8_t err); -static struct bt_gatt_attr *mp_bt_zephyr_find_attr_by_handle(uint16_t value_handle); - -static struct bt_conn_cb mp_bt_zephyr_conn_callbacks = { - .connected = mp_bt_zephyr_connected, - .disconnected = mp_bt_zephyr_disconnected, -}; - -static mp_bt_zephyr_conn_t *mp_bt_zephyr_find_connection(uint8_t conn_handle) { - struct bt_conn_info info; - for (mp_bt_zephyr_conn_t *connection = MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections; connection != NULL; connection = connection->next) { - if (connection->conn) { - bt_conn_get_info(connection->conn, &info); - if (info.id == conn_handle) { - return connection; - } - } - } - return NULL; -} - -static void mp_bt_zephyr_insert_connection(mp_bt_zephyr_conn_t *connection) { - connection->next = MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections; - MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections = connection; -} - -static void mp_bt_zephyr_remove_connection(uint8_t conn_handle) { - struct bt_conn_info info; - mp_bt_zephyr_conn_t *prev = NULL; - for (mp_bt_zephyr_conn_t *connection = MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections; connection != NULL; connection = connection->next) { - if (connection->conn) { - bt_conn_get_info(connection->conn, &info); - if (info.id == conn_handle) { - // unlink this item and the gc will eventually collect it - if (prev != NULL) { - prev->next = connection->next; - } else { - // move the start pointer - MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections = connection->next; - } - break; - } else { - prev = connection; - } - } - } -} - -static void mp_bt_zephyr_connected(struct bt_conn *conn, uint8_t err) { - struct bt_conn_info info; - bt_conn_get_info(conn, &info); - - if (err) { - uint8_t addr[6] = {0}; - DEBUG_printf("Connection from central failed (err %u)\n", err); - mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT, info.id, 0xff, addr); - } else { - DEBUG_printf("Central connected with id %d\n", info.id); - mp_bt_zephyr_next_conn->conn = bt_conn_ref(conn); - mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_CENTRAL_CONNECT, info.id, info.le.dst->type, info.le.dst->a.val); - mp_bt_zephyr_insert_connection(mp_bt_zephyr_next_conn); - } -} - -static void mp_bt_zephyr_disconnected(struct bt_conn *conn, uint8_t reason) { - struct bt_conn_info info; - bt_conn_get_info(conn, &info); - DEBUG_printf("Central disconnected (id %d reason %u)\n", info.id, reason); - bt_conn_unref(conn); - mp_bt_zephyr_remove_connection(info.id); - mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT, info.id, info.le.dst->type, info.le.dst->a.val); -} - -static int bt_err_to_errno(int err) { - // Zephyr uses errno codes directly, but they are negative. - return -err; -} - -// modbluetooth (and the layers above it) work in BE for addresses, Zephyr works in LE. -static void reverse_addr_byte_order(uint8_t *addr_out, const bt_addr_le_t *addr_in) { - for (int i = 0; i < 6; ++i) { - addr_out[i] = addr_in->a.val[5 - i]; - } -} - -#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE -void gap_scan_cb_recv(const struct bt_le_scan_recv_info *info, struct net_buf_simple *buf) { - DEBUG_printf("gap_scan_cb_recv: adv_type=%d\n", info->adv_type); - - if (!mp_bluetooth_is_active()) { - return; - } - - if (mp_bluetooth_zephyr_gap_scan_state != MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_ACTIVE) { - return; - } - - uint8_t addr[6]; - reverse_addr_byte_order(addr, info->addr); - mp_bluetooth_gap_on_scan_result(info->addr->type, addr, info->adv_type, info->rssi, buf->data, buf->len); -} - -static mp_obj_t gap_scan_stop(mp_obj_t unused) { - (void)unused; - mp_bluetooth_gap_scan_stop(); - return mp_const_none; -} -static MP_DEFINE_CONST_FUN_OBJ_1(gap_scan_stop_obj, gap_scan_stop); - -void gap_scan_cb_timeout(struct k_timer *timer_id) { - DEBUG_printf("gap_scan_cb_timeout\n"); - // Cannot call bt_le_scan_stop from a timer callback because this callback may be - // preempting the BT stack. So schedule it to be called from the main thread. - while (!mp_sched_schedule(MP_OBJ_FROM_PTR(&gap_scan_stop_obj), mp_const_none)) { - k_yield(); - } - // Indicate scanning has stopped so that no more scan result events are generated - // (they may still come in until bt_le_scan_stop is called by gap_scan_stop). - mp_bluetooth_zephyr_gap_scan_state = MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_DEACTIVATING; -} -#endif - -int mp_bluetooth_init(void) { - DEBUG_printf("mp_bluetooth_init\n"); - - // Clean up if necessary. - mp_bluetooth_deinit(); - - // Allocate memory for state. - MP_STATE_PORT(bluetooth_zephyr_root_pointers) = m_new0(mp_bluetooth_zephyr_root_pointers_t, 1); - mp_bluetooth_gatts_db_create(&MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db); - - MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections = NULL; - mp_bt_zephyr_next_conn = NULL; - - MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list = mp_obj_new_list(0, NULL); - - #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE - mp_bluetooth_zephyr_gap_scan_state = MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_INACTIVE; - k_timer_init(&mp_bluetooth_zephyr_gap_scan_timer, gap_scan_cb_timeout, NULL); - mp_bluetooth_zephyr_gap_scan_cb_struct.recv = gap_scan_cb_recv; - mp_bluetooth_zephyr_gap_scan_cb_struct.timeout = NULL; // currently not implemented in Zephyr - bt_le_scan_cb_register(&mp_bluetooth_zephyr_gap_scan_cb_struct); - #endif - - if (mp_bluetooth_zephyr_ble_state == MP_BLUETOOTH_ZEPHYR_BLE_STATE_OFF) { - - bt_conn_cb_register(&mp_bt_zephyr_conn_callbacks); - - // bt_enable can only be called once. - int ret = bt_enable(NULL); - if (ret) { - return bt_err_to_errno(ret); - } - } - - mp_bluetooth_zephyr_ble_state = MP_BLUETOOTH_ZEPHYR_BLE_STATE_ACTIVE; - - DEBUG_printf("mp_bluetooth_init: ready\n"); - - return 0; -} - -int mp_bluetooth_deinit(void) { - DEBUG_printf("mp_bluetooth_deinit %d\n", mp_bluetooth_zephyr_ble_state); - if (mp_bluetooth_zephyr_ble_state == MP_BLUETOOTH_ZEPHYR_BLE_STATE_OFF - || mp_bluetooth_zephyr_ble_state == MP_BLUETOOTH_ZEPHYR_BLE_STATE_SUSPENDED) { - return 0; - } - - mp_bluetooth_gap_advertise_stop(); - - #if CONFIG_BT_GATT_DYNAMIC_DB - for (size_t i = 0; i < MP_STATE_PORT(bluetooth_zephyr_root_pointers)->n_services; ++i) { - bt_gatt_service_unregister(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i]); - MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i] = NULL; - } - #endif - - #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE - mp_bluetooth_gap_scan_stop(); - bt_le_scan_cb_unregister(&mp_bluetooth_zephyr_gap_scan_cb_struct); - #endif - - // There is no way to turn off the BT stack in Zephyr, so just set the - // state as suspended so it can be correctly reactivated later. - mp_bluetooth_zephyr_ble_state = MP_BLUETOOTH_ZEPHYR_BLE_STATE_SUSPENDED; - - MP_STATE_PORT(bluetooth_zephyr_root_pointers) = NULL; - mp_bt_zephyr_next_conn = NULL; - return 0; -} - -bool mp_bluetooth_is_active(void) { - return mp_bluetooth_zephyr_ble_state == MP_BLUETOOTH_ZEPHYR_BLE_STATE_ACTIVE; -} - -void mp_bluetooth_get_current_address(uint8_t *addr_type, uint8_t *addr) { - if (!mp_bluetooth_is_active()) { - mp_raise_OSError(ERRNO_BLUETOOTH_NOT_ACTIVE); - } - bt_addr_le_t le_addr; - size_t count = 1; - bt_id_get(&le_addr, &count); - if (count == 0) { - mp_raise_OSError(EIO); - } - reverse_addr_byte_order(addr, &le_addr); - *addr_type = le_addr.type; -} - -void mp_bluetooth_set_address_mode(uint8_t addr_mode) { - mp_raise_OSError(MP_EOPNOTSUPP); -} - -size_t mp_bluetooth_gap_get_device_name(const uint8_t **buf) { - const char *name = bt_get_name(); - *buf = (const uint8_t *)name; - return strlen(name); -} - -int mp_bluetooth_gap_set_device_name(const uint8_t *buf, size_t len) { - char tmp_buf[CONFIG_BT_DEVICE_NAME_MAX + 1]; - if (len + 1 > sizeof(tmp_buf)) { - return MP_EINVAL; - } - memcpy(tmp_buf, buf, len); - tmp_buf[len] = '\0'; - return bt_err_to_errno(bt_set_name(tmp_buf)); -} - -// Zephyr takes advertising/scan data as an array of (type, len, payload) packets, -// and this function constructs such an array from raw advertising/scan data. -static void mp_bluetooth_prepare_bt_data(const uint8_t *data, size_t len, struct bt_data *bt_data, size_t *bt_len) { - size_t i = 0; - const uint8_t *d = data; - while (d < data + len && i < *bt_len) { - bt_data[i].type = d[1]; - bt_data[i].data_len = d[0] - 1; - bt_data[i].data = &d[2]; - i += 1; - d += 1 + d[0]; - } - *bt_len = i; -} - -int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, const uint8_t *adv_data, size_t adv_data_len, const uint8_t *sr_data, size_t sr_data_len) { - if (!mp_bluetooth_is_active()) { - return ERRNO_BLUETOOTH_NOT_ACTIVE; - } - - mp_bluetooth_gap_advertise_stop(); - - if (adv_data) { - bt_ad_len = MP_ARRAY_SIZE(bt_ad_data); - mp_bluetooth_prepare_bt_data(adv_data, adv_data_len, bt_ad_data, &bt_ad_len); - } - - if (sr_data) { - bt_sd_len = MP_ARRAY_SIZE(bt_sd_data); - mp_bluetooth_prepare_bt_data(sr_data, sr_data_len, bt_sd_data, &bt_sd_len); - } - - struct bt_le_adv_param param = { - .id = 0, - .sid = 0, - .secondary_max_skip = 0, - .options = (connectable ? BT_LE_ADV_OPT_CONNECTABLE : 0) - | BT_LE_ADV_OPT_ONE_TIME - | BT_LE_ADV_OPT_USE_IDENTITY - | BT_LE_ADV_OPT_SCANNABLE, - .interval_min = interval_us / 625, - .interval_max = interval_us / 625 + 1, // min/max cannot be the same value - .peer = NULL, - }; - - // pre-allocate a new connection structure as we cannot allocate this inside the connection callback - mp_bt_zephyr_next_conn = m_new0(mp_bt_zephyr_conn_t, 1); - mp_obj_list_append(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list, MP_OBJ_FROM_PTR(mp_bt_zephyr_next_conn)); - - return bt_err_to_errno(bt_le_adv_start(¶m, bt_ad_data, bt_ad_len, bt_sd_data, bt_sd_len)); -} - -void mp_bluetooth_gap_advertise_stop(void) { - // Note: bt_le_adv_stop returns 0 if adv is already stopped. - int ret = bt_le_adv_stop(); - if (ret != 0) { - mp_raise_OSError(bt_err_to_errno(ret)); - } -} - -int mp_bluetooth_gatts_register_service_begin(bool append) { - #if CONFIG_BT_GATT_DYNAMIC_DB - - if (!mp_bluetooth_is_active()) { - return ERRNO_BLUETOOTH_NOT_ACTIVE; - } - - if (append) { - // Don't support append yet (modbluetooth.c doesn't support it yet anyway). - return MP_EOPNOTSUPP; - } - - // Unregister and unref any previous service definitions. - for (size_t i = 0; i < MP_STATE_PORT(bluetooth_zephyr_root_pointers)->n_services; ++i) { - bt_gatt_service_unregister(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i]); - MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i] = NULL; - } - MP_STATE_PORT(bluetooth_zephyr_root_pointers)->n_services = 0; - - // Reset the gatt characteristic value db. - mp_bluetooth_gatts_db_reset(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db); - MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections = NULL; - MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list = mp_obj_new_list(0, NULL); - mp_bt_zephyr_next_conn = NULL; - - return 0; - - #else - return MP_EOPNOTSUPP; - #endif -} - -int mp_bluetooth_gatts_register_service_end(void) { - return 0; -} - -int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, mp_obj_bluetooth_uuid_t **characteristic_uuids, uint16_t *characteristic_flags, mp_obj_bluetooth_uuid_t **descriptor_uuids, uint16_t *descriptor_flags, uint8_t *num_descriptors, uint16_t *handles, size_t num_characteristics) { - #if CONFIG_BT_GATT_DYNAMIC_DB - if (MP_STATE_PORT(bluetooth_zephyr_root_pointers)->n_services >= MP_BLUETOOTH_ZEPHYR_MAX_SERVICES) { - return MP_E2BIG; - } - - // first of all allocate the entire memory for all the attributes that this service is composed of - // 1 for the service itself, 2 for each characteristic (the declaration and the value), and one for each descriptor - size_t total_descriptors = 0; - for (size_t i = 0; i < num_characteristics; ++i) { - total_descriptors += num_descriptors[i]; - // we have to add the CCC manually - if (characteristic_flags[i] & (MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY | MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE)) { - total_descriptors += 1; - } - } - size_t total_attributes = 1 + (num_characteristics * 2) + total_descriptors; - - // allocate one extra so that we can know later where the final attribute is - struct bt_gatt_attr *svc_attributes = m_new(struct bt_gatt_attr, total_attributes + 1); - mp_obj_list_append(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list, MP_OBJ_FROM_PTR(svc_attributes)); - - size_t handle_index = 0; - size_t descriptor_index = 0; - size_t attr_index = 0; - // bitfield of the handles we should ignore, should be more than enough for most applications - uint64_t attrs_to_ignore = 0; - uint64_t attrs_are_chrs = 0; - uint64_t chr_has_ccc = 0; - - add_service(create_zephyr_uuid(service_uuid), &svc_attributes[attr_index]); - attr_index += 1; - - for (size_t i = 0; i < num_characteristics; ++i) { - - struct add_characteristic add_char; - add_char.uuid = create_zephyr_uuid(characteristic_uuids[i]); - add_char.permissions = 0; - add_char.properties = 0; - if (characteristic_flags[i] & MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ) { - add_char.permissions |= BT_GATT_PERM_READ; - add_char.properties |= BT_GATT_CHRC_READ; - } - if (characteristic_flags[i] & MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY) { - add_char.properties |= BT_GATT_CHRC_NOTIFY; - } - if (characteristic_flags[i] & MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE) { - add_char.properties |= BT_GATT_CHRC_INDICATE; - } - if (characteristic_flags[i] & (MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE | MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_NO_RESPONSE)) { - add_char.permissions |= BT_GATT_PERM_WRITE; - add_char.properties |= (BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP); - } - - add_characteristic(&add_char, &svc_attributes[attr_index], &svc_attributes[attr_index + 1]); - - struct bt_gatt_attr *curr_char = &svc_attributes[attr_index]; - attrs_are_chrs |= (1 << attr_index); - if (characteristic_flags[i] & (MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY | MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE)) { - chr_has_ccc |= (1 << attr_index); - } - attr_index += 1; - attrs_to_ignore |= (1 << attr_index); // ignore the value handle - attr_index += 1; - - if (num_descriptors[i] > 0) { - for (size_t j = 0; j < num_descriptors[i]; ++j) { - - struct add_descriptor add_desc; - add_desc.uuid = create_zephyr_uuid(descriptor_uuids[descriptor_index]); - add_desc.permissions = 0; - if (descriptor_flags[descriptor_index] & MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ) { - add_desc.permissions |= BT_GATT_PERM_READ; - } - if (descriptor_flags[descriptor_index] & (MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE | MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_NO_RESPONSE)) { - add_desc.permissions |= BT_GATT_PERM_WRITE; - } - - add_descriptor(curr_char, &add_desc, &svc_attributes[attr_index]); - attr_index += 1; - - descriptor_index++; - } - } - - // to support indications and notifications we must add the CCC descriptor manually - if (characteristic_flags[i] & (MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY | MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE)) { - struct add_descriptor add_desc; - mp_obj_bluetooth_uuid_t ccc_uuid; - ccc_uuid.base.type = &mp_type_bluetooth_uuid; - ccc_uuid.data[0] = BT_UUID_GATT_CCC_VAL & 0xff; - ccc_uuid.data[1] = (BT_UUID_GATT_CCC_VAL >> 8) & 0xff; - ccc_uuid.type = MP_BLUETOOTH_UUID_TYPE_16; - add_desc.uuid = create_zephyr_uuid(&ccc_uuid); - add_desc.permissions = BT_GATT_PERM_READ | BT_GATT_PERM_WRITE; - - attrs_to_ignore |= (1 << attr_index); - - add_descriptor(curr_char, &add_desc, &svc_attributes[attr_index]); - attr_index += 1; - } - } - - struct bt_gatt_service *service = m_new(struct bt_gatt_service, 1); - mp_obj_list_append(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list, MP_OBJ_FROM_PTR(service)); - service->attrs = svc_attributes; - service->attr_count = attr_index; - // invalidate the last attribute uuid pointer so that we new this is the end of attributes for this service - svc_attributes[attr_index].uuid = NULL; - - // Note: advertising must be stopped for gatts registration to work - - int err = bt_gatt_service_register(service); - if (err) { - return bt_err_to_errno(err); - } - - // now that the service has been registered, we can assign the handles for the characteristics and the descriptors - // we are not interested in the handle of the service itself, so we start the loop from index 1 - for (int i = 1; i < total_attributes; i++) { - // store all the relevant handles (characteristics and descriptors defined in Python) - if (!((uint64_t)(attrs_to_ignore >> i) & (uint64_t)0x01)) { - if (svc_attributes[i].user_data == NULL) { - mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, svc_attributes[i].handle, MP_BLUETOOTH_DEFAULT_ATTR_LEN); - mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, svc_attributes[i].handle); - svc_attributes[i].user_data = entry->data; - } else if (((uint64_t)(attrs_are_chrs >> i) & (uint64_t)0x01)) { - if (svc_attributes[i + 1].user_data == NULL) { - mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, svc_attributes[i].handle, MP_BLUETOOTH_DEFAULT_ATTR_LEN); - mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, svc_attributes[i].handle); - svc_attributes[i + 1].user_data = entry->data; - - if (((uint64_t)(chr_has_ccc >> i) & (uint64_t)0x01)) { - // create another database entry for the ccc of this characteristic - mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, svc_attributes[i].handle + 2, 1); - } - } - } - handles[handle_index++] = svc_attributes[i].handle; - } - } - - MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[MP_STATE_PORT(bluetooth_zephyr_root_pointers)->n_services++] = service; - - return 0; - - #else - return MP_EOPNOTSUPP; - #endif -} - -int mp_bluetooth_gap_disconnect(uint16_t conn_handle) { - if (!mp_bluetooth_is_active()) { - return ERRNO_BLUETOOTH_NOT_ACTIVE; - } - mp_bt_zephyr_conn_t *connection = mp_bt_zephyr_find_connection(conn_handle); - if (connection) { - return bt_conn_disconnect(connection->conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); - } - return MP_ENOENT; -} - -int mp_bluetooth_gatts_read(uint16_t value_handle, const uint8_t **value, size_t *value_len) { - if (!mp_bluetooth_is_active()) { - return ERRNO_BLUETOOTH_NOT_ACTIVE; - } - return mp_bluetooth_gatts_db_read(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, value_handle, value, value_len); -} - -int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t value_len, bool send_update) { - if (!mp_bluetooth_is_active()) { - return ERRNO_BLUETOOTH_NOT_ACTIVE; - } - - int err = mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, value_handle, value, value_len); - - if ((err == 0) && send_update) { - struct bt_gatt_attr *attr_val = mp_bt_zephyr_find_attr_by_handle(value_handle + 1); - mp_bluetooth_gatts_db_entry_t *ccc_entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, value_handle + 2); - - if (ccc_entry && (ccc_entry->data[0] == BT_GATT_CCC_NOTIFY)) { - err = bt_gatt_notify(NULL, attr_val, value, value_len); - } else if (ccc_entry && (ccc_entry->data[0] == BT_GATT_CCC_INDICATE)) { - struct bt_gatt_indicate_params params = { - .uuid = NULL, - .attr = attr_val, - .func = mp_bt_zephyr_gatt_indicate_done, - .destroy = NULL, - .data = value, - .len = value_len - }; - err = bt_gatt_indicate(NULL, ¶ms); - } - } - return err; -} - -static void mp_bt_zephyr_gatt_indicate_done(struct bt_conn *conn, struct bt_gatt_indicate_params *params, uint8_t err) { - struct bt_conn_info info; - bt_conn_get_info(conn, &info); - uint16_t chr_handle = params->attr->handle - 1; - mp_bluetooth_gatts_on_indicate_complete(info.id, chr_handle, err); -} - -static ssize_t mp_bt_zephyr_gatts_attr_read(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { - // we receive the value handle, but to look up in the gatts db we need the characteristic handle, and that is is the value handle minus 1 - uint16_t _handle = attr->handle - 1; - - DEBUG_printf("BLE attr read for handle %d\n", attr->handle); - - mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, _handle); - if (!entry) { - // it could be a descriptor instead - _handle = attr->handle; - entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, _handle); - if (!entry) { - return BT_GATT_ERR(BT_ATT_ERR_INVALID_HANDLE); - } - } - - return bt_gatt_attr_read(conn, attr, buf, len, offset, entry->data, entry->data_len); -} - -static ssize_t mp_bt_zephyr_gatts_attr_write(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { - struct bt_conn_info info; - bt_conn_get_info(conn, &info); - - DEBUG_printf("BLE attr write for handle %d\n", attr->handle); - - // the characteristic handle is the value handle minus 1 - uint16_t _handle = attr->handle - 1; - - mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, _handle); - if (!entry) { - // it could be a descriptor instead - _handle = attr->handle; - entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, _handle); - if (!entry) { - return BT_GATT_ERR(BT_ATT_ERR_INVALID_HANDLE); - } - } - - // Don't write anything if prepare flag is set - if (flags & BT_GATT_WRITE_FLAG_PREPARE) { - return 0; - } - - if (offset > entry->data_alloc) { - return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); - } - - if ((offset + len) > entry->data_alloc) { - return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); - } - - if (entry->append) { - offset = entry->data_len; - } - - // copy the data into the buffer in the gatts database - memcpy(&entry->data[offset], buf, len); - entry->data_len = offset + len; - - mp_bluetooth_gatts_on_write(info.id, _handle); - - return len; -} - -static struct bt_gatt_attr *mp_bt_zephyr_find_attr_by_handle(uint16_t value_handle) { - for (int i = 0; i < MP_STATE_PORT(bluetooth_zephyr_root_pointers)->n_services; i++) { - int j = 0; - while (MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i]->attrs[j].uuid != NULL) { - if (MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i]->attrs[j].handle == value_handle) { - return &MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i]->attrs[j]; - } - j++; - } - } - return NULL; -} - -int mp_bluetooth_gatts_notify_indicate(uint16_t conn_handle, uint16_t value_handle, int gatts_op, const uint8_t *value, size_t value_len) { - if (!mp_bluetooth_is_active()) { - return ERRNO_BLUETOOTH_NOT_ACTIVE; - } - - int err = MP_ENOENT; - mp_bt_zephyr_conn_t *connection = mp_bt_zephyr_find_connection(conn_handle); - - if (connection) { - struct bt_gatt_attr *attr_val = mp_bt_zephyr_find_attr_by_handle(value_handle + 1); - - if (attr_val) { - switch (gatts_op) { - case MP_BLUETOOTH_GATTS_OP_NOTIFY: { - err = bt_gatt_notify(connection->conn, attr_val, value, value_len); - break; - } - case MP_BLUETOOTH_GATTS_OP_INDICATE: { - struct bt_gatt_indicate_params params = { - .uuid = NULL, - .attr = attr_val, - .func = mp_bt_zephyr_gatt_indicate_done, - .destroy = NULL, - .data = value, - .len = value_len - }; - err = bt_gatt_indicate(connection->conn, ¶ms); - break; - } - } - } - } - - return err; -} - -int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append) { - if (!mp_bluetooth_is_active()) { - return ERRNO_BLUETOOTH_NOT_ACTIVE; - } - return mp_bluetooth_gatts_db_resize(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, value_handle, len, append); -} - -int mp_bluetooth_get_preferred_mtu(void) { - if (!mp_bluetooth_is_active()) { - mp_raise_OSError(ERRNO_BLUETOOTH_NOT_ACTIVE); - } - mp_raise_OSError(MP_EOPNOTSUPP); -} - -int mp_bluetooth_set_preferred_mtu(uint16_t mtu) { - if (!mp_bluetooth_is_active()) { - return ERRNO_BLUETOOTH_NOT_ACTIVE; - } - return MP_EOPNOTSUPP; -} - -#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE - -int mp_bluetooth_gap_scan_start(int32_t duration_ms, int32_t interval_us, int32_t window_us, bool active_scan) { - // Stop any ongoing GAP scan. - int ret = mp_bluetooth_gap_scan_stop(); - if (ret) { - return ret; - } - - struct bt_le_scan_param param = { - .type = active_scan ? BT_HCI_LE_SCAN_ACTIVE : BT_HCI_LE_SCAN_PASSIVE, - .options = BT_LE_SCAN_OPT_NONE, - .interval = MAX(BLE_HCI_SCAN_ITVL_MIN, MIN(BLE_HCI_SCAN_ITVL_MAX, interval_us / 625)), - .window = MAX(BLE_HCI_SCAN_WINDOW_MIN, MIN(BLE_HCI_SCAN_WINDOW_MAX, window_us / 625)), - }; - k_timer_start(&mp_bluetooth_zephyr_gap_scan_timer, K_MSEC(duration_ms), K_NO_WAIT); - mp_bluetooth_zephyr_gap_scan_state = MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_ACTIVE; - int err = bt_le_scan_start(¶m, NULL); - return bt_err_to_errno(err); -} - -int mp_bluetooth_gap_scan_stop(void) { - DEBUG_printf("mp_bluetooth_gap_scan_stop\n"); - if (!mp_bluetooth_is_active()) { - return ERRNO_BLUETOOTH_NOT_ACTIVE; - } - if (mp_bluetooth_zephyr_gap_scan_state == MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_INACTIVE) { - // Already stopped. - return 0; - } - mp_bluetooth_zephyr_gap_scan_state = MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_INACTIVE; - k_timer_stop(&mp_bluetooth_zephyr_gap_scan_timer); - int err = bt_le_scan_stop(); - if (err == 0) { - mp_bluetooth_gap_on_scan_complete(); - return 0; - } - return bt_err_to_errno(err); -} - -int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, int32_t duration_ms, int32_t min_conn_interval_us, int32_t max_conn_interval_us) { - DEBUG_printf("mp_bluetooth_gap_peripheral_connect\n"); - if (!mp_bluetooth_is_active()) { - return ERRNO_BLUETOOTH_NOT_ACTIVE; - } - return MP_EOPNOTSUPP; -} - -int mp_bluetooth_gap_peripheral_connect_cancel(void) { - DEBUG_printf("mp_bluetooth_gap_peripheral_connect_cancel\n"); - if (!mp_bluetooth_is_active()) { - return ERRNO_BLUETOOTH_NOT_ACTIVE; - } - return MP_EOPNOTSUPP; -} - -#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE - -// Note: modbluetooth UUIDs store their data in LE. -static struct bt_uuid *create_zephyr_uuid(const mp_obj_bluetooth_uuid_t *uuid) { - struct bt_uuid *result = (struct bt_uuid *)m_new(union uuid_u, 1); - mp_obj_list_append(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list, MP_OBJ_FROM_PTR(result)); - if (uuid->type == MP_BLUETOOTH_UUID_TYPE_16) { - bt_uuid_create(result, uuid->data, 2); - } else if (uuid->type == MP_BLUETOOTH_UUID_TYPE_32) { - bt_uuid_create(result, uuid->data, 4); - } else { // MP_BLUETOOTH_UUID_TYPE_128 - bt_uuid_create(result, uuid->data, 16); - } - return result; -} - -static void gatt_db_add(const struct bt_gatt_attr *pattern, struct bt_gatt_attr *attr, size_t user_data_len) { - const union uuid_u *u = CONTAINER_OF(pattern->uuid, union uuid_u, uuid); - size_t uuid_size = sizeof(u->u16); - - if (u->uuid.type == BT_UUID_TYPE_32) { - uuid_size = sizeof(u->u32); - } else if (u->uuid.type == BT_UUID_TYPE_128) { - uuid_size = sizeof(u->u128); - } - - memcpy(attr, pattern, sizeof(*attr)); - - // Store the UUID. - attr->uuid = (const struct bt_uuid *)m_new(union uuid_u, 1); - mp_obj_list_append(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list, MP_OBJ_FROM_PTR(attr->uuid)); - memcpy((void *)attr->uuid, &u->uuid, uuid_size); - - // Copy user_data to the buffer. - if (user_data_len) { - attr->user_data = m_new(uint8_t, user_data_len); - mp_obj_list_append(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list, MP_OBJ_FROM_PTR(attr->user_data)); - memcpy(attr->user_data, pattern->user_data, user_data_len); - } -} - -static void add_service(const struct bt_uuid *u, struct bt_gatt_attr *attr) { - union uuid_u *uuid = (union uuid_u *)u; - - size_t uuid_size = sizeof(uuid->u16); - - if (uuid->uuid.type == BT_UUID_TYPE_32) { - uuid_size = sizeof(uuid->u32); - } else if (uuid->uuid.type == BT_UUID_TYPE_128) { - uuid_size = sizeof(uuid->u128); - } - - gatt_db_add(&(struct bt_gatt_attr)BT_GATT_PRIMARY_SERVICE(&uuid->uuid), attr, uuid_size); -} - -static void add_characteristic(struct add_characteristic *ch, struct bt_gatt_attr *attr_chrc, struct bt_gatt_attr *attr_value) { - struct bt_gatt_chrc *chrc_data; - - // Add Characteristic Declaration - gatt_db_add(&(struct bt_gatt_attr) - BT_GATT_ATTRIBUTE(BT_UUID_GATT_CHRC, - BT_GATT_PERM_READ, - bt_gatt_attr_read_chrc, NULL, - (&(struct bt_gatt_chrc) {})), attr_chrc, sizeof(*chrc_data)); - - // Allow prepare writes - ch->permissions |= BT_GATT_PERM_PREPARE_WRITE; - - // Add Characteristic Value - gatt_db_add(&(struct bt_gatt_attr) - BT_GATT_ATTRIBUTE(ch->uuid, - ch->permissions & GATT_PERM_MASK, - mp_bt_zephyr_gatts_attr_read, mp_bt_zephyr_gatts_attr_write, NULL), attr_value, 0); - - chrc_data = attr_chrc->user_data; - chrc_data->properties = ch->properties; - chrc_data->uuid = attr_value->uuid; -} - -static void ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { - mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, attr->handle); - entry->data[0] = value; -} - -static struct bt_gatt_attr ccc_definition = BT_GATT_CCC(ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE); - -static void add_ccc(struct bt_gatt_attr *attr, struct bt_gatt_attr *attr_desc) { - struct bt_gatt_chrc *chrc = attr->user_data; - - // Check characteristic properties - if (!(chrc->properties & (BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE))) { - mp_raise_OSError(MP_EINVAL); - } - - // Add CCC descriptor to GATT database - gatt_db_add(&ccc_definition, attr_desc, 0); -} - -static void add_cep(const struct bt_gatt_attr *attr_chrc, struct bt_gatt_attr *attr_desc) { - struct bt_gatt_chrc *chrc = attr_chrc->user_data; - struct bt_gatt_cep cep_value; - - // Extended Properties bit shall be set - if (!(chrc->properties & BT_GATT_CHRC_EXT_PROP)) { - mp_raise_OSError(MP_EINVAL); - } - - cep_value.properties = 0x0000; - - // Add CEP descriptor to GATT database - gatt_db_add(&(struct bt_gatt_attr)BT_GATT_CEP(&cep_value), attr_desc, sizeof(cep_value)); -} - -static void add_descriptor(struct bt_gatt_attr *chrc, struct add_descriptor *d, struct bt_gatt_attr *attr_desc) { - if (!bt_uuid_cmp(d->uuid, BT_UUID_GATT_CEP)) { - add_cep(chrc, attr_desc); - } else if (!bt_uuid_cmp(d->uuid, BT_UUID_GATT_CCC)) { - add_ccc(chrc, attr_desc); - } else { - // Allow prepare writes - d->permissions |= BT_GATT_PERM_PREPARE_WRITE; - - gatt_db_add(&(struct bt_gatt_attr) - BT_GATT_DESCRIPTOR(d->uuid, - d->permissions & GATT_PERM_MASK, - mp_bt_zephyr_gatts_attr_read, mp_bt_zephyr_gatts_attr_write, NULL), attr_desc, 0); - } -} - -MP_REGISTER_ROOT_POINTER(struct _mp_bluetooth_zephyr_root_pointers_t *bluetooth_zephyr_root_pointers); - -#endif // MICROPY_PY_BLUETOOTH diff --git a/ports/zephyr/modmachine.c b/ports/zephyr/modmachine.c index 89ad12f185daa..b53ab7229b89a 100644 --- a/ports/zephyr/modmachine.c +++ b/ports/zephyr/modmachine.c @@ -60,5 +60,5 @@ static mp_obj_t machine_reset_cause(void) { MP_DEFINE_CONST_FUN_OBJ_0(machine_reset_cause_obj, machine_reset_cause); static void mp_machine_idle(void) { - k_yield(); + k_msleep(1); } diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index f52b42fc2f01d..63bb8312b933c 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -36,6 +36,12 @@ #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_BASIC_FEATURES) #endif +// Enable random module (not included at BASIC_FEATURES level). +// Zephyr provides entropy via hardware RNG (device tree) or +// CONFIG_TEST_RANDOM_GENERATOR fallback (prj.conf). +#define MICROPY_PY_RANDOM (1) +#define MICROPY_PY_RANDOM_EXTRA_FUNCS (1) + // Usually passed from Makefile #ifndef MICROPY_HEAP_SIZE #define MICROPY_HEAP_SIZE (16 * 1024) @@ -91,12 +97,25 @@ #define MICROPY_PY_SOCKET (1) #endif #ifdef CONFIG_BT -#define MICROPY_PY_BLUETOOTH (1) +#define MICROPY_PY_BLUETOOTH (1) +#ifndef MICROPY_BLUETOOTH_ZEPHYR +#define MICROPY_BLUETOOTH_ZEPHYR (1) +#endif #ifdef CONFIG_BT_CENTRAL #define MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE (1) #endif +#ifdef CONFIG_BT_GATT_CLIENT +#define MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT (1) +#else #define MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT (0) #endif +#ifdef CONFIG_BT_L2CAP_DYNAMIC_CHANNEL +#define MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS (1) +#endif +#ifdef CONFIG_BT_SMP +#define MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING (1) +#endif +#endif #define MICROPY_PY_BINASCII (1) #define MICROPY_PY_HASHLIB (1) #define MICROPY_PY_OS (1) @@ -113,26 +132,6 @@ #define MICROPY_VFS (1) #define MICROPY_READER_VFS (MICROPY_VFS) -#if defined(CONFIG_RISCV_ISA_RV32I) && defined(CONFIG_RISCV_ISA_EXT_M) && defined(CONFIG_RISCV_ISA_EXT_C) - -#ifndef MICROPY_EMIT_RV32 -#define MICROPY_EMIT_RV32 (1) -#endif - -#ifndef MICROPY_EMIT_INLINE_RV32 -#define MICROPY_EMIT_INLINE_RV32 (1) -#endif - -#ifdef CONFIG_RISCV_ISA_EXT_ZBA -#define MICROPY_EMIT_RV32_ZBA (1) -#endif - -#ifdef CONFIG_RISCV_ISA_EXT_ZCMP -#define MICROPY_EMIT_RV32_ZCMP (1) -#endif - -#endif // CONFIG_RISCV_ISA_RV32I - // fatfs configuration used in ffconf.h #define MICROPY_FATFS_ENABLE_LFN (1) #define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ @@ -181,10 +180,15 @@ typedef long mp_off_t; #define MP_SSIZE_MAX (0x7fffffff) +// extra built in names to add to the global namespace +#define MICROPY_PORT_BUILTINS \ + { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_builtin_open_obj) }, + #if MICROPY_PY_THREAD #define MICROPY_EVENT_POLL_HOOK \ do { \ - mp_handle_pending(MP_HANDLE_PENDING_CALLBACKS_AND_EXCEPTIONS); \ + extern void mp_handle_pending(bool); \ + mp_handle_pending(true); \ MP_THREAD_GIL_EXIT(); \ k_msleep(1); \ MP_THREAD_GIL_ENTER(); \ @@ -192,7 +196,8 @@ typedef long mp_off_t; #else #define MICROPY_EVENT_POLL_HOOK \ do { \ - mp_handle_pending(MP_HANDLE_PENDING_CALLBACKS_AND_EXCEPTIONS); \ + extern void mp_handle_pending(bool); \ + mp_handle_pending(true); \ k_msleep(1); \ } while (0); #endif diff --git a/ports/zephyr/mpzephyrport_ble.c b/ports/zephyr/mpzephyrport_ble.c new file mode 100644 index 0000000000000..3b701016effad --- /dev/null +++ b/ports/zephyr/mpzephyrport_ble.c @@ -0,0 +1,168 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 MicroPython Contributors + * + * 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. + */ + +// No-op implementations for HAL/port functions called by modbluetooth_zephyr.c. +// On native Zephyr, the real kernel handles work queues, timers, HCI transport, +// and net_buf pools. + +#include "py/mpconfig.h" + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_ZEPHYR + +#include +#include "extmod/zephyr_ble/hal/zephyr_ble_work.h" +#include "extmod/zephyr_ble/hal/zephyr_ble_port.h" +#include "extmod/zephyr_ble/hal/zephyr_ble_poll.h" + +// --- Globals referenced by zephyr_ble_work.h --- +volatile bool mp_bluetooth_zephyr_in_wait_loop = false; +volatile int mp_bluetooth_zephyr_hci_processing_depth = 0; + +// --- Work queue (Zephyr's own work thread handles processing) --- +void mp_bluetooth_zephyr_work_process(void) { +} + +void mp_bluetooth_zephyr_work_process_init(void) { +} + +void mp_bluetooth_zephyr_init_phase_enter(void) { +} + +void mp_bluetooth_zephyr_init_phase_exit(void) { +} + +bool mp_bluetooth_zephyr_in_init_phase(void) { + return false; +} + +bool mp_bluetooth_zephyr_init_work_pending(void) { + return false; +} + +struct k_work *mp_bluetooth_zephyr_init_work_get(void) { + return NULL; +} + +void mp_bluetooth_zephyr_work_thread_start(void) { +} + +void mp_bluetooth_zephyr_work_thread_stop(void) { +} + +bool mp_bluetooth_zephyr_work_drain(void) { + return false; +} + +void mp_bluetooth_zephyr_work_reset(void) { +} + +void mp_bluetooth_zephyr_work_debug_stats(void) { +} + +// --- Port hooks --- +void mp_bluetooth_zephyr_port_init(void) { +} + +void mp_bluetooth_zephyr_port_deinit(void) { +} + +void mp_bluetooth_zephyr_port_poll_in_ms(uint32_t ms) { + (void)ms; +} + +// --- HCI (Zephyr handles HCI transport internally) --- +void mp_bluetooth_hci_poll(void) { +} + +void mp_bluetooth_hci_poll_now(void) { +} + +void mp_bluetooth_zephyr_hci_uart_wfi(void) { +} + +void mp_bluetooth_zephyr_hci_uart_process(void) { +} + +// --- Polling subsystem (not needed, Zephyr is event-driven) --- +void mp_bluetooth_zephyr_poll(void) { +} + +void mp_bluetooth_zephyr_poll_init(void) { +} + +void mp_bluetooth_zephyr_poll_deinit(void) { +} + +bool mp_bluetooth_zephyr_buffers_available(void) { + return true; +} + +void mp_bluetooth_zephyr_poll_init_timer(void) { +} + +void mp_bluetooth_zephyr_poll_stop_timer(void) { +} + +void mp_bluetooth_zephyr_poll_cleanup(void) { +} + +// --- Net buf pool (Zephyr manages pools) --- +void mp_net_buf_pool_state_reset(void) { +} + +// --- GATT pool (not using bump allocator on native Zephyr) --- +void mp_bluetooth_zephyr_gatt_pool_reset(void) { +} + +// --- Timer processing (Zephyr kernel handles timers) --- +void mp_bluetooth_zephyr_timer_process(void) { +} + +// --- HCI RX task stubs (Zephyr handles HCI reception internally) --- +void mp_bluetooth_zephyr_hci_rx_task_start(void) { +} + +void mp_bluetooth_zephyr_hci_rx_task_stop(void) { +} + +bool mp_bluetooth_zephyr_hci_rx_task_active(void) { + return false; +} + +void mp_bluetooth_zephyr_hci_rx_task_debug(uint32_t *polls, uint32_t *packets) { + if (polls) { + *polls = 0; + } + if (packets) { + *packets = 0; + } +} + +uint32_t mp_bluetooth_zephyr_hci_rx_queue_dropped(void) { + return 0; +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_ZEPHYR From 9d4cd7899cc0dadbbaf50df08f3cfe0639c4f84b Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 4 Mar 2026 10:57:15 +1100 Subject: [PATCH 6/7] zephyr: Enable sync events and increase UART buffer for BLE. Enable synchronous BLE events and increase UART RX buffer to 512 bytes for reliable raw-paste operation on the nRF52840 DK. Signed-off-by: Andrew Leech --- ports/zephyr/src/zephyr_getchar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/zephyr/src/zephyr_getchar.c b/ports/zephyr/src/zephyr_getchar.c index bf504a97c9b5a..a88ead84e6119 100644 --- a/ports/zephyr/src/zephyr_getchar.c +++ b/ports/zephyr/src/zephyr_getchar.c @@ -24,7 +24,7 @@ extern int mp_interrupt_char; void mp_sched_keyboard_interrupt(void); void mp_hal_signal_event(void); -#define UART_BUFSIZE (512) +#define UART_BUFSIZE (4096) static uint8_t uart_ringbuf[UART_BUFSIZE]; static uint16_t i_get, i_put; From 45850ade5f059f04313f61c8ea51ac0253361efd Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Fri, 20 Feb 2026 06:50:54 +1100 Subject: [PATCH 7/7] zephyr: Switch nRF52840 DK to UART console, fix USB ordering. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nRF52840 DK's USB CDC ACM console was unreliable — device enumeration failures and stalls during raw-paste mode. Switch to UART via JLink OB (uart0 with hw-flow-control) which is always available. Move USB device stack init from mp_task (after console init) to zephyr_start.c main() (before console init) so CDC ACM UART is ready when the console subsystem opens the device. Add DTR wait for CDC ACM console boards so output isn't lost before a host connects. Reduce MICROPY_REPL_STDIN_BUFFER_MAX to 64 (raw-paste window=32 bytes) to avoid overflowing USB-UART bridge buffers at 115200 baud. Signed-off-by: Andrew Leech --- ports/zephyr/boards/nrf52840dk_nrf52840.conf | 7 +---- .../zephyr/boards/nrf52840dk_nrf52840.overlay | 7 +++++ ports/zephyr/main.c | 9 ++---- ports/zephyr/mpconfigport.h | 9 +++--- ports/zephyr/src/usbd.c | 2 +- ports/zephyr/src/zephyr_start.c | 30 +++++++++++++++++++ 6 files changed, 46 insertions(+), 18 deletions(-) create mode 100644 ports/zephyr/boards/nrf52840dk_nrf52840.overlay diff --git a/ports/zephyr/boards/nrf52840dk_nrf52840.conf b/ports/zephyr/boards/nrf52840dk_nrf52840.conf index 72cd68a65b859..8ce4e68870348 100644 --- a/ports/zephyr/boards/nrf52840dk_nrf52840.conf +++ b/ports/zephyr/boards/nrf52840dk_nrf52840.conf @@ -1,9 +1,5 @@ CONFIG_NETWORKING=n -# USB CDC ACM console -CONFIG_USB_DEVICE_STACK_NEXT=y -CONFIG_USBD_CDC_ACM_CLASS=y - CONFIG_BT=y CONFIG_BT_DEVICE_NAME_DYNAMIC=y CONFIG_BT_GATT_DYNAMIC_DB=y @@ -43,7 +39,6 @@ CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 CONFIG_MICROPY_HEAP_SIZE=98304 CONFIG_MAIN_STACK_SIZE=16384 -# CONFIG_DYNAMIC_THREAD=y CONFIG_THREAD_CUSTOM_DATA=y CONFIG_THREAD_MONITOR=y CONFIG_THREAD_STACK_INFO=y @@ -52,7 +47,7 @@ CONFIG_THREAD_STACK_INFO=y # Full mbedTLS ECP causes DHKey check failure on nRF52840 native controller CONFIG_BT_LONG_WQ_STACK_SIZE=4096 -# SMP/pairing debug via RTT +# RTT logging for debug (keeps log output off the UART console) CONFIG_LOG=y CONFIG_LOG_BACKEND_RTT=y CONFIG_LOG_BACKEND_UART=n diff --git a/ports/zephyr/boards/nrf52840dk_nrf52840.overlay b/ports/zephyr/boards/nrf52840dk_nrf52840.overlay new file mode 100644 index 0000000000000..388fcad7f67c4 --- /dev/null +++ b/ports/zephyr/boards/nrf52840dk_nrf52840.overlay @@ -0,0 +1,7 @@ +/* Enable UART hardware flow control (RTS/CTS) for reliable raw REPL. + * Pins already routed on nRF52840 DK: RTS=P0.5, CTS=P0.7. + * JLink OB UART bridge supports hardware flow control. + */ +&uart0 { + hw-flow-control; +}; diff --git a/ports/zephyr/main.c b/ports/zephyr/main.c index 8d399358849e7..67519518b8288 100644 --- a/ports/zephyr/main.c +++ b/ports/zephyr/main.c @@ -85,10 +85,6 @@ DT_FOREACH_STATUS_OKAY(micropython_heap, MICROPY_HEAP_DEFINE) static __noinit char heap[MICROPY_HEAP_SIZE]; -#if defined(CONFIG_USB_DEVICE_STACK_NEXT) -extern int mp_usbd_init(void); -#endif // defined(CONFIG_USB_DEVICE_STACK_NEXT) - void init_zephyr(void) { // We now rely on CONFIG_NET_APP_SETTINGS to set up bootstrap // network addresses. @@ -139,9 +135,8 @@ int real_main(void) { usb_enable(NULL); #endif - #ifdef CONFIG_USB_DEVICE_STACK_NEXT - mp_usbd_init(); - #endif + // Note: CONFIG_USB_DEVICE_STACK_NEXT init is in zephyr_start.c main() + // so it runs before the console is initialised (required for CDC ACM). #if MICROPY_VFS && MICROPY_MODULE_FROZEN_MPY // Mount and/or create the filesystem diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index 63bb8312b933c..3cb30a528a0ee 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -59,6 +59,9 @@ #define MICROPY_ENABLE_FINALISER (MICROPY_VFS) #define MICROPY_HELPER_REPL (1) #define MICROPY_REPL_AUTO_INDENT (1) +// Reduce raw-paste window to 32 bytes for UART-bridge connections (JLink OB). +// Default 256 (window=128) overflows USB-UART bridge buffers at 115200 baud. +#define MICROPY_REPL_STDIN_BUFFER_MAX (64) #define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) #define MICROPY_KBD_EXCEPTION (1) #define MICROPY_PY_BUILTINS_BYTES_HEX (1) @@ -187,8 +190,7 @@ typedef long mp_off_t; #if MICROPY_PY_THREAD #define MICROPY_EVENT_POLL_HOOK \ do { \ - extern void mp_handle_pending(bool); \ - mp_handle_pending(true); \ + mp_handle_pending(MP_HANDLE_PENDING_CALLBACKS_AND_EXCEPTIONS); \ MP_THREAD_GIL_EXIT(); \ k_msleep(1); \ MP_THREAD_GIL_ENTER(); \ @@ -196,8 +198,7 @@ typedef long mp_off_t; #else #define MICROPY_EVENT_POLL_HOOK \ do { \ - extern void mp_handle_pending(bool); \ - mp_handle_pending(true); \ + mp_handle_pending(MP_HANDLE_PENDING_CALLBACKS_AND_EXCEPTIONS); \ k_msleep(1); \ } while (0); #endif diff --git a/ports/zephyr/src/usbd.c b/ports/zephyr/src/usbd.c index 118463a0756a5..af3903752a38e 100644 --- a/ports/zephyr/src/usbd.c +++ b/ports/zephyr/src/usbd.c @@ -190,4 +190,4 @@ int mp_usbd_init(void) { return 0; } -#endif // defined(CONFIG_USB_DEVICE_STACK_NEXT) +#endif // defined(CONFIG_USB_DEVICE_STACK_NEXT) && !defined(CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT) diff --git a/ports/zephyr/src/zephyr_start.c b/ports/zephyr/src/zephyr_start.c index c37af8e5b3a87..066aaa9e75225 100644 --- a/ports/zephyr/src/zephyr_start.c +++ b/ports/zephyr/src/zephyr_start.c @@ -24,17 +24,47 @@ * THE SOFTWARE. */ #include +#include +#include #include "zephyr_getchar.h" int real_main(void); int mp_console_init(void); +#if defined(CONFIG_USB_DEVICE_STACK_NEXT) +extern int mp_usbd_init(void); +#endif + +// Check if the console device is a CDC ACM UART (USB serial). +#define MP_CONSOLE_IS_CDC_ACM DT_NODE_HAS_COMPAT(DT_CHOSEN(zephyr_console), zephyr_cdc_acm_uart) + +#if MP_CONSOLE_IS_CDC_ACM && defined(CONFIG_UART_LINE_CTRL) +static void mp_wait_for_usb_dtr(void) { + const struct device *dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_console)); + uint32_t dtr = 0; + + while (!dtr) { + uart_line_ctrl_get(dev, UART_LINE_CTRL_DTR, &dtr); + k_msleep(100); + } +} +#endif + int main(void) { + #if defined(CONFIG_USB_DEVICE_STACK_NEXT) + mp_usbd_init(); + #endif + + #if MP_CONSOLE_IS_CDC_ACM && defined(CONFIG_UART_LINE_CTRL) + mp_wait_for_usb_dtr(); + #endif + #ifdef CONFIG_CONSOLE_SUBSYS mp_console_init(); #else zephyr_getchar_init(); #endif + real_main(); return 0;