Every Espressif Wi-Fi-capable chip ships a binary blob — typically
libphy.a plus libpp.a and libnet80211.a — that contains a much
larger surface than the public esp_wifi_* API exposes. This page
maps the undocumented but exported symbols across the whole
family, identifies which features each chip supports, and points at
what's wrappable from a host MCU via this project's RPC channel.
The data is regenerated mechanically from IDF v6.0 — see Methodology at the bottom.
Why this matters. If you see a feature ticked for your chip below, the libphy.a it ships with already contains the code path. You don't need a kernel patch or a custom IDF — just the matching
phy_*extern declaration and a call site. The esp-hosted-open slave does this work for you:phy_rpc_handlers.ccalls these symbols, and our host stub library lets a different MCU drive them over SDIO.What it doesn't mean. Espressif gives no contract on these symbols — names, signatures, and behaviour can shift between IDF releases. Treat each new IDF version as a re-validation step.
Symbols counted only when present in libphy.a for that target on
IDF v6.0. A ✓ means the symbol is exported; calling it still
needs a correct prototype (some args are unknown without RE) and
the right context (Wi-Fi must be running, etc.).
| Feature | ESP32 | S2 | S3 | C2 | C3 | C5 | C6 | C61 | H2 | H4 |
|---|---|---|---|---|---|---|---|---|---|---|
| Force channel (regulatory bypass) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Direct frequency tuning (Hz) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| Band switch (2.4 / 5 GHz) | ✓ | |||||||||
802.11p PHY mode (phy_11p_set) |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||||
| Force fixed TX rate | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |||
| Low-rate filter override | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |||
| 20 / 40 MHz bandwidth | ✓ | ✓ | ||||||||
Manual RX gain (phy_force_rx_gain) |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||||
| AGC max-gain ceiling | ✓ | |||||||||
| RX gain table swap | ✓ | ✓ | ||||||||
| AGC enable / disable | ✓ | ✓ | ||||||||
| CCA enable / disable | ✓ | ✓ | ✓ | ✓ | ||||||
| Channel-busy counters (CBR) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| Instantaneous PHY RSSI | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| Noise-floor read | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||
TX power direct set (phy_set_most_tpw) |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| Forced LLTF CSI dump | ✓ | ✓ | ||||||||
| Internal loopback (self-test) | ✓ | ✓ | ||||||||
| Channel filter override | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| BT-radio TX gain (nRF24-style hacks) | ✓ | ✓ | ||||||||
| BT-radio filter override | ✓ | ✓ | ||||||||
| 802.11mc FTM compensation | ✓ | ✓ | ✓ | ✓ | ✓ | |||||
| Japan ch14 micro-config | ✓ | ✓ | ||||||||
Temperature sensor read (phy_xpd_tsens) |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
H2 and H4 are 802.15.4 / Thread parts with very limited Wi-Fi PHY, which is why most rows are blank.
These are short summaries of what each row in the matrix does, what arguments the call expects (where known), and what to watch for. "Inferred" means we know the symbol exists but haven't confirmed the argument list against scope evidence.
extern void phy_change_channel(int channel, int /*?*/, int /*?*/, int ht_mode);Bypasses the IDF regulatory channel allow-list. Lets you tune to
channels the country code disallows (5 GHz UNII-2C / 3, ITS 172–184,
Japanese ch14, etc.). The first arg is the IEEE channel; the last
is HT mode (0 = HT20). Bootstrap with a regular esp_wifi_set_channel
to a nearby allowed channel first so RF cal runs.
Sub-channel-grid tuning, useful if you need an off-grid centre frequency for testing. Args are reverse-engineered per chip; on the C5 it's a 4-byte integer in MHz units.
C5-only. Toggles the radio between 2.4 and 5 GHz. Implicitly invoked
by phy_change_channel if the target channel is on the other band,
so most callers don't need to use it directly.
extern void phy_11p_set(int enable, int reserved /* 0 */);Six chips have this symbol. The PHY-layer effect is partially reverse-engineered from community work (originally on ESP32):
- relaxes regulatory band-edge checks
- adjusts OFDM front-end timing toward 802.11p-style behaviour
Only the C5 also has a 5 GHz radio, so it's the only chip where "802.11p-on-Espressif" can mean genuine 5.9 GHz V2X. On 2.4 GHz chips (S2/S3/C3/C6/C61) the same call still produces an 802.11p-shaped waveform on the 2.4 GHz band — useful for loopback / protocol-stack development.
extern void phy_force_rx_gain(int gain_index); /* 0..max */
extern void phy_rx_gain_force(int enable); /* 1 = lock */
extern void phy_disable_agc(void);
extern void phy_enable_agc(void);Disables AGC and pins the receiver at a fixed gain. Useful for
sensitivity testing, fixed-gain monitor mode, and capturing weak
signals AGC would otherwise track away from. Re-arm AGC by calling
phy_enable_agc() and phy_rx_gain_force(0).
C5-only. Caps how much amplification the AGC can apply. Less disruptive than disabling AGC; useful for saying "track signal level, but never go above this".
Turns Clear-Channel-Assessment off. The radio will then transmit
without listening first — you've just disabled the only
spectrum-courtesy mechanism in 802.11. Use only on a wire / in a
chamber. Pair with phy_get_cca_cnt for proper CBR measurement
when re-enabled.
extern uint32_t phy_get_cca_cnt(void);
extern void phy_set_cca_cnt(uint32_t value);Universal across all Wi-Fi chips. Returns a monotonic "busy ticks"
counter; combine with elapsed wall time for a CBR percentage. Reset
with phy_set_cca_cnt(0).
PHY-side RSSI poll, distinct from per-frame RSSI in the promiscuous
callback. phy_get_sigrssi returns the most-recent signal frame's
RSSI; phy_get_rssi returns a wider-band reading that also picks
up noise.
Returns the current noise-floor estimate in dBm. Combined with PHY RSSI gives a real-time SNR.
Bypasses the regulatory power table. Lower-level than
esp_wifi_set_max_tx_power, which goes through the country
database. "Tpw" appears to be quarter-dBm.
C5/C61 only. Forces CSI capture on every frame's L-LTF (legacy
training field). Combined with the public esp_wifi_set_csi_rx_cb
this gives unconditional CSI — useful for sensing applications that
can't rely on the default conditional capture.
C5/C61. Routes the TX output back into the RX chain at a configurable gain for self-test. No spectrum emission.
C5/C61. The BT 2.4 GHz radio's TX gain is exposed separately from
the Wi-Fi radio's. With the right preamble and CRC handling, this is
the path that lets you emit nRF24L01+-style Shockburst frames from
an ESP32 — see also phy_bt_filter_reg.
S2/S3/C5/C6/C61. PHY-side timestamp correction for
Fine-Time-Measurement ranging. The high-level FTM API
(esp_wifi_ftm_initiate_session, etc.) is public and lives in
libnet80211.a; this PHY symbol is what improves accuracy.
The slave's phy_rpc_handlers.c ships handlers for these features
on the C5 by default. Adding more chips is mostly Kconfig + matching
the right symbol prototype — the dispatcher is target-agnostic.
| RPC | Underlying symbol |
|---|---|
set_channel |
phy_change_channel |
set_phy_11p |
phy_11p_set |
set_country_permissive |
esp_wifi_set_country (public) |
set_tx_power |
esp_wifi_set_max_tx_power (public) |
set_rx_gain |
phy_force_rx_gain + AGC off |
set_agc_max_gain |
phy_agc_max_gain_set |
set_cca |
phy_disable_cca / phy_enable_cca |
get_cca_counters / reset_cca_counters |
phy_get_cca_cnt / phy_set_cca_cnt |
set_low_rate |
phy_disable_low_rate |
set_bandwidth |
phy_bb_bss_cbw40 |
get_phy_rssi |
phy_get_sigrssi / phy_get_rssi |
These are present in the C5 blob and look broadly useful — adding them to the RPC layer is one packed struct + one host stub + one slave handler each.
phy_get_noise_floor— instantaneous noise-floor pollphy_set_freq— direct frequency tuning (off-grid testing)phy_set_loopback_gain— internal RX/TX loopback for self-testphy_set_rate— force fixed TX rate / MCSphy_csidump_force_lltf_cfg— forced CSI dumpphy_bt_set_tx_gain_new+phy_bt_filter_reg— nRF24-style hacks on the BT radiophy_xpd_tsens— die temperature read (handy for long captures)
See extending.md for the recipe.
Pre-extracted dumps of every T phy_* symbol per chip live under
symbols/. They're regenerated on every release of
this repo. Useful when investigating something chip-specific — grep
for a name pattern in the file matching your target.
ls docs/symbols/
# → esp32.phy.txt esp32c5.phy.txt esp32c61.phy.txt ...Symbol counts (ballpark — a measure of how much of the radio Espressif keeps internal):
| chip | phy_* exports |
|---|---|
| esp32 | 63 |
| esp32s2 | 52 |
| esp32s3 | 74 |
| esp32c2 | 47 |
| esp32c3 | 75 |
| esp32c5 | 465 |
| esp32c6 | 81 |
| esp32c61 | 390 |
| esp32h2 | 52 |
| esp32h4 | 67 |
C5 and C61 (newest Wi-Fi 6 silicon) carry an order of magnitude more PHY surface than the older parts — that's where most of the interesting opportunities are.
libpp.a (low-level packet processor) exports ~30 pp_/wifi_
functions per chip, fairly stable across the family.
libnet80211.a is much larger (488–641 exports) and contains the
802.11 MAC machinery: scan management, TX/RX paths, AMPDU,
mesh, FTM, monitor mode. Some of these are reachable from a host
via existing public IDF APIs; others (raw 80211 TX without the
CSA / sequence-number machinery, monitor-mode filter masks the
public API doesn't surface) need wrapping.
We have not enumerated libnet80211.a here yet because the symbol
namespace is messier — ieee80211_*, ftm_*, wifi_*,
pp_post_to_wifi_task_*, ad hoc helpers — and most callers shouldn't
need to reach below esp_wifi_* for MAC concerns. If you have a
specific ask, open an issue.
Anyone can reproduce this map. From an IDF v6.0 install:
# Per-chip phy_* symbol dump:
for chip in esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c5 esp32c6 esp32c61 esp32h2 esp32h4; do
nm -gU $IDF_PATH/components/esp_phy/lib/$chip/libphy.a 2>/dev/null \
| awk '/T phy_/ {print $3}' | sort -u > $chip.phy.txt
done
# Symbols present on every chip (universal):
sort *.phy.txt | uniq -c | awk '$1==10 {print $2}'
# Symbols unique to C5:
comm -23 esp32c5.phy.txt <(cat esp32{,s2,s3,c2,c3,c6,c61,h2,h4}.phy.txt | sort -u)nm -gU shows externally-visible (-g) defined-only (-U) symbols.
Filtering on T phy_ keeps text-section exports prefixed with phy_;
adjust the regex for pp_ / ieee80211_ / ftm_ if you're surveying
MAC-layer symbols.
For symbols whose argument list is unknown, scope the call against known-good libpcap / SDR captures before relying on it.