Files
cabletest/docs/modules/fs/README.md
T

92 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FS SFP-10G-T-100 (Broadcom BCM84891L)
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.
- Recorded for a future multi-byte firmware transport (mlx5 MCIA shape): read = write `[001+devad,RegH,RegL]` offset_size=1, STOP, >1 ms, read 2 B offset_size=0; write = offset_size=2, addr = devad<<8|RegH, data = `[RegL,DataH,DataL]`.
## MDIO command handler
Protocol and full verified catalog: [bcm84891l-mdio-commands.md](bcm84891l-mdio-commands.md). Traps:
- **SET commands execute stale DATA** — the handler never clears DATA registers; write every parameter register explicitly before any SET.
- **GETs must be invoked bare** — pre-writing *any* DATA register, not just the documented DATA1 display flag, leaves the handler executing as a no-op with results never written (sentinel pre-fills survived GET_SNR untouched, proven on hardware).
- **STATUS must never be written** — any user value (0x0000 and 0x0008 both tried) closes the mailbox: the next command is silently ignored until firmware restores it.
- **PASS cannot prove completion.** The previous command's PASS stays latched while a slow firmware still executes, and until then the DATA registers are firmware scratch — the die temperature turns up in them (0x004E0x0051 observed as "SNR" and "EEE mode"; the campaign's "anomalous 0x0047" was this same race).
- **Handler writes during firmware-busy windows wedge the µC permanently** — DATA/CMD writes landing while firmware does post-AN provisioning or training work (a window stretching ~10 s past relink) killed the SMI service four times; reads alone never once. The windowed protocol keeps steady-state writes clear of the temp poll's busy windows; bringup-era writes run unwindowed (they fire pre-AN, where writes have never wedged) and post-AN provisioning exposure remains as the diag path's accepted risk.
- **Bridge reads carry no fetch identity** — a fetch that outruns the delay leaves the previous transaction's data at 0xAD with no error. A killed host can also leave the pipeline latched a response behind *persistently* (PHY ID served a stuck stale value across processes until driver re-init); every appliance boot clears it by loading the driver, so only bench runs reusing a loaded driver see it, and the identity check at startup dies loudly on it.
- **Stale reads poisoned the corrected-error channel** — caught with per-register instrumentation: 3.33 reads served 1.147's resident 0x0011 (decoding as exactly 17 errored blocks — the recurring "+17"), SNR DATA values (+21/22), and 1.147 reads served 3.33's 0x8000 (count 16, charged twice by the rollover delta) and the die temperature. Every such burst decodes as a neighbor register. The source is the firmware's own temp poll and the fix is the windowed protocol (firmware-reliability notes): work confined to a fixed window after each observed poll ends the poisoning — reproduced at will by aiming reads at the poll, eliminated by avoiding it.
- Poll STATUS ~100 ms; frozen up to 2 s during 10GBASE-T training — only run after link-up.
## Proven diagnostics
| Capability | How | Observed |
|---|---|---|
| Per-pair SNR | `CMD_GET_SNR` 0x8030, **invoked bare** (writing the documented DATA1 display flag returns zeros; IEEE 1.1331.140 never populate — constant 0x8080) | DATA25 = SNR AD ×0.1 dB; ≈ 2732 dB absolute on bench, 0.10.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. **Each training re-converges differently**: back-to-back retrains with identical roles, temperature and noise moved one pair by 3.6 dB (roles pinned, launch power pinned — the convergence itself is the variable, and the two bench modules differ in stability: ≤1.5 dB spread on one, 3.6 dB on the other). A single training's absolute SNR is a weak qualification number; qualification wants median-of-N trainings, live SNR is honest as a within-session tracker |
| Die temperature | `CMD_GET_CURRENT_TEMP` 0x8031 | ~6870 °C on bench |
| Supply rails | `GET_CURRENT_VOLTAGE` 0x802F | 0.8 V and 1.88 V rails, tenths of mV |
| Error counters | IEEE PCS 3.32/3.33 — block lock, latched errored-block/BER, clear-on-read | The noise-stress error proxy |
| Retrain on demand | IEEE AN restart `7.0 \|= bit9` | Relinks in ~56 s |
## Probed configuration states
| Item | Command | State |
|---|---|---|
| EEE / AutogrEEEn | 0x8008/0x8009 | The famous 0x0047 GET reading was the die temperature in DATA1 (firmware scratch — see reliability notes), not an AutogrEEEn mode; GET 0x8008 is unusable. cabletest forces all-off every boot (SET with explicit params `(0, 0, 0x7A12, 0x480, 0)` + AN restart, proven). The SET programs the advert register directly, so 7.60 reads 0 immediately — bringup verifies it before any AN restart, both µCs quiet; the modules link to each other, so one restart puts both into training, and bringup configures and verifies both before either restart fires (`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 |
| Pair map | 0x8000 | DATA2 = 0x00E4 = identity (A/B/C/D straight through) — MDI wiring verification works |
| Fault pass-through | 0x800D | 0 = LF/RF pass to the line (0x8011/0x8010 could mask them as IDLE — leave off) |
| 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 | GET 0x801D answers in DATA1 — scratch-prone, unusable — and bringup cannot assume a cable to probe through, so cabletest forces enable every boot (SET 0x801C + AN restart); the running mix's 9018-byte frames are the standing wire truth (`phy.go`). The FS at its 9K option passes them at line rate with zero loss/corrupt |
| 1588 | 0x8004 | Disabled (engine registers undocumented — see asks below) |
| 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 |
| Master/slave role | IEEE 7.32 bits 15/14 | Left to AN the resolution is a per-training lottery; manual config writes stick exactly and the resolution follows deterministically (7.33.14, no config fault with complementary ends). cabletest forces A master / B slave every boot (`phy.go`) so sessions are measured under identical conditions |
| Launch power | IEEE 1.130, 1.0.11 | 1.130 reads 0x0003 and is firmware-pinned: host writes to the short-reach bit revert instantly and the value never changes across retrains, so launch power is constant and not host-influenceable. PMA low-power bit clear. The handler catalog has no TX-power command |
## Firmware-reliability notes
**The firmware is its own mailbox client — the handler is a shared, unarbitrated resource.** Watched read-only on an idle module: CMD sits at 0x0031 (`CMD_GET_CURRENT_TEMP`, bit 15 consumed) and DATA1 tracks the live die temperature (0x4F0x52 ≈ 7982 °C under load) — the firmware issues its own temperature command through the same CMD/STATUS/DATA registers the host uses, with no arbitration. Every mystery this explains: temperature appearing in DATA1 (it is that command's output — the campaign's "anomalous 0x0047" included), PASS satisfying a host poll when it belongs to the internal command, and the µC wedges (two writers colliding on CMD/DATA, likeliest when internal management activity spikes after AN events). Any GET whose answer lives in DATA1 is unusable; corroborate through IEEE registers or the wire. The hard rule that stands: write every DATA register explicitly before any SET — the handler executes stale DATA. **The internal poll is not silenceable through the handler**: disabling the temperature warning (SET 0x8032 `(0x007D, 5, 0, 0, 0)` and 0x8034 `(0xFFD8, 5, 0, 0, 0)` — DATA3 = 0 = warning off — both PASS) leaves CMD resident at 0x0031 with DATA1 tracking the die within seconds of the host going quiet; the poll is independent of the warning machinery. GET 0x8033/0x8035 read DATA13 = 0 before and after those SETs (DATA4/5 leftover scratch) — DATA1-answer readbacks, unusable as verification either way.
**The internal poll's clock, measured on hardware.** One poll every 3.494.22 s, regime-dependent but rock-stable within a regime (±15 ms): ≈3.50 s warm and idle, ≈3.70 s, ≈4.01 s cool and idle, ≈4.21 s under continuous host mailbox load — host traffic *defers* the poll, never advances it, and nothing observed brings two polls closer than 3.49 s. The poll is link-independent: it runs straight through an AN restart and the whole retrain without missing a beat. Around each poll's service the bridge serves stale data for ~50 ms: every read returns the last value successfully fetched before the µC went busy (stuck-at-last-fetch, not one-behind), which is exactly how 3.33 inherits 1.147's 0x0011. The window closes *before* the poll is detectable — a CMD read returning the fresh resident 0x0031 is itself proof the µC serviced the fetch, so flip detection doubles as the all-clear.
**The windowed protocol (`phy.go`) — contention resolved by time-division.** Since the poll cannot be silenced, the host schedules around it. One goroutine per module owns every transport touch (requests execute one at a time on the loop — nothing else can reach the wire, by construction), and each request is admitted only inside a fixed 3.4 s window following an observed poll (CMD flipping to resident 0x0031, polled at 10 ms); at the cutoff the host goes quiet until the next flip. 3.4 s sits below every observed cadence, so a regime switch mid-run lands harmlessly in vacated time — no prediction, no period tracking, just the last observed flip plus two constants. **Re-lock — no window at assumed phase**: edge detection needs CMD ≠ 0x0031, and normal work leaves it armed (every handler command rewrites CMD), but after a gap with no commands — a diag plus its relink wait, an idle stretch, a poll that skipped its GET because the link bit read down — the resident is 0x0031 and the phase is unknown. Admission then arms first (a bare GET_PAIR_SWAP, resident 0x0000) and anchors only on the true edge that follows, so every window is edge-locked from first contact at boot; bringup pays one phase acquisition per module (~24 s). A missing heartbeat free-runs one window rather than stall (never observed — the heartbeat survives retrains). Before re-lock the post-diag window anchored blind on the stale resident, and every ghost observed under windowing sat in exactly that window; with re-lock, ghost-free across all bench diags to date. A poisoned batch is self-consistent and forensically visible: the stuck value clears the link bit (SNR skipped, panel shows "-", the ghost-SNR panic sidestepped) while 3.33 charges the same value as errored blocks (0x0011 → the "+17"); every corrected charge and link-down reading prints its raw 1.1/3.33/1.147 on the console, so events self-attribute — a ghost names its neighbor register, real correction decodes as nothing but itself. Validated on hardware in `~/work/phydiag-work/bcm_phaselock_bench.py`: aiming read bursts at the poll reproduces the poisoning on 100% of polls (the "+17" manufactured on demand); windowed operation ran 2,881 back-to-back batches — 16× the production rate, riding the cutoff — with zero stale values, and the production 1 Hz stream is unperturbed (blackout hides inside natural gaps, worst sample gap ~1.6 s).
## ECD — recovered from the OpenBCM SDK, proven on hardware
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/0x40383C) as the BCM84891L datasheet, confirming the shared map. Validated on the FS:
| Register | Role |
|---|---|
| `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) |
| `1.0xA896` | Verdicts, 4 bits per pair: 1 = OK, 2 = open, 3 = short, 4 = inter-pair short |
| `1.0xA8970xA89A` | Per-pair lengths (pairs 1/2, 3/4, 5/6, 7/8), meters |
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.
- **An interrupted run can wedge the µC's SMI service** (observed after a process died mid-diag): the bridge ACKs I2C but serves 0x0000 for every register at any delay and silently drops MDIO writes — no in-band recovery, not even PMA reset — while the EEPROM path and the trained link keep working. Recovery: a driver unbind/rebind of the port (the SFP re-initialization resets the µC); a reseat would do the same.
- 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.
## Missing datasheet chapters
Chapters the datasheet TOC lists but the excerpts here omit — to source elsewhere:
1. The **ECD chapter** — now for confirming bit meanings rather than unblocking.
2. The **1588 operation chapter**.
3. Datasheet **§1.20 loopback** (copper line loopback) and **§1.17 EEE/fast-retrain monitoring**.
4. **Chapter 2 register summary.** The excerpt's TOC names them all.