A minor mystery recently popped up while running IBM’s OS/2 1.1 (1988), the first OS/2 version with the Presentation Manager GUI. While Microsoft’s and IBM’s releases of OS/2 were fully compatible from application perspective, there were differences in the drivers supplied and the kernel itself was slightly different.
One of the differences was that while all Microsoft’s OS/2 kernels (starting with the May 1987 beta) detected a 386 CPU and used 386-specific instructions to switch from protected back to real mode, IBM’s OS/2 1.0 and 1.1 kernels did not. IBM only used the 286-specific method (which MS’s OS/2 kernels also used, but only on 286s) of mode switching: triple fault the CPU, trigger a shutdown cycle, let the motherboard reset the CPU, and ask the BIOS to skip the POST code and jump to a specified address soon after the CPU restarts execution.
This method is not supported by most virtualization products (Virtual PC, VMware) and may not work with some recent BIOSes. But that’s not the interesting part. The really interesting part is that when running a DOS box and hence executing in real mode, IBM OS/2 1.0 and 1.1 in some cases executes the infamous 286 LOADALL instruction. In OS/2 1.0 this appears to be fairly rare, in version 1.1 LOADALL is typically executed within seconds of switching to the DOS box.
Why would OS/2 do this? It is clearly a performance optimization. OS/2 is executing in real mode but needs to access some memory beyond 1MB. The LOADALL instruction is used to load the in-CPU descriptor cache that will allow a segment register (usually ES) address up to 64K of memory anywhere in the 16MB 286 address space.
Memory past the 1MB boundary is of course normally accessed in protected mode, but that has numerous disadvantages. The machine state must be completely changed, real-mode code can (typically) no longer be executed, interrupts must be disabled for relatively long periods of time. Worst of all, returning back to real mode takes quite a while; resetting the CPU in order to switch from protected to real mode works, but it is not fast.
Using LOADALL neatly sidesteps most of these issues. The CPU never leaves real mode and although interrupts are still dangerous (reloading a segment register will overwrite its “magic” contents set up by LOADALL), the entire operation takes far less time and it is even possible to execute memory copies “speculatively” with interrupts enabled.
It is well known that Microsoft’s HIMEM.SYS uses LOADALL on 286 systems for exactly these reasons. It is also well known that although 386s and perhaps some later CPUs implemented a LOADALL instruction, it used a different opcode and was not compatible with the 286 variant. For completeness it should be added that the opcode used by 286 LOADALL (0Fh 05h) was later reused for the SYSCALL instruction.
With these facts in mind, one might wonder how IBM’s OS/2 1.0/1.1 ever worked on 386 and later CPUs, or if it ever did? Work it certainly did, but how then?
IBM sold 386 systems at the time, initially the PS/2 Model 80, eventually joined by Model 70 and others. OS/2 1.0/1.1 “naturally” includes specific support for these systems and does not execute LOADALL. However, generic 386 AT-compatibles can also run OS/2 1.0/1.1. On those systems, IBM’s OS/2 will triple fault the CPU to get back to real mode and it will execute LOADALL. That should not work, but it does. Why?
The answer lies in a typical 386+ BIOS. The BIOS installs an invalid opcode handler, detects attempts to execute a 286 LOADALL instruction, and emulates it well enough to satisfy questionably written software such as IBM OS/2 1.0/1.1.
The BIOS cannot fully emulate all aspects of LOADALL, but can reliably emulate the typical use case of LOADALL: The caller is executing in real mode, wishes to remain in real mode, and wants to modify the descriptor cache of one or more segments such that the base is above 1MB. It is assumed that CS and SS remain normal real-mode segments. The GDT/LDT/IDT/TSS may be modified.
In theory, software could use LOADALL in order to switch from real to protected mode, but that is somewhat pointless because such a switch can be achieved without the use of undocumented instructions. Using LOADALL to switch from protected to real mode is not possible at all, and utilizing LOADALL in protected mode is probably more work than achieving the same effect through documented means. These limitations allow a BIOS to emulate the common LOADALL uses.
It was previously mentioned that the 286 LOADALL opcode was later recycled for use by the SYSCALL instruction. Does that mean systems with newer CPUs cannot utilize the BIOS LOADALL emulation? On Intel processors, SYSCALL must be explicitly enabled and even then is only recognized in 64-bit mode, so it cannot possibly conflict with software written for the 286. On AMD processors, SYSCALL is not limited to 64-bit mode but must still be enabled via EFER.SCE, which again won’t be the case with software designed to run on 286s. Therefore SYSCALL does not conflict with 286 LOADALL emulation.
It would be interesting to know what Intel thought about Microsoft’s use of LOADALL. It’s fairly certain that Intel supplied Microsoft with non-public LOADALL documentation, but it’s unknown whether Intel sanctioned LOADALL use in retail software. Especially since Microsoft only started using the 286 LOADALL instruction after the 386 was already on the market and did not support it.
Did IBM really infer the CPU used based on the model/submodel instead of detecting the 286/386 CPUs properly?
Yes and no. There is 386 CPU detection, but that appears to mostly control FPU management. The use of LOADALL depends on whether the system has ABIOS (Microchannel). The assumption seems to be that if the system doesn’t have MCA, it must be a 286 and LOADALL is safe to use. That was certainly true for IBM machines at the time.
The detection couldn’t have only been for MCA since the PS/2 models 50 and 60 were 286-based and had MCA. I don’t remember if those models also had ABIOS or not.
Note pre-XMS versions of RAMDRIVE.SYS and SMARTDRV.SYS also used the 286 LOADALL. Additionally DOS 3.3 and 4.0 reserved a 102-buffer at 800h (70h:100h) for use by these programs so that they didn’t have to save and restore the contents of this area. The LOADALL buffer was removed in DOS 5 to save memory since 386 and 486 PCs were common at that point (1991) and programs used XMS to access extended memory leaving HIMEM.SYS to deal with LOADALL on 286 PCs.
The OS/2 kernel has 386 detection but most of the decisions depend on whether the machine is a PS/2 (MCA, ABIOS) or not. Model 50/60 definitely should have ABIOS. The result of INT 15h/04h determines whether LOADALL will be executed or not. The alternate code path seems to switch to protected mode and back, but it is a lot more complex.
I see references to LOADALL in SMARTDRV sources from ’89 (part of Windows 2.11 BAK) dating back to 1986. There’s also a mention of “INTEL special documentation of LOADALL instruction”. It’s pretty clear that Microsoft had the info from Intel and that they had it in 1986 or sooner.
It would be interesting to know whether Compaq’s early 386s already had LOADALL emulation built into the BIOS, the way it was done in the 1990s as a matter of course.
Are you aware of IBM ever using LOADALL in IBM-written software? I assume the OS/2 LOADALL code had been written by Microsoft 🙂
To my knowledge IBM never used LOADALL in any of their software. For example IBM VDISK.SYS never used it (it always used INT 15h function 87h) while pre-XMS versions of MS RAMDRIVE.SYS did. All IBM ever did in regard to LOADALL was reserve the 102-byte buffer at 800h in DOS 3.3 and 4.0 so that part of the IBMBIO.COM/IO.SYS data wasn’t there and didn’t have to be saved and restored.
As for early Compaq 386s emulating LOADALL; if you have access to one of these systems then boot DOS and disassemble the INT 06h handler and it should be readily apparent if it does or does not. I would suggest using the enhanced DEBUG on my PC DOS Retro site as it recognizes 386+ opcodes unlike the standard DOS DEBUG which only recognizes 8086/8087 opcodes.
No, I don’t have an old 386 Compaq. If I did I wouldn’t ask 🙂 Actually a BIOS ROM image would be enough.
I have a disassembly of the Phoenix BIOS from a Gateway 2000 486/33 system I had back in 1991. It has LOADALL emulation. This BIOS is dated 05/25/91 but it has an earlier date of 01/15/88 at F000h:FFF5h which is likely the date of the core BIOS codebase so it’s likely that 386+ BIOSes emulated LOADALL at least as far back as 1988.
Interestingly, IBM’s OS/2 Technical Reference actually infers to this behavior:
“On AMD processors, SYSCALL is not limited to 64-bit mode but must still be enabled via EFER.SCE”
I wonder, on a such CPU if SYSCALL is not enabled, and the LOADALL 286 instruction is encountered, what exception would be generated? If it’s just “invalid opcode” then fine, but if it’s something like “SYSCALL disabled”, then perhaps it would be trouble for an eventual BIOS emulation of the LOADALL 286 instruction. If such a thing even exists on new systems, of course.
I’m pretty sure it’s #UD (invalid opcode). That generally happens for all instructions that are disabled or not available in whatever mode the CPU is in (not counting privileged instructions, those produce #GP or general protection fault).