Skip to content

v0.24 R device fixes

Latest

Choose a tag to compare

@brad-colbert brad-colbert released this 15 Aug 04:35
· 2 commits to main since this release
34eaf51

VBXETERM v0.24 — R: is usable again

v0.23 shipped ATASCII mode and, without anyone noticing, broke serial. R: sessions would freeze within a minute or so of typing, and sometimes take the machine down outright. This release fixes that, plus a second R: bug found while chasing it. If you are running v0.23 over R:, upgrade. Nothing else changes — no new features, no behaviour differences.

One register, not saved

kbd_irq — the terminal's own keyboard interrupt handler — saved A and X on entry but never Y.

That was fine for years, because nothing on the ordinary keypress path touched Y. The one routine that did, the escape-sequence builder, wrapped its use in tya/phapla/tay, with a comment spelling out exactly why: preserve Y (IRQ caller doesn't save it for us). The rule was known and it was being followed.

Then v0.23's ATASCII support added ldy atascii_mode to the common single-byte path, outside that guard. From that point on, virtually every keystroke returned from the interrupt with Y quietly changed. Worth stressing: this was not limited to ATASCII mode. The ldy runs whatever mode you are in, so ANSI sessions were hit just as hard.

Y is now pushed alongside X on entry and pulled back on the way out.

Why a lost Y destroys R: specifically

Because of where the CPU almost always is when you press a key.

The R: receive path asks CIO for a status poll on IOCB 1 on every pass of the main loop, to find out how many bytes are waiting. That means the machine spends most of its life inside one small OS routine: the loop that copies a 12-byte IOCB into CIO's zero-page work area. And that loop indexes its source by X and its destination by Y:

E4F3: LDA ICHID,X
E4F6: STA ICHIDZ,Y
E4F9: INX
E4FA: INY
E4FB: CPY #$0C
E4FD: BCC $E4F3

Press a key part-way through and it resumes with X where it left off but Y reset — the two indices no longer step together. The rest of the copy then pulls a mis-aligned slice of memory, straddling the tail of IOCB 1 and the head of IOCB 2, into CIO's work area.

From there it unravels quickly. The handler index and command byte are now garbage, so CIO rejects the call, and its exit path writes that same garbage work area straight back out over IOCB 1 — the destination IOCB was still recorded correctly, which is precisely what makes the corruption land somewhere fatal. The next R: read or write then looks up its device handler through a meaningless index, lands on a table of zeroes, and CIO's dispatch — which works by pushing a vector and returning to it — returns into zero page. Whether that hangs or hits an illegal opcode is pure luck of where it lands, which is why the failure looked like two different bugs.

N: was never affected. It drives SIO directly and never goes near that copy loop.

Selecting a font opened R: twice

A separate bug, found on the way.

v0.23 reorganised the font-loading code, moving the R: reopen into the shared load routine — but the caller kept its own trailing reopen as well. So choosing a font from the settings menu performed one CLOSE and two OPENs.

The second one did not fail harmlessly. The OS skips its device-table lookup when an IOCB is still open, so instead of returning "already open" it went ahead and re-entered the 850 handler's OPEN routine on a live port, resetting per-port state underneath a running connection, then re-ran the whole baud/translation/DTR/concurrent sequence.

The close/reopen bracketing now belongs to the load routine alone, and the redundant helper is gone.

Also in this release

  • A suspected third bug turned out not to be one, and that is now written down. configure_r_device never sets the buffer length before starting concurrent I/O, which looks like it should hand the 850 handler a stale length and a wrong buffer. It doesn't: the handler picks its input buffer based on AUX1, not the length field, and this code always passes AUX1 = 0 — so the handler's own 32-byte buffer is always selected and the length is never read. The comment now records that, so the assumption doesn't get re-derived incorrectly later.
  • Documented that starting concurrent I/O is not idempotent. Issued while concurrent mode is already running, it returns an error and re-applies nothing. The existing comment claimed the XIO sequence could revive a live R: session in place; it cannot. It works only because every caller reaches it immediately after an OPEN. Close and reopen instead.

Note

Both fixes are in the serial path, so N: users see no change. make test covers the encoding and scrolling primitives as before; the interrupt and CIO behaviour here isn't reachable from the host-side tests, and was verified instead against a real R: session under emulation — dialling out, connecting, sustained inbound ANSI and typing throughout — with a watchpoint on the IOCB that the old build tripped within seconds.