Atari OS: services in ROM
The Atari eight-bit operating system lives largely in ROM. It initializes hardware, manages input and output through a device-independent interface, supports the screen editor, handles floating-point routines, and coordinates peripherals over the Serial Input/Output bus.
This architecture explains one of the system's most memorable qualities: printers, disk drives, and other intelligent peripherals could be daisy-chained with relatively little configuration.
Revisions and compatibility
The original 400/800 OS and the later XL/XE revisions are closely related but not identical. Some older software relied on undocumented addresses or behavior, so Atari supplied the Translator disk to temporarily load a more compatible environment.
CIO: one interface, many devices
Central Input/Output (CIO) lets a program ask for an operation without knowing how a particular device performs it. An application opens a named device, reads or writes data, checks status, and closes the connection. A device handler supplies the implementation. Changing the device name can redirect output from the screen to a printer or a disk file without rewriting the application’s output logic.
A channel number is a connection, not a hardware address. Channel 1 can refer to the
keyboard today and a disk file later. The device specification supplies the
destination: in D2:NOTES.TXT, D selects the disk handler,
2 selects the drive, and the rest names the file.
| Layer | Its job |
|---|---|
| Application | Requests OPEN, GET, PUT, STATUS, CLOSE, or a device-specific operation. |
| IOCB → CIO | An I/O Control Block describes the channel, command, buffer, and options. CIO checks and dispatches the request. |
| HATABS → handler | The RAM Handler Address Table connects a device letter to a table of handler entry points. |
| Device implementation | The handler works with display memory, keyboard services, SIO, or another interface as appropriate. |
The common interface does not make all devices identical: a keyboard cannot accept output, a printer cannot supply a line of keyboard input, and disk-specific commands require a disk handler. The handler reports unsupported operations as errors. Atari’s Technical Reference Notes, sections 5 and 9, document both sides of this interface.
The editor, keyboard, screen, and peripherals
| Device | What you access |
|---|---|
E: — screen editor |
Text output with cursor and editing controls, plus edited keyboard input. A record read lets the user edit a logical line and press RETURN. It is more than a raw stream of key presses. |
K: — keyboard |
Individual translated ATASCII key values, without the editor’s line editing or automatic echo. Useful when your program handles its own input. |
S: — screen |
Display operations in the selected graphics mode. Character or pixel interpretation depends on the mode; screen coordinates and special commands support graphics operations. It does not provide the editor’s keyboard interaction. |
P: — printer |
Buffered printer output through the resident printer handler. |
C: — cassette |
Sequential cassette input and output, with recording details handled below the application. |
D: — disk files |
File operations supplied by a loaded DOS/file-management handler. The stock
ROM’s disk boot and sector services alone do not provide a general
D: filesystem.
|
R: — serial interface |
A handler supplied for an interface such as the Atari 850; it is not one of the five standard resident ROM devices. Configuration and special commands depend on the handler. |
In Atari BASIC, plain PRINT and INPUT normally use the
editor on channel 0. BASIC manages IOCBs for you through OPEN,
CLOSE, GET, PUT, and XIO. For
OPEN, auxiliary value 4 requests input, 8 output, and 12 both, where supported.
Channel 6 is normally used by BASIC’s graphics commands, while channel 7 is used by
several interpreter I/O operations; channel 1 is a convenient choice for these small
examples.
10 OPEN #1,4,0,"K:"
20 GET #1,K
30 CLOSE #1
40 PRINT "ATASCII VALUE: ";K
10 OPEN #1,8,0,"P:"
20 PRINT #1;"HELLO FROM CIO"
30 CLOSE #1
The first example waits for one key; it does not wait for a complete edited line. The
second requires a working printer connection. With DOS loaded, changing
"P:" to an unused filename such as "D1:HELLO.TXT" writes a
disk file instead. Output mode 8 can replace an existing file, so choose a new name.
See Technical Reference
Notes, sections 5–6, for device behavior and command parameters.
Calling CIO from 6502 assembly
There are eight 16-byte IOCBs at $0340–$03BF. Use $0340 + channel
× 16 for a channel’s base address, and load X with channel × 16
before calling CIOV ($E456). X is an offset: channel 1 uses
$10, not 1. Use the published entry vector rather than an internal ROM
address that can move between OS revisions.
| Offset / name | Purpose |
|---|---|
+0 ICHID |
OS-maintained index into HATABS; $FF means closed. |
+1 ICDNO |
Device unit number established by OPEN. |
+2 ICCOM |
Command: 3 OPEN, 5 GET RECORD, 7 GET CHARACTERS, 9 PUT RECORD, 11 PUT CHARACTERS, 12 CLOSE, 13 STATUS. |
+3 ICSTA |
Returned status, also returned in Y. Normal success is 1; values with bit 7 set indicate errors. |
+4/+5 ICBAL/H |
Low/high address of the device specification for OPEN, or of the data buffer for transfers. |
+6/+7 ICPTL/H |
Cached PUT BYTE entry address minus one, established by OPEN. |
+8/+9 ICBLL/H |
Low/high byte count for a buffer transfer. |
+10/+11 ICAX1/2 |
Auxiliary options, including the OPEN access mode. |
+12…+15 |
Additional handler-dependent state. |
For a normal OPEN, point the buffer address at an ATASCII device/file specification
terminated by $9B (Atari end-of-line). After a successful OPEN, set the
transfer command, buffer address, and length before calling CIO again. Record
operations recognize end-of-line; character operations transfer bytes without treating
$9B as a record boundary. CLOSE lets a handler flush its buffers and
release resources.
CIOV = $E456
ICCOM = $0342
ICBAL = $0344
ICBAH = $0345
ICBLL = $0348
ICBLH = $0349
PrintHello:
LDX #$00 ; IOCB 0 already open to E:
LDA #11 ; PUT CHARACTERS
STA ICCOM,X
LDA #<Message
STA ICBAL,X
LDA #>Message
STA ICBAH,X
LDA #MessageEnd-Message
STA ICBLL,X
LDA #0
STA ICBLH,X
JSR CIOV
TYA ; explicitly test returned status
BMI Failed
RTS
Failed:
RTS ; return error in Y to the caller
Message:
.BYTE "HELLO FROM CIO",$9B
MessageEnd:
This subroutine assumes the normal editor environment with IOCB 0 already open; it is
not a complete boot program or BASIC USR wrapper. It prints one line, including an
explicit Atari end-of-line. A caller must handle the returned error status. The
assembly snippets use symbolic 6502 notation with
.BYTE/.WORD directives; origin, binary-load headers, and
invocation depend on your assembler and host program. Reference: section 5 and
appendix L, IOCB definitions.
Where CIO ends and SIO begins
CIO deals in logical operations; Serial Input/Output (SIO) deals with transactions on the peripheral bus. A DOS handler can turn a file read into disk-sector requests, then use SIO to exchange those requests with a drive. The drive’s electronics carry out its own part of the protocol. The screen and keyboard do not need to travel over SIO just because an application reaches them through CIO.
To issue a bus transaction directly, a machine-language program fills the Device
Control Block at $0300–$030B and calls SIOV ($E459). Its
fields specify the device ID, unit, command, data direction, buffer address, timeout,
byte count, and two device-specific auxiliary bytes. The direction/status byte
DSTATS ($0303) uses $40 for a transfer to the computer and
$80 for one from the computer; it holds status on return, also reported
in Y.
SIO is useful when you need a peripheral-specific command, but bypassing DOS also bypasses its filenames, allocation, and buffering. Read the device’s command specification before choosing DCB values. A hardware device number in SIO and a channel number in CIO serve different purposes. Reference: section 9, SIO interface and Device Control Block.
Using the floating-point package
The ROM math package is a separate callable library. You do not OPEN a floating-point
device or send it a CIO command. Its numbers use a six-byte decimal floating-point
representation with a base-100 exponent scheme and packed-BCD mantissa, rather than
modern IEEE binary floating point. Operands live in RAM pseudo-registers: FR0
($00D4) and FR1 ($00E0), each six bytes long.
| Routine / entry | Calling convention |
|---|---|
AFP $D800 |
Convert numeric text to FR0. Set the text pointer in INBUFF
($F3/$F4) and its starting offset in CIX ($F2). CIX
advances past the parsed number; check carry for failure. |
FASC $D8E6 |
Convert FR0 to text. INBUFF returns a pointer into the line buffer. Bit 7 of
the final character marks the end; no $9B terminator is appended.
|
IFP $D9AA |
Convert an unsigned 16-bit integer in FR0’s first two bytes, low byte first, into floating point in FR0. |
FPI $D9D2 |
Round FR0 to an unsigned 16-bit integer in its first two bytes. Check carry for an invalid or out-of-range result. |
FADD $DA66 / FSUB $DA60 |
FR0 + FR1 or FR0 − FR1 → FR0; FR1 is altered. Carry indicates an out-of-range result. |
FMUL $DADB / FDIV $DB28 |
FR0 × FR1 or FR0 ÷ FR1 → FR0; FR1 is altered. Check carry for range errors, or division by zero. |
FR0 = $D4
INBUFF = $F3
IFP = $D9AA
FASC = $D8E6
MakeNumber:
LDA #123
STA FR0
LDA #0
STA FR0+1
JSR IFP
JSR FASC
LDY #0
CopyText:
LDA (INBUFF),Y
PHA ; preserve the end-marker bit
AND #$7F
STA NumberText,Y
INY
PLA
BPL CopyText
LDA #$9B
STA NumberText,Y
INY
STY NumberLength ; count includes the end-of-line
RTS
NumberText:
.BYTE 0,0,0,0,0,0,0,0 ; ample space for this example
NumberLength:
.BYTE 0
After this routine, pass NumberText and NumberLength to CIO
PUT CHARACTERS, as in the editor example, to display the number. Copying the text
removes the high-bit end marker and adds the editor’s end-of-line character. The
eight-byte buffer is for this fixed value; general-purpose formatting needs enough
space for signs, decimals, and exponent notation.
The math routines share zero-page workspace and the line buffer around
$0580. Save any live host-program state before borrowing those areas,
especially when called from BASIC, and copy results before another operation reuses
the buffer. Do not assume these routines can be interrupted by another call using the
same workspace. Reference: section 8,
floating-point routines and calling sequences.
Replacing a handler—including E:
The OS makes its device routing writable even when the original handlers live in ROM.
HATABS starts at $031A. Each entry is three bytes: a device letter
followed by the low and high bytes of the address of its handler vector table. The
standard table has space for twelve entries and two trailing zero bytes. Each of the
first six words in a handler vector table stores a routine’s address minus
one; the HATABS pointer to that table is an ordinary address.
| Table offset | Entry and responsibility |
|---|---|
+0 |
OPEN: validate options and prepare the device/channel. |
+2 |
CLOSE: finish buffered output and release resources. |
+4 |
GET BYTE: return one input byte in A. |
+6 |
PUT BYTE: accept one output byte in A. |
+8 |
STATUS: report readiness/device status. |
+10 |
SPECIAL: handle device-specific commands. |
+12 |
A three-byte JMP Init instruction, not another address-minus-one
word. |
CIO dispatches through the six vectors using the 6502 stack and RTS, which accounts
for the minus one. Handler routines return with RTS and a status in Y—normally 1 on
success. X identifies the originating IOCB by its byte offset, and CIO supplies
working parameters in the zero-page IOCB at $20–$2F. An unsupported
operation should report an appropriate error such as $92 (146).
There is an important exception for PUT BYTE: Atari BASIC can call its cached output entry directly without passing through CIO. Such a routine must not assume the zero-page IOCB describes the call. Use the originating IOCB for parameters if needed. Similarly, do not call CIO recursively from inside a handler without explicitly preserving its shared internal state; an output wrapper should chain to the saved handler entry directly.
A small editor extension: uppercase program output
A useful first experiment is to wrap the existing editor instead of implementing every editor function. Keep its OPEN, CLOSE, GET, STATUS, and SPECIAL behavior, and replace just PUT BYTE with a routine that converts ordinary lowercase output to uppercase. Keyboard editing remains with the original editor; this is an output filter, not a complete new text editor.
UpperPut:
CMP #$61 ; ordinary ATASCII lowercase a
BCC ChainPut
CMP #$7B ; one past lowercase z
BCS ChainPut
SEC
SBC #$20 ; a-z becomes A-Z
ChainPut:
JMP $FFFF ; installer patches operand with
; saved original PUT address + 1
This is the wrapper routine only: $FFFF must be replaced before it is
reachable. Control codes and inverse-video characters pass through unchanged. The
original handler returns directly to the caller and supplies its normal status.
- Reserve resident RAM. Keep the wrapper, a writable 15-byte handler table, and saved installation state outside memory that BASIC, DOS, display allocation, or your loader will reuse. A DOS resident driver normally participates in its loader’s memory-reservation scheme; an arbitrary fixed address is not a safe permanent home.
- Find the active editor entry. Search HATABS in three-byte steps
from offset 33 down to 0, matching
E. The high-to-low order matters when a replacement is already installed. Save the entry offset and its original table pointer. Do not assume a particular ROM address or a fixed slot. - Prepare the wrapper before publishing it. Copy the original table
into your RAM table. Read its PUT word at offsets 6–7; it is an address minus one.
Add one with a full 16-bit carry and place the resulting actual address in the
operand of
ChainPut. Replace the RAM table’s PUT word withUpperPut-1. The other entries continue to refer to the original handler. - Redirect new opens. At a point when no I/O is in progress, replace the saved HATABS entry’s two pointer bytes with the ordinary address of your RAM table. An installer must prevent callbacks from seeing a half-written pointer; simply disabling maskable interrupts does not disable the Atari’s NMI-driven activity.
- Update already-open editor channels. Scan IOCBs at offsets
$00,$10,…,$70. For each whose ICHID matches the replaced HATABS slot, save ICPTL/H and set it toUpperPut-1. This includes the usual IOCB 0 and catches BASIC’s direct output calls. An application-owned channel can instead be closed and reopened, but casually closing the interpreter’s console can disrupt its editor state. - Test and uninstall deliberately. Output
"hello"through CIO and BASIC PRINT, exercise editing and RETURN, and verify that control characters still work. To remove the wrapper, restore the original HATABS pointer and affected channels’ output vectors before releasing RAM. Account for channels opened or closed since installation, and do not overwrite a newer handler installed after yours.
From a wrapper to your own editor
A full replacement provides its own routines for all six operations. Its GET side must handle the input interaction your programs expect; an E:-compatible editor needs cursor motion, insertion/deletion, logical-line handling, RETURN/end-of-line behavior, and BREAK/error handling. Its PUT side must interpret the editor’s ATASCII control characters as well as display ordinary text. Decide how it cooperates with graphics modes, text windows, screen memory, and the OS cursor variables.
To add an unrelated device such as N:, install its letter and
vector-table pointer into a free HATABS entry instead. Appending another
E entry can override future OPEN searches, but an existing IOCB retains
its old HATABS index and cached PUT vector. That is why a controlled replacement of
the active entry plus an update of open channels is useful for the console.
Installation and reset survival are separate jobs. The original OS rebuilds HATABS at reset, so the initialization JMP in your table does not by itself make a RAM handler survive. Arrange reinstallation through the appropriate DOS/cartridge initialization path for the target system, initialize private state explicitly, and avoid installing the same wrapper twice. Test the actual 400/800 or XL/XE ROM and DOS combination rather than relying on internal ROM addresses.
The wrapper and installation steps are an implementation guide, not a drop-in resident driver: memory placement, reset integration, and installation/removal code belong to the host environment. The table format and handler contract above follow Atari’s Technical Reference Notes, section 9 (printed pages 134–138). For further explanation, see De Re Atari, chapter 8, and the Atari XL OS addendum.
Disk operating systems
Unlike many later computers, an Atari eight-bit disk operating system is generally loaded from disk. That made DOS replaceable and encouraged alternatives with different command styles, formats, and capabilities.
| Atari DOS 2.0S | A widely recognized menu-driven standard for single-density disks. |
|---|---|
| Atari DOS 2.5 | Added enhanced-density support for the Atari 1050 while retaining the familiar menu. |
| MyDOS | Popular for subdirectories and larger or nonstandard storage configurations. |
| DOS XL | Optimized Systems Software's flexible DOS offered both a command processor and a loadable menu, along with batch files and single- or double-density support. |
| SpartaDOS | A command-line environment with subdirectories, batch files, date/time support, and a more modern filesystem model. |
| SpartaDOS X | A cartridge-based continuation designed for powerful, expandable systems. |
| RealDOS | An XL/XE-oriented DOS using the SpartaDOS File System, with a command processor largely compatible with SpartaDOS and BeweDOS plus an included menu for menu-oriented operation. |
The ROM OS provides device and machine services. A disk OS adds files, directories, disk commands, and storage formats.
TOS and the ST desktop
On the Atari ST, TOS combines GEMDOS file and process services, BIOS and XBIOS machine interfaces, and GEM's graphical components. Users encounter it through a desktop of windows, menus, icons, drives, and files.
Early machines sometimes loaded TOS from disk; later systems commonly carried it in ROM. Multiple revisions followed as Atari added hardware and improved compatibility. The ST's `.PRG`, `.TOS`, and `.TTP` program types reflect whether an application expects the graphical environment, runs directly, or accepts parameters.
Read the history of the Atari ST operating system for the story of TOS, GEMDOS, GEM, and the teams that brought them together.
Professional GEM by Tim Oren
Read Tim Oren’s 15 Professional GEM columns from ANTIC Publishing, covering windows, dialogs, resources, menus, VDI graphics, events, user interfaces, and GEMDOS. The HTML editions include the original reprint notices and sample code, with links to the unchanged text files.
Beyond stock TOS
Owners can explore replacements and extensions such as EmuTOS, MiNT-family multitasking systems, alternate desktops, hard-disk drivers, and utilities. Compatibility depends on the exact machine and configuration, so changes should be documented and reversible.