Skip to content

pci, vmm: Identify a moving PCI BAR by index, not by address - #8695

Open
yamahata wants to merge 8 commits into
cloud-hypervisor:mainfrom
yamahata:fix/202608/pci-bar-index
Open

pci, vmm: Identify a moving PCI BAR by index, not by address#8695
yamahata wants to merge 8 commits into
cloud-hypervisor:mainfrom
yamahata:fix/202608/pci-bar-index

Conversation

@yamahata

@yamahata yamahata commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

A BAR being relocated is currently identified by its current base
address. A guest is free to program two BARs to the same base, in which
case the address comparison can match the wrong BAR. A BAR is uniquely
identified by (PCI device, BAR index), so pass the index down the
relocation path and match on that instead. Where the base address is
still needed, it is taken from the device's own BAR records rather than
read back from config space.

This is preparation for #8572.

  1. pci: Factor out the BAR reprogramming loop for BAR moves
    PciConfigIo and PciConfigMmio carried two copies of the same loop.
    No functional change.
  2. devices, virtio-devices, vmm: Place ioeventfds and ivshmem RAM by index
    Take the creation-time base from the allocated BAR list, already the
    source of truth for the MMIO bus mapping.
  3. devices, pci, virtio-devices, vmm: Identify PCI BARs by index
    The functional change: bar_idx through both move_bar() traits.
  4. devices: Remove unused config_bar_addr()
    Two accessors that never had a caller.
  5. pci, vmm: Add tests for BAR index identification

Migration compatibility: BarReprogrammingParams is passed on the snapshot
state as part of PciConfigurationState::pending_bar_reprogram, so the new
bar_idx field is an Option. An entry restored from a snapshot written
before this series cannot identify its BAR, and is rolled back rather than
applied to a guessed slot -- the same thing that already happens when a
move fails.

Ref: #8572
The whole tree is found at https://github.com/yamahata/cloud-hypervisor/tree/202607/pci-bus-eagar-unmap , it needs some updates, though. It give an idea how thing will be.

@yamahata
yamahata requested a review from a team as a code owner August 6, 2026 22:42
@sboeuf

sboeuf commented Aug 7, 2026

Copy link
Copy Markdown
Member

@yamahata could you please share the intent behind this patch and the whole series that you have linked as well. I'm just trying to understand if you're only trying to solve the bug mentioned at #8572 or if there's more to it.
Reading the bug description #8572, I just want to make sure we tackle the bug only and not follow AI on fixing hypothetical issues ;)

Comment thread pci/src/bus.rs
"BAR reprogramming without a BAR index: 0x{:x}->0x{:x}(0x{:x}), keeping old BAR",
params.old_base, params.new_base, params.len
);
device.restore_bar_addr(params);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I notice restore_bar_addr() matches on address, should it also change to identify by index?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you for pointing it out. The eventual fix will remove the function entirely.
2d9211d

I didn't want unnecessary code churn. code clean up with this patch series, and later eliminate the function entirely. Instead, I left the function as is for now. Future change will delete it.

@yamahata

yamahata commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

@yamahata could you please share the intent behind this patch and the whole series that you have linked as well. I'm just trying to understand if you're only trying to solve the bug mentioned at #8572 or if there's more to it. Reading the bug description #8572, I just want to make sure we tackle the bug only and not follow AI on fixing hypothetical issues ;)

My scope is only to solve #8572. I heavily depend on AI to be honest, though.

I didn't hit the issue to pick up the wrong BAR with real guest BIOS/OS except the test case.
The goal of this patch series is code clean up, the theoretical bug fix is side effect.
This clean up helps to make the logic clearer/more robust, and further change for the fix easier.

It is the BAR itself that is in transition state, not BAR address.
My fix is, to eagar unmap, and to lazy map with two steps. The existing code is to lazy unmap and map with one step.
To split up the BAR movement step into two steps, I need to carry the info like BarReprogrammingParams.
I'd like to simplify it for further code change. Just this BAR of this PCI device is in transient state.

I didn't want to upload the entire fix. I uploaded the logical chunk of the clean up first as this clean up is already big.

@yamahata

yamahata commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

More concretely, two steps are

  • eager unmap step: BAR update with MSE or IOE = 0. unmap eargerly(free region and free underlying resources). The existing code is only to remember it and carry it around.
  • lazy map step: when the guest set MSE or IOE = 1, map the BAR lazily(allocation region and underlying resource). The existing code is to do both unmap and map lazily.

The existing code carries around old_bar_addr and new_bar_addr as BarReprogramParams. That's troublesome and error-prone.
I think essentially we need to remember only which BAR is in transitional state. Other information, e,g, BAR address, can be derived when necessary.

@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.

Just finished my first round of review. I focused primarily on the code, as PCI and BAR handling is outside my main area of expertise.

Comment thread pci/src/bus.rs Outdated
device,
params.region_type,
) {
// Rollback: the config register was already updated to

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 think this comment duplicates much of the code - restore_bar_addr() is pretty descriptive already. Could you rephrase it while you are on it please?

How about something like

// Rollback the changes from detect_bar_reprogramming() or so?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sure. I updated the comment.

Comment thread devices/src/ivshmem.rs Outdated
self.configuration.get_bar_addr(IVSHMEM_BAR2_IDX)
}

pub fn data_bar_index() -> usize {

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.

about commit message:

devices, virtio-devices, vmm: Place ioeventfds and ivshmem RAM by index

I have a feeling like it would be beneficial to rephrase it:

devices, virtio-devices, vmm: Place ioeventfds and ivshmem RAM by index
The device creation of the virtio ioeventfds and the ivshmem used to
get BAR base address from the device.  Instead, get BAR index, and
read BAR base address from the BAR list returned by
allocate_pci_bars() instead. [PLEASE INSERT HERE WHY THE OLD VARIANT 
WAS USED IN THE FIRST PLACE AND **WHY** USING THE allocate_pci_bars()
IS BETTER

I highly appreciate important why explanations when reading/reviewing code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Here is the updated version.

 devices, virtio-devices, vmm: Get ioeventfds/ivshmem BAR base by index

Setting up the virtio ioeventfds and the ivshmem RAM region used to
read the BAR base address from the device.  Instead, read BAR base
address with the BAR index from the BAR list returned by
allocate_pci_bars().

The change set of c7cabc88b40a ("vmm: Conditionally update ioeventfds
for virtio PCI device") first introduced config_bar_addr() to
determine which BAR was updated by its BAR base address to avoid
unnecessarily updating the underlying ioeventfd resources.  Later the
other devices adopted the idiom, and introduced data_bar_addr().
I guess that because move_bar() doesn't receive PCI BAR index, but
old_base and new_base, using base address was easier to implement
than adding bar index to move_bar(). but the commit message and the
diff doesn't tell why bar index wasn't used.

allocate_pci_bars() sets up PCI BARs in pci_device.configuration
internally and returns the BAR list including the base address.
config_bar_addr()/data_bar_addr() return the base address of a
specified BAR from pci_device.configuration.  It's redundant to query
the PCI device for those base addresses again because the caller of
allocate_pci_bars() already obtained BAR information including the
base address.  It is straightforward and uniform to use the BARs
returned by allocate_pci_bars().  This untangles the unnecessary calls
from the device manager to pci devices.

Introduce MissingPciBar error to cover the case when the BAR index is
invalid.  As long as a valid BAR index is passed, it won't be
triggered.

This is a step towards dropping the BAR base address comparison in the
BAR movement code.  Because there are still callers of
config_bar_addr()/data_bar_addr(), this patch does not remove them
yet.

Comment thread vmm/src/device_manager.rs Outdated
MissingPciDevice,

/// Missing PCI BAR.
#[error("Missing PCI BAR at index {0}")]

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.

would it make sense to use hex here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ok, I made it inti "{0:#x}" to match with UnknownPciBdf case.

     #[error("Failed to find the device corresponding to a specific PCI b/d/f: {0:#x}")]
     UnknownPciBdf(u32),

.get_bar_addr(VIRTIO_COMMON_BAR_INDEX.into())
}

pub fn config_bar_index() -> usize {

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.

This helper is providing pretty little value. Your final PR has three usages. Can we simply use the constant at all call sites?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Makes sense. I replace all *_bar_index() with constants.

Comment thread pci/src/bus.rs Outdated
) {
for params in bar_reprogram {
let Some(bar_idx) = params.bar_idx else {
// Restored from a snapshot by the previous version predating the BAR index.

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 think we can simplify this.

How about

// Rollback the BAR index change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

How about this?
// Migration case: the previous version doesn't snapshot the BAR index being moved.
This case happens when migration.

Comment thread pci/src/device.rs

#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct BarReprogrammingParams {
///

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.

Please start without an empty /// top line. This style is only used in some old parts of CH and doesn't relect current rustdoc standards and best practices. After the first line, add an empty line and then elaborate on the details.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sure, will fix it.

VIRTIO_COMMON_BAR_INDEX.into()
}

pub fn shm_bar_index() -> usize {

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 am unsure about the value-add of these wrappers around constants. Why are they used in the code in the first place? Because VIRTIO_SHM_BAR_INDEX is not visible to callers?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, VIRTIO_SHM_BAR_INDEX is private. and I followed to add xxx_bar_index/addr().
Ok, with the next version, I'll make those index public and remove xxx_bar_index(). Let's see how the code will bee.

PciConfigIo and PciConfigMmio each have their own copy of the loop that
applies the BAR moves returned by write_config_register().
Factor out the loop to a PciBus method.

No functional change intended.

Signed-off-by: Isaku Yamahata <iyamahata@crusoe.ai>
Assisted-by: Claude:Opus-5
The device manager will use the constant of ivshmem BAR2 as data BAR.
Rename IVSHMEM_BAR2_IDX to IVSHMEM_DATA_BAR_IDX to give a meaningful
name for later use.

Signed-off-by: Isaku Yamahata <iyamahata@crusoe.ai>
As renamed IVSHMEM_BAR2_IDX to IVSHMEM_DATA_BAR_IDX, update the code
to use data_bar instead of bar2 for consistency.

Signed-off-by: Isaku Yamahata <iyamahata@crusoe.ai>
For code readability rename VIRTIO_COMMON_BAR_INDEX to
VIRTIO_CONFIG_BAR_INDEX as CONFIG makes more sense than COMMON.  At
the same time, change the type from u8 to usize.  PCI BAR index is
typed as usize in common code.  u8 is used only within pci_device.rs.
So change the type from u8 to usize for consistency.  The trade-off is
the cast to u8 for VirtioPciCap/MsixCap.

Signed-off-by: Isaku Yamahata <iyamahata@crusoe.ai>
Setting up the virtio ioeventfds and the ivshmem RAM region used to
read the BAR base address from the device.  Instead, read BAR base
address with the BAR index from the BAR list returned by
allocate_pci_bars().

The change set of c7cabc8 ("vmm: Conditionally update ioeventfds
for virtio PCI device") first introduced config_bar_addr() to
determine which BAR was updated by its BAR base address to avoid
unnecessarily updating the underlying ioeventfd resources.  Later the
other devices adopted the idiom, and introduced data_bar_addr().
I guess that because move_bar() doesn't receive PCI BAR index, but
old_base and new_base, using base address was easier to implement
than adding bar index to move_bar(). but the commit message and the
diff doesn't tell why bar index wasn't used.

allocate_pci_bars() sets up PCI BARs in pci_device.configuration
internally and returns the BAR list including the base address.
config_bar_addr()/data_bar_addr() return the base address of a
specified BAR from pci_device.configuration.  It's redundant to query
the PCI device for those base addresses again because the caller of
allocate_pci_bars() already obtained BAR information including the
base address.  It is straightforward and uniform to use the BARs
returned by allocate_pci_bars().  This untangles the unnecessary calls
from the device manager to pci devices.

Introduce MissingPciBar error to cover the case when the BAR index is
invalid.  As long as a valid BAR index is passed, it won't be
triggered.

This is a step towards dropping the BAR base address comparison in the
BAR movement code.  Because there are still callers of
config_bar_addr()/data_bar_addr(), this patch does not remove them
yet.

Signed-off-by: Isaku Yamahata <iyamahata@crusoe.ai>
Assisted-by: Claude:Fable-5
Identify PCI BAR by (pci device, BAR index) in the PCI BAR relocation
code instead of BAR base address.  In theory, a guest can program two
or more BARs to the same base address, so an address comparison
can match the wrong BAR.  Well-behaved software won't do this, though.

Pass around the BAR index through DeviceRelocation::move_bar() and
PciDevice::move_bar() and match on it instead of base address. VFIO
and vfio-user derive the old address from their own region records.
move_bar() keeps old_base on the DeviceRelocation side: it is still
needed to unregister the ioeventfds and to unmap the shared memory
region at their current addresses.

BarReprogrammingParams is passed around on the snapshot state as part
of PciConfigurationState::pending_bar_reprogram, so bar_idx is
optional: an entry restored from a snapshot that predates it cannot
identify its BAR.  Such an entry is rolled back rather than applied to
a guessed slot, which is what already happens when a move fails.

Device eject therefore takes the settings BAR's mapped address from
the device-tree resource instead of config space.  The device is off
the device tree by the time eject gets there, so a resource that
cannot be found is reported and skipped rather than failing the eject
half way through.

For the virtio shared-memory BAR the address-equality guard is
replaced by the index check.  A move of any other BAR index is now a
no-op, which changes nothing for virtio because only the config and
shared-memory BARs exist and the existing code assumes a non-config
bar means the shared-memory BAR.  Now invalid bar index results in
real error instead of being silently ignored.  In practice that
execution path won't be hit because such a BAR cannot be touched via
pci_config_write().

There is no caller of config_bar_addr() and data_bar_addr() so the
following patch removes them.

Signed-off-by: Isaku Yamahata <iyamahata@crusoe.ai>
Assisted-by: Claude:Fable-5
There is no caller for IvshmemDevice::config_bar_addr(),
data_bar_addr(), PvPanicDevice::config_bar_addr(), and
VirtioPciDevice::config_bar_addr().  Because they are pub, they are
not reported as dead code.  Remove them.

Signed-off-by: Isaku Yamahata <iyamahata@crusoe.ai>
Assisted-by: Claude:Opus-5
Cover the two mechanisms the previous patches introduced.

For detect_bar_reprogramming(), each of the three ways a BAR move is
reported now asserts the slot it names: a plain 32-bit BAR, the
high-dword write of a 64-bit BAR (which must name the low slot, the one
devices record their BAR under), and the expansion ROM BAR.  One test
programs two BARs to the same base first, so the reported index is the
only thing that distinguishes them - the case an address comparison
gets wrong.

Two tests pin the snapshot behavior: a pending entry restored without
a bar_idx stays None rather than defaulting to slot 0, and the wire
form of BarReprogrammingParams still decodes when the field is absent,
which is what a snapshot taken before this series looks like.
apply_bar_reprogramming() is then checked to move the named BAR, and
to roll an unnamed one back instead of moving a guessed slot.
MockDeviceRelocation grows a record of the BAR indexes it was asked to
move, so that those two can tell the cases apart.

For bar_addr_of_idx(), the lookup is checked against a BAR list that is
not ordered by index, and for the error raised when the index is
absent.

Signed-off-by: Isaku Yamahata <iyamahata@crusoe.ai>
Assisted-by: Claude:Opus-5
@yamahata
yamahata force-pushed the fix/202608/pci-bar-index branch from d9e1e38 to 25b4d18 Compare August 24, 2026 08:26
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.

4 participants