C64 OS TECHNICAL DOCS
Last modified: Sep 04, 2018
As was discussed above about what an operating system is, the C64 KERNAL does lots of things. Not all of what the KERNAL does however is intended to be accessed directly by user programs. Because the C64 has no memory protection and the entire address space is always open for any program to access, and because the KERNAL rom has been thoroughly documented, many programs do in fact jump directly into the middle of the KERNAL by using a known absolute memory address. This is frequently done for good reasons, but it can also lead to problems.
Given that the C64 is a product that has essentially been locked in its current form since the late 80s early 90s it doesn't feel like a terrible idea. But from the perspective of a developing platform direct jumps into the KERNAL is a horrible idea. As long as a wide range of software freely jumps into the KERNAL, the KERNAL cannot be changed without risk of breaking all that software. Even as it stands, with the C64 being a product that hasn't commercially changed in over 25 years, jumping straight into the KERNAL is still not recommended. Different models of C64 come with different versions of the KERNAL, so your program might work on the C64 you test it on and may mysteriously break on earlier or later models you haven't tested it on. And there are KERNAL replacements such as JiffyDOS that are more modern, some are still under active development, and have differences that will lead to incompatibilities.
Commodore's recommended programming use of the KERNAL was to use the Jump Table. The C64 Programmers Reference Guide, for example, documents the Jump Table with the expected inputs, expected outputs, and affected registers of each routine. What it does not do is tell you all about the internal implementation of the KERNAL so you can hop anywhere into it at your leisure. The Jump Table exposes set of routines that was relatively stable across their entire 8-bit line. Wikipedia has this to say about the KERNAL:KERNAL is Commodore's name for the ROM-resident operating system core in its 8-bit home computers; from the original PET of 1977, followed by the extended but strongly related versions used in its successors: the VIC-20, Commodore 64, Plus/4, C16, and C128. —https://en.wikipedia.org/wiki/KERNAL
Extended, but strongly related. However, the internal implementation of the KERNALs were able to vary in arbitrary ways. The Jump Table is a set of byte triplets that come at the very end of the ROM. Each triplet consists of either an absolute JMP or an indirect JMP instruction, followed by a 2-byte address. The positions of the JMPs in the Jump Table are stable, but where exactly they point to in the ROM, or in system RAM in the case of the indirect jumps, can vary from version to version. In theory, if a program makes use of the Jump Table, along with PETSCII control characters to interact with the Screen Editor, a program written for a PET should run on a C64, or a VIC 20 or a c128. This theory works, up until the point where the program makes a direct access to the registers of a chip that only exists in one or the other machine.
C64 OS operates much the way the KERNAL rom operates. It's like an extended KERNAL. The C64 KERNAL is in ROM at $E000 to $FFFF. The 6502/6510 has hardware vectors for NMI, RESET and IRQ in the final 6 bytes, $FFFA-$FFFB, $FFFC-$FFFD, $FFFE-$FFFF It makes sense for the C64 KERNAL to sit across these hardware vectors so it can handle them as they occur. Below those vectors are 4 bytes RRBY that don't seem to have any functional role, according to people on IRC they're the initials of some of the developers.
However, besides these exceptional 10 bytes, the 117 bytes below that are for 39 routines. It makes a lot of sense for the Jump Table to come at the end of the ROM as well. If they ever wanted to add a few more routines, they would be added below the lowest entry, and addresses would be shifted around in the others according to how the routines had to be moved about to find space to implement them.
Below $E000 sits the C64's 4K I/O area, $D000 to $DFFFF. Below this is typically a free 4K area called himem from $C000 to $CFFF. Below this is typically the BASIC rom in the 8K area from $A000 to $BFFF. C64 OS patches out the BASIC rom, and loads itself into memory such that it ends at $CFFF. Given that there are no special hardware vectors at $CFFE-$CFFF, etc, the C64 OS Jump Table begins right at $CFFF, 3-bytes per routine, working down in memory. Just like the KERNAL rom.
C64 OS divides its code up into modules. Each module consists of one source code file (*.a), one or more include files for constants and macros (*.s), and one jump table header (*.h). You can read more about this here.
There are currently nine modules, with a tenth planned:
- toolkit, and
There is too much code across all the C64 OS modules to combine them into a single file. However, once the code is split into multiple source files, it becomes difficult for the assembly process of one module to know the absolute addresses of routines in the other modules. For the module which contains the Jump Table, it is difficult for it to know where the routines are to jump to.
The solution is outlined in a couple of blog posts about it. Modules are statically laid out in memory, such that the they pack together in memory. During development, the modules are each assigned a region of memory larger than they need so that code additions or bug fixes don't force all subsequent modules to be relocated. The static layout is done by measuring the size of the assembled object files, and then giving them fixed, known load addresses such that they'll layout side–by–side without overlapping or leaving any gaps. During development 10% to 20% may be added to the object file size to account for future development.
The Jump Table's entries consists of indirect JMPs to the start of the module, plus some offset in 2 byte increments. At the start of each module is not a jump table, but a table of addresses where the routines are found within that source code file.
So the first several lines of code in samplemodule1 consists of what is called an exports table, like this:
*=$c105 ;Some precalculated address ;***** Exports ***** .word routine1 .word routine2 .word routine3 :*******************
In the exports table, routine1, routine2, routine3, etc are local labels. Thus the addresses in the exports table of a module are resolved at the time the module is assembled, and may change each time the module is assembled as the length and layout of the routines within the module move about as the module is developed.
The C64 OS Jump Table is assembled at the end of the memory module. When this module is assembled, its entries for the samplemodule1's exports are based on its known starting address, like this:
samplemodule1 = $c105 jmp (samplemodule1+0) jmp (samplemodule1+2) jmp (samplemodule1+4)
It is not necessary to name the routines in the Jump Table. It is only necessary that the Jump Table contains as many entries for a module as that module has exports. The next piece that makes this module system work is the jump table header file provided by each module. Samplemodule1, since it exports 3 routines, will have an includable header file that lists the names of these three routines, and their offsets within the Jump Table. Like this:
samplemodulebase = $d000 - (10 * 3) routine1 = samplemodulebase+0 routine2 = samplemodulebase+3 routine3 = samplemodulebase+6
The Jump Table header file for samplemodule1 is included by any other module, even the memory module for consistency's sake, that needs to access any of its three exported routines. In the importing module, then, routine1, routine2 and routine3 become defined labels. But they're defined as the addresses of the entries in the C64 OS Jump Table, not directly to the absolute addresses of those routines.
In the jump table header file, the "base" address starts at $d000 and works down 3 bytes per Jump Table entry until the start of the jump table entries for this module. This could change between release versions of C64 OS. But, the goal is to make it as stable as possible. Although it might help to put in some bogus placeholders to routines that are planned for the future.
The above was using a sample module and some fake routine names to discuss how it works in the abstract. Now let's look at the actual modules and their Jump Table routines as they are in C64 OS at the time of this writing.
NOTE: This Jump Table is not complete, because C64 OS is still in progress. New routines are likely to be added before the v1.0 release. The network module has no representation at all because its development has not yet begun.
The Jump Table begins with a set of labels called the Component Linker Table. This is just a block of labels that represent the starting addresses of each of the modules. If some module in the middle, say menu, doubles in size and overflows the space that has been allocated to it, its own start address will have to be changed and that will displace all the modules below it. Service, file and toolkit would have to be moved down in memory as well to make room for the much larger menu module.
Displaced modules have to have their start address adjusted in their source file, and then have to be reassembled. Their new start addresses have to be updated in the Component Linker Table, and the memory module—which is the home of the Jump Table—has to be reassembled. All other modules that make use of routines in those relocated modules can be left as is, because they access the routines via the Jump Table.
The entries in the Jump Table for the memory routines use local labels. That is because the Jump Table is situated at the bottom of the same source code file that implements those memory routines.
The sections below go into detail about the routines provided by each module and how they are to be used, and how they are intended to work together when building a C64 OS application.