Skip to content
FreeDOSDeep Dive Published Updated 7 min readViews unavailable

DOS BIOS Keyboard Input: INT 16h, Buffer Semantics, and Enhanced Keys

Use BIOS INT 16h blocking and polling calls without dropping enhanced keys, confusing scan codes, or stealing keyboard state from DOS and resident software.

In a DOS program, keyboard input can arrive through the DOS character-device layer or through the BIOS keyboard service at interrupt vector 16h. BIOS INT 16h is a low-level interface to keystrokes that the system BIOS has already processed; it is not a raw read from the keyboard controller and it is not the same API as DOS standard input. That distinction affects redirection, extended keys, resident utilities, international layouts, and compatibility with older BIOS implementations.

The most familiar calls are AH=00h to wait for a keystroke and AH=01h to check whether one is ready. Enhanced keyboard BIOSes also offer AH=10h and AH=11h so software can consume and test keystrokes that the legacy pair may discard. Code that chooses one pair without understanding the target machine’s compatibility needs can lose navigation keys or misread a scan code as text.

BIOS keys are already translated events

The keyboard controller and BIOS interrupt handler process hardware scan codes and place logical keystroke records in a BIOS-managed buffer. INT 16h retrieves these records as an ASCII value in AL plus a BIOS scan code in AH. The scan code is useful for function keys and navigation keys that have no printable ASCII representation, but it is not a universal text encoding and is not always identical to the hardware scan code observed by the keyboard interrupt handler.

For text entry, use AL as the BIOS-provided character according to the active keyboard layout. For physical-key identity, use AH only with the BIOS compatibility behavior and keyboard generation in mind. Do not compare a scan code across keyboard layouts as if it were a stable character. The same physical key can yield different text under different layouts; conversely, the same ASCII character can be produced by more than one physical key combination.

Blocking read versus non-destructive check

The legacy blocking call uses AH=00h and returns when a compatible keystroke is available. The legacy check uses AH=01h: ZF is set when there is no keystroke and cleared when a compatible key is available. If a key is present, the check normally leaves it in the BIOS keyboard buffer. These calls are appropriate for traditional programs targeting the original 83/84-key compatibility model.

An assembly sketch for a wait-and-consume interaction is:

        mov     ah, 00h        ; Wait for a BIOS keystroke
        int     16h
        ; AH = BIOS scan code; AL = character value

Polling should remain a small part of a cooperative loop:

        mov     ah, 01h        ; Check compatible-key availability
        int     16h
        jz      no_key_ready   ; ZF=1 means no key is available
        ; Key remains queued; consume it with the matching read API

These are real-mode BIOS call sketches, not FreeDOS C library functions. A program that polls in a tight loop can monopolize CPU time on an original machine or virtual machine; yield or perform useful work between checks. Also do not check, then allow unrelated TSR or interrupt-driven code to consume the shared queue, then assume the same key will still be available.

Enhanced keys and the legacy compatibility trap

On enhanced keyboards, the BIOS offers AH=10h to wait for an enhanced keystroke and AH=11h to test for one without removing it from the buffer. These calls preserve extended keystrokes that the legacy calls may filter. Ralf Brown’s Interrupt List notes that AH=00h on extended keyboards discards keystrokes that are not compatible with 83/84-key keyboards and returns only when a non-extended keystroke is available. It documents the same class of filtering for AH=01h while checking for a legacy-compatible key. Some clone BIOSes instead treat the legacy and enhanced pairs the same.

That variation means a compatibility decision has to be explicit. Use the legacy pair if your software must match old behavior and only needs the old key set. Use the enhanced pair only after detecting support through the documented keyboard-functionality query where available. If a feature is optional, fall back gracefully rather than assuming every PC-compatible BIOS implements it.

AH=11h returns ZF in the documented interface, but the Interrupt List records a historical manual error that reported CF instead for some reference material. Verify the actual calling convention against the tested BIOS and trusted documentation. Testing only in one emulator does not establish behavior on all BIOS revisions.

The keyboard buffer is shared system state

The BIOS data area contains keyboard state, a circular buffer, and modifier flags on conventional IBM PC-compatible systems. Programs sometimes inspect or change this memory directly, but that bypasses the BIOS API and relies on layout and ownership assumptions. A TSR, BIOS extension, emulator, or operating system may alter buffering behavior. Prefer INT 16h for normal input; touch the BIOS data area only when the target platform’s documented ABI requires it and the application has a clear ownership policy.

Polling does not reserve a key. Between a check and a later read, another consumer may remove it. A TSR can install keyboard handling, consume or synthesize events, or alter the interface. This is another reason not to share low-level queue manipulation across unrelated components. Let one layer own keyboard consumption and send higher-level events to the rest of the program.

The keyboard service also does not provide DOS redirection semantics. When a user types at the keyboard, INT 16h reads the BIOS event queue. INT 21h input functions operate through DOS handles and devices, which may be redirected from a file or pipe under a shell. A command-line utility that is expected to support < input.txt should use the DOS standard-input API rather than bypassing it with BIOS calls.

Modifier state, typematic, and text are separate concerns

The legacy AH=02h function returns shift flags, while enhanced BIOSes add AH=12h for extended shift state. Those status functions describe modifier/key state according to the BIOS, not a complete modern keyboard event object. Do not attempt to reconstruct every key combination from one shift byte. Read the function-specific documentation, test the BIOS behavior, and account for the fact that typematic repeat can place repeated events in the buffer.

Keyboard layout and DOS National Language Support also affect the relationship between keystrokes, character values, display code pages, and file encoding. A correct BIOS read can still render a character incorrectly if the application prints it using a mismatched console code page. Keep physical key handling, text encoding, and display mapping as distinct layers.

Test against the compatibility target

Test a plain letter, modifier combinations, function keys, navigation keys, keypad keys, and enhanced-only keys. Test both blocking and polling calls, and confirm that a polling call leaves the event available for a subsequent read. Test with and without keyboard redirection to verify that the program intentionally uses the BIOS or DOS input path. Include multiple BIOS/keyboard configurations or emulator models if broad compatibility is a goal.

For an emulator, compare the BIOS-visible result with the emulated keyboard event path: queue order, modifier flags, extended-key representation, and consumption behavior. For FreeDOS on real hardware, record the machine/BIOS version and keyboard model. Do not classify a mismatch as a FreeDOS kernel defect until you have isolated whether the BIOS, DOS console driver, keyboard TSR, or application consumed the event.

RBIL is an extensive reverse-engineered compatibility reference, not an original IBM specification. Its notes are especially useful because clone-BIOS behavior is not uniform. Use it alongside original PC/AT or PS/2 BIOS technical references for the target model, and preserve uncertainty when a quirk is documented only for specific BIOS families.

Related:

Sources:

Comments