mirror of
https://github.com/logicog/RTLPlayground.git
synced 2026-08-30 14:52:51 +08:00
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:
+1
-9
@@ -1525,15 +1525,7 @@ void cmd_parser(void) __banked
|
|||||||
else
|
else
|
||||||
igmp_setup(); // Reverts to default with IP-MC being flooded
|
igmp_setup(); // Reverts to default with IP-MC being flooded
|
||||||
} else if (cmd_compare(0, "stp")) {
|
} else if (cmd_compare(0, "stp")) {
|
||||||
if (cmd_compare(1, "on")) {
|
stp_parse();
|
||||||
print_string("STP enabled\n");
|
|
||||||
stpEnabled = 1;
|
|
||||||
stp_setup();
|
|
||||||
} else {
|
|
||||||
print_string("STP disabled\n");
|
|
||||||
stp_off();
|
|
||||||
stpEnabled = 0;
|
|
||||||
}
|
|
||||||
} else if (cmd_compare(0, "pvid") && cmd_words_len == 3) {
|
} else if (cmd_compare(0, "pvid") && cmd_words_len == 3) {
|
||||||
__xdata uint16_t pvid;
|
__xdata uint16_t pvid;
|
||||||
uint8_t port;
|
uint8_t port;
|
||||||
|
|||||||
+10
-1
@@ -22,6 +22,14 @@ const conf_cmds = [
|
|||||||
/^laghash\s+\d(\s+\w+)+$/,
|
/^laghash\s+\d(\s+\w+)+$/,
|
||||||
/^isolate\s+\d{1,2}(\s+(off|\d{1,2}))+$/,
|
/^isolate\s+\d{1,2}(\s+(off|\d{1,2}))+$/,
|
||||||
/^stp\s+(on|off)$/,
|
/^stp\s+(on|off)$/,
|
||||||
|
/^stp\s+(prio|hello|maxage|fwd|txhold)\s+\d{1,2}$/,
|
||||||
|
/^stp\s+version\s+(rstp|stp)$/,
|
||||||
|
/^stp\s+port\s+\d{1,2}\s+(on|off)$/,
|
||||||
|
/^stp\s+port\s+\d{1,2}\s+edge\s+(on|off|auto)$/,
|
||||||
|
/^stp\s+port\s+\d{1,2}\s+cost\s+\d{1,3}$/,
|
||||||
|
/^stp\s+port\s+\d{1,2}\s+prio\s+\d{1,3}$/,
|
||||||
|
/^stp\s+port\s+\d{1,2}\s+guard\s+(none|bpdu|root)$/,
|
||||||
|
/^stp\s+port\s+\d{1,2}\s+filter\s+(on|off)$/,
|
||||||
/^igmp\s+(on|off)$/,
|
/^igmp\s+(on|off)$/,
|
||||||
/^mtu\s+\d{1,2}\s+\d+$/,
|
/^mtu\s+\d{1,2}\s+\d+$/,
|
||||||
/^bw\s+(in|out)\s+\d{1,2}\s+\S+$/,
|
/^bw\s+(in|out)\s+\d{1,2}\s+\S+$/,
|
||||||
@@ -45,7 +53,8 @@ const conf_overwrite = [
|
|||||||
/^lag\s+\d+\b/,
|
/^lag\s+\d+\b/,
|
||||||
/^laghash\b/,
|
/^laghash\b/,
|
||||||
/^isolate\s+\d{1,2}\b/,
|
/^isolate\s+\d{1,2}\b/,
|
||||||
/^stp\b/,
|
/^stp\s+(prio|hello|maxage|fwd|txhold|version)\b/,
|
||||||
|
/^stp\s+port\s+\d{1,2}\s+(edge|cost|prio|guard|filter)\b/,
|
||||||
/^igmp\b/,
|
/^igmp\b/,
|
||||||
/^mtu\s+\d{1,2}\b/,
|
/^mtu\s+\d{1,2}\b/,
|
||||||
/^bw\s+(in|out)\s+\d{1,2}\b/,
|
/^bw\s+(in|out)\s+\d{1,2}\b/,
|
||||||
|
|||||||
+21
-1
@@ -13,7 +13,27 @@
|
|||||||
<h2>Mode: <select id="stpMode"><option value="off">Disabled</option><option value="on">Enabled</option></select>
|
<h2>Mode: <select id="stpMode"><option value="off">Disabled</option><option value="on">Enabled</option></select>
|
||||||
<input style="width:15%;margin-left: 3em;" class="action" id="stp_sub" onclick="stpSub();" type="button" value="Apply"></h2>
|
<input style="width:15%;margin-left: 3em;" class="action" id="stp_sub" onclick="stpSub();" type="button" value="Apply"></h2>
|
||||||
<div id="stpStat" style="font-family:monospace;"></div>
|
<div id="stpStat" style="font-family:monospace;"></div>
|
||||||
<div id="stpPorts" style="font-family:monospace;white-space:pre;"></div>
|
<h2>Bridge settings</h2>
|
||||||
|
<table id="stpBridge">
|
||||||
|
<tr>
|
||||||
|
<th>Priority</th><th>Version</th><th>Hello [s]</th><th>Max age [s]</th><th>Fwd delay [s]</th><th>Tx hold</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><select id="bPrio"></select></td>
|
||||||
|
<td><select id="bVer"><option value="rstp">RSTP</option><option value="stp">STP compat</option></select></td>
|
||||||
|
<td><input id="bHello" type="number" min="1" max="10" style="width:4em"></td>
|
||||||
|
<td><input id="bMaxage" type="number" min="6" max="40" style="width:4em"></td>
|
||||||
|
<td><input id="bFwd" type="number" min="4" max="30" style="width:4em"></td>
|
||||||
|
<td><input id="bTxhold" type="number" min="1" max="10" style="width:4em"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<p style="font-size:small">Changes apply immediately. Edge ports skip the listen period; guard/filter act on received BPDUs.</p>
|
||||||
|
<h2>Ports</h2>
|
||||||
|
<table id="stpPortsTbl">
|
||||||
|
<tr>
|
||||||
|
<th>Port</th><th>State</th><th>Role</th><th>STP</th><th>Edge</th><th>Cost [k]</th><th>Priority</th><th>Guard</th><th>Filter</th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
<script src="/stp.js"></script>
|
<script src="/stp.js"></script>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+120
-23
@@ -1,12 +1,78 @@
|
|||||||
/* ---- Spanning Tree (RSTP) section ---- */
|
/* Spanning Tree page: full RSTP configuration + live status.
|
||||||
|
*
|
||||||
|
* Every control applies IMMEDIATELY on change (POST /cmd "stp ...") - there is
|
||||||
|
* no per-row Apply. The refresh (2 s) repopulates controls from /stp.json;
|
||||||
|
* a global dirty flag suppresses that between a change and its confirmation
|
||||||
|
* so the refresh never reverts an edit in flight (same lesson as the LAG page).
|
||||||
|
*/
|
||||||
|
|
||||||
// STP port states as encoded in the ASIC's MSTP register (2 bits per port)
|
// STP port states as encoded in the ASIC's MSTP register (2 bits per port)
|
||||||
const STP_STATES = ["Disabled", "Blocking", "Learning", "Forwarding"];
|
const STP_STATES = ["Disabled", "Blocking", "Learning", "Forwarding"];
|
||||||
|
const STP_ROLES = ["-", "Root", "Designated", "Alternate"];
|
||||||
|
|
||||||
|
// stp_pflags bits (keep in sync with rtl837x_stp.h)
|
||||||
|
const PF_ENABLED = 1, PF_ADMEDGE = 2, PF_AUTOEDGE = 4, PF_BPDUGUARD = 8,
|
||||||
|
PF_ROOTGUARD = 16, PF_FILTER = 32, PF_OPEREDGE = 64, PF_TRIPPED = 128;
|
||||||
|
|
||||||
// "user is editing" flag: while set, the periodic refresh must not overwrite
|
|
||||||
// the mode dropdown (same pattern as the LAG page - without it the 2 s refresh
|
|
||||||
// silently reverts the user's choice before Apply).
|
|
||||||
var stpDirty = false;
|
var stpDirty = false;
|
||||||
|
var stpRows = 0; // ports table built?
|
||||||
|
|
||||||
|
async function stpCmd(cmd) {
|
||||||
|
stpDirty = true;
|
||||||
|
try {
|
||||||
|
await fetch('/cmd', { method: 'POST', body: cmd });
|
||||||
|
} catch(err) {
|
||||||
|
console.error(`Error: ${err}`);
|
||||||
|
}
|
||||||
|
stpDirty = false;
|
||||||
|
fetchStp();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sel(id, opts, onch) {
|
||||||
|
const s = document.createElement("select");
|
||||||
|
s.id = id;
|
||||||
|
for (const [v, label] of opts) {
|
||||||
|
const o = document.createElement("option");
|
||||||
|
o.value = v; o.textContent = label;
|
||||||
|
s.appendChild(o);
|
||||||
|
}
|
||||||
|
s.addEventListener("change", onch);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
function num(id, min, max, onch) {
|
||||||
|
const n = document.createElement("input");
|
||||||
|
n.type = "number"; n.id = id; n.min = min; n.max = max; n.style.width = "4em";
|
||||||
|
n.addEventListener("change", onch);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPortsTable(ports) {
|
||||||
|
const tbl = document.getElementById("stpPortsTbl");
|
||||||
|
for (const p of ports) {
|
||||||
|
const tr = tbl.insertRow();
|
||||||
|
tr.insertCell().textContent = p.p; // Port
|
||||||
|
tr.insertCell().id = "st_" + p.p; // State
|
||||||
|
tr.insertCell().id = "role_" + p.p; // Role
|
||||||
|
tr.insertCell().appendChild(sel("en_" + p.p,
|
||||||
|
[["on","on"],["off","off"]],
|
||||||
|
e => stpCmd("stp port " + p.p + " " + e.target.value)));
|
||||||
|
tr.insertCell().appendChild(sel("edge_" + p.p,
|
||||||
|
[["auto","auto"],["on","edge"],["off","off"]],
|
||||||
|
e => stpCmd("stp port " + p.p + " edge " + e.target.value)));
|
||||||
|
tr.insertCell().appendChild(num("cost_" + p.p, 0, 255,
|
||||||
|
e => stpCmd("stp port " + p.p + " cost " + e.target.value)));
|
||||||
|
tr.insertCell().appendChild(num("prio_" + p.p, 0, 240,
|
||||||
|
e => stpCmd("stp port " + p.p + " prio " + e.target.value)));
|
||||||
|
tr.insertCell().appendChild(sel("guard_" + p.p,
|
||||||
|
[["none","none"],["bpdu","BPDU"],["root","Root"]],
|
||||||
|
e => stpCmd("stp port " + p.p + " guard " + e.target.value)));
|
||||||
|
tr.insertCell().appendChild(sel("filt_" + p.p,
|
||||||
|
[["off","off"],["on","on"]],
|
||||||
|
e => stpCmd("stp port " + p.p + " filter " + e.target.value)));
|
||||||
|
}
|
||||||
|
stpRows = ports.length;
|
||||||
|
}
|
||||||
|
|
||||||
function fetchStp() {
|
function fetchStp() {
|
||||||
var xhttp = new XMLHttpRequest();
|
var xhttp = new XMLHttpRequest();
|
||||||
@@ -15,21 +81,42 @@ function fetchStp() {
|
|||||||
const s = JSON.parse(xhttp.responseText);
|
const s = JSON.parse(xhttp.responseText);
|
||||||
// textContent throughout: rootMac comes from received BPDUs
|
// textContent throughout: rootMac comes from received BPDUs
|
||||||
// (remote-controlled), never render it as HTML
|
// (remote-controlled), never render it as HTML
|
||||||
if (!stpDirty)
|
if (!stpRows)
|
||||||
document.getElementById("stpMode").value = s.on ? "on" : "off";
|
buildPortsTable(s.ports);
|
||||||
document.getElementById("stpStat").textContent = s.on
|
document.getElementById("stpStat").textContent = s.on
|
||||||
? (s.weRoot
|
? (s.weRoot
|
||||||
? "This switch is the root bridge (priority 0x" + s.rootPrio + ")"
|
? "This switch is the root bridge (priority 0x" + s.rootPrio + ") — topology changes: " + parseInt(s.tc, 16)
|
||||||
: "Root bridge: 0x" + s.rootPrio + " / " + s.rootMac
|
: "Root bridge: 0x" + s.rootPrio + " / " + s.rootMac
|
||||||
+ " \u2014 path cost: 0x" + s.cost)
|
+ " via port " + s.rootPort + " — path cost: 0x" + s.cost
|
||||||
|
+ " — topology changes: " + parseInt(s.tc, 16))
|
||||||
: "";
|
: "";
|
||||||
let t = "";
|
// live status columns always refresh
|
||||||
if (s.on) {
|
for (const p of s.ports) {
|
||||||
t = "port state\n";
|
const trip = (p.f & PF_TRIPPED) ? " (guard!)" : "";
|
||||||
for (const p of s.ports)
|
document.getElementById("st_" + p.p).textContent =
|
||||||
t += String(p.p).padEnd(6) + STP_STATES[p.st] + "\n";
|
s.on ? STP_STATES[p.st] + trip : "-";
|
||||||
|
document.getElementById("role_" + p.p).textContent =
|
||||||
|
s.on ? STP_ROLES[p.role] + ((p.f & PF_OPEREDGE) ? " edge" : "") : "-";
|
||||||
|
}
|
||||||
|
if (stpDirty) // an edit is in flight - do not revert controls
|
||||||
|
return;
|
||||||
|
document.getElementById("stpMode").value = s.on ? "on" : "off";
|
||||||
|
document.getElementById("bPrio").value = s.prio;
|
||||||
|
document.getElementById("bVer").value = s.rstp ? "rstp" : "stp";
|
||||||
|
document.getElementById("bHello").value = s.hello;
|
||||||
|
document.getElementById("bMaxage").value = s.maxage;
|
||||||
|
document.getElementById("bFwd").value = s.fwd;
|
||||||
|
document.getElementById("bTxhold").value = s.txhold;
|
||||||
|
for (const p of s.ports) {
|
||||||
|
document.getElementById("en_" + p.p).value = (p.f & PF_ENABLED) ? "on" : "off";
|
||||||
|
document.getElementById("edge_" + p.p).value =
|
||||||
|
(p.f & PF_ADMEDGE) ? "on" : ((p.f & PF_AUTOEDGE) ? "auto" : "off");
|
||||||
|
document.getElementById("cost_" + p.p).value = p.cost;
|
||||||
|
document.getElementById("prio_" + p.p).value = p.prio;
|
||||||
|
document.getElementById("guard_" + p.p).value =
|
||||||
|
(p.f & PF_BPDUGUARD) ? "bpdu" : ((p.f & PF_ROOTGUARD) ? "root" : "none");
|
||||||
|
document.getElementById("filt_" + p.p).value = (p.f & PF_FILTER) ? "on" : "off";
|
||||||
}
|
}
|
||||||
document.getElementById("stpPorts").textContent = t;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
xhttp.open("GET", `/stp.json`, true);
|
xhttp.open("GET", `/stp.json`, true);
|
||||||
@@ -38,21 +125,31 @@ function fetchStp() {
|
|||||||
|
|
||||||
async function stpSub() {
|
async function stpSub() {
|
||||||
const on = document.getElementById("stpMode").value === "on";
|
const on = document.getElementById("stpMode").value === "on";
|
||||||
try {
|
await stpCmd(on ? "stp on" : "stp off");
|
||||||
await fetch('/cmd', { method: 'POST', body: on ? "stp on" : "stp off" });
|
|
||||||
} catch(err) {
|
|
||||||
console.error(`Error: ${err}`);
|
|
||||||
}
|
|
||||||
stpDirty = false; // editing done - let the refresh show the truth
|
|
||||||
fetchStp();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("load", function() {
|
window.addEventListener("load", function() {
|
||||||
|
// bridge priority: 0-15 (x4096)
|
||||||
|
const bp = document.getElementById("bPrio");
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
const o = document.createElement("option");
|
||||||
|
o.value = i; o.textContent = (i * 4096) + (i === 8 ? " (default)" : "");
|
||||||
|
bp.appendChild(o);
|
||||||
|
}
|
||||||
|
bp.addEventListener("change", e => stpCmd("stp prio " + e.target.value));
|
||||||
|
document.getElementById("bVer")
|
||||||
|
.addEventListener("change", e => stpCmd("stp version " + e.target.value));
|
||||||
|
document.getElementById("bHello")
|
||||||
|
.addEventListener("change", e => stpCmd("stp hello " + e.target.value));
|
||||||
|
document.getElementById("bMaxage")
|
||||||
|
.addEventListener("change", e => stpCmd("stp maxage " + e.target.value));
|
||||||
|
document.getElementById("bFwd")
|
||||||
|
.addEventListener("change", e => stpCmd("stp fwd " + e.target.value));
|
||||||
|
document.getElementById("bTxhold")
|
||||||
|
.addEventListener("change", e => stpCmd("stp txhold " + e.target.value));
|
||||||
document.getElementById("stpMode")
|
document.getElementById("stpMode")
|
||||||
.addEventListener("change", () => { stpDirty = true; });
|
.addEventListener("change", () => { stpDirty = true; });
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("load", function() {
|
|
||||||
update( () => {
|
update( () => {
|
||||||
fetchStp();
|
fetchStp();
|
||||||
const interval = setInterval(update, 2000);
|
const interval = setInterval(update, 2000);
|
||||||
|
|||||||
+3
-3
@@ -41,8 +41,8 @@ __xdata uint32_t cont_addr;
|
|||||||
|
|
||||||
// HTTP header properties
|
// HTTP header properties
|
||||||
__xdata uint8_t boundary[72];
|
__xdata uint8_t boundary[72];
|
||||||
__xdata uint8_t *content_type = 0;
|
__xdata uint8_t * __xdata content_type = 0;
|
||||||
__xdata uint8_t *session = 0;
|
__xdata uint8_t * __xdata session = 0;
|
||||||
|
|
||||||
// Global variables holding POST state
|
// Global variables holding POST state
|
||||||
__xdata uint16_t bindex; // Current index into the boundary
|
__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 char session_id[SESSION_ID_LENGTH + 1];
|
||||||
__xdata uint8_t authenticated;
|
__xdata uint8_t authenticated;
|
||||||
__xdata uint32_t now;
|
__xdata uint32_t now;
|
||||||
__xdata uint8_t *timeptr;
|
__xdata uint8_t * __xdata timeptr;
|
||||||
__xdata uint32_t last_session_use;
|
__xdata uint32_t last_session_use;
|
||||||
|
|
||||||
#define TSTATE_NONE 0
|
#define TSTATE_NONE 0
|
||||||
|
|||||||
+48
-20
@@ -528,14 +528,14 @@ void send_lag(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* STP status for the L2 page ("/stp.json"): enable state, the elected root
|
/* STP status + configuration for the Spanning Tree page ("/stp.json").
|
||||||
* bridge (priority+MAC) and our path cost to it, whether we are the root, and
|
* Bridge config (prio index 0-15, hello/maxage/fwd seconds, rstp flag, tx
|
||||||
* the live per-port STP state read from the ASIC's MSTP register (2 bits per
|
* hold), elected root (priority byte + MAC), our path cost, root port, TC
|
||||||
* port: 0 disable, 1 blocking, 2 learning, 3 forwarding - same encoding
|
* counter, and per port: physical number, live ASIC state (2-bit MSTP field:
|
||||||
* stp_setup() writes). Ports are reported by their physical number. */
|
* 0 Dis 1 Blk 2 Lrn 3 Fwd), an approximated role, and the per-port config
|
||||||
/* Scratch for send_stp(): a plain local would land in the near-full 8051
|
* (enabled, edge admin/auto/oper, cost/1000, prio, guard, filter, tripped). */
|
||||||
* internal-RAM overlay (OSEG). */
|
|
||||||
__xdata uint8_t stp_we_root;
|
__xdata uint8_t stp_we_root;
|
||||||
|
__xdata uint8_t pi_i, pi_j; /* shared loop iterators (DSEG relief) */
|
||||||
|
|
||||||
void send_stp(void)
|
void send_stp(void)
|
||||||
{
|
{
|
||||||
@@ -544,33 +544,61 @@ void send_stp(void)
|
|||||||
|
|
||||||
slen += strtox(outbuf + slen, "{\"on\":");
|
slen += strtox(outbuf + slen, "{\"on\":");
|
||||||
bool_to_html(stpEnabled);
|
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\":\"");
|
slen += strtox(outbuf + slen, ",\"rootPrio\":\"");
|
||||||
byte_to_html(root_bridge.prio);
|
byte_to_html(root_bridge.prio);
|
||||||
byte_to_html(root_bridge.ext);
|
byte_to_html(root_bridge.ext);
|
||||||
slen += strtox(outbuf + slen, "\",\"rootMac\":\"");
|
slen += strtox(outbuf + slen, "\",\"rootMac\":\"");
|
||||||
for (uint8_t j = 0; j < 6; j++)
|
for (pi_j = 0; pi_j < 6; pi_j++)
|
||||||
byte_to_html(root_bridge.mac[j]);
|
byte_to_html(root_bridge.mac[pi_j]);
|
||||||
slen += strtox(outbuf + slen, "\",\"cost\":\"");
|
slen += strtox(outbuf + slen, "\",\"cost\":\"");
|
||||||
byte_to_html(root_bridge_cost >> 24);
|
byte_to_html(root_bridge_cost >> 24);
|
||||||
byte_to_html(root_bridge_cost >> 16);
|
byte_to_html(root_bridge_cost >> 16);
|
||||||
byte_to_html(root_bridge_cost >> 8);
|
byte_to_html(root_bridge_cost >> 8);
|
||||||
byte_to_html(root_bridge_cost);
|
byte_to_html(root_bridge_cost);
|
||||||
/* are we the elected root? (our MAC == root MAC) */
|
stp_we_root = (stp_root_port == 0xff) ? 1 : 0;
|
||||||
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;
|
|
||||||
}
|
|
||||||
slen += strtox(outbuf + slen, "\",\"weRoot\":");
|
slen += strtox(outbuf + slen, "\",\"weRoot\":");
|
||||||
bool_to_html(stp_we_root);
|
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);
|
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\":");
|
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\":");
|
slen += strtox(outbuf + slen, ",\"st\":");
|
||||||
/* 2-bit field per logical port, packed from byte 3 up (cf. stp_setup) */
|
stp_we_root = (sfr_data[3 - (pi_i >> 2)] >> ((pi_i << 1) & 0x7)) & 0x3;
|
||||||
itoa_html((sfr_data[3 - (i >> 2)] >> ((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 += strtox(outbuf + slen, "},");
|
||||||
}
|
}
|
||||||
slen -= 1; // remove comma
|
slen -= 1; // remove comma
|
||||||
|
|||||||
+408
-94
@@ -1,6 +1,17 @@
|
|||||||
/*
|
/*
|
||||||
* This is a driver implementation for the Spanning Tree Protocol features for the RTL837x platform
|
* This is a driver implementation for the Spanning Tree Protocol features for the RTL837x platform
|
||||||
* This code is in the Public Domain
|
* This code is in the Public Domain
|
||||||
|
*
|
||||||
|
* Configurable per 802.1D-2004/802.1w: bridge priority, hello time, max age,
|
||||||
|
* forward delay, force-version (RSTP/STP), tx hold count; per port: enable,
|
||||||
|
* admin/auto edge, path cost, port priority, BPDU guard, root guard, BPDU
|
||||||
|
* filter. CLI: "stp ..." (see stp_parse), status: /stp.json (send_stp).
|
||||||
|
*
|
||||||
|
* The engine itself stays deliberately simple (no proposal/agreement
|
||||||
|
* handshake, no full port-role machine): we elect a root from received BPDUs,
|
||||||
|
* promote ports to forwarding after the listen period (or immediately for
|
||||||
|
* edge ports), age the root out via max age, and block ports on which we see
|
||||||
|
* our own BPDU (loop!) or - with root guard - a better root.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// #define REGDBG
|
// #define REGDBG
|
||||||
@@ -34,14 +45,48 @@ extern __xdata struct uip_eth_addr uip_ethaddr;
|
|||||||
|
|
||||||
extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2];
|
extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2];
|
||||||
|
|
||||||
/* struct bridge now lives in rtl837x_stp.h (shared with the web UI). */
|
/* CLI tokenizer state + helpers (owned by cmd_parser.c, HOME bank) */
|
||||||
|
extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE];
|
||||||
|
extern __xdata uint8_t cmd_words_len;
|
||||||
|
extern __xdata uint8_t cmd_words_b[15];
|
||||||
|
uint8_t cmd_compare(uint8_t start, __code uint8_t * cmd);
|
||||||
|
uint8_t atoi_byte(__xdata uint8_t *out, uint8_t idx);
|
||||||
|
|
||||||
|
/* ---- Configuration ---- */
|
||||||
|
__xdata uint8_t stp_prio; /* bridge priority high byte (0x80 = 32768) */
|
||||||
|
__xdata uint8_t stp_hello_s;
|
||||||
|
__xdata uint8_t stp_maxage_s;
|
||||||
|
__xdata uint8_t stp_fwddelay_s;
|
||||||
|
__xdata uint8_t stp_rstp;
|
||||||
|
__xdata uint8_t stp_txhold;
|
||||||
|
|
||||||
|
__xdata uint8_t stp_pflags[10];
|
||||||
|
__xdata uint32_t stp_pcost[10];
|
||||||
|
__xdata uint8_t stp_pprio[10];
|
||||||
|
|
||||||
|
/* ---- Status / runtime ---- */
|
||||||
__xdata struct bridge root_bridge;
|
__xdata struct bridge root_bridge;
|
||||||
__xdata uint32_t root_bridge_cost;
|
__xdata uint32_t root_bridge_cost; /* our cost to the root (rx cost + root port cost) */
|
||||||
|
__xdata uint8_t stp_root_port; /* 0xff = we are the root */
|
||||||
|
__xdata uint16_t stp_tc_count;
|
||||||
|
|
||||||
__xdata uint8_t port_types[10];
|
__xdata uint16_t port_timers[10]; /* listen-period countdown (0 = not listening) */
|
||||||
__xdata uint16_t port_timers[10];
|
__xdata uint16_t port_hello[10]; /* hello TX countdown */
|
||||||
__xdata uint16_t port_hello[10];
|
__xdata uint16_t stp_bpdu_age[10]; /* ticks since last BPDU seen on port (saturating) */
|
||||||
|
__xdata uint8_t stp_tx_budget[10]; /* tx hold: BPDUs left in the current second */
|
||||||
|
__xdata uint16_t stp_sec_tick; /* 1 s window for the tx budget */
|
||||||
|
|
||||||
|
/* Scratch (8051: locals would overflow the internal-RAM overlay area) */
|
||||||
|
__xdata uint8_t stp_scratch;
|
||||||
|
__xdata uint8_t stp_i; /* shared loop iterator (DSEG relief) */
|
||||||
|
__xdata uint32_t stp_cost_scratch;
|
||||||
|
|
||||||
|
/* stp_timers() runs at ~64 Hz (main loop ~256 Hz / (STP_TICK_DIVIDER+1)) */
|
||||||
|
#define STP_HZ 64
|
||||||
|
#define STP_EDGE_DELAY (3 * STP_HZ) /* auto-edge: forward after 3 s without BPDU */
|
||||||
|
|
||||||
|
#define AUTO_COST 20000UL /* path cost used when stp_pcost == 0 (1G default) */
|
||||||
|
#define PCOST(i) (stp_pcost[i] ? stp_pcost[i] : AUTO_COST)
|
||||||
|
|
||||||
struct stp_pkt {
|
struct stp_pkt {
|
||||||
uint8_t stp_addr[6];
|
uint8_t stp_addr[6];
|
||||||
@@ -93,11 +138,7 @@ struct stp_pkt_in {
|
|||||||
#define STP_O ((__xdata struct stp_pkt *)&uip_buf[RTL_FRAME_DESC_SIZE])
|
#define STP_O ((__xdata struct stp_pkt *)&uip_buf[RTL_FRAME_DESC_SIZE])
|
||||||
#define STP_I ((__xdata struct stp_pkt_in *)&uip_buf[0])
|
#define STP_I ((__xdata struct stp_pkt_in *)&uip_buf[0])
|
||||||
|
|
||||||
#define FLAG_PROPOSAL 0x02
|
signed char cmpMAC(__xdata uint8_t *m1, __xdata uint8_t *m2) __reentrant
|
||||||
#define P_DESIGNATED ((STP_I->flags & 0x0c) == 0x0c)
|
|
||||||
#define P_PROPOSAL (STP_I->flags & FLAG_PROPOSAL)
|
|
||||||
|
|
||||||
signed char cmpMAC(__xdata uint8_t *m1, __xdata uint8_t *m2)
|
|
||||||
{
|
{
|
||||||
for (uint8_t i = 0; i < 6; i++) {
|
for (uint8_t i = 0; i < 6; i++) {
|
||||||
if (m1[i] == m2[i])
|
if (m1[i] == m2[i])
|
||||||
@@ -110,50 +151,37 @@ signed char cmpMAC(__xdata uint8_t *m1, __xdata uint8_t *m2)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void stp_in(void) __banked
|
/* Write one port's 2-bit state into the ASIC's MSTP register.
|
||||||
|
* 00 disable, 01 blocking, 10 learning, 11 forwarding. */
|
||||||
|
static void stp_state_set(uint8_t port, uint8_t state) __reentrant
|
||||||
{
|
{
|
||||||
// By default we do not send anything out
|
reg_read_m(RTL837X_MSTP_STATES);
|
||||||
uip_len = 0;
|
stp_scratch = 3 - (port >> 2);
|
||||||
// MSTPSTP_I_STATES 0x5310
|
sfr_data[stp_scratch] &= ~(uint8_t)(0b11 << ((port << 1) & 0x7));
|
||||||
// reg_read_m(RTL837X_MSTP_STATES);
|
sfr_data[stp_scratch] |= (uint8_t)(state << ((port << 1) & 0x7));
|
||||||
|
reg_write_m(RTL837X_MSTP_STATES);
|
||||||
print_string("Check BPDU... \n");
|
|
||||||
for (uint8_t i = 0; i < 80; i++) {
|
|
||||||
print_byte(uip_buf[i]);
|
|
||||||
write_char(' ');
|
|
||||||
}
|
|
||||||
write_char('\n');
|
|
||||||
print_byte(STP_I->dsap);
|
|
||||||
print_byte(STP_I->ssap);
|
|
||||||
print_byte(STP_I->ctrl);
|
|
||||||
|
|
||||||
write_char('\n');
|
|
||||||
// Make sure this is the type of RSTP packet we are interested in:
|
|
||||||
if (!(STP_I->dsap == 0x42 && STP_I->ssap == 0x42 && STP_I->ctrl == 0x03))
|
|
||||||
return;
|
|
||||||
print_string("Checking RSTP\n");
|
|
||||||
if (STP_I->proto)
|
|
||||||
return;
|
|
||||||
// write_char('A'); print_byte(STP_I->version); write_char('\n');
|
|
||||||
if (STP_I->version != 2)
|
|
||||||
return;
|
|
||||||
// write_char('B'); print_byte(STP_I->bpdu_type); write_char('\n');
|
|
||||||
if (STP_I->bpdu_type != 2)
|
|
||||||
return;
|
|
||||||
// write_char('\n');
|
|
||||||
// print_string("Flags: "); print_byte(STP_I->flags); write_char('\n');
|
|
||||||
print_string("Check new Root\n");
|
|
||||||
if (STP_I->root.prio < root_bridge.prio
|
|
||||||
|| ((STP_I->root.prio == root_bridge.prio) && cmpMAC(STP_I->root.mac, root_bridge.mac) < 0)) {
|
|
||||||
print_string("Updating Root bridge\n");
|
|
||||||
root_bridge.prio = STP_I->root.prio;
|
|
||||||
memcpy(root_bridge.mac, STP_I->root.mac, 6);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void stp_cnf_send(uint8_t port)
|
/* Take the bridge back as root of its own tree (initial state / root aged out) */
|
||||||
|
static void stp_claim_root(void)
|
||||||
{
|
{
|
||||||
|
root_bridge.prio = stp_prio;
|
||||||
|
root_bridge.ext = 0x00;
|
||||||
|
memcpy(root_bridge.mac, uip_ethaddr.addr, 6);
|
||||||
|
root_bridge_cost = 0;
|
||||||
|
stp_root_port = 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void stp_cnf_send(uint8_t port) __reentrant
|
||||||
|
{
|
||||||
|
if (!(stp_pflags[port] & STP_PF_ENABLED) || (stp_pflags[port] & (STP_PF_FILTER | STP_PF_TRIPPED)))
|
||||||
|
return;
|
||||||
|
if (!stp_tx_budget[port]) /* tx hold count exhausted for this second */
|
||||||
|
return;
|
||||||
|
stp_tx_budget[port]--;
|
||||||
|
|
||||||
STP_O->stp_addr[0] = 0x01; STP_O->stp_addr[1] = 0x80; STP_O->stp_addr[2] = 0xc2;
|
STP_O->stp_addr[0] = 0x01; STP_O->stp_addr[1] = 0x80; STP_O->stp_addr[2] = 0xc2;
|
||||||
STP_O->stp_addr[3] = STP_O->stp_addr[4] = STP_O->stp_addr[5] = 0x00;
|
STP_O->stp_addr[3] = STP_O->stp_addr[4] = STP_O->stp_addr[5] = 0x00;
|
||||||
|
|
||||||
@@ -168,61 +196,211 @@ void stp_cnf_send(uint8_t port)
|
|||||||
STP_O->ssap = 0x42;
|
STP_O->ssap = 0x42;
|
||||||
STP_O->ctrl = 0x03;
|
STP_O->ctrl = 0x03;
|
||||||
STP_O->proto = 0x0000;
|
STP_O->proto = 0x0000;
|
||||||
STP_O->version = 0x02; // RSTP
|
if (stp_rstp) {
|
||||||
STP_O->bpdu_type = 0x00; // Config
|
STP_O->version = 0x02; /* RSTP */
|
||||||
STP_O->flags = 0x81;
|
STP_O->bpdu_type = 0x02; /* Rapid Spanning Tree BPDU */
|
||||||
|
/* flags: role designated (0b11 << 2) + learning + forwarding */
|
||||||
|
STP_O->flags = 0x3c;
|
||||||
|
} else {
|
||||||
|
STP_O->version = 0x00; /* legacy STP */
|
||||||
|
STP_O->bpdu_type = 0x00; /* Config BPDU */
|
||||||
|
STP_O->flags = 0x00;
|
||||||
|
}
|
||||||
|
|
||||||
memcpy(STP_O->src_addr, uip_ethaddr.addr, 6);
|
memcpy(STP_O->src_addr, uip_ethaddr.addr, 6);
|
||||||
memcpy(STP_O->root.mac, root_bridge.mac, 6);
|
memcpy(STP_O->root.mac, root_bridge.mac, 6);
|
||||||
memcpy(STP_O->bridge.mac, uip_ethaddr.addr, 6);
|
memcpy(STP_O->bridge.mac, uip_ethaddr.addr, 6);
|
||||||
|
|
||||||
STP_O->root.prio = root_bridge.prio;
|
STP_O->root.prio = root_bridge.prio;
|
||||||
STP_O->root.ext = 0x00;
|
STP_O->root.ext = root_bridge.ext;
|
||||||
STP_O->root_path_cost = 0x00000000;
|
/* Our root path cost, big-endian (0 while we are the root ourselves) */
|
||||||
|
STP_O->root_path_cost = ((root_bridge_cost & 0xff) << 24)
|
||||||
|
| ((root_bridge_cost & 0xff00) << 8)
|
||||||
|
| ((root_bridge_cost >> 8) & 0xff00)
|
||||||
|
| (root_bridge_cost >> 24);
|
||||||
|
|
||||||
STP_O->bridge.prio = 0x80;
|
STP_O->bridge.prio = stp_prio;
|
||||||
STP_O->bridge.ext = 0x00;
|
STP_O->bridge.ext = 0x00;
|
||||||
|
|
||||||
STP_O->port_prio = 0x80;
|
STP_O->port_prio = stp_pprio[port];
|
||||||
STP_O->port_id = port;
|
STP_O->port_id = port + 1;
|
||||||
STP_O->age = 0x00; // FIXME: This only works because we do not use HTONS and the values are in 1/256 seconds
|
STP_O->age = 0x00; // FIXME: This only works because we do not use HTONS and the values are in 1/256 seconds
|
||||||
STP_O->age_max = 20;
|
STP_O->age_max = stp_maxage_s;
|
||||||
STP_O->hello = 2;
|
STP_O->hello = stp_hello_s;
|
||||||
STP_O->fwd_delay = 0x0f;
|
STP_O->fwd_delay = stp_fwddelay_s;
|
||||||
|
|
||||||
// uip_len = 0x27 + sizeof(struct rtl_tag);
|
|
||||||
uip_len = sizeof(struct stp_pkt);
|
uip_len = sizeof(struct stp_pkt);
|
||||||
tcpip_output();
|
tcpip_output();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void stp_in(void) __banked
|
||||||
|
{
|
||||||
|
// By default we do not send anything out
|
||||||
|
uip_len = 0;
|
||||||
|
|
||||||
|
/* Ingress port: low nibble of the CPU tag's pmask on RX */
|
||||||
|
stp_scratch = ((uint8_t)HTONS(STP_I->rtl_tag.pmask)) & 0x0f;
|
||||||
|
if (stp_scratch < machine.min_port || stp_scratch > machine.max_port)
|
||||||
|
return;
|
||||||
|
{
|
||||||
|
__xdata static uint8_t port_l; /* NOT stp_scratch: stp_state_set() clobbers it */
|
||||||
|
uint8_t port = (port_l = stp_scratch);
|
||||||
|
(void)port_l;
|
||||||
|
|
||||||
|
// Make sure this is the type of (R)STP packet we are interested in:
|
||||||
|
if (!(STP_I->dsap == 0x42 && STP_I->ssap == 0x42 && STP_I->ctrl == 0x03))
|
||||||
|
return;
|
||||||
|
if (STP_I->proto)
|
||||||
|
return;
|
||||||
|
/* Accept RSTP BPDUs (v2 type 2) and legacy Config BPDUs (v0 type 0) */
|
||||||
|
if (!((STP_I->version == 2 && STP_I->bpdu_type == 2)
|
||||||
|
|| (STP_I->version == 0 && STP_I->bpdu_type == 0)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!(stp_pflags[port] & STP_PF_ENABLED) || (stp_pflags[port] & STP_PF_FILTER))
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* BPDU guard: an edge-facing port must never see a BPDU - shut it down. */
|
||||||
|
if (stp_pflags[port] & STP_PF_BPDUGUARD) {
|
||||||
|
print_string("STP: BPDU guard tripped, disabling port ");
|
||||||
|
print_byte(port); write_char('\n');
|
||||||
|
stp_pflags[port] |= STP_PF_TRIPPED;
|
||||||
|
stp_state_set(port, 0b00);
|
||||||
|
stp_tc_count++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stp_bpdu_age[port] = 0;
|
||||||
|
|
||||||
|
/* Our own BPDU coming back at us = a loop in the network. Block the port
|
||||||
|
* for a listen period; if the loop persists the BPDUs keep arriving and
|
||||||
|
* the port stays blocked. */
|
||||||
|
if (cmpMAC(STP_I->bridge.mac, uip_ethaddr.addr) == 0) {
|
||||||
|
if (port_timers[port] == 0 && !(stp_pflags[port] & STP_PF_TRIPPED)) {
|
||||||
|
print_string("STP: loop detected on port ");
|
||||||
|
print_byte(port); write_char('\n');
|
||||||
|
stp_state_set(port, 0b01);
|
||||||
|
port_timers[port] = (uint16_t)stp_fwddelay_s * STP_HZ;
|
||||||
|
stp_pflags[port] &= ~STP_PF_OPEREDGE;
|
||||||
|
stp_tc_count++;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Better root than the one we know? */
|
||||||
|
if (STP_I->root.prio < root_bridge.prio
|
||||||
|
|| ((STP_I->root.prio == root_bridge.prio) && cmpMAC(STP_I->root.mac, root_bridge.mac) < 0)) {
|
||||||
|
/* Root guard: this port must never become our path to the root. */
|
||||||
|
if (stp_pflags[port] & STP_PF_ROOTGUARD) {
|
||||||
|
print_string("STP: root guard blocking port ");
|
||||||
|
print_byte(port); write_char('\n');
|
||||||
|
stp_state_set(port, 0b01);
|
||||||
|
port_timers[port] = (uint16_t)stp_fwddelay_s * STP_HZ;
|
||||||
|
stp_pflags[port] &= ~STP_PF_OPEREDGE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
print_string("Updating Root bridge\n");
|
||||||
|
root_bridge.prio = STP_I->root.prio;
|
||||||
|
root_bridge.ext = STP_I->root.ext;
|
||||||
|
memcpy(root_bridge.mac, STP_I->root.mac, 6);
|
||||||
|
stp_root_port = port;
|
||||||
|
stp_tc_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Refresh our cost to the root when the update comes in on the root port */
|
||||||
|
if (port == stp_root_port) {
|
||||||
|
stp_cost_scratch = STP_I->root_path_cost;
|
||||||
|
/* big-endian on the wire */
|
||||||
|
root_bridge_cost = ((stp_cost_scratch & 0xff) << 24)
|
||||||
|
| ((stp_cost_scratch & 0xff00) << 8)
|
||||||
|
| ((stp_cost_scratch >> 8) & 0xff00)
|
||||||
|
| (stp_cost_scratch >> 24);
|
||||||
|
root_bridge_cost += PCOST(port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void stp_timers(void) __banked
|
void stp_timers(void) __banked
|
||||||
{
|
{
|
||||||
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
|
/* Refill the per-port tx budgets once per second (tx hold count) */
|
||||||
port_hello[i]--;
|
if (++stp_sec_tick >= STP_HZ) {
|
||||||
if (!port_hello[i]) {
|
stp_sec_tick = 0;
|
||||||
port_hello[i] = TIME_HELLO;
|
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++)
|
||||||
print_string("STP_HELLO port ");
|
stp_tx_budget[stp_i] = stp_txhold;
|
||||||
print_byte(i); write_char('\n');
|
|
||||||
stp_cnf_send(i);
|
|
||||||
}
|
}
|
||||||
/* Promote a port out of the initial blocking state once its listen
|
|
||||||
* period expires. stp_setup() puts every port into blocking with
|
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) {
|
||||||
* port_timers = 10 s, but nothing ever counted that down - so on a
|
if (!(stp_pflags[stp_i] & STP_PF_ENABLED))
|
||||||
* network with no other RSTP bridge (nobody sends us BPDUs) every
|
continue;
|
||||||
* port stayed blocking FOREVER and "stp on" killed the whole
|
|
||||||
* network. If no better root was heard during the listen period we
|
if (stp_bpdu_age[stp_i] < 0xffff)
|
||||||
* are the designated bridge on that port: go to forwarding. */
|
stp_bpdu_age[stp_i]++;
|
||||||
if (port_timers[i]) {
|
|
||||||
if (!--port_timers[i]) {
|
/* Periodic hello */
|
||||||
reg_read_m(RTL837X_MSTP_STATES);
|
if (port_hello[stp_i])
|
||||||
sfr_data[3 - (i >> 2)] |= (uint8_t)(0b11 << ((i << 1) & 0x7));
|
port_hello[stp_i]--;
|
||||||
reg_write_m(RTL837X_MSTP_STATES);
|
if (!port_hello[stp_i]) {
|
||||||
|
port_hello[stp_i] = (uint16_t)stp_hello_s * STP_HZ;
|
||||||
|
stp_cnf_send(stp_i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Promote a port out of blocking once its listen period expires
|
||||||
|
* with no reason to stay blocked (no better root heard: we are
|
||||||
|
* the designated bridge on that port). */
|
||||||
|
if (port_timers[stp_i]) {
|
||||||
|
if (!--port_timers[stp_i]) {
|
||||||
|
stp_state_set(stp_i, 0b11);
|
||||||
print_string("STP: port forwarding ");
|
print_string("STP: port forwarding ");
|
||||||
print_byte(i); write_char('\n');
|
print_byte(stp_i); write_char('\n');
|
||||||
|
stp_tc_count++;
|
||||||
|
} else if ((stp_pflags[stp_i] & STP_PF_AUTOEDGE)
|
||||||
|
&& stp_bpdu_age[stp_i] > STP_EDGE_DELAY) {
|
||||||
|
/* Auto edge: nothing talks (R)STP on this port - it is
|
||||||
|
* host-facing, go to forwarding without the full wait. */
|
||||||
|
port_timers[stp_i] = 0;
|
||||||
|
stp_pflags[stp_i] |= STP_PF_OPEREDGE;
|
||||||
|
stp_state_set(stp_i, 0b11);
|
||||||
|
print_string("STP: edge port forwarding ");
|
||||||
|
print_byte(stp_i); write_char('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Age out a root that went silent: reclaim the tree. */
|
||||||
|
if (stp_root_port != 0xff
|
||||||
|
&& stp_bpdu_age[stp_root_port] > (uint16_t)stp_maxage_s * STP_HZ) {
|
||||||
|
print_string("STP: root aged out, claiming root\n");
|
||||||
|
stp_claim_root();
|
||||||
|
stp_tc_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Reset all configuration to the 802.1D/802.1w defaults. Called once at boot
|
||||||
|
* (before the startup config replays "stp ..." commands over it). */
|
||||||
|
void stp_defaults(void) __banked
|
||||||
|
{
|
||||||
|
stp_prio = 0x80; /* 32768 */
|
||||||
|
stp_hello_s = 2;
|
||||||
|
stp_maxage_s = 20;
|
||||||
|
stp_fwddelay_s = 15;
|
||||||
|
stp_rstp = 1;
|
||||||
|
stp_txhold = 6;
|
||||||
|
for (stp_i = 0; stp_i < 10; stp_i++) {
|
||||||
|
/* enabled, auto-edge on: host-facing ports go forwarding after
|
||||||
|
* 3 s of BPDU silence instead of the full forward delay */
|
||||||
|
stp_pflags[stp_i] = STP_PF_ENABLED | STP_PF_AUTOEDGE;
|
||||||
|
stp_pcost[stp_i] = 0; /* auto */
|
||||||
|
stp_pprio[stp_i] = 0x80;
|
||||||
|
stp_bpdu_age[stp_i] = 0;
|
||||||
|
port_timers[stp_i] = 0;
|
||||||
|
port_hello[stp_i] = 0;
|
||||||
|
stp_tx_budget[stp_i] = 6;
|
||||||
|
}
|
||||||
|
stp_tc_count = 0;
|
||||||
|
stp_claim_root();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -263,22 +441,29 @@ void stp_setup(void) __banked
|
|||||||
{
|
{
|
||||||
print_string("Enabling STP: ");
|
print_string("Enabling STP: ");
|
||||||
sfr_data[0] = sfr_data[1] = sfr_data[2] = sfr_data[3] = 0;
|
sfr_data[0] = sfr_data[1] = sfr_data[2] = sfr_data[3] = 0;
|
||||||
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
|
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) {
|
||||||
// Set STP port state to blocking
|
stp_pflags[stp_i] &= ~(STP_PF_OPEREDGE | STP_PF_TRIPPED);
|
||||||
// States are: 00 disable, 01 blocking, 10 learning, 11 forwarding
|
stp_bpdu_age[stp_i] = 0;
|
||||||
uint8_t bit_mask = 0b01 << ( (i << 1) & 0x7);
|
stp_tx_budget[stp_i] = stp_txhold;
|
||||||
sfr_data[3 - (i >> 2)] |= bit_mask;
|
if (!(stp_pflags[stp_i] & STP_PF_ENABLED) || (stp_pflags[stp_i] & STP_PF_ADMEDGE)) {
|
||||||
port_hello[i] = TIME_HELLO;
|
/* not participating, or admin edge: forwarding immediately */
|
||||||
port_timers[i] = 0x280; // 10 s in blocking state (at the ~64 Hz stp_timers rate)
|
if (stp_pflags[stp_i] & STP_PF_ADMEDGE)
|
||||||
|
stp_pflags[stp_i] |= STP_PF_OPEREDGE;
|
||||||
|
sfr_data[3 - (stp_i >> 2)] |= (uint8_t)(0b11 << ((stp_i << 1) & 0x7));
|
||||||
|
port_timers[stp_i] = 0;
|
||||||
|
} else {
|
||||||
|
/* listen first: blocking until the forward-delay expires */
|
||||||
|
sfr_data[3 - (stp_i >> 2)] |= (uint8_t)(0b01 << ((stp_i << 1) & 0x7));
|
||||||
|
port_timers[stp_i] = (uint16_t)stp_fwddelay_s * STP_HZ;
|
||||||
|
}
|
||||||
|
port_hello[stp_i] = (uint16_t)stp_hello_s * STP_HZ;
|
||||||
}
|
}
|
||||||
sfr_data[1] |= 0x0c; // Do not block the CPU port (bits 3:2 of byte 1 = port 9)
|
sfr_data[1] |= 0x0c; // Do not block the CPU port (bits 3:2 of byte 1 = port 9)
|
||||||
reg_write_m(RTL837X_MSTP_STATES);
|
reg_write_m(RTL837X_MSTP_STATES);
|
||||||
|
|
||||||
print_reg(RTL837X_MSTP_STATES); write_char('\n');
|
print_reg(RTL837X_MSTP_STATES); write_char('\n');
|
||||||
|
|
||||||
root_bridge.prio = 0x80; // This corresponds to 32768
|
stp_claim_root();
|
||||||
root_bridge.ext = 0x00;
|
|
||||||
memcpy(root_bridge.mac, uip_ethaddr.addr, 6);
|
|
||||||
|
|
||||||
/* Take BPDUs to the CPU only - we are a participating bridge now. */
|
/* Take BPDUs to the CPU only - we are a participating bridge now. */
|
||||||
stp_fdb_update(PMASK_CPU);
|
stp_fdb_update(PMASK_CPU);
|
||||||
@@ -288,11 +473,12 @@ void stp_setup(void) __banked
|
|||||||
void stp_off(void) __banked
|
void stp_off(void) __banked
|
||||||
{
|
{
|
||||||
sfr_data[0] = sfr_data[1] = sfr_data[2] = sfr_data[3] = 0;
|
sfr_data[0] = sfr_data[1] = sfr_data[2] = sfr_data[3] = 0;
|
||||||
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
|
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) {
|
||||||
// Set STP port state to forwarding
|
// Set STP port state to forwarding
|
||||||
// States are: 00 disable, 01 blocking, 10 learning, 11 forwarding
|
// States are: 00 disable, 01 blocking, 10 learning, 11 forwarding
|
||||||
uint8_t bit_mask = 0b11 << ( (i << 1) & 0x7);
|
sfr_data[3 - (stp_i >> 2)] |= (uint8_t)(0b11 << ((stp_i << 1) & 0x7));
|
||||||
sfr_data[3 - (i >> 2)] |= bit_mask;
|
stp_pflags[stp_i] &= ~(STP_PF_OPEREDGE | STP_PF_TRIPPED);
|
||||||
|
port_timers[stp_i] = 0;
|
||||||
}
|
}
|
||||||
sfr_data[1] |= 0x0c; // Do not block the CPU port (bits 3:2 of byte 1 = port 9)
|
sfr_data[1] |= 0x0c; // Do not block the CPU port (bits 3:2 of byte 1 = port 9)
|
||||||
reg_write_m(RTL837X_MSTP_STATES);
|
reg_write_m(RTL837X_MSTP_STATES);
|
||||||
@@ -300,3 +486,131 @@ void stp_off(void) __banked
|
|||||||
/* Restore BPDU transparency: flood them again like an unmanaged switch. */
|
/* Restore BPDU transparency: flood them again like an unmanaged switch. */
|
||||||
stp_fdb_update(PMASK_CPU | (machine_detected.isRTL8373 ? PMASK_9 : PMASK_6));
|
stp_fdb_update(PMASK_CPU | (machine_detected.isRTL8373 ? PMASK_9 : PMASK_6));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---- "stp ..." CLI ----
|
||||||
|
* stp on|off
|
||||||
|
* stp prio <0-15> (bridge priority = n * 4096)
|
||||||
|
* stp hello <1-10> | stp maxage <6-40> | stp fwd <4-30> | stp txhold <1-10>
|
||||||
|
* stp version rstp|stp
|
||||||
|
* stp port <1-9> on|off
|
||||||
|
* stp port <1-9> edge on|off|auto
|
||||||
|
* stp port <1-9> cost <0-255> (x1000; 0 = auto/20000)
|
||||||
|
* stp port <1-9> prio <0-240>
|
||||||
|
* stp port <1-9> guard none|bpdu|root
|
||||||
|
* stp port <1-9> filter on|off
|
||||||
|
*/
|
||||||
|
void stp_parse(void) __banked __reentrant
|
||||||
|
{
|
||||||
|
if (cmd_compare(1, "on")) {
|
||||||
|
print_string("STP enabled\n");
|
||||||
|
stpEnabled = 1;
|
||||||
|
stp_setup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cmd_compare(1, "off")) {
|
||||||
|
print_string("STP disabled\n");
|
||||||
|
stp_off();
|
||||||
|
stpEnabled = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cmd_words_len < 3)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
if (cmd_compare(1, "port")) {
|
||||||
|
if (cmd_words_len < 4)
|
||||||
|
goto err;
|
||||||
|
if (atoi_byte(&stp_scratch, cmd_words_b[2]) || stp_scratch < 1 || stp_scratch > 9)
|
||||||
|
goto err;
|
||||||
|
{
|
||||||
|
uint8_t port = machine.phys_to_log_port[stp_scratch - 1];
|
||||||
|
if (cmd_compare(3, "on")) {
|
||||||
|
stp_pflags[port] |= STP_PF_ENABLED;
|
||||||
|
stp_pflags[port] &= ~STP_PF_TRIPPED;
|
||||||
|
if (stpEnabled) { /* (re)join: listen first */
|
||||||
|
stp_state_set(port, 0b01);
|
||||||
|
port_timers[port] = (uint16_t)stp_fwddelay_s * STP_HZ;
|
||||||
|
}
|
||||||
|
} else if (cmd_compare(3, "off")) {
|
||||||
|
stp_pflags[port] &= ~STP_PF_ENABLED;
|
||||||
|
if (stpEnabled)
|
||||||
|
stp_state_set(port, 0b11); /* plain forwarding */
|
||||||
|
} else if (cmd_compare(3, "edge")) {
|
||||||
|
stp_pflags[port] &= ~(STP_PF_ADMEDGE | STP_PF_AUTOEDGE);
|
||||||
|
if (cmd_compare(4, "on"))
|
||||||
|
stp_pflags[port] |= STP_PF_ADMEDGE;
|
||||||
|
else if (cmd_compare(4, "auto"))
|
||||||
|
stp_pflags[port] |= STP_PF_AUTOEDGE;
|
||||||
|
else if (!cmd_compare(4, "off"))
|
||||||
|
goto err;
|
||||||
|
} else if (cmd_compare(3, "cost")) {
|
||||||
|
if (atoi_byte(&stp_scratch, cmd_words_b[4]))
|
||||||
|
goto err;
|
||||||
|
stp_pcost[port] = (uint32_t)stp_scratch * 1000;
|
||||||
|
} else if (cmd_compare(3, "prio")) {
|
||||||
|
if (atoi_byte(&stp_scratch, cmd_words_b[4]))
|
||||||
|
goto err;
|
||||||
|
stp_pprio[port] = stp_scratch & 0xf0;
|
||||||
|
} else if (cmd_compare(3, "guard")) {
|
||||||
|
stp_pflags[port] &= ~(STP_PF_BPDUGUARD | STP_PF_ROOTGUARD);
|
||||||
|
if (cmd_compare(4, "bpdu"))
|
||||||
|
stp_pflags[port] |= STP_PF_BPDUGUARD;
|
||||||
|
else if (cmd_compare(4, "root"))
|
||||||
|
stp_pflags[port] |= STP_PF_ROOTGUARD;
|
||||||
|
else if (!cmd_compare(4, "none"))
|
||||||
|
goto err;
|
||||||
|
} else if (cmd_compare(3, "filter")) {
|
||||||
|
if (cmd_compare(4, "on"))
|
||||||
|
stp_pflags[port] |= STP_PF_FILTER;
|
||||||
|
else if (cmd_compare(4, "off"))
|
||||||
|
stp_pflags[port] &= ~STP_PF_FILTER;
|
||||||
|
else
|
||||||
|
goto err;
|
||||||
|
} else {
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (atoi_byte(&stp_scratch, cmd_words_b[2])) {
|
||||||
|
if (cmd_compare(1, "version")) {
|
||||||
|
if (cmd_compare(2, "rstp"))
|
||||||
|
stp_rstp = 1;
|
||||||
|
else if (cmd_compare(2, "stp"))
|
||||||
|
stp_rstp = 0;
|
||||||
|
else
|
||||||
|
goto err;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
if (cmd_compare(1, "prio")) {
|
||||||
|
if (stp_scratch > 15)
|
||||||
|
goto err;
|
||||||
|
stp_prio = stp_scratch << 4; /* n * 4096, as the BPDU's high byte */
|
||||||
|
if (stp_root_port == 0xff)
|
||||||
|
stp_claim_root(); /* re-announce with the new priority */
|
||||||
|
} else if (cmd_compare(1, "hello")) {
|
||||||
|
if (stp_scratch < 1 || stp_scratch > 10)
|
||||||
|
goto err;
|
||||||
|
stp_hello_s = stp_scratch;
|
||||||
|
} else if (cmd_compare(1, "maxage")) {
|
||||||
|
if (stp_scratch < 6 || stp_scratch > 40)
|
||||||
|
goto err;
|
||||||
|
stp_maxage_s = stp_scratch;
|
||||||
|
} else if (cmd_compare(1, "fwd")) {
|
||||||
|
if (stp_scratch < 4 || stp_scratch > 30)
|
||||||
|
goto err;
|
||||||
|
stp_fwddelay_s = stp_scratch;
|
||||||
|
} else if (cmd_compare(1, "txhold")) {
|
||||||
|
if (stp_scratch < 1 || stp_scratch > 10)
|
||||||
|
goto err;
|
||||||
|
stp_txhold = stp_scratch;
|
||||||
|
} else {
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
err:
|
||||||
|
print_string("Error: stp on|off | prio <0-15> | hello <1-10> | maxage <6-40> | fwd <4-30> | txhold <1-10> | version rstp|stp | port <1-9> on|off|edge|cost|prio|guard|filter ...\n");
|
||||||
|
}
|
||||||
|
|||||||
+28
-6
@@ -6,8 +6,8 @@ void stp_in(void) __banked;
|
|||||||
void stp_setup(void) __banked;
|
void stp_setup(void) __banked;
|
||||||
void stp_timers(void) __banked;
|
void stp_timers(void) __banked;
|
||||||
void stp_off(void) __banked;
|
void stp_off(void) __banked;
|
||||||
|
void stp_parse(void) __banked __reentrant; /* "stp ..." CLI handler (cmd_parser delegates here) */
|
||||||
#define TIME_HELLO 0x80 // 2 sec (stp_timers runs at ~64 Hz: main loop ~256 Hz / STP_TICK_DIVIDER+1)
|
void stp_defaults(void) __banked; /* boot init: 802.1D/w default configuration */
|
||||||
|
|
||||||
/* Bridge identifier as carried in a BPDU (priority, extension, MAC). */
|
/* Bridge identifier as carried in a BPDU (priority, extension, MAC). */
|
||||||
struct bridge {
|
struct bridge {
|
||||||
@@ -16,11 +16,33 @@ struct bridge {
|
|||||||
uint8_t mac[6];
|
uint8_t mac[6];
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Protocol state, exposed read-only for the web UI (page_impl.c send_stp())
|
/* ---- Configuration (defaults per 802.1D-2004/802.1w, set in stp_defaults) --- */
|
||||||
* - the elected root bridge and our path cost to it. Owned by rtl837x_stp.c;
|
|
||||||
* stpEnabled is owned by rtlplayground.c. */
|
|
||||||
extern __xdata uint8_t stpEnabled;
|
extern __xdata uint8_t stpEnabled;
|
||||||
|
extern __xdata uint8_t stp_prio; /* bridge priority, high byte: 0x80 = 32768; CLI takes 0-15 (steps of 4096) */
|
||||||
|
extern __xdata uint8_t stp_hello_s; /* hello time, 1-10 s (default 2) */
|
||||||
|
extern __xdata uint8_t stp_maxage_s; /* max age, 6-40 s (default 20) */
|
||||||
|
extern __xdata uint8_t stp_fwddelay_s; /* forward delay, 4-30 s (default 15); our listen period */
|
||||||
|
extern __xdata uint8_t stp_rstp; /* 1 = RSTP BPDUs (v2), 0 = STP-compatible Config BPDUs (v0) */
|
||||||
|
extern __xdata uint8_t stp_txhold; /* max BPDUs per port per second (default 6) */
|
||||||
|
|
||||||
|
/* Per-port config/status flags (stp_pflags[]) */
|
||||||
|
#define STP_PF_ENABLED 0x01 /* port participates in STP (default on) */
|
||||||
|
#define STP_PF_ADMEDGE 0x02 /* admin edge: forwarding immediately */
|
||||||
|
#define STP_PF_AUTOEDGE 0x04 /* auto edge: forward after 3 s without BPDU */
|
||||||
|
#define STP_PF_BPDUGUARD 0x08 /* disable port if a BPDU arrives */
|
||||||
|
#define STP_PF_ROOTGUARD 0x10 /* never accept a better root on this port */
|
||||||
|
#define STP_PF_FILTER 0x20 /* neither send nor accept BPDUs */
|
||||||
|
#define STP_PF_OPEREDGE 0x40 /* runtime: port went forwarding as an edge */
|
||||||
|
#define STP_PF_TRIPPED 0x80 /* runtime: disabled by BPDU guard */
|
||||||
|
|
||||||
|
extern __xdata uint8_t stp_pflags[10];
|
||||||
|
extern __xdata uint32_t stp_pcost[10]; /* path cost; 0 = auto (20000) */
|
||||||
|
extern __xdata uint8_t stp_pprio[10]; /* port priority (default 0x80) */
|
||||||
|
|
||||||
|
/* ---- Status, exposed read-only for the web UI (send_stp) ---- */
|
||||||
extern __xdata struct bridge root_bridge;
|
extern __xdata struct bridge root_bridge;
|
||||||
extern __xdata uint32_t root_bridge_cost;
|
extern __xdata uint32_t root_bridge_cost; /* our path cost to the root (0 if we are root) */
|
||||||
|
extern __xdata uint8_t stp_root_port; /* logical port towards the root; 0xff = we are root */
|
||||||
|
extern __xdata uint16_t stp_tc_count; /* topology change counter (diagnostics) */
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -2141,6 +2141,7 @@ void main(void)
|
|||||||
print_reg(RTL837X_REG_SEC_COUNTER);
|
print_reg(RTL837X_REG_SEC_COUNTER);
|
||||||
#endif
|
#endif
|
||||||
stpEnabled = 0;
|
stpEnabled = 0;
|
||||||
|
stp_defaults(); /* 802.1D/w default config before any "stp ..." replay */
|
||||||
nic_setup();
|
nic_setup();
|
||||||
vlan_setup();
|
vlan_setup();
|
||||||
port_l2_setup();
|
port_l2_setup();
|
||||||
|
|||||||
Reference in New Issue
Block a user