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