Files
RTLPlayground/rtl837x_stp.c
T
d00f eb51128ec7 stp: a port without carrier does not become the root port
stp_reselect() took every port with fresh information as a candidate, so a
BPDU credited to a port whose carrier the once-a-second link poll had not
seen yet made that port the root port before the port was set up for it.
A candidate now needs its link bit as well; the poll that sees the carrier
a moment later runs the selection again. Scenario 54.
2026-10-27 22:11:24 +01:00

1521 lines
48 KiB
C

/*
* This is a driver implementation for the Spanning Tree Protocol features for the RTL837x platform
* This code is in the Public Domain
*/
// #define REGDBG
// #define DEBUG
#include <stdint.h>
#include "rtl837x_common.h"
#include "rtl837x_sfr.h"
#include "rtl837x_regs.h"
#include "rtl837x_stp.h"
#include "rtl837x_port.h"
#include "rtl837x_trap.h"
#include "uip.h"
#include "machine.h"
extern __xdata uint8_t err_status;
// All entry points are __banked and nothing here runs from an interrupt,
// so the module does not need to stay in the resident bank
#pragma codeseg BANK4
#pragma constseg BANK4
extern __code const struct machine machine;
extern __xdata uint8_t sfr_data[4];
extern __xdata struct machine_runtime machine_detected;
extern __xdata struct uip_eth_addr uip_ethaddr;
extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_ALLOC_SIZE];
/* ---- 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[STP_ENTITIES];
__xdata uint32_t stp_pcost[STP_ENTITIES]; /* 0 = auto */
__xdata uint8_t stp_pprio[STP_ENTITIES];
__xdata uint8_t stp_pspeed[STP_ENTITIES]; /* speed nibble last read from the ASIC */
__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_info_while
* tells whether they are still current.
*/
__xdata struct bridge stp_dbridge[STP_ENTITIES];
__xdata struct bridge stp_droot[STP_ENTITIES];
__xdata uint16_t stp_dpid[STP_ENTITIES];
__xdata uint32_t stp_dcost[STP_ENTITIES];
__xdata uint16_t stp_alt; /* bit per port: blocked, a better bridge owns the segment */
__xdata struct bridge stp_self;
__xdata uint8_t stp_j;
__xdata uint8_t stp_best;
__xdata uint32_t stp_cost_cand;
__xdata uint32_t stp_cost_best;
__xdata int8_t stp_cmp;
__xdata uint8_t stp_rxage[STP_ENTITIES]; /* message age heard on the port, seconds */
__xdata uint8_t stp_rxmaxage[STP_ENTITIES];
__xdata uint8_t stp_rxhello[STP_ENTITIES];
__xdata uint8_t stp_rxfwd[STP_ENTITIES];
__xdata uint16_t stp_info_while[STP_ENTITIES]; /* ticks the heard information stays valid, 0 = none */
__xdata uint8_t stp_root_maxage; /* max age and forward delay of the root */
__xdata uint8_t stp_root_fwd;
__xdata uint8_t stp_reselect_due;
__xdata uint32_t stp_rpcost[STP_ENTITIES]; /* root path cost through the port: heard cost plus port cost */
/* ---- 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_tc_seen;
__xdata uint32_t stp_tc_secs; /* seconds since the topology change counter last moved */
__xdata uint8_t stp_pcost_short; /* 1: automatic port costs from the 802.1D-1998 table */
__xdata uint8_t stp_bpdu_filter; /* 1: keep BPDUs on the CPU while STP is off instead of flooding */
__xdata uint16_t stp_scratch16; /* scratch for status printing only */
__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 uint16_t stp_heard; /* bit per port: a BPDU arrived since the link last went down */
__xdata uint8_t stp_tx_budget[STP_ENTITIES]; /* tx hold: BPDUs left in the current second */
__xdata uint32_t stp_cnt[STP_CNT_N][STP_ENTITIES]; /* per-port counters, first index STP_CNT_* */
__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 uint8_t stp_rxbody;
__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_tcwhile[STP_ENTITIES]; /* ticks left to send TC on the port, 0 = none */
__xdata uint16_t stp_newinfo; /* bit per port: send a BPDU at the next tick */
__xdata uint8_t stp_k;
__xdata uint8_t stp_tcn;
__xdata uint8_t stp_armed;
__xdata uint16_t stp_agree; /* bit per port: answer with an agreement in the next BPDU */
__xdata uint16_t stp_rrwhile[STP_ENTITIES]; /* ticks the port still counts as a recent root port */
__xdata uint16_t stp_rbwhile[STP_ENTITIES]; /* ticks the port still counts as a recent backup port */
__xdata uint16_t stp_reroot; /* bit per port: recent root port held out of forwarding */
__xdata uint8_t stp_synced_root; /* root port the last sync was done for, 0xff = none */
__xdata uint16_t stp_backup; /* bit per port: blocked because another port of ours owns the segment */
__xdata uint16_t stp_alt_agreed; /* bit per port: alternate or backup port that already agreed to the information it holds */
__xdata uint16_t stp_legacy; /* bit per port: the neighbour speaks 802.1D, send Config BPDUs and TCN */
__xdata uint16_t stp_seen_stp; /* bit per port: an 802.1D BPDU arrived during the migrate time */
__xdata uint16_t stp_seen_rstp; /* bit per port: an RST BPDU arrived during the migrate time */
__xdata uint8_t stp_mdelay[STP_ENTITIES]; /* ticks before the port may change protocol again */
__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 */
__xdata uint8_t stp_ent_of[STP_PORTS]; /* entity a port answers to: itself, or STP_LAG_BASE + lag */
__xdata uint16_t stp_lag_mask[STP_LAG_COUNT]; /* member ports of each lag, 0 = no such lag */
__xdata uint16_t stp_map_changed; /* bit per entity: its membership changed, start it over */
__xdata uint16_t stp_link_phys; /* carrier bitmap of the physical ports */
__xdata uint16_t stp_ent_bit;
__xdata uint16_t stp_ss_mask;
__xdata uint8_t stp_ss_i;
__xdata uint8_t stp_lag;
#define STP_EDGE_DELAY (3 * STP_HZ) /* auto-edge: forward after 3 s without BPDU */
#define MAXAGE_S (stp_root_port == 0xff ? stp_maxage_s : stp_root_maxage)
#define FWD_S (stp_root_port == 0xff ? stp_fwddelay_s : stp_root_fwd)
#define FWD_TICKS ((uint16_t)FWD_S * STP_HZ)
#define P2P(i) (stp_pp2p[i] != 2)
#define SEND_RSTP(i) (stp_rstp && !((stp_legacy >> (i)) & 1))
#define STP_MIGRATE (3 * STP_HZ)
#define AUTO_COST 20000UL /* path cost of a link whose speed we cannot read */
#define PCOST(i) (stp_pcost[i] ? stp_pcost[i] : stp_speed_cost[(stp_pcost_short << 3) | (stp_pspeed[i] & 0x7)])
/* Path costs indexed by the speed nibble the ASIC reports: 0 10M, 1 100M,
* 2 1G, 4 10G, 5 2.5G, 6 5G; the rest are unknown to us. Entries 0-7 hold the
* 802.1Q recommended values, 8-15 the 802.1D-1998 ones for the same speeds. */
static __code const uint32_t stp_speed_cost[16] = {
2000000UL, 200000UL, 20000UL, AUTO_COST,
2000UL, 8000UL, 4000UL, AUTO_COST,
100UL, 19UL, 4UL, 4UL,
2UL, 3UL, 3UL, 4UL
};
struct stp_pkt {
uint8_t stp_addr[6];
uint8_t src_addr[6];
struct rtl_tag rtl_tag;
uint16_t msg_len;
uint8_t dsap;
uint8_t ssap;
uint8_t ctrl;
uint16_t proto;
uint8_t version;
uint8_t bpdu_type;
uint8_t flags;
struct bridge root;
uint32_t root_path_cost;
struct bridge bridge;
uint8_t port_prio;
uint8_t port_id;
uint16_t age;
uint16_t age_max;
uint16_t hello;
uint16_t fwd_delay;
uint8_t version1_length; /* RST BPDU only: length of the (empty) v1 part */
};
struct stp_pkt_in_hdr {
uint8_t stp_addr[6];
uint8_t src_addr[6];
struct rtl_tag rtl_tag;
};
struct stp_pkt_in {
uint16_t msg_len;
uint8_t dsap;
uint8_t ssap;
uint8_t ctrl;
uint16_t proto;
uint8_t version;
uint8_t bpdu_type;
uint8_t flags;
struct bridge root;
uint32_t root_path_cost;
struct bridge bridge;
uint8_t port_prio;
uint8_t port_id;
uint16_t age;
uint16_t age_max;
uint16_t hello;
uint16_t fwd_delay;
uint8_t version1_length; /* RST BPDU only: length of the (empty) v1 part */
};
#define STP_O ((__xdata struct stp_pkt *)&uip_buf[RTL_FRAME_DESC_SIZE])
#define STP_H ((__xdata struct stp_pkt_in_hdr *)&uip_buf[0])
#define STP_I ((__xdata struct stp_pkt_in *)&uip_buf[stp_rxbody])
#define BPDU_VER_STP 0x00
#define BPDU_VER_RSTP 0x02
#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_BODY 9
#define BPDU_LEN_FULL_BODY 40
#define BPDU_LEN_TCN 0x07 // LLC and a 4 byte body
#define BPDU_FLAG_TC 0x01
#define BPDU_FLAG_PROPOSAL 0x02
#define BPDU_FLAG_AGREEMENT 0x40
#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)
#define BPDU_ROLE_MASK (0b11 << 2)
#define BPDU_ROLE_ALTBACK (0b01 << 2)
/* Console messages name the port on the front panel, not the internal index. */
static void print_ent(uint8_t e) __reentrant
{
if (e >= STP_LAG_BASE) {
write_char('L');
write_char('1' + e - STP_LAG_BASE);
} else {
print_byte(machine.log_to_phys_port[e]);
}
}
static void print_port_nl(uint8_t port) __reentrant
{
print_ent(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[] = "dis rootdesgaltnback";
static __code const char stp_edge_txt[] = "no yes ";
static uint8_t stp_state_get(uint8_t port) __reentrant;
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;
while (width--)
write_char(*txt++);
}
void stp_status(void) __banked
{
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_ent(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");
for (stp_i = 0; stp_i < STP_ENTITIES; stp_i++) {
if (!stp_ent_active(stp_i))
continue;
write_char(' ');
print_ent(stp_i);
print_string(" ");
print_field(stp_state_txt, stp_state_get(stp_i), 5);
write_char(' ');
print_field(stp_role_txt, stp_port_role(stp_i), 4);
write_char(' ');
print_field(stp_edge_txt, stp_pflags[stp_i] & STP_PF_OPEREDGE ? 1 : 0, 4);
write_char(' ');
print_byte((uint8_t)stp_cnt[STP_CNT_TX][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');
}
}
/* Lexicographic compare of n bytes. A MAC is 6 of them; a Bridge Identifier
* is 8, the two priority octets ahead of the MAC, compared as one unsigned
* number per 802.1D. */
int8_t cmpBytes(__xdata uint8_t *m1, __xdata uint8_t *m2, uint8_t n) __reentrant
{
for (uint8_t i = 0; i < n; i++) {
if (m1[i] == m2[i])
continue;
if (m1[i] < m2[i])
return -1;
return 1;
}
return 0;
}
static int8_t stp_msg_cmp(uint8_t port) __reentrant
{
stp_cmp = cmpBytes((__xdata uint8_t *)&STP_I->root, (__xdata uint8_t *)&stp_droot[port], 8);
if (stp_cmp)
return stp_cmp;
if (stp_cost_scratch != stp_dcost[port])
return stp_cost_scratch < stp_dcost[port] ? -1 : 1;
stp_cmp = cmpBytes((__xdata uint8_t *)&STP_I->bridge, (__xdata uint8_t *)&stp_dbridge[port], 8);
if (stp_cmp)
return stp_cmp;
if (STP_I->port_prio != (uint8_t)(stp_dpid[port] >> 8))
return STP_I->port_prio < (uint8_t)(stp_dpid[port] >> 8) ? -1 : 1;
if (STP_I->port_id != (uint8_t)stp_dpid[port])
return STP_I->port_id < (uint8_t)stp_dpid[port] ? -1 : 1;
return 0;
}
static uint8_t stp_clamp(uint8_t v, uint8_t lo, uint8_t hi, uint8_t dflt) __reentrant
{
return (v < lo || v > hi) ? dflt : v;
}
static void stp_rcv_info(uint8_t port) __reentrant
{
if (STP_I->bpdu_type == BPDU_TYPE_RST
&& (STP_I->flags & BPDU_ROLE_MASK) != BPDU_ROLE_DESIGNATED)
return;
((__xdata uint8_t *)&stp_cost_scratch)[0] = ((__xdata uint8_t *)&STP_I->root_path_cost)[3];
((__xdata uint8_t *)&stp_cost_scratch)[1] = ((__xdata uint8_t *)&STP_I->root_path_cost)[2];
((__xdata uint8_t *)&stp_cost_scratch)[2] = ((__xdata uint8_t *)&STP_I->root_path_cost)[1];
((__xdata uint8_t *)&stp_cost_scratch)[3] = ((__xdata uint8_t *)&STP_I->root_path_cost)[0];
if (stp_info_while[port]
&& (cmpBytes(STP_I->bridge.mac, stp_dbridge[port].mac, 6)
|| STP_I->port_id != (uint8_t)stp_dpid[port])
&& stp_msg_cmp(port) > 0)
return;
if (!stp_info_while[port] || stp_msg_cmp(port))
stp_alt_agreed &= ~((uint16_t)1 << port);
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_droot[port].prio = STP_I->root.prio;
stp_droot[port].ext = STP_I->root.ext;
memcpy(stp_droot[port].mac, STP_I->root.mac, 6);
stp_dpid[port] = ((uint16_t)STP_I->port_prio << 8) | STP_I->port_id;
stp_dcost[port] = stp_cost_scratch;
stp_rxage[port] = (uint8_t)STP_I->age;
stp_rxmaxage[port] = stp_clamp((uint8_t)STP_I->age_max, 6, 40, 20);
stp_rxhello[port] = stp_clamp((uint8_t)STP_I->hello, 1, 10, 2);
stp_rxfwd[port] = stp_clamp((uint8_t)STP_I->fwd_delay, 4, 30, 15);
stp_info_while[port] = (uint16_t)stp_rxage[port] + 1 <= stp_rxmaxage[port]
? (uint16_t)3 * stp_rxhello[port] * STP_HZ : 0;
}
/* Write one port's 2-bit state into the ASIC's MSTP register.
* 00 disable, 01 blocking, 10 learning, 11 forwarding. */
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;
if (!MACHINE_PORT_VALID(e))
return 0;
return stp_ent_of[e] == e;
}
uint8_t stp_ent_id(uint8_t e) __banked
{
if (!stp_ent_active(e))
return 0;
if (e >= STP_LAG_BASE)
return e + 101 - STP_LAG_BASE;
return machine.log_to_phys_port[e];
}
static uint16_t stp_members(uint8_t e) __reentrant
{
return e >= STP_LAG_BASE ? stp_lag_mask[e - STP_LAG_BASE] : (uint16_t)1 << e;
}
static uint8_t stp_first(uint16_t mask) __reentrant
{
for (stp_ss_i = 0; stp_ss_i < STP_PORTS; stp_ss_i++)
if ((mask >> stp_ss_i) & 1)
return stp_ss_i;
return 0xff;
}
static void stp_state_set(uint8_t port, uint8_t state) __reentrant
{
stp_ss_mask = stp_members(port);
reg_read_m(RTL837X_MSTP_STATES);
for (stp_ss_i = 0; stp_ss_i < STP_PORTS; stp_ss_i++) {
if (!((stp_ss_mask >> stp_ss_i) & 1))
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);
}
static uint8_t stp_state_get(uint8_t port) __reentrant
{
if (stp_first(stp_members(port)) == 0xff)
return 0;
reg_read_m(RTL837X_MSTP_STATES);
return (sfr_data[3 - (stp_ss_i >> 2)] >> ((stp_ss_i << 1) & 0x7)) & 0b11;
}
uint8_t stp_port_state(uint8_t port) __banked
{
return stp_state_get(port);
}
static void stp_forget(uint8_t e) __reentrant
{
stp_ss_mask = stp_members(e);
for (stp_ss_i = 0; stp_ss_i < STP_PORTS; stp_ss_i++)
if ((stp_ss_mask >> stp_ss_i) & 1)
port_l2_forget_port(stp_ss_i);
}
static void stp_new_tc_while(uint8_t port) __reentrant
{
if (stp_tcwhile[port])
return;
stp_tcwhile[port] = SEND_RSTP(port) ? ((uint16_t)stp_hello_s + 1) * STP_HZ
: (uint16_t)MAXAGE_S * STP_HZ + FWD_TICKS;
stp_newinfo |= (uint16_t)1 << port;
}
static uint8_t stp_tc_prop(uint8_t from) __reentrant
{
stp_armed = 0;
for (stp_k = 0; stp_k < STP_ENTITIES; stp_k++) {
if (stp_k == from || !stp_ent_active(stp_k) || stp_tcwhile[stp_k] || stp_state_get(stp_k) != 0b11)
continue;
if (!(stp_pflags[stp_k] & STP_PF_ENABLED) || (stp_pflags[stp_k] & STP_PF_OPEREDGE))
continue;
stp_forget(stp_k);
stp_new_tc_while(stp_k);
stp_armed = 1;
}
return stp_armed;
}
static void stp_tc_detected(uint8_t port) __reentrant
{
if (stp_pflags[port] & STP_PF_OPEREDGE)
return;
stp_tc_count++;
stp_new_tc_while(port);
stp_tc_prop(port);
}
static void stp_forward_now(uint8_t port) __reentrant
{
port_timers[port] = 0;
stp_loop_held[port] = 0;
stp_backup &= ~((uint16_t)1 << port);
stp_state_set(port, 0b11);
print_string("STP: rapid transition, port forwarding ");
print_port_nl(port);
stp_tc_detected(port);
}
static void stp_sync(uint8_t from) __reentrant
{
for (stp_k = 0; stp_k < STP_ENTITIES; stp_k++) {
if (!stp_ent_active(stp_k))
continue;
if (stp_k == from || stp_k == stp_root_port || !(stp_pflags[stp_k] & STP_PF_ENABLED)
|| (stp_pflags[stp_k] & STP_PF_OPEREDGE) || ((stp_alt >> stp_k) & 1)
|| !((stp_link_prev >> stp_k) & 1))
continue;
if (stp_state_get(stp_k) != 0b01) {
stp_state_set(stp_k, 0b01);
port_timers[stp_k] = FWD_TICKS;
}
stp_newinfo |= (uint16_t)1 << stp_k;
}
if (from != stp_root_port)
return;
stp_synced_root = from;
if (port_timers[from])
stp_forward_now(from);
}
/* 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 (!stp_ent_active(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_forget(port);
stp_tcwhile[port] = 0;
}
stp_loop_held[port] = 1;
stp_backup |= (uint16_t)1 << port;
port_timers[port] = FWD_TICKS;
}
/* 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;
}
static uint8_t stp_info_fresh(uint8_t port) __reentrant
{
return stp_info_while[port] != 0;
}
static int8_t stp_cmp_root_path(uint8_t a, uint8_t b) __reentrant
{
stp_cmp = cmpBytes((__xdata uint8_t *)&stp_droot[a], (__xdata uint8_t *)&stp_droot[b], 8);
if (stp_cmp)
return stp_cmp;
if (stp_rpcost[a] != stp_rpcost[b])
return stp_rpcost[a] < stp_rpcost[b] ? -1 : 1;
stp_cmp = cmpBytes((__xdata uint8_t *)&stp_dbridge[a], (__xdata uint8_t *)&stp_dbridge[b], 8);
if (stp_cmp)
return stp_cmp;
if (stp_dpid[a] != stp_dpid[b])
return stp_dpid[a] < stp_dpid[b] ? -1 : 1;
if (stp_pprio[a] != stp_pprio[b])
return stp_pprio[a] < stp_pprio[b] ? -1 : 1;
return a < b ? -1 : 1;
}
static int8_t stp_cmp_designated(uint8_t port) __reentrant
{
stp_cmp = cmpBytes((__xdata uint8_t *)&stp_droot[port], (__xdata uint8_t *)&root_bridge, 8);
if (stp_cmp)
return stp_cmp;
if (stp_dcost[port] != root_bridge_cost)
return stp_dcost[port] < root_bridge_cost ? -1 : 1;
stp_self.prio = stp_prio;
stp_self.ext = 0x00;
memcpy(stp_self.mac, uip_ethaddr.addr, 6);
stp_cmp = cmpBytes((__xdata uint8_t *)&stp_dbridge[port], (__xdata uint8_t *)&stp_self, 8);
if (stp_cmp)
return stp_cmp;
if (stp_dpid[port] == (((uint16_t)stp_pprio[port] << 8) | (port + 1)))
return 0;
return stp_dpid[port] < (((uint16_t)stp_pprio[port] << 8) | (port + 1)) ? -1 : 1;
}
static void stp_reroot_tree(void) __reentrant
{
for (stp_k = 0; stp_k < STP_ENTITIES; stp_k++) {
if (!stp_ent_active(stp_k))
continue;
if (stp_k == stp_root_port || !stp_rrwhile[stp_k]
|| !(stp_pflags[stp_k] & STP_PF_ENABLED) || (stp_pflags[stp_k] & STP_PF_OPEREDGE))
continue;
stp_reroot |= (uint16_t)1 << stp_k;
if (stp_state_get(stp_k) != 0b01) {
stp_state_set(stp_k, 0b01);
port_timers[stp_k] = FWD_TICKS;
stp_newinfo |= (uint16_t)1 << stp_k;
}
}
}
static void stp_reselect(void)
{
stp_self.prio = stp_prio;
stp_self.ext = 0x00;
memcpy(stp_self.mac, uip_ethaddr.addr, 6);
stp_best = 0xff;
for (stp_j = 0; stp_j < STP_ENTITIES; stp_j++) {
if (!stp_ent_active(stp_j))
continue;
if (!(stp_pflags[stp_j] & STP_PF_ENABLED) || (stp_pflags[stp_j] & STP_PF_ROOTGUARD))
continue;
if (!((stp_link_now >> stp_j) & 1) || !stp_info_fresh(stp_j))
continue;
if (!cmpBytes(stp_dbridge[stp_j].mac, uip_ethaddr.addr, 6))
continue;
if (cmpBytes((__xdata uint8_t *)&stp_droot[stp_j], (__xdata uint8_t *)&stp_self, 8) >= 0)
continue;
stp_rpcost[stp_j] = stp_dcost[stp_j] + PCOST(stp_j);
if (stp_best == 0xff || stp_cmp_root_path(stp_j, stp_best) < 0)
stp_best = stp_j;
}
if (stp_best != 0xff) {
if (stp_root_port != stp_best
|| cmpBytes((__xdata uint8_t *)&root_bridge, (__xdata uint8_t *)&stp_droot[stp_best], 8)) {
if (cmpBytes((__xdata uint8_t *)&root_bridge, (__xdata uint8_t *)&stp_droot[stp_best], 8))
print_string("Updating Root bridge\n");
stp_synced_root = 0xff;
stp_root_port = stp_best;
root_bridge.prio = stp_droot[stp_best].prio;
root_bridge.ext = stp_droot[stp_best].ext;
memcpy(root_bridge.mac, stp_droot[stp_best].mac, 6);
stp_tc_count++;
}
root_bridge_cost = stp_rpcost[stp_best];
stp_msg_age = stp_rxage[stp_best];
stp_root_maxage = stp_rxmaxage[stp_best];
stp_root_fwd = stp_rxfwd[stp_best];
} else if (stp_root_port != 0xff) {
print_string("STP: root aged out, claiming root\n");
stp_synced_root = 0xff;
stp_claim_root();
stp_tc_count++;
}
for (stp_j = 0; stp_j < STP_ENTITIES; stp_j++) {
if (!stp_ent_active(stp_j))
continue;
if (!(stp_pflags[stp_j] & STP_PF_ENABLED))
continue;
if (stp_j != stp_root_port && stp_info_fresh(stp_j)
&& stp_cmp_designated(stp_j) < 0) {
stp_pflags[stp_j] &= ~STP_PF_OPEREDGE;
port_timers[stp_j] = 0;
stp_state_set(stp_j, 0b01);
if (!((stp_alt >> stp_j) & 1)) {
stp_alt |= (uint16_t)1 << stp_j;
stp_forget(stp_j);
stp_tcwhile[stp_j] = 0;
print_string("STP: better bridge on the segment, blocking port ");
print_port_nl(stp_j);
}
continue;
}
if (!((stp_alt >> stp_j) & 1))
continue;
stp_alt &= ~((uint16_t)1 << stp_j);
stp_alt_agreed &= ~((uint16_t)1 << stp_j);
port_timers[stp_j] = FWD_TICKS;
print_string("STP: port released, listening ");
print_port_nl(stp_j);
}
if (stp_rstp && stp_root_port != 0xff && port_timers[stp_root_port]
&& !((stp_alt >> stp_root_port) & 1) && !stp_rbwhile[stp_root_port]) {
stp_reroot_tree();
stp_forward_now(stp_root_port);
}
}
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_link_prev >> port) & 1) || !(stp_members(port) & stp_link_phys)) {
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_cnt[STP_CNT_TX][port]++;
stp_tcn = !SEND_RSTP(port) && port == stp_root_port;
STP_O->stp_addr[0] = 0x01; STP_O->stp_addr[1] = 0x80; STP_O->stp_addr[2] = 0xc2;
STP_O->stp_addr[3] = STP_O->stp_addr[4] = STP_O->stp_addr[5] = 0x00;
STP_O->rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID);
STP_O->rtl_tag.version = RTL_FRAME_TAG_VERSION;
STP_O->rtl_tag.reason = 0x00;
STP_O->rtl_tag.flags = HTONS(RTL_TAG_LEARN_DIS);
STP_O->rtl_tag.pmask = HTONS(((uint16_t)1) << stp_first(stp_members(port) & stp_link_phys));
STP_O->dsap = 0x42;
STP_O->ssap = 0x42;
STP_O->ctrl = 0x03;
STP_O->proto = 0x0000;
if (stp_tcn) {
STP_O->msg_len = HTONS(BPDU_LEN_TCN);
STP_O->version = BPDU_VER_STP;
STP_O->bpdu_type = BPDU_TYPE_TCN;
STP_O->flags = 0x00;
} else if (SEND_RSTP(port)) {
STP_O->msg_len = HTONS(BPDU_LEN_RST);
STP_O->version = BPDU_VER_RSTP;
STP_O->bpdu_type = BPDU_TYPE_RST;
STP_O->flags = port == stp_root_port ? BPDU_ROLE_ROOT
: (((stp_alt | stp_backup) >> port) & 1) ? BPDU_ROLE_ALTBACK : BPDU_ROLE_DESIGNATED;
stp_scratch = stp_state_get(port);
if (stp_scratch == 0b11)
STP_O->flags |= BPDU_FLAG_LEARNING | BPDU_FLAG_FORWARDING;
else if (stp_scratch == 0b10)
STP_O->flags |= BPDU_FLAG_LEARNING;
if (port != stp_root_port && port_timers[port] && P2P(port)
&& !(stp_pflags[port] & STP_PF_OPEREDGE) && !(((stp_alt | stp_backup) >> port) & 1))
STP_O->flags |= BPDU_FLAG_PROPOSAL;
if ((stp_agree >> port) & 1) {
STP_O->flags |= BPDU_FLAG_AGREEMENT;
stp_agree &= ~((uint16_t)1 << port);
}
} 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_tcwhile[port])
STP_O->flags |= BPDU_FLAG_TC;
STP_O->flags |= stp_tx_flags_extra;
stp_tx_flags_extra = 0;
if (stp_tcn || (STP_O->flags & BPDU_FLAG_TC))
stp_cnt[STP_CNT_TCTX][port]++;
memcpy(STP_O->src_addr, uip_ethaddr.addr, 6);
STP_O->src_addr[0] |= 0x02;
STP_O->src_addr[5] = (uip_ethaddr.addr[5] & 0xf0) | port;
memcpy(STP_O->root.mac, root_bridge.mac, 6);
memcpy(STP_O->bridge.mac, uip_ethaddr.addr, 6);
STP_O->root.prio = root_bridge.prio;
STP_O->root.ext = root_bridge.ext;
/* Our root path cost, big-endian (0 while we are the root ourselves) */
STP_O->root_path_cost = ((root_bridge_cost & 0xff) << 24)
| ((root_bridge_cost & 0xff00) << 8)
| ((root_bridge_cost >> 8) & 0xff00)
| (root_bridge_cost >> 24);
STP_O->bridge.prio = stp_prio;
STP_O->bridge.ext = 0x00;
STP_O->port_prio = stp_pprio[port];
STP_O->port_id = port + 1;
/* Message age, incremented by one second per bridge we relay through.
* The timer fields are in 1/256 s on the wire, and sdcc stores uint16
* little-endian, so assigning the plain second count lands the value in
* the high (seconds) octet - see age_max/hello/fwd_delay below. */
STP_O->age = (stp_root_port == 0xff) ? 0 : (uint16_t)(stp_msg_age + 1);
STP_O->age_max = MAXAGE_S;
STP_O->hello = stp_hello_s;
STP_O->fwd_delay = FWD_S;
STP_O->version1_length = 0; /* RST BPDU: no version-1 information */
uip_len = stp_tcn ? sizeof(struct stp_pkt) - 32
: (SEND_RSTP(port) ? sizeof(struct stp_pkt) : sizeof(struct stp_pkt) - 1);
tcpip_output();
}
static uint8_t stp_len_ok(void) __reentrant
{
if (STP_I->bpdu_type == BPDU_TYPE_TCN)
return HTONS(STP_I->msg_len) >= BPDU_LEN_TCN;
if (STP_I->bpdu_type == BPDU_TYPE_RST)
return HTONS(STP_I->msg_len) >= BPDU_LEN_RST;
return HTONS(STP_I->msg_len) >= BPDU_LEN_CONFIG;
}
static void stp_rx_seen(uint8_t port) __reentrant
{
stp_bpdu_age[port] = 0;
stp_cnt[STP_CNT_RX][port]++;
if (stp_len_ok() && (STP_I->bpdu_type == BPDU_TYPE_TCN || (STP_I->flags & BPDU_FLAG_TC)))
stp_cnt[STP_CNT_TCRX][port]++;
if (!stp_rstp)
return;
if (STP_I->version < BPDU_VER_RSTP)
stp_seen_stp |= (uint16_t)1 << port;
else
stp_seen_rstp |= (uint16_t)1 << port;
}
static void stp_migrate_check(uint8_t port) __reentrant
{
if (stp_mdelay[port]) {
stp_mdelay[port]--;
return;
}
if (!((stp_legacy >> port) & 1) && ((stp_seen_stp >> port) & 1)) {
stp_legacy |= (uint16_t)1 << port;
print_string("STP: 802.1D neighbour, sending STP on port ");
} else if (((stp_legacy >> port) & 1) && ((stp_seen_rstp >> port) & 1)) {
stp_legacy &= ~((uint16_t)1 << port);
print_string("STP: RSTP neighbour, sending RSTP on port ");
} else {
return;
}
print_port_nl(port);
stp_seen_stp &= ~((uint16_t)1 << port);
stp_seen_rstp &= ~((uint16_t)1 << port);
stp_mdelay[port] = STP_MIGRATE;
stp_newinfo |= (uint16_t)1 << port;
}
static int8_t stp_msg_vs_ours(uint8_t port) __reentrant
{
stp_cmp = cmpBytes((__xdata uint8_t *)&STP_I->root, (__xdata uint8_t *)&root_bridge, 8);
if (stp_cmp)
return stp_cmp;
((__xdata uint8_t *)&stp_cost_scratch)[0] = ((__xdata uint8_t *)&STP_I->root_path_cost)[3];
((__xdata uint8_t *)&stp_cost_scratch)[1] = ((__xdata uint8_t *)&STP_I->root_path_cost)[2];
((__xdata uint8_t *)&stp_cost_scratch)[2] = ((__xdata uint8_t *)&STP_I->root_path_cost)[1];
((__xdata uint8_t *)&stp_cost_scratch)[3] = ((__xdata uint8_t *)&STP_I->root_path_cost)[0];
if (stp_cost_scratch != root_bridge_cost)
return stp_cost_scratch < root_bridge_cost ? -1 : 1;
stp_self.prio = stp_prio;
stp_self.ext = 0x00;
memcpy(stp_self.mac, uip_ethaddr.addr, 6);
stp_cmp = cmpBytes((__xdata uint8_t *)&STP_I->bridge, (__xdata uint8_t *)&stp_self, 8);
if (stp_cmp)
return stp_cmp;
if (STP_I->port_prio != stp_pprio[port])
return STP_I->port_prio < stp_pprio[port] ? -1 : 1;
if (STP_I->port_id != port + 1)
return STP_I->port_id < port + 1 ? -1 : 1;
return 0;
}
static void stp_dispute_rx(uint8_t port) __reentrant
{
if (port == stp_root_port || ((stp_alt >> port) & 1))
return;
if (STP_I->bpdu_type == BPDU_TYPE_RST
&& (STP_I->flags & BPDU_ROLE_MASK) != BPDU_ROLE_DESIGNATED)
return;
if (stp_msg_vs_ours(port) <= 0)
return;
stp_newinfo |= (uint16_t)1 << port;
if (!stp_rstp || STP_I->bpdu_type != BPDU_TYPE_RST
|| !(STP_I->flags & BPDU_FLAG_LEARNING))
return;
if (stp_state_get(port) != 0b01) {
stp_state_set(port, 0b01);
print_string("STP: dispute, port discarding ");
print_port_nl(port);
}
port_timers[port] = FWD_TICKS;
}
static void stp_rapid_rx(uint8_t port) __reentrant
{
if (stp_rstp && STP_I->bpdu_type == BPDU_TYPE_RST && P2P(port)) {
if (port == stp_root_port
&& (STP_I->flags & BPDU_ROLE_MASK) == BPDU_ROLE_DESIGNATED
&& (STP_I->flags & BPDU_FLAG_PROPOSAL)) {
if (stp_synced_root != port)
stp_sync(port);
stp_agree |= (uint16_t)1 << port;
stp_newinfo |= (uint16_t)1 << port;
} else if ((((stp_alt | stp_backup) >> port) & 1)
&& (STP_I->flags & BPDU_ROLE_MASK) == BPDU_ROLE_DESIGNATED
&& (STP_I->flags & BPDU_FLAG_PROPOSAL)) {
if (!((stp_alt_agreed >> port) & 1))
stp_sync(port);
stp_alt_agreed |= (uint16_t)1 << port;
stp_agree |= (uint16_t)1 << port;
stp_newinfo |= (uint16_t)1 << port;
} else if (port != stp_root_port && port_timers[port]
&& !((stp_alt >> port) & 1)
&& !(((stp_reroot >> port) & 1) && stp_rrwhile[port])
&& ((STP_I->flags & BPDU_ROLE_MASK) == BPDU_ROLE_ROOT
|| (STP_I->flags & BPDU_ROLE_MASK) == BPDU_ROLE_ALTBACK)
&& (STP_I->flags & BPDU_FLAG_AGREEMENT)
&& !cmpBytes((__xdata uint8_t *)&STP_I->root, (__xdata uint8_t *)&root_bridge, 8)) {
stp_forward_now(port);
}
}
}
void stp_in(void) __banked
{
uint8_t port;
stp_rxbody = TRAP_RX_BODY();
if (uip_len < stp_rxbody + BPDU_LEN_MIN_BODY) {
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_H->rtl_tag.pmask)) & 0x0f;
if (!MACHINE_PORT_VALID(stp_scratch))
return;
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))
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_rx_seen(port);
/* 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 topology changes skip
* edge ports and 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;
stp_heard |= (uint16_t)1 << port;
if (!stp_len_ok())
return;
if (STP_I->bpdu_type == BPDU_TYPE_TCN) {
if (stp_state_get(port) != 0b11)
return;
stp_tx_flags_extra = BPDU_FLAG_TCACK;
stp_cnf_send(port);
uip_len = 0;
stp_tc_count++;
stp_new_tc_while(port);
stp_tc_prop(port);
return;
}
/* Everything below reads the full Config/RST body. */
if (stp_rxlen < stp_rxbody + BPDU_LEN_FULL_BODY)
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;
}
if ((STP_I->flags & BPDU_FLAG_TCACK) && port == stp_root_port)
stp_tcwhile[port] = 0;
if ((STP_I->flags & BPDU_FLAG_TC) && stp_state_get(port) == 0b11 && stp_tc_prop(port))
stp_tc_count++;
if ((stp_pflags[port] & STP_PF_ROOTGUARD)
&& cmpBytes((__xdata uint8_t *)&STP_I->root, (__xdata uint8_t *)&root_bridge, 8) < 0) {
print_string("STP: root guard blocking port ");
print_port_nl(port);
stp_state_set(port, 0b01);
port_timers[port] = FWD_TICKS;
stp_pflags[port] &= ~STP_PF_OPEREDGE;
return;
}
stp_rcv_info(port);
stp_reselect();
stp_rapid_rx(port);
stp_dispute_rx(port);
}
static void stp_tc_clock(void) __reentrant
{
if (stp_tc_seen != stp_tc_count) {
stp_tc_seen = stp_tc_count;
stp_tc_secs = 0;
} else if (stp_tc_secs != 0xffffffffUL) {
stp_tc_secs++;
}
}
void stp_lag_map(void) __banked
{
for (stp_ss_i = 0; stp_ss_i < STP_PORTS; stp_ss_i++)
stp_ent_of[stp_ss_i] = stp_ss_i;
for (stp_lag = 0; stp_lag < STP_LAG_COUNT; stp_lag++) {
stp_ss_mask = port_lag_members_get(stp_lag);
if (stp_ss_mask != stp_lag_mask[stp_lag]) {
stp_map_changed |= stp_ss_mask | stp_lag_mask[stp_lag]
| (uint16_t)1 << (STP_LAG_BASE + stp_lag);
stp_lag_mask[stp_lag] = stp_ss_mask;
}
for (stp_ss_i = 0; stp_ss_i < STP_PORTS; stp_ss_i++)
if ((stp_ss_mask >> stp_ss_i) & 1)
stp_ent_of[stp_ss_i] = STP_LAG_BASE + stp_lag;
}
}
static void stp_links_read(void) __reentrant
{
reg_read_m(RTL837X_REG_LINKS_STS);
stp_link_phys = (uint16_t)sfr_data[1] | ((uint16_t)sfr_data[2] << 8);
stp_link_now = 0;
for (stp_k = 0; stp_k < STP_ENTITIES; stp_k++) {
if (!stp_ent_active(stp_k) || stp_first(stp_members(stp_k) & stp_link_phys) == 0xff)
continue;
stp_link_now |= (uint16_t)1 << stp_k;
if (stp_k >= STP_LAG_BASE)
stp_pspeed[stp_k] = stp_pspeed[stp_ss_i];
}
}
static void stp_ent_reset(uint8_t e) __reentrant
{
stp_ent_bit = (uint16_t)1 << e;
stp_alt &= ~stp_ent_bit;
stp_backup &= ~stp_ent_bit;
stp_alt_agreed &= ~stp_ent_bit;
stp_legacy &= ~stp_ent_bit;
stp_seen_stp &= ~stp_ent_bit;
stp_seen_rstp &= ~stp_ent_bit;
stp_agree &= ~stp_ent_bit;
stp_reroot &= ~stp_ent_bit;
stp_heard &= ~stp_ent_bit;
stp_newinfo &= ~stp_ent_bit;
stp_link_prev = (stp_link_prev & ~stp_ent_bit) | (stp_link_now & stp_ent_bit);
stp_pflags[e] &= ~(STP_PF_OPEREDGE | STP_PF_TRIPPED);
stp_loop_held[e] = 0;
stp_bpdu_age[e] = 0;
stp_tx_budget[e] = stp_txhold;
stp_info_while[e] = 0;
stp_tcwhile[e] = 0;
stp_mdelay[e] = STP_MIGRATE;
stp_rrwhile[e] = 0;
stp_rbwhile[e] = 0;
port_timers[e] = 0;
port_hello[e] = (uint16_t)stp_hello_s * STP_HZ;
if (!stp_ent_active(e))
return;
if (!(stp_pflags[e] & STP_PF_ENABLED) || (stp_pflags[e] & STP_PF_ADMEDGE)) {
if (stp_pflags[e] & STP_PF_ADMEDGE)
stp_pflags[e] |= STP_PF_OPEREDGE;
stp_state_set(e, 0b11);
} else {
stp_state_set(e, 0b01);
port_timers[e] = FWD_TICKS;
}
}
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 = 0; stp_i < STP_ENTITIES; stp_i++)
stp_tx_budget[stp_i] = stp_txhold;
stp_tc_clock();
/* 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);
for (stp_i = MACHINE_MIN_PORT; stp_i <= MACHINE_MAX_PORT; stp_i++) {
if (stp_i == 8)
reg_read_m(RTL837X_REG_LINKS_89);
stp_pspeed[stp_i] = (stp_i & 1)
? (sfr_data[3 - ((stp_i & 7) >> 1)] >> 4)
: (sfr_data[3 - ((stp_i & 7) >> 1)] & 0xf);
}
stp_lag_map();
stp_links_read();
if (stp_map_changed) {
for (stp_i = 0; stp_i < STP_ENTITIES; stp_i++)
if ((stp_map_changed >> stp_i) & 1)
stp_ent_reset(stp_i);
stp_map_changed = 0;
}
if (stp_link_now != stp_link_prev) {
for (stp_i = 0; stp_i < STP_ENTITIES; stp_i++) {
if (!stp_ent_active(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);
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] = FWD_TICKS;
stp_pflags[stp_i] &= ~STP_PF_OPEREDGE;
stp_alt &= ~((uint16_t)1 << stp_i);
stp_bpdu_age[stp_i] = 0;
stp_newinfo |= (uint16_t)1 << stp_i;
stp_legacy &= ~((uint16_t)1 << stp_i);
stp_seen_stp &= ~((uint16_t)1 << stp_i);
stp_seen_rstp &= ~((uint16_t)1 << stp_i);
stp_mdelay[stp_i] = STP_MIGRATE;
} else {
port_timers[stp_i] = 0;
stp_info_while[stp_i] = 0;
stp_rrwhile[stp_i] = 0;
stp_reroot &= ~((uint16_t)1 << stp_i);
stp_backup &= ~((uint16_t)1 << stp_i);
stp_alt_agreed &= ~((uint16_t)1 << stp_i);
stp_heard &= ~((uint16_t)1 << stp_i);
stp_alt &= ~((uint16_t)1 << stp_i);
print_string("STP: link down, port blocking ");
print_port_nl(stp_i);
stp_forget(stp_i);
stp_tcwhile[stp_i] = 0;
}
}
stp_link_prev = stp_link_now;
}
stp_reselect();
}
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)
stp_bpdu_age[stp_i]++;
if (stp_info_while[stp_i] && !--stp_info_while[stp_i])
stp_reselect_due = 1;
if (stp_tcwhile[stp_i])
stp_tcwhile[stp_i]--;
if (stp_i == stp_root_port)
stp_rrwhile[stp_i] = FWD_TICKS;
else if (stp_rrwhile[stp_i] && !--stp_rrwhile[stp_i])
stp_reroot &= ~((uint16_t)1 << stp_i);
if ((stp_backup >> stp_i) & 1)
stp_rbwhile[stp_i] = (uint16_t)2 * stp_hello_s * STP_HZ;
else if (stp_rbwhile[stp_i])
stp_rbwhile[stp_i]--;
stp_migrate_check(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_agree >> stp_i) & 1) || (!((stp_alt >> stp_i) & 1) && (stp_i != stp_root_port || stp_tcwhile[stp_i])))
stp_cnf_send(stp_i);
}
if ((stp_newinfo >> stp_i) & 1) {
stp_newinfo &= ~((uint16_t)1 << stp_i);
if (((stp_agree >> stp_i) & 1) || (!((stp_alt >> stp_i) & 1) && (stp_i != stp_root_port || stp_tcwhile[stp_i])))
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] && ((stp_alt >> stp_i) & 1))
port_timers[stp_i] = 0;
if (port_timers[stp_i]) {
if (!--port_timers[stp_i]) {
if (((stp_reroot >> stp_i) & 1) && stp_rrwhile[stp_i]) {
port_timers[stp_i] = stp_rrwhile[stp_i] + 1;
} else if (stp_state_get(stp_i) == 0b01) {
stp_state_set(stp_i, 0b10);
port_timers[stp_i] = FWD_TICKS;
print_string("STP: port learning ");
print_port_nl(stp_i);
} else {
stp_loop_held[stp_i] = 0;
stp_backup &= ~((uint16_t)1 << stp_i);
stp_state_set(stp_i, 0b11);
print_string("STP: port forwarding ");
print_port_nl(stp_i);
stp_tc_detected(stp_i);
}
} else if ((stp_pflags[stp_i] & STP_PF_AUTOEDGE)
&& !stp_loop_held[stp_i]
&& !((stp_heard >> stp_i) & 1)
&& 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_reselect_due) {
stp_reselect_due = 0;
stp_reselect();
}
}
/* 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;
stp_pcost_short = 0;
stp_bpdu_filter = 0;
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_changed = 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;
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_info_while[stp_i] = 0;
stp_tcwhile[stp_i] = 0;
stp_mdelay[stp_i] = 0;
stp_rrwhile[stp_i] = 0;
stp_rbwhile[stp_i] = 0;
}
stp_reroot = 0;
stp_newinfo = 0;
stp_legacy = 0;
stp_seen_stp = 0;
stp_seen_rstp = 0;
stp_agree = 0;
stp_synced_root = 0xff;
stp_backup = 0;
stp_alt_agreed = 0;
stp_heard = 0;
stp_alt = 0;
stp_tc_count = 0;
stp_claim_root();
}
/*
* Restore BPDU flooding when STP stops.
*/
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);
}
}
static void stp_times_check(void) __reentrant
{
if ((uint16_t)2 * (stp_fwddelay_s - 1) >= stp_maxage_s
&& stp_maxage_s >= (uint16_t)2 * (stp_hello_s + 1))
return;
print_string("STP: timers break 2*(fwd-1) >= maxage >= 2*(hello+1), using 2/20/15\n");
stp_hello_s = 2;
stp_maxage_s = 20;
stp_fwddelay_s = 15;
}
void stp_counters_clear(void) __banked __reentrant
{
for (uint8_t i = 0; i < STP_CNT_N * STP_ENTITIES; i++)
((__xdata uint32_t *)stp_cnt)[i] = 0;
}
void stp_setup(void) __banked
{
stp_times_check();
stp_counters_clear();
stp_tc_seen = stp_tc_count;
stp_tc_secs = 0;
print_string("Enabling STP: ");
stp_claim_root();
stp_newinfo = 0;
stp_reroot = 0;
stp_legacy = 0;
stp_seen_stp = 0;
stp_seen_rstp = 0;
stp_agree = 0;
stp_synced_root = 0xff;
stp_backup = 0;
stp_alt_agreed = 0;
sfr_data[0] = sfr_data[1] = sfr_data[2] = sfr_data[3] = 0;
sfr_data[1] |= 0x0c; // Do not block the CPU port (bits 3:2 of byte 1 = port 9)
reg_write_m(RTL837X_MSTP_STATES);
stp_lag_map();
stp_links_read();
stp_link_prev = stp_link_now;
stp_map_changed = 0;
for (stp_i = 0; stp_i < STP_ENTITIES; stp_i++)
stp_ent_reset(stp_i);
print_reg(RTL837X_MSTP_STATES); write_char('\n');
trap_rma_set(RTL837X_RMA0_CONF, RTL837X_RMA_ACT_TRAP);
}
void stp_off(void) __banked
{
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++)
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_loop_held[stp_i] = 0;
port_timers[stp_i] = 0;
}
sfr_data[1] |= 0x0c; // Do not block the CPU port (bits 3:2 of byte 1 = port 9)
reg_write_m(RTL837X_MSTP_STATES);
if (stp_bpdu_filter) {
stp_fdb_update(PMASK_CPU);
trap_rma_set(RTL837X_RMA0_CONF, RTL837X_RMA_ACT_DROP);
} else {
/* Restore BPDU transparency: flood them again like an unmanaged switch. */
stp_fdb_update(PMASK_CPU | (machine_detected.isRTL8373 ? PMASK_9 : PMASK_6));
trap_rma_set(RTL837X_RMA0_CONF, RTL837X_RMA_ACT_FORWARD);
}
}
void stp_port_admin(uint8_t port, uint8_t on) __banked
{
if (on) {
stp_state_set(port, 0b01);
port_timers[port] = FWD_TICKS;
} else {
stp_state_set(port, 0b11);
}
}
uint8_t stp_port_role(uint8_t port) __banked
{
if (!(stp_pflags[port] & STP_PF_ENABLED) || (stp_pflags[port] & STP_PF_TRIPPED)
|| !((stp_link_prev >> port) & 1))
return 0;
if (port == stp_root_port)
return 1;
if ((stp_backup >> port) & 1)
return 4;
if ((stp_alt >> port) & 1)
return 3;
return 2;
}
void stp_port_mcheck(uint8_t port) __banked
{
stp_legacy &= ~((uint16_t)1 << port);
stp_seen_stp &= ~((uint16_t)1 << port);
stp_seen_rstp &= ~((uint16_t)1 << port);
stp_mdelay[port] = STP_MIGRATE;
stp_newinfo |= (uint16_t)1 << port;
}
void stp_prio_apply(void) __banked
{
if (stp_root_port == 0xff)
stp_claim_root();
}