The System/65 BIOS is not meant to be a complex thing. It mainly exists so that applications won't have to be re-assembled if anything about the disk and serial hardware is changed. In view of this, we want to make the use of BIOS calls as clean and abstracted from the application as possible. Commodore computers used to do this by having a "jump table" of pointers to the different BIOS ('scuse me, "Kernal") calls, but I'm going to go one better and do it through software interrupts, so that the application doesn't even need to know where the jump table is.
Now, the 6502 actually only has one software interrupt, the BRK instruction. This instruction sets the B flag in the processor status register and jumps to the IRQ handler. But how can a set of multiple different BIOS calls be implemented with just the one interrupt? It could be done using one of the registers to hold the call number, but we need registers to pass arguments, too, and with only three main registers in the CPU, using even one of them for this would strain our capabilities quite a bit.
So instead, we're going to take advantage of the way the BRK instruction is represented in memory. The BRK opcode is 0, and it takes no arguments; however, the instruction actually takes up two bytes in memory. The first byte is the opcode, but the second is irrelevant; the instruction works the same no matter what the value of the second byte is. Therefore, we can use the second byte of the BRK instruction to hold the call number, and have the IRQ handler use that to decide what routine to jump to.
Now, since most if not all 6502 assemblers generate two zero bytes when they encounter a BRK instruction, we'll need to define a macro to generate the necessary pair of bytes for a BIOS call. Here's how it would be done in Michal Kowalski's 6502 simulator/assembler (my coding tool of choice):
bios .MACRO call ; bios is the macro name, call is the (internal) name of its argument
.DB $00 ; the BRK opcode
.DB call ; the call number
Which could then be used like so:
LDA #$41 ; load a character to print to the serial port
bios $08 ; or whatever we've set the "print character" call to
In addition to the fact that this completely divorces the ROM layout from the application side of BIOS calls, this approach also takes one less byte in memory than the Commodore-style jump-table approach, and makes BIOS calls instantly recognizeable in a disassembly. The process of fetching the second call number involves some stack manipulation and makes things a little slower than a jump table, but I think the advantages outweigh this.
We only have so many interrupts to go around in the 6502, and applications need to be able to use them, too. The NMI handler is strictly reserved for the ROM monitor, but the IRQ line is shared by a number of interrupt-generating devices and software interrupts, all of which applications may want to use. Therefore, the BIOS keeps track of which devices it is expecting an IRQ from, and passes any unhandled IRQs through to the Application IRQ vector. The same is done with unhandled BRKs and the Application BRK vector.
Having a one-byte argument for the call number gives us up to 256 possible BIOS calls. Frankly, the BIOS isn't going to need nearly that many, and I will probably want to use the same technique for any operating system written for the System/65. Therefore, only the high 128 call numbers will be used for the BIOS; since the N flag in the status register is set based on the high bit of a load operation, we can simply use a BMI when loading the call number to see if this BRK needs to be handled by the BIOS or passed on to the application.
Call $FF is reserved for software invocation of the monitor. All the call numbers between the last implemented BIOS call and call $FF are set to the Unimplemented call (properly $FE,) which normally is passed through to the application, but can be set to invoke the monitor instead.
The full list of BIOS calls can be found on the BIOS Calls page.
On boot-up, the BIOS performs a RAM check to make sure that memory is working correctly. After this, it resets the banking control register to 0 and checks the two disk drives to see if a disk is present in either. If a disk is found, the BIOS loads the first sector into $0200-03FF and transfers control to the routine at $0200. If no disk is found, the BIOS transfers control to the ROM monitor.
(Odds are that performing a RAM test on a small static RAM is basically unnecessary these days. Consider it a ritual of remembrance for the time when one couldn't automatically assume that something as basic as system memory would function without error.)
In addition to the BIOS, the System/65 ROM will contain a machine-language monitor that is invoked when the system boots with no disks in the drives, when a BRK $FF BIOS call is executed, or when an NMI is generated. (The only devices that can generate an NMI are the NMI button on the front panel, the NMI header on the board, which is connected to the interrupt line of the MicroVGA board, and the NMI line on the expansion port. The NMI is solely reserved for invocation of the monitor.)
The monitor will be fairly simple; while it will support some basic niceties like relative-branch calculations, it's mostly going to be a straight-up assembler/disassembler with no macro-assembler features. It will communicate via the BIOS, and can load data into memory from either the disk drives or one of the serial ports, as well as being able to boot from disk. In addition to standard 6502 monitor features, it will support a second-byte argument for the BRK instruction, in order to make BIOS calls simple to enter.
The BIOS and monitor reserve certain areas of memory for private use. This memory should be considered off-limits for application use:
|$00:000-1FF||BIOS zero page and stack|
|$BFE0-BFFF||Reserved common RAM|