Skip to content
FreeDOSDeep Dive Published Updated 7 min readViews unavailable

VBE/DDC on FreeDOS: Read and Validate the Monitor’s EDID

Use optional VBE/DDC function 15h to read EDID block 0 on FreeDOS, check its header and checksum, and stop before unsafe mode changes.

A DOS utility can ask the video BIOS to read the attached display’s EDID without changing video modes. That is useful for an inventory report or for diagnosing why a monitor is detected differently from its label. The interface is optional, however, and the returned bytes are firmware- and connection-provided input, not proof that the display is genuine or that every timing it advertises is safe to use.

This article is deliberately limited to the 128-byte base EDID block, block 0. It does not set a VBE mode, parse extension blocks, or implement monitor control. The existing VBE mode-discovery workflow is a separate step with different risks.

Check the optional DDC capability before reading

VBE 3.0 lists function 15h (4F15h) as an optional Display Data Channel service and refers implementations to the separate VBE/DDC standard for its detailed register contract. A machine can provide ordinary VBE mode services and still reject DDC. FreeDOS itself does not guarantee that the active video BIOS or extender implements this call; the installed graphics firmware and monitor path determine what is available.

The VBE/DDC 1.0 register contract used by the ATI BIOS documentation begins with a capability query:

Register Input for capability query Meaning on return
AX 4F15h VBE DDC service
BH Not an input in this capability call Approximate seconds, rounded up, to transfer one 128-byte EDID block
BL 00h Report capabilities
CX 0000h Controller unit number; 00h selects the primary controller
ES:DI 0000:0000 Null pointer for the version-1.0 query
AX n/a Require 004Fh for success; otherwise stop
BL n/a Bit 0: DDC1; bit 1: DDC2; bit 2: transfer will blank the screen

The capability result describes the DDC levels supported by the controller and display, and whether transfer blanks the screen. It is not the result of the separate EDID-read call: do not treat one capability bit as a universal substitute for that call’s status. Record the reported levels, use only a BIOS implementation known to provide the read service, and stop if the read call fails. If bit 2 is set, the controller reports that the screen will be blanked during transfer. Schedule the probe when temporary blanking is acceptable; do not run it in the middle of an interactive display workflow.

For the VBE/DDC 1.0 base-block read, the register outline is AX=4F15h, BL=01h, CX=0000h, DX=0000h, and ES:DI pointing to a writable 128-byte buffer. DX=0 requests block 0. The ATI register description lists BH as unchanged for this subfunction, so do not treat it as a required input. Require AX=004Fh after the call. Keep a copy of the raw buffer until validation is complete; do not interpret strings or timings while the status is unknown.

This is a register outline, not compiler-ready C. A 16-bit compiler, DOS extender, and protected-mode runtime can expose different interrupt-call helpers. The pointer passed to the BIOS must be a real-mode address the BIOS can access. Use the compiler or extender’s documented real-mode interrupt bridge and far-pointer conversion; do not cast a protected-mode pointer to ES:DI or assume that the caller’s data segment is addressable by firmware. If the environment cannot provide a valid low-memory transfer buffer and register bridge, stop rather than guessing.

Validate the complete base block before parsing

EDID base block 0 is 128 bytes. Check both its standard eight-byte header and its checksum before reading any later field. The checksum is the sum of all 128 bytes modulo 256; a zero result means the block is probably valid according to the E-EDID specification. It is an error-detection check, not a signature or authenticity test.

The following C89-compatible helper validates only those basic properties. It does not decode vendor IDs, names, extension blocks, or detailed timings.

#include <stddef.h>
#include <string.h>

int edid_base_block_valid(const unsigned char *block, size_t length)
{
    static const unsigned char header[8] = {
        0x00U, 0xFFU, 0xFFU, 0xFFU,
        0xFFU, 0xFFU, 0xFFU, 0x00U
    };
    size_t i;
    unsigned sum = 0U;

    if (block == NULL || length < 128U)
        return 0;
    if (memcmp(block, header, sizeof(header)) != 0)
        return 0;

    for (i = 0U; i < 128U; ++i)
        sum = (sum + block[i]) & 0xFFU;
    return sum == 0U;
}

The length check precedes every read, so a caller that supplies fewer than 128 bytes cannot trigger an out-of-bounds parse. The BIOS interface defines a 128-byte output block but does not return a transferred-byte count; a successful status is necessary but not sufficient, so validate the header and checksum before parsing. For tests, use one known-good 128-byte fixture and a copy with one checksum-covered byte flipped; require the first to pass and the second to fail. Also test null, 127-byte, and wrong-header inputs. These parser tests run without calling the video BIOS or touching the display.

Byte 126 of the base block gives the count of extension blocks that follow; it does not enlarge block 0. This workflow intentionally does not request those blocks. If the utility later needs extensions, implement their transport and per-block validation as a separate feature, validate the declared count against strict limits, and apply each extension’s own format rules before parsing it.

Keep EDID claims in their proper scope

After the header and checksum pass, report the block as “EDID data received and structurally checked,” not “trusted monitor.” Firmware, a KVM, adapter, virtual machine, or faulty cable may change, synthesize, truncate, or misreport the data. A valid checksum only detects many accidental corruptions. It does not authorize a mode, authenticate a product, or prove the physical monitor can safely display every advertised timing.

When reporting identity fields, preserve the original bytes and label decoded values as display-reported metadata. Be cautious with optional serial numbers and strings; they can be absent, malformed, or unsuitable for logs. Do not use a display name as a security key or as the sole way to distinguish devices. Keep the parser bounded to the fields and EDID revisions it explicitly understands.

If the call is unsupported, stop and record that the BIOS did not offer DDC. If the returned status is a failure, preserve the register status before making other BIOS calls. If the screen blanks, report the capability flag and adapter/monitor path rather than trying random mode switches to recover it. For an invalid header or checksum, retain the raw bytes only if the user has agreed to a diagnostic capture; do not treat corruption as permission to guess at offsets.

Verification and environment limits

The VBE 3.0 core specification lists function 15h as optional and refers readers to the separate VBE/DDC standard for its details. VBE/DDC support is therefore not implied by FreeDOS, VBE version, or a working graphics mode. Capability depends on the BIOS implementation, display controller, DDC wiring or virtual display, monitor, and the runtime used to make the real-mode call. A VM may report its virtual display rather than the physical screen attached to the host.

Test first in an emulator or on disposable hardware where a brief blank is acceptable. Record the VBE status, DDC capability bits, transfer result, header check, checksum result, controller identity, and whether the path is virtual or physical. Then compare the reported manufacturer/product information with a trusted source for the monitor. Do not set a mode as part of this diagnostic. Choosing a display mode requires a separate check of VBE mode attributes, EDID extensions and timings, adapter limits, and the user’s current display state.

The safe sequence is small: probe; record the reported DDC capabilities; warn or defer if transfer blanks; request block 0 only from a BIOS implementation that supports the read service; use a BIOS-addressable 128-byte buffer; check status, header, and checksum; then report only bounded, clearly labeled data. Anything unsupported or malformed is a reason to stop, not a reason to write to the display or interpret arbitrary bytes.

Related:

Sources:

Comments