To give an idea of the interface now, here is the code that loads the C65 ROM into place:
; Prepare pointer for load address ($0020000)
As you can see, for the simplest use-cases, it is pretty simple: just set the name of the file you want to load, provide the load address, and then use the dos_readfileintomemory utility function that looks after everthing else. We are using the convention of setting the carry flag if a function completes successfully, or clear otherwise. In the case of an error, dos_error_code contains the reason fore failure. I have tried to provide meaningful error codes for all things that can go wrong at the moment.
Drilling down a little, we can see how the dos_readfileintomemory function works. There are few little complexities in that function that I will explain:
; file name must be already loaded into
; with length in dos_requested_filename_length
We keep track of the number of sectors read, so that we don't get stuck forever if we hit a file with a tangled cluster chain.
; Clear number of sectors read
Next, we need to find the file and get its details in the single directory entry structure (dos_dirent). A call to dos_findfirst leaves the file descriptor for the directory search open, in case you want to call dos_findnext to find more matching files. So we need to take care to close the file descriptor so that we don't run out. This is really important, because the hypervisor supplies a grand total of only four file descriptors. We can't trust dos_closefile to not mess with the carry flag, so we save the processor flags on the stack before closing the file descriptor, and then propagate any error up by jumping to dos_return_error_already_set, which preserves the value in dos_error_code.
; close directory now that we have what we were
; looking for so that we don't leak file descriptors ...
; ... but report if we hit an error
Next we need to make the SD card sector buffer visible at $DE00-$DFFF. In future, this and the following code will need to be generalised a bit to allow disks on other media, in which case we will need to instead have a pointer to the sector buffer. But for now we have a structure that works, and has the flexibility to add such functionality later without breaking compatibility.
; Make sector buffer visible at $DE00-$DFFF
Now we enter the main loop where we read the data into memory. We read the current sector, then copy the 512 bytes into place, update the pointer, and ask for the next sector, if there is one. For now, we just use a simple copy loop using the 32-bit pointer access. It would be quite a bit faster if we used DMA to transfer the sector buffer. It might even take a few less bytes, especially if we embed the load address pointer directly into the DMA list.
; copy sector to memory
nop ; 32-bit pointer access follows
nop ; 32-bit pointer access follows
Here we have a little security feature: You cannot load across a 16MB memory boundary. This is designed to prevent calls from user-space inadvertently or maliciously trying to load code that will end up loading over the top of the hypervisor. The 16MB boundary is enforced simply by refusing to increment the upper byte of the load address when we update the memory pointer used to write the data into memory. It isn't actually sufficient yet to be completely effective in this function, because any load to the IO memory space, which includes the hypervisor, could potentially overwrite the hypervisor, but I am already thinking about how to make these calls secure, so that, (a) the machine doesn't crash easily, and (b) so that it actually has good security.
; We only allow loading into a 16MB space
; Provided that we check the load address before starting,
; this ensures that a user-land request cannot load a huge file
; that eventually overwrites the hypervisor and results in privilege
; Increment number of sectors read (16 bit valie)
; see if there is another sector
This point in the code is reached if we have read 64K sectors = 64K x 512 bytes = 32MB. Note that we supply a meaninful error code so that the caller knows what has happened.
; File is >65535 sectors (32MB), report error
Finally, when we reach the end of file, we need to close the file to protect our precious few file descriptors, and indicate success to the caller.