mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2024-08-21 23:41:15 +00:00
vm-virtio: Handle special virtio-pci capability CAP_PCI_CFG
The virtio capability VIRTIO_PCI_CAP_PCI_CFG is exposed through the device's PCI config space the same way other virtio-pci capabilities are exposed. The main and important difference is that this specific capability is designed as a way for the guest to access virtio capabilities without mapping the PCI BAR. This is very rarely used, but it can be useful when it is too early for the guest to be able to map the BARs. One thing to note, this special feature MUST be implemented, based on the virtio specification. Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
This commit is contained in:
parent
db9f9b7820
commit
db42caef42
@ -29,6 +29,8 @@ use pci::{
|
|||||||
PciHeaderType, PciMassStorageSubclass, PciNetworkControllerSubclass, PciSubclass,
|
PciHeaderType, PciMassStorageSubclass, PciNetworkControllerSubclass, PciSubclass,
|
||||||
};
|
};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
use std::cmp;
|
||||||
|
use std::io::Write;
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::atomic::{AtomicU16, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicU16, AtomicUsize, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
@ -48,6 +50,11 @@ enum PciCapabilityType {
|
|||||||
SharedMemoryConfig = 8,
|
SharedMemoryConfig = 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This offset represents the 2 bytes omitted from the VirtioPciCap structure
|
||||||
|
// as they are already handled through add_capability(). These 2 bytes are the
|
||||||
|
// fields cap_vndr (1 byte) and cap_next (1 byte) defined in the virtio spec.
|
||||||
|
const VIRTIO_PCI_CAP_OFFSET: usize = 2;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Clone, Copy, Default)]
|
#[derive(Clone, Copy, Default)]
|
||||||
@ -172,6 +179,41 @@ impl VirtioPciCap64 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[repr(packed)]
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
struct VirtioPciCfgCap {
|
||||||
|
cap: VirtioPciCap,
|
||||||
|
pci_cfg_data: [u8; 4],
|
||||||
|
}
|
||||||
|
// It is safe to implement ByteValued. All members are simple numbers and any value is valid.
|
||||||
|
unsafe impl ByteValued for VirtioPciCfgCap {}
|
||||||
|
|
||||||
|
impl PciCapability for VirtioPciCfgCap {
|
||||||
|
fn bytes(&self) -> &[u8] {
|
||||||
|
self.as_slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> PciCapabilityID {
|
||||||
|
PciCapabilityID::VendorSpecific
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VirtioPciCfgCap {
|
||||||
|
fn new() -> Self {
|
||||||
|
VirtioPciCfgCap {
|
||||||
|
cap: VirtioPciCap::new(PciCapabilityType::PciConfig, 0, 0, 0),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
struct VirtioPciCfgCapInfo {
|
||||||
|
offset: usize,
|
||||||
|
cap: VirtioPciCfgCap,
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum PciVirtioSubclass {
|
pub enum PciVirtioSubclass {
|
||||||
@ -246,6 +288,14 @@ pub struct VirtioPciDevice {
|
|||||||
|
|
||||||
// Whether to use 64-bit bar location or 32-bit
|
// Whether to use 64-bit bar location or 32-bit
|
||||||
use_64bit_bar: bool,
|
use_64bit_bar: bool,
|
||||||
|
|
||||||
|
// Add a dedicated structure to hold information about the very specific
|
||||||
|
// virtio-pci capability VIRTIO_PCI_CAP_PCI_CFG. This is needed to support
|
||||||
|
// the legacy/backward compatible mechanism of letting the guest access the
|
||||||
|
// other virtio capabilities without mapping the PCI BARs. This can be
|
||||||
|
// needed when the guest tries to early access the virtio configuration of
|
||||||
|
// a device.
|
||||||
|
cap_pci_cfg_info: VirtioPciCfgCapInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtioPciDevice {
|
impl VirtioPciDevice {
|
||||||
@ -345,6 +395,7 @@ impl VirtioPciDevice {
|
|||||||
settings_bar: 0,
|
settings_bar: 0,
|
||||||
use_64bit_bar,
|
use_64bit_bar,
|
||||||
interrupt_source_group,
|
interrupt_source_group,
|
||||||
|
cap_pci_cfg_info: VirtioPciCfgCapInfo::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(msix_config) = &virtio_pci_device.msix_config {
|
if let Some(msix_config) = &virtio_pci_device.msix_config {
|
||||||
@ -436,11 +487,13 @@ impl VirtioPciDevice {
|
|||||||
.add_capability(¬ify_cap)
|
.add_capability(¬ify_cap)
|
||||||
.map_err(PciDeviceError::CapabilitiesSetup)?;
|
.map_err(PciDeviceError::CapabilitiesSetup)?;
|
||||||
|
|
||||||
//TODO(dgreid) - How will the configuration_cap work?
|
let configuration_cap = VirtioPciCfgCap::new();
|
||||||
let configuration_cap = VirtioPciCap::new(PciCapabilityType::PciConfig, 0, 0, 0);
|
self.cap_pci_cfg_info.offset = self
|
||||||
self.configuration
|
.configuration
|
||||||
.add_capability(&configuration_cap)
|
.add_capability(&configuration_cap)
|
||||||
.map_err(PciDeviceError::CapabilitiesSetup)?;
|
.map_err(PciDeviceError::CapabilitiesSetup)?
|
||||||
|
+ VIRTIO_PCI_CAP_OFFSET;
|
||||||
|
self.cap_pci_cfg_info.cap = configuration_cap;
|
||||||
|
|
||||||
if self.msix_config.is_some() {
|
if self.msix_config.is_some() {
|
||||||
let msix_cap = MsixCap::new(
|
let msix_cap = MsixCap::new(
|
||||||
@ -458,6 +511,49 @@ impl VirtioPciDevice {
|
|||||||
self.settings_bar = settings_bar;
|
self.settings_bar = settings_bar;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_cap_pci_cfg(&mut self, offset: usize, mut data: &mut [u8]) {
|
||||||
|
let cap_slice = self.cap_pci_cfg_info.cap.as_slice();
|
||||||
|
let data_len = data.len();
|
||||||
|
let cap_len = cap_slice.len();
|
||||||
|
if offset + data_len > cap_len {
|
||||||
|
error!("Failed to read cap_pci_cfg from config space");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset < std::mem::size_of::<VirtioPciCap>() {
|
||||||
|
if let Some(end) = offset.checked_add(data_len) {
|
||||||
|
// This write can't fail, offset and end are checked against config_len.
|
||||||
|
data.write_all(&cap_slice[offset..cmp::min(end, cap_len)])
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Safe since we know self.cap_pci_cfg_info.cap.cap.offset is 32bits long.
|
||||||
|
let bar_offset: u32 =
|
||||||
|
unsafe { std::mem::transmute(self.cap_pci_cfg_info.cap.cap.offset) };
|
||||||
|
self.read_bar(0, bar_offset as u64, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_cap_pci_cfg(&mut self, offset: usize, data: &[u8]) {
|
||||||
|
let cap_slice = self.cap_pci_cfg_info.cap.as_mut_slice();
|
||||||
|
let data_len = data.len();
|
||||||
|
let cap_len = cap_slice.len();
|
||||||
|
if offset + data_len > cap_len {
|
||||||
|
error!("Failed to write cap_pci_cfg to config space");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset < std::mem::size_of::<VirtioPciCap>() {
|
||||||
|
let (_, right) = cap_slice.split_at_mut(offset);
|
||||||
|
right[..data_len].copy_from_slice(&data[..]);
|
||||||
|
} else {
|
||||||
|
// Safe since we know self.cap_pci_cfg_info.cap.cap.offset is 32bits long.
|
||||||
|
let bar_offset: u32 =
|
||||||
|
unsafe { std::mem::transmute(self.cap_pci_cfg_info.cap.cap.offset) };
|
||||||
|
self.write_bar(0, bar_offset as u64, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtioTransport for VirtioPciDevice {
|
impl VirtioTransport for VirtioPciDevice {
|
||||||
@ -552,13 +648,38 @@ impl VirtioInterrupt for VirtioInterruptMsix {
|
|||||||
|
|
||||||
impl PciDevice for VirtioPciDevice {
|
impl PciDevice for VirtioPciDevice {
|
||||||
fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) {
|
fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) {
|
||||||
|
// Handle the special case where the capability VIRTIO_PCI_CAP_PCI_CFG
|
||||||
|
// is accessed. This capability has a special meaning as it allows the
|
||||||
|
// guest to access other capabilities without mapping the PCI BAR.
|
||||||
|
let base = reg_idx * 4;
|
||||||
|
if base + offset as usize >= self.cap_pci_cfg_info.offset
|
||||||
|
&& base + offset as usize + data.len()
|
||||||
|
<= self.cap_pci_cfg_info.offset + self.cap_pci_cfg_info.cap.bytes().len()
|
||||||
|
{
|
||||||
|
let offset = base + offset as usize - self.cap_pci_cfg_info.offset;
|
||||||
|
self.write_cap_pci_cfg(offset, data);
|
||||||
|
} else {
|
||||||
self.configuration
|
self.configuration
|
||||||
.write_config_register(reg_idx, offset, data);
|
.write_config_register(reg_idx, offset, data);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn read_config_register(&mut self, reg_idx: usize) -> u32 {
|
fn read_config_register(&mut self, reg_idx: usize) -> u32 {
|
||||||
|
// Handle the special case where the capability VIRTIO_PCI_CAP_PCI_CFG
|
||||||
|
// is accessed. This capability has a special meaning as it allows the
|
||||||
|
// guest to access other capabilities without mapping the PCI BAR.
|
||||||
|
let base = reg_idx * 4;
|
||||||
|
if base >= self.cap_pci_cfg_info.offset
|
||||||
|
&& base + 4 <= self.cap_pci_cfg_info.offset + self.cap_pci_cfg_info.cap.bytes().len()
|
||||||
|
{
|
||||||
|
let offset = base - self.cap_pci_cfg_info.offset;
|
||||||
|
let mut data = [0u8; 4];
|
||||||
|
self.read_cap_pci_cfg(offset, &mut data);
|
||||||
|
u32::from_le_bytes(data)
|
||||||
|
} else {
|
||||||
self.configuration.read_reg(reg_idx)
|
self.configuration.read_reg(reg_idx)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn detect_bar_reprogramming(
|
fn detect_bar_reprogramming(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
Loading…
Reference in New Issue
Block a user