From 9a12b779bb7a5ee0f8d135183ac313e2816c4b08 Mon Sep 17 00:00:00 2001 From: Cyd Date: Sat, 18 Jul 2026 00:45:40 -0500 Subject: [PATCH] Firmware 31250 v2: widen the byte-scaled reply ACK-wait ($D9E7: 04->28) Disassembly confirmed the bench theory: the no-ACK wait loop at $D9E0 self-clocks in byte times (each tick transmits an IDLE keep-alive and re-enters on its TX-complete interrupt), so raising the baud silently shrank the ACK grace from ~5.2ms to ~1.6ms - under USB turnaround, hence the v1 retry storm. --widen-ackwait (requires --baud31250) sets the limit to 40 ticks (~12.8ms at 31250). v2 image: 25 bytes changed, sha256 420d4cfc...; re-disasm diff vs v1 is exactly the CMPA operand. v1/classic hashes reproduce unchanged. Co-Authored-By: Claude Fable 5 --- rio-firmware/RIOv4_2-ANALYSIS.md | 18 + rio-firmware/RIOv4_2_patched_31250v2.bin | 38 + .../RIOv4_2_patched_31250v2.disasm.asm | 13133 ++++++++++++++++ rio-firmware/make_patch.py | 16 + 4 files changed, 13205 insertions(+) create mode 100644 rio-firmware/RIOv4_2_patched_31250v2.bin create mode 100644 rio-firmware/RIOv4_2_patched_31250v2.disasm.asm diff --git a/rio-firmware/RIOv4_2-ANALYSIS.md b/rio-firmware/RIOv4_2-ANALYSIS.md index e7791b6..32707a4 100644 --- a/rio-firmware/RIOv4_2-ANALYSIS.md +++ b/rio-firmware/RIOv4_2-ANALYSIS.md @@ -264,3 +264,21 @@ Two conclusions: reply rate overstates throughput — unique samples are still capped by the host's 55ms poll; harvesting the speed needs a faster poll AFTER the retry window is fixed. + +### 31250 v2 (2026-07-18) — `RIOv4_2_patched_31250v2.bin` — widened ACK-wait + +Root cause of the v1 retry storm CONFIRMED in the disassembly: the reply +ACK-wait loop at `$D9E0` self-clocks in **byte times** — each tick sends an +IDLE (`$FF`) keep-alive and re-enters on that byte's TX-complete interrupt, +so the `CMPA #$04` limit means ~5 byte times of grace: ~5.2ms at 9600 +(USB ACK wins; zero framing in both 9600 runs) vs ~1.6ms at 31250 (USB +loses; framing 5655 / Abandon 65 on the v1 bench run). Those keep-alive/ +RESTART bytes landing mid-packet are exactly the host-side framing resyncs. + +`make_patch.py --baud31250 --widen-ackwait` → one more byte, +`$D9E7: $04 → $28` (40 ticks ≈ 12.8ms at 31250, clears FTDI worst case +with margin). The NAK-retry limit ($3175) is event-counted, untouched. +sha256 `420d4cfc6b513651687982a70db2daeecda7bd00a5324321e272f35b95dca753`, +25 bytes vs original; re-disassembly diff vs the v1 31250 image is exactly +the one CMPA operand line. Expected on the bench: framing 5655 → ~0, +Abandon 65 → ~0, wedges 0 → 0. diff --git a/rio-firmware/RIOv4_2_patched_31250v2.bin b/rio-firmware/RIOv4_2_patched_31250v2.bin new file mode 100644 index 0000000..a36bda3 --- /dev/null +++ b/rio-firmware/RIOv4_2_patched_31250v2.bin @@ -0,0 +1,38 @@ +$"f qFLiÐÙƽôýâë,ʽ5ӽ$$ J K M 4 7  N$ɽ|$!$B$C$G$E$I$F$J$$K$$H$1111% %!%"Ty1y~y˽y~<<67̽4hɜ$F$E32889}$I'|$J$J&$!'м$J$I9̭#""9    =  =   @#""9 #""9^ q9oZ&J&9í$E#; @&    @ @#; $I#;%;<<76 %;% +&~8 & +&G ~8& +&6 &0~$') +&  &&~8$!˧A%523889~;&F(;'~O& ̽;'~^& ;'u~o& 4;'d~€ & h;'S~‘& ɜ;'B~¢&%ұB;'\~¹&ҽJ;'H~Ё&*p;'4~˧; '~A|,$!9&ν~;'~~%~ÐÙi M9 K~S J91Ԣ%9 P۰d< 1 18Z&9" + 32< 1 18Z&z 3&9`!b>9H#ν>9H!½>9$>9H$T>9H$>9$Ļ3U9%08 (9 >9 >9oZ&9   Π)Π!Π#Π%Π' 9 Π) 9 Π! 9 Π# 9 Π% 9 Π' 9 + '>9$"91D1X 9i&$"1''X' +''9~~ƿâë$$!J1Ѱ$ɽ1S~bNJǵ$!99˽ 9~ƾi&1'~ƏLiÐÙƽôýâë,$$ J K M 4 7  N$ɽ|$!$B$C$G$E$I$F$J$$K$$H$1111% %!%"~ƾ&z~ƾ&Ċ~ƾ&Ě~ƾ&Ī~ƾ&ĺ9i&1$Gi&1$K$GH$?967<<-A-B;8832967<<-A-B$!;8832967<<-A-B%%;8832967<<-A-B%%;8832967<<-A-B1;8832967<<-A-B1;8832967<<-A-B1;8832967<<$!'0 '% +%-B -A<%%8z%&;88329$!'-A$ -B 7;9$!'#%-A$ -B% J;9$!'#%-A$ -B% K;99 LΠ( Π8 !ʗ #ʰ &   + 9 LΠ Π0 !ʗ #ʰ &    9 LΠ" Π2 !ʗ  #ʰ &     9 LΠ$ Π4 !ʗ  #ʰ &     9 LΠ& Π6 !ʗ  #ʰ &     9 ' L&#  H$ H~ʖ H~ʖ&#  H$ H~ʖ H~ʖ&#  H$ H~ʖ H~ʖ &#  H$ H~ʖ H~ʖ&  H$ H ~ʖ H 9  & ! %9< 89O_ % & #'|   ҽ9<6$!& 8  H  H 6I289<6$!& 8 % H & H 6I289 HDDDD9O L H 9 8962962962962962967<<  N P !̲8832967<<  N : P2: :!̲88329  4 4&~&O~&̀~ͱ "Z& 5 +< 1 18Z&z 5&z 4&9  + + 9 + +  9 + +   9 + + 9  6 5 NZ'D~&r~| z 5&z 6&967<< !:&7< 1 1 : N& J8~d K8$ Ȝ~l$ o8832967<< !:'@< 1 1 : N& J8~κ K8$ Ȝ~Ȇ$ J%o88329<6$!& 8 K H O L6I289 M& +$ ~$ # M& +$ +~0$ +967<< 7$B N$!P϶| NV!P϶| N܈!P϶ܺ!P϶| N!P϶!P϶P!P϶| N| N| N݂!P϶| Nݴ!P϶883299D967<<!R!PZ& 6 +< 1 18Z&z 6&8832967< 0 else "RIOv4_2.bin" DST = args[1] if len(args) > 1 else ( + "RIOv4_2_patched_31250v2.bin" if WIDEN_ACKWAIT else "RIOv4_2_patched_31250.bin" if BAUD31250 else "RIOv4_2_patched.bin") d = bytearray(open(SRC, "rb").read()) @@ -72,6 +76,18 @@ if BAUD31250: "expected LDAA #imm ; STAA $102B at $D62A" patch(0xD62B, [0x30], [0x02]) +# --- edit 4 (--widen-ackwait, 31250 only): reply ACK-wait 4 -> 40 ticks ----- +# The no-ACK wait loop at $D9E0 self-clocks in BYTE TIMES: each tick transmits +# an IDLE ($FF) keep-alive and re-enters on that byte's TX-complete interrupt. +# 4 ticks = ~5.2ms at 9600 (USB ACK wins) but ~1.6ms at 31250 (USB loses -> +# retry storm; bench 2026-07-18: framing 5655, Abandon 65). $28 = 40 ticks +# = ~12.8ms at 31250, clearing FTDI worst-case turnaround with margin. +# $D9E6 81 04 CMPA #$04 -> 81 28 CMPA #$28 +if WIDEN_ACKWAIT: + assert d[0xD9E6] == 0x81 and bytes(d[0xD9E3:0xD9E6]) == bytes([0xB6,0x31,0x7B]), \ + "expected LDAA $317B ; CMPA #imm at $D9E3" + patch(0xD9E7, [0x04], [0x28]) + open(DST, "wb").write(d) new_sha = hashlib.sha256(d).hexdigest() # byte-diff report