--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+#define _GNU_SOURCE
+
+#include <dirent.h>
+#include <sched.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+
+#include <linux/perf_event.h>
+
+#include "../kselftest_harness.h"
+
+#define RB_SIZE                0x3000
+#define AUX_SIZE       0x10000
+#define AUX_OFFS       0x4000
+
+#define HOLE_SIZE      0x1000
+
+/* Reserve space for rb, aux with space for shrink-beyond-vma testing. */
+#define REGION_SIZE    (2 * RB_SIZE + 2 * AUX_SIZE)
+#define REGION_AUX_OFFS (2 * RB_SIZE)
+
+#define MAP_BASE       1
+#define MAP_AUX                2
+
+#define EVENT_SRC_DIR  "/sys/bus/event_source/devices"
+
+FIXTURE(perf_mmap)
+{
+       int             fd;
+       void            *ptr;
+       void            *region;
+};
+
+FIXTURE_VARIANT(perf_mmap)
+{
+       bool            aux;
+       unsigned long   ptr_size;
+};
+
+FIXTURE_VARIANT_ADD(perf_mmap, rb)
+{
+       .aux = false,
+       .ptr_size = RB_SIZE,
+};
+
+FIXTURE_VARIANT_ADD(perf_mmap, aux)
+{
+       .aux = true,
+       .ptr_size = AUX_SIZE,
+};
+
+static bool read_event_type(struct dirent *dent, __u32 *type)
+{
+       char typefn[512];
+       FILE *fp;
+       int res;
+
+       snprintf(typefn, sizeof(typefn), "%s/%s/type", EVENT_SRC_DIR, dent->d_name);
+       fp = fopen(typefn, "r");
+       if (!fp)
+               return false;
+
+       res = fscanf(fp, "%u", type);
+       fclose(fp);
+       return res > 0;
+}
+
+FIXTURE_SETUP(perf_mmap)
+{
+       struct perf_event_attr attr = {
+               .size           = sizeof(attr),
+               .disabled       = 1,
+               .exclude_kernel = 1,
+               .exclude_hv     = 1,
+       };
+       struct perf_event_attr attr_ok = {};
+       unsigned int eacces = 0, map = 0;
+       struct perf_event_mmap_page *rb;
+       struct dirent *dent;
+       void *aux, *region;
+       DIR *dir;
+
+       self->ptr = NULL;
+
+       dir = opendir(EVENT_SRC_DIR);
+       if (!dir)
+               SKIP(return, "perf not available.");
+
+       region = mmap(NULL, REGION_SIZE, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0);
+       ASSERT_NE(region, MAP_FAILED);
+       self->region = region;
+
+       // Try to find a suitable event on this system
+       while ((dent = readdir(dir))) {
+               int fd;
+
+               if (!read_event_type(dent, &attr.type))
+                       continue;
+
+               fd = syscall(SYS_perf_event_open, &attr, 0, -1, -1, 0);
+               if (fd < 0) {
+                       if (errno == EACCES)
+                               eacces++;
+                       continue;
+               }
+
+               // Check whether the event supports mmap()
+               rb = mmap(region, RB_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0);
+               if (rb == MAP_FAILED) {
+                       close(fd);
+                       continue;
+               }
+
+               if (!map) {
+                       // Save the event in case that no AUX capable event is found
+                       attr_ok = attr;
+                       map = MAP_BASE;
+               }
+
+               if (!variant->aux)
+                       continue;
+
+               rb->aux_offset = AUX_OFFS;
+               rb->aux_size = AUX_SIZE;
+
+               // Check whether it supports a AUX buffer
+               aux = mmap(region + REGION_AUX_OFFS, AUX_SIZE, PROT_READ | PROT_WRITE,
+                          MAP_SHARED | MAP_FIXED, fd, AUX_OFFS);
+               if (aux == MAP_FAILED) {
+                       munmap(rb, RB_SIZE);
+                       close(fd);
+                       continue;
+               }
+
+               attr_ok = attr;
+               map = MAP_AUX;
+               munmap(aux, AUX_SIZE);
+               munmap(rb, RB_SIZE);
+               close(fd);
+               break;
+       }
+       closedir(dir);
+
+       if (!map) {
+               if (!eacces)
+                       SKIP(return, "No mappable perf event found.");
+               else
+                       SKIP(return, "No permissions for perf_event_open()");
+       }
+
+       self->fd = syscall(SYS_perf_event_open, &attr_ok, 0, -1, -1, 0);
+       ASSERT_NE(self->fd, -1);
+
+       rb = mmap(region, RB_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, self->fd, 0);
+       ASSERT_NE(rb, MAP_FAILED);
+
+       if (!variant->aux) {
+               self->ptr = rb;
+               return;
+       }
+
+       if (map != MAP_AUX)
+               SKIP(return, "No AUX event found.");
+
+       rb->aux_offset = AUX_OFFS;
+       rb->aux_size = AUX_SIZE;
+       aux = mmap(region + REGION_AUX_OFFS, AUX_SIZE, PROT_READ | PROT_WRITE,
+                  MAP_SHARED | MAP_FIXED, self->fd, AUX_OFFS);
+       ASSERT_NE(aux, MAP_FAILED);
+       self->ptr = aux;
+}
+
+FIXTURE_TEARDOWN(perf_mmap)
+{
+       ASSERT_EQ(munmap(self->region, REGION_SIZE), 0);
+       if (self->fd != -1)
+               ASSERT_EQ(close(self->fd), 0);
+}
+
+TEST_F(perf_mmap, remap)
+{
+       void *tmp, *ptr = self->ptr;
+       unsigned long size = variant->ptr_size;
+
+       // Test the invalid remaps
+       ASSERT_EQ(mremap(ptr, size, HOLE_SIZE, MREMAP_MAYMOVE), MAP_FAILED);
+       ASSERT_EQ(mremap(ptr + HOLE_SIZE, size, HOLE_SIZE, MREMAP_MAYMOVE), MAP_FAILED);
+       ASSERT_EQ(mremap(ptr + size - HOLE_SIZE, HOLE_SIZE, size, MREMAP_MAYMOVE), MAP_FAILED);
+       // Shrink the end of the mapping such that we only unmap past end of the VMA,
+       // which should succeed and poke a hole into the PROT_NONE region
+       ASSERT_NE(mremap(ptr + size - HOLE_SIZE, size, HOLE_SIZE, MREMAP_MAYMOVE), MAP_FAILED);
+
+       // Remap the whole buffer to a new address
+       tmp = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+       ASSERT_NE(tmp, MAP_FAILED);
+
+       // Try splitting offset 1 hole size into VMA, this should fail
+       ASSERT_EQ(mremap(ptr + HOLE_SIZE, size - HOLE_SIZE, size - HOLE_SIZE,
+                        MREMAP_MAYMOVE | MREMAP_FIXED, tmp), MAP_FAILED);
+       // Remapping the whole thing should succeed fine
+       ptr = mremap(ptr, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, tmp);
+       ASSERT_EQ(ptr, tmp);
+       ASSERT_EQ(munmap(tmp, size), 0);
+}
+
+TEST_F(perf_mmap, unmap)
+{
+       unsigned long size = variant->ptr_size;
+
+       // Try to poke holes into the mappings
+       ASSERT_NE(munmap(self->ptr, HOLE_SIZE), 0);
+       ASSERT_NE(munmap(self->ptr + HOLE_SIZE, HOLE_SIZE), 0);
+       ASSERT_NE(munmap(self->ptr + size - HOLE_SIZE, HOLE_SIZE), 0);
+}
+
+TEST_F(perf_mmap, map)
+{
+       unsigned long size = variant->ptr_size;
+
+       // Try to poke holes into the mappings by mapping anonymous memory over it
+       ASSERT_EQ(mmap(self->ptr, HOLE_SIZE, PROT_READ | PROT_WRITE,
+                      MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0), MAP_FAILED);
+       ASSERT_EQ(mmap(self->ptr + HOLE_SIZE, HOLE_SIZE, PROT_READ | PROT_WRITE,
+                      MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0), MAP_FAILED);
+       ASSERT_EQ(mmap(self->ptr + size - HOLE_SIZE, HOLE_SIZE, PROT_READ | PROT_WRITE,
+                      MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0), MAP_FAILED);
+}
+
+TEST_HARNESS_MAIN