uip-fw.c forwards packets between several network interfaces. Nothing in
this firmware calls it: the only references to uip_fw_output() outside the
module are commented out in uip-split.c. It is compiled and linked all the
same, and its register pressure lands in direct RAM, which on SWTGW218AS is
already reported as having no spare space.
Add UIP_CONF_FORWARDING alongside the existing UIP_CONF_BROADCAST and build
the module only when it is set. A device that forwards turns it on and gets
the module unchanged; a single-interface device stops paying for it.
On SWTGW218AS the stack gains 9 bytes, from 151 available to 160, and BANK1
drops 1200 bytes, from 47994 to 46794.
Add support for the F7008-2.5 board (PCB F7008-2.5GPOE-V1.2), an
RTL8373N + RTL8224N design with eight 2.5GBASE-T ports and one SFP+ cage.
The LED pads are not wired in port order, so the generic DEFAULT_8C_1SFP
target lights the wrong lamp on this board. The pad to lamp map was
measured on the hardware and is documented in doc/devices/F7008-2.5.md.
The green lamp marks a 2.5G link, the yellow lamp blinks while data flows.
The counters, the phase hooks in idle() and the "health" command are now
behind HEALTH, set from the command line with HEALTH=1. Without it the
hooks are empty macros and the command does not exist, so a default build
carries none of it: on SWTGW218AS that is 1065 bytes of bank 2, 101 of the
common segment and 28 of xdata, plus seven banked calls per main-loop pass.
It is a developer's tool. Reading the dump takes knowing the main loop, and
a user who needs it for a report can build with the flag on, which is how
the kernel treats its lock and latency instrumentation as well.
The tcp slot dump printed the slot index behind `if (UIP_CONNS > 1)`, and
with one connection configured that is a constant-false condition around
live code: sdcc reported two "unreachable code" warnings for the body and
one "conditional flow changed by optimizer" for the loop that could only
run once.
Print the single slot directly and state the assumption as a build-time
check, so a build that raises UIP_CONNS fails loudly here instead of
quietly labelling every line as slot zero. Builds warning-free again.
What each line of the dump means, how two dumps give rates, and the
two things worth knowing before trusting the numbers: the command
inflates its own cmd phase counters by the cost of printing, and the
untouched figure is a high-water mark, not a live level.
Every probe of a misbehaving switch so far measured it from the
outside. This puts the witnesses inside.
Each idle() pass increments a loop counter and stamps a checkpoint
after every phase: link polling, SFP, RX, TX, STP and the command
dispatch. A phase spanning two or more system ticks counts as slow and
its worst time is kept, in 5 ms units. handle_rx counts frames, so the
dump separates a flooded CPU from a stalled loop.
At boot the free stack area is painted, preserving the interrupt state
around the painting, and the dump reports how much of the pattern
survives next to the live stack pointer. The TCP connection table is
printed whole: state, timers, retransmissions, ports and peer, next to
the httpd transfer state, so a permanently occupied slot names its
occupant. With more than one slot configured every slot is listed.
Everything is raw hex; two dumps a known time apart give the rates.
The implementation sits in cmd_parser next to the other command
handlers and needs no storage beyond its counters.
handle_sfp() called sfp_module_read() as soon as it saw a module appear, and
that path opened with delay(100). delay() spins on PCON until the tick counter
runs down, so the main loop stood still for 100 ticks - half a second at
SYS_TICK_HZ 200. Nothing else ran in that window: no STP, no LACP, no packet
handling. A diagnostic build measured the sfp phase at up to 107 ticks, the
longest stall of any phase in the loop.
Remember when the module showed up and let the loop continue. The I2C read
happens on a later pass, once the wake-up period has elapsed. Pulling a module
cancels a pending read, and a failed read still restores the detect bit, so the
existing retry-on-the-next-poll behaviour is unchanged.
The comparison is written out rather than using a helper so that this does not
depend on anything outside the current tree. The pending flag is an array
indexed by slot, like the other per-slot state in this file.
Cost on SWTGW218AS, measured against the branch point: BANK3 11173 -> 11264,
CSEG, BANK1 and BANK2 unchanged, plus 4 bytes of xdata.
Some generic 1.25gbps DAC cables were seen to have a NUL *after* the
space padding in the model field. This caused the normalization to miss
the space padding in the string. The vendor field did not have the
issue.
Note that this seems to be against spec per SFF-8472, which mentions
only space padding or *all* NUL, not a combination of both. In the case
of all NUL, we'll waste a few cycles with no-op writes.
Building with SDCC 4.6.0 emits tons of warnings e.g.:
cmd_parser.c:31: warning 356: object in read-only code space should be const
cmd_parser.c:36: warning 357: pointer to object in read-only code space should be pointer to const
These of course become fatal when building with --Werror.
Comparing resultant binaries, it seems that a single string is shifted
(deduplication?) but the numbers emitted at the end of the build are
otherwise identical.
idle() went to sleep before it looked at the receive buffer, so a packet that
arrived just after the loop dozed off waited for the next tick before anything
touched it. Every TCP round-trip paid that toll, which is why the web UI felt
sluggish even though no phase of the loop was anywhere near busy.
Check the receive buffer first and only sleep when it is empty. The read takes
its result straight from SFR_DATA_U16 rather than going through reg_read_m()
and the sfr_data[] copy, which is what the rest of the tree already does.
Measured on SWTGW218AS with this change alone:
web UI, median of 15 requests for the main page 88 ms -> 76 ms
CPU port under a one-way UDP stream 159 -> 230 packets/s
The loop still sleeps normally when there is nothing to do, so this does not
turn the core into a busy-wait: loops/ticks stays at 0.99 on an idle switch,
and rises above 1.0 only while packets are actually queued.
Costs 12 bytes of code and no extra XDATA.
handle_rx() picked up exactly one packet per iteration of the main loop. Since
the loop sleeps until the next tick, that capped the CPU port at roughly one
packet per tick no matter how little work it had to do. A burst of broadcast,
ARP or IGMP traffic aimed at the CPU queued up behind that and pushed out the
STP and LACP timers.
Drain up to RX_BUDGET packets in one pass instead. The budget matters: without
one a sustained flood would spin here and starve everything else in the loop.
The two register reads in the loop take their result straight from
SFR_DATA_U16 rather than going through reg_read_m() and the sfr_data[] copy,
which is what the rest of the tree already does.
Measured on SWTGW218AS with a one-way UDP stream, counting the packets the CPU
actually processed, with this change alone:
before 159 packets/s
RX_BUDGET 4 245 packets/s, loop iteration ~16 ms
RX_BUDGET 8 298 packets/s, loop iteration ~26 ms
Throughput is already most of the way there at 4, so the larger budget mostly
buys a longer worst-case delay for the other phases. STP stayed responsive in
both cases.
Costs 71 bytes of code and no extra XDATA.
All the possibilities are handled, or otherwise a compile-time macro
I2CBUS_VALID ensures invalid options are rejected with a compiler error.
Also fix a compiler warning.
Use 0x7 as error value for an invalid I2C SDA/SCL-pin value.
The macro `I2CBUS_VALID` checks for this value.
When a invalid pin is ues, the compiler errors out with:
"machine.c:<line>: error 176: sizeof applied to an incomplete type",
Error message is not ideal but at-least points to the right location in
the code.
We can reduce the i2c-settings to just one byte, and this new const value
can directly be used by the I2C-peripheral without additional computation.
This saves 1-xdata load and a lot of bit-shifting.
I see a codesize savings of +200 bytes.
The TODO next to the disabled TCP checksum code asks which bit of the RX tag
carries the result. It is bit 2 of byte 1: the hardware sets it when the L4
checksum is bad.
That answers the question for UDP as well, which was not being checked either
because UIP_CONF_UDP_CHECKSUMS is 0. Both paths now read the flag instead of
running a checksum over the payload on the 8051.
A zero UDP checksum, which is optional in IPv4, is not flagged by the hardware,
so DHCP keeps working and no extra condition is needed.
rx_headers lives in rtlplayground.c, which the host tests do not link, so
test_httpd_tx carries the storage itself the way it already carries uip_buf.
A zeroed descriptor reads as a good checksum, so the existing scenarios are
unaffected.
A new scenario sets the flag on a request and expects it counted as a checksum
error and left unanswered, then sends the same request with the flag clear and
expects it served. Built against uip.c without this change, those first two
checks fail and the two control checks still pass, so the scenario tells the
two versions apart rather than merely exercising the code.
Costs 156 bytes of code on SWTGW218AS and no extra XDATA.
The speed selector on the Ports tab was filled with its options and never
given a value, so it read "auto" on every load whatever the port was set
to. Forcing a port to 1G and reloading showed "auto" in the selector
beside a link badge reading 1G.
The advertisement bits ports.json already reports say what the port is
set to: phy_set_speed() writes the same three registers the JSON reads
back, and every forced speed leaves exactly one of the six bits standing
while auto leaves the lot. Map those to the selector, and leave it alone
while it has focus, as the MTU field does.
Values with no entry in the table, such as the 100M setting that
advertises both duplex modes, leave the selector untouched rather than
claim a setting the list cannot express.
port_eee_enable() and port_eee_disable() report the port with
print_byte(), which prints the internal index in hex, so turning EEE off
on port 5 answers "EEE off for 04". port_eee_status() in the same file
already uses print_phys_port(), and the status listing it produces counts
from one, so the two halves of the same command disagreed.
Use print_phys_port() in all three messages.
max_upload was armed once when the upload began and never reduced, so
`max_upload - uip_len > 0` was an unsigned expression that only went
false when a segment arrived whose length matched the limit exactly. The
write pointer climbed from the staging address with nothing to stop it,
while flash_write_bytes() carries the address in three bytes and drops
whatever does not fit.
Subtract each segment as it is taken and compare before subtracting, which
is what the comment there described. The limit was only ever armed for
firmware uploads; configuration uploads keep the bound their own buffer
already imposes.
The multipart branch for /config tests authenticated before it touches
anything; the branch for /upload does not, and the 401 for it is only
raised further down at the dispatch. By then the request has already set
config_upload, uptr, verify_crc, max_upload and pre_acc, and announced a
firmware upload on the console.
Those five are globals while the transfer state they steer is per
connection, and the stack is built for ten connections, so a request that
is about to be rejected can still retarget an upload another client is in
the middle of: a configuration upload continues as a firmware one and its
remaining parts are written to the staging area instead of the
configuration sector.
Apply the same test the neighbouring branch uses, before the assignments.