1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
|
// 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
|