ARMADA - Marvell CN913x¶
PCI id 11ab:7080
driver npuep (contrib/npuep)
platforms xgsdt1, xgsdt2-116, xgsdt2-126136, xgsdt2-138
hardware Sophos XGS 136, assembly AMDA0201, 14 ports - ON THE BENCH
state all fourteen front ports carry traffic, connections forwarded in hardware -
93% by the table below (re-weighed 2026-10-20)
This is the family the project started on and the only one where the datapath works. Everything in the top-level README's "What works" section is this family, and the protocol documents describe it:
- ../facility-protocol.md - the facility framework and the handshake
- ../giu.md - the GIU datapath, which is what carries packets
- ../mvmgmt.md - the management netdev
- ../netagent.md - the network agent and the switch
- ../rpc.md - the RPC facility
- ../npu-bring-up.md - what has to happen, in order
Where this stands¶
First weighed on 2026-10-17, in the same form as the XGS 3300's table: each row says what it covers, the percentage is a judgement, and the column beside it names what holds the remainder.
| component | done | weight | still missing | what holds it | |
|---|---|---|---|---|---|
| 1 | the receive rate - one stream 773 Mbit/s in and 934 out with the 1 ms poll (415), 976 and 989 at MTU 9,000; a thousand connections at once through the box at MTU 1,500, the link full, in hardware (measured, 2026-10-18) | 95% | 20 | 1.00 | |
| 2 | network interfaces - fourteen, assigned by the first boot itself (measured 2026-10-17: LAN bridge 192.168.1.1, WAN on Port1); IPv6 neighbours found on a routed port since 2026-10-18 (455) | 95% | 12 | 0.60 | the bridge is not rebuilt after a reload of the module - three configctl calls by hand |
| 3 | all fourteen front ports - bound, addressed, presented; F1 with a 1000baseT, read from the module, since #533 - measured), two streams of 2 GB through it counted to the frame (measured) |
8 | |||
| 4 | receive service - the rings polled every millisecond, 1,024 frames a pass | 90% | 6 | 0.60 | receive is polled, not driven by the coprocessor's interrupts |
| 5 | performance - the gigabit link full from MTU 1,000 (908 to 961 clear, IPsec 2 to 8% below) | 80% | 7 | 1.40 | the 2.5G labels 11 and 12 not measured at 2.5G; the datasheet's way not taken |
| 6 | persistence - boot hook, a kernel stamp that is enforced, the kernel followed through 26.7 to 26.7.7 with nothing done by hand, the switch's jumbo mode, IPsec offload and the temperature at every boot | 100% | 5 | - | |
| 7 | resilience - two hours of TLS through the box with the flow maker on, 795 GiB and not a byte changed (measured); a connection the fast path gives back no longer loops (measured) | 4 | |||
| 8 | protocol and hardware understanding | 95% | 15 | 0.75 | 383 - another board of this family whose facility table never appears |
| 9 | the control plane | 100% | 10 | - | |
| 10 | the outbound datapath - on all four transmit queues since 2026-10-18, measured with every queue in use (ipv6-and-queues-2026-10-18) | 100% | 10 | - | |
| 11 | the rest of the appliance - the coprocessor's temperature in OPNsense's sensor list, fan and PHY readings over the management link, PoE read, the port identify light (the owner saw it work) | 80% | 6 | 1.20 | the SFP cages' diagnostics, |
| 12 | flow offload - |
10 | |||
| 13 | IPsec offload - AES-GCM and CBC with HMAC both ways in hardware, NAT-T, fragmentation before the envelope, replay windows to 4,096, every decrypted frame terminated; on by its setting since 418 | 85% | 10 | 1.50 | IPv6, outside and inside, not yet taken on this board; many tunnels not measured |
Weights sum to 123. Weighted: 91% - re-weighed 2026-10-18 with row 12 at 92% (it was 85% on a delivery that was not happening): the remainder is 10.65 of 123, which is still 91%. Weighted: 92% - row 12 at 95% once IPv6 was measured through the box the same evening: the remainder is 10.35 of 123. Weighted: 92% - row 12 at 97% once the flow writes were posted and a takeout took a connection out, later the same evening: the remainder is 10.15 of 123. Weighted: 92% - re-weighed 2026-10-19 with row 3 at 95%, F1 carrying a module and measured, and row 12 at 98%, its counts reaching pf and the interfaces: the remainder is 9.65 of 123. Weighted: 93% - re-weighed 2026-10-20 with row 7 at 80%, once the integrity soak was run on this board: the remainder is 8.45 of 123.
What is specific to this family¶
The facility table sits at a fixed offset in the window and is guarded by a cookie -
0xAFACAFAC for the facility itself, 0xD0FAC10D for the barmap - and its facility records are
{bar, type, offset, size}. Target-to-host doorbells are MSI-X vectors, which npuep allocates.
A fifth facility, GIU, exists here and is what carries the front ports. It does not exist on OCTEON at all, and its absence there is the clearest single sign that these are different protocols rather than one protocol with two PCI ids.
The reset polarity is a per-board table rather than a constant, so the module reads the assembly number out of the bridge's EEPROM and looks it up. Values are carried for AMDA0200, AMDA0201, AMDA0202-0205, AMDA0208 and AMDA0224. Only AMDA0201 has been tested on real hardware.
What is held here, and what it allows¶
| material | detail |
|---|---|
| hardware | XGS 136 on the bench, assembly AMDA0201 |
| coprocessor rootfs | complete, including NPU-BSP-slot1/rootfs/ |
| fast path | usfp for this family, 1.9 MB, with symbols |
| firmware | U-Boot for all four ARMADA platforms, from BSP 21.0.0.169 and 22.0.2.546 |
| source | the Sophos GPL release, 794 files, including the pcie_ep_armada patches |
| launcher | armada_target_setup_usfp.sh, selected by DP_DT1/DT2_STARTUP_SCRIPT |
This is the family with the most complete material and the only one whose ports all carry traffic.
Building and running it¶
npuep is not packaged, for the same reason octep is not: an out-of-tree module has to be
compiled against the headers of the kernel that is actually running, and OPNsense ships no kernel
sources. So it is built on the appliance. install/install.sh lays the sources down in
/usr/local/share/os-xgs-npu/npuep and builds through install/kernel-follow.sh - the same
follower that rebuilds the OCTEON TX module after every kernel update, which since 2026-10-13
follows whichever of the two modules is installed - and 07-npuep loads it at boot, after
reading the kernel stamp beside it and refusing a module built for another kernel (the previous
module is kept as npuep.ko.prev for the kernel it was built for). Before that date the follower
answered every tick on this appliance with "the driver is not installed on this machine", the
upgrade and stop hooks were gated on the OCTEON TX's PCI identity alone, and the boot hook read
no stamp: a kernel update here would have loaded the old module unchecked.
Measured on 2026-10-13, on the XGS 136, with the owner running the update from the GUI: OPNsense
26.7 to 26.7.7 brought the kernel from 15.1-RELEASE-p1 to p4. The reboot's stop hook ran the
follower; it read p4 from /boot/kernel/kernel, fetched that commit's sources, built the module
from the laid-down tree, kept the p1 module as npuep.ko.prev with its stamp, installed the p4
one and cleared the fall-back - 2 min 59 s in all - and the host came up on p4 with the module
loaded, all fourteen ports present and the WAN lease back, nothing done by hand.
sh contrib/npuep/fetch-sources.sh
sh install/install.sh
The coprocessor from the host¶
The coprocessor's console is the host's second UART - hint.uart.2.at="isa", port="0x3E8",
irq="3" in /boot/loader.conf.local, so /dev/cuau2 on OPNsense at 115200 - the same
arrangement as the OCTEON TX appliance's. A newline answers marvell login:; root with no
password gives a root shell on a Buildroot 2018.11 userland over Linux 4.14.207, with 3 GB of
memory and usfp running. An idle coprocessor prints nothing, so silence is not death. Read on
2026-10-14 on the XGS 136.
The helper that runs one command and returns its reply (as on the OCTEON TX box: a background
reader on the line, the command between two echoed markers) has one difference here: the marker
is sent in two quoted halves, echo "---MARK$$""---", because this shell echoes the command line
back wrapped at 80 columns and bracketed-paste codes around it, and a marker that appears whole in
that echo is taken for the first marker. Keep each command short and plain; some pipes with
quoted field separators came back empty through the line and were not understood.
What the console gives on this appliance, all of it read-only:
- The vendor's tools are on the coprocessor, not on the host as they were under the
original firmware on the OCTEON TX box:
xgs-platform(the platform store: assembly AMDA0201-0003, revision 08, the base MAC and 16 addresses),xgs-ports -b(the real MAC of every label),xgs-phy-temperature -l <label>,xgs-poe -m all,xgs-sff-decode -l <label>,xgs-eeprom -r,xgs-i2c-reg,xgs-mdio(clause 22 and 45),xgs-reg(raw NPU registers),xgs-led <phy|all> <left> <right>andxgs-led-identify -l <label> <seconds>(the identify light this project's Link Health page has no path to from the host - here it has one, through the console; not wired),xgs-sgpio. Two are never run:xgs-healthmondandxgs-1us-led-bus-test. - There is no CPLD on the XGS 136.
xgs-cpld -tand every register read answer "Error opening cpld handle. No such device". The 1U sensor block - CPU, NPU and inlet temperatures and two fans behind a SMBus address, the one the OCTEON TX appliance reads from its host bus - is not on this board. Its temperatures are the host's own (amdtemp, the NCT6779D), the coprocessor's thermal zones (ap-thermal-icandcpu0..3, and the two CP dies atf2400078/f4400078, 41 to 50 C idle), and the PHYs:xgs-phy-temperaturereads 55 C on label 1 (an 88E6193X PHY) and 49 to 53 C on labels 9 to 12 (the AQR412C lanes). - The port map, from the vendor's own platform data (
AMDA0201-0003R00.txtin the GPL release, confirmed by the tools): labels 1 to 8 are the 88E6193X's ports 1 to 8; F1 and F2 are its ports 9 and 10, SFP cages oni2c1(buses 2 and 1, 0x50 and 0x51) with GPIO lines for presence, TX fault, TX disable, RX LOS and rate select; labels 9 to 12 are the four lanes of the AQR412C on the CN9131's own ports, 9 and 10 rate-capped at 1G, 11 and 12 at 2.5G and on the PoE module - one module, 62 W, status good, asxgs-poereports. - The fast path's syslog (tag
dpdk) saysmrvl_mtu_set(): MTU too big, max MTU possible limited by current mbuf sizeat start-up: the jumbo question for this family, unmeasured. mvmgmt0, the management link over PCIe, is up at the host and NO-CARRIER at the coprocessor and carries nothing (issue 374); the coprocessor runssshd, so that link, once it works, is the fast channel. Until then the console is the only path.- Where to look, and what this was read from. The parts named above are photographed on
the hardware page: the 88E6193X switch,
the AQR412C quad PHY, the PD69104 PoE controller,
the NCT6779D, the CN9130 and its memory,
the J1 LED header, with the port chain
and the board map drawn from them. The port map, the PoE
module and the PHY addresses come from the vendor's own platform data file for this assembly,
data/AMDA0201-0003R00.txtin the GPL release, read beside the tools' answers; the console helper and the link are on mvmgmt.md and mvmgmt-in-opnsense.md; the measurements of 2026-10-14 are on issues 374 and 372. - The plugins read and act over the link now. With the management link in place, os-fanctl
reads the coprocessor's seven thermal zones and the PHY behind every copper label in one
command over it (nineteen readings in half a second, cached a minute; the groups
npuandnicon its page), and Link Health identifies a front port by asking the coprocessor's own identify tool by the printed label - the chassis group saysidentify: coprocessor, and the capability is offered while the link's script is installed. Both measured on the XGS 136 on 2026-10-14; the light itself was not yet watched. - The switch tool runs here unchanged.
contrib/mvsw, built for the OCTEON TX box, drives this board's 88E6193X through the samemvmdio_uioat bus 0, SMI address 2 (the board file'smdio22:0:2):idanswers device 0x0193 revision 0;dumpshows port 0 (the coprocessor's uplink) forwarding with link and a VLAN map to every port, every panel port forwarding with a VLAN map to port 0 alone - the same isolation as on the 3300 - and the two SFP cages at ports 9 and 10; Port Control 2 reads jumbo mode 2 (10,240 bytes) on port 0 and mode 1 (2,048) on ports 1 to 10, the 3300's values exactly - as the coprocessor leaves them; since 2026-10-17 a start hook writes 10,240 into ports 1 to 10 at every boot (scripts/xgs/switch-jumbo.sh). It went over the console in base64, 500 characters a line, verified by checksum at both ends. One difference from the 3300: about one read in ten returns another transaction's data here - the coprocessor's own network agent polls the same MDIO bus, and the multi-chip access is a command write followed by a data read - so a register is read three times and the majority taken, and nothing here writes on the strength of a single read.
The discussion of this page is held in the project's GitHub Discussions. Showing it here loads giscus from giscus.app and GitHub, and writing in it needs a GitHub account - what that means.