Commit Graph
34 Commits
Author SHA1 Message Date
mckero f3a369d38f Make the PC probe interval configurable, and trace the app's life as one tick
uvk5_pc_probe_interval_ms() reads UVK5_PC_PROBE_MS and falls back to 100, so existing probe runs are unchanged. At 2 ms a launch gives 468 samples in about 0.94 s and exactly one of them is inside the 4 KiB overlay: the app lives for roughly 2 ms, not 100.

The trace settles two things and adds one. The loader copies and jumps (the single overlay sample; a 16-byte app that calls nothing stays there forever). The sector-cache overwrite is dead: the flash log's last transaction for the run is the app's own code load (addr=103000 len=2408) and nothing follows it, no font read included. New: after the app dies the firmware loops inside its own flash (PCs near 0x08005118/0x08005122/0x08005248) with zero flash reads and no key response at all -- F then 7 no longer opens the menu and an explicit MENU down/up changes nothing -- so the launcher or its fault path is wedged.
2026-10-02 10:14:50 +08:00
mckero 394f473df0 Correct the overlay cause: the log order rules the sector-cache overwrite out
The flash probe records every transaction in order and the last one for that launch is the app's own code load (addr=103000 len=544, first bytes f0b583b0); nothing follows before the app is gone, so no font or resource read lands on the running app in that window. The 7848 font-region reads in the log belong to the firmware's own repainting.

What stands measured: the loader copies and jumps (a PC sample lands in the overlay, and a 16-byte app that calls nothing loops there forever); the app dies well under 100 ms (panel reads at t+113 ms still show the menu, at t+193 ms only the launcher's title box); an app that spins first survives a bar, a blit and led and dies around delay_ms; one that calls blit immediately is gone before it can be seen. Next: drop the PC probe interval from 100 ms to a couple of milliseconds, which is a one-line model change and turns it into a real trace of the app's short life.
2026-10-02 10:08:40 +08:00
mckero e9c344b52a Name the overlay failure: an app runs until it calls a service that reads the external flash
Measured on the page's emulator with UVK5_PC_PROBE and UVK5_FLASH_PROBE inherited through its own launcher, so the instance measured is the one that draws. Spin (16 bytes, calls nothing) sits at 0x20000286 in 42 of 383 PC samples after MENU: it loops in the overlay forever, so the loader works. Minesweeper (2408 bytes) is loaded -- the only big flash read is 0x109000 len 2408, first bytes its own -- and a single PC sample catches it inside the overlay before it is gone: it lives well under 100 ms. Phases (372 bytes) draws a progress bar straight into the framebuffer between calls, and the bars for framebuffer-only and led are on screen while the bar after delay_ms never appears.

The overlay is the PY25Q16 sector cache (app_overlay.h says so), so a service reading the external flash -- the font table is at 0x1E0000 -- lands on top of the running app. Real hardware cannot behave that way, since upstream's own apps call print_tiny, so the firmware must gate the sector cache while an app is loaded; that gate is what the model is missing. Next: find it in the flash driver and check what the model answers.
2026-10-02 10:05:24 +08:00
mckero 53a04ee869 Measure the launch on the page's own healthy instance: the menu works, the app never runs
With the pristine dump restored the page draws (485 lit bytes) and answers 0x0730 with four committed apps. Driving its /api/key and reading its /api/panel: F then 7 opens the menu (ink 1810 -> 1976), DOWN x3 moves the selection (1976 -> 1832), MENU on the app row drops the screen to the title box alone (1832 -> 210) after which MENU, DOWN, UP and F change nothing, and EXIT brings the menu back (1832). So the launcher is entered and left, and the app neither draws nor reads keys.

This supersedes the inconclusive PC measurements taken on hand-started QEMU instances, which came up without a picture while the page's instance draws. Recorded in both AGENTS files: restore work/user-flash.img before testing (the copy carrying the adopted FMP3 marker, image_crc32 0x9d27c3db rather than 0x4d87ce48, comes up blank and makes keys dead, which earlier rounds mistook for the app failing), and measure through the page rather than a hand-started emulator.
2026-10-02 09:59:14 +08:00
mckero bfd1a9ed2c Correct the overlay measurements: they were taken on blank instances, and this build has no F+7 menu
Hand-started QEMU instances all came up with a blank panel (0 lit pixels) while the page's own instance draws (803), same image and a different firmware file, so 'the PC never entered the overlay' was measured on a radio that never reached its main loop and is inconclusive rather than a finding.

Driving the page's own /api/key and reading /api/panel reproduces what the user sees: F, 7, DOWN x3 and MENU all answer ok and the screen does not change by a pixel (ink 232 throughout). The page's firmware.bin is 109.3 KiB and the Labs build that does open the app menu is 111.9 KiB -- a different build. The radio still answers 0x0730 with four committed apps, so it supports the app region but has no F+7 entry. Testing an overlay app requires running the Labs build, and measuring through the page rather than a hand-started QEMU.
2026-10-02 09:51:58 +08:00
mckero fa0f4e7f23 Record that no overlay app runs: the copy lands, the jump never happens
Measured by polling the PC over QMP every 30 ms, finer than the model's own 100 ms probe. After launching Tetris the overlay at 0x20000280 holds Tetris's code byte for byte, so the loader's copy is correct and aligned; the earlier claim in this session that it was shifted by one byte was my own parsing dropping the first value. But 172 PC samples over 4.5 s and 64 more over 2 s were all inside firmware flash at 0x08013260, a wait loop, and none landed in the overlay. A 16-byte app that calls nothing behaves identically, and Breakout's header is field-for-field the shape of ours, so neither the app's code nor its header is the reason. The failure is upstream of the app, which makes the earlier 'Tetris runs' note stale: per this file's own rule it is treated as unverified until it reproduces.

Also carries the CI-fix branch merge and a regression test for _edit_flash creating its working-copy directory (its fixture image has to be a real size: slot 0 sits at 0x102000).
2026-10-02 09:45:33 +08:00
mckero c0c2c3230e Merge the CI fix for the unit job 2026-10-02 08:37:40 +08:00
mckero f938d0b3bd Build overlay apps without the Arm toolchain, and say how far verification got
pip install ziglang cross-compiles to thumb-freestanding-eabi, which is enough to build a .app with no arm-none-eabi-gcc and no Docker. Measured refusals: --defsym, -Ttext and --section-start come back as unsupported linker args, so the VMA is resolved into a copy of app.ld; -T is forwarded (a missing script errors); --image-base is accepted but page-aligns the segments into 0x200103C8 and 0x20020C94. tools/elf2bin.py extracts allocated sections rather than program headers, because lld maps the ELF header and phdr table as a 180-byte LOAD of its own -- following the headers starts the image at 0x20000000 and APP_ERR_VMA. One division pulled in __aeabi_uidiv, which a -nostdlib blob cannot have: Minesweeper now avoids division entirely. app_main carries the .text.entry attribute upstream's apps use, so the entry is first for the loader's jump to offset 0.

Minesweeper builds to 2408 bytes of code against a 4096-byte budget. Installed through the page, the firmware reads the slot header twice and then exactly code_size bytes from slot+0x1000 -- the only read of that size in the boot log -- so the blob shape, header, CRC, VMA and offset are all accepted. Whether control reaches the overlay is unproven: the 100 ms PC probe saw no overlay address, no APP ERROR screen appears, and the app does not draw. test_elf2bin pins the phantom-header-segment lesson; both AGENTS files record the rest.
2026-10-02 08:32:22 +08:00
mckero 61344e486e Minesweeper: verify its logic on the host, and document it in both languages
apps/minesweeper/host_test.c includes the app source with a fake app_api_t, so the real state machine runs on a PC: every string it draws is recorded and the lit pixels are counted. Measured -- a reveal/flag/new-game/digit/quit script returns normally, draws the title, the mine count and lights 24 pixels; a script that blindly reveals 85 cells reaches a terminal state eleven times, draws BOOM, and MENU starts a new game after it (terminal at record 356, title again at 4180). The win path is the one branch blind play does not reach.

Both READMEs now say what is verified (compiles clean under gcc -Wall -Wextra -Werror against upstream's real app_api.h, which is what caught the API's true member names and the absence of left/right keys; the logic runs on the host) and what is not (never built for ARM, never run on the radio -- no toolchain and no Docker here).
2026-10-01 22:31:22 +08:00
mckero ff14dffb67 Fix the blank screen, correct the multiboot note, and add Minesweeper
The blank screen was self-inflicted and the earlier explanation was wrong. The FMP3 marker at 0x100000 is an ordinary state record -- generation, image_size, image_crc32, firmware_slot/slot_inv, config_bank/bank_inv and a matching state_crc32 (0x661286F1) -- not a pending flag. What actually happened: a firmware uploaded over a state that recorded a different identity made the firmware take the restore/adopt path, which draws nothing (panel 1024/1024 bytes zero) while the serial banner printed normally. Restoring the untouched dump fixed it at once (485/1024 bytes lit) and the three apps reinstalled and were confirmed by 0x0730.

Clearing the marker sectors was tried twice (a whole 8 KiB, then just the 24-byte headers) and is not a fix: it sends the firmware down MB_MARK_MISSING = fresh radio, which adopts the running firmware slowly and without drawing, and its own write-back restores the marker anyway. AGENTS.md and AGENTS.zh-CN.md now say so in place of the wrong claim.

apps/minesweeper/ adds our own 9x9 minesweeper for the 4 KiB overlay: no left/right keys exist on this radio (so the cursor walks with UP/DOWN and digits pick a row then a column), 81 cells need three 9-byte bit arrays rather than a uint16_t mask (the uint16_t version compiled fine and was wrong past cell 15), mines are placed after the first reveal so it cannot lose immediately, and the source compiles clean under gcc -Wall -Wextra -Werror against upstream's real app_api.h. It has not been built for ARM or run -- no toolchain here -- and the README says so.
2026-10-01 22:29:36 +08:00
mckero 2b3222155f Record the restore loop that ate the installs
A real image whose 0x100000 marker held FMP3 next to a committed slot 0 made the factory bootloader reflash the internal flash from that slot on every power-on: the serial banner reappeared once a cycle (7 -> 8 in 25 s) while the screen never changed. Clearing the two marker sectors (0x100000..0x101FFF, stopping just before the app region at 0x102000) ended it, and the radio booted once and stayed.

It also explained why page-side installs vanished: the emulator writes its in-memory image back on exit, and the looping guest's copy was older than the file, so powering it off overwrote the installs. With the loop gone the same installs survive a power cycle -- installed, powered off, still listed, powered on, still listed, and 0x0730 answered with all three. Both notes are in AGENTS.md and AGENTS.zh-CN.md; work/app-template/ got a starter app, its README and the upstream api/ld it needs (git-ignored).
2026-10-01 22:13:00 +08:00
mckero dcf9dc00f5 Keep what the page installed when the server restarts
The page edits a working copy of the flash image on purpose -- the file it was first pointed at may be a real calibration dump -- but a restart pointed at a different image switched to that file's copy instead, and the games installed through the page looked like they had vanished (measured: the app table came back holding an older slot). work/run-webui.ps1 now prefers the working copy the page has been editing, ahead of the original, and the page's own hint says which image it is editing and that the loaded one is never modified. A test asserts the order in the script.
2026-10-01 22:03:37 +08:00
mckero 673c181d59 Fold the slot and app tables away instead of one long page
Sixteen app rows (most of them empty or holding something that is not an app) plus five slot rows made the column long enough to be annoying, so each table is now a details pane: the firmware slots start open, the app list starts folded, and the state that used to sit in its own row moved into the summary line. Same ids, so loadSlots and loadApps are untouched. A front-end test asserts both panes exist and that the app list starts folded.
2026-10-01 22:00:54 +08:00
mckero c305ee5ebd Document how an app is launched, in both languages
F then 7 opens the F4HWN APPS menu and MENU runs the selection -- upstream's own wording in UVStudio's locales/en.js, and what App/apps/app_menu.c does: KEY_MENU on an installed row calls APP_LaunchOverlay, which runs until the app exits; EXIT leaves the menu. The page's hint now says it too.

Recorded with it: twenty keys short and long, the whole 79-entry settings menu and the multiboot menu all left the app region untouched (the multiboot menu reads firmware slots, not apps), and the way in turned out to be written in the flasher's translation file rather than the firmware source. When a feature's entry point is missing, the host tool that installs it is the document.
2026-10-01 17:32:19 +08:00
mckero dbc8fb7661 Say how to launch an app, and record how the way in was found
F then 7 opens the F4HWN APPS menu and MENU runs the selected app, which is upstream's own wording in UVStudio's locales/en.js and what App/apps/app_menu.c does (KEY_MENU on an installed row calls APP_LaunchOverlay, which runs until the app exits; EXIT leaves). Verified in the emulator with Tetris.app installed through the page's endpoint: F,7 reads the app region eight times and shows the boxed menu; MENU reads once inside slot 0's code and the game's screen replaces the radio's; DOWN moves the piece and the game is still there two seconds later. The page's own hint now says so.

Worth recording: twenty keys short and long, the whole 79-entry settings menu and the multiboot menu all left the app region untouched -- the multiboot menu reads firmware slots, not apps. The entry point was written down in the flasher's translation file, not in the firmware source: when a feature's way in is missing, the host tool that installs it is the document.
2026-10-01 17:30:35 +08:00
mckero b855b0b047 Let the page ask the radio whether it sees an installed app
GET /api/apps/radio opens the firmware's serial port and sends 0x0730 for all sixteen slots, so the answer comes from the running firmware rather than from our reading of the file -- which is the check that matters, because the bytes can be right and the firmware still refuse a slot. Measured through the page after installing Beam.app into slot 0: slot 0 -> Beam 1.0, 1100 B, crc 0xd976058, shortcut beam, committed; slots 1..3 -> status 2 with unrelated data, the resource-block overlap the install guard refuses. A button beside the table asks it and shows the answer in its own column.

The server gives its own emulator a serial port (--serial-port, default 4445) and uvk5_slots_serial.Radio gained a public app_info(slot), so nothing reaches into a private helper. uvk5_apps.parse_radio_reply decodes the answer and is tested without a radio. Also recorded: QEMU needs the mingw64 DLLs on PATH, and started by hand without them it exits before opening QMP, which surfaces only as 'QMP socket never appeared'.
2026-10-01 17:25:32 +08:00
mckero 0267371e28 The firmware confirms the app slots, and the page numbers them like upstream
UVStudio's own js/flash.js names the protocol: MSG_APP_INFO 0x0730/0x0731, MSG_APP_ERASE 0x0732/0x0733, MSG_APP_WRITE 0x0734/0x0735, MSG_APP_VALIDATE 0x0736/0x0737, with APP_SLOT_COUNT 16, APP_IMG_OFFSET 0x1000, APP_HDR_SIZE 64 and APP_MAGIC 0x31504146 -- the constants this page already used. It uses slots 0..7 and labels them 1..8, and writes the header last so a partial write cannot validate; the page now labels slots the same way.

The Labs build answers 0x0730 over USART, so an installed Beam.app was queried on the radio: slot 0 came back status 0 with the header this page wrote (FAP1, code_size 1100, CRC 0x0d976058, flags 0x0801, name Beam), which confirms the region, the offset and the layout from the firmware's side rather than from a header I read. Slots 1 and 2 answered status 2 with unrelated data -- the overlap the install guard refuses to overwrite.
2026-10-01 17:17:22 +08:00
mckero 617c1e2dbd Keep the two AGENTS files in step about the app panel
The English line about the page's app table went in with the previous commit; the Chinese one did not, because the sentence it anchors to wraps mid-phrase and the marker I used did not match. Added, so the pair says the same thing again.
2026-10-01 17:12:00 +08:00
mckero f6b375f66b Put the overlay apps on the page: a slot table with install and erase
The endpoints existed; now the page shows them. An Overlay apps block beside the firmware slots lists all 16 app slots with their name, version, shortcut and size, a .app file picker and an Erase button per row, and says where the region is. It reads GET /api/apps and posts to POST /api/apps/<n> and /api/apps/<n>/erase, so installing a game is a file pick where the firmware slots already are -- no WebSerial, no browser permission.

An upload into a slot that holds something which is not an app is refused by the server; the page asks once and retries with ?force=1 rather than either failing quietly or destroying the factory resource data that overlaps this region on a localised image. Front-end tests assert the table and the endpoint are in the served page and that it never asks for a serial port or audio, and TestPageScriptParses keeps checking that the page's script parses.
2026-10-01 17:11:23 +08:00
mckero d76d95851f Apps on the page: list, install and erase overlay apps in the flash image
The Labs edition's apps live in the external flash, in the region its own App/apps/app_overlay.h defines (16 slots of 8 KiB from 0x102000, a 64-byte FAP1 header at the slot base, code one 4 KiB sector later), and upstream installs them from UVStudio over WebSerial. The page owns the image, so this adds GET /api/apps, POST /api/apps/<n> and POST /api/apps/<n>/erase, which put the same bytes at the same offsets with no serial protocol and no browser permission.

Measured on a real image while wiring it up: every one of the 16 slots already held data that is neither empty nor an app -- the localised build's factory resource block overlaps 0x102000 -- so install now refuses to overwrite anything that is not an app unless asked (--force, ?force=1), naming what is there. And _edit_flash edits a copy, which is why the source image shows no changed bytes; the first test read that as 'the install did nothing' and now checks FlashSlot.path. Tests: test_uvk5_apps grew to 20, test_webui.TestAppEndpoints adds 7 over the endpoints.
2026-10-01 17:09:00 +08:00
mckero c64b5e6ad8 Remove a probe log that a wrong --flash-probe path left behind
UVK5_FLASH_PROBE is a file path, not a flag: passing 1 wrote the probe output to a file called 1 in the repository root, and the previous commit's git add -A picked it up. Deleted and untracked; the real probe logs live under work/, which is ignored.
2026-10-01 16:57:19 +08:00
mckero f7cd4816e3 Overlay apps: the region, the format, and a tool that writes them
The Labs edition runs overlay apps (Tetris, Breakout, Plasma, Cube3D, Beam, Beacon, FoxHunt, BroadcastFM) that upstream UVStudio installs over WebSerial. This page owns the flash image, so the same bytes go to the same offsets with no serial protocol and no browser permission: APP_REGION_BASE 0x102000, APP_SLOT_STRIDE 0x2000, APP_CODE_OFFSET 0x1000, 16 slots, taken from the firmware's own App/apps/app_overlay.h rather than inferred.

tools/uvk5_apps.py parses and validates the 64-byte FAP1 header (zlib CRC-32 over the code, vma 0x20000280, name, version, capabilities), lists, installs and erases slots, and refuses what the firmware would show as APP ERROR. test_uvk5_apps covers those refusals plus install/erase/list round trips, and parses a real upstream Beam.app when one has been downloaded. The header struct was 60 bytes at first -- a missing vma field -- which the real file's bytes showed at once.
2026-10-01 16:56:59 +08:00
mckero fc432d2055 Report what the device says it is running, not the file we handed it
The page only knew its own input. A build called f4hwn.fusion.bin reports EGZUMER+F4HWN v6.0.0.CN, and with the multi-system release a committed external slot makes the factory bootloader reflash the internal flash from that slot on every power-on -- so the uploaded image never runs and the page keeps naming it. The firmware prints its own banner on USART1; tools/uvk5_banner.py reads it back, /api/firmware returns running: {banner, matches_uploaded, note}, and the page shows what the device reports, flagging it only when the running version is not in the uploaded image at all.

That reader also exposed a regression of my own: _start_stderr_pump had been rewritten to read the pipe in 64 KB chunks, which kept QEMU from blocking but delivered nothing to the log until 64 KB had accumulated -- and the banner is forty bytes, so it never appeared. It reads lines again, still starting before anything waits on QEMU, and test_uvk5_supervisor passes either way.
2026-10-01 16:22:44 +08:00
mckero 11e3678140 Take the stale addresses out of the AGENTS examples too
The screenshot example pasted one build's numbers and told the reader to get them with nm -- which cannot work here, because the images are program-header-only ELFs with no symbol table. It now asks the firmware through tools/uvk5_buffers.py first. The webui example drops the flags entirely, since the page draws the controller's memory and needs none.

Also fixed the paragraph in both READMEs that had a tool name and a flag on one line, which the doc checker (correctly) read as passing that flag to that tool.
2026-10-01 16:14:01 +08:00
mckero a237bd92c6 Stop teaching the old address flags in the docs
The flags are optional now and the page needs no addresses at all, so the examples no longer paste one build's numbers: screenshot.py is shown asking the firmware through tools/uvk5_buffers.py, and the webui example omits them entirely. The paragraph that said they default to one known build is replaced with what actually happens.
2026-10-01 16:12:36 +08:00
mckero 1e9fdf685c Find the screen buffers in the firmware instead of hardcoding one build's
The page was told --frame-addr 0x200012BE --status-addr 0x2000163E and used them as a fallback. The firmware the user actually flashed keeps its buffers at 0x2000129E/0x2000161E, 32 bytes earlier, so every line landed 32 bytes off: that is the "other firmware looks shifted" report. The images here are minimal ELFs with no symbol table, so there is nothing to read -- but the firmware's own buffers hold the same bytes the controller holds, and tools/uvk5_buffers.py finds them by matching (1024/1024 bytes for that file).

The two address flags are optional now, work/run-webui.ps1 passes no machine-specific values at all, and the page reports what it found in /api/status and /api/panel. tools/uvk5_testenv.qemu() also looks in the sibling qemu-7.2/build the rest of the repo assumes. Fixed /api/panel's emulator-off branch, which called jsonify with both a dict and kwargs and 500'd.

Tests: test_uvk5_buffers (the search must count matches, not pairs -- its first version scored every offset full marks and always answered the first one).
2026-10-01 16:09:26 +08:00
mckero 57106c0a66 Fix the two pixel bugs behind "the other firmware looks shifted"
uvk5_stream.py used STATUS_BYTES without importing it, so the panel branch raised NameError on every frame and a bare except swallowed it: every screen the page drew came from guest RAM at one build's addresses. The pump now reports which source it used and why, webui exposes it (/api/panel and frame_source), and test_uvk5_stream asserts the panel wins when reachable and that a fallback is announced.

The ST7565 column counter wrapped at 128 instead of the controller's 132, so addresses 128..131 came back as 0..3, fell outside the col>=4 store, and were dropped: every row lost its last four pixels, which is where the battery icon lives. Pre-fix, filling a page with 0xFF left columns 124..127 blank; now they carry content and the page's frame matches the panel memory 8192/8192.
2026-10-01 15:59:39 +08:00
mckero f4c9d343fc Probe what a BK4819 read presents, and say plainly that it is not the guard yet
UVK5_BK4819_PROBE reassembles the sixteen bits a read clocks out and logs them against the register's own value: 1566 of 1566 agree on the working model. That agreement is not proof -- removing the skip_falling fix, which is exactly the historical left-shift regression, leaves the reassembled word unchanged, so this observation point is not the one the guest samples at. tools/test_bk4819_readback.sh therefore stays the guard.

tools/test_bk4819_readback.py is the working draft of a portable replacement (no source patch, no rebuild, no ARM gdb) and is deliberately NOT registered in run_tests.sh, so a proven guard is not swapped for an unproven one. Both the file and the two READMEs say so.
2026-10-01 15:28:47 +08:00
mckero e1ffdf8fdd panel_dump: say why QMP is unreachable, and exit 2 for it
QMP takes a single client and the web UI holds it for its whole lifetime, so this is the normal outcome while the page is open rather than a broken emulator. Verified against a private instance: 128x64, 1819 pixels lit, 64 rows of ASCII.
2026-10-01 15:23:27 +08:00
mckero 4bddccfe7b Measure how a build renders, and keep the tool that does it
tools/panel_dump.py prints the display controller's own memory as ASCII or PNG for any firmware, with the four mappings, so two builds can be compared instead of glanced at. The panel model gained a bounded UVK5_PANEL_PROBE diagnostic along the way.

Measured: the 5.9.0.CN panel agrees with its own framebuffer 8188 of 8192 pixels with the data untouched, and the fetched 6.0.0 build renders identically. Both program the same geometry registers, which is why the mapping is a driver convention and cannot be derived from the controller: it has to be measured.

Noted, not yet fixed: the display start line (0x40|n) is ignored, and the model stores pixels at col-4 with the column counter wrapping at 128 instead of the controller's 132 columns.
2026-10-01 15:22:02 +08:00
mckero 542515d5ad Finish the portability sweep: one firmware resolver, no author paths
The probe scripts, run.sh, trace_run.sh, webui.py and two emulator tests each named the same hardcoded firmware from a source tree that is not in this repository. They now resolve QEMU and the firmware the way tools/uvk5_testenv.py does -- environment, then PATH, then whatever the checkout has -- and skip with a reason when there is nothing.

webui.py's --qemu and --elf lost their author defaults too: a bare qemu-system-arm through PATH, and no firmware until one is uploaded, which the page already reports.
2026-10-01 15:15:31 +08:00
mckero ae8b48c85a Run anywhere: no author paths left, and CI that proves it
tools/run_tests.sh defaults QEMU_SRC to a sibling of the checkout, which is where setup_qemu.sh puts it; the two defaults disagreed, so a fresh clone rebuilt nothing and reported a build that was not there. The interpreter list was also reading an empty $PY.

tools/test_bk4819_readback.sh was the last test with the author's paths, and the only one that could not run elsewhere. It now takes QEMU, GDB and ELF from the environment or PATH like the python tests, skips with a reason when one is missing, and says so on a platform whose QEMU cannot make the unix socket it uses.

tools/check_docs.py points UVK5_FW_DIR at a sibling and skips the file:line checks, with a message, when there is no firmware tree -- a fresh clone used to see thirteen failures it could do nothing about.

Added .github/workflows/unit.yml (the fast half of run_tests.sh on every push and PR), requirements-dev.txt for the one pip dependency, and a Dockerfile. Flask is not always installed, so test_webui now skips through setUpModule rather than erroring.
2026-10-01 15:12:45 +08:00
mckero 1308c98769 Document where Moto/DFU entry is, and let a boot key hold PTT plus a key
The bootloader's DFU handler is reachable only when SRAM[0x20000020] is 3, which only a program that then resets can write. The application's 0x05DD takes that path only with ENABLE_OVERLAY; this build resets straight back into the application instead. Ruled out by measurement: PTT alone, PTT+SIDE1/SIDE2, MENU, a host byte in the boot window including 0x0530, and 0x05DD.

boot-key now reads a + separated list, because the firmware's own BOOT_GetMode() needs PTT and a matrix key together for every special boot mode. Tested against all four documented combinations.
2026-10-01 15:06:20 +08:00
mckero 2667e046e8 Emulator: multiboot slots from the page, flash controller, portable tests
flash controller: store ACR/OPTKEYR instead of swallowing them, which is what stopped the factory bootloader from starting

slots over the firmware's own serial protocol (0x0720 family); uvk5_socket/uvk5_testenv so a fresh checkout skips instead of failing; web UI slot table and Multiboot button; quick start, CONTRIBUTING, and stop tracking firmware images and radio dumps
2026-10-01 14:54:34 +08:00