bank_occupancy.sh takes the main and branch build directories as
optional arguments and prints a markdown table, so CI calls it instead
of a second script. It gains the HOME, BANK4 and stack rows, and reads
the web assets from the build directory, where html_min is now.
A port named in the CLI or the web UI (port <n> name ...) now carries
that name in a Port Description TLV, so a neighbour sees "Uplink" next
to "Port 9". The tail of the frame is built per port, since the TLV
length differs from port to port; ports without a name send no TLV.
The bank growth step only exported the sizes to the environment since the
comment that used them was removed, so nothing showed them any more. It now
writes a table of the code banks, XSEG, the stack left after the internal
RAM is allocated and the web assets, main against the branch, to the job
summary, which needs no write permission on the pull request.
The mcs51 library target writes into ../build/$(PORT) without depending on
it, so with -j the archiver can start before the directory exists and the
toolchain build fails with
sdar: ../build/small/mcs51.lib: No such file or directory
It is intermittent: the same tree builds fine on a retry.
The port ID in the BPDUs was the chip port plus one, so on most boards
it did not match the port label, neither on the Spanning Tree page nor
on the neighbour. Send the front panel number instead, and 101 and up
for the LAGs, the same numbers the page already uses for the Port
column. A returned BPDU of our own is mapped back to the port by that
number.
Scenario 66 gives one port a front panel number that differs from its
chip port.
send_lldp(), send_eee(), send_bandwidth(), send_storm(), send_mtu() and
send_status() wrote the closing ']' inside the loop, on the last port.
Write it once after the loop, as send_isolation() already does, so the
array is closed however the loop ends.
Build the harness with -fpack-struct=1 and assert the frame struct sizes,
so the host reads wire bytes the way the firmware lays them out. Add
scenarios for a port that regains edge after a hold, one that heard a BPDU
and stays non-edge, auto-edge switched off, and a captured RouterOS BPDU
fed in on every port, tagged and untagged.
stp_loop_hold_peer() takes operEdge away from the port that sent the
BPDU we got back, though that port heard nothing itself. When the hold
ran out the port stayed non-edge until its next link change, and every
later state change on it counted as a topology change. Restore edge when
the hold ends with the port still silent and auto-edge on, before the
transition is reported, so the recovery itself is not a change. Hellos
go out on edge ports too, so a loop that persists is caught again.
The console column that shows seconds since the last BPDU is now called
age[s]; it never was a receive count.
stp_reselect() took every port with fresh information as a candidate, so a
BPDU credited to a port whose carrier the once-a-second link poll had not
seen yet made that port the root port before the port was set up for it.
A candidate now needs its link bit as well; the poll that sees the carrier
a moment later runs the selection again. Scenario 54.
stp_in() propagated the TC flag of any BPDU that did not arrive on an
alternate port, and acknowledged and propagated a TCN from any port, so a
port still in its listen period or held discarding in a dispute could flush
the addresses of every other port and move the topology change counter.
802.1D-2004 17.31 keeps the Topology Change machine in LEARNING until the
port forwards and drops rcvdTc and rcvdTcn there. Both paths now ask for
the forwarding state first; an alternate port is discarding, so the old
check is covered by the new one.
Scenario 53 plays a TC flag and a TCN into a port that is still discarding,
and then into the same port once it forwards. Scenario 51 used a TCN on a
port one tick short of its edge transition as the trigger for the clock it
tests; it now waits for the transition.
The frame the simulator builds carries a vlan_tag between the RTL tag and the BPDU, but left its TPID zero. stp_in() now takes the body offset from TRAP_RX_BODY(), which reads those two bytes, so it took the untagged branch and parsed the body four bytes early. Writing 0x8100 makes the frame what its layout says it is.
Reception moved to the RMA trap in main, so rtl837x_stp.c calls trap_rma_set() from stp_setup() and stp_off(). The simulator defines neither, so it does not link. Linking rtl837x_trap.c instead would pull trap_init() and the register primitives it needs, which the simulator does not mock.
stp_sim.c declared variables in the middle of the scenarios and put
whole loops and conditions on one line. Each block now starts with its
declarations; an initializer that depends on what happens before it
stays where it was as an assignment. Bodies of if and for are on lines
of their own, functions are separated by an empty line, and lines stay
within 120 columns. The output of stp_sim -v is unchanged.
Scenarios 4 and 7 start from the state scenario 3 leaves behind, and 38
from the one 37 leaves, and they called those scenarios for it, so the
headings of 3 and 37 showed up again in the middle of the run. The setup
of 3 and 37 now lives in functions of its own that the scenarios call.
test/stp/shim held hand-written copies of the headers the STP code
includes, and they had drifted: the uIP buffer was 600 bytes instead of
1556, struct machine had three fields, port_ingress_filter_get()
returned a plain uint8_t. The simulator now compiles rtl837x_stp.c and
rtl837x_stp_cli.c the way test/ compiles firmware sources, with the real
headers and sdcc_shim.h forced in, and with the same warnings, including
-Wno-pointer-sign for the string literals passed to cmd_compare(). It
stays on -std=c11: in gnu11 mode glibc defines LITTLE_ENDIAN, which
uip-conf.h names as the byte order, and HTONS() stops swapping. Its own
print_string() and cmd_compare() take the firmware's argument types.
* stp: select the root port by cost and block redundant links
* stp: move the protocol to BANK4 and keep the command line in BANK2
* stp: implement the RSTP state machines of 802.1D-2004 clause 17
* stp: counters, more settings and a fuller Spanning Tree page
* stp: make a link aggregation group one port of the RSTP tree
* web: serve the Spanning Tree JSON from BANK4 as well
Kept in BANK1, the larger Spanning Tree page would leave under 1 kB
there for the rest of the web server and every page still to come, so
send_stp() and send_stp_counters() move to httpd/page_stp.c in BANK4
with the protocol, as banked calls, with their own copies of the few output
helpers and of the JSON header, so nothing in BANK4 calls into BANK1
directly or hands it a pointer into its code space. BANK4 takes the
free flash between BANK3 and the web files.
KP_9000_6XHML_X2: BANK1 44336, BANK2 36969, BANK3 11942, BANK4 20965
bytes of 49152.
Horaco HC-SWTGW218AS units on this PCB keep their factory MAC at
0x1FC000, the offset the other boards in machine/ read it from, so use
it instead of a generated locally administered one. If the flash holds
no valid MAC there, the generated one is still used.
200 seconds was too short in practice: the UI stops polling while its
tab is hidden, so looking at another tab for a few minutes ended the
session. Make it 3600 seconds; "session <seconds>" still overrides it.
Hardware reset starts the firmware with SFR 0x97 = 1, which maps xdata
0x4000-0xbfff to the flash image: reads return flash bytes, writes are
lost. main() cleared it only after __mcs51_genXINIT and the XSEG clear
had run, so XISEG (initialised globals) and the part of XSEG above
0x4000 kept stale RAM contents, e.g. session_timeout = 4484 and LLDP
blocked on logical ports 0, 3 and 5. Clear it in
__sdcc_external_startup(), which SDCC calls before both.
Rename SFR_97 to MAP_FLASH_PAGE_TO_XDATA.
SW1 on PCB-SWTG024AS-A-V2.0.1_19650 is wired to GPIO48. Measured on
HC-SWTG124AS: GPIO48 reads 1 idle and 0 while the button is held.
HC_SWTG124AS.c is a symlink to PCB_SWTG024AS_A_2_0_1.c.
A commit hash can be looked up directly, a checksum cannot. Print the last commit that touched sdcc/, with -dirty for local changes, and fall back to the checksum when git is not available. The toolchain directory keeps the checksum.
The installer Makefile called whatever sdcc was on the PATH, so an
installer built by hand came out of the system compiler, without the
fixes in sdcc/. Use the same toolchain/sdcc-<cksum> as the main Makefile,
built on demand by sdcc/build.sh if a bare installer build gets there
first, and keep CC= on the command line as the way to pick another
compiler. The README example shows the new command lines.
The boot banner and the version command print the SDCC release and
revision from the compiler's own macros, and the name of the in-tree
toolchain the Makefile built, which is the checksum of sdcc/ and so
changes with every patch in it. A build with CC given on the command
line says "system" instead, since that compiler need not carry the
patches. A plain release and the same release with the patches report
the same revision, so the toolchain line is what tells them apart.
Costs 66 bytes of BANK2.
Review feedback on #560 (DrDoof):
- A login that came with a cookie that is still valid took a second
slot. The first one looked fresh, so the next login could push out
someone who is using the switch. The slot of the cookie now gets the
new id.
- Changing the password leaves no old login valid: session_init() from
parse_passwd(). With one session the next login pushed a leftover one
out, with several it would stay valid until the next restart.
- The 32-bit comparison of the last use times moves into a leaf function,
so the compiler does not give it a 4-byte scratch in internal RAM.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The web server's bank had no room left for the four sessions once the
original firmware's more robust header handling came in. Picking a slot,
checking a cookie and starting a session move to sessions.c in bank 3;
httpd.c only calls session_init(), session_check() and session_new().
The random id is written straight into the reply, which drops
gen_random_hex_chars() from bank 1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Logging in from a second browser ended the first one's session at its next
request: two browsers open on the switch logged each other out every few
seconds. Up to four sessions now, each with its own cookie and expiry; a
fifth login replaces the least recently used. The expiry check is a leaf
function so its 32-bit scratch stays out of internal RAM (stack still
159 bytes with HEALTH).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tx2W7xVecMoKbBCkD2i8PG
(cherry picked from commit 03330f4077ef8872d861d951c8e44ece3c573935)
it'll accept just the definition.
Also throughout the regex we have over-specified the number of digits
sometimes ports are 4 digits long, sometimes they are 2. This seems
unnecessary, 1 or more is sufficient
the user clicks apply on the specific vlan. In order for this to
work when the user is simultaneously changing the vlan that their
machine is connected through and the mgmt vlan id, we need to be
able to send a batch of commands through to the switch atomically
so the switch can process all of them and the momentary disconnection
of the machine doesn't matter
When adding a new VLAN, we should load it after addition
Prevent deleting the mgmt vlan
Adding a button to promote a vlan to be the management VLAN
We can't create a vlan called d or mgmt as that clashes with the
commands to delete and promote a vlan
Making the Management VLAN more obvious and adding a "confused"
disabled button to where delete and promote are
Switching to the i image and making it so you can click the entire
row apart from the cell where the buttons are (to prevent accidents)
Cleaning up the last bits of ingress filtering I can see
Also making the first VLAN in the table load when we load the UI
Removing the tickbox for the PVID, I'll swap this out to ticks
or something later on, but the PVID will be automatically set
based on the untagged membership, otherwise it will be 1
Send the vlan with name command when attempting to add a new VLAN
Because of the structure of the vlan command, we need to send the name as well if its set
Set the PVID based on the untagged membership and reload the
table when applied. We need to block setting the PVID to the tagged by proxy
Blue button
Need to provide array of pvid commands not concat
Storing state of each vlan so we can refer later
Blue button
Device page following doc/adding-a-device.md: label data, PCB versus label
naming, CPU identified with 'regget 4' (RTL8373, non-N), separate PHY and flash
identification, build target, PCB silkscreen, port mapping, board signals, front
panel and LED configuration, reset button, MAC location, what works and what is
unverified, plus photos. Row added to supported_devices.md.
RTL8373 (non-N) with an RTL8224 quad 2.5G PHY, 2 MB GD25Q16ESIG flash, no PoE.
The wiring is close to the KP-9000-9XHML-X V3.1 (same 8+1 topology, same I2C on
GPIO39/40), but three things differ and were determined on hardware:
- ModAbs is GPIO30 (low with a module inserted, high with an empty cage) and LOS
is GPIO37 (high without light), not GPIO38. GPIO38, the ModAbs pin of the
KP-9000 reference, is tied low on this board, which made the firmware believe a
module was permanently inserted and retry the I2C read forever.
- The reset button is GPIO54, not GPIO48. GPIO48 never changes here, so the
button appeared to do nothing.
- The LED wiring follows the stock port order (pin 3n+m belongs to port n+1,
LED function m), so the mux table is the chip default except that pin 25 --
the SFP LED -- is driven by function 0, the only populated function in SET1.
Per copper port the left LED shows link/activity and the right LED shows
2.5G, matching the front panel labels.
Verified on hardware: all eight 2.5G ports at 10/100/1000/2500 Mbps, 1G and 10G
SFP modules including DDM data, both LED columns, module insert/remove and the
reset button.
html_min was phony, so every make deleted output/html_min and minified
all of html/ again, even with nothing changed. A stamp file now stands for
the minified copy and depends on the sources and on minify.py. A list of
the source names, rewritten only when it changes, is a dependency too, so
adding or removing a file in html/ still redoes the copy and a removed
file does not stay behind in the image. If output/html_min is missing,
the stamp is remade in the same run.
The note on version.h in webui-compression.md still said it was phony.
Three more bench scenarios around the closing boundary. Every value
of the last image byte, uploaded whole and split right after the
payload, must end the part. A run of CR before the boundary of even
length passed before the fix and an odd one did not, so both are
checked. An image made of CR only, longer than a flash page, and one
made of repeated "\r\n-" exercise the broken matches back to back:
each one appends to the page buffer without a byte written, and the
page flush after both paths has to keep up.
stream_upload() matches the closing multipart boundary byte by byte.
When a byte breaks a partial match, the matched prefix goes to flash as
data, and so does the breaking byte, without being compared against the
start of the boundary.
The boundary starts with "\r\n--", so an image whose last byte is '\r'
never completes: that '\r' starts a match, the '\r' of the boundary
breaks it and is written as data, and the rest of the boundary no
longer matches. The browser waits for a reply that never comes.
Flush the matched prefix and look at the breaking byte again from the
start of the boundary. A boundary may not contain CR, so no longer
suffix of the matched prefix can start a new match.
The 8051 assembler sign-extends every value to 24 bits, so the top
byte of a __sfr32 address list like 0xa4a5a6a7 comes out as 0xff.
The patch keeps 32-bit arithmetic while leaving the 24-bit T line
addresses alone, so the relocations are unchanged. Proposed upstream
in the bug thread. With it SFR_DATA_U32 can be used again, which
saves about 240 bytes of code in REG_SET.
The workflow and the Dockerfile install what building SDCC needs instead
of the distribution's sdcc, and the workflow caches toolchain/ keyed on
the contents of sdcc/.
Released SDCC 4.5.0 and 4.6.0 miscompile some of our code in the mcs51
register allocator (SDCC bugs 4153 and 4154, fixed in trunk r17025 and
r17039). The first make now downloads the 4.6.0 sources, checks their
sha256, applies the two fixes from sdcc/ and installs the compiler into
toolchain/, named after a checksum of sdcc/, so a changed patch builds a
new one. make CC=sdcc still uses an installed compiler.
The masks were 0, so 0x3e2 was ORed into the reset value 0xbff and both
SerDes stayed off on boards without 10G ports (n_10g == 0), e.g. the
HC-SWTG124AS. With SDS 0 off RTL837X_REG_SEC_COUNTER does not hold a
written value: it reads 0, so NTP never retried after a failed first
attempt at boot and froze the clock after a sync.
Tested with regset on an HC-SWTG124AS: the register keeps a write with
SDS 0 in SGMII or 10GR, not with SDS 0 off (also not with only SDS 1
on).
Firefox filled a saved login into the System tab's password fields and
its user name into the NTP poll interval, the input before them. That
input event marked the Time card as edited, so ntpLoad() left NTP off,
the server empty and the time zone at UTC-12:00.
In HEALTH=1 builds health_phase(HEALTH_LLDP) used id 7 while ph_max[] and
ph_slow[] had HEALTH_PHASES = 6 entries. The LLDP checkpoint therefore
wrote one byte past ph_max (into the high byte of ph_slow[0]) and, when
the phase took two ticks or more, incremented two bytes past ph_slow
(dhcp_state.state and transaction_id on KP_9000_6XHML_X2).
Rename it HEALTH_PH_LLDP = 6, derive HEALTH_PHASES from it so the arrays
always cover the last phase, print an lldp line in "health" and document
it.
Fixes#658
Chrome on macOS labels .bin files application/macbinary. Searching for application/octet-stream mistakes the image bytes for more part headers and aborts when the preamble buffer fills.
Find the multipart header terminator instead, after the HTTP separator retained by handle_post. Keep authentication, CRC checks and staging limits unchanged. Cover MacBinary, an omitted part Content-Type, and a split header terminator with exact staged-byte checks.
the bits we need
But we need both main and branch checked out and the action can
do it for us
Pulling the bank occupancy from the map produced by sdcc rather
than the output of the build as it appears the output number is
only the maximum size of the banks
BANK0 isn't output, but what I'm guessing are the consituents for it are
With UIP_CONF_MAX_CONNECTIONS set to one, a verified upload can leave
reset intent set when its response is never acknowledged. A subsequent
TCP connection reuses that slot, and ACKing its plain GET response can
otherwise trigger reset_chip(). This is sequential slot reuse, not
concurrent HTTP connections.
Store reset intent in the connection appstate and initialize both tstate
and fw_reset_pending on every uip_connected() event, regardless of stale
appstate contents. Reset only after the verified upload response is fully
acknowledged.
Exercise the real HTTP callback for all stale tstate bytes, sequential
slot reuse and a later GET, partial/final ACKs, abort, timeout and CRC
failure. Keep the regression tests in the existing tab-indented style.
Address review by documenting regget 4 and RTL8373 N/non-N ID values under Step 4 of adding-a-device.md, without referring to a particular board or set of units. Keep the verified Horaco CPU and flash capacity in its device table and retain the unverified-chip caveat.
Read register 0x0004 on all three tested HC-SWTGW218AS units. Each returned 0x83730000, identifying RTL8373 rather than RTL8373N. Firmware reports 2 MB flash on all three.
Fill in the verified device-table fields and document the non-N/N register distinction. Leave PHY and flash-chip models explicitly unverified.
The HC-SWTGW218AS is listed only with the SWTG018AS-A V2.0 board, but it
also ships with SWTG118AS-V2.1, the board of the XikeStor SKS3200-8E1X.
That version runs MACHINE=SWTGW218AS: three such switches were flashed
through the OEM web upgrade and work.
Note in the device page that the model name alone does not tell which
firmware to build.
Add the assembled top, front and bottom of the case next to the PCB photos, and
record what the nameplate says (manufacturer, the model description translated
from the Chinese label, 12 V DC 2 A and the serial number format) so a device
can be identified by its label. The top of the case carries no model name.
Also point out where the three console through-holes sit in the top side photo
and note the PWR and SFP1/SFP2 LEDs on the front panel; the case legend for the
copper LEDs reads 1000M/2500M.
The hongyavision HYWS-SGT0204S is sold as an unmanaged 4x2.5G + 2x10G SFP+
switch and carries the 2G5F4_10G2_V1.01 marking on the PCB. It is a sibling of
the KP-9000-6XH-X2 (2M-PCB43-V1.1), so machine/2G5F4_10G2_V1_01.c is that board
definition with these changes:
- The two SFP+ cages (physical ports 5 and 6) are wired the other way round:
physical 5 is on SDS1 with GPIO30/GPIO37, physical 6 on SDS0 with
GPIO50/GPIO10. Copper ports 1-4 match the KP-9000 layout.
- No reset button is fitted and the case has no opening for one, so reset_pin
is GPIO_NA. GPIO54, the reset pin on the KP-9000 layout, reads as permanently
pressed here.
- The board definition sets flash_sio, so the flash is read with single-IO: the
fitted 512 KB Bright Moon T25S40 cannot be read in DIO mode on this board, an
image that leaves DIO enabled does not come up at all, while the same image
with single-IO boots and runs normally (two units).
- The board is added to the machine_custom_init() group that sets
LED_GLB_IO_EN bit 6, otherwise the RTL837x "solid" conditions (LINK/speed)
never drive the LEDs and the 2.5G LED glows faintly at 100M/1000M.
flash_sio is a new field of struct machine that rtlplayground.c hands to
flash_init(); only this board sets it, every other machine keeps DIO. It costs
management-plane speed only, the ASIC keeps forwarding in hardware.
The stock firmware runs a factory test console on the serial header at 9600
baud, whose silkscreen labels are shifted by one pin.
Verified: warning-free build, `make machine_check` passes for every target, the
host tests pass, and a control build of this target without the LED hook differs
from the KP-9000-6XH_X2_V1_1 image in 44 bytes: all inside the machine
description (including flash_sio) and the image CRC16, nothing else in the
firmware moved. On the board the single-IO image boots and runs on two units;
port order, SFP cages and LED behaviour come from the machine definition
verified on this board earlier.
UART is broken out to the unpopulated switch header at the front of the
board.
This isn't quite the same as the V2.0 definition as the system led is
always on, not routed through the CPU.
Additionally, document that the newer revision of the same "YS25-0402"
is supported via the existing PCB-K0402WS-V3.0 target.
The strip no longer wraps. Ports shrink to fit the row and scroll
when they cannot, so the header on a phone stays at two rows. The
strip moves under the title only below 720 px, where it no longer
fits beside it. Drop the unused d_ports strings and the strip title.
The row of port icons lived in a card on the Dashboard, so link
state and speed were out of sight on every other tab. Move it into the
header between the title and the save button, a little smaller, and
poll /status.json whenever the page is open instead of only on the
Dashboard, Ports and Statistics tabs; the existing idle and hidden-tab
gating still pauses it. On narrow screens the strip wraps to its own
row under the title.
The column counts the addresses the switch learns from incoming frames,
so "Connected" suggested a connection it does not show. The translations
follow the words the L2 tab already uses for learned entries.
Save to flash merges the running log into the startup configuration.
Neither its toggle nor its overwrite rules knew "lldp on|off", so every
switch of LLDP added another line: four switches left four lines.
"port <n> lldp permit|block" fell under the rule for "port <n> <speed>",
so setting a port's speed dropped its LLDP setting from the saved
configuration, and the other way round.
"lldp on|off" is now a toggle, and a per-port LLDP setting replaces only
an earlier per-port LLDP setting of that port.
hexvalue[] holds four bytes, but atoi_hex() stored every digit it was
given, so a ninth digit was written past the array into whatever follows
it in xdata. More than eight digits now return 0, as any bad input does.
c - '0' < 10 is computed as an int, so characters below '0' such as '-',
'.' or '/' passed as digits and "-1" came out as 0xdf. The difference is
now taken as uint8_t, so they are rejected.
The button in the header was always drawn as the primary action, on
every page, so it competed with each page's own buttons, for example
the upload button on the Firmware page. It is now a plain button and
turns primary together with the "unsaved changes" badge.
The port tooltip and the port details showed "TX good / bad: 8794 / 0
pkts", which reads as 8794 bad packets. Each counter now has its own
line, labelled as on the Statistics page, and the two strings only that
line used are removed from every language.
"table.t th" sets text-align:left and is more specific than ".num", so
the headers of the counter columns on the dashboard and the statistics
page stayed left-aligned above right-aligned values.
The switch keeps flooding 224.0.0.0/24 with IGMP on, but a report for a
group in that block still made an entry for it, and the entry limited
the group to the ports that reported it. A host announcing mDNS on
224.0.0.251 thereby cut mDNS off from every other port. Such reports
are now ignored, as RFC 4541 asks.
igmp_setup(), which `igmp off` also calls, enabled the lookup of IPv4
multicast in the L2 table. Groups learned while IGMP was on therefore
kept going only to the ports that had reported them, and with reports
no longer trapped nothing updated those entries until a reboot.
The lookup is now enabled in igmp_enable() and disabled in igmp_setup(),
so with IGMP off all IP multicast is flooded again.
Move port_bit() to rtl837x_common.h as PORT_BIT(), a uint16_t shift,
and MAC_ADDR_LEN from rtl837x_lldp.h next to it, and use MAC_ADDR_LEN
for every address in the LACP code instead of a literal 6. Define
lacp_mux_update() before its first caller and drop the forward
declaration, declare variables at the top of their blocks, put the
operator at the end of a continued line, drop the field value comments
of the LACPDU struct, and leave lacp_port_lag to lacp_init() instead of
initialising it in the test environment.
The trap is described with STP, so the LACP page only says that it is
used. The LACP sections leave link_aggregation.md, which now points to
lacp.md, and the timing section gives the values in seconds and the
802.3ad requirements without the test history.
The comments repeated the documentation, described tests or explained
the code to its author. What stays describes the LACPDU structures. The
timer constants are decimal, in units of the LACP timer.
The LACP test now compiles with the shared harness flags and
sdcc_shim.h against the firmware headers, so the copies under
test/lacp/shim are gone. test_port_tables already covers the trunk hash
seed that test/port_hash checked against its own register copies.
lacp_in() declares its locals at the top, which the stricter host build
needs.
The port and group of lacp_in(), the member mask passed to
lacp_lag_set() and the compiler temporaries both functions spilled took
six bytes of DSEG, and with them six bytes of stack. The locals and the
mask live in xdata now, the partner fields go through the scratch word,
and lacp_lag_set() records the new group of a port before it releases
the old one. One byte is left: the group number passed in.
lacp_in() always leaves uip_len at 0, so the tcpip_output() after it
never ran. The LAG page names the active members by front-panel port
instead of a logical mask. The partner key is not part of selection;
say so under Known limits.
Listing by next-address also returned the reserved-multicast entries STP
writes, shown as port 1 with a delete button, while l2_delete() still
looks up by next-unicast and so removed the unicast entry after that
index instead. The change was there to show the LACP steering entries;
delivery by trap writes none, so it goes.
Both paths wrote the trunk registers directly and left the protocol
running on the group, so "lag show" said disabled while the partner kept
both links in one aggregate, and re-sending the LACP line did not repair
it because the same-ports shortcut returns early. Each path now releases
the group from LACP first; for a group that never ran LACP that is a
no-op.
The receive machine armed current_while from the partner's TIMEOUT bit,
so against a partner on the slow rate a port whose LACPDUs stopped with
the link still up stayed in the trunk for the long timeout, two minutes.
802.3ad 43.4.12 starts the timer from the actor's own LACP_Timeout, and
we always advertise the short one: the partner sends every second and is
let go after the short timeout.
Sandbox T23 plays a slow-rate partner that goes quiet and fails on the
code before this.
Dynamic link aggregation on top of the static LAGG, for RTL8372/8373:
receive, selection and mux state machines (coupled mux, one aggregator
per group), with "lacp show" to look inside. Each of the four trunk
groups runs its own aggregator on the candidate ports assigned with
"lag <n> lacp <ports>"; the engine starts with the first such group and
stops with the last.
LACPDUs reach the CPU through the reserved-multicast trap, so delivery
needs no L2 entry and nothing written per VLAN or per PVID. The trap
acts on the address before the VLAN lookup and before the ingress
filters, so a LACPDU also arrives on a port that admits tagged frames
only and on a port STP holds in blocking, neither of which a forwarded
one reaches. A trapped frame carries no VLAN tag,
so lacp_in() takes the offset of the PDU body from TRAP_RX_BODY(). The
action is set with trap_rma_set(), which leaves the flags sharing that
register alone, and "lacp off" puts the slow-protocols drop default
back. doc/lacp.md and doc/link_aggregation.md record what was measured,
including why the trap looked like a dead end at first.
The web UI gets a Static/LACP mode per group on the LAG page with live
status, and the startup-config validator knows the per-group syntax. A
host-side sandbox for the state machines runs in the test harness
together with the LAG hash check. It drives both forms of an incoming
LACPDU, so the body offset of a trapped one is covered as well.
The L2 table page iterated with the "next unicast" read method, so
static multicast entries such as an IGMP group were invisible. It now
iterates with "next address", skips L3 entries (their layout carries IP
addresses where a MAC is expected), and reports the lowest member of a
multicast entry's port mask, so such an entry shows up as a static
entry on that port.
A port taken out of LACP forgets its partner, so "lacp show" after
"lacp off" no longer reports one. Applying the same candidate set to a
group again, as the LAG page does on every Apply, leaves the trunk
alone instead of tearing it down for a second or two. lacp_in() checks
the frame length and ethertype before reading a LACPDU, so a runt or a
foreign frame on the slow-protocols address cannot leave stale buffer
bytes as a partner. The sandbox covers these three in T20-T22. On the
LAG page, LACP mode with no ports checked sends "lag N lacp off", and
the startup-config validator wants at least one port after "lacp".
The document was renamed from doc/trunking.md in "Update LAG configuration
registers" and the entry in the README kept the old name, so it leads
nowhere.
"port <port> cabletest" runs the cable diagnostic of the copper PHYs
and reports one line per pair: ok, or open, short, short to another
pair or impedance mismatch with the distance to the fault.
The PHY part follows the cable_test_start and cable_test_get_status
ops of the Linux phy_driver and takes the port number:
rtl_phy_cable_test_start() triggers the test and
rtl_phy_cable_test_get_status() returns true once all four pairs are
done, with a result code and a length per pair. The decode order is
the one of the Linux driver. The same two functions drive the RTL8224
and the internal PHYs of the RTL8372 and RTL8373.
The command only powers the port down and returns. A state machine
called from handle_tick() calls the start op after one second, polls
the status op, stores the result, clears the trigger, also after a
timeout, and restores the control register, so the main loop never
waits. The result is printed when it is ready and "port <port>
cabletest show" prints it again. One test runs at a time.
The result flags are the low byte of the per-pair status word. The
vendor code reads the high byte, which is always zero on the RTL8373.
A link partner that keeps transmitting makes the PHY report an
inter-pair short on every pair, and forcing the speed with
autonegotiation off, as the Linux driver does, does not take the link
down. The port is therefore powered down for a second before the
test. The control register is restored afterwards with an
autonegotiation restart, so speed settings survive and the link
returns.
A test started on a port that is switched off never finishes and
leaves the PHY unusable until the switch is reset, so such a port is
refused, and a test is dropped if the port is switched off during the
wait. SFP ports are refused by the existing check. The external copper
PHYs of the RTL8372 boards are refused because the test has not been
run on one.
The distance is the raw length word minus 150, at 43 counts per 50 cm.
Fitted on open cables of 0.9 m, 2 m and 7.6 m; the pairs of one cable
differ by about 0.2 m. The Linux formula, raw minus 620 at 78 counts
per metre, gives 0, 0 and 2.4 m for these cables.
An unplugged port has no defined result: some pairs report open at
about 0.5 m and others a short or a mismatch at 2 to 3 m.
lldp_tick counted main-loop passes, so the 6000 meant for 30 s at
SYS_TICK_HZ ran at whatever rate the loop happened to have, and the
interval moved with httpd and SFP load. Step once per second from the
tick counter like the STP timers do, count seconds to the interval and
derive the TTL from it with the 802.1AB hold multiplier of 4.
The System Description TLV carried the model name alone, so an
inventory built from LLDP could not tell which firmware a switch runs.
Append the version string, as in "SWTGW218AS 8+1 Managed Switch
v0.1.0-1856aeba".
lldp off and port <n> lldp block went silent and left the neighbour
holding our entry until its TTL ran out, 120 s later. Send one LLDPDU
with a TTL of 0 on the ports that stop, the shutdown form 802.1AB
defines for this, before the transmit mask changes.
Neighbours could not tell a bridge from a host and had no address to
reach us on. Add the System Capabilities TLV (bridge, enabled) and a
Management Address TLV with the IPv4 address, left out while the
address is still 0.0.0.0. Size the payload buffer for the frame it can
now hold: 64 bytes did not cover a 23 character hostname next to the
longer model names.
The Port ID TLV carried a single digit with the locally assigned
subtype, so a neighbour listed us as port "3". Send "Port 3" with the
interface name subtype instead, the form the port keeps in the CLI and
the web UI.
The simulator answered /status.json without the "macs" field the Ports page now reads. It counts the dynamic entries of its own L2 table for the port, so the column and /l2.json agree and the two static entries show the difference.
The column listed the MAC addresses of the L2 table entries of a port, which cost a chain of paged /l2.json requests every tenth status poll. The status JSON carries the count now, so the column reads it from there and the page does not touch the L2 table at all.
The register counts the dynamic entries, so the static ones the list used to include are not in the number.
"mirror status" printed the local mirroring_port, which is still 0 at
that point, so it named the same port whatever was configured. Print
the port from RTL837x_MIRROR_CTRL instead, which the function has
already read to tell whether mirroring is enabled. send_mirror() decodes
the register the same way for the web interface.
This adds a howto for figuring out LED configuration via tracing
of functions in the originam firmware image. The method also provides
information on PHY and SDS configuration, including PHY patches.
The switch keeps a count of the dynamic L2 entries learned on every
port in L2_LRN_PORT_CONSTRT_CNT (0x53ac + 4 * port, 13 bits). `stat`
prints it in a new MACs column and /status.json carries it as "macs".
On a SWTGW218AS the count matched the L2 table port by port, static
entries are not included, and taking a port down drops it to 0.
uip has no reassembly, so every fragment it receives is discarded and
logged. A neighbour that broadcasts fragmented traffic therefore produces a
continuous stream of "ip: fragment dropped." rather than an occasional one,
at roughly thirteen lines a second on a quiet segment, which buries every
other console message.
The message carries real information: a sudden burst of it says something
about the network. What carries none is repeating it per fragment. Report
the count once per five second window instead, which keeps the signal and
ends the flood.
The count is printed through the no_syslog primitives, as the original
message was, so the flood cannot move from the console into the log ring.
itoa() writes through write_char(), which also feeds the ring, so it gets
a no_syslog twin beside it rather than a private copy of its digits.
2026-09-25 08:20:27 -05:00
454 changed files with 16915 additions and 4861 deletions
cd main && make CI=1 MACHINE="KEEPLINK_KP_9000_6XHML_X2" && cp -r output/KEEPLINK_KP_9000_6XHML_X2/rtlplayground.map output/KEEPLINK_KP_9000_6XHML_X2/rtlplayground.mem output/KEEPLINK_KP_9000_6XHML_X2/html_min /tmp/main/
A device is supported by adding one board header `.h` and board definition `.c` file
under `machine/`.
the build selects the definition by name, and CI compiles every file it finds.
To compile for your added device, do:
```
make MACHINE=<NAME>
```
## What a new device consists of
| File | Purpose |
|------|---------|
| `machine/<NAME>.c` | the `struct machine machine` board definition |
| `machine/<NAME>.h` | `MACHINE_MODEL` (the model string shown in the UI and the CLI) and `MACHINE_CHIPSET` (`RTL8372` or `RTL8373`) |
| `machine/<NAME>_init.c` | optional `machine_custom_init()` boot hook, for registers the machine definition cannot express |
| `doc/devices/<name>.md` + `doc/supported_devices.md` | (only if the board is verified and you want to support it publicly) user-facing documentation |
`<NAME>` is the machine symbol without its `MACHINE_` prefix, so `MACHINE` is
passed exactly as spelled on the command line: `make MACHINE=MY_BOARD_1_0`
needs `machine/MY_BOARD_1_0.c` and `machine/MY_BOARD_1_0.h`.
## Step 1: the board header
`machine/<NAME>.h` carries the product string and the SoC family. Use the
existing files as the template:
```c
#ifndef _MACHINE_MY_BOARD_1_0_H_
#define _MACHINE_MY_BOARD_1_0_H_
#define MACHINE_MODEL "Brand Model V1.0"
#define MACHINE_CHIPSET RTL8373
#endif
```
`MACHINE_CHIPSET` is `RTL8373` or `RTL8372`; a build without it will fail. It selects the default
logical port range: `MACHINE_MIN_PORT` is `0` for RTL8373 and `3` for RTL8372, while
`MACHINE_MAX_PORT` is `8`, the highest logical port index. A board with a different range defines
`MACHINE_MIN_PORT` and/or `MACHINE_MAX_PORT` in its header to override the
defaults.
The device product string is what the CLI or the Web-Interface will print to identify the device; `machine.h` includes it by name.
> [!NOTE]
> `machine_name` is a `char[30]` and `MACHINE_MODEL` is copied into it at
> compile time. A longer model string still builds, but SDCC emits
> `warning 147: excess elements in array of chars initializer` and the name is
> truncated in the UI. Keep it to 29 characters or less.
## Step 2: the board definition
`machine/<NAME>.c` starts with the shared prelude and then defines the single
`machine` object. Copy the closest existing board and adjust it — do not start
from a blank file, the field meanings are easier to see in a real example.
The best choice is usually a device that has the same number and type of Ethernet
`MACHINE_COMMON_SETTING` (from `machine_defs.h`) initializes `machine_name` and
`isRTL8373` from the header's `MACHINE_MODEL` and `MACHINE_CHIPSET`. The logical
port range comes from `MACHINE_MIN_PORT` and `MACHINE_MAX_PORT` in `machine.h`.
Keep it as the first initializer and list the board-specific fields after it.
### Fields
| Field | Meaning |
|-------|---------|
| `MACHINE_COMMON_SETTING` | expands to `machine_name` (`MACHINE_MODEL`) and `isRTL8373` (`1` for `RTL8373`, `0` for `RTL8372`); both are derived from the header's `MACHINE_CHIPSET` (see Step 1) |
| `n_sfp` | number of SFP+ cages |
| `n_10g` | SerDes 10G capability used by the RTL837xN init: `0` none, `1` one, `2` two 10G-capable SerDes |
| `log_to_phys_port[9]` | logical port (0-based index) → physical front-panel port number |
| `phys_to_log_port[9]` | the inverse, physical port 1 is index 0, unused slots are 0 |
| `is_sfp[9]` | `0` for copper, `1` for left slot and `2` for right slot |
| `sfp_port[2]` | per-cage wiring, `sfp_port[0]` is the leftmost cage |
| `reset_pin` | GPIO of the reset button, `GPIO_NA` if unpopulated |
| `high_leds` | mux/enable for the LED pins 27–29 (`LED_27`, `LED_28_SYS`, `LED_29`) |
| `port_led_set[9]` | which LED set (0–3) each logical port uses |
| `led_sets[4][4]` | Each port can have up to 4 GPIO output pins assigned. 4 sets of LEDs with 4 LED-conditions per set; the conditions define which GPIO pins become active for a port given a port link-state. Which LEDs are then enabled depends on the wiring of the pins to up to 3 LEDs for that port, which can make a logical combination of the 4 LED output-pins. |
| `led_mux_custom` / `led_mux[28]` | `1` plus the 28 mux values when the stock LED muxing does not fit the board |
| `mac_flash_offset` | flash offset of the factory MAC address, omit or `0` if the board does not store one |
Each `struct sfp_port` entry holds `pin_detect`, `pin_los`, `pin_tx_disable`,
`sds` (which SerDes drives the cage) and `i2c` (built with `I2CBUS(sda, scl)`,
or `GPIO_NA`-based values where a cage has no such pin).
`I2CBUS(...)` only accepts the pin pairs listed in `machine_defs.h`. A wrong
pair is caught at compile time: SDCC reports
`error 176: sizeof applied to an incomplete type` pointing at the offending
`.i2c` line, because the macro deliberately turns an invalid bus number into an
array of size 0.
### Reusing an existing definition
If the new board is electrically identical to one that already exists — same
port mapping, same SFP wiring, same LEDs — do not copy the file. Make the new
`.c` a symlink and keep only a distinct `.h`, so the two boards cannot drift
apart:
```
ln -s KP_9000_6XHML_X2.c machine/MY_BOARD_1_0.c
```
The new board still needs `machine/MY_BOARD_1_0.h` (pick the version number on
the PCB) with its own `MACHINE_MODEL` and `MACHINE_CHIPSET`: `MACHINE_MODEL` is
what distinguishes the products, and `MACHINE_CHIPSET` must match the SoC of
- All 4 2.5GBASE-T RJ45 ports work at 10/100/1000/2500 Mbps
- The SFP+ port supports 1G, 2.5G and 10G modules
- LEDs work with the same indiciations as the OEM firmware (use KP_9000_6XHML_X2_V1_1 in machine.h if building yourself or the corresponding pre-compiled binary)
- LEDs work with the same indiciations as the OEM firmware (build with `make MACHINE=MOKERLINK_2G040210GSM` if building yourself, or use the corresponding pre-compiled binary)
- untested due to missing Hardware: SFP+ ports equipped with 1G or 2.5G SFPs.
The `POE-2G080110GS` is an unmanaged 8x2.5G RJ45 and 1xSFP+ PoE switch sold by Mokerlink. There is also a `POE-2G080110GSM` managed version of the switch which may be similar but this has not been verified on actual hardware.
The`KP-9000-9XHP-X` is the KeepLink rebrand for same product.
> 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.
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 `MOKERLINK_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.
*Photos courtesy of [andreas5232](https://github.com/andreas5232) from [emesix/ONT-S207CW-62TS-SE#1](https://github.com/emesix/ONT-S207CW-62TS-SE/issues/1)*
@@ -90,6 +90,8 @@ Custom mapping for ONT-S207CW-62TS-SE:
## Flashing Instructions
See the [README.md](../../README.md) for general flashing instructions.
For compilation, use command `make MACHINE=ONT_S207CW_62TS_SE` to specify the machine.
If you are using managed version of the switch, you can then also compile the installer and flash using GUI - run command `cd installer` and finally `make`.
This is the first revision of the PCB-K0402WS series of boards; it's very similar to 2.0 in terms of layout/wiring.
The board does have footprints for a VLAN modeswitch at the front and a second flash chip on the bottom of the PCB, but these have been seen to ship unpopulated.
### Brands
* YuanLey YS25-0402 <sup><sub>Note: there are [multiple revisions](PCB-K0402W-V3.0.md) of the YS25-0402. This first revision lacks the "Vlan" switch cutout on the front of the enclosure.</sub></sup>
### Programming
Use a SOIC-8 clamp to the flash chip (U1 on silkscreen) at the top of the board, leaving it otherwise unpowered.
The board has been seen to ship with a single [BoyaMicro BY25Q16BSSIG](https://www.lcsc.com/datasheet/C382744.pdf) 16Mbit (2MiB) chip, though exact BOM may vary.
* YuanLey YS25-0402 <sup><sub>Note: there are [multiple revisions](PCB-K0402W-V1.0.md) of the YS25-0402. This is the newer revision that has a "Vlan" switch on the front.</sub></sup>
Using SPI clamp in-board is the only method for initial installation.
@@ -32,7 +37,7 @@ The switch actually controls the HOLD line of each flash chip, and toggling the
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.
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-*-HISOURCE_HI_K0402WS_PCB_K0402WS_V3_0.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)
@@ -43,15 +48,15 @@ For the initial flash (at least with flashrom), the bin file produced by the bui
### PCB overview
**Board markings**
- Top silkscreen: PCB-KO4022W-V3.0 / DIP-KO4022WS-V3.0
- Top silkscreen: PCB-K0402W-V3.0 / DIP-K0402WS-V3.0
@@ -77,3 +82,15 @@ Voltage is made by a `Techcode TD1720` .
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.**
## Reset Mod
A reset button can be easily added by soldering a push button from TP131 (near the Spi ROM) to ground via a 1KOhm resistor.
**PS:** `The reset button GPIO must be defined in the machine.c file before building the image, it must be selected the GPIO30_ACL_BIT3_EN instead of GPIO_NA under the PCB-K0402W-V3.0 configuration.`
The four-pin through-hole serial header can be seen to the right of the CPU heatsink in the photo above. It is labelled J1, with pin 1 (Indicated by a triangle on the silkscren) at the bottom. the pinout is also on the silkscreen:
| Pin | Label |
|-----|--------|
| 1 | TX |
| 2 | RX |
| 3 | GND |
| 4 | 3V3 |
Adding a header is a few moments work with a soldering iron. Only pins 1-3 are required for serial port use, and the stock firmware outputs at 57600bps at boot.
@@ -68,7 +69,7 @@ Port 5 RJ45 is interfaced through a RTL8221B IC.
This connector goes to U4 `I2C EEPROM` and U10 `SPI FLASH` (mappings identical to SWTG024AS).
For detailed Slave Interface functionality and protocol information, see [T3 documentation in SWTG024AS.md](SWTG024AS.md#t3-slave-interface).
For detailed Slave Interface functionality and protocol information, see [T3 documentation in SWTG024AS.md](PCB-SWTG024AS-V2.0-17452.md#t3-slave-interface).
|`T3` pin|what|Signal|
|---|---|---|
@@ -108,7 +109,7 @@ For detailed Slave Interface functionality and protocol information, see [T3 doc
#### Notes
* 1 pin is square shaped.
* Mapping unverified but assumed the same as [LIANGUO SWTG024AS](SWTG024AS.md#t8).
* Mapping unverified but assumed the same as [LIANGUO SWTG024AS](PCB-SWTG024AS-V2.0-17452.md#t8).
## Reset Circuit
| Function | GPIO |
@@ -120,7 +121,7 @@ For detailed Slave Interface functionality and protocol information, see [T3 doc
## 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.
Note: T3/U4/U10-related signal annotations below are copied from [LIANGUO SWTG024AS T3 section](PCB-SWTG024AS-V2.0-17452.md#t3-slave-interface) as well as T8 port from [LIANGUO SWTG024AS T8 section](PCB-SWTG024AS-V2.0-17452.md#t8). They should be treated as assumed identical for ZX-SWTGW215AS as it has not been 100% confirmed true at the moment.
| 00100000 | GPIO20 | PORT4 LED GREEN | | | GPIO52 | | |
@@ -176,7 +177,7 @@ Note: T3/U4/U10-related signal annotations below are copied from [LIANGUO SWTG02
## Notes
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.
While [SWTG024AS.md](PCB-SWTG024AS-V2.0-17452.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.
Loaded 100 of 454 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.