Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Guide/src/reference/openvmm/management/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ For `--virtio-rng` and `--virtio-console`, use their separate PCIe port flags:
--iommu id=iommu0 --vfio host=0000:01:00.0,port=rp0,iommu=iommu0

# Pin BAR0 to its physical address for P2P DMA:
--vfio host=0000:01:00.0,port=rp0,bar0=pt
--vfio host=0000:01:00.0,port=rp0,bar0=host
```

### SMMU (aarch64 only)
Expand Down
21 changes: 17 additions & 4 deletions Guide/src/user_guide/openvmm/vfio.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,12 @@ The `--vfio` value is a comma-separated list of `key=value` pairs:
- `host=<pci_bdf>` (required) — the PCI BDF of the VFIO device on the host (e.g., `0000:01:00.0`)
- `port=<name>` (required) — the name of the PCIe root port to attach the device to (must match a `--pcie-root-port` name)
- `iommu=<id>` (optional) — reference to an `--iommu` context; see [Using iommufd (cdev path)](#using-iommufd-cdev-path) below
- `bar0=pt` through `bar5=pt` (optional) — pin the specified BAR to its
- `bar0=host` through `bar5=host` (optional) — pin the specified BAR to its
physical host address (GPA = HPA); see [Peer-to-peer DMA](#peer-to-peer-dma)
below
- `bar0=0x<addr>` through `bar5=0x<addr>` (optional) — pin the specified BAR
to an explicit host physical address; use this for BARs synthesized by a
VFIO variant driver whose address is not reported through sysfs

```admonish tip
You can assign multiple devices by adding more root ports and `--vfio` flags:
Expand Down Expand Up @@ -182,7 +185,7 @@ P2P DMA. This means the guest BAR addresses must be identity-mapped to
the host BAR addresses (GPA = HPA), or P2P DMA will target the wrong
location.

To enable this, pin the relevant BARs with `bar<N>=pt` on each `--vfio`
To enable this, pin the relevant BARs with `bar<N>=host` on each `--vfio`
device and set `preserve_bars` on the root complex so the PCI resource
allocator keeps pinned BARs at their physical addresses:

Expand All @@ -192,11 +195,21 @@ sudo openvmm \
rc0,preserve_bars,low_mmio_base=0xc0000000,high_mmio_base=0x100000000 \
--pcie-root-port rc0:rp0 \
--pcie-root-port rc0:rp1 \
--vfio host=0000:01:00.0,port=rp0,bar0=pt \
--vfio host=0000:02:00.0,port=rp1,bar0=pt \
--vfio host=0000:01:00.0,port=rp0,bar0=host \
--vfio host=0000:02:00.0,port=rp1,bar0=host \
...
```

For a BAR synthesized by a VFIO variant driver, the host physical address may
not appear in the device's sysfs resource table. In that case, provide the
address directly with `bar<N>=0x<addr>`. For example, the `nvgrace-gpu` driver
exposes CPU-coherent GPU memory as a synthetic 64-bit BAR4 whose physical base
comes from the `nvidia,gpu-mem-base-pa` ACPI `_DSD` property:

```bash
--vfio host=0008:06:00.0,port=rp0,bar0=host,bar2=host,bar4=0x110000000000
```

The `low_mmio_base=` and `high_mmio_base=` options pin the MMIO apertures
to fixed addresses so the allocator can place both pinned and dynamic BARs
correctly.
Expand Down
81 changes: 50 additions & 31 deletions openvmm/openvmm_entry/src/cli_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3058,7 +3058,7 @@ pub struct PcieRemoteCli {

/// CLI configuration for a VFIO-assigned PCI device.
///
/// Syntax: `host=<bdf>,port=<name>[,iommu=<id>][,bar0=pt..bar5=pt]`
/// Syntax: `host=<bdf>,port=<name>[,iommu=<id>][,barN=host|barN=0x<addr>]`
#[cfg(target_os = "linux")]
#[derive(Clone, Debug)]
pub struct VfioDeviceCli {
Expand All @@ -3069,36 +3069,43 @@ pub struct VfioDeviceCli {
/// Optional iommufd context ID. When set, uses VFIO cdev + iommufd
/// instead of the legacy group/container path.
pub iommu: Option<String>,
/// Per-BAR passthrough flags. When `bar_pt[i]` is true, the virtual
/// BAR is pre-programmed with the physical BAR address (GPA = HPA).
pub bar_pt: [bool; 6],
/// Per-BAR pre-programming configuration.
pub bar_addresses: [vfio_assigned_device_resources::BarAddressConfig; 6],
}

/// Marker for a BAR passthrough value; only `pt` is accepted.
/// Per-BAR address configuration parsed from the CLI.
#[cfg(target_os = "linux")]
struct Pt;
struct BarAddressCli(vfio_assigned_device_resources::BarAddressConfig);

#[cfg(target_os = "linux")]
impl FromStr for Pt {
impl FromStr for BarAddressCli {
type Err = anyhow::Error;

fn from_str(s: &str) -> anyhow::Result<Self> {
anyhow::ensure!(s == "pt", "expected 'pt'");
Ok(Pt)
let config = if s == "host" {
vfio_assigned_device_resources::BarAddressConfig::HostAssigned
} else if let Some(value) = s.strip_prefix("0x") {
let address = u64::from_str_radix(value, 16).context("invalid BAR address")?;
anyhow::ensure!(address != 0, "BAR address must be nonzero");
vfio_assigned_device_resources::BarAddressConfig::Fixed(address)
} else {
anyhow::bail!("expected 'host' or a hexadecimal address starting with '0x'");
};
Ok(Self(config))
}
}

/// Per-BAR passthrough flags (`bar0=pt` .. `bar5=pt`), flattened into
/// Per-BAR address configuration, flattened into
/// [`VfioArgs`].
#[cfg(target_os = "linux")]
#[derive(vmm_cli::KeyValueArgs)]
struct BarFlags {
bar0: Option<Pt>,
bar1: Option<Pt>,
bar2: Option<Pt>,
bar3: Option<Pt>,
bar4: Option<Pt>,
bar5: Option<Pt>,
bar0: Option<BarAddressCli>,
bar1: Option<BarAddressCli>,
bar2: Option<BarAddressCli>,
bar3: Option<BarAddressCli>,
bar4: Option<BarAddressCli>,
bar5: Option<BarAddressCli>,
}

/// Raw `--vfio` options, resolved and validated into a [`VfioDeviceCli`].
Expand All @@ -3125,20 +3132,20 @@ impl FromStr for VfioDeviceCli {
}

let bars = args.bars;
let bar_pt = [
bars.bar0.is_some(),
bars.bar1.is_some(),
bars.bar2.is_some(),
bars.bar3.is_some(),
bars.bar4.is_some(),
bars.bar5.is_some(),
let bar_addresses = [
bars.bar0.map(|bar| bar.0).unwrap_or_default(),
bars.bar1.map(|bar| bar.0).unwrap_or_default(),
bars.bar2.map(|bar| bar.0).unwrap_or_default(),
bars.bar3.map(|bar| bar.0).unwrap_or_default(),
bars.bar4.map(|bar| bar.0).unwrap_or_default(),
bars.bar5.map(|bar| bar.0).unwrap_or_default(),
];

Ok(VfioDeviceCli {
port_name: args.port,
pci_id: args.host,
iommu: args.iommu,
bar_pt,
bar_addresses,
})
}
}
Expand Down Expand Up @@ -4805,6 +4812,8 @@ mod tests {
#[cfg(target_os = "linux")]
#[test]
fn test_vfio_device_cli_parse() {
use vfio_assigned_device_resources::BarAddressConfig;

// Required keys only.
let v = VfioDeviceCli::from_str("host=0000:01:00.0,port=rp0").unwrap();
assert_eq!(v.pci_id, "0000:01:00.0");
Expand All @@ -4817,13 +4826,14 @@ mod tests {
assert_eq!(v.port_name, "rp1");
assert_eq!(v.iommu.as_deref(), Some("iommu0"));

// BAR passthrough flags set the corresponding indices.
let v = VfioDeviceCli::from_str("host=0000:01:00.0,port=rp0,bar0=pt,bar2=pt").unwrap();
assert_eq!(v.bar_pt, [true, false, true, false, false, false]);

// A BAR flag only accepts `pt`, and requires a value.
assert!(VfioDeviceCli::from_str("host=0000:01:00.0,port=rp0,bar0=x").is_err());
assert!(VfioDeviceCli::from_str("host=0000:01:00.0,port=rp0,bar0").is_err());
let v = VfioDeviceCli::from_str(
"host=0000:03:00.0,port=rp2,bar0=host,bar2=0x80000000,bar4=0x110000000000",
)
.unwrap();
assert_eq!(v.bar_addresses[0], BarAddressConfig::HostAssigned);
assert_eq!(v.bar_addresses[1], BarAddressConfig::GuestAssigned);
assert_eq!(v.bar_addresses[2], BarAddressConfig::Fixed(0x80000000));
assert_eq!(v.bar_addresses[4], BarAddressConfig::Fixed(0x110000000000));
}

#[cfg(target_os = "linux")]
Expand Down Expand Up @@ -4853,6 +4863,15 @@ mod tests {
// Path-traversal characters in the host BDF are rejected.
assert!(VfioDeviceCli::from_str("host=../../etc/passwd,port=rp0").is_err());
assert!(VfioDeviceCli::from_str("host=foo/bar,port=rp0").is_err());

// Invalid and duplicate BAR configurations are rejected.
assert!(VfioDeviceCli::from_str("host=0000:01:00.0,port=rp0,bar0=0").is_err());
assert!(VfioDeviceCli::from_str("host=0000:01:00.0,port=rp0,bar0=0x0").is_err());
assert!(VfioDeviceCli::from_str("host=0000:01:00.0,port=rp0,bar0=0xnope").is_err());
assert!(VfioDeviceCli::from_str("host=0000:01:00.0,port=rp0,bar0=pt").is_err());
assert!(
VfioDeviceCli::from_str("host=0000:01:00.0,port=rp0,bar0=0x1000,bar0=host").is_err()
);
}

#[cfg(target_os = "linux")]
Expand Down
4 changes: 2 additions & 2 deletions openvmm/openvmm_entry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1058,7 +1058,7 @@ async fn vm_config_from_command_line(
cdev,
iommufd,
iommu_id: iommu_id.clone(),
bar_pt: cli_cfg.bar_pt,
bar_addresses: cli_cfg.bar_addresses,
}
.into_resource(),
})
Expand All @@ -1085,7 +1085,7 @@ async fn vm_config_from_command_line(
resource: vfio_assigned_device_resources::VfioDeviceHandle {
pci_id: cli_cfg.pci_id.clone(),
group,
bar_pt: cli_cfg.bar_pt,
bar_addresses: cli_cfg.bar_addresses,
}
.into_resource(),
})
Expand Down
79 changes: 77 additions & 2 deletions openvmm/openvmm_entry/src/ttrpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1643,7 +1643,11 @@ async fn build_pcie_device(
/// opened. The device must already be bound to `vfio-pci` on the host.
#[cfg(target_os = "linux")]
fn build_vfio_device(vfio: vmservice::VfioDevice) -> anyhow::Result<Resource<PciDeviceHandleKind>> {
let vmservice::VfioDevice { host_pci_address } = vfio;
let vmservice::VfioDevice {
host_pci_address,
bar_addresses,
} = vfio;
let bar_addresses = parse_vfio_bar_addresses(bar_addresses)?;
// The address is joined into a sysfs path below; reject path separators so
// it cannot escape `/sys/bus/pci/devices` (an absolute path or `..` would
// otherwise redirect the join).
Expand All @@ -1669,11 +1673,40 @@ fn build_vfio_device(vfio: vmservice::VfioDevice) -> anyhow::Result<Resource<Pci
Ok(vfio_assigned_device_resources::VfioDeviceHandle {
pci_id: host_pci_address,
group,
bar_pt: [false; 6],
bar_addresses,
}
.into_resource())
}

fn parse_vfio_bar_addresses(
entries: Vec<vmservice::VfioBarAddress>,
) -> anyhow::Result<[vfio_assigned_device_resources::BarAddressConfig; 6]> {
use vfio_assigned_device_resources::BarAddressConfig;
use vmservice::vfio_bar_address::Source;

let mut bar_addresses = [BarAddressConfig::GuestAssigned; 6];
for entry in entries {
let index =
usize::try_from(entry.bar_index).context("VFIO BAR index does not fit usize")?;
let config = bar_addresses
.get_mut(index)
.with_context(|| format!("VFIO BAR index {} is out of range", entry.bar_index))?;
anyhow::ensure!(
*config == BarAddressConfig::GuestAssigned,
"duplicate VFIO BAR index {}",
entry.bar_index
);
*config = match entry.source.context("missing VFIO BAR address source")? {
Source::Host(()) => BarAddressConfig::HostAssigned,
Source::Fixed(address) => {
anyhow::ensure!(address != 0, "VFIO BAR fixed address must be nonzero");
BarAddressConfig::Fixed(address)
}
};
}
Ok(bar_addresses)
}

#[cfg(not(target_os = "linux"))]
fn build_vfio_device(
_vfio: vmservice::VfioDevice,
Expand Down Expand Up @@ -1915,3 +1948,45 @@ fn build_vhost_user_device(
) -> anyhow::Result<Resource<VirtioDeviceHandle>> {
anyhow::bail!("vhost-user is only supported on unix hosts")
}

#[cfg(test)]
mod tests {
use super::*;
use vfio_assigned_device_resources::BarAddressConfig;
use vmservice::vfio_bar_address::Source;

fn vfio_bar_address(bar_index: u32, source: Option<Source>) -> vmservice::VfioBarAddress {
vmservice::VfioBarAddress { bar_index, source }
}

#[test]
fn parse_vfio_bar_address_config() {
let bars = parse_vfio_bar_addresses(vec![
vfio_bar_address(0, Some(Source::Host(()))),
vfio_bar_address(4, Some(Source::Fixed(0x11_0000_0000))),
])
.unwrap();

assert_eq!(bars[0], BarAddressConfig::HostAssigned);
assert_eq!(bars[1], BarAddressConfig::GuestAssigned);
assert_eq!(bars[4], BarAddressConfig::Fixed(0x11_0000_0000));
}

#[test]
fn reject_invalid_vfio_bar_address_config() {
assert!(
parse_vfio_bar_addresses(vec![vfio_bar_address(6, Some(Source::Host(())))]).is_err()
);
assert!(parse_vfio_bar_addresses(vec![vfio_bar_address(0, None)]).is_err());
assert!(
parse_vfio_bar_addresses(vec![vfio_bar_address(0, Some(Source::Fixed(0)))]).is_err()
);
assert!(
parse_vfio_bar_addresses(vec![
vfio_bar_address(0, Some(Source::Host(()))),
vfio_bar_address(0, Some(Source::Fixed(0x1000))),
])
.is_err()
);
}
}
15 changes: 15 additions & 0 deletions openvmm/openvmm_ttrpc_vmservice/src/vmservice.proto
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,21 @@ message NvmeNamespace {
message VfioDevice {
// Host PCI address (BDF), e.g. "0000:01:00.0".
string host_pci_address = 1;
// BARs whose initial addresses are pinned. Omitted BARs are assigned by
// the guest normally.
repeated VfioBarAddress bar_addresses = 2;
}

// Initial address configuration for one VFIO device BAR.
message VfioBarAddress {
// BAR index, from 0 through 5.
uint32 bar_index = 1;
oneof source {
// Use the physical BAR address reported by the host.
google.protobuf.Empty host = 2;
// Use an explicit host physical address.
uint64 fixed = 3;
}
}

//
Expand Down
Loading
Loading