-
Notifications
You must be signed in to change notification settings - Fork 163
DO NOT MERGE/WIP: Retry all VFIO operations #2665
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -31,6 +31,7 @@ use uevent::UeventListener; | |||||||||||||||||||||||||||||||||||
| use vfio_bindings::bindings::vfio::VFIO_PCI_CONFIG_REGION_INDEX; | ||||||||||||||||||||||||||||||||||||
| use vfio_sys::IommuType; | ||||||||||||||||||||||||||||||||||||
| use vfio_sys::IrqInfo; | ||||||||||||||||||||||||||||||||||||
| use vfio_sys::VfioErrata; | ||||||||||||||||||||||||||||||||||||
| use vmcore::vm_task::VmTaskDriver; | ||||||||||||||||||||||||||||||||||||
| use vmcore::vm_task::VmTaskDriverSource; | ||||||||||||||||||||||||||||||||||||
| use zerocopy::FromBytes; | ||||||||||||||||||||||||||||||||||||
|
|
@@ -123,22 +124,26 @@ impl VfioDevice { | |||||||||||||||||||||||||||||||||||
| tracing::info!(pci_id, keepalive, "device arrived"); | ||||||||||||||||||||||||||||||||||||
| vfio_sys::print_relevant_params(); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| let workaround = VfioDelayWorkaroundConfig::new(driver_source.clone(), None); | ||||||||||||||||||||||||||||||||||||
| let container = vfio_sys::Container::new()?; | ||||||||||||||||||||||||||||||||||||
| let group_id = vfio_sys::Group::find_group_for_device(&path)?; | ||||||||||||||||||||||||||||||||||||
| let group = vfio_sys::Group::open_noiommu(group_id)?; | ||||||||||||||||||||||||||||||||||||
| let group = workaround | ||||||||||||||||||||||||||||||||||||
| .retry_unconditional("open_noiommu", async |_| { | ||||||||||||||||||||||||||||||||||||
| vfio_sys::Group::open_noiommu(group_id) | ||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||
| .await?; | ||||||||||||||||||||||||||||||||||||
| group.set_container(&container)?; | ||||||||||||||||||||||||||||||||||||
| if !group.status()?.viable() { | ||||||||||||||||||||||||||||||||||||
| anyhow::bail!("group is not viable"); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| let driver = driver_source.simple(); | ||||||||||||||||||||||||||||||||||||
| container.set_iommu(IommuType::NoIommu)?; | ||||||||||||||||||||||||||||||||||||
| if keepalive { | ||||||||||||||||||||||||||||||||||||
| // Prevent physical hardware interaction when restoring. | ||||||||||||||||||||||||||||||||||||
| group.set_keep_alive(pci_id, &driver).await?; | ||||||||||||||||||||||||||||||||||||
| group.set_keep_alive(pci_id).await?; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| tracing::debug!(pci_id, "about to open device"); | ||||||||||||||||||||||||||||||||||||
| let device = group.open_device(pci_id, &driver).await?; | ||||||||||||||||||||||||||||||||||||
| let device = group.open_device(pci_id).await?; | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+143
to
+146
|
||||||||||||||||||||||||||||||||||||
| let msix_info = device.irq_info(vfio_bindings::bindings::vfio::VFIO_PCI_MSIX_IRQ_INDEX)?; | ||||||||||||||||||||||||||||||||||||
| if msix_info.flags.noresize() { | ||||||||||||||||||||||||||||||||||||
| anyhow::bail!("unsupported: kernel does not support dynamic msix allocation"); | ||||||||||||||||||||||||||||||||||||
|
|
@@ -230,6 +235,60 @@ impl VfioDevice { | |||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| struct VfioDelayWorkaroundConfig { | ||||||||||||||||||||||||||||||||||||
| driver: VmTaskDriverSource, | ||||||||||||||||||||||||||||||||||||
| enodev_sleep_duration: Duration, | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| impl VfioDelayWorkaroundConfig { | ||||||||||||||||||||||||||||||||||||
| const DEFAULT_ENODEV_SLEEP_DURATION: Duration = Duration::from_millis(250); | ||||||||||||||||||||||||||||||||||||
| pub fn new(driver: VmTaskDriverSource, enodev_sleep_duration: Option<Duration>) -> Self { | ||||||||||||||||||||||||||||||||||||
| Self { | ||||||||||||||||||||||||||||||||||||
| driver, | ||||||||||||||||||||||||||||||||||||
| enodev_sleep_duration: enodev_sleep_duration | ||||||||||||||||||||||||||||||||||||
| .unwrap_or(Self::DEFAULT_ENODEV_SLEEP_DURATION), | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| pub async fn sleep(&self) { | ||||||||||||||||||||||||||||||||||||
| pal_async::timer::PolledTimer::new(&self.driver.simple()) | ||||||||||||||||||||||||||||||||||||
| .sleep(self.enodev_sleep_duration) | ||||||||||||||||||||||||||||||||||||
| .await; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // There is a small race window in the 6.1 kernel between when the | ||||||||||||||||||||||||||||||||||||
| // vfio device is visible to userspace, and when it is added to its | ||||||||||||||||||||||||||||||||||||
| // internal list. Try one more time on ENODEV failure after a brief | ||||||||||||||||||||||||||||||||||||
| // sleep. | ||||||||||||||||||||||||||||||||||||
| pub async fn retry_on_enodev<F, T>(&self, op_name: &str, mut f: F) -> anyhow::Result<T> | ||||||||||||||||||||||||||||||||||||
| where | ||||||||||||||||||||||||||||||||||||
| F: AsyncFnMut(bool) -> anyhow::Result<T>, | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| match f(false).await { | ||||||||||||||||||||||||||||||||||||
| Err(e) if VfioErrata::from_error(&e) == VfioErrata::DelayWorkaround => { | ||||||||||||||||||||||||||||||||||||
| tracing::warn!("vfio operation {op_name} got ENODEV, retrying after delay"); | ||||||||||||||||||||||||||||||||||||
| self.sleep().await; | ||||||||||||||||||||||||||||||||||||
| f(true).await | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| r => r, | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
Comment on lines
+259
to
+276
|
||||||||||||||||||||||||||||||||||||
| // There is a small race window in the 6.1 kernel between when the | |
| // vfio device is visible to userspace, and when it is added to its | |
| // internal list. Try one more time on ENODEV failure after a brief | |
| // sleep. | |
| pub async fn retry_on_enodev<F, T>(&self, op_name: &str, mut f: F) -> anyhow::Result<T> | |
| where | |
| F: AsyncFnMut(bool) -> anyhow::Result<T>, | |
| { | |
| match f(false).await { | |
| Err(e) if VfioErrata::from_error(&e) == VfioErrata::DelayWorkaround => { | |
| tracing::warn!("vfio operation {op_name} got ENODEV, retrying after delay"); | |
| self.sleep().await; | |
| f(true).await | |
| } | |
| r => r, | |
| } | |
| } |
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The AsyncFnMut closure accepts a boolean parameter 'is_retry' but it's not used by the calling code. In the usage at line 131-133, the closure ignores this parameter with '_'. Consider either removing this unused parameter from the trait bound, or documenting its intended purpose if it's meant for future use.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -9,8 +9,6 @@ | |||||||||||||||||||
| use anyhow::Context; | ||||||||||||||||||||
| use bitfield_struct::bitfield; | ||||||||||||||||||||
| use libc::c_void; | ||||||||||||||||||||
| use pal_async::driver::Driver; | ||||||||||||||||||||
| use pal_async::timer::PolledTimer; | ||||||||||||||||||||
| use std::ffi::CString; | ||||||||||||||||||||
| use std::fs; | ||||||||||||||||||||
| use std::fs::File; | ||||||||||||||||||||
|
|
@@ -85,6 +83,21 @@ mod ioctl { | |||||||||||||||||||
| ); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| #[derive(Debug, PartialEq, Eq)] | ||||||||||||||||||||
| pub enum VfioErrata { | ||||||||||||||||||||
| None, | ||||||||||||||||||||
| DelayWorkaround, | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| impl VfioErrata { | ||||||||||||||||||||
| pub fn from_error(observed: &anyhow::Error) -> Self { | ||||||||||||||||||||
| if observed.downcast_ref::<nix::errno::Errno>() == Some(&nix::errno::Errno::ENODEV) { | ||||||||||||||||||||
| return VfioErrata::DelayWorkaround; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return VfioErrata::None; | ||||||||||||||||||||
|
||||||||||||||||||||
| return VfioErrata::None; | |
| VfioErrata::None |
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary return statement. In Rust, the last expression in a function is automatically returned, so this explicit return is redundant and goes against idiomatic Rust style.
| return VfioErrata::DelayWorkaround; | |
| } | |
| return VfioErrata::None; | |
| VfioErrata::DelayWorkaround | |
| } else { | |
| VfioErrata::None | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The retry_unconditional method retries on any error, which is overly broad for the stated purpose of working around a specific kernel race condition (ENODEV). This could mask real errors and retry operations that should fail fast. Consider using retry_on_enodev instead, or document why unconditional retry is necessary for this specific operation.