Bring the Spanning Tree page in line with a typical managed switch's
per-port panel. Configuration gains the full-range path cost (raw
0..200000000, 0 = auto, replacing the old 1000x-scaled byte), a
point-to-point admin control (auto/on/off), and the priority is now a
0..240 step-16 dropdown. A new status table shows, per port, the Port
State, Role, Designated Bridge / Port ID / Cost (learned from received
BPDUs, kept per port and aged via the BPDU age), Operational Edge and
Operational Point-to-Point.
The designated fields fall back to presenting this switch as the
segment's designated bridge when no fresh BPDU has been heard (so a
quiet port shows our own bridge-id, as the vendor UIs do). /stp.json
carries the packed hex fields plus our own MAC for that fallback.
Space: reclaim BANK2 for the above by moving rtl837x_pins to HOME and
compacting leds_dump into a register-address table (~800B); bandwidth
returns to BANK1. No BANK3 - hardware-verified that PSBANK > 2 crashes
this SoC at boot (a bricked unit and an SPI-programmer recovery earlier
today); a warning to that effect is now in rtl837x_lldp.c.
Hardware-verified: cost 200000000 and p2p off round-trip through the CLI
and JSON, the status table populates correctly with STP enabled (all
ports Forwarding/Designated, oper-edge and oper-p2p True), LACP 3f/3f
and the LAN unaffected.
(cherry picked from commit 2ec62072f061dc9e78bc821ba1c297cb6819e206)
Enabling STP on a bridge whose management rides an in-band VLAN can cut
off that very management - and not only by our own blocking: on this
network the upstream TP-Link Easy Smart switch's "loop prevention"
reacted to our BPDU hellos by blocking ITS port towards us while our
ASIC was all-forwarding, isolating the whole segment until a power
cycle. Recoverable only by going quiet.
Add a commit-confirm watchdog: while STP is enabled, any HTTP request
re-arms a countdown ("stp failsafe <seconds>", default 180, 0 disables);
if management stays silent for the whole window, STP disables itself,
which also stops BPDU TX so a neighbour's loop protection can release
its block. The web UI polls /stp.json every 2 s, so an open browser
naturally keeps the watchdog re-armed. The trip is reported via
/stp.json (fs, fsT) and as a warning on the Spanning Tree page.
Deliberately not conditioned on our own MSTP port states - the incident
above proves the uplink can be dead while every local port forwards.
Also bound the NIC DMA busy-waits (nic_tx_packet, nic_rx_header,
nic_rx_packet): an unbounded spin on SFR_NIC_CTRL freezes the entire
main loop (timers, HTTP, ARP) if the ASIC ever fails to consume a
transfer; give up after ~65k polls and drop the frame instead.
Hardware-verified end to end: with priority 15 against a live RSTP
bridge the uplink died 6 s after "stp on" and the network recovered BY
ITSELF 66 s later (trip at 45 s + neighbour release), fsT=1, LACP and
LAN intact. Telemetry via syslog-to-edge-port host confirmed the full
chain: countdown 44->4, trip, hello TX stopping at the trip.
(cherry picked from commit 1fa9775156fd6d7ebfdda2382f73430b86601230)
Implements the standard 802.1D-2004/802.1w configuration surface:
Bridge: priority (0-15 x4096), hello time, max age, forward delay,
force-version (RSTP v2 / STP-compatible v0 Config BPDUs), tx hold
count (per-port per-second BPDU budget).
Per port: enable, admin edge (forwarding immediately - no listen gap),
auto edge (forwarding after 3 s of BPDU silence; DEFAULT, so
host-facing ports no longer take the full forward delay),
path cost (0=auto/20000), port priority, BPDU guard (port disabled
on BPDU receipt), root guard (never accept a better root on the
port), BPDU filter (no BPDUs in or out).
Engine additions: root max-age expiry (reclaim the tree when the root goes
silent), root path cost accounting (rx cost + root-port cost, advertised in
our BPDUs), loop detection (our own BPDU coming back blocks the port for a
listen period), topology-change counter, approximated per-port roles
(Root/Designated/Alternate) for diagnostics.
CLI: "stp prio|hello|maxage|fwd|txhold|version ..." and
"stp port <n> on|off|edge|cost|prio|guard|filter ..." (stp_parse, delegated
from cmd_parser); all forms accepted by the startup-config validator so the
whole configuration persists. /stp.json now reports config + status; the
Spanning Tree page exposes everything with immediate-apply controls and live
state/role columns (edit-in-flight guard against the 2 s refresh).
8051 memory: the module moves to code BANK2; internal-RAM pressure from
cross-bank calls resolved by xdata loop iterators/scratch, __reentrant on
the small helpers, and moving httpd's header-pointer globals to xdata.
Verified on hardware (SWTGW218AS): defaults land per standard; priority and
hello change live; admin-edge ports (the LACP bond uplinks) keep the LAN at
0% loss THROUGH "stp on"; auto-edge ports forward after 3 s; a port that
heard real BPDUs (a VM bridge behind physical port 6) correctly declined
auto-edge, sat out the full listen period and became Designated; tc counts
promotions; we win the root election at priority 16384 vs 32768.
(cherry picked from commit 09a34dc6acdc81ab9cab0727d2f4a59c68131a3e)
Add a Spanning Tree page: an on/off toggle driving the existing "stp"
command over /cmd, and a live status section fed by a new /stp.json
endpoint - the elected root bridge (priority + MAC), our path cost,
whether we are the root, and the per-port STP state read live from the
ASIC's MSTP register (same 2-bit encoding stp_setup() writes). Ports are
reported by their physical numbers.
Recovered-from: 3132319, 9365c86
Real root cause of the "web login fails from a browser but works from curl":
the uIP httpd is built with UIP_CONF_MAX_CONNECTIONS = 1 and uses global
response state (outbuf/slen/session), i.e. it serves exactly one TCP connection
at a time and closes it after each response - but never advertises that via
the Connection header. A browser's HTTP/1.1 client therefore assumes the
connection may be persistent and can park it in its keep-alive pool for reuse;
a later request sent on that pooled connection hits one the server has already
closed, and a POST (unlike a GET) is never retried by the browser, so it can
be silently lost this way.
This adds "Connection: close" to every response so the browser does not pool
and reuse a connection the server is about to drop. On its own this did not
fully explain the reported login failures - the actual authentication bug is
fixed in the next commit (the Cookie header parsed at a fixed offset) - but it
is correct behaviour for a server that only ever handles one connection, and
removes one source of dropped requests.
On a QSFPTEK QT-SFP+-T (RTL8261C) 10GBase-T module, the diag type field
(92) comes back as 0x00, but there's actually some statistics available
like temperature. Other metrics may be hard-coded values.
Add a basic struct that allows matching on vendor and/or model, using a
bitfield to allow multiple quirks for a given SFP module. Only
SFP_QUIRK_DDM is implemented.
For modules matching SFP_QUIRK_DDM, attempt an I2C read of the MSB of
module voltage during probe if DDM is "unsupported" - if it's not 0xff,
override the reported options so we can pull the diagnostic data.
To allow simpler comparisons, convert the ASCII fields (vendor,
model, serial) from space-padded to standard NULL-terminated strings.
strcmp() is moved from httpd.c to rtlplayground.c alongside other string
functions and shared between them.
The __reentrant keyword is used for the new functions to avoid using up
additional OSEG space. This allocates the variables on the stack, which
is OK for this particular code path.
The JSON assembly in send_status() is slightly modified to treat the
sfp_module_* data as standard NULL-terminated strings, and a repeated
subtraction was moved into a uint8_t to declutter the code.
- itoa16_html: add comment that the function is sufficient for VLAN
IDs (<=4094); generalization not needed.
- send_vlanlist: replace post-write bounds check with a pre-write
guard using worst-case entry size (138 bytes + 1 for closing
bracket). Old comment claimed ~45 bytes per entry, which only
held for short names. With a 117-char name (the actual bound from
CMD_BUF_SIZE) entries can reach 138 bytes, and the post-check
would not have prevented an overflow.
- Document the 0x02 check in sfr_data[0] as the VLAN table entry
valid flag.
Returns a JSON array of all configured VLANs with their IDs and names,
e.g. [{"id":1,"name":""},{"id":20,"name":"IoT"}].
The endpoint iterates VLAN IDs 1..4094 and filters by the validity bit
in sfr_data[0] (0x02), following the same pattern as vlan_create() and
vlan_setup() in rtl837x_port.c.
Also adds a small itoa16_html() helper for emitting decimal numbers
up to 4 digits (analogous to the existing 8-bit itoa_html()), used
for VLAN IDs which can reach 4094. Response builder uses the existing
vlan_name() helper for the name lookup, consistent with send_vlan().
Buffer overflow is prevented by breaking out of the iteration loop at
TCP_OUTBUF_SIZE - 60.
This endpoint is the foundation for upcoming UI improvements
(VLAN selector dropdown and overview table).
While the current implementation works for what it is actually used, it
is broken when trying to do larger transfers.
The length field in the control register has a size of 4 bits. In every
transfer, length+1 bytes are read. Thus, each transfer is limited to a
maximum of 16 bytes. Add a check for the length, and write the correct
value to the register.
Also update the loop in "sfp_send_data" to properly increment the output
register. Remove the unused special case for a length of 128 bytes.
When device has no LOS pin and module has no extended status, the
RX LOS value will not be shown. When both are present, the equality
check is performed. When only one is present, the present value
will be shown.
json from the switch, will still contain the field, but can be
null for when pin is not available.
The state of the SFP module is being already send in status.json,
but was not parsed via Web UI. When debuging SFP LOS/Signal detection
it is usefull to see what module is reporting back.
Extended the SFP mouse-over information with:
- RX LOS as reported in 0x02 bit of register 238
- External TX Disabled from 0x80 bit of register 238
- TX fault from 0x04 of the same register
- The last state of RX LOS pin (available also when there is no 0x40
option on the module)
This should help identify missing/incorrect setup of TX disabled pin.
I noticed that entering some vlan config, clicking "Update/Create", then
clicking "Get Configuration" resulted in Tagged/Untagged/PVID displaying
completely wrong data. It looks like the parisng in fetchVLAN() in
vlan.js was just completely disconnected from the layout of the
registers read by vlan_get() in rtl837x_port.c. Who knows how that
happened.
1. Fix fetchVLAN() member/untag parsing: the old code read bits [9:0]
as untagged and bits [10:19] as tagged, but the VLAN table register
layout is members in [9:0] and untag in [19:10]. Now correctly
derives tagged (member && !untag) and untagged (member && untag).
2. Add PVID to vlan.json response: PVID is stored per-port in separate
PVID registers (RTL837x_PVID_BASE_REG), not in the VLAN table entry.
The old code nonsensically tried to read it from bits [20:29] of the
VLAN table. Add port_pvid_get() and build a pvid bitmask in
send_vlan() so the JS can parse it correctly.
3. Remove auto-PVID logic from setC(): setC() is called by fetchVLAN()
while it's loading existing config, and so this logic mangled the
display of the existing config. Also, it doesn't make sense to
auto-set the PVID for tagged members (PVID relates to untagged
ingress.)
We use the SoC's UUID to generate a fixed MAC for a particular
switch device. The MAC generated uses a Realtek prefix and then
is followed by 3 bytes genertaed from the first 3 bytes of the
UUID xored with the last byte in order to prevent being able
to deduce the UUID from the public MAC.
RTL has up to 3 SCL pins and up to 4 SDA pins. Lets allow configuring
I2C with individual BUS numbers instead of 0/1 I2C.
This enables support for devices where SCL line is not shared between
SFP modules. MUX registry is now initialized depending on needed
pin function.
More over, some SFP pins require special MUX settings, lets initialize
those depending on SFP configuration. This adds TX Disable pin,
which right now is set to low at startup.
Caveats:
- We may still override MUX registry later
- Not sure how to handle invalid bus definitions
- Without SFP is it fine to *not* initialize anything?
- Do to RAM limitation we do inititalize output GPIO to low