From 73df6b86bad751c2937c79677500b79c76002db1 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 22 Jul 2022 22:26:45 +1000 Subject: [PATCH 01/11] extmod/modbluetooth: Remove non-sync event support (via ringbuf). This ringbuffer-based approach was the original implementation before we had a way to support running events synchronously from the BLE stack. Zephyr is the last remaining port using this. ESP32 no longer requires it as of 5dbb822ca & e05d0a633. The Zephyr BLE support is about to be updated anyway. This commit also removes the MICROPY_PY_BLUETOOTH_ENTER/EXIT guards. This was previously a no-op on ports using sync events (e.g. stm32). Replaced with MICROPY_BEGIN_ATOMIC_SECTION where necessary it was used in other locations (e.g. unix mpbthciport.c). Also makes pairing & bonding enabled by default (this was effectively the current behavior as it was enabled when sync events were enabled). This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared --- docs/library/bluetooth.rst | 10 +- extmod/btstack/btstack.mk | 2 - extmod/modbluetooth.c | 479 --------------------------- extmod/modbluetooth.h | 20 +- extmod/nimble/modbluetooth_nimble.c | 2 - extmod/nimble/nimble.mk | 12 - extmod/nimble/nimble/nimble_npl_os.c | 14 +- ports/esp32/mpconfigport.h | 2 - ports/renesas-ra/pendsv.h | 3 - ports/rp2/CMakeLists.txt | 2 +- ports/stm32/mpbthciport.c | 12 - ports/stm32/mpconfigport.h | 11 - ports/stm32/pendsv.h | 3 - ports/stm32/rfcore.c | 4 - ports/unix/mpbthciport.c | 26 +- ports/zephyr/mpconfigport.h | 1 + 16 files changed, 19 insertions(+), 584 deletions(-) diff --git a/docs/library/bluetooth.rst b/docs/library/bluetooth.rst index 78cb4cc281486..94ac9ca9c7988 100644 --- a/docs/library/bluetooth.rst +++ b/docs/library/bluetooth.rst @@ -70,12 +70,6 @@ Configuration characteristic 0x2a00. This can be set at any time and changed multiple times. - - ``'rxbuf'``: Get/set the size in bytes of the internal buffer used to store - incoming events. This buffer is global to the entire BLE driver and so - handles incoming data for all events, including all characteristics. - Increasing this allows better handling of bursty incoming data (for - example scan results) and the ability to receive larger characteristic values. - - ``'mtu'``: Get/set the MTU that will be used during a ATT MTU exchange. The resulting MTU will be the minimum of this and the remote device's MTU. ATT MTU exchange will not happen automatically (unless the remote device initiates @@ -114,7 +108,7 @@ Event Handling **Note:** As an optimisation to prevent unnecessary allocations, the ``addr``, ``adv_data``, ``char_data``, ``notify_data``, and ``uuid`` entries in the tuples are read-only memoryview instances pointing to :mod:`bluetooth`'s internal - ringbuffer, and are only valid during the invocation of the IRQ handler + memory, and are only valid during the invocation of the IRQ handler function. If your program needs to save one of these values to access after the IRQ handler has returned (e.g. by saving it in a class instance or global variable), then it needs to take a copy of the data, either by using ``bytes()`` @@ -128,6 +122,8 @@ Event Handling used elsewhere in the program. And to print data from within the IRQ handler, ``print(bytes(addr))`` will be needed. + This is also true of the ``data`` tuple itself, do not store a reference to it. + An event handler showing all possible events:: def bt_irq(event, data): diff --git a/extmod/btstack/btstack.mk b/extmod/btstack/btstack.mk index 281d032ae1108..66ed00f043c73 100644 --- a/extmod/btstack/btstack.mk +++ b/extmod/btstack/btstack.mk @@ -16,8 +16,6 @@ INC += -I$(TOP)/$(BTSTACK_EXTMOD_DIR) CFLAGS_EXTMOD += -DMICROPY_BLUETOOTH_BTSTACK=1 CFLAGS_EXTMOD += -DMICROPY_BLUETOOTH_BTSTACK_CONFIG_FILE=$(MICROPY_BLUETOOTH_BTSTACK_CONFIG_FILE) -CFLAGS_EXTMOD += -DMICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS=1 -CFLAGS_EXTMOD += -DMICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING=1 BTSTACK_DIR = $(TOP)/lib/btstack diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 7b13f9556c4fe..40658ef65241b 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -44,14 +44,6 @@ #error modbluetooth requires MICROPY_ENABLE_SCHEDULER #endif -#if MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS && !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS -#error l2cap channels require synchronous modbluetooth events -#endif - -#if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING && !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS -#error pairing and bonding require synchronous modbluetooth events -#endif - // NimBLE can have fragmented data for GATTC events, so requires reassembly. #define MICROPY_PY_BLUETOOTH_USE_GATTC_EVENT_DATA_REASSEMBLY MICROPY_BLUETOOTH_NIMBLE @@ -59,27 +51,11 @@ #define MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN 5 -#if !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS -// This formula is intended to allow queuing the data of a large characteristic -// while still leaving room for a couple of normal (small, fixed size) events. -#define MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN(ringbuf_size) (MAX((int)((ringbuf_size) / 2), (int)(ringbuf_size) - 64)) -#endif - // bluetooth.BLE type. This is currently a singleton, however in the future // this could allow having multiple BLE interfaces on different UARTs. typedef struct { mp_obj_base_t base; mp_obj_t irq_handler; - #if !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS - bool irq_scheduled; - mp_obj_t irq_data_tuple; - uint8_t irq_data_addr_bytes[6]; - uint16_t irq_data_data_alloc; - mp_obj_array_t irq_data_addr; - mp_obj_array_t irq_data_data; - mp_obj_bluetooth_uuid_t irq_data_uuid; - ringbuf_t ringbuf; - #endif } mp_obj_bluetooth_ble_t; STATIC const mp_obj_type_t mp_type_bluetooth_ble; @@ -218,31 +194,6 @@ STATIC mp_int_t bluetooth_uuid_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bu return 0; } -#if !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS - -#if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT -STATIC void ringbuf_put_uuid(ringbuf_t *ringbuf, mp_obj_bluetooth_uuid_t *uuid) { - assert(ringbuf_free(ringbuf) >= (size_t)uuid->type + 1); - ringbuf_put(ringbuf, uuid->type); - for (int i = 0; i < uuid->type; ++i) { - ringbuf_put(ringbuf, uuid->data[i]); - } -} -#endif - -#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE -STATIC void ringbuf_get_uuid(ringbuf_t *ringbuf, mp_obj_bluetooth_uuid_t *uuid) { - assert(ringbuf_avail(ringbuf) >= 1); - uuid->type = ringbuf_get(ringbuf); - assert(ringbuf_avail(ringbuf) >= uuid->type); - for (int i = 0; i < uuid->type; ++i) { - uuid->data[i] = ringbuf_get(ringbuf); - } -} -#endif - -#endif // !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS - MP_DEFINE_CONST_OBJ_TYPE( mp_type_bluetooth_uuid, MP_QSTR_UUID, @@ -269,20 +220,6 @@ STATIC mp_obj_t bluetooth_ble_make_new(const mp_obj_type_t *type, size_t n_args, o->irq_handler = mp_const_none; - #if !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS - // Pre-allocate the event data tuple to prevent needing to allocate in the IRQ handler. - o->irq_data_tuple = mp_obj_new_tuple(MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN, NULL); - - // Pre-allocated buffers for address, payload and uuid. - mp_obj_memoryview_init(&o->irq_data_addr, 'B', 0, 0, o->irq_data_addr_bytes); - o->irq_data_data_alloc = MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN(MICROPY_PY_BLUETOOTH_RINGBUF_SIZE); - mp_obj_memoryview_init(&o->irq_data_data, 'B', 0, 0, m_new(uint8_t, o->irq_data_data_alloc)); - o->irq_data_uuid.base.type = &mp_type_bluetooth_uuid; - - // Allocate the default ringbuf. - ringbuf_alloc(&o->ringbuf, MICROPY_PY_BLUETOOTH_RINGBUF_SIZE); - #endif - MP_STATE_VM(bluetooth) = MP_OBJ_FROM_PTR(o); } return MP_STATE_VM(bluetooth); @@ -323,12 +260,6 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map mp_obj_t items[] = { MP_OBJ_NEW_SMALL_INT(addr_type), mp_obj_new_bytes(addr, MP_ARRAY_SIZE(addr)) }; return mp_obj_new_tuple(2, items); } - #if !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS - case MP_QSTR_rxbuf: { - mp_obj_bluetooth_ble_t *self = MP_OBJ_TO_PTR(args[0]); - return mp_obj_new_int(self->ringbuf.size); - } - #endif case MP_QSTR_mtu: return mp_obj_new_int(mp_bluetooth_get_preferred_mtu()); default: @@ -350,42 +281,6 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map bluetooth_handle_errno(mp_bluetooth_gap_set_device_name(bufinfo.buf, bufinfo.len)); break; } - #if !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS - case MP_QSTR_rxbuf: { - // Determine new buffer sizes - mp_int_t ringbuf_alloc = mp_obj_get_int(e->value); - if (ringbuf_alloc < 16 || ringbuf_alloc > 0xffff) { - mp_raise_ValueError(NULL); - } - size_t irq_data_alloc = MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN(ringbuf_alloc); - - // Allocate new buffers - uint8_t *ringbuf = m_new(uint8_t, ringbuf_alloc); - uint8_t *irq_data = m_new(uint8_t, irq_data_alloc); - - // Get old buffer sizes and pointers - mp_obj_bluetooth_ble_t *self = MP_OBJ_TO_PTR(args[0]); - uint8_t *old_ringbuf_buf = self->ringbuf.buf; - size_t old_ringbuf_alloc = self->ringbuf.size; - uint8_t *old_irq_data_buf = (uint8_t *)self->irq_data_data.items; - size_t old_irq_data_alloc = self->irq_data_data_alloc; - - // Atomically update the ringbuf and irq data - MICROPY_PY_BLUETOOTH_ENTER - self->ringbuf.size = ringbuf_alloc; - self->ringbuf.buf = ringbuf; - self->ringbuf.iget = 0; - self->ringbuf.iput = 0; - self->irq_data_data_alloc = irq_data_alloc; - self->irq_data_data.items = irq_data; - MICROPY_PY_BLUETOOTH_EXIT - - // Free old buffers - m_del(uint8_t, old_ringbuf_buf, old_ringbuf_alloc); - m_del(uint8_t, old_irq_data_buf, old_irq_data_alloc); - break; - } - #endif case MP_QSTR_mtu: { mp_int_t mtu = mp_obj_get_int(e->value); bluetooth_handle_errno(mp_bluetooth_set_preferred_mtu(mtu)); @@ -436,10 +331,8 @@ STATIC mp_obj_t bluetooth_ble_irq(mp_obj_t self_in, mp_obj_t handler_in) { } // Update the callback. - MICROPY_PY_BLUETOOTH_ENTER mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); o->irq_handler = handler_in; - MICROPY_PY_BLUETOOTH_EXIT return mp_const_none; } @@ -1010,135 +903,10 @@ const mp_obj_module_t mp_module_bluetooth = { // `ubluetooth` alias will continue to work. MP_REGISTER_EXTENSIBLE_MODULE(MP_QSTR_bluetooth, mp_module_bluetooth); -// Helpers - -#if !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS -STATIC void ringbuf_extract(ringbuf_t *ringbuf, mp_obj_tuple_t *data_tuple, size_t n_u16, size_t n_u8, mp_obj_array_t *bytes_addr, size_t n_i8, mp_obj_bluetooth_uuid_t *uuid, mp_obj_array_t *bytes_data) { - assert(ringbuf_avail(ringbuf) >= n_u16 * 2 + n_u8 + (bytes_addr ? 6 : 0) + n_i8 + (uuid ? 1 : 0) + (bytes_data ? 1 : 0)); - size_t j = 0; - - for (size_t i = 0; i < n_u16; ++i) { - data_tuple->items[j++] = MP_OBJ_NEW_SMALL_INT(ringbuf_get16(ringbuf)); - } - for (size_t i = 0; i < n_u8; ++i) { - data_tuple->items[j++] = MP_OBJ_NEW_SMALL_INT(ringbuf_get(ringbuf)); - } - if (bytes_addr) { - bytes_addr->len = 6; - for (size_t i = 0; i < bytes_addr->len; ++i) { - ((uint8_t *)bytes_addr->items)[i] = ringbuf_get(ringbuf); - } - data_tuple->items[j++] = MP_OBJ_FROM_PTR(bytes_addr); - } - for (size_t i = 0; i < n_i8; ++i) { - // Note the int8_t got packed into the ringbuf as a uint8_t. - data_tuple->items[j++] = MP_OBJ_NEW_SMALL_INT((int8_t)ringbuf_get(ringbuf)); - } - #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE - if (uuid) { - ringbuf_get_uuid(ringbuf, uuid); - data_tuple->items[j++] = MP_OBJ_FROM_PTR(uuid); - } - #endif - // The code that enqueues into the ringbuf should ensure that it doesn't - // put more than bt->irq_data_data_alloc bytes into the ringbuf, because - // that's what's available here. - if (bytes_data) { - bytes_data->len = ringbuf_get16(ringbuf); - for (size_t i = 0; i < bytes_data->len; ++i) { - ((uint8_t *)bytes_data->items)[i] = ringbuf_get(ringbuf); - } - data_tuple->items[j++] = MP_OBJ_FROM_PTR(bytes_data); - } - - data_tuple->len = j; -} - -STATIC mp_obj_t bluetooth_ble_invoke_irq(mp_obj_t none_in) { - (void)none_in; - // This is always executing in schedule context. - - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - o->irq_scheduled = false; - - for (;;) { - MICROPY_PY_BLUETOOTH_ENTER - - mp_int_t event = ringbuf_get(&o->ringbuf); - if (event < 0) { - // Nothing available in ringbuf. - MICROPY_PY_BLUETOOTH_EXIT - break; - } - - // Although we're in schedule context, this code still avoids using any allocations: - // - IRQs are disabled (to protect the ringbuf), and we need to avoid triggering GC - // - The user's handler might not alloc, so we shouldn't either. - - mp_obj_t handler = handler = o->irq_handler; - mp_obj_tuple_t *data_tuple = MP_OBJ_TO_PTR(o->irq_data_tuple); - - if (event == MP_BLUETOOTH_IRQ_CENTRAL_CONNECT || event == MP_BLUETOOTH_IRQ_PERIPHERAL_CONNECT || event == MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT || event == MP_BLUETOOTH_IRQ_PERIPHERAL_DISCONNECT) { - // conn_handle, addr_type, addr - ringbuf_extract(&o->ringbuf, data_tuple, 1, 1, &o->irq_data_addr, 0, NULL, NULL); - } else if (event == MP_BLUETOOTH_IRQ_CONNECTION_UPDATE) { - // conn_handle, conn_interval, conn_latency, supervision_timeout, status - ringbuf_extract(&o->ringbuf, data_tuple, 5, 0, NULL, 0, NULL, NULL); - } else if (event == MP_BLUETOOTH_IRQ_GATTS_WRITE) { - // conn_handle, value_handle - ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, NULL, NULL); - } else if (event == MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE) { - // conn_handle, value_handle, status - ringbuf_extract(&o->ringbuf, data_tuple, 2, 1, NULL, 0, NULL, NULL); - } else if (event == MP_BLUETOOTH_IRQ_MTU_EXCHANGED) { - // conn_handle, mtu - ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, NULL, NULL); - #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE - } else if (event == MP_BLUETOOTH_IRQ_SCAN_RESULT) { - // addr_type, addr, adv_type, rssi, adv_data - ringbuf_extract(&o->ringbuf, data_tuple, 0, 1, &o->irq_data_addr, 2, NULL, &o->irq_data_data); - } else if (event == MP_BLUETOOTH_IRQ_SCAN_DONE) { - // No params required. - data_tuple->len = 0; - #endif - #if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT - } else if (event == MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT) { - // conn_handle, start_handle, end_handle, uuid - ringbuf_extract(&o->ringbuf, data_tuple, 3, 0, NULL, 0, &o->irq_data_uuid, NULL); - } else if (event == MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT) { - // conn_handle, end_handle, value_handle, properties, uuid - ringbuf_extract(&o->ringbuf, data_tuple, 3, 1, NULL, 0, &o->irq_data_uuid, NULL); - } else if (event == MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT) { - // conn_handle, handle, uuid - ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, &o->irq_data_uuid, NULL); - } else if (event == MP_BLUETOOTH_IRQ_GATTC_SERVICE_DONE || event == MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_DONE || event == MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_DONE) { - // conn_handle, status - ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, NULL, NULL); - } else if (event == MP_BLUETOOTH_IRQ_GATTC_READ_RESULT || event == MP_BLUETOOTH_IRQ_GATTC_NOTIFY || event == MP_BLUETOOTH_IRQ_GATTC_INDICATE) { - // conn_handle, value_handle, data - ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, NULL, &o->irq_data_data); - } else if (event == MP_BLUETOOTH_IRQ_GATTC_READ_DONE || event == MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE) { - // conn_handle, value_handle, status - ringbuf_extract(&o->ringbuf, data_tuple, 3, 0, NULL, 0, NULL, NULL); - #endif // MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT - } - - MICROPY_PY_BLUETOOTH_EXIT - - mp_call_function_2(handler, MP_OBJ_NEW_SMALL_INT(event), MP_OBJ_FROM_PTR(data_tuple)); - } - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(bluetooth_ble_invoke_irq_obj, bluetooth_ble_invoke_irq); -#endif // !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS - // ---------------------------------------------------------------------------- // Port API // ---------------------------------------------------------------------------- -#if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS - STATIC mp_obj_t invoke_irq_handler_run(uint16_t event, const mp_int_t *numeric, size_t n_unsigned, size_t n_signed, const uint8_t *addr, @@ -1458,245 +1226,6 @@ void mp_bluetooth_gattc_on_read_write_status(uint8_t event, uint16_t conn_handle #endif // MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT -#else // !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS -// Callbacks are called in interrupt context (i.e. can't allocate), so we need to push the data -// into the ringbuf and schedule the callback via mp_sched_schedule. - -STATIC bool enqueue_irq(mp_obj_bluetooth_ble_t *o, size_t len, uint8_t event) { - if (!o || o->irq_handler == mp_const_none) { - return false; - } - - // Check if there is enough room for . - if (ringbuf_free(&o->ringbuf) < len + 1) { - // Ringbuffer doesn't have room (and is therefore non-empty). - - // If this is another scan result, or the front of the ringbuffer isn't a scan result, then nothing to do. - if (event == MP_BLUETOOTH_IRQ_SCAN_RESULT || ringbuf_peek(&o->ringbuf) != MP_BLUETOOTH_IRQ_SCAN_RESULT) { - return false; - } - - // Front of the queue is a scan result, remove it. - - // event, addr_type, addr, adv_type, rssi - int n = 1 + 1 + 6 + 1 + 1; - for (int i = 0; i < n; ++i) { - ringbuf_get(&o->ringbuf); - } - // adv_data - n = ringbuf_get(&o->ringbuf); - for (int i = 0; i < n; ++i) { - ringbuf_get(&o->ringbuf); - } - } - - // Append this event, the caller will then append the arguments. - ringbuf_put(&o->ringbuf, event); - return true; -} - -// Must hold the atomic section before calling this (MICROPY_PY_BLUETOOTH_ENTER). -STATIC void schedule_ringbuf(mp_uint_t atomic_state) { - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if (!o->irq_scheduled) { - o->irq_scheduled = true; - MICROPY_PY_BLUETOOTH_EXIT - mp_sched_schedule(MP_OBJ_FROM_PTR(&bluetooth_ble_invoke_irq_obj), mp_const_none); - } else { - MICROPY_PY_BLUETOOTH_EXIT - } -} - -void mp_bluetooth_gap_on_connected_disconnected(uint8_t event, uint16_t conn_handle, uint8_t addr_type, const uint8_t *addr) { - MICROPY_PY_BLUETOOTH_ENTER - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if (enqueue_irq(o, 2 + 1 + 6, event)) { - ringbuf_put16(&o->ringbuf, conn_handle); - ringbuf_put(&o->ringbuf, addr_type); - for (int i = 0; i < 6; ++i) { - ringbuf_put(&o->ringbuf, addr[i]); - } - } - schedule_ringbuf(atomic_state); -} - -void mp_bluetooth_gap_on_connection_update(uint16_t conn_handle, uint16_t conn_interval, uint16_t conn_latency, uint16_t supervision_timeout, uint16_t status) { - MICROPY_PY_BLUETOOTH_ENTER - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if (enqueue_irq(o, 2 + 2 + 2 + 2 + 2, MP_BLUETOOTH_IRQ_CONNECTION_UPDATE)) { - ringbuf_put16(&o->ringbuf, conn_handle); - ringbuf_put16(&o->ringbuf, conn_interval); - ringbuf_put16(&o->ringbuf, conn_latency); - ringbuf_put16(&o->ringbuf, supervision_timeout); - ringbuf_put16(&o->ringbuf, status); - } - schedule_ringbuf(atomic_state); -} - -void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle) { - MICROPY_PY_BLUETOOTH_ENTER - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if (enqueue_irq(o, 2 + 2, MP_BLUETOOTH_IRQ_GATTS_WRITE)) { - ringbuf_put16(&o->ringbuf, conn_handle); - ringbuf_put16(&o->ringbuf, value_handle); - } - schedule_ringbuf(atomic_state); -} - -void mp_bluetooth_gatts_on_indicate_complete(uint16_t conn_handle, uint16_t value_handle, uint8_t status) { - MICROPY_PY_BLUETOOTH_ENTER - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if (enqueue_irq(o, 2 + 2 + 1, MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE)) { - ringbuf_put16(&o->ringbuf, conn_handle); - ringbuf_put16(&o->ringbuf, value_handle); - ringbuf_put(&o->ringbuf, status); - } - schedule_ringbuf(atomic_state); -} - -mp_int_t mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle) { - (void)conn_handle; - (void)value_handle; - // This must be handled synchronously and therefore cannot implemented with the ringbuffer. - return MP_BLUETOOTH_GATTS_NO_ERROR; -} - -void mp_bluetooth_gatts_on_mtu_exchanged(uint16_t conn_handle, uint16_t value) { - MICROPY_PY_BLUETOOTH_ENTER - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if (enqueue_irq(o, 2 + 2, MP_BLUETOOTH_IRQ_MTU_EXCHANGED)) { - ringbuf_put16(&o->ringbuf, conn_handle); - ringbuf_put16(&o->ringbuf, value); - } - schedule_ringbuf(atomic_state); -} - -#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE -void mp_bluetooth_gap_on_scan_complete(void) { - MICROPY_PY_BLUETOOTH_ENTER - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if (enqueue_irq(o, 0, MP_BLUETOOTH_IRQ_SCAN_DONE)) { - } - schedule_ringbuf(atomic_state); -} - -void mp_bluetooth_gap_on_scan_result(uint8_t addr_type, const uint8_t *addr, uint8_t adv_type, const int8_t rssi, const uint8_t *data, uint16_t data_len) { - MICROPY_PY_BLUETOOTH_ENTER - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - data_len = MIN(o->irq_data_data_alloc, data_len); - if (enqueue_irq(o, 1 + 6 + 1 + 1 + 2 + data_len, MP_BLUETOOTH_IRQ_SCAN_RESULT)) { - ringbuf_put(&o->ringbuf, addr_type); - for (int i = 0; i < 6; ++i) { - ringbuf_put(&o->ringbuf, addr[i]); - } - // The adv_type will get extracted as an int8_t but that's ok because valid values are 0x00-0x04. - ringbuf_put(&o->ringbuf, adv_type); - // Note conversion of int8_t rssi to uint8_t. Must un-convert on the way out. - ringbuf_put(&o->ringbuf, (uint8_t)rssi); - // Length field is 16-bit. - data_len = MIN(UINT16_MAX, data_len); - ringbuf_put16(&o->ringbuf, data_len); - for (size_t i = 0; i < data_len; ++i) { - ringbuf_put(&o->ringbuf, data[i]); - } - } - schedule_ringbuf(atomic_state); -} -#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE - -#if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT -void mp_bluetooth_gattc_on_primary_service_result(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle, mp_obj_bluetooth_uuid_t *service_uuid) { - MICROPY_PY_BLUETOOTH_ENTER - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if (enqueue_irq(o, 2 + 2 + 2 + 1 + service_uuid->type, MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT)) { - ringbuf_put16(&o->ringbuf, conn_handle); - ringbuf_put16(&o->ringbuf, start_handle); - ringbuf_put16(&o->ringbuf, end_handle); - ringbuf_put_uuid(&o->ringbuf, service_uuid); - } - schedule_ringbuf(atomic_state); -} - -void mp_bluetooth_gattc_on_characteristic_result(uint16_t conn_handle, uint16_t value_handle, uint16_t end_handle, uint8_t properties, mp_obj_bluetooth_uuid_t *characteristic_uuid) { - MICROPY_PY_BLUETOOTH_ENTER - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if (enqueue_irq(o, 2 + 2 + 2 + 1 + characteristic_uuid->type, MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT)) { - ringbuf_put16(&o->ringbuf, conn_handle); - // Note: "end_handle" replaces "def_handle" from the original version of this event. - ringbuf_put16(&o->ringbuf, end_handle); - ringbuf_put16(&o->ringbuf, value_handle); - ringbuf_put(&o->ringbuf, properties); - ringbuf_put_uuid(&o->ringbuf, characteristic_uuid); - } - schedule_ringbuf(atomic_state); -} - -void mp_bluetooth_gattc_on_descriptor_result(uint16_t conn_handle, uint16_t handle, mp_obj_bluetooth_uuid_t *descriptor_uuid) { - MICROPY_PY_BLUETOOTH_ENTER - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if (enqueue_irq(o, 2 + 2 + 1 + descriptor_uuid->type, MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT)) { - ringbuf_put16(&o->ringbuf, conn_handle); - ringbuf_put16(&o->ringbuf, handle); - ringbuf_put_uuid(&o->ringbuf, descriptor_uuid); - } - schedule_ringbuf(atomic_state); -} - -void mp_bluetooth_gattc_on_discover_complete(uint8_t event, uint16_t conn_handle, uint16_t status) { - MICROPY_PY_BLUETOOTH_ENTER - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if (enqueue_irq(o, 2 + 2, event)) { - ringbuf_put16(&o->ringbuf, conn_handle); - ringbuf_put16(&o->ringbuf, status); - } - schedule_ringbuf(atomic_state); -} - -void mp_bluetooth_gattc_on_data_available(uint8_t event, uint16_t conn_handle, uint16_t value_handle, const uint8_t **data, uint16_t *data_len, size_t num) { - MICROPY_PY_BLUETOOTH_ENTER - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - - // Get the total length of the fragmented buffers. - uint16_t total_len = 0; - for (size_t i = 0; i < num; ++i) { - total_len += data_len[i]; - } - - // Truncate the data at what we'll be able to pass to Python. - total_len = MIN(o->irq_data_data_alloc, total_len); - - if (enqueue_irq(o, 2 + 2 + 2 + total_len, event)) { - ringbuf_put16(&o->ringbuf, conn_handle); - ringbuf_put16(&o->ringbuf, value_handle); - - ringbuf_put16(&o->ringbuf, total_len); - - // Copy total_len from the fragments to the ringbuffer. - uint16_t copied_bytes = 0; - for (size_t i = 0; i < num; ++i) { - for (size_t j = 0; i < data_len[i] && copied_bytes < total_len; ++j) { - ringbuf_put(&o->ringbuf, data[i][j]); - ++copied_bytes; - } - } - } - schedule_ringbuf(atomic_state); -} - -void mp_bluetooth_gattc_on_read_write_status(uint8_t event, uint16_t conn_handle, uint16_t value_handle, uint16_t status) { - MICROPY_PY_BLUETOOTH_ENTER - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if (enqueue_irq(o, 2 + 2 + 2, event)) { - ringbuf_put16(&o->ringbuf, conn_handle); - ringbuf_put16(&o->ringbuf, value_handle); - ringbuf_put16(&o->ringbuf, status); - } - schedule_ringbuf(atomic_state); -} -#endif // MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT - -#endif // MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS - // ---------------------------------------------------------------------------- // GATTS DB // ---------------------------------------------------------------------------- @@ -1720,7 +1249,6 @@ mp_bluetooth_gatts_db_entry_t *mp_bluetooth_gatts_db_lookup(mp_gatts_db_t db, ui } int mp_bluetooth_gatts_db_read(mp_gatts_db_t db, uint16_t handle, const uint8_t **value, size_t *value_len) { - MICROPY_PY_BLUETOOTH_ENTER mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle); if (entry) { *value = entry->data; @@ -1729,12 +1257,10 @@ int mp_bluetooth_gatts_db_read(mp_gatts_db_t db, uint16_t handle, const uint8_t entry->data_len = 0; } } - MICROPY_PY_BLUETOOTH_EXIT return entry ? 0 : MP_EINVAL; } int mp_bluetooth_gatts_db_write(mp_gatts_db_t db, uint16_t handle, const uint8_t *value, size_t value_len) { - MICROPY_PY_BLUETOOTH_ENTER mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle); if (entry) { if (value_len > entry->data_alloc) { @@ -1743,7 +1269,6 @@ int mp_bluetooth_gatts_db_write(mp_gatts_db_t db, uint16_t handle, const uint8_t entry->data = data; entry->data_alloc = value_len; } else { - MICROPY_PY_BLUETOOTH_EXIT return MP_ENOMEM; } } @@ -1751,12 +1276,10 @@ int mp_bluetooth_gatts_db_write(mp_gatts_db_t db, uint16_t handle, const uint8_t memcpy(entry->data, value, value_len); entry->data_len = value_len; } - MICROPY_PY_BLUETOOTH_EXIT return entry ? 0 : MP_EINVAL; } int mp_bluetooth_gatts_db_resize(mp_gatts_db_t db, uint16_t handle, size_t len, bool append) { - MICROPY_PY_BLUETOOTH_ENTER mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle); if (entry) { uint8_t *data = m_renew_maybe(uint8_t, entry->data, entry->data_alloc, len, true); @@ -1766,11 +1289,9 @@ int mp_bluetooth_gatts_db_resize(mp_gatts_db_t db, uint16_t handle, size_t len, entry->data_len = 0; entry->append = append; } else { - MICROPY_PY_BLUETOOTH_EXIT return MP_ENOMEM; } } - MICROPY_PY_BLUETOOTH_EXIT return entry ? 0 : MP_EINVAL; } diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 630a2705231e2..fc8427f1e99f2 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -45,25 +45,18 @@ #ifndef MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT // Enable the client by default if we're enabling central mode. It's possible -// to enable client without central though. +// to enable client without central though (e.g. non-connecting scanner). #define MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT (MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE) #endif -#ifndef MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS -// This can be enabled if the BLE stack runs entirely in scheduler context -// and therefore is able to call directly into the VM to run Python callbacks. -#define MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS (0) -#endif - // A port can optionally enable support for L2CAP "Connection Oriented Channels". #ifndef MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS #define MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS (0) #endif -// A port can optionally enable support for pairing and bonding. -// Requires MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS. +// Pairing and bonding enabled by default, but can be disabled by a port. #ifndef MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING -#define MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING (0) +#define MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING (1) #endif // Optionally enable support for the `hci_cmd` function allowing @@ -72,13 +65,6 @@ #define MICROPY_PY_BLUETOOTH_ENABLE_HCI_CMD (0) #endif -// This is used to protect the ringbuffer. -// A port may no-op this if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS is enabled. -#ifndef MICROPY_PY_BLUETOOTH_ENTER -#define MICROPY_PY_BLUETOOTH_ENTER mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); -#define MICROPY_PY_BLUETOOTH_EXIT MICROPY_END_ATOMIC_SECTION(atomic_state); -#endif - // Common constants. #ifndef MP_BLUETOOTH_DEFAULT_ATTR_LEN #define MP_BLUETOOTH_DEFAULT_ATTR_LEN (20) diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index e23ffbf0f9814..a799fc8384532 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -1832,7 +1832,6 @@ int mp_bluetooth_l2cap_recvinto(uint16_t conn_handle, uint16_t cid, uint8_t *buf return MP_EINVAL; } - MICROPY_PY_BLUETOOTH_ENTER if (chan->rx_pending) { size_t avail = OS_MBUF_PKTLEN(chan->rx_pending); @@ -1877,7 +1876,6 @@ int mp_bluetooth_l2cap_recvinto(uint16_t conn_handle, uint16_t cid, uint8_t *buf *len = 0; } - MICROPY_PY_BLUETOOTH_EXIT return 0; } diff --git a/extmod/nimble/nimble.mk b/extmod/nimble/nimble.mk index fc1709f0e50ab..dddb3e1df5284 100644 --- a/extmod/nimble/nimble.mk +++ b/extmod/nimble/nimble.mk @@ -19,18 +19,6 @@ ifeq ($(MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY),0) GIT_SUBMODULES += lib/mynewt-nimble -# On all ports where we provide the full implementation (i.e. not just -# bindings like on ESP32), then we don't need to use the ringbuffer. In this -# case, all NimBLE events are run by the MicroPython scheduler. On Unix, the -# scheduler is also responsible for polling the UART, whereas on STM32 the -# UART is also polled by the RX IRQ. -CFLAGS_EXTMOD += -DMICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS=1 - -# Without the ringbuffer, and with the full implementation, we can also -# enable pairing and bonding. This requires both synchronous events and -# some customisation of the key store. -CFLAGS_EXTMOD += -DMICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING=1 - NIMBLE_LIB_DIR = lib/mynewt-nimble SRC_THIRDPARTY_C += $(addprefix $(NIMBLE_LIB_DIR)/, \ diff --git a/extmod/nimble/nimble/nimble_npl_os.c b/extmod/nimble/nimble/nimble_npl_os.c index b68957fabf385..ec703b77aed3b 100644 --- a/extmod/nimble/nimble/nimble_npl_os.c +++ b/extmod/nimble/nimble/nimble_npl_os.c @@ -502,19 +502,17 @@ void ble_npl_time_delay(ble_npl_time_t ticks) { // This is used anywhere NimBLE modifies global data structures. -// Currently all NimBLE code is invoked by the scheduler so there should be no -// races, so on STM32 MICROPY_PY_BLUETOOTH_ENTER/MICROPY_PY_BLUETOOTH_EXIT are -// no-ops. However, in the future we may wish to make HCI UART processing -// happen asynchronously (e.g. on RX IRQ), so the port can implement these -// macros accordingly. +// Currently all NimBLE code is invoked by the scheduler so there is no +// concurrency. In the future we may wish to make HCI UART processing happen +// asynchronously (e.g. on RX IRQ), so the port can implement these macros +// accordingly. uint32_t ble_npl_hw_enter_critical(void) { DEBUG_CRIT_printf("ble_npl_hw_enter_critical()\n"); - MICROPY_PY_BLUETOOTH_ENTER - return atomic_state; + return 0; } void ble_npl_hw_exit_critical(uint32_t atomic_state) { - MICROPY_PY_BLUETOOTH_EXIT + (void)atomic_state; DEBUG_CRIT_printf("ble_npl_hw_exit_critical(%u)\n", (uint)atomic_state); } diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index ae73e02e41a53..b4c92cbb46b11 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -77,11 +77,9 @@ #endif #ifndef MICROPY_PY_BLUETOOTH #define MICROPY_PY_BLUETOOTH (1) -#define MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS (1) #define MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS_WITH_INTERLOCK (1) #define MICROPY_PY_BLUETOOTH_SYNC_EVENT_STACK_SIZE (CONFIG_BT_NIMBLE_TASK_STACK_SIZE) #define MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE (1) -#define MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING (1) #define MICROPY_BLUETOOTH_NIMBLE (1) #define MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY (1) #endif diff --git a/ports/renesas-ra/pendsv.h b/ports/renesas-ra/pendsv.h index 9d7c3d94135d6..4d95a2ec6fafd 100644 --- a/ports/renesas-ra/pendsv.h +++ b/ports/renesas-ra/pendsv.h @@ -36,9 +36,6 @@ enum { PENDSV_DISPATCH_CYW43, #endif #endif - #if MICROPY_PY_BLUETOOTH && !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS - PENDSV_DISPATCH_BLUETOOTH_HCI, - #endif MICROPY_BOARD_PENDSV_ENTRIES PENDSV_DISPATCH_MAX }; diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 7718697b438f8..8a3dd80e8dd9a 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -217,8 +217,8 @@ if(MICROPY_PY_BLUETOOTH) 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 + MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS=1 ) endif() diff --git a/ports/stm32/mpbthciport.c b/ports/stm32/mpbthciport.c index fe061a1240885..7c7031bf2f825 100644 --- a/ports/stm32/mpbthciport.c +++ b/ports/stm32/mpbthciport.c @@ -65,8 +65,6 @@ void mp_bluetooth_hci_poll_in_ms_default(uint32_t ms) { soft_timer_reinsert(&mp_bluetooth_hci_soft_timer, ms); } -#if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS - STATIC mp_sched_node_t mp_bluetooth_hci_sched_node; // For synchronous mode, we run all BLE stack code inside a scheduled task. @@ -84,14 +82,6 @@ void mp_bluetooth_hci_poll_now_default(void) { mp_sched_schedule_node(&mp_bluetooth_hci_sched_node, run_events_scheduled_task); } -#else // !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS - -void mp_bluetooth_hci_poll_now_default(void) { - pendsv_schedule_dispatch(PENDSV_DISPATCH_BLUETOOTH_HCI, mp_bluetooth_hci_poll); -} - -#endif - #if defined(STM32WB) /******************************************************************************/ @@ -126,9 +116,7 @@ int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate) { } int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len) { - MICROPY_PY_BLUETOOTH_ENTER rfcore_ble_hci_cmd(len, (const uint8_t *)buf); - MICROPY_PY_BLUETOOTH_EXIT return 0; } diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 2d2d81486bbb7..832a34e45b037 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -287,17 +287,6 @@ static inline mp_uint_t disable_irq(void) { #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-stm32" #endif -#if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS -// 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; -#else -// When async events are enabled, need to prevent PendSV execution racing with -// scheduler execution. -#define MICROPY_PY_BLUETOOTH_ENTER MICROPY_PY_PENDSV_ENTER -#define MICROPY_PY_BLUETOOTH_EXIT MICROPY_PY_PENDSV_EXIT -#endif - #if defined(STM32WB) #define MICROPY_PY_BLUETOOTH_HCI_READ_MODE MICROPY_PY_BLUETOOTH_HCI_READ_MODE_PACKET #else diff --git a/ports/stm32/pendsv.h b/ports/stm32/pendsv.h index f97581e99a308..ee1a1f56499c0 100644 --- a/ports/stm32/pendsv.h +++ b/ports/stm32/pendsv.h @@ -39,9 +39,6 @@ enum { PENDSV_DISPATCH_WIZNET, #endif #endif - #if MICROPY_PY_BLUETOOTH && !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS - PENDSV_DISPATCH_BLUETOOTH_HCI, - #endif MICROPY_BOARD_PENDSV_ENTRIES PENDSV_DISPATCH_MAX }; diff --git a/ports/stm32/rfcore.c b/ports/stm32/rfcore.c index 2e660d6d9e17e..30c097fd74085 100644 --- a/ports/stm32/rfcore.c +++ b/ports/stm32/rfcore.c @@ -49,10 +49,6 @@ #error "STM32WB must use NimBLE." #endif -#if !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS -#error "STM32WB must use synchronous BLE events." -#endif - #endif #define DEBUG_printf(...) // printf("rfcore: " __VA_ARGS__) diff --git a/ports/unix/mpbthciport.c b/ports/unix/mpbthciport.c index 8813ce147c1ee..2477421e28479 100644 --- a/ports/unix/mpbthciport.c +++ b/ports/unix/mpbthciport.c @@ -59,8 +59,6 @@ STATIC int uart_fd = -1; // Must be provided by the stack bindings (e.g. mpnimbleport.c or mpbtstackport.c). extern bool mp_bluetooth_hci_poll(void); -#if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS - // For synchronous mode, we run all BLE stack code inside a scheduled task. // This task is scheduled periodically (every 1ms) by a background thread. @@ -72,16 +70,14 @@ STATIC volatile bool events_task_is_scheduled = false; STATIC mp_obj_t run_events_scheduled_task(mp_obj_t none_in) { (void)none_in; - MICROPY_PY_BLUETOOTH_ENTER - events_task_is_scheduled = false; - MICROPY_PY_BLUETOOTH_EXIT + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + events_task_is_scheduled = false; + MICROPY_END_ATOMIC_SECTION(atomic_state); mp_bluetooth_hci_poll(); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(run_events_scheduled_task_obj, run_events_scheduled_task); -#endif // MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS - STATIC const useconds_t UART_POLL_INTERVAL_US = 1000; STATIC pthread_t hci_poll_thread_id; @@ -90,29 +86,17 @@ STATIC void *hci_poll_thread(void *arg) { DEBUG_printf("hci_poll_thread: starting\n"); - #if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS - events_task_is_scheduled = false; while (mp_bluetooth_hci_active()) { - MICROPY_PY_BLUETOOTH_ENTER + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); if (!events_task_is_scheduled) { events_task_is_scheduled = mp_sched_schedule(MP_OBJ_FROM_PTR(&run_events_scheduled_task_obj), mp_const_none); } - MICROPY_PY_BLUETOOTH_EXIT - usleep(UART_POLL_INTERVAL_US); - } - - #else - - // In asynchronous (i.e. ringbuffer) mode, we run the BLE stack directly from the thread. - // This will return false when the stack is shutdown. - while (mp_bluetooth_hci_poll()) { + MICROPY_END_ATOMIC_SECTION(atomic_state); usleep(UART_POLL_INTERVAL_US); } - #endif - DEBUG_printf("hci_poll_thread: stopped\n"); return NULL; diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index 2f3e314db6e8b..77ba437ae1a6e 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -81,6 +81,7 @@ #define MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE (1) #endif #define MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT (0) +#define MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING (0) #endif #define MICROPY_PY_BINASCII (1) #define MICROPY_PY_HASHLIB (1) From c789daf10273b516231ac2347aaf00269a11d084 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 19 Sep 2023 22:37:56 +1000 Subject: [PATCH 02/11] py/mpthread: Add support for a recursive mutex. Also provide a no-op mutex when threading is disabled. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared --- ports/unix/mpthreadport.c | 7 +++++++ py/mpthread.h | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/ports/unix/mpthreadport.c b/ports/unix/mpthreadport.c index 2190bf4ad1ba3..4fed863598c0a 100644 --- a/ports/unix/mpthreadport.c +++ b/ports/unix/mpthreadport.c @@ -298,6 +298,13 @@ void mp_thread_mutex_init(mp_thread_mutex_t *mutex) { pthread_mutex_init(mutex, NULL); } +void mp_thread_mutex_init_recursive(mp_thread_mutex_t *mutex) { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(mutex, &attr); +} + int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait) { int ret; if (wait) { diff --git a/py/mpthread.h b/py/mpthread.h index f335cc02911fc..e92078b9d331b 100644 --- a/py/mpthread.h +++ b/py/mpthread.h @@ -44,10 +44,29 @@ mp_uint_t mp_thread_create(void *(*entry)(void *), void *arg, size_t *stack_size mp_uint_t mp_thread_get_id(void); void mp_thread_start(void); void mp_thread_finish(void); + void mp_thread_mutex_init(mp_thread_mutex_t *mutex); +void mp_thread_mutex_init_recursive(mp_thread_mutex_t *mutex); int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait); void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex); +#else // !MICROPY_PY_THREAD + +// When threading is disabled, provide no-op implementation of mp_thread_mutex_t. + +typedef struct { +} mp_thread_mutex_t; + +static inline void mp_thread_mutex_init(mp_thread_mutex_t *mutex) { +} +static inline void mp_thread_mutex_init_recursive(mp_thread_mutex_t *mutex) { +} +static inline int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait) { + return 0; +} +static inline void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex) { +} + #endif // MICROPY_PY_THREAD #if MICROPY_PY_THREAD && MICROPY_PY_THREAD_GIL From 94094f02f1bafc53accf2cc4b10264b5a5982d4f Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Thu, 21 Sep 2023 15:47:34 +1000 Subject: [PATCH 03/11] py/mpthread: Add a semaphore primitive. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared --- ports/unix/mpthreadport.c | 29 +++++++++++++++++++++++++++ ports/unix/mpthreadport.h | 2 ++ py/mpthread.c | 0 py/mpthread.h | 41 +++++++++++++++++++++++++++++++++++++-- 4 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 py/mpthread.c diff --git a/ports/unix/mpthreadport.c b/ports/unix/mpthreadport.c index 4fed863598c0a..a28b951b9ed37 100644 --- a/ports/unix/mpthreadport.c +++ b/ports/unix/mpthreadport.c @@ -328,6 +328,35 @@ void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex) { // TODO check return value } +void mp_thread_sem_init(mp_thread_sem_t *sem, mp_uint_t value) { + sem_init(sem, 0, value); +} + +bool mp_thread_sem_wait(mp_thread_sem_t *sem, bool wait) { + int ret; + do { + if (wait) { + ret = sem_wait(sem); + } else { + ret = sem_trywait(sem); + } + } while (ret == EINTR); + return ret == 0; +} + +void mp_thread_sem_post(mp_thread_sem_t *sem) { + sem_post(sem); +} + +int mp_thread_sem_value(mp_thread_sem_t *sem) { + int val; + int ret = sem_getvalue(sem, &val); + (void)ret; + assert(ret == 0); + val = MAX(0, val); + return val; +} + #endif // MICROPY_PY_THREAD // this is used even when MICROPY_PY_THREAD is disabled diff --git a/ports/unix/mpthreadport.h b/ports/unix/mpthreadport.h index b365f200edf97..a04323e447f27 100644 --- a/ports/unix/mpthreadport.h +++ b/ports/unix/mpthreadport.h @@ -25,9 +25,11 @@ */ #include +#include #include typedef pthread_mutex_t mp_thread_mutex_t; +typedef sem_t mp_thread_sem_t; void mp_thread_init(void); void mp_thread_deinit(void); diff --git a/py/mpthread.c b/py/mpthread.c new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/py/mpthread.h b/py/mpthread.h index e92078b9d331b..1e2585c1d7e9d 100644 --- a/py/mpthread.h +++ b/py/mpthread.h @@ -26,6 +26,8 @@ #ifndef MICROPY_INCLUDED_PY_MPTHREAD_H #define MICROPY_INCLUDED_PY_MPTHREAD_H +#include + #include "py/mpconfig.h" #if MICROPY_PY_THREAD @@ -50,9 +52,15 @@ void mp_thread_mutex_init_recursive(mp_thread_mutex_t *mutex); int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait); void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex); +void mp_thread_sem_init(mp_thread_sem_t *sem, mp_uint_t value); +bool mp_thread_sem_wait(mp_thread_sem_t *sem, bool wait); +void mp_thread_sem_post(mp_thread_sem_t *sem); +int mp_thread_sem_value(mp_thread_sem_t *sem); + #else // !MICROPY_PY_THREAD -// When threading is disabled, provide no-op implementation of mp_thread_mutex_t. +// When threading is disabled, provide no-op implementation of +// mp_thread_mutex_t and mp_thread_sem_t. typedef struct { } mp_thread_mutex_t; @@ -62,11 +70,40 @@ static inline void mp_thread_mutex_init(mp_thread_mutex_t *mutex) { static inline void mp_thread_mutex_init_recursive(mp_thread_mutex_t *mutex) { } static inline int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait) { - return 0; + return 1; } static inline void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex) { } +typedef struct { + volatile mp_uint_t value; +} mp_thread_sem_t; + +static inline void mp_thread_sem_init(mp_thread_sem_t *sem, mp_uint_t value) { + sem->value = value; +} +static inline bool mp_thread_sem_wait(mp_thread_sem_t *sem, bool wait) { + if (wait) { + while (sem->value == 0) { + } + --sem->value; + return true; + } else { + if (sem->value == 0) { + return false; + } else { + --sem->value; + return true; + } + } +} +static inline void mp_thread_sem_post(mp_thread_sem_t *sem) { + ++sem->value; +} +static inline int mp_thread_sem_value(mp_thread_sem_t *sem) { + return sem->value; +} + #endif // MICROPY_PY_THREAD #if MICROPY_PY_THREAD && MICROPY_PY_THREAD_GIL From 843204c717b7c8446398fffe4ebcdb0bd13765df Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Mon, 4 Sep 2023 21:42:59 +1000 Subject: [PATCH 04/11] py/mpthread: Add RTOS-support function to run on mp thread. This abstracts the implementation currently used by modbluetooth.c to get BLE events from the host stack running in an RTOS task into a MicroPython task. This existing implementation only works when the GIL is enabled, so also add a scheduler-based implementation for non-GIL ports (e.g. Unix). This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared --- py/mpthread.c | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++ py/mpthread.h | 13 +++++ py/py.cmake | 1 + py/py.mk | 1 + 4 files changed, 146 insertions(+) diff --git a/py/mpthread.c b/py/mpthread.c index e69de29bb2d1d..258f96b4d68f7 100644 --- a/py/mpthread.c +++ b/py/mpthread.c @@ -0,0 +1,131 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Damien George + * Copyright (c) 2023 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/mpthread.h" +#include "py/stackctrl.h" +#include "py/runtime.h" + +#if MICROPY_PY_THREAD && MICROPY_PY_THREAD_RTOS + +// When we have a GIL, we let the RTOS thread/task acquire the GIL (which +// means no MicroPython thread is currently running), and then take over the +// thread-local state of the main thread to run the callback. Essentially the +// RTOS task becomes the main MicroPython thread temporarily. + +#if MICROPY_PY_THREAD_GIL + +#if MICROPY_ENABLE_PYSTACK +#error not supported +#endif + +void mp_thread_run_on_mp_thread(mp_run_on_thread_function_t fn, void *arg, mp_uint_t stack_size) { + // This code may run on an existing MicroPython thread, or a non-MicroPython thread + // that's not using the mp_thread_get_state() value. In the former case the state + // must be restored once this callback finishes. + mp_state_thread_t *ts_orig = mp_thread_get_state(); + + mp_state_thread_t ts; + if (ts_orig == NULL) { + mp_thread_set_state(&ts); + mp_stack_set_top(&ts + 1); // need to include ts in root-pointer scan + mp_stack_set_limit(stack_size); + ts.gc_lock_depth = 0; + ts.mp_pending_exception = MP_OBJ_NULL; + mp_locals_set(mp_state_ctx.thread.dict_locals); // set from the outer context + mp_globals_set(mp_state_ctx.thread.dict_globals); // set from the outer context + MP_THREAD_GIL_ENTER(); + } + + mp_sched_lock(); + fn(arg); + mp_sched_unlock(); + + if (ts_orig == NULL) { + MP_THREAD_GIL_EXIT(); + mp_thread_set_state(ts_orig); + } +} + +#else // !MICROPY_PY_THREAD_GIL + +#if !MICROPY_ENABLE_SCHEDULER +#error MICROPY_PY_THREAD_RTOS requires MICROPY_ENABLE_SCHEDULER if MICROPY_PY_THREAD_GIL is not enabled. +#endif + +// When we don't have a GIL, we instead schedule a callback to run on a +// MicroPython thread, and wait while it executes the callback for us. + +// TODO: We could make this require the caller to pass a static scheduler +// node, with a unique node per source task. + +typedef struct { + mp_run_on_thread_function_t fn; + void *arg; + mp_thread_sem_t sem; +} mp_run_on_thread_context_t; + +STATIC mp_obj_t run_in_scheduler(mp_obj_t context_in) { + mp_run_on_thread_context_t *context = MP_OBJ_TO_PTR(context_in); + + // Run the callback. + context->fn(context->arg); + + // Signal to the waiting RTOS task that we're done. + mp_thread_sem_post(&context->sem); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(run_in_scheduler_obj, run_in_scheduler); + +void mp_thread_run_on_mp_thread(mp_run_on_thread_function_t fn, void *arg, mp_uint_t stack_size) { + (void)stack_size; + + if (mp_thread_get_state()) { + // We're already on a MicroPython thread, just invoke the callback + // directly. + fn(arg); + return; + } + + mp_run_on_thread_context_t context = { .fn = fn, .arg = arg }; + mp_thread_sem_init(&context.sem, 0); + + if (!mp_sched_schedule(MP_OBJ_FROM_PTR(&run_in_scheduler_obj), MP_OBJ_FROM_PTR(&context))) { + mp_printf(&mp_plat_print, "failed to schedule\n"); + } + // TODO: How to handle fail-to-schedule? See above, perhaps should use + // static nodes to make that impossible. + + // Wait for the scheduled task to complete. + mp_thread_sem_wait(&context.sem, true); +} + +#endif // MICROPY_PY_THREAD_GIL + +#endif // MICROPY_PY_THREAD && MICROPY_PY_THREAD_RTOS diff --git a/py/mpthread.h b/py/mpthread.h index 1e2585c1d7e9d..0e0b252636d29 100644 --- a/py/mpthread.h +++ b/py/mpthread.h @@ -115,4 +115,17 @@ static inline int mp_thread_sem_value(mp_thread_sem_t *sem) { #define MP_THREAD_GIL_EXIT() #endif +#if MICROPY_PY_THREAD && MICROPY_PY_THREAD_RTOS +// Helper functions to assist with RTOS (or OS) threads/tasks that want to +// call into the MicroPython VM. For example, a Unix pthread or FreeRTOS that +// wants to execute Python code on a MicroPython thread. For example, on ESP32 +// the BLE host stack runs in a FreeRTOS task. +typedef void (*mp_run_on_thread_function_t)(void *arg); +void mp_thread_run_on_mp_thread(const mp_run_on_thread_function_t fn, void *arg, mp_uint_t stack_size); +#else +// When not using an RTOS (e.g. bare-metal STM32), all threads are MicroPython +// threads. +#define mp_thread_run_on_mp_thread(fn, arg, stack_size) fn(arg) +#endif + #endif // MICROPY_INCLUDED_PY_MPTHREAD_H diff --git a/py/py.cmake b/py/py.cmake index 95a841b5f4a02..71f7a64f390b2 100644 --- a/py/py.cmake +++ b/py/py.cmake @@ -50,6 +50,7 @@ set(MICROPY_SOURCE_PY ${MICROPY_PY_DIR}/moderrno.c ${MICROPY_PY_DIR}/mpprint.c ${MICROPY_PY_DIR}/mpstate.c + ${MICROPY_PY_DIR}/mpthread.c ${MICROPY_PY_DIR}/mpz.c ${MICROPY_PY_DIR}/nativeglue.c ${MICROPY_PY_DIR}/nlr.c diff --git a/py/py.mk b/py/py.mk index ffb853c652db9..859f9290c2203 100644 --- a/py/py.mk +++ b/py/py.mk @@ -90,6 +90,7 @@ PY_CORE_O_BASENAME = $(addprefix py/,\ qstr.o \ vstr.o \ mpprint.o \ + mpthread.o \ unicode.o \ mpz.o \ reader.o \ From bb08f2aaf67af28e3359c63bd3a4ecf46de210a4 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Mon, 4 Sep 2023 21:47:55 +1000 Subject: [PATCH 05/11] extmod/modbluetooth: Use mp_thread_run_on_mp_thread. This replaces the bluetooth-specific implementation previously enabled when MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS_WITH_INTERLOCK was enabled to synchronously run a host-stack callback on a MicroPython thread. When MICROPY_PY_THREAD_RTOS is not enabled, this is a no-op and the call goes directly (e.g. stm32, where the host stack is running in the scheduler). Also enables this for esp32 by removing MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS_WITH_INTERLOCK and adding MICROPY_PY_THREAD_RTOS. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared --- extmod/modbluetooth.c | 241 ++++++++++++++++--------------------- ports/esp32/mpconfigport.h | 2 +- 2 files changed, 106 insertions(+), 137 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 40658ef65241b..6bb525a7f71c0 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -907,103 +907,103 @@ MP_REGISTER_EXTENSIBLE_MODULE(MP_QSTR_bluetooth, mp_module_bluetooth); // Port API // ---------------------------------------------------------------------------- -STATIC mp_obj_t invoke_irq_handler_run(uint16_t event, - const mp_int_t *numeric, size_t n_unsigned, size_t n_signed, - const uint8_t *addr, - const mp_obj_bluetooth_uuid_t *uuid, - const uint8_t **data, uint16_t *data_len, size_t n_data) { +typedef struct _ble_event_tuple_args { + uint16_t event; + const mp_int_t *numeric; + size_t n_unsigned; + size_t n_signed; + const uint8_t *addr; + const mp_obj_bluetooth_uuid_t *uuid; + const uint8_t **data; + uint16_t *data_len; + size_t n_data; + mp_obj_t *result; +} ble_event_tuple_args; + +STATIC void invoke_irq_handler_protected(void *args_in) { + ble_event_tuple_args *args = args_in; + *args->result = mp_const_none; - mp_obj_array_t mv_addr; - mp_obj_array_t mv_data[2]; - assert(n_data <= 2); + mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); + if (o->irq_handler == mp_const_none) { + return; + } - mp_obj_tuple_t *data_tuple = mp_local_alloc(sizeof(mp_obj_tuple_t) + sizeof(mp_obj_t) * MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN); - data_tuple->base.type = &mp_type_tuple; - data_tuple->len = 0; + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_obj_array_t mv_addr; + mp_obj_array_t mv_data[2]; + assert(args->n_data <= 2); - for (size_t i = 0; i < n_unsigned; ++i) { - data_tuple->items[data_tuple->len++] = MP_OBJ_NEW_SMALL_INT(numeric[i]); - } - if (addr) { - mp_obj_memoryview_init(&mv_addr, 'B', 0, 6, (void *)addr); - data_tuple->items[data_tuple->len++] = MP_OBJ_FROM_PTR(&mv_addr); - } - for (size_t i = 0; i < n_signed; ++i) { - data_tuple->items[data_tuple->len++] = MP_OBJ_NEW_SMALL_INT(numeric[i + n_unsigned]); - } - #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE - if (uuid) { - data_tuple->items[data_tuple->len++] = MP_OBJ_FROM_PTR(uuid); - } - #endif + mp_obj_tuple_t *data_tuple = mp_local_alloc(sizeof(mp_obj_tuple_t) + sizeof(mp_obj_t) * MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN); + data_tuple->base.type = &mp_type_tuple; + data_tuple->len = 0; - #if MICROPY_PY_BLUETOOTH_USE_GATTC_EVENT_DATA_REASSEMBLY - void *buf_to_free = NULL; - uint16_t buf_to_free_len = 0; - if (event == MP_BLUETOOTH_IRQ_GATTC_NOTIFY || event == MP_BLUETOOTH_IRQ_GATTC_INDICATE || event == MP_BLUETOOTH_IRQ_GATTC_READ_RESULT) { - if (n_data > 1) { - // Fragmented buffer, need to combine into a new heap-allocated buffer - // in order to pass to Python. - // Only gattc_on_data_available calls this code, so data and data_len are writable. - uint16_t total_len = 0; - for (size_t i = 0; i < n_data; ++i) { - total_len += data_len[i]; - } - uint8_t *buf = m_new(uint8_t, total_len); - uint8_t *p = buf; - for (size_t i = 0; i < n_data; ++i) { - memcpy(p, data[i], data_len[i]); - p += data_len[i]; + for (size_t i = 0; i < args->n_unsigned; ++i) { + data_tuple->items[data_tuple->len++] = MP_OBJ_NEW_SMALL_INT(args->numeric[i]); + } + if (args->addr) { + mp_obj_memoryview_init(&mv_addr, 'B', 0, 6, (void *)args->addr); + data_tuple->items[data_tuple->len++] = MP_OBJ_FROM_PTR(&mv_addr); + } + for (size_t i = 0; i < args->n_signed; ++i) { + data_tuple->items[data_tuple->len++] = MP_OBJ_NEW_SMALL_INT(args->numeric[i + args->n_unsigned]); + } + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + if (args->uuid) { + data_tuple->items[data_tuple->len++] = MP_OBJ_FROM_PTR(args->uuid); + } + #endif + + #if MICROPY_PY_BLUETOOTH_USE_GATTC_EVENT_DATA_REASSEMBLY + void *buf_to_free = NULL; + uint16_t buf_to_free_len = 0; + if (args->event == MP_BLUETOOTH_IRQ_GATTC_NOTIFY || args->event == MP_BLUETOOTH_IRQ_GATTC_INDICATE || args->event == MP_BLUETOOTH_IRQ_GATTC_READ_RESULT) { + if (args->n_data > 1) { + // Fragmented buffer, need to combine into a new heap-allocated buffer + // in order to pass to Python. + // Only gattc_on_data_available calls this code, so data and data_len are writable. + uint16_t total_len = 0; + for (size_t i = 0; i < args->n_data; ++i) { + total_len += args->data_len[i]; + } + uint8_t *buf = m_new(uint8_t, total_len); + uint8_t *p = buf; + for (size_t i = 0; i < args->n_data; ++i) { + memcpy(p, args->data[i], args->data_len[i]); + p += args->data_len[i]; + } + args->data[0] = buf; + args->data_len[0] = total_len; + args->n_data = 1; + buf_to_free = buf; + buf_to_free_len = total_len; } - data[0] = buf; - data_len[0] = total_len; - n_data = 1; - buf_to_free = buf; - buf_to_free_len = total_len; } - } - #endif + #endif - for (size_t i = 0; i < n_data; ++i) { - if (data[i]) { - mp_obj_memoryview_init(&mv_data[i], 'B', 0, data_len[i], (void *)data[i]); - data_tuple->items[data_tuple->len++] = MP_OBJ_FROM_PTR(&mv_data[i]); - } else { - data_tuple->items[data_tuple->len++] = mp_const_none; + for (size_t i = 0; i < args->n_data; ++i) { + if (args->data[i]) { + mp_obj_memoryview_init(&mv_data[i], 'B', 0, args->data_len[i], (void *)args->data[i]); + data_tuple->items[data_tuple->len++] = MP_OBJ_FROM_PTR(&mv_data[i]); + } else { + data_tuple->items[data_tuple->len++] = mp_const_none; + } } - } - - assert(data_tuple->len <= MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN); - - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - mp_obj_t result = mp_call_function_2(o->irq_handler, MP_OBJ_NEW_SMALL_INT(event), MP_OBJ_FROM_PTR(data_tuple)); - #if MICROPY_PY_BLUETOOTH_USE_GATTC_EVENT_DATA_REASSEMBLY - if (buf_to_free != NULL) { - m_del(uint8_t, (uint8_t *)buf_to_free, buf_to_free_len); - } - #endif + assert(data_tuple->len <= MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN); - mp_local_free(data_tuple); + mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); + *args->result = mp_call_function_2(o->irq_handler, MP_OBJ_NEW_SMALL_INT(args->event), MP_OBJ_FROM_PTR(data_tuple)); - return result; -} + #if MICROPY_PY_BLUETOOTH_USE_GATTC_EVENT_DATA_REASSEMBLY + if (buf_to_free != NULL) { + m_del(uint8_t, (uint8_t *)buf_to_free, buf_to_free_len); + } + #endif -STATIC mp_obj_t invoke_irq_handler_run_protected(uint16_t event, - const mp_int_t *numeric, size_t n_unsigned, size_t n_signed, - const uint8_t *addr, - const mp_obj_bluetooth_uuid_t *uuid, - const uint8_t **data, uint16_t *data_len, size_t n_data) { + mp_local_free(data_tuple); - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if (o->irq_handler == mp_const_none) { - return mp_const_none; - } - - mp_obj_t result = mp_const_none; - nlr_buf_t nlr; - if (nlr_push(&nlr) == 0) { - result = invoke_irq_handler_run(event, numeric, n_unsigned, n_signed, addr, uuid, data, data_len, n_data); nlr_pop(); } else { // Uncaught exception, print it out. @@ -1013,18 +1013,10 @@ STATIC mp_obj_t invoke_irq_handler_run_protected(uint16_t event, // Disable the BLE IRQ handler. o->irq_handler = mp_const_none; } - - return result; } -#if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS_WITH_INTERLOCK - -// On some systems the BLE event callbacks may occur on a system thread which is not -// a MicroPython thread. In such cases the callback must set up relevant MicroPython -// state and obtain the GIL, to synchronised with the rest of the runtime. - -#if MICROPY_ENABLE_PYSTACK -#error not supported +#ifndef MICROPY_PY_BLUETOOTH_SYNC_EVENT_STACK_SIZE +#define MICROPY_PY_BLUETOOTH_SYNC_EVENT_STACK_SIZE 4096 #endif STATIC mp_obj_t invoke_irq_handler(uint16_t event, @@ -1032,51 +1024,28 @@ STATIC mp_obj_t invoke_irq_handler(uint16_t event, const uint8_t *addr, const mp_obj_bluetooth_uuid_t *uuid, const uint8_t **data, uint16_t *data_len, size_t n_data) { - - // This code may run on an existing MicroPython thread, or a non-MicroPython thread - // that's not using the mp_thread_get_state() value. In the former case the state - // must be restored once this callback finishes. - mp_state_thread_t *ts_orig = mp_thread_get_state(); - - mp_state_thread_t ts; - if (ts_orig == NULL) { - mp_thread_set_state(&ts); - mp_stack_set_top(&ts + 1); // need to include ts in root-pointer scan - mp_stack_set_limit(MICROPY_PY_BLUETOOTH_SYNC_EVENT_STACK_SIZE - 1024); - ts.gc_lock_depth = 0; - ts.mp_pending_exception = MP_OBJ_NULL; - mp_locals_set(mp_state_ctx.thread.dict_locals); // set from the outer context - mp_globals_set(mp_state_ctx.thread.dict_globals); // set from the outer context - MP_THREAD_GIL_ENTER(); - } - - mp_sched_lock(); - mp_obj_t result = invoke_irq_handler_run_protected(event, numeric, n_unsigned, n_signed, addr, uuid, data, data_len, n_data); - mp_sched_unlock(); - - if (ts_orig == NULL) { - MP_THREAD_GIL_EXIT(); - mp_thread_set_state(ts_orig); - } - + mp_obj_t result; + ble_event_tuple_args args = { + .result = &result, + .event = event, + .numeric = numeric, + .n_unsigned = n_unsigned, + .n_signed = n_signed, + .addr = addr, + .uuid = uuid, + .data = data, + .data_len = data_len, + .n_data = n_data, + }; + // On bare-metal (e.g STM32), this will just invoke the callback directly, + // the host stack is already running in the scheduler. On ESP32, Unix, and + // Zephyr, we are currently in RTOS/OS task context, so this will run the + // callback on a MicroPython thread. If we're on an RTOS task, assume that + // at most 1k bytes of stack has been used already on the host stack. + mp_thread_run_on_mp_thread(invoke_irq_handler_protected, &args, MICROPY_PY_BLUETOOTH_SYNC_EVENT_STACK_SIZE - 1024); return result; } -#else - -// BLE event callbacks are called directly from the MicroPython runtime, so additional -// synchronisation is not needed, and BLE event handlers can be called directly. - -STATIC mp_obj_t invoke_irq_handler(uint16_t event, - const mp_int_t *numeric, size_t n_unsigned, size_t n_signed, - const uint8_t *addr, - const mp_obj_bluetooth_uuid_t *uuid, - const uint8_t **data, uint16_t *data_len, size_t n_data) { - return invoke_irq_handler_run_protected(event, numeric, n_unsigned, n_signed, addr, uuid, data, data_len, n_data); -} - -#endif - #define NULL_NUMERIC NULL #define NULL_ADDR NULL #define NULL_UUID NULL diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index b4c92cbb46b11..8872533ed9184 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -65,6 +65,7 @@ #define MICROPY_PY_TIME_TIME_TIME_NS (1) #define MICROPY_PY_TIME_INCLUDEFILE "ports/esp32/modtime.c" #define MICROPY_PY_THREAD (1) +#define MICROPY_PY_THREAD_RTOS (1) #define MICROPY_PY_THREAD_GIL (1) #define MICROPY_PY_THREAD_GIL_VM_DIVISOR (32) @@ -77,7 +78,6 @@ #endif #ifndef MICROPY_PY_BLUETOOTH #define MICROPY_PY_BLUETOOTH (1) -#define MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS_WITH_INTERLOCK (1) #define MICROPY_PY_BLUETOOTH_SYNC_EVENT_STACK_SIZE (CONFIG_BT_NIMBLE_TASK_STACK_SIZE) #define MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE (1) #define MICROPY_BLUETOOTH_NIMBLE (1) From 4a41563bfca7f54882c1544b8dd3bf4a741c8cc1 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 19 Sep 2023 22:37:41 +1000 Subject: [PATCH 06/11] extmod/nimble/nimble/nimble_npl_os: Implement synchronisation. This provides a full implementation of the NimBLE mutex, critical section, and semaphore. On ports without threading, this is a no-op change, but this is necessary to make NimBLE's locking functionality work on threaded ports. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared --- extmod/nimble/nimble/nimble_npl_os.c | 56 +++++++++++----------------- extmod/nimble/nimble/nimble_npl_os.h | 6 ++- 2 files changed, 25 insertions(+), 37 deletions(-) diff --git a/extmod/nimble/nimble/nimble_npl_os.c b/extmod/nimble/nimble/nimble_npl_os.c index ec703b77aed3b..cdcfdaba4c2dd 100644 --- a/extmod/nimble/nimble/nimble_npl_os.c +++ b/extmod/nimble/nimble/nimble_npl_os.c @@ -298,27 +298,22 @@ void ble_npl_event_set_arg(struct ble_npl_event *ev, void *arg) { ble_npl_error_t ble_npl_mutex_init(struct ble_npl_mutex *mu) { DEBUG_MUTEX_printf("ble_npl_mutex_init(%p)\n", mu); - mu->locked = 0; + mp_thread_mutex_init_recursive(&mu->mutex); return BLE_NPL_OK; } ble_npl_error_t ble_npl_mutex_pend(struct ble_npl_mutex *mu, ble_npl_time_t timeout) { - DEBUG_MUTEX_printf("ble_npl_mutex_pend(%p, %u) locked=%u\n", mu, (uint)timeout, (uint)mu->locked); - - // All NimBLE code is executed by the scheduler (and is therefore - // implicitly mutexed) so this mutex implementation is a no-op. - - ++mu->locked; - + DEBUG_MUTEX_printf("ble_npl_mutex_pend(%p, %u)\n", mu, (uint)timeout); + if (!mp_thread_mutex_lock(&mu->mutex, 1)) { + printf("FAILED TO LOCK\n"); + assert(0); + } return BLE_NPL_OK; } ble_npl_error_t ble_npl_mutex_release(struct ble_npl_mutex *mu) { - DEBUG_MUTEX_printf("ble_npl_mutex_release(%p) locked=%u\n", mu, (uint)mu->locked); - assert(mu->locked > 0); - - --mu->locked; - + DEBUG_MUTEX_printf("ble_npl_mutex_release(%p)\n", mu); + mp_thread_mutex_unlock(&mu->mutex); return BLE_NPL_OK; } @@ -327,12 +322,12 @@ ble_npl_error_t ble_npl_mutex_release(struct ble_npl_mutex *mu) { ble_npl_error_t ble_npl_sem_init(struct ble_npl_sem *sem, uint16_t tokens) { DEBUG_SEM_printf("ble_npl_sem_init(%p, %u)\n", sem, (uint)tokens); - sem->count = tokens; + mp_thread_sem_init(&sem->sem, tokens); return BLE_NPL_OK; } 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); + DEBUG_SEM_printf("ble_npl_sem_pend(%p, %u) tokens=%u\n", sem, (uint)timeout, (uint)ble_npl_sem_get_count(sem)); // This is only called by NimBLE in ble_hs_hci_cmd_tx to synchronously // wait for an HCI ACK. The corresponding ble_npl_sem_release is called @@ -340,36 +335,32 @@ ble_npl_error_t ble_npl_sem_pend(struct ble_npl_sem *sem, ble_npl_time_t timeout // extmod/nimble/hal/hal_uart.c). So this loop needs to run only the HCI // UART processing but not run any events. - if (sem->count == 0) { - uint32_t t0 = mp_hal_ticks_ms(); - while (sem->count == 0 && mp_hal_ticks_ms() - t0 < timeout) { - if (sem->count != 0) { - break; - } + uint32_t t0 = mp_hal_ticks_ms(); - mp_bluetooth_nimble_hci_uart_wfi(); + while (true) { + if (mp_thread_sem_wait(&sem->sem, false)) { + return BLE_NPL_OK; } - if (sem->count == 0) { - DEBUG_SEM_printf("ble_npl_sem_pend: semaphore timeout\n"); + if (mp_hal_ticks_ms() - t0 > timeout) { return BLE_NPL_TIMEOUT; } - DEBUG_SEM_printf("ble_npl_sem_pend: acquired in %u ms\n", (int)(mp_hal_ticks_ms() - t0)); + mp_bluetooth_nimble_hci_uart_wfi(); } - sem->count -= 1; + return BLE_NPL_OK; } ble_npl_error_t ble_npl_sem_release(struct ble_npl_sem *sem) { DEBUG_SEM_printf("ble_npl_sem_release(%p)\n", sem); - sem->count += 1; + mp_thread_sem_post(&sem->sem); return BLE_NPL_OK; } uint16_t ble_npl_sem_get_count(struct ble_npl_sem *sem) { DEBUG_SEM_printf("ble_npl_sem_get_count(%p)\n", sem); - return sem->count; + return mp_thread_sem_value(&sem->sem); } /******************************************************************************/ @@ -502,17 +493,12 @@ void ble_npl_time_delay(ble_npl_time_t ticks) { // This is used anywhere NimBLE modifies global data structures. -// Currently all NimBLE code is invoked by the scheduler so there is no -// concurrency. In the future we may wish to make HCI UART processing happen -// asynchronously (e.g. on RX IRQ), so the port can implement these macros -// accordingly. - uint32_t ble_npl_hw_enter_critical(void) { DEBUG_CRIT_printf("ble_npl_hw_enter_critical()\n"); - return 0; + return MICROPY_BEGIN_ATOMIC_SECTION(); } void ble_npl_hw_exit_critical(uint32_t atomic_state) { - (void)atomic_state; DEBUG_CRIT_printf("ble_npl_hw_exit_critical(%u)\n", (uint)atomic_state); + MICROPY_END_ATOMIC_SECTION(atomic_state); } diff --git a/extmod/nimble/nimble/nimble_npl_os.h b/extmod/nimble/nimble/nimble_npl_os.h index 3205baa032da7..5c41bd22b4e38 100644 --- a/extmod/nimble/nimble/nimble_npl_os.h +++ b/extmod/nimble/nimble/nimble_npl_os.h @@ -32,6 +32,8 @@ #include #include +#include "py/mpthread.h" + // --- Configuration of NimBLE data structures -------------------------------- // This is used at runtime to align allocations correctly. @@ -78,11 +80,11 @@ struct ble_npl_callout { }; struct ble_npl_mutex { - volatile uint8_t locked; + mp_thread_mutex_t mutex; }; struct ble_npl_sem { - volatile uint16_t count; + mp_thread_sem_t sem; }; // --- Called by the MicroPython port ----------------------------------------- From 864228d7b7410d8b5f8522d98ad6b74fe420c2d6 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Mon, 4 Sep 2023 21:51:08 +1000 Subject: [PATCH 07/11] unix/mpconfigport: Allow running with the GIL enabled. MICROPY_EVENT_POLL_HOOK should bounce the GIL like other ports. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared --- ports/unix/mpconfigport.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index 2de05a0a6c866..cad13c332d9ad 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -218,11 +218,12 @@ static inline unsigned long mp_random_seed_init(void) { #include #endif -// If threading is enabled, configure the atomic section. #if MICROPY_PY_THREAD +// If threading is enabled, configure the atomic section. #define MICROPY_BEGIN_ATOMIC_SECTION() (mp_thread_unix_begin_atomic_section(), 0xffffffff) #define MICROPY_END_ATOMIC_SECTION(x) (void)x; mp_thread_unix_end_atomic_section() -#endif + +#include // In lieu of a WFI(), slow down polling from being a tight loop. #ifndef MICROPY_EVENT_POLL_HOOK @@ -230,12 +231,13 @@ static inline unsigned long mp_random_seed_init(void) { do { \ extern void mp_handle_pending(bool); \ mp_handle_pending(true); \ + MP_THREAD_GIL_EXIT(); \ usleep(500); /* equivalent to mp_hal_delay_us(500) */ \ + MP_THREAD_GIL_ENTER(); \ } while (0); #endif // Configure the implementation of machine.idle(). -#include #define MICROPY_UNIX_MACHINE_IDLE sched_yield(); #ifndef MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE From bd993873a55d295bf175bffaca42bca761fb83d2 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Mon, 4 Sep 2023 21:51:08 +1000 Subject: [PATCH 08/11] unix/mpbthciport: Use mp_thread_run_on_mp_thread. This replaces the previous code that used the scheduler (via thread) to run the BLE host stack. Now the host stack can run directly on the thread, and then modbluetooth.c will use mp_thread_run_on_mp_thread to run the Python-level callbacks on a MicroPython thread as necessary. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared --- ports/unix/mpbthciport.c | 39 +++++++++++---------------------------- ports/unix/mpconfigport.h | 4 ++++ 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/ports/unix/mpbthciport.c b/ports/unix/mpbthciport.c index 2477421e28479..c94925ccb414e 100644 --- a/ports/unix/mpbthciport.c +++ b/ports/unix/mpbthciport.c @@ -34,6 +34,10 @@ #error Unix HCI UART requires MICROPY_PY_THREAD #endif +#if !MICROPY_PY_THREAD_RTOS +#error Unix HCI UART requires MICROPY_PY_THREAD_RTOS +#endif + #include "extmod/modbluetooth.h" #include "extmod/mpbthci.h" @@ -59,46 +63,25 @@ STATIC int uart_fd = -1; // Must be provided by the stack bindings (e.g. mpnimbleport.c or mpbtstackport.c). extern bool mp_bluetooth_hci_poll(void); -// For synchronous mode, we run all BLE stack code inside a scheduled task. -// This task is scheduled periodically (every 1ms) by a background thread. - -// Allows the stack to tell us that we should stop trying to schedule. +// Allows the stack to tell us that we should stop running the hci poll loop. extern bool mp_bluetooth_hci_active(void); -// Prevent double-enqueuing of the scheduled task. -STATIC volatile bool events_task_is_scheduled = false; - -STATIC mp_obj_t run_events_scheduled_task(mp_obj_t none_in) { - (void)none_in; - mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); - events_task_is_scheduled = false; - MICROPY_END_ATOMIC_SECTION(atomic_state); - mp_bluetooth_hci_poll(); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(run_events_scheduled_task_obj, run_events_scheduled_task); - STATIC const useconds_t UART_POLL_INTERVAL_US = 1000; STATIC pthread_t hci_poll_thread_id; +// We run the host stack periodically (every 1ms) by a background thread. +// Because MICROPY_PY_THREAD_RTOS is enabled, when the host stack tries +// to call into Python, it will use mp_thread_run_on_mp_thread to run +// the callbacks on a Python thread. STATIC void *hci_poll_thread(void *arg) { (void)arg; - DEBUG_printf("hci_poll_thread: starting\n"); - - events_task_is_scheduled = false; - + // TODO: Replace with select-readable on the uart with a 1ms timeout. while (mp_bluetooth_hci_active()) { - mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); - if (!events_task_is_scheduled) { - events_task_is_scheduled = mp_sched_schedule(MP_OBJ_FROM_PTR(&run_events_scheduled_task_obj), mp_const_none); - } - MICROPY_END_ATOMIC_SECTION(atomic_state); + mp_bluetooth_hci_poll(); usleep(UART_POLL_INTERVAL_US); } - DEBUG_printf("hci_poll_thread: stopped\n"); - return NULL; } diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index cad13c332d9ad..a665796f7e510 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -223,6 +223,10 @@ static inline unsigned long mp_random_seed_init(void) { #define MICROPY_BEGIN_ATOMIC_SECTION() (mp_thread_unix_begin_atomic_section(), 0xffffffff) #define MICROPY_END_ATOMIC_SECTION(x) (void)x; mp_thread_unix_end_atomic_section() +// Also enable RTOS support (really, OS in this case). +#define MICROPY_PY_THREAD_RTOS (1) +#endif // MICROPY_PY_THREAD + #include // In lieu of a WFI(), slow down polling from being a tight loop. From 80458e776089132f358c13769ca52c5c9f20b71a Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Thu, 21 Sep 2023 16:25:26 +1000 Subject: [PATCH 09/11] extmod/nimble: Update to NimBLE 1.6.0. Implement a UART transport: The way transports worked changed slightly in NimBLE 1.6.0 anyway, but this allows us to hook the "send payload" directly, rather than having to go byte-at-a-time via the tx callback. Also implemenets a mutex on transmission as nothing prevents NimBLE sending both a CMD and ACL payload at the same time. Common implementation of mpnimbleport.c for scheduler-based ports: Makes stm32, renesas-ra, mimxrt, rp2 use a shared implementation. Simplify the NPL event queue: We only need to process the single default queue, so remove the additional logic to handle the linked list of queues. Also rename the functions to execute the queues and run timers. Unix: Implement the HCI layer more like an RTOS port, where you have a task running the host stack, and UART data is sent directly to NimBLE from a UART IRQ. This makes it behave more like ESP32 or Zephyr, where we only run the Python-level callbacks in a MicroPython thread. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared --- extmod/modbluetooth.h | 2 +- extmod/nimble/hal/hal_uart.c | 131 --------------------------- extmod/nimble/hal/hal_uart.h | 48 ---------- extmod/nimble/modbluetooth_nimble.c | 24 +++-- extmod/nimble/nimble.cmake | 9 +- extmod/nimble/nimble.mk | 12 ++- extmod/nimble/nimble/nimble_npl_os.c | 63 ++++++------- extmod/nimble/nimble/nimble_npl_os.h | 5 +- extmod/nimble/syscfg/syscfg.h | 58 +++++++++--- lib/mynewt-nimble | 2 +- ports/mimxrt/Makefile | 5 +- ports/mimxrt/mpbthciport.c | 9 +- ports/mimxrt/mpnimbleport.c | 78 ---------------- ports/renesas-ra/Makefile | 5 +- ports/renesas-ra/mpbthciport.c | 9 +- ports/renesas-ra/mpnimbleport.c | 78 ---------------- ports/rp2/CMakeLists.txt | 2 +- ports/rp2/mpbthciport.c | 8 +- ports/rp2/mpbthciport.h | 6 -- ports/rp2/mpnimbleport.c | 80 ---------------- ports/stm32/Makefile | 5 +- ports/stm32/mpbthciport.c | 15 ++- ports/stm32/mpbthciport.h | 6 -- ports/stm32/mpconfigport.h | 2 +- ports/stm32/mpnimbleport.c | 78 ---------------- ports/unix/mpbthciport.c | 76 +++++++++++++--- ports/unix/mpconfigport.h | 2 +- ports/unix/mpnimbleport.c | 47 ++++++---- 28 files changed, 240 insertions(+), 625 deletions(-) delete mode 100644 extmod/nimble/hal/hal_uart.c delete mode 100644 extmod/nimble/hal/hal_uart.h delete mode 100644 ports/mimxrt/mpnimbleport.c delete mode 100644 ports/renesas-ra/mpnimbleport.c delete mode 100644 ports/rp2/mpnimbleport.c delete mode 100644 ports/stm32/mpnimbleport.c diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index fc8427f1e99f2..5e01aceaee005 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -56,7 +56,7 @@ // Pairing and bonding enabled by default, but can be disabled by a port. #ifndef MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING -#define MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING (1) +#define MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING (0) #endif // Optionally enable support for the `hci_cmd` function allowing diff --git a/extmod/nimble/hal/hal_uart.c b/extmod/nimble/hal/hal_uart.c deleted file mode 100644 index f4a9319c8b5fa..0000000000000 --- a/extmod/nimble/hal/hal_uart.c +++ /dev/null @@ -1,131 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2018-2019 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/mphal.h" -#include "nimble/ble.h" -#include "extmod/nimble/modbluetooth_nimble.h" -#include "extmod/nimble/hal/hal_uart.h" -#include "extmod/nimble/nimble/nimble_npl_os.h" -#include "extmod/mpbthci.h" - -#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE - -#ifndef MICROPY_PY_BLUETOOTH_HCI_READ_MODE -#define MICROPY_PY_BLUETOOTH_HCI_READ_MODE MICROPY_PY_BLUETOOTH_HCI_READ_MODE_BYTE -#endif - -#define HCI_TRACE (0) -#define COL_OFF "\033[0m" -#define COL_GREEN "\033[0;32m" -#define COL_BLUE "\033[0;34m" - -static hal_uart_tx_cb_t hal_uart_tx_cb; -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; - hal_uart_rx_cb = rx_cb; - hal_uart_rx_arg = rx_arg; - return 0; // success -} - -int hal_uart_config(uint32_t port, uint32_t baudrate, uint32_t bits, uint32_t stop, uint32_t parity, uint32_t flow) { - return mp_bluetooth_hci_uart_init(port, baudrate); -} - -void hal_uart_start_tx(uint32_t port) { - size_t len = 0; - for (;;) { - int data = hal_uart_tx_cb(hal_uart_tx_arg); - if (data == -1) { - break; - } - mp_bluetooth_hci_cmd_buf[len++] = data; - } - - #if HCI_TRACE - printf(COL_GREEN "< [% 8d] %02x", (int)mp_hal_ticks_ms(), mp_bluetooth_hci_cmd_buf[0]); - for (size_t i = 1; i < len; ++i) { - printf(":%02x", mp_bluetooth_hci_cmd_buf[i]); - } - printf(COL_OFF "\n"); - #endif - - mp_bluetooth_hci_uart_write(mp_bluetooth_hci_cmd_buf, len); - - if (len > 0) { - // Allow modbluetooth bindings to hook "sent packet" (e.g. to un-stall l2cap channels). - mp_bluetooth_nimble_sent_hci_packet(); - } -} - -int hal_uart_close(uint32_t port) { - return 0; // success -} - -STATIC void mp_bluetooth_hci_uart_char_cb(uint8_t chr) { - #if HCI_TRACE - printf(COL_BLUE "> [% 8d] %02x" COL_OFF "\n", (int)mp_hal_ticks_ms(), chr); - #endif - hal_uart_rx_cb(hal_uart_rx_arg, chr); -} - -void mp_bluetooth_nimble_hci_uart_process(bool run_events) { - bool host_wake = mp_bluetooth_hci_controller_woken(); - - for (;;) { - #if MICROPY_PY_BLUETOOTH_HCI_READ_MODE == MICROPY_PY_BLUETOOTH_HCI_READ_MODE_BYTE - int chr = mp_bluetooth_hci_uart_readchar(); - if (chr < 0) { - break; - } - mp_bluetooth_hci_uart_char_cb(chr); - #elif MICROPY_PY_BLUETOOTH_HCI_READ_MODE == MICROPY_PY_BLUETOOTH_HCI_READ_MODE_PACKET - if (mp_bluetooth_hci_uart_readpacket(mp_bluetooth_hci_uart_char_cb) < 0) { - break; - } - #endif - - // Incoming data may result in events being enqueued. If we're in - // scheduler context then we can run those events immediately. - if (run_events) { - mp_bluetooth_nimble_os_eventq_run_all(); - } - } - - 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 deleted file mode 100644 index 647e8ab4772e2..0000000000000 --- a/extmod/nimble/hal/hal_uart.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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 - -#include - -#define SYSINIT_PANIC_ASSERT_MSG(cond, msg) - -#define HAL_UART_PARITY_NONE (0) - -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. --------------------------- -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(bool run_events); - -#endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_HAL_HAL_UART_H diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index a799fc8384532..2c94997a753b9 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -270,8 +270,6 @@ STATIC void set_random_address(bool nrpa) { #if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING // For ble_hs_pvcy_set_our_irk #include "nimble/host/src/ble_hs_pvcy_priv.h" -// For ble_hs_hci_util_rand -#include "nimble/host/src/ble_hs_hci_priv.h" // For ble_hs_misc_restore_irks #include "nimble/host/src/ble_hs_priv.h" @@ -299,7 +297,7 @@ STATIC int load_irk(void) { } else { DEBUG_printf("load_irk: Generating new IRK.\n"); uint8_t rand_irk[16]; - rc = ble_hs_hci_util_rand(rand_irk, 16); + rc = ble_hs_hci_rand(rand_irk, 16); if (rc) { return rc; } @@ -531,12 +529,12 @@ STATIC int central_gap_event_cb(struct ble_gap_event *event, void *arg) { // 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" +#include "nimble/transport.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(); + // This calls mp_bluetooth_hci_uart_init (via ble_transport_hs_init --> hal_uart_config --> mp_bluetooth_hci_uart_init). + ble_transport_ll_init(); mp_bluetooth_hci_controller_init(); } @@ -618,20 +616,20 @@ int mp_bluetooth_init(void) { MP_STATE_PORT(bluetooth_nimble_memory) = NULL; #endif - // Allow port (ESP32) to override NimBLE's HCI init. - // Otherwise default implementation above calls ble_hci_uart_init(). - mp_bluetooth_nimble_port_hci_init(); - - // Static initialization is complete, can start processing events. - mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC; - // Initialise NimBLE memory and data structures. DEBUG_printf("mp_bluetooth_init: nimble_port_init\n"); nimble_port_init(); + // Allow port (ESP32) to override NimBLE's HCI init. + // Otherwise default implementation above calls ble_transport_hs_init(). + mp_bluetooth_nimble_port_hci_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; + // Run the scheduler while we wait for stack startup. // On non-ringbuffer builds (NimBLE on STM32/Unix) this will also poll the UART and run the event queue. mp_uint_t timeout_start_ticks_ms = mp_hal_ticks_ms(); diff --git a/extmod/nimble/nimble.cmake b/extmod/nimble/nimble.cmake index 3dff1d7d0579b..765506067be8b 100644 --- a/extmod/nimble/nimble.cmake +++ b/extmod/nimble/nimble.cmake @@ -15,12 +15,13 @@ target_include_directories(micropy_extmod_nimble INTERFACE ${NIMBLE_LIB_DIR}/nimble/host/store/ram/include ${NIMBLE_LIB_DIR}/nimble/host/util/include ${NIMBLE_LIB_DIR}/nimble/include - ${NIMBLE_LIB_DIR}/nimble/transport/uart/include + ${NIMBLE_LIB_DIR}/nimble/transport/include + ${NIMBLE_LIB_DIR}/nimble/transport/common/hci_h4/include ${NIMBLE_LIB_DIR}/porting/nimble/include ) target_sources(micropy_extmod_nimble INTERFACE - ${NIMBLE_EXTMOD_DIR}/hal/hal_uart.c + ${NIMBLE_EXTMOD_DIR}/transport/uart_ll.c ${NIMBLE_EXTMOD_DIR}/nimble/nimble_npl_os.c ${NIMBLE_LIB_DIR}/ext/tinycrypt/src/aes_encrypt.c ${NIMBLE_LIB_DIR}/ext/tinycrypt/src/cmac_mode.c @@ -60,7 +61,6 @@ target_sources(micropy_extmod_nimble INTERFACE ${NIMBLE_LIB_DIR}/nimble/host/src/ble_l2cap_coc.c ${NIMBLE_LIB_DIR}/nimble/host/src/ble_l2cap_sig.c ${NIMBLE_LIB_DIR}/nimble/host/src/ble_l2cap_sig_cmd.c - ${NIMBLE_LIB_DIR}/nimble/host/src/ble_monitor.c ${NIMBLE_LIB_DIR}/nimble/host/src/ble_sm.c ${NIMBLE_LIB_DIR}/nimble/host/src/ble_sm_alg.c ${NIMBLE_LIB_DIR}/nimble/host/src/ble_sm_cmd.c @@ -70,7 +70,8 @@ target_sources(micropy_extmod_nimble INTERFACE ${NIMBLE_LIB_DIR}/nimble/host/src/ble_store_util.c ${NIMBLE_LIB_DIR}/nimble/host/src/ble_uuid.c ${NIMBLE_LIB_DIR}/nimble/host/util/src/addr.c - ${NIMBLE_LIB_DIR}/nimble/transport/uart/src/ble_hci_uart.c + ${NIMBLE_LIB_DIR}/nimble/transport/src/transport.c + ${NIMBLE_LIB_DIR}/nimble/transport/common/hci_h4/src/hci_h4.c ${NIMBLE_LIB_DIR}/porting/nimble/src/endian.c ${NIMBLE_LIB_DIR}/porting/nimble/src/mem.c ${NIMBLE_LIB_DIR}/porting/nimble/src/nimble_port.c diff --git a/extmod/nimble/nimble.mk b/extmod/nimble/nimble.mk index dddb3e1df5284..6998f3a626c2d 100644 --- a/extmod/nimble/nimble.mk +++ b/extmod/nimble/nimble.mk @@ -63,7 +63,6 @@ SRC_THIRDPARTY_C += $(addprefix $(NIMBLE_LIB_DIR)/, \ ble_l2cap_coc.c \ ble_l2cap_sig.c \ ble_l2cap_sig_cmd.c \ - ble_monitor.c \ ble_sm_alg.c \ ble_sm.c \ ble_sm_cmd.c \ @@ -74,7 +73,8 @@ SRC_THIRDPARTY_C += $(addprefix $(NIMBLE_LIB_DIR)/, \ ble_uuid.c \ ) \ nimble/host/util/src/addr.c \ - nimble/transport/uart/src/ble_hci_uart.c \ + nimble/transport/src/transport.c \ + nimble/transport/common/hci_h4/src/hci_h4.c \ $(addprefix porting/nimble/src/, \ endian.c \ mem.c \ @@ -86,9 +86,10 @@ SRC_THIRDPARTY_C += $(addprefix $(NIMBLE_LIB_DIR)/, \ ) # nimble/host/store/ram/src/ble_store_ram.c \ + SRC_THIRDPARTY_C += $(addprefix $(NIMBLE_EXTMOD_DIR)/, \ nimble/nimble_npl_os.c \ - hal/hal_uart.c \ + transport/uart_ll.c \ ) INC += -I$(TOP)/$(NIMBLE_EXTMOD_DIR) @@ -100,10 +101,11 @@ INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/host/services/gatt/include INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/host/store/ram/include INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/host/util/include INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/include -INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/transport/uart/include +INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/transport/include +INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/transport/common/hci_h4/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 -Wno-sign-compare -Wno-old-style-declaration +$(BUILD)/$(NIMBLE_LIB_DIR)/%.o: CFLAGS += -Wno-maybe-uninitialized -Wno-pointer-arith -Wno-unused-but-set-variable -Wno-format -Wno-sign-compare -Wno-old-style-declaration -Wno-implicit-fallthrough endif diff --git a/extmod/nimble/nimble/nimble_npl_os.c b/extmod/nimble/nimble/nimble_npl_os.c index cdcfdaba4c2dd..f0e32d15eb5a3 100644 --- a/extmod/nimble/nimble/nimble_npl_os.c +++ b/extmod/nimble/nimble/nimble_npl_os.c @@ -29,7 +29,6 @@ #include "py/runtime.h" #include "nimble/ble.h" #include "nimble/nimble_npl.h" -#include "extmod/nimble/hal/hal_uart.h" #include "extmod/modbluetooth.h" #include "extmod/nimble/modbluetooth_nimble.h" @@ -177,41 +176,28 @@ int nimble_sprintf(char *str, const char *fmt, ...) { /******************************************************************************/ // EVENTQ -struct ble_npl_eventq *global_eventq = NULL; +struct ble_npl_eventq* g_eventq_dflt; -// This must not be called recursively or concurrently with the UART handler. -void mp_bluetooth_nimble_os_eventq_run_all(void) { - if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { - return; - } +// Run all events in the default queue. +STATIC void ble_npl_run_default_queue(void) { + DEBUG_EVENT_printf("mp_bluetooth_nimble_npl_run_default_queue(%p, %p)\n", g_eventq_dflt, g_eventq_dflt->head); // Keep running while there are pending events. while (true) { - struct ble_npl_event *ev = NULL; - os_sr_t sr; OS_ENTER_CRITICAL(sr); - // Search all queues for an event. - for (struct ble_npl_eventq *evq = global_eventq; evq != NULL; evq = evq->nextq) { - ev = evq->head; - if (ev) { - // Remove this event from the queue. - evq->head = ev->next; - if (ev->next) { - ev->next->prev = NULL; - ev->next = NULL; - } - ev->prev = NULL; - - ev->pending = false; - - // Stop searching and execute this event. - break; - } + struct ble_npl_event *ev = NULL; + if (g_eventq_dflt->head) { + ev = g_eventq_dflt->head; + // Remove this event from the queue. + g_eventq_dflt->head = ev->next; + // Mark it as executed. + ev->pending = false; } OS_EXIT_CRITICAL(sr); if (!ev) { + // Queue is empty. break; } @@ -233,12 +219,9 @@ void ble_npl_eventq_init(struct ble_npl_eventq *evq) { DEBUG_EVENT_printf("ble_npl_eventq_init(%p)\n", evq); os_sr_t sr; OS_ENTER_CRITICAL(sr); + assert(g_eventq_dflt == NULL); + g_eventq_dflt = evq; evq->head = NULL; - struct ble_npl_eventq **evq2; - for (evq2 = &global_eventq; *evq2 != NULL; evq2 = &(*evq2)->nextq) { - } - *evq2 = evq; - evq->nextq = NULL; OS_EXIT_CRITICAL(sr); } @@ -248,10 +231,11 @@ void ble_npl_eventq_put(struct ble_npl_eventq *evq, struct ble_npl_event *ev) { OS_ENTER_CRITICAL(sr); ev->next = NULL; ev->pending = true; + if (evq->head == NULL) { + DEBUG_EVENT_printf(" --> set head\n"); // Empty list, make this the first item. evq->head = ev; - ev->prev = NULL; } else { // Find the tail of this list. struct ble_npl_event *tail = evq->head; @@ -263,9 +247,9 @@ void ble_npl_eventq_put(struct ble_npl_eventq *evq, struct ble_npl_event *ev) { break; } if (tail->next == NULL) { + DEBUG_EVENT_printf(" --> added to tail\n"); // Found the end of the list, add this event as the tail. tail->next = ev; - ev->prev = tail; break; } DEBUG_EVENT_printf(" --> %p\n", tail->next); @@ -368,7 +352,7 @@ uint16_t ble_npl_sem_get_count(struct ble_npl_sem *sem) { static struct ble_npl_callout *global_callout = NULL; -void mp_bluetooth_nimble_os_callout_process(void) { +STATIC void ble_npl_run_callouts(void) { os_sr_t sr; OS_ENTER_CRITICAL(sr); uint32_t tnow = mp_hal_ticks_ms(); @@ -502,3 +486,14 @@ void ble_npl_hw_exit_critical(uint32_t atomic_state) { DEBUG_CRIT_printf("ble_npl_hw_exit_critical(%u)\n", (uint)atomic_state); MICROPY_END_ATOMIC_SECTION(atomic_state); } + +/******************************************************************************/ + +void mp_bluetooth_nimble_run_host_stack(void) { + if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { + return; + } + + ble_npl_run_callouts(); + ble_npl_run_default_queue(); +} diff --git a/extmod/nimble/nimble/nimble_npl_os.h b/extmod/nimble/nimble/nimble_npl_os.h index 5c41bd22b4e38..bd3d8cb706fa2 100644 --- a/extmod/nimble/nimble/nimble_npl_os.h +++ b/extmod/nimble/nimble/nimble_npl_os.h @@ -62,13 +62,11 @@ struct ble_npl_event { ble_npl_event_fn *fn; void *arg; bool pending; - struct ble_npl_event *prev; struct ble_npl_event *next; }; struct ble_npl_eventq { struct ble_npl_event *head; - struct ble_npl_eventq *nextq; }; struct ble_npl_callout { @@ -89,8 +87,7 @@ struct ble_npl_sem { // --- Called by the MicroPython port ----------------------------------------- -void mp_bluetooth_nimble_os_eventq_run_all(void); -void mp_bluetooth_nimble_os_callout_process(void); +void mp_bluetooth_nimble_run_host_stack(void); // --- Must be provided by the MicroPython port ------------------------------- diff --git a/extmod/nimble/syscfg/syscfg.h b/extmod/nimble/syscfg/syscfg.h index a051a8fefd069..ce5e99905916d 100644 --- a/extmod/nimble/syscfg/syscfg.h +++ b/extmod/nimble/syscfg/syscfg.h @@ -17,7 +17,16 @@ void *nimble_realloc(void *ptr, size_t size); int nimble_sprintf(char *str, const char *fmt, ...); #define sprintf(str, fmt, ...) nimble_sprintf(str, fmt, __VA_ARGS__) +#ifndef min +#define min(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef max +#define max(a, b) ((a) > (b) ? (a) : (b)) +#endif + #define MYNEWT_VAL(x) MYNEWT_VAL_ ## x +#define MYNEWT_VAL_CHOICE(_name, _val) MYNEWT_VAL_ ## _name ## __ ## _val #define MYNEWT_VAL_LOG_LEVEL (255) @@ -91,7 +100,7 @@ int nimble_sprintf(char *str, const char *fmt, ...); #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_DEBUG (1) #define MYNEWT_VAL_BLE_HS_FLOW_CTRL (0) #define MYNEWT_VAL_BLE_HS_FLOW_CTRL_ITVL (1000) #define MYNEWT_VAL_BLE_HS_FLOW_CTRL_THRESH (2) @@ -101,6 +110,7 @@ int nimble_sprintf(char *str, const char *fmt, ...); #define MYNEWT_VAL_BLE_HS_STOP_ON_SHUTDOWN_TIMEOUT (2000) #define MYNEWT_VAL_BLE_L2CAP_COC_MAX_NUM (1) #define MYNEWT_VAL_BLE_L2CAP_COC_MPS (MYNEWT_VAL_MSYS_1_BLOCK_SIZE - 8) +#define MYNEWT_VAL_BLE_L2CAP_COC_SDU_BUFF_COUNT (1) #define MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC (0) #define MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS (1) #define MYNEWT_VAL_BLE_L2CAP_MAX_CHANS (3 * MYNEWT_VAL_BLE_MAX_CONNECTIONS) @@ -125,6 +135,8 @@ int nimble_sprintf(char *str, const char *fmt, ...); #define MYNEWT_VAL_BLE_STORE_MAX_BONDS (3) #define MYNEWT_VAL_BLE_STORE_MAX_CCCDS (8) // These can be overridden at runtime with ble.config(le_secure, mitm, bond, io). +#define MYNEWT_VAL_BLE_SM_LVL (0) +#define MYNEWT_VAL_BLE_SM_SC_ONLY (0) #define MYNEWT_VAL_BLE_SM_SC (1) #define MYNEWT_VAL_BLE_SM_MITM (0) #define MYNEWT_VAL_BLE_SM_BONDING (0) @@ -149,20 +161,40 @@ int nimble_sprintf(char *str, const char *fmt, ...); #define MYNEWT_VAL_BLE_HCI_TRANSPORT_UART (1) /*** nimble/transport/uart */ -#define MYNEWT_VAL_BLE_ACL_BUF_COUNT (12) -#define MYNEWT_VAL_BLE_ACL_BUF_SIZE (255) -#define MYNEWT_VAL_BLE_HCI_ACL_OUT_COUNT (12) -#define MYNEWT_VAL_BLE_HCI_EVT_BUF_SIZE (70) -#define MYNEWT_VAL_BLE_HCI_EVT_HI_BUF_COUNT (8) -#define MYNEWT_VAL_BLE_HCI_EVT_LO_BUF_COUNT (8) +#define MYNEWT_VAL_BLE_TRANSPORT_ACL_COUNT (12) +#define MYNEWT_VAL_BLE_TRANSPORT_ACL_SIZE (255) +#define MYNEWT_VAL_BLE_TRANSPORT_ISO_COUNT (10) +#define MYNEWT_VAL_BLE_TRANSPORT_ISO_SIZE (300) +#define MYNEWT_VAL_BLE_TRANSPORT_EVT_COUNT (4) +#define MYNEWT_VAL_BLE_TRANSPORT_EVT_DISCARDABLE_COUNT (16) +#define MYNEWT_VAL_BLE_TRANSPORT_EVT_SIZE (70) +#define MYNEWT_VAL_BLE_TRANSPORT_ACL_FROM_HS_COUNT (MYNEWT_VAL_BLE_TRANSPORT_ACL_COUNT) +#define MYNEWT_VAL_BLE_TRANSPORT_ACL_FROM_LL_COUNT (MYNEWT_VAL_BLE_TRANSPORT_ACL_COUNT) +#define MYNEWT_VAL_BLE_TRANSPORT_ISO_FROM_HS_COUNT (MYNEWT_VAL_BLE_TRANSPORT_ISO_COUNT) +#define MYNEWT_VAL_BLE_TRANSPORT_ISO_FROM_LL_COUNT (MYNEWT_VAL_BLE_TRANSPORT_ISO_COUNT) /* Overridden by targets/porting-nimble (defined by nimble/transport/uart) */ -#define MYNEWT_VAL_BLE_HCI_UART_BAUD (MICROPY_HW_BLE_UART_BAUDRATE) -#define MYNEWT_VAL_BLE_HCI_UART_DATA_BITS (8) -#define MYNEWT_VAL_BLE_HCI_UART_FLOW_CTRL (1) -#define MYNEWT_VAL_BLE_HCI_UART_PARITY (HAL_UART_PARITY_NONE) -#define MYNEWT_VAL_BLE_HCI_UART_PORT (MICROPY_HW_BLE_UART_ID) -#define MYNEWT_VAL_BLE_HCI_UART_STOP_BITS (1) + +enum hal_uart_parity { + HAL_UART_PARITY_ODD, + HAL_UART_PARITY_EVEN, + HAL_UART_PARITY_NONE +}; + +enum hal_uart_flow_ctl { + HAL_UART_FLOW_CTL_RTS_CTS, + HAL_UART_FLOW_CTL_NONE +}; + +#define MYNEWT_VAL_BLE_TRANSPORT_UART_BAUDRATE (MICROPY_HW_BLE_UART_BAUDRATE) +#define MYNEWT_VAL_BLE_TRANSPORT_UART_DATA_BITS (8) +#define MYNEWT_VAL_BLE_TRANSPORT_UART_FLOW_CONTROL__rtscts (1) +#define MYNEWT_VAL_BLE_TRANSPORT_UART_FLOW_CONTROL__none (0) +#define MYNEWT_VAL_BLE_TRANSPORT_UART_PARITY__odd (0) +#define MYNEWT_VAL_BLE_TRANSPORT_UART_PARITY__even (0) +#define MYNEWT_VAL_BLE_TRANSPORT_UART_PARITY__none (1) +#define MYNEWT_VAL_BLE_TRANSPORT_UART_PORT (MICROPY_HW_BLE_UART_ID) +#define MYNEWT_VAL_BLE_TRANSPORT_UART_STOP_BITS (1) /* Required for code that uses BLE_HS_LOG */ #define MYNEWT_VAL_NEWT_FEATURE_LOGCFG (1) diff --git a/lib/mynewt-nimble b/lib/mynewt-nimble index 42849560ba790..ceb07308efa13 160000 --- a/lib/mynewt-nimble +++ b/lib/mynewt-nimble @@ -1 +1 @@ -Subproject commit 42849560ba7906f023f61e5f7ff3709ba2c1dfca +Subproject commit ceb07308efa13ed1d8babe81020653c04d2896db diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index d5bb7081b01aa..b439e97b23ab5 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -269,7 +269,10 @@ SRC_C += mpbthciport.c endif # MICROPY_PY_BLUETOOTH ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) -SRC_C += mpnimbleport.c +# Use the common scheduler-based implementation of mpnimbleport.c. +SRC_THIRDPARTY_C += $(addprefix $(NIMBLE_EXTMOD_DIR)/, \ + mpnimbleport_scheduler.c \ + ) endif # Math library source files diff --git a/ports/mimxrt/mpbthciport.c b/ports/mimxrt/mpbthciport.c index d8b384136452c..4115ef1119642 100644 --- a/ports/mimxrt/mpbthciport.c +++ b/ports/mimxrt/mpbthciport.c @@ -67,8 +67,15 @@ void mp_bluetooth_hci_poll_in_ms(uint32_t ms) { // For synchronous mode, we run all BLE stack code inside a scheduled task. STATIC void run_events_scheduled_task(mp_sched_node_t *node) { + (void)node; + + // Provided by either mpnimbleport.c or mpbtstackport.c. + extern bool mp_bluetooth_run_hci_uart(void); + extern bool mp_bluetooth_run_host_stack(void); + // This will process all buffered HCI UART data, and run any callouts or events. - mp_bluetooth_hci_poll(); + mp_bluetooth_run_hci_uart(); + mp_bluetooth_run_host_stack(); } // Called periodically (systick) or directly (e.g. UART RX IRQ) in order to diff --git a/ports/mimxrt/mpnimbleport.c b/ports/mimxrt/mpnimbleport.c deleted file mode 100644 index e2d39e271ce6c..0000000000000 --- a/ports/mimxrt/mpnimbleport.c +++ /dev/null @@ -1,78 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2019 Jim Mussared - * 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_NIMBLE - -#define DEBUG_printf(...) // printf("mpnimbleport.c: " __VA_ARGS__) - -#include "host/ble_hs.h" -#include "nimble/nimble_npl.h" - -#include "extmod/mpbthci.h" -#include "extmod/modbluetooth.h" -#include "extmod/nimble/modbluetooth_nimble.h" -#include "extmod/nimble/hal/hal_uart.h" -#include "mpbthciport.h" - -// Get any pending data from the UART and send it to NimBLE's HCI buffers. -// Any further processing by NimBLE will be run via its event queue. -void mp_bluetooth_hci_poll(void) { - if (mp_bluetooth_nimble_ble_state >= MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC) { - // DEBUG_printf("mp_bluetooth_hci_poll_uart %d\n", mp_bluetooth_nimble_ble_state); - - // Run any timers. - mp_bluetooth_nimble_os_callout_process(); - - // Process incoming UART data, and run events as they are generated. - mp_bluetooth_nimble_hci_uart_process(true); - - // Run any remaining events (e.g. if there was no UART data). - mp_bluetooth_nimble_os_eventq_run_all(); - } - - if (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { - // Call this function again in 128ms to check for new events. - // TODO: improve this by only calling back when needed. - mp_bluetooth_hci_poll_in_ms(128); - } -} - -// --- Port-specific helpers for the generic NimBLE bindings. ----------------- - -void mp_bluetooth_nimble_hci_uart_wfi(void) { - __WFI(); - - // This is called while NimBLE is waiting in ble_npl_sem_pend, i.e. waiting for an HCI ACK. - // Do not need to run events here (it must not invoke Python code), only processing incoming HCI data. - mp_bluetooth_nimble_hci_uart_process(false); -} - -#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE diff --git a/ports/renesas-ra/Makefile b/ports/renesas-ra/Makefile index 8126dbe7772d5..151609d064839 100644 --- a/ports/renesas-ra/Makefile +++ b/ports/renesas-ra/Makefile @@ -353,7 +353,10 @@ SRC_C += mpbthciport.c endif ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) -SRC_C += mpnimbleport.c +# Use the common scheduler-based implementation of mpnimbleport.c. +SRC_THIRDPARTY_C += $(addprefix $(NIMBLE_EXTMOD_DIR)/, \ + mpnimbleport_scheduler.c \ + ) endif SRC_O += \ diff --git a/ports/renesas-ra/mpbthciport.c b/ports/renesas-ra/mpbthciport.c index 5092f8709a126..ecf06e00d6074 100644 --- a/ports/renesas-ra/mpbthciport.c +++ b/ports/renesas-ra/mpbthciport.c @@ -68,8 +68,15 @@ void mp_bluetooth_hci_poll_in_ms(uint32_t ms) { // For synchronous mode, we run all BLE stack code inside a scheduled task. STATIC void run_events_scheduled_task(mp_sched_node_t *node) { + (void)node; + + // Provided by either mpnimbleport.c or mpbtstackport.c. + extern bool mp_bluetooth_run_hci_uart(void); + extern bool mp_bluetooth_run_host_stack(void); + // This will process all buffered HCI UART data, and run any callouts or events. - mp_bluetooth_hci_poll(); + mp_bluetooth_run_hci_uart(); + mp_bluetooth_run_host_stack(); } // Called periodically (systick) or directly (e.g. UART RX IRQ) in order to diff --git a/ports/renesas-ra/mpnimbleport.c b/ports/renesas-ra/mpnimbleport.c deleted file mode 100644 index 904b1a804ee76..0000000000000 --- a/ports/renesas-ra/mpnimbleport.c +++ /dev/null @@ -1,78 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2019-2023 Jim Mussared - * Copyright (c) 2020-2023 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_NIMBLE - -#define DEBUG_printf(...) // printf("mpnimbleport.c: " __VA_ARGS__) - -#include "host/ble_hs.h" -#include "nimble/nimble_npl.h" - -#include "extmod/mpbthci.h" -#include "extmod/modbluetooth.h" -#include "extmod/nimble/modbluetooth_nimble.h" -#include "extmod/nimble/hal/hal_uart.h" -#include "mpbthciport.h" - -// Get any pending data from the UART and send it to NimBLE's HCI buffers. -// Any further processing by NimBLE will be run via its event queue. -void mp_bluetooth_hci_poll(void) { - if (mp_bluetooth_nimble_ble_state >= MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC) { - // DEBUG_printf("mp_bluetooth_hci_poll_uart %d\n", mp_bluetooth_nimble_ble_state); - - // Run any timers. - mp_bluetooth_nimble_os_callout_process(); - - // Process incoming UART data, and run events as they are generated. - mp_bluetooth_nimble_hci_uart_process(true); - - // Run any remaining events (e.g. if there was no UART data). - mp_bluetooth_nimble_os_eventq_run_all(); - } - - if (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { - // Call this function again in 128ms to check for new events. - // TODO: improve this by only calling back when needed. - mp_bluetooth_hci_poll_in_ms(128); - } -} - -// --- Port-specific helpers for the generic NimBLE bindings. ----------------- - -void mp_bluetooth_nimble_hci_uart_wfi(void) { - __WFI(); - - // This is called while NimBLE is waiting in ble_npl_sem_pend, i.e. waiting for an HCI ACK. - // Do not need to run events here (it must not invoke Python code), only processing incoming HCI data. - mp_bluetooth_nimble_hci_uart_process(false); -} - -#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 8a3dd80e8dd9a..b90ff96a0ea8b 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -259,7 +259,7 @@ if(MICROPY_BLUETOOTH_NIMBLE) message(FATAL_ERROR " mynewt-nimble not initialized.\n Run 'make BOARD=${MICROPY_BOARD} submodules'") endif() - list(APPEND MICROPY_SOURCE_PORT mpnimbleport.c) + list(APPEND MICROPY_SOURCE_PORT ${MICROPY_DIR}/extmod/nimble/mpnimbleport_scheduler.c) target_compile_definitions(${MICROPY_TARGET} PRIVATE MICROPY_BLUETOOTH_NIMBLE=1 MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY=0 diff --git a/ports/rp2/mpbthciport.c b/ports/rp2/mpbthciport.c index 7722360920916..79d1c6ea94c8e 100644 --- a/ports/rp2/mpbthciport.c +++ b/ports/rp2/mpbthciport.c @@ -65,8 +65,14 @@ void mp_bluetooth_hci_poll_in_ms(uint32_t ms) { // This task is scheduled periodically via a timer, or immediately after UART RX IRQ. STATIC void run_events_scheduled_task(mp_sched_node_t *node) { (void)node; + + // Provided by either mpnimbleport.c or mpbtstackport.c. + extern bool mp_bluetooth_run_hci_uart(void); + extern bool mp_bluetooth_run_host_stack(void); + // This will process all buffered HCI UART data, and run any callouts or events. - mp_bluetooth_hci_poll(); + mp_bluetooth_run_hci_uart(); + mp_bluetooth_run_host_stack(); } // Called periodically (systick) or directly (e.g. UART RX IRQ) in order to diff --git a/ports/rp2/mpbthciport.h b/ports/rp2/mpbthciport.h index fdf46c506c16b..978fb41379da8 100644 --- a/ports/rp2/mpbthciport.h +++ b/ports/rp2/mpbthciport.h @@ -33,10 +33,4 @@ void mp_bluetooth_hci_init(void); void mp_bluetooth_hci_poll_now(void); void mp_bluetooth_hci_poll_in_ms(uint32_t ms); -// Must be provided by the stack bindings (e.g. mpnimbleport.c or mpbtstackport.c). -// Request new HCI data and pass to the stack, and run pending events/callouts. -// This is a low-level function and should not be called directly, use -// mp_bluetooth_hci_poll_now/mp_bluetooth_hci_poll_in_ms instead. -void mp_bluetooth_hci_poll(void); - #endif // MICROPY_INCLUDED_RP2_MPBTHCIPORT_H diff --git a/ports/rp2/mpnimbleport.c b/ports/rp2/mpnimbleport.c deleted file mode 100644 index 74e9ecb02602a..0000000000000 --- a/ports/rp2/mpnimbleport.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2019 Jim Mussared - * 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" -#include "py/stream.h" - -#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE - -#define DEBUG_printf(...) // printf("mpnimbleport.c: " __VA_ARGS__) - -#include "host/ble_hs.h" -#include "nimble/nimble_npl.h" - -#include "extmod/modbluetooth.h" -#include "extmod/mpbthci.h" -#include "extmod/nimble/modbluetooth_nimble.h" -#include "extmod/nimble/hal/hal_uart.h" -#include "mpbthciport.h" - -// Get any pending data from the UART and send it to NimBLE's HCI buffers. -// Any further processing by NimBLE will be run via its event queue. -void mp_bluetooth_hci_poll(void) { - if (mp_bluetooth_nimble_ble_state >= MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC) { - // DEBUG_printf("mp_bluetooth_hci_poll_uart %d\n", mp_bluetooth_nimble_ble_state); - - // Run any timers. - mp_bluetooth_nimble_os_callout_process(); - - // Process incoming UART data, and run events as they are generated. - mp_bluetooth_nimble_hci_uart_process(true); - - // Run any remaining events (e.g. if there was no UART data). - mp_bluetooth_nimble_os_eventq_run_all(); - } - - if (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { - // Call this function again in 128ms to check for new events. - // TODO: improve this by only calling back when needed. - mp_bluetooth_hci_poll_in_ms(128); - } -} - -// --- Port-specific helpers for the generic NimBLE bindings. ----------------- - -void mp_bluetooth_nimble_hci_uart_wfi(void) { - #if defined(__WFI) - __WFI(); - #endif - // This is called while NimBLE is waiting in ble_npl_sem_pend, i.e. waiting for an HCI ACK. - // Do not need to run events here (it must not invoke Python code), only processing incoming HCI data. - mp_bluetooth_nimble_hci_uart_process(false); -} - -#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 475d8f1004f08..6bb88eb8f654d 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -490,7 +490,10 @@ ifeq ($(MICROPY_PY_BLUETOOTH),1) SRC_C += mpbthciport.c ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) -SRC_C += mpnimbleport.c +# Use the common scheduler-based implementation of mpnimbleport.c. +SRC_THIRDPARTY_C += $(addprefix $(NIMBLE_EXTMOD_DIR)/, \ + mpnimbleport_scheduler.c \ + ) endif ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) diff --git a/ports/stm32/mpbthciport.c b/ports/stm32/mpbthciport.c index 7c7031bf2f825..bf14fa299321f 100644 --- a/ports/stm32/mpbthciport.c +++ b/ports/stm32/mpbthciport.c @@ -71,9 +71,15 @@ STATIC mp_sched_node_t mp_bluetooth_hci_sched_node; // This task is scheduled periodically via a soft timer, or // immediately on HCI UART RXIDLE. STATIC void run_events_scheduled_task(mp_sched_node_t *node) { - // This will process all buffered HCI UART data, and run any callouts or events. (void)node; - mp_bluetooth_hci_poll(); + + // Provided by either mpnimbleport.c or mpbtstackport.c. + extern bool mp_bluetooth_run_hci_uart(void); + extern bool mp_bluetooth_run_host_stack(void); + + // This will process all buffered HCI UART data, and run any callouts or events. + mp_bluetooth_run_hci_uart(); + mp_bluetooth_run_host_stack(); } // Called periodically (systick) or directly (e.g. UART RX IRQ) in order to @@ -146,6 +152,11 @@ mp_irq_obj_t mp_bluetooth_hci_uart_irq_obj; static uint8_t hci_uart_rxbuf[768]; mp_obj_t mp_uart_interrupt(mp_obj_t self_in) { + // Note: This could potentially run mp_bluetooth_run_hci_uart() directly + // to get the data out of the UART with as little latency as possible. + // Right now to keep things simple we run _everything_ in scheduler + // context. + // Queue up the scheduler to run the HCI UART and event processing ASAP. mp_bluetooth_hci_poll_now(); diff --git a/ports/stm32/mpbthciport.h b/ports/stm32/mpbthciport.h index d510ab52ad5c7..4267496e4157e 100644 --- a/ports/stm32/mpbthciport.h +++ b/ports/stm32/mpbthciport.h @@ -45,10 +45,4 @@ static inline void mp_bluetooth_hci_poll_in_ms(uint32_t ms) { MICROPY_BOARD_BT_HCI_POLL_IN_MS(ms); } -// Must be provided by the stack bindings (e.g. mpnimbleport.c or mpbtstackport.c). -// Request new data from the uart and pass to the stack, and run pending events/callouts. -// This is a low-level function and should not be called directly, use -// mp_bluetooth_hci_poll_now/mp_bluetooth_hci_poll_in_ms instead. -void mp_bluetooth_hci_poll(void); - #endif // MICROPY_INCLUDED_STM32_MPBTHCIPORT_H diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 832a34e45b037..c74d5a0269ddf 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -298,7 +298,7 @@ static inline mp_uint_t disable_irq(void) { #endif #ifndef MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS -#define MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS (MICROPY_BLUETOOTH_NIMBLE) +#define MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS (0)// MICROPY_BLUETOOTH_NIMBLE) #endif // We need an implementation of the log2 function which is not a macro diff --git a/ports/stm32/mpnimbleport.c b/ports/stm32/mpnimbleport.c deleted file mode 100644 index e2d39e271ce6c..0000000000000 --- a/ports/stm32/mpnimbleport.c +++ /dev/null @@ -1,78 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2019 Jim Mussared - * 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_NIMBLE - -#define DEBUG_printf(...) // printf("mpnimbleport.c: " __VA_ARGS__) - -#include "host/ble_hs.h" -#include "nimble/nimble_npl.h" - -#include "extmod/mpbthci.h" -#include "extmod/modbluetooth.h" -#include "extmod/nimble/modbluetooth_nimble.h" -#include "extmod/nimble/hal/hal_uart.h" -#include "mpbthciport.h" - -// Get any pending data from the UART and send it to NimBLE's HCI buffers. -// Any further processing by NimBLE will be run via its event queue. -void mp_bluetooth_hci_poll(void) { - if (mp_bluetooth_nimble_ble_state >= MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC) { - // DEBUG_printf("mp_bluetooth_hci_poll_uart %d\n", mp_bluetooth_nimble_ble_state); - - // Run any timers. - mp_bluetooth_nimble_os_callout_process(); - - // Process incoming UART data, and run events as they are generated. - mp_bluetooth_nimble_hci_uart_process(true); - - // Run any remaining events (e.g. if there was no UART data). - mp_bluetooth_nimble_os_eventq_run_all(); - } - - if (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { - // Call this function again in 128ms to check for new events. - // TODO: improve this by only calling back when needed. - mp_bluetooth_hci_poll_in_ms(128); - } -} - -// --- Port-specific helpers for the generic NimBLE bindings. ----------------- - -void mp_bluetooth_nimble_hci_uart_wfi(void) { - __WFI(); - - // This is called while NimBLE is waiting in ble_npl_sem_pend, i.e. waiting for an HCI ACK. - // Do not need to run events here (it must not invoke Python code), only processing incoming HCI data. - mp_bluetooth_nimble_hci_uart_process(false); -} - -#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE diff --git a/ports/unix/mpbthciport.c b/ports/unix/mpbthciport.c index c94925ccb414e..adf8224464da8 100644 --- a/ports/unix/mpbthciport.c +++ b/ports/unix/mpbthciport.c @@ -48,6 +48,7 @@ #include #include #include +#include #define DEBUG_printf(...) // printf(__VA_ARGS__) @@ -61,27 +62,60 @@ uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; STATIC int uart_fd = -1; // Must be provided by the stack bindings (e.g. mpnimbleport.c or mpbtstackport.c). -extern bool mp_bluetooth_hci_poll(void); +extern bool mp_bluetooth_run_hci_uart(void); +extern bool mp_bluetooth_run_host_stack(void); // Allows the stack to tell us that we should stop running the hci poll loop. extern bool mp_bluetooth_hci_active(void); -STATIC const useconds_t UART_POLL_INTERVAL_US = 1000; -STATIC pthread_t hci_poll_thread_id; +STATIC const useconds_t UART_POLL_INTERVAL_MS = 200; +STATIC const useconds_t TIMER_POLL_INTERVAL_MS = 1; +STATIC pthread_t hci_uart_poll_thread_id; +STATIC pthread_t hci_event_poll_thread_id; + +// This simulates a UART RX irq that delivers incoming UART data into the host +// stack as it arrives. +STATIC void *hci_uart_poll_thread(void *arg) { + (void)arg; + + DEBUG_printf("hci_uart_poll_thread: start\n"); + + while (mp_bluetooth_hci_active()) { + // Wait for the UART to become readable (or closed). + struct pollfd fd = { .fd = uart_fd, .events = POLLIN | POLLHUP }; + int ret = poll(&fd, 1, UART_POLL_INTERVAL_MS); + if (ret > 0 && (fd.revents & POLLIN)) { + mp_bluetooth_run_hci_uart(); + } + } + + DEBUG_printf("~hci_uart_poll_thread\n"); + + return NULL; +} // We run the host stack periodically (every 1ms) by a background thread. // Because MICROPY_PY_THREAD_RTOS is enabled, when the host stack tries // to call into Python, it will use mp_thread_run_on_mp_thread to run // the callbacks on a Python thread. -STATIC void *hci_poll_thread(void *arg) { +STATIC void *hci_event_poll_thread(void *arg) { (void)arg; - // TODO: Replace with select-readable on the uart with a 1ms timeout. + DEBUG_printf("hci_event_poll_thread: start\n"); + while (mp_bluetooth_hci_active()) { - mp_bluetooth_hci_poll(); - usleep(UART_POLL_INTERVAL_US); + // TODO: A potential optimisation here would be to immediately wake up + // hci_event_poll_thread when the UART thread receives data, which + // would decrease the latency of responding to the incoming UART data, + // and allow a longer (more efficient) sleep interval. Alternatively, + // this could know more about the stack's internals (e.g. length of + // queue or next timer event). + usleep(TIMER_POLL_INTERVAL_MS * 1000); + mp_bluetooth_run_host_stack(); } + DEBUG_printf("~hci_event_poll_thread\n"); + return NULL; } @@ -131,6 +165,8 @@ STATIC int configure_uart(void) { return 0; } +pthread_mutex_t nimble_mutex; + // HCI UART bindings. int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { (void)port; @@ -162,11 +198,17 @@ int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { return -1; } - // Create a thread to run the polling loop. - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - pthread_create(&hci_poll_thread_id, &attr, &hci_poll_thread, NULL); + pthread_mutexattr_t nimble_mutex_attr; + pthread_mutexattr_init(&nimble_mutex_attr); + pthread_mutexattr_settype(&nimble_mutex_attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&nimble_mutex, &nimble_mutex_attr); + + // Create a threads to run the uart and the host stack. + pthread_attr_t joinable_attr; + pthread_attr_init(&joinable_attr); + pthread_attr_setdetachstate(&joinable_attr, PTHREAD_CREATE_JOINABLE); + pthread_create(&hci_uart_poll_thread_id, &joinable_attr, &hci_uart_poll_thread, NULL); + pthread_create(&hci_event_poll_thread_id, &joinable_attr, &hci_event_poll_thread, NULL); return 0; } @@ -175,16 +217,20 @@ int mp_bluetooth_hci_uart_deinit(void) { DEBUG_printf("mp_bluetooth_hci_uart_deinit\n"); if (uart_fd == -1) { + DEBUG_printf("--> nothing to do\n"); return 0; } - // Wait for the poll loop to terminate when the state is set to OFF. - pthread_join(hci_poll_thread_id, NULL); - // Close the UART. close(uart_fd); uart_fd = -1; + // Wait for the uart + host stack threads to terminate when the state is set to OFF. + pthread_join(hci_uart_poll_thread_id, NULL); + pthread_join(hci_event_poll_thread_id, NULL); + + DEBUG_printf("~mp_bluetooth_hci_uart_deinit\n"); + return 0; } diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index a665796f7e510..18476ce2a58b0 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -249,5 +249,5 @@ static inline unsigned long mp_random_seed_init(void) { #endif #ifndef MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS -#define MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS (MICROPY_BLUETOOTH_NIMBLE) +#define MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS (0)// MICROPY_BLUETOOTH_NIMBLE) #endif diff --git a/ports/unix/mpnimbleport.c b/ports/unix/mpnimbleport.c index 29f558f74db51..3644765ce3491 100644 --- a/ports/unix/mpnimbleport.c +++ b/ports/unix/mpnimbleport.c @@ -33,42 +33,51 @@ #include "nimble/nimble_npl.h" #include "extmod/nimble/modbluetooth_nimble.h" -#include "extmod/nimble/hal/hal_uart.h" +#include "extmod/nimble/transport/uart_ll.h" #define DEBUG_printf(...) // printf(__VA_ARGS__) -// Called by the UART polling thread in mpbthciport.c. -bool mp_bluetooth_hci_poll(void) { - // DEBUG_printf("mp_bluetooth_hci_poll (unix nimble) %d\n", mp_bluetooth_nimble_ble_state); +extern pthread_mutex_t nimble_mutex; - if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { - DEBUG_printf("mp_bluetooth_hci_poll (unix nimble) -- shutdown\n"); +bool mp_bluetooth_hci_active(void) { + return mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF; +} + +bool mp_bluetooth_run_hci_uart(void) { + if (!mp_bluetooth_hci_active()) { return false; } if (mp_bluetooth_nimble_ble_state >= MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC) { - // Run any timers. - mp_bluetooth_nimble_os_callout_process(); - - // Process incoming UART data, and run events as they are generated. - mp_bluetooth_nimble_hci_uart_process(true); - - // Run any remaining events (e.g. if there was no UART data). - mp_bluetooth_nimble_os_eventq_run_all(); + mp_bluetooth_nimble_hci_uart_process(); } return true; } -bool mp_bluetooth_hci_active(void) { - return mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF; +bool mp_bluetooth_run_host_stack(void) { + if (!mp_bluetooth_hci_active()) { + return false; + } + + if (mp_bluetooth_nimble_ble_state >= MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC) { + // Run any timers and pending events in the queue. + mp_bluetooth_nimble_run_host_stack(); + } + + return true; } // Extra port-specific helpers. void mp_bluetooth_nimble_hci_uart_wfi(void) { - // This is called while NimBLE is waiting in ble_npl_sem_pend, i.e. waiting for an HCI ACK. - // Do not need to run events here, only processing incoming HCI data. - mp_bluetooth_nimble_hci_uart_process(false); + // pthread_mutex_lock(&nimble_mutex); + // // This is called while NimBLE is waiting in ble_npl_sem_pend, i.e. waiting for an HCI ACK. + // // Do not need to run events here, only processing incoming HCI data. + // mp_bluetooth_nimble_hci_uart_process(false); + // pthread_mutex_unlock(&nimble_mutex); + // mp_bluetooth_hci_poll(true); + + sched_yield(); } #endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE From 44e5a86094ea40e48fb122ba99a67a1c8cb701f1 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 19 Sep 2023 23:06:26 +1000 Subject: [PATCH 10/11] tests/multi_bluetooth: Improve event ordering handling. For _IRQ_GATTC_READ_DONE, we should wait for it to ensure that we receive it before we sent the write (this prevents an ordering race with the "gattc_write" vs the "_IRQ_GATTC_READ_DONE" line). For _IRQ_GATTC_WRITE_DONE, don't print it out because we wait for it anyway (so it will be confirmed to have been generated), and it can race with the concurrent notify or indicate event. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared --- tests/multi_bluetooth/ble_characteristic.py | 10 +++++++++- tests/multi_bluetooth/ble_characteristic.py.exp | 3 --- tests/multi_bluetooth/ble_descriptor.py | 2 ++ tests/multi_bluetooth/ble_gap_device_name.py | 1 + tests/multi_bluetooth/ble_gap_pair.py | 1 + tests/multi_bluetooth/ble_gap_pair_bond.py | 1 + tests/multi_bluetooth/ble_subscribe.py | 1 + 7 files changed, 15 insertions(+), 4 deletions(-) diff --git a/tests/multi_bluetooth/ble_characteristic.py b/tests/multi_bluetooth/ble_characteristic.py index 50b12681eba91..b3773a7ca6688 100644 --- a/tests/multi_bluetooth/ble_characteristic.py +++ b/tests/multi_bluetooth/ble_characteristic.py @@ -65,7 +65,11 @@ def irq(event, data): elif event == _IRQ_GATTC_READ_DONE: print("_IRQ_GATTC_READ_DONE", data[-1]) elif event == _IRQ_GATTC_WRITE_DONE: - print("_IRQ_GATTC_WRITE_DONE", data[-1]) + if data[-1] != 0: + # Don't print successful write done, we wait for this event anyway + # (so it's definitely getting captured), and it may appear out of + # order with the notify/indicate that arrives at the same time. + print("_IRQ_GATTC_WRITE_DONE", data[-1]) elif event == _IRQ_GATTC_NOTIFY: print("_IRQ_GATTC_NOTIFY", bytes(data[-1])) elif event == _IRQ_GATTC_INDICATE: @@ -164,6 +168,7 @@ def instance1(): print("gattc_read") ble.gattc_read(conn_handle, value_handle) wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_READ_DONE, TIMEOUT_MS) # Write to the characteristic, which will trigger a notification. print("gattc_write") @@ -174,6 +179,7 @@ def instance1(): print("gattc_read") # Read the new value set immediately before notification. ble.gattc_read(conn_handle, value_handle) wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_READ_DONE, TIMEOUT_MS) # Write to the characteristic, which will trigger a value-included notification. print("gattc_write") @@ -184,6 +190,7 @@ def instance1(): print("gattc_read") # Read value should be unchanged. ble.gattc_read(conn_handle, value_handle) wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_READ_DONE, TIMEOUT_MS) # Write to the characteristic, which will trigger an indication. print("gattc_write") @@ -194,6 +201,7 @@ def instance1(): print("gattc_read") # Read the new value set immediately before indication. ble.gattc_read(conn_handle, value_handle) wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_READ_DONE, TIMEOUT_MS) # Write-without-response, which will trigger another notification with that value. ble.gattc_write(conn_handle, value_handle, "central3", 0) diff --git a/tests/multi_bluetooth/ble_characteristic.py.exp b/tests/multi_bluetooth/ble_characteristic.py.exp index e4c9f1a57e1c6..da64f02307dc0 100644 --- a/tests/multi_bluetooth/ble_characteristic.py.exp +++ b/tests/multi_bluetooth/ble_characteristic.py.exp @@ -22,19 +22,16 @@ gattc_read _IRQ_GATTC_READ_RESULT b'periph0' _IRQ_GATTC_READ_DONE 0 gattc_write -_IRQ_GATTC_WRITE_DONE 0 _IRQ_GATTC_NOTIFY b'periph1' gattc_read _IRQ_GATTC_READ_RESULT b'periph1' _IRQ_GATTC_READ_DONE 0 gattc_write -_IRQ_GATTC_WRITE_DONE 0 _IRQ_GATTC_NOTIFY b'periph2' gattc_read _IRQ_GATTC_READ_RESULT b'central1' _IRQ_GATTC_READ_DONE 0 gattc_write -_IRQ_GATTC_WRITE_DONE 0 _IRQ_GATTC_INDICATE b'periph3' gattc_read _IRQ_GATTC_READ_RESULT b'periph3' diff --git a/tests/multi_bluetooth/ble_descriptor.py b/tests/multi_bluetooth/ble_descriptor.py index 3effda9268bc0..dc09fa6139d8c 100644 --- a/tests/multi_bluetooth/ble_descriptor.py +++ b/tests/multi_bluetooth/ble_descriptor.py @@ -151,11 +151,13 @@ def instance1(): print("gattc_read characteristic") ble.gattc_read(conn_handle, value_handle) wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_READ_DONE, TIMEOUT_MS) # Issue read of descriptor, should get initial value. print("gattc_read descriptor") ble.gattc_read(conn_handle, desc_handle) wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_READ_DONE, TIMEOUT_MS) # Write-with-response to the characteristic. print("gattc_write characteristic") diff --git a/tests/multi_bluetooth/ble_gap_device_name.py b/tests/multi_bluetooth/ble_gap_device_name.py index 273c0bd11f4fd..b8cdb6acda8bb 100644 --- a/tests/multi_bluetooth/ble_gap_device_name.py +++ b/tests/multi_bluetooth/ble_gap_device_name.py @@ -110,6 +110,7 @@ def instance1(): print("gattc_read") ble.gattc_read(conn_handle, value_handle) wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_READ_DONE, TIMEOUT_MS) # Disconnect from peripheral. print("gap_disconnect:", ble.gap_disconnect(conn_handle)) diff --git a/tests/multi_bluetooth/ble_gap_pair.py b/tests/multi_bluetooth/ble_gap_pair.py index f10fa37074010..2904cffdee8c8 100644 --- a/tests/multi_bluetooth/ble_gap_pair.py +++ b/tests/multi_bluetooth/ble_gap_pair.py @@ -119,6 +119,7 @@ def instance1(): print("gattc_read") ble.gattc_read(conn_handle, value_handle) wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_READ_DONE, TIMEOUT_MS) # Disconnect from the peripheral. print("gap_disconnect:", ble.gap_disconnect(conn_handle)) diff --git a/tests/multi_bluetooth/ble_gap_pair_bond.py b/tests/multi_bluetooth/ble_gap_pair_bond.py index d7224cc127b1f..bcb6e90b0c73a 100644 --- a/tests/multi_bluetooth/ble_gap_pair_bond.py +++ b/tests/multi_bluetooth/ble_gap_pair_bond.py @@ -124,6 +124,7 @@ def instance1(): print("gattc_read") ble.gattc_read(conn_handle, value_handle) wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_READ_DONE, TIMEOUT_MS) # Disconnect from the peripheral. print("gap_disconnect:", ble.gap_disconnect(conn_handle)) diff --git a/tests/multi_bluetooth/ble_subscribe.py b/tests/multi_bluetooth/ble_subscribe.py index e80c18263ced3..2f98d5c81a233 100644 --- a/tests/multi_bluetooth/ble_subscribe.py +++ b/tests/multi_bluetooth/ble_subscribe.py @@ -191,6 +191,7 @@ def instance1(): print("gattc_read") ble.gattc_read(conn_handle, value_handle) wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_READ_DONE, TIMEOUT_MS) # While the four states are active, all incoming notifications # and indications will be printed by the event handler. We From 1ba264e9cae04c655dd196d032acd52d700ba31f Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Thu, 21 Sep 2023 16:26:31 +1000 Subject: [PATCH 11/11] tools/hci_trace_to_pcap.py: Support colour input. Filter out escape sequences to set the color. Also allow the "TX:" and "RX:" used in some places. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared --- tools/hci_trace_to_pcap.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tools/hci_trace_to_pcap.py b/tools/hci_trace_to_pcap.py index d55df3fb61ee0..cf06018d458f0 100755 --- a/tools/hci_trace_to_pcap.py +++ b/tools/hci_trace_to_pcap.py @@ -65,8 +65,15 @@ with open(sys.argv[1], "r") as f: for line in f: - line = line.strip() - m = re.match("([<>]) \\[ *([0-9]+)\\] ([A-Fa-f0-9:]+)", line) + # Remove color formatting. + line = line.strip().encode() + if line[0] == ord("\x1b"): + line = line[7:] + if len(line) >= 4 and line[-4] == ord("\x1b"): + line = line[:-4] + line = line.decode() + + m = re.match("([<>]) \\[ *([0-9]+)\\] (?:[TR]X: )?([A-Fa-f0-9:]+)", line) if not m: continue