On some systems, Windows 3.1 and 3.11 in Standard mode crashes when it is configured to use APM, i.e. POWER.DRV is loaded. The crash only happens in Standard mode, not the typical default 386 Enhanced mode.
Since POWER.DRV is involved (“MS-DOS System with APM” vs. “MS-DOS System” in Windows setup), it’s obvious that power management/APM is somehow significant, but how, and why is only Standard mode affected? The explanation is a bit complicated.
Let’s start by looking at what may cause the crash. Windows 3.1 (specifically POWER.DRV) enters the idle state and calls APM service equivalent to INT 15h/5305h, CPU Idle. The call is executed using the 16-bit protected-mode interface. There are other possible failure modes which involve different APM services.
The idle-state crash is caused by the APM BIOS attempting to execute the HLT instruction with CPL (Current Privilege Level) other than zero, i.e. other than the highest privilege. The CPU state indicates that IOPL is 3 and CPL is 3 as well. The strange Intel x86 design is highlighted here: User code running with IOPL 3 can disable interrupts (CLI) and enter an endless loop, but it is not allowed to execute the HLT instruction.
Some APM implementations use port I/O to do their work; such systems have no trouble. But implementations trying to execute HLT or other instructions requiring CPL of 0 will crash Windows 3.1 in Standard mode.
But why does 386 Enhanced mode not have the same problem? The reason is very indirect and almost certainly unintentional. POWER.DRV allocates the LDT descriptor which is used for calling into APM BIOS using DPMI service 0000h (Allocate LDT Descriptors). The service returns descriptor(s) with privilege level same as the caller’s. And that’s where the difference is—in Standard mode, the descriptor is allocated in ring 3 and therefore calls into the APM BIOS with CPL of 3, but in 386 Enhanced mode, the descriptor is allocated in ring 0 and therefore the APM BIOS runs at with CPL 0 as well (and can execute HLT without trouble).
The problem is not applicable to Windows 3.11 for Workgroups because Standard mode support was removed there. It also isn’t applicable to Windows 3.0 because POWER.DRV was only introduced with Windows 3.1.
This problem is unquestionably a bug in Windows 3.1. The APM 1.0 specification clearly states that “The code segment descriptor must specify protection level 0, and the BIOS routines must be invoked with CPL = 0 so they can execute privileged instructions.”
Microsoft violated the APM requirements, but the problem is only visible on systems which provide APM (not all do), provide a 16-bit protected-mode APM interface (not all do), and execute instructions which require CPL = 0 and not just IOPL = 3 (again not all do).
The crash is reproducible on IBM ThinkPad T42 as well as in some virtualized environments. It is not clear if systems affected by this bug existed in 1992/1993 when Windows 3.1/3.11 was released. The workaround is to either use 386 Enhanced mode, disable POWER.DRV (e.g. comment it out in SYSTEM.INI) in Standard mode, or possibly prevent APM from offering the 16-bit protected-mode interface by hooking and modifying INT 15h.
June 2023 Update: Poking around POWER.DRV revealed a workaround for the crashes. It is possible to add the following to SYSTEM.INI:
As the name of the setting suggests, this causes POWER.DRV to use the real-mode APM interface rather than the protected-mode one. This will have a slight performance impact, since APM services can’t be called directly, but it avoids the crashes in the 16-bit protected mode APM interface and still allows APM to function.