32 Commits
Author SHA1 Message Date
Rob Emery 1400431337 Merge pull request #575 from logicog/zoerax
doc: Add HOWTO for using firmware tracing to figure out LED configuration
2026-10-29 08:54:01 +00:00
Rob Emery 119b5ddf13 Merge pull request #743 from gnacho/i18n/es-strings-20261028
l10n: complete the Spanish translation
2026-10-29 08:26:02 +00:00
René van Dorst 231b43b264 Merge pull request #742 from vankxr/device/add-mac
Add MAC offset for GT_ST024M and YS25_0402M
2026-10-28 20:01:19 +00:00
René van Dorst e53ebddc21 Merge pull request #727 from andrebrait/fix/json-array-close
httpd: close the port arrays after their loops
2026-10-28 19:46:25 +00:00
gnacho f23eb6bebb l10n: complete the Spanish translation 2026-10-28 20:45:41 +01:00
João Silva cd51cffb1d Add MAC offset for GT_ST024M and YS25_0402M
Signed-off-by: João Silva <jgc3silva@gmail.com>
2026-10-28 19:06:55 +00:00
Rob Emery 01fc5db9a3 Merge pull request #737 from DrDoof/fix/stp-port-id-panel
stp: use the front panel number as the port ID
2026-10-28 15:32:38 +00:00
DrDoof bdca888ecd stp: use the front panel number as the port ID
The port ID in the BPDUs was the chip port plus one, so on most boards
it did not match the port label, neither on the Spanning Tree page nor
on the neighbour. Send the front panel number instead, and 101 and up
for the LAGs, the same numbers the page already uses for the Port
column. A returned BPDU of our own is mapped back to the port by that
number.

Scenario 66 gives one port a front panel number that differs from its
chip port.
2026-10-28 16:05:12 +01:00
Andre Brait e8431999e5 httpd: close the port arrays after their loops
send_lldp(), send_eee(), send_bandwidth(), send_storm(), send_mtu() and
send_status() wrote the closing ']' inside the loop, on the last port.
Write it once after the loop, as send_isolation() already does, so the
array is closed however the loop ends.
2026-10-28 11:08:54 +01:00
René van Dorst 7ca4d2c8dc Merge pull request #723 from zoeimogen/main
SWTG024AS-V2.1.0 support (Xikestor SKS3200-4E2X, Sodola SL902-SWTG024AS)
2026-10-28 08:09:02 +01:00
Zoe O'Connell 32dd21bbd3 Add additional information on SWTG024AS-V2.1.0 2026-10-28 00:15:26 +00:00
feelfree69 92f9025109 Merge pull request #671 from gtytup/s25g1109fp
Add Sirivision SR-S25G2109F device (PCB S25G1109FP V1.3)
2026-10-27 22:38:33 +01:00
feelfree69 b65aa82d78 Merge pull request #616 from logicog/zoerax_hg0402xg
New Device: Add support for ZoeRax HG0402XG V1.1
2026-10-27 22:36:49 +01:00
logicog 851eb39344 Merge pull request #444 from DrDoof/feat/rstp
stp: full RSTP and a reworked Spanning Tree page
2026-10-27 22:34:54 +01:00
João Silva 970e98f4c5 test/stp: pack host structs like SDCC and cover the edge recovery
Build the harness with -fpack-struct=1 and assert the frame struct sizes,
so the host reads wire bytes the way the firmware lays them out. Add
scenarios for a port that regains edge after a hold, one that heard a BPDU
and stays non-edge, auto-edge switched off, and a captured RouterOS BPDU
fed in on every port, tagged and untagged.
2026-10-27 22:11:24 +01:00
João Silva 4d009bc8e8 stp: give auto-edge back to a port held by its own BPDU
stp_loop_hold_peer() takes operEdge away from the port that sent the
BPDU we got back, though that port heard nothing itself. When the hold
ran out the port stayed non-edge until its next link change, and every
later state change on it counted as a topology change. Restore edge when
the hold ends with the port still silent and auto-edge on, before the
transition is reported, so the recovery itself is not a change. Hellos
go out on edge ports too, so a loop that persists is caught again.

The console column that shows seconds since the last BPDU is now called
age[s]; it never was a receive count.
2026-10-27 22:11:24 +01:00
d00f eb51128ec7 stp: a port without carrier does not become the root port
stp_reselect() took every port with fresh information as a candidate, so a
BPDU credited to a port whose carrier the once-a-second link poll had not
seen yet made that port the root port before the port was set up for it.
A candidate now needs its link bit as well; the poll that sees the carrier
a moment later runs the selection again. Scenario 54.
2026-10-27 22:11:24 +01:00
d00f b86fd37eaa stp: pass a topology change on only from a forwarding port
stp_in() propagated the TC flag of any BPDU that did not arrive on an
alternate port, and acknowledged and propagated a TCN from any port, so a
port still in its listen period or held discarding in a dispute could flush
the addresses of every other port and move the topology change counter.
802.1D-2004 17.31 keeps the Topology Change machine in LEARNING until the
port forwards and drops rcvdTc and rcvdTcn there. Both paths now ask for
the forwarding state first; an alternate port is discarding, so the old
check is covered by the new one.

Scenario 53 plays a TC flag and a TCN into a port that is still discarding,
and then into the same port once it forwards. Scenario 51 used a TCN on a
port one tick short of its edge transition as the trigger for the clock it
tests; it now waits for the transition.
2026-10-27 22:11:24 +01:00
d00f b92a773cad test/stp: tag the frames the simulator feeds in
The frame the simulator builds carries a vlan_tag between the RTL tag and the BPDU, but left its TPID zero. stp_in() now takes the body offset from TRAP_RX_BODY(), which reads those two bytes, so it took the untagged branch and parsed the body four bytes early. Writing 0x8100 makes the frame what its layout says it is.
2026-10-27 22:11:24 +01:00
d00f 068e7467af test/stp: stub the RMA action the trap now sets
Reception moved to the RMA trap in main, so rtl837x_stp.c calls trap_rma_set() from stp_setup() and stp_off(). The simulator defines neither, so it does not link. Linking rtl837x_trap.c instead would pull trap_init() and the register primitives it needs, which the simulator does not mock.
2026-10-27 22:11:24 +01:00
DrDoof da4f5058a7 test/stp: declarations first, one statement per line
stp_sim.c declared variables in the middle of the scenarios and put
whole loops and conditions on one line. Each block now starts with its
declarations; an initializer that depends on what happens before it
stays where it was as an assignment. Bodies of if and for are on lines
of their own, functions are separated by an empty line, and lines stay
within 120 columns. The output of stp_sim -v is unchanged.
2026-10-27 22:11:24 +01:00
DrDoof fee4f17bff test/stp: print each scenario heading once
Scenarios 4 and 7 start from the state scenario 3 leaves behind, and 38
from the one 37 leaves, and they called those scenarios for it, so the
headings of 3 and 37 showed up again in the middle of the run. The setup
of 3 and 37 now lives in functions of its own that the scenarios call.
2026-10-27 22:11:24 +01:00
DrDoof e19271fc44 test/stp: build the simulator against the firmware headers
test/stp/shim held hand-written copies of the headers the STP code
includes, and they had drifted: the uIP buffer was 600 bytes instead of
1556, struct machine had three fields, port_ingress_filter_get()
returned a plain uint8_t. The simulator now compiles rtl837x_stp.c and
rtl837x_stp_cli.c the way test/ compiles firmware sources, with the real
headers and sdcc_shim.h forced in, and with the same warnings, including
-Wno-pointer-sign for the string literals passed to cmd_compare(). It
stays on -std=c11: in gnu11 mode glibc defines LITTLE_ENDIAN, which
uip-conf.h names as the byte order, and HTONS() stops swapping. Its own
print_string() and cmd_compare() take the firmware's argument types.
2026-10-27 22:11:24 +01:00
DrDoof aff6ac0923 stp: full RSTP and a reworked Spanning Tree page
* stp: select the root port by cost and block redundant links
* stp: move the protocol to BANK4 and keep the command line in BANK2
* stp: implement the RSTP state machines of 802.1D-2004 clause 17
* stp: counters, more settings and a fuller Spanning Tree page
* stp: make a link aggregation group one port of the RSTP tree
* web: serve the Spanning Tree JSON from BANK4 as well

Kept in BANK1, the larger Spanning Tree page would leave under 1 kB
there for the rest of the web server and every page still to come, so
send_stp() and send_stp_counters() move to httpd/page_stp.c in BANK4
with the protocol, as banked calls, with their own copies of the few output
helpers and of the JSON header, so nothing in BANK4 calls into BANK1
directly or hands it a pointer into its code space. BANK4 takes the
free flash between BANK3 and the web files.

KP_9000_6XHML_X2: BANK1 44336, BANK2 36969, BANK3 11942, BANK4 20965
bytes of 49152.
2026-10-27 22:11:24 +01:00
feelfree69 6c62791d23 Merge pull request #722 from frogamic/fix-docker-oem-build
Document oem installer build in docker
2026-10-27 22:04:23 +01:00
Zoë O'Connell 3910888052 Merge branch 'logicog:main' into main 2026-10-27 17:24:37 +00:00
Zoe O'Connell a0cb3c1850 SWTG024AS-V2.1.0 2026-10-27 17:24:03 +00:00
Dominic Shelton bbce2d6727 doc: Document Docker oem installer build 2026-10-28 00:52:31 +11:00
gtytup ec14dbf323 doc: add Sirivision SR-S25G2109F (S25G1109FP V1.3)
Device page following doc/adding-a-device.md: label data, PCB versus label
naming, CPU identified with 'regget 4' (RTL8373, non-N), separate PHY and flash
identification, build target, PCB silkscreen, port mapping, board signals, front
panel and LED configuration, reset button, MAC location, what works and what is
unverified, plus photos.  Row added to supported_devices.md.
2026-10-25 15:37:28 +08:00
gtytup deedb92c5d machine: add Sirivision S25G1109FP V1.3 (8x2.5G + 1xSFP+)
RTL8373 (non-N) with an RTL8224 quad 2.5G PHY, 2 MB GD25Q16ESIG flash, no PoE.
The wiring is close to the KP-9000-9XHML-X V3.1 (same 8+1 topology, same I2C on
GPIO39/40), but three things differ and were determined on hardware:

- ModAbs is GPIO30 (low with a module inserted, high with an empty cage) and LOS
  is GPIO37 (high without light), not GPIO38.  GPIO38, the ModAbs pin of the
  KP-9000 reference, is tied low on this board, which made the firmware believe a
  module was permanently inserted and retry the I2C read forever.
- The reset button is GPIO54, not GPIO48.  GPIO48 never changes here, so the
  button appeared to do nothing.
- The LED wiring follows the stock port order (pin 3n+m belongs to port n+1,
  LED function m), so the mux table is the chip default except that pin 25 --
  the SFP LED -- is driven by function 0, the only populated function in SET1.
  Per copper port the left LED shows link/activity and the right LED shows
  2.5G, matching the front panel labels.

Verified on hardware: all eight 2.5G ports at 10/100/1000/2500 Mbps, 1G and 10G
SFP modules including DDM data, both LED columns, module insert/remove and the
reset button.
2026-10-25 15:37:28 +08:00
logicog c263a8ab64 Add assmebler injection script 2026-10-15 06:53:23 +02:00
logicog c02f660c08 doc: Add HOWTO for using firmware tracing to figure LED configuration
This adds a howto for figuring out LED configuration via tracing
of functions in the originam firmware image. The method also provides
information on PHY and SDS configuration, including PHY patches.
2026-10-11 22:31:33 +02:00
38 changed files with 5323 additions and 710 deletions

No files matched your search

+5 -3
View File
@@ -150,10 +150,12 @@ SRCS += \
rtl837x_lldp.c \
rtl837x_storm.c \
rtl837x_lacp.c \
rtl837x_trap.c
rtl837x_trap.c \
rtl837x_stp_cli.c
SRCS += \
httpd/httpd.c \
httpd/page_impl.c
httpd/page_impl.c \
httpd/page_stp.c
SRCS += \
uip/timer.c \
uip/uip.c \
@@ -246,7 +248,7 @@ $(BUILDDIR)/%.rel: %.asm $(CCFLAGS_STAMP) | create_build_dir
# 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-r -o $@ $^
$(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.img: $(BUILDDIR)/rtlplayground.ihx
objcopy --input-target=ihex -O binary $< $@
+8
View File
@@ -97,6 +97,14 @@ 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:
```
+148
View File
@@ -0,0 +1,148 @@
# 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)
+48
View File
@@ -0,0 +1,48 @@
### 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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

+572
View File
@@ -0,0 +1,572 @@
# 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
}
}
};
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

+154 -50
View File
@@ -2,9 +2,11 @@
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, 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.
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.
STP can be enabled and controlled via the web interface or the command line,
as follows:
@@ -64,32 +66,6 @@ LACPDUs, delivered by forward, stopped arriving on port 3, and BPDUs sent into
the same port, delivered by trap, kept arriving. A port that admits tagged
frames only therefore takes part in STP.
## Link aggregation
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
blocking forwards nothing between ports, but it still sends what the CPU
@@ -109,19 +85,83 @@ encodes. All configured values are in seconds:
| `stp fwd <n>` | 15 | 4–30 |
| `stp txhold <n>` | 6 | 1–10 |
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.
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.
## Topology changes
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.
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.
## Bridge settings
@@ -129,6 +169,8 @@ acknowledged with TCA and then treated like a local change.
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
@@ -136,6 +178,17 @@ 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
```
@@ -146,6 +199,7 @@ 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
@@ -162,26 +216,76 @@ 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, per port, the live state
read from the ASIC together with the configured options. The same data is
available as JSON:
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:
```
GET /stp.json
```
The `stp status` command prints the same view on the serial console.
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.
## Limitations
* One spanning-tree instance; no MSTP, no per-VLAN trees.
* 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.
* 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.
+3
View File
@@ -43,11 +43,14 @@ The following devices have been tested and are fully working:
| 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 |
| 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 |
| 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 |
+46 -11
View File
@@ -41,6 +41,11 @@ stp_en_d:"Ports start blocked and take up to twice the forward delay to reach fo
stp_dis_q:"Disable spanning tree?",stp_dis_d:"All ports go straight to forwarding; loop protection is lost.",
stp_cost_err:"Path cost must be 0-200000000",
lldp_enabled: "LLDP Enabled",lldp_title:"Service Status",lldp_portname:"Name",lldp_enable:"Send Frames",
stp_pcs:"Path cost",stp_pcs_long:"Long (32 bit)",stp_pcs_short:"Short (16 bit)",
stp_bh:"BPDUs while disabled",stp_bh_flood:"Flood",stp_bh_filter:"Filter",
stp_r4:"Backup",stp_times:"max age / forward delay",stp_proto:"Protocol",stp_nolink:"no link",stp_lasttc:"last",
stp_mcheck:"Check",stp_mcheck_t:"Send RST BPDUs again to find out whether the neighbour still needs 802.1D",
stp_cnt:"Counters",stp_crx:"BPDUs received",stp_ctx:"BPDUs sent",stp_ctrx:"TC received",stp_cttx:"TC sent",stp_cage:"Last BPDU [s]",
st_title:"Port statistics",st_h:"totals since boot",st_txg:"TX good",st_txb:"TX bad",st_rxg:"RX good",
st_rxb:"RX bad",st_details:"Details",st_counters:"MIB counters",st_nonzero:"non-zero only",
st_autoref:"auto-refresh",st_counter:"Counter",st_value:"Value",st_fail:"failed to load counters",
@@ -460,7 +465,8 @@ fw_noreboot:"no se detectó reinicio: lo más probable es que la imagen fuera re
fw_applied:"actualización aplicada",fw_done_t:"Firmware actualizado",
fw_done:"El switch verificó la imagen y se reinició con ella. La sesión se restableció, así que se te pedirá que vuelvas a iniciar sesión.",
fw_login:"Ir al inicio de sesión",fw_rebooting:"el switch se está reiniciando...",
fw_timeout:"el switch no ha vuelto tras 150 s: comprueba la alimentación / consola serie"
fw_timeout:"el switch no ha vuelto tras 150 s: comprueba la alimentación / consola serie",
nav_isolate:"Aislamiento",nav_lldp:"LLDP",i_temp:"Temperatura",lldp_enabled:"LLDP activado",lldp_title:"Estado del servicio",lldp_portname:"Nombre",lldp_enable:"Enviar tramas",stp_pcs:"Coste de ruta",stp_pcs_long:"Largo (32 bits)",stp_pcs_short:"Corto (16 bits)",stp_bh:"BPDU con STP desactivado",stp_bh_flood:"Inundar",stp_bh_filter:"Filtrar",stp_r4:"Respaldo",stp_times:"edad máxima / retardo de reenvío",stp_proto:"Protocolo",stp_nolink:"sin enlace",stp_lasttc:"último",stp_mcheck:"Comprobar",stp_mcheck_t:"Reenviar BPDUs RST para saber si el vecino sigue necesitando 802.1D",stp_cnt:"Contadores",stp_crx:"BPDUs recibidas",stp_ctx:"BPDUs enviadas",stp_ctrx:"TC recibidas",stp_cttx:"TC enviadas",stp_cage:"Última BPDU [s]",v_setmgmt_q:"¿Fijar {n} como ID de gestión?",v_add:"Añadir una VLAN",v_mgmt_c:"AVISO: debes poder acceder a esta VLAN para seguir gestionando el switch.",v_rename:"Renombrar",v_rename_t:"Renombrar VLAN",v_mgmt_tt:"La VLAN de gestión no se puede borrar, primero hay que asignar la gestión a otra VLAN.",iso_title:"Aislamiento de puertos",iso_from:"Desde el puerto",iso_to:"Hacia el puerto",iso_all:"Permitir todo",iso_intro:"Una trama que entra por un puerto solo puede salir por los puertos marcados en su fila. El aislamiento se decide antes del reenvío, así que un puerto sin marcar es inalcanzable de forma directa, diga lo que diga la tabla de direcciones.",iso_legend:"La celda del propio puerto siempre está permitida. Un número de puerto en rojo indica un puerto que no está del todo abierto.",lacp_heading:"LACP (802.3ad)",lag_mode:"Modo",lag_static:"Estático",lacp_neg:"negociando",lacp_agg:"agregador",lacp_members:"miembros activos",lacp_hint:"LACP: elige los puertos candidatos y pulsa Aplicar para iniciar la negociación",lacp_actor:"Local",lacp_partner:"Remoto",lacp_rxstate:"Estado RX",lacp_rx:"LACPDUs recibidas",lacp_psys:"Sistema remoto",sc_title:"Control de tormentas",sc_h:"por puerto y tipo de tráfico, vacío = desactivado",sc_bcast:"Difusión",sc_mcast:"Multidifusión",sc_ucast:"Unidifusión desconocida",sc_umcast:"Multidifusión desconocida",sc_err:"El límite de tormenta debe ser 1-1048575 pps o 1-10000000 kbit/s",sc_note:"Las tramas de un tipo por encima de su límite se descartan a la entrada del switch.",sy_dns1:"Servidor DNS",sy_dns2:"DNS secundario",sy_dns_err:"IP del servidor DNS no válida",sy_dns_dhcp:"desde DHCP: {ip}",sy_time:"Hora",sy_ntp:"NTP",sy_ntpsrv:"Servidor NTP",sy_ntpsrv_p:"nombre de host o dirección IPv4",sy_ntpint:"Intervalo de consulta [min]",sy_tz:"Zona horaria",sy_dst:"Horario de verano",sy_dst_eu:"Europa",sy_dst_us:"Estados Unidos",sy_now:"Hora local",sy_sync:"Última sincronización",sy_ntpaddr:"Dirección del servidor",sy_nosync:"sin sincronizar",sy_waiting:"esperando al servidor",sy_ago:"hace {n} min",sy_ntp_err:"Introduce el nombre de host o la dirección IPv4 del servidor NTP",sy_ntpint_err:"El intervalo de consulta debe ser 1-1440",sy_time_note:"El switch no tiene reloj con batería: la hora se pierde en cada arranque y se vuelve a fijar desde el servidor NTP cuando la red está lista. Guarda en flash para conservar los ajustes.",cw_longline:"La línea {n} mide {m} bytes: el switch solo puede ejecutar líneas de hasta 126 bytes y la saltaría al arrancar, así que se rechaza la escritura. Acórtala primero.",fw_other:"Esta imagen no se compiló para este switch ({m}): los pines de SFP, LEDs y botones pueden no coincidir. Súbela solo si estás seguro."
},
fr:{
nav_dash:"Tableau de bord",nav_ports:"Ports",nav_stp:"Arborescence",nav_stats:"Statistiques",
@@ -834,6 +840,8 @@ var CONF_CMDS=[
/^isolate\s+\d+(\s+(off|\d+))+$/,
/^stp\s+(on|off)$/,/^stp\s+(prio|hello|maxage|fwd|txhold)\s+\d+$/,
/^stp\s+version\s+(rstp|stp)$/,
/^stp\s+pathcost\s+(long|short)$/,
/^stp\s+bpdu\s+(filter|flood)$/,
/^stp\s+(port\s+\d+|lag\s+[1-4])\s+(on|off)$/,
/^stp\s+(port\s+\d+|lag\s+[1-4])\s+edge\s+(on|off|auto)$/,
/^stp\s+(port\s+\d+|lag\s+[1-4])\s+cost\s+\d+$/,
@@ -1223,12 +1231,14 @@ function fmtBridge(hex){
if(!hex||hex.length<16)return"-";
return parseInt(hex.slice(0,4),16)+" / "+hex.slice(4).replace(/(..)(?=.)/g,"$1:");
}
function stpEnt(pt){pt.lag=pt.p>100?pt.p-100:0}
function stpKey(pt){return pt.lag?"L"+pt.lag:String(pt.p)}
function stpName(pt){return pt.lag?"LAG"+pt.lag+" ("+maskToPorts(pt.mbr).join(",")+")":String(pt.p)}
function stpName(pt){return pt.lag?"LAG"+pt.lag:String(pt.p)}
function stpDhms(x){return Math.floor(x/86400)+"d "+Math.floor(x/3600)%24+"h "+Math.floor(x/60)%60+"m "+x%60+"s"}
function stpPre(k){return k.charAt(0)==="L"?"stp lag "+k.slice(1)+" ":"stp port "+k+" "}
function stpBuild(ports){
var tb=$("stpcfg").tBodies[0],st=$("stpstat").tBodies[0];
tb.innerHTML="";st.innerHTML="";
var tb=$("stpcfg").tBodies[0],st=$("stpstat").tBodies[0],ct=$("stpcnt").tBodies[0];
tb.innerHTML="";st.innerHTML="";ct.innerHTML="";
ports.forEach(function(pt){
var p=stpKey(pt);
var tr=tb.insertRow();
@@ -1246,7 +1256,12 @@ function stpBuild(ports){
tr.insertCell().appendChild(h("button",{class:"ctl",text:t("c_apply"),onclick:function(){stpApplyPort(p)}}));
var sr=st.insertRow();
sr.insertCell().textContent=stpName(pt);
["ro","st","db","dp","dc","oe","op"].forEach(function(k){sr.insertCell().id="st"+k+p});
["ro","st","db","dp","dc","oe","op","pv"].forEach(function(k){sr.insertCell().id="st"+k+p});
sr.insertCell().appendChild(h("button",{class:"ctl",id:"stmc"+p,text:t("stp_mcheck"),title:t("stp_mcheck_t"),
onclick:function(){postCmd(stpPre(p)+"mcheck").then(stpLoad).catch(function(){})}}));
var cr=ct.insertRow();
cr.insertCell().textContent=stpName(pt);
["rx","tx","trx","ttx","age"].forEach(function(k){cr.insertCell().id="sc"+k+p});
});
}
function stpPortVals(pt){
@@ -1260,10 +1275,21 @@ function stpPortVals(pt){
p2p:["auto","on","off"][pt.p2]||"auto",
};
}
function stpCnt(c){
c.ports.forEach(function(pt){
stpEnt(pt);
var p=stpKey(pt);
if(!$("scrx"+p))return;
var v=[0,1,2,3].map(function(k){return parseInt(pt.c.slice(8*k,8*k+8),16)}),age=parseInt(pt.c.slice(32,36),16);
["rx","tx","trx","ttx"].forEach(function(k,i){$("sc"+k+p).textContent=v[i]});
$("scage"+p).textContent=!v[0]?"-":(age===0xffff?">":"")+Math.floor(age/c.hz);
});
}
function stpLoad(){
return getJSON("/stp.json").then(function(s){
s.ports.sort(function(a,b){return((a.lag||0)-(b.lag||0))||(a.p-b.p)});
var sig=s.ports.map(function(pt){return stpKey(pt)+":"+(pt.mbr||0)}).join();
s.ports.forEach(stpEnt);
s.ports.sort(function(a,b){return(a.lag-b.lag)||(a.p-b.p)});
var sig=s.ports.map(stpKey).join();
if(sig!==stpSig){stpSig=sig;stpBuild(s.ports);}
stpCur=s;
var en=$("stpen");
@@ -1271,16 +1297,19 @@ function stpLoad(){
var msg;
if(!s.on)msg=t("stp_off_msg");
else{
var me=(s.prio*4096).toString(16).padStart(4,"0")+s.myMac;
var me=(s.prio*4096).toString(16).padStart(4,"0")+s.myMac,tc=parseInt(s.tc,16),rp="-";
s.ports.forEach(function(pt){if(pt.role===1)rp=stpName(pt)});
msg=t("stp_bridge")+" "+fmtBridge(me)+(s.weRoot
?" ("+t("stp_root_self")+")"
:"; "+t("stp_root")+" "+fmtBridge(s.rootPrio+s.rootMac)+" "+t("stp_via")+" "+s.rootPort+", "+t("stp_cost")+" "+parseInt(s.cost,16))
+"; "+t("stp_tc")+": "+parseInt(s.tc,16);
:"; "+t("stp_root")+" "+fmtBridge(s.rootPrio+s.rootMac)+" "+t("stp_via")+" "+rp+", "+t("stp_cost")+" "+parseInt(s.cost,16))
+"; "+t("stp_tc")+": "+tc+(tc?" ("+t("stp_lasttc")+" "+stpDhms(parseInt(s.tcs,16))+")":"")
+"; "+t("stp_times")+" "+s.rMaxage+" / "+s.rFwd+" s";
}
$("stpids").textContent=msg;
var bc=$("stpbridge");
if(!bc.dataset.dirty){
$("stpver").value=s.rstp?"rstp":"stp";
$("stppcs").value=s.pcs?"short":"long";$("stpbh").value=s.bh?"filter":"flood";
$("stpprio").value=String(s.prio);
$("stphello").value=s.hello;$("stpmaxage").value=s.maxage;
$("stpfwd").value=s.fwd;$("stptxhold").value=s.txhold;
@@ -1289,18 +1318,21 @@ function stpLoad(){
var p=stpKey(pt);
var trip=(pt.f&STP_PF.TRIP)?" "+badge(t("stp_trip"),"bad"):"";
$("stro"+p).textContent=s.on&&pt.role?t("stp_r"+pt.role):"-";
$("stst"+p).innerHTML=s.on?badge(t("stp_s"+pt.st),pt.st===3?"ok":"")+trip:"-";
$("stst"+p).innerHTML=s.on?(pt.lk?badge(t("stp_s"+pt.st),pt.st===3?"ok":""):badge(t("stp_nolink")))+trip:"-";
$("stdb"+p).textContent=s.on?fmtBridge(pt.db):"-";
$("stdp"+p).textContent=s.on?parseInt(pt.dp.slice(0,2),16)+"."+parseInt(pt.dp.slice(2),16):"-";
$("stdc"+p).textContent=s.on?parseInt(pt.dc,16):"-";
$("stoe"+p).textContent=s.on?t((pt.f&STP_PF.OPEREDGE)?"c_yes":"c_no"):"-";
$("stop"+p).textContent=s.on?t(pt.p2===2?"c_no":"c_yes"):"-";
$("stpv"+p).textContent=s.on&&pt.role?(s.rstp&&!pt.lg?"RSTP":"STP"):"-";
$("stmc"+p).disabled=!(s.on&&pt.role&&s.rstp);
var row=$("sten"+p).closest("tr");
if(row.dataset.dirty)return;
var v=stpPortVals(pt);
$("sten"+p).checked=v.en;$("sted"+p).value=v.edge;$("stco"+p).value=v.cost;
$("stpr"+p).value=v.prio;$("stgu"+p).value=v.guard;$("stfi"+p).value=v.filter;$("stpp"+p).value=v.p2p;
});
if($("stpcntd").open)return getJSON("/stpcnt.json").then(stpCnt);
}).catch(function(){});
}
function stpApplyPort(p){
@@ -1328,6 +1360,8 @@ $("stpbapply").addEventListener("click",function(){
var ver=$("stpver").value,prio=$("stpprio").value;
if(ver!==(s.rstp?"rstp":"stp"))cmds.push("stp version "+ver);
if(prio!==String(s.prio))cmds.push("stp prio "+prio);
if($("stppcs").value!==(s.pcs?"short":"long"))cmds.push("stp pathcost "+$("stppcs").value);
if($("stpbh").value!==(s.bh?"filter":"flood"))cmds.push("stp bpdu "+$("stpbh").value);
[["stphello","hello"],["stpmaxage","maxage"],["stpfwd","fwd"],["stptxhold","txhold"]].forEach(function(f){
var v=$(f[0]).value;
if(String(v)!==String(s[f[1]]))cmds.push("stp "+f[1]+" "+v);
@@ -1346,6 +1380,7 @@ $("stpen").addEventListener("change",function(){
var sel=$("stpprio");
for(var i=0;i<16;i++)sel.appendChild(h("option",{value:String(i),text:i+" ("+(i*4096)+")"}));
})();
$("stpcntd").addEventListener("toggle",function(){if(this.open)stpLoad()});
var stpPoller=new Poller(stpLoad,3000);
tabHooks.stp={enter:function(){stpPoller.start()},leave:function(){stpPoller.stop()}};
+8 -1
View File
@@ -264,6 +264,8 @@ 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>
@@ -281,9 +283,14 @@ 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></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><th data-i18n="stp_proto">Protocol</th><th></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">
+2
View File
@@ -1055,6 +1055,8 @@ void httpd_appcall(void)
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")) {
+7 -149
View File
@@ -600,143 +600,6 @@ void send_lag(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)
{
__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(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;
}
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 && !MACHINE_PORT_VALID(i))
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(i < STP_LAG_BASE ? machine.log_to_phys_port[i] : 0);
slen += strtox(outbuf + slen, ",\"lag\":");
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_lacp(void)
{
dbg_string("send_lacp called\n");
@@ -810,12 +673,12 @@ void send_lldp(void)
if (i < MACHINE_MAX_PORT)
char_to_html(',');
else
char_to_html(']');
}
char_to_html(']');
char_to_html('}');
}
void send_eee(void)
{
dbg_string("send_eee called\nsending EEE status\n");
@@ -859,9 +722,8 @@ void send_eee(void)
char_to_html('}');
if (i < MACHINE_MAX_PORT)
char_to_html(',');
else
char_to_html(']');
}
char_to_html(']');
}
@@ -902,9 +764,8 @@ void send_bandwidth(void)
char_to_html('}');
if (i < MACHINE_MAX_PORT)
char_to_html(',');
else
char_to_html(']');
}
char_to_html(']');
}
@@ -936,9 +797,8 @@ void send_storm(void)
char_to_html('}');
if (i < MACHINE_MAX_PORT)
char_to_html(',');
else
char_to_html(']');
}
char_to_html(']');
}
@@ -982,9 +842,8 @@ void send_mtu(void)
char_to_html('}');
if (i < MACHINE_MAX_PORT)
char_to_html(',');
else
char_to_html(']');
}
char_to_html(']');
}
@@ -1117,9 +976,8 @@ void send_status(void)
slen += strtox(outbuf + slen, "\"}");
if (i < MACHINE_MAX_PORT)
char_to_html(',');
else
char_to_html(']');
}
char_to_html(']');
}
+2 -1
View File
@@ -22,9 +22,10 @@ void send_isolation(void);
void send_config(void);
void send_cmd_log(void);
void send_lag(void);
void send_stp(void);
void send_stp(void) __banked;
void send_lldp(void);
void send_lacp(void);
void send_stp_counters(void) __banked;
void send_vlanlist(void);
/* Convert only the lower nibble to ascii HEX char.
+195
View File
@@ -0,0 +1,195 @@
#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, "]}");
}
+1
View File
@@ -2,6 +2,7 @@
__code const struct machine machine = {
MACHINE_COMMON_SETTING,
.mac_flash_offset = 0x1FC000,
.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},
+54
View File
@@ -0,0 +1,54 @@
#include "machine_defs.h"
__code const struct machine machine = {
MACHINE_COMMON_SETTING,
.n_sfp = 2,
.log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6},
.phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0},
.is_sfp = {0, 0, 0, 1, 0, 0, 0, 0, 2},
// SFP port on SDS0 / logical port 3
.sfp_port[0].pin_detect = GPIO37,
.sfp_port[0].pin_los = GPIO_NA,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 0,
.sfp_port[0].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ),
// SFP port on SDS1 / logical port 8
.sfp_port[1].pin_detect = GPIO38,
.sfp_port[1].pin_los = GPIO_NA,
.sfp_port[1].pin_tx_disable = GPIO_NA,
.sfp_port[1].sds = 1,
.sfp_port[1].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
.high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1 },
.led_sets = {
{
LEDS_2G5 | LEDS_LINK | LEDS_ACT,
LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT,
0,
0
},
{
LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT,
LEDS_10G | LEDS_LINK | LEDS_ACT,
0,
0
},
},
.led_mux_custom = 1,
.led_mux = {
0x00, 0x01, 0x04, 0x05, 0x08,
0x09, 0x0c, 0x3f, 0x0d, 0x10,
0x11, 0x3f, 0x14, 0x3f, 0x3f,
0x15, 0x3f, 0x3f, 0x18, 0x3f,
0x3f, 0x19, 0x3f, 0x3f, 0x1c,
0x1d, 0x20, 0x21
},
};
// Boot 1-4G 6Y
// 1-4Y 5G 6Y
+7
View File
@@ -0,0 +1,7 @@
#ifndef _MACHINE_SWTG024AS_V2_1_0_H_
#define _MACHINE_SWTG024AS_V2_1_0_H_
#define MACHINE_MODEL "SWTG024AS-V2.1.0"
#define MACHINE_CHIPSET RTL8372
#endif
+22
View File
@@ -0,0 +1,22 @@
#include "machine_init_defs.h"
void machine_custom_init(void) __banked
{
// Stock LED_MODE: [8:5]=1, [12:9]=2 (leds_setup hard-codes 5/3); same as clearing bits 7 and 9
reg_bit_set(RTL837X_REG_LED_MODE, 17);
reg_bit_clear(RTL837X_REG_LED_MODE, 9);
reg_bit_clear(RTL837X_REG_LED_MODE, 7);
// Stock LED_GLB_IO_EN [27:0] = 0x0f249740: enable LED pins 6, 8, 9, 10, 12, 15, 18, 21, 24-27
reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 6);
reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 8);
reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 9);
reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 10);
reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 12);
reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 15);
reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 18);
reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 21);
reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 24);
reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 25);
reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 26);
}
+53
View File
@@ -0,0 +1,53 @@
#include "machine_defs.h"
// Sirivision S25G1109FP V1.3 (label: SR-S25G2109F): 8x2.5G RJ45 + 1x10G SFP+,
// 2 MB flash, RTL8373 (non-N) + RTL8224. Derived from the KP-9000-9XHML-X V3.1
// definition; every value below was confirmed on hardware, see the device doc
// in doc/devices/S25G1109FP_V1_3.md.
__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},
/* ModAbs is GPIO30 (low with a module inserted, high with an empty cage),
* LOS is GPIO37 (high without light). GPIO38, the ModAbs pin of the
* KP-9000 8+1 reference, is tied low here and never changes, which made the
* firmware believe a module was always present. */
.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 button: GPIO54 reads high when idle and low while pressed.
* GPIO48, used by the KP-9000 reference, never changes on this board. */
.reset_pin = GPIO54_ACL_BIT2_EN,
.high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1},
.led_sets = {
{ /* RJ45: left LED = link/activity, right LED = 2.5G */
LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT,
/* speed-only conditions leave the LED dark: the LINK bit has to be
* set as well, otherwise the LED controller stays off */
LEDS_2G5 | LEDS_LINK,
0,
0,
}, { /* SFP PORT, SINGLE GREEN LED */
LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G,
0,
0,
0,
}},
/* 6 bits per LED pin, encoded as (port << 2) | led. This is the chip
* default for port-ordered wiring (pin 3n+m = port n+1, function m) with
* one change: pin 25 drives the SFP LED with function 0, the only function
* populated in led_sets[1]. See the device doc for the details. */
.led_mux_custom = 1,
.led_mux = {0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0a, 0x0c,
0x0d, 0x0e, 0x10, 0x11, 0x12, 0x14, 0x15, 0x16, 0x18, 0x19,
0x1a, 0x1c, 0x1d, 0x1e, 0x20, 0x20, 0x22, 0x23},
/* Factory MAC at flash 0x1FC000, verified in the stock dump. The stock
* value is a locally administered address, which is rejected, so a
* UUID-derived MAC is used unless a valid one is stored here. */
.mac_flash_offset = 0x1FC000,
};
+10
View File
@@ -0,0 +1,10 @@
#ifndef _MACHINE_S25G1109FP_V1_3_H_
#define _MACHINE_S25G1109FP_V1_3_H_
/* machine_name[] in struct machine is 30 bytes -> keep this <= 29 chars */
/* The product is sold as SR-S25G2109F; S25G1109FP is the PCB silkscreen and
* belongs to a different (unmanaged PoE) model that shares this PCB. */
#define MACHINE_MODEL "Sirivision SR-S25G2109F"
#define MACHINE_CHIPSET RTL8373
#endif
+1
View File
@@ -2,6 +2,7 @@
__code const struct machine machine = {
MACHINE_COMMON_SETTING,
.mac_flash_offset = 0x1FC000,
.n_sfp = 2,
.log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5},
.phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0},
+892 -488
View File
File diff suppressed because it is too large. Load diff
+31 -7
View File
@@ -2,13 +2,21 @@
#define _RTL837X_STP_H_
#include <stdint.h>
#include "rtl837x_common.h"
void stp_in(void) __banked;
void stp_setup(void) __banked;
void stp_timers(void) __banked;
void stp_off(void) __banked;
void stp_parse(void) __banked __reentrant;
void stp_defaults(void) __banked;
void stp_status(void) __banked;
void stp_port_admin(uint8_t port, uint8_t on) __banked;
void stp_prio_apply(void) __banked;
void stp_port_mcheck(uint8_t port) __banked;
uint8_t stp_port_role(uint8_t port) __banked;
uint8_t stp_port_state(uint8_t port) __banked;
uint8_t stp_ent_id(uint8_t e) __banked;
void stp_lag_map(void) __banked;
void stp_counters_clear(void) __banked __reentrant;
/* Tick rate of stp_timers(), also used by the web UI. */
#define STP_HZ 50
@@ -43,22 +51,38 @@ extern __xdata uint8_t stp_txhold;
#define STP_PF_TRIPPED 0x80 /* runtime: disabled by BPDU guard */
extern __xdata uint8_t stp_pflags[STP_ENTITIES];
extern __xdata uint32_t stp_pcost[STP_ENTITIES]; /* path cost; 0 = auto (20000) */
extern __xdata uint32_t stp_pcost[STP_ENTITIES];
extern __xdata uint8_t stp_pprio[STP_ENTITIES];
extern __xdata uint8_t stp_pp2p[STP_ENTITIES];
extern __xdata uint8_t stp_ent_of[STP_PORTS]; /* the STP entity a port answers to: itself, or its lag */
extern __xdata uint16_t stp_lag_mask[STP_LAG_COUNT]; /* member ports of each lag; 0 = not part of STP */
extern __xdata uint8_t stp_ent_of[STP_PORTS];
extern __xdata uint16_t stp_lag_mask[STP_LAG_COUNT];
/* Last-heard designated info per port (from received BPDUs); consult
* stp_bpdu_age to decide whether it is still current. */
extern __xdata struct bridge stp_dbridge[STP_ENTITIES];
extern __xdata uint16_t stp_dpid[STP_ENTITIES];
extern __xdata uint32_t stp_dcost[STP_ENTITIES];
extern __xdata uint16_t stp_bpdu_age[STP_ENTITIES]; /* ticks since a BPDU was heard */
extern __xdata uint16_t stp_bpdu_age[STP_ENTITIES];
#define STP_CNT_RX 0 /* BPDUs taken in on an STP port */
#define STP_CNT_TX 1 /* BPDUs put on the wire */
#define STP_CNT_TCRX 2 /* BPDUs received with the TC flag, TCNs */
#define STP_CNT_TCTX 3 /* BPDUs sent with the TC flag, TCNs */
#define STP_CNT_N 4
extern __xdata uint32_t stp_cnt[STP_CNT_N][STP_ENTITIES];
extern __xdata uint16_t stp_info_while[STP_ENTITIES];
extern __xdata struct bridge root_bridge;
extern __xdata uint32_t root_bridge_cost;
extern __xdata uint8_t stp_root_port;
extern __xdata uint8_t stp_root_maxage;
extern __xdata uint8_t stp_root_fwd;
extern __xdata uint16_t stp_link_prev;
extern __xdata uint16_t stp_alt;
extern __xdata uint16_t stp_backup;
extern __xdata uint16_t stp_legacy;
extern __xdata uint16_t stp_rrwhile[STP_ENTITIES];
extern __xdata uint16_t stp_rbwhile[STP_ENTITIES];
extern __xdata uint16_t stp_tc_count;
extern __xdata uint32_t stp_tc_secs;
extern __xdata uint8_t stp_pcost_short;
extern __xdata uint8_t stp_bpdu_filter;
#endif
+228
View File
@@ -0,0 +1,228 @@
/*
* Command line interface of the Spanning Tree Protocol for the RTL837x platform
* This code is in the Public Domain
*/
#include <stdint.h>
#include "rtl837x_common.h"
#include "rtl837x_stp.h"
#include "machine.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];
uint8_t cmd_compare(uint8_t start, __code const uint8_t * cmd);
uint8_t atoi_byte(uint8_t idx);
uint8_t cmd_parse_port_separator(uint8_t idx);
extern __xdata uint8_t atoi_results_u8;
extern __xdata char save_cmd;
extern __xdata uint8_t err_status;
#define TIMES_OK(h, m, f) ((uint16_t)2 * ((f) - 1) >= (m) && (m) >= (uint16_t)2 * ((h) + 1))
__xdata uint32_t stp_cli_cost;
__xdata uint8_t stp_cli_val;
void stp_parse(void) __banked __reentrant
{
uint8_t port;
if (cmd_compare(1, "on")) {
print_string("STP enabled\n");
stp_enabled = 1;
stp_setup();
return;
}
if (cmd_compare(1, "off")) {
print_string("STP disabled\n");
stp_off();
stp_enabled = 0;
return;
}
if (cmd_compare(1, "status")) {
stp_status();
return;
}
if (cmd_compare(1, "clear")) {
stp_counters_clear();
return;
}
if (cmd_words_len < 3)
goto err;
if (cmd_compare(1, "port") || cmd_compare(1, "lag")) {
if (cmd_words_len < 4)
goto err;
stp_lag_map();
if (cmd_compare(1, "lag")) {
if (!atoi_byte(cmd_words_b[2]) || !atoi_results_u8 || atoi_results_u8 > STP_LAG_COUNT)
goto err;
port = STP_LAG_BASE - 1 + atoi_results_u8;
} else {
if (!cmd_parse_port_separator(cmd_words_b[2]))
goto err;
port = atoi_results_u8;
if (stp_ent_of[port] != port) {
print_string("Error: the port is in lag ");
write_char('1' + stp_ent_of[port] - STP_LAG_BASE);
write_char('\n');
err_status = ERR_INVALID_ARGUMENT;
return;
}
}
if (cmd_words_len < 5 && !cmd_compare(3, "on") && !cmd_compare(3, "off")
&& !cmd_compare(3, "mcheck"))
goto err;
if (cmd_compare(3, "on")) {
stp_pflags[port] |= STP_PF_ENABLED;
stp_pflags[port] &= ~STP_PF_TRIPPED;
if (stp_enabled) { /* (re)join: listen first */
stp_port_admin(port, 1);
}
} else if (cmd_compare(3, "mcheck")) {
if (stp_enabled)
stp_port_mcheck(port);
} else if (cmd_compare(3, "off")) {
stp_pflags[port] &= ~STP_PF_ENABLED;
if (stp_enabled)
stp_port_admin(port, 0); /* plain forwarding */
} else if (cmd_compare(3, "edge")) {
/* Also drop the *operational* edge flag: it is what exempts the
* port from topology changes and lets it skip the listen period,
* so leaving it set would keep the old behaviour until the next
* "stp off"/"stp on". An admin edge is operational immediately. */
stp_pflags[port] &= ~(STP_PF_ADMEDGE | STP_PF_AUTOEDGE | STP_PF_OPEREDGE);
if (cmd_compare(4, "on"))
stp_pflags[port] |= STP_PF_ADMEDGE | STP_PF_OPEREDGE;
else if (cmd_compare(4, "auto"))
stp_pflags[port] |= STP_PF_AUTOEDGE;
else if (!cmd_compare(4, "off"))
goto err;
} else if (cmd_compare(3, "cost")) {
/* raw 802.1D value, 0..200000000; 0 = auto (speed-based) */
stp_cli_cost = 0;
{
__xdata uint8_t *cp = &cmd_buffer[cmd_words_b[4]];
if (*cp < '0' || *cp > '9')
goto err;
while (*cp >= '0' && *cp <= '9') {
stp_cli_cost = stp_cli_cost * 10 + (*cp - '0');
cp++;
}
}
if (stp_cli_cost > 200000000UL)
goto err;
stp_pcost[port] = stp_cli_cost;
} else if (cmd_compare(3, "p2p")) {
if (cmd_compare(4, "auto"))
stp_pp2p[port] = 0;
else if (cmd_compare(4, "on"))
stp_pp2p[port] = 1;
else if (cmd_compare(4, "off"))
stp_pp2p[port] = 2;
else
goto err;
} else if (cmd_compare(3, "prio")) {
if (!atoi_byte(cmd_words_b[4]))
goto err;
if (atoi_results_u8 > 240 || (atoi_results_u8 & 0x0f))
goto err;
stp_pprio[port] = atoi_results_u8;
} else if (cmd_compare(3, "guard")) {
stp_pflags[port] &= ~(STP_PF_BPDUGUARD | STP_PF_ROOTGUARD);
if (cmd_compare(4, "bpdu"))
stp_pflags[port] |= STP_PF_BPDUGUARD;
else if (cmd_compare(4, "root"))
stp_pflags[port] |= STP_PF_ROOTGUARD;
else if (!cmd_compare(4, "none"))
goto err;
} else if (cmd_compare(3, "filter")) {
if (cmd_compare(4, "on"))
stp_pflags[port] |= STP_PF_FILTER;
else if (cmd_compare(4, "off"))
stp_pflags[port] &= ~STP_PF_FILTER;
else
goto err;
} else {
goto err;
}
return;
}
if (!atoi_byte(cmd_words_b[2])) {
if (cmd_compare(1, "version")) {
if (cmd_compare(2, "rstp"))
stp_rstp = 1;
else if (cmd_compare(2, "stp"))
stp_rstp = 0;
else
goto err;
return;
}
if (cmd_compare(1, "pathcost")) {
if (cmd_compare(2, "long"))
stp_pcost_short = 0;
else if (cmd_compare(2, "short"))
stp_pcost_short = 1;
else
goto err;
return;
}
if (cmd_compare(1, "bpdu")) {
if (cmd_compare(2, "filter"))
stp_bpdu_filter = 1;
else if (cmd_compare(2, "flood"))
stp_bpdu_filter = 0;
else
goto err;
if (!stp_enabled)
stp_off();
return;
}
goto err;
}
stp_cli_val = atoi_results_u8;
if (cmd_compare(1, "prio")) {
if (stp_cli_val > 15)
goto err;
stp_prio = stp_cli_val << 4; /* n * 4096, as the BPDU's high byte */
stp_prio_apply();
} else if (cmd_compare(1, "hello")) {
if (stp_cli_val < 1 || stp_cli_val > 10)
goto err;
if (save_cmd && !TIMES_OK(stp_cli_val, stp_maxage_s, stp_fwddelay_s))
goto times;
stp_hello_s = stp_cli_val;
} else if (cmd_compare(1, "maxage")) {
if (stp_cli_val < 6 || stp_cli_val > 40)
goto err;
if (save_cmd && !TIMES_OK(stp_hello_s, stp_cli_val, stp_fwddelay_s))
goto times;
stp_maxage_s = stp_cli_val;
} else if (cmd_compare(1, "fwd")) {
if (stp_cli_val < 4 || stp_cli_val > 30)
goto err;
if (save_cmd && !TIMES_OK(stp_hello_s, stp_maxage_s, stp_cli_val))
goto times;
stp_fwddelay_s = stp_cli_val;
} else if (cmd_compare(1, "txhold")) {
if (stp_cli_val < 1 || stp_cli_val > 10)
goto err;
stp_txhold = stp_cli_val;
} else {
goto err;
}
return;
times:
print_string("Error: needs 2*(fwd-1) >= maxage >= 2*(hello+1)\n");
err_status = ERR_INVALID_ARGUMENT;
return;
err:
err_status = ERR_INVALID_ARGUMENT;
print_string("Error: stp on|off|status|clear | prio <0-15> | hello <1-10> | maxage <6-40> | fwd <4-30> | txhold <1-10> | version rstp|stp | pathcost long|short | bpdu filter|flood | port <1-9>|lag <1-4> on|off|mcheck|edge|cost|prio|guard|filter ...\n");
}
+410
View File
@@ -0,0 +1,410 @@
; 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, 0xc7
.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
;
; print the address of a register read The address is in R2/R3. Registers 4-7 are scratch
;
reg_read_head:
mov a, #'r'
acall putc
mov a, r2
acall wbyte
mov a, r3
acall wbyte
mov a, #':'
acall putc
clr a
mov r7, a
mov r6, a
mov r5, a
mov r4, a
ret
reg_read_tail:
mov dptr, #0xc5
movx a, @dptr
acall wbyte
mov dptr, #0xc5 + 1
movx a, @dptr
acall wbyte
mov dptr, #0xc5 + 2
movx a, @dptr
acall wbyte
mov dptr, #0xc5 + 3
movx a, @dptr
acall wbyte
mov a, #' '
acall putc
mov dptr,#0xc5
ret
;
; print phy write options
;
phy_write:
mov a, #'P'
acall putc
mov a, r4
acall wbyte
; phymask
mov a, r6
acall wbyte
mov a, r7
acall wbyte
mov a, #'.'
acall putc
; dev_id?
mov a, r5
acall wbyte
; x_phywrite_opt
mov dptr, #0xf1
movx a, @dptr
acall wbyte
; The register going to SFR_C2 and SFR_C3
mov a, r2
acall wbyte
mov a, r3
acall wbyte
mov a, #':'
acall putc
mov dptr, #0xf2
movx a, @dptr
acall wbyte
mov dptr, #0xf3
movx a, @dptr
acall wbyte
mov a, #' '
acall putc
mov dptr, #0xeb
mov r4, #0
ret
;
; print phy read options
;
phy_read_head:
mov r1, 4 ; actually r4
mov a, #'p'
acall putc
mov a, r7
acall wbyte
mov a, r5
acall wbyte
mov a, #'.'
acall putc
mov a, r2
acall wbyte
mov a, r3
acall wbyte
mov a, #':'
acall putc
mov r1, 7
mov a, r2
ret
phy_read_tail:
mov r6, 4
mov a, r6
acall wbyte
mov a, r7
acall wbyte
mov a, #' '
acall putc
mov r4, 6
ret
;
; print SDS write options
;
sds_write:
mov a, r4
mov r2, a ; R2 is scratch
mov a, #'Q'
acall putc
mov a, r7
acall wbyte
mov a, r5
acall wbyte
mov a, r3
acall wbyte
mov a, #':'
acall putc
mov dptr, #0xe1
movx a, @dptr
acall wbyte
mov dptr, #0xe2
movx a, @dptr
acall wbyte
mov a, #' '
acall putc
mov a, r7
mov r1, a
mov a, r5
mov r7, a
mov a, r2
mov r4, a
ret
;
; print SDS read options
;
sds_read_head:
mov a, r4
mov r1, a ; R1 is scratch
mov a, #'q'
acall putc
mov a, r7 ; sds_index bit
acall wbyte
mov a, r5 ; SFR_94 (2 x r5 | r7)
acall wbyte
mov a, r3 ; SFR_93
acall wbyte
mov a, #':'
acall putc
mov a, r1
mov r4, a ; R1 is scratch
mov a, r7
mov r1, a
mov a, r5
mov r7, a
ret
;
; print SDS read result
;
sds_read_tail:
mov a, r4
mov r6, a ; R6 is returned and preserved
mov a, r7 ; = r7 = SFR_DATA_8
acall wbyte
mov r7, SFR_DATA_0
mov a, r7 ; SFR_DATA_0
acall wbyte
mov a, #' '
acall putc
mov a, r6 ; R6 is returned and preserved
mov r4, a
ret
;
; Write a byte as hex to serial
;
nop
nop
wbyte:
mov r4, a
swap a
anl a, #0x0f
mov dptr,#hex
movc a, @a+dptr
jb ET0, wbyte1
ret
wbyte1:
jbc TI, wbyte2
sjmp wbyte1
wbyte2:
mov SBUF,a
mov a, r4
anl a, #0x0f
mov dptr,#hex
movc a, @a+dptr
wbyte3:
jbc TI, wbyte4
sjmp wbyte3
wbyte4:
mov SBUF,a
ret
;
; Intercept isr_ext0
;
isr_ext0:
mov a, #'i'
acall putc
mov r0,#0x51
inc @r0
ret
;
; Intercept isr_ext1
;
isr_ext1:
mov a, #'I'
acall putc
mov r0,#0x25
mov a, @r0
ret
;
; Intercept isr_ext2
;
isr_ext2:
mov a, #'J'
acall putc
mov r0, #0x1e
mov a, #0xa
ret
;
; Intercept isr_ext3
;
isr_ext3:
mov a, #'j'
acall putc
mov r0,#0x1e
mov a,#0x14
ret
;
; Write a character to serial
;
nop
nop
putc:
jb ET0, putc1
ret
putc1:
jbc TI,putc2
sjmp putc1
putc2:
mov SBUF,a
ret
hex:
.byte "0123456789abcdef"
.end
+6
View File
@@ -121,6 +121,12 @@ uint8_t stp_pflags[STP_ENTITIES];
uint8_t stp_pp2p[STP_ENTITIES];
uint8_t stp_pprio[STP_ENTITIES];
uint8_t stp_prio = 0x80, stp_root_port = 0xff, stp_rstp = 1, stp_txhold = 6;
uint8_t stp_bpdu_filter, stp_pcost_short, stp_root_fwd = 15, stp_root_maxage = 20;
uint16_t stp_legacy, stp_link_prev = 0x3ff;
uint16_t stp_info_while[STP_ENTITIES];
uint32_t stp_cnt[STP_CNT_N][STP_ENTITIES];
uint32_t stp_tc_secs;
uint8_t stp_ent_id(uint8_t e) { return e >= STP_LAG_BASE ? e + 101 - STP_LAG_BASE : machine.log_to_phys_port[e]; }
/* ---- SFP: no module present, reads fail ---- */
uint8_t sfp_buf[16];
+29
View File
@@ -0,0 +1,29 @@
CC ?= cc
SRCROOT = ../..
CFLAGS = -std=c11 -g -O1 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unknown-pragmas \
-Wno-pointer-sign \
-fpack-struct=1 \
-DRTLP_HOST_TEST \
-fsanitize=address,undefined -fno-omit-frame-pointer \
-include ../sdcc_shim.h -I../stub -I$(SRCROOT) -I$(SRCROOT)/httpd -I$(SRCROOT)/uip
ifeq ($(findstring clang,$(shell $(CC) --version 2>/dev/null)),)
CFLAGS += -Wno-builtin-declaration-mismatch
endif
LDFLAGS = -fsanitize=address,undefined
SRCS = stp_sim.c $(SRCROOT)/rtl837x_stp.c $(SRCROOT)/rtl837x_stp_cli.c
all: build/stp_sim
./build/stp_sim
verbose: build/stp_sim
./build/stp_sim -v
build/stp_sim: $(SRCS) $(SRCROOT)/rtl837x_stp.h
mkdir -p build
$(CC) $(CFLAGS) $(SRCS) -o $@ $(LDFLAGS)
clean:
rm -rf build
.PHONY: all verbose clean
+2381
View File
File diff suppressed because it is too large. Load diff