summaryrefslogtreecommitdiffstatshomepage
path: root/extmod/modbluetooth.c
diff options
context:
space:
mode:
authorJim Mussared <jim.mussared@gmail.com>2020-10-28 13:06:45 +1100
committerDamien George <damien@micropython.org>2020-11-13 17:19:05 +1100
commit81e92d3d6e1a605a6115821ac24dcbc2546ba0f9 (patch)
treef640bf328a0364808b1946130e802621a554c239 /extmod/modbluetooth.c
parent6d9fdff8d07f3fa2a05eddb05e1a55754ae3542f (diff)
downloadmicropython-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.c224
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);