mirror of
https://github.com/logicog/RTLPlayground.git
synced 2026-10-29 17:54:14 +08:00
* stp: select the root port by cost and block redundant links * stp: move the protocol to BANK4 and keep the command line in BANK2 * stp: implement the RSTP state machines of 802.1D-2004 clause 17 * stp: counters, more settings and a fuller Spanning Tree page * stp: make a link aggregation group one port of the RSTP tree * web: serve the Spanning Tree JSON from BANK4 as well Kept in BANK1, the larger Spanning Tree page would leave under 1 kB there for the rest of the web server and every page still to come, so send_stp() and send_stp_counters() move to httpd/page_stp.c in BANK4 with the protocol, as banked calls, with their own copies of the few output helpers and of the JSON header, so nothing in BANK4 calls into BANK1 directly or hands it a pointer into its code space. BANK4 takes the free flash between BANK3 and the web files. KP_9000_6XHML_X2: BANK1 44336, BANK2 36969, BANK3 11942, BANK4 20965 bytes of 49152.
1519 lines
48 KiB
C
1519 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_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) {
|
|
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_alt >> port) & 1) && 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();
|
|
}
|