Skip to content

Conversation

@cagatay-y
Copy link
Contributor

According to the PCI spec (v6.0, p 930), "[t]o determine how much address space a Function requires, system software should write a value of all 1's to each BAR register and then read the value back." QEMU (and possibly others) mask the provided address based on the size of the address space [2], which is always larger than 128 bytes for memory BARs, so the value of the last nibble has no effect. However, cloud-hypervisor (with possibly others) is more strict in its interpretation of the specification and check for exactly the all-bits-set pattern [3]. On the latter platforms, the current pattern can be erroneously interpreted as a BAR relocation instead of sizing.

According to the PCI spec [1], "[t]o determine how much address space a
Function requires, system software should write a value of all 1's to
each BAR register and then read the value back." QEMU (and possibly
others) mask the provided address based on the size of the address space
[2], which is always larger than 128 bytes for memory BARs, so the value
of the last nibble has no effect. However, cloud-hypervisor (with
possibly others) is more strict in its interpretation of the
specification and check for exactly the all-bits-set pattern [3]. On the
latter platforms, the current pattern can be erroneously interpreted as
a BAR relocation instead of sizing.

[1]: PCI Express Base Specification Revision 6.0, page 930
[2]: v10.1.2:hw/pci/pci.c:1658
[3]: v49.0:pci/src/configuration.rs:979
@bjorn3
Copy link
Contributor

bjorn3 commented Nov 20, 2025

I don't have a copy of the pci spec (not worth it to pay for one and couldn't find any copies online, just a bunch of wiki pages, linux source code and some presentation slides), but I would have assumed that the writing all 1s to a BAR register would also change the BAR location on real hardware. Just to something outside of the range the parent bridge allows, hence effectively disabling the BAR until you write back the original value. I wouldn't expect there to be a special case for the literal all 1s value that keeps a shadow copy of the BAR value.

@mkroening
Copy link
Member

Ah, that makes sense. We should definitely make sure to disable the address spaces before sizing the bar, but I think that is separate from this PR.

This PR should be a strict improvement over the status quo by closer aligning the code with the specification and would still be required regardless of disabling address spaces.

Writing back the value does happen here (unrelated to this PR):

let bar = unsafe { access.read(self.0, offset) };

let address = bar.get_bits(4..32) << 4;

access.write(self.0, offset, address);

@IsaacWoods
Copy link
Member

IsaacWoods commented Nov 29, 2025

Thanks for the PR.

Looking at the spec (I have the PCI Express spec rev 5.0 v1.0; relevant section is 7.5.1.2.1), this is correct - we should be writing all-ones; I'm not sure why we were manually zeroing the bottom bits before.

However, I think an implementation that misbehaves with 0xfffffff0 vs 0xffffffff is likely incorrect, with bits 0..4 being read-only. I'd imagine these are just hard-coded to the correct values on real devices, along with the bottom bits being hardcoded to zero for the sizing mechanism to work at all. I think a hypervisor relying on different behaviour is probably a bad idea.

The same section does mention disabling address decoding via the function's command register - I've interpreted this as meaning we should be clearing bit 1 of the command register before sizing the BAR and restoring it afterwards. I would be very happy to accept another PR to do so (or I'll try to remember to do so when I next work on this crate).

@IsaacWoods IsaacWoods merged commit a551398 into rust-osdev:main Nov 29, 2025
3 checks passed
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