Skip to content

feat(vhost-user): add a device-type agnostic vhost-user frontend - #6072

Open
1stvamp wants to merge 2 commits into
firecracker-microvm:mainfrom
triggerdotdev:feat/generic-vhost-user
Open

feat(vhost-user): add a device-type agnostic vhost-user frontend#6072
1stvamp wants to merge 2 commits into
firecracker-microvm:mainfrom
triggerdotdev:feat/generic-vhost-user

Conversation

@1stvamp

@1stvamp 1stvamp commented Jul 31, 2026

Copy link
Copy Markdown

Changes

Adds VhostUserDevice, a vhost-user frontend that carries no knowledge of the virtio device type it serves. It owns the parts of the protocol that aren't device specific: feature negotiation, the config space fetch, queue allocation and vring setup.

Nothing uses it yet. vhost-user-block moves onto it in the next PR of the stack, and the generic device is built on it after that.

Reason

vhost-user-block owns all of this logic today, and a generic vhost-user device needs the same again. Pulling it out on its own keeps the block refactor separate from the new device, so each can be reviewed for one thing at a time.

This is step 1 of the 3 @ShadowCurse asked for in #6072.

License Acceptance

By submitting this pull request, I confirm that my contribution is made under
the terms of the Apache 2.0 license. For more information on following Developer
Certificate of Origin and signing off your commits, please check
CONTRIBUTING.md.

PR Checklist

  • I have read and understand CONTRIBUTING.md.
  • I have run tools/devtool checkbuild --all to verify that the PR passes
    build checks on all supported architectures.
  • I have run tools/devtool checkstyle to verify that the PR passes the
    automated style checks.
  • I have described what is done in these changes, why they are needed, and
    how they are solving the problem in a clear and encompassing way.
  • I have updated any relevant documentation (both in code and in the docs)
    in the PR.
  • I have mentioned all user-facing changes in CHANGELOG.md.
  • If a specific issue led to this PR, this PR closes the issue.
  • When making API changes, I have followed the
    Runbook for Firecracker API changes.
  • I have tested all new and changed functionalities in unit tests and/or
    integration tests.
  • I have linked an issue to every new TODO.

  • This functionality cannot be added in rust-vmm.

This is part 2 of 4 in a stack made with GitButler:

@1stvamp
1stvamp marked this pull request as ready for review July 31, 2026 01:34
@ShadowCurse

ShadowCurse commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Hi @1stvamp, sorry for a long delay.

Taking a very quick look at the description of this PR, I see you decided to keep working on exposing the generic vhost-user device first instead of adapting the existing vhost-user-blk to use generic paths, thus de-duplicating the code and making the at least initial set of changes smaller (since updating vhost-user-blk does not need to touch all the api/config paths).

Well, I would really appreciate if you could rework this set of patches to be in another order (the option 2 I mentioned in #5773 (comment)):

  1. Add internal generic vhost-user type
  2. Move vhost-user-blk to use generic vhost-user type
  3. Expose generic vhost-user type to the user API.

The reasoning here is that 1 and 2 are just some internal FC changes which can be reviewed and merged separately and don't require any new CI tests. And the 3 can built on top of them with only minimal changes only required to expose the API and add necessary CI tests.

Once you do the change, feel free to ping me or anyone else on the team to take a look.

@1stvamp

1stvamp commented Aug 6, 2026

Copy link
Copy Markdown
Author

@ShadowCurse sorry, I misread your orig comment: I saw option 1 by mistake. 🙃

While starting on cutting this up for option 2 I found a read-only negotiation bug in vhost-user-blk, up separately as #6083.

I'll redo this in your 1/2/3 order, and keep 1 and 2 together in one PR as you suggested on #5773 with the API exposure stacked on top. I'll ping you once it's up.

@1stvamp
1stvamp force-pushed the feat/generic-vhost-user branch from 22d31f7 to 2a2bad4 Compare August 21, 2026 23:02
@1stvamp 1stvamp changed the title feat(vhost-user): add a generic vhost-user frontend device feat(vhost-user): add a device-type agnostic vhost-user frontend Aug 22, 2026
@1stvamp

1stvamp commented Aug 22, 2026

Copy link
Copy Markdown
Author

@ShadowCurse split up into 3 phases/PRs, hth

@1stvamp

1stvamp commented Aug 23, 2026

Copy link
Copy Markdown
Author

@ShadowCurse this is reworked in your 1/2/3 order now, sorry it took a while.

It ended up as three PRs rather than the two I said I'd do. You mentioned 1 and 2 could be reviewed and merged separately, and once they were actually split they didn't really want to share a PR, so:

Feature passthrough is implemented now, so the read-only xfail is gone: the frontend passes the backend's features on and withholds only the bits it would have to implement itself (packed rings, a platform IOMMU, notification data, per-queue reset, an admin queue, SR-IOV and dirty page logging). That has a consequence I hadn't expected, a guest offered the backend's features can size itself from the backend's config space, which an agnostic frontend can't parse. VIRTIO_BLK_F_MQ is the one I hit, the guest takes its queue count from the backend and then hangs if the frontend was configured with fewer, so the configured queue count has to agree with the backend and that's in the docs now.

One behaviour does change in step 2: block's config space fetch goes through the shared frontend, which requires the backend to return as many bytes as were asked for, so a backend answering a 60 byte request with fewer now fails to attach where before it was accepted.

Happy to reshuffle if you'd rather they were combined differently.

The VIRTIO_BLK_F_RO check read acked_features after it had been
reassigned to acked_features & PROTOCOL_FEATURES, so it tested bit 5
against 0x4000_0000 and never matched. read_only was therefore always
false, even though the device does ask the backend for VIRTIO_BLK_F_RO,
and docs/api_requests/block-vhost-user.md documents the backend as the
place a readonly vhost-user drive is configured.

read_only feeds Block::read_only(), which decides whether a root device
gets ro or rw on the guest kernel cmdline, so a readonly vhost-user root
device was given rw. The guest still sees VIRTIO_BLK_F_RO and retries
the root mount readonly, so it boots, but /proc/cmdline is wrong and a
later remount,rw fails. There is no way to correct this from config:
VhostUserBlockConfig::try_from rejects is_read_only, which leaves the
backend as the only source of the flag.

Compute read_only before acked_features is narrowed, so the shadowing
cannot swallow the bit again. test_new_all_features already advertises
VIRTIO_BLK_F_RO from its mock backend and asserted the broken result, so
it now asserts read_only is set, and test_vhost_user_block asserts ro
reaches the guest cmdline.

Signed-off-by: Wes Mason <wes@1stvamp.org>
The vhost-user frontends duplicate everything that is not specific to
the virtio device type they implement: connecting to the backend socket,
negotiating virtio and protocol features, fetching the config space,
allocating queues and eventfds, and setting up the backend's vrings on
activation.

Add VhostUserDevice to hold that shared state, built from a
VhostUserDeviceSpec carrying the parts that do vary: socket path, queue
count and size, the features to offer, the config space size, whether
CONFIG is mandatory, and the metrics name. Device types embed it and
keep their own state alongside.

Two details worth noting for the callers that follow. Fetching the
config space is skipped when the backend does not ack CONFIG, leaving an
empty config space, since only a frontend with no device-specific
fallback needs to insist on it. And activate() sets up the queues the
guest marked ready rather than every allocated queue, because the number
a driver initialises is dictated by the backend-owned config space, so
initializing an unconfigured queue returns NotReady and aborts
activation. Real vring indices are preserved: queues 0 and 2 being ready
maps to vrings 0 and 2, not 0 and 1. Activating with no ready queues is
rejected rather than quietly setting up nothing.

The spec is validated before the socket is touched, since a frontend
built from it may be configured at runtime rather than from constants:
the queue count is bounded by what the PCI notification region can
address, the queue size has to be a power of two, and the config space
size has to fit the protocol's own limit.

The config space size is a size rather than an upper bound, in that the
backend has to return exactly that many bytes. The vhost crate checks
the size the reply declares but not the length of the payload behind it,
so the length is checked here too. Otherwise a backend could declare the
size that was asked for, send fewer bytes, and leave the guest reading
past the end of what it sent.

No device type uses this yet.

Signed-off-by: Wes Mason <wes@1stvamp.org>
@1stvamp
1stvamp force-pushed the feat/generic-vhost-user branch from 2a2bad4 to 099cc7e Compare August 25, 2026 01:17
@zulinx86 zulinx86 added the Status: Awaiting assignee Indicates that an issue or pull request is awaiting action from its assignee. label Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Status: Awaiting assignee Indicates that an issue or pull request is awaiting action from its assignee.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants