-
It's Done. I'm Finished. Stick a Fork In It. Computer is Served!
02/24/2018 at 15:56 • 1 commentI'm throwing in the towel once more on the Kestrel-2DX project. However, not because I'm aggravated, or feel defeated over some seemingly insurmountable technical issue (*cough!* Pseudo-SDRAM *cough!*). Oh no -- this is far, far better than that. I'm throwing in the towel because I've won. The Kestrel-2DX, as I've come to envision the computer design, is complete.
This computer is, bugs and/or feature requests notwithstanding, fully operational.
For the last two weeks, I have spent zero time using my workstation PC for the purposes of Kestrel development. My entire interaction with the Kestrel, backups notwithstanding, has been with developing software directly on the Kestrel, inside of DX-Forth. It's even largely the reason why I haven't been making updates as of late.
This has lead me to what I think is perhaps my first Law of Computing:
You know you're finished when you spend more time with your project than you do with the tools to build it.
I've been having a blast. I spent the last week or so building up a set of slides to present at today's SVFIG meeting. Despite DX-Forth not being a super-high performer, I'm planning writing a simple, terribly elementary game -- you know, the sorts of games one would write in BASIC on a Commodore 64 or Apple II. I doubt it'll push its limits; but, it's all that I know how to do, and it should show nicely what is possible to accomplish with the machine to others. I'm considering creating some videos along the way as well, so folks can see what it's like working with the system in real-time.
-
DX-Forth Prints Numbers, Stack
01/08/2018 at 17:53 • 0 commentsJust a quick update before I partake in more celebratory shenanigans. I managed to implement . and U. in DX-Forth, so now I can print numbers. Unlike most Forths, these words do not append a space to the numbers they print; I'm not convinced it will remain this way forever, but for now, it's convenient. Using these words, I implemented .S to dump the contents of the stack as well.
Now that I have these features implemented, my next step is to implement block storage. I intend on having four block buffers, and will implement a simple command-line text editor lexicon. (I do not think I have enough room to implement a clone of VIBE.) Only after I implement the block storage system will I consider starting to develop the colon compiler.
I have a fur-suiting convention to attend this weekend, and a family get-together the weekend following, so I do not expect to make further progress until at least February.
-
Kestrel-2DX Booting a Prototype Forth Environment
12/22/2017 at 01:01 • 0 commentsApologies for the lengthy update delay; life intervened as usual, and with the Hackaday contest over, I didn't put as much priority on releasing updates.
But, I was not idle! As this video shows, the Kestrel-2DX is live and is now booting arbitrary binary blobs.
The computer is coming out of hard reset; it's actually waiting for input before the monitor comes up with a video display. Once I have a TIM/V prompt, I use it to initialize the SD card, then to load the binary image into RAM, then to jump into it.
The image loaded is 4KB in size, and is the beginnings of a Forth interpreter made just for the Kestrel-2DX. (The old Kestrel-3 eForth port won't work on the 2DX because it doesn't have enough memory for it.)
The Forth interpreter is not complete: literally, it only supports small decimal numbers and the ! operation. It's got a long ways to go before supporting compilation. Still, this is a positive development! I'm quite excited!
-
Full Screen Editor-based Operating System
10/16/2017 at 06:54 • 0 commentsThis past week, I've been working on an experimental piece of software that I hope will be relatively easy to port to the Kestrel-2DX. It's an operating system, but unlike Unix and much more like IBM mainframes, it's based on the concept of presenting a user with a set of editable fields on "panels", which are presented to the user a full screen at a time. Any 3270 terminal user of an IBM mainframe would feel quite at home using it.
Thankfully, the programming model is substantially different, and easier to get working. ;)
For expediency, I wrote the first major version of this environment using GForth 0.7.0 on a Linux environment. I am currently using this environment to write a set of filesystem utilities (e.g., format volume, allocate file, etc.), which accomplishes two goals simultaneously:
I can work out the kinks of using the FSE environment, so when it comes time to port the software, I can do it the right way, and,
I can build up a library of code to manipulate Kestrel-2 SL-5 volumes. As some may remember, SL-5 filesystems came into existence with the introduction of STS, my operating system for the S16X4-based, 16-bit Kestrel-2. This time, I intend on fixing some long-standing problems with SL-5 (e.g., each file having only one extent).
The software is written in ANS Forth for the moment; however, it should be relatively easy to target-compile into RISC-V assembly language when the time comes. It'd be nice to have the same basic set of routines working on both my Linux PC and the Kestrel-2DX. :)
-
COM in BIOS? Not Anymore.
10/16/2017 at 05:14 • 0 commentsThe Problem
Well, that was a fun experiment, and I managed to get everything working; but, I had to remove COM from the BIOS. The problem was that, with MGIA video buffer spanning $10000-$13E7F, I only have 384 bytes for the BIOS to use between $13E80-$13FFF. I don't nest subroutines very deeply, so you'd expect this to be plenty of space, even with GCC, which requires a stack pointer to be aligned on a 16-byte boundary at all times.
Well, it turns out that, for some reason, COM method calls resulted in a bit too much nesting, as more pressure was placed on the stack to hold such things as method table pointers and such. The result is that the BIOS would corrupt the video display. Don't get me wrong: nothing ever crashed (though it could have under some pathological cases); but it was a very obvious bug that needed repair.
Most video bugs are, I guess, "very obvious bugs" by definition. ;)
The Resolution
Instead of COM, I just use a flat entry-point vector (table of function pointers). Programs loaded from secondary storage have zero idea where this table is actually located, so it must scan memory for a special signature to locate it. It's hokey and hackey, but it works, and allows me the freedom to relocate where I load programs in memory at any time in the future. The only hard requirement I have in place currently is that the signature must reside in the first 128KiB of the CPU's address space.
This more direct approach to linking functionality from ROM into RAM has reduced stack pressure enough that the video display is no longer corrupted during normal operation of the TIM/V monitor.
Lessons Learned
384 bytes is a relatively small amount of stack space, especially for software compiled with GCC. Point blank, C is not a good language to write small, tight, highly efficient software in. The problem is not code size (at least not directly); rather it's how it uses its stack. Overloading a single stack with both continuation and state information results in a larger than expected pressure on the stack, for two reasons:
- The compiler must generate stack frame constructors and destructors for each procedure you compile. If all you're doing is threading values from one function to another three call levels down, the two intermediate procedures must include code to shuttle data to and from: wasted instructions and stack space that otherwise serves no useful purpose. In COM, this happens with surprising frequency. With a dual-stack environment, this never happens. The result is a reduction of code you need to run, as well as a reduced stack frame size, without sacrificing proper code structure.
- Second, Forth-like languages often make use of two (or more) small stacks, not one big stack. Separating data from continuation information makes it vastly easier to recycle the relevant stack space. It's not uncommon for very small Forth systems to have between 8 and 16 slots on their data and/or return stacks. Phil Koopman, in his book Stack Computers, The New Wave, documents how incredibly rare it can be for software to exceed 24, 16 slots on their data, return stacks (respectively). Packing a Forth runtime environment's stacks into 384 bytes would be an exercise in triviality.
I'd like to rewrite the software in a static subset of Forth, using a compiler specially design for the Kestrel-2DX's unique memory requirements. This means it should be quite miserly with its use of stack space in practice. Obviously, I have something which works now; I've not decided if I will go forward with the Forth compiler idea yet.
In either case, however, software bootstrapped from secondary storage is expected to relocate the stack, so the 384 bytes configured at system start-up time is not set in stone.
-
Component Object Model for a BIOS?! Why, yes!
09/29/2017 at 16:04 • 0 commentsNow that I have SD card access working, I find myself in a position where I must now consider how to invoke BIOS services from programs developed long after BIOS itself has been compiled. I don't want to have to resort to using the CPU's ECALL or EBREAK instructions (or any trap, for that matter), because this both consumes an opcode that should really be used by a proper operating system, and because it would require that I renovate the linkage approach I have between the assembly language bootstrap and the C code. Jump-tables or entry-point vectors are ideal for the needs of the BIOS; since I am working primarily in C at the moment, it seems reasonable that invoking services through a C-accessible mechanism is the best solution (otherwise, I'd have to write yet more assembly language stubs/proxies). I knew just where to look; I decided to go back a decade, and excavate my old code from my GCOM project, my own clean-room clone of Component Object Model.
I'm happy to announce that I've managed to port a reasonable subset of GCOM into the firmware. As the proof of concept, I only implemented console output functions and basic cursor control. Keyboard input and SD card access will come later as time permits.
---------- more ----------Here's how I defined the console output interface:
BEGIN_INTERFACE(IConsoleState) void (*cursor_on)(IConsoleState *); void (*cursor_off)(IConsoleState *); void (*cursor_up)(IConsoleState *); void (*cursor_down)(IConsoleState *); void (*cursor_left)(IConsoleState *); void (*cursor_right)(IConsoleState *); void (*clear_screen)(IConsoleState *); void (*write_char)(IConsoleState *, char); void (*write_buf)(IConsoleState *, char *, size_t); void (*write_str)(IConsoleState *, char *); END_INTERFACE(IConsoleState)
This ends up defining two structures for me:
typedef struct IConsoleState IConsoleState; typedef struct IConsoleStateVtbl IConsoleStateVtbl; struct IConsoleState { IConsoleStateVtbl *_v; }; struct IConsoleStateVtbl { HRESULT (*QueryInterface)(IConsoleState *, REFIID, void**); uint32_t (*AddRef)(IConsoleState *); uint32_t (*Release)(IConsoleState *); // ... rest of definitions as seen above goes here };
The basic idea is fairly straight-forward: the interface you want to distribute to your clients must start with the IConsoleState header. For example, elsewhere in the BIOS sources, I define a ConsoleStateImpl structure:
struct ConsoleStateImpl { struct IConsoleState base; // rest of definitions goes here. };
In this way, I can conveniently define a single interface object. Objects which support multiple interfaces requires a bit more thought, but it's not overwhelmingly complicated to work with, and in my experience, are quite rare in practice. In fact, I've yet to write one in the decade that GCOM has existed as a project.
It should be noted that, because the console is itself a singleton object that never goes away, the AddRef() and Release() methods both are mapped to a function that just returns zero; they do nothing. QueryInterface() will return itself if you ask it for either IID_IUnknown or for IID_IConsoleState, or E_NOINTERFACE otherwise. In this way, this implements a proper COM object as far as any client software is concerned. You can look at the (rather bulgingly ugly at the moment) source file to find how IIDs are defined.
Neither aggregation nor delegation are a consideration, since these are truly primitive objects.
Creation of the first object happens in RAM, where you'd expect. First, I had to change the console output code to locate its static data in a data structure called BiosData. Eventually, all BIOS state will appear here. This allows me to assign a matchword to the structure, thus allowing programs loaded from secondary storage to scan through the first 256KB of memory (or whatever limit I choose) for it. Once references to the BIOS-provided objects are attained, services upon them may be called.
Here's the new console state definition:
struct ConsoleStateImpl { IConsoleState base; // Coordinates of the cursor on the screen currently. uint8_t cx, cy; // Flags relating to the cursor. // // Bit Purpose // 0: 1=cursor blink state visible; 0=cursor blink state invisible. uint8_t cflags; // This field counts how many times the cursor_off procedure has been // called. Every cursor_off must be matched with a corresponding call // to cursor_on before the cursor will ever be visible again. uint_t coffctr; };
And here's how it's mapped into the BIOS data structure:
#define BD_MATCHWORD (0x0BADC0DEB105DA7A) struct BiosData { uint64_t matchword; struct ConsoleStateImpl c; }; static struct BiosData g;
So, BIOS-resident code would access the console state variables by referencing g.c.whatever. Since this is in RAM, we must initialize g.c, and it now looks like this:
static void con_init(void) { g.c.base._v = (IConsoleStateVtbl *)ROM_RELOC(&console_vtbl); g.c.cflags = 0; g.c.coffctr = 1; clear_screen(); }
The new addition here is setting g.c.base._v to refer to the correct address for the IConsoleStateVtbl instance, which was defined statically elsewhere in the code.
Using the new services is pretty straight-forward:
void _start(void) { int erc; IConsoleState *ics = (IConsoleState *)(&g.c); con_init(); spi_init(); // con_write_string((char *)ROM_RELOC("Kestrel-2DX Development System.\r\n")); // con_write_string((char *)ROM_RELOC("ROM version 0.1\r\n\n")); con_write_string((char *)ROM_RELOC("ics=")); hex64((uint64_t)ics); LOG("\r\n\n"); ics->_v->write_str(ics, (char *)ROM_RELOC("Kestrel-2DX Development System.\r\n")); ics->_v->write_str(ics, (char *)ROM_RELOC("ROM version 0.1\r\n\n")); for(;;) timv(); }
First, we need to get a reference to the IConsoleState interface somehow; since we're already in BIOS, we can do this easily by just taking the address of our BiosData field. However, if we were to do this from RAM, we'd need to scan for the BiosData matchword, and then grab the reference from the discovered address.
Once you have the initial reference, invoking methods is done as you'd expect: dereference _v to access the appropriate function pointer, making sure to pass the interface as the first argument every time. Using the interface is otherwise identical to using the flat, statically linked interface.
The rest of the code is pretty much boilerplate or inefficient code purely for the sake of expediency. With COM integrated into BIOS like this, synthesis takes up 71% of the FPGA's area, which means I don't have much more room to play with. Some things help though:
- First, I can refactor the existing code to remove redundancies and streamline the API. This would help save space in the ROM.
- Second, I need to seal the keyboard input methods and SD card driver interfaces into their own COM objects. Well, I'll probably put the keyboard functions into IConsoleState, but SD card interfaces definitely need to go into their own interfaces.
- Third, I need a "root"-level COM interface which lets me discover other components compiled into BIOS, something like a name resolution service of some kind. It should also answer to me which storage device was used for bootstrapping, so that boot loaders don't need to have hard-wired boot drive information in them like PCs currently do. For instance, I don't expect every BIOS build I make to have TIM/V in it, and eventually, there'll be FPGA boards which lacks PS/2 hardware or video output capabilities. Some might use CompactFlash instead of SD/MMC cards. It'd be nice if the BIOS were modular enough to handle these configurations.
Once these three things are complete, I feel that the need to alter ROM significantly will fall off a cliff, meaning the Kestrel-2DX will more or less be "complete" as a computer design goes. From there, it's a "small matter" of writing an operating system for it, but that's a project for another day.
-
TIM/V: The Terminal Interface Monitor for RISC-V
09/24/2017 at 16:48 • 0 commentsA double-whammy of good news today! First, I'll talk about TIM/V, then I'll segue into SD card support.
TIM/V
If you're reading my progress, you're probably also familiar with the Commodore PET series of computers. If you've ever used them, you might also remember the built-in machine language monitor. You could break into the monitor at any time from BASIC with a SYS 4 command. This monitor's name was never printed to the screen; but it's official name is TIM, the Terminal Interface Monitor. Its heritage actually goes back to the KIM-1 computer!
Well, today[*], I'm happy to announce that my Kestrel-2DX computer has its own port of TIM, which I've named TIM/V. For expediency, I wrote it in C, and cannot yet do everything the real TIM can do. Part of the reason for this is limitations I'm running into as software size in ROM grows.
TIM/V does, however, have the minimum number of features required to provide self-hosted software development on the Kestrel-2DX. No, they're not convenient to use, particularly if you're pampered by today's software development environments. But it does work, and with enough patience and practice, it can be used to write literally anything that can run on the Kestrel-2DX.
TIM/V supports the following features:
- You can use the m [addr1][,addr2] command to inspect the contents of memory.
- You can use the = addr1 byte byte byte ... command to set the contents of memory.
- You can use the g [addr1] command to "goto" (actually, call) an arbitrary machine language subroutine.
- You can use the l addr1,blk-start,blk-len command to load a binary image into memory from an initialized SD/MMC card.
- You can use the s addr1,blk-start,blk-len command to save a binary image back to your SD/MMC card.
- You can use the i command to initialize the most recently inserted SD/MMC card.
There's no unmount command, so you can insert and remove cards arbitrarily. In fact, since the SD card interface lacks power control (not enough I/Os in the PMOD and no driver circuitry on the adapter circuit), if ever the SD/MMC card gets into a weird state, the only way to recover is literally to pull it out and re-insert it. This manually cycles power to the card. Remember to follow up with an i command to reinitialize it.
As of today, no commands exist to dump or set the contents of CPU registers, and certainly no support for breakpoints or watch-points exist yet either. These may come later after I've solved some higher priority issues/tasks.
Using TIM/V as a Boot Loader
As it happens, you can use TIM/V as a primitive boot loader. Assuming you just inserted an SD/MMC card into the slot, you can boot the computer with the following sequence of commands:
- i
- l 1bc00,0,2
- g 1bc00
The 2nd command tells TIM/V to load a binary image from SD/MMC starting at sector 0, for two sectors (so, 1KiB), and place the data at address $1BC00 in IPL RAM. The 3rd command, obviously, then executes the loaded code.
SD/MMC Card Support
Like the original Kestrel-2, the 2DX only supports a single SD/MMC card. It's all I have, sorry. I have future plans to expand this with the Kestrel-3; but, for now, it's bit-banged via the GPIA and only a single slave-select pin is mapped.
That said, since its inception six years ago, the Kestrel-2 has never been able to write to an SD/MMC card. There just wasn't enough space in the bootstrap code to include it! Thankfully, now that the 2DX isolates ROM from RAM, and ROM is truly permanent, write support now exists!
This means that any operating system I implement for the 2DX will properly be able to save work artifacts. (With the original Kestrel-2, I was intending on including write-support in a code overlay library, but never got that far.)
SDHC Support Still Lacking
Alas, the 2DX remains constrained to using normal SD cards (it's not tested with a proper MMC card). None of the SDHC cards I have seem to initialize (not one responds properly to a CMD8 command) per documented procedures, so I'm not sure what to do about this. I'll defer this problem for later, I guess.
Fun Facts and Notes
* - I'm announcing this today; however, I managed to complete the working code a mere three minutes before my scheduled talk on the Kestrel-2DX at yesterday's SVFIG meeting. In true Commodore fashion, this was a real "COMDEX Moment."
-
BIOS Update and Plans
09/13/2017 at 16:22 • 0 commentsA quickie, since I'm getting ready for work as I type this.
Status Update
The Kestrel-2DX BIOS now has ASCII-capable keyboard functionality. So, with character output and keyboard input now defined, I basically have a really dumb terminal at my disposal.
I need to wrap the functionality up into interfaces that are more convenient to use, however. For example, console output requires that you manually turn the cursor off before printing or moving the cursor to a new location on the screen, then turn it back on again afterwards (worry not; cursor_off() and cursor_on() properly nest up to 65535 levels deep). Likewise, keyboard input is handled using a kind of polling interface that would suit an evented software stack quite well, but is awkward for Unix-style interfaces.
So far, the software consumes 3688 bytes of ROM space, with another 3200 bytes consumed by PS/2-to-ASCII translation tables and the 8x8 fixed-width font, for a total space occupation of 6888 bytes as of this log post.
Future Plans/Ideas
Looking forward, I'm seriously thinking about migrating the design of the bootstrap firmware towards an event-driven architecture. The code as written seems already well suited to this architecture. In fact, in my dummy integration test code currently in ROM (which will undeniably change by the time you read this, as progress continues), there is an event loop which illustrates how to properly poll for keyboard input:
static void ps2_playground(void) { uint16_t rawkey, code; int valid; char ascii; int shifted = 0, ctrled = 0; for(;;) { kia_get_raw( (uint16_t *)(&rawkey), (int *)(&valid) ); if(valid) { code = rawkey & 0x7FFF; if((code == RAWKEY_CTRL_L) || (code == RAWKEY_CTRL_R)) { ctrled = (rawkey & RAWKEYF_RELEASE) == 0; } if((code == RAWKEY_SHIFT_L) || (code == RAWKEY_SHIFT_R)) { shifted = (rawkey & RAWKEYF_RELEASE) == 0; } kia_raw_to_ascii(rawkey, shifted, ctrled, &ascii, &valid); if(valid) { cursor_off(); con_write_char(ascii); cursor_on(); } } } }
I have written keyboard code many times over the years (Kestrel-2 emulator, Kestrel-2 FPGA, Kestrel-3 emulator, etc.), and none have been so compact as this, especially considering how trivial it is to maintain shift state (required for proper ASCII conversion). Funny to think that this code is not designed; it's not even unit tested. It just fell out of the design naturally, and "just worked."
So, for the purposes of writing the first interactive shell for the Kestrel-2DX, I think the only things I need to work on is:
- a centralized BIOS event polling and dispatching function,
- a mechanism for installing callbacks for said events, and,
- a mechanism for invoking BIOS functions from programs loaded into RAM.
The another nice advantage of building a BIOS in an event-driven manner like this, which I believe I touched upon in an earlier blog post on the main Kestrel Computer Project page, is that it completely isolates the event handlers from whether or not I choose to use interrupts. Currently, despite the CPU supporting them, I make no use of interrupts. But, if/when I add timer support, interrupts will need to be supported. Further, longer term, when I support SD card I/O, initial implementations will not use DMA; but I do intend on using DMA eventually, which again requires interrupts. The programming interface should remain invariant under these changing conditions.
I know from experience that making the KIA interrupt-driven fundamentally alters how to write a keyboard driver and, if you insist on a Unix-style synchronous I/O model, not in a pleasant way. You end up needing all manner of additional impedance matching constructs like FIFOs, and you need to worry about race conditions between interrupt handlers and FIFO consumers, etc. Although not technically multi-threaded, you suddenly need to think in multi-threaded terms. From there, it's not much of a leap to implementing a multitasking OS of some kind, which I feel I'm not quite ready for. I can write one (and I have written many some decades ago), but with only 32KB of usable RAM, the pay-off for doing so just isn't there.
It'd be nice to isolate programs from significant changes; going with an evented architecture not only eliminates all that cruft, but in addition, it results in substantially smaller programs. Just look at what was done with the Commodore 64/128 version of GEOS, to cite one real-world example. The Contiki OS is another great example.
In my case, 6888 bytes of 64-bit, non-compressed, RISC-V code compiled from C to implement 95% of a dumb terminal using a bitmapped display and a PS/2 keyboard for input is not a small achievement. But, I will claim that, as I add required features to the BIOS image, its size will not grow substantially, thanks to its evented architecture. I suspect it will culminate in a distinctly GEOS-like environment when I'm done.
-
More Detail: How I Intend K2DX to Help Bring Up K3
09/11/2017 at 17:54 • 1 commentOnce I get the Kestrel-2DX to a point where I can interact with it and program it live, I intend on starting work on bringing up the Kestrel-3. But, to do this, I need access to external RAM chips, so I can play with more than 24K to 48K of memory.
Port Kestrel-2DX to Icoboard Gamma/MyStorm Black Ice
Everything discussed herein applies equally to the Icoboard Gamma or to the MyStorm Black Ice. Both have an iCE40HX8K-compatible FPGA on board, and are equipped (or can be be so equipped) with 1MB of externally accessible SRAM. I'll refer to these boards as "the target" for brevity.
Perhaps one of the first things I can do is port the Kestrel-2DX design files to the target FPGA development board. Alas, both of the target boards lack human-interface I/O ports like VGA or PS/2 port, so for now, we need a simpler I/O approach.
My plan here is to use my Nexys-2 as a terminal for the Icoboard Gamma implementation. Thanks to both systems having at least one available, 3.3V, 4-bit PMOD connector, my current train of thought is to implement my SIA core on each of them, and get them both to talk to each other over a synchronous serial interconnect. I should be able to accomplish a link by stringing over just five wires.
The Nexys-2 would consume PS/2 keyboard input and serialize it for the Icoboard; and, vice versa, it would serve as a remote frame buffer for display output purposes. This kind of channel requires intelligence on either side of the link, of course, which means we could load additional telemetry functionality onto the link as well.
System software changes will be necessary to take advantage of this serial interconnect, of course. I'm not quite sure how to implement these yet, but I have some nebulous ideas. I'll discuss these later as time permits.
Beyond the Icoboard Gamma/Black Ice Boards
Once I have a remote system up, and have a working, interactive environment there, I can attempt to work on the CGIA video interface (Configurable Graphics Interface Adapter). The CGIA is the MGIA's replacement, supporting features such as color, higher resolutions, and other features one would expect of a contemporary computer. First generations of development won't have any blitters or GPUs; sorry. For those old enough to remember them, you can probably get a good mental image in your head of the CGIA's capabilities by comparing it to the Atari ST 520/1040's "shifter" video chip.
Once again, system software will require retrofitting to take advantage of the CGIA. I anticipate this should be rather simple; remember that the step prior required us to route bitmapped data over a serial interconnect, so by this point, acceptable video primitives and abstractions should exist in a state which can make this relatively easy to do.
After getting the CGIA-based system software running, self-hosted software development should then be possible (well, of course, after porting compilers and other development tools). This would probably be my first "minimum viable" Kestrel-3. If I can be so lucky as to port Fossil SCM over to the Kestrel-3 environment, that would totally be icing on the cake!
-
KIA Integrated.
09/05/2017 at 04:52 • 0 commentsWell, OK, that went a whole lot faster than I anticipated. :)
I guess my next step from here is to write a proper PS/2 driver and return ASCII values from typed keys, so that I can build that simple boot shell.