I promised last time that I will only write a new post if I have something interesting to say. Today’s topic is interesting to maybe three people in the world, but I’m sure they will be thrilled. Today I was asked a question about the SMSQ/E boot sequence and it’s been so long that even I had to research again how it works, so I might as well write it up now. In the following text I’ll try to explain the early boot sequence of both QPC and Q40.
Attention: The whole text is based on me looking at the sources for roughly two hours. I took care to be accurate, but I’ve been wrong before in my life. Twice or so.
SMSQ/E is a bunch of modules
An SMSQ/E file or ROM consists of separate modules, one concatenated after the other (hardware initialisation, screen driver, disc driver, etc.). Every module has a header except the first one, the host module. As most SMSQ/E files can be executed directly from another host OS, the host module must start with code. It has a trailer instead, which is at the end of the whole SMSQ/E file (so one could say the host module includes all other modules).
ROM locations
On the Q40, the ROM is seen in two places: $00000000-$00018000 (only first 96kb) and $FE000000. The lower region has one special feature: when the ROM is visible, all write accesses go to a shadow RAM area (default mode). The hardware can switch the mode (by writing to address $FF018000) so that a read will be from the shadow RAM and any write will be ignored. The ROM is hidden in this case. The mode can be switched back by writing to address $FF010000.
The CPU will get its initial instruction pointer from address $00000004, i.e. in the lower ROM area. In case of SMSQ/E this will point to $FE000062, so execution continues in the upper ROM copy. The lower copy of the ROM is not used again after that.
On QPC the SMSQE.BIN file is simply loaded to $00030000 and the virtual CPU starts executing instructions from there.
1st module: Host module
Q40: sys_boot_q40_rom_asm
- Offset 0: File was loaded through LRESPR from another OS
- Disable interrupts etc.
- Copy the whole SMSQ/E binary to $28480 so that it is at a known and safe place
- Disable caches
- Jump to 2nd module
- On ROM entry point:
- Disable caches
- Setup dummy stack
- Clear first 256 bytes of legacy screen RAM at $00020000 for whatever reason. Probably for debugging purposes.
- Jump to 2nd module
QPC: smsq_qpc_host_asm
- Setup dummy stack
- Jump to 2nd module
2nd module: SMSQ loader
File: smsq_smsq_loader_asm
- Call hardware initialisation in 3rd module. This will return hardware dependent data that will be used in the rest of the code.
- Copy the code of sms_wbase to an area below RAMTOP. sms_wbase is a simple function that writes a word located in D0 to the address in A5. It is used to write to the low RAM area, which for Q40 needs the special bank switching mentioned above. In QPC the RAM area is nothing special and the whole routine consists of one instruction: move.w d0,(a5)+
- Update the vector sms.wbase with address of the newly copied sms_wbase
- Copy code area from label ldm_reset to lpm_resetend (the reset routines) to first slice in the module table (see below). The first slice is always at sms.base = $900.
- Copy remaining modules to available slices. If no fitting slice could be found, copy to just below RAMTOP. Copying is done by the routine returned by the hardware initialisation (q40_mdinst/qpc_mdinst).
This means that after this step the original ROM or RAM copy of the SMSQ/E file is not used anymore! - Jump to 4th module, which is the OS initialisation, but this time to the new RAM copy!
3rd module: Hardware initialisation
Q40: smsq_q40_hwinit_asm
QPC: smsq_qpc_hwinit_asm
- Execution starts at label hwinit
- Do the actual initialisation (scan RAM size, setup MMU, etc.)
- Setup data and pointers for SMSQ loader:
- d7 = RAMTOP
- a2 = config block
- a3 = sms_wbase (the routine mentioned above that copies d0 to (a5))
- a4 = module table. This is a table that denotes special regions (“slices”) of memory where modules can be copied to and that would otherwise be unused.
On Q40 this is only one slice:
“sms.base-4 to sms.base”. The first slice is special, it will always be used to host the reset routine, no matter the size. This is why it’s only 4 bytes
On QPC there are 3 usable slices:
“sms.base-4 to $4000”
“$4200-4 to $c000”
“$10000-4 to $17d00” - a5 = loader communication block
- a6 = module initialisation function
On Q40 this is q40_mdinst, on QPC this is qpc_mdinst. Both copy the memory pointed to by (a0) to 4(a1) up to (a2). The Q40 just adds the ROM bank switching to the mix.
- Return to SMSQ loader
4th module: OS initialisation
File: smsq_smsq_base_asm
- This starts at label smsq_base
- Initialise real stack now
- Jump to sms_reset
- Clear memory
- Initialise vector area ($0008-$0400)
- Initialise trap vectors
- Initialise system variables
- Initialise SBasic stuff
- Call init_ext, which will call the initialisation code of all remaining SMSQ/E modules
- … from here on Job 0 (SBasic) basically starts running and the OS initialisation is finished
The end
The whole boot scheme is pretty clever but also hard to understand at first glance. But it does the job of catering for many different platforms and usage scenarios (boot from ROM, loaded from other host OS etc.) very well. For me it was enjoyable to once again dig a bit deeper into the OS I once have loved and known so well and I hope at least 3 other will join me in my delight 😉
Updated with input from Peter Graf.