feat: add Broadcom FullMAC Wi-Fi driver
This commit is contained in:
1 parent
8b4a938164
commit
51a2b3a932
34 files changed
+4719
-95
No files matched your search
+85
-2
@@ -157,6 +157,7 @@ PL011 on GPIO 14/15: header pins 8 (TX) and 10 (RX), ground on pin 6, 115200
|
||||
| VideoCore firmware mailbox (radio power, clocks, board MAC) | `BCM2849` (`RPIQ`) | `Drivers/Platform/RpiFirmware.cpp` |
|
||||
| GPIO pin functions | `BCM2845` (`GPI0`) | `Drivers/Platform/RpiGpio.cpp` |
|
||||
| Ethernet (GENET v5 MAC, BCM54213PE PHY) | `BCM6E4E` (`\_SB.ETH0`) | `Drivers/Net/Genet.cpp` |
|
||||
| Wi-Fi (the same CYW4345x, SDIO) | Arasan SD controller `BCM2847`, GPIO 34-39 | `Drivers/Net/Wifi/Brcmf*.cpp` |
|
||||
|
||||
The VL805 sits behind the BCM2711's PCIe, whose config space is not ECAM
|
||||
(the root bridge's `_DSD` says `linux-ecam-quirk-id = "bcm2711"`), so the
|
||||
@@ -239,18 +240,100 @@ The MAC address is the board's, read from OTP through the firmware mailbox
|
||||
driver programmed is the fallback. `dhcp.elf` waits up to 10 s for a wired
|
||||
link, since autonegotiation takes about 3 s after the PHY reset.
|
||||
|
||||
## Wi-Fi
|
||||
|
||||
The radio's WLAN half is an SDIO card on the BCM2711's older SD controller,
|
||||
the Arasan-derived EMMC (`BCM2847`, `0x7E300000`); the card slot is on
|
||||
EMMC2, so the two never share a bus. Its pins are GPIO 34-39 (ALT3; clock
|
||||
without a pull, the rest pulled up, as Linux's `sdio_pins`) and its power
|
||||
switch, `WL_ON`, is line 1 of the firmware's GPIO expander. The probe
|
||||
claims the controller only while the slot is on EMMC2 (`BRCME88C` present);
|
||||
if the tables do not describe the Arasan, its address is taken as EMMC2's
|
||||
less 256 KiB.
|
||||
|
||||
It is a FullMAC radio: the firmware does the 802.11 MLME itself and the host
|
||||
sees Ethernet frames. The driver follows Linux's brcmfmac (ISC license,
|
||||
Broadcom; its notice is in `NOTICES.txt`):
|
||||
|
||||
| File | Part |
|
||||
|---|---|
|
||||
| `BrcmfSdio.cpp` | the Arasan as an SDIO host: CMD5/3/7, 4-bit at 25 MHz, CMD52/CMD53, polled PIO, 32-bit register access |
|
||||
| `BrcmfChip.cpp` | backplane window (function 1), clocks, the enumeration ROM's core list, firmware + NVRAM download, function 2, the firmware console |
|
||||
| `BrcmfProto.cpp` | SDPCM framing and transmit credit, CDC ioctls, BDC data, events, the receive pump, the transmit queue |
|
||||
| `BrcmfConnect.cpp` | preinit and CLM, escan, join, keys, link state |
|
||||
|
||||
Once the root is mounted the idle loop:
|
||||
|
||||
1. routes the pins, switches the EMMC clock on, pulses `WL_ON`, enumerates
|
||||
the card (function 1 blocks of 64 bytes, function 2 of 512);
|
||||
2. requests the ALP clock, reads the chip ID: `0x4345` revision 9 is the
|
||||
CYW43456 (Pi 400), any other revision the CYW43455 (Pi 4B), which picks
|
||||
`0:/os/firmware/brcm/brcmfmac43456-sdio.*` or `brcmfmac43455-sdio.*`
|
||||
(shipped unmodified from Raspberry Pi OS's firmware-nonfree);
|
||||
3. halts the ARM Cortex-R4, holds the 802.11 core in reset, sizes the RAM
|
||||
from the CR4's banks, writes the firmware at `0x198000` and the NVRAM
|
||||
(stripped to `key=value\0` pairs and a length token) at the top of RAM,
|
||||
reading both back to verify, puts the reset vector at address 0 and
|
||||
releases the CPU;
|
||||
4. waits for the HT clock, sends the SDPCM protocol version through the
|
||||
mailbox, enables function 2, the F2 watermark and MES busy signalling of
|
||||
Linux's CYW43455 settings, save/restore if the chip has it, and the card
|
||||
interrupt;
|
||||
5. reads `cur_etheraddr` (the NVRAM's shared placeholder address
|
||||
`b8:27:eb:74:f2:6c` is replaced with the board address + 1, as on
|
||||
Raspberry Pi OS), loads the CLM blob (`clmload`, 1400-byte chunks: these
|
||||
firmware builds carry no regulatory data of their own), turns off glom,
|
||||
roaming, ARP/ND offload and power save, enables the events it handles and
|
||||
brings the interface up (`WLC_UP`).
|
||||
|
||||
Nothing uses the controller's interrupt: the bus is swept from the idle
|
||||
loop (and from the timer tick when the cores never idle), every 10 ms while
|
||||
a scan or a connection is active and every 250 ms otherwise, and at once when
|
||||
the controller shows the card interrupt. Every sweep reads the SDIO core's
|
||||
interrupt status, then every frame waiting in function 2 (a 4-byte header
|
||||
read, then the rest). Ioctls wait for transmit credit and their answer by
|
||||
sweeping the bus themselves. Frames are delivered with the bus unlocked, and
|
||||
transmitting never waits for the bus: data frames go into a 32-frame queue
|
||||
that whoever holds the bus, or the next sweep, sends.
|
||||
|
||||
Scans are escans (all channels, active, firmware dwell times); each result
|
||||
event carries a `brcmf_bss_info_le` whose information elements go into the
|
||||
same scan table the Intel driver fills from beacons. A join sets `wpaie`
|
||||
(the RSN element our supplicant builds), `wpa_auth`, `auth` and `wsec`, then
|
||||
`join` with the BSSID and the chanspec escan reported. The 4-way handshake
|
||||
runs in the host's supplicant (`Wpa.cpp`) as for the Intel adapter: EAPOL
|
||||
frames are ordinary Ethernet frames here, and the keys go to the firmware
|
||||
with `wsec_key` once message 4 has left the transmit queue. The radio does
|
||||
AES-CCMP only: GCMP networks are refused, as are WPA3-only (SAE), WEP and
|
||||
TKIP ones.
|
||||
|
||||
Unanswered ioctls (a leaky bucket of three), a halt reported through
|
||||
the mailbox, or repeated bus errors mark the radio failed: the firmware's
|
||||
console and any trap record are printed to the kernel log (`WiFi-FW`), and
|
||||
the facade's recovery powers the radio off and repeats the bring-up (at
|
||||
most three times).
|
||||
|
||||
`tests/wifi/run.sh` drives `BrcmfProto.cpp` and `BrcmfConnect.cpp` with the
|
||||
real supplicant against a simulated firmware: preinit and the CLM chunks,
|
||||
escan parsing, open and WPA2 joins (an independent authenticator checks
|
||||
messages 2 and 4 and the installed keys), the wrong passphrase, refusals,
|
||||
join timeouts and a firmware that stops answering.
|
||||
|
||||
## Not supported yet
|
||||
|
||||
- SD high-speed mode: the driver transfers by polled PIO at 25 MHz on a
|
||||
4-bit bus (multi-block CMD18/CMD25 with Auto CMD12), at most 12.5 MB/s;
|
||||
switching the card to 50 MHz (CMD6) would double that.
|
||||
- Wi-Fi (the CYW4345x's SDIO half, on the Arasan controller).
|
||||
- Wi-Fi beyond 802.11 station mode on one interface: no access point mode,
|
||||
no SDIO high speed (50 MHz), no glom, no firmware supplicant.
|
||||
- Audio (HDMI via VideoCore, the 3.5 mm jack via PWM).
|
||||
|
||||
## Status
|
||||
|
||||
Bluetooth works on a Pi 400 (CYW43456), including A2DP music to
|
||||
headphones. Ethernet has not run yet: QEMU has no GENET model. It is untested on a Pi 4B (CYW43455), and QEMU has no model of
|
||||
headphones. Ethernet has not run yet: QEMU has no GENET model. Wi-Fi has
|
||||
not run on hardware yet either (QEMU has no model of the radio); only its
|
||||
protocol and connection layers are tested, in the host harness. It is untested on a Pi 4B (CYW43455), and QEMU has no model of
|
||||
the radio.
|
||||
|
||||
Everything else above is verified on QEMU only, including the Pi-specific path:
|
||||
|
||||
@@ -12,4 +12,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MONTAUK_BUILD_NUMBER 109
|
||||
#define MONTAUK_BUILD_NUMBER 123
|
||||
@@ -36,6 +36,9 @@ namespace kcp {
|
||||
Arch::Pause();
|
||||
}
|
||||
}
|
||||
bool TryAcquire() {
|
||||
return !flag.test_and_set(std::memory_order_acquire);
|
||||
}
|
||||
void Release() {
|
||||
flag.clear(std::memory_order_release);
|
||||
}
|
||||
|
||||
@@ -449,11 +449,43 @@ namespace Drivers {
|
||||
Net::Genet::ProbePlatform(base, length, gsi, level, activeLow, coherent, phyMode, burst);
|
||||
}
|
||||
|
||||
// The Raspberry Pi 4/400's Wi-Fi: the CYW4345x's SDIO half on the
|
||||
// BCM2711's older Arasan controller (BCM2847, GPIO 34-39). Only while the
|
||||
// card slot is on EMMC2 (BRCME88C): with the firmware's "SD on Arasan"
|
||||
// option the Arasan runs the slot instead, and is not the radio's.
|
||||
static void ProbePlatformBroadcomWifi() {
|
||||
Hal::AcpiDevices::DeviceInfo emmc2{};
|
||||
if (!Hal::AcpiDevices::FindDeviceByHid("BRCME88C", emmc2)) return;
|
||||
|
||||
Hal::AML::ResourceList resources;
|
||||
uint64_t base = 0, length = 0;
|
||||
Hal::AcpiDevices::DeviceInfo info{};
|
||||
const char* name = "BCM2847";
|
||||
if (Hal::AcpiDevices::FindDeviceByHid("BCM2847", info) &&
|
||||
Hal::AcpiDevices::EvaluateCrs(info.NodeIndex, resources) &&
|
||||
resources.GetMemory(base, length) && base != 0) {
|
||||
name = info.Path;
|
||||
} else {
|
||||
// Not described: the Arasan sits 256 KiB below EMMC2 on the
|
||||
// BCM2711 (0x7E300000 / 0x7E340000 bus addresses).
|
||||
Hal::AML::ResourceList emmc2Resources;
|
||||
uint64_t emmc2Base = 0;
|
||||
if (!Hal::AcpiDevices::EvaluateCrs(emmc2.NodeIndex, emmc2Resources) ||
|
||||
!emmc2Resources.GetMemory(emmc2Base, length) || emmc2Base < 0x40000) {
|
||||
return;
|
||||
}
|
||||
base = emmc2Base - 0x40000;
|
||||
name = "the Arasan controller (inferred from EMMC2)";
|
||||
}
|
||||
Net::Wifi::ProbeBroadcomSdio(base, name);
|
||||
}
|
||||
|
||||
void ProbeNormal() {
|
||||
Pci::ProbeAll(g_driverTable, g_driverTableCount, Pci::ProbePhase::Normal);
|
||||
ProbePlatformXhci();
|
||||
ProbePlatformSdhci();
|
||||
ProbePlatformGenet();
|
||||
ProbePlatformBroadcomWifi();
|
||||
Bluetooth::ProbePlatformAdapter();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Brcmf.hpp
|
||||
* Broadcom/Cypress FullMAC Wi-Fi over SDIO (CYW43455 / CYW43456 on the
|
||||
* Raspberry Pi 4 / 400): driver-internal interface
|
||||
* Derived in part from Linux's brcmfmac, Copyright (c) 2010-2014 Broadcom
|
||||
* Corporation, ISC license (notice in programs/data/licenses/NOTICES.txt).
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <CppLib/Spinlock.hpp>
|
||||
|
||||
// A FullMAC radio runs the 802.11 MLME itself: the host asks for scans and
|
||||
// joins through ioctls and sees Ethernet frames. Layout, after Linux's
|
||||
// brcmfmac:
|
||||
//
|
||||
// BrcmfSdio.cpp the SD controller as an SDIO host (CMD52/CMD53)
|
||||
// BrcmfChip.cpp backplane access, core enumeration (EROM), clocks,
|
||||
// firmware + NVRAM download, F2 bring-up, firmware console
|
||||
// BrcmfProto.cpp SDPCM framing, CDC ioctls, BDC data, the RX pump,
|
||||
// firmware events
|
||||
// BrcmfConnect.cpp preinit, CLM, escan, join, keys, the facade entry points
|
||||
//
|
||||
// The handshake runs on the host (Wpa.cpp), exactly as for the Intel
|
||||
// adapter: EAPOL frames arrive as Ethernet frames and the keys go back to the
|
||||
// firmware with "wsec_key".
|
||||
//
|
||||
// Threading: everything on the bus holds g_brcmf.BusLock for the length of one
|
||||
// SDIO exchange. The RX pump (BrcmfPoll) has a reentrancy guard and releases
|
||||
// the bus lock before dispatching, so the network stack may transmit from
|
||||
// inside a delivered frame. Dispatch must never issue an ioctl -- its answer
|
||||
// is read by the very pump it would be waiting inside. Work needing one is
|
||||
// queued for BrcmfConnectService().
|
||||
|
||||
namespace Drivers::Net::Wifi {
|
||||
|
||||
// Facade-visible adapter state; values are the WIFI_STATE_* ABI ones.
|
||||
enum class BrcmfState : uint8_t {
|
||||
Absent = 0,
|
||||
Detected,
|
||||
Booting,
|
||||
Running,
|
||||
Error,
|
||||
};
|
||||
|
||||
struct BrcmfCore {
|
||||
uint16_t Id = 0;
|
||||
uint8_t Rev = 0;
|
||||
uint32_t Base = 0;
|
||||
uint32_t Wrap = 0;
|
||||
};
|
||||
|
||||
struct BrcmfDevice {
|
||||
BrcmfState State = BrcmfState::Absent;
|
||||
uint64_t ArasanMmio = 0;
|
||||
|
||||
// Chip
|
||||
uint16_t ChipId = 0;
|
||||
uint8_t ChipRev = 0;
|
||||
const char* FwName = nullptr; // "43455" / "43456"
|
||||
BrcmfCore Cores[16];
|
||||
uint32_t CoreCount = 0;
|
||||
uint32_t RamBase = 0;
|
||||
uint32_t RamSize = 0;
|
||||
uint32_t SdioCoreBase = 0;
|
||||
uint32_t PmuBase = 0;
|
||||
bool SrEnabled = false;
|
||||
uint32_t SharedAddr = 0; // sdpcm_shared, 0 = not read yet
|
||||
uint32_t ConsoleIdx = 0; // console bytes already printed
|
||||
|
||||
// Bus
|
||||
kcp::Mutex BusLock;
|
||||
uint32_t Window = 0xFFFFFFFF; // backplane window, 0xFFFFFFFF = unknown
|
||||
uint32_t HostIntMask = 0;
|
||||
|
||||
// SDPCM
|
||||
uint8_t TxSeq = 0;
|
||||
uint8_t TxMax = 1;
|
||||
bool RxPending = false;
|
||||
|
||||
// Firmware
|
||||
uint8_t Mac[6] = {};
|
||||
char FwVersion[32] = {};
|
||||
uint16_t ChannelCount = 0;
|
||||
bool Band24 = true;
|
||||
bool Band5 = false;
|
||||
|
||||
// Health
|
||||
uint32_t FwErrors = 0;
|
||||
uint32_t CmdFailBucket = 0; // leaky bucket of unanswered ioctls
|
||||
uint64_t RxPackets = 0;
|
||||
uint64_t TxPackets = 0;
|
||||
uint64_t LastPollMs = 0;
|
||||
};
|
||||
|
||||
extern BrcmfDevice g_brcmf;
|
||||
|
||||
// =========================================================================
|
||||
// BrcmfChip.cpp
|
||||
// =========================================================================
|
||||
|
||||
bool BrcmfBpRead32(uint32_t addr, uint32_t& value);
|
||||
bool BrcmfBpWrite32(uint32_t addr, uint32_t value);
|
||||
bool BrcmfBpReadMem(uint32_t addr, uint8_t* buffer, uint32_t len);
|
||||
bool BrcmfBpWriteMem(uint32_t addr, const uint8_t* buffer, uint32_t len);
|
||||
// Point the backplane window at `addr` (function 2 transfers want it on
|
||||
// ChipCommon, as Linux keeps it).
|
||||
bool BrcmfBpSelectWindow(uint32_t addr);
|
||||
|
||||
// Function-1 SDIO-core registers in the 0x1000x space.
|
||||
bool BrcmfF1Read8(uint32_t reg, uint8_t& value);
|
||||
bool BrcmfF1Write8(uint32_t reg, uint8_t value);
|
||||
|
||||
// Power the radio, enumerate the SDIO card, identify the chip, download
|
||||
// firmware + NVRAM, start the ARM and bring up function 2. Leaves
|
||||
// g_brcmf ready for SDPCM traffic.
|
||||
bool BrcmfChipBringUp();
|
||||
|
||||
// Hold the radio in reset (WL_ON low).
|
||||
void BrcmfChipPowerOff();
|
||||
|
||||
// Print what the firmware wrote to its console since the last call, and
|
||||
// a trap/assert summary if it has one. Takes the bus lock.
|
||||
void BrcmfDumpConsole();
|
||||
|
||||
// Load 0:/os/firmware/brcm/brcmfmac<chip>-sdio.<ext>. The caller frees.
|
||||
uint8_t* BrcmfLoadFile(const char* ext, uint32_t& size, bool required);
|
||||
|
||||
// =========================================================================
|
||||
// BrcmfProto.cpp
|
||||
// =========================================================================
|
||||
|
||||
void BrcmfProtoReset();
|
||||
|
||||
// Drain the device: interrupt status, mailbox, every pending frame, and
|
||||
// queued transmits. Reentrancy guarded; returns at once if another core
|
||||
// is already pumping.
|
||||
void BrcmfPoll();
|
||||
|
||||
// Whether a poll is due (card interrupt, queued TX, the periodic sweep).
|
||||
bool BrcmfPollDue(uint64_t now, uint64_t intervalMs);
|
||||
|
||||
// Synchronous firmware command. `buf` carries the request (for a query,
|
||||
// its first bytes, e.g. an iovar name) and receives up to `len` bytes of
|
||||
// answer. Returns false on a transport failure, a timeout, or a negative
|
||||
// firmware status (*fwStatus, when given, receives it).
|
||||
bool BrcmfIoctl(uint32_t cmd, bool set, void* buf, uint32_t len, int32_t* fwStatus = nullptr);
|
||||
|
||||
bool BrcmfSetInt(uint32_t cmd, uint32_t value);
|
||||
bool BrcmfGetInt(uint32_t cmd, uint32_t& value);
|
||||
bool BrcmfIovarSet(const char* name, const void* data, uint32_t len, bool quiet = false);
|
||||
bool BrcmfIovarGet(const char* name, void* out, uint32_t len, bool quiet = false);
|
||||
bool BrcmfIovarSetInt(const char* name, uint32_t value, bool quiet = false);
|
||||
bool BrcmfIovarGetInt(const char* name, uint32_t& value, bool quiet = false);
|
||||
|
||||
// Queue an Ethernet frame for transmission (802.3, 14-byte header).
|
||||
bool BrcmfTxEthernet(const uint8_t* frame, uint32_t len);
|
||||
bool BrcmfTxPending();
|
||||
|
||||
// =========================================================================
|
||||
// BrcmfConnect.cpp -- called from the protocol layer
|
||||
// =========================================================================
|
||||
|
||||
// A firmware event. Runs inside the pump: no ioctls.
|
||||
void BrcmfOnEvent(uint32_t type, uint32_t status, uint32_t reason, uint16_t flags,
|
||||
const uint8_t* addr, const uint8_t* data, uint32_t len);
|
||||
|
||||
// A received Ethernet frame. Runs inside the pump.
|
||||
void BrcmfOnRxEthernet(const uint8_t* frame, uint32_t len);
|
||||
|
||||
// The firmware halted or stopped answering.
|
||||
void BrcmfOnFirmwareFailure(const char* why);
|
||||
|
||||
// =========================================================================
|
||||
// BrcmfConnect.cpp -- the facade's backend (Wifi.cpp)
|
||||
// =========================================================================
|
||||
|
||||
bool BrcmfProbe(uint64_t arasanMmio);
|
||||
bool BrcmfCompleteInit();
|
||||
void BrcmfShutdown(); // for recovery: power the radio off
|
||||
|
||||
BrcmfState BrcmfGetState();
|
||||
bool BrcmfHasWork(uint64_t now);
|
||||
void BrcmfService(); // pump + connection service
|
||||
|
||||
bool BrcmfStartScan();
|
||||
void BrcmfAbortScan();
|
||||
bool BrcmfScanActive();
|
||||
|
||||
bool BrcmfConnectStart(const uint8_t* bssid, uint8_t channel, const char* ssid,
|
||||
const char* password, const uint8_t* rsnIe, uint8_t rsnIeLen);
|
||||
void BrcmfConnectAbort();
|
||||
int BrcmfConnectState(); // IwxConnStateId values
|
||||
bool BrcmfConnectRefusedForSecurity();
|
||||
bool BrcmfJoinFailedOnAuth();
|
||||
const char* BrcmfConnectSsid();
|
||||
const uint8_t* BrcmfConnectBssid();
|
||||
uint8_t BrcmfConnectChannel();
|
||||
bool BrcmfLinkUp();
|
||||
bool BrcmfSendEthernet(const uint8_t* frame, uint32_t len);
|
||||
|
||||
bool BrcmfWpaTxEapol(const uint8_t* body, uint32_t len);
|
||||
bool BrcmfWpaInstallPtk(const uint8_t* tk, uint32_t tkLen, uint8_t cipher);
|
||||
bool BrcmfWpaInstallGtk(const uint8_t* gtk, uint32_t gtkLen, uint8_t keyIdx,
|
||||
uint8_t cipher, const uint8_t* rsc);
|
||||
|
||||
// =========================================================================
|
||||
// Wifi.cpp -- the facade, called by the backend
|
||||
// =========================================================================
|
||||
|
||||
// One BSS from a scan: its information elements, as in a beacon body
|
||||
// after the fixed fields.
|
||||
void WifiRxBssInfo(const uint8_t* bssid, uint16_t beaconInterval, uint16_t capability,
|
||||
const uint8_t* ies, uint32_t iesLen, uint8_t channel, int8_t rssiDbm);
|
||||
void WifiRxEthernet(const uint8_t* frame, uint32_t len);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,849 @@
|
||||
/*
|
||||
* BrcmfChip.cpp
|
||||
* Broadcom FullMAC over SDIO: backplane access, core enumeration, clocks,
|
||||
* firmware and NVRAM download, function 2 bring-up, firmware console
|
||||
* Derived in part from Linux's brcmfmac, Copyright (c) 2010-2014 Broadcom
|
||||
* Corporation, ISC license (notice in programs/data/licenses/NOTICES.txt).
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "Brcmf.hpp"
|
||||
#include "BrcmfSdio.hpp"
|
||||
#include <Arch/Cpu.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Drivers/Platform/RpiFirmware.hpp>
|
||||
#include <Drivers/Platform/RpiGpio.hpp>
|
||||
#include <Fs/Vfs.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Memory/Heap.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <Timekeeping/Timer.hpp>
|
||||
|
||||
// The radio is a small SoC of its own (a Broadcom "AI" interconnect): an
|
||||
// ARM Cortex-R4 with its tightly coupled RAM, the 802.11 MAC/PHY core (D11),
|
||||
// ChipCommon (with the PMU) and an SDIO device core. Function 1 of the SDIO
|
||||
// card is a window onto that backplane: three window registers select the
|
||||
// 32 KiB page, and CMD53 at offset | 0x8000 reaches it.
|
||||
//
|
||||
// Bring-up follows Linux's brcmfmac (sdio.c, chip.c, firmware.c):
|
||||
// 1. route GPIO 34-39 to the Arasan controller, pulse WL_ON (line 1 of the
|
||||
// VideoCore's GPIO expander), enumerate the card;
|
||||
// 2. request the ALP clock, read the chip ID, walk the enumeration ROM for
|
||||
// the cores;
|
||||
// 3. halt the ARM and hold the D11 core in reset, size the RAM from the
|
||||
// CR4's bank registers;
|
||||
// 4. write the firmware at the RAM base and the NVRAM at its top (with the
|
||||
// length token in the last word), write the reset vector to address 0
|
||||
// and release the ARM;
|
||||
// 5. wait for the HT clock the firmware brings up, tell the SDIO core the
|
||||
// protocol version, enable function 2 (frames) and its interrupts.
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Drivers::Net::Wifi {
|
||||
|
||||
namespace RpiFirmware = Drivers::Platform::RpiFirmware;
|
||||
namespace RpiGpio = Drivers::Platform::RpiGpio;
|
||||
|
||||
BrcmfDevice g_brcmf;
|
||||
|
||||
// =========================================================================
|
||||
// Registers
|
||||
// =========================================================================
|
||||
|
||||
// Function 1, SDIO core local space
|
||||
static constexpr uint32_t F1_WATERMARK = 0x10008;
|
||||
static constexpr uint32_t F1_DEVICE_CTL = 0x10009;
|
||||
static constexpr uint32_t F1_SBADDRLOW = 0x1000A;
|
||||
static constexpr uint32_t F1_SBADDRMID = 0x1000B;
|
||||
static constexpr uint32_t F1_SBADDRHIGH = 0x1000C;
|
||||
static constexpr uint32_t F1_CHIPCLKCSR = 0x1000E;
|
||||
static constexpr uint32_t F1_SDIOPULLUP = 0x1000F;
|
||||
static constexpr uint32_t F1_MESBUSYCTRL = 0x1001D;
|
||||
static constexpr uint32_t F1_WAKEUPCTRL = 0x1001E;
|
||||
|
||||
static constexpr uint8_t CLK_FORCE_ALP = 0x01;
|
||||
static constexpr uint8_t CLK_FORCE_HT = 0x02;
|
||||
static constexpr uint8_t CLK_ALP_AVAIL_REQ = 0x08;
|
||||
static constexpr uint8_t CLK_HT_AVAIL_REQ = 0x10;
|
||||
static constexpr uint8_t CLK_FORCE_HW_REQ_OFF= 0x20;
|
||||
static constexpr uint8_t CLK_ALP_AVAIL = 0x40;
|
||||
static constexpr uint8_t CLK_HT_AVAIL = 0x80;
|
||||
|
||||
static constexpr uint8_t DEVCTL_F2WM_ENAB = 0x10;
|
||||
static constexpr uint8_t MESBUSY_ENAB = 0x80;
|
||||
static constexpr uint8_t WAKEUP_HTWAIT = 0x02;
|
||||
|
||||
// CYW43455 values (Linux CY_43455_*): a deeper F2 watermark and
|
||||
// MES busy signalling keep the card's FIFO from overrunning.
|
||||
static constexpr uint8_t F2_WATERMARK_43455 = 0x60;
|
||||
static constexpr uint8_t MES_WATERMARK_43455 = 0x50;
|
||||
|
||||
// Broadcom vendor CCCR
|
||||
static constexpr uint32_t CCCR_BRCM_CARDCAP = 0xF0;
|
||||
static constexpr uint8_t CARDCAP_CMD14 = 0x02 | 0x04; // support | ext
|
||||
static constexpr uint32_t CCCR_BRCM_CARDCTRL = 0xF1;
|
||||
static constexpr uint8_t CARDCTRL_WLANRESET = 0x02;
|
||||
|
||||
// Backplane
|
||||
static constexpr uint32_t SI_ENUM_BASE = 0x18000000; // ChipCommon
|
||||
static constexpr uint32_t CC_CHIPID = 0x000;
|
||||
static constexpr uint32_t CC_CAPS_EXT = 0x0AC;
|
||||
static constexpr uint32_t CAPS_EXT_AOB = 0x40; // PMU in its own "always on" core
|
||||
static constexpr uint32_t CC_EROMPTR = 0x0FC;
|
||||
static constexpr uint32_t CC_PMUCONTROL = 0x600;
|
||||
static constexpr uint32_t CC_CHIPCONTROL_ADDR = 0x650;
|
||||
static constexpr uint32_t CC_CHIPCONTROL_DATA = 0x654;
|
||||
static constexpr uint32_t PMU_CTL_RES_RELOAD = 2u << 13;
|
||||
|
||||
// SDIO device core
|
||||
static constexpr uint32_t SD_INTSTATUS = 0x20;
|
||||
static constexpr uint32_t SD_HOSTINTMASK = 0x24;
|
||||
static constexpr uint32_t SD_TOSBMAILBOXDATA = 0x48;
|
||||
static constexpr uint32_t SDPCM_PROT_VERSION = 4;
|
||||
static constexpr uint32_t HOSTINTMASK = 0x000000F0 | (1u << 29); // HMB SW | CHIPACTIVE
|
||||
|
||||
// Core wrapper (AI)
|
||||
static constexpr uint32_t WRAP_IOCTL = 0x408;
|
||||
static constexpr uint32_t WRAP_RESET_CTL = 0x800;
|
||||
static constexpr uint32_t IOCTL_CLK = 0x0001;
|
||||
static constexpr uint32_t IOCTL_FGC = 0x0002;
|
||||
static constexpr uint32_t RESET_CTL_RESET = 0x0001;
|
||||
|
||||
static constexpr uint32_t CR4_IOCTL_CPUHALT = 0x0020;
|
||||
static constexpr uint32_t D11_IOCTL_PHYCLKEN = 0x0004;
|
||||
static constexpr uint32_t D11_IOCTL_PHYRESET = 0x0008;
|
||||
|
||||
// ARM CR4 core
|
||||
static constexpr uint32_t CR4_CAP = 0x04;
|
||||
static constexpr uint32_t CR4_BANKIDX = 0x40;
|
||||
static constexpr uint32_t CR4_BANKINFO = 0x44;
|
||||
|
||||
// Core IDs
|
||||
static constexpr uint16_t CORE_CHIPCOMMON = 0x800;
|
||||
static constexpr uint16_t CORE_80211 = 0x812;
|
||||
static constexpr uint16_t CORE_PMU = 0x827;
|
||||
static constexpr uint16_t CORE_SDIO_DEV = 0x829;
|
||||
static constexpr uint16_t CORE_ARM_CR4 = 0x83E;
|
||||
static constexpr uint16_t CORE_GCI = 0x840;
|
||||
|
||||
static constexpr uint16_t CHIP_4345 = 0x4345;
|
||||
static constexpr uint32_t RAMBASE_4345 = 0x198000;
|
||||
|
||||
// =========================================================================
|
||||
// Function 1 and the backplane
|
||||
// =========================================================================
|
||||
|
||||
bool BrcmfF1Read8(uint32_t reg, uint8_t& value) { return Sdio::Read8(Sdio::FN1, reg, value); }
|
||||
bool BrcmfF1Write8(uint32_t reg, uint8_t value) { return Sdio::Write8(Sdio::FN1, reg, value); }
|
||||
|
||||
static bool SetWindow(uint32_t addr) {
|
||||
uint32_t window = addr & ~0x7FFFu;
|
||||
if (window == g_brcmf.Window) return true;
|
||||
bool ok = BrcmfF1Write8(F1_SBADDRLOW, (uint8_t)((window >> 8) & 0x80))
|
||||
&& BrcmfF1Write8(F1_SBADDRMID, (uint8_t)(window >> 16))
|
||||
&& BrcmfF1Write8(F1_SBADDRHIGH, (uint8_t)(window >> 24));
|
||||
g_brcmf.Window = ok ? window : 0xFFFFFFFF;
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool BrcmfBpSelectWindow(uint32_t addr) { return SetWindow(addr); }
|
||||
|
||||
// 0x8000 in the function 1 address selects a 4-byte backplane access.
|
||||
static constexpr uint32_t SB_ACCESS_4B = 0x8000;
|
||||
|
||||
bool BrcmfBpRead32(uint32_t addr, uint32_t& value) {
|
||||
uint32_t word = 0;
|
||||
if (!SetWindow(addr)) return false;
|
||||
if (!Sdio::ReadBlock(Sdio::FN1, (addr & 0x7FFF) | SB_ACCESS_4B, true, (uint8_t*)&word, 4))
|
||||
return false;
|
||||
value = word;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BrcmfBpWrite32(uint32_t addr, uint32_t value) {
|
||||
if (!SetWindow(addr)) return false;
|
||||
return Sdio::WriteBlock(Sdio::FN1, (addr & 0x7FFF) | SB_ACCESS_4B, true, (const uint8_t*)&value, 4);
|
||||
}
|
||||
|
||||
static bool BpMem(bool write, uint32_t addr, uint8_t* buffer, uint32_t len) {
|
||||
while (len > 0) {
|
||||
uint32_t offset = addr & 0x7FFF;
|
||||
uint32_t run = 0x8000 - offset;
|
||||
if (run > len) run = len;
|
||||
if (!SetWindow(addr)) return false;
|
||||
bool ok = write ? Sdio::WriteBlock(Sdio::FN1, offset, true, buffer, run)
|
||||
: Sdio::ReadBlock(Sdio::FN1, offset, true, buffer, run);
|
||||
if (!ok) return false;
|
||||
addr += run;
|
||||
buffer += run;
|
||||
len -= run;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BrcmfBpReadMem(uint32_t addr, uint8_t* buffer, uint32_t len) {
|
||||
return BpMem(false, addr, buffer, len);
|
||||
}
|
||||
|
||||
bool BrcmfBpWriteMem(uint32_t addr, const uint8_t* buffer, uint32_t len) {
|
||||
return BpMem(true, addr, (uint8_t*)buffer, len);
|
||||
}
|
||||
|
||||
static uint32_t Rd(uint32_t addr) {
|
||||
uint32_t v = 0;
|
||||
BrcmfBpRead32(addr, v);
|
||||
return v;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Cores
|
||||
// =========================================================================
|
||||
|
||||
static const BrcmfCore* FindCore(uint16_t id) {
|
||||
for (uint32_t i = 0; i < g_brcmf.CoreCount; i++)
|
||||
if (g_brcmf.Cores[i].Id == id) return &g_brcmf.Cores[i];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Enumeration ROM (Linux brcmf_chip_dmp_erom_scan).
|
||||
static constexpr uint32_t DESC_TYPE_MSK = 0x0000000F;
|
||||
static constexpr uint32_t DESC_VALID = 0x00000001;
|
||||
static constexpr uint32_t DESC_COMPONENT = 0x00000001;
|
||||
static constexpr uint32_t DESC_MASTER_PORT = 0x00000003;
|
||||
static constexpr uint32_t DESC_ADDRESS = 0x00000005;
|
||||
static constexpr uint32_t DESC_ADDRSIZE_GT32 = 0x00000008;
|
||||
static constexpr uint32_t DESC_EOT = 0x0000000F;
|
||||
static constexpr uint32_t COMP_PARTNUM_MASK = 0x000FFF00, COMP_PARTNUM_SHIFT = 8;
|
||||
static constexpr uint32_t COMP_REV_MASK = 0xFF000000, COMP_REV_SHIFT = 24;
|
||||
static constexpr uint32_t COMP_NSW_MASK = 0x00F80000, COMP_NSW_SHIFT = 19;
|
||||
static constexpr uint32_t COMP_NMW_MASK = 0x0007C000, COMP_NMW_SHIFT = 14;
|
||||
static constexpr uint32_t SLAVE_ADDR_BASE = 0xFFFFF000;
|
||||
static constexpr uint32_t SLAVE_TYPE_MASK = 0x000000C0, SLAVE_TYPE_SHIFT = 6;
|
||||
static constexpr uint32_t SLAVE_TYPE_SLAVE = 0;
|
||||
static constexpr uint32_t SLAVE_TYPE_SWRAP = 2;
|
||||
static constexpr uint32_t SLAVE_TYPE_MWRAP = 3;
|
||||
static constexpr uint32_t SLAVE_SIZE_MASK = 0x00000030, SLAVE_SIZE_SHIFT = 4;
|
||||
static constexpr uint32_t SLAVE_SIZE_4K = 0;
|
||||
static constexpr uint32_t SLAVE_SIZE_8K = 1;
|
||||
static constexpr uint32_t SLAVE_SIZE_DESC = 3;
|
||||
|
||||
static uint32_t EromNext(uint32_t& erom, uint32_t* type) {
|
||||
uint32_t v = Rd(erom);
|
||||
erom += 4;
|
||||
uint32_t t = v & DESC_TYPE_MSK;
|
||||
if ((t & ~DESC_ADDRSIZE_GT32) == DESC_ADDRESS) t = DESC_ADDRESS;
|
||||
if (type) *type = t;
|
||||
return v;
|
||||
}
|
||||
|
||||
static bool EromRegAddr(uint32_t& erom, uint32_t& regBase, uint32_t& wrapBase) {
|
||||
regBase = 0;
|
||||
wrapBase = 0;
|
||||
uint32_t desc = 0, val = 0;
|
||||
uint32_t wrapType;
|
||||
|
||||
// One descriptor decides it, as in Linux: a master port means the
|
||||
// core's wrapper is a master wrapper (any further master ports are
|
||||
// skipped by the address search below); an address straight away
|
||||
// means a slave wrapper. Cores with master ports -- the CR4, D11,
|
||||
// the SDIO core, and ChipCommon on these chips -- must not be
|
||||
// looked up as slave-wrapped: their wrapper is never found, and the
|
||||
// halt/release writes go nowhere.
|
||||
val = EromNext(erom, &desc);
|
||||
if (desc == DESC_MASTER_PORT) {
|
||||
wrapType = SLAVE_TYPE_MWRAP;
|
||||
} else if (desc == DESC_ADDRESS) {
|
||||
erom -= 4;
|
||||
wrapType = SLAVE_TYPE_SWRAP;
|
||||
} else {
|
||||
erom -= 4;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int guard = 0; guard < 64; guard++) {
|
||||
do {
|
||||
val = EromNext(erom, &desc);
|
||||
if (desc == DESC_EOT) { erom -= 4; return false; }
|
||||
} while (desc != DESC_ADDRESS && desc != DESC_COMPONENT);
|
||||
|
||||
if (desc == DESC_COMPONENT) { erom -= 4; return true; }
|
||||
|
||||
if (val & DESC_ADDRSIZE_GT32) EromNext(erom, nullptr);
|
||||
uint32_t sizeType = (val & SLAVE_SIZE_MASK) >> SLAVE_SIZE_SHIFT;
|
||||
if (sizeType == SLAVE_SIZE_DESC) {
|
||||
uint32_t size = EromNext(erom, nullptr);
|
||||
if (size & DESC_ADDRSIZE_GT32) EromNext(erom, nullptr);
|
||||
}
|
||||
if (sizeType != SLAVE_SIZE_4K && sizeType != SLAVE_SIZE_8K) continue;
|
||||
|
||||
uint32_t slaveType = (val & SLAVE_TYPE_MASK) >> SLAVE_TYPE_SHIFT;
|
||||
if (regBase == 0 && slaveType == SLAVE_TYPE_SLAVE) regBase = val & SLAVE_ADDR_BASE;
|
||||
if (wrapBase == 0 && slaveType == wrapType) wrapBase = val & SLAVE_ADDR_BASE;
|
||||
if (regBase && wrapBase) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ScanCores() {
|
||||
g_brcmf.CoreCount = 0;
|
||||
uint32_t erom = Rd(SI_ENUM_BASE + CC_EROMPTR);
|
||||
if (erom == 0 || erom == 0xFFFFFFFF) return false;
|
||||
|
||||
uint32_t desc = 0;
|
||||
for (int guard = 0; guard < 512 && desc != DESC_EOT; guard++) {
|
||||
uint32_t val = EromNext(erom, &desc);
|
||||
if (!(val & DESC_VALID) || desc != DESC_COMPONENT) continue;
|
||||
uint16_t id = (uint16_t)((val & COMP_PARTNUM_MASK) >> COMP_PARTNUM_SHIFT);
|
||||
|
||||
val = EromNext(erom, &desc);
|
||||
if (desc != DESC_COMPONENT) return false;
|
||||
uint32_t nmw = (val & COMP_NMW_MASK) >> COMP_NMW_SHIFT;
|
||||
uint32_t nsw = (val & COMP_NSW_MASK) >> COMP_NSW_SHIFT;
|
||||
uint8_t rev = (uint8_t)((val & COMP_REV_MASK) >> COMP_REV_SHIFT);
|
||||
if (nmw + nsw == 0 && id != CORE_PMU && id != CORE_GCI) continue;
|
||||
|
||||
uint32_t base = 0, wrap = 0;
|
||||
if (!EromRegAddr(erom, base, wrap)) continue;
|
||||
if (g_brcmf.CoreCount < sizeof(g_brcmf.Cores) / sizeof(g_brcmf.Cores[0])) {
|
||||
g_brcmf.Cores[g_brcmf.CoreCount++] = { id, rev, base, wrap };
|
||||
}
|
||||
}
|
||||
const BrcmfCore* cr4 = FindCore(CORE_ARM_CR4);
|
||||
return FindCore(CORE_CHIPCOMMON) && FindCore(CORE_SDIO_DEV) && cr4 && cr4->Wrap != 0;
|
||||
}
|
||||
|
||||
static void CoreDisable(const BrcmfCore* core, uint32_t prereset, uint32_t reset) {
|
||||
if (!(Rd(core->Wrap + WRAP_RESET_CTL) & RESET_CTL_RESET)) {
|
||||
BrcmfBpWrite32(core->Wrap + WRAP_IOCTL, prereset | IOCTL_FGC | IOCTL_CLK);
|
||||
Rd(core->Wrap + WRAP_IOCTL);
|
||||
BrcmfBpWrite32(core->Wrap + WRAP_RESET_CTL, RESET_CTL_RESET);
|
||||
Timekeeping::BusyWaitUs(20);
|
||||
for (int i = 0; i < 30 && Rd(core->Wrap + WRAP_RESET_CTL) != RESET_CTL_RESET; i++)
|
||||
Timekeeping::BusyWaitUs(10);
|
||||
}
|
||||
BrcmfBpWrite32(core->Wrap + WRAP_IOCTL, reset | IOCTL_FGC | IOCTL_CLK);
|
||||
Rd(core->Wrap + WRAP_IOCTL);
|
||||
}
|
||||
|
||||
static void CoreReset(const BrcmfCore* core, uint32_t prereset, uint32_t reset, uint32_t postreset) {
|
||||
CoreDisable(core, prereset, reset);
|
||||
for (int i = 0; i < 50 && (Rd(core->Wrap + WRAP_RESET_CTL) & RESET_CTL_RESET); i++) {
|
||||
BrcmfBpWrite32(core->Wrap + WRAP_RESET_CTL, 0);
|
||||
Timekeeping::BusyWaitUs(50);
|
||||
}
|
||||
BrcmfBpWrite32(core->Wrap + WRAP_IOCTL, postreset | IOCTL_CLK);
|
||||
Rd(core->Wrap + WRAP_IOCTL);
|
||||
}
|
||||
|
||||
// The CR4's TCM: ATCM and BTCM bank counts in CAP, each bank's size in
|
||||
// 8 KiB units in BANKINFO.
|
||||
static uint32_t Cr4RamSize(const BrcmfCore* cr4) {
|
||||
uint32_t cap = Rd(cr4->Base + CR4_CAP);
|
||||
uint32_t banks = (cap & 0xF) + ((cap >> 4) & 0xF);
|
||||
uint32_t size = 0;
|
||||
for (uint32_t i = 0; i < banks; i++) {
|
||||
BrcmfBpWrite32(cr4->Base + CR4_BANKIDX, i);
|
||||
uint32_t info = Rd(cr4->Base + CR4_BANKINFO);
|
||||
size += ((info & 0x3F) + 1) * 8192;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Firmware files
|
||||
// =========================================================================
|
||||
|
||||
uint8_t* BrcmfLoadFile(const char* ext, uint32_t& size, bool required) {
|
||||
char path[80];
|
||||
int n = 0;
|
||||
auto add = [&](const char* s) { while (*s && n < 78) path[n++] = *s++; };
|
||||
add("0:/os/firmware/brcm/brcmfmac");
|
||||
add(g_brcmf.FwName);
|
||||
add("-sdio.");
|
||||
add(ext);
|
||||
path[n] = '\0';
|
||||
|
||||
size = 0;
|
||||
Fs::Vfs::BackendFile file;
|
||||
if (Fs::Vfs::OpenBackendFile(path, file) < 0) {
|
||||
if (required) KernelLogStream(ERROR, "WiFi") << "Firmware file not found: " << path;
|
||||
return nullptr;
|
||||
}
|
||||
uint64_t fileSize = Fs::Vfs::GetBackendFileSize(file);
|
||||
if (fileSize == 0 || fileSize > 4 * 1024 * 1024) {
|
||||
Fs::Vfs::CloseBackendFile(file);
|
||||
KernelLogStream(ERROR, "WiFi") << "Unusable firmware file " << path;
|
||||
return nullptr;
|
||||
}
|
||||
// Rounded up and zero padded: everything goes over the bus in dwords.
|
||||
uint32_t padded = ((uint32_t)fileSize + 3) & ~3u;
|
||||
uint8_t* data = (uint8_t*)Memory::g_heap->Request(padded);
|
||||
if (!data) {
|
||||
Fs::Vfs::CloseBackendFile(file);
|
||||
return nullptr;
|
||||
}
|
||||
memset(data, 0, padded);
|
||||
int got = Fs::Vfs::ReadBackendFile(file, data, 0, fileSize);
|
||||
Fs::Vfs::CloseBackendFile(file);
|
||||
if (got != (int)fileSize) {
|
||||
Memory::g_heap->Free(data);
|
||||
KernelLogStream(ERROR, "WiFi") << "Short read of " << path;
|
||||
return nullptr;
|
||||
}
|
||||
size = (uint32_t)fileSize;
|
||||
KernelLogStream(INFO, "WiFi") << "Loaded " << path << " (" << (uint64_t)size << " bytes)";
|
||||
return data;
|
||||
}
|
||||
|
||||
// The NVRAM text ("key=value" lines, '#' comments) becomes the binary
|
||||
// form the firmware reads: NUL-terminated pairs, zero padded to a dword
|
||||
// (at least one extra NUL), then a token word holding the padded length
|
||||
// in dwords and its complement (Linux brcmf_fw_nvram_strip).
|
||||
static uint8_t* BuildNvram(const uint8_t* text, uint32_t len, uint32_t& outLen) {
|
||||
uint8_t* out = (uint8_t*)Memory::g_heap->Request(len + 8);
|
||||
if (!out) return nullptr;
|
||||
uint32_t n = 0;
|
||||
uint32_t i = 0;
|
||||
auto printable = [](uint8_t c) { return c > 0x20 && c < 0x7F && c != '#'; };
|
||||
while (i < len) {
|
||||
while (i < len && (text[i] == ' ' || text[i] == '\t' || text[i] == '\r' || text[i] == '\n')) i++;
|
||||
if (i >= len) break;
|
||||
if (text[i] == '#') {
|
||||
while (i < len && text[i] != '\n') i++;
|
||||
continue;
|
||||
}
|
||||
uint32_t start = i;
|
||||
bool sawEquals = false;
|
||||
while (i < len && printable(text[i])) {
|
||||
if (text[i] == '=') sawEquals = true;
|
||||
i++;
|
||||
}
|
||||
if (sawEquals && i > start) {
|
||||
memcpy(out + n, text + start, i - start);
|
||||
n += i - start;
|
||||
out[n++] = '\0';
|
||||
}
|
||||
while (i < len && text[i] != '\n') i++;
|
||||
}
|
||||
uint32_t padded = (n + 1 + 3) & ~3u;
|
||||
while (n < padded) out[n++] = 0;
|
||||
uint32_t words = padded / 4;
|
||||
uint32_t token = (~words << 16) | (words & 0xFFFF);
|
||||
memcpy(out + n, &token, 4);
|
||||
outLen = n + 4;
|
||||
return out;
|
||||
}
|
||||
|
||||
static bool DownloadAndVerify(uint32_t addr, const uint8_t* data, uint32_t len) {
|
||||
if (!BrcmfBpWriteMem(addr, data, len)) return false;
|
||||
|
||||
// Read the image back: a marginal SDIO bus shows up here as a clean
|
||||
// error instead of as a firmware that never starts.
|
||||
uint8_t* check = (uint8_t*)Memory::g_heap->Request(4096);
|
||||
if (!check) return true;
|
||||
bool ok = true;
|
||||
for (uint32_t off = 0; off < len && ok; off += 4096) {
|
||||
uint32_t run = len - off < 4096 ? len - off : 4096;
|
||||
if (!BrcmfBpReadMem(addr + off, check, run) || memcmp(check, data + off, run) != 0) {
|
||||
KernelLogStream(ERROR, "WiFi") << "Firmware verify failed at offset 0x"
|
||||
<< base::hex << (uint64_t)off << base::dec;
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
Memory::g_heap->Free(check);
|
||||
return ok;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Bring-up
|
||||
// =========================================================================
|
||||
|
||||
static bool WaitClock(uint8_t bit, uint64_t timeoutMs) {
|
||||
uint64_t start = Timekeeping::GetMilliseconds();
|
||||
for (;;) {
|
||||
uint8_t v = 0;
|
||||
if (BrcmfF1Read8(F1_CHIPCLKCSR, v) && (v & bit)) return true;
|
||||
if (Timekeeping::GetMilliseconds() - start > timeoutMs) return false;
|
||||
Timekeeping::BusyWaitUs(200);
|
||||
}
|
||||
}
|
||||
|
||||
void BrcmfChipPowerOff() {
|
||||
RpiFirmware::SetGpioOutput(RpiFirmware::EXPGPIO_WL_ON, false);
|
||||
}
|
||||
|
||||
static bool PowerOnAndEnumerate() {
|
||||
// GPIO 34-39: CLK, CMD, DAT0-3 on ALT3 (the Arasan), pulled up but
|
||||
// for the clock, as Linux's sdio_pins.
|
||||
for (uint32_t pin = 34; pin <= 39; pin++) {
|
||||
RpiGpio::SetFunction(pin, RpiGpio::Function::Alt3);
|
||||
RpiGpio::SetPull(pin, pin == 34 ? RpiGpio::Pull::None : RpiGpio::Pull::Up);
|
||||
}
|
||||
|
||||
if (!RpiFirmware::SetGpioOutput(RpiFirmware::EXPGPIO_WL_ON, false)) {
|
||||
KernelLogStream(ERROR, "WiFi") << "Cannot switch the radio's power (WL_ON)";
|
||||
return false;
|
||||
}
|
||||
Timekeeping::BusyWaitMs(20);
|
||||
RpiFirmware::SetGpioOutput(RpiFirmware::EXPGPIO_WL_ON, true);
|
||||
Timekeeping::BusyWaitMs(250);
|
||||
|
||||
// The legacy EMMC clock feeds the Arasan; nothing on the boot path
|
||||
// uses that controller, so make sure the clock is not gated.
|
||||
if (!RpiFirmware::SetClockState(RpiFirmware::CLOCK_EMMC, true))
|
||||
KernelLogStream(WARNING, "WiFi") << "Could not switch the EMMC clock on";
|
||||
uint32_t clockHz = 0;
|
||||
if (!RpiFirmware::GetClockRate(RpiFirmware::CLOCK_EMMC, clockHz)) clockHz = 0;
|
||||
if (!Sdio::HostInit(g_brcmf.ArasanMmio, clockHz)) return false;
|
||||
return Sdio::CardInit();
|
||||
}
|
||||
|
||||
static bool AttachChip() {
|
||||
g_brcmf.Window = 0xFFFFFFFF;
|
||||
|
||||
// ALP first: the backplane runs from it until the firmware has set
|
||||
// up the PLL.
|
||||
uint8_t req = CLK_FORCE_HW_REQ_OFF | CLK_ALP_AVAIL_REQ;
|
||||
uint8_t v = 0;
|
||||
if (!BrcmfF1Write8(F1_CHIPCLKCSR, req) || !BrcmfF1Read8(F1_CHIPCLKCSR, v) ||
|
||||
(v & ~(CLK_ALP_AVAIL | CLK_HT_AVAIL)) != req) {
|
||||
KernelLogStream(ERROR, "WiFi") << "ChipClkCSR: wrote 0x" << base::hex << (uint64_t)req
|
||||
<< ", read 0x" << (uint64_t)v << base::dec;
|
||||
return false;
|
||||
}
|
||||
if (!WaitClock(CLK_ALP_AVAIL, 100)) {
|
||||
KernelLogStream(ERROR, "WiFi") << "ALP clock never became available";
|
||||
return false;
|
||||
}
|
||||
BrcmfF1Write8(F1_CHIPCLKCSR, CLK_FORCE_HW_REQ_OFF | CLK_FORCE_ALP);
|
||||
Timekeeping::BusyWaitUs(65);
|
||||
BrcmfF1Write8(F1_SDIOPULLUP, 0);
|
||||
|
||||
uint32_t chipId = 0;
|
||||
if (!BrcmfBpRead32(SI_ENUM_BASE + CC_CHIPID, chipId)) {
|
||||
KernelLogStream(ERROR, "WiFi") << "Backplane read failed";
|
||||
return false;
|
||||
}
|
||||
g_brcmf.ChipId = (uint16_t)(chipId & 0xFFFF);
|
||||
g_brcmf.ChipRev = (uint8_t)((chipId >> 16) & 0xF);
|
||||
uint32_t socType = chipId >> 28;
|
||||
|
||||
KernelLogStream(INFO, "WiFi") << "Broadcom chip " << base::hex << (uint64_t)g_brcmf.ChipId
|
||||
<< base::dec << " rev " << (uint64_t)g_brcmf.ChipRev;
|
||||
|
||||
if (g_brcmf.ChipId != CHIP_4345 || socType != 1) {
|
||||
KernelLogStream(ERROR, "WiFi") << "Unsupported chip (only the BCM4345 family: "
|
||||
"CYW43455 / CYW43456)";
|
||||
return false;
|
||||
}
|
||||
// Linux's table: revision 9 is the CYW43456 (Pi 400), the others
|
||||
// the CYW43455 (Pi 4B).
|
||||
g_brcmf.FwName = g_brcmf.ChipRev == 9 ? "43456" : "43455";
|
||||
|
||||
if (!ScanCores()) {
|
||||
KernelLogStream(ERROR, "WiFi") << "Enumeration ROM scan found no usable cores";
|
||||
return false;
|
||||
}
|
||||
// Linux brcmf_chip_get_pmu: the PMU registers live in a core of their
|
||||
// own only when ChipCommon (rev 35+) says the always-on block exists;
|
||||
// otherwise they are ChipCommon's, at the same offsets.
|
||||
const BrcmfCore* cc = FindCore(CORE_CHIPCOMMON);
|
||||
const BrcmfCore* pmu = FindCore(CORE_PMU);
|
||||
uint32_t capsExt = cc->Rev >= 35 ? Rd(cc->Base + CC_CAPS_EXT) : 0;
|
||||
g_brcmf.PmuBase = (pmu && (capsExt & CAPS_EXT_AOB)) ? pmu->Base : cc->Base;
|
||||
g_brcmf.SdioCoreBase = FindCore(CORE_SDIO_DEV)->Base;
|
||||
|
||||
for (uint32_t i = 0; i < g_brcmf.CoreCount; i++) {
|
||||
const BrcmfCore& c = g_brcmf.Cores[i];
|
||||
KernelLogStream(INFO, "WiFi") << " core 0x" << base::hex << (uint64_t)c.Id << " rev "
|
||||
<< base::dec << (uint64_t)c.Rev << " at 0x" << base::hex << (uint64_t)c.Base
|
||||
<< ", wrapper 0x" << (uint64_t)c.Wrap << base::dec;
|
||||
}
|
||||
KernelLogStream(INFO, "WiFi") << "ChipCommon rev " << (uint64_t)cc->Rev << ", caps_ext 0x"
|
||||
<< base::hex << (uint64_t)capsExt << ", PMU registers at 0x" << (uint64_t)g_brcmf.PmuBase
|
||||
<< base::dec;
|
||||
|
||||
// Passive: ARM halted, D11 held in reset with its PHY clock on.
|
||||
const BrcmfCore* cr4 = FindCore(CORE_ARM_CR4);
|
||||
CoreReset(cr4, CR4_IOCTL_CPUHALT, CR4_IOCTL_CPUHALT, CR4_IOCTL_CPUHALT);
|
||||
if (const BrcmfCore* d11 = FindCore(CORE_80211)) {
|
||||
CoreReset(d11, D11_IOCTL_PHYRESET | D11_IOCTL_PHYCLKEN, D11_IOCTL_PHYCLKEN, D11_IOCTL_PHYCLKEN);
|
||||
}
|
||||
uint32_t haltIoctl = Rd(cr4->Wrap + WRAP_IOCTL);
|
||||
uint32_t haltReset = Rd(cr4->Wrap + WRAP_RESET_CTL);
|
||||
if (haltIoctl != (CR4_IOCTL_CPUHALT | IOCTL_CLK) || (haltReset & RESET_CTL_RESET)) {
|
||||
KernelLogStream(ERROR, "WiFi") << "The CR4 did not take the halt (wrapper 0x" << base::hex
|
||||
<< (uint64_t)cr4->Wrap << ": ioctl 0x" << (uint64_t)haltIoctl << " reset 0x"
|
||||
<< (uint64_t)haltReset << base::dec << ", expected 0x21 / 0x0)";
|
||||
return false;
|
||||
}
|
||||
|
||||
g_brcmf.RamBase = RAMBASE_4345;
|
||||
g_brcmf.RamSize = Cr4RamSize(cr4);
|
||||
if (g_brcmf.RamSize == 0 || g_brcmf.RamSize > 4 * 1024 * 1024) {
|
||||
KernelLogStream(ERROR, "WiFi") << "Implausible RAM size " << (uint64_t)g_brcmf.RamSize;
|
||||
return false;
|
||||
}
|
||||
|
||||
// An SDIO card reset also resets the WLAN backplane, and a backplane
|
||||
// reset reloads the PMU state.
|
||||
uint8_t cardCtrl = 0;
|
||||
if (Sdio::Read8(Sdio::FN0, CCCR_BRCM_CARDCTRL, cardCtrl))
|
||||
Sdio::Write8(Sdio::FN0, CCCR_BRCM_CARDCTRL, cardCtrl | CARDCTRL_WLANRESET);
|
||||
BrcmfBpWrite32(g_brcmf.PmuBase + CC_PMUCONTROL,
|
||||
Rd(g_brcmf.PmuBase + CC_PMUCONTROL) | PMU_CTL_RES_RELOAD);
|
||||
|
||||
KernelLogStream(INFO, "WiFi") << "CYW" << g_brcmf.FwName << ": " << (uint64_t)g_brcmf.CoreCount
|
||||
<< " cores, " << (uint64_t)(g_brcmf.RamSize / 1024) << " KiB RAM at 0x" << base::hex
|
||||
<< (uint64_t)g_brcmf.RamBase << base::dec;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DownloadFirmware() {
|
||||
uint32_t fwLen = 0, nvLen = 0;
|
||||
uint8_t* fw = BrcmfLoadFile("bin", fwLen, true);
|
||||
if (!fw) return false;
|
||||
uint8_t* nvText = BrcmfLoadFile("txt", nvLen, true);
|
||||
if (!nvText) {
|
||||
Memory::g_heap->Free(fw);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t nvramLen = 0;
|
||||
uint8_t* nvram = BuildNvram(nvText, nvLen, nvramLen);
|
||||
Memory::g_heap->Free(nvText);
|
||||
uint32_t fwPadded = (fwLen + 3) & ~3u;
|
||||
|
||||
bool ok = nvram && fwPadded + nvramLen < g_brcmf.RamSize;
|
||||
if (!ok) KernelLogStream(ERROR, "WiFi") << "Firmware and NVRAM do not fit in the radio's RAM";
|
||||
|
||||
uint32_t rstvec = 0;
|
||||
memcpy(&rstvec, fw, 4);
|
||||
|
||||
// Download under a plain ALP request, as Linux does (alp_only):
|
||||
// the probe's FORCE_HW_REQ_OFF | FORCE_ALP must be gone before the
|
||||
// ARM starts, or the firmware's own clock requests -- the HT clock
|
||||
// its PLL setup asks for above all -- are blocked.
|
||||
if (ok) {
|
||||
BrcmfF1Write8(F1_CHIPCLKCSR, CLK_ALP_AVAIL_REQ);
|
||||
if (!WaitClock(CLK_ALP_AVAIL, 100))
|
||||
KernelLogStream(WARNING, "WiFi") << "ALP clock lost on its plain request";
|
||||
}
|
||||
|
||||
uint64_t start = Timekeeping::GetMilliseconds();
|
||||
if (ok && !DownloadAndVerify(g_brcmf.RamBase, fw, fwPadded)) {
|
||||
KernelLogStream(ERROR, "WiFi") << "Firmware download failed";
|
||||
ok = false;
|
||||
}
|
||||
if (ok && !DownloadAndVerify(g_brcmf.RamBase + g_brcmf.RamSize - nvramLen, nvram, nvramLen)) {
|
||||
KernelLogStream(ERROR, "WiFi") << "NVRAM download failed";
|
||||
ok = false;
|
||||
}
|
||||
if (ok) {
|
||||
KernelLogStream(INFO, "WiFi") << "Firmware and NVRAM written in "
|
||||
<< (Timekeeping::GetMilliseconds() - start) << " ms";
|
||||
}
|
||||
|
||||
Memory::g_heap->Free(fw);
|
||||
if (nvram) Memory::g_heap->Free(nvram);
|
||||
if (!ok) return false;
|
||||
|
||||
// Start the ARM: clear the SDIO core's interrupts, put the reset
|
||||
// vector at address 0 and take the CPU out of halt.
|
||||
BrcmfBpWrite32(g_brcmf.SdioCoreBase + SD_INTSTATUS, 0xFFFFFFFF);
|
||||
uint32_t vecBack = 0;
|
||||
bool vecOk = BrcmfBpWriteMem(0, (const uint8_t*)&rstvec, 4)
|
||||
&& BrcmfBpReadMem(0, (uint8_t*)&vecBack, 4) && vecBack == rstvec;
|
||||
const BrcmfCore* cr4 = FindCore(CORE_ARM_CR4);
|
||||
CoreReset(cr4, CR4_IOCTL_CPUHALT, 0, 0);
|
||||
if (!vecOk)
|
||||
KernelLogStream(ERROR, "WiFi") << "Reset vector did not stick: address 0 reads 0x"
|
||||
<< base::hex << (uint64_t)vecBack << base::dec;
|
||||
KernelLogStream(INFO, "WiFi") << "Reset vector 0x" << base::hex << (uint64_t)rstvec
|
||||
<< "; CR4 released: ioctl 0x" << (uint64_t)Rd(cr4->Wrap + WRAP_IOCTL) << " reset 0x"
|
||||
<< (uint64_t)Rd(cr4->Wrap + WRAP_RESET_CTL) << base::dec << " (expect 0x1 / 0x0)";
|
||||
return true;
|
||||
}
|
||||
|
||||
// Save/restore: the firmware can power its domains down between
|
||||
// frames; the host then has to allow for the HT clock coming back.
|
||||
static bool SrCapable() {
|
||||
BrcmfBpWrite32(g_brcmf.PmuBase + CC_CHIPCONTROL_ADDR, 3);
|
||||
return (Rd(g_brcmf.PmuBase + CC_CHIPCONTROL_DATA) & (1u << 2)) != 0;
|
||||
}
|
||||
|
||||
static bool StartFrames() {
|
||||
// Clear the ALP force, then ask for HT: the running firmware brings
|
||||
// up the PLL.
|
||||
BrcmfF1Write8(F1_CHIPCLKCSR, 0);
|
||||
BrcmfF1Write8(F1_CHIPCLKCSR, CLK_HT_AVAIL_REQ);
|
||||
bool ht = WaitClock(CLK_HT_AVAIL, 20);
|
||||
if (!ht) {
|
||||
// Not yet: a snapshot 20 ms in tells a CPU that never ran from
|
||||
// one that ran and was reset.
|
||||
const BrcmfCore* cr4 = FindCore(CORE_ARM_CR4);
|
||||
uint8_t csr = 0;
|
||||
BrcmfF1Read8(F1_CHIPCLKCSR, csr);
|
||||
KernelLogStream(INFO, "WiFi") << "20 ms after release: ChipClkCSR 0x" << base::hex
|
||||
<< (uint64_t)csr << ", CR4 ioctl 0x" << (uint64_t)Rd(cr4->Wrap + WRAP_IOCTL) << " reset 0x"
|
||||
<< (uint64_t)Rd(cr4->Wrap + WRAP_RESET_CTL) << ", RAM top 0x"
|
||||
<< (uint64_t)Rd(g_brcmf.RamBase + g_brcmf.RamSize - 4) << base::dec;
|
||||
ht = WaitClock(CLK_HT_AVAIL, 2000);
|
||||
}
|
||||
if (!ht) {
|
||||
// What the hardware was doing, for the next attempt: the clock
|
||||
// register (or whether function 1 still answers at all), the
|
||||
// CPU's wrapper state, and whether the firmware ever replaced
|
||||
// the NVRAM token in the last word of RAM with its shared area.
|
||||
uint8_t csr = 0;
|
||||
bool csrOk = BrcmfF1Read8(F1_CHIPCLKCSR, csr);
|
||||
const BrcmfCore* cr4 = FindCore(CORE_ARM_CR4);
|
||||
uint32_t ioctl = Rd(cr4->Wrap + WRAP_IOCTL);
|
||||
uint32_t reset = Rd(cr4->Wrap + WRAP_RESET_CTL);
|
||||
uint32_t top = Rd(g_brcmf.RamBase + g_brcmf.RamSize - 4);
|
||||
KernelLogStream(ERROR, "WiFi") << "HT clock never became available (firmware did not start)";
|
||||
KernelLogStream(ERROR, "WiFi") << "ChipClkCSR " << (csrOk ? "0x" : "unreadable")
|
||||
<< base::hex << (uint64_t)(csrOk ? csr : 0) << ", CR4 ioctl 0x" << (uint64_t)ioctl
|
||||
<< " reset 0x" << (uint64_t)reset << ", RAM top word 0x" << (uint64_t)top
|
||||
<< base::dec << (((~top >> 16) & 0xFFFF) == (top & 0xFFFF)
|
||||
? " (still the NVRAM token)" : " (written by the firmware)");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t saveClk = 0;
|
||||
BrcmfF1Read8(F1_CHIPCLKCSR, saveClk);
|
||||
BrcmfF1Write8(F1_CHIPCLKCSR, saveClk | CLK_FORCE_HT);
|
||||
|
||||
BrcmfBpWrite32(g_brcmf.SdioCoreBase + SD_TOSBMAILBOXDATA, SDPCM_PROT_VERSION << 16);
|
||||
if (!Sdio::EnableFunction(Sdio::FN2, true, 3000)) {
|
||||
KernelLogStream(ERROR, "WiFi") << "Function 2 did not become ready";
|
||||
Sdio::EnableFunction(Sdio::FN2, false, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
g_brcmf.HostIntMask = HOSTINTMASK;
|
||||
BrcmfBpWrite32(g_brcmf.SdioCoreBase + SD_HOSTINTMASK, HOSTINTMASK);
|
||||
|
||||
BrcmfF1Write8(F1_WATERMARK, F2_WATERMARK_43455);
|
||||
uint8_t devCtl = 0;
|
||||
if (BrcmfF1Read8(F1_DEVICE_CTL, devCtl))
|
||||
BrcmfF1Write8(F1_DEVICE_CTL, devCtl | DEVCTL_F2WM_ENAB);
|
||||
BrcmfF1Write8(F1_MESBUSYCTRL, MES_WATERMARK_43455 | MESBUSY_ENAB);
|
||||
|
||||
g_brcmf.SrEnabled = SrCapable();
|
||||
if (g_brcmf.SrEnabled) {
|
||||
uint8_t wake = 0;
|
||||
if (BrcmfF1Read8(F1_WAKEUPCTRL, wake)) BrcmfF1Write8(F1_WAKEUPCTRL, wake | WAKEUP_HTWAIT);
|
||||
Sdio::Write8(Sdio::FN0, CCCR_BRCM_CARDCAP, CARDCAP_CMD14);
|
||||
BrcmfF1Write8(F1_CHIPCLKCSR, CLK_FORCE_HT);
|
||||
} else {
|
||||
BrcmfF1Write8(F1_CHIPCLKCSR, saveClk);
|
||||
}
|
||||
|
||||
// Master, function 1 and function 2.
|
||||
Sdio::EnableCardInterrupts(0x07);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BrcmfChipBringUp() {
|
||||
g_brcmf.BusLock.Acquire();
|
||||
g_brcmf.SharedAddr = 0;
|
||||
g_brcmf.ConsoleIdx = 0;
|
||||
g_brcmf.RamSize = 0;
|
||||
bool ok = PowerOnAndEnumerate() && AttachChip() && DownloadFirmware() && StartFrames();
|
||||
g_brcmf.BusLock.Release();
|
||||
if (!ok) {
|
||||
// A firmware that started and then trapped has said why on its
|
||||
// console (no-op when it never got that far).
|
||||
BrcmfDumpConsole();
|
||||
BrcmfChipPowerOff();
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Firmware console and trap report
|
||||
// =========================================================================
|
||||
|
||||
static bool InRam(uint32_t addr, uint32_t len) {
|
||||
return addr >= g_brcmf.RamBase && addr + len <= g_brcmf.RamBase + g_brcmf.RamSize
|
||||
&& addr + len >= addr;
|
||||
}
|
||||
|
||||
static bool ReadShared(uint32_t sh[16]) {
|
||||
if (!g_brcmf.SharedAddr) {
|
||||
uint32_t addr = Rd(g_brcmf.RamBase + g_brcmf.RamSize - 4);
|
||||
// Still the NVRAM token: the firmware has not published it.
|
||||
if (((~addr >> 16) & 0xFFFF) == (addr & 0xFFFF) || !InRam(addr, 64)) return false;
|
||||
g_brcmf.SharedAddr = addr;
|
||||
}
|
||||
return BrcmfBpReadMem(g_brcmf.SharedAddr, (uint8_t*)sh, 64);
|
||||
}
|
||||
|
||||
static void EmitConsole(const char* buf, uint32_t size, uint32_t from, uint32_t to) {
|
||||
char line[161];
|
||||
uint32_t n = 0;
|
||||
for (uint32_t i = from; i != to; i = (i + 1) % size) {
|
||||
char c = buf[i];
|
||||
if (c == '\r') continue;
|
||||
if (c == '\n' || n == sizeof(line) - 1) {
|
||||
line[n] = '\0';
|
||||
if (n) KernelLogStream(INFO, "WiFi-FW") << line;
|
||||
n = 0;
|
||||
if (c == '\n') continue;
|
||||
}
|
||||
line[n++] = (c >= 0x20 && c < 0x7F) ? c : '.';
|
||||
}
|
||||
line[n] = '\0';
|
||||
if (n) KernelLogStream(INFO, "WiFi-FW") << line;
|
||||
}
|
||||
|
||||
void BrcmfDumpConsole() {
|
||||
if (g_brcmf.RamSize == 0) return;
|
||||
g_brcmf.BusLock.Acquire();
|
||||
|
||||
// sdpcm_shared: flags, trap_addr, assert_exp_addr, assert_file_addr,
|
||||
// assert_line, console_addr, msgtrace_addr, tag[32], brpt_addr
|
||||
uint32_t sh[16] = {};
|
||||
if (!ReadShared(sh)) {
|
||||
g_brcmf.BusLock.Release();
|
||||
return;
|
||||
}
|
||||
uint32_t flags = sh[0];
|
||||
|
||||
// rte_console: vcons_in, vcons_out, then the log: buf, buf_size,
|
||||
// idx, out_idx.
|
||||
uint32_t log[4] = {};
|
||||
if (InRam(sh[5], 24) && BrcmfBpReadMem(sh[5] + 8, (uint8_t*)log, 16)
|
||||
&& log[1] > 0 && log[1] <= 16384 && InRam(log[0], log[1]) && log[2] < log[1]) {
|
||||
uint32_t size = log[1];
|
||||
char* buf = (char*)Memory::g_heap->Request((size + 3) & ~3u);
|
||||
if (buf && BrcmfBpReadMem(log[0], (uint8_t*)buf, (size + 3) & ~3u)) {
|
||||
if (g_brcmf.ConsoleIdx >= size) g_brcmf.ConsoleIdx = 0;
|
||||
EmitConsole(buf, size, g_brcmf.ConsoleIdx, log[2]);
|
||||
g_brcmf.ConsoleIdx = log[2];
|
||||
}
|
||||
if (buf) Memory::g_heap->Free(buf);
|
||||
}
|
||||
|
||||
if ((flags & 0x0400) && InRam(sh[1], 72)) {
|
||||
// brcmf_trap_info: type, epc, cpsr, spsr, r0-r12, sp, lr, pc
|
||||
uint32_t trap[20] = {};
|
||||
if (BrcmfBpReadMem(sh[1], (uint8_t*)trap, sizeof(trap))) {
|
||||
KernelLogStream(ERROR, "WiFi-FW") << "Firmware trap: type 0x" << base::hex
|
||||
<< (uint64_t)trap[0] << ", epc 0x" << (uint64_t)trap[1] << ", lr 0x"
|
||||
<< (uint64_t)trap[18] << ", pc 0x" << (uint64_t)trap[19] << base::dec;
|
||||
}
|
||||
}
|
||||
if (flags & 0x0200) {
|
||||
char file[64] = {};
|
||||
if (InRam(sh[3], 60)) BrcmfBpReadMem(sh[3], (uint8_t*)file, 60);
|
||||
file[63] = '\0';
|
||||
KernelLogStream(ERROR, "WiFi-FW") << "Firmware assert at " << file << ":"
|
||||
<< (uint64_t)sh[4];
|
||||
}
|
||||
g_brcmf.BusLock.Release();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,902 @@
|
||||
/*
|
||||
* BrcmfConnect.cpp
|
||||
* Broadcom FullMAC over SDIO: firmware preinit (CLM, events), escan,
|
||||
* join, key installation, link state, and the entry points the Wi-Fi
|
||||
* facade calls
|
||||
* Derived in part from Linux's brcmfmac, Copyright (c) 2010-2014 Broadcom
|
||||
* Corporation, ISC license (notice in programs/data/licenses/NOTICES.txt).
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "Brcmf.hpp"
|
||||
#include "BrcmfSdio.hpp"
|
||||
#include "Ieee80211.hpp"
|
||||
#include "Wpa.hpp"
|
||||
#include <Api/Syscall.hpp>
|
||||
#include <Arch/Cpu.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Drivers/Platform/RpiFirmware.hpp>
|
||||
#include <Libraries/Crypto.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Memory/Heap.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <Timekeeping/Timer.hpp>
|
||||
|
||||
// Joining follows brcmf_cfg80211_connect with the supplicant on the host:
|
||||
// "wpaie" the RSN element our supplicant built (the firmware puts it in
|
||||
// the association request, and it must match message 2's copy)
|
||||
// "wpa_auth" WPA2-PSK (0x80) or PSK-SHA256 (0x8000), 0 for open
|
||||
// "auth" open system
|
||||
// "wsec" AES (4) for CCMP, 0 for open
|
||||
// "join" SSID, BSSID and the chanspec escan reported
|
||||
// The firmware answers with E_SET_SSID (status 0 = associated) and E_LINK;
|
||||
// the access point then starts the 4-way handshake, whose EAPOL frames come
|
||||
// and go as ordinary Ethernet frames. The keys it produces go to the
|
||||
// firmware with "wsec_key". The radio does AES-CCMP only: GCMP networks are
|
||||
// refused.
|
||||
|
||||
using namespace Kt;
|
||||
using namespace montauk::abi;
|
||||
|
||||
namespace Drivers::Net::Wifi {
|
||||
|
||||
// WLC ioctls
|
||||
static constexpr uint32_t WLC_UP = 2;
|
||||
static constexpr uint32_t WLC_SET_INFRA = 20;
|
||||
static constexpr uint32_t WLC_SET_SSID = 26;
|
||||
static constexpr uint32_t WLC_SCAN = 50;
|
||||
static constexpr uint32_t WLC_DISASSOC = 52;
|
||||
static constexpr uint32_t WLC_SET_PM = 86;
|
||||
static constexpr uint32_t WLC_GET_VALID_CHANNELS = 217;
|
||||
|
||||
// Events
|
||||
static constexpr uint32_t E_SET_SSID = 0;
|
||||
static constexpr uint32_t E_AUTH = 3;
|
||||
static constexpr uint32_t E_DEAUTH = 5;
|
||||
static constexpr uint32_t E_DEAUTH_IND = 6;
|
||||
static constexpr uint32_t E_ASSOC = 7;
|
||||
static constexpr uint32_t E_REASSOC = 9;
|
||||
static constexpr uint32_t E_DISASSOC = 11;
|
||||
static constexpr uint32_t E_DISASSOC_IND = 12;
|
||||
static constexpr uint32_t E_LINK = 16;
|
||||
static constexpr uint32_t E_ROAM = 19;
|
||||
static constexpr uint32_t E_PSK_SUP = 46;
|
||||
static constexpr uint32_t E_ESCAN_RESULT = 69;
|
||||
|
||||
static constexpr uint32_t STATUS_SUCCESS = 0;
|
||||
static constexpr uint32_t STATUS_PARTIAL = 8;
|
||||
static constexpr uint16_t EVENT_FLAG_LINK = 0x01;
|
||||
|
||||
static constexpr uint32_t WPA_AUTH_DISABLED = 0;
|
||||
static constexpr uint32_t WPA2_AUTH_PSK = 0x80;
|
||||
static constexpr uint32_t WPA2_AUTH_PSK_SHA256= 0x8000;
|
||||
static constexpr uint32_t AES_ENABLED = 4;
|
||||
static constexpr uint32_t CRYPTO_ALGO_AES_CCM = 4;
|
||||
static constexpr uint32_t KEY_FLAG_PRIMARY = 1u << 1;
|
||||
|
||||
|
||||
static constexpr uint64_t JOIN_TIMEOUT_MS = 12000;
|
||||
static constexpr uint64_t SCAN_TIMEOUT_MS = 25000;
|
||||
static constexpr uint64_t POLL_ACTIVE_MS = 10;
|
||||
static constexpr uint64_t POLL_IDLE_MS = 250;
|
||||
|
||||
// The NVRAM files ship one address for every board; the radio's own
|
||||
// OTP normally overrides it.
|
||||
static constexpr uint8_t NVRAM_PLACEHOLDER_MAC[6] = { 0xb8, 0x27, 0xeb, 0x74, 0xf2, 0x6c };
|
||||
|
||||
// =========================================================================
|
||||
// State
|
||||
// =========================================================================
|
||||
|
||||
static volatile uint32_t g_conn = WIFI_CONN_IDLE;
|
||||
static uint8_t g_bssid[6];
|
||||
static char g_ssid[33];
|
||||
static uint8_t g_ssidLen = 0;
|
||||
static uint8_t g_channel = 0;
|
||||
static bool g_secure = false;
|
||||
static bool g_refusedSecurity = false;
|
||||
static bool g_authFailed = false;
|
||||
static bool g_sawEapol = false;
|
||||
static uint64_t g_joinStartMs = 0;
|
||||
static uint64_t g_lastServiceMs = 0;
|
||||
static WpaConfig g_wpaCfg = {};
|
||||
|
||||
// Raised by events inside the pump, consumed by ConnectService().
|
||||
static volatile bool g_evAssoc = false;
|
||||
static volatile bool g_evJoinFailed = false;
|
||||
static volatile bool g_evLinkDown = false;
|
||||
static volatile uint32_t g_evStatus = 0;
|
||||
static volatile uint32_t g_evReason = 0;
|
||||
|
||||
static volatile bool g_scanActive = false;
|
||||
static uint64_t g_scanStartMs = 0;
|
||||
static uint16_t g_syncId = 0;
|
||||
static uint32_t g_scanSeen = 0;
|
||||
|
||||
static volatile bool g_dumpConsole = false;
|
||||
static bool g_failureLogged = false;
|
||||
|
||||
// The chanspec escan reported for each BSS, in the firmware's own
|
||||
// encoding, so the join names the channel exactly.
|
||||
struct BssChan { uint8_t Bssid[6]; uint16_t Chanspec; bool Used; };
|
||||
static BssChan g_chans[64];
|
||||
static kcp::Spinlock g_chanLock;
|
||||
|
||||
struct PendingEapol { uint32_t Len; uint8_t Data[512]; };
|
||||
static constexpr uint32_t EAPOL_DEPTH = 4;
|
||||
static PendingEapol g_eapolQ[EAPOL_DEPTH];
|
||||
static uint32_t g_eapolHead = 0, g_eapolTail = 0;
|
||||
static kcp::Spinlock g_eapolLock;
|
||||
|
||||
static inline uint16_t Le16(const uint8_t* p) { return (uint16_t)(p[0] | p[1] << 8); }
|
||||
static inline uint32_t Le32(const uint8_t* p) {
|
||||
return (uint32_t)p[0] | (uint32_t)p[1] << 8 | (uint32_t)p[2] << 16 | (uint32_t)p[3] << 24;
|
||||
}
|
||||
static inline void Put32(uint8_t* p, uint32_t v) { memcpy(p, &v, 4); }
|
||||
static inline void Put16(uint8_t* p, uint16_t v) { memcpy(p, &v, 2); }
|
||||
|
||||
static bool Elapsed(uint64_t now, uint64_t since, uint64_t ms) {
|
||||
return now >= since && now - since >= ms;
|
||||
}
|
||||
|
||||
static void PutMac(KernelLogStream& s, const uint8_t* m) {
|
||||
static const char hex[] = "0123456789abcdef";
|
||||
char buf[18];
|
||||
for (int i = 0; i < 6; i++) {
|
||||
buf[i * 3] = hex[m[i] >> 4];
|
||||
buf[i * 3 + 1] = hex[m[i] & 0xF];
|
||||
buf[i * 3 + 2] = i < 5 ? ':' : '\0';
|
||||
}
|
||||
s << buf;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Events (inside the pump: record, never send)
|
||||
// =========================================================================
|
||||
|
||||
static void RememberChanspec(const uint8_t* bssid, uint16_t chanspec) {
|
||||
g_chanLock.Acquire();
|
||||
BssChan* slot = nullptr;
|
||||
for (auto& c : g_chans) {
|
||||
if (c.Used && memcmp(c.Bssid, bssid, 6) == 0) { slot = &c; break; }
|
||||
if (!c.Used && !slot) slot = &c;
|
||||
}
|
||||
if (slot) {
|
||||
memcpy(slot->Bssid, bssid, 6);
|
||||
slot->Chanspec = chanspec;
|
||||
slot->Used = true;
|
||||
}
|
||||
g_chanLock.Release();
|
||||
}
|
||||
|
||||
static uint16_t LookupChanspec(const uint8_t* bssid, uint8_t channel) {
|
||||
uint16_t found = 0;
|
||||
g_chanLock.Acquire();
|
||||
for (auto& c : g_chans) {
|
||||
if (c.Used && memcmp(c.Bssid, bssid, 6) == 0) { found = c.Chanspec; break; }
|
||||
}
|
||||
g_chanLock.Release();
|
||||
if (found) return found;
|
||||
// 802.11ac encoding: channel, 20 MHz, band.
|
||||
return (uint16_t)(channel | 0x1000 | (channel > 14 ? 0xC000 : 0x0000));
|
||||
}
|
||||
|
||||
// brcmf_escan_result_le: buflen, version, sync_id, bss_count, then
|
||||
// brcmf_bss_info_le records (128 bytes fixed, IEs at ie_offset).
|
||||
static void HandleEscanResult(const uint8_t* data, uint32_t len) {
|
||||
if (len < 12) return;
|
||||
uint16_t count = Le16(data + 10);
|
||||
uint32_t off = 12;
|
||||
for (uint16_t i = 0; i < count; i++) {
|
||||
if (off + 128 > len) break;
|
||||
const uint8_t* bi = data + off;
|
||||
uint32_t biLen = Le32(bi + 4);
|
||||
if (biLen < 128 || biLen > len - off) break;
|
||||
|
||||
const uint8_t* bssid = bi + 8;
|
||||
uint16_t beacon = Le16(bi + 14);
|
||||
uint16_t caps = Le16(bi + 16);
|
||||
uint16_t chanspec = Le16(bi + 72);
|
||||
int16_t rssi = (int16_t)Le16(bi + 78);
|
||||
uint8_t ctlCh = bi[88];
|
||||
uint16_t ieOff = Le16(bi + 116);
|
||||
uint32_t ieLen = Le32(bi + 120);
|
||||
if (ieOff > biLen || ieLen > biLen - ieOff) ieLen = 0;
|
||||
|
||||
uint8_t channel = ctlCh ? ctlCh : (uint8_t)(chanspec & 0xFF);
|
||||
if (rssi < -127) rssi = -127;
|
||||
if (rssi > 0) rssi = 0;
|
||||
RememberChanspec(bssid, chanspec);
|
||||
WifiRxBssInfo(bssid, beacon, caps, ieLen ? bi + ieOff : nullptr, ieLen,
|
||||
channel, (int8_t)rssi);
|
||||
g_scanSeen++;
|
||||
off += biLen;
|
||||
}
|
||||
}
|
||||
|
||||
void BrcmfOnEvent(uint32_t type, uint32_t status, uint32_t reason, uint16_t flags,
|
||||
const uint8_t* addr, const uint8_t* data, uint32_t len) {
|
||||
(void)addr;
|
||||
switch (type) {
|
||||
case E_ESCAN_RESULT:
|
||||
if (status == STATUS_PARTIAL) {
|
||||
HandleEscanResult(data, len);
|
||||
} else if (g_scanActive) {
|
||||
g_scanActive = false;
|
||||
KernelLogStream(INFO, "WiFi") << "Scan " << (status == STATUS_SUCCESS ? "complete" : "ended")
|
||||
<< ": " << (uint64_t)g_scanSeen << " results";
|
||||
}
|
||||
break;
|
||||
|
||||
case E_SET_SSID:
|
||||
if (g_conn != WIFI_CONN_ASSOCIATING) break;
|
||||
if (status == STATUS_SUCCESS) {
|
||||
g_evAssoc = true;
|
||||
} else {
|
||||
g_evStatus = status;
|
||||
g_evJoinFailed = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case E_LINK:
|
||||
if (flags & EVENT_FLAG_LINK) {
|
||||
if (g_conn == WIFI_CONN_ASSOCIATING) g_evAssoc = true;
|
||||
} else if (g_conn >= WIFI_CONN_ASSOCIATED && g_conn != WIFI_CONN_FAILED) {
|
||||
g_evReason = reason;
|
||||
g_evLinkDown = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case E_DEAUTH:
|
||||
case E_DEAUTH_IND:
|
||||
case E_DISASSOC_IND:
|
||||
if (g_conn >= WIFI_CONN_ASSOCIATED && g_conn != WIFI_CONN_FAILED) {
|
||||
g_evReason = reason;
|
||||
g_evLinkDown = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case E_AUTH:
|
||||
if (status != STATUS_SUCCESS && g_conn == WIFI_CONN_ASSOCIATING)
|
||||
KernelLogStream(WARNING, "WiFi") << "Authentication rejected (status "
|
||||
<< (uint64_t)status << ", reason " << (uint64_t)reason << ")";
|
||||
break;
|
||||
|
||||
case E_ASSOC:
|
||||
case E_REASSOC:
|
||||
if (status != STATUS_SUCCESS && g_conn == WIFI_CONN_ASSOCIATING)
|
||||
KernelLogStream(WARNING, "WiFi") << "Association rejected (status "
|
||||
<< (uint64_t)status << ", reason " << (uint64_t)reason << ")";
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void QueueEapol(const uint8_t* body, uint32_t len) {
|
||||
if (len > sizeof(g_eapolQ[0].Data)) return;
|
||||
g_eapolLock.Acquire();
|
||||
uint32_t next = (g_eapolHead + 1) % EAPOL_DEPTH;
|
||||
if (next == g_eapolTail) g_eapolTail = (g_eapolTail + 1) % EAPOL_DEPTH;
|
||||
memcpy(g_eapolQ[g_eapolHead].Data, body, len);
|
||||
g_eapolQ[g_eapolHead].Len = len;
|
||||
g_eapolHead = next;
|
||||
g_eapolLock.Release();
|
||||
}
|
||||
|
||||
static bool EapolPending() {
|
||||
g_eapolLock.Acquire();
|
||||
bool pending = g_eapolHead != g_eapolTail;
|
||||
g_eapolLock.Release();
|
||||
return pending;
|
||||
}
|
||||
|
||||
void BrcmfOnRxEthernet(const uint8_t* frame, uint32_t len) {
|
||||
uint16_t etherType = (uint16_t)(frame[12] << 8 | frame[13]);
|
||||
if (etherType == ETHERTYPE_EAPOL) {
|
||||
if (g_secure && g_conn >= WIFI_CONN_ASSOCIATING && g_conn != WIFI_CONN_FAILED) {
|
||||
QueueEapol(frame + 14, len - 14);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (BrcmfLinkUp()) WifiRxEthernet(frame, len);
|
||||
}
|
||||
|
||||
void BrcmfOnFirmwareFailure(const char* why) {
|
||||
if (g_brcmf.State != BrcmfState::Running) return;
|
||||
g_brcmf.State = BrcmfState::Error;
|
||||
g_brcmf.FwErrors++;
|
||||
g_scanActive = false;
|
||||
g_dumpConsole = true;
|
||||
if (!g_failureLogged) {
|
||||
g_failureLogged = true;
|
||||
KernelLogStream(ERROR, "WiFi") << "Radio failed: " << why;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Bring-up
|
||||
// =========================================================================
|
||||
|
||||
// CLM blob: the regulatory database the firmware ships without
|
||||
// ("clm_min"). Sent in 1400-byte chunks behind brcmf_dload_data_le:
|
||||
// flag (handler version 1 << 12 | begin 2 | end 4), type (2 = CLM),
|
||||
// length, CRC (unused), data.
|
||||
static bool LoadClm() {
|
||||
uint32_t size = 0;
|
||||
uint8_t* blob = BrcmfLoadFile("clm_blob", size, false);
|
||||
if (!blob) {
|
||||
KernelLogStream(WARNING, "WiFi") << "No CLM blob; the firmware's own regulatory data is used";
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr uint32_t CHUNK = 1400;
|
||||
constexpr uint32_t HDR = 12;
|
||||
uint8_t* chunk = (uint8_t*)Memory::g_heap->Request(HDR + CHUNK + 4);
|
||||
bool ok = chunk != nullptr;
|
||||
uint32_t off = 0;
|
||||
uint16_t flag = 1u << 12 | 0x0002;
|
||||
while (ok && off < size) {
|
||||
uint32_t n = size - off > CHUNK ? CHUNK : size - off;
|
||||
if (off + n == size) flag |= 0x0004;
|
||||
memset(chunk, 0, HDR + CHUNK + 4);
|
||||
Put16(chunk, flag);
|
||||
Put16(chunk + 2, 2);
|
||||
Put32(chunk + 4, n);
|
||||
Put32(chunk + 8, 0);
|
||||
memcpy(chunk + HDR, blob + off, n);
|
||||
// Linux sends sizeof(struct) + len - 1: three bytes of padding.
|
||||
ok = BrcmfIovarSet("clmload", chunk, HDR + n + 3);
|
||||
flag &= (uint16_t)~0x0002;
|
||||
off += n;
|
||||
}
|
||||
if (chunk) Memory::g_heap->Free(chunk);
|
||||
Memory::g_heap->Free(blob);
|
||||
|
||||
uint32_t status = 0;
|
||||
if (ok && BrcmfIovarGetInt("clmload_status", status, true) && status != 0) {
|
||||
KernelLogStream(ERROR, "WiFi") << "The firmware rejected the CLM blob (status "
|
||||
<< (uint64_t)status << ")";
|
||||
ok = false;
|
||||
}
|
||||
if (ok) KernelLogStream(INFO, "WiFi") << "CLM blob loaded (" << (uint64_t)size << " bytes)";
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void SetEventMask() {
|
||||
uint8_t mask[16] = {};
|
||||
const uint32_t events[] = {
|
||||
E_SET_SSID, E_AUTH, E_DEAUTH, E_DEAUTH_IND, E_ASSOC, E_REASSOC, E_DISASSOC,
|
||||
E_DISASSOC_IND, E_LINK, E_ROAM, E_PSK_SUP, E_ESCAN_RESULT,
|
||||
};
|
||||
for (uint32_t e : events) mask[e / 8] |= (uint8_t)(1u << (e % 8));
|
||||
BrcmfIovarSet("event_msgs", mask, sizeof(mask));
|
||||
}
|
||||
|
||||
// "Version: 7.45.265 (28bca26 CY) CRC: ..." -> "7.45.265"
|
||||
static void ParseVersion(const char* ver) {
|
||||
const char* p = ver;
|
||||
const char* key = "version ";
|
||||
while (*p) {
|
||||
const char* a = p;
|
||||
const char* b = key;
|
||||
while (*a && *b && (*a | 0x20) == *b) { a++; b++; }
|
||||
if (!*b) { p = a; break; }
|
||||
p++;
|
||||
}
|
||||
if (!*p) p = ver;
|
||||
int n = 0;
|
||||
while (p[n] && p[n] != ' ' && p[n] != '\n' && n < 31) {
|
||||
g_brcmf.FwVersion[n] = p[n];
|
||||
n++;
|
||||
}
|
||||
g_brcmf.FwVersion[n] = '\0';
|
||||
}
|
||||
|
||||
static void ReadChannels() {
|
||||
uint32_t list[65] = {};
|
||||
list[0] = 64;
|
||||
g_brcmf.ChannelCount = 0;
|
||||
if (!BrcmfIoctl(WLC_GET_VALID_CHANNELS, false, list, sizeof(list))) return;
|
||||
uint32_t count = list[0] > 64 ? 64 : list[0];
|
||||
bool b24 = false, b5 = false;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (list[1 + i] > 14) b5 = true;
|
||||
else if (list[1 + i] > 0) b24 = true;
|
||||
}
|
||||
g_brcmf.ChannelCount = (uint16_t)count;
|
||||
g_brcmf.Band24 = b24;
|
||||
g_brcmf.Band5 = b5;
|
||||
}
|
||||
|
||||
static bool Preinit() {
|
||||
uint8_t mac[6] = {};
|
||||
if (!BrcmfIovarGet("cur_etheraddr", mac, 6)) return false;
|
||||
if (memcmp(mac, NVRAM_PLACEHOLDER_MAC, 6) == 0) {
|
||||
// Raspberry Pi convention: wlan0 is the board (Ethernet) address
|
||||
// plus one.
|
||||
uint8_t board[6];
|
||||
if (Drivers::Platform::RpiFirmware::GetBoardMacAddress(board)) {
|
||||
memcpy(mac, board, 6);
|
||||
for (int i = 5; i >= 3; i--) if (++mac[i] != 0) break;
|
||||
BrcmfIovarSet("cur_etheraddr", mac, 6);
|
||||
}
|
||||
}
|
||||
memcpy(g_brcmf.Mac, mac, 6);
|
||||
|
||||
char ver[256] = {};
|
||||
if (BrcmfIovarGet("ver", ver, sizeof(ver) - 1)) {
|
||||
ver[sizeof(ver) - 1] = '\0';
|
||||
for (char& c : ver) if (c == '\n' || c == '\r') c = ' ';
|
||||
KernelLogStream(INFO, "WiFi") << "Firmware: " << ver;
|
||||
ParseVersion(ver);
|
||||
}
|
||||
|
||||
if (!LoadClm()) return false;
|
||||
|
||||
// No glomming in either direction: one SDPCM frame per transfer.
|
||||
BrcmfIovarSetInt("bus:txglom", 0, true);
|
||||
BrcmfIovarSetInt("bus:rxglom", 0, true);
|
||||
|
||||
BrcmfSetInt(WLC_SET_INFRA, 1);
|
||||
// Roaming would re-key without the host's supplicant; ARP/ND offload
|
||||
// would answer for an address the firmware was never told.
|
||||
BrcmfIovarSetInt("roam_off", 1, true);
|
||||
BrcmfIovarSetInt("arpoe", 0, true);
|
||||
BrcmfIovarSetInt("ndoe", 0, true);
|
||||
// Power save off: the radio misses broadcasts (ARP, DHCP offers)
|
||||
// while dozing, and the Pi is not on a battery.
|
||||
BrcmfSetInt(WLC_SET_PM, 0);
|
||||
|
||||
SetEventMask();
|
||||
if (!BrcmfSetInt(WLC_UP, 1)) {
|
||||
KernelLogStream(ERROR, "WiFi") << "The firmware would not bring the interface up";
|
||||
return false;
|
||||
}
|
||||
ReadChannels();
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ResetConnection() {
|
||||
g_conn = WIFI_CONN_IDLE;
|
||||
g_evAssoc = false;
|
||||
g_evJoinFailed = false;
|
||||
g_evLinkDown = false;
|
||||
g_secure = false;
|
||||
g_sawEapol = false;
|
||||
g_eapolLock.Acquire();
|
||||
g_eapolHead = g_eapolTail = 0;
|
||||
g_eapolLock.Release();
|
||||
WpaReset();
|
||||
}
|
||||
|
||||
bool BrcmfProbe(uint64_t arasanMmio) {
|
||||
if (g_brcmf.State != BrcmfState::Absent) return false;
|
||||
g_brcmf.ArasanMmio = arasanMmio;
|
||||
g_brcmf.State = BrcmfState::Detected;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BrcmfCompleteInit() {
|
||||
g_brcmf.State = BrcmfState::Booting;
|
||||
g_failureLogged = false;
|
||||
g_dumpConsole = false;
|
||||
g_scanActive = false;
|
||||
g_brcmf.CmdFailBucket = 0;
|
||||
ResetConnection();
|
||||
BrcmfProtoReset();
|
||||
|
||||
// One retry from power-off: the Bluetooth half of the same chip may
|
||||
// be powering up or taking its patch download at the same moment.
|
||||
bool up = BrcmfChipBringUp();
|
||||
if (!up) {
|
||||
KernelLogStream(WARNING, "WiFi") << "Bring-up failed; power-cycling the radio and trying once more";
|
||||
Timekeeping::BusyWaitMs(100);
|
||||
up = BrcmfChipBringUp();
|
||||
}
|
||||
if (!up) {
|
||||
g_brcmf.State = BrcmfState::Error;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Running from here: the preinit below is the first SDPCM traffic.
|
||||
g_brcmf.LastPollMs = Timekeeping::GetMilliseconds();
|
||||
g_brcmf.State = BrcmfState::Running;
|
||||
BrcmfDumpConsole();
|
||||
|
||||
if (!Preinit()) {
|
||||
KernelLogStream(ERROR, "WiFi") << "Firmware initialization failed";
|
||||
BrcmfDumpConsole();
|
||||
g_brcmf.State = BrcmfState::Error;
|
||||
BrcmfChipPowerOff();
|
||||
return false;
|
||||
}
|
||||
BrcmfDumpConsole();
|
||||
|
||||
auto log = KernelLogStream(OK, "WiFi");
|
||||
log << "CYW" << g_brcmf.FwName << " ready: firmware " << g_brcmf.FwVersion << ", "
|
||||
<< (uint64_t)g_brcmf.ChannelCount << " channels, MAC ";
|
||||
PutMac(log, g_brcmf.Mac);
|
||||
return true;
|
||||
}
|
||||
|
||||
void BrcmfShutdown() {
|
||||
g_scanActive = false;
|
||||
ResetConnection();
|
||||
BrcmfChipPowerOff();
|
||||
g_brcmf.State = BrcmfState::Detected;
|
||||
}
|
||||
|
||||
BrcmfState BrcmfGetState() { return g_brcmf.State; }
|
||||
|
||||
// =========================================================================
|
||||
// Scanning
|
||||
// =========================================================================
|
||||
|
||||
bool BrcmfScanActive() { return g_scanActive; }
|
||||
|
||||
bool BrcmfStartScan() {
|
||||
if (g_brcmf.State != BrcmfState::Running) return false;
|
||||
if (g_scanActive) return true;
|
||||
|
||||
// brcmf_escan_params_le: version 1, action 1 (start), sync id, then
|
||||
// brcmf_scan_params_le: SSID (any), BSSID (broadcast), BSS type any,
|
||||
// active scan, firmware defaults for every dwell, all channels.
|
||||
uint8_t p[72] = {};
|
||||
Put32(p, 1);
|
||||
Put16(p + 4, 1);
|
||||
Put16(p + 6, ++g_syncId);
|
||||
memset(p + 44, 0xFF, 6);
|
||||
p[50] = 2;
|
||||
p[51] = 0;
|
||||
Put32(p + 52, 0xFFFFFFFF);
|
||||
Put32(p + 56, 0xFFFFFFFF);
|
||||
Put32(p + 60, 0xFFFFFFFF);
|
||||
Put32(p + 64, 0xFFFFFFFF);
|
||||
Put32(p + 68, 0);
|
||||
|
||||
g_scanSeen = 0;
|
||||
g_scanStartMs = Timekeeping::GetMilliseconds();
|
||||
g_scanActive = true; // results may arrive while the ioctl is pumping
|
||||
if (!BrcmfIovarSet("escan", p, sizeof(p))) {
|
||||
g_scanActive = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void BrcmfAbortScan() {
|
||||
if (!g_scanActive) return;
|
||||
// A WLC_SCAN naming channel -1 cancels the scan in progress.
|
||||
uint8_t p[68] = {};
|
||||
memset(p + 36, 0xFF, 6);
|
||||
p[42] = 2;
|
||||
Put32(p + 44, 0xFFFFFFFF);
|
||||
Put32(p + 48, 0xFFFFFFFF);
|
||||
Put32(p + 52, 0xFFFFFFFF);
|
||||
Put32(p + 56, 0xFFFFFFFF);
|
||||
Put32(p + 60, 1);
|
||||
Put16(p + 64, 0xFFFF);
|
||||
BrcmfIoctl(WLC_SCAN, true, p, sizeof(p));
|
||||
g_scanActive = false;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Joining
|
||||
// =========================================================================
|
||||
|
||||
int BrcmfConnectState() { return (int)g_conn; }
|
||||
bool BrcmfConnectRefusedForSecurity() { return g_refusedSecurity; }
|
||||
bool BrcmfJoinFailedOnAuth() { return g_authFailed || WpaGetState() == WpaState::Failed; }
|
||||
const char* BrcmfConnectSsid() { return g_ssid; }
|
||||
const uint8_t* BrcmfConnectBssid() { return g_bssid; }
|
||||
uint8_t BrcmfConnectChannel() { return g_channel; }
|
||||
bool BrcmfLinkUp() { return g_brcmf.State == BrcmfState::Running && g_conn == WIFI_CONN_CONNECTED; }
|
||||
|
||||
static void Disassociate() {
|
||||
uint8_t scb[12] = {};
|
||||
Put32(scb, 3); // deauthenticated: leaving
|
||||
memcpy(scb + 4, g_bssid, 6);
|
||||
BrcmfIoctl(WLC_DISASSOC, true, scb, sizeof(scb));
|
||||
}
|
||||
|
||||
void BrcmfConnectAbort() {
|
||||
if (g_conn != WIFI_CONN_IDLE && g_brcmf.State == BrcmfState::Running) Disassociate();
|
||||
ResetConnection();
|
||||
}
|
||||
|
||||
bool BrcmfConnectStart(const uint8_t* bssid, uint8_t channel, const char* ssid,
|
||||
const char* password, const uint8_t* rsnIe, uint8_t rsnIeLen) {
|
||||
if (g_brcmf.State != BrcmfState::Running) return false;
|
||||
if (g_conn != WIFI_CONN_IDLE) BrcmfConnectAbort();
|
||||
g_refusedSecurity = false;
|
||||
g_authFailed = false;
|
||||
|
||||
memcpy(g_bssid, bssid, 6);
|
||||
g_channel = channel;
|
||||
g_ssidLen = 0;
|
||||
while (ssid && g_ssidLen < 32 && ssid[g_ssidLen]) {
|
||||
g_ssid[g_ssidLen] = ssid[g_ssidLen];
|
||||
g_ssidLen++;
|
||||
}
|
||||
g_ssid[g_ssidLen] = '\0';
|
||||
|
||||
bool secure = rsnIe && rsnIeLen > 0;
|
||||
uint8_t ie[64];
|
||||
uint32_t ieLen = 0;
|
||||
if (secure) {
|
||||
memset(&g_wpaCfg, 0, sizeof(g_wpaCfg));
|
||||
if (!WpaParseApRsn(rsnIe, rsnIeLen, g_wpaCfg)) {
|
||||
g_refusedSecurity = true;
|
||||
return false;
|
||||
}
|
||||
if (g_wpaCfg.PairwiseCipher != RSN_CIPHER_CCMP || g_wpaCfg.GroupCipher != RSN_CIPHER_CCMP) {
|
||||
KernelLogStream(WARNING, "WiFi") << "This radio does AES-CCMP only; the network uses GCMP";
|
||||
g_refusedSecurity = true;
|
||||
return false;
|
||||
}
|
||||
uint32_t pl = 0;
|
||||
if (password) while (pl < sizeof(g_wpaCfg.Passphrase) - 1 && password[pl]) pl++;
|
||||
if (pl == 0) {
|
||||
KernelLogStream(WARNING, "WiFi") << "This network is encrypted and needs a passphrase";
|
||||
g_refusedSecurity = true;
|
||||
return false;
|
||||
}
|
||||
memcpy(g_wpaCfg.Passphrase, password, pl);
|
||||
g_wpaCfg.Passphrase[pl] = '\0';
|
||||
g_wpaCfg.PassLen = (uint8_t)pl;
|
||||
memcpy(g_wpaCfg.OwnMac, g_brcmf.Mac, 6);
|
||||
memcpy(g_wpaCfg.Bssid, g_bssid, 6);
|
||||
memcpy(g_wpaCfg.Ssid, g_ssid, g_ssidLen);
|
||||
g_wpaCfg.SsidLen = g_ssidLen;
|
||||
if (!WpaStart(g_wpaCfg)) {
|
||||
g_refusedSecurity = true;
|
||||
return false;
|
||||
}
|
||||
ieLen = WpaBuildRsnIe(ie, sizeof(ie));
|
||||
if (ieLen == 0) return false;
|
||||
}
|
||||
|
||||
if (g_scanActive) BrcmfAbortScan();
|
||||
|
||||
bool sha256 = secure && g_wpaCfg.Akm == RSN_AKM_PSK_SHA256;
|
||||
if (secure) BrcmfIovarSet("wpaie", ie, ieLen);
|
||||
else BrcmfIovarSet("wpaie", nullptr, 0, true);
|
||||
bool ok = BrcmfIovarSetInt("wpa_auth", secure ? (sha256 ? WPA2_AUTH_PSK_SHA256 : WPA2_AUTH_PSK)
|
||||
: WPA_AUTH_DISABLED)
|
||||
&& BrcmfIovarSetInt("auth", 0)
|
||||
&& BrcmfIovarSetInt("wsec", secure ? AES_ENABLED : 0);
|
||||
BrcmfIovarSetInt("mfp", 0, true);
|
||||
BrcmfIovarSetInt("sup_wpa", 0, true);
|
||||
if (!ok) {
|
||||
WpaReset();
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t chanspec = LookupChanspec(g_bssid, channel);
|
||||
|
||||
g_secure = secure;
|
||||
g_evAssoc = false;
|
||||
g_evJoinFailed = false;
|
||||
g_evLinkDown = false;
|
||||
g_joinStartMs = Timekeeping::GetMilliseconds();
|
||||
g_conn = WIFI_CONN_ASSOCIATING; // before "join": its events may land while it pumps
|
||||
|
||||
// brcmf_ext_join_params_le: SSID; join scan (type default, probes
|
||||
// and dwells as Linux uses for a known channel); BSSID, one chanspec.
|
||||
uint8_t jp[72] = {};
|
||||
Put32(jp, g_ssidLen);
|
||||
memcpy(jp + 4, g_ssid, g_ssidLen);
|
||||
jp[36] = 0xFF;
|
||||
Put32(jp + 40, 16);
|
||||
Put32(jp + 44, 320);
|
||||
Put32(jp + 48, 400);
|
||||
Put32(jp + 52, 0xFFFFFFFF);
|
||||
memcpy(jp + 56, g_bssid, 6);
|
||||
Put32(jp + 64, 1);
|
||||
Put16(jp + 68, chanspec);
|
||||
|
||||
if (!BrcmfIovarSet("join", jp, sizeof(jp), true)) {
|
||||
// Older firmware: WLC_SET_SSID with brcmf_join_params_le.
|
||||
uint8_t sp[52] = {};
|
||||
Put32(sp, g_ssidLen);
|
||||
memcpy(sp + 4, g_ssid, g_ssidLen);
|
||||
memcpy(sp + 36, g_bssid, 6);
|
||||
Put32(sp + 44, 1);
|
||||
Put16(sp + 48, chanspec);
|
||||
if (!BrcmfIoctl(WLC_SET_SSID, true, sp, sizeof(sp))) {
|
||||
KernelLogStream(ERROR, "WiFi") << "The firmware refused the join request";
|
||||
ResetConnection();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto log = KernelLogStream(INFO, "WiFi");
|
||||
log << "Joining \"" << g_ssid << "\" (";
|
||||
PutMac(log, g_bssid);
|
||||
log << ", channel " << (uint64_t)channel << ", chanspec 0x" << base::hex << (uint64_t)chanspec
|
||||
<< base::dec << (secure ? ", WPA2-PSK)" : ", open)");
|
||||
return true;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Keys and EAPOL (the supplicant's hooks)
|
||||
// =========================================================================
|
||||
|
||||
bool BrcmfWpaTxEapol(const uint8_t* body, uint32_t len) {
|
||||
uint8_t frame[14 + 512];
|
||||
if (len > 512) return false;
|
||||
memcpy(frame, g_bssid, 6);
|
||||
memcpy(frame + 6, g_brcmf.Mac, 6);
|
||||
frame[12] = ETHERTYPE_EAPOL >> 8;
|
||||
frame[13] = ETHERTYPE_EAPOL & 0xFF;
|
||||
memcpy(frame + 14, body, len);
|
||||
return BrcmfTxEthernet(frame, 14 + len);
|
||||
}
|
||||
|
||||
// brcmf_wsec_key_le: index, len, data[32], pad[18], algo, flags, pad[3],
|
||||
// iv_initialized, pad, rxiv {hi, lo}, pad[2], ea[6] -- 164 bytes.
|
||||
static bool SendKey(uint32_t index, const uint8_t* key, uint32_t keyLen, uint32_t flags,
|
||||
const uint8_t* ea, const uint8_t* rsc) {
|
||||
if (keyLen > 32) return false;
|
||||
uint8_t k[164] = {};
|
||||
Put32(k, index);
|
||||
Put32(k + 4, keyLen);
|
||||
memcpy(k + 8, key, keyLen);
|
||||
Put32(k + 112, CRYPTO_ALGO_AES_CCM);
|
||||
Put32(k + 116, flags);
|
||||
if (rsc) {
|
||||
Put32(k + 132, 1);
|
||||
Put32(k + 140, (uint32_t)rsc[2] | (uint32_t)rsc[3] << 8 | (uint32_t)rsc[4] << 16
|
||||
| (uint32_t)rsc[5] << 24);
|
||||
Put16(k + 144, (uint16_t)(rsc[0] | rsc[1] << 8));
|
||||
}
|
||||
if (ea) memcpy(k + 156, ea, 6);
|
||||
bool ok = BrcmfIovarSet("wsec_key", k, sizeof(k));
|
||||
Crypto::SecureZero(k, sizeof(k));
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Message 4 is still in the transmit queue when the supplicant installs
|
||||
// the pairwise key; it has to reach the radio first or it goes out
|
||||
// encrypted and the access point never sees it (Linux waits for its
|
||||
// pending 802.1X frames the same way).
|
||||
static void DrainTx() {
|
||||
uint64_t start = Timekeeping::GetMilliseconds();
|
||||
while (BrcmfTxPending() && Timekeeping::GetMilliseconds() - start < 200) {
|
||||
BrcmfPoll();
|
||||
Timekeeping::BusyWaitUs(200);
|
||||
}
|
||||
Timekeeping::BusyWaitMs(10);
|
||||
}
|
||||
|
||||
bool BrcmfWpaInstallPtk(const uint8_t* tk, uint32_t tkLen, uint8_t cipher) {
|
||||
if (cipher != RSN_CIPHER_CCMP) return false;
|
||||
DrainTx();
|
||||
return SendKey(0, tk, tkLen, 0, g_bssid, nullptr);
|
||||
}
|
||||
|
||||
bool BrcmfWpaInstallGtk(const uint8_t* gtk, uint32_t gtkLen, uint8_t keyIdx,
|
||||
uint8_t cipher, const uint8_t* rsc) {
|
||||
if (cipher != RSN_CIPHER_CCMP) return false;
|
||||
DrainTx();
|
||||
return SendKey(keyIdx, gtk, gtkLen, KEY_FLAG_PRIMARY, nullptr, rsc);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Service (idle loop / syscall waits: may send ioctls)
|
||||
// =========================================================================
|
||||
|
||||
static void ConnectService() {
|
||||
static volatile bool inService = false;
|
||||
if (__atomic_test_and_set(&inService, __ATOMIC_ACQUIRE)) return;
|
||||
struct Guard { ~Guard() { __atomic_clear(&inService, __ATOMIC_RELEASE); } } guard;
|
||||
|
||||
uint64_t now = Timekeeping::GetMilliseconds();
|
||||
g_lastServiceMs = now;
|
||||
|
||||
if (g_scanActive && Elapsed(now, g_scanStartMs, SCAN_TIMEOUT_MS)) {
|
||||
KernelLogStream(WARNING, "WiFi") << "Scan never finished; abandoning it";
|
||||
BrcmfAbortScan();
|
||||
}
|
||||
|
||||
if (g_conn == WIFI_CONN_ASSOCIATING) {
|
||||
if (g_evJoinFailed) {
|
||||
g_evJoinFailed = false;
|
||||
KernelLogStream(WARNING, "WiFi") << "Could not join \"" << g_ssid << "\" (status "
|
||||
<< (uint64_t)g_evStatus << (g_evStatus == 3 ? ": network not found)" : ")");
|
||||
g_conn = WIFI_CONN_FAILED;
|
||||
} else if (g_evAssoc) {
|
||||
g_evAssoc = false;
|
||||
if (g_secure) {
|
||||
KernelLogStream(OK, "WiFi") << "Associated with \"" << g_ssid << "\"; starting the key handshake";
|
||||
g_conn = WIFI_CONN_HANDSHAKING;
|
||||
} else {
|
||||
KernelLogStream(OK, "WiFi") << "Connected to \"" << g_ssid << "\" (open network)";
|
||||
g_conn = WIFI_CONN_CONNECTED;
|
||||
}
|
||||
} else if (Elapsed(Timekeeping::GetMilliseconds(), g_joinStartMs, JOIN_TIMEOUT_MS)) {
|
||||
KernelLogStream(WARNING, "WiFi") << "Timed out joining \"" << g_ssid << "\"";
|
||||
g_conn = WIFI_CONN_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_evLinkDown && g_conn >= WIFI_CONN_ASSOCIATED && g_conn != WIFI_CONN_FAILED) {
|
||||
g_evLinkDown = false;
|
||||
if (g_conn == WIFI_CONN_HANDSHAKING) {
|
||||
// The access point gives up on a handshake whose MICs do not
|
||||
// verify: a wrong passphrase, nearly always.
|
||||
KernelLogStream(WARNING, "WiFi") << "The access point dropped the connection during the key "
|
||||
"handshake (reason " << (uint64_t)g_evReason << "); check the passphrase";
|
||||
g_authFailed = g_sawEapol;
|
||||
g_conn = WIFI_CONN_FAILED;
|
||||
} else {
|
||||
KernelLogStream(WARNING, "WiFi") << "Disconnected from \"" << g_ssid << "\" (reason "
|
||||
<< (uint64_t)g_evReason << ")";
|
||||
ResetConnection();
|
||||
}
|
||||
}
|
||||
|
||||
if (g_conn == WIFI_CONN_HANDSHAKING || g_conn == WIFI_CONN_CONNECTED) {
|
||||
uint8_t body[512];
|
||||
for (;;) {
|
||||
uint32_t len = 0;
|
||||
g_eapolLock.Acquire();
|
||||
if (g_eapolHead != g_eapolTail) {
|
||||
len = g_eapolQ[g_eapolTail].Len;
|
||||
memcpy(body, g_eapolQ[g_eapolTail].Data, len);
|
||||
g_eapolTail = (g_eapolTail + 1) % EAPOL_DEPTH;
|
||||
}
|
||||
g_eapolLock.Release();
|
||||
if (!len) break;
|
||||
g_sawEapol = true;
|
||||
WpaOnEapol(body, len);
|
||||
}
|
||||
}
|
||||
|
||||
if (g_conn == WIFI_CONN_HANDSHAKING) {
|
||||
WpaService(Timekeeping::GetMilliseconds());
|
||||
if (WpaIsComplete()) {
|
||||
KernelLogStream(OK, "WiFi") << "Connected to \"" << g_ssid << "\"";
|
||||
g_conn = WIFI_CONN_CONNECTED;
|
||||
} else if (WpaGetState() == WpaState::Failed) {
|
||||
g_authFailed = true;
|
||||
g_conn = WIFI_CONN_FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BrcmfHasWork(uint64_t now) {
|
||||
if (g_brcmf.State == BrcmfState::Error) return g_dumpConsole;
|
||||
if (g_brcmf.State != BrcmfState::Running) return false;
|
||||
|
||||
bool active = g_scanActive || (g_conn != WIFI_CONN_IDLE && g_conn != WIFI_CONN_FAILED);
|
||||
if (BrcmfPollDue(now, active ? POLL_ACTIVE_MS : POLL_IDLE_MS)) return true;
|
||||
if (g_evAssoc || g_evJoinFailed || g_evLinkDown || EapolPending()) return true;
|
||||
// Deadlines and the supplicant's retransmissions, even with the air quiet.
|
||||
if ((g_conn == WIFI_CONN_ASSOCIATING || g_conn == WIFI_CONN_HANDSHAKING || g_scanActive)
|
||||
&& Elapsed(now, g_lastServiceMs, 50))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void BrcmfService() {
|
||||
if (g_brcmf.State == BrcmfState::Error) {
|
||||
if (g_dumpConsole) {
|
||||
g_dumpConsole = false;
|
||||
BrcmfDumpConsole();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (g_brcmf.State != BrcmfState::Running) return;
|
||||
BrcmfPoll();
|
||||
ConnectService();
|
||||
}
|
||||
|
||||
bool BrcmfSendEthernet(const uint8_t* frame, uint32_t len) {
|
||||
if (!BrcmfLinkUp()) return false;
|
||||
return BrcmfTxEthernet(frame, len);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,592 @@
|
||||
/*
|
||||
* BrcmfProto.cpp
|
||||
* Broadcom FullMAC over SDIO: SDPCM framing, CDC ioctls, BDC data frames,
|
||||
* firmware events, the RX pump and the transmit queue
|
||||
* Derived in part from Linux's brcmfmac, Copyright (c) 2010-2014 Broadcom
|
||||
* Corporation, ISC license (notice in programs/data/licenses/NOTICES.txt).
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "Brcmf.hpp"
|
||||
#include "BrcmfSdio.hpp"
|
||||
#include <Arch/Cpu.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <Timekeeping/Timer.hpp>
|
||||
#include <atomic>
|
||||
|
||||
// Every frame on function 2 starts with the SDPCM header (Linux sdio.c):
|
||||
//
|
||||
// hardware u16 len (the whole frame), u16 ~len
|
||||
// software u8 seq, u8 channel (low nibble; bit 7 = glom descriptor),
|
||||
// u8 next length, u8 data offset (from the frame start),
|
||||
// u8 flow control bitmap, u8 max seq (TX credit, inbound),
|
||||
// u16 reserved
|
||||
//
|
||||
// Channel 0 carries ioctls and their answers behind a 16-byte CDC header
|
||||
// (cmd, len, flags: id [31:16] | interface [15:12] | set [1] | error [0],
|
||||
// status). Channels 1 (events) and 2 (data) carry Ethernet frames behind a
|
||||
// 4-byte BDC header (flags: version 2 in [7:4]; priority; flags2: interface;
|
||||
// data offset in dwords to skip). Events are Ethernet frames of type 0x886C
|
||||
// with Broadcom's OUI.
|
||||
//
|
||||
// Transmit credit: the device's max seq bounds what may be sent, every frame
|
||||
// (ioctls included) takes the next sequence number.
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Drivers::Net::Wifi {
|
||||
|
||||
static constexpr uint32_t SDPCM_HDRLEN = 12;
|
||||
static constexpr uint8_t CHAN_CONTROL = 0;
|
||||
static constexpr uint8_t CHAN_EVENT = 1;
|
||||
static constexpr uint8_t CHAN_DATA = 2;
|
||||
static constexpr uint8_t CHAN_GLOM = 3;
|
||||
static constexpr uint8_t GLOM_DESC = 0x80;
|
||||
|
||||
static constexpr uint32_t CDC_HDRLEN = 16;
|
||||
static constexpr uint32_t CDCF_ERROR = 0x01;
|
||||
static constexpr uint32_t CDCF_SET = 0x02;
|
||||
static constexpr uint32_t BDC_HDRLEN = 4;
|
||||
static constexpr uint8_t BDC_VERSION = 2;
|
||||
|
||||
static constexpr uint16_t ETHERTYPE_BRCM = 0x886C;
|
||||
|
||||
// SDIO device core
|
||||
static constexpr uint32_t SD_INTSTATUS = 0x20;
|
||||
static constexpr uint32_t SD_TOSBMAILBOX = 0x40;
|
||||
static constexpr uint32_t SD_TOHOSTMAILBOXDATA = 0x4C;
|
||||
static constexpr uint32_t I_HMB_FRAME_IND = 1u << 6;
|
||||
static constexpr uint32_t I_HMB_HOST_INT = 1u << 7;
|
||||
static constexpr uint32_t SMB_INT_ACK = 1u << 1;
|
||||
static constexpr uint32_t HMB_DATA_FWHALT = 0x10;
|
||||
|
||||
static constexpr uint32_t F1_FRAMECTRL = 0x1000D;
|
||||
static constexpr uint32_t F1_RFRAMEBCLO = 0x1001B;
|
||||
static constexpr uint32_t F1_RFRAMEBCHI = 0x1001C;
|
||||
static constexpr uint8_t SFC_RF_TERM = 0x01;
|
||||
|
||||
// Function 2's FIFO; the address is ChipCommon's window | 4-byte access.
|
||||
static constexpr uint32_t F2_ADDR = 0x8000;
|
||||
static constexpr uint32_t SI_ENUM_BASE = 0x18000000;
|
||||
|
||||
static constexpr uint32_t RX_MAX = 8192;
|
||||
static constexpr uint32_t CTRL_MAX = 2048; // ioctl payload
|
||||
static constexpr uint32_t TX_SLOT = 2048; // padded data frame
|
||||
static constexpr uint32_t TXQ_DEPTH = 32;
|
||||
static constexpr uint64_t CREDIT_TIMEOUT_MS = 1000;
|
||||
static constexpr uint64_t IOCTL_TIMEOUT_MS = 2000;
|
||||
static constexpr uint32_t FAIL_BUCKET_LIMIT = 3;
|
||||
static constexpr uint32_t BUS_ERROR_LIMIT = 16;
|
||||
|
||||
static uint32_t g_rxBuf[RX_MAX / 4 + 16];
|
||||
static volatile bool g_pumping = false;
|
||||
static uint32_t g_busErrors = 0;
|
||||
static bool g_glomWarned = false;
|
||||
|
||||
struct TxSlot {
|
||||
uint32_t Len; // bytes on the bus (padded)
|
||||
uint32_t Data[TX_SLOT / 4];
|
||||
};
|
||||
static TxSlot g_txq[TXQ_DEPTH];
|
||||
static uint32_t g_txHead = 0, g_txTail = 0;
|
||||
static kcp::Spinlock g_txqLock;
|
||||
|
||||
// The outstanding ioctl, if any. The pump on whichever core finds the
|
||||
// answer copies it here under g_respLock; the caller copies it out.
|
||||
static kcp::Mutex g_ctrlLock;
|
||||
static kcp::Spinlock g_respLock;
|
||||
// Room for the largest ioctl padded to whole blocks.
|
||||
static uint32_t g_ctrlTx[(SDPCM_HDRLEN + CDC_HDRLEN + CTRL_MAX + Sdio::FN2_BLOCK - 1)
|
||||
/ Sdio::FN2_BLOCK * Sdio::FN2_BLOCK / 4];
|
||||
static uint8_t g_ctrlResp[CTRL_MAX];
|
||||
static uint32_t g_ctrlRespLen = 0;
|
||||
static uint16_t g_reqId = 0;
|
||||
static uint16_t g_waitId = 0; // 0 = nobody waiting
|
||||
static int32_t g_ctrlStatus = 0;
|
||||
static uint32_t g_ctrlFlags = 0;
|
||||
static std::atomic<bool> g_ctrlDone{false};
|
||||
|
||||
static inline uint16_t Be16(const uint8_t* p) { return (uint16_t)(p[0] << 8 | p[1]); }
|
||||
static inline uint32_t Be32(const uint8_t* p) {
|
||||
return (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | p[3];
|
||||
}
|
||||
static inline void Le32(uint8_t* p, uint32_t v) { memcpy(p, &v, 4); }
|
||||
|
||||
void BrcmfProtoReset() {
|
||||
g_brcmf.TxSeq = 0;
|
||||
g_brcmf.TxMax = 1;
|
||||
g_brcmf.RxPending = false;
|
||||
g_txqLock.Acquire();
|
||||
g_txHead = g_txTail = 0;
|
||||
g_txqLock.Release();
|
||||
g_respLock.Acquire();
|
||||
g_waitId = 0;
|
||||
g_respLock.Release();
|
||||
g_busErrors = 0;
|
||||
g_glomWarned = false;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Bus transfers (bus lock held)
|
||||
// =========================================================================
|
||||
|
||||
static bool CanSend() {
|
||||
uint8_t room = (uint8_t)(g_brcmf.TxMax - g_brcmf.TxSeq);
|
||||
return room != 0 && (room & 0x80) == 0;
|
||||
}
|
||||
|
||||
static void NoteBusError() {
|
||||
if (++g_busErrors == BUS_ERROR_LIMIT) BrcmfOnFirmwareFailure("SDIO bus errors");
|
||||
}
|
||||
|
||||
static bool SendFrameLocked(uint8_t* frame, uint32_t busLen) {
|
||||
frame[4] = g_brcmf.TxSeq;
|
||||
if (!BrcmfBpSelectWindow(SI_ENUM_BASE) ||
|
||||
!Sdio::WriteBlock(Sdio::FN2, F2_ADDR, false, frame, busLen)) {
|
||||
NoteBusError();
|
||||
return false;
|
||||
}
|
||||
g_busErrors = 0;
|
||||
g_brcmf.TxSeq++;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Bytes on the bus for a frame: dwords, and whole blocks once it is
|
||||
// longer than one (the device takes the length from the header).
|
||||
static uint32_t BusLength(uint32_t len) {
|
||||
if (len > Sdio::FN2_BLOCK)
|
||||
return (len + Sdio::FN2_BLOCK - 1) / Sdio::FN2_BLOCK * Sdio::FN2_BLOCK;
|
||||
return (len + 3) & ~3u;
|
||||
}
|
||||
|
||||
static void PutSdpcm(uint8_t* f, uint32_t len, uint8_t channel) {
|
||||
uint16_t l = (uint16_t)len;
|
||||
f[0] = (uint8_t)l; f[1] = (uint8_t)(l >> 8);
|
||||
f[2] = (uint8_t)~l; f[3] = (uint8_t)(~l >> 8);
|
||||
f[4] = 0; // seq, filled in at send time
|
||||
f[5] = channel;
|
||||
f[6] = 0;
|
||||
f[7] = SDPCM_HDRLEN;
|
||||
f[8] = f[9] = f[10] = f[11] = 0;
|
||||
}
|
||||
|
||||
// Abandon a frame half read: abort function 2, have the device drop the
|
||||
// rest of it, and wait for its read FIFO to empty (Linux rxfail).
|
||||
static void RxFailLocked() {
|
||||
Sdio::Abort(Sdio::FN2);
|
||||
BrcmfF1Write8(F1_FRAMECTRL, SFC_RF_TERM);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
uint8_t hi = 0, lo = 0;
|
||||
if (!BrcmfF1Read8(F1_RFRAMEBCHI, hi) || !BrcmfF1Read8(F1_RFRAMEBCLO, lo)) break;
|
||||
if (hi == 0 && lo == 0) break;
|
||||
}
|
||||
NoteBusError();
|
||||
}
|
||||
|
||||
// Read one frame into g_rxBuf. 1 = a frame, 0 = none waiting, -1 = error.
|
||||
static int ReadFrameLocked(uint32_t& len) {
|
||||
uint8_t* buf = (uint8_t*)g_rxBuf;
|
||||
if (!BrcmfBpSelectWindow(SI_ENUM_BASE) ||
|
||||
!Sdio::ReadBlock(Sdio::FN2, F2_ADDR, false, buf, 4)) {
|
||||
RxFailLocked();
|
||||
return -1;
|
||||
}
|
||||
uint16_t l = (uint16_t)(buf[0] | buf[1] << 8);
|
||||
uint16_t check = (uint16_t)(buf[2] | buf[3] << 8);
|
||||
if ((l | check) == 0) return 0;
|
||||
if ((uint16_t)(l ^ check) != 0xFFFF || l < SDPCM_HDRLEN || l > RX_MAX) {
|
||||
KernelLogStream(WARNING, "WiFi") << "Bad SDPCM header (len 0x" << base::hex
|
||||
<< (uint64_t)l << ", check 0x" << (uint64_t)check << base::dec << ")";
|
||||
RxFailLocked();
|
||||
return -1;
|
||||
}
|
||||
uint32_t rest = ((l + 3u) & ~3u) - 4;
|
||||
if (rest && !Sdio::ReadBlock(Sdio::FN2, F2_ADDR, false, buf + 4, rest)) {
|
||||
RxFailLocked();
|
||||
return -1;
|
||||
}
|
||||
g_busErrors = 0;
|
||||
len = l;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void FlushTxLocked() {
|
||||
for (;;) {
|
||||
g_txqLock.Acquire();
|
||||
bool empty = g_txHead == g_txTail;
|
||||
g_txqLock.Release();
|
||||
if (empty || !CanSend()) return;
|
||||
|
||||
TxSlot& slot = g_txq[g_txTail % TXQ_DEPTH];
|
||||
if (SendFrameLocked((uint8_t*)slot.Data, slot.Len)) g_brcmf.TxPackets++;
|
||||
|
||||
g_txqLock.Acquire();
|
||||
g_txTail++;
|
||||
g_txqLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Receive dispatch (bus lock released)
|
||||
// =========================================================================
|
||||
|
||||
static void HandleControl(const uint8_t* p, uint32_t len) {
|
||||
if (len < CDC_HDRLEN) return;
|
||||
uint32_t cmd, dlen, flags, status;
|
||||
memcpy(&cmd, p, 4);
|
||||
memcpy(&dlen, p + 4, 4);
|
||||
memcpy(&flags, p + 8, 4);
|
||||
memcpy(&status, p + 12, 4);
|
||||
(void)cmd;
|
||||
(void)dlen;
|
||||
uint16_t id = (uint16_t)(flags >> 16);
|
||||
|
||||
g_respLock.Acquire();
|
||||
if (g_waitId != 0 && id == g_waitId) {
|
||||
uint32_t n = len - CDC_HDRLEN;
|
||||
if (n > CTRL_MAX) n = CTRL_MAX;
|
||||
memcpy(g_ctrlResp, p + CDC_HDRLEN, n);
|
||||
g_ctrlRespLen = n;
|
||||
g_ctrlStatus = (int32_t)status;
|
||||
g_ctrlFlags = flags;
|
||||
g_waitId = 0;
|
||||
g_ctrlDone.store(true, std::memory_order_release);
|
||||
}
|
||||
g_respLock.Release();
|
||||
}
|
||||
|
||||
static void HandleEvent(const uint8_t* eth, uint32_t len) {
|
||||
// Ethernet (14), bcm_ethhdr (10): subtype 0x8001, length, version 1,
|
||||
// OUI 00:10:18, user subtype 1; then the 48-byte event message.
|
||||
constexpr uint32_t MSG = 24;
|
||||
constexpr uint32_t DATA = MSG + 48;
|
||||
if (len < DATA) return;
|
||||
const uint8_t* h = eth + 14;
|
||||
if (Be16(h) != 0x8001 || h[5] != 0x00 || h[6] != 0x10 || h[7] != 0x18 || Be16(h + 8) != 1)
|
||||
return;
|
||||
|
||||
const uint8_t* m = eth + MSG;
|
||||
uint16_t flags = Be16(m + 2);
|
||||
uint32_t type = Be32(m + 4);
|
||||
uint32_t status = Be32(m + 8);
|
||||
uint32_t reason = Be32(m + 12);
|
||||
uint32_t dataLen = Be32(m + 20);
|
||||
const uint8_t* addr = m + 24;
|
||||
uint8_t ifidx = m[46];
|
||||
if (ifidx != 0) return;
|
||||
if (dataLen > len - DATA) dataLen = len - DATA;
|
||||
BrcmfOnEvent(type, status, reason, flags, addr, eth + DATA, dataLen);
|
||||
}
|
||||
|
||||
static void Dispatch(const uint8_t* f, uint32_t len) {
|
||||
const uint8_t* sw = f + 4;
|
||||
uint8_t channel = sw[1] & 0x0F;
|
||||
uint8_t offset = sw[3];
|
||||
|
||||
// Credit: the device's max seq, sanity checked as Linux does.
|
||||
uint8_t max = sw[5];
|
||||
if ((uint8_t)(max - g_brcmf.TxSeq) > 0x40) max = (uint8_t)(g_brcmf.TxSeq + 2);
|
||||
g_brcmf.TxMax = max;
|
||||
|
||||
if ((sw[1] & GLOM_DESC) || channel == CHAN_GLOM) {
|
||||
if (!g_glomWarned) {
|
||||
g_glomWarned = true;
|
||||
KernelLogStream(WARNING, "WiFi") << "Dropping a glommed superframe (rx glom is off)";
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (offset < SDPCM_HDRLEN || offset > len) return;
|
||||
const uint8_t* p = f + offset;
|
||||
uint32_t plen = len - offset;
|
||||
if (plen == 0) return; // credit update only
|
||||
|
||||
if (channel == CHAN_CONTROL) {
|
||||
HandleControl(p, plen);
|
||||
return;
|
||||
}
|
||||
if (channel != CHAN_EVENT && channel != CHAN_DATA) return;
|
||||
|
||||
if (plen < BDC_HDRLEN || (p[0] >> 4) != BDC_VERSION) return;
|
||||
if ((p[2] & 0x0F) != 0) return; // another interface
|
||||
uint32_t skip = BDC_HDRLEN + (uint32_t)p[3] * 4;
|
||||
if (skip + 14 > plen) return;
|
||||
const uint8_t* eth = p + skip;
|
||||
uint32_t elen = plen - skip;
|
||||
|
||||
if (Be16(eth + 12) == ETHERTYPE_BRCM) {
|
||||
HandleEvent(eth, elen);
|
||||
return;
|
||||
}
|
||||
if (channel == CHAN_DATA) {
|
||||
g_brcmf.RxPackets++;
|
||||
BrcmfOnRxEthernet(eth, elen);
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// The pump
|
||||
// =========================================================================
|
||||
|
||||
bool BrcmfTxPending() {
|
||||
g_txqLock.Acquire();
|
||||
bool pending = g_txHead != g_txTail;
|
||||
g_txqLock.Release();
|
||||
return pending;
|
||||
}
|
||||
|
||||
bool BrcmfPollDue(uint64_t now, uint64_t intervalMs) {
|
||||
if (g_brcmf.State != BrcmfState::Running) return false;
|
||||
if (g_brcmf.RxPending || BrcmfTxPending()) return true;
|
||||
if (Sdio::CardInterruptPending()) return true;
|
||||
uint64_t last = g_brcmf.LastPollMs;
|
||||
return now < last || now - last >= intervalMs;
|
||||
}
|
||||
|
||||
void BrcmfPoll() {
|
||||
if (g_brcmf.State != BrcmfState::Running) return;
|
||||
if (__atomic_test_and_set(&g_pumping, __ATOMIC_ACQUIRE)) return;
|
||||
struct Guard { ~Guard() { __atomic_clear(&g_pumping, __ATOMIC_RELEASE); } } guard;
|
||||
|
||||
const uint32_t sdio = g_brcmf.SdioCoreBase;
|
||||
for (int round = 0; round < 8 && g_brcmf.State == BrcmfState::Running; round++) {
|
||||
bool frames = g_brcmf.RxPending;
|
||||
uint32_t intStatus = 0;
|
||||
bool halted = false;
|
||||
|
||||
g_brcmf.BusLock.Acquire();
|
||||
if (BrcmfBpRead32(sdio + SD_INTSTATUS, intStatus)) {
|
||||
intStatus &= g_brcmf.HostIntMask;
|
||||
if (intStatus) BrcmfBpWrite32(sdio + SD_INTSTATUS, intStatus);
|
||||
if (intStatus & I_HMB_HOST_INT) {
|
||||
uint32_t mbox = 0;
|
||||
BrcmfBpRead32(sdio + SD_TOHOSTMAILBOXDATA, mbox);
|
||||
BrcmfBpWrite32(sdio + SD_TOSBMAILBOX, SMB_INT_ACK);
|
||||
halted = (mbox & HMB_DATA_FWHALT) != 0;
|
||||
}
|
||||
if (intStatus & I_HMB_FRAME_IND) frames = true;
|
||||
} else {
|
||||
NoteBusError();
|
||||
}
|
||||
g_brcmf.BusLock.Release();
|
||||
g_brcmf.LastPollMs = Timekeeping::GetMilliseconds();
|
||||
|
||||
if (halted) {
|
||||
BrcmfOnFirmwareFailure("firmware halted");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int n = 0; frames && n < 32; n++) {
|
||||
uint32_t len = 0;
|
||||
g_brcmf.BusLock.Acquire();
|
||||
int r = ReadFrameLocked(len);
|
||||
g_brcmf.BusLock.Release();
|
||||
if (r <= 0) {
|
||||
frames = false;
|
||||
break;
|
||||
}
|
||||
Dispatch((const uint8_t*)g_rxBuf, len);
|
||||
}
|
||||
g_brcmf.RxPending = frames;
|
||||
|
||||
if (BrcmfTxPending()) {
|
||||
g_brcmf.BusLock.Acquire();
|
||||
FlushTxLocked();
|
||||
g_brcmf.BusLock.Release();
|
||||
}
|
||||
|
||||
if (!frames && intStatus == 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Transmit
|
||||
// =========================================================================
|
||||
|
||||
bool BrcmfTxEthernet(const uint8_t* frame, uint32_t len) {
|
||||
if (g_brcmf.State != BrcmfState::Running || len < 14 || len > 1518) return false;
|
||||
uint32_t total = SDPCM_HDRLEN + BDC_HDRLEN + len;
|
||||
uint32_t busLen = BusLength(total);
|
||||
|
||||
g_txqLock.Acquire();
|
||||
if (g_txHead - g_txTail >= TXQ_DEPTH) {
|
||||
g_txqLock.Release();
|
||||
return false;
|
||||
}
|
||||
TxSlot& slot = g_txq[g_txHead % TXQ_DEPTH];
|
||||
uint8_t* f = (uint8_t*)slot.Data;
|
||||
PutSdpcm(f, total, CHAN_DATA);
|
||||
f[12] = BDC_VERSION << 4;
|
||||
f[13] = 0; // priority: best effort
|
||||
f[14] = 0; // interface 0
|
||||
f[15] = 0; // no signalling TLVs
|
||||
memcpy(f + 16, frame, len);
|
||||
memset(f + total, 0, busLen - total);
|
||||
slot.Len = busLen;
|
||||
g_txHead++;
|
||||
g_txqLock.Release();
|
||||
|
||||
// Never wait for the bus here: the caller may be the network stack
|
||||
// inside a frame the pump is delivering, or a process that just
|
||||
// preempted the bus lock's holder. Whoever holds it, or the next
|
||||
// pump, sends what is queued.
|
||||
if (g_brcmf.BusLock.TryAcquire()) {
|
||||
FlushTxLocked();
|
||||
g_brcmf.BusLock.Release();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Control
|
||||
// =========================================================================
|
||||
|
||||
bool BrcmfIoctl(uint32_t cmd, bool set, void* buf, uint32_t len, int32_t* fwStatus) {
|
||||
if (fwStatus) *fwStatus = 0;
|
||||
if (g_brcmf.State != BrcmfState::Running || len > CTRL_MAX) return false;
|
||||
|
||||
g_ctrlLock.Acquire();
|
||||
|
||||
uint16_t id = ++g_reqId;
|
||||
if (id == 0) id = ++g_reqId;
|
||||
uint8_t* f = (uint8_t*)g_ctrlTx;
|
||||
uint32_t total = SDPCM_HDRLEN + CDC_HDRLEN + len;
|
||||
PutSdpcm(f, total, CHAN_CONTROL);
|
||||
uint8_t* cdc = f + SDPCM_HDRLEN;
|
||||
Le32(cdc, cmd);
|
||||
Le32(cdc + 4, len);
|
||||
Le32(cdc + 8, (uint32_t)id << 16 | (set ? CDCF_SET : 0));
|
||||
Le32(cdc + 12, 0);
|
||||
if (len) memcpy(cdc + CDC_HDRLEN, buf, len);
|
||||
uint32_t busLen = BusLength(total);
|
||||
memset(f + total, 0, busLen - total);
|
||||
|
||||
g_respLock.Acquire();
|
||||
g_waitId = id;
|
||||
g_ctrlDone.store(false, std::memory_order_relaxed);
|
||||
g_respLock.Release();
|
||||
|
||||
// Wait for transmit credit, pumping meanwhile: credit only ever
|
||||
// arrives in the header of a received frame.
|
||||
bool sent = false;
|
||||
uint64_t start = Timekeeping::GetMilliseconds();
|
||||
while (!sent && g_brcmf.State == BrcmfState::Running) {
|
||||
g_brcmf.BusLock.Acquire();
|
||||
if (CanSend()) {
|
||||
sent = SendFrameLocked(f, busLen);
|
||||
if (!sent) {
|
||||
g_brcmf.BusLock.Release();
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_brcmf.BusLock.Release();
|
||||
if (sent) break;
|
||||
if (Timekeeping::GetMilliseconds() - start > CREDIT_TIMEOUT_MS) break;
|
||||
BrcmfPoll();
|
||||
Timekeeping::BusyWaitUs(100);
|
||||
}
|
||||
|
||||
bool done = false;
|
||||
if (sent) {
|
||||
start = Timekeeping::GetMilliseconds();
|
||||
while (g_brcmf.State == BrcmfState::Running) {
|
||||
if (g_ctrlDone.load(std::memory_order_acquire)) { done = true; break; }
|
||||
if (Timekeeping::GetMilliseconds() - start > IOCTL_TIMEOUT_MS) break;
|
||||
BrcmfPoll();
|
||||
if (g_ctrlDone.load(std::memory_order_acquire)) { done = true; break; }
|
||||
Timekeeping::BusyWaitUs(50);
|
||||
}
|
||||
}
|
||||
|
||||
g_respLock.Acquire();
|
||||
g_waitId = 0;
|
||||
bool ok = false;
|
||||
if (done) {
|
||||
int32_t status = g_ctrlStatus;
|
||||
if (fwStatus) *fwStatus = status;
|
||||
ok = !(g_ctrlFlags & CDCF_ERROR) && status >= 0;
|
||||
if (!set && len) {
|
||||
uint32_t n = g_ctrlRespLen < len ? g_ctrlRespLen : len;
|
||||
memcpy(buf, g_ctrlResp, n);
|
||||
}
|
||||
}
|
||||
g_respLock.Release();
|
||||
|
||||
if (done) {
|
||||
if (g_brcmf.CmdFailBucket) g_brcmf.CmdFailBucket--;
|
||||
} else if (g_brcmf.State == BrcmfState::Running) {
|
||||
KernelLogStream(WARNING, "WiFi") << "Firmware command " << (uint64_t)cmd
|
||||
<< (sent ? " went unanswered" : " could not be sent (no transmit credit)");
|
||||
if (++g_brcmf.CmdFailBucket >= FAIL_BUCKET_LIMIT)
|
||||
BrcmfOnFirmwareFailure("firmware stopped answering commands");
|
||||
}
|
||||
|
||||
g_ctrlLock.Release();
|
||||
return ok;
|
||||
}
|
||||
|
||||
static constexpr uint32_t WLC_GET_VAR = 262;
|
||||
static constexpr uint32_t WLC_SET_VAR = 263;
|
||||
|
||||
static kcp::Mutex g_iovarLock;
|
||||
static uint8_t g_iovarBuf[CTRL_MAX];
|
||||
|
||||
static uint32_t NameLen(const char* name) {
|
||||
uint32_t n = 0;
|
||||
while (name[n]) n++;
|
||||
return n + 1;
|
||||
}
|
||||
|
||||
bool BrcmfIovarSet(const char* name, const void* data, uint32_t len, bool quiet) {
|
||||
uint32_t nameLen = NameLen(name);
|
||||
if (nameLen + len > CTRL_MAX) return false;
|
||||
g_iovarLock.Acquire();
|
||||
memcpy(g_iovarBuf, name, nameLen);
|
||||
if (len) memcpy(g_iovarBuf + nameLen, data, len);
|
||||
int32_t status = 0;
|
||||
bool ok = BrcmfIoctl(WLC_SET_VAR, true, g_iovarBuf, nameLen + len, &status);
|
||||
g_iovarLock.Release();
|
||||
if (!ok && !quiet)
|
||||
KernelLogStream(WARNING, "WiFi") << "Setting \"" << name << "\" failed (status "
|
||||
<< (int)status << ")";
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool BrcmfIovarGet(const char* name, void* out, uint32_t len, bool quiet) {
|
||||
uint32_t nameLen = NameLen(name);
|
||||
uint32_t bufLen = nameLen > len ? nameLen : len;
|
||||
if (bufLen > CTRL_MAX) return false;
|
||||
g_iovarLock.Acquire();
|
||||
memset(g_iovarBuf, 0, bufLen);
|
||||
memcpy(g_iovarBuf, name, nameLen);
|
||||
int32_t status = 0;
|
||||
bool ok = BrcmfIoctl(WLC_GET_VAR, false, g_iovarBuf, bufLen, &status);
|
||||
if (ok) memcpy(out, g_iovarBuf, len);
|
||||
g_iovarLock.Release();
|
||||
if (!ok && !quiet)
|
||||
KernelLogStream(WARNING, "WiFi") << "Reading \"" << name << "\" failed (status "
|
||||
<< (int)status << ")";
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool BrcmfIovarSetInt(const char* name, uint32_t value, bool quiet) {
|
||||
return BrcmfIovarSet(name, &value, 4, quiet);
|
||||
}
|
||||
|
||||
bool BrcmfIovarGetInt(const char* name, uint32_t& value, bool quiet) {
|
||||
return BrcmfIovarGet(name, &value, 4, quiet);
|
||||
}
|
||||
|
||||
bool BrcmfSetInt(uint32_t cmd, uint32_t value) {
|
||||
return BrcmfIoctl(cmd, true, &value, 4);
|
||||
}
|
||||
|
||||
bool BrcmfGetInt(uint32_t cmd, uint32_t& value) {
|
||||
uint32_t v = 0;
|
||||
if (!BrcmfIoctl(cmd, false, &v, 4)) return false;
|
||||
value = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
* BrcmfSdio.cpp
|
||||
* SDIO host on the Raspberry Pi 4/400's Arasan SD controller
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "BrcmfSdio.hpp"
|
||||
#include <Arch/Cpu.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <Timekeeping/Timer.hpp>
|
||||
|
||||
// Register access follows Drivers/Storage/Sdhci: the Broadcom/Arasan
|
||||
// controller only takes 32-bit accesses, so 8/16-bit fields are written by
|
||||
// read-modify-write of their dword and the transfer-mode/command pair in
|
||||
// the one write that issues the command. Linux (sdhci-iproc, bcm2835 data)
|
||||
// also waits four SD clocks after every write while the bus runs at 400 kHz
|
||||
// or below; so does this.
|
||||
//
|
||||
// SDIO commands (SDIO Simplified Specification 3.00):
|
||||
// CMD5 IO_SEND_OP_COND, R4: OCR [23:0], ready [31]
|
||||
// CMD52 IO_RW_DIRECT, R5: flags [15:8], data [7:0]
|
||||
// arg: R/W [31], function [30:28], RAW [27], address [25:9], data [7:0]
|
||||
// CMD53 IO_RW_EXTENDED, R5
|
||||
// arg: R/W [31], function [30:28], block mode [27], incrementing
|
||||
// address [26], address [25:9], count [8:0] (0 = 512)
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Drivers::Net::Wifi::Sdio {
|
||||
|
||||
static constexpr uint32_t REG_BLOCK_SIZE_COUNT = 0x04;
|
||||
static constexpr uint32_t REG_ARGUMENT = 0x08;
|
||||
static constexpr uint32_t REG_XFER_MODE_CMD = 0x0C;
|
||||
static constexpr uint32_t REG_RESPONSE = 0x10;
|
||||
static constexpr uint32_t REG_BUFFER_DATA = 0x20;
|
||||
static constexpr uint32_t REG_PRESENT_STATE = 0x24;
|
||||
static constexpr uint32_t REG_HOST_CONTROL = 0x28;
|
||||
static constexpr uint32_t REG_CLOCK_CONTROL = 0x2C;
|
||||
static constexpr uint32_t REG_INT_STATUS = 0x30;
|
||||
static constexpr uint32_t REG_INT_ENABLE = 0x34;
|
||||
static constexpr uint32_t REG_INT_SIGNAL = 0x38;
|
||||
static constexpr uint32_t REG_CAPABILITIES = 0x40;
|
||||
static constexpr uint32_t REG_VERSION = 0xFC;
|
||||
|
||||
static constexpr uint32_t PS_CMD_INHIBIT = 1u << 0;
|
||||
static constexpr uint32_t PS_DAT_INHIBIT = 1u << 1;
|
||||
|
||||
static constexpr uint32_t INT_CMD_COMPLETE = 1u << 0;
|
||||
static constexpr uint32_t INT_XFER_COMPLETE = 1u << 1;
|
||||
static constexpr uint32_t INT_WRITE_READY = 1u << 4;
|
||||
static constexpr uint32_t INT_READ_READY = 1u << 5;
|
||||
static constexpr uint32_t INT_CARD = 1u << 8;
|
||||
static constexpr uint32_t INT_ERROR = 1u << 15;
|
||||
static constexpr uint32_t INT_ERROR_MASK = 0xFFFF0000u;
|
||||
// Status enables: the completions, both buffer-ready bits, the card
|
||||
// interrupt and every error. Card insert/remove stay off: nothing is
|
||||
// ever inserted, and the detect line is not wired to the radio.
|
||||
static constexpr uint32_t INT_ENABLES = 0x03FF0000u | INT_CMD_COMPLETE | INT_XFER_COMPLETE
|
||||
| INT_WRITE_READY | INT_READ_READY | INT_CARD;
|
||||
|
||||
static constexpr uint32_t HC_4BIT = 1u << 1;
|
||||
static constexpr uint32_t PWR_ON = 1u << 8;
|
||||
static constexpr uint32_t PWR_330 = 7u << 9;
|
||||
|
||||
static constexpr uint32_t CLK_INT_EN = 1u << 0;
|
||||
static constexpr uint32_t CLK_INT_STABLE = 1u << 1;
|
||||
static constexpr uint32_t CLK_SD_EN = 1u << 2;
|
||||
static constexpr uint32_t TIMEOUT_MAX = 0xEu << 16;
|
||||
|
||||
static constexpr uint32_t RESET_ALL = 1u << 24;
|
||||
static constexpr uint32_t RESET_CMD = 1u << 25;
|
||||
static constexpr uint32_t RESET_DAT = 1u << 26;
|
||||
|
||||
static constexpr uint32_t XM_BLOCK_COUNT_EN = 1u << 1;
|
||||
static constexpr uint32_t XM_READ = 1u << 4;
|
||||
static constexpr uint32_t XM_MULTI_BLOCK = 1u << 5;
|
||||
|
||||
enum Response : uint32_t {
|
||||
RespNone = 0,
|
||||
Resp48 = 2 | 3u << 3, // R1, R5, R6: CRC and index checked
|
||||
Resp48NoCrc = 2, // R4
|
||||
};
|
||||
static constexpr uint32_t CMD_DATA = 1u << 5;
|
||||
|
||||
// R5 flags: COM_CRC_ERROR, ILLEGAL_COMMAND, ERROR, FUNCTION_NUMBER,
|
||||
// OUT_OF_RANGE.
|
||||
static constexpr uint32_t R5_ERRORS = 0xCB00;
|
||||
|
||||
// CCCR
|
||||
static constexpr uint32_t CCCR_IOEN = 0x02;
|
||||
static constexpr uint32_t CCCR_IORDY = 0x03;
|
||||
static constexpr uint32_t CCCR_INTEN = 0x04;
|
||||
static constexpr uint32_t CCCR_ABORT = 0x06;
|
||||
static constexpr uint32_t CCCR_BUSIF = 0x07;
|
||||
static constexpr uint32_t BUSIF_4BIT = 0x02;
|
||||
static constexpr uint32_t BUSIF_CD_DIS = 0x80;
|
||||
static constexpr uint32_t FBR_BLKSIZE = 0x10; // in function n's FBR at n * 0x100
|
||||
|
||||
static constexpr uint64_t CommandTimeoutMs = 100;
|
||||
static constexpr uint64_t DataTimeoutMs = 500;
|
||||
// CMD53's count field is nine bits.
|
||||
static constexpr uint32_t MaxBlocks = 511;
|
||||
|
||||
// The legacy EMMC clock the firmware sets up; only used when neither the
|
||||
// mailbox nor the capabilities name one. Assuming it high errs towards a
|
||||
// slower bus, never a faster one.
|
||||
static constexpr uint32_t FallbackBaseClockHz = 250000000;
|
||||
|
||||
static volatile uint8_t* g_regs = nullptr;
|
||||
static uint32_t g_version = 0;
|
||||
static uint32_t g_baseClockHz = 0;
|
||||
static uint32_t g_clockHz = 0;
|
||||
static uint32_t g_rca = 0;
|
||||
static bool g_initialized = false;
|
||||
|
||||
static inline uint32_t Read(uint32_t reg) {
|
||||
return *(volatile uint32_t*)(g_regs + reg);
|
||||
}
|
||||
|
||||
static inline void Write(uint32_t reg, uint32_t value) {
|
||||
*(volatile uint32_t*)(g_regs + reg) = value;
|
||||
if (g_clockHz <= 400000) Timekeeping::BusyWaitUs(10);
|
||||
}
|
||||
|
||||
static bool WaitClear(uint32_t reg, uint32_t mask, uint64_t timeoutMs) {
|
||||
uint64_t start = Timekeeping::GetMilliseconds();
|
||||
while (Read(reg) & mask) {
|
||||
if (Timekeeping::GetMilliseconds() - start > timeoutMs) return false;
|
||||
Arch::Pause();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool Reset(uint32_t which) {
|
||||
Write(REG_CLOCK_CONTROL, Read(REG_CLOCK_CONTROL) | which);
|
||||
return WaitClear(REG_CLOCK_CONTROL, which, 100);
|
||||
}
|
||||
|
||||
static bool WaitStatus(uint32_t bits, uint64_t timeoutMs) {
|
||||
uint64_t start = Timekeeping::GetMilliseconds();
|
||||
for (;;) {
|
||||
uint32_t status = Read(REG_INT_STATUS);
|
||||
if (status & (INT_ERROR | INT_ERROR_MASK)) {
|
||||
Write(REG_INT_STATUS, status & ~INT_CARD);
|
||||
Reset(RESET_CMD | RESET_DAT);
|
||||
return false;
|
||||
}
|
||||
if (status & bits) {
|
||||
Write(REG_INT_STATUS, status & bits);
|
||||
return true;
|
||||
}
|
||||
if (Timekeeping::GetMilliseconds() - start > timeoutMs) {
|
||||
Reset(RESET_CMD | RESET_DAT);
|
||||
return false;
|
||||
}
|
||||
Arch::Pause();
|
||||
}
|
||||
}
|
||||
|
||||
static bool SendCommand(uint32_t index, uint32_t argument, uint32_t response,
|
||||
uint32_t transferMode = 0, bool data = false) {
|
||||
uint32_t inhibit = PS_CMD_INHIBIT | (data ? PS_DAT_INHIBIT : 0);
|
||||
if (!WaitClear(REG_PRESENT_STATE, inhibit, CommandTimeoutMs)) {
|
||||
Reset(RESET_CMD | RESET_DAT);
|
||||
return false;
|
||||
}
|
||||
|
||||
Write(REG_INT_STATUS, ~INT_CARD);
|
||||
Write(REG_ARGUMENT, argument);
|
||||
uint32_t command = index << 8 | response | (data ? CMD_DATA : 0);
|
||||
Write(REG_XFER_MODE_CMD, command << 16 | transferMode);
|
||||
return WaitStatus(INT_CMD_COMPLETE, CommandTimeoutMs);
|
||||
}
|
||||
|
||||
static bool SetClock(uint32_t hz) {
|
||||
Write(REG_CLOCK_CONTROL, Read(REG_CLOCK_CONTROL) & ~0xFFFFu);
|
||||
|
||||
uint32_t divider = 0; // base / (2 * divider); 0 = undivided
|
||||
if (hz < g_baseClockHz) {
|
||||
divider = (g_baseClockHz + 2 * hz - 1) / (2 * hz);
|
||||
uint32_t max = g_version >= 2 ? 0x3FF : 0x80;
|
||||
if (divider > max) divider = max;
|
||||
if (g_version < 2) {
|
||||
uint32_t p = 1;
|
||||
while (p < divider) p <<= 1;
|
||||
divider = p;
|
||||
}
|
||||
}
|
||||
uint32_t actual = divider ? g_baseClockHz / (2 * divider) : g_baseClockHz;
|
||||
|
||||
uint32_t field = (divider & 0xFF) << 8 | ((divider >> 8) & 0x3) << 6;
|
||||
Write(REG_CLOCK_CONTROL, (Read(REG_CLOCK_CONTROL) & ~0xFFFFu) | field | CLK_INT_EN);
|
||||
|
||||
uint64_t start = Timekeeping::GetMilliseconds();
|
||||
while (!(Read(REG_CLOCK_CONTROL) & CLK_INT_STABLE)) {
|
||||
if (Timekeeping::GetMilliseconds() - start > 20) return false;
|
||||
Arch::Pause();
|
||||
}
|
||||
Write(REG_CLOCK_CONTROL, Read(REG_CLOCK_CONTROL) | CLK_SD_EN);
|
||||
g_clockHz = actual;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool Cmd52(bool write, uint32_t fn, uint32_t addr, uint8_t in, uint8_t* out) {
|
||||
uint32_t arg = (write ? 1u << 31 : 0) | (fn & 7) << 28 | (addr & 0x1FFFF) << 9 | in;
|
||||
if (!SendCommand(52, arg, Resp48)) return false;
|
||||
uint32_t r = Read(REG_RESPONSE);
|
||||
if (r & R5_ERRORS) return false;
|
||||
if (out) *out = (uint8_t)r;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Read8(uint32_t fn, uint32_t addr, uint8_t& value) {
|
||||
return Cmd52(false, fn, addr, 0, &value);
|
||||
}
|
||||
|
||||
bool Write8(uint32_t fn, uint32_t addr, uint8_t value) {
|
||||
return Cmd52(true, fn, addr, value, nullptr);
|
||||
}
|
||||
|
||||
// One CMD53: `count` blocks of `blockSize` bytes, or (byte mode) one run
|
||||
// of `blockSize` bytes. `words` is 4-byte aligned.
|
||||
static bool Cmd53(bool write, uint32_t fn, uint32_t addr, bool incr, uint32_t* words,
|
||||
bool blockMode, uint32_t count, uint32_t blockSize) {
|
||||
uint32_t arg = (write ? 1u << 31 : 0) | (fn & 7) << 28 | (blockMode ? 1u << 27 : 0)
|
||||
| (incr ? 1u << 26 : 0) | (addr & 0x1FFFF) << 9
|
||||
| ((blockMode ? count : blockSize) & 0x1FF);
|
||||
uint32_t blocks = blockMode ? count : 1;
|
||||
Write(REG_BLOCK_SIZE_COUNT, blocks << 16 | blockSize);
|
||||
|
||||
uint32_t mode = write ? 0 : XM_READ;
|
||||
if (blockMode) mode |= XM_BLOCK_COUNT_EN | (blocks > 1 ? XM_MULTI_BLOCK : 0);
|
||||
if (!SendCommand(53, arg, Resp48, mode, true)) return false;
|
||||
if (Read(REG_RESPONSE) & R5_ERRORS) {
|
||||
Abort(fn);
|
||||
Reset(RESET_CMD | RESET_DAT);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t perBlock = (blockSize + 3) / 4;
|
||||
for (uint32_t b = 0; b < blocks; b++) {
|
||||
if (!WaitStatus(write ? INT_WRITE_READY : INT_READ_READY, DataTimeoutMs)) {
|
||||
Abort(fn);
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < perBlock; i++) {
|
||||
if (write) *(volatile uint32_t*)(g_regs + REG_BUFFER_DATA) = *words++;
|
||||
else *words++ = Read(REG_BUFFER_DATA);
|
||||
}
|
||||
}
|
||||
if (!WaitStatus(INT_XFER_COMPLETE, DataTimeoutMs)) {
|
||||
Abort(fn);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool Transfer(bool write, uint32_t fn, uint32_t addr, bool incr,
|
||||
uint8_t* buffer, uint32_t len) {
|
||||
if (!g_initialized || (len & 3) != 0) return false;
|
||||
uint32_t blockSize = fn == FN2 ? FN2_BLOCK : FN1_BLOCK;
|
||||
|
||||
// The data port moves dwords; callers' buffers are only byte aligned
|
||||
// in general.
|
||||
uint32_t bounce[FN2_BLOCK / 4];
|
||||
while (len > 0) {
|
||||
bool blockMode = len >= blockSize;
|
||||
uint32_t count = blockMode ? len / blockSize : 0;
|
||||
if (count > MaxBlocks) count = MaxBlocks;
|
||||
uint32_t run = blockMode ? count * blockSize : len;
|
||||
if (!blockMode && run > FN2_BLOCK) run = FN2_BLOCK;
|
||||
|
||||
bool aligned = ((uintptr_t)buffer & 3) == 0;
|
||||
if (aligned) {
|
||||
if (!Cmd53(write, fn, addr, incr, (uint32_t*)buffer, blockMode, count,
|
||||
blockMode ? blockSize : run)) return false;
|
||||
} else {
|
||||
// Unaligned: one block (or the byte-mode run) at a time
|
||||
// through the bounce buffer.
|
||||
if (blockMode) { count = 1; run = blockSize; }
|
||||
if (write) memcpy(bounce, buffer, run);
|
||||
if (!Cmd53(write, fn, addr, incr, bounce, blockMode, count,
|
||||
blockMode ? blockSize : run)) return false;
|
||||
if (!write) memcpy(buffer, bounce, run);
|
||||
}
|
||||
buffer += run;
|
||||
if (incr) addr += run;
|
||||
len -= run;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadBlock(uint32_t fn, uint32_t addr, bool incr, uint8_t* buffer, uint32_t len) {
|
||||
return Transfer(false, fn, addr, incr, buffer, len);
|
||||
}
|
||||
|
||||
bool WriteBlock(uint32_t fn, uint32_t addr, bool incr, const uint8_t* buffer, uint32_t len) {
|
||||
return Transfer(true, fn, addr, incr, (uint8_t*)buffer, len);
|
||||
}
|
||||
|
||||
void Abort(uint32_t fn) {
|
||||
Cmd52(true, FN0, CCCR_ABORT, (uint8_t)fn, nullptr);
|
||||
}
|
||||
|
||||
bool EnableFunction(uint32_t fn, bool enable, uint64_t timeoutMs) {
|
||||
uint8_t ioen = 0;
|
||||
if (!Read8(FN0, CCCR_IOEN, ioen)) return false;
|
||||
uint8_t bit = (uint8_t)(1u << fn);
|
||||
ioen = enable ? (ioen | bit) : (ioen & ~bit);
|
||||
if (!Write8(FN0, CCCR_IOEN, ioen)) return false;
|
||||
if (!enable) return true;
|
||||
|
||||
uint64_t start = Timekeeping::GetMilliseconds();
|
||||
for (;;) {
|
||||
uint8_t ready = 0;
|
||||
if (Read8(FN0, CCCR_IORDY, ready) && (ready & bit)) return true;
|
||||
if (Timekeeping::GetMilliseconds() - start > timeoutMs) return false;
|
||||
Timekeeping::BusyWaitUs(500);
|
||||
}
|
||||
}
|
||||
|
||||
bool EnableCardInterrupts(uint8_t mask) {
|
||||
return Write8(FN0, CCCR_INTEN, mask);
|
||||
}
|
||||
|
||||
bool CardInterruptPending() {
|
||||
return g_initialized && (Read(REG_INT_STATUS) & INT_CARD) != 0;
|
||||
}
|
||||
|
||||
bool IsInitialized() { return g_initialized; }
|
||||
|
||||
bool HostInit(uint64_t mmioPhys, uint32_t baseClockHz) {
|
||||
g_initialized = false;
|
||||
if (!g_regs) {
|
||||
Memory::VMM::g_paging->MapMMIO(mmioPhys & ~0xFFFULL, Memory::HHDM(mmioPhys & ~0xFFFULL));
|
||||
g_regs = (volatile uint8_t*)Memory::HHDM(mmioPhys);
|
||||
}
|
||||
g_clockHz = 0;
|
||||
|
||||
if (!Reset(RESET_ALL)) {
|
||||
KernelLogStream(ERROR, "WiFi-SDIO") << "Controller reset timed out";
|
||||
return false;
|
||||
}
|
||||
|
||||
g_version = (Read(REG_VERSION) >> 16) & 0xFF;
|
||||
uint32_t caps = Read(REG_CAPABILITIES);
|
||||
uint32_t capsMhz = g_version >= 2 ? (caps >> 8) & 0xFF : (caps >> 8) & 0x3F;
|
||||
g_baseClockHz = baseClockHz ? baseClockHz
|
||||
: capsMhz ? capsMhz * 1000000 : FallbackBaseClockHz;
|
||||
|
||||
// The radio's 3.3 V supply is not switched by the controller, but the
|
||||
// bus power bit gates the controller's own drivers.
|
||||
Write(REG_HOST_CONTROL, (Read(REG_HOST_CONTROL) & ~0xFF00u) | PWR_330 | PWR_ON);
|
||||
Write(REG_INT_ENABLE, INT_ENABLES);
|
||||
Write(REG_INT_SIGNAL, 0);
|
||||
Write(REG_CLOCK_CONTROL, (Read(REG_CLOCK_CONTROL) & ~0xFF0000u) | TIMEOUT_MAX);
|
||||
|
||||
if (!SetClock(400000)) {
|
||||
KernelLogStream(ERROR, "WiFi-SDIO") << "Clock did not stabilize";
|
||||
return false;
|
||||
}
|
||||
// 74 clocks before the first command.
|
||||
Timekeeping::BusyWaitMs(2);
|
||||
|
||||
KernelLogStream(INFO, "WiFi-SDIO") << "Arasan controller at 0x" << base::hex << mmioPhys
|
||||
<< base::dec << ", version " << (uint64_t)(g_version + 1) << ".00, base clock "
|
||||
<< (uint64_t)(g_baseClockHz / 1000000) << " MHz";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CardInit() {
|
||||
// Allow Transfer()/Cmd52 during bring-up; cleared again on failure.
|
||||
g_initialized = true;
|
||||
auto fail = [](const char* what) {
|
||||
KernelLogStream(WARNING, "WiFi-SDIO") << what;
|
||||
g_initialized = false;
|
||||
return false;
|
||||
};
|
||||
|
||||
SendCommand(0, 0, RespNone);
|
||||
Timekeeping::BusyWaitMs(2);
|
||||
|
||||
// CMD5 with no voltage first to read the OCR; the radio may still be
|
||||
// coming out of reset, so give it a few tries.
|
||||
uint32_t ocr = 0;
|
||||
bool answered = false;
|
||||
for (int i = 0; i < 10 && !answered; i++) {
|
||||
answered = SendCommand(5, 0, Resp48NoCrc);
|
||||
if (!answered) Timekeeping::BusyWaitMs(10);
|
||||
}
|
||||
if (!answered) return fail("No SDIO card answers CMD5");
|
||||
ocr = Read(REG_RESPONSE) & 0x00FFFFFF;
|
||||
|
||||
uint32_t want = ocr & 0x00300000; // 3.2-3.4 V
|
||||
if (!want) want = ocr & 0x00FF8000;
|
||||
uint64_t start = Timekeeping::GetMilliseconds();
|
||||
for (;;) {
|
||||
if (!SendCommand(5, want, Resp48NoCrc)) return fail("CMD5 failed");
|
||||
if (Read(REG_RESPONSE) & (1u << 31)) break;
|
||||
if (Timekeeping::GetMilliseconds() - start > 1000) return fail("Card stayed busy (CMD5)");
|
||||
Timekeeping::BusyWaitMs(1);
|
||||
}
|
||||
|
||||
if (!SendCommand(3, 0, Resp48)) return fail("CMD3 failed");
|
||||
g_rca = Read(REG_RESPONSE) >> 16;
|
||||
if (!SendCommand(7, g_rca << 16, Resp48)) return fail("CMD7 failed");
|
||||
|
||||
// 4-bit bus; disconnect the card-detect pull-up on DAT3.
|
||||
uint8_t busif = 0;
|
||||
if (!Read8(FN0, CCCR_BUSIF, busif)) return fail("CCCR read failed");
|
||||
busif = (uint8_t)((busif & ~0x03) | BUSIF_4BIT | BUSIF_CD_DIS);
|
||||
if (!Write8(FN0, CCCR_BUSIF, busif)) return fail("Could not select the 4-bit bus");
|
||||
Write(REG_HOST_CONTROL, Read(REG_HOST_CONTROL) | HC_4BIT);
|
||||
|
||||
if (!SetClock(25000000)) return fail("Clock did not stabilize at 25 MHz");
|
||||
|
||||
// Function block sizes (little endian in the FBR).
|
||||
bool ok = Write8(FN0, 1 * 0x100 + FBR_BLKSIZE, (uint8_t)(FN1_BLOCK & 0xFF))
|
||||
&& Write8(FN0, 1 * 0x100 + FBR_BLKSIZE + 1, (uint8_t)(FN1_BLOCK >> 8))
|
||||
&& Write8(FN0, 2 * 0x100 + FBR_BLKSIZE, (uint8_t)(FN2_BLOCK & 0xFF))
|
||||
&& Write8(FN0, 2 * 0x100 + FBR_BLKSIZE + 1, (uint8_t)(FN2_BLOCK >> 8));
|
||||
if (!ok) return fail("Could not set the function block sizes");
|
||||
|
||||
if (!EnableFunction(FN1, true, 1000)) return fail("Function 1 did not become ready");
|
||||
|
||||
KernelLogStream(OK, "WiFi-SDIO") << "SDIO card ready (RCA 0x" << base::hex << (uint64_t)g_rca
|
||||
<< base::dec << ", OCR 0x" << base::hex << (uint64_t)ocr << base::dec << ", "
|
||||
<< (uint64_t)(g_clockHz / 1000) << " kHz, 4-bit)";
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* BrcmfSdio.hpp
|
||||
* SDIO host on the Raspberry Pi 4/400's Arasan SD controller, for the
|
||||
* CYW4345x Wi-Fi radio wired to it
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
// The BCM2711 has two SD controllers: EMMC2 runs the card slot
|
||||
// (Drivers/Storage/Sdhci) and the older Arasan-derived EMMC, routed to GPIO
|
||||
// 34-39, runs the radio's SDIO interface. This is a minimal SDIO host for
|
||||
// the latter: one card, polled, programmed I/O, CMD52 and CMD53 only.
|
||||
//
|
||||
// None of these calls lock; the Broadcom driver above serializes them.
|
||||
|
||||
namespace Drivers::Net::Wifi::Sdio {
|
||||
|
||||
// SDIO functions on the radio.
|
||||
constexpr uint32_t FN0 = 0; // CCCR / FBR
|
||||
constexpr uint32_t FN1 = 1; // backplane
|
||||
constexpr uint32_t FN2 = 2; // WLAN frames
|
||||
|
||||
// Block sizes the card is configured with.
|
||||
constexpr uint32_t FN1_BLOCK = 64;
|
||||
constexpr uint32_t FN2_BLOCK = 512;
|
||||
|
||||
// Map the controller's registers and reset it. `baseClockHz` is the
|
||||
// controller's input clock (0 = take it from the capabilities).
|
||||
bool HostInit(uint64_t mmioPhys, uint32_t baseClockHz);
|
||||
|
||||
// Enumerate the card: CMD0/5/3/7, 4-bit bus at 25 MHz, function 1/2
|
||||
// block sizes, function 1 enabled. Returns false when nothing answers.
|
||||
bool CardInit();
|
||||
|
||||
// CMD52: one byte.
|
||||
bool Read8(uint32_t fn, uint32_t addr, uint8_t& value);
|
||||
bool Write8(uint32_t fn, uint32_t addr, uint8_t value);
|
||||
|
||||
// CMD53: `len` bytes, a multiple of 4. `incr` selects an incrementing
|
||||
// address (backplane memory) over a fixed one (the function 2 FIFO).
|
||||
// Longer transfers are split into block-mode runs plus a byte-mode tail.
|
||||
bool ReadBlock(uint32_t fn, uint32_t addr, bool incr, uint8_t* buffer, uint32_t len);
|
||||
bool WriteBlock(uint32_t fn, uint32_t addr, bool incr, const uint8_t* buffer, uint32_t len);
|
||||
|
||||
// Enable or disable function `fn`'s I/O; waits up to `timeoutMs` for the
|
||||
// card to report it ready.
|
||||
bool EnableFunction(uint32_t fn, bool enable, uint64_t timeoutMs);
|
||||
|
||||
// CCCR interrupt enables (master | functions).
|
||||
bool EnableCardInterrupts(uint8_t mask);
|
||||
|
||||
// Abort a function's transfer in progress (CCCR I/O Abort).
|
||||
void Abort(uint32_t fn);
|
||||
|
||||
// The card is signalling an interrupt on DAT1. One register read, safe
|
||||
// from any context.
|
||||
bool CardInterruptPending();
|
||||
|
||||
bool IsInitialized();
|
||||
|
||||
}
|
||||
@@ -443,6 +443,12 @@ namespace Drivers::Net::Wifi {
|
||||
// Send an Ethernet frame over the air (called by the netif).
|
||||
bool IwxConnectSendEthernet(const uint8_t* frame, uint32_t len);
|
||||
|
||||
// The supplicant's hooks (Wpa.hpp) for this MLME; Wifi.cpp routes them.
|
||||
bool IwxWpaTxEapol(const uint8_t* body, uint32_t len);
|
||||
bool IwxWpaInstallPtk(const uint8_t* tk, uint32_t tkLen, uint8_t cipher);
|
||||
bool IwxWpaInstallGtk(const uint8_t* gtk, uint32_t gtkLen, uint8_t keyIdx,
|
||||
uint8_t cipher, const uint8_t* rsc);
|
||||
|
||||
// Sink for decapsulated Ethernet frames, implemented by Wifi.cpp.
|
||||
void WifiRxEthernet(const uint8_t* frame, uint32_t len);
|
||||
}
|
||||
@@ -535,7 +535,7 @@ namespace Drivers::Net::Wifi {
|
||||
|
||||
// The supplicant transmits EAPOL frames unencrypted: they are what
|
||||
// establishes the key in the first place.
|
||||
bool WpaTxEapol(const uint8_t* body, uint32_t len) {
|
||||
bool IwxWpaTxEapol(const uint8_t* body, uint32_t len) {
|
||||
return SendDataFrame(g_bssid, ETHERTYPE_EAPOL, body, len, false);
|
||||
}
|
||||
|
||||
@@ -545,14 +545,14 @@ namespace Drivers::Net::Wifi {
|
||||
static InstalledKey g_ptkKey = {};
|
||||
static InstalledKey g_gtkKey = {};
|
||||
|
||||
bool WpaInstallPtk(const uint8_t* tk, uint32_t tkLen, uint8_t cipher) {
|
||||
bool IwxWpaInstallPtk(const uint8_t* tk, uint32_t tkLen, uint8_t cipher) {
|
||||
if (!IwxSetKey(tk, tkLen, 0, true, cipher, nullptr)) return false;
|
||||
g_ptkKey = { true, 0, cipher, (uint8_t)tkLen };
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WpaInstallGtk(const uint8_t* gtk, uint32_t gtkLen, uint8_t keyIdx,
|
||||
uint8_t cipher, const uint8_t* rsc) {
|
||||
bool IwxWpaInstallGtk(const uint8_t* gtk, uint32_t gtkLen, uint8_t keyIdx,
|
||||
uint8_t cipher, const uint8_t* rsc) {
|
||||
if (gtkLen != 16 && gtkLen != 32) return false;
|
||||
if (!IwxSetKey(gtk, gtkLen, keyIdx, false, cipher, rsc)) return false;
|
||||
g_gtkKey = { true, keyIdx, cipher, (uint8_t)gtkLen };
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "Wifi.hpp"
|
||||
#include <Arch/Cpu.hpp>
|
||||
#include "Iwx.hpp"
|
||||
#include "Brcmf.hpp"
|
||||
#include "Wpa.hpp"
|
||||
#include <Fs/Vfs.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
@@ -34,6 +35,72 @@ namespace Drivers::Net::Wifi {
|
||||
static std::atomic<bool> g_initPending{false};
|
||||
static bool g_initialized = false;
|
||||
|
||||
// =========================================================================
|
||||
// Backend
|
||||
//
|
||||
// Two kinds of adapter sit behind this facade: the Intel AX210 family on
|
||||
// PCIe (Iwx*: the firmware is a SoftMAC, the MLME is ours) and the
|
||||
// Raspberry Pi 4/400's Broadcom/Cypress radio on SDIO (Brcmf*: FullMAC,
|
||||
// the firmware joins by itself). A machine has at most one; whichever
|
||||
// probes first owns the facade. Both use the same scan table, the same
|
||||
// supplicant and the same connection-state numbering (WIFI_CONN_*, which
|
||||
// IwxConnStateId mirrors).
|
||||
// =========================================================================
|
||||
|
||||
enum class Backend : uint8_t { None, Iwx, Brcmf };
|
||||
static Backend g_backend = Backend::None;
|
||||
|
||||
static bool IsBrcmf() { return g_backend == Backend::Brcmf; }
|
||||
|
||||
static bool FwRunning() {
|
||||
return IsBrcmf() ? BrcmfGetState() == BrcmfState::Running
|
||||
: g_iwx.State == IwxFwState::Running;
|
||||
}
|
||||
|
||||
static bool FwFailed() {
|
||||
return IsBrcmf() ? BrcmfGetState() == BrcmfState::Error
|
||||
: g_iwx.State == IwxFwState::Error;
|
||||
}
|
||||
|
||||
static bool ScanActive() { return IsBrcmf() ? BrcmfScanActive() : g_iwx.ScanActive; }
|
||||
static bool BeginScan() { return IsBrcmf() ? BrcmfStartScan() : IwxStartScan(nullptr); }
|
||||
|
||||
static void AbortScan() {
|
||||
if (IsBrcmf()) BrcmfAbortScan();
|
||||
else IwxAbortScan();
|
||||
}
|
||||
|
||||
// What a blocking wait pumps: the receive path, and for a join the
|
||||
// state machine behind it.
|
||||
static void PumpEvents() {
|
||||
if (IsBrcmf()) BrcmfService();
|
||||
else IwxProcessEvents();
|
||||
}
|
||||
|
||||
static void PumpConnection() {
|
||||
if (IsBrcmf()) {
|
||||
BrcmfService();
|
||||
} else {
|
||||
IwxProcessEvents();
|
||||
IwxConnectService();
|
||||
}
|
||||
}
|
||||
|
||||
static int ConnState() { return IsBrcmf() ? BrcmfConnectState() : IwxConnectState(); }
|
||||
|
||||
static void ConnAbort() {
|
||||
if (IsBrcmf()) BrcmfConnectAbort();
|
||||
else IwxConnectAbort();
|
||||
}
|
||||
|
||||
// A handshake that got as far as exchanging EAPOL frames and then failed
|
||||
// is almost always a wrong passphrase.
|
||||
static bool JoinFailedOnAuth() {
|
||||
return IsBrcmf() ? BrcmfJoinFailedOnAuth() : WpaGetState() == WpaState::Failed;
|
||||
}
|
||||
|
||||
static bool LinkUp() { return IsBrcmf() ? BrcmfLinkUp() : IwxLinkUp(); }
|
||||
|
||||
// =========================================================================
|
||||
// Scan result table
|
||||
// =========================================================================
|
||||
@@ -141,22 +208,12 @@ namespace Drivers::Net::Wifi {
|
||||
uint8_t RsnLen = 0;
|
||||
};
|
||||
|
||||
static bool ParseBeacon(const uint8_t* frame, uint32_t len, ParsedBeacon* out) {
|
||||
// 24-byte MAC header, then timestamp(8) + beacon interval(2) + caps(2).
|
||||
constexpr uint32_t HDR = 24;
|
||||
constexpr uint32_t FIXED = 12;
|
||||
if (len < HDR + FIXED) return false;
|
||||
|
||||
const uint8_t* fixed = frame + HDR;
|
||||
out->BeaconInterval = (uint16_t)(fixed[8] | (fixed[9] << 8));
|
||||
uint16_t caps = (uint16_t)(fixed[10] | (fixed[11] << 8));
|
||||
bool privacy = (caps & 0x0010) != 0;
|
||||
|
||||
// The information elements of a beacon or probe response; `privacy` is
|
||||
// the capability field's Privacy bit.
|
||||
static void ParseIes(const uint8_t* ie, const uint8_t* end, bool privacy, ParsedBeacon* out) {
|
||||
bool haveRsn = false, haveWpa = false;
|
||||
|
||||
const uint8_t* ie = frame + HDR + FIXED;
|
||||
const uint8_t* end = frame + len;
|
||||
while (ie + 2 <= end) {
|
||||
while (ie && ie + 2 <= end) {
|
||||
uint8_t id = ie[0];
|
||||
uint8_t ielen = ie[1];
|
||||
if (ie + 2 + ielen > end) break;
|
||||
@@ -200,9 +257,24 @@ namespace Drivers::Net::Wifi {
|
||||
else if (privacy) out->Security = WIFI_SEC_WEP;
|
||||
else out->Security = WIFI_SEC_OPEN;
|
||||
}
|
||||
}
|
||||
|
||||
static bool ParseBeacon(const uint8_t* frame, uint32_t len, ParsedBeacon* out) {
|
||||
// 24-byte MAC header, then timestamp(8) + beacon interval(2) + caps(2).
|
||||
constexpr uint32_t HDR = 24;
|
||||
constexpr uint32_t FIXED = 12;
|
||||
if (len < HDR + FIXED) return false;
|
||||
|
||||
const uint8_t* fixed = frame + HDR;
|
||||
out->BeaconInterval = (uint16_t)(fixed[8] | (fixed[9] << 8));
|
||||
uint16_t caps = (uint16_t)(fixed[10] | (fixed[11] << 8));
|
||||
ParseIes(frame + HDR + FIXED, frame + len, (caps & 0x0010) != 0, out);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void StoreScanResult(const uint8_t* bssid, const ParsedBeacon& pb, uint8_t ch,
|
||||
int8_t rssiDbm);
|
||||
|
||||
// Called by the MVM RX path for every received management frame.
|
||||
void WifiRxMgmtFrame(const uint8_t* frame, uint32_t len, uint8_t channel,
|
||||
int8_t rssiDbm) {
|
||||
@@ -224,9 +296,21 @@ namespace Drivers::Net::Wifi {
|
||||
ParsedBeacon pb;
|
||||
if (!ParseBeacon(frame, len, &pb)) return;
|
||||
|
||||
const uint8_t* bssid = frame + 16; // addr3 of a beacon
|
||||
uint8_t ch = pb.Channel ? pb.Channel : channel;
|
||||
// addr3 of a beacon
|
||||
StoreScanResult(frame + 16, pb, pb.Channel ? pb.Channel : channel, rssiDbm);
|
||||
}
|
||||
|
||||
// One BSS a FullMAC firmware reported from its own scan.
|
||||
void WifiRxBssInfo(const uint8_t* bssid, uint16_t beaconInterval, uint16_t capability,
|
||||
const uint8_t* ies, uint32_t iesLen, uint8_t channel, int8_t rssiDbm) {
|
||||
ParsedBeacon pb;
|
||||
pb.BeaconInterval = beaconInterval;
|
||||
ParseIes(ies, ies ? ies + iesLen : nullptr, (capability & 0x0010) != 0, &pb);
|
||||
StoreScanResult(bssid, pb, pb.Channel ? pb.Channel : channel, rssiDbm);
|
||||
}
|
||||
|
||||
static void StoreScanResult(const uint8_t* bssid, const ParsedBeacon& pb, uint8_t ch,
|
||||
int8_t rssiDbm) {
|
||||
g_resultLock.Acquire();
|
||||
|
||||
ScanEntry* slot = nullptr;
|
||||
@@ -275,7 +359,9 @@ namespace Drivers::Net::Wifi {
|
||||
// =========================================================================
|
||||
|
||||
bool Probe(const Pci::PciDevice& dev) {
|
||||
if (g_backend != Backend::None) return false;
|
||||
if (!IwxProbe(dev)) return false;
|
||||
g_backend = Backend::Iwx;
|
||||
|
||||
// Firmware comes off the ramdisk, which is not mounted during the PCI
|
||||
// scan; hand the rest of the bring-up to the idle loop.
|
||||
@@ -283,7 +369,23 @@ namespace Drivers::Net::Wifi {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProbeBroadcomSdio(uint64_t mmioPhys, const char* name) {
|
||||
if (g_backend != Backend::None) return false;
|
||||
if (!BrcmfProbe(mmioPhys)) return false;
|
||||
g_backend = Backend::Brcmf;
|
||||
KernelLogStream(INFO, "WiFi") << "Broadcom SDIO radio on " << name
|
||||
<< "; firmware loads once the root filesystem is up";
|
||||
// Same reason as the PCI path: the firmware is on the root volume.
|
||||
g_initPending.store(true, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void CompleteInit() {
|
||||
if (IsBrcmf()) {
|
||||
g_initialized = BrcmfCompleteInit();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IwxReadFirmware()) {
|
||||
g_iwx.State = IwxFwState::Error;
|
||||
return;
|
||||
@@ -348,7 +450,7 @@ namespace Drivers::Net::Wifi {
|
||||
static constexpr uint64_t RECOVERY_BACKOFF_MS = 5000;
|
||||
|
||||
static void ServiceRecovery() {
|
||||
if (g_iwx.State != IwxFwState::Error || g_recoveryGaveUp) return;
|
||||
if (!FwFailed() || g_recoveryGaveUp) return;
|
||||
// An adapter that never finished its first bring-up is ServiceDeferredInit's
|
||||
// problem, not this one.
|
||||
if (!g_initialized) return;
|
||||
@@ -366,7 +468,7 @@ namespace Drivers::Net::Wifi {
|
||||
|
||||
// Re-check under the gate: the winner of the race may have just
|
||||
// finished a reset that fixed things.
|
||||
if (g_iwx.State != IwxFwState::Error) return;
|
||||
if (!FwFailed()) return;
|
||||
|
||||
uint64_t now = Timekeeping::GetMilliseconds();
|
||||
if (g_lastRecoveryMs && now - g_lastRecoveryMs < RECOVERY_BACKOFF_MS) return;
|
||||
@@ -384,6 +486,24 @@ namespace Drivers::Net::Wifi {
|
||||
<< " (attempt " << (uint64_t)g_recoveryAttempts << " of "
|
||||
<< (uint64_t)MAX_RECOVERY_ATTEMPTS << ")";
|
||||
|
||||
if (IsBrcmf()) {
|
||||
// A FullMAC radio is reset by power: WL_ON low, then the whole
|
||||
// bring-up (firmware download included) again.
|
||||
g_initialized = false;
|
||||
g_asyncConnect = false;
|
||||
g_scanDeadline = 0;
|
||||
g_lastError = WIFI_ERR_NO_ADAPTER;
|
||||
ClearResults();
|
||||
BrcmfShutdown();
|
||||
CompleteInit();
|
||||
if (g_initialized) {
|
||||
KernelLogStream(OK, "WiFi") << "Adapter recovered";
|
||||
g_recoveryAttempts = 0;
|
||||
g_lastError = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Nothing may be mid-command while the device is torn down. Taking
|
||||
// CmdLock is enough: it is what serializes every sender, and the RX
|
||||
// pump has its own reentrancy guard.
|
||||
@@ -517,8 +637,24 @@ namespace Drivers::Net::Wifi {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The facade's own deadlines (async scan and join) and a due reset.
|
||||
static bool FacadeWorkDue(uint64_t now) {
|
||||
if (g_scanDeadline != 0 && (now >= g_scanDeadline || !ScanActive())) return true;
|
||||
if (g_asyncConnect) return true;
|
||||
return FwFailed() && g_initialized && !g_recoveryGaveUp &&
|
||||
(g_lastRecoveryMs == 0 || now - g_lastRecoveryMs >= RECOVERY_BACKOFF_MS);
|
||||
}
|
||||
|
||||
bool HasPolledWork() {
|
||||
return IsBrcmf() && BrcmfHasWork(Timekeeping::GetMilliseconds());
|
||||
}
|
||||
|
||||
bool HasDeferredWork() {
|
||||
if (InitDue()) return true;
|
||||
if (IsBrcmf()) {
|
||||
uint64_t now = Timekeeping::GetMilliseconds();
|
||||
return BrcmfHasWork(now) || FacadeWorkDue(now);
|
||||
}
|
||||
if (!g_iwx.Mmio) return false;
|
||||
if (g_iwx.WorkPending) return true;
|
||||
|
||||
@@ -527,7 +663,29 @@ namespace Drivers::Net::Wifi {
|
||||
return HasBlockingWork(now);
|
||||
}
|
||||
|
||||
// The Broadcom radio has no interrupt in use: everything on its bus is
|
||||
// polled, and everything on its bus waits (SDIO transfers, ioctls), so
|
||||
// all of it goes through the blocking gate.
|
||||
static void ServiceBrcmf() {
|
||||
uint64_t now = Timekeeping::GetMilliseconds();
|
||||
bool radioWork = BrcmfHasWork(now);
|
||||
if (!radioWork && !FacadeWorkDue(now)) return;
|
||||
if (LeaveToAp()) return;
|
||||
if (__atomic_test_and_set(&g_blockingBusy, __ATOMIC_ACQUIRE)) return;
|
||||
struct Guard {
|
||||
~Guard() { __atomic_clear(&g_blockingBusy, __ATOMIC_RELEASE); }
|
||||
} guard;
|
||||
|
||||
if (radioWork) BrcmfService();
|
||||
ServiceAsync();
|
||||
ServiceRecovery();
|
||||
}
|
||||
|
||||
void ServiceEvents() {
|
||||
if (IsBrcmf()) {
|
||||
ServiceBrcmf();
|
||||
return;
|
||||
}
|
||||
if (!g_iwx.Mmio) return;
|
||||
|
||||
// The non-blocking half: any core, the BSP included.
|
||||
@@ -573,7 +731,11 @@ namespace Drivers::Net::Wifi {
|
||||
}
|
||||
|
||||
bool IsInitialized() { return g_initialized; }
|
||||
bool IsPresent() { return g_iwx.State != IwxFwState::Absent; }
|
||||
|
||||
bool IsPresent() {
|
||||
return IsBrcmf() ? BrcmfGetState() != BrcmfState::Absent
|
||||
: g_iwx.State != IwxFwState::Absent;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Public operations
|
||||
@@ -611,26 +773,26 @@ namespace Drivers::Net::Wifi {
|
||||
int Scan(WifiNetwork* out, int maxCount, uint32_t timeoutMs) {
|
||||
if (!out || maxCount <= 0) return -1;
|
||||
if (!g_initialized) return -1;
|
||||
if (g_iwx.State != IwxFwState::Running) return -1;
|
||||
if (!FwRunning()) return -1;
|
||||
|
||||
if (timeoutMs < 1000) timeoutMs = 1000;
|
||||
if (timeoutMs > 20000) timeoutMs = 20000;
|
||||
|
||||
ClearResults();
|
||||
|
||||
if (!IwxStartScan(nullptr)) return -1;
|
||||
if (!BeginScan()) return -1;
|
||||
|
||||
uint64_t start = Timekeeping::GetMilliseconds();
|
||||
while (g_iwx.ScanActive && Timekeeping::GetMilliseconds() - start < timeoutMs) {
|
||||
IwxProcessEvents();
|
||||
while (ScanActive() && Timekeeping::GetMilliseconds() - start < timeoutMs) {
|
||||
PumpEvents();
|
||||
for (int i = 0; i < 200; i++) Arch::Pause();
|
||||
}
|
||||
|
||||
if (g_iwx.ScanActive) {
|
||||
IwxAbortScan();
|
||||
if (ScanActive()) {
|
||||
AbortScan();
|
||||
// Drain whatever the firmware queued before the abort landed.
|
||||
uint64_t t0 = Timekeeping::GetMilliseconds();
|
||||
while (Timekeeping::GetMilliseconds() - t0 < 200) IwxProcessEvents();
|
||||
while (Timekeeping::GetMilliseconds() - t0 < 200) PumpEvents();
|
||||
}
|
||||
|
||||
g_scanGeneration++;
|
||||
@@ -639,14 +801,14 @@ namespace Drivers::Net::Wifi {
|
||||
|
||||
int StartScan(uint32_t timeoutMs) {
|
||||
if (!g_initialized) return -1;
|
||||
if (g_iwx.State != IwxFwState::Running) return -1;
|
||||
if (g_iwx.ScanActive) return 1; // already sweeping
|
||||
if (!FwRunning()) return -1;
|
||||
if (ScanActive()) return 1; // already sweeping
|
||||
|
||||
if (timeoutMs < 1000) timeoutMs = 1000;
|
||||
if (timeoutMs > 20000) timeoutMs = 20000;
|
||||
|
||||
ClearResults();
|
||||
if (!IwxStartScan(nullptr)) return -1;
|
||||
if (!BeginScan()) return -1;
|
||||
|
||||
g_scanDeadline = Timekeeping::GetMilliseconds() + timeoutMs;
|
||||
return 0;
|
||||
@@ -657,33 +819,47 @@ namespace Drivers::Net::Wifi {
|
||||
memset(out, 0, sizeof(*out));
|
||||
|
||||
out->present = IsPresent() ? 1 : 0;
|
||||
out->state = (uint8_t)g_iwx.State;
|
||||
if (!IsPresent()) return -1;
|
||||
|
||||
memcpy(out->mac, g_iwx.Nvm.HwAddr, 6);
|
||||
out->scanning = g_iwx.ScanActive ? 1 : 0;
|
||||
out->bands = (uint8_t)((g_iwx.Nvm.Sku24GHz ? 1 : 0)
|
||||
| (g_iwx.Nvm.Sku52GHz ? 2 : 0));
|
||||
out->channels = (uint16_t)g_iwx.ChannelCount;
|
||||
out->rxPackets = g_iwx.RxPackets;
|
||||
out->txPackets = g_iwx.TxPackets;
|
||||
out->fwErrors = (uint32_t)g_iwx.FwErrors;
|
||||
out->connState = (uint32_t)IwxConnectState();
|
||||
out->connected = IwxLinkUp() ? 1 : 0;
|
||||
const char* fwVersion;
|
||||
if (IsBrcmf()) {
|
||||
out->state = (uint8_t)BrcmfGetState();
|
||||
memcpy(out->mac, g_brcmf.Mac, 6);
|
||||
out->bands = (uint8_t)((g_brcmf.Band24 ? 1 : 0) | (g_brcmf.Band5 ? 2 : 0));
|
||||
out->channels = g_brcmf.ChannelCount;
|
||||
out->rxPackets = g_brcmf.RxPackets;
|
||||
out->txPackets = g_brcmf.TxPackets;
|
||||
out->fwErrors = g_brcmf.FwErrors;
|
||||
fwVersion = g_brcmf.FwVersion;
|
||||
} else {
|
||||
out->state = (uint8_t)g_iwx.State;
|
||||
memcpy(out->mac, g_iwx.Nvm.HwAddr, 6);
|
||||
out->bands = (uint8_t)((g_iwx.Nvm.Sku24GHz ? 1 : 0)
|
||||
| (g_iwx.Nvm.Sku52GHz ? 2 : 0));
|
||||
out->channels = (uint16_t)g_iwx.ChannelCount;
|
||||
out->rxPackets = g_iwx.RxPackets;
|
||||
out->txPackets = g_iwx.TxPackets;
|
||||
out->fwErrors = (uint32_t)g_iwx.FwErrors;
|
||||
fwVersion = g_iwx.Fw.Version;
|
||||
}
|
||||
out->scanning = ScanActive() ? 1 : 0;
|
||||
out->connState = (uint32_t)ConnState();
|
||||
out->connected = LinkUp() ? 1 : 0;
|
||||
out->lastError = (int32_t)g_lastError;
|
||||
out->scanGeneration = g_scanGeneration;
|
||||
out->joining = g_asyncConnect ? 1 : 0;
|
||||
|
||||
if (IwxConnectState() != (int)IwxConnStateId::Idle) {
|
||||
const char* ssid = IwxConnectSsid();
|
||||
if (ConnState() != (int)IwxConnStateId::Idle) {
|
||||
const char* ssid = IsBrcmf() ? BrcmfConnectSsid() : IwxConnectSsid();
|
||||
int k = 0;
|
||||
for (; k < 32 && ssid[k]; k++) out->ssid[k] = ssid[k];
|
||||
out->ssid[k] = '\0';
|
||||
memcpy(out->bssid, IwxConnectBssid(), 6);
|
||||
memcpy(out->bssid, IsBrcmf() ? BrcmfConnectBssid() : IwxConnectBssid(), 6);
|
||||
if (IsBrcmf()) out->channel = BrcmfConnectChannel();
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (; i < 31 && g_iwx.Fw.Version[i]; i++) out->fwVersion[i] = g_iwx.Fw.Version[i];
|
||||
for (; i < 31 && fwVersion[i]; i++) out->fwVersion[i] = fwVersion[i];
|
||||
out->fwVersion[i] = '\0';
|
||||
return 0;
|
||||
}
|
||||
@@ -695,24 +871,20 @@ namespace Drivers::Net::Wifi {
|
||||
uint64_t start = Timekeeping::GetMilliseconds();
|
||||
|
||||
while (Timekeeping::GetMilliseconds() - start < timeoutMs) {
|
||||
IwxProcessEvents();
|
||||
IwxConnectService();
|
||||
PumpConnection();
|
||||
|
||||
auto state = (IwxConnStateId)IwxConnectState();
|
||||
auto state = (IwxConnStateId)ConnState();
|
||||
if (state == IwxConnStateId::Connected) return 0;
|
||||
if (state == IwxConnStateId::Failed) {
|
||||
// A handshake that got as far as exchanging EAPOL frames and
|
||||
// then failed is almost always a wrong passphrase.
|
||||
int rc = WpaGetState() == WpaState::Failed
|
||||
? WIFI_ERR_AUTH : WIFI_ERR_FAILED;
|
||||
int rc = JoinFailedOnAuth() ? WIFI_ERR_AUTH : WIFI_ERR_FAILED;
|
||||
// A rejection seen on the RX path only marks the state; the
|
||||
// firmware contexts are still up and have to come back down.
|
||||
IwxConnectAbort();
|
||||
ConnAbort();
|
||||
return rc;
|
||||
}
|
||||
if (state == IwxConnStateId::Idle) return WIFI_ERR_FAILED;
|
||||
if (g_iwx.State == IwxFwState::Error) {
|
||||
IwxConnectAbort();
|
||||
if (FwFailed()) {
|
||||
ConnAbort();
|
||||
return WIFI_ERR_FAILED;
|
||||
}
|
||||
|
||||
@@ -721,7 +893,7 @@ namespace Drivers::Net::Wifi {
|
||||
Sched::Schedule();
|
||||
}
|
||||
|
||||
IwxConnectAbort();
|
||||
ConnAbort();
|
||||
return WIFI_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
@@ -732,7 +904,7 @@ namespace Drivers::Net::Wifi {
|
||||
// scan table is worth a (blocking) sweep to look for it.
|
||||
static int StartJoin(const char* ssid, const char* password, bool rescan) {
|
||||
if (!g_initialized || !ssid) return WIFI_ERR_NO_ADAPTER;
|
||||
if (g_iwx.State != IwxFwState::Running) return WIFI_ERR_NO_ADAPTER;
|
||||
if (!FwRunning()) return WIFI_ERR_NO_ADAPTER;
|
||||
|
||||
// Locate the network in the most recent scan results: the firmware
|
||||
// contexts need its BSSID and channel, and the connect path needs its
|
||||
@@ -809,17 +981,24 @@ namespace Drivers::Net::Wifi {
|
||||
if (!havePassword) return WIFI_ERR_NEED_KEY;
|
||||
}
|
||||
|
||||
if (!IwxConnectStart(bssid, channel, is5, ssid,
|
||||
havePassword ? password : nullptr,
|
||||
security == WIFI_SEC_OPEN ? nullptr : rsnIe,
|
||||
security == WIFI_SEC_OPEN ? 0 : rsnIeLen,
|
||||
beaconInterval, dtimPeriod)) {
|
||||
// IwxConnectStart already logged the specific reason. Only report
|
||||
bool started = IsBrcmf()
|
||||
? BrcmfConnectStart(bssid, channel, ssid,
|
||||
havePassword ? password : nullptr,
|
||||
security == WIFI_SEC_OPEN ? nullptr : rsnIe,
|
||||
security == WIFI_SEC_OPEN ? 0 : rsnIeLen)
|
||||
: IwxConnectStart(bssid, channel, is5, ssid,
|
||||
havePassword ? password : nullptr,
|
||||
security == WIFI_SEC_OPEN ? nullptr : rsnIe,
|
||||
security == WIFI_SEC_OPEN ? 0 : rsnIeLen,
|
||||
beaconInterval, dtimPeriod);
|
||||
if (!started) {
|
||||
// The backend already logged the specific reason. Only report
|
||||
// a security problem when that is genuinely what happened; a radio
|
||||
// or firmware failure needs completely different advice.
|
||||
bool security_ = IwxConnectRefusedForSecurity();
|
||||
auto state = (IwxConnStateId)IwxConnectState();
|
||||
if (state != IwxConnStateId::Idle) IwxConnectAbort();
|
||||
bool security_ = IsBrcmf() ? BrcmfConnectRefusedForSecurity()
|
||||
: IwxConnectRefusedForSecurity();
|
||||
auto state = (IwxConnStateId)ConnState();
|
||||
if (state != IwxConnStateId::Idle) ConnAbort();
|
||||
return security_ ? WIFI_ERR_UNSUPPORTED : WIFI_ERR_FAILED;
|
||||
}
|
||||
|
||||
@@ -835,8 +1014,8 @@ namespace Drivers::Net::Wifi {
|
||||
int ConnectAsync(const char* ssid, const char* password) {
|
||||
// A join already in flight owns the firmware contexts; tear it down
|
||||
// rather than stacking a second one on top.
|
||||
if (g_asyncConnect || (IwxConnectState() != (int)IwxConnStateId::Idle))
|
||||
IwxConnectAbort();
|
||||
if (g_asyncConnect || (ConnState() != (int)IwxConnStateId::Idle))
|
||||
ConnAbort();
|
||||
|
||||
g_asyncConnect = false;
|
||||
g_lastError = 0;
|
||||
@@ -858,7 +1037,7 @@ namespace Drivers::Net::Wifi {
|
||||
if (!g_initialized) return -1;
|
||||
g_asyncConnect = false;
|
||||
g_lastError = 0;
|
||||
IwxConnectAbort();
|
||||
ConnAbort();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -881,11 +1060,11 @@ namespace Drivers::Net::Wifi {
|
||||
uint64_t now = Timekeeping::GetMilliseconds();
|
||||
|
||||
if (g_scanDeadline) {
|
||||
if (!g_iwx.ScanActive) {
|
||||
if (!ScanActive()) {
|
||||
g_scanDeadline = 0;
|
||||
g_scanGeneration++;
|
||||
} else if (now >= g_scanDeadline) {
|
||||
IwxAbortScan();
|
||||
AbortScan();
|
||||
g_scanDeadline = 0;
|
||||
g_scanGeneration++;
|
||||
}
|
||||
@@ -893,31 +1072,29 @@ namespace Drivers::Net::Wifi {
|
||||
|
||||
if (!g_asyncConnect) return;
|
||||
|
||||
auto state = (IwxConnStateId)IwxConnectState();
|
||||
auto state = (IwxConnStateId)ConnState();
|
||||
if (state == IwxConnStateId::Connected) {
|
||||
g_asyncConnect = false;
|
||||
g_lastError = 0;
|
||||
return;
|
||||
}
|
||||
if (state == IwxConnStateId::Failed) {
|
||||
// Same reading as the blocking path: a handshake that exchanged
|
||||
// EAPOL frames and then failed is almost always a wrong passphrase.
|
||||
g_lastError = WpaGetState() == WpaState::Failed
|
||||
? WIFI_ERR_AUTH : WIFI_ERR_FAILED;
|
||||
// Same reading as the blocking path.
|
||||
g_lastError = JoinFailedOnAuth() ? WIFI_ERR_AUTH : WIFI_ERR_FAILED;
|
||||
g_asyncConnect = false;
|
||||
IwxConnectAbort();
|
||||
ConnAbort();
|
||||
return;
|
||||
}
|
||||
if (state == IwxConnStateId::Idle || g_iwx.State == IwxFwState::Error) {
|
||||
if (state == IwxConnStateId::Idle || FwFailed()) {
|
||||
g_lastError = WIFI_ERR_FAILED;
|
||||
g_asyncConnect = false;
|
||||
if (state != IwxConnStateId::Idle) IwxConnectAbort();
|
||||
if (state != IwxConnStateId::Idle) ConnAbort();
|
||||
return;
|
||||
}
|
||||
if (now >= g_asyncConnectDeadline) {
|
||||
g_lastError = WIFI_ERR_TIMEOUT;
|
||||
g_asyncConnect = false;
|
||||
IwxConnectAbort();
|
||||
ConnAbort();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -927,13 +1104,33 @@ namespace Drivers::Net::Wifi {
|
||||
|
||||
static RxCallback g_rxCallback = nullptr;
|
||||
|
||||
const uint8_t* GetMacAddress() { return g_iwx.Nvm.HwAddr; }
|
||||
const uint8_t* GetMacAddress() { return IsBrcmf() ? g_brcmf.Mac : g_iwx.Nvm.HwAddr; }
|
||||
|
||||
bool IsLinkUp() { return g_initialized && IwxLinkUp(); }
|
||||
bool IsLinkUp() { return g_initialized && LinkUp(); }
|
||||
|
||||
bool SendPacket(const uint8_t* data, uint16_t length) {
|
||||
if (!IsLinkUp()) return false;
|
||||
return IwxConnectSendEthernet(data, length);
|
||||
return IsBrcmf() ? BrcmfSendEthernet(data, length)
|
||||
: IwxConnectSendEthernet(data, length);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Supplicant hooks (Wpa.hpp): to whichever backend is running the join
|
||||
// =========================================================================
|
||||
|
||||
bool WpaTxEapol(const uint8_t* body, uint32_t len) {
|
||||
return IsBrcmf() ? BrcmfWpaTxEapol(body, len) : IwxWpaTxEapol(body, len);
|
||||
}
|
||||
|
||||
bool WpaInstallPtk(const uint8_t* tk, uint32_t tkLen, uint8_t cipher) {
|
||||
return IsBrcmf() ? BrcmfWpaInstallPtk(tk, tkLen, cipher)
|
||||
: IwxWpaInstallPtk(tk, tkLen, cipher);
|
||||
}
|
||||
|
||||
bool WpaInstallGtk(const uint8_t* gtk, uint32_t gtkLen, uint8_t keyIdx,
|
||||
uint8_t cipher, const uint8_t* rsc) {
|
||||
return IsBrcmf() ? BrcmfWpaInstallGtk(gtk, gtkLen, keyIdx, cipher, rsc)
|
||||
: IwxWpaInstallGtk(gtk, gtkLen, keyIdx, cipher, rsc);
|
||||
}
|
||||
|
||||
void SetRxCallback(RxCallback callback) { g_rxCallback = callback; }
|
||||
|
||||
@@ -14,6 +14,11 @@ namespace Drivers::Net::Wifi {
|
||||
// PCI driver-table probe entry point.
|
||||
bool Probe(const Pci::PciDevice& dev);
|
||||
|
||||
// The Raspberry Pi 4/400's Broadcom/Cypress radio, on the SDIO
|
||||
// controller (Arasan) at `mmioPhys`. Claims it; the bring-up waits for
|
||||
// the root filesystem like the PCI path.
|
||||
bool ProbeBroadcomSdio(uint64_t mmioPhys, const char* name);
|
||||
|
||||
// Complete the firmware-dependent bring-up once the ramdisk is mounted.
|
||||
// Cheap no-op unless a device is waiting. Called from the idle loop.
|
||||
void ServiceDeferredInit();
|
||||
@@ -25,6 +30,11 @@ namespace Drivers::Net::Wifi {
|
||||
// deadline, or a due recovery attempt needs idle-context servicing.
|
||||
bool HasDeferredWork();
|
||||
|
||||
// A polled adapter (the Broadcom radio has no interrupt in use) is due a
|
||||
// sweep. Cheap and safe from the timer interrupt: the tick uses it to
|
||||
// force a bottom-half pass when the cores never go idle.
|
||||
bool HasPolledWork();
|
||||
|
||||
bool IsInitialized();
|
||||
bool IsPresent();
|
||||
|
||||
|
||||
@@ -66,7 +66,8 @@ namespace Drivers::Net::Wifi {
|
||||
bool WpaParseApRsn(const uint8_t* ie, uint32_t len, WpaConfig& cfg);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Hooks implemented by the MLME (IwxConnect.cpp)
|
||||
// Hooks into the driver running the join (Wifi.cpp routes them to
|
||||
// IwxConnect.cpp or BrcmfConnect.cpp)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Transmit an EAPOL frame body (802.1X header included) to the AP.
|
||||
|
||||
@@ -45,6 +45,7 @@ namespace Drivers::Platform::RpiFirmware {
|
||||
static constexpr uint32_t TAG_RESPONSE = 0x80000000;
|
||||
|
||||
static constexpr uint32_t TAG_GET_BOARD_MAC = 0x00010003;
|
||||
static constexpr uint32_t TAG_SET_CLOCK_STATE = 0x00038001;
|
||||
static constexpr uint32_t TAG_GET_CLOCK_RATE = 0x00030002;
|
||||
static constexpr uint32_t TAG_GET_GPIO_STATE = 0x00030041;
|
||||
static constexpr uint32_t TAG_SET_GPIO_STATE = 0x00038041;
|
||||
@@ -233,6 +234,14 @@ namespace Drivers::Platform::RpiFirmware {
|
||||
return hz != 0;
|
||||
}
|
||||
|
||||
// State bit 0 = on; the answer echoes the clock ID and its new state
|
||||
// (bit 1 set: no such clock).
|
||||
bool SetClockState(uint32_t clockId, bool on) {
|
||||
uint32_t v[2] = { clockId, on ? 1u : 0u };
|
||||
if (PropertyCall(TAG_SET_CLOCK_STATE, v, 2) < 8 || v[0] != clockId) return false;
|
||||
return (v[1] & 2) == 0 && ((v[1] & 1) != 0) == on;
|
||||
}
|
||||
|
||||
// The GPIO tags answer in place of the GPIO number with a status word,
|
||||
// 0 for success (as Linux's raspberrypi-exp-gpio driver checks).
|
||||
bool SetGpioState(uint32_t gpio, bool high) {
|
||||
|
||||
@@ -27,11 +27,15 @@ namespace Drivers::Platform::RpiFirmware {
|
||||
bool IsPresent();
|
||||
|
||||
// Clock identifiers (the firmware's numbering).
|
||||
constexpr uint32_t CLOCK_EMMC = 1; // legacy EMMC (Arasan), runs the Wi-Fi SDIO bus
|
||||
constexpr uint32_t CLOCK_CORE = 4; // VPU core clock, feeds the mini UART
|
||||
|
||||
// Current rate of a firmware clock, in Hz.
|
||||
bool GetClockRate(uint32_t clockId, uint32_t& hz);
|
||||
|
||||
// Gate a firmware clock on or off.
|
||||
bool SetClockState(uint32_t clockId, bool on);
|
||||
|
||||
// The firmware's GPIO expander lines are numbered from 128.
|
||||
constexpr uint32_t EXPGPIO_BASE = 128;
|
||||
constexpr uint32_t EXPGPIO_BT_ON = EXPGPIO_BASE + 0; // Bluetooth power (high = on)
|
||||
|
||||
@@ -67,7 +67,8 @@ namespace Timekeeping {
|
||||
bool audioWork = Drivers::Audio::IntelHda::HasDeferredWork();
|
||||
bool netWork = Drivers::Net::E1000::HasDeferredWork() ||
|
||||
Drivers::Net::E1000E::HasDeferredWork() ||
|
||||
Drivers::Net::Genet::HasDeferredWork();
|
||||
Drivers::Net::Genet::HasDeferredWork() ||
|
||||
Drivers::Net::Wifi::HasPolledWork();
|
||||
// A forced trip into the idle bottom-half context is safe only when
|
||||
// this timer interrupted ring 3. If it interrupted a syscall, that
|
||||
// suspended kernel stack may own one of the process-context mutexes
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,98 @@
|
||||
# Cloned from bcm94345wlpagb_p2xx.txt
|
||||
NVRAMRev=$Rev: 498373 $
|
||||
sromrev=11
|
||||
vendid=0x14e4
|
||||
devid=0x43ab
|
||||
manfid=0x2d0
|
||||
prodid=0x06e4
|
||||
#macaddr=00:90:4c:c5:12:38
|
||||
macaddr=b8:27:eb:74:f2:6c
|
||||
nocrc=1
|
||||
boardtype=0x6e4
|
||||
boardrev=0x1304
|
||||
|
||||
#XTAL 37.4MHz
|
||||
xtalfreq=37400
|
||||
|
||||
btc_mode=1
|
||||
#------------------------------------------------------
|
||||
#boardflags: 5GHz eTR switch by default
|
||||
# 2.4GHz eTR switch by default
|
||||
# bit1 for btcoex
|
||||
boardflags=0x00480201
|
||||
boardflags2=0x40800000
|
||||
boardflags3=0x44200100
|
||||
phycal_tempdelta=15
|
||||
rxchain=1
|
||||
txchain=1
|
||||
aa2g=1
|
||||
aa5g=1
|
||||
tssipos5g=1
|
||||
tssipos2g=1
|
||||
femctrl=0
|
||||
AvVmid_c0=1,165,2,100,2,100,2,100,2,100
|
||||
pa2ga0=-129,6525,-718
|
||||
pa2ga1=-149,4408,-601
|
||||
pa5ga0=-185,6836,-815,-186,6838,-815,-184,6859,-815,-184,6882,-818
|
||||
pa5ga1=-202,4285,-574,-201,4312,-578,-196,4391,-586,-201,4294,-575
|
||||
itrsw=1
|
||||
pdoffsetcckma0=2
|
||||
pdoffset2gperchan=0,-2,1,0,1,0,1,1,1,0,0,-1,-1,0
|
||||
pdoffset2g40ma0=16
|
||||
pdoffset40ma0=0x8888
|
||||
pdoffset80ma0=0x8888
|
||||
extpagain5g=2
|
||||
extpagain2g=2
|
||||
tworangetssi2g=1
|
||||
tworangetssi5g=1
|
||||
# LTECX flags
|
||||
# WCI2
|
||||
ltecxmux=0
|
||||
ltecxpadnum=0x0504
|
||||
ltecxfnsel=0x22
|
||||
ltecxgcigpio=0x32
|
||||
|
||||
maxp2ga0=80
|
||||
ofdmlrbw202gpo=0x0022
|
||||
dot11agofdmhrbw202gpo=0x4442
|
||||
mcsbw202gpo=0x98444422
|
||||
mcsbw402gpo=0x98444422
|
||||
maxp5ga0=82,82,82,82
|
||||
mcsbw205glpo=0xb9555000
|
||||
mcsbw205gmpo=0xb9555000
|
||||
mcsbw205ghpo=0xb9555000
|
||||
mcsbw405glpo=0xb9555000
|
||||
mcsbw405gmpo=0xb9555000
|
||||
mcsbw405ghpo=0xb9555000
|
||||
mcsbw805glpo=0xb9555000
|
||||
mcsbw805gmpo=0xb9555000
|
||||
mcsbw805ghpo=0xb9555000
|
||||
|
||||
swctrlmap_2g=0x00000000,0x00000000,0x00000000,0x010000,0x3ff
|
||||
swctrlmap_5g=0x00100010,0x00200020,0x00200020,0x010000,0x3fe
|
||||
swctrlmapext_5g=0x00000000,0x00000000,0x00000000,0x000000,0x3
|
||||
swctrlmapext_2g=0x00000000,0x00000000,0x00000000,0x000000,0x3
|
||||
|
||||
vcodivmode=1
|
||||
deadman_to=481500000
|
||||
|
||||
ed_thresh2g=-54
|
||||
ed_thresh5g=-54
|
||||
eu_edthresh2g=-54
|
||||
eu_edthresh5g=-54
|
||||
ldo1=4
|
||||
rawtempsense=0x1ff
|
||||
cckPwrIdxCorr=3
|
||||
cckTssiDelay=150
|
||||
ofdmTssiDelay=150
|
||||
txpwr2gAdcScale=1
|
||||
txpwr5gAdcScale=1
|
||||
dot11b_opts=0x3aa85
|
||||
cbfilttype=1
|
||||
fdsslevel_ch11=6
|
||||
|
||||
# Improved Bluetooth coexistence parameters from Cypress
|
||||
btc_mode=1
|
||||
btc_params8=0x4e20
|
||||
btc_params1=0x7530
|
||||
btc_params50=0x972c
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,97 @@
|
||||
# Cloned from bcm94345wlpagb_p2xx.txt
|
||||
NVRAMRev=$Rev: 498373 $
|
||||
sromrev=11
|
||||
vendid=0x14e4
|
||||
devid=0x43ab
|
||||
manfid=0x2d0
|
||||
prodid=0x06e4
|
||||
#macaddr=00:90:4c:c5:12:38
|
||||
macaddr=b8:27:eb:74:f2:6c
|
||||
nocrc=1
|
||||
boardtype=0x6e4
|
||||
boardrev=0x1304
|
||||
|
||||
#XTAL 37.4MHz
|
||||
xtalfreq=37400
|
||||
|
||||
btc_mode=1
|
||||
#------------------------------------------------------
|
||||
#boardflags: 5GHz eTR switch by default
|
||||
# 2.4GHz eTR switch by default
|
||||
# bit1 for btcoex
|
||||
boardflags=0x00480201
|
||||
boardflags2=0x40800000
|
||||
boardflags3=0x44200100
|
||||
phycal_tempdelta=15
|
||||
rxchain=1
|
||||
txchain=1
|
||||
aa2g=1
|
||||
aa5g=1
|
||||
tssipos5g=1
|
||||
tssipos2g=1
|
||||
femctrl=0
|
||||
AvVmid_c0=0,157,1,126,1,126,1,126,1,126
|
||||
pa2ga0=-170,5896,-680
|
||||
pa2ga1=-156,4032,-547
|
||||
pa5ga0=-150,5547,-628,-150,5583,-640,-158,5608,-649,-160,5757,-673
|
||||
pa5ga1=-109,3831,-519,-88,3821,-512,-125,3731,-528,-103,3829,-516
|
||||
itrsw=1
|
||||
pdoffsetcckma0=2
|
||||
pdoffset2gperchan=0,-2,1,0,1,0,1,1,1,0,0,-1,-1,0
|
||||
pdoffset2g40ma0=16
|
||||
pdoffset40ma0=0x8888
|
||||
pdoffset80ma0=0x8888
|
||||
extpagain5g=2
|
||||
extpagain2g=2
|
||||
tworangetssi2g=1
|
||||
tworangetssi5g=1
|
||||
# LTECX flags
|
||||
# WCI2
|
||||
ltecxmux=0
|
||||
ltecxpadnum=0x0504
|
||||
ltecxfnsel=0x22
|
||||
ltecxgcigpio=0x32
|
||||
|
||||
maxp2ga0=80
|
||||
ofdmlrbw202gpo=0x0022
|
||||
dot11agofdmhrbw202gpo=0x4442
|
||||
mcsbw202gpo=0x98444422
|
||||
mcsbw402gpo=0x98444422
|
||||
maxp5ga0=82,82,82,82
|
||||
mcsbw205glpo=0xb9555000
|
||||
mcsbw205gmpo=0xb9555000
|
||||
mcsbw205ghpo=0xb9555000
|
||||
mcsbw405glpo=0xb9555000
|
||||
mcsbw405gmpo=0xb9555000
|
||||
mcsbw405ghpo=0xb9555000
|
||||
mcsbw805glpo=0xb9555000
|
||||
mcsbw805gmpo=0xb9555000
|
||||
mcsbw805ghpo=0xb9555000
|
||||
|
||||
swctrlmap_2g=0x00000000,0x00000000,0x00000000,0x010000,0x3ff
|
||||
swctrlmap_5g=0x00100010,0x00200020,0x00200020,0x010000,0x3fe
|
||||
swctrlmapext_5g=0x00000000,0x00000000,0x00000000,0x000000,0x3
|
||||
swctrlmapext_2g=0x00000000,0x00000000,0x00000000,0x000000,0x3
|
||||
|
||||
vcodivmode=1
|
||||
deadman_to=481500000
|
||||
|
||||
ed_thresh2g=-54
|
||||
ed_thresh5g=-54
|
||||
eu_edthresh2g=-54
|
||||
eu_edthresh5g=-54
|
||||
ldo1=4
|
||||
rawtempsense=0x1ff
|
||||
cckPwrIdxCorr=3
|
||||
cckTssiDelay=150
|
||||
ofdmTssiDelay=150
|
||||
txpwr2gAdcScale=1
|
||||
txpwr5gAdcScale=1
|
||||
dot11b_opts=0x3aa85
|
||||
cbfilttype=1
|
||||
fdsslevel_ch11=6
|
||||
|
||||
# Improved Bluetooth coexistence parameters from Cypress
|
||||
btc_mode=1
|
||||
btc_params8=0x4e20
|
||||
btc_params1=0x7530
|
||||
@@ -675,6 +675,61 @@ License: Cypress Wireless Connectivity Devices Driver End User License
|
||||
use with Cypress integrated circuit products. The full license text is
|
||||
included at 0:/os/licenses/Cypress-bt-firmware.txt.
|
||||
|
||||
================================================================================
|
||||
24. Cypress / Synaptics (Broadcom) Wi-Fi firmware
|
||||
================================================================================
|
||||
Shipped as binary firmware, CLM regulatory blobs and NVRAM files
|
||||
(0:/os/firmware/brcm/brcmfmac43455-sdio.{bin,clm_blob,txt} and
|
||||
brcmfmac43456-sdio.{bin,clm_blob,txt}), loaded at runtime into the CYW43455 /
|
||||
CYW43456 Wi-Fi radio of the Raspberry Pi 4 and 400 by the MontaukOS Wi-Fi
|
||||
driver. The files are taken unmodified from Raspberry Pi OS's firmware-nonfree
|
||||
package (brcmfmac43455-sdio.bin and .clm_blob are its
|
||||
cypress/cyfmac43455-sdio-standard.bin and cyfmac43455-sdio.clm_blob).
|
||||
|
||||
Upstream: https://github.com/RPi-Distro/firmware-nonfree
|
||||
(debian/config/brcm80211/)
|
||||
|
||||
CYW43455 firmware, CLM blob and NVRAM:
|
||||
Copyright (c) Cypress Semiconductor Corporation (an Infineon company)
|
||||
License: Cypress Wireless Connectivity Devices Driver End User License
|
||||
Agreement (the same text as for the Bluetooth firmware above); full
|
||||
text at 0:/os/licenses/Cypress-bt-firmware.txt. Reproduction and
|
||||
distribution in object code form are permitted solely for use with
|
||||
Cypress integrated circuit products.
|
||||
|
||||
CYW43456 firmware, CLM blob and NVRAM:
|
||||
Copyright (c) Synaptics Incorporated
|
||||
License: Synaptics Wireless Connectivity Devices Driver End User License
|
||||
Agreement (binary distribution); full text at
|
||||
0:/os/licenses/Synaptics-wifi-firmware.txt. Distribution in object code
|
||||
form is permitted solely for use with Synaptics integrated circuit
|
||||
products.
|
||||
|
||||
================================================================================
|
||||
25. Linux brcmfmac (Broadcom FullMAC Wi-Fi driver)
|
||||
================================================================================
|
||||
The MontaukOS Broadcom Wi-Fi driver (kernel/src/Drivers/Net/Wifi/Brcmf.hpp,
|
||||
BrcmfChip.cpp, BrcmfProto.cpp, BrcmfConnect.cpp) is derived in part from the
|
||||
brcmfmac driver in the Linux kernel (drivers/net/wireless/broadcom/brcm80211/):
|
||||
its chip enumeration, core reset, clock and firmware start-up sequences, NVRAM
|
||||
format, and the firmware interface's constants and structure layouts.
|
||||
|
||||
License: ISC License
|
||||
|
||||
Copyright (c) 2010-2014 Broadcom Corporation
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||
OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
For licensing inquiries regarding MontaukOS itself, contact: licensing@montaukos.org
|
||||
================================================================================
|
||||
@@ -0,0 +1,161 @@
|
||||
SYNAPTICS WIRELESS CONNECTIVITY DEVICES
|
||||
DRIVER END USER LICENSE AGREEMENT (BINARY DISTRIBUTION)
|
||||
|
||||
PLEASE READ THIS END USER LICENSE AGREEMENT ("Agreement") CAREFULLY BEFORE
|
||||
DOWNLOADING, INSTALLING, OR USING THIS SOFTWARE, ANY ACCOMPANYING
|
||||
DOCUMENTATION, OR ANY UPDATES PROVIDED BY SYNAPTICS ("Software"). BY
|
||||
DOWNLOADING, INSTALLING, OR USING THE SOFTWARE, YOU ARE AGREEING TO BE BOUND
|
||||
BY THIS AGREEMENT. IF YOU DO NOT AGREE TO ALL OF THE TERMS OF THIS
|
||||
AGREEMENT, PROMPTLY RETURN AND DO NOT USE THE SOFTWARE. IF YOU HAVE
|
||||
PURCHASED THE SOFTWARE, YOUR RIGHT TO RETURN THE SOFTWARE EXPIRES 30 DAYS
|
||||
AFTER YOUR PURCHASE AND APPLIES ONLY TO THE ORIGINAL PURCHASER.
|
||||
|
||||
Software Provided in Binary Code Form. This paragraph applies to any Software
|
||||
provided in binary code form. Subject to the terms and conditions of this
|
||||
Agreement, Synaptics Incorporated ("Synaptics") grants you a
|
||||
non-exclusive, non-transferable license under its copyright rights in the
|
||||
Software to reproduce and distribute the Software in object code form only,
|
||||
solely for use in connection with Synaptics integrated circuit products
|
||||
("Purpose").
|
||||
|
||||
Proprietary Rights. The Software, including all intellectual property rights
|
||||
therein, is and will remain the sole and exclusive property of Synaptics or its
|
||||
suppliers. Except as otherwise expressly provided in this Agreement, you may
|
||||
not: (i) modify, adapt, or create derivative works based upon the Software;
|
||||
(ii) copy the Software; (iii) except and only to the extent explicitly
|
||||
permitted by applicable law despite this limitation, decompile, translate,
|
||||
reverse engineer, disassemble or otherwise reduce the Software to
|
||||
human-readable form; or (iv) use the Software other than for the Purpose.
|
||||
|
||||
No Support. Synaptics may, but is not required to, provide technical support for
|
||||
the Software.
|
||||
|
||||
Term and Termination. This Agreement is effective until terminated. This
|
||||
Agreement and Your license rights will terminate immediately without notice
|
||||
from Synaptics if you fail to comply with any provision of this Agreement. Upon
|
||||
termination, you must destroy all copies of Software in your possession or
|
||||
control. Termination of this Agreement will not affect any licenses validly
|
||||
granted as of the termination date to any end users of the Software. The
|
||||
following paragraphs shall survive any termination of this Agreement:
|
||||
"Proprietary Rights," "Compliance With Law,"
|
||||
"Disclaimer," "Limitation of Liability," and "General."
|
||||
|
||||
Compliance With Law. Each party agrees to comply with all applicable laws,
|
||||
rules and regulations in connection with its activities under this Agreement.
|
||||
Without limiting the foregoing, the Software may be subject to export control
|
||||
laws and regulations of the United States and other countries. You agree to
|
||||
comply strictly with all such laws and regulations and acknowledge that you
|
||||
have the responsibility to obtain licenses to export, re-export, or import
|
||||
the Software.
|
||||
|
||||
Disclaimer. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, SYNAPTICS MAKES
|
||||
NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, WITH REGARD TO THE SOFTWARE,
|
||||
INCLUDING, BUT NOT LIMITED TO, INFRINGEMENT AND THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Synaptics reserves the
|
||||
right to make changes to the Software without notice. Synaptics does not assume
|
||||
any liability arising out of the application or use of Software or any
|
||||
product or circuit described in the Software. Synaptics does not authorize its
|
||||
products for use as critical components in life-support systems where a
|
||||
malfunction or failure may reasonably be expected to result in significant
|
||||
injury to the user. The inclusion of Synaptics product in a life-support
|
||||
system or application implies that the manufacturer of such system or
|
||||
application assumes all risk of such use and in doing so indemnifies Synaptics
|
||||
against all charges.
|
||||
|
||||
Limitation of Liability. IN NO EVENT WILL SYNAPTICS OR ITS SUPPLIERS,
|
||||
RESELLERS, OR DISTRIBUTORS BE LIABLE FOR ANY LOST REVENUE, PROFIT, OR DATA,
|
||||
OR FOR SPECIAL, INDIRECT, CONSEQUENTIAL, INCIDENTAL, OR PUNITIVE DAMAGES
|
||||
HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE
|
||||
USE OF OR INABILITY TO USE THE SOFTWARE EVEN IF SYNAPTICS OR ITS SUPPLIERS,
|
||||
RESELLERS, OR DISTRIBUTORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES. IN NO EVENT SHALL SYNAPTICS' OR ITS SUPPLIERS' RESELLERS', OR
|
||||
DISTRIBUTORS' TOTAL LIABILITY TO YOU, WHETHER IN CONTRACT, TORT (INCLUDING
|
||||
NEGLIGENCE), OR OTHERWISE, EXCEED THE PRICE PAID BY YOU FOR THE SOFTWARE.
|
||||
THE FOREGOING LIMITATIONS SHALL APPLY EVEN IF THE ABOVE-STATED WARRANTY FAILS
|
||||
OF ITS ESSENTIAL PURPOSE. BECAUSE SOME STATES OR JURISDICTIONS DO NOT ALLOW
|
||||
LIMITATION OR EXCLUSION OF CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE ABOVE
|
||||
LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
Restricted Rights. The Software under this Agreement is commercial computer
|
||||
software as that term is described in 48 C.F.R. 252.227-7014(a)(1). If
|
||||
acquired by or on behalf of a civilian agency, the U.S. Government acquires
|
||||
this commercial computer software and/or commercial computer software
|
||||
documentation subject to the terms of this Agreement as specified in 48
|
||||
C.F.R. 12.212 (Computer Software) and 12.211 (Technical Data) of the Federal
|
||||
Acquisition Regulations ("FAR") and its successors. If acquired by or on
|
||||
behalf of any agency within the Department of Defense ("DOD"), the U.S.
|
||||
Government acquires this commercial computer software and/or commercial
|
||||
computer software documentation subject to the terms of this Agreement as
|
||||
specified in 48 C.F.R. 227.7202-3 of the DOD FAR Supplement ("DFAR") and its
|
||||
successors.
|
||||
|
||||
General. This Agreement will bind and inure to the benefit of each party's
|
||||
successors and assigns, provided that you may not assign or transfer this
|
||||
Agreement, in whole or in part, without Synaptics' written consent. This
|
||||
Agreement shall be governed by and construed in accordance with the laws of
|
||||
the State of California, United States of America, as if performed wholly
|
||||
within the state and without giving effect to the principles of conflict of
|
||||
law. The parties consent to personal and exclusive jurisdiction of and venue
|
||||
in, the state and federal courts within Santa Clara County, California;
|
||||
provided however, that nothing in this Agreement will limit Synaptics' right to
|
||||
bring legal action in any venue in order to protect or enforce its
|
||||
intellectual property rights. No failure of either party to exercise or
|
||||
enforce any of its rights under this Agreement will act as a waiver of such
|
||||
rights. If any portion hereof is found to be void or unenforceable, the
|
||||
remaining provisions of this Agreement shall remain in full force and
|
||||
effect. This Agreement is the complete and exclusive agreement between the
|
||||
parties with respect to the subject matter hereof, superseding and replacing
|
||||
any and all prior agreements, communications, and understandings (both
|
||||
written and oral) regarding such subject matter. Any notice to Synaptics will
|
||||
application assumes all risk of such use and in doing so indemnifies Synaptics
|
||||
against all charges.
|
||||
|
||||
Limitation of Liability. IN NO EVENT WILL SYNAPTICS OR ITS SUPPLIERS,
|
||||
RESELLERS, OR DISTRIBUTORS BE LIABLE FOR ANY LOST REVENUE, PROFIT, OR DATA,
|
||||
OR FOR SPECIAL, INDIRECT, CONSEQUENTIAL, INCIDENTAL, OR PUNITIVE DAMAGES
|
||||
HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE
|
||||
USE OF OR INABILITY TO USE THE SOFTWARE EVEN IF SYNAPTICS OR ITS SUPPLIERS,
|
||||
RESELLERS, OR DISTRIBUTORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES. IN NO EVENT SHALL SYNAPTICS' OR ITS SUPPLIERS' RESELLERS', OR
|
||||
DISTRIBUTORS' TOTAL LIABILITY TO YOU, WHETHER IN CONTRACT, TORT (INCLUDING
|
||||
NEGLIGENCE), OR OTHERWISE, EXCEED THE PRICE PAID BY YOU FOR THE SOFTWARE.
|
||||
THE FOREGOING LIMITATIONS SHALL APPLY EVEN IF THE ABOVE-STATED WARRANTY FAILS
|
||||
OF ITS ESSENTIAL PURPOSE. BECAUSE SOME STATES OR JURISDICTIONS DO NOT ALLOW
|
||||
LIMITATION OR EXCLUSION OF CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE ABOVE
|
||||
LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
Restricted Rights. The Software under this Agreement is commercial computer
|
||||
software as that term is described in 48 C.F.R. 252.227-7014(a)(1). If
|
||||
acquired by or on behalf of a civilian agency, the U.S. Government acquires
|
||||
this commercial computer software and/or commercial computer software
|
||||
documentation subject to the terms of this Agreement as specified in 48
|
||||
C.F.R. 12.212 (Computer Software) and 12.211 (Technical Data) of the Federal
|
||||
Acquisition Regulations ("FAR") and its successors. If acquired by or on
|
||||
behalf of any agency within the Department of Defense ("DOD"), the U.S.
|
||||
Government acquires this commercial computer software and/or commercial
|
||||
computer software documentation subject to the terms of this Agreement as
|
||||
specified in 48 C.F.R. 227.7202-3 of the DOD FAR Supplement ("DFAR") and its
|
||||
successors.
|
||||
|
||||
General. This Agreement will bind and inure to the benefit of each party's
|
||||
successors and assigns, provided that you may not assign or transfer this
|
||||
Agreement, in whole or in part, without Synaptics' written consent. This
|
||||
Agreement shall be governed by and construed in accordance with the laws of
|
||||
the State of California, United States of America, as if performed wholly
|
||||
within the state and without giving effect to the principles of conflict of
|
||||
law. The parties consent to personal and exclusive jurisdiction of and venue
|
||||
in, the state and federal courts within Santa Clara County, California;
|
||||
provided however, that nothing in this Agreement will limit Synaptics' right to
|
||||
bring legal action in any venue in order to protect or enforce its
|
||||
intellectual property rights. No failure of either party to exercise or
|
||||
enforce any of its rights under this Agreement will act as a waiver of such
|
||||
rights. If any portion hereof is found to be void or unenforceable, the
|
||||
remaining provisions of this Agreement shall remain in full force and
|
||||
effect. This Agreement is the complete and exclusive agreement between the
|
||||
parties with respect to the subject matter hereof, superseding and replacing
|
||||
any and all prior agreements, communications, and understandings (both
|
||||
written and oral) regarding such subject matter. Any notice to Synaptics will
|
||||
be deemed effective when actually received and must be sent to Synaptics
|
||||
Incorporated, ATTN: General Counsel, 1109 McKay Dr.
|
||||
San Jose, CA 95131, USA
|
||||
|
||||
@@ -146,8 +146,8 @@ static abi::WifiNetwork g_nets[64];
|
||||
static bool require_adapter(abi::WifiInfo& info) {
|
||||
if (wifi_info(&info) != 0 || !info.present) {
|
||||
print("wifi: no supported Wi-Fi adapter found.\n");
|
||||
print(" Only Intel AX210-family devices with an RF type of GF "
|
||||
"(AX211) are supported.\n");
|
||||
print(" Supported: Intel AX210-family devices with an RF type of GF "
|
||||
"(AX211), and the Raspberry Pi 4/400's on-board radio.\n");
|
||||
return false;
|
||||
}
|
||||
if (info.state != abi::WIFI_STATE_RUNNING) {
|
||||
|
||||
@@ -0,0 +1,769 @@
|
||||
/*
|
||||
* brcmf_harness.cpp
|
||||
* Host harness for the Broadcom FullMAC driver's protocol and connection
|
||||
* layers (BrcmfProto.cpp, BrcmfConnect.cpp, with the real supplicant).
|
||||
*
|
||||
* The SDIO bus and the backplane are replaced by a simulated CYW4345x
|
||||
* firmware: it decodes every SDPCM frame the driver writes to function 2
|
||||
* (CDC ioctls, BDC data), answers ioctls the way the firmware does, and
|
||||
* feeds back events and data frames through the same framing. The access
|
||||
* point side of the WPA2 handshake is written here independently of
|
||||
* Wpa.cpp, on the crypto primitives crypto_vectors checks against the
|
||||
* published vectors.
|
||||
*
|
||||
* What stays untested is everything that needs the chip: the SDIO host,
|
||||
* the backplane, firmware download (BrcmfSdio.cpp, BrcmfChip.cpp), and
|
||||
* the firmware's own opinion of the structures it is sent.
|
||||
*
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Drivers/Net/Wifi/Brcmf.hpp"
|
||||
#include "Drivers/Net/Wifi/BrcmfSdio.hpp"
|
||||
#include "Drivers/Net/Wifi/Ieee80211.hpp"
|
||||
#include "Drivers/Net/Wifi/Wpa.hpp"
|
||||
#include "Drivers/Platform/RpiFirmware.hpp"
|
||||
#include "Libraries/Crypto.hpp"
|
||||
|
||||
using namespace Drivers::Net::Wifi;
|
||||
using Bytes = std::vector<uint8_t>;
|
||||
|
||||
namespace Timekeeping {
|
||||
uint64_t g_ms = 1000;
|
||||
uint64_t GetMilliseconds() { return g_ms; }
|
||||
void BusyWaitMs(uint64_t ms) { g_ms += ms; }
|
||||
void BusyWaitUs(uint64_t us) { g_ms += (us + 999) / 1000; }
|
||||
}
|
||||
|
||||
static int g_fails = 0;
|
||||
static void check(bool cond, const char* what) {
|
||||
printf(" %s %s\n", cond ? "PASS" : "FAIL", what);
|
||||
if (!cond) g_fails++;
|
||||
}
|
||||
|
||||
static uint16_t be16(const uint8_t* p) { return (uint16_t)(p[0] << 8 | p[1]); }
|
||||
static uint16_t le16(const uint8_t* p) { return (uint16_t)(p[0] | p[1] << 8); }
|
||||
static uint32_t le32(const uint8_t* p) { uint32_t v; memcpy(&v, p, 4); return v; }
|
||||
static void putle16(Bytes& b, uint16_t v) { b.push_back((uint8_t)v); b.push_back((uint8_t)(v >> 8)); }
|
||||
static void putle32(Bytes& b, uint32_t v) { for (int i = 0; i < 4; i++) b.push_back((uint8_t)(v >> (8 * i))); }
|
||||
static void putbe16(Bytes& b, uint16_t v) { b.push_back((uint8_t)(v >> 8)); b.push_back((uint8_t)v); }
|
||||
static void putbe32(Bytes& b, uint32_t v) { for (int i = 3; i >= 0; i--) b.push_back((uint8_t)(v >> (8 * i))); }
|
||||
static void append(Bytes& b, const uint8_t* p, size_t n) { b.insert(b.end(), p, p + n); }
|
||||
|
||||
static const uint8_t PLACEHOLDER_MAC[6] = { 0xb8, 0x27, 0xeb, 0x74, 0xf2, 0x6c };
|
||||
static const uint8_t BOARD_MAC[6] = { 0xdc, 0xa6, 0x32, 0x01, 0x02, 0xff };
|
||||
static const uint8_t WLAN_MAC[6] = { 0xdc, 0xa6, 0x32, 0x01, 0x03, 0x00 }; // board + 1, with carry
|
||||
static const uint8_t AP_OPEN[6] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x01 };
|
||||
static const uint8_t AP_WPA[6] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x02 };
|
||||
|
||||
// =============================================================================
|
||||
// Simulated firmware
|
||||
// =============================================================================
|
||||
|
||||
struct Ioctl {
|
||||
uint32_t Cmd;
|
||||
bool Set;
|
||||
std::string Name; // iovar name, for GET_VAR/SET_VAR
|
||||
Bytes Data; // what follows the name (or the whole payload)
|
||||
uint64_t Order;
|
||||
};
|
||||
|
||||
struct Fw {
|
||||
std::deque<Bytes> ToHost; // complete SDPCM frames
|
||||
Bytes Cur;
|
||||
size_t CurOff = 0;
|
||||
uint8_t HostSeq = 0; // next sequence number expected
|
||||
uint8_t Window = 8;
|
||||
bool SeqError = false;
|
||||
bool Mute = false; // stop answering ioctls
|
||||
std::vector<Ioctl> Ioctls;
|
||||
std::vector<std::pair<Bytes, uint64_t>> DataTx; // Ethernet frames, order
|
||||
uint64_t Order = 0;
|
||||
bool AutoJoin = true;
|
||||
void (*OnEapol)(const Bytes& body) = nullptr;
|
||||
};
|
||||
static Fw fw;
|
||||
|
||||
static void QueueFrame(uint8_t channel, const Bytes& payload) {
|
||||
Bytes f(12, 0);
|
||||
uint16_t len = (uint16_t)(12 + payload.size());
|
||||
f[0] = (uint8_t)len; f[1] = (uint8_t)(len >> 8);
|
||||
f[2] = (uint8_t)~len; f[3] = (uint8_t)(~len >> 8);
|
||||
f[4] = 0;
|
||||
f[5] = channel;
|
||||
f[7] = 12;
|
||||
f[9] = (uint8_t)(fw.HostSeq + fw.Window); // credit
|
||||
append(f, payload.data(), payload.size());
|
||||
fw.ToHost.push_back(f);
|
||||
}
|
||||
|
||||
static Bytes Bdc(const Bytes& eth) {
|
||||
Bytes b = { 0x20, 0, 0, 0 };
|
||||
append(b, eth.data(), eth.size());
|
||||
return b;
|
||||
}
|
||||
|
||||
static void QueueEvent(uint32_t type, uint32_t status, uint32_t reason, uint16_t flags,
|
||||
const uint8_t* addr, const Bytes& data = {}) {
|
||||
Bytes e;
|
||||
append(e, WLAN_MAC, 6);
|
||||
append(e, AP_WPA, 6);
|
||||
putbe16(e, 0x886C);
|
||||
putbe16(e, 0x8001);
|
||||
putbe16(e, (uint16_t)(48 + data.size()));
|
||||
e.push_back(1);
|
||||
e.push_back(0x00); e.push_back(0x10); e.push_back(0x18);
|
||||
putbe16(e, 1);
|
||||
putbe16(e, 2); // version
|
||||
putbe16(e, flags);
|
||||
putbe32(e, type);
|
||||
putbe32(e, status);
|
||||
putbe32(e, reason);
|
||||
putbe32(e, 0); // auth type
|
||||
putbe32(e, (uint32_t)data.size());
|
||||
Bytes a(6, 0);
|
||||
if (addr) memcpy(a.data(), addr, 6);
|
||||
append(e, a.data(), 6);
|
||||
Bytes ifname(16, 0);
|
||||
memcpy(ifname.data(), "wlan0", 5);
|
||||
append(e, ifname.data(), 16);
|
||||
e.push_back(0); // ifidx
|
||||
e.push_back(0); // bsscfgidx
|
||||
append(e, data.data(), data.size());
|
||||
QueueFrame(1, Bdc(e));
|
||||
}
|
||||
|
||||
static void QueueEthernet(const uint8_t* src, uint16_t type, const Bytes& body) {
|
||||
Bytes e;
|
||||
append(e, WLAN_MAC, 6);
|
||||
append(e, src, 6);
|
||||
putbe16(e, type);
|
||||
append(e, body.data(), body.size());
|
||||
QueueFrame(2, Bdc(e));
|
||||
}
|
||||
|
||||
// One brcmf_bss_info_le, laid out as Linux's struct.
|
||||
static Bytes BssInfo(const uint8_t* bssid, const char* ssid, uint16_t chanspec, uint8_t ctl,
|
||||
int16_t rssi, uint16_t caps, const Bytes& ies) {
|
||||
Bytes b(128, 0);
|
||||
memcpy(&b[0], "\x6d\x00\x00\x00", 4); // version 109
|
||||
uint32_t len = (uint32_t)(128 + ies.size());
|
||||
memcpy(&b[4], &len, 4);
|
||||
memcpy(&b[8], bssid, 6);
|
||||
uint16_t bcn = 100;
|
||||
memcpy(&b[14], &bcn, 2);
|
||||
memcpy(&b[16], &caps, 2);
|
||||
b[18] = (uint8_t)strlen(ssid);
|
||||
memcpy(&b[19], ssid, strlen(ssid));
|
||||
memcpy(&b[72], &chanspec, 2);
|
||||
b[76] = 3;
|
||||
memcpy(&b[78], &rssi, 2);
|
||||
b[88] = ctl;
|
||||
uint16_t ieOff = 128;
|
||||
memcpy(&b[116], &ieOff, 2);
|
||||
uint32_t ieLen = (uint32_t)ies.size();
|
||||
memcpy(&b[120], &ieLen, 4);
|
||||
append(b, ies.data(), ies.size());
|
||||
return b;
|
||||
}
|
||||
|
||||
static const Bytes RSN_CCMP = {
|
||||
0x01, 0x00, 0x00, 0x0f, 0xac, 0x04, 0x01, 0x00, 0x00, 0x0f, 0xac, 0x04,
|
||||
0x01, 0x00, 0x00, 0x0f, 0xac, 0x02, 0x00, 0x00,
|
||||
};
|
||||
static const Bytes RSN_GCMP = {
|
||||
0x01, 0x00, 0x00, 0x0f, 0xac, 0x08, 0x01, 0x00, 0x00, 0x0f, 0xac, 0x08,
|
||||
0x01, 0x00, 0x00, 0x0f, 0xac, 0x02, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static Bytes Ies(const char* ssid, uint8_t channel, const Bytes* rsn) {
|
||||
Bytes ie;
|
||||
ie.push_back(0); ie.push_back((uint8_t)strlen(ssid));
|
||||
append(ie, (const uint8_t*)ssid, strlen(ssid));
|
||||
ie.push_back(1); ie.push_back(4); ie.push_back(0x82); ie.push_back(0x84); ie.push_back(0x0b); ie.push_back(0x16);
|
||||
ie.push_back(3); ie.push_back(1); ie.push_back(channel);
|
||||
if (rsn) {
|
||||
ie.push_back(48); ie.push_back((uint8_t)rsn->size());
|
||||
append(ie, rsn->data(), rsn->size());
|
||||
}
|
||||
return ie;
|
||||
}
|
||||
|
||||
static void QueueEscan(uint16_t syncId) {
|
||||
Bytes bss1 = BssInfo(AP_OPEN, "OpenNet", 0x1006, 6, -48, 0x0401, Ies("OpenNet", 6, nullptr));
|
||||
Bytes bss2 = BssInfo(AP_WPA, "SecureNet", 0xd024, 36, -61, 0x0411, Ies("SecureNet", 36, &RSN_CCMP));
|
||||
for (const Bytes* bss : { &bss1, &bss2 }) {
|
||||
Bytes r;
|
||||
putle32(r, (uint32_t)(12 + bss->size()));
|
||||
putle32(r, 1);
|
||||
putle16(r, syncId);
|
||||
putle16(r, 1);
|
||||
append(r, bss->data(), bss->size());
|
||||
QueueEvent(69, 8, 0, 0, nullptr, r); // PARTIAL
|
||||
}
|
||||
QueueEvent(69, 0, 0, 0, nullptr); // done
|
||||
}
|
||||
|
||||
static void FwControl(const uint8_t* p, size_t len) {
|
||||
uint32_t cmd = le32(p), dlen = le32(p + 4), flags = le32(p + 8);
|
||||
bool set = flags & 2;
|
||||
const uint8_t* data = p + 16;
|
||||
size_t avail = len - 16;
|
||||
if (dlen > avail) dlen = (uint32_t)avail;
|
||||
|
||||
Ioctl io{cmd, set, "", {}, fw.Order++};
|
||||
if (cmd == 262 || cmd == 263) {
|
||||
size_t n = strnlen((const char*)data, dlen);
|
||||
io.Name.assign((const char*)data, n);
|
||||
if (n + 1 < dlen) io.Data.assign(data + n + 1, data + dlen);
|
||||
} else {
|
||||
io.Data.assign(data, data + dlen);
|
||||
}
|
||||
fw.Ioctls.push_back(io);
|
||||
if (fw.Mute) return;
|
||||
|
||||
int32_t status = 0;
|
||||
Bytes resp(dlen, 0);
|
||||
if (cmd == 262) {
|
||||
if (io.Name == "cur_etheraddr") memcpy(resp.data(), PLACEHOLDER_MAC, 6);
|
||||
else if (io.Name == "ver") {
|
||||
const char* v = "wl0: Aug 29 2023 01:51:02 version 7.45.265 (28bca26 CY) FWID 01-b677b91b\n";
|
||||
memcpy(resp.data(), v, strlen(v) < dlen ? strlen(v) : dlen);
|
||||
} else if (io.Name == "clmload_status") {
|
||||
} else status = -23; // BCME_UNSUPPORTED
|
||||
} else if (cmd == 263) {
|
||||
if (io.Name == "escan") {
|
||||
QueueEscan(le16(io.Data.data() + 6));
|
||||
} else if (io.Name == "join" && fw.AutoJoin) {
|
||||
QueueEvent(0, 0, 0, 0, io.Data.data() + 56);
|
||||
QueueEvent(16, 0, 0, 1, io.Data.data() + 56);
|
||||
} else if (io.Name == "bus:rxglom" || io.Name == "mfp" || io.Name == "sup_wpa") {
|
||||
status = -23;
|
||||
}
|
||||
} else if (cmd == 217) { // valid channels
|
||||
uint32_t list[3] = { 2, 1, 36 };
|
||||
memcpy(resp.data(), list, sizeof(list) < dlen ? sizeof(list) : dlen);
|
||||
}
|
||||
|
||||
Bytes r;
|
||||
putle32(r, cmd);
|
||||
putle32(r, dlen);
|
||||
putle32(r, (flags & 0xFFFF0002u) | (status < 0 ? 1 : 0));
|
||||
putle32(r, (uint32_t)status);
|
||||
append(r, resp.data(), resp.size());
|
||||
QueueFrame(0, r);
|
||||
}
|
||||
|
||||
static void FwReceive(const uint8_t* f, size_t busLen) {
|
||||
uint16_t len = le16(f), check = le16(f + 2);
|
||||
if ((uint16_t)(len ^ check) != 0xFFFF || len > busLen) { fw.SeqError = true; return; }
|
||||
if (f[4] != fw.HostSeq) fw.SeqError = true;
|
||||
fw.HostSeq = (uint8_t)(f[4] + 1);
|
||||
uint8_t ch = f[5] & 0x0F, off = f[7];
|
||||
const uint8_t* p = f + off;
|
||||
size_t plen = len - off;
|
||||
if (ch == 0) {
|
||||
FwControl(p, plen);
|
||||
} else if (ch == 2) {
|
||||
if (plen < 4 || (p[0] >> 4) != 2 || p[3] != 0) { fw.SeqError = true; return; }
|
||||
Bytes eth(p + 4, p + plen);
|
||||
fw.DataTx.push_back({eth, fw.Order++});
|
||||
if (eth.size() >= 14 && be16(ð[12]) == 0x888E && fw.OnEapol)
|
||||
fw.OnEapol(Bytes(eth.begin() + 14, eth.end()));
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// What the driver links against instead of the hardware
|
||||
// =============================================================================
|
||||
|
||||
namespace Drivers::Net::Wifi {
|
||||
|
||||
BrcmfDevice g_brcmf;
|
||||
|
||||
bool BrcmfBpRead32(uint32_t addr, uint32_t& value) {
|
||||
value = 0;
|
||||
if (addr == g_brcmf.SdioCoreBase + 0x20 && (!fw.ToHost.empty() || !fw.Cur.empty()))
|
||||
value = 0x40; // I_HMB_FRAME_IND
|
||||
return true;
|
||||
}
|
||||
bool BrcmfBpWrite32(uint32_t, uint32_t) { return true; }
|
||||
bool BrcmfBpReadMem(uint32_t, uint8_t*, uint32_t) { return true; }
|
||||
bool BrcmfBpWriteMem(uint32_t, const uint8_t*, uint32_t) { return true; }
|
||||
bool BrcmfBpSelectWindow(uint32_t) { return true; }
|
||||
bool BrcmfF1Read8(uint32_t, uint8_t& v) { v = 0; return true; }
|
||||
bool BrcmfF1Write8(uint32_t, uint8_t) { return true; }
|
||||
|
||||
bool BrcmfChipBringUp() {
|
||||
g_brcmf.FwName = "43456";
|
||||
g_brcmf.SdioCoreBase = 0x18002000;
|
||||
g_brcmf.HostIntMask = 0x200000F0;
|
||||
return true;
|
||||
}
|
||||
void BrcmfChipPowerOff() {}
|
||||
void BrcmfDumpConsole() {}
|
||||
|
||||
uint8_t* BrcmfLoadFile(const char* ext, uint32_t& size, bool) {
|
||||
if (strcmp(ext, "clm_blob") != 0) return nullptr;
|
||||
size = 3000;
|
||||
uint8_t* p = (uint8_t*)malloc(size);
|
||||
for (uint32_t i = 0; i < size; i++) p[i] = (uint8_t)i;
|
||||
return p;
|
||||
}
|
||||
|
||||
struct Seen { Bytes Bssid; Bytes Ies; uint16_t Beacon, Caps; uint8_t Channel; int8_t Rssi; };
|
||||
static std::vector<Seen> g_seen;
|
||||
static std::vector<Bytes> g_delivered;
|
||||
|
||||
void WifiRxBssInfo(const uint8_t* bssid, uint16_t beaconInterval, uint16_t capability,
|
||||
const uint8_t* ies, uint32_t iesLen, uint8_t channel, int8_t rssiDbm) {
|
||||
g_seen.push_back({Bytes(bssid, bssid + 6), Bytes(ies, ies + iesLen), beaconInterval,
|
||||
capability, channel, rssiDbm});
|
||||
}
|
||||
void WifiRxEthernet(const uint8_t* frame, uint32_t len) {
|
||||
g_delivered.push_back(Bytes(frame, frame + len));
|
||||
}
|
||||
|
||||
// Routed by Wifi.cpp in the kernel.
|
||||
bool WpaTxEapol(const uint8_t* body, uint32_t len) { return BrcmfWpaTxEapol(body, len); }
|
||||
bool WpaInstallPtk(const uint8_t* tk, uint32_t tkLen, uint8_t cipher) {
|
||||
return BrcmfWpaInstallPtk(tk, tkLen, cipher);
|
||||
}
|
||||
bool WpaInstallGtk(const uint8_t* gtk, uint32_t gtkLen, uint8_t keyIdx,
|
||||
uint8_t cipher, const uint8_t* rsc) {
|
||||
return BrcmfWpaInstallGtk(gtk, gtkLen, keyIdx, cipher, rsc);
|
||||
}
|
||||
}
|
||||
|
||||
namespace Drivers::Net::Wifi::Sdio {
|
||||
bool HostInit(uint64_t, uint32_t) { return true; }
|
||||
bool CardInit() { return true; }
|
||||
bool Read8(uint32_t, uint32_t, uint8_t& v) { v = 0; return true; }
|
||||
bool Write8(uint32_t, uint32_t, uint8_t) { return true; }
|
||||
bool EnableFunction(uint32_t, bool, uint64_t) { return true; }
|
||||
bool EnableCardInterrupts(uint8_t) { return true; }
|
||||
void Abort(uint32_t) {}
|
||||
bool CardInterruptPending() { return !fw.ToHost.empty(); }
|
||||
bool IsInitialized() { return true; }
|
||||
|
||||
bool ReadBlock(uint32_t fn, uint32_t, bool, uint8_t* buffer, uint32_t len) {
|
||||
if (fn != FN2 || (len & 3)) return false;
|
||||
Timekeeping::g_ms++;
|
||||
if (fw.Cur.empty()) {
|
||||
if (fw.ToHost.empty()) { memset(buffer, 0, len); return true; }
|
||||
fw.Cur = fw.ToHost.front();
|
||||
fw.ToHost.pop_front();
|
||||
fw.CurOff = 0;
|
||||
}
|
||||
for (uint32_t i = 0; i < len; i++, fw.CurOff++)
|
||||
buffer[i] = fw.CurOff < fw.Cur.size() ? fw.Cur[fw.CurOff] : 0;
|
||||
if (fw.CurOff >= ((fw.Cur.size() + 3) & ~(size_t)3)) fw.Cur.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteBlock(uint32_t fn, uint32_t, bool, const uint8_t* buffer, uint32_t len) {
|
||||
if (fn != FN2 || (len & 3)) return false;
|
||||
if (len > FN2_BLOCK && len % FN2_BLOCK) fw.SeqError = true; // padded to whole blocks
|
||||
Timekeeping::g_ms++;
|
||||
FwReceive(buffer, len);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Drivers::Platform::RpiFirmware {
|
||||
bool GetBoardMacAddress(uint8_t mac[6]) { memcpy(mac, BOARD_MAC, 6); return true; }
|
||||
bool SetGpioOutput(uint32_t, bool) { return true; }
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// An independent WPA2-PSK authenticator
|
||||
// =============================================================================
|
||||
|
||||
namespace Ap {
|
||||
using namespace Kt::Crypto;
|
||||
static const char* SSID = "SecureNet";
|
||||
static const char* PASS = "supersecret123";
|
||||
static uint8_t pmk[32], ptk[48], anonce[32], snonce[32], gtk[16];
|
||||
static uint64_t replay = 0;
|
||||
static Bytes lastMsg2, lastMsg4, staRsn;
|
||||
static bool msg2MicOk = false, msg4MicOk = false;
|
||||
|
||||
static void Prf(const uint8_t* key, const char* label, const Bytes& data, uint8_t* out, size_t n) {
|
||||
uint8_t counter = 0;
|
||||
size_t done = 0;
|
||||
while (done < n) {
|
||||
Bytes m((const uint8_t*)label, (const uint8_t*)label + strlen(label) + 1);
|
||||
append(m, data.data(), data.size());
|
||||
m.push_back(counter++);
|
||||
uint8_t h[20];
|
||||
HmacSha1(key, 32, m.data(), m.size(), h);
|
||||
size_t take = n - done < 20 ? n - done : 20;
|
||||
memcpy(out + done, h, take);
|
||||
done += take;
|
||||
}
|
||||
}
|
||||
|
||||
static Bytes KeyFrame(uint16_t info, const Bytes& keyData, bool mic) {
|
||||
Bytes k(99, 0);
|
||||
k[0] = 2; k[1] = 3;
|
||||
uint16_t bodyLen = (uint16_t)(95 + keyData.size());
|
||||
k[2] = (uint8_t)(bodyLen >> 8); k[3] = (uint8_t)bodyLen;
|
||||
k[4] = 2;
|
||||
k[5] = (uint8_t)(info >> 8); k[6] = (uint8_t)info;
|
||||
k[8] = 16;
|
||||
for (int i = 0; i < 8; i++) k[9 + i] = (uint8_t)(replay >> (56 - 8 * i));
|
||||
memcpy(&k[17], anonce, 32);
|
||||
k[97] = (uint8_t)(keyData.size() >> 8); k[98] = (uint8_t)keyData.size();
|
||||
append(k, keyData.data(), keyData.size());
|
||||
if (mic) {
|
||||
uint8_t h[20];
|
||||
HmacSha1(ptk, 16, k.data(), k.size(), h);
|
||||
memcpy(&k[81], h, 16);
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
static bool MicOk(Bytes frame) {
|
||||
if (frame.size() < 99) return false;
|
||||
uint8_t mic[16];
|
||||
memcpy(mic, &frame[81], 16);
|
||||
memset(&frame[81], 0, 16);
|
||||
uint8_t h[20];
|
||||
HmacSha1(ptk, 16, frame.data(), frame.size(), h);
|
||||
return memcmp(h, mic, 16) == 0;
|
||||
}
|
||||
|
||||
static void Start(const char* pass) {
|
||||
Pbkdf2Sha1(pass, strlen(pass), (const uint8_t*)SSID, strlen(SSID), 4096, pmk, 32);
|
||||
for (int i = 0; i < 32; i++) anonce[i] = (uint8_t)(0xA0 + i);
|
||||
for (int i = 0; i < 16; i++) gtk[i] = (uint8_t)(0x30 + i);
|
||||
replay = 1;
|
||||
lastMsg2.clear(); lastMsg4.clear(); staRsn.clear();
|
||||
msg2MicOk = msg4MicOk = false;
|
||||
QueueEthernet(AP_WPA, 0x888E, KeyFrame(0x008A, {}, false)); // msg 1
|
||||
}
|
||||
|
||||
static void OnEapol(const Bytes& f) {
|
||||
if (f.size() < 99 || f[1] != 3) return;
|
||||
uint16_t info = be16(&f[5]);
|
||||
if (!(info & 0x0100)) return;
|
||||
if (lastMsg2.empty()) {
|
||||
lastMsg2 = f;
|
||||
memcpy(snonce, &f[17], 32);
|
||||
Bytes data;
|
||||
const uint8_t* a = memcmp(AP_WPA, WLAN_MAC, 6) < 0 ? AP_WPA : WLAN_MAC;
|
||||
const uint8_t* b = a == AP_WPA ? WLAN_MAC : AP_WPA;
|
||||
append(data, a, 6); append(data, b, 6);
|
||||
const uint8_t* n1 = memcmp(anonce, snonce, 32) < 0 ? anonce : snonce;
|
||||
const uint8_t* n2 = n1 == anonce ? snonce : anonce;
|
||||
append(data, n1, 32); append(data, n2, 32);
|
||||
Prf(pmk, "Pairwise key expansion", data, ptk, 48);
|
||||
msg2MicOk = MicOk(f);
|
||||
uint16_t kdl = be16(&f[97]);
|
||||
staRsn.assign(f.begin() + 99, f.begin() + 99 + kdl);
|
||||
if (!msg2MicOk) {
|
||||
// Wrong passphrase: the AP gives up and deauthenticates.
|
||||
QueueEvent(6, 0, 15, 0, AP_WPA);
|
||||
return;
|
||||
}
|
||||
// Message 3: our RSN IE and the GTK KDE, key-wrapped under the KEK.
|
||||
Bytes plain = { 0x30, (uint8_t)RSN_CCMP.size() };
|
||||
append(plain, RSN_CCMP.data(), RSN_CCMP.size());
|
||||
Bytes kde = { 0xdd, 22, 0x00, 0x0f, 0xac, 0x01, 0x01, 0x00 };
|
||||
append(kde, gtk, 16);
|
||||
append(plain, kde.data(), kde.size());
|
||||
plain.push_back(0xdd);
|
||||
while (plain.size() % 8) plain.push_back(0);
|
||||
Bytes wrapped(plain.size() + 8);
|
||||
AesKeyWrap(ptk + 16, 16, plain.data(), plain.size(), wrapped.data());
|
||||
replay = 2;
|
||||
QueueEthernet(AP_WPA, 0x888E, KeyFrame(0x13CA, wrapped, true));
|
||||
} else if (lastMsg4.empty()) {
|
||||
lastMsg4 = f;
|
||||
msg4MicOk = MicOk(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Cases
|
||||
// =============================================================================
|
||||
|
||||
static void Pump(int rounds = 20) {
|
||||
for (int i = 0; i < rounds; i++) {
|
||||
BrcmfService();
|
||||
Timekeeping::g_ms += 5;
|
||||
}
|
||||
}
|
||||
|
||||
static const Ioctl* LastSet(const char* name) {
|
||||
for (auto it = fw.Ioctls.rbegin(); it != fw.Ioctls.rend(); ++it)
|
||||
if (it->Set && it->Name == name) return &*it;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static uint32_t SetInt(const char* name) {
|
||||
const Ioctl* io = LastSet(name);
|
||||
return io && io->Data.size() >= 4 ? le32(io->Data.data()) : 0xDEADBEEF;
|
||||
}
|
||||
|
||||
static const Ioctl* LastCmd(uint32_t cmd) {
|
||||
for (auto it = fw.Ioctls.rbegin(); it != fw.Ioctls.rend(); ++it)
|
||||
if (it->Cmd == cmd) return &*it;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static constexpr int CONN_IDLE = 0, CONN_ASSOCIATING = 4, CONN_HANDSHAKING = 6,
|
||||
CONN_CONNECTED = 7, CONN_FAILED = 8;
|
||||
|
||||
static void CaseInit() {
|
||||
printf("== bring-up and preinit ==\n");
|
||||
check(BrcmfProbe(0xFE300000), "the Arasan controller is claimed");
|
||||
check(BrcmfCompleteInit(), "preinit completes against the firmware");
|
||||
check(!fw.SeqError, "every frame carries the next sequence number and a valid header");
|
||||
check(BrcmfGetState() == BrcmfState::Running, "the radio is running");
|
||||
check(memcmp(g_brcmf.Mac, WLAN_MAC, 6) == 0, "the NVRAM placeholder MAC becomes the board address + 1");
|
||||
const Ioctl* mac = LastSet("cur_etheraddr");
|
||||
check(mac && mac->Data.size() == 6 && memcmp(mac->Data.data(), WLAN_MAC, 6) == 0,
|
||||
"and is programmed into the firmware");
|
||||
check(strcmp(g_brcmf.FwVersion, "7.45.265") == 0, "the firmware version is read from \"ver\"");
|
||||
|
||||
std::vector<const Ioctl*> clm;
|
||||
for (auto& io : fw.Ioctls) if (io.Set && io.Name == "clmload") clm.push_back(&io);
|
||||
check(clm.size() == 3, "a 3000-byte CLM blob goes down in three chunks");
|
||||
if (clm.size() == 3) {
|
||||
check(le16(clm[0]->Data.data()) == 0x1002 && le16(clm[1]->Data.data()) == 0x1000 &&
|
||||
le16(clm[2]->Data.data()) == 0x1004, "flagged begin / middle / end, handler version 1");
|
||||
check(le16(clm[0]->Data.data() + 2) == 2, "of type CLM");
|
||||
check(le32(clm[0]->Data.data() + 4) == 1400 && le32(clm[2]->Data.data() + 4) == 200,
|
||||
"with 1400-byte chunks and the remainder last");
|
||||
check(clm[1]->Data[12] == (uint8_t)1400 && clm[2]->Data[12 + 199] == (uint8_t)2999,
|
||||
"carrying the blob's bytes in order");
|
||||
}
|
||||
|
||||
const Ioctl* ev = LastSet("event_msgs");
|
||||
bool evOk = ev && ev->Data.size() == 16;
|
||||
if (evOk) {
|
||||
for (uint32_t e : { 0u, 5u, 6u, 12u, 16u, 69u })
|
||||
evOk = evOk && (ev->Data[e / 8] & (1u << (e % 8)));
|
||||
}
|
||||
check(evOk, "events for join, link, deauth/disassoc and escan are enabled");
|
||||
check(SetInt("roam_off") == 1, "firmware roaming is off");
|
||||
const Ioctl* up = LastCmd(2);
|
||||
check(up && up->Set, "WLC_UP is sent");
|
||||
const Ioctl* pm = LastCmd(86);
|
||||
check(pm && le32(pm->Data.data()) == 0, "power save is off");
|
||||
check(g_brcmf.ChannelCount == 2 && g_brcmf.Band24 && g_brcmf.Band5,
|
||||
"the valid channel list gives the channel count and bands");
|
||||
|
||||
uint32_t v = 0;
|
||||
check(!BrcmfIovarGetInt("no_such_iovar", v, true), "an iovar the firmware rejects reports failure");
|
||||
check(BrcmfGetState() == BrcmfState::Running, "without counting against the firmware's health");
|
||||
}
|
||||
|
||||
static void CaseScan() {
|
||||
printf("== escan ==\n");
|
||||
check(BrcmfStartScan(), "a scan starts");
|
||||
const Ioctl* es = LastSet("escan");
|
||||
check(es && es->Data.size() == 72, "escan carries the 72-byte v1 parameter block");
|
||||
if (es && es->Data.size() == 72) {
|
||||
const uint8_t* p = es->Data.data();
|
||||
check(le32(p) == 1 && le16(p + 4) == 1, "version 1, action start");
|
||||
bool bcast = true;
|
||||
for (int i = 0; i < 6; i++) bcast = bcast && p[44 + i] == 0xFF;
|
||||
check(bcast && p[50] == 2 && p[51] == 0, "broadcast BSSID, any BSS type, active");
|
||||
check(le32(p + 52) == 0xFFFFFFFF && le32(p + 68) == 0, "default dwells, every channel");
|
||||
}
|
||||
Pump();
|
||||
check(!BrcmfScanActive(), "the completion event ends the scan");
|
||||
check(g_seen.size() == 2, "both BSSs reach the scan table");
|
||||
if (g_seen.size() == 2) {
|
||||
check(memcmp(g_seen[0].Bssid.data(), AP_OPEN, 6) == 0 && g_seen[0].Channel == 6 &&
|
||||
g_seen[0].Rssi == -48 && g_seen[0].Beacon == 100, "BSSID, channel, RSSI, beacon interval");
|
||||
check(g_seen[1].Channel == 36 && (g_seen[1].Caps & 0x10), "the 5 GHz network, privacy set");
|
||||
check(g_seen[1].Ies.size() == Ies("SecureNet", 36, &RSN_CCMP).size() && g_seen[1].Ies[0] == 0,
|
||||
"with its information elements intact");
|
||||
}
|
||||
}
|
||||
|
||||
static void CaseOpenJoin() {
|
||||
printf("== open network ==\n");
|
||||
check(BrcmfConnectStart(AP_OPEN, 6, "OpenNet", nullptr, nullptr, 0), "the join is accepted");
|
||||
check(SetInt("wpa_auth") == 0 && SetInt("wsec") == 0 && SetInt("auth") == 0,
|
||||
"no WPA, no encryption, open system authentication");
|
||||
const Ioctl* j = LastSet("join");
|
||||
check(j && j->Data.size() == 72, "join carries brcmf_ext_join_params_le");
|
||||
if (j && j->Data.size() == 72) {
|
||||
const uint8_t* p = j->Data.data();
|
||||
check(le32(p) == 7 && memcmp(p + 4, "OpenNet", 7) == 0, "the SSID");
|
||||
check(memcmp(p + 56, AP_OPEN, 6) == 0 && le32(p + 64) == 1 && le16(p + 68) == 0x1006,
|
||||
"the BSSID and the chanspec escan reported");
|
||||
}
|
||||
check(BrcmfConnectState() == CONN_ASSOCIATING, "associating until the firmware says otherwise");
|
||||
Pump();
|
||||
check(BrcmfConnectState() == CONN_CONNECTED && BrcmfLinkUp(), "E_SET_SSID + E_LINK bring the link up");
|
||||
|
||||
Bytes eth;
|
||||
append(eth, AP_OPEN, 6); append(eth, WLAN_MAC, 6); putbe16(eth, 0x0800);
|
||||
for (int i = 0; i < 100; i++) eth.push_back((uint8_t)i);
|
||||
size_t before = fw.DataTx.size();
|
||||
check(BrcmfSendEthernet(eth.data(), (uint32_t)eth.size()), "an Ethernet frame is accepted");
|
||||
Pump(2);
|
||||
check(fw.DataTx.size() == before + 1 && fw.DataTx.back().first == eth,
|
||||
"and reaches the firmware unchanged behind a BDC header");
|
||||
|
||||
Bytes big = eth;
|
||||
big.resize(1514, 0x5a);
|
||||
check(BrcmfSendEthernet(big.data(), (uint32_t)big.size()), "a full-size frame is accepted");
|
||||
Pump(2);
|
||||
check(fw.DataTx.back().first == big && !fw.SeqError, "and goes out padded to whole blocks");
|
||||
|
||||
g_delivered.clear();
|
||||
QueueEthernet(AP_OPEN, 0x0800, Bytes(60, 0x11));
|
||||
Pump(2);
|
||||
check(g_delivered.size() == 1 && g_delivered[0].size() == 74 && be16(&g_delivered[0][12]) == 0x0800,
|
||||
"a received data frame is delivered to the stack");
|
||||
|
||||
QueueEvent(6, 0, 3, 0, AP_OPEN); // DEAUTH_IND
|
||||
Pump();
|
||||
check(BrcmfConnectState() == CONN_IDLE && !BrcmfLinkUp(), "a deauthentication takes the link down");
|
||||
check(!BrcmfSendEthernet(eth.data(), (uint32_t)eth.size()), "and nothing is sent while it is down");
|
||||
}
|
||||
|
||||
static const Ioctl* FindKey(uint32_t index) {
|
||||
for (auto it = fw.Ioctls.rbegin(); it != fw.Ioctls.rend(); ++it)
|
||||
if (it->Set && it->Name == "wsec_key" && it->Data.size() == 164 && le32(it->Data.data()) == index)
|
||||
return &*it;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void CaseWpaJoin() {
|
||||
printf("== WPA2-PSK ==\n");
|
||||
fw.OnEapol = Ap::OnEapol;
|
||||
size_t ioctlsBefore = fw.Ioctls.size();
|
||||
check(BrcmfConnectStart(AP_WPA, 36, "SecureNet", Ap::PASS, RSN_CCMP.data(), (uint8_t)RSN_CCMP.size()),
|
||||
"the join is accepted");
|
||||
check(SetInt("wpa_auth") == 0x80 && SetInt("wsec") == 4 && SetInt("auth") == 0,
|
||||
"WPA2-PSK, AES, open system authentication");
|
||||
const Ioctl* ie = LastSet("wpaie");
|
||||
Bytes want = { 0x30, (uint8_t)RSN_CCMP.size() };
|
||||
append(want, RSN_CCMP.data(), RSN_CCMP.size());
|
||||
check(ie && ie->Data == want, "the supplicant's RSN element goes into the association request");
|
||||
const Ioctl* j = LastSet("join");
|
||||
check(j && le16(j->Data.data() + 68) == 0xd024, "the 5 GHz chanspec from the scan");
|
||||
|
||||
Pump();
|
||||
check(BrcmfConnectState() == CONN_HANDSHAKING && !BrcmfLinkUp(),
|
||||
"association waits for the handshake before the link is usable");
|
||||
|
||||
Ap::Start(Ap::PASS);
|
||||
Pump(40);
|
||||
check(!Ap::lastMsg2.empty() && Ap::msg2MicOk, "message 2 carries a MIC the AP verifies");
|
||||
check(Ap::staRsn == want, "and the same RSN element");
|
||||
check(!Ap::lastMsg4.empty() && Ap::msg4MicOk, "message 4 follows message 3, MIC verified");
|
||||
check(BrcmfConnectState() == CONN_CONNECTED && BrcmfLinkUp(), "the link is up once keyed");
|
||||
|
||||
const Ioctl* ptk = FindKey(0);
|
||||
const Ioctl* gtk = FindKey(1);
|
||||
check(ptk && memcmp(ptk->Data.data() + 8, Ap::ptk + 32, 16) == 0 && le32(ptk->Data.data() + 4) == 16,
|
||||
"the pairwise key is the TK the AP derived");
|
||||
if (ptk) {
|
||||
const uint8_t* k = ptk->Data.data();
|
||||
check(le32(k + 112) == 4 && le32(k + 116) == 0 && memcmp(k + 156, AP_WPA, 6) == 0,
|
||||
"AES-CCM, bound to the AP's address");
|
||||
}
|
||||
check(gtk && memcmp(gtk->Data.data() + 8, Ap::gtk, 16) == 0, "the group key is the AP's GTK");
|
||||
if (gtk) {
|
||||
const uint8_t* k = gtk->Data.data();
|
||||
bool noEa = true;
|
||||
for (int i = 0; i < 6; i++) noEa = noEa && k[156 + i] == 0;
|
||||
check(le32(k + 116) == 2 && noEa && le32(k + 132) == 1, "primary, broadcast, receive IV set");
|
||||
}
|
||||
uint64_t msg4Order = 0;
|
||||
for (auto& d : fw.DataTx)
|
||||
if (d.first.size() > 14 && be16(&d.first[12]) == 0x888E) msg4Order = d.second;
|
||||
check(ptk && msg4Order != 0 && msg4Order < ptk->Order,
|
||||
"message 4 reaches the radio before the pairwise key is installed");
|
||||
(void)ioctlsBefore;
|
||||
|
||||
QueueEvent(16, 0, 0, 0, AP_WPA); // link down
|
||||
Pump();
|
||||
check(BrcmfConnectState() == CONN_IDLE, "losing the link returns to idle");
|
||||
}
|
||||
|
||||
static void CaseWrongPassphrase() {
|
||||
printf("== wrong passphrase ==\n");
|
||||
check(BrcmfConnectStart(AP_WPA, 36, "SecureNet", "not-the-password", RSN_CCMP.data(),
|
||||
(uint8_t)RSN_CCMP.size()), "the join is accepted");
|
||||
Pump();
|
||||
Ap::Start(Ap::PASS);
|
||||
Pump(40);
|
||||
check(!Ap::lastMsg2.empty() && !Ap::msg2MicOk, "the AP rejects message 2's MIC");
|
||||
check(BrcmfConnectState() == CONN_FAILED, "the deauthentication fails the join");
|
||||
check(BrcmfJoinFailedOnAuth(), "and it is reported as an authentication failure");
|
||||
BrcmfConnectAbort();
|
||||
const Ioctl* d = LastCmd(52);
|
||||
check(d && d->Set && memcmp(d->Data.data() + 4, AP_WPA, 6) == 0, "abort disassociates from the AP");
|
||||
check(BrcmfConnectState() == CONN_IDLE, "and leaves the connection idle");
|
||||
}
|
||||
|
||||
static void CaseRefusals() {
|
||||
printf("== refusals ==\n");
|
||||
check(!BrcmfConnectStart(AP_WPA, 36, "SecureNet", Ap::PASS, RSN_GCMP.data(), (uint8_t)RSN_GCMP.size()),
|
||||
"a GCMP network is refused before anything is sent");
|
||||
check(BrcmfConnectRefusedForSecurity(), "as a security refusal");
|
||||
check(!BrcmfConnectStart(AP_WPA, 36, "SecureNet", nullptr, RSN_CCMP.data(), (uint8_t)RSN_CCMP.size()),
|
||||
"an encrypted network without a passphrase is refused");
|
||||
}
|
||||
|
||||
static void CaseJoinTimeout() {
|
||||
printf("== join timeout ==\n");
|
||||
fw.AutoJoin = false;
|
||||
check(BrcmfConnectStart(AP_OPEN, 6, "OpenNet", nullptr, nullptr, 0), "the join is accepted");
|
||||
Timekeeping::g_ms += 13000;
|
||||
Pump(2);
|
||||
check(BrcmfConnectState() == CONN_FAILED, "no answer from the firmware fails the join in bounded time");
|
||||
BrcmfConnectAbort();
|
||||
|
||||
check(BrcmfConnectStart(AP_OPEN, 6, "OpenNet", nullptr, nullptr, 0), "another join is accepted");
|
||||
QueueEvent(0, 3, 0, 0, AP_OPEN); // SET_SSID: no networks
|
||||
Pump();
|
||||
check(BrcmfConnectState() == CONN_FAILED, "an E_SET_SSID failure fails it at once");
|
||||
BrcmfConnectAbort();
|
||||
fw.AutoJoin = true;
|
||||
}
|
||||
|
||||
static void CaseDeadFirmware() {
|
||||
printf("== firmware stops answering ==\n");
|
||||
fw.Mute = true;
|
||||
uint32_t v = 0;
|
||||
for (int i = 0; i < 3; i++) BrcmfIovarGetInt("ver", v, true);
|
||||
check(BrcmfGetState() == BrcmfState::Error, "three unanswered commands mark the radio failed");
|
||||
check(!BrcmfStartScan(), "and nothing more is attempted");
|
||||
fw.Mute = false;
|
||||
}
|
||||
|
||||
int main() {
|
||||
CaseInit();
|
||||
CaseScan();
|
||||
CaseOpenJoin();
|
||||
CaseWpaJoin();
|
||||
CaseWrongPassphrase();
|
||||
CaseRefusals();
|
||||
CaseJoinTimeout();
|
||||
CaseDeadFirmware();
|
||||
if (g_fails) {
|
||||
printf("\n%d Broadcom case(s) FAILED\n", g_fails);
|
||||
return 1;
|
||||
}
|
||||
printf("\nAll Broadcom protocol and connection cases passed.\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -135,6 +135,16 @@ namespace Drivers::Net::Wifi {
|
||||
void WifiRxEthernet(const uint8_t* frame, uint32_t len) {
|
||||
puthex("ETH", frame, len);
|
||||
}
|
||||
|
||||
// Wifi.cpp routes the supplicant's hooks to the running backend.
|
||||
bool WpaTxEapol(const uint8_t* body, uint32_t len) { return IwxWpaTxEapol(body, len); }
|
||||
bool WpaInstallPtk(const uint8_t* tk, uint32_t tkLen, uint8_t cipher) {
|
||||
return IwxWpaInstallPtk(tk, tkLen, cipher);
|
||||
}
|
||||
bool WpaInstallGtk(const uint8_t* gtk, uint32_t gtkLen, uint8_t keyIdx,
|
||||
uint8_t cipher, const uint8_t* rsc) {
|
||||
return IwxWpaInstallGtk(gtk, gtkLen, keyIdx, cipher, rsc);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
+12
-1
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Host-side tests for the Wi-Fi crypto and the WPA supplicant.
|
||||
# Host-side tests for the Wi-Fi crypto, the WPA supplicant, the Intel MLME and
|
||||
# the Broadcom FullMAC protocol layer.
|
||||
#
|
||||
# These compile the real kernel sources (Libraries/Crypto.cpp and
|
||||
# Drivers/Net/Wifi/Wpa.cpp) for the host against the small shim in shim/, so
|
||||
@@ -44,5 +45,15 @@ $CXX $CXXFLAGS -o "$out/mlme" \
|
||||
|
||||
python3 "$here/ap_mlme.py" "$out/mlme"
|
||||
|
||||
echo
|
||||
echo "== Broadcom FullMAC protocol and join against a simulated firmware =="
|
||||
$CXX $CXXFLAGS -o "$out/brcmf" \
|
||||
"$here/brcmf_harness.cpp" \
|
||||
"$src/Drivers/Net/Wifi/BrcmfProto.cpp" \
|
||||
"$src/Drivers/Net/Wifi/BrcmfConnect.cpp" \
|
||||
"$src/Drivers/Net/Wifi/Wpa.cpp" "$src/Libraries/Crypto.cpp"
|
||||
|
||||
"$out/brcmf" 2>/dev/null
|
||||
|
||||
echo
|
||||
echo "All Wi-Fi host tests passed."
|
||||
@@ -14,6 +14,7 @@ namespace kcp {
|
||||
std::atomic_flag f{ATOMIC_FLAG_INIT};
|
||||
public:
|
||||
void Acquire() { while (f.test_and_set(std::memory_order_acquire)) {} }
|
||||
bool TryAcquire() { return !f.test_and_set(std::memory_order_acquire); }
|
||||
void Release() { f.clear(std::memory_order_release); }
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include <cstdlib>
|
||||
namespace Memory {
|
||||
struct HostHeap {
|
||||
void* Request(size_t n) { return malloc(n); }
|
||||
void Free(void* p) { free(p); }
|
||||
};
|
||||
inline HostHeap g_hostHeap;
|
||||
inline HostHeap* g_heap = &g_hostHeap;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <CppLib/Stream.hpp>
|
||||
namespace Kt {
|
||||
enum KernelLogLevel { INFO, OK, WARNING, ERROR };
|
||||
class KernelLogStream {
|
||||
@@ -10,5 +11,6 @@ namespace Kt {
|
||||
KernelLogStream& operator<<(const char* s) { fprintf(stderr, "%s", s); return *this; }
|
||||
KernelLogStream& operator<<(uint64_t v) { fprintf(stderr, "%llu", (unsigned long long)v); return *this; }
|
||||
KernelLogStream& operator<<(int v) { fprintf(stderr, "%d", v); return *this; }
|
||||
KernelLogStream& operator<<(base::Manip) { return *this; }
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
namespace Timekeeping { uint64_t GetMilliseconds(); }
|
||||
namespace Timekeeping {
|
||||
uint64_t GetMilliseconds();
|
||||
void BusyWaitMs(uint64_t ms);
|
||||
void BusyWaitUs(uint64_t us);
|
||||
}
|
||||
Reference in new issue
Block a user