Skip to content

Commit 54eb06d

Browse files
committed
fix(pci): handling of PCI configuration capability
The PCI configuration capability gives a way to the guest to access BAR memory for a device without actually mapping the BAR in guest memory. The way this works is that the guest accesses the capability directly (in PCI configuration space) and it programs reads/writes from/to BAR memory. When such an access is detected we redirect certain PCI configuration space access to VirtIO BAR accesses. These accesses happen in chunks of 4 bytes, however the code that handles BAR accesses expects the length of an access to match exactly the length of the field we are accessing, e.g. accessing a u8 field needs a &[u8] slice with length of 1. The emulation logic was not taking that into account so the mechanism wasn't effectively working. Fix that and also get rid off an unsafe `transmute` in favour of an Le32::into<u32>(). Finally, add unit tests that ensure everything works as expected. Signed-off-by: Babis Chalios <[email protected]>
1 parent 2540713 commit 54eb06d

File tree

1 file changed

+97
-10
lines changed
  • src/vmm/src/devices/virtio/transport/pci

1 file changed

+97
-10
lines changed

src/vmm/src/devices/virtio/transport/pci/device.rs

Lines changed: 97 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -571,10 +571,14 @@ impl VirtioPciDevice {
571571
.unwrap();
572572
}
573573
} else {
574-
let bar_offset: u32 =
575-
// SAFETY: we know self.cap_pci_cfg_info.cap.cap.offset is 32bits long.
576-
unsafe { std::mem::transmute(self.cap_pci_cfg_info.cap.cap.offset) };
577-
self.read_bar(0, bar_offset as u64, data)
574+
let bar_offset: u32 = self.cap_pci_cfg_info.cap.cap.offset.into();
575+
let len = u32::from(self.cap_pci_cfg_info.cap.cap.length) as usize;
576+
// BAR reads expect that the buffer has the exact size of the field that
577+
// offset is pointing to. So, do some check that the `length` has a meaningful value
578+
// and only use the part of the buffer we actually need.
579+
if len <= 4 {
580+
self.read_bar(0, bar_offset as u64, &mut data[..len]);
581+
}
578582
}
579583
}
580584

@@ -592,10 +596,16 @@ impl VirtioPciDevice {
592596
right[..data_len].copy_from_slice(data);
593597
None
594598
} else {
595-
let bar_offset: u32 =
596-
// SAFETY: we know self.cap_pci_cfg_info.cap.cap.offset is 32bits long.
597-
unsafe { std::mem::transmute(self.cap_pci_cfg_info.cap.cap.offset) };
598-
self.write_bar(0, bar_offset as u64, data)
599+
let bar_offset: u32 = self.cap_pci_cfg_info.cap.cap.offset.into();
600+
let len = u32::from(self.cap_pci_cfg_info.cap.cap.length) as usize;
601+
// BAR writes expect that the buffer has the exact size of the field that
602+
// offset is pointing to. So, do some check that the `length` has a meaningful value
603+
// and only use the part of the buffer we actually need.
604+
if len <= 4 {
605+
self.write_bar(0, bar_offset as u64, &data[..len])
606+
} else {
607+
None
608+
}
599609
}
600610
}
601611

@@ -771,8 +781,13 @@ impl PciDevice for VirtioPciDevice {
771781
{
772782
let offset = base - self.cap_pci_cfg_info.offset;
773783
let mut data = [0u8; 4];
774-
self.read_cap_pci_cfg(offset, &mut data);
775-
u32::from_le_bytes(data)
784+
let len = u32::from(self.cap_pci_cfg_info.cap.cap.length) as usize;
785+
if len <= 4 {
786+
self.read_cap_pci_cfg(offset, &mut data[..len]);
787+
u32::from_le_bytes(data)
788+
} else {
789+
0
790+
}
776791
} else {
777792
self.configuration.read_reg(reg_idx)
778793
}
@@ -1373,4 +1388,76 @@ mod tests {
13731388
assert_eq!(id, PciCapabilityId::MsiX);
13741389
assert_eq!(next, 0);
13751390
}
1391+
1392+
fn cap_pci_cfg_read(device: &mut VirtioPciDevice, bar_offset: u32, length: u32) -> u32 {
1393+
let pci_config_cap_offset = capabilities_start(device) as usize
1394+
+ 3 * (size_of::<VirtioPciCap>() + 2)
1395+
+ (size_of::<VirtioPciNotifyCap>() + 2);
1396+
1397+
// To program the access through the PCI config capability mechanism, we need to write the
1398+
// bar offset and read length in the `VirtioPciCfgCap::cap.offset` and
1399+
// `VirtioPciCfgCap::length` fields. These are the third and fourth word respectively
1400+
// within the capability. The fifth word of the capability should contain the data
1401+
let offset_register = (pci_config_cap_offset + 8) >> 2;
1402+
let length_register = (pci_config_cap_offset + 12) >> 2;
1403+
let data_register = (pci_config_cap_offset + 16) >> 2;
1404+
1405+
device.write_config_register(offset_register, 0, bar_offset.as_slice());
1406+
device.write_config_register(length_register, 0, length.as_slice());
1407+
device.read_config_register(data_register)
1408+
}
1409+
1410+
fn cap_pci_cfg_write(device: &mut VirtioPciDevice, bar_offset: u32, length: u32, data: u32) {
1411+
let pci_config_cap_offset = capabilities_start(device) as usize
1412+
+ 3 * (size_of::<VirtioPciCap>() + 2)
1413+
+ (size_of::<VirtioPciNotifyCap>() + 2);
1414+
1415+
// To program the access through the PCI config capability mechanism, we need to write the
1416+
// bar offset and read length in the `VirtioPciCfgCap::cap.offset` and
1417+
// `VirtioPciCfgCap::length` fields. These are the third and fourth word respectively
1418+
// within the capability. The fifth word of the capability should contain the data
1419+
let offset_register = (pci_config_cap_offset + 8) >> 2;
1420+
let length_register = (pci_config_cap_offset + 12) >> 2;
1421+
let data_register = (pci_config_cap_offset + 16) >> 2;
1422+
1423+
device.write_config_register(offset_register, 0, bar_offset.as_slice());
1424+
device.write_config_register(length_register, 0, length.as_slice());
1425+
device.write_config_register(data_register, 0, data.as_slice());
1426+
}
1427+
1428+
#[test]
1429+
fn test_pci_configuration_cap() {
1430+
let mut vmm = create_vmm_with_virtio_pci_device();
1431+
let device = get_virtio_device(&vmm);
1432+
let mut locked_virtio_pci_device = device.lock().unwrap();
1433+
1434+
// Let's read the number of queues of the entropy device
1435+
// That information is located at offset 0x12 past the BAR region belonging to the common
1436+
// config capability.
1437+
let bar_offset = u32::try_from(COMMON_CONFIG_BAR_OFFSET).unwrap() + 0x12;
1438+
let len = 2u32;
1439+
let num_queues = cap_pci_cfg_read(&mut locked_virtio_pci_device, bar_offset, len);
1440+
assert_eq!(num_queues, 1);
1441+
1442+
// Let's update the driver features and see if that takes effect
1443+
let bar_offset = u32::try_from(COMMON_CONFIG_BAR_OFFSET).unwrap() + 0x14;
1444+
let len = 1u32;
1445+
let device_status = cap_pci_cfg_read(&mut locked_virtio_pci_device, bar_offset, len);
1446+
assert_eq!(device_status, 0);
1447+
cap_pci_cfg_write(&mut locked_virtio_pci_device, bar_offset, len, 0x42);
1448+
let device_status = cap_pci_cfg_read(&mut locked_virtio_pci_device, bar_offset, len);
1449+
assert_eq!(device_status, 0x42);
1450+
1451+
// reads with out-of-bounds lengths should return 0s
1452+
assert_eq!(
1453+
cap_pci_cfg_read(&mut locked_virtio_pci_device, bar_offset, 8),
1454+
0
1455+
);
1456+
// writes out-of-bounds lengths should have no effect
1457+
cap_pci_cfg_write(&mut locked_virtio_pci_device, bar_offset, 8, 0x84);
1458+
assert_eq!(
1459+
cap_pci_cfg_read(&mut locked_virtio_pci_device, bar_offset, 1),
1460+
0x42
1461+
);
1462+
}
13761463
}

0 commit comments

Comments
 (0)