Skip to content

Commit a9d1959

Browse files
committed
Pico 2 W (RP2350 + CYW43439): bare-metal wolfIP port
New port under src/port/rp2350_cyw43439/ for the Raspberry Pi Pico 2 W, building for both RP2350 cores (Cortex-M33 and Hazard3 RISC-V). Hardware-proven on a real Pico 2 W: RP2350 boot (vectors at image base, XOSC/clock init, UART0 console), clean-room CYW43439 gSPI transport over a PIO state machine, 32-bit bus mode, backplane + ALP clock, firmware blob download (ARM RUNNING), the SDPCM/CDC ioctl path (WLC_UP + STA MAC read), and the WPA2 join: cyw43_connect (sup_wpa=0, event_msgs, WSEC/WPA_AUTH, RSN IE, WLC_SET_SSID) associates to a live AP and cyw43_poll decodes the WLC_E_LINK events to report assoc up/down via SPI_STATUS (replacing the invalid PIO-owned GP24 read). Not yet exercised on silicon (run once the 4-way drives them): the BDC EAPOL/802.3 data path and WLC_SET_KEY. The wolfIP_wifi_ops vtable plus a host-supplicant 4-way driver gated behind WOLFIP_WITH_SUPPLICANT wire it to the in-tree supplicant. The ~225 KB Infineon firmware blob is NOT committed: weak accessors return NULL when it is absent (the build links and stops cleanly after the backplane stage), and a present fw_local/cyw43_fw_blob.c is compiled in. Bring-up logging is gated behind DEBUG_BRINGUP (default on).
1 parent 0ae9ba4 commit a9d1959

27 files changed

Lines changed: 3725 additions & 0 deletions
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# CYW43439 firmware blob - third-party license (Broadcom/Infineon +
2+
# George Robotics/Raspberry Pi, RP-silicon only). NOT distributed with
3+
# wolfIP. Obtain from the pico-sdk cyw43-driver firmware/ directory and
4+
# place the vendor headers + cyw43_fw_blob.c here for local builds.
5+
fw_local/
6+
7+
# Build artifacts
8+
*.o
9+
*.elf
10+
*.bin
11+
*.uf2

src/port/rp2350_cyw43439/Makefile

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Pi Pico 2 W (RP2350 + CYW43439) port Makefile
2+
#
3+
# Build: make CORE=m33 (default - Cortex-M33)
4+
# make CORE=hazard3 (Hazard3 RISC-V variant)
5+
#
6+
# Optional:
7+
# WIFI_SSID, WIFI_PSK : compile-time Wi-Fi credentials
8+
# (override config.h defaults)
9+
#
10+
# Output: app.elf + app.uf2 (UF2 via picotool, if available on PATH).
11+
# picotool load app.uf2 flashes via BOOTSEL.
12+
13+
CORE ?= m33
14+
15+
# The CORE switch forces the toolchain unconditionally. To use a
16+
# different cross compiler (e.g. a vendored toolchain), pass it on the
17+
# command line: `make CORE=m33 CC=/opt/arm/bin/arm-none-eabi-gcc`.
18+
ifeq ($(CORE),m33)
19+
CROSS_PREFIX ?= arm-none-eabi-
20+
ARCH_CFLAGS := -mcpu=cortex-m33+nodsp -mthumb -mfloat-abi=soft
21+
ARCH_LDFLAGS := -T target_m33.ld
22+
STARTUP_SRC := startup_m33.c
23+
else ifeq ($(CORE),hazard3)
24+
# The Debian/Ubuntu cross toolchain ships as riscv64-unknown-elf-* and
25+
# selects rv32 via -march/-mabi. Override CROSS_PREFIX if you have a
26+
# native riscv32-unknown-elf-* build (e.g. from SiFive or upstream).
27+
CROSS_PREFIX ?= riscv64-unknown-elf-
28+
ARCH_CFLAGS := -march=rv32imac -mabi=ilp32
29+
ARCH_LDFLAGS := -T target_hazard3.ld
30+
STARTUP_SRC := startup_hazard3.c
31+
else
32+
$(error CORE must be m33 or hazard3 (got '$(CORE)'))
33+
endif
34+
35+
CC := $(CROSS_PREFIX)gcc
36+
OBJCOPY := $(CROSS_PREFIX)objcopy
37+
SIZE := $(CROSS_PREFIX)size
38+
39+
ROOT := ../../..
40+
41+
CFLAGS := $(ARCH_CFLAGS) -Os -ffreestanding
42+
CFLAGS += -fdata-sections -ffunction-sections
43+
CFLAGS += -g -ggdb -Wall -Wextra -Werror
44+
CFLAGS += -I. -I$(ROOT) -I$(ROOT)/src
45+
CFLAGS += $(EXTRA_CFLAGS)
46+
47+
ifdef WIFI_SSID
48+
CFLAGS += -DWOLFIP_WIFI_SSID="\"$(WIFI_SSID)\""
49+
endif
50+
ifdef WIFI_PSK
51+
CFLAGS += -DWOLFIP_WIFI_PSK="\"$(WIFI_PSK)\""
52+
endif
53+
54+
LDFLAGS := -nostdlib $(ARCH_LDFLAGS) -Wl,-gc-sections
55+
56+
APP_SRCS := $(STARTUP_SRC) syscalls.c main.c rp2350_uart.c \
57+
rp2350_clocks.c rp2350_spi.c rp2350_pio.c \
58+
cyw43439_driver.c cyw43439_wifi.c
59+
60+
# CYW43439 firmware blob (NOT in the repo - third-party license, RP
61+
# silicon only). When fw_local/cyw43_fw_blob.c is present it is compiled
62+
# in and the firmware download runs; otherwise the weak accessors return
63+
# NULL and bring-up stops cleanly after the backplane/ALP stage.
64+
ifneq ($(wildcard fw_local/cyw43_fw_blob.c),)
65+
APP_SRCS += fw_local/cyw43_fw_blob.c
66+
CFLAGS += -Ifw_local
67+
endif
68+
APP_OBJS := $(APP_SRCS:.c=.o)
69+
70+
WOLFIP_SRC := $(ROOT)/src/wolfip.c
71+
WOLFIP_OBJ := wolfip.o
72+
73+
ALL_OBJS := $(APP_OBJS) $(WOLFIP_OBJ)
74+
75+
all: app.uf2
76+
@echo "Built RP2350+CYW43439 wolfIP port (CORE=$(CORE))"
77+
@$(SIZE) app.elf
78+
79+
app.elf: $(ALL_OBJS) target_$(CORE).ld
80+
$(CC) $(CFLAGS) $(ALL_OBJS) $(LDFLAGS) \
81+
-Wl,--start-group -lc -lm -lgcc -lnosys -Wl,--end-group -o $@
82+
83+
app.bin: app.elf
84+
$(OBJCOPY) -O binary $< $@
85+
86+
# Convert ELF to UF2. Prefers picotool when available; falls back to
87+
# the bundled tools/elf2uf2.py (~100 lines of Python, no deps) when
88+
# not. Both produce the same UF2 a BOOTSEL drive will accept.
89+
ifeq ($(CORE),m33)
90+
UF2_FAMILY := arm
91+
else
92+
UF2_FAMILY := riscv
93+
endif
94+
95+
app.uf2: app.elf
96+
@if command -v picotool >/dev/null 2>&1; then \
97+
picotool uf2 convert $< $@ ; \
98+
else \
99+
python3 tools/elf2uf2.py $< $@ --family $(UF2_FAMILY) ; \
100+
fi
101+
102+
%.o: %.c
103+
$(CC) $(CFLAGS) -c $< -o $@
104+
105+
$(WOLFIP_OBJ): $(WOLFIP_SRC)
106+
$(CC) $(CFLAGS) -Wno-unused-variable -Wno-unused-function \
107+
-Wno-unused-parameter -Wno-sign-compare \
108+
-Wno-missing-field-initializers -c $< -o $@
109+
110+
flash: app.uf2
111+
picotool load -f app.uf2
112+
113+
clean:
114+
rm -f *.o fw_local/*.o app.elf app.bin app.uf2 \
115+
selftest.elf selftest.bin selftest.uf2
116+
117+
.PHONY: all clean flash

src/port/rp2350_cyw43439/README.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# wolfIP port: Pi Pico 2 W (RP2350 + CYW43439)
2+
3+
Bare-metal wolfIP port for the [Raspberry Pi Pico 2 W](https://www.raspberrypi.com/products/raspberry-pi-pico-2/) board (RP2350 dual-arch SoC + Infineon CYW43439 Wi-Fi radio).
4+
5+
## Highlights
6+
7+
- **Dual-arch build**. `make CORE=m33` for Cortex-M33, `make CORE=hazard3` for Hazard3 RISC-V. Same wolfIP + supplicant source, different toolchain and entry stub.
8+
- **Firmware-loader-only CYW43439 driver**. Clean-room gSPI transport + SDPCM/CDC/BDC ioctl shim. No `cyw43-driver`, no WHD source reuse. The Infineon firmware blob is loaded into the radio's RAM at boot and is the only binary artifact carried over.
9+
- **wolfIP supplicant runs in host RAM**. All of WPA2-PSK 4-way, EAP-TLS (optional), WPA3-SAE (optional, software) is the in-tree `src/supplicant/`. The CYW43439 firmware does 802.11 mgmt (scan, auth-open, assoc) and never sees the PSK.
10+
11+
## Status
12+
13+
| Milestone | State |
14+
|-----------------------------------|-------|
15+
| Scaffolding (linker, startup) | done |
16+
| Boot + clocks + UART console | done, hardware-proven |
17+
| gSPI/PIO transport + 32-bit mode | done, hardware-proven |
18+
| Backplane + ALP + firmware load | done, hardware-proven (firmware RUNNING) |
19+
| SDPCM/CDC ioctl control plane | done, hardware-proven (WLC_UP + STA MAC read) |
20+
| Join + event-driven assoc state | done, hardware-proven (`cyw43_connect` associates to a real AP; `cyw43_poll` decodes `WLC_E_LINK` up/down) |
21+
| BDC EAPOL/802.3 TX-RX + `set_key` | **written, not yet validated on silicon** (needs the 4-way to drive them) |
22+
| `wolfIP_wifi_ops` impl | done (scan still a stub) |
23+
| Host supplicant 4-way integration | written behind `WOLFIP_WITH_SUPPLICANT` (needs cross-built wolfSSL) |
24+
| WPA2-PSK 4-way + UDP echo on real AP | pending wolfSSL-linked build |
25+
26+
The control plane (boot -> firmware -> ioctl) and the **join + event path** are proven on a real Pico 2 W: the radio associates to a WPA2 AP and `cyw43_poll` reports `assoc: UP`/`down` from the decoded events. Still unexercised: the BDC EAPOL data path and `cyw43_set_key`, which only run once the 4-way handshake drives them - that needs the wolfIP supplicant linked in (`WOLFIP_WITH_SUPPLICANT`). See "Open items" below.
27+
28+
Hardware validation runs against a real AP on the desk - hwsim does not validate this port (see `tools/hostapd/README.md` for the FullMAC limitation).
29+
30+
## Build
31+
32+
Requires:
33+
34+
- `arm-none-eabi-gcc` (M33 path) and/or `riscv32-unknown-elf-gcc` (Hazard3 path)
35+
- `picotool` on PATH (for UF2 conversion + flash)
36+
- A wolfSSL install with `WOLFSSL_PUBLIC_MP` if you enable SAE
37+
38+
```sh
39+
# Cortex-M33 build (default)
40+
make
41+
42+
# Hazard3 RISC-V build
43+
make CORE=hazard3
44+
45+
# Override the SSID / PSK baked into the binary
46+
make WIFI_SSID="my-ap" WIFI_PSK="my-passphrase"
47+
```
48+
49+
## Flash and run
50+
51+
```sh
52+
# Hold BOOTSEL on the Pico 2 W, plug USB, then:
53+
make flash
54+
# or copy app.uf2 to the RPI-RP2 mass-storage drive.
55+
56+
# Watch the UART console (GP0/GP1, 115200 8N1):
57+
stty -F /dev/ttyACM0 115200 raw -echo
58+
cat /dev/ttyACM0
59+
```
60+
61+
A Picoprobe (CMSIS-DAP) attached over SWD gives you live GDB:
62+
63+
```sh
64+
openocd -f interface/cmsis-dap.cfg -c "transport select swd" \
65+
-f target/rp2350.cfg -c "init"
66+
gdb-multiarch app.elf -ex 'target remote localhost:3333'
67+
```
68+
69+
## Pin map
70+
71+
| Function | RP2350 GPIO | Notes |
72+
|-------------------|------------:|------------------------------------------------|
73+
| UART0 TX | GP0 | console out |
74+
| UART0 RX | GP1 | console in |
75+
| CYW43 WL_REG_ON | GP23 | active high; pulses radio power |
76+
| CYW43 SPI DATA | GP24 | shared MOSI/MISO via 470 ohm series resistor; PIO-driven; also the chip's host-IRQ line when idle |
77+
| CYW43 SPI CS | GP25 | active low; CPU-driven |
78+
| CYW43 SPI CLK | GP29 | PIO side-set clock |
79+
80+
## Memory budget
81+
82+
| Region | Size | Notes |
83+
|----------|------|---------------------------------------------------------|
84+
| Flash | 4 MB | XIP from QSPI. ~225 KB consumed by CYW43439 blob. |
85+
| SRAM | 520 KB | Generous; wolfIP + supplicant + 8 TCP + driver < 200 KB|
86+
| Stack | 16 KB | Reserved at top of SRAM by `target_*.ld`. |
87+
88+
## Known constraints
89+
90+
- The CYW43439 gSPI bus is single-data-line and shares MOSI/MISO via a 470 ohm series resistor on the Pico 2 W carrier. The clean-room driver in `cyw43439_driver.c` accounts for this (PIO transport in `rp2350_pio.c`).
91+
- The DATA line (GP24) is owned by the PIO state machine, so the chip's host-IRQ-when-idle signal cannot be read via SIO. `cyw43_poll()` therefore polls `SPI_STATUS` (F2-packet-available) rather than a GPIO; IRQ-driven RX is deferred (RP2350 erratum E9 also makes edge-IRQ GPIO modes risky).
92+
- Bring-up logging is gated by `DEBUG_BRINGUP` (default 1 in `cyw43439_driver.h`). Build with `EXTRA_CFLAGS=-DDEBUG_BRINGUP=0` to compile out the gSPI/firmware/ioctl progress prints for a production image.
93+
- The supplicant defaults to WPA2-PSK with `mfp_capable=1`. For WPA3-SAE targets, build with `WOLFIP_ENABLE_SAE=1 WOLFSSL_PUBLIC_MP` and set `cfg.auth_mode=WOLFIP_AUTH_SAE`.
94+
95+
## Open items / not yet validated
96+
97+
Validated on real silicon (a WPA2 AP on the desk): the **join** (`cyw43_connect` -> rc=0, radio associates) and the **event path** (`cyw43_poll` -> `cyw43_handle_event` decodes `WLC_E_LINK` and reports `assoc: UP`/`down`). With no host 4-way linked the link flaps UP/down as the AP times out the missing EAPOL - which also exercises the link-down decode. Note: `cyw43_rsn_ie_wpa2_psk[]` MUST match the supplicant's `own_rsn_ie` (WPA2-PSK/CCMP, MFP off); if MFP is enabled the IE has to be plumbed from the supplicant instead of hard-coded.
98+
99+
Still to validate, in order:
100+
101+
1. **BDC TX/RX**: EAPOL (`cyw43_tx_eapol`) and 802.3 (`cyw43_tx_eth`) framing, and the inbound `data_offset` handling in `cyw43_poll` (only exercised once EAPOL flows).
102+
2. **Key install** (`cyw43_set_key`): the 160-byte `wl_wsec_key` layout (CCMP algo, primary-key flag, pairwise vs group MAC).
103+
3. **End-to-end**: link the host supplicant (`WOLFIP_WITH_SUPPLICANT` + cross-built wolfSSL), reach `AUTHENTICATED` (stops the flap), then DHCP + UDP echo on port 7.
104+
105+
Other gaps: `op_scan` is a stub (join-by-known-SSID only); the `supp_now_ms()` time source in `main.c` is a placeholder counter and needs a real RP2350 timer; the PMKSA-cache reuse path (PSK re-init) is exercised only by construction, not by a dedicated unit test; SAE-on-hardware stays software-validated this pass.

src/port/rp2350_cyw43439/board.h

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/* board.h - Pi Pico 2 W (RP2350 + CYW43439) board definitions
2+
*
3+
* Copyright (C) 2026 wolfSSL Inc.
4+
*
5+
* This file is part of wolfIP TCP/IP stack.
6+
*
7+
* wolfIP is free software; you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation; either version 3 of the License, or
10+
* (at your option) any later version.
11+
*/
12+
13+
#ifndef WOLFIP_RP2350_CYW43439_BOARD_H
14+
#define WOLFIP_RP2350_CYW43439_BOARD_H
15+
16+
/* RP2350 base addresses (RP2350 datasheet 2.2). */
17+
#define RP2350_ROM_BASE 0x00000000UL
18+
#define RP2350_XIP_BASE 0x10000000UL
19+
#define RP2350_SRAM_BASE 0x20000000UL
20+
#define RP2350_SRAM_SIZE 0x00082000UL /* 520 KB */
21+
#define RP2350_APB_BASE 0x40000000UL
22+
#define RP2350_AHB_BASE 0x50000000UL
23+
24+
/* SIO (single-cycle IO) - GPIO mailbox/atomic regs for both cores. */
25+
#define RP2350_SIO_BASE 0xD0000000UL
26+
27+
/* Pi Pico W and Pico 2 W carrier: the CYW43439 is attached to RP2350 via
28+
* a constrained gSPI bus (1-bit data line, shared MOSI/MISO through a
29+
* 470 ohm series resistor). Pin assignment is fixed on the Pico 2 W
30+
* carrier - see datasheet "Raspberry Pi Pico 2 W" Schematic v0.4.
31+
*
32+
* GP23 = WL_REG_ON (power enable; active high)
33+
* GP24 = SPI data (shared MOSI/MISO via 470 R; doubles as the
34+
* host-wake IRQ from CYW43 when SPI is idle)
35+
* GP25 = SPI CS (active low)
36+
* GP29 = SPI clock
37+
*
38+
* The clean-room driver polls GP24 for data-ready; an IRQ path can be
39+
* added once the polled bringup proves stable (RP2350 erratum E9: edge
40+
* IRQ on some pin modes can deadlock the core, prefer poll first).
41+
* These match the pico-sdk cyw43 pin config (WL_CLOCK=29, WL_DATA=24,
42+
* WL_CS=25, WL_REG_ON=23, WL_HOST_WAKE=24).
43+
*/
44+
#define CYW43_PIN_WL_REG_ON 23
45+
#define CYW43_PIN_SPI_DATA 24
46+
#define CYW43_PIN_SPI_CS 25
47+
#define CYW43_PIN_SPI_CLK 29
48+
#define CYW43_PIN_HOST_IRQ 24
49+
50+
/* UART0 for stdout console. Pi Pico 2 W exposes UART0 on GP0/GP1 by
51+
* default. The pico-sdk uses these and so do we. */
52+
#define UART0_PIN_TX 0
53+
#define UART0_PIN_RX 1
54+
#define UART0_BAUD 115200
55+
56+
#endif /* WOLFIP_RP2350_CYW43439_BOARD_H */

src/port/rp2350_cyw43439/config.h

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/* config.h - wolfIP configuration for Pi Pico 2 W (RP2350 + CYW43439)
2+
*
3+
* Copyright (C) 2026 wolfSSL Inc.
4+
*
5+
* This file is part of wolfIP TCP/IP stack.
6+
*
7+
* wolfIP is free software; you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation; either version 3 of the License, or
10+
* (at your option) any later version.
11+
*/
12+
13+
#ifndef WOLF_CONFIG_H
14+
#define WOLF_CONFIG_H
15+
16+
#ifndef CONFIG_IPFILTER
17+
#define CONFIG_IPFILTER 0
18+
#endif
19+
20+
/* 802.11 air MTU is 2304 (CCMP/QoS overhead); after SDPCM + WHD
21+
* encapsulation the host sees standard 1500-byte Ethernet frames. Keep
22+
* LINK_MTU at 1536 to leave room for VLAN tags. */
23+
#define ETHERNET
24+
#define LINK_MTU 1536
25+
26+
/* RP2350 has 520 KB SRAM; we can afford generous socket budgets.
27+
* 8 TCP * ~9 KB = ~72 KB
28+
* 3 UDP * ~3 KB = ~9 KB (DHCP + DNS + app)
29+
* 1 ICMP = ~3 KB
30+
* wolfIP core + supplicant + driver = ~80 KB
31+
* CYW43439 firmware blob (XIP, not SRAM) = ~225 KB in flash
32+
* Total static SRAM ~170 KB, leaves >300 KB for stack + heap (libc).
33+
*/
34+
#define MAX_TCPSOCKETS 8
35+
#define MAX_UDPSOCKETS 3
36+
#define MAX_ICMPSOCKETS 1
37+
#define RXBUF_SIZE LINK_MTU
38+
#define TXBUF_SIZE LINK_MTU
39+
40+
#define MAX_NEIGHBORS 8
41+
#define WOLFIP_ARP_PENDING_MAX 4
42+
43+
#ifndef WOLFIP_MAX_INTERFACES
44+
#define WOLFIP_MAX_INTERFACES 1
45+
#endif
46+
47+
#ifndef WOLFIP_ENABLE_FORWARDING
48+
#define WOLFIP_ENABLE_FORWARDING 0
49+
#endif
50+
51+
#ifndef WOLFIP_ENABLE_LOOPBACK
52+
#define WOLFIP_ENABLE_LOOPBACK 0
53+
#endif
54+
55+
#ifndef WOLFIP_ENABLE_DHCP
56+
#define WOLFIP_ENABLE_DHCP 1
57+
#endif
58+
59+
/* Default Wi-Fi credentials baked into the firmware. Override at build
60+
* time via -DWOLFIP_WIFI_SSID=... -DWOLFIP_WIFI_PSK=... or edit. */
61+
#ifndef WOLFIP_WIFI_SSID
62+
#define WOLFIP_WIFI_SSID "wolfIP-test"
63+
#endif
64+
#ifndef WOLFIP_WIFI_PSK
65+
#define WOLFIP_WIFI_PSK "ThisIsAPassword!"
66+
#endif
67+
68+
/* Static IP fallback when DHCP is disabled. */
69+
#define WOLFIP_IP "192.168.12.11"
70+
#define WOLFIP_NETMASK "255.255.255.0"
71+
#define WOLFIP_GW "192.168.12.1"
72+
#define WOLFIP_STATIC_DNS_IP "8.8.8.8"
73+
74+
#if WOLFIP_ENABLE_DHCP
75+
#define DHCP
76+
#endif
77+
78+
#endif /* WOLF_CONFIG_H */

0 commit comments

Comments
 (0)