| // 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 |