From ed14435a8e6199b845c3404a9052f9ff4213292c Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 14 Aug 2020 15:43:09 +1000 Subject: [PATCH] extmod/modbluetooth: Refactor stack/hci/driver/port bindings. Previously the interaction between the different layers of the Bluetooth stack was different on each port and each stack. This commit defines common interfaces between them and implements them for cyw43, btstack, nimble, stm32, unix. --- drivers/cyw43/cywbt.c | 14 +- extmod/btstack/btstack.mk | 11 ++ extmod/btstack/btstack_config.h | 1 + .../btstack/btstack_hci_uart.c | 94 ++++---------- .../btstack_hci_uart.h} | 21 ++- extmod/btstack/modbluetooth_btstack.c | 5 + extmod/btstack/modbluetooth_btstack.h | 1 + extmod/mpbthci.c | 38 ++++++ extmod/{modbluetooth_hci.h => mpbthci.h} | 25 ++-- extmod/nimble/hal/hal_uart.c | 25 ++-- extmod/nimble/hal/hal_uart.h | 31 ++++- extmod/nimble/logcfg/logcfg.h | 45 +++++++ extmod/nimble/modbluetooth_nimble.c | 122 ++++++++++++++---- extmod/nimble/modbluetooth_nimble.h | 18 ++- extmod/nimble/nimble.mk | 13 +- .../nimble/{npl_os.c => nimble_npl_os.c} | 64 ++++++--- extmod/nimble/nimble/nimble_npl_os.h | 19 ++- extmod/nimble/syscfg/syscfg.h | 5 +- ports/esp32/Makefile | 2 +- ports/esp32/{nimble.c => mpnimbleport.c} | 30 ++++- ports/stm32/Makefile | 7 +- .../{modbluetooth_hci.c => mpbthciport.c} | 97 +++++++++----- ports/stm32/mpbtstackport.c | 107 +++++++++++++++ ports/stm32/mpbtstackport.h | 34 +++++ ports/stm32/mpconfigport.h | 21 ++- ports/stm32/{nimble.c => mpnimbleport.c} | 69 ++++------ ports/stm32/mpnimbleport.h | 34 +++++ ports/unix/Makefile | 9 +- ports/unix/mpbtstackport.h | 39 ++++++ ports/unix/mpbtstackport_common.c | 92 +++++++++++++ .../{btstack_usb.c => mpbtstackport_usb.c} | 101 ++------------- ports/unix/mpconfigport.h | 4 +- 32 files changed, 853 insertions(+), 345 deletions(-) rename ports/stm32/btstack.c => extmod/btstack/btstack_hci_uart.c (67%) rename extmod/{nimble/nimble/nimble_hci_uart.h => btstack/btstack_hci_uart.h} (66%) create mode 100644 extmod/mpbthci.c rename extmod/{modbluetooth_hci.h => mpbthci.h} (71%) create mode 100644 extmod/nimble/logcfg/logcfg.h rename extmod/nimble/nimble/{npl_os.c => nimble_npl_os.c} (85%) rename ports/esp32/{nimble.c => mpnimbleport.c} (66%) rename ports/stm32/{modbluetooth_hci.c => mpbthciport.c} (75%) create mode 100644 ports/stm32/mpbtstackport.c create mode 100644 ports/stm32/mpbtstackport.h rename ports/stm32/{nimble.c => mpnimbleport.c} (56%) create mode 100644 ports/stm32/mpnimbleport.h create mode 100644 ports/unix/mpbtstackport.h create mode 100644 ports/unix/mpbtstackport_common.c rename ports/unix/{btstack_usb.c => mpbtstackport_usb.c} (54%) diff --git a/drivers/cyw43/cywbt.c b/drivers/cyw43/cywbt.c index 79318f4483..defe670683 100644 --- a/drivers/cyw43/cywbt.c +++ b/drivers/cyw43/cywbt.c @@ -31,13 +31,19 @@ #include "py/mphal.h" #include "pin_static_af.h" #include "uart.h" -#include "extmod/modbluetooth_hci.h" +#include "extmod/mpbthci.h" #if MICROPY_PY_NETWORK_CYW43 extern const char fw_4343WA1_7_45_98_50_start; #define CYWBT_FW_ADDR (&fw_4343WA1_7_45_98_50_start + 749 * 512 + 29 * 256) +// Provided by the port. +extern pyb_uart_obj_t mp_bluetooth_hci_uart_obj; + +// Provided by the port, and also possibly shared with the stack. +extern uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; + /******************************************************************************/ // CYW BT HCI low-level driver @@ -168,10 +174,6 @@ int mp_bluetooth_hci_controller_init(void) { mp_hal_pin_config(pyb_pin_WL_GPIO_4, MP_HAL_PIN_MODE_OUTPUT, MP_HAL_PIN_PULL_NONE, 0); // RF-switch power mp_hal_pin_high(pyb_pin_WL_GPIO_4); // Turn the RF-switch on - return 0; -} - -int mp_bluetooth_hci_controller_activate(void) { uint8_t buf[256]; mp_hal_pin_low(pyb_pin_BT_REG_ON); @@ -219,7 +221,7 @@ int mp_bluetooth_hci_controller_activate(void) { return 0; } -int mp_bluetooth_hci_controller_deactivate(void) { +int mp_bluetooth_hci_controller_deinit(void) { mp_hal_pin_low(pyb_pin_BT_REG_ON); return 0; diff --git a/extmod/btstack/btstack.mk b/extmod/btstack/btstack.mk index dd96e63379..3084601b85 100644 --- a/extmod/btstack/btstack.mk +++ b/extmod/btstack/btstack.mk @@ -38,6 +38,17 @@ CFLAGS += $(shell pkg-config libusb-1.0 --cflags) LDFLAGS += $(shell pkg-config libusb-1.0 --libs) endif +ifeq ($(MICROPY_BLUETOOTH_BTSTACK_H4),1) +SRC_BTSTACK += \ + lib/btstack/src/hci_transport_h4.c \ + lib/btstack/chipset/zephyr/btstack_chipset_zephyr.c + +EXTMOD_SRC_C += \ + extmod/btstack/btstack_hci_uart.c \ + +CFLAGS_MOD += -DMICROPY_BLUETOOTH_BTSTACK_H4=1 +endif + ifeq ($(MICROPY_BLUETOOTH_BTSTACK_ENABLE_CLASSIC),1) include $(BTSTACK_DIR)/src/classic/Makefile.inc SRC_BTSTACK += \ diff --git a/extmod/btstack/btstack_config.h b/extmod/btstack/btstack_config.h index f420f47a5b..e56a84f94a 100644 --- a/extmod/btstack/btstack_config.h +++ b/extmod/btstack/btstack_config.h @@ -8,6 +8,7 @@ // #define ENABLE_CLASSIC #define ENABLE_LE_DATA_CHANNELS // #define ENABLE_LOG_INFO +// #define ENABLE_LOG_DEBUG #define ENABLE_LOG_ERROR // BTstack configuration. buffers, sizes, ... diff --git a/ports/stm32/btstack.c b/extmod/btstack/btstack_hci_uart.c similarity index 67% rename from ports/stm32/btstack.c rename to extmod/btstack/btstack_hci_uart.c index cbb15a86cb..5c96e02dc0 100644 --- a/ports/stm32/btstack.c +++ b/extmod/btstack/btstack_hci_uart.c @@ -4,6 +4,7 @@ * The MIT License (MIT) * * Copyright (c) 2020 Damien P. George + * Copyright (c) 2020 Jim Mussared * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -31,12 +32,14 @@ #if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK #include "lib/btstack/src/btstack.h" -#include "lib/btstack/platform/embedded/btstack_run_loop_embedded.h" -#include "lib/btstack/platform/embedded/hal_cpu.h" -#include "lib/btstack/platform/embedded/hal_time_ms.h" -#include "extmod/modbluetooth_hci.h" -#include "extmod/btstack/modbluetooth_btstack.h" +#include "extmod/mpbthci.h" +#include "extmod/btstack/btstack_hci_uart.h" + +#include "mpbtstackport.h" + +// Implements a btstack btstack_uart_block_t on top of the mphciuart.h +// interface to an HCI UART provided by the port. // We pass the bytes directly to the UART during a send, but then notify btstack in the next poll. STATIC bool send_done; @@ -48,42 +51,29 @@ STATIC size_t recv_len; STATIC size_t recv_idx; STATIC void (*recv_handler)(void); -// The IRQ functionality in btstack_run_loop_embedded.c is not used, so the -// following three functions are empty. - -void hal_cpu_disable_irqs(void) { -} - -void hal_cpu_enable_irqs(void) { -} - -void hal_cpu_enable_irqs_and_sleep(void) { -} - -uint32_t hal_time_ms(void) { - return mp_hal_ticks_ms(); -} - STATIC int btstack_uart_init(const btstack_uart_config_t *uart_config) { + (void)uart_config; + send_done = false; recv_len = 0; recv_idx = 0; recv_handler = NULL; send_handler = NULL; - // Set up the UART periperhal. - mp_bluetooth_hci_uart_init(MICROPY_HW_BLE_UART_ID); + // Set up the UART peripheral, attach IRQ and power up the HCI controller. + // We haven't been told the baud rate yet, so defer that until btstack_uart_set_baudrate. + mp_bluetooth_hci_uart_init(MICROPY_HW_BLE_UART_ID, 0); + mp_bluetooth_hci_controller_init(); return 0; } STATIC int btstack_uart_open(void) { - // Attach IRQ and power up the HCI controller. - mp_bluetooth_hci_uart_activate(); return 0; } STATIC int btstack_uart_close(void) { + mp_bluetooth_hci_controller_deinit(); return 0; } @@ -101,10 +91,12 @@ STATIC int btstack_uart_set_baudrate(uint32_t baudrate) { } STATIC int btstack_uart_set_parity(int parity) { + (void)parity; return 0; } STATIC int btstack_uart_set_flowcontrol(int flowcontrol) { + (void)flowcontrol; return 0; } @@ -123,14 +115,16 @@ STATIC int btstack_uart_get_supported_sleep_modes(void) { } STATIC void btstack_uart_set_sleep(btstack_uart_sleep_mode_t sleep_mode) { + (void)sleep_mode; // printf("btstack_uart_set_sleep %u\n", sleep_mode); } STATIC void btstack_uart_set_wakeup_handler(void (*wakeup_handler)(void)) { + (void)wakeup_handler; // printf("btstack_uart_set_wakeup_handler\n"); } -STATIC const btstack_uart_block_t btstack_uart_block = { +const btstack_uart_block_t mp_bluetooth_btstack_hci_uart_block = { &btstack_uart_init, &btstack_uart_open, &btstack_uart_close, @@ -146,15 +140,9 @@ STATIC const btstack_uart_block_t btstack_uart_block = { &btstack_uart_set_wakeup_handler, }; -STATIC const hci_transport_config_uart_t hci_transport_config_uart = { - HCI_TRANSPORT_CONFIG_UART, - MICROPY_HW_BLE_UART_BAUDRATE, - 3000000, - 0, - NULL, -}; +void mp_bluetooth_btstack_hci_uart_process(void) { + bool host_wake = mp_bluetooth_hci_controller_woken(); -STATIC void btstack_uart_process(void) { if (send_done) { // If we'd done a TX in the last interval, notify btstack that it's complete. send_done = false; @@ -176,48 +164,10 @@ STATIC void btstack_uart_process(void) { } } } -} -void mp_bluetooth_hci_poll(void) { - if (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_OFF) { - return; - } - - // Process uart data. - bool host_wake = mp_bluetooth_hci_controller_woken(); - btstack_uart_process(); if (host_wake) { mp_bluetooth_hci_controller_sleep_maybe(); } - - // Call the BTstack run loop. - btstack_run_loop_embedded_execute_once(); -} - -void mp_bluetooth_btstack_port_init(void) { - static bool run_loop_init = false; - if (!run_loop_init) { - run_loop_init = true; - btstack_run_loop_init(btstack_run_loop_embedded_get_instance()); - } else { - btstack_run_loop_embedded_get_instance()->init(); - } - - // hci_dump_open(NULL, HCI_DUMP_STDOUT); - const hci_transport_t *transport = hci_transport_h4_instance(&btstack_uart_block); - hci_init(transport, &hci_transport_config_uart); - - // TODO: Probably not necessary for BCM (we have our own firmware loader), - // but might be worth investigating for other controllers in the future. - // hci_set_chipset(btstack_chipset_bcm_instance()); -} - -void mp_bluetooth_btstack_port_deinit(void) { - hci_power_control(HCI_POWER_OFF); -} - -void mp_bluetooth_btstack_port_start(void) { - hci_power_control(HCI_POWER_ON); } #endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK diff --git a/extmod/nimble/nimble/nimble_hci_uart.h b/extmod/btstack/btstack_hci_uart.h similarity index 66% rename from extmod/nimble/nimble/nimble_hci_uart.h rename to extmod/btstack/btstack_hci_uart.h index 646a12dac1..8011e587de 100644 --- a/extmod/nimble/nimble/nimble_hci_uart.h +++ b/extmod/btstack/btstack_hci_uart.h @@ -3,7 +3,8 @@ * * The MIT License (MIT) * - * Copyright (c) 2019 Damien P. George + * Copyright (c) 2020 Damien P. George + * Copyright (c) 2020 Jim Mussared * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,18 +24,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_EXTMOD_NIMBLE_NIMBLE_NIMBLE_HCI_UART_H -#define MICROPY_INCLUDED_EXTMOD_NIMBLE_NIMBLE_NIMBLE_HCI_UART_H -// Extensions to extmod/modbluetooth_hci.h specific to NimBLE. +#ifndef MICROPY_INCLUDED_EXTMOD_BTSTACK_HCI_UART_H +#define MICROPY_INCLUDED_EXTMOD_BTSTACK_HCI_UART_H -#include "extmod/nimble/hal/hal_uart.h" +#include "lib/btstack/src/btstack.h" -// Helpers called from ports. -void mp_bluetooth_nimble_hci_uart_process(void); +// --- Used by the port to create the HCI transport --------------------------- +extern const btstack_uart_block_t mp_bluetooth_btstack_hci_uart_block; -// Must be provided by the port. -void mp_bluetooth_nimble_hci_uart_rx(hal_uart_rx_cb_t rx_cb, void *rx_arg); -void mp_bluetooth_nimble_hci_uart_tx_strn(const char *str, uint len); +// --- Called by the MicroPython port when UART data is available ------------- +void mp_bluetooth_btstack_hci_uart_process(void); -#endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_NIMBLE_NIMBLE_HCI_UART_H +#endif // MICROPY_INCLUDED_EXTMOD_BTSTACK_MODBLUETOOTH_BTSTACK_H diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index 9aef7e6879..03ec12b734 100644 --- a/extmod/btstack/modbluetooth_btstack.c +++ b/extmod/btstack/modbluetooth_btstack.c @@ -53,6 +53,8 @@ STATIC const uint16_t BTSTACK_GAP_DEVICE_NAME_HANDLE = 3; volatile int mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; +#define ERRNO_BLUETOOTH_NOT_ACTIVE MP_ENODEV + STATIC int btstack_error_to_errno(int err) { DEBUG_printf(" --> btstack error: %d\n", err); if (err == ERROR_CODE_SUCCESS) { @@ -300,6 +302,9 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t if (state == HCI_STATE_WORKING) { // Signal that initialisation has completed. mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_ACTIVE; + } else if (state == HCI_STATE_HALTING) { + // Signal that de-initialisation has begun. + mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_HALTING; } else if (state == HCI_STATE_OFF) { // Signal that de-initialisation has completed. mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; diff --git a/extmod/btstack/modbluetooth_btstack.h b/extmod/btstack/modbluetooth_btstack.h index 2fad86f226..7890bbfae2 100644 --- a/extmod/btstack/modbluetooth_btstack.h +++ b/extmod/btstack/modbluetooth_btstack.h @@ -56,6 +56,7 @@ enum { MP_BLUETOOTH_BTSTACK_STATE_OFF, MP_BLUETOOTH_BTSTACK_STATE_STARTING, MP_BLUETOOTH_BTSTACK_STATE_ACTIVE, + MP_BLUETOOTH_BTSTACK_STATE_HALTING, MP_BLUETOOTH_BTSTACK_STATE_TIMEOUT, }; diff --git a/extmod/mpbthci.c b/extmod/mpbthci.c new file mode 100644 index 0000000000..79a8654242 --- /dev/null +++ b/extmod/mpbthci.c @@ -0,0 +1,38 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Default definitions in case a controller doesn't implement this. + +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH + +#define DEBUG_printf(...) // printf(__VA_ARGS__) + +#include "extmod/mpbthci.h" + + +#endif // MICROPY_PY_BLUETOOTH diff --git a/extmod/modbluetooth_hci.h b/extmod/mpbthci.h similarity index 71% rename from extmod/modbluetooth_hci.h rename to extmod/mpbthci.h index d6b432d224..acb5b832ba 100644 --- a/extmod/modbluetooth_hci.h +++ b/extmod/mpbthci.h @@ -24,15 +24,15 @@ * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_HCI_H -#define MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_HCI_H +#ifndef MICROPY_INCLUDED_EXTMOD_MPBTHCI_H +#define MICROPY_INCLUDED_EXTMOD_MPBTHCI_H -#include "uart.h" +// --- Optionally can be implemented by the driver. --------------------------- -// Optionally can be implemented by the driver. +// Start/stop the HCI controller. +// Requires the UART to this HCI controller is available. int mp_bluetooth_hci_controller_init(void); -int mp_bluetooth_hci_controller_activate(void); -int mp_bluetooth_hci_controller_deactivate(void); +int mp_bluetooth_hci_controller_deinit(void); // Tell the controller to go to sleep (e.g. on RX if we don't think we're expecting anything more). int mp_bluetooth_hci_controller_sleep_maybe(void); @@ -41,16 +41,11 @@ bool mp_bluetooth_hci_controller_woken(void); // Wake up the controller (e.g. we're about to TX). int mp_bluetooth_hci_controller_wakeup(void); -// Storage and bindings that need to be implemented by the port. -// These are used by the stack bindings (e.g. nimble/hal_uart.c) -// as well as potentially the driver (e.g. cywbt.c). -extern uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; -extern pyb_uart_obj_t mp_bluetooth_hci_uart_obj; - -int mp_bluetooth_hci_uart_init(uint32_t port); -int mp_bluetooth_hci_uart_activate(void); +// --- Bindings that need to be implemented by the port. ---------------------- +int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate); +int mp_bluetooth_hci_uart_deinit(void); int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate); int mp_bluetooth_hci_uart_readchar(void); int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len); -#endif // MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_HCI_H +#endif // MICROPY_INCLUDED_EXTMOD_MPBTHCI_H diff --git a/extmod/nimble/hal/hal_uart.c b/extmod/nimble/hal/hal_uart.c index fba82b8304..c6d0850fea 100644 --- a/extmod/nimble/hal/hal_uart.c +++ b/extmod/nimble/hal/hal_uart.c @@ -26,11 +26,9 @@ #include "py/runtime.h" #include "py/mphal.h" -#include "pin_static_af.h" #include "nimble/ble.h" #include "extmod/nimble/hal/hal_uart.h" -#include "extmod/modbluetooth_hci.h" -#include "extmod/nimble/nimble/nimble_hci_uart.h" +#include "extmod/mpbthci.h" #if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE @@ -39,6 +37,9 @@ static void *hal_uart_tx_arg; static hal_uart_rx_cb_t hal_uart_rx_cb; static void *hal_uart_rx_arg; +// Provided by the port, and also possibly shared with the driver. +extern uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; + int hal_uart_init_cbs(uint32_t port, hal_uart_tx_cb_t tx_cb, void *tx_arg, hal_uart_rx_cb_t rx_cb, void *rx_arg) { hal_uart_tx_cb = tx_cb; hal_uart_tx_arg = tx_arg; @@ -48,9 +49,7 @@ int hal_uart_init_cbs(uint32_t port, hal_uart_tx_cb_t tx_cb, void *tx_arg, hal_u } int hal_uart_config(uint32_t port, uint32_t baudrate, uint32_t bits, uint32_t stop, uint32_t parity, uint32_t flow) { - mp_bluetooth_hci_uart_init(port); - mp_bluetooth_hci_uart_set_baudrate(baudrate); - return mp_bluetooth_hci_uart_activate(); + return mp_bluetooth_hci_uart_init(port, baudrate); } void hal_uart_start_tx(uint32_t port) { @@ -71,7 +70,7 @@ void hal_uart_start_tx(uint32_t port) { printf("\n"); #endif - mp_bluetooth_nimble_hci_uart_tx_strn((void*)mp_bluetooth_hci_cmd_buf, len); + mp_bluetooth_hci_uart_write(mp_bluetooth_hci_cmd_buf, len); } int hal_uart_close(uint32_t port) { @@ -79,7 +78,17 @@ int hal_uart_close(uint32_t port) { } void mp_bluetooth_nimble_hci_uart_process(void) { - mp_bluetooth_nimble_hci_uart_rx(hal_uart_rx_cb, hal_uart_rx_arg); + bool host_wake = mp_bluetooth_hci_controller_woken(); + + int chr; + while ((chr = mp_bluetooth_hci_uart_readchar()) >= 0) { + // printf("UART RX: %02x\n", data); + hal_uart_rx_cb(hal_uart_rx_arg, chr); + } + + if (host_wake) { + mp_bluetooth_hci_controller_sleep_maybe(); + } } #endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE diff --git a/extmod/nimble/hal/hal_uart.h b/extmod/nimble/hal/hal_uart.h index 0ef04bc81e..1ff1c17436 100644 --- a/extmod/nimble/hal/hal_uart.h +++ b/extmod/nimble/hal/hal_uart.h @@ -1,3 +1,29 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + #ifndef MICROPY_INCLUDED_EXTMOD_NIMBLE_HAL_HAL_UART_H #define MICROPY_INCLUDED_EXTMOD_NIMBLE_HAL_HAL_UART_H @@ -10,10 +36,13 @@ typedef int (*hal_uart_tx_cb_t)(void *arg); typedef int (*hal_uart_rx_cb_t)(void *arg, uint8_t data); -// Called by NimBLE, implemented in hal_uart.c. +// --- Called by NimBLE, implemented in hal_uart.c. --------------------------- int hal_uart_init_cbs(uint32_t port, hal_uart_tx_cb_t tx_cb, void *tx_arg, hal_uart_rx_cb_t rx_cb, void *rx_arg); int hal_uart_config(uint32_t port, uint32_t baud, uint32_t bits, uint32_t stop, uint32_t parity, uint32_t flow); void hal_uart_start_tx(uint32_t port); int hal_uart_close(uint32_t port); +// --- Called by the MicroPython port when UART data is available ------------- +void mp_bluetooth_nimble_hci_uart_process(void); + #endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_HAL_HAL_UART_H diff --git a/extmod/nimble/logcfg/logcfg.h b/extmod/nimble/logcfg/logcfg.h new file mode 100644 index 0000000000..e9023dae65 --- /dev/null +++ b/extmod/nimble/logcfg/logcfg.h @@ -0,0 +1,45 @@ +/** + * This file was generated by Apache newt version: 1.8.0-dev + */ + +#ifndef MICROPY_INCLUDED_EXTMOD_NIMBLE_LOGCFG_LOGCFG_H +#define MICROPY_INCLUDED_EXTMOD_NIMBLE_LOGCFG_LOGCFG_H + +#include "py/mphal.h" +#include "modlog/modlog.h" +#include "log_common/log_common.h" + +#define MICROPY_PY_BLUETOOTH_DIAGNOSTIC_LOGGING (1) + +#if MICROPY_PY_BLUETOOTH_DIAGNOSTIC_LOGGING +#define DFLT_LOG_DEBUG(...) MODLOG_DEBUG(4, __VA_ARGS__) +#else +#define DFLT_LOG_DEBUG(...) IGNORE(__VA_ARGS__) +#endif + +#if MICROPY_PY_BLUETOOTH_DIAGNOSTIC_LOGGING > 1 +#define BLE_HS_LOG_DEBUG(...) MODLOG_DEBUG(4, __VA_ARGS__) +#else +#define BLE_HS_LOG_DEBUG(...) IGNORE(__VA_ARGS__) +#endif + +#define BLE_HS_LOG_INFO(...) MODLOG_INFO(4, __VA_ARGS__) +#define BLE_HS_LOG_WARN(...) MODLOG_WARN(4, __VA_ARGS__) +#define BLE_HS_LOG_ERROR(...) MODLOG_ERROR(4, __VA_ARGS__) +#define BLE_HS_LOG_CRITICAL(...) MODLOG_CRITICAL(4, __VA_ARGS__) +#define BLE_HS_LOG_DISABLED(...) MODLOG_DISABLED(4, __VA_ARGS__) + +#define DFLT_LOG_INFO(...) MODLOG_INFO(0, __VA_ARGS__) +#define DFLT_LOG_WARN(...) MODLOG_WARN(0, __VA_ARGS__) +#define DFLT_LOG_ERROR(...) MODLOG_ERROR(0, __VA_ARGS__) +#define DFLT_LOG_CRITICAL(...) MODLOG_CRITICAL(0, __VA_ARGS__) +#define DFLT_LOG_DISABLED(...) MODLOG_DISABLED(0, __VA_ARGS__) + +#define MFG_LOG_DEBUG(...) IGNORE(__VA_ARGS__) +#define MFG_LOG_INFO(...) IGNORE(__VA_ARGS__) +#define MFG_LOG_WARN(...) IGNORE(__VA_ARGS__) +#define MFG_LOG_ERROR(...) IGNORE(__VA_ARGS__) +#define MFG_LOG_CRITICAL(...) IGNORE(__VA_ARGS__) +#define MFG_LOG_DISABLED(...) MODLOG_DISABLED(128, __VA_ARGS__) + +#endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_LOGCFG_LOGCFG_H diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 36bfa6cccf..61ba3a3aba 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -33,6 +33,7 @@ #include "extmod/nimble/modbluetooth_nimble.h" #include "extmod/modbluetooth.h" +#include "extmod/mpbthci.h" #include "host/ble_hs.h" #include "host/util/util.h" @@ -64,10 +65,11 @@ STATIC int8_t ble_hs_err_to_errno_table[] = { }; STATIC int ble_hs_err_to_errno(int err) { + DEBUG_printf("ble_hs_err_to_errno: %d\n", err); if (!err) { return 0; } - if (0 <= err && err < MP_ARRAY_SIZE(ble_hs_err_to_errno_table) && ble_hs_err_to_errno_table[err]) { + if (err >= 0 && (unsigned)err < MP_ARRAY_SIZE(ble_hs_err_to_errno_table) && ble_hs_err_to_errno_table[err]) { return ble_hs_err_to_errno_table[err]; } else { return MP_EIO; @@ -150,6 +152,12 @@ STATIC void sync_cb(void) { int rc; ble_addr_t addr; + DEBUG_printf("sync_cb: state=%d\n", mp_bluetooth_nimble_ble_state); + + if (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC) { + return; + } + rc = ble_hs_util_ensure_addr(0); // prefer public address if (rc != 0) { // https://mynewt.apache.org/latest/tutorials/ble/eddystone.html#configure-the-nimble-stack-with-an-address @@ -264,11 +272,67 @@ STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { return 0; } +#if !MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY + +// On ports such as ESP32 where we only implement the bindings, then +// the port must provide these functions. +// But for STM32 / Unix-H4, we provide a default implementation of the +// port-specific functionality. +// TODO: In the future if a port ever needs to customise these functions +// then investigate using MP_WEAK or splitting them out to another .c file. + +#include "transport/uart/ble_hci_uart.h" + +void mp_bluetooth_nimble_port_hci_init(void) { + DEBUG_printf("mp_bluetooth_nimble_port_hci_init (nimble default)\n"); + // This calls mp_bluetooth_hci_uart_init (via ble_hci_uart_init --> hal_uart_config --> mp_bluetooth_hci_uart_init). + ble_hci_uart_init(); + mp_bluetooth_hci_controller_init(); +} + +void mp_bluetooth_nimble_port_hci_deinit(void) { + DEBUG_printf("mp_bluetooth_nimble_port_hci_deinit (nimble default)\n"); + mp_bluetooth_hci_controller_deinit(); + mp_bluetooth_hci_uart_deinit(); +} + +void mp_bluetooth_nimble_port_start(void) { + DEBUG_printf("mp_bluetooth_nimble_port_start (nimble default)\n"); + // By default, assume port is already running its own background task (e.g. SysTick on STM32). + // ESP32 runs a FreeRTOS task, Unix has a thread. +} + +// Called when the host stop procedure has completed. +STATIC void ble_hs_shutdown_stop_cb(int status, void *arg) { + (void)status; + (void)arg; + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF; +} + +STATIC struct ble_hs_stop_listener ble_hs_shutdown_stop_listener; + +void mp_bluetooth_nimble_port_shutdown(void) { + DEBUG_printf("mp_bluetooth_nimble_port_shutdown (nimble default)\n"); + // By default, just call ble_hs_stop directly and wait for the stack to stop. + + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_STOPPING; + + ble_hs_stop(&ble_hs_shutdown_stop_listener, ble_hs_shutdown_stop_cb, NULL); + + while (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { + MICROPY_EVENT_POLL_HOOK + } +} + +#endif // !MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY + int mp_bluetooth_init(void) { DEBUG_printf("mp_bluetooth_init\n"); // Clean up if necessary. mp_bluetooth_deinit(); + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_STARTING; + ble_hs_cfg.reset_cb = reset_cb; ble_hs_cfg.sync_cb = sync_cb; ble_hs_cfg.gatts_register_cb = gatts_register_cb; @@ -277,56 +341,70 @@ int mp_bluetooth_init(void) { MP_STATE_PORT(bluetooth_nimble_root_pointers) = m_new0(mp_bluetooth_nimble_root_pointers_t, 1); mp_bluetooth_gatts_db_create(&MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db); - mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_STARTING; + #if !MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY + // Dereference any previous NimBLE mallocs. + MP_STATE_PORT(bluetooth_nimble_memory) = NULL; + #endif - mp_bluetooth_nimble_port_preinit(); + // Allow port (ESP32) to override NimBLE's HCI init. + // Otherwise default implementation above calls ble_hci_uart_init(). + mp_bluetooth_nimble_port_hci_init(); + + // Initialise NimBLE memory and data structures. nimble_port_init(); - mp_bluetooth_nimble_port_postinit(); // By default, just register the default gap/gatt service. ble_svc_gap_init(); ble_svc_gatt_init(); + // Make sure that the HCI UART and event handling task is running. mp_bluetooth_nimble_port_start(); + // Static initialization is complete, can start processing events. + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC; + // Wait for sync callback while (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE) { MICROPY_EVENT_POLL_HOOK } + DEBUG_printf("mp_bluetooth_init: ready\n"); return 0; } -// Called when the host stop procedure has completed. -STATIC void ble_hs_shutdown_stop_cb(int status, void *arg) { - mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF; -} - -STATIC struct ble_hs_stop_listener ble_hs_shutdown_stop_listener; - void mp_bluetooth_deinit(void) { DEBUG_printf("mp_bluetooth_deinit\n"); if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { return; } - mp_bluetooth_gap_advertise_stop(); - #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE - mp_bluetooth_gap_scan_stop(); - #endif + // Must call ble_hs_stop() in a port-specific way to stop the background + // task. Default implementation provided above. + if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE) { + mp_bluetooth_gap_advertise_stop(); + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + mp_bluetooth_gap_scan_stop(); + #endif - mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_STOPPING; + DEBUG_printf("mp_bluetooth_deinit: starting port shutdown\n"); - ble_hs_stop(&ble_hs_shutdown_stop_listener, ble_hs_shutdown_stop_cb, NULL); - - while (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { - MICROPY_EVENT_POLL_HOOK + mp_bluetooth_nimble_port_shutdown(); + assert(mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF); + } else { + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF; } - mp_bluetooth_nimble_port_deinit(); + // Shutdown the HCI controller. + mp_bluetooth_nimble_port_hci_deinit(); MP_STATE_PORT(bluetooth_nimble_root_pointers) = NULL; + + #if !MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY + // Dereference any previous NimBLE mallocs. + MP_STATE_PORT(bluetooth_nimble_memory) = NULL; + #endif + DEBUG_printf("mp_bluetooth_deinit: shut down\n"); } @@ -485,7 +563,7 @@ int mp_bluetooth_gatts_register_service_begin(bool append) { if (!append) { // Unref any previous service definitions. - for (int i = 0; i < MP_STATE_PORT(bluetooth_nimble_root_pointers)->n_services; ++i) { + for (size_t i = 0; i < MP_STATE_PORT(bluetooth_nimble_root_pointers)->n_services; ++i) { MP_STATE_PORT(bluetooth_nimble_root_pointers)->services[i] = NULL; } MP_STATE_PORT(bluetooth_nimble_root_pointers)->n_services = 0; diff --git a/extmod/nimble/modbluetooth_nimble.h b/extmod/nimble/modbluetooth_nimble.h index f44e1d69dc..7e401781e7 100644 --- a/extmod/nimble/modbluetooth_nimble.h +++ b/extmod/nimble/modbluetooth_nimble.h @@ -43,15 +43,27 @@ typedef struct _mp_bluetooth_nimble_root_pointers_t { enum { MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF, MP_BLUETOOTH_NIMBLE_BLE_STATE_STARTING, + MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC, MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE, MP_BLUETOOTH_NIMBLE_BLE_STATE_STOPPING, }; extern volatile int mp_bluetooth_nimble_ble_state; -void mp_bluetooth_nimble_port_preinit(void); -void mp_bluetooth_nimble_port_postinit(void); -void mp_bluetooth_nimble_port_deinit(void); +// --- Optionally provided by the MicroPython port. --------------------------- +// (default implementations provided by modbluetooth_nimble.c) + +// Tell the port to init the UART and start the HCI controller. +void mp_bluetooth_nimble_port_hci_init(void); + +// Tell the port to deinit the UART and shutdown the HCI controller. +void mp_bluetooth_nimble_port_hci_deinit(void); + +// Tell the port to run its background task (i.e. poll the UART and pump events). void mp_bluetooth_nimble_port_start(void); +// Tell the port to stop its background task. +void mp_bluetooth_nimble_port_shutdown(void); + + #endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_MODBLUETOOTH_NIMBLE_H diff --git a/extmod/nimble/nimble.mk b/extmod/nimble/nimble.mk index 4e1642c98a..f8a68bc6f5 100644 --- a/extmod/nimble/nimble.mk +++ b/extmod/nimble/nimble.mk @@ -2,16 +2,19 @@ ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) -EXTMOD_SRC_C += extmod/nimble/modbluetooth_nimble.c +EXTMOD_DIR = extmod +NIMBLE_EXTMOD_DIR = $(EXTMOD_DIR)/nimble + +EXTMOD_SRC_C += $(NIMBLE_EXTMOD_DIR)/modbluetooth_nimble.c CFLAGS_MOD += -DMICROPY_BLUETOOTH_NIMBLE=1 -NIMBLE_EXTMOD_DIR = extmod/nimble - # Use NimBLE from the submodule in lib/mynewt-nimble by default, # allowing a port to use their own system version (e.g. ESP32). MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY ?= 0 +CFLAGS_MOD += -DMICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY=$(MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY) + ifeq ($(MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY),0) NIMBLE_LIB_DIR = lib/mynewt-nimble @@ -82,7 +85,7 @@ LIB_SRC_C += $(addprefix $(NIMBLE_LIB_DIR)/, \ ) EXTMOD_SRC_C += $(addprefix $(NIMBLE_EXTMOD_DIR)/, \ - nimble/npl_os.c \ + nimble/nimble_npl_os.c \ hal/hal_uart.c \ ) @@ -98,7 +101,7 @@ INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/include INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/transport/uart/include INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/porting/nimble/include -$(BUILD)/$(NIMBLE_LIB_DIR)/%.o: CFLAGS += -Wno-maybe-uninitialized -Wno-pointer-arith -Wno-unused-but-set-variable -Wno-format +$(BUILD)/$(NIMBLE_LIB_DIR)/%.o: CFLAGS += -Wno-maybe-uninitialized -Wno-pointer-arith -Wno-unused-but-set-variable -Wno-format -Wno-sign-compare endif diff --git a/extmod/nimble/nimble/npl_os.c b/extmod/nimble/nimble/nimble_npl_os.c similarity index 85% rename from extmod/nimble/nimble/npl_os.c rename to extmod/nimble/nimble/nimble_npl_os.c index 20c2604054..ba3031a8ff 100644 --- a/extmod/nimble/nimble/npl_os.c +++ b/extmod/nimble/nimble/nimble_npl_os.c @@ -29,16 +29,18 @@ #include "py/runtime.h" #include "nimble/ble.h" #include "nimble/nimble_npl.h" -#include "nimble/nimble_hci_uart.h" +#include "extmod/nimble/hal/hal_uart.h" -#define DEBUG_OS_printf(...) //printf(__VA_ARGS__) -#define DEBUG_MALLOC_printf(...) //printf(__VA_ARGS__) -#define DEBUG_EVENT_printf(...) //printf(__VA_ARGS__) -#define DEBUG_MUTEX_printf(...) //printf(__VA_ARGS__) -#define DEBUG_SEM_printf(...) //printf(__VA_ARGS__) -#define DEBUG_CALLOUT_printf(...) //printf(__VA_ARGS__) -#define DEBUG_TIME_printf(...) //printf(__VA_ARGS__) -#define DEBUG_CRIT_printf(...) //printf(__VA_ARGS__) +#include "extmod/modbluetooth.h" + +#define DEBUG_OS_printf(...) // printf(__VA_ARGS__) +#define DEBUG_MALLOC_printf(...) // printf(__VA_ARGS__) +#define DEBUG_EVENT_printf(...) // printf(__VA_ARGS__) +#define DEBUG_MUTEX_printf(...) // printf(__VA_ARGS__) +#define DEBUG_SEM_printf(...) // printf(__VA_ARGS__) +#define DEBUG_CALLOUT_printf(...) // printf(__VA_ARGS__) +#define DEBUG_TIME_printf(...) // printf(__VA_ARGS__) +#define DEBUG_CRIT_printf(...) // printf(__VA_ARGS__) bool ble_npl_os_started(void) { DEBUG_OS_printf("ble_npl_os_started\n"); @@ -138,7 +140,7 @@ int nimble_sprintf(char *str, const char *fmt, ...) { struct ble_npl_eventq *global_eventq = NULL; -void os_eventq_run_all(void) { +void mp_bluetooth_nimble_os_eventq_run_all(void) { for (struct ble_npl_eventq *evq = global_eventq; evq != NULL; evq = evq->nextq) { while (evq->head != NULL) { struct ble_npl_event *ev = evq->head; @@ -238,23 +240,39 @@ ble_npl_error_t ble_npl_sem_init(struct ble_npl_sem *sem, uint16_t tokens) { ble_npl_error_t ble_npl_sem_pend(struct ble_npl_sem *sem, ble_npl_time_t timeout) { DEBUG_SEM_printf("ble_npl_sem_pend(%p, %u) count=%u\n", sem, (uint)timeout, (uint)sem->count); + + // This is called by NimBLE to synchronously wait for an HCI ACK. The + // corresponding ble_npl_sem_release is called directly by the UART rx + // handler (i.e. hal_uart_rx_cb in extmod/nimble/hal/hal_uart.c). + + // So this implementation just polls the UART until either the semaphore + // is released, or the timeout occurs. + if (sem->count == 0) { uint32_t t0 = mp_hal_ticks_ms(); while (sem->count == 0 && mp_hal_ticks_ms() - t0 < timeout) { - // This function may be called at thread-level, so execute - // mp_bluetooth_nimble_hci_uart_process at raised priority. + // This can be called either from code running in NimBLE's "task" + // (i.e. PENDSV) or directly by application code, so for the + // latter case, prevent the "task" from running while we poll the + // UART directly. MICROPY_PY_BLUETOOTH_ENTER mp_bluetooth_nimble_hci_uart_process(); MICROPY_PY_BLUETOOTH_EXIT + if (sem->count != 0) { break; } - __WFI(); + + // Because we're polling, might as well wait for a UART IRQ indicating + // more data available. + mp_bluetooth_nimble_hci_uart_wfi(); } + if (sem->count == 0) { - printf("timeout\n"); + printf("NimBLE: HCI ACK timeout\n"); return BLE_NPL_TIMEOUT; } + DEBUG_SEM_printf("got response in %u ms\n", (int)(mp_hal_ticks_ms() - t0)); } sem->count -= 1; @@ -277,7 +295,7 @@ uint16_t ble_npl_sem_get_count(struct ble_npl_sem *sem) { static struct ble_npl_callout *global_callout = NULL; -void os_callout_process(void) { +void mp_bluetooth_nimble_os_callout_process(void) { uint32_t tnow = mp_hal_ticks_ms(); for (struct ble_npl_callout *c = global_callout; c != NULL; c = c->nextc) { if (!c->active) { @@ -386,12 +404,24 @@ void ble_npl_time_delay(ble_npl_time_t ticks) { /******************************************************************************/ // CRITICAL +// This is used anywhere NimBLE modifies global data structures. +// We need to protect between: +// - A MicroPython VM thread. +// - The NimBLE "task" (e.g. PENDSV on STM32, pthread on Unix). +// On STM32, by disabling PENDSV, we ensure that either: +// - If we're in the NimBLE task, we're exclusive anyway. +// - If we're in a VM thread, we can't be interrupted by the NimBLE task, or switched to another thread. +// On Unix, there's a global mutex. + +// TODO: Both ports currently use MICROPY_PY_BLUETOOTH_ENTER in their implementation, +// maybe this doesn't need to be port-specific? + uint32_t ble_npl_hw_enter_critical(void) { DEBUG_CRIT_printf("ble_npl_hw_enter_critical()\n"); - return raise_irq_pri(15); + return mp_bluetooth_nimble_hci_uart_enter_critical(); } void ble_npl_hw_exit_critical(uint32_t ctx) { DEBUG_CRIT_printf("ble_npl_hw_exit_critical(%u)\n", (uint)ctx); - restore_irq_pri(ctx); + mp_bluetooth_nimble_hci_uart_exit_critical(ctx); } diff --git a/extmod/nimble/nimble/nimble_npl_os.h b/extmod/nimble/nimble/nimble_npl_os.h index 3d886fedb5..5542520661 100644 --- a/extmod/nimble/nimble/nimble_npl_os.h +++ b/extmod/nimble/nimble/nimble_npl_os.h @@ -24,11 +24,15 @@ * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NPL_OS_H -#define MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NPL_OS_H +#ifndef MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NIMBLE_NPL_OS_H +#define MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NIMBLE_NPL_OS_H + +// This is included by nimble/nimble_npl.h -- include that rather than this file directly. #include +// --- Configuration of NimBLE data structures -------------------------------- + #define BLE_NPL_OS_ALIGNMENT (4) #define BLE_NPL_TIME_FOREVER (0xffffffff) @@ -63,4 +67,15 @@ struct ble_npl_sem { volatile uint16_t count; }; +// --- Called by the MicroPython port ----------------------------------------- + +void mp_bluetooth_nimble_os_eventq_run_all(void); +void mp_bluetooth_nimble_os_callout_process(void); + +// --- Must be provided by the MicroPython port ------------------------------- + +void mp_bluetooth_nimble_hci_uart_wfi(void); +uint32_t mp_bluetooth_nimble_hci_uart_enter_critical(void); +void mp_bluetooth_nimble_hci_uart_exit_critical(uint32_t atomic_state); + #endif // MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NPL_OS_H diff --git a/extmod/nimble/syscfg/syscfg.h b/extmod/nimble/syscfg/syscfg.h index 6ac420f35e..8a6f2338be 100644 --- a/extmod/nimble/syscfg/syscfg.h +++ b/extmod/nimble/syscfg/syscfg.h @@ -2,12 +2,14 @@ #define MICROPY_INCLUDED_EXTMOD_NIMBLE_SYSCFG_H #include "py/mphal.h" -#include "uart.h" + +#include "mpnimbleport.h" void *nimble_malloc(size_t size); void nimble_free(void *ptr); void *nimble_realloc(void *ptr, size_t size); +// Redirect NimBLE malloc to the GC heap. #define malloc(size) nimble_malloc(size) #define free(ptr) nimble_free(ptr) #define realloc(ptr, size) nimble_realloc(ptr, size) @@ -88,6 +90,7 @@ int nimble_sprintf(char *str, const char *fmt, ...); #define MYNEWT_VAL_BLE_GATT_WRITE_NO_RSP (MYNEWT_VAL_BLE_ROLE_CENTRAL) #define MYNEWT_VAL_BLE_GATT_WRITE_RELIABLE (MYNEWT_VAL_BLE_ROLE_CENTRAL) #define MYNEWT_VAL_BLE_HOST (1) +#define MYNEWT_VAL_BLE_HS_AUTO_START (1) #define MYNEWT_VAL_BLE_HS_DEBUG (0) #define MYNEWT_VAL_BLE_HS_FLOW_CTRL (0) #define MYNEWT_VAL_BLE_HS_FLOW_CTRL_ITVL (1000) diff --git a/ports/esp32/Makefile b/ports/esp32/Makefile index 2cbe9f6be5..dcf4110cfc 100644 --- a/ports/esp32/Makefile +++ b/ports/esp32/Makefile @@ -341,7 +341,7 @@ SRC_C = \ modnetwork.c \ network_lan.c \ network_ppp.c \ - nimble.c \ + mpnimbleport.c \ modsocket.c \ modesp.c \ esp32_partition.c \ diff --git a/ports/esp32/nimble.c b/ports/esp32/mpnimbleport.c similarity index 66% rename from ports/esp32/nimble.c rename to ports/esp32/mpnimbleport.c index 16829732c3..a58fcbdbf4 100644 --- a/ports/esp32/nimble.c +++ b/ports/esp32/mpnimbleport.c @@ -30,28 +30,48 @@ #if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE +#define DEBUG_printf(...) // printf("nimble (esp32): " __VA_ARGS__) + #include "esp_nimble_hci.h" #include "nimble/nimble_port.h" #include "nimble/nimble_port_freertos.h" +#include "extmod/nimble/modbluetooth_nimble.h" + STATIC void ble_host_task(void *param) { + DEBUG_printf("ble_host_task\n"); nimble_port_run(); // This function will return only when nimble_port_stop() is executed. nimble_port_freertos_deinit(); } -void mp_bluetooth_nimble_port_preinit(void) { +void mp_bluetooth_nimble_port_hci_init(void) { + DEBUG_printf("mp_bluetooth_nimble_port_hci_init\n"); esp_nimble_hci_and_controller_init(); } -void mp_bluetooth_nimble_port_postinit(void) { -} +void mp_bluetooth_nimble_port_hci_deinit(void) { + DEBUG_printf("mp_bluetooth_nimble_port_hci_deinit\n"); -void mp_bluetooth_nimble_port_deinit(void) { - nimble_port_stop(); + esp_nimble_hci_and_controller_deinit(); } void mp_bluetooth_nimble_port_start(void) { + DEBUG_printf("mp_bluetooth_nimble_port_start\n"); nimble_port_freertos_init(ble_host_task); } +void mp_bluetooth_nimble_port_shutdown(void) { + DEBUG_printf("mp_bluetooth_nimble_port_shutdown\n"); + + // Despite the name, these is an ESP32-specific (no other NimBLE ports have these functions). + // Calls ble_hs_stop() and waits for stack shutdown. + nimble_port_stop(); + + // Shuts down the event queue. + nimble_port_deinit(); + + // Mark stack as shutdown. + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF; +} + #endif diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index e7d2e2abc2..7b68299e04 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -480,7 +480,7 @@ endif ifeq ($(MICROPY_PY_BLUETOOTH),1) -SRC_C += modbluetooth_hci.c +SRC_C += mpbthciport.c ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) @@ -490,12 +490,13 @@ endif ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) include $(TOP)/extmod/nimble/nimble.mk -SRC_C += nimble.c +SRC_C += mpnimbleport.c endif ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) +MICROPY_BLUETOOTH_BTSTACK_H4 ?= 1 include $(TOP)/extmod/btstack/btstack.mk -SRC_C += btstack.c +SRC_C += mpbtstackport.c endif ifeq ($(MICROPY_PY_NETWORK_CYW43),1) diff --git a/ports/stm32/modbluetooth_hci.c b/ports/stm32/mpbthciport.c similarity index 75% rename from ports/stm32/modbluetooth_hci.c rename to ports/stm32/mpbthciport.c index 54c60adc01..71a9e4fc7f 100644 --- a/ports/stm32/modbluetooth_hci.c +++ b/ports/stm32/mpbthciport.c @@ -26,17 +26,20 @@ #include "py/runtime.h" #include "py/mphal.h" -#include "extmod/modbluetooth_hci.h" +#include "extmod/mpbthci.h" #include "systick.h" #include "pendsv.h" +#include "lib/utils/mpirq.h" #include "py/obj.h" #if MICROPY_PY_BLUETOOTH +#define DEBUG_printf(...) // printf(__VA_ARGS__) + uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; -// Must be provided by the stack bindings. +// Must be provided by the stack bindings (e.g. mpnimbleport.c or mpbtstackport.c). extern void mp_bluetooth_hci_poll(void); // Hook for pendsv poller to run this periodically every 128ms @@ -61,34 +64,19 @@ STATIC uint16_t hci_uart_rx_buf_cur; STATIC uint16_t hci_uart_rx_buf_len; STATIC uint8_t hci_uart_rx_buf_data[256]; -int mp_bluetooth_hci_controller_deactivate(void) { - return 0; -} - -int mp_bluetooth_hci_controller_sleep_maybe(void) { - return 0; -} - -bool mp_bluetooth_hci_controller_woken(void) { - return true; -} - -int mp_bluetooth_hci_controller_wakeup(void) { - return 0; -} - -int mp_bluetooth_hci_uart_init(uint32_t port) { +int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { (void)port; - return 0; -} - -int mp_bluetooth_hci_uart_activate(void) { + (void)baudrate; rfcore_ble_init(); hci_uart_rx_buf_cur = 0; hci_uart_rx_buf_len = 0; return 0; } +int mp_bluetooth_hci_uart_deinit(void) { + return 0; +} + int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate) { (void)baudrate; return 0; @@ -140,13 +128,16 @@ mp_irq_obj_t mp_bluetooth_hci_uart_irq_obj; static uint8_t hci_uart_rxbuf[512]; mp_obj_t mp_uart_interrupt(mp_obj_t self_in) { - // New HCI data, schedule mp_bluetooth_hci_poll to make the stack handle it. + // DEBUG_printf("mp_uart_interrupt\n"); + // New HCI data, schedule mp_bluetooth_hci_poll via PENDSV to make the stack handle it. mp_bluetooth_hci_poll_wrapper(0); return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_1(mp_uart_interrupt_obj, mp_uart_interrupt); -int mp_bluetooth_hci_uart_init(uint32_t port) { +int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { + DEBUG_printf("mp_bluetooth_hci_uart_init (stm32)\n"); + // bits (8), stop (1), parity (none) and flow (rts/cts) are assumed to match MYNEWT_VAL_BLE_HCI_UART_ constants in syscfg.h. mp_bluetooth_hci_uart_obj.base.type = &pyb_uart_type; mp_bluetooth_hci_uart_obj.uart_id = port; @@ -154,16 +145,29 @@ int mp_bluetooth_hci_uart_init(uint32_t port) { mp_bluetooth_hci_uart_obj.timeout = 2; mp_bluetooth_hci_uart_obj.timeout_char = 2; MP_STATE_PORT(pyb_uart_obj_all)[mp_bluetooth_hci_uart_obj.uart_id - 1] = &mp_bluetooth_hci_uart_obj; + + // This also initialises the UART. + mp_bluetooth_hci_uart_set_baudrate(baudrate); + + return 0; +} + +int mp_bluetooth_hci_uart_deinit(void) { + DEBUG_printf("mp_bluetooth_hci_uart_deinit (stm32)\n"); + // TODO: deinit mp_bluetooth_hci_uart_obj + return 0; } int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate) { + DEBUG_printf("mp_bluetooth_hci_uart_set_baudrate(%lu) (stm32)\n", baudrate); + if (!baudrate) { + return -1; + } + uart_init(&mp_bluetooth_hci_uart_obj, baudrate, UART_WORDLENGTH_8B, UART_PARITY_NONE, UART_STOPBITS_1, UART_HWCONTROL_RTS | UART_HWCONTROL_CTS); uart_set_rxbuf(&mp_bluetooth_hci_uart_obj, sizeof(hci_uart_rxbuf), hci_uart_rxbuf); - return 0; -} -int mp_bluetooth_hci_uart_activate(void) { // Add IRQ handler for IDLE (i.e. packet finished). uart_irq_config(&mp_bluetooth_hci_uart_obj, false); mp_irq_init(&mp_bluetooth_hci_uart_irq_obj, &uart_irq_methods, MP_OBJ_FROM_PTR(&mp_bluetooth_hci_uart_obj)); @@ -173,13 +177,12 @@ int mp_bluetooth_hci_uart_activate(void) { mp_bluetooth_hci_uart_irq_obj.ishard = true; uart_irq_config(&mp_bluetooth_hci_uart_obj, true); - mp_bluetooth_hci_controller_init(); - mp_bluetooth_hci_controller_activate(); - return 0; } int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len) { + // DEBUG_printf("mp_bluetooth_hci_uart_write (stm32)\n"); + mp_bluetooth_hci_controller_wakeup(); uart_tx_strn(&mp_bluetooth_hci_uart_obj, (void *)buf, len); return 0; @@ -188,7 +191,10 @@ int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len) { // This function expects the controller to be in the wake state via a previous call // to mp_bluetooth_hci_controller_woken. int mp_bluetooth_hci_uart_readchar(void) { + // DEBUG_printf("mp_bluetooth_hci_uart_readchar (stm32)\n"); + if (uart_rx_any(&mp_bluetooth_hci_uart_obj)) { + // DEBUG_printf("... available\n"); return uart_rx_char(&mp_bluetooth_hci_uart_obj); } else { return -1; @@ -197,4 +203,33 @@ int mp_bluetooth_hci_uart_readchar(void) { #endif // defined(STM32WB) +// Default (weak) implementation of the HCI controller interface. +// A driver (e.g. cywbt43.c) can override these for controller-specific +// functionality (i.e. power management). + +MP_WEAK int mp_bluetooth_hci_controller_init(void) { + DEBUG_printf("mp_bluetooth_hci_controller_init (default)\n"); + return 0; +} + +MP_WEAK int mp_bluetooth_hci_controller_deinit(void) { + DEBUG_printf("mp_bluetooth_hci_controller_deinit (default)\n"); + return 0; +} + +MP_WEAK int mp_bluetooth_hci_controller_sleep_maybe(void) { + DEBUG_printf("mp_bluetooth_hci_controller_sleep_maybe (default)\n"); + return 0; +} + +MP_WEAK bool mp_bluetooth_hci_controller_woken(void) { + DEBUG_printf("mp_bluetooth_hci_controller_woken (default)\n"); + return true; +} + +MP_WEAK int mp_bluetooth_hci_controller_wakeup(void) { + DEBUG_printf("mp_bluetooth_hci_controller_wakeup (default)\n"); + return 0; +} + #endif // MICROPY_PY_BLUETOOTH diff --git a/ports/stm32/mpbtstackport.c b/ports/stm32/mpbtstackport.c new file mode 100644 index 0000000000..a031a6a151 --- /dev/null +++ b/ports/stm32/mpbtstackport.c @@ -0,0 +1,107 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Damien P. George + * + * 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 && MICROPY_BLUETOOTH_BTSTACK + +#include "lib/btstack/src/btstack.h" +#include "lib/btstack/platform/embedded/btstack_run_loop_embedded.h" +#include "lib/btstack/platform/embedded/hal_cpu.h" +#include "lib/btstack/platform/embedded/hal_time_ms.h" + +#include "extmod/mpbthci.h" +#include "extmod/btstack/btstack_hci_uart.h" +#include "extmod/btstack/modbluetooth_btstack.h" + +// The IRQ functionality in btstack_run_loop_embedded.c is not used, so the +// following three functions are empty. + +void hal_cpu_disable_irqs(void) { +} + +void hal_cpu_enable_irqs(void) { +} + +void hal_cpu_enable_irqs_and_sleep(void) { +} + +uint32_t hal_time_ms(void) { + return mp_hal_ticks_ms(); +} + +STATIC const hci_transport_config_uart_t hci_transport_config_uart = { + HCI_TRANSPORT_CONFIG_UART, + MICROPY_HW_BLE_UART_BAUDRATE, + 3000000, + 0, + NULL, +}; + +void mp_bluetooth_hci_poll(void) { + if (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_OFF) { + return; + } + + // Process uart data. + if (mp_bluetooth_btstack_state != MP_BLUETOOTH_BTSTACK_STATE_HALTING) { + mp_bluetooth_btstack_hci_uart_process(); + } + + // Call the BTstack run loop. + btstack_run_loop_embedded_execute_once(); +} + +void mp_bluetooth_btstack_port_init(void) { + static bool run_loop_init = false; + if (!run_loop_init) { + run_loop_init = true; + btstack_run_loop_init(btstack_run_loop_embedded_get_instance()); + } else { + btstack_run_loop_embedded_get_instance()->init(); + } + + // hci_dump_open(NULL, HCI_DUMP_STDOUT); + const hci_transport_t *transport = hci_transport_h4_instance(&mp_bluetooth_btstack_hci_uart_block); + hci_init(transport, &hci_transport_config_uart); + + // TODO: Probably not necessary for BCM (we have our own firmware loader in the cyw43 driver), + // but might be worth investigating for other controllers in the future. + // hci_set_chipset(btstack_chipset_bcm_instance()); +} + +void mp_bluetooth_btstack_port_deinit(void) { + hci_power_control(HCI_POWER_OFF); + hci_close(); +} + +void mp_bluetooth_btstack_port_start(void) { + hci_power_control(HCI_POWER_ON); +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK diff --git a/ports/stm32/mpbtstackport.h b/ports/stm32/mpbtstackport.h new file mode 100644 index 0000000000..110f921d9a --- /dev/null +++ b/ports/stm32/mpbtstackport.h @@ -0,0 +1,34 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_STM32_BTSTACK_PORT_H +#define MICROPY_INCLUDED_STM32_BTSTACK_PORT_H + +// To allow MICROPY_HW_BLE_UART_ID to be resolved. + +#include "uart.h" + +#endif // MICROPY_INCLUDED_STM32_BTSTACK_PORT_H diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 14a0589895..e1e1491d64 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -298,6 +298,7 @@ extern const struct _mp_obj_module_t mp_module_onewire; #if MICROPY_BLUETOOTH_NIMBLE struct _mp_bluetooth_nimble_root_pointers_t; +struct _mp_bluetooth_nimble_malloc_t; #define MICROPY_PORT_ROOT_POINTER_BLUETOOTH_NIMBLE void **bluetooth_nimble_memory; struct _mp_bluetooth_nimble_root_pointers_t *bluetooth_nimble_root_pointers; #else #define MICROPY_PORT_ROOT_POINTER_BLUETOOTH_NIMBLE @@ -410,14 +411,20 @@ static inline mp_uint_t disable_irq(void) { #define MICROPY_THREAD_YIELD() #endif -// The LwIP interface must run at a raised IRQ priority -#define MICROPY_PY_LWIP_ENTER uint32_t atomic_state = raise_irq_pri(IRQ_PRI_PENDSV); -#define MICROPY_PY_LWIP_REENTER atomic_state = raise_irq_pri(IRQ_PRI_PENDSV); -#define MICROPY_PY_LWIP_EXIT restore_irq_pri(atomic_state); +// For regular code that wants to prevent "background tasks" from running. +// These background tasks (LWIP, Bluetooth) run in PENDSV context. +#define MICROPY_PY_PENDSV_ENTER uint32_t atomic_state = raise_irq_pri(IRQ_PRI_PENDSV); +#define MICROPY_PY_PENDSV_REENTER atomic_state = raise_irq_pri(IRQ_PRI_PENDSV); +#define MICROPY_PY_PENDSV_EXIT restore_irq_pri(atomic_state); -// Bluetooth calls must run at a raised IRQ priority -#define MICROPY_PY_BLUETOOTH_ENTER MICROPY_PY_LWIP_ENTER -#define MICROPY_PY_BLUETOOTH_EXIT MICROPY_PY_LWIP_EXIT +// Prevent the "LWIP task" from running. +#define MICROPY_PY_LWIP_ENTER MICROPY_PY_PENDSV_ENTER +#define MICROPY_PY_LWIP_REENTER MICROPY_PY_PENDSV_REENTER +#define MICROPY_PY_LWIP_EXIT MICROPY_PY_PENDSV_EXIT + +// Prevent the "Bluetooth task" from running (either NimBLE or btstack). +#define MICROPY_PY_BLUETOOTH_ENTER MICROPY_PY_PENDSV_ENTER +#define MICROPY_PY_BLUETOOTH_EXIT MICROPY_PY_PENDSV_EXIT // We need an implementation of the log2 function which is not a macro #define MP_NEED_LOG2 (1) diff --git a/ports/stm32/nimble.c b/ports/stm32/mpnimbleport.c similarity index 56% rename from ports/stm32/nimble.c rename to ports/stm32/mpnimbleport.c index 0d349585fb..1d7c095139 100644 --- a/ports/stm32/nimble.c +++ b/ports/stm32/mpnimbleport.c @@ -31,58 +31,41 @@ #if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE -#include "transport/uart/ble_hci_uart.h" #include "host/ble_hs.h" +#include "nimble/nimble_npl.h" -#include "extmod/modbluetooth_hci.h" +#include "extmod/mpbthci.h" #include "extmod/nimble/modbluetooth_nimble.h" -#include "extmod/nimble/nimble/nimble_hci_uart.h" - -extern void os_eventq_run_all(void); -extern void os_callout_process(void); +#include "extmod/nimble/hal/hal_uart.h" +// This implements the Nimble "background task". It's called at PENDSV +// priority, either every 128ms or whenever there's UART data available. +// Because it's called via PENDSV, you can implicitly consider that it +// is surrounded by MICROPY_PY_BLUETOOTH_ENTER / MICROPY_PY_BLUETOOTH_EXIT. void mp_bluetooth_hci_poll(void) { - if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { - return; - } + if (mp_bluetooth_nimble_ble_state >= MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC) { + // Ask NimBLE to process UART data. + mp_bluetooth_nimble_hci_uart_process(); - mp_bluetooth_nimble_hci_uart_process(); - os_callout_process(); - os_eventq_run_all(); -} - -void mp_bluetooth_nimble_port_preinit(void) { - MP_STATE_PORT(bluetooth_nimble_memory) = NULL; - ble_hci_uart_init(); -} - -void mp_bluetooth_nimble_port_postinit(void) { -} - -void mp_bluetooth_nimble_port_deinit(void) { - mp_bluetooth_hci_controller_deactivate(); -} - -void mp_bluetooth_nimble_port_start(void) { - ble_hs_start(); -} - -void mp_bluetooth_nimble_hci_uart_rx(hal_uart_rx_cb_t rx_cb, void *rx_arg) { - bool host_wake = mp_bluetooth_hci_controller_woken(); - - int chr; - while ((chr = mp_bluetooth_hci_uart_readchar()) >= 0) { - // printf("UART RX: %02x\n", data); - rx_cb(rx_arg, chr); - } - - if (host_wake) { - mp_bluetooth_hci_controller_sleep_maybe(); + // Run pending background operations and events, but only after HCI sync. + mp_bluetooth_nimble_os_callout_process(); + mp_bluetooth_nimble_os_eventq_run_all(); } } -void mp_bluetooth_nimble_hci_uart_tx_strn(const char *str, uint len) { - mp_bluetooth_hci_uart_write((const uint8_t *)str, len); +// --- Port-specific helpers for the generic NimBLE bindings. ----------------- + +void mp_bluetooth_nimble_hci_uart_wfi(void) { + __WFI(); +} + +uint32_t mp_bluetooth_nimble_hci_uart_enter_critical(void) { + MICROPY_PY_BLUETOOTH_ENTER + return atomic_state; +} + +void mp_bluetooth_nimble_hci_uart_exit_critical(uint32_t atomic_state) { + MICROPY_PY_BLUETOOTH_EXIT } #endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE diff --git a/ports/stm32/mpnimbleport.h b/ports/stm32/mpnimbleport.h new file mode 100644 index 0000000000..f3ee597183 --- /dev/null +++ b/ports/stm32/mpnimbleport.h @@ -0,0 +1,34 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_STM32_NIMBLE_PORT_H +#define MICROPY_INCLUDED_STM32_NIMBLE_PORT_H + +// To allow MICROPY_HW_BLE_UART_ID to be resolved. + +#include "uart.h" + +#endif // MICROPY_INCLUDED_STM32_NIMBLE_PORT_H diff --git a/ports/unix/Makefile b/ports/unix/Makefile index 3e2fa63a16..a9ba5edfad 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -140,18 +140,19 @@ ifeq ($(HAVE_LIBUSB),1) CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH=1 CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE=1 -CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK=1 +# Runs in a thread, cannot make calls into the VM. +CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK=0 MICROPY_BLUETOOTH_BTSTACK ?= 1 MICROPY_BLUETOOTH_BTSTACK_USB ?= 1 ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) GIT_SUBMODULES += lib/btstack - include $(TOP)/extmod/btstack/btstack.mk endif endif + endif ifeq ($(MICROPY_PY_FFI),1) @@ -198,7 +199,9 @@ SRC_C = \ alloc.c \ coverage.c \ fatfs_port.c \ - btstack_usb.c \ + mpbtstackport_common.c \ + mpbtstackport_usb.c \ + mpnimbleport.c \ $(SRC_MOD) \ $(wildcard $(VARIANT_DIR)/*.c) diff --git a/ports/unix/mpbtstackport.h b/ports/unix/mpbtstackport.h new file mode 100644 index 0000000000..29433cc7fb --- /dev/null +++ b/ports/unix/mpbtstackport.h @@ -0,0 +1,39 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_UNIX_BTSTACK_PORT_H +#define MICROPY_INCLUDED_UNIX_BTSTACK_PORT_H + +#define MICROPY_HW_BLE_UART_ID (0) +#define MICROPY_HW_BLE_UART_BAUDRATE (1000000) + +bool mp_bluetooth_hci_poll(void); + +#if MICROPY_BLUETOOTH_BTSTACK_USB +void mp_bluetooth_btstack_port_init_usb(void); +#endif + +#endif // MICROPY_INCLUDED_UNIX_BTSTACK_PORT_H diff --git a/ports/unix/mpbtstackport_common.c b/ports/unix/mpbtstackport_common.c new file mode 100644 index 0000000000..402cf95283 --- /dev/null +++ b/ports/unix/mpbtstackport_common.c @@ -0,0 +1,92 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK + +#include "lib/btstack/src/btstack.h" + +#include "lib/btstack/platform/embedded/btstack_run_loop_embedded.h" +#include "lib/btstack/platform/embedded/hal_cpu.h" +#include "lib/btstack/platform/embedded/hal_time_ms.h" + +#include "extmod/btstack/modbluetooth_btstack.h" + +#include "mpbtstackport.h" + +// Called by the UART polling thread in mpbthciport.c, or by the USB polling thread in mpbtstackport_usb.c. +bool mp_bluetooth_hci_poll(void) { + if (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_STARTING || mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE || mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_HALTING) { + // Pretend like we're running in IRQ context (i.e. other things can't be running at the same time). + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + btstack_run_loop_embedded_execute_once(); + MICROPY_END_ATOMIC_SECTION(atomic_state); + + return true; + } + + return false; +} + +// The IRQ functionality in btstack_run_loop_embedded.c is not used, so the +// following three functions are empty. + +void hal_cpu_disable_irqs(void) { +} + +void hal_cpu_enable_irqs(void) { +} + +void hal_cpu_enable_irqs_and_sleep(void) { +} + +uint32_t hal_time_ms(void) { + return mp_hal_ticks_ms(); +} + +void mp_bluetooth_btstack_port_init(void) { + static bool run_loop_init = false; + if (!run_loop_init) { + run_loop_init = true; + btstack_run_loop_init(btstack_run_loop_embedded_get_instance()); + } else { + btstack_run_loop_embedded_get_instance()->init(); + } + + // hci_dump_open(NULL, HCI_DUMP_STDOUT); + + #if MICROPY_BLUETOOTH_BTSTACK_USB + mp_bluetooth_btstack_port_init_usb(); + #endif +} + +void mp_hal_get_mac(int idx, uint8_t buf[6]) { +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK diff --git a/ports/unix/btstack_usb.c b/ports/unix/mpbtstackport_usb.c similarity index 54% rename from ports/unix/btstack_usb.c rename to ports/unix/mpbtstackport_usb.c index ab6a49f39a..28d2c8c543 100644 --- a/ports/unix/btstack_usb.c +++ b/ports/unix/mpbtstackport_usb.c @@ -31,7 +31,7 @@ #include "py/mperrno.h" #include "py/mphal.h" -#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_USB #include "lib/btstack/src/btstack.h" #include "lib/btstack/platform/embedded/btstack_run_loop_embedded.h" @@ -40,76 +40,15 @@ #include "extmod/btstack/modbluetooth_btstack.h" +#include "mpbtstackport.h" + #if !MICROPY_PY_THREAD #error Unix btstack requires MICROPY_PY_THREAD #endif STATIC const useconds_t USB_POLL_INTERVAL_US = 1000; -STATIC const uint8_t read_static_address_command_complete_prefix[] = { 0x0e, 0x1b, 0x01, 0x09, 0xfc }; - -STATIC uint8_t local_addr[6] = {0}; -STATIC uint8_t static_address[6] = {0}; -STATIC volatile bool have_addr = false; -STATIC bool using_static_address = false; - -STATIC btstack_packet_callback_registration_t hci_event_callback_registration; - -STATIC void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { - (void)channel; - (void)size; - if (packet_type != HCI_EVENT_PACKET) { - return; - } - switch (hci_event_packet_get_type(packet)) { - case BTSTACK_EVENT_STATE: - if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING) { - return; - } - gap_local_bd_addr(local_addr); - if (using_static_address) { - memcpy(local_addr, static_address, sizeof(local_addr)); - } - have_addr = true; - break; - case HCI_EVENT_COMMAND_COMPLETE: - if (memcmp(packet, read_static_address_command_complete_prefix, sizeof(read_static_address_command_complete_prefix)) == 0) { - reverse_48(&packet[7], static_address); - gap_random_address_set(static_address); - using_static_address = true; - have_addr = true; - } - break; - default: - break; - } -} - -// The IRQ functionality in btstack_run_loop_embedded.c is not used, so the -// following three functions are empty. - -void hal_cpu_disable_irqs(void) { -} - -void hal_cpu_enable_irqs(void) { -} - -void hal_cpu_enable_irqs_and_sleep(void) { -} - -uint32_t hal_time_ms(void) { - return mp_hal_ticks_ms(); -} - -void mp_bluetooth_btstack_port_init(void) { - static bool run_loop_init = false; - if (!run_loop_init) { - run_loop_init = true; - btstack_run_loop_init(btstack_run_loop_embedded_get_instance()); - } else { - btstack_run_loop_embedded_get_instance()->init(); - } - +void mp_bluetooth_btstack_port_init_usb(void) { // MICROPYBTUSB can be a ':'' or '-' separated port list. char *path = getenv("MICROPYBTUSB"); if (path != NULL) { @@ -128,11 +67,7 @@ void mp_bluetooth_btstack_port_init(void) { hci_transport_usb_set_path(usb_path_len, usb_path); } - // hci_dump_open(NULL, HCI_DUMP_STDOUT); hci_init(hci_transport_usb_instance(), NULL); - - hci_event_callback_registration.callback = &packet_handler; - hci_add_event_handler(&hci_event_callback_registration); } STATIC pthread_t bstack_thread_id; @@ -142,9 +77,12 @@ void mp_bluetooth_btstack_port_deinit(void) { // Wait for the poll loop to terminate when the state is set to OFF. pthread_join(bstack_thread_id, NULL); - have_addr = false; } + +// Provided by mpbstackport_common.c. +extern bool mp_bluetooth_hci_poll(void); + STATIC void *btstack_thread(void *arg) { (void)arg; hci_power_control(HCI_POWER_ON); @@ -155,19 +93,15 @@ STATIC void *btstack_thread(void *arg) { // in modbluetooth_btstack.c setting the state back to OFF. // Or, if a timeout results in it being set to TIMEOUT. - while (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_STARTING || mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE) { - // Pretend like we're running in IRQ context (i.e. other things can't be running at the same time). - mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); - btstack_run_loop_embedded_execute_once(); - MICROPY_END_ATOMIC_SECTION(atomic_state); + while (true) { + if (!mp_bluetooth_hci_poll()) { + break; + } // The USB transport schedules events to the run loop at 1ms intervals, // and the implementation currently polls rather than selects. usleep(USB_POLL_INTERVAL_US); } - - hci_close(); - return NULL; } @@ -179,13 +113,4 @@ void mp_bluetooth_btstack_port_start(void) { pthread_create(&bstack_thread_id, &attr, &btstack_thread, NULL); } -void mp_hal_get_mac(int idx, uint8_t buf[6]) { - if (idx == MP_HAL_MAC_BDADDR) { - if (!have_addr) { - mp_raise_OSError(MP_ENODEV); - } - memcpy(buf, local_addr, sizeof(local_addr)); - } -} - -#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_USB diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index 08605842fd..9e24ab10cd 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -309,9 +309,11 @@ void mp_unix_mark_exec(void); #define MP_STATE_PORT MP_STATE_VM -#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK +#if MICROPY_PY_BLUETOOTH +#if MICROPY_BLUETOOTH_BTSTACK struct _mp_bluetooth_btstack_root_pointers_t; #define MICROPY_BLUETOOTH_ROOT_POINTERS struct _mp_bluetooth_btstack_root_pointers_t *bluetooth_btstack_root_pointers; +#endif #else #define MICROPY_BLUETOOTH_ROOT_POINTERS #endif