MxL862xx firmware 1.0.85 — ABI notes for DSA driver work

Reference notes from working on the mxl862xx DSA driver (frank-w 7.1-main, and dangowrt/linux for comparison) against the BananaPi-shipped MxL86252C firmware 1.0.85 on a BPI-R4 Pro. Everything below was derived from the firmware image’s command dispatch table and handler code, then checked against driver behaviour on hardware. Addresses are given so the findings can be reproduced; no firmware contents are reproduced here. Part is also AI inferred. I have no NDA so this is just based on work on the firmware blob released.

Hope it helps get people started.


1. BRIDGEPORT_CONFIGSET (0x0402) is a read-modify-write with a blind spot

This is the one that cost the most time.

How the handler works. It reads the bridge port’s two hardware table entries (ingress 20, egress 21), applies only the field groups selected by the 32-bit mask, and writes both entries back unconditionally. So unselected fields are preserved — from whatever the read returned.

The blind spot. For bridge ports the firmware allocated itself (BRIDGEPORT_ALLOC, i.e. anything not backed by a physical port: tag_8021q virtual bridge ports, LAG bridge ports), the read-back returns an empty port map. BRIDGEPORT_CONFIGGET shows this directly — nBridgePortMap[] reads as all-zero for such ports even when forwarding through them works. CONFIGGET and CONFIGSET use the same read path and copy the same words, so the SET’s RMW starts from that same empty map.

Consequence for drivers. Any BRIDGEPORT_CONFIGSET on a firmware-allocated bridge port that does not include MASK_BRIDGE_PORT_MAP (bit 8) writes the map back empty. The port then has no egress members and CPU-originated traffic reassigned onto it goes nowhere. Physical bridge ports are unaffected — their read-back is accurate.

How it hides. In frank-w’s tag_8021q mode the VBP map is cleared at boot by an EGRESS_VLAN-only write that runs after the map was set, and then restored by a later full write triggered when the port joins a bridge (br_add_if sets promiscuity → port_set_host_flood → VBP rewrite). A port that is bridged at boot works; a port that is never bridged doesn’t. Symptom: “only works once it’s in a bridge”.

Rule of thumb. Treat a firmware-allocated bridge port as write-only: have one function own the whole request and select every field the driver ever sets, every time.

Handler: thunk 0x400a065c0x40065728; table read 0x400690e8; write-back 0x40068ed0; GET 0x400651b8; ALLOC 0x4006499c.

2. There is a firmware-side port map manager

The image contains a Zephyr subsystem, zephyr/subsys/net/l2/bp/bp_update.c (__bp_update, __bp_link_up, __bp_link_down, log strings like "BP%u: link up - entering portmap (fid=%u)").

  • It keeps an 11-byte record per bridge port (allocated, link-up, FID, logical port) and a per-FID active bitmap.
  • BRIDGEPORT_CONFIGSET runs it as a post-step whenever MASK_BRIDGE_ID is selected and the main handler succeeded. It then rewrites this bridge port’s map to “all bridge ports in the new FID, if this port’s link is up, else empty”, and adds/removes this port’s bit in every peer’s map — using its own CONFIGSET calls.
  • It is gated on a configuration bit (bit 17 of a RAM word at 0x20f369e0).

On the BPI-R4 Pro with 1.0.85 this gate appears to be off: the manager ignores whatever map the driver sent, and driver-supplied map contents demonstrably matter on this board. That is an inference from behaviour, not a register read.

Entry: 0x4001f69c(bpid, bridge_id)0x4001ef6c; record base 0x20f11a45.

3. Command-set deltas vs. the driver headers

Dispatch table: 234 entries of {u32 cmd, u32 handler, u32 payload_size} at file offset 0x11cab8 of the switch image. Comparing every MXL862XX_API_* call site’s struct size against the declared payload size:

  • Present, used by frank-w, payload mismatch (XPCS only): 0x1a01 PCS_CONFIG fw 10 / driver 6; 0x1a02 PCS_GET_STATE fw 12 / driver 8. Everything else matched exactly (35/35).
  • Absent from 1.0.85, used by frank-w: 0x1a03 XPCS_PCS_ENABLE, 0x1a06 XPCS_AN_DISABLE. These are the source of the -134 (Zephyr ENOTSUP) errors — the command is gone, not refused. dangowrt’s XPCS v2 structs (10/12/4/4/6) match the firmware byte-for-byte.
  • Present, unknown to frank-w: 0x1908 SYS_MISC_REG_RD (8 B), 0x0106 PORTLINKCFGSET (30 B), 0x0108 PORTCFGSET (37 B), 0x020a PCERULELOGICWRITE (466 B, same struct as 0x0202), 0x1a0e (10 B, §5).
  • Present, used by frank-w, dropped by dangowrt: 0x1a0a PRBS_CFG, 0x1a0b BERT_CFG, 0x1a0c EQ_GET, 0x1a0d SIGNAL_DETECT — the serdes self-test and ethtool serdes stats. A wholesale sync to dangowrt loses them.
  • 0x1a07 (6 B): frank-w calls it FORCE_SPEED, dangowrt PCS_LINK_UP. The handler takes a packed mode word plus a speed in Mbit/s, acts for SGMII/USXGMII/QSGMII, and short-circuits when in-band AN is on — it is a per-link-up PCS_LINK_UP.

4. SYS_MISC_REG_RD (0x1908)

{ __le32 addr; __le32 val; }. Reads one 32-bit register. The address must be in [0xC0938000, 0xD0FBFFFF] (the switch register window) or the firmware returns -EINVAL (-22). No side effects, so it is safe to probe for rather than version-gate. dangowrt uses it to read the CHIP ID registers at 0xC0D28884/0xC0D28888 (part number from L[15:12] | M[11:0] << 4, revision from M[14:12]). Its sibling 0x190a (12 B) is a masked write against a five-entry address whitelist.

Handler 0x40060ce0.

5. 0x1a0e identified: XPCS per-channel AN/link status

10-byte payload, not in either driver. Inputs: byte 0 XPCS index (<2), byte 1 channel index (< channel count). Outputs: byte 2 AN complete, byte 3 link, u16 at 4 speed in Mbit/s, byte 6 duplex, bytes 7–8 two further AN-result bits. Channel count is derived from the PCS mode (USXGMII 1/2/4-channel via 0xe001c[12:10] → table {1,1,1,2,2,4}; QSGMII → 4). It’s the per-channel form of what 0x1a02 already returns via its sub-port field; nothing in the driver needs it.

Handler 0x40053c9c.

6. The image is two firmwares

The BPI .bin is a container with two MCUboot images: the switch firmware (1.0.85, ~1.27 MB, Zephyr on ARC) at file 0x00a7f4, and the internal GPHY firmware (build 105 = 0x69) at 0x1ba7f4. devlink dev flash updates both at once. If you see GPHY-side behaviour change across a “switch firmware” update, that’s why.

7. Reproducing any of this

  • Load mapping for the switch image: file_offset = vaddr - 0x40000000 + 0xa014.
  • Toolchain: binutils-arc-linux-gnu (Debian/Ubuntu package). Wrap the image with an assembler stub that .incbins it, then arc-linux-gnu-objdump -d -M cpu=hs38 --adjust-vma=0x3fff5fec.
  • Dispatch table at 0x11cab8; walk it with a 12-byte struct.unpack('<III').
  • Log strings resolve through the same mapping and are the fastest way to find a subsystem (bp_update.c, mxl_bp.h, etc.).

Corrections welcome.