mirror of
https://github.com/MCKero6423/uv-k5-v3-emulator.git
synced 2026-10-02 11:07:31 +00:00
Wrap page-program writes within their 256-byte page
Real SPI NOR latches only the low address bits into its page buffer, so a program burst that runs past the page boundary continues at the start of the same page. The model incremented the address straight through instead. Consequence: the firmware issues a 512-byte burst at 0x008F00 inside a single CS assertion (measured -- the CS never drops mid-burst), which spilled into 0x009000. That is the per-band VFO frequency area in eeprom_compat.c's map, so stored frequencies were zeroed. RADIO_ConfigureChannel only substitutes the band's lower limit when it reads 0xFFFFFFFF, so a stored 0 was taken literally and clamped to BX4819_band1_lower -- which is why every typed frequency reverted to 18.000 MHz. Verified: writes now align to sectors (0x8000-0x9000 and 0xA000-0xB000) and an instrumented build records zero stores into 0x9000-0x90D6, where before it was overwritten on every boot. test_flash_persist.py had encoded the bug in its expectations: it watched 0x008100 and 0x00A100, which were only ever written *because* of the missing wrap. Those move to 0x008000/0x00A000, and a MUST_NOT_CHANGE guard on the frequency area now fails if a write spills there again. keypad_test.py still passes.
This commit is contained in:
1 parent
0b879227e5
commit
da1ad7e1e6
2 files changed
+46
-4
No files matched your search
+22
-1
@@ -986,6 +986,9 @@ OBJECT_DECLARE_SIMPLE_TYPE(PY25Q16State, PY25Q16)
|
|||||||
|
|
||||||
#define PY25Q16_SIZE (2 * MiB)
|
#define PY25Q16_SIZE (2 * MiB)
|
||||||
|
|
||||||
|
/* Page-program buffer size. Programming wraps within a page; see PY25Q16_CMD_PP. */
|
||||||
|
#define PY25Q16_PAGE_SIZE 0x100
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
PY25Q16_CMD_NONE = 0,
|
PY25Q16_CMD_NONE = 0,
|
||||||
PY25Q16_CMD_READ = 0x03,
|
PY25Q16_CMD_READ = 0x03,
|
||||||
@@ -1067,7 +1070,25 @@ static uint8_t py25q16_xfer(void *opaque, uint8_t out)
|
|||||||
s->data[s->addr % PY25Q16_SIZE] &= out;
|
s->data[s->addr % PY25Q16_SIZE] &= out;
|
||||||
s->dirty = true;
|
s->dirty = true;
|
||||||
}
|
}
|
||||||
s->addr++;
|
/*
|
||||||
|
* Page program wraps within its 256-byte page: a burst that runs past the
|
||||||
|
* page boundary continues at the start of the same page rather than
|
||||||
|
* spilling into the next one. Real SPI NOR works this way because the
|
||||||
|
* chip latches only the low address bits into its page buffer.
|
||||||
|
*
|
||||||
|
* Without this the model let one transaction walk straight through, and a
|
||||||
|
* 512-byte burst at 0x008F00 overwrote 0x009000 -- which is the VFO
|
||||||
|
* frequency area in eeprom_compat.c's map. The stored frequency became
|
||||||
|
* zero, RADIO_ConfigureChannel only substitutes the band's lower limit for
|
||||||
|
* 0xFFFFFFFF, so the frequency was taken as 0 and clamped to
|
||||||
|
* BX4819_band1_lower. That is why a typed frequency always reverted to
|
||||||
|
* 18 MHz.
|
||||||
|
*
|
||||||
|
* Measured: the firmware really does send 512 bytes inside a single CS
|
||||||
|
* assertion here, so the wrap has to be modelled rather than assumed away.
|
||||||
|
*/
|
||||||
|
s->addr = (s->addr & ~(PY25Q16_PAGE_SIZE - 1))
|
||||||
|
| ((s->addr + 1) & (PY25Q16_PAGE_SIZE - 1));
|
||||||
return 0xff;
|
return 0xff;
|
||||||
|
|
||||||
case PY25Q16_CMD_SE:
|
case PY25Q16_CMD_SE:
|
||||||
|
|||||||
@@ -43,11 +43,20 @@ QMP = "/tmp/uvk5-persist-test.sock"
|
|||||||
# guessed. The mapping is in App/driver/eeprom_compat.c: these are *flash* addresses,
|
# guessed. The mapping is in App/driver/eeprom_compat.c: these are *flash* addresses,
|
||||||
# not the EEPROM addresses the settings code uses, and an earlier version of this test
|
# not the EEPROM addresses the settings code uses, and an earlier version of this test
|
||||||
# watched EEPROM offsets by mistake and reported "same" for everything.
|
# watched EEPROM offsets by mistake and reported "same" for everything.
|
||||||
|
#
|
||||||
|
# These were 0x008100 and 0x00A100 while page-program wrapping was missing from the
|
||||||
|
# model: writes ran past the page boundary and landed a page high. With wrapping in
|
||||||
|
# place the firmware's writes align to the sector, as they do on real hardware.
|
||||||
WATCH = [
|
WATCH = [
|
||||||
("mr/vfo attrs", 0x008100, 0x20), # 1024 MR + 7 VFO attributes, 2 bytes each
|
("mr/vfo attrs", 0x008000, 0x20), # 1024 MR + 7 VFO attributes, 2 bytes each
|
||||||
("settings", 0x00A100, 0x20), # the settings block
|
("settings", 0x00A000, 0x20), # the settings block
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Must stay untouched: this is where per-band VFO frequencies live. The missing page
|
||||||
|
# wrap let a 512-byte burst at 0x008F00 spill into it, zeroing stored frequencies so
|
||||||
|
# every typed frequency reverted to BX4819_band1_lower (18 MHz).
|
||||||
|
MUST_NOT_CHANGE = [("vfo frequencies", 0x009000, 0xD6)]
|
||||||
|
|
||||||
|
|
||||||
class Qmp:
|
class Qmp:
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
@@ -128,7 +137,8 @@ def sha(path):
|
|||||||
|
|
||||||
def snapshot(path):
|
def snapshot(path):
|
||||||
data = open(path, "rb").read()
|
data = open(path, "rb").read()
|
||||||
return {name: data[off:off + length] for name, off, length in WATCH}
|
regions = WATCH + MUST_NOT_CHANGE
|
||||||
|
return {name: data[off:off + length] for name, off, length in regions}
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -182,6 +192,17 @@ def main():
|
|||||||
else:
|
else:
|
||||||
print("PASS the settings and channel regions were written")
|
print("PASS the settings and channel regions were written")
|
||||||
|
|
||||||
|
# The frequency area must be left alone. A page-program burst that fails to
|
||||||
|
# wrap spills into it and zeroes stored frequencies, which is what made a
|
||||||
|
# typed frequency always revert to 18 MHz.
|
||||||
|
spilled = [n for n, _, _ in MUST_NOT_CHANGE if after[n] != before[n]]
|
||||||
|
if spilled:
|
||||||
|
failures.append(
|
||||||
|
f"{', '.join(spilled)} was overwritten -- a write spilled past a "
|
||||||
|
"page boundary into the VFO frequency area")
|
||||||
|
else:
|
||||||
|
print("PASS the VFO frequency area was not overwritten")
|
||||||
|
|
||||||
# A second boot must see what the first one left behind.
|
# A second boot must see what the first one left behind.
|
||||||
print("\nbooting again from the same file")
|
print("\nbooting again from the same file")
|
||||||
proc, qmp = boot(image)
|
proc, qmp = boot(image)
|
||||||
|
|||||||
Reference in new issue
Block a user