Expansion modules: every port the coprocessor reports, named as the coprocessor names it¶
Since 2026-10-16 the XGS 3300's front-bay module ports come up as interfaces of their own, named as
the metal names them - A1 to A4 for a 4x10G module in bay A - after the panel's twelve. Nothing
in the code names a module: the ports are read from the coprocessor at every boot, so a module that
nobody here has seen binds the same way. This page says where each fact comes from, what was
measured, and what is not tested yet.
The bay belongs to the coprocessor¶
The front bay with the PCIe-like edge is the coprocessor's slot, not the host's - hardware/xgs3300.md. The board file names how its presence and its identity are read, on the coprocessor:
npu0.slotA.presdet = cpld:8:20 presence: CPLD register 0x08, bit 20
npu0.slotA.vpd = i2c1:1:0x50 the module's VPD EEPROM, on the coprocessor's I2C bus 1
A module is an assembly of its own, with its own CPLD and its own VPD, and the vendor's board files
describe it as a template: a module file (AMDA0207-0001R00.txt, "phymod 4x10GBaseSR (SFP+)")
names its ports %c4 to %c1 and leaves their numbers to be computed, and at every boot the
coprocessor's xgs-platform-init folds the template into the board's own file under the bay's
letter. The result is the coprocessor's platform database, and xgs-platform - the vendor's read
tool, on the coprocessor - prints it.
Three sources, joined by address¶
| source | what it knows | how the host reads it |
|---|---|---|
| the platform database | every port's name on the metal, its lifport (the fast path's logical interface), its speed, its MAC; each bay's module part number | src/opnsense/scripts/octep/npu-platform.sh, through the coprocessor's console (/dev/cuau2), with xgs-platform |
| NetAgent's discover | every port's tag - the handle the host binds and every frame carries | dev.octep.0.nwa.discover, then dev.octep.0.nwa.ports |
| NetAgent's MAC attribute | the MAC of a tag | nwa.op=4 nwa.sub=3 nwa.port=<tag> |
Neither side knows what the other does - NetAgent has no names and the platform database has no
tags - but both carry the port's address, so the address is the join:
src/opnsense/scripts/octep/ports.py.
The discover answer, decoded¶
The reply's layout is the vendor's own, struct nwa_msg_init_ret in its GPL NetAgent host module
(netagent/module-host/mv_nwa_host.h): after the eight-byte header (the type 0x14 and the status),
a port count, then one twenty-byte struct nwa_msg_port per port - the tag and the flags, the
largest MTU, the unicast, multicast and MAC table sizes, and two feature words. Read on the second
XGS 3300 with the 4x10G module fitted:
| # | tag | flags | name | |
|---|---|---|---|---|
| 0 | 0x0000 |
0x0000 |
not a front port | |
| 1, 2 | 0x0001, 0x0002 |
0x0001 |
F1, F2 | the board's two 10G cages |
| 3 - 6 | 0x0003 - 0x0006 |
0x0001 |
the module | the same table sizes as F1 and F2: ports straight on the coprocessor |
| 7 | 0x8000 |
0x0000 |
the switch itself, not a front port | |
| 8 - 17 | 0x8100 - 0x8a00 |
0x0001 |
Port1 - Port8, F3, F4 | the ten behind the switch |
Eighteen entries, sixteen front ports. Before this the answer could not be read at all: the link
poll asks the same window every round and wrote over nwa.last the instant a discover released it.
The discover now keeps its answer in a record of its own.
The join, measured¶
| tag | MAC matches | name | lifport | speed |
|---|---|---|---|---|
| 1 | eth10 | F1 | 10 | 10G |
| 2 | eth11 | F2 | 11 | 10G |
| 3 | eth12 | A4 | 12 | 10G |
| 4 | eth13 | A3 | 13 | 10G |
| 5 | eth14 | A2 | 14 | 10G |
| 6 | eth15 | A1 | 15 | 10G |
0x8100 |
eth0 | 1 | 0 | 1G |
0x8a00 |
eth9 | F4 | 9 | 1G |
The rows for tags 1, 2, 0x8100 and 0x8a00 agree with what the driver already knew about the
panel's ports, which is what says the join is right; the module's four rows are new. The coprocessor
lists the same sixteen with xgs-ports -b: 1 to 8, F3, F4, F1, F2, A4, A3, A2, A1.
What the boot does now¶
src/opnsense/scripts/octep/bringup.sh, after it has bound the panel's twelve as before:
npu-platform.shreads the platform database through the console - logging in only if the console asks (root, no password), reading withxgs-platform, and logging out again - into/var/db/os-xgs-npu/npu-platform.txt.ports.pyissues a discover, takes every port with flag bit 0 set that is not one of the panel's twelve, asks each for its address, and joins it to the database. It prints one line per port for the boot and writes everything known - the module's part number included - to/var/db/os-xgs-npu/ports.json.- Each port is bound as the board's own cages are: raised, its logical interface installed with its
own address (
LIF_ADD), then its tag (PPORT_UPDATE). - After the panel's twelve interfaces, each module port is presented with
dp.if_ifaceset to its lifport anddp.if_labelto its name, so the driver describes it "XGS front port A1" the way it describes the panel's.oxp0tooxp11keep the panel's ports, so an assignment made before the module was fitted still names the same metal.
OCTEP_DP_IF_MAX is 32 (it was 13): the largest the vendor's board files put on one coprocessor is
twenty front ports, plus a module's eight.
Without the console - a reader already holding it, or a coprocessor that does not answer - the
ports still bind: a direct port's lifport follows the board's two cages in tag order, which is the
rule the measurement above shows, and its name is Ext and the tag (Ext3), which says plainly
that it is not the name on the metal.
The modules the vendor's board files describe¶
From the GPL xgs-bsp/data files (the first line of each module template). Only the first has been
fitted here.
| assembly | module |
|---|---|
| AMDA0207-0001 | 4 x 10GBase-SR (SFP+) - fitted, measured above |
| AMDA0105-0001 | 4 x 2.5GBase-T PoE |
| AMDA0106-0001 | 8 x 1000Base-T |
| AMDA0108-0001 | 8 x 1000Base-SX (SFP) |
| AMDA0108-0002 | 8 x 10GBase-SR (SFP+) |
| AMDA0109-0001 | 4 x 1000Base-T, fail-to-wire |
| AMDA0110-0001 | 4 x 1000Base-SX (SFP), 2 x 1000Base-SX fail-to-wire |
| AMDA0110-0002 | 4 x 10GBase-SR (SFP+), 2 x 10GBase-SR fail-to-wire |
| AMDA0110-0003 | 2 x 10GBase-SR fail-to-wire |
| AMDA0112-0001 | 2 x 40GBase-SR4 (QSFP) |
| AMDA0112-0002 | 1 x 40GBase-SR4 (QSFP) |
| AMDA0124-0001 | 4 x 1000Base-T, 4 x 1000Base-T PoE |
| AMDA0213-0001 | 12 x 1000Base-T, 4 x 2.5GBase-T ("nicmod") |
| AMDA0214-0001 | 2 x NBase-T (RJ45), 2 x 10GBase-SR (SFP+) |
An XG-generation module - a host PCIe NIC - does not belong in this bay and was measured absent from all five places it could appear; see the hardware page.
Tested, and not yet¶
On the second XGS 3300 (board revision 09, the 4x10G module in bay A), 2026-10-16, after a reboot onto this driver and bring-up:
- the bring-up log: "read the coprocessor's platform database", "bound 4 expansion module port(s): A4 A3 A2 A1", "up: 16 front-port interfaces" (it said 12 before);
oxp12tooxp15described "XGS front port A4" to "A1";oxp15, A1, reports link at 10Gbase-SR, the other three no carrier;- the panel's twelve unchanged - the WAN still on
oxp3, its address and route as before; dp.if_labelempty after the adds; the fallback, run by hand without the database, gave the same lifports under the namesExt3toExt6.
Not yet measured: traffic through a module port. With A1 up and nothing assigned to it, nothing arrived in fifteen seconds and the driver's count of frames with an unclaimed tag recorded none for tag 6 - which says nothing about the module until it is known what the far end of that cable sends.
Also open: Link Health draws the panel's twelve and not the bay; the first-boot assignment (interface-assignment.md) puts module ports in the LAN bridge only on a box nobody has set up, so on a box in service they come up unassigned and are assigned by hand.
The XGS 136 is a different kind of bay¶
The desktops' expansion bay is the host's: the vendor sells a 3G/4G module, a 5G module, or a
second Wi-Fi radio for the w models, and the XGS 136's board has an empty M.2 socket ( Mini PCIe socket, beside CN8)CN8, which by its position is the bay's own connector (corrected 2026-10-18). The
vendor's host system carries modem drivers (qmi_wwan, cdc_mbim, option, qcserial, sierra)
and Wi-Fi drivers (ath9k, ath10k, mt7915e). OPNsense 26.7's kernel has umb (MBIM) and
u3g for modems, so a modem in MBIM or serial mode is a FreeBSD device the moment it is fitted;
it has neither ath10k nor mt7915. None of that has been fitted or measured here, and none of it
goes through this page's code.
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.