* [PATCH 1/5] vfio/pci: Hold memory_lock for all calls to vfio_pci_set_power_state()
2026-10-29 20:33 [PATCH 0/5] vfio/pci: Fix power state race with SR-IOV enablement David Matlack
@ 2026-10-29 20:33 ` David Matlack
2026-10-29 20:33 ` [PATCH 2/5] vfio/pci: Annotate lockless accesses to vdev->sriov_active David Matlack
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: David Matlack @ 2026-10-29 20:33 UTC (permalink / raw)
To: kvm, linux-kernel, linux-kselftest, stable
Cc: Abhishek Sahu, Alex Williamson, David Matlack,
Raghavendra Rao Ananta, Shuah Khan
Hold vdev->memory_lock for write when calling vfio_pci_set_power_state()
from vfio_pci_core_disable() and vfio_pci_core_register_device(), and
assert that the lock is held for all power state transitions, not just
transitions out of D0.
vfio_pci_core_disable() calls vfio_pci_set_power_state(vdev, PCI_D0)
without holding memory_lock, which allows it to race with
vfio_pci_core_sriov_configure() when userspace writes to sriov_numvfs
while the device is being closed. If the user left the device in D3hot
via a config space write (which leaves the device RPM_ACTIVE, so
pm_runtime_resume() does not bring it back to D0), both threads can
transition the device from D3hot to D0 concurrently.
For devices that need_pm_restore (NoSoftRst-), this can result in:
- vfio_pci_core_disable() restoring the PF's BARs after
vfio_pci_core_sriov_configure() has enabled VFs, since the D3hot->D0
transition sleeps before restoring device state. This is easy to hit
and triggers the following warning:
vfio-pci 0000:01:00.0: can't update enabled VF BAR0 [mem 0xe0200000000-0xe020001ffff 64bit pref]
WARNING: drivers/pci/iov.c:1062 at pci_iov_update_resource+0x170/0x3b0
Call Trace:
pci_update_resource+0x1a3/0x5b0
__pci_set_power_state+0x597/0xb90
vfio_pci_core_disable+0x13b/0x1180
vfio_pci_core_close_device+0x144/0x260
vfio_df_close+0xf2/0x210
vfio_df_unbind_iommufd+0x89/0x140
vfio_device_fops_release+0x85/0xf0
__fput+0x2d6/0x720
fput_close_sync+0xe8/0x180
__x64_sys_close+0x7f/0xc0
do_syscall_64+0xbc/0x320
entry_SYSCALL_64_after_hwframe+0x76/0x7e
- Both threads observing pdev->current_state >= PCI_D3hot and calling
pci_load_and_free_saved_state(pdev, &vdev->pm_save) concurrently,
resulting in a double-free of vdev->pm_save.
vfio_pci_core_register_device() cannot race with other callers since
the device is not yet registered and device_lock is held across probe,
but take the lock there as well so that the lockdep assertion can be
made unconditional and catch any future unlocked callers.
Taking memory_lock in these paths does not introduce any new lock
dependencies. vfio_pci_core_disable() already acquires memory_lock
under dev_set->lock via vfio_pci_runtime_pm_exit(), and
vfio_pci_core_sriov_configure() already acquires memory_lock under
device_lock.
Fixes: f4162eb1e2fc ("vfio/pci: Change the PF power state to D0 before enabling VFs")
Cc: stable@vger.kernel.org
Signed-off-by: David Matlack <dmatlack@google.com>
---
drivers/vfio/pci/vfio_pci_core.c | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c
index fcbb2da860e2..95aee6b7cbb3 100644
--- a/drivers/vfio/pci/vfio_pci_core.c
+++ b/drivers/vfio/pci/vfio_pci_core.c
@@ -325,12 +325,15 @@ int vfio_pci_set_power_state(struct vfio_pci_core_device *vdev, pci_power_t stat
bool needs_restore = false, needs_save = false;
int ret;
+ /*
+ * Serialize power state changes and accesses to vdev->pm_save, and
+ * protect the check of vdev->sriov_active below.
+ */
+ lockdep_assert_held_write(&vdev->memory_lock);
+
/* Prevent changing power state for PFs with VFs enabled */
- if (state > PCI_D0) {
- lockdep_assert_held_write(&vdev->memory_lock);
- if (vdev->sriov_active)
- return -EBUSY;
- }
+ if (state > PCI_D0 && vdev->sriov_active)
+ return -EBUSY;
if (vdev->needs_pm_restore) {
if (pdev->current_state < PCI_D3hot && state >= PCI_D3hot) {
@@ -699,7 +702,9 @@ void vfio_pci_core_disable(struct vfio_pci_core_device *vdev)
* reset without restoring the original state (saved locally in
* 'vdev->pm_save').
*/
+ down_write(&vdev->memory_lock);
vfio_pci_set_power_state(vdev, PCI_D0);
+ up_write(&vdev->memory_lock);
/* Stop the device from further DMA */
pci_clear_master(pdev);
@@ -2273,7 +2278,9 @@ int vfio_pci_core_register_device(struct vfio_pci_core_device *vdev)
* be able to get to D3. Therefore first do a D0 transition
* before enabling runtime PM.
*/
+ down_write(&vdev->memory_lock);
vfio_pci_set_power_state(vdev, PCI_D0);
+ up_write(&vdev->memory_lock);
pm_runtime_allow(dev);
if (!vdev->disable_idle_d3)
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 2/5] vfio/pci: Annotate lockless accesses to vdev->sriov_active
2026-10-29 20:33 [PATCH 0/5] vfio/pci: Fix power state race with SR-IOV enablement David Matlack
2026-10-29 20:33 ` [PATCH 1/5] vfio/pci: Hold memory_lock for all calls to vfio_pci_set_power_state() David Matlack
@ 2026-10-29 20:33 ` David Matlack
2026-10-29 20:33 ` [PATCH 3/5] vfio: selftests: Add vfio_pci_find_capability() David Matlack
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: David Matlack @ 2026-10-29 20:33 UTC (permalink / raw)
To: kvm, linux-kernel, linux-kselftest, stable
Cc: Abhishek Sahu, Alex Williamson, David Matlack,
Raghavendra Rao Ananta, Shuah Khan
Use READ_ONCE() and WRITE_ONCE() to annotate the read of
vdev->sriov_active in vfio_pci_set_power_state() and the lockless write
that clears it in vfio_pci_core_sriov_configure().
vfio_pci_core_sriov_configure() intentionally clears sriov_active
without holding memory_lock, so the write can race with the read in
vfio_pci_set_power_state() (which runs under memory_lock). The race
itself is benign, at worst it results in a spurious -EBUSY while the
device stays in D0, but the accesses should be marked to prevent the
compiler from tearing or otherwise optimizing them and to document the
intentional data race (e.g. for KCSAN).
The write that sets sriov_active is done under memory_lock and is
serialized against the lockless write by device_lock, so it does not
need to be annotated.
Fixes: 40ef3edf151e ("vfio/pci: Use a private flag to prevent power state change with VFs")
Signed-off-by: David Matlack <dmatlack@google.com>
---
drivers/vfio/pci/vfio_pci_core.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c
index 95aee6b7cbb3..9a62797e50bc 100644
--- a/drivers/vfio/pci/vfio_pci_core.c
+++ b/drivers/vfio/pci/vfio_pci_core.c
@@ -332,7 +332,7 @@ int vfio_pci_set_power_state(struct vfio_pci_core_device *vdev, pci_power_t stat
lockdep_assert_held_write(&vdev->memory_lock);
/* Prevent changing power state for PFs with VFs enabled */
- if (state > PCI_D0 && vdev->sriov_active)
+ if (state > PCI_D0 && READ_ONCE(vdev->sriov_active))
return -EBUSY;
if (vdev->needs_pm_restore) {
@@ -2396,7 +2396,7 @@ int vfio_pci_core_sriov_configure(struct vfio_pci_core_device *vdev,
* state transition would at most result in an -EBUSY, leaving the
* device in PCI_D0.
*/
- vdev->sriov_active = false;
+ WRITE_ONCE(vdev->sriov_active, false);
mutex_lock(&vfio_pci_sriov_pfs_mutex);
list_del_init(&vdev->sriov_pfs_item);
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 3/5] vfio: selftests: Add vfio_pci_find_capability()
2026-10-29 20:33 [PATCH 0/5] vfio/pci: Fix power state race with SR-IOV enablement David Matlack
2026-10-29 20:33 ` [PATCH 1/5] vfio/pci: Hold memory_lock for all calls to vfio_pci_set_power_state() David Matlack
2026-10-29 20:33 ` [PATCH 2/5] vfio/pci: Annotate lockless accesses to vdev->sriov_active David Matlack
@ 2026-10-29 20:33 ` David Matlack
2026-10-29 20:33 ` [PATCH 4/5] vfio: selftests: Add sysfs_kernel_warn_count_get() David Matlack
2026-10-29 20:33 ` [PATCH 5/5] vfio: selftests: Add test for device power state transitions David Matlack
4 siblings, 0 replies; 6+ messages in thread
From: David Matlack @ 2026-10-29 20:33 UTC (permalink / raw)
To: kvm, linux-kernel, linux-kselftest, stable
Cc: Abhishek Sahu, Alex Williamson, David Matlack,
Raghavendra Rao Ananta, Shuah Khan
Add a helper to find the config space offset of a capability in a
device's standard capability list, mirroring the kernel's
pci_find_capability(). Return 0 if the device does not have the
capability.
This will be used in a subsequent commit to locate the PM capability.
Signed-off-by: David Matlack <dmatlack@google.com>
---
.../lib/include/libvfio/vfio_pci_device.h | 7 ++++++
.../selftests/vfio/lib/vfio_pci_device.c | 25 +++++++++++++++++++
2 files changed, 32 insertions(+)
diff --git a/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h b/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h
index 007adc322c1e..1839e95cc792 100644
--- a/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h
+++ b/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h
@@ -79,6 +79,13 @@ void vfio_pci_config_access(struct vfio_pci_device *device, bool write,
#define vfio_pci_config_writew(_d, _o, _v) vfio_pci_config_write(_d, _o, _v, u16)
#define vfio_pci_config_writel(_d, _o, _v) vfio_pci_config_write(_d, _o, _v, u32)
+/*
+ * Return the config space offset of the first capability with the given ID
+ * (PCI_CAP_ID_*) in the device's standard capability list, or 0 if the device
+ * does not have the capability.
+ */
+u8 vfio_pci_find_capability(struct vfio_pci_device *device, u8 cap_id);
+
static inline void vfio_pci_cmd_set(struct vfio_pci_device *device, u16 bits)
{
u16 cmd = vfio_pci_config_readw(device, PCI_COMMAND);
diff --git a/tools/testing/selftests/vfio/lib/vfio_pci_device.c b/tools/testing/selftests/vfio/lib/vfio_pci_device.c
index 4063a0e2b3df..2afbc465d30c 100644
--- a/tools/testing/selftests/vfio/lib/vfio_pci_device.c
+++ b/tools/testing/selftests/vfio/lib/vfio_pci_device.c
@@ -260,6 +260,31 @@ void vfio_pci_config_access(struct vfio_pci_device *device, bool write,
write ? "write to" : "read from", config);
}
+/* Bound the capability list walk in case the list contains a loop. */
+#define PCI_FIND_CAP_TTL 48
+
+u8 vfio_pci_find_capability(struct vfio_pci_device *device, u8 cap_id)
+{
+ int ttl = PCI_FIND_CAP_TTL;
+ u8 pos;
+
+ if (!(vfio_pci_config_readw(device, PCI_STATUS) & PCI_STATUS_CAP_LIST))
+ return 0;
+
+ pos = vfio_pci_config_readb(device, PCI_CAPABILITY_LIST);
+
+ while (pos >= PCI_STD_HEADER_SIZEOF && ttl--) {
+ pos &= ~3;
+
+ if (vfio_pci_config_readb(device, pos + PCI_CAP_LIST_ID) == cap_id)
+ return pos;
+
+ pos = vfio_pci_config_readb(device, pos + PCI_CAP_LIST_NEXT);
+ }
+
+ return 0;
+}
+
int __vfio_pci_device_reset(struct vfio_pci_device *device)
{
if (ioctl(device->fd, VFIO_DEVICE_RESET, NULL))
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 4/5] vfio: selftests: Add sysfs_kernel_warn_count_get()
2026-10-29 20:33 [PATCH 0/5] vfio/pci: Fix power state race with SR-IOV enablement David Matlack
` (2 preceding siblings ...)
2026-10-29 20:33 ` [PATCH 3/5] vfio: selftests: Add vfio_pci_find_capability() David Matlack
@ 2026-10-29 20:33 ` David Matlack
2026-10-29 20:33 ` [PATCH 5/5] vfio: selftests: Add test for device power state transitions David Matlack
4 siblings, 0 replies; 6+ messages in thread
From: David Matlack @ 2026-10-29 20:33 UTC (permalink / raw)
To: kvm, linux-kernel, linux-kselftest, stable
Cc: Abhishek Sahu, Alex Williamson, David Matlack,
Raghavendra Rao Ananta, Shuah Khan
Add a helper to read /sys/kernel/warn_count, which counts the number of
warnings (e.g. WARN(), KASAN, and UBSAN reports) the kernel has
encountered since boot. Return a negative value if the file does not
exist, e.g. on kernels that predate it.
Factor the code to read an integer from a sysfs file out of
sysfs_val_get_int() so that it can be shared by both helpers.
This will be used in a subsequent commit to detect kernel warnings
triggered by tests.
Signed-off-by: David Matlack <dmatlack@google.com>
---
.../vfio/lib/include/libvfio/sysfs.h | 7 +++++++
tools/testing/selftests/vfio/lib/sysfs.c | 19 +++++++++++++++----
2 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/vfio/lib/include/libvfio/sysfs.h b/tools/testing/selftests/vfio/lib/include/libvfio/sysfs.h
index c9ab1ea8f5a9..1b9711d1bf90 100644
--- a/tools/testing/selftests/vfio/lib/include/libvfio/sysfs.h
+++ b/tools/testing/selftests/vfio/lib/include/libvfio/sysfs.h
@@ -9,4 +9,11 @@ char *sysfs_sriov_vf_bdf_get(const char *pf_bdf, int i);
int sysfs_iommu_group_get(const char *bdf);
char *sysfs_driver_get(const char *bdf);
+/*
+ * Return the number of warnings the kernel has encountered since boot (e.g.
+ * WARN(), KASAN, and UBSAN reports), or a negative value if the kernel does
+ * not support /sys/kernel/warn_count.
+ */
+int sysfs_kernel_warn_count_get(void);
+
#endif /* SELFTESTS_VFIO_LIB_INCLUDE_LIBVFIO_SYSFS_H */
diff --git a/tools/testing/selftests/vfio/lib/sysfs.c b/tools/testing/selftests/vfio/lib/sysfs.c
index d88e348a1e0d..71470196f092 100644
--- a/tools/testing/selftests/vfio/lib/sysfs.c
+++ b/tools/testing/selftests/vfio/lib/sysfs.c
@@ -33,15 +33,12 @@ static void readlink_base(const char *path, const char *data_fmt, void *out_data
VFIO_ASSERT_EQ(ret, 1);
}
-static int sysfs_val_get_int(const char *component, const char *name,
- const char *file)
+static int sysfs_path_get_int(const char *path)
{
- char path[PATH_MAX];
char buf[32];
int ret;
int fd;
- snprintf_assert(path, PATH_MAX, "/sys/bus/pci/%s/%s/%s", component, name, file);
fd = open(path, O_RDONLY);
if (fd < 0)
return fd;
@@ -56,6 +53,15 @@ static int sysfs_val_get_int(const char *component, const char *name,
return ret;
}
+static int sysfs_val_get_int(const char *component, const char *name,
+ const char *file)
+{
+ char path[PATH_MAX];
+
+ snprintf_assert(path, PATH_MAX, "/sys/bus/pci/%s/%s/%s", component, name, file);
+ return sysfs_path_get_int(path);
+}
+
static void sysfs_val_set(const char *component, const char *name,
const char *file, const char *val)
{
@@ -148,3 +154,8 @@ char *sysfs_driver_get(const char *bdf)
return out_driver;
}
+
+int sysfs_kernel_warn_count_get(void)
+{
+ return sysfs_path_get_int("/sys/kernel/warn_count");
+}
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 5/5] vfio: selftests: Add test for device power state transitions
2026-10-29 20:33 [PATCH 0/5] vfio/pci: Fix power state race with SR-IOV enablement David Matlack
` (3 preceding siblings ...)
2026-10-29 20:33 ` [PATCH 4/5] vfio: selftests: Add sysfs_kernel_warn_count_get() David Matlack
@ 2026-10-29 20:33 ` David Matlack
4 siblings, 0 replies; 6+ messages in thread
From: David Matlack @ 2026-10-29 20:33 UTC (permalink / raw)
To: kvm, linux-kernel, linux-kselftest, stable
Cc: Abhishek Sahu, Alex Williamson, David Matlack,
Raghavendra Rao Ananta, Shuah Khan
Add vfio_pci_power_state_test to exercise VFIO's handling of device
power state changes initiated by userspace through the PM capability in
config space.
The test covers:
- Closing a device while it is in D3hot, which must return the device
to D0.
- Enabling SR-IOV while the PF is in D3hot, which must return the PF to
D0 and prevent it from leaving D0 while VFs are enabled.
- Racing a close of the device while it is in D3hot against enabling
SR-IOV, both of which transition the device back to D0.
The SR-IOV tests are skipped if the device does not support SR-IOV or
already has VFs enabled.
Bugs in VFIO's power state handling, such as the race above, often only
manifest as kernel warnings (e.g. WARN() or KASAN reports). So fail the
test if /sys/kernel/warn_count changes between fixture setup and
teardown.
Signed-off-by: David Matlack <dmatlack@google.com>
---
tools/testing/selftests/vfio/Makefile | 1 +
.../vfio/vfio_pci_power_state_test.c | 189 ++++++++++++++++++
2 files changed, 190 insertions(+)
create mode 100644 tools/testing/selftests/vfio/vfio_pci_power_state_test.c
diff --git a/tools/testing/selftests/vfio/Makefile b/tools/testing/selftests/vfio/Makefile
index 2c32c48db509..e2b152528560 100644
--- a/tools/testing/selftests/vfio/Makefile
+++ b/tools/testing/selftests/vfio/Makefile
@@ -12,6 +12,7 @@ TEST_GEN_PROGS += vfio_iommufd_setup_test
TEST_GEN_PROGS += vfio_pci_device_test
TEST_GEN_PROGS += vfio_pci_device_init_perf_test
TEST_GEN_PROGS += vfio_pci_driver_test
+TEST_GEN_PROGS += vfio_pci_power_state_test
TEST_GEN_PROGS += vfio_pci_sriov_uapi_test
TEST_FILES += scripts/cleanup.sh
diff --git a/tools/testing/selftests/vfio/vfio_pci_power_state_test.c b/tools/testing/selftests/vfio/vfio_pci_power_state_test.c
new file mode 100644
index 000000000000..d5853eb89856
--- /dev/null
+++ b/tools/testing/selftests/vfio/vfio_pci_power_state_test.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <pthread.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <linux/pci_regs.h>
+
+#include <libvfio.h>
+
+#include "kselftest_harness.h"
+
+/* PCI_PM_CTRL_STATE_MASK values */
+#define D0 0
+#define D3HOT 3
+
+static const char *device_bdf;
+
+static u16 power_state_get(struct vfio_pci_device *device, u8 pm_cap)
+{
+ return vfio_pci_config_readw(device, pm_cap + PCI_PM_CTRL) &
+ PCI_PM_CTRL_STATE_MASK;
+}
+
+static void power_state_set(struct vfio_pci_device *device, u8 pm_cap, u16 state)
+{
+ u16 pmcsr = vfio_pci_config_readw(device, pm_cap + PCI_PM_CTRL);
+
+ pmcsr &= ~PCI_PM_CTRL_STATE_MASK;
+ pmcsr |= state;
+
+ vfio_pci_config_writew(device, pm_cap + PCI_PM_CTRL, pmcsr);
+}
+
+static bool sriov_supported(void)
+{
+ return sysfs_sriov_totalvfs_get(device_bdf) > 0 &&
+ sysfs_sriov_numvfs_get(device_bdf) == 0;
+}
+
+FIXTURE(vfio_pci_power_state_test) {
+ struct iommu *iommu;
+ struct vfio_pci_device *device;
+ u8 pm_cap;
+ int warn_count;
+};
+
+FIXTURE_SETUP(vfio_pci_power_state_test)
+{
+ /*
+ * Snapshot the kernel's warning count so that teardown can check that
+ * the test did not trigger any kernel warnings (e.g. WARN() or KASAN
+ * reports). Bugs in power state handling often only manifest as kernel
+ * warnings, which are otherwise invisible to the test.
+ */
+ self->warn_count = sysfs_kernel_warn_count_get();
+
+ self->iommu = iommu_init(default_iommu_mode);
+ self->device = vfio_pci_device_init(device_bdf, self->iommu);
+
+ self->pm_cap = vfio_pci_find_capability(self->device, PCI_CAP_ID_PM);
+ if (!self->pm_cap)
+ SKIP(return, "Device does not have a PM capability\n");
+
+ ASSERT_EQ(power_state_get(self->device, self->pm_cap), D0);
+}
+
+FIXTURE_TEARDOWN(vfio_pci_power_state_test)
+{
+ if (self->device)
+ vfio_pci_device_cleanup(self->device);
+
+ iommu_cleanup(self->iommu);
+
+ if (self->warn_count >= 0)
+ EXPECT_EQ(sysfs_kernel_warn_count_get(), self->warn_count);
+}
+
+/*
+ * Put the device into D3hot via config space and then close it. VFIO must
+ * return the device to D0 when it is closed.
+ */
+TEST_F(vfio_pci_power_state_test, close_in_d3hot)
+{
+ power_state_set(self->device, self->pm_cap, D3HOT);
+ ASSERT_EQ(power_state_get(self->device, self->pm_cap), D3HOT);
+
+ vfio_pci_device_cleanup(self->device);
+
+ self->device = vfio_pci_device_init(device_bdf, self->iommu);
+ ASSERT_EQ(power_state_get(self->device, self->pm_cap), D0);
+}
+
+/*
+ * Put the device into D3hot via config space and then enable SR-IOV. VFIO
+ * must return the PF to D0 before enabling VFs, and must not allow the PF to
+ * leave D0 while VFs are enabled.
+ */
+TEST_F(vfio_pci_power_state_test, sriov_enable_in_d3hot)
+{
+ if (!sriov_supported())
+ SKIP(return, "SR-IOV is not supported or already enabled\n");
+
+ power_state_set(self->device, self->pm_cap, D3HOT);
+ ASSERT_EQ(power_state_get(self->device, self->pm_cap), D3HOT);
+
+ sysfs_sriov_numvfs_set(device_bdf, 1);
+ EXPECT_EQ(power_state_get(self->device, self->pm_cap), D0);
+
+ power_state_set(self->device, self->pm_cap, D3HOT);
+ EXPECT_EQ(power_state_get(self->device, self->pm_cap), D0);
+
+ sysfs_sriov_numvfs_set(device_bdf, 0);
+
+ power_state_set(self->device, self->pm_cap, D3HOT);
+ EXPECT_EQ(power_state_get(self->device, self->pm_cap), D3HOT);
+
+ power_state_set(self->device, self->pm_cap, D0);
+ EXPECT_EQ(power_state_get(self->device, self->pm_cap), D0);
+}
+
+struct sriov_enable_args {
+ pthread_barrier_t barrier;
+ int delay_us;
+};
+
+static void *sriov_enable_thread(void *arg)
+{
+ struct sriov_enable_args *args = arg;
+
+ pthread_barrier_wait(&args->barrier);
+ usleep(args->delay_us);
+ sysfs_sriov_numvfs_set(device_bdf, 1);
+ return NULL;
+}
+
+/*
+ * Race closing the device while it is in D3hot against enabling SR-IOV. Both
+ * paths transition the device back to D0, which must be serialized by VFIO,
+ * e.g. to avoid restoring the PF's BARs after VFs have been enabled or
+ * double-freeing the saved PM state.
+ *
+ * The D3hot->D0 transition during close includes a delay (10ms by default)
+ * before the device's state is restored. Use a barrier to start closing the
+ * device and enabling SR-IOV at the same time, and then delay enabling SR-IOV
+ * by a few milliseconds so that it lands inside that window. Vary the delay
+ * across iterations to account for differences in timing across systems and
+ * kernel configs.
+ */
+TEST_F(vfio_pci_power_state_test, close_in_d3hot_races_sriov_enable)
+{
+ struct sriov_enable_args args;
+ pthread_t thread;
+ int i;
+
+ if (!sriov_supported())
+ SKIP(return, "SR-IOV is not supported or already enabled\n");
+
+ ASSERT_EQ(pthread_barrier_init(&args.barrier, NULL, 2), 0);
+
+ /*
+ * Keep the number of iterations low since each one enables and
+ * disables SR-IOV, which can take over a second.
+ */
+ for (i = 0; i < 10; i++) {
+ args.delay_us = 2000 + i * 500;
+
+ power_state_set(self->device, self->pm_cap, D3HOT);
+ ASSERT_EQ(power_state_get(self->device, self->pm_cap), D3HOT);
+
+ ASSERT_EQ(pthread_create(&thread, NULL, sriov_enable_thread, &args), 0);
+ pthread_barrier_wait(&args.barrier);
+ vfio_pci_device_cleanup(self->device);
+ self->device = NULL;
+ ASSERT_EQ(pthread_join(thread, NULL), 0);
+
+ sysfs_sriov_numvfs_set(device_bdf, 0);
+
+ self->device = vfio_pci_device_init(device_bdf, self->iommu);
+ ASSERT_EQ(power_state_get(self->device, self->pm_cap), D0);
+ }
+
+ ASSERT_EQ(pthread_barrier_destroy(&args.barrier), 0);
+}
+
+int main(int argc, char *argv[])
+{
+ device_bdf = vfio_selftests_get_bdf(&argc, argv);
+ return test_harness_run(argc, argv);
+}
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 6+ messages in thread