On Incremental Improvement:
With VCF Southwest almost here, and having run into a dead end on getting NetBSD running on my 68030 homebrew computer, Wrap030, I decided to circle back to my multi-user BASIC kernel. There are some important features the system is needing to get it ready for running as an exhibit for all three days of the show.
There's a fun couple fairly standard BASIC commands, PEEK and POKE. The former will read a byte of memory from a specified address, and the latter will write a byte. These were commonly used in the 8-bit era to manipulate hardware registers. For instance, POKE 53280,1 on the Commodore 64 would set the screen frame color to white by writing 1 to the address 53280.
While there were ways to cause problems by writing bad values to the wrong address, on a single-user home computer the impact was only to the one user. On a multi-user system however, PEEK could be used maliciously to look at private data from other users. An errant POKE command could overwrite user data, kernel data, even code being run by users.
A good multi-user system needs some way to prevent one user from accessing memory used by another user, and to prevent users from overwriting shared code. The Motorola 68030 has a couple tools to solve this problem: separate supervisor & user states, and a built-in Memory Management Unit (MMU).
The supervisor state has access to all instructions and registers in the CPU. The user state is blocked from running certain instructions that would change system state or CPU configuration. I was already using supervisor state for my kernel and user state for BASIC programs, but it doesn't prevent users from accessing memory that doesn't belong to them.
That's what the the MMU is for.
The MMU takes the memory address the CPU is outputting (the Logical Address) and uses a table to remap it to a new address (the Physical Address). That table can hold additional information about how a particular region of memory can be used, and can be configured by the kernel at any time.
So we can, for instance, set up the table to mark the program code for BASIC as read-only when the CPU is in the user state. Or remap the memory allocated to each user so that it always starts at logical address zero. In fact, there's also nothing that requires the entirety of physical memory to be mapped — so as far as one user program is concerned, the other users' memory doesn't even exist.
Adding MMU support to my Multibasic kernel has been a goal from the beginning. It's a challenge though. The 68k MMU is a very capable, very complex beast. It supports tables that are up to four levels deep, supports page sizes from 256B to 32kB, and can use separate data and code tables for both supervisor and user states. It's something I've struggled to understand, but my work with NetBSD helped show me how to use it.
I decided to use 32kB pages and only map the 16MB of actual RAM I have installed. This allowed me to use a single-level table with 512 entries. During startup, the kernel initializes the supervisor table and a table for each user. When switching users, only the Root Pointer register in the MMU needs to be updated point to that user's table.
I was able to get the table initialization running after a few rewrites. And then realized I had forgotten to update the user initialization routines to point to their new logical addresses. And I was using the wrong table entry marker for the user tables, so the MMU was expecting more table entries instead of reading page descriptors. This got me to the point of the kernel running with the MMU enabled and I could even run a user program or two in BASIC, but if I tried to run three user programs, things got ... weird.
Overlapping exceptions is never a good sign. Or, it usually isn't. In this case I was trying to print out some debugging data for exceptions which takes a relatively long time. Longer than my timer interrupt, in fact … I had forgotten to disable the timer at the start of an exception handler. My timer was doing exactly what it was supposed to, I just needed to stop it when handling exceptions. That fixed the overlapping exceptions, but I still couldn't run more than two programs at a time.
This one had me stuck for a while, but I finally decided to review the NetBSD source to see what I was doing differently. All of my initialization and task switching code looked similar; there was just one thing that stood out to me as being different — NetBSD was clearing CPU cache on task switch and I wasn't. The 68030 doesn't have a large cache, surely that's not the probl…
Once I added the single instruction to clear cache when switching users, everything ran smoothly no matter how many programs I ran.
Having to enter programs by hand each time you want to run one is no fun. It's tedious and error-prone. Sure, it was common four decades ago for books and magazines to publish listings of BASIC programs. But after taking the time to carefully enter in hundreds of lines of code, most people are going to want to save the program to disk so it can be quickly reloaded later.
In my case, I would like to have a few demos, games, and interactive programs available for my exhibit. I do not want to have to type them in by hand every morning. It's time I finally sit down and figure out how to add file loading to EhBASIC.
The EASy68k applications page has a link to an archive of EhBASIC that supports the EASy68k simulator's I/O traps. This was the perfect starting point. All I needed was to add new system calls to my kernel for similar file open, read, and close operations, then update the EhBASIC file handling routines to use them.
I started by copying the Elm-Chan FAT filesystem library I had used for my bootloader into my kernel. It's a great minimal C library for FAT-formatted disks that doesn't take much to get up and running. I was able to write wrapper functions for its f_open(), f_read(), and f_close() functions that worked with my existing system call format.
This went surprisingly well. I found that EhBASIC was trying to re-open the file after each line, so I did have to update my code to keep track of whether it had reached the end of the file yet. That got me to the point where it would read the entire program and echo it to the terminal, but it couldn't run anything. It turns out EhBASIC was using address refused A0 for a line pointer; gcc C convention treats A0 as a scratch register that doesn't normally need to be saved. I just had to be sure to save the register contents to memory before calling the filesystem library functions.
Finally, I can load programs from disk instead of having to type them in manually every time!
It would be really helpful to be able to see what programs are on the disk. Loading a program requires entering the LOAD command followed by the filename. That's hard to do without knowing what programs are available.
Luckily, the Elm-Chan FatFs library also has functions for reading directory contents. I just needed to add three new system calls for the directory counterparts to the previous file operations.
EhBASIC didn't already have a command for printing directory contents though, I would have to add one. I wrote the function and was able to use the built-in CALL command to run it by the compiled address of the function, but CALL $100178 is not the easiest to remember.
I tried adding a new command, CAT (short for Catalog, a common directory listing command for early BASIC systems), to the command tables. All it would give me was a Syntax Error, however. I eventually stumbled onto the answer for this one — when parsing a line of code, EhBASIC will check if the token for a given keyword is greater or less than the token for the TAB keyword. Keywords less than TAB are treated as commands that can be executed at the beginning of a line; keywords greater than TAB must follow another statement such as PRINT. All I needed to do was move my new CAT command above TAB in the table.
On Incremental Improvement
These three new features go a long way towards making the system something robust enough and usable enough that I feel good about running it as an interactive exhibit for VCFSW this year.
But more than that, these new features bring my little Multibasic kernel just that much closer to a "proper" operating system — it is now a preemptive multiuser kernel with hardware memory protection and the ability to load programs from disk.
It currently does not support saving files to disk (intentionally omitted for now), doesn't support dynamic memory allocation, and can't run any processes other than the eight instances of BASIC. But it is starting to look the part. And I am definitely proud of the work that I have managed to do on this project.
If you would like to see Wrap030 running Multibasic in person, I will be exhibiting it June 20-22, 2025 at VCF Southwest in Richardson, Texas. This will be the third annual VCFSW since it was rebooted after a decade-long hiatus, and the third year in a row that I have had the opportunity to exhibit and volunteer for the show. This year is bigger than ever with over 90 exhibitors & vendors and a full schedule of workshops, talks, & presentations. If you're in the area, I highly recommend attending!