# Wiitek UF-RJ45-10G-100 (Marvell CUX3610)

Test-set module; lying fake-SR EEPROM (passes stock ixgbe qualification). Reached over RollBall ([../README.md](../README.md)).

## ⚠ DANGER: bricks permanently on certain reads

Both original Wiiteks were bricked **permanently** by register exploration — the death survives cold power cycles and reseats.

- The CUX3610's integrated microcontroller (Marvell Alaska-M, PHY ID 0x002B0BF4, firmware 12.1.5.0) boots from onboard non-volatile storage. The high vendor register windows (3.0x8000+, 31.0xF000+) reached over RollBall are **µC-mapped peripheral space, not PHY-core MDIO**. A read of a trapped peripheral crashes the µC mid-operation and corrupts its persistent boot state; it comes up FATAL forever after.
- **The confirmed kill: any register whose low byte is 0x64 in a high window (register high-byte ≥ 0x80), on any devad.** Module 1 died reading 3.0x8064, module 2 reading 31.0xF064. The mailbox writes the address bytes, then the µC dies executing the access — the command never needs to complete. 3.0x8064 is a known QCA808x CDT status register; the ODM firmware appears to emulate/intercept a CDT block there and crash on a cold read. A sloppy `(reg & 0xFF) == 0x64` decode is one hypothesis that fits both deaths — but it is the *confirmed* pattern, not proof the danger stops there. The whole `3.0x8000+` / `31.0xF000+` space is µC-mapped peripheral space; treat all of it as lethal-until-proven, `??64` merely most-proven.
- Rules for any Marvell/RollBall module:
  - **Never sweep vendor register windows.** Treat every `3.0x8000+` / `31.0xF000+` access as potentially lethal, not just `*.0x??64`.
  - Single targeted reads only, on registers with a documented, verified-safe reason.
- Recovery needs a vendor firmware image + the boot-download protocol (1.0xC050 boot status, 1.0xD0F0–D0F3 download) — we have no CUX3610 image, so bricks are effectively dead. A ghost I2C device at 7-bit 0x57 (returns zeros) is the ROM bootloader waiting for a download.

The `rollball_ixgbe.py` client hard-guards the blacklist and raises *before* touching hardware — it structurally cannot repeat the kill.

## Safe register set — proven live

The shared IEEE 802.3an set ([../README.md](../README.md)) plus two safe vendor reads. Verified on the current replacement unit (SNR [8.8, 7.0, 7.3, 8.9] dB, matching the dead originals' A 9.1 / B 7.1 / C 7.7 / D 8.9 — a representative module):

| Registers | What | Reading |
|---|---|---|
| PMA 1.133–1.136 / 1.137–1.140 | Per-pair SNR margin (offset-0x8000, 0.1 dB) | [8.8, 7.0, 7.3, 8.9] dB (as auto-master vs FS manual-slave: [9.2, 7.2, 7.0, 8.7]) |
| PCS 3.32 / 3.33 | Errored-block / block-lock latches | 0x1001 / 0x7f00 |
| AN 7.33 | Master/slave, link partner | 0x3c62 (as master against the FS: 0x7c62) |
| 1.0xC011 / 12 | Firmware version | 0x0c01 / 0x0500 (12.1.5.0) |
| 1.0xC050 | Boot status | 0x087e |
| PMA 1.0 | Speed selection | 0x2040 (10G) — see the reset trap below |
| AN 7.0 / 7.1 / 7.32 / 7.60 | AN control/status, 10GBASE-T control, EEE advert | 0x3000 / 0x0008 idle · 0x00ad linked / 0x11e1 (auto role, 10G advert) / 0x0000 (no EEE advertised) |
| A2.250 (Table 3) | PHY part number ([10gbase-t-phy-register-operation.md](10gbase-t-phy-register-operation.md)) | 9 on the CUX3610 (1 is documented as 88X3310) |

Proven-safe writes, all IEEE standard space: AN restart `7.0 |= 0x1200`, and PMA reset `1.0 = 0x8000` — **the PMA reset clears the speed-selection bits** (1.0 reads 0x0000 after, no speed selected, 10G training impossible) and does not touch 7.x config; write 1.0 = 0x2040 back after any reset or the link never returns.

`rb_forensic.py` in phydiag-work performs this baseline and logs every sff_i2c op (PRE line fsync'd before each op, POST after) to `wiitek-ops.log` — a PRE with no POST names whatever killed the µC. `rollball_ixgbe.py` hard-guards the `*.0x??64` blacklist and raises before touching hardware.

## In the test set — mixed pair with the FS

cabletest drives this module through its own Go RollBall client (`phy.go`): module type dispatched from the EEPROM vendor PN before any protocol touch, mailbox ops as split ≤4-byte offset writes plus single-hold compound reads over the patched-ixgbe `sff_i2c`, and a **whitelist guard** — every MDIO read/write is checked against the proven-safe set above and panics before touching hardware on anything else, so the client structurally cannot repeat the kill. The 1 Hz poller reads link (1.1), per-pair IEEE margins (1.133–136, used directly — no operating-point subtraction), corrected latches (3.33) and fast-retrain count (1.147); margins ride ghost bounds of −10…+25 dB. **This module's IEEE margins are the panel's only SNR source** — the FS's handler SNR is not polled at all (the derived number was never actionable and each GET costs BCM handler traffic), so an all-BCM pair shows no SNR. Roles in the mixed pair: the FS is forced manual-slave and this module auto-resolves master (its 7.32 reads auto and cannot be safely forced) — links in ~3 s. EEE stays down one-sided (this unit advertises none; the FS forces none). Module-level jumbo cannot be forced here; the running mix's 9018-byte frames pass at line rate regardless. Length comes from the FS end's ECD with this module as the far end: healthy per-pair verdicts and lengths within a few meters of the FS↔FS baseline on the same cable.

Not available in safe space: pre-FEC counter, cable length — those live in the µC vendor space that killed the modules.

**Mailbox latency is regime-dependent** — <25 ms per read linked (X520 host), ~220 ms steady with no link (bench-measured, `~/work/phydiag-work/rb_mbox_latency.py`), and beyond 3 s while 10GBASE-T training runs (observed live when a cable plug landed mid-read at bringup). cabletest's mailbox deadline (`rbCmdTimeout`) is sized past a full ~6 s training so a plug event reads busy, not dead.

**Mailbox trap: an orphaned command's completion answers as yours.** If a session dies mid-mailbox-op (killed process), the µC finishes the orphaned command on its own time, leaving CMD=DONE and that command's value in the block. A next session that issues a command and samples status immediately reads the orphan's DONE and value as its own — seen live: a PHY ID 1.2 read answered 0x0006, the orphaned EEE-advert (7.60) value of the killed session, while the very next read was correct. The client therefore never samples status before a full poll gap, reads the value from the same 6-byte block read as the status, and accepts a completion only when the block echoes the issued devad/reg (`phy.go` mbox). The split-transaction python clients always had the pre-sample gap, which is why the field campaign never hit this.

## VCT/cable length: no confirmed-safe path

This PHY offers no length capability the product needs (the length strategy lives in [../README.md](../README.md)); every known VCT candidate lands in the exact danger zone, and the safe-looking option is a documentation error corrected here:

- **The 88Q2220 TDR recipe is NOT safe.** Its registers (MMD 3, 0xFECA/0xFED9/0xFEDA/0xFEDE/0xFEDD) sit at `3.0xFExx` — inside the `3.0x8000+` µC-mapped window this doc identifies as the space that bricked both modules. Low-byte ≠ 0x64 does **not** make them safe; the whole high window is µC-intercept space, and the brick was a µC crash on access, not specifically the 0x64 decode. (The 0x64 pattern is the one *confirmed* kill; it is not the boundary of the danger.) Treat the entire `3.0x8000+` / `31.0xF000+` space as lethal-until-proven, not the `*.??64` addresses alone.
- **The QCA-style CDT block** (`qcom/qcom.h`: 3.0x8064 status, 3.0x8065–0x8068 per-pair results, 3.0x8074–0x807E thresholds) is the most likely *intended* VCT — the ODM firmware emulates it — but it is centered on `3.0x8064`, the precise register that killed both originals. Hypothesis: the kill was a *cold* status read before running the test, and the real protocol is write-start → wait → read-status/results. Plausible, unproven, and not worth a live module.
- **No usable public reference exists.** The kernel 10G Marvell driver (`marvell10g.c`) implements no cable test; `gtVct.c` in the qd-dsdt GPL dumps is legacy clause-22 1000M paged access (wrong architecture for this clause-45 part); no public datasheet documents the Alaska-M VCT register map.

Routes that do not risk the module:

1. **The real VCT register map** from Marvell/Wiitek under NDA — the only route to a *confirmed*-safe sequence.
2. **Deliberate sacrifice** of a spare to test the QCA-CDT-start hypothesis, `rb_forensic.py` running — only if the capability is judged worth a module.

The Marvell 88E151x datasheet PDF in this directory is family reference material.

[10gbase-t-phy-register-operation.md](10gbase-t-phy-register-operation.md) (converted from the vendor `.doc` alongside it) is Wiitek's own spec of the A2 Table 3 I2C→MDIO mailbox — official confirmation of the RollBall protocol this project drives (A2.127=3 table switch, PW2 password at A2.123–126, CMD states at A2.128). It also documents two registers not covered elsewhere: A2.250 (PHY part number, 1 = 88X3310) and A2.120 (copper link rate; vendor-gated to AQR/RTL8221/firmware 377 v4.5+, unverified on the Marvell CUX3610 units).
