714 Commits
Author SHA1 Message Date
logicog 7621045de1 ChaCha20 implementation for encryption
Add an implementation in 8051 assembler for the generation of a
ChaCha20 keystream and an encryption function using the keystream
in C.

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

Keys, nonce, and block counter and the pointers to the input plaintext,
its length and the output pointer are provided via a struct chacha20,
which also holds the working state of the ChaCha20 algorithm
2026-10-05 09:27:52 +02:00
René van Dorst 36a064d03d Merge pull request #554 from DrDoof/cleanup/vlanlist-dead-test
httpd: drop a test in send_vlanlist() that can never be true
2026-10-05 06:11:32 +00:00
DrDoof 2d01b8b7d7 httpd: drop a test in send_vlanlist() that can never be true
vlan_get() only returns an error for VLAN 4095 and up, and the loop stops
at 4094.
2026-10-05 04:35:07 +02:00
René van Dorst ff57084799 Merge pull request #405 from DrDoof/feat/isolation-gui
isolate: configure port isolation from the web interface
2026-10-04 20:47:04 +00:00
René van Dorst 2dc5520928 Merge pull request #545 from brunoz78/kp9000-v3_2
machine: add keepLink KP-9000-9XHML-X V3.2 (2M-PCB23-V3.2)
2026-10-04 20:19:38 +00:00
d00f 7c11142a04 isolate: configure port isolation from the web interface
A matrix of the ports, one row per ingress port, showing which ports a
frame entering there may leave through. The port number of a row turns
red when the port is not fully open, and the cell for the port itself is
marked since it is always allowed. Apply sends one "isolate" command per
changed row, and Allow all ticks the whole matrix.

The switch side is a new /isolation.json, which reports each port's
allowance mask read back from the hardware. The page is an Isolation
section of the single-page UI.
2026-10-04 22:11:19 +02:00
feelfree69 957a5b0e23 Merge pull request #549 from DrDoof/fix/strcpy-definition
Bring back the definition of strcpy
2026-10-04 21:50:09 +02:00
DrDoof c773230a9b Bring back the definition of strcpy
494320b removed strcpy() from rtl837x_flash.c but left its declaration
in rtl837x_common.h. Since then the linker has taken strcpy from the
SDCC library instead, which expects the destination as a generic pointer
in DPL, DPH and B. The callers pass an __xdata pointer and leave B as it
is, so the copy lands wherever B happens to point. boot.c only gets away
with it because B is zero there; a call with B set, as from the ChaCha20
test in #207, writes into internal RAM and stops the 8051.
2026-10-04 19:12:24 +02:00
logicog 0e9c997d06 Merge pull request #543 from brunoz78/i18n-de
web: add German (de) translation
2026-10-04 07:20:15 +02:00
logicog 5ab247e996 Merge pull request #535 from DrDoof/fix/igmp-v2
igmp: handle IGMPv1/v2 reports and v2 leaves
2026-10-04 07:16:52 +02:00
logicog d80717f3f0 Merge pull request #537 from dobodu/main
Update French translations and port configurations
2026-10-04 06:48:33 +02:00
Brunoz78andClaude Opus 5.5 2e24b24426 doc: add a photo of the 2M-PCB23-V3.2 board
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 17:35:00 +02:00
Brunoz78andClaude Opus 5.5 b71239f394 machine: add keepLink KP-9000-9XHML-X V3.2 (2M-PCB23-V3.2)
The V3.2 board has the same component placement as V3.1 and works with
its machine definition: RJ45 links at 2.5G, LEDs, SFP detection with DOM
readings at 10G, factory MAC at 0x1FC000 and the reset button. The new
target only reports "V3.2" as the machine name.

Installed from the original firmware V100.9.25 through its web
interface with the OEM installer image. Reading the flash in-circuit with
a SOIC-8 clip gave the chip ID EF FF FF, as on a V3.1 board.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 17:10:01 +02:00
René van Dorst 6e69f5bba3 Merge pull request #541 from odensc/patch-1
feat: add GoodTop GT-ST018M as supported device
2026-10-03 09:07:30 +00:00
logicog 1224335b66 Merge pull request #544 from HiroGitea/fix/storm-const-sdcc46
Fix SDCC 4.6.0 const warnings in storm control
2026-10-03 10:55:16 +02:00
Hiro e5b19bc594 storm: add the const qualifiers SDCC 4.6 asks for
Follow-up to #494. Storm control (#455) was merged after it and brings
three objects in code space without const: the extern declaration of
machine in rtl837x_storm.c and the name tables storm_types and
storm_names. SDCC 4.6.0 reports them as warnings 356 and 357, which
fail the build with CI=1 (--Werror). SDCC 4.5, which the CI uses, does
not report them.

The declarations now take the form used in the rest of the tree, as in
mime_strings in httpd.c. The images for KP_9000_6XHML_X2 and
KP_9000_9XHML_X_V3_1 are byte-identical before and after.
2026-10-03 12:02:08 +09:00
Brunoz78andClaude Opus 5.5 89fcc6836d web: add German (de) translation
Adds a German dictionary to LANG in app.js (all 317 keys, placeholders
unchanged), the language selector entry in index.html and the login
strings in login.html. Uses Swiss spelling (ss instead of ß), which
reads correctly for all German speakers.

Checked against a mock of the httpd endpoints: every section renders
with the German strings; no layout changes compared to English.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 21:28:57 +02:00
Oden f9d4ab0074 feat: add GoodTop device to SWTG018AS_A_V2_0 PCB doc 2026-10-01 18:37:10 -07:00
Oden 6f68ccef28 feat: add GoodTop GT-ST018M as supported device 2026-10-01 18:35:38 -07:00
dobodu 8e687ef28e Update firmware messages and regex patterns
Correction to missing 100m and small french syntax errors
2026-10-01 20:52:58 +02:00
DrDoof 87db2dd4b8 igmp: handle IGMPv1/v2 reports and v2 leaves
Only frames to 01:00:5e:00:00:16 reached the IGMP handler, so v1/v2
reports, which go to the group address, were never seen. The handler
also took the group from the first v3 group record for every type.

Pass all IPv4 multicast to the handler, which takes the IGMP header
from the IP header length, the group from the IGMP header for v1/v2
reports and v2 leaves and from the first group record for v3, and
returns 0 for anything that is not IGMP so it goes on to the IP stack.
The message and record types have names now.
2026-10-01 20:42:22 +02:00
dobodu aac1aed4c8 Update LINKS array to include '100M' 2026-10-01 12:08:53 +02:00
logicog f0aea3dcac Merge pull request #455 from DrDoof/feat/storm-control
storm: limit broadcast, multicast and unknown unicast and multicast per port
2026-10-01 08:05:37 +02:00
dobodu f10ab30f6d Update French translations and port configurations
Various updates, improvements and corrections to the french translation
2026-09-30 23:06:30 +02:00
DrDoof de431c6d84 storm: meters from 24 up, plainer parser, documentation from the review
Storm control used meters 0 to 39, the same meters the ACL hands out,
so a storm limit and an acl meter could overwrite each other. It now
uses 24 + port * 4 + type, meters 24 to 63.

parse_storm() is no longer reentrant and uses plain locals, looks the
type up in a while loop and returns straight from the one-line cases.
storm_show() uses plain locals too.

The documentation says how IGMP decides between mcast and umcast, how
to get from a meter index to its limit, where the IFG bit of a meter
is, and what an ingress bandwidth limit on the same port does to the
storm limit.
2026-09-30 22:48:31 +02:00
DrDoof 26547f3e01 storm: limit broadcast, multicast and unknown unicast and multicast per port 2026-09-29 21:46:56 +02:00
d00f 13b75a73c9 Merge pull request #474 from DrDoof/feat/chip-temp
temp: read the on-chip thermal sensor
2026-09-29 09:51:57 +02:00
d00f c6fceae724 Merge pull request #420 from DrDoof/fix/static-build-determinism
Make the static-file build repeatable
2026-09-29 07:09:46 +02:00
DrDoof 068ec4c510 fileadder: print the generated files only when there is no image to write
The image rule no longer passes -p, so fileadder fell through to printing
html_data.h and html_data.c on stdout on every image build. The listing is
only useful when fileadder is run without an image, to see what it would
generate.
2026-09-29 05:27:16 +02:00
d00f 3a92459190 build: don't regenerate html_data from the image rule
The .bin rule passes -p html_data to fileadder, which rewrites
html_data.c and html_data.h after the objects that include them have
already been compiled and linked. The next make therefore rebuilds
html_data.c, httpd.c and page_impl.c even when nothing changed, and
so does the one after that. The rule only needs to embed the files
into the image; the header comes from the html_data.c rule above.

Spotted by plaes in #358, which cannot be merged as it stands.

With this and the previous commit, a second make after a full build
compiles nothing, and the image is byte for byte the same as before.
2026-09-29 05:27:07 +02:00
d00f 5ffeb3d16a build: make the static-file build repeatable
Two things kept the same source from producing the same image.

fileadder walked the input directory with readdir(), which returns entries
in whatever order the filesystem hands them over. The same tree therefore
embedded the files in a different order on a different filesystem, and the
image differed byte for byte. scandir() with alphasort() fixes the order.

version.h was listed in .PHONY, which makes it permanently out of date and
propagates that to everything including it, so a plain build recompiled the
four largest translation units even when nothing had changed. Dropping it
from .PHONY alone would go too far the other way: with no prerequisites the
recipe would never run again and the version string would freeze at its
first value. A FORCE prerequisite keeps the recipe running every time, and
the generated header is only moved into place when its content actually
differs, so the timestamp survives an unchanged rebuild.

Measured on this tree: with FORCE the version follows GIT_VERSION and an
unchanged rebuild leaves version.h untouched; without it the header keeps
the value from the first build.
2026-09-29 05:27:07 +02:00
logicog de705380c9 Merge pull request #507 from bloqaudio/fix/build-uip-modules-per-config
uip: build the optional modules only when configured
2026-09-27 17:47:29 +02:00
logicog e4f3315af3 Merge pull request #525 from UAb5eSMn/web-network-config
web: Change ip, netmask and gw network settings at the same time
2026-09-27 17:42:21 +02:00
UAb5eSMn 07ab04e668 web: Change ip, netmask and gw network settings at the same time 2026-09-26 15:33:16 +02:00
bloq c5ce24ff78 Merge pull request #518 from vDorst/refactor_dedup_machine_init
machine-init: dedupe the same machine_custom_init()
2026-09-26 08:21:57 -05:00
René van Dorst bcb237158f machine-init: dedupe the same machine_custom_init() 2026-09-26 08:39:17 +02:00
logicog f103daaafa Merge pull request #519 from dobodu/main
Added french translation
2026-09-26 06:26:17 +02:00
bloq 168add47a3 Merge pull request #452 from gnacho/feat/session-timeout-config
httpd: make the session timeout configurable
2026-09-25 16:57:51 -05:00
gnacho 423886dc54 web: keep service labels unindented and add the es session strings
Restore the sy_igmp and sy_services_note lines to their original
indentation so they do not show up as changed in the diff; the new
session timeout strings move to their own lines. Also add the es
strings, which were missing after the merge with main.
2026-09-25 23:22:35 +02:00
gnacho 4a9137387c test: define session_timeout in the host harness
test_port_tables and test_page_json link httpd/page_impl.c unmodified,
which now references session_timeout. Define it in support.c next to
the other firmware globals so the tests link again on current main.
2026-09-25 23:22:35 +02:00
gnacho 2fc08f40d2 httpd: drop Max-Age from the session cookie
The server timeout is an inactivity timeout: scan_header() refreshes
last_session_use on every authenticated request. Max-Age counts from
login instead, and the cookie is only set there, so the browser deletes
it session_timeout seconds after logging in however busy the session
is. Leave expiry to the server, which already handles it.

The five-digit itoa16_html() stays: information.json must print
timeouts from 10000 to 65535 correctly.
2026-09-25 23:22:35 +02:00
gnacho 4a7fd7a098 Merge remote-tracking branch 'origin/main' into feat/session-timeout-config 2026-09-25 23:16:16 +02:00
dobodu 19cd1200b4 Update French translations in app.js 2026-09-25 21:59:13 +02:00
bloq 333adbaf19 Merge pull request #523 from DrDoof/cleanup/drop-call-traces
port, dhcp: drop the called/done traces from the console and syslog
2026-09-25 14:21:09 -05:00
bloq 8051061fd7 Merge pull request #522 from DrDoof/cleanup/dhcp-dead-memcpy
dhcp: drop the MAC copy after the requested IP and server id
2026-09-25 14:20:59 -05:00
bloq 8ae3f5e70f Merge pull request #521 from DrDoof/fix/dhcp-option-length
dhcp: skip IP and time options that are shorter than four bytes
2026-09-25 14:20:50 -05:00
dobodu 9ce328d155 Update French translations in app.js 2026-09-25 21:07:50 +02:00
dobodu 2e463a2f30 Add French translations to login page 2026-09-25 20:53:25 +02:00
DrDoof 422dc3f24d port, dhcp: drop the called/done traces from the console and syslog
rtl837x_port.c prints "called" and "done" around mirroring, PVID, VLAN
and L2 setup, and dhcp.c around discover, request, start and stop. They
land on the console on every boot and config replay, and in the log
once syslog is running. The VLAN line keeps the VLAN, members and
tagged values, only without the function name in front.

On the KP_9000_6XHML_X2 this takes 359 bytes out of BANK1, 88 out of
BANK2 and 24 out of BANK3. BANK1 goes from 431 to 790 bytes free.
2026-09-25 07:45:03 +02:00
DrDoof 4b01ff9f8a dhcp: drop the MAC copy after the requested IP and server id
dhcp_addopt_request_ip() and dhcp_addopt_server_id() both end with a
copy of four bytes of our MAC to the current position, without moving
the position on. The next option always starts there, so those bytes
are written over before the packet goes out and never reach the wire.
The packet stays the same without the copy, and BANK3 is 102 bytes
smaller on the KP_9000_6XHML_X2.
2026-09-25 07:45:03 +02:00
DrDoof 456a4d9d3f dhcp: skip IP and time options that are shorter than four bytes
ip_opt() copies four bytes whatever the option length says, so a
subnet, router, DNS, server id or broadcast option shorter than four
bytes stores the start of the next option as the address. long_opt()
does not look at the length at all and always moves on by six bytes, so
a lease, renewal or rebind option of another length puts the parser out
of step for the rest of the packet.

Both now read the length. An address or time shorter than four bytes is
skipped, a longer one is taken from its first four bytes and stepped
over whole, and a skipped time leaves the previous value alone.

Found in the code, not on the wire; no server here sends such options.
BANK3 is 47 bytes smaller on the KP_9000_6XHML_X2.
2026-09-25 07:45:03 +02:00
dobodu 02e9189f5f Add French to language selection
Added French language options to the selection.
2026-09-25 01:00:14 +02:00
dobodu aff7d9c098 Add French translations for various UI elements 2026-09-25 00:56:34 +02:00
René van Dorst 770926abd1 Merge pull request #517 from DrDoof/fix/rx-drain
rx: keep draining while the NIC buffer holds data
2026-09-24 19:27:08 +00:00
René van Dorst 13f04d8acb Merge pull request #515 from polyfaces/hasivo_s1100wp_8gt_1sx_se
machine: add the Hasivo S1100WP-8GT-1SX-SE target
2026-09-24 16:55:35 +00:00
polyfaces 49284fee15 Update docuementation and PCB photos 2026-09-24 22:01:53 +08:00
DrDoof db9ce7543e rx: keep draining while the NIC buffer holds data
The main loop drains the NIC only after an RX interrupt, at most
RX_BUDGET frames, then clears the status and waits for the next one.
When frames are left and the buffer reaches the RX stop threshold, the
NIC takes no more frames and raises no further interrupt, so reception
stops for good.

Check the buffer after clearing the status and keep draining while it
holds data; the interrupt is re-enabled only once it is empty.
2026-09-24 15:44:10 +02:00
polyfaces 7a14c2d4a5 Add Hasivo S1100WP_8GT_1SX_SE to supported devices
Update support_devices.md
2026-09-24 18:58:07 +08:00
polyfaces 92948079a9 machine: add the Hasivo S1100WP-8GT-1SX-SE target
RTL8373-based 8×2.5G PoE + 1×SFP switch.

- 8 copper ports at 10/100/1000/2500 Mbps, SFP at 1G/2.5G/10G (SDS 1)
- board doc with label specs and photos
2026-09-24 12:28:54 +08:00
René van Dorst e406a3e82b Merge pull request #499 from DrDoof/up/nic-rx-irq
rtlplayground: receive on the NIC interrupt instead of polling the buffer
2026-09-23 18:32:47 +00:00
René van Dorst c43b955db0 Merge pull request #498 from DrDoof/up/tick-block
rtlplayground: run the timed work once per tick
2026-09-23 18:31:47 +00:00
logicog 0321763e91 Merge pull request #511 from DrDoof/perf/rtl8224-ready
rtl8224: wait for the PHY to answer instead of a fixed 2.5 s
2026-09-23 20:29:10 +02:00
René van Dorst 4bbbcd7d04 Merge pull request #513 from DrDoof/fix/port-name-no-arg
cmd: "port <n> name" without a name clears it instead of reading stale words
2026-09-23 18:21:14 +00:00
DrDoof 682ed861fa rtl8224: wait for the PHY to answer instead of a fixed 2.5 s
rtl8224_enable() waits 2.5 s after releasing the RTL8224 reset before
anything talks to the PHY. On an SWTGW218AS the chip id already reads
back 0x8224 at the first poll, about 265 ms after the release.

Remember when the reset was released and, once SMI is up, poll the chip
id until it answers, giving up after the same 2.5 s as before. The PHY
writes that follow read back the same as before.

The reset pulse before it goes from 0.5 s to 20 ms, twice the 10 ms the
related RTL8372 and RTL8221B need. The pulse does reset the chip: the
MDI reverse register (MMD 30, 0x0a90) still reads its power-on value
0x00f3 just before the init writes 0x00fc, as it does with 0.5 s.
Together the console banner comes 3 s sooner.

The SerDes write loop in phy_config_8224() waited on the busy bit with
no limit, so a PHY that never answers stopped the boot before the
network came up. Bound it and report a timeout.
2026-09-23 20:21:11 +02:00
d00f 3a9045b97e cmd: "port <n> name" without a name clears it instead of reading stale words
The name branch read the fourth word without checking that the line had
one, and the tokenizer does not reset unused word slots, so "port 5 name"
took the name from wherever the previous command's fourth word had been:
"port 5 name hello" followed by "port 5 name" gave "hello" again, and
after a longer line it gave the tail of that line. The web UI never sends
the form, the console can. With no argument the name is now empty, which
also gives the console a way to remove a name.
2026-09-23 17:46:05 +02:00
d00f 4b9f6c319f Merge pull request #508 from DrDoof/fix/rebuild-on-flag-change
build: rebuild objects when the compiler flags change
2026-09-23 16:38:08 +02:00
DrDoof 6e2701bc8c rtlplayground: receive on the NIC interrupt instead of polling the buffer
The receive interrupt was wired up but never used: EX1 was enabled, the
handler re-enabled itself and did nothing, and the mask bit for it in the
NIC stayed clear, so idle() read the receive buffer register on every
pass to find out whether a packet had arrived, and slept until the next
tick when none had. A packet that arrived during that sleep waited for
the tick: 2.2 ms on average, 5 ms at worst, for every frame the CPU
handles.

Now the handler latches rx_irq and masks itself, and idle() sleeps
unless a receive, a link change, a console command or the tick is
pending. On rx_irq the loop drains the buffer, clears the status bit and
re-enables EX1, in that order: the NIC holds the line as long as the
status bit is set, and the bit does not clear while the buffer still
holds a frame, so a frame that lands between the last read and EX1 = 1
raises the interrupt again rather than getting lost. The other order,
clearing before draining, left the line asserted and produced an
interrupt on every pass. The mask is written after nic_setup(), which
resets it.

Measured on an RTL8373 board with the same counters in both images,
clocked by the tick counter, traffic from a host behind an LACP trunk:

```
                          polling     interrupt
idle, register ops/s          788            79
25 pps, RTT avg/max     2.19/4.96     0.44/0.75 ms
500 pps, RTT avg/max    2.78/5.06     0.39/5.40 ms
500 pps, loss               0.08%         0.08%
```

The handler compiles to clr EX1 / setb rx_irq / reti.

The timer interrupt now raises a tick flag too, so idle() no longer works
out TICKS_DUE twice per pass, and cmd_available moves from xdata to a
bit. The four wake-up flags sit in the low bits of one byte at 0x20, so
the sleep test is a single read of that byte. tx_buf_empty deliberately
stays out: it is 1 when there is nothing to do, so including it would
keep the sleep test from ever firing.
2026-09-23 16:12:32 +02:00
DrDoof ac3b4b17ae rtlplayground: run the timed work once per tick
idle() ran everything on every pass: the seconds block, the SFP pin
test, the TCP periodic timer behind a gate of its own, the STP timers
behind theirs. The timed work moves into handle_tick(), which idle()
calls once per system tick from a single gate; handle_tx() loses its
private one.

handle_sfp() ran on every pass as well, two GPIO reads per pass to learn
that nothing changed; it now runs every ten ticks. A module inserted is
still seen within 50 ms, and the read itself has been off the loop since
the wake-up wait moved to the tick.

No change in behaviour otherwise: the link, receive and console paths
stay where they were. SWTGW218AS: code +45 B, xdata +1 B, stack
unchanged.

The health checkpoints follow the work they measure: the seconds block,
SFP, TX and STP phases are taken inside handle_tick(), the link, RX and
command phases in idle().
2026-09-23 16:09:18 +02:00
DrDoof f87eb70c6a rtlplayground: read the link registers when the switch reports a change
The link-change interrupt was set up at boot (mode 0x5f84, mask 0x5f34)
but never enabled: the handler did nothing and did not clear the event in
the switch, so EX0 stayed commented out and idle() read the two link
registers on every pass instead, 400 reads a second on a quiet switch.

The handler now only latches a flag and masks itself; the register access
macros build a write in the shared SFR data bytes, so an interrupt that
touched the switch in the middle of a write from the main loop would
corrupt it. idle() clears the event register (write-one-to-clear, 0x3ff
for the ten ports), re-enables EX0 and then reads the link state, in that
order, so a change that lands during the read raises a new edge. The flag
is also set once a second from the seconds block, so a board whose INT0
line turns out not to be wired keeps detecting link changes, a second
late at worst. Boot drops the events pending from bringing the links up
before the mask is set.

Measured on an RTL8373 switch before this change: 0x5f88 held 0x3fd, nine
ports had reported a change nobody read. With the change a counter in the
loop went 2 -> 3 -> 4 on "port 5 off" / "port 5 on" and stayed at 4 over
three reads without events, so the event is delivered, cleared and
re-armed, and there is no flood.

SWTGW218AS: code +57 B, xdata 0, stack 151 -> 150 B; the flag is a bit
variable. The handler compiles to clr EX0 / setb link_irq / reti.

idle() also keeps in a bit what it saw in NIC_RX_BUFF_DATA when deciding
whether to sleep, and the first iteration of the receive loop uses it
instead of reading the register again a moment later. Between the two
only the link check and handle_sfp() run, and neither can make a queued
packet disappear. Every later iteration reads the register as before, so
the budget loop behaves as it did. This part was not measured on
hardware: one register read per pass needs a throughput run to show up.
SWTGW218AS: BANK1 and BANK2 +6 bytes each, BANK3 unchanged.
2026-09-23 16:04:39 +02:00
DrDoof 214cc6b737 rtlplayground: keep the STP and TCP timers on the system tick
stp_timers() ran every fourth pass of the main loop and stp_clock counted
passes, which only measures time while a pass is one tick long. It never
quite was: a pass is as long as the packets it handles, and #482 now takes
up to four of them, so under an IPv4 stream a pass is about 16 ms and STP
time runs at a third of its rate, with hellos 6 s apart and a root aged out
after a minute. Since #483 the loop also stops sleeping while packets wait,
so under a flood of cheap frames passes come faster than ticks and STP time
runs fast instead, shortening the forward delay in exactly the situation a
loop produces. uip_periodic() had the same problem from the other side: it
ran once per pass, so the retransmit timer sped up with the loop.

Both now go by the tick counter the timer interrupt keeps. The STP block
catches up on ticks a long pass missed, at most eight steps at a time, and
handle_tx() runs its periodics once per tick at most. One byte of xdata
more, 48 bytes of code.

A host test drives the gate with one pass every 5 ms, every 16 ms and
every 0.6 ms for ten seconds each; the STP clock lands on 500 steps in
all three, where counting passes gave 156 and 4166. A pass of 1.3 s loses
time rather than firing 65 steps at once, and the arithmetic survives the
tick counter wrapping.
2026-09-23 16:00:59 +02:00
feelfree69 40177f6318 Merge pull request #352 from DrDoof/feat/health-cmd
health: a CLI dump of where the main loop's time goes
2026-09-23 15:05:55 +02:00
feelfree69 d3dda09079 Merge pull request #489 from DrDoof/feat/port-media-pause
port: report link role and negotiated pause per port
2026-09-23 14:59:35 +02:00
feelfree69 277e729f2c Merge pull request #509 from DrDoof/phy/advertise-pause-above-1g
phy: advertise pause on the forced speeds from 1G up
2026-09-23 14:32:32 +02:00
feelfree69 f778b9159b Merge pull request #462 from DrDoof/fix/gui-save-merge
web: keep stored config lines and per-port ingress when saving
2026-09-23 08:53:05 +02:00
feelfree69 b6d6d2391d Merge pull request #473 from DrDoof/fix/config-replay-skip
config, httpd: skip an over-long line and refuse to store one
2026-09-23 08:52:19 +02:00
d00f 2c231fdf69 phy: advertise pause on the forced speeds from 1G up
The advertisement written for a forced speed of 1G and above is 0x1001,
while auto and the forced 10M and 100M modes all carry bit 10, the PAUSE
capability. Forcing a port to 1g or faster therefore takes pause off the
link: the advertisement narrows and the far end has nothing left to
agree to.

Measured on a SWTGW218AS through phyget and physet, on a port with no
link so nothing was disturbed. Auto reads 0x15e1, port 1 1g reads
0x1001, port 1 auto reads 0x15e1 again, and writing 0x1401 by hand
sticks. That bit 10 is the pause capability follows from a second
reading: two ports advertise the same 0x15e1 from this side, and pause
is active only against the partner whose own advertisement carries the
bit.

Reported by feelfree69 in #489.
2026-09-22 23:12:50 +02:00
d00f 689751ffe6 build: rebuild objects when the compiler flags change
The .d files track headers, so adding or removing a flag on a tree that
was already built leaves every object that did not change for some other
reason compiled with the old flags, and the image ends up a mix of the
two.

How that shows depends on the flag. DIAG=1 fails the link on undefined
symbols, which at least stops the build. An opt-in whose hooks expand to
empty macros loses the calls instead, so the link succeeds and the
feature quietly measures nothing.

Objects now depend on a stamp holding CC_FLAGS, rewritten only when the
flags differ, so an unchanged flag set still does not force a rebuild.
2026-09-22 22:25:53 +02:00
feelfree69 fa93340c24 Merge pull request #443 from DrDoof/build/xram-size
Makefile: let the linker check external RAM against 0xBFFF
2026-09-22 20:50:59 +02:00
feelfree69 e9523a2f67 Merge pull request #460 from DrDoof/fix/multipart-request-path
httpd: keep a continued upload away from the cmd and login handlers
2026-09-22 20:46:52 +02:00
feelfree69 088be074f5 Merge pull request #411 from DrDoof/fix/httpd-upload-bound
httpd: stop a firmware upload at the end of the image area
2026-09-22 20:45:55 +02:00
d00f 340fdd97e5 port: report link role and negotiated pause per port
Two per-port MAC status registers that nothing in the firmware reads:
0x6408 says which end is master on a BASE-T link, 0x63FC and 0x6400 say
whether pause was negotiated in each direction. port <n> show prints
both at the end.

The read sits in rtl837x_port.c rather than phy_show(), because these
are MAC registers and phy_show() is skipped for SFP ports. Master/slave
keys off machine.is_sfp[] so it stays off the fibre ports, where the
notion does not apply.

Bit N of each register is chip port N, which is not how
RTL837X_REG_LINKS_STS indexes its bytes a few lines above in the same
header.
2026-09-22 20:28:54 +02:00
DrDoof 09ce4b391b httpd: stop a firmware upload at the end of the image area
edc469f made max_upload count, which bounds how many bytes a request may
carry. Nothing bounds where those bytes land. The two are not the same
check: the first watches the request, the second watches the flash address.

uptr walks forward one page at a time from FIRMWARE_UPLOAD_START and has no
ceiling. A body whose closing boundary never arrives keeps writing page after
page past the staging region, and on a flash exactly twice FIRMWARE_UPLOAD_START
the address wraps onto the running image at zero, so a bad upload takes the
working firmware with it. That commit's own message notes the related hazard:
flash_write_bytes() carries the address in three bytes and drops what does not
fit.

Bound it where uptr actually moves: refuse the next page once it would leave
the area check_and_flash_update_image() reads back, and answer 400. A partial
image left behind is harmless - boot rejects it on CRC and erases it.

This rebase drops the part of the original patch that removed max_upload;
the length limit and the address limit now sit side by side.
2026-09-22 20:04:44 +02:00
feelfree69 7e6577a811 Merge pull request #415 from DrDoof/fix/syslog-ring-overrun
syslog: stop the log ring from lapping its reader
2026-09-22 18:54:51 +02:00
bloqaudio 675faf0471 uip: build the IPv6 neighbour cache only for IPv6 builds
uip-neighbor.c is the IPv6 neighbour cache. Its only caller is the neighbour
solicitation handler in uip_process(), which sits inside #if UIP_CONF_IPV6,
so on an IPv4 build the module is linked without being reachable.

Guard it on the same macro as its caller.

Worth recording separately: the module does not currently compile with
UIP_CONF_IPV6 set. uip_ipaddr_cmpx() is defined only in the non-IPv6 half of
uip.h, and uip_neighbor_add() calls it, so an IPv6 build stops with an
implicit declaration. That is true before this change as well, and is left
alone here.

On SWTGW218AS, on top of the forwarding change, the stack gains a further 12
bytes to 172 available and BANK1 drops another 714 bytes to 46080.
2026-09-22 09:23:13 -05:00
bloqaudio 175ca9464a uip: build the forwarding module only when configured
uip-fw.c forwards packets between several network interfaces. Nothing in
this firmware calls it: the only references to uip_fw_output() outside the
module are commented out in uip-split.c. It is compiled and linked all the
same, and its register pressure lands in direct RAM, which on SWTGW218AS is
already reported as having no spare space.

Add UIP_CONF_FORWARDING alongside the existing UIP_CONF_BROADCAST and build
the module only when it is set. A device that forwards turns it on and gets
the module unchanged; a single-interface device stops paying for it.

On SWTGW218AS the stack gains 9 bytes, from 151 available to 160, and BANK1
drops 1200 bytes, from 47994 to 46794.
2026-09-22 09:23:13 -05:00
René van Dorst 38241f0a08 Merge pull request #500 from 13974684927/add-f7008-2-5
machine: add the F7008-2.5 (GPOE-V1.2) 8+1 target
2026-09-22 13:28:01 +02:00
笙笙宝宝 0fe323b12f machine: add the F7008-2.5 (GPOE-V1.2) 8+1 target
Add support for the F7008-2.5 board (PCB F7008-2.5GPOE-V1.2), an
RTL8373N + RTL8224N design with eight 2.5GBASE-T ports and one SFP+ cage.

The LED pads are not wired in port order, so the generic DEFAULT_8C_1SFP
target lights the wrong lamp on this board. The pad to lamp map was
measured on the hardware and is documented in doc/devices/F7008-2.5.md.

The green lamp marks a 2.5G link, the yellow lamp blinks while data flows.
2026-09-22 19:12:12 +08:00
d00f 94187461c6 ci: build once with HEALTH=1 as well
Code behind a build flag nobody sets in CI rots quietly. One extra build of
the same machine with the instrumentation on keeps it compiling.
2026-09-21 22:25:32 +02:00
d00f fa0eb91586 health: build the instrumentation only when asked for
The counters, the phase hooks in idle() and the "health" command are now
behind HEALTH, set from the command line with HEALTH=1. Without it the
hooks are empty macros and the command does not exist, so a default build
carries none of it: on SWTGW218AS that is 1065 bytes of bank 2, 101 of the
common segment and 28 of xdata, plus seven banked calls per main-loop pass.

It is a developer's tool. Reading the dump takes knowing the main loop, and
a user who needs it for a report can build with the flag on, which is how
the kernel treats its lock and latency instrumentation as well.
2026-09-21 22:25:32 +02:00
d00f 2f872ea08e health: drop the constant-false branch the compiler warned about
The tcp slot dump printed the slot index behind `if (UIP_CONNS > 1)`, and
with one connection configured that is a constant-false condition around
live code: sdcc reported two "unreachable code" warnings for the body and
one "conditional flow changed by optimizer" for the loop that could only
run once.

Print the single slot directly and state the assumption as a build-time
check, so a build that raises UIP_CONNS fails loudly here instead of
quietly labelling every line as slot zero. Builds warning-free again.
2026-09-21 22:25:32 +02:00
d00f ab2d30a05b doc: describe the health command
What each line of the dump means, how two dumps give rates, and the
two things worth knowing before trusting the numbers: the command
inflates its own cmd phase counters by the cost of printing, and the
untouched figure is a high-water mark, not a live level.
2026-09-21 22:25:32 +02:00
d00f d37ce07119 health: a CLI dump of where the main loop's time goes
Every probe of a misbehaving switch so far measured it from the
outside. This puts the witnesses inside.

Each idle() pass increments a loop counter and stamps a checkpoint
after every phase: link polling, SFP, RX, TX, STP and the command
dispatch. A phase spanning two or more system ticks counts as slow and
its worst time is kept, in 5 ms units. handle_rx counts frames, so the
dump separates a flooded CPU from a stalled loop.

At boot the free stack area is painted, preserving the interrupt state
around the painting, and the dump reports how much of the pattern
survives next to the live stack pointer. The TCP connection table is
printed whole: state, timers, retransmissions, ports and peer, next to
the httpd transfer state, so a permanently occupied slot names its
occupant. With more than one slot configured every slot is listed.

Everything is raw hex; two dumps a known time apart give the rates.
The implementation sits in cmd_parser next to the other command
handlers and needs no storage beyond its counters.
2026-09-21 22:25:32 +02:00
René van Dorst 4bd6ffd083 Merge pull request #492 from vDorst/feat_build
CI: Turn on error on warning also for sdcc-compiler.
2026-09-21 18:11:15 +00:00
René van Dorst 94cfec8693 CI: Build a 4x2/8372 and 8x1/8373 version 2026-09-21 19:50:15 +02:00
René van Dorst a4bb3f1725 CI: Enable CI option to stop at warnings 2026-09-21 19:50:15 +02:00
René van Dorst 931e6da0ab Makefile: Add option CI to turn all warnings into errors 2026-09-21 19:50:11 +02:00
René van Dorst 88454ec7d7 Merge pull request #439 from vDorst/feat_i2c_precompute
I2CBUS: Calculate I2C-settings as compiletime instead at runtime.
2026-09-21 15:30:56 +02:00
René van Dorst f0efb84abb Merge pull request #494 from tofurky/sdcc_4.6.0_warnings
Fix warnings seen on SDCC 4.6.0 due to missing const qualifiers
2026-09-21 10:51:54 +02:00
René van Dorst 62d3424a92 Merge pull request #496 from DrDoof/fix/sfp-nonblocking-wake
sfp: do not block the main loop while a module wakes up
2026-09-21 10:32:22 +02:00
DrDoof a3860c29cb sfp: do not block the main loop while a module wakes up
handle_sfp() called sfp_module_read() as soon as it saw a module appear, and
that path opened with delay(100). delay() spins on PCON until the tick counter
runs down, so the main loop stood still for 100 ticks - half a second at
SYS_TICK_HZ 200. Nothing else ran in that window: no STP, no LACP, no packet
handling. A diagnostic build measured the sfp phase at up to 107 ticks, the
longest stall of any phase in the loop.

Remember when the module showed up and let the loop continue. The I2C read
happens on a later pass, once the wake-up period has elapsed. Pulling a module
cancels a pending read, and a failed read still restores the detect bit, so the
existing retry-on-the-next-poll behaviour is unchanged.

The comparison is written out rather than using a helper so that this does not
depend on anything outside the current tree. The pending flag is an array
indexed by slot, like the other per-slot state in this file.

Cost on SWTGW218AS, measured against the branch point: BANK3 11173 -> 11264,
CSEG, BANK1 and BANK2 unchanged, plus 4 bytes of xdata.
2026-09-21 09:45:18 +02:00
René van Dorst b1961c6955 Merge pull request #495 from tofurky/sfp_normalize_nul
Properly handle NULs when normalizing SFP fields
2026-09-21 08:51:57 +02:00
Matt Merhar bb84bdc55a Properly handle NULs when normalizing SFP fields
Some generic 1.25gbps DAC cables were seen to have a NUL *after* the
space padding in the model field. This caused the normalization to miss
the space padding in the string. The vendor field did not have the
issue.

Note that this seems to be against spec per SFF-8472, which mentions
only space padding or *all* NUL, not a combination of both. In the case
of all NUL, we'll waste a few cycles with no-op writes.
2026-09-20 19:55:07 -04:00
Matt Merhar 1b5243714e Fix warnings seen on SDCC 4.6.0 due to missing const qualifiers
Building with SDCC 4.6.0 emits tons of warnings e.g.:

	cmd_parser.c:31: warning 356: object in read-only code space should be const
	cmd_parser.c:36: warning 357: pointer to object in read-only code space should be pointer to const

These of course become fatal when building with --Werror.

Comparing resultant binaries, it seems that a single string is shifted
(deduplication?) but the numbers emitted at the end of the build are
otherwise identical.
2026-09-20 18:25:17 -04:00
René van Dorst ce11790a0d I2C: Silence compiler warning, de-magic values. 2026-09-20 20:31:43 +02:00
René van Dorst 55431b39f6 Merge pull request #483 from DrDoof/perf/rx-nosleep
rtlplayground: do not sleep in idle() while a packet is waiting
2026-09-20 13:02:59 +00:00
René van Dorst 46e1c955ba Merge pull request #482 from DrDoof/perf/rx-budget
rtlplayground: take several packets per loop in handle_rx()
2026-09-20 13:02:07 +00:00
d00f 7b728cec6f rtlplayground: do not sleep in idle() while a packet is waiting
idle() went to sleep before it looked at the receive buffer, so a packet that
arrived just after the loop dozed off waited for the next tick before anything
touched it. Every TCP round-trip paid that toll, which is why the web UI felt
sluggish even though no phase of the loop was anywhere near busy.

Check the receive buffer first and only sleep when it is empty. The read takes
its result straight from SFR_DATA_U16 rather than going through reg_read_m()
and the sfr_data[] copy, which is what the rest of the tree already does.

Measured on SWTGW218AS with this change alone:

  web UI, median of 15 requests for the main page   88 ms -> 76 ms
  CPU port under a one-way UDP stream               159 -> 230 packets/s

The loop still sleeps normally when there is nothing to do, so this does not
turn the core into a busy-wait: loops/ticks stays at 0.99 on an idle switch,
and rises above 1.0 only while packets are actually queued.

Costs 12 bytes of code and no extra XDATA.
2026-09-20 14:56:44 +02:00
d00f 0d17dd563f rtlplayground: take several packets per loop in handle_rx()
handle_rx() picked up exactly one packet per iteration of the main loop. Since
the loop sleeps until the next tick, that capped the CPU port at roughly one
packet per tick no matter how little work it had to do. A burst of broadcast,
ARP or IGMP traffic aimed at the CPU queued up behind that and pushed out the
STP and LACP timers.

Drain up to RX_BUDGET packets in one pass instead. The budget matters: without
one a sustained flood would spin here and starve everything else in the loop.

The two register reads in the loop take their result straight from
SFR_DATA_U16 rather than going through reg_read_m() and the sfr_data[] copy,
which is what the rest of the tree already does.

Measured on SWTGW218AS with a one-way UDP stream, counting the packets the CPU
actually processed, with this change alone:

  before          159 packets/s
  RX_BUDGET 4     245 packets/s, loop iteration ~16 ms
  RX_BUDGET 8     298 packets/s, loop iteration ~26 ms

Throughput is already most of the way there at 4, so the larger budget mostly
buys a longer worst-case delay for the other phases. STP stayed responsive in
both cases.

Costs 71 bytes of code and no extra XDATA.
2026-09-20 14:42:50 +02:00
René van Dorst d556d634ca Merge pull request #487 from jagheterfredrik/patch-1
add zlib1g-dev to Dockerfile
2026-09-20 12:34:33 +00:00
Fredrik Gustafsson 2844a1ed6d add zlib1g-dev to Dockerfile
Current Docker-based build fails without it
2026-09-20 14:31:03 +02:00
René van Dorst 7b5e365d5e Merge pull request #409 from gnacho/i18n-es
feat(translate): add Spanish (es) translations
2026-09-20 11:40:56 +00:00
gnacho 45fb382b07 feat(translate): add Spanish option to the language selector 2026-09-20 13:29:12 +02:00
René van Dorst 8973774594 Merge pull request #476 from bloqaudio/fix/eee-port-argument
eee: accept a port again, and stop the web UI discarding other ports
2026-09-20 09:50:25 +00:00
René van Dorst 48c5b0c9cf Merge pull request #486 from bloqaudio/fix/syslog-null-conn
syslog: do not read the connection before there is one
2026-09-20 09:39:04 +00:00
René van Dorst 1557a7cf3c MACHINE: rebase, update the new added devices 2026-09-20 11:30:17 +02:00
René van Dorst 131bc106af boot/i2c-setup(): Remove the switch-statement defaults.
All the possibilities are handled, or otherwise a compile-time macro
I2CBUS_VALID ensures invalid options are rejected with a compiler error.

Also fix a compiler warning.
2026-09-20 11:25:35 +02:00
René van Dorst 19ccc4003d i2c: Add compiletime check macro for validating the i2c pins.
Use 0x7 as error value for an invalid I2C SDA/SCL-pin value.
The macro `I2CBUS_VALID` checks for this value.
When a invalid pin is ues, the compiler errors out with:
  "machine.c:<line>: error 176: sizeof applied to an incomplete type",

Error message is not ideal but at-least points to the right location in
the code.
2026-09-20 11:25:35 +02:00
René van Dorst b5fe9f217f i2c/sfp: Demagic shift-values 2026-09-20 11:25:35 +02:00
René van Dorst f65d14f452 I2CBUS: Calculate I2C-settings as compiletime instead at runtime.
We can reduce the i2c-settings to just one byte, and this new const value
can directly be used by the I2C-peripheral without additional computation.
This saves 1-xdata load and a lot of bit-shifting.

I see a codesize savings of +200 bytes.
2026-09-20 11:25:34 +02:00
René van Dorst b92fba870c Merge pull request #484 from bloqaudio/fix/upload-auth-gate
httpd: gate /upload on authentication before it changes state, and make the length limit count
2026-09-20 09:21:25 +00:00
René van Dorst 4deee377a7 Merge pull request #485 from bloqaudio/fix/led-set-index
leds: index the second LED register from the set it belongs to
2026-09-20 08:40:52 +00:00
René van Dorst fe42b07e2b Merge pull request #481 from DrDoof/fix/rx-checksum
uip: verify TCP and UDP checksums using the RX descriptor flag
2026-09-20 07:59:46 +00:00
d00f 9984649177 uip: verify TCP and UDP checksums using the RX descriptor flag
The TODO next to the disabled TCP checksum code asks which bit of the RX tag
carries the result. It is bit 2 of byte 1: the hardware sets it when the L4
checksum is bad.

That answers the question for UDP as well, which was not being checked either
because UIP_CONF_UDP_CHECKSUMS is 0. Both paths now read the flag instead of
running a checksum over the payload on the 8051.

A zero UDP checksum, which is optional in IPv4, is not flagged by the hardware,
so DHCP keeps working and no extra condition is needed.

rx_headers lives in rtlplayground.c, which the host tests do not link, so
test_httpd_tx carries the storage itself the way it already carries uip_buf.
A zeroed descriptor reads as a good checksum, so the existing scenarios are
unaffected.

A new scenario sets the flag on a request and expects it counted as a checksum
error and left unanswered, then sends the same request with the flag clear and
expects it served. Built against uip.c without this change, those first two
checks fail and the two control checks still pass, so the scenario tells the
two versions apart rather than merely exercising the code.

Costs 156 bytes of code on SWTGW218AS and no extra XDATA.
2026-09-20 00:35:10 +02:00
René van Dorst 7d47edb649 Merge pull request #477 from bloqaudio/fix/port-speed-select
web: show the speed a port is set to, not always auto
2026-09-19 11:26:03 +00:00
René van Dorst fdb19fe0ee Merge pull request #451 from SkullyDev/main
HASIVO_S600W_4GT_2XGT_SE machine
2026-09-19 11:25:30 +00:00
SkullyDev b9b50f9a70 Merge branch 'main' into main 2026-09-19 13:07:28 +03:00
SkullyDev c31b67572b Remove led behavior & LED comments
Should work without gpio_input_setup
2026-09-19 13:03:42 +03:00
bloqaudio 93003c8cb8 web: show the speed a port is set to, not always auto
The speed selector on the Ports tab was filled with its options and never
given a value, so it read "auto" on every load whatever the port was set
to. Forcing a port to 1G and reloading showed "auto" in the selector
beside a link badge reading 1G.

The advertisement bits ports.json already reports say what the port is
set to: phy_set_speed() writes the same three registers the JSON reads
back, and every forced speed leaves exactly one of the six bits standing
while auto leaves the lot. Map those to the selector, and leave it alone
while it has focus, as the MTU field does.

Values with no entry in the table, such as the 100M setting that
advertises both duplex modes, leave the selector untouched rather than
claim a setting the list cannot express.
2026-09-18 15:13:41 -05:00
bloqaudio fd058ea74d port: name the port an eee command acted on the way the user wrote it
port_eee_enable() and port_eee_disable() report the port with
print_byte(), which prints the internal index in hex, so turning EEE off
on port 5 answers "EEE off for 04". port_eee_status() in the same file
already uses print_phys_port(), and the status listing it produces counts
from one, so the two halves of the same command disagreed.

Use print_phys_port() in all three messages.
2026-09-18 14:59:47 -05:00
bloqaudio edc469fe29 httpd: make the firmware upload length limit count
max_upload was armed once when the upload began and never reduced, so
`max_upload - uip_len > 0` was an unsigned expression that only went
false when a segment arrived whose length matched the limit exactly. The
write pointer climbed from the staging address with nothing to stop it,
while flash_write_bytes() carries the address in three bytes and drops
whatever does not fit.

Subtract each segment as it is taken and compare before subtracting, which
is what the comment there described. The limit was only ever armed for
firmware uploads; configuration uploads keep the bound their own buffer
already imposes.
2026-09-18 14:01:03 -05:00
bloqaudio a71c5359a5 httpd: refuse an unauthenticated firmware upload before it changes state
The multipart branch for /config tests authenticated before it touches
anything; the branch for /upload does not, and the 401 for it is only
raised further down at the dispatch. By then the request has already set
config_upload, uptr, verify_crc, max_upload and pre_acc, and announced a
firmware upload on the console.

Those five are globals while the transfer state they steer is per
connection, and the stack is built for ten connections, so a request that
is about to be rejected can still retarget an upload another client is in
the middle of: a configuration upload continues as a firmware one and its
remaining parts are written to the staging area instead of the
configuration sector.

Apply the same test the neighbouring branch uses, before the assignments.
2026-09-18 14:00:51 -05:00
bloqaudio 9e3863cc4e syslog: do not read the connection before there is one
udp_callbacks() offers every UDP connection event to syslog_callback(),
which decides whether the event is its own by comparing the local port
against state.syslog_conn->lport. The pointer is null until syslog is
started: syslog_init() clears it at boot and syslog_stop() clears it
again, so with syslog off, which is the default, every poll dereferences
null and reads whatever lives at the bottom of xdata.

Should that word ever match the port being offered, the send path runs on
another application's connection, overwrites uip_appdata and drops the
log line it was holding.

Test the pointer first.
2026-09-18 13:59:59 -05:00
bloqaudio d22ec04b5b leds: index the second LED register from the set it belongs to
leds_setup() walks all four LED sets and writes the high-speed nibble of
each into one of two registers, LED3_0_SET1 for sets 0 and 1 and
LED3_0_SET3 for sets 2 and 3. The second branch reuses the index
expression of the first without rebasing it, so sets 2 and 3 address
sfr_data[-1], [-2], [-3] and [-4].

Two things follow. Sets 2 and 3 never receive their nibble, because the
read-modify-write pair writes back exactly what it read. And four bytes
below sfr_data are overwritten on every boot of every machine: the loop
runs over all four sets whatever the board declares. On this build the
linker places sfr_data directly after the serial receive ring, so the
writes land in the tail of that ring.

leds_dump() reads the same two registers back and already rebases with
(set - 2); use the same form here.
2026-09-18 13:59:50 -05:00
bloqaudio 0a65536a7f web: send an eee command in the documented argument order
The EEE toggle posted `eee <port> <on|off>`, the reverse of the
`eee [on|off|status] [port] [100m|1g|2g5]` the parser and the
documentation describe, so the switch answered with its usage line and
the checkbox sprang back. The multi-page UI this replaced sent `eee on`
with no port at all, which toggled every port instead of one.

Moving the port to the end breaks the stored-configuration merge, which
builds its drop key as a line prefix: with the port trailing, a per-port
line matches the bare eee entry and replaces every eee line in the
configuration rather than the one for that port. Give the command its own
case beside the bandwidth one, so a line with a port replaces only that
port and a line without one clears them all, and accept the optional
speed word the command has always taken.

Reported by tofurky in issue #459.
2026-09-18 13:57:07 -05:00
bloqaudio 0631c813e2 cmd: parse the port of an eee command from the start of the word
parse_eee() sniffs word 2 for a trailing 'g' or 'm' to tell a speed from
a port number, and advances idx past the digits to do so. The port branch
then hands that advanced idx to cmd_parse_port_separator(), which expects
the first character of the number and finds the separator instead, so it
returns 0 and the command is rejected. Every other caller passes
cmd_words_b[].

The result is that no eee command carrying a port number works, in any
argument order, and the stored per-port lines a saved configuration
replays are silently dropped.

Reported by tofurky in issue #459, along with this fix.
2026-09-18 13:56:53 -05:00
René van Dorst eaebeba528 Merge pull request #464 from DrDoof/fix/json-escape
httpd: don't let a quote in a port name break the web page
2026-09-18 18:40:16 +00:00
René van Dorst b36fd95356 Merge pull request #450 from bloqaudio/feat/idle-poll-pause
web: stop polling when the page has had no input for ten minutes
2026-09-18 18:36:34 +00:00
d00f 52b1d1a4ea web: show the chip temperature in the System page
The row sits between Hardware and Flash. The firmware sends the formatted string
rather than the raw register, since the System table prints its values as they
arrive.

Costs 186 bytes in BANK1.
2026-09-18 18:28:13 +02:00
d00f 48f28ae7ff temp: read the on-chip thermal sensor
The chip has a pair of thermal meters and nothing was reading them. This adds a
`temp` command that prints the current reading next to the one taken at power-on.

The result register at 0x340 splits a 19 bit value across two fields: bits 18:3
live in the low 16 bits, bits 2:0 in bits 18:16, with the sign at bit 18, degrees
in 17:10 and a fraction below. The nice part of that layout is that the degrees
fall out of a shift of the low half, so the conversion stays 16 bit.

The meter comes out of reset latched: the register keeps whatever was measured
when the board powered up and then never moves. Four samples 40 s apart came back
identical down to the raw ADC, and 0x340 matched the power-on register at 0x350.
Clearing bit 2 of the control register at 0x338 during init is what makes it live.

Measured on an SWTGW218AS: 58.7 to 59.5 C while running, with the power-on
register still showing the 49.2 C it started from. For what it is worth, the
scale lines up three ways: the threshold register reads -40 and +125, the slope
from warm-up works out at 4450 LSB/C, and the slope between the two meters at
4388 LSB/C.

We only read the meter that is up after boot. The other sits in reset with no
coefficients, so it would just repeat its power-on value.

Costs 231 bytes in BANK2.
2026-09-18 18:28:13 +02:00
d00f a7fc8303e9 cmd: move name_char() above the parse_mac_cmd comment
The helper landed between that comment and the function it describes, so the
comment now reads as if it belonged to name_char(). Moving the helper up puts
the two back together. The generated code is unchanged.
2026-09-18 17:33:24 +02:00
René van Dorst a62866a93a Merge pull request #472 from DrDoof/fix/httpd-sent-len
httpd: account for the bytes actually sent
2026-09-18 16:19:46 +02:00
d00f 42bbed9661 httpd: brace the empty else body so the host tests build clean
gcc flags the else whose only statement is dbg_string(), because that macro
expands to nothing unless DEBUG is on, leaving the branch with an empty body.
The line is older than this branch; the bench is simply the first thing that
compiles httpd.c with a host compiler and -Wall -Wextra.

Turn warnings into errors in the test Makefile too, so the next one is caught
where it appears instead of scrolling past. It is the only warning the four
test binaries produce, so nothing else has to change.
2026-09-18 16:09:56 +02:00
d00f 3c8de6da87 httpd: refuse a configuration with a line the replay cannot take
The replay takes a line of at most 126 bytes, so a longer one is skipped
on boot. Neither the upload path nor the editor looked at line length,
so the file the user saved was not the configuration the switch would
come up with.

Reject such a body in config_take(), before the sector is erased, which
the upload path already maps to 400. The editor points at the offending
line and its length, and measures it in bytes rather than characters, so
a name written in a non-ASCII script is counted the way the switch
counts it.
2026-09-18 16:03:16 +02:00
d00f 3a6f62b1d5 config: skip a line the parser cannot buffer instead of stopping
execute_config() replays the saved configuration line by line into
cmd_buffer. A line that does not fit, or one cmd_tokenize() rejects,
ended the replay right there: everything below it was silently left
unapplied, while the file itself and the editor showing it stayed
complete. On a switch here, a 130-byte line placed after the last real
setting was enough to lose every line that followed it on the next boot.

Report the offending line and carry on with the next one. The skip flag
lives across the flash page refill, so a long line that crosses a page
boundary is not resumed halfway and run as a truncated command.
2026-09-18 16:03:16 +02:00
René van Dorst 530e2683f0 Merge pull request #471 from ScApi/main
Add support for Goalake IG108 / Netcore GS9 / PB-2132 PCB
2026-09-18 11:14:53 +02:00
Sylwester Petela 9fedc6dd3d Remove unnecessary comments and fix removed empty line 2026-09-18 10:19:06 +02:00
Sylwester Petela 5e23d13771 Sort Devices in alphabetical order 2026-09-18 10:17:35 +02:00
Sylwester Petela 09d5857fdd Trim case and PCB images 2026-09-18 10:16:54 +02:00
d00f be309853c9 test: bench for the httpd transmit path
Drives the unmodified httpd.c and uip.c through a handshake and a GET
from a client that moves its receive window, and compares the bytes that
reach the wire with the file they are meant to carry. The served file
holds a position-dependent pattern, so a repeat or a gap is reported at
its offset rather than as content that differs somewhere.

The first scenario holds the window still and must pass: it is the check
that the bench itself is sound, before the two moving-window scenarios
are believed.
2026-09-18 03:17:11 +02:00
d00f 072edb36a8 httpd: account for the bytes actually sent
uip_mss() is the limit on the next segment, not a record of the last
one: uIP recomputes it from the window the peer advertises in every
incoming segment, before the application is called. The ACK path
subtracted it from slen and added it to o_idx, and the retransmit path
handed it to uip_send(), so whenever the window moved between a segment
going out and its ACK coming back, the file being served lost or
repeated the difference. A window shrinking from 1460 to 600 between the
two repeats 860 bytes under fresh sequence numbers, which a browser
writes into the file; a window opening the same way skips 860.

Remember the length in tx_send() and use it in both places. The
retransmit path then repeats exactly the segment uIP still counts as
outstanding, which is what uip_connr->len makes it put on the wire.
2026-09-18 02:39:52 +02:00
d00f 10dd330e3c httpd: subtract through a pointer of known type
uip_appdata is void *, so p - uip_appdata subtracts pointers of
different types. SDCC accepts it, C does not, and no host compiler will
build the file for a unit test. Cast to the type the byte count is meant
in; the generated firmware code is unchanged.
2026-09-18 02:39:52 +02:00
d00f e2c4ed6be3 sfp: clean the module strings where they are read
The vendor, model and serial strings are filtered while the EEPROM read
fills the buffers, so the page copies them out as they are. NUL still
ends a string, so a field shorter than its slot reads the same as before.

The three loops in send_status() become one call each. strcpy() cannot
return the length: rtl837x_common.h hides that prototype under
RTLP_HOST_TEST so host builds take the C library version, and a call
that adds its result to slen does not compile there. A small static
helper does the copy instead, and bank 1 drops by 96 bytes.
2026-09-17 22:32:46 +02:00
d00f 56ee56af2e cmd: keep quotes and control bytes out of names when they are set
The console, POST /cmd and the saved config all go through the command
parser, so one filter there covers every way a name can be set. The
hostname already did this inline; port and VLAN names now do it too.

The SFP strings still need escaping on the way out: they come from the
module's EEPROM, not from a command.
2026-09-17 22:29:57 +02:00
d00f 8626b4c1d4 httpd: don't let a quote in a port name break the web page
A port named NAS"1 made status.json unparsable, so the page stopped
loading, and the name survives a reboot in the saved config. VLAN names
and the SFP strings have the same problem.

They now go through a helper that replaces ", \ and non-printable bytes
with a dot, the way the hostname is already sanitized on ingest. The
length does not change, so the size checks still hold.

The helper also covers the 0xa0 byte that send_sfp_info() skipped as a
special case, so that check is gone.
2026-09-17 22:29:57 +02:00
René van Dorst 0cc09e4015 Merge pull request #467 from DrDoof/test/table-engine-mock
test: run the table code and the JSON pages on the host
2026-09-17 18:56:40 +00:00
d00f 674dc1f35d port, httpd: parenthesise the mixed operators and drop a dead local
Compiling these two files for the host turns up warnings that SDCC does
not print: four places mix & with | and two mix && with ||, and l2_delete
keeps a counter it never reads. The parentheses only say what the
compiler already does, so the generated code is unchanged.

counter_to_html() reads the SFRs directly for the zero test, which saves
14 bytes of bank 1.
2026-09-17 20:49:01 +02:00
Sylwester Petela 7e6b443e07 Merge branch 'main' of https://github.com/ScApi/RTLPlayground 2026-09-17 20:01:38 +02:00
Sylwester Petela 73ec184c23 Added the port-type and led colours 2026-09-17 20:01:29 +02:00
René van Dorst bf41fbd202 Merge pull request #463 from DrDoof/fix/cmd-error-status
cmd: report a rejected command as an error and keep it out of the history
2026-09-17 17:33:56 +00:00
Sylwester Petela 04ab442c6d Merge branch 'logicog:main' into main 2026-09-17 19:28:02 +02:00
René van Dorst c02623a610 Merge pull request #468 from mintsoft/readme-fix
Updating images for new UI
2026-09-17 17:16:19 +00:00
Sylwester Petela c69bf4c5ec Add support for Goalake IG108 / Netcore GS9 / PB-2132 PCB
Support For Goalake IG108 and Netcore GS9 based on same PCB = PB-2132

SFP port untested
2026-09-17 19:13:27 +02:00
Rob Emery 7f4f6dae03 Fixing typo's and image sizes 2026-09-17 09:25:35 +00:00
Rob Emery 4eeb12c020 Merge branch 'logicog:main' into readme-fix 2026-09-17 12:17:09 +03:00
Rob Emery 7d81b15b6e Updating images to match new UI 2026-09-17 10:13:14 +01:00
d00f d6253ddc25 test: the JSON generators against the tables the CLI wrote
page_impl.c and rtl837x_port.c are linked together over the simulated
engine, so /vlan.json, /vlanlist and /l2.json are checked against what
vlan_create, pvid and the L2 table hold, through the same bytes the switch
would. The L2 walk is driven the way the web page drives it, page after
page until an index repeats, which is also where the repeat of the first
entry is pinned down as the wrap marker the page relies on. The counter
test fails on the code before the previous commit.
2026-09-17 04:22:22 +02:00
d00f 8cac67cdf7 httpd: print 64-bit packet counters in full on the status page
send_status wrote the high and the low word of a packet counter one after
the other, each without its leading zeros. That reads fine until a port
passes four billion packets; from then on a low word of 5 prints as "0x15"
next to a high word of 1, and the rates the page derives from the
difference between two readings jump around. send_counters already padded
the low word, so both now share one helper that pads it only when the high
word is set, which also drops the leading "0" the counters page used to
print in front of every small value.
2026-09-17 04:22:22 +02:00
d00f 5620e950ce test: simulate the register file and the table engine, and test the tables
The switch is reached through four register functions and the SFR_DATA
registers, so a flat register file behind them is enough to run the table
code on the host. A few addresses are live: writing TBL_CTRL with EXECUTE
performs the VLAN or L2 operation at once and clears the bit, STAT_GET
fetches a counter, L2_TBL_FLUSH_CTRL drops entries. Nothing ever reports
busy, so a polling loop runs once and the tests say nothing about what
happens when hardware stalls; that question was settled elsewhere.

The header states the VLAN and L2 entry layouts on its own, so a test can
hold what rtl837x_port.c writes against a second reading of the same bits
rather than against itself. The first suite does that for the VLAN entry,
the PVID register two ports share, the static multicast and management
entries, the per-port flush and the trunk registers. Two stub headers stand
in for the ones a firmware build generates, so the harness needs no SDCC
build first.
2026-09-17 04:22:22 +02:00
d00f 83491bc9e3 test: let the shim compile sources that touch the SFRs and libc
Two things kept every register-driven module out of the host harness. The
SFR keywords were unknown, so rtl837x_sfr.h did not compile at all, and once
it did, each translation unit would have defined its own copy of the
registers. They map to extern declarations now; a mock emits the storage
once by re-including the header with the keywords redefined.

The other was memset. rtl837x_common.h hides the firmware's own prototypes
under RTLP_HOST_TEST so host builds use the C library, but nothing declared
the library versions, and the modules do call them. The shim pulls in
string.h ahead of everything, where nothing can clash with it.
2026-09-17 03:55:30 +02:00
d00f bece4a20a1 cmd: let the helper print the Error prefix
The messages said Error: themselves, or usage:, or nothing at all.
cmd_error() prints the prefix now, so every rejected command answers the
same way and the strings only carry what is specific to them.

Two messages that started with Error or refusing are reworded so the
prefix does not read twice. The syslog usage text becomes one string
instead of three calls.
2026-09-16 23:55:11 +02:00
gnacho cdc84c75e4 httpd: give the session cookie a Max-Age matching the timeout
The session cookie carried neither Expires nor Max-Age, so browsers that
clean up session cookies aggressively (LibreWolf, for one) may drop it
while the session is still valid. Send Max-Age=session_timeout so the
declared lifetime matches the timeout already enforced by the firmware.

itoa16_html() only converted four digits, so extend it to five to print
timeouts up to 65535. That also fixes information.json, which used the
same helper and reported wrong values from 10000 s up.
2026-09-16 19:05:48 +02:00
gnacho 6be1871bb4 Merge remote-tracking branch 'origin/main' into feat/session-timeout-config
# Conflicts:
#	httpd/httpd.c
2026-09-16 18:08:21 +02:00
gnacho 4686515de3 web: add a session timeout field to the Services card
Adds a numeric input next to the syslog settings to read and set the
session timeout without knowing the command. The current value is
reported in information.json, so the field also shows the default the
firmware was built with.

Costs 86 bytes of code (BANK1) and nothing in RAM.
2026-09-16 18:07:49 +02:00
d00f c39cc421a7 print: take the message as a pointer to const
The strings are literals in code space, so the parameter can say so.
Drops six warning 357 from the build.
2026-09-16 16:17:46 +02:00
d00f c1df7c23d1 cmd: set the error status and print the message in one helper
Every rejecting path repeated the same two lines. cmd_error() does both,
which also makes it harder to add a message and forget the status.
2026-09-16 16:16:10 +02:00
René van Dorst 9bae4052a3 Merge pull request #454 from bennydiamond/Fix_MACHINE_LIANGUO_HYWS_SGT0108S
Support Lianguo HYWS-SGT0108S - Missing closing bracket
2026-09-16 09:12:33 +02:00
bennydiamond 0d115e0e0b Include HYWS-SGT0108S in makefile recipe 'machine_check' 2026-09-15 15:40:56 -04:00
bennydiamond 8e78d1e629 Missing closing bracket 2026-09-15 15:40:56 -04:00
d00f 13cc9db1ce cmd: report a rejected command as an error and keep it out of the history
run_cmd_body() answers POST /cmd with 400 when err_status is set, but
only the tokenizer set it. Every "Error:", "Invalid ..." and "Unknown
command" from the parsers went back as 200, so the web page reported the
command as applied, and save_cmd put the rejected line into the command
history, from where Save to flash wrote it into the startup config.

Set ERR_INVALID_ARGUMENT on every path that rejects a command: the err:
labels of the parsers, the parsers that print their own error, the
stp parser and the unknown command fallback. Warnings that still apply
the command, such as a full VLAN name table, are left alone.

Record a command in the history only when it was accepted, and let
execute_commands() report the first failure of a multi-line body, since
cmd_tokenize() clears err_status again for every following line.
2026-09-15 21:17:47 +02:00
d00f 3dcab09047 web: keep stored config lines and per-port ingress when saving
Save to flash ran the stored config through the same filter as the
command log, so stored lines the page does not know were dropped. The
stored lines are now carried over as they are and only the log is
filtered.

ingress only changes the ports it lists, but the merge replaced every
stored ingress line. It now merges per port; "ingress t|u|a" still
replaces everything, and per-port settings an all-ports line already
overrode are not revived.

Write from the config editor no longer clears the command log or the
unsaved marker, since the edited text does not hold the changes made on
other pages; only Save, which merges them, clears the log.

The command log grows from 1 kB to 2 kB, which still fits the 2500 byte
output buffer, and send_cmd_log() stops at the end of that buffer.
2026-09-15 20:18:17 +02:00
René van Dorst 94992562fd Merge pull request #458 from DrDoof/fix/httpd-session-init
httpd: do not accept a session cookie before the first login
2026-09-15 18:14:27 +00:00
René van Dorst 6150ada5f8 Merge pull request #457 from DrDoof/fix/flash-zero-length
flash: bail out of reads of zero length
2026-09-15 18:10:50 +00:00
René van Dorst 8116ef0a5b Merge pull request #461 from DrDoof/ci/pull-request
ci: build pull requests too
2026-09-15 18:06:42 +00:00
d00f 0cd56b1196 ci: build pull requests too
The workflow only ran on push, so a pull request opened from a fork
never got a build, and neither did the host-side unit tests that run in
the same job.
2026-09-15 19:27:50 +02:00
d00f 3b3f36256c flash: drop the unused status variable from flash_read_bulk()
Since #427 the busy wait calls flash_read_status() directly, so the
local it used to go through is no longer read anywhere.
2026-09-15 15:28:36 +02:00
d00f 18b28edcbb httpd: keep a continued upload away from the cmd and login handlers
handle_post() takes the request path from byte 6 of the segment it is
given. For the first segment of a POST that is the path, but for a later
segment of a multipart upload it is whatever part of the file happens to
sit there. The cmd and login handlers were matched before the multipart
state was checked, so a segment of a config or firmware upload with
"cmd " at that offset ran the rest of the segment as CLI commands, and
one with "login " there aborted the upload with 400.

Check for an upload in progress, and for new upload and config requests,
before looking at cmd and login. A new POST cannot match both sets of
words, so nothing changes for requests that are not uploads.
2026-09-15 09:57:49 +02:00
d00f 6ea9503ab2 httpd: do not accept a session cookie before the first login
session_id is cleared at boot and only a successful login fills it in,
while the uptime counter and last_session_use both start at 0. For the
first SESSION_TIMEOUT seconds after a start, a request carrying an empty
"Cookie: session=" therefore passes is_word_x(session, session_id) and
is treated as logged in, which is enough for /cmd, /config and /upload.

Only look at the cookie once a session has been handed out.
2026-09-15 09:37:11 +02:00
DrDoof e0df37f359 flash: bail out of a security-register read of zero length
flash_read_security() ends its loop the same way flash_read_bulk() did
before the previous commit: only through comparisons with 1, 2, 3 and 4. A length of
zero matches none of them, the following subtraction wraps on uint16_t
and the loop keeps printing until the counter comes back down to 4.

No caller passes zero today, since the only one is the CLI with a
constant length of 40, but the bound belongs in the function rather
than in whoever calls it.
2026-09-15 09:14:40 +02:00
DrDoof c04a5fc3a4 flash: bail out of a bulk read of zero length
flash_read_bulk() ends its copy loop only through the comparisons
len == 1|2|3|4, so a length of zero matches none of them. The
following len -= 4 wraps on uint16_t to 65532 and reaches 4 only
after 16384 iterations, writing 65536 bytes to the destination and
overwriting the whole XDATA space.

send_config() gets there with valid_len 0 whenever the stored
configuration begins with a NUL byte, which is the state left by
uploading an empty configuration file. Every later request for the
configuration page then clears the management VLAN and the STP
flag and replaces the host address, while the switch keeps running
and keeps forwarding traffic.
2026-09-15 09:14:40 +02:00
gnacho f0b311fc43 httpd: make the session timeout configurable
The session timeout was a compile-time constant (200 s), so changing it
meant rebuilding the firmware. Make it a variable that keeps the same
default and add a "session <seconds>" command, so it can be set from
the console or from the startup configuration like the other settings.

Measured on KP_9000_9XHML_X_V3_1: ROM +180 bytes (BANK1 +17, BANK2
+161) and xdata +2 bytes; the internal RAM map is unchanged.
2026-09-15 00:24:45 +02:00
René van Dorst 2f005d2993 Merge pull request #324 from bloqaudio/pr/host-test-harness
test: add host-side unit-test harness, covering cmd_editor
2026-09-14 21:59:07 +00:00
SkullyDev 974052877d Added config for HASIVO_S600W_4GT_2XGT_SE (4x2.5G, 2x10G)
SysLed behavior logic added so it can mimic any original style
2026-09-15 00:37:27 +03:00
gnacho ae77ffe495 feat(translate): add Spanish (es) translations for single-page UI 2026-09-14 22:44:25 +02:00
bloqaudio f65416f0d1 ci: run the host-side unit tests on every push
The harness builds with the gcc the workflow already installs and runs
in under a second, so it goes before the SDCC steps and fails the build
first when a host-visible regression lands.
2026-09-14 15:04:19 -05:00
bloqaudio ffde4eaf60 web: stop polling when the page has had no input for ten minutes
The firmware counts every authenticated request as session activity, and
the page polls the status every 2.5 s, so an open tab never let the
session expire. Track the last mouse, key, touch or wheel event and treat
the page as hidden once ten minutes have passed without one: the pollers
keep ticking but send nothing, the session times out in the firmware as
it did before, and the next input resumes polling, which meets the 401
and returns to the login page.
2026-09-14 14:06:41 -05:00
bloqaudio c05d83132b test: drop the unused typedef keyword and make the gcc-only warning switch conditional
Building the firmware headers on the host showed two warnings from
machine.h: `typedef struct machine { ... };` and the same for
machine_runtime declare no alias, so the keyword only draws "useless
storage class specifier in empty declaration". Every user says
`struct machine`, so drop the keyword.

-Wno-builtin-declaration-mismatch is a gcc switch and clang reports it
as an unknown option on every build; add it only when the compiler is
not clang.
2026-09-14 14:05:33 -05:00
bloqaudio dcd3cd5a98 test: add host-side unit-test harness, covering cmd_editor
Compile selected firmware translation units unmodified under host gcc with
AddressSanitizer and UBSan, force-including a shim header that erases the
SDCC 8051 keywords, and link them against a mocked serial ring and console.
A first suite covers the CLI line editor: line entry, backspace editing,
and both long-input overflow cases (length cap and ring-pointer stall).

The only firmware-source change is one #ifndef RTLP_HOST_TEST block in
rtl837x_common.h grouping the libc-named prototypes (sleep/memcpy/memset/
strlen/strcpy/strcmp) so host builds take them from the C library instead;
on-target builds are unaffected.

Run with: make -C test
2026-09-14 14:02:35 -05:00
René van Dorst 9f107b6e60 Merge pull request #410 from DrDoof/fix/httpd-rexmit-cont
httpd: retransmit a continued chunk from where it was sent
2026-09-14 18:12:43 +00:00
René van Dorst 15b10d9ed0 Merge pull request #427 from bennydiamond/Flash_SingleIO_ReadStatus
Temporary revert to SingleIO for Flash operations (except data read)
2026-09-14 17:19:29 +00:00
René van Dorst abf1cd1344 Merge pull request #429 from bloqaudio/feat/webui-v2
web: replace the multi-page UI with a themed single-page app
2026-09-14 17:17:39 +00:00
René van Dorst b5b52776bc Merge pull request #449 from bloqaudio/fix/bank2-dhcp-bank3
Move DHCP out of BANK2 into BANK3
2026-09-14 16:55:24 +00:00
René van Dorst 0486c34bfd Merge pull request #448 from bloqaudio/fix/stp-port-keyword
stp: accept the documented port keyword again
2026-09-14 16:55:08 +00:00
bloqaudio 690092d564 Move DHCP out of BANK2 into BANK3
BANK2 outgrew its 48 KB window on PCB_SWTG018AS_V2_1_0 (49182 bytes, 30
over) once the spanning tree code learnt about link aggregation, and the
image builder refuses to link. Only KP_9000_6XHML_X2 is built in CI, so
the overflow was not caught there.

dhcp.c is self-contained: its three exports are __banked, the rest of
the module is only reached from inside it, it holds no __code pointers,
and its uip calls already crossed into BANK1. Moving the pragma is the
whole change. BANK2 49182 -> 43691 on PCB_SWTG018AS_V2_1_0, BANK3 5709
-> 11206, BANK1 +6 for the trampolines; the generated assembly shows no
plain lcall leaving the module's bank.
2026-09-14 08:29:39 -05:00
bloqaudio 115ea09ebb web: address a link aggregation group as one spanning tree entity
The firmware lists a LAG as its own entity in /stp.json (`lag` and `mbr`,
`p` 0) and drops the member ports from the list; its settings take
`stp lag <n>`. Give the group its own row in both spanning tree tables,
named with its members, key the cells by entity rather than port number,
and rebuild the tables whenever the entity set or a membership changes so
a group created on the LAG tab appears without a reload. The `stp lag`
commands are recognised for the unsaved-changes badge and the startup
config merge alongside `stp port`.
2026-09-14 08:14:53 -05:00
bloqaudio f3e1a393b5 stp: accept the documented port keyword again
The rename of the entity index turned `stp port <n>` into `stp ent <n>`
in the parser only. doc/stp.md, the web UI and every saved startup
config still spell it `stp port`, so per-port settings were rejected on
the console and silently dropped when the startup config was replayed at
boot. Restore the keyword; `stp lag <n>` is unchanged.
2026-09-14 08:05:57 -05:00
bloqaudio 206c72b5ef web: floor the optical power at -40 dBm
A dark receiver reports 0 mW, which printed as -Infinity dBm. The DOM
value has a resolution of 0.1 uW, so -40 dBm is the lowest reading the
module can express; clamp there.
2026-09-14 08:03:36 -05:00
bloqaudio 472811cc9a doc: add the single-page UI to the changelog 2026-09-14 08:03:36 -05:00
bloqaudio 80552816b9 web: move the language and theme selectors to the System section
The header carried the save button, the language selector and the theme
selector on every section, although the latter two are set once. They
now live in a Display card in the System section; the header keeps the
title, the unsaved-changes badge and Save to flash.
2026-09-14 08:03:36 -05:00
bloqaudio c1caf6f569 web: stop the MAC table walk when its section is left
The forwarding database is read a page at a time; leaving the MAC table
or ports section let the walk carry on through the request queue. Give
the walk a generation number that the leave hooks bump, and stop the
walk at the next page when it is stale.
2026-09-14 08:03:36 -05:00
bloqaudio ddad65fe90 web: keep a VLAN's row when its details cannot be read
The VLAN table is one /vlanlist plus one /vlan.json per VLAN. A failed
per-VLAN request rejected the chain, so every VLAN after it was missing
from the table, indistinguishable from VLANs that do not exist. Keep the
row with the id and name from the list and a ? in the port columns.
2026-09-14 08:03:36 -05:00
bloqaudio ff7b517491 web: let the merged config be edited before it is written
Save to flash showed the merged startup config read-only, so a stray line
had to be fixed afterwards in the System section. The preview is now a
textarea: the size and unknown-line notes follow the edits, the write
button is disabled above 2048 bytes, and what is written is the edited
text.
2026-09-14 08:03:36 -05:00
bloqaudio 65417433eb web: fit the hostname in the sidebar, widen the number fields and the page
The default hostname (RTLPlayground- plus six hex digits) overflows the
200 px sidebar; clip it with an ellipsis and keep the full name in the
title. The MTU and Mbit/s fields were 70 px, which leaves no room for five
digits next to a number spinner. The main column stops at 1400 px instead
of 1100 px so the dashboard cards sit side by side on a wide screen.
2026-09-14 08:03:36 -05:00
bloqaudio b7621fad31 web: keep the port detail view live and summarise each port in a tooltip
The old dashboard showed a hover tooltip per port with the link state and,
for SFP ports, the module and its DOM readings, and rewrote it on every
poll. The single-page app replaced it with a click-to-open dialog that was
a snapshot: the values did not change while it was open.

Build the rows once per port and use them for both: the dialog is
re-rendered on every status poll while it is open, and each port in the
strip carries the same rows as its title.
2026-09-14 08:03:36 -05:00
bloqaudio 82f78cb8f2 web: render the per-port tables in physical port order
The firmware emits every per-port array in logical port order, which on a
board with a remapped port table (SWGT024: phys_to_log = {4,5,6,7,8,3,...})
is 6, 1, 2, 3, 4, 5. The old pages iterated over physical port numbers and
looked each entry up; the single-page app rendered the arrays as received,
so the ports, spanning tree, EEE and bandwidth tables and the dashboard
strip came out in logical order, and the strip put the SFP marker and the
detail view on the wrong port.

Sort every array by portNum on receipt so index i is port i+1 throughout.
2026-09-14 08:03:36 -05:00
bloqaudio 75f2ade1a1 doc: describe the web UI files and translation table after the rewrite 2026-09-14 08:03:36 -05:00
bloqaudio 3e91b05a42 web: replace the multi-page UI with a themed single-page app
The pages become one index.html holding every section, one app.js and a
self-contained login.html. The layout is a sidebar with light, dark and
Selenized themes (or follow the OS), a live dashboard with per-port
packet rates, and confirmation dialogs in front of the actions that can
lock the operator out.

Behaviour changes against the previous pages:

- every request goes through one queue that waits for the previous
  response BODY rather than its headers: the firmware shares a single
  output buffer across connections and delimits responses by closing,
  so a second request in flight corrupts the first
- "Save to flash" merges the command log into the startup configuration
  with the grammar the console accepts, shows the result before writing,
  verifies the sector by reading it back and clears the command log only
  after that; the header shows an "unsaved changes" badge while the
  running configuration differs from flash
- the console section shows the output the firmware returns for a
  command, including the parser's explanation on a rejected one
- the firmware update section checks size, bank header and CRC16 of the
  image in the browser before uploading, shows upload progress and waits
  for the switch to come back after the reset
- the ports table shows the learned MAC of a port when there is exactly
  one and the count otherwise; the MAC table sorts per column, filters,
  and names the LAG a learned address sits behind
- the spanning tree section sends only the settings that changed on
  Apply instead of one command per control event
- link aggregation groups are numbered 1-4 like the command line, and
  applying an empty group clears it

The translation table is inline in app.js with English, Japanese and
Chinese. The language selector sits in the page header and reloads the
page so that dynamically built tables are rebuilt in the new language.

Only login.html and the favicon are served before authentication; the
stylesheet, the port images and the old script bundle no longer exist.
The simulator's allowlist follows the firmware's.
2026-09-14 08:03:36 -05:00
bloqaudio ec1cb4106d httpd: count every authenticated request as session activity
The session timer was refreshed only when a page from the file table was
served, so a client that loads its pages once and then talks to the JSON
endpoints alone is logged out SESSION_TIMEOUT seconds after the load,
however busy it is. A firmware upload started after that point is answered
with 401 instead of a verdict. Refresh the timer wherever the session
cookie validates, which covers every authenticated request, and drop the
file-serving refresh that this makes redundant.
2026-09-14 08:03:35 -05:00
René van Dorst 62c6c65f1c Merge pull request #435 from bennydiamond/Support_HYWS-SGT0108S
Support for Lianguo HYWS-SGT0108S 8 + 1 unmanaged
2026-09-14 09:46:20 +02:00
René van Dorst c691591295 Merge branch 'main' into Support_HYWS-SGT0108S 2026-09-14 09:40:16 +02:00
logicog 434acbcc13 Merge pull request #397 from DrDoof/feat/stp-lag-entity
stp: treat a link aggregation group as one entity
2026-09-14 09:32:36 +02:00
René van Dorst 1aae2fac30 Merge pull request #364 from dw4rren/add-mokerlink-poe-2g080110gs
add support for MokerLink POE-2G080110GS
2026-09-14 09:23:15 +02:00
David Warren 1f9b3df3e6 Add basic support for MokerLink POE-2G080110GS 2026-09-13 23:18:55 -04:00
d00f eb4bae278b Makefile: let the linker check external RAM against 0xBFFF
The 8051 in the RTL837x has 48 kB of XDATA, 0x0000 to 0xBFFF. Writing
a byte and reading it back from the same address works all the way up
to 0xBFFF on a SWTGW218AS, while at 0xC000 the byte comes back as 0x00.
Without --xram-size the linker assumes 16 MB, so running past the end
would still build cleanly and only show up at run time.

The value is 49151 rather than 49152. The linker check adds up the
sizes of the XDATA areas, and XDATA allocation starts at 0x0001, so a
limit of 49152 would still let the last byte land on 0xC000.
2026-09-14 02:07:57 +02:00
bennydiamond 7908044a44 Rework flash_write_enable() 2026-09-13 17:32:46 -04:00
bennydiamond 0ec1a5e63f Proper mapping for phys_to_log_port array 2026-09-13 17:16:34 -04:00
bennydiamond 114af559c7 Add support for Lianguo HYWS-SGT0108S unmanaged 8+1 2026-09-13 12:08:20 -04:00
René van Dorst 5a2c4ca029 Merge pull request #421 from DrDoof/fix/fileadder-binary-safe-length
fileadder: track the data length instead of measuring it with strlen
2026-09-13 11:51:37 +00:00
René van Dorst 63e367d298 Merge pull request #424 from DrDoof/refactor/split-sfp-boot
Move the boot and SFP handling out of the common area into BANK3
2026-09-13 09:51:57 +00:00
René van Dorst 5e513daf17 Merge pull request #423 from DrDoof/fix/arp-refresh-from-ip
Refresh ARP entries from incoming IP traffic
2026-09-13 08:41:10 +00:00
bennydiamond 3ec9319d77 Fix flash driver for proper SIO ops
Set SPI peripheral to SIO mode for all operations except READ.
Set back SPI Peripheral to DIO after said operation.
2026-09-13 01:40:26 -04:00
d00f b42eace21d Declare flash_default_config() in rtl837x_common.h
boot.c calls it from BANK3 and only rtlplayground.c had a prototype, so
the compiler saw an implicit declaration. The call itself was fine, the
function lives in the common area, but the warning was real. Move the
prototype into the shared header.

Reported by vDorst.
2026-09-13 05:18:07 +02:00
d00f 3dfd6a88c7 Move the SFP handling out of the common area into BANK3
sfp.c takes handle_sfp, sfp_module_read, sfp_apply_quirks, sfp_get_info,
sfp_read_field, sfp_print_info, setup_sfp_gpio, the quirk table and the module
state that goes with them. Everything exported is __banked: handle_sfp() and
sfp_print_info() are reached from cmd_parser.c in BANK2, the rest from the
common area, and both are bank boundaries.

The quirk table needed checking rather than assuming. Its entries are __code
pointers, and a __code pointer carries no bank, so passing one to a function in
a different bank hands it an offset read through the wrong window. That is safe
here because strcmp() sits in the common segment at 0x51f and is reached with a
plain lcall, so the bank register still says 3 while it runs. Verified in the
built image: the table at 0x35580 holds 0x563c and 0x5644, which resolve to
"QSFPTEK" and "QT-SFP+-T" inside BANK3.

Common segment 13393 -> 8432 bytes, leaving 7492 free rather than 2531. BANK3
holds 5710, BANK1 is unchanged and BANK2 grows by 31 for the trampolines.
All 34 machines in machine.h build and link, and the same walk over the
generated assembly finds no plain lcall crossing a bank boundary.
2026-09-13 05:16:50 +02:00
d00f 7fa26e0ae3 Move the boot and SerDes setup out of the common area into BANK3
The common segment is the scarce one: sixteen kilobytes shared by every bank,
and before this it had about 2.5 kB left. Most of what moves here runs once at
startup and does not have to sit in it. The two SerDes helpers are the
exception: the link-change handler and the SFP polling both reach sds_config()
while the switch is running, so they are not startup-only. None of it is
reached from an interrupt.

boot.c takes setup_i2c, init_smi, nic_setup, rtl8373_revision, rtl8224_enable,
early_boot_handle_button, set_hostname_default and the two SerDes helpers.
BANK3 is enabled with the linker flag; nothing else in the build needed
changing.

Everything moved is declared __banked, since every caller is outside BANK3:
main() and friends run from the common area and rtl837x_init.c calls
sds_config() and sds_config_mac() from BANK2. The calls the moved code makes
outward were already banked, so that side needed no work.

Common segment 13393 -> 10137 bytes, leaving 5787 free rather than 2531.
BANK3 holds 3732. All 34 machines in machine.h build and link. Walking
the generated assembly for plain lcalls that cross a bank boundary finds none
among 2808 calls; those build cleanly and only fail at run time, so it seemed
worth checking by machine rather than by eye.
2026-09-13 05:16:50 +02:00
d00f 1c8cdab4bf arp: take the sender MAC from where a received frame keeps it
IPBUF is laid out for frames we send, with the 12 byte TX descriptor in
front of the Ethernet header. A received frame starts with the Ethernet
header at uip_buf[0] and carries the RTL and VLAN tags after the
addresses, so IPBUF->ethhdr.src read the port mask and both VLAN fields
as a MAC. Every IP packet from the local network then replaced the
sender's ARP entry with that, and our replies went to an address nobody
has. The IP source address sits at the same offset in both layouts,
which is why the rest looked right.

Use BUF, the receive view uip_arp_arpin() already works from.
2026-09-13 04:14:21 +02:00
René van Dorst f49ca0d6fe Merge pull request #366 from gnacho/fix/kp9000-v31-mac-offset
Add factory MAC flash offset for keepLink KP-9000-9XHML-X V3.1 + mac command
2026-09-12 07:58:54 +00:00
gnacho 12fb6fd809 cmd_parser: parse mac into a global result buffer
parse_mac() now writes to the global mac_parse_result[6] and takes only the
index, like the other parsers (ip[4]). This drops the local __xdata buffer
and the now-unused hexval() helper.

Build (KP_9000_9XHML_X_V3_1): ROM 110302 -> 110193 bytes, XSEG and internal
RAM unchanged.
2026-09-11 23:42:01 +02:00
René van Dorst dad65fd550 Merge pull request #426 from DrDoof/fix/igmp-port-shift
igmp: mask the port number before shifting by it
2026-09-11 20:22:34 +00:00
René van Dorst dc82e044bc Merge pull request #436 from mintsoft/fix_gh_build
Fix Github Build - Add zlib
2026-09-11 17:22:37 +00:00
Rob Emery 7daa9effe0 Fixing dependencies and also broken link 2026-09-11 11:21:05 +00:00
Rob Emery 950586135a Build seems to be breaking on zlib.h being missing? 2026-09-11 11:14:32 +00:00
d00f c1ad800fc5 stp: name the MSTP state bits and the entity index
The state literals were spread over eighteen places in the file, sixteen
of them older than this branch, so naming only the new ones would have
left it written half one way and half the other.

Naming them separates two roles the literal hid: 0b11 is the forwarding
state in a call and a two bit field mask in a read-modify-write, hence
both STP_ST_FORWARDING and STP_ST_MASK. The comment above stp_state_set()
said what the defines now say, so it goes.

stp_parse() holds an entity index, not a port. cmd_parse_port() has
already mapped the front panel digit through machine.phys_to_log_port[],
and the aggregates continue that same logical range above the CPU port,
so call the variable ent. Reading the two branches of the if no longer
means following the parser two functions down.

No change to the generated code: 5331 instructions before and after.
2026-09-10 15:17:53 +02:00
gnacho dc4fb5becc fix(parser): reject malformed MAC inputs and make print helpers banked
parse_mac now accepts only a strict aa:bb:cc:dd:ee:ff (or '-' separators)
form: each octet is exactly two hex digits, the 5 separators are a single
consistent character, and nothing is allowed after the 6th octet. This
rejects the malformed values the reviewer flagged (repeated separators,
missing digits, trailing characters). A new inline hexval() helper does the
digit-to-value conversion and can be reused by future commands.

print_mac and print_ip are marked __banked to match cmd_parser(), which
lives in codeseg BANK2 and is itself __banked, so callers across the code
bank work correctly. cmd_parser.h is updated with the new prototypes and
the __banked qualifier on print_ip.

Verified with make MACHINE=KP_9000_9XHML_X_V3_1: the build links cleanly
and the internal-RAM (DSEG) layout is unchanged against main; XSEG is
unchanged and ROM grows by 184 bytes. A host-side test confirms parse_mac
rejects the two malformed inputs from the review.
2026-09-10 00:14:58 +02:00
d00f 78ecef15e9 stp gui: address a link aggregation group as one entity
The web UI became one index.html plus one main.js while this branch was
open, so the STP page changes had no html/stp.js and html/config.js left to
live in. They are re-applied here against the new structure.

A row now carries two derived fields: the name the CLI expects for it
("port N" or "lag N") and the key its controls are keyed by (the port
number or "LN"). A group's row is labelled with its front panel members,
which /stp.json reports in the mbr field.

conf_cmds and conf_overwrite accept the "stp lag N ..." forms so the
startup config keeps them.
2026-09-09 21:01:15 +02:00
d00f eab927f52a stp: name the port count instead of spelling out ten
Review of #397 pointed at the bare 10, which shows up in fifteen places. It
is the width of the port field in the MSTP register, so it is the CPU port
index plus one and it gets a name.

The web page counted to ten as well when listing a group's members. It uses
numPorts now, like the rest of that page does.

The aggregation section of the doc no longer names the OEM firmware or calls
a group a trunk, and it says plainly that the hardware keeps no per-group
state, so the firmware writes the members itself.

Generated code is unchanged: the only difference in the assembly is the
version string.
2026-09-09 20:58:35 +02:00
d00f 02c816647c stp: treat a link aggregation group as one entity
Spanning tree so far saw only physical ports. Two ports aggregated towards
the same neighbour look to the protocol like two parallel paths, so it
blocks one of them and cuts the group in half.

Give each of the four groups an STP entity of its own, numbered above the
physical ports. A member port answers to its group: BPDUs arriving on it
are accounted to the entity, the entity's state is written to every member
together, and its BPDUs carry the state of a live member. Ports outside
any group keep behaving exactly as before.

Membership comes from the aggregation tables once a second; when it
changed, the entity's state is reapplied to the new set of members. The
CLI takes lag <1-4> wherever it takes port <n>, the web UI shows a group
as one row, and the saved config keeps the lag form of the commands.
2026-09-09 20:58:35 +02:00
logicog d3f1069285 Merge pull request #315 from eraiza0816/feat/webui-compression
Feat/webui: compress the embedded assets and switch to a single-page layout
2026-09-09 15:31:22 +02:00
eraiza0816 98bda62181 Fix send_counters validation and changelog 2026-09-09 17:51:51 +09:00
René van Dorst 3f65db9984 Merge pull request #413 from DrDoof/fix/vlan-name-bounds
vlan: bound the name table before writing into it
2026-09-09 08:47:05 +02:00
logicog df95c544d4 Merge pull request #418 from DrDoof/fix/crc16-psbank
crc16: leave the code bank as it was found
2026-09-09 06:13:56 +02:00
eraiza0816 7df3c87892 Fix counters rejection of logical port 0 2026-09-09 10:58:16 +09:00
d00f 53f12a42c5 crc16: banked wrapper for cross-bank callers
The lookup tables live in BANK1, so the routine switches the code bank to
reach them with movc and never switches it back. That is the contract the
__naked declaration already implied: same bank or common segment only.
The name now says it.

crc16_bank1 is the routine unchanged. crc16 becomes a __banked wrapper in
BANK1, so a caller from another bank has its own bank put back by
__sdcc_banked_ret. The two existing call sites take the direct path and
pay nothing.
2026-09-09 01:52:12 +02:00
d00f 1b514b5ea8 vlan: bound the name table before writing into it
A VLAN name is copied into vlan_names with nothing checking vlan_ptr
against VLAN_NAMES_SIZE. The only limit is the 128 byte command line, so
enough named VLANs walk straight off the end of a 1 kB table.

What sits immediately behind it makes this worse than a plain overflow: at
0x048A comes vlan_ptr itself, the index driving the write, then port_names
and cmd_buffer. Once the index is corrupted the following writes land
wherever it now points, and the configuration is replayed at boot, so a
config file that overflows does it again on every start.

Measure the name first and refuse it if the three id digits, the name, its
separating space and the terminator would not fit. The check goes before
vlan_name_remove() so a name that does not fit cannot take the existing one
with it, and the VLAN still gets its ports: dropping the whole line would
lose port membership during a config replay, which is worse than losing a
name.

The copy walks a pointer instead of indexing vlan_names[vlan_ptr++] each time,
and the word offset comes out of cmd_words_b once. Writing the bound as
VLAN_NAMES_SIZE - 5 folds the constant at compile time instead of adding it on
every call. BANK2 ends up 25 bytes smaller than it was before the check
existed.
2026-09-09 00:38:37 +02:00
René van Dorst dd09be9dd0 Merge pull request #412 from DrDoof/fix/httpd-init-conn
httpd: do not initialise tstate through uip_conn
2026-09-08 22:22:08 +00:00
René van Dorst b2b7f8a0c6 Merge pull request #414 from DrDoof/fix/dhcp-restart-leak
dhcp: do not leave the old socket behind when starting again
2026-09-08 22:19:18 +00:00
René van Dorst 5b28ddf80a Merge pull request #416 from DrDoof/fix/phy-100m-half-adv
phy: advertise half duplex only when half duplex was asked for
2026-09-08 22:06:11 +00:00
d00f 624f93a28a igmp: mask the port number before shifting by it
The RTL tag carries a 4-bit port number for received frames, and the two
places that turn it into a mask bit take the whole byte:

	entry.pmask |= (1L << (IGMP_I->rtl_tag.pmask >> 8));

The comment beside it already says only the low four bits count, but nothing
takes them. rtl837x_stp.c:424 does the same extraction and does mask:

	stp_scratch = ((uint8_t)HTONS(STP_I->rtl_tag.pmask)) & 0x0f;

With a count above 15 the result is not a bit at all. SDCC narrows the 1L to
16 bits and emits the shift as a djnz loop, so a count of 200 spins the loop
200 times and leaves zero behind: the join adds no port and the leave clears
none, neither saying anything. It also spends those iterations inside packet
handling.

Mask to four bits and shift a uint16_t, which is the width the surrounding
code works in. Six bytes of BANK1.

Worth saying that this does not make snooping work on its own. struct
igmp_pkt has no vlan_tag field while struct eth_in describes the same frame
with one, so every IP and IGMP field below rtl_tag is read four bytes early.
2026-09-08 23:51:25 +02:00
d00f fe3da0c7a5 phy: advertise half duplex only when half duplex was asked for
The 100M branch of phy_set_speed() was missing an else. A port set to 100M
half wrote 0x1481, walked into the next if, found duplex was not 1, took its
else and overwrote the register with 0x1581 straight away, so it advertised
100BASE-TX half and full both. That is what asking for both is meant to do,
and forcing half is something people do to match a partner that cannot
negotiate, so the link comes up looking fine and misbehaves under load.

Bit 7 is 100BASE-TX half and bit 8 is full, which makes 0x1481, 0x1501 and
0x1581 half, full and both, the same shape as 0x1421, 0x1441 and 0x1461 in
the 10M branch above.

Both branches now choose the advertisement into a variable and write it once,
and they compare against the PHY_DUPLEX_* names instead of 0, 1 and whatever
is left. Same values reach the PHY; BANK2 loses 92 bytes.
2026-09-08 23:49:11 +02:00
d00f 412098d0ed phy: advertise half duplex only when half duplex was asked for
The 100M branch of phy_set_speed() is missing an else. A port set to 100M
half writes 0x1481, falls into the next if, finds duplex is not 1 and takes
its else, and immediately overwrites the register with 0x1581. The port
ends up advertising 100BASE-TX half and full, which is what "both" is
supposed to mean.

The 10M branch directly above has the chain the 100M one meant to have, and
the values line up with it: bit 7 is 100BASE-TX half, bit 8 is full, so
0x1481, 0x1501 and 0x1581 are half, full and both, exactly as 0x1421,
0x1441 and 0x1461 are for 10M.

Forcing half duplex is something people do to match a link partner that
cannot negotiate, so quietly advertising full as well is the wrong way for
this to fail.

Not tried on hardware: every port on the switch here has a link up, and
setting the speed restarts negotiation.
2026-09-08 23:49:11 +02:00
René van Dorst 5ecce86ef1 Merge pull request #422 from DrDoof/fix/no-idle-in-isr
Do not enter Idle mode from the external interrupt 2 handler
2026-09-08 21:47:02 +00:00
d00f 247b230212 Do not enter Idle mode from the external interrupt 2 handler
isr_ext2() ends with PCON |= 1, putting the core to sleep from inside an
interrupt handler.

The DW8051 leaves idle when any enabled interrupt is activated, so the core
does not stay down for long. It naps until the next enabled interrupt request
turns up, with the 200 Hz system tick putting a ceiling of 5 ms on that, and
then carries on.

The sleep buys nothing either way, because the instruction after it is the
return. While it lasts the handler holds the priority latch, so everything at
its level or below waits, and that includes the tick, the console and the NIC
receive interrupt.

The handler still clears its flag, which is what it has to do. The bit numbers
beside the two masks were wrong: 0xef clears bit 4 and 0xdf clears bit 5,
not 7 and 6.
2026-09-08 23:35:27 +02:00
d00f c012dda029 Do not enter Idle mode from the external interrupt 2 handler
isr_ext2() ends with PCON |= 1, putting the core to sleep from inside an
interrupt handler.

The DW8051 leaves idle when any enabled interrupt is activated, so the core
does not stay down for long. It naps until the next enabled interrupt request
turns up, with the 200 Hz system tick putting a ceiling of 5 ms on that, and
then carries on.

The sleep buys nothing either way, because the instruction after it is the
return. While it lasts the handler holds the priority latch, so everything at
its level or below waits, and that includes the tick, the console and the NIC
receive interrupt.

The handler still clears its flag, which is what it has to do. The two wrong
comments next to the masks go as well: 0xef clears bit 4 and 0xdf clears bit 5,
not 7 and 6, and the masks already say that without help.
2026-09-08 23:01:28 +02:00
gnacho 6616a0c9ce feat(cli): add mac command to set the management MAC from the config
Add a `mac <aa:bb:cc:dd:ee:ff>` console command (and a bare `mac` to show
the current address) that overrides the management MAC at runtime. The
address is validated (unicast, globally administered, non-zero OUI) and
the static L2 management entry is re-registered so the change applies
without a reboot. Because it is a normal command, a line in the startup
config persists it across boots: execute_config() runs it before the final
static L2 entry and the derived hostname are set up.

Also point the keepLink KP-9000-9XHML-X V3.1 machine definition at the
factory MAC area (0x1FC000, same location other machines use) so a valid
factory MAC is picked up at boot instead of falling back to a locally
administered address.

The MAC parsing/printing lives in parse_mac()/print_mac() helpers next to
parse_ip()/print_ip() so future commands can reuse them, and a memcmp()
helper joins the project's own memcpy()/memset() (no libc is linked).
2026-09-08 22:46:14 +02:00
d00f eab8c901cf Fix the EXIF bit numbers in the ext2 and ext3 handler comments
0xef clears bit 4 (IE2) and 0xdf clears bit 5 (IE3). The comments
said 7 and 6.
2026-09-08 22:43:06 +02:00
d00f 21a9fdf45a Do not enter Idle mode from the external interrupt 2 handler
isr_ext2() ends with PCON |= 1. On the DW8051 any enabled interrupt request
ends idle, so the handler sleeps until the next request arrives, the system
tick at the latest, and only then returns. Everything at the same priority
waits with it, and the instruction after the idle is RETI, so the sleep buys
nothing.

The handler still clears its flag, which is what it has to do. The other four
uses of PCON |= 1 are in task context and are left alone.
2026-09-08 22:43:05 +02:00
René van Dorst 1ed12d1101 Merge pull request #417 from DrDoof/fix/dhcp-missing-t1
dhcp: renew at half the lease when the server omits T1
2026-09-08 19:50:39 +00:00
d00f 8d7faf5312 httpd: retransmit a continued chunk from where it was sent
A file larger than the output buffer is sent in two phases: the first
buffer goes out in MSS-sized pieces, with o_idx walking through it, and
what is left is then streamed from flash a chunk at a time into outbuf
from offset 0.

The continuation branch never reset o_idx, so it kept the offset the first
phase had left it at, while uip_rexmit() retransmits from outbuf + o_idx.
A lost continuation segment was therefore retransmitted as the bytes 1460
further into the buffer, which the client accepts as the missing data, and
with o_idx + slen past the 2500-byte buffer it also read whatever xdata
follows outbuf.

Reproduced on a SWTGW218AS by dropping 15% of the switch's TCP segments:
seven of eight fetches of a 21 kB static came back the right length and
the wrong content, each first differing at a fixed phase of the 1460-byte
chunk. With o_idx reset the retransmission repeats the chunk it sent.

The o_idx += slen right after slen = 0 went too: it added nothing.

(cherry picked from commit 789d29423288f89411a41673e26ce1854b296c3d)
2026-09-08 16:22:07 +02:00
eraiza0816 af4101f060 Fix language select, stat port index, devWalk polling, and duplicate BW command 2026-09-08 14:15:44 +09:00
d00f d6d9c368e9 Refresh ARP entries from incoming IP traffic
uip_arp_ipin() is an empty macro with its body under #if 0, both inherited
from the uIP import and untouched since. That leaves uip_arp_update()
reachable only from uip_arp_arpin(), so an entry is refreshed only by ARP
addressed to us and ages out after UIP_ARP_MAXAGE no matter how much traffic
passed over it.

When it goes, the next transmission to that host hits the miss path of
uip_arp_out(), which overwrites the packet in uip_buf with an ARP request and
returns. For TCP that costs a retransmission. For UDP nothing retransmits, and
handle_tx() sends the UDP connections through uip_arp_out() too, so the
datagram is lost.

The body could not be enabled as written: uip_len -= sizeof(struct uip_eth_hdr)
is a leftover from when UIP_LLH_LEN was 0. Here it is ETHER_HEADER_SIZE +
RTL_FRAME_DESC_SIZE and uip.c reads the IP header at uip_buf[UIP_LLH_LEN], so
uip_len has to keep the link layer in it. The rest of the function is sound.
2026-09-08 01:55:37 +02:00
d00f 36de9791be fileadder: track the data length instead of measuring it with strlen
replaceCalls() ended with strlen(&buffer[pos]), so the length recorded for a
file was the distance to its first 0x00 byte rather than the file size. The
byte just past the content happens to be zero today, which is the only reason
the numbers come out right.

Nothing in html/ contains a 0x00 today, so this is latent, but it is a trap
waiting for the first gzipped or binary asset: gzip puts a zero in the fourth
byte of its header. A gzipped style.css lands in the index as 3 bytes, and
since addr advances by that same value the next file is placed 3 bytes in and
overwrites what came before it. Two gzipped files in html/ are enough to see
it: both come out as 3 bytes and the second sits 1356 bytes inside the first.

Pass the length in and return the adjusted one. The substitution shortens
#{function} to #{NNN}, so the delta is known without measuring. The loops are
bounded by the length as well, which also covers -n, where nothing had put a
terminator in the buffer for strlen to find in the first place.

No output change: the image built from an unmodified html/ is byte for byte
the same before and after, and html_data.h is identical.
2026-09-08 00:52:24 +02:00
d00f eab17f8a3f dhcp: renew at half the lease when the server omits T1
Option 58 is optional, and dhcp_state.renewal is only ever written by
parse_opts(), so a server that leaves it out keeps it at zero. The ACK
handler then sets dhcp_timer to zero, and the callback treats a zero timer
in LEASING as time to renew, so it sends a REQUEST on the next pass.

That REQUEST does set a 30 second timeout, but the ACK that answers it
comes back through the same path and zeroes the timer again, so the two
sides trade REQUEST and ACK as fast as the round trip allows, with the
console printing the whole lease each time round.

RFC 2131 has the client renew at half the lease when the server does not
say, so use that, and fall back to a fixed value if there is no lease
either. Both only fill in a value that is missing: a server that asks for a
short renewal still gets exactly what it asked for, which a blanket lower
bound would have overridden.

Not reproduced here, that needs a server configured without the option.
2026-09-07 23:10:29 +02:00
d00f 552bd1cb18 syslog: stop the log ring from lapping its reader
write_char() masked writeptr and never looked at readptr, so once more
than LOGBUF_SIZE bytes were written between two drains the ring wrapped
over content that had not been sent yet.

That is not a corner case. The ring is emptied once per main loop pass,
while a single command runs to completion inside one pass, and a full L2
listing prints well over twice the 512 byte buffer. On hardware the whole
1227 byte dump came out as one 174 byte datagram whose first line began in
the middle of a MAC address.

Drop the character when the ring is full instead of overwriting, so what
has been logged stays intact and it is the tail that is lost. A full ring
also flags a line, otherwise a burst without a newline would leave the
reader waiting on line_available forever and syslog would stop for good.
2026-09-07 22:51:57 +02:00
d00f 4bcbada764 dhcp: do not leave the old socket behind when starting again
dhcp_start() never looked at whether DHCP was already running, and "ip dhcp"
calls it without stopping first, so a second start took a fresh slot from
uip_udp_conns and left the previous one bound.

The leak is the smaller half of it. dhcp_callback() decides whether a call
is for it by comparing the local port, not the connection, and every
leftover socket still holds port 68, so each one runs the state machine on
every poll. The timers live in one shared dhcp_state, so ticks count down
once per socket: two starts and the retransmit and renewal timing runs at
twice the intended rate.

Stop the old one first. dhcp_stop() already does exactly that, and the
state test keeps it away from a connection that was never created.
2026-09-07 22:44:51 +02:00
d00f 9fae0f9249 httpd: do not initialise tstate through uip_conn
uip_init() does not set uip_conn, and httpd_init() runs right after it, so
the assignment went through a pointer nothing had assigned yet.

It has stayed invisible because the startup code does clear xdata: uip_conn
is NULL rather than garbage, appstate sits 28 bytes into struct uip_conn, and
the byte therefore lands at xdata 0x1C. With the current map that is inside
the command line buffer, at an offset nothing reads while the line is empty,
and cmd_editor_init() runs later anyway.

Nothing is broken today, but it is a write to an address that has nothing to
do with the state being set, and it moves the moment the struct grows or the
map shifts. Address the connection directly.
2026-09-07 22:23:12 +02:00
eraiza0816 774e04816e Fix login loop and system input wipe 2026-09-08 00:31:14 +09:00
eraiza0816 ead1c40aac Merge upstream/main into feat/webui-compression 2026-09-07 15:36:46 +09:00
René van Dorst 6488433584 Merge pull request #401 from DrDoof/fix/ports-sfp-row
ports: read the right link state in the port table
2026-09-06 20:18:24 +00:00
René van Dorst 59088b4787 Merge pull request #407 from DrDoof/fix/arp-aging
Age the ARP cache by calling uip_arp_timer()
2026-09-06 20:10:54 +00:00
d00f 30547cc453 ports: drop the SFP row, keep the link-state index fix
Leaving SFP ports out of the table is deliberate for now, and being able to
force a module's speed belongs in a feature of its own rather than arriving
inside a fix, so that half goes again.

What remains is the off-by-one: createPortTable() read pState[i] where the
array is filled by portNum - 1, so every row started out showing the next
port's link speed until the first refresh corrected it. The debug line above
it goes too; it printed a third index, pState[i-2].
2026-09-06 21:13:23 +02:00
René van Dorst 58aead38cf Merge pull request #399 from DrDoof/fix/lag-cli-and-gui
lag: fix the web page's group numbering, and the command's syntax line
2026-09-06 18:56:12 +00:00
René van Dorst 9c3433cf6b Merge pull request #406 from DrDoof/fix/dhcp-xid-check
dhcp: fix transaction-id check so it actually compares
2026-09-06 18:37:42 +00:00
d00f 97d44fbf4b dhcp: drop the 16-bit swap on the 32-bit transaction id
The id was built and checked with HTONS(), which casts to 16 bits, so the
top half of the 32-bit transaction id was thrown away and the rest byte
swapped. The id is opaque: the client only has to receive its own value
back, so there is no byte order to honour and no reason to narrow it.

Assign and compare the field directly, so the full 32-bit id round-trips.
2026-09-06 20:30:40 +02:00
René van Dorst 00ee3d1325 Merge pull request #408 from DrDoof/fix/isr-no-console-write
Do not call write_char() from the external interrupt handlers
2026-09-06 18:11:30 +00:00
d00f 078a446cee lag: address review, single arg-count check and "d" only
Fold the "group with no ports" case into the argument-count guard
(cmd_words_len < 3) instead of a separate check, and accept only "d" for
delete, dropping the longer "delete" spelling. Update the serial-console
docs to match.
2026-09-06 18:41:27 +02:00
d00f efdf93a97a Do not call write_char() from the external interrupt handlers
The EX0..EX3 handlers each emitted a single debug character through
write_char(), which busy-waits on the serial TX-empty flag that is set by
the serial interrupt. If one of these handlers runs while a byte is being
transmitted, it waits for a flag that the equal-or-lower-priority serial
interrupt cannot deliver, hanging the handler with the system tick stopped.

The characters were only ever debug markers, so drop them; the handlers now
just acknowledge the interrupt. No interrupt service routine should call the
blocking console path.
2026-09-06 18:07:16 +02:00
d00f 025e1a186b Call uip_arp_timer() so the ARP cache ages
uip_arp_timer() was defined but never called, so ARP entries were only
refreshed on new learning and only removed under table pressure. A cached
entry for a peer whose MAC later changes (a gateway failover, a replaced
device) is then used until it is evicted, and traffic to anything reached
through it is sent to the old MAC and lost, while on-link traffic is
unaffected.

uIP expects the timer roughly every ten seconds; drive it from the
once-per-second housekeeping in idle().
2026-09-06 18:05:29 +02:00
d00f dc5cbcda16 dhcp: fix transaction-id check so it actually compares
The guard read `if (!DHCP_P->tid == HTONS(...))`. Unary `!` binds tighter
than `==`, so this evaluated `(!tid) == HTONS(id)`, i.e. `(tid ? 0 : 1) ==
HTONS(id)`, which is false for any non-zero id and never returns. The reply
was therefore accepted regardless of its transaction id, so a broadcast
reply meant for another client on the segment was parsed as ours.

Compare the field directly. The id is stored network-order in the packet
and host-order in the state, matching the HTONS() used when the request is
built, so `DHCP_P->tid != HTONS(dhcp_state.transaction_id)` is the intended
test.
2026-09-06 18:03:18 +02:00
René van Dorst 12c15603ed Merge pull request #395 from vDorst/fix_l2_cli_static_entry
Fix l2 cli static entry and use wait_table_ready() in more places
2026-09-06 14:17:41 +00:00
René van Dorst 3a7da77a99 Move wait_table_ready() up and replace all the same code for this function. 2026-09-06 10:06:02 +02:00
René van Dorst 13509d56e5 Fix l2 table, same as #355 but for CLI. 2026-09-06 10:06:02 +02:00
logicog 54b5e90976 Merge pull request #400 from DrDoof/feat/l2-show-lag
l2: name the group a learned address sits behind
2026-09-06 09:09:41 +02:00
d00f 38b8e38c04 ports: show the SFP port in the table, and read the right link state
The port table skipped every SFP port outright, so on a SWTGW218AS the
uplink simply was not there. Of the seven columns only Set Speed is
meaningless for a module: the name, the link speed and the devices behind
the port all are worth showing, and the name in particular was accepted on
another page and then never displayed anywhere. The MTU table further down
the same page has always drawn the port, so the page disagreed with itself.

Give an SFP port its row and leave the three control cells empty. Whether
speed and disable should work for a module is a separate question and
untested, so this does not offer a button for it.

The link state was read one port too far. main.js fills pState by
portNum - 1, updatePortTable() reads pState[i-1], and createPortTable()
read pState[i], so every row started out showing the next port's link
speed until the first refresh corrected it. On the last copper port that
meant showing the uplink's speed. Feeding the real status.json of a nine
port switch through both versions:

  expected  7  7  7  1  7  1  7  7  6
  before    7  7  1  7  1  7  7  6
  after     7  7  7  1  7  1  7  7  6

Dropping the skip also removes a trap rather than papering over it.
updatePortTable() addresses rows as tbl.rows[i], which only lined up with
port i because the SFP port happens to be last on this board. With a
module in the middle every row after it would have been off by one.
2026-09-06 00:09:22 +02:00
eraiza0816 d245249385 Merge upstream/main into feat/webui-compression
Resolve conflicts against ~55 upstream commits since 37eefb4:
- Makefile: keep upstream parallel-build fixes (machine_init.c, SUBDIRSCLEAN, html_data grouped target) and retain minify+gzip pipeline (-z, html_min, .css/.ico)
- tools: retain gzip support (zlib, -lz) lost in upstream revert
- httpd/httpd.c: keep Content-Encoding: gzip alongside upstream GET walk, config upload, syslog/hostname changes
- html: port upstream HTML/JS changes into SPA (single-page) bundle:
  * i18n: add port_col_devices / port_devices keys (en/ja/zh)
  * ports: add Connected devices column, devRender/devWalk via walkL2 (8c3df64)
  * config: add syslog port, extended stp/igmp/bw patterns, conf_toggle handling
  * system: show /cmd output in console (console_out), 401 handling
  * l2: hide delete for CPU entry, delL2Button, sort/filter improvements
  * navigation: add STP entry
- SPA: integrate new STP page (stp.html/js) as page-stp section with sectionInits.stp, bridge/prio handling, per-port config/status tables
- Remove per-page HTML/JS files (now bundled) and standalone stp.html/js

Verified: tools build, html_min generation (main.js 93k->81k->19k gzip, 8 files), html_data.h gzip sizes, main.js syntax check.
2026-09-06 04:45:46 +09:00
d00f 8b7306f995 l2: name the group a learned address sits behind
A device on an aggregate is learned against whichever member the frame came in
on, and the hash moves that around: the same address changes port every time
the flow mix changes. The table was telling the truth and there was no way to
read it, since nothing on the page or the console says those two ports are one
link.

port_lag_of() already exists and already knows the answer, so both views ask
it. The console prints LAG1 in place of the member, and /l2.json carries a
"lag" field beside the port, 0 when the port stands alone, which is the shape
/stp.json already uses.

Costs 151 bytes of BANK1.
2026-09-05 17:29:13 +02:00
d00f ecac6d1934 lag: say what the command takes, and delete only when asked
Two small things about the same command.

The usage line offered "lag <1-4> [port]...", which leaves out "lag show" even
though that is the only way to see the groups, so the one subcommand a reader
most needs is the one the error does not mention.

And a group number with no ports after it quietly wiped the group. That is a
reasonable thing to want and an unreasonable thing to do by accident, as it is
one fat-fingered return away from a bare "lag 1". It now takes "lag 1 delete"
or "lag 1 d", and a bare group number prints the usage instead.

Nothing changes for a saved configuration: "lag 1 5 6" is what gets written and
what gets replayed, and neither the old bare form nor the new delete matches
the pattern that decides what is persisted.
2026-09-05 13:28:15 +02:00
d00f f2966ad248 lag gui: create the group the page says it is creating
The LAG page draws four sections headed LAG 1 to LAG 4 and wires their buttons
to lagSub(0) through lagSub(3), which then sends the index straight through as
the group number. So the first button asks for "lag 0", which the parser
rejects, and the page shows nothing at all because the error only goes to the
console. Every other button is off by one: the section headed LAG 2 creates
group 1.

The index stays 0-based for the element ids and only the command gets the
1-based number the CLI documents.
2026-09-05 13:28:14 +02:00
René van Dorst 8a7b146892 Merge pull request #396 from feelfree69/fix/syslog_port
Fix printing of syslog portNumber in WebUI
2026-09-04 11:35:33 +02:00
feelfree69 0998a9dfaa Fix printing of syslog portNumber in WebUI 2026-09-04 11:19:46 +02:00
René van Dorst aded07730e Merge pull request #365 from janh/zx-swtgw215as
Add PCB V2.1 to ZX-SWTGW215AS documentation and use MAC address on flash
2026-09-04 09:07:31 +02:00
René van Dorst 58d059384d Merge branch 'main' into zx-swtgw215as 2026-09-04 09:07:08 +02:00
d00f a7692022f1 httpd: reap idle TCP connections (#390)
* uip: time out an ESTABLISHED connection whose peer went silent

uIP has no keepalive and no idle timeout for an ESTABLISHED connection, so
a peer that vanishes without FIN or RST holds it open forever. A crashed
client does it, and so does a SYN caught while the switch is still
booting. With UIP_CONF_MAX_CONNECTIONS = 1 that wedges the single slot:
the web UI stops answering, port 80 no longer even SYN-ACKs, and only a
power cycle recovers it. Every other TCP state already has an exit path
(UIP_MAXRTX, UIP_TIME_WAIT_TIMEOUT); idle ESTABLISHED was the one hole.

Close it inside uIP rather than in httpd, so any future TCP service is
covered too. The retransmission timer is unused while nothing is in
flight, so the idle count lives in it: a 1 Hz strobe derived from the
uip_periodic() rate ages it, any arriving segment clears it, and past the
limit the connection is aborted through the same path as UIP_MAXRTX. Two
new options in uipopt.h, default off; this project enables 30 s.

httpd also joins uip_timedout() to its cleanup: it only handled closed
and aborted, so both this timeout and the existing UIP_MAXRTX one would
have left the transfer state stuck on a dead connection.

* uip: keep the tuning values out of the prose

The comment named the timeout and the tick rate, which go stale the
moment either constant is edited. It now says why an idle connection
is aged out and leaves the numbers to the defines under it.
2026-09-04 07:11:49 +02:00
René van Dorst e26693826e Merge pull request #393 from bloqaudio/fix/l2-delete-own-mac
httpd: refuse to delete the switch's own L2 entry
2026-09-03 21:09:16 +00:00
René van Dorst be6bd2a5b3 Merge pull request #380 from vDorst/fix_vlan_docs
Fix VLAN command documenation. 'u'-suffix is not accepted anymore.
2026-09-03 21:03:23 +00:00
René van Dorst 6c43091b7a Merge pull request #329 from DrDoof/feat/cmd-output
httpd: answer /cmd with what the command printed, and show it in the console tab
2026-09-03 20:33:26 +00:00
d00f b57b7d1a3d httpd: answer /cmd with the shared text header
The /cmd path carried its own copy of the plain-text response header,
which was the same string page_impl.c already keeps in
HTTP_RESPONCE_TXT, minus one difference: the shared one never said
Connection: close.

This httpd closes after every response, so a reply that does not
advertise it invites the browser to reuse the socket and lose the next
request. HTTP_RESPONCE_JSON has always said so; the text one was the
odd one out, and send_config() and send_cmd_log() were answering
without it.

The copy is gone and the shared string carries the header. The check
for a command that printed nothing now compares against the length
strtox() returned instead of sizeof() on the macro.
2026-09-03 22:18:45 +02:00
René van Dorst fc86ea8be5 Merge pull request #333 from DrDoof/feat/syslog-hostname-port
syslog: send a hostname, allow a port other than 514, and stop the save from dropping the address
2026-09-03 20:02:41 +00:00
René van Dorst 91780110f2 Merge pull request #389 from DrDoof/fix/login-page-i18n
httpd: let the login page fetch its translations
2026-09-03 20:00:17 +00:00
bloqaudio f0e9953081 web: hide the L2 delete button for the CPU entry and drop a deleted row
The firmware now refuses to delete the switch's own entry, so the
button on the CPU row can only fail; leave it out. When a delete
succeeds, remove the entry from the cached table and repaint instead
of leaving the stale row until the next refresh.
2026-09-03 14:33:07 -05:00
bloqaudio 1c9bf10b87 httpd: read the table registers straight from the SFRs in l2_delete()
Test the busy flag and compare the entry's MAC against the SFR data
registers directly instead of going through the sfr_data copy, and
rewrite TBL_DATA_0 in place rather than reassembling all four bytes.
Smaller and faster on the deepest httpd path.

Suggested-by: vDorst
2026-09-03 14:33:07 -05:00
d00f 2e2529be27 syslog: print the server address with print_ip()
The status line built the dotted quad by hand, which is what
print_ip() from cmd_parser.c already does. Reusing it drops seven
calls to one and takes 48 bytes off BANK2.

The prototype is not __banked because cmd_parser.c and syslog.c are
both in BANK2; a plain call is what the compiler needs here. Should
either file move to another bank, this has to become __banked with it.
2026-09-03 21:27:02 +02:00
René van Dorst f6348765b4 Merge pull request #343 from bloqaudio/feat/mgmt-static-l2
port: pin the management MAC to the CPU port with a static L2 entry
2026-09-03 18:25:11 +00:00
Pietro AmeruosoandPietro Ameruoso 6411c5157c Add Hi-K0402WS V2 hardware support (#391)
Signed-off-by: Pietro Ameruoso <p.ameruso@live.it>
Co-authored-by: Pietro Ameruoso <p.ameruso@live.it>
2026-09-03 19:47:02 +02:00
eeeple d06dc3e5f8 Added Siscolink SL-G0402F support (#392)
* Added Siscolink SL-G0402F picture

* Update PCB-K0402WS-V3.0.md

* Update supported_devices.md

* Rotated picture
2026-09-03 19:44:17 +02:00
bloqaudio dd7f9f7c7b httpd: refuse to delete the switch's own L2 entry
The L2 page's Delete button removes any table entry by index, including
the entry for the switch's own MAC on the CPU port. Without that entry
unicast to the management address is no longer delivered to the CPU, so
management goes dark until the CPU happens to transmit again and the
entry is relearned. Compare the entry's MAC against uip_ethaddr before
rewriting it and answer result 0 for the switch's own address, as for a
missing entry. The compare is inline: this handler sits at the bottom of
the uIP call chain, where every extra call level costs internal RAM.
2026-09-03 10:33:53 -05:00
bloqaudio 3b8dba6e87 port: pin the management MAC to the CPU port with a static L2 entry
The CPU's own MAC exists in the L2 table only as a learned entry,
created whenever the CPU transmits, and it ages out like any other
dynamic entry. Unknown unicast is not flooded to the CPU port, so on an
idle switch (no web session, no management traffic) the entry expires
after the aging time and frames for the management IP no longer reach
the CPU: pings die until the next CPU transmission (an ARP reply)
re-teaches the entry, then die again. Real management sessions keep the
entry alive continuously, which is why this is only visible on idle
switches.

Install a static entry for the management MAC on the CPU port after the
startup config has been replayed, so it lands in the final management
VLAN, and move it when the management VLAN is changed at runtime.

Entry mechanics, established on hardware: a table write with read-method
0 hashes the key in TBL_DATA_IN and places the entry itself (a search
returns index 0 on a miss, there is no software-visible insertion slot).
The age field in the third data word must be non-zero for the entry to
be visible to lookup at all, and the static flag (bit 0 of byte 1 of
that word) stops the aging engine from decrementing it: with the flag a
full-age entry survives untouched where an identical unflagged entry
ages to invisible within the aging period. With no management VLAN
configured the entry lives in VLAN 1.
2026-09-02 21:35:51 -05:00
d00f a259587bf9 system: do not let a failed console request look like an empty answer
fetch() does not throw on 4xx, so a request that came back 401 after the
session had timed out reached the same line as a successful one: the
window showed the prompt and nothing under it, and the input was cleared
as if the command had run. The session lasts 200 seconds, so leaving the
tab open is enough to hit it.

A 401 now goes to the login page instead. The transcript also gets a
trailing newline when the answer lacks one, so the next prompt does not
run into the last line of output, and it is capped so a long session
does not keep growing the node.
2026-09-03 01:43:30 +02:00
d00f f23d840cee httpd: make the /cmd answer usable when a command fails or floods
Three ways the answer could mislead the caller, all in the paths that
matter most when something is wrong.

The capture sat in write_char(), so anything printed through
print_string_no_syslog() escaped it. That is exactly the replies of the
syslog commands, which deliberately avoid the syslog path: "syslog on"
answered with the bare address and no sentence around it, "syslog off"
answered with nothing at all. Moving the capture one level down, into
write_char_no_syslog(), catches both paths and costs nothing, since
write_char() reaches it anyway.

On a parse error send_bad_request() rewrote the buffer from the start,
throwing away the very text that explains the error and replacing it
with the 400 HTML page. Keeping the captured text and restating only the
status keeps the parser's own message; "400 NO" is as long as "200 OK",
so the header does not have to be rebuilt.

An overlong listing was cut at the buffer end with nothing to show for
it, so a clipped answer read as a short one. It now ends in a marker,
and the reserved room is accounted for in the guard.

While here: a command that configures something prints nothing, and an
empty body reads the same as "nothing ran", so a successful silent
command now answers "OK".

No internal RAM and no xdata; 174 bytes of BANK1, which has 7356 left.
2026-09-03 01:43:28 +02:00
d00f f46ef3a43c system: show what the console command printed
The console tab posts to /cmd and throws the response away, so the page stayed
silent whether the command worked, was refused or was just misspelt. Now that
/cmd answers with the command's output, the page keeps a running transcript:
each command echoed after a prompt, then whatever it printed.

The box is a pre element filled through textContent, so nothing the device
prints is taken for markup. It scrolls to the newest line and the input clears
after a send.
2026-09-03 01:43:09 +02:00
d00f 81af3a29a2 httpd: return what a command printed over /cmd
POST /cmd runs the command and answers with an empty 200, so a caller cannot
tell "done" from "nothing happened", and whatever the command prints only
reaches the serial console. Reading a register over the network needs a second
channel just to see the answer.

write_char() is the one funnel every console helper goes through, and it already
tees into the syslog buffer. A third sink, live only while the httpd runs a
command that arrived over the network, puts the same bytes into the response
behind the header. Serial and syslog keep getting their copy.

The capture stops at the end of the response buffer, so a long listing is
truncated rather than overrunning it.

BANK1 grows 39 bytes and the common area 52. One byte of xdata, BANK2 unchanged.
2026-09-03 01:43:07 +02:00
d00f 5a55524115 httpd: let the login page fetch its translations
login.html asks for i18n.js, but the file is not on the short list a
client may have before it authenticates, so the request is answered with
a redirect back to the login page and the strings stay as the English
that is baked into the markup.

The file carries only the interface text, the same text the page already
ships untranslated, so serving it before login gives nothing away.
2026-09-03 01:12:56 +02:00
René van Dorst 6b39bcc448 JS: vlan don't accept u-suffic anymore.
Als update CHANGELOG about CPU_PORT
2026-09-02 19:44:41 +02:00
René van Dorst 4c8e0ce975 Merge pull request #375 from bloqaudio/fix/post-body-split
httpd: buffer a plain POST body that arrives after the headers
2026-09-02 08:59:56 +02:00
René van Dorst 71344825c7 Merge pull request #382 from DrDoof/fix/sfp-empty-slot-json
sfp: stop the web pages printing the previous slot's bytes
2026-09-02 08:25:42 +02:00
d00f 40ce742d04 syslog: take the review notes on the state flag and the header length
The enabled flag is a flag, so say bool. The header length no longer needs
to live in xdata: it was put there when a local would not link, and there
is room for it here.

The overview names the row after the JSON key, so the syslog row said
"syslog_server_ip" while carrying only an address. It now says
"syslog_server" and carries the port with it.
2026-09-01 23:08:42 +02:00
bloqaudio f5e4fa5134 httpd: buffer a plain POST body that arrives after the headers
A POST /cmd whose body arrives in a separate TCP segment from the headers
executed nothing: handle_post() read the body from the segment that
carried the request line, found it empty, and answered 200 OK. Python's
urllib and requests both write headers and body separately, so every
scripted command from those clients became a silent no-op. POST /login had
the same hole. The multipart endpoints were fixed earlier; this covers the
plain ones.

When the first segment holds fewer body bytes than Content-Length
announces, the bytes accumulate in config_buf (idle outside multipart
uploads) and the connection waits in TSTATE_POSTBODY; once the announced
length is in, the endpoint runs against the buffer. The /cmd and /login
executions move into run_cmd_body() and run_login_body() so both paths
share them; the 200 OK builder they duplicated becomes send_ok(), and
run_login_body() checks the pwd= prefix instead of skipping four bytes
blindly.

The announced length is the contract on both paths. A body that arrives
with the headers is terminated at Content-Length before it runs, so
pipelined bytes after it are not executed as commands the way the old
code did. A request without a usable Content-Length answers 411: without
one there is no way to know when the body has arrived, and the old 200
OK for an empty body is the bug this fixes. Bodies announced at or above
the buffer size answer 400 before anything is buffered.

The wait state has a deadline. uIP is built with a single connection,
and an ESTABLISHED connection with nothing in flight never times out on
its own, so headers followed by silence (a script interrupted, a link
dropped mid-request, or someone holding the socket on purpose) would
otherwise keep the slot until reboot; POST /login reaches the wait
before authentication. The poll handler aborts the connection when the
body has made no progress for five seconds, measured on the 200 Hz
system tick rather than on poll count, which runs faster under interrupt
load.
2026-09-01 14:55:56 -05:00
bloqaudio 28020e8186 httpd: parse Content-Length in scan_header()
Record the announced body length alongside Content-Type and the session
cookie. The value goes through parse_short(), so an announcement that does
not fit sixteen bits saturates at 0xffff rather than wrapping to something
the size checks would accept, and a missing or unparseable header reads as 0.
Nothing consumes it yet; the plain POST endpoints take it up next.
2026-09-01 14:52:09 -05:00
bloqaudio cbcf67a693 httpd: keep scan_header() on the terminator of a truncated header
The scan stepped over the NUL that ends the received data before breaking,
so on a request whose headers were cut short it returned a pointer one past
the data. handle_post() then tested that byte for the end of the header,
reading whatever an earlier packet had left there instead of the NUL the
appcall wrote, and could go on to run an endpoint against the stale bytes.
Break before advancing so the returned pointer sits on the NUL and the
caller's check sees it.
2026-09-01 14:52:09 -05:00
bloqaudio d6bf46595a httpd: saturate parse_short() instead of wrapping
The digit loop accumulated into a uint16_t without a bound, so a query
such as /vlan.json?vid=65540 read as VLAN 4 and every consumer saw a small,
valid-looking number for an out-of-range one. Clamp the result at 0xffff
once another digit would overflow (6552 * 10 + 9 is the last value that
fits). The consumers already reject or mask 0xffff: vlan_get() refuses
anything from 4095 up, send_l2() masks the index to the table size, and
l2_delete() masks the high byte.
2026-09-01 14:52:09 -05:00
bloqaudio 2499d116a3 httpd: match header field names case-insensitively
scan_header() found Content-Type and Cookie with is_word(), which compares
bytes exactly and requires a separator after the pattern. Field names are
case-insensitive (RFC 7230 section 3.2), and the whitespace after the colon
is optional, so "content-type: multipart/..." and "Content-Type:multipart/..."
were both treated as absent. The value pointers were then fixed offsets that
assumed exactly one space.

Add header_value(), which matches a lower-case name anchored at the line
start, folds the request bytes to lower case as it compares, and returns
the start of the value past any blanks, so a call site no longer adds the
name length by hand. Cookie scanning reuses the returned pointer, and the
end-of-header test becomes the strstart() the file already has.
2026-09-01 14:52:09 -05:00
René van Dorst 74a1c6831a Merge pull request #335 from bloqaudio/feat/ports-connected-devices
ports: show the devices connected behind each port
2026-09-01 18:02:22 +00:00
René van Dorst a79c483f94 Merge pull request #385 from 7Ji/fg_8gt_1sx
Add FG-8GT-1SX
2026-09-01 17:35:54 +00:00
René van Dorst 75aa23468c Merge pull request #374 from bloqaudio/upload-cleanup
httpd: clean up the upload path in handle_post()
2026-09-01 17:30:56 +00:00
René van Dorst 373a21f4a1 Add CHANGELOG.md and vlan command breaking change. 2026-09-01 19:05:21 +02:00
logicog 89b4cf83ce Merge pull request #314 from DrDoof/feat/stp-v2
stp: Spanning Tree support for the RTL837x switches
2026-09-01 18:52:20 +02:00
bloqaudio 005209f97a httpd: split the upload fragment handlers out of handle_post()
The tail of handle_post() interleaved the config and firmware upload
paths behind one config_upload conditional. Give each path its own
function, handle_config_fragment() and handle_firmware_fragment(), and
reduce handle_post() to routing.

Both handlers keep their own copy of the buffer-and-bounds-check
prologue: with SDCC a shared helper costs 2 bytes of direct RAM for
the fragment pointer (a spill slot when the parameter lives in xdata,
a DSEG home when it does not), and direct RAM is fully allocated. The
memory layout is unchanged from the pre-split code.
2026-09-01 09:59:52 -05:00
bloqaudio f0f142fbe0 httpd: track buffered upload bytes in one accumulator 2026-09-01 09:59:52 -05:00
bloqaudio daf9d8965a httpd: scope the multipart parse cursors to their functions
cfg_pos, cfg_hdr, cfg_body, cfg_end, cfg_last and cfg_bl are used only
by config_take(), so move them out of file scope and into the function.
The five cursors stay static: SDCC register-caches automatic xdata
locals and spills them through DSEG slots, which costs 6 bytes of direct
RAM this build has no room for; static keeps the access pattern of the
old globals and the memory layout is unchanged. cfg_bl produces no spill
slot at the current register pressure, so it is a plain local. The
mechanism is recorded in issue #386.

handle_post() reused cfg_pos and cfg_end as scratch for unrelated
values; those uses get their own named locals, frag_len and
payload_start.
2026-09-01 09:59:52 -05:00
René van Dorst 58b12c4aa6 Merge pull request #384 from DrDoof/fix/cmd-history-ring
cmd: keep the history pointer inside the ring
2026-09-01 09:22:12 +02:00
d00f 4ce3e33447 cmd: keep the history pointer inside the ring
The pointer is masked when it advances over the command text, then
incremented once more for the newline without masking, so it can come to
rest one past the end of the ring.

Both readers walk from the pointer with their own index masked every
step, so an index that sits outside the ring is never reached and the
walk does not end. `history` on the console spins there, and so does
`/cmd_log`, which the web page fetches whenever settings are saved: it
fills the transmit buffer and keeps going past it.
2026-09-01 09:02:28 +02:00
Guoxin Pu ebd27e4763 Add FG-8GT-1SX
This PCB is used in RY-8GT-1SX. The UART has swapped Rx and 3.3V VCC as
documented in `doc/devices/FG-8GT-1SX.md` so I imagine there would be
a revision later, after the current '2622' batch, like the RY-4GT-2SX /
FG-4GT-2SX_V2.0 pair from the same vendor.
2026-09-01 15:01:04 +08:00
René van Dorst e60a09fb7e Merge pull request #383 from DrDoof/fix/isolate-off-top-port
cmd: let "isolate off" put the top port back in the member list
2026-09-01 08:23:05 +02:00
d00f 828963b586 cmd: let "isolate off" put the top port back in the member list
The member mask is filled with a loop that stops one short, so the
highest front panel port never gets its bit and stays isolated from the
port that was just opened up. Every other loop over the port range in
the tree treats machine.max_port as the last port rather than the one
past it.
2026-09-01 04:11:30 +02:00
d00f 944f4ce6f0 sfp: stop the web pages printing the previous slot's bytes
An empty slot used to read back 0xa0 per byte and the filter dropped it,
so the field came out empty. Block reads report the failure instead and
leave the buffer alone, and these two callers ignored that and printed
what the last successful read had left there.

Say nothing when there is nothing to read, which is what the other eight
callers of sfp_read_block() already do.
2026-08-31 23:26:37 +02:00
René van Dorst 73d9a8ccb1 Fix VLAN command documentation. 'u'-suffix is not accepted anymore. 2026-08-31 23:04:57 +02:00
d00f 3a3967960f stp: take the review notes on the port and priority arguments
The port argument went through its own digit test and machine table
lookup; cmd_parse_port_separator() does both and also checks the port
exists on this board, which the open-coded test did not.

A port priority was masked to its top nibble, so "prio 17" quietly became
16. The bridge priority next to it refuses what it cannot represent, and
now the port priority does too.
2026-08-31 22:55:41 +02:00
d00f e38abf27c9 Merge upstream/main into the Spanning Tree branch 2026-08-31 22:48:14 +02:00
d00f 05ded88f85 stp: keep the FDB update's counters to the function that uses them
Both only ever served stp_fdb_update(), so they belong there. Internal RAM
has room for them on this branch and on the one that carries the
aggregation module too.
2026-08-31 22:18:28 +02:00
logicog dc22c4908b Merge pull request #376 from bloqaudio/fix/make-clean-tools
Makefile: have clean and distclean recurse into the tool directory
2026-08-31 21:58:58 +02:00
d00f c0d9bf7f0d stp: take the review notes on types and register reads
stp_enabled is a flag, so say bool. cmpBytes() returns a comparison
result, so say int8_t. The three busy waits this branch adds read the
status straight out of the SFR instead of copying four bytes to xdata
first.
2026-08-31 21:50:19 +02:00
René van Dorst 1947356ec5 Merge pull request #368 from vDorst/fix_all_compiler_warnings
Disable all `__sfr32` SDCC bug 4070 and fix all compiler warnings.
2026-08-31 19:47:06 +00:00
René van Dorst 2fa4686b33 fix review points. 2026-08-31 21:00:37 +02:00
d00f bd06f7f5e5 stp: do not let auto edge undo a loop block
A port held out of forwarding because a loop was seen on it stops hearing
the frames that justified the hold, so its BPDU age climbs. The auto edge
branch lives inside the same countdown as the hold, reads that age, and
promotes the port back to forwarding after three seconds. The loop opens,
a BPDU comes back, the block is applied again, and the two take turns.

Measured on a looped pair present when the tree came up: twenty cycles in
one window and fifteen in the next, each block followed by a promotion,
still going when the capture ended.

The countdown alone cannot tell a loop hold from an ordinary listen, so
record the hold and leave the port alone while it stands.
2026-08-31 14:26:05 +02:00
René van Dorst b6d2d7c072 Merge pull request #351 from DrDoof/feat/lag-interface
port: let a pvid name an aggregation group
2026-08-31 13:37:14 +02:00
René van Dorst 3c72f0e1b6 Merge pull request #372 from vDorst/fix_macros
Fix define macros, was missing the do {} while (0) wrapping
2026-08-31 13:07:29 +02:00
bloqaudio 6be5693385 Makefile: have clean and distclean recurse into the tool directory
make clean left tools/output in place, so the host tools were never
rebuilt after a clean. SUBDIRSCLEAN, which builds the per-directory
clean target names for exactly that purpose, was defined but nothing
consumed it. Give clean and distclean those targets as prerequisites
and add the rule that runs the subdirectory's own clean, which
tools/Makefile already provides.
2026-08-30 21:49:56 -05:00
d00f 8e9bb56a29 nic: send a frame with the layout it actually has
tcpip_output() decides whether to splice in the 802.1Q tag, and that
decision also moves the frame: the header is shifted forward over its
padding, so a tagged frame starts at uip_buf with the q_frame layout,
while an untagged one keeps the padding and the nonq_frame layout.

nic_tx_packet() took that decision a second time, from management_vlan
alone. That agreed while the sender suppressed the management VLAN around
the transmission, but not since the tag is skipped per frame for a
CPU-tagged one: the descriptor then still sits behind the padding while
the transfer is set up for the shifted layout. The frame goes out four
bytes early and its length is read from the offset where tx_seq and
chksum_flags live, so a sixty byte BPDU is sent as around 1800 bytes of
whatever follows it. Looped back it is no longer a BPDU, the port hears
nothing, auto edge promotes it and the loop stays open.

Record the decision where it is taken and let the transfer follow it.

A variable rather than an argument because internal RAM is full once the
aggregation module shares the image: the overlay area ends at 0x7f, and
an argument or any temporary for the condition no longer fits.
2026-08-31 02:52:00 +02:00
d00f e50d3c6950 syslog: parse the port with atoi_short()
The hand rolled loop was here because atoi_short() reported "no digits"
but not overflow, so 65540 would have wrapped to 4 and opened a
connection to a port nobody asked for. #326 gave it the same bound this
loop was carrying, so the reason is gone.

Zero still covers all three ways to be wrong, except that "no digits"
and overflow now come back as a zero return and port zero as a zero
value, so both are tested.

Three xdata scratch variables go with the loop.
2026-08-30 22:38:31 +02:00
d00f 4db1ccbbd9 syslog: fold the port parser's split conditions back together
The loop tested the digit range and the overflow bound with separate ifs,
on the strength of a comment saying the joined form makes sdcc park a
boolean in the bit area, and that an image carrying LACP and STP as well
is one bit short of needing a third byte of it. Measured on this branch
that is not what happens: the joined form links, the bit segment is
unchanged at fourteen bits overall and nine in cmd_parser, and BANK2
comes out ten bytes smaller.

The claim was about an image this branch does not build, and no branch
currently carries LACP, STP and this parser at once, so there is nothing
left to check it against. A justification nobody can verify, for a shape
nobody would otherwise write, is worse than no comment at all.

Behaviour is unchanged. The two forms agree on every input from 0 to
99999 and on the edges: empty, non digits, trailing junk, leading zeros,
65535 and 65536.

The note about atoi_short() stays, because that one still reports "no
digits" without reporting overflow. #326 replaces it with a version that
does both, and this should move onto it once that lands.
2026-08-30 22:38:31 +02:00
d00f ccafa01c74 system: stop "syslog on" from wiping the server address
Saving the configuration drops the syslog server address whenever the log
of executed commands also contains "syslog on", which it almost always
does, since you have to enable the thing after pointing it somewhere.

parseConf() deduplicates by taking the matched text of a conf_overwrite
entry and removing every stored line that starts with it. For "syslog on"
the entry that matches is the catch-all ^syslog\b, so the matched text is
just "syslog" and the filter removes anything beginning with "syslog ",
including the "syslog ip ..." line saved a moment earlier. The address then
falls back to 0.0.0.0 on the next boot.

It fails quietly: nothing is reported, the running switch keeps logging
until it is restarted, and the loss looks like the firmware forgetting a
setting rather than the page dropping it.

Narrow the entry to the commands it is meant to collapse, so the address and
the port keep their own entries and are no longer collateral.

That alone would stop "syslog on" and "syslog off" from replacing each other,
because the list drops lines starting with the text it matched and "syslog off"
does not start with "syslog on". A separate list names the stem of such a pair
and collapses the two, which is what the catch-all used to do by accident.
2026-08-30 22:38:31 +02:00
d00f cb43b3cbd6 syslog: make the destination port configurable
The port was frozen at 514 in the call that opens the connection, so a
collector listening anywhere else was simply out of reach, and plenty do,
because 514 is privileged and containerised collectors usually get a high
port instead. Working around it meant putting a proxy in front, which then
rewrites the source address and hides which device actually sent the line.

Add `syslog port <n>`. The default stays 514, so nothing changes for an
existing configuration. Setting it tears the connection down and builds a
new one, the same dance the address already does, because uip_udp_new()
freezes the port into the connection.

The value is parsed here rather than through atoi_short(), which reports
"no digits" but not overflow: 65540 would quietly wrap to 4 and we would
open a connection to a port nobody asked for. Checking before each multiply
keeps it in 16-bit arithmetic. Zero then covers all three ways to be wrong -
no digits, past 65535, or an explicit "0", which is not a port either.

Whitelist the new command in config.js as well, next to `syslog ip`, or the
line is dropped without a word when the configuration is saved and the
setting quietly fails to survive a reboot. The dedup entry has to precede
the general `^syslog\b` one, otherwise it would deduplicate against
`syslog on`.

itoa_short() is new: itoa() only handles a byte and print_short() prints
hex, so there was no way to show a port as a decimal number. Kept separate
from itoa() rather than widening it, because every existing caller passes a
byte and would start paying for 16-bit divisions it does not need.

The scratch variables for the parser live in xdata. Locals there would take
internal RAM, and this image has none left: data ends at 0x77, the overlay
occupies 0x78-0x7f and the stack starts at 0x80. Measured: no module's DSEG
grew.
2026-08-30 22:38:31 +02:00
d00f 9d4e531306 syslog: put our hostname where the receiver expects it
RFC 3164 has a hostname between the priority and the message text, and we
were sending nothing there. A receiver still has to fill that field, so it
takes the first word of the text instead, so every line arrives attributed to
"STP:", or "REGGET:", or whatever the log happens to start with. The sender
is then impossible to select on: the name changes with the content, and
anything in front of us on the path replaces the source address with its
own.

Observed on a collector behind a UDP proxy: the same switch showed up as two
different hosts depending on which subsystem had logged last.

Send the configured hostname. Skipped when it is empty, so we never emit a
lone separator.

The header length lives in xdata rather than on the stack. syslog_callback()
is __banked, so a local there cannot be overlaid, and while the variable
itself is only two bytes it costs more than that: it takes a register, which
pushes log_start out of one and into internal RAM. Those two bytes are
enough to stop an image carrying LACP and STP from linking at all.
2026-08-30 22:38:30 +02:00
d00f 7f0e7fdc82 Merge upstream/main into the Spanning Tree branch
Both conflicts are the register keyword removal meeting a change of ours,
so each takes main's signature and keeps what the branch was saying:
nic_rx_packet() still returns a bool, and port_ingress_filter_get() stays
declared.

main has since put its own copy of the STP module in BANK2, so the pragma
this branch carried is now duplicated. The one with main's comment stays.
2026-08-30 21:58:54 +02:00
René van Dorst f187261085 Fix all compiler warnings.
- Many times we need help the compiler to reason about variable type or
define.
2026-08-30 21:41:30 +02:00
René van Dorst aba69cd640 DHCP: Added workarounds for SDCC bug 4070 2026-08-30 21:37:37 +02:00
René van Dorst b6dcb75130 Disable all __sfr32 due to bug SDCC 4070 and add workarounds.
Generate wrong address for `a4` location.
Both on read from and write to a __sfr32 variable.

Workaround:
Use two __sfr16 instead of one __sfr32.
We still get some optimalizations / better code gen.
2026-08-30 21:36:03 +02:00
René van Dorst 4e04735a5f Fix define macros, was missing the do {} while (0) wrapping 2026-08-30 19:35:37 +02:00
René van Dorst fc3c686564 Merge pull request #359 from bloqaudio/fix/upload-preamble-split
httpd: buffer a firmware upload's part header before streaming
2026-08-30 16:48:53 +00:00
bloqaudio e826180d06 httpd: pass stream_upload() state through an xdata struct 2026-08-30 04:09:30 -05:00
bloqaudio b63117ba6c httpd: scan the upload preamble with a register cursor
The preamble scan cursor and the buffered-length parameter carry no
state between calls, so they do not need static xdata slots. As plain
locals the compiler places both in registers, trimming 64 bytes of
BANK1 code and two bytes of xdata.

The offsets shared by config_take() stay static: direct data space is
fully allocated on machines like the SWTGW218AS, so plain locals there
add overlay bytes that no longer link, and xdata-class locals spill
three temporaries into direct space while growing the code by roughly
120 bytes. Document pre_acc, whose accumulation across TCP segments is
why it must remain global.
2026-08-30 04:09:30 -05:00
bloqaudio be7796a22c httpd: send a real verdict for firmware uploads
A firmware upload previously ended in a silent connection close, leaving
the client unable to distinguish a verified upload from a failed one.
Send an explicit 200/400 verdict with the CRC result, with
Content-Length so the browser completes the response before the reset,
and only reset the chip once the verdict has been fully ACKed.

The unconditional close after a config upload is gone since the
buffered config path answers with its own response, so drop the
now-unreachable close hack from the streaming path.
2026-08-30 04:09:30 -05:00
bloqaudio 1bd4a8d3cc httpd: buffer a firmware upload's part header before streaming
A firmware image cannot be buffered whole, so the upload is streamed to
flash, and stream_upload() already resumes across TCP segments. The
multipart preamble did not: handle_post() walked the part headers from the
start of whichever segment it held, so a client that split inside the
octet-stream part header lost its place, never started streaming, and the
request hung. Firefox splits exactly there, right after filename=".

Buffer the multipart body only until the octet-stream part header is
complete, locate the payload, then stream from that point; later segments
stream as before. The header reuses the configuration buffer, which is idle
during a firmware upload, so no extra memory is needed.
2026-08-30 04:09:30 -05:00
René van Dorst 1db30f22de Merge pull request #330 from vDorst/SRAM-easy-changes
Sram easy changes
2026-08-29 18:33:29 +00:00
logicog 4c70e654d1 Merge pull request #363 from vDorst/refactor_remove_keyword_register
Remove register keyword from all the function arguments.
2026-08-29 11:56:22 +02:00
feelfree69 3c24f2bdea Merge pull request #260 from donbernhardo/fix-kp-9000-6xhml-x2-led-mux
Add KP-9000 6XH and 6XHML PCB-revisions to Machine targets
2026-08-29 10:16:16 +02:00
donbernhardo 3e610392a9 Merge remote-tracking branch 'origin/main' into fix-kp-9000-6xhml-x2-led-mux
# Conflicts:
#	machine.c
2026-08-28 23:01:38 +02:00
Jan Hoffmann f00dbab0c4 Use MAC address from flash for ZX-SWTGW215AS 2026-08-27 16:53:29 +02:00
Jan Hoffmann bc46646398 Document PCB version 2.1 for ZX-SWTGW215AS
This PCB appears only slightly different to the already supported and
documented PCB-SWTG115AS-V2.0. It works without any changes to the
machine definition.

While at it, also fix a typo (8272 instead of 8372), and include the
existing unused pictures on the device page.
2026-08-27 16:53:29 +02:00
Jan Hoffmann 5fa6c36b73 Restructure headings in ZX-SWTGW215AS documentation
Use a proper hierarchical heading structure.
2026-08-27 16:53:29 +02:00
René van Dorst 146771f5ca Merge pull request #362 from bloqaudio/fix/v210-bank0
leds: move the SWTG018AS-V2.1.0 custom LED init to BANK2
2026-08-27 16:32:09 +02:00
René van Dorst bf0bea1edd Remove register keyword from all the function arguments.
Adds no value.
2026-08-26 21:52:31 +02:00
René van Dorst 907ad08830 uip: uip_arp_update() put more arguments in xdata
Saves 7-bytes.
2026-08-26 20:51:32 +02:00
René van Dorst 7cf0115182 httpd: scan_header() put argument on xdata
Saves 2 bytes
2026-08-26 20:51:32 +02:00
René van Dorst adfbead438 syslog: syslog_callback() put local variables on xdata.
Save atleast 2 bytes.
2026-08-26 20:51:32 +02:00
René van Dorst 301774040b uip: place some pointer in xdata.
Saves 2 bytes.
2026-08-26 20:51:32 +02:00
René van Dorst 683037f410 httpd: rename gen_random_bytes to gen_random_hex_chars
This reflexs the function better.
Moving `byte` arguments to __xdata which saves 1 SRAM byte.
2026-08-26 20:51:27 +02:00
René van Dorst 57b820c4e8 httpd: mark pointer variable as __xdata.
Otherwise it is put on SRAM location.

Saves 6 SRAM bytes
2026-08-26 20:49:58 +02:00
d00f 75b223223c Merge upstream/main into the Spanning Tree branch
cmd_parser.c conflicted twice: main rewrote the pvid command around the
new port-separator and atoi helpers, next to the line where this branch
delegates "stp" to stp_parse(). Both belong, so the STP delegation keeps
main's pvid body.

The linker caught what the merge could not see: main changed atoi_byte()
to return the digit count and leave the value in atoi_results_u8, where
it used to return non-zero on failure and write through a pointer. Note
the sense is inverted, so the three calls in rtl837x_stp.c are adjusted
rather than just re-arranged.
2026-08-26 16:29:39 +02:00
d00f 9a6e2b3e01 pins: put the module back in BANK2
This branch moved rtl837x_pins to the common window in eaff953 to reclaim
BANK2 for the per-port status work, which was free at the time because the
module was only the I2C and GPIO pin helpers.

main has since put the SFP EEPROM transfer there, so the module now weighs
934 bytes and the common window is 156 bytes short. BANK2 has room for it
again, and that is where main keeps it.

Common segment +934 bytes, BANK2 -934.
2026-08-26 01:05:40 +02:00
d00f 6e394300c8 Merge upstream/main into the Spanning Tree branch
Only httpd.c conflicted: main added the buffered configuration upload
next to the pointers this branch had moved into xdata to free internal
RAM. Both belong, so the new globals sit above declarations that keep
their storage class.
2026-08-25 23:28:09 +02:00
bloqaudio 801150431b stp: host the spanning tree module in BANK2
All entry points are already __banked and none of the code runs in
interrupt context, so the module can leave the resident bank. This
relieves pressure on bank 0, which no longer fits a machine with a
custom init table.
2026-08-25 10:05:19 -05:00
logicog 45d0aac118 Merge pull request #326 from vDorst/SRAM-cmd-parse-port-helper
Add/refactor helpers to parse IP, numbers and port arguments
2026-08-25 12:49:00 +02:00
René van Dorst ee05cd4000 Fix: send_counters(), port-argument have to be physical port number.
Not the physical port index!
2026-08-25 07:55:17 +02:00
René van Dorst 74013bb14f CMD: Treat port zero as CPU-port 2026-08-25 07:55:17 +02:00
René van Dorst beb14deba5 httpd: send_counter(): Validate phys_port_idx and better error handling 2026-08-25 07:55:17 +02:00
René van Dorst 1212def799 CMD: fixes many comments 2026-08-25 07:55:17 +02:00
René van Dorst 95b3bde822 Rename print_port() to print_phys_port() 2026-08-25 07:55:17 +02:00
René van Dorst f511453f79 CMD: refactor code 2026-08-25 07:55:17 +02:00
René van Dorst 8653216047 CMD: Fix some comments 2026-08-25 07:55:17 +02:00
René van Dorst e62e0fc8bd CMD: Isolate allow the CPU-port as a member. 2026-08-25 07:55:17 +02:00
René van Dorst 2ff418f80f Replace '\0' to NUL to make it more clear that it is a NUL-terminated string. 2026-08-25 07:55:06 +02:00
René van Dorst 5d42e8843e Rename cmd_is_space_or_null to cmd_is_space_or_nul 2026-08-25 07:54:15 +02:00
René van Dorst 3c183f73f8 Added print_port() helper to print physical port number. 2026-08-25 07:54:15 +02:00
René van Dorst 3776f12f5c Fix and refactor parse_bw() 2026-08-25 07:54:15 +02:00
René van Dorst 5ddec9710a CMD: Fix parse_lag_hash() 2026-08-25 07:54:15 +02:00
René van Dorst f627559005 fix CMD: pvid: remove atoi_results_u8 from check 2026-08-25 07:54:15 +02:00
René van Dorst dca92d7f45 cmd_parse_port_separator() cmd_parse_port() remove CPU-port support
Removing the CPU-port support, As stated in #334, it is not needed to
manual add the CPU-port to any command. Because the CPU-port should be
added automatilly when CPU-port is needed.

Refactor cmd_parse_port() because port number is only 1 byte.
This simplifies the parsing code.

Refactor cmd_parse_port_separator() to ensure the return size is correct.
2026-08-25 07:54:15 +02:00
René van Dorst 149dc8537f Rename cmd_parse_port_space() to cmd_parse_port_separator()
cmd_parse_port_separator() parse the full port number and checks that the
number end with a NUL of space.
Now this function can also be used to parse the last argument number
because this ends with a NUL.

Also refactor code.
2026-08-25 07:54:15 +02:00
René van Dorst 42f7b4fba1 Fix parse_isolate(), ensure spaces between arguments 2026-08-25 07:54:15 +02:00
René van Dorst 17642699d4 Improve parse_ingress() 2026-08-25 07:54:15 +02:00
René van Dorst 724fd1e061 Added print_ip() to print IPv4 addresses. 2026-08-25 07:54:15 +02:00
René van Dorst 3eb2dfe0c9 Change parse_ip() 2026-08-25 07:54:15 +02:00
René van Dorst 73df006d39 Replace all manual port parsing with cmd_parse_port_space() or cmd_parse_port().
Saves no SRAM but around 1k code size
2026-08-25 07:54:15 +02:00
René van Dorst 175f3bb185 Add cmd_parse_port() and cmd_parse_port_space() helper.
A lot of places we manual parse and translate the port.
These helper functions will do that for us.
2026-08-25 07:54:15 +02:00
René van Dorst 57061161b6 Refactor code to make use of new atoi_byte() 2026-08-25 07:54:15 +02:00
René van Dorst 80f97a50e0 Refactor atoi_byte() same as atoi_short() 2026-08-25 07:54:15 +02:00
René van Dorst c265a20d83 Refactor the code for the new atoi_short()
Free-up 1 SRAM-byte
2026-08-25 07:54:15 +02:00
René van Dorst 4937618684 Change atoi_short() so it returns number of bytes consumed. 2026-08-25 07:54:15 +02:00
bloqaudio 1a743b3889 leds: reduce the SWTG018AS-V2.1.0 custom init to the effective registers
Measured against the generic leds_setup() output on the board: eight of
the 21 stock values are identical to what leds_setup() computes from
the machine's led_mux table, and eight more revert to the generic
values with no change in LED behaviour in any tested link state (copper
2.5G, SFP 2.5G, SFP 10G). Keep the five with a measurable effect: 6528
selects blue over green at 10G, 6540/6548 carry the SFP and copper LED
set behaviour, 65dc enables the LED outputs, and PIN_MUX_0 routes the
blue pin.

Verified from a clean boot on a SWTG018AS-V2.1.0 board: copper solid
green with activity blink at 2.5G on two ports, SFP green at 2.5G,
SFP blue at 10G, LEDs off on link down.
2026-08-24 23:44:36 -05:00
bloqaudio 425283b774 machine: host the per-machine custom init hooks in BANK2
machine_custom_init() runs once at boot, but its code and any tables it
uses were compiled into machine.c and so into the common bank. On the
SWTG018AS-V2.1.0 variant, whose init carries a 21-entry LED register
table, that overflows bank 0 by 0x66 bytes and main no longer links for
MACHINE_PCB_SWTG018AS_V2_1_0; any machine whose init grows can hit the
same wall. SDCC segment pragmas apply file-wide, so the hooks move to a
new machine_init.c compiled into BANK2, and the prototype becomes
banked. machine_check also compiles the new file per machine so the
hooks keep CI syntax coverage.
2026-08-24 23:36:17 -05:00
logicog f582576b10 Merge pull request #345 from DrDoof/fix/sfp-i2c-error
sfp: notice when an I2C read fails
2026-08-25 05:13:47 +02:00
feelfree69 16b0f207ed Merge pull request #317 from bloqaudio/pr/swtgw218as-sfp-led
machine: add PCB SWTG018AS-V2.1.0 variant of the SWTGW218AS
2026-08-25 02:22:21 +02:00
René van Dorst fe2bcc7f2d Merge pull request #356 from bloqaudio/fix/config-post-race
httpd: buffer a configuration upload before touching the flash
2026-08-24 23:35:19 +00:00
d00f a0628c7df5 sfp: read the EEPROM in blocks instead of a byte at a time
The I2C controller transfers up to sixteen bytes per transaction and
page_impl.c already used that for sfp_send_data(), while sfp_read_reg()
asked for one byte and every caller looped. Reading a module therefore
cost one address phase per byte: 87 transactions when a module is
inserted, 52 for the sfp command, 36 for the vendor block in status.json.

sfp_read_block() replaces sfp_read_reg() and the callers that already
wanted a run of registers ask for it once: the vendor fields as three
16 byte pages, the diagnostics as one transfer, rate and encoding
together. That drops the three paths above to 8, 6 and 3 transactions,
and sfp_send_data() loses its copy of the transfer.

The vendor loops now run over 16..63 rather than 20..59 so the page base
is a multiple of sixteen and the index into the buffer is a single AND.
The four extra bytes at each end are read and discarded. The diagnostics
read asks for 16 bytes rather than the 15 it uses, because 16 is a width
the shipped firmware already exercises and 15 is not.

The device address, the bus selection and the start bit go into the
control register in one write now that the memory address is written
first, so a transfer touches that register once instead of three times.
The register reads take their result from the SFRs directly rather than
through the sfr_data mirror. The result is a bool and the destination is
sfp_buf, so a caller that cares about a failed transfer looks at the
return value instead of a flag.

Every caller gives up on the first failed read rather than carrying a
flag to the end, which is why the module read moved out of handle_sfp
into a function of its own. A module whose read fails is left marked as
absent, so the next poll retries it instead of configuring the SerDes
from bytes that never arrived.

BANK1 -194 bytes, BANK2 +382, common segment +44, xdata +15 for the
buffer, and one byte more of internal RAM free than before the series.
Built for all 25 machine definitions on sdcc 4.5.0; the tightest common
segment is 98 bytes free on SWTG024AS_V2_0, against 54 before this
series.
2026-08-25 01:09:20 +02:00
bloqaudio cefe48fe51 httpd: size the config upload buffer for a full config sector
CONFIG_UPLOAD_BUF at 2560 capped a configuration upload at about 2.2K
while the config sector holds 4K. Size the buffer as CONFIG_LEN plus
room for the multipart framing so the whole sector is usable.

config_take() wrote the payload without checking it against the sector:
safe before only because the buffer could not hold an oversized one.
Reject a payload that does not fit CONFIG_LEN, terminator included,
instead of writing past the erased sector.
2026-08-24 15:45:25 -05:00
bloqaudio e8e7fcbe7f httpd: send the config tail from the config sector, not a bare offset
send_config() streams a configuration larger than the TCP output buffer
through the cont_addr/cont_len continuation, but set cont_addr to the
offset within the config instead of a flash address, so the tail was read
from code space. The file server sets cont_addr absolute; do the same by
adding CONFIG_START.

The bug was unreachable while CONFIG_UPLOAD_BUF capped uploads below
TCP_OUTBUF_SIZE, but a stored config near the full sector exposes it.
2026-08-24 15:45:25 -05:00
donbernhardo 8a28b4fc88 Merge upstream main and resolve conflicts 2026-08-24 10:51:27 +02:00
d00f 9a96c80af2 sfp: move the I2C transfer to the banked pins module
sfp_read_reg() sat in rtlplayground.c, so it occupied the common 16 KB
window that every bank shares, even though nothing outside the SFP paths
calls it. That window is the tightest resource in the image:
SWTG024AS_V2_0 and SWTG024AS_A_2_0_1_5C_1SFP had 54 bytes left in it.

rtl837x_pins.c is already in BANK2 and already holds the I2C bus helpers
this function calls, so the transfer belongs there. The function moves
verbatim and becomes __banked; the prototype in rtl837x_common.h says so,
which is what keeps the callers in BANK1 and BANK2 honest.

No behaviour change. The common segment gains 200 bytes on every machine:
159 to 359 free on SWTGW218AS, 54 to 254 on the two variants above.
BANK1 +6 bytes, BANK2 +336. Built on sdcc 4.5.0.
2026-08-24 02:34:39 +02:00
bloqaudio 772e9dc526 httpd: parse a configuration upload once the whole request has arrived
Saving the configuration works in Chrome and fails in Firefox, and the
difference is only how each browser splits the request. The handler
erased the config sector as soon as the request started and then parsed
the multipart body one TCP segment at a time, which requires every
boundary and every part header to fall inside a single segment. Firefox
splits inside a part header, so the parser lost its place and the
erased sector was left holding a truncated body or nothing at all. A
single-burst scripted post lost the whole body the same way.

The configuration is limited to two kilobytes, so the whole request body
now accumulates in xdata and is parsed only after the closing boundary
has arrived. The parts are walked in one pass, the part carrying a
filename is written to a freshly erased sector, and the client receives
a 200 instead of the previous silent close. No segmentation can confuse
this, since the parser only ever sees a complete body.

Locating the closing boundary first also bounds the walk over the parts,
since none can lie beyond it, so the length of the buffer is no longer
the bound and the test for the two trailing dashes is unnecessary.

The walk matches at offsets inside a buffer that is not terminated, so
neither existing helper fits: strcmp() goes on to compare the byte after
the match and is_word_x() demands a separator there. Add strstart() and
strstart_x() for that case, and use strlen_x() for the boundary length.

The firmware upload path still streams, since a megabyte cannot be
buffered, and is untouched.
2026-08-23 18:11:46 -05:00
logicog 025f72c876 Merge pull request #258 from bennydiamond/newline-on-uip-serial-print
Automatically print newline on serial interface
2026-08-22 19:14:45 +02:00
d00f 62f80f0ad2 nic: report a transfer that does not complete instead of carrying on
The bounded waits were silent: on timeout the code went straight back to
its caller, and handle_rx() then read a frame the DMA may never have
delivered, which is worse than waiting longer.

Each of the three transfers now says so on the console, the two RX ones
report failure to handle_rx(), and handle_rx() acknowledges the packet
and gives up on it rather than parsing whatever is in the buffer. The
guard variable moved to the top of its function, so the block that held
it and its indentation are gone.
2026-08-19 16:20:34 +02:00
d00f 803d9c1242 nic: do not claim a cause for the bounded TX wait
The comment said the ASIC never completes a TX when the egress port is in a
non-forwarding MSTP state. The guard is worth keeping either way, since an
unbounded spin in the DMA wait takes the whole main loop down, but the
mechanism is more than the evidence supports and the DMA into the TX ring
has no business knowing the egress port at all.
2026-08-18 23:32:33 +02:00
d00f ec069a5bcc stp: rename stpEnabled and take the comments out of the header
The variable lives in rtlplayground.c, so it is declared in rtl837x_common.h
with the others there, and it follows the naming of the rest.

The header carried comments on the externs that the definitions in
rtl837x_stp.c repeat, sometimes differently, which is one place too many to
keep in step. What only the header had, the value ranges and what the
designated arrays hold, moved to the definitions; the rest is gone. Function
declarations lost their comments too.

The status printer only prints, so its running commentary went. A define
replaces the bare 33 in stp_in(), and the note on the loop check is down to
what applies at that line.
2026-08-18 23:32:25 +02:00
d00f 81d11c246c stp: correct what the blocking state does
A blocked port does pass a received BPDU up to the CPU. The evidence is in
logicog's capture of a looped pair: the port the loop check had already
blocked kept reporting a BPDU age of zero seconds across dumps taken more
than a forward delay apart, and that counter is only cleared in stp_in().
That is also what makes the loop latch work, since the port that stays
forwarding has to go on hearing the blocked one.

The tag flag comment said the source address is not learned on the egress
port. doc/CpuPort.md defines it as not learning the source address from the
frame at all, which is the narrower claim to make. The dangling heading
above it described a field that is documented there too.
2026-08-18 23:31:06 +02:00
d00f 85d1d7520a stp: send BPDUs with a per port source address
802.1D puts the port's own address in the source field and the bridge
address only in the Bridge Identifier. We used the bridge address for
both, and on this hardware that costs the management path.

Measured on a SWTGW218AS: the ASIC learns the source address of a frame
addressed to 01:80:c2:00:00:00, and the bridge's own address is not
exempt. A BPDU that leaves a blocked port and comes back on a forwarding
one therefore moves the management address off the CPU port, and frames
for it are then sent down that port instead of to the CPU. Traffic
between other stations is unaffected, which is what makes it look like
the CPU port has been blocked.

The derived address keeps the bridge address and sets the locally
administered bit, so it differs from the bridge address in the first
octet for any globally assigned OUI, with the port number in the low
nibble of the last octet. Nothing here reads the source address of a
received BPDU; the loop check compares the Bridge Identifier.
2026-08-18 23:31:05 +02:00
d00f 810db48a00 stp: drop the bare scope blocks around the port variable
Declaring port at the top of stp_in() and stp_parse() does the same job
without a block that is not indented like one. The static xdata copy in
stp_in() went with it, it was only ever written.

The argument count check in stp_parse() that lost its comment guards
against cmd_compare(4, ..) reading a stale word from the previous command
line, because cmd_words_b is not cleared between commands.
2026-08-18 23:31:02 +02:00
d00f f9d3dec50f Do not insert the management VLAN tag into a CPU-tagged frame
tcpip_output() splices the 802.1Q tag in right behind the source address,
which is exactly where the ASIC expects the RTL tag of a frame the CPU
addressed to a port itself. The tag then ends up behind the VLAN tag, the
ASIC does not find it, and the frame goes out flooded with the 0x8899
header still on it instead of being sent to the port that was asked for.

Whether a frame is CPU-tagged is a property of the frame, so decide it
here from the ether-type rather than having every sender of such a frame
clear management_vlan around its tcpip_output() call. stp_cnf_send() did
that, and no longer has to.
2026-08-18 23:31:00 +02:00
d00f 236eac610d stp: name the BPDU version, type and flag constants
The comments they replace are gone with them. Two things the comments
carried that the names do not:

Accepting version >= 2 rather than == 2 is deliberate. 802.1D-2004 14.4
has an RSTP bridge accept a higher Protocol Version and treat it as RST,
and MSTP sends version 3 type 2 with a prefix identical to an RST BPDU
for exactly that reason, so insisting on == 2 would make us blind to
every MST bridge on the segment.

In the TCN branch stp_cnf_send() transmits by itself, so uip_len is
cleared afterwards to keep handle_rx() from sending the frame twice.
2026-08-18 23:30:53 +02:00
d00f 9df9eece3f stp: drop the comment on the CPU tag flags
Worth keeping out of the code but on record: RTL_TAG_KEEP is deliberately
not set here. On an LLC/802.3 frame the ASIC drops the frame outright with
that flag, while on ethertype frames such as LACP it works fine.
2026-08-18 23:30:52 +02:00
d00f 3cd9131795 stp: drop the inline comments in stp_loop_hold_peer
The port number comes out of a received BPDU, so the range check is
there to keep a forged frame from naming a port this module does not
manage - including the CPU port, which would cost us the management
path. Nothing outside min_port..max_port would ever release the block
either, because stp_timers() only counts down the ports it walks.
2026-08-18 23:30:50 +02:00
d00f 50b68f908e stp: drop the owner comments on the externs 2026-08-18 23:30:49 +02:00
d00f f1f3d521b5 stp: drop the comment on the rtl837x_port.h include 2026-08-18 23:30:46 +02:00
d00f addca1cb26 httpd: use local variables for the loop indexes in send_stp
The three xdata bytes and the reused stp_we_root scratch are gone; the
compiler needs a register for an index either way. Frees four bytes of
xdata and 29 bytes of code.
2026-08-18 23:30:46 +02:00
d00f cbd2a8c080 httpd: drop the tick rate comment on the ticks extern 2026-08-18 23:30:45 +02:00
d00f 78b3971782 stp: pass a received topology change through the switch
The tree structure already crossed the switch by regeneration, but the
topology-change information did not: a received TC flag was ignored and
a TCN only acknowledged, so bridges behind this one kept stale entries
until normal aging.

A TC flag in a received BPDU now flushes the other non-edge ports once
and arms the transmit window our BPDUs already copy the flag from,
refreshed to hello+1 seconds by every further flagged frame so it ends
one hello after the neighbour stops, without shortening the longer
window a local change arms. A TCN is acknowledged as before and then
treated like a local change on that port.
2026-08-18 23:30:44 +02:00
d00f 201c7e333d httpd: drop the byte-access justification from u32hex_html
Byte access instead of 32-bit shifts is how this codebase works
everywhere, so the comment explained a house rule at one call site.
2026-08-18 23:30:40 +02:00
d00f 2c26a4f389 stp: count the BPDUs each port has sent
The table could say a port was designated and had heard nothing, which is
two different situations wearing the same face: either we are not
announcing on that segment, or we are and nobody is answering. Telling them
apart needed a capture on the far side.

    port state role edge tx bpdu
     05  fwd   desg yes  2a 255
     01  block desg no   2a 21
     03  fwd   root no   00 0

The tx column counts BPDUs actually handed to the hardware, so it moves
only past the enable, filter and tx hold checks in stp_cnf_send(). A
designated port has to show it climbing once per hello time. The root port
never does, because we do not announce back towards the root, so a
neighbour that has taken us as root falls silent in both directions on that
link and the two columns together say exactly that rather than looking like
a fault.

The counter is a byte and wraps at 256. It is meant to be watched moving,
not summed, and it starts again when STP is enabled.

67 bytes of BANK2 and 10 of xdata, nothing in BANK1 or internal RAM. Built
for SWTGW218AS and KP_9000_6XHML_X2 on sdcc 4.5.0.
2026-08-18 23:30:38 +02:00
d00f 266c95e4d7 stp: name the port state, role and edge in stp status
The table printed the ASIC's raw two bit state, a 1 or a 2 for the role and
a 1 or a 0 for the edge flag, so reading it meant having the source open
next to the console. The columns carry the words now:

    port state role edge bpdu
     05  fwd   desg yes  255
     01  block desg no   21
     02  learn desg no   5
     03  fwd   root no   0

They come from fixed width tables indexed by the same values as before, so
nothing about how any of the three is derived changes, and the columns line
up under the header without a formatter.

The role column still only tells the root port from everything else,
because that is all the state machine tracks. A port sitting in blocking
because a better BPDU arrived on it reads as designated here. Naming the
column makes that visible rather than introducing it.

154 bytes of BANK2, nothing in BANK1, xdata or internal RAM. Built for
SWTGW218AS and KP_9000_6XHML_X2 on sdcc 4.5.0.
2026-08-18 23:30:37 +02:00
d00f 38b19820e4 stp: show how long ago each port last heard a BPDU
The status output named state, role and edge, none of which separates a
port nobody is speaking (R)STP to from a port whose BPDUs we are
dropping. Both look the same: forwarding, designated, edge, and the tree
rooted at ourselves. stp_in() leaves on eight different conditions, from
a short frame through an unexpected LLC header to a disabled port, and
none of them says anything.

stp_bpdu_age was already maintained for the ageing rules, so this only
prints it, in seconds and capped at 255. A column that counts up means
nothing is arriving; a column that stays near zero means frames are
arriving and any disagreement about the tree is ours.

Eighty two bytes of BANK2 and two of xdata, most of it the sixteen bit
divide by the tick rate. It comes out of a branch that gives back three
hundred and twenty eight, so it is affordable, and printing raw ticks to
save it would put the reader back to converting in their head.
2026-08-18 23:30:37 +02:00
d00f 988bddacf7 stp: compare the whole Bridge Identifier, not the priority byte and the MAC
A Bridge Identifier is two priority octets followed by the MAC, compared
as one unsigned number. The test here read the first priority octet and
then went straight to the MAC, so the system ID extension in between was
never looked at and two bridges differing only in it were ranked by MAC
instead. The field is stored, sent and printed, just not compared.

Ordinary single instance RSTP leaves the extension zero on both sides,
which is why this has not shown up. Where it is not zero the ranking is
simply wrong: same priority octet, extension 0x0a against 0x00, and the
worse bridge wins if its MAC happens to be lower.

cmpMAC becomes cmpBytes with a length, since the identifier is eight
contiguous bytes in both the packet overlay and root_bridge, and the
loop was already doing the right thing for six of them. sdcc lays the
struct out with no padding, checked, so the eight byte compare is the
standard's rule written directly.
2026-08-18 23:30:35 +02:00
d00f 4264856109 stp: stop treating a port as an edge once it hears a BPDU
802.1D has a port leave the edge state when a BPDU arrives on it. Here
the flag was only ever cleared by the loop latch, root guard, a link
coming back, "stp on", "stp off" and the edge command itself, so a port
that auto-edged during the three seconds of silence after link-up kept
the flag for as long as it stayed up, whatever the neighbour sent.

Two things read that flag. The status page prints it, so a port talking
to a bridge reported edge 1 and there was no way to tell from the output
whether a BPDU had ever arrived. More quietly, stp_topology_change()
returns early for an edge port, which is right for a real one and wrong
for this: a topology change on such a port was neither counted nor
propagated, and port_l2_forget_port() never ran, so what was learned
behind it stayed in the table.

Only the flag is cleared. The port is not pushed back through the listen
period, which would take a working link out of forwarding for a forward
delay the first time a neighbour speaks.
2026-08-18 23:30:34 +02:00
d00f 35d26cb08f stp: name the status subcommand in the usage line
"stp status" has always worked, but the line printed on a bad command
listed only on and off, so the one subcommand that shows what the bridge
thinks was the one you had to already know about.
2026-08-18 23:30:32 +02:00
d00f a9af466702 stp: drop the management failsafe
The window could be armed from the serial console but only ever disarmed
by an HTTP request. save_cmd, which gates arming, is cleared only while
execute_config() replays the startup config, so every interactive command
armed it wherever it was typed, while mgmt_alive, which disarms it, was
written in exactly one place, on HTTP traffic. An operator working
entirely on the serial console therefore lost STP 180 seconds after
enabling it however much they typed, which is what makes the mechanism
impossible to test from a console.

The documentation described the behaviour that was intended rather than
the one that was built, and in both directions: it said a command on the
serial console also confirms, and it said a reboot with STP in the
startup config disables it again three minutes later. Neither held. The
replay path never armed the window at all.

Repairing the asymmetry would have kept a mechanism whose premise is
contested anyway. A watchdog that switches the protection off in response
to silence adds a second failure mode on top of the first: where the
network is misconfigured and STP is the thing holding a storm back,
restoring forwarding removes the last reason management still answers.

Gone with it: the stp failsafe command, the fs and fsT fields of
/stp.json, the input and the tripped banner on the Spanning Tree page,
the two persistence patterns in config.js, the documentation section, and
mgmt_alive itself, which had no other reader.

550 bytes back, 145 of BANK1 and 405 of BANK2, and five of xdata, which
is the four counters and mgmt_alive and nothing else. Built for
SWTGW218AS and KP_9000_6XHML_X2 on sdcc 4.5.0.
2026-08-18 23:30:25 +02:00
d00f 024c8cef49 stp: arm the management failsafe only where an operator asked for it
Your console session shows the shape of this better than I could have. STP found
a loop, blocked the port, unblocked the other side of the pair, and then the
failsafe turned STP off. It had been disarmed by your first console command and
re-armed by the loop detection itself, so a mechanism that exists to protect
against a lockout ended up removing loop protection while a loop was physically
present. That is the part that did not make sense, and it wasn't the console.

Two changes, both narrowing.

Loop detection and root guard no longer arm the window. Those are the protocol
doing its job on evidence off the wire. Nothing an operator did needs undoing
there, and nobody is waiting to confirm anything.

Typing on the serial console no longer disarms it. The failsafe asks one
question, whether the operator can still reach management over the network, and
serial activity doesn't answer it. It proves somebody is standing at the box,
which is the one case where a lockout doesn't matter, and it took the safety net
away from a remote operator on behalf of someone not using it. HTTP activity
still confirms, because that is the path being measured, and the console in the
web interface counts for the same reason.

What is left arms on stp on, stp port N on and stp failsafe, each of them an
operator choosing something whose outcome the protocol then decides.

Gives back 61 bytes of BANK2, 31 of the common area and a byte of xdata.
2026-08-18 23:29:18 +02:00
d00f 64906ad135 stp: name front panel ports on the console, and add "stp status"
Two things from the review, both about the console being where you end up when
the tree is not what you expected.

The six messages that name a port were printing the internal index. On a board
whose map is not the identity that is a different number from the one written
next to the socket, which is worse than no number at all. They go through
machine.log_to_phys_port now, in a small helper that also swallows the newline
each of them repeated.

"stp status" prints the bridge and root IDs, the root port and path cost, the
topology change count, the failsafe setting, and a line per port with state,
role and operational edge. Everything it shows is state the module already
keeps, apart from the port states, which come from one read of MSTP_STATES.

660 bytes of BANK2, which leaves 3366 free. No internal RAM, no xdata.
2026-08-18 23:29:17 +02:00
d00f 4d7a2e28c7 stp: keep the designated bridge recording out of internal RAM
The report on the PR is that it will not link for KP_9000_6XHML_X2, with
"?ASlink-Error-Could not get N consecutive bytes in internal RAM for area OSEG"
five times over. It builds here on sdcc 4.2.0 and 4.5.0 for that same machine
and the same commit, so something in the toolchain differs, but the pressure it
is complaining about is mine and it costs little to give back.

stp_in() is __banked, so its temporaries get exclusive DSEG instead of
overlaying with anything else. Recording the designated bridge put four more
live values across a memcpy in the middle of it and the register allocator
answered with five spill locations. The module went from 5 bytes of DSEG to 12,
and from 17 sloc references to 49.

Moving that block into a __reentrant helper puts its temporaries on the stack
instead. The module now claims no DSEG at all, 5 bytes better than before the
recording was added, and the image sits at 95 bytes of DSEG against 101 on main.
It costs 170 bytes of BANK2, where there is room.
2026-08-18 23:29:16 +02:00
d00f a6c5f558bf stp: do not arm the management failsafe while the config replays
The failsafe is a commit confirm window for an interactive change: turn STP on,
and if management goes quiet for stp_failsafe_s seconds the switch undoes it.
The three places that arm it sit in the command parser, and execute_config()
drives that same parser at boot, so a saved "stp on" arms the window too. A
switch that reboots with nobody watching then turns its own STP back off.

Measured on a SWTGW218AS with "stp failsafe 180" in the saved config: cold boot,
no HTTP and no console for four minutes, and "STP failsafe: disabling" arrives
on time, with stp.json reporting on:0 and fsT:1.

execute_config() already clears save_cmd while it replays and sets it again at
the end, so the three parser sites can just test it. The two on the protocol
side, the loop latch and the root guard, stay unconditional. They react to what
arrived on the wire, which is the case the failsafe exists for, and they only
run once the replay is long finished.

BANK2 grows 24 bytes. Nothing else moves.
2026-08-18 23:29:16 +02:00
d00f 5a6b854c5d stp: record the designated bridge, port and cost from received BPDUs
stp_dbridge, stp_dpid and stp_dcost were declared and read by the status page,
but nothing ever wrote them, so they stayed zero for the life of the firmware.
The page's validity test then always failed, and the Designated Bridge,
Designated Port ID and Designated Cost columns reported our own values on every
port, including the root port where the answer is the upstream neighbour. The
three arrays reserved 140 bytes of xdata and never used any of it.

They are filled now, right after the loop check, so a frame that came back from
one of our own ports is not mistaken for a neighbour.

The validity test moves from the last byte of the stored MAC to the stored Port
ID. A Port ID is 1-based on the wire and cannot be zero, while a neighbour whose
MAC happens to end in 0x00 would have failed the old test.

The root path cost byte swap happens once now, and the root port branch reuses
the value instead of repeating the shifts.

BANK2 grows 148 bytes, BANK1 loses 9, and xdata does not move.
2026-08-18 23:29:15 +02:00
d00f d31980eb9e stp: sort the port rows, show this switch's bridge ID, react to enabling
Three things from the page feedback.

The rows came out in logical order while carrying the physical port number,
so on the six-port boards the first row is labelled 5. Sorting the rows by
that number in JS puts every board back into front-panel order. I walked all
25 machine definitions and each one now yields a clean 1..N.

The Designated Bridge column is hard to read without knowing this switch's
own bridge ID, so the status line shows it in the same priority and MAC shape
as the cells use. On the test switch that reads 61440-06:05:16:1E:F9:24 and
matches the Designated Bridge of every locally designated port, which is the
comparison that was missing. The root bridge and the path cost now use the
same formatting as the columns instead of raw hex.

Enabling STP printed nothing until the next poll, and because the ports start
blocked, management can stay quiet for the whole listening and learning
period, so the page had no chance to say anything later. It now writes what
is about to happen before the command goes out, and how long the ports need.

Page data only. Both banks, xdata and the common bank are unchanged.
2026-08-18 23:29:14 +02:00
d00f eb4a6ac275 stp: drop the bounds on the L2 flush and L2MC table waits
The review asked for these to come out and the reasoning holds. A guard that
gives up mid-transaction lets the code carry on with whatever the engine left
behind, and that is what cost me an SPI clip twice. An unbounded wait on a
wedged engine still hangs, but it hangs in a known place instead of writing
garbage into the L2 table.

BANK1 loses 45 bytes and xdata one. BANK2 and the common bank do not move.
2026-08-18 23:29:13 +02:00
d00f 1ba1224644 stp: trim the comments, and put one back on the variable it describes
Review asked for this across the other commits too. Gone are the blocks
that restate what doc/stp.md already says, the ones that explain what an
embedded programmer already knows, and one that had gone stale inside this
very branch: the CLI summary above stp_parse still described "cost <0-255>
(x1000)" while the parser has taken the raw 0 to 200000000 for some time,
and it never learned about p2p or trk at all. A usage list next to the
parser is the kind of thing that rots first, so it is out rather than
updated.

The review flagged one comment saying a variable is in xdata because the
internal RAM overlay is full, on the grounds that it may stop being true.
Four more of the same kind were in these files and are out as well, one of
them pointing at a file that does not exist in this branch at all. The
declarations still say __xdata, which is the part a reader needs.

Also out: the note on why three helpers are __reentrant, which was really
a paragraph about two bytes of DSEG, and the measurement story behind the
tick divider, which belongs with the other timer numbers in doc/stp.md.

One comment was not stale but simply wrong. "max BPDUs per port per second"
sat on stp_failsafe_tripped, having slid down two lines when the two
failsafe variables were inserted above it. It describes stp_txhold and is
back there now.

Short factual labels stay: they sit next to the magic number they explain
and the codebase uses them throughout. The generated code is byte for byte
what it was before this commit, both banks and xdata unchanged.
2026-08-18 23:29:11 +02:00
d00f 4a78b2dc8c doc: move the L2 multicast and tag word details out of the code
Review asked for this directly: the hardware layout above port_l2mc_set()
would be better as documentation than as a comment, keeping only the two
lines that say what the function does.

doc/l2.md gains a section on static multicast entries, why delivery uses
the forward action rather than the trap, and the SMI layout of the entry.
doc/CpuPort.md gains the layout of the tag's flags and pmask words, with
the byte order trap that cost an afternoon: writing the flags constant raw
instead of through HTONS puts 0x0020 on the wire as 0x2000, which is EFID
rather than LEARN_DIS, and the ASIC then leaves the 0x8899 header on the
frame.

The comments those paragraphs came from are replaced by a pointer to the
file that now holds them.
2026-08-18 23:29:10 +02:00
d00f 0160b430f4 doc: correct what a blocked port does with frames
The note said a blocked port drops frames the CPU injects into it and so
cannot send BPDUs of its own. Hardware says otherwise, and it matters,
because that sentence is the reason one would go looking for a way to let
control frames out of a blocked port when there is nothing to fix there.

Held a two port group in blocking and watched from the neighbour. Our
BPDUs kept leaving it, 27 of them with a largest gap of 2.00 s, which is
the hello interval with nothing missed. Pings across the same port stopped
dead for 11.63 s in one unbroken gap, so data really is held. In the same
window the neighbour sent 74 frames with a largest gap of 1.04 s while our
receive counter for them moved by 2, and the port stayed in its trunk
throughout, so nothing in the aggregation code was discarding them.
2026-08-18 23:29:09 +02:00
d00f c72d36af36 stp: turn the management failsafe into a one-shot window
The failsafe used to watch management traffic for as long as STP ran, so
three minutes of nobody looking at the web UI took the tree down on any
quiet network. That made a standing STP config impractical, which is the
problem raised in the review of the original PR.

Enabling STP arms a window of stp_failsafe_s seconds. One HTTP request
inside it confirms that management survived the new tree and disarms the
watchdog; a silent window disables STP and restores forwarding. Both
outcomes print to the console and the syslog.

The window re-arms on any later event that newly takes a port out of
forwarding: a port rejoining via "stp port N on", root guard firing, the
loop latch. Those were covered by the old always-on surveillance and a
disarmed window would have left them able to cut management off for good.
If management traffic keeps flowing past the new block, the next request
confirms straight away, which is the correct verdict, the block did not
cut it. The arming deliberately does not refresh an already armed window:
root guard can re-fire on every hello, and refreshing the countdown on
each one would keep a cut-off window from ever expiring. A stable network
with nothing newly blocked never re-arms, which is the reviewed-for
behaviour.

The request or console command that causes the arming never counts as its
own confirmation: mgmt_alive is cleared when a command arms, and the
console hook only disarms when the window predates the command. Without
that, enabling from the web UI or the console would confirm the window
before the new tree had any chance to cut management off.

A command on the serial console confirms like HTTP does. An operator at
the console has out-of-band access that no tree can cut, so the automatic
restore only takes STP away from someone equipped to deal with the
situation. The hook sits on the interactive console path only, identified
by cmd_available, so neither the config replay at boot nor HTTP commands
pass through it.

After a confirmation STP runs unsupervised until something new blocks.
Headless installs where nobody will confirm should set stp failsafe 0;
doc/stp.md says so.

Costs two bytes of XDATA, the armed flag and the console-path snapshot;
stp.rel and rtlplayground.rel keep their segment sizes.
2026-08-18 23:29:08 +02:00
d00f 7ebb420e7d stp: warn when an enabled port cannot receive BPDUs
A port set to admit tagged frames only will never see a BPDU, because
delivery rides the forward action and the ingress pipeline drops untagged
frames before the L2 lookup. The failure is silent and looks like a dead
receive path: the port turns edge after three seconds, the bridge elects
itself root, and nothing hints at the ingress setting. Diagnosing exactly
that cost most of a day on a live switch, with the neighbour provably
transmitting the whole time.

stp_setup() now prints one line per affected port, so the hint lands at
"stp on" and at every config replay on boot. The check runs in its own
loop after the MSTP write: port_ingress_filter_get() reads a register
into sfr_data, which the state-building loop above is still using. The
port number in the message is physical, matching what the ingress
command takes.

doc/stp.md explains why this can happen here and not on a normal bridge,
where BPDUs are consumed before any VLAN classification.

stp.rel stays at DSEG 5 with no OSEG and the image at 10498 bytes of
XDATA.
2026-08-18 23:28:39 +02:00
d00f 2cf60e177b stp: derive the BPDU flags from the port state
Every RST BPDU we sent carried flags 0x3c - designated, learning, forwarding -
whatever the port was actually doing. A blocked port kept announcing itself as
forwarding, and the root port would have called itself designated. Nothing on
this bench acted on it, but it is a lie in the protocol frame and the kind
that surfaces in somebody else's mixed network.

Derive the flags instead: the root port reports the root role, every other
transmitting port is designated (alternates do not transmit at all), and the
learning and forwarding bits mirror the ASIC state, so a listening port now
sends 0x0c. TC and TCA stay dynamic as before. Legacy Config BPDUs are
unchanged - their flags only ever carried TC and TCA.

Costs nothing in internal RAM; the state comes from the register scratch that
is already there.
2026-08-18 23:28:38 +02:00
d00f 0ce5fb4af3 stp: latch the block on a looped port
Two of our own ports on one segment blocked each other in turn instead
of settling. The guard on the loop path only acted when port_timers[]
had already run out, so a BPDU arriving while the port was blocked did
nothing: the timer expired, the port went forwarding, the loop reopened
and the pair started over. The comment above the code claimed the
opposite - "if the loop persists the BPDUs keep arriving and the port
stays blocked" - but nothing implemented it.

Measured on a SWTGW218AS with a patch cord between two free ports: both
ports blocked, both returned to forwarding one forward delay later, and
the topology-change counter reached 0x51 in 5.5 minutes - 15.6 changes
per minute for as long as the cable was in.

Let the better Port ID decide for both. That port is forwarding by
construction, so it goes on hearing the loop and re-arms the other
port's timer on every BPDU, which is what turns the block into a latch;
the held port only has to keep transmitting, which the send path already
allows in any MSTP state. Nothing here depends on a blocked port still
receiving - that was never established. Having one writer also removes a
race: while both ends decided for themselves, the winner's re-arm could
land in the loser's port_timers[] first, the loser read it as "already
blocked" and skipped its own state change, and the loop stayed open.

802.1D compares the priority before the number and stp_cnf_send() puts
stp_pprio[] on the wire next to it, so compare that first - otherwise
"stp port N prio" would quietly not influence which end of a looped pair
keeps forwarding.

The port number arrives in a frame and our bridge MAC is public in every
BPDU we send, so bound it to the ports this module manages before
indexing anything. Outside that range nothing would release the block
either: stp_timers() walks min_port..max_port and skips ports that are
not STP-enabled, so their port_timers[] never counts down.

Equal Port IDs mean the frame came back on the port it left - a loop
further out, behind an unmanaged switch. There is no pair to choose
from, so that port holds itself down; since it can only re-arm while it
is receiving, that case stays the forward-delay pulse it was before
rather than becoming a real latch.

The work sits in a __reentrant helper on purpose, like the two functions
above it: parameters and locals then live on the stack. Inlined into
stp_in(), which is __banked and whose temporaries cannot be overlaid,
the same code costs two more bytes of DSEG - enough to stop an image
that also carries LACP from linking at all.

Verified on hardware: with the loop in place for 1 h 36 min exactly one
port blocked, the other kept forwarding, and the topology-change counter
moved four times in total - three of them the link event and the
promotion in the first minute.
2026-08-18 23:28:37 +02:00
d00f 4b5bf09c83 doc: separate the trap action from CPU-port delivery
The wording read as a claim about the CPU interface in general, which is
wrong and misleading: the 8051 sits behind an ordinary port of the internal
switch and is an ordinary member of a forwarding mask - which is exactly
what this implementation relies on.

Say what is actually broken instead: the trap action, a separate mechanism
whose destination is an external CPU port these boards do not populate.
Record the measurements behind it, including the widened CPU_PMSK and both
external-CPU destinations, and add the ACL trap result - a rule matching the
group intercepts frames but does not deliver them either, which is a second,
independent path to the same conclusion.

Reported-by: vDorst
2026-08-18 23:28:37 +02:00
d00f be520d6716 stp: accept BPDUs with a protocol version above 2
We only recognised RST BPDUs when the Protocol Version Identifier was
exactly 2, which silently drops every MST BPDU: 802.1s uses version 3
with type 2 and a prefix deliberately laid out to be identical to an RST
BPDU, precisely so that an RSTP bridge can parse it.

802.1D-2004 14.4 spells the rule out - a bridge shall accept a version
identifier of 2 or greater and treat the BPDU as RST, ignoring anything
beyond what it understands. Compare with >= instead of ==. The receive
path already length-checks before touching the body and only reads the
fields common to both formats, so a longer MST body needs no other care.

The two fields are deliberately asymmetric: the Protocol Identifier must
be exactly zero (it is a sanity check), while the version is an extension
point that has to tolerate the future.
2026-08-18 23:28:35 +02:00
d00f f3c1b7f3ac stp: react to a port losing carrier
The state machine never looked at link state, so a port whose cable was
pulled stayed in forwarding: it kept being announced, kept its learned
entries, and the most ordinary topology change there is went unnoticed.
Observed on hardware - a non-edge port with the link administratively
down still reported forwarding and left the topology-change counter at
zero for the whole observation window.

Sample the carrier bitmap once per second, alongside the tx-budget refill
(the 50 Hz tick has no business doing register reads). On carrier loss put
the port back to blocking and run the normal topology-change path, which
flushes just that port's entries. On carrier return re-run the listen
period rather than forwarding immediately - the segment may have been
rewired while we were down - and clear the operational edge flag so a port
that was auto-edged has to earn it again.

stp_setup() seeds the bitmap from the hardware so enabling STP does not
report every already-down port as a fresh topology change.
2026-08-18 23:28:34 +02:00
d00f 7a790b32b1 README: STP is no longer missing
Point at doc/stp.md instead, with the caveat that the implementation is a
simplified one and that enabling it remotely deserves a read first.
2026-08-18 23:28:34 +02:00
d00f 293a196c91 doc: describe the Spanning Tree support
Covers enabling it, the bridge and per-port settings, how BPDUs are
delivered on this hardware (RMA forward constrained by a CPU-only static
L2 entry, since trap-to-CPU targets an external CPU these boards do not
have), the timer base, and the management failsafe - enabling STP over
the network can block the very port the management VLAN rides on, so
that part is spelled out rather than left to be discovered.
2026-08-18 23:28:33 +02:00
d00f 6b4d2b9cfa stp: apply an edge-port change immediately
"stp port N edge off" cleared only the admin and auto flags, not the
operational one - and that is the flag the engine actually consults: it
exempts the port from topology changes and lets it skip the listen
period. A port therefore stayed an edge port until the next "stp off" /
"stp on", silently ignoring the new setting. Clear it with the others,
and mark an admin edge operational right away, as stp_setup() does.
2026-08-18 23:28:33 +02:00
d00f 3c4fb679bd stp: correct the timer tick rate (50 Hz, measured)
The timers assumed stp_timers() runs at 64 Hz. It does not: the main loop
idles on the 200 Hz system tick and calls us every fourth pass, i.e.
50 Hz. Measured on hardware - with hello configured to 2 s the BPDUs left
the port 2.560 s apart, exactly the 28 % overshoot the wrong constant
implies, and every other timer (forward delay, max age, tx-hold refill)
was stretched the same way.

Move the constant to the header with the arithmetic spelled out, and use
it in the status page too, which had the 64 hardcoded and therefore aged
the same counters differently than the engine.
2026-08-18 23:28:32 +02:00
d00f f1f69e26fd stp: do not carry a dropped BPDU's flags over to the next one
The Topology Change Acknowledgment is staged in a one-shot variable and
consumed when the BPDU is built - but stp_cnf_send() can return before
that, when the port is filtered/tripped or its tx-hold budget for this
second is spent. The flag then survived and was OR-ed into the next BPDU
this switch sent, on whatever port that happened to be. Clear it with
the frame it belonged to.
2026-08-18 23:28:31 +02:00
d00f b51ed71bb1 stp: announce topology changes and flush the port that changed
A port entering forwarding, or being blocked because its own BPDU came
back, changes where MAC addresses live - but the counter was bumped and
nothing else happened: our forwarding table kept the stale entries and
the neighbours were never told.

Flush the affected port's dynamic entries (bounded single-port flush)
and set the Topology Change flag in our BPDUs for max age + forward
delay, so neighbours age their tables out as well. Edge ports are
exempt: a host coming or going is not a topology change.
2026-08-18 23:28:30 +02:00
d00f 65a41eaddd port: add a bounded single-port L2 flush
port_l2_forget() flushes the whole table and polls the flush engine
without a bound. A topology change only needs to age out the port that
changed, and the STP tick cannot afford an unbounded poll: add
port_l2_forget_port() with a single-port mask and the same bounded wait
the static-entry helper uses.
2026-08-18 23:28:30 +02:00
d00f 5361c85d30 stp: keep the root port quiet and relay the message age
Two protocol-correctness fixes on the information we advertise:

Only designated ports announce periodically. The root port is where our
root information arrives; sending it back there feeds the upstream
bridge its own data and makes us look like a competing designated
bridge on that segment.

Relay the message age instead of always claiming zero. A bridge
increments the received age by one second per hop, so downstream
neighbours can age the information out; advertising 0 forever made our
BPDUs look eternally fresh no matter how stale the root information was.
Age stays 0 while we are the root ourselves.
2026-08-18 23:28:29 +02:00
d00f a31be093e7 stp: answer TCN BPDUs and validate the received BPDU length
Accept legacy Topology Change Notification BPDUs (v0, type 0x80): reply
on the ingress port with a Config BPDU carrying Topology Change
Acknowledgment so the sender stops repeating, and count the change.

Also stop reading fields past the end of short frames: require the
header through bpdu_type (33 bytes with the CPU/VLAN prefix) before
classifying, and the full 35-byte body before the election logic -
truncated or fuzzed BPDUs are dropped instead of parsed as garbage.
2026-08-18 23:28:28 +02:00
d00f f39eefa90c stp: carry the version-1 length field in RST BPDUs
An RST BPDU body is 36 bytes: the Config-BPDU fields plus a trailing
version-1 length octet (zero - there is no version-1 information).
Ours was 35 - strict 802.1w parsers treat such a BPDU as malformed and
drop it. Add the field, keep legacy Config BPDUs at 35 bytes, and set
the 802.3 length accordingly (0x27 with LLC for RST, 0x26 for Config).
2026-08-18 23:28:27 +02:00
d00f eaff9537ef stp: vendor-style per-port config + status (path cost, p2p, designated info)
Bring the Spanning Tree page in line with a typical managed switch's
per-port panel. Configuration gains the full-range path cost (raw
0..200000000, 0 = auto, replacing the old 1000x-scaled byte), a
point-to-point admin control (auto/on/off), and the priority is now a
0..240 step-16 dropdown. A new status table shows, per port, the Port
State, Role, Designated Bridge / Port ID / Cost (learned from received
BPDUs, kept per port and aged via the BPDU age), Operational Edge and
Operational Point-to-Point.

The designated fields fall back to presenting this switch as the
segment's designated bridge when no fresh BPDU has been heard (so a
quiet port shows our own bridge-id, as the vendor UIs do). /stp.json
carries the packed hex fields plus our own MAC for that fallback.

Space: reclaim BANK2 for the above by moving rtl837x_pins to HOME and
compacting leds_dump into a register-address table (~800B); bandwidth
returns to BANK1. No BANK3 - hardware-verified that PSBANK > 2 crashes
this SoC at boot (a bricked unit and an SPI-programmer recovery earlier
today); a warning to that effect is now in rtl837x_lldp.c.

Hardware-verified: cost 200000000 and p2p off round-trip through the CLI
and JSON, the status table populates correctly with STP enabled (all
ports Forwarding/Designated, oper-edge and oper-p2p True), LACP 3f/3f
and the LAN unaffected.

(cherry picked from commit 2ec62072f061dc9e78bc821ba1c297cb6819e206)
2026-08-18 23:28:27 +02:00
d00f 2110cf128a stp: management failsafe (commit-confirm) + bounded NIC waits
Enabling STP on a bridge whose management rides an in-band VLAN can cut
off that very management - and not only by our own blocking: on this
network the upstream TP-Link Easy Smart switch's "loop prevention"
reacted to our BPDU hellos by blocking ITS port towards us while our
ASIC was all-forwarding, isolating the whole segment until a power
cycle. Recoverable only by going quiet.

Add a commit-confirm watchdog: while STP is enabled, any HTTP request
re-arms a countdown ("stp failsafe <seconds>", default 180, 0 disables);
if management stays silent for the whole window, STP disables itself,
which also stops BPDU TX so a neighbour's loop protection can release
its block. The web UI polls /stp.json every 2 s, so an open browser
naturally keeps the watchdog re-armed. The trip is reported via
/stp.json (fs, fsT) and as a warning on the Spanning Tree page.

Deliberately not conditioned on our own MSTP port states - the incident
above proves the uplink can be dead while every local port forwards.

Also bound the NIC DMA busy-waits (nic_tx_packet, nic_rx_header,
nic_rx_packet): an unbounded spin on SFR_NIC_CTRL freezes the entire
main loop (timers, HTTP, ARP) if the ASIC ever fails to consume a
transfer; give up after ~65k polls and drop the frame instead.

Hardware-verified end to end: with priority 15 against a live RSTP
bridge the uplink died 6 s after "stp on" and the network recovered BY
ITSELF 66 s later (trip at 45 s + neighbour release), fsT=1, LACP and
LAN intact. Telemetry via syslog-to-edge-port host confirmed the full
chain: countdown 44->4, trip, hello TX stopping at the trip.

(cherry picked from commit 1fa9775156fd6d7ebfdda2382f73430b86601230)
2026-08-18 23:28:26 +02:00
d00f 267e371d3c stp: BPDUs finally reach the wire (CPU tag flags + management VLAN)
Two TX bugs meant our BPDUs NEVER left the switch as valid STP frames -
on the wire they appeared as ethertype 0x8899 (the raw Realtek CPU tag)
and were flooded to all ports instead of directed. Every earlier root
election was a solo act: no other bridge ever saw us. Both are the same
bug classes fixed for LACP earlier:

- rtl_tag.flags was written raw (0x0020); like every other tag field it
  must go through HTONS, otherwise the bits land in the wrong byte
  (0x2000 = EFID), the ASIC fails to parse the tag and floods the frame
  with the 0x8899 header still attached.
- With a management VLAN set, tcpip_output() splices an 802.1Q tag after
  the SA, again shifting the CPU tag out of the parsed position. BPDUs are
  link-local and must egress untagged: suppress the VLAN insert per frame,
  exactly as lacp_send() does.

Hardware note discovered while fixing this: RTL_TAG_KEEP on an LLC/802.3
(length-field) frame makes the ASIC drop it entirely - the same flag works
fine on ethertype frames (LACP). So BPDUs use LEARN_DIS only.

Verified on the wire (tcpdump on the peer): clean "802.3 ... LLC, dsap STP
0x42 ... Rapid STP, bridge-id 8000.<our mac>" at the hello interval, sent
directed (no flood), management HTTP unaffected, LAN at 0% loss throughout.

(cherry picked from commit 4a41a292a9ab88d4fb05a8481ad28f8ffcfd9bc4)
2026-08-18 23:28:25 +02:00
d00f 9702f8e4c4 stp: reject port sub-commands with a missing argument
"stp port 7 edge" (no value) passed the cmd_words_len < 4 check and then
cmd_compare(4, ...) read a stale word left over from the PREVIOUS command
line - cmd_words_b is not cleared between commands - so the sub-command
could randomly match whatever was typed before. Require 5 words for every
per-port sub-command that carries an argument (everything except on/off).

(cherry picked from commit 1210f4f9257b14c31ad653fc7616ef403a494d28)
2026-08-18 23:28:24 +02:00
d00f 524c37d7c7 stp: full RSTP configuration (bridge + per-port), CLI + GUI + persistence
Implements the standard 802.1D-2004/802.1w configuration surface:

Bridge:  priority (0-15 x4096), hello time, max age, forward delay,
         force-version (RSTP v2 / STP-compatible v0 Config BPDUs), tx hold
         count (per-port per-second BPDU budget).
Per port: enable, admin edge (forwarding immediately - no listen gap),
         auto edge (forwarding after 3 s of BPDU silence; DEFAULT, so
         host-facing ports no longer take the full forward delay),
         path cost (0=auto/20000), port priority, BPDU guard (port disabled
         on BPDU receipt), root guard (never accept a better root on the
         port), BPDU filter (no BPDUs in or out).

Engine additions: root max-age expiry (reclaim the tree when the root goes
silent), root path cost accounting (rx cost + root-port cost, advertised in
our BPDUs), loop detection (our own BPDU coming back blocks the port for a
listen period), topology-change counter, approximated per-port roles
(Root/Designated/Alternate) for diagnostics.

CLI: "stp prio|hello|maxage|fwd|txhold|version ..." and
"stp port <n> on|off|edge|cost|prio|guard|filter ..." (stp_parse, delegated
from cmd_parser); all forms accepted by the startup-config validator so the
whole configuration persists. /stp.json now reports config + status; the
Spanning Tree page exposes everything with immediate-apply controls and live
state/role columns (edit-in-flight guard against the 2 s refresh).

8051 memory: the module moves to code BANK2; internal-RAM pressure from
cross-bank calls resolved by xdata loop iterators/scratch, __reentrant on
the small helpers, and moving httpd's header-pointer globals to xdata.

Verified on hardware (SWTGW218AS): defaults land per standard; priority and
hello change live; admin-edge ports (the LACP bond uplinks) keep the LAN at
0% loss THROUGH "stp on"; auto-edge ports forward after 3 s; a port that
heard real BPDUs (a VM bridge behind physical port 6) correctly declined
auto-edge, sat out the full listen period and became Designated; tc counts
promotions; we win the root election at priority 16384 vs 32768.

(cherry picked from commit 09a34dc6acdc81ab9cab0727d2f4a59c68131a3e)
2026-08-18 23:28:23 +02:00
d00f b5387016fa gui: Spanning Tree page (config + live status)
Add a Spanning Tree page: an on/off toggle driving the existing "stp"
command over /cmd, and a live status section fed by a new /stp.json
endpoint - the elected root bridge (priority + MAC), our path cost,
whether we are the root, and the per-port STP state read live from the
ASIC's MSTP register (same 2-bit encoding stp_setup() writes). Ports are
reported by their physical numbers.

Recovered-from: 3132319, 9365c86
2026-08-18 23:27:09 +02:00
d00f 5c881da4fe stp: stop forcing the SFP port to forwarding in the CPU-port mask
The "do not block the CPU port" mask 0x0f covers bits 3:0 of MSTP_STATES
byte 1, which is ports 8 AND 9 - so stp_setup unconditionally forced
port 8 (a real front port, the SFP uplink on SWTGW218AS) into forwarding
and it could never be blocked. The CPU port alone is bits 3:2 = 0x0c.
2026-08-18 23:27:08 +02:00
d00f 0df699659a stp: actually promote ports out of blocking; calibrate timers
"stp on" put every port into blocking (stp_setup, port_timers = "10 s") but
nothing ever counted those timers down: stp_timers() only sent hello BPDUs.
On a network with no other (R)STP bridge - i.e. nobody sends us BPDUs - every
port therefore stayed blocking FOREVER and enabling STP took the whole
network down until "stp off".

- stp_timers(): count port_timers down; when a port's listen period expires
  with no better root heard, promote it to forwarding in MSTP_STATES (we are
  the designated bridge on that port).
- Calibrate the tick constants to the real stp_timers() rate (~64 Hz: main
  loop ~256 Hz / (STP_TICK_DIVIDER+1)): TIME_HELLO 0x200->0x80 is an actual
  2 s hello, port_timers 0xa00->0x280 an actual 10 s listen period. Measured
  before the fix, ports converged only after ~40 s.
- Move struct bridge into rtl837x_stp.h and export root_bridge/-_cost for
  the web UI status endpoint.

Verified on hardware: "stp on" -> ports report Blocking, after the 10 s
listen period all ports promote to Forwarding and LAN connectivity returns;
"stp off" restores forwarding immediately. We elect ourselves root (weRoot)
with no other bridge present.

(cherry picked from commit 8537a15ca254b2122272b20bec7a66426e86df4b)
2026-08-18 23:27:08 +02:00
d00f 032305ad3a stp: move the STP module to code bank 2
The always-mapped common area is nearly full (349 bytes free before this
change), and the STP state machine that follows does not fit there. Move
the module to BANK2 next to the other protocol code; its public entry
points are already __banked, and cmpMAC/stp_cnf_send have no callers
outside the file.
2026-08-18 23:27:07 +02:00
d00f b16c7d9723 common: define the RTL frame-tag flag bits shared by STP and LACP
The rtl_tag `flags` word (LEARN_DIS, KEEP) and the `pmask` ALLOW-bit
semantics are properties of the RTL8_4 CPU tag, not of any one protocol:
STP injects BPDUs with LEARN_DIS set and LACP emits slow-protocol frames
the same way. Define them once in the shared header, with the HTONS
byte-order caveat documented, so every feature that hand-builds a CPU
tag frame uses the same constants.

(cherry picked from commit 7f905b3e90f9bc5df586a5138723d97edf3d6aaf)
2026-08-18 23:27:06 +02:00
d00f 6e1f199571 stp: contain BPDUs to the CPU while STP runs
With STP enabled the switch is a participating bridge, so BPDUs must be
consumed, not relayed - yet the reserved group 01:80:C2:00:00:00 was
flooded across the VLAN just like any multicast, leaking every BPDU to
all ports (the same defect class as the LACPDU flood addressed in the
LACP branch, PR #299).

On stp on, write a CPU-only static L2 multicast entry for the BPDU group
per VLAN: BPDUs can arrive VLAN-tagged and classify into the tag's VID,
so cover every VLAN present in the VLAN table plus every port's PVID for
the untagged case.

On stp off the same entries are retargeted to all ports + CPU, restoring
the previous flood behaviour: an unmanaged switch is expected to be
transparent to BPDUs so the surrounding spanning tree can span through
it, and dropping them instead would partition that topology.

Note: with STP enabled the ports start out blocking, which also stops
egress of CPU-originated LACPDUs, so an active LACP aggregate drops
until the ports reach forwarding - a pre-existing interaction, not
changed here.
2026-08-18 23:27:06 +02:00
d00f 5329193987 port: add a helper to steer a link-local group via a static L2 entry
port_l2mc_set() writes a static L2 multicast entry for a reserved group
01:80:C2:00:00:<last> in a given VLAN with a given member portmask.

Slow-protocol frames must reach the management CPU without being flooded
to other ports, but the RMA "trap" action cannot deliver to the internal
NIC on this hardware - its destination is an external CPU attached to a
physical port. The working alternative is to keep the RMA action at
"forward" and constrain the egress with a static entry: the forward
lookup then hits the entry's member mask instead of the VLAN flood mask.
Hardware-verified on a SWTGW218AS in both directions: a mask without the
CPU bit stops delivery to the CPU, a CPU-only mask delivers with no port
egress. Lookups are IVL, so callers add one entry per VID they care
about; rewriting the same MAC+VID replaces the entry in place.

Used by the BPDU containment in the next commit; the pending LACP branch
adopts it for 01:80:C2:00:00:02 the same way.
2026-08-18 23:27:05 +02:00
logicog 52cf759bec Merge pull request #355 from bloqaudio/fix/l2-static-display
httpd: read the static flag of an L2 entry from the byte that holds it
2026-08-18 19:44:05 +02:00
René van Dorst cbbc6160f4 Merge pull request #357 from bloqaudio/fix/ingress-port-bound
cmd_parser: reject port 0 in the ingress command
2026-08-18 09:05:06 +02:00
bloqaudio a8d3b7d39a cmd_parser: reject port 0 in the ingress command
The single-digit arm of the ingress parser guards with p - '1' > 9,
which no digit can satisfy: the largest, '9', gives 8. The digit that
needed rejecting is '0', which gives -1 and indexes one byte before
phys_to_log_port, so "ingress 0 t" reads out of bounds and applies the
ingress mode to whatever port number that byte happens to contain.
Ports are 1-based, so reject anything below '1'; values above '9' are
already excluded by the isnumber check before this.
2026-08-17 17:19:46 -05:00
René van Dorst 80095ba617 Merge pull request #354 from DrDoof/fix/isolate-cpu-port
port: reject the CPU port in isolate instead of refusing it silently
2026-08-17 18:31:25 +00:00
bloqaudio a3d1a35e2f httpd: read the static flag of an L2 entry from the byte that holds it
The MAC table listing tests bit 0 of byte 2 of the third table data
word for the static flag, but the flag lives in bit 0 of byte 1: an
entry written with byte 1 bit 0 set survives the aging engine
indefinitely where an identical entry without it ages out, and reads
back with exactly that bit set through both the address and the
next-entry read methods. Byte 2 of that word reads zero for learned and
static entries alike, so every entry has always been listed as learned
and a static entry has never been visible as such in the table listing.
2026-08-17 12:17:42 -05:00
bloqaudio 8c3df643d0 ports: show the devices connected behind each port
Adds a Connected devices column to the Port Configuration table, fed by
the shared walkL2() helper. A port with a single MAC behind it shows the
MAC directly; several are shown as a count with the full list in the
cell tooltip, since picking one of many to display would suggest it was
"the" device. The walk restarts 15 seconds after each completion and
starts 3 seconds after page load to stay clear of the initial status and
MTU requests. A walk that ends without ok keeps the previous cells,
since an incomplete table would blank ports that have devices behind
them. Translations for the three languages included.
2026-08-17 10:49:39 -05:00
eraiza0816 37eefb4b35 Merge upstream/main into feat/webui-compression
Resolve the conflicts against the 41 upstream commits since 52692d0:

- Makefile: keep the upstream parallel-build fixes (grouped html_data
  target with order-only deps, create_build_dir, crtbank, deterministic
  BUILD_DATE, atomic version.h write) and keep the minify + gzip
  pipeline (-z, html_min, .css/.ico in the html file list) on top.
- html: l2.js stays removed; port the upstream L2 fixes into the
  single-page main.js (walkL2() with empty-reply and 4096-entry cap
  handling, 3 retries on error).  The L2 poll now re-schedules itself
  after each walk and stops when the section is hidden.
- httpd/httpd.c merged cleanly (gzip Content-Encoding alongside the
  upstream GET walk bound and counters.json port check).
- doc/webui-compression.md: describe the upstream version.h write.

Verified: KP_9000_6XHML_X2 and PCB_K0402WS_V3 build sequentially and
with make -j8; all 8 embedded files decompress byte-identically;
httpd_sim serves main.js/index.html and the L2 JSON endpoint.
2026-08-17 22:53:06 +09:00
eraiza0816 3f5093f180 Delete: go-lang 2026-08-17 22:18:08 +09:00
d00f 47b3ee60b6 port: reject the CPU port in isolate instead of refusing it silently
parse_isolate() accepted a two digit port and mapped it to logical port 9,
the CPU port, while port_isolate() and port_isolation_get() both refuse
anything above machine.max_port. Setting the isolation of the CPU port was
therefore declined without a word and reading it always answered no
members, whatever the hardware held.

Bound the port to the front panel, so the command says what it does. The
digit is checked before it indexes phys_to_log_port[], which a non numeric
argument used to read past.
2026-08-17 10:53:17 +02:00
logicog 59d20ed9b6 Merge pull request #339 from plaes/parallel-build
Fix parallel build
2026-08-17 08:11:25 +02:00
logicog f41ed2b943 Merge pull request #332 from DrDoof/fix/igmp-enable-gate
igmp: only hand reports to the CPU while snooping is on
2026-08-17 07:58:18 +02:00
d00f 56fa96c494 igmp: only hand reports to the CPU while snooping is on
handle_rx() dispatched to igmp_packet_handler() on the destination
address alone, so an ordinary IGMPv3 report off the wire reached the
handler and could write a table entry whether or not anyone had asked
for snooping. The STP branch right above it is gated on stpEnabled;
this brings the IGMP branch in line.

Snooping state lived only in the per-port registers, and the receive
path cannot afford to read one per packet, so the flag shadows it:
igmp_enable() sets it, igmp_setup() clears it, and igmp_setup() runs
from both the boot path and "igmp off".

While here, igmp off becomes an explicit subcommand instead of the
fall-through, and an unrecognised igmp subcommand prints the usage
line rather than silently turning snooping off.

Six bytes of BANK1 and one of xdata, no internal RAM.
2026-08-16 23:42:46 +02:00
René van Dorst 6bfcbd2f9a Merge pull request #349 from DrDoof/fix/lag-cmd-bounds
lag: number the groups from one and bound what the command is given
2026-08-16 06:48:28 +00:00
d00f fa7895ad62 doc: the aggregation example used group zero, which no longer parses
The command now numbers groups the way 'lag show' prints them, so the
walkthrough would have failed at its first step.
2026-08-16 04:10:03 +02:00
d00f d405dd7776 port: let a pvid name an aggregation group
VLAN membership, PVID, egress tagging, isolation and MTU are all per
physical port in this ASIC, so nothing stopped a member of a working
aggregation group being given a different PVID from its peers. The group
then forwards asymmetrically depending on which member the hash picks,
and no part of the firmware says a word about it.

port_lag_of() answers which group a port belongs to, reading the
membership through the shared reader rather than a fourth private copy.
port_pvid_set() expands to the whole group when the port it is given is
a member, and ports outside a group keep the path they had.

This is the second and third of the three steps set out in #347. I said
there that the lookup would come when something needed it, which had it
the wrong way round: nothing in the tree asks which group a port is in,
so the lookup only earns its place alongside a caller. PVID is the
smallest such caller, and the rest of the per port settings can follow
the same shape once this one is agreed.

Built for SWTGW218AS and KP_9000_6XHML_X2 on sdcc 4.5.0.
2026-08-16 01:52:03 +02:00
d00f 1ed8b131bc httpd: keep the send_l2 flags in bit memory
The two flags added with the JSON fix sit in data, where internal RAM is
full enough that this branch stopped linking for some toolchains. __bit
puts them in the bit area instead and hands three bytes back to the
stack: SSEG goes from 131 to 134 on SWTGW218AS.

Patch by vDorst on the pull request.
2026-08-16 01:23:56 +02:00
d00f 9ada6adad7 lag: number the groups from one and bound what the command is given
lag show has always printed the groups as 1 to 4 while lag <n> took the
number literally, so typing what you saw configured the group beside it.
Both lag and lag hash count from one now, matching how ports are numbered
everywhere else, and reject anything outside 1 to 4. Subtracting '1' makes
0 wrap well past three, so one test covers both ends.

The port argument indexed machine.phys_to_log_port, which holds nine
entries, before it was checked, and a two digit argument reaches 109. It is
bounded before the table is touched rather than after.

port_lag_members_set() and port_lag_hash_set() complained about a group out
of range and then wrote the registers anyway, past the four the groups
occupy. They return instead.

lag hash also read cmd_words_b[1] without checking a word was there, and
now shares the error path parse_lag() already had.
2026-08-16 01:08:30 +02:00
René van Dorst 719c6db228 Merge pull request #344 from DrDoof/feat/walk-l2
html: move the L2 table walk into a shared walkL2() helper
2026-08-15 21:38:43 +00:00
d00f a96fdfe10c html: move the L2 table walk into a shared walkL2() helper
Both the L2 page and the ports page in #335 need to page through /l2.json
and decode the same fields, and the second copy arrived carrying the two
bugs the first one had only just been fixed for. Rather than keep two
copies in step by hand, the transport and the decoding move to main.js,
which every page already loads, and each page keeps only what is its own.

walkL2(onDone) pages through the table once, parses idx and vlan out of
hex, maps the port to a physical number or to 'CPU', and calls
onDone(entries, ok). It stops on a wrapped index, an empty page or 4096
entries, all of which set ok. A page that comes back as anything other than
200, or with a body JSON.parse rejects, is asked for again at the same
index up to three times; only once those run out does the walk end with ok
clear, so a caller can tell a finished table from a partial one. l2.js
keeps the s and l to label mapping, since that needs the page's own
translations, redraws only when ok is set, and restarts the walk from its
callback either way.

Two things change while moving:

The next request goes out from the previous reply rather than from a
setInterval that fires whether or not the last one came back. The httpd
serves one connection at a time, so a timer that outruns the responses only
queues work it cannot use.

A walk that reaches 4096 entries hands over what it collected. Before it
threw the entries away and cleared its own interval, which left the page
unable to refresh again until it was reloaded.

The retry is not a new idea, it is the old behaviour written down. The
previous code ignored anything that was not a 200 and let the interval ask
for the same index again, so a blip never disturbed the table on screen.
Dropping that on the way to a chained walk would have made every timeout
redraw the page with a truncated table, which at one connection at a time
is not a rare event.

Driven with a scripted server in node, running the helper itself rather
than a copy of it: an empty table gives 0 entries in 1 request; three pages
ending in a repeated index give 61 entries in 3 requests, asking for 0, 30
and 60; an empty page ends the walk after 2; a 500 and a malformed body are
each retried at the same index and then complete normally, asking 0, 30, 30
and 31; three failures in a row end the walk with ok clear and the 30
entries already collected; 4096 entries in one page end it with ok set; the
CPU port decodes to 'CPU'; vlan and idx come back as numbers.

main.js grows by 1331 bytes and l2.js loses 1039, so 292 bytes of flash.
Worth stating where they land: main.js is loaded by every page, so pages
that never walk the table now carry the helper too. That is the cost of
having the decoding exist exactly once, which is the point of the move.
2026-08-15 23:16:29 +02:00
d00f 1d1e33f4d5 sfp: notice when an I2C read fails
sfp_read_reg() waited for the transfer to finish and then read the output
register whatever the outcome, so an address nothing acknowledged came back
as an ordinary byte and no caller could tell it apart from data. The vendor
SDK looks at bit 1 of the control register for exactly this, and we did
not.

A failure now sets sfp_i2c_fail and the read returns 0xff, which is already
the value sfp_apply_quirks() reads as either a failed transfer or a voltage
the spec does not allow, so that test starts being true when it should be.
The insertion path and the sfp command clear the flag first and say so
afterwards, rather than presenting the bytes as though they came from the
module.

What this deliberately does not do is act on the failure. Skipping
sds_config() when the rate read failed is the obvious next step, but a
module that raises the bit spuriously would then never be configured at
all, which is worse than what happens today, and I have no way to judge how
often the bit is right. That decision belongs with someone holding the
board.

It also leaves the other half of the rewrite alone, reading and writing up
to sixteen bytes per transaction. doc/sfp.md describes only the single byte
path and does not name a length field, and guessing at a register I cannot
test is how the last attempt at this function went wrong.

40 bytes of the common segment, 51 of BANK2 and 1 of xdata, nothing in
BANK1 or internal RAM. Built for SWTGW218AS and KP_9000_6XHML_X2 on sdcc
4.5.0. Not tested on hardware: shorting the clock line, as in #342, should
now print the failure line instead of a plausible looking byte.
2026-08-15 23:14:53 +02:00
d00f 3fda9ccd86 html: handle an empty MAC table reply and fix the entry cap
l2.js reads the last index of a reply to know where the next page starts.
With the firmware side of this branch an empty table answers [], and
s[s.length-1] then throws on undefined. It used to answer commas with
nothing between them and throw in JSON.parse instead, so this is the same
case reaching a different line rather than a new one. An empty reply now renders what
has been collected and starts the next pass from zero.

The 4096 entry cap compared the array against the number instead of its
length, so it never fired: an empty array and a 5000 element one both
compare false. Comparing the length restores what the check was for.
2026-08-15 23:14:49 +02:00
d00f 3be6667789 httpd: emit valid JSON from send_l2
The MAC table listing wrote its separator once per iteration rather than
once per object. An entry the table engine reports as invalid produces no
object, so it contributed a bare comma, and two in a row give ",," which
JSON.parse rejects. The whole table then fails to load, not just the row
that was missing. The separator now goes before each object and the
closing bracket after the loop, which is the shape send_vlanlist already
uses further down the file.

The next index for an invalid entry was computed as h | low + 1, and the
addition binds tighter than the or. That agrees with (h | low) + 1 except
when the low byte reads 0xff and bit 8 of the index is already set, eight
of the 4096 combinations. There the result is the start of the current
block of 256 rather than the start of the next one, so the walk repeats a
block it has already covered. Reading the index once after the branch
rather than once in each arm removes the second copy of that expression
along with the bug.

The VLAN now comes first in each object. It is taken from the same
L2_DATA_OUT_B read that decides whether the entry is valid, which saves
reading that register a second time. The page addresses the fields by
name, so the order they arrive in does not matter to it.

A bound check on the output buffer goes in for consistency with
send_vlanlist. Thirty entries of at most 74 bytes plus the brackets fit in
the 2500 byte buffer with 179 to spare, so nothing changes today, but the
margin was nowhere stated and L2_MAX_TRANSFER is a tunable.

5 bytes of BANK1, nothing in BANK2, xdata or internal RAM. Built for SWTGW218AS
and KP_9000_6XHML_X2 on sdcc 4.5.0.
2026-08-15 23:14:48 +02:00
René van Dorst 5103d1c168 Merge pull request #338 from DrDoof/fix/lag-hash-default
port: make the trunk hash default reachable again, and per group
2026-08-15 21:01:09 +00:00
René van Dorst ce85576882 Merge pull request #346 from DrDoof/fix/crtstart-home
Rename crtstart.asm to crtbank.asm
put the bank switching helpers in HOME-code location
2026-08-15 20:33:54 +00:00
d00f 59c60504e7 crtbank: put the bank switching helpers in HOME
__sdcc_banked_call and __sdcc_banked_ret were assembled into GSFINAL, which
sits in the startup path: GSINIT ends exactly where GSFINAL begins, so the
processor falls into it rather than being sent there. It works today only
because this object comes after every C object on the link line, so the
LJMP to __sdcc_program_startup is laid down first and the helpers land
behind it. Reordering that line, or moving main() into another module,
would put the helper body at the fallthrough address instead, and the board
would not come up out of a build that reports nothing wrong.

SDCC's own crtbank.asm declares the area order and then puts both symbols
in HOME, so the file takes that name and that preamble as well.

GSFINAL now holds the three byte jump and nothing else. Of 401 symbols 17
change address, every one in the startup region, and between the reset
vector and 0x0094 not a byte differs, so no interrupt vector is disturbed.

Run on a SWTGW218AS: it came back after about 42 seconds reporting the new
build, with its stored configuration byte identical and every link at the
speed it had before.
2026-08-15 22:18:13 +02:00
d00f a3c586ef38 port: make the trunk hash default reachable again, and per group
Wrapping REG_SET restored the guard in front of the hash default, and
that exposed three things about the line it guards.

The test was against zero. The register does not read zero: it comes out
of reset holding source port number plus both MAC fields, both IP fields
and the L4 source port, which the header now names LAG_HASH_RESET.
Measured on an SWTGW218AS, where all four groups read 0x3f after a cold
boot and a value written before a power cycle is gone afterwards. With
the guard working and the test unreachable, the default would never be
installed, where before it was installed on every call. Testing against
the reset value restores the intent, and zero is still accepted in case
another device does reset that way.

The write went to the base address while the read that decides it used
the group offset, so a group other than zero was tested and group zero
was written. Both ends use the offset now.

The range check printed a complaint and carried on. It returns, which
matters more now that the hash write also uses the group number to build
an address.

Thirty bytes of BANK1.
2026-08-15 22:01:08 +02:00
René van Dorst 8bc530d3d0 Merge pull request #347 from DrDoof/feat/lag-members-get
port: read a trunk's members through one function
2026-08-15 19:23:50 +00:00
d00f f2c6ac01d9 port: read a trunk's members through one function
The member mask of an aggregation group is decoded by hand in two places,
the lag command and the JSON behind the aggregation page, and every branch
that touches trunks adds another copy.

port_lag_members_get() sits next to port_lag_members_set() and both readers
call it. It answers from the hardware, so it covers a group configured with
lag and one a protocol brought up, without either having to say so.

It reads through reg_read() rather than reg_read_m(), so sfr_data is left
alone. Neither caller looked at it afterwards; both read the hash register
next.
2026-08-15 20:55:56 +02:00
René van Dorst 94d9f2c8e8 Merge pull request #341 from DrDoof/fix/counters-port-index
httpd: check the port index /counters.json is given
2026-08-15 07:52:54 +00:00
d00f 0503e7952d httpd: check the port index /counters.json is given
The handler took one raw character of the request line and passed it to
send_counters(), which uses it to index machine.phys_to_log_port. That
array has nine entries and the character is whatever the client sent, so
the read ran up to 246 entries past the end and the result went on to
STAT_GET as a port number. is_word() accepts any request whose name is
followed by a question mark, so nothing constrained the byte to a digit.

Bounding it where it is read keeps the check beside the assumption it
protects and needs nothing from the machine description. sdcc leaves
plain char unsigned and the subtraction wraps in eight bits, so a byte
below '0' comes out above 200 and one upper test covers both ends:
exactly '0' to '8' now reach send_counters. The compiled test is
add a,#0xf7 followed by jnc, which I read back out of the assembly rather
than assuming.

Out of range answers 400 by the path the other malformed requests already
take, rather than an empty array. An empty array would have been worse
than useless here, since the statistics page calls BigInt on the first
element before it looks at the length. The page asks only for index zero
to the port count minus one, so nothing that answered before stops
answering, and a non-200 reply makes its handler do nothing at all.

11 bytes of BANK1, nothing in the common segment, BANK2, xdata or
internal RAM. Built for SWTGW218AS and KP_9000_6XHML_X2 on sdcc 4.5.0.
2026-08-15 00:49:21 +02:00
René van Dorst 787d593996 Merge pull request #337 from DrDoof/fix/igmp-dup-loop
igmp: drop the duplicate port configuration loop
2026-08-14 22:12:33 +00:00
René van Dorst db2a541e5f Merge pull request #331 from DrDoof/fix/reg-macros-braces
regs: wrap REG_SET and REG_WRITE, and fix what that uncovers
2026-08-14 21:29:03 +00:00
Priit Laes 11be13fe53 build: Fix double generation of html_data
Make supports grouped target which runs once for all listed targets.
2026-08-14 11:16:05 +03:00
Priit Laes 65f8afb908 build: Remove undefined html variable
HTML target references $(html) which was never defined, which
causes find to run through the entire source tree.
2026-08-14 11:16:05 +03:00
Priit Laes 0126b159bf build: Make create_build_dir PHONY and add order-only deps
Mark create_build_dir as PHONY so directory creation is never skipped.
Also add html_data.h as order-only prerequisite to the .c pattern rule,
fixing another the race where httpd/httpd.c and httpd/page_impl.c are
compiled before the generated header exists.
2026-08-14 11:14:59 +03:00
Priit Laes 31024388ba build: Fix tools dependency for parallel builds
Replace file-path prerequisite tools/output/fileadder with order-only
dependency on the tools PHONY target.

Also add tools as order-only dependency to the final .bin target which
invokes all of the tools.
2026-08-14 11:14:29 +03:00
Priit Laes 9f2d6b72b7 build: Fix version.h race condition in parallel builds 2026-08-14 11:13:17 +03:00
d00f 5c5dcb5209 igmp: drop the duplicate port configuration loop
igmp_setup() writes the per-port IGMP configuration twice, once with the
value spelled out and once with the same number assembled from the
constants: IGMP_MAX_GROUP | IGMP_PROTOCOL_ENABLE | IGMP_FLOOD is exactly
0x00ff7c15. The second loop carries the comment block explaining the bit
layout, so the literal one is the one to drop.

Today the cost is one redundant register write, because REG_SET is not a
single statement and the unbraced loop body only ever reaches index
machine.max_port + 1. Once the macro is wrapped it becomes one redundant
write per port on every boot and on every "igmp off", which is what makes
this worth removing rather than leaving.

Fifty nine bytes of BANK1 on SWTGW218AS, nothing anywhere else.
2026-08-14 07:39:24 +02:00
d00f be6d47a3ab regs: wrap REG_SET and REG_WRITE in do { } while (0)
Both macros expand to a run of statements joined by backslashes with
nothing around them, so as the unbraced body of an if or a for only the
first assignment belongs to that body. The other three and the
reg_write() call sit after it and run once, unconditionally, with
whatever the loop counter ended on. Wrapping each macro into a single
statement is what every call site already assumes it to be.

This hands the compiler no new room around the SFR writes, which is worth
showing rather than asserting. Building the whole image before and after
and comparing the generated assembly module by module, with label
numbering, block scope suffixes and the version string normalised away,
three modules differ: rtl837x_igmp, rtl837x_port and rtl837x_leds. Ten
modules call these macros, so the other seven come out identical, and so
does everything else in the image.

Those three differ because they hold the five unbraced uses. Two of them
want a fix rather than only the brace, and that is left to the commits
that follow.

Six bytes of BANK1 on SWTGW218AS, nothing anywhere else.
2026-08-14 07:34:42 +02:00
d00fandd00f 4ff009dbfc uip: cap TCP MSS to 1460 to survive jumbo-MTU clients (#298)
* uip: parenthesise UIP_LLH_LEN

The macro expands to a bare sum, so wherever it is subtracted the second term
gets added instead. UIP_TCP_MSS - and with it UIP_RECEIVE_WINDOW - therefore
comes out 24 bytes above the buffer's real capacity. UIP_APPDATA_SIZE and
UIP_REASS_BUFSIZE are wrong the same way, though neither is reachable today.

The additions, uip_buf[UIP_LLH_LEN] and friends, were right by luck.

* uip: keep the advertised MSS below the buffer edge

Deriving the MSS straight from the buffer size makes the switch advertise
exactly the segment that fills uip_buf to its last byte, and a peer that takes
it literally corrupts every large upload: the firmware image arrives fully
acknowledged, with no retransmissions on the wire, yet the CRC over the
streamed body never matches and the flash write is abandoned.

Isolated by changing nothing but the segment size, same buffer and same file:
1490-byte segments fail four times out of four, 1460-byte segments succeed,
745-byte segments succeed. Linux halves its segments against a window this
small, so only macOS on a jumbo link ever produces a full-size segment - which
is why the failure hides so well.

Where exactly the full segment breaks the stream is not pinned down yet; until
it is, the advertised MSS stays a step below the edge.

* uip: size the buffer to the largest frame the CPU port accepts

UIP_TCP_MSS derives from UIP_CONF_BUFFER_SIZE, and the buffer was large enough
for frames the hardware will never deliver, so the switch advertised a segment
size no peer could usefully reach. A client on a jumbo-MTU link took it at its
word and the oversized replies went nowhere.

Size the buffer to the ingress limit instead. ICMP bypasses MSS and so probes
the hardware directly: on a SWTGW218AS a 1502-byte payload is answered and 1503
never arrives, which puts the largest frame the NIC hands us at 1556 bytes of
uip_buf. UIP_TCP_MSS then derives to 1490, the same edge measured over TCP.

Frames above the limit are dropped by the NIC rather than written to the
buffer - an 8 kB ping leaves the switch untouched - so nothing overruns it.
Frees 644 bytes of XDATA.

* uip: trim these comments, one of which had stopped being true

The note above UIP_CONF_BUFFER_SIZE claimed the MSS derives from it as 1490.
It does not: the commit that follows pins the MSS at 1460 on purpose, a step
below that ceiling, because a segment filling the buffer to its last byte
corrupts large uploads. Left as it was, the file argued with itself.

Both blocks are shorter now. What justifies the numbers stays, which is the
ICMP measurement behind 1556 and the four-out-of-four failure behind 1460.
What went is the storytelling around them, which belongs in this thread rather
than in a config header.

* uip: derive the MSS from the buffer again, minus explicit headroom

The review asked why the buffer size and the MSS are both set by hand when one
used to follow from the other. They answer different questions, but the gap
between them is a number in its own right, so it gets a name now:
UIP_CONF_BUFFER_EXTRA, and UIP_TCP_MSS goes back to being derived.

The headroom is where the measurement lives. A segment that fills uip_buf to
its last byte corrupts large uploads: with nothing but the segment size
changing, 1490 fails four times out of four and 1460 succeeds. With the buffer
sized to the frame the NIC accepts, an extra of 30 lands on 1460.

Deriving it the other way round does not work. Sizing the buffer from a 1460
byte MSS gives 1526, which is 30 bytes under the frame the NIC actually
delivers. A 1502 byte ICMP payload occupies 1556 bytes of uip_buf and is
answered today, and it would stop fitting.

The generated image is byte for byte the same as the one with 1460 written out,
so the expression lands on the value that was measured.

---------

Co-authored-by: d00f <tokyusho@chatik.pl>
2026-08-14 06:42:36 +02:00
bloqaudio 0998b7381f machine: add PCB SWTG018AS-V2.1.0 variant of the SWTGW218AS
The SWTGW218AS label covers more than one PCB. On boards with the
SWTG018AS-V2.1.0 silkscreen the SFP module-detect is GPIO38 (no LOS pin
wired) and the LED block is wired differently, including a bi-color SFP
LED (green up to 2.5G, blue at 10G) that the LED-set encoding cannot
express. With the existing MACHINE_SWTGW218AS definition these boards
never detect an SFP module, so an SFP-uplinked switch comes up with no
working uplink.

Add the variant as its own machine define named by the PCB marking,
leaving MACHINE_SWTGW218AS unchanged for boards that match its wiring.
The LED register values are taken from the stock firmware; the PIN_MUX_0
write routes the blue-LED pin to the LED controller, without which no
LED register value can drive it. PIN_MUX_1/2 stay untouched so SFP
detect and i2c remain GPIOs.
2026-08-13 16:19:25 -05:00
René van Dorst 0c339e9123 Merge pull request #328 from DrDoof/fix/httpd-get-path-walk
httpd: stop the GET request line walk at the end of the buffer
2026-08-12 09:28:22 +02:00
René van Dorst 75151e0b00 Merge pull request #319 from bloqaudio/pr/cmd-editor-overflow
cmd_editor: fix cmd_buffer overflow and CLI hang on long input
2026-08-12 09:26:17 +02:00
eraiza0816 4c4232cc07 fix(makefile): undo 2026-08-12 14:15:44 +09:00
d00f 43845b911b httpd: stop the GET request line walk at the end of the buffer
The POST path tests for a NUL before it looks at a byte. The GET path did not,
and is_separator() counts only space, tab, question mark and equals, so a request
line carrying none of those walks past the end of uip_buf and writes its
terminator into whatever xdata it happens to stop on.

Everything that is not a POST reaches that walk. The pointer advances past the
method before anything checks that the method was GET, so a TLS record sent to
port 80 by a browser trying https first is enough on its own, as is a port
scanner or a malformed line. The stop is wherever the first space, tab, question
mark or equals turns up in memory, which is why the symptoms are erratic.

Two bytes of BANK1. BANK2 and xdata do not move.
2026-08-12 03:22:01 +02:00
bloqaudio 0ed45e0710 cmd_editor: fix cmd_buffer overflow and serial-ring skip on long input
Typing or pasting a line of 128 characters or more into the CLI hangs the
editor and overruns cmd_buffer[128]: the length check did not reserve room
for the terminating NUL written on Enter, and on a full buffer the character
was retried via continue without advancing the serial-ring read pointer at
the bottom of the loop, so input processing never caught up again.

Cap the line at CMD_BUF_SIZE-1 and, when full, drop the character but fall
through to consume the ring byte instead of spinning on it. The functional
change is three lines; the rest of the diff is re-indentation of the
insert-and-echo block (git diff -w shows the minimal form).
2026-08-11 10:26:06 -05:00
logicog 5e30f8e6c0 Merge pull request #318 from bloqaudio/pr/mac-from-flash
machine: optionally read the factory MAC from flash
2026-08-11 14:32:32 +02:00
logicog 7f3c91ac0c Merge pull request #321 from bloqaudio/pr/dhcp-hostname-opt12
dhcp: announce the hostname via option 12
2026-08-11 14:30:21 +02:00
bloqaudio 3853a2701d machine: read a factory MAC from flash via mac_flash_offset
Adds an optional per-board mac_flash_offset. When non-zero, RTLPlayground
reads a 6-byte MAC from that flash address and uses it if it is a valid
globally-administered unicast address; otherwise it falls back to the
generated locally-administered MAC. Offset 0 (default for every board)
preserves current behaviour.

Enable it for the SWTGW218AS, whose stock firmware keeps the factory MAC
in nvcfg at 0x1FC000. RTLPlayground images and web upgrades only touch
flash below that region, so the factory MAC survives flashing.
2026-08-11 02:32:31 -05:00
bloqaudio 00a5f81871 dhcp: announce the hostname via option 12
Send the configured hostname on discover and request so the DHCP server
can register the device (e.g. in local DNS). Skipped while the hostname
is still empty, i.e. before set_hostname_default() has run.
2026-08-11 02:03:11 -05:00
logicog 53d865059d Merge pull request #320 from bloqaudio/pr/reproducible-build
makefile: derive BUILD_DATE deterministically
2026-08-11 08:50:05 +02:00
logicog b90aedd8f2 Merge pull request #316 from bloqaudio/pr/igmp-memset-cast
rtl837x_igmp: cast &entry to __xdata pointer in memset()
2026-08-11 08:43:28 +02:00
eraiza0816 c4928e5546 docs(webui): describe the single-page layout and updated numbers
The Web UI is now 8 files at 25,302 bytes gzip (23.0 % of the raw
109,814); section switching measured on a real device.
2026-08-11 06:38:55 +09:00
eraiza0816 67ae4598d9 feat(webui): turn the Web UI into a single-page application
index.html now contains all pages as sections (overview, ports, stat,
vlan, l2, mirror, lag, eee, bandwidth, system, update); the sidebar
switches between them via the URL hash (#/ports, #/vlan, ...).
login.html stays separate as the authentication gate.

A section's initialisation runs when the section is shown, its polling
intervals run only while it is visible, and re-entering a section
refetches the data; the checkbox builders (lag/vlan/mirror) and the L2
scanner were made idempotent so re-initialising cannot duplicate rows.

tools/minify.py: recognise regex literals - an escaped slash before the
closing delimiter was previously stripped as a // comment.

Measured (make MACHINE=KP_9000_6XHML_X2): 8 embedded files, 109,814 ->
97,730 -> 25,302 bytes gzip (23.0 %); the data block ends at 0x462d6
(~86 KB freed).  On a real PCB-K0402WS-V3: cold load ~450 ms, section
switches ~60-160 ms with no asset re-downloads, all sections render
without JavaScript errors.
2026-08-11 06:37:20 +09:00
bloqaudio 6b190eb9f3 makefile: derive BUILD_DATE deterministically
Honor SOURCE_DATE_EPOCH, falling back to the HEAD commit date and then
to wall-clock when git is absent. Same-commit builds become
byte-identical (BUILD_DATE is baked into the image and covered by the
trailing CRC), which makes it possible to verify that a binary matches
a source tree.
2026-08-10 15:55:08 -05:00
bloqaudio fae16dabf5 rtl837x_igmp: cast &entry to __xdata pointer in memset()
SDCC rejects memset() on the generic &entry pointer (error 78,
incompatible types). The ipmc/l2mc table entries are __xdata; pass an
explicit (__xdata uint8_t *) so the call resolves.
2026-08-10 15:54:08 -05:00
eraiza0816 b651890a3a docs(webui): update the compression numbers for the merged bundle
The bundle merge brings the Web UI to 28,200 bytes gzip (25.3 % of the
raw 111,280); the table of embedded files now has 18 entries.
2026-08-11 05:40:58 +09:00
eraiza0816 caa7874bb3 feat(webui): consolidate the page scripts into a single main.js bundle
All 17 page scripts (i18n, shared helpers, navigation and the per-page
scripts) are merged into one html/main.js that every page loads as its
only script, so the browser fetches it once and gzip can compress
across all of it.

The page-specific initialisation is guarded by the presence of the
matching page elements (it used to be selected implicitly by which
script a page loaded); the sidebar fill and the stat popup wiring moved
into DOMContentLoaded handlers because the bundle loads in <head>,
before the body exists.

tools/httpd_sim.h: BUFFER_SIZE 24000 -> 131072 so the simulator can
serve the larger bundle (the firmware is unaffected, it serves the
gzip'd bundle of ~17 KB).

Measured (make MACHINE=KP_9000_6XHML_X2): the JS drops from 23,302 to
16,948 bytes gzip; the whole Web UI from 34,784 to 28,200 bytes (25.3 %
of the raw 111,280); the data block ends at 0x46e28.  Verified with a
headless browser against httpd_sim and against a real PCB-K0402WS-V3:
all pages render, the sidebar and the port strip are drawn, no
JavaScript errors.
2026-08-11 05:40:01 +09:00
eraiza0816 53f33bd606 docs(webui): describe the minify + gzip asset compression
Pipeline, measured per-stage sizes (113,015 -> 101,498 -> 34,784 bytes)
and how to verify a build against the embedded image.
2026-08-11 03:06:45 +09:00
eraiza0816 57e3fce95e feat(webui): minify and gzip-compress the embedded web assets
Two build-time steps shrink the embedded Web UI (html/) from 113,015 to
34,784 bytes (30.8 %):

- tools/minify.py (new): string-literal-safe minifier that strips
  comments and indentation; comments are pure overhead on the wire, so
  the transfer size drops already before gzip (113,015 -> 101,498).
  Binary files (.ico) are copied through unchanged.  The output goes to
  output/html_min/, the new fileadder input.
- fileadder -z: gzip-compresses every added file (zlib gzip format,
  Z_BEST_COMPRESSION); the generated f_data entries carry a gzip flag.
  tools/Makefile links the tool against -lz.
- httpd: adds "Content-Encoding: gzip" for flagged entries; the 8051
  only copies compressed bytes, decompression happens in the browser.
  The JSON API stays uncompressed.

Fixes required to make the sizes deterministic:
- fileadder terminated files at data_read + 1, so the strlen()-based
  size computation depended on uninitialised buffer content; the NUL
  now lands directly after the content.
- version.h was .PHONY, so every build rewrote it while parallel builds
  could read a half-written copy.
- the file list was unscoped and missed .css/.ico; it is now scoped to
  html/ and covers all embedded types.

Measured with make MACHINE=KP_9000_6XHML_X2: data block end moves from
0x5b977 to 0x487e0 (~78 KB freed on a 512 KB image).  Verified with
MACHINE=PCB_K0402WS_V3 and on real hardware (PCB-K0402WS-V3).
2026-08-11 03:04:19 +09:00
René van Dorst 52692d05b1 Merge pull request #313 from DrDoof/fix/vlan-id-range
Reject VLAN IDs and numeric arguments the hardware cannot take
2026-08-08 20:51:43 +00:00
René van Dorst d364bd69e4 Merge pull request #306 from DrDoof/mgmt-vlan-gui
system: show and set the management VLAN in System Settings
2026-08-08 20:44:07 +00:00
René van Dorst 70ab7ff769 Merge pull request #307 from DrDoof/l2-sort-filter
l2: fix the CPU/SFP port label, add sorting and filtering
2026-08-08 20:29:50 +00:00
d00f e30976be76 httpd: move the management VLAN into /vlanlist
Review feedback on #306. The value used to ride in /information.json and
the picker fetched /vlanlist separately, so the page needed both requests
to mean anything. /vlanlist now answers {"mgmt":N,"vlan":[...]} and the
picker reads both from the one response. Both consumers in vlan.js were
taught the new shape, and /information.json no longer carries mgmt_vlan.

The truncation guard now reserves 141 bytes instead of 139: the closing
grew to two bytes with the wrapping object, and the comma in front of a
non-first entry was never counted, so the worst case could land one byte
past outbuf even before this change. char_to_html() does not check.

The comments added on this branch are gone as well, style.css and
system.js both, since these files are served byte for byte.

page_impl.rel stays at DSEG 5, OSEG 0, BSEG 3 and the image reports the
same 10183 bytes of XDATA before and after.
2026-08-08 18:26:49 +02:00
d00f 1a55a134c7 system: pick the management VLAN from System Settings
`vlan <id> mgmt` existed on the CLI, but nothing reported which VLAN
currently carries management, so the setting was invisible from the web UI -
the only way to find out was to read the startup config.

Report it in information.json and offer it where the other switch-wide
addressing settings live, between Gateway and Language, as a picker filled
from the configured VLANs. When management is untagged there is no VLAN to
select, so that state shows as a disabled entry rather than inventing an id
the switch would reject.

Confirm before applying, and say what will happen rather than echoing the
action back: the switch starts tagging its own frames, and if the port you
came in on does not carry that VLAN the page becomes unreachable and the way
back is the console. Cancelling puts the picker back where it was.
2026-08-08 18:26:49 +02:00
d00f 23f0ba995c css: align read-only values with the input fields
The System Settings rows all pair a label with a form control, except Model,
which is a bare span. The inputs carry padding and a left margin, so their
text starts about 32px further in than the model name did - close enough to
look like a mistake and far enough to see it.

Give read-only values a class with the same metrics instead of styling the
model specifically; any other value shown without a control gets the
alignment for free.
2026-08-08 18:26:15 +02:00
d00f 22a09bd06a l2: drop the comments and define the column list once
Review feedback on #307: these files are served from flash byte for byte,
so comments ride along on every page load. The three added in this branch
are gone, 357 bytes across l2.js and style.css. The column list existed in
three copies inside renderL2() and is now a single const.
2026-08-08 17:55:27 +02:00
d00f 930a08ec52 cmd: validate the port argument of mtu and pvid
Both handlers turned the first character of the port word into an index
with no check at all. "mtu 0 100" computes '0' - '1' = 255, reads far past
the end of phys_to_log_port[9], and writes the size to whatever register
0x1250 plus that garbage points at. "pvid 0 2" walks the same path into
port_pvid_set(). lag and vlan already validate their port arguments; these
two just did not.

The port now has to be a single digit 1 to 9, which is exactly the range
the mapping table holds. A second digit or a stray letter falls to the
usage message.

"mtu show" also gained the return it was missing: after printing the table
it fell through, derived a port from the word "show" and printed the
garbage byte before the length check stopped it.
2026-08-08 17:46:24 +02:00
d00f 1701d4dc53 cmd: refuse a management VLAN that cannot exist
"vlan 5000 mgmt" parked the management interface on a VLAN the table
cannot hold, which quietly cuts management off. IDs above 4094 now fall
through to the usage message. 0 still switches the management VLAN off,
which is the documented way to disable it.
2026-08-08 17:38:10 +02:00
d00f 1e19a9abe2 cmd: bound the PVID and say something when it is refused
"pvid 1 5000" packed 5000 into the 12-bit PVID field and truncated on the
way, so the port ended up with a PVID nobody chose. 0 and anything above
4094 are refused now, matching what the VLAN table can hold. A failed
parse used to be dropped without a word; both cases print the usage line.
2026-08-08 17:38:10 +02:00
d00f 1098e73337 cmd: stop "mtu" from acting on a value it failed to parse
The handler threw away atoi_short()'s return and leant on the range test
alone. The range test cannot tell a failed parse from a small number, so
"mtu 1 99999" stopped at the partial 9999 and went through as a number
nobody typed. With the parse result checked, a failure is rejected with
the same message as an out-of-range value.
2026-08-08 17:38:10 +02:00
d00f 99b0fc4b32 cmd: give mtu a lower bound as well as an upper one
"mtu 1 0" was accepted. The chip takes it verbatim, the port reports 0x0000
back, and it stops passing frames: on a live 2.5G LAG member the LACPDU
receives moved by 4 in fifteen seconds against 21 on the sibling port, and
the partner went expired. Restoring the size brought both back.

Nothing shorter than a minimum Ethernet frame is a usable maximum, so the
range is now 64 to 16383. The upper end is unchanged and still comes from
the width of the field the value is written into.

This also covers most of #312 by accident: "mtu 1 abc" leaves the parse
result at 0 and now gets rejected on the bound rather than reaching the
register. It does not cover all of it. The handler still ignores what
atoi_short() returns, so "mtu 1 99999" stops on a partial 9999 and goes
through as a number nobody typed.

The GUI is not affected either way, it offers a fixed list of sizes.
2026-08-08 17:30:25 +02:00
d00f 10d472d8a6 vlan: reject VLAN IDs the table cannot hold
vlan_create() and vlan_delete() wrote the ID straight into the table index
register. vlan_get() has refused anything >= 0xfff for a while, so reads were
guarded and writes were not: "vlan 4095 1 2" built an entry that no read path
can see, and IDs above that either miss the table or alias onto another VLAN.

Both writers now enforce the range vlan_get() already did, and parse_vlan()
rejects the same values with the usage message so the CLI says why. The check
sits after the "vlan 0 mgmt" branch, which legitimately takes 0 to switch the
management VLAN off.

Costs nothing in RAM: rtl837x_port.rel stays at DSEG 0, OSEG 5, and the image
still reports 10207 bytes of XDATA in use.
2026-08-08 17:13:28 +02:00
d00f 6254f44001 cmd: reject out-of-range numeric arguments instead of wrapping
atoi_short() accumulated into a uint16_t without checking, so "vlan 65540"
wrapped to 4 and edited VLAN 4 instead of failing. atoi_byte() had the same
hole with "300" landing on 44. Both now refuse the digit that would push the
value past its type, before it lands.

The partial result is deliberately left alone rather than zeroed. Zeroing
would give the function one tidy rule, every failure leaves 0, but a caller
that ignores the return would then write that 0, and 0 is not a harmless
number everywhere. Set as a port MTU it stops the port taking frames: I put
0 on a live 2.5G LAG member and its LACPDU receives moved by 4 in fifteen
seconds against 21 on the sibling port, with the partner going expired.
Putting the size back recovered both. A wrong number does less damage than
that, and the real fix belongs in the callers that ignore the return anyway.

The test sits inside the loop rather than after it, so no wider accumulator
is needed and the parser stays off the internal RAM budget.

056a30a on the branch in #303 fixes atoi_byte a different way, by widening
the accumulator. Whichever lands first, the other hunk should go.
2026-08-08 17:13:08 +02:00
René van Dorst c435838376 Merge pull request #308 from plaes/horaco-zx-sg4t2
docs: Add Horaco XZ-SG4T2 skeleton documentation and images
2026-08-08 12:08:27 +00:00
René van Dorst 3f7db7e430 Merge pull request #297 from DrDoof/hostname
system: configurable hostname + show model on the System page
2026-08-07 18:10:34 +00:00
René van Dorst f98fe32d0c Merge pull request #309 from plaes/http-security-fixes
httpd: Fix buffer offerflow in scan_header
2026-08-07 10:08:11 +00:00
Priit Laes d2e8ee0e41 httpd: Fix buffer offerflow in scan_header
Fixes #300
2026-08-06 15:24:09 +03:00
Priit Laes a9a455e447 docs: Add Puya P25D40SH flash chip to list of known supported 2026-08-06 10:57:49 +03:00
Priit Laes 3f26bf7348 docs: Use relative paths 2026-08-06 10:57:49 +03:00
Priit Laes ac302d4a67 Add barebones docs for already supported Horaco ZX-SG4T2 / WTG024AS-A-V2.0.1_4C_2SFP 2026-08-06 10:47:35 +03:00
Priit Laes 409bc58e17 docs: Add HC-SWTGW215AS to supported devices index 2026-08-06 10:13:29 +03:00
Priit Laes fbcec2cb5a docs: Fix capitalization/typos in company names 2026-08-06 10:06:24 +03:00
d00f ccb90ed16f l2: refresh the type column when a row is reused
The paint loop reuses existing rows and rewrites port, MAC, VLAN and the
delete button, but never the type cell - only the insert path set it. With a
fixed row order that stayed invisible, since a row usually landed back where
it was. Sorting moves rows, so every other column followed the data while
type kept the previous row's value, which made sorting by type look broken
when the sort itself was correct.
2026-08-05 21:42:54 +02:00
d00f 4389cfcd8b l2: show which column sorts and in which direction
Clicking a heading sorted the table but nothing said so afterwards - the
only feedback was the rows moving, which is no help when the sort key is a
column you are not looking at.

Give every sortable heading a permanent marker: a neutral double arrow when
it is not the sort key, up or down when it is. The marker sits in its own
span so the i18n pass, which replaces the heading text, does not wipe it.
2026-08-05 21:35:54 +02:00
d00f 5e901fb5c2 l2: sort and filter the forwarding table from its header
The table lists every learned and static entry in one flat block, which is
fine with a handful and unusable with a few hundred: finding out where one
MAC sits, or what a port has learned, meant reading the whole thing.

Make the column headings sort and give each one a filter box. Filters are
substring matches combined with AND, so "port 8 + static" is two keystrokes.
A counter above the table shows matched out of total, so a filter that hides
everything is obvious rather than looking like an empty table.

Sorting keeps the CPU entry from comparing as a number - it sorts last
instead of landing between ports 8 and 9, where a string-vs-number compare
would otherwise put it.

The filter inputs live inside the existing header cells rather than in a
second row: the paint loop addresses data rows as rows[i+1], and a second
header row would have shifted every one of them.
2026-08-05 21:21:03 +02:00
d00f 2974e4b66a l2: stop labelling the SFP port as CPU
The port column was mapped from logical to physical numbering first and the
CPU label applied afterwards, testing for port 9. On an 8+1 board the SFP
port maps to physical 9 as well, so every entry learned on the SFP was shown
as CPU - on this switch that was 26 of 30 entries.

Label the CPU while the number is still logical, before the mapping, so the
two cannot collide.
2026-08-05 21:21:03 +02:00
d00f 108ac9b8fc system: derive the default hostname after the startup config
Move the MAC-derived default name out of main() into its own function and
call it after execute_config(), returning early when the config already
set a name - a configured switch then does no work for it at all.

The body deliberately has no local variables. Locals here - counters and
pointers alike - land in the 8051's internal-RAM overlay, and on an image
with LACP and STP both enabled that overlay is exhausted: a loop makes the
linker fail with "Could not get 8 consecutive bytes in internal RAM for
area OSEG". Moving the code into its own function does not help, since the
overlay is shared across the whole image, and hoisting the locals to xdata
does not either, because itohex() is inline and brings its own frame. This
only shows up in an integrated build; the branch on its own links fine.

Suggested-by: vDorst
2026-08-05 16:48:36 +02:00
René van Dorst de3eca26c3 Merge pull request #301 from zytstudio/MACHINE_FG_4GT_2SX_V2_0
Add FG-4GT-2SX_V2.0
2026-08-05 08:53:02 +00:00
ZYT 8164166465 Fix mistake in comments 2026-08-05 11:15:48 +08:00
ZYT 87fe1e77ae Merge branch 'main' into MACHINE_FG_4GT_2SX_V2_0 2026-08-05 11:09:53 +08:00
ZYT a753f6f02e Add led colors and remove an unnecessary photo 2026-08-05 11:07:51 +08:00
René van Dorst c86d4b31f2 Merge pull request #304 from plaes/makefile
makefile: Make CC and ASM overridable via command line
2026-08-04 19:50:31 +00:00
Priit Laes 3c89b22207 makefile: Use origin check for CC variable
Make defines defaults for commonly used variables, therefore
we need to check first how the variable was defined. If `default`
value was used, override it with our own default.
2026-08-04 21:55:28 +03:00
Priit Laes bebe79c4c9 makefile: Fix possible shell injections in makefile 2026-08-04 18:53:23 +03:00
Priit Laes adb0b54691 makefile: Reorder sources to immediately fail on invalid MACHINE
Also group and reorder source files and move each of these to
separate line.
2026-08-04 17:57:53 +03:00
Priit Laes b99436543a makefile: Make CC and ASM overridable via command line
This makes it easier to run make without docker within
distros without editing Makefile. For example Fedora:
`ASM=sdcc-sdas8051 CC=sdcc-sdcc make`
2026-08-04 13:17:53 +03:00
d00f 1194823cd5 system: drop the defensive checks around the System page fields
The hostname and model fields were populated through element-existence
checks and empty-string fallbacks that the other fields on the page do
without. Assign them the same way.

Suggested-by: vDorst
2026-08-04 05:43:05 +02:00
d00f 94716ceb5d system: derive the default hostname from the MAC address
Every switch came up as "RTLPlayground", so several of them on one
network were indistinguishable until someone configured a name. Append
the last three MAC octets (e.g. RTLPlayground-1ef924) - unique in
practice, still recognisable, and any "hostname ..." line in the startup
config overrides it as before.

Suggested-by: plaes
2026-08-04 05:43:05 +02:00
d00f 13120127df cmd: make "hostname" report the name and reject stray arguments
Typing "hostname" on its own cleared the name: with no argument the copy
loop never ran and the terminating NUL landed at index 0. Report the
current name instead, accept exactly one argument to set it, and reject
anything longer - a name with spaces tokenizes into several words, and
silently keeping only the first one is worse than an error. Walk the
buffer with a pointer, which the compiler codes better than indexing.

Suggested-by: vDorst
2026-08-04 05:43:05 +02:00
ZYT 46037cd54d Add FG-4GT-2SX_V2.0 2026-08-02 06:46:52 +08:00
d00f e6cd362a1e system: show the hardware model on the System page
machine.machine_name is already reported in /information.json as hw_ver;
surface it read-only on the System Settings page so the exact build
target is visible in the UI, without adding a redundant JSON field.
2026-07-25 13:21:13 +02:00
d00f f21b3a32bd system: configurable hostname (device identity)
Add a device hostname settable from the CLI (`hostname <text>`) and the
System Settings page. The value is sanitized on ingest to JSON-safe
printable ASCII (<=23 chars), stored in a shared __xdata buffer, seeded
to "RTLPlayground" at boot, persisted through the startup-config, and
reported in /information.json. It lives in the common header so other
modules can advertise it (LLDP uses it as the System Name TLV).
2026-07-25 13:07:09 +02:00
logicog 35941dd19f Merge pull request #291 from DrDoof/fix/webui-login-cookies
httpd: fix browser login (cookie parsed by name, Connection: close, self-contained login page)
2026-07-25 11:09:19 +02:00
d00f 0f257d38df httpd: shorten comments per review
Keep only the non-obvious bits; the rationale for each change already
lives in the respective commit messages.
2026-07-25 10:39:32 +02:00
logicog 912e68d6eb Merge pull request #295 from sempr/main
Fix rate range for SDS_10GR return value
2026-07-25 08:21:53 +02:00
Sempr c473646389 Fix rate range for SDS_10GR return value
Broaden range of SFP+ rates mapped to SDS_10GR
A commonly used SFP+ form-factor 10GBASE-LR (Hisense LTF1303-BH+) reports 0x62(	BR Nominal: 9800MBd) at
power on and then switches to 0x64 after booting up.

0x62 -> 9.8gbps
0x63 -> 9.9gbps
0x64 -> 10gbps
0x6f -> 11.1gbps
0x70 -> 11.2gbps

These are outside of the previous 0x63-0x6f range which prevented the link from ever being brought up. So I changed the range to 0x62-0x6f
2026-07-24 16:02:20 +08:00
logicog 51cf40d040 Merge pull request #290 from eraiza0816/feat/dockerfile
add: docker use prerequisites
2026-07-24 07:58:12 +02:00
d00f 3fd9cdfa7e httpd: parse the session cookie by name, not fixed offset
Root cause of the "browser login always bounces back with Wrong password!
while curl works": scan_header() read the session id from a fixed offset
into the Cookie header (p + 17), assuming "session=" is the first and only
cookie. Browsers keep stale cookies for a long time - e.g. an "admin" cookie
left over from this switch's VENDOR firmware - so the header can arrive as
"Cookie: admin=..; session=..", the fixed offset then points into the admin
value, authentication silently fails and every page bounces to login although
the password had been accepted. curl sends only "session=", which is why
command-line tests passed while a real browser (with that stale cookie) failed.

- scan_header(): scan the Cookie header for the actual "session=" key
  (matched as "session" - is_word() requires a separator after the pattern
  and '=' is on its list, the first value byte is not).
- is_word_x(): accept ';' as a terminating separator so the session value
  also matches when it is not the last cookie in the header.

Verified on hardware end-to-end in a real browser WITH the stale "admin"
cookie present: login -> index.html, all pages and JSON endpoints work.
2026-07-21 06:48:18 +02:00
d00f 3189820ba0 httpd: send Connection: close (single-connection uIP mitigation)
Real root cause of the "web login fails from a browser but works from curl":
the uIP httpd is built with UIP_CONF_MAX_CONNECTIONS = 1 and uses global
response state (outbuf/slen/session), i.e. it serves exactly one TCP connection
at a time and closes it after each response - but never advertises that via
the Connection header. A browser's HTTP/1.1 client therefore assumes the
connection may be persistent and can park it in its keep-alive pool for reuse;
a later request sent on that pooled connection hits one the server has already
closed, and a POST (unlike a GET) is never retried by the browser, so it can
be silently lost this way.

This adds "Connection: close" to every response so the browser does not pool
and reuse a connection the server is about to drop. On its own this did not
fully explain the reported login failures - the actual authentication bug is
fixed in the next commit (the Cookie header parsed at a fixed offset) - but it
is correct behaviour for a server that only ever handles one connection, and
removes one source of dropped requests.
2026-07-21 06:47:44 +02:00
d00f ed74ec1e97 httpd/login: complete CSP + password autocomplete hint
Two hygiene fixes for the web UI, prompted by a login that appeared to fail
under privacy shields (Brave Shields / NoScript-family extensions):

- httpd: replace the partial "style-src 'self' 'unsafe-inline'" CSP with a
  complete, first-party policy (default-src 'self'; script-src 'self'
  'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;
  connect-src 'self'; form-action 'self'). Everything the UI needs is
  same-origin; the explicit policy stops shields injecting their own
  restrictive report-only probes (the noisy script-src-elem 'none' console
  spam) and passes a strict-CSP audit. Verified: no CSP violations in-browser.
- login.html: add autocomplete="current-password" so password managers
  recognise the field (they showed "unknown password" without it).

NOTE: these do NOT bypass a browser's LAN-device protection (NoScript "lan" /
Brave Shields), which strips the POST body of requests to a LAN address and is
why the login can fail in-browser while the same credentials work over curl.
That is a deliberate browser security feature; the user must allow the site in
their shields to log in. The backend password (default 1234) is unchanged and
correct.
2026-07-21 06:47:10 +02:00
Your Name dd95a05487 add: docker use prerequisites 2026-07-19 19:49:30 +09:00
logicog 481c02c740 Merge pull request #289 from eraiza0816/fix/httpd-sim-const-warning
fix: httpd-sim-const-warning
2026-07-19 09:49:08 +02:00
Your Name 0a31a65477 fix: httpd_sim 2026-07-19 16:44:21 +09:00
Lynn-Becky 2d176b7ccc add SWTG024AS-A-V2.0.1 with 5 RJ45 port (#282)
* add MACHINE_SWTG024AS_A_2_0_1_5_RJ45

* fix(machine): update SFP port LED color descriptions for PCB-SWTG024AS V2.0

* add(doc): create a simple documentation for SWTG024AS-A-V2.0.1_5_RJ45

* refactor(machine): update machine definition
2026-07-18 06:20:47 +02:00
logicog a1fac63710 Merge pull request #286 from eraiza0816/feature/language-settings
add language selection to system settings
2026-07-18 06:18:33 +02:00
logicog 5c7f82e2fe Merge pull request #285 from eraiza0816/feat/dockerfile
feat: add Dockerfile for development environment
2026-07-18 05:58:10 +02:00
Your Name e765d17c2b feat: add language selection to system settings
Add a language selector to the System Settings page supporting English,
Japanese, and Chinese. The i18n dictionary already contained zh
translations but lacked the UI to switch languages.

Changes:
- html/i18n.js: add sys_language key to en/ja/zh
- html/system.html: add language selector dropdown
- html/system.js: add changeLang() and selector initialization
- tools/httpd_sim.c: fix cookie parsing for multi-cookie headers;
  fix Set-Cookie response (missing Path=/, missing \r\n\r\n);
  add SO_REUSEADDR for faster port reuse
2026-07-15 01:35:10 +09:00
Your Name 8fb9afb955 feat: add Dockerfile for development environment
- Debian 13 (trixie) based with sdcc 4.5.0 from apt
- Includes gcc, make, xxd, python3, libjson-c-dev, golang-go
- Add .dockerignore to exclude build artifacts
- Add Docker usage section to README (collapsible)
2026-07-14 02:10:55 +09:00
logicog e96780fadb Merge pull request #280 from tofurky/ddm_broken_module
Add SFP quirk for devices that misreport DDM capability
2026-07-13 09:57:35 +02:00
Matt Merhar 2221f0fa32 Add SFP quirk for devices that misreport DDM capability
On a QSFPTEK QT-SFP+-T (RTL8261C) 10GBase-T module, the diag type field
(92) comes back as 0x00, but there's actually some statistics available
like temperature. Other metrics may be hard-coded values.

Add a basic struct that allows matching on vendor and/or model, using a
bitfield to allow multiple quirks for a given SFP module. Only
SFP_QUIRK_DDM is implemented.

For modules matching SFP_QUIRK_DDM, attempt an I2C read of the MSB of
module voltage during probe if DDM is "unsupported" - if it's not 0xff,
override the reported options so we can pull the diagnostic data.

To allow simpler comparisons, convert the ASCII fields (vendor,
model, serial) from space-padded to standard NULL-terminated strings.

strcmp() is moved from httpd.c to rtlplayground.c alongside other string
functions and shared between them.

The __reentrant keyword is used for the new functions to avoid using up
additional OSEG space. This allocates the variables on the stack, which
is OK for this particular code path.

The JSON assembly in send_status() is slightly modified to treat the
sfp_module_* data as standard NULL-terminated strings, and a repeated
subtraction was moved into a uint8_t to declutter the code.
2026-07-13 01:31:29 -04:00
logicog f86b8f32de Merge pull request #283 from Lynn-Becky/translate/chinese
Translate to Chinese
2026-07-13 07:15:52 +02:00
logicog 9389db9e9b Merge pull request #284 from eraiza0816/feature-translate
fix: rename local variable to avoid shadowing global translation
2026-07-13 07:15:29 +02:00
Your Name 519c3a8df3 fix: rename local variable to avoid shadowing global translation function 2026-07-10 13:09:41 +09:00
Lynn-Becky 64422367d5 feat(translate):translate to Chinese 2026-07-08 20:40:00 +08:00
logicog dc2b03e331 Merge pull request #279 from eraiza0816/translate-japanese
Support i18n & translate japanese
2026-07-07 19:28:19 +02:00
Your Name fcf1b3c310 Fix: Remove the language-specific display logic. 2026-07-05 02:31:37 +09:00
Your Name 1dd6156a1f add: how to support i18n 2026-07-05 01:09:50 +09:00
Your Name e75a4c2a95 support i18n & translate japanese 2026-07-05 00:51:27 +09:00
René van Dorst 05d8c36afe Merge pull request #278 from eraiza0816/update-docs-SWTG024AS
update document: SKS3200M-4GPY2XF flash size
2026-07-04 12:47:04 +00:00
René van Dorst b6c9eaf00f Merge pull request #275 from riker77/mokerlink-2G040210GSM
Add Mokerlink 2G040210GSM (2M-PCB43-V1.1)
2026-07-04 12:45:47 +00:00
Your Name ecf5f47960 update SKS3200M-4GPY2XF flash size 2026-07-04 15:52:25 +09:00
donbernhardo 457f117216 Add PCB43 V1.1 and symmetric KP-9000 6XH targets 2026-06-29 20:59:28 +02:00
Ron ebf6ed7526 Update 2M-PCB43-V1.1.md
Add hint to use KP_9000_6XHML_X2 as a compatible machine definition.
2026-06-29 20:30:07 +02:00
riker77 96f2c860fc Add Mokerlink 2G040210GSM (2M-PCB43-V1.1) 2026-06-29 17:57:09 +02:00
donbernhardo dd52e8e85e Discard incorrect KP-9000 V1.2 LED mux override 2026-06-29 14:12:31 +02:00
donbernhardo ecf8c999b1 Merge main into KP-9000 PCB revision split 2026-06-29 14:00:22 +02:00
donbernhardo 64a1d252cd Split KP-9000 6XH targets by PCB revision 2026-06-29 13:54:47 +02:00
feelfree69 b80d46955c Merge pull request #274 from Lynn-Becky/feat/PCB-SWTG024AS-V2.0
fix(machine): move SWTG024AS-specific SDS0 setup to machine.c
2026-06-28 21:32:53 +02:00
Lynn-Becky 0a18c64dcc fix(machine): move SWTG024AS-specific SDS0 setup to machine.c 2026-06-28 22:47:33 +08:00
René van Dorst b53c56e7f6 Merge pull request #273 from Lynn-Becky/feat/PCB-SWTG024AS-V2.0
fix(machine): SWTG024AS V2.0 fix 5th RJ45 port
2026-06-27 19:50:05 +00:00
Lynn-Becky 17d41b53b7 update doc 2026-06-28 00:56:50 +08:00
Lynn-Becky bd98cb0312 fix(machine): SWTG024AS V2.0 fix 5th RJ45 port 2026-06-28 00:48:05 +08:00
René van Dorst 5566d71522 Merge pull request #272 from Lynn-Becky/feat/PCB-SWTG024AS-V2.0
fix(SWTG024AS-V2.0): correct SFP port GPIO assignments
2026-06-27 13:33:29 +00:00
Lynn-Becky 5e1396cc42 fix(machine): correct SFP port GPIO assignments 2026-06-27 21:16:47 +08:00
René van Dorst 7fe0678c92 Merge pull request #271 from th0m4sek/main
Add reset button gpio to SWTG018AS-A V2.0
2026-06-25 18:33:18 +00:00
th0m4sek 7ec25a39a2 Add brands information for SWTG018AS-A V2.0
Added brands section with details for Ampcom and Horaco.
2026-06-25 20:23:54 +02:00
th0m4sek 98e3cde2f5 Update reset_pin on SWTG018AS-A V2.0 2026-06-25 20:16:56 +02:00
René van Dorst d90c02f951 Merge pull request #268 from th0m4sek/main
Table with supported devices
2026-06-23 17:40:51 +00:00
th0m4sek bbb92c86db Fix formatting issue in supported_devices.md 2026-06-23 09:16:05 +02:00
René van Dorst 1635236a05 Merge pull request #266 from hlyi/pr-tool
Tools for detecting GPIO mapping and extracting LED configure
2026-06-23 06:28:38 +00:00
th0m4sek 5e3a280b5c Update supported devices list with new entries 2026-06-23 07:26:35 +02:00
HL Yi a56e2a419b sleep-interval default value is reduced to 1.
i2c-bus is required argument
replace --bus with --i2c-bus for i2c_dump_rtl_regs.py for better consistency
format lint clean up
2026-06-22 18:02:34 -05:00
th0m4sek 9c01227572 Fix typo in device manufacturer name from 'Haraco' to 'Horaco' 2026-06-22 23:21:13 +02:00
th0m4sek 6d37a47481 Rename Horaco SWTGW215AS to HC-SWTGW218AS 2026-06-22 23:18:42 +02:00
th0m4sek 87b9e59cdb Update ZX-SWTG124AS entry with new data 2026-06-22 22:47:02 +02:00
th0m4sek 0e86b84efa Add Ports column to supported devices list
Updated the supported devices table to include Ports column and adjusted device details.
2026-06-22 22:45:20 +02:00
René van Dorst bd595b7f94 Merge pull request #263 from Lynn-Becky/feat/PCB-SWTG024AS-V2.0
Add PCB-SWTG024AS V2.0 (Unmanaged)
2026-06-22 19:27:38 +00:00
Lynn-Becky 06d4a3504a Add LED status comments,try to fix 5th rj45 2026-06-23 00:42:31 +08:00
Lynn-Becky d68534e833 update PCB-SWTG024AS-V2.0 to SWTG024AS-V2.0 2026-06-22 23:51:57 +08:00
Lynn-Becky e0356bd751 Add SWTG024AS-V2.0 doc and photo 2026-06-22 22:24:46 +08:00
HL Yi 4b770a8abc Fixed document based upon vDorst
Expand ignore IO list to the GPIOs related to SYS_LED, UART, SMI, and SPI
2026-06-22 07:08:19 -05:00
th0m4sek 709436ca13 Update supported devices list
Added new device 'Keeplink KP-9000-6XH-X2' and removed duplicate 'Horaco SWTGW215AS' entry.
2026-06-22 12:08:52 +02:00
th0m4sek 28856ef495 Revise supported devices documentation
Updated the supported devices list with new entries and details.
2026-06-22 12:07:30 +02:00
th0m4sek 113bb654aa Add links to PCB documentation for supported devices
Updated device entries with links to detailed PCB documentation.
2026-06-22 11:14:04 +02:00
th0m4sek efc1883685 Table with supported devices
Added a table listing supported hardware devices with details.
2026-06-22 10:38:40 +02:00
feelfree69 72921ba3be Merge pull request #267 from hlyi/pr-doc
add SKS3200-8E1X to SWTGW218AS.md doc
2026-06-22 08:00:46 +02:00
HL Yi 70be673fca updates based upon vDorst's feedback.
change i2c_read_rtl_gpio sleep tiemr to 1s
update README.md
Sorted port speed
2026-06-22 00:18:32 -05:00
HL Yi bf5205258e add SKS3200-8E1X to SWTGW218AS.md doc 2026-06-21 17:24:02 -05:00
René van Dorst 98ddb0bb29 Merge pull request #253 from Erdnusschokolade/fix/vlan-mgmt-filter
Fix conf_overwrite filter removing vlan mgmt entries
2026-06-21 18:52:59 +00:00
HL Yi d152f89549 remove redundant code 2026-06-21 13:22:54 -05:00
HL Yi c61e94527e add clarification for firmware requirement 2026-06-21 13:21:06 -05:00
HL Yi f76ccaf3d8 add disclaim that tool tested in linux 2026-06-21 13:08:58 -05:00
HL Yi ebc6cd06ad add utilities for led and gpio dump and detection 2026-06-21 13:04:04 -05:00
Lynn-Becky 017aaf71d6 Add PCB-SWTG024AS V2.0 machine target 2026-06-21 01:15:34 +08:00
Erdnusschokolade 4eaf16f96d Drop dead exact-match check in vlan delete filter
A bare "vlan N" line never matches conf_cmds (a VLAN entry always carries
a member port or the mgmt/name keyword), so it is never stored. The
c !== "vlan N" comparison could therefore never match an entry; the
startsWith(prefix) check alone covers all stored vlan lines.
2026-06-20 08:13:43 +02:00
Erdnusschokolade 718745e7b5 Reduce stale management VLAN entries on config save
The firmware has a single management VLAN (one management_vlan variable),
so a stored config should never carry more than one "vlan N mgmt" line.
When parsing a mgmt command, drop any previously stored mgmt entry so
repeatedly changing the management VLAN no longer accumulates stale lines.
2026-06-20 08:00:54 +02:00
Erdnusschokolade 06ee1ce38e Persist port speed settings in config parser
parseConf() only recognized "port N name" in conf_cmds, so "port N <speed>"
lines were treated as unknown commands and dropped on save - configured port
speeds never persisted.

Add the speed command (incl. optional half/full duplex suffix) to conf_cmds,
add a "port N" entry to conf_overwrite so re-saving a speed replaces the
previous value, and guard the per-port name entry so changing a port's speed
no longer wipes its configured name.
2026-06-18 14:49:56 +02:00
donbernhardo d3e8698e40 Fix KP-9000-6XHML-X2 LED mux mapping 2026-06-17 21:16:21 +02:00
René van Dorst f122402bdd Merge pull request #259 from logicog/rtl8221b_100m_support
Add support for 100M on RTL8221B ports
2026-06-15 17:54:03 +00:00
logicog 842d68af97 Add support for 100M on RTL8221B ports 2026-06-15 18:40:03 +02:00
logicog bfd1ce9183 Merge pull request #209 from hlyi/pr
Add Steamemo IG204 V1 support
2026-06-12 17:35:17 +02:00
feelfree69 4ed6575a87 Merge branch 'main' into pr 2026-06-10 08:40:35 +02:00
feelfree69 1b1c656404 Merge pull request #215 from logicog/2x10g
Add  ZX310S-4T2XT device support
2026-06-10 08:21:48 +02:00
logicog 018eab9810 Also display RTL837X_REG_LED_MODE 2026-06-10 05:45:19 +02:00
logicog ec36658ab9 Add ZX310S-4T2XT device photos 2026-06-10 05:45:19 +02:00
logicog eeded0f788 Add (Horaco) ZX310S-4T2XT
The ZX310S-4T2XT is a managed device with 2x2.5GBit and 2x10GBit Ethernet.
It is sold, among others, by Horaco as an unbranded device.

CPU: RTL8372
Flash: 2MByte Winbond W25Q16DV (U3)
PHY 2x RTL8261BE
2026-06-10 05:45:19 +02:00
bennydiamond 7a7ffd3bf8 Automatically print newline on serial interface
For some print_string
2026-06-07 14:52:56 -04:00
logicog 8f3b738e59 Merge pull request #257 from logicog/revert-252-main
Revert "Don't disable flash DIO operation before flashing"
2026-06-07 10:47:10 +02:00
TylerDurden-23 62065875e3 Revert "Don't disable flash DIO operation before flashing" 2026-06-07 10:42:39 +02:00
HL Yi e7f70a724b add doc for STEAMEMO_IG204_V1 2026-06-05 20:05:26 -05:00
HL Yi d7a7bdac7e add support of STEAMEMO_IG204_V1 2026-06-05 18:32:22 -05:00
René van Dorst 94e34a5991 Merge pull request #251 from feelfree69/make
make: seperate output directories by MACHINE
2026-06-05 19:16:04 +00:00
René van Dorst b58b1dfb35 Merge pull request #252 from feelfree69/main
Don't disable flash DIO operation before flashing
2026-06-05 19:13:30 +00:00
Erdnusschokolade 592b903305 Fix conf_overwrite filter removing vlan mgmt entries
The vlan/mgmt lookahead split (commit dcc60c3) correctly separated the
patterns so that `vlan N mgmt` and `vlan N <members>` match different
conf_overwrite entries. But the filter that applies the overwrite still
used `item.startsWith(matchStr + " ")` with matchStr = "vlan N", which
also matches "vlan N mgmt" — so the management entry was removed whenever
the VLAN's membership definition was re-saved.

Example: with both `vlan 44 management 2t 4t 5 6t` and `vlan 44 mgmt`
in the config, changing the membership dropped `vlan 44 mgmt` entirely.

Added a `!item.endsWith(" mgmt")` guard so management entries survive the
filter. No reordering is needed: `vlan N mgmt` only sets the global
management_vlan variable (cmd_parser.c) and does not depend on the VLAN
table entry existing, so its position in the config is irrelevant.
2026-06-02 22:52:06 +02:00
feelfree69 4ca5b4e87a Don't disable flash DIO operation before flashing - it works fine with DIO 2026-06-01 15:07:37 +02:00
logicog 5a8a8ed91b Merge pull request #237 from Erdnusschokolade/fix/config-persistence
Fix config persistence: missing commands, delete handling, multipart upload
2026-06-01 06:46:11 +02:00
René van Dorst ad64e450e1 Merge pull request #246 from logicog/sfp_speed
Add support for configuring SFP modules speeds via CLI
2026-05-31 18:33:05 +00:00
feelfree69 cfeffc434d Merge pull request #248 from zectaiga/swtg024as-a-2-0-1-fixes
Fix SWTG024AS-A 2.0.1 port and LED mapping
2026-05-31 10:42:01 +02:00
feelfree69 8670709b6e make: seperate output directories by MACHINE 2026-05-31 01:17:33 +02:00
logicog ce496ab25f Merge pull request #249 from vDorst/fix_led_settings
Fix SFP led for 2.5gbit for MACHINE_SWGT024_V2_0
2026-05-30 22:07:56 +02:00
René van Dorst 964ef2da7a Fix SFP led for 2.5gbit for MACHINE_SWGT024_V2_0
While testing #246, I noticd that SFP did not light-up while forcing
2.5gbit link. This is because RJ45 and SFP share the same LED-settings.
But RJ45 has two leds-settings.
Solution is to add settings for SFP for both managed and unmanaged
variant.
2026-05-30 21:02:42 +02:00
Taiga Ogawa 053dbe286e Fix SWTG024AS-A 2.0.1 port and LED mapping 2026-05-30 03:44:15 +09:00
feelfree69 e68e5cef46 Merge pull request #244 from kekopop/main
Add PCB-SWTG024AS-A-V2.0.1
2026-05-26 22:15:21 +02:00
kekopop b5543b6aeb Update machine.c 2026-05-26 22:04:26 +02:00
kekopop a51fcf1478 Update machine.h 2026-05-26 22:03:42 +02:00
kekopop 247cb4dd2f Add files via upload 2026-05-26 19:44:31 +02:00
logicog 394fc99485 Merge pull request #242 from tofurky/fix_xgspon_rates
Broaden range of SFP+ rates mapped to SDS_10GR
2026-05-26 05:32:54 +02:00
logicog cf0707475d Merge pull request #241 from tofurky/regset_arg_count
Correct argument count for regset command
2026-05-26 05:25:22 +02:00
Matt Merhar 73b99b2f08 Broaden range of SFP+ rates mapped to SDS_10GR
A commonly used SFP+ form-factor XGS-PON ONT (WAS-110) reports 0x63 at
power on and then switches to 0x64 after booting up.

0x63 -> 9.9gbps
0x64 -> 10gbps

These are outside of the previous 0x66-0x69 range which prevented the
link from ever being brought up.
2026-05-25 21:45:42 -04:00
Matt Merhar 005eaa3248 Correct argument count for regset command
Fixes: 2cdab5f9d9 ("parse_reg{set,get}() make use cmd_words_len.")
2026-05-25 20:24:27 -04:00
feelfree69 98f1e9443a Merge pull request #239 from amstan/ztyuav
doc: Describe other PCB-K0402WS-V3.0 devices
2026-05-25 23:23:16 +02:00
feelfree69 4c2be334d5 Merge pull request #233 from Erdnusschokolade/feature/vlan-overview
Add VLAN selector dropdown and overview table to vlan.html
2026-05-25 12:30:30 +02:00
Alexandru M Stan 073cc1e4a4 doc: Describe other PCB-K0402WS-V3.0 devices
I recently brought a "Ztyuav Z-QWYT0402", the insides looked identical to the
existing supported "Hisource Hi-K0402WS". Both of them have the
"PCB-K0402-V3.0" silkscreen.

I flashed the vanilla image and it seems to work great.

I changed the documentation to be more inclusive of other devices (including
adding a picture of mine). I suspect more devices might fall into this
"PCB-K0402-V3.0" bucket, for example the "YuanLey YS25-0402" with
the vlan switch also looks suspiciously identical from the outside.

I also added some details I noticed while flashing mine:
teardown, flash padding, how the selector switch actually works.
2026-05-24 18:33:23 -07:00
Erdnusschokolade ba919280ac Address PR #233 review feedback
- itoa16_html: add comment that the function is sufficient for VLAN
  IDs (<=4094); generalization not needed.
- send_vlanlist: replace post-write bounds check with a pre-write
  guard using worst-case entry size (138 bytes + 1 for closing
  bracket). Old comment claimed ~45 bytes per entry, which only
  held for short names. With a 117-char name (the actual bound from
  CMD_BUF_SIZE) entries can reach 138 bytes, and the post-check
  would not have prevented an overflow.
- Document the 0x02 check in sfr_data[0] as the VLAN table entry
  valid flag.
2026-05-24 23:31:18 +02:00
Erdnusschokolade dcc60c3824 Fix conf_overwrite vlan/mgmt conflict
The pattern /^vlan\s+\d{1,4}\b/ matched both VLAN membership entries
(vlan N <ports>) and management entries (vlan N mgmt), causing them
to overwrite each other in configuration[]. When saving, whichever
form came last in the cmd_log would dedupe the other out of the
final config — resulting in either lost membership or a stale mgmt
setting.

Split into two patterns using negative lookahead:
- /^vlan\s+\d{1,4}\s+mgmt$/      matches only mgmt entries
- /^vlan\s+\d{1,4}(?!\s+mgmt\b)/  matches everything else

Both dedup independently. Discovered via hardware test on 6XH-X
where 'vlan 44 mgmt' silently dropped 'vlan 44 management 2t 5u'
from saved config, locking out web UI on next boot.
2026-05-24 11:26:04 +02:00
Erdnusschokolade caad366b3b Fix config persistence: missing commands, delete handling, multipart
Multiple related bugs in the Save-to-Flash path:

1. Multipart upload was missing the required filename argument,
   causing the backend parser to fail. Added 'config.txt' to the
   form.append call. This was likely the primary reason Save-to-Flash
   was unreliable.

2. Web UI was not pausing its polling interval during flash write,
   causing CPU contention and intermittent crashes. Added isSaving
   lock and clearInterval before sendConfig.

3. conf_cmds whitelist was incomplete. Added: syslog, passwd, pvid,
   ingress, port name, lag, laghash, isolate, stp, igmp, mtu, bw,
   vlan N mgmt, vlan N d.

4. VLAN regex blocked named VLANs. New pattern allows optional name
   (starts with letter, matching CLI parser semantics).

5. vlan N d (delete) was not persisted. parseConf now removes the
   matching vlan N ... entry from configuration[] when seeing a
   delete command, without storing the delete itself. Result: the
   saved config describes the end state.

6. configuration[] was not cleared between flashSave invocations,
   leading to stale entries from prior interactions.

7. conf_overwrite boundary fix: 'pvid 1' no longer matches 'pvid 10'
   etc. Added trailing space in startsWith check.

8. All conf_cmds patterns now anchored with ^...$ for full-line
   match. parseConf normalizes whitespace before testing.

9. Port range widened to \d{1,2} so ports 11+ are accepted.

Structural fixes (1, 2, 6, 7, 8) ported from mcaptur's closed PR #219;
remaining fixes (3, 4, 5, 9) and overall regex strategy are new.
2026-05-24 11:26:04 +02:00
Erdnusschokolade ce0ee859d6 Fix: Untagged Ports column included non-member ports
The hardware bitmask format encodes both "untagged members" and
"non-members" in the upper 10 bits (per doc/vlan.md). The existing
fetchVLAN() correctly masks this with the membership bitmask before
display, but loadVlanTable() did not, causing non-member ports to
appear in the Untagged Ports column.

Tested on KeepLiNK KP-9000-6XH-X.
2026-05-24 11:20:57 +02:00
Erdnusschokolade a369e46fe6 Add VLAN overview table to VLAN configuration page
Below the configuration form, a new table lists all configured VLANs
with their port memberships: Member, Tagged, Untagged, and PVID
columns. Port ranges are formatted compactly (e.g. "1-2,5").

Port-to-bit mapping uses the existing physToLogPort[] array populated
by /status.json, so the table is consistent with the icon view in
fetchVLAN() and works on any supported machine.

Each row has a delete button that sends "vlan N d" via /cmd, with
a confirmation dialog. VLAN 1 cannot be deleted (no button shown).

Table and dropdown both refresh automatically after Update/Create
and after delete via a new refreshVlanViews() helper.

The N+1 request pattern (1x /vlanlist + Nx /vlan.json) keeps the
backend simple and avoids buffer overflow risks for systems with
many VLANs. For typical configurations (<50 VLANs), page load
remains under one second.
2026-05-24 11:20:57 +02:00
Erdnusschokolade defb37c06a Add VLAN selector dropdown to VLAN configuration page
A new <select> element above the VLAN ID input lets users pick an
existing VLAN by name instead of typing the ID. Options are loaded
from /vlanlist on page load and re-loaded after successful
Update/Create operations.

Selection triggers the existing fetchVLAN() flow.

Falls back gracefully if /vlanlist returns an empty list or fails:
the dropdown is hidden and the existing ID input remains functional.
2026-05-24 11:20:57 +02:00
Erdnusschokolade a5f77e4caf Add /vlanlist HTTP endpoint
Returns a JSON array of all configured VLANs with their IDs and names,
e.g. [{"id":1,"name":""},{"id":20,"name":"IoT"}].

The endpoint iterates VLAN IDs 1..4094 and filters by the validity bit
in sfr_data[0] (0x02), following the same pattern as vlan_create() and
vlan_setup() in rtl837x_port.c.

Also adds a small itoa16_html() helper for emitting decimal numbers
up to 4 digits (analogous to the existing 8-bit itoa_html()), used
for VLAN IDs which can reach 4094. Response builder uses the existing
vlan_name() helper for the name lookup, consistent with send_vlan().

Buffer overflow is prevented by breaking out of the iteration loop at
TCP_OUTBUF_SIZE - 60.

This endpoint is the foundation for upcoming UI improvements
(VLAN selector dropdown and overview table).
2026-05-24 11:20:57 +02:00
logicog 1311cb9ea9 Merge pull request #232 from Erdnusschokolade/fix/vlan-name-rename
Fix VLAN name persistence across rename and delete operations
2026-05-23 12:47:06 +02:00
Erdnusschokoladeandlogicog 7d0d8525d2 Fix two OOB reads in parse_vlan()
1. While loop scanning VLAN name terminated only on ' ', not '\0'.
   When the name is the last token in cmd_buffer, the loop reads past
   the buffer into adjacent XRAM.

2. Entering 'vlan' without arguments causes parse_vlan() to read
   cmd_words_b[1] which points to undefined memory, causing atoi_short()
   to interpret residual bytes from previous commands as a VLAN ID.
   Bug found and fix proposed by logicog during review of PR #232.

Co-Authored-By: logicog <logicog@users.noreply.github.com>
2026-05-23 11:19:42 +02:00
Erdnusschokolade 21f33abfa7 Fix VLAN name persistence across rename and delete operations
Previously, renaming a VLAN or deleting and recreating it with a
different name did not update the displayed name. The vlan_names[]
array is an append-only buffer where vlan_name() returns the first
matching entry, so stale entries kept winning.

This commit adds vlan_name_remove(), which locates an entry by
VLAN ID and removes it via array compaction. The function is called
in two places:

  - parse_vlan() in cmd_parser.c, before appending a new name entry,
    to remove any pre-existing entry for the same VLAN ID
  - vlan_delete() in rtl837x_port.c, to clean up the name when a
    VLAN is removed

The implementation reuses the existing vlan_name() lookup, scans for
the trailing space of the matched entry, then shifts remaining bytes
left. Locals are declared as static __xdata to avoid the SDCC
overlay segment limit on banked functions.

Tested on KeepLiNK KP-9000-6XH-X:
  - vlan 99 AAA p1u; vlan 99 BBB -> name updated to BBB
  - vlan 99 d; vlan 99 CCC p1u   -> name correctly CCC, not stale AAA

Note: This fix addresses the runtime XMEM state. Persistence of
renamed VLAN names across reboot requires the user to download and
re-upload /config, as is the existing pattern for all configuration
changes in this firmware.
2026-05-21 17:39:29 +02:00
209 changed files with 14114 additions and 4326 deletions

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
.git/
.gitignore
.gitattributes
.github/
output/
installer/output/
*.bin
html_data.c
html_data.h
*.o
*.rel
*.lst
*.sym
*.asm
*.ihx
*.img
*.map
*.mem
*.lk
+10 -3
View File
@@ -3,6 +3,7 @@ name: Build firmware
on:
push:
branches: ['**']
pull_request:
jobs:
build:
@@ -16,8 +17,14 @@ jobs:
- name: Install dependencies
run: |
apt update
apt install make gcc sdcc xxd python-is-python3 libjson-c-dev -y
apt install make gcc sdcc xxd python-is-python3 libjson-c-dev zlib1g-dev -y
- name: Run the host-side unit tests
run: make -C test
- name: Check if machine.c can be compiled for all machines
run: make machine_check
- name: Make project
run: make MACHINE="KP_9000_6XHML_X2"
- name: Make project (4x2/RTL8372)
run: make CI=1 MACHINE="KP_9000_6XHML_X2"
- name: Make project (8x1/RTL8373)
run: make CI=1 MACHINE="KP_9000_9XHML_X_V3_1"
- name: Make project with the health instrumentation
run: make clean && make CI=1 MACHINE="KP_9000_6XHML_X2" HEALTH=1
+4
View File
@@ -7,3 +7,7 @@ version.h
tools/httpd_sim
tools/injector
tools/fileadder
# host-test stand-ins for the two generated headers
!test/stub/html_data.h
!test/stub/version.h
+37
View File
@@ -0,0 +1,37 @@
# Change Log
## [0.x] - 2026-xx-XX
## Added
- Web UI
- Compress the embedded assets (minify + gzip) and switch to a single-page layout. #315
- Replace the multi-page UI with a themed single-page app: light, dark and Selenized themes following the browser by default,
English, Japanese and Chinese, save to flash merges the command log into the startup config and verifies the write,
firmware images are checked in the browser before upload. #429
## Changed
## Fixed
- Config
- A startup configuration line longer than the command buffer no longer stops the replay:
the offending line is skipped and the following lines are still applied.
- The web UI and the configuration upload now refuse a line the replay cannot take,
instead of writing it to flash.
## Breaking changes
- Config
- VLAN don't accept port `u`-suffix anymore.
So `vlan 1 4u` is not valid anymore.
Replace it with `vlan 1 4`.
- Commands
- port zero/`0` is treated as the `CPU_PORT`. #326
- Many commands don't accept the CPU_PORT anymore. See #334.
When the CPU_PORT is needed, the command/service will add the CPU_PORT automaticly.
Only `isolate` accept the CPU_PORT as destination port.
## [v0.1_aplha] - 2025-11-03
First release.
+19
View File
@@ -0,0 +1,19 @@
FROM debian:13-slim
RUN apt-get update && apt-get install -y \
make \
gcc \
sdcc \
xxd \
python3 \
libjson-c-dev \
git \
zlib1g-dev \
&& rm -rf /var/lib/apt/lists/*
# git safe.directory for mounted repos (Makefile uses git describe)
RUN git config --global --add safe.directory /workspace
WORKDIR /workspace
CMD ["bash"]
+118 -35
View File
@@ -4,22 +4,32 @@ DEFAULT_CONFIG_LOCATION = 454656
CONFIG_LOCATION = 458752
HTML_LOCATION = 262144
ifeq ($(origin CC),default)
CC = sdcc
endif
CC_FLAGS = -mmcs51 -I. -Ihttpd -Iuip
ASM = sdas8051
ASM ?= sdas8051
AFLAGS= -plosgff
SUBDIRS := tools
SUBDIRSCLEAN=$(addsuffix clean,$(SUBDIRS))
BUILDDIR = output
VERSION_HEADER := version.h
ifeq ($(MACHINE),)
MACHINE:= $(shell grep "^\s*#define MACHINE_" machine.h | sed "s/^\s*#define MACHINE_//")
else
CC_FLAGS += -DMACHINE_$(MACHINE)
endif
# Health instrumentation and the "health" console command: HEALTH=1 gmake ...
ifneq ($(HEALTH),)
CC_FLAGS += -DHEALTH
endif
ifeq ($(CI),1)
CC_FLAGS += --Werror
endif
BUILDDIR = output/$(MACHINE)
VERSION_HEADER := version.h
GIT_VERSION := $(shell git rev-parse --short HEAD)
ifeq ($(shell git status --porcelain --untracked-files=no),)
@@ -30,76 +40,149 @@ endif
VERSION_EXTENSION = v$(VERSION)-$(GIT_VERSION)
FILENAME_EXTENSION = $(VERSION_EXTENSION)-$(MACHINE)
# Deterministic build date: honor SOURCE_DATE_EPOCH, else the HEAD commit date,
# else wall-clock (no-git fallback). Keeps same-commit builds byte-identical
# (BUILD_DATE is baked into the image and covered by the trailing CRC).
SOURCE_DATE_EPOCH ?= $(shell git show -s --format=%ct HEAD 2>/dev/null)
ifeq ($(SOURCE_DATE_EPOCH),)
BUILD_DATE := $(shell date +"%Y-%m-%d %H:%M:%S")
else
BUILD_DATE := $(shell date -u -d @$(SOURCE_DATE_EPOCH) +"%Y-%m-%d %H:%M:%S" 2>/dev/null \
|| date -u -r $(SOURCE_DATE_EPOCH) +"%Y-%m-%d %H:%M:%S")
endif
all: create_build_dir $(VERSION_HEADER) $(SUBDIRS) $(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin
create_build_dir:
mkdir -p $(BUILDDIR)
mkdir -p $(BUILDDIR)/uip
mkdir -p $(BUILDDIR)/httpd
mkdir -p "$(BUILDDIR)"
mkdir -p "$(BUILDDIR)/uip"
mkdir -p "$(BUILDDIR)/httpd"
mkdir -p "$(BUILDDIR)/crypto"
# Keep machine.c in first position to fail immediately on invalid $MACHINE value
SRCS = \
machine.c \
machine_init.c \
cmd_editor.c \
cmd_parser.c \
dhcp.c \
html_data.c \
rtlplayground.c \
boot.c \
sfp.c \
syslog.c \
udp_apps.c \
crypto/chacha20.c
# RTL837x
SRCS += \
rtl837x_bandwidth.c \
rtl837x_flash.c \
rtl837x_igmp.c \
rtl837x_init.c \
rtl837x_leds.c \
rtl837x_phy.c \
rtl837x_pins.c\
rtl837x_port.c \
rtl837x_stp.c \
rtl837x_storm.c
SRCS += \
httpd/httpd.c \
httpd/page_impl.c
SRCS += \
uip/timer.c \
uip/uip.c \
uip/uiplib.c \
uip/uip_arp.c \
uip/uip-fw.c \
uip/uip-neighbor.c \
uip/uip-split.c
SRCS = rtlplayground.c rtl837x_flash.c rtl837x_leds.c rtl837x_phy.c rtl837x_port.c cmd_parser.c html_data.c rtl837x_igmp.c
SRCS += rtl837x_stp.c rtl837x_pins.c dhcp.c machine.c cmd_editor.c rtl837x_bandwidth.c rtl837x_init.c syslog.c
SRCS += uip/timer.c uip/uip.c uip/uip_arp.c uip/uiplib.c uip/uip-fw.c uip/uip-neighbor.c uip/uip-split.c udp_apps.c
SRCS += httpd/httpd.c httpd/page_impl.c
OBJS = ${SRCS:%.c=$(BUILDDIR)/%.rel}
DEPS := ${SRCS:%.c=$(BUILDDIR)/%.d}
HTML := $(shell find $(html) -name '*.js' -or -name '*.html' -or -name '*.svg')
HTML := $(shell find html -name '*.js' -or -name '*.html' -or -name '*.svg' -or -name '*.css' -or -name '*.ico')
html_data.c html_data.h: $(HTML) tools/$(BUILDDIR)/fileadder
tools/$(BUILDDIR)/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -b BANK1 -d html -p html_data
# Minified copy of the web UI sources, used as the fileadder input.
# The raw html/ sources stay untouched for development; the minified
# copy is a build artifact under output/.
HTML_MIN := output/html_min
.PHONY: html_min
html_min: $(HTML)
rm -rf $(HTML_MIN)
mkdir -p $(HTML_MIN)
@for f in $(HTML); do python3 tools/minify.py $$f $(HTML_MIN)/$$(basename $$f) || exit 1; done
$(VERSION_HEADER):
@echo "#ifndef VERSION_H" > $(VERSION_HEADER)
@echo "#define VERSION_H" >> $(VERSION_HEADER)
@echo "#define VERSION_SW \"$(VERSION_EXTENSION)\"" >> $(VERSION_HEADER)
@echo "#define BUILD_DATE \"$(shell date +"%Y-%m-%d %H:%M:%S")\"" >> $(VERSION_HEADER)
@echo "#endif" >> $(VERSION_HEADER)
html_data.c html_data.h &: $(HTML) | tools html_min
tools/output/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -b BANK1 -z -d $(HTML_MIN) -p html_data
$(VERSION_HEADER): FORCE
@printf '%s\n' "#ifndef VERSION_H" "#define VERSION_H" \
"#define VERSION_SW \"$(VERSION_EXTENSION)\"" \
"#define BUILD_DATE \"$(BUILD_DATE)\"" \
"#endif" > $(VERSION_HEADER).tmp
@cmp -s $(VERSION_HEADER).tmp $(VERSION_HEADER) \
&& rm -f $(VERSION_HEADER).tmp \
|| mv $(VERSION_HEADER).tmp $(VERSION_HEADER)
httpd: html_data.h
$(SUBDIRS):
$(MAKE) -C $@
clean:
clean: $(SUBDIRSCLEAN)
-rm -f html_data.c html_data.h $(VERSION_HEADER)
-if [ -d $(BUILDDIR) ]; then find $(BUILDDIR) -type f ! -name "*.bin" -delete; fi
distclean:
distclean: $(SUBDIRSCLEAN)
-rm -f html_data.c html_data.h $(VERSION_HEADER)
-rm -rf $(BUILDDIR)
$(BUILDDIR)/%.rel: %.c
$(SUBDIRSCLEAN):
$(MAKE) -C $(@:clean=) clean
# Objects depend on the flags they were built with, through a stamp file that
# is rewritten only when CC_FLAGS actually changes.
CCFLAGS_STAMP := $(BUILDDIR)/.ccflags
.PHONY: FORCE
FORCE:
$(CCFLAGS_STAMP): FORCE | create_build_dir
@echo '$(CC_FLAGS)' | cmp -s - $@ 2>/dev/null || echo '$(CC_FLAGS)' > $@
$(BUILDDIR)/%.rel: %.c $(CCFLAGS_STAMP) | create_build_dir html_data.h
$(CC) -MMD $(CC_FLAGS) -o $@ -c $<
$(BUILDDIR)/%.rel: %.asm
$(BUILDDIR)/%.rel: %.asm $(CCFLAGS_STAMP) | create_build_dir
${ASM} ${AFLAGS} -o $@ $<
# mv -f $(addprefix $(basename $^), .lst .rel .sym) .
$(BUILDDIR)/rtlplayground.ihx: $(OBJS) $(BUILDDIR)/crtstart.rel $(BUILDDIR)/crc16.rel
$(CC) $(CC_FLAGS) -Wl-bHOME=0x00000 -Wl-bBANK1=0x14000 -Wl-bBANK2=0x24000 -Wl-r -o $@ $^
$(BUILDDIR)/rtlplayground.ihx: $(OBJS) $(BUILDDIR)/crtbank.rel $(BUILDDIR)/crc16.rel $(BUILDDIR)/crypto/chacha_8051.rel
$(CC) $(CC_FLAGS) --xram-size 49151 -Wl-bHOME=0x00000 -Wl-bBANK1=0x14000 -Wl-bBANK2=0x24000 -Wl-bBANK3=0x34000 -Wl-r -o $@ $^
$(BUILDDIR)/rtlplayground.img: $(BUILDDIR)/rtlplayground.ihx
objcopy --input-target=ihex -O binary $< $@
$(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin: $(BUILDDIR)/rtlplayground.img
$(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin: $(BUILDDIR)/rtlplayground.img | tools
if [ -e $@ ]; then rm $@; fi
tools/$(BUILDDIR)/imagebuilder -i $^ $@
tools/$(BUILDDIR)/fileadder -a $(DEFAULT_CONFIG_LOCATION) -s $(IMAGESIZE) -d config.txt $@
tools/$(BUILDDIR)/fileadder -a $(CONFIG_LOCATION) -s $(IMAGESIZE) -d config.txt $@
tools/$(BUILDDIR)/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -d html -p html_data -b BANK1 $@
tools/$(BUILDDIR)/crc_calculator -u $@
ln -sf rtlplayground-$(FILENAME_EXTENSION).bin $(BUILDDIR)/rtlplayground.bin
tools/output/imagebuilder -i $^ $@
tools/output/fileadder -a $(DEFAULT_CONFIG_LOCATION) -s $(IMAGESIZE) -d config.txt $@
tools/output/fileadder -a $(CONFIG_LOCATION) -s $(IMAGESIZE) -d config.txt $@
tools/output/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -z -d $(HTML_MIN) -b BANK1 $@
tools/output/crc_calculator -u $@
ln -sf $(MACHINE)/rtlplayground-$(FILENAME_EXTENSION).bin output/rtlplayground.bin
.PHONY: clean all $(SUBDIRS) $(VERSION_HEADER)
.PHONY: clean distclean all $(SUBDIRS) $(SUBDIRSCLEAN) create_build_dir
.PHONY:
machine_check:
@mkdir -p $(BUILDDIR)/tmp
@set -eo pipefail; \
for MACHINE in `grep -e ' MACHINE_' machine.c | sed -e 's%^.* MACHINE_%%' -e 's%[ ]*//.*$$%%' | sort -u`; \
for MACHINE in `grep -E '^[[:space:]]*(//[[:space:]]*)?#define MACHINE_' machine.h | sed -E 's%^[[:space:]]*(//[[:space:]]*)?#define MACHINE_%%' | awk '{print $$1}' | sort -u`; \
do \
echo "Checking $${MACHINE}"; \
$(CC) $(CC_FLAGS) -DMACHINE_$${MACHINE} -MMD -o $(BUILDDIR)/tmp/machine_check -c machine.c; \
$(CC) $(CC_FLAGS) -DMACHINE_$${MACHINE} -MMD -o $(BUILDDIR)/tmp/machine_check -c machine_init.c; \
done
@rm -rf $(BUILDDIR)/tmp
+71 -20
View File
@@ -2,15 +2,15 @@
A Playground for Firmware development for advanced user of RTL8372/RTL8373 based 2.5GBit Switches.
For each hardware configuration of these devices, there is usually a managed and an
umanaged version sold, with mostly identical hardware. The aim is to provide management
unmanaged version sold, with mostly identical hardware. The aim is to provide management
features also for unmanaged devices with additional features such as Management VLAN,
dhcp servers, multi-language support, IPv6 and TLS-encrypted web-pages. At present, however
DHCP servers, multi-language support, IPv6 and TLS-encrypted web-pages. At present, however
only the following features are provided:
- A modern web-interface with mouse-over to display further information
- A serial console interface to configure all features
- IGMP to configure Multicast streaming
- Port configuration showing detailed informtion about own and Link-partner advertised
Speed settins and configuration of these settings on the local side
- Port configuration showing detailed information about own and Link-partner advertised
Speed settings and configuration of these settings on the local side
- Per-port configuration of frame sizes (MTUs) for Jumbo-Frame support or limiting MTUs
for particular devices
- EEE (Energy Efficient Ethernet) can be configured per-port. Detailed information is
@@ -25,17 +25,19 @@ only the following features are provided:
- Firmware updates via the web
- Installation as a firmware upgrade from the original web-interface
<img width="1420" height="623" alt="GUI" src="doc/images/gui.png" />
<img width="1673" height="977" alt="GUI" src="doc/images/gui.png" />
While the firmware provides already considerable improvements over the original managed firmware,
the firmware still lacks support for STP and the proprietary loop prevention
protocols as well as DHCP. If you need these features, do not install the playground on your managed
the firmware still lacks support for the proprietary loop prevention
protocols as well as DHCP. Spanning Tree is available (see doc/stp.md), but is
a simplified implementation - read that document before enabling it on a
switch you administer over the network. If you need these features, do not install the playground on your managed
devices. In any case, installation is strongly discouraged unless you can at least make
a backup of the original flash content via a SOIC clamp such as also used for BIOS
backups and can re-install that firmware in case something is wrong. For this no soldering
skills are necessary.
The firmware supports all hardware featues of devices with
The firmware supports all hardware features of devices with
- 4 2.5GBit ports + 2 SFP+ ports
- 5 2.5GBIT + 1 SFP+ port
- 8 2.5GBit + 1 SFP+ port
@@ -57,9 +59,56 @@ device simulator is provided, which runs entirely under Linux as a local webserv
Install the following particular build requisites (Debian 12/13), note that Ubuntu 24.04
still has an older version of sdcc, but you will need sdcc version 4.5 for the code to compile:
```
sudo apt install make gcc sdcc xxd python-is-python3 libjson-c-dev
sudo apt install make gcc sdcc xxd python-is-python3 libjson-c-dev zlib1g-dev
```
<details>
<summary>If using Docker (click to expand)</summary>
### Prerequisites
Install Docker for your platform:
- **Linux (Debian/Ubuntu)**: `sudo apt install docker.io` then `sudo usermod -aG docker $USER` (log out and back in)
- **Linux (other distros)**: Follow the [Docker Engine install guide](https://docs.docker.com/engine/install/)
- **Windows**: Install [Docker Desktop for Windows](https://docs.docker.com/desktop/setup/install/windows-install/)
- **macOS**: Install [Docker Desktop for Mac](https://docs.docker.com/desktop/setup/install/mac-install/)
### Usage
A Dockerfile is provided for a reproducible build environment:
```
docker build -t rtlplayground-dev .
```
Build the firmware (replace MACHINE with your target, e.g. `DEFAULT_8C_1SFP`):
```
docker run --rm -v $(pwd):/workspace rtlplayground-dev make MACHINE=DEFAULT_8C_1SFP
```
The resulting `.bin` file appears in `output/` on your host.
Build host tools only:
```
docker run --rm -v $(pwd):/workspace rtlplayground-dev make -C tools
```
Run the web-interface simulator locally:
```
docker run --rm -p 8080:8080 -v $(pwd):/workspace rtlplayground-dev \
tools/output/httpd_sim /workspace/html
```
Edit `machine.h` or `config.txt` on your host, then re-run `make` — the
source directory is mounted into the container, so changes take effect
immediately. To build for a different machine, pass `MACHINE=...`.
</details>
## (1) Compiling for direct chip flashing AND upgrading an existing RTLPlayground running device
Edit machine.h with an editor like vi or nano. Select the correct machine the firmware should build for.
@@ -82,7 +131,7 @@ rtlplayground-v0.1.0-12c98ba-dirty-LIANGUO_ZX_SWTGW215AS.bin
> [!CAUTION]
> This image can be flashed directly to the chip OR through the firmware update/upgrade
> interface of RTLPlaygound interface
> interface of RTLPlayground interface
## (2) Compiling for OEM running device with management options (web upgrade)
@@ -126,11 +175,11 @@ Header checksum is: 0x5a1
## (3) Sandbox Usage with Ghidra (optional)
You can play with the image using ghidra or flash real Switch Hardware. For
ghidra see this information about [Ghidra images](ghidra.md).
ghidra see this information about [Ghidra images](doc/ghidra.md).
## (4) Installation through the Web interface (software way)
Managed switches (OEM firmware of RTLplaygroud firmware) can be upgraded via the web interface.
Managed switches (OEM firmware of RTLplayground firmware) can be upgraded via the web interface.
Unmanaged switch cannot be flashed this way (see 5).
Go to "Firmware update" tab, select the correct file.
@@ -141,7 +190,7 @@ Go to "Firmware update" tab, select the correct file.
> [!CAUTION]
> Check one more time that your device matches the machine type before flashing.
> Be shure you have a backup of the original firmware before diving in RTLPlaygroung.
> Be sure you have a backup of the original firmware before diving in RTLPlayground.
Finally, push the Upload File Button and you're done !
@@ -149,7 +198,7 @@ Finally, push the Upload File Button and you're done !
## (5) Flashing the ROM directly (hardware way, but also only way to rescue)
This procedure is the only way to flash unmanaged switches, if the ROM chip is large enough.
This is also the only way to unbrick your device if something went wroong.
This is also the only way to unbrick your device if something went wrong.
> [!IMPORTANT]
> You need a SOIC-8 clip to flash the ROM chip directly onboard.
@@ -169,7 +218,7 @@ This is also the only way to unbrick your device if something went wroong.
- ERASE THE ROM (BLANK) !
- Load the firmware into IMSProg.
- Flash is to the ROM chip.
- Disconect the clip from the ROM chip.
- Disconnect the clip from the ROM chip.
- You're done, ready for the first boot.
## (6) Connecting a serial interface (optional)
@@ -184,7 +233,7 @@ When you power up the switch, the device will perform some examples and provide
## (8) The web-interface
The web-interface can be reached under the [default 192.168.10.247](http://192.168.10.247) unless you
specified an IP adress in the config.txt before compilation.
specified an IP address in the config.txt before compilation.
> [!TIP]
> The default password is `1234`.
@@ -284,17 +333,18 @@ While in compilation part, you might write directly to config.txt file before ma
nano config.txt
```
If you want to modify settings after the flash is done, go to the Advanced Settings tab in System Settings
If you want to modify settings after the flash is done, go to the System tab and find the Startup Configuration.
<img width="1085" height="646" alt="ADVANCED SETTINGS" src="doc/images/advanced_settings.png" />
<img width="1673" height="978" alt="ADVANCED SETTINGS" src="doc/images/advanced_settings.png" />
```
ip xxx.xxx.xxx.xxx = IP adress of the switch
gw yyy.yyy.yyy.yyy = IP adress of the gateway
ip xxx.xxx.xxx.xxx = IP address of the switch
gw yyy.yyy.yyy.yyy = IP address of the gateway
netmask zzz.zzz.zzz.zzz = Network mask of the switch
port x name xxx = Name xxx the port number x
port z 1g = Set 1g speed for port z
igmp on/off = Turn IGMP on or off
session xxxx = Web session timeout in seconds (default 200)
```
[To be continue]
@@ -310,4 +360,5 @@ The following documents give further documentation on specific features of the R
- [SFP+ ports](doc/sfp.md)
- [Trunking aka. port aggregation](doc/trunking.md)
- [VLAN](doc/vlan.md)
- [Storm control](doc/storm_control.md)
- [Modifications and Flash replacement](doc/mods.md)
+479
View File
@@ -0,0 +1,479 @@
#pragma codeseg BANK3
#pragma constseg BANK3
#include <8051.h>
#include <stdint.h>
#include <stdbool.h>
#include "rtl837x_sfr.h"
#include "rtl837x_regs.h"
#include "rtl837x_common.h"
#include "rtl837x_flash.h"
#include "rtl837x_pins.h"
#include "rtl837x_phy.h"
#include "rtl837x_port.h"
#include "rtl837x_init.h"
#include "machine.h"
#include "phy.h"
#include "boot.h"
extern __code const struct machine machine;
extern __xdata struct machine_runtime machine_detected;
extern __xdata uint8_t sfr_data[4];
extern volatile __xdata uint32_t ticks;
__xdata uint32_t rtl8224_release_tick;
extern __xdata uint8_t tx_seq;
extern __code const uint8_t * __code const hex;
void sds_config_mac(uint8_t sds, uint8_t mode) __banked
{
reg_read_m(RTL837X_REG_SDS_MODES);
sfr_data[0] = 0;
sfr_data[1] = 0;
switch (sds) {
case 0:
sfr_mask_data(0, 0x1f, mode);
break;
case 1:
sfr_mask_data(0, 0xe0, mode << 5);
sfr_mask_data(1, 0x03, mode >> 3);
break;
case 2:
sfr_mask_data(1, 0xfc, 0x02 << 2);
}
if (machine_detected.isRTL8373) // Set 3rd SERDES Mode to 0x2 for RTL8224
sfr_mask_data(1, 0xfc, 0x02 << 2);
else
sfr_data[2] &= 0x03;
reg_write_m(RTL837X_REG_SDS_MODES);
print_string("\nRTL837X_REG_SDS_MODES: ");
print_reg(RTL837X_REG_SDS_MODES);
print_string("\n");
}
void early_boot_handle_button(void) __banked
{
if (machine.reset_pin == GPIO_NA)
return;
gpio_input_setup(machine.reset_pin);
// Debounce after init
delay(100);
// If the button is not already held at boot, continue normally.
if (gpio_pin_test(machine.reset_pin))
return;
set_sys_led_state(SYS_LED_FAST);
print_string("\n[Reset button held at boot]\n");
if (gpio_pin_test(machine.reset_pin))
return;
const __xdata uint32_t min_hold_ticks = 10UL * SYS_TICK_HZ;
const __xdata uint32_t max_hold_ticks = 30UL * SYS_TICK_HZ;
const __xdata uint32_t blink_ticks = SYS_TICK_HZ / 10; // 100 ms
const __xdata uint32_t pause_ticks = SYS_TICK_HZ / 2; // 500 ms
__xdata uint32_t start_ticks = ticks;
__xdata uint32_t last_blink_step = start_ticks;
__xdata uint8_t blink_step = 0;
set_sys_led_state(SYS_LED_ON);
while (!gpio_pin_test(machine.reset_pin)) {
__xdata uint32_t held_ticks = ticks - start_ticks;
if (held_ticks > max_hold_ticks) {
print_string("[Button held >30s at boot; continuing normal boot]\n");
return;
}
// Double blink pattern while button is held:
// ON (100ms), OFF (100ms), ON (100ms), OFF (500ms)
__xdata uint32_t step_ticks = (blink_step == 3) ? pause_ticks : blink_ticks;
if ((ticks - last_blink_step) >= step_ticks) {
blink_step = (blink_step + 1) & 0x3;
set_sys_led_state((blink_step == 0 || blink_step == 2) ? SYS_LED_ON : SYS_LED_OFF);
last_blink_step = ticks;
}
PCON |= 1;
}
set_sys_led_state(SYS_LED_ON);
if ((ticks - start_ticks) >= min_hold_ticks) {
print_string("[Button held 10s-30s at boot; restoring default config]\n");
set_sys_led_state(SYS_LED_FAST);
flash_default_config();
delay(3UL * SYS_TICK_HZ);
}
}
/*
* Configure the SerDes of the SoC for a particular mode
* to connect to an SFP module or a PHY
* Valid modes are SDS_10GR, SDS_QXGMII, SDS_HISGMII, SDS_HSG, SDS_SGMII and SDS_1000BX_FIBER
* The SerDes ID may be 0 or 1 for RTL8272 and 0-2 for RTL8373
* SDS_QXGMII is used for 10G Fiber, RTL8224 and RTL8261BE
*/
void sds_config(uint8_t sds, uint8_t mode) __banked
{
print_string("sds_config sds: "); print_byte(sds); print_string(", mode: "); print_byte(mode); write_char('\n');
sds_config_mac(sds, mode);
if (mode == SDS_10GR || mode == SDS_QXGMII)
sds_write_v(sds, 0x21, 0x10, 0x4480); // Q002110:6480
else
sds_write_v(sds, 0x21, 0x10, 0x6480); // Q002110:6480
sds_write_v(sds, 0x21, 0x13, 0x0400); // Q002113:0400
sds_write_v(sds, 0x21, 0x18, 0x6d02); // Q002118:6d02
sds_write_v(sds, 0x21, 0x1b, 0x424e); // Q00211b:424e
sds_write_v(sds, 0x21, 0x1d, 0x0002); // Q00211d:0002
sds_write_v(sds, 0x36, 0x1c, 0x1390); // Q00361c:1390
sds_write_v(sds, 0x36, 0x14, 0x003f); // Q003614:003f
uint8_t page = 0;
uint16_t v = 0;
switch (mode) {
case SDS_SGMII:
case SDS_1000BX_FIBER:
v = 0x0300;
page = 0x24;
break;
case SDS_HISGMII:
case SDS_HSG:
v = 0x0200;
page = 0x28;
break;
case SDS_10GR:
case SDS_QXGMII:
v = 0x0200;
page = 0x2e;
break;
case SDS_100FX:
v = 0x0200;
page = 0x26;
break;
default:
print_string("Error in SDS Mode\n");
return;
}
sds_write_v(sds, 0x36, 0x10, v); // Q003610:0200
if (page == 0x2e) { // 10G Fiber / SDS_QXGMII
sds_write_v(sds, page, 0x04, 0x0080); // Q012e04:0080
sds_write_v(sds, page, 0x06, 0x0408); // Q012e06:0408
sds_write_v(sds, page, 0x07, 0x020d); // Q012e07:020d
sds_write_v(sds, page, 0x09, 0x0601); // Q012e09:0601
sds_write_v(sds, page, 0x0b, 0x222c); // Q012e0b:222c
sds_write_v(sds, page, 0x0c, 0xa217); // Q012e0c:a217
sds_write_v(sds, page, 0x0d, 0xfe40); // Q012e0d:fe40
sds_write_v(sds, page, 0x15, 0xf5c1); // Q012e15:f5c1
} else {
sds_write_v(sds, page, 0x04, 0x0080); // Q002804:0080
sds_write_v(sds, page, 0x07, 0x1201); // Q002807:1201
sds_write_v(sds, page, 0x09, 0x0601); // Q002809:0601
sds_write_v(sds, page, 0x0b, 0x232c); // Q00280b:232c
sds_write_v(sds, page, 0x0c, 0x9217); // Q00280c:9217
sds_write_v(sds, page, 0x0f, 0x5b50); // Q00280f:5b50
sds_write_v(sds, page, 0x15, 0xe7c1); // Q002815:e7f1 BUG !
}
sds_write_v(sds, page, 0x16, 0x0443); // Q002816:0443 / Q012e16:0443
sds_write_v(sds, page, 0x1d, 0xabb0); // Q00281d:abb0 / Q012e1d:abb0
sds_write_v(sds, 0x06, 0x12, 0x5078); // Q000612:5078
sds_write_v(sds, 0x07, 0x06, 0x9401); // Q000706:9401
sds_write_v(sds, 0x07, 0x08, 0x9401); // Q000708:9401
sds_write_v(sds, 0x07, 0x0a, 0x9401); // Q00070a:9401
sds_write_v(sds, 0x07, 0x0c, 0x9401); // Q00070c:9401
sds_write_v(sds, 0x1f, 0x0b, 0x0003); // Q001f0b:0003
sds_write_v(sds, 0x06, 0x03, 0xc45c); // Q000603:c45c
// RTL8261BE
if (machine.n_10g && mode == SDS_QXGMII) {
sds_write_v(sds, 0x06, 0x1f, 0x2100); // Q00061f:2100
sds_write_v(sds, 0x07, 0x11, 0x054f); // Q000711:054f
sds_write_v(sds, 0x20, 0x00, 0x0030); // Q002000:0030
sds_write_v(sds, 0x20, 0x00, 0x0010); // Q002000:0010
sds_write_v(sds, 0x20, 0x00, 0x0050); // Q002000:0050
sds_write_v(sds, 0x20, 0x00, 0x00d0); // Q002000:00d0
sds_write_v(sds, 0x20, 0x00, 0x0cd0); // Q002000:0cd0
sds_write_v(sds, 0x20, 0x00, 0x04d0); // Q002000:04d0
sds_write_v(sds, 0x20, 0x00, 0x04d0); // Q002000:04d0
sds_write_v(sds, 0x20, 0x00, 0x0cd0); // Q002000:0cd0
sds_write_v(sds, 0x20, 0x00, 0x00d0); // Q002000:00d0
sds_write_v(sds, 0x20, 0x00, 0x00d0); // Q002000:00d0
sds_write_v(sds, 0x20, 0x00, 0x0050); // Q002000:0050
sds_write_v(sds, 0x20, 0x00, 0x0010); // Q002000:0010
sds_write_v(sds, 0x20, 0x00, 0x0010); // Q002000:0010
sds_write_v(sds, 0x20, 0x00, 0x0030); // Q002000:0030
sds_write_v(sds, 0x20, 0x00, 0x0000); // Q002000:0000
sds_write_v(sds, 0x1f, 0x00, 0x000b); // Q001f00:000b
sds_write_v(sds, 0x1f, 0x00, 0x0000); // Q001f00:0000
return;
}
if (mode != SDS_QXGMII)
sds_write_v(sds, 0x06, 0x1f, 0x2100); // Q00061f:2100
if (mode == SDS_1000BX_FIBER) {
sds_write_v(sds, 0x02, 0x04, 0x0020); // Q000204:0020
sds_write_v(sds, 0x00, 0x02, 0x73d0); // Q000002:73d0
sds_write_v(sds, 0x00, 0x04, 0x074d); // Q000004:074d
sds_write_v(sds, 0x20, 0x04, 0x0000); // Q002000:0000
sds_write_v(sds, 0x1f, 0x00, 0x0000); // Q001f00:0000
}
}
void rtl8224_enable(void) __banked
{
// Set Pin 4 low
reg_bit_clear(RTL837X_REG_GPIO_32_63_OUTPUT, 4);
// Configure Pin as output
reg_bit_set(RTL837X_REG_GPIO_32_63_DIRECTION, 4);
delay(4);
// Set pin 4 high
reg_bit_set(RTL837X_REG_GPIO_32_63_OUTPUT, 4);
rtl8224_release_tick = ticks;
}
void nic_setup(void) __banked
{
// Enable NIC
// r6040:00000100 R6040-00001100
reg_bit_set(RTL837X_REG_HW_CONF, 0xc);
// This sets the size of the RX buffer, the filling level is in 0x7874
// R7848-000004ff
REG_SET(RTL837X_REG_NIC_RXBUFF_RX, 0x4ff);
// R7844-000007fe
REG_SET(RTL837X_REG_NIC_BUFFSIZE_TX, 0x7fe);
// Configure NIC RX to receive various types of packets
// RTL837X_REG_RX_CTRL: Set bits 24-31 to 0x4, clear bits 16/17
reg_read_m(RTL837X_REG_RX_CTRL);
sfr_mask_data(3, 0xff, 0x04);
sfr_mask_data(2, 0x03, 0);
reg_write_m(RTL837X_REG_RX_CTRL);
// Enable NIC TX (set bit 0)
reg_bit_set(RTL837X_REG_TX_CTRL, 0);
// Enable NIC RX (set bit 0)
reg_bit_set(RTL837X_REG_RX_CTRL, 0);
// Drop packets with invalid CRC
reg_bit_clear(RTL837X_REG_RX_CTRL, 2);
// R603c-00000200
// CPU-port is CPU-Tag aware (bit 9)
REG_SET(RTL837X_REG_CPU_TAG_AWARE_PMASK, 0x200);
// Insert CPU-tag for internally received packets (bit 0), MODE is 0, i.e. ALL packets (bits 8-9)
reg_read_m(RTL837X_REG_CPU_TAG);
sfr_mask_data(0, 1, 1);
sfr_mask_data(1, 3, 0);
reg_write_m(RTL837X_REG_CPU_TAG);
// Force MAC mode of the CPU port (port 9)
// r6368:00000194 R6368-00000197
reg_read_m(RTL837X_REG_MAC_FORCE_MODE + 9 * 4);
sfr_mask_data(0, 0, 3); // Set bits 0, 1: Force link
reg_write_m(RTL837X_REG_MAC_FORCE_MODE+ 9 * 4);
// Sequence number of TX packets
tx_seq = 0;
}
void rtl8373_revision(void) __banked
{
reg_read_m(RTL837X_REG_CHIP_INFO);
sfr_mask_data(2, 0x0a, 0x0a); // Enable reading version
reg_write_m(RTL837X_REG_CHIP_INFO);
delay(50);
reg_read_m(RTL837X_REG_CHIP_INFO);
print_string("CPU revision: "); print_byte(sfr_data[2]); print_byte(sfr_data[2]); write_char('\n');
sfr_mask_data(2, 0x0a, 0x00); // Enable reading version
reg_write_m(RTL837X_REG_CHIP_INFO);
}
/*
* The SoC manages Link-State for steering the LEDs and can set PHY-settings
* automatically through Realtek's SMI (Simple Managagement) Interface, a
* proprietary version of MDIO which for example allows for more PHYs on the same
* bus.
* Configure polling via SMI and the interface setup during boot.
*/
void init_smi(void) __banked
{
print_string("\ninit_switch called\n");
/* Set the SMI(i.e.I2C) type for PHY polling, 0b01 is 2.5/10G PHY. Disable (0b00) for the SFP-ports
* which are at port 8 and additionally at port 3 for a dual SFP device
*/
// Default: 0x00005555
// Workaround for SDCC BUG 4070: SFR_DATA_U32 = 0x00005555;
SFR_DATA_U16_UPPER = 0x0000;
SFR_DATA_U16 = 0x5555;
if (machine.n_10g == 2) {
// 0x00015555, only change the bytes that differs from the default.
SFR_DATA_16 = 0x01;
} else if (machine.n_sfp == 2)
// 0x00005515
SFR_DATA_0 = 0x15;
reg_write(RTL837X_REG_SMI_MAC_TYPE);
// Configure polling of all PHYs by the MAC to detect link-state changes
// Default: 0x000000ff
// Workaround for SDCC BUG 4070: SFR_DATA_U32 = 0x000000ff;
SFR_DATA_U16_UPPER = 0x0000;
SFR_DATA_U16 = 0x00ff;
if (!machine_detected.isRTL8373) {
if (machine.n_sfp == 2) {
// 0x000000f0, only change the bytes that differs from the default.
SFR_DATA_0 = 0xf0;
} else {
// 0x000001f8, only change the bytes that differs from the default.
SFR_DATA_8 = 0x01;
SFR_DATA_0 = 0xf8;
}
}
reg_write(RTL837X_REG_SMI_PORT_POLLING);
// Enable MDC
reg_read_m(RTL837X_REG_SMI_CTRL);
sfr_mask_data(1, 0, 0x70); // Set bits 12-14 to enable MDC for SMI0-SMI2
reg_write_m(RTL837X_REG_SMI_CTRL);
delay(50);
if (!machine_detected.isRTL8373) {
// Change I2C addresses for SMI of the non-existent PHYs
// r6450:000020e6 R6450-000000e6
reg_read_m(RTL837X_REG_SMI_PORT6_9_ADDR);
sfr_mask_data(1, 0x7c, 0);
reg_write_m(RTL837X_REG_SMI_PORT6_9_ADDR);
// r644c:0a418820 R644c-0a400820
reg_read_m(RTL837X_REG_SMI_PORT0_5_ADDR);
sfr_mask_data(2, 0x0f, 0);
sfr_mask_data(1, 0x80, 0);
reg_write_m(RTL837X_REG_SMI_PORT0_5_ADDR);
}
if (machine.n_10g == 2) {
// Set address of second external PHY on port 8
REG_SET(RTL837X_REG_SMI_PORT6_9_ADDR, 0x000040e6);
}
}
void setup_i2c(void) __banked
{
REG_SET(RTL837X_REG_I2C_MST_IF_CTRL, 0);
// Configure SFP EEPROM address (0x50) as I2C device address
// Configure SFP readings address (0x51) as I2C device address
REG_WRITE(RTL837X_REG_I2C_CTRL, 0x00, 0x1 << (I2C_MEM_ADDR_WIDTH-16), 0x50 >> 5, (0x50 << 3) & 0xff);
REG_SET(RTL837X_REG_I2C_CTRL2, 0);
// HW Control register, enable I2C depending on PIN configuration
reg_read_m(RTL837X_PIN_MUX_1);
for (uint8_t sfp = 0; sfp < machine.n_sfp; sfp++) {
uint8_t i2c = machine.sfp_port[sfp].i2c;
uint8_t scl_bus = (i2c >> RTL837X_REG_I2C_SCL_SHIFT) & RTL837X_REG_I2C_SCL_MASK;
uint8_t sda_bus = (i2c >> RTL837X_REG_I2C_SDA_SHIFT) & RTL837X_REG_I2C_SDA_MASK;
print_string("Configuring I2C for SFP idx="); print_byte(sfp); print_string(" SCL="); print_byte(scl_bus); print_string(", SDA="); print_byte(sda_bus); write_char('\n');
// `& RTL837X_REG_I2C_SCL_MASK` is needed to silens a compiler warning 110.
switch (scl_bus & RTL837X_REG_I2C_SCL_MASK) {
case 3:
// Bit 5-6 0b10 -> SCL (implies enabled SDA on bus 3)
sfr_mask_data(0, 0x60, 0x40);
break;
case 2:
// Bit 15-16 0b01 -> SCL
sfr_mask_data(1, 0x80, 0x80);
sfr_mask_data(2, 0x01, 0x00);
break;
case 1:
// Bit 11-12 0b01 -> SCL
sfr_mask_data(1, 0x18, 0x08);
break;
case 0:
// Bit 7-8 0b01 -> SCL
sfr_mask_data(0, 0x80, 0x80);
sfr_mask_data(1, 0x01, 0x00);
break;
}
switch (sda_bus) {
case 4:
// Bit 29 0b0 -> SDA
sfr_mask_data(3, 0x20, 0x00);
break;
case 3:
// Bit 5-6 0b10 -> SDA (implies enabled SCL on bus 3)
sfr_mask_data(0, 0x60, 0x40);
break;
case 2:
// Bit 17-18 0b01 -> SDA
sfr_mask_data(2, 0x06, 0x02);
break;
case 1:
// Bit 13-14 0b01 -> SDA
sfr_mask_data(1, 0x60, 0x20);
break;
case 0:
// Bit 9-10 0b01 -> SDA
sfr_mask_data(1, 0x06, 0x02);
break;
}
}
reg_write_m(RTL837X_PIN_MUX_1);
}
/* Give the switch a name carrying the tail of its MAC, so several of them on
* one network are distinguishable out of the box. Called after the startup
* config has been replayed and returns at once if that config already set a
* name, so a configured switch does no work for it (suggested in review).
*
* Written without a loop on purpose. Locals - counters and pointers alike -
* land in the 8051's internal-RAM overlay, and on an image with LACP and STP
* both enabled that overlay is exhausted: a loop here makes the linker fail
* with "Could not get 8 consecutive bytes in internal RAM for area OSEG".
* Moving the code into its own function does not help; the overlay is shared
* across the whole image. Hoisting the locals to xdata does not help either,
* because itohex() is inline and brings its own frame. */
void set_hostname_default(void) __banked
{
if (hostname[0] != NUL)
return;
strcpy((__xdata uint8_t *)hostname, "RTLPlayground-");
hostname[14] = hex[uip_ethaddr.addr[3] >> 4];
hostname[15] = hex[uip_ethaddr.addr[3] & 0xf];
hostname[16] = hex[uip_ethaddr.addr[4] >> 4];
hostname[17] = hex[uip_ethaddr.addr[4] & 0xf];
hostname[18] = hex[uip_ethaddr.addr[5] >> 4];
hostname[19] = hex[uip_ethaddr.addr[5] & 0xf];
hostname[20] = NUL;
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef BOOT_H
#define BOOT_H
void sds_config_mac(uint8_t sds, uint8_t mode) __banked;
void sds_config(uint8_t sds, uint8_t mode) __banked;
void early_boot_handle_button(void) __banked;
void rtl8224_enable(void) __banked;
void nic_setup(void) __banked;
void rtl8373_revision(void) __banked;
void init_smi(void) __banked;
void setup_i2c(void) __banked;
void set_hostname_default(void) __banked;
#endif
+19 -16
View File
@@ -40,21 +40,24 @@ void cmd_edit(void) __banked
{
while (l != sbuf_ptr) {
if (sbuf[l] >= ' ' && sbuf[l] < 127) { // A printable character, copy to command line
if (cmd_line_len >= CMD_BUF_SIZE)
continue;
write_char(sbuf[l]);
// Shift buffer to right
for (uint8_t i = cmd_line_len; i > cursor; i--)
cmd_buffer[i] = cmd_buffer[i-1];
// Insert char in comand buffer
cmd_buffer[cursor++] = sbuf[l];
cmd_line_len++;
// Print rest of line
for (uint8_t i = cursor; i < cmd_line_len; i++)
write_char(cmd_buffer[i]);
// Move backwards
for (uint8_t i = cursor; i < cmd_line_len; i++)
write_char('\010'); // BS works like cursor-left
// Reserve one byte for the terminating NUL written on Enter. When the
// line is full, drop the character but still fall through to advance the
// serial-ring read pointer below; a 'continue' here would spin forever.
if (cmd_line_len < CMD_BUF_SIZE - 1) {
write_char(sbuf[l]);
// Shift buffer to right
for (uint8_t i = cmd_line_len; i > cursor; i--)
cmd_buffer[i] = cmd_buffer[i-1];
// Insert char in comand buffer
cmd_buffer[cursor++] = sbuf[l];
cmd_line_len++;
// Print rest of line
for (uint8_t i = cursor; i < cmd_line_len; i++)
write_char(cmd_buffer[i]);
// Move backwards
for (uint8_t i = cursor; i < cmd_line_len; i++)
write_char('\010'); // BS works like cursor-left
}
} else if (sbuf[l] == '\033') { // ESC-Sequence
// Wait until we have at least 3 characters including the ESC character in the serial buffer
if (((sbuf_ptr + SBUF_SIZE - l) & SBUF_MASK) < 3)
@@ -190,7 +193,7 @@ void cmd_edit(void) __banked
// Check whether return was pressed:
if (sbuf[l] == '\n' || sbuf[l] == '\r') {
write_char('\n');
cmd_buffer[cmd_line_len] = '\0';
cmd_buffer[cmd_line_len] = NUL;
// write_char('>'); print_string_x(cmd_buffer); write_char('<');
// If there is a command we print the prompt after execution
// otherwise immediately because there is nothing to execute
+953 -353
View File
File diff suppressed because it is too large. Load diff
+29 -1
View File
@@ -6,13 +6,41 @@
#include "rtl837x_common.h"
extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE];
extern __xdata uint8_t cmd_available;
extern __bit __at(0x03) cmd_available;
extern __xdata uint8_t err_status;
/* Health instrumentation, built with HEALTH=1. Without it the hooks below
* compile to nothing and the "health" command does not exist. */
#ifdef HEALTH
/* Phase ids, in main-loop order */
#define HEALTH_PH_LINK 0
#define HEALTH_PH_SFP 1
#define HEALTH_PH_RX 2
#define HEALTH_PH_TX 3
#define HEALTH_PH_STP 4
#define HEALTH_PH_CMD 5
#define HEALTH_PHASES 6
extern __xdata uint16_t health_rx_frames;
#define health_rx_frame() health_rx_frames++
void health_loop_start(void) __banked; /* once per idle() pass, resets the phase baseline */
void health_phase(uint8_t id) __banked; /* after each phase; accounts ticks since last checkpoint */
void health_stack_paint(void) __banked; /* paint the unused stack area once at boot */
void health_show(void) __banked; /* "health" CLI dump */
#else
#define health_rx_frame()
#define health_loop_start()
#define health_phase(id)
#define health_stack_paint()
#endif
void cmd_tokenize(void) __banked;
void cmd_parser(void) __banked;
void execute_config(void) __banked;
void execute_commands(__xdata uint8_t *p) __banked;
void print_ip(__xdata uint8_t *ptr) __banked;
void print_mac(__xdata uint8_t *ptr) __banked;
void print_sw_version(void) __banked;
void clear_command_history(void) __banked;
+7 -1
View File
@@ -3,6 +3,8 @@
;
.globl _crc_value
.globl _crc16
.globl _crc16_bank1
.globl __sdcc_banked_ret
.equ BANK, 0x96
; .equ DPS, 0x86
; Variable in XMEM holding current CRC16 value, being updated
@@ -18,7 +20,7 @@ _crc_value::
.area HOME (CODE)
.area CSEG (CODE)
; .area BANK1 (CODE)
_crc16:
_crc16_bank1:
mov BANK, #1
push dph
push dpl
@@ -52,6 +54,10 @@ _crc16:
.area BANK1 (CODE)
_crc16:
lcall _crc16_bank1
ljmp __sdcc_banked_ret
crc16_table_l:
.byte #0x00, #0xc1, #0x81, #0x40, #0x01, #0xc0, #0x80, #0x41
.byte #0x01, #0xc0, #0x80, #0x41, #0x00, #0xc1, #0x81, #0x40
+29
View File
@@ -0,0 +1,29 @@
.area HOME (CODE)
.area GSINIT0 (CODE)
.area GSINIT1 (CODE)
.area GSINIT2 (CODE)
.area GSINIT3 (CODE)
.area GSINIT4 (CODE)
.area GSINIT5 (CODE)
.area GSINIT (CODE)
.area GSFINAL (CODE)
.area CSEG (CODE)
.area HOME (CODE)
__sdcc_banked_call::
push _PSBANK
xch a,r0
push a
mov a,r1
push a
mov a,r2
anl a,#0x1f
mov _PSBANK, a
xch a, r0
ret
__sdcc_banked_ret::
pop _PSBANK
ret
-17
View File
@@ -1,17 +0,0 @@
.area GSFINAL (CODE)
__sdcc_banked_call::
push _PSBANK
xch a,r0
push a
mov a,r1
push a
mov a,r2
anl a,#0x1f
mov _PSBANK, a
xch a, r0
ret
__sdcc_banked_ret::
pop _PSBANK
ret
+23
View File
@@ -0,0 +1,23 @@
#ifndef _CHACHA_H_
#define _CHACHA_H_
#include <stdint.h>
struct chacha20_t {
uint8_t wstate[64]; // 64 bytes written here
uint8_t constant[16]; // 128 bit constant
uint8_t key[32]; // 256-bit secret key
uint32_t cnt; // 32-bit block counter 1, 2.. Big Endian!
uint8_t nonce[12]; // 96-bit nonce
__xdata uint8_t *plaintext;
uint16_t length;
__xdata uint8_t *cyphertext;
};
// Encrypt a plaintext with ChaCha20 as per RFC7539
void chacha20_encrypt(void);
// Test ChaCha20 using the example from RFC7539
void chacha20_test(void);
#endif
+98
View File
@@ -0,0 +1,98 @@
#include "rtl837x_common.h"
#include "chacha.h"
void chacha_20(void);
void chacha_update(void);
void chacha_count(void);
// generate a block of ChaCha20 keystream as per RFC7539
__xdata struct chacha20_t __at(0xbf00) chacha20;
__xdata uint8_t plaintext[256];
__xdata uint8_t cyphertext[256];
void chacha20_print_block(void)
{
for (uint8_t i=0; i < 64; i++) {
print_byte(*(uint8_t * __xdata)(chacha20.wstate + i));
if (i%4 == 3)
write_char(' ');
}
}
void chacha20_encrypt(void)
{
__xdata uint8_t *p = chacha20.plaintext;
while (chacha20.length) {
register uint8_t i;
chacha_count();
memcpy(chacha20.wstate, chacha20.wstate + 64, 64);
#ifdef DEBUG
chacha20_print_block(); write_char('\n');
#endif
chacha_20();
chacha_update();
#ifdef DEBUG
chacha20_print_block(); write_char('\n');
#endif
for (i = 0; i < ((chacha20.length > 64) ? 64 : chacha20.length); i++)
*chacha20.cyphertext++ = *p++ ^ chacha20.wstate[i];
chacha20.length -= chacha20.length > 64 ? 64 : chacha20.length;
#ifdef DEBUG
print_string("\n round done\n");
#endif
};
}
// Test the example of RFC 7539 Section 2.4.2
void chacha20_test(void)
{
__code uint8_t chacha_c[16] =
{ 0x61, 0x70, 0x78, 0x65, 0x33, 0x20, 0x64, 0x6e,
0x79, 0x62, 0x2d, 0x32, 0x6b, 0x20, 0x65, 0x74 };
__code uint8_t key[32] =
{ 0x03, 0x02, 0x01, 0x00, 0x07, 0x06, 0x05, 0x04,
0x0b, 0x0a, 0x09, 0x08, 0x0f, 0x0e, 0x0d, 0x0c,
0x13, 0x12, 0x11, 0x10, 0x17, 0x16, 0x15, 0x14,
0x1b, 0x1a, 0x19, 0x18, 0x1f, 0x1e, 0x1d, 0x1c };
__code uint8_t nonce[12] = {
0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00 };
__code uint8_t sunscreen[] = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for " \
"the future, sunscreen would be it.";
/*
Ciphertext Sunscreen (from RFC 7539 Section 2.4.2):
000 6e 2e 35 9a 25 68 f9 80 41 ba 07 28 dd 0d 69 81 n.5.%h..A..(..i.
016 e9 7e 7a ec 1d 43 60 c2 0a 27 af cc fd 9f ae 0b .~z..C`..'......
032 f9 1b 65 c5 52 47 33 ab 8f 59 3d ab cd 62 b3 57 ..e.RG3..Y=..b.W
048 16 39 d6 24 e6 51 52 ab 8f 53 0c 35 9f 08 61 d8 .9.$.QR..S.5..a.
064 07 ca 0d bf 50 0d 6a 61 56 a3 8e 08 8a 22 b6 5e ....P.jaV....".^
080 52 bc 51 4d 16 cc f8 06 81 8c e9 1a b7 79 37 36 R.QM.........y76
096 5a f9 0b bf 74 a3 5b e6 b4 0b 8e ed f2 78 5e 42 Z...t.[......x^B
112 87 4d
*/
strcpy(plaintext, sunscreen);
memcpyc(chacha20.constant, chacha_c, 16);
memcpyc(chacha20.key, key, 32);
chacha20.cnt = 0;
memcpyc(chacha20.nonce, nonce, 12);
chacha20.plaintext = plaintext;
chacha20.length = strlen_x(plaintext);
chacha20.cyphertext = cyphertext;
print_string("Encrypting...\n");
chacha20_encrypt();
print_string("Cypertext:\n");
uint16_t len = strlen_x(plaintext);
for (uint8_t i = 0; i < len; i++) {
print_byte(cyphertext[i]); write_char(' ');
}
}
+455
View File
@@ -0,0 +1,455 @@
; ChaCha Quater-Round implementation in Assembler
.module chacha_8051
; Global variables:
.globl _chacha_20
.globl _chacha_update
.globl _chacha_count
;#define CHACHA_QR(A, B, C, D) { \
; A += B; D ^= A; D = ROTL32(D, 16); \
; C += D; B ^= C; B = ROTL32(B, 12); \
; A += B; D ^= A; D = ROTL32(D, 8); \
; C += D; B ^= C; B = ROTL32(B, 7); \
;}
; r0 r1 r2 r3
; CHACHA_QR( v[ 0], v[ 4], v[ 8], v[12] );
.area HOME (CODE)
store_32:
ar7 = 0x07
ar6 = 0x06
ar5 = 0x05
ar4 = 0x04
ar3 = 0x03
ar2 = 0x02
ar1 = 0x01
ar0 = 0x00
mov a, r7
movx @dptr, a
dec dpl
mov a, r6
movx @dptr, a
dec dpl
mov a, r5
movx @dptr, a
dec dpl
mov a, r4
movx @dptr, a
ret
rol_b:
clr c
mov a, r7
rlc a
mov r7, a
mov a, r6
rlc a
mov r6, a
mov a, r5
rlc a
mov r5, a
mov a, r4
rlc a
mov r4, a
clr a
addc a, r7
mov r7, a
djnz dpl, rol_b
ret
chacha_plus_xor:
; Load A into registers r4-r7, A pointed to by r0
mov dptr, #_chacha20
mov dpl, r0
movx a, @dptr
mov r7, a
dec dpl
movx a, @dptr
mov r6, a
dec dpl
movx a, @dptr
mov r5, a
dec dpl
movx a, @dptr
mov r4, a
; A += B, B pointed to by r1
mov dpl, r1
movx a, @dptr
add a, r7
mov r7, a
dec dpl
movx a, @dptr
addc a, r6
mov r6, a
dec dpl
movx a, @dptr
addc a, r5
mov r5, a
dec dpl
movx a, @dptr
addc a, r4
mov r4, a
mov dpl, r0 ; Store A back
mov a, r7
movx @dptr, a
dec dpl
mov a, r6
movx @dptr, a
dec dpl
mov a, r5
movx @dptr, a
dec dpl
mov a, r4
movx @dptr, a
; D ^= A
mov dpl, r3
movx a, @dptr
xrl a, r7
mov r7, a
dec dpl
movx a, @dptr
xrl a, r6
mov r6, a
dec dpl
movx a, @dptr
xrl a, r5
mov r5, a
dec dpl
movx a, @dptr
xrl a, r4
mov r4, a
dec dpl
ret
chacha_qr:
; QR Part: A += B; D ^= A; D = ROTL32(D, 16);
acall chacha_plus_xor
; rotate left 16. D is r4, r5, r6, r7 -> r6, r7, r4, r5
mov a, r6
xch a, r4
mov r6, a
mov a, r7
xch a, r5
mov r7, a
mov dpl, r3 ; Store D
acall store_32
;QR Part: C += D; B ^= C; B = ROTL32(B, 12);
; Swap A <-> C and D <-> B
mov a, r0
xch a, r2
mov r0, a
mov a, r1
xch a, r3
mov r1, a
acall chacha_plus_xor
; rotate left 12. D is r4, r5, r6, r7 -> r5, r6, r7, r4
mov a, r4
xch a, r7
xch a, r6
xch a, r5
mov r4, a
mov dpl, #4
acall rol_b
mov dpl, r3 ; Store D (being B)
acall store_32
; Swap A <-> C and D <-> B
mov a, r0
xch a, r2
mov r0, a
mov a, r1
xch a, r3
mov r1, a
; QR Part A += B; D ^= A; D = ROTL32(D, 8);
acall chacha_plus_xor
mov a, r4
xch a, r7
xch a, r6
xch a, r5
mov r4, a
mov dpl, r3 ; Store D
acall store_32
; QR Part
; C += D; B ^= C; B = ROTL32(B, 7);
line4:
; Swap A <-> C and D <-> B
mov a, r0
xch a, r2
mov r0, a
mov a, r1
xch a, r3
mov r1, a
acall chacha_plus_xor
; Roll left 7 bits, start by rolling 8 bits left, then roll 1 to the right
mov a, r4
xch a, r7
xch a, r6
xch a, r5
mov r4, a
clr c
mov a, r4
rrc a
mov r4, a
mov a, r5
rrc a
mov r5, a
mov a, r6
rrc a
mov r6, a
mov a, r7
rrc a
mov r7, a
clr a
rrc a
add a, r4
mov r4, a
; Swap A <-> C and D <-> B
mov a, r0
xch a, r2
mov r0, a
mov a, r1
xch a, r3
mov r1, a
mov dpl, r1 ; Store B
acall store_32
ret
;
; Implementation of 20 ChaCha Rounds (10 Double Rounds)
;
_chacha_20:
push acc
push b
push dpl
push dph
push ar7
push ar6
push ar5
push ar4
push ar3
push ar2
push ar1
push ar0
push psw
mov b, #10 ; 10 Double rounds
chacha_20_loop:
; CHACHA_QR( v[ 0], v[ 4], v[ 8], v[12] );
mov r0, #3
mov r1, #19
mov r2, #35
mov r3, #51
acall chacha_qr
; CHACHA_QR( v[ 1], v[ 5], v[ 9], v[13] ); 7 23 39 55
mov r0, #7
mov r1, #23
mov r2, #39
mov r3, #55
acall chacha_qr
; CHACHA_QR( v[ 2], v[ 6], v[10], v[14] ); 11 27 43 59
mov r0, #11
mov r1, #27
mov r2, #43
mov r3, #59
acall chacha_qr
; CHACHA_QR( v[ 3], v[ 7], v[11], v[15] ); 15 31 47 63
mov r0, #15
mov r1, #31
mov r2, #47
mov r3, #63
acall chacha_qr
; CHACHA_QR( v[ 0], v[ 5], v[10], v[15] ); 3 23 43 63
mov r0, #3
mov r1, #23
mov r2, #43
mov r3, #63
acall chacha_qr
; CHACHA_QR( v[ 1], v[ 6], v[11], v[12] ); 7 27 47 51
mov r0, #7
mov r1, #27
mov r2, #47
mov r3, #51
acall chacha_qr
; CHACHA_QR( v[ 2], v[ 7], v[ 8], v[13] ); 11 31 35 55
mov r0, #11
mov r1, #31
mov r2, #35
mov r3, #55
acall chacha_qr
; CHACHA_QR( v[ 3], v[ 4], v[ 9], v[14] ); 15 19 39 59
mov r0, #15
mov r1, #19
mov r2, #39
mov r3, #59
acall chacha_qr
djnz b, chacha_20_loop
pop psw
pop ar0
pop ar1
pop ar2
pop ar3
pop ar4
pop ar5
pop ar6
pop ar7
pop dph
pop dpl
pop b
pop acc
ret
;
; Update ChaCHa state
;
_chacha_update:
push acc
push b
push dpl
push dph
push ar4
push ar3
push ar2
push ar1
push ar0
push psw
mov dptr, #_chacha20
mov b, #16 ; 16 uint32
update_loop:
mov a, b ; multiply by 4 and subtract 1
rl a
rl a
dec a
; Load Working State variable into registers r0-r3
mov dpl, a
orl a, #64 ; Save pointer to state
mov r4, a
movx a, @dptr
mov r3, a
dec dpl
movx a, @dptr
mov r2, a
dec dpl
movx a, @dptr
mov r1, a
dec dpl
movx a, @dptr
mov r0, a
; Add to State variable
mov a, r4
mov dpl, a
movx a, @dptr
add a, r3
mov r3, a
dec dpl
movx a, @dptr
addc a, r2
mov r2, a
dec dpl
movx a, @dptr
addc a, r1
mov r1, a
dec dpl
movx a, @dptr
addc a, r0
mov r0, a
; Store back state variable in LSB, first sequence (serialized)
mov a, r4
xrl a, #64 ; Save pointer to working state
mov dpl, a
mov a, r0
movx @dptr, a
dec dpl
mov a, r1
movx @dptr, a
dec dpl
mov a, r2
movx @dptr, a
dec dpl
mov a, r3
movx @dptr, a
djnz b, update_loop
pop psw
pop ar0
pop ar1
pop ar2
pop ar3
pop ar4
pop dph
pop dpl
pop b
pop acc
ret
;
; Increase counter in state
;
_chacha_count:
push acc
push dpl
push dph
mov dptr, #_chacha20
mov dpl, #112 + 3
movx a, @dptr
inc a
movx @dptr, a
jnz chacha_count_done
dec dpl
movx a, @dptr
inc a
movx @dptr, a
jnz chacha_count_done
dec dpl
movx a, @dptr
inc a
movx @dptr, a
jnz chacha_count_done
dec dpl
movx a, @dptr
inc a
movx @dptr, a
chacha_count_done:
pop dph
pop dpl
pop acc
ret
+61 -23
View File
@@ -41,6 +41,7 @@ __xdata uip_ipaddr_t server;
#define DHCP_REBIND_LEN 4
#define DHCP_CLIENT_ID 61
#define DHCP_CLIENT_ID_LEN 7
#define DHCP_HOSTNAME 12
#define DHCP_REQUEST_IP 50
#define DHCP_REQUEST_IP_LEN 4
#define DHCP_PARAMS 55
@@ -49,7 +50,7 @@ __xdata uip_ipaddr_t server;
#define DHCP_PARAM_DNS 6
#define DHCP_END 255
#pragma codeseg BANK2
#pragma codeseg BANK3
#pragma constseg BANK2
struct dhcp_pkt {
@@ -93,7 +94,7 @@ void dhcp_prepare_request(void)
DHCP_P->hw_len = 6;
DHCP_P->hops = 0;
DHCP_P->tid = HTONS(dhcp_state.transaction_id);
DHCP_P->tid = dhcp_state.transaction_id;
DHCP_P->delay = HTONS(0);
DHCP_P->flags = 0;
// Clear fields client_ip to bootp_file
@@ -116,6 +117,20 @@ void dhcp_addopt_client_id(void)
}
void dhcp_addopt_hostname(void)
{
uint8_t len = 0;
while (hostname[len])
len++;
if (!len)
return;
DHCP_OPT[dhcp_state.opt_ptr++] = DHCP_HOSTNAME;
DHCP_OPT[dhcp_state.opt_ptr++] = len;
memcpy(&DHCP_OPT[dhcp_state.opt_ptr], hostname, len);
dhcp_state.opt_ptr += len;
}
void dhcp_addopt_request_ip(void)
{
DHCP_OPT[dhcp_state.opt_ptr++] = DHCP_REQUEST_IP;
@@ -124,7 +139,6 @@ void dhcp_addopt_request_ip(void)
DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.current_ip[1];
DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.current_ip[2];
DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.current_ip[3];
memcpy(&DHCP_OPT[dhcp_state.opt_ptr], uip_ethaddr.addr, 4);
}
@@ -136,13 +150,11 @@ void dhcp_addopt_server_id(void)
DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.server[1];
DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.server[2];
DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.server[3];
memcpy(&DHCP_OPT[dhcp_state.opt_ptr], uip_ethaddr.addr, 4);
}
void dhcp_send_discover(void)
{
print_string("dhcp_send_discover called\n");
dhcp_prepare_request();
dhcp_state.opt_ptr = 0;
@@ -152,6 +164,7 @@ void dhcp_send_discover(void)
dhcp_addopt_client_id();
dhcp_addopt_request_ip();
dhcp_addopt_hostname();
DHCP_OPT[dhcp_state.opt_ptr++] = DHCP_PARAMS;
DHCP_OPT[dhcp_state.opt_ptr++] = 3;
@@ -177,7 +190,6 @@ void dhcp_send_discover(void)
void dhcp_send_request(void)
{
print_string("dhcp_send_request called\n");
dhcp_prepare_request();
dhcp_state.opt_ptr = 0;
@@ -188,6 +200,7 @@ void dhcp_send_request(void)
dhcp_addopt_client_id();
dhcp_addopt_request_ip();
dhcp_addopt_server_id();
dhcp_addopt_hostname();
DHCP_OPT[dhcp_state.opt_ptr++] = DHCP_PARAMS;
DHCP_OPT[dhcp_state.opt_ptr++] = 3;
@@ -215,19 +228,21 @@ void ip_opt(__xdata uint8_t * ip)
{
dhcp_state.opt_ptr++;
uint8_t len = DHCP_OPT[dhcp_state.opt_ptr++];
*ip++ = DHCP_OPT[dhcp_state.opt_ptr++];
*ip++ = DHCP_OPT[dhcp_state.opt_ptr++];
*ip++ = DHCP_OPT[dhcp_state.opt_ptr++];
*ip++ = DHCP_OPT[dhcp_state.opt_ptr++];
if (len >= 4)
memcpy(ip, &DHCP_OPT[dhcp_state.opt_ptr], 4);
// There may be more than one IP option, such as 2 DNS servers advertised
dhcp_state.opt_ptr += len - 4;
dhcp_state.opt_ptr += len;
}
void long_opt(void)
uint8_t long_opt(void)
{
dhcp_state.opt_ptr++;
dhcp_state.opt_ptr++;
uint8_t len = DHCP_OPT[dhcp_state.opt_ptr++];
if (len < 4) {
dhcp_state.opt_ptr += len;
return 0;
}
long_value = DHCP_OPT[dhcp_state.opt_ptr++];
long_value <<= 8;
long_value |= DHCP_OPT[dhcp_state.opt_ptr++];
@@ -235,6 +250,8 @@ void long_opt(void)
long_value |= DHCP_OPT[dhcp_state.opt_ptr++];
long_value <<= 8;
long_value |= DHCP_OPT[dhcp_state.opt_ptr++];
dhcp_state.opt_ptr += len - 4;
return 1;
}
@@ -258,16 +275,16 @@ void parse_opts(void)
ip_opt(&dhcp_state.broadcast[0]);
break;
case DHCP_LEASE:
long_opt();
dhcp_state.lease = long_value;
if (long_opt())
dhcp_state.lease = long_value;
break;
case DHCP_REBIND:
long_opt();
dhcp_state.rebind = long_value;
if (long_opt())
dhcp_state.rebind = long_value;
break;
case DHCP_RENEWAL:
long_opt();
dhcp_state.renewal = long_value;
if (long_opt())
dhcp_state.renewal = long_value;
break;
case DHCP_END:
break;
@@ -283,7 +300,7 @@ void parse_opts(void)
void parse_dhcp(void)
{
if (!DHCP_P->tid == HTONS(dhcp_state.transaction_id))
if (DHCP_P->tid != dhcp_state.transaction_id)
return;
if (DHCP_P->cookie[0] != 0x63 || DHCP_P->cookie[1] != 0x82 || DHCP_P->cookie[2] != 0x53 || DHCP_P->cookie[3] != 0x63)
return;
@@ -315,6 +332,18 @@ void parse_dhcp(void)
uip_ipaddr(&uip_hostaddr, dhcp_state.current_ip[0], dhcp_state.current_ip[1], dhcp_state.current_ip[2], dhcp_state.current_ip[3]);
uip_ipaddr(&uip_draddr, dhcp_state.router[0], dhcp_state.router[1], dhcp_state.router[2], dhcp_state.router[3]);
uip_ipaddr(&uip_netmask, dhcp_state.subnet[0], dhcp_state.subnet[1], dhcp_state.subnet[2], dhcp_state.subnet[3]);
/* T1 is optional. Without it the renewal time stays zero, which
* makes dhcp_timer zero, and the callback then sends a REQUEST on
* every pass through LEASING: client and server trade REQUEST and
* ACK as fast as the round trip allows. RFC 2131 has the client
* renew at half the lease when the server does not say, and if
* there is no lease either, pick something rather than zero. Only
* filled in when absent, so a server that asks for a short renewal
* still gets it. */
if (!dhcp_state.renewal)
dhcp_state.renewal = dhcp_state.lease / 2;
if (!dhcp_state.renewal)
dhcp_state.renewal = 60;
dhcp_state.state = DHCP_LEASING;
dhcp_state.ticks = SYS_TICK_HZ;
dhcp_state.dhcp_timer = dhcp_state.renewal > 0xffff ? 0xffff : dhcp_state.renewal;
@@ -324,6 +353,12 @@ void parse_dhcp(void)
void dhcp_start(void) __banked
{
/* Starting twice would leave the first socket bound: uip_udp_new() takes
* the next free slot while the old one keeps port 68, and dhcp_callback()
* matches on that port rather than on the connection, so every leftover
* runs the state machine too and the timers count down once per socket. */
if (dhcp_state.state != DHCP_OFF)
dhcp_stop();
uip_ipaddr(server, 255,255,255,255);
dhcp_state.conn = uip_udp_new(&server, HTONS(DHCPC_SERVER_PORT));
dhcp_state.current_ip[0] = dhcp_state.current_ip[1] = dhcp_state.current_ip[2] = dhcp_state.current_ip[3] = 0;
@@ -334,15 +369,18 @@ void dhcp_start(void) __banked
return;
}
get_random_32();
dhcp_state.transaction_id = SFR_DATA_U32;
// Workaround SDCC bug 4070: dhcp_state.transaction_id = SFR_DATA_U32;
__xdata uint8_t * tid = &dhcp_state.transaction_id;
*tid++ = SFR_DATA_24;
*tid++ = SFR_DATA_16;
*tid++ = SFR_DATA_8;
*tid = SFR_DATA_0;
dhcp_state.state = DHCP_START;
print_string("dhcp_start done\n");
}
void dhcp_stop(void) __banked
{
print_string("dhcp_stop called\n");
uip_udp_remove(dhcp_state.conn);
dhcp_state.state = DHCP_OFF;
}
+30
View File
@@ -64,3 +64,33 @@ Writing 0x1 to register 0x7850 will transmit the frame. The Ethernet frame
checksum and the TCP checksum are automatically calculated (offloaded) by the
ASIC before transmitting on the wire.
## The RTL tag words
The frame header uses the Realtek Remote Control Protocol (RRCP) format or
the like.
The `flags` word:
```
bit15 EFID_EN | 14:12 EFID | 11 PRI_EN | 10:8 PRI |
bit7 KEEP | 6 VSEL | 5 LEARN_DIS | 4:0 VIDX
```
All fields are in network byte order.
* `EFID_EN`, `EFID`: look the destination up under this filtering ID
instead of the port's own
* `PRI_EN`, `PRI`: force the given priority on the frame
* `KEEP`: keep the 802.1Q tagging of the frame exactly as injected,
bypassing the egress tagging rules of the port
* `VSEL`, `VIDX`: classify the frame into the VLAN at this index of the
VLAN table
* `LEARN_DIS`: do not learn the source address from this frame
The `pmask` word: bit 15 is `ALLOW`, bits 14 to 0 are a port mask.
* `ALLOW` clear: the mask is the egress set, the frame goes to exactly
the ports given
* `ALLOW` set: the ASIC looks the destination up as usual and the mask
only limits which ports the result may use
+40
View File
@@ -0,0 +1,40 @@
### 2M-PCB23-V3.2
## Brands
|Brand|Type|Managed|PCB|Flash|Chip RTL|
|---|---|---|---|---|---|
| keepLINK | KP-9000-9XHML-X | Yes| 2M-PCB23-V3.2 | 2M (Winbond 25Q) | |
The original web interface reports this board as hardware version V3.2
(original firmware V100.9.25, January 2026).
## PCB
<img src="photos/2M-PCB23-V3.2-managed/2M-PCB23-V3.2-top.jpeg" width="300" />
## Compared to V3.1
The component placement matches [2M-PCB23-V3.1](2M-PCB23-V3_1.md): same
positions for the flash (U8), the T7/T8/T9/T10 headers, the SFP cage and its
LED, and the unpopulated U7/U9 footprints. Build with
`MACHINE=KP_9000_9XHML_X_V3_2`, which uses the V3.1 machine definition and
only differs in the reported name.
## Tested
Installed from the original firmware through its web interface with
`installer/output/rtlplayground_oem_upgrade.bin`, then:
- RJ45 ports link at 2.5G; LEDs: orange at 2.5G, green below
- SFP port: module detected with vendor/model/serial and DOM readings
(temperature, Vcc, TX bias, TX/RX power), link up at 10G
- factory MAC read from flash (0x1FC000)
- reset button (GPIO48): held for more than 10 s it restores the default config
## Flash access
Reading the flash in-circuit with a SOIC-8 clip did not work: the chip ID
comes back as `EF FF FF` (only the manufacturer byte is right) and reads
return 0xff, the same as on a V3.1 board. The switch chip is powered through
the clip and keeps driving the SPI lines. Make sure a way back is acceptable
before installing, or hold the switch chip in reset while reading.
+55
View File
@@ -0,0 +1,55 @@
# 2G040210GSM
The following is a documentation for the managed switch marked as `2G040210GSM`
and sold by Mokerlink.
### Label specifications
- **Name**: 4-port 2.5G Web Managed Switch
- **Ports**:
- 4 × RJ45: 10/100/1000/2500 Mbps
- 2 × SFP+: 1000 / 2500 / 10000 Mbps
- **Power**: 12V DC, 1A barrel connector
### What works
The device is fully supported:
- All 4 2.5GBASE-T RJ45 ports work at 10/100/1000/2500 Mbps
- The SFP+ port supports 1G, 2.5G and 10G modules
- LEDs work with the same indiciations as the OEM firmware (use KP_9000_6XHML_X2_V1_1 in machine.h if building yourself or the corresponding pre-compiled binary)
- untested due to missing Hardware: SFP+ ports equipped with 1G or 2.5G SFPs.
### Hardware overview
Front
<img src="photos/2M-PCB43-V1.1-managed/2M-PCB43-V1.1-front.jpeg" width="300" />
Label
<img src="photos/2M-PCB43-V1.1-managed/2M-PCB43-V1.1-label.jpeg" width="300" />
### PCB overview
**Board markings**
- Top silkscreen: 2M-PCB43-V1.1
Top side
<img src="photos/2M-PCB43-V1.1-managed/2M-PCB43-V1.1-top.jpeg" width="300" />
Bottom
<img src="photos/2M-PCB43-V1.1-managed/2M-PCB43-V1.1-bottom.jpeg" width="300" />
### J1, serial console
| `J8` pin | Signal |
| -------- | ----------- |
| 1 | RX (Input) |
| 2 | TX (Output) |
| 3 | GND |
| 4 | 3V3 |
## Power supply
Input power is delivered via barell plug, `12V 1A` adapter was provided.
+32
View File
@@ -0,0 +1,32 @@
# MokerLink POE-2G080110GS
The `POE-2G080110GS` is an unmanaged 8x2.5G RJ45 and 1xSFP+ PoE switch sold by Mokerlink. There is also a `POE-2G080110GSM` managed version of the switch which may be similar but this has not been verified on actual hardware.
## Brands
| Brand | Type |Managed| PCB | Flash | Chip RTL |
|--------|------------------|-------|------------------|-----------------|---------------|
| Mokerlink | POE-2G080110GS | No | 2M-PCB51-ML-V1.1 | 4MB (W25Q32JV) | 8373N + 8224N |
## Hardware overview
Front
<img src="photos/POE-2G080110GS/POE-2G080110GS-front.jpg" width="300" />
Label
<img src="photos/POE-2G080110GS/POE-2G080110GS-label.jpg" width="300" />
The stock firmware from a POE-2G080110GS device had a sha256sum of `4c280853465eaad80e1772075c0f2d29c11fb8c561547f06353d3a5443c388e9`.
### PCB
Top silkscreen is marked `2M-PCB51-ML-V1.1`. PoE is provided by RTL8238C. Flash chip is 4 MB Winbond 25Q32JV (U8).
<img src="photos/POE-2G080110GS/POE-2G080110GS-pcb.jpg" width="300" />
> [!CAUTION]
> This device operates on mains voltage, which can be a dangerous or potentially lethal hazard. Even after the device is unplugged, high-voltage capacitors can remain charged afterward and may still shock you. Do not attempt to open/disassemble a mains powered device unless you understand how to do so safely. Also, opening the device may void your warranty.
## Notes
The stock firmware blinks both the 2.5G and 1G LEDs on activity, despite the front of the device indicating that each LED should be independent. The `POE_2G080110GS` machine config behaves as the front of the device indicates (and what seems more logical) -- each port LED turns on and blinks independently.
+70
View File
@@ -0,0 +1,70 @@
# F7008-2.5
Documentation for the unmanaged 8 + 1 switch sold as `7018-2.5G` by Guangnianwei
(光年微), board marking `F7008-2.5`, PCB `F7008-2.5GPOE-V1.2`.
### Brands
| Brand | Type | Managed | PCB | Flash | Chip RTL |
|-------|------|---------|-----|-------|----------|
| Guangnianwei (光年微) | 7018-2.5G | No | F7008-2.5GPOE-V1.2 | W25Q64JVSSIQ | 8373N + 8224N |
### What works
- All eight 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
- SFP+ port with 1G/2.5G/10G modules
- LEDs of the eight RJ45 ports
### Not verified
- The LED pads for the SFP+ cage are not mapped yet. The SFP+ lamps are left
undriven, so an inserted module lights nothing.
- The UART header footprint is present but unpopulated, and no boot log could be
captured from it, so the baud rate is unknown. There are most likely
unpopulated 0-Ohm resistors next to the header; bridging those should bring
the console to life.
### Build target
```
make MACHINE=F7008_2_5
```
### LED wiring
Measured pad to lamp map (`led_mux` index -> lamp):
| Port | green lamp | yellow lamp |
|------|-----------|-------------|
| 1 | pad 0 | pad 1 |
| 2 | pad 2 | pad 3 |
| 3 | pad 4 | pad 5 |
| 4 | pad 6 | pad 8 |
| 5 | pad 9 | pad 10 |
| 6 | pad 12 | pad 15 |
| 7 | pad 18 | pad 21 |
| 8 | pad 24 | pad 25 |
Pads 7, 11, 13, 14, 16, 17, 19, 20, 22, 23, 26 and 27 do not drive an RJ45 lamp.
The front panel is silkscreened `2.5G: Green / 10/100M: Yellow`, and the target
follows it: the green lamp is on while the port is linked at 2.5G, the yellow
lamp blinks while data is flowing.
### PCB overview
The board is marked `F7008-2.5GPOE-V1.2`.
Top side:
<img src="photos/F7008-2.5/PCB-top.jpg" width="600" />
Bottom side:
<img src="photos/F7008-2.5/PCB-bottom.jpg" width="600" />
### Hardware notes
- 4 x `RT4802SNE` magnetics
- Unpopulated UART header footprint, next to the magnetics
- Power input 12V 1A, 180 x 84 x 29 mm metal case
+39
View File
@@ -0,0 +1,39 @@
# FG-4GT-2SX_V2.0
Following is documentation for unmanaged switch marked as `FG-4GT-2SX_V2.0`.
Original software is running UART on 9600 baud rate.
## Brands
* Ruiying RY-4GT-2SX
<img src="photos/FG-4GT-2SX_V2.0/RY-4GT-2SX_label.jpg" width="300" />
## What works
- All four 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
- Both SFP ports supporting 1G, 2.5G and 10G modules
- LEDs
## PCB overview
**Board markings**
- Top silkscreen: FG-4GT-2SX_V2.0
Front panel
<img src="photos/FG-4GT-2SX_V2.0/chassis-front.jpg" width="300" />
Top side
<img src="photos/FG-4GT-2SX_V2.0/PCB-top.jpg" width="300" />
Bottom
<img src="photos/FG-4GT-2SX_V2.0/PCB-bottom.jpg" width="300" />
## Power supply
Input power is delivered via barell plug, `12V 1A` adapter was provided.
+79
View File
@@ -0,0 +1,79 @@
# FG-8GT-1SX
Following is documentation for unmanaged switch marked as `FG-8GT-1SX`.
Original software is running UART on 9600 baud rate.
Using SPI clamp in-board is the only method for initial installation.
### Brands
| Brand | Type | Managed | PCB | Flash | Chip RTL |
|---------|------------|---------|------------|-------------|---------------|
| Ruiying | RY-8GT-1SX | No | FG-8GT-1SX | GD25Q80ESIG | 8373N + 8224N |
### What works
- All eight 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
- SFP port with 1G/2.5G/10G modules
- LEDs
### PCB overview
**Board markings**
- Top silkscreen: FG-8GT-1SX
Top side:
<img src="photos/FG-8GT-1SX/PCB-top.jpg" width="600" />
Bottom:
<img src="photos/FG-8GT-1SX/PCB-bottom.jpg" width="600" />
### Serial console
The PCB has five unpopulated through-holes near the SoC, with a white rectangle surrouding them, labeled as `J18` and a triangle points to the square shaped first pin.
This is where the "expected" UART header should be soldered at, with redundant 3V3 VCC, but also with missing 0Ω resistors between TX/RX and SoC, so simply soldering a header would not work. One need also add the missing resistors or solder the pads together, while making sure not connecting unrelated pads.
Numbered from the triangle, the header pinout is:
| Position | Signal | GPIO | Status |
|----------|--------|--------|-------------|
| 1 | GND | GND | Internal |
| 2 | TX | GPIO31 | Unconnected |
| 3 | RX | GPIO32 | Unconnected |
| 4 | 3V3 | - | Internal |
| 5 | 3V3 | - | Internal |
There're four resistor pads near the header.
| Resistor ID | SoC Side | Header Side |
|-------------|-------------|-------------|
| R1240 | TX / GPIO31 | Pin 2 |
| R1243 | 3V3 | - |
| R1242 | 3V3 | Pin 3 |
| R1241 | RX / GPIO32 | - |
To get TX working, solder a 0Ω resistor between the pads for R1240 or solder them together.
For RX However, the designer certainly made a mistake, as the SoC-side lines to the resistor expected for Rx (R1242) and 3V3 (R1241) are swapped. For RX to work, the R1241 SoC side and R1242 header side shall be connected, so either:
- Solder: R1241 SoC side -> R1241 header side -> R1242 header side
- Jump wire: R1241 SoC side -> R1242 header side
Be sure not to bring R1242 SoC side to the connection as that would wire Rx to 3v3.
A complete working serial header should look like following on this PCB:
<img src="photos/FG-8GT-1SX/Serial.png" width="600" />
- **Settings**: 115200 baud / 8N1 / 3.3V TTL
- Connect a USB-TTL adapter: adapter GND → pin 1, RX → pin 2, TX → pin 3
### Power supply
Input power is delivered via barell plug, `12V 1A` adapter was provided.
+75
View File
@@ -0,0 +1,75 @@
# Hasivo S1100WP-8GT-1SX-SE
RTL8373-based 8×2.5G PoE + 1×SFP switch.
### Label specifications
- **Manufacturer**: Hasivo
- **Model**: S1100WP-8GT-1SX-SE
- **Ports**:
- 8 × RJ45: 10/100/1000/2500 Mbps with PoE
- 1 × SFP: 1G / 2.5G / 10G
- **Power input**: DC 52V 2.5A
- **Stock firmware defaults**: IP 192.168.0.1, user/password `admin`/`admin`
### What works
- All eight 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps (PoE is not configurable via RTLPlayground)
- SFP port supporting 1G, 2.5G and 10G modules
- LEDs: green (2.5G) and amber (1G/100M/10M) per copper port; combined link/act on the SFP port
### Photos
Front panel:
<img src="photos/HASIVO_S1100WP-8GT-1SX-SE/front.jpg" width="600" />
Label:
<img src="photos/HASIVO_S1100WP-8GT-1SX-SE/label.jpg" width="600" />
### PCB overview
Top side:
<img src="photos/HASIVO_S1100WP-8GT-1SX-SE/pcb_top.jpg" width="600" />
Bottom side:
<img src="photos/HASIVO_S1100WP-8GT-1SX-SE/pcb_bottom.jpg" width="600" />
### Port layout
| Logical port | Physical port | Type |
|--------------|---------------|--------|
| 1-8 | 1-8 | Copper |
| 9 | 9 | SFP |
### LED configuration
Copper ports use LED SET0, the SFP port uses LED SET1.
| SET | LED0 | LED2 |
|------|-----------------------------------------------|------------------------------------------------------|
| SET0 | Green — lights on 2.5G link with activity | Amber — lights on 1G / 100M / 10M link with activity |
| SET1 | All speeds — lights on any link with activity | — |
### SFP GPIO assignments
| SFP (logical 9) | pin_detect (ModAbs) | pin_los | pin_tx_disable | SerDes | I2C SDA | I2C SCL |
|-----------------|---------------------|---------|----------------|--------|-----------------|---------------------|
| SFP | GPIO30_ACL_BIT3_EN | GPIO37 | GPIO_NA | SDS1 | GPIO39_I2C_SDA4 | GPIO40_I2C_SCL3_MDC1 |
### Machine configuration
- `reset_pin`: GPIO54_ACL_BIT2_EN
- High LEDs (pads 27-29): mux = `LED_27 | LED_28_SYS | LED_29`, enable = `LED_28_SYS | LED_29`
### PoE
The 8 copper ports support PoE powered by a DC 52V 2.5A input. Power management is handled by two HS104PTI chipsets over I2C (GPIO47_I2C_SDA0, GPIO46_I2C_SCL0):
* Chip 1 (I2C address 0x1A): 2.5G Ports 1–4
* Chip 2 (I2C address 0x2A): 2.5G Ports 5–8
Note: The current implementation does not yet support PoE management. Consequently, PoE currently operates in standalone mode without software control.
-62
View File
@@ -1,62 +0,0 @@
# Hisource Hi-K0402WS
Following is documentation for unmanaged switch marked as `Hi-K0402WS`.
Original software is running UART on 9600 baud rate.
Using SPI clamp in-board is the only method for initial installation.
The board has two flash chips `BY25Q16BS` with 16M-bit size. The front switch, switches between the two flash chips.
These can be programed independently by using said switch - so it is e.g. possible to run the original and new firmware in parallel.
### Label specifications
- **Name**: 2.5G Ethernet Switch
- **Model**: Hi-K0402WS
- **Ports**:
- 4 × RJ45: 10/100/1000/2500 Mbps
- 2 × SFP: 1000 / 2500 / 10000 Mbps
### What works (expected from label + similar devices)
- All four 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
- Both SFP ports supporting 1G, 2.5G and 10G modules
- LEDs
### PCB overview
**Board markings**
- Top silkscreen: PCB-KO4022W-V3.0 / DIP-KO4022WS-V3.0
Top side
<img src="photos/K0402W-V3.0-unmanaged\PCB-top.jpg" width="300" />
Bottom
<img src="photos/K0402W-V3.0-unmanaged\PCB-bottom.jpg" width="300" />
### T2, serial console
| `J2` pin | Signal |
| -------- | ----------- |
| 1 | 3V3 |
| 2 | RX (Input) |
| 3 | TX (Output) |
| 4 | GND |
## Power supply
Input power is delivered via barell plug, `12V 1A` adapter was provided.
Board has two supply rails. `0.95` and `3.3` volt.
### `0.95` Core Voltage
Voltage is made by a `Techcode TD1720` .
### `3.3` Voltage
Voltage is created by chip marked as `Techcode TD1720`.
**There seems to have been a miscalculation when choosing the inductor and the device is ~25% more efficient with an 5V power supply.**
+64
View File
@@ -0,0 +1,64 @@
# Lianguo HYWS-SGT0108S
Following is documentation for unmanaged switch marked as `HYWS-SGT0108S`.
Original software is running UART on 9600 baud rate. Output is very minimal.
Using SPI clamp in-board is the only method for initial installation and update.
Stock flash chip is 512KB in size. Consider replacing for update through Web UI.
- Header for uart is clearly identified.
- The red LED act as a powered-on LED.
- There is no SYS LED.
- There is a footprint for a Reset button. Unpopulated. Unclear if routed back to RTL8373.
### Label specifications
- **Name**: 2.5G Ethernet Switch 8+1
- **Model**: HYWS-SGT0108S
- **Ports**:
- 8 × RJ45: 10/100/1000/2500 Mbps
- 1 × SFP: 1000 / 2500 / 10000 Mbps
### What works
- All eight 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
- SFP port supporting 1G, 2.5G and 10G modules
- RJ45 LEDs (Blue+White)
- SFP Single LED (Blue)
### Assembled unit
Top side
<img src="photos/HYWS-SGT0108S-unmanaged\Assembled-top.jpg" width="300" />
Front
<img src="photos/HYWS-SGT0108S-unmanaged\Assembled-front.jpg" width="300" />
Bottom
<img src="photos/HYWS-SGT0108S-unmanaged\Assembled-bottom.jpg" width="300" />
### PCB overview
**Board markings**
- Top silkscreen: 2G5F_20G_V1.01 / 2023-09-28
Top side
<img src="photos/HYWS-SGT0108S-unmanaged\PCB-top.jpg" width="300" />
Bottom
<img src="photos/HYWS-SGT0108S-unmanaged\PCB-bot.jpg" width="300" />
Original SPI Flash Chip
<img src="photos/HYWS-SGT0108S-unmanaged\og_spi_flash.jpg" width="300" />
## Power supply
Input power is delivered via barell plug, `12V 1A` adapter was provided.
+41 -5
View File
@@ -1,17 +1,36 @@
# Keeplink KP-9000-6XH-X2
# Keeplink KP-9000-6XH-X2 / KP-9000-6XHML-X2
Following is documentation for unmanaged switch marked as `KP-9000-6XH-X2`.
Following is documentation for switches marked as `KP-9000-6XH-X2` or
`KP-9000-6XHML-X2`.
Using SPI clamp in-board is the only method for initial installation.
### Label specifications
- **Name**: 4X 2.5G RJ45 Port + 2 X 10G SFP+ Port
- **Model**: KP-9000-6XH-X2
- **Model**: KP-9000-6XH-X2 / KP-9000-6XHML-X2
- **Ports**:
- 4 × RJ45: 10/100/1000/2500 Mbps
- 2 × SFP+: 1000 / 2500 / 10000 Mbps
### Machine target
These devices exist with different PCB revisions. Select the machine target by
the PCB silkscreen, not by the label on the case. The `6XH` / `6XHML`
distinction appears to be a stock firmware, SKU or label difference, not a
reliable indicator of PCB wiring. The PCB revision defines the hardware layout.
| PCB silkscreen | Known labels / devices | Recommended machine target | Equivalent target | Legacy target |
| --- | --- | --- | --- | --- |
| `2M-PCB43-V1.1` | Mokerlink 2G040210GSM web-managed 4+2 switch | `MACHINE_KP_9000_6XHML_X2_V1_1` | `MACHINE_KP_9000_6XH_X2_V1_1` | `MACHINE_KP_9000_6XHML_X2` |
| `2M-PCB43-V1.2` | `KP-9000-6XHML-X2` | `MACHINE_KP_9000_6XHML_X2_V1_2` | `MACHINE_KP_9000_6XH_X2_V1_2` | `MACHINE_KP_9000_6XHML_X2` |
| `2M-PCB43-V2.1` | `KP-9000-6XH-X2`, `KP-9000-6XHML-X2` | `MACHINE_KP_9000_6XH_X2_V2_1` or `MACHINE_KP_9000_6XHML_X2_V2_1` | same V2.1 layout | `MACHINE_KP_9000_6XH_X2` |
The V1.1 and V1.2 boards currently use the same V1.x GPIO, SFP, port and LED
layout. The V2.1 board uses a different V2.1 layout with custom LED muxing.
Legacy targets are preserved for compatibility with already-tested devices, but
new builds should prefer the explicit PCB-revision targets.
### What works
- All four 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
@@ -19,7 +38,7 @@ Using SPI clamp in-board is the only method for initial installation.
- LEDs
- untested due to missing Hardware: SFP+ ports equipped with 1G or 2.5G SFPs.
### Hardware overview
### Hardware overview: 2M-PCB43-V2.1
Front side:
@@ -44,6 +63,24 @@ Bottom
<img src="photos/2M-PCB43-V2.1-unmanaged/2M-PCB43-V2.1-bottom.jpg" width="600" />
### Hardware overview: 2M-PCB43-V1.1 / V1.2
The V1.1 board has been reported in the Mokerlink 2G040210GSM web-managed 4+2
switch. The V1.2 board has been seen in managed `KP-9000-6XHML-X2` devices.
Both use the same V1.x layout.
Label:
<img src="photos/KP-9000-6XHML-X2/label.jpg" width="600" />
Top side:
<img src="photos/KP-9000-6XHML-X2/pcb_top.jpg" width="600" />
Bottom:
<img src="photos/KP-9000-6XHML-X2/pcb_bottom.jpg" width="600" />
## Reset Button
There's an unpopulated Reset button on the front left side of the PCB.
@@ -54,4 +91,3 @@ The front case has already the hole in the metal case, you just have to punch a
## Power supply
Input power is delivered via barell plug, `12V 1A` adapter was provided.
+53
View File
@@ -0,0 +1,53 @@
# IG108
Following is documentation for unmanaged switch marked as `IG108`.
Original software is running UART on 9600 baud rate.
Using SPI clamp in-board is the only method for initial installation.
### Brands
| Brand | Type | Managed | PCB | Flash | Chip RTL |
|---------|------------|---------|------------|-------------|---------------|
| Goalake | IG108 | No | PB-2132 | cFeon Q32A | 8373N + 8224N |
| Netcore | GS9 | No | PB-2132 | cFeon Q32A | 8373N + 8224N |
### What works
- All eight 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
- SFP port with 1G/2.5G/10G modules
- LEDs
### PCB overview
**Board markings**
- Top silkscreen: PB-2132
Front Goalake:
<img src="photos/PB-2132/case.jpg" width="600" />
Label Goalake:
<img src="photos/PB-2132/label.jpg" width="600" />
Top side:
<img src="photos/PB-2132/PCB-top.jpg" width="600" />
Bottom:
<img src="photos/PB-2132/PCB-bottom.jpg" width="600" />
### Serial console
The PCB has unpopulated through-holes near the SFP Port, with description of each pin, works out of the box.
- **Settings**: 115200 baud / 8N1 / 3.3V TTL
### Power supply
Input power is delivered via barell plug, `12V 1A` adapter was provided.
+97
View File
@@ -0,0 +1,97 @@
Hisource Hi-K0402WS V2
RTL8372-based 4×2.5G + 2×SFP+ unmanaged switch.
Using SPI clamp in-board is the only method for initial installation.
### Label specifications
- **Manufacturer**: HISOURCE
- **Model**: Hi-K0402WS V2
- **Ports**:
- 4 × RJ45: 10/100/1000/2500 Mbps
- 2 × SFP+: 1G / 2.5G / 10G
### What works
- All four 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
- Both SFP+ ports supporting 1G, 2.5G and 10G modules
- LEDs: amber (2.5G) and green (1G/100M/10M) per copper port; combined link/act on SFP ports
### PCB overview
**Board markings**
- PCB-K0402WS-V2.0
- DIP-K0402WS-V2.0
**Key components**
- U1: SPI NOR flash, 2 MiB
- S1: unpopulated slide switch footprint (three through-holes used as serial console)
Front panel
<img src="photos/PCB-K0402WS-V2.0/chassis-front.jpg" width="400" />
Bottom label
<img src="photos/PCB-K0402WS-V2.0/chassis-bottom.jpg" width="400" />
Top side (PCB)
<img src="photos/PCB-K0402WS-V2.0/PCB-top.jpg" width="300" />
### Port layout
| Front panel position | Logical port | Physical port | Type |
|----------------------|--------------|---------------|---------|
| SFP left | 8 | 5 | SFP+ |
| RJ45 1 | 4 | 1 | Copper |
| RJ45 2 | 5 | 2 | Copper |
| RJ45 3 | 6 | 3 | Copper |
| RJ45 4 | 7 | 4 | Copper |
| SFP right | 3 | 6 | SFP+ |
### Serial console
The PCB has three unpopulated through-holes intended for a slide switch, directly connected to UART0.
Numbered from the left (SFP port side), the pinout is:
| Position (left→right) | Signal | GPIO |
|-----------------------|--------|--------------------------|
| 1 (leftmost) | RX | GPIO32\_UART0\_RX (32) |
| 2 (middle) | GND | GND |
| 3 (rightmost) | TX | GPIO31\_UART0\_TX (31) |
- **Settings**: 115200 baud / 8N1 / 3.3V TTL
- Connect a USB-TTL adapter: adapter TX → pin 1, GND → pin 2, adapter RX → pin 3
### LED configuration
Copper ports use LED SET0, SFP ports use LED SET1.
| SET | LED0 | LED2 |
|------|--------------------------------------------------|---------------------------------------------------|
| SET0 | Amber — lights on 2.5G link | Green — lights on 1G / 100M / 10M link |
| SET1 | All speeds — lights on any link with activity | — |
LED pad to physical port mapping:
| GPIO pads | Port |
|-----------|-------------------------|
| GPIO8–11 | Physical port 5 (left SFP) |
| GPIO12–14 | Physical port 1 (RJ45 1) |
| GPIO15–17 | Physical port 2 (RJ45 2) |
| GPIO18–20 | Physical port 3 (RJ45 3) |
| GPIO21–23 | Physical port 4 (RJ45 4) |
| GPIO24–27 | Physical port 6 (right SFP) |
### SFP GPIO assignments
| SFP | pin\_detect (ModAbs) | pin\_los | SerDes | I2C SDA | I2C SCL |
|------------------|-----------------------------|------------------------|--------|----------------------|--------------------------|
| Left (logical 8) | GPIO30\_ACL\_BIT3\_EN | GPIO37 | SDS1 | GPIO39\_I2C\_SDA4 | GPIO40\_I2C\_SCL3\_MDC1 |
| Right (logical 3)| GPIO50\_I2C\_SCL2\_UART1\_TX | GPIO51\_I2C\_SDA2\_UART1\_RX | SDS0 | GPIO41\_I2C\_SDA3\_MDIO1 | GPIO40\_I2C\_SCL3\_MDC1 |
GPIO assignments were verified by observing GPIO state changes during SFP module insertion/removal
and cross-checked against an original firmware register dump.
`pin_tx_disable` is GPIO\_NA on both ports (original firmware keeps all GPIOs as inputs).
+79
View File
@@ -0,0 +1,79 @@
# PCB-K0402WS-V3.0
Following is documentation for a variety of unmanaged switch internally marked as `PCB-K0402WS-V3.0`. They are sold under many brands.
Original software is running UART on 9600 baud rate.
Note during opening the device: there might be a hidden 5th screw on the back
of the device just above the big label, might be covered by a QC sticker.
### Brands
* Hisource Hi-K0402WS
<img src="photos/PCB-K0402WS-V3.0/HiSource_HI-K0402WS.jpg" width="300" />
* Ztyuav Z-QWYT0402
<img src="photos/PCB-K0402WS-V3.0/Ztyuav_Z-QWYT0402.jpg" width="300" />
<img src="photos/PCB-K0402WS-V3.0/Ztyuav_Z-QWYT0402_label.jpg" width="300" />
* Siscolink SL-G0402F
<img src="photos/PCB-K0402WS-V3.0/Siscolink SL-G0402F.jpg" width="300"/>
### Programming
Using SPI clamp in-board is the only method for initial installation.
The board has two flash chips `BY25Q16BS` with 16M-bit size. The front switch, switches between the two flash chips.
These can be programed independently by using said switch - so it is e.g. possible to run the original and new firmware in parallel.
The switch actually controls the HOLD line of each flash chip, and toggling the switch results in a reboot.
If the programming clip keeps HOLD not connected, the flashing will commence on whatever the switch selected, regardless on which chip was clipped.
For the initial flash (at least with flashrom), the bin file produced by the build is much smaller than the flash chip, it is suggested to pad the file to keep flashrom happy: `truncate -s 2097152 rtlplayground-*-PCB_K0402WS_V3.bin`. Note: do not then proceed to use this resulting padded file for the web flashing (as it bricks the device), use the original unpadded .bin.
### What works (expected from label + similar devices)
- All four 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
- Both SFP ports supporting 1G, 2.5G and 10G modules
- LEDs
### PCB overview
**Board markings**
- Top silkscreen: PCB-KO4022W-V3.0 / DIP-KO4022WS-V3.0
Top side
<img src="photos/PCB-K0402WS-V3.0/PCB-top.jpg" width="300" />
Bottom
<img src="photos/PCB-K0402WS-V3.0/PCB-bottom.jpg" width="300" />
### T2, serial console
| `J2` pin | Signal |
| -------- | ----------- |
| 1 | 3V3 |
| 2 | RX (Input) |
| 3 | TX (Output) |
| 4 | GND |
## Power supply
Input power is delivered via barell plug, `12V 1A` adapter was provided.
Board has two supply rails. `0.95` and `3.3` volt.
### `0.95` Core Voltage
Voltage is made by a `Techcode TD1720` .
### `3.3` Voltage
Voltage is created by chip marked as `Techcode TD1720`.
**There seems to have been a miscalculation when choosing the inductor and the device is ~25% more efficient with an 5V power supply.**
+43
View File
@@ -0,0 +1,43 @@
# Steamemo IG204-V1
Following is documentation for unmanaged switch marked as `IG204-V1`.
Using SPI clamp in-board is the only method for initial installation.
### Label specifications
- **Name**: 2.5G Ethernet Switch
- **Model**: IG204 V1
- **Ports**:
- 4 × RJ45: 10/100/1000/2500 Mbps
- 2 × SFP: 1000 / 2500 / 10000 Mbps
### What works (expected from label + similar devices)
- Four 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
- Two SFP ports supporting 1G, 2.5G and 10G modules
- LEDs
### PCB overview
**Board markings**
- Top silkscreen: PB-2131
Top side
<img src="photos/STEAMEMO_IG204_V1/PCB-top.jpg" width="600" />
### Connectors
### T7, serial console
| `T7` pin | Signal |
| -------- | ----------- |
| 1 | TX (Output) |
| 2 | GND |
| 3 | RX (Input) |
| 4 | 3V3 |
### Power supply
Input power is delivered via barell plug, `12V 1A` adapter was provided.
+7
View File
@@ -1,4 +1,11 @@
# SWTG018AS-A V2.0
## Brands
| Brand | Type |Managed| PCB | Flash | Chip RTL |
|---------|------------------|-------|------------------|-----------------|---------------|
| Ampcom | SWTG018AS-A V2.0 | No | SWTG018AS-A V2.0 | 2MB | 8273N + 8224N |
| Horaco | HC-SWTGW218AS-A | Yes | SWTG018AS-A V2.0 | 2MB(25Q16JVSIQ) | 8273N + 8224N |
| GoodTop | GT-ST018M | Yes | SWTG018AS-A V2.0 | ? | 8273N + 8224N |
The following is a documentation for the unmanaged switch marked as `SWTG018AS-A V2.0`.
It is e.g. sold under the Ampcom brand, but no branh-markings are found on the device.
+43
View File
@@ -0,0 +1,43 @@
### SWTG024AS-A-V2.0.1_4C_2SFP
It is highly similar to SWTG024AS-V2.0, with the only difference being the GPIO configuration for the SFP port.
## Brands
|Brand|Type|Managed|PCB|Flash|Chip RTL|
|---|---|---|---|---|---|
| Horaco | ZX-SG4T2 | | PCB-SWTG024AS-A-V2.0.1_19650 | P25D40SH | ??? |
### Label specifications
- **Name**:
- **Ports**:
- 4 × RJ45: 10/100/1000/2500 Mbps
- 2 × SFP+: 1000 / 2500 / 10000 Mbps
<img src="photos/SWTG024AS-A-V2_0_1_19650/horaco-zx-sg4t2-label.jpg" width="300" />
### What works
The device is fully supported:
- ALL 2.5GBASE-T RJ45 ports work at 10/100/1000/2500 Mbps
- The SFP+ port supports 1G, 2.5G and 10G modules
### PCB overview
**Board markings**
- Top silkscreen: PCB-SWTG024AS-A-V2.0.1_19650
Top side
<img src="photos/SWTG024AS-A-V2_0_1_19650/horaco-zx-sg4t2-pcb-top.jpg" width="300" />
Bottom
<img src="photos/SWTG024AS-A-V2_0_1_19650/horaco-zx-sg4t2-pcb-bottom.jpg" width="300" />
### J1, serial console
| `J1` pin | Signal |
| -------- | ----------- |
| 1 | 3V3 |
| 2 | GND |
| 3 | RX (Input) |
| 4 | TX (Output) |
+44
View File
@@ -0,0 +1,44 @@
### SWTG024AS-A-V2.0.1_5C_1SFP
It is highly similar to SWTG024AS-V2.0, with the only difference being the GPIO configuration for the SFP port.
## Brands
|Brand|Type|Managed|PCB|Flash|Chip RTL|
|---|---|---|---|---|---|
| Horaco | HC-SWTGW215AS | | PCB-SWTG024AS-A-V2.0.1_19650 | W25Q16JV | 8272N |
### Label specifications
- **Name**:
- **Ports**:
- 5 × RJ45: 10/100/1000/2500 Mbps
- 1 × SFP+: 1000 / 2500 / 10000 Mbps
<!-- <img src="" width="300" /> -->
### What works
The device is fully supported:
- ALL 2.5GBASE-T RJ45 ports work at 10/100/1000/2500 Mbps
- The SFP+ port supports 1G, 2.5G and 10G modules
- LEDs work with the same indiciations as the OEM firmware
### PCB overview
**Board markings**
- Top silkscreen: PCB-SWTG024AS-A-V2.0.1_19650
Top side
<!-- <img src="" width="300" /> -->
Bottom
<!-- <img src="" width="300" /> -->
### J1, serial console
| `J1` pin | Signal |
| -------- | ----------- |
| 1 | 3V3 |
| 2 | GND |
| 3 | RX (Input) |
| 4 | TX (Output) |
+49
View File
@@ -0,0 +1,49 @@
### SWTG024AS-V2.0
## Brands
|Brand|Type|Managed|PCB|Flash|Chip RTL|
|---|---|---|---|---|---|
| hongyavision | LG-SG5T1 | No | PCB-SWTG024AS-V2.0_16895 | 25Q40 | 8272 |
### Label specifications
- **Name**:
- **Ports**:
- 5 × RJ45: 10/100/1000/2500 Mbps
- 1 × SFP+: 1000 / 2500 / 10000 Mbps
- **Power**: 12V DC, 1A 5525 connector
<img src="photos/SWTG024AS-V2.0/label.jpg" width="300" />
### What works
The device is fully supported:
- ALL 2.5GBASE-T RJ45 ports work at 10/100/1000/2500 Mbps
- The SFP+ port supports 1G, 2.5G and 10G modules
- LEDs work with the same indiciations as the OEM firmware
- Online update does not work with 512KiB flash.
### PCB overview
**Board markings**
- Top silkscreen: PCB-SWTG024AS-V2.0
Top side
<img src="photos/SWTG024AS-V2.0/pcb_top.jpg" width="300" />
Bottom
<img src="photos/SWTG024AS-V2.0/pcb_bottom.jpg" width="300" />
### J1, serial console
| `J1` pin | Signal |
| -------- | ----------- |
| 1 | GND |
| 2 | RX (Input) |
| 3 | TX (Output) |
Note,`R52`、`R53`may not be installed.You need to bridge them using either solder or resistors.
## Power supply
Input power is delivered via barell plug, `12V 1A` adapter was provided.
+2 -2
View File
@@ -9,8 +9,8 @@ Also the RJ45 connectors can be all plastic/non-shielded or with metal shielding
|Brand|Type|Managed|PCB|PCB Label|Flash|Chip RTL|
|---|---|---|---|---|---|---|
| LIANGUO |SWTG024AS |No| SWTG024AS-v2.0-17452 | CM-23-11-2336 023-17453| 512 KiB | 8272 |
| Haraco |ZX-SWTG124AS | Yes | SWTG024AS-v2.0 | ??? | ??? | 8272 |
| Xikestore |SKS3200M-4GPY2XF | Yes | SWTG024AS-v1.0 | CM-23-08-2043 023-16721 | ??? | 8272 |
| Horaco |ZX-SWTG124AS | Yes | SWTG024AS-v2.0 | ??? | ??? | 8272 |
| Xikestore |SKS3200M-4GPY2XF | Yes | SWTG024AS-v1.0 | CM-23-08-2043 023-16721 | 2048 KiB | 8272 |
| Sodola | SL-SWTG124AS-D | Yes | SWTG024AS-v2.0-17452 | ??? | 2048 KiB | 8272 |
## PCB
+36 -17
View File
@@ -1,9 +1,11 @@
### ZX-SWTGW215AS
# ZX-SWTGW215AS
## Brands
|Brand|Type|Managed|PCB|Flash|Chip RTL|
|---|---|---|---|---|---|
| Lianguo | ZX-SWTGW215AS | Yes | PCB-SWTG115AS-V2.0 | FM25Q16A | 8272 |
| Lianguo | ZX-SWTGW215AS | Yes | PCB-SWTG115AS-V2.0 | FM25Q16A | 8372 |
| sold as Lianguo, actual device unbranded | ZX-SWTGW215AS | Yes | PCB-SWTG115AS-V2.1 | W25Q16JVSIQ (2MiB) | 8372 |
## RTLPlayground target
@@ -12,13 +14,23 @@ Use machine target `MACHINE_LIANGUO_ZX_SWTGW215AS` for this device.
Physical hardware verification: 5x RJ45 ports + 1x SFP port.
Port 5 RJ45 is interfaced through a RTL8221B IC.
## PCB
## Label (V2.0)
<img src="photos/ZX-SWTGW215AS/pcb_top.jpg" width="300" />
<img src="photos/ZX-SWTGW215AS-V2.0/label.jpg" width="300" />
# Connectors
## PCB (V2.0)
## Port overview
<img src="photos/ZX-SWTGW215AS-V2.0/pcb_top.jpg" width="300" />
<img src="photos/ZX-SWTGW215AS-V2.0/pcb_bottom.jpg" width="300" />
## PCB (V2.1)
<img src="photos/ZX-SWTGW215AS-V2.1/pcb-top.jpg" width="300" />
<img src="photos/ZX-SWTGW215AS-V2.1/pcb-bottom.jpg" width="300" />
## Connectors
### Port overview
```
┌──────────────────────────────────────────────────────────────────────────────────┐
@@ -36,7 +48,7 @@ Port 5 RJ45 is interfaced through a RTL8221B IC.
| RJ45 | 3, 4, 5, 6, 7 | 1-5 |
| SFP | 8 | 6 |
## J4
### J4
* Location: SFP connector `J4`.
* Connected to: 10GMAC number 8, SerDes 1.
@@ -48,10 +60,11 @@ Port 5 RJ45 is interfaced through a RTL8221B IC.
|5| MODDEF1 – SCL | GPIO40 | I2C SCL |
|6| MODDEF0 – PRESENT | GPIO30 | Detect |
|8| LOS | GPIO37 | RX Loss of Signal |
### Notes
#### Notes
* Not all signals were mapped mechanically, hence they've been left out of documentation.
## T3, Slave Interface
### T3, Slave Interface
This connector goes to U4 `I2C EEPROM` and U10 `SPI FLASH` (mappings identical to SWTG024AS).
@@ -65,11 +78,12 @@ For detailed Slave Interface functionality and protocol information, see [T3 doc
|4| VCC |
|5| 33R -> U10-P2 | SPI-DO/D1 |
|6| U10-P1 | SPI-CS |
### Notes
#### Notes
* 1 pin is square shaped.
## T5, Serial Console
### T5, Serial Console
|`T5` pin|GPIO|Signal|
|---|---|---|
@@ -77,10 +91,11 @@ For detailed Slave Interface functionality and protocol information, see [T3 doc
| 2 | GND | |
| 3 | GPIO32 | U0RXD (Input) |
| 4 | 3V3 | |
### Notes
#### Notes
* 1 pin is square shaped.
## T8
### T8
|`T8` pin|GPIO|Signal|
|---|---|---|
@@ -91,18 +106,19 @@ For detailed Slave Interface functionality and protocol information, see [T3 doc
| 5 | GPIO47 | |
| 6 | GPIO49 | |
### Notes
#### Notes
* 1 pin is square shaped.
* Mapping unverified but assumed the same as [LIANGUO SWTG024AS](SWTG024AS.md#t8).
# Reset Circuit
## Reset Circuit
| Function | GPIO |
|---|---|
| Reset button | GPIO54 |
### Notes
* Circuit is active-low
# GPIO
## GPIO
Note: T3/U4/U10-related signal annotations below are copied from [LIANGUO SWTG024AS T3 section](SWTG024AS.md#t3-slave-interface) as well as T8 port from [LIANGUO SWTG024AS T8 section](SWTG024AS.md#t8). They should be treated as assumed identical for ZX-SWTGW215AS as it has not been 100% confirmed true at the moment.
@@ -141,7 +157,7 @@ Note: T3/U4/U10-related signal annotations below are copied from [LIANGUO SWTG02
| 40000000 | GPIO30 | J4-6 | SFP DETECT | | GPIO62 | | |
| 80000000 | GPIO31 | T5-1 | U0TXD | | GPIO63 | | |
# LEDs
## LEDs
| NAME | GPIO | Port(s) | Function | Notes |
| ---- | ---- | ---- | ---- | ---- |
@@ -162,3 +178,6 @@ Note: T3/U4/U10-related signal annotations below are copied from [LIANGUO SWTG02
While [SWTG024AS.md](SWTG024AS.md) can be used as a general reference for hardware concepts and interface specifications, this device should not be assumed to be identical beside the difference implicitely highlighted below. Not all information has been validated for compatibility with the SWTG215AS. Consult the SWTG024AS documentation with caution and verify any critical details against this device's.
## Flashing
For devices with Winbond 25Q16JVSIQ, flashing is possible for example using flashprog with device "W25Q16.V". Note that you should not overwrite the whole flash to keep device-specific data (like the MAC address at 0x1fc000). Also, creating a backup of the entire falsh before is of course a very good idea.
+1
View File
@@ -6,6 +6,7 @@
| Mokerlink | ZX-SWTGW218AS | Yes| SWTG118AS-V2.0-16029 | 2MB (FM25Q16A)| 8273N + 8224N |
| Sodola | | | | | |
| Horaco | | | | | |
| XikeStor | SKS3200-8E1X | Yes | SWTG118AS-V2.1-17462 | 2MB (25Q16JVSIQ) | |
## Photos
+53
View File
@@ -0,0 +1,53 @@
# ZX310S-4T2XT
The following is a documentation for the managed switch marked as
`ZX310S-4T2XT` and sold by Horaco.
The original software is running UART on 57600 baud rate 8N1.
CPU: RTL8372
Flash: 2MByte Winbond W25Q16DV (U3)
PHY 2x RTL8261BE
### Label specifications
- **Name**:
- **Ports**:
- 4 × RJ45: 10/100/1000/2500 Mbps
- 2 x RJ45: 10/100/1000/2500/5000/10000 Mbps
- **Power**: 12V DC, 2A barrel connector
<img src="photos/ZX310S-4T2XT/label.jpg" width="300" />
### What works
The device is fully supported:
- All 4 2.5GBASE-T RJ45 ports work at 10/100/1000/2500 Mbps, including EEE
- The 10GBit ports works, including EEE.
- LEDs work with the same indiciations as the OEM firmware
### PCB overview
**Board markings**
- Top silkscreen: PCB-SL310S-4T2XT-V1.0.0-22273
Top side
<img src="photos/ZX310S-4T2XT/pcb_top.jpg" width="300" />
Bottom
<img src="photos/ZX310S-4T2XT/pcb_bottom.jpg" width="300" />
### J1, serial console
| `J1` pin | Signal |
| -------- | ----------- |
| 1 | TX (Output) |
| 2 | RX (Input) |
| 3 | GND |
| 4 | 3V3 |
## Power supply
Input power is delivered via barell plug, `12V 2A` adapter was provided.
Binary file not shown.

After

Width:  |  Height:  |  Size: 480 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 907 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

+79
View File
@@ -0,0 +1,79 @@
# The health command
`health` prints a one-page snapshot of the firmware's vital signs on the
serial console. It exists for the situations where the switch misbehaves and
every probe from the network measures the network instead of the device: the
dump is taken from inside, costs one command, and works while HTTP does not.
```
> health
up 0000001f ticks 0x00001f4c loops 0x000016da rx 0x024e
link max 01 slow 0x0000
sfp max 74 slow 0x0001
rx max 00 slow 0x0000
tx max 00 slow 0x0000
stp max 04 slow 0x0001
cmd max 02 slow 0x0001
sp 80 untouched 62
tcp st 00 tmr 00 rtx 00 rto 00 len 0x0000 mss 0x05b4
lport 0x0000 rport 0x0000 rip 0x0000 0x0000
httpd left 0x0000 entry 00 stp 01 mvlan 0x0002
```
The instrumentation is not part of a default build. It costs about 1.1 kB
of bank 2, a few bytes of xdata and seven banked calls per main-loop pass,
which is nothing a user needs unless they are chasing a problem, so it is
switched on at build time:
```
make MACHINE=SWTGW218AS HEALTH=1
```
Without it the hooks compile to nothing and `health` is not a command.
All figures are raw hex. The counters are cumulative: take two dumps a known
time apart and the differences give the rates.
## The first line
`up` is the seconds counter, `ticks` the system tick (200 per second),
`loops` the number of main-loop passes and `rx` the frames handle_rx has
taken off the NIC. A healthy loop completes one pass per tick, so the
difference in `loops` tracks the difference in `ticks`; a loop that falls
behind is being held up by one of the phases below. The `rx` rate separates
a flooded CPU port from a stalled loop, which look identical from outside.
## Phases
Each line is one section of the main loop: the link poll, SFP handling,
frame reception, TCP transmission, the STP timers and the command dispatch.
`max` is the worst time that section has taken, in ticks of 5 ms; `slow`
counts the passes that took two ticks or more. A misbehaving subsystem
names itself here, which is the difference between knowing that the loop is
slow and knowing why.
A dump of its own output takes the console some milliseconds, so running
`health` repeatedly increments the `cmd` counters by itself. That is the
instrument observing itself, not a fault.
## Stack
At boot the free stack area is painted with a pattern. `sp` is the live
stack pointer; `untouched` counts the painted bytes still intact at the top,
which is the closest thing to a high-water mark this hardware offers. A
shrinking `untouched` across dumps means something is reaching deeper than
anything before it.
## The TCP slot
The connection table is printed whole, one slot per pair of lines: TCP
state (3 is ESTABLISHED, 0 closed), retransmission timer and counter, bytes
in flight, negotiated MSS, then the ports and the peer address. With a
single slot serving the entire web interface, one glance answers the
question every management outage starts with: who is holding it. A slot
showing state 3 with nothing in flight and a peer that no longer answers is
a connection whose owner went away.
`httpd left` and `entry` describe the file transfer the web server believes
it is in the middle of, and the trailing flags show whether STP is running
and which VLAN carries management.
+6 -2
View File
@@ -69,7 +69,7 @@ The code currently provides the following functions:
void igmp_setup(void) __banked;
void igmp_enable(void) __banked;
void igmp_router_port_set(uint16_t pmask) __banked;
void igmp_packet_handler(void) __banked;
uint8_t igmp_packet_handler(void) __banked;
void igmp_show(void) __banked;
```c
`igmp_setup()` is called at boot-time and configures flooding of all IP-MC packets by
@@ -80,7 +80,11 @@ of IP-MC packets to be limited to only subscribed ports.
`igmp_router_port_set()`configures forwarding ports for IGMP messages.
`igmp_packet_handler()` implements handling of trapped IGMP packets by the CPU.
`igmp_packet_handler()` implements handling of trapped IGMP packets by the CPU. It is called
for frames to 01:00:5e:xx:xx:xx and returns 0 when the frame is not a trapped IGMP packet,
so that it is passed on to the IP stack. IGMPv1/v2 reports (0x12, 0x16) join the group given
in the IGMP header, IGMPv2 leaves (0x17) leave it, IGMPv3 reports use the first group record
(type 4 joins, type 3 leaves). Trapped IGMP frames arrive without a VLAN tag.
`igmp_show()` prints out the IGMP configuration on the CLI.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Loaded 100 of 209 files, more files were not shown because too many files have changed in this diff. Show more