From 4462972fa9839b96c434189742bbb593f20f792a Mon Sep 17 00:00:00 2001 From: Leander Kohler Date: Mon, 1 Jun 2026 15:04:10 +0200 Subject: [PATCH] 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]: https://github.com/openbsd/src/blob/54a2bc1e5457a3dc555cb0cc642641a62266c778/sys/dev/pci/pci_map.c#L154-L180 [1]: https://github.com/openbsd/src/blob/54a2bc1e5457a3dc555cb0cc642641a62266c778/sys/dev/pci/pcireg.h#L429-L443 Co-authored-by: Thomas Prescher On-behalf-of: SAP leander.kohler@sap.com Signed-off-by: Leander Kohler --- pci/src/configuration.rs | 112 +++++++++++++++++++++++++++++++++++---- 1 file changed, 101 insertions(+), 11 deletions(-) diff --git a/pci/src/configuration.rs b/pci/src/configuration.rs index 8973c3883d..7a28e18c1b 100644 --- a/pci/src/configuration.rs +++ b/pci/src/configuration.rs @@ -26,6 +26,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; @@ -652,12 +653,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(®_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 @@ -980,13 +979,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(®_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 @@ -1086,6 +1085,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(®_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 { self.pending_bar_reprogram.clone() } @@ -1343,4 +1363,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); + } }