pci: Detect masked BAR size probes

A PCI BAR stores its address in the upper bits and type information in
the lower bits. To determine the BAR size, an operating system disables
memory decoding, writes ones to the BAR's address bits, and reads the
value back. Bits that remain zero reveal the required alignment and
therefore the size.

We currently recognize only 0xffffffff as a size probe. OpenBSD instead
writes the memory BAR address mask 0xfffffff0, without setting the lower
four non-address bits [0][1].

We misinterpret that probe as a real BAR relocation. Because OpenBSD has
disabled memory space, the relocation remains pending. We apply it when
OpenBSD enables memory space again, moving the virtio block BAR away
from its mapped address and preventing the guest from booting.

Recognize probes using the address mask for each BAR type and exclude
them from BAR reprogramming. Add a test for the OpenBSD memory BAR
probe.

[0]: 54a2bc1e54/sys/dev/pci/pci_map.c (L154-L180)
[1]: 54a2bc1e54/sys/dev/pci/pcireg.h (L429-L443)

Co-authored-by: Thomas Prescher <thomas.prescher@cyberus-technology.de>
On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
This commit is contained in:
Leander Kohler
2026-06-01 15:04:10 +02:00
committed by Rob Bradford
parent 668e8f789a
commit b67e7da10c

View File

@@ -27,6 +27,7 @@ const STATUS_REG_CAPABILITIES_USED_MASK: u32 = 0x0010_0000;
const BAR0_REG: usize = 4;
const ROM_BAR_REG: usize = 12;
const ROM_BAR_IDX: usize = 6;
const BAR_SIZE_PROBE_ALL_ONES: u32 = 0xffff_ffff;
const BAR_IO_ADDR_MASK: u32 = 0xffff_fffc;
const BAR_MEM_ADDR_MASK: u32 = 0xffff_fff0;
const ROM_BAR_ADDR_MASK: u32 = 0xffff_f800;
@@ -646,12 +647,10 @@ impl PciConfiguration {
pub fn write_reg(&mut self, reg_idx: usize, value: u32) {
let mut mask = self.writable_bits[reg_idx];
if (BAR0_REG..BAR0_REG + NUM_BAR_REGS).contains(&reg_idx) {
// Handle very specific case where the BAR is being written with
// all 1's to retrieve the BAR size during next BAR reading.
if value == 0xffff_ffff {
mask &= self.bars[reg_idx - 4].size;
}
// Handle the case where all BAR address bits are set to retrieve
// the BAR size during the next BAR read.
if self.is_bar_size_probe(reg_idx, value) {
mask &= self.bars[reg_idx - BAR0_REG].size;
} else if reg_idx == ROM_BAR_REG {
// Handle very specific case where the BAR is being written with
// all 1's on bits 31-11 to retrieve the BAR size during next BAR
@@ -974,13 +973,13 @@ impl PciConfiguration {
let value = LittleEndian::read_u32(data);
// Ignore the case where the BAR size is being asked for.
if self.is_bar_size_probe(reg_idx, value) {
return None;
}
let mask = self.writable_bits[reg_idx];
if (BAR0_REG..BAR0_REG + NUM_BAR_REGS).contains(&reg_idx) {
// Ignore the case where the BAR size is being asked for.
if value == 0xffff_ffff {
return None;
}
let bar_idx = reg_idx - BAR0_REG;
// Handle special case where the address being written is
// different from the address initially provided. This is a
@@ -1080,6 +1079,27 @@ impl PciConfiguration {
None
}
/// Guests determine a BAR's size by disabling address decoding, writing
/// ones to its address bits, and reading back the device's size mask.
/// Matching only the address portion recognizes probes that preserve the
/// BAR type bits, such as OpenBSD's `0xffff_fff0`, and keeps them out of
/// the BAR reprogramming path. A 64-bit BAR's upper half and an unused BAR
/// have no type bits and therefore use the literal all-ones pattern.
fn is_bar_size_probe(&self, reg_idx: usize, value: u32) -> bool {
if !(BAR0_REG..BAR0_REG + NUM_BAR_REGS).contains(&reg_idx) {
return false;
}
let bar_idx = reg_idx - BAR0_REG;
match self.bars[bar_idx].r#type {
Some(PciBarRegionType::IoRegion) => value & BAR_IO_ADDR_MASK == BAR_IO_ADDR_MASK,
Some(PciBarRegionType::Memory32BitRegion | PciBarRegionType::Memory64BitRegion) => {
value & BAR_MEM_ADDR_MASK == BAR_MEM_ADDR_MASK
}
None => value == BAR_SIZE_PROBE_ALL_ONES,
}
}
pub(crate) fn pending_bar_reprogram(&self) -> Vec<BarReprogrammingParams> {
self.pending_bar_reprogram.clone()
}
@@ -1336,4 +1356,74 @@ mod unit_tests {
assert_eq!(subclass, 0x01);
assert_eq!(prog_if, 0x5a);
}
#[test]
fn mem_bar_size_probe_ignores_all_address_bits_set() {
let mut cfg = PciConfiguration::new(
0x1234,
0x5678,
0x1,
PciClassCode::MultimediaController,
&PciMultimediaSubclass::AudioController,
None,
PciHeaderType::Device,
0xABCD,
0x2468,
None,
None,
);
let bar_addr = 0xc000_0000;
let bar = PciBarConfiguration::new(
0,
0x80000,
PciBarRegionType::Memory32BitRegion,
PciBarPrefetchable::NotPrefetchable,
)
.set_address(bar_addr);
cfg.add_pci_bar(&bar).unwrap();
cfg.write_reg(COMMAND_REG, COMMAND_REG_MEMORY_SPACE_MASK);
let reprogram = cfg.write_config_register(BAR0_REG, 0, &0xffff_fff0u32.to_le_bytes());
assert!(reprogram.is_empty());
assert_eq!(cfg.read_reg(BAR0_REG), 0xfff8_0000);
assert_eq!(cfg.get_bar_addr(0), bar_addr);
}
#[test]
fn unused_bar_size_probe_does_not_reprogram_previous_bar() {
let mut cfg = PciConfiguration::new(
0x1234,
0x5678,
0x1,
PciClassCode::MultimediaController,
&PciMultimediaSubclass::AudioController,
None,
PciHeaderType::Device,
0xABCD,
0x2468,
None,
None,
);
let bar_addr = 0xc000_0000;
let bar = PciBarConfiguration::new(
0,
0x80000,
PciBarRegionType::Memory32BitRegion,
PciBarPrefetchable::NotPrefetchable,
)
.set_address(bar_addr);
cfg.add_pci_bar(&bar).unwrap();
cfg.write_config_register(BAR0_REG, 0, &BAR_SIZE_PROBE_ALL_ONES.to_le_bytes());
cfg.write_config_register(BAR0_REG + 1, 0, &BAR_SIZE_PROBE_ALL_ONES.to_le_bytes());
cfg.write_config_register(BAR0_REG, 0, &(bar_addr as u32).to_le_bytes());
let reprogram =
cfg.write_config_register(COMMAND_REG, 0, &COMMAND_REG_MEMORY_SPACE_MASK.to_le_bytes());
assert!(reprogram.is_empty());
assert_eq!(cfg.get_bar_addr(0), bar_addr);
}
}