BIOS INT 15h AH=86h: A Compatibility-Bounded DOS Wait
Use the BIOS wait service with checked status, realistic timing precision, bounded fallbacks, and clear separation from application deadlines.
Real-mode software sometimes needs to pause without burning CPU cycles in a calibrated loop. On AT-class BIOSes, INT 15h with AH=86h requests a wait interval. It is useful for coarse pacing and compatibility code, but it is not a universal high-resolution timer, a scheduler, or a substitute for an application’s own deadline logic. BIOS implementations vary, old machines may not provide the service, and the resolution reported in historical references is close to one millisecond on many systems rather than one microsecond.
The interface is compact: put the requested interval, in microseconds, in CX:DX, set AH=86h, and invoke INT 15h. On return, carry clear means the wait completed; carry set means the request failed. Treat the status byte in AH as diagnostic information only when the call reports an error, and do not infer success from elapsed wall time alone.
; Request a nominal 20 ms wait. CX:DX is an unsigned microsecond count.
mov ah, 86h
mov cx, 0000h
mov dx, 4E20h ; 20,000 decimal microseconds
int 15h
jc wait_unavailable
; Carry clear: the BIOS reports the requested wait has elapsed.
This is a small calling-convention example, not a complete timing subsystem. The caller still needs an unsupported-service path, a deadline policy, and a way to test behavior on its target firmware. Do not use this call from an interrupt handler or a DOS critical-error callback: it can block while the context that invoked it must finish promptly.
What the service promises, and what it does not
The documented operation is a BIOS-managed wait for a requested interval. It does not promise that the CPU will be asleep, that interrupts are disabled, that a particular amount of useful work will occur, or that the returned time is exact. Many BIOSes historically derived the wait from the AT real-time clock periodic interrupt, whose common base period is about 976.6 microseconds. That explains why a request smaller than that period cannot be assumed to have microsecond precision. Other firmware may use a different timer implementation, so the actual accuracy and minimum interval are machine-dependent.
The CX:DX value is an input duration, not a timestamp. It should be built from integer microseconds carefully: calculate in a wider type in C, check for overflow, then split the final 32-bit quantity into its high and low words. A buggy conversion can turn a short request into a long pause. For a fixed delay, express the constant visibly and label the units instead of passing an unexplained pair of register values.
Do not confuse AH=86h with the BIOS wait-on-event function AH=83h. The latter has an asynchronous event-flag contract, while AH=86h asks the firmware to wait synchronously. They differ in state and completion behavior. A program that sees an error related to another outstanding wait must not silently retry forever; log the service, input duration, returned status, and whether its own code or a resident component previously armed an event wait.
Choose a timer for the requirement
For a user-visible pause such as a brief status message, BIOS wait may be a reasonable compatibility choice. For animation, audio sampling, a serial protocol, disk-controller deadlines, or a benchmark, it is generally too vague. Use the timer or device-specific mechanism that owns the event and measure the result. INT 1Ah tick counts, PIT channel 0, the RTC periodic interrupt, and a device’s completion interrupt each have different ownership and resolution constraints. Reprogramming a system timer to obtain finer delays can break DOS, BIOS services, and resident drivers; it is not a safe universal fallback.
Busy-wait loops are not automatically better. Their duration varies with CPU speed, cache state, emulation pacing, and compiler output; they monopolize the CPU and can fail when virtualized. A calibrated loop can still be appropriate for a documented hardware settling interval when that hardware requires it, but use a bounded counter and verify the target. Do not use an unbounded loop that waits for a device bit to change without a separate timeout.
An application deadline should be represented independently from a delay call. If the requirement is “finish within 250 ms,” record the start and completion against a monotonic time source available in that execution environment, compare elapsed time, and handle timeout separately from a requested sleep. A BIOS wait can overshoot, be unsupported, or be interrupted by platform behavior; it does not itself prove a deadline was met.
The asynchronous AH=83h service is worth understanding even when the program chooses not to use it. It arms a wait-on-event operation associated with a flag; that state outlives the call that starts it. Applications should not share the same event flag among unrelated components or assume a return from the setup call means the interval has completed. The flag’s storage must remain valid, and cancellation/completion should follow the BIOS contract. A program that only needs a synchronous pause is easier to reason about with AH=86h, while a program that must continue doing work should use an event model instead of simulating concurrency with a blocking call.
For code that must be usable across BIOSes, model the result explicitly as completed, unsupported, busy, or timed out. Preserve AH immediately after an error because subsequent BIOS or DOS calls can overwrite it. This makes telemetry and retry policy more useful than collapsing every carry-set result into “timer too fast.”
Failure handling and portability
Treat carry set as a normal compatibility outcome. Older BIOSes, unusual firmware, virtual machines, and protected-mode execution environments may not implement the function in the same way. In a DOS extender, a direct real-mode interrupt may need the extender’s real-mode call interface; do not assume a protected-mode INT 15h instruction automatically reaches the BIOS. Use the runtime or DPMI host’s documented bridge.
Avoid retrying the same unavailable call in a tight loop. If the wait is optional, continue without it or use a documented fallback. If the wait is safety-critical, fail explicitly rather than pretending an unverified software loop meets a physical timing requirement. A fallback should have a known upper bound and a testable clock source, not simply repeat the failed BIOS request.
Log timing in a way that helps reproduce firmware differences. Record the requested interval, returned carry/status, BIOS or emulator identity where available, and observed elapsed time from an independent clock. Do not compare a host wall clock with a guest timestamp without accounting for pause, throttling, and virtual machine scheduling. Emulators can implement BIOS calls with host timers whose granularity and pause behavior differ from physical hardware.
Acceptance checks
Test a supported BIOS with a short interval and a practical interval, then compare the observed elapsed duration against a broad tolerance appropriate to the feature. Test a BIOS or emulator that returns an error and verify the program takes its fallback exactly once. Confirm that an error does not get reported as a successful wait and that a subsequent DOS call still works. For any use inside a DOS extender, run through the extender’s documented real-mode call path.
For reproducible tests, measure elapsed time with an independent source and repeat the same request under representative machine load. A test that measures the wait with the same BIOS service only proves that the call returned; it cannot quantify timing error. Record the minimum, median, and maximum observed durations, requested duration, firmware or emulator identity, and whether the host machine was under load. Do not turn one emulator run into a claim about every AT-compatible BIOS.
Keep the conclusion narrow: AH=86h is a convenient firmware wait request with platform-dependent timing characteristics. It can make a coarse pause without a spin loop, but it cannot supply exact microsecond timing, a cross-platform scheduler, or proof that an external device completed an operation. Measure what matters, distinguish unsupported firmware from elapsed time, and reserve hardware-critical timing for a documented owner of that hardware.
Related:
- PC Programmable Interval Timer: 8253/8254 Channels and DOS Boundaries
- DOS BIOS Clock INT 1Ah: Tick Counts, Midnight Rollover, and Date Safety
Sources: