Annotate the USB-MIDI router and the USB-1->CV patch in the disassembly

Add a comment-injection mechanism to roundtrip.py (COMMENTS / PATCH_COMMENTS
address-keyed dicts) so annotations survive regeneration -- they live in the
script, not the generated file.

Annotated in firmware.asm:
  * The router at 0xA57B: register-bank switch, event-buffer save, the
    CV-source-locked early-out, the cable-number switch (cable 0 -> 0x70,
    cable 1 -> 0xc3, cable 2 -> 0x82 = CV ring), the descriptor write via
    0x551e, and the single common routing pass at 0xA5C0.
  * The dispatcher at 0xDF05 / dispatch call at 0xDF28.
  * The usb1_cv_stub at 0x8126 (each instruction) and the retargeted call.

The router's structure clarifies why re-injection is used rather than a
router patch: 0x551e only writes a 3-byte destination descriptor, and the
actual routing pass (0xA5C0+) runs once per invocation, wrapped by a
PSW push/pop -- so two destinations require two full router calls.
This commit is contained in:
2026-08-17 23:09:38 +02:00
parent 5022b59934
commit 3340cafb46
2 changed files with 68 additions and 0 deletions
+43
View File
@@ -51,6 +51,44 @@ ABS_OPS = {"ljmp", "lcall", "ajmp", "acall"}
LINE_RE = re.compile(
r'^\s*[\\|/]?\s*0x([0-9a-fA-F]{8})\s+([0-9a-fA-F]+)\s+(\S.*)$')
# Annotated addresses. Emitted as `;` comment lines before the instruction,
# so the disassembly reads as a narrative. These survive regeneration (they
# live in this script, not in the generated file). Patch-mode comments
# (PATCH_COMMENTS) override COMMENTS at the same address.
COMMENTS = {
0xa57b: "USB-MIDI router (fcn.0000a57b): route the 4-byte event at @r6:r7 to a per-cable destination ring (called from 0xdf28)",
0xa57d: "switch to register bank 1 (PSW.RS0 = 1)",
0xa580: "save event-buffer pointer r6:r7 -> 0x0d3b/0x0d3c",
0xa588: "if CV-source-locked flag (bit 0x22.2) set, early-out (r7 = 1)",
0xa590: "read event[0]; extract cable number (high nibble) -> r7",
0xa59c: "cable 0 (USB-1 / Control Surface) -> dest 0x70",
0xa5a4: "cable 1 -> dest 0xc3",
0xa5b0: "cable 2 (USB-3) -> dest 0x82 (CV ring)",
0xa5ba: "write destination descriptor (r3,r2,r1) -> 0x0d3d/e/f (fcn.0000551e)",
0xa5c0: "common routing pass: copy MIDI bytes from the event buffer to the selected destination ring (runs ONCE per router invocation)",
0xa660: "restore PSW; return (r7 = status)",
0xdf05: "USB-MIDI event dispatcher (fcn.0000df05)",
0xdf28: "dispatch call -> router 0xa57b",
}
# Patch-mode-only annotations (the stub + retargeted call site).
# Each comment describes the instruction AT that address.
PATCH_COMMENTS = {
0x8126: "usb1_cv_stub: mirror cable-0 (USB-1) events into the CV ring (dest 0x82); first run the original router unchanged",
0x8129: "USB-MIDI 4-byte event buffer at 0x0F9B",
0x812c: "load event[0] (cable nibble in top 4 bits)",
0x812d: "isolate cable number (top nibble of event[0])",
0x812f: "not cable 0 -> done (no mirror needed)",
0x8131: "reload event[0]",
0x8132: "retag cable 0 as cable 2 (set bit 0x20)",
0x8134: "store back",
0x8135: "DPTR high = 0x0F (buffer page)",
0x8137: "DPTR low = 0x9B (buffer addr)",
0x8139: "route again -> USB-3 / CV ring (dest 0x82)",
0x813c: "done",
0xdf28: "dispatch call -> usb1_cv_stub (was: router 0xa57b)",
}
def signed(b):
return b - 0x100 if b >= 0x80 else b
@@ -181,8 +219,13 @@ def emit_source(items, labels, pinned, patch=False):
lines.append(".area CODE (ABS)")
lines.append(".org 0x%04x" % BASE)
item_line = {}
cmt = dict(COMMENTS)
if patch:
cmt.update(PATCH_COMMENTS)
for idx, it in enumerate(items):
a = it["addr"]
if a in cmt:
lines.append("; " + cmt[a])
if a in labels:
lines.append("L_%04X:" % a)
if it["is_gap"] or it["force_db"] or a in pinned: