radio_esp32::phy
Bringing the PHY up, and the calibration that goes with it.
Step 3.6. Everything else in this crate answers a call from the blob; this is the one place FlintOS calls into it, and the first place where “compiles and links” stops meaning much.
The sequence, which is esp-idf’s
Section titled “The sequence, which is esp-idf’s”esp_phy_enable in phy_init.c, reduced to what FlintOS has:
- Take a reference to the shared radio clocks (
DPORT_WIFI_CLK_EN). - First time only: load a stored calibration, hand it and the init data
to
register_chipv7_phy, and store the result back if it was recalculated. - Afterwards:
phy_wakeup_init, which is the cheap path.
Disable runs it backwards: phy_close_rf, then the clocks.
The calibration mode is a decision, not a constant
Section titled “The calibration mode is a decision, not a constant”esp-idf asks for PHY_RF_CAL_FULL when it has no usable stored
calibration and PHY_RF_CAL_PARTIAL when it has one. The difference is
hundreds of milliseconds at every boot, which is the entire reason
[crate::calibration] exists.
It also uses PHY_RF_CAL_NONE after a deep-sleep wake, on the grounds
that the calibration in RAM is still good. FlintOS has no deep sleep, so
that case cannot arise here and is not implemented rather than guessed at.
What the hardware said
Section titled “What the hardware said”apps/tests/radioprobe runs this on a board. On an ESP32-WROOM:
register_chipv7_phy returns 0 with our init data and our eFuse MAC, a
full calibration takes ~183 ms, and a re-enable takes ~250 µs.
It also found a kernel bug, since fixed: this double-faulted on every
preemption, and the cause was four wrong window overflow/underflow vectors
rather than anything about the blob. Nothing in FlintOS’s own Rust reaches
the call8 and call12 handlers; a GCC-built blob reaches them constantly.
enable is preemptible now, and there is no critical section anywhere on
this path – see arch/xtensa/src/asm/vectors.S.
One thing it found that is not fixed:
- The stored calibration currently costs more than it saves. Reading it
back is ~1.36 s, against the ~183 ms of calibration it avoids, because
kvstore::getscans the whole log per key and [crate::calibration] needs eighteen of them. [CalibrationMode::Partial] is the right decision and the store is the right idea; the log is the wrong shape.
PhyError
Section titled “PhyError”enum PhyError { Register(i32), OutOfMemory,}Why the PHY could not be brought up.
Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq
Functions
Section titled “Functions”common_clock_disable
Section titled “common_clock_disable”unsafe fn common_clock_disable()Release one reference to the clock gates shared by Wi-Fi and Bluetooth.
Safety
Section titled “Safety”Must match a prior [common_clock_enable].
common_clock_enable
Section titled “common_clock_enable”unsafe fn common_clock_enable()Take one reference to the clock gates shared by Wi-Fi and Bluetooth.
esp-idf reference-counts wifi_bt_common_module_enable: the Wi-Fi driver
takes a temporary reference of its own around the longer-lived reference
held by esp_phy_enable. Treating either callback as a raw bit operation
lets the temporary disable shut the PHY off while its owner is still live.
Safety
Section titled “Safety”Writes DPORT on the first reference. Every call must be paired with
[common_clock_disable].
disable
Section titled “disable”unsafe fn disable(mask: u32)Drop a reference, closing the RF when the last one goes.
Safety
Section titled “Safety”Calls into the blob and writes DPORT. Must match an [enable].
enable
Section titled “enable”unsafe fn enable(mask: u32) -> Result<(), PhyError>Bring the PHY up, or take another reference to an already-running one.
The first call does the expensive part: it loads a stored calibration if there is a usable one, registers the PHY, and stores the result back when the PHY had to recalculate. Later calls only re-wake it.
Safety
Section titled “Safety”Calls into the blob and writes DPORT. The caller must pair every enable
with a [disable], and must have initialised [crate::nvs] first if the
calibration is to survive a reboot – without it this still works, and
simply recalibrates every time.
References: PhyError
fn refs() -> u32How many references are outstanding. Diagnostics.
rf_cal_version
Section titled “rf_cal_version”unsafe fn rf_cal_version() -> u32The calibration layout this PHY speaks.
Safety
Section titled “Safety”Calls into the blob. Cheap and side-effect-free, but it is still the blob.
version_str
Section titled “version_str”unsafe fn version_str() -> &'static strThe PHY blob’s version string, as it reports itself.
Which archive is linked is otherwise invisible: make blobs fetches a
pinned revision, and nothing downstream would notice if it fetched a
different one.
Safety
Section titled “Safety”Calls into the blob, which returns a pointer to its own static storage.