A commit hash can be looked up directly, a checksum cannot. Print the last commit that touched sdcc/, with -dirty for local changes, and fall back to the checksum when git is not available. The toolchain directory keeps the checksum.
The boot banner and the version command print the SDCC release and
revision from the compiler's own macros, and the name of the in-tree
toolchain the Makefile built, which is the checksum of sdcc/ and so
changes with every patch in it. A build with CC given on the command
line says "system" instead, since that compiler need not carry the
patches. A plain release and the same release with the patches report
the same revision, so the toolchain line is what tells them apart.
Costs 66 bytes of BANK2.
The web server's bank had no room left for the four sessions once the
original firmware's more robust header handling came in. Picking a slot,
checking a cookie and starting a session move to sessions.c in bank 3;
httpd.c only calls session_init(), session_check() and session_new().
The random id is written straight into the reply, which drops
gen_random_hex_chars() from bank 1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
html_min was phony, so every make deleted output/html_min and minified
all of html/ again, even with nothing changed. A stamp file now stands for
the minified copy and depends on the sources and on minify.py. A list of
the source names, rewritten only when it changes, is a dependency too, so
adding or removing a file in html/ still redoes the copy and a removed
file does not stay behind in the image. If output/html_min is missing,
the stamp is remade in the same run.
The note on version.h in webui-compression.md still said it was phony.
Released SDCC 4.5.0 and 4.6.0 miscompile some of our code in the mcs51
register allocator (SDCC bugs 4153 and 4154, fixed in trunk r17025 and
r17039). The first make now downloads the 4.6.0 sources, checks their
sha256, applies the two fixes from sdcc/ and installs the compiler into
toolchain/, named after a checksum of sdcc/, so a changed patch builds a
new one. make CC=sdcc still uses an installed compiler.
"port <port> cabletest" runs the cable diagnostic of the copper PHYs
and reports one line per pair: ok, or open, short, short to another
pair or impedance mismatch with the distance to the fault.
The PHY part follows the cable_test_start and cable_test_get_status
ops of the Linux phy_driver and takes the port number:
rtl_phy_cable_test_start() triggers the test and
rtl_phy_cable_test_get_status() returns true once all four pairs are
done, with a result code and a length per pair. The decode order is
the one of the Linux driver. The same two functions drive the RTL8224
and the internal PHYs of the RTL8372 and RTL8373.
The command only powers the port down and returns. A state machine
called from handle_tick() calls the start op after one second, polls
the status op, stores the result, clears the trigger, also after a
timeout, and restores the control register, so the main loop never
waits. The result is printed when it is ready and "port <port>
cabletest show" prints it again. One test runs at a time.
The result flags are the low byte of the per-pair status word. The
vendor code reads the high byte, which is always zero on the RTL8373.
A link partner that keeps transmitting makes the PHY report an
inter-pair short on every pair, and forcing the speed with
autonegotiation off, as the Linux driver does, does not take the link
down. The port is therefore powered down for a second before the
test. The control register is restored afterwards with an
autonegotiation restart, so speed settings survive and the link
returns.
A test started on a port that is switched off never finishes and
leaves the PHY unusable until the switch is reset, so such a port is
refused, and a test is dropped if the port is switched off during the
wait. SFP ports are refused by the existing check. The external copper
PHYs of the RTL8372 boards are refused because the test has not been
run on one.
The distance is the raw length word minus 150, at 43 counts per 50 cm.
Fitted on open cables of 0.9 m, 2 m and 7.6 m; the pairs of one cable
differ by about 0.2 m. The Linux formula, raw minus 620 at 78 counts
per metre, gives 0, 0 and 2.4 m for these cables.
An unplugged port has no defined result: some pairs report open at
about 0.5 m and others a short or a mismatch at 2 to 3 m.
Dynamic link aggregation on top of the static LAGG, for RTL8372/8373:
receive, selection and mux state machines (coupled mux, one aggregator
per group), with "lacp show" to look inside. Each of the four trunk
groups runs its own aggregator on the candidate ports assigned with
"lag <n> lacp <ports>"; the engine starts with the first such group and
stops with the last.
LACPDUs reach the CPU through the reserved-multicast trap, so delivery
needs no L2 entry and nothing written per VLAN or per PVID. The trap
acts on the address before the VLAN lookup and before the ingress
filters, so a LACPDU also arrives on a port that admits tagged frames
only and on a port STP holds in blocking, neither of which a forwarded
one reaches. A trapped frame carries no VLAN tag,
so lacp_in() takes the offset of the PDU body from TRAP_RX_BODY(). The
action is set with trap_rma_set(), which leaves the flags sharing that
register alone, and "lacp off" puts the slow-protocols drop default
back. doc/lacp.md and doc/link_aggregation.md record what was measured,
including why the trap looked like a dead end at first.
The web UI gets a Static/LACP mode per group on the LAG page with live
status, and the startup-config validator knows the per-group syntax. A
host-side sandbox for the state machines runs in the test harness
together with the LAG hash check. It drives both forms of an incoming
LACPDU, so the body offset of a trapped one is covered as well.
The L2 table page iterated with the "next unicast" read method, so
static multicast entries such as an IGMP group were invisible. It now
iterates with "next address", skips L3 entries (their layout carries IP
addresses where a MAC is expected), and reports the lowest member of a
multicast entry's port mask, so such an entry shows up as a static
entry on that port.
A port taken out of LACP forgets its partner, so "lacp show" after
"lacp off" no longer reports one. Applying the same candidate set to a
group again, as the LAG page does on every Apply, leaves the trunk
alone instead of tearing it down for a second or two. lacp_in() checks
the frame length and ethertype before reading a LACPDU, so a runt or a
foreign frame on the slow-protocols address cannot leave stale buffer
bytes as a partner. The sandbox covers these three in T20-T22. On the
LAG page, LACP mode with no ports checked sends "lag N lacp off", and
the startup-config validator wants at least one port after "lacp".
A reserved-multicast trap goes to the port named in EXT_CPU_CTRL, which
names no port out of reset, so trapped frames were dropped at ingress.
trap_init() points it at the CPU port once at startup, and
trap_rma_set() changes only the action field of an RMA register.
A trapped frame arrives without the VLAN tag a forwarded one gets, so
TRAP_RX_BODY() gives the offset of the payload for both forms.
STP now traps its address while it runs instead of steering it with a
static L2 entry per VLAN, so turning it on no longer walks the VLAN
table and a pvid change no longer needs stp off and on. With STP off
the flooding entries are restored as before.
A trapped BPDU passes the tagged-only ingress filter that drops a
forwarded one, so the stp_setup() warning about such ports goes.
to each port once every 30 seconds.
* Additionally fixes the on-ward passing of LLDP frames through
the switch
The ports on the case are marked 1-8 so this should reflect that
rather than being 0-based
We can send the hostname as well, this can be dynamically allocated
Let's send the model as the system description as well
leds_dump() printed the pin mux, the LED enables and every LED set on each
boot, from BANK2. It now lives in its own file in BANK3, where there is
room, and runs only on the new "leds" command, next to gpio and sds, so it
stays at hand for bringing up a new board without costing BANK2 or boot
output.
The switch has no battery backed clock, only a seconds counter that starts
at zero on every boot. An SNTP client keeps UTC as the time of the last
answer plus the seconds counted since: "ntp server <name|ip>", "ntp interval
<minutes>", "ntp timezone <+hh:mm>", "ntp dst off|eu|us", "ntp on|off",
"ntp" and "time". A server given by name goes through the DNS resolver, an
answer from an unsynchronised server is ignored, and a failed attempt is
repeated after 30 seconds.
The local time adds the time zone offset and, while the European or United
States rule says so, one hour. The date is split with a shift and subtract
division and a walk over the years, so no 32-bit division from the library
ends up in the common area.
The System page gets the NTP settings and shows the local time.
A small resolver for IPv4 address records, so that clients such as NTP can
be given a host name: "dns server <ip> [<ip>]", "dns lookup <name>" and
"dns". Without a configured server it uses the one DHCP handed out. A query
goes to the servers in turn every two seconds and gives up after six tries;
answers with compressed names and CNAME records ahead of the address are
understood. A name that already is a dotted address is taken without a
query.
The resolver sits in BANK3, the command line in BANK2 next to the helpers it
uses. The System page shows and sets the servers.
The .bin rule passes -p html_data to fileadder, which rewrites
html_data.c and html_data.h after the objects that include them have
already been compiled and linked. The next make therefore rebuilds
html_data.c, httpd.c and page_impl.c even when nothing changed, and
so does the one after that. The rule only needs to embed the files
into the image; the header comes from the html_data.c rule above.
Spotted by plaes in #358, which cannot be merged as it stands.
With this and the previous commit, a second make after a full build
compiles nothing, and the image is byte for byte the same as before.
Two things kept the same source from producing the same image.
fileadder walked the input directory with readdir(), which returns entries
in whatever order the filesystem hands them over. The same tree therefore
embedded the files in a different order on a different filesystem, and the
image differed byte for byte. scandir() with alphasort() fixes the order.
version.h was listed in .PHONY, which makes it permanently out of date and
propagates that to everything including it, so a plain build recompiled the
four largest translation units even when nothing had changed. Dropping it
from .PHONY alone would go too far the other way: with no prerequisites the
recipe would never run again and the version string would freeze at its
first value. A FORCE prerequisite keeps the recipe running every time, and
the generated header is only moved into place when its content actually
differs, so the timestamp survives an unchanged rebuild.
Measured on this tree: with FORCE the version follows GIT_VERSION and an
unchanged rebuild leaves version.h untouched; without it the header keeps
the value from the first build.
The .d files track headers, so adding or removing a flag on a tree that
was already built leaves every object that did not change for some other
reason compiled with the old flags, and the image ends up a mix of the
two.
How that shows depends on the flag. DIAG=1 fails the link on undefined
symbols, which at least stops the build. An opt-in whose hooks expand to
empty macros loses the calls instead, so the link succeeds and the
feature quietly measures nothing.
Objects now depend on a stamp holding CC_FLAGS, rewritten only when the
flags differ, so an unchanged flag set still does not force a rebuild.
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 8051 in the RTL837x has 48 kB of XDATA, 0x0000 to 0xBFFF. Writing
a byte and reading it back from the same address works all the way up
to 0xBFFF on a SWTGW218AS, while at 0xC000 the byte comes back as 0x00.
Without --xram-size the linker assumes 16 MB, so running past the end
would still build cleanly and only show up at run time.
The value is 49151 rather than 49152. The linker check adds up the
sizes of the XDATA areas, and XDATA allocation starts at 0x0001, so a
limit of 49152 would still let the last byte land on 0xC000.
sfp.c takes handle_sfp, sfp_module_read, sfp_apply_quirks, sfp_get_info,
sfp_read_field, sfp_print_info, setup_sfp_gpio, the quirk table and the module
state that goes with them. Everything exported is __banked: handle_sfp() and
sfp_print_info() are reached from cmd_parser.c in BANK2, the rest from the
common area, and both are bank boundaries.
The quirk table needed checking rather than assuming. Its entries are __code
pointers, and a __code pointer carries no bank, so passing one to a function in
a different bank hands it an offset read through the wrong window. That is safe
here because strcmp() sits in the common segment at 0x51f and is reached with a
plain lcall, so the bank register still says 3 while it runs. Verified in the
built image: the table at 0x35580 holds 0x563c and 0x5644, which resolve to
"QSFPTEK" and "QT-SFP+-T" inside BANK3.
Common segment 13393 -> 8432 bytes, leaving 7492 free rather than 2531. BANK3
holds 5710, BANK1 is unchanged and BANK2 grows by 31 for the trampolines.
All 34 machines in machine.h build and link, and the same walk over the
generated assembly finds no plain lcall crossing a bank boundary.
The common segment is the scarce one: sixteen kilobytes shared by every bank,
and before this it had about 2.5 kB left. Most of what moves here runs once at
startup and does not have to sit in it. The two SerDes helpers are the
exception: the link-change handler and the SFP polling both reach sds_config()
while the switch is running, so they are not startup-only. None of it is
reached from an interrupt.
boot.c takes setup_i2c, init_smi, nic_setup, rtl8373_revision, rtl8224_enable,
early_boot_handle_button, set_hostname_default and the two SerDes helpers.
BANK3 is enabled with the linker flag; nothing else in the build needed
changing.
Everything moved is declared __banked, since every caller is outside BANK3:
main() and friends run from the common area and rtl837x_init.c calls
sds_config() and sds_config_mac() from BANK2. The calls the moved code makes
outward were already banked, so that side needed no work.
Common segment 13393 -> 10137 bytes, leaving 5787 free rather than 2531.
BANK3 holds 3732. All 34 machines in machine.h build and link. Walking
the generated assembly for plain lcalls that cross a bank boundary finds none
among 2808 calls; those build cleanly and only fail at run time, so it seemed
worth checking by machine rather than by eye.
make clean left tools/output in place, so the host tools were never
rebuilt after a clean. SUBDIRSCLEAN, which builds the per-directory
clean target names for exactly that purpose, was defined but nothing
consumed it. Give clean and distclean those targets as prerequisites
and add the rule that runs the subdirectory's own clean, which
tools/Makefile already provides.
machine_custom_init() runs once at boot, but its code and any tables it
uses were compiled into machine.c and so into the common bank. On the
SWTG018AS-V2.1.0 variant, whose init carries a 21-entry LED register
table, that overflows bank 0 by 0x66 bytes and main no longer links for
MACHINE_PCB_SWTG018AS_V2_1_0; any machine whose init grows can hit the
same wall. SDCC segment pragmas apply file-wide, so the hooks move to a
new machine_init.c compiled into BANK2, and the prototype becomes
banked. machine_check also compiles the new file per machine so the
hooks keep CI syntax coverage.
Resolve the conflicts against the 41 upstream commits since 52692d0:
- Makefile: keep the upstream parallel-build fixes (grouped html_data
target with order-only deps, create_build_dir, crtbank, deterministic
BUILD_DATE, atomic version.h write) and keep the minify + gzip
pipeline (-z, html_min, .css/.ico in the html file list) on top.
- html: l2.js stays removed; port the upstream L2 fixes into the
single-page main.js (walkL2() with empty-reply and 4096-entry cap
handling, 3 retries on error). The L2 poll now re-schedules itself
after each walk and stops when the section is hidden.
- httpd/httpd.c merged cleanly (gzip Content-Encoding alongside the
upstream GET walk bound and counters.json port check).
- doc/webui-compression.md: describe the upstream version.h write.
Verified: KP_9000_6XHML_X2 and PCB_K0402WS_V3 build sequentially and
with make -j8; all 8 embedded files decompress byte-identically;
httpd_sim serves main.js/index.html and the L2 JSON endpoint.
__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.