mirror of
https://github.com/logicog/RTLPlayground.git
synced 2026-10-29 17:54:14 +08:00
stp_in() propagated the TC flag of any BPDU that did not arrive on an alternate port, and acknowledged and propagated a TCN from any port, so a port still in its listen period or held discarding in a dispute could flush the addresses of every other port and move the topology change counter. 802.1D-2004 17.31 keeps the Topology Change machine in LEARNING until the port forwards and drops rcvdTc and rcvdTcn there. Both paths now ask for the forwarding state first; an alternate port is discarding, so the old check is covered by the new one. Scenario 53 plays a TC flag and a TCN into a port that is still discarding, and then into the same port once it forwards. Scenario 51 used a TCN on a port one tick short of its edge transition as the trigger for the clock it tests; it now waits for the transition.
1521 lines
48 KiB
C
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_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();
|
|
}
|