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.
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.
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.
Two build-time steps shrink the embedded Web UI (html/) from 113,015 to
34,784 bytes (30.8 %):
- tools/minify.py (new): string-literal-safe minifier that strips
comments and indentation; comments are pure overhead on the wire, so
the transfer size drops already before gzip (113,015 -> 101,498).
Binary files (.ico) are copied through unchanged. The output goes to
output/html_min/, the new fileadder input.
- fileadder -z: gzip-compresses every added file (zlib gzip format,
Z_BEST_COMPRESSION); the generated f_data entries carry a gzip flag.
tools/Makefile links the tool against -lz.
- httpd: adds "Content-Encoding: gzip" for flagged entries; the 8051
only copies compressed bytes, decompression happens in the browser.
The JSON API stays uncompressed.
Fixes required to make the sizes deterministic:
- fileadder terminated files at data_read + 1, so the strlen()-based
size computation depended on uninitialised buffer content; the NUL
now lands directly after the content.
- version.h was .PHONY, so every build rewrote it while parallel builds
could read a half-written copy.
- the file list was unscoped and missed .css/.ico; it is now scoped to
html/ and covers all embedded types.
Measured with make MACHINE=KP_9000_6XHML_X2: data block end moves from
0x5b977 to 0x487e0 (~78 KB freed on a 512 KB image). Verified with
MACHINE=PCB_K0402WS_V3 and on real hardware (PCB-K0402WS-V3).
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 presence of the main function is a hardcoded trigger in sdcc that causes
the interrupt vectors and atomic rollback helpers to be generated as part of
compilation of the file containing the main function.
This makes sure the runtime library is correctly initialized and reduces
crtstart.asm to contain only the banked call helpers.
To ensure that the __interrupt_vect in area HOME is located at the beginning
of the firmware, the linker flags are updated to set the HOME start to
0x00000 and the linker object order is modified to have rtlplayground.rel
first.
Generated code in rtlplayground.asm:
```
;--------------------------------------------------------
; interrupt vector
;--------------------------------------------------------
.area HOME (CODE)
__interrupt_vect:
ljmp __sdcc_gsinit_startup
ljmp _isr_ext0
.ds 5
ljmp _isr_timer0
.ds 5
ljmp _isr_ext1
.ds 5
reti
.ds 7
ljmp _isr_serial
.ds 5
ljmp _isr_timer2
.ds 5
reti
.ds 7
reti
.ds 7
ljmp _isr_ext2
.ds 5
ljmp _isr_ext3
; restartable atomic support routines
.ds 2
sdcc_atomic_exchange_rollback_start::
nop
nop
sdcc_atomic_exchange_pdata_impl:
movx a, @r0
mov r3, a
mov a, r2
movx @r0, a
sjmp sdcc_atomic_exchange_exit
nop
nop
sdcc_atomic_exchange_xdata_impl:
movx a, @dptr
mov r3, a
mov a, r2
movx @dptr, a
sjmp sdcc_atomic_exchange_exit
sdcc_atomic_compare_exchange_idata_impl:
mov a, @r0
cjne a, ar2, .+#5
mov a, r3
mov @r0, a
ret
nop
sdcc_atomic_compare_exchange_pdata_impl:
movx a, @r0
cjne a, ar2, .+#5
mov a, r3
movx @r0, a
ret
nop
sdcc_atomic_compare_exchange_xdata_impl:
movx a, @dptr
cjne a, ar2, .+#5
mov a, r3
movx @dptr, a
ret
sdcc_atomic_exchange_rollback_end::
sdcc_atomic_exchange_gptr_impl::
jnb b.6, sdcc_atomic_exchange_xdata_impl
mov r0, dpl
jb b.5, sdcc_atomic_exchange_pdata_impl
sdcc_atomic_exchange_idata_impl:
mov a, r2
xch a, @r0
mov dpl, a
ret
sdcc_atomic_exchange_exit:
mov dpl, r3
ret
sdcc_atomic_compare_exchange_gptr_impl::
jnb b.6, sdcc_atomic_compare_exchange_xdata_impl
mov r0, dpl
jb b.5, sdcc_atomic_compare_exchange_pdata_impl
sjmp sdcc_atomic_compare_exchange_idata_impl
```
The current code for entering commands via the serial CLI is completely
refactored and moved out of rtlplayground.c into a separate file cmd_edit.c
putting code into BANK2.
The serial ring buffer sbuf[] is now exclusively used for receiving
keys, including escape sequences (DEL will generate a 4-byte escape
sequence). The command is now held in cmd_buffer. This reduces
XMEM use, because the serial buffer can be much smaller.
Supported is only editing the current command line via backspace, del,
cursor left and right.
Because the code cannot distinguish escape sequences which are not yet
complete (because the serial isr has not yet put all characters into
the serial buffer) from the dozens of unsupported ones (F-keys/home,
Page up/down, cursor up/down...) they are ignored and remain in the
serial buffer until the ring-pointer overwrites them. This is standard
behaviour for consoles, which will print out garbage for unsupported
characters. In this implementation, the command line buffer and the
visible command line in the terminal are always synced, so garbage
can be removed again by ediing the line.
The code makes several redundant checks in order to prevent race-conditions
between the serial ISR and the comand-editing code.