Merge pull request #314 from DrDoof/feat/stp-v2

stp: Spanning Tree support for the RTL837x switches
This commit is contained in:
logicog
2026-09-01 18:52:20 +02:00
committed by GitHub
18 changed files with 1506 additions and 125 deletions
+4 -2
View File
@@ -28,8 +28,10 @@ only the following features are provided:
<img width="1420" height="623" alt="GUI" src="doc/images/gui.png" />
While the firmware provides already considerable improvements over the original managed firmware,
the firmware still lacks support for STP and the proprietary loop prevention
protocols as well as DHCP. If you need these features, do not install the playground on your managed
the firmware still lacks support for the proprietary loop prevention
protocols as well as DHCP. Spanning Tree is available (see doc/stp.md), but is
a simplified implementation - read that document before enabling it on a
switch you administer over the network. If you need these features, do not install the playground on your managed
devices. In any case, installation is strongly discouraged unless you can at least make
a backup of the original flash content via a SOIC clamp such as also used for BIOS
backups and can re-install that firmware in case something is wrong. For this no soldering
+3 -10
View File
@@ -26,7 +26,8 @@
#pragma constseg BANK2
extern __code struct machine machine;
extern __xdata uint8_t stpEnabled;
extern __xdata bool stp_enabled;
extern __code uint8_t log_to_phys_port[9];
extern volatile __xdata uint32_t ticks;
extern volatile __xdata uint8_t sfr_data[4];
@@ -1683,15 +1684,7 @@ void cmd_parser(void) __banked
print_string("Error: hostname [name] - the name must not contain spaces\n");
}
} else if (cmd_compare(0, "stp")) {
if (cmd_compare(1, "on")) {
print_string("STP enabled\n");
stpEnabled = 1;
stp_setup();
} else {
print_string("STP disabled\n");
stp_off();
stpEnabled = 0;
}
stp_parse();
} else if (cmd_compare(0, "pvid")) {
if (cmd_words_len == 3 && cmd_parse_port_separator(cmd_words_b[1]) != 0
&& atoi_short(cmd_words_b[2]) && atoi_results_short && atoi_results_short <= 4094)
+30
View File
@@ -64,3 +64,33 @@ Writing 0x1 to register 0x7850 will transmit the frame. The Ethernet frame
checksum and the TCP checksum are automatically calculated (offloaded) by the
ASIC before transmitting on the wire.
## The RTL tag words
The frame header uses the Realtek Remote Control Protocol (RRCP) format or
the like.
The `flags` word:
```
bit15 EFID_EN | 14:12 EFID | 11 PRI_EN | 10:8 PRI |
bit7 KEEP | 6 VSEL | 5 LEARN_DIS | 4:0 VIDX
```
All fields are in network byte order.
* `EFID_EN`, `EFID`: look the destination up under this filtering ID
instead of the port's own
* `PRI_EN`, `PRI`: force the given priority on the frame
* `KEEP`: keep the 802.1Q tagging of the frame exactly as injected,
bypassing the egress tagging rules of the port
* `VSEL`, `VIDX`: classify the frame into the VLAN at this index of the
VLAN table
* `LEARN_DIS`: do not learn the source address from this frame
The `pmask` word: bit 15 is `ALLOW`, bits 14 to 0 are a port mask.
* `ALLOW` clear: the mask is the egress set, the frame goes to exactly
the ports given
* `ALLOW` set: the ASIC looks the destination up as usual and the mask
only limits which ports the result may use
+33
View File
@@ -65,3 +65,36 @@ ASIC and flushing the table in order to quickly forget the learned entries.
3c:18:a0:7e:11:00 0x0001 learned 5
1c:2a:a3:23:00:02 0x0001 learned 7
```
## Static multicast entries
Slow-protocol frames such as LACPDUs and STP BPDUs have to reach the CPU
without being flooded to the other ports. No bridge relays these frames:
their addresses are in the set that 802.1D-2004 clause 7.12.6 forbids a
bridge to forward, and what travels the network is the information, with
every bridge regenerating BPDUs of its own on its designated ports. The reserved-multicast *trap* action
cannot do that on this hardware, because its destination is an external CPU
attached to a physical port, which these boards do not populate. The protocol
modules therefore leave the reserved-multicast action at *forward* and constrain
the egress with a static L2 multicast entry instead: the lookup hits the entry's
own port mask rather than the VLAN flood mask. Verified on a SWTGW218AS both
ways, with the CPU bit cleared, where delivery stops, and with the CPU bit alone,
where nothing egresses.
`port_l2mc_set()` writes one such entry. The SMI layout is the L2 multicast
variant of the table entry:
```
DATA_IN_A = MAC bytes 5..2 -> c2 00 00 <mac_last>
DATA_IN_B = MAC[1..0] | vid<<16 | IVL<<29 | pmask[1:0]<<30
DATA_IN_C = pmask[9:2]
```
Lookups are IVL, so an entry made for VID 0 is never matched and a caller adds
one entry per PVID in use. The write goes through the table access register
with the table selector set to the L2 lookup table, `TBL_L2_UNICAST` in the
code, a name that despite appearances covers the multicast entries as well.
The hardware hashes MAC and VID to pick the bucket slot by itself.
Writing the same MAC and VID again replaces the entry rather than adding a
second one, so a caller can retarget the mask at will, for instance back to all
ports to restore flooding.
+155
View File
@@ -0,0 +1,155 @@
# Spanning Tree (STP / RSTP)
The switch can take part in a spanning tree (IEEE 802.1D / 802.1w) so that
redundant links between bridges are blocked instead of forming a loop. The
implementation elects a root bridge from the BPDUs it receives, promotes ports
to forwarding once their listen period expires, ages the root out when it goes
silent, and blocks a port on which it sees its own BPDU.
STP can be enabled and controlled via the web interface or the command line,
as follows:
## Quick start
```
stp on # start participating
stp off # stop, all ports back to forwarding
```
Live status is on the Spanning Tree page of the web UI (or `/stp.json`),
and on the serial console via `stp status`.
With no other bridge around, the switch elects itself root and every port ends
up forwarding — you can leave it on safely. Put the settings in the startup
config to make them survive a reboot:
```
stp prio 15
stp port 1 edge on
stp on
```
## Hardware background
BPDUs are addressed to `01:80:C2:00:00:00`, a reserved link-local group. The
ASIC's Reserved-Multicast action for that address decides what happens to the
frame.
Forwarding to the CPU port works normally: the 8051 sits behind an ordinary
port of the internal switch and is an ordinary member of a forwarding mask.
The *trap* action does not deliver to it. Its destination is an external CPU
attached to a physical port (`cpuTag_externalCpuPort_set`, `EXT_CPU_CTRL` in
the vendor SDK), which these boards do not populate. The ACL trap and
redirect actions do not deliver to the 8051 either.
Delivery therefore uses the *forward* action, constrained to the CPU port
by a static L2 multicast entry (`port_l2mc_set()`), one per VLAN in use:
* while STP runs, the entry's member mask is the CPU port only — BPDUs reach
the CPU and are not flooded to other ports, as a participating bridge
requires;
* with STP off, the same entries are retargeted to all ports, restoring the
transparency an unmanaged switch is expected to have, so a surrounding
spanning tree can span *through* this device.
A BPDU delivered this way is an ordinary frame to the port's ingress logic
and passes through its acceptable-frame-type filter. BPDUs are untagged, so a
port set to admit tagged frames only (`ingress <port>t`) never delivers one
to the CPU. `stp_setup()` prints a warning for every STP-enabled port in that
state.
Port states live in `RTL837X_MSTP_STATES (0x5310)`, two bits per port:
`00` disabled, `01` blocking, `10` learning, `11` forwarding. A port in
blocking forwards nothing between ports, but it still sends what the CPU
hands it and still passes a received BPDU up to the CPU, which is what lets
loop detection go on working on a port it has already blocked.
## Timers
`stp_timers()` runs at 50 Hz (the main loop idles on the 200 Hz system tick and
STP is called every fourth pass), which is what `STP_HZ` in `rtl837x_stp.h`
encodes. All configured values are in seconds:
| setting | default | range |
|---|---|---|
| `stp hello <n>` | 2 | 110 |
| `stp maxage <n>` | 20 | 640 |
| `stp fwd <n>` | 15 | 430 |
| `stp txhold <n>` | 6 | 110 |
A port entering the tree spends `fwd` seconds in blocking before it forwards
(an edge port skips the wait). Root information is discarded after `maxage`
seconds without a BPDU, and the switch then reclaims the root role.
## Topology changes
A change on a local non-edge port (the link coming or going, a port promoted
to forwarding) flushes the addresses learned on it and sets the TC flag in
our BPDUs for `maxage + fwd` seconds. A TC flag received in a BPDU is passed
on: the switch flushes the other non-edge ports once and keeps the flag in
its own BPDUs until one hello after the last flagged frame, so the
notification crosses the switch instead of dying at it. A legacy TCN is
acknowledged with TCA and then treated like a local change.
## Bridge settings
```
stp prio <0-15> # bridge priority = n * 4096, default 8 (32768)
stp version rstp|stp # RST BPDUs (default) or legacy Config BPDUs
stp hello|maxage|fwd|txhold <seconds>
```
The bridge with the lowest priority wins the root election; ties are broken by
the MAC address. If you do not want this switch to become the root of an
existing network, give it a worse priority than the current root — `stp prio 15`
(61440) is the usual "never me" value.
## Per-port settings
```
stp port <1-9> on|off # take part in STP, or stay plain forwarding
stp port <1-9> edge on|off|auto # host-facing port handling (default: auto)
stp port <1-9> cost <0-200000000> # path cost, 0 = automatic (20000)
stp port <1-9> prio <0-240> # port priority, steps of 16
stp port <1-9> guard none|bpdu|root
stp port <1-9> filter on|off # neither send nor accept BPDUs
stp port <1-9> p2p auto|on|off
```
**edge** — an edge port forwards immediately and does not trigger a
topology change when its link comes and goes; `auto` promotes a port to edge
after three seconds without a BPDU, and demotes it as soon as one arrives. Use
`edge on` for ports where only hosts are attached.
**guard** — `bpdu` disables a port as soon as a BPDU arrives on it (a host port
should never see one); `root` keeps a port from ever becoming the path to the
root, which protects an existing topology from a newly attached bridge that
claims a better priority.
**filter** — the port neither sends nor accepts BPDUs. Useful when the device
on the far side reacts badly to them (some unmanaged switches with loop
prevention cut the link) but you still want STP on the rest of the ports.
## Status
The Spanning Tree page shows the elected root (priority and MAC), the path cost
to it, the root port, the topology-change counter and, per port, the live state
read from the ASIC together with the configured options. The same data is
available as JSON:
```
GET /stp.json
```
The `stp status` command prints the same view on the serial console.
## Limitations
* One spanning-tree instance; no MSTP, no per-VLAN trees.
* No proposal/agreement handshake — an RST-capable neighbour will still
converge, but through the timers rather than the fast transition.
* Port roles are approximated: the root port and designated ports are
distinguished, alternate/backup are not.
* Topology changes propagate away from the root only: nothing is announced
on the root port (no TCN and no BPDUs at all), so bridges upstream rely on
their own detection.
+11 -1
View File
@@ -22,6 +22,15 @@ const conf_cmds = [
/^laghash\s+\d(\s+\w+)+$/,
/^isolate\s+\d{1,2}(\s+(off|\d{1,2}))+$/,
/^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,9}$/,
/^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)$/,
/^stp\s+port\s+\d{1,2}\s+p2p\s+(auto|on|off)$/,
/^igmp\s+(on|off)$/,
/^mtu\s+\d{1,2}\s+\d+$/,
/^bw\s+(in|out)\s+\d{1,2}\s+\S+$/,
@@ -46,7 +55,8 @@ const conf_overwrite = [
/^lag\s+\d+\b/,
/^laghash\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|p2p)\b/,
/^igmp\b/,
/^mtu\s+\d{1,2}\b/,
/^bw\s+(in|out)\s+\d{1,2}\b/,
+1
View File
@@ -4,6 +4,7 @@ document.getElementById('sidebar').innerHTML =
+ "<li><a href='stat.html' data-i18n='nav_port_stat'>Port Statistics</a></li>"
+ "<li><a href='vlan.html' >VLAN</a></li>"
+ "<li><a href='l2.html' data-i18n='nav_l2'>L2 Configuration</a></li>"
+ "<li><a href='stp.html'>Spanning Tree</a></li>"
+ "<li><a href='mirror.html' data-i18n='nav_mirror'>Mirroring</a></li>"
+ "<li><a href='lag.html' data-i18n='nav_lag'>Link Aggregation</a></li>"
+ "<li><a href='eee.html' data-i18n='nav_eee'>EEE</a></li>"
+47
View File
@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<script src="/main.js"></script>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css">
<title>FreeSwitchOS Spanning Tree</title>
</head>
<body>
<nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div>
<h1>Spanning Tree (RSTP)</h1>
<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>
<div id="stpStat" style="font-family:monospace;"></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>Port configuration</h2>
<table id="stpPortsTbl">
<tr>
<th>Port</th><th>State</th><th>Path Cost<br><span style="font-weight:normal;font-size:small">(0 = Auto)</span></th><th>Priority</th><th>Edge Port</th><th>BPDU Filter</th><th>Guard</th><th>Point-to-Point</th>
</tr>
</table>
<h2>Port status</h2>
<table id="stpStatTbl">
<tr>
<th>Port</th><th>Port State</th><th>Role</th><th>Designated Bridge</th><th>Designated Port ID</th><th>Designated Cost</th><th>Oper. Edge</th><th>Oper. P2P</th>
</tr>
</table>
<script src="/stp.js"></script>
</div>
</body>
<script src="/navigation.js"></script>
</html>
+187
View File
@@ -0,0 +1,187 @@
const STP_STATES = ["Disabled", "Blocking", "Learning", "Forwarding"];
const STP_ROLES = ["-", "Root", "Designated", "Alternate"];
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;
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");
const stat = document.getElementById("stpStatTbl");
for (const p of [...ports].sort((a, b) => a.p - b.p)) {
const tr = tbl.insertRow();
tr.insertCell().textContent = p.p; // Port
tr.insertCell().appendChild(sel("en_" + p.p,
[["on","Enable"],["off","Disable"]],
e => stpCmd("stp port " + p.p + " " + e.target.value)));
const pc = num("cost_" + p.p, 0, 200000000,
e => stpCmd("stp port " + p.p + " cost " + e.target.value));
pc.style.width = "7em";
pc.title = "0 - 200000000 (0 = Auto)";
tr.insertCell().appendChild(pc);
const pr = sel("prio_" + p.p, [],
e => stpCmd("stp port " + p.p + " prio " + e.target.value));
for (let v = 0; v <= 240; v += 16) {
const o = document.createElement("option");
o.value = v; o.textContent = v + (v === 128 ? " (default)" : "");
pr.appendChild(o);
}
tr.insertCell().appendChild(pr);
tr.insertCell().appendChild(sel("edge_" + p.p,
[["auto","Auto"],["on","Enable"],["off","Disable"]],
e => stpCmd("stp port " + p.p + " edge " + e.target.value)));
tr.insertCell().appendChild(sel("filt_" + p.p,
[["off","Disable"],["on","Enable"]],
e => stpCmd("stp port " + p.p + " filter " + 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("p2p_" + p.p,
[["auto","Auto"],["on","Enable"],["off","Disable"]],
e => stpCmd("stp port " + p.p + " p2p " + e.target.value)));
const sr = stat.insertRow();
sr.insertCell().textContent = p.p;
for (const id of ["st","role","db","dp","dc","oe","op"])
sr.insertCell().id = id + "_" + p.p;
}
stpRows = ports.length;
}
function bridgeSelf(s) {
return fmtBridgeId((s.prio * 4096).toString(16).padStart(4, "0") + s.myMac);
}
function fmtBridgeId(h) {
if (!h || h.length < 16) return "";
const prio = parseInt(h.slice(0, 4), 16);
const mac = h.slice(4).replace(/(..)(?=.)/g, "$1:");
return prio + "-" + mac.toUpperCase();
}
function fetchStp() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
const s = JSON.parse(xhttp.responseText);
if (!stpRows)
buildPortsTable(s.ports);
document.getElementById("stpStat").textContent = s.on
? (s.weRoot
? "This switch (" + bridgeSelf(s) + ") is the root bridge — topology changes: "
+ parseInt(s.tc, 16)
: "This switch: " + bridgeSelf(s)
+ " — root bridge: " + fmtBridgeId(s.rootPrio + s.rootMac)
+ " via port " + s.rootPort + " — path cost: " + parseInt(s.cost, 16)
+ " — topology changes: " + parseInt(s.tc, 16))
: "";
for (const p of s.ports) {
const trip = (p.f & PF_TRIPPED) ? " (guard!)" : "";
document.getElementById("st_" + p.p).textContent =
s.on ? STP_STATES[p.st] + trip : "-";
document.getElementById("role_" + p.p).textContent =
s.on ? STP_ROLES[p.role] : "-";
document.getElementById("db_" + p.p).textContent = s.on ? fmtBridgeId(p.db) : "-";
document.getElementById("dp_" + p.p).textContent =
s.on ? (parseInt(p.dp.slice(0, 2), 16) + "-" + parseInt(p.dp.slice(2), 16)) : "-";
document.getElementById("dc_" + p.p).textContent = s.on ? parseInt(p.dc, 16) : "-";
document.getElementById("oe_" + p.p).textContent =
s.on ? ((p.f & PF_OPEREDGE) ? "True" : "False") : "-";
document.getElementById("op_" + p.p).textContent = s.on ? (p.p2 == 2 ? "False" : "True") : "-";
}
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 = parseInt(p.pc, 16);
document.getElementById("prio_" + p.p).value = p.prio;
document.getElementById("p2p_" + p.p).value = ["auto","on","off"][p.p2];
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";
}
}
};
xhttp.open("GET", `/stp.json`, true);
sendXHTTP(xhttp);
}
async function stpSub() {
const on = document.getElementById("stpMode").value === "on";
document.getElementById("stpStat").textContent = on
? "Enabling STP. The ports start blocked and take up to "
+ (2 * document.getElementById("bFwd").value)
+ " s to reach forwarding, and this page can stay silent until they do."
: "Disabling STP.";
await stpCmd(on ? "stp on" : "stp off");
}
window.addEventListener("load", function() {
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")
.addEventListener("change", () => { stpDirty = true; });
update( () => {
fetchStp();
const interval = setInterval(update, 2000);
const stpInt = setInterval(fetchStp, 2000);
});
});
+3
View File
@@ -20,6 +20,7 @@
#pragma constseg BANK1
extern volatile __xdata uint8_t sfr_data[4];
extern volatile __xdata uint32_t ticks;
extern __code uint8_t * __code hex;
extern __code struct f_data f_data[];
extern __code char * __code mime_strings[];
@@ -825,6 +826,8 @@ void httpd_appcall(void)
send_mtu();
} else if (is_word(q, "/lag.json")) {
send_lag();
} else if (is_word(q, "/stp.json")) {
send_stp();
} else if (is_word(q, "/vlanlist")) {
send_vlanlist();
} else if (is_word(q, "/config")) {
+114
View File
@@ -12,6 +12,7 @@
#include "phy.h"
#include "version.h"
#include "machine.h"
#include "rtl837x_stp.h"
#include "page_impl.h"
#include "syslog.h"
@@ -530,6 +531,119 @@ void send_lag(void)
}
static __xdata uint32_t pi_u32;
static __xdata uint8_t pi_prio, pi_ext;
static __xdata uint8_t * __xdata pi_mac;
static void u32hex_html(void)
{
__xdata uint8_t *b = (__xdata uint8_t *)&pi_u32;
byte_to_html(b[3]);
byte_to_html(b[2]);
byte_to_html(b[1]);
byte_to_html(b[0]);
}
static void bridge_to_html(void)
{
byte_to_html(pi_prio);
byte_to_html(pi_ext);
for (uint8_t i = 0; i < 6; i++)
byte_to_html(pi_mac[i]);
}
void send_stp(void)
{
uint8_t i, j, st, dsg;
dbg_string("send_stp called\n");
slen = strtox(outbuf, HTTP_RESPONCE_JSON);
slen += strtox(outbuf + slen, "{\"on\":");
bool_to_html(stp_enabled);
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 (j = 0; j < 6; j++)
byte_to_html(root_bridge.mac[j]);
slen += strtox(outbuf + slen, "\",\"myMac\":\"");
for (j = 0; j < 6; j++)
byte_to_html(uip_ethaddr.addr[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);
slen += strtox(outbuf + slen, "\",\"weRoot\":");
bool_to_html(stp_root_port == 0xff ? 1 : 0);
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 (i = machine.min_port; i <= machine.max_port; i++) {
slen += strtox(outbuf + slen, "{\"p\":");
itoa_html(machine.log_to_phys_port[i]);
slen += strtox(outbuf + slen, ",\"st\":");
st = (sfr_data[3 - (i >> 2)] >> ((i << 1) & 0x7)) & 0x3;
itoa_html(st);
slen += strtox(outbuf + slen, ",\"role\":");
if (!(stp_pflags[i] & STP_PF_ENABLED) || (stp_pflags[i] & STP_PF_TRIPPED))
itoa_html(0);
else if (i == stp_root_port)
itoa_html(1);
else if (st == 3)
itoa_html(2);
else
itoa_html(3);
slen += strtox(outbuf + slen, ",\"f\":");
itoa_html(stp_pflags[i]);
slen += strtox(outbuf + slen, ",\"pc\":\"");
pi_u32 = stp_pcost[i]; u32hex_html();
slen += strtox(outbuf + slen, "\",\"prio\":");
itoa_html(stp_pprio[i]);
slen += strtox(outbuf + slen, ",\"p2\":");
itoa_html(stp_pp2p[i]);
dsg = stp_dpid[i] && stp_bpdu_age[i] < (uint16_t)stp_maxage_s * STP_HZ;
slen += strtox(outbuf + slen, ",\"db\":\"");
if (dsg) {
pi_prio = stp_dbridge[i].prio; pi_ext = stp_dbridge[i].ext;
pi_mac = stp_dbridge[i].mac;
} else {
pi_prio = stp_prio; pi_ext = 0;
pi_mac = uip_ethaddr.addr;
}
bridge_to_html();
slen += strtox(outbuf + slen, "\",\"dp\":\"");
byte_to_html(dsg ? (stp_dpid[i] >> 8) : stp_pprio[i]);
byte_to_html(dsg ? stp_dpid[i] : (i + 1));
slen += strtox(outbuf + slen, "\",\"dc\":\"");
pi_u32 = dsg ? stp_dcost[i] : root_bridge_cost; u32hex_html();
slen += strtox(outbuf + slen, "\"},");
}
slen -= 1; // remove comma
slen += strtox(outbuf + slen, "]}");
}
void send_eee(void)
{
dbg_string("send_eee called\nsending EEE status\n");
+1
View File
@@ -16,6 +16,7 @@ void send_mtu(void);
void send_config(void);
void send_cmd_log(void);
void send_lag(void);
void send_stp(void);
void send_vlanlist(void);
/* Convert only the lower nibble to ascii HEX char.
+5
View File
@@ -72,6 +72,9 @@ struct vlan_tag {
#define VLAN_TAG_SIZE (sizeof (struct vlan_tag))
#define RTL_FRAME_TAG_ID 0x8899
#define RTL_FRAME_TAG_VERSION 0x04
/* Bits of the tag's `flags` word, see doc/CpuPort.md. */
#define RTL_TAG_LEARN_DIS 0x0020 /* do not learn the source address from this frame */
#define RTL_TAG_KEEP 0x0080 /* keep the frame's 802.1Q tag format as injected */
// For TX, an 8 byte (plus 4 byte padding when when VLAN is enabled)
// header describing the frame to be moved to the Asic is used
@@ -116,6 +119,8 @@ struct flash_region_t {
extern __xdata char port_names[9][PORT_NAME_SIZE];
extern __xdata bool stp_enabled;
/* System hostname (device identity). Set via `hostname <text>` and the System
* Settings page, reported in /information.json. Other modules (e.g. LLDP, which
* advertises it as the System Name TLV) read it from here. */
+35
View File
@@ -329,6 +329,20 @@ void vlan_setup(void) __banked
}
/*
* Forget the dynamic L2 entries learned on one port.
*/
void port_l2_forget_port(uint8_t port) __banked
{
REG_SET(RTL837x_L2_TBL_FLUSH_CNF, 0x0); /* port-based, dynamic entries */
REG_SET(RTL837x_L2_TBL_FLUSH_CTRL, L2_TBL_FLUSH_EXEC | (((uint16_t)1) << port));
do {
reg_read(RTL837x_L2_TBL_FLUSH_CTRL);
} while (SFR_DATA_16);
}
/*
* Forget all dynamic L2 learned entries
*/
@@ -417,6 +431,27 @@ void port_l2_learned(void) __banked
}
/*
* Static L2 multicast entry for the link-local group 01:80:C2:00:00:<mac_last>
* in VLAN `vid`, with member portmask `pmask` (bit 9 = CPU port).
*/
void port_l2mc_set(uint8_t mac_last, __xdata uint16_t vid, __xdata uint16_t pmask) __banked
{
do {
reg_read(RTL837X_TBL_CTRL);
} while (SFR_DATA_0 & TBL_EXECUTE);
REG_WRITE(RTL837x_TBL_DATA_IN_A, 0xc2, 0x00, 0x00, mac_last);
REG_WRITE(RTL837x_TBL_DATA_IN_B, 0x20 | (vid >> 8) | ((pmask & 0x3) << 6), vid, 0x01, 0x80);
REG_WRITE(RTL837x_TBL_DATA_IN_C, 0, 0, 0, pmask >> 2);
REG_WRITE(RTL837X_TBL_CTRL, 0, 0, TBL_L2_UNICAST, TBL_WRITE | TBL_EXECUTE);
do {
reg_read(RTL837X_TBL_CTRL);
} while (SFR_DATA_0 & TBL_EXECUTE);
}
/*
* Basic L2 configuration such as time to forget an entry
*/
+3
View File
@@ -55,6 +55,8 @@ void vlan_name_remove(uint16_t vlan) __banked;
void vlan_setup(void) __banked;
void port_pvid_set(uint8_t port, __xdata uint16_t pvid) __banked;
uint16_t port_pvid_get(uint8_t port) __banked;
void port_l2mc_set(uint8_t mac_last, __xdata uint16_t vid, __xdata uint16_t pmask) __banked;
void port_l2_forget_port(uint8_t port) __banked;
void vlan_create(void) __banked;
void vlan_delete(uint16_t vlan) __banked;
void vlan_dump(void) __banked;
@@ -76,6 +78,7 @@ void port_eee_status(uint8_t port) __banked;
void print_port_ingress_filter_mode(vlan_ingress_mode_t mode) __banked;
bool port_ingress_vlan_filter_set(uint8_t port, __xdata bool enabled) __banked;
bool port_ingress_vlan_filter_get(uint8_t port) __banked;
vlan_ingress_mode_t port_ingress_filter_get(__xdata uint8_t port) __banked;
void port_isolate(uint8_t port, __xdata uint16_t pmask) __banked;
uint16_t port_isolation_get(uint8_t port) __banked;
+778 -92
View File
@@ -11,6 +11,7 @@
#include "rtl837x_sfr.h"
#include "rtl837x_regs.h"
#include "rtl837x_stp.h"
#include "rtl837x_port.h"
#include "uip.h"
#include "machine.h"
@@ -21,24 +22,73 @@
extern __code struct machine machine;
extern __xdata uint8_t sfr_data[4];
extern __xdata struct machine_runtime machine_detected;
extern __xdata struct uip_eth_addr uip_ethaddr;
extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2];
struct bridge {
uint8_t prio;
uint8_t ext;
uint8_t mac[6];
};
extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE];
extern __xdata uint8_t cmd_words_len;
extern __xdata uint8_t cmd_words_b[15];
extern __xdata char save_cmd; /* 0 while execute_config() replays the saved config */
uint8_t cmd_compare(uint8_t start, __code uint8_t * cmd);
uint8_t atoi_byte(uint8_t idx);
uint8_t cmd_parse_port_separator(uint8_t idx);
extern __xdata uint8_t atoi_results_u8;
/* ---- Configuration ---- */
__xdata uint8_t stp_prio; /* bridge priority high byte (0x80 = 32768) */
__xdata uint8_t stp_hello_s; /* 1-10 s */
__xdata uint8_t stp_maxage_s; /* 6-40 s */
__xdata uint8_t stp_fwddelay_s; /* 4-30 s, also our listen period */
__xdata uint8_t stp_rstp; /* 1 = RST BPDUs, 0 = legacy Config BPDUs */
__xdata uint8_t stp_txhold; /* BPDUs per port per second */
__xdata uint8_t stp_pflags[10];
__xdata uint32_t stp_pcost[10]; /* 0 = auto */
__xdata uint8_t stp_pprio[10];
__xdata uint8_t stp_pp2p[10]; /* admin point-to-point: 0 auto, 1 on, 2 off */
/* Designated bridge, port and cost last heard on the port; stp_bpdu_age tells
* whether they are still current.
*/
__xdata struct bridge stp_dbridge[10];
__xdata uint16_t stp_dpid[10];
__xdata uint32_t stp_dcost[10];
/* ---- Status / runtime ---- */
__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 uint16_t stp_scratch16; /* scratch for status printing only */
__xdata uint8_t port_types[10];
__xdata uint16_t port_timers[10];
__xdata uint16_t port_hello[10];
__xdata uint16_t port_timers[10]; /* listen-period countdown (0 = not listening) */
__xdata uint16_t port_hello[10]; /* hello TX countdown */
__xdata uint16_t stp_bpdu_age[10]; /* ticks since last BPDU seen on port (saturating) */
__xdata uint8_t stp_loop_held[10]; /* port is out of forwarding because a loop was seen on it */
__xdata uint8_t stp_tx_budget[10]; /* tx hold: BPDUs left in the current second */
__xdata uint8_t stp_tx_count[10]; /* BPDUs actually put on the wire, wraps at 256 */
__xdata uint16_t stp_sec_tick; /* 1 s window for the tx budget */
__xdata uint16_t stp_link_prev; /* carrier bitmap as of the last check */
__xdata uint16_t stp_link_now;
__xdata uint8_t stp_scratch;
__xdata uint8_t stp_tx_flags_extra; /* one-shot flags OR-ed into the next BPDU (TCA) */
__xdata uint16_t stp_rxlen; /* received frame length, saved before uip_len is consumed */
__xdata uint8_t stp_msg_age; /* message age of the root info we hold, seconds */
__xdata uint16_t stp_tc_while; /* ticks left to set the TC flag in our BPDUs */
__xdata uint8_t stp_i;
__xdata uint32_t stp_cost_scratch;
__xdata uint8_t stp_loop_peer; /* the other own port seen on a looped segment */
#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 {
uint8_t stp_addr[6];
@@ -61,6 +111,7 @@ struct stp_pkt {
uint16_t age_max;
uint16_t hello;
uint16_t fwd_delay;
uint8_t version1_length; /* RST BPDU only: length of the (empty) v1 part */
};
struct stp_pkt_in {
@@ -85,18 +136,126 @@ struct stp_pkt_in {
uint16_t age_max;
uint16_t hello;
uint16_t fwd_delay;
uint8_t version1_length; /* RST BPDU only: length of the (empty) v1 part */
};
#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 FLAG_PROPOSAL 0x02
#define P_DESIGNATED ((STP_I->flags & 0x0c) == 0x0c)
#define P_PROPOSAL (STP_I->flags & FLAG_PROPOSAL)
#define BPDU_VER_STP 0x00
#define BPDU_VER_RSTP 0x02
signed char cmpMAC(__xdata uint8_t *m1, __xdata uint8_t *m2)
#define BPDU_TYPE_CONFIG 0x00
#define BPDU_TYPE_RST 0x02
#define BPDU_TYPE_TCN 0x80
#define BPDU_LEN_CONFIG 0x26 // LLC and a 35 byte body
#define BPDU_LEN_RST 0x27 // LLC and a 36 byte body
#define BPDU_LEN_MIN_HEADER 33 // addresses through bpdu_type
#define BPDU_FLAG_TC 0x01
#define BPDU_FLAG_LEARNING 0x10
#define BPDU_FLAG_FORWARDING 0x20
#define BPDU_FLAG_TCACK 0x80
#define BPDU_ROLE_ROOT (0b10 << 2)
#define BPDU_ROLE_DESIGNATED (0b11 << 2)
/* Console messages name the port on the front panel, not the internal index. */
static void print_port_nl(uint8_t port) __reentrant
{
for (uint8_t i = 0; i < 6; i++) {
print_byte(machine.log_to_phys_port[port]);
write_char('\n');
}
static void print_bridge_id(uint8_t prio, uint8_t ext, __xdata uint8_t *mac) __reentrant
{
print_byte(prio); print_byte(ext); write_char('/');
for (stp_i = 0; stp_i < 6; stp_i++)
print_byte(mac[stp_i]);
}
/* Fixed width columns so the rows line up under the header without a
* formatter. The state indices are the ASIC's own two bits, in the order
* stp_state_set() writes them. */
static __code const char stp_state_txt[] = "off blocklearnfwd ";
static __code const char stp_role_txt[] = "desgroot";
static __code const char stp_edge_txt[] = "no yes ";
static void print_field(__code const char *txt, uint8_t idx, uint8_t width) __reentrant
{
txt += idx * width;
while (width--)
write_char(*txt++);
}
static void stp_status(void)
{
if (!stp_enabled) {
print_string("STP off\n");
return;
}
print_string(stp_rstp ? "STP on, RSTP\n" : "STP on, STP\n");
print_string("bridge ");
print_bridge_id(stp_prio, 0, uip_ethaddr.addr);
print_string("\nroot ");
print_bridge_id(root_bridge.prio, root_bridge.ext, root_bridge.mac);
if (stp_root_port == 0xff) {
print_string(" (this switch)\n");
} else {
print_string(" port ");
print_byte(machine.log_to_phys_port[stp_root_port]);
print_string(" cost ");
print_long(root_bridge_cost);
write_char('\n');
}
print_string("changes ");
print_short(stp_tc_count);
write_char('\n');
print_string("port state role edge tx bpdu\n");
reg_read_m(RTL837X_MSTP_STATES);
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) {
write_char(' ');
print_byte(machine.log_to_phys_port[stp_i]);
print_string(" ");
print_field(stp_state_txt, (sfr_data[3 - (stp_i >> 2)] >> ((stp_i << 1) & 0x7)) & 0x3, 5);
write_char(' ');
print_field(stp_role_txt, stp_i == stp_root_port ? 1 : 0, 4);
write_char(' ');
print_field(stp_edge_txt, stp_pflags[stp_i] & STP_PF_OPEREDGE ? 1 : 0, 4);
write_char(' ');
print_byte(stp_tx_count[stp_i]);
write_char(' ');
stp_scratch16 = stp_bpdu_age[stp_i] / STP_HZ;
itoa(stp_scratch16 > 255 ? 255 : (uint8_t)stp_scratch16);
write_char('\n');
}
}
static void stp_record_designated(uint8_t port) __reentrant
{
stp_dbridge[port].prio = STP_I->bridge.prio;
stp_dbridge[port].ext = STP_I->bridge.ext;
memcpy(stp_dbridge[port].mac, STP_I->bridge.mac, 6);
stp_dpid[port] = ((uint16_t)STP_I->port_prio << 8) | STP_I->port_id;
stp_cost_scratch = STP_I->root_path_cost;
stp_dcost[port] = ((stp_cost_scratch & 0xff) << 24)
| ((stp_cost_scratch & 0xff00) << 8)
| ((stp_cost_scratch >> 8) & 0xff00)
| (stp_cost_scratch >> 24);
}
/* Lexicographic compare of n bytes. A MAC is 6 of them; a Bridge Identifier
* is 8, the two priority octets ahead of the MAC, compared as one unsigned
* number per 802.1D. */
int8_t cmpBytes(__xdata uint8_t *m1, __xdata uint8_t *m2, uint8_t n) __reentrant
{
for (uint8_t i = 0; i < n; i++) {
if (m1[i] == m2[i])
continue;
if (m1[i] < m2[i])
@@ -107,103 +266,448 @@ 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
uip_len = 0;
// MSTPSTP_I_STATES 0x5310
// reg_read_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);
}
reg_read_m(RTL837X_MSTP_STATES);
stp_scratch = 3 - (port >> 2);
sfr_data[stp_scratch] &= ~(uint8_t)(0b11 << ((port << 1) & 0x7));
sfr_data[stp_scratch] |= (uint8_t)(state << ((port << 1) & 0x7));
reg_write_m(RTL837X_MSTP_STATES);
}
void stp_cnf_send(uint8_t port)
/* Signal a topology change. Edge ports are exempt. */
static void stp_topology_change(uint8_t port) __reentrant
{
if (stp_pflags[port] & STP_PF_OPEREDGE)
return;
stp_tc_count++;
stp_tc_while = ((uint16_t)stp_maxage_s + stp_fwddelay_s) * STP_HZ;
port_l2_forget_port(port);
}
/* Hold one port out of forwarding because a loop was seen on it, and keep
* holding it for as long as the caller keeps saying so. The caller is the
* port that won the Port ID compare (see stp_in) - a different port than
* the one held, except when the frame came back on the port it left.
*/
static void stp_loop_hold_peer(uint8_t port) __reentrant
{
if (port < machine.min_port || port > machine.max_port)
return;
if (!(stp_pflags[port] & STP_PF_ENABLED))
return;
if (stp_pflags[port] & STP_PF_TRIPPED)
return;
if (!port_timers[port]) {
print_string("STP: loop detected, blocking port ");
print_port_nl(port);
stp_state_set(port, 0b01);
stp_pflags[port] &= ~STP_PF_OPEREDGE;
stp_topology_change(port);
}
stp_loop_held[port] = 1;
port_timers[port] = (uint16_t)stp_fwddelay_s * STP_HZ;
}
/* 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;
stp_msg_age = 0;
}
void stp_cnf_send(uint8_t port) __reentrant
{
/* A one-shot flag (TCA) belongs to the BPDU we were asked to send: drop
* it with the frame, or it would surface on an unrelated port later. */
if (!(stp_pflags[port] & STP_PF_ENABLED) || (stp_pflags[port] & (STP_PF_FILTER | STP_PF_TRIPPED))) {
stp_tx_flags_extra = 0;
return;
}
if (!stp_tx_budget[port]) { /* tx hold count exhausted for this second */
stp_tx_flags_extra = 0;
return;
}
stp_tx_budget[port]--;
stp_tx_count[port]++;
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->rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID);
STP_O->rtl_tag.version = RTL_FRAME_TAG_VERSION;
STP_O->rtl_tag.reason = 0x00;
STP_O->rtl_tag.flags = 0x0020; // Disable L2 learning
STP_O->rtl_tag.flags = HTONS(RTL_TAG_LEARN_DIS);
STP_O->rtl_tag.pmask = HTONS(((uint16_t)1) << port);
STP_O->msg_len = HTONS(0x27);
STP_O->dsap = 0x42;
STP_O->ssap = 0x42;
STP_O->ctrl = 0x03;
STP_O->proto = 0x0000;
STP_O->version = 0x02; // RSTP
STP_O->bpdu_type = 0x00; // Config
STP_O->flags = 0x81;
if (stp_rstp) {
STP_O->msg_len = HTONS(BPDU_LEN_RST);
STP_O->version = BPDU_VER_RSTP;
STP_O->bpdu_type = BPDU_TYPE_RST;
reg_read_m(RTL837X_MSTP_STATES);
STP_O->flags = port == stp_root_port ? BPDU_ROLE_ROOT : BPDU_ROLE_DESIGNATED;
if (((sfr_data[3 - (port >> 2)] >> ((port << 1) & 0x7)) & 0b11) == 0b11)
STP_O->flags |= BPDU_FLAG_LEARNING | BPDU_FLAG_FORWARDING;
} else {
STP_O->msg_len = HTONS(BPDU_LEN_CONFIG);
STP_O->version = BPDU_VER_STP;
STP_O->bpdu_type = BPDU_TYPE_CONFIG;
STP_O->flags = 0x00;
}
if (stp_tc_while)
STP_O->flags |= BPDU_FLAG_TC;
STP_O->flags |= stp_tx_flags_extra;
stp_tx_flags_extra = 0;
memcpy(STP_O->src_addr, uip_ethaddr.addr, 6);
STP_O->src_addr[0] |= 0x02;
STP_O->src_addr[5] = (uip_ethaddr.addr[5] & 0xf0) | port;
memcpy(STP_O->root.mac, root_bridge.mac, 6);
memcpy(STP_O->bridge.mac, uip_ethaddr.addr, 6);
STP_O->root.prio = root_bridge.prio;
STP_O->root.ext = 0x00;
STP_O->root_path_cost = 0x00000000;
STP_O->root.ext = root_bridge.ext;
/* 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->port_prio = 0x80;
STP_O->port_id = port;
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->hello = 2;
STP_O->fwd_delay = 0x0f;
STP_O->port_prio = stp_pprio[port];
STP_O->port_id = port + 1;
/* Message age, incremented by one second per bridge we relay through.
* The timer fields are in 1/256 s on the wire, and sdcc stores uint16
* little-endian, so assigning the plain second count lands the value in
* the high (seconds) octet - see age_max/hello/fwd_delay below. */
STP_O->age = (stp_root_port == 0xff) ? 0 : (uint16_t)(stp_msg_age + 1);
STP_O->age_max = stp_maxage_s;
STP_O->hello = stp_hello_s;
STP_O->fwd_delay = stp_fwddelay_s;
STP_O->version1_length = 0; /* RST BPDU: no version-1 information */
// uip_len = 0x27 + sizeof(struct rtl_tag);
uip_len = sizeof(struct stp_pkt);
uip_len = stp_rstp ? sizeof(struct stp_pkt) : sizeof(struct stp_pkt) - 1;
tcpip_output();
}
void stp_in(void) __banked
{
uint8_t port;
if (uip_len < BPDU_LEN_MIN_HEADER) {
uip_len = 0;
return;
}
stp_rxlen = uip_len;
// 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;
port = stp_scratch;
// 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;
if (!((STP_I->version >= BPDU_VER_RSTP && STP_I->bpdu_type == BPDU_TYPE_RST)
|| (STP_I->version == BPDU_VER_STP
&& (STP_I->bpdu_type == BPDU_TYPE_CONFIG
|| STP_I->bpdu_type == BPDU_TYPE_TCN))))
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_port_nl(port);
stp_pflags[port] |= STP_PF_TRIPPED;
stp_state_set(port, 0b00);
stp_tc_count++;
return;
}
stp_bpdu_age[port] = 0;
/* A port that hears a BPDU is not an edge port, whatever it decided
* during the silence after the link came up. Only the flag is dropped:
* the port keeps whatever forwarding state the rules below give it,
* rather than being pushed back through the listen period, which would
* black-hole a working link for a forward delay on the first BPDU. The
* flag matters beyond the status page, since stp_topology_change()
* exempts edge ports and so would go on skipping the counter and the
* L2 flush for a port that has a bridge behind it. */
stp_pflags[port] &= ~STP_PF_OPEREDGE;
if (STP_I->bpdu_type == BPDU_TYPE_TCN) {
stp_tx_flags_extra = BPDU_FLAG_TCACK;
stp_cnf_send(port);
uip_len = 0;
stp_topology_change(port);
return;
}
/* Everything below reads the full Config/RST body. */
if (stp_rxlen < 64)
return;
/* Our own BPDU coming back: two of our ports sit on one segment. Only
* the one with the worse Port ID stops forwarding, and only the other
* one writes that state, so the two never race each other.
*/
if (cmpBytes(STP_I->bridge.mac, uip_ethaddr.addr, 6) == 0) {
/* Equal means the frame came back on the port it left: a loop
* further out, behind an unmanaged switch. There is no pair to
* pick from, so that port holds itself down - and since it can
* only re-arm while it is receiving, that case degrades to the
* forward-delay pulse we had before rather than a real latch.
* The peer's number is validated by the callee, not here. */
stp_loop_peer = STP_I->port_id; /* 1-based, as we send it */
if (!stp_loop_peer)
return;
stp_loop_peer--;
/* A Port ID is (priority, number) and priority is compared
* first - stp_cnf_send() puts stp_pprio[] on the wire next to
* the number, so "stp port N prio" has to be able to decide
* which end of a looped pair keeps forwarding. Comparing the
* number alone would quietly ignore it. */
if (STP_I->port_prio != stp_pprio[port]) {
if (STP_I->port_prio < stp_pprio[port])
return; /* peer is better: it decides */
} else if (stp_loop_peer < port) {
return;
}
stp_loop_hold_peer(stp_loop_peer);
return;
}
/* Topology Change in transit. The flag arms a short window that our
* own BPDUs copy downstream (the TX side already sends TC while
* stp_tc_while runs) and that each further flagged BPDU refreshes, so
* it expires one hello after the neighbour stops - without shortening
* the long window a local change may have armed. The flush runs once,
* on the arming edge: everything learned on the other non-edge ports
* may sit behind the moved link and must be relearned. */
if (STP_I->flags & BPDU_FLAG_TC) {
if (!stp_tc_while) {
uint8_t i;
stp_tc_count++;
for (i = machine.min_port; i <= machine.max_port; i++)
if (i != port && (stp_pflags[i] & STP_PF_ENABLED)
&& !(stp_pflags[i] & STP_PF_OPEREDGE))
port_l2_forget_port(i);
}
if (stp_tc_while < ((uint16_t)stp_hello_s + 1) * STP_HZ)
stp_tc_while = ((uint16_t)stp_hello_s + 1) * STP_HZ;
}
stp_record_designated(port);
/* Better root than the one we know? The identifier is priority, system
* ID extension and MAC in that order: comparing the priority byte and
* then jumping to the MAC skipped the twelve bits in between, so two
* bridges differing only in the extension were ranked by MAC. */
if (cmpBytes((__xdata uint8_t *)&STP_I->root, (__xdata uint8_t *)&root_bridge, 8) < 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_port_nl(port);
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) {
/* Age of the information we now hold (see the TX note on the wire
* format); saturate rather than wrap on absurd input. */
stp_msg_age = (STP_I->age > 254) ? 254 : (uint8_t)STP_I->age;
root_bridge_cost = stp_dcost[port] + PCOST(port);
}
}
void stp_timers(void) __banked
{
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
port_hello[i]--;
if (!port_hello[i]) {
port_hello[i] = TIME_HELLO;
print_string("STP_HELLO port ");
print_byte(i); write_char('\n');
stp_cnf_send(i);
/* Refill the per-port tx budgets once per second (tx hold count) */
if (++stp_sec_tick >= STP_HZ) {
stp_sec_tick = 0;
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++)
stp_tx_budget[stp_i] = stp_txhold;
/* Link supervision. Without this the state machine never learns
* that a port lost carrier: it keeps the port in forwarding, keeps
* announcing on it, and never flushes what was learned behind it -
* yet losing a link is the most ordinary topology change there is.
* Once per second is soon enough, and it keeps register reads out
* of the 50 Hz tick. */
reg_read_m(RTL837X_REG_LINKS_STS);
stp_link_now = (uint16_t)sfr_data[1] | ((uint16_t)sfr_data[2] << 8);
if (stp_link_now != stp_link_prev) {
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) {
if (!(stp_pflags[stp_i] & STP_PF_ENABLED))
continue;
if (!((stp_link_now ^ stp_link_prev) >> stp_i & 1))
continue;
/* Either way the port must stop forwarding first. */
stp_state_set(stp_i, 0b01);
if ((stp_link_now >> stp_i) & 1) {
/* Carrier back: re-run the listen period rather than
* forwarding straight away - the segment may have been
* rewired while we were down. Auto edge still applies. */
port_timers[stp_i] = (uint16_t)stp_fwddelay_s * STP_HZ;
stp_pflags[stp_i] &= ~STP_PF_OPEREDGE;
stp_bpdu_age[stp_i] = 0;
} else {
port_timers[stp_i] = 0;
print_string("STP: link down, port blocking ");
print_port_nl(stp_i);
stp_topology_change(stp_i);
}
}
stp_link_prev = stp_link_now;
}
}
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) {
if (!(stp_pflags[stp_i] & STP_PF_ENABLED))
continue;
if (stp_bpdu_age[stp_i] < 0xffff)
stp_bpdu_age[stp_i]++;
/* Periodic hello */
if (port_hello[stp_i])
port_hello[stp_i]--;
if (!port_hello[stp_i]) {
port_hello[stp_i] = (uint16_t)stp_hello_s * STP_HZ;
/* Only designated ports announce periodically: the root port is
* where our own root information comes FROM, and echoing it back
* there just feeds the upstream bridge its own data (and looks
* like a competing designated bridge on that segment). */
if (stp_i != stp_root_port)
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_loop_held[stp_i] = 0;
stp_state_set(stp_i, 0b11);
print_string("STP: port forwarding ");
print_port_nl(stp_i);
stp_topology_change(stp_i);
} else if ((stp_pflags[stp_i] & STP_PF_AUTOEDGE)
&& !stp_loop_held[stp_i]
&& 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_port_nl(stp_i);
}
}
}
if (stp_tc_while)
stp_tc_while--;
/* 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; /* high byte of the priority: 0x8000 is 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_tc_while = 0;
stp_claim_root();
}
/*
* Steer BPDUs while STP runs, and restore flooding when it stops.
* Changing a port's PVID while STP runs needs "stp off" then "stp on".
*/
static void stp_fdb_update(__xdata uint16_t pmask)
{
uint16_t stp_fdb_vid;
uint8_t stp_fdb_i;
/* Unlike LACPDUs (always untagged, so per-PVID entries suffice), BPDUs
* can arrive VLAN-tagged and then classify into the tag's VID - cover
* every VLAN that exists in the VLAN table, plus every port's PVID for
* the untagged case. A duplicate VID just overwrites the same slot. */
for (stp_fdb_vid = 1; stp_fdb_vid < 4095; stp_fdb_vid++) {
if (vlan_get(stp_fdb_vid) < 0)
continue;
if (!(sfr_data[0] & 0x02)) /* bit 1: VLAN table entry valid */
continue;
port_l2mc_set(0x00, stp_fdb_vid, pmask);
}
for (stp_fdb_i = machine.min_port; stp_fdb_i <= machine.max_port; stp_fdb_i++) {
stp_fdb_vid = port_pvid_get(stp_fdb_i);
port_l2mc_set(0x00, stp_fdb_vid, pmask);
}
}
@@ -211,34 +715,216 @@ void stp_setup(void) __banked
{
print_string("Enabling STP: ");
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++) {
// Set STP port state to blocking
// States are: 00 disable, 01 blocking, 10 learning, 11 forwarding
uint8_t bit_mask = 0b01 << ( (i << 1) & 0x7);
sfr_data[3 - (i >> 2)] |= bit_mask;
port_hello[i] = TIME_HELLO;
port_timers[i] = 0xa00; // 10 sec in blocking state
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) {
stp_pflags[stp_i] &= ~(STP_PF_OPEREDGE | STP_PF_TRIPPED);
stp_loop_held[stp_i] = 0;
stp_bpdu_age[stp_i] = 0;
stp_tx_budget[stp_i] = stp_txhold;
stp_tx_count[stp_i] = 0;
if (!(stp_pflags[stp_i] & STP_PF_ENABLED) || (stp_pflags[stp_i] & STP_PF_ADMEDGE)) {
/* not participating, or admin edge: forwarding immediately */
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;
}
sfr_data[1] |= 0x0f; // Do not block CPU-Port
reg_write_m(RTL837X_MSTP_STATES); // R5310-000d555f
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)
reg_write_m(RTL837X_MSTP_STATES);
print_reg(RTL837X_MSTP_STATES); write_char('\n');
root_bridge.prio = 0x80; // This corresponds to 32768
root_bridge.ext = 0x00;
memcpy(root_bridge.mac, uip_ethaddr.addr, 6);
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) {
if (!(stp_pflags[stp_i] & STP_PF_ENABLED))
continue;
if (port_ingress_filter_get(stp_i) != VLAN_TAGGED)
continue;
print_string("STP: port ");
write_char('0' + machine.log_to_phys_port[stp_i]);
print_string(" admits tagged frames only - BPDUs are untagged and will not arrive\n");
}
/* Seed the carrier bitmap, so turning STP on does not report every
* port that was already down as a fresh topology change. */
reg_read_m(RTL837X_REG_LINKS_STS);
stp_link_prev = (uint16_t)sfr_data[1] | ((uint16_t)sfr_data[2] << 8);
stp_claim_root();
/* Take BPDUs to the CPU only - we are a participating bridge now. */
stp_fdb_update(PMASK_CPU);
}
void stp_off(void) __banked
{
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
// States are: 00 disable, 01 blocking, 10 learning, 11 forwarding
uint8_t bit_mask = 0b11 << ( (i << 1) & 0x7);
sfr_data[3 - (i >> 2)] |= bit_mask;
sfr_data[3 - (stp_i >> 2)] |= (uint8_t)(0b11 << ((stp_i << 1) & 0x7));
stp_pflags[stp_i] &= ~(STP_PF_OPEREDGE | STP_PF_TRIPPED);
stp_loop_held[stp_i] = 0;
port_timers[stp_i] = 0;
}
sfr_data[1] |= 0x0f; // Do not block CPU-Port
sfr_data[1] |= 0x0c; // Do not block the CPU port (bits 3:2 of byte 1 = port 9)
reg_write_m(RTL837X_MSTP_STATES);
/* Restore BPDU transparency: flood them again like an unmanaged switch. */
stp_fdb_update(PMASK_CPU | (machine_detected.isRTL8373 ? PMASK_9 : PMASK_6));
}
void stp_parse(void) __banked __reentrant
{
uint8_t port;
if (cmd_compare(1, "on")) {
print_string("STP enabled\n");
stp_enabled = 1;
stp_setup();
return;
}
if (cmd_compare(1, "off")) {
print_string("STP disabled\n");
stp_off();
stp_enabled = 0;
return;
}
if (cmd_compare(1, "status")) {
stp_status();
return;
}
if (cmd_words_len < 3)
goto err;
if (cmd_compare(1, "port")) {
if (cmd_words_len < 4)
goto err;
if (!cmd_parse_port_separator(cmd_words_b[2]))
goto err;
port = atoi_results_u8;
if (cmd_words_len < 5 && !cmd_compare(3, "on") && !cmd_compare(3, "off"))
goto err;
if (cmd_compare(3, "on")) {
stp_pflags[port] |= STP_PF_ENABLED;
stp_pflags[port] &= ~STP_PF_TRIPPED;
if (stp_enabled) { /* (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 (stp_enabled)
stp_state_set(port, 0b11); /* plain forwarding */
} else if (cmd_compare(3, "edge")) {
/* Also drop the *operational* edge flag: it is what exempts the
* port from topology changes and lets it skip the listen period,
* so leaving it set would keep the old behaviour until the next
* "stp off"/"stp on". An admin edge is operational immediately. */
stp_pflags[port] &= ~(STP_PF_ADMEDGE | STP_PF_AUTOEDGE | STP_PF_OPEREDGE);
if (cmd_compare(4, "on"))
stp_pflags[port] |= STP_PF_ADMEDGE | STP_PF_OPEREDGE;
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")) {
/* raw 802.1D value, 0..200000000; 0 = auto (speed-based) */
stp_cost_scratch = 0;
{
__xdata uint8_t *cp = &cmd_buffer[cmd_words_b[4]];
if (*cp < '0' || *cp > '9')
goto err;
while (*cp >= '0' && *cp <= '9') {
stp_cost_scratch = stp_cost_scratch * 10 + (*cp - '0');
cp++;
}
}
if (stp_cost_scratch > 200000000UL)
goto err;
stp_pcost[port] = stp_cost_scratch;
} else if (cmd_compare(3, "p2p")) {
if (cmd_compare(4, "auto"))
stp_pp2p[port] = 0;
else if (cmd_compare(4, "on"))
stp_pp2p[port] = 1;
else if (cmd_compare(4, "off"))
stp_pp2p[port] = 2;
else
goto err;
} else if (cmd_compare(3, "prio")) {
if (!atoi_byte(cmd_words_b[4]))
goto err;
if (atoi_results_u8 > 240 || (atoi_results_u8 & 0x0f))
goto err;
stp_pprio[port] = atoi_results_u8;
} 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(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;
}
stp_scratch = atoi_results_u8;
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|status | 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");
}
+43 -1
View File
@@ -6,7 +6,49 @@ void stp_in(void) __banked;
void stp_setup(void) __banked;
void stp_timers(void) __banked;
void stp_off(void) __banked;
void stp_parse(void) __banked __reentrant;
void stp_defaults(void) __banked;
#define TIME_HELLO 0x200 // 2 sec
/* Tick rate of stp_timers(), also used by the web UI. */
#define STP_HZ 50
/* Bridge identifier as carried in a BPDU (priority, extension, MAC). */
struct bridge {
uint8_t prio;
uint8_t ext;
uint8_t mac[6];
};
extern __xdata uint8_t stp_prio;
extern __xdata uint8_t stp_hello_s;
extern __xdata uint8_t stp_maxage_s;
extern __xdata uint8_t stp_fwddelay_s;
extern __xdata uint8_t stp_rstp;
extern __xdata uint8_t stp_txhold;
/* 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];
extern __xdata uint8_t stp_pprio[10];
extern __xdata uint8_t stp_pp2p[10];
extern __xdata struct bridge stp_dbridge[10];
extern __xdata uint16_t stp_dpid[10];
extern __xdata uint32_t stp_dcost[10];
extern __xdata uint16_t stp_bpdu_age[10];
extern __xdata struct bridge root_bridge;
extern __xdata uint32_t root_bridge_cost;
extern __xdata uint8_t stp_root_port;
extern __xdata uint16_t stp_tc_count;
#endif
+53 -19
View File
@@ -118,9 +118,10 @@ __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE+2];
__xdata uint16_t rx_packet_vlan;
__xdata uint16_t management_vlan;
__xdata bool frame_tagged;
__xdata uint8_t tx_seq;
__xdata uint8_t stpEnabled;
__xdata bool stp_enabled;
__xdata uint8_t igmpEnabled;
__xdata char hostname[24]; /* device hostname, default set at boot, see rtl837x_common.h */
@@ -204,6 +205,9 @@ struct nonq_frame {
// The output frame structure with 802.1Q field and the padding moved before the buffer-start
#define FRAME_Q ((__xdata struct q_frame *)&uip_buf[0])
// Ether-type of the output frame, which is the RTL tag on a CPU-tagged frame
#define FRAME_ETHERTYPE (*(__xdata uint16_t *)&uip_buf[RTL_FRAME_DESC_SIZE + 2 * sizeof(struct uip_eth_addr)])
void isr_timer0(void) __interrupt(1)
{
}
@@ -651,13 +655,21 @@ void get_random_32(void)
* data will be stored in the rx_header structure
* len is the length of data to be transferred
*/
void nic_rx_header(uint16_t ring_ptr)
bool nic_rx_header(uint16_t ring_ptr)
{
uint16_t buffer = (uint16_t) &rx_headers[0];
uint16_t guard = 0;
SFR_NIC_DATA_U16LE = buffer;
SFR_NIC_RING_U16LE = ring_ptr;
SFR_NIC_CTRL = 1;
do { } while (SFR_NIC_CTRL != 0);
while (SFR_NIC_CTRL != 0) {
if (++guard == 0) {
print_string("NIC: RX header transfer did not complete\n");
return false;
}
}
return true;
}
@@ -667,8 +679,10 @@ void nic_rx_header(uint16_t ring_ptr)
* data will be returned in the xmem buffer points to
* ring_ptr is the current position of the RX Ring on the ASIC side
*/
void nic_rx_packet(uint16_t buffer, uint16_t ring_ptr)
bool nic_rx_packet(uint16_t buffer, uint16_t ring_ptr)
{
uint16_t guard = 0;
SFR_NIC_DATA_U16LE = buffer;
SFR_NIC_RING_U16LE = ring_ptr;
@@ -680,7 +694,13 @@ void nic_rx_packet(uint16_t buffer, uint16_t ring_ptr)
print_short(len);
#endif
SFR_NIC_CTRL = len;
do { } while (SFR_NIC_CTRL != 0);
while (SFR_NIC_CTRL != 0) {
if (++guard == 0) {
print_string("NIC: RX transfer did not complete\n");
return false;
}
}
return true;
}
@@ -690,14 +710,13 @@ void nic_rx_packet(uint16_t buffer, uint16_t ring_ptr)
void nic_tx_packet(uint16_t ring_ptr)
{
uint16_t len;
uint16_t guard = 0;
/* If we have a management VLAN, we have inserted a dot1Q-tag into the frame and
* the frame starts at the beginning of uip_buf with the RTL TX descriptor,
* otherwise the frame is a normal Ethernet frame which starts with
* an RTL TX descriptor being padded at the beginning, in the second case
* we need to skip the padding for the sending of the frame.
/* A frame that got a dot1Q tag was shifted forward over its padding, so it
* starts at uip_buf and carries the q_frame layout. One that did not keeps
* the padding in front and the nonq_frame layout, so the padding is skipped.
*/
if (management_vlan) {
if (frame_tagged) {
SFR_NIC_DATA_U16LE = (uint16_t) uip_buf;
len = FRAME_Q->len;
/*
@@ -724,7 +743,12 @@ void nic_tx_packet(uint16_t ring_ptr)
len += 0xf;
len >>= 3;
SFR_NIC_CTRL = len;
do { } while (SFR_NIC_CTRL != 0);
while (SFR_NIC_CTRL != 0) {
if (++guard == 0) {
print_string("NIC: TX transfer did not complete\n");
return;
}
}
}
@@ -1095,8 +1119,11 @@ void tcpip_output(void)
FRAME->len = uip_len;
FRAME->reserved_2[0] = 0x00; FRAME->reserved_2[1] = 0x00;
// For the management VLAN we insert an 802.1Q VLAN tag
if (management_vlan) {
// For the management VLAN we insert an 802.1Q VLAN tag, but never into a
// CPU-tagged frame, where the ASIC expects its tag right behind the addresses
frame_tagged = false;
if (management_vlan && FRAME_ETHERTYPE != HTONS(RTL_FRAME_TAG_ID)) {
frame_tagged = true;
// Shift the ethernet header before the HW type including the rtl_frame_desc to the beginning of uip_buf
// to allow space to insert the dot 1Q tag
for (uint8_t i = 0; i < sizeof(struct q_frame) - DOT_1Q_TAG_SIZE; i++)
@@ -1130,7 +1157,10 @@ void handle_rx(void)
uint16_t ring_ptr = ((uint16_t)sfr_data[2]) << 8;
ring_ptr |= sfr_data[3];
ring_ptr <<= 3;
nic_rx_header(ring_ptr);
if (!nic_rx_header(ring_ptr)) {
REG_SET(RTL837X_REG_NIC_RXCMD, 1);
return;
}
#ifdef RXTXDBG
__xdata uint8_t *ptr = rx_headers;
print_string("RX on port "); print_byte(rx_headers[3] & 0xf);
@@ -1140,7 +1170,10 @@ void handle_rx(void)
write_char(' ');
}
#endif
nic_rx_packet((uint16_t) &uip_buf[0], ring_ptr + 8);
if (!nic_rx_packet((uint16_t) &uip_buf[0], ring_ptr + 8)) {
REG_SET(RTL837X_REG_NIC_RXCMD, 1);
return;
}
#ifdef RXTXDBG
print_string("\n<< ");
@@ -1161,7 +1194,7 @@ void handle_rx(void)
print_byte(uip_buf[3]); print_byte(uip_buf[4]); print_byte(uip_buf[5]); write_char('\n');
print_string(" MGMT-VLAN: "); print_short(management_vlan); write_char('\n');
#endif
if (stpEnabled && uip_buf[0] == 0x01 && uip_buf[1] == 0x80 && uip_buf[2] == 0xc2 // STP packet?
if (stp_enabled && uip_buf[0] == 0x01 && uip_buf[1] == 0x80 && uip_buf[2] == 0xc2 // STP packet?
&& uip_buf[3] == 0x00 && uip_buf[4] == 0x00 && uip_buf[5] == 0x00) {
stp_in();
if (uip_len) {
@@ -1538,7 +1571,7 @@ void idle(void)
// Check UIP for packets to transmit
handle_tx();
// If STP protocol enabled, decrease STP timers to trigger actions
if (stpEnabled) {
if (stp_enabled) {
if (!stp_clock) {
stp_clock = STP_TICK_DIVIDER;
stp_timers();
@@ -2244,7 +2277,8 @@ void main(void)
REG_SET(RTL837X_REG_SEC_COUNTER, 0x3); write_char(' ');
print_reg(RTL837X_REG_SEC_COUNTER);
#endif
stpEnabled = 0;
stp_enabled = 0;
stp_defaults(); /* 802.1D/w default config before any "stp ..." replay */
nic_setup();
vlan_setup();
port_l2_setup();