Enumerating PCI Devices from Real-Mode DOS with the PCI BIOS
Use PCI BIOS installation checks and indexed device enumeration safely, interpret register results, and avoid treating firmware discovery as a driver.
PCI-era DOS software often needs to identify an adapter before loading a vendor-specific driver. On a machine with a compatible PCI BIOS, the standardized firmware interface can locate devices by vendor and device identifiers without probing configuration ports itself. The interface is useful precisely because it is a discovery contract, not because it turns DOS into a Plug and Play operating system: it does not load a driver, reserve every resource for an application, or guarantee that a modern firmware still exposes the legacy entry point.
The classic PCI BIOS interface is an architecture-independent firmware contract for PCI device discovery and configuration. Its common DOS entry is INT 1Ah with AH=B1h; functions use AL to select an operation. PC Engines’ tinyBIOS v1.3 documentation provides an accessible firmware implementation reference for these calls, while Ralf Brown’s Interrupt List (RBIL) records register-level conventions and compatibility details. The tinyBIOS document is an implementation reference, not a substitute for the PCI-SIG normative specification; test on the actual BIOS or emulator that the program supports.
Start with the installation check
Do not issue enumeration calls blindly. AX=B101h asks whether the PCI BIOS interface is present. A conventional 16-bit call is:
mov ax, 0B101h ; PCI BIOS installation check
int 1Ah
jc no_pci_bios ; carry set means failure
cmp ah, 0
jne no_pci_bios ; AH is the PCI BIOS status
; BX reports version, CL the last bus, EDX the "PCI " signature
The exact return fields are defined by the PCI BIOS interface; use the documented status and signature rather than inferring support from a CPU generation or a motherboard label. RBIL notes that the call has stack requirements and that some registers may be modified. In small-model programs, make sure the caller has adequate stack space and does not assume undocumented register preservation. A successful installation check establishes that an interface answered, not that every PCI function is visible or correctly initialized.
On failure, report a clear unsupported-platform path. Do not fall back to arbitrary reads from ports 0CF8h/0CFCh unless the program separately implements and validates the relevant PCI configuration mechanism and privilege/environment assumptions. Direct configuration I/O can conflict with a chipset, monitor, virtual machine, or another component that owns the bus.
Find by vendor and device ID
AX=B102h searches for a PCI function matching a vendor ID in DX and device ID in CX. SI is a zero-based match index. On success, carry is clear, AH contains status zero, BH identifies the bus, and BL contains the device/function value. On failure, carry is set and the BIOS status indicates why the request did not complete. The index is how software asks for the first, second, or later matching function; it is not itself a bus number.
mov ax, 0B102h
mov dx, 1234h ; example vendor ID, replace with a real ID
mov cx, 5678h ; example device ID
xor si, si ; first matching function
int 1Ah
jc no_matching_device
; BH = bus, BL = device/function; preserve them before another call
The identifiers above are placeholders and must not be copied as a real adapter identity. A robust routine accepts IDs as input, saves returned registers immediately, and treats carry or a nonzero status as a normal “not found or unsupported” result. To enumerate multiple identical functions, repeat the query with SI=1, SI=2, and so on until the BIOS reports no further match. Bound the loop using a conservative application limit and always handle the terminating error; never assume the number of matches from a machine model.
The BL encoding is not simply an 8-bit slot number. PCI encodes device and function in that byte: bits 7 through 3 identify a device and bits 2 through 0 identify a function. BH supplies the bus. Keep these values together as the BDF tuple when correlating firmware enumeration with a diagnostic log. A device may expose several functions, and each function can have its own configuration header and driver binding.
Discovery is not resource ownership
Finding a matching ID tells an installer that a function was enumerated. It does not prove that a specific IRQ, I/O range, or memory BAR is usable by the application. PCI configuration space also contains writable registers; a diagnostic utility should prefer read-only operations and must not casually change command, BAR, bridge, or interrupt-routing registers. Firmware and an operating system may have already configured the device, and other drivers may rely on that configuration.
The PCI BIOS interface provides functions for reading configuration values, but those calls expose low-level state, not a safe resource-allocation API. Read the device’s vendor/device identity and class information first, then let the OS or a documented vendor driver own activation. Avoid assumptions that all devices use the same BAR layout or interrupt routing. A class code narrows the device family; it does not replace a vendor’s programming manual.
If a configuration read is needed for inventory, read only documented offsets and record the width of each access. A byte, word, and doubleword request are distinct BIOS operations; reading a dword and then masking it is not always interchangeable with a narrower read when firmware has device-specific behavior. Check the returned carry/status for every operation and preserve the BDF tuple associated with the result. Treat an all-ones vendor ID as an absent function only in the context of a successful, correctly formed configuration read; do not use a failed BIOS call’s output register as device data.
This distinction matters in FreeDOS. A successful query can support a driver installer or hardware inventory utility. The device still needs a DOS driver designed for the card and its mode of operation. For a storage adapter, the BIOS might expose boot-time disk services while a DOS driver provides later protected or real-mode access; neither fact follows just from finding the PCI function. For a network adapter, enumeration does not create packet-driver services or initialize a network stack.
Compatibility boundaries and failure handling
The PCI BIOS is a legacy firmware interface. Some newer machines use UEFI implementations that do not provide the same 16-bit BIOS services to a DOS boot, while emulators may implement only a subset. A BIOS can expose the interface but omit a particular function or return an error for a request it cannot satisfy. Drivers that run under a DOS extender or DPMI host also need to respect that environment’s real-mode call mechanism rather than assuming a direct INT 1Ah is always permitted.
Keep error paths explicit: distinguish “PCI BIOS absent,” “function unsupported,” “no matching device,” and “unexpected status.” Log the input IDs, returned status, bus/device/function, and firmware or emulator context. Do not silently report “hardware not present” for every carry result; that hides firmware incompatibility and malformed calls. The exact status table should be taken from the PCI BIOS specification revision the program targets.
Test with at least three cases: a system with no PCI BIOS service, a supported system with no matching ID, and a supported system with one or more matching functions. Verify that enumeration stops cleanly and that the program leaves configuration space untouched. When testing under virtualization, compare against the virtual machine’s configured PCI devices; the guest sees the hypervisor’s virtual hardware model, not necessarily the host’s physical inventory.
Practical rules
Use B101h before any other PCI BIOS call. Use B102h for indexed vendor/device matches, preserve the returned BDF before making another BIOS call, and stop when the BIOS reports the end of the match set. Treat the result as inventory data, never as proof of an initialized driver or exclusive resource ownership. Finally, state the supported execution environments in the utility’s documentation: legacy BIOS DOS, a tested emulator, or a particular DPMI host. That narrow contract is more useful than claiming generic PCI support.
Related:
- FreeDOS and BIOS INT 13h: CHS, LBA Extensions, and Safe Disk Access
- Diagnosing IRQ and Driver Conflicts on Real Hardware Running FreeDOS
Sources: