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 SWTGW218AS label covers more than one PCB. On boards with the
SWTG018AS-V2.1.0 silkscreen the SFP module-detect is GPIO38 (no LOS pin
wired) and the LED block is wired differently, including a bi-color SFP
LED (green up to 2.5G, blue at 10G) that the LED-set encoding cannot
express. With the existing MACHINE_SWTGW218AS definition these boards
never detect an SFP module, so an SFP-uplinked switch comes up with no
working uplink.
Add the variant as its own machine define named by the PCB marking,
leaving MACHINE_SWTGW218AS unchanged for boards that match its wiring.
The LED register values are taken from the stock firmware; the PIN_MUX_0
write routes the blue-LED pin to the LED controller, without which no
LED register value can drive it. PIN_MUX_1/2 stay untouched so SFP
detect and i2c remain GPIOs.
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