Skip to content
FreeDOSDeep Dive Published Updated 6 min readViews unavailable

DOS INT 21h AH=06h: Poll Input or Write One Byte

Use DOS INT 21h AH=06h for nonblocking input polls or single-byte output, while accounting for FreeDOS routing and compatibility limits.

INT 21h function 06h is a small DOS character-I/O interface with two operations selected by the value placed in DL. With DL=FFh, the call polls for input and reports availability in the Zero Flag; with another value, it requests output of that byte. The same register therefore selects the operation as well as carrying the output character. Code that treats the service as a general file API, assumes the word “console” always means physical keyboard and screen, or checks AL without checking ZF will eventually mis-handle an edge case.

The IBM PC DOS 7 reference calls this service “Direct Console I/O.” FreeDOS aims to implement MS-DOS/PC-DOS-compatible services, but its current kernel source is especially useful for understanding what its implementation actually dispatches: the output case follows the standard-output path, and the input case uses standard-input helpers. Those details should be attributed to a particular FreeDOS kernel revision, not silently projected onto every DOS clone or historical IBM/MS-DOS release.

The two register contracts

For input, set AH=06h and DL=FFh. If a character is available, DOS returns it in AL and clears ZF. If no character is available, DOS sets ZF; an implementation may also leave AL=00h, so AL alone cannot distinguish “no byte” from an actual zero-valued input byte. Test ZF immediately after the interrupt.

For output, set AH=06h and place the byte in DL. Since FFh is reserved as the input selector, this function cannot express FFh as an output byte. It also has no buffer length, write count, or general-purpose status result. That is a narrow character interface, not a substitute for a handle-based write when exact byte counts, full-byte output, or richer error handling matter.

A one-shot input poll

The following MASM/TASM-style 16-bit example polls once. It assumes the data segment is already set up and that last_byte is a byte in that segment:

        mov     ah, 06h
        mov     dl, 0FFh       ; selector: poll input, do not output
        int     21h
        jz      no_character   ; test ZF, not AL

        mov     last_byte, al  ; valid even when AL is 00h
        ; process the byte here
        jmp     poll_done

no_character:
        ; no byte was reported by this poll
poll_done:

The conditional jump must be the first flag-dependent instruction after INT 21h; intervening arithmetic or comparison would replace the Zero Flag result. If the application supports a device or DOS version with different details, verify the behavior against that target rather than inferring it from another kernel’s source. A tight poll loop can monopolize a single-tasking DOS program’s CPU time. Add a deliberate wait, yield, or application-level pacing strategy when repeated polling is not immediately followed by useful work.

Single-byte output and the FFh trap

For output, DL is the data byte unless it equals the reserved FFh value:

        mov     ah, 06h
        mov     dl, '!'        ; any non-FF byte selects output
        int     21h

There is no carry-flag or byte-count protocol here that a caller should treat like AH=40h. A program that must write arbitrary binary bytes, including FFh, or must know whether all requested bytes were written should use the handle interface and check its result. Keep diagnostic output out of a machine-readable stream unless the program intentionally routes it there.

What the FreeDOS dispatcher does

The FreeDOS kernel source at commit d6791add2043c9d7b584d840a8ffaf8829fd2bdc shows the AH=06h branch in kernel/inthndlr.c. For DL other than FFh, it jumps to the shared AH=02h output path, which passes the byte through write_char_stdout(). For DL=FFh, the handler initializes AL to zero and sets ZF, checks StdinBusy(), and returns along the no-character path if input is busy; that branch also calls DosIdle_int(). If input is available, the handler clears ZF and reads through read_char_stdin(FALSE). The same-revision chario.c implementation routes these helpers through the STDIN and STDOUT system-file-table entries, which is the source-level basis for saying that standard-device or redirection setup can affect the path.

This source-grounded behavior is an important warning about the function’s historical label. In this FreeDOS revision, the dispatcher is not directly reading a physical keyboard controller or unconditionally drawing to a physical display: it delegates to standard-input and standard-output helpers. That makes the active DOS redirection and device setup relevant. Do not assume every compatible DOS kernel routes AH=06h through the same internals; the interface name alone does not prove a given implementation’s routing.

The dispatcher also has a shared Ctrl-Break check before its function switch. In this source snapshot, AH=06h is included in that check when break_ena is set; the unconditional function-number ranges shown there omit 06h. This is one reason to avoid carrying an unqualified “AH=06 never checks Ctrl-Break” rule from a historical reference into every FreeDOS configuration. If Control-C/Break behavior is important to the application, test the exact kernel, setting, and device path.

Pick the service that matches the job

Use AH=06h when the application genuinely needs a one-byte poll or a one-byte compatibility output call and has a defined target DOS environment. It can be convenient for a simple keyboard-driven menu or a small interactive loop. It is a poor choice for parsing redirected files, transferring binary records, reporting output failure, or writing data where FFh is valid.

For line editing, AH=0Ah has its own buffer layout and echo behavior; it is not interchangeable with a nonblocking poll. For stream processing, the DOS handle calls such as AH=3Fh (read) and AH=40h (write) carry a handle, a buffer, and a requested length, and return a byte count or error. The handle API is the better fit when the program needs redirection, exact lengths, or a recovery path. These services still have DOS- and device-specific limits; they do not turn every character device into a regular file.

Compatibility and validation checklist

Before shipping a DOS program around AH=06h, run it under the exact kernel and shell combination it claims to support. Check input when a byte is ready and when none is ready; include a zero byte if the input source can produce one; verify ZF immediately; test standard input redirection if supported; and confirm which destination receives output. Also check whether DL=FFh is mistakenly being used to request output, whether the application depends on Ctrl-Break behavior, and whether a busy-loop poll starves other software.

FreeDOS source is maintained and changes over time. This article’s implementation notes are pinned to one upstream commit so a reader can reproduce the source inspection; a later kernel may differ. The example assembly was reviewed for the documented register/flag ordering but was not executed in a native FreeDOS VM during draft preparation. Treat it as an illustrative 16-bit code sample and assemble/run it in the intended emulator or hardware test matrix before relying on it operationally.

The safe mental model is deliberately small: DL=FFh asks for an input poll whose truth value is ZF, while a non-FFh DL asks for a one-byte output. Redirection, break handling, extended keyboard sequences, timing, and exact device behavior belong to the DOS implementation and device path you have actually tested.

Related:

Sources:

Comments