Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
d55b946
zephyr-cp: raise the rpi_pico UDC thread stack in debug builds
tyeth Sep 8, 2026
43fef48
zephyr-cp: enable BLE on the Raspberry Pi Pico 2 W
tyeth Sep 8, 2026
62d47e6
zephyr-cp: fix Pico 2 W BLE bring-up found on hardware
tyeth Sep 8, 2026
74017f8
zephyr-cp: enable BLE on the Raspberry Pi Pico W
tyeth Sep 8, 2026
17f165b
ci: build zephyr-cp boards in the custom board workflow
tyeth Sep 8, 2026
72b031d
zephyr-cp: give the Pico 2 W twelve network contexts
tyeth Sep 9, 2026
6d50957
zephyr-cp: fix USB loss under BLE load on the Pico W boards
tyeth Sep 9, 2026
e6c2d16
zephyr-cp: enforce the _bleio scan timeout on legacy-scan controllers
tyeth Sep 9, 2026
d2ceb92
ssl: do not demand a client certificate from a server-side context
tyeth Sep 9, 2026
295395f
zephyr-cp: make TCP sockets work on the Pico W boards
tyeth Sep 9, 2026
395113b
zephyr-cp: make TLS 1.2 ECDHE and PEM certificates work on the Pico W…
tyeth Sep 9, 2026
8d7f46d
zephyr-cp: implement softAP with a DHCPv4 server on the Pico W boards
tyeth Sep 9, 2026
70cc61f
zephyr-cp: freeze .mpy modules named in circuitpython.toml
tyeth Sep 9, 2026
0d457f0
zephyr-cp: add make RTT=1 to put the console and log backend on SEGGE…
tyeth Sep 10, 2026
cef2fe5
Pin rebased Zephyr and retained HAL fixes for alpha.2
tyeth Oct 22, 2026
ca28494
Fit Pico W networking in RAM and isolate host mpy-cross build output
tyeth Oct 22, 2026
9e0262f
Fetch the shared-bus HCI driver submodule through west
tyeth Oct 22, 2026
1681490
zephyr-cp: reserve Pico W runtime RAM for USB and Python
tyeth Oct 22, 2026
5afc122
zephyr-cp: refresh version metadata on incremental builds
tyeth Oct 22, 2026
a24d38f
zephyr-cp: pin CYW43 combo firmware and HCI framing fixes
tyeth Oct 22, 2026
3e8c160
Reserve Pico W runtime RAM for verified TLS with BLE
tyeth Oct 22, 2026
6fc7497
Pin atomic CYW43 backplane transactions for scan coexistence
tyeth Oct 22, 2026
e455303
zephyr-cp: allow complete modern CA chains in TLS server records
tyeth Oct 22, 2026
9f2ff69
Pin atomic CYW43 bus wake and power transition fixes
tyeth Oct 22, 2026
1609ec9
Use one mass-storage buffer to recover Pico W TLS heap
tyeth Oct 22, 2026
5bb1d64
zephyr-cp: require completed Bluetooth startup before settings and id…
tyeth Oct 22, 2026
d86cb7a
Restore empty CP_VERSION fallback in Zephyr integration
tyeth Oct 22, 2026
a0c1485
zephyr-cp: reject unsupported extended scan requests
tyeth Oct 22, 2026
1ca7952
Pin Zephyr settings sector validation fix
tyeth Oct 22, 2026
7d4ef1a
zephyr-cp: pin CYW43 receive bus contention fix
tyeth Oct 22, 2026
6b117bd
zephyr: keep socket services runnable alongside the interpreter
tyeth Oct 23, 2026
9e153ad
zephyr: implement station lifecycle and preserve failed disconnect state
tyeth Oct 23, 2026
6839890
Pin USB reconfiguration and CDC cancellation recovery fixes
tyeth Oct 23, 2026
0d545ef
zephyr-cp: pin Bluetooth firmware-ready retry guard
tyeth Oct 23, 2026
5f73172
zephyr-cp: propagate MSC block errors and validate transfer ranges
tyeth Oct 24, 2026
3254e27
zephyr-cp: pin advertiser identity selection for first passive scan
tyeth Oct 24, 2026
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
7 changes: 7 additions & 0 deletions .github/actions/deps/ports/zephyr-cp/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ runs:
run: west zephyr-export
shell: bash
working-directory: ports/zephyr-cp
- name: Fetch cyw43-driver submodule
# The CYW43 shared-bus Bluetooth transport needs the georgerobotics
# cyw43-driver headers and the BT controller patchram, which live in a
# submodule of hal_rpi_pico that west does not check out.
run: git -C modules/hal/rpi_pico submodule update --init --depth 1 lib/cyw43-driver
shell: bash
working-directory: ports/zephyr-cp
- name: Install Zephyr protobuf dependency
run: pip install protobuf==6.33.5
shell: bash
19 changes: 18 additions & 1 deletion .github/workflows/build-board-custom.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ jobs:
PORT=$(python tools/board_to_port.py "${{ inputs.board }}")
echo "port=$PORT" >> $GITHUB_OUTPUT
shell: bash
- name: Build extensions
id: build-extensions
run: |
EXT=$(python tools/board_build_extensions.py "${{ inputs.board }}")
echo "extensions=$EXT" >> $GITHUB_OUTPUT
shell: bash
- name: Set up port
id: set-up-port
uses: ./.github/actions/deps/ports
Expand Down Expand Up @@ -121,7 +127,18 @@ jobs:
BOARD: ${{ inputs.board }}
FLAGS: ${{ inputs.flags }}
DEBUG: ${{ inputs.debug && '1' || '0' }}
run: make -j$(nproc) $FLAGS BOARD="$BOARD" DEBUG=$DEBUG TRANSLATION="$TRANSLATION"
BUILD: build-${{ inputs.board }}
EXTENSIONS: ${{ steps.build-extensions.outputs.extensions }}
run: |
# Name the firmware.<ext> targets explicitly rather than relying on the
# default goal. The zephyr-cp port's default goal is the Zephyr ELF, so
# the firmware.* copies the artifact upload expects are never made.
ARTIFACTS=""
for ext in $EXTENSIONS; do
ARTIFACTS="$ARTIFACTS $BUILD/firmware.$ext"
done
make -j4 $FLAGS BOARD="$BOARD" BUILD="$BUILD" DEBUG=$DEBUG \
TRANSLATION="$TRANSLATION" $ARTIFACTS
working-directory: ports/${{ steps.board-to-port.outputs.port }}
- name: Upload artifact
uses: actions/upload-artifact@v7
Expand Down
13 changes: 10 additions & 3 deletions ports/zephyr-cp/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,17 @@ ifneq ($(CP_BOARD_CONSOLE_OVERLAY),)
CP_BOARD_CONF := $(strip $(CP_BOARD_CONF) $(CURDIR)/console.conf)
endif
endif
# When RTT=1, move the Zephyr console and log backend from UART0 to SEGGER RTT, so
# printk/LOG_* can be read over the same SWD probe with OpenOCD (`rtt setup`,
# `rtt start`, `rtt server start <port> 0`) even when USB CDC is down and no UART
# is wired. Composable with DEBUG=1.
RTT_CONF_FILE ?= $(CURDIR)/rtt.conf
ifeq ($(RTT),1)
CP_BOARD_CONF := $(strip $(CP_BOARD_CONF) $(RTT_CONF_FILE))
endif
CP_BOARD_CONF := $(subst $(eval) ,;,$(CP_BOARD_CONF))
# Always pass the conf and console overlay files, even if empty. This undoes DEBUG=1 or CONSOLE=1
# when it's omitted on a later run. Quoted because the conf value can be a semicolon-separated
# list, and an unquoted semicolon would end the shell command.
# Always pass both values, even if empty, to undo DEBUG/CONSOLE/RTT on a later run.
# Quote the semicolon-separated configuration list for the shell.
WEST_CMAKE_ARGS += -Dzephyr-cp_EXTRA_CONF_FILE="$(CP_BOARD_CONF)"
WEST_CMAKE_ARGS += -Dzephyr-cp_EXTRA_DTC_OVERLAY_FILE="$(CP_BOARD_CONSOLE_OVERLAY)"

Expand Down
7 changes: 7 additions & 0 deletions ports/zephyr-cp/background.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
#include "py/runtime.h"
#include "supervisor/port.h"

#if CIRCUITPY_BLEIO
#include "common-hal/_bleio/__init__.h"
#endif

#include <zephyr/kernel.h>

void port_start_background_tick(void) {
Expand All @@ -26,4 +30,7 @@ void port_background_task(void) {
#if defined(CONFIG_ARCH_POSIX)
k_busy_wait(100);
#endif
#if CIRCUITPY_BLEIO
bleio_background();
#endif
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "Raspberry Pi Foundation Raspberry Pi Pico 2 W"

[modules]
__future__ = true
_bleio = false
_bleio = true # Zephyr board has _bleio
_eve = false
_pew = false
_pixelmap = false
Expand Down
67 changes: 67 additions & 0 deletions ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/board.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ CONFIG_NET_IPV4=y
CONFIG_NET_DHCPV4=y
CONFIG_NET_SOCKETS=y

# TCP. Nothing in the port or the SoC defaults turns it on, so until now every
# SOCK_STREAM socket on these boards failed inside net_context_get() with
# EPROTOTYPE -- surfaced by socketpool as "Out of sockets" -- which also means
# the web workflow's listener never opened. HTTP(S) clients and servers need it.
CONFIG_NET_TCP=y

CONFIG_WIFI=y
CONFIG_WIFI_NM_WPA_SUPPLICANT_LEGACY_CRYPTO=n
CONFIG_NET_L2_WIFI_MGMT=y
Expand All @@ -14,3 +20,64 @@ CONFIG_NET_HOSTNAME_DYNAMIC=y
CONFIG_NET_HOSTNAME="circuitpython"

CONFIG_MBEDTLS=y

# The collector's captive portal serves HTTPS from a PEM certificate chain
# (fullchain.pem / key.pem via ssl.SSLContext.load_cert_chain); mbedTLS only
# parses DER unless PEM decoding is compiled in.
CONFIG_MBEDTLS_PEM_PARSE_C=y

# TLS 1.2 ECDHE. Zephyr's mbedTLS 4 default serves secp256r1 through the p256-m
# PSA driver and leaves the builtin ECP module out. mbedTLS then defines a dummy
# MBEDTLS_ECP_MAX_BITS of 1, and ssl.h sizes the TLS 1.2 premaster buffer
# (union mbedtls_ssl_premaster_secret._pms_ecdh[MBEDTLS_ECP_MAX_BYTES]) from
# it, so every ECDHE key agreement fails with PSA_ERROR_BUFFER_TOO_SMALL -- a
# 1-byte buffer for a 32-byte shared secret -- and surfaces as OSError 138 on
# the first read of any TLS connection, client or server. Keep the builtin
# ECP module (p256-m off) until the header sizes that buffer from PSA.
CONFIG_MBEDTLS_PSA_P256M_DRIVER_ENABLED=n

# SoftAP. wifi.radio.start_ap() drives the AIROC driver's ap_enable through
# wifi_mgmt (the driver already implements it; this only enables the pieces
# around it). The DHCPv4 server hands joining stations addresses from a pool
# just above the AP's own -- 192.168.4.1 by default, as on ESP32 -- and tells
# them the AP is their DNS server and (RFC 8910 option 114) captive portal, so
# a portal served from CircuitPython is found by phones and laptops without
# the user typing an address.
CONFIG_NET_DHCPV4_SERVER=y
CONFIG_NET_DHCPV4_SERVER_ADDR_COUNT=8
CONFIG_NET_DHCPV4_SERVER_OPTION_DNS_ADDRESS="192.168.4.1"
CONFIG_NET_DHCPV4_SERVER_OPTION_CAPTIVE_PORTAL=y

# Bluetooth over the shared gSPI bus (CONFIG_BT itself comes from the
# zephyr,bt-hci chosen node in the overlay).
#
# The WHD WiFi, cybt BT and coexistence call chains are deeper than the
# defaults in ../prj.conf; the Cortex-M33 MPU catches an overflow cleanly
# instead of silently corrupting neighbouring stacks.
CONFIG_HW_STACK_PROTECTION=y
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096

# The CYW43439's BT controller firmware (CYW4343A2_001.003.016.0065.0000) does
# not implement the Bluetooth 5 extended advertising/scanning commands: it
# rejects LE Set Extended Scan Parameters (0x2041) with status 0x01 "Unknown
# HCI Command", so scanning fails with -EIO. The port defaults BT_EXT_ADV on,
# so turn it off here to make the host use the legacy 0x200B/0x200C scan and
# legacy advertising commands instead.
CONFIG_BT_EXT_ADV=n

# Sockets. Every Zephyr socket -- a listener, each accepted client, each UDP
# socket -- takes one net_context, and the default pool is 6. A CircuitPython
# program serving HTTP over the station link to a browser that opens several
# connections per page, with NTP and a captive/mDNS UDP socket beside it, sits
# at that ceiling, and the 6th socket() or accept() fails with ENOMEM. A
# net_context is ~150 bytes; the RP2350 has the RAM, so double the pool and
# the connection-handler table that goes with it (NET_MAX_CONN, default 8).
CONFIG_NET_MAX_CONTEXTS=12
CONFIG_NET_MAX_CONN=16

# File descriptors. Zephyr sizes the fd table from the subsystems' declared
# needs (ZVFS_OPEN_ADD_SIZE_*), which came to 4 here; the DHCPv4 server takes
# one socket plus the socket-service eventfd, leaving a program two, and
# socketpool.socket() then fails with "Out of sockets" at the first TLS
# listener. Give sockets a real ceiling (each fd entry is ~12 bytes).
CONFIG_ZVFS_OPEN_MAX=16
Original file line number Diff line number Diff line change
@@ -1,3 +1,57 @@
/* Partition layout is owned by the mcuboot fork (dts/raspberrypi/rpi_pico2.dtsi)
* and applied to this image by sysbuild. This overlay only adds board/app config. */

/*
* Settings partition: one exception to the layout above. sysbuild prepends the
* adaboot layout (dts/raspberrypi/rpi_pico2_w.dtsi) to this overlay, so what
* is set here wins.
*
* The settings partition backs Bluetooth bond keys via NVS, so it must be
* erase-sector aligned and hold at least two of the RP2350's 4K sectors.
* The layout gives it one (0x17f000, 4K); nvs_mount() rejects a single sector
* with -EINVAL, which makes bt_enable() fail before ever opening the HCI
* driver and surfaces as a bare OSError from "import _bleio".
*
* nvm and the filesystem must stay where ports/raspberrypi puts them
* (cptools/check_partitions.py enforces this), so the second sector comes out
* of the code partition, not out of the CIRCUITPY drive.
*/
&code_partition {
reg = <0x0 0x17e000>;
};

/delete-node/ &storage_partition;

&flash0 {
partitions {
storage_partition: partition@17e000 {
compatible = "zephyr,mapped-partition";
label = "storage";
reg = <0x17e000 0x2000>;
};
};
};

/*
* Bluetooth. The CYW43439's HCI is not on a UART here — it shares the WiFi
* gSPI bus — so hang the shared-bus HCI transport off the board's stock
* infineon,airoc-wifi node and point the host at it. CONFIG_BT then turns
* itself on via the zephyr,bt-hci chosen (see
* ../../../internal-modules/circuitpython-kconfig/Kconfig).
*/
&pio0_spi0 {
airoc-wifi@0 {
cyw43_bt_hci: cyw43_bt_hci {
compatible = "infineon,cyw43-bt-hci";
status = "okay";
};
};
};

/ {
chosen {
zephyr,bt-hci = &cyw43_bt_hci;
};
};

#include "../../../app.overlay"
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "Raspberry Pi Foundation Raspberry Pi Pico W"

[modules]
__future__ = true
_bleio = false
_bleio = true # Zephyr board has _bleio
_eve = false
_pew = false
_pixelmap = false
Expand Down
90 changes: 90 additions & 0 deletions ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/board.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ CONFIG_NET_IPV4=y
CONFIG_NET_DHCPV4=y
CONFIG_NET_SOCKETS=y

# TCP. Nothing in the port or the SoC defaults turns it on, so until now every
# SOCK_STREAM socket on these boards failed inside net_context_get() with
# EPROTOTYPE -- surfaced by socketpool as "Out of sockets" -- which also means
# the web workflow's listener never opened. HTTP(S) clients and servers need it.
CONFIG_NET_TCP=y

CONFIG_WIFI=y
CONFIG_WIFI_NM_WPA_SUPPLICANT_LEGACY_CRYPTO=n
CONFIG_NET_L2_WIFI_MGMT=y
Expand All @@ -15,4 +21,88 @@ CONFIG_NET_HOSTNAME="circuitpython"

CONFIG_MBEDTLS=y

# The collector's captive portal serves HTTPS from a PEM certificate chain
# (fullchain.pem / key.pem via ssl.SSLContext.load_cert_chain); mbedTLS only
# parses DER unless PEM decoding is compiled in.
CONFIG_MBEDTLS_PEM_PARSE_C=y

# TLS 1.2 ECDHE. Zephyr's mbedTLS 4 default serves secp256r1 through the p256-m
# PSA driver and leaves the builtin ECP module out. mbedTLS then defines a dummy
# MBEDTLS_ECP_MAX_BITS of 1, and ssl.h sizes the TLS 1.2 premaster buffer
# (union mbedtls_ssl_premaster_secret._pms_ecdh[MBEDTLS_ECP_MAX_BYTES]) from
# it, so every ECDHE key agreement fails with PSA_ERROR_BUFFER_TOO_SMALL -- a
# 1-byte buffer for a 32-byte shared secret -- and surfaces as OSError 138 on
# the first read of any TLS connection, client or server. Keep the builtin
# ECP module (p256-m off) until the header sizes that buffer from PSA.
CONFIG_MBEDTLS_PSA_P256M_DRIVER_ENABLED=n

# File descriptors. Zephyr sizes the fd table from the subsystems' declared
# needs (ZVFS_OPEN_ADD_SIZE_*), which came to 4 here; the DHCPv4 server takes
# one socket plus the socket-service eventfd, leaving a program two, and
# socketpool.socket() then fails with "Out of sockets" at the first TLS
# listener. Give sockets a real ceiling (each fd entry is ~12 bytes).
CONFIG_ZVFS_OPEN_MAX=16

# SoftAP. wifi.radio.start_ap() drives the AIROC driver's ap_enable through
# wifi_mgmt (the driver already implements it; this only enables the pieces
# around it). The DHCPv4 server hands joining stations addresses from a pool
# just above the AP's own -- 192.168.4.1 by default, as on ESP32 -- and tells
# them the AP is their DNS server and (RFC 8910 option 114) captive portal, so
# a portal served from CircuitPython is found by phones and laptops without
# the user typing an address.
CONFIG_NET_DHCPV4_SERVER=y
CONFIG_NET_DHCPV4_SERVER_ADDR_COUNT=8
CONFIG_NET_DHCPV4_SERVER_OPTION_DNS_ADDRESS="192.168.4.1"
CONFIG_NET_DHCPV4_SERVER_OPTION_CAPTIVE_PORTAL=y

CONFIG_TEST_RANDOM_GENERATOR=y

# Bluetooth over the shared gSPI bus (CONFIG_BT itself comes from the
# zephyr,bt-hci chosen node in the overlay).
#
# The CYW43439's BT controller firmware does not implement the Bluetooth 5
# extended advertising/scanning commands, and the port defaults BT_EXT_ADV on,
# so turn it off here and let the host use the legacy 0x200B/0x200C commands.
CONFIG_BT_EXT_ADV=n
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096

# The RP2040 has 264 KB RAM. Five reserved BLE connections plus WiFi/TLS
# overflow RAM with alpha.2; two cover the workflow and one application peer.
CONFIG_BT_MAX_CONN=2

# Keep the WiFi + BLE integration within RP2040 RAM. The portal uses IPv4;
# six contexts cover DHCP/DNS, a listener, an accepted client and spare
# outbound sockets. Pico 2 W keeps the larger dual-stack configuration.
CONFIG_NET_IPV6=n
CONFIG_NET_MAX_CONTEXTS=6
CONFIG_NET_MAX_CONN=6

# Leave runtime RAM for the Python heap and USB CDC buffers. Merely linking
# the dual-radio image can consume all SRAM and silently disable USB CDC.
# Keep full-sized WHD packets and the six-buffer coexistence reserve; reduce
# burst capacity only on the 264 KB RP2040 (Pico 2 W retains the defaults).
CONFIG_AIROC_WIFI_TX_PACKET_POOL_COUNT=4
CONFIG_AIROC_WIFI_RX_PACKET_POOL_COUNT=4
# A verified RSA TLS handshake alongside BLE used 3,536 bytes of this stack.
# Keep an 8 KiB reservation with overflow detection and high-water evidence;
# the former 16 KiB reservation left too little heap for that handshake.
CONFIG_MAIN_STACK_SIZE=8192

# Keep space for TLS's 16 KB inbound / 8 KB outbound records and handshake.
# IPv4 packets remain full-sized through chained 128-byte buffers; the smaller
# pools limit concurrent bursts rather than the accepted network MTU.
CONFIG_NET_PKT_RX_COUNT=8
CONFIG_NET_PKT_TX_COUNT=8
CONFIG_NET_BUF_RX_COUNT=24
CONFIG_NET_BUF_TX_COUNT=24

# Retain stack/allocator measurements for on-device RAM validation.
CONFIG_INIT_STACKS=y
CONFIG_STACK_SENTINEL=y
CONFIG_SYS_HEAP_RUNTIME_STATS=y

# A second 4 KiB USB mass-storage transfer buffer leaves insufficient native
# heap for a verified RSA handshake with the full-chain TLS record sizes.
# Keep mass storage and its full transfer size; trade pipelined throughput
# for RAM on RP2040. Pico 2 W retains double buffering.
CONFIG_USBD_MSC_DOUBLE_BUFFERING=n
Loading
Loading