 It's been a while since I've done any long form programming, so let's write an operating system. What I'm going to do is attempt to port CPM to the 6.02. This should be a pleasantly futile task because there is no CPM code that will actually run on the 6.02 processor, so this should be interesting. CPM is a very old operating system from 1975 It was originally intended to run on 8080 systems, although it's been ported to a bunch of others including, as you can see here, the 8086, 68000, and the Z8000, which almost never saw light of day. It was originally intended to run on teletypes and has only got, you know, basic support for things like real monitors. It's an absolute masterpiece of minimalism and will provide a usable disk operating system with a easy-to-use command line on a machine with less than 64k. I think the smallest 8080 versions would run in 16, possibly less, although realistically you need the full 64k to do anything useful with it. I would like this in order to provide a reasonably portable operating system. I can run in a variety of 6.02 machines. I've got some compiler stuff I want to do with it, and trying to port the compiler to whatever half-assed operating system any given 8-bit machine is going to run is just a pain, particularly when you get to stuff like the Commodore 64, where the DOS is quite special in many ways, such as not having the ability to seek within ordinary files, which makes life awkward. However, the CPM file system is surprisingly advanced for the era. It supports arbitrary fragmentation files of up to, I believe, 32 megabytes sparse files, where you can have holes that aren't allocated on disk, things like that, and it's just generally better. The one thing it doesn't have is subdirectories, but luckily most of these systems didn't do subdirectories either. So, as I said, I wanted this for the Commodore 64, but I'm actually going to be targeting the BBC Micro first. The main reason is that I've got this rather nice emulator that comes complete with a debugger, and the BBC Micro is a surprisingly advanced operating system for an 8-bit system, complete with pluggable operating systems. As you can see, this one, we are running virtual DFS, which is a pluggable file system that understands the emulator. So, if I look at the directory, we see a single boot file. This is actually mapped to this host directory on the Linux system I'm doing the development on. So, I can do start info boot, and we see we have a file called boot, which is readable and writable by owner, read only to others, load address 400, execute address 400, length 33 hex bytes, and that's actually a symbolic link, but the actual file is here, and of course, 33 hex is 51 bytes. So, I can just write stuff to this directory, and it will immediately show up in here. Let me try that. Like so, there is our new F file, which is empty. And over here, it has gone. So, this will be not just this, but various other features will make this a much easier system to target for the main development than a real Commodore 64. We'll tackle this later if and when. So, the CPM was originally designed for the 8080, and was mostly then run on subsequent Z80s that were backwards compatible with the 8080. And the 8080 and the 6502 are rather different beasts. So, we're going to have to make some changes to the way CPM works to make it run on a 6502 at all. So, let me make a few notes here. So, the original 8080 memory map at the bottom was zero where the vectors lived, and the top was FFFF, of course, 16-bit system. When CPM was loaded, the executable gets loaded at 0100. The CPM operating system itself sits up at the top of memory somewhere, let's say 800. This can actually change. BDOS is the operating system kernel. The BDOS is 3.5K, which is E00. So, immediately above the BDOS would come the BIOS, and the BIOS would occupy everything up to the top of memory. Just below the BIOS was a thing called the CCP, the command processor. That was 2K. So, that would be loaded at 800. So, your application goes from 0100 to D800 in this configuration. All these addresses are static. They are assigned when the system is built. You construct a CPM image that contains the command processor, the BDOS kernel, and the BIOS, which contains all the platform-specific stuff, all the way up to probably the top of memory, and you stick that on disk. And when the system starts, it simply loads all of this slot off disk into memory and then jumps to the BIOS start vector. And that is how CPM starts up. I will add that the TPA address here 0100, it can change. However, CPM binaries are not relocatable. So, if you had a system that used a different address, you wouldn't be able to run binaries belonging to any other system. So, no one ever did that. Now, the 6 of 02 works a bit differently. It's got a rather more complicated memory map. At the bottom, you've got a thing called zero page. At 0100, you've got the stack. These are fixed addresses. 0200 is where the OS stuff starts, and typically somewhere at the top of memory, maybe about here, for example, we have the various IO ports. Zero page is special because the 6 of 02 requires the use of variables in zero page to get certain things done, and zero page is much cheaper to access anyway. So, for example, the LDA instruction loads a value from memory into the A register. So, LDA address is three bytes. One for the instruction, two for the address. LDA 0P address is two bytes. There's a special form of LDA for doing this. And it's, I think, a couple of cycles faster as well. And there are things that you can only do in zero page, such as pointers, offset reads and writes. This then gets combined with the fact that the system that you have to run your stuff on typically allocates things in fixed addresses, which we have to work around, because we want to run this in systems that were not designed for CPM. So, for example, on the Commodore 64, screen memory starts 0400. Basic workspace starts here. This is where your program loads. And if I remember correctly, CO00 is where the basic ROM lives. But everything from there up can be remapped, so you can replace it with RAM if you want. But you're still stuck with your screen memory down here and your stack here and your zero page here. On the BBC Micro, we've got zero page at the bottom. The stack here, this is defined by the CPU. You can't move this. We then have OS stuff all the way up to 0400. Language workspace all the way up to 0800. File system and other utility storage goes all the way up to, on a stock BBC Micro, it's usually 1900. But this will change depending on what ROMs you currently have installed and what file systems you're using. So, this is typically known as PAGE. You then have application workspace that goes up to, for example, 7C00, which is Hymem. Video memory goes up to 8000, where the application ROM lives. CO00 is where the kernel ROM lives. And this goes up to, I believe, F800, which is where the IO ports live. And then right at the very top of memory at about EO, there is more ROM where your system call entry points live. The amount of video memory you've got will change depending whether you're in high resolution, low resolution, or text mode. So, Hymem can vary. You can go all the way down to 3000 and go all the way up to 8000 if you're using no video memory, which is possible on systems like the BBC Master where you can put the video memory in separate RAM. It doesn't live in the main address space. But for simplicity, we're targeting a stock BBC Micro. In the Mode 7 text mode, the one we're going to be using for a lot of this because it occupies almost no RAM, Hymem will be 7C00. PAGE also will vary depending on what file systems you've got. If you don't have a disk system, and you're just running off cassette, PAGE is all the way down here. If you have a disk system, it'll typically be here. If you have a disk and network file system, it'll be here. Which of course means that if you're in the highest resolution text, the highest resolution graphics mode, which occupies a whole 20K or video memory, you've got what 6K, 5.25K of workspace, which isn't so hot. So clearly, if we want this thing to work on a system where the workspace starts at 0800, and where our workspace starts at probably 1,900, but this can vary a lot, we are going to have to make our entire system relocatable. Unlike CPM on the 8080, where everything loads a single address, the CPM operating system itself can move but is always at a fixed address on any given system. None of that is true on the BBC Micro. It's a little bit true on the Commodore 64. And then we get zero PAGE. Because of course, zero PAGE is going to be used by both us, that is CPM, plus the application we want to run, plus the operating system, which we'll be sitting in the background most of the time. So yes, it's just more complicated. I have in fact defined a format for relocatable executables, and done a simple tool makes them. We're going to be using CC65 and LD65 to do all our assembly and linking. We create the relocatable files through the very old trick of assembling, well linking our program three times, add different addresses, and then seeing what bytes have changed. Because we know that any byte that changes is going to be where an address is stored. Therefore, we store the location of the address in the relocation table, and it gives us a really simple algorithm for doing relocations. We just add a constant value to all these bytes in the file after it loads and everything is fine. So how does CPM work? Well, I mentioned these three bits here. Starting from the application view of things, the application makes BDOS calls to get stuff done. These are system calls into CPM that do various things, and this is a list of all the system calls. There aren't really very many. CPM is a very minimalist system. So if we've got stuff like terminate program, read and write a chart from the console, read and write to the auxiliary port or the printer, change device redirections. We're going to talk about these later. A few things like writing a string, although for some reason it uses a dollar sign as the string terminator, which isn't really very helpful. Reading a line of text from the system with basic command line editing. And then we get on to the bulk of it, which is all of this, which is the file system interface. Because that is basically all that CPM gives you, a file system. You can open and close files, you can scan the directory, delete files, read and write sequential files. That is, you just keep reading records from the file until you reach the end and then it stops. Create files, rename files, some stuff to do with drive management. You can have up to 16 different drives on the system. This sets the address at which IO is done. This is not talking about hardware DMA. This is talking about the address in the application workspace where records are read and written. Some stuff to do with low level drive things. You can make drives read only so that you can't accidentally write to them. There's some stuff to do with random access files, and you can tell by the way that these are all the way down here, while these are up here, that these got added later. This does a seek into the file before reading and writing any given record. And that's about it, really. So this is the stuff we're all going to have to implement. On the original CPM, this was all implemented in the BDOS, which is this module here. This was distributed by Digital Research as a single relocatable binary blob. You could port CPM to your own system by simply relocating this to a given address, saving it to disk, writing your own BIOS, and everything worked. But, of course, we're just going to write it in source code. The BDOS gets stuff done by calling into the BIOS. And the BIOS is the platform-specific bit. And the BIOS has a smaller number of system calls. Doing things like start the system up from scratch. This one is called whenever a program terminates, and at which point the BIOS then reloads the CCP. We'll get onto the CCP later and return to the prompt. Console input output. Printer input output. Paper tape input output. And this gives you an idea of just how old CPM is. Stuff to do with reading and writing sectors of disk. And a couple of extra things got added on the end. So, in order to port CPM, you basically just implement these. Slap in the BDOS that you got from the Digital Research disk. Slap in the CCP, create a file system, put the whole thing on disk, and then it'll work. Which brings us to the CCP. The CCP is the command processor. It is actually just a perfectly normal CPM application. It loads the funny address. It's just underneath the BDOS rather than the normal TPA address. TPA is transient program address. This is where programs get loaded off disk. The reason why it's up here is so the CCP can actually load a program down here without overwriting itself. It's nothing more complicated than that. Because, of course, the BDOS has no facilities for running programs. It's the CCP whose job it is to load a file off disk at 010I and jump to it. That's how programs run. And the CCP provides the command prompt, the ability to load and run programs, and a small number of built-in commands which include things like arrays, rename, list the directory, type a file to the console, save, just saves a arbitrary chunk of memory to disk. User, which changes the current user number, which is a kind of Neolithic directory system that CPM has. We'll talk about that hopefully much later. And I think that might be it, actually. It might just be those six. And pressing Ctrl C at the prompt. Ctrl C is what you do whenever you change disks. And this causes the CCP to tell the BDOS that a disk is changed and it will reload things like the allocation bitmap. This doesn't really have a name, but it's definitely a very important command. So, how are we going to do this? Well, I have a tiny stub program, which I came up with earlier, which is a BBC Micro executable. And we are going to turn this into a BIOS. And the BIOS is then going to load the BDOS. And the BDOS is going to load the CCP. This is not actually how 8080 CPM does it, but we have slightly different requirements. So, let me show you the BBC Micro again. So, the BBC Micro, as I said, has a fairly sophisticated operating system. You can have applications that run out of ROM, and we are currently running this one, BASIC. We can actually get the same information from inside the OS. So, BASIC is a language ROM. Language is the acorn term for an application. You could get languages that weren't actually programming languages, like word processors. This is started up on reset and takes control of the system. During the reset process, this actually happens here. The banner BASIC here is telling you that the basic language is being invoked. And we can restart it like this, and you see we get the same banner. We don't actually care about BASIC at all. We're not going to be using it. But we are going to be writing another application. Our whole CPM port is going to run as an application under the acorn operating system. So, we want to be able to load CPM off disk, run it, and it will then take over as the currently running application. We have the executable that I came up with. We can run this in two ways. We can either use the star run command, which just runs a machine code program, or just the name. So, that's it running. It just prints two numbers and stops currently. Or I just press the reset button and it's break. Or we can do shift break, which is auto boot. And that will run the command called exclamation mark boot instead of the current language. And you can see we have the same two numbers here. So, the way the system is going to work is you stick the CPM disk in the drive of a BBC micro, you do shift break, and it will boot and load, which is very straightforward. If we look at our memory map, again, notice that there is a specific block here of language workspace. This is 1k of space, which is at a fixed address. Unlike everything from page to high mem, the application always knows the absolute address of this stuff. So, what we've done is we've made our executable load at that address so that we don't have to worry about relocating it. The BBC micro doesn't actually have features for relocation, which makes writing machine code programs for it a bit irritating. And the rock command, the acorn file system stores the load address and execution address of files so that it knows that it's supposed to load at 400 and then start execution at 400. So, what this is going to do, this program is going to contain all our IO code. It's just going to delegate to the operating system entry points. The operating system on the BBC micro is known as MOS, MOS, machine operating system, which is a very original name. And what this does is, well, we have a routine here called print H8 that just prints a hex byte to the console. Anyone who's worked with the 6502 will recognize this code, which is the standard way you do hex printing. And then we jump to Oswurch, which is the right character entry point. And that is used by this code here, except for these three or four lines. And this calls one of the other system calls Osbite to fetch the, let me fire that up, to fetch the low address of the transient workspace, which is 1900, prints it, then we fetch the high address of the transient workspace, which is 7000, 7C00, and we print that. So everything we do has to go within these bounds. And that is our starting point. But of course, this one cave space isn't big enough to put the entire operating system in. So what we're actually going to do, let me clean this up rather, is we are going to put the BDoS here, the CCP here, and the TPA above it. So the TPA will span from the top of the CCP, well, let me rephrase that slightly. So the TPA will span from the top of the BDoS all the way up to high mem. Yeah, the CCP does not go between BDoS and the TPA. The CCP goes here, just under high mem. The reason for this is exactly the same reason why 8080 CPM put the CCP up high. It's so that the CCP can load the application binary above the BDoS without overwriting itself. Now original CPM 80, it loaded all this by just reading in a image of disk stored on the first few tracks of disk into memory. We are going to have to be cleverer than that because we don't know what address page is. So we are going to need to do a relocating load of disk of the BDoS. So we are going to need a relocation routine. Okay, this is the bit where I stop talking about the stuff I've thought about in advance and start actually doing things, and the doing things involves. We are going to have to decide on the system calls. Okay, I am going to be making some simplifying assumptions here. CPM supports a bunch of devices. We've got the console, we've got the printer, which is write only, and we've got the paper tape input output. The paper tape is normally used as serial port. The printer is normally attached to an actual printer because it's write only. However, the CPM support for these devices is kind of terrible. Like for the console, you can ask it whether a key is waiting. So you can pull the console to see if the user has pressed a key. Con in here will actually just block waiting for a key. There is a list status system call, clearly got added later, for testing whether the printer is ready to accept a byte. But there's nothing like that for the paper tape. So there's no way of knowing if the paper tape is ready either to send a character or to receive a character. On systems that don't actually have paper tapes, which is nearly all of them, this was actually connected to the serial port. But without serial port status, then it's not really very useful. You can't wait on the serial port and the console at the same time. You can't write a serial terminal in CPM without doing direct hardware access. Plus, there is no facility here for doing anything like, you know, changing the serial board rate or input output format and so on. So none of these are particularly useful. So we are in fact just going to ignore these completely. Just take them out, not implement them. I do have a plan for dealing with stuff like serial ports and printers involving loadable device drivers. We will do that much later, if at all. Another thing we can get rid of is Sectran here. This was used to translate sectors. The idea is that systems back then were very slow and you would read a sector of disk, think about it and then try and read the next sector. But while you were thinking about it, the disk continued to rotate. So it's now too late to read the sector immediately after the first one on disk. And this was traditionally worked around by a thing called interleaving, where the sectors were not written in order on disk. So you try to set the interleaving so that you read sector zero, you think about it. And then when you go back to the disk, looking for sector one, sector one happens to be passing the disk head. So you can read it immediately, otherwise you have to wait for a complete disk revolution. And there's only like five a second. Sectran was a way of doing this if your disk controller didn't support interleaving. And it would translate the sector numbers in software. And it was a complete pain because the sector tables were completely arbitrary. And if you were presented with a CPM file system disk, and you didn't know what translation mapping was being used, then you were out of luck. You would just have to somehow guess to make the files make sense. Because even though the sectors on disk are labeled zero, one, two, three, if CPM is translating these into four, nine, 12, zero, then you don't know what's written where. We don't need that because all our modern systems do hardware interleaving so we can get rid of that. So this gives us this set of system calls. We can also get rid of home. All home does is set track zero, sector zero, waste of time. So this gives us these, which is reading and writing single sectors, console input output, cold start and warm start. We're going to add two more. We're going to add relocate. We load a binary into memory. We call the relocate function. The relocate function patches the binary so that all the pointers inside the binary pointing at the right address. Why is this here? You think this is common not platform specific code. This should be in the BDOS up here, which is correct. It should. But we want to use relocate for loading CPM proper. And if it's in the BDOS, we can't use it until the BDOS is loaded. So therefore we can't load the BDOS. So we are also going to use get tpa. This is going to query for the workspace in which CPM lives because this is going to vary from platform to platform and from configuration to configuration and the equivalent one for zero page because we don't know what bytes of zero page can be in use. For example, the BBC micro gives you everything in zero page from zero to 8f, I believe, for language use. However, Commodore 64 reserves zero page addresses zero and one, I believe, for doing memory mapping. So clearly we have to support both of these. So we have to relocate zero page as well. There will probably also need to be set versions for these. For example, after the BDOS is loaded, it will want to update the tpa bound so it doesn't overwrite itself. We could either do this as more entry points. Yeah, let's just do it as more entry points. And like so. Okay, so we've clearly got some variables. Let's go to our boot variables. So I have here some documentation, MOS API, there should be a thing on memory somewhere. No, that's somewhere else. Yeah, I'll go find the memory map. Here we go. So this is the zero page allocation on the BBC micro. So the language gets everything from zero to 8f. Above that, there's like stuff all the way up to the top of zero page. And it describes what they all do. Page one has got the hardware stack. Page two is MOS workspace vectors, etc. It's all the way up to page four to seven language workspace. That's the one K of space described earlier. Sound workspace, buffers, more buffers, etc, etc. And eventually we hit E00, which are allocated dynamically to sideways ROMs. These are application and service modules. So we are assuming that all our address is a page aligned, size or to use absolute values. We know upfront, because we're a language, we know we can access everything from zero to 8f. But we don't know what memory we're going to be using. So let me see. So osbite 8.3 reads the low address. So that will be sty, then the base. Osbite 8.4 reads the high address. So that will be sty, then the top. Okay, and it does not build because ca65 requires dollar signs. Okay, so there is our boot image and we can run it and it won't do anything. It will just hang. It's actually done it. It's just it then does nothing. So now that we figured out where the TPA is, we actually want to load the BDOS image. So we wish to load a file. This is a file of the MOS file system, not the CPM file system. So there is actually a system called called osfile, which is at ffdd, which does simple file loads and saves. There is another API for doing random access, which we're also going to be using. But for now, let's just load the BDOS. And the way this works is you point it at a control block. You give the address in the Y and X registers. C502 only has three registers. And that describes what it's going to do. So let's make our control block. So here's the layout. So we've got a pointer to the file name. This is going to be called BDOS. And this should end in a carriage return, if I remember correctly. Not a zero. Is there a doesn't actually say okay, followed by a 32 bit load address followed by I think the rest will get filled out when we do the load. If I have load file into memory, if the load if the low byte of the execution address is zero. Okay, so we do want to put the execution address in start address. I think also wants to be wait, oh, it's start address. Oh, sorry, I'm looking at the wrong column. Right, this is filled out with the length. This block is used for both reads and writes. It's been about 30 years since I last used Moss for anything in particular or attributes. Also, why don't I put these here? These go here 012. So that's four bytes, 6, 7, 8, 9, 8, another four bytes, A, B, C, D, E, four bytes, E, F, 0, 1. Okay, so in order to load the BDOS, we actually want to take the base address and store it in 0123. So we update the load address with where we actually want this thing to be loaded. We then call osfile with a function code of ff for load file. x wants to be the low address, y wants to be the high address of the block, and call. Right, on return, load is the only call that generates an error. An error is a Moss thing. It's basically an exception. This will break to the system indicating that something's gone wrong. So we should be able to save that 5.1. Sorry, I'm just fixing my tabs. Okay, so that assembles. Here is our binary. Let's run it and see what happens. Okay, so it's tried to load the file called BDOS. It hasn't found it, so it's failed with an error. And because there is no language, there is nothing to process the error, so it just holds. If we go to basic and we run our program, we get dropped back to the basic prompt after the error. And not found is a dead confusing error message because it looks like it hasn't found Pling boot. So I think we should probably display a banner. So this is going to, you give it the address of a string and it will print it to the screen. So this is a site aside from what we were doing here, but we are actually going to have to do this. So now remember I was saying that pointers can only live in zero page. We're going to have to use our first zero page value. And these are incredibly precious. We're going to want to reuse this as much as possible. And in fact, because we have used that, we are going to need to update this. Let's do a print thing first. So store the low byte into pointer plus zero, put low byte of the address, the high byte of the address into pointer plus one, begin a loop, ldy zero, this is going to be our index. So we are going to load the byte at, we are going to add y to the value of the pointer and load that. We are going to, if this is zero, exit. If it is a dollar sign, exit. Otherwise, write it, increase y, jump back to loop. And set p is not a recognized control command. What is zero page stuff called? Zero page. Okay. Right. We need to adjust this. Well, it so happens that some of the stuff I did earlier, zero page size is initialized to the initialized by the linker to be the size of your zero page area. So if we import, actually, I think end is more appropriate here. Is that going to work? We need to update the linker file. Define equals yes here is the thing that causes the symbols to be created. Let's get the documentation. Oh, right. There isn't an end. I'm going to need load and size for this. Okay. So now if we look at our boot image, so down the bottom here, we've got our BDOS file name. We've got the control block os file. Here is our file name. So here we have 0090 for the start and end of the zero page block. Once we want to load, better. We have using two bytes zero page that starts with a two. Okay, let's make a banner. So that will be low word of banner. High word of banner print. Our banner is here. So we start BM. Right. So it prints the banner. In fact, there's no point putting the BBC micro bit because we know it's a BBC micro. It says so right up there and also this is a BBC micro emulator. But you notice that the 10 has caused a line feed but not a carriage return. So we could get around that by putting a 13 here for a CRLF but we're actually going to do it a different way, which is we're going to find a different entry point. We're going to use Osaski. Osaski, it just calls Osruch except if it's the that's interesting. I thought this was going to be OA 10, that's line feed, but it's not. It's 13 carriage return. So we're going to have to implement this ourselves, which is a shame thing actually. Actually, I think we don't want to do that. So what I was thinking was that we would translate 10 to translate a line feed to a carriage return and line feed. But actually, I think that's the BDOS's job, not the BIOS. And this is the BIOS. So let's just do that. Okay. Anyway, right. We have a banner and it is trying to load the BDOS. So we now can't get anywhere because there is no BDOS. So let us make one. Let's make a file for the BDOS and add it to the make file. Okay. So that has created a BDOS, which is one byte long. We wish to put that in our BBC directory, which we're going to do using a symbolic link like so. So now when we try and boot it, it has successfully loaded the BDOS and is then hung because we haven't told it to do anything. We are going places. The next thing that needs to happen is we need to relocate the BDOS. So the first thing is to store the pointer to the start. We are going to need the pointer twice because we need to do two relocation passes once for the zero page stuff and once for the actual, the memory addresses. So we could put it somewhere. We've got quite a lot of scratch space we can use. But instead, I am just going to push stuff onto this back. Okay. Actually, we are going to need two pointers. So we need the pointer, that pointer to the thing we're currently relocating and a pointer to into the relocation buffer. We want to reuse these as much as possible. So if we look at our notes, actually, actually, we're going to stick some stuff in here. This header file contains all our CPM 65 things. So the size of the zero page areas in the first byte and the offset in the file to the relocation table is in the word address one. So we want to get this. We want to get this and add it to pointer one here. So this is fetching the low bytes of the offset. We want to add the value here and store this in pointer two and do the same thing for the high byte. So pointer two is now pointing at the relocation table. Pointer one is pointing at the beginning of the binary. So now we are going to loop around processing all zero page stuff. So the way this works is we get a relocation byte. If the byte is zero, we exit loop. If the byte is ff, then we want to so the way the way this is going to work is that the relocation byte signifies the offset from the current relocation or the beginning of the file to the next one. So at this point, we actually want to add that to the relocation pointer. So that will be add to pointer one plus zero branch if carried to the next ink pointer to pointer one plus one. So this will add an 8-bit value to the address. This will use use up a so we are going to push and pop around it to preserve a so that we can then say is this 0x ff. If it is, then don't actually do a relocation. Instead, we wish to fetch the next byte and continue. We can't actually jump here. Just a quick aside, after reading the relocation byte, we actually want to advance pointer two. Now we've got the instruction set here where it is increment zero page. I'm just looking to see what flags are set by ink and it does indeed set z. So we can say increment the low byte. If it is not zero, we're done. If it is zero, then pointer two, then the low byte has just wrapped around and therefore we need to increment the high byte. So this adds one to a 16-bit pointer. So the thing is, here it would be very easy to say was it ff? Oh, it was ff. Go back to the beginning of the loop. The problem is that if a relocation offset is exactly ff, then the next byte, then this will be stored as an ff followed by a zero, at which point it will hit this. And in fact, that is not going to work anymore because we no longer have the z flag set from here. So we have to compare a against zero and then branch. Yeah, we actually have been a bit too clever here. So each byte represents the interval between relocations. If the interval is too big to be represented as a byte, we're going to represent it as two bytes. That is an ff and then another relocation byte. But zero is our terminator and we don't want to get it confused with ff followed by a zero. So is there another way we can do termination? I actually think there is. So let's make let's make fe our continuation and ff our terminator. Yes, that will do fine. It will occupy no extra space. So I need to go and modify my modify this. So if the alt is greater than fe, okay, our terminator wants to be an ff. So dollar sign, pointer two is undefined. Okay. So here is our boot image, which is still pretty small. We do want to make our bidos. We want to add a header to our bidos followed by code that does nothing. Please have dots on. Okay. So here is our bidos, which contains a header. We're using no zero page bytes. Our relocation offset is nine. And then we have the two jump instructions that go in the header followed by the relocation table, which consists of an empty zero page relocation table and a single offset of 8012345678. So this is this jump instruction here. So that will be fixed up to be whatever page for the high byte and six as the low byte. But we actually want to do the fix up. So pointer two, pointer one is now pointing at the address to fix up. So in fact, we haven't touched y. y can be zero for the entire lifetime of this loop. Six to indexes, you can only do pointer dereferences via y. And you have to use register y for this. With the 65 co2, they actually added a word an instruction variant that didn't have the y register, which is really useful. So get byte. This wants to be zp base, like so. And jump back to zp loop. And then we'll want to do this again for the memory relocations. In fact, this is all going to be exactly the same code, except for this instruction here. Can we do better than this? Probably could. Yes, we can. So this will relocate to zero page. We can then call it again. We have to reset pointer one, put that back at the beginning of the image. And then we can call it again to do the memory stuff. But we have to change this address here. In fact, we're not using the x register anywhere. How do we add x? Well, we kind of can't. So we have to rearrange things. So we put x into the aid register. And then we add the pointer. So can we get rid of this jump? I think we don't. We can't, rather, as we have to, we always have to go, we always have to do this first. We need to read the byte and increment the pointer before we do the comparison. Again, 65 co2 has a bra instruction, which is a short jump. This is three bytes. That'll do. So relocate to zero page. Now we want to reset pointer one, because we stored all this stuff onto the stack. So PLA, STA, this was x, which was the high byte. So this is going to be 0.1 plus one, this is 0.1 plus zero, LZX, Membase, JSR, JMP, relocate, loop. Okay, and that will do our relocation. So after loading the image, we want to relocate the image. The address is in AX, A is zero, X is Membase. So relocate. We now want to jump to the BDOS, which is actually a little bit trickier than it looks, because we don't want to jump to the beginning of it. We want to jump to just a little bit in, because it's an executable header. In fact, we are going to diffuse light changes. So bear header is going to contain just the three bytes needed to relocate. Com header is going to contain what's needed for a natural com file, which will be the bear header followed by a jump to the BDOS. So I then need to edit this to be a com header. And I need to edit this to be a bear header. All right. So here is our BDOS image, which contains the three bytes of relocation of three bytes of header, then a jump instruction for CO300, then the three bytes of relocation data. So we actually want to jump to the address plus three. We put the BIOS here. So the 6502 is actually a little bit short on indirect jumps. There are no indirect JSRs at all. There is a indirect, a single indirect jump takes a 16-bit address to a pointer. This is the only place where a pointer can be 16 bits, which is kind of weird. So, oh, but of course, because the BDOS is always loaded at a page boundary, then the entry point is going to be a nice fixed three. So that computes our start address. And then we call the BDOS. Now, this is actually going to be a new entry point, which original CPM didn't have. Original CPM didn't actually initialize the BDOS at all from the BIOS. The BIOS would call the CCP. And the CCP would call the BDOS. But we're going to actually have to do something a bit more than that, because we have to tell the BDOS where the BIOS entry point table is, which is going to be here. So low address, high address, and jump. And the BDOS is then going to call here to get stuff done. Okay, let's see if this works. It assembles, which is a good start. Okay, so before we actually do anything, let's pull up the debugger. The debugger is down here in this console window. So if I is produced this window here, this window describes the memory use. So it's zeroes the top here and 64K is down here. So when I press the return key, you can see the blues is code execution. So you're seeing the kernel in the bottom chunk here executing. And the basic language that's doing the prompt up here. The reason why this remains blue is interrupts and so on doing things. Up here, red and green, this is the system workspace just idling. And the red streaks you can see moving along here is writing to video memory. So if I switch to a high resolution mode, that big yellow slab was it clearing video memory. So mode zero is the biggest 20K. Mode seven is the smallest 1K. We also have a mode three, which is a bit smaller. It's only 16K. And a mode six, which I believe is 10K. We're staying with mode seven. Okay, let's break again. And just set a break point at 400. Okay. And continue. And boot. All right, we've hit our break point. We are here. So let me figure out how this works. These are all very slightly different. S is depth, D is disassemble. So this is the machine code we've written. Here, four, two, B, this looks like the call to relocate. Four, two, B, go. Okay, we should have loaded our BDOS binary and now we're about to go into relocation. Our, hang on a second. Okay, here are our registers. Right. The pointer to the beginning of the binary is 1900. So let's go and see what happens. So we stash it into zero page in zero and one, push onto the stack, add on the relocation table offset. Okay. So I ain't right. So this is the memory dump. Here is our start pointer. This is supposed to be the relocation pointer and that's wrong. That should not be an FF. That should be something else. Here is our BDOS. Right. That is not what we were expecting. So reset this thing. So the BDOS, nine bytes. That ODFF is an empty basic program because we've just come from basic. So I think we failed to load the BDOS in the right place. So where's our OS file documentation? FF. Load a file into memory. If the low byte of the execution, if the low byte of the execution is zero, the file is loaded at the correct load address. Otherwise it's loaded at its own load address. So these should be zeros. So we should just be able to do shift break and go again. Continue. Okay. We now go into relocate step. Okay. We should have done our pointer computation. Okay. That's better. If we dump at 1-9-0-0, we can see here is our BDOS image, which is tiny. Up here we've got start address relocation table and 1-9-0-6-0-1-2-3-4-5-6 because the zero page relocation table is empty. Okay. So we load the zero page base into X4 and then we start the relocation loop. So we get the relocation byte into A, which is an FF. We add one to the pointer. It did not roll over, so we don't need to add to the upper byte. Was it an FF? It was. So we jump to 8D and return. Excellent. And if we look at our addresses, we can see that this has advanced to the next byte. This hasn't changed at all. So now we want to pull the old code pointer off the stack, which it won't change. Like so. Load the memory base address into X, which is 1-9, and relocate again. So get a relocation byte, which is a 5 this time. Increment the pointer. Is it an FF? No, it's not. Save A. Add the relocation byte onto the pointer, which we've done. We can now see that our pointer has not changed because we've got to write the value back. Okay, so we're here. We have not advanced the pointer correctly. So this is going to fix up the wrong address. It's going to fix up this initial zero. Let's go through it anyway to see if it works. Transfer X to A. Add Store. Okay, we can now see that this byte has been adjusted. Jump back to the top of the loop. Load a byte, increment the pointer. It was an FF, so we return. Okay, so 42E is the end of the relocation. Go again. Continue. Continue. Okay, we should have now finished our relocation, hopefully, correctly. Yes, here is the address, 1903. So we are here. Compute the address. Actually, we can do better than that. The other way to do indirect jumps is by pushing onto the stack and using RTS, and that is actually marginally smaller. I just need to remember what order to push. High byte, second. So they're going to do LDA3, LDA Membase, and push RTS. Okay, right, 42E. Push the address onto the stack. Load Membase, push that onto the stack. Load the BIOS address and return to the wrong address. Let's try that in the other order. Push, load RTS. Right, we are now at 1904, which is also the wrong address, because RTS has got an implicit add one. Do that again. Push, push, load, load RTS, and we are in our BDOS at the right address with a fixed up binary, and we are doing our infinite loop. That's good. That is working. Okay, so we have a relocation routine. We have loaded the BDOS. We have the basics of a BIOS. Next, I believe that I want to define storage for the BIOS, the BIOS entry point. So that's going to be the low address, high address. Let's go again. Let's go and delete, delete break points. Be clear. Be clear zero. Be clear zero. Be clear one. Okay, break at 1903. Continue. We're at 1903. We are storing these values, and D, and then we go on to our infinite loop. That actually all looks like it's working, which is nice. Okay, so we have a, here's our BIOS, we are, we have a way for the BDOS to call to the BIOS to get stuff done. However, however, so the way, the way CPM 80 does BDOS calls is you pass the parameters in two registers, eight-bit registers, and a function number in a third. That's fine on the 6202. We've got three registers. We can do this. The way it does BIOS calls is it just calls the address of the routine, and there's a jump table at the beginning of the BIOS that just has a series of jump instructions in order. They can do this because the BDOS is linked to the BIOS. It knows where the jump table is, so we can just call them directly. We can't do that here. The 6202 makes this kind of thing a little tricky. So if we had a jump table, let's try and call the first thing. Let's call the second entry in the jump table. We could do ldy2, lda, BIOS, py, pj, iny, lda, BIOS, py, pj, rts. This will then push the low byte, then the high byte. I bet that's the wrong way around. It's just the wrong way around. So it needs to be the high byte, then the low byte. The other way we could do it is to... So this will require a table that looks like this. The other way we can do it is to split the low and high bytes. Sorry, that's a common way of doing jump tables, but I don't think I can be bothered. I don't think it makes a difference. The other thing is it requires us to put the BIOS pointer into zero page. I think instead we actually want function numbers, so that you would actually could do ldy3, ldy is the function number, jump. Well, this should be jsr BIOS, but as there isn't a jsr BIOS, this would be something like this. So that pushes the return value onto the stack, calls this, and that does the jump. And then it becomes the BIOS's job to do something with this number. What have we got in the way of indirections? There's not a lot actually. There's this one. This allows you to indirect into a table of pointers. The problem is that the table has to be in zero page, and we don't want to do that as it's waste zero page. Yeah, you can do the same thing with these. This adds y onto the 16-bit address after it's loaded from zero page. That's what we're using here. Of course, the other thing we could do is just do this. This uses self-modifying codes to push the BIOS address into the jump instruction. It is two bytes shorter than putting a variable here. Yeah, let's do it like that. So where is our list of entry points? This will be 0, 1, 2, 3, 4, con out. Let's actually put these in here. Yes, this allows, this will actually allow us to do much faster BIOS calls. So let us print a character from our nascent bidos. So this wants to be BIOS con out. The character wants to be a queue, and we call the BIOS, and we spin. So we now need to change the BIOS itself. So this is actually going to be function. Now parameter is in xa function in y. So what we want to do here is to jump to, use a jump table. There are no index jumps. So we save xa so we can do arithmetic. In fact, we don't need to save x, we just need to save a. So this should be our dispatch function. So this is using a split jump table, and this is a trick that 6.02 is used for a while. If we were to just use a set of entry points like this, then each entry is two bytes apart. We would need to double y, load the low byte, increment y, load the high byte. However, if we split them by doing this, put all the low bytes, and then we put all the high bytes, then this means that no arithmetic is required and everything gets much easier. And that is what we are going to do. So here is our list of system calls. That's going to go here like so, and this is going to be the high bytes. And if you've noticed, we have already implemented this. Also, I now remember that our print s here is in the bios, not the bidos. So there's no point having the test for the dollar sign. Now, this will not link because we're missing so many entry points. We will implement stubs for these, and we don't need relocate. Okay, so that builds. We're going to implement con out, actually for con out. So I picked these registers carefully. The low byte of the parameter is in A and the high byte is in X. So we can actually just point con out at Osruch, and that will just work because Osruch has the right signature. And for con in, we want Osruch A is the byte red. Okay, and we need to implement Osruch FFE0. Okay, so now if we run our program, continue, that did not work. Okay, why did that not work? So we are writing to the jump instruction here. Low, high. So if I now disassemble, we can see 1913 here has got a jump into the BIOS. So we are at 1909 here. So con out is the function in Y, A is the parameter JSR1913, JMP043A, we're now in the bidos. Sorry, we're now in the BIOS. We're here. So push A, let's look at the registers, function number 4, X is garbage, A is the byte. So load the low byte of the function table, stash, load the high byte of the function table, stash, ha, continue, and there is our queue. Okay, we have made our first BIOS entry point call. How big is our BIOS? We should do some renaming. 263 bytes. So we were a quarter full. Most of that's the relocation code. And our bidos is trivial. All right, I think that's a good skeleton. So I think I will commit things and come back next time. Next time we will implement, we will implement. Next time we probably should start work on the file system. I mean, all there is is file system, to be honest. Yes, I think that's going all right.