 All right day two Let's see if we can get this BIOS completed Because we're going to need the BIOS to do anything and as soon as we start up the BDOS We're going to need to make this calls. So this is a prerequisite I've also done a whole bunch of uninteresting cleanup including renaming things a few comments and and Generally restructuring a few things Okay, so the way we're going to do this is Well, we could Call the MOS API to read and write sectors of disk This would require us to emulate an actual disk, which is perfectly possible in BM It's got robust functionality for it But it'd be so much easier not to so what we're going to do is put a CPM file system In the virtual file system In the emulator as a file that I've done that so Here you can see we have now have three files Boot which is the BIOS BDOS, which is the BDOS we did last time and CPM FS Which is a CPM file system and this is it so what we're going to do is As part of our initialization Let's do that here We want to open the file and store the file handle MOS has a decent random access file API This thing we did last time when loading the BDOS is it's non random access file API Where it loads and saves complete files So we're actually going to do first file name CPM FS 13 and We are going to call a function called Osfine which is at FCE Don't know why that is all in red. I think Vim is confused about Yeah, file type. So to use Osfine you specify a function code in a and a pointer in XY and It will open the file and return you a file handle. So this is very easy Think X is the Looks like the high byte Y is the low byte So this should give us our file handle. I don't know what it does on error It will either return an error code or throw an exception return zero if the file does not exist Okay, it'll return an error code. Well, we can't be bothered to actually handle errors. So this is going to be We're just going to store the file handle and we need to create a Variable for that So that's what we do for setup now. We're going to actually want to start trying to read and write it This means we're actually going to need to start doing some entry points Let's a Lot of these are trivial. So let's just do some of the trivial ones So we've got these four These get and set the various values For the memory window So get tpa we are going to store This is the memory base the memory base is Base LDX and yep like that. That's all it is I Get zp is similar Base Set tpa is going to be the opposite. This allows the bedoss to update The valid memory window. In fact, we're going to need to add some of that in a moment to the bedoss Okay We've done con in and con out a const is This is console status is checking to see if there is anything pending and There is a Call for that There should be somewhere in the documentation. This is so in BBC basic this is a None of this is actually what I'm looking for There is a call called in key input key which can pull the Keyboard and I was there is a reference somewhere in this website Describing what it does now here we go. Here we go In key calls osbyte 8 1 read key with time limit This waits for a character from the current input stream or tests for a key press on the keyboard We actually want to scan for any key this is Annoying I don't actually think there's a way of doing this. This is the If you pass a negative number to in key and therefore the system call that actually implements it You can test for a particular key being pressed But I don't think there is an any key number This is the one I would expect for any key, but Sorry, this is the one for any key But I am not it says it's rarely implemented Great. Okay. We're gonna have to do this the hard way and The hard way is that we actually have to implement entry con in so We are going to have to When when somebody calls console status, we're going to have to read key with time limit If a key is returned then we can't don't want to return it immediately But we want to store it for the next call to con in in the pending key variable so When somebody calls con in We load the value of pending key if it is not zero that is there is a pending key then We reset the value of pending key that should be a stx and Return the value if it is zero we call the real system call and return that So console status therefore is going to be console status is Const return status in a zero if no character is ready ff is what if one is So if pending key is not zero this means someone's previously called const and read a key so if it's Not zero we want to return ff If there is no pending key then we actually want to call the system call Which is Osbyte 8 1 The argument part in x y is sine 16 value, which is the timeout A is the function code If the carry is set then there is no key so if carry set to know So if we get here if carry is clear then x is the character. We've just read So we want to store that in pending key and then fall through to the Yes code So that's an annoying piece of code. How big is our BIOS? 328 bytes Remember we have 1k to deal with for this. I mean this won't be a problem. We've got loads of space here the BBC Micro BIOS is very small So that gives us Everything except the disk functions So the way these work Is In order to read and write from the disk you call set track set sector set DMA And then one of read or write All these functions take a 16-bit parameter So Set tracks set sex set DMA They just store a value in memory somewhere read and write actually does the work So we can actually That now because these take 16 bit parameters that allows up to 64,000 tracks and 64,000 sectors, although the latter is somewhat unusual however in CPM 80 it's the BDOS that does the work of Translating file system block numbers into absolute sector Count from the beginning and it then splits these up into track and sector and This is both a annoying chunk of code and Not necessarily very useful The reason for not being useful is that on many systems the back end the BIOS here Actually wants to know the absolute sector number so Splitting these up just means that BIOS has to do more work putting them back together again also a CPM sector is a 128 bytes regardless of How big the actual platform sector is? For example on a PC sectors of 512 bytes on CPM and Moss Sectors are 256 bytes So you have to lie to the BIOS that you have to lie to the BDOS about how many sectors and tracks there are anyway So I think that I'm going to make an executive decision and change the API so that Header in that's better So that instead of having set track and set sec we just have a single set sec call That takes as a parameter a absolute sector number I'm kind of also thinking about the Commodore 64 here because Commodore 64 disk drives are weird the their triangular The disk images have a different number of sectors depending what track you're on They did this to try and make better use of the disk space of course as Discs are physical spinning objects The outer rings on the disk are smaller than so are bigger than the ones on the inside and therefore can store more data But it also means that there's no hope of the BDOS being able to properly calculate the track and sector numbers so we are going to implement this as so because we can't supply a 24-bit number through the Normal function parameters we're going to have to pass in a pointer and We're going for three bytes because CPM file systems support a maximum of 64,000 disk blocks and A disk block we'll talk about later can be it's usually one or two K The BDOS is then going to calculate the sector number from the block number by multiplying it and a 4k sector 4k block has got 32 CPM sectors in it So returning a 16-bit value into that we're multiplying a 16 bit value by 32 therefore the maximum can possibly be is Three bytes 24 bits 24 bits also gives us be this many sectors total which is This many Bites total which is four gigabytes to two gigabytes Yes, it's quite early in the morning. Can you tell? so we are going to want to put in a Think the syntax is 3 So we're going to want to store this so this will be Remember how to do this in loops XA is a pointer. So we're going to want to store this in one of our pointer temporaries So So load the value Store the value Decrement y We're doing this in reverse because this allows us cheap loops with decrement Decrement sets the Z and n flags n means negative so We do that so branch if positive Did I get the right? Yeah, that is correct branch if positive will branch if the value in y is Not negative. So zero one or two So first loop round we write offset to then offset one then offset zero then we stop and it should be set sec not set track So read and write To read and write we are going to call mosses random access Functions that is us goob-poob get byte put byte and As is common with a lot of moss stuff This takes a disk a control block And irritatingly this then updates all the values in the control block So we're going to have to rewrite the whole thing every time It'd be really nice if we could just poke the values directly into the control block Up here and not have to worry about this, but we're going to have to do this so this can be in the SS and It wants to be OD long Okay, so up here in read and write we are going to want to Firstly clear the control block Just to make sure there is nothing in it. There's actually a few bytes that won't change, but let's just be careful One you want one less than the value a with zero Store a zero into the block decrement a Loop return you do this a lot on the 6502 so first thing Clear the control block actually actually After clearing the control block we then want to write all our disk parameters into it But as these are the same for read and write we can do this Down here in a piece of common code so Load the file handle store it in offset zero load the the DMA address low byte store that at offset one high byte set to Count is always going to be 128 bytes That is one CPM sector that goes in Five Offset is going to be our logical sector number shifted left because of The size of CPM sectors, so this is going to be Slightly exciting. Let's see how to do this so High byte Do you want to hi? By So we have our sector number is Like that thinking of 32 bit values. We actually want to shift this so it's basically We want to shift left by seven bits But this is equivalent to shifting right shifting left by eight bits and then shifting right by one bit So we actually do want to start at the bottom This wants to go in nine Like so We're going to turn this into a loop in a moment, but we now want to shift everything so that will be We do want to start shifting from the top so That will be that will be or is this rotate right through carry it is rotate right through carry So we shift the top byte right by one That shifts the bottom bit Into the carry that then allows us to shift the the medium bite right by one are the the bit that got shifted out in the previous bite get shifted in and It also shifts another bite out. So that will be That's nine Plus eight each of these are three bites so we can actually Optimize into our usual Loop so this will be that's why dy branch if positive to the previous Label likewise here We actually have it doesn't matter what order we do this. This is just a copy This has to be in the right order. So we're going to have to count down because the other question is Raw doesn't let you index of Y, but we'll let you index of X. So we're going to do that Okay So this should have initialized our control block ready to actually do the work So we want to Read bytes using the file pointer The a mosque maintains a current file pointer in the usual way most operating systems do You can specify a pointer if you want and Depending what call you use it will either honor it or use the internal one so read bikes using pointer is three do the call and That's basically all we need to do so this Returns Zero for okay one for an error ff if the media changed, but we can't detect ff Mos returns an error in the carry flag So we're just going to reset a Rotate the carry into a this will give us a zero or one and Return and we need to the address of Good pub ffd one Okay And likewise for right. This is exactly the same code except We are doing a right with pointer Which is a one and as this code is all common We're going to do that. So I think that is all our BIOS entry points and We're not using our print H8 routine anymore. So you can delete it I'm going to leave it in for the time being Because I'm sure we're going to be needing to do debugging particularly if this stuff yikes Okay, so how big is our BIOS now? 419 bytes Which is not a lot Actually, it's using a bit more because we've got Variables in bss Did I remember to enable the map? I did not remember to enable the map So this is going to be Okay, so we should now have a we should have a BIOS stop map No, that should be image. So this this is created a map file that describes the layout of this particular module and this lets us see that Our bss size BSS ends at 5b 8 So we are still slightly under half full Because this our memory area goes from 400 to 800 Okay, well Let's just run it. I don't expect anything to happen bad name Ooh, see this is why we test things This suggests that our call to osfind is herring an error. That would be this and That will be because I probably got the XY X is the high byte. Okay, I did get that right Cpmfs file name is yeah, that's a file name and we are calling osfind Cpmfs is there. That's in lowercase, but Most file systems are not case sensitive So what doesn't it like about this? Does that need to be zero terminated? I don't believe so. I think Mars is pretty consistent. It doesn't actually say I think it's pretty consistent about wanting Carriage return terminated File names I do have a notice that this is storing file name in the other direction so I Have a feeling this ought to be yx not xy. Let's just try flipping these And seeing what happens Yep, that works. Okay, that's a bug in the documentation I Should probably Fix that Anyway, that does seem to be working So let us commit it and let's get on with the bedoss This is where all the actual logics are going to be so there's some stuff we need to do normal CPM 80 There is no bedoss configuration. It just when it's loaded off disk. It's pre-configured We are going to have to do some We're only only ever going to be using the initialization code once on system startup which again is unlike real CPM So it'd be nice to be able to get rid of Initialization code So one of the things we're going to have to do on startup is tell the BIOS How much memory we're using and we have Because the the BIOS doesn't know So I thought we had some code that did this to be honest Yeah, this stuff So this is going to be Size and We also want We want to know how big our complete memory allocation is Of course, we've been fixed up so So at the end of the file we want to align to a page boundary So now we can do end minus start to figure out How much how big the entire bedoss is but first let's do the zero page So this gives us the zero page bounds in a and x add on how much zero page we're using to the base and write it back and likewise we do the same thing with the TPA this is all in page sizes so We add on the number of pages that the bedoss is using and write it back and That doesn't work. Why doesn't that work? It doesn't work because zero page size is a 16 bit value We want the low byte of it actually does that work? not conveniently That should be one of these and this will The same thing applies here Segment of bss isn't aligned properly the resulting executable Does this work in the the final address of the segment? It expects the segment to be aligned to a multiple of the alignment That's not helping. That's not how a line should work So what a line normally does it inserts bytes until the code pointer is correctly aligned but of course I Don't think this assembler can do that due to the way segments work So we're going to have to do this the hard way. This is called LD 65 there we go. So if we set define equals yes in the linker script it will insert uh symbols to let us figure out the Position and size of all the segments But we can't just ask for the bss size because we want to know the size of the entire executable So I think we want I think there may be an easier way to do this can we tell the linker to insert a Symbol, I'm not sure if we can actually Yeah, I'm not sure we can So this should be the address of the bss This should be the size of the bss This should be the address of the beginning of our code So what we need to do here is This gives us the address of the end of the bss This gives us the total number of bytes from the beginning of the code to the end of the bss but we now want to Round up and this will give us the low byte So hopefully that will do it So the assembler will give us a listing But it won't show us the values because it won't know the values until actual Execution time So let's execute some break at 1 9 0 0 Continue Go that didn't break. That's because We don't actually start execution at 1 9 0 0. We start execution at 1 9 0 3 There we go That suggests an error happened already open Oh When I did shift break it didn't close the file. So I have to do control break. I Can't do control break because That the break key is mapped to f12 on my keyboard and control f12 does something in my window manager so there should be a There we go So now I can do shift break Right and we break at the right place so store the BIOS entry point pointer update the memory region Is there a step but treat a cold subroutine as one step? Okay So we now have the memory the zero page memory region in the registers Which is from 4 to 9 0 So you want to add on the number of you the amount of zero page that the BDOS is using which is currently zero and set it and Here we do memory and that yep From 1 9 the address we're currently running to 7c and we're adding on To f That's a lot See I would expect this to be one currently We want the high bite Not the low bite Go go add one and Then we write that back So this means that we can now load programs starting at two zero that is safely above the BDOS I'll also say I don't think I actually Said this out loud last time But the reason we're doing it like this and not just linking everything together statically is we want to be able We want the BDOS here and the CCP when we do that to be completely Platform independent Like you can just drop the binary in from one platform to another on the BBC micro or the C64 or whatever And it'll work So it does need to figure all this out at runtime The only bit that you should actually have to write is the BIOS and as we've seen there's very little in the BIOS Okay, so So after we've done that bit of initialization We then get you then fall through into the warm start code and This is where stuff actually Begins to happen This is CPM system call zero. This is the thing you call to exit a program and It's the job of this code to reload the CCP Because the user program may have overwritten that Reset the disk system Not in that order Do that again reset the disk system Do any initialization that needs doing on every every command line, which is not a lot Load the CCP jump to the CCP. So the first thing we're going to want to do is Reset the stack Then we are going to want to reset the disk system. Now in here. I have the An annotated disassembly of the 8080 CPM CCP and bedoss Which I'm going to be using heavily for reference Now CPM 80 doesn't work like this in CPM 80 It's the BIOS's responsibility To load the CCP in bedoss Because it's just going to lift them off the the boot sectors of the disk. We're doing it slightly differently we're going to be loading this the CCP off the actual file system So the first thing we want to do is Reset the disk system CPM doesn't have an awful lot of state so Between runs It only really maintains the current directory Well the current user rather and the current drive and that's it nothing else so in CPM 80 these are stored down in zero page which means something different on the 8080 that does on This is 6202, but here we're going to put them in the bedosses own Memory it also combines them Into one variable So what are we going to do for a reset? Well the first thing it does is clear the write protect status You can tell the bedoss not to write to particular disks This is mainly used in the case of a disk error The disk is automatically marked as write protect as write protected to avoid Corrupting the disk. We also have the login vector is a bit set of Which drives are currently in use the term is logged in it means that the directory of the the drive has been read and The bitmap computed And I just realized I actually forgot a big chunk of the BIOS, but we'll get on to that in a bit so See I'm just wondering if we want to zero initialize this on every Yeah, so Once again, we have our standard Loop you see I was hoping to be able to reuse the zero in the X register, but you can't You can't do absolute indexed So just put X in a SDA DOS Why to the loop So this will clear all this stuff to zero. We're going to put more things in here so the BDOS gets reset after every command But it's likely more complicated than that because Remember the CCP is itself a command and It's when the CCP invokes a program a transient program It is possible for that program to return back to the CCP rather than calling the exit system call This will skip all this initialization because everything Will be happening in the context of the CCP and in fact we are doing this wrong Because we're going to want to I mean the reset disk stuff is itself a system call We now want to log in drive a This will read the Read the directory bitmap Drive a the number of drive a is zero. So we just need to do this So this should actually assemble No, it won't so this this should now assemble. There we go okay, so Is this doing there is a So what yeah, this is updating the login vector to say that this drive has been logged in We selected to say that this is the drive that we're currently working on and then we Rebuild the bitmap where is active, right The active drive is different from the current drive the current drive is The one that the user asked for This is it's a CPM equivalent of the currently working current working directory the active drive is the one that we're working on right now and as that you can like have Files open and multiple drives at once. We're going to be constantly switching between active drives So that currently selected to drive is not actually correct This is going to be User drive Which is also all these terms are overloaded user is is It's CPM's equivalent of directories you can split a disk up into multiple users there are 16 and the numbered Call that current working drive and this is going to be Current working user the whole user concept is basically broken It's one of the things in CPM that actually just works badly Most of CPM is Extremely well-designed, but that is not one of them. So I believe in reset disk This is the CCP We go reset disk We are We're by writing zero to active drive. We are saying we're working on drive a There's also when you select to drive there is work needs to be done That will happen here so Okay, we are going to have to go back to the BIOS because I forgot to implement one of the most important system calls Which is? cell disk so what What this does is? It is called by the Bezos to tell the BIOS what drive we're currently working on and This will then Return a pointer To a structure that defines the disk BIOS here Has exactly one disk. So if a is not zero Then we fail and on error we return zero in The return parameter So we know that a is zero So all we have to do is to set x to zero to return very straightforward. In fact, we can do this Easier way So here we are going to want to return the DPH we only have one so we're going to simply statically If this is like trivial and We return it very simple So the DPH Is this structure? We are copying CPM to format it contains pointers to various bits of workspace That the BDOS is going to need to use to work on the disk so for example, it's got The address of The definition of the drive that describes the format That the directory checks some vector is used for detecting disk changes the allocation vector is the bitmap Where the BDOS keeps track of which blocks are in use and so on Some of this we're not going to use For example the sector translation table and I'm in two thoughts of whether to Get rid of this completely, but this will mean that our DPH will not be the standard format So I think let's not It might come in useful later Dear buff address of 128 bytes sector buffer This is used when scanning the directory Dpb is appointed to the Dpb structure that we're going to work going to define later CSV directory checks some vector This is a small buffer Used for checksumming the directory As it says if the checksum of a sector changes then the disk is assumed to be changed If the disk is not removable, there will be no such buffer into the allocation vector Got the directory buffer the checksum buffer needs to be That should say how big it has to be somewhere Each 128 byte record in the directory So our directory is going to be one block and a block is 1k We're using this format for now So this wants to be 24 by 32 long. I'll describe the format the disk format in a moment the Allocation vector needs to have one bit for every Block on the disk It's be that big Okay So this is actually quite a lot of buffer space One of the biggest problems with cpm is the allocation vector here The bigger the disk the bigger the allocation vector And uh, if you're using hard disks that are like 32 megabytes You end up with enormous allocation vectors And these have to be in memory all the time And what's worse than that? If you've got multiple drives, they all need allocation vectors in parallel You can quickly end up using enormous amounts of space for your allocation vectors for multiple drives What was my looking at? I want to look at the The bios map Okay, we're safe We've used quite a lot of space, but um Actually not as much as all that I would probably better check to make sure that this these maths work Of course, it doesn't say How big the blocks are in the listing even though it knows at Yeah, I suppose this is more or less right so a9 to Bd that's not a lot actually See, I don't know Well, first start my maths here might be wrong But also I don't know uh, how wide ca 65s maths are Expression evaluation 32 bits. Okay, that should be fine so Number of fake tracks number of fake sectors number of bytes in a sector gives us 163 Thousand bytes on the disk divided by the block size 160 blocks add 7 divided by 8 gives 20 bytes Yes, this is a small disk. That's fine. Okay, that all looks reasonable So now we want to do the dpb Which is a fixed structure defining a disk format Let me talk about disk format Here is our cpm file system image. What I came up with earlier It's actually constructed by the make file So that we can easily add fast to it Cpm disks are divided into blocks. You have to specify the block size. We are using a 2k No, we're not we're using a a 1k block size Uh, the directory appears first on the disk. It is currently two blocks wide This gives it enough space for Well, this gives us enough space for 64 directory entries each directory entry Is 32 bytes wide So here you see we have two directory entries the first is for A file called knop.com That is a binary that I produced All it does is it exit immediately. It's like the minimum possible program We could make one a bit small that I'd by just putting an rts in but that would return to the ccp rather than read Rather than warm starting the bedoss the top row Here Contains the file name which is these bytes which is a standard 8.3 file name structure If this looks familiar it's because dosk got it from cpm uh, we have A handful of metadata bytes here this one Indicates the number of records on the disk Oh, sorry records in the file a record is 128 bytes Uh, this one is unused in cpm 2 The tool i'm using to generate the file systems actually sets it to something which is used by cpm 3, but we're ignoring that And these two bytes Describe the extent number And I'll talk about extents later The second 16 bytes Is very simply the list of blocks where you can find the data So that we can see here that knop.com There is one allocated block zeroes mean unallocated And that block is block two So we can see knop.com's data by going to That is the size of one block Therefore, this is the address of block two Which is here And this is our program and That is essentially all there is to a cpm file system There is a wrinkle The block structure here has got enough space for Uh 16 block numbers for a small disk Uh where a block number is one byte or eight block numbers for a large disk Where a block number is two bytes thus giving a maximum of 64 000 available blocks You can have files that are longer than that And the way cpm deals with this is it simply puts multiple directory entries In the directory representing a file And they're disambiguated by the extent numbers up here Now each directory entry is technically called an extent So that uh the first 16 blocks are in extent zero the The next 16 blocks are in the next extent and so on Is actually a little bit more complicated than that because technically an extent can contain eight blocks And on this disk file system Because a block is one byte we can actually fit 16 blocks in a directory entry Therefore each directory entry contains logically Two extent Even though a directory entry is called an extent. Yeah terminology is not brilliant So We need to tell cpm All the parameters about the disk that we need to know Which is This structure here So the first word is Number of Sectors per track, but we're not using tracks. So this is unused The next one block shift This is how many bits left to shift a block number to get a record number so For a 1k block it wants a three. Let me see. Let me see is that right? block shift to record Number a A record is 128 bytes our blocks are 1024 bytes So how many times we need to double this? Once twice thrice. Yes, that is correct The block mask Is I think this is the reverse hang on. Let me just think about this This is the when given a sector number That is an absolute number of sectors from the beginning of the disk You mask that value with this one and this Tells you which sector within the block you're using so uh Our block shift is three. So the block mask is seven which is one one one, which is three bits In fact, these are bytes Real cpm has a big complicated assembler macro that computes all these values Extent mask Took me ages to figure this out The description here is complicated But this basically uh describes um How many logical extents there are within a um Within a single directory entry So because our file system uses two of these We want a We want this value dsm number of blocks on the disk So this is Actually, it's this value here And we want to make this It's actually the value minus one So And again, this this should all be done with macros. You generate the whole dph With a macro and it does this for you number of directory entries Minus one Well, that's 63 because it's defined Here one mine Number of directory entries minus one Okay, directory allocation bitmap The when it uh when the bidos computes the bitmap of which blocks on the disk are used You have to tell it that the directory itself is in use And the way it does this is it writes these two bytes to the bottom of the bitmap Before doing the computation so This this example down here is saying that there are two blocks allocated for the bitmap So this is actually going to be like so Check some vector size number of directories entries divided by four rounded up I Got this Number of directory entry. Hang on this value is 32 Number of directory entries is 64 divided by four Is 16 rounded up. Okay. This should actually be 16 three This will all need cleanup eventually and This last one here is the number of reserved tracks on the disk This is a constant value that gets added to the track number whenever it's working on the disk Of course, we are not using track numbers And our value is zero anyway, but we are going to want to support Reserved tracks so Because our because sectors 128 bytes So this will give us two eight megabytes of available reserved space At the beginning of the disk, which is fine the reason I'm concerned is that section numbers are 24 bits and this is a 16 bit value, but 16 bits gives us enough to be useful So that's actually brought down the size of our bss a bit So the uh, the dpb is down here Here we go. It starts this address zero three seven Stent mask number of blocks on the disk is 9f This is this is a 8 bit value Again, let me say that again. This is a 16 bit value, but the number of blocks fits in eight bits This makes it technically a small disk Which allows us to fit 16 block numbers In the 16 bytes in a directory entry If this didn't fit in eight bits It would make it a large disk and we would have to use 16 bit block numbers The number of directory entries is here Allocation bitmap is here check some vector sizes here number of reserved sectors is here and Above this there is the Our various pointers The dph starts here unused sector translation table one two three words of cpm workspace directory buffer at 05d6 dpb at 05be Check some buffer at 0656 Allocation vector at 0666 good. So now we go back to the bedoss So here's the code for Logging in the disk What's this doing? shift right Is let's see what shift right does shifts hl right c bits So that gets the active drive into a right, this is deciding whether the It's looking at the login vector and it's deciding whether this disk is already logged in If this disk was not already Active Oh, right. Here's the conditional where it's doing it Okay, if the drive is not already Logged in the bitmap Is recomputed in all cases the drive is selected So the way you do this in the 8080 is you load the 16-bit vector You shift it right by the drive number thus leaving you with the login bit at the bottom of the 16 bit word However, we are a 602 We don't have 16 bit values. I'm just trying to think is there a cunning 602 way of doing this I don't think there is so I think Let's just define a A general purpose temporary storage thing So we want to shift the value in temp right by That number of bits so raw temp plus one or temp plus zero de y branch if non zero There So this is one two three four five six seven eight bytes We are going to push that out into a Helper routine We now have the bit we're looking for at the bottom of temp zero We now want to select the current drive By just doing that We want to look at This actually returns an error If the drive is unselectable Yeah This returns an error if the drive is unselectable that is it's not valid then We check the bottom bit That we computed here Why are we doing this first rather than selecting it first? That would seem to make sense as it doesn't require Pushing across right because selecting the drive will actually Will not update the login vector Yeah So in order to test that bottom bit, we're just going to use the same trick that these are using Which is to rotate right The temp zero That will put the The bit we care about into carry and then we can do a a branch But we are going to have to Check to see whether the drive is selectable on the 8080 this returns The z flag Select returns a z flag if the drive is unselectable Is select a system call? No, it's not So this is actually not a complicated Routine all we do is we call the bios So this is going to be Get the active drive Select it This will return a pointer to the dpb in a x We want to come to see whether these are both zero. I am thinking that that's not a very useful call Signature it would actually You see in the 8080 You can very easily or together two registers That's why it returns z. It just oars Uh the two Pointer bytes together and you know, it's that a zero Oh, there's actually quite a lot more code here than I was looking at Um But the 6502 you can't actually do that so I think rather than returning a zero Let's just set the carry because we can do that in A single byte So if carry is set Then we just return So then What the bidos is doing is it's pulling information out of that structure And stashing it in local storage The 8080 can't do pointer in direction Can't do pointer indexing very well The 6502 is much better at that, but I don't think it's enough better that we can just keep track of Let me just store the pointer So let's actually copy the values We are going to steal this It's the dph Copy So x a now contains a pointer Which we stick in temp and then we do our loop as normal Too much of 80 code Temp is I did say it was zero page Didn't use zero page addressing for temp. Why is it not doing that? It knows that this is In zero page Does it know it's in zero page? Do I need to put this? At the top Yes, I do. Okay There we go. It's now using zero page So this Copies the entire dph This Is Also going to be copying the dpb Which makes sense So we have to do exactly the same thing here zero stent mask zero disc word zero Entries will do word zero That in it is A word It's two bytes check some vector size Is a word reserve sectors Is a word Okay Copy dpb into local storage So we set up our pointer And we do A copy loop now There is one additional piece of logic in this code Which is checking the disc size What this is going to be doing Is looking at the blocks on disk high word Using that if that's non-zero then it knows that this is a big disc. I am just thinking Check to see if this Is a big disc You see I wondering I don't think I actually need this at all because We can do this We load the high byte And if we go look at lda in the 6002 We want lda Absolute This is not the right place here we go This sets the z flag So just loading this will then allow us to do Is it a zero it's a small disc So I don't actually think we need A separate big disc flag So I think we're good Until we are selecting the drive Now we want to go back to log in and do more of that Remember what the It also doesn't help that I keep mis-typing shift g and I go to the end of the file Rather than the beginning So log in drive Is this a newly activated drive? The side if the drive is already logged in So We're also going to change shift r to make it smaller So we're using xa as a pointer Because this means that we can save four bytes storing into temp Let's do this Because Just thinking are we using setbit in more than one place? Yes, we are because we're doing stuff with bitmaps So I was going to produce a A setbit function that sets a bit in xa But I think that I actually want A setbit function that uses a pointer Doing this Whoops Doing this is actually four bytes We could use a helper Because a call to a helper would be three bytes, but honestly, it's not worth it I was thinking how is this actually going to work? Setting a bit and a 16 bit value Uh, there's a There is a Bit Instruction This does not do what we want Yes, uh, the 65 co2 Has opcodes for setting and resetting bits, but the 6502 Does not So I Think we need two code paths for High and low So this is I think more annoying that looks also I've noticed that my shift right here is wrong Because we are Post decrementing y this means that if y is goes in as zero Which it will be when we're dealing with drive zero Then we will still shift by one. So we actually want to do So we compare y to zero if y is Not exit now. That's terrible What I'm concerned about is that this is going to take Four bytes for these two instructions Plus another three bytes at the bottom for the jump back at the beginning So you ought to be able to do better than that. We can decrement y here So we still have our own irritating jump But I think we've saved one byte And also looking at the original code I see there is actually a corresponding shift l So let's do that This is all exactly the same code However, rather than rolling right from left to right, we actually want to roll left From right to left and because these are next to each other We can share some code Okay So this actually gives us a way to do our set a bit We are We are actually changing temp to be four bytes long We're storing Our pointer In the upper two bytes of temp We then We put a one into temp plus zero which then allows us to Shift it left by y And we're now going to Or in the shifted value Using a loop and exit Now that's some grim code And it seems wrong To use a loop for Two iterations But this Is a six bytes And if we wanted to unroll it We still need to use y because we need to use pointer in direction Here So each iteration would be eight bytes and this way The second iteration cost is only three bytes Right. Well Okay We have set the login vector bit And we've written a whole bunch of useful stuff that we're going to use later So the next thing is to update the bitmap for the current drive now How many places is this used? Once as far as I can tell So There's also this does another thing. It also looks for files beginning with dollar signs. This is used for running script So what this is going to do is it's going to iterate through the directory reading all the directory entries And Setting the bits in the bitmap To mark which blocks are in use. Let's look at some of this code So looking at our tables down here the The allocation vector. I'm just going to call that I don't have my So the first thing we're going to do is to Zero the bitmap of bytes of bitmap and we want to round Up So we now want to copy the pointer into The second temp register and zero it Problem is the bitmap can be bigger than eight bits So we can't use indexing The sucfo to x and y registers are eight bits wide So if you want to index with 16 bits, we have to do it the long way and So this writes a That here actually this writes a single zero to Our bitmap pointer Then it adds one to the pointer And now we want to decrement one from the bitmap length and zero Okay So this is our 16 bit reset loop thing So we've zeroed the table We now want to copy So we're actually want to copy Put the pointer right back again And our usual loop for copying a 16 bit value Whoops that should be a comma y Okay So we're done with the initialization I'm getting increasingly worried about this exit Because 602 branches are limited to plus and minus about 128 bytes So once we have more than that amount of code in here That branch won't be able to reach the exit, but at least we'll get narrow when that happens So we now get to the point where we actually Start having to read the disk Is this working? So The b-dars seems to use remember these Three scratch entries It seems to use scratch 2 and scratch 3 consistently to indicate where the disk's head is Let's just go through this Code and looking for it Track pointer Track number Track number Oh, it's even commented. That's useful. So we're actually going to rename that as Well, I would say last selected track and sector, but we don't have tracks and sectors anymore So we're actually going to change this to current Sector This is going to be our 24 bit Sector number Scratch one here relative position within directory segment Because of course each 128 byte cpm sector can contain Four directory entries I'll come up with a better name for that later So home drive all it's going to do is set the current sector number to zero Okay, nothing else needs doing. So what this is going to do is Uh Scratch one is being used for the the index within the Within a sector. So that's why it's setting it to three st So nx entry reads the read directory entry right st file pause nx entry and check file pause they I am typing shift g to go to the beginning of the file which is wrong because shift g goes to the end so, uh The low level routines for tracking through the directory Actually keep a count of which directory entry you're on this is how it knows when it's at the end and these routines Do that So we are going to add a directory counter which sets it to minus one so that will be zero three plus one Check directory pause make sure that we have not actually run out of directory entries Actually this checks to see whether it's ff ff Right, we're using the invalid value pattern. That's fine. What a good way of doing this on the 602 I just follow the same patterns the 8080 so compare with If they are Yeah, that's cunning and we're going to put that in x So if they're not the same If they are the same Increment x and now the zero flag Will be set And we can exit Here we want to make sure the zero flag is not set But we don't know what value either of these have and I also want to make sure that ldx Does indeed set zero and we can save a byte okay So our read loop is going to want to Read a directory entry into the directory buffer If the directory position is invalid that is zero We give up Because we reach the end of the directory Otherwise We do some stuff and go back to read loop because I think we're going to have to test this now Read directory entry is See this is taking this c parameter Right, this also calculates the Check some byte So the first thing we need to do is to make sure that we haven't run out of directory entries So this we are going to compare with The Number of directory entries here if they are not equal if If the directory position is equal to the number of directory entries Then we know that we have Already read the last directory entry and we have therefore run out of directory entries So we just Reset the directory position to make it invalid and exit all right, otherwise We want to read the Next directory Because there are four in a Sector So the directory position starts at zero So there was that there was that thing here So scratch one is supposed to be the position inside a directory sector, but that was also different from File paths here. See this is not actually touching scratch one Aha, I have actually missed a bit which is here it has actually Invented the directory position It does that before it does the comparison Because remember we start with ff ff minus one so Move to the next directory entry This is the last But directory entries here is one minus the number Yeah, this is actually behaving slightly so this is doing a addition Then a subtract And checking the carry flag. So that's going to be testing not for comparison, but for greater than so we actually want to do it the other way around Like this so that we do a comparison for equality that Tells us whether we've run out Then we move on to the next one This is then computing which directory entry within the sector we care about So it does that by just adding the directory position By To get a value from zero to four we then want to multiply by 32 which we can do with two three five These are all one byte each. So it's quite cheap. We could use a loop a loop would be two bytes to load y one byte for the shift mix three One byte for the decrement that makes four two bytes for the branch That makes six And we still need to clc That makes seven. So this is in fact smaller So that gives the offset within the directory sector We're not going to do anything just yet. I'm going to add this code later But the big thing we need to do now is If the offset is zero This means this is the first directory entry in the sector And therefore we actually have to read in that sector So So if the offset is zero, we actually have to like do work So We call track sec Does this do This calculates the track and sector That the desired block number is in We're going to be using So this starts with file pods used for anything Other than directory Stuff Yes, it is Not actually Sure No, I don't think it is okay, so so we want some code to Calculate The sector of the deer ent we care about and the deer ent is the one specified in directory pause Now this is combined code with file access Because there's also block number Which is a 16 bit block So what our code is going to do is So this is where using blocks should make things so much simpler So we're going to load the Load the directory count Into xa We then want to Shift it right by two uh Hang on we want to get the block number but the block number varies I mean the number of directory entries per block varies Is this actually talking about sectors not blocks? I think it is you see says here move sector number into bc Yeah, so this gives us a sector number in xa So we're going to store that in current sector plus zero current sector plus one And the high byte is of course going to be A zero all right And the rest of this code Is to do with computing the track and sector number And we don't need any of this Because We're not doing that That's why I wanted to use absolute sectors Right, I actually thought this code was going to convert the File paths to a block number and then the block number To a sector, but no it's not it's just going straight to sectors Okay, so we have calculated the appropriate sector Dear read We'll then read a directory entry In order to do this we have to Set the dma address to be the directory buffer Of course this means that We will overwrite Any user-specified dma address So we're going to need to save that So What's that called? Directory buffer because this is This is a pointer This is going to be bios Set dma Bios Okay, okay, do read is going to be a routine for actually reading from the current sector Did I define exit? Actually, let's inline this so This is the code that actually reads The The sector So the first thing we need to do is to tell the bios What where we want it to put the data And then we actually want to do the Read Okay So this is not actually going to do any work, but there should actually be enough code here to Iterate through the directory How big is our bedos? 500 bytes 8080 the 8080 bedos was three and a half k So we're actually doing reasonably well so Let's give it a try and see what happens 1903 is our bedos entry point go Break right We are here. So store the bios entry point update the memory region Yes, we're now up to two pages. You see adc two set the We're now in entry exit. So we reset the stack And we try and reset the disk Let's go in here. We're now here So we first thing is to reset any of the transient state x I think Why is that there? Is it supposed to be resetting to zero? So I think I need that Ah, right x was zero from here But we can't rely on that because reset disk Is going to be called from elsewhere Okay, anyway, this is going to It's going to fill the state with garbage at the moment So let's Go again. Where did I put the? There it is reset to c Okay Yeah Right load any with zero And we loop Wait just what Why did that not loop? Okay branch and result branch branches if n is zero Which it should have been does dy set n Yes, it does Actually, I think that worked It's just that the n command in the debugger Skips over loops. I have to be careful of that one. Let me take a look at the Yeah, so it has actually reset all this stuff to zero Okay So we have got to here We are Selecting the active drive So we fetch the active drive, which is zero We select it Carry should be clear It's not it is set So in fact that is going to error out But let's try that one again Be nice if this had a step backwards The debugger I normally use for c and c++ does actually have a step backwards and it's incredibly useful All right, we're in the BIOS So this is the jump table We are here Compare with you compare a with zero a is zero Okay We're here We are returning. We are not clearing the carry. It's better We are Here Copy the dph into local storage. There are 15 bytes of it copied Copied the dpb into local storage The dpb is now in 0 1 2 3 4 5 0 5 bf that looks like the right kind of address That's a dpb. All right, okay, and loop And exit We are Here So decide if the drive was already logged in we load the vector The login vector Into ax which is all zeros we load the Active drive into y is also a zero and we start shifting So we store that into temp increment y to a one Decrement y again that sets the flags if it is b n e That should be be eq That should be be q2 1 a 6 b Okay, so if it's zero then exit to 1875 Reload x a and x we are now For an memory locations four and five Contain the current login vector. These are both zeros so We rotate four That puts the bottom bit into carry The carry is clear This means it's not logged in we now update the login vector So the login vector is at 1a ad here is the active drive and we are now in Setbit as we look at 1a ad These two bytes is the login vector So There is our one. No, it's not No, it's not these are wrong 1a 9 5 we're at Our company bother trying to clearly break points 1a 9 5 right we have put a one into There so we now try and shift it And it's garbage Why is it garbage? because Shift L Shifts the value in x a not the value in temp one So we actually want Like so so we want to break out 1 a 2 Okay, so we're actually shifting x a left by zero byte the zero bits Which should leave now we've done it No, that is still wrong honestly Imagine doing this on a real machine Wow Okay Right that looks better So we now have Here is the value that we're going to or into the bit field Here is a pointer to the actual bit field So we now do the Warring One two three four five six. Yep. It's by y. Yeah, okay So now if we look at 1a a which is where our bit field is We see a one. That's correct Right We are now here. We are zeroing the bitmap So the nut blocks on disk are in a x which is 9f We are rounding up by adding seven Or rather we round up by adding one less than the value we're rounding by The shift are here is actually doing the division afterwards So by adding seven we make sure that the value at the end of shift r is correctly rounded 7 That tang goes from 9f to a6 Here carry is not set. Why is that? Stopped that's not all right 9 5f we want to go to Okay, so this should go to Right. Ah, I forgot to actually put a label in That was stupid That's actually missing out huge grapes waves of code Okay, right. That's better. We're now here so Our value is oa6 Divide by eight that gives us Really oa6 this is also not right So let's do that again. We want to stop at 9 5f Okay, so we are in We're shifting x a right by three store store increment Decrement Okay, we're actually doing the shift now if right jump right jump End of loop So zero one two three four one four. Oh, oh, which would be So load a with four x five and We're here. That's correct. So why were we confused earlier? So we should be here. So this is putting the Bitmap pointer in so this is the number of bytes o6 6 7 Is the bitmap which currently happens to be all zeros So here is our big zeroing loop right to zero increment the Pointer what one two three four five six. Okay So that's incremented the value but the carry bit didn't get reset Because increment does not set carry. It's set zero so if if we do the increment and the Zero flag is not Is set then we've just done the roll over So this needs to be an any and this needs to be an any Okay, we're at one nine seven eight I am just going to Restart the emulator So that You can get rid of all the breakpoints right one nine seven eight. Okay. That's better now. We decrement the count Yeah, yeah we're going check for a zero, okay, let's just So let's skip past to the end of the End of the loop that seems wrong Yeah, we've just scribbled all over our code Well for a start this is now wrong We have decremented the count Which means that Rollover happened when the value went to f f and we have no bit for that So we are going to have to do this The old-fashioned way Like so Yeah, because adc will actually set the carry flag actually Sbc one And I think we want carry set Here do we I always get carry and subtract muddled It also doesn't help that different cpu's handle the carry flag on subtract differently. Where is the right here? Yes, I've lost the breakpoint So I don't actually know where to Stop Well, at least our code is still there, which is a good thing Right here is our subtract one nine seven eight reset boot one nine seven eight So A is 14 as we expected A is now 13 carry is set So we Yeah, we're now here Here is our count, which is 13 At the end of the loop is now one nine eight d Go 10 eight seven six five four three two one And we're at the end of the loop We're now here So initialize the bitmap within the directory is just copies a couple of values It's not right That's storing it to the temporary location Oh, no, no, this is actually storing the address of the bitmap in temp, which is correct Which is at 0667 Which is empty So we are now here. We're initializing From the Two bytes there so load store decrement loop load store decrement Yep So now we home the drive with just zeroes the current sector done Reset the directory position Which is even simpler done Read the directory entry Okay Check to see if this is the last directory entry So we read the directory position, which is at 1ab1 Which is ff as we expected So this should not be The number of directory entries, which is 1ace which is 3f Yes Okay, they are not the same So we are I missed a label again That should be here 1 9 b 9 1 9 b 9 we are We are We should have just jumped to here, but that's an ink. I forgot to save the file. That's why So step we are here Okay increment We move to the next drent The index is currently ff. So We're now at position zero So we calculate the Which entry within the sector we're in Which is zero We multiply it by five The value is not zero. We don't have a valid sector Therefore, we need to calculate the drent sector, which is this so Get the directory position into ax shift left by two Still zero As we expected Write it to the current sector Right. We're now here Set the directory buffer Which is the value at 1abf, which is o5d7 And if we look at that we should see zeroes Call set dma Call read sector Which is here So 1abb is our sector number, which is a zero We call the bios We are here Well, we're in the dispatch Now we're here We set the sector pointer to a temporary And we copy it into o5d4 Back in the bedos We now actually perform the read Back into the bios We initialize the control block So first we clear it So here is our control block all zeros We Now initialize it So take the file handle, stash it there The dma address stash it stash it stash all the things Copy the sector number with a loop We are here This is then going to just wonder whether this code is right Because this is supposed to write three bytes To one byte higher than it should be So this should actually be plus 10 Hang on, is it? Yes, and this should be a nine Okay, wasn't so hot 524, okay This is all zeros, so uh No real knowing what it's doing But we then shift right That should be a x 524 And We're here Function code three Call os gbbp We get an error It's through Channel open Really? So is this complaining that the channel is Shouldn't be open So you see it then just prints the Oh just says channel That means it's an invalid channel The file handle is wrong So let's just try that again How to reset I'm sorry, I actually get the Okay, so we're now over here So if we look at This code we can see that Our control block is a six seven b That doesn't Yeah, okay, that looks reasonable So we've got a file handle of six zero It seems Big Destination address of 5d7 It's a 32 bit address Uh size to transfer of 128 see there's an eight zero there offset of zero I think I think we are storing the wrong File handle On exit A is the handle of the opened file So that is does actually seem to be the right file handle Okay, well there's a way to Do that So if we Where did it The the key map On the bbc micro is not the same as on my keyboard Right, so Six zero. Yeah So what we've done we've opened the file that returns the file handle And we see it is actually the value we asked for I bet I bet We have Completely forgotten to set the Uh The control block address need to pass that in in registers That ain't gonna help if I can find the code So that wants to be bpb block, okay, so Um, I think that's actually working, but I need to Get a better breakpoint ffd1 So Or f5 Okay, so here is Uh, we've stopped just at this line of code So we can see that the Control block address is set to 67f Yeah, well this is in fact the second time through So we have file handle Destination buffer length Offset and you see we've already added one to it So we should be able to look in here and see There we are 128 bytes directory entry that we have read from the disk So we do that again There's the next 128 bytes and again The next We should Eventually, there's actually quite a lot of these because they're 128 bytes sectors So I would kind of expect this to have stopped by now Yeah, I think there's something wrong in the bedoss here Okay, so let's reset this I still have muscle memory from Programming this thing ages ago great 1903 continue boot Okay, we want to find We're actually want to find login drive Let's find that adc seven that looks there we go. There is our adc seven Here is our zero loop and then there's four jsrs in a row There and then we this jumps back to 19b2 Now that jumps forwards. This is the one that jumps back 19a7 So if we break at 19a7 and go We are now here. So we read a directory entry This should have read an entry into 05b3 was it that's what I wanted to do Here it is Here's the buffer that she doesn't look Starting at the byte before knob here Okay, so we have read a directory entry We now check the directory position Which is here and the directory position should be zero So we expect these to be the same So they are the same Is this code completely Garbage yes, it is complete garbage. This is just doesn't work. That's why Uh That's better Okay, read a block read a block do the comparison They are the same therefore Increment x and only if Both bytes were ff will the result be zero So that gives us a non-z Results so we end up Not taking the branch And jumping back. So if you put a breakpoint at 19b2 And continue This should read all the sectors And stop at 19b2 Excellent and then we halt Okay, well That took long enough, but we are now successfully iterating through the directory We are not computing the bitmap yet But this has been a while and I want to go and have lunch. So I'm going to call it here That's a good chunk of code and we've done a whole bunch of primitives that we're going to be using For the rest of it like all this stuff about Iterating through the directory. This will be used everywhere Whenever we want to do anything with the directory, we'll be calling These routines. So it's good that these now actually work. How big is rb.sofar? Yep, okay That's good So I will save this See you next time