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.
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.
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.
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.
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.
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.
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.
__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.
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.
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.
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.
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.
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.
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.
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.
* 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>
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.
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).
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.
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.
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.
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.
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.
`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.
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.
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.
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.
"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.
"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.
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.
"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.
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.
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.
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.
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.
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.
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.
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
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.
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
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
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
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.
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).
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
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.
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.
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.
* 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
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
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.
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.
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.
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.
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
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.
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.
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.
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.
- 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.
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.
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.
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.
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.
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.
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).
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>
Adds CLI reference for the previously undocumented vlan <id> mgmt
command, including the disable case (vlan 0 mgmt), default state,
and a lockout warning.
Fixes#235
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.
When checking for a new root, a root_bridge with the same priority as STP_I will never be adopted as cmpMAC always returns 0.
This PR fixes this bug by changing the comparison of MACs to what was intended
With content_type = "application/x-www-form-urlencoded", "+" means space.
This case was not handled.
Also refactor the code to make a loop to process the hex digits.
- machine.c/h: add MACHINE_FNS1200P with verified GPIO assignments for
both SFP ports, LED SET0 (amber 2.5G / green 1G-100M-10M) and SET1
(SFP all-speeds), led_mux from original firmware register dump, and
machine_custom_init() enabling LED_GLB_IO_EN bit 6
- doc/devices/FNS-1200P.md: device overview, port layout, serial
console (S1 three through-holes = UART0 115200 8N1 3.3V),
LED and SFP GPIO tables
- doc/devices/photos/FNS-1200P/: chassis front panel and PCB top photos
- doc/supported_devices.md: add FNS-1200P to the list
GPIO assignments and LED register values were cross-checked between
live GPIO observation (RTLPlayground gpio command) and an original
firmware register dump.
* Revise compiling section and add cautionary notes
Updated compiling instructions and added warnings about flashing procedures.
* Refactor caution messages in README.md
Updated caution messages to use new formatting for emphasis.
* Revise caution and reminder notes in README
Updated caution and reminder sections for clarity and consistency.
* Fix typos in README regarding firmware update
Corrected typo errors in the README.
* Add image for advanced settings
Add advanced_seetings.png
* Add advanced settings configuration details to README
Added advanced settings section with configuration instructions.
* Fix image source in README for advanced settings
Corrected the image source filename for advanced settings.
* Revise IP and port command descriptions in README
Updated command descriptions in README for clarity.
* Revise README.md for clarity and updated instructions
Updated sections in README.md for clarity and accuracy, including compiling requirements, installation instructions, and cautionary notes.
* Revise README for clarity and emphasis
Updated formatting and emphasized important notes in the README.
* Correct image file name and compilation output in README
Updated README to reflect changes in image file names and compilation output.
Chrome sends upload requests using POST with multipart/form-data
content type in multiple packets for the header part of the form-data.
Introduce a TSTATE_MULTIPART for the httpd server states that denotes
that so far only a part of the multipart header has been transmitted.
Once the full header has been transmitted, we change to TSTATE_POST
as for Firefox which sends all the multipart header in one piece.
The main further change required then is to make sure that the parsing
of the initial part of the multipart request is only parsed once and initially
to distinguish between configuration and firmware uploads.
Add support for the ZX310S-4T2XH switch, which is a 4x2.5GBit + 1x10GBit + SFP
device. The device does not have any branding and is sold under different
brand names, in particular by Horaco. There is no branding on either label
nor PCB.
CPU: RTL8372
Flash: 2MByte Winbond W25Q16DV
10GBit PHY: RTL8261BE
UART header present, but solder holes are filled with solder that needs
removal, first, e.g with a 1.2mm drill.
The original firmware uses 57600baud 8N1
Register RTL8373_EEE_CTRL_BASE did not actually exist at 0x606c,
instead RTL837X_EEE_STATUS is actually not only a status, but also
a control register for EEE enable/disable at the MAC.
The defines are not actually register bits, but port EEE settings,
so move them to rtl837x_port.h while adding 5G/10G flags.
This uint8_t field corresponds to the n_sfp field and gives the number
of 10G ports for the machine. The values can be 0, 1, 2 for known machines,
all currently supported machines do not have any 10G ports, so no
need to change any entries in machine.c
Remove the return argument because `err_status` is also reflecting the result.
Fix the error message when too many arguments are found.
refactor `execute_config()`, error out when err_status is not OK.
Because no memory type is specified to the reference location, sdcc is
using a helper function to access the location. But sdcc is using a
register to tell the helper function which memory-type is used.
This registers must also be preseved until all access to that location is done.
Because `cmd` is guaranteed by the compiler to be NULL-terminated, we can make use of that to ensure the loop always ends.
So we don't need to know when the next words starts.
Fix declaration of tx_buf_empty bit var
__sbit is for SFRs, for regular data __bit should be used. Otherwise the variables is not correctly declared in the BSEG section, leading to bit temporaries being allocated in the same location.
When this causes the tx_buf_empty to be overwritten to 0, then the next write_char will hang forever.
With this change the bit is properly declared in BSEG for the linker, which in my testing resolves the overlap issue:
```
;--------------------------------------------------------
; bit data
;--------------------------------------------------------
.area BSEG (BIT)
_tx_buf_empty::
.ds 1
```
Only the vlan and mtu commands call atoi_short, which has a local bit _atoi_short_sloc0_1_0 which is the first bit in the BSEG, and assigning to that one corrupted the tx_buf_empty bit.
Though this is also a compiler optimization-failure, the _atoi_short_sloc0_1_0 value isn't even used after assignment, AFAICS it is a dead store.
Its from the isnumber inline:
```
[...]
;--------------------------------------------------------
; bit data
;--------------------------------------------------------
.area BSEG (BIT)
_atoi_short_sloc0_1_0:
.ds 1
_parse_ip_sloc0_1_0:
.ds 1
[...]
; cmd_parser.c:85: l -= '0';
mov r3,a
add a,#0xd0
; cmd_parser.c:86: return (l <= ('9'-'0'));
add a,#0xff - 0x09
cpl c
; cmd_parser.c:172: while (isnumber(cmd_buffer[idx])) {
mov _atoi_short_sloc0_1_0,c
jnc 00103$
[...]
```
__sbit is for SFRs, for regular data __bit should be used.
Otherwise the variables is not correctly declared in the BSEG
section, leading to bit temporaries being allocated in the same
location.
When this causes the tx_buf_empty to be overwritten to 0, then
the next write_char will hang forever.
This splits the current MACHINE_KP_9000_9XHML_X definition into "V2_2"
and "V3_1" versions for the two known versions of this hardware.
Confirmed to work:
- LED configuration matches stock firmware
- Port ordering matches label
- A SFP can be detected and EEPROM read (I don't have a fiber cable to test link)
- Reset button presses are detected
While the current implementation works for what it is actually used, it
is broken when trying to do larger transfers.
The length field in the control register has a size of 4 bits. In every
transfer, length+1 bytes are read. Thus, each transfer is limited to a
maximum of 16 bytes. Add a check for the length, and write the correct
value to the register.
Also update the loop in "sfp_send_data" to properly increment the output
register. Remove the unused special case for a length of 128 bytes.
It seems very likely that other versions are not compatible, so make it
clear by including it in the machine type and name. Also update the
documentation.
Blocking by design to prevent loading config
Button must be held between 10 and 30 seconds to trigger reset.
If held more than 30 seconds, boot normally. This is to prevent lock-up in case of weird hardware variants.
This board appears for example in the Davuaz Da-K6501W switch, and is
designed similarly to Hi-K0402WS.
However, it only has one SFP port and another 2.5G copper port using
RTL8221B 2.5G PHY instead. Also, some components on the board, like
additional LEDs, mode switch, and second flash chip are not populated.
Like earlier revisions of the K0402W(S) board, there is no UART, so
debugging is limited.
As this is an unmanaged switch, installation needs a flash programmer.
Added new `vlan show` command to dump current VLAN settings.
Supports printing PVID & ingress filtering per port.
Added new `ingress [p]<mode>` command to setup ingress filtering.
Added wrappers for enabling/disabling vlan filtering. Currently
not configurable, but state in console is read via ASIC registers.
Full VLAN dump & web support of new commands will be added later.
When device has no LOS pin and module has no extended status, the
RX LOS value will not be shown. When both are present, the equality
check is performed. When only one is present, the present value
will be shown.
json from the switch, will still contain the field, but can be
null for when pin is not available.
Currently only ASCII DEL (key code 127) is handled as backspace, but some terminal
emulators are sending ^H which is ASCII BS (key code 8). Looks like we can
safely treat both in the same matter.
The state of the SFP module is being already send in status.json,
but was not parsed via Web UI. When debuging SFP LOS/Signal detection
it is usefull to see what module is reporting back.
Extended the SFP mouse-over information with:
- RX LOS as reported in 0x02 bit of register 238
- External TX Disabled from 0x80 bit of register 238
- TX fault from 0x04 of the same register
- The last state of RX LOS pin (available also when there is no 0x40
option on the module)
This should help identify missing/incorrect setup of TX disabled pin.
Looks like the SFP numbers are reversed, since GPIOs were mapped
to SFP numbers, the need to be reversed too. This fixes swapped
display of SFP stats on the webpage.
Added support for all leds on the device.
Stock port sets are slightly differently configured, where additional
LED0 on SET_0 and LED1&LED2 where configured for link/collisions/duplex.
This has not been reproduced as those lines are probably not connected
anywhere.
I noticed that entering some vlan config, clicking "Update/Create", then
clicking "Get Configuration" resulted in Tagged/Untagged/PVID displaying
completely wrong data. It looks like the parisng in fetchVLAN() in
vlan.js was just completely disconnected from the layout of the
registers read by vlan_get() in rtl837x_port.c. Who knows how that
happened.
1. Fix fetchVLAN() member/untag parsing: the old code read bits [9:0]
as untagged and bits [10:19] as tagged, but the VLAN table register
layout is members in [9:0] and untag in [19:10]. Now correctly
derives tagged (member && !untag) and untagged (member && untag).
2. Add PVID to vlan.json response: PVID is stored per-port in separate
PVID registers (RTL837x_PVID_BASE_REG), not in the VLAN table entry.
The old code nonsensically tried to read it from bits [20:29] of the
VLAN table. Add port_pvid_get() and build a pvid bitmask in
send_vlan() so the JS can parse it correctly.
3. Remove auto-PVID logic from setC(): setC() is called by fetchVLAN()
while it's loading existing config, and so this logic mangled the
display of the existing config. Also, it doesn't make sense to
auto-set the PVID for tagged members (PVID relates to untagged
ingress.)
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 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.
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
* Mapping unverified but assumed the same as [LIANGUO SWTG024AS](SWTG024AS.md#t8).
# Reset Circuit
| Function | GPIO |
|---|---|
| Reset button | GPIO54 |
### Notes
* Circuit is active-low
# 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.
| LED-SYSTEM | GPIO28 | --- | System status | --- |
## 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.
*NOTE*: Part numbers are incomplete. Part numbers may contain additional information such as package, temperature specifications, and even the number of devices on a reel. So always check the datasheet so that you have the right orderable partnumber.
all translations are stored in a single JavaScript dictionary embedded in the firmware.
No server-side changes are needed.
## Architecture
All translation logic lives in `html/i18n.js`. The file contains:
- A `LANG` object with one sub-object per language (`en`, `ja`, ...)
- Language auto-detection (browser language → `localStorage` override)
-`t(key)` — look up a translated string
-`setLang(lang)` — switch language and update the page
-`applyTranslation(el)` — apply translation to one DOM element
Translation keys are **flat strings** (no nesting). The English keys in `LANG.en` also serve as the fallback when a key is missing in another language.
## How to Add a New Language
### 1. Add a dictionary entry in `html/i18n.js`
Append a new sub-object to the `LANG` object. Every key from `LANG.en` must be present:
```js
varLANG={
en:{
nav_overview:'Overview',
nav_port_config:'Port Configuration',
// ... all keys for English
},
ja:{
nav_overview:'概要',
nav_port_config:'ポート設定',
// ... all keys for Japanese
},
LANGCODE:{// ← add your language here
nav_overview:'...',
nav_port_config:'...',
// ... translate every key
},
};
```
### 2. Add the language to the navigation sidebar
In `html/navigation.js`, add an `<option>` to the language selector:
The `linkS` array in `html/main.js` maps numeric link states to display strings. The first two entries (`speed_disabled`, `speed_down`) use `t()` for translation; the remaining entries are static literals (they are the same in all languages):
Always use `linkText(idx)` (not `linkS[idx]`) to read these values.
## Size Considerations
-`html/i18n.js` is embedded in the firmware filesystem (~14 KB for two languages)
- Each new language adds roughly the same number of bytes as the English dictionary (~3–4 KB)
- The firmware binary is padded to 512 KiB, so a few extra KB do not change the flash footprint
- Values that are identical in all languages should be inlined as literals rather than added to the dictionary (e.g., `"10M"`, `"2.5G"`, `"MAC"`, `"VLAN"`, `"CPU"`)
## Build
No special flags are needed. The `html/` directory is embedded by `fileadder` during the build.
## Script Load Order
`i18n.js` must be loaded after `main.js` (which defines `t()`'s dependencies like `LANG`) but before any page-specific JS that calls `t()`:
```html
<scriptsrc="/main.js"></script>
<scriptsrc="/i18n.js"></script>
<scriptsrc="/eee.js"></script><!-- uses t() -->
```
The `navigation.js` script is loaded last (bottom of `<body>`).
Other device based on RTL8272/3 that may work are described here: [Up-N-Atoms 2.5 GBit RTL Switch hacking guide](https://github.com/up-n-atom/SWTG118AS)
Many of the RTL8272/3 devices come in versions with PoE support. The RTLPlayground usually also
works on these, however, no support for configuring PoE is provided, simply because these
port_mtu_heading:'Configure Maximum Frame Size (MTU) forwarded at Port',
port_auto:'Auto',
port_2500m:'2500MBit/Full',
port_1000m:'1000MBit/Full',
port_100m_f:'100MBit/Full',
port_100m_h:'100MBit/Half',
port_10m_f:'10MBit/Full',
port_10m_h:'10MBit/Half',
port_apply:'Apply',
stat_title:'FreeSwitchOS Port Statistics',
stat_heading:'Port Statistics',
stat_detailed:'Detailed Port Statistics',
stat_close:'Close',
stat_col_port:'Port',
stat_col_name:'Name',
stat_col_link:'link',
stat_col_tx_good:'TX Good',
stat_col_tx_bad:'TX Bad',
stat_col_rx_good:'RX Good',
stat_col_rx_bad:'RX Bad',
stat_col_all:'All Counters',
stat_counter:'Counter',
stat_value:'Value',
stat_show:'Show',
vlan_title:'FreeSwitchOS VLAN Configuration',
vlan_heading:'VLAN Configuration',
vlan_select:'VLAN Select:',
vlan_choose:'— VLAN Choose —',
vlan_id:'VLAN ID:',
vlan_get_config:'Get Configuration',
vlan_name:'VLAN Name:',
vlan_tagged:'Tagged Ports',
vlan_untagged:'Untagged Ports',
vlan_select_all:'Select all',
vlan_pvid:'Use as default VLAN for incoming traffic (PVID)',
vlan_update:'Update / Create',
vlan_configured:'Configured VLANs',
vlan_col_name:'Name',
vlan_col_member:'Member Ports',
vlan_col_tagged:'Tagged Ports',
vlan_col_untagged:'Untagged Ports',
vlan_col_pvid:'PVID Ports',
vlan_col_delete:'Delete',
vlan_set_id_first:'Set VLAN ID first',
vlan_delete_confirm:'Delete VLAN ',
lag_title:'Link Aggregation Configuration',
lag_heading:'Link Aggregation Groups Configuration',
lag_update:'Update / Create',
mirror_title:'Mirror Configuration',
mirror_heading:'Mirror Configuration',
mirror_enabled:'Enabled:',
mirror_port:'Mirroring Port:',
mirror_tx:'Mirrored Ports (TX)',
mirror_rx:'Mirrored Ports (RX)',
mirror_update:'Update / Create',
mirror_disable:'Disable Mirroring',
mirror_set_port_first:'Set Mirroring Port first',
mirror_select_ports:'Select Mirrored Ports',
eee_title:'EEE Configuration',
eee_heading:'EEE Status',
eee_advertising:'Advertising',
eee_partner:'Link-Partner advertises',
eee_port:'Port',
eee_active:'Active?',
eee_enable:'Enable EEE',
eee_disable:'Disable EEE',
eee_on:'ON',
eee_off:'OFF',
l2_title:'FreeSwitchOS L2 Configuration',
l2_heading:'L2 Configuration',
l2_col_port:'Port',
l2_col_type:'Type',
l2_col_remove:'Remove Entry',
l2_shown:'Shown:',
l2_delete:'Delete',
l2_static:'static',
l2_learned:'learned',
bw_title:'Ingress and Egress Bandwidth',
bw_heading:'Ingress and Egress Bandwidth',
bw_ingress:'Ingress',
bw_egress:'Egress',
bw_col_port:'Port',
bw_col_limit:'Limit',
bw_col_bandwidth:'Bandwidth [kBit/s]',
bw_col_flow:'Flow Control',
bw_col_apply:'Apply',
bw_unlimited:'UNLIMITED',
sys_title:'System Settings',
sys_tab_system:'System',
sys_tab_advanced:'Advanced',
sys_tab_console:'Console',
sys_heading:'System Settings',
sys_ip:'IP address:',
sys_model:'Model:',
sys_hostname:'Hostname:',
sys_apply:'Apply',
sys_netmask:'Netmask:',
sys_gateway:'Gateway:',
sys_language:'Language:',
sys_mgmt_vlan:'Management VLAN:',
sys_mgmt_untagged:'untagged',
sys_mgmt_confirm:'Move switch management to VLAN ',
sys_mgmt_warn:'The switch will start tagging its own traffic with that VLAN. If the port you are connected through does not carry it, this page becomes unreachable and the setting can only be undone over the console. Continue?',
sys_ip_note:'When updating the above settings, remember to point your browser to the new IP afterwards:',
sys_update:'Update Settings',
sys_save_label:'Save all current settings to Flash:',
sys_save:'Save Settings to Flash',
sys_advanced:'Advanced Settings',
sys_startup_config:'Startup configuration:',
sys_startup_warn:'Be careful when saving the directly edited startup configuration, you can lock yourself out:',
sys_clear_config:'Clear Startup Config',
sys_save_startup:'Save Startup Settings to Flash',
sys_reset:'Reset Switch',
sys_console:'Console Command',
sys_enter_cmd:'Enter command:',
sys_send_cmd:'Send Command',
sys_console_warn:'Be careful when entering console commands, you can lock yourself out!',
sys_invalid_ip:'Invalid ip:',
sys_reset_confirm:'Are you sure you want to reset the switch?',
sys_resetting:'Switch is resetting. Please wait and refresh the page.',
login_title:'RTL Switch Login',
login_heading:'RTL Switch Login',
login_wrong:'Wrong password!',
login_password:'Password',
login_login:'Login',
index_title:'FreeSwitchOS Main Page',
index_heading:'Switch Configuration',
index_settings:'Settings',
update_title:'Firmware update',
update_heading:'Firmware Update',
update_instruction:'Choose a firmware update file to upload:',
update_upload:'Upload File',
common_port:'Port ',
common_pkts:' pkts',
},
ja:{
nav_overview:'概要',
nav_port_config:'ポート設定',
nav_port_stat:'ポート統計',
nav_l2:'L2 設定',
nav_mirror:'ミラーリング',
nav_lag:'リンクアグリゲーション',
nav_eee:'EEE',
nav_bandwidth:'帯域制限',
nav_system:'システム設定',
nav_fw_update:'ファームウェア更新',
port_name:'名前',
port_status:'状態',
port_not_enabled:'無効',
port_link_speed:'リンク速度',
port_vendor:'ベンダー',
port_model:'モデル',
port_serial:'シリアル',
port_temp:'温度',
port_vcc:'電圧',
port_tx_fault:'TX 障害',
port_tx_disabled:'TX 無効',
port_tx_bias:'TX バイアス',
port_tx_power:'TX 電力',
port_rx_power:'RX 電力',
port_rx_los:'RX 信号ロス',
speed_disabled:'無効',
speed_down:'リンクダウン',
port_title:'FreeSwitchOS ポート設定',
port_heading:'ポート設定',
port_col_port:'ポート',
port_col_name:'名前',
port_col_speed:'現在のリンク速度',
port_col_set_speed:'速度設定',
port_col_disabled:'無効',
port_col_apply:'適用',
port_mtu_heading:'ポートの最大フレームサイズ (MTU) 設定',
port_auto:'自動',
port_2500m:'2500Mbps/全二重',
port_1000m:'1000Mbps/全二重',
port_100m_f:'100Mbps/全二重',
port_100m_h:'100Mbps/半二重',
port_10m_f:'10Mbps/全二重',
port_10m_h:'10Mbps/半二重',
port_apply:'適用',
stat_title:'FreeSwitchOS ポート統計',
stat_heading:'ポート統計',
stat_detailed:'詳細ポート統計',
stat_close:'閉じる',
stat_col_port:'ポート',
stat_col_name:'名前',
stat_col_link:'リンク',
stat_col_tx_good:'TX 正常',
stat_col_tx_bad:'TX 異常',
stat_col_rx_good:'RX 正常',
stat_col_rx_bad:'RX 異常',
stat_col_all:'全カウンタ',
stat_counter:'カウンタ',
stat_value:'値',
stat_show:'表示',
vlan_title:'FreeSwitchOS VLAN 設定',
vlan_heading:'VLAN 設定',
vlan_select:'VLAN 選択:',
vlan_choose:'— VLAN 選択 —',
vlan_id:'VLAN ID:',
vlan_get_config:'設定取得',
vlan_name:'VLAN 名:',
vlan_tagged:'タグ付きポート',
vlan_untagged:'タグ無しポート',
vlan_select_all:'すべて選択',
vlan_pvid:'受信トラフィックのデフォルト VLAN (PVID)',
vlan_update:'更新 / 作成',
vlan_configured:'設定済み VLAN',
vlan_col_name:'名前',
vlan_col_member:'メンバーポート',
vlan_col_tagged:'タグ付きポート',
vlan_col_untagged:'タグ無しポート',
vlan_col_pvid:'PVID ポート',
vlan_col_delete:'削除',
vlan_set_id_first:'VLAN ID を先に設定してください',
vlan_delete_confirm:'VLAN 削除 ',
lag_title:'リンクアグリゲーション設定',
lag_heading:'リンクアグリゲーショングループ設定',
lag_update:'更新 / 作成',
mirror_title:'ミラーリング設定',
mirror_heading:'ミラーリング設定',
mirror_enabled:'有効:',
mirror_port:'ミラーポート:',
mirror_tx:'ミラー元ポート (TX)',
mirror_rx:'ミラー元ポート (RX)',
mirror_update:'更新 / 作成',
mirror_disable:'ミラーリング無効化',
mirror_set_port_first:'ミラーポートを先に設定してください',
mirror_select_ports:'ミラー元ポートを選択してください',
eee_title:'EEE 設定',
eee_heading:'EEE 状態',
eee_advertising:'EEE アドバタイジング',
eee_partner:'リンクパートナー広告',
eee_port:'ポート',
eee_active:'有効?',
eee_enable:'EEE 有効化',
eee_disable:'EEE 無効化',
eee_on:'オン',
eee_off:'オフ',
l2_title:'FreeSwitchOS L2 設定',
l2_heading:'L2 設定',
l2_col_port:'ポート',
l2_col_type:'タイプ',
l2_col_remove:'エントリ削除',
l2_shown:'Shown:',
l2_delete:'削除',
l2_static:'静的',
l2_learned:'学習',
bw_title:'入力/出力帯域制限',
bw_heading:'入力/出力帯域制限',
bw_ingress:'入力',
bw_egress:'出力',
bw_col_port:'ポート',
bw_col_limit:'制限',
bw_col_bandwidth:'帯域 [kbps]',
bw_col_flow:'フロー制御',
bw_col_apply:'適用',
bw_unlimited:'制限無し',
sys_title:'システム設定',
sys_tab_system:'システム',
sys_tab_advanced:'詳細設定',
sys_tab_console:'コンソール',
sys_heading:'システム設定',
sys_ip:'IP アドレス:',
sys_model:'モデル:',
sys_hostname:'ホスト名:',
sys_apply:'適用',
sys_netmask:'ネットマスク:',
sys_gateway:'ゲートウェイ:',
sys_language:'言語:',
sys_mgmt_vlan:'Management VLAN:',
sys_mgmt_untagged:'untagged',
sys_mgmt_confirm:'Move switch management to VLAN ',
sys_mgmt_warn:'The switch will start tagging its own traffic with that VLAN. If the port you are connected through does not carry it, this page becomes unreachable and the setting can only be undone over the console. Continue?',
sys_ip_note:'上記設定を変更した場合は、ブラウザで新しい IP にアクセスしてください:',
sys_mgmt_confirm:'Move switch management to VLAN ',
sys_mgmt_warn:'The switch will start tagging its own traffic with that VLAN. If the port you are connected through does not carry it, this page becomes unreachable and the setting can only be undone over the console. Continue?',
<tr><th>Port</th><th>Current Link Speed</th><th>Set Speed</th><th>Disabled</th><th>Apply</th></tr>
<tr><thdata-i18n="port_col_port">Port</th><thdata-i18n="port_col_name">Name</th><thdata-i18n="port_col_speed">Current Link Speed</th><thdata-i18n="port_col_set_speed">Set Speed</th><thdata-i18n="port_col_disabled">Disabled</th><thdata-i18n="port_col_apply">Apply</th></tr>
</table>
<h2style="margin-top:3em">Configure Maximum Frame Size (MTU) forwarded at Port</h2>
<h2style="margin-top:3em"data-i18n="port_mtu_heading">Configure Maximum Frame Size (MTU) forwarded at Port</h2>
Some 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.