diff options
author | Jim Mussared <jim.mussared@gmail.com> | 2020-10-28 13:06:45 +1100 |
---|---|---|
committer | Damien George <damien@micropython.org> | 2020-11-13 17:19:05 +1100 |
commit | 81e92d3d6e1a605a6115821ac24dcbc2546ba0f9 (patch) | |
tree | f640bf328a0364808b1946130e802621a554c239 /extmod/modbluetooth.c | |
parent | 6d9fdff8d07f3fa2a05eddb05e1a55754ae3542f (diff) | |
download | micropython-81e92d3d6e1a605a6115821ac24dcbc2546ba0f9.tar.gz micropython-81e92d3d6e1a605a6115821ac24dcbc2546ba0f9.zip |
extmod/modbluetooth: Re-instate optional no-ringbuf modbluetooth.
This requires that the event handlers are called from non-interrupt context
(i.e. the MicroPython scheduler).
This will allow the BLE stack (e.g. NimBLE) to run from the scheduler
rather than an IRQ like PENDSV, and therefore be able to invoke Python
callbacks directly/synchronously. This allows writing Python BLE handlers
for events that require immediate response such as _IRQ_READ_REQUEST (which
was previous a hard IRQ) and future events relating to pairing/bonding.
Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
Diffstat (limited to 'extmod/modbluetooth.c')
-rw-r--r-- | extmod/modbluetooth.c | 224 |
1 files changed, 181 insertions, 43 deletions
diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 6bad134959..d8068df594 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -47,15 +47,18 @@ #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]; @@ -64,8 +67,6 @@ typedef struct { mp_obj_array_t irq_data_data; mp_obj_bluetooth_uuid_t irq_data_uuid; ringbuf_t ringbuf; - #if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK - mp_obj_t irq_read_request_data_tuple; #endif } mp_obj_bluetooth_ble_t; @@ -206,8 +207,7 @@ STATIC mp_int_t bluetooth_uuid_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bu return 0; } -#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE - +#if !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS && MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE 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); @@ -252,14 +252,10 @@ 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); - #if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK - // Pre-allocate a separate data tuple for the read request "hard" irq. - o->irq_read_request_data_tuple = mp_obj_new_tuple(2, NULL); - #endif - // 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); @@ -268,6 +264,7 @@ STATIC mp_obj_t bluetooth_ble_make_new(const mp_obj_type_t *type, size_t n_args, // Allocate the default ringbuf. ringbuf_alloc(&o->ringbuf, MICROPY_PY_BLUETOOTH_RINGBUF_SIZE); + #endif MP_STATE_VM(bluetooth) = MP_OBJ_FROM_PTR(o); } @@ -290,8 +287,6 @@ STATIC mp_obj_t bluetooth_ble_active(size_t n_args, const mp_obj_t *args) { STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_active_obj, 1, 2, bluetooth_ble_active); STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { - mp_obj_bluetooth_ble_t *self = MP_OBJ_TO_PTR(args[0]); - if (kwargs->used == 0) { // Get config value if (n_args != 2) { @@ -311,8 +306,12 @@ 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); } - case MP_QSTR_rxbuf: + #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: @@ -334,6 +333,7 @@ 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); @@ -347,6 +347,7 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map 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; @@ -367,6 +368,7 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map 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)); @@ -845,8 +847,7 @@ const mp_obj_module_t mp_module_ubluetooth = { // Helpers -#include <stdio.h> - +#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; @@ -854,7 +855,7 @@ STATIC void ringbuf_extract(ringbuf_t *ringbuf, mp_obj_tuple_t *data_tuple, size for (size_t i = 0; i < n_u16; ++i) { data_tuple->items[j++] = MP_OBJ_NEW_SMALL_INT(ringbuf_get16(ringbuf)); } - if (n_u8) { + for (size_t i = 0; i < n_u8; ++i) { data_tuple->items[j++] = MP_OBJ_NEW_SMALL_INT(ringbuf_get(ringbuf)); } if (bytes_addr) { @@ -960,11 +961,166 @@ STATIC mp_obj_t bluetooth_ble_invoke_irq(mp_obj_t none_in) { 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(uint16_t event, + const uint16_t *u16, size_t n_u16, + const uint8_t *u8, size_t n_u8, + const uint8_t *addr, + const int8_t *i8, size_t n_i8, + const mp_obj_bluetooth_uuid_t *uuid, + const uint8_t *data, size_t data_len) { + 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_array_t mv_addr; + mp_obj_array_t mv_data; + + 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; + + for (size_t i = 0; i < n_u16; ++i) { + data_tuple->items[data_tuple->len++] = MP_OBJ_NEW_SMALL_INT(u16[i]); + } + for (size_t i = 0; i < n_u8; ++i) { + data_tuple->items[data_tuple->len++] = MP_OBJ_NEW_SMALL_INT(u8[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_i8; ++i) { + data_tuple->items[data_tuple->len++] = MP_OBJ_NEW_SMALL_INT(i8[i]); + } + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + if (uuid) { + data_tuple->items[data_tuple->len++] = MP_OBJ_FROM_PTR(uuid); + } + #endif + if (data) { + mp_obj_memoryview_init(&mv_data, 'B', 0, data_len, (void *)data); + data_tuple->items[data_tuple->len++] = MP_OBJ_FROM_PTR(&mv_data); + } + assert(data_tuple->len <= MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN); + + mp_obj_t result = mp_call_function_2(o->irq_handler, MP_OBJ_NEW_SMALL_INT(event), MP_OBJ_FROM_PTR(data_tuple)); + + mp_local_free(data_tuple); + + return result; +} + +#define NULL_U16 NULL +#define NULL_U8 NULL +#define NULL_ADDR NULL +#define NULL_I8 NULL +#define NULL_UUID NULL +#define NULL_DATA NULL + +void mp_bluetooth_gap_on_connected_disconnected(uint8_t event, uint16_t conn_handle, uint8_t addr_type, const uint8_t *addr) { + invoke_irq_handler(event, &conn_handle, 1, &addr_type, 1, addr, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); +} + +void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle) { + uint16_t args[] = {conn_handle, value_handle}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_WRITE, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); +} + +void mp_bluetooth_gatts_on_indicate_complete(uint16_t conn_handle, uint16_t value_handle, uint8_t status) { + uint16_t args[] = {conn_handle, value_handle}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE, args, 2, &status, 1, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); +} + +bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle) { + uint16_t args[] = {conn_handle, value_handle}; + mp_obj_t result = invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST, args, 2, NULL, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); + return result == mp_const_none || mp_obj_is_true(result); +} + +void mp_bluetooth_gatts_on_mtu_exchanged(uint16_t conn_handle, uint16_t value) { + uint16_t args[] = {conn_handle, value}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_MTU_EXCHANGED, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); +} + +#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +void mp_bluetooth_gap_on_scan_complete(void) { + invoke_irq_handler(MP_BLUETOOTH_IRQ_SCAN_DONE, NULL_U16, 0, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); +} + +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, size_t data_len) { + int8_t args[] = {adv_type, rssi}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_SCAN_RESULT, NULL_U16, 0, &addr_type, 1, addr, args, 2, NULL_UUID, data, data_len); +} + +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) { + uint16_t args[] = {conn_handle, start_handle, end_handle}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT, args, 3, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, service_uuid, NULL_DATA, 0); +} + +void mp_bluetooth_gattc_on_characteristic_result(uint16_t conn_handle, uint16_t def_handle, uint16_t value_handle, uint8_t properties, mp_obj_bluetooth_uuid_t *characteristic_uuid) { + uint16_t args[] = {conn_handle, def_handle, value_handle}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT, args, 3, &properties, 1, NULL_ADDR, NULL_I8, 0, characteristic_uuid, NULL_DATA, 0); +} + +void mp_bluetooth_gattc_on_descriptor_result(uint16_t conn_handle, uint16_t handle, mp_obj_bluetooth_uuid_t *descriptor_uuid) { + uint16_t args[] = {conn_handle, handle}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, descriptor_uuid, NULL_DATA, 0); +} + +void mp_bluetooth_gattc_on_discover_complete(uint8_t event, uint16_t conn_handle, uint16_t status) { + uint16_t args[] = {conn_handle, status}; + invoke_irq_handler(event, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); +} + +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) { + const uint8_t *combined_data; + uint16_t total_len; + + if (num > 1) { + // Fragmented buffer, need to combine into a new heap-allocated buffer + // in order to pass to Python. + total_len = 0; + for (size_t i = 0; i < num; ++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 < num; ++i) { + memcpy(p, data[i], data_len[i]); + p += data_len[i]; + } + combined_data = buf; + } else { + // Single buffer, use directly. + combined_data = *data; + total_len = *data_len; + } + + uint16_t args[] = {conn_handle, value_handle}; + invoke_irq_handler(event, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, combined_data, total_len); + + if (num > 1) { + m_del(uint8_t, (uint8_t *)combined_data, total_len); + } +} + +void mp_bluetooth_gattc_on_read_write_status(uint8_t event, uint16_t conn_handle, uint16_t value_handle, uint16_t status) { + uint16_t args[] = {conn_handle, value_handle, status}; + invoke_irq_handler(event, args, 3, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); +} + +#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + +#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. @@ -1169,36 +1325,12 @@ void mp_bluetooth_gattc_on_read_write_status(uint8_t event, uint16_t conn_handle #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE -#if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK -// This can only be enabled when the thread invoking this is a MicroPython thread. -// On ESP32, for example, this is not the case. bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle) { - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if (o->irq_handler != mp_const_none) { - // When executing code within a handler we must lock the scheduler to - // prevent any scheduled callbacks from running, and lock the GC to - // prevent any memory allocations. - mp_sched_lock(); - gc_lock(); - - // Use pre-allocated tuple distinct to the one used by the "soft" IRQs. - mp_obj_tuple_t *data = MP_OBJ_TO_PTR(o->irq_read_request_data_tuple); - data->items[0] = MP_OBJ_NEW_SMALL_INT(conn_handle); - data->items[1] = MP_OBJ_NEW_SMALL_INT(value_handle); - data->len = 2; - mp_obj_t irq_ret = mp_call_function_2_protected(o->irq_handler, MP_OBJ_NEW_SMALL_INT(MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST), o->irq_read_request_data_tuple); - - gc_unlock(); - mp_sched_unlock(); - - // If the IRQ handler explicitly returned false, then deny the read. Otherwise if it returns None/True, allow it. - return irq_ret != MP_OBJ_NULL && (irq_ret == mp_const_none || mp_obj_is_true(irq_ret)); - } else { - // No IRQ handler, allow the read. - return true; - } + (void)conn_handle; + (void)value_handle; + // This must be handled synchronously and therefore cannot implemented with the ringbuffer. + return true; } -#endif void mp_bluetooth_gatts_on_mtu_exchanged(uint16_t conn_handle, uint16_t value) { MICROPY_PY_BLUETOOTH_ENTER @@ -1210,6 +1342,12 @@ void mp_bluetooth_gatts_on_mtu_exchanged(uint16_t conn_handle, uint16_t value) { schedule_ringbuf(atomic_state); } +#endif // MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS + +// ---------------------------------------------------------------------------- +// GATTS DB +// ---------------------------------------------------------------------------- + void mp_bluetooth_gatts_db_create_entry(mp_gatts_db_t db, uint16_t handle, size_t len) { mp_map_elem_t *elem = mp_map_lookup(db, MP_OBJ_NEW_SMALL_INT(handle), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); mp_bluetooth_gatts_db_entry_t *entry = m_new(mp_bluetooth_gatts_db_entry_t, 1); |