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) <[email protected]> --- 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
