xref: /linux/tools/testing/selftests/perf_events/mmap.c (revision adf12a394c8eb4b857b8f70cc6594a9ab25e3fc6)
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