Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Add option to configure device part of PCI BDFs - #7631

Closed
scholzp wants to merge 15 commits into
cloud-hypervisor:mainfrom
scholzp:upstream_pci_bdf
Closed

scholzp wants to merge 15 commits into
cloud-hypervisor:mainfrom
scholzp:upstream_pci_bdf

Conversation

@scholzp

@scholzp scholzp commented Jan 23, 2026

Copy link
Copy Markdown
Contributor

Configurable PCI BDFs

Motivation

Currently, the PCI address handout in Cloud Hypervisor depends on the order in which devices are added to a VM. This is problematic and can cause VMs to break in two scenarios. First, if we decide to alter the order in which we process devices in the initialization routine, we may end up with two versions that prioritize different device classes. Different prioritization in this case results in different PCI addresses across two Cloud Hypervisor versions, as per the current implementation. Second, management software like libvirt might rely on the fact that it can decide what PCI address a device uses. For example, we observed cases where we hotplugged a disk after VM creation. Disks are prioritized higher than network interfaces in the PCI address handout. After a live migration, for example, this disk was added before the network interface, as network interfaces require a dedicated API call to be added to a VM (configuration). To prevent such a situation, we need a way to tell Cloud Hypervisor which PCI addresses to assign to a specific device. How we can solve this problem is described in the following sections.

Background

Configurable PCI BDF (Bus, Device, Function) allows for choosing the address that a guest can see a PCI device. Qemu, for example, supports adding devices to a VM through the device command. With the help of the addr option for the device command, one can define the device and function part of a device's BDF in the form addr=DD.F, where D denotes a device ID in the range of [0..32] and F a function ID in a range of [0..7]. Qemu denotes the bus number with the option bus, where a valid expression is bus=pci.2 to denote the second PCI bus, for example. [0]

With this pull requests, we try to mimic the command-line interface of Qemu and introduce a new command line parameter to CHV, similarly achieve configurable BDFs.

An example of a BDF for Bus=1, Device=3, and Function=5 is 1:03.5. [1]

Current status

CHV allows defining multiple PCI domains via the pci_segments option. For all PCI segments, CHV creates a dedicated host bridge. Via the option argument pci_segment, one can define to which domain a device is added.

Other than that, CHV currently has no support to influence what exact BDF is assigned to a device, other than the order in which devices are specified in the VM configuration. This is done by calling the next_device_bdf function of the
struct PciSegment for each PCI device to create. next_device_bdf returns the a BDF with the following free device ID and a fixed function ID of 0. PciSegment queries the underlying PciBus to obtain the following device ID, which we also must modify.

Currently, there is no support for multi-function devices in CHV. Because of this, we ignore the Function part of BDFs for now.

Proposed changes

We split the configurable BDF feature into separate PRs with the goals defined in The following subsections.

The BAR address allocation is able to handle the changes listed below. So I don't expect the need for changes there.

1. Definition of Device IDs

To define a device ID, we first must implement some notion for specifying them via the API or command line. For this, we introduce the addr option argument, similar to Qemu. The expected format is addr=DD with DD being in the range
of [0..32]. This argument is stored in a device's configuration and must be propagated to next_device_bdf. This function will be replaced by specific_device_bdf to allow for specifying a concrete device identifier. The steps for implementing configurable device IDs are as follows:

  • Introduce function allocate_device_id to struct PciBus to reserve a device ID, depending on the argument given. If no device ID is specified, use the current mechanism to obtain an ID. This replaces next_device_id.
  • Introduce function allocate_device_bdf to struct PciSegment to allocate a BDF depending on function parameters. If no device parameter is specified, fall back to the algorithm currently used by next_device_bdf. Replaces next_device_bdf.
  • Introduce addr parameter to all forms of VirtIo device configurations
    and adjust parsers
  • Add unit tests covering allocate_device_id in PciBus
  • Add unit tests covering allocate_device_bdf in PciSegment
  • Add unit tests covering the new argument and its parsing

Open Questions

For our work it was enough to also ignore the Bus part, as we mostly have only one bus on x86. As Qemu also uses an additional bus option for this part of a BDF, we should be fine. We did some brief testing with multiple PCI segments though and found no issue in BDF handout, as long as only one bus exists in the segment. Currently, the bus ID is fixed to 0 (see patches).

References

[0] https://qemu-project.gitlab.io/qemu/system/device-emulation.html#device-buses

[1] https://wiki.xenproject.org/wiki/Bus:Device.Function_(BDF)_Notation

I'm looking forward for your input!

@scholzp
scholzp requested a review from a team as a code owner January 23, 2026 10:25
@phip1611

Copy link
Copy Markdown
Member

Please fix clippy. I can recommend to use the following command:

git rebase -i HEAD~7 --exec "cargo check && cargo +nightly fmt --all && cargo clippy"

@likebreath likebreath left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the contribution. Some quick comments:

I am converting the PR to a draft before clippy issues are addressed. @saravan2 I know your work on Generic Initiator has some overlaps with this work. Can you please help review the PR when it is ready? Thank you.

@likebreath
likebreath requested a review from saravan2 January 28, 2026 19:30
@likebreath
likebreath marked this pull request as draft January 28, 2026 19:31
@scholzp
scholzp force-pushed the upstream_pci_bdf branch 3 times, most recently from 01cd731 to 56ac625 Compare February 3, 2026 10:30
@scholzp
scholzp marked this pull request as ready for review February 5, 2026 09:15
@scholzp

scholzp commented Feb 5, 2026

Copy link
Copy Markdown
Contributor Author

I think I'm now ready for review @likebreath and @saravan2 . Thanks in advance!

@phip1611 phip1611 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome work!

Please address the remarks. Further, you could mention that we are already deployed this to a few dozens of nodes with OpenStack and libvirt, and it is working like a charm :)

I'm refraining from an approval for now as I'm biased (Pascal is my colleague)

Comment thread pci/src/bus.rs Outdated
Comment thread option_parser/src/lib.rs
Comment thread pci/src/bus.rs
Comment thread pci/src/bus.rs Outdated
Comment thread pci/src/bus.rs Outdated
Comment thread pci/src/bus.rs Outdated
Comment thread pci/src/bus.rs Outdated
Comment thread vmm/src/pci_segment.rs Outdated
Comment thread option_parser/src/lib.rs Outdated
Comment thread vmm/src/device_manager.rs
Comment thread option_parser/src/lib.rs
Comment thread option_parser/src/lib.rs

@saravan2 saravan2 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and requested changes.

I recommend developing an integration test to validate this feature.

The integration test infrastructure already has everything needed to support a minimal test to boot a guest VM with a (disk) device arg :addr=0A.0. The test runner is capable of running lscpi command inside the guest to confirm the device appears on intended address 0A.0

@saravan2

Copy link
Copy Markdown
Member

Regarding the open question in the PR description:

Both Cloud-Hypervisor and QEMU adhere to the standard PCIe addressing format (Segment:Bus:Device.Function). However, their architectural implementations of the PCI hierarchy differ significantly
QEMU: Supports complex topologies with multiple buses per segment via PCI-to-PCI bridges.
Cloud-Hypervisor: Since cloud workloads do not require bridge emulation. Each PCI segment is limited to a single bus (Bus 0)

The MCFG (Memory Mapped Configuration Space Base Address Description Table) acts as the guest OS's source of truth for PCI enumeration.
Cloud-Hypervisor hardcodes the start and end bus for every pci segment to 0
As seen in :

start: 0,
end: 0,

Since the guest OS relies entirely on the MCFG table to discover the bus range, it will never scan or identify a device on Bus 1, regardless of our internal VMM data structures.

Comment thread vmm/src/api/openapi/cloud-hypervisor.yaml

@saravan2 saravan2 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cloud-hypervisor.yaml changes should be part of a dedicated commit.

@scholzp

scholzp commented Mar 26, 2026

Copy link
Copy Markdown
Contributor Author

I hope to have addressed all remarks accordingly. I triple checked all comments/commit messages for typos or errors and all should be fine now.

@scholzp
scholzp requested a review from rbradford March 26, 2026 16:19

@saravan2 saravan2 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@phip1611

Copy link
Copy Markdown
Member

@rbradford can we merge this? I think Pascal addressed all concerns

@phip1611
phip1611 force-pushed the upstream_pci_bdf branch 2 times, most recently from 0d7bb9f to 8d7935d Compare April 1, 2026 16:55
scholzp added 15 commits April 2, 2026 09:39
This commit refactors the PCI bus struct. It has two major focuses.
First, we change the type of `device_ids` in `PciBus` to an array. A
fixed-size array better reflects real PCI bus constraints, especially
its limited number of PCI devices. Moreover, it can't be grown
accidentally.

The second focus is changing the type of the key of `devices` in
`PciBus` to `u8`, since device IDs are not allowed to exceed 31. We
furthermore replace magic numbers with constants and make them publicly
available so we can use them in a follow-up change when parsing user
input.

Signed-off-by: Pascal Scholz <[email protected]>
On-behalf-of: SAP [email protected]
Allocating a device ID is crucial for assigning a specific ID to a
device. We need this to implement configurable PCI BDF.

Signed-off-by: Pascal Scholz <[email protected]>
On-behalf-of: SAP [email protected]
Next to tests for `allocate_device_bdf`, we introduce a new constructor
`new_without_address_manager`, only available in the test build. As
there is no way to instantiate an `AddressManager` in the tests, we use
this constructor to work around this.

Signed-off-by: Pascal Scholz <[email protected]>
On-behalf-of: SAP [email protected]
To pass a PCI device’s device ID when requesting a PCI BDF, we add a
new member to each device config that is added as a PCI device.

Signed-off-by: Pascal Scholz <[email protected]>
On-behalf-of: SAP [email protected]
The `addr` option is used by Qemu to specify the device and function
ID part of a BDF for a PCI device. We use the same format, but
implement only single-function devices for now. This means we set the
function part to zero and allow specifying only the device part of the
BDF with the `addr` option.

Signed-off-by: Pascal Scholz <[email protected]>
On-behalf-of: SAP [email protected]
Config structs that allow configuring the devices' PCI bus device ID
via `device_bdf` should also allow validating that ID. We need this
option to ensure that invalid device IDs received via an API call
result in an error as soon as possible. In this case, this would be
after deserialization. On this code path, validation via `parse` is
skipped and must be invoked by calling `validate`.

Signed-off-by: Pascal Scholz <[email protected]>
On-behalf-of: SAP [email protected]
We pass the device ID from the BDF to the allocation routine, where it
is used. This enables future support for specifying the segment and bus
in the SBDF. We can then extend the design to provide a complete BDF
structure and pass its parts at the appropriate levels.

Signed-off-by: Pascal Scholz <[email protected]>
On-behalf-of: SAP [email protected]
We add documentation for the `bdf_device` member to all documented
structs.

Signed-off-by: Pascal Scholz <[email protected]>
On-behalf-of: SAP [email protected]
Update the API doc for all PCI devices that use the `device_bdf` member
added previously.

Signed-off-by: Pascal Scholz <[email protected]>
On-behalf-of: SAP [email protected]
Note that the HTTP API doesn't support hex literals, as this would
require a custom de-/serializer for the `bdf_device` field or using an
additional helper crate. We refrain from both as the JSON grammar[0]
does not support hex numbers by default.

[0] https://www.crockford.com/mckeeman.html

Signed-off-by: Pascal Scholz <[email protected]>
On-behalf-of: SAP [email protected]
We use a VecDeque to implicitly sort devices. Devices with a fixed BDF
in their config are added to the front. Devices without a given BDF are
added to the back. Processing the `VecDeque` from first to last ensures
that no clashes occur when assigning BDFs. Otherwise, we could assign a
required BDF to a device that doesn't need it.

`add_virtio_device` is an associated function because we need to call
it in the same place where we cannot create a mutable borrow of the
respective `DeviceManager` instance. This is the case, for example,
in `make_virtio_balloon_devices`.

Signed-off-by: Pascal Scholz <[email protected]>
On-behalf-of: SAP [email protected]
If we want to test for error cases, it can be useful to inspect the
`stderr` of a `Command` to analyze the errors. For example, this allows
us to ensure that a `Command` returns an `IoError` by parsing the
error trace, if an `IoError` is expected.

This commit prepares the implementation of negative integration tests
for the configurable BDFs.

Signed-off-by: Pascal Scholz <[email protected]>
On-behalf-of: SAP [email protected]
This commit adds an integration test to verify that the guest sees the
correct BDF. Moreover, we check that we can allocate a random free BDF
and that freeing BDFs works.

Signed-off-by: Pascal Scholz <[email protected]>
On-behalf-of: SAP [email protected]
This integration test verifies that a BDF cannot be allocated twice.
Moreover, we check that the returned error matches our expectations.

Signed-off-by: Pascal Scholz <[email protected]>
On-behalf-of: SAP [email protected]
This integration test verifies two conditions: first, that a requested
BDF device ID is valid (in the range of 0-31); second, that requesting
a BDF with a function ID returns an error. For both, we confirm that
the correct error log appears.

Signed-off-by: Pascal Scholz <[email protected]>
On-behalf-of: SAP [email protected]
Comment thread vmm/src/config.rs
serial=<serial_number>,backing_files=on|off,sparse=on|off,\
image_type=<raw,qcow2,vhd,vhdx>,lock_granularity=byte-range|full";
image_type=<raw,qcow2,vhd,vhdx>,lock_granularity=byte-range|full,\
addr=<DD.F>";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed this before - we always keep CLI and JSON API/struct names the same. For consistency with the pci_segment this should be called pci_device_id in the CLI and struct. And it should just be an int. Why specify the function?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why specify the function?

for consistency with QEMU, I think. But on the other side, CHV will not support multifunction devices in the foreseeable future I think.

Comment thread vmm/src/pci_segment.rs
}

#[cfg(test)]
mod unit_tests {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a test for if the user specifies the same pci_device_id for two devices. Do you need to add valodation logic for that?

@rbradford

Copy link
Copy Markdown
Member

I did a rebase on the config deduplication (and cleaned up some commit messages along the way)

https://github.com/rbradford/cloud-hypervisor/commits/202604/custom-pci-device-id/

As you can see if you do some simple refactoring first the desired change becomes much simpler. However whilst doing this I realised that the current implementation has a major flaw. Although it uses a clever trick with a double ended vector for virtio devices to make sure that those that have specific PCI device ID get picked first it this doesn't interleave with the non-virtio devices. e.g. if you have a VFIO device marked with a reserved device ID of 3 and a bunch of virtio devices then the allocation will fail.

I'm thinking about other solutions.

@scholzp

scholzp commented Apr 7, 2026

Copy link
Copy Markdown
Contributor Author

First and foremost: Thanks @rbradford for taking the time to refactor and fixing the possible clash. I think that your solution in #7965 is the way forward and we should build on this PR as you already applied all relevant feature changes from my PR (#7631) on top of your refactoring from #7962 there.

I also discussed this with @phip1611 and we agreed to close this PR in favor of your work.

@scholzp

scholzp commented Apr 7, 2026

Copy link
Copy Markdown
Contributor Author

See #7631 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants