stp: full RSTP and a reworked Spanning Tree page

* stp: select the root port by cost and block redundant links
* stp: move the protocol to BANK4 and keep the command line in BANK2
* stp: implement the RSTP state machines of 802.1D-2004 clause 17
* stp: counters, more settings and a fuller Spanning Tree page
* stp: make a link aggregation group one port of the RSTP tree
* web: serve the Spanning Tree JSON from BANK4 as well

Kept in BANK1, the larger Spanning Tree page would leave under 1 kB
there for the rest of the web server and every page still to come, so
send_stp() and send_stp_counters() move to httpd/page_stp.c in BANK4
with the protocol, as banked calls, with their own copies of the few output
helpers and of the JSON header, so nothing in BANK4 calls into BANK1
directly or hands it a pointer into its code space. BANK4 takes the
free flash between BANK3 and the web files.

KP_9000_6XHML_X2: BANK1 44336, BANK2 36969, BANK3 11942, BANK4 20965
bytes of 49152.
This commit is contained in:
DrDoof authored and d00f committed 2026-10-27 22:11:24 +01:00
1 parent 6c62791d23
commit aff6ac0923
20 files changed
+3266 -680

No files matched your search

+5 -3
View File
@@ -150,10 +150,12 @@ SRCS += \
rtl837x_lldp.c \ rtl837x_lldp.c \
rtl837x_storm.c \ rtl837x_storm.c \
rtl837x_lacp.c \ rtl837x_lacp.c \
rtl837x_trap.c rtl837x_trap.c \
rtl837x_stp_cli.c
SRCS += \ SRCS += \
httpd/httpd.c \ httpd/httpd.c \
httpd/page_impl.c httpd/page_impl.c \
httpd/page_stp.c
SRCS += \ SRCS += \
uip/timer.c \ uip/timer.c \
uip/uip.c \ uip/uip.c \
@@ -246,7 +248,7 @@ $(BUILDDIR)/%.rel: %.asm $(CCFLAGS_STAMP) | create_build_dir
# mv -f $(addprefix $(basename $^), .lst .rel .sym) . # mv -f $(addprefix $(basename $^), .lst .rel .sym) .
$(BUILDDIR)/rtlplayground.ihx: $(OBJS) $(BUILDDIR)/crtbank.rel $(BUILDDIR)/crc16.rel $(BUILDDIR)/rtlplayground.ihx: $(OBJS) $(BUILDDIR)/crtbank.rel $(BUILDDIR)/crc16.rel
$(CC) $(CC_FLAGS) --xram-size 49151 -Wl-bHOME=0x00000 -Wl-bBANK1=0x14000 -Wl-bBANK2=0x24000 -Wl-bBANK3=0x34000 -Wl-r -o $@ $^ $(CC) $(CC_FLAGS) --xram-size 49151 -Wl-bHOME=0x00000 -Wl-bBANK1=0x14000 -Wl-bBANK2=0x24000 -Wl-bBANK3=0x34000 -Wl-bBANK4=0x44000 -Wl-r -o $@ $^
$(BUILDDIR)/rtlplayground.img: $(BUILDDIR)/rtlplayground.ihx $(BUILDDIR)/rtlplayground.img: $(BUILDDIR)/rtlplayground.ihx
objcopy --input-target=ihex -O binary $< $@ objcopy --input-target=ihex -O binary $< $@
+142 -50
View File
@@ -2,9 +2,11 @@
The switch can take part in a spanning tree (IEEE 802.1D / 802.1w) so that 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 redundant links between bridges are blocked instead of forming a loop. The
implementation elects a root bridge from the BPDUs it receives, promotes ports implementation elects a root bridge from the BPDUs it receives, picks the
to forwarding once their listen period expires, ages the root out when it goes cheapest path to it as the root port, blocks ports on which a better bridge is
silent, and blocks a port on which it sees its own BPDU. already designated, promotes ports to forwarding once their listen period
expires, drops information a neighbour stopped sending, 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, STP can be enabled and controlled via the web interface or the command line,
as follows: as follows:
@@ -64,32 +66,6 @@ LACPDUs, delivered by forward, stopped arriving on port 3, and BPDUs sent into
the same port, delivered by trap, kept arriving. A port that admits tagged the same port, delivered by trap, kept arriving. A port that admits tagged
frames only therefore takes part in STP. frames only therefore takes part in STP.
## Link aggregation
A LAG (Link Aggregation Group) is handled like an additional port by the STP
protocol, with its own timers and states. The switch devices support up to 4
LAGs, which are shown alongside the physical ports in the STP status. A group
carries its own path cost, priority, edge and guard settings, and gets its own
row on the Spanning Tree page.
```
stp lag 1 cost 10000 # the group decides, not its members
stp lag 1 edge off
```
LAGs are configured via the `lag` command. Once a port is a member of a LAG it
can no longer be configured individually for STP, so `stp port <n>` on a member
tells you which group to configure instead. Membership is re-read from the
aggregation registers once a second, so a group changing under LACP is picked
up without any coordination between the two.
The switch hardware does not handle STP state for a LAG as a whole. State
changes for the members have to be done by the firmware, updating every member
port, which is possible using a single register write. BPDUs go out through the
lowest member and carry the group's own port id. Losing one member of a live
LAG is not a topology change; the logical port only goes down with its last
link.
Port states live in `RTL837X_MSTP_STATES (0x5310)`, two bits per port: Port states live in `RTL837X_MSTP_STATES (0x5310)`, two bits per port:
`00` disabled, `01` blocking, `10` learning, `11` forwarding. A port in `00` disabled, `01` blocking, `10` learning, `11` forwarding. A port in
blocking forwards nothing between ports, but it still sends what the CPU blocking forwards nothing between ports, but it still sends what the CPU
@@ -109,19 +85,80 @@ encodes. All configured values are in seconds:
| `stp fwd <n>` | 15 | 4–30 | | `stp fwd <n>` | 15 | 4–30 |
| `stp txhold <n>` | 6 | 1–10 | | `stp txhold <n>` | 6 | 1–10 |
A port entering the tree spends `fwd` seconds in blocking before it forwards 802.1D also requires `2 * (fwd - 1) >= maxage >= 2 * (hello + 1)`. A
(an edge port skips the wait). Root information is discarded after `maxage` command typed on the console or sent by the web page that would break it is
seconds without a BPDU, and the switch then reclaims the root role. refused and the value stays as it was, so change the values in an order that
keeps the rule at every step (for a larger max age raise the forward delay
first). The web page shows the error, and a refused command is not recorded
in the command history, so saving the configuration does not pick it up. Lines of the saved configuration are taken one by one whatever their
order, and `stp on` checks the result: if the rule is broken it warns and
starts with 2/20/15 instead.
The values above are what the switch uses while it is the root. Otherwise it
takes max age and forward delay from the BPDUs on its root port, as 802.1D
requires, and passes them on in its own BPDUs with the message age one second
higher; the hello time stays its own.
A port entering the tree discards for one forward delay, then learns
addresses without forwarding for a second one, and only then forwards (an
edge port skips the wait). In RSTP mode on a point-to-point link
(`p2p auto` or `on`) it can go faster: a designated port that is still
discarding sets the proposal flag, and forwards as soon as the bridge on the
other side answers with an agreement. When the root port receives a proposal,
the switch first puts its other non-edge designated ports back to discarding
(they propose in turn), then forwards the root port and answers with an
agreement; this sync happens once per root port, later proposals are only
answered. An alternate or backup port that receives a proposal does the same
sync once for the information it holds and answers with an agreement in a
BPDU with the alternate role, so the bridge on the other side does not have
to wait out its timers; it stays discarding itself. An agreement is accepted
from a root or an alternate port on the other side. A new root port forwards at once, after every port that was root within
the last forward delay has been put back to discarding; such a recent root
port stays closed, even to an agreement, until that forward delay is over.
A new root port that was a backup port within the last two hello times does
not take the fast path and waits for its timers.
A designated port that hears worse information from a bridge that still
claims to be designated and learning or forwarding on the same link has a
dispute: usually one direction of the link is lost and the other bridge does
not hear this switch's BPDUs. The port goes back to discarding, and every
further such BPDU restarts its listen period, so it does not forward while
the dispute lasts.
With `stp version rstp` each port still talks 802.1D to a neighbour that
does: once a Config BPDU or TCN arrives and the migrate time (3 s since the
port came up or last switched) is over, the port sends Config BPDUs, uses
the 802.1D TC window and reports changes with TCN if it is the root port. An
RST BPDU after the migrate time switches it back, and so does a link that
comes up again or `stp port <n> mcheck`. Any worse information heard on a designated port, disputed or
not, is answered with a BPDU at once instead of at the next hello, so the
other bridge learns sooner that it is not the designated one. Information heard on a port stays
valid for three of the sender's hello times; a BPDU whose message age has
reached its max age is not used at all. When the root port's information
expires the switch moves to the next best port, or reclaims the root role if
there is none.
Among ports offering the same root, the lowest root path cost wins, then the
designated bridge, the designated port ID, and finally this switch's own port
ID. A worse BPDU from a different bridge does not replace what a port holds;
a worse one from the same designated bridge and port does.
## Topology changes ## Topology changes
A change on a local non-edge port (the link coming or going, a port promoted A topology change is detected when a non-edge port enters forwarding. That
to forwarding) flushes the addresses learned on it and sets the TC flag in port, and every other forwarding non-edge port, then carries the TC flag in
our BPDUs for `maxage + fwd` seconds. A TC flag received in a BPDU is passed its BPDUs for hello time plus one second (max age plus forward delay with
on: the switch flushes the other non-edge ports once and keeps the flag in `stp version stp`), and each of the other ports flushes the addresses it
its own BPDUs until one hello after the last flagged frame, so the learned. The root port takes part too: while its TC window runs it sends
notification crosses the switch instead of dying at it. A legacy TCN is BPDUs toward the root, so the change reaches the rest of the network. With
acknowledged with TCA and then treated like a local change. `stp version stp` the root port sends TCN instead, until a Config BPDU with
TCA comes back.
A TC flag received on a root or designated port is passed on the same way to
the other ports, not back to the port it came in on. A legacy TCN is
acknowledged with TCA and then treated like a received TC. A port that turns
alternate, loses its link, or is blocked for a loop flushes its own addresses
without raising a topology change, and an edge port never raises one.
## Bridge settings ## Bridge settings
@@ -129,6 +166,8 @@ acknowledged with TCA and then treated like a local change.
stp prio <0-15> # bridge priority = n * 4096, default 8 (32768) stp prio <0-15> # bridge priority = n * 4096, default 8 (32768)
stp version rstp|stp # RST BPDUs (default) or legacy Config BPDUs stp version rstp|stp # RST BPDUs (default) or legacy Config BPDUs
stp hello|maxage|fwd|txhold <seconds> stp hello|maxage|fwd|txhold <seconds>
stp pathcost long|short # automatic port costs, 802.1D-2004 (default) or 802.1D-1998
stp bpdu flood|filter # BPDUs while STP is off: pass them on (default) or drop them
``` ```
The bridge with the lowest priority wins the root election; ties are broken by The bridge with the lowest priority wins the root election; ties are broken by
@@ -136,6 +175,17 @@ 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` existing network, give it a worse priority than the current root — `stp prio 15`
(61440) is the usual "never me" value. (61440) is the usual "never me" value.
A port without a configured cost takes it from its link speed. The long
method uses the 802.1D-2004 values (20000 for 1G, 2000 for 10G); the short
one the 802.1D-1998 table for bridges that still use 16-bit costs (100 for
10M, 19 for 100M, 4 for 1G, 2 for 10G, and 3 for 2.5G and 5G, which that
table does not list). All bridges in a network should use the same method.
`stp bpdu filter` keeps BPDUs from being passed between ports while STP is
off, instead of the default flooding that lets a surrounding spanning tree
run through the switch. It takes effect at once when STP is off, otherwise
when STP is turned off.
## Per-port settings ## Per-port settings
``` ```
@@ -146,6 +196,7 @@ stp port <1-9> prio <0-240> # port priority, steps of 16
stp port <1-9> guard none|bpdu|root stp port <1-9> guard none|bpdu|root
stp port <1-9> filter on|off # neither send nor accept BPDUs stp port <1-9> filter on|off # neither send nor accept BPDUs
stp port <1-9> p2p auto|on|off stp port <1-9> p2p auto|on|off
stp port <1-9> mcheck # try RSTP again on a port that fell back to STP
``` ```
**edge** — an edge port forwards immediately and does not trigger a **edge** — an edge port forwards immediately and does not trigger a
@@ -162,26 +213,67 @@ claims a better priority.
on the far side reacts badly to them (some unmanaged switches with loop 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. prevention cut the link) but you still want STP on the rest of the ports.
## Link aggregation
A LAG (Link Aggregation Group) is one port of the tree, with its own roles,
timers and states. The switch supports up to 4 LAGs; each one takes the place
of its member ports in the STP status and gets its own row on the Spanning
Tree page. A group carries its own path cost, priority, edge, guard and
point-to-point settings, and takes the same commands as a port:
```
stp lag <1-4> on|off|mcheck|edge|cost|prio|guard|filter|p2p ...
stp lag 1 cost 10000 # the group decides, not its members
```
LAGs are configured with the `lag` command. Once a port is a member of a LAG
it can no longer be configured on its own for STP, and `stp port <n>` on a
member names the group to configure instead. Membership is read from the
aggregation registers once a second, so a group changing under LACP is picked
up without any coordination between the two; a port that joins or leaves a
group, and the group itself, start over from discarding. The automatic path
cost of a group is the one of its lowest member that has a link.
The hardware keeps no STP state for a LAG as a whole, so every state change is
written to all member ports, in a single register write. BPDUs go out through
the lowest member that has a link and carry the group's own port id, and a
BPDU received on any member belongs to the group. Losing one member of a live
LAG is not a topology change; the group only goes down with its last link.
## Status ## Status
The Spanning Tree page shows the elected root (priority and MAC), the path cost 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 to it, the root port, the topology-change counter and the time since it last
read from the ASIC together with the configured options. The same data is moved, the max age and forward
available as JSON: delay in use (the root's, when this switch is not the root) and, per port, the
live state read from the ASIC, or "No link", together with the configured
options. For a port taking part in the tree it also shows the designated
bridge, port and cost, and whether it talks RSTP or 802.1D to its neighbour;
the Check button runs `stp port <n> mcheck`. The same data is available as
JSON:
``` ```
GET /stp.json GET /stp.json
``` ```
The `stp status` command prints the same view on the serial console. The Counters section of the same page (or `/stpcnt.json`) shows per port the
BPDUs received and sent, those of them that carried the topology change flag
(TCNs included), and the time since the last BPDU arrived. The counters start
from zero with `stp on`, and `stp clear` resets them; the topology change
counter is not reset.
The `stp status` command prints the same view on the serial console. A port
is reported with one of the roles Root, Designated, Alternate, Backup or
Disabled; Disabled covers a port with STP turned off on it, one shut down by
BPDU guard and one without link.
BPDUs shorter than their type allows, judged by the 802.3 length field, are
dropped: 7 bytes for a TCN, 38 for a Config BPDU and 39 for an RST BPDU,
LLC header included.
## Limitations ## Limitations
* One spanning-tree instance; no MSTP, no per-VLAN trees. * One spanning-tree instance; no MSTP, no per-VLAN trees.
* No proposal/agreement handshake — an RST-capable neighbour will still * A backup port, one that hears the BPDUs of another port of this switch,
converge, but through the timers rather than the fast transition. is blocked and reported as Backup, but keeps sending BPDUs: loop detection
* Port roles are approximated: the root port and designated ports are on that segment relies on them.
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.
+44 -10
View File
@@ -41,6 +41,11 @@ stp_en_d:"Ports start blocked and take up to twice the forward delay to reach fo
stp_dis_q:"Disable spanning tree?",stp_dis_d:"All ports go straight to forwarding; loop protection is lost.", stp_dis_q:"Disable spanning tree?",stp_dis_d:"All ports go straight to forwarding; loop protection is lost.",
stp_cost_err:"Path cost must be 0-200000000", stp_cost_err:"Path cost must be 0-200000000",
lldp_enabled: "LLDP Enabled",lldp_title:"Service Status",lldp_portname:"Name",lldp_enable:"Send Frames", lldp_enabled: "LLDP Enabled",lldp_title:"Service Status",lldp_portname:"Name",lldp_enable:"Send Frames",
stp_pcs:"Path cost",stp_pcs_long:"Long (32 bit)",stp_pcs_short:"Short (16 bit)",
stp_bh:"BPDUs while disabled",stp_bh_flood:"Flood",stp_bh_filter:"Filter",
stp_r4:"Backup",stp_times:"max age / forward delay",stp_proto:"Protocol",stp_nolink:"no link",stp_lasttc:"last",
stp_mcheck:"Check",stp_mcheck_t:"Send RST BPDUs again to find out whether the neighbour still needs 802.1D",
stp_cnt:"Counters",stp_crx:"BPDUs received",stp_ctx:"BPDUs sent",stp_ctrx:"TC received",stp_cttx:"TC sent",stp_cage:"Last BPDU [s]",
st_title:"Port statistics",st_h:"totals since boot",st_txg:"TX good",st_txb:"TX bad",st_rxg:"RX good", st_title:"Port statistics",st_h:"totals since boot",st_txg:"TX good",st_txb:"TX bad",st_rxg:"RX good",
st_rxb:"RX bad",st_details:"Details",st_counters:"MIB counters",st_nonzero:"non-zero only", st_rxb:"RX bad",st_details:"Details",st_counters:"MIB counters",st_nonzero:"non-zero only",
st_autoref:"auto-refresh",st_counter:"Counter",st_value:"Value",st_fail:"failed to load counters", st_autoref:"auto-refresh",st_counter:"Counter",st_value:"Value",st_fail:"failed to load counters",
@@ -834,6 +839,8 @@ var CONF_CMDS=[
/^isolate\s+\d+(\s+(off|\d+))+$/, /^isolate\s+\d+(\s+(off|\d+))+$/,
/^stp\s+(on|off)$/,/^stp\s+(prio|hello|maxage|fwd|txhold)\s+\d+$/, /^stp\s+(on|off)$/,/^stp\s+(prio|hello|maxage|fwd|txhold)\s+\d+$/,
/^stp\s+version\s+(rstp|stp)$/, /^stp\s+version\s+(rstp|stp)$/,
/^stp\s+pathcost\s+(long|short)$/,
/^stp\s+bpdu\s+(filter|flood)$/,
/^stp\s+(port\s+\d+|lag\s+[1-4])\s+(on|off)$/, /^stp\s+(port\s+\d+|lag\s+[1-4])\s+(on|off)$/,
/^stp\s+(port\s+\d+|lag\s+[1-4])\s+edge\s+(on|off|auto)$/, /^stp\s+(port\s+\d+|lag\s+[1-4])\s+edge\s+(on|off|auto)$/,
/^stp\s+(port\s+\d+|lag\s+[1-4])\s+cost\s+\d+$/, /^stp\s+(port\s+\d+|lag\s+[1-4])\s+cost\s+\d+$/,
@@ -1223,12 +1230,14 @@ function fmtBridge(hex){
if(!hex||hex.length<16)return"-"; if(!hex||hex.length<16)return"-";
return parseInt(hex.slice(0,4),16)+" / "+hex.slice(4).replace(/(..)(?=.)/g,"$1:"); return parseInt(hex.slice(0,4),16)+" / "+hex.slice(4).replace(/(..)(?=.)/g,"$1:");
} }
function stpEnt(pt){pt.lag=pt.p>100?pt.p-100:0}
function stpKey(pt){return pt.lag?"L"+pt.lag:String(pt.p)} function stpKey(pt){return pt.lag?"L"+pt.lag:String(pt.p)}
function stpName(pt){return pt.lag?"LAG"+pt.lag+" ("+maskToPorts(pt.mbr).join(",")+")":String(pt.p)} function stpName(pt){return pt.lag?"LAG"+pt.lag:String(pt.p)}
function stpDhms(x){return Math.floor(x/86400)+"d "+Math.floor(x/3600)%24+"h "+Math.floor(x/60)%60+"m "+x%60+"s"}
function stpPre(k){return k.charAt(0)==="L"?"stp lag "+k.slice(1)+" ":"stp port "+k+" "} function stpPre(k){return k.charAt(0)==="L"?"stp lag "+k.slice(1)+" ":"stp port "+k+" "}
function stpBuild(ports){ function stpBuild(ports){
var tb=$("stpcfg").tBodies[0],st=$("stpstat").tBodies[0]; var tb=$("stpcfg").tBodies[0],st=$("stpstat").tBodies[0],ct=$("stpcnt").tBodies[0];
tb.innerHTML="";st.innerHTML=""; tb.innerHTML="";st.innerHTML="";ct.innerHTML="";
ports.forEach(function(pt){ ports.forEach(function(pt){
var p=stpKey(pt); var p=stpKey(pt);
var tr=tb.insertRow(); var tr=tb.insertRow();
@@ -1246,7 +1255,12 @@ function stpBuild(ports){
tr.insertCell().appendChild(h("button",{class:"ctl",text:t("c_apply"),onclick:function(){stpApplyPort(p)}})); tr.insertCell().appendChild(h("button",{class:"ctl",text:t("c_apply"),onclick:function(){stpApplyPort(p)}}));
var sr=st.insertRow(); var sr=st.insertRow();
sr.insertCell().textContent=stpName(pt); sr.insertCell().textContent=stpName(pt);
["ro","st","db","dp","dc","oe","op"].forEach(function(k){sr.insertCell().id="st"+k+p}); ["ro","st","db","dp","dc","oe","op","pv"].forEach(function(k){sr.insertCell().id="st"+k+p});
sr.insertCell().appendChild(h("button",{class:"ctl",id:"stmc"+p,text:t("stp_mcheck"),title:t("stp_mcheck_t"),
onclick:function(){postCmd(stpPre(p)+"mcheck").then(stpLoad).catch(function(){})}}));
var cr=ct.insertRow();
cr.insertCell().textContent=stpName(pt);
["rx","tx","trx","ttx","age"].forEach(function(k){cr.insertCell().id="sc"+k+p});
}); });
} }
function stpPortVals(pt){ function stpPortVals(pt){
@@ -1260,10 +1274,21 @@ function stpPortVals(pt){
p2p:["auto","on","off"][pt.p2]||"auto", p2p:["auto","on","off"][pt.p2]||"auto",
}; };
} }
function stpCnt(c){
c.ports.forEach(function(pt){
stpEnt(pt);
var p=stpKey(pt);
if(!$("scrx"+p))return;
var v=[0,1,2,3].map(function(k){return parseInt(pt.c.slice(8*k,8*k+8),16)}),age=parseInt(pt.c.slice(32,36),16);
["rx","tx","trx","ttx"].forEach(function(k,i){$("sc"+k+p).textContent=v[i]});
$("scage"+p).textContent=!v[0]?"-":(age===0xffff?">":"")+Math.floor(age/c.hz);
});
}
function stpLoad(){ function stpLoad(){
return getJSON("/stp.json").then(function(s){ return getJSON("/stp.json").then(function(s){
s.ports.sort(function(a,b){return((a.lag||0)-(b.lag||0))||(a.p-b.p)}); s.ports.forEach(stpEnt);
var sig=s.ports.map(function(pt){return stpKey(pt)+":"+(pt.mbr||0)}).join(); s.ports.sort(function(a,b){return(a.lag-b.lag)||(a.p-b.p)});
var sig=s.ports.map(stpKey).join();
if(sig!==stpSig){stpSig=sig;stpBuild(s.ports);} if(sig!==stpSig){stpSig=sig;stpBuild(s.ports);}
stpCur=s; stpCur=s;
var en=$("stpen"); var en=$("stpen");
@@ -1271,16 +1296,19 @@ function stpLoad(){
var msg; var msg;
if(!s.on)msg=t("stp_off_msg"); if(!s.on)msg=t("stp_off_msg");
else{ else{
var me=(s.prio*4096).toString(16).padStart(4,"0")+s.myMac; var me=(s.prio*4096).toString(16).padStart(4,"0")+s.myMac,tc=parseInt(s.tc,16),rp="-";
s.ports.forEach(function(pt){if(pt.role===1)rp=stpName(pt)});
msg=t("stp_bridge")+" "+fmtBridge(me)+(s.weRoot msg=t("stp_bridge")+" "+fmtBridge(me)+(s.weRoot
?" ("+t("stp_root_self")+")" ?" ("+t("stp_root_self")+")"
:"; "+t("stp_root")+" "+fmtBridge(s.rootPrio+s.rootMac)+" "+t("stp_via")+" "+s.rootPort+", "+t("stp_cost")+" "+parseInt(s.cost,16)) :"; "+t("stp_root")+" "+fmtBridge(s.rootPrio+s.rootMac)+" "+t("stp_via")+" "+rp+", "+t("stp_cost")+" "+parseInt(s.cost,16))
+"; "+t("stp_tc")+": "+parseInt(s.tc,16); +"; "+t("stp_tc")+": "+tc+(tc?" ("+t("stp_lasttc")+" "+stpDhms(parseInt(s.tcs,16))+")":"")
+"; "+t("stp_times")+" "+s.rMaxage+" / "+s.rFwd+" s";
} }
$("stpids").textContent=msg; $("stpids").textContent=msg;
var bc=$("stpbridge"); var bc=$("stpbridge");
if(!bc.dataset.dirty){ if(!bc.dataset.dirty){
$("stpver").value=s.rstp?"rstp":"stp"; $("stpver").value=s.rstp?"rstp":"stp";
$("stppcs").value=s.pcs?"short":"long";$("stpbh").value=s.bh?"filter":"flood";
$("stpprio").value=String(s.prio); $("stpprio").value=String(s.prio);
$("stphello").value=s.hello;$("stpmaxage").value=s.maxage; $("stphello").value=s.hello;$("stpmaxage").value=s.maxage;
$("stpfwd").value=s.fwd;$("stptxhold").value=s.txhold; $("stpfwd").value=s.fwd;$("stptxhold").value=s.txhold;
@@ -1289,18 +1317,21 @@ function stpLoad(){
var p=stpKey(pt); var p=stpKey(pt);
var trip=(pt.f&STP_PF.TRIP)?" "+badge(t("stp_trip"),"bad"):""; var trip=(pt.f&STP_PF.TRIP)?" "+badge(t("stp_trip"),"bad"):"";
$("stro"+p).textContent=s.on&&pt.role?t("stp_r"+pt.role):"-"; $("stro"+p).textContent=s.on&&pt.role?t("stp_r"+pt.role):"-";
$("stst"+p).innerHTML=s.on?badge(t("stp_s"+pt.st),pt.st===3?"ok":"")+trip:"-"; $("stst"+p).innerHTML=s.on?(pt.lk?badge(t("stp_s"+pt.st),pt.st===3?"ok":""):badge(t("stp_nolink")))+trip:"-";
$("stdb"+p).textContent=s.on?fmtBridge(pt.db):"-"; $("stdb"+p).textContent=s.on?fmtBridge(pt.db):"-";
$("stdp"+p).textContent=s.on?parseInt(pt.dp.slice(0,2),16)+"."+parseInt(pt.dp.slice(2),16):"-"; $("stdp"+p).textContent=s.on?parseInt(pt.dp.slice(0,2),16)+"."+parseInt(pt.dp.slice(2),16):"-";
$("stdc"+p).textContent=s.on?parseInt(pt.dc,16):"-"; $("stdc"+p).textContent=s.on?parseInt(pt.dc,16):"-";
$("stoe"+p).textContent=s.on?t((pt.f&STP_PF.OPEREDGE)?"c_yes":"c_no"):"-"; $("stoe"+p).textContent=s.on?t((pt.f&STP_PF.OPEREDGE)?"c_yes":"c_no"):"-";
$("stop"+p).textContent=s.on?t(pt.p2===2?"c_no":"c_yes"):"-"; $("stop"+p).textContent=s.on?t(pt.p2===2?"c_no":"c_yes"):"-";
$("stpv"+p).textContent=s.on&&pt.role?(s.rstp&&!pt.lg?"RSTP":"STP"):"-";
$("stmc"+p).disabled=!(s.on&&pt.role&&s.rstp);
var row=$("sten"+p).closest("tr"); var row=$("sten"+p).closest("tr");
if(row.dataset.dirty)return; if(row.dataset.dirty)return;
var v=stpPortVals(pt); var v=stpPortVals(pt);
$("sten"+p).checked=v.en;$("sted"+p).value=v.edge;$("stco"+p).value=v.cost; $("sten"+p).checked=v.en;$("sted"+p).value=v.edge;$("stco"+p).value=v.cost;
$("stpr"+p).value=v.prio;$("stgu"+p).value=v.guard;$("stfi"+p).value=v.filter;$("stpp"+p).value=v.p2p; $("stpr"+p).value=v.prio;$("stgu"+p).value=v.guard;$("stfi"+p).value=v.filter;$("stpp"+p).value=v.p2p;
}); });
if($("stpcntd").open)return getJSON("/stpcnt.json").then(stpCnt);
}).catch(function(){}); }).catch(function(){});
} }
function stpApplyPort(p){ function stpApplyPort(p){
@@ -1328,6 +1359,8 @@ $("stpbapply").addEventListener("click",function(){
var ver=$("stpver").value,prio=$("stpprio").value; var ver=$("stpver").value,prio=$("stpprio").value;
if(ver!==(s.rstp?"rstp":"stp"))cmds.push("stp version "+ver); if(ver!==(s.rstp?"rstp":"stp"))cmds.push("stp version "+ver);
if(prio!==String(s.prio))cmds.push("stp prio "+prio); if(prio!==String(s.prio))cmds.push("stp prio "+prio);
if($("stppcs").value!==(s.pcs?"short":"long"))cmds.push("stp pathcost "+$("stppcs").value);
if($("stpbh").value!==(s.bh?"filter":"flood"))cmds.push("stp bpdu "+$("stpbh").value);
[["stphello","hello"],["stpmaxage","maxage"],["stpfwd","fwd"],["stptxhold","txhold"]].forEach(function(f){ [["stphello","hello"],["stpmaxage","maxage"],["stpfwd","fwd"],["stptxhold","txhold"]].forEach(function(f){
var v=$(f[0]).value; var v=$(f[0]).value;
if(String(v)!==String(s[f[1]]))cmds.push("stp "+f[1]+" "+v); if(String(v)!==String(s[f[1]]))cmds.push("stp "+f[1]+" "+v);
@@ -1346,6 +1379,7 @@ $("stpen").addEventListener("change",function(){
var sel=$("stpprio"); var sel=$("stpprio");
for(var i=0;i<16;i++)sel.appendChild(h("option",{value:String(i),text:i+" ("+(i*4096)+")"})); for(var i=0;i<16;i++)sel.appendChild(h("option",{value:String(i),text:i+" ("+(i*4096)+")"}));
})(); })();
$("stpcntd").addEventListener("toggle",function(){if(this.open)stpLoad()});
var stpPoller=new Poller(stpLoad,3000); var stpPoller=new Poller(stpLoad,3000);
tabHooks.stp={enter:function(){stpPoller.start()},leave:function(){stpPoller.stop()}}; tabHooks.stp={enter:function(){stpPoller.start()},leave:function(){stpPoller.stop()}};
+8 -1
View File
@@ -264,6 +264,8 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}
<p class="small mono" id="stpids">-</p> <p class="small mono" id="stpids">-</p>
<div id="stpbridge" style="display:flex;gap:14px;flex-wrap:wrap;align-items:flex-end;margin-top:10px"> <div id="stpbridge" style="display:flex;gap:14px;flex-wrap:wrap;align-items:flex-end;margin-top:10px">
<label><span class="small mut" data-i18n="stp_version">Version</span><br><select class="in" id="stpver"><option value="rstp">RSTP</option><option value="stp" data-i18n="stp_v_stp">STP compatible</option></select></label> <label><span class="small mut" data-i18n="stp_version">Version</span><br><select class="in" id="stpver"><option value="rstp">RSTP</option><option value="stp" data-i18n="stp_v_stp">STP compatible</option></select></label>
<label><span class="small mut" data-i18n="stp_pcs">Path cost</span><br><select class="in" id="stppcs"><option value="long" data-i18n="stp_pcs_long">Long (32 bit)</option><option value="short" data-i18n="stp_pcs_short">Short (16 bit)</option></select></label>
<label><span class="small mut" data-i18n="stp_bh">BPDUs while disabled</span><br><select class="in" id="stpbh"><option value="flood" data-i18n="stp_bh_flood">Flood</option><option value="filter" data-i18n="stp_bh_filter">Filter</option></select></label>
<label><span class="small mut" data-i18n="stp_prio">Bridge priority</span><br><select class="in" id="stpprio"></select></label> <label><span class="small mut" data-i18n="stp_prio">Bridge priority</span><br><select class="in" id="stpprio"></select></label>
<label><span class="small mut" data-i18n="stp_hello">Hello [s]</span><br><input class="in sm" id="stphello" type="number" min="1" max="10"></label> <label><span class="small mut" data-i18n="stp_hello">Hello [s]</span><br><input class="in sm" id="stphello" type="number" min="1" max="10"></label>
<label><span class="small mut" data-i18n="stp_maxage">Max age [s]</span><br><input class="in sm" id="stpmaxage" type="number" min="6" max="40"></label> <label><span class="small mut" data-i18n="stp_maxage">Max age [s]</span><br><input class="in sm" id="stpmaxage" type="number" min="6" max="40"></label>
@@ -281,9 +283,14 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}
</div> </div>
<div class="card"><h2 data-i18n="stp_status">Port status</h2> <div class="card"><h2 data-i18n="stp_status">Port status</h2>
<div class="scrollx"><table class="t" id="stpstat"> <div class="scrollx"><table class="t" id="stpstat">
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="stp_role">Role</th><th data-i18n="c_state">State</th><th data-i18n="stp_db">Designated bridge</th><th data-i18n="stp_dp">Designated port</th><th data-i18n="stp_dc">Designated cost</th><th data-i18n="stp_oedge">Oper. edge</th><th data-i18n="stp_op2p">Oper. P2P</th></tr></thead> <thead><tr><th data-i18n="c_port">Port</th><th data-i18n="stp_role">Role</th><th data-i18n="c_state">State</th><th data-i18n="stp_db">Designated bridge</th><th data-i18n="stp_dp">Designated port</th><th data-i18n="stp_dc">Designated cost</th><th data-i18n="stp_oedge">Oper. edge</th><th data-i18n="stp_op2p">Oper. P2P</th><th data-i18n="stp_proto">Protocol</th><th></th></tr></thead>
<tbody></tbody></table></div> <tbody></tbody></table></div>
</div> </div>
<div class="card"><details id="stpcntd"><summary><h2 style="display:inline" data-i18n="stp_cnt">Counters</h2></summary>
<div class="scrollx"><table class="t" id="stpcnt">
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="stp_crx">BPDUs received</th><th data-i18n="stp_ctx">BPDUs sent</th><th data-i18n="stp_ctrx">TC received</th><th data-i18n="stp_cttx">TC sent</th><th data-i18n="stp_cage">Last BPDU [s]</th></tr></thead>
<tbody></tbody></table></div></details>
</div>
</section> </section>
<section class="tab" id="tab-stats"> <section class="tab" id="tab-stats">
+2
View File
@@ -1055,6 +1055,8 @@ void httpd_appcall(void)
send_lldp(); send_lldp();
} else if (is_word(q, "/lacp.json")) { } else if (is_word(q, "/lacp.json")) {
send_lacp(); send_lacp();
} else if (is_word(q, "/stpcnt.json")) {
send_stp_counters();
} else if (is_word(q, "/vlanlist")) { } else if (is_word(q, "/vlanlist")) {
send_vlanlist(); send_vlanlist();
} else if (is_word(q, "/config")) { } else if (is_word(q, "/config")) {
+1 -137
View File
@@ -600,143 +600,6 @@ 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\":");
j = stp_root_port;
if (j != 0xff && j >= STP_LAG_BASE) {
j = 0;
while (j < STP_LAG_BASE && !((stp_lag_mask[stp_root_port - STP_LAG_BASE] >> j) & 1))
j++;
if (j >= STP_LAG_BASE)
j = 0;
}
itoa_html(j == 0xff ? 0 : machine.log_to_phys_port[j]);
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 = 0; i < STP_ENTITIES; i++) {
if (i < STP_LAG_BASE && !MACHINE_PORT_VALID(i))
continue;
if (i < STP_LAG_BASE && stp_ent_of[i] != i)
continue;
if (i >= STP_LAG_BASE && !stp_lag_mask[i - STP_LAG_BASE])
continue;
slen += strtox(outbuf + slen, "{\"p\":");
itoa_html(i < STP_LAG_BASE ? machine.log_to_phys_port[i] : 0);
slen += strtox(outbuf + slen, ",\"lag\":");
itoa_html(i < STP_LAG_BASE ? 0 : i - STP_LAG_BASE + 1);
slen += strtox(outbuf + slen, ",\"mbr\":");
itoa16_html(i < STP_LAG_BASE ? 0 : stp_lag_mask[i - STP_LAG_BASE]);
slen += strtox(outbuf + slen, ",\"st\":");
j = i;
if (i >= STP_LAG_BASE) {
j = 0;
while (j < STP_LAG_BASE && !((stp_lag_mask[i - STP_LAG_BASE] >> j) & 1))
j++;
if (j >= STP_LAG_BASE)
j = 0;
}
st = (sfr_data[3 - (j >> 2)] >> ((j << 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_lacp(void) void send_lacp(void)
{ {
dbg_string("send_lacp called\n"); dbg_string("send_lacp called\n");
@@ -816,6 +679,7 @@ void send_lldp(void)
char_to_html('}'); char_to_html('}');
} }
void send_eee(void) void send_eee(void)
{ {
dbg_string("send_eee called\nsending EEE status\n"); dbg_string("send_eee called\nsending EEE status\n");
+2 -1
View File
@@ -22,9 +22,10 @@ void send_isolation(void);
void send_config(void); void send_config(void);
void send_cmd_log(void); void send_cmd_log(void);
void send_lag(void); void send_lag(void);
void send_stp(void); void send_stp(void) __banked;
void send_lldp(void); void send_lldp(void);
void send_lacp(void); void send_lacp(void);
void send_stp_counters(void) __banked;
void send_vlanlist(void); void send_vlanlist(void);
/* Convert only the lower nibble to ascii HEX char. /* Convert only the lower nibble to ascii HEX char.
+195
View File
@@ -0,0 +1,195 @@
#include "rtl837x_sfr.h"
#include "rtl837x_common.h"
#include "rtl837x_regs.h"
#include "rtl837x_port.h"
#include "uip.h"
#include <stdint.h>
#include "phy.h"
#include "machine.h"
#include "rtl837x_stp.h"
#include "page_impl.h"
#include "debug.h"
#pragma codeseg BANK4
#pragma constseg BANK4
extern __xdata uint8_t outbuf[TCP_OUTBUF_SIZE];
extern __xdata uint16_t slen;
static __code const uint8_t stp_json_hdr[] = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: application/json\r\n\r\n";
static void stp_bool_html(char c)
{
outbuf[slen++] = c ? '1' : '0';
}
static void stp_byte_html(uint8_t val)
{
outbuf[slen++] = itohex(val >> 4);
outbuf[slen++] = itohex(val);
}
static void stp_itoa_html(uint8_t v)
{
uint8_t t = v / 100;
uint8_t print_zeros = t;
if (print_zeros)
outbuf[slen++] = '0' + t;
t = (v / 10) % 10;
print_zeros |= t;
if (print_zeros)
outbuf[slen++] = '0' + t;
outbuf[slen++] = '0' + (v % 10);
}
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;
stp_byte_html(b[3]);
stp_byte_html(b[2]);
stp_byte_html(b[1]);
stp_byte_html(b[0]);
}
static void bridge_to_html(void)
{
stp_byte_html(pi_prio);
stp_byte_html(pi_ext);
for (uint8_t i = 0; i < 6; i++)
stp_byte_html(pi_mac[i]);
}
void send_stp(void) __banked
{
static __xdata uint8_t i, j, st, dsg;
dbg_string("send_stp called\n");
slen = strtox(outbuf, stp_json_hdr);
slen += strtox(outbuf + slen, "{\"on\":");
stp_bool_html(stp_enabled);
slen += strtox(outbuf + slen, ",\"rstp\":");
stp_bool_html(stp_rstp);
slen += strtox(outbuf + slen, ",\"prio\":");
stp_itoa_html(stp_prio >> 4);
slen += strtox(outbuf + slen, ",\"hello\":");
stp_itoa_html(stp_hello_s);
slen += strtox(outbuf + slen, ",\"maxage\":");
stp_itoa_html(stp_maxage_s);
slen += strtox(outbuf + slen, ",\"fwd\":");
stp_itoa_html(stp_fwddelay_s);
slen += strtox(outbuf + slen, ",\"txhold\":");
stp_itoa_html(stp_txhold);
slen += strtox(outbuf + slen, ",\"pcs\":");
stp_itoa_html(stp_pcost_short);
slen += strtox(outbuf + slen, ",\"bh\":");
stp_itoa_html(stp_bpdu_filter);
slen += strtox(outbuf + slen, ",\"tcs\":\"");
pi_u32 = stp_tc_secs; u32hex_html();
slen += strtox(outbuf + slen, "\",\"rMaxage\":");
stp_itoa_html(stp_root_port == 0xff ? stp_maxage_s : stp_root_maxage);
slen += strtox(outbuf + slen, ",\"rFwd\":");
stp_itoa_html(stp_root_port == 0xff ? stp_fwddelay_s : stp_root_fwd);
slen += strtox(outbuf + slen, ",\"rootPrio\":\"");
stp_byte_html(root_bridge.prio);
stp_byte_html(root_bridge.ext);
slen += strtox(outbuf + slen, "\",\"rootMac\":\"");
for (j = 0; j < 6; j++)
stp_byte_html(root_bridge.mac[j]);
slen += strtox(outbuf + slen, "\",\"myMac\":\"");
for (j = 0; j < 6; j++)
stp_byte_html(uip_ethaddr.addr[j]);
slen += strtox(outbuf + slen, "\",\"cost\":\"");
stp_byte_html(root_bridge_cost >> 24);
stp_byte_html(root_bridge_cost >> 16);
stp_byte_html(root_bridge_cost >> 8);
stp_byte_html(root_bridge_cost);
slen += strtox(outbuf + slen, "\",\"weRoot\":");
stp_bool_html(stp_root_port == 0xff ? 1 : 0);
slen += strtox(outbuf + slen, ",\"tc\":\"");
stp_byte_html(stp_tc_count >> 8);
stp_byte_html(stp_tc_count);
slen += strtox(outbuf + slen, "\",\"ports\":[");
for (i = 0; i < STP_ENTITIES; i++) {
j = stp_ent_id(i);
if (!j)
continue;
slen += strtox(outbuf + slen, "{\"p\":");
stp_itoa_html(j);
slen += strtox(outbuf + slen, ",\"st\":");
st = stp_port_state(i);
stp_itoa_html(st);
slen += strtox(outbuf + slen, ",\"role\":");
stp_itoa_html(stp_port_role(i));
slen += strtox(outbuf + slen, ",\"f\":");
stp_itoa_html(stp_pflags[i]);
slen += strtox(outbuf + slen, ",\"pc\":\"");
pi_u32 = stp_pcost[i]; u32hex_html();
slen += strtox(outbuf + slen, "\",\"prio\":");
stp_itoa_html(stp_pprio[i]);
slen += strtox(outbuf + slen, ",\"p2\":");
stp_itoa_html(stp_pp2p[i]);
slen += strtox(outbuf + slen, ",\"lk\":");
stp_itoa_html((stp_link_prev >> i) & 1);
slen += strtox(outbuf + slen, ",\"lg\":");
stp_itoa_html((stp_legacy >> i) & 1);
dsg = stp_dpid[i] && stp_info_while[i];
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\":\"");
stp_byte_html(dsg ? (stp_dpid[i] >> 8) : stp_pprio[i]);
stp_byte_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_stp_counters(void) __banked
{
uint8_t i, j;
slen = strtox(outbuf, stp_json_hdr);
slen += strtox(outbuf + slen, "{\"on\":");
stp_bool_html(stp_enabled);
slen += strtox(outbuf + slen, ",\"hz\":");
stp_itoa_html(STP_HZ);
slen += strtox(outbuf + slen, ",\"tc\":\"");
stp_byte_html(stp_tc_count >> 8);
stp_byte_html(stp_tc_count);
slen += strtox(outbuf + slen, "\",\"ports\":[");
for (i = 0; i < STP_ENTITIES; i++) {
j = stp_ent_id(i);
if (!j)
continue;
slen += strtox(outbuf + slen, "{\"p\":");
stp_itoa_html(j);
slen += strtox(outbuf + slen, ",\"c\":\"");
for (j = 0; j < STP_CNT_N; j++) {
pi_u32 = stp_cnt[j][i]; u32hex_html();
}
stp_byte_html(stp_bpdu_age[i] >> 8);
stp_byte_html(stp_bpdu_age[i]);
slen += strtox(outbuf + slen, "\"},");
}
slen -= 1;
slen += strtox(outbuf + slen, "]}");
}
+852 -471
View File
File diff suppressed because it is too large. Load diff
+31 -7
View File
@@ -2,13 +2,21 @@
#define _RTL837X_STP_H_ #define _RTL837X_STP_H_
#include <stdint.h> #include <stdint.h>
#include "rtl837x_common.h"
void stp_in(void) __banked; 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; void stp_parse(void) __banked __reentrant;
void stp_defaults(void) __banked; void stp_defaults(void) __banked;
void stp_status(void) __banked;
void stp_port_admin(uint8_t port, uint8_t on) __banked;
void stp_prio_apply(void) __banked;
void stp_port_mcheck(uint8_t port) __banked;
uint8_t stp_port_role(uint8_t port) __banked;
uint8_t stp_port_state(uint8_t port) __banked;
uint8_t stp_ent_id(uint8_t e) __banked;
void stp_lag_map(void) __banked;
void stp_counters_clear(void) __banked __reentrant;
/* Tick rate of stp_timers(), also used by the web UI. */ /* Tick rate of stp_timers(), also used by the web UI. */
#define STP_HZ 50 #define STP_HZ 50
@@ -43,22 +51,38 @@ extern __xdata uint8_t stp_txhold;
#define STP_PF_TRIPPED 0x80 /* runtime: disabled by BPDU guard */ #define STP_PF_TRIPPED 0x80 /* runtime: disabled by BPDU guard */
extern __xdata uint8_t stp_pflags[STP_ENTITIES]; extern __xdata uint8_t stp_pflags[STP_ENTITIES];
extern __xdata uint32_t stp_pcost[STP_ENTITIES]; /* path cost; 0 = auto (20000) */ extern __xdata uint32_t stp_pcost[STP_ENTITIES];
extern __xdata uint8_t stp_pprio[STP_ENTITIES]; extern __xdata uint8_t stp_pprio[STP_ENTITIES];
extern __xdata uint8_t stp_pp2p[STP_ENTITIES]; extern __xdata uint8_t stp_pp2p[STP_ENTITIES];
extern __xdata uint8_t stp_ent_of[STP_PORTS]; /* the STP entity a port answers to: itself, or its lag */ extern __xdata uint8_t stp_ent_of[STP_PORTS];
extern __xdata uint16_t stp_lag_mask[STP_LAG_COUNT]; /* member ports of each lag; 0 = not part of STP */ extern __xdata uint16_t stp_lag_mask[STP_LAG_COUNT];
/* Last-heard designated info per port (from received BPDUs); consult
* stp_bpdu_age to decide whether it is still current. */
extern __xdata struct bridge stp_dbridge[STP_ENTITIES]; extern __xdata struct bridge stp_dbridge[STP_ENTITIES];
extern __xdata uint16_t stp_dpid[STP_ENTITIES]; extern __xdata uint16_t stp_dpid[STP_ENTITIES];
extern __xdata uint32_t stp_dcost[STP_ENTITIES]; extern __xdata uint32_t stp_dcost[STP_ENTITIES];
extern __xdata uint16_t stp_bpdu_age[STP_ENTITIES]; /* ticks since a BPDU was heard */ extern __xdata uint16_t stp_bpdu_age[STP_ENTITIES];
#define STP_CNT_RX 0 /* BPDUs taken in on an STP port */
#define STP_CNT_TX 1 /* BPDUs put on the wire */
#define STP_CNT_TCRX 2 /* BPDUs received with the TC flag, TCNs */
#define STP_CNT_TCTX 3 /* BPDUs sent with the TC flag, TCNs */
#define STP_CNT_N 4
extern __xdata uint32_t stp_cnt[STP_CNT_N][STP_ENTITIES];
extern __xdata uint16_t stp_info_while[STP_ENTITIES];
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;
extern __xdata uint8_t stp_root_port; extern __xdata uint8_t stp_root_port;
extern __xdata uint8_t stp_root_maxage;
extern __xdata uint8_t stp_root_fwd;
extern __xdata uint16_t stp_link_prev;
extern __xdata uint16_t stp_alt;
extern __xdata uint16_t stp_backup;
extern __xdata uint16_t stp_legacy;
extern __xdata uint16_t stp_rrwhile[STP_ENTITIES];
extern __xdata uint16_t stp_rbwhile[STP_ENTITIES];
extern __xdata uint16_t stp_tc_count; extern __xdata uint16_t stp_tc_count;
extern __xdata uint32_t stp_tc_secs;
extern __xdata uint8_t stp_pcost_short;
extern __xdata uint8_t stp_bpdu_filter;
#endif #endif
+228
View File
@@ -0,0 +1,228 @@
/*
* Command line interface of the Spanning Tree Protocol for the RTL837x platform
* This code is in the Public Domain
*/
#include <stdint.h>
#include "rtl837x_common.h"
#include "rtl837x_stp.h"
#include "machine.h"
#pragma codeseg BANK2
#pragma constseg BANK2
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 const 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;
extern __xdata char save_cmd;
extern __xdata uint8_t err_status;
#define TIMES_OK(h, m, f) ((uint16_t)2 * ((f) - 1) >= (m) && (m) >= (uint16_t)2 * ((h) + 1))
__xdata uint32_t stp_cli_cost;
__xdata uint8_t stp_cli_val;
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_compare(1, "clear")) {
stp_counters_clear();
return;
}
if (cmd_words_len < 3)
goto err;
if (cmd_compare(1, "port") || cmd_compare(1, "lag")) {
if (cmd_words_len < 4)
goto err;
stp_lag_map();
if (cmd_compare(1, "lag")) {
if (!atoi_byte(cmd_words_b[2]) || !atoi_results_u8 || atoi_results_u8 > STP_LAG_COUNT)
goto err;
port = STP_LAG_BASE - 1 + atoi_results_u8;
} else {
if (!cmd_parse_port_separator(cmd_words_b[2]))
goto err;
port = atoi_results_u8;
if (stp_ent_of[port] != port) {
print_string("Error: the port is in lag ");
write_char('1' + stp_ent_of[port] - STP_LAG_BASE);
write_char('\n');
err_status = ERR_INVALID_ARGUMENT;
return;
}
}
if (cmd_words_len < 5 && !cmd_compare(3, "on") && !cmd_compare(3, "off")
&& !cmd_compare(3, "mcheck"))
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_port_admin(port, 1);
}
} else if (cmd_compare(3, "mcheck")) {
if (stp_enabled)
stp_port_mcheck(port);
} else if (cmd_compare(3, "off")) {
stp_pflags[port] &= ~STP_PF_ENABLED;
if (stp_enabled)
stp_port_admin(port, 0); /* 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_cli_cost = 0;
{
__xdata uint8_t *cp = &cmd_buffer[cmd_words_b[4]];
if (*cp < '0' || *cp > '9')
goto err;
while (*cp >= '0' && *cp <= '9') {
stp_cli_cost = stp_cli_cost * 10 + (*cp - '0');
cp++;
}
}
if (stp_cli_cost > 200000000UL)
goto err;
stp_pcost[port] = stp_cli_cost;
} 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;
}
if (cmd_compare(1, "pathcost")) {
if (cmd_compare(2, "long"))
stp_pcost_short = 0;
else if (cmd_compare(2, "short"))
stp_pcost_short = 1;
else
goto err;
return;
}
if (cmd_compare(1, "bpdu")) {
if (cmd_compare(2, "filter"))
stp_bpdu_filter = 1;
else if (cmd_compare(2, "flood"))
stp_bpdu_filter = 0;
else
goto err;
if (!stp_enabled)
stp_off();
return;
}
goto err;
}
stp_cli_val = atoi_results_u8;
if (cmd_compare(1, "prio")) {
if (stp_cli_val > 15)
goto err;
stp_prio = stp_cli_val << 4; /* n * 4096, as the BPDU's high byte */
stp_prio_apply();
} else if (cmd_compare(1, "hello")) {
if (stp_cli_val < 1 || stp_cli_val > 10)
goto err;
if (save_cmd && !TIMES_OK(stp_cli_val, stp_maxage_s, stp_fwddelay_s))
goto times;
stp_hello_s = stp_cli_val;
} else if (cmd_compare(1, "maxage")) {
if (stp_cli_val < 6 || stp_cli_val > 40)
goto err;
if (save_cmd && !TIMES_OK(stp_hello_s, stp_cli_val, stp_fwddelay_s))
goto times;
stp_maxage_s = stp_cli_val;
} else if (cmd_compare(1, "fwd")) {
if (stp_cli_val < 4 || stp_cli_val > 30)
goto err;
if (save_cmd && !TIMES_OK(stp_hello_s, stp_maxage_s, stp_cli_val))
goto times;
stp_fwddelay_s = stp_cli_val;
} else if (cmd_compare(1, "txhold")) {
if (stp_cli_val < 1 || stp_cli_val > 10)
goto err;
stp_txhold = stp_cli_val;
} else {
goto err;
}
return;
times:
print_string("Error: needs 2*(fwd-1) >= maxage >= 2*(hello+1)\n");
err_status = ERR_INVALID_ARGUMENT;
return;
err:
err_status = ERR_INVALID_ARGUMENT;
print_string("Error: stp on|off|status|clear | prio <0-15> | hello <1-10> | maxage <6-40> | fwd <4-30> | txhold <1-10> | version rstp|stp | pathcost long|short | bpdu filter|flood | port <1-9>|lag <1-4> on|off|mcheck|edge|cost|prio|guard|filter ...\n");
}
+6
View File
@@ -121,6 +121,12 @@ uint8_t stp_pflags[STP_ENTITIES];
uint8_t stp_pp2p[STP_ENTITIES]; uint8_t stp_pp2p[STP_ENTITIES];
uint8_t stp_pprio[STP_ENTITIES]; uint8_t stp_pprio[STP_ENTITIES];
uint8_t stp_prio = 0x80, stp_root_port = 0xff, stp_rstp = 1, stp_txhold = 6; uint8_t stp_prio = 0x80, stp_root_port = 0xff, stp_rstp = 1, stp_txhold = 6;
uint8_t stp_bpdu_filter, stp_pcost_short, stp_root_fwd = 15, stp_root_maxage = 20;
uint16_t stp_legacy, stp_link_prev = 0x3ff;
uint16_t stp_info_while[STP_ENTITIES];
uint32_t stp_cnt[STP_CNT_N][STP_ENTITIES];
uint32_t stp_tc_secs;
uint8_t stp_ent_id(uint8_t e) { return e >= STP_LAG_BASE ? e + 101 - STP_LAG_BASE : machine.log_to_phys_port[e]; }
/* ---- SFP: no module present, reads fail ---- */ /* ---- SFP: no module present, reads fail ---- */
uint8_t sfp_buf[16]; uint8_t sfp_buf[16];
+21
View File
@@ -0,0 +1,21 @@
CC ?= cc
CFLAGS = -std=c11 -Wall -Wextra -Wno-unknown-pragmas \
-D__xdata= -D__code= -D__banked= -D__reentrant= \
-Ishim -I../..
all: build/stp_sim
./build/stp_sim
verbose: build/stp_sim
./build/stp_sim -v
build/stp_sim: stp_sim.c ../../rtl837x_stp.c ../../rtl837x_stp_cli.c ../../rtl837x_stp.h shim/*.h
mkdir -p build
cp ../../rtl837x_stp.c build/stp_under_test.c
cp ../../rtl837x_stp_cli.c build/stp_cli_under_test.c
$(CC) $(CFLAGS) -o $@ stp_sim.c build/stp_under_test.c build/stp_cli_under_test.c
clean:
rm -rf build
.PHONY: all verbose clean
+12
View File
@@ -0,0 +1,12 @@
#ifndef _SHIM_MACHINE_H_
#define _SHIM_MACHINE_H_
#include <stdint.h>
#define MACHINE_MIN_PORT 0
#define MACHINE_MAX_PORT 8
#define MACHINE_PORT_VALID(p) \
((uint8_t)((p) - MACHINE_MIN_PORT) <= (MACHINE_MAX_PORT - MACHINE_MIN_PORT))
struct machine { uint8_t log_to_phys_port[9]; };
extern const struct machine machine;
struct machine_runtime { uint8_t isRTL8373; };
extern struct machine_runtime machine_detected;
#endif
+50
View File
@@ -0,0 +1,50 @@
#ifndef _SHIM_RTL837X_COMMON_H_
#define _SHIM_RTL837X_COMMON_H_
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
struct rtl_tag {
uint16_t tag;
uint8_t version;
uint8_t reason;
uint16_t flags;
uint16_t pmask; /* TX: port bitmask; RX: 4-bit port number */
};
struct vlan_tag {
uint16_t svlan;
uint16_t vlan;
};
#define RTL_FRAME_TAG_ID 0x8899
#define RTL_FRAME_TAG_VERSION 0x04
#define RTL_FRAME_DESC_SIZE 12
#define RTL_TAG_LEARN_DIS 0x0020
#define PMASK_9 0x1ff
#define PMASK_6 0x1f8
#define PMASK_CPU 0x200
#define CMD_BUF_SIZE 128
#define CPU_PORT 9
#define ERR_OK 0
#define ERR_INVALID_ARGUMENT 3
#define HTONS(n) ((uint16_t)((((uint16_t)(n)) << 8) | (((uint16_t)(n)) >> 8)))
extern bool stp_enabled;
void print_string(char *s);
void print_byte(uint8_t b);
void print_short(uint16_t v);
void print_long(uint32_t v);
void write_char(char c);
void itoa(uint8_t v);
void print_reg(uint16_t reg);
void reg_read_m(uint16_t addr);
void reg_write_m(uint16_t addr);
void tcpip_output(void);
#endif
+11
View File
@@ -0,0 +1,11 @@
#ifndef _SHIM_RTL837X_PORT_H_
#define _SHIM_RTL837X_PORT_H_
#include <stdint.h>
#define VLAN_TAGGED 1
int8_t vlan_get(uint16_t vlan);
uint16_t port_pvid_get(uint8_t port);
void port_l2mc_set(uint8_t mac_last, uint16_t vid, uint16_t pmask);
void port_l2_forget_port(uint8_t port);
uint16_t port_lag_members_get(uint8_t lag);
uint8_t port_ingress_filter_get(uint8_t port);
#endif
+9
View File
@@ -0,0 +1,9 @@
#ifndef _SHIM_RTL837X_REGS_H_
#define _SHIM_RTL837X_REGS_H_
#define RTL837X_MSTP_STATES 0x5310
#define RTL837X_REG_LINKS_STS 0x63E8
#define RTL837X_REG_LINKS 0x63f0
#define RTL837X_REG_LINKS_89 0x63f4
#endif
+6
View File
@@ -0,0 +1,6 @@
#ifndef _SHIM_RTL837X_SFR_H_
#define _SHIM_RTL837X_SFR_H_
extern unsigned char sfr_data[4];
#endif
+8
View File
@@ -0,0 +1,8 @@
#ifndef _SHIM_UIP_H_
#define _SHIM_UIP_H_
#include <stdint.h>
#define UIP_CONF_BUFFER_SIZE 600
struct uip_eth_addr { uint8_t addr[6]; };
extern uint16_t uip_len;
void tcpip_output(void);
#endif
+1633
View File
File diff suppressed because it is too large. Load diff