mirror of
https://github.com/logicog/RTLPlayground.git
synced 2026-09-02 15:02:51 +08:00
Merge pull request #314 from DrDoof/feat/stp-v2
stp: Spanning Tree support for the RTL837x switches
This commit is contained in:
@@ -28,8 +28,10 @@ only the following features are provided:
|
|||||||
<img width="1420" height="623" alt="GUI" src="doc/images/gui.png" />
|
<img width="1420" height="623" alt="GUI" src="doc/images/gui.png" />
|
||||||
|
|
||||||
While the firmware provides already considerable improvements over the original managed firmware,
|
While the firmware provides already considerable improvements over the original managed firmware,
|
||||||
the firmware still lacks support for STP and the proprietary loop prevention
|
the firmware still lacks support for the proprietary loop prevention
|
||||||
protocols as well as DHCP. If you need these features, do not install the playground on your managed
|
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
|
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
|
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
|
backups and can re-install that firmware in case something is wrong. For this no soldering
|
||||||
|
|||||||
+3
-10
@@ -26,7 +26,8 @@
|
|||||||
#pragma constseg BANK2
|
#pragma constseg BANK2
|
||||||
|
|
||||||
extern __code struct machine machine;
|
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 uint32_t ticks;
|
||||||
extern volatile __xdata uint8_t sfr_data[4];
|
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");
|
print_string("Error: hostname [name] - the name must not contain spaces\n");
|
||||||
}
|
}
|
||||||
} else if (cmd_compare(0, "stp")) {
|
} else if (cmd_compare(0, "stp")) {
|
||||||
if (cmd_compare(1, "on")) {
|
stp_parse();
|
||||||
print_string("STP enabled\n");
|
|
||||||
stpEnabled = 1;
|
|
||||||
stp_setup();
|
|
||||||
} else {
|
|
||||||
print_string("STP disabled\n");
|
|
||||||
stp_off();
|
|
||||||
stpEnabled = 0;
|
|
||||||
}
|
|
||||||
} else if (cmd_compare(0, "pvid")) {
|
} else if (cmd_compare(0, "pvid")) {
|
||||||
if (cmd_words_len == 3 && cmd_parse_port_separator(cmd_words_b[1]) != 0
|
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)
|
&& atoi_short(cmd_words_b[2]) && atoi_results_short && atoi_results_short <= 4094)
|
||||||
|
|||||||
@@ -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
|
checksum and the TCP checksum are automatically calculated (offloaded) by the
|
||||||
ASIC before transmitting on the wire.
|
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
|
||||||
|
|||||||
@@ -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
|
3c:18:a0:7e:11:00 0x0001 learned 5
|
||||||
1c:2a:a3:23:00:02 0x0001 learned 7
|
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
@@ -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 | 1–10 |
|
||||||
|
| `stp maxage <n>` | 20 | 6–40 |
|
||||||
|
| `stp fwd <n>` | 15 | 4–30 |
|
||||||
|
| `stp txhold <n>` | 6 | 1–10 |
|
||||||
|
|
||||||
|
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
@@ -22,6 +22,15 @@ const conf_cmds = [
|
|||||||
/^laghash\s+\d(\s+\w+)+$/,
|
/^laghash\s+\d(\s+\w+)+$/,
|
||||||
/^isolate\s+\d{1,2}(\s+(off|\d{1,2}))+$/,
|
/^isolate\s+\d{1,2}(\s+(off|\d{1,2}))+$/,
|
||||||
/^stp\s+(on|off)$/,
|
/^stp\s+(on|off)$/,
|
||||||
|
/^stp\s+(prio|hello|maxage|fwd|txhold)\s+\d{1,2}$/,
|
||||||
|
/^stp\s+version\s+(rstp|stp)$/,
|
||||||
|
/^stp\s+port\s+\d{1,2}\s+(on|off)$/,
|
||||||
|
/^stp\s+port\s+\d{1,2}\s+edge\s+(on|off|auto)$/,
|
||||||
|
/^stp\s+port\s+\d{1,2}\s+cost\s+\d{1,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)$/,
|
/^igmp\s+(on|off)$/,
|
||||||
/^mtu\s+\d{1,2}\s+\d+$/,
|
/^mtu\s+\d{1,2}\s+\d+$/,
|
||||||
/^bw\s+(in|out)\s+\d{1,2}\s+\S+$/,
|
/^bw\s+(in|out)\s+\d{1,2}\s+\S+$/,
|
||||||
@@ -46,7 +55,8 @@ const conf_overwrite = [
|
|||||||
/^lag\s+\d+\b/,
|
/^lag\s+\d+\b/,
|
||||||
/^laghash\b/,
|
/^laghash\b/,
|
||||||
/^isolate\s+\d{1,2}\b/,
|
/^isolate\s+\d{1,2}\b/,
|
||||||
/^stp\b/,
|
/^stp\s+(prio|hello|maxage|fwd|txhold|version)\b/,
|
||||||
|
/^stp\s+port\s+\d{1,2}\s+(edge|cost|prio|guard|filter|p2p)\b/,
|
||||||
/^igmp\b/,
|
/^igmp\b/,
|
||||||
/^mtu\s+\d{1,2}\b/,
|
/^mtu\s+\d{1,2}\b/,
|
||||||
/^bw\s+(in|out)\s+\d{1,2}\b/,
|
/^bw\s+(in|out)\s+\d{1,2}\b/,
|
||||||
|
|||||||
@@ -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='stat.html' data-i18n='nav_port_stat'>Port Statistics</a></li>"
|
||||||
+ "<li><a href='vlan.html' >VLAN</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='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='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='lag.html' data-i18n='nav_lag'>Link Aggregation</a></li>"
|
||||||
+ "<li><a href='eee.html' data-i18n='nav_eee'>EEE</a></li>"
|
+ "<li><a href='eee.html' data-i18n='nav_eee'>EEE</a></li>"
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
#pragma constseg BANK1
|
#pragma constseg BANK1
|
||||||
|
|
||||||
extern volatile __xdata uint8_t sfr_data[4];
|
extern volatile __xdata uint8_t sfr_data[4];
|
||||||
|
extern volatile __xdata uint32_t ticks;
|
||||||
extern __code uint8_t * __code hex;
|
extern __code uint8_t * __code hex;
|
||||||
extern __code struct f_data f_data[];
|
extern __code struct f_data f_data[];
|
||||||
extern __code char * __code mime_strings[];
|
extern __code char * __code mime_strings[];
|
||||||
@@ -825,6 +826,8 @@ void httpd_appcall(void)
|
|||||||
send_mtu();
|
send_mtu();
|
||||||
} else if (is_word(q, "/lag.json")) {
|
} else if (is_word(q, "/lag.json")) {
|
||||||
send_lag();
|
send_lag();
|
||||||
|
} else if (is_word(q, "/stp.json")) {
|
||||||
|
send_stp();
|
||||||
} 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")) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "phy.h"
|
#include "phy.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "machine.h"
|
#include "machine.h"
|
||||||
|
#include "rtl837x_stp.h"
|
||||||
#include "page_impl.h"
|
#include "page_impl.h"
|
||||||
#include "syslog.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)
|
void send_eee(void)
|
||||||
{
|
{
|
||||||
dbg_string("send_eee called\nsending EEE status\n");
|
dbg_string("send_eee called\nsending EEE status\n");
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ void send_mtu(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_vlanlist(void);
|
void send_vlanlist(void);
|
||||||
|
|
||||||
/* Convert only the lower nibble to ascii HEX char.
|
/* Convert only the lower nibble to ascii HEX char.
|
||||||
|
|||||||
@@ -72,6 +72,9 @@ struct vlan_tag {
|
|||||||
#define VLAN_TAG_SIZE (sizeof (struct vlan_tag))
|
#define VLAN_TAG_SIZE (sizeof (struct vlan_tag))
|
||||||
#define RTL_FRAME_TAG_ID 0x8899
|
#define RTL_FRAME_TAG_ID 0x8899
|
||||||
#define RTL_FRAME_TAG_VERSION 0x04
|
#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)
|
// 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
|
// 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 char port_names[9][PORT_NAME_SIZE];
|
||||||
|
|
||||||
|
extern __xdata bool stp_enabled;
|
||||||
|
|
||||||
/* System hostname (device identity). Set via `hostname <text>` and the System
|
/* System hostname (device identity). Set via `hostname <text>` and the System
|
||||||
* Settings page, reported in /information.json. Other modules (e.g. LLDP, which
|
* Settings page, reported in /information.json. Other modules (e.g. LLDP, which
|
||||||
* advertises it as the System Name TLV) read it from here. */
|
* advertises it as the System Name TLV) read it from here. */
|
||||||
|
|||||||
@@ -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
|
* 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
|
* Basic L2 configuration such as time to forget an entry
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ void vlan_name_remove(uint16_t vlan) __banked;
|
|||||||
void vlan_setup(void) __banked;
|
void vlan_setup(void) __banked;
|
||||||
void port_pvid_set(uint8_t port, __xdata uint16_t pvid) __banked;
|
void port_pvid_set(uint8_t port, __xdata uint16_t pvid) __banked;
|
||||||
uint16_t port_pvid_get(uint8_t port) __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_create(void) __banked;
|
||||||
void vlan_delete(uint16_t vlan) __banked;
|
void vlan_delete(uint16_t vlan) __banked;
|
||||||
void vlan_dump(void) __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;
|
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_set(uint8_t port, __xdata bool enabled) __banked;
|
||||||
bool port_ingress_vlan_filter_get(uint8_t port) __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;
|
void port_isolate(uint8_t port, __xdata uint16_t pmask) __banked;
|
||||||
uint16_t port_isolation_get(uint8_t port) __banked;
|
uint16_t port_isolation_get(uint8_t port) __banked;
|
||||||
|
|
||||||
|
|||||||
+778
-92
@@ -11,6 +11,7 @@
|
|||||||
#include "rtl837x_sfr.h"
|
#include "rtl837x_sfr.h"
|
||||||
#include "rtl837x_regs.h"
|
#include "rtl837x_regs.h"
|
||||||
#include "rtl837x_stp.h"
|
#include "rtl837x_stp.h"
|
||||||
|
#include "rtl837x_port.h"
|
||||||
#include "uip.h"
|
#include "uip.h"
|
||||||
#include "machine.h"
|
#include "machine.h"
|
||||||
|
|
||||||
@@ -21,24 +22,73 @@
|
|||||||
|
|
||||||
extern __code struct machine machine;
|
extern __code struct machine machine;
|
||||||
extern __xdata uint8_t sfr_data[4];
|
extern __xdata uint8_t sfr_data[4];
|
||||||
|
extern __xdata struct machine_runtime machine_detected;
|
||||||
|
|
||||||
|
|
||||||
extern __xdata struct uip_eth_addr uip_ethaddr;
|
extern __xdata struct uip_eth_addr uip_ethaddr;
|
||||||
|
|
||||||
extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2];
|
extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2];
|
||||||
|
|
||||||
struct bridge {
|
extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE];
|
||||||
uint8_t prio;
|
extern __xdata uint8_t cmd_words_len;
|
||||||
uint8_t ext;
|
extern __xdata uint8_t cmd_words_b[15];
|
||||||
uint8_t mac[6];
|
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 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]; /* listen-period countdown (0 = not listening) */
|
||||||
__xdata uint16_t port_timers[10];
|
__xdata uint16_t port_hello[10]; /* hello TX countdown */
|
||||||
__xdata uint16_t port_hello[10];
|
__xdata uint16_t stp_bpdu_age[10]; /* ticks since last BPDU seen on port (saturating) */
|
||||||
|
__xdata uint8_t stp_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 {
|
struct stp_pkt {
|
||||||
uint8_t stp_addr[6];
|
uint8_t stp_addr[6];
|
||||||
@@ -61,6 +111,7 @@ struct stp_pkt {
|
|||||||
uint16_t age_max;
|
uint16_t age_max;
|
||||||
uint16_t hello;
|
uint16_t hello;
|
||||||
uint16_t fwd_delay;
|
uint16_t fwd_delay;
|
||||||
|
uint8_t version1_length; /* RST BPDU only: length of the (empty) v1 part */
|
||||||
};
|
};
|
||||||
|
|
||||||
struct stp_pkt_in {
|
struct stp_pkt_in {
|
||||||
@@ -85,18 +136,126 @@ struct stp_pkt_in {
|
|||||||
uint16_t age_max;
|
uint16_t age_max;
|
||||||
uint16_t hello;
|
uint16_t hello;
|
||||||
uint16_t fwd_delay;
|
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_O ((__xdata struct stp_pkt *)&uip_buf[RTL_FRAME_DESC_SIZE])
|
||||||
#define STP_I ((__xdata struct stp_pkt_in *)&uip_buf[0])
|
#define STP_I ((__xdata struct stp_pkt_in *)&uip_buf[0])
|
||||||
|
|
||||||
#define FLAG_PROPOSAL 0x02
|
#define BPDU_VER_STP 0x00
|
||||||
#define P_DESIGNATED ((STP_I->flags & 0x0c) == 0x0c)
|
#define BPDU_VER_RSTP 0x02
|
||||||
#define P_PROPOSAL (STP_I->flags & FLAG_PROPOSAL)
|
|
||||||
|
|
||||||
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])
|
if (m1[i] == m2[i])
|
||||||
continue;
|
continue;
|
||||||
if (m1[i] < m2[i])
|
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
|
reg_read_m(RTL837X_MSTP_STATES);
|
||||||
uip_len = 0;
|
stp_scratch = 3 - (port >> 2);
|
||||||
// MSTPSTP_I_STATES 0x5310
|
sfr_data[stp_scratch] &= ~(uint8_t)(0b11 << ((port << 1) & 0x7));
|
||||||
// reg_read_m(RTL837X_MSTP_STATES);
|
sfr_data[stp_scratch] |= (uint8_t)(state << ((port << 1) & 0x7));
|
||||||
|
reg_write_m(RTL837X_MSTP_STATES);
|
||||||
print_string("Check BPDU... \n");
|
|
||||||
for (uint8_t i = 0; i < 80; i++) {
|
|
||||||
print_byte(uip_buf[i]);
|
|
||||||
write_char(' ');
|
|
||||||
}
|
|
||||||
write_char('\n');
|
|
||||||
print_byte(STP_I->dsap);
|
|
||||||
print_byte(STP_I->ssap);
|
|
||||||
print_byte(STP_I->ctrl);
|
|
||||||
|
|
||||||
write_char('\n');
|
|
||||||
// Make sure this is the type of RSTP packet we are interested in:
|
|
||||||
if (!(STP_I->dsap == 0x42 && STP_I->ssap == 0x42 && STP_I->ctrl == 0x03))
|
|
||||||
return;
|
|
||||||
print_string("Checking RSTP\n");
|
|
||||||
if (STP_I->proto)
|
|
||||||
return;
|
|
||||||
// write_char('A'); print_byte(STP_I->version); write_char('\n');
|
|
||||||
if (STP_I->version != 2)
|
|
||||||
return;
|
|
||||||
// write_char('B'); print_byte(STP_I->bpdu_type); write_char('\n');
|
|
||||||
if (STP_I->bpdu_type != 2)
|
|
||||||
return;
|
|
||||||
// write_char('\n');
|
|
||||||
// print_string("Flags: "); print_byte(STP_I->flags); write_char('\n');
|
|
||||||
print_string("Check new Root\n");
|
|
||||||
if (STP_I->root.prio < root_bridge.prio
|
|
||||||
|| ((STP_I->root.prio == root_bridge.prio) && cmpMAC(STP_I->root.mac, root_bridge.mac) < 0)) {
|
|
||||||
print_string("Updating Root bridge\n");
|
|
||||||
root_bridge.prio = STP_I->root.prio;
|
|
||||||
memcpy(root_bridge.mac, STP_I->root.mac, 6);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void stp_cnf_send(uint8_t port)
|
/* 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[0] = 0x01; STP_O->stp_addr[1] = 0x80; STP_O->stp_addr[2] = 0xc2;
|
||||||
STP_O->stp_addr[3] = STP_O->stp_addr[4] = STP_O->stp_addr[5] = 0x00;
|
STP_O->stp_addr[3] = STP_O->stp_addr[4] = STP_O->stp_addr[5] = 0x00;
|
||||||
|
|
||||||
STP_O->rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID);
|
STP_O->rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID);
|
||||||
STP_O->rtl_tag.version = RTL_FRAME_TAG_VERSION;
|
STP_O->rtl_tag.version = RTL_FRAME_TAG_VERSION;
|
||||||
STP_O->rtl_tag.reason = 0x00;
|
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->rtl_tag.pmask = HTONS(((uint16_t)1) << port);
|
||||||
|
|
||||||
STP_O->msg_len = HTONS(0x27);
|
|
||||||
STP_O->dsap = 0x42;
|
STP_O->dsap = 0x42;
|
||||||
STP_O->ssap = 0x42;
|
STP_O->ssap = 0x42;
|
||||||
STP_O->ctrl = 0x03;
|
STP_O->ctrl = 0x03;
|
||||||
STP_O->proto = 0x0000;
|
STP_O->proto = 0x0000;
|
||||||
STP_O->version = 0x02; // RSTP
|
if (stp_rstp) {
|
||||||
STP_O->bpdu_type = 0x00; // Config
|
STP_O->msg_len = HTONS(BPDU_LEN_RST);
|
||||||
STP_O->flags = 0x81;
|
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);
|
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->root.mac, root_bridge.mac, 6);
|
||||||
memcpy(STP_O->bridge.mac, uip_ethaddr.addr, 6);
|
memcpy(STP_O->bridge.mac, uip_ethaddr.addr, 6);
|
||||||
|
|
||||||
STP_O->root.prio = root_bridge.prio;
|
STP_O->root.prio = root_bridge.prio;
|
||||||
STP_O->root.ext = 0x00;
|
STP_O->root.ext = root_bridge.ext;
|
||||||
STP_O->root_path_cost = 0x00000000;
|
/* Our root path cost, big-endian (0 while we are the root ourselves) */
|
||||||
|
STP_O->root_path_cost = ((root_bridge_cost & 0xff) << 24)
|
||||||
|
| ((root_bridge_cost & 0xff00) << 8)
|
||||||
|
| ((root_bridge_cost >> 8) & 0xff00)
|
||||||
|
| (root_bridge_cost >> 24);
|
||||||
|
|
||||||
STP_O->bridge.prio = 0x80;
|
STP_O->bridge.prio = stp_prio;
|
||||||
STP_O->bridge.ext = 0x00;
|
STP_O->bridge.ext = 0x00;
|
||||||
|
|
||||||
STP_O->port_prio = 0x80;
|
STP_O->port_prio = stp_pprio[port];
|
||||||
STP_O->port_id = port;
|
STP_O->port_id = port + 1;
|
||||||
STP_O->age = 0x00; // FIXME: This only works because we do not use HTONS and the values are in 1/256 seconds
|
/* Message age, incremented by one second per bridge we relay through.
|
||||||
STP_O->age_max = 20;
|
* The timer fields are in 1/256 s on the wire, and sdcc stores uint16
|
||||||
STP_O->hello = 2;
|
* little-endian, so assigning the plain second count lands the value in
|
||||||
STP_O->fwd_delay = 0x0f;
|
* 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 = stp_rstp ? sizeof(struct stp_pkt) : sizeof(struct stp_pkt) - 1;
|
||||||
uip_len = sizeof(struct stp_pkt);
|
|
||||||
tcpip_output();
|
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
|
void stp_timers(void) __banked
|
||||||
{
|
{
|
||||||
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
|
/* Refill the per-port tx budgets once per second (tx hold count) */
|
||||||
port_hello[i]--;
|
if (++stp_sec_tick >= STP_HZ) {
|
||||||
if (!port_hello[i]) {
|
stp_sec_tick = 0;
|
||||||
port_hello[i] = TIME_HELLO;
|
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++)
|
||||||
print_string("STP_HELLO port ");
|
stp_tx_budget[stp_i] = stp_txhold;
|
||||||
print_byte(i); write_char('\n');
|
|
||||||
stp_cnf_send(i);
|
/* 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: ");
|
print_string("Enabling STP: ");
|
||||||
sfr_data[0] = sfr_data[1] = sfr_data[2] = sfr_data[3] = 0;
|
sfr_data[0] = sfr_data[1] = sfr_data[2] = sfr_data[3] = 0;
|
||||||
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
|
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) {
|
||||||
// Set STP port state to blocking
|
stp_pflags[stp_i] &= ~(STP_PF_OPEREDGE | STP_PF_TRIPPED);
|
||||||
// States are: 00 disable, 01 blocking, 10 learning, 11 forwarding
|
stp_loop_held[stp_i] = 0;
|
||||||
uint8_t bit_mask = 0b01 << ( (i << 1) & 0x7);
|
stp_bpdu_age[stp_i] = 0;
|
||||||
sfr_data[3 - (i >> 2)] |= bit_mask;
|
stp_tx_budget[stp_i] = stp_txhold;
|
||||||
port_hello[i] = TIME_HELLO;
|
stp_tx_count[stp_i] = 0;
|
||||||
port_timers[i] = 0xa00; // 10 sec in blocking state
|
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;
|
||||||
|
}
|
||||||
|
port_hello[stp_i] = (uint16_t)stp_hello_s * STP_HZ;
|
||||||
}
|
}
|
||||||
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); // R5310-000d555f
|
reg_write_m(RTL837X_MSTP_STATES);
|
||||||
|
|
||||||
print_reg(RTL837X_MSTP_STATES); write_char('\n');
|
print_reg(RTL837X_MSTP_STATES); write_char('\n');
|
||||||
|
|
||||||
root_bridge.prio = 0x80; // This corresponds to 32768
|
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) {
|
||||||
root_bridge.ext = 0x00;
|
if (!(stp_pflags[stp_i] & STP_PF_ENABLED))
|
||||||
memcpy(root_bridge.mac, uip_ethaddr.addr, 6);
|
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
|
void stp_off(void) __banked
|
||||||
{
|
{
|
||||||
sfr_data[0] = sfr_data[1] = sfr_data[2] = sfr_data[3] = 0;
|
sfr_data[0] = sfr_data[1] = sfr_data[2] = sfr_data[3] = 0;
|
||||||
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
|
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) {
|
||||||
// Set STP port state to forwarding
|
// Set STP port state to forwarding
|
||||||
// States are: 00 disable, 01 blocking, 10 learning, 11 forwarding
|
// States are: 00 disable, 01 blocking, 10 learning, 11 forwarding
|
||||||
uint8_t bit_mask = 0b11 << ( (i << 1) & 0x7);
|
sfr_data[3 - (stp_i >> 2)] |= (uint8_t)(0b11 << ((stp_i << 1) & 0x7));
|
||||||
sfr_data[3 - (i >> 2)] |= bit_mask;
|
stp_pflags[stp_i] &= ~(STP_PF_OPEREDGE | STP_PF_TRIPPED);
|
||||||
|
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);
|
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
@@ -6,7 +6,49 @@ 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_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
|
#endif
|
||||||
|
|||||||
+53
-19
@@ -118,9 +118,10 @@ __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE+2];
|
|||||||
|
|
||||||
__xdata uint16_t rx_packet_vlan;
|
__xdata uint16_t rx_packet_vlan;
|
||||||
__xdata uint16_t management_vlan;
|
__xdata uint16_t management_vlan;
|
||||||
|
__xdata bool frame_tagged;
|
||||||
__xdata uint8_t tx_seq;
|
__xdata uint8_t tx_seq;
|
||||||
|
|
||||||
__xdata uint8_t stpEnabled;
|
__xdata bool stp_enabled;
|
||||||
__xdata uint8_t igmpEnabled;
|
__xdata uint8_t igmpEnabled;
|
||||||
__xdata char hostname[24]; /* device hostname, default set at boot, see rtl837x_common.h */
|
__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
|
// 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])
|
#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)
|
void isr_timer0(void) __interrupt(1)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -651,13 +655,21 @@ void get_random_32(void)
|
|||||||
* data will be stored in the rx_header structure
|
* data will be stored in the rx_header structure
|
||||||
* len is the length of data to be transferred
|
* 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 buffer = (uint16_t) &rx_headers[0];
|
||||||
|
uint16_t guard = 0;
|
||||||
|
|
||||||
SFR_NIC_DATA_U16LE = buffer;
|
SFR_NIC_DATA_U16LE = buffer;
|
||||||
SFR_NIC_RING_U16LE = ring_ptr;
|
SFR_NIC_RING_U16LE = ring_ptr;
|
||||||
SFR_NIC_CTRL = 1;
|
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
|
* data will be returned in the xmem buffer points to
|
||||||
* ring_ptr is the current position of the RX Ring on the ASIC side
|
* 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_DATA_U16LE = buffer;
|
||||||
SFR_NIC_RING_U16LE = ring_ptr;
|
SFR_NIC_RING_U16LE = ring_ptr;
|
||||||
|
|
||||||
@@ -680,7 +694,13 @@ void nic_rx_packet(uint16_t buffer, uint16_t ring_ptr)
|
|||||||
print_short(len);
|
print_short(len);
|
||||||
#endif
|
#endif
|
||||||
SFR_NIC_CTRL = len;
|
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)
|
void nic_tx_packet(uint16_t ring_ptr)
|
||||||
{
|
{
|
||||||
uint16_t len;
|
uint16_t len;
|
||||||
|
uint16_t guard = 0;
|
||||||
|
|
||||||
/* If we have a management VLAN, we have inserted a dot1Q-tag into the frame and
|
/* A frame that got a dot1Q tag was shifted forward over its padding, so it
|
||||||
* the frame starts at the beginning of uip_buf with the RTL TX descriptor,
|
* starts at uip_buf and carries the q_frame layout. One that did not keeps
|
||||||
* otherwise the frame is a normal Ethernet frame which starts with
|
* the padding in front and the nonq_frame layout, so the padding is skipped.
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
if (management_vlan) {
|
if (frame_tagged) {
|
||||||
SFR_NIC_DATA_U16LE = (uint16_t) uip_buf;
|
SFR_NIC_DATA_U16LE = (uint16_t) uip_buf;
|
||||||
len = FRAME_Q->len;
|
len = FRAME_Q->len;
|
||||||
/*
|
/*
|
||||||
@@ -724,7 +743,12 @@ void nic_tx_packet(uint16_t ring_ptr)
|
|||||||
len += 0xf;
|
len += 0xf;
|
||||||
len >>= 3;
|
len >>= 3;
|
||||||
SFR_NIC_CTRL = len;
|
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->len = uip_len;
|
||||||
FRAME->reserved_2[0] = 0x00; FRAME->reserved_2[1] = 0x00;
|
FRAME->reserved_2[0] = 0x00; FRAME->reserved_2[1] = 0x00;
|
||||||
|
|
||||||
// For the management VLAN we insert an 802.1Q VLAN tag
|
// For the management VLAN we insert an 802.1Q VLAN tag, but never into a
|
||||||
if (management_vlan) {
|
// 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
|
// 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
|
// to allow space to insert the dot 1Q tag
|
||||||
for (uint8_t i = 0; i < sizeof(struct q_frame) - DOT_1Q_TAG_SIZE; i++)
|
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;
|
uint16_t ring_ptr = ((uint16_t)sfr_data[2]) << 8;
|
||||||
ring_ptr |= sfr_data[3];
|
ring_ptr |= sfr_data[3];
|
||||||
ring_ptr <<= 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
|
#ifdef RXTXDBG
|
||||||
__xdata uint8_t *ptr = rx_headers;
|
__xdata uint8_t *ptr = rx_headers;
|
||||||
print_string("RX on port "); print_byte(rx_headers[3] & 0xf);
|
print_string("RX on port "); print_byte(rx_headers[3] & 0xf);
|
||||||
@@ -1140,7 +1170,10 @@ void handle_rx(void)
|
|||||||
write_char(' ');
|
write_char(' ');
|
||||||
}
|
}
|
||||||
#endif
|
#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
|
#ifdef RXTXDBG
|
||||||
print_string("\n<< ");
|
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_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');
|
print_string(" MGMT-VLAN: "); print_short(management_vlan); write_char('\n');
|
||||||
#endif
|
#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) {
|
&& uip_buf[3] == 0x00 && uip_buf[4] == 0x00 && uip_buf[5] == 0x00) {
|
||||||
stp_in();
|
stp_in();
|
||||||
if (uip_len) {
|
if (uip_len) {
|
||||||
@@ -1538,7 +1571,7 @@ void idle(void)
|
|||||||
// Check UIP for packets to transmit
|
// Check UIP for packets to transmit
|
||||||
handle_tx();
|
handle_tx();
|
||||||
// If STP protocol enabled, decrease STP timers to trigger actions
|
// If STP protocol enabled, decrease STP timers to trigger actions
|
||||||
if (stpEnabled) {
|
if (stp_enabled) {
|
||||||
if (!stp_clock) {
|
if (!stp_clock) {
|
||||||
stp_clock = STP_TICK_DIVIDER;
|
stp_clock = STP_TICK_DIVIDER;
|
||||||
stp_timers();
|
stp_timers();
|
||||||
@@ -2244,7 +2277,8 @@ void main(void)
|
|||||||
REG_SET(RTL837X_REG_SEC_COUNTER, 0x3); write_char(' ');
|
REG_SET(RTL837X_REG_SEC_COUNTER, 0x3); write_char(' ');
|
||||||
print_reg(RTL837X_REG_SEC_COUNTER);
|
print_reg(RTL837X_REG_SEC_COUNTER);
|
||||||
#endif
|
#endif
|
||||||
stpEnabled = 0;
|
stp_enabled = 0;
|
||||||
|
stp_defaults(); /* 802.1D/w default config before any "stp ..." replay */
|
||||||
nic_setup();
|
nic_setup();
|
||||||
vlan_setup();
|
vlan_setup();
|
||||||
port_l2_setup();
|
port_l2_setup();
|
||||||
|
|||||||
Reference in New Issue
Block a user