Start DMA on the peripheral's request, and clock both directions together

Two related faults in the DMA model, both of which corrupted flash reads.

Transfers started when a channel was enabled. On hardware, enabling only arms a
channel; the transfer begins when the peripheral raises its DMA request. The flash
driver's SPI_ReadBuf arms RX, arms TX, then enables SPI and sets TXDMAEN -- so
firing at arm time clocked the bus before the read command had been sent. SPI now
kicks the armed channels from CR1/CR2 when SPE and a DMA request enable are both
set, which covers the read path (SPE last) and the write path alike.

Each channel also ran to completion independently. SPI is duplex: one clocked byte
is simultaneously sent and received, and the driver relies on that, pairing a
memory-to-peripheral channel feeding dummy bytes with a peripheral-to-memory
channel collecting the reply. Running them in sequence meant TX clocked the whole
transfer out before RX looked at the bus, so RX collected nothing. They are now
stepped together, one byte at a time.

Either fault alone made a 4 KB sector read return zeros. PY25Q16_WriteBuffer reads
a sector into SectorCache, patches it, and writes the whole thing back, so a zeroed
read turned into a zeroed sector -- including the per-band VFO frequencies at
0x9000. That is a second, independent cause of typed frequencies reverting to
18 MHz, on top of the missing page wrap fixed in da1ad7e.

Verified: the frequency area stays 0xFF across a boot where it was previously
zeroed every time, and an instrumented build shows the sector read now returning
0xFF rather than 0x00.

test_flash_persist.py now starts from the pristine image rather than
assets/flash.img. A dirty live image left nothing for the boot to write, which
surfaced as "the image is byte-identical" -- a failure that looks like broken
persistence but is really a dirty fixture. Runs twice in a row cleanly now.

keypad_test.py and the 141 unit tests still pass.
This commit is contained in:
mckero committed 2026-08-28 15:05:15 +01:00
1 parent da1ad7e1e6
commit f114666b42
2 files changed
+135 -39

No files matched your search

+13 -1
View File
@@ -149,7 +149,19 @@ def main():
workdir = tempfile.mkdtemp(prefix="uvk5-persist-")
image = os.path.join(workdir, "flash.img")
shutil.copy(SOURCE_IMAGE, image)
# Start from the pristine image, not assets/flash.img. The live image may have
# been written by an earlier session, and then a boot has nothing left to save
# -- which shows up as "the image is byte-identical", a confusing failure that
# looks like persistence is broken when it is the fixture that is dirty.
pristine_gz = os.path.join(os.path.dirname(SOURCE_IMAGE),
"pristine", "flash-pristine.img.gz")
if os.path.exists(pristine_gz):
import gzip
with gzip.open(pristine_gz, "rb") as src, open(image, "wb") as dst:
shutil.copyfileobj(src, dst)
else:
shutil.copy(SOURCE_IMAGE, image)
failures = []
try: