Appendix · reference

Broadcom BCM43455 (WiFi)

AzureWave AP6255 SDIO WiFi radio on RK3399 sdio0

Identity

PartBroadcom BCM43455 (inside the AzureWave AP6255 module, shared die with the BCM4345C5 BT radio)
Role2.4/5 GHz 802.11ac FullMAC WiFi
Bus / addressSDIO function 1 on RK3399 sdio0 / DWMMC (MMIO 0xfe310000)
GPIO / IRQWL_REG_ON = GPIO0_B2 (16) — must be asserted before SDIO probe; SDIO function-1 IRQ over DAT[1]
DatasheetBroadcom BCM43455 (NDA; reference via brcmfmac and Cypress derivatives)
Pine64 wikiPinePhone Pro — Connectivity
Schematicsheet 4 (AP6255 module)

Status — ◐ partial

The native bwfm(4) SDIO path loads the firmware, the PinePhone Pro-specific NVRAM, and the CLM blob; scans the air; completes a WPA2 four-way handshake; and DHCP-binds wlan0. Verified on 2026-05-06 after a reboot: the SDIO bridge requests a 25 MHz run clock after discovery, Rockchip’s CIU clock reports clk_sdio=50000000, wlan0 associates to a WPA2 AP and uses 192.168.1.142, and a 256 KiB local HTTP fetch forced over wlan0 measured 2336 kBps. The same current-boot pass then moved 8 MiB host-to-phone at 1504 kBps, 32 MiB host-to-phone at 2294 kBps, and 8 MiB phone-to-host in 7.55s, all with cmd53_err=0, rx_err=0, tx_err=0, and rx_glom_errors=0. Reinstalling the boot networking path with mise run wifi:boot:phone also fixed the stale USB-default-route rc.conf: the next reboot returned with wlan0=192.168.1.142 and the default route on wlan0. Remaining work is polish: cold-boot reconfirmation, broader AP/auth coverage, and long idle with firmware PM/MPC enabled.

fn2 enable timeout — fixed 2026-05-04

The eMMC migration surfaced a latent bug: bwfm_sdio_attach’s poll for IOR.fn2-ready was capped at 500 ms, but on a cold boot of this hardware the chip’s fn2 fabric needs longer than that to acknowledge IOE.fn2=1. The chip booted firmware fine — debug.bwfm_state showed clk=AVAIL and mb=host=0x00040002 (which decodes as DEVREADY=1 plus SDPCM proto v4 in the upper word) — but every fn2 transaction afterwards timed out because the driver had given up on the function-2 enable handshake and proceeded with sc_fn2_ready=0.

4f93cc4 bwfm: extend fn2-enable timeout to 5 s, log mbdata on timeout bumps the loop bound from 500 to 5000 (matching Linux’s brcmf_sdio_bus_init) and prints TOHOSTMAILBOXDATA plus INTSTATUS on timeout so a future regression has the diagnostic in hand. Post-fix dmesg:

bwfm_sdio0: CLM version: API: 12.2
wlan0: Ethernet address: 90:e8:68:72:f8:df
debug.bwfm_state: clk=AVAIL fn2=1 init=1 sr=0 ...

Driver

The chip is FullMAC: the host hands the firmware a connect request and RSN IE (via the Broadcom wpaie iovar), the firmware drives the join, and the host sees an event when association completes. bwfm(4) synthesizes net80211 scan/join state from those events. The PPP NVRAM (brcmfmac43455-sdio.pine64,pinephone-pro.txt) sets the country/board parameters; the CLM blob (brcmfmac43455-sdio.clm_blob) provides regulatory tables and was the missing piece that turned scans from empty into real beacons.

The biggest open structural issue was SDIO function-1 IRQ delivery and then the post-enumeration run clock. Both now have bench receipts: dev.rockchip_dwmmc.0.sdio_intrs advances in irq-active-watchdog mode, and 8c61d6d sdiob: enable SDIO run clock by default makes debug.sdiob_run_clock_hz=25000000 the default after SDIO discovery. The driver-side Linux-parity cleanup around NVRAM packing, scan-version selection, EAPOL priority, control-frame credit reservation, and credit-window clamping is in the local overlay.

Open work

Parity verification

The NVRAM trailing-NUL count was fixed in bwfm_sdio.c:1061 to match Linux’s brcmf_fw_nvram_strip() in drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c, which does roundup(nvram_len + 1, 4) — exactly one trailing NUL plus alignment fill, then the 4-byte size token.

Bench predicate

After the fix, dump the post-pack NVRAM buffer with a temporary device_printf (or sysctl) inside bwfm_sdio_nvram_pad(). With stripped-content length C:

One-liner cross-check from a Linux PPP image

Compare the trailer that brcmfmac would have produced with what we now emit. From the pppfreebsd-honeyguide Linux side-image (or any postmarketOS PPP rootfs):

xxd /lib/firmware/brcm/brcmfmac43455-sdio.pine64,pinephone-pro.txt | tail

The last non-empty line of the source .txt is the final variable; the stripped-content length C is wc -c of the file minus any trailing \n/NULs. roundup(C + 1, 4) should match the offset our device_printf reports for the token. If they agree byte-for-byte, the firmware-side NVRAM CRC will too.

Credit reservation for control frames

bwfm_sdio_tx_ok now reserves TXCTL_CREDITS = 2 of the firmware sequence window when sc_bcdc_rxctlq is non-empty and the head of sc_txq is a data frame, mirroring Linux’s data_ok(). The head-type check is essential: control frames bypass the reservation so an in-flight ctrl request can’t gate its own dequeue and deadlock the path when credits ≤ 2. The fwvar response timeout in bwfm_proto_bcdc_txctl (bwfm.c:1374) was bumped from hz (1 s) to 2 * hz (2 s) to match BRCMF_FW_IOCTL_RESP_TIMEOUT.

Bench predicate. Saturate wlan0 TX (e.g. iperf3 -c host -t 60) and run wpa_cli reauthenticate from another shell.

Falsifier. If EAPOL handshakes still time out under load, the tx_ok calculation is wrong; the gating predicate should be (tx_max - tx_seq) > 2 whenever a control frame is queued. Verify that branch is reached with dev.bwfm_sdio.0.dump_trace or a temporary counter in bwfm_sdio_tx_ok.

TX credit-window clamp

bwfm_sdio_rx_frames() now clamps impossible firmware credit windows: if (uint8_t)(maxseqnr - sc_tx_seq) > 0x40, it caps sc_tx_max_seq at sc_tx_seq + 0x40, matching Linux’s defensive brcmf_sdio_readframes logic. The initial sc_tx_seq = 0xff sentinel is documented in the attach path: the first TX consumes sequence 0, and the first RX maxseqnr update opens the usable window.

Bench predicate. Under sustained TX, dev.bwfm_sdio.0.dump_trace should never show a credit window larger than 64. If firmware reports a rolled or absurd maxseqnr, the debug log should include the clamp line and traffic should continue instead of flooding the TX path.

Poll fallback observability

The watchdog fallback is now named instead of hidden behind irqmode=1. debug.bwfm_state reports:

The same state line also reports task_irq, task_watchdog, and task_coalesced. A successful SDIO IRQ bench should move work from task_watchdog to task_irq; a fallback-only bench should be honest and show irq=0, task_irq=0, and a rising task_watchdog.

The fallback cadence is tunable at runtime through dev.bwfm_sdio.0.poll_fallback_hz; the post-IRQ safety watchdog is dev.bwfm_sdio.0.irq_watchdog_hz. Invalid rates below 1 Hz or above 1000 Hz are rejected. The defaults are 100 Hz before the first real SDIO IRQ and 1 Hz after IRQs prove alive.

EAPOL priority classification

bcdc->priority is now 7 (NC) for EAPOL frames and 0 (AC_BE) for everything else, set in bwfm_sdio_tx_dataframe at the existing ethertype check (bwfm_sdio.c:1763). Linux brcmfmac does the equivalent in brcmf_proto_bcdc_txdata via brcmf_skb_pri_set().

Bench predicate. Same load test as above. With bwfm_debug raised so the tx eapol … line prints, every EAPOL frame should log bcdc_prio=7. Reauth (wpa_cli reauthenticate) under load should complete in under a second; pre-fix the same test occasionally stalled 5–10 s.

Falsifier. Check bwfm_debug=1 log shows priority=7 for EAPOL frames and priority=0 for everything else. If EAPOL still logs priority=0, the m_pkthdr.len >= sizeof(struct ether_header) guard or the m_copydata is failing — the frame may be a non-Ethernet shape on this code path.

BCM43455 scan version v0 shortcut

bwfm.c::bwfm_attach_net80211 short-circuits the scan_ver iovar probe when sc_chip.ch_chip == BRCM_CC_4345_CHIP_ID and forces sc_scan_ver = 0. The BCM43455 firmware doesn’t support scan v2 and Linux brcmfmac never queries it on this chip.

Bench predicate. First-attach dmesg for bwfm0 should not show the scan_ver get-iovar failure and the post-fail v0 fallback line. scan_ver should be reported as 0 directly. Subsequent scans (ifconfig wlan0 scan) should not produce a v2-iovar error.