There’s not been as much progress over the last few, actually six, months as I would have liked. The good news is that I now have a whole load more time available to work on my projects, including MAXI09-related things. So expect many more blog posts in the coming months!
There has been some progress on the OS which I will talk about later, but first up some news about the creation of another MAXI09 board.
After posting about the MAXI09 project on the retrobrewcomputing forum I initially had very little interest. Which is fair enough; MAXI09 is not for everyone. But a couple of months ago I received a very interesting response from someone who, like me, is a huge fan of the 6809. After sending a few emails back and forth he expressed an interest in building his own MAXI09 and, sure enough, he is now in possession of a running board. There’s still a few things to get working, especially around the screen and keyboard, but his board is very nearly complete and he is able to interact with the system via its UART ports. Here’s a picture of his MAXI09:
He goes by the handle ComputerDoc and you can read more about him here. He’s very active in the retro computing scene and has built more then a few retro machines over the years.
Most of ComputerDoc’s troubles with getting the board running were down to him using Windows for his development machine. I haven’t ever tried using asxxxx under Windows, and while there’s a build which works great, working with MAXI09 also involves being able to run the “flasher” program which, whilst it builds fine under CygWin, doesn’t seem to run properly. So for now ‘Doc is using a Linux box with his board. It would be great to get the system useable from a Windows environment, so at some point I will tackle that extra little project.
Work on MAXI09OS is continuing slowly.
I have been working on introducing a “clean” interface between user code (that is, utilities and such like) and the main system code. I did briefly consider using Software Interrupts, which is the most elegant and recommended solution, but the overhead of stacking and unstacking all registers on every syscall does not make this a high performance solution.
Instead I am using a simple vector table approach. Unlike in my previous “mini OS” made out of my old machine code monitor, the vector table is generated using a script. This script parses the symbol map which aslink produces and generates an array of symbol references. Thus each syscall is presented at a constant offset into the vector table (referred to as the subtable in the code) and a rebuild of the EEPROM which moves the actual routine address around will not break callers of the code – user code – which are calling through the vector instead of directly. This table resides at the very start of ROM, 0xc000 using the current memory map. Currently system variables, like the current task pointer, are also exported to user code but without the indirection of the vector table. This is a reasonable approach since the table of variables has changed very little during MAXI09OS’s development. I might yet change this around and either hide some of these variables behind a syscall subroutine or vector them so they can be moved around if desired. Actually a third option exists: see if its possible to live without exporting them, since most of them are fairly core to the system and do not hold things useful to “end user” code.
Because some globals need to be global across the EEPROM image (ie. the MAXI09OS) but not exported to user code, a convention has been introduced: if the subroutine starts with an underscore it will not be added to the subtable at build time. This is used, for example, by startup code to locate the address of the idler task code, which needs to be a global because it resides in a different file to the startup code. The nice thing is that the generation of these tables is completely automatic; they are produced by small perl scripts which are invoked as part of the build process by make.
To facilitate the vector table being at the start of ROM I had to make an improvement to the 256 byte boot loader I’ve previously written about. Before the loader always jumped to 0xc000 after either reprogramming the EEPROM, or not; the normal boot case. This was a simple approach but it meant it was not possible to chain-boot ROM images which had a regular reset vector stored in ROM at 0xfffe and 0xffff. The loader will now chain the real ROM by jumping through this vector. It achieves this by always copying itself into RAM when it starts. This is necessary because the loader program also resides at the top of the address space (it has to since the 6809 needs to boot it at start-up). Previously this copy to RAM operation – and running in – was only done when rewriting the EEPROM. It’s now performed on each start up since the loader always has to be able to access all of the real ROM including the top page where the reset vector is found. This approach for chaining up the OS feels nicer and is also more flexible, at the expense of a few milliseconds start up time.
The system routine table has been proven out by implementing some test “user programs”, which are executed by the shell. At present the current directory of the shell task is searched, but obviously this is a temporary hack: in the future external commands will reside in a specifically named directory. Obviously this code path is only entered if the command name entered is not one of the built-ins (cd, type, list, etc).
The rough sequence of steps to run an external command is as follows:
- Try to open a file in the current directory with the name obtained from the shell input stream, eg “test.bin”. If this fails, bail with “file not found”.
- If the file opened is not actually a plain file (maybe it’s a directory) then bail with “not a file”.
- Read the file into RAM (readfile sub in drivers/fs/minix.asm):
- Obtain the open file’s length.
- Allocate that many bytes.
- Read from the file handle, that many bytes, into the new memory block, using the device agnostic getchars in lib/io.asm.
- Jump to the start of the allocated memory block as a subroutine.
- On return, free the menory allocated by readfile.
On return from the subroutine ie. the user program, the shell could do something with the register state, like treat the contents of the a register as an error value, or something similar. Currently it just loops back to the top of the shell input loop. Also, on entry to the subroutine the registers are not setup in any particular way; it would be nice if the x register contained the calling tasks IO channel, and y contained the rest (sans command name) of the shell’s command buffer. That way the command could process the rest of the command-line and extract arguments. However, none of the (three) external commands currently take arguments so that is not yet needed.The following screenshot shows the shell being used to run the three external commands:
This screenshot also shows the operation of a new driver for the SPI “controller” within DISCo. It is still bit-banged, with DISCo acting as GPIO pins to the various SPI peripheral ICs. One small improvement I’ve implemented in DISCo’s VHDL is that the SPI select pins are now configured via an address register, instead of individual pins being controlled by register bits written by 6809 code. This means the details of how a peripheral IC is selected is abstracted away in the VHDL SPI address decode logic. The address arrangement is as follows:
SPIANALOGJOY0 .equ 0 SPIANALOGJOY1 .equ 1 SPICLOCK .equ 2 SPIEEPROM .equ 3
All other values mean no peripheral IC is selected, with 0xff being the nominal address to use for this state. The peripheral IC address is set in the a register when the SPI device is opened via sysopen. Unlike other drivers with units (like the Quad UART) it is only possible to open a single instance of the SPI driver at a time. This is because the SPI data effectively travels on a shared bus. While SPI peripheral ICs might remember their state when deselected (allowing flipping between devices during multi-byte reads and writes, which could happen if two tasks open different SPI peripheral ICs) I’m not sure if this is the case or not and, for now at least, this restriction simplifies things. The SPI device works well, though regular readers will know that I fully intended to implement an “intelligent” SPI host controller within DISCo at some point.
I have also been working on the debug monitor.
Previously the monitor was a task that sat idle until wanted, at which point it disabled task switching and entered it’s interactive mode until it was exited. This worked well enough, but it was tied to a particular IO device (ie. a UART port or a virtual console). This made it inflexible.
Now the monitor is entered, with task switching being disabled, by sending the break signal, if using a UART port. Or by hitting a magic key combination, using using a virtual console. This is much more useful as the monitor can be entered regardless of the IO device used.
The implementation of this mechanism is kind of interesting. The OS subroutine sysread has been extended to report error values. Obviously not all device will return these error states. Errors are indicated by setting Not Zero (ie. Zero means no error). The a register will contain one from a list of possible errors:
IO_ERR_OK .equ 0 ; io operation completed ok IO_ERR_WAIT .equ 1 ; task should wait, no data yet IO_ERR_EOF .equ 2 ; end of file reached IO_ERR_BREAK .equ 0xff ; get a break signal
(Prior to this change, sysread returned Not Zero from the UART driver if no data was available indicating the caller should wait. This has now been extended with these new error states.)
Inside the UART driver, the break condition is detected by examining the Line Status Register. If a break is indicated, then the sysread action returns with the appropriate error. Similarly inside the console driver, IO_ERR_BREAK is set when the keyboard translation routine detects that the break combination has been pressed. Break is represented by a virtual ASCII sequence which is on the Help key when shifted. Thus pressing this key combination results in ASC_BREAK being entered into the buffer, which is then turned into the IO_ERR_BREAK condiion within the console driver’s sysread subroutine.
This system is not yet flawless. Phantom characters appear to being entered into the buffer when the break sequence is sent of the UART from the terminal program (minicom). Thus the input buffer needs to be cleared with a dummy Return key press before entering a monitor command. It’s not clear if this is a coding issue, or if the break sequence itself is generating an additional real character, which is swallowed into the buffer. It’s not a big problem, just slightly annoying. Here’s a screenshot of the monitor being entered, two commands being run, and then exited:
The “BREAK!!!” message is printed as a reply to the break signal, inside the generic IO error handler in lib/io.asm. This routine deals with generating wait calls if a IO_ERR_WAIT is indicated, as well as dealing with break. This error handler in turn is called by the getchar routine, which is the generic wrapper for sysread. getchar is in turn used by all IO routines in the IO library, including getstr, which is the main routine for getting an input string from the IO device. It’s thus possible to bypass the break detection, and implement it directly (or ignore it), by calling sysread directly and dealing with errors. Most user code will not use sysread directly however.
I have also added a debugmemory command to help find problems with the memory allocator. This spits out the list of memory blocks in the system; the addresses of each block as well as the next pointer, length and free flag for each block. This was done for a very practical reason: at one point in the implementation of external commands, my memory allocator had a corruption problem.
As is the case with most bugs, the issue is obvious in hindsight.
As described above, the external command run routine reads the entire file (of raw machine code) into a newly allocated memory block, sized exactly as big as the file. The problem was the file did not include the buffers used for input data. For example, gettime.asm requires a buffer to hold the SPI bytes received from the DS1305 (PDF) Real Time Clock. But this data is not included in the resultant gettime.bin file, since the memory buffer used to hold the SPI data is only “reserved” using the .rmb assembly instruction, which only creates labels unless there is trailing data – in which case real byes are used up inside the compiled machine-code file. The solution, albeit temporary, is to add a literal 0 byte value to the end of the gettime.bin file, via the .byte assembly pseudo instruction. This causes the file to contain space for the SPI data buffer.
The proper solution to this problem is introduce a header to the .bin external command files. This header would contain things like what additional space is needed to hold these unset buffers. This is the purpose of the .bss segment found in proper executable and object files. This is data which ha no initial value (usually zero’d out), but none the less needs to exist within a running processes address space.
It is not immediately clear what I will be working on next. In any case, real life has gotten in the way; my first priority at the moment is moving house. Once that is done I can turn my attention back to MAXI09. For a change of pace, I am thinking of working on a joystick. I want to build a nice digital joystick out of an arcade stick and some buttons I bought from eBay years ago. As a side project I want to make this joystick work with modern Macs/PCs/Linux using a USB adapter. I also want to see if I can get the 9 pin dot-matrix printer working. I bought that more then 2 years ago and it has so far sat gathering dust.
After that I want to work on a game. It will run within the OS, so should be a nice demonstration of the multitasking capabilities of the system. It is a lofty goal: after proving the ideas by porting my previously written Snake game to MAXI09OS, I am thinking about tackling a much more sophisticated arcade game…