1 // SPDX-License-Identifier: GPL-2.0-only 2 #define _GNU_SOURCE 3 4 #include <dirent.h> 5 #include <sched.h> 6 #include <stdbool.h> 7 #include <stdio.h> 8 #include <unistd.h> 9 10 #include <sys/ioctl.h> 11 #include <sys/mman.h> 12 #include <sys/syscall.h> 13 #include <sys/types.h> 14 15 #include <linux/perf_event.h> 16 17 #include "../kselftest_harness.h" 18 19 #define RB_SIZE 0x3000 20 #define AUX_SIZE 0x10000 21 #define AUX_OFFS 0x4000 22 23 #define HOLE_SIZE 0x1000 24 25 /* Reserve space for rb, aux with space for shrink-beyond-vma testing. */ 26 #define REGION_SIZE (2 * RB_SIZE + 2 * AUX_SIZE) 27 #define REGION_AUX_OFFS (2 * RB_SIZE) 28 29 #define MAP_BASE 1 30 #define MAP_AUX 2 31 32 #define EVENT_SRC_DIR "/sys/bus/event_source/devices" 33 34 FIXTURE(perf_mmap) 35 { 36 int fd; 37 void *ptr; 38 void *region; 39 }; 40 41 FIXTURE_VARIANT(perf_mmap) 42 { 43 bool aux; 44 unsigned long ptr_size; 45 }; 46 47 FIXTURE_VARIANT_ADD(perf_mmap, rb) 48 { 49 .aux = false, 50 .ptr_size = RB_SIZE, 51 }; 52 53 FIXTURE_VARIANT_ADD(perf_mmap, aux) 54 { 55 .aux = true, 56 .ptr_size = AUX_SIZE, 57 }; 58 59 static bool read_event_type(struct dirent *dent, __u32 *type) 60 { 61 char typefn[512]; 62 FILE *fp; 63 int res; 64 65 snprintf(typefn, sizeof(typefn), "%s/%s/type", EVENT_SRC_DIR, dent->d_name); 66 fp = fopen(typefn, "r"); 67 if (!fp) 68 return false; 69 70 res = fscanf(fp, "%u", type); 71 fclose(fp); 72 return res > 0; 73 } 74 75 FIXTURE_SETUP(perf_mmap) 76 { 77 struct perf_event_attr attr = { 78 .size = sizeof(attr), 79 .disabled = 1, 80 .exclude_kernel = 1, 81 .exclude_hv = 1, 82 }; 83 struct perf_event_attr attr_ok = {}; 84 unsigned int eacces = 0, map = 0; 85 struct perf_event_mmap_page *rb; 86 struct dirent *dent; 87 void *aux, *region; 88 DIR *dir; 89 90 self->ptr = NULL; 91 92 dir = opendir(EVENT_SRC_DIR); 93 if (!dir) 94 SKIP(return, "perf not available."); 95 96 region = mmap(NULL, REGION_SIZE, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0); 97 ASSERT_NE(region, MAP_FAILED); 98 self->region = region; 99 100 // Try to find a suitable event on this system 101 while ((dent = readdir(dir))) { 102 int fd; 103 104 if (!read_event_type(dent, &attr.type)) 105 continue; 106 107 fd = syscall(SYS_perf_event_open, &attr, 0, -1, -1, 0); 108 if (fd < 0) { 109 if (errno == EACCES) 110 eacces++; 111 continue; 112 } 113 114 // Check whether the event supports mmap() 115 rb = mmap(region, RB_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0); 116 if (rb == MAP_FAILED) { 117 close(fd); 118 continue; 119 } 120 121 if (!map) { 122 // Save the event in case that no AUX capable event is found 123 attr_ok = attr; 124 map = MAP_BASE; 125 } 126 127 if (!variant->aux) 128 continue; 129 130 rb->aux_offset = AUX_OFFS; 131 rb->aux_size = AUX_SIZE; 132 133 // Check whether it supports a AUX buffer 134 aux = mmap(region + REGION_AUX_OFFS, AUX_SIZE, PROT_READ | PROT_WRITE, 135 MAP_SHARED | MAP_FIXED, fd, AUX_OFFS); 136 if (aux == MAP_FAILED) { 137 munmap(rb, RB_SIZE); 138 close(fd); 139 continue; 140 } 141 142 attr_ok = attr; 143 map = MAP_AUX; 144 munmap(aux, AUX_SIZE); 145 munmap(rb, RB_SIZE); 146 close(fd); 147 break; 148 } 149 closedir(dir); 150 151 if (!map) { 152 if (!eacces) 153 SKIP(return, "No mappable perf event found."); 154 else 155 SKIP(return, "No permissions for perf_event_open()"); 156 } 157 158 self->fd = syscall(SYS_perf_event_open, &attr_ok, 0, -1, -1, 0); 159 ASSERT_NE(self->fd, -1); 160 161 rb = mmap(region, RB_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, self->fd, 0); 162 ASSERT_NE(rb, MAP_FAILED); 163 164 if (!variant->aux) { 165 self->ptr = rb; 166 return; 167 } 168 169 if (map != MAP_AUX) 170 SKIP(return, "No AUX event found."); 171 172 rb->aux_offset = AUX_OFFS; 173 rb->aux_size = AUX_SIZE; 174 aux = mmap(region + REGION_AUX_OFFS, AUX_SIZE, PROT_READ | PROT_WRITE, 175 MAP_SHARED | MAP_FIXED, self->fd, AUX_OFFS); 176 ASSERT_NE(aux, MAP_FAILED); 177 self->ptr = aux; 178 } 179 180 FIXTURE_TEARDOWN(perf_mmap) 181 { 182 ASSERT_EQ(munmap(self->region, REGION_SIZE), 0); 183 if (self->fd != -1) 184 ASSERT_EQ(close(self->fd), 0); 185 } 186 187 TEST_F(perf_mmap, remap) 188 { 189 void *tmp, *ptr = self->ptr; 190 unsigned long size = variant->ptr_size; 191 192 // Test the invalid remaps 193 ASSERT_EQ(mremap(ptr, size, HOLE_SIZE, MREMAP_MAYMOVE), MAP_FAILED); 194 ASSERT_EQ(mremap(ptr + HOLE_SIZE, size, HOLE_SIZE, MREMAP_MAYMOVE), MAP_FAILED); 195 ASSERT_EQ(mremap(ptr + size - HOLE_SIZE, HOLE_SIZE, size, MREMAP_MAYMOVE), MAP_FAILED); 196 // Shrink the end of the mapping such that we only unmap past end of the VMA, 197 // which should succeed and poke a hole into the PROT_NONE region 198 ASSERT_NE(mremap(ptr + size - HOLE_SIZE, size, HOLE_SIZE, MREMAP_MAYMOVE), MAP_FAILED); 199 200 // Remap the whole buffer to a new address 201 tmp = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); 202 ASSERT_NE(tmp, MAP_FAILED); 203 204 // Try splitting offset 1 hole size into VMA, this should fail 205 ASSERT_EQ(mremap(ptr + HOLE_SIZE, size - HOLE_SIZE, size - HOLE_SIZE, 206 MREMAP_MAYMOVE | MREMAP_FIXED, tmp), MAP_FAILED); 207 // Remapping the whole thing should succeed fine 208 ptr = mremap(ptr, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, tmp); 209 ASSERT_EQ(ptr, tmp); 210 ASSERT_EQ(munmap(tmp, size), 0); 211 } 212 213 TEST_F(perf_mmap, unmap) 214 { 215 unsigned long size = variant->ptr_size; 216 217 // Try to poke holes into the mappings 218 ASSERT_NE(munmap(self->ptr, HOLE_SIZE), 0); 219 ASSERT_NE(munmap(self->ptr + HOLE_SIZE, HOLE_SIZE), 0); 220 ASSERT_NE(munmap(self->ptr + size - HOLE_SIZE, HOLE_SIZE), 0); 221 } 222 223 TEST_F(perf_mmap, map) 224 { 225 unsigned long size = variant->ptr_size; 226 227 // Try to poke holes into the mappings by mapping anonymous memory over it 228 ASSERT_EQ(mmap(self->ptr, HOLE_SIZE, PROT_READ | PROT_WRITE, 229 MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0), MAP_FAILED); 230 ASSERT_EQ(mmap(self->ptr + HOLE_SIZE, HOLE_SIZE, PROT_READ | PROT_WRITE, 231 MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0), MAP_FAILED); 232 ASSERT_EQ(mmap(self->ptr + size - HOLE_SIZE, HOLE_SIZE, PROT_READ | PROT_WRITE, 233 MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0), MAP_FAILED); 234 } 235 236 TEST_HARNESS_MAIN 237