stp: full RSTP configuration (bridge + per-port), CLI + GUI + persistence

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)
This commit is contained in:
d00f
2026-08-04 03:25:10 +02:00
parent 89cc9828c3
commit 531bbbea5d
9 changed files with 641 additions and 158 deletions
+3 -3
View File
@@ -41,8 +41,8 @@ __xdata uint32_t cont_addr;
// HTTP header properties
__xdata uint8_t boundary[72];
__xdata uint8_t *content_type = 0;
__xdata uint8_t *session = 0;
__xdata uint8_t * __xdata content_type = 0;
__xdata uint8_t * __xdata session = 0;
// Global variables holding POST state
__xdata uint16_t bindex; // Current index into the boundary
@@ -54,7 +54,7 @@ __xdata char passwd[21];
__xdata char session_id[SESSION_ID_LENGTH + 1];
__xdata uint8_t authenticated;
__xdata uint32_t now;
__xdata uint8_t *timeptr;
__xdata uint8_t * __xdata timeptr;
__xdata uint32_t last_session_use;
#define TSTATE_NONE 0
+48 -20
View File
@@ -528,14 +528,14 @@ void send_lag(void)
}
/* STP status for the L2 page ("/stp.json"): enable state, the elected root
* bridge (priority+MAC) and our path cost to it, whether we are the root, and
* the live per-port STP state read from the ASIC's MSTP register (2 bits per
* port: 0 disable, 1 blocking, 2 learning, 3 forwarding - same encoding
* stp_setup() writes). Ports are reported by their physical number. */
/* Scratch for send_stp(): a plain local would land in the near-full 8051
* internal-RAM overlay (OSEG). */
/* STP status + configuration for the Spanning Tree page ("/stp.json").
* Bridge config (prio index 0-15, hello/maxage/fwd seconds, rstp flag, tx
* hold), elected root (priority byte + MAC), our path cost, root port, TC
* counter, and per port: physical number, live ASIC state (2-bit MSTP field:
* 0 Dis 1 Blk 2 Lrn 3 Fwd), an approximated role, and the per-port config
* (enabled, edge admin/auto/oper, cost/1000, prio, guard, filter, tripped). */
__xdata uint8_t stp_we_root;
__xdata uint8_t pi_i, pi_j; /* shared loop iterators (DSEG relief) */
void send_stp(void)
{
@@ -544,33 +544,61 @@ void send_stp(void)
slen += strtox(outbuf + slen, "{\"on\":");
bool_to_html(stpEnabled);
slen += strtox(outbuf + slen, ",\"rstp\":");
bool_to_html(stp_rstp);
slen += strtox(outbuf + slen, ",\"prio\":");
itoa_html(stp_prio >> 4);
slen += strtox(outbuf + slen, ",\"hello\":");
itoa_html(stp_hello_s);
slen += strtox(outbuf + slen, ",\"maxage\":");
itoa_html(stp_maxage_s);
slen += strtox(outbuf + slen, ",\"fwd\":");
itoa_html(stp_fwddelay_s);
slen += strtox(outbuf + slen, ",\"txhold\":");
itoa_html(stp_txhold);
slen += strtox(outbuf + slen, ",\"rootPrio\":\"");
byte_to_html(root_bridge.prio);
byte_to_html(root_bridge.ext);
slen += strtox(outbuf + slen, "\",\"rootMac\":\"");
for (uint8_t j = 0; j < 6; j++)
byte_to_html(root_bridge.mac[j]);
for (pi_j = 0; pi_j < 6; pi_j++)
byte_to_html(root_bridge.mac[pi_j]);
slen += strtox(outbuf + slen, "\",\"cost\":\"");
byte_to_html(root_bridge_cost >> 24);
byte_to_html(root_bridge_cost >> 16);
byte_to_html(root_bridge_cost >> 8);
byte_to_html(root_bridge_cost);
/* are we the elected root? (our MAC == root MAC) */
stp_we_root = 1;
for (uint8_t j = 0; j < 6; j++) {
if (root_bridge.mac[j] != uip_ethaddr.addr[j])
stp_we_root = 0;
}
stp_we_root = (stp_root_port == 0xff) ? 1 : 0;
slen += strtox(outbuf + slen, "\",\"weRoot\":");
bool_to_html(stp_we_root);
slen += strtox(outbuf + slen, ",\"ports\":[");
slen += strtox(outbuf + slen, ",\"rootPort\":");
itoa_html(stp_root_port == 0xff ? 0 : machine.log_to_phys_port[stp_root_port]);
slen += strtox(outbuf + slen, ",\"tc\":\"");
byte_to_html(stp_tc_count >> 8);
byte_to_html(stp_tc_count);
slen += strtox(outbuf + slen, "\",\"ports\":[");
reg_read_m(RTL837X_MSTP_STATES);
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
for (pi_i = machine.min_port; pi_i <= machine.max_port; pi_i++) {
slen += strtox(outbuf + slen, "{\"p\":");
itoa_html(machine.log_to_phys_port[i]);
itoa_html(machine.log_to_phys_port[pi_i]);
slen += strtox(outbuf + slen, ",\"st\":");
/* 2-bit field per logical port, packed from byte 3 up (cf. stp_setup) */
itoa_html((sfr_data[3 - (i >> 2)] >> ((i << 1) & 0x7)) & 0x3);
stp_we_root = (sfr_data[3 - (pi_i >> 2)] >> ((pi_i << 1) & 0x7)) & 0x3;
itoa_html(stp_we_root);
/* role (approximated): 0 none/disabled, 1 root, 2 designated, 3 alternate(blocked) */
slen += strtox(outbuf + slen, ",\"role\":");
if (!(stp_pflags[pi_i] & STP_PF_ENABLED) || (stp_pflags[pi_i] & STP_PF_TRIPPED))
itoa_html(0);
else if (pi_i == stp_root_port)
itoa_html(1);
else if (stp_we_root == 3)
itoa_html(2);
else
itoa_html(3);
slen += strtox(outbuf + slen, ",\"f\":");
itoa_html(stp_pflags[pi_i]);
slen += strtox(outbuf + slen, ",\"cost\":");
itoa_html(stp_pcost[pi_i] / 1000);
slen += strtox(outbuf + slen, ",\"prio\":");
itoa_html(stp_pprio[pi_i]);
slen += strtox(outbuf + slen, "},");
}
slen -= 1; // remove comma