June 2019

The Software Stack

Now that the hardware is mostly finalized time to talk about the software stack. My design is modeled after CP/M (and by extension, MS-DOS), in which there is a ROM BIOS that provides a standardized view of the hardware. On top of that is the actual operating system, which uses the BIOS to access the hardware and provides more complex services like working with files and launching applications.

The “official” OS for this project will of course be a version of JR/OS from COLE-1. My hope is to make JR/OS easily portable to future boards (yes, COLE-3 is already in my head!) and possibly other peoples’ boards, such as the Neon816 or the Commander X16.

With that out of the way let’s start at the bottom of the stack and talk a bit about the BIOS.

The BIOS

The BIOS serves three main functions:

  1. Initializing the hardware at power-on or reset
  2. Providing standardized APIs for accessing the hardware.
  3. Booting the operating system. This may consist of passing control to a ROM-resident copy of the operating system, or booting from external media such as an SD card.

The current incarnation of the COLE-2 ROM does not contain any OS code, nor does it implement booting. In fact, there’s not even any mass storage drivers yet.

The BIOS API

The exact list of services that will be provided by the BIOS is still a moving target, but it will for sure provide the following:

  • Attaching the console to an I/O device. COLE-2 supports both VGA and serial consoles
  • Reading/writing a byte to/from the current console device
  • Reading/writing a byte to/from a character device (such as a serial port)
  • Reading/writing a block to/from a block device (such as an SD card)
  • Making device-specific calls, in the manner of Unix’s ioctl() function. Common operations such as “set baud rate” will be standardized.
  • Enumeration of available device drivers
  • Registration of new device drivers

Making API Calls

All BIOS services are accessed via the 65816 COP instruction. The COP signature byte selects the API method to call; parameters are passed in the accumulator or on the stack. For example, here is how you would print the letter “A” to the console:

LDA #'A'
COP $02  ; SYS_CONSOLE_WRITE

All registers and CPU state are preserved, except the accumulator and the carry bit; they are used to pass data and error state back to the caller. System calls must be made in native mode but the registers can be in any combination of 8- or 16-bit mode.

The BIOS uses a dispatch table to transfer control to the selected API method after performing some house keeping. First, it saves all CPU registers onto the stack. Next, it sets the data bank register to the system bank (currently $00), and re-enables interrupts if they were enabled when COP was called (since COP disables them). It then sets the processor to 8-bit mode, sets the direct page to point to the bottom of the system call stack frame, and calls the API method using a JSL. Upon return from the API method, the BIOS will clean up any stack parameters, restore registers, and return control to the caller.

JR/OS

For those who never followed the brief history of COLE-1, JR/OS stands for Josh’s Retro Operating System. The slash in the name is an homage to GS/OS, the operating system of my teenage years.

Due to the short life of the COLE-1 project very little actual JR/OS code exists. What does exist though are some design goals:

  • FAT32 as its native file system, to make it easy to transfer files with other systems.
  • Support any block device that can provide 512-byte blocks
  • Boot from ROM or external storage
  • API provided via COP (BIOS will reserve signature bye values for OS use)
  • Initially single-process but with multi-process, protected memory support in mind for future versions
  • System memory management
  • Command shell for launching applications

I expect this to evolve heavily once I get into writing the actual JR/OS code.

Application Support

The initial version of JR/OS will operate much like CP/M or MS-DOS in that only one application process will be running at any given time. However, even this first version will lay the groundwork for a future multi-process version. Applications will be expected to behave as if they are not the sole running program, and make use of OS services such as memory management to behave properly.

Applications will consist of one or more module files, each of which must fit within a single 64K bank. These module files will be built to run at address $0000 and will be loaded into the beginning of a free bank. Future JR/OS versions may relax this restriction by supporting relocatable object files.

For multi-module applications JR/OS will provide a module loader that loads a file and returns its load address. A given module will only be loaded once; further requests for the same file will return the address of the existing copy. This will be the basis of shared library support in a future JR/OS version.

What’s Next?

Right now I’m busy working on getting the BIOS to something resembling its final state. Basic console and serial port support are already implemented; the next step is to get the SPI and SD card drivers ported over from COLE-1. At that point I’ll be able to get to work on JR/OS itself, probably in parallel with the first actual JR/OS application: the BASIC interpreter.