Documented, robust — survived the whole poking campaign un-bricked. Honest EEPROM (RJ45/10GBASE-T codes), which is what trips stock ixgbe qualification ([../README.md](../README.md)).
References in this directory: [bcm84891l-mdio-commands.md](bcm84891l-mdio-commands.md) (command handler, transcribed), [bcm84891l-sfp-registers.md](bcm84891l-sfp-registers.md) (transport reference, transcribed), and their source PDFs.
## SMI transport (I2C→MDIO bridge)
PHY at I2C 8-bit **0xAC write / 0xAD read**.
| Op | Frames |
|---|---|
| Clause-45 write | one I2C write to 0xAC: `[000+DevAD, RegH, RegL, DataH, DataL]` |
| Clause-45 read | I2C write to 0xAC: `[001+DevAD, RegH, RegL]` → **delay >1 ms** (3 ms proven) → I2C read 2 B from 0xAD |
- Single-byte reads see the bridge as inert (returns 0) — the DevAD-prefixed frame + delay is mandatory. This is why early probing wrongly declared 0x56 dead. Treat 0x0000 reads as retry-with-longer-delay: 0 is also the bridge's not-ready signature.
- **The fetch is asynchronous and a late one serves the wrong register silently**: the read frame arms a fetch, and if it outruns the delay, reading 0xAD returns the *previous* transaction's response with no error — observed under load even at 3 ms (STATUS 0x0004 served as an SNR DATA read → 0.4 dB). Since the buffer only changes when a fetch lands, a result differing from the previous buffer value is provably fresh; an unchanged (or zero) result must be re-read **bare, without re-arming** — preserving clear-on-read registers — until it stabilizes. cabletest implements this freshness check on every bridged read (`phy.go`).
| Per-pair SNR | `CMD_GET_SNR` 0x8030, **invoked bare** (writing the documented DATA1 display flag returns zeros; IEEE 1.133–1.140 never populate — constant 0x8080) | DATA2–5 = SNR A–D ×0.1 dB; ≈ 27–32 dB absolute on bench, 0.1–0.4 dB jitter. 10GBASE-T operating point ≈ 26.5 dB, so margin ≈ value − 26.5. cabletest shows the margin classified green ≥ 3 dB / amber ≥ 1 dB / red below — provisional thresholds until the graded-noise correlation run |
| EEE / AutogrEEEn | 0x8008/0x8009 | Once read 0x0047 (10G AutogrEEEn variable latency + 5G/1G native — local-only, invisible in IEEE 7.60/7.61, which read 0); later reads 0, treated as the truth. **Forced all-off** via SET with explicit params `(0, 0, 0x7A12, 0x480, 0)` + AN restart, verified. cabletest checks 0x8008 and forces only on mismatch, verifying 7.60 reads 0 either way (`phy.go`) |
| EEE wire-truth | arm 0x801A after link-up, read 0x801B | Zero LPI events/duration on idle link; repeat under traffic |
| Fast retrain | 0x800A (datasheet titles it EMI_MODE; description is fast retrain) | Enabled 10G/5G/2.5G; IEEE 1.147 = 0x0019, count bits zero. Keep enabled; read the 1.147 count per run — a marginal cable that fast-retrains still gets counted |
| Host interface | 0x802B/0x802D/0x8027 | XFI polarity normal; TX FIR = main tap 0x3C only; MAC/PHY frequency locked; KR/SyncE/USXGMII/WOL disabled; temp warnings off |
| Jumbo | 0x801C/0x801D | cabletest checks 0x801D and forces enable on mismatch (`phy.go`). The ODM firmware reads back the 9K size option regardless of the requested 10K — and 9018-byte wire frames pass at line rate with zero loss/corrupt, so its "9K" covers ≥ 9018 in practice |
| Limited reach | 0x8006 | Disabled — its DATA3 "linked in LR mode" bit, the only handler-visible trace of the DSP length estimate, is unavailable without a config change |
GET returns are trusted: early exploration saw one anomalous DATA1 (0x0047 from the EEE GET, never reproduced) and die-temperature-like values in other commands' DATA1, treated as one-off noise. cabletest reads config before forcing it and corroborates EEE through 7.60 after any relink. The hard rule that stands: write every DATA register explicitly before any SET — the handler executes stale DATA.
The ECD register mechanism is absent from the handler catalog and the datasheet excerpts here, but the OpenBCM SDK's copper-XGPHY driver (`sdk-6.5.27/src/soc/phy/phy8481.c``phy_8481_cable_diag` + `phy8481.h`) carries it for the 8483x/8485x/8488x family — and the same SDK drives the identical command-handler registers (1E.0x4005/0x4037/0x4038–3C) as the BCM84891L datasheet, confirming the shared map. Validated on the FS:
| `1E.0x4006` | Control/status. Write under mask {15,14,13,12,10}: bit 15 = run now, bit 14 = run at AN, bit 12 = break link, bit 10 = length in meters (SDK writes value 0x8400 = run now + meters). Bit 11 = busy — poll until clear (SDK allows up to 50 s; observed < 0.5 s) |
Observed on a plugged, linked, healthy cable: verdicts OK×4 and per-pair lengths of [45, 45, 41, 46] on a ~45 m cable — **meter-accurate with no calibration**, and this ECD reports length for healthy pairs, not just faults, resolving the terminated-far-end concern in [../README.md](../README.md). Caveats:
- The run blips the link (PMA 1.1 latch-low catches a drop even with the break-link bit clear) — do not run mid-measurement until the disturbance is characterized.
- Fault verdicts (open/short/inter-pair) are unexercised — deliberately: the product is a closed-loop tester, both ends always plugged.
- Family constraints from the SDK: port must be enabled; unsupported at forced 100M.
-`bcm_ecd_probe.py` in phydiag-work implements the recipe.