From a6b09188852a37d747794a851c720ff0e54f20a6 Mon Sep 17 00:00:00 2001 From: HL Yi Date: Thu, 8 Oct 2026 18:43:05 -0500 Subject: [PATCH 01/84] refactoring machine.c, two files per device --- .github/workflows/build.yml | 2 +- Makefile | 47 +- machine.c | 1469 --------------------------- machine.h | 58 +- machine/DEFAULT_8C_1SFP.c | 25 + machine/DEFAULT_8C_1SFP.h | 6 + machine/F7008_2_5.c | 41 + machine/F7008_2_5.h | 6 + machine/FG_4GT_2SX_V2_0.c | 66 ++ machine/FG_4GT_2SX_V2_0.h | 6 + machine/FG_8GT_1SX.c | 45 + machine/FG_8GT_1SX.h | 6 + machine/FNS1200P.c | 55 + machine/FNS1200P.h | 6 + machine/HASIVO_S1100WP_8GT_1SX_SE.c | 32 + machine/HASIVO_S1100WP_8GT_1SX_SE.h | 6 + machine/HASIVO_S600W_4GT_2XGT_SE.c | 43 + machine/HASIVO_S600W_4GT_2XGT_SE.h | 6 + machine/HG0402XG_V1_1.c | 38 + machine/HG0402XG_V1_1.h | 6 + machine/HI_K0402WS.c | 46 + machine/HI_K0402WS.h | 6 + machine/HI_K0801WS.c | 45 + machine/HI_K0801WS.h | 6 + machine/K0501W_V2_0.c | 34 + machine/K0501W_V2_0.h | 6 + machine/KP_9000_6XHML_X2.c | 37 + machine/KP_9000_6XHML_X2.h | 6 + machine/KP_9000_6XHML_X2_V1_1.c | 1 + machine/KP_9000_6XHML_X2_V1_1.h | 6 + machine/KP_9000_6XHML_X2_V1_2.c | 1 + machine/KP_9000_6XHML_X2_V1_2.h | 6 + machine/KP_9000_6XHML_X2_V2_1.c | 1 + machine/KP_9000_6XHML_X2_V2_1.h | 6 + machine/KP_9000_6XH_X.c | 28 + machine/KP_9000_6XH_X.h | 6 + machine/KP_9000_6XH_X2.c | 48 + machine/KP_9000_6XH_X2.h | 6 + machine/KP_9000_6XH_X2_V1_1.c | 1 + machine/KP_9000_6XH_X2_V1_1.h | 6 + machine/KP_9000_6XH_X2_V1_2.c | 1 + machine/KP_9000_6XH_X2_V1_2.h | 6 + machine/KP_9000_6XH_X2_V2_1.c | 1 + machine/KP_9000_6XH_X2_V2_1.h | 6 + machine/KP_9000_9XHML_X_V2_2.c | 51 + machine/KP_9000_9XHML_X_V2_2.h | 6 + machine/KP_9000_9XHML_X_V3_1.c | 37 + machine/KP_9000_9XHML_X_V3_1.h | 6 + machine/KP_9000_9XHML_X_V3_2.c | 1 + machine/KP_9000_9XHML_X_V3_2.h | 6 + machine/KP_9000_9XH_X_EU.c | 25 + machine/KP_9000_9XH_X_EU.h | 6 + machine/LIANGUO_HYWS_SGT0108S.c | 36 + machine/LIANGUO_HYWS_SGT0108S.h | 6 + machine/LIANGUO_ZX_SWTGW215AS.c | 33 + machine/LIANGUO_ZX_SWTGW215AS.h | 6 + machine/ONT_S207CW_62TS_SE.c | 61 ++ machine/ONT_S207CW_62TS_SE.h | 6 + machine/PB_2132.c | 41 + machine/PB_2132.h | 6 + machine/PCB_K0402WS_V2.c | 55 + machine/PCB_K0402WS_V2.h | 6 + machine/PCB_K0402WS_V3.c | 1 + machine/PCB_K0402WS_V3.h | 6 + machine/PCB_SWTG018AS_V2_1_0.c | 42 + machine/PCB_SWTG018AS_V2_1_0.h | 6 + machine/PCB_SWTG024AS_A_2_0_1.c | 48 + machine/PCB_SWTG024AS_A_2_0_1.h | 6 + machine/POE_2G080110GS.c | 44 + machine/POE_2G080110GS.h | 6 + machine/STEAMEMO_IG204_V1.c | 50 + machine/STEAMEMO_IG204_V1.h | 6 + machine/SWGT024_V2_0_MANAGED.c | 41 + machine/SWGT024_V2_0_MANAGED.h | 6 + machine/SWGT024_V2_0_UNMANAGED.c | 41 + machine/SWGT024_V2_0_UNMANAGED.h | 6 + machine/SWTG018AS_A_V_2_0.c | 39 + machine/SWTG018AS_A_V_2_0.h | 6 + machine/SWTG024AS_A_2_0_1_5C_1SFP.c | 43 + machine/SWTG024AS_A_2_0_1_5C_1SFP.h | 6 + machine/SWTG024AS_V2_0.c | 43 + machine/SWTG024AS_V2_0.h | 6 + machine/SWTGW218AS.c | 33 + machine/SWTGW218AS.h | 6 + machine/TRENDNET_TEG_S562.c | 39 + machine/TRENDNET_TEG_S562.h | 6 + machine/ZX310S_4T2XH.c | 43 + machine/ZX310S_4T2XH.h | 6 + machine/ZX310S_4T2XT.c | 37 + machine/ZX310S_4T2XT.h | 6 + machine/default_machine.h | 17 + machine/machine_defs.h | 44 + 92 files changed, 1841 insertions(+), 1527 deletions(-) delete mode 100644 machine.c create mode 100644 machine/DEFAULT_8C_1SFP.c create mode 100644 machine/DEFAULT_8C_1SFP.h create mode 100644 machine/F7008_2_5.c create mode 100644 machine/F7008_2_5.h create mode 100644 machine/FG_4GT_2SX_V2_0.c create mode 100644 machine/FG_4GT_2SX_V2_0.h create mode 100644 machine/FG_8GT_1SX.c create mode 100644 machine/FG_8GT_1SX.h create mode 100644 machine/FNS1200P.c create mode 100644 machine/FNS1200P.h create mode 100644 machine/HASIVO_S1100WP_8GT_1SX_SE.c create mode 100644 machine/HASIVO_S1100WP_8GT_1SX_SE.h create mode 100644 machine/HASIVO_S600W_4GT_2XGT_SE.c create mode 100644 machine/HASIVO_S600W_4GT_2XGT_SE.h create mode 100644 machine/HG0402XG_V1_1.c create mode 100644 machine/HG0402XG_V1_1.h create mode 100644 machine/HI_K0402WS.c create mode 100644 machine/HI_K0402WS.h create mode 100644 machine/HI_K0801WS.c create mode 100644 machine/HI_K0801WS.h create mode 100644 machine/K0501W_V2_0.c create mode 100644 machine/K0501W_V2_0.h create mode 100644 machine/KP_9000_6XHML_X2.c create mode 100644 machine/KP_9000_6XHML_X2.h create mode 120000 machine/KP_9000_6XHML_X2_V1_1.c create mode 100644 machine/KP_9000_6XHML_X2_V1_1.h create mode 120000 machine/KP_9000_6XHML_X2_V1_2.c create mode 100644 machine/KP_9000_6XHML_X2_V1_2.h create mode 120000 machine/KP_9000_6XHML_X2_V2_1.c create mode 100644 machine/KP_9000_6XHML_X2_V2_1.h create mode 100644 machine/KP_9000_6XH_X.c create mode 100644 machine/KP_9000_6XH_X.h create mode 100644 machine/KP_9000_6XH_X2.c create mode 100644 machine/KP_9000_6XH_X2.h create mode 120000 machine/KP_9000_6XH_X2_V1_1.c create mode 100644 machine/KP_9000_6XH_X2_V1_1.h create mode 120000 machine/KP_9000_6XH_X2_V1_2.c create mode 100644 machine/KP_9000_6XH_X2_V1_2.h create mode 120000 machine/KP_9000_6XH_X2_V2_1.c create mode 100644 machine/KP_9000_6XH_X2_V2_1.h create mode 100644 machine/KP_9000_9XHML_X_V2_2.c create mode 100644 machine/KP_9000_9XHML_X_V2_2.h create mode 100644 machine/KP_9000_9XHML_X_V3_1.c create mode 100644 machine/KP_9000_9XHML_X_V3_1.h create mode 120000 machine/KP_9000_9XHML_X_V3_2.c create mode 100644 machine/KP_9000_9XHML_X_V3_2.h create mode 100644 machine/KP_9000_9XH_X_EU.c create mode 100644 machine/KP_9000_9XH_X_EU.h create mode 100644 machine/LIANGUO_HYWS_SGT0108S.c create mode 100644 machine/LIANGUO_HYWS_SGT0108S.h create mode 100644 machine/LIANGUO_ZX_SWTGW215AS.c create mode 100644 machine/LIANGUO_ZX_SWTGW215AS.h create mode 100644 machine/ONT_S207CW_62TS_SE.c create mode 100644 machine/ONT_S207CW_62TS_SE.h create mode 100644 machine/PB_2132.c create mode 100644 machine/PB_2132.h create mode 100644 machine/PCB_K0402WS_V2.c create mode 100644 machine/PCB_K0402WS_V2.h create mode 120000 machine/PCB_K0402WS_V3.c create mode 100644 machine/PCB_K0402WS_V3.h create mode 100644 machine/PCB_SWTG018AS_V2_1_0.c create mode 100644 machine/PCB_SWTG018AS_V2_1_0.h create mode 100644 machine/PCB_SWTG024AS_A_2_0_1.c create mode 100644 machine/PCB_SWTG024AS_A_2_0_1.h create mode 100644 machine/POE_2G080110GS.c create mode 100644 machine/POE_2G080110GS.h create mode 100644 machine/STEAMEMO_IG204_V1.c create mode 100644 machine/STEAMEMO_IG204_V1.h create mode 100644 machine/SWGT024_V2_0_MANAGED.c create mode 100644 machine/SWGT024_V2_0_MANAGED.h create mode 100644 machine/SWGT024_V2_0_UNMANAGED.c create mode 100644 machine/SWGT024_V2_0_UNMANAGED.h create mode 100644 machine/SWTG018AS_A_V_2_0.c create mode 100644 machine/SWTG018AS_A_V_2_0.h create mode 100644 machine/SWTG024AS_A_2_0_1_5C_1SFP.c create mode 100644 machine/SWTG024AS_A_2_0_1_5C_1SFP.h create mode 100644 machine/SWTG024AS_V2_0.c create mode 100644 machine/SWTG024AS_V2_0.h create mode 100644 machine/SWTGW218AS.c create mode 100644 machine/SWTGW218AS.h create mode 100644 machine/TRENDNET_TEG_S562.c create mode 100644 machine/TRENDNET_TEG_S562.h create mode 100644 machine/ZX310S_4T2XH.c create mode 100644 machine/ZX310S_4T2XH.h create mode 100644 machine/ZX310S_4T2XT.c create mode 100644 machine/ZX310S_4T2XT.h create mode 100644 machine/default_machine.h create mode 100644 machine/machine_defs.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 77b37312..31a94891 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,7 @@ jobs: apt install make gcc sdcc xxd python-is-python3 libjson-c-dev zlib1g-dev -y - name: Run the host-side unit tests run: make -C test - - name: Check if machine.c can be compiled for all machines + - name: Check that every machine definition can be compiled run: make machine_check - name: Make project (4x2/RTL8372) run: make CI=1 MACHINE="KP_9000_6XHML_X2" diff --git a/Makefile b/Makefile index 24b9e793..0cd467f1 100644 --- a/Makefile +++ b/Makefile @@ -7,17 +7,20 @@ HTML_LOCATION = 262144 ifeq ($(origin CC),default) CC = sdcc endif -CC_FLAGS = -mmcs51 -I. -Ihttpd -Iuip +CC_FLAGS = -mmcs51 -I. -Imachine -Ihttpd -Iuip ASM ?= sdas8051 AFLAGS= -plosgff SUBDIRS := tools SUBDIRSCLEAN=$(addsuffix clean,$(SUBDIRS)) -ifeq ($(MACHINE),) - MACHINE:= $(shell grep "^\s*#define MACHINE_" machine.h | sed "s/^\s*#define MACHINE_//") -else +# $MACHINE selects machine/$(MACHINE).c as the board definition and is passed +# on as -DMACHINE_$(MACHINE), which the rest of the firmware tests with +# #if defined(MACHINE_...), and as -DMACHINE_NAME, which machine.h uses to +# include machine/$(MACHINE).h. Both take the same value, so they cannot drift. +ifneq ($(MACHINE),) CC_FLAGS += -DMACHINE_$(MACHINE) + CC_FLAGS += -DMACHINE_NAME=$(MACHINE) endif # Health instrumentation and the "health" console command: HEALTH=1 gmake ... ifneq ($(HEALTH),) @@ -28,6 +31,9 @@ ifeq ($(CI),1) CC_FLAGS += --Werror endif +# The board definition being built: one file per MACHINE_* symbol in machine.h. +MACHINE_SRC = machine/$(MACHINE).c + BUILDDIR = output/$(MACHINE) VERSION_HEADER := version.h @@ -51,16 +57,30 @@ BUILD_DATE := $(shell date -u -d @$(SOURCE_DATE_EPOCH) +"%Y-%m-%d %H:%M:%S" 2>/d || date -u -r $(SOURCE_DATE_EPOCH) +"%Y-%m-%d %H:%M:%S") endif -all: create_build_dir $(VERSION_HEADER) $(SUBDIRS) $(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin +# $(MACHINE_SRC) is listed here so an unknown $MACHINE fails before anything else +# is built, including the tools/ subdirectory. +all: create_build_dir $(VERSION_HEADER) $(MACHINE_SRC) $(SUBDIRS) $(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin create_build_dir: mkdir -p "$(BUILDDIR)" mkdir -p "$(BUILDDIR)/uip" mkdir -p "$(BUILDDIR)/httpd" + mkdir -p "$(BUILDDIR)/machine" -# Keep machine.c in first position to fail immediately on invalid $MACHINE value +# Every machine is a pair machine/.c + machine/.h. Selecting +# them by name means no #if/#elif chain, so an unknown or empty $MACHINE is +# caught here instead of by a #error further down the build. +$(MACHINE_SRC): + @echo "ERROR: MACHINE='$(MACHINE)' has no definition (expected $(MACHINE_SRC))." >&2 + @echo " Every machine is one file pair under machine/, named after the" >&2 + @echo " MACHINE_* symbol it defines; see machine/*.c for the full list." >&2 + @echo " Example: make MACHINE=KP_9000_6XHML_X2" >&2 + @false + +# Keep the per-machine definition in first position to fail immediately on an +# invalid $MACHINE value SRCS = \ - machine.c \ + $(MACHINE_SRC) \ machine_init.c \ cmd_editor.c \ cmd_parser.c \ @@ -173,15 +193,22 @@ $(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin: $(BUILDDIR)/rtlplayground.i .PHONY: clean distclean all $(SUBDIRS) $(SUBDIRSCLEAN) create_build_dir +# Every machine/ definition is compiled, so a machine added to machine/ is +# covered without registering it anywhere. The machine-specific defines are +# filtered out of $(CC_FLAGS) and supplied per iteration instead, so this also +# works as "make machine_check MACHINE=...". +MACHINE_DEFS := $(sort $(patsubst machine/%.c,%,$(wildcard machine/*.c))) +MACHINE_LESS_CC_FLAGS = $(filter-out -DMACHINE_%,$(CC_FLAGS)) + .PHONY: machine_check: @mkdir -p $(BUILDDIR)/tmp @set -eo pipefail; \ - for MACHINE in `grep -E '^[[:space:]]*(//[[:space:]]*)?#define MACHINE_' machine.h | sed -E 's%^[[:space:]]*(//[[:space:]]*)?#define MACHINE_%%' | awk '{print $$1}' | sort -u`; \ + for MACHINE in $(MACHINE_DEFS); \ do \ echo "Checking $${MACHINE}"; \ - $(CC) $(CC_FLAGS) -DMACHINE_$${MACHINE} -MMD -o $(BUILDDIR)/tmp/machine_check -c machine.c; \ - $(CC) $(CC_FLAGS) -DMACHINE_$${MACHINE} -MMD -o $(BUILDDIR)/tmp/machine_check -c machine_init.c; \ + $(CC) $(MACHINE_LESS_CC_FLAGS) -DMACHINE_$${MACHINE} -DMACHINE_NAME=$${MACHINE} -MMD -o $(BUILDDIR)/tmp/machine_check -c machine/$${MACHINE}.c; \ + $(CC) $(MACHINE_LESS_CC_FLAGS) -DMACHINE_$${MACHINE} -DMACHINE_NAME=$${MACHINE} -MMD -o $(BUILDDIR)/tmp/machine_check -c machine_init.c; \ done @rm -rf $(BUILDDIR)/tmp diff --git a/machine.c b/machine.c deleted file mode 100644 index af79d920..00000000 --- a/machine.c +++ /dev/null @@ -1,1469 +0,0 @@ -#include "machine.h" -#include "rtl837x_pins.h" -#include "rtl837x_leds.h" -#include "rtl837x_sfr.h" -#include "rtl837x_regs.h" -#include "rtl837x_common.h" - -#define I2CBUS_INVALID (7) -// This is a MACRO to check if I2C_BUS_FROM_SDA_PIN or I2C_BUS_FROM_SCL_PIN don't return I2CBUS_INVALID. -// When I2CBUS_INVALID is returned, compiler stops with "machine.c:: error 176: sizeof applied to an incomplete type" -#define I2CBUS_VALID(bus) (0 * sizeof(char[(bus) != I2CBUS_INVALID ? 1 : 0])) -#define I2CBUS(sda, scl) \ - ((uint8_t)((I2C_BUS_FROM_SCL_PIN(scl) << RTL837X_REG_I2C_SCL_SHIFT) | \ - (I2C_BUS_FROM_SDA_PIN(sda) << RTL837X_REG_I2C_SDA_SHIFT) | \ - I2CBUS_VALID(I2C_BUS_FROM_SCL_PIN(scl)) | \ - I2CBUS_VALID(I2C_BUS_FROM_SDA_PIN(sda)))) - -#define I2C_BUS_FROM_SDA_PIN(sda_pin) \ - ((sda_pin) == GPIO47_I2C_SDA0 ? 0 : \ - (sda_pin) == GPIO49_I2C_SDA1 ? 1 : \ - (sda_pin) == GPIO51_I2C_SDA2_UART1_RX ? 2 : \ - (sda_pin) == GPIO41_I2C_SDA3_MDIO1 ? 3 : \ - (sda_pin) == GPIO39_I2C_SDA4 ? 4 : I2CBUS_INVALID) - -#define I2C_BUS_FROM_SCL_PIN(scl_pin) \ - ((scl_pin) == GPIO46_I2C_SCL0 ? 0 : \ - (scl_pin) == GPIO48_I2C_SCL1 ? 1 : \ - (scl_pin) == GPIO50_I2C_SCL2_UART1_TX ? 2 : \ - (scl_pin) == GPIO40_I2C_SCL3_MDC1 ? 3 : I2CBUS_INVALID) - -#if defined(MACHINE_KP_9000_6XHML_X2) || \ - defined(MACHINE_KP_9000_6XH_X2_V1_1) || \ - defined(MACHINE_KP_9000_6XHML_X2_V1_1) || \ - defined(MACHINE_KP_9000_6XH_X2_V1_2) || \ - defined(MACHINE_KP_9000_6XHML_X2_V1_2) -__code const struct machine machine = { -#if defined(MACHINE_KP_9000_6XH_X2_V1_1) - .machine_name = "keepLink KP-9000-6XH V1.1", -#elif defined(MACHINE_KP_9000_6XHML_X2_V1_1) - .machine_name = "keepLink KP-9000-6XHML V1.1", -#elif defined(MACHINE_KP_9000_6XH_X2_V1_2) - .machine_name = "keepLink KP-9000-6XH V1.2", -#elif defined(MACHINE_KP_9000_6XHML_X2_V1_2) - .machine_name = "keepLink KP-9000-6XHML V1.2", -#else - .machine_name = "keepLink KP-9000-6XHML-X2", -#endif - .isRTL8373 = 0, - .min_port = 3, - .max_port = 8, - .n_sfp = 2, - .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, - .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, - .is_sfp = {0, 0, 0, 1, 0, 0, 0, 0, 2}, - // Left SFP port (5) - .sfp_port[0].pin_detect = GPIO50_I2C_SCL2_UART1_TX, - .sfp_port[0].pin_los = GPIO10_LED10, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 0, - .sfp_port[0].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), - // Right SFP port (6) - .sfp_port[1].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[1].pin_los = GPIO37, - .sfp_port[1].pin_tx_disable = GPIO_NA, - .sfp_port[1].sds = 1, - .sfp_port[1].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - .reset_pin = GPIO54_ACL_BIT2_EN, - .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 0}, - /* Conditions for LED on: - * dual led orange: ledset_0 & ledset_2 - * dual led green: ledset_2 & !ledset_0 - * single right led green: ledset_0 & !ledset_1 - */ - .led_sets = { { LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G, - LEDS_2G5 | LEDS_LINK | LEDS_10G, - LEDS_1G | LEDS_LINK, - 0 }, - }, -}; - -#elif defined MACHINE_KP_9000_6XH_X -__code const struct machine machine = { - .machine_name = "keepLink KP-9000-6XH-X", - .isRTL8373 = 0, - .min_port = 3, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, - .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO37, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - .reset_pin = GPIO_NA, - /* Conditions for LED on: - * dual led orange: ledset_0 & ledset_2 - * dual led green: ledset_2 & !ledset_0 - * single right led green: ledset_0 & !ledset_1 - */ - .led_sets = { { LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G, - LEDS_2G5 | LEDS_LINK | LEDS_10G, - LEDS_1G | LEDS_LINK, - 0 }, - }, -}; - -#elif defined(MACHINE_KP_9000_6XH_X2) || defined(MACHINE_KP_9000_6XH_X2_V2_1) || defined(MACHINE_KP_9000_6XHML_X2_V2_1) -__code const struct machine machine = { -#if defined(MACHINE_KP_9000_6XHML_X2_V2_1) - .machine_name = "keepLink KP-9000-6XHML V2.1", -#elif defined(MACHINE_KP_9000_6XH_X2_V2_1) - .machine_name = "keepLink KP-9000-6XH-X2 V2.1", -#else - .machine_name = "keepLink KP-9000-6XH-X2", -#endif - .isRTL8373 = 0, - .min_port = 3, - .max_port = 8, - .n_sfp = 2, - .log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5}, - .phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0}, - .is_sfp = {0, 0, 0, 2, 0, 0, 0, 0, 1}, - - // Left SFP port - .sfp_port[0].pin_detect = GPIO38, - .sfp_port[0].pin_los = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - - // Right SFP port - .sfp_port[1].pin_detect = GPIO37, - .sfp_port[1].pin_los = GPIO_NA, - .sfp_port[1].sds = 0, - .sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), - - .reset_pin = GPIO48_I2C_SCL1, // Button-Switch is unpopulated on PCB, but can be added manually (hole in case is already there) - .high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, - .led_sets = { - { - LEDS_2G5 | LEDS_LINK, // Left LED (Amber) - LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // Right LED (Green) - 0, - 0 - }, - { - LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - 0, - 0, - 0 - }, - }, - .led_mux_custom = 1, - .led_mux = { - 0x00,0x01,0x04,0x05,0x08,0x09,0x0c,0x3f,0x0d,0x10, - 0x11,0x0e,0x14,0x11,0x12,0x15,0x15,0x16,0x18,0x19, - 0x1a,0x19,0x1d,0x1e,0x1c,0x1d,0x20,0x21 - }, - }; - -#elif defined MACHINE_KP_9000_9XH_X_EU -__code const struct machine machine = { - .machine_name = "keepLink KP-9000-9XH-X-EU", - .isRTL8373 = 1, - .min_port = 0, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, - .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO37, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - .reset_pin = GPIO_NA, - .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 0}, - .led_sets = { { LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5, - LEDS_2G5 | LEDS_LINK, - LEDS_1G | LEDS_LINK, - LEDS_2G5 | LEDS_LINK | LEDS_ACT }, - }, -}; - -#elif defined MACHINE_KP_9000_9XHML_X_V2_2 -__code const struct machine machine = { - .machine_name = "keepLink KP-9000-9XHML-X V2.2", - .isRTL8373 = 1, - .min_port = 0, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, - .phys_to_log_port = { 0, 1, 2, 3, 4, 5, 6, 7, 8 }, - .is_sfp = { 0, 0, 0, 0, 0, 0, 0, 0, 1 }, - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO37, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - .reset_pin = GPIO54_ACL_BIT2_EN, - .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1 }, - .led_sets = { - { /* Set 0 for RJ45 connectors */ - /* LED0: Right Green */ - LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - /* LED1: Left Orange */ - LEDS_2G5 | LEDS_LINK, - /* LED2: Left Green */ - LEDS_1G | LEDS_LINK, - /* LED3: None */ - 0, - }, { /* Set 1 for SFP port */ - /* LED0: Single Green "9" LED */ - LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5, - /* LED1: D23 LED on PCB */ - LEDS_1G | LEDS_LINK, - /* LED2: D22 LED on PCB */ - LEDS_2G5 | LEDS_LINK, - /* LED3: Unused */ - LEDS_COL | LEDS_DUPLEX, - }, { /* Set 2: Unused, but set to same things as stock firmware for consistency */ - LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - LEDS_1G | LEDS_LINK | LEDS_ACT, - LEDS_2G5 | LEDS_LINK | LEDS_ACT | LEDS_5G, - LEDS_10G | LEDS_ACT | LEDS_LINK, - }, { /* Set 3: Unused, but set to same things as stock firmware for consistency */ - LEDS_TX, - LEDS_RX, - LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M | LEDS_ACT | LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5, - LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5, - }, - }, -}; - -#elif defined(MACHINE_KP_9000_9XHML_X_V3_1) || defined(MACHINE_KP_9000_9XHML_X_V3_2) -// 2M-PCB23-V3.2 is wired like V3.1 (SFP detection, LEDs, factory MAC location) -__code const struct machine machine = { -#if defined(MACHINE_KP_9000_9XHML_X_V3_2) - .machine_name = "keepLink KP-9000-9XHML-X V3.2", -#else - .machine_name = "keepLink KP-9000-9XHML-X V3.1", -#endif - .isRTL8373 = 1, - .min_port = 0, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, - .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - .sfp_port[0].pin_detect = GPIO38, - .sfp_port[0].pin_los = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - .reset_pin = GPIO48_I2C_SCL1, - .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, - .led_sets = { - { /* RJ45: First LED, yellow, second LED: green */ - LEDS_2G5 | LEDS_LINK, - LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - 0, - 0, - }, { /* SFP PORT, SINGLE GREEN LED */ - LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G, - 0, - 0, - 0, - }}, - .led_mux_custom = 1, - .led_mux = {0x00, 0x01, 0x04, 0x05, 0x08, 0x09, 0x0c, 0x09, 0x0d, 0x10, - 0x11, 0x0e, 0x14, 0x11, 0x12, 0x15, 0x15, 0x16, 0x18, 0x19, - 0x1a, 0x19, 0x1d, 0x1e, 0x1c, 0x1d, 0x20, 0x21}, - .mac_flash_offset = 0x1FC000, -}; - -#elif defined MACHINE_SWGT024_V2_0_MANAGED -__code const struct machine machine = { - .machine_name = "SWGT024 V2.0 Managed", - .isRTL8373 = 0, - .min_port = 3, - .max_port = 8, - .n_sfp = 2, - .log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5}, - .phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0}, - .is_sfp= {0, 0, 0, 2, 0, 0, 0, 0, 1}, - // Left SFP port (J4) - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO_NA, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), /* GPIO 39 */ - // Right SFP port (J2) - .sfp_port[1].pin_detect = GPIO50_I2C_SCL2_UART1_TX, - .sfp_port[1].pin_los = GPIO_NA, - .sfp_port[1].pin_tx_disable = GPIO_NA, - .sfp_port[1].sds = 0, - .sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), /* GPIO 40 */ - .reset_pin = GPIO54_ACL_BIT2_EN, - .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, - .led_sets = { - { /* RJ45: Green LED */ - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G, - 0, - /* Amber LED */ - LEDS_2G5 | LEDS_LINK | LEDS_ACT, - 0 - }, { /* SFP PORT: SINGLE GREEN LED */ - LEDS_10M | LEDS_100M | LEDS_1G | LEDS_2G5 | LEDS_10G | LEDS_LINK | LEDS_ACT, - 0, - 0, - 0, - }, - }, -}; - -#elif defined MACHINE_SWGT024_V2_0_UNMANAGED -__code const struct machine machine = { - .machine_name = "SWGT024 V2.0 Unmanaged", - .isRTL8373 = 0, - .min_port = 3, - .max_port = 8, - .n_sfp = 2, - .log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5}, - .phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0}, - .is_sfp= {0, 0, 0, 2, 0, 0, 0, 0, 1}, - // Left SFP port (J4) - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO37, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), /* GPIO 39 */ - // Right SFP port (J2) - .sfp_port[1].pin_detect = GPIO50_I2C_SCL2_UART1_TX, - .sfp_port[1].pin_los = GPIO51_I2C_SDA2_UART1_RX, - .sfp_port[1].pin_tx_disable = GPIO_NA, - .sfp_port[1].sds = 0, - .sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), /* GPIO 40 */ - .reset_pin = GPIO_NA, - .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, - .led_sets = { - { /* RJ45: Green LED */ - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G, - 0, - /* Amber LED */ - LEDS_2G5 | LEDS_LINK | LEDS_ACT, - 0 - }, { /* SFP PORT: SINGLE GREEN LED */ - LEDS_10M | LEDS_100M | LEDS_1G | LEDS_2G5 | LEDS_10G | LEDS_LINK | LEDS_ACT, - 0, - 0, - 0, - }, - }, -}; - -#elif defined MACHINE_SWTG018AS_A_V_2_0 -__code const struct machine machine = { - .machine_name = "SWTG018AS-A V2.0", - .isRTL8373 = 1, - .min_port = 0, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, - .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - .sfp_port[0].pin_detect = GPIO38, - .sfp_port[0].pin_los = GPIO_NA, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - .reset_pin = GPIO48_I2C_SCL1, - .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, - .led_sets = { - { /* RJ45: First LED, yellow, second LED: green */ - LEDS_2G5 | LEDS_LINK, - LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - 0, - 0, - }, { /* SFP PORT, SINGLE GREEN LED */ - LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G, - 0, - 0, - 0, - }}, - .led_mux_custom = 1, - .led_mux = { 0x00, 0x01, 0x04, 0x05, 0x08, // 65e0 - 0x09, 0x0c, 0x09, 0x0d, 0x10, // 65e4 - 0x11, 0x0e, 0x14, 0x11, 0x12, // 65e8 - 0x15, 0x15, 0x16, 0x18, 0x19, // 65ec - 0x1a, 0x19, 0x1d, 0x1e, 0x1c, // 65f0 - 0x1d, 0x20, 0x21 }, -}; - -#elif defined MACHINE_HG0402XG_V1_1 -__code const struct machine machine = { - .machine_name = "HG0402XG V1.1", - .isRTL8373 = 0, - .min_port = 3, - .max_port = 8, - .n_sfp = 2, - .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, - .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, - .is_sfp = {0, 0, 0, 2, 0, 0, 0, 0, 1}, - .sfp_port[0].pin_detect = 50, - .sfp_port[0].pin_los = 10, - .sfp_port[0].pin_tx_disable = 0xFF, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), - .sfp_port[1].pin_detect = 30, - .sfp_port[1].pin_los = 51, - .sfp_port[1].pin_tx_disable = 0xFF, - .sfp_port[1].sds = 0, - .sfp_port[1].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - .reset_pin = GPIO_NA, - .high_leds = { .mux = LED_27 , .enable = LED_27 | LED_29 }, - .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, - /* The Ethernet ports have 1 amber LED (left) and 1 green LED (right) - * The SFP ports have also 1 amber LED and 1 green LED - * Ethernet ports use LED-set 0, SFP ports use LED-set 1 - */ - .led_sets = { { LEDS_10M | LEDS_LINK | LEDS_ACT, - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_2G5 | LEDS_LINK | LEDS_ACT, - LEDS_2G5 | LEDS_LINK | LEDS_ACT, - 0 }, - { LEDS_100M | LEDS_10M | LEDS_LINK, - LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G, - LEDS_10G | LEDS_LINK, - 0 }, - }, -}; - -#elif defined MACHINE_SWTGW218AS - -__code const struct machine machine = { - .machine_name = "SWTGW218AS 8+1 Managed Switch", - .isRTL8373 = 1, - .mac_flash_offset = 0x1FC000, - .min_port = 0, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, - .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO37, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - .reset_pin = GPIO54_ACL_BIT2_EN, - .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, - .led_sets = { { LEDS_2G5 | LEDS_LINK | LEDS_ACT, // Green LED (right) - 0, // unused - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // Amber LED (left) - 0 - }, // unused - { LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT, // SFP LED - 0, // unused - 0, // unused - 0 - }, // unused }, - }, -}; - -#elif defined MACHINE_PCB_SWTG018AS_V2_1_0 // Sold as Sodola SL902 / Horaco "SWTGW218AS"; the SWTGW218AS label also covers other PCBs with different SFP and LED wiring (see MACHINE_SWTGW218AS) - -__code const struct machine machine = { - .machine_name = "SWTGW218AS (SWTG018AS-V2.1.0)", - .isRTL8373 = 1, - .mac_flash_offset = 0x1FC000, - .min_port = 0, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, - .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - .sfp_port[0].pin_detect = GPIO38, // pulled low on module insert - .sfp_port[0].pin_los = GPIO_NA, // no LOS pin wired - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - .reset_pin = GPIO54_ACL_BIT2_EN, - .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, - // LED wiring matches the SWTG018AS-A V2.0 (same PCB family) - .led_sets = { - { /* RJ45: First LED, yellow, second LED: green */ - LEDS_2G5 | LEDS_LINK, - LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - 0, - 0, - }, { /* SFP set (superseded by the raw register override in machine_custom_init) */ - LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G, - 0, - 0, - 0, - }}, - .led_mux_custom = 1, - .led_mux = { 0x00, 0x01, 0x04, 0x05, 0x08, // 65e0 - 0x09, 0x0c, 0x09, 0x0d, 0x10, // 65e4 - 0x11, 0x0e, 0x14, 0x11, 0x12, // 65e8 - 0x15, 0x15, 0x16, 0x18, 0x19, // 65ec - 0x1a, 0x19, 0x1d, 0x1e, 0x1c, // 65f0 - 0x1d, 0x20, 0x21 }, -}; - -#elif defined MACHINE_LIANGUO_ZX_SWTGW215AS // Has PCB branded PCB-SWTG115AS-V2.0 but is labeled and reports as a ZX-SWTGW215AS, seems to be identical to the "real" ZX-SWTGW215AS except for the LEDs -__code const struct machine machine = { - .machine_name = "Lianguo ZX-SWTGW215AS", - .isRTL8373 = 0, - .mac_flash_offset = 0x1FC000, - .min_port = 3, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, - .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO37, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - .reset_pin = GPIO54_ACL_BIT2_EN, - .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, - .led_sets = { { LEDS_2G5 | LEDS_LINK | LEDS_ACT, // Green LED (right) - 0, // unused - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // Amber LED (left) - 0 - }, // unused - { LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT, // SFP LED - 0, // unused - 0, // unused - 0 - }, // unused }, - }, - .led_mux_custom = 0, -}; - -#elif defined MACHINE_DEFAULT_8C_1SFP -__code const struct machine machine = { - .machine_name = "8+1 SFP Port Switch", - .isRTL8373 = 1, - .min_port = 0, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, - .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO37, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - .reset_pin = GPIO_NA, - .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 0}, - .led_sets = { { LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5, - LEDS_2G5 | LEDS_LINK, - LEDS_1G | LEDS_LINK, - LEDS_2G5 | LEDS_LINK | LEDS_ACT }, - }, -}; - -#elif defined MACHINE_TRENDNET_TEG_S562 -__code const struct machine machine = { - .machine_name = "Trendnet TEG-S562", - .isRTL8373 = 0, - .min_port = 3, - .max_port = 8, - .n_sfp = 2, - .log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5}, - .phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0}, - .is_sfp = {0, 0, 0, 1, 0, 0, 0, 0, 2}, - .sfp_port[0].pin_detect = GPIO38, - .sfp_port[0].pin_los = GPIO50_I2C_SCL2_UART1_TX, - .sfp_port[0].pin_tx_disable = GPIO54_ACL_BIT2_EN, - .sfp_port[0].sds = 0, - .sfp_port[0].i2c = I2CBUS( GPIO47_I2C_SDA0, GPIO46_I2C_SCL0 ), - .sfp_port[1].pin_detect = GPIO36_PWM_OUT, - .sfp_port[1].pin_los = GPIO37, - .sfp_port[1].pin_tx_disable = GPIO51_I2C_SDA2_UART1_RX, - .sfp_port[1].sds = 1, - .sfp_port[1].i2c = I2CBUS( GPIO49_I2C_SDA1, GPIO48_I2C_SCL1 ), - .reset_pin = GPIO_NA, - .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, - .led_sets = { - { - 0, // Unused - LEDS_2G5 | LEDS_LINK | LEDS_ACT, // Green - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // Amber - 0 // Unused - }, - { - 0, // Unused - 0, // Unused - LEDS_10G | LEDS_1G | LEDS_LINK | LEDS_ACT, // Green - 0, // Unused - }, - }, - -}; - -#elif defined(MACHINE_PCB_K0402WS_V3) || defined(MACHINE_HI_K0402WS) // Sold as a variety of devices, see doc/ -__code const struct machine machine = { - .machine_name = "PCB-K0402WS-V3.0", - .isRTL8373 = 0, - .min_port = 3, - .max_port = 8, - .n_sfp = 2, - .log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5}, - .phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0}, - .is_sfp = {0, 0, 0, 2, 0, 0, 0, 0, 1}, - - // Left SFP port - .sfp_port[0].pin_detect = GPIO38, - .sfp_port[0].pin_los = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - - // Right SFP port - .sfp_port[1].pin_detect = GPIO37, - .sfp_port[1].pin_los = GPIO_NA, - .sfp_port[1].sds = 0, - .sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), - - .reset_pin = GPIO_NA, - .high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, - .led_sets = { - { - LEDS_2G5 | LEDS_LINK | LEDS_10M | LEDS_ACT, - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G, - LEDS_1G | LEDS_LINK, - 0 - }, - { - LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK, - LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_ACT, - 0, - 0 - }, - }, - .led_mux_custom = 1, - .led_mux = { - 0x00,0x01,0x04,0x05,0x08,0x09,0x0c,0x3f,0x0d,0x10,0x11,0x0e,0x14,0x11,0x12,0x15,0x15,0x16,0x18,0x19,0x1a,0x19,0x1d,0x1e,0x1c,0x1d,0x20,0x21 - }, - }; - -#elif defined MACHINE_K0501W_V2_0 -__code const struct machine machine = { - .machine_name = "K0501W V2.0", - .isRTL8373 = 0, - .min_port = 3, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, - .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO37, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - - .reset_pin = GPIO_NA, - .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, - .led_sets = { - { - LEDS_2G5 | LEDS_LINK | LEDS_ACT, - 0, - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - 0 - }, - { - LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - 0, - 0, - 0 - }, - }, -}; - -#elif defined MACHINE_ZX310S_4T2XH -__code const struct machine machine = { - .machine_name = "ZX310S-4T2XH", - .isRTL8373 = 0, - .min_port = 3, - .max_port = 8, - .n_sfp = 1, - .n_10g = 1, - .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, - .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - .sfp_port[0].pin_detect = GPIO38, - .sfp_port[0].pin_los = GPIO_NA, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - .reset_pin = GPIO48_I2C_SCL1, - .high_leds = { .mux = LED_28_SYS, .enable = LED_27 | LED_28_SYS | LED_29 }, - .led_mux_custom = 1, - .led_mux = { 0x00, 0x01, 0x04, 0x05, 0x08, // 65e0 - 0x09, 0x0c, 0x3f, 0x0d, 0x10, // 65e4 - 0x11, 0x0e, 0x14, 0x11, 0x12, // 65e8 - 0x15, 0x15, 0x16, 0x18, 0x19, // 65ec - 0x1a, 0x19, 0x1d, 0x1e, 0x1c, // 65f0 - 0x1d, 0x20, 0x21 }, - .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, - /* Ports 1-4: Orange: 2.5GBit, Green: 10/100/1000MBit - * Port 5: Blue: 10GBit, Green: 10Mbit-5GBit - * SFP-port: Blue: 10GBit, Green 100MBit-5GBit - */ - .led_sets = { { LEDS_2G5 | LEDS_LINK | LEDS_ACT, - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - LEDS_DUPLEX, - LEDS_2G5 | LEDS_LINK | LEDS_ACT }, - { - LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT | LEDS_5G, - LEDS_LINK | LEDS_ACT | LEDS_10G, - LEDS_2G5 | LEDS_LINK, - LEDS_COL | LEDS_DUPLEX - } - }, -}; - -#elif defined MACHINE_STEAMEMO_IG204_V1 -__code const struct machine machine = { - .machine_name = "Steamemo IG204 V1", - .isRTL8373 = 0, - .min_port = 3, - .max_port = 8, - .n_sfp = 2, - .log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5}, - .phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0}, - .is_sfp = {0, 0, 0, 2, 0, 0, 0, 0, 1}, - // Left SFP port (5) - // LED pin 9 - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO37, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - // Right SFP port (6) - // LED pin 24 - .sfp_port[1].pin_detect = GPIO50_I2C_SCL2_UART1_TX, - .sfp_port[1].pin_los = GPIO51_I2C_SDA2_UART1_RX, - .sfp_port[1].pin_tx_disable = GPIO_NA, - .sfp_port[1].sds = 0, - .sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), - .reset_pin = GPIO_NA, - .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, - .led_sets = { - { - // RJ45 Amber LED (left) - LEDS_2G5 | LEDS_LINK, - // RJ45 Green LED (Right) - LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - 0, - 0, - }, - { - // SFP LED - LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT, - 0, // unused - 0, // unused - 0 - }, - }, - .led_mux_custom = 1, - .led_mux = { - 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x0f, 0x20, 0x0d, 0x0e, 0x10, 0x11, 0x12, 0x14, 0x15, 0x16, 0x18, 0x19, 0x1a, 0x1c, 0x1d, 0x1e, 0x0c, 0x21, 0x22, 0x23 - }, -}; - -#elif defined MACHINE_HI_K0801WS -__code const struct machine machine = { - .machine_name = "Hi-Source HI-k0801WS", - .isRTL8373 = 1, - .min_port = 0, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, - .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO37, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - .reset_pin = GPIO_NA, - - .high_leds = { - .mux = LED_27 | LED_29, - .enable = LED_28_SYS | LED_29 - }, - - /* Ports 1-8 use set 0, port 9 SFP uses set 1 */ - .port_led_set = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - - .led_sets = { - { /* Set 0: RJ45 copper ports - * Amber = 2.5G - * Green = 1G/100M/10M with activity - */ - LEDS_2G5 | LEDS_LINK | LEDS_ACT, /* Amber */ - 0, - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, /* Green */ - 0 - }, - { /* Set 1: SFP port, single green LED for all valid speeds */ - LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - 0, - 0, - 0 - }, - }, -}; - -#elif defined(MACHINE_PCB_K0402WS_V2) || defined(MACHINE_FNS1200P) -__code const struct machine machine = { -#if defined(MACHINE_PCB_K0402WS_V2) - .machine_name = "PCB-K0402WS-V2.0", -#else - .machine_name = "FNS-1200P", -#endif - .isRTL8373 = 0, - .min_port = 3, - .max_port = 8, - .n_sfp = 2, - .log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5}, - .phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0}, - .is_sfp = {0, 0, 0, 2, 0, 0, 0, 0, 1}, - - /* Left SFP (logical 8, SDS1): GPIO30=ModAbs, GPIO37=RX_LOS */ - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO37, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - - /* Right SFP (logical 3, SDS0): GPIO50=ModAbs, GPIO51=RX_LOS */ - .sfp_port[1].pin_detect = GPIO50_I2C_SCL2_UART1_TX, - .sfp_port[1].pin_los = GPIO51_I2C_SDA2_UART1_RX, - .sfp_port[1].pin_tx_disable = GPIO_NA, - .sfp_port[1].sds = 0, - .sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), - - .reset_pin = GPIO_NA, - - .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, - - /* Copper ports use SET0; SFP ports use SET1 */ - .port_led_set = {0, 0, 0, 1, 0, 0, 0, 0, 1}, - - .led_sets = { - { /* SET0: copper — LED0=amber (2.5G), LED2=green (1G/100M/10M) */ - LEDS_2G5 | LEDS_LINK | LEDS_ACT, - 0, - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - 0 - }, - { /* SET1: SFP — all speeds link/act */ - LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - 0, - 0, 0 - }, - }, - - .led_mux_custom = 1, - .led_mux = { - 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, /* GPIO0-7: unused */ - 0x0f, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x12, /* GPIO8-14 */ - 0x14, 0x15, 0x16, 0x18, 0x19, 0x1a, /* GPIO15-20 */ - 0x1c, 0x1d, 0x1e, 0x20, 0x21, 0x22, 0x23 /* GPIO21-27 */ - }, -}; - - -#elif defined MACHINE_PCB_SWTG024AS_A_2_0_1 -__code const struct machine machine = { - .machine_name = "PCB-SWTG024AS-A-2.0.1", - .isRTL8373 = 0, - .min_port = 3, - .max_port = 8, - .n_sfp = 2, - .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, - .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, - .is_sfp = {0, 0, 0, 1, 0, 0, 0, 0, 2}, - - // SFP port on SDS0 / logical port 3 - .sfp_port[0].pin_detect = GPIO37, - .sfp_port[0].pin_los = GPIO_NA, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 0, - .sfp_port[0].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), - - // SFP port on SDS1 / logical port 8 - .sfp_port[1].pin_detect = GPIO38, - .sfp_port[1].pin_los = GPIO_NA, - .sfp_port[1].pin_tx_disable = GPIO_NA, - .sfp_port[1].sds = 1, - .sfp_port[1].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - - .reset_pin = GPIO_NA, - .high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, - .led_sets = { - { - LEDS_2G5 | LEDS_LINK | LEDS_ACT, - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - LEDS_DUPLEX, - LEDS_2G5 | LEDS_LINK | LEDS_ACT - }, - { - LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT, - LEDS_10G | LEDS_LINK | LEDS_ACT, - LEDS_2G5 | LEDS_LINK, - LEDS_COL | LEDS_DUPLEX - }, - }, - .led_mux_custom = 1, - .led_mux = { - 0x00,0x01,0x04,0x05,0x08,0x09,0x0c,0x3f,0x0d,0x10,0x11,0x0e,0x14,0x11,0x12,0x15,0x15,0x16,0x18,0x19,0x1a,0x19,0x1d,0x1e,0x1c,0x1d,0x20,0x21 - }, - }; - -#elif defined MACHINE_ONT_S207CW_62TS_SE - -__code const struct machine machine = { - .machine_name = "ONT-S207CW-62TS-SE / Binardat 2G06-04210GSM / Binardat 2G06-04210GS", - .isRTL8373 = 0, - .min_port = 3, - .max_port = 8, - .n_sfp = 2, - .log_to_phys_port = {0, 0, 0, 5, 4, 3, 2, 1, 6}, - .phys_to_log_port = {7, 6, 5, 4, 3, 8, 0, 0, 0}, - .is_sfp = {0, 0, 0, 1, 0, 0, 0, 0, 2}, - - // SFP port on SDS0 / logical port 3 - .sfp_port[0].pin_detect = GPIO37, - .sfp_port[0].pin_los = GPIO_NA, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 0, - .sfp_port[0].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), - - // SFP port on SDS1 / logical port 8 - .sfp_port[1].pin_detect = GPIO38, - .sfp_port[1].pin_los = GPIO_NA, - .sfp_port[1].pin_tx_disable = GPIO_NA, - .sfp_port[1].sds = 1, - .sfp_port[1].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - - .reset_pin = GPIO_NA, - .high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 3, 0, 0, 2, 2, 3}, - .mac_flash_offset = 0x1FC000, - .led_sets = { - { // RJ45 Ports 3-4 (normal wiring) - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - LEDS_2G5 | LEDS_LINK | LEDS_ACT, - 0, - 0 - }, - { // Unused - 0, - 0, - 0, - 0 - }, - { // RJ45 Ports 1-2 (reversed wiring) - LEDS_2G5 | LEDS_LINK | LEDS_ACT, - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - 0, - 0 - }, - { // SFP Ports 5-6 - LEDS_10G | LEDS_LINK | LEDS_ACT, - LEDS_2G5 | LEDS_1G | LEDS_LINK | LEDS_ACT, - 0, - 0 - }, - }, - .led_mux_custom = 1, - .led_mux = { - 0x00,0x01,0x04,0x05,0x08,0x09,0x0c,0x3f,0x0d,0x10,0x11,0x0e,0x14,0x11,0x12,0x15,0x15,0x16,0x18,0x19,0x1a,0x19,0x1d,0x1e,0x1c,0x1d,0x20,0x21 - }, -}; - -#elif defined MACHINE_SWTG024AS_A_2_0_1_5C_1SFP -__code const struct machine machine = { - .machine_name = "SWTG024AS-A-V2.0.1-5C-1SFP", - .isRTL8373 = 0, - .min_port = 3, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, - .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - .sfp_port[0].pin_detect = GPIO38, - .sfp_port[0].pin_los = GPIO_NA, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - - .reset_pin = GPIO_NA, - .high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, - /* Ports 1-5 RJ45 use set 0, port 9 SFP uses set 1 - * Ports 1-5: Green: 2.5GBit, Amber: 10/100/1000MBit - * SFP-port: Blue: 10GBit, Green: 100MBit-2.5GBit - */ - .led_sets = { - { - LEDS_2G5 | LEDS_LINK | LEDS_ACT, - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - LEDS_DUPLEX, - LEDS_2G5 | LEDS_LINK | LEDS_ACT - }, - { - LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT, - LEDS_10G | LEDS_LINK | LEDS_ACT, - LEDS_2G5 | LEDS_LINK, - LEDS_COL | LEDS_DUPLEX - }, - }, - .led_mux_custom = 1, - .led_mux = { - 0x00,0x01,0x04,0x05,0x08,0x09,0x0c,0x3f,0x0d,0x10,0x11,0x0e,0x14,0x11,0x12,0x15,0x15,0x16,0x18,0x19,0x1a,0x19,0x1d,0x1e,0x1c,0x1d,0x20,0x21 - }, - }; - -#elif defined MACHINE_SWTG024AS_V2_0 -__code const struct machine machine = { - .machine_name = "SWTG024AS-V2.0", - .isRTL8373 = 0, - .min_port = 3, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, - .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO37, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - - .reset_pin = GPIO_NA, - .high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, - /* Ports 1-5 RJ45 use set 0, port 9 SFP uses set 1 - * Ports 1-5: Green: 2.5GBit, Amber: 10/100/1000MBit - * SFP-port: Blue: 10GBit, Amber: 100MBit-2.5GBit - */ - .led_sets = { - { - LEDS_2G5 | LEDS_LINK | LEDS_ACT, - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - LEDS_DUPLEX, - LEDS_2G5 | LEDS_LINK | LEDS_ACT - }, - { - LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT, - LEDS_10G | LEDS_LINK | LEDS_ACT, - LEDS_2G5 | LEDS_LINK, - LEDS_COL | LEDS_DUPLEX - }, - }, - .led_mux_custom = 1, - .led_mux = { - 0x00,0x01,0x04,0x05,0x08,0x09,0x0c,0x3f,0x0d,0x10,0x11,0x0e,0x14,0x11,0x12,0x15,0x15,0x16,0x18,0x19,0x1a,0x19,0x1d,0x1e,0x1c,0x1d,0x20,0x21 - }, - }; - -#elif defined MACHINE_ZX310S_4T2XT -__code const struct machine machine = { - .machine_name = "ZX310S_4T2XT", - .isRTL8373 = 0, - .min_port = 3, - .max_port = 8, - .n_sfp = 0, - .n_10g = 2, - .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, - .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 0}, - .reset_pin = GPIO48_I2C_SCL1, - .high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 }, - .led_mux_custom = 1, - .led_mux = { 0x00, 0x01, 0x04, 0x05, 0x08, // 65e0 - 0x09, 0x0c, 0x3f, 0x0d, 0x10, // 65e4 - 0x11, 0x0e, 0x14, 0x11, 0x12, // 65e8 - 0x15, 0x15, 0x16, 0x18, 0x19, // 65ec - 0x1a, 0x19, 0x1d, 0x1e, 0x1c, // 65f0 - 0x1d, 0x20, 0x21 }, - .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, - /* Ports 1-4: Green: 2.5GBit, Orange: 10/100/1000MBit - * Ports 5-6: Green: 10GBit, Orange: <= 5GBit - */ - .led_sets = { { LEDS_2G5 | LEDS_LINK | LEDS_ACT, - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - LEDS_DUPLEX, - LEDS_2G5 | LEDS_LINK | LEDS_ACT }, - { - LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT, - LEDS_LINK | LEDS_ACT | LEDS_10G | LEDS_5G, - LEDS_2G5 | LEDS_LINK, - LEDS_COL | LEDS_DUPLEX - } - }, -}; - -#elif defined MACHINE_FG_4GT_2SX_V2_0 -__code const struct machine machine = { - .machine_name = "FG-4GT-2SX_V2.0", - .isRTL8373 = 0, - .min_port = 3, - .max_port = 8, - .n_sfp = 2, - .log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5}, - .phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0}, - .is_sfp = {0, 0, 0, 2, 0, 0, 0, 0, 1}, - - // Left SFP port - .sfp_port[0].pin_detect = GPIO38, - .sfp_port[0].pin_los = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - - // Right SFP port - .sfp_port[1].pin_detect = GPIO37, - .sfp_port[1].pin_los = GPIO_NA, - .sfp_port[1].sds = 0, - .sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), - - .reset_pin = GPIO_NA, - .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, - /* Ports 1-4 RJ45 use set 0, port 5-6 SFP uses set 1 - * Ports 1-4: Green: 2.5GBit, Amber: 10/100/1000MBit - * Ports 5-6: Green: 100MBit-10GBit - */ - .led_sets = { - { - LEDS_2G5 | LEDS_LINK | LEDS_ACT, - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - 0, - LEDS_2G5 | LEDS_LINK | LEDS_ACT - }, - { - LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - LEDS_10G | LEDS_LINK, - 0, - LEDS_COL | LEDS_DUPLEX - }, - { - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - LEDS_2G5 | LEDS_1G | LEDS_LINK, - LEDS_5G | LEDS_2G5 | LEDS_LINK | LEDS_ACT, - LEDS_10G | LEDS_LINK | LEDS_ACT - }, - { - LEDS_TX, - LEDS_RX, - LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5 | - LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M | LEDS_ACT, - LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5 | - LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M | LEDS_LINK - }, - }, - .led_mux_custom = 1, - .led_mux = { - 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x12, 0x14, 0x3f, 0x15, 0x16, - 0x18, 0x0e, 0x19, 0x11, 0x12, 0x1a, 0x15, 0x16, 0x1c, 0x19, - 0x1a, 0x1d, 0x1d, 0x1e, 0x1e, 0x20, 0x21, 0x22 - }, - }; - -#elif defined MACHINE_FG_8GT_1SX -__code const struct machine machine = { - .machine_name = "FG-8GT-1SX", - .isRTL8373 = 1, - .min_port = 0, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, - .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - .sfp_port[0].pin_detect = GPIO38, - .sfp_port[0].pin_los = GPIO_NA, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - .reset_pin = GPIO_NA, - .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - .led_sets = { - /* - Ports 1-8: Left Amber 10/100/1000MBit (Vendor no 10M, but we add it) - Right Green 2.5GBit - SFP-port: Any speed - */ - { - LEDS_2G5 | LEDS_LINK | LEDS_ACT, - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - 0, - 0, - }, - { - LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - 0, - 0, - 0, - }, - }, - .led_mux_custom = 1, - .led_mux = { - 0x00, 0x01, 0x04, 0x05, 0x08, 0x09, 0x0c, 0x09, 0x0d, 0x10, - 0x11, 0x0e, 0x14, 0x11, 0x12, 0x15, 0x15, 0x16, 0x18, 0x19, - 0x1a, 0x19, 0x1d, 0x1e, 0x1c, 0x1d, 0x20, 0x21, - }, -}; - -#elif defined MACHINE_LIANGUO_HYWS_SGT0108S -__code const struct machine machine = { - .machine_name = "Lianguo HYWS-SGT0108S", - .isRTL8373 = 1, - .min_port = 0, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = {2, 1, 4, 3, 5, 6, 7, 8, 9}, - .phys_to_log_port = {1, 0, 3, 2, 4, 5, 6, 7, 8}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO37, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - - .reset_pin = GPIO_NA, - .high_leds = { .mux = 0, .enable = 0 }, - .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1 }, - .led_sets = { - { - LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // RJ45 Blue LEDs - LEDS_2G5 | LEDS_LINK, // RJ45 White LEDs - 0, - 0 - }, - { - LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // SFP blue LED? - 0, - 0, - 0 - }, - }, -}; -#elif defined MACHINE_POE_2G080110GS -__code const struct machine machine = { - .machine_name = "MokerLink POE-2G080110GS", - .isRTL8373 = 1, - .min_port = 0, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, - .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO37, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - .reset_pin = GPIO_NA, - .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, - .led_sets = { - { - /* SET0: RJ45 2.5G = amber, 1G/100M/10M = green */ - LEDS_2G5 | LEDS_LINK | LEDS_ACT, /* amber */ - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, /* green */ - 0, - 0 - }, - { - /* SET1: SFP+ port = green */ - LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M | - LEDS_LINK | LEDS_ACT | LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5, - 0, - 0, - 0 - } - }, - .led_mux_custom = 1, - .led_mux = { 0x00, 0x01, 0x04, 0x05, 0x08, - 0x09, 0x0c, 0x09, 0x0d, 0x10, - 0x11, 0x0e, 0x14, 0x11, 0x12, - 0x15, 0x15, 0x16, 0x18, 0x19, - 0x1a, 0x19, 0x1d, 0x1e, 0x1c, - 0x1d, 0x20, 0x21 }, -}; -#elif defined MACHINE_PB_2132 -__code const struct machine machine = { - .machine_name = "PB-2132", - .isRTL8373 = 1, - .min_port = 0, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, - .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO37, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - - .reset_pin = GPIO_NA, - - .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, - - .led_sets = { - { - /* LED0 = RJ45: Amber LED: 2.5G */ - LEDS_2G5 | LEDS_LINK | LEDS_ACT, - /* LED1 = RJ45: Green LED: 10M / 100M / 1000M */ - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - 0, - 0 - }, - { - /* SFP+ single Green LED. */ - LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT, - 0, - 0, - 0 - }, - }, - }; - -#elif defined MACHINE_HASIVO_S600W_4GT_2XGT_SE -__code const struct machine machine = { - .machine_name = "Hasivo S600W-4GT-2XGT-SE", - .isRTL8373 = 0, - .min_port = 3, - .max_port = 8, - .n_sfp = 0, - .n_10g = 2, - .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, - .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 0}, - .reset_pin = GPIO54_ACL_BIT2_EN, - .high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1 }, - /* Ports 1-4: Green: 2.5GBit, Orange: 10/100/1000MBit - * Ports 5-6: Green: 10GBit, Orange: 5/2.5GBit, None: 10/100/1000MBit - */ - .led_sets = { - { - LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // Yellow - LEDS_2G5 | LEDS_LINK, // Green - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK, // Orange - 0, // Unused - }, - { - LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // Yellow - LEDS_10G | LEDS_LINK, // Green - LEDS_5G | LEDS_2G5 | LEDS_LINK, // Orange - 0, // Unused - }, - }, - .led_mux_custom = 1, - .led_mux = { - 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, - 0x3f, 0x3f, 0x3f, 0x0f, 0x20, - 0x21, 0x22, 0x10, 0x11, 0x12, - 0x14, 0x15, 0x16, 0x18, 0x19, - 0x1a, 0x1c, 0x1d, 0x1e, 0x0c, - 0x0d, 0x0e, 0x23 - }, - -}; -#elif defined MACHINE_HASIVO_S1100WP_8GT_1SX_SE -__code const struct machine machine = { - .machine_name = "Hasivo S1100WP-8GT-1SX-SE", - .isRTL8373 = 1, - .mac_flash_offset = 0x1FC000, - .min_port = 0, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, - .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO37, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - .reset_pin = GPIO54_ACL_BIT2_EN, - .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, - .led_sets = { { LEDS_2G5 | LEDS_LINK | LEDS_ACT, // Green LED (right) - 0, - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // Amber LED (left) - 0 - }, - { LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT, // SFP LED - 0, - 0, - 0 - }, - }, -}; -#elif defined MACHINE_F7008_2_5 -__code const struct machine machine = { - .machine_name = "F7008-2.5 8+1 SFP Port Switch", - .isRTL8373 = 1, - .min_port = 0, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, - .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO37, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - .reset_pin = GPIO_NA, - .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - .led_sets = { - { /* SET0 */ - LEDS_2G5 | LEDS_LINK, /* LEDID0 - green: 2.5G link */ - LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, /* LEDID1 - yellow: traffic */ - 0, - 0, - }, - { /* SET1 - SFP+ */ - LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_LINK, - LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_LINK | LEDS_ACT, - 0, - 0, - }, - }, - .led_mux_custom = 1, - .led_mux = { - 0x00, 0x01, 0x04, 0x05, 0x08, 0x09, 0x0c, 0x02, - 0x0d, 0x10, 0x11, 0x02, 0x14, 0x02, 0x02, 0x15, - 0x02, 0x02, 0x18, 0x02, 0x02, 0x19, 0x02, 0x02, - 0x1c, 0x1d, 0x02, 0x02, - }, -}; - -#else - #error "Please select a machine type in machine.h" -#endif diff --git a/machine.h b/machine.h index 30575781..d0b85fc1 100644 --- a/machine.h +++ b/machine.h @@ -3,55 +3,19 @@ #include -/* - * Select your machine type below - */ -// Legacy KP-9000 4+2 targets. Prefer the PCB-revision-specific targets below. -// #define MACHINE_KP_9000_6XHML_X2 -// #define MACHINE_KP_9000_6XH_X2 +#define MACHINE_STR_HELPER(x) #x +#define MACHINE_STR(x) MACHINE_STR_HELPER(x) -// KP-9000 4+2 targets by PCB silkscreen revision. -// #define MACHINE_KP_9000_6XH_X2_V1_1 -// #define MACHINE_KP_9000_6XHML_X2_V1_1 -// #define MACHINE_KP_9000_6XH_X2_V1_2 -// #define MACHINE_KP_9000_6XHML_X2_V1_2 -// #define MACHINE_KP_9000_6XH_X2_V2_1 -// #define MACHINE_KP_9000_6XHML_X2_V2_1 -// #define MACHINE_KP_9000_6XH_X -// #define MACHINE_KP_9000_9XH_X_EU -// #define MACHINE_KP_9000_9XHML_X_V2_2 -// #define MACHINE_KP_9000_9XHML_X_V3_1 -// #define MACHINE_KP_9000_9XHML_X_V3_2 -// #define MACHINE_SWGT024_V2_0_MANAGED -// #define MACHINE_SWGT024_V2_0_UNMANAGED -// #define MACHINE_TRENDNET_TEG_S562 -// #define MACHINE_HG0402XG_V1_1 -// #define MACHINE_SWTG018AS_A_V_2_0 -// #define MACHINE_SWTGW218AS -// #define MACHINE_PCB_SWTG018AS_V2_1_0 -// #define MACHINE_PCB_K0402WS_V3 -// #define MACHINE_PCB_K0402WS_V2 -// #define MACHINE_K0501W_V2_0 -// #define MACHINE_LIANGUO_ZX_SWTGW215AS -// #define MACHINE_ZX310S_4T2XH -// #define MACHINE_ZX310S_4T2XT -// #define MACHINE_STEAMEMO_IG204_V1 -// #define MACHINE_DEFAULT_8C_1SFP -// #define MACHINE_HI_K0801WS -// #define MACHINE_FNS1200P -// #define MACHINE_PCB_SWTG024AS_A_2_0_1 -// #define MACHINE_ONT_S207CW_62TS_SE -// #define MACHINE_SWTG024AS_A_2_0_1_5C_1SFP -// #define MACHINE_SWTG024AS_V2_0 -// #define MACHINE_FG_4GT_2SX_V2_0 -// #define MACHINE_FG_8GT_1SX -// #define MACHINE_LIANGUO_HYWS_SGT0108S -// #define MACHINE_POE_2G080110GS -// #define MACHINE_PB_2132 -// #define MACHINE_HASIVO_S600W_4GT_2XGT_SE -// #define MACHINE_HASIVO_S1100WP_8GT_1SX_SE -// #define MACHINE_F7008_2_5 +#define MACHINE_DEF_DIR machine + +#ifndef MACHINE_NAME + #define MACHINE_NAME default_machine +#endif + +#define MACHINE_INC(dir,file) MACHINE_STR(dir/file) + +#include MACHINE_INC(MACHINE_DEF_DIR,MACHINE_NAME.h) #define LED_27 1 // SYSTEM LED diff --git a/machine/DEFAULT_8C_1SFP.c b/machine/DEFAULT_8C_1SFP.c new file mode 100644 index 00000000..2a923596 --- /dev/null +++ b/machine/DEFAULT_8C_1SFP.c @@ -0,0 +1,25 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 1, + .min_port = 0, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, + .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[0].pin_los = GPIO37, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + .reset_pin = GPIO_NA, + .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 0}, + .led_sets = { { LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5, + LEDS_2G5 | LEDS_LINK, + LEDS_1G | LEDS_LINK, + LEDS_2G5 | LEDS_LINK | LEDS_ACT }, + }, +}; diff --git a/machine/DEFAULT_8C_1SFP.h b/machine/DEFAULT_8C_1SFP.h new file mode 100644 index 00000000..2b802a30 --- /dev/null +++ b/machine/DEFAULT_8C_1SFP.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_DEFAULT_8C_1SFP_H_ +#define _MACHINE_DEFAULT_8C_1SFP_H_ + +#define MACHINE_MODEL "8+1 SFP Port Switch" + +#endif diff --git a/machine/F7008_2_5.c b/machine/F7008_2_5.c new file mode 100644 index 00000000..e2d08e61 --- /dev/null +++ b/machine/F7008_2_5.c @@ -0,0 +1,41 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 1, + .min_port = 0, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, + .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[0].pin_los = GPIO37, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + .reset_pin = GPIO_NA, + .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .led_sets = { + { /* SET0 */ + LEDS_2G5 | LEDS_LINK, /* LEDID0 - green: 2.5G link */ + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, /* LEDID1 - yellow: traffic */ + 0, + 0, + }, + { /* SET1 - SFP+ */ + LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_LINK, + LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_LINK | LEDS_ACT, + 0, + 0, + }, + }, + .led_mux_custom = 1, + .led_mux = { + 0x00, 0x01, 0x04, 0x05, 0x08, 0x09, 0x0c, 0x02, + 0x0d, 0x10, 0x11, 0x02, 0x14, 0x02, 0x02, 0x15, + 0x02, 0x02, 0x18, 0x02, 0x02, 0x19, 0x02, 0x02, + 0x1c, 0x1d, 0x02, 0x02, + }, +}; diff --git a/machine/F7008_2_5.h b/machine/F7008_2_5.h new file mode 100644 index 00000000..c3b0cbbf --- /dev/null +++ b/machine/F7008_2_5.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_F7008_2_5_H_ +#define _MACHINE_F7008_2_5_H_ + +#define MACHINE_MODEL "F7008-2.5 8+1 SFP Port Switch" + +#endif diff --git a/machine/FG_4GT_2SX_V2_0.c b/machine/FG_4GT_2SX_V2_0.c new file mode 100644 index 00000000..9590c6f0 --- /dev/null +++ b/machine/FG_4GT_2SX_V2_0.c @@ -0,0 +1,66 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .min_port = 3, + .max_port = 8, + .n_sfp = 2, + .log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5}, + .phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0}, + .is_sfp = {0, 0, 0, 2, 0, 0, 0, 0, 1}, + + // Left SFP port + .sfp_port[0].pin_detect = GPIO38, + .sfp_port[0].pin_los = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + + // Right SFP port + .sfp_port[1].pin_detect = GPIO37, + .sfp_port[1].pin_los = GPIO_NA, + .sfp_port[1].sds = 0, + .sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), + + .reset_pin = GPIO_NA, + .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, + /* Ports 1-4 RJ45 use set 0, port 5-6 SFP uses set 1 + * Ports 1-4: Green: 2.5GBit, Amber: 10/100/1000MBit + * Ports 5-6: Green: 100MBit-10GBit + */ + .led_sets = { + { + LEDS_2G5 | LEDS_LINK | LEDS_ACT, + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0, + LEDS_2G5 | LEDS_LINK | LEDS_ACT + }, + { + LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + LEDS_10G | LEDS_LINK, + 0, + LEDS_COL | LEDS_DUPLEX + }, + { + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + LEDS_2G5 | LEDS_1G | LEDS_LINK, + LEDS_5G | LEDS_2G5 | LEDS_LINK | LEDS_ACT, + LEDS_10G | LEDS_LINK | LEDS_ACT + }, + { + LEDS_TX, + LEDS_RX, + LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5 | + LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M | LEDS_ACT, + LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5 | + LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M | LEDS_LINK + }, + }, + .led_mux_custom = 1, + .led_mux = { + 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x12, 0x14, 0x3f, 0x15, 0x16, + 0x18, 0x0e, 0x19, 0x11, 0x12, 0x1a, 0x15, 0x16, 0x1c, 0x19, + 0x1a, 0x1d, 0x1d, 0x1e, 0x1e, 0x20, 0x21, 0x22 + }, + }; diff --git a/machine/FG_4GT_2SX_V2_0.h b/machine/FG_4GT_2SX_V2_0.h new file mode 100644 index 00000000..8f9697e7 --- /dev/null +++ b/machine/FG_4GT_2SX_V2_0.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_FG_4GT_2SX_V2_0_H_ +#define _MACHINE_FG_4GT_2SX_V2_0_H_ + +#define MACHINE_MODEL "FG-4GT-2SX_V2.0" + +#endif diff --git a/machine/FG_8GT_1SX.c b/machine/FG_8GT_1SX.c new file mode 100644 index 00000000..0fd3dee0 --- /dev/null +++ b/machine/FG_8GT_1SX.c @@ -0,0 +1,45 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 1, + .min_port = 0, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, + .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .sfp_port[0].pin_detect = GPIO38, + .sfp_port[0].pin_los = GPIO_NA, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + .reset_pin = GPIO_NA, + .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .led_sets = { + /* + Ports 1-8: Left Amber 10/100/1000MBit (Vendor no 10M, but we add it) + Right Green 2.5GBit + SFP-port: Any speed + */ + { + LEDS_2G5 | LEDS_LINK | LEDS_ACT, + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0, + 0, + }, + { + LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0, + 0, + 0, + }, + }, + .led_mux_custom = 1, + .led_mux = { + 0x00, 0x01, 0x04, 0x05, 0x08, 0x09, 0x0c, 0x09, 0x0d, 0x10, + 0x11, 0x0e, 0x14, 0x11, 0x12, 0x15, 0x15, 0x16, 0x18, 0x19, + 0x1a, 0x19, 0x1d, 0x1e, 0x1c, 0x1d, 0x20, 0x21, + }, +}; diff --git a/machine/FG_8GT_1SX.h b/machine/FG_8GT_1SX.h new file mode 100644 index 00000000..e75a9946 --- /dev/null +++ b/machine/FG_8GT_1SX.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_FG_8GT_1SX_H_ +#define _MACHINE_FG_8GT_1SX_H_ + +#define MACHINE_MODEL "FG-8GT-1SX" + +#endif diff --git a/machine/FNS1200P.c b/machine/FNS1200P.c new file mode 100644 index 00000000..71334029 --- /dev/null +++ b/machine/FNS1200P.c @@ -0,0 +1,55 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .min_port = 3, + .max_port = 8, + .n_sfp = 2, + .log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5}, + .phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0}, + .is_sfp = {0, 0, 0, 2, 0, 0, 0, 0, 1}, + + /* Left SFP (logical 8, SDS1): GPIO30=ModAbs, GPIO37=RX_LOS */ + .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[0].pin_los = GPIO37, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + + /* Right SFP (logical 3, SDS0): GPIO50=ModAbs, GPIO51=RX_LOS */ + .sfp_port[1].pin_detect = GPIO50_I2C_SCL2_UART1_TX, + .sfp_port[1].pin_los = GPIO51_I2C_SDA2_UART1_RX, + .sfp_port[1].pin_tx_disable = GPIO_NA, + .sfp_port[1].sds = 0, + .sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), + + .reset_pin = GPIO_NA, + + .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, + + /* Copper ports use SET0; SFP ports use SET1 */ + .port_led_set = {0, 0, 0, 1, 0, 0, 0, 0, 1}, + + .led_sets = { + { /* SET0: copper — LED0=amber (2.5G), LED2=green (1G/100M/10M) */ + LEDS_2G5 | LEDS_LINK | LEDS_ACT, + 0, + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0 + }, + { /* SET1: SFP — all speeds link/act */ + LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0, + 0, 0 + }, + }, + + .led_mux_custom = 1, + .led_mux = { + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, /* GPIO0-7: unused */ + 0x0f, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x12, /* GPIO8-14 */ + 0x14, 0x15, 0x16, 0x18, 0x19, 0x1a, /* GPIO15-20 */ + 0x1c, 0x1d, 0x1e, 0x20, 0x21, 0x22, 0x23 /* GPIO21-27 */ + }, +}; diff --git a/machine/FNS1200P.h b/machine/FNS1200P.h new file mode 100644 index 00000000..e5691b48 --- /dev/null +++ b/machine/FNS1200P.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_FNS1200P_H_ +#define _MACHINE_FNS1200P_H_ + +#define MACHINE_MODEL "FNS-1200P" + +#endif diff --git a/machine/HASIVO_S1100WP_8GT_1SX_SE.c b/machine/HASIVO_S1100WP_8GT_1SX_SE.c new file mode 100644 index 00000000..7500ba94 --- /dev/null +++ b/machine/HASIVO_S1100WP_8GT_1SX_SE.c @@ -0,0 +1,32 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 1, + .mac_flash_offset = 0x1FC000, + .min_port = 0, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, + .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[0].pin_los = GPIO37, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + .reset_pin = GPIO54_ACL_BIT2_EN, + .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, + .led_sets = { { LEDS_2G5 | LEDS_LINK | LEDS_ACT, // Green LED (right) + 0, + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // Amber LED (left) + 0 + }, + { LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT, // SFP LED + 0, + 0, + 0 + }, + }, +}; diff --git a/machine/HASIVO_S1100WP_8GT_1SX_SE.h b/machine/HASIVO_S1100WP_8GT_1SX_SE.h new file mode 100644 index 00000000..d50dfc22 --- /dev/null +++ b/machine/HASIVO_S1100WP_8GT_1SX_SE.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_HASIVO_S1100WP_8GT_1SX_SE_H_ +#define _MACHINE_HASIVO_S1100WP_8GT_1SX_SE_H_ + +#define MACHINE_MODEL "Hasivo S1100WP-8GT-1SX-SE" + +#endif diff --git a/machine/HASIVO_S600W_4GT_2XGT_SE.c b/machine/HASIVO_S600W_4GT_2XGT_SE.c new file mode 100644 index 00000000..e43477d8 --- /dev/null +++ b/machine/HASIVO_S600W_4GT_2XGT_SE.c @@ -0,0 +1,43 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .min_port = 3, + .max_port = 8, + .n_sfp = 0, + .n_10g = 2, + .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, + .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 0}, + .reset_pin = GPIO54_ACL_BIT2_EN, + .high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1 }, + /* Ports 1-4: Green: 2.5GBit, Orange: 10/100/1000MBit + * Ports 5-6: Green: 10GBit, Orange: 5/2.5GBit, None: 10/100/1000MBit + */ + .led_sets = { + { + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // Yellow + LEDS_2G5 | LEDS_LINK, // Green + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK, // Orange + 0, // Unused + }, + { + LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // Yellow + LEDS_10G | LEDS_LINK, // Green + LEDS_5G | LEDS_2G5 | LEDS_LINK, // Orange + 0, // Unused + }, + }, + .led_mux_custom = 1, + .led_mux = { + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x0f, 0x20, + 0x21, 0x22, 0x10, 0x11, 0x12, + 0x14, 0x15, 0x16, 0x18, 0x19, + 0x1a, 0x1c, 0x1d, 0x1e, 0x0c, + 0x0d, 0x0e, 0x23 + }, + +}; diff --git a/machine/HASIVO_S600W_4GT_2XGT_SE.h b/machine/HASIVO_S600W_4GT_2XGT_SE.h new file mode 100644 index 00000000..97beb686 --- /dev/null +++ b/machine/HASIVO_S600W_4GT_2XGT_SE.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_HASIVO_S600W_4GT_2XGT_SE_H_ +#define _MACHINE_HASIVO_S600W_4GT_2XGT_SE_H_ + +#define MACHINE_MODEL "Hasivo S600W-4GT-2XGT-SE" + +#endif diff --git a/machine/HG0402XG_V1_1.c b/machine/HG0402XG_V1_1.c new file mode 100644 index 00000000..6a420daa --- /dev/null +++ b/machine/HG0402XG_V1_1.c @@ -0,0 +1,38 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .min_port = 3, + .max_port = 8, + .n_sfp = 2, + .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, + .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, + .is_sfp = {0, 0, 0, 2, 0, 0, 0, 0, 1}, + .sfp_port[0].pin_detect = 50, + .sfp_port[0].pin_los = 10, + .sfp_port[0].pin_tx_disable = 0xFF, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), + .sfp_port[1].pin_detect = 30, + .sfp_port[1].pin_los = 51, + .sfp_port[1].pin_tx_disable = 0xFF, + .sfp_port[1].sds = 0, + .sfp_port[1].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + .reset_pin = GPIO_NA, + .high_leds = { .mux = LED_27 , .enable = LED_27 | LED_29 }, + .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, + /* The Ethernet ports have 1 amber LED (left) and 1 green LED (right) + * The SFP ports have also 1 amber LED and 1 green LED + * Ethernet ports use LED-set 0, SFP ports use LED-set 1 + */ + .led_sets = { { LEDS_10M | LEDS_LINK | LEDS_ACT, + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_2G5 | LEDS_LINK | LEDS_ACT, + LEDS_2G5 | LEDS_LINK | LEDS_ACT, + 0 }, + { LEDS_100M | LEDS_10M | LEDS_LINK, + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G, + LEDS_10G | LEDS_LINK, + 0 }, + }, +}; diff --git a/machine/HG0402XG_V1_1.h b/machine/HG0402XG_V1_1.h new file mode 100644 index 00000000..628dc168 --- /dev/null +++ b/machine/HG0402XG_V1_1.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_HG0402XG_V1_1_H_ +#define _MACHINE_HG0402XG_V1_1_H_ + +#define MACHINE_MODEL "HG0402XG V1.1" + +#endif diff --git a/machine/HI_K0402WS.c b/machine/HI_K0402WS.c new file mode 100644 index 00000000..3ad7b241 --- /dev/null +++ b/machine/HI_K0402WS.c @@ -0,0 +1,46 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .min_port = 3, + .max_port = 8, + .n_sfp = 2, + .log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5}, + .phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0}, + .is_sfp = {0, 0, 0, 2, 0, 0, 0, 0, 1}, + + // Left SFP port + .sfp_port[0].pin_detect = GPIO38, + .sfp_port[0].pin_los = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + + // Right SFP port + .sfp_port[1].pin_detect = GPIO37, + .sfp_port[1].pin_los = GPIO_NA, + .sfp_port[1].sds = 0, + .sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), + + .reset_pin = GPIO_NA, + .high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, + .led_sets = { + { + LEDS_2G5 | LEDS_LINK | LEDS_10M | LEDS_ACT, + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G, + LEDS_1G | LEDS_LINK, + 0 + }, + { + LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK, + LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_ACT, + 0, + 0 + }, + }, + .led_mux_custom = 1, + .led_mux = { + 0x00,0x01,0x04,0x05,0x08,0x09,0x0c,0x3f,0x0d,0x10,0x11,0x0e,0x14,0x11,0x12,0x15,0x15,0x16,0x18,0x19,0x1a,0x19,0x1d,0x1e,0x1c,0x1d,0x20,0x21 + }, + }; diff --git a/machine/HI_K0402WS.h b/machine/HI_K0402WS.h new file mode 100644 index 00000000..d930fce9 --- /dev/null +++ b/machine/HI_K0402WS.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_HI_K0402WS_H_ +#define _MACHINE_HI_K0402WS_H_ + +#define MACHINE_MODEL "PCB-K0402WS-V3.0" + +#endif diff --git a/machine/HI_K0801WS.c b/machine/HI_K0801WS.c new file mode 100644 index 00000000..8fba5e82 --- /dev/null +++ b/machine/HI_K0801WS.c @@ -0,0 +1,45 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 1, + .min_port = 0, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, + .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + + .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[0].pin_los = GPIO37, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + .reset_pin = GPIO_NA, + + .high_leds = { + .mux = LED_27 | LED_29, + .enable = LED_28_SYS | LED_29 + }, + + /* Ports 1-8 use set 0, port 9 SFP uses set 1 */ + .port_led_set = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + + .led_sets = { + { /* Set 0: RJ45 copper ports + * Amber = 2.5G + * Green = 1G/100M/10M with activity + */ + LEDS_2G5 | LEDS_LINK | LEDS_ACT, /* Amber */ + 0, + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, /* Green */ + 0 + }, + { /* Set 1: SFP port, single green LED for all valid speeds */ + LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0, + 0, + 0 + }, + }, +}; diff --git a/machine/HI_K0801WS.h b/machine/HI_K0801WS.h new file mode 100644 index 00000000..4321d0fe --- /dev/null +++ b/machine/HI_K0801WS.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_HI_K0801WS_H_ +#define _MACHINE_HI_K0801WS_H_ + +#define MACHINE_MODEL "Hi-Source HI-k0801WS" + +#endif diff --git a/machine/K0501W_V2_0.c b/machine/K0501W_V2_0.c new file mode 100644 index 00000000..75f36c6b --- /dev/null +++ b/machine/K0501W_V2_0.c @@ -0,0 +1,34 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .min_port = 3, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, + .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[0].pin_los = GPIO37, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + + .reset_pin = GPIO_NA, + .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, + .led_sets = { + { + LEDS_2G5 | LEDS_LINK | LEDS_ACT, + 0, + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0 + }, + { + LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0, + 0, + 0 + }, + }, +}; diff --git a/machine/K0501W_V2_0.h b/machine/K0501W_V2_0.h new file mode 100644 index 00000000..dbe10df6 --- /dev/null +++ b/machine/K0501W_V2_0.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_K0501W_V2_0_H_ +#define _MACHINE_K0501W_V2_0_H_ + +#define MACHINE_MODEL "K0501W V2.0" + +#endif diff --git a/machine/KP_9000_6XHML_X2.c b/machine/KP_9000_6XHML_X2.c new file mode 100644 index 00000000..a6520f01 --- /dev/null +++ b/machine/KP_9000_6XHML_X2.c @@ -0,0 +1,37 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .min_port = 3, + .max_port = 8, + .n_sfp = 2, + .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, + .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, + .is_sfp = {0, 0, 0, 1, 0, 0, 0, 0, 2}, + // Left SFP port (5) + .sfp_port[0].pin_detect = GPIO50_I2C_SCL2_UART1_TX, + .sfp_port[0].pin_los = GPIO10_LED10, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 0, + .sfp_port[0].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), + // Right SFP port (6) + .sfp_port[1].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[1].pin_los = GPIO37, + .sfp_port[1].pin_tx_disable = GPIO_NA, + .sfp_port[1].sds = 1, + .sfp_port[1].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + .reset_pin = GPIO54_ACL_BIT2_EN, + .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 0}, + /* Conditions for LED on: + * dual led orange: ledset_0 & ledset_2 + * dual led green: ledset_2 & !ledset_0 + * single right led green: ledset_0 & !ledset_1 + */ + .led_sets = { { LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G, + LEDS_2G5 | LEDS_LINK | LEDS_10G, + LEDS_1G | LEDS_LINK, + 0 }, + }, +}; diff --git a/machine/KP_9000_6XHML_X2.h b/machine/KP_9000_6XHML_X2.h new file mode 100644 index 00000000..da850ae4 --- /dev/null +++ b/machine/KP_9000_6XHML_X2.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_KP_9000_6XHML_X2_H_ +#define _MACHINE_KP_9000_6XHML_X2_H_ + +#define MACHINE_MODEL "keepLink KP-9000-6XHML-X2" + +#endif diff --git a/machine/KP_9000_6XHML_X2_V1_1.c b/machine/KP_9000_6XHML_X2_V1_1.c new file mode 120000 index 00000000..f9ce2cc7 --- /dev/null +++ b/machine/KP_9000_6XHML_X2_V1_1.c @@ -0,0 +1 @@ +KP_9000_6XHML_X2.c \ No newline at end of file diff --git a/machine/KP_9000_6XHML_X2_V1_1.h b/machine/KP_9000_6XHML_X2_V1_1.h new file mode 100644 index 00000000..1e8b2c49 --- /dev/null +++ b/machine/KP_9000_6XHML_X2_V1_1.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_KP_9000_6XHML_X2_V1_1_H_ +#define _MACHINE_KP_9000_6XHML_X2_V1_1_H_ + +#define MACHINE_MODEL "keepLink KP-9000-6XHML V1.1" + +#endif diff --git a/machine/KP_9000_6XHML_X2_V1_2.c b/machine/KP_9000_6XHML_X2_V1_2.c new file mode 120000 index 00000000..f9ce2cc7 --- /dev/null +++ b/machine/KP_9000_6XHML_X2_V1_2.c @@ -0,0 +1 @@ +KP_9000_6XHML_X2.c \ No newline at end of file diff --git a/machine/KP_9000_6XHML_X2_V1_2.h b/machine/KP_9000_6XHML_X2_V1_2.h new file mode 100644 index 00000000..484ff3e2 --- /dev/null +++ b/machine/KP_9000_6XHML_X2_V1_2.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_KP_9000_6XHML_X2_V1_2_H_ +#define _MACHINE_KP_9000_6XHML_X2_V1_2_H_ + +#define MACHINE_MODEL "keepLink KP-9000-6XHML V1.2" + +#endif diff --git a/machine/KP_9000_6XHML_X2_V2_1.c b/machine/KP_9000_6XHML_X2_V2_1.c new file mode 120000 index 00000000..5ebcb090 --- /dev/null +++ b/machine/KP_9000_6XHML_X2_V2_1.c @@ -0,0 +1 @@ +KP_9000_6XH_X2.c \ No newline at end of file diff --git a/machine/KP_9000_6XHML_X2_V2_1.h b/machine/KP_9000_6XHML_X2_V2_1.h new file mode 100644 index 00000000..4cea1de7 --- /dev/null +++ b/machine/KP_9000_6XHML_X2_V2_1.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_KP_9000_6XHML_X2_V2_1_H_ +#define _MACHINE_KP_9000_6XHML_X2_V2_1_H_ + +#define MACHINE_MODEL "keepLink KP-9000-6XHML V2.1" + +#endif diff --git a/machine/KP_9000_6XH_X.c b/machine/KP_9000_6XH_X.c new file mode 100644 index 00000000..50dcc10b --- /dev/null +++ b/machine/KP_9000_6XH_X.c @@ -0,0 +1,28 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .min_port = 3, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, + .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[0].pin_los = GPIO37, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + .reset_pin = GPIO_NA, + /* Conditions for LED on: + * dual led orange: ledset_0 & ledset_2 + * dual led green: ledset_2 & !ledset_0 + * single right led green: ledset_0 & !ledset_1 + */ + .led_sets = { { LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G, + LEDS_2G5 | LEDS_LINK | LEDS_10G, + LEDS_1G | LEDS_LINK, + 0 }, + }, +}; diff --git a/machine/KP_9000_6XH_X.h b/machine/KP_9000_6XH_X.h new file mode 100644 index 00000000..f52d0ad6 --- /dev/null +++ b/machine/KP_9000_6XH_X.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_KP_9000_6XH_X_H_ +#define _MACHINE_KP_9000_6XH_X_H_ + +#define MACHINE_MODEL "keepLink KP-9000-6XH-X" + +#endif diff --git a/machine/KP_9000_6XH_X2.c b/machine/KP_9000_6XH_X2.c new file mode 100644 index 00000000..aa6a2655 --- /dev/null +++ b/machine/KP_9000_6XH_X2.c @@ -0,0 +1,48 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .min_port = 3, + .max_port = 8, + .n_sfp = 2, + .log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5}, + .phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0}, + .is_sfp = {0, 0, 0, 2, 0, 0, 0, 0, 1}, + + // Left SFP port + .sfp_port[0].pin_detect = GPIO38, + .sfp_port[0].pin_los = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + + // Right SFP port + .sfp_port[1].pin_detect = GPIO37, + .sfp_port[1].pin_los = GPIO_NA, + .sfp_port[1].sds = 0, + .sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), + + .reset_pin = GPIO48_I2C_SCL1, // Button-Switch is unpopulated on PCB, but can be added manually (hole in case is already there) + .high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, + .led_sets = { + { + LEDS_2G5 | LEDS_LINK, // Left LED (Amber) + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // Right LED (Green) + 0, + 0 + }, + { + LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0, + 0, + 0 + }, + }, + .led_mux_custom = 1, + .led_mux = { + 0x00,0x01,0x04,0x05,0x08,0x09,0x0c,0x3f,0x0d,0x10, + 0x11,0x0e,0x14,0x11,0x12,0x15,0x15,0x16,0x18,0x19, + 0x1a,0x19,0x1d,0x1e,0x1c,0x1d,0x20,0x21 + }, + }; diff --git a/machine/KP_9000_6XH_X2.h b/machine/KP_9000_6XH_X2.h new file mode 100644 index 00000000..5493dc79 --- /dev/null +++ b/machine/KP_9000_6XH_X2.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_KP_9000_6XH_X2_H_ +#define _MACHINE_KP_9000_6XH_X2_H_ + +#define MACHINE_MODEL "keepLink KP-9000-6XH-X2" + +#endif diff --git a/machine/KP_9000_6XH_X2_V1_1.c b/machine/KP_9000_6XH_X2_V1_1.c new file mode 120000 index 00000000..f9ce2cc7 --- /dev/null +++ b/machine/KP_9000_6XH_X2_V1_1.c @@ -0,0 +1 @@ +KP_9000_6XHML_X2.c \ No newline at end of file diff --git a/machine/KP_9000_6XH_X2_V1_1.h b/machine/KP_9000_6XH_X2_V1_1.h new file mode 100644 index 00000000..183cc276 --- /dev/null +++ b/machine/KP_9000_6XH_X2_V1_1.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_KP_9000_6XH_X2_V1_1_H_ +#define _MACHINE_KP_9000_6XH_X2_V1_1_H_ + +#define MACHINE_MODEL "keepLink KP-9000-6XH V1.1" + +#endif diff --git a/machine/KP_9000_6XH_X2_V1_2.c b/machine/KP_9000_6XH_X2_V1_2.c new file mode 120000 index 00000000..f9ce2cc7 --- /dev/null +++ b/machine/KP_9000_6XH_X2_V1_2.c @@ -0,0 +1 @@ +KP_9000_6XHML_X2.c \ No newline at end of file diff --git a/machine/KP_9000_6XH_X2_V1_2.h b/machine/KP_9000_6XH_X2_V1_2.h new file mode 100644 index 00000000..4fcd94af --- /dev/null +++ b/machine/KP_9000_6XH_X2_V1_2.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_KP_9000_6XH_X2_V1_2_H_ +#define _MACHINE_KP_9000_6XH_X2_V1_2_H_ + +#define MACHINE_MODEL "keepLink KP-9000-6XH V1.2" + +#endif diff --git a/machine/KP_9000_6XH_X2_V2_1.c b/machine/KP_9000_6XH_X2_V2_1.c new file mode 120000 index 00000000..5ebcb090 --- /dev/null +++ b/machine/KP_9000_6XH_X2_V2_1.c @@ -0,0 +1 @@ +KP_9000_6XH_X2.c \ No newline at end of file diff --git a/machine/KP_9000_6XH_X2_V2_1.h b/machine/KP_9000_6XH_X2_V2_1.h new file mode 100644 index 00000000..a144dc96 --- /dev/null +++ b/machine/KP_9000_6XH_X2_V2_1.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_KP_9000_6XH_X2_V2_1_H_ +#define _MACHINE_KP_9000_6XH_X2_V2_1_H_ + +#define MACHINE_MODEL "keepLink KP-9000-6XH-X2 V2.1" + +#endif diff --git a/machine/KP_9000_9XHML_X_V2_2.c b/machine/KP_9000_9XHML_X_V2_2.c new file mode 100644 index 00000000..e73a7514 --- /dev/null +++ b/machine/KP_9000_9XHML_X_V2_2.c @@ -0,0 +1,51 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 1, + .min_port = 0, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, + .phys_to_log_port = { 0, 1, 2, 3, 4, 5, 6, 7, 8 }, + .is_sfp = { 0, 0, 0, 0, 0, 0, 0, 0, 1 }, + .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[0].pin_los = GPIO37, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + .reset_pin = GPIO54_ACL_BIT2_EN, + .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1 }, + .led_sets = { + { /* Set 0 for RJ45 connectors */ + /* LED0: Right Green */ + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + /* LED1: Left Orange */ + LEDS_2G5 | LEDS_LINK, + /* LED2: Left Green */ + LEDS_1G | LEDS_LINK, + /* LED3: None */ + 0, + }, { /* Set 1 for SFP port */ + /* LED0: Single Green "9" LED */ + LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5, + /* LED1: D23 LED on PCB */ + LEDS_1G | LEDS_LINK, + /* LED2: D22 LED on PCB */ + LEDS_2G5 | LEDS_LINK, + /* LED3: Unused */ + LEDS_COL | LEDS_DUPLEX, + }, { /* Set 2: Unused, but set to same things as stock firmware for consistency */ + LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + LEDS_1G | LEDS_LINK | LEDS_ACT, + LEDS_2G5 | LEDS_LINK | LEDS_ACT | LEDS_5G, + LEDS_10G | LEDS_ACT | LEDS_LINK, + }, { /* Set 3: Unused, but set to same things as stock firmware for consistency */ + LEDS_TX, + LEDS_RX, + LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M | LEDS_ACT | LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5, + LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5, + }, + }, +}; diff --git a/machine/KP_9000_9XHML_X_V2_2.h b/machine/KP_9000_9XHML_X_V2_2.h new file mode 100644 index 00000000..2d6d0a7f --- /dev/null +++ b/machine/KP_9000_9XHML_X_V2_2.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_KP_9000_9XHML_X_V2_2_H_ +#define _MACHINE_KP_9000_9XHML_X_V2_2_H_ + +#define MACHINE_MODEL "keepLink KP-9000-9XHML-X V2.2" + +#endif diff --git a/machine/KP_9000_9XHML_X_V3_1.c b/machine/KP_9000_9XHML_X_V3_1.c new file mode 100644 index 00000000..26ea751f --- /dev/null +++ b/machine/KP_9000_9XHML_X_V3_1.c @@ -0,0 +1,37 @@ +#include "machine_defs.h" + +// 2M-PCB23-V3.2 is wired like V3.1 (SFP detection, LEDs, factory MAC location) +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 1, + .min_port = 0, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, + .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .sfp_port[0].pin_detect = GPIO38, + .sfp_port[0].pin_los = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + .reset_pin = GPIO48_I2C_SCL1, + .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, + .led_sets = { + { /* RJ45: First LED, yellow, second LED: green */ + LEDS_2G5 | LEDS_LINK, + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0, + 0, + }, { /* SFP PORT, SINGLE GREEN LED */ + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G, + 0, + 0, + 0, + }}, + .led_mux_custom = 1, + .led_mux = {0x00, 0x01, 0x04, 0x05, 0x08, 0x09, 0x0c, 0x09, 0x0d, 0x10, + 0x11, 0x0e, 0x14, 0x11, 0x12, 0x15, 0x15, 0x16, 0x18, 0x19, + 0x1a, 0x19, 0x1d, 0x1e, 0x1c, 0x1d, 0x20, 0x21}, + .mac_flash_offset = 0x1FC000, +}; diff --git a/machine/KP_9000_9XHML_X_V3_1.h b/machine/KP_9000_9XHML_X_V3_1.h new file mode 100644 index 00000000..c100b5f8 --- /dev/null +++ b/machine/KP_9000_9XHML_X_V3_1.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_KP_9000_9XHML_X_V3_1_H_ +#define _MACHINE_KP_9000_9XHML_X_V3_1_H_ + +#define MACHINE_MODEL "keepLink KP-9000-9XHML-X V3.1" + +#endif diff --git a/machine/KP_9000_9XHML_X_V3_2.c b/machine/KP_9000_9XHML_X_V3_2.c new file mode 120000 index 00000000..3e9156ac --- /dev/null +++ b/machine/KP_9000_9XHML_X_V3_2.c @@ -0,0 +1 @@ +KP_9000_9XHML_X_V3_1.c \ No newline at end of file diff --git a/machine/KP_9000_9XHML_X_V3_2.h b/machine/KP_9000_9XHML_X_V3_2.h new file mode 100644 index 00000000..e3b4eec2 --- /dev/null +++ b/machine/KP_9000_9XHML_X_V3_2.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_KP_9000_9XHML_X_V3_2_H_ +#define _MACHINE_KP_9000_9XHML_X_V3_2_H_ + +#define MACHINE_MODEL "keepLink KP-9000-9XHML-X V3.2" + +#endif diff --git a/machine/KP_9000_9XH_X_EU.c b/machine/KP_9000_9XH_X_EU.c new file mode 100644 index 00000000..7209e82b --- /dev/null +++ b/machine/KP_9000_9XH_X_EU.c @@ -0,0 +1,25 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 1, + .min_port = 0, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, + .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[0].pin_los = GPIO37, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + .reset_pin = GPIO_NA, + .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 0}, + .led_sets = { { LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5, + LEDS_2G5 | LEDS_LINK, + LEDS_1G | LEDS_LINK, + LEDS_2G5 | LEDS_LINK | LEDS_ACT }, + }, +}; diff --git a/machine/KP_9000_9XH_X_EU.h b/machine/KP_9000_9XH_X_EU.h new file mode 100644 index 00000000..2269b8fb --- /dev/null +++ b/machine/KP_9000_9XH_X_EU.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_KP_9000_9XH_X_EU_H_ +#define _MACHINE_KP_9000_9XH_X_EU_H_ + +#define MACHINE_MODEL "keepLink KP-9000-9XH-X-EU" + +#endif diff --git a/machine/LIANGUO_HYWS_SGT0108S.c b/machine/LIANGUO_HYWS_SGT0108S.c new file mode 100644 index 00000000..aff1713c --- /dev/null +++ b/machine/LIANGUO_HYWS_SGT0108S.c @@ -0,0 +1,36 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 1, + .min_port = 0, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {2, 1, 4, 3, 5, 6, 7, 8, 9}, + .phys_to_log_port = {1, 0, 3, 2, 4, 5, 6, 7, 8}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + + .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[0].pin_los = GPIO37, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + + .reset_pin = GPIO_NA, + .high_leds = { .mux = 0, .enable = 0 }, + .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1 }, + .led_sets = { + { + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // RJ45 Blue LEDs + LEDS_2G5 | LEDS_LINK, // RJ45 White LEDs + 0, + 0 + }, + { + LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // SFP blue LED? + 0, + 0, + 0 + }, + }, +}; diff --git a/machine/LIANGUO_HYWS_SGT0108S.h b/machine/LIANGUO_HYWS_SGT0108S.h new file mode 100644 index 00000000..f82bd128 --- /dev/null +++ b/machine/LIANGUO_HYWS_SGT0108S.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_LIANGUO_HYWS_SGT0108S_H_ +#define _MACHINE_LIANGUO_HYWS_SGT0108S_H_ + +#define MACHINE_MODEL "Lianguo HYWS-SGT0108S" + +#endif diff --git a/machine/LIANGUO_ZX_SWTGW215AS.c b/machine/LIANGUO_ZX_SWTGW215AS.c new file mode 100644 index 00000000..70414eca --- /dev/null +++ b/machine/LIANGUO_ZX_SWTGW215AS.c @@ -0,0 +1,33 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .mac_flash_offset = 0x1FC000, + .min_port = 3, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, + .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[0].pin_los = GPIO37, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + .reset_pin = GPIO54_ACL_BIT2_EN, + .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, + .led_sets = { { LEDS_2G5 | LEDS_LINK | LEDS_ACT, // Green LED (right) + 0, // unused + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // Amber LED (left) + 0 + }, // unused + { LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT, // SFP LED + 0, // unused + 0, // unused + 0 + }, // unused }, + }, + .led_mux_custom = 0, +}; diff --git a/machine/LIANGUO_ZX_SWTGW215AS.h b/machine/LIANGUO_ZX_SWTGW215AS.h new file mode 100644 index 00000000..6c4e2210 --- /dev/null +++ b/machine/LIANGUO_ZX_SWTGW215AS.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_LIANGUO_ZX_SWTGW215AS_H_ +#define _MACHINE_LIANGUO_ZX_SWTGW215AS_H_ + +#define MACHINE_MODEL "Lianguo ZX-SWTGW215AS" + +#endif diff --git a/machine/ONT_S207CW_62TS_SE.c b/machine/ONT_S207CW_62TS_SE.c new file mode 100644 index 00000000..6ba60778 --- /dev/null +++ b/machine/ONT_S207CW_62TS_SE.c @@ -0,0 +1,61 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .min_port = 3, + .max_port = 8, + .n_sfp = 2, + .log_to_phys_port = {0, 0, 0, 5, 4, 3, 2, 1, 6}, + .phys_to_log_port = {7, 6, 5, 4, 3, 8, 0, 0, 0}, + .is_sfp = {0, 0, 0, 1, 0, 0, 0, 0, 2}, + + // SFP port on SDS0 / logical port 3 + .sfp_port[0].pin_detect = GPIO37, + .sfp_port[0].pin_los = GPIO_NA, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 0, + .sfp_port[0].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), + + // SFP port on SDS1 / logical port 8 + .sfp_port[1].pin_detect = GPIO38, + .sfp_port[1].pin_los = GPIO_NA, + .sfp_port[1].pin_tx_disable = GPIO_NA, + .sfp_port[1].sds = 1, + .sfp_port[1].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + + .reset_pin = GPIO_NA, + .high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 3, 0, 0, 2, 2, 3}, + .mac_flash_offset = 0x1FC000, + .led_sets = { + { // RJ45 Ports 3-4 (normal wiring) + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + LEDS_2G5 | LEDS_LINK | LEDS_ACT, + 0, + 0 + }, + { // Unused + 0, + 0, + 0, + 0 + }, + { // RJ45 Ports 1-2 (reversed wiring) + LEDS_2G5 | LEDS_LINK | LEDS_ACT, + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0, + 0 + }, + { // SFP Ports 5-6 + LEDS_10G | LEDS_LINK | LEDS_ACT, + LEDS_2G5 | LEDS_1G | LEDS_LINK | LEDS_ACT, + 0, + 0 + }, + }, + .led_mux_custom = 1, + .led_mux = { + 0x00,0x01,0x04,0x05,0x08,0x09,0x0c,0x3f,0x0d,0x10,0x11,0x0e,0x14,0x11,0x12,0x15,0x15,0x16,0x18,0x19,0x1a,0x19,0x1d,0x1e,0x1c,0x1d,0x20,0x21 + }, +}; diff --git a/machine/ONT_S207CW_62TS_SE.h b/machine/ONT_S207CW_62TS_SE.h new file mode 100644 index 00000000..9938b004 --- /dev/null +++ b/machine/ONT_S207CW_62TS_SE.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_ONT_S207CW_62TS_SE_H_ +#define _MACHINE_ONT_S207CW_62TS_SE_H_ + +#define MACHINE_MODEL "ONT-S207CW-62TS-SE / Binardat 2G06-04210GSM / Binardat 2G06-04210GS" + +#endif diff --git a/machine/PB_2132.c b/machine/PB_2132.c new file mode 100644 index 00000000..8822c066 --- /dev/null +++ b/machine/PB_2132.c @@ -0,0 +1,41 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 1, + .min_port = 0, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, + .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + + .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[0].pin_los = GPIO37, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + + .reset_pin = GPIO_NA, + + .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, + + .led_sets = { + { + /* LED0 = RJ45: Amber LED: 2.5G */ + LEDS_2G5 | LEDS_LINK | LEDS_ACT, + /* LED1 = RJ45: Green LED: 10M / 100M / 1000M */ + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0, + 0 + }, + { + /* SFP+ single Green LED. */ + LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT, + 0, + 0, + 0 + }, + }, + }; diff --git a/machine/PB_2132.h b/machine/PB_2132.h new file mode 100644 index 00000000..a3668767 --- /dev/null +++ b/machine/PB_2132.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_PB_2132_H_ +#define _MACHINE_PB_2132_H_ + +#define MACHINE_MODEL "PB-2132" + +#endif diff --git a/machine/PCB_K0402WS_V2.c b/machine/PCB_K0402WS_V2.c new file mode 100644 index 00000000..c37e6201 --- /dev/null +++ b/machine/PCB_K0402WS_V2.c @@ -0,0 +1,55 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .min_port = 3, + .max_port = 8, + .n_sfp = 2, + .log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5}, + .phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0}, + .is_sfp = {0, 0, 0, 2, 0, 0, 0, 0, 1}, + + /* Left SFP (logical 8, SDS1): GPIO30=ModAbs, GPIO37=RX_LOS */ + .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[0].pin_los = GPIO37, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + + /* Right SFP (logical 3, SDS0): GPIO50=ModAbs, GPIO51=RX_LOS */ + .sfp_port[1].pin_detect = GPIO50_I2C_SCL2_UART1_TX, + .sfp_port[1].pin_los = GPIO51_I2C_SDA2_UART1_RX, + .sfp_port[1].pin_tx_disable = GPIO_NA, + .sfp_port[1].sds = 0, + .sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), + + .reset_pin = GPIO_NA, + + .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, + + /* Copper ports use SET0; SFP ports use SET1 */ + .port_led_set = {0, 0, 0, 1, 0, 0, 0, 0, 1}, + + .led_sets = { + { /* SET0: copper — LED0=amber (2.5G), LED2=green (1G/100M/10M) */ + LEDS_2G5 | LEDS_LINK | LEDS_ACT, + 0, + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0 + }, + { /* SET1: SFP — all speeds link/act */ + LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0, + 0, 0 + }, + }, + + .led_mux_custom = 1, + .led_mux = { + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, /* GPIO0-7: unused */ + 0x0f, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x12, /* GPIO8-14 */ + 0x14, 0x15, 0x16, 0x18, 0x19, 0x1a, /* GPIO15-20 */ + 0x1c, 0x1d, 0x1e, 0x20, 0x21, 0x22, 0x23 /* GPIO21-27 */ + }, +}; diff --git a/machine/PCB_K0402WS_V2.h b/machine/PCB_K0402WS_V2.h new file mode 100644 index 00000000..eb5337ab --- /dev/null +++ b/machine/PCB_K0402WS_V2.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_PCB_K0402WS_V2_H_ +#define _MACHINE_PCB_K0402WS_V2_H_ + +#define MACHINE_MODEL "PCB-K0402WS-V2.0" + +#endif diff --git a/machine/PCB_K0402WS_V3.c b/machine/PCB_K0402WS_V3.c new file mode 120000 index 00000000..1e9dcd82 --- /dev/null +++ b/machine/PCB_K0402WS_V3.c @@ -0,0 +1 @@ +HI_K0402WS.c \ No newline at end of file diff --git a/machine/PCB_K0402WS_V3.h b/machine/PCB_K0402WS_V3.h new file mode 100644 index 00000000..f206079d --- /dev/null +++ b/machine/PCB_K0402WS_V3.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_PCB_K0402WS_V3_H_ +#define _MACHINE_PCB_K0402WS_V3_H_ + +#define MACHINE_MODEL "PCB-K0402WS-V3.0" + +#endif diff --git a/machine/PCB_SWTG018AS_V2_1_0.c b/machine/PCB_SWTG018AS_V2_1_0.c new file mode 100644 index 00000000..1501de45 --- /dev/null +++ b/machine/PCB_SWTG018AS_V2_1_0.c @@ -0,0 +1,42 @@ +#include "machine_defs.h" + + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 1, + .mac_flash_offset = 0x1FC000, + .min_port = 0, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, + .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .sfp_port[0].pin_detect = GPIO38, // pulled low on module insert + .sfp_port[0].pin_los = GPIO_NA, // no LOS pin wired + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + .reset_pin = GPIO54_ACL_BIT2_EN, + .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, + // LED wiring matches the SWTG018AS-A V2.0 (same PCB family) + .led_sets = { + { /* RJ45: First LED, yellow, second LED: green */ + LEDS_2G5 | LEDS_LINK, + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0, + 0, + }, { /* SFP set (superseded by the raw register override in machine_custom_init) */ + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G, + 0, + 0, + 0, + }}, + .led_mux_custom = 1, + .led_mux = { 0x00, 0x01, 0x04, 0x05, 0x08, // 65e0 + 0x09, 0x0c, 0x09, 0x0d, 0x10, // 65e4 + 0x11, 0x0e, 0x14, 0x11, 0x12, // 65e8 + 0x15, 0x15, 0x16, 0x18, 0x19, // 65ec + 0x1a, 0x19, 0x1d, 0x1e, 0x1c, // 65f0 + 0x1d, 0x20, 0x21 }, +}; diff --git a/machine/PCB_SWTG018AS_V2_1_0.h b/machine/PCB_SWTG018AS_V2_1_0.h new file mode 100644 index 00000000..fce78470 --- /dev/null +++ b/machine/PCB_SWTG018AS_V2_1_0.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_PCB_SWTG018AS_V2_1_0_H_ +#define _MACHINE_PCB_SWTG018AS_V2_1_0_H_ + +#define MACHINE_MODEL "SWTGW218AS (SWTG018AS-V2.1.0)" + +#endif diff --git a/machine/PCB_SWTG024AS_A_2_0_1.c b/machine/PCB_SWTG024AS_A_2_0_1.c new file mode 100644 index 00000000..169e479a --- /dev/null +++ b/machine/PCB_SWTG024AS_A_2_0_1.c @@ -0,0 +1,48 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .min_port = 3, + .max_port = 8, + .n_sfp = 2, + .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, + .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, + .is_sfp = {0, 0, 0, 1, 0, 0, 0, 0, 2}, + + // SFP port on SDS0 / logical port 3 + .sfp_port[0].pin_detect = GPIO37, + .sfp_port[0].pin_los = GPIO_NA, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 0, + .sfp_port[0].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), + + // SFP port on SDS1 / logical port 8 + .sfp_port[1].pin_detect = GPIO38, + .sfp_port[1].pin_los = GPIO_NA, + .sfp_port[1].pin_tx_disable = GPIO_NA, + .sfp_port[1].sds = 1, + .sfp_port[1].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + + .reset_pin = GPIO_NA, + .high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, + .led_sets = { + { + LEDS_2G5 | LEDS_LINK | LEDS_ACT, + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + LEDS_DUPLEX, + LEDS_2G5 | LEDS_LINK | LEDS_ACT + }, + { + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT, + LEDS_10G | LEDS_LINK | LEDS_ACT, + LEDS_2G5 | LEDS_LINK, + LEDS_COL | LEDS_DUPLEX + }, + }, + .led_mux_custom = 1, + .led_mux = { + 0x00,0x01,0x04,0x05,0x08,0x09,0x0c,0x3f,0x0d,0x10,0x11,0x0e,0x14,0x11,0x12,0x15,0x15,0x16,0x18,0x19,0x1a,0x19,0x1d,0x1e,0x1c,0x1d,0x20,0x21 + }, + }; diff --git a/machine/PCB_SWTG024AS_A_2_0_1.h b/machine/PCB_SWTG024AS_A_2_0_1.h new file mode 100644 index 00000000..2d3b5c22 --- /dev/null +++ b/machine/PCB_SWTG024AS_A_2_0_1.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_PCB_SWTG024AS_A_2_0_1_H_ +#define _MACHINE_PCB_SWTG024AS_A_2_0_1_H_ + +#define MACHINE_MODEL "PCB-SWTG024AS-A-2.0.1" + +#endif diff --git a/machine/POE_2G080110GS.c b/machine/POE_2G080110GS.c new file mode 100644 index 00000000..73f6dc8c --- /dev/null +++ b/machine/POE_2G080110GS.c @@ -0,0 +1,44 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 1, + .min_port = 0, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, + .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[0].pin_los = GPIO37, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + .reset_pin = GPIO_NA, + .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, + .led_sets = { + { + /* SET0: RJ45 2.5G = amber, 1G/100M/10M = green */ + LEDS_2G5 | LEDS_LINK | LEDS_ACT, /* amber */ + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, /* green */ + 0, + 0 + }, + { + /* SET1: SFP+ port = green */ + LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M | + LEDS_LINK | LEDS_ACT | LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5, + 0, + 0, + 0 + } + }, + .led_mux_custom = 1, + .led_mux = { 0x00, 0x01, 0x04, 0x05, 0x08, + 0x09, 0x0c, 0x09, 0x0d, 0x10, + 0x11, 0x0e, 0x14, 0x11, 0x12, + 0x15, 0x15, 0x16, 0x18, 0x19, + 0x1a, 0x19, 0x1d, 0x1e, 0x1c, + 0x1d, 0x20, 0x21 }, +}; diff --git a/machine/POE_2G080110GS.h b/machine/POE_2G080110GS.h new file mode 100644 index 00000000..40ce3415 --- /dev/null +++ b/machine/POE_2G080110GS.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_POE_2G080110GS_H_ +#define _MACHINE_POE_2G080110GS_H_ + +#define MACHINE_MODEL "MokerLink POE-2G080110GS" + +#endif diff --git a/machine/STEAMEMO_IG204_V1.c b/machine/STEAMEMO_IG204_V1.c new file mode 100644 index 00000000..23d876a7 --- /dev/null +++ b/machine/STEAMEMO_IG204_V1.c @@ -0,0 +1,50 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .min_port = 3, + .max_port = 8, + .n_sfp = 2, + .log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5}, + .phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0}, + .is_sfp = {0, 0, 0, 2, 0, 0, 0, 0, 1}, + // Left SFP port (5) + // LED pin 9 + .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[0].pin_los = GPIO37, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + // Right SFP port (6) + // LED pin 24 + .sfp_port[1].pin_detect = GPIO50_I2C_SCL2_UART1_TX, + .sfp_port[1].pin_los = GPIO51_I2C_SDA2_UART1_RX, + .sfp_port[1].pin_tx_disable = GPIO_NA, + .sfp_port[1].sds = 0, + .sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), + .reset_pin = GPIO_NA, + .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, + .led_sets = { + { + // RJ45 Amber LED (left) + LEDS_2G5 | LEDS_LINK, + // RJ45 Green LED (Right) + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0, + 0, + }, + { + // SFP LED + LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT, + 0, // unused + 0, // unused + 0 + }, + }, + .led_mux_custom = 1, + .led_mux = { + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x0f, 0x20, 0x0d, 0x0e, 0x10, 0x11, 0x12, 0x14, 0x15, 0x16, 0x18, 0x19, 0x1a, 0x1c, 0x1d, 0x1e, 0x0c, 0x21, 0x22, 0x23 + }, +}; diff --git a/machine/STEAMEMO_IG204_V1.h b/machine/STEAMEMO_IG204_V1.h new file mode 100644 index 00000000..170840d7 --- /dev/null +++ b/machine/STEAMEMO_IG204_V1.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_STEAMEMO_IG204_V1_H_ +#define _MACHINE_STEAMEMO_IG204_V1_H_ + +#define MACHINE_MODEL "Steamemo IG204 V1" + +#endif diff --git a/machine/SWGT024_V2_0_MANAGED.c b/machine/SWGT024_V2_0_MANAGED.c new file mode 100644 index 00000000..c2a5d6ab --- /dev/null +++ b/machine/SWGT024_V2_0_MANAGED.c @@ -0,0 +1,41 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .min_port = 3, + .max_port = 8, + .n_sfp = 2, + .log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5}, + .phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0}, + .is_sfp= {0, 0, 0, 2, 0, 0, 0, 0, 1}, + // Left SFP port (J4) + .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[0].pin_los = GPIO_NA, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), /* GPIO 39 */ + // Right SFP port (J2) + .sfp_port[1].pin_detect = GPIO50_I2C_SCL2_UART1_TX, + .sfp_port[1].pin_los = GPIO_NA, + .sfp_port[1].pin_tx_disable = GPIO_NA, + .sfp_port[1].sds = 0, + .sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), /* GPIO 40 */ + .reset_pin = GPIO54_ACL_BIT2_EN, + .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, + .led_sets = { + { /* RJ45: Green LED */ + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G, + 0, + /* Amber LED */ + LEDS_2G5 | LEDS_LINK | LEDS_ACT, + 0 + }, { /* SFP PORT: SINGLE GREEN LED */ + LEDS_10M | LEDS_100M | LEDS_1G | LEDS_2G5 | LEDS_10G | LEDS_LINK | LEDS_ACT, + 0, + 0, + 0, + }, + }, +}; diff --git a/machine/SWGT024_V2_0_MANAGED.h b/machine/SWGT024_V2_0_MANAGED.h new file mode 100644 index 00000000..c5f95a04 --- /dev/null +++ b/machine/SWGT024_V2_0_MANAGED.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_SWGT024_V2_0_MANAGED_H_ +#define _MACHINE_SWGT024_V2_0_MANAGED_H_ + +#define MACHINE_MODEL "SWGT024 V2.0 Managed" + +#endif diff --git a/machine/SWGT024_V2_0_UNMANAGED.c b/machine/SWGT024_V2_0_UNMANAGED.c new file mode 100644 index 00000000..9008bb4b --- /dev/null +++ b/machine/SWGT024_V2_0_UNMANAGED.c @@ -0,0 +1,41 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .min_port = 3, + .max_port = 8, + .n_sfp = 2, + .log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5}, + .phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0}, + .is_sfp= {0, 0, 0, 2, 0, 0, 0, 0, 1}, + // Left SFP port (J4) + .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[0].pin_los = GPIO37, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), /* GPIO 39 */ + // Right SFP port (J2) + .sfp_port[1].pin_detect = GPIO50_I2C_SCL2_UART1_TX, + .sfp_port[1].pin_los = GPIO51_I2C_SDA2_UART1_RX, + .sfp_port[1].pin_tx_disable = GPIO_NA, + .sfp_port[1].sds = 0, + .sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), /* GPIO 40 */ + .reset_pin = GPIO_NA, + .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, + .led_sets = { + { /* RJ45: Green LED */ + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G, + 0, + /* Amber LED */ + LEDS_2G5 | LEDS_LINK | LEDS_ACT, + 0 + }, { /* SFP PORT: SINGLE GREEN LED */ + LEDS_10M | LEDS_100M | LEDS_1G | LEDS_2G5 | LEDS_10G | LEDS_LINK | LEDS_ACT, + 0, + 0, + 0, + }, + }, +}; diff --git a/machine/SWGT024_V2_0_UNMANAGED.h b/machine/SWGT024_V2_0_UNMANAGED.h new file mode 100644 index 00000000..8391b42a --- /dev/null +++ b/machine/SWGT024_V2_0_UNMANAGED.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_SWGT024_V2_0_UNMANAGED_H_ +#define _MACHINE_SWGT024_V2_0_UNMANAGED_H_ + +#define MACHINE_MODEL "SWGT024 V2.0 Unmanaged" + +#endif diff --git a/machine/SWTG018AS_A_V_2_0.c b/machine/SWTG018AS_A_V_2_0.c new file mode 100644 index 00000000..8304bf7e --- /dev/null +++ b/machine/SWTG018AS_A_V_2_0.c @@ -0,0 +1,39 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 1, + .min_port = 0, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, + .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .sfp_port[0].pin_detect = GPIO38, + .sfp_port[0].pin_los = GPIO_NA, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + .reset_pin = GPIO48_I2C_SCL1, + .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, + .led_sets = { + { /* RJ45: First LED, yellow, second LED: green */ + LEDS_2G5 | LEDS_LINK, + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0, + 0, + }, { /* SFP PORT, SINGLE GREEN LED */ + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G, + 0, + 0, + 0, + }}, + .led_mux_custom = 1, + .led_mux = { 0x00, 0x01, 0x04, 0x05, 0x08, // 65e0 + 0x09, 0x0c, 0x09, 0x0d, 0x10, // 65e4 + 0x11, 0x0e, 0x14, 0x11, 0x12, // 65e8 + 0x15, 0x15, 0x16, 0x18, 0x19, // 65ec + 0x1a, 0x19, 0x1d, 0x1e, 0x1c, // 65f0 + 0x1d, 0x20, 0x21 }, +}; diff --git a/machine/SWTG018AS_A_V_2_0.h b/machine/SWTG018AS_A_V_2_0.h new file mode 100644 index 00000000..82b7f632 --- /dev/null +++ b/machine/SWTG018AS_A_V_2_0.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_SWTG018AS_A_V_2_0_H_ +#define _MACHINE_SWTG018AS_A_V_2_0_H_ + +#define MACHINE_MODEL "SWTG018AS-A V2.0" + +#endif diff --git a/machine/SWTG024AS_A_2_0_1_5C_1SFP.c b/machine/SWTG024AS_A_2_0_1_5C_1SFP.c new file mode 100644 index 00000000..2a3ad97b --- /dev/null +++ b/machine/SWTG024AS_A_2_0_1_5C_1SFP.c @@ -0,0 +1,43 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .min_port = 3, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, + .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .sfp_port[0].pin_detect = GPIO38, + .sfp_port[0].pin_los = GPIO_NA, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + + .reset_pin = GPIO_NA, + .high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, + /* Ports 1-5 RJ45 use set 0, port 9 SFP uses set 1 + * Ports 1-5: Green: 2.5GBit, Amber: 10/100/1000MBit + * SFP-port: Blue: 10GBit, Green: 100MBit-2.5GBit + */ + .led_sets = { + { + LEDS_2G5 | LEDS_LINK | LEDS_ACT, + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + LEDS_DUPLEX, + LEDS_2G5 | LEDS_LINK | LEDS_ACT + }, + { + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT, + LEDS_10G | LEDS_LINK | LEDS_ACT, + LEDS_2G5 | LEDS_LINK, + LEDS_COL | LEDS_DUPLEX + }, + }, + .led_mux_custom = 1, + .led_mux = { + 0x00,0x01,0x04,0x05,0x08,0x09,0x0c,0x3f,0x0d,0x10,0x11,0x0e,0x14,0x11,0x12,0x15,0x15,0x16,0x18,0x19,0x1a,0x19,0x1d,0x1e,0x1c,0x1d,0x20,0x21 + }, + }; diff --git a/machine/SWTG024AS_A_2_0_1_5C_1SFP.h b/machine/SWTG024AS_A_2_0_1_5C_1SFP.h new file mode 100644 index 00000000..f492765f --- /dev/null +++ b/machine/SWTG024AS_A_2_0_1_5C_1SFP.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_SWTG024AS_A_2_0_1_5C_1SFP_H_ +#define _MACHINE_SWTG024AS_A_2_0_1_5C_1SFP_H_ + +#define MACHINE_MODEL "SWTG024AS-A-V2.0.1-5C-1SFP" + +#endif diff --git a/machine/SWTG024AS_V2_0.c b/machine/SWTG024AS_V2_0.c new file mode 100644 index 00000000..eb0652e2 --- /dev/null +++ b/machine/SWTG024AS_V2_0.c @@ -0,0 +1,43 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .min_port = 3, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, + .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[0].pin_los = GPIO37, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + + .reset_pin = GPIO_NA, + .high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, + /* Ports 1-5 RJ45 use set 0, port 9 SFP uses set 1 + * Ports 1-5: Green: 2.5GBit, Amber: 10/100/1000MBit + * SFP-port: Blue: 10GBit, Amber: 100MBit-2.5GBit + */ + .led_sets = { + { + LEDS_2G5 | LEDS_LINK | LEDS_ACT, + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + LEDS_DUPLEX, + LEDS_2G5 | LEDS_LINK | LEDS_ACT + }, + { + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT, + LEDS_10G | LEDS_LINK | LEDS_ACT, + LEDS_2G5 | LEDS_LINK, + LEDS_COL | LEDS_DUPLEX + }, + }, + .led_mux_custom = 1, + .led_mux = { + 0x00,0x01,0x04,0x05,0x08,0x09,0x0c,0x3f,0x0d,0x10,0x11,0x0e,0x14,0x11,0x12,0x15,0x15,0x16,0x18,0x19,0x1a,0x19,0x1d,0x1e,0x1c,0x1d,0x20,0x21 + }, + }; diff --git a/machine/SWTG024AS_V2_0.h b/machine/SWTG024AS_V2_0.h new file mode 100644 index 00000000..bd37eece --- /dev/null +++ b/machine/SWTG024AS_V2_0.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_SWTG024AS_V2_0_H_ +#define _MACHINE_SWTG024AS_V2_0_H_ + +#define MACHINE_MODEL "SWTG024AS-V2.0" + +#endif diff --git a/machine/SWTGW218AS.c b/machine/SWTGW218AS.c new file mode 100644 index 00000000..cbb2a2f0 --- /dev/null +++ b/machine/SWTGW218AS.c @@ -0,0 +1,33 @@ +#include "machine_defs.h" + + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 1, + .mac_flash_offset = 0x1FC000, + .min_port = 0, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, + .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[0].pin_los = GPIO37, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + .reset_pin = GPIO54_ACL_BIT2_EN, + .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, + .led_sets = { { LEDS_2G5 | LEDS_LINK | LEDS_ACT, // Green LED (right) + 0, // unused + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // Amber LED (left) + 0 + }, // unused + { LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT, // SFP LED + 0, // unused + 0, // unused + 0 + }, // unused }, + }, +}; diff --git a/machine/SWTGW218AS.h b/machine/SWTGW218AS.h new file mode 100644 index 00000000..ed9c9e54 --- /dev/null +++ b/machine/SWTGW218AS.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_SWTGW218AS_H_ +#define _MACHINE_SWTGW218AS_H_ + +#define MACHINE_MODEL "SWTGW218AS 8+1 Managed Switch" + +#endif diff --git a/machine/TRENDNET_TEG_S562.c b/machine/TRENDNET_TEG_S562.c new file mode 100644 index 00000000..3cfc12c5 --- /dev/null +++ b/machine/TRENDNET_TEG_S562.c @@ -0,0 +1,39 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .min_port = 3, + .max_port = 8, + .n_sfp = 2, + .log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5}, + .phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0}, + .is_sfp = {0, 0, 0, 1, 0, 0, 0, 0, 2}, + .sfp_port[0].pin_detect = GPIO38, + .sfp_port[0].pin_los = GPIO50_I2C_SCL2_UART1_TX, + .sfp_port[0].pin_tx_disable = GPIO54_ACL_BIT2_EN, + .sfp_port[0].sds = 0, + .sfp_port[0].i2c = I2CBUS( GPIO47_I2C_SDA0, GPIO46_I2C_SCL0 ), + .sfp_port[1].pin_detect = GPIO36_PWM_OUT, + .sfp_port[1].pin_los = GPIO37, + .sfp_port[1].pin_tx_disable = GPIO51_I2C_SDA2_UART1_RX, + .sfp_port[1].sds = 1, + .sfp_port[1].i2c = I2CBUS( GPIO49_I2C_SDA1, GPIO48_I2C_SCL1 ), + .reset_pin = GPIO_NA, + .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, + .led_sets = { + { + 0, // Unused + LEDS_2G5 | LEDS_LINK | LEDS_ACT, // Green + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // Amber + 0 // Unused + }, + { + 0, // Unused + 0, // Unused + LEDS_10G | LEDS_1G | LEDS_LINK | LEDS_ACT, // Green + 0, // Unused + }, + }, + +}; diff --git a/machine/TRENDNET_TEG_S562.h b/machine/TRENDNET_TEG_S562.h new file mode 100644 index 00000000..c08ae7a9 --- /dev/null +++ b/machine/TRENDNET_TEG_S562.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_TRENDNET_TEG_S562_H_ +#define _MACHINE_TRENDNET_TEG_S562_H_ + +#define MACHINE_MODEL "Trendnet TEG-S562" + +#endif diff --git a/machine/ZX310S_4T2XH.c b/machine/ZX310S_4T2XH.c new file mode 100644 index 00000000..f5313e02 --- /dev/null +++ b/machine/ZX310S_4T2XH.c @@ -0,0 +1,43 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .min_port = 3, + .max_port = 8, + .n_sfp = 1, + .n_10g = 1, + .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, + .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .sfp_port[0].pin_detect = GPIO38, + .sfp_port[0].pin_los = GPIO_NA, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + .reset_pin = GPIO48_I2C_SCL1, + .high_leds = { .mux = LED_28_SYS, .enable = LED_27 | LED_28_SYS | LED_29 }, + .led_mux_custom = 1, + .led_mux = { 0x00, 0x01, 0x04, 0x05, 0x08, // 65e0 + 0x09, 0x0c, 0x3f, 0x0d, 0x10, // 65e4 + 0x11, 0x0e, 0x14, 0x11, 0x12, // 65e8 + 0x15, 0x15, 0x16, 0x18, 0x19, // 65ec + 0x1a, 0x19, 0x1d, 0x1e, 0x1c, // 65f0 + 0x1d, 0x20, 0x21 }, + .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, + /* Ports 1-4: Orange: 2.5GBit, Green: 10/100/1000MBit + * Port 5: Blue: 10GBit, Green: 10Mbit-5GBit + * SFP-port: Blue: 10GBit, Green 100MBit-5GBit + */ + .led_sets = { { LEDS_2G5 | LEDS_LINK | LEDS_ACT, + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + LEDS_DUPLEX, + LEDS_2G5 | LEDS_LINK | LEDS_ACT }, + { + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT | LEDS_5G, + LEDS_LINK | LEDS_ACT | LEDS_10G, + LEDS_2G5 | LEDS_LINK, + LEDS_COL | LEDS_DUPLEX + } + }, +}; diff --git a/machine/ZX310S_4T2XH.h b/machine/ZX310S_4T2XH.h new file mode 100644 index 00000000..f0873be3 --- /dev/null +++ b/machine/ZX310S_4T2XH.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_ZX310S_4T2XH_H_ +#define _MACHINE_ZX310S_4T2XH_H_ + +#define MACHINE_MODEL "ZX310S-4T2XH" + +#endif diff --git a/machine/ZX310S_4T2XT.c b/machine/ZX310S_4T2XT.c new file mode 100644 index 00000000..c7ec95b9 --- /dev/null +++ b/machine/ZX310S_4T2XT.c @@ -0,0 +1,37 @@ +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 0, + .min_port = 3, + .max_port = 8, + .n_sfp = 0, + .n_10g = 2, + .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6}, + .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 0}, + .reset_pin = GPIO48_I2C_SCL1, + .high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 }, + .led_mux_custom = 1, + .led_mux = { 0x00, 0x01, 0x04, 0x05, 0x08, // 65e0 + 0x09, 0x0c, 0x3f, 0x0d, 0x10, // 65e4 + 0x11, 0x0e, 0x14, 0x11, 0x12, // 65e8 + 0x15, 0x15, 0x16, 0x18, 0x19, // 65ec + 0x1a, 0x19, 0x1d, 0x1e, 0x1c, // 65f0 + 0x1d, 0x20, 0x21 }, + .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1}, + /* Ports 1-4: Green: 2.5GBit, Orange: 10/100/1000MBit + * Ports 5-6: Green: 10GBit, Orange: <= 5GBit + */ + .led_sets = { { LEDS_2G5 | LEDS_LINK | LEDS_ACT, + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + LEDS_DUPLEX, + LEDS_2G5 | LEDS_LINK | LEDS_ACT }, + { + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT, + LEDS_LINK | LEDS_ACT | LEDS_10G | LEDS_5G, + LEDS_2G5 | LEDS_LINK, + LEDS_COL | LEDS_DUPLEX + } + }, +}; diff --git a/machine/ZX310S_4T2XT.h b/machine/ZX310S_4T2XT.h new file mode 100644 index 00000000..15b76217 --- /dev/null +++ b/machine/ZX310S_4T2XT.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_ZX310S_4T2XT_H_ +#define _MACHINE_ZX310S_4T2XT_H_ + +#define MACHINE_MODEL "ZX310S_4T2XT" + +#endif diff --git a/machine/default_machine.h b/machine/default_machine.h new file mode 100644 index 00000000..0919674f --- /dev/null +++ b/machine/default_machine.h @@ -0,0 +1,17 @@ +#ifndef _MACHINE_DEFAULT_H_ +#define _MACHINE_DEFAULT_H_ + +/* + * Fallback selected by machine.h when MACHINE_NAME is not defined on the + * command line. Firmware builds always pass -DMACHINE_NAME=, so this + * is used by translation units that only need the machine.h declarations -- + * notably the host-side tests under test/, which define their own `machine` + * object. + * + * MACHINE_MODEL is deliberately not defined here: a build that reaches this + * header must not silently pick up some default model string. Any code that + * needs MACHINE_MODEL therefore fails to compile unless a real machine is + * selected. + */ + +#endif diff --git a/machine/machine_defs.h b/machine/machine_defs.h new file mode 100644 index 00000000..94b989c4 --- /dev/null +++ b/machine/machine_defs.h @@ -0,0 +1,44 @@ +#ifndef _MACHINE_DEFS_H_ +#define _MACHINE_DEFS_H_ + +/* + * Shared prelude for the per-machine board definitions in this directory. + * + * Each .c file in this directory defines exactly one board's + * `__code const struct machine machine`, and is named after the MACHINE_* symbol + * it belongs to. The Makefile picks the file matching $(MACHINE), so no #if/#elif + * selection happens here. + */ + +#include "machine.h" +#include "rtl837x_pins.h" +#include "rtl837x_leds.h" +#include "rtl837x_sfr.h" +#include "rtl837x_regs.h" +#include "rtl837x_common.h" + +#define I2CBUS_INVALID (7) +// This is a MACRO to check if I2C_BUS_FROM_SDA_PIN or I2C_BUS_FROM_SCL_PIN don't return I2CBUS_INVALID. +// When I2CBUS_INVALID is returned, the compiler stops with +// "error 176: sizeof applied to an incomplete type" pointing at the offending .i2c line. +#define I2CBUS_VALID(bus) (0 * sizeof(char[(bus) != I2CBUS_INVALID ? 1 : 0])) +#define I2CBUS(sda, scl) \ + ((uint8_t)((I2C_BUS_FROM_SCL_PIN(scl) << RTL837X_REG_I2C_SCL_SHIFT) | \ + (I2C_BUS_FROM_SDA_PIN(sda) << RTL837X_REG_I2C_SDA_SHIFT) | \ + I2CBUS_VALID(I2C_BUS_FROM_SCL_PIN(scl)) | \ + I2CBUS_VALID(I2C_BUS_FROM_SDA_PIN(sda)))) + +#define I2C_BUS_FROM_SDA_PIN(sda_pin) \ + ((sda_pin) == GPIO47_I2C_SDA0 ? 0 : \ + (sda_pin) == GPIO49_I2C_SDA1 ? 1 : \ + (sda_pin) == GPIO51_I2C_SDA2_UART1_RX ? 2 : \ + (sda_pin) == GPIO41_I2C_SDA3_MDIO1 ? 3 : \ + (sda_pin) == GPIO39_I2C_SDA4 ? 4 : I2CBUS_INVALID) + +#define I2C_BUS_FROM_SCL_PIN(scl_pin) \ + ((scl_pin) == GPIO46_I2C_SCL0 ? 0 : \ + (scl_pin) == GPIO48_I2C_SCL1 ? 1 : \ + (scl_pin) == GPIO50_I2C_SCL2_UART1_TX ? 2 : \ + (scl_pin) == GPIO40_I2C_SCL3_MDC1 ? 3 : I2CBUS_INVALID) + +#endif From f88f655f5a13b8cc7808f7bcd0794442e192b257 Mon Sep 17 00:00:00 2001 From: DrDoof <8052722+DrDoof@users.noreply.github.com> Date: Thu, 1 Oct 2026 21:24:05 +0200 Subject: [PATCH 02/84] igmp: leave groups in 224.0.0.0/24 to flooding The switch keeps flooding 224.0.0.0/24 with IGMP on, but a report for a group in that block still made an entry for it, and the entry limited the group to the ports that reported it. A host announcing mDNS on 224.0.0.251 thereby cut mDNS off from every other port. Such reports are now ignored, as RFC 4541 asks. --- doc/igmp.md | 3 +++ rtl837x_igmp.c | 2 ++ rtl837x_igmp.h | 1 + 3 files changed, 6 insertions(+) diff --git a/doc/igmp.md b/doc/igmp.md index 1584287f..f54f8bfa 100644 --- a/doc/igmp.md +++ b/doc/igmp.md @@ -85,6 +85,9 @@ for frames to 01:00:5e:xx:xx:xx and returns 0 when the frame is not a trapped IG so that it is passed on to the IP stack. IGMPv1/v2 reports (0x12, 0x16) join the group given in the IGMP header, IGMPv2 leaves (0x17) leave it, IGMPv3 reports use the first group record (type 4 joins, type 3 leaves). Trapped IGMP frames arrive without a VLAN tag. +Reports for groups in 224.0.0.0/24 are ignored: the switch floods that block +even with IGMP on, and an entry for a group such as 224.0.0.251 (mDNS) would +otherwise limit it to the ports that reported it, as RFC 4541 warns against. `igmp_show()` prints out the IGMP configuration on the CLI. diff --git a/rtl837x_igmp.c b/rtl837x_igmp.c index 3ef2b2ec..51ed3abf 100644 --- a/rtl837x_igmp.c +++ b/rtl837x_igmp.c @@ -250,6 +250,8 @@ uint8_t igmp_packet_handler(void) __banked #ifdef DEBUG print_string("IGMP join "); print_byte(igmp_join); write_char('\n'); #endif + if (uip_buf[igmp_grp] == IGMP_LOCAL_NET_0 && !uip_buf[igmp_grp + 1] && !uip_buf[igmp_grp + 2]) + return 1; #ifdef IPMC_USES_L3MC memset((__xdata uint8_t *)&entry, 0, sizeof(struct ipmc_table_entry)); diff --git a/rtl837x_igmp.h b/rtl837x_igmp.h index d6d28f81..1b56e367 100644 --- a/rtl837x_igmp.h +++ b/rtl837x_igmp.h @@ -10,6 +10,7 @@ #define IGMP_V3_REPORT 0x22 #define IGMP_V3_TO_INCLUDE 3 #define IGMP_V3_TO_EXCLUDE 4 +#define IGMP_LOCAL_NET_0 224 void igmp_setup(void) __banked; void igmp_enable(void) __banked; From 706dea63a2932fcd4ad79d47cabfcd4a12b727e6 Mon Sep 17 00:00:00 2001 From: DrDoof <8052722+DrDoof@users.noreply.github.com> Date: Fri, 9 Oct 2026 17:03:41 +0200 Subject: [PATCH 03/84] port: show how many MAC addresses each port has learned The switch keeps a count of the dynamic L2 entries learned on every port in L2_LRN_PORT_CONSTRT_CNT (0x53ac + 4 * port, 13 bits). `stat` prints it in a new MACs column and /status.json carries it as "macs". On a SWTGW218AS the count matched the L2 table port by port, static entries are not included, and taking a port down drops it to 0. --- httpd/page_impl.c | 3 +++ rtl837x_port.c | 5 ++++- rtl837x_regs.h | 2 ++ test/test_page_json.c | 18 ++++++++++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/httpd/page_impl.c b/httpd/page_impl.c index 26a7eb94..1d8bf1cb 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -1032,6 +1032,9 @@ void send_status(void) slen += strtox(outbuf + slen, "\",\"rxB\":\"0x"); STAT_GET(STAT_COUNTER_ERR_PKTS, i); reg_to_html(RTL837X_STAT_V_HIGH); // 32bit RX packet errors + + slen += strtox(outbuf + slen, "\",\"macs\":\"0x"); + reg_to_html(RTL837X_L2_LRN_PORT_CONSTRT_CNT + (i << 2)); slen += strtox(outbuf + slen, "\"}"); if (i < machine.max_port) char_to_html(','); diff --git a/rtl837x_port.c b/rtl837x_port.c index e3600eba..f82fc713 100644 --- a/rtl837x_port.c +++ b/rtl837x_port.c @@ -510,7 +510,7 @@ void port_media_show(uint8_t port) __banked void port_stats_print(void) __banked { - print_string("\nPort\tState\tLink\tTxGood\t\tTxBad\t\tRxGood\t\tRxBad\n"); + print_string("\nPort\tState\tLink\tTxGood\t\tTxBad\t\tRxGood\t\tRxBad\t\tMACs\n"); for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { print_phys_port(i); write_char('\t'); @@ -584,6 +584,9 @@ void port_stats_print(void) __banked STAT_GET(STAT_COUNTER_ERR_PKTS, i); print_reg(RTL837X_STAT_V_HIGH); write_char('\t'); + + reg_read(RTL837X_L2_LRN_PORT_CONSTRT_CNT + (i << 2)); + itoa_short(SFR_DATA_U16 & L2_LRN_PORT_CNT_MASK); print_string("\n"); } } diff --git a/rtl837x_regs.h b/rtl837x_regs.h index 49520a9f..34319c85 100644 --- a/rtl837x_regs.h +++ b/rtl837x_regs.h @@ -214,6 +214,8 @@ #define RTL837x_L2_TBL_FLUSH_CNF 0x53dc #define RTL837X_L2_LRN_PORT_CONSTRAINT 0x5384 #define RTL837X_L2_LRN_PORT_CONSTRT_ACT 0x4f80 +#define RTL837X_L2_LRN_PORT_CONSTRT_CNT 0x53ac +#define L2_LRN_PORT_CNT_MASK 0x1fff #define RTL8373_REG_MAC_L2_PORT_MAX_LEN 0x1250 /* diff --git a/test/test_page_json.c b/test/test_page_json.c index e650a538..a7626565 100644 --- a/test/test_page_json.c +++ b/test/test_page_json.c @@ -185,6 +185,23 @@ static void t_l2_page(void) CHECK(balanced(), "every page is valid JSON"); } +static void t_status_macs(void) +{ + const char *p1, *end, *mac; + + printf("[test] /status.json reports the MAC addresses learned on each port\n"); + hw_reset(); out_reset(); + hw_reg_set(RTL837X_L2_LRN_PORT_CONSTRT_CNT + (1 << 2), 0x21); + send_status(); + p1 = strstr(body(), "\"logPort\":1,"); + end = p1 ? strchr(p1, '}') : NULL; + mac = p1 ? strstr(p1, "\"macs\":\"0x21\"") : NULL; + CHECK(mac && mac < end, "port 1 shows its learned count"); + CHECK(has("\"macs\":\"0x0\""), "a port without learned addresses shows 0"); + CHECK(slen <= TCP_OUTBUF_SIZE, "fits the buffer"); + CHECK(balanced(), "valid JSON"); +} + static void t_status_counters(void) { printf("[test] /status.json prints 64-bit packet counters in full\n"); @@ -211,6 +228,7 @@ int main(void) t_l2(); t_l2_page(); t_status_counters(); + t_status_macs(); printf("\n%d checks, %d failed\n", tests_run, tests_failed); return tests_failed ? 1 : 0; } From 24c0a957343f0e323428a2b0d6ca3235d62a9bd2 Mon Sep 17 00:00:00 2001 From: DrDoof <8052722+DrDoof@users.noreply.github.com> Date: Sat, 10 Oct 2026 02:43:04 +0200 Subject: [PATCH 04/84] igmp: stop looking up IPv4 multicast in the L2 table on igmp off igmp_setup(), which `igmp off` also calls, enabled the lookup of IPv4 multicast in the L2 table. Groups learned while IGMP was on therefore kept going only to the ports that had reported them, and with reports no longer trapped nothing updated those entries until a reboot. The lookup is now enabled in igmp_enable() and disabled in igmp_setup(), so with IGMP off all IP multicast is flooded again. --- doc/igmp.md | 3 ++- rtl837x_igmp.c | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/igmp.md b/doc/igmp.md index 1584287f..adce94f2 100644 --- a/doc/igmp.md +++ b/doc/igmp.md @@ -73,7 +73,8 @@ uint8_t igmp_packet_handler(void) __banked; void igmp_show(void) __banked; ```c `igmp_setup()` is called at boot-time and configures flooding of all IP-MC packets by -default, as otherwise no IP-MC would be possible in the network. +default, as otherwise no IP-MC would be possible in the network. `igmp off` calls it as +well; groups learned while IGMP was on stay in the table, but are no longer looked up. `igmp_enable()`starts IGMP which cause IGMP packets to be handled by the CPU and forwarding of IP-MC packets to be limited to only subscribed ports. diff --git a/rtl837x_igmp.c b/rtl837x_igmp.c index 3ef2b2ec..69cd8568 100644 --- a/rtl837x_igmp.c +++ b/rtl837x_igmp.c @@ -107,8 +107,7 @@ void igmp_setup(void) __banked REG_SET(RTL837X_IPV4_UNKN_MC_FLD_PMSK, mask); REG_SET(RTL837X_IPV6_UNKN_MC_FLD_PMSK, mask); - // Enable lookup of IPv4 MC addresses in table - reg_bit_set(RTL837X_L2_CTRL, L2_CTRL_LUT_IPMC_HASH); + reg_bit_clear(RTL837X_L2_CTRL, L2_CTRL_LUT_IPMC_HASH); /* Configure per-port IGMP operations when protocol messages are received * bits 0-9 enable MC protocol snooping @@ -145,6 +144,8 @@ void igmp_enable(void) __banked { print_string("igmp_enable called\n"); igmpEnabled = 1; + // Enable lookup of IPv4 MC addresses in table + reg_bit_set(RTL837X_L2_CTRL, L2_CTRL_LUT_IPMC_HASH); // Configure trapping of unhandled IGMP protocol packets to CPU REG_SET(RTL837X_IGMP_TRAP_CFG, IGMP_CPU_PORT | IGMP_TRAP_PRIORITY); From b3ac10cae7b4a95ca93fab375ee22d6c9802a8d0 Mon Sep 17 00:00:00 2001 From: hlyi Date: Sat, 10 Oct 2026 06:31:16 -0700 Subject: [PATCH 05/84] Update HG0402XG_V1_1.c Change 0xFF to GPIO_NA --- machine/HG0402XG_V1_1.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/machine/HG0402XG_V1_1.c b/machine/HG0402XG_V1_1.c index 6a420daa..5d7be1e9 100644 --- a/machine/HG0402XG_V1_1.c +++ b/machine/HG0402XG_V1_1.c @@ -11,12 +11,12 @@ __code const struct machine machine = { .is_sfp = {0, 0, 0, 2, 0, 0, 0, 0, 1}, .sfp_port[0].pin_detect = 50, .sfp_port[0].pin_los = 10, - .sfp_port[0].pin_tx_disable = 0xFF, + .sfp_port[0].pin_tx_disable = GPIO_NA, .sfp_port[0].sds = 1, .sfp_port[0].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), .sfp_port[1].pin_detect = 30, .sfp_port[1].pin_los = 51, - .sfp_port[1].pin_tx_disable = 0xFF, + .sfp_port[1].pin_tx_disable = GPIO_NA, .sfp_port[1].sds = 0, .sfp_port[1].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), .reset_pin = GPIO_NA, From 14b2ce1c49851606c0afdd7ddec208e2f6dcefa3 Mon Sep 17 00:00:00 2001 From: DrDoof <8052722+DrDoof@users.noreply.github.com> Date: Thu, 24 Sep 2026 04:27:55 +0200 Subject: [PATCH 06/84] trap: deliver reserved multicast to the CPU by trap, STP first A reserved-multicast trap goes to the port named in EXT_CPU_CTRL, which names no port out of reset, so trapped frames were dropped at ingress. trap_init() points it at the CPU port once at startup, and trap_rma_set() changes only the action field of an RMA register. A trapped frame arrives without the VLAN tag a forwarded one gets, so TRAP_RX_BODY() gives the offset of the payload for both forms. STP now traps its address while it runs instead of steering it with a static L2 entry per VLAN, so turning it on no longer walks the VLAN table and a pvid change no longer needs stp off and on. With STP off the flooding entries are restored as before. A trapped BPDU passes the tagged-only ingress filter that drops a forwarded one, so the stp_setup() warning about such ports goes. --- Makefile | 3 ++- doc/l2.md | 15 ++++++++------- doc/stp.md | 44 +++++++++++++++++++++++++------------------- rtl837x_regs.h | 6 ++++++ rtl837x_stp.c | 38 +++++++++++++++++--------------------- rtl837x_trap.c | 21 +++++++++++++++++++++ rtl837x_trap.h | 16 ++++++++++++++++ rtlplayground.c | 2 ++ 8 files changed, 97 insertions(+), 48 deletions(-) create mode 100644 rtl837x_trap.c create mode 100644 rtl837x_trap.h diff --git a/Makefile b/Makefile index 599d2b95..8c8ca72c 100644 --- a/Makefile +++ b/Makefile @@ -89,7 +89,8 @@ SRCS += \ rtl837x_port.c \ rtl837x_stp.c \ rtl837x_lldp.c \ - rtl837x_storm.c + rtl837x_storm.c \ + rtl837x_trap.c SRCS += \ httpd/httpd.c \ httpd/page_impl.c diff --git a/doc/l2.md b/doc/l2.md index 0a785a8f..ccf891cf 100644 --- a/doc/l2.md +++ b/doc/l2.md @@ -73,13 +73,14 @@ without being flooded to the other ports. No bridge relays these frames: their addresses are in the set that 802.1D-2004 clause 7.12.6 forbids a bridge to forward, and what travels the network is the information, with every bridge regenerating BPDUs of its own on its designated ports. The reserved-multicast *trap* action -cannot do that on this hardware, because its destination is an external CPU -attached to a physical port, which these boards do not populate. The protocol -modules therefore leave the reserved-multicast action at *forward* and constrain -the egress with a static L2 multicast entry instead: the lookup hits the entry's -own port mask rather than the VLAN flood mask. Verified on a SWTGW218AS both -ways, with the CPU bit cleared, where delivery stops, and with the CPU bit alone, -where nothing egresses. +does that: it delivers to the port named in `EXT_CPU_CTRL` (0x6724), which +`trap_init()` sets to 9, the CPU port, at startup; with the reset value 0xf it +names no port and the trapped frame is dropped. STP traps its address while it +runs (see stp.md). Where the action is left at *forward*, the egress can be +constrained with a static L2 multicast entry instead: the lookup hits the +entry's own port mask rather than the VLAN flood mask. Verified on a SWTGW218AS +both ways, with the CPU bit cleared, where delivery stops, and with the CPU bit +alone, where nothing egresses. `port_l2mc_set()` writes one such entry. The SMI layout is the L2 multicast variant of the table entry: diff --git a/doc/stp.md b/doc/stp.md index 8b6780c5..96368535 100644 --- a/doc/stp.md +++ b/doc/stp.md @@ -35,28 +35,34 @@ BPDUs are addressed to `01:80:C2:00:00:00`, a reserved link-local group. The ASIC's Reserved-Multicast action for that address decides what happens to the frame. -Forwarding to the CPU port works normally: the 8051 sits behind an ordinary -port of the internal switch and is an ordinary member of a forwarding mask. -The *trap* action does not deliver to it. Its destination is an external CPU -attached to a physical port (`cpuTag_externalCpuPort_set`, `EXT_CPU_CTRL` in -the vendor SDK), which these boards do not populate. The ACL trap and -redirect actions do not deliver to the 8051 either. +While STP runs, the action is *trap*. A trap delivers to the port named in +`EXT_CPU_CTRL` (0x6724), whose reset value 0xf names no port, and with that +value the trapped frame is dropped at ingress. `trap_init()` sets it to 9, the +port the 8051 sits behind, once at startup. The trap needs no per-VLAN L2 +entries, so it does not depend on which VLANs exist or on the PVIDs, and turning +STP on no longer walks the whole VLAN table. BPDUs reach the CPU on a port that +STP holds in blocking with either action: measured on a SWTGW218AS by sending +BPDUs into a blocked port with forward and with trap, both ports stopped being +edge and waited out the forward delay. The chip exempts this address from the +ingress state check; the slow-protocols address used by LACP is not exempt. -Delivery therefore uses the *forward* action, constrained to the CPU port -by a static L2 multicast entry (`port_l2mc_set()`), one per VLAN in use: +A trapped frame reaches the CPU in the format it came in with: unlike a +forwarded one it has no VLAN tag inserted after the RTL tag, and the RTL tag +carries the trap reason. `stp_in()` reads the BPDU from the offset +`TRAP_RX_BODY()` returns, which works for both. -* while STP runs, the entry's member mask is the CPU port only — BPDUs reach - the CPU and are not flooded to other ports, as a participating bridge - requires; -* with STP off, the same entries are retargeted to all ports, restoring the - transparency an unmanaged switch is expected to have, so a surrounding - spanning tree can span *through* this device. +With STP off, the action goes back to *forward* and a static L2 multicast +entry per VLAN in use (`port_l2mc_set()`) targets all ports, restoring the +transparency an unmanaged switch is expected to have, so a surrounding +spanning tree can span *through* this device. -A BPDU delivered this way is an ordinary frame to the port's ingress logic -and passes through its acceptable-frame-type filter. BPDUs are untagged, so a -port set to admit tagged frames only (`ingress t`) never delivers one -to the CPU. `stp_setup()` prints a warning for every STP-enabled port in that -state. +The port's acceptable-frame-type filter treats the two actions differently. +BPDUs are untagged, so a port set to admit tagged frames only +(`ingress t`) drops a forwarded BPDU before it reaches the CPU, while a +trapped one passes. Both were measured on a SWTGW218AS with `ingress 3t`: +LACPDUs, delivered by forward, stopped arriving on port 3, and BPDUs sent into +the same port, delivered by trap, kept arriving. A port that admits tagged +frames only therefore takes part in STP. ## Link aggregation diff --git a/rtl837x_regs.h b/rtl837x_regs.h index 49520a9f..dc4b2dc1 100644 --- a/rtl837x_regs.h +++ b/rtl837x_regs.h @@ -285,6 +285,12 @@ */ #define RTL8373_RLDP_TIMER 0x1074 #define RTL837X_RMA0_CONF 0x4ecc +#define RTL837X_RMA2_CONF 0x4ed4 +#define RTL837X_RMA_ACT_MASK 0x30 +#define RTL837X_RMA_ACT_FORWARD 0x00 +#define RTL837X_RMA_ACT_TRAP 0x10 +#define RTL837X_RMA_ACT_DROP 0x20 +#define RTL837X_EXT_CPU_CTRL 0x6724 #define RTL837X_IMR_INT_PORT_LINK_STS_CHG 0x5f34 #define RTL837X_ISR_SW_INT_MODE 0x5f84 #define RTL837X_ISR_INT_PORT_LINK_CHG 0x5f88 diff --git a/rtl837x_stp.c b/rtl837x_stp.c index 26e14806..ff451f2d 100644 --- a/rtl837x_stp.c +++ b/rtl837x_stp.c @@ -12,6 +12,7 @@ #include "rtl837x_regs.h" #include "rtl837x_stp.h" #include "rtl837x_port.h" +#include "rtl837x_trap.h" #include "uip.h" #include "machine.h" @@ -85,6 +86,7 @@ __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_tc_while; /* ticks left to set the TC flag in our BPDUs */ @@ -121,11 +123,13 @@ struct stp_pkt { uint8_t version1_length; /* RST BPDU only: length of the (empty) v1 part */ }; -struct stp_pkt_in { +struct stp_pkt_in_hdr { uint8_t stp_addr[6]; uint8_t src_addr[6]; struct rtl_tag rtl_tag; - struct vlan_tag vlan_tag; +}; + +struct stp_pkt_in { uint16_t msg_len; uint8_t dsap; uint8_t ssap; @@ -147,7 +151,8 @@ struct stp_pkt_in { }; #define STP_O ((__xdata struct stp_pkt *)&uip_buf[RTL_FRAME_DESC_SIZE]) -#define STP_I ((__xdata struct stp_pkt_in *)&uip_buf[0]) +#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 @@ -158,7 +163,8 @@ struct stp_pkt_in { #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_HEADER 33 // addresses through bpdu_type +#define BPDU_LEN_MIN_BODY 9 +#define BPDU_LEN_FULL_BODY 40 #define BPDU_FLAG_TC 0x01 #define BPDU_FLAG_LEARNING 0x10 @@ -547,7 +553,8 @@ void stp_in(void) __banked { uint8_t port; - if (uip_len < BPDU_LEN_MIN_HEADER) { + stp_rxbody = TRAP_RX_BODY(); + if (uip_len < stp_rxbody + BPDU_LEN_MIN_BODY) { uip_len = 0; return; } @@ -557,7 +564,7 @@ void stp_in(void) __banked uip_len = 0; /* Ingress port: low nibble of the CPU tag's pmask on RX */ - stp_scratch = ((uint8_t)HTONS(STP_I->rtl_tag.pmask)) & 0x0f; + stp_scratch = ((uint8_t)HTONS(STP_H->rtl_tag.pmask)) & 0x0f; if (stp_scratch < machine.min_port || stp_scratch > machine.max_port) return; port = stp_ent_of[stp_scratch]; @@ -607,7 +614,7 @@ void stp_in(void) __banked } /* Everything below reads the full Config/RST body. */ - if (stp_rxlen < 64) + if (stp_rxlen < stp_rxbody + BPDU_LEN_FULL_BODY) return; /* Our own BPDU coming back: two of our ports sit on one segment. Only @@ -865,8 +872,7 @@ void stp_defaults(void) __banked /* - * Steer BPDUs while STP runs, and restore flooding when it stops. - * Changing a port's PVID while STP runs needs "stp off" then "stp on". + * Restore BPDU flooding when STP stops. */ static void stp_fdb_update(__xdata uint16_t pmask) { @@ -928,16 +934,6 @@ void stp_setup(void) __banked print_reg(RTL837X_MSTP_STATES); write_char('\n'); - for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) { - if (!(stp_pflags[stp_i] & STP_PF_ENABLED)) - continue; - if (port_ingress_filter_get(stp_i) != VLAN_TAGGED) - continue; - print_string("STP: port "); - write_char('0' + machine.log_to_phys_port[stp_i]); - print_string(" admits tagged frames only - BPDUs are untagged and will not arrive\n"); - } - /* Seed the carrier bitmap, so turning STP on does not report every * port that was already down as a fresh topology change. */ reg_read_m(RTL837X_REG_LINKS_STS); @@ -949,8 +945,7 @@ void stp_setup(void) __banked stp_claim_root(); - /* Take BPDUs to the CPU only - we are a participating bridge now. */ - stp_fdb_update(PMASK_CPU); + trap_rma_set(RTL837X_RMA0_CONF, RTL837X_RMA_ACT_TRAP); } @@ -972,6 +967,7 @@ void stp_off(void) __banked /* 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); } diff --git a/rtl837x_trap.c b/rtl837x_trap.c new file mode 100644 index 00000000..1f2373a2 --- /dev/null +++ b/rtl837x_trap.c @@ -0,0 +1,21 @@ +#include "rtl837x_common.h" +#include "rtl837x_sfr.h" +#include "rtl837x_regs.h" +#include "rtl837x_trap.h" + +#pragma codeseg BANK2 +#pragma constseg BANK2 + +extern __xdata uint8_t sfr_data[4]; + +void trap_init(void) __banked +{ + REG_SET(RTL837X_EXT_CPU_CTRL, CPU_PORT); +} + +void trap_rma_set(uint16_t reg, uint8_t act) __banked +{ + reg_read_m(reg); + sfr_data[3] = (sfr_data[3] & ~RTL837X_RMA_ACT_MASK) | act; + reg_write_m(reg); +} diff --git a/rtl837x_trap.h b/rtl837x_trap.h new file mode 100644 index 00000000..88e99da5 --- /dev/null +++ b/rtl837x_trap.h @@ -0,0 +1,16 @@ +#ifndef _RTL837X_TRAP_H_ +#define _RTL837X_TRAP_H_ + +#include +#include "rtl837x_common.h" + +#define TRAP_RX_HDR_SIZE (12 + RTL_TAG_SIZE) + +#define TRAP_RX_BODY() \ + ((uip_buf[TRAP_RX_HDR_SIZE] == 0x81 && uip_buf[TRAP_RX_HDR_SIZE + 1] == 0x00) \ + ? TRAP_RX_HDR_SIZE + VLAN_TAG_SIZE : TRAP_RX_HDR_SIZE) + +void trap_init(void) __banked; +void trap_rma_set(uint16_t reg, uint8_t act) __banked; + +#endif diff --git a/rtlplayground.c b/rtlplayground.c index 2c2184fd..3f7860dc 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -11,6 +11,7 @@ #include "rtl837x_pins.h" #include "rtl837x_phy.h" #include "rtl837x_port.h" +#include "rtl837x_trap.h" #include "rtl837x_stp.h" #include "rtl837x_igmp.h" #include "rtl837x_leds.h" @@ -1793,6 +1794,7 @@ void main(void) stp_defaults(); /* 802.1D/w default config before any "stp ..." replay */ nic_setup(); REG_SET(RTL837X_NIC_INT_MSK, NIC_INT_RXIE); + trap_init(); vlan_setup(); port_l2_setup(); igmp_setup(); From 0e85137e7a8a4e842444b7b0aa5ad773f7c0193d Mon Sep 17 00:00:00 2001 From: avicarmeli <32562196+avicarmeli@users.noreply.github.com> Date: Mon, 12 Oct 2026 10:43:36 +0300 Subject: [PATCH 07/84] fix: accept CR delimiters in flash startup configuration --- cmd_parser.c | 2 +- test/test_startup_crlf.py | 52 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 test/test_startup_crlf.py diff --git a/cmd_parser.c b/cmd_parser.c index e7daa9e8..0338ee32 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -2116,7 +2116,7 @@ void execute_config(void) __banked skipping = 1; } c = flash_buf[cfg_idx++]; - if (c == 0 || c == '\n') { + if (c == 0 || c == '\n' || c == '\r') { if (skipping) { skipping = 0; } else { diff --git a/test/test_startup_crlf.py b/test/test_startup_crlf.py new file mode 100644 index 00000000..f1b99f63 --- /dev/null +++ b/test/test_startup_crlf.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Compile the current firmware execute_config() with mocked flash/tokenizer edges.""" +import pathlib, subprocess +root=pathlib.Path(__file__).resolve().parent.parent +src=(root/'cmd_parser.c').read_text() +start=src.index('void execute_config(void) __banked') +end=src.index('// Execute multiple commands',start) +body=src[start:end] +out=root/'test/build';out.mkdir(exist_ok=True) +harness=r""" +#include +#include +#include +#include +#define __xdata +#define __banked +#define CONFIG_START 0 +#define CONFIG_LEN 1024 +#define FLASH_READ_BURST_SIZE 256 +#define CMD_BUF_SIZE 64 +#define ERR_OK 0 +#define NUL 0 +#define PASSWORD "test-default" +uint8_t flash_buf[256],cmd_buffer[64],save_cmd,err_status; +char passwd[32],image[1024],seen[16][64];int count; +struct {uint32_t addr;uint16_t len;} flash_region; +void strtox(char*d,const char*s){strcpy(d,s);} +void flash_read_bulk(uint8_t*d){memcpy(d,image+flash_region.addr,256);} +void print_string(const char*s){(void)s;} +void print_string_x(uint8_t*s){(void)s;} +void write_char(char c){(void)c;} +void clear_command_history(void){} +void cmd_tokenize(void){err_status=cmd_buffer[0]=='#'?1:0;} +void cmd_parser(void){assert(count<16);strcpy(seen[count++],(char*)cmd_buffer);} +""" +tests=r""" +void run(const char*s){memset(image,0,sizeof image);strncpy(image,s,sizeof image-1);count=0;execute_config();assert(save_cmd==1);} +int main(void){ +run("one\ntwo\n");assert(count==2&&!strcmp(seen[1],"two")); +run("one\rtwo\r");assert(count==2&&!strcmp(seen[0],"one")); +run("one\r\ntwo\r\n");assert(count==2&&!strcmp(seen[1],"two")); +run("\r\n# ignored\r\none\r\n\r\ntwo");assert(count==2&&!strcmp(seen[0],"one")); +run("one\0ignored");assert(count==1&&!strcmp(seen[0],"one")); +char longline[300];memset(longline,'x',sizeof longline);strcpy(longline+280,"\r\nok\r\n");run(longline);assert(count==1&&!strcmp(seen[0],"ok")); +/* CR at the end of a flash burst, LF at the start of the next. */ +char boundary[300];memset(boundary,'\n',255);strcpy(boundary+255,"\r\nok\r\n");run(boundary);assert(count==1&&!strcmp(seen[0],"ok")); +puts("startup CR/LF/CRLF, comments, NUL, overlong and burst-boundary cases passed");return 0; +} +""" +(out/'startup_crlf.c').write_text(harness+body+tests) +subprocess.run(['gcc','-std=gnu11','-Wall','-Wextra','-Werror','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-no-pie',str(out/'startup_crlf.c'),'-o',str(out/'test_startup_crlf')],check=True) +subprocess.run([str(out/'test_startup_crlf')],check=True) From efd61be64eae2970af10a542fac9b08e5af79c7b Mon Sep 17 00:00:00 2001 From: Antony Date: Mon, 12 Oct 2026 13:04:49 +0000 Subject: [PATCH 08/84] mirror: parse the monitor port argument --- cmd_parser.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd_parser.c b/cmd_parser.c index e7daa9e8..e16fa108 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -846,9 +846,14 @@ void parse_mirror(void) if (cmd_words_len < 2) goto err; + // The first argument is the mirroring (monitor) port + uint8_t ret = cmd_parse_port(cmd_words_b[1]); + if (ret == 0) + goto err; + mirroring_port = atoi_results_u8; + uint8_t w = 2; uint8_t port; - uint8_t ret; while (w < cmd_words_len) { uint8_t idx = cmd_words_b[w++]; ret = cmd_parse_port(idx); @@ -858,10 +863,6 @@ void parse_mirror(void) idx += ret; port = atoi_results_u8; - // Use the first port argument as mirroring_port - if (w == 2) - mirroring_port = port; - ret = cmd_buffer[idx]; uint16_t pmask = ((uint16_t)1) << port; if (ret != 't') From c3b4cc893b3a9b4cae6d4e85e484834569262c90 Mon Sep 17 00:00:00 2001 From: DrDoof <8052722+DrDoof@users.noreply.github.com> Date: Wed, 23 Sep 2026 22:13:30 +0200 Subject: [PATCH 09/84] lacp: implement IEEE 802.3ad link aggregation Dynamic link aggregation on top of the static LAGG, for RTL8372/8373: receive, selection and mux state machines (coupled mux, one aggregator per group), with "lacp show" to look inside. Each of the four trunk groups runs its own aggregator on the candidate ports assigned with "lag lacp "; the engine starts with the first such group and stops with the last. LACPDUs reach the CPU through the reserved-multicast trap, so delivery needs no L2 entry and nothing written per VLAN or per PVID. The trap acts on the address before the VLAN lookup and before the ingress filters, so a LACPDU also arrives on a port that admits tagged frames only and on a port STP holds in blocking, neither of which a forwarded one reaches. A trapped frame carries no VLAN tag, so lacp_in() takes the offset of the PDU body from TRAP_RX_BODY(). The action is set with trap_rma_set(), which leaves the flags sharing that register alone, and "lacp off" puts the slow-protocols drop default back. doc/lacp.md and doc/link_aggregation.md record what was measured, including why the trap looked like a dead end at first. The web UI gets a Static/LACP mode per group on the LAG page with live status, and the startup-config validator knows the per-group syntax. A host-side sandbox for the state machines runs in the test harness together with the LAG hash check. It drives both forms of an incoming LACPDU, so the body offset of a trapped one is covered as well. The L2 table page iterated with the "next unicast" read method, so static multicast entries such as an IGMP group were invisible. It now iterates with "next address", skips L3 entries (their layout carries IP addresses where a MAC is expected), and reports the lowest member of a multicast entry's port mask, so such an entry shows up as a static entry on that port. A port taken out of LACP forgets its partner, so "lacp show" after "lacp off" no longer reports one. Applying the same candidate set to a group again, as the LAG page does on every Apply, leaves the trunk alone instead of tearing it down for a second or two. lacp_in() checks the frame length and ethertype before reading a LACPDU, so a runt or a foreign frame on the slow-protocols address cannot leave stale buffer bytes as a partner. The sandbox covers these three in T20-T22. On the LAG page, LACP mode with no ports checked sends "lag N lacp off", and the startup-config validator wants at least one port after "lacp". --- Makefile | 1 + README.md | 1 + cmd_parser.c | 29 +- doc/CpuPort.md | 5 + doc/lacp.md | 231 ++++++++++++ doc/link_aggregation.md | 49 +++ html/app.js | 52 ++- html/index.html | 5 + httpd/httpd.c | 2 + httpd/page_impl.c | 81 ++++- httpd/page_impl.h | 1 + rtl837x_igmp.c | 4 +- rtl837x_lacp.c | 607 ++++++++++++++++++++++++++++++++ rtl837x_lacp.h | 101 ++++++ rtl837x_regs.h | 4 + rtlplayground.c | 25 +- test/Makefile | 9 + test/README.md | 2 + test/env_tables.c | 16 + test/lacp/.gitignore | 1 + test/lacp/Makefile | 31 ++ test/lacp/lacp_sim.c | 464 ++++++++++++++++++++++++ test/lacp/shim/machine.h | 12 + test/lacp/shim/rtl837x_common.h | 63 ++++ test/lacp/shim/rtl837x_port.h | 7 + test/lacp/shim/rtl837x_regs.h | 19 + test/lacp/shim/uip.h | 9 + test/port_hash/.gitignore | 2 + test/port_hash/Makefile | 6 + test/port_hash/test_lag_hash.c | 68 ++++ test/test_httpd_tx.c | 1 + 31 files changed, 1885 insertions(+), 23 deletions(-) create mode 100644 doc/lacp.md create mode 100644 rtl837x_lacp.c create mode 100644 rtl837x_lacp.h create mode 100644 test/lacp/.gitignore create mode 100644 test/lacp/Makefile create mode 100644 test/lacp/lacp_sim.c create mode 100644 test/lacp/shim/machine.h create mode 100644 test/lacp/shim/rtl837x_common.h create mode 100644 test/lacp/shim/rtl837x_port.h create mode 100644 test/lacp/shim/rtl837x_regs.h create mode 100644 test/lacp/shim/uip.h create mode 100644 test/port_hash/.gitignore create mode 100644 test/port_hash/Makefile create mode 100644 test/port_hash/test_lag_hash.c diff --git a/Makefile b/Makefile index 8c8ca72c..68d416f4 100644 --- a/Makefile +++ b/Makefile @@ -90,6 +90,7 @@ SRCS += \ rtl837x_stp.c \ rtl837x_lldp.c \ rtl837x_storm.c \ + rtl837x_lacp.c \ rtl837x_trap.c SRCS += \ httpd/httpd.c \ diff --git a/README.md b/README.md index 6eaf74e8..b605ca5f 100644 --- a/README.md +++ b/README.md @@ -359,6 +359,7 @@ The following documents give further documentation on specific features of the R - [IGMP (IP-MC streaming)](doc/igmp.md) - [SFP+ ports](doc/sfp.md) - [Trunking aka. port aggregation](doc/trunking.md) +- [LACP (802.3ad link aggregation)](doc/lacp.md) - [VLAN](doc/vlan.md) - [Storm control](doc/storm_control.md) - [Modifications and Flash replacement](doc/mods.md) diff --git a/cmd_parser.c b/cmd_parser.c index e7daa9e8..951d6be5 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -14,6 +14,7 @@ #include "rtl837x_regs.h" #include "rtl837x_sfr.h" #include "rtl837x_stp.h" +#include "rtl837x_lacp.h" #include "rtl837x_igmp.h" #include "rtl837x_bandwidth.h" #include "rtl837x_lldp.h" @@ -537,7 +538,18 @@ void parse_lag(void) return; } + /* "lag lacp [port]..." - run LACP on this LAG with the given candidate + * ports; "lag lacp off" (or no ports) removes the LAG from LACP. */ uint8_t w = 2; + __xdata uint8_t lacp_mode = 0; + if (cmd_words_len > 2 && cmd_compare(2, "lacp")) { + lacp_mode = 1; + w = 3; + if (cmd_words_len > 3 && cmd_compare(3, "off")) { + lacp_lag_set(group, 0); + return; + } + } while (w < cmd_words_len) { // Parse port. if (cmd_parse_port_separator(cmd_words_b[w++]) == 0) @@ -545,10 +557,13 @@ void parse_lag(void) members |= ((uint16_t)1) << atoi_results_u8; } - port_lag_members_set(group, members); + if (lacp_mode) + lacp_lag_set(group, members); /* 0 ports also valid: removes the LAG */ + else + port_lag_members_set(group, members); return; err: - cmd_error("lag (show | <1-4> (d | ...))\n"); + cmd_error("lag (show | <1-4> (d | [lacp] ...))\n"); } @@ -1972,11 +1987,19 @@ void cmd_parser(void) __banked } else if (cmd_compare(0, "health")) { health_show(); #endif + } else if (cmd_compare(0, "lacp")) { + if (cmd_compare(1, "show")) + lacp_show(); + else if (cmd_compare(1, "off")) + lacp_off(); + else + cmd_error("lacp (show | off); a group starts LACP with: lag <1-4> lacp ...\n"); } else if (cmd_compare(0, "pvid")) { if (cmd_words_len == 3 && cmd_parse_port_separator(cmd_words_b[1]) != 0 && atoi_short(cmd_words_b[2]) && atoi_results_short && atoi_results_short <= 4094) + { port_pvid_set(atoi_results_u8, atoi_results_short); - else { + } else { cmd_error("pvid <1-4094>\n"); } } else if (cmd_compare(0, "vlan")) { diff --git a/doc/CpuPort.md b/doc/CpuPort.md index c10d0a15..f5e9859b 100644 --- a/doc/CpuPort.md +++ b/doc/CpuPort.md @@ -79,6 +79,11 @@ bit7 KEEP | 6 VSEL | 5 LEARN_DIS | 4:0 VIDX All fields are in network byte order. +This word has to be written through `HTONS` like every other field of the tag. +Writing a constant raw puts the bits in the wrong byte, so `0x0020` reaches the +wire as `0x2000`, which is EFID rather than LEARN_DIS. The ASIC then fails to +parse the tag and forwards the frame with the `0x8899` header still on it. + * `EFID_EN`, `EFID`: look the destination up under this filtering ID instead of the port's own * `PRI_EN`, `PRI`: force the given priority on the frame diff --git a/doc/lacp.md b/doc/lacp.md new file mode 100644 index 00000000..1b78459e --- /dev/null +++ b/doc/lacp.md @@ -0,0 +1,231 @@ +# LACP (Link Aggregation Control Protocol, IEEE 802.3ad) + +A static link aggregation group works only if both ends agree about it in +advance. Nothing checks that they do: cable a member to the wrong neighbour and +the group keeps hashing frames onto a link that goes somewhere else. LACP is the +protocol that makes the two ends agree, and keeps agreeing — it discovers which +links reach the same partner, aggregates only those, and drops a link out of the +group when the partner stops answering. + +The switch runs LACP in software on the 8051. Frames are the Slow Protocols +group: destination `01:80:C2:00:00:02`, ethertype `0x8809`, subtype 1. The +implementation covers the parts that decide whether an aggregate forms: +the Receive machine and the Selection and Mux machines of 802.3ad clause 43, +`recordPDU` and `update_NTT`, and the mis-cabling protection that refuses to +aggregate links whose partners differ. It does not implement the Churn Detection +machines or the Marker protocol, and the Mux machine is the coupled variant, so +collecting and distributing are enabled together rather than in sequence. + +Up to `LACP_NUM_LAGS` (4) groups can be placed under LACP; the rest stay static. + +## Getting the frames to the CPU + +Reserved multicast addresses are handled by the RMA block before the L2 lookup +happens. Each address has its own register and the action field sits in bits +[5:4]: 0 forward, 1 trap to CPU, 2 drop, 3 forward excluding the CPU. The +registers are packed and consecutive by *entry* rather than by address byte, so +the step of 4 counts entries, not bytes of the address: `...:00` sits at +`0x4ecc` and `...:02` at `0x4ed4`, but `...:08` sits at `0x4ee0`. + +``` +#define RTL837X_RMA0_CONF 0x4ecc /* 01:80:C2:00:00:00, entry 0 */ +#define RTL837X_RMA2_CONF 0x4ed4 /* 01:80:C2:00:00:02, Slow Protocols */ +#define RTL837X_RMA_ACT_MASK 0x30 +#define RTL837X_RMA_ACT_TRAP 0x10 +#define RTL837X_RMA_ACT_DROP 0x20 +``` + +The engine puts this address on *trap* while it runs. A trap takes the frame +out of the forwarding path and hands it to the port named in `EXT_CPU_CTRL` +(`0x6724`), whose reset value names no port at all: the destination is meant to +be an *external* CPU on a physical port, which these boards do not have. That +is why the trap first looked like a dead end - the receive counters stayed +frozen while the partner kept sending, and the ingress discard counter grew +instead. `trap_init()` points the register at the CPU port once at startup, and +the same frames then arrive. + +`FORWARD` reaches the CPU too, because the CPU port is in the forwarding +domain, but a forwarded frame is subject to the L2 lookup and on its own floods +to every other port in the VLAN. That leaks a neighbour's LACPDUs to hosts that +must not see them and, with two bonds on one switch, poisons both; a reserved +link-local group must not be forwarded at all. Containing it takes a static L2 +multicast entry with a CPU-only member mask, and because lookups are IVL on +this chip, one entry per distinct pvid over all front panel ports - a VID 0 +entry is never matched, and a port outside the LACP groups needs its pvid +covered as well, or a LACPDU arriving there misses the lookup and is flooded +through its VLAN. + +The trap needs none of that and reaches further, because it acts on the address +before the VLAN lookup and before the ingress filters. An untagged LACPDU is +delivered on a port that admits tagged frames only, where a forwarded one is +dropped by the acceptable-frame-type filter; and it is delivered on a port STP +holds in blocking, so a group converges without waiting out the forward delay. +Both were measured against forward on the same ports of an SWTGW218AS. + +A trapped frame arrives without the VLAN tag the hardware inserts into a +forwarded one, so the LACPDU body sits four bytes earlier. `TRAP_RX_BODY()` +gives the offset of the body for either form, and `lacp_in()` reads the PDU +from there. + +Neighbouring addresses in the same block still differ in other respects - +`...:00` is exempt from the ingress STP-state check and `...:02` is not, which +the trap makes moot only because it acts before that check - so measure before +carrying a conclusion about one of them across to another. + +When LACP is switched off the action goes to `DROP`, which acts before the L2 +lookup, so there is nothing to undo. + +## What has to match across the members + +802.3ad requires that every link in an aggregate be full duplex and run at the +same speed. Beyond the standard, anything that changes how a frame is treated +has to be identical across members, because the hash picks the member per frame +and a difference would make the behaviour depend on that choice: + +* must match: speed and duplex, VLAN membership and pvid, ingress filtering, + port isolation +* may differ: EEE, LED configuration, counters, cable diagnostics + +None of this is enforced yet — `port_lag_members_set()` writes a member mask and +a hash setting and validates nothing. See #377 for the discussion of whether a +mismatch should be rejected or applied to the whole group. + +## Protocol timing + +``` +#define LACP_FAST_PERIODIC 0x0032 /* fast TX 1 s; partner expires at 3 s */ +#define LACP_SLOW_PERIODIC 0x05dc /* slow TX 30 s; partner expires at 90 s */ +#define LACP_SHORT_TIMEOUT 0x00c8 /* silent partner dropped after 4 s (802.3ad: 3 s) */ +#define LACP_LONG_TIMEOUT 0x1770 /* long-timeout variant, after 120 s (802.3ad: 90 s) */ +``` + +One unit is one step of the LACP timers, taken every four system ticks, so 50 +per second whatever the pace of the main loop; 50 units came out as 1.00 s on +the wire in all 116 intervals of a two minute capture. + +The two transmit periods are worth a word, because they are the one thing here +that is not ours to choose freely. Our own timeouts only decide how patient we +are with a partner that has gone quiet, and being slow there costs nobody +anything. The transmit period is the opposite: it decides when the *partner* +gives up on us. 802.3ad pairs a 1 s fast period with a 3 s short timeout, and a +30 s slow period with a 90 s long timeout, and the ratio is the whole point. + +Getting that wrong does not break the aggregate, which is what makes it easy to +miss. An earlier version of this code transmitted every 2 to 3 s while the +partner was asking for the fast rate. The links still aggregated and still +carried traffic, but every time a gap crossed 3 s the partner's receive machine +timed us out and recovered: 13 of its 93 LACPDUs came back carrying `EXPIRED` +and reporting our SYNC as cleared. Since a received actor state is recorded +verbatim as the partner state and sent back out, the flap was visible from both +ends, and the bond's churn machines never settled. With the periods above, the +same capture shows no expiry at all and both churn states read `none`. + +Our own timeouts sit one period above the standard's 3 s and 90 s, which +absorbs a single lost LACPDU plus the tick jitter. A partner whose LACP has +stopped while its link stayed up is out of the trunk after that; anything +longer keeps hashing traffic onto a member that no longer agrees to carry it. + +## LACP API + +``` +void lacp_init(void) __banked; /* boot init: clear per-LAG state */ +void lacp_off(void) __banked; +void lacp_lag_set(uint8_t lag, uint16_t ports) __banked; +void lacp_in(void) __banked; +void lacp_timers(void) __banked; +void lacp_show(void) __banked; +``` + +`lacp_init()` has to run before the startup configuration replays, because xdata +is not zeroed and the port-to-LAG map uses `0xff` as "no LAG"; a stray zero would +make a port look like it belongs to LAG 0. + +## LACP configuration on the serial console + +A group is put under LACP by adding `lacp` to the `lag` command: + +``` +lag 1 lacp 7 8 # ports 7 and 8 become LACP candidates of group 1 +lag 1 lacp off # group 1 goes back to static (so does "lag 1 lacp") +lag 1 7 8 # plain static aggregation, no protocol +``` + +A group put under LACP gives up the static trunk it had: the hardware trunk is +cleared and rebuilt from the ports that converge. A port named in another LACP +group moves to the new one. The protocol starts with the first group put under +LACP and stops when the last one is taken out; there is no separate switch for +it. Two commands act on +the whole engine: + +``` +lacp off # every group back to static, protocol stopped +lacp show +``` + +`lacp show` prints the engine state, then one line per group with its candidate +ports, the elected aggregator and the trunk members actually programmed into the +hardware, then one line per port with the actor and partner state bytes and the +receive-machine state. + +## LACP configuration via the Web Interface + +Each group on the LAG page has a Static/LACP mode next to its member ports; +for an LACP group it shows the elected aggregator and the active members. Below +the groups a table has one row per LACP port with its group, actor and partner +state, receive state, LACPDU count and partner system. The page reads +`/lacp.json` every three seconds while it is open. The member checkboxes of a +static group come from the hardware registers in `/lag.json`. + +## A test with a Linux 802.3ad bond + +Two ports of the switch to two interfaces of one host: + +``` +ip link add bond0 type bond mode 802.3ad +ip link set enp1s0 down && ip link set enp1s0 master bond0 +ip link set enp2s0 down && ip link set enp2s0 master bond0 +ip link set bond0 up +``` + +On the switch, with those two ports as the group: + +``` +lag 1 lacp 7 8 +``` + +`lacp show` should then give both ports an actor state of `0x3f`, which is +ACTIVITY, TIMEOUT, AGGREGATION, SYNC, COLLECTING and DISTRIBUTING together, a +partner state with SYNC in it, and the partner's system MAC. `lag show` should +list both ports as trunk members, since that reads the hardware rather than the +protocol's opinion of itself. + +The host's view is the one worth trusting, in `/proc/net/bonding/bond0`. Both +slaves want the **same `Aggregator ID`**; two different numbers mean the bond +has not aggregated anything and each link is sitting in its own group, which +looks healthy at a glance and is not. Both churn states should read `none`. +`monitoring` that never settles is the signature of our transmit period being +too slow for the rate the partner asked for. + +Three things are worth checking past convergence, because each of them has been +broken here at some point: + +* `tcpdump -i enp1s0 ether proto 0x8809` on each slave should show the switch's + LACPDUs and that slave's own, and never the other slave's. A sibling leak is + what the trap prevents: the PDU never enters the forwarding path at all. +* No `illegal loopback` in the host's log. +* In a hex dump of a frame from the switch, the three reserved bytes after the + actor state should be zero. They used to carry whatever the transmit buffer + held from the previous frame. + +Pulling one slave down and putting it back is the cheapest failure test. The +group narrows to the surviving member and comes back within tens of seconds, +and traffic across the transition loses a few percent at the edges rather than +half of everything, which is what it would lose if the ASIC kept hashing onto +the dead member. + +## Known limits + +* No Churn Detection machines and no Marker protocol. +* Coupled Mux: collecting and distributing are enabled together. +* One aggregator per group. +* Member compatibility is not validated; see #377. diff --git a/doc/link_aggregation.md b/doc/link_aggregation.md index b725e419..462b66de 100644 --- a/doc/link_aggregation.md +++ b/doc/link_aggregation.md @@ -227,3 +227,52 @@ iperf Done. ``` As you can see, the total throughput was 4.71 GBit/sec which is close to the maximum possible with a single 5GBit link. + +## LACP: how the PDUs reach the CPU + +Slow-protocol frames go to the reserved group `01:80:C2:00:00:02`, and the +ASIC's default action for it is *drop*. The engine sets the action to *trap* +while it runs. A trap hands the frame to the port named in `EXT_CPU_CTRL`, +which names no port out of reset - that is why the trap first looked like no +help at all: the receive counters stayed frozen while the partner kept sending, +and the frames were counted as ingress discards. `trap_init()` points the +register at the CPU port once at startup. + +The *forward* action would reach the CPU as well, since the CPU port is in the +forwarding domain, but it leaves the frame in the L2 lookup and so floods it +across the ingress VLAN. That leaks LACPDUs to unrelated ports, and the damage +is real: a bond's own ports see each other's PDUs and Linux reports an illegal +loopback, a second bond on the same switch is poisoned, and the flood escapes +through uplinks. Containing it would take a static L2 multicast entry with a +CPU-only mask for every PVID in use, since untagged LACPDUs classify into the +ingress port's PVID and the forward action is not limited to the LACP ports. + +The trap keys on the address instead, before the VLAN lookup and before the +ingress filters, which settles three things at once: + +* Nothing is written per VLAN or per PVID, so delivery no longer depends on + which PVIDs the ports happen to carry. +* A port that admits tagged frames only still receives: an untagged forwarded + LACPDU is dropped by the acceptable-frame-type filter, a trapped one is not. +* A port STP holds in blocking still receives, so a group converges without + waiting out the forward delay. + +A trapped frame arrives without the VLAN tag the hardware inserts into a +forwarded one, so the PDU body sits four bytes earlier; `TRAP_RX_BODY()` gives +the offset for both forms. + +`lacp off` puts the action back to *drop*, which acts before the L2 lookup. + +## LACP: what the implementation leaves out + +The four state machines of Clause 43 are present in simplified form. Mux control +is coupled rather than independent, one partner system is elected per LAG, and +there are no churn detection machines. + +One interoperability rule is worth stating because getting it wrong is silent. +The Partner block of an outgoing PDU has to echo the peer's own actor identity +verbatim, meaning system and priority, key, port and port priority, and the +aggregation flag. A Linux partner's `__record_pdu()` accepts our SYNC bit only +when that block mirrors what it sent. Hardcoding the priorities to zero +mismatches Linux's default of 0xffff, which clears partner SYNC, moves the state +byte from 0x3f to 0x37, and the bond never reaches collecting and distributing. diff --git a/html/app.js b/html/app.js index d737810b..4c7ed7f5 100644 --- a/html/app.js +++ b/html/app.js @@ -67,6 +67,9 @@ iso_title:"Port isolation",iso_from:"From port",iso_to:"To port",iso_all:"Allow iso_intro:"A frame entering a port may only leave through the ports ticked in its row. Isolation is decided before forwarding, so a port that is not ticked is unreachable directly, whatever the address table says.", iso_legend:"The cell of the port itself is always allowed. A port number in red marks a port that is not fully open.", lag_hash:"Hash:",lag_note:"A LAG needs at least one member to be saved in the startup config; applying an empty group clears it.", +lacp_heading:"LACP (802.3ad)",lag_mode:"Mode",lag_static:"Static",lacp_neg:"negotiating",lacp_agg:"aggregator", +lacp_members:"active members",lacp_hint:"LACP: select candidate ports and press Apply to start negotiation", +lacp_actor:"Actor",lacp_partner:"Partner",lacp_rxstate:"RX state",lacp_rx:"LACPDUs received",lacp_psys:"Partner system", lag_clear_q:"Clear LAG {n}?",lag_clear_d:"All member ports return to normal operation.", e_title:"Energy Efficient Ethernet",e_adv:"Advertised",e_lp:"Link partner",e_active:"Active",e_enable:"Enable", e_note:"Advertised and link-partner flags per speed: 100M, 1G, 2.5G. SFP ports do not support EEE.", @@ -808,7 +811,7 @@ var CONF_CMDS=[ /^port\s+\d{1,2}\s+lldp\s+(permit|block)+$/, /^eee\s+(on|off)(\s+\d{1,2})?(\s+(100m|1g|2g5))?$/, /^mirror(\s+\d{1,2})(\s+\d{1,2}[tr]?)+$/,/^mirror\s+off$/, - /^lag\s+[1-4](\s+\d{1,2})+$/,/^lag\s+[1-4]\s+d$/,/^laghash\s+[1-4](\s+\w+)+$/, + /^lag\s+[1-4](\s+\d{1,2})+$/,/^lag\s+[1-4]\s+d$/,/^lag\s+[1-4]\s+lacp(\s+\d{1,2})+$/,/^lag\s+[1-4]\s+lacp\s+off$/,/^laghash\s+[1-4](\s+\w+)+$/, /^isolate\s+\d{1,2}(\s+(off|\d{1,2}))+$/, /^stp\s+(on|off)$/,/^stp\s+(prio|hello|maxage|fwd|txhold)\s+\d{1,2}$/, /^stp\s+version\s+(rstp|stp)$/, @@ -1814,6 +1817,15 @@ function buildLag(){ for(var g=1;g<=4;g++)(function(g){ var card=h("div",{class:"card"}); card.appendChild(h("h2",{text:"LAG "+g})); + var mr=h("div",{style:"display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:10px",class:"small"}); + mr.appendChild(h("span",{class:"mut",text:t("lag_mode")})); + var ms=h("select",{class:"in",id:"lgm"+g}); + ms.appendChild(h("option",{value:"static",text:t("lag_static")})); + ms.appendChild(h("option",{value:"lacp",text:"LACP"})); + ms.addEventListener("change",function(){$("lgs"+g).textContent=ms.value==="lacp"?t("lacp_hint"):""}); + mr.appendChild(ms); + card.appendChild(mr); + card.appendChild(h("p",{class:"small mono",id:"lgs"+g,style:"margin-bottom:10px"})); var pr=h("div",{style:"display:flex;gap:10px;flex-wrap:wrap;margin-bottom:10px"}); for(var p=1;p<=S.n;p++)pr.appendChild(h("label",null,[ h("input",{type:"checkbox",id:"lg"+g+"p"+p}),document.createTextNode(" "+p+" "), @@ -1826,14 +1838,33 @@ function buildLag(){ }); card.appendChild(hr); card.appendChild(h("button",{class:"ctl pri",text:t("c_apply"),onclick:function(){lagApply(g)}})); + card.addEventListener("change",function(){card.dataset.dirty="1"}); w.appendChild(card); })(g); } +var lacpCfg=[0,0,0,0,0]; function lagLoad(){ - return getJSON("/lag.json").then(function(s){ + return getJSON("/lacp.json").then(function(c){ + var tb=$("lacptbl").tBodies[0]; + tb.innerHTML=""; + c.lags.forEach(function(lg,i){ + var g=i+1; + lacpCfg[g]=parseInt(lg.cfg,16); + if($("lgm"+g).closest(".card").dataset.dirty)return; + $("lgm"+g).value=lacpCfg[g]?"lacp":"static"; + $("lgs"+g).textContent=lacpCfg[g]?"LACP: "+t("lacp_agg")+" "+(lg.aggValid?lg.agg:"("+t("lacp_neg")+")")+", "+t("lacp_members")+" 0x"+lg.members:""; + }); + if(c.on)c.ports.forEach(function(p){ + if(p.lag===255)return; + var tr=tb.insertRow(); + [p.p,p.lag+1,p.a,p.pt,p.rs,parseInt(p.rx,16),p.psys].forEach(function(v){tr.insertCell().textContent=v}); + }); + return getJSON("/lag.json"); + }).then(function(s){ s.forEach(function(l){ var g=l.lagNum+1; - var members=parseInt(l.members,2); + if($("lgm"+g).closest(".card").dataset.dirty)return; + var members=lacpCfg[g]||parseInt(l.members,2); for(var p=1;p<=S.n;p++) $("lg"+g+"p"+p).checked=!!((members>>S.physToLog[p-1])&1); var hash=parseInt(l.hash,16); @@ -1842,21 +1873,25 @@ function lagLoad(){ }); } function lagApply(g){ - var cmd="lag "+g,n=0; + var lacp=$("lgm"+g).value==="lacp",cmd="lag "+g+(lacp?" lacp":""),n=0,pre=[]; + delete $("lgm"+g).closest(".card").dataset.dirty; for(var p=1;p<=S.n;p++)if($("lg"+g+"p"+p).checked){cmd+=" "+p;n++;} - if(!n){ + if(!lacp&&lacpCfg[g])pre.push("lag "+g+" lacp off"); + if(lacp&&!n)cmd="lag "+g+" lacp off"; + if(!n&&!lacp){ confirmModal(t("lag_clear_q",{n:g}),t("lag_clear_d"),function(){ - postCmd("lag "+g+" d").then(lagLoad).catch(function(){}); + postCmds(pre.concat(["lag "+g+" d"])).then(lagLoad).catch(function(){}); }); return; } var hcmd="laghash "+g,nh=0; HASHF.forEach(function(f){if($("lg"+g+"h"+f).checked){hcmd+=" "+f;nh++;}}); - var cmds=[cmd]; + var cmds=pre.concat([cmd]); if(nh)cmds.push(hcmd); postCmds(cmds).then(lagLoad).catch(function(){}); } -tabHooks.lag={enter:function(){needPorts(function(){buildLag();lagLoad().catch(function(){})})}}; +var lagPoller=new Poller(function(){return lagLoad().catch(function(){})},3000); +tabHooks.lag={enter:function(){needPorts(function(){buildLag();lagPoller.start()})},leave:function(){lagPoller.stop()}}; function lldpLoad(){ @@ -2245,6 +2280,7 @@ function mergeConf(base,texts){ else drop(/^eee /); conf.push(line);return; } + if((m=line.match(/^lag (\d) lacp off$/))){drop(new RegExp("^lag "+m[1]+" lacp( |$)"));return;} if(line==="mirror off"){drop(/^mirror /);return;} if(!isConfCmd(line))return; if((m=line.match(/^ingress (.+)$/))){ diff --git a/html/index.html b/html/index.html index f896dd13..21b7d17f 100644 --- a/html/index.html +++ b/html/index.html @@ -356,6 +356,11 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}
+

LACP (802.3ad)

+
+ +
PortLAGActorPartnerRX stateLACPDUs receivedPartner system
+

A LAG needs at least one member to be saved in the startup config; applying an empty group clears it.

diff --git a/httpd/httpd.c b/httpd/httpd.c index aec2eaf1..f2ca2d36 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -1036,6 +1036,8 @@ void httpd_appcall(void) send_stp(); } else if (is_word(q, "/lldp.json")) { send_lldp(); + } else if (is_word(q, "/lacp.json")) { + send_lacp(); } else if (is_word(q, "/vlanlist")) { send_vlanlist(); } else if (is_word(q, "/config")) { diff --git a/httpd/page_impl.c b/httpd/page_impl.c index 2d58457e..44c9ddcb 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -14,6 +14,7 @@ #include "version.h" #include "machine.h" #include "rtl837x_stp.h" +#include "rtl837x_lacp.h" #include "page_impl.h" #include "syslog.h" @@ -408,14 +409,19 @@ void send_l2(uint16_t idx) */ __xdata uint16_t entry = idx & 0xfff; __xdata uint16_t first_entry = 0xffff; // An illegal entry index + __xdata uint16_t mbr; __bit first = true; char_to_html('['); while (1) { entries_left--; uint8_t port = 0; uint8_t lag; + uint8_t mc = 0; reg_read_m(RTL837x_TBL_DATA_0); - REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1] & 0xfc, sfr_data[2] | (TBL_LUTREAD_NEXT_L2UC << 6), sfr_data[3]); + // Iterate with "next address" (method 2) so static multicast entries - + // an IGMP group entry, say - are listed too, not only learned or + // static unicast ones. + REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1] & 0xfc, sfr_data[2] | (TBL_LUTREAD_NEXT_ADDRESS << 6), sfr_data[3]); REG_WRITE(RTL837X_TBL_CTRL, entry >> 8, entry, TBL_L2_UNICAST, TBL_EXECUTE); do { @@ -423,7 +429,7 @@ void send_l2(uint16_t idx) } while (sfr_data[3] & TBL_EXECUTE); reg_read_m(RTL837x_L2_DATA_OUT_B); - __bit valid = (sfr_data[0] & 0x20) != 0; + __bit valid = (sfr_data[0] & 0x20) && !(sfr_data[0] & 0x10); if (valid) { /* separator + 74-byte worst-case entry + closing "]" */ if (slen + 76 > TCP_OUTBUF_SIZE) @@ -432,6 +438,8 @@ void send_l2(uint16_t idx) char_to_html(','); first = false; + mc = sfr_data[2] & 0x01; // multicast MAC: member mask, no SPA/age + // VLAN, taken from the read above instead of reading the register twice slen += strtox(outbuf + slen, "{\"vlan\":\""); charhex_to_html(sfr_data[0] & 0x0f); @@ -450,12 +458,23 @@ void send_l2(uint16_t idx) // type reg_read_m(RTL837x_L2_DATA_OUT_C); - if (sfr_data[1] & 0x1) + if (mc || (sfr_data[1] & 0x1)) slen += strtox(outbuf + slen, "\",\"type\":\"s\",\"port\":"); else slen += strtox(outbuf + slen, "\",\"type\":\"l\",\"port\":"); - port |= (sfr_data[3] & 0x3) << 2; + if (mc) { + // Report the lowest member of the mask (an entry holding only + // the CPU port is rendered as "CPU" by the UI) + mbr = port | ((uint16_t)sfr_data[3] << 2); + port = 0; + while (mbr && !(mbr & 1)) { + mbr >>= 1; + port++; + } + } else { + port |= (sfr_data[3] & 0x3) << 2; + } itoa_html(port); slen += strtox(outbuf + slen, ",\"lag\":"); lag = port_lag_of(port); @@ -736,6 +755,60 @@ void send_stp(void) slen += strtox(outbuf + slen, "]}"); } +/* LACP protocol status for the LAG page ("/lacp.json"). State variables are + * exported read-only via rtl837x_lacp.h; hardware trunk membership itself is + * already visible through send_lag() (it reads the trunk registers). */ +void send_lacp(void) +{ + dbg_string("send_lacp called\n"); + slen = strtox(outbuf, HTTP_RESPONCE_JSON); + + slen += strtox(outbuf + slen, "{\"on\":"); + bool_to_html(lacpEnabled); + /* Per-LAG section: candidate ports, elected aggregator, trunk members. */ + slen += strtox(outbuf + slen, ",\"lags\":["); + for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) { + slen += strtox(outbuf + slen, "{\"cfg\":\""); + byte_to_html(lacp_lag_ports[l] >> 8); + byte_to_html(lacp_lag_ports[l]); + slen += strtox(outbuf + slen, "\",\"aggValid\":"); + bool_to_html(lacp_agg_valid[l]); + slen += strtox(outbuf + slen, ",\"agg\":\""); + for (uint8_t j = 0; j < 6; j++) + byte_to_html(lacp_agg_sys[l][j]); + slen += strtox(outbuf + slen, "\",\"members\":\""); + byte_to_html(lacp_members_last[l] >> 8); + byte_to_html(lacp_members_last[l]); + slen += strtox(outbuf + slen, "\"},"); + } + slen -= 1; // remove comma + slen += strtox(outbuf + slen, "],\"ports\":["); + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + slen += strtox(outbuf + slen, "{\"p\":"); + itoa_html(machine.log_to_phys_port[i]); + /* Which LACP LAG this port belongs to; 255 = none */ + slen += strtox(outbuf + slen, ",\"lag\":"); + itoa_html(lacp_port_lag[i]); + slen += strtox(outbuf + slen, ",\"a\":\""); + byte_to_html(lacp_actor_state[i]); + slen += strtox(outbuf + slen, "\",\"pt\":\""); + byte_to_html(lacp_partner_state[i]); + slen += strtox(outbuf + slen, "\",\"rs\":"); + itoa_html(lacp_rx_state[i]); + /* rx as 4-digit hex: itoa16_html() only renders values up to 9999 + * correctly (see its VLAN-ID comment), the counter goes to 65535 */ + slen += strtox(outbuf + slen, ",\"rx\":\""); + byte_to_html(lacp_rx_count[i] >> 8); + byte_to_html(lacp_rx_count[i]); + slen += strtox(outbuf + slen, "\",\"psys\":\""); + for (uint8_t j = 0; j < 6; j++) + byte_to_html(lacp_partner_sys[i][j]); + slen += strtox(outbuf + slen, "\"},"); + } + slen -= 1; // remove comma + slen += strtox(outbuf + slen, "]}"); +} + void send_lldp(void) { dbg_string("send_lldp called\n"); diff --git a/httpd/page_impl.h b/httpd/page_impl.h index fd51c8c6..5133d2eb 100644 --- a/httpd/page_impl.h +++ b/httpd/page_impl.h @@ -24,6 +24,7 @@ void send_cmd_log(void); void send_lag(void); void send_stp(void); void send_lldp(void); +void send_lacp(void); void send_vlanlist(void); /* Convert only the lower nibble to ascii HEX char. diff --git a/rtl837x_igmp.c b/rtl837x_igmp.c index 3ef2b2ec..39b53405 100644 --- a/rtl837x_igmp.c +++ b/rtl837x_igmp.c @@ -22,8 +22,8 @@ extern __xdata uint8_t igmpEnabled; #include "uip.h" #include "uip_arp.h" -#pragma codeseg BANK1 -#pragma constseg BANK1 +#pragma codeseg BANK3 +#pragma constseg BANK3 extern __xdata uint8_t cpuPort; extern __xdata uint8_t sfr_data[4]; diff --git a/rtl837x_lacp.c b/rtl837x_lacp.c new file mode 100644 index 00000000..b4874f16 --- /dev/null +++ b/rtl837x_lacp.c @@ -0,0 +1,607 @@ +/* + * LACP (IEEE 802.3ad Clause 43) driver for the RTL837x platform. + * This code is in the Public Domain. + */ + +// #define DEBUG + +#pragma codeseg BANK3 +#pragma constseg BANK3 + +#include +#include "rtl837x_common.h" +#include "rtl837x_regs.h" +#include "rtl837x_lacp.h" +#include "rtl837x_trap.h" +#include "rtl837x_port.h" +#include "uip.h" +#include "machine.h" + +extern __code const struct machine machine; +extern __xdata struct machine_runtime machine_detected; /* runtime chip detection, owned by rtl837x_port.c */ +extern __xdata uint16_t management_vlan; /* owned by rtlplayground.c; suppressed per-frame for slow protocols */ +extern __xdata struct uip_eth_addr uip_ethaddr; +extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2]; +/* lacpEnabled: extern in rtl837x_lacp.h, owned by rtlplayground.c */ + +/* 20-byte Actor/Partner information body (802.3ad 43.4.2.2), network byte order */ +struct lacp_info { + uint16_t sys_prio; + uint8_t sys[6]; + uint16_t key; + uint16_t port_prio; + uint16_t port; + uint8_t state; + uint8_t reserved[3]; +}; + +/* Outgoing LACPDU (no VLAN tag on TX, as in stp_pkt) */ +struct lacpdu { + uint8_t dst[6]; + uint8_t src[6]; + struct rtl_tag rtl_tag; + uint16_t ethertype; /* 0x8809 */ + uint8_t subtype; /* 0x01 */ + uint8_t version; /* 0x01 */ + uint8_t tlv_actor; /* 0x01 */ + uint8_t actor_len; /* 0x14 */ + struct lacp_info actor; + uint8_t tlv_partner; /* 0x02 */ + uint8_t partner_len; /* 0x14 */ + struct lacp_info partner; + uint8_t tlv_collector; /* 0x03 */ + uint8_t collector_len; /* 0x10 */ + uint16_t collector_max_delay; + uint8_t collector_reserved[12]; + uint8_t tlv_terminator; /* 0x00 */ + uint8_t terminator_len; /* 0x00 */ + uint8_t terminator_reserved[50]; +}; + +/* Incoming LACPDU, split because a VLAN tag sits between the two only when the + * frame was forwarded: a trapped one carries none (cf. stp_pkt_in) */ +struct lacpdu_in_hdr { + uint8_t dst[6]; + uint8_t src[6]; + struct rtl_tag rtl_tag; +}; + +struct lacpdu_in { + uint16_t ethertype; + uint8_t subtype; + uint8_t version; + uint8_t tlv_actor; + uint8_t actor_len; + struct lacp_info actor; + uint8_t tlv_partner; + uint8_t partner_len; + struct lacp_info partner; +}; + +__xdata uint8_t lacp_rxbody; /* offset of the LACPDU body: the RTL tag, plus a VLAN tag when forwarded */ + +#define LACP_O ((__xdata struct lacpdu *)&uip_buf[RTL_FRAME_DESC_SIZE]) +#define LACP_H ((__xdata struct lacpdu_in_hdr *)&uip_buf[0]) +#define LACP_I ((__xdata struct lacpdu_in *)&uip_buf[lacp_rxbody]) + +/* --- Per-port participant state (802.3ad 43.4.7). Indexed by physical port. --- */ +__xdata uint8_t lacp_actor_state[10]; /* our Actor_State flags */ +__xdata uint8_t lacp_rx_state[10]; /* LACP_RX_* receive machine */ +__xdata uint8_t lacp_partner_state[10]; /* last Partner_State we saw */ +__xdata uint8_t lacp_partner_sys[10][6]; /* partner System ID */ +__xdata uint16_t lacp_partner_key[10]; /* partner Key */ +__xdata uint16_t lacp_partner_port[10]; /* partner Port number */ +/* The Partner block echoes the peer's actor identity verbatim, see + * doc/link_aggregation.md. */ +__xdata uint16_t lacp_partner_sys_prio[10]; /* partner System priority */ +__xdata uint16_t lacp_partner_port_prio[10]; /* partner Port priority */ +__xdata uint16_t lacp_periodic[10]; /* down-counter to next TX */ +__xdata uint16_t lacp_timeout[10]; /* down-counter to partner expiry*/ +__xdata uint8_t lacp_ntt[10]; /* Need-To-Transmit flag */ + +/* The timers step every four system ticks (50 Hz) whatever the pace of the + * main loop; a long pass is caught up on, a few steps at a time. */ +#define LACP_TICK_STEP (SYS_TICK_HZ / 50) +#define LACP_CATCH_UP 8 +__xdata uint8_t lacp_tick_last; +extern volatile __xdata uint32_t ticks; + +/* Per-LAG state: the hardware trunk groups run LACP independently. */ +__xdata uint16_t lacp_lag_ports[LACP_NUM_LAGS]; +__xdata uint8_t lacp_port_lag[10]; +__xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][6]; +__xdata uint8_t lacp_agg_valid[LACP_NUM_LAGS]; +__xdata uint16_t lacp_members_last[LACP_NUM_LAGS]; + +__xdata uint16_t lacp_scratch_mask; +__xdata uint8_t lacp_scratch_flag; +__xdata uint8_t lacp_scratch_lag; + +/* Per-port RX LACPDU count - primarily a hardware bring-up diagnostic to + * verify slow-protocol frames actually reach the CPU port (see "lacp show") */ +__xdata uint16_t lacp_rx_count[10]; + + +void lacp_mux_update(void) __banked; /* defined below, used from lacp_in */ + +static uint8_t lacp_sys_eq(__xdata uint8_t *a, __xdata uint8_t *b) +{ + for (uint8_t i = 0; i < 6; i++) { + if (a[i] != b[i]) + return 0; + } + return 1; +} + + +/* + * Selection logic (simplified 43.4.14): a port may join the aggregator when + * we have fresh partner info, the partner considers the link aggregatable, + * and the partner is the same system the aggregator was elected for. + */ +static uint8_t lacp_port_selected(uint8_t port) +{ + uint8_t lag = lacp_port_lag[port]; + if (lag == LACP_LAG_NONE) /* port not in any LACP LAG */ + return 0; + if (lacp_rx_state[port] != LACP_RX_CURRENT) + return 0; + if (!(lacp_partner_state[port] & LACP_STATE_AGGREGATION)) + return 0; + if (!lacp_agg_valid[lag]) + return 0; + return lacp_sys_eq(lacp_partner_sys[port], lacp_agg_sys[lag]); +} + + +/* + * Mux machine (simplified coupled control, 43.4.15): SYNC follows selection; + * COLLECTING+DISTRIBUTING additionally require the partner to be in sync. + * Returns 1 if the actor state changed (caller sets NTT). + */ +static uint8_t lacp_mux_machine(uint8_t port) +{ + uint8_t old = lacp_actor_state[port]; + uint8_t new = old; + + if (lacp_port_selected(port)) { + new |= LACP_STATE_SYNC; + if (lacp_partner_state[port] & LACP_STATE_SYNC) + new |= LACP_STATE_COLLECTING | LACP_STATE_DISTRIBUTING; + else + new &= ~(LACP_STATE_COLLECTING | LACP_STATE_DISTRIBUTING); + } else { + new &= ~(LACP_STATE_SYNC | LACP_STATE_COLLECTING | LACP_STATE_DISTRIBUTING); + } + + if (new == old) + return 0; + lacp_actor_state[port] = new; + return 1; +} + +#define port_bit(p) (((uint8_t)1) << (p)) + + +/* Build and emit one LACPDU out physical port `port` (802.3ad 43.4.2). */ +void lacp_send(uint8_t port) __banked +{ + memset((__xdata uint8_t *)LACP_O, 0, sizeof(struct lacpdu)); + LACP_O->dst[0] = 0x01; LACP_O->dst[1] = 0x80; LACP_O->dst[2] = 0xc2; + LACP_O->dst[3] = 0x00; LACP_O->dst[4] = 0x00; LACP_O->dst[5] = LACP_DST5; + memcpy(LACP_O->src, uip_ethaddr.addr, 6); + + LACP_O->rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID); + LACP_O->rtl_tag.version = RTL_FRAME_TAG_VERSION; + LACP_O->rtl_tag.reason = 0x00; + /* Must go through HTONS like every other tag field: writing 0x0020 raw put + * the bits in the wrong byte (on the wire 0x2000 = EFID, not LEARN_DIS), so + * the ASIC could not parse the tag and forwarded the frame with the 0x8899 + * header still attached - the partner then saw ethertype 0x8899 instead of + * 0x8809 and ignored the LACPDU. KEEP additionally stops the switch from + * rewriting the 802.1Q tag format of a CPU-injected frame (as mainline). */ + LACP_O->rtl_tag.flags = HTONS(RTL_TAG_LEARN_DIS | RTL_TAG_KEEP); + /* ALLOW cleared => this word is the *forwarding* port mask, i.e. directed + * egress to exactly this port (what mainline does). Setting ALLOW instead + * makes it an allowance/permission mask on top of a normal lookup, which + * for a one-hot mask resolves to an empty egress set and the frame is + * silently dropped - verified on hardware. */ + LACP_O->rtl_tag.pmask = HTONS(port_bit(port)); /* egress this port only */ + + LACP_O->ethertype = HTONS(SLOW_PROTO_ETHERTYPE); + LACP_O->subtype = SLOW_PROTO_SUBTYPE_LACP; + LACP_O->version = LACP_VERSION; + + /* Actor TLV */ + LACP_O->tlv_actor = LACP_TLV_ACTOR; + LACP_O->actor_len = LACP_TLV_LEN_INFO; + LACP_O->actor.sys_prio = HTONS(LACP_SYS_PRIO); + memcpy(LACP_O->actor.sys, uip_ethaddr.addr, 6); + /* Per-LAG Actor Key so a partner never merges ports of our different LAGs + * into one aggregate (only ever called for ports in a LACP LAG). */ + LACP_O->actor.key = HTONS((uint16_t)(lacp_port_lag[port] + 1)); + LACP_O->actor.port_prio = HTONS(LACP_DEF_PORT_PRIO); + LACP_O->actor.port = HTONS((uint16_t)port + 1); /* 1-based port id */ + LACP_O->actor.state = lacp_actor_state[port]; + + /* Partner TLV: echo the last partner info we recorded, VERBATIM. A Linux + * 802.3ad partner (__record_pdu) only accepts the SYNC bit we set above if + * this block mirrors its own actor identity exactly - system+priority, key, + * port+priority. Priorities must be echoed, not hardcoded: Linux defaults to + * system priority 0xffff, so a hardcoded 0 here cleared its partner-SYNC + * (0x3f->0x37) and the aggregate never collected/distributed. */ + LACP_O->tlv_partner = LACP_TLV_PARTNER; + LACP_O->partner_len = LACP_TLV_LEN_INFO; + LACP_O->partner.sys_prio = HTONS(lacp_partner_sys_prio[port]); + memcpy(LACP_O->partner.sys, lacp_partner_sys[port], 6); + LACP_O->partner.key = HTONS(lacp_partner_key[port]); + LACP_O->partner.port_prio = HTONS(lacp_partner_port_prio[port]); + LACP_O->partner.port = HTONS(lacp_partner_port[port]); + LACP_O->partner.state = lacp_partner_state[port]; + + /* Collector TLV + Terminator */ + LACP_O->tlv_collector = LACP_TLV_COLLECTOR; + LACP_O->collector_len = LACP_TLV_LEN_COLLECTOR; + LACP_O->collector_max_delay = 0; + LACP_O->tlv_terminator = LACP_TLV_TERMINATOR; + LACP_O->terminator_len = 0x00; + + lacp_ntt[port] = 0; + /* Slow-protocol frames are link-local and must egress untagged. With a + * management VLAN set, tcpip_output() splices an 802.1Q tag after the SA, + * shifting our in-frame rtl_tag out of the position the ASIC expects - so it + * fails to parse/strip the CPU tag and the 0x8899 header leaks onto the wire + * (the partner then ignores the LACPDU). Suppress the VLAN insert for this + * frame only; management traffic keeps its tag. Verified on HW: with the + * insert suppressed the LACPDU egresses as clean 0x8809 and the bond + * partner converges (partner MAC becomes the switch, both links aggregate). */ + uint16_t saved_mgmt_vlan = management_vlan; + management_vlan = 0; + uip_len = sizeof(struct lacpdu); + tcpip_output(); + management_vlan = saved_mgmt_vlan; +} + + +/* + * Process an incoming LACPDU. Called from the rtlplayground.c dispatch when a + * frame with DA 01:80:C2:00:00:02 and ethertype 0x8809/subtype 0x01 arrives. + * The ingress port is the low nibble of rtl_tag.pmask on RX. + */ +void lacp_in(void) __banked +{ + lacp_rxbody = TRAP_RX_BODY(); + if (uip_len < lacp_rxbody + sizeof(struct lacpdu_in)) { + uip_len = 0; + return; + } + uip_len = 0; + + if (LACP_I->ethertype != HTONS(SLOW_PROTO_ETHERTYPE) + || LACP_I->subtype != SLOW_PROTO_SUBTYPE_LACP) + return; + + /* Per rtl837x_common.h, pmask carries a 4-bit port number on RX + * (hardware-verified on RTL8373: per-port rx counters track the actual + * ingress port through full LACP convergence). */ + uint8_t port = ((uint8_t)HTONS(LACP_H->rtl_tag.pmask)) & 0x0f; + if (port < machine.min_port || port > machine.max_port) + return; + + /* Only ports assigned to a LACP-mode LAG participate in the protocol. */ + uint8_t lag = lacp_port_lag[port]; + if (lag == LACP_LAG_NONE) + return; + + +#ifdef DEBUG + print_string("LACP in, port "); print_byte(port); + print_string(" partner_state "); print_byte(LACP_I->actor.state); write_char('\n'); +#endif + + lacp_rx_count[port]++; + + /* Record partner = the remote's Actor block (802.3ad 43.4.9 recordPDU) */ + memcpy(lacp_partner_sys[port], LACP_I->actor.sys, 6); + lacp_partner_key[port] = HTONS(LACP_I->actor.key); + lacp_partner_port[port] = HTONS(LACP_I->actor.port); + lacp_partner_sys_prio[port] = HTONS(LACP_I->actor.sys_prio); + lacp_partner_port_prio[port] = HTONS(LACP_I->actor.port_prio); + lacp_partner_state[port] = LACP_I->actor.state; + + /* Receive machine -> CURRENT, (re)arm partner timeout (43.4.12) */ + lacp_rx_state[port] = LACP_RX_CURRENT; + lacp_timeout[port] = (LACP_I->actor.state & LACP_STATE_TIMEOUT) + ? LACP_SHORT_TIMEOUT : LACP_LONG_TIMEOUT; + + /* Elect this LAG's aggregator partner system on first contact (43.4.14) */ + if (!lacp_agg_valid[lag]) { + memcpy(lacp_agg_sys[lag], LACP_I->actor.sys, 6); + lacp_agg_valid[lag] = 1; + } + + /* Selection + Mux for this port; state change => Need-To-Transmit */ + if (lacp_mux_machine(port)) + lacp_ntt[port] = 1; + + /* update_NTT (43.4.12): if the partner's view of us is stale (their + * Partner block does not match our actor state/port), tell them again. */ + if (LACP_I->partner.state != lacp_actor_state[port] + || HTONS(LACP_I->partner.port) != (uint16_t)port + 1) + lacp_ntt[port] = 1; + + lacp_mux_update(); +} + + +/* Recompute each LACP LAG's hardware trunk membership: a port joins its LAG's + * trunk once it is fully in sync (SYNC+COLLECTING+DISTRIBUTING and the partner + * in SYNC). Each LACP-mode LAG is programmed independently (simplified 43.4.15). */ +void lacp_mux_update(void) __banked +{ + for (uint8_t lag = 0; lag < LACP_NUM_LAGS; lag++) { + if (!lacp_lag_ports[lag]) /* LAG not under LACP management */ + continue; + lacp_scratch_mask = 0; + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + if (lacp_port_lag[i] != lag) + continue; + if ((lacp_actor_state[i] & LACP_STATE_FULL) == LACP_STATE_FULL + && (lacp_partner_state[i] & LACP_STATE_SYNC)) + lacp_scratch_mask |= port_bit(i); + } + /* Program the trunk only when this LAG's membership actually changed + * - this runs on every timer tick. */ + if (lacp_scratch_mask != lacp_members_last[lag]) { + lacp_members_last[lag] = lacp_scratch_mask; + port_lag_members_set(lag, lacp_scratch_mask); + } + } +} + + +static void lacp_step(void) +{ + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + if (lacp_port_lag[i] == LACP_LAG_NONE) /* not in a LACP LAG */ + continue; + + /* Periodic transmit machine (802.3ad 43.4.13) */ + if (lacp_periodic[i]) { + lacp_periodic[i]--; + } else { + lacp_periodic[i] = (lacp_partner_state[i] & LACP_STATE_TIMEOUT) + ? LACP_FAST_PERIODIC : LACP_SLOW_PERIODIC; + lacp_ntt[i] = 1; + } + + /* Partner timeout -> DEFAULTED (802.3ad 43.4.12): the mux machine + * then drops SYNC/COLLECTING/DISTRIBUTING and the port leaves the + * trunk. TODO(43.4.12): churn detection is diagnostics-only, skipped. */ + if (lacp_timeout[i]) { + if (!--lacp_timeout[i]) { + lacp_rx_state[i] = LACP_RX_DEFAULTED; + lacp_partner_state[i] = LACP_STATE_DEFAULTED; + if (lacp_mux_machine(i)) + lacp_ntt[i] = 1; + } + } + + if (lacp_ntt[i]) + lacp_send(i); + } + + /* Release each LAG's aggregator identity once none of its ports still + * hears the elected partner, so a re-cabled setup can elect a new one + * (43.4.14). A port that is CURRENT with some other system must not keep + * the old identity alive: it would never be selected, and neither would + * the rest of the LAG once it moves to that system too. */ + for (uint8_t lag = 0; lag < LACP_NUM_LAGS; lag++) { + if (!lacp_agg_valid[lag]) + continue; + uint8_t any_current = 0; + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + if (lacp_port_lag[i] == lag && lacp_rx_state[i] == LACP_RX_CURRENT + && lacp_sys_eq(lacp_partner_sys[i], lacp_agg_sys[lag])) { + any_current = 1; + break; + } + } + if (!any_current) + lacp_agg_valid[lag] = 0; + } + + lacp_mux_update(); +} + + +void lacp_timers(void) __banked +{ + uint8_t n = LACP_CATCH_UP; + while (n-- && TICKS_DUE(lacp_tick_last, LACP_TICK_STEP)) { + lacp_tick_last += LACP_TICK_STEP; + lacp_step(); + } +} + + +/* Bring one port up as an active-fast LACP participant (43.4.12 init). */ +static void lacp_port_init(uint8_t port) +{ + lacp_actor_state[port] = LACP_STATE_ACTIVITY | LACP_STATE_AGGREGATION + | LACP_STATE_TIMEOUT; /* active + fast */ + lacp_rx_state[port] = LACP_RX_INITIALIZE; + lacp_partner_state[port] = LACP_STATE_DEFAULTED; + memset(lacp_partner_sys[port], 0, 6); + lacp_partner_key[port] = 0; + lacp_partner_port[port] = 0; + lacp_periodic[port] = LACP_FAST_PERIODIC; + lacp_timeout[port] = 0; + lacp_ntt[port] = 1; /* announce ourselves immediately */ + lacp_rx_count[port] = 0; +} + +/* Return a port to non-LACP state. (Port isolation is no longer touched - a + * trapped LACPDU never enters the forwarding path, so a sibling cannot see + * it, which is what the isolation workaround was for.) */ +static void lacp_port_release(uint8_t port) +{ + lacp_actor_state[port] = 0; + lacp_ntt[port] = 0; + lacp_partner_state[port] = 0; + lacp_rx_state[port] = LACP_RX_INITIALIZE; + lacp_rx_count[port] = 0; + memset(lacp_partner_sys[port], 0, 6); +} + +/* True while any LAG is under LACP management (drives the global RMA trap). */ +static uint8_t lacp_any_lag(void) +{ + for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) + if (lacp_lag_ports[l]) + return 1; + return 0; +} + +/* + * Turn the LACP engine on: trap the Slow-Protocols group to the CPU port. The + * trap acts on the address before the VLAN lookup and before the ingress + * filters, so delivery needs no L2 entry and no PVID of its own. + */ +static void lacp_engine_on(void) +{ + lacp_tick_last = (uint8_t)ticks; + lacpEnabled = 1; + trap_rma_set(RTL837X_RMA2_CONF, RTL837X_RMA_ACT_TRAP); +} + +/* Turn the LACP engine off: stop delivering slow-protocols to the CPU. */ +static void lacp_engine_off(void) +{ + trap_rma_set(RTL837X_RMA2_CONF, RTL837X_RMA_ACT_DROP); + lacpEnabled = 0; +} + +/* One-time boot init: no LAG runs LACP yet, so every port maps to LACP_LAG_NONE. + * MUST run before any "lag ... lacp" config replay - xdata is not zeroed to the + * 0xff sentinel, and a stray 0 would make a port look like it belongs to LAG 0. */ +void lacp_init(void) __banked +{ + for (uint8_t i = 0; i < 10; i++) { + lacp_port_lag[i] = LACP_LAG_NONE; + lacp_actor_state[i] = 0; + lacp_ntt[i] = 0; + } + for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) { + lacp_lag_ports[l] = 0; + lacp_agg_valid[l] = 0; + lacp_members_last[l] = 0; + } + lacpEnabled = 0; +} + +/* + * Assign a candidate-port mask to LACP-mode LAG `lag` (`lag lacp `). + * ports == 0 removes the LAG from LACP management. Ports leaving the LAG are + * returned to normal switching; ports joining start the protocol immediately. + * The engine's RMA trap is enabled on the first LACP LAG and torn down with the + * last. Never wipes a static "lag" the user configured on a different group. + */ +void lacp_lag_set(uint8_t lag, uint16_t ports) __banked +{ + if (lag >= LACP_NUM_LAGS) + return; + if (ports && ports == lacp_lag_ports[lag]) + return; + lacp_scratch_flag = lacp_any_lag(); + + /* Drop ports that were in this LAG but are not in the new mask. */ + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + if (lacp_port_lag[i] == lag && !(ports & port_bit(i))) { + lacp_port_lag[i] = LACP_LAG_NONE; + lacp_port_release(i); + } + } + /* Release this LAG's trunk: LACP's own, or the static one a group put + * under LACP still has. Taking the group out (ports == 0) leaves a static + * trunk alone. */ + if (lacp_members_last[lag] || ports) { + lacp_members_last[lag] = 0; + port_lag_members_set(lag, 0); + } + lacp_agg_valid[lag] = 0; + lacp_lag_ports[lag] = ports; + + if (ports && !lacp_scratch_flag) /* first LACP LAG: bring the engine up first */ + lacp_engine_on(); + + /* Adopt the new member ports (with the engine already up). A port that + * belonged to another LACP LAG leaves that LAG's candidate set first. */ + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + if (!(ports & port_bit(i))) + continue; + lacp_scratch_lag = lacp_port_lag[i]; + if (lacp_scratch_lag != LACP_LAG_NONE && lacp_scratch_lag != lag) { + lacp_lag_ports[lacp_scratch_lag] &= ~port_bit(i); + if (!lacp_lag_ports[lacp_scratch_lag]) { + lacp_agg_valid[lacp_scratch_lag] = 0; + if (lacp_members_last[lacp_scratch_lag]) { + lacp_members_last[lacp_scratch_lag] = 0; + port_lag_members_set(lacp_scratch_lag, 0); + } + } + } + lacp_port_lag[i] = lag; + lacp_port_init(i); + } + + if (!lacp_any_lag() && lacp_scratch_flag) /* removed the last LACP LAG */ + lacp_engine_off(); + + lacp_mux_update(); /* a LAG that lost a port to this one drops it from its trunk */ +} + + +/* "lacp off": remove every LACP LAG (releases their trunks, stops the engine). + * There is no "lacp on": the engine comes up with the first "lag lacp". */ +void lacp_off(void) __banked +{ + for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) + if (lacp_lag_ports[l]) + lacp_lag_set(l, 0); +} + + +/* "lacp show": per-port protocol state - the primary bring-up diagnostic. + * A stuck rx=0 counter means slow-protocol frames never reach the CPU port + * (see the RMA note in lacp_engine_on()). */ +void lacp_show(void) __banked +{ + print_string("LACP "); print_string(lacpEnabled ? "on" : "off"); write_char('\n'); + for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) { + if (!lacp_lag_ports[l]) + continue; + print_string("lag "); print_byte(l + 1); + print_string(" ports "); print_short(lacp_lag_ports[l]); + print_string(" aggregator "); + if (lacp_agg_valid[l]) { + for (uint8_t j = 0; j < 6; j++) + print_byte(lacp_agg_sys[l][j]); + } else { + print_string("(none)"); + } + print_string(" members "); print_short(lacp_members_last[l]); write_char('\n'); + } + + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + print_string("port "); print_byte(machine.log_to_phys_port[i]); + print_string(" actor "); print_byte(lacp_actor_state[i]); + print_string(" partner "); print_byte(lacp_partner_state[i]); + print_string(" rxst "); print_byte(lacp_rx_state[i]); + print_string(" rx "); print_short(lacp_rx_count[i]); + print_string(" psys "); + for (uint8_t j = 0; j < 6; j++) + print_byte(lacp_partner_sys[i][j]); + write_char('\n'); + } +} diff --git a/rtl837x_lacp.h b/rtl837x_lacp.h new file mode 100644 index 00000000..5f03a16e --- /dev/null +++ b/rtl837x_lacp.h @@ -0,0 +1,101 @@ +#ifndef _RTL837X_LACP_H_ +#define _RTL837X_LACP_H_ + +/* + * LACP (IEEE 802.3ad Clause 43) for the RTL837x platform. + * This code is in the Public Domain. + */ + +#include + +void lacp_init(void) __banked; /* boot init: clear per-LAG state */ +void lacp_in(void) __banked; +void lacp_timers(void) __banked; +void lacp_off(void) __banked; +void lacp_show(void) __banked; /* "lacp show" - per-port state + RX counters */ +/* Assign a candidate-port mask to a LACP-mode LAG (`lag lacp `). + * ports == 0 removes the LAG from LACP management. Enables the engine on first + * LACP LAG and tears it down when the last one goes away. */ +void lacp_lag_set(uint8_t lag, uint16_t ports) __banked; + +/* + * Per-LAG LACP: each of the 4 hardware trunk groups (0-3) can independently run + * LACP on an admin-assigned set of candidate ports, forming its own aggregator + * (its own elected partner System). A port participates in LACP only if it has + * been assigned to a LACP-mode LAG (lacp_port_lag[port] != LACP_LAG_NONE). + * Ports of the same LAG that converge with a consistent partner join that LAG's + * hardware trunk. This is the port-channel model: `lag lacp `. + */ +#define LACP_NUM_LAGS 4 +#define LACP_LAG_NONE 0xff /* port not assigned to any LACP LAG */ + +/* Protocol state, exposed read-only for the web UI (page_impl.c send_lacp()) + * and the serial console. Owned by rtl837x_lacp.c / rtlplayground.c. */ +extern __xdata uint8_t lacpEnabled; +extern __xdata uint8_t lacp_actor_state[10]; +extern __xdata uint8_t lacp_partner_state[10]; +extern __xdata uint8_t lacp_rx_state[10]; +extern __xdata uint8_t lacp_partner_sys[10][6]; +extern __xdata uint16_t lacp_rx_count[10]; +extern __xdata uint8_t lacp_port_lag[10]; /* LAG (0-3) a port runs LACP on, or LACP_LAG_NONE */ +extern __xdata uint16_t lacp_lag_ports[LACP_NUM_LAGS]; /* admin candidate-port mask per LACP LAG (0 = LAG not LACP) */ +extern __xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][6]; /* elected partner System per LAG */ +extern __xdata uint8_t lacp_agg_valid[LACP_NUM_LAGS]; /* aggregator elected for this LAG */ +extern __xdata uint16_t lacp_members_last[LACP_NUM_LAGS];/* trunk members we last programmed per LAG */ + +/* Slow-Protocols / LACPDU identifiers (802.3ad 43.4) */ +#define SLOW_PROTO_ETHERTYPE 0x8809 +#define SLOW_PROTO_SUBTYPE_LACP 0x01 +#define LACP_VERSION 0x01 +#define LACP_DST5 0x02 /* 01:80:C2:00:00:02 last octet */ + +/* LACPDU TLV framing (802.3ad 43.4.2): type bytes + fixed TLV lengths. */ +#define LACP_TLV_ACTOR 0x01 +#define LACP_TLV_PARTNER 0x02 +#define LACP_TLV_COLLECTOR 0x03 +#define LACP_TLV_TERMINATOR 0x00 +#define LACP_TLV_LEN_INFO 0x14 /* Actor/Partner Information TLV length */ +#define LACP_TLV_LEN_COLLECTOR 0x10 /* Collector TLV length */ + +/* Our aggregation identity: System priority is a constant (Linux 802.3ad + * defaults to the same 0xffff); the default Actor port priority likewise. */ +#define LACP_SYS_PRIO 0xffff +#define LACP_DEF_PORT_PRIO 0x00ff + +/* Actor_State / Partner_State flag bits (802.3ad 43.4.2) */ +#define LACP_STATE_ACTIVITY 0x01 /* 1 = Active LACP (send periodically) */ +#define LACP_STATE_TIMEOUT 0x02 /* 1 = Short (fast) timeout */ +#define LACP_STATE_AGGREGATION 0x04 /* 1 = link is aggregatable */ +#define LACP_STATE_SYNC 0x08 /* 1 = in sync with partner */ +#define LACP_STATE_COLLECTING 0x10 +#define LACP_STATE_DISTRIBUTING 0x20 +#define LACP_STATE_DEFAULTED 0x40 /* partner info is administrative default*/ +#define LACP_STATE_EXPIRED 0x80 /* wire-format doc; unused by the simplified RX machine */ + +/* Fully-participating actor: in sync AND collecting AND distributing. */ +#define LACP_STATE_FULL (LACP_STATE_SYNC | LACP_STATE_COLLECTING | LACP_STATE_DISTRIBUTING) + +/* + * Timer units: one step of the LACP timers, taken every four system ticks, + * 50 per second. A long pass of the main loop is caught up on a few steps at + * a time, so a period is a range and the worst case is what matters. + * + * The transmit periods are not free to choose. Our own timeouts only decide + * how patient we are, but our TX period decides when the PARTNER gives up on + * us: 802.3ad pairs fast periodic 1 s with a 3 s short timeout, and slow + * periodic 30 s with a 90 s long timeout. A period whose worst case reaches + * the partner's threshold makes it expire us intermittently, which reads as + * an aggregate that works but never stops churning. */ +#define LACP_FAST_PERIODIC 0x0032 /* fast TX 1 s; partner expires at 3 s */ +#define LACP_SLOW_PERIODIC 0x05dc /* slow TX 30 s; partner expires at 90 s */ +#define LACP_SHORT_TIMEOUT 0x00c8 /* silent partner dropped after 4 s (802.3ad: 3 s) */ +#define LACP_LONG_TIMEOUT 0x1770 /* long-timeout variant, after 120 s (802.3ad: 90 s) */ + +/* Per-port LACP receive-machine state (802.3ad 43.4.12) */ +#define LACP_RX_INITIALIZE 0 +#define LACP_RX_PORT_DISABLED 1 /* numbering reserved (exported via /lacp.json "rs") */ +#define LACP_RX_EXPIRED 2 /* numbering reserved (exported via /lacp.json "rs") */ +#define LACP_RX_DEFAULTED 3 +#define LACP_RX_CURRENT 4 + +#endif diff --git a/rtl837x_regs.h b/rtl837x_regs.h index dc4b2dc1..1eb865a4 100644 --- a/rtl837x_regs.h +++ b/rtl837x_regs.h @@ -284,6 +284,10 @@ * Loop detection / STP */ #define RTL8373_RLDP_TIMER 0x1074 +/* Per reserved-multicast-address action. The registers are packed and + * consecutive by ENTRY rather than by the byte of the address: entry 0 is + * 01:80:C2:00:00:00 at 0x4ecc and entry 2 is ...:02 (Slow Protocols / LACP) + * at 0x4ed4, but entry 5 is ...:08, so the step of 4 counts entries. */ #define RTL837X_RMA0_CONF 0x4ecc #define RTL837X_RMA2_CONF 0x4ed4 #define RTL837X_RMA_ACT_MASK 0x30 diff --git a/rtlplayground.c b/rtlplayground.c index 3f7860dc..f61140a2 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -13,6 +13,7 @@ #include "rtl837x_port.h" #include "rtl837x_trap.h" #include "rtl837x_stp.h" +#include "rtl837x_lacp.h" #include "rtl837x_igmp.h" #include "rtl837x_leds.h" #include "rtl837x_bandwidth.h" @@ -133,6 +134,7 @@ __xdata bool stp_enabled; __xdata uint8_t igmpEnabled; __xdata bool lldp_enabled; __xdata char hostname[24]; /* device hostname, default set at boot, see rtl837x_common.h */ +__xdata uint8_t lacpEnabled; __code const uint16_t bit_mask[16] = { 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, @@ -1059,12 +1061,18 @@ void handle_rx(void) print_byte(uip_buf[3]); print_byte(uip_buf[4]); print_byte(uip_buf[5]); write_char('\n'); print_string(" MGMT-VLAN: "); print_short(management_vlan); write_char('\n'); #endif - if (stp_enabled && uip_buf[0] == 0x01 && uip_buf[1] == 0x80 && uip_buf[2] == 0xc2 // STP packet? - && uip_buf[3] == 0x00 && uip_buf[4] == 0x00 && uip_buf[5] == 0x00) { - stp_in(); - if (uip_len) { - print_string("STP TX\n"); - tcpip_output(); + if ((stp_enabled || lacpEnabled) && uip_buf[0] == 0x01 && uip_buf[1] == 0x80 + && uip_buf[2] == 0xc2 && uip_buf[3] == 0x00 && uip_buf[4] == 0x00) { // 01:80:C2:00:00:0x + if (stp_enabled && uip_buf[5] == 0x00) { // STP/RSTP BPDU + stp_in(); + if (uip_len) { + print_string("STP TX\n"); + tcpip_output(); + } + } else if (lacpEnabled && uip_buf[5] == 0x02) { // LACPDU (slow protocols) + lacp_in(); + if (uip_len) + tcpip_output(); } } else if (igmpEnabled && uip_buf[0] == 0x01 && uip_buf[1] == 0x00 && uip_buf[2] == 0x5e // IPv4-MC packet? && igmp_packet_handler()) { @@ -1290,6 +1298,9 @@ static void handle_tick(void) stp_timers(); } } + // If LACP enabled, drive its machines (own tick divider lives in the banked module) + if (lacpEnabled) + lacp_timers(); health_phase(HEALTH_PH_STP); lldp_tick(); health_phase(HEALTH_LLDP); @@ -1792,6 +1803,8 @@ void main(void) #endif stp_enabled = 0; stp_defaults(); /* 802.1D/w default config before any "stp ..." replay */ + lacpEnabled = 0; + lacp_init(); /* clear per-LAG state (port->LAG map = NONE) before any config replay */ nic_setup(); REG_SET(RTL837X_NIC_INT_MSK, NIC_INT_RXIE); trap_init(); diff --git a/test/Makefile b/test/Makefile index ae3884ac..e47cbf4e 100644 --- a/test/Makefile +++ b/test/Makefile @@ -59,15 +59,24 @@ $(BUILDDIR)/test_page_json: test_page_json.c $(TABLE_ENV) hw_mock.h | $(BUILDDIR $(BUILDDIR)/test_httpd_tx: test_httpd_tx.c $(SRCROOT)/httpd/httpd.c $(SRCROOT)/uip/uip.c | $(BUILDDIR) $(CC) $(CFLAGS) $(filter %.c,$^) -o $@ $(LDFLAGS) +# Sandboxes with a Makefile of their own; each fails the run when it fails. +SUBTESTS = lacp port_hash + run: $(TESTS) @fail=0; for t in $(TESTS); do \ echo "=== $$t ==="; \ $$t || fail=1; \ echo; \ done; \ + for d in $(SUBTESTS); do \ + echo "=== $$d ==="; \ + $(MAKE) -C $$d test || fail=1; \ + echo; \ + done; \ if [ $$fail -ne 0 ]; then echo "TESTS FAILED"; exit 1; else echo "ALL TESTS PASSED"; fi clean: rm -rf $(BUILDDIR) + @for d in $(SUBTESTS); do $(MAKE) -C $$d clean; done .PHONY: all run clean diff --git a/test/README.md b/test/README.md index 08ec0cef..4f040d5a 100644 --- a/test/README.md +++ b/test/README.md @@ -48,6 +48,8 @@ fires — so it drops straight into CI. | `test_port_tables` | `rtl837x_port.c` | VLAN entry layout and round trip, PVID register sharing, static multicast and management entries, per-port flush, trunk membership and hash seed | | `test_page_json` | `httpd/page_impl.c` + `rtl837x_port.c` | `/vlan.json`, `/vlanlist`, `/l2.json` (walk, wrap marker, paging inside `outbuf`), 64-bit counters in `/status.json` and `/counters.json` | | `test_httpd_tx` | `httpd/httpd.c` + `uip/uip.c` | a GET of a static file against a client that moves its receive window: ACK accounting, continuation chunks out of flash, retransmission | +| `lacp/` | `rtl837x_lacp.c` | state machines against a simulated 802.3ad partner: convergence, mis-cabling, expiry, re-election, two groups, LACPDU containment entries | +| `port_hash/` | `port_lag_members_set()` | a LAG seeds its own hash register | ## Adding a test for another module 1. Write `test_.c` with `main()` driving the module's entry points and diff --git a/test/env_tables.c b/test/env_tables.c index 41694b72..6a841a19 100644 --- a/test/env_tables.c +++ b/test/env_tables.c @@ -16,6 +16,7 @@ #include "syslog.h" #include "rtl837x_phy.h" #include "support.h" +#include "rtl837x_lacp.h" /* ---- console: everything lands in out_buf (support.c) ---- */ void print_byte(uint8_t v) @@ -124,3 +125,18 @@ uint8_t stp_prio = 0x80, stp_root_port = 0xff, stp_rstp = 1, stp_txhold = 6; uint8_t sfp_buf[16]; bool sfp_read_block(uint8_t slot, uint8_t reg, uint8_t len) { (void)slot; (void)reg; (void)len; return false; } void print_phys_port(uint8_t port) { itoa_short(machine.log_to_phys_port[port]); } + +uint8_t lacpEnabled; +uint8_t lacp_actor_state[10]; +uint8_t lacp_partner_state[10]; +uint8_t lacp_rx_state[10]; +uint8_t lacp_partner_sys[10][6]; +uint16_t lacp_rx_count[10]; +uint8_t lacp_port_lag[10] = { + LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, + LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, +}; +uint16_t lacp_lag_ports[LACP_NUM_LAGS]; +uint8_t lacp_agg_sys[LACP_NUM_LAGS][6]; +uint8_t lacp_agg_valid[LACP_NUM_LAGS]; +uint16_t lacp_members_last[LACP_NUM_LAGS]; diff --git a/test/lacp/.gitignore b/test/lacp/.gitignore new file mode 100644 index 00000000..567609b1 --- /dev/null +++ b/test/lacp/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/test/lacp/Makefile b/test/lacp/Makefile new file mode 100644 index 00000000..856f21e5 --- /dev/null +++ b/test/lacp/Makefile @@ -0,0 +1,31 @@ +# Host-side sandbox for the firmware LACP state machines. +# +# Compiles the UNMODIFIED ../../rtl837x_lacp.c against shim headers: SDCC +# storage-class keywords are blanked via -D, and the module copy lives in +# build/ so quote-includes resolve to shim/ (its own directory has no real +# platform headers) instead of the firmware tree. rtl837x_trap.h is copied +# alongside it for the same reason: TRAP_RX_BODY() is what delivery by trap +# rests on, so the test must use the real macro, not a shim restating it. +CC ?= cc +CFLAGS = -std=c11 -Wall -Wextra -Wno-unknown-pragmas \ + -D__xdata= -D__code= -D__banked= -D__reentrant= \ + -Ishim -I../.. + +all: test + +test: build/lacp_sim + ./build/lacp_sim + +verbose: build/lacp_sim + ./build/lacp_sim -v + +build/lacp_sim: lacp_sim.c ../../rtl837x_lacp.c ../../rtl837x_lacp.h ../../rtl837x_trap.h shim/*.h + mkdir -p build + cp ../../rtl837x_lacp.c build/lacp_under_test.c + cp ../../rtl837x_trap.h build/rtl837x_trap.h + $(CC) $(CFLAGS) -o $@ lacp_sim.c build/lacp_under_test.c + +clean: + rm -rf build + +.PHONY: all test verbose clean diff --git a/test/lacp/lacp_sim.c b/test/lacp/lacp_sim.c new file mode 100644 index 00000000..a833ac9f --- /dev/null +++ b/test/lacp/lacp_sim.c @@ -0,0 +1,464 @@ +/* + * Host-side sandbox for the RTL837x LACP implementation. + * + * Compiles the UNMODIFIED rtl837x_lacp.c against shim headers (see shim/) and + * drives it with synthetic LACPDUs from a simulated partner switch/bond. This + * exercises the real state machines that ship in the firmware image - only + * the platform below them (frame I/O, trunk registers, console) is mocked. + * + * Run: make -C test/lacp (exit code 0 = all scenarios pass) + */ +#include +#include +#include +#include + +#include "rtl837x_common.h" +#include "machine.h" +#include "uip.h" +#include "rtl837x_regs.h" +#include "rtl837x_lacp.h" + +/* ---------- platform mocks ---------- */ + +uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2]; +uint16_t uip_len; +struct uip_eth_addr uip_ethaddr = { .addr = {0x02,0x11,0x22,0x33,0x44,0x55} }; +const struct machine machine = { .min_port = 0, .max_port = 7, + .log_to_phys_port = {5, 1, 2, 3, 4, 6, 7, 8, 9} }; +struct machine_runtime machine_detected = { .isRTL8373 = 1 }; +uint8_t lacpEnabled; +uint16_t management_vlan; /* mocked; slow-protocol path zeroes it per-frame */ + +static int verbose = 0; +void print_string(char *s) { if (verbose) fputs(s, stdout); } +void print_byte(uint8_t b) { if (verbose) printf("%02x", b); } +void print_short(uint16_t v) { if (verbose) printf("%04x", v); } +void write_char(char c) { if (verbose) putchar(c); } + +/* Trunk programming mock: record last mask per LAG + call count. + * hw_members mirrors LAG 0 so the single-aggregator scenarios read naturally. */ +static uint16_t hw_members_lag[4]; +static int hw_set_calls; +#define hw_members hw_members_lag[0] +void port_lag_members_set(uint8_t lag, uint16_t members) +{ + if (lag < 4) + hw_members_lag[lag] = members; + hw_set_calls++; +} + +/* Reserved-multicast action mock: delivery of a LACPDU to the CPU rests on + * the action of the slow-protocols entry alone, so record what the engine + * sets. The real trap_rma_set() touches only the action field. */ +static uint16_t hw_rma_reg; +static uint8_t hw_rma_act; +static int hw_rma_calls; +void trap_rma_set(uint16_t reg, uint8_t act); +void trap_rma_set(uint16_t reg, uint8_t act) +{ + hw_rma_reg = reg; + hw_rma_act = act; + hw_rma_calls++; +} + +/* ---------- on-wire mirrors (independent re-statement of the layout) ---------- */ + +struct wire_info { + uint16_t sys_prio; + uint8_t sys[6]; + uint16_t key; + uint16_t port_prio; + uint16_t port; + uint8_t state; + uint8_t reserved[3]; +}; + +struct wire_pdu_out { /* what lacp_send() writes at uip_buf+12 */ + uint8_t dst[6]; + uint8_t src[6]; + struct rtl_tag rtl_tag; + uint16_t ethertype; + uint8_t subtype; + uint8_t version; + uint8_t tlv_actor, actor_len; + struct wire_info actor; + uint8_t tlv_partner, partner_len; + struct wire_info partner; + uint8_t tlv_collector, collector_len; + uint16_t collector_max_delay; + uint8_t collector_reserved[12]; + uint8_t tlv_terminator, terminator_len; + uint8_t terminator_reserved[50]; +}; + +struct wire_pdu_in { /* forwarded form: the hardware inserts a VLAN tag */ + uint8_t dst[6]; + uint8_t src[6]; + struct rtl_tag rtl_tag; + struct vlan_tag vlan_tag; + uint16_t ethertype; + uint8_t subtype; + uint8_t version; + uint8_t tlv_actor, actor_len; + struct wire_info actor; + uint8_t tlv_partner, partner_len; + struct wire_info partner; +}; + +_Static_assert(sizeof(struct wire_info) == 18, "info TLV body must be 18"); +_Static_assert(sizeof(struct wire_pdu_out) == 132, "TX pdu must be 132"); +_Static_assert(sizeof(struct rtl_tag) == 8, "rtl tag must be 8"); + +/* ---------- TX capture ---------- */ + +static struct wire_pdu_out last_tx[10]; +static int tx_count[10]; + +void tcpip_output(void) +{ + struct wire_pdu_out *p = (struct wire_pdu_out *)&uip_buf[RTL_FRAME_DESC_SIZE]; + uint16_t pmask = HTONS(p->rtl_tag.pmask); + for (int port = 0; port < 10; port++) { + if (pmask & (1u << port)) { + last_tx[port] = *p; + tx_count[port]++; + } + } + uip_len = 0; +} + +/* ---------- simulated partner ---------- */ + +/* Deliver a LACPDU into the switch on `port`, from partner system `sys`. + * pstate = partner's Actor_State flags. If `echo`, the partner echoes the + * switch's last-seen actor block back (fresh view) - required for quiescence. */ +static void partner_frame_full(uint8_t port, const uint8_t sys[6], uint8_t pstate, + int echo, uint16_t len, int tagged) +{ + struct wire_pdu_in in; + memset(&in, 0, sizeof(in)); + + in.dst[0]=0x01; in.dst[1]=0x80; in.dst[2]=0xc2; in.dst[5]=0x02; + memcpy(in.src, sys, 6); + in.rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID); + in.rtl_tag.pmask = HTONS(port); /* RX: 4-bit ingress port */ + in.vlan_tag.svlan = HTONS(0x8100); /* the tag's TPID, which marks the body offset */ + in.ethertype = HTONS(0x8809); + in.subtype = 0x01; + in.version = 0x01; + + in.tlv_actor = 0x01; in.actor_len = 0x14; + in.actor.sys_prio = HTONS(0x8000); + memcpy(in.actor.sys, sys, 6); + in.actor.key = HTONS(0x0011); + in.actor.port_prio = HTONS(0x00ff); + in.actor.port = HTONS((uint16_t)port + 101); + in.actor.state = pstate; + + in.tlv_partner = 0x02; in.partner_len = 0x14; + if (echo) { /* fresh view of us, from our last TX */ + in.partner.sys_prio = last_tx[port].actor.sys_prio; + memcpy(in.partner.sys, last_tx[port].actor.sys, 6); + in.partner.key = last_tx[port].actor.key; + in.partner.port = last_tx[port].actor.port; + in.partner.state = last_tx[port].actor.state; + } /* else: zeros = stale view */ + + memcpy(uip_buf, &in, sizeof(in)); + if (!tagged) { /* trapped: no VLAN tag, so the body sits earlier */ + uint8_t *body = uip_buf + RTL_FRAME_DESC_SIZE + RTL_TAG_SIZE; + memmove(body, body + VLAN_TAG_SIZE, + sizeof(in) - (RTL_FRAME_DESC_SIZE + RTL_TAG_SIZE + VLAN_TAG_SIZE)); + len -= VLAN_TAG_SIZE; + } + uip_len = len; + lacp_in(); +} + +static void partner_frame_len(uint8_t port, const uint8_t sys[6], uint8_t pstate, int echo, uint16_t len) +{ + partner_frame_full(port, sys, pstate, echo, len, 1); +} + +static void partner_frame(uint8_t port, const uint8_t sys[6], uint8_t pstate, int echo) +{ + partner_frame_full(port, sys, pstate, echo, sizeof(struct wire_pdu_in), 1); +} + +/* The same LACPDU as the reserved-multicast trap delivers it. */ +static void partner_frame_trap(uint8_t port, const uint8_t sys[6], uint8_t pstate, int echo) +{ + partner_frame_full(port, sys, pstate, echo, sizeof(struct wire_pdu_in), 0); +} + +/* Advance time by n system ticks; the LACP timers step every fourth one */ +volatile uint32_t ticks; +static void step(int n) { while (n--) { ticks++; lacp_timers(); } } + +/* ---------- scenario runner ---------- */ + +static int failures; +#define CHECK(cond, name) do { \ + if (cond) printf("PASS %s\n", name); \ + else { printf("FAIL %s\n", name); failures++; } \ +} while (0) + +#define P_ACT LACP_STATE_ACTIVITY +#define P_AGG LACP_STATE_AGGREGATION +#define P_SYNC LACP_STATE_SYNC +#define P_COL LACP_STATE_COLLECTING +#define P_DIST LACP_STATE_DISTRIBUTING +#define P_TO LACP_STATE_TIMEOUT /* partner requests short (fast) timeout */ + +static const uint8_t SYS_A[6] = {0x02,0xaa,0xaa,0xaa,0xaa,0x01}; +static const uint8_t SYS_B[6] = {0x02,0xbb,0xbb,0xbb,0xbb,0x02}; + +int main(int argc, char **argv) +{ + verbose = (argc > 1 && !strcmp(argv[1], "-v")); + + /* Boot init first: on host, zeroed globals would otherwise read as "every + * port belongs to LAG 0" (the firmware calls this from main() the same way) */ + lacp_init(); + + /* T1: enable announces on every port with sane field contents */ + lacp_lag_set(0, 0x00ff); + step(8); + int all_tx = 1, sane = 1; + for (int i = machine.min_port; i <= machine.max_port; i++) { + if (!tx_count[i]) all_tx = 0; + if (last_tx[i].ethertype != HTONS(0x8809) || last_tx[i].subtype != 1 + || last_tx[i].dst[5] != 0x02 + || !(last_tx[i].actor.state & (P_ACT|P_AGG|P_TO))) + sane = 0; + } + CHECK(all_tx && sane, "T1 enable: LACPDU announced on all ports, well-formed"); + + /* T2: partner without SYNC -> actor SYNC only, no members yet */ + partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO, 1); + partner_frame(1, SYS_A, P_ACT|P_AGG|P_TO, 1); + step(8); + CHECK((lacp_actor_state[0] & P_SYNC) && !(lacp_actor_state[0] & (P_COL|P_DIST)) + && hw_members == 0, + "T2 partner not in sync: actor SYNC only, trunk empty"); + + /* T3: partner in sync on ports 0,1 -> full converge, members 0x0003 */ + partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + partner_frame(1, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(8); + CHECK(hw_members == 0x0003 + && (lacp_actor_state[0] & (P_SYNC|P_COL|P_DIST)) == (P_SYNC|P_COL|P_DIST) + && (lacp_actor_state[1] & (P_SYNC|P_COL|P_DIST)) == (P_SYNC|P_COL|P_DIST), + "T3 convergence: both ports collecting/distributing, trunk 0x0003"); + + /* T3b: partner-block echo must be VERBATIM. A Linux 802.3ad partner accepts + * our SYNC only if our Partner TLV mirrors its actor identity exactly; a + * hardcoded sys_prio/port_prio (instead of the recorded value) makes Linux + * clear partner-SYNC and the bond never distributes. Verify we echo the + * partner's own priorities back, not a constant. */ + CHECK(HTONS(last_tx[0].partner.sys_prio) == 0x8000 + && HTONS(last_tx[0].partner.port_prio) == 0x00ff + && HTONS(last_tx[0].partner.key) == 0x0011 + && HTONS(last_tx[0].partner.port) == (uint16_t)0 + 101, + "T3b partner echo: recorded partner priorities/key/port echoed verbatim"); + + /* T4: mis-cabling - port 2 sees a DIFFERENT system: must stay out */ + partner_frame(2, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(8); + CHECK(hw_members == 0x0003 && !(lacp_actor_state[2] & P_SYNC), + "T4 mis-cabling: port with different partner system stays out"); + + /* T5: register-write economy: stable state must not rewrite the trunk */ + int calls_before = hw_set_calls; + partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + partner_frame(1, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(64); + CHECK(hw_set_calls == calls_before, + "T5 stable state: no redundant trunk register writes"); + + /* T6: stale echo triggers retransmit (update_NTT) */ + int txc = tx_count[0]; + partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 0 /* stale view of us */); + step(8); + CHECK(tx_count[0] > txc, "T6 update_NTT: stale partner view causes retransmit"); + + /* T7: expiry - partner silent past the short timeout: trunk drains, + * aggregator identity is released */ + step(4 * LACP_SHORT_TIMEOUT + 64); /* past the short timeout */ + CHECK(hw_members == 0 && lacp_agg_valid[0] == 0, + "T7 expiry: members drop to 0 and aggregator is released"); + + /* T8: re-convergence with a NEW partner system after release */ + /* (needs a fresh announce first so the echo below carries current state) */ + step(4 * 0x0100 + 8); /* let periodic TX refresh last_tx */ + partner_frame(3, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1); + partner_frame(3, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(8); + CHECK(hw_members == 0x0008 && (lacp_actor_state[3] & P_COL), + "T8 re-election: new partner system forms a fresh aggregate"); + + /* T9: lacp off clears the trunk */ + lacp_off(); + CHECK(hw_members == 0 && lacpEnabled == 0, "T9 disable: trunk cleared"); + + /* ---- per-LAG scenarios: two independent aggregators ---- */ + + /* T10: lag 1 lacp {0,1} + lag 2 lacp {2,3}, both to the SAME partner + * system (worst case for sibling scoping): each converges into its own + * hardware trunk, and the engine reports enabled. */ + lacp_lag_set(1, 0x0003); /* ports 0,1 */ + lacp_lag_set(2, 0x000c); /* ports 2,3 */ + step(8); /* announce */ + for (int r = 0; r < 2; r++) { /* two rounds so echoes carry fresh state */ + for (int p = 0; p < 4; p++) + partner_frame(p, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(8); + } + CHECK(lacpEnabled == 1 + && hw_members_lag[1] == 0x0003 && hw_members_lag[2] == 0x000c, + "T10 two LAGs: independent aggregates converge on their own trunks"); + + /* T11: removing one LAG releases only its trunk; the other keeps running */ + lacp_lag_set(1, 0); + CHECK(hw_members_lag[1] == 0 && hw_members_lag[2] == 0x000c + && lacpEnabled == 1, + "T11 remove one LAG: its trunk drains, the other LAG unaffected"); + + /* T12: a port in no LACP LAG ignores LACPDUs entirely */ + int rx5_before = tx_count[5]; + partner_frame(5, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(8); + CHECK(!(lacp_actor_state[5] & P_SYNC) && hw_members_lag[2] == 0x000c, + "T12 unassigned port: LACPDU ignored, no state change"); + (void)rx5_before; + + /* T13: removing the last LACP LAG shuts the engine down */ + lacp_lag_set(2, 0); + CHECK(lacpEnabled == 0 && hw_members_lag[2] == 0 + && hw_rma_reg == RTL837X_RMA2_CONF && hw_rma_act == RTL837X_RMA_ACT_DROP, + "T13 last LAG removed: engine off, trunk drained, address dropped"); + + /* T14: while the engine runs, the slow-protocols address is trapped to + * the CPU. The trap keys on the address alone, so delivery needs no L2 + * entry and nothing written per VLAN or per PVID. */ + hw_rma_calls = 0; + lacp_lag_set(0, 0x0003); + CHECK(hw_rma_calls == 1 && hw_rma_reg == RTL837X_RMA2_CONF + && hw_rma_act == RTL837X_RMA_ACT_TRAP, + "T14 engine on traps 01:80:c2:00:00:02 to the CPU"); + + /* T15: a trapped LACPDU arrives without the VLAN tag a forwarded one + * carries, so its body sits four bytes earlier. Both forms must parse. */ + uint16_t rx_before = lacp_rx_count[0]; + lacp_partner_state[0] = 0; /* all three are written past the ethertype gate */ + memset(lacp_partner_sys[0], 0, 6); + partner_frame_trap(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + int trap_ok = lacp_rx_count[0] == (uint16_t)(rx_before + 1) + && lacp_partner_state[0] == (P_ACT|P_AGG|P_TO|P_SYNC) + && !memcmp(lacp_partner_sys[0], SYS_A, 6); + partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + CHECK(trap_ok && lacp_rx_count[0] == (uint16_t)(rx_before + 2), + "T15 trapped LACPDU parses like a forwarded one"); + + /* T16: the partner changes one cable at a time. Port 1 starts hearing B + * while port 0 still hears A, then A goes quiet and B appears on port 0 + * too. The aggregator must let go of A once no port hears it, even though + * port 1 was CURRENT the whole time, or the LAG never aggregates with B. */ + lacp_lag_set(0, 0); + lacp_lag_set(0, 0x0003); + step(8); + for (int r = 0; r < 3; r++) { + partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + partner_frame(1, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(8); + } + for (int t = 0; t < 40; t++) { /* port 1 moved to B, port 0 still on A */ + partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + partner_frame(1, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(4 * 40); + } + int stayed_out = hw_members == 0x0001 && !(lacp_actor_state[1] & P_SYNC); + for (int t = 0; t < 60; t++) /* port 0 unplugged from A, past the short timeout */ + { partner_frame(1, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1); step(4 * 40); } + for (int t = 0; t < 40; t++) { /* port 0 now on B as well */ + partner_frame(0, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1); + partner_frame(1, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(4 * 40); + } + CHECK(stayed_out && hw_members == 0x0003 && !memcmp(lacp_agg_sys[0], SYS_B, 6), + "T16 re-cabling one port at a time: aggregator moves from A to B"); + + /* T17: a group that already has a STATIC trunk is put under LACP with the + * same ports (the LAG page's Static -> LACP switch sends only "lag N lacp"). + * The static trunk must go: until the partner converges the ports are not + * an aggregate, and after a reboot the config replays only the LACP line. */ + hw_members_lag[1] = 0x0030; /* static lag 2: ports 4,5 */ + lacp_lag_set(1, 0x0030); + step(8); + CHECK(hw_members_lag[1] == 0 && lacp_members_last[1] == 0 && lacp_lag_ports[1] == 0x0030, + "T17 static trunk taken over: cleared while the group negotiates"); + lacp_lag_set(1, 0); + + /* T18: a port named in a second group moves there. The first group's + * candidate set drops it, so taking the second group out later leaves no + * orphan that group 1 still shows as a candidate but never runs LACP on. */ + lacp_lag_set(1, 0x0030); /* ports 4,5 */ + step(8); + for (int r = 0; r < 2; r++) { + partner_frame(4, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + partner_frame(5, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(8); + } + int converged = hw_members_lag[1] == 0x0030; + lacp_lag_set(2, 0x0060); /* ports 5,6: port 5 moves from group 2 to 3 */ + int moved = lacp_lag_ports[1] == 0x0010 && lacp_port_lag[5] == 2 + && hw_members_lag[1] == 0x0010; + lacp_lag_set(2, 0); + int no_orphan = lacp_port_lag[5] == LACP_LAG_NONE && lacp_lag_ports[1] == 0x0010; + lacp_lag_set(3, 0x0010); /* the last port leaves group 2 as well */ + CHECK(converged && moved && no_orphan && lacp_lag_ports[1] == 0 && lacp_agg_valid[1] == 0, + "T18 port moves between groups: old group drops it from candidates and trunk"); + lacp_lag_set(3, 0); + + /* T20: taking a group out of LACP leaves nothing of the partner behind + * for "lacp show" to report as if the port still heard it. */ + lacp_lag_set(1, 0x0030); + step(8); + partner_frame(4, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(8); + int had_partner = lacp_rx_state[4] == LACP_RX_CURRENT && lacp_rx_count[4] > 0; + lacp_lag_set(1, 0); + CHECK(had_partner && lacp_partner_state[4] == 0 && lacp_rx_state[4] == LACP_RX_INITIALIZE + && lacp_rx_count[4] == 0 && !memcmp(lacp_partner_sys[4], "\0\0\0\0\0\0", 6), + "T20 release: partner state, rx state, counter and system cleared"); + + /* T21: applying the same candidate set again (the LAG page resends it + * with every Apply) must not tear the trunk down and renegotiate. */ + lacp_lag_set(1, 0x0030); + step(8); + for (int r = 0; r < 2; r++) { + partner_frame(4, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + partner_frame(5, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(8); + } + int before = hw_set_calls; + lacp_lag_set(1, 0x0030); + CHECK(hw_members_lag[1] == 0x0030 && hw_set_calls == before + && (lacp_actor_state[4] & P_COL), + "T21 same ports applied again: trunk and state untouched"); + lacp_lag_set(1, 0); + + /* T22: a runt on the slow-protocols address must not be parsed as a + * LACPDU; the partner block would be read from stale buffer bytes. */ + lacp_lag_set(1, 0x0030); + step(8); + partner_frame_len(4, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1, 40); /* cut before the partner block */ + CHECK(lacp_rx_count[4] == 0 && lacp_rx_state[4] == LACP_RX_INITIALIZE, + "T22 truncated LACPDU ignored: the port does not record it"); + lacp_lag_set(1, 0); + + printf("\n%s (%d failure%s)\n", failures ? "SANDBOX: FAILURES" : "SANDBOX: ALL PASS", + failures, failures == 1 ? "" : "s"); + return failures ? 1 : 0; +} diff --git a/test/lacp/shim/machine.h b/test/lacp/shim/machine.h new file mode 100644 index 00000000..7f6612fb --- /dev/null +++ b/test/lacp/shim/machine.h @@ -0,0 +1,12 @@ +/* Host-build shim: rtl837x_lacp.c reads min_port/max_port and, for the + * console dump, the front-panel numbering. The real map is not the identity + * on any machine, so the harness uses a shuffled one to keep a logical index + * from passing as a physical port by accident. */ +#ifndef _SHIM_MACHINE_H_ +#define _SHIM_MACHINE_H_ +#include +struct machine { uint8_t min_port; uint8_t max_port; uint8_t log_to_phys_port[9]; }; +extern const struct machine machine; +struct machine_runtime { uint8_t isRTL8373; }; +extern struct machine_runtime machine_detected; +#endif diff --git a/test/lacp/shim/rtl837x_common.h b/test/lacp/shim/rtl837x_common.h new file mode 100644 index 00000000..b21c7141 --- /dev/null +++ b/test/lacp/shim/rtl837x_common.h @@ -0,0 +1,63 @@ +/* + * Host-build shim for rtl837x_common.h - just enough for rtl837x_lacp.c. + * Layouts of rtl_tag/vlan_tag and the frame-desc size mirror the real header; + * the harness static-asserts the resulting on-wire struct sizes. + */ +#ifndef _SHIM_RTL837X_COMMON_H_ +#define _SHIM_RTL837X_COMMON_H_ + +#include +#include /* memcpy/memset - firmware provides its own */ + +struct rtl_tag { + uint16_t tag; + uint8_t version; + uint8_t reason; + uint16_t flags; + uint16_t pmask; /* TX: port bitmask; RX: 4-bit port number */ +}; + +struct vlan_tag { + uint16_t svlan; + uint16_t vlan; +}; + +#define RTL_TAG_SIZE (sizeof (struct rtl_tag)) +#define VLAN_TAG_SIZE (sizeof (struct vlan_tag)) +#define RTL_FRAME_TAG_ID 0x8899 +#define RTL_FRAME_TAG_VERSION 0x04 +#define RTL_FRAME_DESC_SIZE 12 +#define ETHER_HEADER_SIZE 14 + +#define HTONS(n) ((uint16_t)((((uint16_t)(n)) << 8) | (((uint16_t)(n)) >> 8))) + +/* Console output - provided by the harness */ +void print_string(char *s); +void print_byte(uint8_t b); +void print_short(uint16_t v); +void write_char(char c); + +#endif + +/* Host-build shim: port masks used by lacp_isolation_update() */ +#ifndef PMASK_CPU +#define CPU_PORT 9 +#define PMASK_9 0x1ff +#define PMASK_6 0x1f8 +#define PMASK_CPU 0x200 +#endif + +/* Host-build shim: directed-egress/strip-tag bit (bit15 of TX pmask word) */ +#ifndef RTL_TAG_ALLOW +#define RTL_TAG_ALLOW 0x8000 +#endif + +/* Host-build shim: tag flags word bits (tag_rtl8_4) */ +#ifndef RTL_TAG_LEARN_DIS +#define RTL_TAG_LEARN_DIS 0x0020 +#define RTL_TAG_KEEP 0x0080 +#define SYS_TICK_HZ 200 +extern volatile uint32_t ticks; +#define TICKS_DUE(last, period) ((uint8_t)((uint8_t)ticks - (last)) >= (period)) + +#endif diff --git a/test/lacp/shim/rtl837x_port.h b/test/lacp/shim/rtl837x_port.h new file mode 100644 index 00000000..646d5253 --- /dev/null +++ b/test/lacp/shim/rtl837x_port.h @@ -0,0 +1,7 @@ +/* Host-build shim: the harness mocks trunk programming and records calls */ +#ifndef _SHIM_RTL837X_PORT_H_ +#define _SHIM_RTL837X_PORT_H_ +#include +void port_lag_members_set(uint8_t lag, uint16_t members); +void port_isolate(uint8_t port, uint16_t pmask); +#endif diff --git a/test/lacp/shim/rtl837x_regs.h b/test/lacp/shim/rtl837x_regs.h new file mode 100644 index 00000000..2dc92d8d --- /dev/null +++ b/test/lacp/shim/rtl837x_regs.h @@ -0,0 +1,19 @@ +/* + * Host-build shim for rtl837x_regs.h - the register numbers rtl837x_lacp.c + * touches, with the values from the real header so a wrong constant shows up + * as a failing expectation rather than as a silent no-op. + */ +#ifndef _SHIM_RTL837X_REGS_H_ +#define _SHIM_RTL837X_REGS_H_ + +#include + +#define RTL837X_RMA2_CONF 0x4ed4 +#define RTL837X_RMA_ACT_TRAP 0x10 +#define RTL837X_RMA_ACT_DROP 0x20 + +/* Registers touched by lacp_send's CPU-port targeting */ +#define RTL837X_PORT_ISOLATION_BASE 0x50c0 +#define RTL837X_REG_NIC_TXCMD 0x7850 + +#endif diff --git a/test/lacp/shim/uip.h b/test/lacp/shim/uip.h new file mode 100644 index 00000000..0d5a51ec --- /dev/null +++ b/test/lacp/shim/uip.h @@ -0,0 +1,9 @@ +/* Host-build shim for uip.h - frame buffer + TX hook used by rtl837x_lacp.c */ +#ifndef _SHIM_UIP_H_ +#define _SHIM_UIP_H_ +#include +#define UIP_CONF_BUFFER_SIZE 600 +struct uip_eth_addr { uint8_t addr[6]; }; +extern uint16_t uip_len; +void tcpip_output(void); +#endif diff --git a/test/port_hash/.gitignore b/test/port_hash/.gitignore new file mode 100644 index 00000000..cf8f788c --- /dev/null +++ b/test/port_hash/.gitignore @@ -0,0 +1,2 @@ +test_lag_hash +port_lag_members_set.inc diff --git a/test/port_hash/Makefile b/test/port_hash/Makefile new file mode 100644 index 00000000..feeee940 --- /dev/null +++ b/test/port_hash/Makefile @@ -0,0 +1,6 @@ +test: port_lag_members_set.inc + gcc -Wall -Wextra -O1 -o test_lag_hash test_lag_hash.c && ./test_lag_hash +port_lag_members_set.inc: ../../rtl837x_port.c + awk '/^void port_lag_members_set/,/^}/' $< > $@ +clean: + rm -f test_lag_hash port_lag_members_set.inc diff --git a/test/port_hash/test_lag_hash.c b/test/port_hash/test_lag_hash.c new file mode 100644 index 00000000..205d3fc8 --- /dev/null +++ b/test/port_hash/test_lag_hash.c @@ -0,0 +1,68 @@ +/* Host test for port_lag_members_set(): the default load-balancing hash must be + * seeded into the CURRENT trunk's register (BASE + lag*4), never trunk 0's, and + * must not overwrite a hash that is already set. Compiles the REAL function + * (extracted verbatim from rtl837x_port.c) against register-recording shims. */ +#include +#include +#include + +#define __banked +#define __xdata +#define __code + +/* register defs (mirror rtl837x_regs.h) */ +#define RTL837X_TRK_MBR_CTRL_BASE 0x4f38 +#define RTL837X_TRK_HASH_CTRL_BASE 0x4f48 +#define LAG_HASH_SOURCE_PORT_NUMBER 0x01 +#define LAG_HASH_L2_SMAC 0x02 +#define LAG_HASH_L2_DMAC 0x04 +#define LAG_HASH_L3_SIP 0x08 +#define LAG_HASH_L3_DIP 0x10 +#define LAG_HASH_L4_SPORT 0x20 +#define LAG_HASH_L4_DPORT 0x40 +#define LAG_HASH_DEFAULT (LAG_HASH_L2_SMAC|LAG_HASH_L2_DMAC|LAG_HASH_L3_SIP|LAG_HASH_L3_DIP|LAG_HASH_L4_SPORT|LAG_HASH_L4_DPORT) +#define LAG_HASH_RESET (LAG_HASH_SOURCE_PORT_NUMBER|LAG_HASH_L2_SMAC|LAG_HASH_L2_DMAC|LAG_HASH_L3_SIP|LAG_HASH_L3_DIP|LAG_HASH_L4_SPORT) + +uint8_t SFR_DATA_24, SFR_DATA_16, SFR_DATA_8, SFR_DATA_0; +uint8_t sfr_data[4]; +static uint32_t reg_store[0x10000]; + +void reg_read_m(uint16_t r){ uint32_t v=reg_store[r]; + sfr_data[0]=(v>>24)&0xff; sfr_data[1]=(v>>16)&0xff; sfr_data[2]=(v>>8)&0xff; sfr_data[3]=v&0xff; } +void reg_write(uint16_t r){ reg_store[r]=((uint32_t)SFR_DATA_24<<24)|((uint32_t)SFR_DATA_16<<16)|((uint32_t)SFR_DATA_8<<8)|SFR_DATA_0; } +void print_string(char*s){(void)s;} void print_byte(uint8_t b){(void)b;} +void print_short(uint16_t s){(void)s;} void write_char(char c){(void)c;} + +#define REG_SET(r,v) do{ SFR_DATA_24=((uint32_t)(v)>>24)&0xff; SFR_DATA_16=((uint32_t)(v)>>16)&0xff; \ + SFR_DATA_8=((uint32_t)(v)>>8)&0xff; SFR_DATA_0=(v)&0xff; reg_write(r); }while(0) +#define REG_WRITE(r,a,b,c,d) do{ SFR_DATA_24=(a); SFR_DATA_16=(b); SFR_DATA_8=(c); SFR_DATA_0=(d); reg_write(r); }while(0) + +/* THE REAL FUNCTION (extracted from rtl837x_port.c at build time) */ +#include "port_lag_members_set.inc" + +static int fails; +#define CK(cond,msg,...) do{ if(cond){printf("PASS " msg "\n",##__VA_ARGS__);} else {printf("FAIL " msg "\n",##__VA_ARGS__); fails++;} }while(0) + +int main(void){ + for(int lag=0; lag<4; lag++){ + memset(reg_store,0,sizeof(reg_store)); + port_lag_members_set(lag, 0x0180); + uint16_t h = RTL837X_TRK_HASH_CTRL_BASE + (lag<<2); + uint16_t m = RTL837X_TRK_MBR_CTRL_BASE + (lag<<2); + CK(reg_store[h]==LAG_HASH_DEFAULT, "lag%d: default 0x%02x -> hash reg 0x%04x", lag, LAG_HASH_DEFAULT, h); + CK(reg_store[m]==0x0180, "lag%d: members 0x0180 -> mbr reg 0x%04x", lag, m); + if(lag) CK(reg_store[RTL837X_TRK_HASH_CTRL_BASE]==0, "lag%d: trunk-0 hash NOT clobbered", lag); + } + /* guard: existing hash must survive a later member change */ + memset(reg_store,0,sizeof(reg_store)); + reg_store[RTL837X_TRK_HASH_CTRL_BASE+(1<<2)] = 0x06; /* lag1 already L2 */ + port_lag_members_set(1, 0x00c0); + CK(reg_store[RTL837X_TRK_HASH_CTRL_BASE+(1<<2)]==0x06, "guard: preset lag1 hash 0x06 preserved"); + /* the chip's reset value counts as unset and is replaced by the default */ + memset(reg_store,0,sizeof(reg_store)); + reg_store[RTL837X_TRK_HASH_CTRL_BASE+(2<<2)] = LAG_HASH_RESET; + port_lag_members_set(2, 0x0003); + CK(reg_store[RTL837X_TRK_HASH_CTRL_BASE+(2<<2)]==LAG_HASH_DEFAULT, "reset value 0x%02x replaced by default on lag2", LAG_HASH_RESET); + printf(fails? "\n=== FAIL (%d) ===\n" : "\n=== ALL PASS ===\n", fails); + return fails?1:0; +} diff --git a/test/test_httpd_tx.c b/test/test_httpd_tx.c index 4df05472..3f056030 100644 --- a/test/test_httpd_tx.c +++ b/test/test_httpd_tx.c @@ -148,6 +148,7 @@ void send_mirror(void) { } void send_mtu(void) { } void send_config(void) { } void send_cmd_log(void) { } +void send_lacp(void) { } void send_lag(void) { } void send_stp(void) { } void send_stp_counters(void) { } From edf6351d85281f0dc79a7d7bba87e6bf46f1e781 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 16:27:19 +0200 Subject: [PATCH 10/84] lacp: arm the partner timeout from our own LACP_Timeout The receive machine armed current_while from the partner's TIMEOUT bit, so against a partner on the slow rate a port whose LACPDUs stopped with the link still up stayed in the trunk for the long timeout, two minutes. 802.3ad 43.4.12 starts the timer from the actor's own LACP_Timeout, and we always advertise the short one: the partner sends every second and is let go after the short timeout. Sandbox T23 plays a slow-rate partner that goes quiet and fails on the code before this. --- rtl837x_lacp.c | 3 +-- test/lacp/lacp_sim.c | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/rtl837x_lacp.c b/rtl837x_lacp.c index b4874f16..4f0fa44d 100644 --- a/rtl837x_lacp.c +++ b/rtl837x_lacp.c @@ -311,8 +311,7 @@ void lacp_in(void) __banked /* Receive machine -> CURRENT, (re)arm partner timeout (43.4.12) */ lacp_rx_state[port] = LACP_RX_CURRENT; - lacp_timeout[port] = (LACP_I->actor.state & LACP_STATE_TIMEOUT) - ? LACP_SHORT_TIMEOUT : LACP_LONG_TIMEOUT; + lacp_timeout[port] = LACP_SHORT_TIMEOUT; /* Elect this LAG's aggregator partner system on first contact (43.4.14) */ if (!lacp_agg_valid[lag]) { diff --git a/test/lacp/lacp_sim.c b/test/lacp/lacp_sim.c index a833ac9f..7efa67c6 100644 --- a/test/lacp/lacp_sim.c +++ b/test/lacp/lacp_sim.c @@ -458,6 +458,23 @@ int main(int argc, char **argv) "T22 truncated LACPDU ignored: the port does not record it"); lacp_lag_set(1, 0); + /* T23: the partner asks for the long timeout (Linux's default slow rate). + * current_while is armed from OUR LACP_Timeout, which is short, so a + * partner that stops speaking LACP with the link up is dropped after the + * short timeout, not after two minutes. */ + lacp_lag_set(1, 0x0030); + step(8); + for (int r = 0; r < 2; r++) { + partner_frame(4, SYS_A, P_ACT|P_AGG|P_SYNC, 1); + partner_frame(5, SYS_A, P_ACT|P_AGG|P_SYNC, 1); + step(8); + } + int slow_converged = hw_members_lag[1] == 0x0030; + step(4 * LACP_SHORT_TIMEOUT + 64); + CHECK(slow_converged && hw_members_lag[1] == 0, + "T23 partner on slow rate goes quiet: dropped after our short timeout"); + lacp_lag_set(1, 0); + printf("\n%s (%d failure%s)\n", failures ? "SANDBOX: FAILURES" : "SANDBOX: ALL PASS", failures, failures == 1 ? "" : "s"); return failures ? 1 : 0; From 25040070e5ef6777c30bf14befe0425e487133e8 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 16:27:20 +0200 Subject: [PATCH 11/84] lacp: "lag d" and a static member list take the group out of LACP Both paths wrote the trunk registers directly and left the protocol running on the group, so "lag show" said disabled while the partner kept both links in one aggregate, and re-sending the LACP line did not repair it because the same-ports shortcut returns early. Each path now releases the group from LACP first; for a group that never ran LACP that is a no-op. --- cmd_parser.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd_parser.c b/cmd_parser.c index 951d6be5..09621fc3 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -534,6 +534,7 @@ void parse_lag(void) goto err; if (cmd_compare(2, "d")) { + lacp_lag_set(group, 0); port_lag_members_set(group, 0); return; } @@ -557,10 +558,12 @@ void parse_lag(void) members |= ((uint16_t)1) << atoi_results_u8; } - if (lacp_mode) + if (lacp_mode) { lacp_lag_set(group, members); /* 0 ports also valid: removes the LAG */ - else + } else { + lacp_lag_set(group, 0); port_lag_members_set(group, members); + } return; err: cmd_error("lag (show | <1-4> (d | [lacp] ...))\n"); From 0df48fbb1cd47fda7971fa93ebd19751fec93cf5 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 16:27:21 +0200 Subject: [PATCH 12/84] httpd: leave the L2 listing as it is on main Listing by next-address also returned the reserved-multicast entries STP writes, shown as port 1 with a delete button, while l2_delete() still looks up by next-unicast and so removed the unicast entry after that index instead. The change was there to show the LACP steering entries; delivery by trap writes none, so it goes. --- httpd/page_impl.c | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/httpd/page_impl.c b/httpd/page_impl.c index 44c9ddcb..94ccb483 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -409,19 +409,14 @@ void send_l2(uint16_t idx) */ __xdata uint16_t entry = idx & 0xfff; __xdata uint16_t first_entry = 0xffff; // An illegal entry index - __xdata uint16_t mbr; __bit first = true; char_to_html('['); while (1) { entries_left--; uint8_t port = 0; uint8_t lag; - uint8_t mc = 0; reg_read_m(RTL837x_TBL_DATA_0); - // Iterate with "next address" (method 2) so static multicast entries - - // an IGMP group entry, say - are listed too, not only learned or - // static unicast ones. - REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1] & 0xfc, sfr_data[2] | (TBL_LUTREAD_NEXT_ADDRESS << 6), sfr_data[3]); + REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1] & 0xfc, sfr_data[2] | (TBL_LUTREAD_NEXT_L2UC << 6), sfr_data[3]); REG_WRITE(RTL837X_TBL_CTRL, entry >> 8, entry, TBL_L2_UNICAST, TBL_EXECUTE); do { @@ -429,7 +424,7 @@ void send_l2(uint16_t idx) } while (sfr_data[3] & TBL_EXECUTE); reg_read_m(RTL837x_L2_DATA_OUT_B); - __bit valid = (sfr_data[0] & 0x20) && !(sfr_data[0] & 0x10); + __bit valid = (sfr_data[0] & 0x20) != 0; if (valid) { /* separator + 74-byte worst-case entry + closing "]" */ if (slen + 76 > TCP_OUTBUF_SIZE) @@ -438,8 +433,6 @@ void send_l2(uint16_t idx) char_to_html(','); first = false; - mc = sfr_data[2] & 0x01; // multicast MAC: member mask, no SPA/age - // VLAN, taken from the read above instead of reading the register twice slen += strtox(outbuf + slen, "{\"vlan\":\""); charhex_to_html(sfr_data[0] & 0x0f); @@ -458,23 +451,12 @@ void send_l2(uint16_t idx) // type reg_read_m(RTL837x_L2_DATA_OUT_C); - if (mc || (sfr_data[1] & 0x1)) + if (sfr_data[1] & 0x1) slen += strtox(outbuf + slen, "\",\"type\":\"s\",\"port\":"); else slen += strtox(outbuf + slen, "\",\"type\":\"l\",\"port\":"); - if (mc) { - // Report the lowest member of the mask (an entry holding only - // the CPU port is rendered as "CPU" by the UI) - mbr = port | ((uint16_t)sfr_data[3] << 2); - port = 0; - while (mbr && !(mbr & 1)) { - mbr >>= 1; - port++; - } - } else { - port |= (sfr_data[3] & 0x3) << 2; - } + port |= (sfr_data[3] & 0x3) << 2; itoa_html(port); slen += strtox(outbuf + slen, ",\"lag\":"); lag = port_lag_of(port); From 4ccbf5197613b903e39e84849eec12f1d0e7c529 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 16:27:22 +0200 Subject: [PATCH 13/84] lacp: ignore a LACPDU that carries our own system id Two ports of one group cabled to each other heard themselves and the group aggregated with itself. Sandbox T24. --- rtl837x_lacp.c | 2 ++ test/lacp/lacp_sim.c | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/rtl837x_lacp.c b/rtl837x_lacp.c index 4f0fa44d..e2ede9e3 100644 --- a/rtl837x_lacp.c +++ b/rtl837x_lacp.c @@ -292,6 +292,8 @@ void lacp_in(void) __banked uint8_t lag = lacp_port_lag[port]; if (lag == LACP_LAG_NONE) return; + if (lacp_sys_eq(LACP_I->actor.sys, uip_ethaddr.addr)) + return; #ifdef DEBUG diff --git a/test/lacp/lacp_sim.c b/test/lacp/lacp_sim.c index 7efa67c6..13d571cd 100644 --- a/test/lacp/lacp_sim.c +++ b/test/lacp/lacp_sim.c @@ -475,6 +475,15 @@ int main(int argc, char **argv) "T23 partner on slow rate goes quiet: dropped after our short timeout"); lacp_lag_set(1, 0); + /* T24: a LACPDU carrying our own system id (two ports of one group + * cabled to each other) must not be taken for a partner. */ + lacp_lag_set(1, 0x0030); + step(8); + partner_frame(4, uip_ethaddr.addr, P_ACT|P_AGG|P_TO|P_SYNC, 1); + CHECK(lacp_rx_count[4] == 0 && lacp_rx_state[4] == LACP_RX_INITIALIZE, + "T24 own system id: LACPDU ignored"); + lacp_lag_set(1, 0); + printf("\n%s (%d failure%s)\n", failures ? "SANDBOX: FAILURES" : "SANDBOX: ALL PASS", failures, failures == 1 ? "" : "s"); return failures ? 1 : 0; From 4e4a2ea4db2cedb7595371f232efdf86b112e205 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 16:27:22 +0200 Subject: [PATCH 14/84] lacp: review follow-ups lacp_in() always leaves uip_len at 0, so the tcpip_output() after it never ran. The LAG page names the active members by front-panel port instead of a logical mask. The partner key is not part of selection; say so under Known limits. --- doc/lacp.md | 2 ++ html/app.js | 3 ++- rtlplayground.c | 2 -- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/lacp.md b/doc/lacp.md index 1b78459e..3160469a 100644 --- a/doc/lacp.md +++ b/doc/lacp.md @@ -228,4 +228,6 @@ the dead member. * No Churn Detection machines and no Marker protocol. * Coupled Mux: collecting and distributing are enabled together. * One aggregator per group. +* Selection compares the partner's system only, not its key: two aggregates + that one partner system offers on the same group land in one trunk here. * Member compatibility is not validated; see #377. diff --git a/html/app.js b/html/app.js index 4c7ed7f5..34e9713e 100644 --- a/html/app.js +++ b/html/app.js @@ -1843,6 +1843,7 @@ function buildLag(){ })(g); } var lacpCfg=[0,0,0,0,0]; +function maskPorts(m){var r=[];for(var p=1;p<=S.n;p++)if((m>>S.physToLog[p-1])&1)r.push(p);return r.length?r.join(" "):"-"} function lagLoad(){ return getJSON("/lacp.json").then(function(c){ var tb=$("lacptbl").tBodies[0]; @@ -1852,7 +1853,7 @@ function lagLoad(){ lacpCfg[g]=parseInt(lg.cfg,16); if($("lgm"+g).closest(".card").dataset.dirty)return; $("lgm"+g).value=lacpCfg[g]?"lacp":"static"; - $("lgs"+g).textContent=lacpCfg[g]?"LACP: "+t("lacp_agg")+" "+(lg.aggValid?lg.agg:"("+t("lacp_neg")+")")+", "+t("lacp_members")+" 0x"+lg.members:""; + $("lgs"+g).textContent=lacpCfg[g]?"LACP: "+t("lacp_agg")+" "+(lg.aggValid?lg.agg:"("+t("lacp_neg")+")")+", "+t("lacp_members")+" "+maskPorts(parseInt(lg.members,16)):""; }); if(c.on)c.ports.forEach(function(p){ if(p.lag===255)return; diff --git a/rtlplayground.c b/rtlplayground.c index f61140a2..38e322e7 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -1071,8 +1071,6 @@ void handle_rx(void) } } else if (lacpEnabled && uip_buf[5] == 0x02) { // LACPDU (slow protocols) lacp_in(); - if (uip_len) - tcpip_output(); } } else if (igmpEnabled && uip_buf[0] == 0x01 && uip_buf[1] == 0x00 && uip_buf[2] == 0x5e // IPv4-MC packet? && igmp_packet_handler()) { From 2e6425f5e63510a3401fd0d8fb80741ee511397b Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 21:21:07 +0200 Subject: [PATCH 15/84] lacp: keep lacp_in() and lacp_lag_set() out of the data segment The port and group of lacp_in(), the member mask passed to lacp_lag_set() and the compiler temporaries both functions spilled took six bytes of DSEG, and with them six bytes of stack. The locals and the mask live in xdata now, the partner fields go through the scratch word, and lacp_lag_set() records the new group of a port before it releases the old one. One byte is left: the group number passed in. --- rtl837x_lacp.c | 25 +++++++++++++++---------- rtl837x_lacp.h | 3 +-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/rtl837x_lacp.c b/rtl837x_lacp.c index e2ede9e3..f03ef8ef 100644 --- a/rtl837x_lacp.c +++ b/rtl837x_lacp.c @@ -284,12 +284,12 @@ void lacp_in(void) __banked /* Per rtl837x_common.h, pmask carries a 4-bit port number on RX * (hardware-verified on RTL8373: per-port rx counters track the actual * ingress port through full LACP convergence). */ - uint8_t port = ((uint8_t)HTONS(LACP_H->rtl_tag.pmask)) & 0x0f; + __xdata uint8_t port = ((uint8_t)HTONS(LACP_H->rtl_tag.pmask)) & 0x0f; if (port < machine.min_port || port > machine.max_port) return; /* Only ports assigned to a LACP-mode LAG participate in the protocol. */ - uint8_t lag = lacp_port_lag[port]; + __xdata uint8_t lag = lacp_port_lag[port]; if (lag == LACP_LAG_NONE) return; if (lacp_sys_eq(LACP_I->actor.sys, uip_ethaddr.addr)) @@ -305,10 +305,14 @@ void lacp_in(void) __banked /* Record partner = the remote's Actor block (802.3ad 43.4.9 recordPDU) */ memcpy(lacp_partner_sys[port], LACP_I->actor.sys, 6); - lacp_partner_key[port] = HTONS(LACP_I->actor.key); - lacp_partner_port[port] = HTONS(LACP_I->actor.port); - lacp_partner_sys_prio[port] = HTONS(LACP_I->actor.sys_prio); - lacp_partner_port_prio[port] = HTONS(LACP_I->actor.port_prio); + lacp_scratch_mask = HTONS(LACP_I->actor.key); + lacp_partner_key[port] = lacp_scratch_mask; + lacp_scratch_mask = HTONS(LACP_I->actor.port); + lacp_partner_port[port] = lacp_scratch_mask; + lacp_scratch_mask = HTONS(LACP_I->actor.sys_prio); + lacp_partner_sys_prio[port] = lacp_scratch_mask; + lacp_scratch_mask = HTONS(LACP_I->actor.port_prio); + lacp_partner_port_prio[port] = lacp_scratch_mask; lacp_partner_state[port] = LACP_I->actor.state; /* Receive machine -> CURRENT, (re)arm partner timeout (43.4.12) */ @@ -508,7 +512,7 @@ void lacp_init(void) __banked * The engine's RMA trap is enabled on the first LACP LAG and torn down with the * last. Never wipes a static "lag" the user configured on a different group. */ -void lacp_lag_set(uint8_t lag, uint16_t ports) __banked +void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked { if (lag >= LACP_NUM_LAGS) return; @@ -542,9 +546,11 @@ void lacp_lag_set(uint8_t lag, uint16_t ports) __banked if (!(ports & port_bit(i))) continue; lacp_scratch_lag = lacp_port_lag[i]; + lacp_port_lag[i] = lag; if (lacp_scratch_lag != LACP_LAG_NONE && lacp_scratch_lag != lag) { - lacp_lag_ports[lacp_scratch_lag] &= ~port_bit(i); - if (!lacp_lag_ports[lacp_scratch_lag]) { + lacp_scratch_mask = lacp_lag_ports[lacp_scratch_lag] & ~port_bit(i); + lacp_lag_ports[lacp_scratch_lag] = lacp_scratch_mask; + if (!lacp_scratch_mask) { lacp_agg_valid[lacp_scratch_lag] = 0; if (lacp_members_last[lacp_scratch_lag]) { lacp_members_last[lacp_scratch_lag] = 0; @@ -552,7 +558,6 @@ void lacp_lag_set(uint8_t lag, uint16_t ports) __banked } } } - lacp_port_lag[i] = lag; lacp_port_init(i); } diff --git a/rtl837x_lacp.h b/rtl837x_lacp.h index 5f03a16e..514f9e35 100644 --- a/rtl837x_lacp.h +++ b/rtl837x_lacp.h @@ -16,7 +16,7 @@ void lacp_show(void) __banked; /* "lacp show" - per-port state + RX counters */ /* Assign a candidate-port mask to a LACP-mode LAG (`lag lacp `). * ports == 0 removes the LAG from LACP management. Enables the engine on first * LACP LAG and tears it down when the last one goes away. */ -void lacp_lag_set(uint8_t lag, uint16_t ports) __banked; +void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked; /* * Per-LAG LACP: each of the 4 hardware trunk groups (0-3) can independently run @@ -89,7 +89,6 @@ extern __xdata uint16_t lacp_members_last[LACP_NUM_LAGS];/* trunk members we las #define LACP_FAST_PERIODIC 0x0032 /* fast TX 1 s; partner expires at 3 s */ #define LACP_SLOW_PERIODIC 0x05dc /* slow TX 30 s; partner expires at 90 s */ #define LACP_SHORT_TIMEOUT 0x00c8 /* silent partner dropped after 4 s (802.3ad: 3 s) */ -#define LACP_LONG_TIMEOUT 0x1770 /* long-timeout variant, after 120 s (802.3ad: 90 s) */ /* Per-port LACP receive-machine state (802.3ad 43.4.12) */ #define LACP_RX_INITIALIZE 0 From 64efd3fb9fc29bc2f34d54ddbd321f8a2c784324 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 21:21:07 +0200 Subject: [PATCH 16/84] lacp: drop the unused long timeout and an unrelated pvid hunk --- cmd_parser.c | 3 +-- doc/lacp.md | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/cmd_parser.c b/cmd_parser.c index 09621fc3..3cfb9007 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -2000,9 +2000,8 @@ void cmd_parser(void) __banked } else if (cmd_compare(0, "pvid")) { if (cmd_words_len == 3 && cmd_parse_port_separator(cmd_words_b[1]) != 0 && atoi_short(cmd_words_b[2]) && atoi_results_short && atoi_results_short <= 4094) - { port_pvid_set(atoi_results_u8, atoi_results_short); - } else { + else { cmd_error("pvid <1-4094>\n"); } } else if (cmd_compare(0, "vlan")) { diff --git a/doc/lacp.md b/doc/lacp.md index 3160469a..390261da 100644 --- a/doc/lacp.md +++ b/doc/lacp.md @@ -96,7 +96,6 @@ mismatch should be rejected or applied to the whole group. #define LACP_FAST_PERIODIC 0x0032 /* fast TX 1 s; partner expires at 3 s */ #define LACP_SLOW_PERIODIC 0x05dc /* slow TX 30 s; partner expires at 90 s */ #define LACP_SHORT_TIMEOUT 0x00c8 /* silent partner dropped after 4 s (802.3ad: 3 s) */ -#define LACP_LONG_TIMEOUT 0x1770 /* long-timeout variant, after 120 s (802.3ad: 90 s) */ ``` One unit is one step of the LACP timers, taken every four system ticks, so 50 From 265a80bb255c9c5a3c333a6a0b6d6abc5471291a Mon Sep 17 00:00:00 2001 From: Rob Emery Date: Mon, 12 Oct 2026 20:32:29 +0100 Subject: [PATCH 17/84] Trapping the 01:80:c2:00:00:0e prevents the switch from passing between ports --- cmd_parser.c | 3 +++ rtl837x_regs.h | 1 + 2 files changed, 4 insertions(+) diff --git a/cmd_parser.c b/cmd_parser.c index e7daa9e8..202fd923 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -18,6 +18,7 @@ #include "rtl837x_bandwidth.h" #include "rtl837x_lldp.h" #include "rtl837x_storm.h" +#include "rtl837x_trap.h" #include "sfp.h" #include "dhcp.h" #include "syslog.h" @@ -2043,9 +2044,11 @@ void cmd_parser(void) __banked if (cmd_compare(1, "on")) { print_string("LLDP enabled\n"); lldp_enabled = 1; + trap_rma_set(RTL837X_RMA0_CONF_0E, RTL837X_RMA_ACT_TRAP); } else { print_string("LLDP disabled\n"); lldp_enabled = 0; + trap_rma_set(RTL837X_RMA0_CONF_0E, RTL837X_RMA_ACT_FORWARD); } } else { diff --git a/rtl837x_regs.h b/rtl837x_regs.h index dc4b2dc1..7730e85f 100644 --- a/rtl837x_regs.h +++ b/rtl837x_regs.h @@ -285,6 +285,7 @@ */ #define RTL8373_RLDP_TIMER 0x1074 #define RTL837X_RMA0_CONF 0x4ecc +#define RTL837X_RMA0_CONF_0E 0x4ee8 #define RTL837X_RMA2_CONF 0x4ed4 #define RTL837X_RMA_ACT_MASK 0x30 #define RTL837X_RMA_ACT_FORWARD 0x00 From 1bfe63217400fcc960676c7cb33f9f3e6096fa15 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 21:59:08 +0200 Subject: [PATCH 18/84] doc: lacp: lag d and a static list leave LACP, new lacp_lag_set() prototype --- doc/lacp.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/doc/lacp.md b/doc/lacp.md index 390261da..ab09db39 100644 --- a/doc/lacp.md +++ b/doc/lacp.md @@ -129,7 +129,7 @@ longer keeps hashing traffic onto a member that no longer agrees to carry it. ``` void lacp_init(void) __banked; /* boot init: clear per-LAG state */ void lacp_off(void) __banked; -void lacp_lag_set(uint8_t lag, uint16_t ports) __banked; +void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked; void lacp_in(void) __banked; void lacp_timers(void) __banked; void lacp_show(void) __banked; @@ -145,19 +145,20 @@ A group is put under LACP by adding `lacp` to the `lag` command: ``` lag 1 lacp 7 8 # ports 7 and 8 become LACP candidates of group 1 -lag 1 lacp off # group 1 goes back to static (so does "lag 1 lacp") +lag 1 lacp off # group 1 leaves LACP, its trunk is cleared (so does "lag 1 lacp") +lag 1 d # group 1 disabled, whether it ran LACP or not lag 1 7 8 # plain static aggregation, no protocol ``` A group put under LACP gives up the static trunk it had: the hardware trunk is -cleared and rebuilt from the ports that converge. A port named in another LACP -group moves to the new one. The protocol starts with the first group put under -LACP and stops when the last one is taken out; there is no separate switch for -it. Two commands act on -the whole engine: +cleared and rebuilt from the ports that converge. The other way round, `lag 1 d` +and a static member list take the group out of LACP before they touch the +trunk. A port named in another LACP group moves to the new one. The protocol +starts with the first group put under LACP and stops when the last one is taken +out; there is no separate switch for it. Two commands act on the whole engine: ``` -lacp off # every group back to static, protocol stopped +lacp off # every group leaves LACP, protocol stopped lacp show ``` From 4a14f5093227be17995e54463499fd0b9b94b106 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 22:02:50 +0200 Subject: [PATCH 19/84] test: build the LACP test on the real headers, drop the port_hash test The LACP test now compiles with the shared harness flags and sdcc_shim.h against the firmware headers, so the copies under test/lacp/shim are gone. test_port_tables already covers the trunk hash seed that test/port_hash checked against its own register copies. lacp_in() declares its locals at the top, which the stricter host build needs. --- rtl837x_lacp.c | 6 ++- test/Makefile | 16 +++---- test/README.md | 3 +- test/lacp/.gitignore | 1 - test/lacp/Makefile | 31 ------------ test/lacp/shim/machine.h | 12 ----- test/lacp/shim/rtl837x_common.h | 63 ------------------------- test/lacp/shim/rtl837x_port.h | 7 --- test/lacp/shim/rtl837x_regs.h | 19 -------- test/lacp/shim/uip.h | 9 ---- test/port_hash/.gitignore | 2 - test/port_hash/Makefile | 6 --- test/port_hash/test_lag_hash.c | 68 --------------------------- test/{lacp/lacp_sim.c => test_lacp.c} | 12 +---- 14 files changed, 12 insertions(+), 243 deletions(-) delete mode 100644 test/lacp/.gitignore delete mode 100644 test/lacp/Makefile delete mode 100644 test/lacp/shim/machine.h delete mode 100644 test/lacp/shim/rtl837x_common.h delete mode 100644 test/lacp/shim/rtl837x_port.h delete mode 100644 test/lacp/shim/rtl837x_regs.h delete mode 100644 test/lacp/shim/uip.h delete mode 100644 test/port_hash/.gitignore delete mode 100644 test/port_hash/Makefile delete mode 100644 test/port_hash/test_lag_hash.c rename test/{lacp/lacp_sim.c => test_lacp.c} (97%) diff --git a/rtl837x_lacp.c b/rtl837x_lacp.c index f03ef8ef..276e80d5 100644 --- a/rtl837x_lacp.c +++ b/rtl837x_lacp.c @@ -270,6 +270,8 @@ void lacp_send(uint8_t port) __banked */ void lacp_in(void) __banked { + __xdata uint8_t port, lag; + lacp_rxbody = TRAP_RX_BODY(); if (uip_len < lacp_rxbody + sizeof(struct lacpdu_in)) { uip_len = 0; @@ -284,12 +286,12 @@ void lacp_in(void) __banked /* Per rtl837x_common.h, pmask carries a 4-bit port number on RX * (hardware-verified on RTL8373: per-port rx counters track the actual * ingress port through full LACP convergence). */ - __xdata uint8_t port = ((uint8_t)HTONS(LACP_H->rtl_tag.pmask)) & 0x0f; + port = ((uint8_t)HTONS(LACP_H->rtl_tag.pmask)) & 0x0f; if (port < machine.min_port || port > machine.max_port) return; /* Only ports assigned to a LACP-mode LAG participate in the protocol. */ - __xdata uint8_t lag = lacp_port_lag[port]; + lag = lacp_port_lag[port]; if (lag == LACP_LAG_NONE) return; if (lacp_sys_eq(LACP_I->actor.sys, uip_ethaddr.addr)) diff --git a/test/Makefile b/test/Makefile index e47cbf4e..cdb27355 100644 --- a/test/Makefile +++ b/test/Makefile @@ -28,7 +28,8 @@ TESTS = $(BUILDDIR)/test_cmd_editor \ $(BUILDDIR)/test_port_tables \ $(BUILDDIR)/test_page_json \ $(BUILDDIR)/test_httpd_tx \ - $(BUILDDIR)/test_tick_gate + $(BUILDDIR)/test_tick_gate \ + $(BUILDDIR)/test_lacp # The table tests link the register mock and one environment file; stub/ # stands in for the two headers a firmware build generates. @@ -54,29 +55,24 @@ $(BUILDDIR)/test_tick_gate: test_tick_gate.c support.c $(SRCROOT)/cmd_editor.c | $(BUILDDIR)/test_page_json: test_page_json.c $(TABLE_ENV) hw_mock.h | $(BUILDDIR) $(CC) $(CFLAGS) $(filter %.c,$^) -o $@ $(LDFLAGS) +# rtl837x_lacp.c is compiled UNMODIFIED from the firmware tree. +$(BUILDDIR)/test_lacp: test_lacp.c $(SRCROOT)/rtl837x_lacp.c | $(BUILDDIR) + $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) + # httpd/httpd.c and uip/uip.c are compiled UNMODIFIED from the firmware tree; # this test carries the environment below them itself. $(BUILDDIR)/test_httpd_tx: test_httpd_tx.c $(SRCROOT)/httpd/httpd.c $(SRCROOT)/uip/uip.c | $(BUILDDIR) $(CC) $(CFLAGS) $(filter %.c,$^) -o $@ $(LDFLAGS) -# Sandboxes with a Makefile of their own; each fails the run when it fails. -SUBTESTS = lacp port_hash - run: $(TESTS) @fail=0; for t in $(TESTS); do \ echo "=== $$t ==="; \ $$t || fail=1; \ echo; \ done; \ - for d in $(SUBTESTS); do \ - echo "=== $$d ==="; \ - $(MAKE) -C $$d test || fail=1; \ - echo; \ - done; \ if [ $$fail -ne 0 ]; then echo "TESTS FAILED"; exit 1; else echo "ALL TESTS PASSED"; fi clean: rm -rf $(BUILDDIR) - @for d in $(SUBTESTS); do $(MAKE) -C $$d clean; done .PHONY: all run clean diff --git a/test/README.md b/test/README.md index 4f040d5a..a79b3242 100644 --- a/test/README.md +++ b/test/README.md @@ -48,8 +48,7 @@ fires — so it drops straight into CI. | `test_port_tables` | `rtl837x_port.c` | VLAN entry layout and round trip, PVID register sharing, static multicast and management entries, per-port flush, trunk membership and hash seed | | `test_page_json` | `httpd/page_impl.c` + `rtl837x_port.c` | `/vlan.json`, `/vlanlist`, `/l2.json` (walk, wrap marker, paging inside `outbuf`), 64-bit counters in `/status.json` and `/counters.json` | | `test_httpd_tx` | `httpd/httpd.c` + `uip/uip.c` | a GET of a static file against a client that moves its receive window: ACK accounting, continuation chunks out of flash, retransmission | -| `lacp/` | `rtl837x_lacp.c` | state machines against a simulated 802.3ad partner: convergence, mis-cabling, expiry, re-election, two groups, LACPDU containment entries | -| `port_hash/` | `port_lag_members_set()` | a LAG seeds its own hash register | +| `test_lacp` | `rtl837x_lacp.c` | state machines against a simulated 802.3ad partner: convergence, mis-cabling, expiry, re-election, two groups, delivery by trap | ## Adding a test for another module 1. Write `test_.c` with `main()` driving the module's entry points and diff --git a/test/lacp/.gitignore b/test/lacp/.gitignore deleted file mode 100644 index 567609b1..00000000 --- a/test/lacp/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build/ diff --git a/test/lacp/Makefile b/test/lacp/Makefile deleted file mode 100644 index 856f21e5..00000000 --- a/test/lacp/Makefile +++ /dev/null @@ -1,31 +0,0 @@ -# Host-side sandbox for the firmware LACP state machines. -# -# Compiles the UNMODIFIED ../../rtl837x_lacp.c against shim headers: SDCC -# storage-class keywords are blanked via -D, and the module copy lives in -# build/ so quote-includes resolve to shim/ (its own directory has no real -# platform headers) instead of the firmware tree. rtl837x_trap.h is copied -# alongside it for the same reason: TRAP_RX_BODY() is what delivery by trap -# rests on, so the test must use the real macro, not a shim restating it. -CC ?= cc -CFLAGS = -std=c11 -Wall -Wextra -Wno-unknown-pragmas \ - -D__xdata= -D__code= -D__banked= -D__reentrant= \ - -Ishim -I../.. - -all: test - -test: build/lacp_sim - ./build/lacp_sim - -verbose: build/lacp_sim - ./build/lacp_sim -v - -build/lacp_sim: lacp_sim.c ../../rtl837x_lacp.c ../../rtl837x_lacp.h ../../rtl837x_trap.h shim/*.h - mkdir -p build - cp ../../rtl837x_lacp.c build/lacp_under_test.c - cp ../../rtl837x_trap.h build/rtl837x_trap.h - $(CC) $(CFLAGS) -o $@ lacp_sim.c build/lacp_under_test.c - -clean: - rm -rf build - -.PHONY: all test verbose clean diff --git a/test/lacp/shim/machine.h b/test/lacp/shim/machine.h deleted file mode 100644 index 7f6612fb..00000000 --- a/test/lacp/shim/machine.h +++ /dev/null @@ -1,12 +0,0 @@ -/* Host-build shim: rtl837x_lacp.c reads min_port/max_port and, for the - * console dump, the front-panel numbering. The real map is not the identity - * on any machine, so the harness uses a shuffled one to keep a logical index - * from passing as a physical port by accident. */ -#ifndef _SHIM_MACHINE_H_ -#define _SHIM_MACHINE_H_ -#include -struct machine { uint8_t min_port; uint8_t max_port; uint8_t log_to_phys_port[9]; }; -extern const struct machine machine; -struct machine_runtime { uint8_t isRTL8373; }; -extern struct machine_runtime machine_detected; -#endif diff --git a/test/lacp/shim/rtl837x_common.h b/test/lacp/shim/rtl837x_common.h deleted file mode 100644 index b21c7141..00000000 --- a/test/lacp/shim/rtl837x_common.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Host-build shim for rtl837x_common.h - just enough for rtl837x_lacp.c. - * Layouts of rtl_tag/vlan_tag and the frame-desc size mirror the real header; - * the harness static-asserts the resulting on-wire struct sizes. - */ -#ifndef _SHIM_RTL837X_COMMON_H_ -#define _SHIM_RTL837X_COMMON_H_ - -#include -#include /* memcpy/memset - firmware provides its own */ - -struct rtl_tag { - uint16_t tag; - uint8_t version; - uint8_t reason; - uint16_t flags; - uint16_t pmask; /* TX: port bitmask; RX: 4-bit port number */ -}; - -struct vlan_tag { - uint16_t svlan; - uint16_t vlan; -}; - -#define RTL_TAG_SIZE (sizeof (struct rtl_tag)) -#define VLAN_TAG_SIZE (sizeof (struct vlan_tag)) -#define RTL_FRAME_TAG_ID 0x8899 -#define RTL_FRAME_TAG_VERSION 0x04 -#define RTL_FRAME_DESC_SIZE 12 -#define ETHER_HEADER_SIZE 14 - -#define HTONS(n) ((uint16_t)((((uint16_t)(n)) << 8) | (((uint16_t)(n)) >> 8))) - -/* Console output - provided by the harness */ -void print_string(char *s); -void print_byte(uint8_t b); -void print_short(uint16_t v); -void write_char(char c); - -#endif - -/* Host-build shim: port masks used by lacp_isolation_update() */ -#ifndef PMASK_CPU -#define CPU_PORT 9 -#define PMASK_9 0x1ff -#define PMASK_6 0x1f8 -#define PMASK_CPU 0x200 -#endif - -/* Host-build shim: directed-egress/strip-tag bit (bit15 of TX pmask word) */ -#ifndef RTL_TAG_ALLOW -#define RTL_TAG_ALLOW 0x8000 -#endif - -/* Host-build shim: tag flags word bits (tag_rtl8_4) */ -#ifndef RTL_TAG_LEARN_DIS -#define RTL_TAG_LEARN_DIS 0x0020 -#define RTL_TAG_KEEP 0x0080 -#define SYS_TICK_HZ 200 -extern volatile uint32_t ticks; -#define TICKS_DUE(last, period) ((uint8_t)((uint8_t)ticks - (last)) >= (period)) - -#endif diff --git a/test/lacp/shim/rtl837x_port.h b/test/lacp/shim/rtl837x_port.h deleted file mode 100644 index 646d5253..00000000 --- a/test/lacp/shim/rtl837x_port.h +++ /dev/null @@ -1,7 +0,0 @@ -/* Host-build shim: the harness mocks trunk programming and records calls */ -#ifndef _SHIM_RTL837X_PORT_H_ -#define _SHIM_RTL837X_PORT_H_ -#include -void port_lag_members_set(uint8_t lag, uint16_t members); -void port_isolate(uint8_t port, uint16_t pmask); -#endif diff --git a/test/lacp/shim/rtl837x_regs.h b/test/lacp/shim/rtl837x_regs.h deleted file mode 100644 index 2dc92d8d..00000000 --- a/test/lacp/shim/rtl837x_regs.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Host-build shim for rtl837x_regs.h - the register numbers rtl837x_lacp.c - * touches, with the values from the real header so a wrong constant shows up - * as a failing expectation rather than as a silent no-op. - */ -#ifndef _SHIM_RTL837X_REGS_H_ -#define _SHIM_RTL837X_REGS_H_ - -#include - -#define RTL837X_RMA2_CONF 0x4ed4 -#define RTL837X_RMA_ACT_TRAP 0x10 -#define RTL837X_RMA_ACT_DROP 0x20 - -/* Registers touched by lacp_send's CPU-port targeting */ -#define RTL837X_PORT_ISOLATION_BASE 0x50c0 -#define RTL837X_REG_NIC_TXCMD 0x7850 - -#endif diff --git a/test/lacp/shim/uip.h b/test/lacp/shim/uip.h deleted file mode 100644 index 0d5a51ec..00000000 --- a/test/lacp/shim/uip.h +++ /dev/null @@ -1,9 +0,0 @@ -/* Host-build shim for uip.h - frame buffer + TX hook used by rtl837x_lacp.c */ -#ifndef _SHIM_UIP_H_ -#define _SHIM_UIP_H_ -#include -#define UIP_CONF_BUFFER_SIZE 600 -struct uip_eth_addr { uint8_t addr[6]; }; -extern uint16_t uip_len; -void tcpip_output(void); -#endif diff --git a/test/port_hash/.gitignore b/test/port_hash/.gitignore deleted file mode 100644 index cf8f788c..00000000 --- a/test/port_hash/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -test_lag_hash -port_lag_members_set.inc diff --git a/test/port_hash/Makefile b/test/port_hash/Makefile deleted file mode 100644 index feeee940..00000000 --- a/test/port_hash/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -test: port_lag_members_set.inc - gcc -Wall -Wextra -O1 -o test_lag_hash test_lag_hash.c && ./test_lag_hash -port_lag_members_set.inc: ../../rtl837x_port.c - awk '/^void port_lag_members_set/,/^}/' $< > $@ -clean: - rm -f test_lag_hash port_lag_members_set.inc diff --git a/test/port_hash/test_lag_hash.c b/test/port_hash/test_lag_hash.c deleted file mode 100644 index 205d3fc8..00000000 --- a/test/port_hash/test_lag_hash.c +++ /dev/null @@ -1,68 +0,0 @@ -/* Host test for port_lag_members_set(): the default load-balancing hash must be - * seeded into the CURRENT trunk's register (BASE + lag*4), never trunk 0's, and - * must not overwrite a hash that is already set. Compiles the REAL function - * (extracted verbatim from rtl837x_port.c) against register-recording shims. */ -#include -#include -#include - -#define __banked -#define __xdata -#define __code - -/* register defs (mirror rtl837x_regs.h) */ -#define RTL837X_TRK_MBR_CTRL_BASE 0x4f38 -#define RTL837X_TRK_HASH_CTRL_BASE 0x4f48 -#define LAG_HASH_SOURCE_PORT_NUMBER 0x01 -#define LAG_HASH_L2_SMAC 0x02 -#define LAG_HASH_L2_DMAC 0x04 -#define LAG_HASH_L3_SIP 0x08 -#define LAG_HASH_L3_DIP 0x10 -#define LAG_HASH_L4_SPORT 0x20 -#define LAG_HASH_L4_DPORT 0x40 -#define LAG_HASH_DEFAULT (LAG_HASH_L2_SMAC|LAG_HASH_L2_DMAC|LAG_HASH_L3_SIP|LAG_HASH_L3_DIP|LAG_HASH_L4_SPORT|LAG_HASH_L4_DPORT) -#define LAG_HASH_RESET (LAG_HASH_SOURCE_PORT_NUMBER|LAG_HASH_L2_SMAC|LAG_HASH_L2_DMAC|LAG_HASH_L3_SIP|LAG_HASH_L3_DIP|LAG_HASH_L4_SPORT) - -uint8_t SFR_DATA_24, SFR_DATA_16, SFR_DATA_8, SFR_DATA_0; -uint8_t sfr_data[4]; -static uint32_t reg_store[0x10000]; - -void reg_read_m(uint16_t r){ uint32_t v=reg_store[r]; - sfr_data[0]=(v>>24)&0xff; sfr_data[1]=(v>>16)&0xff; sfr_data[2]=(v>>8)&0xff; sfr_data[3]=v&0xff; } -void reg_write(uint16_t r){ reg_store[r]=((uint32_t)SFR_DATA_24<<24)|((uint32_t)SFR_DATA_16<<16)|((uint32_t)SFR_DATA_8<<8)|SFR_DATA_0; } -void print_string(char*s){(void)s;} void print_byte(uint8_t b){(void)b;} -void print_short(uint16_t s){(void)s;} void write_char(char c){(void)c;} - -#define REG_SET(r,v) do{ SFR_DATA_24=((uint32_t)(v)>>24)&0xff; SFR_DATA_16=((uint32_t)(v)>>16)&0xff; \ - SFR_DATA_8=((uint32_t)(v)>>8)&0xff; SFR_DATA_0=(v)&0xff; reg_write(r); }while(0) -#define REG_WRITE(r,a,b,c,d) do{ SFR_DATA_24=(a); SFR_DATA_16=(b); SFR_DATA_8=(c); SFR_DATA_0=(d); reg_write(r); }while(0) - -/* THE REAL FUNCTION (extracted from rtl837x_port.c at build time) */ -#include "port_lag_members_set.inc" - -static int fails; -#define CK(cond,msg,...) do{ if(cond){printf("PASS " msg "\n",##__VA_ARGS__);} else {printf("FAIL " msg "\n",##__VA_ARGS__); fails++;} }while(0) - -int main(void){ - for(int lag=0; lag<4; lag++){ - memset(reg_store,0,sizeof(reg_store)); - port_lag_members_set(lag, 0x0180); - uint16_t h = RTL837X_TRK_HASH_CTRL_BASE + (lag<<2); - uint16_t m = RTL837X_TRK_MBR_CTRL_BASE + (lag<<2); - CK(reg_store[h]==LAG_HASH_DEFAULT, "lag%d: default 0x%02x -> hash reg 0x%04x", lag, LAG_HASH_DEFAULT, h); - CK(reg_store[m]==0x0180, "lag%d: members 0x0180 -> mbr reg 0x%04x", lag, m); - if(lag) CK(reg_store[RTL837X_TRK_HASH_CTRL_BASE]==0, "lag%d: trunk-0 hash NOT clobbered", lag); - } - /* guard: existing hash must survive a later member change */ - memset(reg_store,0,sizeof(reg_store)); - reg_store[RTL837X_TRK_HASH_CTRL_BASE+(1<<2)] = 0x06; /* lag1 already L2 */ - port_lag_members_set(1, 0x00c0); - CK(reg_store[RTL837X_TRK_HASH_CTRL_BASE+(1<<2)]==0x06, "guard: preset lag1 hash 0x06 preserved"); - /* the chip's reset value counts as unset and is replaced by the default */ - memset(reg_store,0,sizeof(reg_store)); - reg_store[RTL837X_TRK_HASH_CTRL_BASE+(2<<2)] = LAG_HASH_RESET; - port_lag_members_set(2, 0x0003); - CK(reg_store[RTL837X_TRK_HASH_CTRL_BASE+(2<<2)]==LAG_HASH_DEFAULT, "reset value 0x%02x replaced by default on lag2", LAG_HASH_RESET); - printf(fails? "\n=== FAIL (%d) ===\n" : "\n=== ALL PASS ===\n", fails); - return fails?1:0; -} diff --git a/test/lacp/lacp_sim.c b/test/test_lacp.c similarity index 97% rename from test/lacp/lacp_sim.c rename to test/test_lacp.c index 13d571cd..f0ab5f3f 100644 --- a/test/lacp/lacp_sim.c +++ b/test/test_lacp.c @@ -1,13 +1,3 @@ -/* - * Host-side sandbox for the RTL837x LACP implementation. - * - * Compiles the UNMODIFIED rtl837x_lacp.c against shim headers (see shim/) and - * drives it with synthetic LACPDUs from a simulated partner switch/bond. This - * exercises the real state machines that ship in the firmware image - only - * the platform below them (frame I/O, trunk registers, console) is mocked. - * - * Run: make -C test/lacp (exit code 0 = all scenarios pass) - */ #include #include #include @@ -31,7 +21,7 @@ uint8_t lacpEnabled; uint16_t management_vlan; /* mocked; slow-protocol path zeroes it per-frame */ static int verbose = 0; -void print_string(char *s) { if (verbose) fputs(s, stdout); } +void print_string(const char *s) { if (verbose) fputs(s, stdout); } void print_byte(uint8_t b) { if (verbose) printf("%02x", b); } void print_short(uint16_t v) { if (verbose) printf("%04x", v); } void write_char(char c) { if (verbose) putchar(c); } From 73b23808cf0e99d8b104dbc695c4e30b37159142 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 22:05:56 +0200 Subject: [PATCH 20/84] lacp: keep comments to the frame layout The comments repeated the documentation, described tests or explained the code to its author. What stays describes the LACPDU structures. The timer constants are decimal, in units of the LACP timer. --- cmd_parser.c | 4 +- doc/CpuPort.md | 5 -- doc/link_aggregation.md | 49 ------------ httpd/page_impl.c | 7 -- rtl837x_lacp.c | 160 +++++++--------------------------------- rtl837x_lacp.h | 75 ++++++------------- rtl837x_regs.h | 4 - rtlplayground.c | 9 +-- 8 files changed, 52 insertions(+), 261 deletions(-) diff --git a/cmd_parser.c b/cmd_parser.c index 3cfb9007..160f4011 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -539,8 +539,6 @@ void parse_lag(void) return; } - /* "lag lacp [port]..." - run LACP on this LAG with the given candidate - * ports; "lag lacp off" (or no ports) removes the LAG from LACP. */ uint8_t w = 2; __xdata uint8_t lacp_mode = 0; if (cmd_words_len > 2 && cmd_compare(2, "lacp")) { @@ -559,7 +557,7 @@ void parse_lag(void) members |= ((uint16_t)1) << atoi_results_u8; } if (lacp_mode) { - lacp_lag_set(group, members); /* 0 ports also valid: removes the LAG */ + lacp_lag_set(group, members); } else { lacp_lag_set(group, 0); port_lag_members_set(group, members); diff --git a/doc/CpuPort.md b/doc/CpuPort.md index f5e9859b..c10d0a15 100644 --- a/doc/CpuPort.md +++ b/doc/CpuPort.md @@ -79,11 +79,6 @@ bit7 KEEP | 6 VSEL | 5 LEARN_DIS | 4:0 VIDX All fields are in network byte order. -This word has to be written through `HTONS` like every other field of the tag. -Writing a constant raw puts the bits in the wrong byte, so `0x0020` reaches the -wire as `0x2000`, which is EFID rather than LEARN_DIS. The ASIC then fails to -parse the tag and forwards the frame with the `0x8899` header still on it. - * `EFID_EN`, `EFID`: look the destination up under this filtering ID instead of the port's own * `PRI_EN`, `PRI`: force the given priority on the frame diff --git a/doc/link_aggregation.md b/doc/link_aggregation.md index 462b66de..b725e419 100644 --- a/doc/link_aggregation.md +++ b/doc/link_aggregation.md @@ -227,52 +227,3 @@ iperf Done. ``` As you can see, the total throughput was 4.71 GBit/sec which is close to the maximum possible with a single 5GBit link. - -## LACP: how the PDUs reach the CPU - -Slow-protocol frames go to the reserved group `01:80:C2:00:00:02`, and the -ASIC's default action for it is *drop*. The engine sets the action to *trap* -while it runs. A trap hands the frame to the port named in `EXT_CPU_CTRL`, -which names no port out of reset - that is why the trap first looked like no -help at all: the receive counters stayed frozen while the partner kept sending, -and the frames were counted as ingress discards. `trap_init()` points the -register at the CPU port once at startup. - -The *forward* action would reach the CPU as well, since the CPU port is in the -forwarding domain, but it leaves the frame in the L2 lookup and so floods it -across the ingress VLAN. That leaks LACPDUs to unrelated ports, and the damage -is real: a bond's own ports see each other's PDUs and Linux reports an illegal -loopback, a second bond on the same switch is poisoned, and the flood escapes -through uplinks. Containing it would take a static L2 multicast entry with a -CPU-only mask for every PVID in use, since untagged LACPDUs classify into the -ingress port's PVID and the forward action is not limited to the LACP ports. - -The trap keys on the address instead, before the VLAN lookup and before the -ingress filters, which settles three things at once: - -* Nothing is written per VLAN or per PVID, so delivery no longer depends on - which PVIDs the ports happen to carry. -* A port that admits tagged frames only still receives: an untagged forwarded - LACPDU is dropped by the acceptable-frame-type filter, a trapped one is not. -* A port STP holds in blocking still receives, so a group converges without - waiting out the forward delay. - -A trapped frame arrives without the VLAN tag the hardware inserts into a -forwarded one, so the PDU body sits four bytes earlier; `TRAP_RX_BODY()` gives -the offset for both forms. - -`lacp off` puts the action back to *drop*, which acts before the L2 lookup. - -## LACP: what the implementation leaves out - -The four state machines of Clause 43 are present in simplified form. Mux control -is coupled rather than independent, one partner system is elected per LAG, and -there are no churn detection machines. - -One interoperability rule is worth stating because getting it wrong is silent. -The Partner block of an outgoing PDU has to echo the peer's own actor identity -verbatim, meaning system and priority, key, port and port priority, and the -aggregation flag. A Linux partner's `__record_pdu()` accepts our SYNC bit only -when that block mirrors what it sent. Hardcoding the priorities to zero -mismatches Linux's default of 0xffff, which clears partner SYNC, moves the state -byte from 0x3f to 0x37, and the bond never reaches collecting and distributing. diff --git a/httpd/page_impl.c b/httpd/page_impl.c index 94ccb483..e1f43f6e 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -737,9 +737,6 @@ void send_stp(void) slen += strtox(outbuf + slen, "]}"); } -/* LACP protocol status for the LAG page ("/lacp.json"). State variables are - * exported read-only via rtl837x_lacp.h; hardware trunk membership itself is - * already visible through send_lag() (it reads the trunk registers). */ void send_lacp(void) { dbg_string("send_lacp called\n"); @@ -747,7 +744,6 @@ void send_lacp(void) slen += strtox(outbuf + slen, "{\"on\":"); bool_to_html(lacpEnabled); - /* Per-LAG section: candidate ports, elected aggregator, trunk members. */ slen += strtox(outbuf + slen, ",\"lags\":["); for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) { slen += strtox(outbuf + slen, "{\"cfg\":\""); @@ -768,7 +764,6 @@ void send_lacp(void) for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { slen += strtox(outbuf + slen, "{\"p\":"); itoa_html(machine.log_to_phys_port[i]); - /* Which LACP LAG this port belongs to; 255 = none */ slen += strtox(outbuf + slen, ",\"lag\":"); itoa_html(lacp_port_lag[i]); slen += strtox(outbuf + slen, ",\"a\":\""); @@ -777,8 +772,6 @@ void send_lacp(void) byte_to_html(lacp_partner_state[i]); slen += strtox(outbuf + slen, "\",\"rs\":"); itoa_html(lacp_rx_state[i]); - /* rx as 4-digit hex: itoa16_html() only renders values up to 9999 - * correctly (see its VLAN-ID comment), the counter goes to 65535 */ slen += strtox(outbuf + slen, ",\"rx\":\""); byte_to_html(lacp_rx_count[i] >> 8); byte_to_html(lacp_rx_count[i]); diff --git a/rtl837x_lacp.c b/rtl837x_lacp.c index 276e80d5..5a14e3b4 100644 --- a/rtl837x_lacp.c +++ b/rtl837x_lacp.c @@ -18,11 +18,10 @@ #include "machine.h" extern __code const struct machine machine; -extern __xdata struct machine_runtime machine_detected; /* runtime chip detection, owned by rtl837x_port.c */ -extern __xdata uint16_t management_vlan; /* owned by rtlplayground.c; suppressed per-frame for slow protocols */ +extern __xdata struct machine_runtime machine_detected; +extern __xdata uint16_t management_vlan; extern __xdata struct uip_eth_addr uip_ethaddr; extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2]; -/* lacpEnabled: extern in rtl837x_lacp.h, owned by rtlplayground.c */ /* 20-byte Actor/Partner information body (802.3ad 43.4.2.2), network byte order */ struct lacp_info { @@ -78,35 +77,29 @@ struct lacpdu_in { struct lacp_info partner; }; -__xdata uint8_t lacp_rxbody; /* offset of the LACPDU body: the RTL tag, plus a VLAN tag when forwarded */ +__xdata uint8_t lacp_rxbody; #define LACP_O ((__xdata struct lacpdu *)&uip_buf[RTL_FRAME_DESC_SIZE]) #define LACP_H ((__xdata struct lacpdu_in_hdr *)&uip_buf[0]) #define LACP_I ((__xdata struct lacpdu_in *)&uip_buf[lacp_rxbody]) -/* --- Per-port participant state (802.3ad 43.4.7). Indexed by physical port. --- */ -__xdata uint8_t lacp_actor_state[10]; /* our Actor_State flags */ -__xdata uint8_t lacp_rx_state[10]; /* LACP_RX_* receive machine */ -__xdata uint8_t lacp_partner_state[10]; /* last Partner_State we saw */ -__xdata uint8_t lacp_partner_sys[10][6]; /* partner System ID */ -__xdata uint16_t lacp_partner_key[10]; /* partner Key */ -__xdata uint16_t lacp_partner_port[10]; /* partner Port number */ -/* The Partner block echoes the peer's actor identity verbatim, see - * doc/link_aggregation.md. */ -__xdata uint16_t lacp_partner_sys_prio[10]; /* partner System priority */ -__xdata uint16_t lacp_partner_port_prio[10]; /* partner Port priority */ -__xdata uint16_t lacp_periodic[10]; /* down-counter to next TX */ -__xdata uint16_t lacp_timeout[10]; /* down-counter to partner expiry*/ -__xdata uint8_t lacp_ntt[10]; /* Need-To-Transmit flag */ +__xdata uint8_t lacp_actor_state[10]; +__xdata uint8_t lacp_rx_state[10]; +__xdata uint8_t lacp_partner_state[10]; +__xdata uint8_t lacp_partner_sys[10][6]; +__xdata uint16_t lacp_partner_key[10]; +__xdata uint16_t lacp_partner_port[10]; +__xdata uint16_t lacp_partner_sys_prio[10]; +__xdata uint16_t lacp_partner_port_prio[10]; +__xdata uint16_t lacp_periodic[10]; +__xdata uint16_t lacp_timeout[10]; +__xdata uint8_t lacp_ntt[10]; -/* The timers step every four system ticks (50 Hz) whatever the pace of the - * main loop; a long pass is caught up on, a few steps at a time. */ #define LACP_TICK_STEP (SYS_TICK_HZ / 50) #define LACP_CATCH_UP 8 __xdata uint8_t lacp_tick_last; extern volatile __xdata uint32_t ticks; -/* Per-LAG state: the hardware trunk groups run LACP independently. */ __xdata uint16_t lacp_lag_ports[LACP_NUM_LAGS]; __xdata uint8_t lacp_port_lag[10]; __xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][6]; @@ -117,12 +110,10 @@ __xdata uint16_t lacp_scratch_mask; __xdata uint8_t lacp_scratch_flag; __xdata uint8_t lacp_scratch_lag; -/* Per-port RX LACPDU count - primarily a hardware bring-up diagnostic to - * verify slow-protocol frames actually reach the CPU port (see "lacp show") */ __xdata uint16_t lacp_rx_count[10]; -void lacp_mux_update(void) __banked; /* defined below, used from lacp_in */ +void lacp_mux_update(void) __banked; static uint8_t lacp_sys_eq(__xdata uint8_t *a, __xdata uint8_t *b) { @@ -134,15 +125,10 @@ static uint8_t lacp_sys_eq(__xdata uint8_t *a, __xdata uint8_t *b) } -/* - * Selection logic (simplified 43.4.14): a port may join the aggregator when - * we have fresh partner info, the partner considers the link aggregatable, - * and the partner is the same system the aggregator was elected for. - */ static uint8_t lacp_port_selected(uint8_t port) { uint8_t lag = lacp_port_lag[port]; - if (lag == LACP_LAG_NONE) /* port not in any LACP LAG */ + if (lag == LACP_LAG_NONE) return 0; if (lacp_rx_state[port] != LACP_RX_CURRENT) return 0; @@ -154,11 +140,6 @@ static uint8_t lacp_port_selected(uint8_t port) } -/* - * Mux machine (simplified coupled control, 43.4.15): SYNC follows selection; - * COLLECTING+DISTRIBUTING additionally require the partner to be in sync. - * Returns 1 if the actor state changed (caller sets NTT). - */ static uint8_t lacp_mux_machine(uint8_t port) { uint8_t old = lacp_actor_state[port]; @@ -183,7 +164,6 @@ static uint8_t lacp_mux_machine(uint8_t port) #define port_bit(p) (((uint8_t)1) << (p)) -/* Build and emit one LACPDU out physical port `port` (802.3ad 43.4.2). */ void lacp_send(uint8_t port) __banked { memset((__xdata uint8_t *)LACP_O, 0, sizeof(struct lacpdu)); @@ -194,42 +174,22 @@ void lacp_send(uint8_t port) __banked LACP_O->rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID); LACP_O->rtl_tag.version = RTL_FRAME_TAG_VERSION; LACP_O->rtl_tag.reason = 0x00; - /* Must go through HTONS like every other tag field: writing 0x0020 raw put - * the bits in the wrong byte (on the wire 0x2000 = EFID, not LEARN_DIS), so - * the ASIC could not parse the tag and forwarded the frame with the 0x8899 - * header still attached - the partner then saw ethertype 0x8899 instead of - * 0x8809 and ignored the LACPDU. KEEP additionally stops the switch from - * rewriting the 802.1Q tag format of a CPU-injected frame (as mainline). */ LACP_O->rtl_tag.flags = HTONS(RTL_TAG_LEARN_DIS | RTL_TAG_KEEP); - /* ALLOW cleared => this word is the *forwarding* port mask, i.e. directed - * egress to exactly this port (what mainline does). Setting ALLOW instead - * makes it an allowance/permission mask on top of a normal lookup, which - * for a one-hot mask resolves to an empty egress set and the frame is - * silently dropped - verified on hardware. */ - LACP_O->rtl_tag.pmask = HTONS(port_bit(port)); /* egress this port only */ + LACP_O->rtl_tag.pmask = HTONS(port_bit(port)); LACP_O->ethertype = HTONS(SLOW_PROTO_ETHERTYPE); LACP_O->subtype = SLOW_PROTO_SUBTYPE_LACP; LACP_O->version = LACP_VERSION; - /* Actor TLV */ LACP_O->tlv_actor = LACP_TLV_ACTOR; LACP_O->actor_len = LACP_TLV_LEN_INFO; LACP_O->actor.sys_prio = HTONS(LACP_SYS_PRIO); memcpy(LACP_O->actor.sys, uip_ethaddr.addr, 6); - /* Per-LAG Actor Key so a partner never merges ports of our different LAGs - * into one aggregate (only ever called for ports in a LACP LAG). */ LACP_O->actor.key = HTONS((uint16_t)(lacp_port_lag[port] + 1)); LACP_O->actor.port_prio = HTONS(LACP_DEF_PORT_PRIO); - LACP_O->actor.port = HTONS((uint16_t)port + 1); /* 1-based port id */ + LACP_O->actor.port = HTONS((uint16_t)port + 1); LACP_O->actor.state = lacp_actor_state[port]; - /* Partner TLV: echo the last partner info we recorded, VERBATIM. A Linux - * 802.3ad partner (__record_pdu) only accepts the SYNC bit we set above if - * this block mirrors its own actor identity exactly - system+priority, key, - * port+priority. Priorities must be echoed, not hardcoded: Linux defaults to - * system priority 0xffff, so a hardcoded 0 here cleared its partner-SYNC - * (0x3f->0x37) and the aggregate never collected/distributed. */ LACP_O->tlv_partner = LACP_TLV_PARTNER; LACP_O->partner_len = LACP_TLV_LEN_INFO; LACP_O->partner.sys_prio = HTONS(lacp_partner_sys_prio[port]); @@ -239,7 +199,6 @@ void lacp_send(uint8_t port) __banked LACP_O->partner.port = HTONS(lacp_partner_port[port]); LACP_O->partner.state = lacp_partner_state[port]; - /* Collector TLV + Terminator */ LACP_O->tlv_collector = LACP_TLV_COLLECTOR; LACP_O->collector_len = LACP_TLV_LEN_COLLECTOR; LACP_O->collector_max_delay = 0; @@ -247,14 +206,6 @@ void lacp_send(uint8_t port) __banked LACP_O->terminator_len = 0x00; lacp_ntt[port] = 0; - /* Slow-protocol frames are link-local and must egress untagged. With a - * management VLAN set, tcpip_output() splices an 802.1Q tag after the SA, - * shifting our in-frame rtl_tag out of the position the ASIC expects - so it - * fails to parse/strip the CPU tag and the 0x8899 header leaks onto the wire - * (the partner then ignores the LACPDU). Suppress the VLAN insert for this - * frame only; management traffic keeps its tag. Verified on HW: with the - * insert suppressed the LACPDU egresses as clean 0x8809 and the bond - * partner converges (partner MAC becomes the switch, both links aggregate). */ uint16_t saved_mgmt_vlan = management_vlan; management_vlan = 0; uip_len = sizeof(struct lacpdu); @@ -263,11 +214,6 @@ void lacp_send(uint8_t port) __banked } -/* - * Process an incoming LACPDU. Called from the rtlplayground.c dispatch when a - * frame with DA 01:80:C2:00:00:02 and ethertype 0x8809/subtype 0x01 arrives. - * The ingress port is the low nibble of rtl_tag.pmask on RX. - */ void lacp_in(void) __banked { __xdata uint8_t port, lag; @@ -283,14 +229,10 @@ void lacp_in(void) __banked || LACP_I->subtype != SLOW_PROTO_SUBTYPE_LACP) return; - /* Per rtl837x_common.h, pmask carries a 4-bit port number on RX - * (hardware-verified on RTL8373: per-port rx counters track the actual - * ingress port through full LACP convergence). */ port = ((uint8_t)HTONS(LACP_H->rtl_tag.pmask)) & 0x0f; if (port < machine.min_port || port > machine.max_port) return; - /* Only ports assigned to a LACP-mode LAG participate in the protocol. */ lag = lacp_port_lag[port]; if (lag == LACP_LAG_NONE) return; @@ -305,7 +247,6 @@ void lacp_in(void) __banked lacp_rx_count[port]++; - /* Record partner = the remote's Actor block (802.3ad 43.4.9 recordPDU) */ memcpy(lacp_partner_sys[port], LACP_I->actor.sys, 6); lacp_scratch_mask = HTONS(LACP_I->actor.key); lacp_partner_key[port] = lacp_scratch_mask; @@ -317,22 +258,17 @@ void lacp_in(void) __banked lacp_partner_port_prio[port] = lacp_scratch_mask; lacp_partner_state[port] = LACP_I->actor.state; - /* Receive machine -> CURRENT, (re)arm partner timeout (43.4.12) */ lacp_rx_state[port] = LACP_RX_CURRENT; lacp_timeout[port] = LACP_SHORT_TIMEOUT; - /* Elect this LAG's aggregator partner system on first contact (43.4.14) */ if (!lacp_agg_valid[lag]) { memcpy(lacp_agg_sys[lag], LACP_I->actor.sys, 6); lacp_agg_valid[lag] = 1; } - /* Selection + Mux for this port; state change => Need-To-Transmit */ if (lacp_mux_machine(port)) lacp_ntt[port] = 1; - /* update_NTT (43.4.12): if the partner's view of us is stale (their - * Partner block does not match our actor state/port), tell them again. */ if (LACP_I->partner.state != lacp_actor_state[port] || HTONS(LACP_I->partner.port) != (uint16_t)port + 1) lacp_ntt[port] = 1; @@ -341,13 +277,10 @@ void lacp_in(void) __banked } -/* Recompute each LACP LAG's hardware trunk membership: a port joins its LAG's - * trunk once it is fully in sync (SYNC+COLLECTING+DISTRIBUTING and the partner - * in SYNC). Each LACP-mode LAG is programmed independently (simplified 43.4.15). */ void lacp_mux_update(void) __banked { for (uint8_t lag = 0; lag < LACP_NUM_LAGS; lag++) { - if (!lacp_lag_ports[lag]) /* LAG not under LACP management */ + if (!lacp_lag_ports[lag]) continue; lacp_scratch_mask = 0; for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { @@ -357,8 +290,6 @@ void lacp_mux_update(void) __banked && (lacp_partner_state[i] & LACP_STATE_SYNC)) lacp_scratch_mask |= port_bit(i); } - /* Program the trunk only when this LAG's membership actually changed - * - this runs on every timer tick. */ if (lacp_scratch_mask != lacp_members_last[lag]) { lacp_members_last[lag] = lacp_scratch_mask; port_lag_members_set(lag, lacp_scratch_mask); @@ -370,10 +301,9 @@ void lacp_mux_update(void) __banked static void lacp_step(void) { for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { - if (lacp_port_lag[i] == LACP_LAG_NONE) /* not in a LACP LAG */ + if (lacp_port_lag[i] == LACP_LAG_NONE) continue; - /* Periodic transmit machine (802.3ad 43.4.13) */ if (lacp_periodic[i]) { lacp_periodic[i]--; } else { @@ -382,9 +312,6 @@ static void lacp_step(void) lacp_ntt[i] = 1; } - /* Partner timeout -> DEFAULTED (802.3ad 43.4.12): the mux machine - * then drops SYNC/COLLECTING/DISTRIBUTING and the port leaves the - * trunk. TODO(43.4.12): churn detection is diagnostics-only, skipped. */ if (lacp_timeout[i]) { if (!--lacp_timeout[i]) { lacp_rx_state[i] = LACP_RX_DEFAULTED; @@ -398,11 +325,6 @@ static void lacp_step(void) lacp_send(i); } - /* Release each LAG's aggregator identity once none of its ports still - * hears the elected partner, so a re-cabled setup can elect a new one - * (43.4.14). A port that is CURRENT with some other system must not keep - * the old identity alive: it would never be selected, and neither would - * the rest of the LAG once it moves to that system too. */ for (uint8_t lag = 0; lag < LACP_NUM_LAGS; lag++) { if (!lacp_agg_valid[lag]) continue; @@ -432,11 +354,10 @@ void lacp_timers(void) __banked } -/* Bring one port up as an active-fast LACP participant (43.4.12 init). */ static void lacp_port_init(uint8_t port) { lacp_actor_state[port] = LACP_STATE_ACTIVITY | LACP_STATE_AGGREGATION - | LACP_STATE_TIMEOUT; /* active + fast */ + | LACP_STATE_TIMEOUT; lacp_rx_state[port] = LACP_RX_INITIALIZE; lacp_partner_state[port] = LACP_STATE_DEFAULTED; memset(lacp_partner_sys[port], 0, 6); @@ -444,13 +365,10 @@ static void lacp_port_init(uint8_t port) lacp_partner_port[port] = 0; lacp_periodic[port] = LACP_FAST_PERIODIC; lacp_timeout[port] = 0; - lacp_ntt[port] = 1; /* announce ourselves immediately */ + lacp_ntt[port] = 1; lacp_rx_count[port] = 0; } -/* Return a port to non-LACP state. (Port isolation is no longer touched - a - * trapped LACPDU never enters the forwarding path, so a sibling cannot see - * it, which is what the isolation workaround was for.) */ static void lacp_port_release(uint8_t port) { lacp_actor_state[port] = 0; @@ -461,7 +379,6 @@ static void lacp_port_release(uint8_t port) memset(lacp_partner_sys[port], 0, 6); } -/* True while any LAG is under LACP management (drives the global RMA trap). */ static uint8_t lacp_any_lag(void) { for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) @@ -470,11 +387,6 @@ static uint8_t lacp_any_lag(void) return 0; } -/* - * Turn the LACP engine on: trap the Slow-Protocols group to the CPU port. The - * trap acts on the address before the VLAN lookup and before the ingress - * filters, so delivery needs no L2 entry and no PVID of its own. - */ static void lacp_engine_on(void) { lacp_tick_last = (uint8_t)ticks; @@ -482,16 +394,12 @@ static void lacp_engine_on(void) trap_rma_set(RTL837X_RMA2_CONF, RTL837X_RMA_ACT_TRAP); } -/* Turn the LACP engine off: stop delivering slow-protocols to the CPU. */ static void lacp_engine_off(void) { trap_rma_set(RTL837X_RMA2_CONF, RTL837X_RMA_ACT_DROP); lacpEnabled = 0; } -/* One-time boot init: no LAG runs LACP yet, so every port maps to LACP_LAG_NONE. - * MUST run before any "lag ... lacp" config replay - xdata is not zeroed to the - * 0xff sentinel, and a stray 0 would make a port look like it belongs to LAG 0. */ void lacp_init(void) __banked { for (uint8_t i = 0; i < 10; i++) { @@ -507,13 +415,6 @@ void lacp_init(void) __banked lacpEnabled = 0; } -/* - * Assign a candidate-port mask to LACP-mode LAG `lag` (`lag lacp `). - * ports == 0 removes the LAG from LACP management. Ports leaving the LAG are - * returned to normal switching; ports joining start the protocol immediately. - * The engine's RMA trap is enabled on the first LACP LAG and torn down with the - * last. Never wipes a static "lag" the user configured on a different group. - */ void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked { if (lag >= LACP_NUM_LAGS) @@ -522,16 +423,12 @@ void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked return; lacp_scratch_flag = lacp_any_lag(); - /* Drop ports that were in this LAG but are not in the new mask. */ for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { if (lacp_port_lag[i] == lag && !(ports & port_bit(i))) { lacp_port_lag[i] = LACP_LAG_NONE; lacp_port_release(i); } } - /* Release this LAG's trunk: LACP's own, or the static one a group put - * under LACP still has. Taking the group out (ports == 0) leaves a static - * trunk alone. */ if (lacp_members_last[lag] || ports) { lacp_members_last[lag] = 0; port_lag_members_set(lag, 0); @@ -539,11 +436,9 @@ void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked lacp_agg_valid[lag] = 0; lacp_lag_ports[lag] = ports; - if (ports && !lacp_scratch_flag) /* first LACP LAG: bring the engine up first */ + if (ports && !lacp_scratch_flag) lacp_engine_on(); - /* Adopt the new member ports (with the engine already up). A port that - * belonged to another LACP LAG leaves that LAG's candidate set first. */ for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { if (!(ports & port_bit(i))) continue; @@ -563,15 +458,13 @@ void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked lacp_port_init(i); } - if (!lacp_any_lag() && lacp_scratch_flag) /* removed the last LACP LAG */ + if (!lacp_any_lag() && lacp_scratch_flag) lacp_engine_off(); - lacp_mux_update(); /* a LAG that lost a port to this one drops it from its trunk */ + lacp_mux_update(); } -/* "lacp off": remove every LACP LAG (releases their trunks, stops the engine). - * There is no "lacp on": the engine comes up with the first "lag lacp". */ void lacp_off(void) __banked { for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) @@ -580,9 +473,6 @@ void lacp_off(void) __banked } -/* "lacp show": per-port protocol state - the primary bring-up diagnostic. - * A stuck rx=0 counter means slow-protocol frames never reach the CPU port - * (see the RMA note in lacp_engine_on()). */ void lacp_show(void) __banked { print_string("LACP "); print_string(lacpEnabled ? "on" : "off"); write_char('\n'); diff --git a/rtl837x_lacp.h b/rtl837x_lacp.h index 514f9e35..44e3ad4b 100644 --- a/rtl837x_lacp.h +++ b/rtl837x_lacp.h @@ -8,92 +8,61 @@ #include -void lacp_init(void) __banked; /* boot init: clear per-LAG state */ +void lacp_init(void) __banked; void lacp_in(void) __banked; void lacp_timers(void) __banked; void lacp_off(void) __banked; -void lacp_show(void) __banked; /* "lacp show" - per-port state + RX counters */ -/* Assign a candidate-port mask to a LACP-mode LAG (`lag lacp `). - * ports == 0 removes the LAG from LACP management. Enables the engine on first - * LACP LAG and tears it down when the last one goes away. */ +void lacp_show(void) __banked; void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked; -/* - * Per-LAG LACP: each of the 4 hardware trunk groups (0-3) can independently run - * LACP on an admin-assigned set of candidate ports, forming its own aggregator - * (its own elected partner System). A port participates in LACP only if it has - * been assigned to a LACP-mode LAG (lacp_port_lag[port] != LACP_LAG_NONE). - * Ports of the same LAG that converge with a consistent partner join that LAG's - * hardware trunk. This is the port-channel model: `lag lacp `. - */ #define LACP_NUM_LAGS 4 -#define LACP_LAG_NONE 0xff /* port not assigned to any LACP LAG */ +#define LACP_LAG_NONE 0xff -/* Protocol state, exposed read-only for the web UI (page_impl.c send_lacp()) - * and the serial console. Owned by rtl837x_lacp.c / rtlplayground.c. */ extern __xdata uint8_t lacpEnabled; extern __xdata uint8_t lacp_actor_state[10]; extern __xdata uint8_t lacp_partner_state[10]; extern __xdata uint8_t lacp_rx_state[10]; extern __xdata uint8_t lacp_partner_sys[10][6]; extern __xdata uint16_t lacp_rx_count[10]; -extern __xdata uint8_t lacp_port_lag[10]; /* LAG (0-3) a port runs LACP on, or LACP_LAG_NONE */ -extern __xdata uint16_t lacp_lag_ports[LACP_NUM_LAGS]; /* admin candidate-port mask per LACP LAG (0 = LAG not LACP) */ -extern __xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][6]; /* elected partner System per LAG */ -extern __xdata uint8_t lacp_agg_valid[LACP_NUM_LAGS]; /* aggregator elected for this LAG */ -extern __xdata uint16_t lacp_members_last[LACP_NUM_LAGS];/* trunk members we last programmed per LAG */ +extern __xdata uint8_t lacp_port_lag[10]; +extern __xdata uint16_t lacp_lag_ports[LACP_NUM_LAGS]; +extern __xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][6]; +extern __xdata uint8_t lacp_agg_valid[LACP_NUM_LAGS]; +extern __xdata uint16_t lacp_members_last[LACP_NUM_LAGS]; -/* Slow-Protocols / LACPDU identifiers (802.3ad 43.4) */ #define SLOW_PROTO_ETHERTYPE 0x8809 #define SLOW_PROTO_SUBTYPE_LACP 0x01 #define LACP_VERSION 0x01 -#define LACP_DST5 0x02 /* 01:80:C2:00:00:02 last octet */ +#define LACP_DST5 0x02 -/* LACPDU TLV framing (802.3ad 43.4.2): type bytes + fixed TLV lengths. */ #define LACP_TLV_ACTOR 0x01 #define LACP_TLV_PARTNER 0x02 #define LACP_TLV_COLLECTOR 0x03 #define LACP_TLV_TERMINATOR 0x00 -#define LACP_TLV_LEN_INFO 0x14 /* Actor/Partner Information TLV length */ -#define LACP_TLV_LEN_COLLECTOR 0x10 /* Collector TLV length */ +#define LACP_TLV_LEN_INFO 0x14 +#define LACP_TLV_LEN_COLLECTOR 0x10 -/* Our aggregation identity: System priority is a constant (Linux 802.3ad - * defaults to the same 0xffff); the default Actor port priority likewise. */ #define LACP_SYS_PRIO 0xffff #define LACP_DEF_PORT_PRIO 0x00ff -/* Actor_State / Partner_State flag bits (802.3ad 43.4.2) */ -#define LACP_STATE_ACTIVITY 0x01 /* 1 = Active LACP (send periodically) */ -#define LACP_STATE_TIMEOUT 0x02 /* 1 = Short (fast) timeout */ -#define LACP_STATE_AGGREGATION 0x04 /* 1 = link is aggregatable */ -#define LACP_STATE_SYNC 0x08 /* 1 = in sync with partner */ +#define LACP_STATE_ACTIVITY 0x01 /* active LACP, sends periodically */ +#define LACP_STATE_TIMEOUT 0x02 /* short timeout requested */ +#define LACP_STATE_AGGREGATION 0x04 /* link can be aggregated */ +#define LACP_STATE_SYNC 0x08 /* in sync with the partner */ #define LACP_STATE_COLLECTING 0x10 #define LACP_STATE_DISTRIBUTING 0x20 -#define LACP_STATE_DEFAULTED 0x40 /* partner info is administrative default*/ -#define LACP_STATE_EXPIRED 0x80 /* wire-format doc; unused by the simplified RX machine */ +#define LACP_STATE_DEFAULTED 0x40 /* partner information is the default */ +#define LACP_STATE_EXPIRED 0x80 -/* Fully-participating actor: in sync AND collecting AND distributing. */ #define LACP_STATE_FULL (LACP_STATE_SYNC | LACP_STATE_COLLECTING | LACP_STATE_DISTRIBUTING) -/* - * Timer units: one step of the LACP timers, taken every four system ticks, - * 50 per second. A long pass of the main loop is caught up on a few steps at - * a time, so a period is a range and the worst case is what matters. - * - * The transmit periods are not free to choose. Our own timeouts only decide - * how patient we are, but our TX period decides when the PARTNER gives up on - * us: 802.3ad pairs fast periodic 1 s with a 3 s short timeout, and slow - * periodic 30 s with a 90 s long timeout. A period whose worst case reaches - * the partner's threshold makes it expire us intermittently, which reads as - * an aggregate that works but never stops churning. */ -#define LACP_FAST_PERIODIC 0x0032 /* fast TX 1 s; partner expires at 3 s */ -#define LACP_SLOW_PERIODIC 0x05dc /* slow TX 30 s; partner expires at 90 s */ -#define LACP_SHORT_TIMEOUT 0x00c8 /* silent partner dropped after 4 s (802.3ad: 3 s) */ +#define LACP_FAST_PERIODIC 50 +#define LACP_SLOW_PERIODIC 1500 +#define LACP_SHORT_TIMEOUT 200 -/* Per-port LACP receive-machine state (802.3ad 43.4.12) */ #define LACP_RX_INITIALIZE 0 -#define LACP_RX_PORT_DISABLED 1 /* numbering reserved (exported via /lacp.json "rs") */ -#define LACP_RX_EXPIRED 2 /* numbering reserved (exported via /lacp.json "rs") */ +#define LACP_RX_PORT_DISABLED 1 +#define LACP_RX_EXPIRED 2 #define LACP_RX_DEFAULTED 3 #define LACP_RX_CURRENT 4 diff --git a/rtl837x_regs.h b/rtl837x_regs.h index 1eb865a4..dc4b2dc1 100644 --- a/rtl837x_regs.h +++ b/rtl837x_regs.h @@ -284,10 +284,6 @@ * Loop detection / STP */ #define RTL8373_RLDP_TIMER 0x1074 -/* Per reserved-multicast-address action. The registers are packed and - * consecutive by ENTRY rather than by the byte of the address: entry 0 is - * 01:80:C2:00:00:00 at 0x4ecc and entry 2 is ...:02 (Slow Protocols / LACP) - * at 0x4ed4, but entry 5 is ...:08, so the step of 4 counts entries. */ #define RTL837X_RMA0_CONF 0x4ecc #define RTL837X_RMA2_CONF 0x4ed4 #define RTL837X_RMA_ACT_MASK 0x30 diff --git a/rtlplayground.c b/rtlplayground.c index 38e322e7..4a8b4ec7 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -1062,14 +1062,14 @@ void handle_rx(void) print_string(" MGMT-VLAN: "); print_short(management_vlan); write_char('\n'); #endif if ((stp_enabled || lacpEnabled) && uip_buf[0] == 0x01 && uip_buf[1] == 0x80 - && uip_buf[2] == 0xc2 && uip_buf[3] == 0x00 && uip_buf[4] == 0x00) { // 01:80:C2:00:00:0x - if (stp_enabled && uip_buf[5] == 0x00) { // STP/RSTP BPDU + && uip_buf[2] == 0xc2 && uip_buf[3] == 0x00 && uip_buf[4] == 0x00) { + if (stp_enabled && uip_buf[5] == 0x00) { stp_in(); if (uip_len) { print_string("STP TX\n"); tcpip_output(); } - } else if (lacpEnabled && uip_buf[5] == 0x02) { // LACPDU (slow protocols) + } else if (lacpEnabled && uip_buf[5] == 0x02) { lacp_in(); } } else if (igmpEnabled && uip_buf[0] == 0x01 && uip_buf[1] == 0x00 && uip_buf[2] == 0x5e // IPv4-MC packet? @@ -1296,7 +1296,6 @@ static void handle_tick(void) stp_timers(); } } - // If LACP enabled, drive its machines (own tick divider lives in the banked module) if (lacpEnabled) lacp_timers(); health_phase(HEALTH_PH_STP); @@ -1802,7 +1801,7 @@ void main(void) stp_enabled = 0; stp_defaults(); /* 802.1D/w default config before any "stp ..." replay */ lacpEnabled = 0; - lacp_init(); /* clear per-LAG state (port->LAG map = NONE) before any config replay */ + lacp_init(); nic_setup(); REG_SET(RTL837X_NIC_INT_MSK, NIC_INT_RXIE); trap_init(); From d525229b423574e6928dd1d272f7179bd4e2580a Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 22:05:56 +0200 Subject: [PATCH 21/84] doc: lacp: shorten, point to stp.md for the trap The trap is described with STP, so the LACP page only says that it is used. The LACP sections leave link_aggregation.md, which now points to lacp.md, and the timing section gives the values in seconds and the 802.3ad requirements without the test history. --- doc/lacp.md | 170 +++++++++++----------------------------- doc/link_aggregation.md | 4 + 2 files changed, 50 insertions(+), 124 deletions(-) diff --git a/doc/lacp.md b/doc/lacp.md index ab09db39..b116ef9a 100644 --- a/doc/lacp.md +++ b/doc/lacp.md @@ -1,79 +1,23 @@ # LACP (Link Aggregation Control Protocol, IEEE 802.3ad) -A static link aggregation group works only if both ends agree about it in -advance. Nothing checks that they do: cable a member to the wrong neighbour and -the group keeps hashing frames onto a link that goes somewhere else. LACP is the -protocol that makes the two ends agree, and keeps agreeing — it discovers which -links reach the same partner, aggregates only those, and drops a link out of the -group when the partner stops answering. +A static link aggregation group works only if both ends are configured for the +same links, and nothing checks that they are. LACP lets the two ends agree on +which links reach the same partner, aggregates only those, and takes a link out +of the group when the partner stops answering. -The switch runs LACP in software on the 8051. Frames are the Slow Protocols -group: destination `01:80:C2:00:00:02`, ethertype `0x8809`, subtype 1. The -implementation covers the parts that decide whether an aggregate forms: -the Receive machine and the Selection and Mux machines of 802.3ad clause 43, -`recordPDU` and `update_NTT`, and the mis-cabling protection that refuses to -aggregate links whose partners differ. It does not implement the Churn Detection -machines or the Marker protocol, and the Mux machine is the coupled variant, so -collecting and distributing are enabled together rather than in sequence. - -Up to `LACP_NUM_LAGS` (4) groups can be placed under LACP; the rest stay static. +The switch runs LACP in software on the 8051. LACP frames belong to the Slow +Protocols, with destination `01:80:C2:00:00:02` and ethertype `0x8809`; the +subtype of LACP frames is 1. The implementation has the Receive, Selection and +Mux machines of 802.3ad clause 43 and does not aggregate links whose partners +differ. Up to `LACP_NUM_LAGS` (4) groups can run LACP; the others stay static. ## Getting the frames to the CPU -Reserved multicast addresses are handled by the RMA block before the L2 lookup -happens. Each address has its own register and the action field sits in bits -[5:4]: 0 forward, 1 trap to CPU, 2 drop, 3 forward excluding the CPU. The -registers are packed and consecutive by *entry* rather than by address byte, so -the step of 4 counts entries, not bytes of the address: `...:00` sits at -`0x4ecc` and `...:02` at `0x4ed4`, but `...:08` sits at `0x4ee0`. - -``` -#define RTL837X_RMA0_CONF 0x4ecc /* 01:80:C2:00:00:00, entry 0 */ -#define RTL837X_RMA2_CONF 0x4ed4 /* 01:80:C2:00:00:02, Slow Protocols */ -#define RTL837X_RMA_ACT_MASK 0x30 -#define RTL837X_RMA_ACT_TRAP 0x10 -#define RTL837X_RMA_ACT_DROP 0x20 -``` - -The engine puts this address on *trap* while it runs. A trap takes the frame -out of the forwarding path and hands it to the port named in `EXT_CPU_CTRL` -(`0x6724`), whose reset value names no port at all: the destination is meant to -be an *external* CPU on a physical port, which these boards do not have. That -is why the trap first looked like a dead end - the receive counters stayed -frozen while the partner kept sending, and the ingress discard counter grew -instead. `trap_init()` points the register at the CPU port once at startup, and -the same frames then arrive. - -`FORWARD` reaches the CPU too, because the CPU port is in the forwarding -domain, but a forwarded frame is subject to the L2 lookup and on its own floods -to every other port in the VLAN. That leaks a neighbour's LACPDUs to hosts that -must not see them and, with two bonds on one switch, poisons both; a reserved -link-local group must not be forwarded at all. Containing it takes a static L2 -multicast entry with a CPU-only member mask, and because lookups are IVL on -this chip, one entry per distinct pvid over all front panel ports - a VID 0 -entry is never matched, and a port outside the LACP groups needs its pvid -covered as well, or a LACPDU arriving there misses the lookup and is flooded -through its VLAN. - -The trap needs none of that and reaches further, because it acts on the address -before the VLAN lookup and before the ingress filters. An untagged LACPDU is -delivered on a port that admits tagged frames only, where a forwarded one is -dropped by the acceptable-frame-type filter; and it is delivered on a port STP -holds in blocking, so a group converges without waiting out the forward delay. -Both were measured against forward on the same ports of an SWTGW218AS. - -A trapped frame arrives without the VLAN tag the hardware inserts into a -forwarded one, so the LACPDU body sits four bytes earlier. `TRAP_RX_BODY()` -gives the offset of the body for either form, and `lacp_in()` reads the PDU -from there. - -Neighbouring addresses in the same block still differ in other respects - -`...:00` is exempt from the ingress STP-state check and `...:02` is not, which -the trap makes moot only because it acts before that check - so measure before -carrying a conclusion about one of them across to another. - -When LACP is switched off the action goes to `DROP`, which acts before the L2 -lookup, so there is nothing to undo. +While LACP runs, the reserved multicast action for `01:80:C2:00:00:02` is +*trap*, the same mechanism STP uses for its BPDUs, see [stp.md](stp.md). A +trapped LACPDU has no VLAN tag after the RTL tag, and `TRAP_RX_BODY()` gives the +offset of the LACPDU in the received frame. When the last group leaves LACP, +the action goes back to *drop*. ## What has to match across the members @@ -92,42 +36,30 @@ mismatch should be rejected or applied to the whole group. ## Protocol timing -``` -#define LACP_FAST_PERIODIC 0x0032 /* fast TX 1 s; partner expires at 3 s */ -#define LACP_SLOW_PERIODIC 0x05dc /* slow TX 30 s; partner expires at 90 s */ -#define LACP_SHORT_TIMEOUT 0x00c8 /* silent partner dropped after 4 s (802.3ad: 3 s) */ -``` +The timer values are in units of the LACP timer, which steps every four ticks +of the system clock, 50 times per second: -One unit is one step of the LACP timers, taken every four system ticks, so 50 -per second whatever the pace of the main loop; 50 units came out as 1.00 s on -the wire in all 116 intervals of a two minute capture. +| Constant | Value | Time | +|----------------------|-------|------| +| `LACP_FAST_PERIODIC` | 50 | 1 s | +| `LACP_SLOW_PERIODIC` | 1500 | 30 s | +| `LACP_SHORT_TIMEOUT` | 200 | 4 s | -The two transmit periods are worth a word, because they are the one thing here -that is not ours to choose freely. Our own timeouts only decide how patient we -are with a partner that has gone quiet, and being slow there costs nobody -anything. The transmit period is the opposite: it decides when the *partner* -gives up on us. 802.3ad pairs a 1 s fast period with a 3 s short timeout, and a -30 s slow period with a 90 s long timeout, and the ratio is the whole point. +802.3ad specifies a fast periodic time of 1 s with a short timeout of 3 s, and a +slow periodic time of 30 s with a long timeout of 90 s. The switch transmits at +the rate the partner asks for with its TIMEOUT bit. Its own timeout is always +the short one, one second above the standard, so a partner that stops sending +while its link stays up leaves the trunk after 4 s. -Getting that wrong does not break the aggregate, which is what makes it easy to -miss. An earlier version of this code transmitted every 2 to 3 s while the -partner was asking for the fast rate. The links still aggregated and still -carried traffic, but every time a gap crossed 3 s the partner's receive machine -timed us out and recovered: 13 of its 93 LACPDUs came back carrying `EXPIRED` -and reporting our SYNC as cleared. Since a received actor state is recorded -verbatim as the partner state and sent back out, the flap was visible from both -ends, and the bond's churn machines never settled. With the periods above, the -same capture shows no expiry at all and both churn states read `none`. - -Our own timeouts sit one period above the standard's 3 s and 90 s, which -absorbs a single lost LACPDU plus the tick jitter. A partner whose LACP has -stopped while its link stayed up is out of the trunk after that; anything -longer keeps hashing traffic onto a member that no longer agrees to carry it. +The Partner block of a transmitted LACPDU repeats the partner's actor +information as it was last received: system priority, system, key, port +priority, port and state. A Linux partner sets our SYNC only when this block +matches what it sent. ## LACP API ``` -void lacp_init(void) __banked; /* boot init: clear per-LAG state */ +void lacp_init(void) __banked; void lacp_off(void) __banked; void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked; void lacp_in(void) __banked; @@ -193,35 +125,25 @@ On the switch, with those two ports as the group: lag 1 lacp 7 8 ``` -`lacp show` should then give both ports an actor state of `0x3f`, which is -ACTIVITY, TIMEOUT, AGGREGATION, SYNC, COLLECTING and DISTRIBUTING together, a -partner state with SYNC in it, and the partner's system MAC. `lag show` should -list both ports as trunk members, since that reads the hardware rather than the -protocol's opinion of itself. +`lacp show` should then give both ports an actor state of `0x3f` (ACTIVITY, +TIMEOUT, AGGREGATION, SYNC, COLLECTING and DISTRIBUTING), a partner state with +SYNC set, and the partner's system MAC. `lag show` lists the trunk members as +programmed in the hardware and should show both ports. -The host's view is the one worth trusting, in `/proc/net/bonding/bond0`. Both -slaves want the **same `Aggregator ID`**; two different numbers mean the bond -has not aggregated anything and each link is sitting in its own group, which -looks healthy at a glance and is not. Both churn states should read `none`. -`monitoring` that never settles is the signature of our transmit period being -too slow for the rate the partner asked for. +On the host, `/proc/net/bonding/bond0` should show the same `Aggregator ID` for +both slaves; different IDs mean that each link is in a group of its own. Both +churn states should read `none`. -Three things are worth checking past convergence, because each of them has been -broken here at some point: +Further checks: -* `tcpdump -i enp1s0 ether proto 0x8809` on each slave should show the switch's - LACPDUs and that slave's own, and never the other slave's. A sibling leak is - what the trap prevents: the PDU never enters the forwarding path at all. -* No `illegal loopback` in the host's log. -* In a hex dump of a frame from the switch, the three reserved bytes after the - actor state should be zero. They used to carry whatever the transmit buffer - held from the previous frame. +* `tcpdump -i enp1s0 ether proto 0x8809` on each slave shows the LACPDUs of the + switch and of that slave, never those of the other slave. +* The host log has no `illegal loopback`. +* The three reserved bytes after the actor state in a LACPDU from the switch + are zero. -Pulling one slave down and putting it back is the cheapest failure test. The -group narrows to the surviving member and comes back within tens of seconds, -and traffic across the transition loses a few percent at the edges rather than -half of everything, which is what it would lose if the ASIC kept hashing onto -the dead member. +When one slave goes down, the group narrows to the remaining member, and the +slave rejoins after it comes back. ## Known limits diff --git a/doc/link_aggregation.md b/doc/link_aggregation.md index b725e419..90fc7637 100644 --- a/doc/link_aggregation.md +++ b/doc/link_aggregation.md @@ -227,3 +227,7 @@ iperf Done. ``` As you can see, the total throughput was 4.71 GBit/sec which is close to the maximum possible with a single 5GBit link. + +## LACP + +Dynamic aggregation with LACP is described in [lacp.md](lacp.md). From b94d2aeabc25398530af224db5aa5ac74e8a6e12 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Tue, 13 Oct 2026 01:27:33 +0200 Subject: [PATCH 22/84] port: count the learned MACs in the Connected column The column listed the MAC addresses of the L2 table entries of a port, which cost a chain of paged /l2.json requests every tenth status poll. The status JSON carries the count now, so the column reads it from there and the page does not touch the L2 table at all. The register counts the dynamic entries, so the static ones the list used to include are not in the number. --- html/app.js | 39 ++++++++++----------------------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/html/app.js b/html/app.js index 494a8463..70ea1556 100644 --- a/html/app.js +++ b/html/app.js @@ -11,7 +11,7 @@ th_sel_light:"Selenized Light",th_sel_dark:"Selenized Dark", c_port:"Port",c_name:"Name",c_link:"Link",c_apply:"Apply",c_cancel:"Cancel",c_confirm:"Confirm", c_close:"Close",c_refresh:"Refresh",c_off:"off",c_offc:"Off",c_down:"down",c_on:"On",c_auto:"Auto",c_delete:"Delete", c_type:"Type",c_state:"State",c_enabled:"Enabled",c_load:"Load",c_disable:"Disable",c_sfp:"SFP", -c_full:"full",c_half:"half",c_devices:"devices",c_yes:"yes",c_no:"no", +c_full:"full",c_half:"half",c_yes:"yes",c_no:"no", d_ports:"Ports",d_ports_h:"click a port for details",d_system:"System",d_traffic:"Traffic", d_traffic_h:"packets/s, live",d_txpps:"TX pps",d_rxpps:"RX pps",d_txbad:"TX bad",d_rxbad:"RX bad", i_host:"Hostname",i_ip:"IP address",i_mask:"Netmask",i_gw:"Gateway",i_mac:"MAC",i_fw:"Firmware", @@ -130,7 +130,7 @@ th_sel_light:"Selenized ライト",th_sel_dark:"Selenized ダーク", c_port:"ポート",c_name:"名前",c_link:"リンク",c_apply:"適用",c_cancel:"キャンセル",c_confirm:"確認", c_close:"閉じる",c_refresh:"更新",c_off:"オフ",c_offc:"オフ",c_down:"リンクダウン",c_on:"オン",c_auto:"自動",c_delete:"削除", c_type:"タイプ",c_state:"状態",c_enabled:"有効",c_load:"読み込み",c_disable:"無効化",c_sfp:"SFP", -c_full:"全二重",c_half:"半二重",c_devices:"デバイス",c_yes:"はい",c_no:"いいえ", +c_full:"全二重",c_half:"半二重",c_yes:"はい",c_no:"いいえ", d_ports:"ポート",d_ports_h:"ポートをクリックすると詳細を表示",d_system:"システム",d_traffic:"トラフィック", d_traffic_h:"パケット/秒、リアルタイム",d_txpps:"TX pps",d_rxpps:"RX pps",d_txbad:"TX 異常",d_rxbad:"RX 異常", i_host:"ホスト名",i_ip:"IP アドレス",i_mask:"ネットマスク",i_gw:"ゲートウェイ",i_mac:"MAC",i_fw:"ファームウェア", @@ -243,7 +243,7 @@ th_sel_light:"Selenized 浅色",th_sel_dark:"Selenized 深色", c_port:"端口",c_name:"名称",c_link:"链路",c_apply:"应用",c_cancel:"取消",c_confirm:"确认", c_close:"关闭",c_refresh:"刷新",c_off:"关",c_offc:"关",c_down:"未连接",c_on:"开",c_auto:"自动",c_delete:"删除", c_type:"类型",c_state:"状态",c_enabled:"启用",c_load:"读取",c_disable:"禁用",c_sfp:"SFP", -c_full:"全双工",c_half:"半双工",c_devices:"台设备",c_yes:"是",c_no:"否", +c_full:"全双工",c_half:"半双工",c_yes:"是",c_no:"否", d_ports:"端口",d_ports_h:"点击端口查看详情",d_system:"系统",d_traffic:"流量", d_traffic_h:"包/秒,实时",d_txpps:"TX pps",d_rxpps:"RX pps",d_txbad:"TX 错误",d_rxbad:"RX 错误", i_host:"主机名",i_ip:"IP 地址",i_mask:"子网掩码",i_gw:"网关",i_mac:"MAC",i_fw:"固件", @@ -356,7 +356,7 @@ th_sel_light:"Selenized claro",th_sel_dark:"Selenized oscuro", c_port:"Puerto",c_name:"Nombre",c_link:"Enlace",c_apply:"Aplicar",c_cancel:"Cancelar",c_confirm:"Confirmar", c_close:"Cerrar",c_refresh:"Actualizar",c_off:"apagado",c_offc:"Apagado",c_down:"caído",c_on:"Encendido",c_auto:"Auto",c_delete:"Eliminar", c_type:"Tipo",c_state:"Estado",c_enabled:"Habilitado",c_load:"Cargar",c_disable:"Deshabilitar",c_sfp:"SFP", -c_full:"dúplex completo",c_half:"semidúplex",c_devices:"dispositivos",c_yes:"sí",c_no:"no", +c_full:"dúplex completo",c_half:"semidúplex",c_yes:"sí",c_no:"no", d_ports:"Puertos",d_ports_h:"pulsa un puerto para ver detalles",d_system:"Sistema",d_traffic:"Tráfico", d_traffic_h:"paquetes/s, en vivo",d_txpps:"TX pps",d_rxpps:"RX pps",d_txbad:"TX erróneos",d_rxbad:"RX erróneos", i_host:"Nombre de host",i_ip:"Dirección IP",i_mask:"Máscara de red",i_gw:"Puerta de enlace",i_mac:"MAC",i_fw:"Firmware", @@ -468,7 +468,7 @@ th_sel_light:"Clair (sélénisé)",th_sel_dark:"Sombre (sélénisé)", c_port:"Port",c_name:"Nom",c_link:"Lien",c_apply:"Appliquer",c_cancel:"Annuler",c_confirm:"Confirmer", c_close:"Fermer",c_refresh:"Raffraichir",c_off:"arrêt",c_offc:"Arrêt",c_down:"arrêt",c_on:"Marche",c_auto:"Auto",c_delete:"Supprimer", c_type:"Type",c_state:"Etat",c_enabled:"Activé",c_load:"Charger",c_disable:"Désactivé",c_sfp:"SFP", -c_full:"full",c_half:"half",c_devices:"Equipements",c_yes:"oui",c_no:"non", +c_full:"full",c_half:"half",c_yes:"oui",c_no:"non", d_ports:"Ports",d_ports_h:"Cliquez ou survollez pour plus de détails",d_system:"Système",d_traffic:"Trafic", d_traffic_h:"paquets/s (en direct)",d_txpps:"TX pps",d_rxpps:"RX pps",d_txbad:"TX KO",d_rxbad:"RX KO", i_host:"Nom d'hote",i_ip:"Adresse IP",i_mask:"Masque réseau",i_gw:"Passerelle",i_mac:"Adresse MAC",i_fw:"Micro logiciel", @@ -581,7 +581,7 @@ th_sel_light:"Selenized Hell",th_sel_dark:"Selenized Dunkel", c_port:"Port",c_name:"Name",c_link:"Link",c_apply:"Übernehmen",c_cancel:"Abbrechen",c_confirm:"Bestätigen", c_close:"Schliessen",c_refresh:"Aktualisieren",c_off:"aus",c_offc:"Aus",c_down:"getrennt",c_on:"Ein",c_auto:"Auto",c_delete:"Löschen", c_type:"Typ",c_state:"Zustand",c_enabled:"Aktiviert",c_load:"Laden",c_disable:"Deaktivieren",c_sfp:"SFP", -c_full:"voll",c_half:"halb",c_devices:"Geräte",c_yes:"ja",c_no:"nein", +c_full:"voll",c_half:"halb",c_yes:"ja",c_no:"nein", d_ports:"Ports",d_ports_h:"Port anklicken für Details",d_system:"System",d_traffic:"Datenverkehr", d_traffic_h:"Pakete/s, live",d_txpps:"TX Pakete/s",d_rxpps:"RX Pakete/s",d_txbad:"TX fehlerhaft",d_rxbad:"RX fehlerhaft", i_host:"Hostname",i_ip:"IP-Adresse",i_mask:"Netzmaske",i_gw:"Gateway",i_mac:"MAC",i_fw:"Firmware", @@ -1121,31 +1121,12 @@ function loadMtu(){ }); }); } -/* One learned MAC is the attached device; several means a switch or AP - * sits behind the port, so only the count is shown. */ -function portsMacs(){ - l2Load().then(function(all){ - var by={}; - all.forEach(function(e){ - if(e.pport==="CPU")return; - var l=by[e.pport]=by[e.pport]||[]; - if(l.indexOf(e.mac)<0)l.push(e.mac); - }); - S.ports.forEach(function(p){ - var el=$("pmac"+(p.portNum-1)); - if(!el)return; - var m=by[p.portNum]||[]; - el.textContent=!m.length?"-":(m.length===1?m[0]:m.length+" "+t("c_devices")); - el.title=m.join("\n"); - }); - }).catch(function(){}); -} -var _macTick=0; function portsStatus(){ buildPorts(); - if(++_macTick%10===0)portsMacs(); S.ports.forEach(function(p){ var i=p.portNum-1,el=$("plink"+i); + var mc=$("pmac"+i); + if(mc)mc.textContent=parseInt(p.macs,16)&0x1fff; if(el)el.innerHTML=linkBadge(p); var sel=$("pspd"+i),v=p.isSFP?0:ADVSEL[parseInt(p.adv,2)]; if(sel&&v&&document.activeElement!==sel)sel.value=v; @@ -1173,8 +1154,8 @@ function applyPort(i){ postCmds(cmds).then(loadMtu).catch(function(){}); } tabHooks.ports={ - enter:function(){statusPoller.start();needPorts(function(){buildPorts();portsMacs()})}, - leave:function(){statusPoller.stop();l2Gen++}, + enter:function(){statusPoller.start();needPorts(buildPorts)}, + leave:function(){statusPoller.stop()}, status:portsStatus, }; From 8a7c847ad9d9c0368c93d6e490041c28c1a35a20 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Tue, 13 Oct 2026 01:35:03 +0200 Subject: [PATCH 23/84] tools: httpd_sim serves the MAC count per port The simulator answered /status.json without the "macs" field the Ports page now reads. It counts the dynamic entries of its own L2 table for the port, so the column and /l2.json agree and the two static entries show the difference. --- tools/httpd_sim.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/httpd_sim.c b/tools/httpd_sim.c index 76c088c3..fedd54f2 100644 --- a/tools/httpd_sim.c +++ b/tools/httpd_sim.c @@ -56,6 +56,7 @@ time_t last_session_use; uint64_t txG[PORTS], txB[PORTS], rxG[PORTS], rxB[PORTS]; char txG_buff[20], txB_buff[20], rxG_buff[20], rxB_buff[20], counter_buf[20], counter_name[20]; +char macs_buff[8]; char sfp_temp[8], sfp_vcc[8], sfp_txbias[8], sfp_txpower[8], sfp_rxpower[8], sfp_laser[8], sfp_options[6]; char num_buff[20]; char l2_idx_buff[20]; @@ -180,6 +181,12 @@ void send_status(int s) json_object_object_add(v, "txB", json_object_new_string(txB_buff)); json_object_object_add(v, "rxG", json_object_new_string(rxG_buff)); json_object_object_add(v, "rxB", json_object_new_string(rxB_buff)); + uint16_t macs = 0; + for (int e = 0; e < L2_ENTRY_NUM; e++) + if (l2_entries[e].port == physToLogPort[i-1] && l2_entries[e].type != 's') + macs++; + sprintf(macs_buff, "0x%04x", macs); + json_object_object_add(v, "macs", json_object_new_string(macs_buff)); if (i >= PORTS - NSFP) { uint16_t temp = 0x28fb + rand() / (RAND_MAX / 100); uint16_t vcc = 0x7eda + rand() / (RAND_MAX / 100); From e4adec9c1106af9d5b0898056631e46bfff4f300 Mon Sep 17 00:00:00 2001 From: Rob Emery Date: Tue, 13 Oct 2026 08:27:53 +0100 Subject: [PATCH 24/84] Renaming for consistency and spaces -> tabs --- cmd_parser.c | 4 ++-- rtl837x_regs.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd_parser.c b/cmd_parser.c index 202fd923..1cd128b5 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -2044,11 +2044,11 @@ void cmd_parser(void) __banked if (cmd_compare(1, "on")) { print_string("LLDP enabled\n"); lldp_enabled = 1; - trap_rma_set(RTL837X_RMA0_CONF_0E, RTL837X_RMA_ACT_TRAP); + trap_rma_set(RTL837X_RMA0E_CONF, RTL837X_RMA_ACT_TRAP); } else { print_string("LLDP disabled\n"); lldp_enabled = 0; - trap_rma_set(RTL837X_RMA0_CONF_0E, RTL837X_RMA_ACT_FORWARD); + trap_rma_set(RTL837X_RMA0E_CONF, RTL837X_RMA_ACT_FORWARD); } } else { diff --git a/rtl837x_regs.h b/rtl837x_regs.h index 7730e85f..c2117fb1 100644 --- a/rtl837x_regs.h +++ b/rtl837x_regs.h @@ -285,7 +285,7 @@ */ #define RTL8373_RLDP_TIMER 0x1074 #define RTL837X_RMA0_CONF 0x4ecc -#define RTL837X_RMA0_CONF_0E 0x4ee8 +#define RTL837X_RMA0E_CONF 0x4ee8 #define RTL837X_RMA2_CONF 0x4ed4 #define RTL837X_RMA_ACT_MASK 0x30 #define RTL837X_RMA_ACT_FORWARD 0x00 From 2481810906d135972c7a1935eee74bfd2c5e6b1c Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Wed, 14 Oct 2026 00:55:11 +0200 Subject: [PATCH 25/84] lacp: tidy up after review Move port_bit() to rtl837x_common.h as PORT_BIT(), a uint16_t shift, and MAC_ADDR_LEN from rtl837x_lldp.h next to it, and use MAC_ADDR_LEN for every address in the LACP code instead of a literal 6. Define lacp_mux_update() before its first caller and drop the forward declaration, declare variables at the top of their blocks, put the operator at the end of a continued line, drop the field value comments of the LACPDU struct, and leave lacp_port_lag to lacp_init() instead of initialising it in the test environment. --- httpd/page_impl.c | 4 +- rtl837x_common.h | 2 + rtl837x_lacp.c | 121 +++++++++++++++++++++++----------------------- rtl837x_lacp.h | 5 +- rtl837x_lldp.h | 1 - test/env_tables.c | 9 ++-- test/test_lacp.c | 36 +++++++------- 7 files changed, 88 insertions(+), 90 deletions(-) diff --git a/httpd/page_impl.c b/httpd/page_impl.c index e1f43f6e..183510d0 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -752,7 +752,7 @@ void send_lacp(void) slen += strtox(outbuf + slen, "\",\"aggValid\":"); bool_to_html(lacp_agg_valid[l]); slen += strtox(outbuf + slen, ",\"agg\":\""); - for (uint8_t j = 0; j < 6; j++) + for (uint8_t j = 0; j < MAC_ADDR_LEN; j++) byte_to_html(lacp_agg_sys[l][j]); slen += strtox(outbuf + slen, "\",\"members\":\""); byte_to_html(lacp_members_last[l] >> 8); @@ -776,7 +776,7 @@ void send_lacp(void) byte_to_html(lacp_rx_count[i] >> 8); byte_to_html(lacp_rx_count[i]); slen += strtox(outbuf + slen, "\",\"psys\":\""); - for (uint8_t j = 0; j < 6; j++) + for (uint8_t j = 0; j < MAC_ADDR_LEN; j++) byte_to_html(lacp_partner_sys[i][j]); slen += strtox(outbuf + slen, "\"},"); } diff --git a/rtl837x_common.h b/rtl837x_common.h index a36d41fc..a9747a18 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -16,6 +16,8 @@ #define TICKS_DUE(last, period) ((uint8_t)((uint8_t)ticks - (last)) >= (period)) #define CPU_PORT 9 +#define MAC_ADDR_LEN 6 +#define PORT_BIT(p) ((uint16_t)1 << (p)) #define NUL '\0' // Define Port-masks for 9-port devices and 6-port devices diff --git a/rtl837x_lacp.c b/rtl837x_lacp.c index 5a14e3b4..c1bf4dc2 100644 --- a/rtl837x_lacp.c +++ b/rtl837x_lacp.c @@ -26,7 +26,7 @@ extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2]; /* 20-byte Actor/Partner information body (802.3ad 43.4.2.2), network byte order */ struct lacp_info { uint16_t sys_prio; - uint8_t sys[6]; + uint8_t sys[MAC_ADDR_LEN]; uint16_t key; uint16_t port_prio; uint16_t port; @@ -36,32 +36,32 @@ struct lacp_info { /* Outgoing LACPDU (no VLAN tag on TX, as in stp_pkt) */ struct lacpdu { - uint8_t dst[6]; - uint8_t src[6]; + uint8_t dst[MAC_ADDR_LEN]; + uint8_t src[MAC_ADDR_LEN]; struct rtl_tag rtl_tag; - uint16_t ethertype; /* 0x8809 */ - uint8_t subtype; /* 0x01 */ - uint8_t version; /* 0x01 */ - uint8_t tlv_actor; /* 0x01 */ - uint8_t actor_len; /* 0x14 */ + uint16_t ethertype; + uint8_t subtype; + uint8_t version; + uint8_t tlv_actor; + uint8_t actor_len; struct lacp_info actor; - uint8_t tlv_partner; /* 0x02 */ - uint8_t partner_len; /* 0x14 */ + uint8_t tlv_partner; + uint8_t partner_len; struct lacp_info partner; - uint8_t tlv_collector; /* 0x03 */ - uint8_t collector_len; /* 0x10 */ + uint8_t tlv_collector; + uint8_t collector_len; uint16_t collector_max_delay; uint8_t collector_reserved[12]; - uint8_t tlv_terminator; /* 0x00 */ - uint8_t terminator_len; /* 0x00 */ + uint8_t tlv_terminator; + uint8_t terminator_len; uint8_t terminator_reserved[50]; }; /* Incoming LACPDU, split because a VLAN tag sits between the two only when the * frame was forwarded: a trapped one carries none (cf. stp_pkt_in) */ struct lacpdu_in_hdr { - uint8_t dst[6]; - uint8_t src[6]; + uint8_t dst[MAC_ADDR_LEN]; + uint8_t src[MAC_ADDR_LEN]; struct rtl_tag rtl_tag; }; @@ -86,7 +86,7 @@ __xdata uint8_t lacp_rxbody; __xdata uint8_t lacp_actor_state[10]; __xdata uint8_t lacp_rx_state[10]; __xdata uint8_t lacp_partner_state[10]; -__xdata uint8_t lacp_partner_sys[10][6]; +__xdata uint8_t lacp_partner_sys[10][MAC_ADDR_LEN]; __xdata uint16_t lacp_partner_key[10]; __xdata uint16_t lacp_partner_port[10]; __xdata uint16_t lacp_partner_sys_prio[10]; @@ -102,7 +102,7 @@ extern volatile __xdata uint32_t ticks; __xdata uint16_t lacp_lag_ports[LACP_NUM_LAGS]; __xdata uint8_t lacp_port_lag[10]; -__xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][6]; +__xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][MAC_ADDR_LEN]; __xdata uint8_t lacp_agg_valid[LACP_NUM_LAGS]; __xdata uint16_t lacp_members_last[LACP_NUM_LAGS]; @@ -113,11 +113,9 @@ __xdata uint8_t lacp_scratch_lag; __xdata uint16_t lacp_rx_count[10]; -void lacp_mux_update(void) __banked; - static uint8_t lacp_sys_eq(__xdata uint8_t *a, __xdata uint8_t *b) { - for (uint8_t i = 0; i < 6; i++) { + for (uint8_t i = 0; i < MAC_ADDR_LEN; i++) { if (a[i] != b[i]) return 0; } @@ -161,21 +159,41 @@ static uint8_t lacp_mux_machine(uint8_t port) return 1; } -#define port_bit(p) (((uint8_t)1) << (p)) +void lacp_mux_update(void) __banked +{ + for (uint8_t lag = 0; lag < LACP_NUM_LAGS; lag++) { + if (!lacp_lag_ports[lag]) + continue; + lacp_scratch_mask = 0; + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + if (lacp_port_lag[i] != lag) + continue; + if ((lacp_actor_state[i] & LACP_STATE_FULL) == LACP_STATE_FULL + && (lacp_partner_state[i] & LACP_STATE_SYNC)) + lacp_scratch_mask |= PORT_BIT(i); + } + if (lacp_scratch_mask != lacp_members_last[lag]) { + lacp_members_last[lag] = lacp_scratch_mask; + port_lag_members_set(lag, lacp_scratch_mask); + } + } +} void lacp_send(uint8_t port) __banked { + uint16_t saved_mgmt_vlan; + memset((__xdata uint8_t *)LACP_O, 0, sizeof(struct lacpdu)); LACP_O->dst[0] = 0x01; LACP_O->dst[1] = 0x80; LACP_O->dst[2] = 0xc2; LACP_O->dst[3] = 0x00; LACP_O->dst[4] = 0x00; LACP_O->dst[5] = LACP_DST5; - memcpy(LACP_O->src, uip_ethaddr.addr, 6); + memcpy(LACP_O->src, uip_ethaddr.addr, MAC_ADDR_LEN); LACP_O->rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID); LACP_O->rtl_tag.version = RTL_FRAME_TAG_VERSION; LACP_O->rtl_tag.reason = 0x00; LACP_O->rtl_tag.flags = HTONS(RTL_TAG_LEARN_DIS | RTL_TAG_KEEP); - LACP_O->rtl_tag.pmask = HTONS(port_bit(port)); + LACP_O->rtl_tag.pmask = HTONS(PORT_BIT(port)); LACP_O->ethertype = HTONS(SLOW_PROTO_ETHERTYPE); LACP_O->subtype = SLOW_PROTO_SUBTYPE_LACP; @@ -184,7 +202,7 @@ void lacp_send(uint8_t port) __banked LACP_O->tlv_actor = LACP_TLV_ACTOR; LACP_O->actor_len = LACP_TLV_LEN_INFO; LACP_O->actor.sys_prio = HTONS(LACP_SYS_PRIO); - memcpy(LACP_O->actor.sys, uip_ethaddr.addr, 6); + memcpy(LACP_O->actor.sys, uip_ethaddr.addr, MAC_ADDR_LEN); LACP_O->actor.key = HTONS((uint16_t)(lacp_port_lag[port] + 1)); LACP_O->actor.port_prio = HTONS(LACP_DEF_PORT_PRIO); LACP_O->actor.port = HTONS((uint16_t)port + 1); @@ -193,7 +211,7 @@ void lacp_send(uint8_t port) __banked LACP_O->tlv_partner = LACP_TLV_PARTNER; LACP_O->partner_len = LACP_TLV_LEN_INFO; LACP_O->partner.sys_prio = HTONS(lacp_partner_sys_prio[port]); - memcpy(LACP_O->partner.sys, lacp_partner_sys[port], 6); + memcpy(LACP_O->partner.sys, lacp_partner_sys[port], MAC_ADDR_LEN); LACP_O->partner.key = HTONS(lacp_partner_key[port]); LACP_O->partner.port_prio = HTONS(lacp_partner_port_prio[port]); LACP_O->partner.port = HTONS(lacp_partner_port[port]); @@ -206,7 +224,7 @@ void lacp_send(uint8_t port) __banked LACP_O->terminator_len = 0x00; lacp_ntt[port] = 0; - uint16_t saved_mgmt_vlan = management_vlan; + saved_mgmt_vlan = management_vlan; management_vlan = 0; uip_len = sizeof(struct lacpdu); tcpip_output(); @@ -247,7 +265,7 @@ void lacp_in(void) __banked lacp_rx_count[port]++; - memcpy(lacp_partner_sys[port], LACP_I->actor.sys, 6); + memcpy(lacp_partner_sys[port], LACP_I->actor.sys, MAC_ADDR_LEN); lacp_scratch_mask = HTONS(LACP_I->actor.key); lacp_partner_key[port] = lacp_scratch_mask; lacp_scratch_mask = HTONS(LACP_I->actor.port); @@ -262,7 +280,7 @@ void lacp_in(void) __banked lacp_timeout[port] = LACP_SHORT_TIMEOUT; if (!lacp_agg_valid[lag]) { - memcpy(lacp_agg_sys[lag], LACP_I->actor.sys, 6); + memcpy(lacp_agg_sys[lag], LACP_I->actor.sys, MAC_ADDR_LEN); lacp_agg_valid[lag] = 1; } @@ -277,26 +295,6 @@ void lacp_in(void) __banked } -void lacp_mux_update(void) __banked -{ - for (uint8_t lag = 0; lag < LACP_NUM_LAGS; lag++) { - if (!lacp_lag_ports[lag]) - continue; - lacp_scratch_mask = 0; - for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { - if (lacp_port_lag[i] != lag) - continue; - if ((lacp_actor_state[i] & LACP_STATE_FULL) == LACP_STATE_FULL - && (lacp_partner_state[i] & LACP_STATE_SYNC)) - lacp_scratch_mask |= port_bit(i); - } - if (lacp_scratch_mask != lacp_members_last[lag]) { - lacp_members_last[lag] = lacp_scratch_mask; - port_lag_members_set(lag, lacp_scratch_mask); - } - } -} - static void lacp_step(void) { @@ -307,8 +305,8 @@ static void lacp_step(void) if (lacp_periodic[i]) { lacp_periodic[i]--; } else { - lacp_periodic[i] = (lacp_partner_state[i] & LACP_STATE_TIMEOUT) - ? LACP_FAST_PERIODIC : LACP_SLOW_PERIODIC; + lacp_periodic[i] = (lacp_partner_state[i] & LACP_STATE_TIMEOUT) ? + LACP_FAST_PERIODIC : LACP_SLOW_PERIODIC; lacp_ntt[i] = 1; } @@ -326,9 +324,10 @@ static void lacp_step(void) } for (uint8_t lag = 0; lag < LACP_NUM_LAGS; lag++) { + uint8_t any_current = 0; + if (!lacp_agg_valid[lag]) continue; - uint8_t any_current = 0; for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { if (lacp_port_lag[i] == lag && lacp_rx_state[i] == LACP_RX_CURRENT && lacp_sys_eq(lacp_partner_sys[i], lacp_agg_sys[lag])) { @@ -356,11 +355,11 @@ void lacp_timers(void) __banked static void lacp_port_init(uint8_t port) { - lacp_actor_state[port] = LACP_STATE_ACTIVITY | LACP_STATE_AGGREGATION - | LACP_STATE_TIMEOUT; + lacp_actor_state[port] = LACP_STATE_ACTIVITY | LACP_STATE_AGGREGATION | + LACP_STATE_TIMEOUT; lacp_rx_state[port] = LACP_RX_INITIALIZE; lacp_partner_state[port] = LACP_STATE_DEFAULTED; - memset(lacp_partner_sys[port], 0, 6); + memset(lacp_partner_sys[port], 0, MAC_ADDR_LEN); lacp_partner_key[port] = 0; lacp_partner_port[port] = 0; lacp_periodic[port] = LACP_FAST_PERIODIC; @@ -376,7 +375,7 @@ static void lacp_port_release(uint8_t port) lacp_partner_state[port] = 0; lacp_rx_state[port] = LACP_RX_INITIALIZE; lacp_rx_count[port] = 0; - memset(lacp_partner_sys[port], 0, 6); + memset(lacp_partner_sys[port], 0, MAC_ADDR_LEN); } static uint8_t lacp_any_lag(void) @@ -424,7 +423,7 @@ void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked lacp_scratch_flag = lacp_any_lag(); for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { - if (lacp_port_lag[i] == lag && !(ports & port_bit(i))) { + if (lacp_port_lag[i] == lag && !(ports & PORT_BIT(i))) { lacp_port_lag[i] = LACP_LAG_NONE; lacp_port_release(i); } @@ -440,12 +439,12 @@ void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked lacp_engine_on(); for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { - if (!(ports & port_bit(i))) + if (!(ports & PORT_BIT(i))) continue; lacp_scratch_lag = lacp_port_lag[i]; lacp_port_lag[i] = lag; if (lacp_scratch_lag != LACP_LAG_NONE && lacp_scratch_lag != lag) { - lacp_scratch_mask = lacp_lag_ports[lacp_scratch_lag] & ~port_bit(i); + lacp_scratch_mask = lacp_lag_ports[lacp_scratch_lag] & ~PORT_BIT(i); lacp_lag_ports[lacp_scratch_lag] = lacp_scratch_mask; if (!lacp_scratch_mask) { lacp_agg_valid[lacp_scratch_lag] = 0; @@ -483,7 +482,7 @@ void lacp_show(void) __banked print_string(" ports "); print_short(lacp_lag_ports[l]); print_string(" aggregator "); if (lacp_agg_valid[l]) { - for (uint8_t j = 0; j < 6; j++) + for (uint8_t j = 0; j < MAC_ADDR_LEN; j++) print_byte(lacp_agg_sys[l][j]); } else { print_string("(none)"); @@ -498,7 +497,7 @@ void lacp_show(void) __banked print_string(" rxst "); print_byte(lacp_rx_state[i]); print_string(" rx "); print_short(lacp_rx_count[i]); print_string(" psys "); - for (uint8_t j = 0; j < 6; j++) + for (uint8_t j = 0; j < MAC_ADDR_LEN; j++) print_byte(lacp_partner_sys[i][j]); write_char('\n'); } diff --git a/rtl837x_lacp.h b/rtl837x_lacp.h index 44e3ad4b..a03c4c69 100644 --- a/rtl837x_lacp.h +++ b/rtl837x_lacp.h @@ -7,6 +7,7 @@ */ #include +#include "rtl837x_common.h" void lacp_init(void) __banked; void lacp_in(void) __banked; @@ -22,11 +23,11 @@ extern __xdata uint8_t lacpEnabled; extern __xdata uint8_t lacp_actor_state[10]; extern __xdata uint8_t lacp_partner_state[10]; extern __xdata uint8_t lacp_rx_state[10]; -extern __xdata uint8_t lacp_partner_sys[10][6]; +extern __xdata uint8_t lacp_partner_sys[10][MAC_ADDR_LEN]; extern __xdata uint16_t lacp_rx_count[10]; extern __xdata uint8_t lacp_port_lag[10]; extern __xdata uint16_t lacp_lag_ports[LACP_NUM_LAGS]; -extern __xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][6]; +extern __xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][MAC_ADDR_LEN]; extern __xdata uint8_t lacp_agg_valid[LACP_NUM_LAGS]; extern __xdata uint16_t lacp_members_last[LACP_NUM_LAGS]; diff --git a/rtl837x_lldp.h b/rtl837x_lldp.h index 298775c6..0e6ee674 100644 --- a/rtl837x_lldp.h +++ b/rtl837x_lldp.h @@ -7,7 +7,6 @@ #define LLDP_ETHERTYPE 0x88cc #define LLDP_ETHERTYPE_LENGTH 2 #define LLDP_MAX_FRAME 256 -#define MAC_ADDR_LEN 6 #define LLDP_TX_INTERVAL_TICKS 6000 #define LLDP_PORT_ID_TLV_TYPE 2 diff --git a/test/env_tables.c b/test/env_tables.c index 6a841a19..719e045a 100644 --- a/test/env_tables.c +++ b/test/env_tables.c @@ -130,13 +130,10 @@ uint8_t lacpEnabled; uint8_t lacp_actor_state[10]; uint8_t lacp_partner_state[10]; uint8_t lacp_rx_state[10]; -uint8_t lacp_partner_sys[10][6]; +uint8_t lacp_partner_sys[10][MAC_ADDR_LEN]; uint16_t lacp_rx_count[10]; -uint8_t lacp_port_lag[10] = { - LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, - LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, -}; +uint8_t lacp_port_lag[10]; uint16_t lacp_lag_ports[LACP_NUM_LAGS]; -uint8_t lacp_agg_sys[LACP_NUM_LAGS][6]; +uint8_t lacp_agg_sys[LACP_NUM_LAGS][MAC_ADDR_LEN]; uint8_t lacp_agg_valid[LACP_NUM_LAGS]; uint16_t lacp_members_last[LACP_NUM_LAGS]; diff --git a/test/test_lacp.c b/test/test_lacp.c index f0ab5f3f..dbadea04 100644 --- a/test/test_lacp.c +++ b/test/test_lacp.c @@ -56,7 +56,7 @@ void trap_rma_set(uint16_t reg, uint8_t act) struct wire_info { uint16_t sys_prio; - uint8_t sys[6]; + uint8_t sys[MAC_ADDR_LEN]; uint16_t key; uint16_t port_prio; uint16_t port; @@ -65,8 +65,8 @@ struct wire_info { }; struct wire_pdu_out { /* what lacp_send() writes at uip_buf+12 */ - uint8_t dst[6]; - uint8_t src[6]; + uint8_t dst[MAC_ADDR_LEN]; + uint8_t src[MAC_ADDR_LEN]; struct rtl_tag rtl_tag; uint16_t ethertype; uint8_t subtype; @@ -83,8 +83,8 @@ struct wire_pdu_out { /* what lacp_send() writes at uip_buf+12 */ }; struct wire_pdu_in { /* forwarded form: the hardware inserts a VLAN tag */ - uint8_t dst[6]; - uint8_t src[6]; + uint8_t dst[MAC_ADDR_LEN]; + uint8_t src[MAC_ADDR_LEN]; struct rtl_tag rtl_tag; struct vlan_tag vlan_tag; uint16_t ethertype; @@ -123,14 +123,14 @@ void tcpip_output(void) /* Deliver a LACPDU into the switch on `port`, from partner system `sys`. * pstate = partner's Actor_State flags. If `echo`, the partner echoes the * switch's last-seen actor block back (fresh view) - required for quiescence. */ -static void partner_frame_full(uint8_t port, const uint8_t sys[6], uint8_t pstate, +static void partner_frame_full(uint8_t port, const uint8_t sys[MAC_ADDR_LEN], uint8_t pstate, int echo, uint16_t len, int tagged) { struct wire_pdu_in in; memset(&in, 0, sizeof(in)); in.dst[0]=0x01; in.dst[1]=0x80; in.dst[2]=0xc2; in.dst[5]=0x02; - memcpy(in.src, sys, 6); + memcpy(in.src, sys, MAC_ADDR_LEN); in.rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID); in.rtl_tag.pmask = HTONS(port); /* RX: 4-bit ingress port */ in.vlan_tag.svlan = HTONS(0x8100); /* the tag's TPID, which marks the body offset */ @@ -140,7 +140,7 @@ static void partner_frame_full(uint8_t port, const uint8_t sys[6], uint8_t pstat in.tlv_actor = 0x01; in.actor_len = 0x14; in.actor.sys_prio = HTONS(0x8000); - memcpy(in.actor.sys, sys, 6); + memcpy(in.actor.sys, sys, MAC_ADDR_LEN); in.actor.key = HTONS(0x0011); in.actor.port_prio = HTONS(0x00ff); in.actor.port = HTONS((uint16_t)port + 101); @@ -149,7 +149,7 @@ static void partner_frame_full(uint8_t port, const uint8_t sys[6], uint8_t pstat in.tlv_partner = 0x02; in.partner_len = 0x14; if (echo) { /* fresh view of us, from our last TX */ in.partner.sys_prio = last_tx[port].actor.sys_prio; - memcpy(in.partner.sys, last_tx[port].actor.sys, 6); + memcpy(in.partner.sys, last_tx[port].actor.sys, MAC_ADDR_LEN); in.partner.key = last_tx[port].actor.key; in.partner.port = last_tx[port].actor.port; in.partner.state = last_tx[port].actor.state; @@ -166,18 +166,18 @@ static void partner_frame_full(uint8_t port, const uint8_t sys[6], uint8_t pstat lacp_in(); } -static void partner_frame_len(uint8_t port, const uint8_t sys[6], uint8_t pstate, int echo, uint16_t len) +static void partner_frame_len(uint8_t port, const uint8_t sys[MAC_ADDR_LEN], uint8_t pstate, int echo, uint16_t len) { partner_frame_full(port, sys, pstate, echo, len, 1); } -static void partner_frame(uint8_t port, const uint8_t sys[6], uint8_t pstate, int echo) +static void partner_frame(uint8_t port, const uint8_t sys[MAC_ADDR_LEN], uint8_t pstate, int echo) { partner_frame_full(port, sys, pstate, echo, sizeof(struct wire_pdu_in), 1); } /* The same LACPDU as the reserved-multicast trap delivers it. */ -static void partner_frame_trap(uint8_t port, const uint8_t sys[6], uint8_t pstate, int echo) +static void partner_frame_trap(uint8_t port, const uint8_t sys[MAC_ADDR_LEN], uint8_t pstate, int echo) { partner_frame_full(port, sys, pstate, echo, sizeof(struct wire_pdu_in), 0); } @@ -201,8 +201,8 @@ static int failures; #define P_DIST LACP_STATE_DISTRIBUTING #define P_TO LACP_STATE_TIMEOUT /* partner requests short (fast) timeout */ -static const uint8_t SYS_A[6] = {0x02,0xaa,0xaa,0xaa,0xaa,0x01}; -static const uint8_t SYS_B[6] = {0x02,0xbb,0xbb,0xbb,0xbb,0x02}; +static const uint8_t SYS_A[MAC_ADDR_LEN] = {0x02,0xaa,0xaa,0xaa,0xaa,0x01}; +static const uint8_t SYS_B[MAC_ADDR_LEN] = {0x02,0xbb,0xbb,0xbb,0xbb,0x02}; int main(int argc, char **argv) { @@ -342,11 +342,11 @@ int main(int argc, char **argv) * carries, so its body sits four bytes earlier. Both forms must parse. */ uint16_t rx_before = lacp_rx_count[0]; lacp_partner_state[0] = 0; /* all three are written past the ethertype gate */ - memset(lacp_partner_sys[0], 0, 6); + memset(lacp_partner_sys[0], 0, MAC_ADDR_LEN); partner_frame_trap(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); int trap_ok = lacp_rx_count[0] == (uint16_t)(rx_before + 1) && lacp_partner_state[0] == (P_ACT|P_AGG|P_TO|P_SYNC) - && !memcmp(lacp_partner_sys[0], SYS_A, 6); + && !memcmp(lacp_partner_sys[0], SYS_A, MAC_ADDR_LEN); partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); CHECK(trap_ok && lacp_rx_count[0] == (uint16_t)(rx_before + 2), "T15 trapped LACPDU parses like a forwarded one"); @@ -376,7 +376,7 @@ int main(int argc, char **argv) partner_frame(1, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1); step(4 * 40); } - CHECK(stayed_out && hw_members == 0x0003 && !memcmp(lacp_agg_sys[0], SYS_B, 6), + CHECK(stayed_out && hw_members == 0x0003 && !memcmp(lacp_agg_sys[0], SYS_B, MAC_ADDR_LEN), "T16 re-cabling one port at a time: aggregator moves from A to B"); /* T17: a group that already has a STATIC trunk is put under LACP with the @@ -420,7 +420,7 @@ int main(int argc, char **argv) int had_partner = lacp_rx_state[4] == LACP_RX_CURRENT && lacp_rx_count[4] > 0; lacp_lag_set(1, 0); CHECK(had_partner && lacp_partner_state[4] == 0 && lacp_rx_state[4] == LACP_RX_INITIALIZE - && lacp_rx_count[4] == 0 && !memcmp(lacp_partner_sys[4], "\0\0\0\0\0\0", 6), + && lacp_rx_count[4] == 0 && !memcmp(lacp_partner_sys[4], "\0\0\0\0\0\0", MAC_ADDR_LEN), "T20 release: partner state, rx state, counter and system cleared"); /* T21: applying the same candidate set again (the LAG page resends it From b096efd63a4eaebd01a6a43b894d4f1d10c2a24f Mon Sep 17 00:00:00 2001 From: HL Yi Date: Tue, 13 Oct 2026 23:48:28 -0500 Subject: [PATCH 26/84] symlink HASIVO_S1100WP_8GT_1SX_SE.c to SWTGW218AS.c, FNS1200P.c to PCB_K0402WS_V2.c, DEFAULT_8C_1SFP.c to KP_9000_9XH_X_EU.c, as only differences are white space and comments. add SKS3200_8E1X --- machine/DEFAULT_8C_1SFP.c | 26 +------------- machine/FNS1200P.c | 56 +---------------------------- machine/HASIVO_S1100WP_8GT_1SX_SE.c | 33 +---------------- machine/PCB_K0402WS_V2.c | 2 +- machine/SKS3200_8E1X.c | 1 + machine/SKS3200_8E1X.h | 7 ++++ 6 files changed, 12 insertions(+), 113 deletions(-) mode change 100644 => 120000 machine/DEFAULT_8C_1SFP.c mode change 100644 => 120000 machine/FNS1200P.c mode change 100644 => 120000 machine/HASIVO_S1100WP_8GT_1SX_SE.c create mode 120000 machine/SKS3200_8E1X.c create mode 100644 machine/SKS3200_8E1X.h diff --git a/machine/DEFAULT_8C_1SFP.c b/machine/DEFAULT_8C_1SFP.c deleted file mode 100644 index 2a923596..00000000 --- a/machine/DEFAULT_8C_1SFP.c +++ /dev/null @@ -1,25 +0,0 @@ -#include "machine_defs.h" - -__code const struct machine machine = { - .machine_name = MACHINE_MODEL, - .isRTL8373 = 1, - .min_port = 0, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, - .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO37, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - .reset_pin = GPIO_NA, - .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 0}, - .led_sets = { { LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5, - LEDS_2G5 | LEDS_LINK, - LEDS_1G | LEDS_LINK, - LEDS_2G5 | LEDS_LINK | LEDS_ACT }, - }, -}; diff --git a/machine/DEFAULT_8C_1SFP.c b/machine/DEFAULT_8C_1SFP.c new file mode 120000 index 00000000..f00039af --- /dev/null +++ b/machine/DEFAULT_8C_1SFP.c @@ -0,0 +1 @@ +KP_9000_9XH_X_EU.c \ No newline at end of file diff --git a/machine/FNS1200P.c b/machine/FNS1200P.c deleted file mode 100644 index 71334029..00000000 --- a/machine/FNS1200P.c +++ /dev/null @@ -1,55 +0,0 @@ -#include "machine_defs.h" - -__code const struct machine machine = { - .machine_name = MACHINE_MODEL, - .isRTL8373 = 0, - .min_port = 3, - .max_port = 8, - .n_sfp = 2, - .log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5}, - .phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0}, - .is_sfp = {0, 0, 0, 2, 0, 0, 0, 0, 1}, - - /* Left SFP (logical 8, SDS1): GPIO30=ModAbs, GPIO37=RX_LOS */ - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO37, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - - /* Right SFP (logical 3, SDS0): GPIO50=ModAbs, GPIO51=RX_LOS */ - .sfp_port[1].pin_detect = GPIO50_I2C_SCL2_UART1_TX, - .sfp_port[1].pin_los = GPIO51_I2C_SDA2_UART1_RX, - .sfp_port[1].pin_tx_disable = GPIO_NA, - .sfp_port[1].sds = 0, - .sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), - - .reset_pin = GPIO_NA, - - .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, - - /* Copper ports use SET0; SFP ports use SET1 */ - .port_led_set = {0, 0, 0, 1, 0, 0, 0, 0, 1}, - - .led_sets = { - { /* SET0: copper — LED0=amber (2.5G), LED2=green (1G/100M/10M) */ - LEDS_2G5 | LEDS_LINK | LEDS_ACT, - 0, - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - 0 - }, - { /* SET1: SFP — all speeds link/act */ - LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, - 0, - 0, 0 - }, - }, - - .led_mux_custom = 1, - .led_mux = { - 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, /* GPIO0-7: unused */ - 0x0f, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x12, /* GPIO8-14 */ - 0x14, 0x15, 0x16, 0x18, 0x19, 0x1a, /* GPIO15-20 */ - 0x1c, 0x1d, 0x1e, 0x20, 0x21, 0x22, 0x23 /* GPIO21-27 */ - }, -}; diff --git a/machine/FNS1200P.c b/machine/FNS1200P.c new file mode 120000 index 00000000..c836c871 --- /dev/null +++ b/machine/FNS1200P.c @@ -0,0 +1 @@ +PCB_K0402WS_V2.c \ No newline at end of file diff --git a/machine/HASIVO_S1100WP_8GT_1SX_SE.c b/machine/HASIVO_S1100WP_8GT_1SX_SE.c deleted file mode 100644 index 7500ba94..00000000 --- a/machine/HASIVO_S1100WP_8GT_1SX_SE.c +++ /dev/null @@ -1,32 +0,0 @@ -#include "machine_defs.h" - -__code const struct machine machine = { - .machine_name = MACHINE_MODEL, - .isRTL8373 = 1, - .mac_flash_offset = 0x1FC000, - .min_port = 0, - .max_port = 8, - .n_sfp = 1, - .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, - .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, - .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, - .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, - .sfp_port[0].pin_los = GPIO37, - .sfp_port[0].pin_tx_disable = GPIO_NA, - .sfp_port[0].sds = 1, - .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), - .reset_pin = GPIO54_ACL_BIT2_EN, - .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, - .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, - .led_sets = { { LEDS_2G5 | LEDS_LINK | LEDS_ACT, // Green LED (right) - 0, - LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // Amber LED (left) - 0 - }, - { LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT, // SFP LED - 0, - 0, - 0 - }, - }, -}; diff --git a/machine/HASIVO_S1100WP_8GT_1SX_SE.c b/machine/HASIVO_S1100WP_8GT_1SX_SE.c new file mode 120000 index 00000000..1fc0cdb2 --- /dev/null +++ b/machine/HASIVO_S1100WP_8GT_1SX_SE.c @@ -0,0 +1 @@ +SWTGW218AS.c \ No newline at end of file diff --git a/machine/PCB_K0402WS_V2.c b/machine/PCB_K0402WS_V2.c index c37e6201..71334029 100644 --- a/machine/PCB_K0402WS_V2.c +++ b/machine/PCB_K0402WS_V2.c @@ -1,7 +1,7 @@ #include "machine_defs.h" __code const struct machine machine = { - .machine_name = MACHINE_MODEL, + .machine_name = MACHINE_MODEL, .isRTL8373 = 0, .min_port = 3, .max_port = 8, diff --git a/machine/SKS3200_8E1X.c b/machine/SKS3200_8E1X.c new file mode 120000 index 00000000..2eb54be2 --- /dev/null +++ b/machine/SKS3200_8E1X.c @@ -0,0 +1 @@ +PCB_SWTG018AS_V2_1_0.c \ No newline at end of file diff --git a/machine/SKS3200_8E1X.h b/machine/SKS3200_8E1X.h new file mode 100644 index 00000000..e644baf4 --- /dev/null +++ b/machine/SKS3200_8E1X.h @@ -0,0 +1,7 @@ +#ifndef _MACHINE_SKS3200_8E1X_H_ +#define _MACHINE_SKS3200_8E1X_H_ + +#define MACHINE_MODEL "XikeStor SKS3200-8E1X" + +#endif + From 813a62ca7d87f24fd43a3800ec574d2f24874053 Mon Sep 17 00:00:00 2001 From: HL Yi Date: Wed, 14 Oct 2026 00:11:01 -0500 Subject: [PATCH 27/84] add document for adding a new device. clean up existing document due to split machin.c --- README.md | 20 ++-- doc/adding-a-device.md | 183 +++++++++++++++++++++++++++++++++++ doc/devices/2M-PCB23-V3_2.md | 4 +- doc/devices/2M-PCB43-V1.1.md | 2 +- doc/devices/SWTGW215AS.md | 2 +- 5 files changed, 200 insertions(+), 11 deletions(-) create mode 100644 doc/adding-a-device.md diff --git a/README.md b/README.md index 6eaf74e8..3d61c1e5 100644 --- a/README.md +++ b/README.md @@ -103,24 +103,29 @@ docker run --rm -p 8080:8080 -v $(pwd):/workspace rtlplayground-dev \ tools/output/httpd_sim /workspace/html ``` -Edit `machine.h` or `config.txt` on your host, then re-run `make` — the +Edit `config.txt` on your host, then re-run `make` — the source directory is mounted into the container, so changes take effect -immediately. To build for a different machine, pass `MACHINE=...`. +immediately. To build for a different machine, pass `MACHINE=`, which +selects `machine/.c` (see [Adding a new device](doc/adding-a-device.md)). ## (1) Compiling for direct chip flashing AND upgrading an existing RTLPlayground running device -Edit machine.h with an editor like vi or nano. Select the correct machine the firmware should build for. +Pass the machine that matches your device, for example: +``` +make MACHINE=DEFAULT_8C_1SFP +``` +The name selects the board definition `machine/.c`; `make` without a +`MACHINE` value stops with an error listing what is expected. If your board is +not in the list yet, see [Adding a new device](doc/adding-a-device.md). > [!TIP] > You can write configuration parameters in config.txt (see below) in order your switch to get > straight at the first boot, a correct IP configuration. -Now, building the firmware image should work: -``` -make -``` +Now, building the firmware image should work. + Note, that the image generated ends in .bin, not .img, in order to make IMSProg happy. image location is stored in `RTLPlayground/output/rtlplayground_version_machine.bin` @@ -353,6 +358,7 @@ Enjoy playing! ## (11) Other documents The following documents give further documentation on specific features of the RTL837x SoCs: +- [Adding a new device](doc/adding-a-device.md) - [RTL8372/3 Feature support](doc/hardware.md) - [CPU Port](doc/CpuPort.md) - [L2 learning](doc/l2.md) diff --git a/doc/adding-a-device.md b/doc/adding-a-device.md new file mode 100644 index 00000000..8992b512 --- /dev/null +++ b/doc/adding-a-device.md @@ -0,0 +1,183 @@ +# Adding a new device + +A device is supported by adding one board definition under `machine/`. +the build selects the definition by name, and CI compiles every file it finds. + +``` +make MACHINE= +``` + +`MACHINE=` selects `machine/.c` and is passed on to the compiler as +both `-DMACHINE_` (for the `#if defined(MACHINE_...)` blocks in the rest +of the firmware) and `-DMACHINE_NAME=` (which `machine.h` uses to include +`machine/.h`). The two always take the same value, so they cannot drift. + +## What a new device consists of + +| File | Purpose | +|------|---------| +| `machine/.c` | the `struct machine machine` board definition | +| `machine/.h` | `MACHINE_MODEL`, the model string shown in the UI and the CLI | +| `doc/devices/.md` + `doc/supported_devices.md` | (only if the board is verified) user-facing documentation | + +`` is the machine symbol without its `MACHINE_` prefix, so `MACHINE` is +passed exactly as spelled on the command line: `make MACHINE=MY_BOARD_1_0` +needs `machine/MY_BOARD_1_0.c` and `machine/MY_BOARD_1_0.h`. + +## Step 1: the model header + +`machine/.h` only carries the product string. Use the existing files as +the template: + +```c +#ifndef _MACHINE_MY_BOARD_1_0_H_ +#define _MACHINE_MY_BOARD_1_0_H_ + +#define MACHINE_MODEL "Brand Model V1.0" + +#endif +``` + +The header is what the UI and the CLI print; `machine.h` includes it by name. +Nothing else belongs here — the machine is selected by the build, not by a +`#define` in the header. + +> [!NOTE] +> `machine_name` is a `char[30]` and `MACHINE_MODEL` is copied into it at +> compile time. A longer model string still builds, but SDCC emits +> `warning 147: excess elements in array of chars initializer` and the name is +> truncated in the UI. Keep it to 29 characters or less. + +## Step 2: the board definition + +`machine/.c` starts with the shared prelude and then defines the single +`machine` object. Copy the closest existing board and adjust it — do not start +from a blank file, the field meanings are easier to see in a real example. + +```c +#include "machine_defs.h" + +__code const struct machine machine = { + .machine_name = MACHINE_MODEL, + .isRTL8373 = 1, + .min_port = 0, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, + .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + + .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN, + .sfp_port[0].pin_los = GPIO37, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), + .reset_pin = GPIO_NA, + + .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1 }, + .led_sets = { + { /* RJ45 ports */ + LEDS_2G5 | LEDS_LINK, + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0, + 0 + }, { /* SFP port, single green LED */ + LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0, + 0, + 0 + } + }, +}; +``` + +### Fields + +| Field | Meaning | +|-------|---------| +| `machine_name` | always `MACHINE_MODEL` | +| `isRTL8373` | `1` for an RTL8373 SoC, `0` for RTL8372 | +| `min_port` / `max_port` | range of logical port numbers the device has; `min_port` is `0` for RTL8373 boards and `3` for RTL8372 boards (logical ports 1–2 are the SoC's internal ports on the 8372) | +| `n_sfp` | number of SFP+ cages | +| `n_10g` | SerDes 10G capability used by the RTL837xN init: `0` none, `1` one, `2` two 10G-capable SerDes | +| `log_to_phys_port[9]` | logical port (0-based index) → physical front-panel port number | +| `phys_to_log_port[9]` | the inverse, physical port 1 is index 0, unused slots are 0 | +| `is_sfp[9]` | `0` for copper, `1`/`2` for the I2C bus number of the SFP on that port | +| `sfp_port[2]` | per-cage wiring, `sfp_port[0]` is the leftmost cage | +| `reset_pin` | GPIO of the reset button, `GPIO_NA` if unpopulated | +| `high_leds` | mux/enable for the LED pins 27–29 (`LED_27`, `LED_28_SYS`, `LED_29`) | +| `port_led_set[9]` | which LED set (0–3) each logical port uses | +| `led_sets[4][4]` | the 4 LED combinations per set; which of the 4 is used depends on how the LED is wired | +| `led_mux_custom` / `led_mux[28]` | `1` plus the 28 mux values when the stock LED muxing does not fit the board | +| `mac_flash_offset` | flash offset of the factory MAC address, omit or `0` if the board does not store one | + +Each `struct sfp_port` entry holds `pin_detect`, `pin_los`, `pin_tx_disable`, +`sds` (which SerDes drives the cage) and `i2c` (built with `I2CBUS(sda, scl)`, +or `GPIO_NA`-based values where a cage has no such pin). + +`I2CBUS(...)` only accepts the pin pairs listed in `machine_defs.h`. A wrong +pair is caught at compile time: SDCC reports +`error 176: sizeof applied to an incomplete type` pointing at the offending +`.i2c` line, because the macro deliberately turns an invalid bus number into an +array of size 0. + +### Reusing an existing definition + +If the new board is electrically identical to one that already exists — same +port mapping, same SFP wiring, same LEDs — do not copy the file. Make the new +`.c` a symlink and keep only a distinct `.h`, so the two boards cannot drift +apart: + +``` +ln -s KP_9000_6XHML_X2.c machine/MY_BOARD_1_0.c +``` + +The new board still needs `machine/MY_BOARD_1_0.h` with its own +`MACHINE_MODEL`, because `MACHINE_MODEL` is what distinguishes the products. +Existing examples: `machine/PCB_K0402WS_V3.c`, `machine/KP_9000_9XHML_X_V3_2.c` +and the six `KP_9000_*` revision variants. `git ls-files -s machine` shows these +as mode `120000`. + +Two files that differ only in whitespace or comments are duplicates and should +be collapsed this way. + +## Step 3: build and verify + +``` +make MACHINE=MY_BOARD_1_0 +``` + +The image appears as `output/rtlplayground--MY_BOARD_1_0.bin`. An +unknown or empty `MACHINE` fails immediately with a message naming the expected +file, before anything is compiled. + +Then run the checks CI runs: + +``` +make machine_check # compiles machine/.c and machine_init.c for every machine +make -C test # host-side unit tests +``` + +`make machine_check` discovers the definitions with +`$(wildcard machine/*.c)`, so a new file is covered without registering it +anywhere. CI (`.github/workflows/build.yml`) runs it on every push and pull +request, alongside full builds for a 4+2 and an 8+1 board. + +A successful compile only proves the definition is syntactically valid. The +actual behaviour — port numbering, SFP detection, LEDs — has to be confirmed on +hardware, and the console commands (`port`, `stat`, `sfp`, `leds_dump`) are the +tools for that. + +## Step 4: documentation + +Once the board has been tested on hardware: + +1. Add a row to the table in [Supported devices](supported_devices.md). +2. Add `doc/devices/.md` describing the board, the build target + (`make MACHINE=MY_BOARD_1_0`), what works and what is unverified, plus + photos and the PCB silkscreen marking. +3. If the board shares a PCB with other revisions, follow + `doc/devices/KP-9000-6XH-X2.md` and document which `MACHINE` to pick for + which silkscreen. + diff --git a/doc/devices/2M-PCB23-V3_2.md b/doc/devices/2M-PCB23-V3_2.md index 9cfe3acc..f86f8db2 100644 --- a/doc/devices/2M-PCB23-V3_2.md +++ b/doc/devices/2M-PCB23-V3_2.md @@ -17,8 +17,8 @@ The original web interface reports this board as hardware version V3.2 The component placement matches [2M-PCB23-V3.1](2M-PCB23-V3_1.md): same positions for the flash (U8), the T7/T8/T9/T10 headers, the SFP cage and its LED, and the unpopulated U7/U9 footprints. Build with -`MACHINE=KP_9000_9XHML_X_V3_2`, which uses the V3.1 machine definition and -only differs in the reported name. +`make MACHINE=KP_9000_9XHML_X_V3_2`, whose board file is a symlink to the V3.1 +definition and only differs in the reported name. ## Tested diff --git a/doc/devices/2M-PCB43-V1.1.md b/doc/devices/2M-PCB43-V1.1.md index 97343f02..0a11f513 100644 --- a/doc/devices/2M-PCB43-V1.1.md +++ b/doc/devices/2M-PCB43-V1.1.md @@ -15,7 +15,7 @@ and sold by Mokerlink. The device is fully supported: - All 4 2.5GBASE-T RJ45 ports work at 10/100/1000/2500 Mbps - The SFP+ port supports 1G, 2.5G and 10G modules -- LEDs work with the same indiciations as the OEM firmware (use KP_9000_6XHML_X2_V1_1 in machine.h if building yourself or the corresponding pre-compiled binary) +- LEDs work with the same indiciations as the OEM firmware (build with `make MACHINE=KP_9000_6XHML_X2_V1_1` if building yourself, or use the corresponding pre-compiled binary) - untested due to missing Hardware: SFP+ ports equipped with 1G or 2.5G SFPs. ### Hardware overview diff --git a/doc/devices/SWTGW215AS.md b/doc/devices/SWTGW215AS.md index 0f986986..6bfdaadc 100644 --- a/doc/devices/SWTGW215AS.md +++ b/doc/devices/SWTGW215AS.md @@ -9,7 +9,7 @@ ## RTLPlayground target -Use machine target `MACHINE_LIANGUO_ZX_SWTGW215AS` for this device. +Build for this device with `make MACHINE=LIANGUO_ZX_SWTGW215AS`, which selects the `machine/LIANGUO_ZX_SWTGW215AS.c` board definition. Physical hardware verification: 5x RJ45 ports + 1x SFP port. Port 5 RJ45 is interfaced through a RTL8221B IC. From e890e429a5ba5587b39b6ce84f30401d87b21838 Mon Sep 17 00:00:00 2001 From: hlyi Date: Wed, 14 Oct 2026 00:22:08 -0500 Subject: [PATCH 28/84] Update adding-a-device.md --- doc/adding-a-device.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/doc/adding-a-device.md b/doc/adding-a-device.md index 8992b512..aab8f62f 100644 --- a/doc/adding-a-device.md +++ b/doc/adding-a-device.md @@ -1,17 +1,12 @@ # Adding a new device A device is supported by adding one board definition under `machine/`. -the build selects the definition by name, and CI compiles every file it finds. +the build selects the definition by name. ``` make MACHINE= ``` -`MACHINE=` selects `machine/.c` and is passed on to the compiler as -both `-DMACHINE_` (for the `#if defined(MACHINE_...)` blocks in the rest -of the firmware) and `-DMACHINE_NAME=` (which `machine.h` uses to include -`machine/.h`). The two always take the same value, so they cannot drift. - ## What a new device consists of | File | Purpose | @@ -39,8 +34,6 @@ the template: ``` The header is what the UI and the CLI print; `machine.h` includes it by name. -Nothing else belongs here — the machine is selected by the build, not by a -`#define` in the header. > [!NOTE] > `machine_name` is a `char[30]` and `MACHINE_MODEL` is copied into it at @@ -136,8 +129,7 @@ ln -s KP_9000_6XHML_X2.c machine/MY_BOARD_1_0.c The new board still needs `machine/MY_BOARD_1_0.h` with its own `MACHINE_MODEL`, because `MACHINE_MODEL` is what distinguishes the products. Existing examples: `machine/PCB_K0402WS_V3.c`, `machine/KP_9000_9XHML_X_V3_2.c` -and the six `KP_9000_*` revision variants. `git ls-files -s machine` shows these -as mode `120000`. +and the six `KP_9000_*` revision variants. Two files that differ only in whitespace or comments are duplicates and should be collapsed this way. From 40505556675f1b5e46cf63475ba8339bad0f8d3f Mon Sep 17 00:00:00 2001 From: bloqaudio Date: Sun, 11 Oct 2026 18:46:11 -0500 Subject: [PATCH 29/84] port: add a cable test command "port cabletest" runs the cable diagnostic of the copper PHYs and reports one line per pair: ok, or open, short, short to another pair or impedance mismatch with the distance to the fault. The PHY part follows the cable_test_start and cable_test_get_status ops of the Linux phy_driver and takes the port number: rtl_phy_cable_test_start() triggers the test and rtl_phy_cable_test_get_status() returns true once all four pairs are done, with a result code and a length per pair. The decode order is the one of the Linux driver. The same two functions drive the RTL8224 and the internal PHYs of the RTL8372 and RTL8373. The command only powers the port down and returns. A state machine called from handle_tick() calls the start op after one second, polls the status op, stores the result, clears the trigger, also after a timeout, and restores the control register, so the main loop never waits. The result is printed when it is ready and "port cabletest show" prints it again. One test runs at a time. The result flags are the low byte of the per-pair status word. The vendor code reads the high byte, which is always zero on the RTL8373. A link partner that keeps transmitting makes the PHY report an inter-pair short on every pair, and forcing the speed with autonegotiation off, as the Linux driver does, does not take the link down. The port is therefore powered down for a second before the test. The control register is restored afterwards with an autonegotiation restart, so speed settings survive and the link returns. A test started on a port that is switched off never finishes and leaves the PHY unusable until the switch is reset, so such a port is refused, and a test is dropped if the port is switched off during the wait. SFP ports are refused by the existing check. The external copper PHYs of the RTL8372 boards are refused because the test has not been run on one. The distance is the raw length word minus 150, at 43 counts per 50 cm. Fitted on open cables of 0.9 m, 2 m and 7.6 m; the pairs of one cable differ by about 0.2 m. The Linux formula, raw minus 620 at 78 counts per metre, gives 0, 0 and 2.4 m for these cables. An unplugged port has no defined result: some pairs report open at about 0.5 m and others a short or a mismatch at 2 to 3 m. --- Makefile | 1 + cmd_parser.c | 7 ++ rtl837x_cabletest.c | 216 ++++++++++++++++++++++++++++++++++++++++++++ rtl837x_cabletest.h | 21 +++++ rtlplayground.c | 2 + 5 files changed, 247 insertions(+) create mode 100644 rtl837x_cabletest.c create mode 100644 rtl837x_cabletest.h diff --git a/Makefile b/Makefile index 67f3fa87..00bbf7b9 100644 --- a/Makefile +++ b/Makefile @@ -99,6 +99,7 @@ SRCS = \ # RTL837x SRCS += \ rtl837x_bandwidth.c \ + rtl837x_cabletest.c \ rtl837x_flash.c \ rtl837x_igmp.c \ rtl837x_init.c \ diff --git a/cmd_parser.c b/cmd_parser.c index 9e226dbf..28965072 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -20,6 +20,7 @@ #include "rtl837x_lldp.h" #include "rtl837x_storm.h" #include "rtl837x_trap.h" +#include "rtl837x_cabletest.h" #include "sfp.h" #include "dhcp.h" #include "syslog.h" @@ -903,6 +904,7 @@ void parse_port(void) "\nport [show|on|off]" \ "\nport [10m|100m|1g|2g5|duplex] [half|full]" \ "\nport lldp [block|permit]" \ + "\nport cabletest [show]" \ "\nport name [custom port name]\n"); return; } @@ -945,6 +947,11 @@ void parse_port(void) } } else if (machine.is_sfp[phy_settings.port]) { print_string(" is SFP no PHY information available.\n"); + } else if (cmd_compare(2, "cabletest")) { + if (cmd_words_len > 3 && cmd_compare(3, "show")) + cabletest_show(); + else + cabletest_run(); } else if (cmd_compare(2, "10m")) { print_string(" 10M\n"); phy_settings.speed = PHY_SPEED_10M; diff --git a/rtl837x_cabletest.c b/rtl837x_cabletest.c new file mode 100644 index 00000000..120e87fa --- /dev/null +++ b/rtl837x_cabletest.c @@ -0,0 +1,216 @@ +#include +#include +#include "rtl837x_common.h" +#include "rtl837x_sfr.h" +#include "rtl837x_cabletest.h" +#include "rtl837x_phy.h" +#include "phy.h" +#include "machine.h" + +#pragma codeseg BANK3 +#pragma constseg BANK3 + +#define RTCT_CTRL 0xa422 +#define RTCT_CTRL_START 0x00f1 +#define RTCT_CTRL_DONE 0x8000 +#define RTCT_SRAM_ADDR 0xa436 +#define RTCT_SRAM_DATA 0xa438 +#define RTCT_SRAM_STATUS 0x8026 +#define RTCT_SRAM_LEN 0x8028 + +#define RTCT_ST_BUSY 0x01 +#define RTCT_ST_MISMATCH 0x06 +#define RTCT_ST_OPEN 0x08 +#define RTCT_ST_SHORT 0x10 +#define RTCT_ST_OK 0x20 +#define RTCT_ST_DONE 0x40 +#define RTCT_ST_CROSS_SHORT 0x80 + +#define RTCT_LEN_ZERO 150 +#define RTCT_LEN_PER_50CM 43 + +#define PHY_POWER 0xa610 +#define PHY_POWER_OFF 0x0800 + +#define BMCR_PDOWN_1G_FULL 0x0940 +#define BMCR_1G_FULL 0x0140 +#define BMCR_ANRESTART 0x0200 + +#define CT_IDLE 0 +#define CT_SILENCE 1 +#define CT_RUN 2 +#define CT_POLL_TICKS (SYS_TICK_HZ / 20) +#define CT_POLLS 100 + +extern volatile __xdata uint32_t ticks; +extern __code const struct machine machine; +extern __xdata struct machine_runtime machine_detected; + +__xdata uint8_t ct_state = CT_IDLE; +__xdata uint8_t ct_port; +__xdata uint8_t ct_last; +__xdata uint8_t ct_polls; +__xdata uint8_t ct_valid; +__xdata uint8_t ct_have = 0; +__xdata uint8_t ct_pair; +__xdata uint8_t ct_flags; +__xdata uint16_t ct_bmcr; +__xdata uint8_t ct_result[4]; +__xdata uint16_t ct_len[4]; + +void rtl_phy_cable_test_start(uint8_t port) __banked +{ + phy_write(port, PHY_MMD31, PHY_MMD31_FEDCR, BMCR_1G_FULL); + phy_write(port, PHY_MMD31, RTCT_CTRL, RTCT_CTRL_START); +} + +bool rtl_phy_cable_test_get_status(uint8_t port) __banked +{ + phy_read(port, PHY_MMD31, RTCT_CTRL); + if (!(SFR_DATA_U16 & RTCT_CTRL_DONE)) + return false; + + for (ct_pair = 0; ct_pair < 4; ct_pair++) { + // The flags are the low byte of the word, not the high one. + phy_write(port, PHY_MMD31, RTCT_SRAM_ADDR, RTCT_SRAM_STATUS + (ct_pair << 2)); + phy_read(port, PHY_MMD31, RTCT_SRAM_DATA); + ct_flags = SFR_DATA_U16; + if (!(ct_flags & RTCT_ST_DONE)) + return false; + if (ct_flags & RTCT_ST_OK) + ct_result[ct_pair] = CABLE_RESULT_OK; + else if (ct_flags & RTCT_ST_OPEN) + ct_result[ct_pair] = CABLE_RESULT_OPEN; + else if (ct_flags & RTCT_ST_SHORT) + ct_result[ct_pair] = CABLE_RESULT_SAME_SHORT; + else if (ct_flags & RTCT_ST_BUSY) + ct_result[ct_pair] = CABLE_RESULT_UNSPEC; + else if (ct_flags & RTCT_ST_CROSS_SHORT) + ct_result[ct_pair] = CABLE_RESULT_CROSS_SHORT; + else if (ct_flags & RTCT_ST_MISMATCH) + ct_result[ct_pair] = CABLE_RESULT_MISMATCH; + else + ct_result[ct_pair] = CABLE_RESULT_UNSPEC; + + phy_write(port, PHY_MMD31, RTCT_SRAM_ADDR, RTCT_SRAM_LEN + (ct_pair << 2)); + phy_read(port, PHY_MMD31, RTCT_SRAM_DATA); + ct_len[ct_pair] = SFR_DATA_U16; + ct_len[ct_pair] = ct_len[ct_pair] > RTCT_LEN_ZERO ? ct_len[ct_pair] - RTCT_LEN_ZERO : 0; + ct_len[ct_pair] = ct_len[ct_pair] * 5 / RTCT_LEN_PER_50CM; + } + return true; +} + +static void cabletest_print(void) +{ + print_string("Cable test port "); + print_phys_port(ct_port); + if (!ct_valid) { + print_string(": timed out\n"); + return; + } + write_char('\n'); + for (ct_pair = 0; ct_pair < 4; ct_pair++) { + print_string("Pair "); + write_char('A' + ct_pair); + switch (ct_result[ct_pair]) { + case CABLE_RESULT_OK: + print_string(": ok\n"); + continue; + case CABLE_RESULT_OPEN: + print_string(": open at "); + break; + case CABLE_RESULT_SAME_SHORT: + print_string(": short at "); + break; + case CABLE_RESULT_CROSS_SHORT: + print_string(": short to another pair at "); + break; + case CABLE_RESULT_MISMATCH: + print_string(": impedance mismatch at "); + break; + default: + print_string(": unknown\n"); + continue; + } + itoa_short(ct_len[ct_pair] / 10); + write_char('.'); + write_char('0' + ct_len[ct_pair] % 10); + print_string(" m\n"); + } +} + +void cabletest_run(void) __banked +{ + if (ct_state) { + print_string("Cable test running on port "); + print_phys_port(ct_port); + write_char('\n'); + return; + } + if (!machine_detected.isRTL8373 && (phy_settings.port < 4 || phy_settings.port > 7)) { + print_string("No cable test on this PHY\n"); + return; + } + + // A test started on a port that is off never ends and blocks the PHY until reset. + phy_read(phy_settings.port, PHY_MMD31, PHY_POWER); + if (SFR_DATA_U16 & PHY_POWER_OFF) { + print_string("Port is off\n"); + return; + } + + ct_port = phy_settings.port; + phy_read(ct_port, PHY_MMD31, PHY_MMD31_FEDCR); + ct_bmcr = SFR_DATA_U16; + + // Forcing the speed leaves the link up; only a power-down silences the partner. + phy_write(ct_port, PHY_MMD31, PHY_MMD31_FEDCR, BMCR_PDOWN_1G_FULL); + ct_last = ticks; + ct_have = 0; + ct_state = CT_SILENCE; + print_string("Cable test started\n"); +} + +void cabletest_show(void) __banked +{ + if (ct_state) + print_string("Cable test running\n"); + else if (ct_port != phy_settings.port || !ct_have) + print_string("No cable test result\n"); + else + cabletest_print(); +} + +void cabletest_tick(void) __banked +{ + if (ct_state == CT_SILENCE) { + if (!TICKS_DUE(ct_last, SYS_TICK_HZ)) + return; + phy_read(ct_port, PHY_MMD31, PHY_POWER); + if (SFR_DATA_U16 & PHY_POWER_OFF) { + phy_write(ct_port, PHY_MMD31, PHY_MMD31_FEDCR, ct_bmcr); + ct_state = CT_IDLE; + print_string("Cable test aborted, port is off\n"); + return; + } + rtl_phy_cable_test_start(ct_port); + ct_polls = CT_POLLS; + ct_last = ticks; + ct_state = CT_RUN; + return; + } + if (ct_state != CT_RUN || !TICKS_DUE(ct_last, CT_POLL_TICKS)) + return; + ct_last = ticks; + + ct_valid = rtl_phy_cable_test_get_status(ct_port); + if (!ct_valid && --ct_polls) + return; + + phy_write(ct_port, PHY_MMD31, RTCT_CTRL, 0); + phy_write(ct_port, PHY_MMD31, PHY_MMD31_FEDCR, ct_bmcr | BMCR_ANRESTART); + ct_have = 1; + ct_state = CT_IDLE; + cabletest_print(); +} diff --git a/rtl837x_cabletest.h b/rtl837x_cabletest.h new file mode 100644 index 00000000..5846b82f --- /dev/null +++ b/rtl837x_cabletest.h @@ -0,0 +1,21 @@ +#ifndef _RTL837X_CABLETEST_H_ +#define _RTL837X_CABLETEST_H_ + +#include +#include + +#define CABLE_RESULT_UNSPEC 0 +#define CABLE_RESULT_OK 1 +#define CABLE_RESULT_OPEN 2 +#define CABLE_RESULT_SAME_SHORT 3 +#define CABLE_RESULT_CROSS_SHORT 4 +#define CABLE_RESULT_MISMATCH 5 + +void rtl_phy_cable_test_start(uint8_t port) __banked; +bool rtl_phy_cable_test_get_status(uint8_t port) __banked; + +void cabletest_run(void) __banked; +void cabletest_show(void) __banked; +void cabletest_tick(void) __banked; + +#endif diff --git a/rtlplayground.c b/rtlplayground.c index 4a8b4ec7..a69778ee 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -19,6 +19,7 @@ #include "rtl837x_bandwidth.h" #include "rtl837x_init.h" #include "rtl837x_lldp.h" +#include "rtl837x_cabletest.h" #include "dhcp.h" #include "cmd_parser.h" #include "cmd_editor.h" @@ -1301,6 +1302,7 @@ static void handle_tick(void) health_phase(HEALTH_PH_STP); lldp_tick(); health_phase(HEALTH_LLDP); + cabletest_tick(); } From e125ebd097ad91e849313584ffd93db643ad7545 Mon Sep 17 00:00:00 2001 From: Brunoz78 Date: Thu, 8 Oct 2026 11:03:45 +0200 Subject: [PATCH 30/84] i18n(de): translate the port isolation page Co-Authored-By: Claude Opus 5.5 --- html/app.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/html/app.js b/html/app.js index 34e9713e..aa90524e 100644 --- a/html/app.js +++ b/html/app.js @@ -588,7 +588,7 @@ fw_timeout:"Le commutateur n'est plus joignable depuis 150 s: verifiez l'aliment }, de:{ nav_dash:"Übersicht",nav_ports:"Ports",nav_stp:"Spanning Tree",nav_stats:"Statistik", -nav_vlan:"VLANs",nav_l2:"MAC-Tabelle",nav_mirror:"Spiegelung",nav_lag:"LAG",nav_eee:"EEE", +nav_vlan:"VLANs",nav_l2:"MAC-Tabelle",nav_mirror:"Spiegelung",nav_isolate:"Isolation",nav_lag:"LAG",nav_eee:"EEE", nav_bw:"Bandbreite",nav_system:"System",nav_fw:"Firmware", hdr_dirty:"ungespeicherte Änderungen",hdr_dirty_t:"Laufende Konfiguration weicht von der Startkonfiguration ab", hdr_save:"Im Flash speichern",hdr_save_t:"Laufende Konfiguration dauerhaft im Flash speichern", @@ -648,6 +648,9 @@ l2_learned:"gelernt",l2_loading:"lädt...",l2_failed:"Laden fehlgeschlagen",l2_e l2_del_t:"Eintrag löschen",l2_flush_q:"Alle gelernten MAC-Einträge löschen?", m_title:"Port-Spiegelung",m_active:"aktiv",m_monitor:"Monitor-Port",m_mirror:"Spiegeln",m_both:"Beide", m_note:"Beide = RX und TX des Ports auf den Monitor-Port spiegeln.",m_none:"Mindestens einen zu spiegelnden Port wählen", +iso_title:"Port-Isolation",iso_from:"Von Port",iso_to:"Zu Port",iso_all:"Alle erlauben", +iso_intro:"Ein Paket, das über einen Port hereinkommt, darf den Switch nur über die Ports verlassen, die in seiner Zeile angehakt sind. Die Isolation wirkt vor der Weiterleitung: Ein nicht angehakter Port ist direkt nicht erreichbar, egal was in der MAC-Tabelle steht.", +iso_legend:"Das Feld des Ports selbst ist immer erlaubt. Eine rote Portnummer markiert einen Port, der nicht ganz offen ist.", lag_hash:"Hash:",lag_note:"Eine LAG braucht mindestens ein Mitglied, um in der Startkonfiguration gespeichert zu werden; eine leere Gruppe zu übernehmen löscht sie.", lag_clear_q:"LAG {n} auflösen?",lag_clear_d:"Alle Mitglieds-Ports kehren in den Normalbetrieb zurück.", e_title:"Energy Efficient Ethernet",e_adv:"Angeboten",e_lp:"Gegenstelle",e_active:"Aktiv",e_enable:"Aktivieren", From 3a8d1d323f786669054084b7adcd1348be2a53eb Mon Sep 17 00:00:00 2001 From: Brunoz78 Date: Sun, 11 Oct 2026 16:43:18 +0200 Subject: [PATCH 31/84] i18n(de): translate the DNS server settings Co-Authored-By: Claude Opus 5.5 --- html/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html/app.js b/html/app.js index aa90524e..129ce631 100644 --- a/html/app.js +++ b/html/app.js @@ -675,7 +675,7 @@ sy_ip_err:"Ungültige IP / Netzmaske / Gateway",sy_host_err:"Hostname: 1-23 druc sy_net_q:"Netzwerkeinstellungen übernehmen?",sy_net_d:"Die Management-IP ändert sich auf {ip}: Diese Seite muss dort neu geöffnet werden.", sy_ip_changed:"IP geändert, neu verbinden unter http://{ip}/",sy_dhcp_q:"Auf DHCP umstellen?", sy_dhcp_d:"Der Switch bezieht seine Adresse per DHCP. Du musst seine neue IP herausfinden, um dich wieder zu verbinden.", -sy_sysip_err:"Ungültige IP des Syslog-Servers",sy_sysport_err:"Syslog-Port muss zwischen 1 und 65535 liegen", +sy_sysip_err:"Ungültige IP des Syslog-Servers",sy_dns1:"DNS-Server",sy_dns2:"Zweiter DNS-Server",sy_dns_err:"Ungültige IP des DNS-Servers",sy_dns_dhcp:"von DHCP: {ip}",sy_sysport_err:"Syslog-Port muss zwischen 1 und 65535 liegen", sy_pw_len:"Passwort: 1-20 Zeichen",sy_pw_space:"Das Passwort darf keine Leerzeichen enthalten",sy_pw_match:"Die Passwörter stimmen nicht überein", sy_pw_q:"Admin-Passwort ändern?",sy_pw_d:"Gilt sofort für neue Anmeldungen. Im Flash speichern, damit es erhalten bleibt.", sy_reboot_q:"Switch neu starten?",sy_reboot_d:"Es gibt UNGESPEICHERTE Änderungen, sie gehen verloren. Zuerst im Flash speichern, wenn du sie behalten willst.", From ecc8356f4d0e983321cbc76b74ab4c8620d00166 Mon Sep 17 00:00:00 2001 From: Brunoz78 Date: Mon, 12 Oct 2026 21:47:21 +0200 Subject: [PATCH 32/84] i18n(de): translate the NTP settings Co-Authored-By: Claude Opus 5.5 --- html/app.js | 1 + 1 file changed, 1 insertion(+) diff --git a/html/app.js b/html/app.js index 129ce631..482b3773 100644 --- a/html/app.js +++ b/html/app.js @@ -676,6 +676,7 @@ sy_net_q:"Netzwerkeinstellungen übernehmen?",sy_net_d:"Die Management-IP änder sy_ip_changed:"IP geändert, neu verbinden unter http://{ip}/",sy_dhcp_q:"Auf DHCP umstellen?", sy_dhcp_d:"Der Switch bezieht seine Adresse per DHCP. Du musst seine neue IP herausfinden, um dich wieder zu verbinden.", sy_sysip_err:"Ungültige IP des Syslog-Servers",sy_dns1:"DNS-Server",sy_dns2:"Zweiter DNS-Server",sy_dns_err:"Ungültige IP des DNS-Servers",sy_dns_dhcp:"von DHCP: {ip}",sy_sysport_err:"Syslog-Port muss zwischen 1 und 65535 liegen", +sy_time:"Uhrzeit",sy_ntp:"NTP",sy_ntpsrv:"NTP-Server",sy_ntpsrv_p:"Hostname oder IPv4-Adresse",sy_ntpint:"Abfrageintervall [min]",sy_tz:"Zeitzone",sy_dst:"Sommerzeit",sy_dst_eu:"Europa",sy_dst_us:"USA",sy_now:"Lokale Zeit",sy_sync:"Letzte Synchronisation",sy_ntpaddr:"Server-Adresse",sy_nosync:"nicht synchronisiert",sy_waiting:"warte auf den Server",sy_ago:"vor {n} min",sy_ntp_err:"Hostname oder IPv4-Adresse des NTP-Servers eingeben",sy_ntpint_err:"Abfrageintervall muss zwischen 1 und 1440 liegen",sy_time_note:"Der Switch hat keine batteriegepufferte Uhr: Die Zeit geht bei jedem Neustart verloren und wird neu vom NTP-Server bezogen, sobald das Netzwerk bereit ist. Im Flash speichern, damit die Einstellungen erhalten bleiben.", sy_pw_len:"Passwort: 1-20 Zeichen",sy_pw_space:"Das Passwort darf keine Leerzeichen enthalten",sy_pw_match:"Die Passwörter stimmen nicht überein", sy_pw_q:"Admin-Passwort ändern?",sy_pw_d:"Gilt sofort für neue Anmeldungen. Im Flash speichern, damit es erhalten bleibt.", sy_reboot_q:"Switch neu starten?",sy_reboot_d:"Es gibt UNGESPEICHERTE Änderungen, sie gehen verloren. Zuerst im Flash speichern, wenn du sie behalten willst.", From 682ac462c42bca41c721e24f1d9526ac90b758f6 Mon Sep 17 00:00:00 2001 From: Brunoz78 Date: Sat, 10 Oct 2026 11:40:17 +0200 Subject: [PATCH 33/84] i18n(de): translate the LLDP page Co-Authored-By: Claude Opus 5.5 --- html/app.js | 1 + 1 file changed, 1 insertion(+) diff --git a/html/app.js b/html/app.js index 482b3773..c3458117 100644 --- a/html/app.js +++ b/html/app.js @@ -626,6 +626,7 @@ stp_en_q:"Spanning Tree aktivieren?", stp_en_d:"Die Ports starten blockiert und brauchen bis zum doppelten Forward Delay, bis sie weiterleiten; Edge-Ports sind sofort wieder aktiv.", stp_dis_q:"Spanning Tree deaktivieren?",stp_dis_d:"Alle Ports leiten sofort weiter; der Schutz vor Netzwerkschleifen entfällt.", stp_cost_err:"Pfadkosten müssen zwischen 0 und 200000000 liegen", +nav_lldp:"LLDP",lldp_enabled:"LLDP aktiv",lldp_title:"Dienststatus",lldp_portname:"Name",lldp_enable:"Pakete senden", st_title:"Port-Statistik",st_h:"Summen seit dem Start",st_txg:"TX gut",st_txb:"TX fehlerhaft",st_rxg:"RX gut", st_rxb:"RX fehlerhaft",st_details:"Details",st_counters:"MIB-Zähler",st_nonzero:"nur ungleich null", st_autoref:"automatisch aktualisieren",st_counter:"Zähler",st_value:"Wert",st_fail:"Zähler konnten nicht geladen werden", From d5f84cc2fba1328c6fa1b2310e8ebb47370ff822 Mon Sep 17 00:00:00 2001 From: Brunoz78 Date: Wed, 14 Oct 2026 20:52:53 +0200 Subject: [PATCH 34/84] i18n(de): translate the LACP settings of the LAG page Co-Authored-By: Claude Opus 5.5 --- html/app.js | 1 + 1 file changed, 1 insertion(+) diff --git a/html/app.js b/html/app.js index c3458117..626e522c 100644 --- a/html/app.js +++ b/html/app.js @@ -654,6 +654,7 @@ iso_intro:"Ein Paket, das über einen Port hereinkommt, darf den Switch nur übe iso_legend:"Das Feld des Ports selbst ist immer erlaubt. Eine rote Portnummer markiert einen Port, der nicht ganz offen ist.", lag_hash:"Hash:",lag_note:"Eine LAG braucht mindestens ein Mitglied, um in der Startkonfiguration gespeichert zu werden; eine leere Gruppe zu übernehmen löscht sie.", lag_clear_q:"LAG {n} auflösen?",lag_clear_d:"Alle Mitglieds-Ports kehren in den Normalbetrieb zurück.", +lacp_heading:"LACP (802.3ad)",lag_mode:"Modus",lag_static:"Statisch",lacp_neg:"wird ausgehandelt",lacp_agg:"Aggregator",lacp_members:"aktive Mitglieder",lacp_hint:"LACP: Ports auswählen und Übernehmen drücken, um die Aushandlung zu starten",lacp_actor:"Lokal",lacp_partner:"Partner",lacp_rxstate:"Empfangsstatus",lacp_rx:"Empfangene LACPDUs",lacp_psys:"Partner-System", e_title:"Energy Efficient Ethernet",e_adv:"Angeboten",e_lp:"Gegenstelle",e_active:"Aktiv",e_enable:"Aktivieren", e_note:"Angebotene und von der Gegenstelle unterstützte Stufen je Geschwindigkeit: 100M, 1G, 2.5G. SFP-Ports unterstützen kein EEE.", e_idle:"inaktiv",e_na:"k. A.", From b1026490a8541664c7c93ebee86c0e728bb708c0 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Thu, 15 Oct 2026 00:59:58 +0200 Subject: [PATCH 35/84] doc: point the link aggregation entry at the document that exists The document was renamed from doc/trunking.md in "Update LAG configuration registers" and the entry in the README kept the old name, so it leads nowhere. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b605ca5f..82c44557 100644 --- a/README.md +++ b/README.md @@ -358,7 +358,7 @@ The following documents give further documentation on specific features of the R - [L2 learning](doc/l2.md) - [IGMP (IP-MC streaming)](doc/igmp.md) - [SFP+ ports](doc/sfp.md) -- [Trunking aka. port aggregation](doc/trunking.md) +- [Trunking aka. port aggregation](doc/link_aggregation.md) - [LACP (802.3ad link aggregation)](doc/lacp.md) - [VLAN](doc/vlan.md) - [Storm control](doc/storm_control.md) From ed069b67fd812f65e57b0ecf3ec69f9f22a255f8 Mon Sep 17 00:00:00 2001 From: HL Yi Date: Wed, 14 Oct 2026 20:17:03 -0500 Subject: [PATCH 36/84] update doc to match new split machine structure --- README.md | 20 +++++++++++++------- doc/devices/2M-PCB23-V3_2.md | 4 ++-- doc/devices/2M-PCB43-V1.1.md | 2 +- doc/devices/SWTGW215AS.md | 2 +- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 6eaf74e8..3d61c1e5 100644 --- a/README.md +++ b/README.md @@ -103,24 +103,29 @@ docker run --rm -p 8080:8080 -v $(pwd):/workspace rtlplayground-dev \ tools/output/httpd_sim /workspace/html ``` -Edit `machine.h` or `config.txt` on your host, then re-run `make` — the +Edit `config.txt` on your host, then re-run `make` — the source directory is mounted into the container, so changes take effect -immediately. To build for a different machine, pass `MACHINE=...`. +immediately. To build for a different machine, pass `MACHINE=`, which +selects `machine/.c` (see [Adding a new device](doc/adding-a-device.md)). ## (1) Compiling for direct chip flashing AND upgrading an existing RTLPlayground running device -Edit machine.h with an editor like vi or nano. Select the correct machine the firmware should build for. +Pass the machine that matches your device, for example: +``` +make MACHINE=DEFAULT_8C_1SFP +``` +The name selects the board definition `machine/.c`; `make` without a +`MACHINE` value stops with an error listing what is expected. If your board is +not in the list yet, see [Adding a new device](doc/adding-a-device.md). > [!TIP] > You can write configuration parameters in config.txt (see below) in order your switch to get > straight at the first boot, a correct IP configuration. -Now, building the firmware image should work: -``` -make -``` +Now, building the firmware image should work. + Note, that the image generated ends in .bin, not .img, in order to make IMSProg happy. image location is stored in `RTLPlayground/output/rtlplayground_version_machine.bin` @@ -353,6 +358,7 @@ Enjoy playing! ## (11) Other documents The following documents give further documentation on specific features of the RTL837x SoCs: +- [Adding a new device](doc/adding-a-device.md) - [RTL8372/3 Feature support](doc/hardware.md) - [CPU Port](doc/CpuPort.md) - [L2 learning](doc/l2.md) diff --git a/doc/devices/2M-PCB23-V3_2.md b/doc/devices/2M-PCB23-V3_2.md index 9cfe3acc..f86f8db2 100644 --- a/doc/devices/2M-PCB23-V3_2.md +++ b/doc/devices/2M-PCB23-V3_2.md @@ -17,8 +17,8 @@ The original web interface reports this board as hardware version V3.2 The component placement matches [2M-PCB23-V3.1](2M-PCB23-V3_1.md): same positions for the flash (U8), the T7/T8/T9/T10 headers, the SFP cage and its LED, and the unpopulated U7/U9 footprints. Build with -`MACHINE=KP_9000_9XHML_X_V3_2`, which uses the V3.1 machine definition and -only differs in the reported name. +`make MACHINE=KP_9000_9XHML_X_V3_2`, whose board file is a symlink to the V3.1 +definition and only differs in the reported name. ## Tested diff --git a/doc/devices/2M-PCB43-V1.1.md b/doc/devices/2M-PCB43-V1.1.md index 97343f02..0a11f513 100644 --- a/doc/devices/2M-PCB43-V1.1.md +++ b/doc/devices/2M-PCB43-V1.1.md @@ -15,7 +15,7 @@ and sold by Mokerlink. The device is fully supported: - All 4 2.5GBASE-T RJ45 ports work at 10/100/1000/2500 Mbps - The SFP+ port supports 1G, 2.5G and 10G modules -- LEDs work with the same indiciations as the OEM firmware (use KP_9000_6XHML_X2_V1_1 in machine.h if building yourself or the corresponding pre-compiled binary) +- LEDs work with the same indiciations as the OEM firmware (build with `make MACHINE=KP_9000_6XHML_X2_V1_1` if building yourself, or use the corresponding pre-compiled binary) - untested due to missing Hardware: SFP+ ports equipped with 1G or 2.5G SFPs. ### Hardware overview diff --git a/doc/devices/SWTGW215AS.md b/doc/devices/SWTGW215AS.md index 0f986986..6bfdaadc 100644 --- a/doc/devices/SWTGW215AS.md +++ b/doc/devices/SWTGW215AS.md @@ -9,7 +9,7 @@ ## RTLPlayground target -Use machine target `MACHINE_LIANGUO_ZX_SWTGW215AS` for this device. +Build for this device with `make MACHINE=LIANGUO_ZX_SWTGW215AS`, which selects the `machine/LIANGUO_ZX_SWTGW215AS.c` board definition. Physical hardware verification: 5x RJ45 ports + 1x SFP port. Port 5 RJ45 is interfaced through a RTL8221B IC. From 56686185cec80202911ec5f0725301a727fda67c Mon Sep 17 00:00:00 2001 From: DrDoof <8052722+DrDoof@users.noreply.github.com> Date: Wed, 23 Sep 2026 22:13:30 +0200 Subject: [PATCH 37/84] lacp: implement IEEE 802.3ad link aggregation Dynamic link aggregation on top of the static LAGG, for RTL8372/8373: receive, selection and mux state machines (coupled mux, one aggregator per group), with "lacp show" to look inside. Each of the four trunk groups runs its own aggregator on the candidate ports assigned with "lag lacp "; the engine starts with the first such group and stops with the last. LACPDUs reach the CPU through the reserved-multicast trap, so delivery needs no L2 entry and nothing written per VLAN or per PVID. The trap acts on the address before the VLAN lookup and before the ingress filters, so a LACPDU also arrives on a port that admits tagged frames only and on a port STP holds in blocking, neither of which a forwarded one reaches. A trapped frame carries no VLAN tag, so lacp_in() takes the offset of the PDU body from TRAP_RX_BODY(). The action is set with trap_rma_set(), which leaves the flags sharing that register alone, and "lacp off" puts the slow-protocols drop default back. doc/lacp.md and doc/link_aggregation.md record what was measured, including why the trap looked like a dead end at first. The web UI gets a Static/LACP mode per group on the LAG page with live status, and the startup-config validator knows the per-group syntax. A host-side sandbox for the state machines runs in the test harness together with the LAG hash check. It drives both forms of an incoming LACPDU, so the body offset of a trapped one is covered as well. The L2 table page iterated with the "next unicast" read method, so static multicast entries such as an IGMP group were invisible. It now iterates with "next address", skips L3 entries (their layout carries IP addresses where a MAC is expected), and reports the lowest member of a multicast entry's port mask, so such an entry shows up as a static entry on that port. A port taken out of LACP forgets its partner, so "lacp show" after "lacp off" no longer reports one. Applying the same candidate set to a group again, as the LAG page does on every Apply, leaves the trunk alone instead of tearing it down for a second or two. lacp_in() checks the frame length and ethertype before reading a LACPDU, so a runt or a foreign frame on the slow-protocols address cannot leave stale buffer bytes as a partner. The sandbox covers these three in T20-T22. On the LAG page, LACP mode with no ports checked sends "lag N lacp off", and the startup-config validator wants at least one port after "lacp". --- Makefile | 1 + README.md | 1 + cmd_parser.c | 29 +- doc/CpuPort.md | 5 + doc/lacp.md | 231 ++++++++++++ doc/link_aggregation.md | 49 +++ html/app.js | 52 ++- html/index.html | 5 + httpd/httpd.c | 2 + httpd/page_impl.c | 81 ++++- httpd/page_impl.h | 1 + rtl837x_igmp.c | 4 +- rtl837x_lacp.c | 607 ++++++++++++++++++++++++++++++++ rtl837x_lacp.h | 101 ++++++ rtl837x_regs.h | 4 + rtlplayground.c | 25 +- test/Makefile | 9 + test/README.md | 2 + test/env_tables.c | 16 + test/lacp/.gitignore | 1 + test/lacp/Makefile | 31 ++ test/lacp/lacp_sim.c | 464 ++++++++++++++++++++++++ test/lacp/shim/machine.h | 12 + test/lacp/shim/rtl837x_common.h | 63 ++++ test/lacp/shim/rtl837x_port.h | 7 + test/lacp/shim/rtl837x_regs.h | 19 + test/lacp/shim/uip.h | 9 + test/port_hash/.gitignore | 2 + test/port_hash/Makefile | 6 + test/port_hash/test_lag_hash.c | 68 ++++ test/test_httpd_tx.c | 1 + 31 files changed, 1885 insertions(+), 23 deletions(-) create mode 100644 doc/lacp.md create mode 100644 rtl837x_lacp.c create mode 100644 rtl837x_lacp.h create mode 100644 test/lacp/.gitignore create mode 100644 test/lacp/Makefile create mode 100644 test/lacp/lacp_sim.c create mode 100644 test/lacp/shim/machine.h create mode 100644 test/lacp/shim/rtl837x_common.h create mode 100644 test/lacp/shim/rtl837x_port.h create mode 100644 test/lacp/shim/rtl837x_regs.h create mode 100644 test/lacp/shim/uip.h create mode 100644 test/port_hash/.gitignore create mode 100644 test/port_hash/Makefile create mode 100644 test/port_hash/test_lag_hash.c diff --git a/Makefile b/Makefile index 11cee779..67f3fa87 100644 --- a/Makefile +++ b/Makefile @@ -110,6 +110,7 @@ SRCS += \ rtl837x_stp.c \ rtl837x_lldp.c \ rtl837x_storm.c \ + rtl837x_lacp.c \ rtl837x_trap.c SRCS += \ httpd/httpd.c \ diff --git a/README.md b/README.md index 3d61c1e5..d369a69e 100644 --- a/README.md +++ b/README.md @@ -365,6 +365,7 @@ The following documents give further documentation on specific features of the R - [IGMP (IP-MC streaming)](doc/igmp.md) - [SFP+ ports](doc/sfp.md) - [Trunking aka. port aggregation](doc/trunking.md) +- [LACP (802.3ad link aggregation)](doc/lacp.md) - [VLAN](doc/vlan.md) - [Storm control](doc/storm_control.md) - [Modifications and Flash replacement](doc/mods.md) diff --git a/cmd_parser.c b/cmd_parser.c index 1aa2a5ad..b585a835 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -14,6 +14,7 @@ #include "rtl837x_regs.h" #include "rtl837x_sfr.h" #include "rtl837x_stp.h" +#include "rtl837x_lacp.h" #include "rtl837x_igmp.h" #include "rtl837x_bandwidth.h" #include "rtl837x_lldp.h" @@ -538,7 +539,18 @@ void parse_lag(void) return; } + /* "lag lacp [port]..." - run LACP on this LAG with the given candidate + * ports; "lag lacp off" (or no ports) removes the LAG from LACP. */ uint8_t w = 2; + __xdata uint8_t lacp_mode = 0; + if (cmd_words_len > 2 && cmd_compare(2, "lacp")) { + lacp_mode = 1; + w = 3; + if (cmd_words_len > 3 && cmd_compare(3, "off")) { + lacp_lag_set(group, 0); + return; + } + } while (w < cmd_words_len) { // Parse port. if (cmd_parse_port_separator(cmd_words_b[w++]) == 0) @@ -546,10 +558,13 @@ void parse_lag(void) members |= ((uint16_t)1) << atoi_results_u8; } - port_lag_members_set(group, members); + if (lacp_mode) + lacp_lag_set(group, members); /* 0 ports also valid: removes the LAG */ + else + port_lag_members_set(group, members); return; err: - cmd_error("lag (show | <1-4> (d | ...))\n"); + cmd_error("lag (show | <1-4> (d | [lacp] ...))\n"); } @@ -1974,11 +1989,19 @@ void cmd_parser(void) __banked } else if (cmd_compare(0, "health")) { health_show(); #endif + } else if (cmd_compare(0, "lacp")) { + if (cmd_compare(1, "show")) + lacp_show(); + else if (cmd_compare(1, "off")) + lacp_off(); + else + cmd_error("lacp (show | off); a group starts LACP with: lag <1-4> lacp ...\n"); } else if (cmd_compare(0, "pvid")) { if (cmd_words_len == 3 && cmd_parse_port_separator(cmd_words_b[1]) != 0 && atoi_short(cmd_words_b[2]) && atoi_results_short && atoi_results_short <= 4094) + { port_pvid_set(atoi_results_u8, atoi_results_short); - else { + } else { cmd_error("pvid <1-4094>\n"); } } else if (cmd_compare(0, "vlan")) { diff --git a/doc/CpuPort.md b/doc/CpuPort.md index c10d0a15..f5e9859b 100644 --- a/doc/CpuPort.md +++ b/doc/CpuPort.md @@ -79,6 +79,11 @@ bit7 KEEP | 6 VSEL | 5 LEARN_DIS | 4:0 VIDX All fields are in network byte order. +This word has to be written through `HTONS` like every other field of the tag. +Writing a constant raw puts the bits in the wrong byte, so `0x0020` reaches the +wire as `0x2000`, which is EFID rather than LEARN_DIS. The ASIC then fails to +parse the tag and forwards the frame with the `0x8899` header still on it. + * `EFID_EN`, `EFID`: look the destination up under this filtering ID instead of the port's own * `PRI_EN`, `PRI`: force the given priority on the frame diff --git a/doc/lacp.md b/doc/lacp.md new file mode 100644 index 00000000..1b78459e --- /dev/null +++ b/doc/lacp.md @@ -0,0 +1,231 @@ +# LACP (Link Aggregation Control Protocol, IEEE 802.3ad) + +A static link aggregation group works only if both ends agree about it in +advance. Nothing checks that they do: cable a member to the wrong neighbour and +the group keeps hashing frames onto a link that goes somewhere else. LACP is the +protocol that makes the two ends agree, and keeps agreeing — it discovers which +links reach the same partner, aggregates only those, and drops a link out of the +group when the partner stops answering. + +The switch runs LACP in software on the 8051. Frames are the Slow Protocols +group: destination `01:80:C2:00:00:02`, ethertype `0x8809`, subtype 1. The +implementation covers the parts that decide whether an aggregate forms: +the Receive machine and the Selection and Mux machines of 802.3ad clause 43, +`recordPDU` and `update_NTT`, and the mis-cabling protection that refuses to +aggregate links whose partners differ. It does not implement the Churn Detection +machines or the Marker protocol, and the Mux machine is the coupled variant, so +collecting and distributing are enabled together rather than in sequence. + +Up to `LACP_NUM_LAGS` (4) groups can be placed under LACP; the rest stay static. + +## Getting the frames to the CPU + +Reserved multicast addresses are handled by the RMA block before the L2 lookup +happens. Each address has its own register and the action field sits in bits +[5:4]: 0 forward, 1 trap to CPU, 2 drop, 3 forward excluding the CPU. The +registers are packed and consecutive by *entry* rather than by address byte, so +the step of 4 counts entries, not bytes of the address: `...:00` sits at +`0x4ecc` and `...:02` at `0x4ed4`, but `...:08` sits at `0x4ee0`. + +``` +#define RTL837X_RMA0_CONF 0x4ecc /* 01:80:C2:00:00:00, entry 0 */ +#define RTL837X_RMA2_CONF 0x4ed4 /* 01:80:C2:00:00:02, Slow Protocols */ +#define RTL837X_RMA_ACT_MASK 0x30 +#define RTL837X_RMA_ACT_TRAP 0x10 +#define RTL837X_RMA_ACT_DROP 0x20 +``` + +The engine puts this address on *trap* while it runs. A trap takes the frame +out of the forwarding path and hands it to the port named in `EXT_CPU_CTRL` +(`0x6724`), whose reset value names no port at all: the destination is meant to +be an *external* CPU on a physical port, which these boards do not have. That +is why the trap first looked like a dead end - the receive counters stayed +frozen while the partner kept sending, and the ingress discard counter grew +instead. `trap_init()` points the register at the CPU port once at startup, and +the same frames then arrive. + +`FORWARD` reaches the CPU too, because the CPU port is in the forwarding +domain, but a forwarded frame is subject to the L2 lookup and on its own floods +to every other port in the VLAN. That leaks a neighbour's LACPDUs to hosts that +must not see them and, with two bonds on one switch, poisons both; a reserved +link-local group must not be forwarded at all. Containing it takes a static L2 +multicast entry with a CPU-only member mask, and because lookups are IVL on +this chip, one entry per distinct pvid over all front panel ports - a VID 0 +entry is never matched, and a port outside the LACP groups needs its pvid +covered as well, or a LACPDU arriving there misses the lookup and is flooded +through its VLAN. + +The trap needs none of that and reaches further, because it acts on the address +before the VLAN lookup and before the ingress filters. An untagged LACPDU is +delivered on a port that admits tagged frames only, where a forwarded one is +dropped by the acceptable-frame-type filter; and it is delivered on a port STP +holds in blocking, so a group converges without waiting out the forward delay. +Both were measured against forward on the same ports of an SWTGW218AS. + +A trapped frame arrives without the VLAN tag the hardware inserts into a +forwarded one, so the LACPDU body sits four bytes earlier. `TRAP_RX_BODY()` +gives the offset of the body for either form, and `lacp_in()` reads the PDU +from there. + +Neighbouring addresses in the same block still differ in other respects - +`...:00` is exempt from the ingress STP-state check and `...:02` is not, which +the trap makes moot only because it acts before that check - so measure before +carrying a conclusion about one of them across to another. + +When LACP is switched off the action goes to `DROP`, which acts before the L2 +lookup, so there is nothing to undo. + +## What has to match across the members + +802.3ad requires that every link in an aggregate be full duplex and run at the +same speed. Beyond the standard, anything that changes how a frame is treated +has to be identical across members, because the hash picks the member per frame +and a difference would make the behaviour depend on that choice: + +* must match: speed and duplex, VLAN membership and pvid, ingress filtering, + port isolation +* may differ: EEE, LED configuration, counters, cable diagnostics + +None of this is enforced yet — `port_lag_members_set()` writes a member mask and +a hash setting and validates nothing. See #377 for the discussion of whether a +mismatch should be rejected or applied to the whole group. + +## Protocol timing + +``` +#define LACP_FAST_PERIODIC 0x0032 /* fast TX 1 s; partner expires at 3 s */ +#define LACP_SLOW_PERIODIC 0x05dc /* slow TX 30 s; partner expires at 90 s */ +#define LACP_SHORT_TIMEOUT 0x00c8 /* silent partner dropped after 4 s (802.3ad: 3 s) */ +#define LACP_LONG_TIMEOUT 0x1770 /* long-timeout variant, after 120 s (802.3ad: 90 s) */ +``` + +One unit is one step of the LACP timers, taken every four system ticks, so 50 +per second whatever the pace of the main loop; 50 units came out as 1.00 s on +the wire in all 116 intervals of a two minute capture. + +The two transmit periods are worth a word, because they are the one thing here +that is not ours to choose freely. Our own timeouts only decide how patient we +are with a partner that has gone quiet, and being slow there costs nobody +anything. The transmit period is the opposite: it decides when the *partner* +gives up on us. 802.3ad pairs a 1 s fast period with a 3 s short timeout, and a +30 s slow period with a 90 s long timeout, and the ratio is the whole point. + +Getting that wrong does not break the aggregate, which is what makes it easy to +miss. An earlier version of this code transmitted every 2 to 3 s while the +partner was asking for the fast rate. The links still aggregated and still +carried traffic, but every time a gap crossed 3 s the partner's receive machine +timed us out and recovered: 13 of its 93 LACPDUs came back carrying `EXPIRED` +and reporting our SYNC as cleared. Since a received actor state is recorded +verbatim as the partner state and sent back out, the flap was visible from both +ends, and the bond's churn machines never settled. With the periods above, the +same capture shows no expiry at all and both churn states read `none`. + +Our own timeouts sit one period above the standard's 3 s and 90 s, which +absorbs a single lost LACPDU plus the tick jitter. A partner whose LACP has +stopped while its link stayed up is out of the trunk after that; anything +longer keeps hashing traffic onto a member that no longer agrees to carry it. + +## LACP API + +``` +void lacp_init(void) __banked; /* boot init: clear per-LAG state */ +void lacp_off(void) __banked; +void lacp_lag_set(uint8_t lag, uint16_t ports) __banked; +void lacp_in(void) __banked; +void lacp_timers(void) __banked; +void lacp_show(void) __banked; +``` + +`lacp_init()` has to run before the startup configuration replays, because xdata +is not zeroed and the port-to-LAG map uses `0xff` as "no LAG"; a stray zero would +make a port look like it belongs to LAG 0. + +## LACP configuration on the serial console + +A group is put under LACP by adding `lacp` to the `lag` command: + +``` +lag 1 lacp 7 8 # ports 7 and 8 become LACP candidates of group 1 +lag 1 lacp off # group 1 goes back to static (so does "lag 1 lacp") +lag 1 7 8 # plain static aggregation, no protocol +``` + +A group put under LACP gives up the static trunk it had: the hardware trunk is +cleared and rebuilt from the ports that converge. A port named in another LACP +group moves to the new one. The protocol starts with the first group put under +LACP and stops when the last one is taken out; there is no separate switch for +it. Two commands act on +the whole engine: + +``` +lacp off # every group back to static, protocol stopped +lacp show +``` + +`lacp show` prints the engine state, then one line per group with its candidate +ports, the elected aggregator and the trunk members actually programmed into the +hardware, then one line per port with the actor and partner state bytes and the +receive-machine state. + +## LACP configuration via the Web Interface + +Each group on the LAG page has a Static/LACP mode next to its member ports; +for an LACP group it shows the elected aggregator and the active members. Below +the groups a table has one row per LACP port with its group, actor and partner +state, receive state, LACPDU count and partner system. The page reads +`/lacp.json` every three seconds while it is open. The member checkboxes of a +static group come from the hardware registers in `/lag.json`. + +## A test with a Linux 802.3ad bond + +Two ports of the switch to two interfaces of one host: + +``` +ip link add bond0 type bond mode 802.3ad +ip link set enp1s0 down && ip link set enp1s0 master bond0 +ip link set enp2s0 down && ip link set enp2s0 master bond0 +ip link set bond0 up +``` + +On the switch, with those two ports as the group: + +``` +lag 1 lacp 7 8 +``` + +`lacp show` should then give both ports an actor state of `0x3f`, which is +ACTIVITY, TIMEOUT, AGGREGATION, SYNC, COLLECTING and DISTRIBUTING together, a +partner state with SYNC in it, and the partner's system MAC. `lag show` should +list both ports as trunk members, since that reads the hardware rather than the +protocol's opinion of itself. + +The host's view is the one worth trusting, in `/proc/net/bonding/bond0`. Both +slaves want the **same `Aggregator ID`**; two different numbers mean the bond +has not aggregated anything and each link is sitting in its own group, which +looks healthy at a glance and is not. Both churn states should read `none`. +`monitoring` that never settles is the signature of our transmit period being +too slow for the rate the partner asked for. + +Three things are worth checking past convergence, because each of them has been +broken here at some point: + +* `tcpdump -i enp1s0 ether proto 0x8809` on each slave should show the switch's + LACPDUs and that slave's own, and never the other slave's. A sibling leak is + what the trap prevents: the PDU never enters the forwarding path at all. +* No `illegal loopback` in the host's log. +* In a hex dump of a frame from the switch, the three reserved bytes after the + actor state should be zero. They used to carry whatever the transmit buffer + held from the previous frame. + +Pulling one slave down and putting it back is the cheapest failure test. The +group narrows to the surviving member and comes back within tens of seconds, +and traffic across the transition loses a few percent at the edges rather than +half of everything, which is what it would lose if the ASIC kept hashing onto +the dead member. + +## Known limits + +* No Churn Detection machines and no Marker protocol. +* Coupled Mux: collecting and distributing are enabled together. +* One aggregator per group. +* Member compatibility is not validated; see #377. diff --git a/doc/link_aggregation.md b/doc/link_aggregation.md index b725e419..462b66de 100644 --- a/doc/link_aggregation.md +++ b/doc/link_aggregation.md @@ -227,3 +227,52 @@ iperf Done. ``` As you can see, the total throughput was 4.71 GBit/sec which is close to the maximum possible with a single 5GBit link. + +## LACP: how the PDUs reach the CPU + +Slow-protocol frames go to the reserved group `01:80:C2:00:00:02`, and the +ASIC's default action for it is *drop*. The engine sets the action to *trap* +while it runs. A trap hands the frame to the port named in `EXT_CPU_CTRL`, +which names no port out of reset - that is why the trap first looked like no +help at all: the receive counters stayed frozen while the partner kept sending, +and the frames were counted as ingress discards. `trap_init()` points the +register at the CPU port once at startup. + +The *forward* action would reach the CPU as well, since the CPU port is in the +forwarding domain, but it leaves the frame in the L2 lookup and so floods it +across the ingress VLAN. That leaks LACPDUs to unrelated ports, and the damage +is real: a bond's own ports see each other's PDUs and Linux reports an illegal +loopback, a second bond on the same switch is poisoned, and the flood escapes +through uplinks. Containing it would take a static L2 multicast entry with a +CPU-only mask for every PVID in use, since untagged LACPDUs classify into the +ingress port's PVID and the forward action is not limited to the LACP ports. + +The trap keys on the address instead, before the VLAN lookup and before the +ingress filters, which settles three things at once: + +* Nothing is written per VLAN or per PVID, so delivery no longer depends on + which PVIDs the ports happen to carry. +* A port that admits tagged frames only still receives: an untagged forwarded + LACPDU is dropped by the acceptable-frame-type filter, a trapped one is not. +* A port STP holds in blocking still receives, so a group converges without + waiting out the forward delay. + +A trapped frame arrives without the VLAN tag the hardware inserts into a +forwarded one, so the PDU body sits four bytes earlier; `TRAP_RX_BODY()` gives +the offset for both forms. + +`lacp off` puts the action back to *drop*, which acts before the L2 lookup. + +## LACP: what the implementation leaves out + +The four state machines of Clause 43 are present in simplified form. Mux control +is coupled rather than independent, one partner system is elected per LAG, and +there are no churn detection machines. + +One interoperability rule is worth stating because getting it wrong is silent. +The Partner block of an outgoing PDU has to echo the peer's own actor identity +verbatim, meaning system and priority, key, port and port priority, and the +aggregation flag. A Linux partner's `__record_pdu()` accepts our SYNC bit only +when that block mirrors what it sent. Hardcoding the priorities to zero +mismatches Linux's default of 0xffff, which clears partner SYNC, moves the state +byte from 0x3f to 0x37, and the bond never reaches collecting and distributing. diff --git a/html/app.js b/html/app.js index d737810b..4c7ed7f5 100644 --- a/html/app.js +++ b/html/app.js @@ -67,6 +67,9 @@ iso_title:"Port isolation",iso_from:"From port",iso_to:"To port",iso_all:"Allow iso_intro:"A frame entering a port may only leave through the ports ticked in its row. Isolation is decided before forwarding, so a port that is not ticked is unreachable directly, whatever the address table says.", iso_legend:"The cell of the port itself is always allowed. A port number in red marks a port that is not fully open.", lag_hash:"Hash:",lag_note:"A LAG needs at least one member to be saved in the startup config; applying an empty group clears it.", +lacp_heading:"LACP (802.3ad)",lag_mode:"Mode",lag_static:"Static",lacp_neg:"negotiating",lacp_agg:"aggregator", +lacp_members:"active members",lacp_hint:"LACP: select candidate ports and press Apply to start negotiation", +lacp_actor:"Actor",lacp_partner:"Partner",lacp_rxstate:"RX state",lacp_rx:"LACPDUs received",lacp_psys:"Partner system", lag_clear_q:"Clear LAG {n}?",lag_clear_d:"All member ports return to normal operation.", e_title:"Energy Efficient Ethernet",e_adv:"Advertised",e_lp:"Link partner",e_active:"Active",e_enable:"Enable", e_note:"Advertised and link-partner flags per speed: 100M, 1G, 2.5G. SFP ports do not support EEE.", @@ -808,7 +811,7 @@ var CONF_CMDS=[ /^port\s+\d{1,2}\s+lldp\s+(permit|block)+$/, /^eee\s+(on|off)(\s+\d{1,2})?(\s+(100m|1g|2g5))?$/, /^mirror(\s+\d{1,2})(\s+\d{1,2}[tr]?)+$/,/^mirror\s+off$/, - /^lag\s+[1-4](\s+\d{1,2})+$/,/^lag\s+[1-4]\s+d$/,/^laghash\s+[1-4](\s+\w+)+$/, + /^lag\s+[1-4](\s+\d{1,2})+$/,/^lag\s+[1-4]\s+d$/,/^lag\s+[1-4]\s+lacp(\s+\d{1,2})+$/,/^lag\s+[1-4]\s+lacp\s+off$/,/^laghash\s+[1-4](\s+\w+)+$/, /^isolate\s+\d{1,2}(\s+(off|\d{1,2}))+$/, /^stp\s+(on|off)$/,/^stp\s+(prio|hello|maxage|fwd|txhold)\s+\d{1,2}$/, /^stp\s+version\s+(rstp|stp)$/, @@ -1814,6 +1817,15 @@ function buildLag(){ for(var g=1;g<=4;g++)(function(g){ var card=h("div",{class:"card"}); card.appendChild(h("h2",{text:"LAG "+g})); + var mr=h("div",{style:"display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:10px",class:"small"}); + mr.appendChild(h("span",{class:"mut",text:t("lag_mode")})); + var ms=h("select",{class:"in",id:"lgm"+g}); + ms.appendChild(h("option",{value:"static",text:t("lag_static")})); + ms.appendChild(h("option",{value:"lacp",text:"LACP"})); + ms.addEventListener("change",function(){$("lgs"+g).textContent=ms.value==="lacp"?t("lacp_hint"):""}); + mr.appendChild(ms); + card.appendChild(mr); + card.appendChild(h("p",{class:"small mono",id:"lgs"+g,style:"margin-bottom:10px"})); var pr=h("div",{style:"display:flex;gap:10px;flex-wrap:wrap;margin-bottom:10px"}); for(var p=1;p<=S.n;p++)pr.appendChild(h("label",null,[ h("input",{type:"checkbox",id:"lg"+g+"p"+p}),document.createTextNode(" "+p+" "), @@ -1826,14 +1838,33 @@ function buildLag(){ }); card.appendChild(hr); card.appendChild(h("button",{class:"ctl pri",text:t("c_apply"),onclick:function(){lagApply(g)}})); + card.addEventListener("change",function(){card.dataset.dirty="1"}); w.appendChild(card); })(g); } +var lacpCfg=[0,0,0,0,0]; function lagLoad(){ - return getJSON("/lag.json").then(function(s){ + return getJSON("/lacp.json").then(function(c){ + var tb=$("lacptbl").tBodies[0]; + tb.innerHTML=""; + c.lags.forEach(function(lg,i){ + var g=i+1; + lacpCfg[g]=parseInt(lg.cfg,16); + if($("lgm"+g).closest(".card").dataset.dirty)return; + $("lgm"+g).value=lacpCfg[g]?"lacp":"static"; + $("lgs"+g).textContent=lacpCfg[g]?"LACP: "+t("lacp_agg")+" "+(lg.aggValid?lg.agg:"("+t("lacp_neg")+")")+", "+t("lacp_members")+" 0x"+lg.members:""; + }); + if(c.on)c.ports.forEach(function(p){ + if(p.lag===255)return; + var tr=tb.insertRow(); + [p.p,p.lag+1,p.a,p.pt,p.rs,parseInt(p.rx,16),p.psys].forEach(function(v){tr.insertCell().textContent=v}); + }); + return getJSON("/lag.json"); + }).then(function(s){ s.forEach(function(l){ var g=l.lagNum+1; - var members=parseInt(l.members,2); + if($("lgm"+g).closest(".card").dataset.dirty)return; + var members=lacpCfg[g]||parseInt(l.members,2); for(var p=1;p<=S.n;p++) $("lg"+g+"p"+p).checked=!!((members>>S.physToLog[p-1])&1); var hash=parseInt(l.hash,16); @@ -1842,21 +1873,25 @@ function lagLoad(){ }); } function lagApply(g){ - var cmd="lag "+g,n=0; + var lacp=$("lgm"+g).value==="lacp",cmd="lag "+g+(lacp?" lacp":""),n=0,pre=[]; + delete $("lgm"+g).closest(".card").dataset.dirty; for(var p=1;p<=S.n;p++)if($("lg"+g+"p"+p).checked){cmd+=" "+p;n++;} - if(!n){ + if(!lacp&&lacpCfg[g])pre.push("lag "+g+" lacp off"); + if(lacp&&!n)cmd="lag "+g+" lacp off"; + if(!n&&!lacp){ confirmModal(t("lag_clear_q",{n:g}),t("lag_clear_d"),function(){ - postCmd("lag "+g+" d").then(lagLoad).catch(function(){}); + postCmds(pre.concat(["lag "+g+" d"])).then(lagLoad).catch(function(){}); }); return; } var hcmd="laghash "+g,nh=0; HASHF.forEach(function(f){if($("lg"+g+"h"+f).checked){hcmd+=" "+f;nh++;}}); - var cmds=[cmd]; + var cmds=pre.concat([cmd]); if(nh)cmds.push(hcmd); postCmds(cmds).then(lagLoad).catch(function(){}); } -tabHooks.lag={enter:function(){needPorts(function(){buildLag();lagLoad().catch(function(){})})}}; +var lagPoller=new Poller(function(){return lagLoad().catch(function(){})},3000); +tabHooks.lag={enter:function(){needPorts(function(){buildLag();lagPoller.start()})},leave:function(){lagPoller.stop()}}; function lldpLoad(){ @@ -2245,6 +2280,7 @@ function mergeConf(base,texts){ else drop(/^eee /); conf.push(line);return; } + if((m=line.match(/^lag (\d) lacp off$/))){drop(new RegExp("^lag "+m[1]+" lacp( |$)"));return;} if(line==="mirror off"){drop(/^mirror /);return;} if(!isConfCmd(line))return; if((m=line.match(/^ingress (.+)$/))){ diff --git a/html/index.html b/html/index.html index f896dd13..21b7d17f 100644 --- a/html/index.html +++ b/html/index.html @@ -356,6 +356,11 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}
+

LACP (802.3ad)

+
+ +
PortLAGActorPartnerRX stateLACPDUs receivedPartner system
+

A LAG needs at least one member to be saved in the startup config; applying an empty group clears it.

diff --git a/httpd/httpd.c b/httpd/httpd.c index aec2eaf1..f2ca2d36 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -1036,6 +1036,8 @@ void httpd_appcall(void) send_stp(); } else if (is_word(q, "/lldp.json")) { send_lldp(); + } else if (is_word(q, "/lacp.json")) { + send_lacp(); } else if (is_word(q, "/vlanlist")) { send_vlanlist(); } else if (is_word(q, "/config")) { diff --git a/httpd/page_impl.c b/httpd/page_impl.c index 2d58457e..44c9ddcb 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -14,6 +14,7 @@ #include "version.h" #include "machine.h" #include "rtl837x_stp.h" +#include "rtl837x_lacp.h" #include "page_impl.h" #include "syslog.h" @@ -408,14 +409,19 @@ void send_l2(uint16_t idx) */ __xdata uint16_t entry = idx & 0xfff; __xdata uint16_t first_entry = 0xffff; // An illegal entry index + __xdata uint16_t mbr; __bit first = true; char_to_html('['); while (1) { entries_left--; uint8_t port = 0; uint8_t lag; + uint8_t mc = 0; reg_read_m(RTL837x_TBL_DATA_0); - REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1] & 0xfc, sfr_data[2] | (TBL_LUTREAD_NEXT_L2UC << 6), sfr_data[3]); + // Iterate with "next address" (method 2) so static multicast entries - + // an IGMP group entry, say - are listed too, not only learned or + // static unicast ones. + REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1] & 0xfc, sfr_data[2] | (TBL_LUTREAD_NEXT_ADDRESS << 6), sfr_data[3]); REG_WRITE(RTL837X_TBL_CTRL, entry >> 8, entry, TBL_L2_UNICAST, TBL_EXECUTE); do { @@ -423,7 +429,7 @@ void send_l2(uint16_t idx) } while (sfr_data[3] & TBL_EXECUTE); reg_read_m(RTL837x_L2_DATA_OUT_B); - __bit valid = (sfr_data[0] & 0x20) != 0; + __bit valid = (sfr_data[0] & 0x20) && !(sfr_data[0] & 0x10); if (valid) { /* separator + 74-byte worst-case entry + closing "]" */ if (slen + 76 > TCP_OUTBUF_SIZE) @@ -432,6 +438,8 @@ void send_l2(uint16_t idx) char_to_html(','); first = false; + mc = sfr_data[2] & 0x01; // multicast MAC: member mask, no SPA/age + // VLAN, taken from the read above instead of reading the register twice slen += strtox(outbuf + slen, "{\"vlan\":\""); charhex_to_html(sfr_data[0] & 0x0f); @@ -450,12 +458,23 @@ void send_l2(uint16_t idx) // type reg_read_m(RTL837x_L2_DATA_OUT_C); - if (sfr_data[1] & 0x1) + if (mc || (sfr_data[1] & 0x1)) slen += strtox(outbuf + slen, "\",\"type\":\"s\",\"port\":"); else slen += strtox(outbuf + slen, "\",\"type\":\"l\",\"port\":"); - port |= (sfr_data[3] & 0x3) << 2; + if (mc) { + // Report the lowest member of the mask (an entry holding only + // the CPU port is rendered as "CPU" by the UI) + mbr = port | ((uint16_t)sfr_data[3] << 2); + port = 0; + while (mbr && !(mbr & 1)) { + mbr >>= 1; + port++; + } + } else { + port |= (sfr_data[3] & 0x3) << 2; + } itoa_html(port); slen += strtox(outbuf + slen, ",\"lag\":"); lag = port_lag_of(port); @@ -736,6 +755,60 @@ void send_stp(void) slen += strtox(outbuf + slen, "]}"); } +/* LACP protocol status for the LAG page ("/lacp.json"). State variables are + * exported read-only via rtl837x_lacp.h; hardware trunk membership itself is + * already visible through send_lag() (it reads the trunk registers). */ +void send_lacp(void) +{ + dbg_string("send_lacp called\n"); + slen = strtox(outbuf, HTTP_RESPONCE_JSON); + + slen += strtox(outbuf + slen, "{\"on\":"); + bool_to_html(lacpEnabled); + /* Per-LAG section: candidate ports, elected aggregator, trunk members. */ + slen += strtox(outbuf + slen, ",\"lags\":["); + for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) { + slen += strtox(outbuf + slen, "{\"cfg\":\""); + byte_to_html(lacp_lag_ports[l] >> 8); + byte_to_html(lacp_lag_ports[l]); + slen += strtox(outbuf + slen, "\",\"aggValid\":"); + bool_to_html(lacp_agg_valid[l]); + slen += strtox(outbuf + slen, ",\"agg\":\""); + for (uint8_t j = 0; j < 6; j++) + byte_to_html(lacp_agg_sys[l][j]); + slen += strtox(outbuf + slen, "\",\"members\":\""); + byte_to_html(lacp_members_last[l] >> 8); + byte_to_html(lacp_members_last[l]); + slen += strtox(outbuf + slen, "\"},"); + } + slen -= 1; // remove comma + slen += strtox(outbuf + slen, "],\"ports\":["); + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + slen += strtox(outbuf + slen, "{\"p\":"); + itoa_html(machine.log_to_phys_port[i]); + /* Which LACP LAG this port belongs to; 255 = none */ + slen += strtox(outbuf + slen, ",\"lag\":"); + itoa_html(lacp_port_lag[i]); + slen += strtox(outbuf + slen, ",\"a\":\""); + byte_to_html(lacp_actor_state[i]); + slen += strtox(outbuf + slen, "\",\"pt\":\""); + byte_to_html(lacp_partner_state[i]); + slen += strtox(outbuf + slen, "\",\"rs\":"); + itoa_html(lacp_rx_state[i]); + /* rx as 4-digit hex: itoa16_html() only renders values up to 9999 + * correctly (see its VLAN-ID comment), the counter goes to 65535 */ + slen += strtox(outbuf + slen, ",\"rx\":\""); + byte_to_html(lacp_rx_count[i] >> 8); + byte_to_html(lacp_rx_count[i]); + slen += strtox(outbuf + slen, "\",\"psys\":\""); + for (uint8_t j = 0; j < 6; j++) + byte_to_html(lacp_partner_sys[i][j]); + slen += strtox(outbuf + slen, "\"},"); + } + slen -= 1; // remove comma + slen += strtox(outbuf + slen, "]}"); +} + void send_lldp(void) { dbg_string("send_lldp called\n"); diff --git a/httpd/page_impl.h b/httpd/page_impl.h index fd51c8c6..5133d2eb 100644 --- a/httpd/page_impl.h +++ b/httpd/page_impl.h @@ -24,6 +24,7 @@ void send_cmd_log(void); void send_lag(void); void send_stp(void); void send_lldp(void); +void send_lacp(void); void send_vlanlist(void); /* Convert only the lower nibble to ascii HEX char. diff --git a/rtl837x_igmp.c b/rtl837x_igmp.c index 3ef2b2ec..39b53405 100644 --- a/rtl837x_igmp.c +++ b/rtl837x_igmp.c @@ -22,8 +22,8 @@ extern __xdata uint8_t igmpEnabled; #include "uip.h" #include "uip_arp.h" -#pragma codeseg BANK1 -#pragma constseg BANK1 +#pragma codeseg BANK3 +#pragma constseg BANK3 extern __xdata uint8_t cpuPort; extern __xdata uint8_t sfr_data[4]; diff --git a/rtl837x_lacp.c b/rtl837x_lacp.c new file mode 100644 index 00000000..b4874f16 --- /dev/null +++ b/rtl837x_lacp.c @@ -0,0 +1,607 @@ +/* + * LACP (IEEE 802.3ad Clause 43) driver for the RTL837x platform. + * This code is in the Public Domain. + */ + +// #define DEBUG + +#pragma codeseg BANK3 +#pragma constseg BANK3 + +#include +#include "rtl837x_common.h" +#include "rtl837x_regs.h" +#include "rtl837x_lacp.h" +#include "rtl837x_trap.h" +#include "rtl837x_port.h" +#include "uip.h" +#include "machine.h" + +extern __code const struct machine machine; +extern __xdata struct machine_runtime machine_detected; /* runtime chip detection, owned by rtl837x_port.c */ +extern __xdata uint16_t management_vlan; /* owned by rtlplayground.c; suppressed per-frame for slow protocols */ +extern __xdata struct uip_eth_addr uip_ethaddr; +extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2]; +/* lacpEnabled: extern in rtl837x_lacp.h, owned by rtlplayground.c */ + +/* 20-byte Actor/Partner information body (802.3ad 43.4.2.2), network byte order */ +struct lacp_info { + uint16_t sys_prio; + uint8_t sys[6]; + uint16_t key; + uint16_t port_prio; + uint16_t port; + uint8_t state; + uint8_t reserved[3]; +}; + +/* Outgoing LACPDU (no VLAN tag on TX, as in stp_pkt) */ +struct lacpdu { + uint8_t dst[6]; + uint8_t src[6]; + struct rtl_tag rtl_tag; + uint16_t ethertype; /* 0x8809 */ + uint8_t subtype; /* 0x01 */ + uint8_t version; /* 0x01 */ + uint8_t tlv_actor; /* 0x01 */ + uint8_t actor_len; /* 0x14 */ + struct lacp_info actor; + uint8_t tlv_partner; /* 0x02 */ + uint8_t partner_len; /* 0x14 */ + struct lacp_info partner; + uint8_t tlv_collector; /* 0x03 */ + uint8_t collector_len; /* 0x10 */ + uint16_t collector_max_delay; + uint8_t collector_reserved[12]; + uint8_t tlv_terminator; /* 0x00 */ + uint8_t terminator_len; /* 0x00 */ + uint8_t terminator_reserved[50]; +}; + +/* Incoming LACPDU, split because a VLAN tag sits between the two only when the + * frame was forwarded: a trapped one carries none (cf. stp_pkt_in) */ +struct lacpdu_in_hdr { + uint8_t dst[6]; + uint8_t src[6]; + struct rtl_tag rtl_tag; +}; + +struct lacpdu_in { + uint16_t ethertype; + uint8_t subtype; + uint8_t version; + uint8_t tlv_actor; + uint8_t actor_len; + struct lacp_info actor; + uint8_t tlv_partner; + uint8_t partner_len; + struct lacp_info partner; +}; + +__xdata uint8_t lacp_rxbody; /* offset of the LACPDU body: the RTL tag, plus a VLAN tag when forwarded */ + +#define LACP_O ((__xdata struct lacpdu *)&uip_buf[RTL_FRAME_DESC_SIZE]) +#define LACP_H ((__xdata struct lacpdu_in_hdr *)&uip_buf[0]) +#define LACP_I ((__xdata struct lacpdu_in *)&uip_buf[lacp_rxbody]) + +/* --- Per-port participant state (802.3ad 43.4.7). Indexed by physical port. --- */ +__xdata uint8_t lacp_actor_state[10]; /* our Actor_State flags */ +__xdata uint8_t lacp_rx_state[10]; /* LACP_RX_* receive machine */ +__xdata uint8_t lacp_partner_state[10]; /* last Partner_State we saw */ +__xdata uint8_t lacp_partner_sys[10][6]; /* partner System ID */ +__xdata uint16_t lacp_partner_key[10]; /* partner Key */ +__xdata uint16_t lacp_partner_port[10]; /* partner Port number */ +/* The Partner block echoes the peer's actor identity verbatim, see + * doc/link_aggregation.md. */ +__xdata uint16_t lacp_partner_sys_prio[10]; /* partner System priority */ +__xdata uint16_t lacp_partner_port_prio[10]; /* partner Port priority */ +__xdata uint16_t lacp_periodic[10]; /* down-counter to next TX */ +__xdata uint16_t lacp_timeout[10]; /* down-counter to partner expiry*/ +__xdata uint8_t lacp_ntt[10]; /* Need-To-Transmit flag */ + +/* The timers step every four system ticks (50 Hz) whatever the pace of the + * main loop; a long pass is caught up on, a few steps at a time. */ +#define LACP_TICK_STEP (SYS_TICK_HZ / 50) +#define LACP_CATCH_UP 8 +__xdata uint8_t lacp_tick_last; +extern volatile __xdata uint32_t ticks; + +/* Per-LAG state: the hardware trunk groups run LACP independently. */ +__xdata uint16_t lacp_lag_ports[LACP_NUM_LAGS]; +__xdata uint8_t lacp_port_lag[10]; +__xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][6]; +__xdata uint8_t lacp_agg_valid[LACP_NUM_LAGS]; +__xdata uint16_t lacp_members_last[LACP_NUM_LAGS]; + +__xdata uint16_t lacp_scratch_mask; +__xdata uint8_t lacp_scratch_flag; +__xdata uint8_t lacp_scratch_lag; + +/* Per-port RX LACPDU count - primarily a hardware bring-up diagnostic to + * verify slow-protocol frames actually reach the CPU port (see "lacp show") */ +__xdata uint16_t lacp_rx_count[10]; + + +void lacp_mux_update(void) __banked; /* defined below, used from lacp_in */ + +static uint8_t lacp_sys_eq(__xdata uint8_t *a, __xdata uint8_t *b) +{ + for (uint8_t i = 0; i < 6; i++) { + if (a[i] != b[i]) + return 0; + } + return 1; +} + + +/* + * Selection logic (simplified 43.4.14): a port may join the aggregator when + * we have fresh partner info, the partner considers the link aggregatable, + * and the partner is the same system the aggregator was elected for. + */ +static uint8_t lacp_port_selected(uint8_t port) +{ + uint8_t lag = lacp_port_lag[port]; + if (lag == LACP_LAG_NONE) /* port not in any LACP LAG */ + return 0; + if (lacp_rx_state[port] != LACP_RX_CURRENT) + return 0; + if (!(lacp_partner_state[port] & LACP_STATE_AGGREGATION)) + return 0; + if (!lacp_agg_valid[lag]) + return 0; + return lacp_sys_eq(lacp_partner_sys[port], lacp_agg_sys[lag]); +} + + +/* + * Mux machine (simplified coupled control, 43.4.15): SYNC follows selection; + * COLLECTING+DISTRIBUTING additionally require the partner to be in sync. + * Returns 1 if the actor state changed (caller sets NTT). + */ +static uint8_t lacp_mux_machine(uint8_t port) +{ + uint8_t old = lacp_actor_state[port]; + uint8_t new = old; + + if (lacp_port_selected(port)) { + new |= LACP_STATE_SYNC; + if (lacp_partner_state[port] & LACP_STATE_SYNC) + new |= LACP_STATE_COLLECTING | LACP_STATE_DISTRIBUTING; + else + new &= ~(LACP_STATE_COLLECTING | LACP_STATE_DISTRIBUTING); + } else { + new &= ~(LACP_STATE_SYNC | LACP_STATE_COLLECTING | LACP_STATE_DISTRIBUTING); + } + + if (new == old) + return 0; + lacp_actor_state[port] = new; + return 1; +} + +#define port_bit(p) (((uint8_t)1) << (p)) + + +/* Build and emit one LACPDU out physical port `port` (802.3ad 43.4.2). */ +void lacp_send(uint8_t port) __banked +{ + memset((__xdata uint8_t *)LACP_O, 0, sizeof(struct lacpdu)); + LACP_O->dst[0] = 0x01; LACP_O->dst[1] = 0x80; LACP_O->dst[2] = 0xc2; + LACP_O->dst[3] = 0x00; LACP_O->dst[4] = 0x00; LACP_O->dst[5] = LACP_DST5; + memcpy(LACP_O->src, uip_ethaddr.addr, 6); + + LACP_O->rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID); + LACP_O->rtl_tag.version = RTL_FRAME_TAG_VERSION; + LACP_O->rtl_tag.reason = 0x00; + /* Must go through HTONS like every other tag field: writing 0x0020 raw put + * the bits in the wrong byte (on the wire 0x2000 = EFID, not LEARN_DIS), so + * the ASIC could not parse the tag and forwarded the frame with the 0x8899 + * header still attached - the partner then saw ethertype 0x8899 instead of + * 0x8809 and ignored the LACPDU. KEEP additionally stops the switch from + * rewriting the 802.1Q tag format of a CPU-injected frame (as mainline). */ + LACP_O->rtl_tag.flags = HTONS(RTL_TAG_LEARN_DIS | RTL_TAG_KEEP); + /* ALLOW cleared => this word is the *forwarding* port mask, i.e. directed + * egress to exactly this port (what mainline does). Setting ALLOW instead + * makes it an allowance/permission mask on top of a normal lookup, which + * for a one-hot mask resolves to an empty egress set and the frame is + * silently dropped - verified on hardware. */ + LACP_O->rtl_tag.pmask = HTONS(port_bit(port)); /* egress this port only */ + + LACP_O->ethertype = HTONS(SLOW_PROTO_ETHERTYPE); + LACP_O->subtype = SLOW_PROTO_SUBTYPE_LACP; + LACP_O->version = LACP_VERSION; + + /* Actor TLV */ + LACP_O->tlv_actor = LACP_TLV_ACTOR; + LACP_O->actor_len = LACP_TLV_LEN_INFO; + LACP_O->actor.sys_prio = HTONS(LACP_SYS_PRIO); + memcpy(LACP_O->actor.sys, uip_ethaddr.addr, 6); + /* Per-LAG Actor Key so a partner never merges ports of our different LAGs + * into one aggregate (only ever called for ports in a LACP LAG). */ + LACP_O->actor.key = HTONS((uint16_t)(lacp_port_lag[port] + 1)); + LACP_O->actor.port_prio = HTONS(LACP_DEF_PORT_PRIO); + LACP_O->actor.port = HTONS((uint16_t)port + 1); /* 1-based port id */ + LACP_O->actor.state = lacp_actor_state[port]; + + /* Partner TLV: echo the last partner info we recorded, VERBATIM. A Linux + * 802.3ad partner (__record_pdu) only accepts the SYNC bit we set above if + * this block mirrors its own actor identity exactly - system+priority, key, + * port+priority. Priorities must be echoed, not hardcoded: Linux defaults to + * system priority 0xffff, so a hardcoded 0 here cleared its partner-SYNC + * (0x3f->0x37) and the aggregate never collected/distributed. */ + LACP_O->tlv_partner = LACP_TLV_PARTNER; + LACP_O->partner_len = LACP_TLV_LEN_INFO; + LACP_O->partner.sys_prio = HTONS(lacp_partner_sys_prio[port]); + memcpy(LACP_O->partner.sys, lacp_partner_sys[port], 6); + LACP_O->partner.key = HTONS(lacp_partner_key[port]); + LACP_O->partner.port_prio = HTONS(lacp_partner_port_prio[port]); + LACP_O->partner.port = HTONS(lacp_partner_port[port]); + LACP_O->partner.state = lacp_partner_state[port]; + + /* Collector TLV + Terminator */ + LACP_O->tlv_collector = LACP_TLV_COLLECTOR; + LACP_O->collector_len = LACP_TLV_LEN_COLLECTOR; + LACP_O->collector_max_delay = 0; + LACP_O->tlv_terminator = LACP_TLV_TERMINATOR; + LACP_O->terminator_len = 0x00; + + lacp_ntt[port] = 0; + /* Slow-protocol frames are link-local and must egress untagged. With a + * management VLAN set, tcpip_output() splices an 802.1Q tag after the SA, + * shifting our in-frame rtl_tag out of the position the ASIC expects - so it + * fails to parse/strip the CPU tag and the 0x8899 header leaks onto the wire + * (the partner then ignores the LACPDU). Suppress the VLAN insert for this + * frame only; management traffic keeps its tag. Verified on HW: with the + * insert suppressed the LACPDU egresses as clean 0x8809 and the bond + * partner converges (partner MAC becomes the switch, both links aggregate). */ + uint16_t saved_mgmt_vlan = management_vlan; + management_vlan = 0; + uip_len = sizeof(struct lacpdu); + tcpip_output(); + management_vlan = saved_mgmt_vlan; +} + + +/* + * Process an incoming LACPDU. Called from the rtlplayground.c dispatch when a + * frame with DA 01:80:C2:00:00:02 and ethertype 0x8809/subtype 0x01 arrives. + * The ingress port is the low nibble of rtl_tag.pmask on RX. + */ +void lacp_in(void) __banked +{ + lacp_rxbody = TRAP_RX_BODY(); + if (uip_len < lacp_rxbody + sizeof(struct lacpdu_in)) { + uip_len = 0; + return; + } + uip_len = 0; + + if (LACP_I->ethertype != HTONS(SLOW_PROTO_ETHERTYPE) + || LACP_I->subtype != SLOW_PROTO_SUBTYPE_LACP) + return; + + /* Per rtl837x_common.h, pmask carries a 4-bit port number on RX + * (hardware-verified on RTL8373: per-port rx counters track the actual + * ingress port through full LACP convergence). */ + uint8_t port = ((uint8_t)HTONS(LACP_H->rtl_tag.pmask)) & 0x0f; + if (port < machine.min_port || port > machine.max_port) + return; + + /* Only ports assigned to a LACP-mode LAG participate in the protocol. */ + uint8_t lag = lacp_port_lag[port]; + if (lag == LACP_LAG_NONE) + return; + + +#ifdef DEBUG + print_string("LACP in, port "); print_byte(port); + print_string(" partner_state "); print_byte(LACP_I->actor.state); write_char('\n'); +#endif + + lacp_rx_count[port]++; + + /* Record partner = the remote's Actor block (802.3ad 43.4.9 recordPDU) */ + memcpy(lacp_partner_sys[port], LACP_I->actor.sys, 6); + lacp_partner_key[port] = HTONS(LACP_I->actor.key); + lacp_partner_port[port] = HTONS(LACP_I->actor.port); + lacp_partner_sys_prio[port] = HTONS(LACP_I->actor.sys_prio); + lacp_partner_port_prio[port] = HTONS(LACP_I->actor.port_prio); + lacp_partner_state[port] = LACP_I->actor.state; + + /* Receive machine -> CURRENT, (re)arm partner timeout (43.4.12) */ + lacp_rx_state[port] = LACP_RX_CURRENT; + lacp_timeout[port] = (LACP_I->actor.state & LACP_STATE_TIMEOUT) + ? LACP_SHORT_TIMEOUT : LACP_LONG_TIMEOUT; + + /* Elect this LAG's aggregator partner system on first contact (43.4.14) */ + if (!lacp_agg_valid[lag]) { + memcpy(lacp_agg_sys[lag], LACP_I->actor.sys, 6); + lacp_agg_valid[lag] = 1; + } + + /* Selection + Mux for this port; state change => Need-To-Transmit */ + if (lacp_mux_machine(port)) + lacp_ntt[port] = 1; + + /* update_NTT (43.4.12): if the partner's view of us is stale (their + * Partner block does not match our actor state/port), tell them again. */ + if (LACP_I->partner.state != lacp_actor_state[port] + || HTONS(LACP_I->partner.port) != (uint16_t)port + 1) + lacp_ntt[port] = 1; + + lacp_mux_update(); +} + + +/* Recompute each LACP LAG's hardware trunk membership: a port joins its LAG's + * trunk once it is fully in sync (SYNC+COLLECTING+DISTRIBUTING and the partner + * in SYNC). Each LACP-mode LAG is programmed independently (simplified 43.4.15). */ +void lacp_mux_update(void) __banked +{ + for (uint8_t lag = 0; lag < LACP_NUM_LAGS; lag++) { + if (!lacp_lag_ports[lag]) /* LAG not under LACP management */ + continue; + lacp_scratch_mask = 0; + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + if (lacp_port_lag[i] != lag) + continue; + if ((lacp_actor_state[i] & LACP_STATE_FULL) == LACP_STATE_FULL + && (lacp_partner_state[i] & LACP_STATE_SYNC)) + lacp_scratch_mask |= port_bit(i); + } + /* Program the trunk only when this LAG's membership actually changed + * - this runs on every timer tick. */ + if (lacp_scratch_mask != lacp_members_last[lag]) { + lacp_members_last[lag] = lacp_scratch_mask; + port_lag_members_set(lag, lacp_scratch_mask); + } + } +} + + +static void lacp_step(void) +{ + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + if (lacp_port_lag[i] == LACP_LAG_NONE) /* not in a LACP LAG */ + continue; + + /* Periodic transmit machine (802.3ad 43.4.13) */ + if (lacp_periodic[i]) { + lacp_periodic[i]--; + } else { + lacp_periodic[i] = (lacp_partner_state[i] & LACP_STATE_TIMEOUT) + ? LACP_FAST_PERIODIC : LACP_SLOW_PERIODIC; + lacp_ntt[i] = 1; + } + + /* Partner timeout -> DEFAULTED (802.3ad 43.4.12): the mux machine + * then drops SYNC/COLLECTING/DISTRIBUTING and the port leaves the + * trunk. TODO(43.4.12): churn detection is diagnostics-only, skipped. */ + if (lacp_timeout[i]) { + if (!--lacp_timeout[i]) { + lacp_rx_state[i] = LACP_RX_DEFAULTED; + lacp_partner_state[i] = LACP_STATE_DEFAULTED; + if (lacp_mux_machine(i)) + lacp_ntt[i] = 1; + } + } + + if (lacp_ntt[i]) + lacp_send(i); + } + + /* Release each LAG's aggregator identity once none of its ports still + * hears the elected partner, so a re-cabled setup can elect a new one + * (43.4.14). A port that is CURRENT with some other system must not keep + * the old identity alive: it would never be selected, and neither would + * the rest of the LAG once it moves to that system too. */ + for (uint8_t lag = 0; lag < LACP_NUM_LAGS; lag++) { + if (!lacp_agg_valid[lag]) + continue; + uint8_t any_current = 0; + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + if (lacp_port_lag[i] == lag && lacp_rx_state[i] == LACP_RX_CURRENT + && lacp_sys_eq(lacp_partner_sys[i], lacp_agg_sys[lag])) { + any_current = 1; + break; + } + } + if (!any_current) + lacp_agg_valid[lag] = 0; + } + + lacp_mux_update(); +} + + +void lacp_timers(void) __banked +{ + uint8_t n = LACP_CATCH_UP; + while (n-- && TICKS_DUE(lacp_tick_last, LACP_TICK_STEP)) { + lacp_tick_last += LACP_TICK_STEP; + lacp_step(); + } +} + + +/* Bring one port up as an active-fast LACP participant (43.4.12 init). */ +static void lacp_port_init(uint8_t port) +{ + lacp_actor_state[port] = LACP_STATE_ACTIVITY | LACP_STATE_AGGREGATION + | LACP_STATE_TIMEOUT; /* active + fast */ + lacp_rx_state[port] = LACP_RX_INITIALIZE; + lacp_partner_state[port] = LACP_STATE_DEFAULTED; + memset(lacp_partner_sys[port], 0, 6); + lacp_partner_key[port] = 0; + lacp_partner_port[port] = 0; + lacp_periodic[port] = LACP_FAST_PERIODIC; + lacp_timeout[port] = 0; + lacp_ntt[port] = 1; /* announce ourselves immediately */ + lacp_rx_count[port] = 0; +} + +/* Return a port to non-LACP state. (Port isolation is no longer touched - a + * trapped LACPDU never enters the forwarding path, so a sibling cannot see + * it, which is what the isolation workaround was for.) */ +static void lacp_port_release(uint8_t port) +{ + lacp_actor_state[port] = 0; + lacp_ntt[port] = 0; + lacp_partner_state[port] = 0; + lacp_rx_state[port] = LACP_RX_INITIALIZE; + lacp_rx_count[port] = 0; + memset(lacp_partner_sys[port], 0, 6); +} + +/* True while any LAG is under LACP management (drives the global RMA trap). */ +static uint8_t lacp_any_lag(void) +{ + for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) + if (lacp_lag_ports[l]) + return 1; + return 0; +} + +/* + * Turn the LACP engine on: trap the Slow-Protocols group to the CPU port. The + * trap acts on the address before the VLAN lookup and before the ingress + * filters, so delivery needs no L2 entry and no PVID of its own. + */ +static void lacp_engine_on(void) +{ + lacp_tick_last = (uint8_t)ticks; + lacpEnabled = 1; + trap_rma_set(RTL837X_RMA2_CONF, RTL837X_RMA_ACT_TRAP); +} + +/* Turn the LACP engine off: stop delivering slow-protocols to the CPU. */ +static void lacp_engine_off(void) +{ + trap_rma_set(RTL837X_RMA2_CONF, RTL837X_RMA_ACT_DROP); + lacpEnabled = 0; +} + +/* One-time boot init: no LAG runs LACP yet, so every port maps to LACP_LAG_NONE. + * MUST run before any "lag ... lacp" config replay - xdata is not zeroed to the + * 0xff sentinel, and a stray 0 would make a port look like it belongs to LAG 0. */ +void lacp_init(void) __banked +{ + for (uint8_t i = 0; i < 10; i++) { + lacp_port_lag[i] = LACP_LAG_NONE; + lacp_actor_state[i] = 0; + lacp_ntt[i] = 0; + } + for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) { + lacp_lag_ports[l] = 0; + lacp_agg_valid[l] = 0; + lacp_members_last[l] = 0; + } + lacpEnabled = 0; +} + +/* + * Assign a candidate-port mask to LACP-mode LAG `lag` (`lag lacp `). + * ports == 0 removes the LAG from LACP management. Ports leaving the LAG are + * returned to normal switching; ports joining start the protocol immediately. + * The engine's RMA trap is enabled on the first LACP LAG and torn down with the + * last. Never wipes a static "lag" the user configured on a different group. + */ +void lacp_lag_set(uint8_t lag, uint16_t ports) __banked +{ + if (lag >= LACP_NUM_LAGS) + return; + if (ports && ports == lacp_lag_ports[lag]) + return; + lacp_scratch_flag = lacp_any_lag(); + + /* Drop ports that were in this LAG but are not in the new mask. */ + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + if (lacp_port_lag[i] == lag && !(ports & port_bit(i))) { + lacp_port_lag[i] = LACP_LAG_NONE; + lacp_port_release(i); + } + } + /* Release this LAG's trunk: LACP's own, or the static one a group put + * under LACP still has. Taking the group out (ports == 0) leaves a static + * trunk alone. */ + if (lacp_members_last[lag] || ports) { + lacp_members_last[lag] = 0; + port_lag_members_set(lag, 0); + } + lacp_agg_valid[lag] = 0; + lacp_lag_ports[lag] = ports; + + if (ports && !lacp_scratch_flag) /* first LACP LAG: bring the engine up first */ + lacp_engine_on(); + + /* Adopt the new member ports (with the engine already up). A port that + * belonged to another LACP LAG leaves that LAG's candidate set first. */ + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + if (!(ports & port_bit(i))) + continue; + lacp_scratch_lag = lacp_port_lag[i]; + if (lacp_scratch_lag != LACP_LAG_NONE && lacp_scratch_lag != lag) { + lacp_lag_ports[lacp_scratch_lag] &= ~port_bit(i); + if (!lacp_lag_ports[lacp_scratch_lag]) { + lacp_agg_valid[lacp_scratch_lag] = 0; + if (lacp_members_last[lacp_scratch_lag]) { + lacp_members_last[lacp_scratch_lag] = 0; + port_lag_members_set(lacp_scratch_lag, 0); + } + } + } + lacp_port_lag[i] = lag; + lacp_port_init(i); + } + + if (!lacp_any_lag() && lacp_scratch_flag) /* removed the last LACP LAG */ + lacp_engine_off(); + + lacp_mux_update(); /* a LAG that lost a port to this one drops it from its trunk */ +} + + +/* "lacp off": remove every LACP LAG (releases their trunks, stops the engine). + * There is no "lacp on": the engine comes up with the first "lag lacp". */ +void lacp_off(void) __banked +{ + for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) + if (lacp_lag_ports[l]) + lacp_lag_set(l, 0); +} + + +/* "lacp show": per-port protocol state - the primary bring-up diagnostic. + * A stuck rx=0 counter means slow-protocol frames never reach the CPU port + * (see the RMA note in lacp_engine_on()). */ +void lacp_show(void) __banked +{ + print_string("LACP "); print_string(lacpEnabled ? "on" : "off"); write_char('\n'); + for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) { + if (!lacp_lag_ports[l]) + continue; + print_string("lag "); print_byte(l + 1); + print_string(" ports "); print_short(lacp_lag_ports[l]); + print_string(" aggregator "); + if (lacp_agg_valid[l]) { + for (uint8_t j = 0; j < 6; j++) + print_byte(lacp_agg_sys[l][j]); + } else { + print_string("(none)"); + } + print_string(" members "); print_short(lacp_members_last[l]); write_char('\n'); + } + + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + print_string("port "); print_byte(machine.log_to_phys_port[i]); + print_string(" actor "); print_byte(lacp_actor_state[i]); + print_string(" partner "); print_byte(lacp_partner_state[i]); + print_string(" rxst "); print_byte(lacp_rx_state[i]); + print_string(" rx "); print_short(lacp_rx_count[i]); + print_string(" psys "); + for (uint8_t j = 0; j < 6; j++) + print_byte(lacp_partner_sys[i][j]); + write_char('\n'); + } +} diff --git a/rtl837x_lacp.h b/rtl837x_lacp.h new file mode 100644 index 00000000..5f03a16e --- /dev/null +++ b/rtl837x_lacp.h @@ -0,0 +1,101 @@ +#ifndef _RTL837X_LACP_H_ +#define _RTL837X_LACP_H_ + +/* + * LACP (IEEE 802.3ad Clause 43) for the RTL837x platform. + * This code is in the Public Domain. + */ + +#include + +void lacp_init(void) __banked; /* boot init: clear per-LAG state */ +void lacp_in(void) __banked; +void lacp_timers(void) __banked; +void lacp_off(void) __banked; +void lacp_show(void) __banked; /* "lacp show" - per-port state + RX counters */ +/* Assign a candidate-port mask to a LACP-mode LAG (`lag lacp `). + * ports == 0 removes the LAG from LACP management. Enables the engine on first + * LACP LAG and tears it down when the last one goes away. */ +void lacp_lag_set(uint8_t lag, uint16_t ports) __banked; + +/* + * Per-LAG LACP: each of the 4 hardware trunk groups (0-3) can independently run + * LACP on an admin-assigned set of candidate ports, forming its own aggregator + * (its own elected partner System). A port participates in LACP only if it has + * been assigned to a LACP-mode LAG (lacp_port_lag[port] != LACP_LAG_NONE). + * Ports of the same LAG that converge with a consistent partner join that LAG's + * hardware trunk. This is the port-channel model: `lag lacp `. + */ +#define LACP_NUM_LAGS 4 +#define LACP_LAG_NONE 0xff /* port not assigned to any LACP LAG */ + +/* Protocol state, exposed read-only for the web UI (page_impl.c send_lacp()) + * and the serial console. Owned by rtl837x_lacp.c / rtlplayground.c. */ +extern __xdata uint8_t lacpEnabled; +extern __xdata uint8_t lacp_actor_state[10]; +extern __xdata uint8_t lacp_partner_state[10]; +extern __xdata uint8_t lacp_rx_state[10]; +extern __xdata uint8_t lacp_partner_sys[10][6]; +extern __xdata uint16_t lacp_rx_count[10]; +extern __xdata uint8_t lacp_port_lag[10]; /* LAG (0-3) a port runs LACP on, or LACP_LAG_NONE */ +extern __xdata uint16_t lacp_lag_ports[LACP_NUM_LAGS]; /* admin candidate-port mask per LACP LAG (0 = LAG not LACP) */ +extern __xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][6]; /* elected partner System per LAG */ +extern __xdata uint8_t lacp_agg_valid[LACP_NUM_LAGS]; /* aggregator elected for this LAG */ +extern __xdata uint16_t lacp_members_last[LACP_NUM_LAGS];/* trunk members we last programmed per LAG */ + +/* Slow-Protocols / LACPDU identifiers (802.3ad 43.4) */ +#define SLOW_PROTO_ETHERTYPE 0x8809 +#define SLOW_PROTO_SUBTYPE_LACP 0x01 +#define LACP_VERSION 0x01 +#define LACP_DST5 0x02 /* 01:80:C2:00:00:02 last octet */ + +/* LACPDU TLV framing (802.3ad 43.4.2): type bytes + fixed TLV lengths. */ +#define LACP_TLV_ACTOR 0x01 +#define LACP_TLV_PARTNER 0x02 +#define LACP_TLV_COLLECTOR 0x03 +#define LACP_TLV_TERMINATOR 0x00 +#define LACP_TLV_LEN_INFO 0x14 /* Actor/Partner Information TLV length */ +#define LACP_TLV_LEN_COLLECTOR 0x10 /* Collector TLV length */ + +/* Our aggregation identity: System priority is a constant (Linux 802.3ad + * defaults to the same 0xffff); the default Actor port priority likewise. */ +#define LACP_SYS_PRIO 0xffff +#define LACP_DEF_PORT_PRIO 0x00ff + +/* Actor_State / Partner_State flag bits (802.3ad 43.4.2) */ +#define LACP_STATE_ACTIVITY 0x01 /* 1 = Active LACP (send periodically) */ +#define LACP_STATE_TIMEOUT 0x02 /* 1 = Short (fast) timeout */ +#define LACP_STATE_AGGREGATION 0x04 /* 1 = link is aggregatable */ +#define LACP_STATE_SYNC 0x08 /* 1 = in sync with partner */ +#define LACP_STATE_COLLECTING 0x10 +#define LACP_STATE_DISTRIBUTING 0x20 +#define LACP_STATE_DEFAULTED 0x40 /* partner info is administrative default*/ +#define LACP_STATE_EXPIRED 0x80 /* wire-format doc; unused by the simplified RX machine */ + +/* Fully-participating actor: in sync AND collecting AND distributing. */ +#define LACP_STATE_FULL (LACP_STATE_SYNC | LACP_STATE_COLLECTING | LACP_STATE_DISTRIBUTING) + +/* + * Timer units: one step of the LACP timers, taken every four system ticks, + * 50 per second. A long pass of the main loop is caught up on a few steps at + * a time, so a period is a range and the worst case is what matters. + * + * The transmit periods are not free to choose. Our own timeouts only decide + * how patient we are, but our TX period decides when the PARTNER gives up on + * us: 802.3ad pairs fast periodic 1 s with a 3 s short timeout, and slow + * periodic 30 s with a 90 s long timeout. A period whose worst case reaches + * the partner's threshold makes it expire us intermittently, which reads as + * an aggregate that works but never stops churning. */ +#define LACP_FAST_PERIODIC 0x0032 /* fast TX 1 s; partner expires at 3 s */ +#define LACP_SLOW_PERIODIC 0x05dc /* slow TX 30 s; partner expires at 90 s */ +#define LACP_SHORT_TIMEOUT 0x00c8 /* silent partner dropped after 4 s (802.3ad: 3 s) */ +#define LACP_LONG_TIMEOUT 0x1770 /* long-timeout variant, after 120 s (802.3ad: 90 s) */ + +/* Per-port LACP receive-machine state (802.3ad 43.4.12) */ +#define LACP_RX_INITIALIZE 0 +#define LACP_RX_PORT_DISABLED 1 /* numbering reserved (exported via /lacp.json "rs") */ +#define LACP_RX_EXPIRED 2 /* numbering reserved (exported via /lacp.json "rs") */ +#define LACP_RX_DEFAULTED 3 +#define LACP_RX_CURRENT 4 + +#endif diff --git a/rtl837x_regs.h b/rtl837x_regs.h index c2117fb1..13150ec4 100644 --- a/rtl837x_regs.h +++ b/rtl837x_regs.h @@ -284,6 +284,10 @@ * Loop detection / STP */ #define RTL8373_RLDP_TIMER 0x1074 +/* Per reserved-multicast-address action. The registers are packed and + * consecutive by ENTRY rather than by the byte of the address: entry 0 is + * 01:80:C2:00:00:00 at 0x4ecc and entry 2 is ...:02 (Slow Protocols / LACP) + * at 0x4ed4, but entry 5 is ...:08, so the step of 4 counts entries. */ #define RTL837X_RMA0_CONF 0x4ecc #define RTL837X_RMA0E_CONF 0x4ee8 #define RTL837X_RMA2_CONF 0x4ed4 diff --git a/rtlplayground.c b/rtlplayground.c index 3f7860dc..f61140a2 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -13,6 +13,7 @@ #include "rtl837x_port.h" #include "rtl837x_trap.h" #include "rtl837x_stp.h" +#include "rtl837x_lacp.h" #include "rtl837x_igmp.h" #include "rtl837x_leds.h" #include "rtl837x_bandwidth.h" @@ -133,6 +134,7 @@ __xdata bool stp_enabled; __xdata uint8_t igmpEnabled; __xdata bool lldp_enabled; __xdata char hostname[24]; /* device hostname, default set at boot, see rtl837x_common.h */ +__xdata uint8_t lacpEnabled; __code const uint16_t bit_mask[16] = { 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, @@ -1059,12 +1061,18 @@ void handle_rx(void) print_byte(uip_buf[3]); print_byte(uip_buf[4]); print_byte(uip_buf[5]); write_char('\n'); print_string(" MGMT-VLAN: "); print_short(management_vlan); write_char('\n'); #endif - if (stp_enabled && uip_buf[0] == 0x01 && uip_buf[1] == 0x80 && uip_buf[2] == 0xc2 // STP packet? - && uip_buf[3] == 0x00 && uip_buf[4] == 0x00 && uip_buf[5] == 0x00) { - stp_in(); - if (uip_len) { - print_string("STP TX\n"); - tcpip_output(); + if ((stp_enabled || lacpEnabled) && uip_buf[0] == 0x01 && uip_buf[1] == 0x80 + && uip_buf[2] == 0xc2 && uip_buf[3] == 0x00 && uip_buf[4] == 0x00) { // 01:80:C2:00:00:0x + if (stp_enabled && uip_buf[5] == 0x00) { // STP/RSTP BPDU + stp_in(); + if (uip_len) { + print_string("STP TX\n"); + tcpip_output(); + } + } else if (lacpEnabled && uip_buf[5] == 0x02) { // LACPDU (slow protocols) + lacp_in(); + if (uip_len) + tcpip_output(); } } else if (igmpEnabled && uip_buf[0] == 0x01 && uip_buf[1] == 0x00 && uip_buf[2] == 0x5e // IPv4-MC packet? && igmp_packet_handler()) { @@ -1290,6 +1298,9 @@ static void handle_tick(void) stp_timers(); } } + // If LACP enabled, drive its machines (own tick divider lives in the banked module) + if (lacpEnabled) + lacp_timers(); health_phase(HEALTH_PH_STP); lldp_tick(); health_phase(HEALTH_LLDP); @@ -1792,6 +1803,8 @@ void main(void) #endif stp_enabled = 0; stp_defaults(); /* 802.1D/w default config before any "stp ..." replay */ + lacpEnabled = 0; + lacp_init(); /* clear per-LAG state (port->LAG map = NONE) before any config replay */ nic_setup(); REG_SET(RTL837X_NIC_INT_MSK, NIC_INT_RXIE); trap_init(); diff --git a/test/Makefile b/test/Makefile index ae3884ac..e47cbf4e 100644 --- a/test/Makefile +++ b/test/Makefile @@ -59,15 +59,24 @@ $(BUILDDIR)/test_page_json: test_page_json.c $(TABLE_ENV) hw_mock.h | $(BUILDDIR $(BUILDDIR)/test_httpd_tx: test_httpd_tx.c $(SRCROOT)/httpd/httpd.c $(SRCROOT)/uip/uip.c | $(BUILDDIR) $(CC) $(CFLAGS) $(filter %.c,$^) -o $@ $(LDFLAGS) +# Sandboxes with a Makefile of their own; each fails the run when it fails. +SUBTESTS = lacp port_hash + run: $(TESTS) @fail=0; for t in $(TESTS); do \ echo "=== $$t ==="; \ $$t || fail=1; \ echo; \ done; \ + for d in $(SUBTESTS); do \ + echo "=== $$d ==="; \ + $(MAKE) -C $$d test || fail=1; \ + echo; \ + done; \ if [ $$fail -ne 0 ]; then echo "TESTS FAILED"; exit 1; else echo "ALL TESTS PASSED"; fi clean: rm -rf $(BUILDDIR) + @for d in $(SUBTESTS); do $(MAKE) -C $$d clean; done .PHONY: all run clean diff --git a/test/README.md b/test/README.md index 08ec0cef..4f040d5a 100644 --- a/test/README.md +++ b/test/README.md @@ -48,6 +48,8 @@ fires — so it drops straight into CI. | `test_port_tables` | `rtl837x_port.c` | VLAN entry layout and round trip, PVID register sharing, static multicast and management entries, per-port flush, trunk membership and hash seed | | `test_page_json` | `httpd/page_impl.c` + `rtl837x_port.c` | `/vlan.json`, `/vlanlist`, `/l2.json` (walk, wrap marker, paging inside `outbuf`), 64-bit counters in `/status.json` and `/counters.json` | | `test_httpd_tx` | `httpd/httpd.c` + `uip/uip.c` | a GET of a static file against a client that moves its receive window: ACK accounting, continuation chunks out of flash, retransmission | +| `lacp/` | `rtl837x_lacp.c` | state machines against a simulated 802.3ad partner: convergence, mis-cabling, expiry, re-election, two groups, LACPDU containment entries | +| `port_hash/` | `port_lag_members_set()` | a LAG seeds its own hash register | ## Adding a test for another module 1. Write `test_.c` with `main()` driving the module's entry points and diff --git a/test/env_tables.c b/test/env_tables.c index 41694b72..6a841a19 100644 --- a/test/env_tables.c +++ b/test/env_tables.c @@ -16,6 +16,7 @@ #include "syslog.h" #include "rtl837x_phy.h" #include "support.h" +#include "rtl837x_lacp.h" /* ---- console: everything lands in out_buf (support.c) ---- */ void print_byte(uint8_t v) @@ -124,3 +125,18 @@ uint8_t stp_prio = 0x80, stp_root_port = 0xff, stp_rstp = 1, stp_txhold = 6; uint8_t sfp_buf[16]; bool sfp_read_block(uint8_t slot, uint8_t reg, uint8_t len) { (void)slot; (void)reg; (void)len; return false; } void print_phys_port(uint8_t port) { itoa_short(machine.log_to_phys_port[port]); } + +uint8_t lacpEnabled; +uint8_t lacp_actor_state[10]; +uint8_t lacp_partner_state[10]; +uint8_t lacp_rx_state[10]; +uint8_t lacp_partner_sys[10][6]; +uint16_t lacp_rx_count[10]; +uint8_t lacp_port_lag[10] = { + LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, + LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, +}; +uint16_t lacp_lag_ports[LACP_NUM_LAGS]; +uint8_t lacp_agg_sys[LACP_NUM_LAGS][6]; +uint8_t lacp_agg_valid[LACP_NUM_LAGS]; +uint16_t lacp_members_last[LACP_NUM_LAGS]; diff --git a/test/lacp/.gitignore b/test/lacp/.gitignore new file mode 100644 index 00000000..567609b1 --- /dev/null +++ b/test/lacp/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/test/lacp/Makefile b/test/lacp/Makefile new file mode 100644 index 00000000..856f21e5 --- /dev/null +++ b/test/lacp/Makefile @@ -0,0 +1,31 @@ +# Host-side sandbox for the firmware LACP state machines. +# +# Compiles the UNMODIFIED ../../rtl837x_lacp.c against shim headers: SDCC +# storage-class keywords are blanked via -D, and the module copy lives in +# build/ so quote-includes resolve to shim/ (its own directory has no real +# platform headers) instead of the firmware tree. rtl837x_trap.h is copied +# alongside it for the same reason: TRAP_RX_BODY() is what delivery by trap +# rests on, so the test must use the real macro, not a shim restating it. +CC ?= cc +CFLAGS = -std=c11 -Wall -Wextra -Wno-unknown-pragmas \ + -D__xdata= -D__code= -D__banked= -D__reentrant= \ + -Ishim -I../.. + +all: test + +test: build/lacp_sim + ./build/lacp_sim + +verbose: build/lacp_sim + ./build/lacp_sim -v + +build/lacp_sim: lacp_sim.c ../../rtl837x_lacp.c ../../rtl837x_lacp.h ../../rtl837x_trap.h shim/*.h + mkdir -p build + cp ../../rtl837x_lacp.c build/lacp_under_test.c + cp ../../rtl837x_trap.h build/rtl837x_trap.h + $(CC) $(CFLAGS) -o $@ lacp_sim.c build/lacp_under_test.c + +clean: + rm -rf build + +.PHONY: all test verbose clean diff --git a/test/lacp/lacp_sim.c b/test/lacp/lacp_sim.c new file mode 100644 index 00000000..a833ac9f --- /dev/null +++ b/test/lacp/lacp_sim.c @@ -0,0 +1,464 @@ +/* + * Host-side sandbox for the RTL837x LACP implementation. + * + * Compiles the UNMODIFIED rtl837x_lacp.c against shim headers (see shim/) and + * drives it with synthetic LACPDUs from a simulated partner switch/bond. This + * exercises the real state machines that ship in the firmware image - only + * the platform below them (frame I/O, trunk registers, console) is mocked. + * + * Run: make -C test/lacp (exit code 0 = all scenarios pass) + */ +#include +#include +#include +#include + +#include "rtl837x_common.h" +#include "machine.h" +#include "uip.h" +#include "rtl837x_regs.h" +#include "rtl837x_lacp.h" + +/* ---------- platform mocks ---------- */ + +uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2]; +uint16_t uip_len; +struct uip_eth_addr uip_ethaddr = { .addr = {0x02,0x11,0x22,0x33,0x44,0x55} }; +const struct machine machine = { .min_port = 0, .max_port = 7, + .log_to_phys_port = {5, 1, 2, 3, 4, 6, 7, 8, 9} }; +struct machine_runtime machine_detected = { .isRTL8373 = 1 }; +uint8_t lacpEnabled; +uint16_t management_vlan; /* mocked; slow-protocol path zeroes it per-frame */ + +static int verbose = 0; +void print_string(char *s) { if (verbose) fputs(s, stdout); } +void print_byte(uint8_t b) { if (verbose) printf("%02x", b); } +void print_short(uint16_t v) { if (verbose) printf("%04x", v); } +void write_char(char c) { if (verbose) putchar(c); } + +/* Trunk programming mock: record last mask per LAG + call count. + * hw_members mirrors LAG 0 so the single-aggregator scenarios read naturally. */ +static uint16_t hw_members_lag[4]; +static int hw_set_calls; +#define hw_members hw_members_lag[0] +void port_lag_members_set(uint8_t lag, uint16_t members) +{ + if (lag < 4) + hw_members_lag[lag] = members; + hw_set_calls++; +} + +/* Reserved-multicast action mock: delivery of a LACPDU to the CPU rests on + * the action of the slow-protocols entry alone, so record what the engine + * sets. The real trap_rma_set() touches only the action field. */ +static uint16_t hw_rma_reg; +static uint8_t hw_rma_act; +static int hw_rma_calls; +void trap_rma_set(uint16_t reg, uint8_t act); +void trap_rma_set(uint16_t reg, uint8_t act) +{ + hw_rma_reg = reg; + hw_rma_act = act; + hw_rma_calls++; +} + +/* ---------- on-wire mirrors (independent re-statement of the layout) ---------- */ + +struct wire_info { + uint16_t sys_prio; + uint8_t sys[6]; + uint16_t key; + uint16_t port_prio; + uint16_t port; + uint8_t state; + uint8_t reserved[3]; +}; + +struct wire_pdu_out { /* what lacp_send() writes at uip_buf+12 */ + uint8_t dst[6]; + uint8_t src[6]; + struct rtl_tag rtl_tag; + uint16_t ethertype; + uint8_t subtype; + uint8_t version; + uint8_t tlv_actor, actor_len; + struct wire_info actor; + uint8_t tlv_partner, partner_len; + struct wire_info partner; + uint8_t tlv_collector, collector_len; + uint16_t collector_max_delay; + uint8_t collector_reserved[12]; + uint8_t tlv_terminator, terminator_len; + uint8_t terminator_reserved[50]; +}; + +struct wire_pdu_in { /* forwarded form: the hardware inserts a VLAN tag */ + uint8_t dst[6]; + uint8_t src[6]; + struct rtl_tag rtl_tag; + struct vlan_tag vlan_tag; + uint16_t ethertype; + uint8_t subtype; + uint8_t version; + uint8_t tlv_actor, actor_len; + struct wire_info actor; + uint8_t tlv_partner, partner_len; + struct wire_info partner; +}; + +_Static_assert(sizeof(struct wire_info) == 18, "info TLV body must be 18"); +_Static_assert(sizeof(struct wire_pdu_out) == 132, "TX pdu must be 132"); +_Static_assert(sizeof(struct rtl_tag) == 8, "rtl tag must be 8"); + +/* ---------- TX capture ---------- */ + +static struct wire_pdu_out last_tx[10]; +static int tx_count[10]; + +void tcpip_output(void) +{ + struct wire_pdu_out *p = (struct wire_pdu_out *)&uip_buf[RTL_FRAME_DESC_SIZE]; + uint16_t pmask = HTONS(p->rtl_tag.pmask); + for (int port = 0; port < 10; port++) { + if (pmask & (1u << port)) { + last_tx[port] = *p; + tx_count[port]++; + } + } + uip_len = 0; +} + +/* ---------- simulated partner ---------- */ + +/* Deliver a LACPDU into the switch on `port`, from partner system `sys`. + * pstate = partner's Actor_State flags. If `echo`, the partner echoes the + * switch's last-seen actor block back (fresh view) - required for quiescence. */ +static void partner_frame_full(uint8_t port, const uint8_t sys[6], uint8_t pstate, + int echo, uint16_t len, int tagged) +{ + struct wire_pdu_in in; + memset(&in, 0, sizeof(in)); + + in.dst[0]=0x01; in.dst[1]=0x80; in.dst[2]=0xc2; in.dst[5]=0x02; + memcpy(in.src, sys, 6); + in.rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID); + in.rtl_tag.pmask = HTONS(port); /* RX: 4-bit ingress port */ + in.vlan_tag.svlan = HTONS(0x8100); /* the tag's TPID, which marks the body offset */ + in.ethertype = HTONS(0x8809); + in.subtype = 0x01; + in.version = 0x01; + + in.tlv_actor = 0x01; in.actor_len = 0x14; + in.actor.sys_prio = HTONS(0x8000); + memcpy(in.actor.sys, sys, 6); + in.actor.key = HTONS(0x0011); + in.actor.port_prio = HTONS(0x00ff); + in.actor.port = HTONS((uint16_t)port + 101); + in.actor.state = pstate; + + in.tlv_partner = 0x02; in.partner_len = 0x14; + if (echo) { /* fresh view of us, from our last TX */ + in.partner.sys_prio = last_tx[port].actor.sys_prio; + memcpy(in.partner.sys, last_tx[port].actor.sys, 6); + in.partner.key = last_tx[port].actor.key; + in.partner.port = last_tx[port].actor.port; + in.partner.state = last_tx[port].actor.state; + } /* else: zeros = stale view */ + + memcpy(uip_buf, &in, sizeof(in)); + if (!tagged) { /* trapped: no VLAN tag, so the body sits earlier */ + uint8_t *body = uip_buf + RTL_FRAME_DESC_SIZE + RTL_TAG_SIZE; + memmove(body, body + VLAN_TAG_SIZE, + sizeof(in) - (RTL_FRAME_DESC_SIZE + RTL_TAG_SIZE + VLAN_TAG_SIZE)); + len -= VLAN_TAG_SIZE; + } + uip_len = len; + lacp_in(); +} + +static void partner_frame_len(uint8_t port, const uint8_t sys[6], uint8_t pstate, int echo, uint16_t len) +{ + partner_frame_full(port, sys, pstate, echo, len, 1); +} + +static void partner_frame(uint8_t port, const uint8_t sys[6], uint8_t pstate, int echo) +{ + partner_frame_full(port, sys, pstate, echo, sizeof(struct wire_pdu_in), 1); +} + +/* The same LACPDU as the reserved-multicast trap delivers it. */ +static void partner_frame_trap(uint8_t port, const uint8_t sys[6], uint8_t pstate, int echo) +{ + partner_frame_full(port, sys, pstate, echo, sizeof(struct wire_pdu_in), 0); +} + +/* Advance time by n system ticks; the LACP timers step every fourth one */ +volatile uint32_t ticks; +static void step(int n) { while (n--) { ticks++; lacp_timers(); } } + +/* ---------- scenario runner ---------- */ + +static int failures; +#define CHECK(cond, name) do { \ + if (cond) printf("PASS %s\n", name); \ + else { printf("FAIL %s\n", name); failures++; } \ +} while (0) + +#define P_ACT LACP_STATE_ACTIVITY +#define P_AGG LACP_STATE_AGGREGATION +#define P_SYNC LACP_STATE_SYNC +#define P_COL LACP_STATE_COLLECTING +#define P_DIST LACP_STATE_DISTRIBUTING +#define P_TO LACP_STATE_TIMEOUT /* partner requests short (fast) timeout */ + +static const uint8_t SYS_A[6] = {0x02,0xaa,0xaa,0xaa,0xaa,0x01}; +static const uint8_t SYS_B[6] = {0x02,0xbb,0xbb,0xbb,0xbb,0x02}; + +int main(int argc, char **argv) +{ + verbose = (argc > 1 && !strcmp(argv[1], "-v")); + + /* Boot init first: on host, zeroed globals would otherwise read as "every + * port belongs to LAG 0" (the firmware calls this from main() the same way) */ + lacp_init(); + + /* T1: enable announces on every port with sane field contents */ + lacp_lag_set(0, 0x00ff); + step(8); + int all_tx = 1, sane = 1; + for (int i = machine.min_port; i <= machine.max_port; i++) { + if (!tx_count[i]) all_tx = 0; + if (last_tx[i].ethertype != HTONS(0x8809) || last_tx[i].subtype != 1 + || last_tx[i].dst[5] != 0x02 + || !(last_tx[i].actor.state & (P_ACT|P_AGG|P_TO))) + sane = 0; + } + CHECK(all_tx && sane, "T1 enable: LACPDU announced on all ports, well-formed"); + + /* T2: partner without SYNC -> actor SYNC only, no members yet */ + partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO, 1); + partner_frame(1, SYS_A, P_ACT|P_AGG|P_TO, 1); + step(8); + CHECK((lacp_actor_state[0] & P_SYNC) && !(lacp_actor_state[0] & (P_COL|P_DIST)) + && hw_members == 0, + "T2 partner not in sync: actor SYNC only, trunk empty"); + + /* T3: partner in sync on ports 0,1 -> full converge, members 0x0003 */ + partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + partner_frame(1, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(8); + CHECK(hw_members == 0x0003 + && (lacp_actor_state[0] & (P_SYNC|P_COL|P_DIST)) == (P_SYNC|P_COL|P_DIST) + && (lacp_actor_state[1] & (P_SYNC|P_COL|P_DIST)) == (P_SYNC|P_COL|P_DIST), + "T3 convergence: both ports collecting/distributing, trunk 0x0003"); + + /* T3b: partner-block echo must be VERBATIM. A Linux 802.3ad partner accepts + * our SYNC only if our Partner TLV mirrors its actor identity exactly; a + * hardcoded sys_prio/port_prio (instead of the recorded value) makes Linux + * clear partner-SYNC and the bond never distributes. Verify we echo the + * partner's own priorities back, not a constant. */ + CHECK(HTONS(last_tx[0].partner.sys_prio) == 0x8000 + && HTONS(last_tx[0].partner.port_prio) == 0x00ff + && HTONS(last_tx[0].partner.key) == 0x0011 + && HTONS(last_tx[0].partner.port) == (uint16_t)0 + 101, + "T3b partner echo: recorded partner priorities/key/port echoed verbatim"); + + /* T4: mis-cabling - port 2 sees a DIFFERENT system: must stay out */ + partner_frame(2, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(8); + CHECK(hw_members == 0x0003 && !(lacp_actor_state[2] & P_SYNC), + "T4 mis-cabling: port with different partner system stays out"); + + /* T5: register-write economy: stable state must not rewrite the trunk */ + int calls_before = hw_set_calls; + partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + partner_frame(1, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(64); + CHECK(hw_set_calls == calls_before, + "T5 stable state: no redundant trunk register writes"); + + /* T6: stale echo triggers retransmit (update_NTT) */ + int txc = tx_count[0]; + partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 0 /* stale view of us */); + step(8); + CHECK(tx_count[0] > txc, "T6 update_NTT: stale partner view causes retransmit"); + + /* T7: expiry - partner silent past the short timeout: trunk drains, + * aggregator identity is released */ + step(4 * LACP_SHORT_TIMEOUT + 64); /* past the short timeout */ + CHECK(hw_members == 0 && lacp_agg_valid[0] == 0, + "T7 expiry: members drop to 0 and aggregator is released"); + + /* T8: re-convergence with a NEW partner system after release */ + /* (needs a fresh announce first so the echo below carries current state) */ + step(4 * 0x0100 + 8); /* let periodic TX refresh last_tx */ + partner_frame(3, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1); + partner_frame(3, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(8); + CHECK(hw_members == 0x0008 && (lacp_actor_state[3] & P_COL), + "T8 re-election: new partner system forms a fresh aggregate"); + + /* T9: lacp off clears the trunk */ + lacp_off(); + CHECK(hw_members == 0 && lacpEnabled == 0, "T9 disable: trunk cleared"); + + /* ---- per-LAG scenarios: two independent aggregators ---- */ + + /* T10: lag 1 lacp {0,1} + lag 2 lacp {2,3}, both to the SAME partner + * system (worst case for sibling scoping): each converges into its own + * hardware trunk, and the engine reports enabled. */ + lacp_lag_set(1, 0x0003); /* ports 0,1 */ + lacp_lag_set(2, 0x000c); /* ports 2,3 */ + step(8); /* announce */ + for (int r = 0; r < 2; r++) { /* two rounds so echoes carry fresh state */ + for (int p = 0; p < 4; p++) + partner_frame(p, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(8); + } + CHECK(lacpEnabled == 1 + && hw_members_lag[1] == 0x0003 && hw_members_lag[2] == 0x000c, + "T10 two LAGs: independent aggregates converge on their own trunks"); + + /* T11: removing one LAG releases only its trunk; the other keeps running */ + lacp_lag_set(1, 0); + CHECK(hw_members_lag[1] == 0 && hw_members_lag[2] == 0x000c + && lacpEnabled == 1, + "T11 remove one LAG: its trunk drains, the other LAG unaffected"); + + /* T12: a port in no LACP LAG ignores LACPDUs entirely */ + int rx5_before = tx_count[5]; + partner_frame(5, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(8); + CHECK(!(lacp_actor_state[5] & P_SYNC) && hw_members_lag[2] == 0x000c, + "T12 unassigned port: LACPDU ignored, no state change"); + (void)rx5_before; + + /* T13: removing the last LACP LAG shuts the engine down */ + lacp_lag_set(2, 0); + CHECK(lacpEnabled == 0 && hw_members_lag[2] == 0 + && hw_rma_reg == RTL837X_RMA2_CONF && hw_rma_act == RTL837X_RMA_ACT_DROP, + "T13 last LAG removed: engine off, trunk drained, address dropped"); + + /* T14: while the engine runs, the slow-protocols address is trapped to + * the CPU. The trap keys on the address alone, so delivery needs no L2 + * entry and nothing written per VLAN or per PVID. */ + hw_rma_calls = 0; + lacp_lag_set(0, 0x0003); + CHECK(hw_rma_calls == 1 && hw_rma_reg == RTL837X_RMA2_CONF + && hw_rma_act == RTL837X_RMA_ACT_TRAP, + "T14 engine on traps 01:80:c2:00:00:02 to the CPU"); + + /* T15: a trapped LACPDU arrives without the VLAN tag a forwarded one + * carries, so its body sits four bytes earlier. Both forms must parse. */ + uint16_t rx_before = lacp_rx_count[0]; + lacp_partner_state[0] = 0; /* all three are written past the ethertype gate */ + memset(lacp_partner_sys[0], 0, 6); + partner_frame_trap(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + int trap_ok = lacp_rx_count[0] == (uint16_t)(rx_before + 1) + && lacp_partner_state[0] == (P_ACT|P_AGG|P_TO|P_SYNC) + && !memcmp(lacp_partner_sys[0], SYS_A, 6); + partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + CHECK(trap_ok && lacp_rx_count[0] == (uint16_t)(rx_before + 2), + "T15 trapped LACPDU parses like a forwarded one"); + + /* T16: the partner changes one cable at a time. Port 1 starts hearing B + * while port 0 still hears A, then A goes quiet and B appears on port 0 + * too. The aggregator must let go of A once no port hears it, even though + * port 1 was CURRENT the whole time, or the LAG never aggregates with B. */ + lacp_lag_set(0, 0); + lacp_lag_set(0, 0x0003); + step(8); + for (int r = 0; r < 3; r++) { + partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + partner_frame(1, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(8); + } + for (int t = 0; t < 40; t++) { /* port 1 moved to B, port 0 still on A */ + partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + partner_frame(1, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(4 * 40); + } + int stayed_out = hw_members == 0x0001 && !(lacp_actor_state[1] & P_SYNC); + for (int t = 0; t < 60; t++) /* port 0 unplugged from A, past the short timeout */ + { partner_frame(1, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1); step(4 * 40); } + for (int t = 0; t < 40; t++) { /* port 0 now on B as well */ + partner_frame(0, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1); + partner_frame(1, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(4 * 40); + } + CHECK(stayed_out && hw_members == 0x0003 && !memcmp(lacp_agg_sys[0], SYS_B, 6), + "T16 re-cabling one port at a time: aggregator moves from A to B"); + + /* T17: a group that already has a STATIC trunk is put under LACP with the + * same ports (the LAG page's Static -> LACP switch sends only "lag N lacp"). + * The static trunk must go: until the partner converges the ports are not + * an aggregate, and after a reboot the config replays only the LACP line. */ + hw_members_lag[1] = 0x0030; /* static lag 2: ports 4,5 */ + lacp_lag_set(1, 0x0030); + step(8); + CHECK(hw_members_lag[1] == 0 && lacp_members_last[1] == 0 && lacp_lag_ports[1] == 0x0030, + "T17 static trunk taken over: cleared while the group negotiates"); + lacp_lag_set(1, 0); + + /* T18: a port named in a second group moves there. The first group's + * candidate set drops it, so taking the second group out later leaves no + * orphan that group 1 still shows as a candidate but never runs LACP on. */ + lacp_lag_set(1, 0x0030); /* ports 4,5 */ + step(8); + for (int r = 0; r < 2; r++) { + partner_frame(4, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + partner_frame(5, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(8); + } + int converged = hw_members_lag[1] == 0x0030; + lacp_lag_set(2, 0x0060); /* ports 5,6: port 5 moves from group 2 to 3 */ + int moved = lacp_lag_ports[1] == 0x0010 && lacp_port_lag[5] == 2 + && hw_members_lag[1] == 0x0010; + lacp_lag_set(2, 0); + int no_orphan = lacp_port_lag[5] == LACP_LAG_NONE && lacp_lag_ports[1] == 0x0010; + lacp_lag_set(3, 0x0010); /* the last port leaves group 2 as well */ + CHECK(converged && moved && no_orphan && lacp_lag_ports[1] == 0 && lacp_agg_valid[1] == 0, + "T18 port moves between groups: old group drops it from candidates and trunk"); + lacp_lag_set(3, 0); + + /* T20: taking a group out of LACP leaves nothing of the partner behind + * for "lacp show" to report as if the port still heard it. */ + lacp_lag_set(1, 0x0030); + step(8); + partner_frame(4, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(8); + int had_partner = lacp_rx_state[4] == LACP_RX_CURRENT && lacp_rx_count[4] > 0; + lacp_lag_set(1, 0); + CHECK(had_partner && lacp_partner_state[4] == 0 && lacp_rx_state[4] == LACP_RX_INITIALIZE + && lacp_rx_count[4] == 0 && !memcmp(lacp_partner_sys[4], "\0\0\0\0\0\0", 6), + "T20 release: partner state, rx state, counter and system cleared"); + + /* T21: applying the same candidate set again (the LAG page resends it + * with every Apply) must not tear the trunk down and renegotiate. */ + lacp_lag_set(1, 0x0030); + step(8); + for (int r = 0; r < 2; r++) { + partner_frame(4, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + partner_frame(5, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); + step(8); + } + int before = hw_set_calls; + lacp_lag_set(1, 0x0030); + CHECK(hw_members_lag[1] == 0x0030 && hw_set_calls == before + && (lacp_actor_state[4] & P_COL), + "T21 same ports applied again: trunk and state untouched"); + lacp_lag_set(1, 0); + + /* T22: a runt on the slow-protocols address must not be parsed as a + * LACPDU; the partner block would be read from stale buffer bytes. */ + lacp_lag_set(1, 0x0030); + step(8); + partner_frame_len(4, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1, 40); /* cut before the partner block */ + CHECK(lacp_rx_count[4] == 0 && lacp_rx_state[4] == LACP_RX_INITIALIZE, + "T22 truncated LACPDU ignored: the port does not record it"); + lacp_lag_set(1, 0); + + printf("\n%s (%d failure%s)\n", failures ? "SANDBOX: FAILURES" : "SANDBOX: ALL PASS", + failures, failures == 1 ? "" : "s"); + return failures ? 1 : 0; +} diff --git a/test/lacp/shim/machine.h b/test/lacp/shim/machine.h new file mode 100644 index 00000000..7f6612fb --- /dev/null +++ b/test/lacp/shim/machine.h @@ -0,0 +1,12 @@ +/* Host-build shim: rtl837x_lacp.c reads min_port/max_port and, for the + * console dump, the front-panel numbering. The real map is not the identity + * on any machine, so the harness uses a shuffled one to keep a logical index + * from passing as a physical port by accident. */ +#ifndef _SHIM_MACHINE_H_ +#define _SHIM_MACHINE_H_ +#include +struct machine { uint8_t min_port; uint8_t max_port; uint8_t log_to_phys_port[9]; }; +extern const struct machine machine; +struct machine_runtime { uint8_t isRTL8373; }; +extern struct machine_runtime machine_detected; +#endif diff --git a/test/lacp/shim/rtl837x_common.h b/test/lacp/shim/rtl837x_common.h new file mode 100644 index 00000000..b21c7141 --- /dev/null +++ b/test/lacp/shim/rtl837x_common.h @@ -0,0 +1,63 @@ +/* + * Host-build shim for rtl837x_common.h - just enough for rtl837x_lacp.c. + * Layouts of rtl_tag/vlan_tag and the frame-desc size mirror the real header; + * the harness static-asserts the resulting on-wire struct sizes. + */ +#ifndef _SHIM_RTL837X_COMMON_H_ +#define _SHIM_RTL837X_COMMON_H_ + +#include +#include /* memcpy/memset - firmware provides its own */ + +struct rtl_tag { + uint16_t tag; + uint8_t version; + uint8_t reason; + uint16_t flags; + uint16_t pmask; /* TX: port bitmask; RX: 4-bit port number */ +}; + +struct vlan_tag { + uint16_t svlan; + uint16_t vlan; +}; + +#define RTL_TAG_SIZE (sizeof (struct rtl_tag)) +#define VLAN_TAG_SIZE (sizeof (struct vlan_tag)) +#define RTL_FRAME_TAG_ID 0x8899 +#define RTL_FRAME_TAG_VERSION 0x04 +#define RTL_FRAME_DESC_SIZE 12 +#define ETHER_HEADER_SIZE 14 + +#define HTONS(n) ((uint16_t)((((uint16_t)(n)) << 8) | (((uint16_t)(n)) >> 8))) + +/* Console output - provided by the harness */ +void print_string(char *s); +void print_byte(uint8_t b); +void print_short(uint16_t v); +void write_char(char c); + +#endif + +/* Host-build shim: port masks used by lacp_isolation_update() */ +#ifndef PMASK_CPU +#define CPU_PORT 9 +#define PMASK_9 0x1ff +#define PMASK_6 0x1f8 +#define PMASK_CPU 0x200 +#endif + +/* Host-build shim: directed-egress/strip-tag bit (bit15 of TX pmask word) */ +#ifndef RTL_TAG_ALLOW +#define RTL_TAG_ALLOW 0x8000 +#endif + +/* Host-build shim: tag flags word bits (tag_rtl8_4) */ +#ifndef RTL_TAG_LEARN_DIS +#define RTL_TAG_LEARN_DIS 0x0020 +#define RTL_TAG_KEEP 0x0080 +#define SYS_TICK_HZ 200 +extern volatile uint32_t ticks; +#define TICKS_DUE(last, period) ((uint8_t)((uint8_t)ticks - (last)) >= (period)) + +#endif diff --git a/test/lacp/shim/rtl837x_port.h b/test/lacp/shim/rtl837x_port.h new file mode 100644 index 00000000..646d5253 --- /dev/null +++ b/test/lacp/shim/rtl837x_port.h @@ -0,0 +1,7 @@ +/* Host-build shim: the harness mocks trunk programming and records calls */ +#ifndef _SHIM_RTL837X_PORT_H_ +#define _SHIM_RTL837X_PORT_H_ +#include +void port_lag_members_set(uint8_t lag, uint16_t members); +void port_isolate(uint8_t port, uint16_t pmask); +#endif diff --git a/test/lacp/shim/rtl837x_regs.h b/test/lacp/shim/rtl837x_regs.h new file mode 100644 index 00000000..2dc92d8d --- /dev/null +++ b/test/lacp/shim/rtl837x_regs.h @@ -0,0 +1,19 @@ +/* + * Host-build shim for rtl837x_regs.h - the register numbers rtl837x_lacp.c + * touches, with the values from the real header so a wrong constant shows up + * as a failing expectation rather than as a silent no-op. + */ +#ifndef _SHIM_RTL837X_REGS_H_ +#define _SHIM_RTL837X_REGS_H_ + +#include + +#define RTL837X_RMA2_CONF 0x4ed4 +#define RTL837X_RMA_ACT_TRAP 0x10 +#define RTL837X_RMA_ACT_DROP 0x20 + +/* Registers touched by lacp_send's CPU-port targeting */ +#define RTL837X_PORT_ISOLATION_BASE 0x50c0 +#define RTL837X_REG_NIC_TXCMD 0x7850 + +#endif diff --git a/test/lacp/shim/uip.h b/test/lacp/shim/uip.h new file mode 100644 index 00000000..0d5a51ec --- /dev/null +++ b/test/lacp/shim/uip.h @@ -0,0 +1,9 @@ +/* Host-build shim for uip.h - frame buffer + TX hook used by rtl837x_lacp.c */ +#ifndef _SHIM_UIP_H_ +#define _SHIM_UIP_H_ +#include +#define UIP_CONF_BUFFER_SIZE 600 +struct uip_eth_addr { uint8_t addr[6]; }; +extern uint16_t uip_len; +void tcpip_output(void); +#endif diff --git a/test/port_hash/.gitignore b/test/port_hash/.gitignore new file mode 100644 index 00000000..cf8f788c --- /dev/null +++ b/test/port_hash/.gitignore @@ -0,0 +1,2 @@ +test_lag_hash +port_lag_members_set.inc diff --git a/test/port_hash/Makefile b/test/port_hash/Makefile new file mode 100644 index 00000000..feeee940 --- /dev/null +++ b/test/port_hash/Makefile @@ -0,0 +1,6 @@ +test: port_lag_members_set.inc + gcc -Wall -Wextra -O1 -o test_lag_hash test_lag_hash.c && ./test_lag_hash +port_lag_members_set.inc: ../../rtl837x_port.c + awk '/^void port_lag_members_set/,/^}/' $< > $@ +clean: + rm -f test_lag_hash port_lag_members_set.inc diff --git a/test/port_hash/test_lag_hash.c b/test/port_hash/test_lag_hash.c new file mode 100644 index 00000000..205d3fc8 --- /dev/null +++ b/test/port_hash/test_lag_hash.c @@ -0,0 +1,68 @@ +/* Host test for port_lag_members_set(): the default load-balancing hash must be + * seeded into the CURRENT trunk's register (BASE + lag*4), never trunk 0's, and + * must not overwrite a hash that is already set. Compiles the REAL function + * (extracted verbatim from rtl837x_port.c) against register-recording shims. */ +#include +#include +#include + +#define __banked +#define __xdata +#define __code + +/* register defs (mirror rtl837x_regs.h) */ +#define RTL837X_TRK_MBR_CTRL_BASE 0x4f38 +#define RTL837X_TRK_HASH_CTRL_BASE 0x4f48 +#define LAG_HASH_SOURCE_PORT_NUMBER 0x01 +#define LAG_HASH_L2_SMAC 0x02 +#define LAG_HASH_L2_DMAC 0x04 +#define LAG_HASH_L3_SIP 0x08 +#define LAG_HASH_L3_DIP 0x10 +#define LAG_HASH_L4_SPORT 0x20 +#define LAG_HASH_L4_DPORT 0x40 +#define LAG_HASH_DEFAULT (LAG_HASH_L2_SMAC|LAG_HASH_L2_DMAC|LAG_HASH_L3_SIP|LAG_HASH_L3_DIP|LAG_HASH_L4_SPORT|LAG_HASH_L4_DPORT) +#define LAG_HASH_RESET (LAG_HASH_SOURCE_PORT_NUMBER|LAG_HASH_L2_SMAC|LAG_HASH_L2_DMAC|LAG_HASH_L3_SIP|LAG_HASH_L3_DIP|LAG_HASH_L4_SPORT) + +uint8_t SFR_DATA_24, SFR_DATA_16, SFR_DATA_8, SFR_DATA_0; +uint8_t sfr_data[4]; +static uint32_t reg_store[0x10000]; + +void reg_read_m(uint16_t r){ uint32_t v=reg_store[r]; + sfr_data[0]=(v>>24)&0xff; sfr_data[1]=(v>>16)&0xff; sfr_data[2]=(v>>8)&0xff; sfr_data[3]=v&0xff; } +void reg_write(uint16_t r){ reg_store[r]=((uint32_t)SFR_DATA_24<<24)|((uint32_t)SFR_DATA_16<<16)|((uint32_t)SFR_DATA_8<<8)|SFR_DATA_0; } +void print_string(char*s){(void)s;} void print_byte(uint8_t b){(void)b;} +void print_short(uint16_t s){(void)s;} void write_char(char c){(void)c;} + +#define REG_SET(r,v) do{ SFR_DATA_24=((uint32_t)(v)>>24)&0xff; SFR_DATA_16=((uint32_t)(v)>>16)&0xff; \ + SFR_DATA_8=((uint32_t)(v)>>8)&0xff; SFR_DATA_0=(v)&0xff; reg_write(r); }while(0) +#define REG_WRITE(r,a,b,c,d) do{ SFR_DATA_24=(a); SFR_DATA_16=(b); SFR_DATA_8=(c); SFR_DATA_0=(d); reg_write(r); }while(0) + +/* THE REAL FUNCTION (extracted from rtl837x_port.c at build time) */ +#include "port_lag_members_set.inc" + +static int fails; +#define CK(cond,msg,...) do{ if(cond){printf("PASS " msg "\n",##__VA_ARGS__);} else {printf("FAIL " msg "\n",##__VA_ARGS__); fails++;} }while(0) + +int main(void){ + for(int lag=0; lag<4; lag++){ + memset(reg_store,0,sizeof(reg_store)); + port_lag_members_set(lag, 0x0180); + uint16_t h = RTL837X_TRK_HASH_CTRL_BASE + (lag<<2); + uint16_t m = RTL837X_TRK_MBR_CTRL_BASE + (lag<<2); + CK(reg_store[h]==LAG_HASH_DEFAULT, "lag%d: default 0x%02x -> hash reg 0x%04x", lag, LAG_HASH_DEFAULT, h); + CK(reg_store[m]==0x0180, "lag%d: members 0x0180 -> mbr reg 0x%04x", lag, m); + if(lag) CK(reg_store[RTL837X_TRK_HASH_CTRL_BASE]==0, "lag%d: trunk-0 hash NOT clobbered", lag); + } + /* guard: existing hash must survive a later member change */ + memset(reg_store,0,sizeof(reg_store)); + reg_store[RTL837X_TRK_HASH_CTRL_BASE+(1<<2)] = 0x06; /* lag1 already L2 */ + port_lag_members_set(1, 0x00c0); + CK(reg_store[RTL837X_TRK_HASH_CTRL_BASE+(1<<2)]==0x06, "guard: preset lag1 hash 0x06 preserved"); + /* the chip's reset value counts as unset and is replaced by the default */ + memset(reg_store,0,sizeof(reg_store)); + reg_store[RTL837X_TRK_HASH_CTRL_BASE+(2<<2)] = LAG_HASH_RESET; + port_lag_members_set(2, 0x0003); + CK(reg_store[RTL837X_TRK_HASH_CTRL_BASE+(2<<2)]==LAG_HASH_DEFAULT, "reset value 0x%02x replaced by default on lag2", LAG_HASH_RESET); + printf(fails? "\n=== FAIL (%d) ===\n" : "\n=== ALL PASS ===\n", fails); + return fails?1:0; +} diff --git a/test/test_httpd_tx.c b/test/test_httpd_tx.c index 4df05472..3f056030 100644 --- a/test/test_httpd_tx.c +++ b/test/test_httpd_tx.c @@ -148,6 +148,7 @@ void send_mirror(void) { } void send_mtu(void) { } void send_config(void) { } void send_cmd_log(void) { } +void send_lacp(void) { } void send_lag(void) { } void send_stp(void) { } void send_stp_counters(void) { } From bd974e0aaf3db9bc2df70ddd5a91f83cae193f41 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 16:27:19 +0200 Subject: [PATCH 38/84] lacp: arm the partner timeout from our own LACP_Timeout The receive machine armed current_while from the partner's TIMEOUT bit, so against a partner on the slow rate a port whose LACPDUs stopped with the link still up stayed in the trunk for the long timeout, two minutes. 802.3ad 43.4.12 starts the timer from the actor's own LACP_Timeout, and we always advertise the short one: the partner sends every second and is let go after the short timeout. Sandbox T23 plays a slow-rate partner that goes quiet and fails on the code before this. --- rtl837x_lacp.c | 3 +-- test/lacp/lacp_sim.c | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/rtl837x_lacp.c b/rtl837x_lacp.c index b4874f16..4f0fa44d 100644 --- a/rtl837x_lacp.c +++ b/rtl837x_lacp.c @@ -311,8 +311,7 @@ void lacp_in(void) __banked /* Receive machine -> CURRENT, (re)arm partner timeout (43.4.12) */ lacp_rx_state[port] = LACP_RX_CURRENT; - lacp_timeout[port] = (LACP_I->actor.state & LACP_STATE_TIMEOUT) - ? LACP_SHORT_TIMEOUT : LACP_LONG_TIMEOUT; + lacp_timeout[port] = LACP_SHORT_TIMEOUT; /* Elect this LAG's aggregator partner system on first contact (43.4.14) */ if (!lacp_agg_valid[lag]) { diff --git a/test/lacp/lacp_sim.c b/test/lacp/lacp_sim.c index a833ac9f..7efa67c6 100644 --- a/test/lacp/lacp_sim.c +++ b/test/lacp/lacp_sim.c @@ -458,6 +458,23 @@ int main(int argc, char **argv) "T22 truncated LACPDU ignored: the port does not record it"); lacp_lag_set(1, 0); + /* T23: the partner asks for the long timeout (Linux's default slow rate). + * current_while is armed from OUR LACP_Timeout, which is short, so a + * partner that stops speaking LACP with the link up is dropped after the + * short timeout, not after two minutes. */ + lacp_lag_set(1, 0x0030); + step(8); + for (int r = 0; r < 2; r++) { + partner_frame(4, SYS_A, P_ACT|P_AGG|P_SYNC, 1); + partner_frame(5, SYS_A, P_ACT|P_AGG|P_SYNC, 1); + step(8); + } + int slow_converged = hw_members_lag[1] == 0x0030; + step(4 * LACP_SHORT_TIMEOUT + 64); + CHECK(slow_converged && hw_members_lag[1] == 0, + "T23 partner on slow rate goes quiet: dropped after our short timeout"); + lacp_lag_set(1, 0); + printf("\n%s (%d failure%s)\n", failures ? "SANDBOX: FAILURES" : "SANDBOX: ALL PASS", failures, failures == 1 ? "" : "s"); return failures ? 1 : 0; From 0d748fa8086e897b0a0168b0ebd26359d74beb23 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 16:27:20 +0200 Subject: [PATCH 39/84] lacp: "lag d" and a static member list take the group out of LACP Both paths wrote the trunk registers directly and left the protocol running on the group, so "lag show" said disabled while the partner kept both links in one aggregate, and re-sending the LACP line did not repair it because the same-ports shortcut returns early. Each path now releases the group from LACP first; for a group that never ran LACP that is a no-op. --- cmd_parser.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd_parser.c b/cmd_parser.c index b585a835..3a1da620 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -535,6 +535,7 @@ void parse_lag(void) goto err; if (cmd_compare(2, "d")) { + lacp_lag_set(group, 0); port_lag_members_set(group, 0); return; } @@ -558,10 +559,12 @@ void parse_lag(void) members |= ((uint16_t)1) << atoi_results_u8; } - if (lacp_mode) + if (lacp_mode) { lacp_lag_set(group, members); /* 0 ports also valid: removes the LAG */ - else + } else { + lacp_lag_set(group, 0); port_lag_members_set(group, members); + } return; err: cmd_error("lag (show | <1-4> (d | [lacp] ...))\n"); From 913e9780b86bdd5a9939ec4a62016a0562bd5f2b Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 16:27:21 +0200 Subject: [PATCH 40/84] httpd: leave the L2 listing as it is on main Listing by next-address also returned the reserved-multicast entries STP writes, shown as port 1 with a delete button, while l2_delete() still looks up by next-unicast and so removed the unicast entry after that index instead. The change was there to show the LACP steering entries; delivery by trap writes none, so it goes. --- httpd/page_impl.c | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/httpd/page_impl.c b/httpd/page_impl.c index 44c9ddcb..94ccb483 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -409,19 +409,14 @@ void send_l2(uint16_t idx) */ __xdata uint16_t entry = idx & 0xfff; __xdata uint16_t first_entry = 0xffff; // An illegal entry index - __xdata uint16_t mbr; __bit first = true; char_to_html('['); while (1) { entries_left--; uint8_t port = 0; uint8_t lag; - uint8_t mc = 0; reg_read_m(RTL837x_TBL_DATA_0); - // Iterate with "next address" (method 2) so static multicast entries - - // an IGMP group entry, say - are listed too, not only learned or - // static unicast ones. - REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1] & 0xfc, sfr_data[2] | (TBL_LUTREAD_NEXT_ADDRESS << 6), sfr_data[3]); + REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1] & 0xfc, sfr_data[2] | (TBL_LUTREAD_NEXT_L2UC << 6), sfr_data[3]); REG_WRITE(RTL837X_TBL_CTRL, entry >> 8, entry, TBL_L2_UNICAST, TBL_EXECUTE); do { @@ -429,7 +424,7 @@ void send_l2(uint16_t idx) } while (sfr_data[3] & TBL_EXECUTE); reg_read_m(RTL837x_L2_DATA_OUT_B); - __bit valid = (sfr_data[0] & 0x20) && !(sfr_data[0] & 0x10); + __bit valid = (sfr_data[0] & 0x20) != 0; if (valid) { /* separator + 74-byte worst-case entry + closing "]" */ if (slen + 76 > TCP_OUTBUF_SIZE) @@ -438,8 +433,6 @@ void send_l2(uint16_t idx) char_to_html(','); first = false; - mc = sfr_data[2] & 0x01; // multicast MAC: member mask, no SPA/age - // VLAN, taken from the read above instead of reading the register twice slen += strtox(outbuf + slen, "{\"vlan\":\""); charhex_to_html(sfr_data[0] & 0x0f); @@ -458,23 +451,12 @@ void send_l2(uint16_t idx) // type reg_read_m(RTL837x_L2_DATA_OUT_C); - if (mc || (sfr_data[1] & 0x1)) + if (sfr_data[1] & 0x1) slen += strtox(outbuf + slen, "\",\"type\":\"s\",\"port\":"); else slen += strtox(outbuf + slen, "\",\"type\":\"l\",\"port\":"); - if (mc) { - // Report the lowest member of the mask (an entry holding only - // the CPU port is rendered as "CPU" by the UI) - mbr = port | ((uint16_t)sfr_data[3] << 2); - port = 0; - while (mbr && !(mbr & 1)) { - mbr >>= 1; - port++; - } - } else { - port |= (sfr_data[3] & 0x3) << 2; - } + port |= (sfr_data[3] & 0x3) << 2; itoa_html(port); slen += strtox(outbuf + slen, ",\"lag\":"); lag = port_lag_of(port); From 0f63244ca1f598cdaf5f6d66e8a24b13c4e963d8 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 16:27:22 +0200 Subject: [PATCH 41/84] lacp: ignore a LACPDU that carries our own system id Two ports of one group cabled to each other heard themselves and the group aggregated with itself. Sandbox T24. --- rtl837x_lacp.c | 2 ++ test/lacp/lacp_sim.c | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/rtl837x_lacp.c b/rtl837x_lacp.c index 4f0fa44d..e2ede9e3 100644 --- a/rtl837x_lacp.c +++ b/rtl837x_lacp.c @@ -292,6 +292,8 @@ void lacp_in(void) __banked uint8_t lag = lacp_port_lag[port]; if (lag == LACP_LAG_NONE) return; + if (lacp_sys_eq(LACP_I->actor.sys, uip_ethaddr.addr)) + return; #ifdef DEBUG diff --git a/test/lacp/lacp_sim.c b/test/lacp/lacp_sim.c index 7efa67c6..13d571cd 100644 --- a/test/lacp/lacp_sim.c +++ b/test/lacp/lacp_sim.c @@ -475,6 +475,15 @@ int main(int argc, char **argv) "T23 partner on slow rate goes quiet: dropped after our short timeout"); lacp_lag_set(1, 0); + /* T24: a LACPDU carrying our own system id (two ports of one group + * cabled to each other) must not be taken for a partner. */ + lacp_lag_set(1, 0x0030); + step(8); + partner_frame(4, uip_ethaddr.addr, P_ACT|P_AGG|P_TO|P_SYNC, 1); + CHECK(lacp_rx_count[4] == 0 && lacp_rx_state[4] == LACP_RX_INITIALIZE, + "T24 own system id: LACPDU ignored"); + lacp_lag_set(1, 0); + printf("\n%s (%d failure%s)\n", failures ? "SANDBOX: FAILURES" : "SANDBOX: ALL PASS", failures, failures == 1 ? "" : "s"); return failures ? 1 : 0; From fcaacd442952094b882325b96ab5058164d238ef Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 16:27:22 +0200 Subject: [PATCH 42/84] lacp: review follow-ups lacp_in() always leaves uip_len at 0, so the tcpip_output() after it never ran. The LAG page names the active members by front-panel port instead of a logical mask. The partner key is not part of selection; say so under Known limits. --- doc/lacp.md | 2 ++ html/app.js | 3 ++- rtlplayground.c | 2 -- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/lacp.md b/doc/lacp.md index 1b78459e..3160469a 100644 --- a/doc/lacp.md +++ b/doc/lacp.md @@ -228,4 +228,6 @@ the dead member. * No Churn Detection machines and no Marker protocol. * Coupled Mux: collecting and distributing are enabled together. * One aggregator per group. +* Selection compares the partner's system only, not its key: two aggregates + that one partner system offers on the same group land in one trunk here. * Member compatibility is not validated; see #377. diff --git a/html/app.js b/html/app.js index 4c7ed7f5..34e9713e 100644 --- a/html/app.js +++ b/html/app.js @@ -1843,6 +1843,7 @@ function buildLag(){ })(g); } var lacpCfg=[0,0,0,0,0]; +function maskPorts(m){var r=[];for(var p=1;p<=S.n;p++)if((m>>S.physToLog[p-1])&1)r.push(p);return r.length?r.join(" "):"-"} function lagLoad(){ return getJSON("/lacp.json").then(function(c){ var tb=$("lacptbl").tBodies[0]; @@ -1852,7 +1853,7 @@ function lagLoad(){ lacpCfg[g]=parseInt(lg.cfg,16); if($("lgm"+g).closest(".card").dataset.dirty)return; $("lgm"+g).value=lacpCfg[g]?"lacp":"static"; - $("lgs"+g).textContent=lacpCfg[g]?"LACP: "+t("lacp_agg")+" "+(lg.aggValid?lg.agg:"("+t("lacp_neg")+")")+", "+t("lacp_members")+" 0x"+lg.members:""; + $("lgs"+g).textContent=lacpCfg[g]?"LACP: "+t("lacp_agg")+" "+(lg.aggValid?lg.agg:"("+t("lacp_neg")+")")+", "+t("lacp_members")+" "+maskPorts(parseInt(lg.members,16)):""; }); if(c.on)c.ports.forEach(function(p){ if(p.lag===255)return; diff --git a/rtlplayground.c b/rtlplayground.c index f61140a2..38e322e7 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -1071,8 +1071,6 @@ void handle_rx(void) } } else if (lacpEnabled && uip_buf[5] == 0x02) { // LACPDU (slow protocols) lacp_in(); - if (uip_len) - tcpip_output(); } } else if (igmpEnabled && uip_buf[0] == 0x01 && uip_buf[1] == 0x00 && uip_buf[2] == 0x5e // IPv4-MC packet? && igmp_packet_handler()) { From d4345c1bf8c7fad577b15489fc47a18feb7b5848 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 21:21:07 +0200 Subject: [PATCH 43/84] lacp: keep lacp_in() and lacp_lag_set() out of the data segment The port and group of lacp_in(), the member mask passed to lacp_lag_set() and the compiler temporaries both functions spilled took six bytes of DSEG, and with them six bytes of stack. The locals and the mask live in xdata now, the partner fields go through the scratch word, and lacp_lag_set() records the new group of a port before it releases the old one. One byte is left: the group number passed in. --- rtl837x_lacp.c | 25 +++++++++++++++---------- rtl837x_lacp.h | 3 +-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/rtl837x_lacp.c b/rtl837x_lacp.c index e2ede9e3..f03ef8ef 100644 --- a/rtl837x_lacp.c +++ b/rtl837x_lacp.c @@ -284,12 +284,12 @@ void lacp_in(void) __banked /* Per rtl837x_common.h, pmask carries a 4-bit port number on RX * (hardware-verified on RTL8373: per-port rx counters track the actual * ingress port through full LACP convergence). */ - uint8_t port = ((uint8_t)HTONS(LACP_H->rtl_tag.pmask)) & 0x0f; + __xdata uint8_t port = ((uint8_t)HTONS(LACP_H->rtl_tag.pmask)) & 0x0f; if (port < machine.min_port || port > machine.max_port) return; /* Only ports assigned to a LACP-mode LAG participate in the protocol. */ - uint8_t lag = lacp_port_lag[port]; + __xdata uint8_t lag = lacp_port_lag[port]; if (lag == LACP_LAG_NONE) return; if (lacp_sys_eq(LACP_I->actor.sys, uip_ethaddr.addr)) @@ -305,10 +305,14 @@ void lacp_in(void) __banked /* Record partner = the remote's Actor block (802.3ad 43.4.9 recordPDU) */ memcpy(lacp_partner_sys[port], LACP_I->actor.sys, 6); - lacp_partner_key[port] = HTONS(LACP_I->actor.key); - lacp_partner_port[port] = HTONS(LACP_I->actor.port); - lacp_partner_sys_prio[port] = HTONS(LACP_I->actor.sys_prio); - lacp_partner_port_prio[port] = HTONS(LACP_I->actor.port_prio); + lacp_scratch_mask = HTONS(LACP_I->actor.key); + lacp_partner_key[port] = lacp_scratch_mask; + lacp_scratch_mask = HTONS(LACP_I->actor.port); + lacp_partner_port[port] = lacp_scratch_mask; + lacp_scratch_mask = HTONS(LACP_I->actor.sys_prio); + lacp_partner_sys_prio[port] = lacp_scratch_mask; + lacp_scratch_mask = HTONS(LACP_I->actor.port_prio); + lacp_partner_port_prio[port] = lacp_scratch_mask; lacp_partner_state[port] = LACP_I->actor.state; /* Receive machine -> CURRENT, (re)arm partner timeout (43.4.12) */ @@ -508,7 +512,7 @@ void lacp_init(void) __banked * The engine's RMA trap is enabled on the first LACP LAG and torn down with the * last. Never wipes a static "lag" the user configured on a different group. */ -void lacp_lag_set(uint8_t lag, uint16_t ports) __banked +void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked { if (lag >= LACP_NUM_LAGS) return; @@ -542,9 +546,11 @@ void lacp_lag_set(uint8_t lag, uint16_t ports) __banked if (!(ports & port_bit(i))) continue; lacp_scratch_lag = lacp_port_lag[i]; + lacp_port_lag[i] = lag; if (lacp_scratch_lag != LACP_LAG_NONE && lacp_scratch_lag != lag) { - lacp_lag_ports[lacp_scratch_lag] &= ~port_bit(i); - if (!lacp_lag_ports[lacp_scratch_lag]) { + lacp_scratch_mask = lacp_lag_ports[lacp_scratch_lag] & ~port_bit(i); + lacp_lag_ports[lacp_scratch_lag] = lacp_scratch_mask; + if (!lacp_scratch_mask) { lacp_agg_valid[lacp_scratch_lag] = 0; if (lacp_members_last[lacp_scratch_lag]) { lacp_members_last[lacp_scratch_lag] = 0; @@ -552,7 +558,6 @@ void lacp_lag_set(uint8_t lag, uint16_t ports) __banked } } } - lacp_port_lag[i] = lag; lacp_port_init(i); } diff --git a/rtl837x_lacp.h b/rtl837x_lacp.h index 5f03a16e..514f9e35 100644 --- a/rtl837x_lacp.h +++ b/rtl837x_lacp.h @@ -16,7 +16,7 @@ void lacp_show(void) __banked; /* "lacp show" - per-port state + RX counters */ /* Assign a candidate-port mask to a LACP-mode LAG (`lag lacp `). * ports == 0 removes the LAG from LACP management. Enables the engine on first * LACP LAG and tears it down when the last one goes away. */ -void lacp_lag_set(uint8_t lag, uint16_t ports) __banked; +void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked; /* * Per-LAG LACP: each of the 4 hardware trunk groups (0-3) can independently run @@ -89,7 +89,6 @@ extern __xdata uint16_t lacp_members_last[LACP_NUM_LAGS];/* trunk members we las #define LACP_FAST_PERIODIC 0x0032 /* fast TX 1 s; partner expires at 3 s */ #define LACP_SLOW_PERIODIC 0x05dc /* slow TX 30 s; partner expires at 90 s */ #define LACP_SHORT_TIMEOUT 0x00c8 /* silent partner dropped after 4 s (802.3ad: 3 s) */ -#define LACP_LONG_TIMEOUT 0x1770 /* long-timeout variant, after 120 s (802.3ad: 90 s) */ /* Per-port LACP receive-machine state (802.3ad 43.4.12) */ #define LACP_RX_INITIALIZE 0 From 9745dfe6ae25a5185ce0407e1c0aebce9d1af87e Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 21:21:07 +0200 Subject: [PATCH 44/84] lacp: drop the unused long timeout and an unrelated pvid hunk --- cmd_parser.c | 3 +-- doc/lacp.md | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/cmd_parser.c b/cmd_parser.c index 3a1da620..5c7e0dfd 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -2002,9 +2002,8 @@ void cmd_parser(void) __banked } else if (cmd_compare(0, "pvid")) { if (cmd_words_len == 3 && cmd_parse_port_separator(cmd_words_b[1]) != 0 && atoi_short(cmd_words_b[2]) && atoi_results_short && atoi_results_short <= 4094) - { port_pvid_set(atoi_results_u8, atoi_results_short); - } else { + else { cmd_error("pvid <1-4094>\n"); } } else if (cmd_compare(0, "vlan")) { diff --git a/doc/lacp.md b/doc/lacp.md index 3160469a..390261da 100644 --- a/doc/lacp.md +++ b/doc/lacp.md @@ -96,7 +96,6 @@ mismatch should be rejected or applied to the whole group. #define LACP_FAST_PERIODIC 0x0032 /* fast TX 1 s; partner expires at 3 s */ #define LACP_SLOW_PERIODIC 0x05dc /* slow TX 30 s; partner expires at 90 s */ #define LACP_SHORT_TIMEOUT 0x00c8 /* silent partner dropped after 4 s (802.3ad: 3 s) */ -#define LACP_LONG_TIMEOUT 0x1770 /* long-timeout variant, after 120 s (802.3ad: 90 s) */ ``` One unit is one step of the LACP timers, taken every four system ticks, so 50 From 9bd301447ba535422c470dd014c513c3d6913607 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 21:59:08 +0200 Subject: [PATCH 45/84] doc: lacp: lag d and a static list leave LACP, new lacp_lag_set() prototype --- doc/lacp.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/doc/lacp.md b/doc/lacp.md index 390261da..ab09db39 100644 --- a/doc/lacp.md +++ b/doc/lacp.md @@ -129,7 +129,7 @@ longer keeps hashing traffic onto a member that no longer agrees to carry it. ``` void lacp_init(void) __banked; /* boot init: clear per-LAG state */ void lacp_off(void) __banked; -void lacp_lag_set(uint8_t lag, uint16_t ports) __banked; +void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked; void lacp_in(void) __banked; void lacp_timers(void) __banked; void lacp_show(void) __banked; @@ -145,19 +145,20 @@ A group is put under LACP by adding `lacp` to the `lag` command: ``` lag 1 lacp 7 8 # ports 7 and 8 become LACP candidates of group 1 -lag 1 lacp off # group 1 goes back to static (so does "lag 1 lacp") +lag 1 lacp off # group 1 leaves LACP, its trunk is cleared (so does "lag 1 lacp") +lag 1 d # group 1 disabled, whether it ran LACP or not lag 1 7 8 # plain static aggregation, no protocol ``` A group put under LACP gives up the static trunk it had: the hardware trunk is -cleared and rebuilt from the ports that converge. A port named in another LACP -group moves to the new one. The protocol starts with the first group put under -LACP and stops when the last one is taken out; there is no separate switch for -it. Two commands act on -the whole engine: +cleared and rebuilt from the ports that converge. The other way round, `lag 1 d` +and a static member list take the group out of LACP before they touch the +trunk. A port named in another LACP group moves to the new one. The protocol +starts with the first group put under LACP and stops when the last one is taken +out; there is no separate switch for it. Two commands act on the whole engine: ``` -lacp off # every group back to static, protocol stopped +lacp off # every group leaves LACP, protocol stopped lacp show ``` From 7e90f6a1269edf93fe3220efc0a5e9b9211a8759 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 22:02:50 +0200 Subject: [PATCH 46/84] test: build the LACP test on the real headers, drop the port_hash test The LACP test now compiles with the shared harness flags and sdcc_shim.h against the firmware headers, so the copies under test/lacp/shim are gone. test_port_tables already covers the trunk hash seed that test/port_hash checked against its own register copies. lacp_in() declares its locals at the top, which the stricter host build needs. --- rtl837x_lacp.c | 6 ++- test/Makefile | 16 +++---- test/README.md | 3 +- test/lacp/.gitignore | 1 - test/lacp/Makefile | 31 ------------ test/lacp/shim/machine.h | 12 ----- test/lacp/shim/rtl837x_common.h | 63 ------------------------- test/lacp/shim/rtl837x_port.h | 7 --- test/lacp/shim/rtl837x_regs.h | 19 -------- test/lacp/shim/uip.h | 9 ---- test/port_hash/.gitignore | 2 - test/port_hash/Makefile | 6 --- test/port_hash/test_lag_hash.c | 68 --------------------------- test/{lacp/lacp_sim.c => test_lacp.c} | 12 +---- 14 files changed, 12 insertions(+), 243 deletions(-) delete mode 100644 test/lacp/.gitignore delete mode 100644 test/lacp/Makefile delete mode 100644 test/lacp/shim/machine.h delete mode 100644 test/lacp/shim/rtl837x_common.h delete mode 100644 test/lacp/shim/rtl837x_port.h delete mode 100644 test/lacp/shim/rtl837x_regs.h delete mode 100644 test/lacp/shim/uip.h delete mode 100644 test/port_hash/.gitignore delete mode 100644 test/port_hash/Makefile delete mode 100644 test/port_hash/test_lag_hash.c rename test/{lacp/lacp_sim.c => test_lacp.c} (97%) diff --git a/rtl837x_lacp.c b/rtl837x_lacp.c index f03ef8ef..276e80d5 100644 --- a/rtl837x_lacp.c +++ b/rtl837x_lacp.c @@ -270,6 +270,8 @@ void lacp_send(uint8_t port) __banked */ void lacp_in(void) __banked { + __xdata uint8_t port, lag; + lacp_rxbody = TRAP_RX_BODY(); if (uip_len < lacp_rxbody + sizeof(struct lacpdu_in)) { uip_len = 0; @@ -284,12 +286,12 @@ void lacp_in(void) __banked /* Per rtl837x_common.h, pmask carries a 4-bit port number on RX * (hardware-verified on RTL8373: per-port rx counters track the actual * ingress port through full LACP convergence). */ - __xdata uint8_t port = ((uint8_t)HTONS(LACP_H->rtl_tag.pmask)) & 0x0f; + port = ((uint8_t)HTONS(LACP_H->rtl_tag.pmask)) & 0x0f; if (port < machine.min_port || port > machine.max_port) return; /* Only ports assigned to a LACP-mode LAG participate in the protocol. */ - __xdata uint8_t lag = lacp_port_lag[port]; + lag = lacp_port_lag[port]; if (lag == LACP_LAG_NONE) return; if (lacp_sys_eq(LACP_I->actor.sys, uip_ethaddr.addr)) diff --git a/test/Makefile b/test/Makefile index e47cbf4e..cdb27355 100644 --- a/test/Makefile +++ b/test/Makefile @@ -28,7 +28,8 @@ TESTS = $(BUILDDIR)/test_cmd_editor \ $(BUILDDIR)/test_port_tables \ $(BUILDDIR)/test_page_json \ $(BUILDDIR)/test_httpd_tx \ - $(BUILDDIR)/test_tick_gate + $(BUILDDIR)/test_tick_gate \ + $(BUILDDIR)/test_lacp # The table tests link the register mock and one environment file; stub/ # stands in for the two headers a firmware build generates. @@ -54,29 +55,24 @@ $(BUILDDIR)/test_tick_gate: test_tick_gate.c support.c $(SRCROOT)/cmd_editor.c | $(BUILDDIR)/test_page_json: test_page_json.c $(TABLE_ENV) hw_mock.h | $(BUILDDIR) $(CC) $(CFLAGS) $(filter %.c,$^) -o $@ $(LDFLAGS) +# rtl837x_lacp.c is compiled UNMODIFIED from the firmware tree. +$(BUILDDIR)/test_lacp: test_lacp.c $(SRCROOT)/rtl837x_lacp.c | $(BUILDDIR) + $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) + # httpd/httpd.c and uip/uip.c are compiled UNMODIFIED from the firmware tree; # this test carries the environment below them itself. $(BUILDDIR)/test_httpd_tx: test_httpd_tx.c $(SRCROOT)/httpd/httpd.c $(SRCROOT)/uip/uip.c | $(BUILDDIR) $(CC) $(CFLAGS) $(filter %.c,$^) -o $@ $(LDFLAGS) -# Sandboxes with a Makefile of their own; each fails the run when it fails. -SUBTESTS = lacp port_hash - run: $(TESTS) @fail=0; for t in $(TESTS); do \ echo "=== $$t ==="; \ $$t || fail=1; \ echo; \ done; \ - for d in $(SUBTESTS); do \ - echo "=== $$d ==="; \ - $(MAKE) -C $$d test || fail=1; \ - echo; \ - done; \ if [ $$fail -ne 0 ]; then echo "TESTS FAILED"; exit 1; else echo "ALL TESTS PASSED"; fi clean: rm -rf $(BUILDDIR) - @for d in $(SUBTESTS); do $(MAKE) -C $$d clean; done .PHONY: all run clean diff --git a/test/README.md b/test/README.md index 4f040d5a..a79b3242 100644 --- a/test/README.md +++ b/test/README.md @@ -48,8 +48,7 @@ fires — so it drops straight into CI. | `test_port_tables` | `rtl837x_port.c` | VLAN entry layout and round trip, PVID register sharing, static multicast and management entries, per-port flush, trunk membership and hash seed | | `test_page_json` | `httpd/page_impl.c` + `rtl837x_port.c` | `/vlan.json`, `/vlanlist`, `/l2.json` (walk, wrap marker, paging inside `outbuf`), 64-bit counters in `/status.json` and `/counters.json` | | `test_httpd_tx` | `httpd/httpd.c` + `uip/uip.c` | a GET of a static file against a client that moves its receive window: ACK accounting, continuation chunks out of flash, retransmission | -| `lacp/` | `rtl837x_lacp.c` | state machines against a simulated 802.3ad partner: convergence, mis-cabling, expiry, re-election, two groups, LACPDU containment entries | -| `port_hash/` | `port_lag_members_set()` | a LAG seeds its own hash register | +| `test_lacp` | `rtl837x_lacp.c` | state machines against a simulated 802.3ad partner: convergence, mis-cabling, expiry, re-election, two groups, delivery by trap | ## Adding a test for another module 1. Write `test_.c` with `main()` driving the module's entry points and diff --git a/test/lacp/.gitignore b/test/lacp/.gitignore deleted file mode 100644 index 567609b1..00000000 --- a/test/lacp/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build/ diff --git a/test/lacp/Makefile b/test/lacp/Makefile deleted file mode 100644 index 856f21e5..00000000 --- a/test/lacp/Makefile +++ /dev/null @@ -1,31 +0,0 @@ -# Host-side sandbox for the firmware LACP state machines. -# -# Compiles the UNMODIFIED ../../rtl837x_lacp.c against shim headers: SDCC -# storage-class keywords are blanked via -D, and the module copy lives in -# build/ so quote-includes resolve to shim/ (its own directory has no real -# platform headers) instead of the firmware tree. rtl837x_trap.h is copied -# alongside it for the same reason: TRAP_RX_BODY() is what delivery by trap -# rests on, so the test must use the real macro, not a shim restating it. -CC ?= cc -CFLAGS = -std=c11 -Wall -Wextra -Wno-unknown-pragmas \ - -D__xdata= -D__code= -D__banked= -D__reentrant= \ - -Ishim -I../.. - -all: test - -test: build/lacp_sim - ./build/lacp_sim - -verbose: build/lacp_sim - ./build/lacp_sim -v - -build/lacp_sim: lacp_sim.c ../../rtl837x_lacp.c ../../rtl837x_lacp.h ../../rtl837x_trap.h shim/*.h - mkdir -p build - cp ../../rtl837x_lacp.c build/lacp_under_test.c - cp ../../rtl837x_trap.h build/rtl837x_trap.h - $(CC) $(CFLAGS) -o $@ lacp_sim.c build/lacp_under_test.c - -clean: - rm -rf build - -.PHONY: all test verbose clean diff --git a/test/lacp/shim/machine.h b/test/lacp/shim/machine.h deleted file mode 100644 index 7f6612fb..00000000 --- a/test/lacp/shim/machine.h +++ /dev/null @@ -1,12 +0,0 @@ -/* Host-build shim: rtl837x_lacp.c reads min_port/max_port and, for the - * console dump, the front-panel numbering. The real map is not the identity - * on any machine, so the harness uses a shuffled one to keep a logical index - * from passing as a physical port by accident. */ -#ifndef _SHIM_MACHINE_H_ -#define _SHIM_MACHINE_H_ -#include -struct machine { uint8_t min_port; uint8_t max_port; uint8_t log_to_phys_port[9]; }; -extern const struct machine machine; -struct machine_runtime { uint8_t isRTL8373; }; -extern struct machine_runtime machine_detected; -#endif diff --git a/test/lacp/shim/rtl837x_common.h b/test/lacp/shim/rtl837x_common.h deleted file mode 100644 index b21c7141..00000000 --- a/test/lacp/shim/rtl837x_common.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Host-build shim for rtl837x_common.h - just enough for rtl837x_lacp.c. - * Layouts of rtl_tag/vlan_tag and the frame-desc size mirror the real header; - * the harness static-asserts the resulting on-wire struct sizes. - */ -#ifndef _SHIM_RTL837X_COMMON_H_ -#define _SHIM_RTL837X_COMMON_H_ - -#include -#include /* memcpy/memset - firmware provides its own */ - -struct rtl_tag { - uint16_t tag; - uint8_t version; - uint8_t reason; - uint16_t flags; - uint16_t pmask; /* TX: port bitmask; RX: 4-bit port number */ -}; - -struct vlan_tag { - uint16_t svlan; - uint16_t vlan; -}; - -#define RTL_TAG_SIZE (sizeof (struct rtl_tag)) -#define VLAN_TAG_SIZE (sizeof (struct vlan_tag)) -#define RTL_FRAME_TAG_ID 0x8899 -#define RTL_FRAME_TAG_VERSION 0x04 -#define RTL_FRAME_DESC_SIZE 12 -#define ETHER_HEADER_SIZE 14 - -#define HTONS(n) ((uint16_t)((((uint16_t)(n)) << 8) | (((uint16_t)(n)) >> 8))) - -/* Console output - provided by the harness */ -void print_string(char *s); -void print_byte(uint8_t b); -void print_short(uint16_t v); -void write_char(char c); - -#endif - -/* Host-build shim: port masks used by lacp_isolation_update() */ -#ifndef PMASK_CPU -#define CPU_PORT 9 -#define PMASK_9 0x1ff -#define PMASK_6 0x1f8 -#define PMASK_CPU 0x200 -#endif - -/* Host-build shim: directed-egress/strip-tag bit (bit15 of TX pmask word) */ -#ifndef RTL_TAG_ALLOW -#define RTL_TAG_ALLOW 0x8000 -#endif - -/* Host-build shim: tag flags word bits (tag_rtl8_4) */ -#ifndef RTL_TAG_LEARN_DIS -#define RTL_TAG_LEARN_DIS 0x0020 -#define RTL_TAG_KEEP 0x0080 -#define SYS_TICK_HZ 200 -extern volatile uint32_t ticks; -#define TICKS_DUE(last, period) ((uint8_t)((uint8_t)ticks - (last)) >= (period)) - -#endif diff --git a/test/lacp/shim/rtl837x_port.h b/test/lacp/shim/rtl837x_port.h deleted file mode 100644 index 646d5253..00000000 --- a/test/lacp/shim/rtl837x_port.h +++ /dev/null @@ -1,7 +0,0 @@ -/* Host-build shim: the harness mocks trunk programming and records calls */ -#ifndef _SHIM_RTL837X_PORT_H_ -#define _SHIM_RTL837X_PORT_H_ -#include -void port_lag_members_set(uint8_t lag, uint16_t members); -void port_isolate(uint8_t port, uint16_t pmask); -#endif diff --git a/test/lacp/shim/rtl837x_regs.h b/test/lacp/shim/rtl837x_regs.h deleted file mode 100644 index 2dc92d8d..00000000 --- a/test/lacp/shim/rtl837x_regs.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Host-build shim for rtl837x_regs.h - the register numbers rtl837x_lacp.c - * touches, with the values from the real header so a wrong constant shows up - * as a failing expectation rather than as a silent no-op. - */ -#ifndef _SHIM_RTL837X_REGS_H_ -#define _SHIM_RTL837X_REGS_H_ - -#include - -#define RTL837X_RMA2_CONF 0x4ed4 -#define RTL837X_RMA_ACT_TRAP 0x10 -#define RTL837X_RMA_ACT_DROP 0x20 - -/* Registers touched by lacp_send's CPU-port targeting */ -#define RTL837X_PORT_ISOLATION_BASE 0x50c0 -#define RTL837X_REG_NIC_TXCMD 0x7850 - -#endif diff --git a/test/lacp/shim/uip.h b/test/lacp/shim/uip.h deleted file mode 100644 index 0d5a51ec..00000000 --- a/test/lacp/shim/uip.h +++ /dev/null @@ -1,9 +0,0 @@ -/* Host-build shim for uip.h - frame buffer + TX hook used by rtl837x_lacp.c */ -#ifndef _SHIM_UIP_H_ -#define _SHIM_UIP_H_ -#include -#define UIP_CONF_BUFFER_SIZE 600 -struct uip_eth_addr { uint8_t addr[6]; }; -extern uint16_t uip_len; -void tcpip_output(void); -#endif diff --git a/test/port_hash/.gitignore b/test/port_hash/.gitignore deleted file mode 100644 index cf8f788c..00000000 --- a/test/port_hash/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -test_lag_hash -port_lag_members_set.inc diff --git a/test/port_hash/Makefile b/test/port_hash/Makefile deleted file mode 100644 index feeee940..00000000 --- a/test/port_hash/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -test: port_lag_members_set.inc - gcc -Wall -Wextra -O1 -o test_lag_hash test_lag_hash.c && ./test_lag_hash -port_lag_members_set.inc: ../../rtl837x_port.c - awk '/^void port_lag_members_set/,/^}/' $< > $@ -clean: - rm -f test_lag_hash port_lag_members_set.inc diff --git a/test/port_hash/test_lag_hash.c b/test/port_hash/test_lag_hash.c deleted file mode 100644 index 205d3fc8..00000000 --- a/test/port_hash/test_lag_hash.c +++ /dev/null @@ -1,68 +0,0 @@ -/* Host test for port_lag_members_set(): the default load-balancing hash must be - * seeded into the CURRENT trunk's register (BASE + lag*4), never trunk 0's, and - * must not overwrite a hash that is already set. Compiles the REAL function - * (extracted verbatim from rtl837x_port.c) against register-recording shims. */ -#include -#include -#include - -#define __banked -#define __xdata -#define __code - -/* register defs (mirror rtl837x_regs.h) */ -#define RTL837X_TRK_MBR_CTRL_BASE 0x4f38 -#define RTL837X_TRK_HASH_CTRL_BASE 0x4f48 -#define LAG_HASH_SOURCE_PORT_NUMBER 0x01 -#define LAG_HASH_L2_SMAC 0x02 -#define LAG_HASH_L2_DMAC 0x04 -#define LAG_HASH_L3_SIP 0x08 -#define LAG_HASH_L3_DIP 0x10 -#define LAG_HASH_L4_SPORT 0x20 -#define LAG_HASH_L4_DPORT 0x40 -#define LAG_HASH_DEFAULT (LAG_HASH_L2_SMAC|LAG_HASH_L2_DMAC|LAG_HASH_L3_SIP|LAG_HASH_L3_DIP|LAG_HASH_L4_SPORT|LAG_HASH_L4_DPORT) -#define LAG_HASH_RESET (LAG_HASH_SOURCE_PORT_NUMBER|LAG_HASH_L2_SMAC|LAG_HASH_L2_DMAC|LAG_HASH_L3_SIP|LAG_HASH_L3_DIP|LAG_HASH_L4_SPORT) - -uint8_t SFR_DATA_24, SFR_DATA_16, SFR_DATA_8, SFR_DATA_0; -uint8_t sfr_data[4]; -static uint32_t reg_store[0x10000]; - -void reg_read_m(uint16_t r){ uint32_t v=reg_store[r]; - sfr_data[0]=(v>>24)&0xff; sfr_data[1]=(v>>16)&0xff; sfr_data[2]=(v>>8)&0xff; sfr_data[3]=v&0xff; } -void reg_write(uint16_t r){ reg_store[r]=((uint32_t)SFR_DATA_24<<24)|((uint32_t)SFR_DATA_16<<16)|((uint32_t)SFR_DATA_8<<8)|SFR_DATA_0; } -void print_string(char*s){(void)s;} void print_byte(uint8_t b){(void)b;} -void print_short(uint16_t s){(void)s;} void write_char(char c){(void)c;} - -#define REG_SET(r,v) do{ SFR_DATA_24=((uint32_t)(v)>>24)&0xff; SFR_DATA_16=((uint32_t)(v)>>16)&0xff; \ - SFR_DATA_8=((uint32_t)(v)>>8)&0xff; SFR_DATA_0=(v)&0xff; reg_write(r); }while(0) -#define REG_WRITE(r,a,b,c,d) do{ SFR_DATA_24=(a); SFR_DATA_16=(b); SFR_DATA_8=(c); SFR_DATA_0=(d); reg_write(r); }while(0) - -/* THE REAL FUNCTION (extracted from rtl837x_port.c at build time) */ -#include "port_lag_members_set.inc" - -static int fails; -#define CK(cond,msg,...) do{ if(cond){printf("PASS " msg "\n",##__VA_ARGS__);} else {printf("FAIL " msg "\n",##__VA_ARGS__); fails++;} }while(0) - -int main(void){ - for(int lag=0; lag<4; lag++){ - memset(reg_store,0,sizeof(reg_store)); - port_lag_members_set(lag, 0x0180); - uint16_t h = RTL837X_TRK_HASH_CTRL_BASE + (lag<<2); - uint16_t m = RTL837X_TRK_MBR_CTRL_BASE + (lag<<2); - CK(reg_store[h]==LAG_HASH_DEFAULT, "lag%d: default 0x%02x -> hash reg 0x%04x", lag, LAG_HASH_DEFAULT, h); - CK(reg_store[m]==0x0180, "lag%d: members 0x0180 -> mbr reg 0x%04x", lag, m); - if(lag) CK(reg_store[RTL837X_TRK_HASH_CTRL_BASE]==0, "lag%d: trunk-0 hash NOT clobbered", lag); - } - /* guard: existing hash must survive a later member change */ - memset(reg_store,0,sizeof(reg_store)); - reg_store[RTL837X_TRK_HASH_CTRL_BASE+(1<<2)] = 0x06; /* lag1 already L2 */ - port_lag_members_set(1, 0x00c0); - CK(reg_store[RTL837X_TRK_HASH_CTRL_BASE+(1<<2)]==0x06, "guard: preset lag1 hash 0x06 preserved"); - /* the chip's reset value counts as unset and is replaced by the default */ - memset(reg_store,0,sizeof(reg_store)); - reg_store[RTL837X_TRK_HASH_CTRL_BASE+(2<<2)] = LAG_HASH_RESET; - port_lag_members_set(2, 0x0003); - CK(reg_store[RTL837X_TRK_HASH_CTRL_BASE+(2<<2)]==LAG_HASH_DEFAULT, "reset value 0x%02x replaced by default on lag2", LAG_HASH_RESET); - printf(fails? "\n=== FAIL (%d) ===\n" : "\n=== ALL PASS ===\n", fails); - return fails?1:0; -} diff --git a/test/lacp/lacp_sim.c b/test/test_lacp.c similarity index 97% rename from test/lacp/lacp_sim.c rename to test/test_lacp.c index 13d571cd..f0ab5f3f 100644 --- a/test/lacp/lacp_sim.c +++ b/test/test_lacp.c @@ -1,13 +1,3 @@ -/* - * Host-side sandbox for the RTL837x LACP implementation. - * - * Compiles the UNMODIFIED rtl837x_lacp.c against shim headers (see shim/) and - * drives it with synthetic LACPDUs from a simulated partner switch/bond. This - * exercises the real state machines that ship in the firmware image - only - * the platform below them (frame I/O, trunk registers, console) is mocked. - * - * Run: make -C test/lacp (exit code 0 = all scenarios pass) - */ #include #include #include @@ -31,7 +21,7 @@ uint8_t lacpEnabled; uint16_t management_vlan; /* mocked; slow-protocol path zeroes it per-frame */ static int verbose = 0; -void print_string(char *s) { if (verbose) fputs(s, stdout); } +void print_string(const char *s) { if (verbose) fputs(s, stdout); } void print_byte(uint8_t b) { if (verbose) printf("%02x", b); } void print_short(uint16_t v) { if (verbose) printf("%04x", v); } void write_char(char c) { if (verbose) putchar(c); } From 6cd1f683bbdacb3076ae2076797827a3e44e53b8 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 22:05:56 +0200 Subject: [PATCH 47/84] lacp: keep comments to the frame layout The comments repeated the documentation, described tests or explained the code to its author. What stays describes the LACPDU structures. The timer constants are decimal, in units of the LACP timer. --- cmd_parser.c | 4 +- doc/CpuPort.md | 5 -- doc/link_aggregation.md | 49 ------------ httpd/page_impl.c | 7 -- rtl837x_lacp.c | 160 +++++++--------------------------------- rtl837x_lacp.h | 75 ++++++------------- rtl837x_regs.h | 4 - rtlplayground.c | 9 +-- 8 files changed, 52 insertions(+), 261 deletions(-) diff --git a/cmd_parser.c b/cmd_parser.c index 5c7e0dfd..9e226dbf 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -540,8 +540,6 @@ void parse_lag(void) return; } - /* "lag lacp [port]..." - run LACP on this LAG with the given candidate - * ports; "lag lacp off" (or no ports) removes the LAG from LACP. */ uint8_t w = 2; __xdata uint8_t lacp_mode = 0; if (cmd_words_len > 2 && cmd_compare(2, "lacp")) { @@ -560,7 +558,7 @@ void parse_lag(void) members |= ((uint16_t)1) << atoi_results_u8; } if (lacp_mode) { - lacp_lag_set(group, members); /* 0 ports also valid: removes the LAG */ + lacp_lag_set(group, members); } else { lacp_lag_set(group, 0); port_lag_members_set(group, members); diff --git a/doc/CpuPort.md b/doc/CpuPort.md index f5e9859b..c10d0a15 100644 --- a/doc/CpuPort.md +++ b/doc/CpuPort.md @@ -79,11 +79,6 @@ bit7 KEEP | 6 VSEL | 5 LEARN_DIS | 4:0 VIDX All fields are in network byte order. -This word has to be written through `HTONS` like every other field of the tag. -Writing a constant raw puts the bits in the wrong byte, so `0x0020` reaches the -wire as `0x2000`, which is EFID rather than LEARN_DIS. The ASIC then fails to -parse the tag and forwards the frame with the `0x8899` header still on it. - * `EFID_EN`, `EFID`: look the destination up under this filtering ID instead of the port's own * `PRI_EN`, `PRI`: force the given priority on the frame diff --git a/doc/link_aggregation.md b/doc/link_aggregation.md index 462b66de..b725e419 100644 --- a/doc/link_aggregation.md +++ b/doc/link_aggregation.md @@ -227,52 +227,3 @@ iperf Done. ``` As you can see, the total throughput was 4.71 GBit/sec which is close to the maximum possible with a single 5GBit link. - -## LACP: how the PDUs reach the CPU - -Slow-protocol frames go to the reserved group `01:80:C2:00:00:02`, and the -ASIC's default action for it is *drop*. The engine sets the action to *trap* -while it runs. A trap hands the frame to the port named in `EXT_CPU_CTRL`, -which names no port out of reset - that is why the trap first looked like no -help at all: the receive counters stayed frozen while the partner kept sending, -and the frames were counted as ingress discards. `trap_init()` points the -register at the CPU port once at startup. - -The *forward* action would reach the CPU as well, since the CPU port is in the -forwarding domain, but it leaves the frame in the L2 lookup and so floods it -across the ingress VLAN. That leaks LACPDUs to unrelated ports, and the damage -is real: a bond's own ports see each other's PDUs and Linux reports an illegal -loopback, a second bond on the same switch is poisoned, and the flood escapes -through uplinks. Containing it would take a static L2 multicast entry with a -CPU-only mask for every PVID in use, since untagged LACPDUs classify into the -ingress port's PVID and the forward action is not limited to the LACP ports. - -The trap keys on the address instead, before the VLAN lookup and before the -ingress filters, which settles three things at once: - -* Nothing is written per VLAN or per PVID, so delivery no longer depends on - which PVIDs the ports happen to carry. -* A port that admits tagged frames only still receives: an untagged forwarded - LACPDU is dropped by the acceptable-frame-type filter, a trapped one is not. -* A port STP holds in blocking still receives, so a group converges without - waiting out the forward delay. - -A trapped frame arrives without the VLAN tag the hardware inserts into a -forwarded one, so the PDU body sits four bytes earlier; `TRAP_RX_BODY()` gives -the offset for both forms. - -`lacp off` puts the action back to *drop*, which acts before the L2 lookup. - -## LACP: what the implementation leaves out - -The four state machines of Clause 43 are present in simplified form. Mux control -is coupled rather than independent, one partner system is elected per LAG, and -there are no churn detection machines. - -One interoperability rule is worth stating because getting it wrong is silent. -The Partner block of an outgoing PDU has to echo the peer's own actor identity -verbatim, meaning system and priority, key, port and port priority, and the -aggregation flag. A Linux partner's `__record_pdu()` accepts our SYNC bit only -when that block mirrors what it sent. Hardcoding the priorities to zero -mismatches Linux's default of 0xffff, which clears partner SYNC, moves the state -byte from 0x3f to 0x37, and the bond never reaches collecting and distributing. diff --git a/httpd/page_impl.c b/httpd/page_impl.c index 94ccb483..e1f43f6e 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -737,9 +737,6 @@ void send_stp(void) slen += strtox(outbuf + slen, "]}"); } -/* LACP protocol status for the LAG page ("/lacp.json"). State variables are - * exported read-only via rtl837x_lacp.h; hardware trunk membership itself is - * already visible through send_lag() (it reads the trunk registers). */ void send_lacp(void) { dbg_string("send_lacp called\n"); @@ -747,7 +744,6 @@ void send_lacp(void) slen += strtox(outbuf + slen, "{\"on\":"); bool_to_html(lacpEnabled); - /* Per-LAG section: candidate ports, elected aggregator, trunk members. */ slen += strtox(outbuf + slen, ",\"lags\":["); for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) { slen += strtox(outbuf + slen, "{\"cfg\":\""); @@ -768,7 +764,6 @@ void send_lacp(void) for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { slen += strtox(outbuf + slen, "{\"p\":"); itoa_html(machine.log_to_phys_port[i]); - /* Which LACP LAG this port belongs to; 255 = none */ slen += strtox(outbuf + slen, ",\"lag\":"); itoa_html(lacp_port_lag[i]); slen += strtox(outbuf + slen, ",\"a\":\""); @@ -777,8 +772,6 @@ void send_lacp(void) byte_to_html(lacp_partner_state[i]); slen += strtox(outbuf + slen, "\",\"rs\":"); itoa_html(lacp_rx_state[i]); - /* rx as 4-digit hex: itoa16_html() only renders values up to 9999 - * correctly (see its VLAN-ID comment), the counter goes to 65535 */ slen += strtox(outbuf + slen, ",\"rx\":\""); byte_to_html(lacp_rx_count[i] >> 8); byte_to_html(lacp_rx_count[i]); diff --git a/rtl837x_lacp.c b/rtl837x_lacp.c index 276e80d5..5a14e3b4 100644 --- a/rtl837x_lacp.c +++ b/rtl837x_lacp.c @@ -18,11 +18,10 @@ #include "machine.h" extern __code const struct machine machine; -extern __xdata struct machine_runtime machine_detected; /* runtime chip detection, owned by rtl837x_port.c */ -extern __xdata uint16_t management_vlan; /* owned by rtlplayground.c; suppressed per-frame for slow protocols */ +extern __xdata struct machine_runtime machine_detected; +extern __xdata uint16_t management_vlan; extern __xdata struct uip_eth_addr uip_ethaddr; extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2]; -/* lacpEnabled: extern in rtl837x_lacp.h, owned by rtlplayground.c */ /* 20-byte Actor/Partner information body (802.3ad 43.4.2.2), network byte order */ struct lacp_info { @@ -78,35 +77,29 @@ struct lacpdu_in { struct lacp_info partner; }; -__xdata uint8_t lacp_rxbody; /* offset of the LACPDU body: the RTL tag, plus a VLAN tag when forwarded */ +__xdata uint8_t lacp_rxbody; #define LACP_O ((__xdata struct lacpdu *)&uip_buf[RTL_FRAME_DESC_SIZE]) #define LACP_H ((__xdata struct lacpdu_in_hdr *)&uip_buf[0]) #define LACP_I ((__xdata struct lacpdu_in *)&uip_buf[lacp_rxbody]) -/* --- Per-port participant state (802.3ad 43.4.7). Indexed by physical port. --- */ -__xdata uint8_t lacp_actor_state[10]; /* our Actor_State flags */ -__xdata uint8_t lacp_rx_state[10]; /* LACP_RX_* receive machine */ -__xdata uint8_t lacp_partner_state[10]; /* last Partner_State we saw */ -__xdata uint8_t lacp_partner_sys[10][6]; /* partner System ID */ -__xdata uint16_t lacp_partner_key[10]; /* partner Key */ -__xdata uint16_t lacp_partner_port[10]; /* partner Port number */ -/* The Partner block echoes the peer's actor identity verbatim, see - * doc/link_aggregation.md. */ -__xdata uint16_t lacp_partner_sys_prio[10]; /* partner System priority */ -__xdata uint16_t lacp_partner_port_prio[10]; /* partner Port priority */ -__xdata uint16_t lacp_periodic[10]; /* down-counter to next TX */ -__xdata uint16_t lacp_timeout[10]; /* down-counter to partner expiry*/ -__xdata uint8_t lacp_ntt[10]; /* Need-To-Transmit flag */ +__xdata uint8_t lacp_actor_state[10]; +__xdata uint8_t lacp_rx_state[10]; +__xdata uint8_t lacp_partner_state[10]; +__xdata uint8_t lacp_partner_sys[10][6]; +__xdata uint16_t lacp_partner_key[10]; +__xdata uint16_t lacp_partner_port[10]; +__xdata uint16_t lacp_partner_sys_prio[10]; +__xdata uint16_t lacp_partner_port_prio[10]; +__xdata uint16_t lacp_periodic[10]; +__xdata uint16_t lacp_timeout[10]; +__xdata uint8_t lacp_ntt[10]; -/* The timers step every four system ticks (50 Hz) whatever the pace of the - * main loop; a long pass is caught up on, a few steps at a time. */ #define LACP_TICK_STEP (SYS_TICK_HZ / 50) #define LACP_CATCH_UP 8 __xdata uint8_t lacp_tick_last; extern volatile __xdata uint32_t ticks; -/* Per-LAG state: the hardware trunk groups run LACP independently. */ __xdata uint16_t lacp_lag_ports[LACP_NUM_LAGS]; __xdata uint8_t lacp_port_lag[10]; __xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][6]; @@ -117,12 +110,10 @@ __xdata uint16_t lacp_scratch_mask; __xdata uint8_t lacp_scratch_flag; __xdata uint8_t lacp_scratch_lag; -/* Per-port RX LACPDU count - primarily a hardware bring-up diagnostic to - * verify slow-protocol frames actually reach the CPU port (see "lacp show") */ __xdata uint16_t lacp_rx_count[10]; -void lacp_mux_update(void) __banked; /* defined below, used from lacp_in */ +void lacp_mux_update(void) __banked; static uint8_t lacp_sys_eq(__xdata uint8_t *a, __xdata uint8_t *b) { @@ -134,15 +125,10 @@ static uint8_t lacp_sys_eq(__xdata uint8_t *a, __xdata uint8_t *b) } -/* - * Selection logic (simplified 43.4.14): a port may join the aggregator when - * we have fresh partner info, the partner considers the link aggregatable, - * and the partner is the same system the aggregator was elected for. - */ static uint8_t lacp_port_selected(uint8_t port) { uint8_t lag = lacp_port_lag[port]; - if (lag == LACP_LAG_NONE) /* port not in any LACP LAG */ + if (lag == LACP_LAG_NONE) return 0; if (lacp_rx_state[port] != LACP_RX_CURRENT) return 0; @@ -154,11 +140,6 @@ static uint8_t lacp_port_selected(uint8_t port) } -/* - * Mux machine (simplified coupled control, 43.4.15): SYNC follows selection; - * COLLECTING+DISTRIBUTING additionally require the partner to be in sync. - * Returns 1 if the actor state changed (caller sets NTT). - */ static uint8_t lacp_mux_machine(uint8_t port) { uint8_t old = lacp_actor_state[port]; @@ -183,7 +164,6 @@ static uint8_t lacp_mux_machine(uint8_t port) #define port_bit(p) (((uint8_t)1) << (p)) -/* Build and emit one LACPDU out physical port `port` (802.3ad 43.4.2). */ void lacp_send(uint8_t port) __banked { memset((__xdata uint8_t *)LACP_O, 0, sizeof(struct lacpdu)); @@ -194,42 +174,22 @@ void lacp_send(uint8_t port) __banked LACP_O->rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID); LACP_O->rtl_tag.version = RTL_FRAME_TAG_VERSION; LACP_O->rtl_tag.reason = 0x00; - /* Must go through HTONS like every other tag field: writing 0x0020 raw put - * the bits in the wrong byte (on the wire 0x2000 = EFID, not LEARN_DIS), so - * the ASIC could not parse the tag and forwarded the frame with the 0x8899 - * header still attached - the partner then saw ethertype 0x8899 instead of - * 0x8809 and ignored the LACPDU. KEEP additionally stops the switch from - * rewriting the 802.1Q tag format of a CPU-injected frame (as mainline). */ LACP_O->rtl_tag.flags = HTONS(RTL_TAG_LEARN_DIS | RTL_TAG_KEEP); - /* ALLOW cleared => this word is the *forwarding* port mask, i.e. directed - * egress to exactly this port (what mainline does). Setting ALLOW instead - * makes it an allowance/permission mask on top of a normal lookup, which - * for a one-hot mask resolves to an empty egress set and the frame is - * silently dropped - verified on hardware. */ - LACP_O->rtl_tag.pmask = HTONS(port_bit(port)); /* egress this port only */ + LACP_O->rtl_tag.pmask = HTONS(port_bit(port)); LACP_O->ethertype = HTONS(SLOW_PROTO_ETHERTYPE); LACP_O->subtype = SLOW_PROTO_SUBTYPE_LACP; LACP_O->version = LACP_VERSION; - /* Actor TLV */ LACP_O->tlv_actor = LACP_TLV_ACTOR; LACP_O->actor_len = LACP_TLV_LEN_INFO; LACP_O->actor.sys_prio = HTONS(LACP_SYS_PRIO); memcpy(LACP_O->actor.sys, uip_ethaddr.addr, 6); - /* Per-LAG Actor Key so a partner never merges ports of our different LAGs - * into one aggregate (only ever called for ports in a LACP LAG). */ LACP_O->actor.key = HTONS((uint16_t)(lacp_port_lag[port] + 1)); LACP_O->actor.port_prio = HTONS(LACP_DEF_PORT_PRIO); - LACP_O->actor.port = HTONS((uint16_t)port + 1); /* 1-based port id */ + LACP_O->actor.port = HTONS((uint16_t)port + 1); LACP_O->actor.state = lacp_actor_state[port]; - /* Partner TLV: echo the last partner info we recorded, VERBATIM. A Linux - * 802.3ad partner (__record_pdu) only accepts the SYNC bit we set above if - * this block mirrors its own actor identity exactly - system+priority, key, - * port+priority. Priorities must be echoed, not hardcoded: Linux defaults to - * system priority 0xffff, so a hardcoded 0 here cleared its partner-SYNC - * (0x3f->0x37) and the aggregate never collected/distributed. */ LACP_O->tlv_partner = LACP_TLV_PARTNER; LACP_O->partner_len = LACP_TLV_LEN_INFO; LACP_O->partner.sys_prio = HTONS(lacp_partner_sys_prio[port]); @@ -239,7 +199,6 @@ void lacp_send(uint8_t port) __banked LACP_O->partner.port = HTONS(lacp_partner_port[port]); LACP_O->partner.state = lacp_partner_state[port]; - /* Collector TLV + Terminator */ LACP_O->tlv_collector = LACP_TLV_COLLECTOR; LACP_O->collector_len = LACP_TLV_LEN_COLLECTOR; LACP_O->collector_max_delay = 0; @@ -247,14 +206,6 @@ void lacp_send(uint8_t port) __banked LACP_O->terminator_len = 0x00; lacp_ntt[port] = 0; - /* Slow-protocol frames are link-local and must egress untagged. With a - * management VLAN set, tcpip_output() splices an 802.1Q tag after the SA, - * shifting our in-frame rtl_tag out of the position the ASIC expects - so it - * fails to parse/strip the CPU tag and the 0x8899 header leaks onto the wire - * (the partner then ignores the LACPDU). Suppress the VLAN insert for this - * frame only; management traffic keeps its tag. Verified on HW: with the - * insert suppressed the LACPDU egresses as clean 0x8809 and the bond - * partner converges (partner MAC becomes the switch, both links aggregate). */ uint16_t saved_mgmt_vlan = management_vlan; management_vlan = 0; uip_len = sizeof(struct lacpdu); @@ -263,11 +214,6 @@ void lacp_send(uint8_t port) __banked } -/* - * Process an incoming LACPDU. Called from the rtlplayground.c dispatch when a - * frame with DA 01:80:C2:00:00:02 and ethertype 0x8809/subtype 0x01 arrives. - * The ingress port is the low nibble of rtl_tag.pmask on RX. - */ void lacp_in(void) __banked { __xdata uint8_t port, lag; @@ -283,14 +229,10 @@ void lacp_in(void) __banked || LACP_I->subtype != SLOW_PROTO_SUBTYPE_LACP) return; - /* Per rtl837x_common.h, pmask carries a 4-bit port number on RX - * (hardware-verified on RTL8373: per-port rx counters track the actual - * ingress port through full LACP convergence). */ port = ((uint8_t)HTONS(LACP_H->rtl_tag.pmask)) & 0x0f; if (port < machine.min_port || port > machine.max_port) return; - /* Only ports assigned to a LACP-mode LAG participate in the protocol. */ lag = lacp_port_lag[port]; if (lag == LACP_LAG_NONE) return; @@ -305,7 +247,6 @@ void lacp_in(void) __banked lacp_rx_count[port]++; - /* Record partner = the remote's Actor block (802.3ad 43.4.9 recordPDU) */ memcpy(lacp_partner_sys[port], LACP_I->actor.sys, 6); lacp_scratch_mask = HTONS(LACP_I->actor.key); lacp_partner_key[port] = lacp_scratch_mask; @@ -317,22 +258,17 @@ void lacp_in(void) __banked lacp_partner_port_prio[port] = lacp_scratch_mask; lacp_partner_state[port] = LACP_I->actor.state; - /* Receive machine -> CURRENT, (re)arm partner timeout (43.4.12) */ lacp_rx_state[port] = LACP_RX_CURRENT; lacp_timeout[port] = LACP_SHORT_TIMEOUT; - /* Elect this LAG's aggregator partner system on first contact (43.4.14) */ if (!lacp_agg_valid[lag]) { memcpy(lacp_agg_sys[lag], LACP_I->actor.sys, 6); lacp_agg_valid[lag] = 1; } - /* Selection + Mux for this port; state change => Need-To-Transmit */ if (lacp_mux_machine(port)) lacp_ntt[port] = 1; - /* update_NTT (43.4.12): if the partner's view of us is stale (their - * Partner block does not match our actor state/port), tell them again. */ if (LACP_I->partner.state != lacp_actor_state[port] || HTONS(LACP_I->partner.port) != (uint16_t)port + 1) lacp_ntt[port] = 1; @@ -341,13 +277,10 @@ void lacp_in(void) __banked } -/* Recompute each LACP LAG's hardware trunk membership: a port joins its LAG's - * trunk once it is fully in sync (SYNC+COLLECTING+DISTRIBUTING and the partner - * in SYNC). Each LACP-mode LAG is programmed independently (simplified 43.4.15). */ void lacp_mux_update(void) __banked { for (uint8_t lag = 0; lag < LACP_NUM_LAGS; lag++) { - if (!lacp_lag_ports[lag]) /* LAG not under LACP management */ + if (!lacp_lag_ports[lag]) continue; lacp_scratch_mask = 0; for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { @@ -357,8 +290,6 @@ void lacp_mux_update(void) __banked && (lacp_partner_state[i] & LACP_STATE_SYNC)) lacp_scratch_mask |= port_bit(i); } - /* Program the trunk only when this LAG's membership actually changed - * - this runs on every timer tick. */ if (lacp_scratch_mask != lacp_members_last[lag]) { lacp_members_last[lag] = lacp_scratch_mask; port_lag_members_set(lag, lacp_scratch_mask); @@ -370,10 +301,9 @@ void lacp_mux_update(void) __banked static void lacp_step(void) { for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { - if (lacp_port_lag[i] == LACP_LAG_NONE) /* not in a LACP LAG */ + if (lacp_port_lag[i] == LACP_LAG_NONE) continue; - /* Periodic transmit machine (802.3ad 43.4.13) */ if (lacp_periodic[i]) { lacp_periodic[i]--; } else { @@ -382,9 +312,6 @@ static void lacp_step(void) lacp_ntt[i] = 1; } - /* Partner timeout -> DEFAULTED (802.3ad 43.4.12): the mux machine - * then drops SYNC/COLLECTING/DISTRIBUTING and the port leaves the - * trunk. TODO(43.4.12): churn detection is diagnostics-only, skipped. */ if (lacp_timeout[i]) { if (!--lacp_timeout[i]) { lacp_rx_state[i] = LACP_RX_DEFAULTED; @@ -398,11 +325,6 @@ static void lacp_step(void) lacp_send(i); } - /* Release each LAG's aggregator identity once none of its ports still - * hears the elected partner, so a re-cabled setup can elect a new one - * (43.4.14). A port that is CURRENT with some other system must not keep - * the old identity alive: it would never be selected, and neither would - * the rest of the LAG once it moves to that system too. */ for (uint8_t lag = 0; lag < LACP_NUM_LAGS; lag++) { if (!lacp_agg_valid[lag]) continue; @@ -432,11 +354,10 @@ void lacp_timers(void) __banked } -/* Bring one port up as an active-fast LACP participant (43.4.12 init). */ static void lacp_port_init(uint8_t port) { lacp_actor_state[port] = LACP_STATE_ACTIVITY | LACP_STATE_AGGREGATION - | LACP_STATE_TIMEOUT; /* active + fast */ + | LACP_STATE_TIMEOUT; lacp_rx_state[port] = LACP_RX_INITIALIZE; lacp_partner_state[port] = LACP_STATE_DEFAULTED; memset(lacp_partner_sys[port], 0, 6); @@ -444,13 +365,10 @@ static void lacp_port_init(uint8_t port) lacp_partner_port[port] = 0; lacp_periodic[port] = LACP_FAST_PERIODIC; lacp_timeout[port] = 0; - lacp_ntt[port] = 1; /* announce ourselves immediately */ + lacp_ntt[port] = 1; lacp_rx_count[port] = 0; } -/* Return a port to non-LACP state. (Port isolation is no longer touched - a - * trapped LACPDU never enters the forwarding path, so a sibling cannot see - * it, which is what the isolation workaround was for.) */ static void lacp_port_release(uint8_t port) { lacp_actor_state[port] = 0; @@ -461,7 +379,6 @@ static void lacp_port_release(uint8_t port) memset(lacp_partner_sys[port], 0, 6); } -/* True while any LAG is under LACP management (drives the global RMA trap). */ static uint8_t lacp_any_lag(void) { for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) @@ -470,11 +387,6 @@ static uint8_t lacp_any_lag(void) return 0; } -/* - * Turn the LACP engine on: trap the Slow-Protocols group to the CPU port. The - * trap acts on the address before the VLAN lookup and before the ingress - * filters, so delivery needs no L2 entry and no PVID of its own. - */ static void lacp_engine_on(void) { lacp_tick_last = (uint8_t)ticks; @@ -482,16 +394,12 @@ static void lacp_engine_on(void) trap_rma_set(RTL837X_RMA2_CONF, RTL837X_RMA_ACT_TRAP); } -/* Turn the LACP engine off: stop delivering slow-protocols to the CPU. */ static void lacp_engine_off(void) { trap_rma_set(RTL837X_RMA2_CONF, RTL837X_RMA_ACT_DROP); lacpEnabled = 0; } -/* One-time boot init: no LAG runs LACP yet, so every port maps to LACP_LAG_NONE. - * MUST run before any "lag ... lacp" config replay - xdata is not zeroed to the - * 0xff sentinel, and a stray 0 would make a port look like it belongs to LAG 0. */ void lacp_init(void) __banked { for (uint8_t i = 0; i < 10; i++) { @@ -507,13 +415,6 @@ void lacp_init(void) __banked lacpEnabled = 0; } -/* - * Assign a candidate-port mask to LACP-mode LAG `lag` (`lag lacp `). - * ports == 0 removes the LAG from LACP management. Ports leaving the LAG are - * returned to normal switching; ports joining start the protocol immediately. - * The engine's RMA trap is enabled on the first LACP LAG and torn down with the - * last. Never wipes a static "lag" the user configured on a different group. - */ void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked { if (lag >= LACP_NUM_LAGS) @@ -522,16 +423,12 @@ void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked return; lacp_scratch_flag = lacp_any_lag(); - /* Drop ports that were in this LAG but are not in the new mask. */ for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { if (lacp_port_lag[i] == lag && !(ports & port_bit(i))) { lacp_port_lag[i] = LACP_LAG_NONE; lacp_port_release(i); } } - /* Release this LAG's trunk: LACP's own, or the static one a group put - * under LACP still has. Taking the group out (ports == 0) leaves a static - * trunk alone. */ if (lacp_members_last[lag] || ports) { lacp_members_last[lag] = 0; port_lag_members_set(lag, 0); @@ -539,11 +436,9 @@ void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked lacp_agg_valid[lag] = 0; lacp_lag_ports[lag] = ports; - if (ports && !lacp_scratch_flag) /* first LACP LAG: bring the engine up first */ + if (ports && !lacp_scratch_flag) lacp_engine_on(); - /* Adopt the new member ports (with the engine already up). A port that - * belonged to another LACP LAG leaves that LAG's candidate set first. */ for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { if (!(ports & port_bit(i))) continue; @@ -563,15 +458,13 @@ void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked lacp_port_init(i); } - if (!lacp_any_lag() && lacp_scratch_flag) /* removed the last LACP LAG */ + if (!lacp_any_lag() && lacp_scratch_flag) lacp_engine_off(); - lacp_mux_update(); /* a LAG that lost a port to this one drops it from its trunk */ + lacp_mux_update(); } -/* "lacp off": remove every LACP LAG (releases their trunks, stops the engine). - * There is no "lacp on": the engine comes up with the first "lag lacp". */ void lacp_off(void) __banked { for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) @@ -580,9 +473,6 @@ void lacp_off(void) __banked } -/* "lacp show": per-port protocol state - the primary bring-up diagnostic. - * A stuck rx=0 counter means slow-protocol frames never reach the CPU port - * (see the RMA note in lacp_engine_on()). */ void lacp_show(void) __banked { print_string("LACP "); print_string(lacpEnabled ? "on" : "off"); write_char('\n'); diff --git a/rtl837x_lacp.h b/rtl837x_lacp.h index 514f9e35..44e3ad4b 100644 --- a/rtl837x_lacp.h +++ b/rtl837x_lacp.h @@ -8,92 +8,61 @@ #include -void lacp_init(void) __banked; /* boot init: clear per-LAG state */ +void lacp_init(void) __banked; void lacp_in(void) __banked; void lacp_timers(void) __banked; void lacp_off(void) __banked; -void lacp_show(void) __banked; /* "lacp show" - per-port state + RX counters */ -/* Assign a candidate-port mask to a LACP-mode LAG (`lag lacp `). - * ports == 0 removes the LAG from LACP management. Enables the engine on first - * LACP LAG and tears it down when the last one goes away. */ +void lacp_show(void) __banked; void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked; -/* - * Per-LAG LACP: each of the 4 hardware trunk groups (0-3) can independently run - * LACP on an admin-assigned set of candidate ports, forming its own aggregator - * (its own elected partner System). A port participates in LACP only if it has - * been assigned to a LACP-mode LAG (lacp_port_lag[port] != LACP_LAG_NONE). - * Ports of the same LAG that converge with a consistent partner join that LAG's - * hardware trunk. This is the port-channel model: `lag lacp `. - */ #define LACP_NUM_LAGS 4 -#define LACP_LAG_NONE 0xff /* port not assigned to any LACP LAG */ +#define LACP_LAG_NONE 0xff -/* Protocol state, exposed read-only for the web UI (page_impl.c send_lacp()) - * and the serial console. Owned by rtl837x_lacp.c / rtlplayground.c. */ extern __xdata uint8_t lacpEnabled; extern __xdata uint8_t lacp_actor_state[10]; extern __xdata uint8_t lacp_partner_state[10]; extern __xdata uint8_t lacp_rx_state[10]; extern __xdata uint8_t lacp_partner_sys[10][6]; extern __xdata uint16_t lacp_rx_count[10]; -extern __xdata uint8_t lacp_port_lag[10]; /* LAG (0-3) a port runs LACP on, or LACP_LAG_NONE */ -extern __xdata uint16_t lacp_lag_ports[LACP_NUM_LAGS]; /* admin candidate-port mask per LACP LAG (0 = LAG not LACP) */ -extern __xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][6]; /* elected partner System per LAG */ -extern __xdata uint8_t lacp_agg_valid[LACP_NUM_LAGS]; /* aggregator elected for this LAG */ -extern __xdata uint16_t lacp_members_last[LACP_NUM_LAGS];/* trunk members we last programmed per LAG */ +extern __xdata uint8_t lacp_port_lag[10]; +extern __xdata uint16_t lacp_lag_ports[LACP_NUM_LAGS]; +extern __xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][6]; +extern __xdata uint8_t lacp_agg_valid[LACP_NUM_LAGS]; +extern __xdata uint16_t lacp_members_last[LACP_NUM_LAGS]; -/* Slow-Protocols / LACPDU identifiers (802.3ad 43.4) */ #define SLOW_PROTO_ETHERTYPE 0x8809 #define SLOW_PROTO_SUBTYPE_LACP 0x01 #define LACP_VERSION 0x01 -#define LACP_DST5 0x02 /* 01:80:C2:00:00:02 last octet */ +#define LACP_DST5 0x02 -/* LACPDU TLV framing (802.3ad 43.4.2): type bytes + fixed TLV lengths. */ #define LACP_TLV_ACTOR 0x01 #define LACP_TLV_PARTNER 0x02 #define LACP_TLV_COLLECTOR 0x03 #define LACP_TLV_TERMINATOR 0x00 -#define LACP_TLV_LEN_INFO 0x14 /* Actor/Partner Information TLV length */ -#define LACP_TLV_LEN_COLLECTOR 0x10 /* Collector TLV length */ +#define LACP_TLV_LEN_INFO 0x14 +#define LACP_TLV_LEN_COLLECTOR 0x10 -/* Our aggregation identity: System priority is a constant (Linux 802.3ad - * defaults to the same 0xffff); the default Actor port priority likewise. */ #define LACP_SYS_PRIO 0xffff #define LACP_DEF_PORT_PRIO 0x00ff -/* Actor_State / Partner_State flag bits (802.3ad 43.4.2) */ -#define LACP_STATE_ACTIVITY 0x01 /* 1 = Active LACP (send periodically) */ -#define LACP_STATE_TIMEOUT 0x02 /* 1 = Short (fast) timeout */ -#define LACP_STATE_AGGREGATION 0x04 /* 1 = link is aggregatable */ -#define LACP_STATE_SYNC 0x08 /* 1 = in sync with partner */ +#define LACP_STATE_ACTIVITY 0x01 /* active LACP, sends periodically */ +#define LACP_STATE_TIMEOUT 0x02 /* short timeout requested */ +#define LACP_STATE_AGGREGATION 0x04 /* link can be aggregated */ +#define LACP_STATE_SYNC 0x08 /* in sync with the partner */ #define LACP_STATE_COLLECTING 0x10 #define LACP_STATE_DISTRIBUTING 0x20 -#define LACP_STATE_DEFAULTED 0x40 /* partner info is administrative default*/ -#define LACP_STATE_EXPIRED 0x80 /* wire-format doc; unused by the simplified RX machine */ +#define LACP_STATE_DEFAULTED 0x40 /* partner information is the default */ +#define LACP_STATE_EXPIRED 0x80 -/* Fully-participating actor: in sync AND collecting AND distributing. */ #define LACP_STATE_FULL (LACP_STATE_SYNC | LACP_STATE_COLLECTING | LACP_STATE_DISTRIBUTING) -/* - * Timer units: one step of the LACP timers, taken every four system ticks, - * 50 per second. A long pass of the main loop is caught up on a few steps at - * a time, so a period is a range and the worst case is what matters. - * - * The transmit periods are not free to choose. Our own timeouts only decide - * how patient we are, but our TX period decides when the PARTNER gives up on - * us: 802.3ad pairs fast periodic 1 s with a 3 s short timeout, and slow - * periodic 30 s with a 90 s long timeout. A period whose worst case reaches - * the partner's threshold makes it expire us intermittently, which reads as - * an aggregate that works but never stops churning. */ -#define LACP_FAST_PERIODIC 0x0032 /* fast TX 1 s; partner expires at 3 s */ -#define LACP_SLOW_PERIODIC 0x05dc /* slow TX 30 s; partner expires at 90 s */ -#define LACP_SHORT_TIMEOUT 0x00c8 /* silent partner dropped after 4 s (802.3ad: 3 s) */ +#define LACP_FAST_PERIODIC 50 +#define LACP_SLOW_PERIODIC 1500 +#define LACP_SHORT_TIMEOUT 200 -/* Per-port LACP receive-machine state (802.3ad 43.4.12) */ #define LACP_RX_INITIALIZE 0 -#define LACP_RX_PORT_DISABLED 1 /* numbering reserved (exported via /lacp.json "rs") */ -#define LACP_RX_EXPIRED 2 /* numbering reserved (exported via /lacp.json "rs") */ +#define LACP_RX_PORT_DISABLED 1 +#define LACP_RX_EXPIRED 2 #define LACP_RX_DEFAULTED 3 #define LACP_RX_CURRENT 4 diff --git a/rtl837x_regs.h b/rtl837x_regs.h index 13150ec4..c2117fb1 100644 --- a/rtl837x_regs.h +++ b/rtl837x_regs.h @@ -284,10 +284,6 @@ * Loop detection / STP */ #define RTL8373_RLDP_TIMER 0x1074 -/* Per reserved-multicast-address action. The registers are packed and - * consecutive by ENTRY rather than by the byte of the address: entry 0 is - * 01:80:C2:00:00:00 at 0x4ecc and entry 2 is ...:02 (Slow Protocols / LACP) - * at 0x4ed4, but entry 5 is ...:08, so the step of 4 counts entries. */ #define RTL837X_RMA0_CONF 0x4ecc #define RTL837X_RMA0E_CONF 0x4ee8 #define RTL837X_RMA2_CONF 0x4ed4 diff --git a/rtlplayground.c b/rtlplayground.c index 38e322e7..4a8b4ec7 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -1062,14 +1062,14 @@ void handle_rx(void) print_string(" MGMT-VLAN: "); print_short(management_vlan); write_char('\n'); #endif if ((stp_enabled || lacpEnabled) && uip_buf[0] == 0x01 && uip_buf[1] == 0x80 - && uip_buf[2] == 0xc2 && uip_buf[3] == 0x00 && uip_buf[4] == 0x00) { // 01:80:C2:00:00:0x - if (stp_enabled && uip_buf[5] == 0x00) { // STP/RSTP BPDU + && uip_buf[2] == 0xc2 && uip_buf[3] == 0x00 && uip_buf[4] == 0x00) { + if (stp_enabled && uip_buf[5] == 0x00) { stp_in(); if (uip_len) { print_string("STP TX\n"); tcpip_output(); } - } else if (lacpEnabled && uip_buf[5] == 0x02) { // LACPDU (slow protocols) + } else if (lacpEnabled && uip_buf[5] == 0x02) { lacp_in(); } } else if (igmpEnabled && uip_buf[0] == 0x01 && uip_buf[1] == 0x00 && uip_buf[2] == 0x5e // IPv4-MC packet? @@ -1296,7 +1296,6 @@ static void handle_tick(void) stp_timers(); } } - // If LACP enabled, drive its machines (own tick divider lives in the banked module) if (lacpEnabled) lacp_timers(); health_phase(HEALTH_PH_STP); @@ -1802,7 +1801,7 @@ void main(void) stp_enabled = 0; stp_defaults(); /* 802.1D/w default config before any "stp ..." replay */ lacpEnabled = 0; - lacp_init(); /* clear per-LAG state (port->LAG map = NONE) before any config replay */ + lacp_init(); nic_setup(); REG_SET(RTL837X_NIC_INT_MSK, NIC_INT_RXIE); trap_init(); From 6a31b4a2791064250018babada212d160783f1c4 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 12 Oct 2026 22:05:56 +0200 Subject: [PATCH 48/84] doc: lacp: shorten, point to stp.md for the trap The trap is described with STP, so the LACP page only says that it is used. The LACP sections leave link_aggregation.md, which now points to lacp.md, and the timing section gives the values in seconds and the 802.3ad requirements without the test history. --- doc/lacp.md | 170 +++++++++++----------------------------- doc/link_aggregation.md | 4 + 2 files changed, 50 insertions(+), 124 deletions(-) diff --git a/doc/lacp.md b/doc/lacp.md index ab09db39..b116ef9a 100644 --- a/doc/lacp.md +++ b/doc/lacp.md @@ -1,79 +1,23 @@ # LACP (Link Aggregation Control Protocol, IEEE 802.3ad) -A static link aggregation group works only if both ends agree about it in -advance. Nothing checks that they do: cable a member to the wrong neighbour and -the group keeps hashing frames onto a link that goes somewhere else. LACP is the -protocol that makes the two ends agree, and keeps agreeing — it discovers which -links reach the same partner, aggregates only those, and drops a link out of the -group when the partner stops answering. +A static link aggregation group works only if both ends are configured for the +same links, and nothing checks that they are. LACP lets the two ends agree on +which links reach the same partner, aggregates only those, and takes a link out +of the group when the partner stops answering. -The switch runs LACP in software on the 8051. Frames are the Slow Protocols -group: destination `01:80:C2:00:00:02`, ethertype `0x8809`, subtype 1. The -implementation covers the parts that decide whether an aggregate forms: -the Receive machine and the Selection and Mux machines of 802.3ad clause 43, -`recordPDU` and `update_NTT`, and the mis-cabling protection that refuses to -aggregate links whose partners differ. It does not implement the Churn Detection -machines or the Marker protocol, and the Mux machine is the coupled variant, so -collecting and distributing are enabled together rather than in sequence. - -Up to `LACP_NUM_LAGS` (4) groups can be placed under LACP; the rest stay static. +The switch runs LACP in software on the 8051. LACP frames belong to the Slow +Protocols, with destination `01:80:C2:00:00:02` and ethertype `0x8809`; the +subtype of LACP frames is 1. The implementation has the Receive, Selection and +Mux machines of 802.3ad clause 43 and does not aggregate links whose partners +differ. Up to `LACP_NUM_LAGS` (4) groups can run LACP; the others stay static. ## Getting the frames to the CPU -Reserved multicast addresses are handled by the RMA block before the L2 lookup -happens. Each address has its own register and the action field sits in bits -[5:4]: 0 forward, 1 trap to CPU, 2 drop, 3 forward excluding the CPU. The -registers are packed and consecutive by *entry* rather than by address byte, so -the step of 4 counts entries, not bytes of the address: `...:00` sits at -`0x4ecc` and `...:02` at `0x4ed4`, but `...:08` sits at `0x4ee0`. - -``` -#define RTL837X_RMA0_CONF 0x4ecc /* 01:80:C2:00:00:00, entry 0 */ -#define RTL837X_RMA2_CONF 0x4ed4 /* 01:80:C2:00:00:02, Slow Protocols */ -#define RTL837X_RMA_ACT_MASK 0x30 -#define RTL837X_RMA_ACT_TRAP 0x10 -#define RTL837X_RMA_ACT_DROP 0x20 -``` - -The engine puts this address on *trap* while it runs. A trap takes the frame -out of the forwarding path and hands it to the port named in `EXT_CPU_CTRL` -(`0x6724`), whose reset value names no port at all: the destination is meant to -be an *external* CPU on a physical port, which these boards do not have. That -is why the trap first looked like a dead end - the receive counters stayed -frozen while the partner kept sending, and the ingress discard counter grew -instead. `trap_init()` points the register at the CPU port once at startup, and -the same frames then arrive. - -`FORWARD` reaches the CPU too, because the CPU port is in the forwarding -domain, but a forwarded frame is subject to the L2 lookup and on its own floods -to every other port in the VLAN. That leaks a neighbour's LACPDUs to hosts that -must not see them and, with two bonds on one switch, poisons both; a reserved -link-local group must not be forwarded at all. Containing it takes a static L2 -multicast entry with a CPU-only member mask, and because lookups are IVL on -this chip, one entry per distinct pvid over all front panel ports - a VID 0 -entry is never matched, and a port outside the LACP groups needs its pvid -covered as well, or a LACPDU arriving there misses the lookup and is flooded -through its VLAN. - -The trap needs none of that and reaches further, because it acts on the address -before the VLAN lookup and before the ingress filters. An untagged LACPDU is -delivered on a port that admits tagged frames only, where a forwarded one is -dropped by the acceptable-frame-type filter; and it is delivered on a port STP -holds in blocking, so a group converges without waiting out the forward delay. -Both were measured against forward on the same ports of an SWTGW218AS. - -A trapped frame arrives without the VLAN tag the hardware inserts into a -forwarded one, so the LACPDU body sits four bytes earlier. `TRAP_RX_BODY()` -gives the offset of the body for either form, and `lacp_in()` reads the PDU -from there. - -Neighbouring addresses in the same block still differ in other respects - -`...:00` is exempt from the ingress STP-state check and `...:02` is not, which -the trap makes moot only because it acts before that check - so measure before -carrying a conclusion about one of them across to another. - -When LACP is switched off the action goes to `DROP`, which acts before the L2 -lookup, so there is nothing to undo. +While LACP runs, the reserved multicast action for `01:80:C2:00:00:02` is +*trap*, the same mechanism STP uses for its BPDUs, see [stp.md](stp.md). A +trapped LACPDU has no VLAN tag after the RTL tag, and `TRAP_RX_BODY()` gives the +offset of the LACPDU in the received frame. When the last group leaves LACP, +the action goes back to *drop*. ## What has to match across the members @@ -92,42 +36,30 @@ mismatch should be rejected or applied to the whole group. ## Protocol timing -``` -#define LACP_FAST_PERIODIC 0x0032 /* fast TX 1 s; partner expires at 3 s */ -#define LACP_SLOW_PERIODIC 0x05dc /* slow TX 30 s; partner expires at 90 s */ -#define LACP_SHORT_TIMEOUT 0x00c8 /* silent partner dropped after 4 s (802.3ad: 3 s) */ -``` +The timer values are in units of the LACP timer, which steps every four ticks +of the system clock, 50 times per second: -One unit is one step of the LACP timers, taken every four system ticks, so 50 -per second whatever the pace of the main loop; 50 units came out as 1.00 s on -the wire in all 116 intervals of a two minute capture. +| Constant | Value | Time | +|----------------------|-------|------| +| `LACP_FAST_PERIODIC` | 50 | 1 s | +| `LACP_SLOW_PERIODIC` | 1500 | 30 s | +| `LACP_SHORT_TIMEOUT` | 200 | 4 s | -The two transmit periods are worth a word, because they are the one thing here -that is not ours to choose freely. Our own timeouts only decide how patient we -are with a partner that has gone quiet, and being slow there costs nobody -anything. The transmit period is the opposite: it decides when the *partner* -gives up on us. 802.3ad pairs a 1 s fast period with a 3 s short timeout, and a -30 s slow period with a 90 s long timeout, and the ratio is the whole point. +802.3ad specifies a fast periodic time of 1 s with a short timeout of 3 s, and a +slow periodic time of 30 s with a long timeout of 90 s. The switch transmits at +the rate the partner asks for with its TIMEOUT bit. Its own timeout is always +the short one, one second above the standard, so a partner that stops sending +while its link stays up leaves the trunk after 4 s. -Getting that wrong does not break the aggregate, which is what makes it easy to -miss. An earlier version of this code transmitted every 2 to 3 s while the -partner was asking for the fast rate. The links still aggregated and still -carried traffic, but every time a gap crossed 3 s the partner's receive machine -timed us out and recovered: 13 of its 93 LACPDUs came back carrying `EXPIRED` -and reporting our SYNC as cleared. Since a received actor state is recorded -verbatim as the partner state and sent back out, the flap was visible from both -ends, and the bond's churn machines never settled. With the periods above, the -same capture shows no expiry at all and both churn states read `none`. - -Our own timeouts sit one period above the standard's 3 s and 90 s, which -absorbs a single lost LACPDU plus the tick jitter. A partner whose LACP has -stopped while its link stayed up is out of the trunk after that; anything -longer keeps hashing traffic onto a member that no longer agrees to carry it. +The Partner block of a transmitted LACPDU repeats the partner's actor +information as it was last received: system priority, system, key, port +priority, port and state. A Linux partner sets our SYNC only when this block +matches what it sent. ## LACP API ``` -void lacp_init(void) __banked; /* boot init: clear per-LAG state */ +void lacp_init(void) __banked; void lacp_off(void) __banked; void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked; void lacp_in(void) __banked; @@ -193,35 +125,25 @@ On the switch, with those two ports as the group: lag 1 lacp 7 8 ``` -`lacp show` should then give both ports an actor state of `0x3f`, which is -ACTIVITY, TIMEOUT, AGGREGATION, SYNC, COLLECTING and DISTRIBUTING together, a -partner state with SYNC in it, and the partner's system MAC. `lag show` should -list both ports as trunk members, since that reads the hardware rather than the -protocol's opinion of itself. +`lacp show` should then give both ports an actor state of `0x3f` (ACTIVITY, +TIMEOUT, AGGREGATION, SYNC, COLLECTING and DISTRIBUTING), a partner state with +SYNC set, and the partner's system MAC. `lag show` lists the trunk members as +programmed in the hardware and should show both ports. -The host's view is the one worth trusting, in `/proc/net/bonding/bond0`. Both -slaves want the **same `Aggregator ID`**; two different numbers mean the bond -has not aggregated anything and each link is sitting in its own group, which -looks healthy at a glance and is not. Both churn states should read `none`. -`monitoring` that never settles is the signature of our transmit period being -too slow for the rate the partner asked for. +On the host, `/proc/net/bonding/bond0` should show the same `Aggregator ID` for +both slaves; different IDs mean that each link is in a group of its own. Both +churn states should read `none`. -Three things are worth checking past convergence, because each of them has been -broken here at some point: +Further checks: -* `tcpdump -i enp1s0 ether proto 0x8809` on each slave should show the switch's - LACPDUs and that slave's own, and never the other slave's. A sibling leak is - what the trap prevents: the PDU never enters the forwarding path at all. -* No `illegal loopback` in the host's log. -* In a hex dump of a frame from the switch, the three reserved bytes after the - actor state should be zero. They used to carry whatever the transmit buffer - held from the previous frame. +* `tcpdump -i enp1s0 ether proto 0x8809` on each slave shows the LACPDUs of the + switch and of that slave, never those of the other slave. +* The host log has no `illegal loopback`. +* The three reserved bytes after the actor state in a LACPDU from the switch + are zero. -Pulling one slave down and putting it back is the cheapest failure test. The -group narrows to the surviving member and comes back within tens of seconds, -and traffic across the transition loses a few percent at the edges rather than -half of everything, which is what it would lose if the ASIC kept hashing onto -the dead member. +When one slave goes down, the group narrows to the remaining member, and the +slave rejoins after it comes back. ## Known limits diff --git a/doc/link_aggregation.md b/doc/link_aggregation.md index b725e419..90fc7637 100644 --- a/doc/link_aggregation.md +++ b/doc/link_aggregation.md @@ -227,3 +227,7 @@ iperf Done. ``` As you can see, the total throughput was 4.71 GBit/sec which is close to the maximum possible with a single 5GBit link. + +## LACP + +Dynamic aggregation with LACP is described in [lacp.md](lacp.md). From 2404e7a9b69df4ac65cd79b6762f22d3e1c2b92f Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Wed, 14 Oct 2026 00:55:11 +0200 Subject: [PATCH 49/84] lacp: tidy up after review Move port_bit() to rtl837x_common.h as PORT_BIT(), a uint16_t shift, and MAC_ADDR_LEN from rtl837x_lldp.h next to it, and use MAC_ADDR_LEN for every address in the LACP code instead of a literal 6. Define lacp_mux_update() before its first caller and drop the forward declaration, declare variables at the top of their blocks, put the operator at the end of a continued line, drop the field value comments of the LACPDU struct, and leave lacp_port_lag to lacp_init() instead of initialising it in the test environment. --- httpd/page_impl.c | 4 +- rtl837x_common.h | 2 + rtl837x_lacp.c | 121 +++++++++++++++++++++++----------------------- rtl837x_lacp.h | 5 +- rtl837x_lldp.h | 1 - test/env_tables.c | 9 ++-- test/test_lacp.c | 36 +++++++------- 7 files changed, 88 insertions(+), 90 deletions(-) diff --git a/httpd/page_impl.c b/httpd/page_impl.c index e1f43f6e..183510d0 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -752,7 +752,7 @@ void send_lacp(void) slen += strtox(outbuf + slen, "\",\"aggValid\":"); bool_to_html(lacp_agg_valid[l]); slen += strtox(outbuf + slen, ",\"agg\":\""); - for (uint8_t j = 0; j < 6; j++) + for (uint8_t j = 0; j < MAC_ADDR_LEN; j++) byte_to_html(lacp_agg_sys[l][j]); slen += strtox(outbuf + slen, "\",\"members\":\""); byte_to_html(lacp_members_last[l] >> 8); @@ -776,7 +776,7 @@ void send_lacp(void) byte_to_html(lacp_rx_count[i] >> 8); byte_to_html(lacp_rx_count[i]); slen += strtox(outbuf + slen, "\",\"psys\":\""); - for (uint8_t j = 0; j < 6; j++) + for (uint8_t j = 0; j < MAC_ADDR_LEN; j++) byte_to_html(lacp_partner_sys[i][j]); slen += strtox(outbuf + slen, "\"},"); } diff --git a/rtl837x_common.h b/rtl837x_common.h index a36d41fc..a9747a18 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -16,6 +16,8 @@ #define TICKS_DUE(last, period) ((uint8_t)((uint8_t)ticks - (last)) >= (period)) #define CPU_PORT 9 +#define MAC_ADDR_LEN 6 +#define PORT_BIT(p) ((uint16_t)1 << (p)) #define NUL '\0' // Define Port-masks for 9-port devices and 6-port devices diff --git a/rtl837x_lacp.c b/rtl837x_lacp.c index 5a14e3b4..c1bf4dc2 100644 --- a/rtl837x_lacp.c +++ b/rtl837x_lacp.c @@ -26,7 +26,7 @@ extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2]; /* 20-byte Actor/Partner information body (802.3ad 43.4.2.2), network byte order */ struct lacp_info { uint16_t sys_prio; - uint8_t sys[6]; + uint8_t sys[MAC_ADDR_LEN]; uint16_t key; uint16_t port_prio; uint16_t port; @@ -36,32 +36,32 @@ struct lacp_info { /* Outgoing LACPDU (no VLAN tag on TX, as in stp_pkt) */ struct lacpdu { - uint8_t dst[6]; - uint8_t src[6]; + uint8_t dst[MAC_ADDR_LEN]; + uint8_t src[MAC_ADDR_LEN]; struct rtl_tag rtl_tag; - uint16_t ethertype; /* 0x8809 */ - uint8_t subtype; /* 0x01 */ - uint8_t version; /* 0x01 */ - uint8_t tlv_actor; /* 0x01 */ - uint8_t actor_len; /* 0x14 */ + uint16_t ethertype; + uint8_t subtype; + uint8_t version; + uint8_t tlv_actor; + uint8_t actor_len; struct lacp_info actor; - uint8_t tlv_partner; /* 0x02 */ - uint8_t partner_len; /* 0x14 */ + uint8_t tlv_partner; + uint8_t partner_len; struct lacp_info partner; - uint8_t tlv_collector; /* 0x03 */ - uint8_t collector_len; /* 0x10 */ + uint8_t tlv_collector; + uint8_t collector_len; uint16_t collector_max_delay; uint8_t collector_reserved[12]; - uint8_t tlv_terminator; /* 0x00 */ - uint8_t terminator_len; /* 0x00 */ + uint8_t tlv_terminator; + uint8_t terminator_len; uint8_t terminator_reserved[50]; }; /* Incoming LACPDU, split because a VLAN tag sits between the two only when the * frame was forwarded: a trapped one carries none (cf. stp_pkt_in) */ struct lacpdu_in_hdr { - uint8_t dst[6]; - uint8_t src[6]; + uint8_t dst[MAC_ADDR_LEN]; + uint8_t src[MAC_ADDR_LEN]; struct rtl_tag rtl_tag; }; @@ -86,7 +86,7 @@ __xdata uint8_t lacp_rxbody; __xdata uint8_t lacp_actor_state[10]; __xdata uint8_t lacp_rx_state[10]; __xdata uint8_t lacp_partner_state[10]; -__xdata uint8_t lacp_partner_sys[10][6]; +__xdata uint8_t lacp_partner_sys[10][MAC_ADDR_LEN]; __xdata uint16_t lacp_partner_key[10]; __xdata uint16_t lacp_partner_port[10]; __xdata uint16_t lacp_partner_sys_prio[10]; @@ -102,7 +102,7 @@ extern volatile __xdata uint32_t ticks; __xdata uint16_t lacp_lag_ports[LACP_NUM_LAGS]; __xdata uint8_t lacp_port_lag[10]; -__xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][6]; +__xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][MAC_ADDR_LEN]; __xdata uint8_t lacp_agg_valid[LACP_NUM_LAGS]; __xdata uint16_t lacp_members_last[LACP_NUM_LAGS]; @@ -113,11 +113,9 @@ __xdata uint8_t lacp_scratch_lag; __xdata uint16_t lacp_rx_count[10]; -void lacp_mux_update(void) __banked; - static uint8_t lacp_sys_eq(__xdata uint8_t *a, __xdata uint8_t *b) { - for (uint8_t i = 0; i < 6; i++) { + for (uint8_t i = 0; i < MAC_ADDR_LEN; i++) { if (a[i] != b[i]) return 0; } @@ -161,21 +159,41 @@ static uint8_t lacp_mux_machine(uint8_t port) return 1; } -#define port_bit(p) (((uint8_t)1) << (p)) +void lacp_mux_update(void) __banked +{ + for (uint8_t lag = 0; lag < LACP_NUM_LAGS; lag++) { + if (!lacp_lag_ports[lag]) + continue; + lacp_scratch_mask = 0; + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + if (lacp_port_lag[i] != lag) + continue; + if ((lacp_actor_state[i] & LACP_STATE_FULL) == LACP_STATE_FULL + && (lacp_partner_state[i] & LACP_STATE_SYNC)) + lacp_scratch_mask |= PORT_BIT(i); + } + if (lacp_scratch_mask != lacp_members_last[lag]) { + lacp_members_last[lag] = lacp_scratch_mask; + port_lag_members_set(lag, lacp_scratch_mask); + } + } +} void lacp_send(uint8_t port) __banked { + uint16_t saved_mgmt_vlan; + memset((__xdata uint8_t *)LACP_O, 0, sizeof(struct lacpdu)); LACP_O->dst[0] = 0x01; LACP_O->dst[1] = 0x80; LACP_O->dst[2] = 0xc2; LACP_O->dst[3] = 0x00; LACP_O->dst[4] = 0x00; LACP_O->dst[5] = LACP_DST5; - memcpy(LACP_O->src, uip_ethaddr.addr, 6); + memcpy(LACP_O->src, uip_ethaddr.addr, MAC_ADDR_LEN); LACP_O->rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID); LACP_O->rtl_tag.version = RTL_FRAME_TAG_VERSION; LACP_O->rtl_tag.reason = 0x00; LACP_O->rtl_tag.flags = HTONS(RTL_TAG_LEARN_DIS | RTL_TAG_KEEP); - LACP_O->rtl_tag.pmask = HTONS(port_bit(port)); + LACP_O->rtl_tag.pmask = HTONS(PORT_BIT(port)); LACP_O->ethertype = HTONS(SLOW_PROTO_ETHERTYPE); LACP_O->subtype = SLOW_PROTO_SUBTYPE_LACP; @@ -184,7 +202,7 @@ void lacp_send(uint8_t port) __banked LACP_O->tlv_actor = LACP_TLV_ACTOR; LACP_O->actor_len = LACP_TLV_LEN_INFO; LACP_O->actor.sys_prio = HTONS(LACP_SYS_PRIO); - memcpy(LACP_O->actor.sys, uip_ethaddr.addr, 6); + memcpy(LACP_O->actor.sys, uip_ethaddr.addr, MAC_ADDR_LEN); LACP_O->actor.key = HTONS((uint16_t)(lacp_port_lag[port] + 1)); LACP_O->actor.port_prio = HTONS(LACP_DEF_PORT_PRIO); LACP_O->actor.port = HTONS((uint16_t)port + 1); @@ -193,7 +211,7 @@ void lacp_send(uint8_t port) __banked LACP_O->tlv_partner = LACP_TLV_PARTNER; LACP_O->partner_len = LACP_TLV_LEN_INFO; LACP_O->partner.sys_prio = HTONS(lacp_partner_sys_prio[port]); - memcpy(LACP_O->partner.sys, lacp_partner_sys[port], 6); + memcpy(LACP_O->partner.sys, lacp_partner_sys[port], MAC_ADDR_LEN); LACP_O->partner.key = HTONS(lacp_partner_key[port]); LACP_O->partner.port_prio = HTONS(lacp_partner_port_prio[port]); LACP_O->partner.port = HTONS(lacp_partner_port[port]); @@ -206,7 +224,7 @@ void lacp_send(uint8_t port) __banked LACP_O->terminator_len = 0x00; lacp_ntt[port] = 0; - uint16_t saved_mgmt_vlan = management_vlan; + saved_mgmt_vlan = management_vlan; management_vlan = 0; uip_len = sizeof(struct lacpdu); tcpip_output(); @@ -247,7 +265,7 @@ void lacp_in(void) __banked lacp_rx_count[port]++; - memcpy(lacp_partner_sys[port], LACP_I->actor.sys, 6); + memcpy(lacp_partner_sys[port], LACP_I->actor.sys, MAC_ADDR_LEN); lacp_scratch_mask = HTONS(LACP_I->actor.key); lacp_partner_key[port] = lacp_scratch_mask; lacp_scratch_mask = HTONS(LACP_I->actor.port); @@ -262,7 +280,7 @@ void lacp_in(void) __banked lacp_timeout[port] = LACP_SHORT_TIMEOUT; if (!lacp_agg_valid[lag]) { - memcpy(lacp_agg_sys[lag], LACP_I->actor.sys, 6); + memcpy(lacp_agg_sys[lag], LACP_I->actor.sys, MAC_ADDR_LEN); lacp_agg_valid[lag] = 1; } @@ -277,26 +295,6 @@ void lacp_in(void) __banked } -void lacp_mux_update(void) __banked -{ - for (uint8_t lag = 0; lag < LACP_NUM_LAGS; lag++) { - if (!lacp_lag_ports[lag]) - continue; - lacp_scratch_mask = 0; - for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { - if (lacp_port_lag[i] != lag) - continue; - if ((lacp_actor_state[i] & LACP_STATE_FULL) == LACP_STATE_FULL - && (lacp_partner_state[i] & LACP_STATE_SYNC)) - lacp_scratch_mask |= port_bit(i); - } - if (lacp_scratch_mask != lacp_members_last[lag]) { - lacp_members_last[lag] = lacp_scratch_mask; - port_lag_members_set(lag, lacp_scratch_mask); - } - } -} - static void lacp_step(void) { @@ -307,8 +305,8 @@ static void lacp_step(void) if (lacp_periodic[i]) { lacp_periodic[i]--; } else { - lacp_periodic[i] = (lacp_partner_state[i] & LACP_STATE_TIMEOUT) - ? LACP_FAST_PERIODIC : LACP_SLOW_PERIODIC; + lacp_periodic[i] = (lacp_partner_state[i] & LACP_STATE_TIMEOUT) ? + LACP_FAST_PERIODIC : LACP_SLOW_PERIODIC; lacp_ntt[i] = 1; } @@ -326,9 +324,10 @@ static void lacp_step(void) } for (uint8_t lag = 0; lag < LACP_NUM_LAGS; lag++) { + uint8_t any_current = 0; + if (!lacp_agg_valid[lag]) continue; - uint8_t any_current = 0; for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { if (lacp_port_lag[i] == lag && lacp_rx_state[i] == LACP_RX_CURRENT && lacp_sys_eq(lacp_partner_sys[i], lacp_agg_sys[lag])) { @@ -356,11 +355,11 @@ void lacp_timers(void) __banked static void lacp_port_init(uint8_t port) { - lacp_actor_state[port] = LACP_STATE_ACTIVITY | LACP_STATE_AGGREGATION - | LACP_STATE_TIMEOUT; + lacp_actor_state[port] = LACP_STATE_ACTIVITY | LACP_STATE_AGGREGATION | + LACP_STATE_TIMEOUT; lacp_rx_state[port] = LACP_RX_INITIALIZE; lacp_partner_state[port] = LACP_STATE_DEFAULTED; - memset(lacp_partner_sys[port], 0, 6); + memset(lacp_partner_sys[port], 0, MAC_ADDR_LEN); lacp_partner_key[port] = 0; lacp_partner_port[port] = 0; lacp_periodic[port] = LACP_FAST_PERIODIC; @@ -376,7 +375,7 @@ static void lacp_port_release(uint8_t port) lacp_partner_state[port] = 0; lacp_rx_state[port] = LACP_RX_INITIALIZE; lacp_rx_count[port] = 0; - memset(lacp_partner_sys[port], 0, 6); + memset(lacp_partner_sys[port], 0, MAC_ADDR_LEN); } static uint8_t lacp_any_lag(void) @@ -424,7 +423,7 @@ void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked lacp_scratch_flag = lacp_any_lag(); for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { - if (lacp_port_lag[i] == lag && !(ports & port_bit(i))) { + if (lacp_port_lag[i] == lag && !(ports & PORT_BIT(i))) { lacp_port_lag[i] = LACP_LAG_NONE; lacp_port_release(i); } @@ -440,12 +439,12 @@ void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked lacp_engine_on(); for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { - if (!(ports & port_bit(i))) + if (!(ports & PORT_BIT(i))) continue; lacp_scratch_lag = lacp_port_lag[i]; lacp_port_lag[i] = lag; if (lacp_scratch_lag != LACP_LAG_NONE && lacp_scratch_lag != lag) { - lacp_scratch_mask = lacp_lag_ports[lacp_scratch_lag] & ~port_bit(i); + lacp_scratch_mask = lacp_lag_ports[lacp_scratch_lag] & ~PORT_BIT(i); lacp_lag_ports[lacp_scratch_lag] = lacp_scratch_mask; if (!lacp_scratch_mask) { lacp_agg_valid[lacp_scratch_lag] = 0; @@ -483,7 +482,7 @@ void lacp_show(void) __banked print_string(" ports "); print_short(lacp_lag_ports[l]); print_string(" aggregator "); if (lacp_agg_valid[l]) { - for (uint8_t j = 0; j < 6; j++) + for (uint8_t j = 0; j < MAC_ADDR_LEN; j++) print_byte(lacp_agg_sys[l][j]); } else { print_string("(none)"); @@ -498,7 +497,7 @@ void lacp_show(void) __banked print_string(" rxst "); print_byte(lacp_rx_state[i]); print_string(" rx "); print_short(lacp_rx_count[i]); print_string(" psys "); - for (uint8_t j = 0; j < 6; j++) + for (uint8_t j = 0; j < MAC_ADDR_LEN; j++) print_byte(lacp_partner_sys[i][j]); write_char('\n'); } diff --git a/rtl837x_lacp.h b/rtl837x_lacp.h index 44e3ad4b..a03c4c69 100644 --- a/rtl837x_lacp.h +++ b/rtl837x_lacp.h @@ -7,6 +7,7 @@ */ #include +#include "rtl837x_common.h" void lacp_init(void) __banked; void lacp_in(void) __banked; @@ -22,11 +23,11 @@ extern __xdata uint8_t lacpEnabled; extern __xdata uint8_t lacp_actor_state[10]; extern __xdata uint8_t lacp_partner_state[10]; extern __xdata uint8_t lacp_rx_state[10]; -extern __xdata uint8_t lacp_partner_sys[10][6]; +extern __xdata uint8_t lacp_partner_sys[10][MAC_ADDR_LEN]; extern __xdata uint16_t lacp_rx_count[10]; extern __xdata uint8_t lacp_port_lag[10]; extern __xdata uint16_t lacp_lag_ports[LACP_NUM_LAGS]; -extern __xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][6]; +extern __xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][MAC_ADDR_LEN]; extern __xdata uint8_t lacp_agg_valid[LACP_NUM_LAGS]; extern __xdata uint16_t lacp_members_last[LACP_NUM_LAGS]; diff --git a/rtl837x_lldp.h b/rtl837x_lldp.h index 298775c6..0e6ee674 100644 --- a/rtl837x_lldp.h +++ b/rtl837x_lldp.h @@ -7,7 +7,6 @@ #define LLDP_ETHERTYPE 0x88cc #define LLDP_ETHERTYPE_LENGTH 2 #define LLDP_MAX_FRAME 256 -#define MAC_ADDR_LEN 6 #define LLDP_TX_INTERVAL_TICKS 6000 #define LLDP_PORT_ID_TLV_TYPE 2 diff --git a/test/env_tables.c b/test/env_tables.c index 6a841a19..719e045a 100644 --- a/test/env_tables.c +++ b/test/env_tables.c @@ -130,13 +130,10 @@ uint8_t lacpEnabled; uint8_t lacp_actor_state[10]; uint8_t lacp_partner_state[10]; uint8_t lacp_rx_state[10]; -uint8_t lacp_partner_sys[10][6]; +uint8_t lacp_partner_sys[10][MAC_ADDR_LEN]; uint16_t lacp_rx_count[10]; -uint8_t lacp_port_lag[10] = { - LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, - LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, LACP_LAG_NONE, -}; +uint8_t lacp_port_lag[10]; uint16_t lacp_lag_ports[LACP_NUM_LAGS]; -uint8_t lacp_agg_sys[LACP_NUM_LAGS][6]; +uint8_t lacp_agg_sys[LACP_NUM_LAGS][MAC_ADDR_LEN]; uint8_t lacp_agg_valid[LACP_NUM_LAGS]; uint16_t lacp_members_last[LACP_NUM_LAGS]; diff --git a/test/test_lacp.c b/test/test_lacp.c index f0ab5f3f..dbadea04 100644 --- a/test/test_lacp.c +++ b/test/test_lacp.c @@ -56,7 +56,7 @@ void trap_rma_set(uint16_t reg, uint8_t act) struct wire_info { uint16_t sys_prio; - uint8_t sys[6]; + uint8_t sys[MAC_ADDR_LEN]; uint16_t key; uint16_t port_prio; uint16_t port; @@ -65,8 +65,8 @@ struct wire_info { }; struct wire_pdu_out { /* what lacp_send() writes at uip_buf+12 */ - uint8_t dst[6]; - uint8_t src[6]; + uint8_t dst[MAC_ADDR_LEN]; + uint8_t src[MAC_ADDR_LEN]; struct rtl_tag rtl_tag; uint16_t ethertype; uint8_t subtype; @@ -83,8 +83,8 @@ struct wire_pdu_out { /* what lacp_send() writes at uip_buf+12 */ }; struct wire_pdu_in { /* forwarded form: the hardware inserts a VLAN tag */ - uint8_t dst[6]; - uint8_t src[6]; + uint8_t dst[MAC_ADDR_LEN]; + uint8_t src[MAC_ADDR_LEN]; struct rtl_tag rtl_tag; struct vlan_tag vlan_tag; uint16_t ethertype; @@ -123,14 +123,14 @@ void tcpip_output(void) /* Deliver a LACPDU into the switch on `port`, from partner system `sys`. * pstate = partner's Actor_State flags. If `echo`, the partner echoes the * switch's last-seen actor block back (fresh view) - required for quiescence. */ -static void partner_frame_full(uint8_t port, const uint8_t sys[6], uint8_t pstate, +static void partner_frame_full(uint8_t port, const uint8_t sys[MAC_ADDR_LEN], uint8_t pstate, int echo, uint16_t len, int tagged) { struct wire_pdu_in in; memset(&in, 0, sizeof(in)); in.dst[0]=0x01; in.dst[1]=0x80; in.dst[2]=0xc2; in.dst[5]=0x02; - memcpy(in.src, sys, 6); + memcpy(in.src, sys, MAC_ADDR_LEN); in.rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID); in.rtl_tag.pmask = HTONS(port); /* RX: 4-bit ingress port */ in.vlan_tag.svlan = HTONS(0x8100); /* the tag's TPID, which marks the body offset */ @@ -140,7 +140,7 @@ static void partner_frame_full(uint8_t port, const uint8_t sys[6], uint8_t pstat in.tlv_actor = 0x01; in.actor_len = 0x14; in.actor.sys_prio = HTONS(0x8000); - memcpy(in.actor.sys, sys, 6); + memcpy(in.actor.sys, sys, MAC_ADDR_LEN); in.actor.key = HTONS(0x0011); in.actor.port_prio = HTONS(0x00ff); in.actor.port = HTONS((uint16_t)port + 101); @@ -149,7 +149,7 @@ static void partner_frame_full(uint8_t port, const uint8_t sys[6], uint8_t pstat in.tlv_partner = 0x02; in.partner_len = 0x14; if (echo) { /* fresh view of us, from our last TX */ in.partner.sys_prio = last_tx[port].actor.sys_prio; - memcpy(in.partner.sys, last_tx[port].actor.sys, 6); + memcpy(in.partner.sys, last_tx[port].actor.sys, MAC_ADDR_LEN); in.partner.key = last_tx[port].actor.key; in.partner.port = last_tx[port].actor.port; in.partner.state = last_tx[port].actor.state; @@ -166,18 +166,18 @@ static void partner_frame_full(uint8_t port, const uint8_t sys[6], uint8_t pstat lacp_in(); } -static void partner_frame_len(uint8_t port, const uint8_t sys[6], uint8_t pstate, int echo, uint16_t len) +static void partner_frame_len(uint8_t port, const uint8_t sys[MAC_ADDR_LEN], uint8_t pstate, int echo, uint16_t len) { partner_frame_full(port, sys, pstate, echo, len, 1); } -static void partner_frame(uint8_t port, const uint8_t sys[6], uint8_t pstate, int echo) +static void partner_frame(uint8_t port, const uint8_t sys[MAC_ADDR_LEN], uint8_t pstate, int echo) { partner_frame_full(port, sys, pstate, echo, sizeof(struct wire_pdu_in), 1); } /* The same LACPDU as the reserved-multicast trap delivers it. */ -static void partner_frame_trap(uint8_t port, const uint8_t sys[6], uint8_t pstate, int echo) +static void partner_frame_trap(uint8_t port, const uint8_t sys[MAC_ADDR_LEN], uint8_t pstate, int echo) { partner_frame_full(port, sys, pstate, echo, sizeof(struct wire_pdu_in), 0); } @@ -201,8 +201,8 @@ static int failures; #define P_DIST LACP_STATE_DISTRIBUTING #define P_TO LACP_STATE_TIMEOUT /* partner requests short (fast) timeout */ -static const uint8_t SYS_A[6] = {0x02,0xaa,0xaa,0xaa,0xaa,0x01}; -static const uint8_t SYS_B[6] = {0x02,0xbb,0xbb,0xbb,0xbb,0x02}; +static const uint8_t SYS_A[MAC_ADDR_LEN] = {0x02,0xaa,0xaa,0xaa,0xaa,0x01}; +static const uint8_t SYS_B[MAC_ADDR_LEN] = {0x02,0xbb,0xbb,0xbb,0xbb,0x02}; int main(int argc, char **argv) { @@ -342,11 +342,11 @@ int main(int argc, char **argv) * carries, so its body sits four bytes earlier. Both forms must parse. */ uint16_t rx_before = lacp_rx_count[0]; lacp_partner_state[0] = 0; /* all three are written past the ethertype gate */ - memset(lacp_partner_sys[0], 0, 6); + memset(lacp_partner_sys[0], 0, MAC_ADDR_LEN); partner_frame_trap(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); int trap_ok = lacp_rx_count[0] == (uint16_t)(rx_before + 1) && lacp_partner_state[0] == (P_ACT|P_AGG|P_TO|P_SYNC) - && !memcmp(lacp_partner_sys[0], SYS_A, 6); + && !memcmp(lacp_partner_sys[0], SYS_A, MAC_ADDR_LEN); partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1); CHECK(trap_ok && lacp_rx_count[0] == (uint16_t)(rx_before + 2), "T15 trapped LACPDU parses like a forwarded one"); @@ -376,7 +376,7 @@ int main(int argc, char **argv) partner_frame(1, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1); step(4 * 40); } - CHECK(stayed_out && hw_members == 0x0003 && !memcmp(lacp_agg_sys[0], SYS_B, 6), + CHECK(stayed_out && hw_members == 0x0003 && !memcmp(lacp_agg_sys[0], SYS_B, MAC_ADDR_LEN), "T16 re-cabling one port at a time: aggregator moves from A to B"); /* T17: a group that already has a STATIC trunk is put under LACP with the @@ -420,7 +420,7 @@ int main(int argc, char **argv) int had_partner = lacp_rx_state[4] == LACP_RX_CURRENT && lacp_rx_count[4] > 0; lacp_lag_set(1, 0); CHECK(had_partner && lacp_partner_state[4] == 0 && lacp_rx_state[4] == LACP_RX_INITIALIZE - && lacp_rx_count[4] == 0 && !memcmp(lacp_partner_sys[4], "\0\0\0\0\0\0", 6), + && lacp_rx_count[4] == 0 && !memcmp(lacp_partner_sys[4], "\0\0\0\0\0\0", MAC_ADDR_LEN), "T20 release: partner state, rx state, counter and system cleared"); /* T21: applying the same candidate set again (the LAG page resends it From aba58e7f99cee197a9af12c96c6f9962001f3411 Mon Sep 17 00:00:00 2001 From: DrDoof <8052722+DrDoof@users.noreply.github.com> Date: Sat, 10 Oct 2026 02:43:04 +0200 Subject: [PATCH 50/84] igmp: stop looking up IPv4 multicast in the L2 table on igmp off igmp_setup(), which `igmp off` also calls, enabled the lookup of IPv4 multicast in the L2 table. Groups learned while IGMP was on therefore kept going only to the ports that had reported them, and with reports no longer trapped nothing updated those entries until a reboot. The lookup is now enabled in igmp_enable() and disabled in igmp_setup(), so with IGMP off all IP multicast is flooded again. --- doc/igmp.md | 3 ++- rtl837x_igmp.c | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/igmp.md b/doc/igmp.md index 1584287f..adce94f2 100644 --- a/doc/igmp.md +++ b/doc/igmp.md @@ -73,7 +73,8 @@ uint8_t igmp_packet_handler(void) __banked; void igmp_show(void) __banked; ```c `igmp_setup()` is called at boot-time and configures flooding of all IP-MC packets by -default, as otherwise no IP-MC would be possible in the network. +default, as otherwise no IP-MC would be possible in the network. `igmp off` calls it as +well; groups learned while IGMP was on stay in the table, but are no longer looked up. `igmp_enable()`starts IGMP which cause IGMP packets to be handled by the CPU and forwarding of IP-MC packets to be limited to only subscribed ports. diff --git a/rtl837x_igmp.c b/rtl837x_igmp.c index 39b53405..5628ec5b 100644 --- a/rtl837x_igmp.c +++ b/rtl837x_igmp.c @@ -107,8 +107,7 @@ void igmp_setup(void) __banked REG_SET(RTL837X_IPV4_UNKN_MC_FLD_PMSK, mask); REG_SET(RTL837X_IPV6_UNKN_MC_FLD_PMSK, mask); - // Enable lookup of IPv4 MC addresses in table - reg_bit_set(RTL837X_L2_CTRL, L2_CTRL_LUT_IPMC_HASH); + reg_bit_clear(RTL837X_L2_CTRL, L2_CTRL_LUT_IPMC_HASH); /* Configure per-port IGMP operations when protocol messages are received * bits 0-9 enable MC protocol snooping @@ -145,6 +144,8 @@ void igmp_enable(void) __banked { print_string("igmp_enable called\n"); igmpEnabled = 1; + // Enable lookup of IPv4 MC addresses in table + reg_bit_set(RTL837X_L2_CTRL, L2_CTRL_LUT_IPMC_HASH); // Configure trapping of unhandled IGMP protocol packets to CPU REG_SET(RTL837X_IGMP_TRAP_CFG, IGMP_CPU_PORT | IGMP_TRAP_PRIORITY); From 70f896491853fa0a10e6a8e0fd42aac842acb975 Mon Sep 17 00:00:00 2001 From: DrDoof <8052722+DrDoof@users.noreply.github.com> Date: Thu, 1 Oct 2026 21:24:05 +0200 Subject: [PATCH 51/84] igmp: leave groups in 224.0.0.0/24 to flooding The switch keeps flooding 224.0.0.0/24 with IGMP on, but a report for a group in that block still made an entry for it, and the entry limited the group to the ports that reported it. A host announcing mDNS on 224.0.0.251 thereby cut mDNS off from every other port. Such reports are now ignored, as RFC 4541 asks. --- doc/igmp.md | 3 +++ rtl837x_igmp.c | 2 ++ rtl837x_igmp.h | 1 + 3 files changed, 6 insertions(+) diff --git a/doc/igmp.md b/doc/igmp.md index adce94f2..b1aeda67 100644 --- a/doc/igmp.md +++ b/doc/igmp.md @@ -86,6 +86,9 @@ for frames to 01:00:5e:xx:xx:xx and returns 0 when the frame is not a trapped IG so that it is passed on to the IP stack. IGMPv1/v2 reports (0x12, 0x16) join the group given in the IGMP header, IGMPv2 leaves (0x17) leave it, IGMPv3 reports use the first group record (type 4 joins, type 3 leaves). Trapped IGMP frames arrive without a VLAN tag. +Reports for groups in 224.0.0.0/24 are ignored: the switch floods that block +even with IGMP on, and an entry for a group such as 224.0.0.251 (mDNS) would +otherwise limit it to the ports that reported it, as RFC 4541 warns against. `igmp_show()` prints out the IGMP configuration on the CLI. diff --git a/rtl837x_igmp.c b/rtl837x_igmp.c index 5628ec5b..b5513b4d 100644 --- a/rtl837x_igmp.c +++ b/rtl837x_igmp.c @@ -251,6 +251,8 @@ uint8_t igmp_packet_handler(void) __banked #ifdef DEBUG print_string("IGMP join "); print_byte(igmp_join); write_char('\n'); #endif + if (uip_buf[igmp_grp] == IGMP_LOCAL_NET_0 && !uip_buf[igmp_grp + 1] && !uip_buf[igmp_grp + 2]) + return 1; #ifdef IPMC_USES_L3MC memset((__xdata uint8_t *)&entry, 0, sizeof(struct ipmc_table_entry)); diff --git a/rtl837x_igmp.h b/rtl837x_igmp.h index d6d28f81..1b56e367 100644 --- a/rtl837x_igmp.h +++ b/rtl837x_igmp.h @@ -10,6 +10,7 @@ #define IGMP_V3_REPORT 0x22 #define IGMP_V3_TO_INCLUDE 3 #define IGMP_V3_TO_EXCLUDE 4 +#define IGMP_LOCAL_NET_0 224 void igmp_setup(void) __banked; void igmp_enable(void) __banked; From c3fc54133946bb36f6e8cc35c4a884843bc994e4 Mon Sep 17 00:00:00 2001 From: HL Yi Date: Wed, 14 Oct 2026 20:23:35 -0500 Subject: [PATCH 52/84] update doc based upon new machine structure --- README.md | 20 +++++++++++++------- doc/devices/2M-PCB23-V3_2.md | 4 ++-- doc/devices/2M-PCB43-V1.1.md | 2 +- doc/devices/SWTGW215AS.md | 2 +- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index b605ca5f..d369a69e 100644 --- a/README.md +++ b/README.md @@ -103,24 +103,29 @@ docker run --rm -p 8080:8080 -v $(pwd):/workspace rtlplayground-dev \ tools/output/httpd_sim /workspace/html ``` -Edit `machine.h` or `config.txt` on your host, then re-run `make` — the +Edit `config.txt` on your host, then re-run `make` — the source directory is mounted into the container, so changes take effect -immediately. To build for a different machine, pass `MACHINE=...`. +immediately. To build for a different machine, pass `MACHINE=`, which +selects `machine/.c` (see [Adding a new device](doc/adding-a-device.md)). ## (1) Compiling for direct chip flashing AND upgrading an existing RTLPlayground running device -Edit machine.h with an editor like vi or nano. Select the correct machine the firmware should build for. +Pass the machine that matches your device, for example: +``` +make MACHINE=DEFAULT_8C_1SFP +``` +The name selects the board definition `machine/.c`; `make` without a +`MACHINE` value stops with an error listing what is expected. If your board is +not in the list yet, see [Adding a new device](doc/adding-a-device.md). > [!TIP] > You can write configuration parameters in config.txt (see below) in order your switch to get > straight at the first boot, a correct IP configuration. -Now, building the firmware image should work: -``` -make -``` +Now, building the firmware image should work. + Note, that the image generated ends in .bin, not .img, in order to make IMSProg happy. image location is stored in `RTLPlayground/output/rtlplayground_version_machine.bin` @@ -353,6 +358,7 @@ Enjoy playing! ## (11) Other documents The following documents give further documentation on specific features of the RTL837x SoCs: +- [Adding a new device](doc/adding-a-device.md) - [RTL8372/3 Feature support](doc/hardware.md) - [CPU Port](doc/CpuPort.md) - [L2 learning](doc/l2.md) diff --git a/doc/devices/2M-PCB23-V3_2.md b/doc/devices/2M-PCB23-V3_2.md index 9cfe3acc..f86f8db2 100644 --- a/doc/devices/2M-PCB23-V3_2.md +++ b/doc/devices/2M-PCB23-V3_2.md @@ -17,8 +17,8 @@ The original web interface reports this board as hardware version V3.2 The component placement matches [2M-PCB23-V3.1](2M-PCB23-V3_1.md): same positions for the flash (U8), the T7/T8/T9/T10 headers, the SFP cage and its LED, and the unpopulated U7/U9 footprints. Build with -`MACHINE=KP_9000_9XHML_X_V3_2`, which uses the V3.1 machine definition and -only differs in the reported name. +`make MACHINE=KP_9000_9XHML_X_V3_2`, whose board file is a symlink to the V3.1 +definition and only differs in the reported name. ## Tested diff --git a/doc/devices/2M-PCB43-V1.1.md b/doc/devices/2M-PCB43-V1.1.md index 97343f02..0a11f513 100644 --- a/doc/devices/2M-PCB43-V1.1.md +++ b/doc/devices/2M-PCB43-V1.1.md @@ -15,7 +15,7 @@ and sold by Mokerlink. The device is fully supported: - All 4 2.5GBASE-T RJ45 ports work at 10/100/1000/2500 Mbps - The SFP+ port supports 1G, 2.5G and 10G modules -- LEDs work with the same indiciations as the OEM firmware (use KP_9000_6XHML_X2_V1_1 in machine.h if building yourself or the corresponding pre-compiled binary) +- LEDs work with the same indiciations as the OEM firmware (build with `make MACHINE=KP_9000_6XHML_X2_V1_1` if building yourself, or use the corresponding pre-compiled binary) - untested due to missing Hardware: SFP+ ports equipped with 1G or 2.5G SFPs. ### Hardware overview diff --git a/doc/devices/SWTGW215AS.md b/doc/devices/SWTGW215AS.md index 0f986986..6bfdaadc 100644 --- a/doc/devices/SWTGW215AS.md +++ b/doc/devices/SWTGW215AS.md @@ -9,7 +9,7 @@ ## RTLPlayground target -Use machine target `MACHINE_LIANGUO_ZX_SWTGW215AS` for this device. +Build for this device with `make MACHINE=LIANGUO_ZX_SWTGW215AS`, which selects the `machine/LIANGUO_ZX_SWTGW215AS.c` board definition. Physical hardware verification: 5x RJ45 ports + 1x SFP port. Port 5 RJ45 is interfaced through a RTL8221B IC. From 44ae7a8d3b198bdafa3c3f08cbb8c8c8bf13e767 Mon Sep 17 00:00:00 2001 From: HL Yi Date: Wed, 14 Oct 2026 20:37:57 -0500 Subject: [PATCH 53/84] move modification of current files into a diff PR --- README.md | 20 +++++++------------- doc/devices/2M-PCB23-V3_2.md | 4 ++-- doc/devices/2M-PCB43-V1.1.md | 2 +- doc/devices/SWTGW215AS.md | 2 +- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 3d61c1e5..6eaf74e8 100644 --- a/README.md +++ b/README.md @@ -103,29 +103,24 @@ docker run --rm -p 8080:8080 -v $(pwd):/workspace rtlplayground-dev \ tools/output/httpd_sim /workspace/html ``` -Edit `config.txt` on your host, then re-run `make` — the +Edit `machine.h` or `config.txt` on your host, then re-run `make` — the source directory is mounted into the container, so changes take effect -immediately. To build for a different machine, pass `MACHINE=`, which -selects `machine/.c` (see [Adding a new device](doc/adding-a-device.md)). +immediately. To build for a different machine, pass `MACHINE=...`. ## (1) Compiling for direct chip flashing AND upgrading an existing RTLPlayground running device -Pass the machine that matches your device, for example: -``` -make MACHINE=DEFAULT_8C_1SFP -``` -The name selects the board definition `machine/.c`; `make` without a -`MACHINE` value stops with an error listing what is expected. If your board is -not in the list yet, see [Adding a new device](doc/adding-a-device.md). +Edit machine.h with an editor like vi or nano. Select the correct machine the firmware should build for. > [!TIP] > You can write configuration parameters in config.txt (see below) in order your switch to get > straight at the first boot, a correct IP configuration. -Now, building the firmware image should work. - +Now, building the firmware image should work: +``` +make +``` Note, that the image generated ends in .bin, not .img, in order to make IMSProg happy. image location is stored in `RTLPlayground/output/rtlplayground_version_machine.bin` @@ -358,7 +353,6 @@ Enjoy playing! ## (11) Other documents The following documents give further documentation on specific features of the RTL837x SoCs: -- [Adding a new device](doc/adding-a-device.md) - [RTL8372/3 Feature support](doc/hardware.md) - [CPU Port](doc/CpuPort.md) - [L2 learning](doc/l2.md) diff --git a/doc/devices/2M-PCB23-V3_2.md b/doc/devices/2M-PCB23-V3_2.md index f86f8db2..9cfe3acc 100644 --- a/doc/devices/2M-PCB23-V3_2.md +++ b/doc/devices/2M-PCB23-V3_2.md @@ -17,8 +17,8 @@ The original web interface reports this board as hardware version V3.2 The component placement matches [2M-PCB23-V3.1](2M-PCB23-V3_1.md): same positions for the flash (U8), the T7/T8/T9/T10 headers, the SFP cage and its LED, and the unpopulated U7/U9 footprints. Build with -`make MACHINE=KP_9000_9XHML_X_V3_2`, whose board file is a symlink to the V3.1 -definition and only differs in the reported name. +`MACHINE=KP_9000_9XHML_X_V3_2`, which uses the V3.1 machine definition and +only differs in the reported name. ## Tested diff --git a/doc/devices/2M-PCB43-V1.1.md b/doc/devices/2M-PCB43-V1.1.md index 0a11f513..97343f02 100644 --- a/doc/devices/2M-PCB43-V1.1.md +++ b/doc/devices/2M-PCB43-V1.1.md @@ -15,7 +15,7 @@ and sold by Mokerlink. The device is fully supported: - All 4 2.5GBASE-T RJ45 ports work at 10/100/1000/2500 Mbps - The SFP+ port supports 1G, 2.5G and 10G modules -- LEDs work with the same indiciations as the OEM firmware (build with `make MACHINE=KP_9000_6XHML_X2_V1_1` if building yourself, or use the corresponding pre-compiled binary) +- LEDs work with the same indiciations as the OEM firmware (use KP_9000_6XHML_X2_V1_1 in machine.h if building yourself or the corresponding pre-compiled binary) - untested due to missing Hardware: SFP+ ports equipped with 1G or 2.5G SFPs. ### Hardware overview diff --git a/doc/devices/SWTGW215AS.md b/doc/devices/SWTGW215AS.md index 6bfdaadc..0f986986 100644 --- a/doc/devices/SWTGW215AS.md +++ b/doc/devices/SWTGW215AS.md @@ -9,7 +9,7 @@ ## RTLPlayground target -Build for this device with `make MACHINE=LIANGUO_ZX_SWTGW215AS`, which selects the `machine/LIANGUO_ZX_SWTGW215AS.c` board definition. +Use machine target `MACHINE_LIANGUO_ZX_SWTGW215AS` for this device. Physical hardware verification: 5x RJ45 ports + 1x SFP port. Port 5 RJ45 is interfaced through a RTL8221B IC. From adecc26ae263543a662489598986b003e04e5f7a Mon Sep 17 00:00:00 2001 From: HL Yi Date: Wed, 14 Oct 2026 21:31:26 -0500 Subject: [PATCH 54/84] update doc based upoin logicog's feedback --- doc/adding-a-device.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/doc/adding-a-device.md b/doc/adding-a-device.md index aab8f62f..0063dd71 100644 --- a/doc/adding-a-device.md +++ b/doc/adding-a-device.md @@ -1,8 +1,10 @@ # Adding a new device -A device is supported by adding one board definition under `machine/`. -the build selects the definition by name. +A device is supported by adding one board definition `.h` and board configuration `.c` file +under `machine/`. +the build selects the definition by name, and CI compiles every file it finds. +To compile for your added device, do: ``` make MACHINE= ``` @@ -13,7 +15,7 @@ make MACHINE= |------|---------| | `machine/.c` | the `struct machine machine` board definition | | `machine/.h` | `MACHINE_MODEL`, the model string shown in the UI and the CLI | -| `doc/devices/.md` + `doc/supported_devices.md` | (only if the board is verified) user-facing documentation | +| `doc/devices/.md` + `doc/supported_devices.md` | (only if the board is verified and you want to support it publicly) user-facing documentation | `` is the machine symbol without its `MACHINE_` prefix, so `MACHINE` is passed exactly as spelled on the command line: `make MACHINE=MY_BOARD_1_0` @@ -33,7 +35,7 @@ the template: #endif ``` -The header is what the UI and the CLI print; `machine.h` includes it by name. +The device product string is what the CLI or the Web-Interface will print to identify the device; `machine.h` includes it by name. > [!NOTE] > `machine_name` is a `char[30]` and `MACHINE_MODEL` is copied into it at @@ -46,6 +48,8 @@ The header is what the UI and the CLI print; `machine.h` includes it by name. `machine/.c` starts with the shared prelude and then defines the single `machine` object. Copy the closest existing board and adjust it — do not start from a blank file, the field meanings are easier to see in a real example. +The best choice is usually a device that has the same number and type of Ethernet +ports and the same number of SFP+ cages. ```c #include "machine_defs.h" @@ -91,17 +95,17 @@ __code const struct machine machine = { |-------|---------| | `machine_name` | always `MACHINE_MODEL` | | `isRTL8373` | `1` for an RTL8373 SoC, `0` for RTL8372 | -| `min_port` / `max_port` | range of logical port numbers the device has; `min_port` is `0` for RTL8373 boards and `3` for RTL8372 boards (logical ports 1–2 are the SoC's internal ports on the 8372) | +| `min_port` / `max_port` | range of logical port numbers the device has; `min_port` is `0` for RTL8373 boards and `3` for RTL8372 boards (logical ports 1–2 are the SoC's internal ports on the 8372), `max_port` is usually 8. | | `n_sfp` | number of SFP+ cages | | `n_10g` | SerDes 10G capability used by the RTL837xN init: `0` none, `1` one, `2` two 10G-capable SerDes | | `log_to_phys_port[9]` | logical port (0-based index) → physical front-panel port number | | `phys_to_log_port[9]` | the inverse, physical port 1 is index 0, unused slots are 0 | -| `is_sfp[9]` | `0` for copper, `1`/`2` for the I2C bus number of the SFP on that port | +| `is_sfp[9]` | `0` for copper, `1` for left slot and `2` for right slot | | `sfp_port[2]` | per-cage wiring, `sfp_port[0]` is the leftmost cage | | `reset_pin` | GPIO of the reset button, `GPIO_NA` if unpopulated | | `high_leds` | mux/enable for the LED pins 27–29 (`LED_27`, `LED_28_SYS`, `LED_29`) | | `port_led_set[9]` | which LED set (0–3) each logical port uses | -| `led_sets[4][4]` | the 4 LED combinations per set; which of the 4 is used depends on how the LED is wired | +| `led_sets[4][4]` | Each port can have up to 4 GPIO output pins assigned. 4 sets of LEDs with 4 LED-conditions per set; the conditions define which GPIO pins become active for a port given a port link-state. Which LEDs are then enabled depends on the wiring of the pins to up to 3 LEDs for that port, which can make a logical combination of the 4 LED output-pins. | | `led_mux_custom` / `led_mux[28]` | `1` plus the 28 mux values when the stock LED muxing does not fit the board | | `mac_flash_offset` | flash offset of the factory MAC address, omit or `0` if the board does not store one | @@ -126,7 +130,7 @@ apart: ln -s KP_9000_6XHML_X2.c machine/MY_BOARD_1_0.c ``` -The new board still needs `machine/MY_BOARD_1_0.h` with its own +The new board still needs `machine/MY_BOARD_1_0.h` (pick the version number on the PCB) with its own `MACHINE_MODEL`, because `MACHINE_MODEL` is what distinguishes the products. Existing examples: `machine/PCB_K0402WS_V3.c`, `machine/KP_9000_9XHML_X_V3_2.c` and the six `KP_9000_*` revision variants. @@ -140,7 +144,7 @@ be collapsed this way. make MACHINE=MY_BOARD_1_0 ``` -The image appears as `output/rtlplayground--MY_BOARD_1_0.bin`. An +The generated flash image appears as `output/rtlplayground--MY_BOARD_1_0.bin`. An unknown or empty `MACHINE` fails immediately with a message naming the expected file, before anything is compiled. @@ -158,7 +162,7 @@ request, alongside full builds for a 4+2 and an 8+1 board. A successful compile only proves the definition is syntactically valid. The actual behaviour — port numbering, SFP detection, LEDs — has to be confirmed on -hardware, and the console commands (`port`, `stat`, `sfp`, `leds_dump`) are the +hardware, and the console commands (`port`, `stat`, `sfp`, `leds`) are the tools for that. ## Step 4: documentation From f325bdbd113e27bfeb70571ead63b82afd3398ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Silva?= Date: Thu, 15 Oct 2026 12:55:05 +0100 Subject: [PATCH 55/84] Add GT-ST024M MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Silva --- doc/devices/SWTG024AS-A-V2.0.1_4C_2SFP.md | 1 + doc/supported_devices.md | 1 + machine/GT_ST024M.c | 1 + machine/GT_ST024M.h | 6 ++++++ 4 files changed, 9 insertions(+) create mode 120000 machine/GT_ST024M.c create mode 100644 machine/GT_ST024M.h diff --git a/doc/devices/SWTG024AS-A-V2.0.1_4C_2SFP.md b/doc/devices/SWTG024AS-A-V2.0.1_4C_2SFP.md index f1fcc535..2189dd8b 100644 --- a/doc/devices/SWTG024AS-A-V2.0.1_4C_2SFP.md +++ b/doc/devices/SWTG024AS-A-V2.0.1_4C_2SFP.md @@ -5,6 +5,7 @@ It is highly similar to SWTG024AS-V2.0, with the only difference being the GPIO |Brand|Type|Managed|PCB|Flash|Chip RTL| |---|---|---|---|---|---| | Horaco | ZX-SG4T2 | | PCB-SWTG024AS-A-V2.0.1_19650 | P25D40SH | ??? | +| GoodTop | GT-ST024M | Yes | PCB-SWTG024AS-A-V2.0.1 | 2M | ??? | ### Label specifications diff --git a/doc/supported_devices.md b/doc/supported_devices.md index 52e62bcd..c4946f6f 100644 --- a/doc/supported_devices.md +++ b/doc/supported_devices.md @@ -8,6 +8,7 @@ The following devices have been tested and are fully working: | FOXNEO | FNS-1200P | No | [PCB-K0402W-U13-V2.0](devices/FNS-1200P.md) | 2M | 4 + 2 | | Goalake | IG108 | No | [PB-2132](devices/PB-2132.md) | 4M | 8 + 1 | | GoodTop | GT-ST018M | Yes | [SWTG018AS-A V2.0](devices/SWTG018AS_A_V2_0.md) | ? | 8 + 1 | +| GoodTop | GT-ST024M | Yes | [SWTG024AS-A V2.0.1](devices/SWTG024AS-A-V2.0.1_4C_2SFP.md) | 2M | 4 + 2 | | Hasivo | S1100WP_8GT_1SX_SE | Yes | [S1100WP_8GT_1SX_SE-V1.05](devices/HASIVO_S1100WP_8GT_1SX_SE.md) | 2M | 8 + 1 | | Hisource | Hi-K0402WS | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 | | Hisource | Hi-K0402WS | No | [PCB-K0402WS-V2.0](devices/PCB-K0402WS-V2.0.md) | 2M | 4 + 2 | diff --git a/machine/GT_ST024M.c b/machine/GT_ST024M.c new file mode 120000 index 00000000..f0f8441d --- /dev/null +++ b/machine/GT_ST024M.c @@ -0,0 +1 @@ +PCB_SWTG024AS_A_2_0_1.c \ No newline at end of file diff --git a/machine/GT_ST024M.h b/machine/GT_ST024M.h new file mode 100644 index 00000000..be57f788 --- /dev/null +++ b/machine/GT_ST024M.h @@ -0,0 +1,6 @@ +#ifndef _MACHINE_GT_ST024M_H_ +#define _MACHINE_GT_ST024M_H_ + +#define MACHINE_MODEL "GoodTop GT-ST024M" + +#endif From 0b6b613c032ba07ab75a4b1be2033bc1d537fadd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20van=20Dorst?= Date: Thu, 15 Oct 2026 18:30:46 +0200 Subject: [PATCH 56/84] MACHINE_SWGT024_V2_0_MANAGED: Add mac_flash_offset --- machine/SWGT024_V2_0_MANAGED.c | 1 + 1 file changed, 1 insertion(+) diff --git a/machine/SWGT024_V2_0_MANAGED.c b/machine/SWGT024_V2_0_MANAGED.c index c2a5d6ab..ad52bc5b 100644 --- a/machine/SWGT024_V2_0_MANAGED.c +++ b/machine/SWGT024_V2_0_MANAGED.c @@ -38,4 +38,5 @@ __code const struct machine machine = { 0, }, }, + .mac_flash_offset = 0x1FC000, }; From d562fd661e1c8ad2d56ba0ba9d18316084299321 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Thu, 15 Oct 2026 20:38:55 +0200 Subject: [PATCH 57/84] cmd_parser: stop atoi_hex() at eight digits and at non-hex characters hexvalue[] holds four bytes, but atoi_hex() stored every digit it was given, so a ninth digit was written past the array into whatever follows it in xdata. More than eight digits now return 0, as any bad input does. c - '0' < 10 is computed as an int, so characters below '0' such as '-', '.' or '/' passed as digits and "-1" came out as 0xdf. The difference is now taken as uint8_t, so they are rejected. --- cmd_parser.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cmd_parser.c b/cmd_parser.c index 9e226dbf..2902fbcf 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -166,10 +166,15 @@ uint8_t atoi_hex(uint8_t idx) break; } + if (h_idx == 8) { + h_idx = 0; + break; + } + // swap hex nibbles val = (val >> 4) | (val << 4); - if (c - '0' < 10) { + if ((uint8_t)(c - '0') < 10) { val |= c - '0'; } else { c |= 0x20; From c15b2d48dabf5a01e8407f97b59537227ff64165 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Fri, 16 Oct 2026 23:34:02 +0200 Subject: [PATCH 58/84] html: call the MAC count column Devices learned The column counts the addresses the switch learns from incoming frames, so "Connected" suggested a connection it does not show. The translations follow the words the L2 tab already uses for learned entries. --- html/app.js | 12 ++++++------ html/index.html | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/html/app.js b/html/app.js index 70ea1556..f8349fae 100644 --- a/html/app.js +++ b/html/app.js @@ -19,7 +19,7 @@ i_built:"Built",i_hw:"Hardware",i_temp:"Temperature",i_flash:"Flash",i_syslog:"S p_state:"State",p_disabled:"disabled",p_up:"up",p_txgb:"TX good / bad",p_rxgb:"RX good / bad", p_pkts:"pkts",p_module:"Module",p_temp:"Temperature",p_vcc:"Vcc",p_txbias:"TX bias",p_txpower:"TX power", p_rxpower:"RX power",p_txfault:"TX fault",p_txdis:"TX disabled",p_rxlos:"RX LOS",p_adv:"Advertising", -pt_title:"Port configuration",pt_conn:"Connected",pt_speed:"Speed",pt_mtu:"MTU", +pt_title:"Port configuration",pt_conn:"Devices learned",pt_speed:"Speed",pt_mtu:"MTU", pt_sfp_note:"SFP speed is set per slot and is not saved in the startup config.", pt_name_err:"Port name: 1-15 characters, no spaces",pt_mtu_err:"MTU must be 64-16383", stp_title:"Spanning tree",stp_h:"RSTP, 802.1w",stp_version:"Version",stp_v_stp:"STP compatible", @@ -138,7 +138,7 @@ i_built:"ビルド日",i_hw:"ハードウェア",i_temp:"温度",i_flash:"フラ p_state:"状態",p_disabled:"無効",p_up:"アップ",p_txgb:"TX 正常 / 異常",p_rxgb:"RX 正常 / 異常", p_pkts:"pkts",p_module:"モジュール",p_temp:"温度",p_vcc:"電圧",p_txbias:"TX バイアス",p_txpower:"TX 電力", p_rxpower:"RX 電力",p_txfault:"TX 障害",p_txdis:"TX 無効",p_rxlos:"RX 信号ロス",p_adv:"アドバタイズ", -pt_title:"ポート設定",pt_conn:"接続デバイス",pt_speed:"速度",pt_mtu:"MTU", +pt_title:"ポート設定",pt_conn:"学習済みデバイス",pt_speed:"速度",pt_mtu:"MTU", pt_sfp_note:"SFP の速度はスロット単位で設定され、起動設定には保存されません。", pt_name_err:"ポート名: 1〜15 文字、空白不可",pt_mtu_err:"MTU は 64〜16383 の範囲で指定してください", stp_title:"スパニングツリー",stp_h:"RSTP, 802.1w",stp_version:"バージョン",stp_v_stp:"STP 互換", @@ -251,7 +251,7 @@ i_built:"构建日期",i_hw:"硬件",i_temp:"温度",i_flash:"Flash",i_syslog:"S p_state:"状态",p_disabled:"已禁用",p_up:"已连接",p_txgb:"TX 正常 / 错误",p_rxgb:"RX 正常 / 错误", p_pkts:"个包",p_module:"模块",p_temp:"温度",p_vcc:"供电电压",p_txbias:"TX 偏置电流",p_txpower:"TX 光功率", p_rxpower:"RX 光功率",p_txfault:"TX 故障",p_txdis:"TX 禁用",p_rxlos:"RX 信号丢失",p_adv:"通告能力", -pt_title:"端口配置",pt_conn:"已连接设备",pt_speed:"速率",pt_mtu:"MTU", +pt_title:"端口配置",pt_conn:"已学习设备",pt_speed:"速率",pt_mtu:"MTU", pt_sfp_note:"SFP 速率按插槽设置,不会保存到启动配置。", pt_name_err:"端口名称: 1-15 个字符,不能包含空格",pt_mtu_err:"MTU 范围为 64-16383", stp_title:"生成树",stp_h:"RSTP, 802.1w",stp_version:"版本",stp_v_stp:"STP 兼容", @@ -364,7 +364,7 @@ i_built:"Compilado",i_hw:"Hardware",i_flash:"Flash",i_syslog:"Syslog", p_state:"Estado",p_disabled:"deshabilitado",p_up:"activo",p_txgb:"TX correctos / erróneos",p_rxgb:"RX correctos / erróneos", p_pkts:"paquetes",p_module:"Módulo",p_temp:"Temperatura",p_vcc:"Vcc",p_txbias:"Polarización TX",p_txpower:"Potencia TX", p_rxpower:"Potencia RX",p_txfault:"Fallo TX",p_txdis:"TX deshabilitado",p_rxlos:"Pérdida RX (LOS)",p_adv:"Anunciado", -pt_title:"Configuración de puerto",pt_conn:"Conectado",pt_speed:"Velocidad",pt_mtu:"MTU", +pt_title:"Configuración de puerto",pt_conn:"Dispositivos aprendidos",pt_speed:"Velocidad",pt_mtu:"MTU", pt_sfp_note:"La velocidad del SFP se ajusta por ranura y no se guarda en la configuración de inicio.", pt_name_err:"Nombre de puerto: 1-15 caracteres, sin espacios",pt_mtu_err:"El MTU debe estar entre 64 y 16383", stp_title:"Árbol de expansión",stp_h:"RSTP, 802.1w",stp_version:"Versión",stp_v_stp:"STP compatible", @@ -476,7 +476,7 @@ i_built:"Compilé le",i_hw:"Matériel",i_temp:"Température",i_flash:"Taille Fla p_state:"Etat",p_disabled:"Inactif",p_up:"Actif",p_txgb:"TX bon / mauvais",p_rxgb:"RX bon / mauvais", p_pkts:"paquets",p_module:"Module",p_temp:"Température",p_vcc:"Vcc",p_txbias:"TX biais",p_txpower:"TX puissance", p_rxpower:"RX puissance",p_txfault:"TX erreur",p_txdis:"TX désactivé",p_rxlos:"RX pertes de signaux",p_adv:"Capacités", -pt_title:"Configuration du port",pt_conn:"Connecté à",pt_speed:"Vitesse",pt_mtu:"MTU", +pt_title:"Configuration du port",pt_conn:"Appareils appris",pt_speed:"Vitesse",pt_mtu:"MTU", pt_sfp_note:"La vitesse SFP est paramétrée pour le port et n'est pas enregistrée dans la configuration de démarrage.", pt_name_err:"Nom du port: 1 à 15 caractères, sans espaces",pt_mtu_err:"MTU doit être comrpis entre 64 et 16383", stp_title:"Arborescence",stp_h:"RSTP, 802.1w",stp_version:"Protocole",stp_v_stp:"Compatible STP", @@ -589,7 +589,7 @@ i_built:"Erstellt",i_hw:"Hardware",i_temp:"Temperatur",i_flash:"Flash",i_syslog: p_state:"Zustand",p_disabled:"deaktiviert",p_up:"verbunden",p_txgb:"TX gut / fehlerhaft",p_rxgb:"RX gut / fehlerhaft", p_pkts:"Pakete",p_module:"Modul",p_temp:"Temperatur",p_vcc:"Vcc",p_txbias:"TX-Bias",p_txpower:"Sendeleistung", p_rxpower:"Empfangsleistung",p_txfault:"TX-Fehler",p_txdis:"TX deaktiviert",p_rxlos:"RX LOS",p_adv:"Angeboten", -pt_title:"Port-Konfiguration",pt_conn:"Verbunden",pt_speed:"Geschwindigkeit",pt_mtu:"MTU", +pt_title:"Port-Konfiguration",pt_conn:"Gelernte Geräte",pt_speed:"Geschwindigkeit",pt_mtu:"MTU", pt_sfp_note:"Die SFP-Geschwindigkeit wird pro Steckplatz gesetzt und nicht in der Startkonfiguration gespeichert.", pt_name_err:"Port-Name: 1-15 Zeichen, keine Leerzeichen",pt_mtu_err:"MTU muss zwischen 64 und 16383 liegen", stp_title:"Spanning Tree",stp_h:"RSTP, 802.1w",stp_version:"Version",stp_v_stp:"STP-kompatibel", diff --git a/html/index.html b/html/index.html index 54ab11d2..bc44bd38 100644 --- a/html/index.html +++ b/html/index.html @@ -237,7 +237,7 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}

Port configuration

- +
PortNameLinkConnectedSpeedEnabledMTU
PortNameLinkDevices learnedSpeedEnabledMTU

SFP speed is set per slot and is not saved in the startup config.

From 2415015a87ffe2ec73275fc743a424f3682b2984 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Tue, 13 Oct 2026 05:03:50 +0200 Subject: [PATCH 59/84] web: keep the port strip in the header on every tab The row of port icons lived in a card on the Dashboard, so link state and speed were out of sight on every other tab. Move it into the header between the title and the save button, a little smaller, and poll /status.json whenever the page is open instead of only on the Dashboard, Ports and Statistics tabs; the existing idle and hidden-tab gating still pauses it. On narrow screens the strip wraps to its own row under the title. --- html/app.js | 10 ++++------ html/index.html | 27 +++++++++++++-------------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/html/app.js b/html/app.js index 3d499b38..13cc938f 100644 --- a/html/app.js +++ b/html/app.js @@ -964,6 +964,7 @@ function pollStatus(){ }); } var statusPoller=new Poller(pollStatus,2500); +statusPoller.start(); function needPorts(fn){ if(S.n)return fn(); pollStatus().then(fn).catch(function(){}); @@ -1108,8 +1109,7 @@ function dashStatus(){ }); } tabHooks.dash={ - enter:function(){statusPoller.start();pollInfo().catch(function(){})}, - leave:function(){statusPoller.stop()}, + enter:function(){pollInfo().catch(function(){})}, status:dashStatus, }; @@ -1186,8 +1186,7 @@ function applyPort(i){ postCmds(cmds).then(loadMtu).catch(function(){}); } tabHooks.ports={ - enter:function(){statusPoller.start();needPorts(buildPorts)}, - leave:function(){statusPoller.stop()}, + enter:function(){needPorts(buildPorts)}, status:portsStatus, }; @@ -1443,8 +1442,7 @@ function statsStatus(){ }); } tabHooks.stats={ - enter:function(){statusPoller.start()}, - leave:function(){statusPoller.stop();if(ctrPoll)ctrPoll.stop()}, + leave:function(){if(ctrPoll)ctrPoll.stop()}, status:statsStatus, }; diff --git a/html/index.html b/html/index.html index 9b14660b..a0e24ef7 100644 --- a/html/index.html +++ b/html/index.html @@ -55,11 +55,11 @@ a{color:var(--ac);text-decoration:none} #navfoot{padding:10px 16px;border-top:1px solid var(--bd);font-size:12px;color:var(--fg2)} #wrap{flex:1;min-width:0;display:flex;flex-direction:column} #top{ - display:flex;align-items:center;gap:10px;padding:10px 20px; + display:flex;align-items:center;flex-wrap:wrap;gap:10px;padding:8px 20px; background:var(--panel);border-bottom:1px solid var(--bd);position:sticky;top:0;z-index:5; } #burger{display:none;background:none;border:0;cursor:pointer;padding:4px} -#ttitle{font-size:16px;font-weight:650;flex:1} +#ttitle{font-size:16px;font-weight:650;min-width:120px} #dirty{ display:none;font-size:12px;font-weight:600;color:var(--warn); border:1px solid var(--warn);border-radius:20px;padding:2px 10px; @@ -112,19 +112,19 @@ label.row span.lb{width:110px;flex:none;color:var(--fg2);font-size:13px} } .switch input:checked+i{background:var(--ok)} .switch input:checked+i:before{transform:translateX(15px)} -#strip{display:flex;gap:8px;flex-wrap:wrap;padding:4px 0 2px} +#strip{display:flex;gap:6px;flex-wrap:wrap;flex:1;justify-content:center;min-width:0} .port{ - width:46px;border:2px solid var(--bd);border-radius:7px;padding:5px 0 3px; + width:40px;border:2px solid var(--bd);border-radius:7px;padding:3px 0 2px; text-align:center;cursor:pointer;background:var(--panel2);position:relative; } -.port.sfp{width:56px} -.port .pn{font-weight:650;font-size:13px} -.port .ps{font-size:10px;color:var(--fg2);display:block;line-height:1.2} +.port.sfp{width:50px} +.port .pn{font-weight:650;font-size:12px} +.port .ps{font-size:9px;color:var(--fg2);display:block;line-height:1.2} .port .jack{ - display:block;width:18px;height:12px;margin:3px auto 2px;border-radius:2px; - border:2px solid var(--fg2);border-top-width:5px;opacity:.55; + display:block;width:16px;height:10px;margin:2px auto 1px;border-radius:2px; + border:2px solid var(--fg2);border-top-width:4px;opacity:.55; } -.port.sfp .jack{width:28px;height:10px;border-top-width:3px} +.port.sfp .jack{width:24px;height:9px;border-top-width:3px} .port.up{border-color:var(--pc);background:color-mix(in srgb,var(--pc) 10%,var(--panel2))} .port.up .jack{border-color:var(--pc);opacity:1} .port.up .ps{color:var(--pc);font-weight:600} @@ -180,7 +180,8 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent} #burger{display:block} main{padding:14px 12px 40px} #top{padding:8px 10px;gap:7px} - #ttitle{font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} + #ttitle{font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1} + #strip{order:3;flex-basis:100%;justify-content:flex-start} #dirty{padding:2px 7px} } @@ -215,15 +216,13 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent} +
unsaved changes
-

Ports click a port for details

-
-

System

Traffic packets/s, live

From 6fad2836aef03c1fe6548f6b913b544f304bb3e6 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Fri, 16 Oct 2026 18:47:26 +0200 Subject: [PATCH 60/84] web: keep the port strip on one row The strip no longer wraps. Ports shrink to fit the row and scroll when they cannot, so the header on a phone stays at two rows. The strip moves under the title only below 720 px, where it no longer fits beside it. Drop the unused d_ports strings and the strip title. --- html/app.js | 12 ++++++------ html/index.html | 13 +++++++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/html/app.js b/html/app.js index 13cc938f..1599e3f0 100644 --- a/html/app.js +++ b/html/app.js @@ -12,7 +12,7 @@ c_port:"Port",c_name:"Name",c_link:"Link",c_apply:"Apply",c_cancel:"Cancel",c_co c_close:"Close",c_refresh:"Refresh",c_off:"off",c_offc:"Off",c_down:"down",c_on:"On",c_auto:"Auto",c_delete:"Delete", c_type:"Type",c_state:"State",c_enabled:"Enabled",c_load:"Load",c_disable:"Disable",c_sfp:"SFP", c_full:"full",c_half:"half",c_yes:"yes",c_no:"no", -d_ports:"Ports",d_ports_h:"click a port for details",d_system:"System",d_traffic:"Traffic", +d_system:"System",d_traffic:"Traffic", d_traffic_h:"packets/s, live",d_txpps:"TX pps",d_rxpps:"RX pps",d_txbad:"TX bad",d_rxbad:"RX bad", i_host:"Hostname",i_ip:"IP address",i_mask:"Netmask",i_gw:"Gateway",i_mac:"MAC",i_fw:"Firmware", i_built:"Built",i_hw:"Hardware",i_temp:"Temperature",i_flash:"Flash",i_syslog:"Syslog", @@ -143,7 +143,7 @@ c_port:"ポート",c_name:"名前",c_link:"リンク",c_apply:"適用",c_cancel: c_close:"閉じる",c_refresh:"更新",c_off:"オフ",c_offc:"オフ",c_down:"リンクダウン",c_on:"オン",c_auto:"自動",c_delete:"削除", c_type:"タイプ",c_state:"状態",c_enabled:"有効",c_load:"読み込み",c_disable:"無効化",c_sfp:"SFP", c_full:"全二重",c_half:"半二重",c_yes:"はい",c_no:"いいえ", -d_ports:"ポート",d_ports_h:"ポートをクリックすると詳細を表示",d_system:"システム",d_traffic:"トラフィック", +d_system:"システム",d_traffic:"トラフィック", d_traffic_h:"パケット/秒、リアルタイム",d_txpps:"TX pps",d_rxpps:"RX pps",d_txbad:"TX 異常",d_rxbad:"RX 異常", i_host:"ホスト名",i_ip:"IP アドレス",i_mask:"ネットマスク",i_gw:"ゲートウェイ",i_mac:"MAC",i_fw:"ファームウェア", i_built:"ビルド日",i_hw:"ハードウェア",i_temp:"温度",i_flash:"フラッシュ",i_syslog:"Syslog", @@ -256,7 +256,7 @@ c_port:"端口",c_name:"名称",c_link:"链路",c_apply:"应用",c_cancel:"取 c_close:"关闭",c_refresh:"刷新",c_off:"关",c_offc:"关",c_down:"未连接",c_on:"开",c_auto:"自动",c_delete:"删除", c_type:"类型",c_state:"状态",c_enabled:"启用",c_load:"读取",c_disable:"禁用",c_sfp:"SFP", c_full:"全双工",c_half:"半双工",c_yes:"是",c_no:"否", -d_ports:"端口",d_ports_h:"点击端口查看详情",d_system:"系统",d_traffic:"流量", +d_system:"系统",d_traffic:"流量", d_traffic_h:"包/秒,实时",d_txpps:"TX pps",d_rxpps:"RX pps",d_txbad:"TX 错误",d_rxbad:"RX 错误", i_host:"主机名",i_ip:"IP 地址",i_mask:"子网掩码",i_gw:"网关",i_mac:"MAC",i_fw:"固件", i_built:"构建日期",i_hw:"硬件",i_temp:"温度",i_flash:"Flash",i_syslog:"Syslog", @@ -369,7 +369,7 @@ c_port:"Puerto",c_name:"Nombre",c_link:"Enlace",c_apply:"Aplicar",c_cancel:"Canc c_close:"Cerrar",c_refresh:"Actualizar",c_off:"apagado",c_offc:"Apagado",c_down:"caído",c_on:"Encendido",c_auto:"Auto",c_delete:"Eliminar", c_type:"Tipo",c_state:"Estado",c_enabled:"Habilitado",c_load:"Cargar",c_disable:"Deshabilitar",c_sfp:"SFP", c_full:"dúplex completo",c_half:"semidúplex",c_yes:"sí",c_no:"no", -d_ports:"Puertos",d_ports_h:"pulsa un puerto para ver detalles",d_system:"Sistema",d_traffic:"Tráfico", +d_system:"Sistema",d_traffic:"Tráfico", d_traffic_h:"paquetes/s, en vivo",d_txpps:"TX pps",d_rxpps:"RX pps",d_txbad:"TX erróneos",d_rxbad:"RX erróneos", i_host:"Nombre de host",i_ip:"Dirección IP",i_mask:"Máscara de red",i_gw:"Puerta de enlace",i_mac:"MAC",i_fw:"Firmware", i_built:"Compilado",i_hw:"Hardware",i_flash:"Flash",i_syslog:"Syslog", @@ -481,7 +481,7 @@ c_port:"Port",c_name:"Nom",c_link:"Lien",c_apply:"Appliquer",c_cancel:"Annuler", c_close:"Fermer",c_refresh:"Raffraichir",c_off:"arrêt",c_offc:"Arrêt",c_down:"arrêt",c_on:"Marche",c_auto:"Auto",c_delete:"Supprimer", c_type:"Type",c_state:"Etat",c_enabled:"Activé",c_load:"Charger",c_disable:"Désactivé",c_sfp:"SFP", c_full:"full",c_half:"half",c_yes:"oui",c_no:"non", -d_ports:"Ports",d_ports_h:"Cliquez ou survollez pour plus de détails",d_system:"Système",d_traffic:"Trafic", +d_system:"Système",d_traffic:"Trafic", d_traffic_h:"paquets/s (en direct)",d_txpps:"TX pps",d_rxpps:"RX pps",d_txbad:"TX KO",d_rxbad:"RX KO", i_host:"Nom d'hote",i_ip:"Adresse IP",i_mask:"Masque réseau",i_gw:"Passerelle",i_mac:"Adresse MAC",i_fw:"Micro logiciel", i_built:"Compilé le",i_hw:"Matériel",i_temp:"Température",i_flash:"Taille Flash",i_syslog:"Journal système", @@ -598,7 +598,7 @@ c_port:"Port",c_name:"Name",c_link:"Link",c_apply:"Übernehmen",c_cancel:"Abbrec c_close:"Schliessen",c_refresh:"Aktualisieren",c_off:"aus",c_offc:"Aus",c_down:"getrennt",c_on:"Ein",c_auto:"Auto",c_delete:"Löschen", c_type:"Typ",c_state:"Zustand",c_enabled:"Aktiviert",c_load:"Laden",c_disable:"Deaktivieren",c_sfp:"SFP", c_full:"voll",c_half:"halb",c_yes:"ja",c_no:"nein", -d_ports:"Ports",d_ports_h:"Port anklicken für Details",d_system:"System",d_traffic:"Datenverkehr", +d_system:"System",d_traffic:"Datenverkehr", d_traffic_h:"Pakete/s, live",d_txpps:"TX Pakete/s",d_rxpps:"RX Pakete/s",d_txbad:"TX fehlerhaft",d_rxbad:"RX fehlerhaft", i_host:"Hostname",i_ip:"IP-Adresse",i_mask:"Netzmaske",i_gw:"Gateway",i_mac:"MAC",i_fw:"Firmware", i_built:"Erstellt",i_hw:"Hardware",i_temp:"Temperatur",i_flash:"Flash",i_syslog:"Syslog", diff --git a/html/index.html b/html/index.html index a0e24ef7..f95c8219 100644 --- a/html/index.html +++ b/html/index.html @@ -112,7 +112,9 @@ label.row span.lb{width:110px;flex:none;color:var(--fg2);font-size:13px} } .switch input:checked+i{background:var(--ok)} .switch input:checked+i:before{transform:translateX(15px)} -#strip{display:flex;gap:6px;flex-wrap:wrap;flex:1;justify-content:center;min-width:0} +#strip{display:flex;gap:6px;flex:1;min-width:0;overflow-x:auto;scrollbar-width:thin} +.port:first-child{margin-left:auto} +.port:last-child{margin-right:auto} .port{ width:40px;border:2px solid var(--bd);border-radius:7px;padding:3px 0 2px; text-align:center;cursor:pointer;background:var(--panel2);position:relative; @@ -180,10 +182,13 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent} #burger{display:block} main{padding:14px 12px 40px} #top{padding:8px 10px;gap:7px} - #ttitle{font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1} - #strip{order:3;flex-basis:100%;justify-content:flex-start} + #ttitle{font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} #dirty{padding:2px 7px} } +@media (max-width:720px){ + #ttitle{flex:1} + #strip{order:3;flex-basis:100%} +}