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
FIXTURE(perf_mmap)34 FIXTURE(perf_mmap)
35 {
36 int fd;
37 void *ptr;
38 void *region;
39 };
40
FIXTURE_VARIANT(perf_mmap)41 FIXTURE_VARIANT(perf_mmap)
42 {
43 bool aux;
44 unsigned long ptr_size;
45 };
46
FIXTURE_VARIANT_ADD(perf_mmap,rb)47 FIXTURE_VARIANT_ADD(perf_mmap, rb)
48 {
49 .aux = false,
50 .ptr_size = RB_SIZE,
51 };
52
FIXTURE_VARIANT_ADD(perf_mmap,aux)53 FIXTURE_VARIANT_ADD(perf_mmap, aux)
54 {
55 .aux = true,
56 .ptr_size = AUX_SIZE,
57 };
58
read_event_type(struct dirent * dent,__u32 * type)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
FIXTURE_SETUP(perf_mmap)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
FIXTURE_TEARDOWN(perf_mmap)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
TEST_F(perf_mmap,remap)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
TEST_F(perf_mmap,unmap)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
TEST_F(perf_mmap,map)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