Identity
| Part | Broadcom BCM43455 (inside the AzureWave AP6255 module, shared die with the BCM4345C5 BT radio) |
| Role | 2.4/5 GHz 802.11ac FullMAC WiFi |
| Bus / address | SDIO function 1 on RK3399 sdio0 / DWMMC (MMIO 0xfe310000) |
| GPIO / IRQ | WL_REG_ON = GPIO0_B2 (16) — must be asserted before SDIO probe; SDIO function-1 IRQ over DAT[1] |
| Datasheet | Broadcom BCM43455 (NDA; reference via brcmfmac and Cypress derivatives) |
| Pine64 wiki | PinePhone Pro — Connectivity |
| Schematic | sheet 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
- Our tree:
src/sys/dev/bwfm/bwfm_sdio.c(~2.3k lines) andsrc/sys/dev/bwfm/bwfm.c(~3.5k lines) — OpenBSD-derivedbwfm(4)ported to FreeBSD’smmc(4)/sdio(4)core, with PPP-specific NVRAM resolution, CLM blob upload, and syntheticnet80211glue. - OpenBSD origin:
sys/dev/sdmmc/if_bwfm_sdio.c— the SDIO bus glue and SDPCMD framing came from here. - Linux mainline:
drivers/net/wireless/broadcom/brcm80211/brcmfmac/— canonical FullMAC reference; NVRAM trimming, scan version probing, and credit reservation logic are all cribbed fromsdio.candcore.c.
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
- Reconfirm DHCP on a true cold boot; reboot already comes up without manual
repair after
mise run wifi:boot:phone. - Run long idle with
dev.bwfm_sdio.0.power_save=1and confirm firmware still reportspm=2 mpc=1afterwards. - Test wrong-password behavior and at least one additional AP/auth shape.
- Keep
debug.sdiob_run_clock_hz=25000000as the default, with0as the recovery knob if an SDIO-card regression needs probe-speed-only boot.
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:
- Trailer token lands at byte offset
roundup(C + 1, 4)(i.e. word indexroundup(C + 1, 4) / 4from the start of the buffer). - Byte at offset
Cis0x00(the single trailing NUL). - Bytes from
C + 1throughtoken_offset - 1are also0x00(M_ZERO alignment fill), but that count is0..3, never1..4like the old+= 2code produced. - Encoded token value is
(~w << 16) | (w & 0xffff)wherew = token_offset / 4— same shape as before, but the offset itself shifts back by one byte for anyC ≡ 3 (mod 4).
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.
- Pre-fix:
wpa_supplicantlog showsset_var: timeoutor 4-way handshake stalls 5–10 s while bulk data is queued; rekey can fail outright. - Post-fix: rekey completes in well under a second under sustained TX,
no
set_vartimeouts indmesg.
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:
irqmode=poll-onlywhen function IRQ claim failed.irqmode=irq-armed-init-pollduring deferred attach.irqmode=irq-armed-poll-fallbackwhen IRQ is claimed butsc_irq_countis still zero.irqmode=irq-active-watchdogafter at least one real function IRQ.
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.
Related
- Where we are — current WiFi status snapshot.
- What’s next — finish plan for SDIO IRQ + throughput.
- Finishing plan — bench predicates for the IRQ work.
- Cross-driver audit (2026-04-30) — bwfm + DWMMC audit, including NVRAM padding and credit reservation.
- Hardware reference — chip manifest.