[PATCH RESEND v2 20/23] selftests/proc: check that a mapped entry can be removed
From: Christian Brauner
Date: Wed Nov 04 2026 - 16:37:01 EST
Map a memory BAR of a PCI device through /proc/bus/pci and remove the
device through sysfs:
- the removal doesn't wait for the mapped file
- the entry is gone
- and reading the file fails with -EIO
The mapping itself survives. /proc/bus/pci maps through the iomem
address space of /dev/mem and procfs leaves that alone, like the sysfs
resource files leave theirs, so the test doesn't touch the mapping
after the removal. What the BAR of a removed device reads like is
between the reader and the hardware.
Nothing in the test can know which device is safe to remove, so it
takes the device from PROC_PCI_DEV, skips without it and rescans the bus
afterwards. It also skips where PCI files can't be mapped or that isn't
allowed.
Signed-off-by: Christian Brauner (Amutable) <brauner@xxxxxxxxxx>
---
tools/testing/selftests/proc/.gitignore | 1 +
tools/testing/selftests/proc/Makefile | 1 +
tools/testing/selftests/proc/proc-remove-mapped.c | 160 ++++++++++++++++++++++
3 files changed, 162 insertions(+)
diff --git a/tools/testing/selftests/proc/.gitignore b/tools/testing/selftests/proc/.gitignore
index e2566cc0ae07..cafd8c263c9a 100644
--- a/tools/testing/selftests/proc/.gitignore
+++ b/tools/testing/selftests/proc/.gitignore
@@ -23,6 +23,7 @@
/proc-pidns
/proc-remove-slow-sysctl
/proc-remove-slow-reader
+/proc-remove-mapped
/proc-remove-slow-readlink
/read
/self
diff --git a/tools/testing/selftests/proc/Makefile b/tools/testing/selftests/proc/Makefile
index 6a9bdc999f54..c38297afc244 100644
--- a/tools/testing/selftests/proc/Makefile
+++ b/tools/testing/selftests/proc/Makefile
@@ -34,6 +34,7 @@ TEST_GEN_PROGS += proc-sysctl-readdir-churn
TEST_GEN_PROGS += proc-pidns
TEST_GEN_PROGS += proc-remove-slow-sysctl
TEST_GEN_PROGS += proc-remove-slow-reader
+TEST_GEN_PROGS += proc-remove-mapped
TEST_GEN_PROGS += proc-remove-slow-readlink
include ../lib.mk
diff --git a/tools/testing/selftests/proc/proc-remove-mapped.c b/tools/testing/selftests/proc/proc-remove-mapped.c
new file mode 100644
index 000000000000..92860fb2a8f4
--- /dev/null
+++ b/tools/testing/selftests/proc/proc-remove-mapped.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Removing a /proc entry with a mapped file open must not wait for that file
+ * and must leave the file dead. Map a memory BAR of a PCI device through
+ * /proc/bus/pci and remove the device, which removes its entry. Nothing here
+ * can know which device is safe to remove and rescan, so it is named in
+ * PROC_PCI_DEV, e.g. PROC_PCI_DEV=0000:00:03.0.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/pci.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "../kselftest_harness.h"
+
+#define DEV_ENV "PROC_PCI_DEV"
+#define SYSFS_DEVICES "/sys/bus/pci/devices/"
+#define SYSFS_RESCAN "/sys/bus/pci/rescan"
+#define PROC_PCI "/proc/bus/pci/"
+#define IORESOURCE_MEM 0x00000200
+
+static sigjmp_buf fault;
+
+static void on_sigbus(int sig)
+{
+ siglongjmp(fault, 1);
+}
+
+/* the first memory BAR of @dev */
+static int mem_bar(const char *dev, unsigned long long *start,
+ unsigned long long *len)
+{
+ unsigned long long s, e, flags;
+ char path[PATH_MAX];
+ int ret = -ENODEV;
+ FILE *f;
+
+ snprintf(path, sizeof(path), SYSFS_DEVICES "%s/resource", dev);
+ f = fopen(path, "r");
+ if (!f)
+ return -errno;
+ while (fscanf(f, "%llx %llx %llx", &s, &e, &flags) == 3) {
+ if ((flags & IORESOURCE_MEM) && e >= s) {
+ *start = s;
+ *len = e - s + 1;
+ ret = 0;
+ break;
+ }
+ }
+ fclose(f);
+ return ret;
+}
+
+static int write_one(const char *path)
+{
+ int fd, ret;
+
+ fd = open(path, O_WRONLY | O_CLOEXEC);
+ if (fd < 0)
+ return -errno;
+ ret = write(fd, "1", 1) < 0 ? -errno : 0;
+ close(fd);
+ return ret;
+}
+
+FIXTURE(pci) {
+ char dev[16];
+ char path[64];
+ bool removed;
+};
+
+FIXTURE_SETUP(pci)
+{
+ const char *dev = getenv(DEV_ENV);
+
+ self->removed = false;
+ if (!dev)
+ SKIP(return, DEV_ENV " isn't set");
+ /* 0000:BB:DD.F is /proc/bus/pci/BB/DD.F */
+ if (strlen(dev) != 12 || strncmp(dev, "0000:", 5))
+ SKIP(return, "%s: not a device in PCI domain 0", dev);
+ strcpy(self->dev, dev);
+ snprintf(self->path, sizeof(self->path), PROC_PCI "%.2s/%s",
+ dev + 5, dev + 8);
+ if (access(self->path, R_OK))
+ SKIP(return, "%s: %s", self->path, strerror(errno));
+}
+
+FIXTURE_TEARDOWN(pci)
+{
+ if (self->removed && write_one(SYSFS_RESCAN))
+ TH_LOG("couldn't rescan the PCI bus for %s", self->dev);
+}
+
+TEST_F(pci, remove_while_mapped)
+{
+ long psz = sysconf(_SC_PAGESIZE);
+ unsigned long long start = 0, len = 0;
+ char path[PATH_MAX];
+ volatile unsigned int *p;
+ unsigned int v;
+ int fd;
+
+ if (mem_bar(self->dev, &start, &len))
+ SKIP(return, "%s has no memory BAR", self->dev);
+
+ fd = open(self->path, O_RDONLY | O_CLOEXEC);
+ ASSERT_GE(fd, 0);
+ if (ioctl(fd, PCIIOC_MMAP_IS_MEM)) {
+ /* no PCI mmap on this architecture, or locked down */
+ if (errno == EINVAL || errno == EPERM) {
+ close(fd);
+ SKIP(return, "PCIIOC_MMAP_IS_MEM: %s", strerror(errno));
+ }
+ ASSERT_TRUE(false) {
+ TH_LOG("PCIIOC_MMAP_IS_MEM: %s", strerror(errno));
+ }
+ }
+ p = mmap(NULL, psz, PROT_READ, MAP_SHARED, fd, start);
+ if (p == MAP_FAILED)
+ SKIP(return, "mmap: %s", strerror(errno));
+
+ /* The BAR can be read while the device is there. */
+ ASSERT_NE(signal(SIGBUS, on_sigbus), SIG_ERR);
+ if (sigsetjmp(fault, 1) == 0) {
+ v = *p;
+ } else {
+ ASSERT_TRUE(false) {
+ TH_LOG("reading the mapping faulted before the removal");
+ }
+ }
+
+ /* Removing the device removes its entry. */
+ snprintf(path, sizeof(path), SYSFS_DEVICES "%s/remove", self->dev);
+ ASSERT_EQ(write_one(path), 0);
+ self->removed = true;
+ EXPECT_EQ(access(self->path, F_OK), -1);
+
+ /*
+ * The file is dead. The mapping isn't procfs's to unmap, it lives in
+ * the iomem address space of /dev/mem, see proc_bus_pci_open(), and
+ * is left alone like a sysfs resource file's.
+ */
+ EXPECT_EQ(pread(fd, &v, sizeof(v), 0), -1);
+ EXPECT_EQ(errno, EIO);
+
+ munmap((void *)p, psz);
+ close(fd);
+}
+
+TEST_HARNESS_MAIN
--
2.53.0