[PATCH RESEND v2 20/23] selftests/proc: check that a mapped entry can be removed

Christian Brauner brauner at kernel.org
Wed Nov 4 11:34:05 PST 2026


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 at kernel.org>
---
 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




More information about the linux-um mailing list