U-Boot VirtIO Negotiation: Device Features Are Not the Driver’s Accepted Feature Set
A virtual machine advertises a storage feature, but U-Boot does not use it. That can be correct: the device’s capability bitmap is an offer, not the final contract. Before changing the virtual machine or patching an operation, reconstruct how the bootloader intersects the offer with its driver’s feature table and completes negotiation.
This article follows U-Boot v2025.01, commit 6d41f0a39d6423c8e57e92ebbe9f8c0333a63f72, through the VirtIO uclass and block driver. It covers source-derived behavior, not a claim about every firmware release or host implementation. No virtual machine, disk operation, benchmark, or fault-injection test was run.
Identify the transport and its child driver
The VirtIO uclass separates a transport device from the functional child, such as a block device. The child uses wrapper functions that dispatch configuration, status, and feature operations through its parent transport. Seeing the transport in the device inventory therefore does not establish that the desired functional driver negotiated successfully.
The transport post-probe path maps recognized device IDs to available driver names. Unsupported IDs and an unconfigured matching driver can leave that path without a functional child while returning success. Start the investigation with the actual child identity, selected driver, and build configuration, not merely the presence of a VirtIO transport.
The block driver’s bind operation registers its feature table with the parent uclass state. This is the bootloader-side declaration to inspect alongside the host’s offered bitmap. A Linux guest’s richer table does not describe what this U-Boot binary requested.
Reconstruct the selected feature mask
During child pre-probe, the uclass resets the device, marks driver participation, obtains the device feature bitmap, and constructs a driver bitmap from the registered feature-number array. If the driver supplied a separate legacy table, that table has its own bitmap; otherwise the normal bitmap is also used for the legacy case.
For the selected mode, the implementation first computes a bitwise intersection of the driver and device masks. A device-specific capability offered by the host but absent from the applicable driver table does not become negotiated merely because the host can implement it. Equally, listing a feature in the driver table does not force the host to supply it.
The v2025.01 feature definitions identify VERSION_1 as bit 32. In the examined initialization path, absence of that offered bit selects legacy handling. Preserve the effective mode in any diagnostic record: interpreting the feature table without knowing which branch used it can give the wrong expected mask.

Account for transport bits without claiming every bit survives
After the device-specific intersection, the uclass loops through the defined transport feature range. In this version it explicitly preserves offered VERSION_1 and IOMMU_PLATFORM bits. The loop is not a blanket copy of every offered transport bit.
This detail matters when reviewing a printed final mask. Some bits can appear because of the transport-preservation step rather than membership in the functional driver’s table. Conversely, a host-advertised transport feature outside that selected pair must not be assumed negotiated from this loop alone.
Use symbolic feature names and numeric positions together when explaining the result. A hexadecimal mask without its pinned definitions is easy to misread, especially when comparing modern and legacy paths. Do not infer an IOMMU configuration or DMA isolation guarantee solely from the existence of one feature bit; the transport and platform implementation need their own review.
Distinguish acceptance from driver readiness
virtio_finalize_features first submits the negotiated features through the transport. If that operation fails, it returns the error. For legacy handling, the function then returns without the modern FEATURES_OK handshake.
For the modern path, it requests FEATURES_OK and reads the device status back. A read error is propagated; a successful read without FEATURES_OK produces -EINVAL. Thus a locally constructed mask is not sufficient evidence that a modern device accepted the agreement. Preserve the readback and the exact failing stage.
The examined status-update helper itself returns no error to its caller. Also, child pre-probe does not check the return of its feature-read wrapper before using the output variable. These are reasons to inspect transport errors directly during diagnosis, not to assume every upstream failure necessarily appears as a clean high-level negotiation error.
DRIVER_OK is requested later, in child post-probe. It is not a synonym for FEATURES_OK. The driver still has its own probe work between accepted negotiation and that later readiness step. A report should state which stage was observed rather than saying simply that “VirtIO started.”
Use the block driver as a concrete capability example
The pinned block driver lists WRITE_ZEROES in its feature table. Its erase implementation checks whether that feature was negotiated and returns -EOPNOTSUPP when it was not. The host’s standalone feature advertisement is therefore insufficient to predict that operation’s availability.
This is a capability explanation, not a recommendation to erase a disk. To investigate without changing user data, inspect the source, negotiated mask, and a controlled mock path first. Any later destructive qualification belongs on an explicitly disposable image with a separate approved procedure.
The same driver’s probe creates one virtqueue and reads capacity. A successful negotiation does not replace checking those later operations, block geometry, or the intended disk identity before using storage. Keep the diagnostic sequence aligned with what the driver actually performs.
Make the compatibility test discriminate between stages
A proposed software fixture should vary the device offer while holding the driver table fixed. Include a device-specific bit offered by both sides, a host-only bit, and a driver-only bit. Verify the intersection and then the separately preserved transport bits. These cases test mask construction rather than storage throughput.
For a modern device, add a status response that clears FEATURES_OK and require rejection before normal readiness. Include feature-submission and status-read errors, recording the exact transport callback result. Keep legacy behavior in a separate case rather than expecting the modern status exchange there.
The release evidence should retain the host device configuration, bootloader revision and configuration, functional driver table, offered mask, negotiated mask, acceptance status, and later probe outcome. This makes an absent feature an explainable compatibility result instead of an invitation to enable every advertised capability blindly.