OS Calls — Entry Points & Vectors

The MOS exposes its API through fixed entry points in high ROM (&FF**). Most of them JMP-indirect through a vector in page 2, allowing interception.

RoutineEntryVectorA holdsFunction
OSCLI&FFF7&208 CLIVExecute *-command (X+Y = command address)
OSBYTE&FFF4&20A BYTEVcall #OSBYTE dispatcher
OSWORD&FFF1&20C WORDVcall #OSWORD dispatcher
OSWRCH&FFEE&20E WRCHVcharWrite char to current output stream
OSNEWL&FFE7Write LF+CR (calls OSWRCH twice)
OSASCI&FFE3charWrite char; if &D writes LF+CR
OSRDCH&FFE0&210 RDCHVRead char from current input stream → A; C=1 if error
OSFILE&FFDD&212 FILEVopFile load/save (param block in X+Y)
OSARGS&FFDA&214 ARGSVopFile arguments
OSBGET&FFD7&216 BGETVGet byte from file (Y=handle) → A
OSBPUT&FFD4&218 BPUTVbytePut byte to file (Y=handle)
OSGBPB&FFD1&21A GBPBVopMulti-byte file I/O
OSFIND&FFCE&21C FINDVopOpen/close file
NVWRCH&FFCBcharNon-vectored OSWRCH (faster, no Tube)
NVRDCH&FFC8Non-vectored OSRDCH
GSREAD&FFC5Read char from GSINIT string
GSINIT&FFC2Init for GSREAD
OSEVEN&FFBFevent #Generate an event
OSRDSC&FFB9Read byte from screen / paged ROM
OSWRSC&FFB3valWrite byte to screen

Plus interceptable vectors with no fixed entry point:

VectorAddressTriggered by
USERV&200Unknown OSWORD &E0-&FF, custom user use
BRKV&202BRK instruction
IRQ1V&204All maskable IRQs (high priority)
IRQ2V&206MOS-unrecognised IRQs (low priority)
FSCV&21EFiling-system control entry
EVNTV&220Event dispatch
UPTV&222User print
NETV&224Econet
VDUV&226Unrecognised VDU command (e.g. VDU 23,17..31)
KEYV&228Keyboard
INSV&22AInsert into buffer
REMV&22CRemove from buffer
CNPV&22ECount / purge buffer
IND1V/IND2V/IND3V&230/&232/&234Spare

Calling conventions

  • A: primary parameter / call selector.
  • X, Y: secondary parameters, or parameter-block address LSB+MSB.
  • C, N, V, Z: undefined on return unless the call specifies otherwise.
  • A, X, Y: preserved on return for most calls (OSWRCH preserves all three). OSCLI and OSEVEN are exceptions.
  • Interrupt status: preserved on return, but may be enabled during the call. So you cannot call OS routines from inside an IRQ handler — the inner CLI will re-enable IRQs and a nested interrupt will overwrite zp &EF-&F1, &FC etc.
  • D flag must be clear before any OS call.

OSWRCH details

LDA #'A'
JSR &FFEE       ; or JMP (&20E) from your own re-implementation

Writes to the currently selected output stream(s). Default: VDU + printer (if VDU 2) + spool (if *SPOOL). Controlled by OSBYTE &03 mask.

Cost varies widely:

  • VDU only, plain ASCII to MODE 7: ~400 cycles
  • VDU only, MODE 0 with 1bpp pixel rendering: ~1500 cycles (font lookup + 8 byte writes)
  • Multi-stream (printer + spool): add the printer driver overhead

For fast text output in MODE 0/1/2/4/5: render glyphs yourself by reading the font definitions from MOS ROM (or your own font) and STA to screen RAM directly. OSWRCH is the slowest possible way to put a character on screen.

OSRDCH details

JSR &FFE0
BCS error       ; C=1 means read error (ESCAPE etc.)
; A = character read

Blocks until a character is available. To poll instead, use OSBYTE &81 with a timeout, or read &FF (Escape flag) and the key buffer directly.

OSCLI details

LDX #cmd_str MOD 256
LDY #cmd_str DIV 256
JSR &FFF7
 
.cmd_str
EQUS "FX 15,0"
EQUB &0D        ; CR terminator

Equivalent to typing the command at the BASIC prompt. Slow (parser overhead) but powerful — any * command works.

Non-vectored variants

NVWRCH (&FFCB) and NVRDCH (&FFC8) skip the indirection through &20E/&210. Save 3 cycles per call. But: not available across the Tube (second processors require the vectored version to forward I/O over the Tube link). Useful in time-critical I/O loops where you’re sure no Tube is present.

Bypassing OSWRCH entirely

For a tight character-printing loop, OSWRCH overhead dominates. Three faster paths:

  1. Pre-render strings: build the screen-RAM byte sequence once at startup, STA/LDA directly each frame.
  2. Inline glyph writer: JSR to a routine that does font-lookup + 8× STA to screen RAM. Skip MOS entirely.
  3. VDU queue stuffing: write directly into the VDU queue at &323 and decrement &DA (VDU queue count) — bypasses dispatcher cost. Rarely worth it; #1 or #2 wins.

In hot game loops, #1 (precompose) is almost always the right answer.

See also

  • osbyte and osword — directory tables.
  • interrupts — IRQ vector chain.
  • brk — BRK protocol.
  • vdu — VDU control codes consumed by OSWRCH / OSASCI.
  • errors — error number ↔ message reference.
  • star-commands*command dispatch (OSCLI is the entry point).
  • os-workspace — Page 2 vector table + OS variables.

This wiki is curated by Claude following the LLM-Wiki methodology — a human curates source documents, the LLM compiles structured cross-linked markdown. Content may contain errors, omissions, or stale claims. For authoritative information refer to the original source documents in the bbc-documents GitHub archive.