soc_esp32::dport
DPORT: peripheral clock gating, reset, and safe access to the block.
Most ESP32 peripherals come out of reset clock-gated off and held in reset. Every register access to such a peripheral reads as zero and writes nowhere, with no fault — so a driver that forgets this looks like a driver with a wrong register map, and behaves identically to one.
Bit positions confirmed against esp-idf soc/dport_reg.h.
Two hazards, and they are not the same one
Section titled “Two hazards, and they are not the same one”DPORT is shared by both cores, which makes it unsafe in two independent ways. Fixing either alone leaves a real failure in place.
1. A silicon erratum on reads. When one CPU reads a DPORT register while the other accesses APB, the DPORT read can return the APB value. Nothing faults; the caller simply gets the wrong number. Espressif’s workaround is not a lock — it is to read any APB register immediately before the DPORT read, with the two loads adjacent and interrupts masked. The APB pre-read synchronises the two CPUs’ view of the bus.
Verified against esp-idf soc/esp32/dport_access.c (v5.1) and
esp_hw_support/port/esp32/dport_access.c (v4.4), which agree
instruction for instruction. Note what the same header says about writes:
Write value to DPORT register (does not require protecting)
So write is a plain store. Only reads need the dance.
2. An ordinary read-modify-write race. Two cores each setting a
different bit in PERIP_CLK_EN can lose one of them — the second read
happens before the first write lands. The erratum workaround does nothing
about this, because each read is individually correct. [modify] and the
clock-gate helpers take a lock across the whole sequence.
Before both cores ran, neither hazard was reachable and the second was documented as a caller’s problem. Both are live now.
Structs
Section titled “Structs”ClockBit
Section titled “ClockBit”struct ClockBit(/* private */);Clock-enable / reset bit for a peripheral, in DPORT_PERIP_CLK_EN_REG and
DPORT_PERIP_RST_EN_REG. The same bit position serves both registers.
Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq
CryptoClockBit
Section titled “CryptoClockBit”struct CryptoClockBit(/* private */);A clock-enable / reset bit in DPORT_PERI_CLK_EN_REG / DPORT_PERI_RST_EN_REG.
Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq
Functions
Section titled “Functions”clock_bit
Section titled “clock_bit”fn clock_bit(base: u32) -> Option<ClockBit>The clock/reset bit for a peripheral base address.
References: ClockBit
disable
Section titled “disable”unsafe fn disable(bit: ClockBit)Gate a peripheral’s clock off and hold it in reset.
Safety
Section titled “Safety”Same as [enable].
References: ClockBit
disable_crypto
Section titled “disable_crypto”unsafe fn disable_crypto(bit: CryptoClockBit)Gate a crypto block’s clock off and hold it in reset.
Safety
Section titled “Safety”Same as [enable_crypto].
References: CryptoClockBit
enable
Section titled “enable”unsafe fn enable(bit: ClockBit)Enable a peripheral’s clock and release it from reset.
Call this before touching any of the peripheral’s registers.
Both registers are updated under one lock acquisition. Taking the lock twice would let the other core see a peripheral clocked but still held in reset, which is the one intermediate state a caller must never observe.
Safety
Section titled “Safety”Touches shared DPORT state. Safe against the other core and against interrupts; still requires that the peripheral is yours to gate.
References: ClockBit
enable_crypto
Section titled “enable_crypto”unsafe fn enable_crypto(bit: CryptoClockBit)Enable a crypto block’s clock and release it from reset.
The reset polarity here is the reverse of [enable]: a set bit in
PERI_RST_EN holds the block in reset, so releasing it clears the bit.
Same one-lock invariant as [enable] — clock and reset move together so no
core sees the block clocked but still reset.
Safety
Section titled “Safety”Touches shared DPORT state. Safe against the other core and interrupts; still requires that the block is yours to gate.
References: CryptoClockBit
modify
Section titled “modify”unsafe fn modify(reg: u32, clear: u32, set: u32)Read-modify-write one DPORT register, atomically against the other core.
Safety
Section titled “Safety”reg must be a valid DPORT register address. Must not be called from
inside another modify/[enable]/[disable].
radio_clock_disable
Section titled “radio_clock_disable”unsafe fn radio_clock_disable(mask: u32)Turn off the radio clocks in mask.
Not the inverse of [radio_clock_enable] in practice. The common bits
are shared, so clearing them while the other radio is up stops it dead.
Nothing here tracks that; the caller owns the decision, and today the only
caller refcounts it.
Safety
Section titled “Safety”Writes DPORT. mask must be a combination of the RADIO_CLK_* constants.
radio_clock_enable
Section titled “radio_clock_enable”unsafe fn radio_clock_enable(mask: u32)Turn on the radio clocks in mask.
Read-modify-write through [read]/write, so it inherits the DPORT
erratum workaround and the lock: the PHY is brought up from a task while
the other core may be doing anything at all, and a lost bit here is a
radio block that never gets a clock.
Safety
Section titled “Safety”Writes DPORT. mask must be a combination of the RADIO_CLK_* constants.
unsafe fn read(reg: u32) -> u32Read a DPORT register, with the erratum workaround.
Use this for every DPORT read. A plain read_volatile on this block is
correct almost always, which is what makes the bug so unpleasant: it fails
only when the other core happens to touch APB in the same few cycles, so it
is rare, load-dependent, and reproduces on nobody’s desk.
Safety
Section titled “Safety”reg must be a valid DPORT register address.
wifi_mac_reset
Section titled “wifi_mac_reset”unsafe fn wifi_mac_reset()Pulse the Wi-Fi MAC’s reset.
esp-idf’s wifi_reset_mac_wrapper, which is a set followed immediately by
a clear — the reset is edge-driven and leaving the bit asserted would hold
the MAC down rather than restart it.
Deliberately not periph_module_reset(PERIPH_WIFI_MODULE), which is what
NuttX calls: on the ESP32 that lands in periph_ll_get_rst_en_mask’s
default arm and returns a mask of zero, so it sets and clears nothing.
The two references disagree, and this follows esp-idf, whose version is the
one the blob was built and tested against.
Safety
Section titled “Safety”Writes DPORT and resets a peripheral. The Wi-Fi MAC must not be mid-frame, which is the caller’s business — the blob calls this while its own driver is stopped.
unsafe fn write(reg: u32, value: u32)Write a DPORT register.
Plain store: esp-idf’s DPORT_REG_WRITE is _DPORT_REG_WRITE, documented
as not requiring protection. This exists so that call sites read
symmetrically with [read], and so the one place that could change is one
place.
Safety
Section titled “Safety”reg must be a valid DPORT register address, and value meaningful for it.
Constants
Section titled “Constants”PERIP_CLK_EN
Section titled “PERIP_CLK_EN”const PERIP_CLK_EN: u32;DPORT_PERIP_CLK_EN_REG. Public so the on-target self-tests have a real
DPORT register to read — the erratum workaround can only be checked against
hardware, and a test that invents an address checks nothing.
PERIP_RST_EN
Section titled “PERIP_RST_EN”const PERIP_RST_EN: u32;DPORT_PERIP_RST_EN_REG.
PERI_CLK_EN
Section titled “PERI_CLK_EN”const PERI_CLK_EN: u32;DPORT_PERI_CLK_EN_REG. The crypto blocks — AES, SHA, RSA — are not
gated by PERIP_CLK_EN like every other peripheral; they share this
separate register (and PERI_RST_EN below). Confirmed against esp-idf
soc/esp32/include/soc/dport_reg.h and hal/esp32/clk_gate_ll.h.
PERI_RST_EN
Section titled “PERI_RST_EN”const PERI_RST_EN: u32;DPORT_PERI_RST_EN_REG.
RADIO_CLK_BT
Section titled “RADIO_CLK_BT”const RADIO_CLK_BT: u32;Clocks only Bluetooth needs. Bits 11, 16 and 17.
RADIO_CLK_COMMON
Section titled “RADIO_CLK_COMMON”const RADIO_CLK_COMMON: u32;Clocks both radios need. Enabling either radio needs these on, and only the last one out may turn them off – which is the caller’s business, since this crate has no idea who else is using the radio.
RADIO_CLK_WIFI
Section titled “RADIO_CLK_WIFI”const RADIO_CLK_WIFI: u32;Clocks only Wi-Fi needs. Bits 1, 2 and 10.