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/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 2caa5428f39ec..a4a2a53658c43 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,35 @@ 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. + 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} 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..455d44efa4e86 --- /dev/null +++ b/ports/rp2/mpzephyrport_rp2.c @@ -0,0 +1,714 @@ +/* + * 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 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 +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_cmd_complete++; + valid_event = true; + is_cmd_event = true; + } else if (evt_code == 0x0F) { + hci_rx_evt_cmd_status++; + valid_event = true; + is_cmd_event = true; + } else if (evt_code == 0x3E) { + 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_le_adv_report++; + } else if (subevent == 0x01) { + // LE Connection Complete + hci_rx_evt_le_conn_complete++; + } else if (subevent == 0x0A) { + // LE Enhanced Connection Complete + hci_rx_evt_le_enh_conn_complete++; + } + } + } else if (evt_code == 0x05) { + // 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) { + // Other valid events + 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 (CONFIG_BT_BUF_ACL_RX_SIZE = 27) + if (pkt_len > 27 + 4) { // 27 data + 4 header + hci_rx_rejected_acl++; + return; // Oversized + } + 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. +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. + if (recv_cb == NULL || mp_bluetooth_zephyr_shutting_down) { + return; + } + + run_task_in_progress = true; + + // Process Zephyr BLE work queues and semaphores + 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 bool mp_bluetooth_zephyr_work_process(void); + + while (1) { + // Check buffer availability before reading HCI data. + if (!mp_bluetooth_zephyr_buffers_available()) { + did_work |= 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); + did_work |= mp_bluetooth_zephyr_work_process(); + } + + // 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; + + // 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 + +// Forward declarations for poll_uart stats (defined later in file) +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); + 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 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 + 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 + // Allocate temporary buffer for CYW43 packet format + size_t cyw43_pkt_size = CYW43_HCI_HEADER_SIZE + buf->len; + 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); + 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); + + m_del(uint8_t, 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 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; +} + +uint32_t mp_bluetooth_zephyr_poll_uart_hci_reads(void) { + return poll_uart_hci_reads; +} + +// Debug: track poll_uart entry reasons +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) { + // 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 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..8ce4e68870348 100644 --- a/ports/zephyr/boards/nrf52840dk_nrf52840.conf +++ b/ports/zephyr/boards/nrf52840dk_nrf52840.conf @@ -1,18 +1,60 @@ CONFIG_NETWORKING=n + 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 + +# RTT logging for debug (keeps log output off the UART console) +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 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/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/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/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..3cb30a528a0ee 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) @@ -53,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) @@ -91,12 +100,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 +135,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,6 +183,10 @@ 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 { \ 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 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_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; 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;