Merge pull request #397 from DrDoof/feat/stp-lag-entity

stp: treat a link aggregation group as one entity
This commit is contained in:
logicog authored and GitHub committed 2026-09-14 09:32:36 +02:00
commit 434acbcc13
5 files changed
+413 -118

No files matched your search

+26
View File
@@ -58,6 +58,32 @@ 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.
## Link aggregation
A LAG (Link Aggregation Group) is handled like an additional port by the STP
protocol, with its own timers and states. The switch devices support up to 4
LAGs, which are shown alongside the physical ports in the STP status. A group
carries its own path cost, priority, edge and guard settings, and gets its own
row on the Spanning Tree page.
```
stp lag 1 cost 10000 # the group decides, not its members
stp lag 1 edge off
```
LAGs are configured via the `lag` command. Once a port is a member of a LAG it
can no longer be configured individually for STP, so `stp port <n>` on a member
tells you which group to configure instead. Membership is re-read from the
aggregation registers once a second, so a group changing under LACP is picked
up without any coordination between the two.
The switch hardware does not handle STP state for a LAG as a whole. State
changes for the members have to be done by the firmware, updating every member
port, which is possible using a single register write. BPDUs go out through the
lowest member and carry the group's own port id. Losing one member of a live
LAG is not a topology change; the logical port only goes down with its last
link.
Port states live in `RTL837X_MSTP_STATES (0x5310)`, two bits per port:
`00` disabled, `01` blocking, `10` learning, `11` forwarding. A port in
blocking forwards nothing between ports, but it still sends what the CPU
+54 -31
View File
@@ -1020,6 +1020,13 @@ const conf_cmds = [
/^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)$/,
/^stp\s+lag\s+[1-4]\s+(on|off)$/,
/^stp\s+lag\s+[1-4]\s+edge\s+(on|off|auto)$/,
/^stp\s+lag\s+[1-4]\s+cost\s+\d{1,9}$/,
/^stp\s+lag\s+[1-4]\s+prio\s+\d{1,3}$/,
/^stp\s+lag\s+[1-4]\s+guard\s+(none|bpdu|root)$/,
/^stp\s+lag\s+[1-4]\s+filter\s+(on|off)$/,
/^stp\s+lag\s+[1-4]\s+p2p\s+(auto|on|off)$/,
/^igmp\s+(on|off)$/,
/^mtu\s+\d{1,2}\s+\d+$/,
/^bw\s+(in|out)\s+\d{1,2}\s+\S+$/,
@@ -1055,6 +1062,7 @@ const conf_overwrite = [
/^isolate\s+\d{1,2}\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/,
/^stp\s+lag\s+[1-4]\s+(edge|cost|prio|guard|filter|p2p)\b/,
/^igmp\b/,
/^mtu\s+\d{1,2}\b/,
/^bw\s+(in|out)\s+\d{1,2}\b/,
@@ -2601,45 +2609,58 @@ function num(id, min, max, onch) {
return n;
}
function members(mask) {
const a = [];
for (let i = 0; i < numPorts; i++)
if (mask & (1 << i)) a.push(logToPhysPort[i]);
return a.join(",");
}
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)) {
p.n = p.lag ? "lag " + p.lag : "port " + p.p;
p.k = p.lag ? "L" + p.lag : p.p;
const tr = tbl.insertRow();
tr.insertCell().textContent = p.p;
tr.insertCell().appendChild(sel("en_" + p.p,
tr.insertCell().textContent = p.lag
? "LAG" + p.lag + " (" + members(p.mbr) + ")"
: p.p;
tr.insertCell().appendChild(sel("en_" + p.k,
[["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));
e => stpCmd("stp " + p.n + " " + e.target.value)));
const pc = num("cost_" + p.k, 0, 200000000,
e => stpCmd("stp " + p.n + " 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));
const pr = sel("prio_" + p.k, [],
e => stpCmd("stp " + p.n + " 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,
tr.insertCell().appendChild(sel("edge_" + p.k,
[["auto","Auto"],["on","Enable"],["off","Disable"]],
e => stpCmd("stp port " + p.p + " edge " + e.target.value)));
tr.insertCell().appendChild(sel("filt_" + p.p,
e => stpCmd("stp " + p.n + " edge " + e.target.value)));
tr.insertCell().appendChild(sel("filt_" + p.k,
[["off","Disable"],["on","Enable"]],
e => stpCmd("stp port " + p.p + " filter " + e.target.value)));
tr.insertCell().appendChild(sel("guard_" + p.p,
e => stpCmd("stp " + p.n + " filter " + e.target.value)));
tr.insertCell().appendChild(sel("guard_" + p.k,
[["none","None"],["bpdu","BPDU"],["root","Root"]],
e => stpCmd("stp port " + p.p + " guard " + e.target.value)));
tr.insertCell().appendChild(sel("p2p_" + p.p,
e => stpCmd("stp " + p.n + " guard " + e.target.value)));
tr.insertCell().appendChild(sel("p2p_" + p.k,
[["auto","Auto"],["on","Enable"],["off","Disable"]],
e => stpCmd("stp port " + p.p + " p2p " + e.target.value)));
e => stpCmd("stp " + p.n + " p2p " + e.target.value)));
const sr = stat.insertRow();
sr.insertCell().textContent = p.p;
sr.insertCell().textContent = p.lag
? "LAG" + p.lag + " (" + members(p.mbr) + ")"
: p.p;
for (const id of ["st","role","db","dp","dc","oe","op"])
sr.insertCell().id = id + "_" + p.p;
sr.insertCell().id = id + "_" + p.k;
}
stpRows = ports.length;
}
@@ -2672,18 +2693,19 @@ function fetchStp() {
+ " \u2014 topology changes: " + parseInt(s.tc, 16))
: "";
for (const p of s.ports) {
const k = p.lag ? "L" + p.lag : p.p;
const trip = (p.f & PF_TRIPPED) ? " (guard!)" : "";
document.getElementById("st_" + p.p).textContent =
document.getElementById("st_" + k).textContent =
s.on ? STP_STATES[p.st] + trip : "-";
document.getElementById("role_" + p.p).textContent =
document.getElementById("role_" + k).textContent =
s.on ? STP_ROLES[p.role] : "-";
document.getElementById("db_" + p.p).textContent = s.on ? fmtBridgeId(p.db) : "-";
document.getElementById("dp_" + p.p).textContent =
document.getElementById("db_" + k).textContent = s.on ? fmtBridgeId(p.db) : "-";
document.getElementById("dp_" + k).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 =
document.getElementById("dc_" + k).textContent = s.on ? parseInt(p.dc, 16) : "-";
document.getElementById("oe_" + k).textContent =
s.on ? ((p.f & PF_OPEREDGE) ? "True" : "False") : "-";
document.getElementById("op_" + p.p).textContent = s.on ? (p.p2 == 2 ? "False" : "True") : "-";
document.getElementById("op_" + k).textContent = s.on ? (p.p2 == 2 ? "False" : "True") : "-";
}
if (stpDirty)
return;
@@ -2695,15 +2717,16 @@ function fetchStp() {
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 =
const k = p.lag ? "L" + p.lag : p.p;
document.getElementById("en_" + k).value = (p.f & PF_ENABLED) ? "on" : "off";
document.getElementById("edge_" + k).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 =
document.getElementById("cost_" + k).value = parseInt(p.pc, 16);
document.getElementById("prio_" + k).value = p.prio;
document.getElementById("p2p_" + k).value = ["auto","on","off"][p.p2];
document.getElementById("guard_" + k).value =
(p.f & PF_BPDUGUARD) ? "bpdu" : ((p.f & PF_ROOTGUARD) ? "root" : "none");
document.getElementById("filt_" + p.p).value = (p.f & PF_FILTER) ? "on" : "off";
document.getElementById("filt_" + k).value = (p.f & PF_FILTER) ? "on" : "off";
}
}
};
+32 -4
View File
@@ -607,17 +607,43 @@ void send_stp(void)
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]);
j = stp_root_port;
if (j != 0xff && j >= STP_LAG_BASE) {
j = 0;
while (j < STP_LAG_BASE && !((stp_lag_mask[stp_root_port - STP_LAG_BASE] >> j) & 1))
j++;
if (j >= STP_LAG_BASE)
j = 0;
}
itoa_html(j == 0xff ? 0 : machine.log_to_phys_port[j]);
slen += strtox(outbuf + slen, ",\"tc\":\"");
byte_to_html(stp_tc_count >> 8);
byte_to_html(stp_tc_count);
slen += strtox(outbuf + slen, "\",\"ports\":[");
reg_read_m(RTL837X_MSTP_STATES);
for (i = machine.min_port; i <= machine.max_port; i++) {
for (i = 0; i < STP_ENTITIES; i++) {
if (i < STP_LAG_BASE && (i < machine.min_port || i > machine.max_port))
continue;
if (i < STP_LAG_BASE && stp_ent_of[i] != i)
continue;
if (i >= STP_LAG_BASE && !stp_lag_mask[i - STP_LAG_BASE])
continue;
slen += strtox(outbuf + slen, "{\"p\":");
itoa_html(machine.log_to_phys_port[i]);
itoa_html(i < STP_LAG_BASE ? machine.log_to_phys_port[i] : 0);
slen += strtox(outbuf + slen, ",\"lag\":");
itoa_html(i < STP_LAG_BASE ? 0 : i - STP_LAG_BASE + 1);
slen += strtox(outbuf + slen, ",\"mbr\":");
itoa16_html(i < STP_LAG_BASE ? 0 : stp_lag_mask[i - STP_LAG_BASE]);
slen += strtox(outbuf + slen, ",\"st\":");
st = (sfr_data[3 - (i >> 2)] >> ((i << 1) & 0x7)) & 0x3;
j = i;
if (i >= STP_LAG_BASE) {
j = 0;
while (j < STP_LAG_BASE && !((stp_lag_mask[i - STP_LAG_BASE] >> j) & 1))
j++;
if (j >= STP_LAG_BASE)
j = 0;
}
st = (sfr_data[3 - (j >> 2)] >> ((j << 1) & 0x7)) & 0x3;
itoa_html(st);
slen += strtox(outbuf + slen, ",\"role\":");
if (!(stp_pflags[i] & STP_PF_ENABLED) || (stp_pflags[i] & STP_PF_TRIPPED))
@@ -658,6 +684,8 @@ void send_stp(void)
}
void send_eee(void)
{
dbg_string("send_eee called\nsending EEE status\n");
+283 -75
View File
@@ -47,31 +47,36 @@ __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 */
__xdata uint8_t stp_ent_of[STP_PORTS];
__xdata uint16_t stp_lag_mask[STP_LAG_COUNT];
__xdata uint8_t stp_ss_i;
__xdata uint8_t stp_map_dirty;
__xdata uint8_t stp_pflags[STP_ENTITIES];
__xdata uint32_t stp_pcost[STP_ENTITIES]; /* 0 = auto */
__xdata uint8_t stp_pprio[STP_ENTITIES];
__xdata uint8_t stp_pp2p[STP_ENTITIES]; /* 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];
__xdata struct bridge stp_dbridge[STP_ENTITIES];
__xdata uint16_t stp_dpid[STP_ENTITIES];
__xdata uint32_t stp_dcost[STP_ENTITIES];
/* ---- Status / runtime ---- */
__xdata struct bridge root_bridge;
__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 uint16_t stp_scratch16; /* scratch for status printing and the lag map */
__xdata uint8_t stp_st_of;
__xdata uint16_t port_timers[10]; /* listen-period countdown (0 = not listening) */
__xdata uint16_t port_hello[10]; /* hello TX countdown */
__xdata uint16_t stp_bpdu_age[10]; /* ticks since last BPDU seen on port (saturating) */
__xdata uint8_t stp_loop_held[10]; /* port is out of forwarding because a loop was seen on it */
__xdata uint8_t stp_tx_budget[10]; /* tx hold: BPDUs left in the current second */
__xdata uint8_t stp_tx_count[10]; /* BPDUs actually put on the wire, wraps at 256 */
__xdata uint16_t port_timers[STP_ENTITIES]; /* listen-period countdown (0 = not listening) */
__xdata uint16_t port_hello[STP_ENTITIES]; /* hello TX countdown */
__xdata uint16_t stp_bpdu_age[STP_ENTITIES]; /* ticks since last BPDU seen on port (saturating) */
__xdata uint8_t stp_loop_held[STP_ENTITIES]; /* port is out of forwarding because a loop was seen on it */
__xdata uint8_t stp_tx_budget[STP_ENTITIES]; /* tx hold: BPDUs left in the current second */
__xdata uint8_t stp_tx_count[STP_ENTITIES]; /* 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;
@@ -161,10 +166,22 @@ struct stp_pkt_in {
#define BPDU_ROLE_ROOT (0b10 << 2)
#define BPDU_ROLE_DESIGNATED (0b11 << 2)
/* Port state as the MSTP register encodes it, two bits per port. */
#define STP_ST_DISABLED 0b00
#define STP_ST_BLOCKING 0b01
#define STP_ST_LEARNING 0b10
#define STP_ST_FORWARDING 0b11
#define STP_ST_MASK 0b11
/* Console messages name the port on the front panel, not the internal index. */
static void print_port_nl(uint8_t port) __reentrant
{
print_byte(machine.log_to_phys_port[port]);
if (port >= STP_LAG_BASE) {
write_char('L');
write_char('1' + port - STP_LAG_BASE);
} else {
print_byte(machine.log_to_phys_port[port]);
}
write_char('\n');
}
@@ -184,6 +201,8 @@ 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 uint8_t stp_ent_active(uint8_t e) __reentrant;
static void print_field(__code const char *txt, uint8_t idx, uint8_t width) __reentrant
{
txt += idx * width;
@@ -207,7 +226,12 @@ static void stp_status(void)
print_string(" (this switch)\n");
} else {
print_string(" port ");
print_byte(machine.log_to_phys_port[stp_root_port]);
if (stp_root_port >= STP_LAG_BASE) {
write_char('L');
write_char('1' + stp_root_port - STP_LAG_BASE);
} else {
print_byte(machine.log_to_phys_port[stp_root_port]);
}
print_string(" cost ");
print_long(root_bridge_cost);
write_char('\n');
@@ -217,11 +241,26 @@ static void stp_status(void)
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++) {
for (stp_i = 0; stp_i < STP_ENTITIES; stp_i++) {
if (!stp_ent_active(stp_i))
continue;
write_char(' ');
print_byte(machine.log_to_phys_port[stp_i]);
if (stp_i >= STP_LAG_BASE) {
write_char('L');
write_char('1' + stp_i - STP_LAG_BASE);
} else {
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);
stp_st_of = stp_i;
if (stp_i >= STP_LAG_BASE) {
stp_st_of = 0;
while (stp_st_of < STP_PORTS && !((stp_lag_mask[stp_i - STP_LAG_BASE] >> stp_st_of) & 1))
stp_st_of++;
if (stp_st_of >= STP_PORTS)
stp_st_of = 0;
}
print_field(stp_state_txt, (sfr_data[3 - (stp_st_of >> 2)] >> ((stp_st_of << 1) & 0x7)) & 0x3, 5);
write_char(' ');
print_field(stp_role_txt, stp_i == stp_root_port ? 1 : 0, 4);
write_char(' ');
@@ -266,18 +305,95 @@ int8_t cmpBytes(__xdata uint8_t *m1, __xdata uint8_t *m2, uint8_t n) __reentrant
}
/* Write one port's 2-bit state into the ASIC's MSTP register.
* 00 disable, 01 blocking, 10 learning, 11 forwarding. */
static void stp_lag_map(void)
{
for (stp_ss_i = 0; stp_ss_i < STP_PORTS; stp_ss_i++)
stp_ent_of[stp_ss_i] = stp_ss_i;
for (stp_scratch = 0; stp_scratch < STP_LAG_COUNT; stp_scratch++) {
stp_scratch16 = port_lag_members_get(stp_scratch);
if (stp_lag_mask[stp_scratch] != stp_scratch16)
stp_map_dirty = 1;
stp_lag_mask[stp_scratch] = stp_scratch16;
for (stp_ss_i = 0; stp_ss_i < STP_PORTS; stp_ss_i++)
if ((stp_lag_mask[stp_scratch] >> stp_ss_i) & 1)
stp_ent_of[stp_ss_i] = STP_LAG_BASE + stp_scratch;
}
}
static uint8_t stp_ent_active(uint8_t e) __reentrant
{
if (e >= STP_ENTITIES)
return 0;
if (e >= STP_LAG_BASE)
return stp_lag_mask[e - STP_LAG_BASE] != 0;
return e >= machine.min_port && e <= machine.max_port && stp_ent_of[e] == e;
}
static uint8_t stp_ent_has(uint8_t ent, uint8_t p) __reentrant
{
if (ent < STP_LAG_BASE)
return ent == p;
return (stp_lag_mask[ent - STP_LAG_BASE] >> p) & 1;
}
static uint8_t stp_state_port(uint8_t e) __reentrant
{
if (e < STP_LAG_BASE)
return e;
for (stp_ss_i = 0; stp_ss_i < STP_PORTS; stp_ss_i++)
if ((stp_lag_mask[e - STP_LAG_BASE] >> stp_ss_i) & 1)
return stp_ss_i;
return 0;
}
static void stp_state_bits(uint8_t port, uint8_t state) __reentrant
{
for (stp_ss_i = 0; stp_ss_i < STP_PORTS; stp_ss_i++)
if (stp_ent_has(port, stp_ss_i))
sfr_data[3 - (stp_ss_i >> 2)] |= (uint8_t)(state << ((stp_ss_i << 1) & 0x7));
}
static void stp_state_set(uint8_t port, uint8_t state) __reentrant
{
reg_read_m(RTL837X_MSTP_STATES);
stp_scratch = 3 - (port >> 2);
sfr_data[stp_scratch] &= ~(uint8_t)(0b11 << ((port << 1) & 0x7));
sfr_data[stp_scratch] |= (uint8_t)(state << ((port << 1) & 0x7));
for (stp_ss_i = 0; stp_ss_i < STP_PORTS; stp_ss_i++) {
if (!stp_ent_has(port, stp_ss_i))
continue;
stp_scratch = 3 - (stp_ss_i >> 2);
sfr_data[stp_scratch] &= ~(uint8_t)(STP_ST_MASK << ((stp_ss_i << 1) & 0x7));
sfr_data[stp_scratch] |= (uint8_t)(state << ((stp_ss_i << 1) & 0x7));
}
reg_write_m(RTL837X_MSTP_STATES);
}
static void stp_ent_apply(uint8_t e) __reentrant
{
if (!(stp_pflags[e] & STP_PF_ENABLED)) {
stp_state_set(e, STP_ST_FORWARDING);
return;
}
if (stp_pflags[e] & STP_PF_TRIPPED) {
stp_state_set(e, STP_ST_DISABLED);
return;
}
if (stp_pflags[e] & STP_PF_ADMEDGE) {
stp_pflags[e] |= STP_PF_OPEREDGE;
stp_state_set(e, STP_ST_FORWARDING);
return;
}
if (port_timers[e])
stp_state_set(e, STP_ST_BLOCKING);
else
stp_state_set(e, ((stp_link_prev >> e) & 1) ? STP_ST_FORWARDING : STP_ST_BLOCKING);
}
/* Signal a topology change. Edge ports are exempt. */
static void stp_topology_change(uint8_t port) __reentrant
{
@@ -285,7 +401,13 @@ static void stp_topology_change(uint8_t port) __reentrant
return;
stp_tc_count++;
stp_tc_while = ((uint16_t)stp_maxage_s + stp_fwddelay_s) * STP_HZ;
port_l2_forget_port(port);
if (port >= STP_LAG_BASE) {
for (uint8_t i = machine.min_port; i <= machine.max_port; i++)
if (stp_ent_has(port, i))
port_l2_forget_port(i);
} else {
port_l2_forget_port(port);
}
}
@@ -296,7 +418,7 @@ static void stp_topology_change(uint8_t port) __reentrant
*/
static void stp_loop_hold_peer(uint8_t port) __reentrant
{
if (port < machine.min_port || port > machine.max_port)
if (port >= STP_ENTITIES || !stp_ent_active(port))
return;
if (!(stp_pflags[port] & STP_PF_ENABLED))
return;
@@ -305,7 +427,7 @@ static void stp_loop_hold_peer(uint8_t port) __reentrant
if (!port_timers[port]) {
print_string("STP: loop detected, blocking port ");
print_port_nl(port);
stp_state_set(port, 0b01);
stp_state_set(port, STP_ST_BLOCKING);
stp_pflags[port] &= ~STP_PF_OPEREDGE;
stp_topology_change(port);
}
@@ -348,7 +470,18 @@ void stp_cnf_send(uint8_t port) __reentrant
STP_O->rtl_tag.version = RTL_FRAME_TAG_VERSION;
STP_O->rtl_tag.reason = 0x00;
STP_O->rtl_tag.flags = HTONS(RTL_TAG_LEARN_DIS);
STP_O->rtl_tag.pmask = HTONS(((uint16_t)1) << port);
if (port >= STP_LAG_BASE) {
stp_scratch = 0;
while (stp_scratch < STP_PORTS && !stp_ent_has(port, stp_scratch))
stp_scratch++;
if (stp_scratch >= STP_PORTS) {
stp_tx_flags_extra = 0;
return;
}
STP_O->rtl_tag.pmask = HTONS(((uint16_t)1) << stp_scratch);
} else {
STP_O->rtl_tag.pmask = HTONS(((uint16_t)1) << port);
}
STP_O->dsap = 0x42;
STP_O->ssap = 0x42;
@@ -359,8 +492,9 @@ void stp_cnf_send(uint8_t port) __reentrant
STP_O->version = BPDU_VER_RSTP;
STP_O->bpdu_type = BPDU_TYPE_RST;
reg_read_m(RTL837X_MSTP_STATES);
stp_scratch = stp_state_port(port);
STP_O->flags = port == stp_root_port ? BPDU_ROLE_ROOT : BPDU_ROLE_DESIGNATED;
if (((sfr_data[3 - (port >> 2)] >> ((port << 1) & 0x7)) & 0b11) == 0b11)
if (((sfr_data[3 - (stp_scratch >> 2)] >> ((stp_scratch << 1) & 0x7)) & STP_ST_MASK) == STP_ST_FORWARDING)
STP_O->flags |= BPDU_FLAG_LEARNING | BPDU_FLAG_FORWARDING;
} else {
STP_O->msg_len = HTONS(BPDU_LEN_CONFIG);
@@ -424,7 +558,7 @@ void stp_in(void) __banked
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;
port = stp_ent_of[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))
@@ -445,7 +579,7 @@ void stp_in(void) __banked
print_string("STP: BPDU guard tripped, disabling port ");
print_port_nl(port);
stp_pflags[port] |= STP_PF_TRIPPED;
stp_state_set(port, 0b00);
stp_state_set(port, STP_ST_DISABLED);
stp_tc_count++;
return;
}
@@ -516,8 +650,9 @@ void stp_in(void) __banked
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))
if (!stp_ent_has(port, i)
&& (stp_pflags[stp_ent_of[i]] & STP_PF_ENABLED)
&& !(stp_pflags[stp_ent_of[i]] & STP_PF_OPEREDGE))
port_l2_forget_port(i);
}
if (stp_tc_while < ((uint16_t)stp_hello_s + 1) * STP_HZ)
@@ -535,7 +670,7 @@ void stp_in(void) __banked
if (stp_pflags[port] & STP_PF_ROOTGUARD) {
print_string("STP: root guard blocking port ");
print_port_nl(port);
stp_state_set(port, 0b01);
stp_state_set(port, STP_ST_BLOCKING);
port_timers[port] = (uint16_t)stp_fwddelay_s * STP_HZ;
stp_pflags[port] &= ~STP_PF_OPEREDGE;
return;
@@ -563,9 +698,35 @@ void stp_timers(void) __banked
/* Refill the per-port tx budgets once per second (tx hold count) */
if (++stp_sec_tick >= STP_HZ) {
stp_sec_tick = 0;
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++)
for (stp_i = 0; stp_i < STP_ENTITIES; stp_i++)
stp_tx_budget[stp_i] = stp_txhold;
stp_lag_map();
if (stp_map_dirty) {
stp_map_dirty = 0;
for (stp_i = 0; stp_i < STP_LAG_BASE; stp_i++) {
if (stp_i < machine.min_port || stp_i > machine.max_port)
continue;
if (stp_ent_of[stp_i] != stp_i) {
stp_loop_held[stp_i] = 0;
port_timers[stp_i] = 0;
} else {
stp_ent_apply(stp_i);
}
}
for (stp_i = STP_LAG_BASE; stp_i < STP_ENTITIES; stp_i++) {
if (stp_lag_mask[stp_i - STP_LAG_BASE]) {
stp_ent_apply(stp_i);
} else {
stp_pflags[stp_i] &= ~(STP_PF_OPEREDGE | STP_PF_TRIPPED);
stp_loop_held[stp_i] = 0;
port_timers[stp_i] = 0;
}
}
if (stp_root_port != 0xff && !stp_ent_active(stp_root_port))
stp_claim_root();
}
/* 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 -
@@ -574,14 +735,26 @@ void stp_timers(void) __banked
* 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);
{
__xdata static uint16_t eff;
eff = 0;
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++)
if ((stp_link_now >> stp_i) & 1)
eff |= (uint16_t)1 << stp_ent_of[stp_i];
stp_link_now = eff;
}
if (stp_link_now != stp_link_prev) {
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) {
for (stp_i = 0; stp_i < STP_ENTITIES; stp_i++) {
if (stp_i < STP_PORTS && (stp_i < machine.min_port || stp_i > machine.max_port))
continue;
if (stp_i < STP_PORTS && stp_ent_of[stp_i] != stp_i)
continue;
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);
stp_state_set(stp_i, STP_ST_BLOCKING);
if ((stp_link_now >> stp_i) & 1) {
/* Carrier back: re-run the listen period rather than
* forwarding straight away - the segment may have been
@@ -600,8 +773,8 @@ void stp_timers(void) __banked
}
}
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) {
if (!(stp_pflags[stp_i] & STP_PF_ENABLED))
for (stp_i = 0; stp_i < STP_ENTITIES; stp_i++) {
if (!stp_ent_active(stp_i) || !(stp_pflags[stp_i] & STP_PF_ENABLED))
continue;
if (stp_bpdu_age[stp_i] < 0xffff)
@@ -626,7 +799,7 @@ void stp_timers(void) __banked
if (port_timers[stp_i]) {
if (!--port_timers[stp_i]) {
stp_loop_held[stp_i] = 0;
stp_state_set(stp_i, 0b11);
stp_state_set(stp_i, STP_ST_FORWARDING);
print_string("STP: port forwarding ");
print_port_nl(stp_i);
stp_topology_change(stp_i);
@@ -637,7 +810,7 @@ void stp_timers(void) __banked
* 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);
stp_state_set(stp_i, STP_ST_FORWARDING);
print_string("STP: edge port forwarding ");
print_port_nl(stp_i);
}
@@ -667,7 +840,12 @@ void stp_defaults(void) __banked
stp_fwddelay_s = 15;
stp_rstp = 1;
stp_txhold = 6;
for (stp_i = 0; stp_i < 10; stp_i++) {
for (stp_i = 0; stp_i < STP_PORTS; stp_i++)
stp_ent_of[stp_i] = stp_i;
for (stp_i = 0; stp_i < STP_LAG_COUNT; stp_i++)
stp_lag_mask[stp_i] = 0;
stp_map_dirty = 0;
for (stp_i = 0; stp_i < STP_ENTITIES; 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;
@@ -714,25 +892,34 @@ static void stp_fdb_update(__xdata uint16_t pmask)
void stp_setup(void) __banked
{
print_string("Enabling STP: ");
stp_lag_map();
sfr_data[0] = sfr_data[1] = sfr_data[2] = sfr_data[3] = 0;
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) {
for (stp_i = 0; stp_i < STP_ENTITIES; stp_i++) {
stp_pflags[stp_i] &= ~(STP_PF_OPEREDGE | STP_PF_TRIPPED);
stp_loop_held[stp_i] = 0;
stp_bpdu_age[stp_i] = 0;
stp_tx_budget[stp_i] = stp_txhold;
stp_tx_count[stp_i] = 0;
if (!(stp_pflags[stp_i] & STP_PF_ENABLED) || (stp_pflags[stp_i] & STP_PF_ADMEDGE)) {
port_timers[stp_i] = 0;
port_hello[stp_i] = (uint16_t)stp_hello_s * STP_HZ;
if (stp_i < STP_PORTS && (stp_i < machine.min_port || stp_i > machine.max_port))
continue;
if (stp_i >= STP_LAG_BASE && !stp_lag_mask[stp_i - STP_LAG_BASE])
continue;
if (stp_i < STP_PORTS && stp_ent_of[stp_i] != stp_i)
continue;
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));
stp_state_bits(stp_i, STP_ST_FORWARDING);
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));
stp_state_bits(stp_i, STP_ST_BLOCKING);
port_timers[stp_i] = (uint16_t)stp_fwddelay_s * STP_HZ;
}
port_hello[stp_i] = (uint16_t)stp_hello_s * STP_HZ;
}
sfr_data[1] |= 0x0c; // Do not block the CPU port (bits 3:2 of byte 1 = port 9)
reg_write_m(RTL837X_MSTP_STATES);
@@ -752,7 +939,11 @@ void stp_setup(void) __banked
/* 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_link_now = (uint16_t)sfr_data[1] | ((uint16_t)sfr_data[2] << 8);
stp_link_prev = 0;
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++)
if ((stp_link_now >> stp_i) & 1)
stp_link_prev |= (uint16_t)1 << stp_ent_of[stp_i];
stp_claim_root();
@@ -767,7 +958,9 @@ void stp_off(void) __banked
for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) {
// Set STP port state to forwarding
// States are: 00 disable, 01 blocking, 10 learning, 11 forwarding
sfr_data[3 - (stp_i >> 2)] |= (uint8_t)(0b11 << ((stp_i << 1) & 0x7));
sfr_data[3 - (stp_i >> 2)] |= (uint8_t)(STP_ST_FORWARDING << ((stp_i << 1) & 0x7));
}
for (stp_i = 0; stp_i < STP_ENTITIES; stp_i++) {
stp_pflags[stp_i] &= ~(STP_PF_OPEREDGE | STP_PF_TRIPPED);
stp_loop_held[stp_i] = 0;
port_timers[stp_i] = 0;
@@ -782,7 +975,7 @@ void stp_off(void) __banked
void stp_parse(void) __banked __reentrant
{
uint8_t port;
uint8_t ent;
if (cmd_compare(1, "on")) {
print_string("STP enabled\n");
@@ -803,35 +996,50 @@ void stp_parse(void) __banked __reentrant
if (cmd_words_len < 3)
goto err;
if (cmd_compare(1, "port")) {
if (cmd_compare(1, "ent") || cmd_compare(1, "lag")) {
stp_lag_map();
if (cmd_words_len < 4)
goto err;
if (!cmd_parse_port_separator(cmd_words_b[2]))
goto err;
port = atoi_results_u8;
if (cmd_compare(1, "lag")) {
if (!atoi_byte(cmd_words_b[2]))
goto err;
if (!atoi_results_u8 || atoi_results_u8 > STP_LAG_COUNT)
goto err;
ent = STP_LAG_BASE + atoi_results_u8 - 1;
} else {
if (!cmd_parse_port_separator(cmd_words_b[2]))
goto err;
ent = atoi_results_u8;
if (stp_ent_of[ent] != ent) {
print_string("Port belongs to a LAG, configure it as lag ");
print_byte(stp_ent_of[ent] - STP_LAG_BASE + 1);
write_char('\n');
return;
}
}
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;
stp_pflags[ent] |= STP_PF_ENABLED;
stp_pflags[ent] &= ~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;
stp_state_set(ent, STP_ST_BLOCKING);
port_timers[ent] = (uint16_t)stp_fwddelay_s * STP_HZ;
}
} else if (cmd_compare(3, "off")) {
stp_pflags[port] &= ~STP_PF_ENABLED;
stp_pflags[ent] &= ~STP_PF_ENABLED;
if (stp_enabled)
stp_state_set(port, 0b11); /* plain forwarding */
stp_state_set(ent, STP_ST_FORWARDING); /* 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,
* ent 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);
stp_pflags[ent] &= ~(STP_PF_ADMEDGE | STP_PF_AUTOEDGE | STP_PF_OPEREDGE);
if (cmd_compare(4, "on"))
stp_pflags[port] |= STP_PF_ADMEDGE | STP_PF_OPEREDGE;
stp_pflags[ent] |= STP_PF_ADMEDGE | STP_PF_OPEREDGE;
else if (cmd_compare(4, "auto"))
stp_pflags[port] |= STP_PF_AUTOEDGE;
stp_pflags[ent] |= STP_PF_AUTOEDGE;
else if (!cmd_compare(4, "off"))
goto err;
} else if (cmd_compare(3, "cost")) {
@@ -848,14 +1056,14 @@ void stp_parse(void) __banked __reentrant
}
if (stp_cost_scratch > 200000000UL)
goto err;
stp_pcost[port] = stp_cost_scratch;
stp_pcost[ent] = stp_cost_scratch;
} else if (cmd_compare(3, "p2p")) {
if (cmd_compare(4, "auto"))
stp_pp2p[port] = 0;
stp_pp2p[ent] = 0;
else if (cmd_compare(4, "on"))
stp_pp2p[port] = 1;
stp_pp2p[ent] = 1;
else if (cmd_compare(4, "off"))
stp_pp2p[port] = 2;
stp_pp2p[ent] = 2;
else
goto err;
} else if (cmd_compare(3, "prio")) {
@@ -863,20 +1071,20 @@ void stp_parse(void) __banked __reentrant
goto err;
if (atoi_results_u8 > 240 || (atoi_results_u8 & 0x0f))
goto err;
stp_pprio[port] = atoi_results_u8;
stp_pprio[ent] = atoi_results_u8;
} else if (cmd_compare(3, "guard")) {
stp_pflags[port] &= ~(STP_PF_BPDUGUARD | STP_PF_ROOTGUARD);
stp_pflags[ent] &= ~(STP_PF_BPDUGUARD | STP_PF_ROOTGUARD);
if (cmd_compare(4, "bpdu"))
stp_pflags[port] |= STP_PF_BPDUGUARD;
stp_pflags[ent] |= STP_PF_BPDUGUARD;
else if (cmd_compare(4, "root"))
stp_pflags[port] |= STP_PF_ROOTGUARD;
stp_pflags[ent] |= 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;
stp_pflags[ent] |= STP_PF_FILTER;
else if (cmd_compare(4, "off"))
stp_pflags[port] &= ~STP_PF_FILTER;
stp_pflags[ent] &= ~STP_PF_FILTER;
else
goto err;
} else {
@@ -926,5 +1134,5 @@ void stp_parse(void) __banked __reentrant
}
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");
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 | ent <1-9>|lag <1-4> on|off|edge|cost|prio|guard|filter ...\n");
}
+18 -8
View File
@@ -2,6 +2,7 @@
#define _RTL837X_STP_H_
#include <stdint.h>
#include "rtl837x_common.h"
void stp_in(void) __banked;
void stp_setup(void) __banked;
void stp_timers(void) __banked;
@@ -12,6 +13,11 @@ void stp_defaults(void) __banked;
/* Tick rate of stp_timers(), also used by the web UI. */
#define STP_HZ 50
#define STP_PORTS (CPU_PORT + 1)
#define STP_LAG_BASE STP_PORTS
#define STP_LAG_COUNT 4
#define STP_ENTITIES (STP_LAG_BASE + STP_LAG_COUNT)
/* Bridge identifier as carried in a BPDU (priority, extension, MAC). */
struct bridge {
uint8_t prio;
@@ -36,15 +42,19 @@ extern __xdata uint8_t stp_txhold;
#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 uint8_t stp_pflags[STP_ENTITIES];
extern __xdata uint32_t stp_pcost[STP_ENTITIES]; /* path cost; 0 = auto (20000) */
extern __xdata uint8_t stp_pprio[STP_ENTITIES];
extern __xdata uint8_t stp_pp2p[STP_ENTITIES];
extern __xdata uint8_t stp_ent_of[10]; /* the STP entity a port answers to: itself, or its lag */
extern __xdata uint16_t stp_lag_mask[STP_LAG_COUNT]; /* member ports of each lag; 0 = not part of STP */
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];
/* Last-heard designated info per port (from received BPDUs); consult
* stp_bpdu_age to decide whether it is still current. */
extern __xdata struct bridge stp_dbridge[STP_ENTITIES];
extern __xdata uint16_t stp_dpid[STP_ENTITIES];
extern __xdata uint32_t stp_dcost[STP_ENTITIES];
extern __xdata uint16_t stp_bpdu_age[STP_ENTITIES]; /* ticks since a BPDU was heard */
extern __xdata struct bridge root_bridge;
extern __xdata uint32_t root_bridge_cost;