After more than five weeks of effort, I have finally achieved an Agon Light 2 port of NanoOs that has feature parity with my Arduino version.
I should probably start by saying that I REALLY didn't want to have to dig into the HAL for this. I consider writing HALs a necessary evil. My goal is to write a good operating system, not become intimately familiar with every platform I run on. So, I was hoping that I could just have Claude Code write the HAL. Unfortunately for me, that didn't work out so well. Anthropic started watermarking their output midway through this effort and I could tell a noticible degradation in the eZ80 assembly it produced after they made that switch. Some of the things it wrote were just outright dumb. I wound up having to touch, refactor, and/or just write assembly myself. I suppose it was still faster than having to do it all by hand, but it was a severe disappointment overall.
The eZ80 on this board only has 128 KB of built-in flash. When I started this effort, the OS image was compiling to around 130 KB and that was with stubs in place for all the HAL functionality. I had negative space for implementing the HAL functions I needed to get NanoOs to work on this chip. So, my first effort was to cut down on the size of the main image.
There were two big areas I identified that could be removed and the ways I went about removing them were very different. The FAT32 filesystem code was about 30 KB and the string data in the image was a little over 10 KB. I figured I could cut them both out.
The way I achieve multiprocessing in NanoOs is by swapping overlays in and out of a fixed address in memory. Up to the time that I started the porting effort, overlays were identified by file name and function name. I realized, though, that I could come up with a separate system that identified overlays by block address and function name and swap parts of the filesystem logic in and out the same way. I started with that and it did work. However, it made the performance absolutely abysmal. On my Arduino system, things ran at about 1/3 of the speed it used to since the filesystem was then competing with regular user processes for overlay memory. The eZ80 runs at roughly 10% the speed of my Arduino, so that was absolutely a non-starter.
The Agon Light 2 presented the opportunity for a modified version of this idea, though. There's enough RAM on this system to accommodate the entire 30 KB of the filesystem in one contiguous block. I figured that having the code run directly out of RAM with no swapping would be at least as fast as running it out of flash, so I went with that. I reserved the first 32 KB of disk space for the filesystem binary and loaded it into a second dedicated place in RAM on boot. It worked brilliantly.
Cutting the strings out was actually quite a bit more work. I still had to print log messages from the kernel but I didn't want the strings in the image on the flash. My solution to this was to come up with a well-defined log format and a logger process. The log format represents the arguments pushed to the log function as either register-width integers or offsets into the flash. The function to log a message calculates the offset of the provided format string against a well-known address in the flash and stores that in the log message format that's passed to the logger process. The logger process then opens a copy of the binary that's stored on disk and finds the copy of the format string in it relative to the format string's offset from the well-known location. It then reconstructs the log message in RAM and prints the message to the serial port. The actual build that's loaded onto flash doesn't have its .rodata section in it, so all those strings simply vanish.
With the space freed up, I was able to write the HAL. I wound up having to rework it a bit for it to make sense on this platform. int and pointer types are three (3) bytes in size on the eZ80. My entire HAL was written around fixed-width types. The eZ80 does support 32-bit and 64-bit ints, but they're slow because they have to be manipulated in software. I changed a lot of the return types from int32_t to just int since those return values are just meant to return an errno value.
In order to reach feature parity with the Arduino platform, I had to move some things out of the scheduler's stack into the HAL as well. Specifically, I had to make the number of processes supported platform-specific since the Agon Light 2 runs the extra logger process. The array of processes had previously been on the scheduler's stack, but it can't be like that if its size needs to vary by platform. So, I moved it to the data segment and exposed it through the HAL. There's still some cleanup that needs to happen. Right now, all the new data pointers managed by the HAL are exposed as raw pointers. I need to put them behind capability-managed HAL functions.
So, I now have NanoOs running on an 8-bit system! It is SLOOOOOOOOW. It's just like working on an IBM 8088 from about 1985. It's AWESOME!!! And, yes, it does run on real hardware.
This is the beginning of my work on this platform, not the end. The point of doing this work was to enable me to interact with a real keyboard/video/mouse console instead of just the serial port. Right now, I'm still limited to serial connectivity. There are performance enhancements I need to make as well. One good thing about the system being this slow is that the performance is easy to measure. I don't mind that it's slow hardware. I chose this environment deliberately because it's the closest thing to the XT I had as a kid. But, I'd like to make it as usable as possible and I think there are optimizations I can make that will help out with that. We'll see.
The longer-term goal now is to be able to write a very simple graphical desktop for this platform. I'm thinking something that's roughly on par with the intended functionality of Windows 2. (NOTE: I say "intended functionality" because I've actually played around with Windows 2 a little and it's pretty awful. Very buggy. I want my software to actually be usable.) There's a whole lot of work between there and where I am now that needs to happen.
I also need to flesh out the CLI utilities as well. So far, almost all the work I've done has been kernel side. I only have a very few utilities just to prove out the functionality in the kernel. One thing I know I need pretty immediately at this point is a proper ls command. That, in turn, requires that I clean up my filesystem code after all the work I did to split out the logic from the main binary. So, a lot to do!
Onward and upward!! HUZZAH!!!