Skip to content

unix: Fix support for Bluetooth via libusb, add HCI dump feature #14006

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 43 additions & 10 deletions ports/unix/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,37 @@ QSTR_GLOBAL_DEPENDENCIES += $(VARIANT_DIR)/mpconfigvariant.h
# OS name, for simple autoconfig
UNAME_S := $(shell uname -s)

# If the variant enables it, enable modbluetooth.
ifeq ($(MICROPY_PY_BLUETOOTH),1)
ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1)
HAVE_LIBUSB := $(shell (which pkg-config > /dev/null && pkg-config --exists libusb-1.0) 2>/dev/null && echo '1')

# Figure out which BTstack transport to use.
ifeq ($(HAVE_LIBUSB),1)
# Default to btstack-over-usb.
MICROPY_BLUETOOTH_BTSTACK_USB ?= 1
MICROPY_BLUETOOTH_BTSTACK_H4 ?= 0
else
# Fallback to HCI controller via a H4 UART (e.g. Zephyr on nRF) over a /dev/tty serial port.
MICROPY_BLUETOOTH_BTSTACK_USB ?= 0
MICROPY_BLUETOOTH_BTSTACK_H4 ?= 1
endif

# MICROPY_BLUETOOTH_BTSTACK_HCI_DUMP could be set to 1 to activate logging
# to file "/tmp/hci_dump.pklg" for btstack-over-usb.
ifndef MICROPY_BLUETOOTH_BTSTACK_HCI_DUMP
MICROPY_BLUETOOTH_BTSTACK_HCI_DUMP ?= 0
endif
Copy link
Contributor

@projectgus projectgus Oct 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
endif
endif # MICROPY_PY_BLUETOOTH

(I know the original version doesn't have this, but it makes it all a lot easier to read.)


# Set the file that will be included by "extmod/btstack/btstack_config.h"
#
# The original file "extmod/btstack/btstack_config_common.h"
# will be included by it.
MICROPY_BLUETOOTH_BTSTACK_CONFIG_FILE ?= '"ports/unix/btstack_config.h"'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest moving this up before the endif for bluetooth


endif # MICROPY_BLUETOOTH_BTSTACK
endif # MICROPY_PY_BLUETOOTH

# include py core make definitions
include $(TOP)/py/py.mk
include $(TOP)/extmod/extmod.mk
Expand Down Expand Up @@ -144,21 +175,23 @@ ifeq ($(MICROPY_SSL_AXTLS),1)
endif
endif

# If the variant enables it, enable modbluetooth.
ifeq ($(MICROPY_PY_BLUETOOTH),1)
ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1)
HAVE_LIBUSB := $(shell (which pkg-config > /dev/null && pkg-config --exists libusb-1.0) 2>/dev/null && echo '1')

# Figure out which BTstack transport to use.
ifeq ($(HAVE_LIBUSB),1)
# Default to btstack-over-usb.
MICROPY_BLUETOOTH_BTSTACK_USB ?= 1
else
# Fallback to HCI controller via a H4 UART (e.g. Zephyr on nRF) over a /dev/tty serial port.
MICROPY_BLUETOOTH_BTSTACK_H4 ?= 1
ifeq ($(MICROPY_BLUETOOTH_BTSTACK_USB),0)
SRC_BTSTACK_C += lib/btstack/platform/embedded/btstack_run_loop_embedded.c
endif

ifeq ($(MICROPY_BLUETOOTH_BTSTACK_USB),1)
SRC_BTSTACK_C += lib/btstack/platform/posix/btstack_run_loop_posix.c

ifeq ($(MICROPY_BLUETOOTH_BTSTACK_HCI_DUMP),1)
CFLAGS += -DMICROPY_BLUETOOTH_BTSTACK_HCI_DUMP=1
SRC_BTSTACK_C += lib/btstack/platform/posix/hci_dump_posix_fs.c
endif

endif

SRC_BTSTACK_C += lib/btstack/platform/embedded/btstack_run_loop_embedded.c
endif
endif

Expand Down
23 changes: 23 additions & 0 deletions ports/unix/btstack_config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#ifndef MICROPY_INCLUDED_PORTS_UNIX_BTSTACK_CONFIG_H
#define MICROPY_INCLUDED_PORTS_UNIX_BTSTACK_CONFIG_H


#include "extmod/btstack/btstack_config_common.h"

#ifdef MICROPY_BLUETOOTH_BTSTACK_USB
#if MICROPY_BLUETOOTH_BTSTACK_USB

// Workaround:
//
// If ENABLE_SCO_OVER_HCI is not defined there is a compiler error:
//
// ../../lib/btstack/platform/libusb/hci_transport_h2_libusb.c:1354:13:
// error: ‘signal_sco_can_send_now’ defined but not used
//
// Because the problem is in an external code this workaround is done.
#define ENABLE_SCO_OVER_HCI

#endif
#endif

#endif // MICROPY_INCLUDED_PORTS_UNIX_BTSTACK_CONFIG_H
4 changes: 3 additions & 1 deletion ports/unix/mpbtstackport_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@

#include "mpbtstackport.h"

// Called by the UART polling thread in mpbthciport.c, or by the USB polling thread in mpbtstackport_usb.c.
// Called by the UART polling thread in mpbthciport.c
bool mp_bluetooth_hci_poll(void) {
if (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_STARTING || mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE || mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_HALTING) {
// Pretend like we're running in IRQ context (i.e. other things can't be running at the same time).
Expand Down Expand Up @@ -80,9 +80,11 @@ uint32_t hal_time_ms(void) {
}

void mp_bluetooth_btstack_port_init(void) {
#if !MICROPY_BLUETOOTH_BTSTACK_USB
btstack_run_loop_init(btstack_run_loop_embedded_get_instance());

// hci_dump_init(hci_dump_embedded_stdout_get_instance());
#endif

#if MICROPY_BLUETOOTH_BTSTACK_H4
mp_bluetooth_btstack_port_init_h4();
Expand Down
98 changes: 76 additions & 22 deletions ports/unix/mpbtstackport_usb.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@
#include "py/runtime.h"
#include "py/mperrno.h"
#include "py/mphal.h"
#include "py/stackctrl.h"

#include "lib/btstack/src/btstack.h"
#include "lib/btstack/src/hci_transport_usb.h"
#include "lib/btstack/platform/embedded/btstack_run_loop_embedded.h"
#include "lib/btstack/platform/embedded/hal_cpu.h"
#include "lib/btstack/platform/embedded/hal_time_ms.h"
#include "lib/btstack/platform/posix/btstack_run_loop_posix.h"
#include "lib/btstack/platform/posix/hci_dump_posix_fs.h"

#include "extmod/btstack/modbluetooth_btstack.h"

Expand All @@ -49,9 +49,21 @@
#error Unix btstack requires MICROPY_PY_THREAD
#endif

static const useconds_t USB_POLL_INTERVAL_US = 1000;
#define DEBUG_printf(...) // printf("mpbtstackport_usb.c: " __VA_ARGS__)

void mp_bluetooth_btstack_port_init_usb(void) {

#if MICROPY_BLUETOOTH_BTSTACK_HCI_DUMP
// log into file using HCI_DUMP_PACKETLOGGER format
const char *pklg_path = "/tmp/hci_dump.pklg";
hci_dump_posix_fs_open(pklg_path, HCI_DUMP_PACKETLOGGER);
const hci_dump_t *hci_dump_impl = hci_dump_posix_fs_get_instance();
hci_dump_init(hci_dump_impl);
printf("Packet Log: %s\n", pklg_path);
#endif

btstack_run_loop_init(btstack_run_loop_posix_get_instance());

// MICROPYBTUSB can be a ':'' or '-' separated port list.
char *path = getenv("MICROPYBTUSB");
if (path != NULL) {
Expand All @@ -76,35 +88,77 @@ void mp_bluetooth_btstack_port_init_usb(void) {
static pthread_t bstack_thread_id;

void mp_bluetooth_btstack_port_deinit(void) {
hci_power_control(HCI_POWER_OFF);
DEBUG_printf("mp_bluetooth_btstack_port_deinit: begin: mp_bluetooth_btstack_state=%d\n", mp_bluetooth_btstack_state);

// Workaround:
//
// Possibly hci_power_control(HCI_POWER_OFF);
// crashes with memory fault if state is already MP_BLUETOOTH_BTSTACK_STATE_OFF.
if (mp_bluetooth_btstack_state != MP_BLUETOOTH_BTSTACK_STATE_OFF &&
mp_bluetooth_btstack_state != MP_BLUETOOTH_BTSTACK_STATE_TIMEOUT) {

// Wait for the poll loop to terminate when the state is set to OFF.
hci_power_control(HCI_POWER_OFF);

DEBUG_printf("mp_bluetooth_btstack_port_deinit: after HCI_POWER_OFF: mp_bluetooth_btstack_state=%d\n", mp_bluetooth_btstack_state);
}

// btstack_run_loop_trigger_exit() will call btstack_run_loop_t::trigger_exit()
// i.e. btstack_run_loop_posix_trigger_exit().
// And that will trigger the exit of btstack_run_loop_execute()
// i.e. btstack_run_loop_posix_execute().
btstack_run_loop_trigger_exit();

// A call of btstack_run_loop_poll_data_sources_from_irq() is needed
// additional to btstack_run_loop_trigger_exit() to exit the posix run loop.
//
// Otherwise the posix run loop will wait forever for ready File Descriptors.
//
// Hint:
// btstack_run_loop_poll_data_sources_from_irq()
// calls btstack_run_loop_posix_poll_data_sources_from_irq()
btstack_run_loop_poll_data_sources_from_irq();

// MicroPython threads are created with PTHREAD_CREATE_DETACHED.
//
// Nonetheless the thread is created with PTHREAD_CREATE_JOINABLE and
// pthread_join() is used.

DEBUG_printf("mp_bluetooth_btstack_port_deinit: pthread_join()\n");
pthread_join(bstack_thread_id, NULL);
}

#if MICROPY_BLUETOOTH_BTSTACK_HCI_DUMP
DEBUG_printf("mp_bluetooth_btstack_port_deinit: hci_dump_posix_fs_close()\n");
hci_dump_posix_fs_close();
#endif

// Provided by mpbstackport_common.c.
extern bool mp_bluetooth_hci_poll(void);
DEBUG_printf("mp_bluetooth_btstack_port_deinit: end\n");
}

static void *btstack_thread(void *arg) {
(void)arg;

// This code runs on an non-MicroPython thread.
// But some of the code e.g. in "extmod/btstack/modbluetooth_btstack.c"
// make calls that needs the state of a MicroPython thread e. g. "m_del()".
//
// So set up relevant MicroPython state and obtain the GIL,
// to synchronised with the rest of the runtime.

mp_state_thread_t ts;
mp_thread_init_state(&ts, 40000 * (sizeof(void *) / 4) - 1024, NULL, NULL);
MP_THREAD_GIL_ENTER();

log_info("btstack_thread: HCI_POWER_ON");
hci_power_control(HCI_POWER_ON);

// modbluetooth_btstack.c will have set the state to STARTING before
// calling mp_bluetooth_btstack_port_start.
// This loop will terminate when the HCI_POWER_OFF above results
// in modbluetooth_btstack.c setting the state back to OFF.
// Or, if a timeout results in it being set to TIMEOUT.
btstack_run_loop_execute();

while (true) {
if (!mp_bluetooth_hci_poll()) {
break;
}
log_info("btstack_thread: end");

// Give back the GIL and reset the MicroPython state.
MP_THREAD_GIL_EXIT();
mp_thread_set_state(NULL);

// The USB transport schedules events to the run loop at 1ms intervals,
// and the implementation currently polls rather than selects.
usleep(USB_POLL_INTERVAL_US);
}
return NULL;
}

Expand Down
Loading