FreeDOS INT 21h IOCTL: Device Status and Control Requests
Use DOS function 44h carefully: inspect handles, distinguish character and block IOCTLs, handle readiness and redirection, and avoid undocumented driver commands.
DOS INT 21h function 44h (commonly called IOCTL) is a family of control operations around file handles and devices. It can query or change device information, send control data to a driver, check whether a handle is ready, and issue generic device-specific requests. It is not one universal “configure hardware” command: each subfunction has its own register contract, some require explicit driver support, and the meaning of generic control buffers belongs to the particular driver or category.
The most important distinction is between ordinary data I/O and control I/O. INT 21h/AH=3Fh and 40h transfer bytes through a handle. IOCTL operations can change how DOS treats a handle or ask a device driver to process a control request that is not ordinary file content. A successful function number does not mean every driver supports every control operation.
Start with the handle information word
Subfunctions AL=00h and AL=01h get and set the DOS device-information word for a handle in BX. The carry flag reports an invalid request or handle; when the query succeeds, DX contains the information word. Its bit 7 distinguishes a device handle from a disk-file handle. For a device, bit 5 controls whether DOS checks certain control characters during I/O: setting it requests the historical raw behavior, while clearing it requests cooked checking. The word also contains device flags, some reserved or read-only; callers must not treat it as an arbitrary application bit field.
That query is useful before changing a standard handle. Shell redirection can make standard input or output refer to a disk file instead of a console device. A program that blindly changes device mode on a redirected handle may receive an error or make an incorrect assumption about how later reads will behave. Check bit 7 first, preserve the original low-byte mode, and restore it before returning to the shell when changing a shared standard device.
; Query standard output. The DOS handle number is in BX.
mov bx, 1
mov ax, 4400h
int 21h
jc ioctl_error
test dl, 80h ; DX bit 7: device (1) or file (0)?
jz redirected_file
mov [saved_device_flags], dl
or dl, 20h ; bit 5: raw control-character handling
xor dh, dh ; required when setting device data
mov ax, 4401h
int 21h
jc ioctl_error
; Before exit, restore only after confirming the saved value is valid.
mov dl, [saved_device_flags]
xor dh, dh
mov ax, 4401h
int 21h
jc ioctl_error
This is an interface sketch, not complete production error handling: the program must preserve the original mode, define a cleanup path for every exit, and decide what to do if restoration itself fails. DOS documentation warns that changing standard-handle mode and not restoring it can affect subsequent programs. Avoid setting reserved bits or copying an arbitrary device word back as though every bit were writable.
Character-device control data is not a normal read or write
AL=02h and AL=03h send and receive control data using a character-device handle in BX. CX gives the buffer length and DS:DX points to the buffer. On success, AX reports the number of bytes transferred; on failure, the carry flag and AX report an error. The device may interpret those bytes as configuration commands, for example, a serial device might expose baud-rate or framing controls, but DOS does not define the payload for every driver.
The device must advertise and implement control-string support. The returned device-information word includes a support flag for these calls, but querying that bit is only a capability check, not a substitute for the device’s documentation. A printer driver, serial driver, and virtual device need not agree on a control-buffer format. Sending a buffer guessed from another device’s documentation can be rejected or change device state in an unexpected way.
This is precisely why control operations should be separated from 3Fh/40h data streams in code. A serial port’s normal output might be a string of bytes to transmit; its IOCTL string could instead describe a communication parameter. Treating a control request as ordinary output, or assuming a generic string works on every driver, mixes two different interfaces.
Block-device IOCTLs use a drive, not a file handle
AL=04h and AL=05h send and receive control data for a block device. They take the logical drive number in BL (0 for the default drive, 1 for A:, and so on), byte count in CX, and a buffer at DS:DX. These subfunctions are also driver-dependent. They are not general replacements for file reads and writes, nor should they be confused with BIOS INT 13h sector operations. The correct payload and side effects depend on the target block-device driver and DOS version.
Later generic IOCTL interfaces add a category and a function code. AL=0Ch addresses a handle, while AL=0Dh addresses a block device; the CH/CL pair identifies category and minor function, with a buffer or parameter block at DS:DX. DOS can handle recognized categories itself and forward other commands to the driver. Therefore, a numeric category/function pair found in a hardware manual is meaningful only for the driver family that defines it.
Before sending a generic request, obtain the exact ABI from the driver or controller vendor: buffer layout, input/output direction, version requirements, returned length, error codes, and whether the command changes persistent device state. Use a correctly sized, aligned buffer where the documentation requires one, initialize reserved fields as specified, and avoid exposing unvalidated user input directly as a device command.
Readiness queries are snapshots, not waits
Subfunctions AL=06h and AL=07h check input or output status for the handle in BX. A successful call returns AL=00h for not ready or AL=FFh for ready. These are status checks; they do not perform a blocking read, guarantee the next operation will succeed, or reserve the device until the program uses it. Device state can change between the check and the subsequent I/O.
Polling in a tight loop is usually a poor response to “not ready.” It can consume the machine while making no progress, and a readiness result can become stale immediately. Where the device and runtime permit it, prefer the normal read/write operation with its documented blocking or error behavior, or use a bounded wait policy appropriate to the driver. Do not reinterpret a failure of the status call as a “not ready” result; check the carry flag first.
Other 44h subfunctions query properties such as removable-media or remote-device status, adjust sharing retry behavior on supported DOS versions, and map logical drive identities. Network-specific status calls are not portable evidence that a path is remote on every DOS-compatible system. If the application can operate without knowing whether storage is local or redirected, keep it location-independent instead of branching on a vendor- or redirector-specific query.
Compatibility and troubleshooting discipline
The broad name “IOCTL” is used by many operating systems, but DOS INT 21h/AH=44h is its own historical ABI. It is also distinct from the request-packet commands sent internally by DOS to installable .SYS drivers. An application calls DOS with a handle or drive number; DOS may then dispatch a request to a driver. Understanding both layers helps explain why a handle can be valid while a particular IOCTL remains unsupported.
When a call fails, record the DOS version, subfunction, handle or drive number, carry flag, AX, device name, and whether the handle was redirected. For a character or block control request, verify the driver capability and use the driver’s exact buffer specification. For a generic request, verify both category and function rather than changing one until the error disappears. Test mode changes and stateful controls in a FreeDOS virtual machine or disposable environment before relying on them on physical equipment.
FreeDOS aims for DOS API compatibility, but compatibility does not make every third-party driver implement every optional extension. The kernel’s own history records IOCTL corrections over time, reinforcing a practical rule: write to the documented common contract, detect unsupported operations, and test on the kernel and driver combination that the application will actually use.
Related:
- Device Drivers on FreeDOS: How .SYS Files Extend the Kernel
- DOS File Handles: Open, Read, Inherit, and Redirect I/O
Sources: