--- name: kernal-routines description: > Use this skill for using the C64 Kernal (OS) routines from machine language. Covers all Kernal jump table entries, input parameters, output values, error handling, screen I/O, serial I/O, tape I/O, RS-232, system vectors, and interrupt handling. Sources: COMPUTE!'s VIC-20 and C64 Tool Kit: Kernal, The Anatomy of the C64, The Complete C64 ROM Disassembly. --- # Kernal Routines Reference ## Overview The **Kernal** is the C64's operating system ROM, occupying $E000–$FFFF (57344–65535). It provides a jump table at $FF81–$FFF3 that remains accessible regardless of the current ROM banking state. **Always call through the jump table**, never directly into ROM routines, as addresses may differ between ROM revisions. --- ## System and Reset Routines ### $FF81 — CINT: Initialize Screen Editor ``` Input: None Output: Screen cleared, cursor home Clobbers: A, X, Y Use: Called automatically at reset; call to reinitialize screen ``` ### $FF84 — IOINIT: Initialize I/O Devices ``` Input: None Output: CIA chips, VIC-II, SID reset to defaults; IRQ enabled Clobbers: A, X, Y ``` ### $FF87 — RAMTAS: Test and Initialize RAM ``` Input: None Output: Zero page and stack cleared, tape buffer zeroed, memory size set Clobbers: A, X, Y ``` ### $FF8A — RESTOR: Restore Default Vectors ``` Input: None Output: RAM vectors at $0314-$0333 restored to Kernal defaults Clobbers: A, X Note: Vectors are copied from ROM table at $E518 ``` ### $FF8D — VECTOR: Read or Set RAM Vectors ``` Input: Carry clear → copy RAM vectors to user area ($FB/$FC points to destination) Carry set → copy user area to RAM vectors ($FB/$FC points to source) X = low byte, Y = high byte of 2-byte pointer at $FB/$FC Output: Vectors read or written Clobbers: A, X, Y ``` ### $FF90 — SETMSG: Control OS Messages ``` Input: A = flag byte bit 7 = 1: enable BASIC error messages bit 6 = 1: enable status messages (FOUND, SAVING, etc) Output: Previous flag value in A ``` ### $FF99 — MEMTOP: Read/Set Top of Memory ``` Input: Carry set → set: X = low byte, Y = high byte of new top address Carry clear → read current top Output: X = low byte, Y = high byte of top of memory ``` ### $FF9C — MEMBOT: Read/Set Bottom of Memory ``` Input: Carry set → set: X = low byte, Y = high byte of new bottom Carry clear → read current bottom Output: X = low byte, Y = high byte of bottom of memory ``` --- ## Screen I/O Routines ### $FFED — SCREEN: Return Screen Format ``` Input: None Output: X = number of columns (40), Y = number of rows (25) ``` ### $FFF0 — PLOT: Read/Set Cursor Position ``` Input: Carry set → set cursor: X = row (0-24), Y = column (0-39) Carry clear → read cursor position Output: (read) X = current row, Y = current column Note: Cursor blink is reset ``` ### $FFD2 — BSOUT: Output Character to Current Channel ``` Input: A = character to output (PETSCII) Output: Character sent to current output channel (screen or file) Clobbers: A Note: Also accessible via KERNAL vector at $0326/$0327 ``` ### $FFCF — BASIN: Input Character from Current Channel ``` Input: None Output: A = character received (PETSCII) Note: Waits for input if keyboard; also accessible at $0324/$0325 ``` ### $FFE4 — GETIN: Get Character from Input Queue ``` Input: None Output: A = character (0 if no character available) Note: Non-blocking keyboard read; does not wait for keypress ``` ### $FFE1 — STOP: Test STOP Key ``` Input: None Output: Zero flag set if STOP key pressed A = current value of keyboard row (matrix scan) Note: Also restores registers if STOP held (calls CLRCH) ``` --- ## File I/O: Setup Routines ### $FFBA — SETLFS: Set Logical File, Device, Command ``` Input: A = logical file number (1-127) X = device number (0=keyboard, 1=tape, 2=RS232, 3=screen, 4-7=serial, 8-15=disk) Y = secondary address (0-15; 255=no secondary address) Output: Values stored in OS variables ($B8, $B9, $BA, $BB) ``` ### $FFBD — SETNAM: Set Filename ``` Input: A = length of filename (0 if no name) X = low byte of filename address Y = high byte of filename address Output: Filename parameters stored in OS variables ``` ### $FFC0 — OPEN: Open Logical File ``` Input: Parameters set by SETLFS and SETNAM Output: Carry clear = success Carry set = error; A = error code Error codes: 1=file open, 2=too many files, 4=file not found, 5=device not present, 6=not input file, 7=not output file ``` ### $FFC3 — CLOSE: Close Logical File ``` Input: A = logical file number to close Output: File closed, channels freed Clobbers: A, X, Y ``` ### $FFC6 — CHKIN: Open Channel for Input ``` Input: X = logical file number (must be open) Output: Carry clear = success; subsequent BASIN reads from this file Carry set = error; A = error code ``` ### $FFC9 — CHKOUT: Open Channel for Output ``` Input: X = logical file number (must be open) Output: Carry clear = success; subsequent BSOUT writes to this file Carry set = error; A = error code ``` ### $FFCC — CLRCH: Clear Input/Output Channels ``` Input: None Output: Default channels restored (keyboard input, screen output) Clobbers: A, X ``` ### $FFE7 — CLALL: Close All Files and Channels ``` Input: None Output: All logical files closed, channels restored to defaults Clobbers: A, X ``` --- ## File I/O: Load and Save ### $FFD5 — LOAD: Load from Device ``` Input: A = 0 for LOAD, 1 for VERIFY X = low byte of load address (if SA=0, ignored — file provides address) Y = high byte of load address SA (set by SETLFS): 0 = load to file's own start address 1 = load to X/Y address Output: Carry clear = success; X = low byte, Y = high byte of last address+1 Carry set = error; A = error code Note: Set SETLFS with SA=0 to load at the address stored in the file header ``` ### $FFD8 — SAVE: Save to Device ``` Input: A = zero-page address containing 2-byte start address pointer X = low byte of end address + 1 Y = high byte of end address + 1 Output: Carry clear = success Carry set = error; A = error code Example: LDA #STARTADR STA $C2 LDA #$C1 ; ZP pointer to start address LDX #<(ENDADR+1) LDY #>(ENDADR+1) JSR $FFD8 ``` --- ## I/O Status ### $FFB7 — READST: Read I/O Status Word ``` Input: None Output: A = status byte (ST variable at $90) Status bits: Bit 0: Write timeout on serial bus Bit 1: (serial) Byte lost Bit 2: Short block (tape) Bit 3: Long block (tape) Bit 4: Unrecoverable read error (tape) Bit 5: Checksum error (tape) Bit 6: End of file Bit 7: End of tape / device not present ``` --- ## Serial Bus Routines ### $FFB1 — LISTEN: Command Device to Listen ``` Input: A = device address (8-30) Output: Device commanded to LISTEN; subsequent bytes go to device ``` ### $FFB4 — TALK: Command Device to Talk ``` Input: A = device address (8-30) Output: Device commanded to TALK; subsequent bytes come from device ``` ### $FF93 — SECOND: Send Secondary Address After LISTEN ``` Input: A = secondary address (0-31) Output: Secondary address sent to listening device ``` ### $FF96 — TKSA: Send Secondary Address After TALK ``` Input: A = secondary address (0-31) Output: Secondary address sent to talking device ``` ### $FFA5 — ACPTR: Input Byte from Serial Bus ``` Input: None (a device must be talking) Output: A = byte received from serial bus ``` ### $FFA8 — CIOUT: Output Byte to Serial Bus ``` Input: A = byte to send (device must be listening) Output: Byte sent to serial bus ``` ### $FFAB — UNTLK: Send UNTALK ``` Input: None Output: UNTALK sent; talking device releases bus ``` ### $FFAE — UNLSN: Send UNLISTEN ``` Input: None Output: UNLISTEN sent; all listening devices release bus ``` --- ## Timing ### $FFDB — SETTIM: Set Real-Time Clock ``` Input: A = high byte, X = middle byte, Y = low byte of 60Hz jiffy count ``` ### $FFDE — RDTIM: Read Real-Time Clock ``` Input: None Output: A = high byte, X = middle byte, Y = low byte of jiffy count Note: Count increments 60× per second (NTSC) or 50× per second (PAL) ``` ### $FFEA — UDTIM: Increment Real-Time Clock ``` Input: None Output: Jiffy count incremented; called by IRQ handler ``` --- ## RAM Vectors (Kernal and BASIC) The Kernal uses indirect vectors in RAM. These can be patched to intercept OS calls. | Address | Vector | Default ROM target | Purpose | |---------|--------|--------------------|---------| | $0314-$0315 | CINV | $EA31 | IRQ handler | | $0316-$0317 | CBINV | $FE66 | BRK handler | | $0318-$0319 | NMINV | $FE47 | NMI handler | | $031A-$031B | IOPEN | $F34A | OPEN routine | | $031C-$031D | ICLOSE | $F2C7 | CLOSE routine | | $031E-$031F | ICHKIN | $F20E | CHKIN routine | | $0320-$0321 | ICHKOUT | $F250 | CHKOUT routine | | $0322-$0323 | ICLRCH | $F333 | CLRCH routine | | $0324-$0325 | IBASIN | $F157 | BASIN (CHRIN) | | $0326-$0327 | IBSOUT | $F1CA | BSOUT (CHROUT) | | $0328-$0329 | ISTOP | $F6ED | STOP test | | $032A-$032B | IGETIN | $F13E | GETIN | | $032C-$032D | ICLALL | $F32F | CLALL | | $032E-$032F | USRCMD | $FE66 | User command | | $0330-$0331 | ILOAD | $F4A5 | LOAD | | $0332-$0333 | ISAVE | $F5ED | SAVE | ### Intercepting BSOUT (Output) Example ```asm ; Install hook on BSOUT to intercept all character output SEI LDA $0326 STA OLDBSOUT ; save original LDA $0327 STA OLDBSOUT+1 LDA #MYHOOK STA $0327 CLI RTS MYHOOK ; A = character about to be output ; do your interception here JMP (OLDBSOUT) ; chain to original routine OLDBSOUT .WORD $F1CA ``` --- ## Kernal Usage Template (Assembly) ```asm ; Complete file write example ; 1. Set LFS LDA #1 ; logical file 1 LDX #8 ; disk drive LDY #2 ; SA=2 (write sequential) JSR $FFBA ; SETLFS ; 2. Set filename LDA #FNLEN LDX #FNAME JSR $FFBD ; SETNAM ; 3. Open JSR $FFC0 ; OPEN BCS OERR ; branch on error ; 4. Redirect output LDX #1 ; LFN JSR $FFC9 ; CHKOUT BCS CERR ; 5. Write data LDX #0 WLOOP LDA DATA,X BEQ DONE JSR $FFD2 ; BSOUT INX BNE WLOOP ; 6. Clean up DONE JSR $FFCC ; CLRCH LDA #1 JSR $FFC3 ; CLOSE RTS OERR ; handle open error (A = code) CERR ; handle channel error FNAME .TEXT "MYFILE,S,W" FNLEN = * - FNAME DATA .TEXT "HELLO WORLD" .BYTE 0 ```