First, the cool bit:
Booting from an image stored in ROM, with purely non-destructive modifications to the system. No overwriting drivers in your system ROM, no overwriting add-on cards' DeclROMs. Everything should work as it did before, except you can now choose a ROM image from the Startup Disk control panel, and boot from it.
All development & testing done on a IIx, which should work out of the box on an SE/30, and with minor modifcations work on many other systems.
dougg3 on the 68kmla forums developed a custom ROM SIMM that works with many 68k systems (IIx, SE/30, IIsi, IIci, IIfx, Quadra 700, possibly others), and an accompanying SIMM programmer to allow writing to the ROM SIMM.
The SIMM can hold up to 2MB of storage, and most of the systems it work with have ROMs significantly smaller than that (the IIx has a 256k ROM), which leaves plenty of space available for other uses.
Unfortunately, on the IIx at least, only the first 1MB is mapped in at boot time. But, that still leaves 768KB for us to play with during boot, which is still plenty. So I've placed a 768KB image immediately following the System ROM and programmed it into the ROM SIMM.
In order to boot from the image, we need a driver, and it must be loaded by ROM at boot time. Declaration ROMs are the firmware on 68k mac peripherals (PDS cards, Nubus cards, etc.), and this firmware is read at boot time by the system ROM, and are extremely flexible. Among other things, they can allow booting from a peripheral device.
So, we can use a peripheral device's ROM as a way to get our image to boot, but we don't want to just stomp on the peripheral's ROM. That's needed by the peripheral after all. Fortunately, most non-video peripheral devices, such as ethernet cards, have mostly empty ROMs. For example, Asante Nubus ethernet cards are 32KB in size (27C256), and only use about 200 bytes of that.
I have some code that reads in a DeclROM, in theory any (insert disclaimer) DeclROM, and add the necessary magic to make that ROM boot our ROM image, while still allowing full functionality of the original peripheral.
Specifically, it appends an sResource to the DeclROM, containing both the driver, and an sRsrcBootRec resource, which is just a blob of code that gets run at boot time to do whatever is necessary to make the card bootable (in this case, loading our driver, which makes the system see our ROM image as a disk).
Now, the boring details:
It's a hacked up set of POSIXy routines for reading and writing Declaration ROMs. Basically, there's really only 2 routines: sdeclrom_readfile() and sdeclrom_writefile(). Everything else is currently done by manipulating the structs directly for the moment. Ugly, I know. Sorry. It's better than nothing, right?
This also includes a patchrom.c file which takes a declrom file as its only argument, and inserts the above bits, and writes out a "mergedrom.bin" file with our bits added. This code can act as an example for using the library.
The declrom input I'm using has been generated by reading the ROM on an EPROM burner. It is possible to generate the ROM image from a booted system, but care must be taken. DeclROMs have a concept of "byte lanes", as in only certain bytes within a word are used. This allows the use of an 8bit EPROM instead of needing to have the ROM 32bit accessible. So when generating the image, make sure only valid byte lanes are used.
The driver: romdrv 0.3
This is the ROM disk driver. It includes constants at the top for the size and location of the ROM image, but other than that is just a generic disk driver that creates a locked (read-only) disk structure, and only allows reading.
The boot code: sRsrcBootRec1
This is the boot code that just loads the driver from the DeclROM. The added sResource is expected to have a name that matches the driver name, minus leading "." that device drivers have prefixed by convention. This code doesn't know anything about the added sResource and is completely generic. It will read the name of the sResource (in this case "Romdrv") and load a driver with the same name by prepending a "." (in this case ".Romdrv").
Tips for prospective DeclROM hackers:
Although most Nubus ROMs I've found are 27C256, which are UV erasable, you'll probably be erasing, burning, and testing a lot. Erasing is slow. The 28C256 is pin compatible EEPROM, electrically erasable, and leads to much faster testing cycles.
At the end of every system ROM since the introduction of the Mac II, is a DeclROM which shows up as "Slot 0". My big plan had been to somehow modify this declrom to include the romdisk driver and boot code, to have a self contained bootable solution without relying on a peripheral's declrom.
There's good news and bad news on this front.
The bad news:
With the IIx ROM (and presumably SE/30, which is identical, and Mac II), the "Slot 0" DeclROM isn't referenced. In fact, it can be completely zeroed out and it doesn't matter. Slot Manager appears to fake up an entry, even when none is present.
The good news:
The IIsi uses the Slot 0 DeclROM to provide drivers for its builtin video, so it stood to reason that at least the IIsi ROM would use the Slot 0 DeclROM. Additionally, the IIsi ROM is known to work in the IIx & SE/30, as a 32bit clean replacement ROM. Sure enough, I can construct a new "Macintosh II" DeclROM from scratch, which includes the romdisk driver and the boot code, put it into the IIx, and the ROM disk is recognized on boot.
Even better news is, there is sufficient room at the end of the IIsi rom for this all to fit (although losing the IIsi's video driver).
More bad news:
The Slot 0 based ROM disk does not seem bootable, at least using the same sRsrcBootRec that non-Slot 0 based DeclROMs use.
So, here is the Slot 0 DeclROM created from scratch. I simply blat this over the end of the IIsi ROM:
dd if=newrom.bin of=systemrom.bin seek=521616 count=2672 bs=1
Then recompute and update the ROM's checksum. The first 4 bytes of the system ROM is the checksum, code to compute the checksum is here
Then append your disk image, and away you go.
Other notes on using the IIsi ROM:
The IIsi ROM is twice the size of the IIx rom: 512KB.
However, I'm currently using a 1.5MB disk image appended to the ROM, since the ROM SIMM can hold 2MB.
The reason the Slot 0 declrom isn't bootable via the normal methods for peripheral declroms is due to the way peripheral booting and startup device selection is handled.
The boot record in a declrom resource is code that gets executed in order to boot the device. This code gets run only if the pram contents say to run it.
For slot device startup (which this would be, since it's from a slot declrom, and is not a SCSI device) selection, 3 pieces of information are stored in PRAM: Slot, device id, and resource id.
Unfortunately, a 0 slot number is not being interpreted as "Slot 0".
Other declrom resources that contain code:
Primary Init: The documentation says this has to be minimal. It gets run early in the boot process (after chimes, before video initialization, afaict), with interrupts disabled, and the only mac toolbox calls that can be made are Slot Manager calls. The key here is we'll need to allocate memory, and update the driver unit table. But it's not clear either of those things have been initialized at this time.
Secondary Init: This happens much later, after the boot device is already booting. It happens after the loaded System file patches ROM, and right before INIT 31 time. So, it isn't very useful for loading a boot driver.
I'm still searching for a good system rom bootable solution.
The IIsi ROM actually has a decent amount of available space. The .netBOOT driver isn't enabled by default, for instance, so I've been experimenting with replacing the .netBOOT driver with the ROMDisk driver, the way I had with the .Sony driver previously.
The .netBOOT driver is 580 bytes. I've gotten the ROMDisk driver down below that, and can now easily replace the .netBOOT driver resource, and get it loaded on startup. But, there's always a catch.
In this case, the catch is how to boot from it. This was easily bootable before when replacing the .Sony driver because the system thought it was a floppy disk, and floppy disks are always preferred. The .Sony driver is special cased. This also had the unfortunate side effect of always being the boot device, even when a disk was available.
.Sony driver aside, the system normally has 2 paths for booting: booting from a slot (already explored), and booting from a SCSI disk. Unfortunately, this is neither. It doesn't have a SCSI ID.
So, if it can't be set as a startup disk from the Startup control panel, I've explored forcing it to be the boot device based on keys held down at boot. This is how the Classic's ROM disk works. Unfortunately, the .netBOOT driver is loaded after SCSI has been initialized, so it can never be the first device in the disk queue. This isn't a problem for the Classic ROM, because again, it is special cased.
I'm looking at patching the boot code to be aware of my driver specifically, or for the driver to manipulate the disk queue in order to masquerade as the primary boot device.
One way to get the ROM disk recognized as a bootable disk is to have the boot process believe it is a floppy. During the boot process, the IIsi ROM uses this to check if an entry in the drive queue is a floppy disk:
This is checking to see that the driver reference number associated with the disk is -5 (aka driver unit entry 4, which is the floppy). The good news is, there's 8 trailing bytes which can be commandeered as such:
So if it is a floppy, return to the caller, if it is not a floppy, see if the driver reference number is -50 (aka driver unit entry 49, which is the .netBOOT driver). This consumes all 8 bytes.
Now, the ROM disk is recognized as a floppy, which is always booted from if present, which isn't exactly what I'm looking for. So I'd like to key booting off of something, like a held down key combo. There is an easy way to check keys down using the KeyMap (0x174) global variable. The obvious thing to do is check this when the driver is opened, and if they keys aren't held down, don't create the drive queue entry, and the system will continue booting as normal. Unfortunately, ADB, the keyboard driver, and the KeyMap global variable are not initialized at the time ROM drivers are loaded and opened, so the check cannot meaningfully be performed in the driver open routine.
The stock .netBOOT driver checks for keys down during the Prime routine, so when the system later tries to read boot blocks off the pseudo-device, the KeyMap is initialized and the check is valid. This gets a little tricky for the ROM disk driver because 1) I'm tight on space to fit within the .netBOOT driver size constraints, and 2) avoiding stray keystrokes interfering with normal (booted) driver read operations.
BOOM. Conditionally bootable ROM disk for IIx (and probably SE/30, IIsi, and with image modifications, IIci).
If holding down 'r' at boot, the system will boot from the ROM disk.
Files: romdrv0.4.cpt.hqx ROMDisk driver source romdrv0.4.drvr The raw DRVR resource suitable for dd'ing into a ROM image iisi+romdrv0.4.bin Modified IIsi ROM with driver, without disk image appended iisi+romdrv0.4+img.bin Modified IIsi ROM with driver, with bootable 6.0.8 (Minimal system install for II, IIx, SE/30, IIsi) disk image
How this works:
This is based off the IIsi ROM, with the custom ROMDisk driver installed in place of the .netBOOT driver located in the IIsi ROM. The .netBOOT driver isn't used in the IIsi ROM, so replacing this driver doesn't result in a loss of functionality over just using a stock IIsi ROM. In order to get the driver loaded, a change to the .netBOOT DRVR resource was needed, documented in the .netBOOT driver thread in this forum. Additionally, the change from the earlier post to treat the ROMDisk driver as a floppy disk to make the system treat it as a bootable disk was made.
The ROMDisk driver its self does the following:
When it is opened (early in ROM initialization), it creates a drive queue entry and adds it to the system drive queue.
Later, when the system selects a boot device, it is seen as a floppy disk, and always wants to be booted from (assuming no floppy is present). The system then goes to read the boot blocks off the device. The ROM's read routine uses a field in the drive queue entry to see if it has previously been called (writeProt entry), and if not, initializes that entry to 0xFF (write protected), and checks the KeyMap global variable to see if the 'r' key is held down. If it is, it will return an error. If not, a regular read is performed. On subsequent read operations, the drive queue entry has been initialized and this check will no longer be performed.
As a result of all this, if the 'r' key is not held down on boot, the system still has the drive queue entry, but no disk insertion event was posted, so it doesn't show up in the Finder. It is possible to issue the insertion event at any time while the system is booted in order to get the Finder to see the disk.
Some potentially useful tools used during the development of this: dumpdrvr.cpt.hqx A simple tool that displays all loaded device drivers, and whether they're ROM or RAM based. Some ROM drivers (notably all DeclROM drivers) get loaded and run from RAM, so this isn't an indication of where the driver originated, simply where it is currently running from. dumpdsk.cpt.hqx Another simple tool that displays the drive number, and the drive's driver entry number, of all drives in the system drive queue. Use this to see if the ROMDisk's drive is in the queue, but not not mounted. Use this in conjunction with the dumpdrvr tool to see what driver is associated with each drive in the queue. This also lets you post a diskEvt notification for a disk to get the Finder to try mounting it. sc68 The sc68 atari project includes an assembler, as68, which was helpful for generating machine code to patch the IIsi ROM. The assembler can run under linux or OSX, and just dumps the machine code for the assembly you've written, without any sort of executable file type wrapper.
As an addendum to this, the driver uses a 512KB disk image (512KB ROM + 512KB disk image). In some of my earlier efforts for booting from ROM, I had troubles with disk images beyond the 1MB boundary. Those problems may have been misattributed, or may have been specific to the IIx ROM I was initially working with. Whatever the reason...
Testing with a 1.5MB image seems to work fine, as long as the driver's size constant is updated accordingly.
Good news and bad news. Bad news is 1.5mb disks don't actually work with the above links. The first 1MB is all that works (512KB disk), at least for booting. It worked in my testing simply because the important bits fell in the first 512KB.
The problem is the ROM has loaded and executed the driver in 24bit mode. The ROM lives from 0x00800000 to 0x008FFFFF in 24bit mode, so only 1MB. The driver can call SwapMMUMode() to switch to 32bit mode to access the ROM at the 32bit location 0x40800000, however the driver was loaded from a resource in 24bit mode, so the program counter has a 24bit address with a flag set in the high byte indicating it is a resource. So swapping to 32bit mode with the program counter containing a 32bit dirty address causes a crash.
The good news is, I've got a fix that seems to work. I've implemented the ROM disk image accesses in a separate function that gets copied into a location in RAM that is both 24 and 32bit accessible, and the driver calls that function, which then does the SetMMUMode switch (and switches back before returning). However, that's not enough. When the ROM sets up the MMU, it only maps in the size of the ROM its self, which in the case of the IIsi ROM is 512KB. However, I suspect that's getting rounded up to 1MB since 1MB is clearly available. My workaround has been to modify the ROM size stored at offset 0x40 within the ROM its self from 0x00080000 to 0x00200000.
Well, the above worked on my IIx, but did not work on the IIci or IIsi.
The problem seems to revolve around setting the ROM size to 2MB. It looks like that size is used by other parts of the system, and the ROM has chimes of death before video is displayed on both IIci and IIsi.
However, we still need 2MB of ROM mapped in. So... Spelunking for ROM MMU layout I went, and found:
41B5A C38B Exg.L D1, A3
41B5C 32FC 0002 Move $2, (A1)+
41B60 32FC 0001 Move $1, (A1)+
41B64 47F9 FFFB E49C DT106: Lea.L ($-41B64), A3
41B6A 47FB B8F8 Lea.L DT106(A3.L), A3 ; Getting base of ROM
41B6E 22CB Move.L A3, (A1)+ ; Storing base of ROM in 32bit mode
41B70 202B 0040 Move.L $40(A3), D0 ; Getting size of ROM at offset $40
41B74 22C0 Move.L D0, (A1)+ ; Storing size of ROM in 32bit mode
41B76 C38B Exg.L D1, A3
41B78 222D FF88 Move.L $-78(A5), D1
41B7C 22C1 Move.L D1, (A1)+ ; Storing base of ROM in 32bit mode
41B7E 22C0 Move.L D0, (A1)+ ; Storing size of ROM in 32bit mode
41B80 0281 00FF FFFF And.L $FFFFFF, D1 ; Getting 24bit base of ROM
41B86 22C1 Move.L D1, (A1)+ ; Storing base of ROM in 24bit mode
41B88 22C0 Move.L D0, (A1)+ ; Storing size of ROM in 32bit mode
41B8A C38B Exg.L D1, A3
41B8C 4E75 Rts
This is essentially laying out a table to construct MMU entries. So, I've modified the ROM to leave the size of ROM (at offset 0x40) unchanged at 0x00080000, but modified this routine.
Starting at 41B64, I replaced the code with hard coded constants:
I've got a slight change to the ROMDisk driver to allow conditionally copying the disk image into RAM on boot.
If you hold down just 'r' on boot, you get the same behavior as now, entirely ROM based (well, minus some RAM use by the driver). If you hold down both 'r' and 'a' on boot, it will allocate RAM and copy the contents of the disk image into RAM, and mark the disk as writeable. I've done some testing with this copying small programs into the booted image and running them, and it seems to work for me using the SE/30.
I'd appreciate any feedback you guys might have.
That's pretty cool--seems to work fine for me! It rebuilt the Desktop file as soon as I booted. I was able to copy various small files and everything seemed OK with light testing. I booted into 24-bit addressing so only a small amount of RAM is usable:
With R/W mode: Largest Unused Block = 5,674K
With R/O mode: Largest Unused Block = 7,212K
Thanks! I noticed a couple things with this in my testing:
1) Sometimes, on some machines, the machine would lock up when trying to boot from the RAM disk. I suspect this is because I was doing a memory allocation in the Prime (read) call, which is kind of sketchy. If interrupts are disabled at the time of the Prime call, or if the Prime call is made from an interrupt, things would be bad if I tried to make a memory allocation.
So, I've restructured this a bit so the memory allocation is made in the Open call, which is defined to be safe. The memory is always allocated on open, then if we're booting from ROM, I free the memory. Hopefully this is a bit more reliable.
2) I noticed the same problem you did with slow SCSI drives. My IIsi's drive is super slow to spin up as well, and I ended up always boot from ROM.
I added a counter here, so for the first 16 reads, if the 'r' key isn't held down, it'll return an error. This seems to be sufficient for my slow SCSI drive...