stp: treat a link aggregation group as one entity

Spanning tree so far saw only physical ports. Two ports aggregated towards
the same neighbour look to the protocol like two parallel paths, so it
blocks one of them and cuts the group in half.

Give each of the four groups an STP entity of its own, numbered above the
physical ports. A member port answers to its group: BPDUs arriving on it
are accounted to the entity, the entity's state is written to every member
together, and its BPDUs carry the state of a live member. Ports outside
any group keep behaving exactly as before.

Membership comes from the aggregation tables once a second; when it
changed, the entity's state is reapplied to the new set of members. The
CLI takes lag <1-4> wherever it takes port <n>, the web UI shows a group
as one row, and the saved config keeps the lag form of the commands.
This commit is contained in:
d00f committed 2026-09-09 20:58:35 +02:00
1 parent d3f1069285
commit 02c816647c
4 files changed
+319 -59

No files matched your search

+22
View File
@@ -58,6 +58,28 @@ 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 to the CPU. `stp_setup()` prints a warning for every STP-enabled port in that
state. state.
## Link aggregation
A LAG is a port to the protocol, the way the vendor firmware presents it as
Trk1 to Trk4. The four groups are entities alongside the nine physical ports:
they carry their own path cost, priority, edge and guard settings, they hold
their own timers, and they appear as their own rows on the Spanning Tree page.
```
stp lag 1 cost 10000 # the group decides, not its members
stp lag 1 edge off
```
Membership comes from the aggregation registers the `lag` command writes, re-read
once a second, so a group changing under LACP is picked up without any
coordination between the two. A member port is not an STP port of its own:
`stp port <n>` on a member says which group to configure instead. State
changes are written to every member in one register write, since the hardware
has no per-group state, and BPDUs are transmitted through the lowest
member while carrying the group's own port id. Losing one member of a live
LAG is not a topology change; the logical port only goes down with its last
link.
Port states live in `RTL837X_MSTP_STATES (0x5310)`, two bits per port: Port states live in `RTL837X_MSTP_STATES (0x5310)`, two bits per port:
`00` disabled, `01` blocking, `10` learning, `11` forwarding. A port in `00` disabled, `01` blocking, `10` learning, `11` forwarding. A port in
blocking forwards nothing between ports, but it still sends what the CPU blocking forwards nothing between ports, but it still sends what the CPU
+32 -4
View File
@@ -607,17 +607,43 @@ void send_stp(void)
slen += strtox(outbuf + slen, "\",\"weRoot\":"); slen += strtox(outbuf + slen, "\",\"weRoot\":");
bool_to_html(stp_root_port == 0xff ? 1 : 0); bool_to_html(stp_root_port == 0xff ? 1 : 0);
slen += strtox(outbuf + slen, ",\"rootPort\":"); 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\":\""); slen += strtox(outbuf + slen, ",\"tc\":\"");
byte_to_html(stp_tc_count >> 8); byte_to_html(stp_tc_count >> 8);
byte_to_html(stp_tc_count); byte_to_html(stp_tc_count);
slen += strtox(outbuf + slen, "\",\"ports\":["); slen += strtox(outbuf + slen, "\",\"ports\":[");
reg_read_m(RTL837X_MSTP_STATES); 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\":"); 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\":"); 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); itoa_html(st);
slen += strtox(outbuf + slen, ",\"role\":"); slen += strtox(outbuf + slen, ",\"role\":");
if (!(stp_pflags[i] & STP_PF_ENABLED) || (stp_pflags[i] & STP_PF_TRIPPED)) 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) void send_eee(void)
{ {
dbg_string("send_eee called\nsending EEE status\n"); dbg_string("send_eee called\nsending EEE status\n");
+249 -47
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_txhold; /* BPDUs per port per second */
__xdata uint8_t stp_pflags[10]; __xdata uint8_t stp_ent_of[10];
__xdata uint32_t stp_pcost[10]; /* 0 = auto */ __xdata uint16_t stp_lag_mask[STP_LAG_COUNT];
__xdata uint8_t stp_pprio[10]; __xdata uint8_t stp_ss_i;
__xdata uint8_t stp_pp2p[10]; /* admin point-to-point: 0 auto, 1 on, 2 off */ __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 /* Designated bridge, port and cost last heard on the port; stp_bpdu_age tells
* whether they are still current. * whether they are still current.
*/ */
__xdata struct bridge stp_dbridge[10]; __xdata struct bridge stp_dbridge[STP_ENTITIES];
__xdata uint16_t stp_dpid[10]; __xdata uint16_t stp_dpid[STP_ENTITIES];
__xdata uint32_t stp_dcost[10]; __xdata uint32_t stp_dcost[STP_ENTITIES];
/* ---- Status / runtime ---- */ /* ---- Status / runtime ---- */
__xdata struct bridge root_bridge; __xdata struct bridge root_bridge;
__xdata uint32_t root_bridge_cost; /* our cost to the root (rx cost + root port 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 uint8_t stp_root_port; /* 0xff = we are the root */
__xdata uint16_t stp_tc_count; __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_timers[STP_ENTITIES]; /* listen-period countdown (0 = not listening) */
__xdata uint16_t port_hello[10]; /* hello TX countdown */ __xdata uint16_t port_hello[STP_ENTITIES]; /* hello TX countdown */
__xdata uint16_t stp_bpdu_age[10]; /* ticks since last BPDU seen on port (saturating) */ __xdata uint16_t stp_bpdu_age[STP_ENTITIES]; /* 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_loop_held[STP_ENTITIES]; /* 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_budget[STP_ENTITIES]; /* 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 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_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_prev; /* carrier bitmap as of the last check */
__xdata uint16_t stp_link_now; __xdata uint16_t stp_link_now;
@@ -164,7 +169,12 @@ struct stp_pkt_in {
/* Console messages name the port on the front panel, not the internal index. */ /* Console messages name the port on the front panel, not the internal index. */
static void print_port_nl(uint8_t port) __reentrant 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'); write_char('\n');
} }
@@ -184,6 +194,8 @@ static __code const char stp_state_txt[] = "off blocklearnfwd ";
static __code const char stp_role_txt[] = "desgroot"; static __code const char stp_role_txt[] = "desgroot";
static __code const char stp_edge_txt[] = "no yes "; 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 static void print_field(__code const char *txt, uint8_t idx, uint8_t width) __reentrant
{ {
txt += idx * width; txt += idx * width;
@@ -207,7 +219,12 @@ static void stp_status(void)
print_string(" (this switch)\n"); print_string(" (this switch)\n");
} else { } else {
print_string(" port "); 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_string(" cost ");
print_long(root_bridge_cost); print_long(root_bridge_cost);
write_char('\n'); write_char('\n');
@@ -217,11 +234,26 @@ static void stp_status(void)
write_char('\n'); write_char('\n');
print_string("port state role edge tx bpdu\n"); print_string("port state role edge tx bpdu\n");
reg_read_m(RTL837X_MSTP_STATES); 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(' '); 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_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 < 10 && !((stp_lag_mask[stp_i - STP_LAG_BASE] >> stp_st_of) & 1))
stp_st_of++;
if (stp_st_of >= 10)
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(' '); write_char(' ');
print_field(stp_role_txt, stp_i == stp_root_port ? 1 : 0, 4); print_field(stp_role_txt, stp_i == stp_root_port ? 1 : 0, 4);
write_char(' '); write_char(' ');
@@ -266,18 +298,96 @@ 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. static void stp_lag_map(void)
* 00 disable, 01 blocking, 10 learning, 11 forwarding. */ {
for (stp_ss_i = 0; stp_ss_i < 10; 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 < 10; 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 < 10; 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 < 10; 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));
}
/* MSTP register encoding: 00 disable, 01 blocking, 10 learning, 11 forwarding. */
static void stp_state_set(uint8_t port, uint8_t state) __reentrant static void stp_state_set(uint8_t port, uint8_t state) __reentrant
{ {
reg_read_m(RTL837X_MSTP_STATES); reg_read_m(RTL837X_MSTP_STATES);
stp_scratch = 3 - (port >> 2); for (stp_ss_i = 0; stp_ss_i < 10; stp_ss_i++) {
sfr_data[stp_scratch] &= ~(uint8_t)(0b11 << ((port << 1) & 0x7)); if (!stp_ent_has(port, stp_ss_i))
sfr_data[stp_scratch] |= (uint8_t)(state << ((port << 1) & 0x7)); continue;
stp_scratch = 3 - (stp_ss_i >> 2);
sfr_data[stp_scratch] &= ~(uint8_t)(0b11 << ((stp_ss_i << 1) & 0x7));
sfr_data[stp_scratch] |= (uint8_t)(state << ((stp_ss_i << 1) & 0x7));
}
reg_write_m(RTL837X_MSTP_STATES); 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, 0b11);
return;
}
if (stp_pflags[e] & STP_PF_TRIPPED) {
stp_state_set(e, 0b00);
return;
}
if (stp_pflags[e] & STP_PF_ADMEDGE) {
stp_pflags[e] |= STP_PF_OPEREDGE;
stp_state_set(e, 0b11);
return;
}
if (port_timers[e])
stp_state_set(e, 0b01);
else
stp_state_set(e, ((stp_link_prev >> e) & 1) ? 0b11 : 0b01);
}
/* Signal a topology change. Edge ports are exempt. */ /* Signal a topology change. Edge ports are exempt. */
static void stp_topology_change(uint8_t port) __reentrant static void stp_topology_change(uint8_t port) __reentrant
{ {
@@ -285,7 +395,13 @@ static void stp_topology_change(uint8_t port) __reentrant
return; return;
stp_tc_count++; stp_tc_count++;
stp_tc_while = ((uint16_t)stp_maxage_s + stp_fwddelay_s) * STP_HZ; 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 +412,7 @@ static void stp_topology_change(uint8_t port) __reentrant
*/ */
static void stp_loop_hold_peer(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; return;
if (!(stp_pflags[port] & STP_PF_ENABLED)) if (!(stp_pflags[port] & STP_PF_ENABLED))
return; return;
@@ -348,7 +464,18 @@ void stp_cnf_send(uint8_t port) __reentrant
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 = HTONS(RTL_TAG_LEARN_DIS); 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 < 10 && !stp_ent_has(port, stp_scratch))
stp_scratch++;
if (stp_scratch >= 10) {
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->dsap = 0x42;
STP_O->ssap = 0x42; STP_O->ssap = 0x42;
@@ -359,8 +486,9 @@ void stp_cnf_send(uint8_t port) __reentrant
STP_O->version = BPDU_VER_RSTP; STP_O->version = BPDU_VER_RSTP;
STP_O->bpdu_type = BPDU_TYPE_RST; STP_O->bpdu_type = BPDU_TYPE_RST;
reg_read_m(RTL837X_MSTP_STATES); 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; 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)) & 0b11) == 0b11)
STP_O->flags |= BPDU_FLAG_LEARNING | BPDU_FLAG_FORWARDING; STP_O->flags |= BPDU_FLAG_LEARNING | BPDU_FLAG_FORWARDING;
} else { } else {
STP_O->msg_len = HTONS(BPDU_LEN_CONFIG); STP_O->msg_len = HTONS(BPDU_LEN_CONFIG);
@@ -424,7 +552,7 @@ void stp_in(void) __banked
stp_scratch = ((uint8_t)HTONS(STP_I->rtl_tag.pmask)) & 0x0f; stp_scratch = ((uint8_t)HTONS(STP_I->rtl_tag.pmask)) & 0x0f;
if (stp_scratch < machine.min_port || stp_scratch > machine.max_port) if (stp_scratch < machine.min_port || stp_scratch > machine.max_port)
return; return;
port = stp_scratch; port = stp_ent_of[stp_scratch];
// Make sure this is the type of (R)STP packet we are interested in: // 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)) if (!(STP_I->dsap == 0x42 && STP_I->ssap == 0x42 && STP_I->ctrl == 0x03))
@@ -516,8 +644,9 @@ void stp_in(void) __banked
uint8_t i; uint8_t i;
stp_tc_count++; stp_tc_count++;
for (i = machine.min_port; i <= machine.max_port; i++) for (i = machine.min_port; i <= machine.max_port; i++)
if (i != port && (stp_pflags[i] & STP_PF_ENABLED) if (!stp_ent_has(port, i)
&& !(stp_pflags[i] & STP_PF_OPEREDGE)) && (stp_pflags[stp_ent_of[i]] & STP_PF_ENABLED)
&& !(stp_pflags[stp_ent_of[i]] & STP_PF_OPEREDGE))
port_l2_forget_port(i); port_l2_forget_port(i);
} }
if (stp_tc_while < ((uint16_t)stp_hello_s + 1) * STP_HZ) if (stp_tc_while < ((uint16_t)stp_hello_s + 1) * STP_HZ)
@@ -563,9 +692,35 @@ void stp_timers(void) __banked
/* Refill the per-port tx budgets once per second (tx hold count) */ /* Refill the per-port tx budgets once per second (tx hold count) */
if (++stp_sec_tick >= STP_HZ) { if (++stp_sec_tick >= STP_HZ) {
stp_sec_tick = 0; 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_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 /* Link supervision. Without this the state machine never learns
* that a port lost carrier: it keeps the port in forwarding, keeps * that a port lost carrier: it keeps the port in forwarding, keeps
* announcing on it, and never flushes what was learned behind it - * announcing on it, and never flushes what was learned behind it -
@@ -574,8 +729,20 @@ void stp_timers(void) __banked
* of the 50 Hz tick. */ * of the 50 Hz tick. */
reg_read_m(RTL837X_REG_LINKS_STS); reg_read_m(RTL837X_REG_LINKS_STS);
stp_link_now = (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);
{
__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) { 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 < 10 && (stp_i < machine.min_port || stp_i > machine.max_port))
continue;
if (stp_i < 10 && stp_ent_of[stp_i] != stp_i)
continue;
if (!(stp_pflags[stp_i] & STP_PF_ENABLED)) if (!(stp_pflags[stp_i] & STP_PF_ENABLED))
continue; continue;
if (!((stp_link_now ^ stp_link_prev) >> stp_i & 1)) if (!((stp_link_now ^ stp_link_prev) >> stp_i & 1))
@@ -600,8 +767,8 @@ void stp_timers(void) __banked
} }
} }
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_pflags[stp_i] & STP_PF_ENABLED)) if (!stp_ent_active(stp_i) || !(stp_pflags[stp_i] & STP_PF_ENABLED))
continue; continue;
if (stp_bpdu_age[stp_i] < 0xffff) if (stp_bpdu_age[stp_i] < 0xffff)
@@ -667,7 +834,12 @@ void stp_defaults(void) __banked
stp_fwddelay_s = 15; stp_fwddelay_s = 15;
stp_rstp = 1; stp_rstp = 1;
stp_txhold = 6; stp_txhold = 6;
for (stp_i = 0; stp_i < 10; stp_i++) { for (stp_i = 0; stp_i < 10; 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 /* enabled, auto-edge on: host-facing ports go forwarding after
* 3 s of BPDU silence instead of the full forward delay */ * 3 s of BPDU silence instead of the full forward delay */
stp_pflags[stp_i] = STP_PF_ENABLED | STP_PF_AUTOEDGE; stp_pflags[stp_i] = STP_PF_ENABLED | STP_PF_AUTOEDGE;
@@ -714,25 +886,34 @@ static void stp_fdb_update(__xdata uint16_t pmask)
void stp_setup(void) __banked void stp_setup(void) __banked
{ {
print_string("Enabling STP: "); print_string("Enabling STP: ");
stp_lag_map();
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 (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_pflags[stp_i] &= ~(STP_PF_OPEREDGE | STP_PF_TRIPPED);
stp_loop_held[stp_i] = 0; stp_loop_held[stp_i] = 0;
stp_bpdu_age[stp_i] = 0; stp_bpdu_age[stp_i] = 0;
stp_tx_budget[stp_i] = stp_txhold; stp_tx_budget[stp_i] = stp_txhold;
stp_tx_count[stp_i] = 0; 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 < 10 && (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 < 10 && 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 */ /* not participating, or admin edge: forwarding immediately */
if (stp_pflags[stp_i] & STP_PF_ADMEDGE) if (stp_pflags[stp_i] & STP_PF_ADMEDGE)
stp_pflags[stp_i] |= STP_PF_OPEREDGE; 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, 0b11);
port_timers[stp_i] = 0; port_timers[stp_i] = 0;
} else { } else {
/* listen first: blocking until the forward-delay expires */ /* 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, 0b01);
port_timers[stp_i] = (uint16_t)stp_fwddelay_s * STP_HZ; port_timers[stp_i] = (uint16_t)stp_fwddelay_s * STP_HZ;
} }
port_hello[stp_i] = (uint16_t)stp_hello_s * STP_HZ;
} }
sfr_data[1] |= 0x0c; // Do not block the CPU port (bits 3:2 of byte 1 = port 9) sfr_data[1] |= 0x0c; // Do not block the CPU port (bits 3:2 of byte 1 = port 9)
reg_write_m(RTL837X_MSTP_STATES); reg_write_m(RTL837X_MSTP_STATES);
@@ -752,7 +933,11 @@ void stp_setup(void) __banked
/* Seed the carrier bitmap, so turning STP on does not report every /* Seed the carrier bitmap, so turning STP on does not report every
* port that was already down as a fresh topology change. */ * port that was already down as a fresh topology change. */
reg_read_m(RTL837X_REG_LINKS_STS); 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(); stp_claim_root();
@@ -768,6 +953,8 @@ void stp_off(void) __banked
// 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
sfr_data[3 - (stp_i >> 2)] |= (uint8_t)(0b11 << ((stp_i << 1) & 0x7)); sfr_data[3 - (stp_i >> 2)] |= (uint8_t)(0b11 << ((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_pflags[stp_i] &= ~(STP_PF_OPEREDGE | STP_PF_TRIPPED);
stp_loop_held[stp_i] = 0; stp_loop_held[stp_i] = 0;
port_timers[stp_i] = 0; port_timers[stp_i] = 0;
@@ -803,12 +990,27 @@ void stp_parse(void) __banked __reentrant
if (cmd_words_len < 3) if (cmd_words_len < 3)
goto err; goto err;
if (cmd_compare(1, "port")) { if (cmd_compare(1, "port") || cmd_compare(1, "lag")) {
stp_lag_map();
if (cmd_words_len < 4) if (cmd_words_len < 4)
goto err; goto err;
if (!cmd_parse_port_separator(cmd_words_b[2])) if (cmd_compare(1, "lag")) {
goto err; if (!atoi_byte(cmd_words_b[2]))
port = atoi_results_u8; goto err;
if (!atoi_results_u8 || atoi_results_u8 > STP_LAG_COUNT)
goto err;
port = STP_LAG_BASE + atoi_results_u8 - 1;
} else {
if (!cmd_parse_port_separator(cmd_words_b[2]))
goto err;
port = atoi_results_u8;
if (stp_ent_of[port] != port) {
print_string("Port belongs to a LAG, configure it as lag ");
print_byte(stp_ent_of[port] - STP_LAG_BASE + 1);
write_char('\n');
return;
}
}
if (cmd_words_len < 5 && !cmd_compare(3, "on") && !cmd_compare(3, "off")) if (cmd_words_len < 5 && !cmd_compare(3, "on") && !cmd_compare(3, "off"))
goto err; goto err;
if (cmd_compare(3, "on")) { if (cmd_compare(3, "on")) {
@@ -926,5 +1128,5 @@ void stp_parse(void) __banked __reentrant
} }
return; return;
err: 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 | port <1-9>|lag <1-4> on|off|edge|cost|prio|guard|filter ...\n");
} }
+16 -8
View File
@@ -12,6 +12,10 @@ void stp_defaults(void) __banked;
/* Tick rate of stp_timers(), also used by the web UI. */ /* Tick rate of stp_timers(), also used by the web UI. */
#define STP_HZ 50 #define STP_HZ 50
#define STP_LAG_BASE 10
#define STP_LAG_COUNT 4
#define STP_ENTITIES (STP_LAG_BASE + STP_LAG_COUNT)
/* Bridge identifier as carried in a BPDU (priority, extension, MAC). */ /* Bridge identifier as carried in a BPDU (priority, extension, MAC). */
struct bridge { struct bridge {
uint8_t prio; uint8_t prio;
@@ -36,15 +40,19 @@ extern __xdata uint8_t stp_txhold;
#define STP_PF_OPEREDGE 0x40 /* runtime: port went forwarding as an edge */ #define STP_PF_OPEREDGE 0x40 /* runtime: port went forwarding as an edge */
#define STP_PF_TRIPPED 0x80 /* runtime: disabled by BPDU guard */ #define STP_PF_TRIPPED 0x80 /* runtime: disabled by BPDU guard */
extern __xdata uint8_t stp_pflags[10]; extern __xdata uint8_t stp_pflags[STP_ENTITIES];
extern __xdata uint32_t stp_pcost[10]; extern __xdata uint32_t stp_pcost[STP_ENTITIES]; /* path cost; 0 = auto (20000) */
extern __xdata uint8_t stp_pprio[10]; extern __xdata uint8_t stp_pprio[STP_ENTITIES];
extern __xdata uint8_t stp_pp2p[10]; 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]; /* Last-heard designated info per port (from received BPDUs); consult
extern __xdata uint16_t stp_dpid[10]; * stp_bpdu_age to decide whether it is still current. */
extern __xdata uint32_t stp_dcost[10]; extern __xdata struct bridge stp_dbridge[STP_ENTITIES];
extern __xdata uint16_t stp_bpdu_age[10]; 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 struct bridge root_bridge;
extern __xdata uint32_t root_bridge_cost; extern __xdata uint32_t root_bridge_cost;