From 222ae61338c04d72be88e6d294ac412d24763a3c Mon Sep 17 00:00:00 2001 From: James Aguilar Date: Wed, 26 Nov 2025 07:45:58 -0700 Subject: [PATCH 1/3] tools: Tool to collect bt firmware. collect_bt_patches.py collects bluetooth module firmware patch files for the most popular Broadcom and Realtek modules into a directory where they can be used by the virtualhub for initializing bluetooth modules. --- .gitignore | 4 + tools/collect_bt_patches.py | 189 ++++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100755 tools/collect_bt_patches.py diff --git a/.gitignore b/.gitignore index 2761cae44..202330134 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,7 @@ Pipfile.lock # Jupyter Notebook ###################### .ipynb_checkpoints + +# BT Patches fetched by tools/collect_bt_patches.py +########################################## +.bt_firmware/ diff --git a/tools/collect_bt_patches.py b/tools/collect_bt_patches.py new file mode 100755 index 000000000..49eb113ab --- /dev/null +++ b/tools/collect_bt_patches.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: MIT +# Copyright (c) 2025 The Pybricks Authors + +""" +Tool to collect bluetooth patch files in the user's cache directory. +""" + +import argparse +import os +import re +import subprocess +import shutil +from pathlib import Path + +# Destination directory in user's cache directory +DEST_DIR = Path.cwd() / ".bt_firmware" + + +def _fetch_and_checkout(checkout_dir: Path, ref_to_fetch: str): + """Fetch a specific ref (branch/tag/sha) from origin and check it out. + + Uses a shallow fetch (--depth=1) and then forces checkout of FETCH_HEAD so + callers don't need to duplicate the subprocess logic. + """ + subprocess.run( + ["git", "fetch", "--depth=1", "origin", ref_to_fetch], + cwd=checkout_dir, + check=True, + ) + subprocess.run( + [ + "git", + "checkout", + "--force", + "FETCH_HEAD", + ], + cwd=checkout_dir, + check=True, + ) + + +def sparse_checkout( + subdir: str, + repo_url: str, + paths: list[str], + branch: str = "master", + ref: str | None = None, +): + """ + Perform sparse checkout of specified paths from a git repository. + + Args: + subdir: Subdirectory name under DEST_DIR + repo_url: URL of the git repository + paths: List of paths to checkout (e.g., ['brcm/', 'rtl_bt/']) + branch: Git branch to pull from (default: 'master') + ref: Optional git ref (commit sha or tag) to checkout. If provided, + this ref will be fetched and checked out instead of pulling the + branch. If None, the function will pull the specified branch as + before. + """ + checkout_dir = DEST_DIR / subdir + git_dir = checkout_dir / ".git" + + # Check if repo already exists + if git_dir.exists(): + # If a specific ref was requested, fetch and checkout that ref. + if ref: + _fetch_and_checkout(checkout_dir, ref) + return + + # No ref requested: just pull the latest changes from the branch + subprocess.run(["git", "pull", "origin", branch], cwd=checkout_dir, check=True) + return + + # Create the directory + checkout_dir.mkdir(parents=True, exist_ok=True) + + # Initialize git repo + subprocess.run(["git", "init"], cwd=checkout_dir, check=True) + + # Add remote + subprocess.run( + ["git", "remote", "add", "origin", repo_url], cwd=checkout_dir, check=True + ) + + # Enable sparse checkout + subprocess.run( + ["git", "config", "core.sparseCheckout", "true"], cwd=checkout_dir, check=True + ) + + # Specify the paths to checkout + sparse_checkout_file = checkout_dir / ".git" / "info" / "sparse-checkout" + sparse_checkout_file.parent.mkdir(parents=True, exist_ok=True) + sparse_checkout_file.write_text("\n".join(paths) + "\n") + + # Fetch and checkout either the requested ref or the branch tip. + ref_to_fetch = ref if ref is not None else branch + _fetch_and_checkout(checkout_dir, ref_to_fetch) + + +def collect_firmware(subdir: str, pattern: str): + """ + Create symbolic links for firmware files in DEST_DIR. + + Args: + subdir: Subdirectory under DEST_DIR containing firmware files + pattern: Regex pattern to match and optionally extract parts for renaming. + - If pattern has 2 capture groups, uses them concatenated as the link name + - If pattern has no capture groups, uses original filename for matches + """ + firmware_dir = DEST_DIR / subdir + + if not firmware_dir.exists(): + return + + # Compile pattern + regex = re.compile(pattern) + + for firmware_file in firmware_dir.iterdir(): + if not firmware_file.is_file(): + continue + + # Check if filename matches pattern + match = regex.match(firmware_file.name) + if not match: + continue + + # Determine link name based on capture groups + groups = match.groups() + if len(groups) == 2: + # Two groups: concatenate them for the link name + link_name = DEST_DIR / "".join(groups) + else: + # No groups: use original filename + link_name = DEST_DIR / firmware_file.name + + # Skip if link already exists + if link_name.exists() or link_name.is_symlink(): + continue + + # Create the symbolic link + link_name.symlink_to(firmware_file) + + +def main(): + """Main entry point for collecting bluetooth patch files.""" + parser = argparse.ArgumentParser( + description="Collect bluetooth patch files in the user's cache directory" + ) + parser.add_argument( + "--clean", + action="store_true", + help="Delete the entire destination directory before collecting", + ) + args = parser.parse_args() + + # Clean destination directory if requested + if args.clean and DEST_DIR.exists(): + shutil.rmtree(DEST_DIR) + + # Checkout brcm directory from broadcom-bt-firmware repo + sparse_checkout( + subdir="brcm", + repo_url="https://github.com/winterheart/broadcom-bt-firmware", + paths=["brcm/"], + branch="master", + ref="v12.0.1.1105_p4", + ) + + # Checkout rtl_bt and intel directories from linux-firmware repo + sparse_checkout( + subdir="linux_firmware", + repo_url="https://gitlab.com/kernel-firmware/linux-firmware.git", + paths=["rtl_bt/", "intel/"], + branch="main", + ref="20251125", + ) + + # Collect firmware files into a single directory. Rename the brcm firmware + # files to match btstack's filename expectations. + collect_firmware("brcm/brcm", r"([^-]+)[^.]*(\..+)") + collect_firmware("linux_firmware/intel", r"^ibt.*(?:(ddc|sfi))$") + collect_firmware("linux_firmware/rtl_bt", r"^.*\.bin$") + + +if __name__ == "__main__": + main() From 7aaa1742bc41847a35016b4cadc430f5e2b13425 Mon Sep 17 00:00:00 2001 From: James Aguilar Date: Wed, 26 Nov 2025 07:45:58 -0700 Subject: [PATCH 2/3] pbdrv/bluetooth: Add POSIX btstack for virtualhub. - Changes pbdrv_bluetooth_btstack_set_chipset to convey all necessary information to set the correct chipset both from the read local version information command as well as events from the USB subsystem. - Adds a POSIX implementation for pbdrv_bluetooth_btstack_set_chipset. This supports the most common Realtek and Broadcom chipsets, which comprise the vast majority of USB dongles. - Sets up the virtualhub platform to use this chipset. - Adjusts the runloop to check for readability and writability of file descriptors, which is required for the libusb transport. --- .vscode/c_cpp_properties.json | 5 + bricks/_common/common.mk | 48 ++++ bricks/_common/sources.mk | 3 +- bricks/virtualhub/Makefile | 1 + lib/pbio/drv/bluetooth/bluetooth_btstack.c | 99 +++++++- lib/pbio/drv/bluetooth/bluetooth_btstack.h | 18 +- .../drv/bluetooth/bluetooth_btstack_ev3.c | 5 +- .../drv/bluetooth/bluetooth_btstack_posix.c | 220 ++++++++++++++++++ .../drv/bluetooth/bluetooth_btstack_posix.h | 19 ++ .../bluetooth/bluetooth_btstack_stm32_hal.c | 2 +- .../platform/virtual_hub/btstack_config.h | 66 ++++++ lib/pbio/platform/virtual_hub/pbdrvconfig.h | 5 +- lib/pbio/platform/virtual_hub/platform.c | 11 + lib/pbio/test/drv/test_bluetooth_btstack.c | 2 +- 14 files changed, 488 insertions(+), 16 deletions(-) create mode 100644 lib/pbio/drv/bluetooth/bluetooth_btstack_posix.c create mode 100644 lib/pbio/drv/bluetooth/bluetooth_btstack_posix.h create mode 100644 lib/pbio/platform/virtual_hub/btstack_config.h diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 5ddcf64c4..bb9a447c5 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -386,6 +386,11 @@ "${workspaceFolder}/bricks/virtualhub", "${workspaceFolder}/bricks/virtualhub/build", "${workspaceFolder}/bricks/virtualhub/build-debug", + "${workspaceFolder}/lib/btstack/chipset/bcm", + "${workspaceFolder}/lib/btstack/chipset/realtek", + "${workspaceFolder}/lib/btstack/platform/libusb", + "${workspaceFolder}/lib/btstack/platform/posix", + "${workspaceFolder}/lib/btstack/src", "${workspaceFolder}/lib/lego", "${workspaceFolder}/lib/lwrb/src/include", "${workspaceFolder}/lib/pbio", diff --git a/bricks/_common/common.mk b/bricks/_common/common.mk index 9ae98610d..86b4f3c31 100644 --- a/bricks/_common/common.mk +++ b/bricks/_common/common.mk @@ -124,6 +124,18 @@ endif ifneq ($(strip $(PB_LIB_BTSTACK)),) INC += -I$(PBTOP)/lib/btstack/chipset/cc256x INC += -I$(PBTOP)/lib/btstack/src +ifeq ($(PBIO_PLATFORM),virtual_hub) +INC += -I$(PBTOP)/lib/btstack/platform/posix +INC += -I$(PBTOP)/lib/btstack/platform/embedded +INC += -I$(PBTOP)/lib/btstack/3rd-party/tinydir +INC += -I$(PBTOP)/lib/btstack/3rd-party/rijndael +INC += -I$(PBTOP)/lib/btstack/3rd-party/micro-ecc +INC += -I$(PBTOP)/lib/btstack/chipset/bcm +INC += -I$(PBTOP)/lib/btstack/chipset/intel +INC += -I$(PBTOP)/lib/btstack/chipset/realtek +INC += -I$(PBTOP)/lib/btstack/chipset/zephyr +INC += $(shell pkg-config libusb-1.0 --cflags) +endif endif ifeq ($(PB_LIB_LSM6DS3TR_C),1) INC += -I$(PBTOP)/lib/lsm6ds3tr_c_STdC/driver @@ -162,6 +174,9 @@ else ifeq ($(UNAME_S),Darwin) LDFLAGS += -Wl,-map,$@.map -Wl,-dead_strip endif LIBS = -lm +ifeq ($(PB_LIB_BTSTACK),lowenergy) +LIBS += $(shell pkg-config libusb-1.0 --libs) +endif else # end native, begin embedded CROSS_COMPILE ?= arm-none-eabi- ifeq ($(PB_MCU_FAMILY),STM32) @@ -395,6 +410,37 @@ BTSTACK_SRC_C += $(addprefix lib/btstack/chipset/cc256x/,\ btstack_chipset_cc256x.c \ ) +# libusb-specific BTStack sources for virtual_hub +ifeq ($(PBIO_PLATFORM),virtual_hub) +BTSTACK_SRC_C += $(addprefix lib/btstack/platform/libusb/,\ + hci_transport_h2_libusb.c \ + ) +BTSTACK_SRC_C += $(addprefix lib/btstack/platform/posix/,\ + hci_dump_posix_stdout.c \ + ) +BTSTACK_SRC_C += $(addprefix lib/btstack/src/ble/,\ + le_device_db_tlv.c \ + ) +BTSTACK_SRC_C += $(addprefix lib/btstack/chipset/zephyr/,\ + btstack_chipset_zephyr.c \ + ) +BTSTACK_SRC_C += $(addprefix lib/btstack/chipset/realtek/,\ + btstack_chipset_realtek.c \ + ) +BTSTACK_SRC_C += $(addprefix lib/btstack/chipset/bcm/,\ + btstack_chipset_bcm.c \ + ) +BTSTACK_SRC_C += $(addprefix lib/btstack/chipset/intel/,\ + btstack_chipset_intel_firmware.c \ + ) +BTSTACK_SRC_C += $(addprefix lib/btstack/3rd-party/rijndael/,\ + rijndael.c \ + ) +BTSTACK_SRC_C += $(addprefix lib/btstack/3rd-party/micro-ecc/,\ + uECC.c \ + ) +endif + # STM32 HAL COPT += -DUSE_FULL_LL_DRIVER @@ -530,11 +576,13 @@ endif ifeq ($(PB_LIB_BTSTACK),classic) OBJ += $(addprefix $(BUILD)/, $(BTSTACK_SRC_C:.c=.o)) +$(BUILD)/lib/btstack/%.o: CFLAGS += -Wno-error endif ifeq ($(PB_LIB_BTSTACK),lowenergy) OBJ += $(addprefix $(BUILD)/, $(BTSTACK_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(BTSTACK_BLE_SRC_C:.c=.o)) +$(BUILD)/lib/btstack/%.o: CFLAGS += -Wno-error endif ifeq ($(PB_LIB_STM32_HAL),1) diff --git a/bricks/_common/sources.mk b/bricks/_common/sources.mk index aefbf87fb..4e78da3ad 100644 --- a/bricks/_common/sources.mk +++ b/bricks/_common/sources.mk @@ -114,9 +114,10 @@ PBIO_SRC_C = $(addprefix lib/pbio/,\ drv/block_device/block_device_test.c \ drv/block_device/block_device_w25qxx_stm32.c \ drv/bluetooth/bluetooth.c \ - drv/bluetooth/bluetooth_btstack_stm32_hal.c \ drv/bluetooth/bluetooth_btstack.c \ drv/bluetooth/bluetooth_btstack_ev3.c \ + drv/bluetooth/bluetooth_btstack_posix.c \ + drv/bluetooth/bluetooth_btstack_stm32_hal.c \ drv/bluetooth/bluetooth_simulation.c \ drv/bluetooth/bluetooth_stm32_bluenrg.c \ drv/bluetooth/bluetooth_stm32_cc2640.c \ diff --git a/bricks/virtualhub/Makefile b/bricks/virtualhub/Makefile index bc24a90a4..357ac52d9 100644 --- a/bricks/virtualhub/Makefile +++ b/bricks/virtualhub/Makefile @@ -6,5 +6,6 @@ PB_MCU_FAMILY = native PB_FROZEN_MODULES = 1 MICROPY_ROM_TEXT_COMPRESSION = 1 PB_LIB_UMM_MALLOC = 1 +PB_LIB_BTSTACK = lowenergy include ../_common/common.mk diff --git a/lib/pbio/drv/bluetooth/bluetooth_btstack.c b/lib/pbio/drv/bluetooth/bluetooth_btstack.c index 8f3e04208..5e4d8c0a3 100644 --- a/lib/pbio/drv/bluetooth/bluetooth_btstack.c +++ b/lib/pbio/drv/bluetooth/bluetooth_btstack.c @@ -30,12 +30,23 @@ #include "pybricks_service_server.h" #endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE +#if PBDRV_CONFIG_BLUETOOTH_BTSTACK_POSIX + +#include +#include + +#endif + #ifdef PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_KIND #define HUB_KIND PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_KIND #else #error "PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_KIND is required" #endif +#ifndef PBDRV_CONFIG_BLUETOOTH_BTSTACK_POSIX +#define PBDRV_CONFIG_BLUETOOTH_BTSTACK_POSIX 0 +#endif + // location of product variant in bootloader flash memory of Technic Large hubs #if PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_VARIANT_ADDR #define HUB_VARIANT (*(const uint16_t *)PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_VARIANT_ADDR) @@ -223,16 +234,29 @@ static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packe pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton; #endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE + static pbdrv_bluetooth_btstack_device_discriminator device_info; + switch (hci_event_packet_get_type(packet)) { + case HCI_EVENT_TRANSPORT_USB_INFO: { + // Store USB vendor and product IDs for later use + device_info.usb_vendor_id = hci_event_transport_usb_info_get_vendor_id(packet); + device_info.usb_product_id = hci_event_transport_usb_info_get_product_id(packet); + break; + } case HCI_EVENT_COMMAND_COMPLETE: { const uint8_t *rp = hci_event_command_complete_get_return_parameters(packet); switch (hci_event_command_complete_get_command_opcode(packet)) { case HCI_OPCODE_HCI_READ_LOCAL_VERSION_INFORMATION: { - uint16_t lmp_pal_subversion = pbio_get_uint16_le(&rp[7]); - pbdrv_bluetooth_btstack_set_chipset(lmp_pal_subversion); + device_info.hci_version = rp[0]; + device_info.hci_revision = pbio_get_uint16_le(&rp[1]); + device_info.lmp_pal_version = rp[3]; + device_info.manufacturer = pbio_get_uint16_le(&rp[4]); + device_info.lmp_pal_subversion = pbio_get_uint16_le(&rp[6]); + pbdrv_bluetooth_btstack_set_chipset(&device_info); #if DEBUG // Show version in ev3dev format. + uint16_t lmp_pal_subversion = device_info.lmp_pal_subversion; uint16_t chip = (lmp_pal_subversion & 0x7C00) >> 10; uint16_t min_ver = (lmp_pal_subversion & 0x007F); uint16_t maj_ver = (lmp_pal_subversion & 0x0380) >> 7; @@ -240,6 +264,9 @@ static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packe maj_ver |= 0x0008; } DEBUG_PRINT("LMP %04x: TIInit_%d.%d.%d.bts\n", lmp_pal_subversion, chip, maj_ver, min_ver); + (void)maj_ver; // In lib/pbio/test, this variable appears unused even though it's not. + (void)min_ver; + (void)chip; #endif break; } @@ -999,6 +1026,13 @@ static void bluetooth_btstack_run_loop_execute(void) { // not used } +static bool do_poll_handler; + +void pbdrv_bluetooth_btstack_run_loop_trigger(void) { + do_poll_handler = true; + pbio_os_request_poll(); +} + static const btstack_run_loop_t bluetooth_btstack_run_loop = { .init = btstack_run_loop_base_init, .add_data_source = btstack_run_loop_base_add_data_source, @@ -1011,14 +1045,10 @@ static const btstack_run_loop_t bluetooth_btstack_run_loop = { .execute = bluetooth_btstack_run_loop_execute, .dump_timer = btstack_run_loop_base_dump_timer, .get_time_ms = pbdrv_clock_get_ms, + .poll_data_sources_from_irq = pbdrv_bluetooth_btstack_run_loop_trigger, }; -static bool do_poll_handler; -void pbdrv_bluetooth_btstack_run_loop_trigger(void) { - do_poll_handler = true; - pbio_os_request_poll(); -} static pbio_os_process_t pbdrv_bluetooth_hci_process; @@ -1028,9 +1058,57 @@ static pbio_os_process_t pbdrv_bluetooth_hci_process; */ static pbio_error_t pbdrv_bluetooth_hci_process_thread(pbio_os_state_t *state, void *context) { - if (do_poll_handler) { + #if PBDRV_CONFIG_BLUETOOTH_BTSTACK_POSIX + int nfds = btstack_linked_list_count(&btstack_run_loop_base_data_sources); + struct pollfd fds[nfds]; + #endif + + if (do_poll_handler || PBDRV_CONFIG_BLUETOOTH_BTSTACK_POSIX) { do_poll_handler = false; btstack_run_loop_base_poll_data_sources(); + + #if PBDRV_CONFIG_BLUETOOTH_BTSTACK_POSIX + btstack_linked_list_iterator_t it; + int i; + for (i = 0, btstack_linked_list_iterator_init(&it, &btstack_run_loop_base_data_sources); + btstack_linked_list_iterator_has_next(&it); ++i) { + // cache pointer to next data_source to allow data source to remove itself + btstack_data_source_t *ds = (void *)btstack_linked_list_iterator_next(&it); + + // In POSIX mode we must additionally identify data source FDs that + // are ready for reading or writing. + struct pollfd *pfd = &fds[i]; + pfd->fd = ds->source.fd; + pfd->events = 0; + if (ds->flags & DATA_SOURCE_CALLBACK_READ) { + pfd->events |= POLLIN; + } + if (ds->flags & DATA_SOURCE_CALLBACK_WRITE) { + pfd->events |= POLLOUT; + } + + } + + int err = poll(fds, nfds, 0); + if (err < 0) { + DEBUG_PRINT("btstack: poll() returned %d, ignoring\n", errno); + } else if (err > 0) { + // Some fd was ready. + for (i = 0, btstack_linked_list_iterator_init(&it, &btstack_run_loop_base_data_sources); + btstack_linked_list_iterator_has_next(&it); ++i) { + btstack_data_source_t *ds = (void *)btstack_linked_list_iterator_next(&it); + struct pollfd *pfd = &fds[i]; + if (pfd->revents & POLLIN) { + ds->process(ds, DATA_SOURCE_CALLBACK_READ); + } else if (pfd->revents & POLLOUT) { + ds->process(ds, DATA_SOURCE_CALLBACK_WRITE); + } else if (pfd->revents & POLLERR) { + DEBUG_PRINT("btstack: poll() error on fd %d\n", pfd->fd); + } + } + + } + #endif } static pbio_os_timer_t btstack_timer = { @@ -1065,9 +1143,12 @@ void pbdrv_bluetooth_init_hci(void) { btstack_run_loop_init(&bluetooth_btstack_run_loop); hci_init(pdata->transport_instance(), pdata->transport_config()); - hci_set_chipset(pdata->chipset_instance()); + if (pdata->chipset_instance != NULL) { + hci_set_chipset(pdata->chipset_instance()); + } hci_set_control(pdata->control_instance()); + // REVISIT: do we need to call btstack_chipset_cc256x_set_power() or btstack_chipset_cc256x_set_power_vector()? hci_event_callback_registration.callback = &packet_handler; diff --git a/lib/pbio/drv/bluetooth/bluetooth_btstack.h b/lib/pbio/drv/bluetooth/bluetooth_btstack.h index 26e0304bd..4ee99b7d0 100644 --- a/lib/pbio/drv/bluetooth/bluetooth_btstack.h +++ b/lib/pbio/drv/bluetooth/bluetooth_btstack.h @@ -23,13 +23,29 @@ typedef struct { void pbdrv_bluetooth_btstack_run_loop_trigger(void); +typedef struct { + // Only set on POSIX -- these data come from the USB device descriptor. + uint16_t usb_vendor_id; + uint16_t usb_product_id; + + // Set on all platforms -- these data come from the + // HCI_OPCODE_HCI_READ_LOCAL_VERSION_INFORMATION response. + uint8_t hci_version; + uint16_t hci_revision; + uint8_t lmp_pal_version; + uint16_t manufacturer; + uint16_t lmp_pal_subversion; + +} pbdrv_bluetooth_btstack_device_discriminator; + /** * Hook called when BTstack reads the local version information. * * This is called _after_ hci_set_chipset but _before_ the init script is sent * over the wire, so this can be used to dynamically select the init script. */ -void pbdrv_bluetooth_btstack_set_chipset(uint16_t lmp_pal_subversion); +void pbdrv_bluetooth_btstack_set_chipset( + pbdrv_bluetooth_btstack_device_discriminator *device_info); typedef struct { const hci_transport_t *(*transport_instance)(void); diff --git a/lib/pbio/drv/bluetooth/bluetooth_btstack_ev3.c b/lib/pbio/drv/bluetooth/bluetooth_btstack_ev3.c index 5ec20340e..3e5afe90a 100644 --- a/lib/pbio/drv/bluetooth/bluetooth_btstack_ev3.c +++ b/lib/pbio/drv/bluetooth/bluetooth_btstack_ev3.c @@ -62,12 +62,13 @@ static const hci_dump_t bluetooth_btstack_classic_hci_dump = { .log_packet = pbdrv_hci_dump_log_packet, .log_message = pbdrv_hci_dump_log_message, }; + #else #define DEBUG_PRINT(...) #endif -void pbdrv_bluetooth_btstack_set_chipset(uint16_t lmp_pal_subversion) { - const pbdrv_bluetooth_btstack_chipset_info_t *info = lmp_pal_subversion == cc2560_info.lmp_version ? +void pbdrv_bluetooth_btstack_set_chipset(pbdrv_bluetooth_btstack_device_discriminator *device_info) { + const pbdrv_bluetooth_btstack_chipset_info_t *info = device_info->lmp_pal_subversion == cc2560_info.lmp_version ? &cc2560_info : &cc2560a_info; btstack_chipset_cc256x_set_init_script((uint8_t *)info->init_script, info->init_script_size); diff --git a/lib/pbio/drv/bluetooth/bluetooth_btstack_posix.c b/lib/pbio/drv/bluetooth/bluetooth_btstack_posix.c new file mode 100644 index 000000000..b2148742b --- /dev/null +++ b/lib/pbio/drv/bluetooth/bluetooth_btstack_posix.c @@ -0,0 +1,220 @@ +#include "bluetooth_btstack_posix.h" + +#if PBDRV_CONFIG_BLUETOOTH_BTSTACK_POSIX + +#include +#include +#include +#include +#include + +#include "btstack.h" +#include "btstack_chipset_bcm.h" +#include "btstack_chipset_realtek.h" +#include "hci_transport_usb.h" + +#include "../uart/uart_debug_first_port.h" + +#include "bluetooth_btstack.h" + +// Broadcom USB device name lookup table +// Maps USB vendor:product ID to BCM device name for firmware file selection +typedef struct { + uint16_t vendor_id; + uint16_t product_id; + const char *device_name; +} bcm_device_entry_t; + +static const bcm_device_entry_t bcm_device_table[] = { + // Broadcom vendor ID (0x0a5c) + { 0x0a5c, 0x21e8, "BCM20702A0" }, // DeLOCK Bluetooth 4.0 + { 0x0a5c, 0x22be, "BCM20702B0" }, // Generic USB Detuned Class 1 + { 0x0a5c, 0x21e1, "BCM20702A0" }, // HP Bluetooth module + { 0x0a5c, 0x21e3, "BCM20702A0" }, // Broadcom BCM20702A0 + { 0x0a5c, 0x21e6, "BCM20702A1" }, // ThinkPad Bluetooth 4.0 + { 0x0a5c, 0x21f1, "BCM20702A0" }, // HP Bluetooth module + { 0x0a5c, 0x21fb, "BCM20702A0" }, // Broadcom BCM20702A0 + { 0x0a5c, 0x21fd, "BCM20702A0" }, // Broadcom BCM20702A0 + { 0x0a5c, 0x640b, "BCM20703A1" }, // Broadcom BCM20703A1 + { 0x0a5c, 0x6410, "BCM20703A1" }, // Broadcom BCM20703A1 + + // ASUS vendor ID (0x0b05) - uses Broadcom chips + { 0x0b05, 0x17cb, "BCM20702A0" }, // ASUS BT400 + { 0x0b05, 0x17cf, "BCM20702A0" }, // ASUS USB-BT400 + + // Dell vendor ID (0x413c) - uses Broadcom chips + { 0x413c, 0x8143, "BCM20702A0" }, // Dell Wireless 365 Bluetooth + { 0x413c, 0x8197, "BCM20702A0" }, // Dell DW380 Bluetooth + + // Apple vendor ID (0x05ac) - uses Broadcom chips + { 0x05ac, 0x828d, "BCM43142A0" }, // Apple Bluetooth USB Host Controller + { 0x05ac, 0x8286, "BCM43142A0" }, // Apple Bluetooth USB Host Controller +}; + +static const char *get_bcm_device_name(uint16_t vendor_id, uint16_t product_id) { + for (size_t i = 0; i < sizeof(bcm_device_table) / sizeof(bcm_device_table[0]); i++) { + if (bcm_device_table[i].vendor_id == vendor_id && + bcm_device_table[i].product_id == product_id) { + return bcm_device_table[i].device_name; + } + } + return NULL; +} + +// Get the firmware directory path, checking environment variable first +static void set_firmware_folder(void) { + static char path_buffer[512] = ""; + + // Check for environment variable override + const char *env_path = getenv("PYBRICKS_VIRTUALHUB_FIRMWARE_DIR"); + if (env_path != NULL && env_path[0] != '\0') { + strncpy(path_buffer, env_path, sizeof(path_buffer) - 1); + path_buffer[sizeof(path_buffer) - 1] = '\0'; + } + + // Default to ~/.cache/pybricks/virtualhub/bt_firmware + if (strlen(path_buffer) == 0) { + const char *cwd = getenv("PWD"); + if (cwd != NULL) { + snprintf(path_buffer, sizeof(path_buffer), "%s/.bt_firmware", cwd); + } else { + // Use the current working directory otherwise. + path_buffer[0] = '.'; + path_buffer[1] = '\0'; + } + } + + btstack_chipset_bcm_set_hcd_folder_path(path_buffer); + btstack_chipset_realtek_set_firmware_folder_path(path_buffer); + btstack_chipset_realtek_set_config_folder_path(path_buffer); +} + +static void once_add_realtek_controllers(void) { + static bool done = false; + if (done) { + return; + } + done = true; + + // The realtek controllers need to be registered manually. + uint16_t realtek_num_controllers = btstack_chipset_realtek_get_num_usb_controllers(); + uint16_t i; + for (i = 0; i < realtek_num_controllers; i++) { + uint16_t vendor_id; + uint16_t product_id; + btstack_chipset_realtek_get_vendor_product_id(i, &vendor_id, &product_id); + hci_transport_usb_add_device(vendor_id, product_id); + } +} + +void pbdrv_bluetooth_btstack_set_chipset(pbdrv_bluetooth_btstack_device_discriminator *device_info) { + set_firmware_folder(); + + // Configure chipset based on Bluetooth manufacturer ID + switch (device_info->manufacturer) { + case BLUETOOTH_COMPANY_ID_REALTEK_SEMICONDUCTOR_CORPORATION: + btstack_chipset_realtek_set_lmp_subversion(device_info->lmp_pal_subversion); + hci_set_chipset(btstack_chipset_realtek_instance()); + break; + case BLUETOOTH_COMPANY_ID_BROADCOM_CORPORATION: { + // Look up BCM device name from USB vendor:product ID + const char *bcm_device_name = get_bcm_device_name( + device_info->usb_vendor_id, device_info->usb_product_id); + if (bcm_device_name) { + printf("Detected Broadcom device: %s (USB %04x:%04x)\n", + bcm_device_name, device_info->usb_vendor_id, device_info->usb_product_id); + btstack_chipset_bcm_set_device_name(bcm_device_name); + } + hci_set_chipset(btstack_chipset_bcm_instance()); + break; + } + default: + // Unknown manufacturer - no chipset-specific initialization possible. + break; + } +} + +static void noop_voidstararg(const void *) { +} + +static int noop_returnint(void) { + return 0; +} + +static const btstack_control_t noop_btstack_control = { + .init = noop_voidstararg, + .on = noop_returnint, + .off = noop_returnint, + .sleep = noop_returnint, + .wake = noop_returnint, + .register_for_power_notifications = NULL, +}; + +const btstack_control_t *pbdrv_bluetooth_btstack_posix_control_instance(void) { + return &noop_btstack_control; +} + +static const int MAX_USB_PATH_LEN = 7; + +// Parses a USB path in PYBRICKS_VIRTUALHUB_USB_PATH and sets +// hci_transport_usb_set_path using the result. The expected format is +// 8 bit unsigned integers separated by slashes. If an incorrect format +// is passed, no path is set. +// +// Note that btstack (I think) incorrectly ignores the bus number, leading to +// potential ambiguity here -- you could have a bluetooth device plugged into +// port 3 of both bus 1 and bus 2. If you're selecting your device by looking at +// lsusb -t, you need to put only the port number in this environment variable, +// NOT the bus number. +static void once_parse_usb_path_from_env() { + static bool once_flag = false; + if (once_flag) { + return; + } + once_flag = true; + + const char *usb_path = getenv("PYBRICKS_VIRTUALHUB_USB_PATH"); + if (!usb_path) { + return; + } + + uint8_t port_numbers[MAX_USB_PATH_LEN]; + int port_numbers_len = 0; + + + int len = strlen(usb_path); + for (int i = 0; i < MAX_USB_PATH_LEN && len > 0; ++i) { + int used = sscanf(usb_path, "%hhu", &port_numbers[i]); + if (used <= 0) { + return; + } + usb_path += used; + len -= used; + port_numbers_len++; + if (len > 0) { + if (*usb_path == '/') { + ++usb_path; + --len; + } else { + pbdrv_uart_debug_printf("Invalid USB path format (should be [/...])\n"); + memset(port_numbers, 0, MAX_USB_PATH_LEN); + port_numbers_len = 0; + return; + } + } + } + + hci_transport_usb_set_path(port_numbers_len, port_numbers); +} + +const hci_transport_t *pbdrv_bluetooth_btstack_posix_transport_instance(void) { + once_add_realtek_controllers(); + once_parse_usb_path_from_env(); + return hci_transport_usb_instance(); +} + +const void *pbdrv_bluetooth_btstack_posix_transport_config(void) { + return NULL; +} + +#endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_POSIX diff --git a/lib/pbio/drv/bluetooth/bluetooth_btstack_posix.h b/lib/pbio/drv/bluetooth/bluetooth_btstack_posix.h new file mode 100644 index 000000000..34d189985 --- /dev/null +++ b/lib/pbio/drv/bluetooth/bluetooth_btstack_posix.h @@ -0,0 +1,19 @@ +#ifndef PBDRV_BLUETOOTH_BLUETOOTH_BTSTACK_POSIX_H +#define PBDRV_BLUETOOTH_BLUETOOTH_BTSTACK_POSIX_H + +#include "pbdrvconfig.h" + +#if PBDRV_CONFIG_BLUETOOTH_BTSTACK_POSIX + +#include + +const btstack_control_t *pbdrv_bluetooth_btstack_posix_control_instance(void); + +const hci_transport_t *pbdrv_bluetooth_btstack_posix_transport_instance(void); + +const void *pbdrv_bluetooth_btstack_posix_transport_config(void); + + +#endif + +#endif diff --git a/lib/pbio/drv/bluetooth/bluetooth_btstack_stm32_hal.c b/lib/pbio/drv/bluetooth/bluetooth_btstack_stm32_hal.c index f065cf02f..ffed1fe36 100644 --- a/lib/pbio/drv/bluetooth/bluetooth_btstack_stm32_hal.c +++ b/lib/pbio/drv/bluetooth/bluetooth_btstack_stm32_hal.c @@ -24,7 +24,7 @@ #include -void pbdrv_bluetooth_btstack_set_chipset(uint16_t lmp_pal_subversion) { +void pbdrv_bluetooth_btstack_set_chipset(pbdrv_bluetooth_btstack_device_discriminator *) { const pbdrv_bluetooth_btstack_platform_data_t *pdata = &pbdrv_bluetooth_btstack_platform_data; diff --git a/lib/pbio/platform/virtual_hub/btstack_config.h b/lib/pbio/platform/virtual_hub/btstack_config.h new file mode 100644 index 000000000..75531363a --- /dev/null +++ b/lib/pbio/platform/virtual_hub/btstack_config.h @@ -0,0 +1,66 @@ +// +// btstack_config.h for libusb port +// +// Documentation: https://bluekitchen-gmbh.com/btstack/#how_to/ +// + +#ifndef BTSTACK_CONFIG_H +#define BTSTACK_CONFIG_H + +// Port related features +#define HAVE_ASSERT +#define HAVE_BTSTACK_STDIN +#define HAVE_MALLOC +#define HAVE_POSIX_FILE_IO +#define HAVE_POSIX_TIME + +#define ENABLE_LOG_INFO +#define ENABLE_LOG_ERROR +#define ENABLE_LOG_DEBUG + +// BTstack features that can be enabled +#define ENABLE_ATT_DELAYED_RESPONSE +#define ENABLE_BLE +#define ENABLE_BTSTACK_STDIN_LOGGING +#define ENABLE_CLASSIC +#define ENABLE_CROSS_TRANSPORT_KEY_DERIVATION +#define ENABLE_HFP_WIDE_BAND_SPEECH +#define ENABLE_L2CAP_ENHANCED_RETRANSMISSION_MODE +#define ENABLE_LE_CENTRAL +#define ENABLE_L2CAP_LE_CREDIT_BASED_FLOW_CONTROL_MODE +#define ENABLE_LE_DATA_LENGTH_EXTENSION +#define ENABLE_LE_PERIPHERAL +#define ENABLE_LE_PRIVACY_ADDRESS_RESOLUTION +#define ENABLE_LE_SECURE_CONNECTIONS +#define ENABLE_LOG_ERROR +#define ENABLE_LOG_INFO +#define ENABLE_MICRO_ECC_FOR_LE_SECURE_CONNECTIONS +#define ENABLE_PRINTF_HEXDUMP +#define ENABLE_SCO_OVER_HCI +#define ENABLE_SDP_DES_DUMP +#define ENABLE_SOFTWARE_AES128 + +// BTstack configuration. buffers, sizes, ... +#define HCI_ACL_PAYLOAD_SIZE (1691 + 4) +#define HCI_INCOMING_PRE_BUFFER_SIZE 14 // sizeof BNEP header, avoid memcpy + +#define NVM_NUM_DEVICE_DB_ENTRIES 16 +#define NVM_NUM_LINK_KEYS 16 + +// Mesh Configuration +#define ENABLE_MESH +#define ENABLE_MESH_ADV_BEARER +#define ENABLE_MESH_GATT_BEARER +#define ENABLE_MESH_PB_ADV +#define ENABLE_MESH_PB_GATT +#define ENABLE_MESH_PROVISIONER +#define ENABLE_MESH_PROXY_SERVER + +#define MAX_NR_MESH_SUBNETS 2 +#define MAX_NR_MESH_TRANSPORT_KEYS 16 +#define MAX_NR_MESH_VIRTUAL_ADDRESSES 16 + +// allow for one NetKey update +#define MAX_NR_MESH_NETWORK_KEYS (MAX_NR_MESH_SUBNETS + 1) + +#endif diff --git a/lib/pbio/platform/virtual_hub/pbdrvconfig.h b/lib/pbio/platform/virtual_hub/pbdrvconfig.h index 561f82b2f..5b785b677 100644 --- a/lib/pbio/platform/virtual_hub/pbdrvconfig.h +++ b/lib/pbio/platform/virtual_hub/pbdrvconfig.h @@ -9,7 +9,10 @@ #define PBDRV_CONFIG_BLOCK_DEVICE_TEST (1) #define PBDRV_CONFIG_BLUETOOTH (1) -#define PBDRV_CONFIG_BLUETOOTH_SIMULATION (1) +#define PBDRV_CONFIG_BLUETOOTH_BTSTACK (1) +#define PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE (1) +#define PBDRV_CONFIG_BLUETOOTH_BTSTACK_POSIX (1) +#define PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_KIND (LWP3_HUB_KIND_TECHNIC_LARGE) #define PBDRV_CONFIG_BUTTON (1) #define PBDRV_CONFIG_BUTTON_TEST (1) diff --git a/lib/pbio/platform/virtual_hub/platform.c b/lib/pbio/platform/virtual_hub/platform.c index d2b3a1b85..b05da3b81 100644 --- a/lib/pbio/platform/virtual_hub/platform.c +++ b/lib/pbio/platform/virtual_hub/platform.c @@ -11,6 +11,8 @@ #include #include "../../drv/motor_driver/motor_driver_virtual_simulation.h" +#include "../../drv/bluetooth/bluetooth_btstack.h" +#include "../../drv/bluetooth/bluetooth_btstack_posix.h" #include "pbio_os_config.h" @@ -142,6 +144,15 @@ const pbdrv_motor_driver_virtual_simulation_platform_data_t }, }; +const pbdrv_bluetooth_btstack_platform_data_t pbdrv_bluetooth_btstack_platform_data = { + .transport_instance = pbdrv_bluetooth_btstack_posix_transport_instance, + .transport_config = pbdrv_bluetooth_btstack_posix_transport_config, + .chipset_instance = NULL, + .control_instance = pbdrv_bluetooth_btstack_posix_control_instance, + .er_key = (const uint8_t *)"placeholderplaceholder", + .ir_key = (const uint8_t *)"placeholderplaceholder", +}; + // Socket used to send data to Python animation. static int data_socket = -1; static struct sockaddr_in serv_addr; diff --git a/lib/pbio/test/drv/test_bluetooth_btstack.c b/lib/pbio/test/drv/test_bluetooth_btstack.c index 9d4559851..588600d76 100644 --- a/lib/pbio/test/drv/test_bluetooth_btstack.c +++ b/lib/pbio/test/drv/test_bluetooth_btstack.c @@ -770,7 +770,7 @@ static void handle_data_source(btstack_data_source_t *ds, btstack_data_source_c } } -void pbdrv_bluetooth_btstack_set_chipset(uint16_t lmp_pal_subversion) { +void pbdrv_bluetooth_btstack_set_chipset(pbdrv_bluetooth_btstack_device_discriminator *device_info) { } From 1bca9023071f629bb872a9cbc0754c67f37a5003 Mon Sep 17 00:00:00 2001 From: James Aguilar Date: Wed, 26 Nov 2025 07:45:58 -0700 Subject: [PATCH 3/3] pbdrv/bluetooth: Debugging changes for virtualhub. - Implements HCI logging in bluetooth BTStack. - Adds a stderr version of uart_debug_first_port. - (Revert?) Enables debug logging for virtualhub bluetooth. --- lib/pbio/drv/bluetooth/bluetooth_btstack.c | 27 ++++++++++++++++++++- lib/pbio/drv/uart/uart_debug_first_port.h | 11 +++++++++ lib/pbio/platform/virtual_hub/pbdrvconfig.h | 2 ++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/lib/pbio/drv/bluetooth/bluetooth_btstack.c b/lib/pbio/drv/bluetooth/bluetooth_btstack.c index 5e4d8c0a3..101e6cf68 100644 --- a/lib/pbio/drv/bluetooth/bluetooth_btstack.c +++ b/lib/pbio/drv/bluetooth/bluetooth_btstack.c @@ -54,11 +54,27 @@ #define HUB_VARIANT 0x0000 #endif -#define DEBUG 0 +#define DEBUG 1 #if DEBUG #include #define DEBUG_PRINT pbdrv_uart_debug_printf +#include +#define DEBUG_PRINT pbdrv_uart_debug_printf +static void pbdrv_hci_dump_reset(void) { +} +static void pbdrv_hci_dump_log_packet(uint8_t packet_type, uint8_t in, uint8_t *packet, uint16_t len) { + pbdrv_uart_debug_printf("HCI %s packet type: %02x, len: %u\n", in ? "in" : "out", packet_type, len); +} +static void pbdrv_hci_dump_log_message(int log_level, const char *format, va_list argptr) { + pbdrv_uart_debug_vprintf(format, argptr); + pbdrv_uart_debug_printf("\n"); +} +static const hci_dump_t bluetooth_btstack_classic_hci_dump = { + .reset = pbdrv_hci_dump_reset, + .log_packet = pbdrv_hci_dump_log_packet, + .log_message = pbdrv_hci_dump_log_message, +}; #else #define DEBUG_PRINT(...) #endif @@ -1143,6 +1159,15 @@ void pbdrv_bluetooth_init_hci(void) { btstack_run_loop_init(&bluetooth_btstack_run_loop); hci_init(pdata->transport_instance(), pdata->transport_config()); + + #if DEBUG + hci_dump_init(&bluetooth_btstack_classic_hci_dump); + hci_dump_enable_log_level(HCI_DUMP_LOG_LEVEL_INFO, true); + hci_dump_enable_log_level(HCI_DUMP_LOG_LEVEL_ERROR, true); + hci_dump_enable_log_level(HCI_DUMP_LOG_LEVEL_DEBUG, true); + hci_dump_enable_packet_log(true); + #endif + if (pdata->chipset_instance != NULL) { hci_set_chipset(pdata->chipset_instance()); } diff --git a/lib/pbio/drv/uart/uart_debug_first_port.h b/lib/pbio/drv/uart/uart_debug_first_port.h index 7e4d0701e..17f90892f 100644 --- a/lib/pbio/drv/uart/uart_debug_first_port.h +++ b/lib/pbio/drv/uart/uart_debug_first_port.h @@ -21,9 +21,20 @@ void pbdrv_uart_debug_init(void); #else // PBDRV_CONFIG_UART_DEBUG_FIRST_PORT +#if PBDRV_CONFIG_UART_DEBUG_FIRST_PORT_IS_STDERR + +#include + +#define pbdrv_uart_debug_printf(...) fprintf(stderr, __VA_ARGS__) +#define pbdrv_uart_debug_vprintf(format, argptr) vfprintf(stderr, format, argptr) + +#else + #define pbdrv_uart_debug_printf(...) #define pbdrv_uart_debug_vprintf(format, argptr) +#endif // PBDRV_CONFIG_UART_DEBUG_FIRST_PORT_IS_STDERR + #define pbdrv_uart_debug_is_done() (true) #define pbdrv_uart_debug_init() diff --git a/lib/pbio/platform/virtual_hub/pbdrvconfig.h b/lib/pbio/platform/virtual_hub/pbdrvconfig.h index 5b785b677..2918c71bb 100644 --- a/lib/pbio/platform/virtual_hub/pbdrvconfig.h +++ b/lib/pbio/platform/virtual_hub/pbdrvconfig.h @@ -41,6 +41,8 @@ #define PBDRV_CONFIG_MOTOR_DRIVER_NUM_DEV (6) #define PBDRV_CONFIG_MOTOR_DRIVER_VIRTUAL_SIMULATION (1) +#define PBDRV_CONFIG_UART_DEBUG_FIRST_PORT_IS_STDERR (1) + #define PBDRV_CONFIG_HAS_PORT_A (1) #define PBDRV_CONFIG_HAS_PORT_B (1) #define PBDRV_CONFIG_HAS_PORT_C (1)