2 Commits
Author SHA1 Message Date
logicog 555aa9820d Makefile: Lower limit of --xram-size to protect ChaCha20 space
ChaCha20 uses 256 bytes starting from 0xbf00 for its calculations.
Make sure the linker checks that this space is not overwritten.
2026-10-10 07:31:35 +02:00
logicog 1c685edde5 ChaCha20 implementation for encryption
Add an implementation in 8051 assembler for the generation of a
ChaCha20 keystream and an encryption function using the keystream
in C.

A test function using the example from RFC7539 is provided in
chacha20_test.

Keys, nonce, and block counter and the pointers to the input plaintext,
its length and the output pointer are provided via a struct chacha20,
which also holds the working state of the ChaCha20 algorithm
2026-10-10 07:31:35 +02:00
292 changed files with 3665 additions and 14219 deletions

No files matched your search

-1
View File
@@ -3,7 +3,6 @@
.gitattributes
.github/
output/
toolchain/
installer/output/
*.bin
html_data.c
+7 -35
View File
@@ -5,11 +5,6 @@ on:
branches: ['**']
pull_request:
permissions:
contents: read
pull-requests: write
issues: write
jobs:
build:
runs-on: ubuntu-latest
@@ -18,41 +13,18 @@ jobs:
steps:
- uses: actions/checkout@v6
with:
path: branch
- uses: actions/checkout@v6
with:
path: main
ref: main
- name: Install dependencies
run: |
apt update
apt install make gcc g++ bison flex libboost-dev patch bzip2 curl ca-certificates xxd python-is-python3 libjson-c-dev zlib1g-dev -y
- name: Cache the patched SDCC
uses: actions/cache@v4
with:
path: branch/toolchain
key: sdcc-${{ hashFiles('branch/sdcc/*') }}
apt install make gcc sdcc xxd python-is-python3 libjson-c-dev zlib1g-dev -y
- name: Run the host-side unit tests
run: cd branch && make -C test
- name: Check that every machine definition can be compiled
run: cd branch && make machine_check
run: make -C test
- name: Check if machine.c can be compiled for all machines
run: make machine_check
- name: Make project (4x2/RTL8372)
run: cd branch && make CI=1 MACHINE="KP_9000_6XHML_X2" && cp output/KP_9000_6XHML_X2/rtlplayground.map /tmp/branch.map
run: make CI=1 MACHINE="KP_9000_6XHML_X2"
- name: Make project (8x1/RTL8373)
run: cd branch && make CI=1 MACHINE="KP_9000_9XHML_X_V3_1"
run: make CI=1 MACHINE="KP_9000_9XHML_X_V3_1"
- name: Make project with the health instrumentation
run: cd branch && make clean && make CI=1 MACHINE="KP_9000_6XHML_X2" HEALTH=1
- name: Make main
run: |
ln -s ../branch/toolchain main/toolchain
PATH="$(echo "$PWD"/branch/toolchain/sdcc-*/bin):$PATH"
cd main && make CI=1 MACHINE="KP_9000_6XHML_X2" && cp output/KP_9000_6XHML_X2/rtlplayground.map /tmp/main.map
- name: Calculate bank growth
run: |
sed -rn 's/^([XCD]SEG|(BANK[0-9])).*=\s+([0-9]+)\. bytes.*$/MAIN_\1=\3/p' /tmp/main.map | sort -u >> "$GITHUB_ENV"
sed -rn 's/^([XCD]SEG|(BANK[0-9])).*=\s+([0-9]+)\. bytes.*$/BRANCH_\1=\3/p' /tmp/branch.map | sort -u >> "$GITHUB_ENV"
du -sb main/output/KP_9000_6XHML_X2/html_min | awk '{print "MAIN_WEB="$1}' >> "$GITHUB_ENV"
du -sb branch/output/KP_9000_6XHML_X2/html_min | awk '{print "BRANCH_WEB="$1}' >>"$GITHUB_ENV"
run: make clean && make CI=1 MACHINE="KP_9000_6XHML_X2" HEALTH=1
-4
View File
@@ -1,17 +1,13 @@
.gitignore
.idea/
output/
output-main/
toolchain/
html_data.c
html_data.h
version.h
tools/httpd_sim
tools/injector
tools/fileadder
build/
# host-test stand-ins for the two generated headers
!test/stub/html_data.h
!test/stub/version.h
.vscode
-33
View File
@@ -4,17 +4,6 @@
## Added
- Devices
- Support the hongyavision HYWS-SGT0204S (PCB marking `2G5F4_10G2_V1.01`), an
RTL8372 based 4x2.5G + 2xSFP+ unmanaged switch with a 512 KB Bright Moon
`T25S40` flash. It is a sibling of the KP-9000-6XH-X2 (2M-PCB43-V1.1) with
the two SFP+ cages wired the other way round (physical 5 is on SDS1,
physical 6 on SDS0), no reset button fitted, wrong silkscreen labels on the
serial header, `LED_GLB_IO_EN` bit 6 has to be set for the port LEDs to
work, and the fitted T25S40 cannot be read in DIO mode on this board, so the
target selects single-IO flash reads (`flash_sio`). New machine target
`MACHINE_2G5F4_10G2_V1_01`, see `doc/devices/2G5F4_10G2_V1.01.md`.
- Web UI
- Compress the embedded assets (minify + gzip) and switch to a single-page layout. #315
- Replace the multi-page UI with a themed single-page app: light, dark and Selenized themes following the browser by default,
@@ -25,34 +14,12 @@
## Fixed
- Time
- `rtl8372_init()` sets SDS 0 to SGMII as the stock firmware does. The masks were 0, so on RTL8372
boards without 10G ports both SerDes stayed off, and with SDS 0 off `RTL837X_REG_SEC_COUNTER`
does not hold a written value: NTP never retried after a failed first attempt at boot and
froze the clock after a sync.
- Web UI
- Firefox no longer fills a saved login into the new admin password fields. It also put the
user name into the NTP poll interval, which marked the Time card as edited, so it kept its
defaults (NTP off, no server, UTC-12:00) instead of the switch's settings.
- Accept firmware uploads regardless of the file part's MIME type, including
`application/macbinary` sent by Chrome on macOS for `.bin` files.
- Health
- `HEALTH=1` builds no longer write past the phase timing arrays at the LLDP checkpoint, which
corrupted the `link` slow counter and the DHCP client state. #658
- Config
- A startup configuration line longer than the command buffer no longer stops the replay:
the offending line is skipped and the following lines are still applied.
- The web UI and the configuration upload now refuse a line the replay cannot take,
instead of writing it to flash.
- Startup
- Initialised and zero-initialised globals above xdata 0x4000 now get their values at boot.
Hardware reset leaves SFR 0x97 at 1, which maps xdata 0x4000-0xbfff to the flash image,
and it was cleared only in `main()`, after the C startup code had copied and cleared them.
This showed as a random web session timeout and LLDP blocked on some ports without config.
## Breaking changes
- Config
+1 -8
View File
@@ -3,14 +3,7 @@ FROM debian:13-slim
RUN apt-get update && apt-get install -y \
make \
gcc \
g++ \
bison \
flex \
libboost-dev \
patch \
bzip2 \
curl \
ca-certificates \
sdcc \
xxd \
python3 \
libjson-c-dev \
+38 -141
View File
@@ -4,39 +4,20 @@ DEFAULT_CONFIG_LOCATION = 454656
CONFIG_LOCATION = 458752
HTML_LOCATION = 262144
# Startup configuration embedded in the image. Override it to build a specific
# machine with its own configuration: make MACHINE=<name> CONFIG=<file>
CONFIG ?= config.txt
SDCC_TOOLCHAIN = sdcc-$(shell cat sdcc/* | cksum | cut -d' ' -f1)
SDCC_PREFIX = toolchain/$(SDCC_TOOLCHAIN)
ifeq ($(origin CC),default)
SDCC = $(SDCC_PREFIX)/bin/sdcc
CC = $(SDCC)
ASM = $(SDCC_PREFIX)/bin/sdas8051
SDCC_REV := $(shell git log -1 --format=%h -- sdcc 2>/dev/null)
ifneq ($(SDCC_REV),)
ifneq ($(shell git status --porcelain --untracked-files=no -- sdcc 2>/dev/null),)
SDCC_REV := $(SDCC_REV)-dirty
CC = sdcc
endif
endif
SDCC_BANNER = $(if $(SDCC_REV),sdcc/ at $(SDCC_REV),$(SDCC_TOOLCHAIN)) ($(words $(wildcard sdcc/*.patch)) patches)
else
SDCC_BANNER = system
endif
CC_FLAGS = -mmcs51 -I. -Imachine -Ihttpd -Iuip
CC_FLAGS = -mmcs51 -I. -Ihttpd -Iuip
ASM ?= sdas8051
AFLAGS= -plosgff
SUBDIRS := tools
SUBDIRSCLEAN=$(addsuffix clean,$(SUBDIRS))
# $MACHINE selects machine/$(MACHINE).c as the board definition and is passed
# on as -DMACHINE_NAME, which machine.h uses to include machine/$(MACHINE).h.
# Board selection itself needs no MACHINE_* symbol for #if chains, so the
# definition is all the compiler has to know.
ifneq ($(MACHINE),)
CC_FLAGS += -DMACHINE_NAME=$(MACHINE)
ifeq ($(MACHINE),)
MACHINE:= $(shell grep "^\s*#define MACHINE_" machine.h | sed "s/^\s*#define MACHINE_//")
else
CC_FLAGS += -DMACHINE_$(MACHINE)
endif
# Health instrumentation and the "health" console command: HEALTH=1 gmake ...
ifneq ($(HEALTH),)
@@ -47,20 +28,7 @@ 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
MACHINE_INIT_DEFAULT := machine/default_machine_init.c
MACHINE_INIT_SRC := $(wildcard machine/$(MACHINE)_init.c)
ifeq ($(MACHINE_INIT_SRC),)
MACHINE_INIT_SRC := $(MACHINE_INIT_DEFAULT)
endif
ifndef BUILDDIR
BUILDDIR = output/$(MACHINE)
endif
CONFIG_STAMP := $(BUILDDIR)/.config
BUILDDIR = output/$(MACHINE)
VERSION_HEADER := version.h
GIT_VERSION := $(shell git rev-parse --short HEAD)
@@ -83,41 +51,18 @@ 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
# $(MACHINE_SRC) and $(CONFIG) are listed here so an unknown $MACHINE or a
# missing config file fails before anything else is built, including the tools/
# subdirectory.
all: create_build_dir $(VERSION_HEADER) $(MACHINE_SRC) $(CONFIG) $(SUBDIRS) $(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin
all: create_build_dir $(VERSION_HEADER) $(SUBDIRS) $(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin
bank_size:
tools/bank_occupancy.sh "$(MACHINE)"
create_build_dir: $(SDCC)
create_build_dir:
mkdir -p "$(BUILDDIR)"
mkdir -p "$(BUILDDIR)/uip"
mkdir -p "$(BUILDDIR)/httpd"
mkdir -p "$(BUILDDIR)/machine"
mkdir -p "$(BUILDDIR)/crypto"
# Every machine is a pair machine/<MACHINE>.c + machine/<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
# Fail with a clear message when CONFIG names a file that does not exist.
$(CONFIG):
@echo "ERROR: CONFIG='$(CONFIG)' does not exist." >&2
@echo " Point CONFIG at a configuration file, or omit it to use config.txt." >&2
@false
# Keep the per-machine definition in first position to fail immediately on an
# invalid $MACHINE value
# Keep machine.c in first position to fail immediately on invalid $MACHINE value
SRCS = \
$(MACHINE_SRC) \
$(MACHINE_INIT_SRC) \
machine.c \
machine_init.c \
cmd_editor.c \
cmd_parser.c \
dhcp.c \
@@ -125,37 +70,25 @@ SRCS = \
rtlplayground.c \
boot.c \
sfp.c \
dns.c \
dns_cli.c \
sessions.c \
ntp.c \
ntp_cli.c \
syslog.c \
udp_apps.c
udp_apps.c \
crypto/chacha20.c
# RTL837x
SRCS += \
rtl837x_bandwidth.c \
rtl837x_cabletest.c \
rtl837x_flash.c \
rtl837x_igmp.c \
rtl837x_init.c \
rtl837x_nic.c \
rtl837x_leds.c \
rtl837x_leds_dump.c \
rtl837x_phy.c \
rtl837x_pins.c\
rtl837x_port.c \
rtl837x_stp.c \
rtl837x_lldp.c \
rtl837x_storm.c \
rtl837x_lacp.c \
rtl837x_trap.c \
rtl837x_stp_cli.c
rtl837x_storm.c
SRCS += \
httpd/httpd.c \
httpd/page_impl.c \
httpd/page_stp.c
httpd/page_impl.c
SRCS += \
uip/timer.c \
uip/uip.c \
@@ -172,31 +105,20 @@ HTML := $(shell find html -name '*.js' -or -name '*.html' -or -name '*.svg' -or
# Minified copy of the web UI sources, used as the fileadder input.
# The raw html/ sources stay untouched for development; the minified
# copy is a build artifact under output/.
HTML_MIN := $(BUILDDIR)/html_min
HTML_MIN_LIST := $(BUILDDIR)/html_min.list
HTML_MIN_STAMP := $(BUILDDIR)/html_min.stamp
HTML_MIN := output/html_min
.PHONY: html_min
html_min: $(HTML_MIN_STAMP)
$(HTML_MIN_LIST): FORCE
@mkdir -p $(BUILDDIR)
@echo $(sort $(HTML)) > $@.tmp
@cmp -s $@.tmp $@ && rm -f $@.tmp || mv $@.tmp $@
$(HTML_MIN_STAMP): $(HTML) $(HTML_MIN_LIST) tools/minify.py $(if $(wildcard $(HTML_MIN)),,FORCE)
html_min: $(HTML)
rm -rf $(HTML_MIN)
mkdir -p $(HTML_MIN)
@for f in $(HTML); do python3 tools/minify.py $$f $(HTML_MIN)/$$(basename $$f) || exit 1; done
@touch $@
html_data.c html_data.h &: $(HTML_MIN_STAMP) | tools
tools/$(BUILDDIR)/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -b BANK1 -z -d $(HTML_MIN) -p html_data
html_data.c html_data.h &: $(HTML) | tools html_min
tools/output/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -b BANK1 -z -d $(HTML_MIN) -p html_data
$(VERSION_HEADER): FORCE
@printf '%s\n' "#ifndef VERSION_H" "#define VERSION_H" \
"#define VERSION_SW \"$(VERSION_EXTENSION)\"" \
"#define BUILD_DATE \"$(BUILD_DATE)\"" \
"#define SDCC_TOOLCHAIN \"$(SDCC_BANNER)\"" \
"#endif" > $(VERSION_HEADER).tmp
@cmp -s $(VERSION_HEADER).tmp $(VERSION_HEADER) \
&& rm -f $(VERSION_HEADER).tmp \
@@ -205,21 +127,15 @@ $(VERSION_HEADER): FORCE
httpd: html_data.h
$(SUBDIRS):
$(MAKE) -C $@ BUILDDIR=$(BUILDDIR)
$(MAKE) -C $@
clean: $(SUBDIRSCLEAN)
-rm -f html_data.c html_data.h $(VERSION_HEADER) $(HTML_MIN_LIST) $(HTML_MIN_STAMP)
-rm -f html_data.c html_data.h $(VERSION_HEADER)
-if [ -d $(BUILDDIR) ]; then find $(BUILDDIR) -type f ! -name "*.bin" -delete; fi
distclean: $(SUBDIRSCLEAN)
-rm -f html_data.c html_data.h $(VERSION_HEADER) $(HTML_MIN_LIST) $(HTML_MIN_STAMP)
-rm -f html_data.c html_data.h $(VERSION_HEADER)
-rm -rf $(BUILDDIR)
-rm -rf toolchain
ifneq ($(SDCC),)
$(SDCC):
sh sdcc/build.sh $(abspath $(SDCC_PREFIX))
endif
$(SUBDIRSCLEAN):
$(MAKE) -C $(@:clean=) clean
@@ -234,12 +150,6 @@ FORCE:
$(CCFLAGS_STAMP): FORCE | create_build_dir
@echo '$(CC_FLAGS)' | cmp -s - $@ 2>/dev/null || echo '$(CC_FLAGS)' > $@
# The same for $(CONFIG): its path is stamped so switching to a config file
# older than the image still rebuilds it, while $(CONFIG) itself as a normal
# prerequisite covers edits to the selected file.
$(CONFIG_STAMP): FORCE | create_build_dir
@echo '$(CONFIG)' | cmp -s - $@ 2>/dev/null || echo '$(CONFIG)' > $@
$(BUILDDIR)/%.rel: %.c $(CCFLAGS_STAMP) | create_build_dir html_data.h
$(CC) -MMD $(CC_FLAGS) -o $@ -c $<
@@ -247,45 +157,32 @@ $(BUILDDIR)/%.rel: %.asm $(CCFLAGS_STAMP) | create_build_dir
${ASM} ${AFLAGS} -o $@ $<
# mv -f $(addprefix $(basename $^), .lst .rel .sym) .
$(BUILDDIR)/rtlplayground.ihx: $(OBJS) $(BUILDDIR)/crtbank.rel $(BUILDDIR)/crc16.rel
$(CC) $(CC_FLAGS) --xram-size 49151 -Wl-bHOME=0x00000 -Wl-bBANK1=0x14000 -Wl-bBANK2=0x24000 -Wl-bBANK3=0x34000 -Wl-bBANK4=0x44000 -Wl-r -o $@ $^
$(BUILDDIR)/rtlplayground.ihx: $(OBJS) $(BUILDDIR)/crtbank.rel $(BUILDDIR)/crc16.rel $(BUILDDIR)/crypto/chacha_8051.rel
$(CC) $(CC_FLAGS) --xram-size 48896 -Wl-bHOME=0x00000 -Wl-bBANK1=0x14000 -Wl-bBANK2=0x24000 -Wl-bBANK3=0x34000 -Wl-r -o $@ $^
$(BUILDDIR)/rtlplayground.img: $(BUILDDIR)/rtlplayground.ihx
objcopy --input-target=ihex -O binary $< $@
$(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin: $(BUILDDIR)/rtlplayground.img $(CONFIG) $(CONFIG_STAMP) | tools
$(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin: $(BUILDDIR)/rtlplayground.img | tools
if [ -e $@ ]; then rm $@; fi
tools/$(BUILDDIR)/imagebuilder -i $(BUILDDIR)/rtlplayground.img $@
tools/$(BUILDDIR)/fileadder -a $(DEFAULT_CONFIG_LOCATION) -s $(IMAGESIZE) -d "$(CONFIG)" $@
tools/$(BUILDDIR)/fileadder -a $(CONFIG_LOCATION) -s $(IMAGESIZE) -d "$(CONFIG)" $@
tools/$(BUILDDIR)/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -z -d $(HTML_MIN) -b BANK1 $@
tools/$(BUILDDIR)/crc_calculator -u $@
ln -sf $(MACHINE)/rtlplayground-$(FILENAME_EXTENSION).bin $(BUILDDIR)/../rtlplayground.bin
tools/output/imagebuilder -i $^ $@
tools/output/fileadder -a $(DEFAULT_CONFIG_LOCATION) -s $(IMAGESIZE) -d config.txt $@
tools/output/fileadder -a $(CONFIG_LOCATION) -s $(IMAGESIZE) -d config.txt $@
tools/output/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -z -d $(HTML_MIN) -b BANK1 $@
tools/output/crc_calculator -u $@
ln -sf $(MACHINE)/rtlplayground-$(FILENAME_EXTENSION).bin output/rtlplayground.bin
.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. A board only counts once both its
# <machine>.c and its <machine>.h are present: the header is what the rest of
# the build includes, so a .c without one is an incomplete board. The _init.c
# files are excluded here: they are the boot hooks, compiled separately below
# (the board's own if it exists, otherwise default_machine_init.c). 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 $(filter $(patsubst machine/%.h,%,$(wildcard machine/*.h)),$(patsubst machine/%.c,%,$(filter-out machine/%_init.c,$(wildcard machine/*.c)))))
MACHINE_LESS_CC_FLAGS = $(filter-out -DMACHINE_%,$(CC_FLAGS))
.PHONY: machine_check
machine_check: $(SDCC)
.PHONY:
machine_check:
@mkdir -p $(BUILDDIR)/tmp
@set -eo pipefail; \
for MACHINE in $(MACHINE_DEFS); \
for MACHINE in `grep -E '^[[:space:]]*(//[[:space:]]*)?#define MACHINE_' machine.h | sed -E 's%^[[:space:]]*(//[[:space:]]*)?#define MACHINE_%%' | awk '{print $$1}' | sort -u`; \
do \
echo "Checking $${MACHINE}"; \
$(CC) $(MACHINE_LESS_CC_FLAGS) -DMACHINE_NAME=$${MACHINE} -MMD -o $(BUILDDIR)/tmp/machine_check -c machine/$${MACHINE}.c; \
INIT=machine/$${MACHINE}_init.c; \
if [ ! -e $$INIT ] && [ ! -L $$INIT ]; then INIT=$(MACHINE_INIT_DEFAULT); fi; \
$(CC) $(MACHINE_LESS_CC_FLAGS) -DMACHINE_NAME=$${MACHINE} -MMD -o $(BUILDDIR)/tmp/machine_check -c $$INIT; \
$(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; \
done
@rm -rf $(BUILDDIR)/tmp
+15 -44
View File
@@ -56,19 +56,12 @@ device simulator is provided, which runs entirely under Linux as a local webserv
## (0) Compiling Requirements
Install the following particular build requisites (Debian 12/13):
Install the following particular build requisites (Debian 12/13), note that Ubuntu 24.04
still has an older version of sdcc, but you will need sdcc version 4.5 for the code to compile:
```
sudo apt install make gcc g++ bison flex libboost-dev patch bzip2 curl xxd python-is-python3 libjson-c-dev zlib1g-dev
sudo apt install make gcc sdcc xxd python-is-python3 libjson-c-dev zlib1g-dev
```
The first `make` downloads SDCC 4.6.0, applies the fixes in `sdcc/` and builds it into
`toolchain/`, which takes a few minutes. Later builds reuse it. The released SDCC 4.5.0
and 4.6.0 miscompile parts of this code, see [SDCC bug #4153](https://sourceforge.net/p/sdcc/bugs/4153/)
and [#4154](https://sourceforge.net/p/sdcc/bugs/4154/); both fixes are in SDCC trunk since
r17039. The third patch is for [#4070](https://sourceforge.net/p/sdcc/bugs/4070/), where the
assembler drops the top byte of a `__sfr32` address list; it is proposed upstream and lets
`SFR_DATA_U32` be used again. To use an SDCC you installed yourself instead, run `make CC=sdcc ...`.
<details>
<summary>If using Docker (click to expand)</summary>
@@ -97,14 +90,6 @@ docker run --rm -v $(pwd):/workspace rtlplayground-dev make MACHINE=DEFAULT_8C_1
The resulting `.bin` file appears in `output/` on your host.
Build the OEM web upgrade bin (after building the firmware bin)
```
docker run --rm -v $(pwd):/workspace rtlplayground-dev make -C installer
```
The resulting `rtlplayground_oem_upgrade.bin` file appears in `installer/output/` on your host.
Build host tools only:
```
@@ -118,36 +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=<name>`, which
selects `machine/<name>.c` (see [Adding a new device](doc/adding-a-device.md)).
immediately. To build for a different machine, pass `MACHINE=...`.
</details>
## (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/<name>.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).
To embed a machine-specific startup configuration, pass `CONFIG`:
```
make MACHINE=DEFAULT_8C_1SFP CONFIG=configs/DEFAULT_8C_1SFP.txt
```
`CONFIG` defaults to `config.txt`; the selected file is embedded in the image
and changing it triggers a rebuild.
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`
@@ -185,9 +158,9 @@ example of compilation console output
RTLPlayground/installer$ make
mkdir -p output
gcc updatebuilder.c -o output/updatebuilder
../toolchain/sdcc-3390765201/bin/sdas8051 -plosgff -o output/crtstart.rel crtstart.asm
../toolchain/sdcc-3390765201/bin/sdcc -mmcs51 --code-loc 0x1000 -o output/installer.rel -c installer.c
../toolchain/sdcc-3390765201/bin/sdcc -mmcs51 -Wl-bHOME=0x1100 -Wl-r -o output/rtlinstaller.ihx output/crtstart.rel output/installer.rel
sdas8051 -plosgff -o output/crtstart.rel crtstart.asm
sdcc -mmcs51 --code-loc 0x1000 -o output/installer.rel -c installer.c
sdcc -mmcs51 -Wl-bHOME=0x1100 -Wl-r -o output/rtlinstaller.ihx output/crtstart.rel output/installer.rel
./output/updatebuilder -i output/rtlinstaller.ihx -o output/rtlplayground_oem_upgrade.bin ../output/rtlplayground.bin
Input file size: 524288
Bytes read: 524288
@@ -213,7 +186,7 @@ Go to "Firmware update" tab, select the correct file.
> [!IMPORTANT]
> If your device already runs RTLPlayground, you must upload the binary file /RTLPlayground/output/rtlplayground_Version_Machine.bin
> If your device is OEM, you must upload the binary file /RTLPlayground/installer/output/rtlplayground_oem_upgrade.bin
> If your device is OEM, you must upload the binary file /RTLPlayground/installer/outputrtlplayground_oem_upgrade.bin
> [!CAUTION]
> Check one more time that your device matches the machine type before flashing.
@@ -380,14 +353,12 @@ 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)
- [IGMP (IP-MC streaming)](doc/igmp.md)
- [SFP+ ports](doc/sfp.md)
- [Trunking aka. port aggregation](doc/link_aggregation.md)
- [LACP (802.3ad link aggregation)](doc/lacp.md)
- [Trunking aka. port aggregation](doc/trunking.md)
- [VLAN](doc/vlan.md)
- [Storm control](doc/storm_control.md)
- [Modifications and Flash replacement](doc/mods.md)
+13 -17
View File
@@ -125,6 +125,18 @@ void early_boot_handle_button(void) __banked
void sds_config(uint8_t sds, uint8_t mode) __banked
{
print_string("sds_config sds: "); print_byte(sds); print_string(", mode: "); print_byte(mode); write_char('\n');
sds_config_mac(sds, mode);
if (mode == SDS_10GR || mode == SDS_QXGMII)
sds_write_v(sds, 0x21, 0x10, 0x4480); // Q002110:6480
else
sds_write_v(sds, 0x21, 0x10, 0x6480); // Q002110:6480
sds_write_v(sds, 0x21, 0x13, 0x0400); // Q002113:0400
sds_write_v(sds, 0x21, 0x18, 0x6d02); // Q002118:6d02
sds_write_v(sds, 0x21, 0x1b, 0x424e); // Q00211b:424e
sds_write_v(sds, 0x21, 0x1d, 0x0002); // Q00211d:0002
sds_write_v(sds, 0x36, 0x1c, 0x1390); // Q00361c:1390
sds_write_v(sds, 0x36, 0x14, 0x003f); // Q003614:003f
uint8_t page = 0;
uint16_t v = 0;
@@ -153,21 +165,6 @@ void sds_config(uint8_t sds, uint8_t mode) __banked
print_string("Error in SDS Mode\n");
return;
}
sds_config_mac(sds, mode);
if (mode == SDS_10GR || mode == SDS_QXGMII)
sds_write_v(sds, 0x21, 0x10, 0x4480); // Q002110:6480
else
sds_write_v(sds, 0x21, 0x10, 0x6480); // Q002110:6480
sds_write_v(sds, 0x21, 0x13, 0x0400); // Q002113:0400
sds_write_v(sds, 0x21, 0x18, 0x6d02); // Q002118:6d02
sds_write_v(sds, 0x21, 0x1b, 0x424e); // Q00211b:424e
sds_write_v(sds, 0x21, 0x1d, 0x0002); // Q00211d:0002
sds_write_v(sds, 0x36, 0x1c, 0x1390); // Q00361c:1390
sds_write_v(sds, 0x36, 0x14, 0x003f); // Q003614:003f
sds_write_v(sds, 0x36, 0x10, v); // Q003610:0200
if (page == 0x2e) { // 10G Fiber / SDS_QXGMII
@@ -260,7 +257,7 @@ void nic_setup(void) __banked
// This sets the size of the RX buffer, the filling level is in 0x7874
// R7848-000004ff
REG_SET(RTL837X_REG_NIC_RXBUFF_RX, RTL837X_NIC_RX_LAST_SLOT);
REG_SET(RTL837X_REG_NIC_RXBUFF_RX, 0x4ff);
// R7844-000007fe
REG_SET(RTL837X_REG_NIC_BUFFSIZE_TX, 0x7fe);
@@ -299,7 +296,6 @@ void nic_setup(void) __banked
// Sequence number of TX packets
tx_seq = 0;
REG_SET(RTL837X_NIC_INT_MSK, NIC_INT_RXIE);
}
+22 -91
View File
@@ -14,24 +14,16 @@
#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"
#include "rtl837x_storm.h"
#include "rtl837x_trap.h"
#include "rtl837x_cabletest.h"
#include "sfp.h"
#include "dhcp.h"
#include "syslog.h"
#include "sessions.h"
#include "dns.h"
#include "ntp.h"
#include "uip/uip.h"
#include "version.h"
#include "machine.h"
#include "rtl837x_leds.h"
#include "phy.h"
#pragma codeseg BANK2
@@ -40,7 +32,6 @@
extern __code const struct machine machine;
extern __xdata bool stp_enabled;
extern __code const uint8_t log_to_phys_port[9];
extern __xdata bool lldp_enabled;
extern volatile __xdata uint32_t ticks;
extern volatile __xdata uint8_t sfr_data[4];
@@ -168,15 +159,10 @@ 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 ((uint8_t)(c - '0') < 10) {
if (c - '0' < 10) {
val |= c - '0';
} else {
c |= 0x20;
@@ -267,7 +253,7 @@ uint8_t cmd_parse_port(uint8_t idx) {
return 0;
port = machine.phys_to_log_port[port];
if (!MACHINE_PORT_VALID(port))
if (port < machine.min_port || port > machine.max_port)
return 0;
atoi_results_u8 = port;
@@ -542,21 +528,11 @@ void parse_lag(void)
goto err;
if (cmd_compare(2, "d")) {
lacp_lag_set(group, 0);
port_lag_members_set(group, 0);
return;
}
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)
@@ -564,15 +540,10 @@ void parse_lag(void)
members |= ((uint16_t)1) << atoi_results_u8;
}
if (lacp_mode) {
lacp_lag_set(group, members);
} else {
lacp_lag_set(group, 0);
port_lag_members_set(group, members);
}
port_lag_members_set(group, members);
return;
err:
cmd_error("lag (show | <1-4> (d | [lacp] <port>...))\n");
cmd_error("lag (show | <1-4> (d | <port>...))\n");
}
@@ -744,9 +715,9 @@ void parse_isolate(void)
}
if (cmd_compare(2, "off")) {
for (uint8_t i = MACHINE_MIN_PORT; i <= MACHINE_MAX_PORT; i++)
for (uint8_t i = machine.min_port; i <= machine.max_port; i++)
members |= ((uint16_t)1) << i;
members |= PMASK_CPU; // CPU-port
members |= 0x200; // CPU-port
port_isolate(port_configured, members);
return;
}
@@ -798,7 +769,7 @@ void parse_ingress(void)
if (!cmd_is_space_or_nul(idx))
goto err;
// Setting mode for all ports at once
for (log_port = MACHINE_MIN_PORT; log_port <= MACHINE_MAX_PORT; log_port++) {
for (log_port = machine.min_port; log_port <= machine.max_port; log_port++) {
if (!port_ingress_filter(log_port, mode)) {
cmd_error("cannot set the ingress filter for port "); print_phys_port(log_port); write_char('\n');
return;
@@ -850,7 +821,7 @@ void parse_mirror(void)
print_string("NOT Enabled: ");
}
print_string("Mirroring port: ");
print_phys_port(mPort >> 1);
print_phys_port(mirroring_port);
reg_read_m(RTL837x_MIRROR_CONF);
uint16_t m = sfr_data[0];
m = (m << 8) | sfr_data[1];
@@ -870,14 +841,9 @@ 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);
@@ -887,6 +853,10 @@ 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')
@@ -909,8 +879,6 @@ void parse_port(void)
cmd_error("\nUsage:" \
"\nport <port> [show|on|off]" \
"\nport <port> [10m|100m|1g|2g5|duplex] [half|full]" \
"\nport <port> lldp [block|permit]" \
"\nport <port> cabletest [show]" \
"\nport <port> name [custom port name]\n");
return;
}
@@ -943,21 +911,8 @@ void parse_port(void)
print_string("\nName set to: \"");
print_string_x(port_names[phy_settings.port]);
print_string("\"\n");
} else if (cmd_compare(2, "lldp")) {
if (cmd_compare(3, "block")){
lldp_logical_port_status &= ~((uint16_t)1 << phy_settings.port);
} else if (cmd_compare(3, "permit")) {
lldp_logical_port_status |= (uint16_t)1 << phy_settings.port;
} else {
print_string ("Unknown port <port> lldp [block|permit] command\n");
}
} 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;
@@ -1020,7 +975,7 @@ void parse_mtu(void)
uint8_t p;
if (cmd_compare(1, "show")) {
for (p = MACHINE_MIN_PORT; p <= MACHINE_MAX_PORT; p++) {
for (p = machine.min_port; p <= machine.max_port; p++) {
reg_read_m(RTL8373_REG_MAC_L2_PORT_MAX_LEN + ((uint16_t) p << 8));
uint16_t mtu = SFR_DATA_U16 & 0x3fff;
print_string("Port "); print_phys_port(p);
@@ -1480,9 +1435,6 @@ void parse_passwd(void)
passwd[j++] = c;
} while (c != NUL && j < 20);
passwd[j] = NUL;
/* Logins made with the old password end here. With one session the next
* login pushed a leftover one out; with several it would stay valid. */
session_init();
return;
}
cmd_error("Missing password\n");
@@ -1840,8 +1792,6 @@ void print_gpio_status(void) {
void print_sw_version(void) __banked {
print_string("Software version: " VERSION_SW);
print_string("\nBuild date: " BUILD_DATE);
print_string("\nCompiler: SDCC " STR(__SDCC_VERSION_MAJOR) "." STR(__SDCC_VERSION_MINOR) "." STR(__SDCC_VERSION_PATCH) " #" STR(__SDCC_REVISION));
print_string("\nToolchain: " SDCC_TOOLCHAIN);
print_string("\nHardware: ");
print_string(machine.machine_name);
write_char('\n');
@@ -1904,10 +1854,6 @@ void cmd_parser(void) __banked
parse_mtu();
} else if (cmd_compare(0, "syslog")) {
parse_syslog();
} else if (cmd_compare(0, "dns")) {
dns_parse();
} else if (cmd_compare(0, "ntp") || cmd_compare(0, "time")) {
ntp_parse();
} else if (cmd_compare(0, "ip")) {
if (cmd_compare(1, "dhcp")) {
dhcp_start();
@@ -2008,19 +1954,12 @@ 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 <port>...\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 <= 4094)
&& atoi_short(cmd_words_b[2]) && atoi_results_short && atoi_results_short <= 4094)
port_pvid_set(atoi_results_u8, atoi_results_short);
else {
cmd_error("pvid <port> <0-4094>\n");
cmd_error("pvid <port> <1-4094>\n");
}
} else if (cmd_compare(0, "vlan")) {
parse_vlan();
@@ -2037,8 +1976,6 @@ void cmd_parser(void) __banked
write_char('\n');
} else if (cmd_compare(0, "gpio")) {
print_gpio_status();
} else if (cmd_compare(0, "leds")) {
leds_dump();
} else if (cmd_compare(0, "regget")) {
parse_regget();
} else if (cmd_compare(0, "regset")) {
@@ -2065,6 +2002,11 @@ void cmd_parser(void) __banked
parse_storm();
} else if (cmd_compare(0, "version")) {
print_sw_version();
} else if (cmd_compare(0, "time")) {
print_string(" Tick counter: "); print_long(ticks); print_string(" Sec Counter: ");
reg_read_m(RTL837X_REG_SEC_COUNTER);
print_sfr_data();
write_char('\n');
} else if (cmd_compare(0, "history")) {
__xdata uint16_t p = (cmd_history_ptr + 1) & CMD_HISTORY_MASK;
__xdata uint8_t found_begin = 0;
@@ -2082,16 +2024,6 @@ void cmd_parser(void) __banked
parse_ingress();
} else if (cmd_compare(0, "session")) {
parse_session();
} else if (cmd_compare(0, "lldp")) {
if (cmd_compare(1, "on")) {
print_string("LLDP enabled\n");
lldp_enabled = 1;
trap_rma_set(RTL837X_RMA0E_CONF, RTL837X_RMA_ACT_TRAP);
} else {
print_string("LLDP disabled\n");
lldp_enabled = 0;
trap_rma_set(RTL837X_RMA0E_CONF, RTL837X_RMA_ACT_FORWARD);
}
}
else {
cmd_error("Unknown command\n");
@@ -2161,7 +2093,7 @@ void execute_config(void) __banked
skipping = 1;
}
c = flash_buf[cfg_idx++];
if (c == 0 || c == '\n' || c == '\r') {
if (c == 0 || c == '\n') {
if (skipping) {
skipping = 0;
} else {
@@ -2335,7 +2267,6 @@ void health_show(void) __banked
ph_line("tx ", HEALTH_PH_TX);
ph_line("stp ", HEALTH_PH_STP);
ph_line("cmd ", HEALTH_PH_CMD);
ph_line("lldp", HEALTH_PH_LLDP);
/* Stack: current depth plus how much of the painted area is untouched */
print_string("sp ");
+1 -2
View File
@@ -19,8 +19,7 @@ extern __xdata uint8_t err_status;
#define HEALTH_PH_TX 3
#define HEALTH_PH_STP 4
#define HEALTH_PH_CMD 5
#define HEALTH_PH_LLDP 6
#define HEALTH_PHASES (HEALTH_PH_LLDP + 1) /* last id + 1: sizes the arrays in cmd_parser.c */
#define HEALTH_PHASES 6
extern __xdata uint16_t health_rx_frames;
#define health_rx_frame() health_rx_frames++
+23
View File
@@ -0,0 +1,23 @@
#ifndef _CHACHA_H_
#define _CHACHA_H_
#include <stdint.h>
struct chacha20_t {
uint8_t wstate[64]; // 64 bytes written here
uint8_t constant[16]; // 128 bit constant
uint8_t key[32]; // 256-bit secret key
uint32_t cnt; // 32-bit block counter 1, 2.. Big Endian!
uint8_t nonce[12]; // 96-bit nonce
__xdata uint8_t *plaintext;
uint16_t length;
__xdata uint8_t *cyphertext;
};
// Encrypt a plaintext with ChaCha20 as per RFC7539
void chacha20_encrypt(void);
// Test ChaCha20 using the example from RFC7539
void chacha20_test(void);
#endif
+103
View File
@@ -0,0 +1,103 @@
#include "rtl837x_common.h"
#include "chacha.h"
void chacha_20(void);
void chacha_update(void);
void chacha_count(void);
// generate a block of ChaCha20 keystream as per RFC7539
__xdata struct chacha20_t __at(0xbf00) chacha20;
#ifdef CHACHA20_TEST
__xdata uint8_t plaintext[256];
__xdata uint8_t cyphertext[256];
#endif
#ifdef DEBUG
void chacha20_print_block(void)
{
for (uint8_t i=0; i < 64; i++) {
print_byte(*(uint8_t * __xdata)(chacha20.wstate + i));
if (i%4 == 3)
write_char(' ');
}
}
#endif
void chacha20_encrypt(void)
{
__xdata uint8_t *p = chacha20.plaintext;
while (chacha20.length) {
register uint8_t i;
chacha_count();
memcpy(chacha20.wstate, chacha20.wstate + 64, 64);
#ifdef DEBUG
chacha20_print_block(); write_char('\n');
#endif
chacha_20();
chacha_update();
#ifdef DEBUG
chacha20_print_block(); write_char('\n');
#endif
for (i = 0; i < ((chacha20.length > 64) ? 64 : chacha20.length); i++)
*chacha20.cyphertext++ = *p++ ^ chacha20.wstate[i];
chacha20.length -= chacha20.length > 64 ? 64 : chacha20.length;
#ifdef DEBUG
print_string("\n round done\n");
#endif
};
}
#ifdef CHACHA20_TEST
// Test the example of RFC 7539 Section 2.4.2
void chacha20_test(void)
{
__code uint8_t chacha_c[16] =
{ 0x61, 0x70, 0x78, 0x65, 0x33, 0x20, 0x64, 0x6e,
0x79, 0x62, 0x2d, 0x32, 0x6b, 0x20, 0x65, 0x74 };
__code uint8_t key[32] =
{ 0x03, 0x02, 0x01, 0x00, 0x07, 0x06, 0x05, 0x04,
0x0b, 0x0a, 0x09, 0x08, 0x0f, 0x0e, 0x0d, 0x0c,
0x13, 0x12, 0x11, 0x10, 0x17, 0x16, 0x15, 0x14,
0x1b, 0x1a, 0x19, 0x18, 0x1f, 0x1e, 0x1d, 0x1c };
__code uint8_t nonce[12] = {
0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00 };
__code uint8_t sunscreen[] = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for " \
"the future, sunscreen would be it.";
/*
Ciphertext Sunscreen (from RFC 7539 Section 2.4.2):
000 6e 2e 35 9a 25 68 f9 80 41 ba 07 28 dd 0d 69 81 n.5.%h..A..(..i.
016 e9 7e 7a ec 1d 43 60 c2 0a 27 af cc fd 9f ae 0b .~z..C`..'......
032 f9 1b 65 c5 52 47 33 ab 8f 59 3d ab cd 62 b3 57 ..e.RG3..Y=..b.W
048 16 39 d6 24 e6 51 52 ab 8f 53 0c 35 9f 08 61 d8 .9.$.QR..S.5..a.
064 07 ca 0d bf 50 0d 6a 61 56 a3 8e 08 8a 22 b6 5e ....P.jaV....".^
080 52 bc 51 4d 16 cc f8 06 81 8c e9 1a b7 79 37 36 R.QM.........y76
096 5a f9 0b bf 74 a3 5b e6 b4 0b 8e ed f2 78 5e 42 Z...t.[......x^B
112 87 4d
*/
strcpy(plaintext, sunscreen);
memcpyc(chacha20.constant, chacha_c, 16);
memcpyc(chacha20.key, key, 32);
chacha20.cnt = 0;
memcpyc(chacha20.nonce, nonce, 12);
chacha20.plaintext = plaintext;
chacha20.length = strlen_x(plaintext);
chacha20.cyphertext = cyphertext;
print_string("Encrypting...\n");
chacha20_encrypt();
print_string("Cypertext:\n");
uint16_t len = strlen_x(plaintext);
for (uint8_t i = 0; i < len; i++) {
print_byte(cyphertext[i]); write_char(' ');
}
}
#endif
+455
View File
@@ -0,0 +1,455 @@
; ChaCha Quater-Round implementation in Assembler
.module chacha_8051
; Global variables:
.globl _chacha_20
.globl _chacha_update
.globl _chacha_count
;#define CHACHA_QR(A, B, C, D) { \
; A += B; D ^= A; D = ROTL32(D, 16); \
; C += D; B ^= C; B = ROTL32(B, 12); \
; A += B; D ^= A; D = ROTL32(D, 8); \
; C += D; B ^= C; B = ROTL32(B, 7); \
;}
; r0 r1 r2 r3
; CHACHA_QR( v[ 0], v[ 4], v[ 8], v[12] );
.area HOME (CODE)
store_32:
ar7 = 0x07
ar6 = 0x06
ar5 = 0x05
ar4 = 0x04
ar3 = 0x03
ar2 = 0x02
ar1 = 0x01
ar0 = 0x00
mov a, r7
movx @dptr, a
dec dpl
mov a, r6
movx @dptr, a
dec dpl
mov a, r5
movx @dptr, a
dec dpl
mov a, r4
movx @dptr, a
ret
rol_b:
clr c
mov a, r7
rlc a
mov r7, a
mov a, r6
rlc a
mov r6, a
mov a, r5
rlc a
mov r5, a
mov a, r4
rlc a
mov r4, a
clr a
addc a, r7
mov r7, a
djnz dpl, rol_b
ret
chacha_plus_xor:
; Load A into registers r4-r7, A pointed to by r0
mov dptr, #_chacha20
mov dpl, r0
movx a, @dptr
mov r7, a
dec dpl
movx a, @dptr
mov r6, a
dec dpl
movx a, @dptr
mov r5, a
dec dpl
movx a, @dptr
mov r4, a
; A += B, B pointed to by r1
mov dpl, r1
movx a, @dptr
add a, r7
mov r7, a
dec dpl
movx a, @dptr
addc a, r6
mov r6, a
dec dpl
movx a, @dptr
addc a, r5
mov r5, a
dec dpl
movx a, @dptr
addc a, r4
mov r4, a
mov dpl, r0 ; Store A back
mov a, r7
movx @dptr, a
dec dpl
mov a, r6
movx @dptr, a
dec dpl
mov a, r5
movx @dptr, a
dec dpl
mov a, r4
movx @dptr, a
; D ^= A
mov dpl, r3
movx a, @dptr
xrl a, r7
mov r7, a
dec dpl
movx a, @dptr
xrl a, r6
mov r6, a
dec dpl
movx a, @dptr
xrl a, r5
mov r5, a
dec dpl
movx a, @dptr
xrl a, r4
mov r4, a
dec dpl
ret
chacha_qr:
; QR Part: A += B; D ^= A; D = ROTL32(D, 16);
acall chacha_plus_xor
; rotate left 16. D is r4, r5, r6, r7 -> r6, r7, r4, r5
mov a, r6
xch a, r4
mov r6, a
mov a, r7
xch a, r5
mov r7, a
mov dpl, r3 ; Store D
acall store_32
;QR Part: C += D; B ^= C; B = ROTL32(B, 12);
; Swap A <-> C and D <-> B
mov a, r0
xch a, r2
mov r0, a
mov a, r1
xch a, r3
mov r1, a
acall chacha_plus_xor
; rotate left 12. D is r4, r5, r6, r7 -> r5, r6, r7, r4
mov a, r4
xch a, r7
xch a, r6
xch a, r5
mov r4, a
mov dpl, #4
acall rol_b
mov dpl, r3 ; Store D (being B)
acall store_32
; Swap A <-> C and D <-> B
mov a, r0
xch a, r2
mov r0, a
mov a, r1
xch a, r3
mov r1, a
; QR Part A += B; D ^= A; D = ROTL32(D, 8);
acall chacha_plus_xor
mov a, r4
xch a, r7
xch a, r6
xch a, r5
mov r4, a
mov dpl, r3 ; Store D
acall store_32
; QR Part
; C += D; B ^= C; B = ROTL32(B, 7);
line4:
; Swap A <-> C and D <-> B
mov a, r0
xch a, r2
mov r0, a
mov a, r1
xch a, r3
mov r1, a
acall chacha_plus_xor
; Roll left 7 bits, start by rolling 8 bits left, then roll 1 to the right
mov a, r4
xch a, r7
xch a, r6
xch a, r5
mov r4, a
clr c
mov a, r4
rrc a
mov r4, a
mov a, r5
rrc a
mov r5, a
mov a, r6
rrc a
mov r6, a
mov a, r7
rrc a
mov r7, a
clr a
rrc a
add a, r4
mov r4, a
; Swap A <-> C and D <-> B
mov a, r0
xch a, r2
mov r0, a
mov a, r1
xch a, r3
mov r1, a
mov dpl, r1 ; Store B
acall store_32
ret
;
; Implementation of 20 ChaCha Rounds (10 Double Rounds)
;
_chacha_20:
push acc
push b
push dpl
push dph
push ar7
push ar6
push ar5
push ar4
push ar3
push ar2
push ar1
push ar0
push psw
mov b, #10 ; 10 Double rounds
chacha_20_loop:
; CHACHA_QR( v[ 0], v[ 4], v[ 8], v[12] );
mov r0, #3
mov r1, #19
mov r2, #35
mov r3, #51
acall chacha_qr
; CHACHA_QR( v[ 1], v[ 5], v[ 9], v[13] ); 7 23 39 55
mov r0, #7
mov r1, #23
mov r2, #39
mov r3, #55
acall chacha_qr
; CHACHA_QR( v[ 2], v[ 6], v[10], v[14] ); 11 27 43 59
mov r0, #11
mov r1, #27
mov r2, #43
mov r3, #59
acall chacha_qr
; CHACHA_QR( v[ 3], v[ 7], v[11], v[15] ); 15 31 47 63
mov r0, #15
mov r1, #31
mov r2, #47
mov r3, #63
acall chacha_qr
; CHACHA_QR( v[ 0], v[ 5], v[10], v[15] ); 3 23 43 63
mov r0, #3
mov r1, #23
mov r2, #43
mov r3, #63
acall chacha_qr
; CHACHA_QR( v[ 1], v[ 6], v[11], v[12] ); 7 27 47 51
mov r0, #7
mov r1, #27
mov r2, #47
mov r3, #51
acall chacha_qr
; CHACHA_QR( v[ 2], v[ 7], v[ 8], v[13] ); 11 31 35 55
mov r0, #11
mov r1, #31
mov r2, #35
mov r3, #55
acall chacha_qr
; CHACHA_QR( v[ 3], v[ 4], v[ 9], v[14] ); 15 19 39 59
mov r0, #15
mov r1, #19
mov r2, #39
mov r3, #59
acall chacha_qr
djnz b, chacha_20_loop
pop psw
pop ar0
pop ar1
pop ar2
pop ar3
pop ar4
pop ar5
pop ar6
pop ar7
pop dph
pop dpl
pop b
pop acc
ret
;
; Update ChaCHa state
;
_chacha_update:
push acc
push b
push dpl
push dph
push ar4
push ar3
push ar2
push ar1
push ar0
push psw
mov dptr, #_chacha20
mov b, #16 ; 16 uint32
update_loop:
mov a, b ; multiply by 4 and subtract 1
rl a
rl a
dec a
; Load Working State variable into registers r0-r3
mov dpl, a
orl a, #64 ; Save pointer to state
mov r4, a
movx a, @dptr
mov r3, a
dec dpl
movx a, @dptr
mov r2, a
dec dpl
movx a, @dptr
mov r1, a
dec dpl
movx a, @dptr
mov r0, a
; Add to State variable
mov a, r4
mov dpl, a
movx a, @dptr
add a, r3
mov r3, a
dec dpl
movx a, @dptr
addc a, r2
mov r2, a
dec dpl
movx a, @dptr
addc a, r1
mov r1, a
dec dpl
movx a, @dptr
addc a, r0
mov r0, a
; Store back state variable in LSB, first sequence (serialized)
mov a, r4
xrl a, #64 ; Save pointer to working state
mov dpl, a
mov a, r0
movx @dptr, a
dec dpl
mov a, r1
movx @dptr, a
dec dpl
mov a, r2
movx @dptr, a
dec dpl
mov a, r3
movx @dptr, a
djnz b, update_loop
pop psw
pop ar0
pop ar1
pop ar2
pop ar3
pop ar4
pop dph
pop dpl
pop b
pop acc
ret
;
; Increase counter in state
;
_chacha_count:
push acc
push dpl
push dph
mov dptr, #_chacha20
mov dpl, #112 + 3
movx a, @dptr
inc a
movx @dptr, a
jnz chacha_count_done
dec dpl
movx a, @dptr
inc a
movx @dptr, a
jnz chacha_count_done
dec dpl
movx a, @dptr
inc a
movx @dptr, a
jnz chacha_count_done
dec dpl
movx a, @dptr
inc a
movx @dptr, a
chacha_count_done:
pop dph
pop dpl
pop acc
ret
+1 -1
View File
@@ -51,7 +51,7 @@ __xdata uip_ipaddr_t server;
#define DHCP_END 255
#pragma codeseg BANK3
#pragma constseg BANK3
#pragma constseg BANK2
struct dhcp_pkt {
uint8_t type;
-238
View File
@@ -1,238 +0,0 @@
/*
* DNS resolver for the RTL837x platform
* This code is in the Public Domain
*/
#include <stdint.h>
#include "rtl837x_common.h"
#include "cmd_parser.h"
#include "uip/uip.h"
#include "dhcp.h"
#include "dns.h"
#pragma codeseg BANK3
#pragma constseg BANK3
#define DNS_PORT 53
#define DNS_TRIES 6
#define DNS_TIMEOUT (2 * SYS_TICK_HZ)
extern __xdata struct dhcp_state dhcp_state;
extern volatile __xdata uint32_t ticks;
__xdata struct dns_state dns_state;
__xdata uip_ipaddr_t dns_ip;
uint8_t * __xdata dns_cand[3];
static uint8_t dns_used(__xdata uint8_t *a) __reentrant
{
return a[0] | a[1] | a[2] | a[3];
}
static uint8_t dns_name_is_ip(void) __reentrant
{
__xdata char *s = dns_state.name;
uint8_t i, n;
uint16_t v;
for (i = 0; i < 4; i++) {
v = 0;
n = 0;
while (*s >= '0' && *s <= '9') {
v = v * 10 + (*s++ - '0');
if (++n > 3 || v > 255)
return 0;
}
if (!n)
return 0;
dns_state.addr[i] = v;
if (i < 3 && *s++ != '.')
return 0;
}
return !*s;
}
static void dns_next(void) __reentrant
{
uint8_t k = 0;
if (dns_state.conn) {
uip_udp_remove(dns_state.conn);
dns_state.conn = 0;
}
if (dns_used(dns_state.server[0]))
dns_cand[k++] = dns_state.server[0];
if (dns_used(dns_state.server[1]))
dns_cand[k++] = dns_state.server[1];
if (dns_used(dhcp_state.dns))
dns_cand[k++] = dhcp_state.dns;
if (!k || dns_state.tries >= DNS_TRIES) {
dns_state.status = DNS_FAILED;
if (dns_state.verbose)
print_string(k ? "DNS: no answer\n" : "DNS: no server\n");
return;
}
k = dns_state.tries % k;
uip_ipaddr(dns_ip, dns_cand[k][0], dns_cand[k][1], dns_cand[k][2], dns_cand[k][3]);
dns_state.conn = uip_udp_new(&dns_ip, HTONS(DNS_PORT));
if (!dns_state.conn) {
dns_state.status = DNS_FAILED;
print_string("DNS: no free UDP connection\n");
return;
}
dns_state.send = 1;
}
static void dns_query(void) __reentrant
{
__xdata uint8_t *p = (__xdata uint8_t *)uip_appdata;
__xdata char *n = dns_state.name;
__xdata uint8_t *label;
uint16_t len = 12;
dns_state.id++;
p[0] = dns_state.id >> 8;
p[1] = dns_state.id;
p[2] = 0x01;
p[3] = 0x00;
p[4] = 0;
p[5] = 1;
p[6] = p[7] = p[8] = p[9] = p[10] = p[11] = 0;
while (*n) {
label = p + len++;
*label = 0;
while (*n && *n != '.') {
p[len++] = *n++;
(*label)++;
}
if (*n)
n++;
}
p[len++] = 0;
p[len++] = 0;
p[len++] = 1;
p[len++] = 0;
p[len++] = 1;
uip_udp_send(len);
}
static uint16_t dns_skip_name(__xdata uint8_t *p, uint16_t i, uint16_t len) __reentrant
{
while (i < len) {
if (!p[i])
return i + 1;
if ((p[i] & 0xc0) == 0xc0)
return i + 2;
i += p[i] + 1;
}
return len;
}
static void dns_answer(void) __reentrant
{
__xdata uint8_t *p = (__xdata uint8_t *)uip_appdata;
uint16_t len = uip_datalen();
uint16_t i, an;
if (dns_state.status != DNS_PENDING || len < 12 || !(p[2] & 0x80)
|| p[0] != (uint8_t)(dns_state.id >> 8) || p[1] != (uint8_t)dns_state.id)
return;
an = ((uint16_t)p[6] << 8) | p[7];
i = dns_skip_name(p, 12, len) + 4;
while (!(p[3] & 0x0f) && an-- && i + 10 <= len) {
i = dns_skip_name(p, i, len);
if (i + 10 > len)
break;
if (!p[i] && p[i + 1] == 1 && !p[i + 8] && p[i + 9] == 4 && i + 14 <= len) {
memcpy(dns_state.addr, p + i + 10, 4);
dns_state.status = DNS_DONE;
uip_udp_remove(dns_state.conn);
dns_state.conn = 0;
if (dns_state.verbose) {
print_string("DNS: ");
print_string_x(dns_state.name);
print_string(" is ");
print_ip(dns_state.addr);
write_char('\n');
}
return;
}
i += 10 + (((uint16_t)p[i + 8] << 8) | p[i + 9]);
}
dns_state.tries = DNS_TRIES;
dns_next();
}
void dns_init(void) __banked
{
memset(dns_state.server, 0, sizeof(dns_state.server));
dns_state.name[0] = 0;
dns_state.status = DNS_IDLE;
dns_state.conn = 0;
}
void dns_lookup(void) __banked
{
if (dns_name_is_ip()) {
dns_state.status = DNS_DONE;
return;
}
dns_state.tries = 0;
dns_state.status = DNS_PENDING;
dns_next();
}
void dns_show(void) __banked
{
print_string("DNS servers ");
print_ip(dns_state.server[0]);
write_char(' ');
print_ip(dns_state.server[1]);
print_string(", from DHCP ");
print_ip(dhcp_state.dns);
write_char('\n');
if (!dns_state.name[0])
return;
print_string("Last lookup ");
print_string_x(dns_state.name);
print_string(": ");
if (dns_state.status == DNS_DONE)
print_ip(dns_state.addr);
else if (dns_state.status == DNS_PENDING)
print_string("pending");
else
print_string("failed");
write_char('\n');
}
void dns_callback(uint16_t lport) __banked __reentrant
{
if (!dns_state.conn || lport != dns_state.conn->lport)
return;
if (uip_newdata()) {
dns_answer();
return;
}
if (dns_state.status != DNS_PENDING)
return;
if (dns_state.send) {
dns_state.send = 0;
dns_state.sent = ticks;
dns_query();
return;
}
if (ticks - dns_state.sent >= DNS_TIMEOUT) {
dns_state.tries++;
dns_next();
}
}
-36
View File
@@ -1,36 +0,0 @@
#ifndef _DNS_H_
#define _DNS_H_
#include <stdint.h>
#define DNS_NAME_LEN 48
#define DNS_IDLE 0
#define DNS_PENDING 1
#define DNS_DONE 2
#define DNS_FAILED 3
struct uip_udp_conn;
struct dns_state {
uint8_t server[2][4]; /* configured servers, 0.0.0.0 = unused */
char name[DNS_NAME_LEN]; /* name of the current or last lookup */
uint8_t addr[4]; /* address found by the last lookup */
uint8_t status; /* DNS_* */
uint8_t verbose; /* print the result of the lookup */
uint8_t tries; /* queries sent for the current lookup */
uint8_t send; /* a query is due on the connection */
uint16_t id;
uint32_t sent; /* ticks when the last query went out */
struct uip_udp_conn *conn;
};
extern __xdata struct dns_state dns_state;
void dns_init(void) __banked;
void dns_lookup(void) __banked;
void dns_show(void) __banked;
void dns_callback(uint16_t lport) __banked __reentrant;
void dns_parse(void) __banked __reentrant;
#endif
-63
View File
@@ -1,63 +0,0 @@
/*
* Command line interface of the DNS resolver for the RTL837x platform
* This code is in the Public Domain
*/
#include <stdint.h>
#include "rtl837x_common.h"
#include "cmd_parser.h"
#include "dns.h"
#pragma codeseg BANK2
#pragma constseg BANK2
extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE];
extern __xdata uint8_t cmd_words_len;
extern __xdata uint8_t cmd_words_b[15];
extern __xdata uint8_t ip[4];
uint8_t cmd_compare(uint8_t start, __code const uint8_t * cmd);
uint8_t parse_ip(uint8_t idx);
void dns_parse(void) __banked __reentrant
{
__xdata uint8_t *s;
uint8_t n;
if (cmd_words_len < 2) {
dns_show();
return;
}
if (cmd_compare(1, "server") && cmd_words_len >= 3 && parse_ip(cmd_words_b[2])) {
memcpy(dns_state.server[0], ip, 4);
memset(dns_state.server[1], 0, 4);
if (cmd_words_len >= 4) {
if (!parse_ip(cmd_words_b[3]))
goto err;
memcpy(dns_state.server[1], ip, 4);
}
return;
}
if (cmd_compare(1, "lookup") && cmd_words_len >= 3) {
if (dns_state.status == DNS_PENDING) {
print_string("DNS: a lookup is already running\n");
return;
}
s = &cmd_buffer[cmd_words_b[2]];
for (n = 0; s[n] && s[n] != ' '; n++) {
if (n == DNS_NAME_LEN - 1)
goto err;
dns_state.name[n] = s[n];
}
dns_state.name[n] = 0;
dns_state.verbose = 1;
dns_lookup();
if (dns_state.status == DNS_DONE) {
print_ip(dns_state.addr);
write_char('\n');
}
return;
}
err:
print_string("Error: dns [server <ip> [<ip>]|lookup <name>]\n");
}
-15
View File
@@ -34,21 +34,6 @@ Next, transfer the actual packet over by repeating the SFR function with a
pointer to the frame on the ASIC directly after the frame header and a
length as given by the length in the frame header + 7, again divided by 8.
The RX ring's inclusive last slot is configured as `0x4ff`, giving a byte
range of `0x0000` through `0x27ff`. If the descriptor occupies the last slot
at `0x27f8`, its payload starts at `0x0000`, not `0x2800`. The RX copy code
normalizes that starting address before DMA. It relies on the hardware to
wrap transfers that start inside the ring and cross its end, as observed
on SWTGW218AS. Programming an initial address of `0x2800` does not wrap
and can instead read zeros or stale TX data outside the RX ring.
Frame lengths are checked against `UIP_CONF_BUFFER_SIZE` before copying.
DMA works in whole eight-byte units. `UIP_CONF_BUFFER_ALLOC_SIZE` rounds
the backing storage up to the next complete word: 1,560 bytes for the
1,556-byte logical frame limit. This padding allows a maximum-size frame
to be transferred directly without a separate tail copy. The logical
frame limit and advertised MSS are unchanged.
The received frame will have an RTL proprietary Ethernet frame type of
0x8899 (RRPC) where normally the frame type 0x0800 for IPv4 would be located.
Further 6 bytes follow describing the frame, before the normal IPv4 data
-220
View File
@@ -1,220 +0,0 @@
# Adding a new device
A device is supported by adding one board header `.h` and board definition `.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=<NAME>
```
## What a new device consists of
| File | Purpose |
|------|---------|
| `machine/<NAME>.c` | the `struct machine machine` board definition |
| `machine/<NAME>.h` | `MACHINE_MODEL` (the model string shown in the UI and the CLI) and `MACHINE_CHIPSET` (`RTL8372` or `RTL8373`) |
| `machine/<NAME>_init.c` | optional `machine_custom_init()` boot hook, for registers the machine definition cannot express |
| `doc/devices/<name>.md` + `doc/supported_devices.md` | (only if the board is verified and you want to support it publicly) user-facing documentation |
`<NAME>` 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 board header
`machine/<NAME>.h` carries the product string and the SoC family. 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"
#define MACHINE_CHIPSET RTL8373
#endif
```
`MACHINE_CHIPSET` is `RTL8373` or `RTL8372`; a build without it will fail. It selects the default
logical port range: `MACHINE_MIN_PORT` is `0` for RTL8373 and `3` for RTL8372, while
`MACHINE_MAX_PORT` is `8`, the highest logical port index. A board with a different range defines
`MACHINE_MIN_PORT` and/or `MACHINE_MAX_PORT` in its header to override the
defaults.
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
> 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/<NAME>.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"
__code const struct machine machine = {
MACHINE_COMMON_SETTING,
.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
}
},
};
```
`MACHINE_COMMON_SETTING` (from `machine_defs.h`) initializes `machine_name` and
`isRTL8373` from the header's `MACHINE_MODEL` and `MACHINE_CHIPSET`. The logical
port range comes from `MACHINE_MIN_PORT` and `MACHINE_MAX_PORT` in `machine.h`.
Keep it as the first initializer and list the board-specific fields after it.
### Fields
| Field | Meaning |
|-------|---------|
| `MACHINE_COMMON_SETTING` | expands to `machine_name` (`MACHINE_MODEL`) and `isRTL8373` (`1` for `RTL8373`, `0` for `RTL8372`); both are derived from the header's `MACHINE_CHIPSET` (see Step 1) |
| `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` 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]` | 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 |
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` (pick the version number on
the PCB) with its own `MACHINE_MODEL` and `MACHINE_CHIPSET`: `MACHINE_MODEL` is
what distinguishes the products, and `MACHINE_CHIPSET` must match the SoC of
the board definition being reused.
Existing examples: `machine/PCB_K0402WS_V3.c`, `machine/KP_9000_9XHML_X_V3_2.c`
and the six `KP_9000_*` revision variants.
Two files that differ only in whitespace or comments are duplicates and should
be collapsed this way.
### Optional custom init hook
If the board needs custom initialization, add
`machine/<NAME>_init.c` defining `machine_custom_init()`. The Makefile picks the
file up by name and compiles it.
Boards that need the same hook share one file: keep the code in a single
`machine/<NAME>_init.c` whose `machine/<NAME>.c` is a real file (not a symlink),
and make the other boards' `_init.c` symlinks to it, for example:
```
ln -s KP_9000_6XH_X2_init.c machine/MY_BOARD_1_0_init.c
```
## Step 3: build and verify
```
make MACHINE=MY_BOARD_1_0
```
The generated flash image appears as `output/rtlplayground-<version>-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/<NAME>.c and its optional _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`) 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/<name>.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.
### Identifying the CPU variant
For RTL8373-based devices, use the read-only console command below to distinguish
the N and non-N CPU variants:
```text
regget 4
```
| Register 0x0004 value | CPU |
|---|---|
| `0x83730000` | RTL8373 (non-N) |
| `0x83737000` | RTL8373N |
Record the identified CPU in the device documentation. These values identify
only the CPU variant; document PHY and flash-chip models separately when verified.
-178
View File
@@ -1,178 +0,0 @@
hongyavision HYWS-SGT0204S
RTL8372-based 4×2.5G + 2×SFP+ unmanaged switch.
Also marked on the PCB as `2G5F4_10G2_V1.01`.
Stock firmware runs a factory test console on UART at 9600 baud 8N1 (banner
`RTL8371B start`, prompt `RTL8371B:`). There is no management CLI and no Web
interface.
Using SPI clamp in-board is the only method for initial installation and update.
Erase the whole chip and verify the write (`flashrom -v`) before powering the
board, so an incomplete write is caught before the board is powered up.
Stock flash chip is 512 KB (Bright Moon `T25S40`, 4 Mbit, SOP-8), which the
RTLPlayground image fills exactly.
### Label specifications
Case, top / front / nameplate:
<img src="photos/2G5F4_10G2_V1.01/assembled-top.jpg" width="300" />
<img src="photos/2G5F4_10G2_V1.01/assembled-front.jpg" width="300" />
<img src="photos/2G5F4_10G2_V1.01/assembled-bottom.jpg" width="300" />
- **Manufacturer**: hongyavision (Shenzhen Hongyavision Electronic Technology
Co., Ltd.)
- **Model**: HYWS-SGT0204S — the nameplate is printed in Chinese and reads
"10/100/1000/2500M fibre switch" and "4x 2.5G copper port + 2x 10G SFP+
optical port"
- **Ports**:
- 4 × RJ45: 10/100/1000/2500 Mbps
- 2 × SFP+: 1G / 2.5G / 10G
- **Power**: 12 V DC, 2 A
- **Serial number**: `S/N-YYYYMMDDNNNN`, e.g. `S/N-202312010028`
The top of the case only carries generic printing ("2.5G+10G High Speed Fiber
Transceiver / 4-Port 2.5G Ethernet Switch+10G Fiber"), so the nameplate on the
bottom is what identifies the device.
### What works
- All four 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
- Both SFP+ ports supporting 1G, 2.5G and 10G modules
- Copper LEDs: white (2.5G) and blue (link/activity)
- SFP LEDs: one single LED per port (link/activity)
- SFP EEPROM and DDM readable on both cages, verified with a 10G module
(`Rate: 0x67`, module identity and DDM values via the `sfp` console command)
- Serial console at 115200 8N1 (RTLPlayground) / 9600 8N1 (stock)
### Not verified
- The reset button footprint is unpopulated and the case has no opening for it,
so it could not be tested.
### PCB overview
**Board markings**
- Top silkscreen: `2G5F4_10G2_V1.01`
**Key components**
- Main switch: RTL8372 (RTL8371B family), reported as `RTL8372` (non-N) by
RTLPlayground. The chip sits under a heatsink and its package marking is not
readable.
- U1: SPI NOR flash, Bright Moon `T25S40`, 4 Mbit / 512 KB, SOP-8
- A 3-hole reset button footprint is present but unpopulated, and the case has
no opening for it, so there is no usable reset button
Top side:
<img src="photos/2G5F4_10G2_V1.01/pcb_top.jpg" width="600" />
Bottom:
<img src="photos/2G5F4_10G2_V1.01/pcb_bottom.jpg" width="600" />
The stock flash chip, Bright Moon `T25S40` (SOP-8, 4 Mbit / 512 KB):
<img src="photos/2G5F4_10G2_V1.01/flash_t25s40.jpg" width="300" />
### Port layout
| Front panel position | Logical port | Physical port | Type |
|----------------------|--------------|---------------|---------|
| SFP left | 8 | 5 | SFP+ |
| RJ45 1 | 4 | 1 | Copper |
| RJ45 2 | 5 | 2 | Copper |
| RJ45 3 | 6 | 3 | Copper |
| RJ45 4 | 7 | 4 | Copper |
| SFP right | 3 | 6 | SFP+ |
Copper ports 1–4 match the KP-9000-6XH-X2 (2M-PCB43-V1.1) layout, which this
board is a sibling of. The two SFP+ cages are wired the other way round
compared to that board: physical port 5 is on SDS1 and physical port 6 is on
SDS0.
### Serial console
The board has three through-holes used as the serial console, next to the
heatsink in the middle of the board and visible in the top side photo above (the
silkscreen labels are printed rotated by 90°). **The silkscreen labels are
wrong**: they are shifted relative to the actual signals.
| Silkscreen label | Actual signal |
|------------------|---------------|
| TX | GND |
| RX | TX |
| GND | RX |
Wiring (following the measured signals, not the labels):
| USB-TTL adapter | Board pad |
|-----------------|----------------------|
| GND | pad labelled `TX` |
| RX | pad labelled `RX` |
| TX | pad labelled `GND` |
- **Settings**: 115200 baud / 8N1 / 3.3V TTL (RTLPlayground), 9600 baud for the
stock firmware
- The stock firmware only prints a few bytes and a factory test menu
(`?`, `rst`, `f`, `r`, `10`, `100`, `g1`, `sd`, `eye`, …); it is not an
interactive management console
### LED configuration
Case silkscreen, below the RJ45 ports:
```
White : 2.5G Blue : 1000M/2500M L/A
```
The front panel also carries a `PWR` LED and one LED per SFP cage (`SFP1`,
`SFP2`) on the right hand side.
| Port | LED | Linked, idle | With traffic |
|------------|--------------|--------------|--------------|
| Copper | white | 2.5G only | same |
| Copper | blue | solid | blinks (L/A) |
| SFP | single LED | solid | blinks (L/A) |
Measured LED-ID mapping (from the RTL837x LED set registers):
| LED-ID | Function |
|--------|---------------------------------|
| LED-ID0| copper blue LED |
| LED-ID1| copper white LED |
| LED-ID2| SFP single LED |
**Important**: `RTL837X_REG_LED_GLB_IO_EN` bit 6 must be set in
`machine_custom_init()`. Without it the RTL837x "solid" conditions (LINK/speed)
do not drive the LEDs at all and only `ACT` does, which makes the white LED
glow faintly at non-2.5G speeds.
### SFP GPIO assignments
| SFP | pin\_detect (ModAbs) | pin\_los | SerDes | I2C SDA | I2C SCL |
|-------------------|-------------------------------|------------------|--------|-------------------------|--------------------------|
| Left (logical 8, physical 5) | GPIO30\_ACL\_BIT3\_EN | GPIO37 | SDS1 | GPIO39\_I2C\_SDA4 | GPIO40\_I2C\_SCL3\_MDC1 |
| Right (logical 3, physical 6)| GPIO50\_I2C\_SCL2\_UART1\_TX | GPIO10\_LED10 | SDS0 | GPIO41\_I2C\_SDA3\_MDIO1 | GPIO40\_I2C\_SCL3\_MDC1 |
`pin_tx_disable` is GPIO\_NA on both ports. Both I2C buses are confirmed
working: with the same 10G module inserted, the `sfp` console command reads its
vendor and model from the left cage (`Slot 1`) and from the right cage
(`Slot 2`), DDM values included. The insertion also shows which SerDes drives
each cage - the left cage reports `sds_config sds: 01` and the right cage
`sds: 00`, which matches the table above.
### Machine target
Build for this device with `make MACHINE=2G5F4_10G2_V1_01`, which selects the
`machine/2G5F4_10G2_V1_01.c` board definition. It is derived from
`machine/KP_9000_6XHML_X2.c` with the SFP cage order and the reset pin changed.
The fitted T25S40 cannot be read in DIO mode on this board: an image that leaves
DIO enabled does not come up at all, both at the default read clock divider of 4
and at 8 (two boards tested), while the same image with single-IO boots and runs
normally. The board definition therefore sets `flash_sio`, which makes
`flash_init()` use `CMD_FREAD` at divider 8. Only the management plane is
slower, the ASIC keeps forwarding in hardware.
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
-2
View File
@@ -1,13 +1,11 @@
# MokerLink POE-2G080110GS
The `POE-2G080110GS` is an unmanaged 8x2.5G RJ45 and 1xSFP+ PoE switch sold by Mokerlink. There is also a `POE-2G080110GSM` managed version of the switch which may be similar but this has not been verified on actual hardware.
The`KP-9000-9XHP-X` is the KeepLink rebrand for same product.
## Brands
| Brand | Type |Managed| PCB | Flash | Chip RTL |
|--------|------------------|-------|------------------|-----------------|---------------|
| Mokerlink | POE-2G080110GS | No | 2M-PCB51-ML-V1.1 | 4MB (W25Q32JV) | 8373N + 8224N |
| KeepLink | KP-9000-9XHP-X | No | 2M-PCB51-ML-V1.1 | 4MB (W25Q32JV) | 8373N + 8224N |
## Hardware overview
Front
-41
View File
@@ -1,41 +0,0 @@
# Hasivo S1100P-8GT-1SX-S
RTL8373N switch with 8 × 2.5G RJ45 ports and 1 × SFP port. GD25Q16 2 MiB flash.
## Port mapping
| Front port | ASIC MAC | Interface |
| --- | --- | --- |
| 1–8 | 0–7 | RJ45 |
| 9 | 8 | SFP (SDS1) |
## Board signals
| Function | GPIO |
| --- | --- |
| SFP detect | 38 |
| SFP LOS | 37 |
| SFP TX disable | Not connected (`GPIO_NA`) |
| SFP I2C SDA / SCL | 39 / 40 |
| Reset | 54 |
PoE is managed by an RTL8238B and a separate STC8H8K64U controller, not directly by the RTL8373N. RTLPlayground does not control PoE on this board.
## Build
`make MACHINE=HASIVO_S1100P_8GT_1SX_S CI=1`
## Photos
Front panel:
![Front panel](photos/Hasivo-S1100P-8GT-1SX-S/front.jpg)
PCB top:
![PCB top](photos/Hasivo-S1100P-8GT-1SX-S/pcb_top.jpg)
PCB bottom:
![PCB bottom](photos/Hasivo-S1100P-8GT-1SX-S/pcb_bottom.jpg)
-124
View File
@@ -1,124 +0,0 @@
# ONT-S207CW-62TS-SE / Binardat 2G06-04210GSM / Binardat 2G06-04210GS
## Overview
ONT-S207CW-62TS-SE, Binardat 2G06-04210GSM (managed), and Binardat 2G06-04210GS (unmanaged) are **switches with identical hardware**:
- **CPU:** RTL8372N (confirmed via serial: "Detecting CPU: RTL8372N")
- **Flash:** GD25Q128E (16MB)
- **Ports:** 4x 2.5G RJ45 + 2x 10G SFP+
- **Console:** 115200 baud (RTLPlayground firmware) / 9600 baud (stock firmware)
**Note:** These devices are **RTL8372N-based** with **LAN ports mounted upside-down on the PCB**, requiring a custom configuration. The `MACHINE_ONT_S207CW_62TS_SE` definition uses a **custom configuration** with:
- Corrected port mapping (physical ports 1-4 = RJ45, 5-6 = SFP)
- Optimized LED sets for different port wiring (RJ45 Ports 1-2 have reversed LED wiring)
- Custom LED mux configuration
## Device Photos
### Binardat 2G06-04210GSM
**Front View:**
<img src="photos/ONT-S207CW-Binardat-2G06-04210GSM/binardat-2g06-04210gsm-front.jpg" width="400" />
**Back View:**
<img src="photos/ONT-S207CW-Binardat-2G06-04210GSM/binardat-2g06-04210gsm-back.jpg" width="400" />
**PCB View:**
<img src="photos/ONT-S207CW-Binardat-2G06-04210GSM/binardat-2g06-04210gsm-pcb.jpg" width="400" />
*Photos courtesy of [andreas5232](https://github.com/andreas5232) from [emesix/ONT-S207CW-62TS-SE#1](https://github.com/emesix/ONT-S207CW-62TS-SE/issues/1)*
## Hardware Specification
| Feature | Value |
|---|---|
| **CPU** | RTL8372N |
| **Flash** | GD25Q128E (16MB) |
| **RAM** | Integrated in RTL8372N |
| **RJ45 Ports** | 4x 2.5GBase-T (Ports 1-4) |
| **SFP+ Ports** | 2x 10G (Ports 5-6) |
| **Console** | UART (115200 baud for RTLPlayground, 9600 baud for stock) |
| **Stock Web UI IP** | 192.168.2.1 |
| **Loader Mode IP** | 192.168.10.247 |
## Port Layout
```
┌─────────────────────────────────────────────┐
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │RJ45 │ │RJ45 │ │RJ45 │ │RJ45 │ │SFP+ │ │SFP+ │ │
│ │ 1 │ │ 2 │ │ 3 │ │ 4 │ │ 5 │ │ 6 │ │
│ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │
│ │
│ [System LEDs] │
└─────────────────────────────────────────────┘
```
## LED Behavior
### RJ45 Ports (Physical 1-4)
| LED Color | Speed |
|---|---|
| **Green** | 2.5G |
| **Orange** | 1G / 100M / 10M |
| **Off** | No link |
### SFP+ Ports (Physical 5-6)
| LED Color | Speed |
|---|---|
| **Green** | 10G |
| **Orange** | 2.5G / 1G |
| **Off** | No link / No SFP module |
## Port to Logical Mapping
Custom mapping for ONT-S207CW-62TS-SE:
| Physical Port | Logical Port | Type | LED Set |
|---|---|---|---|
| 1 | 7 | RJ45 | 2 |
| 2 | 6 | RJ45 | 2 |
| 3 | 5 | RJ45 | 0 |
| 4 | 4 | RJ45 | 0 |
| 5 | 3 | SFP+ (SDS0) | 3 |
| 6 | 8 | SFP+ (SDS1) | 3 |
**Note:** Ports 1-2 (RJ45) have reversed LED wiring and use LED-Set 2. SFP Ports 5-6 use LED-Set 3 to account for wiring differences.
## Flashing Instructions
See the [README.md](../../README.md) for general flashing instructions.
For compilation, use command `make MACHINE=ONT_S207CW_62TS_SE` to specify the machine.
If you are using managed version of the switch, you can then also compile the installer and flash using GUI - run command `cd installer` and finally `make`.
## Stock Firmware Information
| Property | Value |
|---|---|
| **Default IP** | 192.168.2.1 (ONT) / 192.168.10.247 (Loader Mode) |
| **Default Login** | admin/admin (may vary by OEM) |
| **Console Baud** | 9600 (stock) / 115200 (RTLPlayground) |
| **CPU String** | "RTL8372N" |
| **Loader Mode IP** | 192.168.10.247 |
## Configuration Details
- **CPU:** RTL8372N
- **SFP:** SDS0 on logical port 3 (GPIO37), SDS1 on logical port 8 (GPIO38)
- **Port Mapping:** Physical 1-4 → Logical 7-4, Physical 5-6 → Logical 3,8
- **LED Wiring:** RJ45 Ports 1-2 and SFP Port 6 have reversed LED pins (orange/green swapped)
- **LED Sets:** Set 0 for RJ45 Ports 3-4, Set 1 unused, Set 2 for RJ45 Ports 1-2, Set 3 for SFP Ports 5-6
## Notes
- ONT-S207CW-62TS-SE, Binardat 2G06-04210GSM (managed), and Binardat 2G06-04210GS (unmanaged) are **RTL8372N-based** with **LAN ports mounted upside-down on the PCB**
- Web UI shows correct physical port order (1-2-3-4 for RJ45, 5-6 for SFP)
- Loader Mode always remains accessible at `192.168.10.247` for recovery
- Serial console: 9600 baud (stock) → 115200 baud (RTLPlayground)
## References
- [GitHub Issue: emesix/ONT-S207CW-62TS-SE#1](https://github.com/emesix/ONT-S207CW-62TS-SE/issues/1) - Original photos and discussion
- [PR: logicog/RTLPlayground#465](https://github.com/logicog/RTLPlayground/pull/465) - Feedback and improvements
-93
View File
@@ -1,93 +0,0 @@
# PCB-AFGW SWTG218ANS V2.0.2
RTL8373-based 8×2.5G PoE + 1×10G SFP+ web-managed switch, sold as the
**RealHD PSW8-25MG**.
### Label specifications
- **Manufacturer**: RealHD
- **Model**: PSW8-25MG
- **PCB marking**: `PCB-AFGW SWTG218ANS_V2.0.2` (silkscreened as `PCB-AFGW SWTG218ANS_V2.0.2_23308`)
- **Ports**:
- 8 × RJ45: 10/100/1000/2500 Mbps with PoE
- 1 × SFP+: 1G / 2.5G / 10G (front panel marking `9(10G)`)
- **Power input**: internal AC PSU, `100-240V~ 50/60Hz 2A MAX`, DC output `53.5V 2.8A 150W`
- **Front panel LEDs**: `PWR`, `SYS`, per RJ45 port `Link/Act` and `PoE`, per SFP port 2.5G (green) and 10G (blue)
- **Stock firmware defaults**: web-managed PoE switch
### What works
- All eight 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps (PoE is not configurable via RTLPlayground)
- SFP+ port supporting 1G, 2.5G and 10G modules
- LEDs: green (2.5G) and amber (1G/100M/10M) per RJ45 port with activity, green (1G/2.5G) and blue (10G) on the SFP port, plus the `SYS` LED
### Photos
Front panel:
<img src="photos/PCB-AFGW_SWTG218ANS_V2.0.2/device_front.jpg" width="800" />
Board mounted in the case, with the internal PSU above it:
<img src="photos/PCB-AFGW_SWTG218ANS_V2.0.2/dev_top_w_pcb.jpg" width="800" />
### PCB overview
Top side:
<img src="photos/PCB-AFGW_SWTG218ANS_V2.0.2/pcb_front.jpg" width="800" />
Bottom side:
<img src="photos/PCB-AFGW_SWTG218ANS_V2.0.2/pcb_back.jpg" width="800" />
A 4-pad UART header with `RXD` / `TXD` silkscreened sits next to the right heatsink.
### Port layout
| Logical port | Physical port | Type |
|--------------|---------------|--------|
| 1-8 | 1-8 | RJ45 |
| 9 | 9 | SFP+ |
### LED configuration
RJ45 ports use LED SET0, the SFP+ port uses LED SET1. The board needs the custom
LED mux (`led_mux_custom = 1`), and `RTL837X_REG_LED_GLB_IO_EN` is written to
`0x7f24977f` in `machine_custom_init()` so the LED pins are enabled as wired here.
| SET | LED pin 0 | LED pin 1 |
|------|-----------------------------------------------|-------------------------------------------------------|
| SET0 | Green — lights on 2.5G link with activity | Amber — lights on 1G / 100M / 10M link with activity |
| SET1 | All speeds — lights on any link with activity | 10G — lights on 10G link with activity |
On the front panel this gives one bi-color `Link/Act` LED per RJ45 port (green at
2.5G, amber at 10/100/1000M, blinking with activity) and one `PoE` LED per RJ45
port, which is driven by the PoE hardware and not by the switch. The SFP+ port has
a green 2.5G LED and a blue 10G LED.
### SFP GPIO assignments
| SFP (logical 9) | pin_detect (ModAbs) | pin_los | pin_tx_disable | SerDes | I2C SDA | I2C SCL |
|-----------------|---------------------|---------|----------------|--------|------------------|----------------------|
| SFP | GPIO38 | GPIO_NA | GPIO_NA | SDS1 | GPIO39_I2C_SDA4 | GPIO40_I2C_SCL3_MDC1 |
### Machine configuration
```
make MACHINE=REALHD_PSW8_25MG
```
### Programming
The serial console runs at 8N1 on 115200 baud with the RTLPlayground firmware
(see the UART header note under *PCB overview*). The stock firmware baud rate 57600. The flash chip was 2MB.
### PoE
The 8 RJ45 ports support PoE, powered by the internal 150W AC PSU (DC output
53.5V 2.8A) mounted above the main board.
Note: RTLPlayground does not provide PoE management for this board. The PoE
hardware runs with its own configuration, the same as on the other PoE boards in
this project.
-72
View File
@@ -1,72 +0,0 @@
# PCB-K0402WS-V1.0
This is the first revision of the PCB-K0402WS series of boards; it's very similar to 2.0 in terms of layout/wiring.
The board does have footprints for a VLAN modeswitch at the front and a second flash chip on the bottom of the PCB, but these have been seen to ship unpopulated.
### Brands
* YuanLey YS25-0402 <sup><sub>Note: there are [multiple revisions](PCB-K0402WS-V3.0.md) of the YS25-0402. This first revision lacks the "Vlan" switch cutout on the front of the enclosure.</sub></sup>
### Programming
Use a SOIC-8 clamp to the flash chip (U1 on silkscreen) at the top of the board, leaving it otherwise unpowered.
The board has been seen to ship with a single [BoyaMicro BY25Q16BSSIG](https://www.lcsc.com/datasheet/C382744.pdf) 16Mbit (2MiB) chip, though exact BOM may vary.
<a href="photos/PCB-K0402WS-V1.0/flash.jpg"><img src="photos/PCB-K0402WS-V1.0/flash.jpg" width="300" /></a>
### What works
- All four 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
- Both SFP ports supporting 1G, 2.5G and 10G modules
- LEDs (Power LED is always on)
### PCB overview
#### Board markings
- Top silkscreen: PCB-K0402W-V1.0 / DIP-K0402WS-V1.0
#### Key components
- U1: SPI NOR flash, 2 MiB
- S1: unpopulated slide switch footprint (three through-holes used as serial console)
Front panel
<a href="photos/PCB-K0402WS-V1.0/ports.jpg"><img src="photos/PCB-K0402WS-V1.0/ports.jpg" width="300" /></a>
PCB top
<a href="photos/PCB-K0402WS-V1.0/pcb-top.jpg"><img src="photos/PCB-K0402WS-V1.0/pcb-top.jpg" width="300" /></a>
PCB bottom
<a href="photos/PCB-K0402WS-V1.0/pcb-bottom.jpg"><img src="photos/PCB-K0402WS-V1.0/pcb-bottom.jpg" width="300" /></a>
Bottom and rear
<a href="photos/PCB-K0402WS-V1.0/bottom.jpg"><img src="photos/PCB-K0402WS-V1.0/bottom.jpg" width="300" /></a>
### Power supply
Input power is delivered via a standard DC barrel jack (5.5x2.1mm) with an included 12V 1A adapter.
There is an optional grounding screw next to the DC input.
### Serial console
The PCB has a slide switch footprint (unpopulated) at the front right that is connected to the UART. It is labeled as "S1" on the silkscreen.
Facing the top front (port) side of the board, the pinout from left to right is:
| Position | Signal | GPIO |
|----------|--------|-------------------|
| 1 | RX | GPIO32\_UART0\_RX |
| 2 | GND | N/A |
| 3 | TX | GPIO31\_UART0\_TX |
Standard 0.1" / 2.54mm pin headers will not fit, so wires or clips may be used instead.
The image below shows some oversized clips clumsily attached.
<a href="photos/PCB-K0402WS-V1.0/uart.jpg"><img src="photos/PCB-K0402WS-V1.0/uart.jpg" width="300" /></a>
+3 -8
View File
@@ -19,14 +19,9 @@ of the device just above the big label, might be covered by a QC sticker.
<img src="photos/PCB-K0402WS-V3.0/Ztyuav_Z-QWYT0402_label.jpg" width="300" />
* SICSOLINK SL-G0402F
<img src="photos/PCB-K0402WS-V3.0/SICSOLINK SL-G0402F.jpg" width="300"/>
* YuanLey YS25-0402 <sup><sub>Note: there are [multiple revisions](PCB-K0402WS-V1.0.md) of the YS25-0402. This is the newer revision that has a "Vlan" switch on the front.</sub></sup>
<img src="photos/PCB-K0402WS-V3.0/YuanLey_YS25-0402.jpg" width="300"/>
* Siscolink SL-G0402F
<img src="photos/PCB-K0402WS-V3.0/Siscolink SL-G0402F.jpg" width="300"/>
### Programming
Using SPI clamp in-board is the only method for initial installation.
@@ -48,7 +43,7 @@ For the initial flash (at least with flashrom), the bin file produced by the bui
### PCB overview
**Board markings**
- Top silkscreen: PCB-K0402W-V3.0 / DIP-K0402WS-V3.0
- Top silkscreen: PCB-KO4022W-V3.0 / DIP-KO4022WS-V3.0
Top side
-148
View File
@@ -1,148 +0,0 @@
# Sirivision SR-S25G2109F (S25G1109FP V1.3)
RTL8373 (non-N) with an RTL8224 quad 2.5G PHY: 8 × 2.5G RJ45 + 1 × SFP+,
2 MB flash, no PoE.
## Label specifications
| Item | Value |
|------|-------|
| Manufacturer | Sirivision |
| Model | SR-S25G2109F |
| Ports | 8 × RJ45 10/100/1000/2500 Mbps, 1 × SFP+ 1G/2.5G/10G |
| Power input | 12 V DC, 1.5 A |
| Stock firmware | version 1.0.8.0 |
| Stock defaults | IP 192.168.1.199, user `admin`, password `admin` |
| Serial number | 001008130002003A000005 (also on the PCB barcode sticker) |
The label does not carry a MAC address. After a factory initialisation the
stock firmware uses exactly the values printed on the label.
## PCB silkscreen and naming
Top side: `S25G1109FP V1.3 SIRIVISION`. That model name belongs to an unmanaged
PoE switch; this board omits the PoE section entirely. What is actually shipped
is the label model `SR-S25G2109F`: a simply managed, non-PoE switch. Its stock
firmware provides a web interface, but it responds very slowly.
## CPU, PHY and flash
| Part | Identification |
|------|----------------|
| SoC | RTL8373, non-N: `regget 4` returns `0x83730000` and the boot console prints `Detecting CPU: RTL8373` |
| PHY | RTL8224, reported as `RTL8224 ID: 0x82240000` |
| Flash | GigaDevice 25Q16ESIG (U7), 2 MB, JEDEC id `c8 40 15` |
The two large ICs sit under soldered heatsinks, their markings are not readable.
## Build
```
make MACHINE=S25G1109FP_V1_3 CI=1
```
The resulting image can be uploaded through the web interface (`System` page)
or the stock web UI.
## Port mapping
| Front port | ASIC MAC | Interface |
|------------|----------|-----------|
| 1–8 | 0–7 | RJ45 2.5G |
| 9 | 8 | SFP+ (SDS1) |
## Board signals
| Function | GPIO |
|----------|------|
| SFP detect (ModAbs) | 30, low with a module inserted |
| SFP LOS | 37, high without light |
| SFP TX disable | not connected, `GPIO_NA` |
| SFP I2C | SDA 39 / SCL 40, `I2CBUS(GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1)` |
| Reset button | 54 |
ModAbs and LOS are separate signals here: GPIO30 tells whether a module is
inserted, GPIO37 whether it receives light, so a module is identified even
before the link comes up and loss of signal is reported. GPIO38 and GPIO48,
which the 8+1 reference boards use for ModAbs and for the reset button, never
change on this board; with GPIO38 as ModAbs the firmware believed a module was
permanently present and retried the I2C read forever.
Serial console: the stock firmware runs the loader at 57600 baud and the runtime
at 9600 baud, RTLPlayground uses 115200.
## Front panel
Each copper port has two LEDs, labelled `Link/Act` (left, yellow) and `2.5G`
(right, green). The SFP cage has a single green `Link/Act` LED; next to it are
a `Power` LED and the `Reset` button.
## LED configuration
Copper ports use LED set 0, the SFP port uses LED set 1:
| Set | Function 0 (left / SFP) | Function 1 (right) |
|-----|-------------------------|--------------------|
| SET0 | all speeds, link with activity | 2.5G link |
| SET1 | all speeds, link with activity | not populated |
The board is wired in the stock port order — LED pin `3n+m` belongs to port
`n+1`, LED function `m` — so the mux table is the chip default with a single
change: the SFP LED sits on pin 25 and is driven by LED function 0, while the
default table maps pin 25 to function 1, which is unpopulated in SET1. Without
that change the SFP LED stays dark.
A speed condition alone leaves an LED dark, `LEDS_LINK` has to be set as well.
## Reset button
| Hold time while running | Effect |
|-------------------------|--------|
| less than 3 s | no action |
| 3–10 s | reset the switch chip, the firmware reboots (~20 s of downtime) |
| more than 10 s | additionally restore the default configuration |
The system LED blinks slowly while the button is held. Holding the button
during power-up instead waits for the release and restores the default
configuration when it is released after 10–30 s.
## MAC address
The factory MAC sits at `0x1FC000`, six bytes followed by `0xFF` padding. The
stock value is a locally administered address (`1A:5D:23:47:4D:EB`), which the
firmware rejects, so the device runs with a UUID-derived MAC (`06:…`). A valid
globally administered address stored there is used instead.
## What works
- all eight 2.5GBASE-T ports at 10/100/1000/2500 Mbps
- SFP+ with 10G and 1G modules, including DDM data where the module provides it
- 10G: Finisar FCBG110SD1C05 AOC (no DDM), rate `0x67`, SerDes mode `1a`
- 1G: ETU ESB5312-3SCD20 (DDM), rate `0x0d`, SerDes mode `04`
- LEDs, module insert/remove detection, loss of signal reporting, reset button
## Unverified
- 2.5G SFP modules, none was available for testing
## Photos
Front panel:
![Front panel](photos/S25G1109FP_V1_3/front.jpg)
Label:
![Label](photos/S25G1109FP_V1_3/label.jpg)
PCB top side:
![PCB top side](photos/S25G1109FP_V1_3/pcb_top.jpg)
PCB bottom side:
![PCB bottom side](photos/S25G1109FP_V1_3/pcb_bottom.jpg)
Flash chip, GigaDevice 25Q16ESIG:
![Flash chip](photos/S25G1109FP_V1_3/flash.jpg)
-32
View File
@@ -1,32 +0,0 @@
### SIRIVISION_S25G2206FP_V1_1
## Brands
|Brand|Type|Managed|PCB|Flash|Chip RTL|
|---|---|---|---|---|---|
| YuanLey | YS25-0402M | Yes | SIRIVISION_S25G2206FP_V1_1 | 2M | 8372 |
### Label specifications
- **Name**:
- **Ports**:
- 4 × RJ45: 10/100/1000/2500 Mbps
- 2 × SFP+: 1000 / 2500 / 10000 Mbps
- **Power**: 12V DC, 1A 5525 connector
### What works
The device is fully supported:
- ALL 2.5GBASE-T RJ45 ports work at 10/100/1000/2500 Mbps
- ALL SFP+ ports support 1G, 2.5G and 10G modules
- LEDs work with the same indiciations as the OEM firmware
### PCB overview
**Board markings**
- Top silkscreen: SIRIVISION S25G2206FP V1.1
Top side
<img src="photos/SIRIVISION_S25G2206FP_V1_1/dev_pcb_top.jpg" width="300" />
## Power supply
Input power is delivered via barell plug, `12V 1A` adapter was provided.
+2 -3
View File
@@ -4,8 +4,7 @@
|---------|------------------|-------|------------------|-----------------|---------------|
| Ampcom | SWTG018AS-A V2.0 | No | SWTG018AS-A V2.0 | 2MB | 8273N + 8224N |
| Horaco | HC-SWTGW218AS-A | Yes | SWTG018AS-A V2.0 | 2MB(25Q16JVSIQ) | 8273N + 8224N |
| GoodTop | GT-ST018M | Yes | SWTG018AS-A V2.0 | 2MB | 8273N + 8224N |
| MokerLink | 2G08110GSN | No | SWTG018AS-A V2.0 | 512KB(25Q40) | 8273N + 8224N |
| GoodTop | GT-ST018M | Yes | SWTG018AS-A V2.0 | ? | 8273N + 8224N |
The following is a documentation for the unmanaged switch marked as `SWTG018AS-A V2.0`.
@@ -15,7 +14,7 @@ The original software is running UART on 9600 baud rate.
Using a SOIC clamp in-board is the only method for initial installation.
The board has a single flash chips `BS` with 2M-byte size. The front switch, switches between the two flash chips.
The board has a single flash chips `BS` with 4M-bit size. The front switch, switches between the two flash chips.
These can be programed independently by using said switch - so it is e.g. possible to run the original and new firmware in parallel.
### Label specifications
+3 -7
View File
@@ -2,11 +2,9 @@
It is highly similar to SWTG024AS-V2.0, with the only difference being the GPIO configuration for the SFP port.
## Brands
|Brand |Type |Managed|PCB |Flash |Chip RTL|
|---------|--------------|-------|------------------------------|----------------|--------|
| Horaco | ZX-SG4T2 | No | PCB-SWTG024AS-A-V2.0.1_19650 | 512K (P25D40SH)| 8372N |
| Horaco | HC-SWTG124AS | Yes | PCB-SWTG024AS-A-V2.0.1_19650 | 2M (W25Q16JV)| 8372N |
| GoodTop | GT-ST024M | Yes | PCB-SWTG024AS-A-V2.0.1 | 2M | 8372N |
|Brand|Type|Managed|PCB|Flash|Chip RTL|
|---|---|---|---|---|---|
| Horaco | ZX-SG4T2 | | PCB-SWTG024AS-A-V2.0.1_19650 | P25D40SH | ??? |
### Label specifications
@@ -35,8 +33,6 @@ Bottom
<img src="photos/SWTG024AS-A-V2_0_1_19650/horaco-zx-sg4t2-pcb-bottom.jpg" width="300" />
On Horaco HC-SWTG124AS with board PCB-SWTG024AS-A-V2.0.1_19650 the reset button is soldered at `SW1` (on the top left, GPIO48).
### J1, serial console
| `J1` pin | Signal |
+1 -1
View File
@@ -46,4 +46,4 @@ Note,`R52`、`R53`may not be installed.You need to bridge them using either sold
## Power supply
Input power is delivered via barell plug, `12V 1A` adapter was provided.
Input power is delivered via barell plug, `12V 1A` adapter was provided.
-48
View File
@@ -1,48 +0,0 @@
### SWTG024AS-V2.1
## Brands
|Brand|Type|Managed|PCB|Flash|Chip RTL|
|---|---|---|---|---|---|
| Xikestor | SKS3200-4E2X | Yes | PCB-SWTG024AS-V2.1.0_19023 | 25Q16A | 8372N |
| Solola | SL902-SWTG024AS | Yes | PCB-SWTG024AS-V2.1.0_19023 | | 8372N |
### Label specifications
- **Ports**:
- 4 × RJ45: 10/100/1000/2500 Mbps
- 2 × SFP+: 1000/2500/10000 Mbps
- **Power**: 1A @ 12V DC
#### Xikestore SKS-3200-4E2X label
<img src="photos/SWTG024AS-V2.1.0/label.jpg" width="300" />
### What works
- ALL 2.5GBASE-T RJ45 ports work at 10/100/1000/2500 Mbps
- The SFP+ ports supports 1G, 2.5G and 10G modules.
- LEDs work with the same indiciations as the OEM firmware.
### PCB overview
**Board markings**
- Top silkscreen: PCB-SWTG024AS-V2.1.0
Top side
<img src="photos/SWTG024AS-V2.1.0/pcb-top.jpg" width="300" />
Bottom
<img src="photos/SWTG024AS-V2.1.0/pcb-bottom.jpg" width="300" />
### Serial console
The four-pin through-hole serial header can be seen to the right of the CPU heatsink in the photo above. It is labelled J1, with pin 1 (Indicated by a triangle on the silkscren) at the bottom. the pinout is also on the silkscreen:
| Pin | Label |
|-----|--------|
| 1 | TX |
| 2 | RX |
| 3 | GND |
| 4 | 3V3 |
Adding a header is a few moments work with a soldering iron. Only pins 1-3 are required for serial port use, and the stock firmware outputs at 57600bps at boot.
+1 -1
View File
@@ -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.
+1 -7
View File
@@ -5,15 +5,9 @@
|---|---|---|---|---|---|
| Mokerlink | ZX-SWTGW218AS | Yes| SWTG118AS-V2.0-16029 | 2MB (FM25Q16A)| 8273N + 8224N |
| Sodola | | | | | |
| Horaco | HC-SWTGW218AS | Yes | SWTG118AS-V2.1 | 2MB | 8373 (non-N) |
| Horaco | | | | | |
| XikeStor | SKS3200-8E1X | Yes | SWTG118AS-V2.1-17462 | 2MB (25Q16JVSIQ) | |
All boards in this table use `MACHINE=SWTGW218AS`. The Horaco HC-SWTGW218AS is also sold with the
[SWTG018AS-A V2.0](SWTG018AS_A_V2_0.md) board, which needs a different firmware. Check the marking
on the PCB before flashing.
The PHY and flash-chip models for the tested Horaco units remain unverified.
## Photos
-36
View File
@@ -1,36 +0,0 @@
### HG0402XG V1.1
## Brands
|Brand|Type|Managed|PCB|Flash|Chip RTL|
|---|---|---|---|---|---|
| ZoeRax | HG0402XG | No| HG0402XG V1.1 | 1M (Winbond 25Q) | |
### Label specifications
- **Name**: ZOERAX 2.5G 2SFP 4-Port Gigabit Switch
- **Model**: HG0402XG-EU
- **Ports**:
- 4 × RJ45: 10/100/1000/2500 Mbps
- 2 × SFP+: 1000 / 2500 / 10000 Mbps
<img src="photos/HG0402XG_V1_1/HG0402XG_V1_1_bottom.jpg" width="300" />
## PCB
<img src="photos/HG0402XG_V1_1/HG0402XG_V1_1_top.jpg" width="300" />
<img src="photos/HG0402XG_V1_1/HG0402XG_V1_1_bottom.jpg" width="300" />
Be aware that the UART header is metric, the pitch is 2mm. The JST PH2.0mm header shown was addeded after purchase
The slider switch controls microcontroller U4 in order to configure the VLAN on/off settings. U4 can reset the SoC
and also write into its registers to configure VLAN. With RTLPlayground the slider cannot be used for anything else
than triggering reset. Any change in the position will trigger this reset.
### Installation
Using SPI clamp on-board is the only method for initial installation.
### What works
- All four 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
- SFP ports with various 1G/10G Fiber and Ethernet modules
- LEDs
Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 565 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 546 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 478 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 639 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 944 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 804 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

-23
View File
@@ -1,23 +0,0 @@
# DNS
The switch can resolve host names to IPv4 addresses, for the NTP client and
for anything else that is given a name instead of an address.
```
dns server <ip> [<ip>] # one or two servers, 0.0.0.0 removes them
dns lookup <name> # resolve a name and print the address
dns # show the servers and the last lookup
```
The answer to a lookup arrives after the command has returned, so it is
printed on the serial console and in the syslog. In the console of the web
interface, run `dns` afterwards to see it.
Without a configured server the resolver uses the one handed out by DHCP.
Only address records are looked up; a query goes to the servers in turn,
every two seconds, and gives up after six tries. A name that already is a
dotted address is taken as it is, without a query.
The DNS servers are set in the Network card of the System page as well.
Settings made there or on the console are kept only once the configuration is
saved.
+1 -2
View File
@@ -14,7 +14,6 @@ rx max 00 slow 0x0000
tx max 00 slow 0x0000
stp max 04 slow 0x0001
cmd max 02 slow 0x0001
lldp max 00 slow 0x0000
sp 80 untouched 62
tcp st 00 tmr 00 rtx 00 rto 00 len 0x0000 mss 0x05b4
lport 0x0000 rport 0x0000 rip 0x0000 0x0000
@@ -47,7 +46,7 @@ a flooded CPU port from a stalled loop, which look identical from outside.
## Phases
Each line is one section of the main loop: the link poll, SFP handling,
frame reception, TCP transmission, the STP timers, the LLDP timer and the command dispatch.
frame reception, TCP transmission, the STP timers and the command dispatch.
`max` is the worst time that section has taken, in ticks of 5 ms; `slow`
counts the passes that took two ticks or more. A misbehaving subsystem
names itself here, which is the difference between knowing that the loop is
-572
View File
@@ -1,572 +0,0 @@
# LEDs HOWTO using firmware function tracing
This is a tutorial to show how to figure out the LED configuration for
unmanaged switches (on managed ones one can just print out the registers
from the command line) without using an I2C port on the device together
an I2C-capable microcontroller. You will only need to have a SOIC clamp
and will have to run ghidra to do a bit of reversing.
The additional advantage of this method is that it not only gives the LED
configuration but also the full PHY configuration and SDS configuration e.g.
in case of additional PHYs being used on the device.
We will use the ZoeRax HG0402XG V1.1 as an example device.
The first step is to read out the flash with a SOIC clamp.
Save the image as
`zoerax_hg0402xg.bin`
The next step is to use a decompiler such as ghidra to identify the
functions used to write or read registers, SDS or to the PHY.
<img src="images/ghidra_open.png" width="300" />
First, load the code in BANK0 into ghidra. The type of code is
8051 default big-endian. The code starts at offset 0x0002 in the file
and has length 0x3ffd
<img src="images/ghidra_add_block.png" width="300" />
Add BANK1 into ghidra by using File->Add to program...
and add the code starting at 0x4000 in `zoerax_hg0402xg.bin` with length
0xc000. Repeat for BANK2 (starts at 0x10000) and BANK3 (starts at 0x1c0000).
They both have length 0xc000.
<img src="images/ghidra_search_read_reg.png" width="300" />
Now, find the read_reg function. An example of the read-register function
is given below. Note the critical bit, namely the execution of the
read-function of an ASIC register via an SFR:
```
CODE1::4dc9 75 a0 01 MOV SFR_EXEC_GO,#0x1
```
This sequence of bytes is very specific. Replace 0x01 with 0x03 and you have
the regster write SFR, 0x05 reads the SerDes, 0x07 writes it. 0x09 reads
from the PHY and 0x0b writes to it.
```
**************************************************************
* FUNCTION *
**************************************************************
undefined reg_read()
undefined <UNASSIGNED> <RETURN>
reg_read
CODE1::4da9 ab 07 MOV R3,BANK0_R7 = ??
CODE1::4dab aa 06 MOV R2,BANK0_R6 = ??
CODE1::4dad e4 CLR A
CODE1::4dae ff MOV R7,A
CODE1::4daf fe MOV R6,A
CODE1::4db0 fd MOV R5,A
CODE1::4db1 fc MOV R4,A
CODE1::4db2 90 00 c9 MOV DPTR,#0xc9
CODE1::4db5 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
CODE1::4db8 e4 CLR A
CODE1::4db9 ff MOV R7,A
CODE1::4dba fe MOV R6,A
CODE1::4dbb fd MOV R5,A
CODE1::4dbc fc MOV R4,A
CODE1::4dbd 90 00 c5 MOV DPTR,#0xc5
CODE1::4dc0 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
CODE1::4dc3 ea MOV A,R2
CODE1::4dc4 f5 a2 MOV SFR_A_ADDRH,A = ??
CODE1::4dc6 eb MOV A,R3
CODE1::4dc7 f5 a3 MOV SFR_A_ADDRL,A = ??
CODE1::4dc9 75 a0 01 MOV SFR_EXEC_GO,#0x1 <<<<<<<< Executes READ REG
LAB_CODE1__4dcc XREF[1]: CODE1::4dcf(j)
CODE1::4dcc af a1 MOV R7,SFR_EXEC_STATUS = ??
CODE1::4dce ef MOV A,R7
CODE1::4dcf 70 fb JNZ LAB_CODE1__4dcc
CODE1::4dd1 af a4 MOV R7,DAT_SFR_a4 = ??
CODE1::4dd3 fc MOV R4,A
CODE1::4dd4 fd MOV R5,A
CODE1::4dd5 fe MOV R6,A
CODE1::4dd6 90 00 c9 MOV DPTR,#0xc9
CODE1::4dd9 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
CODE1::4ddc 90 00 c5 MOV DPTR,#0xc5
CODE1::4ddf 12 05 f9 LCALL FUN_CODE_05f9 undefined FUN_CODE_05f9()
CODE1::4de2 c0 00 PUSH BANK0_R0
CODE1::4de4 90 00 c9 MOV DPTR,#0xc9
CODE1::4de7 12 05 dd LCALL read_xmem_32_b undefined read_xmem_32_b()
CODE1::4dea 78 18 MOV R0,#0x18
CODE1::4dec 12 05 86 LCALL FUN_CODE_0586 undefined FUN_CODE_0586()
CODE1::4def d0 00 POP BANK0_R0
CODE1::4df1 12 05 48 LCALL FUN_CODE_0548 undefined FUN_CODE_0548()
CODE1::4df4 90 00 c5 MOV DPTR,#0xc5
CODE1::4df7 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
CODE1::4dfa af a5 MOV R7,DAT_SFR_a5 = ??
CODE1::4dfc e4 CLR A
CODE1::4dfd fc MOV R4,A
CODE1::4dfe fd MOV R5,A
CODE1::4dff fe MOV R6,A
CODE1::4e00 90 00 c9 MOV DPTR,#0xc9
CODE1::4e03 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
CODE1::4e06 90 00 c5 MOV DPTR,#0xc5
CODE1::4e09 12 05 f9 LCALL FUN_CODE_05f9 undefined FUN_CODE_05f9()
CODE1::4e0c c0 00 PUSH BANK0_R0
CODE1::4e0e 90 00 c9 MOV DPTR,#0xc9
CODE1::4e11 12 05 dd LCALL read_xmem_32_b undefined read_xmem_32_b()
CODE1::4e14 78 10 MOV R0,#0x10
CODE1::4e16 12 05 86 LCALL FUN_CODE_0586 undefined FUN_CODE_0586()
CODE1::4e19 d0 00 POP BANK0_R0
CODE1::4e1b 12 05 48 LCALL FUN_CODE_0548 undefined FUN_CODE_0548()
CODE1::4e1e 90 00 c5 MOV DPTR,#0xc5
CODE1::4e21 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
CODE1::4e24 af a6 MOV R7,DAT_SFR_a6 = ??
CODE1::4e26 e4 CLR A
CODE1::4e27 fc MOV R4,A
CODE1::4e28 fd MOV R5,A
CODE1::4e29 fe MOV R6,A
CODE1::4e2a 90 00 c9 MOV DPTR,#0xc9
CODE1::4e2d 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
CODE1::4e30 90 00 c5 MOV DPTR,#0xc5
CODE1::4e33 12 05 f9 LCALL FUN_CODE_05f9 undefined FUN_CODE_05f9()
CODE1::4e36 c0 00 PUSH BANK0_R0
CODE1::4e38 90 00 c9 MOV DPTR,#0xc9
CODE1::4e3b 12 05 dd LCALL read_xmem_32_b undefined read_xmem_32_b()
CODE1::4e3e 78 08 MOV R0,#0x8
CODE1::4e40 12 05 86 LCALL FUN_CODE_0586 undefined FUN_CODE_0586()
CODE1::4e43 d0 00 POP BANK0_R0
CODE1::4e45 12 05 48 LCALL FUN_CODE_0548 undefined FUN_CODE_0548()
CODE1::4e48 90 00 c5 MOV DPTR,#0xc5
CODE1::4e4b 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
CODE1::4e4e af a7 MOV R7,DAT_SFR_a7 = ??
CODE1::4e50 e4 CLR A
CODE1::4e51 fc MOV R4,A
CODE1::4e52 fd MOV R5,A
CODE1::4e53 fe MOV R6,A
CODE1::4e54 90 00 c9 MOV DPTR,#0xc9
CODE1::4e57 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
CODE1::4e5a 90 00 c5 MOV DPTR,#0xc5
CODE1::4e5d 12 05 dd LCALL read_xmem_32_b undefined read_xmem_32_b()
CODE1::4e60 90 00 c9 MOV DPTR,#0xc9
CODE1::4e63 12 05 f9 LCALL FUN_CODE_05f9 undefined FUN_CODE_05f9()
CODE1::4e66 12 05 48 LCALL FUN_CODE_0548 undefined FUN_CODE_0548()
CODE1::4e69 90 00 c5 MOV DPTR,#0xc5
CODE1::4e6c 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
CODE1::4e6f 90 00 c5 MOV DPTR,#0xc5
CODE1::4e72 02 05 dd LJMP read_xmem_32_b undefined read_xmem_32_b()
-- Flow Override: CALL_RETURN (CALL_TERMINATOR)
```
INSERT ghidra_search_read_reg.png
Identify all the read and write function to the HW of the SoC using the find
in memory function.
The next task is to adapt the intercept routines in a special assembler
file. Don't worry, you do not need to understand assembler to do this.
```
; Assembler file to intercept register writes/reads and PHY writes/reads
; assemble with as31
; Compile with
; as31 -Fbyte -Orwintercept_zoerax_hg0402xg.hex rwintercept_zoerax_hg0402xg.asm
; ./injector zoerax_hg0402xg.bin RTLPlayground/rwintercept_zoerax_hg0402xg.hex zoerax_hg0402xg_patched.bin
; Start at 0x3b00, where there is always some space in BANK 0
.equ valptr, 0x199
.equ SFR_DATA_0, 0xa7
.equ SFR_DATA_8, 0xa6
.equ SFR_DATA_16, 0xa5
.equ SFR_DATA_24, 0xa4
; Intercept Register Write
.org 0x4d62
lcall reg_write
nop
; Intercept Register Read
.org 0x4dad
lcall reg_read_head
.org 0x4e6f
lcall reg_read_tail
; Intercept PHY write
.org 0x522c
lcall phy_write
; Intercept PHY read
.org 0x526f
lcall phy_read_head
.org 0x5295
ljmp phy_read_tail
; Intercept SDS write
.org 0x503c
lcall sds_write
nop
; Intercept SDS read
.org 0x5061
lcall sds_read_head
nop
.org 0x507b
ljmp sds_read_tail
;
; BEGIN actual code at unused end of BANK0
;
.org 0x3b00
;
; print the address of a register write. The address is in R6/R7. A and r4 can be scratched
;
reg_write:
mov a, #'R'
acall putc
mov a, r6
acall wbyte
mov a, r7
acall wbyte
mov a, #'-'
acall putc
mov dptr, #valptr
movx a, @dptr
acall wbyte
mov dptr, #valptr + 1
movx a, @dptr
acall wbyte
mov dptr, #valptr + 2
movx a, @dptr
acall wbyte
mov dptr, #valptr + 3
movx a, @dptr
acall wbyte
mov a, #' '
acall putc
mov a, r7
mov r5, a
mov a, r6
mov r4, a
ret
[...]
```
The full assembler file can be found here: XXXXXXXXXX
Once you have assembled the code, inject the bytes into the existing image,
creating a patched version:
```
$ as31 -Fbyte -Orwintercept_zoerax_hg0402xg.hex rwintercept_zoerax_hg0402xg.asm
Begin Pass #1
Begin Pass #2
./injector zoerax_hg0402xg.bin rwintercept_zoerax_hg0402xg.hex zoerax_hg0402xg_patched.bin
Input file size: 1048576
Bytes read:: 1048576
Patched 417 bytes.
```
Now flash `zoerax_hg0402xg_patched.bin` onto device.
Connect a serial adapter and plug in power. You will see:
```
$ picocom -b 9600 /dev/ttyUSB0
picocom v3.1
port is : /dev/ttyUSB0
flowcontrol : none
baudrate is : 9600
parity is : none
databits are : 8
stopbits are : 1
escape is : C-a
local echo is : no
noinit is : no
noreset is : no
hangup is : no
nolock is : no
send_cmd is : sz -vv
receive_cmd is : rz -vv -E
imap is :
omap is :
emap is : crcrlf,delbs,
logfile is : none
initstring : none
exit_after is : not set
exit is : no
Type [C-a] [C-h] to see available commands
Terminal ready
[The printout only starts once the serial peripherial has been set up. Initially its timing is off:]
�.Ӛ�҂���������30-00005555 r6330:00005555 R6330-00005515 [RTL837X_REG_SMI_MAC_TYPE]
[RTL837X_REG_SMI_PORT_POLLING: ]
r6334:00000000 R6334-000000f0
r6454:00000000 R6454-00007000
[Read SoC-Revision]
r000c:00200000 R000c-002a0000 r000c:202a6818 r000c:202a6818 R000c-20206818
p001e.000d:ffff R02f8-0000ffff R02f4-0000fffa P000001.1e00000d:fffa p001e.000d:ffff p001e.000d:ffff
R02f8-0000ffff R02f4-0000fff0 P000001.1e00000d:fff0
[RTL837X_REG_LED_MODE being configured:]
r6520:0021fdb0 R6520-0021e7b0 r6520:0021e7b0 R6520-0021e6b0
[RLDP Leds configured via RTL837X_REG_LED_RLDP_X:]
r65f8:00000018 R65f8-0000001b R65fc-fffff000 r6600:00000000 R6600-0000000f
[RTL837X_REG_LED_GLB_IO_EN: ]
r65dc:5fffff00 R65dc-7fffff00 r65dc:7fffff00 R65dc-77ffff00
[RTL837X_PIN_MUX_0, makes serial go out of sync:]
r7f8c:30000000 R7f8c-30000000 r7f8c:300��.Ӛ�҂���������30-00005555
r6330:00005555 R6330-00005515 r6334:00000000 R6334-000000f0
r6454:00000000 R6454-00007000
[Read SoC-Revision (again)]
r000c:00200000 R000c-002a0000 r000c:202a6818 r000c:202a6818 R000c-20206818
p001e.000d:ffff R02f8-0000ffff R02f4-0000fffa P000001.1e00000d:fffa p001e.000d:ffff p001e.000d:ffff
R02f8-0000ffff R02f4-0000fff0
P000001.1e00000d:fff0
#### LED configuration ########
[RTL837X_REG_LED_MODE being configured (final):]
r6520:0021fdb0 R6520-0021e7b0 r6520:0021e7b0 R6520-0021e6b0
[More RTL837X_REG_LED_RLDP]
r65f8:00000018 R65f8-0000001b R65fc-fffff000 r6600:00000000 R6600-0000000f
[RTL837X_REG_LED_GLB_IO_EN]
r65dc:5fffff00 R65dc-7fffff00 r65dc:7fffff00 R65dc-77ffff00
[RTL837X_PIN_MUX_0
r7f8c:30000000 R7f8c-30000000 r7f8c:30000000 R7f8c-38000000
[RTL837X_LED_PORT_SET_SEL]
r654c:00000000 R654c-00010000
[RTL837X_REG_LED1_0_SET0]
R6548-00000141
[RTL837X_REG_LED3_2_SET0]
r6544:01411000 R6544-01410174
[RTL837X_REG_LED1_0_SET1]
r6540:00440070 R6540-0044017f
[RTL837X_REG_LED3_0_SET1]
r6528:00000000 R6528-000f0000
[SDS 0 being configure:]
r7b20:00000bff R7b20-000003ff r7b20:000003ff R7b20-000003fa r7b20:000003fa R7b20-000003fa
Q002110:4480 Q002113:0400 Q002118:6d02 Q00211b:424e Q00211d:0002 Q00361c:1390
Q003614:003f Q003610:0200 Q002e04:0080 Q002e06:0408 Q002e07:020d Q002e09:0601
Q002e0b:222c Q002e0c:a217 Q002e0d:fe40 Q002e15:f5c1 Q002e16:0443 Q002e1d:abb0
Q000612:5078 Q000706:9401 Q000708:9401 Q00070a:9401 Q00070c:9401 Q001f0b:0003
Q000603:c45c Q00061f:2100 q001f0b:0003 Q001f0b:000f q002000:0000 Q002000:0030
q002000:0030 Q002000:0010 q002000:0010 Q002000:0050 q002000:0050 Q002000:00d0
q002000:00d0 Q002000:0cd0 q002000:0cd0 Q002000:04d0 q002000:04d0 Q002000:04d0
q002000:04d0 Q002000:0cd0 q002000:0cd0 Q002000:00d0 q002000:00d0 Q002000:00d0
q002000:00d0 Q002000:0050 q002000:0050 Q002000:0010 q002000:0010 Q002000:0010
q002000:0010 Q002000:0030 q002000:0030 Q002000:0000 q001f00:0000 Q001f00:000b
q001f00:000b Q001f00:0000
[SDS 1 being configured]
r7b20:000003fa R7b20-000003fa r7b20:000003fa R7b20-0000035a r7b20:0000035a R7b20-0000035a
Q012110:4480 Q012113:0400 Q012118:6d02 Q01211b:424e Q01211d:0002 Q01361c:1390
Q013614:003f Q013610:0200 Q012e04:0080 Q012e06:0408 Q012e07:020d Q012e09:0601
Q012e0b:222c Q012e0c:a217 Q012e0d:fe40 Q012e15:f5c1 Q012e16:0443 Q012e1d:abb0
Q010612:5078 Q010706:9401 Q010708:9401 Q01070a:9401 Q01070c:9401 Q011f0b:0003
Q010603:c45c Q01061f:2100 q011f0b:0003 Q011f0b:000f q012000:0000 Q012000:0030
q012000:0030 Q012000:0010 q012000:0010 Q012000:0050 q012000:0050 Q012000:00d0
q012000:00d0 Q012000:0cd0 q012000:0cd0 Q012000:04d0 q012000:04d0 Q012000:04d0
q012000:04d0 Q012000:0cd0 q012000:0cd0 Q012000:00d0 q012000:00d0 Q012000:00d0
q012000:00d0 Q012000:0050 q012000:0050 Q012000:0010 q012000:0010 Q012000:0010
q012000:0010 Q012000:0030 q012000:0030 Q012000:0000 q011f00:0000 Q011f00:000b
q011f00:000b Q011f00:0000 q012100:4902 Q012100:4906 q013605:0000 Q013605:4000
Q011f02:001f q011f15:0096 q002100:4902 Q002100:4906 q003605:0000 Q003605:4000
Q001f02:001f q001f15:00a6
r0a90:000000f3 R0a90-000000fc P0000f0.1f00a610:2858 r5fd4:0002914a
R5fd4-001a914a r1538:00000e33 R1538-00000e37 r1538:00000e37 R1538-00000e37
r1538:00000e37 R1538-00000f37 r1638:00000e33 R1638-00000e37 r1638:00000e37
R1638-00000e37 r1638:00000e37 R1638-00000f37 r1738:00000e33 R1738-00000e37
r1738:00000e37 R1738-00000e37 r1738:00000e37 R1738-00000f37 r1838:00000e33
R1838-00000e37 r1838:00000e37 R1838-00000e37 r1838:00000e37 R1838-00000f37
r1938:00000e33 R1938-00000e37 r1938:00000e37 R1938-00000e37 r1938:00000e37
R1938-00000f37 r1a38:00000e33 R1a38-00000e37 r1a38:00000e37 R1a38-00000e37
r1a38:00000e37 R1a38-00000f37 r0b7c:000000d8 R0b7c-000000f8 R7124-00001050
R7128-00001050 R712c-00001050 R7130-00001050 R7134-00001050 R7138-00001050
R713c-00001050 R7140-00001050 R7144-00001050 R7148-00001050 r6040:00000030
R6040-00000031 p041f.a436:8195 P000010.1f00a436:0005 p041f.a438:02d4
P000010.1f00a436:801e p041f.a438:0000 p041f.b820:0000 P000010.1f00b820:0010
p041f.b800:00c0 p041f.a436:801e P000010.1f00a436:8023 p041f.a438:0000
P000010.1f00a438:1802 p041f.a436:8025
[PHY with ID 5 being patched] P000010.1f00a436:b82e p041f.a438:0000
P000010.1f00a438:0001 p041f.b820:0010 P000010.1f00b820:0090
P000010.1f00a436:a016 P000010.1f00a438:0000 P000010.1f00a436:a012
P000010.1f00a438:0000 P000010.1f00a436:a014 P000010.1f00a438:1800
P000010.1f00a438:8010 P000010.1f00a438:1800 P000010.1f00a438:8013
P000010.1f00a438:1800 P000010.1f00a438:8026 P000010.1f00a438:1800
P000010.1f00a438:802a P000010.1f00a438:1800 P000010.1f00a438:802e
P000010.1f00a438:1800 P000010.1f00a438:802e P000010.1f00a438:1800
P000010.1f00a438:802e P000010.1f00a438:1800 P000010.1f00a438:802e
...
```
Where:
```
r: read-reg
R: write-reg
q: read SDS
Q: write SDS
p: read PHY
P: write SDS
```
The LED configuration has been marked above. We now write exactly those registers
into the registers of `rtlplayground.c` on startup:
```
[...]
set_sys_led_state(SYS_LED_ON);
// [RTL837X_REG_LED_MODE being configured (final):]
// r6520:0021fdb0 R6520-0021e7b0 r6520:0021e7b0 R6520-0021e6b0
REG_SET(RTL837X_REG_LED_MODE, 0x0021e6b0);
// [More RTL837X_REG_LED_RLDP]
// r65f8:00000018 R65f8-0000001b R65fc-fffff000 r6600:00000000 R6600-0000000f
REG_SET(0x65f8, 0x0000001b);
REG_SET(0x65fc, 0xfffff000);
REG_SET(0x6600, 0x0000000f);
// [RTL837X_REG_LED_GLB_IO_EN]
// r65dc:5fffff00 R65dc-7fffff00 r65dc:7fffff00 R65dc-77ffff00
REG_SET(0x65dc, 0x77ffff00);
// [RTL837X_PIN_MUX_0
// r7f8c:30000000 R7f8c-30000000 r7f8c:30000000 R7f8c-38000000
REG_SET(0x7f8c, 0x38000000);
// [RTL837X_LED_PORT_SET_SEL]
// r654c:00000000 R654c-00010000
REG_SET(0x654c, 0x00010040);
// [RTL837X_REG_LED1_0_SET0]
// R6548-00000141
REG_SET(0x6548, 0x00000141);
// [RTL837X_REG_LED3_2_SET0]
// r6544:01411000 R6544-01410174
REG_SET(0x6544, 0x01410174);
// [RTL837X_REG_LED1_0_SET1]
// r6540:00440070 R6540-0044017f
REG_SET(0x6540, 0x0044017f);
// [RTL837X_REG_LED3_0_SET1]
// r6528:00000000 R6528-000f0000
REG_SET(0x6528, 0x000f0000);
cmd_editor_init();
[...]
```
Create an initial configuraiton for your device by copying over an existing one
for the same device type, i.e. same port-configuraiton and same SoC,
give it a new name. Make sure that the led_mux_custom and led_mux fields are not
used, because our device does not customize the LED muxes.
Compile and flash onto your device.
Now, boot the switch and issue the `leds`command, which will print out:
```
RTL837X_PIN_MUX_0: 0x38000000
RTL837X_REG_LED_GLB_IO_EN: 0x77ffff00
RTL837X_REG_LED1_0_SET0: 0x00000141
RTL837X_REG_LED3_2_SET0: 0x01410174
RTL837X_REG_LED1_0_SET1: 0x0044017f
RTL837X_REG_LED3_2_SET1: 0x18000040
RTL837X_REG_LED1_0_SET2: 0x00000000
RTL837X_REG_LED3_2_SET2: 0x00000000
RTL837X_REG_LED1_0_SET3: 0x00000000
RTL837X_REG_LED3_2_SET3: 0x00000000
RTL837X_REG_LED3_0_SET1: 0x000f0000
RTL837X_REG_LED3_0_SET3: 0x00000000
RTL837X_LED_PORT_SET_SEL: 0x00010040
RTL837X_REG_LED_GLB_MUX_1: 0x3fffffff
RTL837X_REG_LED_GLB_MUX_2: 0x0c3fffff
RTL837X_REG_LED_GLB_MUX_3: 0x1245038d
RTL837X_REG_LED_GLB_MUX_4: 0x19616554
RTL837X_REG_LED_GLB_MUX_5: 0x2079d71a
RTL837X_REG_LED_GLB_MUX_6: 0x000238a1
RTL837X_REG_LED_GLB_ACTIVE: 0x3ffbedff
RTL837X_REG_LED_MODE: 0x0021e6b0
LED pad Configuration:
00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b
3f 3f 3f 3f 3f 3f 3f 3f 0f 0c 0d 0e 10 11 12 14 15 16 18 19 1a 1c 1d 1e 20 21 22 23
LED-set Configuration:
LED-ID 0 1 2 3
SET 0: 000141 000000 000174 000141
SET 1: 0f017f 000044 000040 001800
SET 2: 000000 000000 000000 000000
SET 3: 000000 000000 000000 000000
Port 3: SET 1: ( 2G5 TWO_1G 1G 500M 100M 10M LINK ACT 10G TWO_5G 5G TWO_2G5), ( 1G LINK), ( LINK), ( COL DUPLEX),
Port 4: SET 0: ( 2G5 LINK ACT), (), ( 1G 100M 10M LINK ACT), ( 2G5 LINK ACT),
Port 5: SET 0: ( 2G5 LINK ACT), (), ( 1G 100M 10M LINK ACT), ( 2G5 LINK ACT),
Port 6: SET 0: ( 2G5 LINK ACT), (), ( 1G 100M 10M LINK ACT), ( 2G5 LINK ACT),
Port 7: SET 0: ( 2G5 LINK ACT), (), ( 1G 100M 10M LINK ACT), ( 2G5 LINK ACT),
Port 8: SET 1: ( 2G5 TWO_1G 1G 500M 100M 10M LINK ACT 10G TWO_5G 5G TWO_2G5), ( 1G LINK), ( LINK), ( COL DUPLEX),
```
Together with the GPIO information, this will give you the following machine configuration:
```
_code const struct machine machine = {
.machine_name = "ZOERAX HG0402XG",
.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 = 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 ),
// Right SFP port (6)
.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 ),
.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},
/* On Ethernet: Left led is orange, right green
* 100M and 1G give right led green with activity
* 2.5GBit adds solid left orange LED
* LEDs for the SFP-ports are the same as for the Ethernet ports
*/
.led_sets = { { LEDS_2G5 | LEDS_LINK | LEDS_ACT,
0,
LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT,
LEDS_2G5 | LEDS_LINK | LEDS_ACT
},
{ 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_1G | LEDS_LINK,
LEDS_LINK,
LEDS_COL | LEDS_DUPLEX
}
}
};
```
+1 -5
View File
@@ -73,8 +73,7 @@ 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. `igmp off` calls it as
well; groups learned while IGMP was on stay in the table, but are no longer looked up.
default, as otherwise no IP-MC would be possible in the network.
`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.
@@ -86,9 +85,6 @@ 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.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

+7 -8
View File
@@ -73,14 +73,13 @@ 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
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.
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.
`port_l2mc_set()` writes one such entry. The SMI layout is the L2 multicast
variant of the table entry:
-155
View File
@@ -1,155 +0,0 @@
# LACP (Link Aggregation Control Protocol, IEEE 802.3ad)
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. 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
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
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
The timer values are in units of the LACP timer, which steps every four ticks
of the system clock, 50 times per second:
| Constant | Value | Time |
|----------------------|-------|------|
| `LACP_FAST_PERIODIC` | 50 | 1 s |
| `LACP_SLOW_PERIODIC` | 1500 | 30 s |
| `LACP_SHORT_TIMEOUT` | 200 | 4 s |
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.
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;
void lacp_off(void) __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;
```
`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 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. 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 leaves LACP, 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` (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.
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`.
Further checks:
* `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.
When one slave goes down, the group narrows to the remaining member, and the
slave rejoins after it comes back.
## Known limits
* 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.
-4
View File
@@ -227,7 +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
Dynamic aggregation with LACP is described in [lacp.md](lacp.md).
-33
View File
@@ -1,33 +0,0 @@
# Time
The switch has no battery backed clock. It counts seconds from power on, and
it can take the date and time from an NTP server once the network is up. The
time is lost at every restart and set again at the first synchronisation.
```
ntp server <name|ip> # default pool.ntp.org
ntp interval <1-1440> # minutes between two synchronisations, default 60
ntp timezone <+hh:mm> # offset from UTC, -12:00 to +14:00
ntp dst off|eu|us # daylight saving rule
ntp on
ntp off
ntp # settings and time
time # local date and time
```
The client speaks SNTP version 4 and synchronises at once when turned on,
then after every interval. A server given by name is resolved through the DNS
resolver before every synchronisation. An answer from an unsynchronised
server is ignored, and a failed attempt is repeated after 30 seconds.
The local time is UTC plus the time zone offset, plus one hour while daylight
saving time applies:
* `eu`: from 01:00 UTC on the last Sunday of March to 01:00 UTC on the last
Sunday of October;
* `us`: from 02:00 local time on the second Sunday of March to 02:00 local
time on the first Sunday of November.
The same settings, with the current local time and the last synchronisation,
are in the Time card of the System page. Settings made there or on the
console are kept only once the configuration is saved.
+69 -179
View File
@@ -2,11 +2,9 @@
The switch can take part in a spanning tree (IEEE 802.1D / 802.1w) so that
redundant links between bridges are blocked instead of forming a loop. The
implementation elects a root bridge from the BPDUs it receives, picks the
cheapest path to it as the root port, blocks ports on which a better bridge is
already designated, promotes ports to forwarding once their listen period
expires, drops information a neighbour stopped sending, and blocks a port on
which it sees its own BPDU.
implementation elects a root bridge from the BPDUs it receives, promotes ports
to forwarding once their listen period expires, ages the root out when it goes
silent, and blocks a port on which it sees its own BPDU.
STP can be enabled and controlled via the web interface or the command line,
as follows:
@@ -37,34 +35,54 @@ 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.
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.
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.
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.
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:
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.
* 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.
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 <port>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.
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 <port>t`) never delivers one
to the CPU. `stp_setup()` prints a warning for every STP-enabled port in that
state.
## Link aggregation
A LAG (Link Aggregation Group) is handled like an additional port by the STP
protocol, with its own timers and states. The switch devices support up to 4
LAGs, which are shown alongside the physical ports in the STP status. A group
carries its own path cost, priority, edge and guard settings, and gets its own
row on the Spanning Tree page.
```
stp lag 1 cost 10000 # the group decides, not its members
stp lag 1 edge off
```
LAGs are configured via the `lag` command. Once a port is a member of a LAG it
can no longer be configured individually for STP, so `stp port <n>` on a member
tells you which group to configure instead. Membership is re-read from the
aggregation registers once a second, so a group changing under LACP is picked
up without any coordination between the two.
The switch hardware does not handle STP state for a LAG as a whole. State
changes for the members have to be done by the firmware, updating every member
port, which is possible using a single register write. BPDUs go out through the
lowest member and carry the group's own port id. Losing one member of a live
LAG is not a topology change; the logical port only goes down with its last
link.
Port states live in `RTL837X_MSTP_STATES (0x5310)`, two bits per port:
`00` disabled, `01` blocking, `10` learning, `11` forwarding. A port in
@@ -85,83 +103,19 @@ encodes. All configured values are in seconds:
| `stp fwd <n>` | 15 | 4–30 |
| `stp txhold <n>` | 6 | 1–10 |
802.1D also requires `2 * (fwd - 1) >= maxage >= 2 * (hello + 1)`. A
command typed on the console or sent by the web page that would break it is
refused and the value stays as it was, so change the values in an order that
keeps the rule at every step (for a larger max age raise the forward delay
first). The web page shows the error, and a refused command is not recorded
in the command history, so saving the configuration does not pick it up. Lines of the saved configuration are taken one by one whatever their
order, and `stp on` checks the result: if the rule is broken it warns and
starts with 2/20/15 instead.
The values above are what the switch uses while it is the root. Otherwise it
takes max age and forward delay from the BPDUs on its root port, as 802.1D
requires, and passes them on in its own BPDUs with the message age one second
higher; the hello time stays its own.
A port entering the tree discards for one forward delay, then learns
addresses without forwarding for a second one, and only then forwards (an
edge port skips the wait). In RSTP mode on a point-to-point link
(`p2p auto` or `on`) it can go faster: a designated port that is still
discarding sets the proposal flag, and forwards as soon as the bridge on the
other side answers with an agreement. When the root port receives a proposal,
the switch first puts its other non-edge designated ports back to discarding
(they propose in turn), then forwards the root port and answers with an
agreement; this sync happens once per root port, later proposals are only
answered. An alternate or backup port that receives a proposal does the same
sync once for the information it holds and answers with an agreement in a
BPDU with the alternate role, so the bridge on the other side does not have
to wait out its timers; it stays discarding itself. An agreement is accepted
from a root or an alternate port on the other side. A new root port forwards at once, after every port that was root within
the last forward delay has been put back to discarding; such a recent root
port stays closed, even to an agreement, until that forward delay is over.
A new root port that was a backup port within the last two hello times does
not take the fast path and waits for its timers.
A designated port that hears worse information from a bridge that still
claims to be designated and learning or forwarding on the same link has a
dispute: usually one direction of the link is lost and the other bridge does
not hear this switch's BPDUs. The port goes back to discarding, and every
further such BPDU restarts its listen period, so it does not forward while
the dispute lasts.
With `stp version rstp` each port still talks 802.1D to a neighbour that
does: once a Config BPDU or TCN arrives and the migrate time (3 s since the
port came up or last switched) is over, the port sends Config BPDUs, uses
the 802.1D TC window and reports changes with TCN if it is the root port. An
RST BPDU after the migrate time switches it back, and so does a link that
comes up again or `stp port <n> mcheck`. Any worse information heard on a designated port, disputed or
not, is answered with a BPDU at once instead of at the next hello, so the
other bridge learns sooner that it is not the designated one. Information heard on a port stays
valid for three of the sender's hello times; a BPDU whose message age has
reached its max age is not used at all. When the root port's information
expires the switch moves to the next best port, or reclaims the root role if
there is none.
Among ports offering the same root, the lowest root path cost wins, then the
designated bridge, the designated port ID, and finally this switch's own port
ID. A worse BPDU from a different bridge does not replace what a port holds;
a worse one from the same designated bridge and port does.
The number in a port ID is the one on the front panel, and 101 and up for
the LAGs, so it matches the port labels on both ends of a link.
A port entering the tree spends `fwd` seconds in blocking before it forwards
(an edge port skips the wait). Root information is discarded after `maxage`
seconds without a BPDU, and the switch then reclaims the root role.
## Topology changes
A topology change is detected when a non-edge port enters forwarding. That
port, and every other forwarding non-edge port, then carries the TC flag in
its BPDUs for hello time plus one second (max age plus forward delay with
`stp version stp`), and each of the other ports flushes the addresses it
learned. The root port takes part too: while its TC window runs it sends
BPDUs toward the root, so the change reaches the rest of the network. With
`stp version stp` the root port sends TCN instead, until a Config BPDU with
TCA comes back.
A TC flag received on a forwarding root or designated port is passed on the
same way to the other ports, not back to the port it came in on; a port that
is still discarding or learning drops it. A legacy TCN is acknowledged with
TCA and then treated like a received TC. A port that turns
alternate, loses its link, or is blocked for a loop flushes its own addresses
without raising a topology change, and an edge port never raises one.
A change on a local non-edge port (the link coming or going, a port promoted
to forwarding) flushes the addresses learned on it and sets the TC flag in
our BPDUs for `maxage + fwd` seconds. A TC flag received in a BPDU is passed
on: the switch flushes the other non-edge ports once and keeps the flag in
its own BPDUs until one hello after the last flagged frame, so the
notification crosses the switch instead of dying at it. A legacy TCN is
acknowledged with TCA and then treated like a local change.
## Bridge settings
@@ -169,8 +123,6 @@ without raising a topology change, and an edge port never raises one.
stp prio <0-15> # bridge priority = n * 4096, default 8 (32768)
stp version rstp|stp # RST BPDUs (default) or legacy Config BPDUs
stp hello|maxage|fwd|txhold <seconds>
stp pathcost long|short # automatic port costs, 802.1D-2004 (default) or 802.1D-1998
stp bpdu flood|filter # BPDUs while STP is off: pass them on (default) or drop them
```
The bridge with the lowest priority wins the root election; ties are broken by
@@ -178,17 +130,6 @@ the MAC address. If you do not want this switch to become the root of an
existing network, give it a worse priority than the current root — `stp prio 15`
(61440) is the usual "never me" value.
A port without a configured cost takes it from its link speed. The long
method uses the 802.1D-2004 values (20000 for 1G, 2000 for 10G); the short
one the 802.1D-1998 table for bridges that still use 16-bit costs (100 for
10M, 19 for 100M, 4 for 1G, 2 for 10G, and 3 for 2.5G and 5G, which that
table does not list). All bridges in a network should use the same method.
`stp bpdu filter` keeps BPDUs from being passed between ports while STP is
off, instead of the default flooding that lets a surrounding spanning tree
run through the switch. It takes effect at once when STP is off, otherwise
when STP is turned off.
## Per-port settings
```
@@ -199,7 +140,6 @@ stp port <1-9> prio <0-240> # port priority, steps of 16
stp port <1-9> guard none|bpdu|root
stp port <1-9> filter on|off # neither send nor accept BPDUs
stp port <1-9> p2p auto|on|off
stp port <1-9> mcheck # try RSTP again on a port that fell back to STP
```
**edge** — an edge port forwards immediately and does not trigger a
@@ -216,76 +156,26 @@ claims a better priority.
on the far side reacts badly to them (some unmanaged switches with loop
prevention cut the link) but you still want STP on the rest of the ports.
## Link aggregation
A LAG (Link Aggregation Group) is one port of the tree, with its own roles,
timers and states. The switch supports up to 4 LAGs; each one takes the place
of its member ports in the STP status and gets its own row on the Spanning
Tree page. A group carries its own path cost, priority, edge, guard and
point-to-point settings, and takes the same commands as a port:
```
stp lag <1-4> on|off|mcheck|edge|cost|prio|guard|filter|p2p ...
stp lag 1 cost 10000 # the group decides, not its members
```
LAGs are configured with the `lag` command. Once a port is a member of a LAG
it can no longer be configured on its own for STP, and `stp port <n>` on a
member names the group to configure instead. Membership is read from the
aggregation registers once a second, so a group changing under LACP is picked
up without any coordination between the two; a port that joins or leaves a
group, and the group itself, start over from discarding. The automatic path
cost of a group is the one of its lowest member that has a link.
The hardware keeps no STP state for a LAG as a whole, so every state change is
written to all member ports, in a single register write. BPDUs go out through
the lowest member that has a link and carry the group's own port id, and a
BPDU received on any member belongs to the group. Losing one member of a live
LAG is not a topology change; the group only goes down with its last link.
## Status
The Spanning Tree page shows the elected root (priority and MAC), the path cost
to it, the root port, the topology-change counter and the time since it last
moved, the max age and forward
delay in use (the root's, when this switch is not the root) and, per port, the
live state read from the ASIC, or "No link", together with the configured
options. For a port taking part in the tree it also shows the designated
bridge, port and cost, and whether it talks RSTP or 802.1D to its neighbour;
the Check button runs `stp port <n> mcheck`. The same data is available as
JSON:
to it, the root port, the topology-change counter and, per port, the live state
read from the ASIC together with the configured options. The same data is
available as JSON:
```
GET /stp.json
```
The Counters section of the same page (or `/stpcnt.json`) shows per port the
BPDUs received and sent, those of them that carried the topology change flag
(TCNs included), and the time since the last BPDU arrived. The counters start
from zero with `stp on`, and `stp clear` resets them; the topology change
counter is not reset.
The `stp status` command prints the same view on the serial console. A port
is reported with one of the roles Root, Designated, Alternate, Backup or
Disabled; Disabled covers a port with STP turned off on it, one shut down by
BPDU guard and one without link.
The console's `age[s]` column (previously labelled `bpdu`) is the elapsed
time since the last BPDU, capped at 255 seconds; it is not a receive count.
Use the web counters or `/stpcnt.json` for full receive counts.
An own BPDU received back on another port can temporarily hold its sending
port in discarding, even if that sending port has received no BPDUs itself.
When the hold expires, auto-edge is restored if the sending port has remained
silent. A continuing loop keeps the hold active.
BPDUs shorter than their type allows, judged by the 802.3 length field, are
dropped: 7 bytes for a TCN, 38 for a Config BPDU and 39 for an RST BPDU,
LLC header included.
The `stp status` command prints the same view on the serial console.
## Limitations
* One spanning-tree instance; no MSTP, no per-VLAN trees.
* A backup port, one that hears the BPDUs of another port of this switch,
is blocked and reported as Backup, but keeps sending BPDUs: loop detection
on that segment relies on them.
* No proposal/agreement handshake — an RST-capable neighbour will still
converge, but through the timers rather than the fast transition.
* Port roles are approximated: the root port and designated ports are
distinguished, alternate/backup are not.
* Topology changes propagate away from the root only: nothing is announced
on the root port (no TCN and no BPDUs at all), so bridges upstream rely on
their own detection.
+11 -27
View File
@@ -4,58 +4,42 @@ The following devices have been tested and are fully working:
| Brand | Type | Managed | PCB | Flash | Ports |
|----------|-----------------|---------|---------------------------------------------------------------------------|-------|-------|
| Ampcom | WAM902-SWTG018AS| No | [SWTG018AS-A V2.0](devices/SWTG018AS_A_V2_0.md) | | 8 + 1 |
| Binardat | 2G06-04210GSM | No | [PCB-SWTG024AS-A-2.0.1](devices/ONT-S207CW-62TS-SE.md) | 0.5M | 4 + 2 |
| Davuaz | Da-K6501W | No | [PCB-K0501W-V2.0](devices/K0501W_V2_0.md) | | 5 + 1 |
| 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) | 2M | 8 + 1 |
| GoodTop | GT-ST024M | Yes | [SWTG024AS-A V2.0.1](devices/SWTG024AS-A-V2.0.1_4C_2SFP.md) | 2M | 4 + 2 |
| Guangnianwei | 7018-2.5G | No | [F7008-2.5GPOE-V1.2](devices/F7008-2.5.md) | 8M | 8 + 1 |
| GoodTop | GT-ST018M | Yes | [SWTG018AS-A V2.0](devices/SWTG018AS_A_V2_0.md) | ? | 8 + 1 |
| 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-V2.0](devices/PCB-K0402WS-V2.0.md) | 2M | 4 + 2 |
| 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 |
| Hisource | Hi-K0801WS | No | [PCB-KO801W-V2.0](devices/HI-K0801WS.md) | | 8 + 1 |
| hongyavision | HYWS-SGT0204S | No | [2G5F4_10G2_V1.01](devices/2G5F4_10G2_V1.01.md) | 0.5M | 4 + 2 |
| hongyavision | LG-SG5T1 | No | [PCB-SWTG024AS-V2.0_16895](devices/SWTG024AS-V2.0.md) | 0.5M | 5 + 1 |
| Horaco | HC-SWTG124AS | Yes | [SWTG024AS-A-V2.0.1](devices/SWTG024AS-A-V2.0.1_4C_2SFP.md) | 2M | 4 + 2 |
| Horaco | HC-SWTGW215AS | Yes | [SWTG024AS-A-V2.0.1_19650 5C 1SFP](devices/SWTG024AS-A-V2.0.1_5C_1SFP.md) | ? | 5 + 1 |
| Horaco | HC-SWTGW218AS | Yes | [SWTG018AS-A V2.0](devices/SWTG018AS_A_V2_0.md) | | 8 + 1 |
| Horaco | HC-SWTGW218AS | Yes | [SWTG118AS-V2.1](devices/SWTGW218AS.md) | 2M | 8 + 1 |
| Horaco | ZX310S-4T2XH | Yes | [PCB-SL310S-4T1T1X-V1.0.1-24107](devices/ZX310S-4T2XH.md) | 2M | 5 + 1 |
| Horaco | ZX310S-4T2XT | Yes | [PCB-SL310S-4T2XT-V1.0.0-22273](devices/ZX310S-4T2XT.md) | 2M | 6 |
| Horaco | ZX-SG4T2 | No | [SWTG024AS-A-V2.0.1_19650_4C_2SFP](devices/SWTG024AS-A-V2.0.1_4C_2SFP.md) | 0.5M | 4 + 2 |
| Horaco | ZX-SWTG124AS | Yes | [SWTG024AS-v2.0](devices/SWTG024AS.md) | | 4 + 2 |
| keepLINK | KP-9000-6XH-X2 / KP-9000-6XHML-X2 | No/Yes | [2M-PCB43-V1.2 / V2.1](devices/KP-9000-6XH-X2.md) | | 4 + 2 |
| Keeplink | KP-9000-6XH-X2 / KP-9000-6XHML-X2 | No/Yes | [2M-PCB43-V1.2 / V2.1](devices/KP-9000-6XH-X2.md) | | 4 + 2 |
| keepLINK | KP-9000-9XHML-X | Yes | [2M-PCB23-V2.2](devices/2M-PCB23-V2_2.md) | 2M | 8 + 1 |
| keepLINK | KP-9000-9XHML-X | Yes | [2M-PCB23-V3.1](devices/2M-PCB23-V3_1.md) | 2M | 8 + 1 |
| keepLINK | KP-9000-9XHML-X | Yes | [2M-PCB23-V3.2](devices/2M-PCB23-V3_2.md) | 2M | 8 + 1 |
| keepLINK | KP-9000-9XHP-X | No | [2M-PCB51-ML-V1.1](devices/2M-PCB51-ML-V1_1.md) | 4M | 8 + 1 |
| LIANGUO | HYWS-SGT0108S | No | [2G5F_20G_V1.01](devices/HYWS-SGT0108S.md) | 0.5M | 8 + 1 |
| LIANGUO | SWTG024AS | No | [SWTG024AS-v2.0-17452](devices/SWTG024AS.md) | 0.5M | 4 + 2 |
| LIANGUO | ZX-SWTGW215AS | Yes | [PCB-SWTG115AS-V2.0 / PCB-SWTG115AS-V2.1](devices/SWTGW215AS.md) | 2M | 5 + 1 |
| MokerLink| 2G040210GSM | Yes | [2M-PCB43-V1.1](devices/2M-PCB43-V1.1.md) | | 4 + 2 |
| Lianguo | ZX-SWTGW215AS | Yes | [PCB-SWTG115AS-V2.0 / PCB-SWTG115AS-V2.1](devices/SWTGW215AS.md) | 2M | 5 + 1 |
| Lianguo | HYWS-SGT0108S | No | [2G5F_20G_V1.01](devices/HYWS-SGT0108S.md) | 0.5M | 8 + 1 |
| Mokerlink| 2G040210GSM | Yes | [2M-PCB43-V1.1](devices/2M-PCB43-V1.1.md) | | 4 + 2 |
| Mokerlink| ZX-SWTGW218AS | Yes | [SWTG118AS-V2.0-16029](devices/SWTGW218AS.md) | 2M | 8 + 1 |
| MokerLink| POE-2G080110GS | No | [2M-PCB51-ML-V1.1](devices/2M-PCB51-ML-V1_1.md) | 4M | 8 + 1 |
| MokerLink| ZX-SWTGW218AS | Yes | [SWTG118AS-V2.0-16029](devices/SWTGW218AS.md) | 2M | 8 + 1 |
| MokerLink| 2G08110GSN | No | [SWTG018AS-A V2.0](devices/SWTG018AS_A_V2_0.md) | 0.5M | 8 + 1 |
| Netcore | GS9 | No | [PB-2132](devices/PB-2132.md) | 4M | 8 + 1 |
| ONT | ONT-S207CW-62TS-SE | No | [PCB-SWTG024AS-A-2.0.1](devices/ONT-S207CW-62TS-SE.md) | 0.5M | 4 + 2 |
| RealHD | PSW8-25MG | Yes | [PCB-AFGW SWTG218ANS V2.0.2](devices/PCB-AFGW_SWTG218ANS_V2.0.2.md) | 2M | 8 + 1 |
| Ruiying | RY-4GT-2SX | No | [FG-4GT-2SX_V2.0](devices/FG-4GT-2SX_V2.0.md) | 4M | 4 + 2 |
| Ruiying | RY-8GT-1SX | No | [FG-8GT-1SX](devices/FG-8GT-1SX.md) | 1M | 8 + 1 |
| SICSOLINK| SL-G0402F | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 |
| Sirivision | SR-S25G2109F | Yes | [S25G1109FP V1.3](devices/S25G1109FP_V1_3.md) | 2M | 8 + 1 |
| SiscoLink | SL-G0402F | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 |
| Sodola | SL-SWTG124AS-D | Yes | [SWTG024AS-v2.0-17452](devices/SWTG024AS.md) | 2M | 4 + 2 |
| Sodola | SL902-SWTG024AS | Yes | [SWTG024AS-v2.1.0](devices/SWTG024AS-V2.1.0.md) | 2M | 4 + 2 |
| Steamemo | IG204-V1 | No | [PB-2131](devices/STEAMEMO_IG204_V1.md) | | 4 + 2 |
| TRENDnet | TEG-S562 | No | [TEG-S563/EU H/W: V1.0R](devices/TEG-S562.md) | 2M | 4 + 2 |
| XikeStor | SKS3200M-4GPY2XF| Yes | [SWTG024AS-v1.0](devices/SWTG024AS.md) | | 4 + 2 |
| TrendNet | TEG-S562 | No | [TEG-S563/EU H/W: V1.0R](devices/TEG-S562.md) | 2M | 4 + 2 |
| Xikestore| SKS3200M-4GPY2XF| Yes | [SWTG024AS-v1.0](devices/SWTG024AS.md) | | 4 + 2 |
| XikeStor | SKS3200-8E1X | Yes | [SWTG118AS-V2.1-17462](devices/SWTGW218AS.md) | 2M | 8 + 1 |
| XikeStor | SKS3200-4E2X | Yes | [SWTG024AS-v2.1.0](devices/SWTG024AS-V2.1.0.md) | 2M | 4 + 2 |
| YuanLey | YS25-0402 | No | [PCB-K0402WS-V1.0](devices/PCB-K0402WS-V1.0.md) | 2M | 4 + 2 |
| YuanLey | YS25-0402 | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | 2M | 4 + 2 |
| YuanLey | YS25-0402M | Yes | [SIRIVISION_S25G2206FP_V1_1](devices/SIRIVISION_S25G2206FP_V1_1.md) | 2M | 4 + 2 |
| Ztyuav | Z-QWYT0402 | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 |
| ZoeRax | HG0402XG | No | [HG0402XG_V1.1](devices/ZOERAX_HG0402XG_V1.1.md) | 1M | 4 + 2 |
| Guangnianwei | 7018-2.5G | No | [F7008-2.5GPOE-V1.2](devices/F7008-2.5.md) | 8M | 8 + 1 |
For KP-9000-6XH-X2 / KP-9000-6XHML-X2 / Mokerlink 2G040210GSM devices, select
the machine target by PCB revision. The ML/non-ML or managed/unmanaged label
+2 -5
View File
@@ -79,11 +79,8 @@ JSON polling of the visible section).
computation no longer depends on uninitialised buffer content.
- `version.h` is now written by a single atomic `printf` (change from
upstream) instead of five separate `echo`s, so a parallel build
(`make -j`) can no longer read a half-written copy. It depends on
`FORCE`, so its recipe runs on every build, but the file is only
rewritten when its content changes. The minified copy in
`output/html_min/` is redone only when a web file, the list of web
files or `tools/minify.py` changes.
(`make -j`) can no longer read a half-written copy. It stays in
`.PHONY` so it is regenerated on every build.
## Verification
+243 -599
View File
File diff suppressed because it is too large. Load diff
+34 -84
View File
@@ -33,7 +33,7 @@
html,body{height:100%}
body{
font:14px/1.45 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
background:var(--bg);color:var(--fg);display:flex;overflow:hidden;
background:var(--bg);color:var(--fg);display:flex;
}
button,input,select,textarea{font:inherit;color:var(--fg)}
a{color:var(--ac);text-decoration:none}
@@ -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;flex-wrap:wrap;gap:10px;padding:8px 20px;
display:flex;align-items:center;gap:10px;padding:10px 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;min-width:120px}
#ttitle{font-size:16px;font-weight:650;flex:1}
#dirty{
display:none;font-size:12px;font-weight:600;color:var(--warn);
border:1px solid var(--warn);border-radius:20px;padding:2px 10px;
@@ -72,8 +72,8 @@ select.ctl,button.ctl{
button.pri{background:var(--ac);color:var(--acfg);border-color:transparent;font-weight:600}
button.pri:hover,button.ctl:hover{filter:brightness(1.07)}
button:disabled{opacity:.45;cursor:default;filter:none}
main{padding:18px 20px 40px;width:100%;overflow-y:auto;}
section.tab{display:none;max-width:1400px;}
main{padding:18px 20px 40px;max-width:1400px;width:100%}
section.tab{display:none}
section.tab.act{display:block}
.card{
background:var(--panel);border:1px solid var(--bd);border-radius:10px;
@@ -89,11 +89,9 @@ table.t th{
padding:6px 8px;border-bottom:1px solid var(--bd);white-space:nowrap;
}
table.t th[data-sort]{cursor:pointer;user-select:none}
table.t th.num{text-align:right}
table.t td{padding:6px 8px;border-bottom:1px solid var(--bd);vertical-align:middle}
table.t tr:last-child td{border-bottom:0}
table.t tr:hover td{background:var(--sel)}
table.t tr.sel{background:var(--panel2); font-weight: bold;}
.num{font-variant-numeric:tabular-nums;text-align:right}
.scrollx{overflow-x:auto}
input.in,select.in,textarea.in{
@@ -114,31 +112,21 @@ 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:1;min-width:0;overflow-x:auto;scrollbar-width:thin}
.port:first-child{margin-left:auto}
.port:last-child{margin-right:auto}
#strip{display:flex;gap:8px;flex-wrap:wrap;padding:4px 0 2px}
.port{
width:40px;border:2px solid var(--bd);border-radius:7px;padding:3px 0 2px;
width:46px;border:2px solid var(--bd);border-radius:7px;padding:5px 0 3px;
text-align:center;cursor:pointer;background:var(--panel2);position:relative;
}
.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.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 .jack{
display:block;width:21px;height:12px;margin:4px auto 2px;border-radius:2px;
border:2px solid var(--fg2);border-top-width:8px;opacity:.55;position:relative;
display:block;width:18px;height:12px;margin:3px auto 2px;border-radius:2px;
border:2px solid var(--fg2);border-top-width:5px;opacity:.55;
}
.port .jack:before{content:"";
position:absolute;bottom:100%;left:50%;transform:translateX(-50%);
width:6px;height:5px;margin-bottom:7px;border-radius: 1px 1px 0 0;
background-color:var(--fg2);
}
.port.sfp .jack{width:24px;height:16px;margin:0 auto 2px;border-top-width:5px}
.port.sfp .jack:before{width:10px;margin-bottom:2px;border-radius:0 0 1px 1px;background-color:var(--panel2)}
.port.sfp .jack{width:28px;height:10px;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 .jack:before{background-color:var(--pc)}
.port.up.sfp .jack:before{background-color:color-mix(in srgb,var(--pc) 10%,var(--panel2))}
.port.up .ps{color:var(--pc);font-weight:600}
.port.dis{opacity:.45;border-style:dashed}
.badge{font-size:11px;font-weight:600;border-radius:10px;padding:1px 8px;background:var(--panel2);color:var(--fg2)}
@@ -195,10 +183,6 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}
#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%}
}
</style>
<script>
(function(){
@@ -231,13 +215,15 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--fg)" stroke-width="2"><path d="M4 6h16M4 12h16M4 18h16"/></svg>
</button>
<span id="ttitle"></span>
<div id="strip"></div>
<span id="dirty" data-i18n="hdr_dirty" data-i18n-t="hdr_dirty_t">unsaved changes</span>
<button class="ctl" id="saveBtn" data-i18n="hdr_save" data-i18n-t="hdr_save_t">Save to flash</button>
<button class="ctl pri" id="saveBtn" data-i18n="hdr_save" data-i18n-t="hdr_save_t">Save to flash</button>
</header>
<main>
<section class="tab act" id="tab-dash">
<div class="card"><h2><span data-i18n="d_ports">Ports</span> <span class="hint" data-i18n="d_ports_h">click a port for details</span></h2>
<div id="strip"></div>
</div>
<div class="grid2">
<div class="card"><h2 data-i18n="d_system">System</h2><table class="t" id="sysinfo"></table></div>
<div class="card"><h2><span data-i18n="d_traffic">Traffic</span> <span class="hint" data-i18n="d_traffic_h">packets/s, live</span></h2>
@@ -251,7 +237,7 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}
<section class="tab" id="tab-ports">
<div class="card"><h2 data-i18n="pt_title">Port configuration</h2>
<div class="scrollx"><table class="t" id="ptable">
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="c_name">Name</th><th data-i18n="c_link">Link</th><th data-i18n="pt_conn">Devices learned</th><th data-i18n="pt_speed">Speed</th><th data-i18n="c_enabled">Enabled</th><th data-i18n="pt_mtu">MTU</th><th></th></tr></thead>
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="c_name">Name</th><th data-i18n="c_link">Link</th><th data-i18n="pt_conn">Connected</th><th data-i18n="pt_speed">Speed</th><th data-i18n="c_enabled">Enabled</th><th data-i18n="pt_mtu">MTU</th><th></th></tr></thead>
<tbody></tbody></table></div>
<p class="small mut" style="margin-top:8px" data-i18n="pt_sfp_note">SFP speed is set per slot and is not saved in the startup config.</p>
</div>
@@ -264,8 +250,6 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}
<p class="small mono" id="stpids">-</p>
<div id="stpbridge" style="display:flex;gap:14px;flex-wrap:wrap;align-items:flex-end;margin-top:10px">
<label><span class="small mut" data-i18n="stp_version">Version</span><br><select class="in" id="stpver"><option value="rstp">RSTP</option><option value="stp" data-i18n="stp_v_stp">STP compatible</option></select></label>
<label><span class="small mut" data-i18n="stp_pcs">Path cost</span><br><select class="in" id="stppcs"><option value="long" data-i18n="stp_pcs_long">Long (32 bit)</option><option value="short" data-i18n="stp_pcs_short">Short (16 bit)</option></select></label>
<label><span class="small mut" data-i18n="stp_bh">BPDUs while disabled</span><br><select class="in" id="stpbh"><option value="flood" data-i18n="stp_bh_flood">Flood</option><option value="filter" data-i18n="stp_bh_filter">Filter</option></select></label>
<label><span class="small mut" data-i18n="stp_prio">Bridge priority</span><br><select class="in" id="stpprio"></select></label>
<label><span class="small mut" data-i18n="stp_hello">Hello [s]</span><br><input class="in sm" id="stphello" type="number" min="1" max="10"></label>
<label><span class="small mut" data-i18n="stp_maxage">Max age [s]</span><br><input class="in sm" id="stpmaxage" type="number" min="6" max="40"></label>
@@ -283,14 +267,9 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}
</div>
<div class="card"><h2 data-i18n="stp_status">Port status</h2>
<div class="scrollx"><table class="t" id="stpstat">
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="stp_role">Role</th><th data-i18n="c_state">State</th><th data-i18n="stp_db">Designated bridge</th><th data-i18n="stp_dp">Designated port</th><th data-i18n="stp_dc">Designated cost</th><th data-i18n="stp_oedge">Oper. edge</th><th data-i18n="stp_op2p">Oper. P2P</th><th data-i18n="stp_proto">Protocol</th><th></th></tr></thead>
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="stp_role">Role</th><th data-i18n="c_state">State</th><th data-i18n="stp_db">Designated bridge</th><th data-i18n="stp_dp">Designated port</th><th data-i18n="stp_dc">Designated cost</th><th data-i18n="stp_oedge">Oper. edge</th><th data-i18n="stp_op2p">Oper. P2P</th></tr></thead>
<tbody></tbody></table></div>
</div>
<div class="card"><details id="stpcntd"><summary><h2 style="display:inline" data-i18n="stp_cnt">Counters</h2></summary>
<div class="scrollx"><table class="t" id="stpcnt">
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="stp_crx">BPDUs received</th><th data-i18n="stp_ctx">BPDUs sent</th><th data-i18n="stp_ctrx">TC received</th><th data-i18n="stp_cttx">TC sent</th><th data-i18n="stp_cage">Last BPDU [s]</th></tr></thead>
<tbody></tbody></table></div></details>
</div>
</section>
<section class="tab" id="tab-stats">
@@ -302,21 +281,27 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}
</section>
<section class="tab" id="tab-vlan">
<div class="card"><h2><span data-i18n="v_title">VLANs</span><span id="vmgmtcur"></span></h2>
<div class="card"><h2><span data-i18n="v_title">VLANs</span> <span class="hint"><span data-i18n="v_mgmt">Management VLAN</span>: <span id="vmgmtcur">-</span></span></h2>
<div class="scrollx"><table class="t" id="vtable">
<thead><tr><th data-i18n="v_vid">VID</th><th data-i18n="c_name">Name</th><th data-i18n="v_members">Members</th><th data-i18n="v_tagged">Tagged</th><th data-i18n="v_untagged">Untagged</th><th data-i18n="v_pvid_on">PVID on</th><th></th></tr></thead>
<tbody></tbody></table></div>
<p class="small mut" id="vempty" style="display:none;margin-top:6px" data-i18n="v_empty">No VLANs configured.</p>
<button class="ctl pri" id="vlan_add" data-i18n="v_add">Add a new VLAN</button>
</div>
<div class="card"><h2><span data-i18n="v_editor">VLAN editor</span> <span class="hint" id="vtitle"></span></h2>
<div><label class="row"><span class="lb" style="width:150px;" data-i18n="v_setmgmt_t"></span>
<span class="switch"><input type="checkbox" id="vmgmtpromote"><i></i></span>
</label></div>
<div class="card"><h2 data-i18n="v_editor">VLAN editor</h2>
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:10px">
<label><span data-i18n="v_vid">VID</span> <input class="in sm" id="vvid" type="number" min="1" max="4094"></label>
<label><span data-i18n="c_name">Name</span> <input class="in" id="vname" size="12" maxlength="15" data-i18n-p="v_optional"></label>
<button class="ctl" id="vload" data-i18n="c_load">Load</button>
<button class="ctl pri" id="vapply" data-i18n="c_apply">Apply</button>
<button class="ctl" id="vmgmt" data-i18n="v_setmgmt" data-i18n-t="v_setmgmt_t">Set as mgmt VLAN</button>
</div>
<div class="scrollx"><table class="t" id="vedit"><tbody></tbody></table></div>
<div style="margin-top:10px"><button class="ctl pri" id="vapply" data-i18n="c_apply">Apply</button></div>
<p class="small mut" style="margin-top:6px" data-i18n="v_legend">U = untagged member, T = tagged member, - = not a member. PVID assigns this VLAN to untagged ingress traffic.</p>
</div>
<div class="card"><h2><span data-i18n="v_ingress">Ingress filtering</span> <span class="hint" data-i18n="v_ingress_h">write-only: the state is not readable from the switch</span></h2>
<div class="scrollx"><table class="t" id="ingress"><tbody></tbody></table></div>
<div style="margin-top:10px"><button class="ctl pri" id="ingapply" data-i18n="v_ing_apply">Apply ingress modes</button></div>
</div>
</section>
<section class="tab" id="tab-l2">
@@ -346,17 +331,6 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}
</div>
</section>
<section class="tab" id="tab-lldp">
<div class="card"><h2 data-i18n="lldp_title">LLDP</h2>
<label class="row"><span class="lb" data-i18n="lldp_enabled">Enabled</span>
<span class="switch"><input type="checkbox" id="lldp_en"><i></i></span>
</label>
<div class="scrollx"><table class="t" id="lldp_table">
<thead><tr><th data-i18n="c_port">Port Number</th><th data-i18n="lldp_portname">Name</th><th data-i18n="lldp_enable">Send Frames</th></tr></thead>
<tbody></tbody></table></div>
</div>
</section>
<section class="tab" id="tab-isolate">
<div class="card"><h2 data-i18n="iso_title">Port isolation</h2>
<p class="small mut" style="margin-bottom:10px" data-i18n="iso_intro">A frame entering a port may only leave through the ports ticked in its row.</p>
@@ -371,11 +345,6 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}
<section class="tab" id="tab-lag">
<div id="lagwrap" class="grid2"></div>
<div class="card" style="margin-top:14px"><h2 data-i18n="lacp_heading">LACP (802.3ad)</h2>
<div class="scrollx"><table class="t" id="lacptbl">
<thead><tr><th data-i18n="c_port">Port</th><th>LAG</th><th data-i18n="lacp_actor">Actor</th><th data-i18n="lacp_partner">Partner</th><th data-i18n="lacp_rxstate">RX state</th><th data-i18n="lacp_rx">LACPDUs received</th><th data-i18n="lacp_psys">Partner system</th></tr></thead>
<tbody></tbody></table></div>
</div>
<p class="small mut" style="margin-top:10px" data-i18n="lag_note">A LAG needs at least one member to be saved in the startup config; applying an empty group clears it.</p>
</section>
@@ -409,9 +378,6 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}
<label class="row"><span class="lb" data-i18n="i_ip">IP address</span><input class="in" id="sy-ip" size="15"></label>
<label class="row"><span class="lb" data-i18n="i_mask">Netmask</span><input class="in" id="sy-mask" size="15"></label>
<label class="row"><span class="lb" data-i18n="i_gw">Gateway</span><input class="in" id="sy-gw" size="15"></label>
<label class="row"><span class="lb" data-i18n="sy_dns1">DNS server</span><input class="in" id="sy-dns1" size="15"></label>
<label class="row"><span class="lb" data-i18n="sy_dns2">Second DNS</span><input class="in" id="sy-dns2" size="15"></label>
<p class="small mut" id="sy-dnsdhcp"></p>
<div style="display:flex;gap:10px;margin-top:12px">
<button class="ctl pri" id="sy-apply" data-i18n="c_apply">Apply</button>
<button class="ctl" id="sy-dhcp" data-i18n="sy_dhcp" data-i18n-t="sy_dhcp_t">Use DHCP</button>
@@ -431,25 +397,9 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}
<input class="in" id="sy-sesstmo" style="width:80px" type="number" min="1" max="65535"></label>
<p class="small mut" data-i18n="sy_services_note">Service state reflects the startup config; runtime state is not readable.</p>
</div>
<div class="card" id="sy-ntpcard"><h2 data-i18n="sy_time">Time</h2>
<label class="row" style="justify-content:space-between"><span data-i18n="sy_ntp">NTP</span>
<span class="switch"><input type="checkbox" id="sy-ntp"><i></i></span></label>
<label class="row"><span class="lb" data-i18n="sy_ntpsrv">NTP server</span><input class="in" id="sy-ntpsrv" maxlength="47" data-i18n-p="sy_ntpsrv_p"></label>
<label class="row"><span class="lb" data-i18n="sy_ntpint">Poll interval [min]</span><input class="in sm" id="sy-ntpint" type="number" min="1" max="1440"></label>
<label class="row"><span class="lb" data-i18n="sy_tz">Time zone</span><select class="in" id="sy-tz"></select></label>
<label class="row"><span class="lb" data-i18n="sy_dst">Daylight saving</span><select class="in" id="sy-dst"><option value="off" data-i18n="c_offc">Off</option><option value="eu" data-i18n="sy_dst_eu">Europe</option><option value="us" data-i18n="sy_dst_us">United States</option></select></label>
<div style="display:flex;gap:10px;margin-top:8px">
<button class="ctl pri" id="sy-ntpapply" data-i18n="c_apply">Apply</button>
<button class="ctl" id="sy-ntprefresh" data-i18n="c_refresh">Refresh</button>
</div>
<label class="row" style="margin-top:10px"><span class="lb" data-i18n="sy_now">Local time</span><span class="mono" id="sy-now">-</span></label>
<label class="row"><span class="lb" data-i18n="sy_sync">Last synchronisation</span><span id="sy-sync">-</span></label>
<label class="row"><span class="lb" data-i18n="sy_ntpaddr">Server address</span><span class="mono" id="sy-ntpaddr">-</span></label>
<p class="small mut" style="margin-top:8px" data-i18n="sy_time_note">The switch has no battery backed clock.</p>
</div>
<div class="card"><h2 data-i18n="sy_password">Admin password</h2>
<label class="row"><span class="lb" data-i18n="sy_newpw">New password</span><input class="in" id="sy-pw1" type="password" autocomplete="new-password" maxlength="20"></label>
<label class="row"><span class="lb" data-i18n="sy_repeat">Repeat</span><input class="in" id="sy-pw2" type="password" autocomplete="new-password" maxlength="20"></label>
<label class="row"><span class="lb" data-i18n="sy_newpw">New password</span><input class="in" id="sy-pw1" type="password" maxlength="20"></label>
<label class="row"><span class="lb" data-i18n="sy_repeat">Repeat</span><input class="in" id="sy-pw2" type="password" maxlength="20"></label>
<button class="ctl pri" id="sy-pwapply" style="margin-top:8px" data-i18n="sy_pwapply">Change password</button>
<p class="small mut" style="margin-top:8px" data-i18n="sy_pw_note">Takes effect immediately; save to flash to keep it after reboot.</p>
</div>
+2 -6
View File
@@ -92,12 +92,8 @@ button:hover{filter:brightness(1.1)}
try{l=localStorage.getItem("rtl_lang");}catch(e){}
if(!L[l])l=(navigator.language||"en").slice(0,2);
var s=L[l]||L.en;
// Name the tab after the switch: the hostname seen at the last login here,
// otherwise the address, so several open switches can be told apart
var host=null;
try{host=localStorage.getItem("rtl_host");}catch(e){}
document.title=(host||location.hostname)+" – "+s[0];
document.getElementById("hn").textContent=host||s[0];
document.title=s[0];
document.getElementById("hn").textContent=s[0];
document.getElementById("sub").textContent=s[1];
document.getElementById("lp").textContent=s[2];
document.getElementById("lb").textContent=s[3];
+70 -93
View File
@@ -6,12 +6,12 @@
#include "cmd_parser.h"
#include "rtl837x_flash.h"
#include "uip.h"
#include "sessions.h"
#include "html_data.h"
// #define DEBUG
#include "debug.h"
#define SESSION_ID_LENGTH 12
#define SESSION_TIMEOUT_DEFAULT 200
/* Seconds of inactivity after which a session cookie stops validating.
@@ -55,8 +55,8 @@ __xdata uint8_t boundary[72];
#define CONFIG_UPLOAD_BUF (CONFIG_LEN + 384)
__xdata uint8_t config_upload;
__xdata uint8_t config_buf[CONFIG_UPLOAD_BUF];
// bytes buffered in config_buf so far (HTTP header, POST body, or a firmware
// part header); accumulates across TCP segments
// bytes buffered in config_buf so far (config body, or a firmware part
// header); accumulates across TCP segments
__xdata uint16_t pre_acc;
__xdata uint8_t * __xdata content_type = 0;
__xdata uint8_t * __xdata session = 0;
@@ -75,9 +75,13 @@ __xdata uint8_t postbody_endpoint;
__xdata uint16_t postbody_start;
__xdata char passwd[21];
// Up to four logins at once, see sessions.c
// Set when a verified firmware upload awaits its response ACK, after
// which the chip resets to apply the staged image
__xdata uint8_t fw_reset_pending;
__xdata char session_id[SESSION_ID_LENGTH + 1];
__xdata uint8_t authenticated;
__xdata uint32_t now;
__xdata uint32_t last_session_use;
#define TSTATE_NONE 0
#define TSTATE_TX 1
@@ -86,7 +90,6 @@ __xdata uint32_t now;
#define TSTATE_POST 4
#define TSTATE_MULTIPART 5
#define TSTATE_POSTBODY 6
#define TSTATE_HEADER 7
extern __xdata uint16_t crc_value;
__xdata uint16_t crc_final;
@@ -106,9 +109,8 @@ void httpd_init(void) __banked
uip_listen(HTONS(80));
// Not through uip_conn: it only points at a connection while uIP is
// handling one, and nothing has set it yet at init time.
for (uint8_t i = 0; i < UIP_CONNS; i++)
uip_conns[i].appstate.tstate = TSTATE_CLOSED;
session_init();
uip_conns[0].appstate.tstate = TSTATE_CLOSED;
fw_reset_pending = 0; // xdata is not zeroed by the startup code
}
@@ -343,11 +345,38 @@ __xdata uint8_t *scan_header(__xdata uint8_t * __xdata p)
read_reg_timer(&now);
if (session)
session_check();
if (session && session_id[0]) {
if (now - last_session_use > session_timeout) {
dbg_string("Session expired\n");
} else {
if (is_word_x(session, session_id)) {
authenticated = 1;
last_session_use = now;
} else {
dbg_string("Invalid session cookie!\n");
}
}
}
return p;
}
/*
* Generate random HEX-chars at the buffer location.
*/
void gen_random_hex_chars(__xdata uint8_t * b, __xdata uint8_t bytes)
{
uint8_t i = 0;
while (bytes) {
if (!i)
get_random_32();
b[--bytes] = itohex(sfr_data[i]);
if (!bytes) { break; }
b[--bytes] = itohex(sfr_data[i] >> 4 | sfr_data[i] << 4);
i = (i + 1) & 0x3;
}
}
/* 0: body incomplete, 1: configuration stored, 2: malformed */
static uint8_t config_take(void)
{
@@ -421,9 +450,15 @@ static uint8_t config_take(void)
// unlike scan_header(), keeps no auth state, so it may run on every buffered segment
static uint16_t preamble_payload_start(uint16_t n)
{
// Skip the HTTP header separator retained by handle_post().
uint16_t pos = 4;
uint16_t pos;
for (pos = 0; pos + 24 <= n; pos++) {
if (strstart(&config_buf[pos], "application/octet-stream"))
break;
}
if (pos + 24 > n)
return 0;
pos += 24;
while (pos + 3 < n && !strstart(&config_buf[pos], "\r\n\r\n"))
pos++;
if (pos + 3 >= n)
@@ -476,7 +511,7 @@ uint8_t stream_upload(void)
"Content-Type: text/plain\r\n\r\n"
"OK: checksum verified, rebooting\n");
// Reset once the response is fully ACKed
s->fw_reset_pending = 1;
fw_reset_pending = 1;
} else {
print_string("Checksum incorrect! Aborting.\n");
slen = strtox(outbuf, "HTTP/1.1 400 Bad Request\r\nContent-Length: 33\r\n"
@@ -504,10 +539,9 @@ uint8_t stream_upload(void)
memcpy(flash_buf + write_len, boundary, bindex);
write_len += bindex;
bindex = 0;
} else {
crc16_bank1(upload_settings.p + upload_settings.bptr);
flash_buf[write_len++] = upload_settings.p[upload_settings.bptr++];
}
crc16_bank1(upload_settings.p + upload_settings.bptr);
flash_buf[write_len++] = upload_settings.p[upload_settings.bptr++];
if (write_len >= FLASH_PAGE_SIZE) {
/* The staged image spans FIRMWARE_UPLOAD_START to twice that,
* the span check_and_flash_update_image() reads back. Nothing
@@ -539,6 +573,7 @@ uint8_t stream_upload(void)
memcpy(flash_buf, flash_buf + FLASH_PAGE_SIZE, write_len);
}
}
bindex = 0;
}
} while(1);
}
@@ -648,10 +683,13 @@ static void run_login_body(__xdata uint8_t *body)
{
if (strstart(body, "pwd=") && is_url_word_x(body + 4, passwd)) {
dbg_string("Password accepted!\n");
read_reg_timer(&now);
read_reg_timer(&last_session_use);
gen_random_hex_chars(session_id, SESSION_ID_LENGTH);
session_id[SESSION_ID_LENGTH] = NUL;
slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nConnection: close\r\nLocation: index.html\r\n" \
"Set-Cookie: session=");
session_new();
for (uint8_t i = 0; i < SESSION_ID_LENGTH; i++)
outbuf[slen++] = session_id[i];
slen += strtox(outbuf + slen, "; SameSite=Strict\r\n\r\n");
} else {
dbg_string("Password invalid!\n");
@@ -708,42 +746,7 @@ static void post_body_continue(void)
}
/*
* Buffer an HTTP request header across TCP receive events. Stop copying at
* CRLFCRLF so any body bytes in the final segment stay in uip_appdata.
*
* Return the body offset in the current receive buffer, 0 while incomplete,
* or 0xffff after sending an error response.
*/
static uint16_t request_header_take(void)
{
uint16_t i;
for (i = 0; i < uip_len; i++) {
if (pre_acc + 1 >= CONFIG_UPLOAD_BUF) {
uip_conn->appstate.tstate = TSTATE_NONE;
send_bad_request();
return 0xffff;
}
config_buf[pre_acc++] = ((__xdata uint8_t *)uip_appdata)[i];
config_buf[pre_acc] = NUL;
if (pre_acc >= 4
&& config_buf[pre_acc - 4] == '\r'
&& config_buf[pre_acc - 3] == '\n'
&& config_buf[pre_acc - 2] == '\r'
&& config_buf[pre_acc - 1] == '\n') {
uip_conn->appstate.tstate = TSTATE_NONE;
return i + 1;
}
}
postbody_start = ticks;
uip_conn->appstate.tstate = TSTATE_HEADER;
return 0;
}
void handle_post(uint16_t body_off)
void handle_post(void)
{
__xdata struct httpd_state * __xdata s = &(uip_conn->appstate);
__xdata uint8_t *p = uip_appdata;
@@ -756,9 +759,6 @@ void handle_post(uint16_t body_off)
// Was the multipart header sent in multiple packets?
if (s->tstate != TSTATE_MULTIPART) {
p = config_buf;
request_path = p + 6;
dbg_string("Is POST\n");
p += 5; // Skip post
// Find end of request path
@@ -772,12 +772,9 @@ void handle_post(uint16_t body_off)
dbg_string("Boundary: >"); dbg_string_x(boundary); dbg_string("<\n");
if (!*p || !content_type) {
dbg_string("Bad Request!\n");
send_bad_request();
send_not_found();
return;
}
pre_acc = 0;
p = (__xdata uint8_t *)uip_appdata + body_off;
if (is_word(request_path, "upload")) {
if (!authenticated) {
send_unauthorized();
@@ -803,10 +800,7 @@ void handle_post(uint16_t body_off)
dbg_string("Configuration upload\n");
verify_crc = 0;
config_upload = 1;
// config_take() matches CRLF-prefixed delimiters, including the first.
config_buf[0] = '\r';
config_buf[1] = '\n';
pre_acc = 2;
pre_acc = 0;
}
// Check for other POST requests, which are not multipart, below
} else {
@@ -830,6 +824,7 @@ void handle_post(uint16_t body_off)
handle_firmware_fragment(p);
return;
} else if (is_word(request_path, "cmd")) {
p += 4;
if (!authenticated) {
send_unauthorized();
return;
@@ -848,6 +843,7 @@ void handle_post(uint16_t body_off)
return;
}
p += 4;
postbody_endpoint = POSTBODY_LOGIN;
if (!post_body_take(p))
return;
@@ -878,10 +874,9 @@ void httpd_appcall(void)
print_byte(s->tstate);
write_char(' ');
#endif
if(uip_connected()) {
if(uip_connected() && s->tstate == TSTATE_CLOSED) {
dbg_string("Connected...\n");
s->tstate = TSTATE_NONE;
s->fw_reset_pending = 0; // explicitly initialize reused XDATA appstate
} else if (uip_closed()) {
dbg_string("Connection closed\n");
s->tstate = TSTATE_CLOSED;
@@ -895,9 +890,9 @@ void httpd_appcall(void)
dbg_string("Closing because everything has been transmitted\n");
uip_close();
s->tstate = TSTATE_CLOSED;
} else if ((s->tstate == TSTATE_POSTBODY || s->tstate == TSTATE_HEADER)
} else if (s->tstate == TSTATE_POSTBODY
&& (uint16_t)ticks - postbody_start > POSTBODY_TIMEOUT) {
dbg_string("HTTP request never completed\n");
dbg_string("Body never arrived\n");
uip_abort();
s->tstate = TSTATE_CLOSED;
}
@@ -933,7 +928,7 @@ void httpd_appcall(void)
cont_len -= slen;
cont_addr += slen;
s->tstate = TSTATE_TX;
} else if (s->fw_reset_pending) {
} else if (fw_reset_pending) {
// The upload verdict has been fully ACKed by the client;
// now it is safe to reset and apply the staged image
print_string("Resetting to apply update\n");
@@ -960,7 +955,6 @@ void httpd_appcall(void)
cont_len = 0;
dbg_char('<'); dbg_short(uip_len); dbg_char('\n');
__xdata uint8_t *p = uip_appdata;
uint16_t body_off = 0;
// Mark end of request header with \0
p[uip_len] = 0;
#ifdef DEBUG
@@ -969,21 +963,9 @@ void httpd_appcall(void)
dbg_char('\n');
#endif
p = uip_appdata;
if (s->tstate != TSTATE_MULTIPART && s->tstate != TSTATE_POSTBODY) {
if (s->tstate != TSTATE_HEADER)
pre_acc = 0;
body_off = request_header_take();
if (!body_off) {
uip_len = 0;
return;
}
if (body_off == 0xffff)
goto do_send;
}
if (s->tstate == TSTATE_MULTIPART || s->tstate == TSTATE_POSTBODY
|| is_word(config_buf, "POST")) {
handle_post(body_off);
if (is_word(p, "POST") || s->tstate == TSTATE_MULTIPART
|| s->tstate == TSTATE_POSTBODY) {
handle_post();
// If this is an ongoing post stream, then wait for the next packet
if (s->tstate == TSTATE_POST || s->tstate == TSTATE_MULTIPART
|| s->tstate == TSTATE_POSTBODY) {
@@ -993,11 +975,12 @@ void httpd_appcall(void)
goto do_send;
}
p = config_buf;
// We only expect a GET request here.
if (!is_word(p, "GET")) {
send_bad_request();
goto do_send;
}
dbg_string("GET request ");
p += 4;
scan_header(p);
@@ -1051,12 +1034,6 @@ void httpd_appcall(void)
send_lag();
} else if (is_word(q, "/stp.json")) {
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, "/stpcnt.json")) {
send_stp_counters();
} else if (is_word(q, "/vlanlist")) {
send_vlanlist();
} else if (is_word(q, "/config")) {
-1
View File
@@ -12,7 +12,6 @@
for each TCP connection. */
typedef struct httpd_state {
uint8_t tstate;
uint8_t fw_reset_pending;
} uip_tcp_appstate_t;
/* Finally we define the application function to be called by uIP. */
+149 -89
View File
@@ -14,7 +14,6 @@
#include "version.h"
#include "machine.h"
#include "rtl837x_stp.h"
#include "rtl837x_lacp.h"
#include "page_impl.h"
#include "syslog.h"
@@ -50,9 +49,6 @@ extern __xdata char sfp_module_model[2][17];
extern __xdata char sfp_module_serial[2][17];
extern __xdata uint8_t sfp_options[2];
extern __xdata uint16_t lldp_logical_port_status;
extern __xdata bool lldp_enabled;
__code const uint8_t * __code const HTTP_RESPONCE_JSON = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: application/json\r\n\r\n";
__code const uint8_t * __code const HTTP_RESPONCE_TXT = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\n";
@@ -341,7 +337,7 @@ void send_vlan(uint16_t vlan)
}
slen += strtox(outbuf + slen, "\",\"pvid\":\"0x");
uint16_t pvid_mask = 0;
for (uint8_t i = MACHINE_MIN_PORT; i <= MACHINE_MAX_PORT; i++) {
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
if (port_pvid_get(i) == vlan)
pvid_mask |= (1 << i);
}
@@ -357,7 +353,7 @@ void send_vlan(uint16_t vlan)
bool send_counters(uint8_t phys_port)
{
uint8_t phys_port_idx = phys_port - 1;
if (phys_port_idx > (MACHINE_MAX_PORT - MACHINE_MIN_PORT))
if (phys_port_idx > (machine.max_port - machine.min_port))
goto err;
uint8_t log_port = machine.phys_to_log_port[phys_port_idx];
@@ -600,83 +596,145 @@ void send_lag(void)
}
void send_lacp(void)
static __xdata uint32_t pi_u32;
static __xdata uint8_t pi_prio, pi_ext;
static __xdata uint8_t * __xdata pi_mac;
static void u32hex_html(void)
{
dbg_string("send_lacp called\n");
__xdata uint8_t *b = (__xdata uint8_t *)&pi_u32;
byte_to_html(b[3]);
byte_to_html(b[2]);
byte_to_html(b[1]);
byte_to_html(b[0]);
}
static void bridge_to_html(void)
{
byte_to_html(pi_prio);
byte_to_html(pi_ext);
for (uint8_t i = 0; i < 6; i++)
byte_to_html(pi_mac[i]);
}
void send_stp(void)
{
uint8_t i, j, st, dsg;
dbg_string("send_stp called\n");
slen = strtox(outbuf, HTTP_RESPONCE_JSON);
slen += strtox(outbuf + slen, "{\"on\":");
bool_to_html(lacpEnabled);
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 < 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);
byte_to_html(lacp_members_last[l]);
slen += strtox(outbuf + slen, "\"},");
bool_to_html(stp_enabled);
slen += strtox(outbuf + slen, ",\"rstp\":");
bool_to_html(stp_rstp);
slen += strtox(outbuf + slen, ",\"prio\":");
itoa_html(stp_prio >> 4);
slen += strtox(outbuf + slen, ",\"hello\":");
itoa_html(stp_hello_s);
slen += strtox(outbuf + slen, ",\"maxage\":");
itoa_html(stp_maxage_s);
slen += strtox(outbuf + slen, ",\"fwd\":");
itoa_html(stp_fwddelay_s);
slen += strtox(outbuf + slen, ",\"txhold\":");
itoa_html(stp_txhold);
slen += strtox(outbuf + slen, ",\"rootPrio\":\"");
byte_to_html(root_bridge.prio);
byte_to_html(root_bridge.ext);
slen += strtox(outbuf + slen, "\",\"rootMac\":\"");
for (j = 0; j < 6; j++)
byte_to_html(root_bridge.mac[j]);
slen += strtox(outbuf + slen, "\",\"myMac\":\"");
for (j = 0; j < 6; j++)
byte_to_html(uip_ethaddr.addr[j]);
slen += strtox(outbuf + slen, "\",\"cost\":\"");
byte_to_html(root_bridge_cost >> 24);
byte_to_html(root_bridge_cost >> 16);
byte_to_html(root_bridge_cost >> 8);
byte_to_html(root_bridge_cost);
slen += strtox(outbuf + slen, "\",\"weRoot\":");
bool_to_html(stp_root_port == 0xff ? 1 : 0);
slen += strtox(outbuf + slen, ",\"rootPort\":");
j = stp_root_port;
if (j != 0xff && j >= STP_LAG_BASE) {
j = 0;
while (j < STP_LAG_BASE && !((stp_lag_mask[stp_root_port - STP_LAG_BASE] >> j) & 1))
j++;
if (j >= STP_LAG_BASE)
j = 0;
}
slen -= 1; // remove comma
slen += strtox(outbuf + slen, "],\"ports\":[");
for (uint8_t i = MACHINE_MIN_PORT; i <= MACHINE_MAX_PORT; i++) {
itoa_html(j == 0xff ? 0 : machine.log_to_phys_port[j]);
slen += strtox(outbuf + slen, ",\"tc\":\"");
byte_to_html(stp_tc_count >> 8);
byte_to_html(stp_tc_count);
slen += strtox(outbuf + slen, "\",\"ports\":[");
reg_read_m(RTL837X_MSTP_STATES);
for (i = 0; i < STP_ENTITIES; i++) {
if (i < STP_LAG_BASE && (i < machine.min_port || i > machine.max_port))
continue;
if (i < STP_LAG_BASE && stp_ent_of[i] != i)
continue;
if (i >= STP_LAG_BASE && !stp_lag_mask[i - STP_LAG_BASE])
continue;
slen += strtox(outbuf + slen, "{\"p\":");
itoa_html(machine.log_to_phys_port[i]);
itoa_html(i < STP_LAG_BASE ? machine.log_to_phys_port[i] : 0);
slen += strtox(outbuf + slen, ",\"lag\":");
itoa_html(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]);
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 < MAC_ADDR_LEN; j++)
byte_to_html(lacp_partner_sys[i][j]);
itoa_html(i < STP_LAG_BASE ? 0 : i - STP_LAG_BASE + 1);
slen += strtox(outbuf + slen, ",\"mbr\":");
itoa16_html(i < STP_LAG_BASE ? 0 : stp_lag_mask[i - STP_LAG_BASE]);
slen += strtox(outbuf + slen, ",\"st\":");
j = i;
if (i >= STP_LAG_BASE) {
j = 0;
while (j < STP_LAG_BASE && !((stp_lag_mask[i - STP_LAG_BASE] >> j) & 1))
j++;
if (j >= STP_LAG_BASE)
j = 0;
}
st = (sfr_data[3 - (j >> 2)] >> ((j << 1) & 0x7)) & 0x3;
itoa_html(st);
slen += strtox(outbuf + slen, ",\"role\":");
if (!(stp_pflags[i] & STP_PF_ENABLED) || (stp_pflags[i] & STP_PF_TRIPPED))
itoa_html(0);
else if (i == stp_root_port)
itoa_html(1);
else if (st == 3)
itoa_html(2);
else
itoa_html(3);
slen += strtox(outbuf + slen, ",\"f\":");
itoa_html(stp_pflags[i]);
slen += strtox(outbuf + slen, ",\"pc\":\"");
pi_u32 = stp_pcost[i]; u32hex_html();
slen += strtox(outbuf + slen, "\",\"prio\":");
itoa_html(stp_pprio[i]);
slen += strtox(outbuf + slen, ",\"p2\":");
itoa_html(stp_pp2p[i]);
dsg = stp_dpid[i] && stp_bpdu_age[i] < (uint16_t)stp_maxage_s * STP_HZ;
slen += strtox(outbuf + slen, ",\"db\":\"");
if (dsg) {
pi_prio = stp_dbridge[i].prio; pi_ext = stp_dbridge[i].ext;
pi_mac = stp_dbridge[i].mac;
} else {
pi_prio = stp_prio; pi_ext = 0;
pi_mac = uip_ethaddr.addr;
}
bridge_to_html();
slen += strtox(outbuf + slen, "\",\"dp\":\"");
byte_to_html(dsg ? (stp_dpid[i] >> 8) : stp_pprio[i]);
byte_to_html(dsg ? stp_dpid[i] : (i + 1));
slen += strtox(outbuf + slen, "\",\"dc\":\"");
pi_u32 = dsg ? stp_dcost[i] : root_bridge_cost; u32hex_html();
slen += strtox(outbuf + slen, "\"},");
}
slen -= 1; // remove comma
slen += strtox(outbuf + slen, "]}");
}
void send_lldp(void)
{
dbg_string("send_lldp called\n");
slen = strtox(outbuf, HTTP_RESPONCE_JSON);
slen += strtox(outbuf + slen, "{\"on\":");
bool_to_html(lldp_enabled);
slen += strtox(outbuf + slen, ",\"portStatus\":[");
for (uint8_t i = MACHINE_MIN_PORT; i <= MACHINE_MAX_PORT; i++) {
slen += strtox(outbuf + slen, "{\"portNum\":");
itoa_html(machine.log_to_phys_port[i]);
slen += strtox(outbuf + slen, ",\"logPort\":");
itoa_html(i);
slen += strtox(outbuf + slen, ",\"name\":\"");
for (uint8_t j = 0; j < PORT_NAME_SIZE && port_names[i][j]; j++) {
json_char_to_html(port_names[i][j]);
}
slen += strtox(outbuf + slen, "\",\"permitted\":");
bool_to_html(1 & (lldp_logical_port_status >> i));
slen += strtox(outbuf + slen, "}");
if (i < MACHINE_MAX_PORT)
char_to_html(',');
}
char_to_html(']');
char_to_html('}');
}
void send_eee(void)
@@ -688,7 +746,7 @@ void send_eee(void)
uint8_t eee_ablty = sfr_data[3];
char_to_html('[');
for (uint8_t i = MACHINE_MIN_PORT; i <= MACHINE_MAX_PORT; i++) {
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
slen += strtox(outbuf + slen, "{\"portNum\":");
itoa_html(machine.log_to_phys_port[i]);
@@ -720,10 +778,11 @@ void send_eee(void)
bool_to_html(eee_ablty & (1 << i));
}
char_to_html('}');
if (i < MACHINE_MAX_PORT)
if (i < machine.max_port)
char_to_html(',');
else
char_to_html(']');
}
char_to_html(']');
}
@@ -732,7 +791,7 @@ void send_bandwidth(void)
dbg_string("send_bandwidth called\n");
slen = strtox(outbuf, HTTP_RESPONCE_JSON);
char_to_html('[');
for (uint8_t i = MACHINE_MIN_PORT; i <= MACHINE_MAX_PORT; i++) {
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
slen += strtox(outbuf + slen, "{\"portNum\":");
itoa_html(machine.log_to_phys_port[i]);
slen += strtox(outbuf + slen, ",\"iLimited\":");
@@ -762,10 +821,11 @@ void send_bandwidth(void)
byte_to_html(sfr_data[3]);
char_to_html('"');
char_to_html('}');
if (i < MACHINE_MAX_PORT)
if (i < machine.max_port)
char_to_html(',');
else
char_to_html(']');
}
char_to_html(']');
}
@@ -775,7 +835,7 @@ void send_storm(void)
slen = strtox(outbuf, HTTP_RESPONCE_JSON);
char_to_html('[');
for (i = MACHINE_MIN_PORT; i <= MACHINE_MAX_PORT; i++) {
for (i = machine.min_port; i <= machine.max_port; i++) {
slen += strtox(outbuf + slen, "{\"portNum\":");
itoa_html(machine.log_to_phys_port[i]);
slen += strtox(outbuf + slen, ",\"en\":\"");
@@ -795,10 +855,11 @@ void send_storm(void)
}
char_to_html('"');
char_to_html('}');
if (i < MACHINE_MAX_PORT)
if (i < machine.max_port)
char_to_html(',');
else
char_to_html(']');
}
char_to_html(']');
}
@@ -809,7 +870,7 @@ void send_isolation(void)
dbg_string("send_isolation called\n");
slen = strtox(outbuf, HTTP_RESPONCE_JSON);
char_to_html('[');
for (uint8_t i = MACHINE_MIN_PORT; i <= MACHINE_MAX_PORT; i++) {
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
slen += strtox(outbuf + slen, "{\"portNum\":");
itoa_html(machine.log_to_phys_port[i]);
slen += strtox(outbuf + slen, ",\"allow\":\"");
@@ -818,7 +879,7 @@ void send_isolation(void)
byte_to_html(allow & 0xff);
char_to_html('"');
char_to_html('}');
if (i < MACHINE_MAX_PORT)
if (i < machine.max_port)
char_to_html(',');
}
char_to_html(']');
@@ -830,7 +891,7 @@ void send_mtu(void)
dbg_string("send_mtu called\n");
slen = strtox(outbuf, HTTP_RESPONCE_JSON);
char_to_html('[');
for (uint8_t i = MACHINE_MIN_PORT; i <= MACHINE_MAX_PORT; i++) {
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
slen += strtox(outbuf + slen, "{\"portNum\":");
itoa_html(machine.log_to_phys_port[i]);
slen += strtox(outbuf + slen, ",\"mtu\":\"0x");
@@ -840,10 +901,11 @@ void send_mtu(void)
byte_to_html(mtu & 0xff);
char_to_html('"');
char_to_html('}');
if (i < MACHINE_MAX_PORT)
if (i < machine.max_port)
char_to_html(',');
else
char_to_html(']');
}
char_to_html(']');
}
@@ -853,7 +915,7 @@ void send_status(void)
dbg_string("sending status\n");
char_to_html('[');
for (uint8_t i = MACHINE_MIN_PORT; i <= MACHINE_MAX_PORT; i++) {
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
slen += strtox(outbuf + slen, "{\"portNum\":");
itoa_html(machine.log_to_phys_port[i]);
slen += strtox(outbuf + slen, ",\"logPort\":");
@@ -970,14 +1032,12 @@ 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)
if (i < machine.max_port)
char_to_html(',');
else
char_to_html(']');
}
char_to_html(']');
}
+1 -4
View File
@@ -22,10 +22,7 @@ void send_isolation(void);
void send_config(void);
void send_cmd_log(void);
void send_lag(void);
void send_stp(void) __banked;
void send_lldp(void);
void send_lacp(void);
void send_stp_counters(void) __banked;
void send_stp(void);
void send_vlanlist(void);
/* Convert only the lower nibble to ascii HEX char.
-195
View File
@@ -1,195 +0,0 @@
#include "rtl837x_sfr.h"
#include "rtl837x_common.h"
#include "rtl837x_regs.h"
#include "rtl837x_port.h"
#include "uip.h"
#include <stdint.h>
#include "phy.h"
#include "machine.h"
#include "rtl837x_stp.h"
#include "page_impl.h"
#include "debug.h"
#pragma codeseg BANK4
#pragma constseg BANK4
extern __xdata uint8_t outbuf[TCP_OUTBUF_SIZE];
extern __xdata uint16_t slen;
static __code const uint8_t stp_json_hdr[] = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: application/json\r\n\r\n";
static void stp_bool_html(char c)
{
outbuf[slen++] = c ? '1' : '0';
}
static void stp_byte_html(uint8_t val)
{
outbuf[slen++] = itohex(val >> 4);
outbuf[slen++] = itohex(val);
}
static void stp_itoa_html(uint8_t v)
{
uint8_t t = v / 100;
uint8_t print_zeros = t;
if (print_zeros)
outbuf[slen++] = '0' + t;
t = (v / 10) % 10;
print_zeros |= t;
if (print_zeros)
outbuf[slen++] = '0' + t;
outbuf[slen++] = '0' + (v % 10);
}
static __xdata uint32_t pi_u32;
static __xdata uint8_t pi_prio, pi_ext;
static __xdata uint8_t * __xdata pi_mac;
static void u32hex_html(void)
{
__xdata uint8_t *b = (__xdata uint8_t *)&pi_u32;
stp_byte_html(b[3]);
stp_byte_html(b[2]);
stp_byte_html(b[1]);
stp_byte_html(b[0]);
}
static void bridge_to_html(void)
{
stp_byte_html(pi_prio);
stp_byte_html(pi_ext);
for (uint8_t i = 0; i < 6; i++)
stp_byte_html(pi_mac[i]);
}
void send_stp(void) __banked
{
static __xdata uint8_t i, j, st, dsg;
dbg_string("send_stp called\n");
slen = strtox(outbuf, stp_json_hdr);
slen += strtox(outbuf + slen, "{\"on\":");
stp_bool_html(stp_enabled);
slen += strtox(outbuf + slen, ",\"rstp\":");
stp_bool_html(stp_rstp);
slen += strtox(outbuf + slen, ",\"prio\":");
stp_itoa_html(stp_prio >> 4);
slen += strtox(outbuf + slen, ",\"hello\":");
stp_itoa_html(stp_hello_s);
slen += strtox(outbuf + slen, ",\"maxage\":");
stp_itoa_html(stp_maxage_s);
slen += strtox(outbuf + slen, ",\"fwd\":");
stp_itoa_html(stp_fwddelay_s);
slen += strtox(outbuf + slen, ",\"txhold\":");
stp_itoa_html(stp_txhold);
slen += strtox(outbuf + slen, ",\"pcs\":");
stp_itoa_html(stp_pcost_short);
slen += strtox(outbuf + slen, ",\"bh\":");
stp_itoa_html(stp_bpdu_filter);
slen += strtox(outbuf + slen, ",\"tcs\":\"");
pi_u32 = stp_tc_secs; u32hex_html();
slen += strtox(outbuf + slen, "\",\"rMaxage\":");
stp_itoa_html(stp_root_port == 0xff ? stp_maxage_s : stp_root_maxage);
slen += strtox(outbuf + slen, ",\"rFwd\":");
stp_itoa_html(stp_root_port == 0xff ? stp_fwddelay_s : stp_root_fwd);
slen += strtox(outbuf + slen, ",\"rootPrio\":\"");
stp_byte_html(root_bridge.prio);
stp_byte_html(root_bridge.ext);
slen += strtox(outbuf + slen, "\",\"rootMac\":\"");
for (j = 0; j < 6; j++)
stp_byte_html(root_bridge.mac[j]);
slen += strtox(outbuf + slen, "\",\"myMac\":\"");
for (j = 0; j < 6; j++)
stp_byte_html(uip_ethaddr.addr[j]);
slen += strtox(outbuf + slen, "\",\"cost\":\"");
stp_byte_html(root_bridge_cost >> 24);
stp_byte_html(root_bridge_cost >> 16);
stp_byte_html(root_bridge_cost >> 8);
stp_byte_html(root_bridge_cost);
slen += strtox(outbuf + slen, "\",\"weRoot\":");
stp_bool_html(stp_root_port == 0xff ? 1 : 0);
slen += strtox(outbuf + slen, ",\"tc\":\"");
stp_byte_html(stp_tc_count >> 8);
stp_byte_html(stp_tc_count);
slen += strtox(outbuf + slen, "\",\"ports\":[");
for (i = 0; i < STP_ENTITIES; i++) {
j = stp_ent_id(i);
if (!j)
continue;
slen += strtox(outbuf + slen, "{\"p\":");
stp_itoa_html(j);
slen += strtox(outbuf + slen, ",\"st\":");
st = stp_port_state(i);
stp_itoa_html(st);
slen += strtox(outbuf + slen, ",\"role\":");
stp_itoa_html(stp_port_role(i));
slen += strtox(outbuf + slen, ",\"f\":");
stp_itoa_html(stp_pflags[i]);
slen += strtox(outbuf + slen, ",\"pc\":\"");
pi_u32 = stp_pcost[i]; u32hex_html();
slen += strtox(outbuf + slen, "\",\"prio\":");
stp_itoa_html(stp_pprio[i]);
slen += strtox(outbuf + slen, ",\"p2\":");
stp_itoa_html(stp_pp2p[i]);
slen += strtox(outbuf + slen, ",\"lk\":");
stp_itoa_html((stp_link_prev >> i) & 1);
slen += strtox(outbuf + slen, ",\"lg\":");
stp_itoa_html((stp_legacy >> i) & 1);
dsg = stp_dpid[i] && stp_info_while[i];
slen += strtox(outbuf + slen, ",\"db\":\"");
if (dsg) {
pi_prio = stp_dbridge[i].prio; pi_ext = stp_dbridge[i].ext;
pi_mac = stp_dbridge[i].mac;
} else {
pi_prio = stp_prio; pi_ext = 0;
pi_mac = uip_ethaddr.addr;
}
bridge_to_html();
slen += strtox(outbuf + slen, "\",\"dp\":\"");
stp_byte_html(dsg ? (stp_dpid[i] >> 8) : stp_pprio[i]);
stp_byte_html(dsg ? stp_dpid[i] : j);
slen += strtox(outbuf + slen, "\",\"dc\":\"");
pi_u32 = dsg ? stp_dcost[i] : root_bridge_cost; u32hex_html();
slen += strtox(outbuf + slen, "\"},");
}
slen -= 1; // remove comma
slen += strtox(outbuf + slen, "]}");
}
void send_stp_counters(void) __banked
{
uint8_t i, j;
slen = strtox(outbuf, stp_json_hdr);
slen += strtox(outbuf + slen, "{\"on\":");
stp_bool_html(stp_enabled);
slen += strtox(outbuf + slen, ",\"hz\":");
stp_itoa_html(STP_HZ);
slen += strtox(outbuf + slen, ",\"tc\":\"");
stp_byte_html(stp_tc_count >> 8);
stp_byte_html(stp_tc_count);
slen += strtox(outbuf + slen, "\",\"ports\":[");
for (i = 0; i < STP_ENTITIES; i++) {
j = stp_ent_id(i);
if (!j)
continue;
slen += strtox(outbuf + slen, "{\"p\":");
stp_itoa_html(j);
slen += strtox(outbuf + slen, ",\"c\":\"");
for (j = 0; j < STP_CNT_N; j++) {
pi_u32 = stp_cnt[j][i]; u32hex_html();
}
stp_byte_html(stp_bpdu_age[i] >> 8);
stp_byte_html(stp_bpdu_age[i]);
slen += strtox(outbuf + slen, "\"},");
}
slen -= 1;
slen += strtox(outbuf + slen, "]}");
}
+3 -13
View File
@@ -1,14 +1,9 @@
CODE_LOCATION=0x1000
INSTALLER_ADDRESS=0x1100
SDCC_PREFIX = ../toolchain/sdcc-$(shell cat ../sdcc/* | cksum | cut -d' ' -f1)
ifeq ($(origin CC),default)
SDCC = $(SDCC_PREFIX)/bin/sdcc
CC = $(SDCC)
ASM = $(SDCC_PREFIX)/bin/sdas8051
endif
CC = sdcc
CC_FLAGS = -mmcs51
ASM ?= sdas8051
ASM = sdas8051
AFLAGS= -plosgff
BUILDDIR = output
@@ -18,14 +13,9 @@ OBJS = ${SRCS:%.c=$(BUILDDIR)/%.rel}
all: create_build_dir $(BUILDDIR)/updatebuilder $(BUILDDIR)/rtlplayground_oem_upgrade.bin
create_build_dir: $(SDCC)
create_build_dir:
mkdir -p $(BUILDDIR)
ifneq ($(SDCC),)
$(SDCC):
sh ../sdcc/build.sh $(abspath $(SDCC_PREFIX))
endif
$(BUILDDIR)/updatebuilder: updatebuilder.c
gcc $^ -o $@
+1 -1
View File
@@ -348,7 +348,7 @@ void installer(void)
{
CKCON = 0; // Initial Clock configuration
MAP_FLASH_PAGE_TO_XDATA = 0; // HADDR?
SFR_97 = 0; // HADDR?
// Set in managed mode:
SFR_b9 = 0x00;
+1407
View File
File diff suppressed because it is too large. Load diff
+50 -60
View File
@@ -3,62 +3,54 @@
#include <stdint.h>
#define MACHINE_STR_HELPER(x) #x
#define MACHINE_STR(x) MACHINE_STR_HELPER(x)
/*
* 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
// 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_DEF_DIR machine
#ifndef MACHINE_NAME
#define MACHINE_NAME default_machine
#endif
#define MACHINE_INC(dir,file) MACHINE_STR(dir/file)
#define RTL8370 0x70
#define RTL8372 0x72
#define RTL8373 0x73
#include MACHINE_INC(MACHINE_DEF_DIR,MACHINE_NAME.h)
#ifndef MACHINE_CHIPSET
#error "MACHINE_CHIPSET shall be defined in <machine>.h file"
#endif
#ifndef MACHINE_MIN_PORT
#if MACHINE_CHIPSET == RTL8372
#define MACHINE_MIN_PORT 3
#else
#define MACHINE_MIN_PORT 0
#endif
#endif
#ifndef MACHINE_MAX_PORT
#if MACHINE_CHIPSET == RTL8370
#define MACHINE_MAX_PORT 7
#else
#define MACHINE_MAX_PORT 8
#endif
#endif
#define MACHINE_PORT_VALID(p) \
((uint8_t)((p) - MACHINE_MIN_PORT) <= (MACHINE_MAX_PORT - MACHINE_MIN_PORT))
// auto derive SOC_FAMILY
#define RTL836X 6
#define RTL837X 7
#ifdef SOC_FAMILY
#error "SOC_FAMILY can not be manually defined, shall be auto derived"
#endif
#if (MACHINE_CHIPSET == RTL8372) || (MACHINE_CHIPSET == RTL8373)
#define SOC_FAMILY RTL837X
#elif (MACHINE_CHIPSET == RTL8370 )
#define SOC_FAMILY RTL836X
#else
#error "Unsupported MACHINE_CHIPSET is specified!"
#endif
// #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_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 LED_27 1
// SYSTEM LED
@@ -84,6 +76,10 @@ struct sfp_port
struct machine {
char machine_name[30];
uint8_t isRTL8373;
// Lowest logical port number
uint8_t min_port;
// Highest logical port number
uint8_t max_port;
uint8_t n_sfp;
uint8_t n_10g;
uint8_t log_to_phys_port[9];
@@ -103,12 +99,6 @@ struct machine {
uint8_t led_mux_custom;
uint8_t led_mux[28];
uint32_t mac_flash_offset;
// Read the flash with single-IO (CMD_FREAD) instead of DIO for MMIO/code
// fetch. Set for boards whose flash chip is not usable in DIO mode, which
// shows up as a board that does not boot at all, garbled console output or
// random resets. Costs management-plane speed only, the ASIC keeps
// forwarding in hardware.
uint8_t flash_sio;
};
struct machine_runtime
-1
View File
@@ -1 +0,0 @@
SWTG018AS_A_V_2_0.c
Loaded 100 of 292 files, more files were not shown because too many files have changed in this diff. Show more