1 // SPDX-License-Identifier: GPL-2.0
2 
3 #include <dirent.h>
4 #include <errno.h>
5 #include <fcntl.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <stdint.h>
9 #include <string.h>
10 #include <unistd.h>
11 #include <sys/ioctl.h>
12 #include <sys/mman.h>
13 #include <sys/types.h>
14 
15 #include <linux/dma-buf.h>
16 #include <linux/dma-heap.h>
17 #include <drm/drm.h>
18 
19 #define DEVPATH "/dev/dma_heap"
20 
21 static int check_vgem(int fd)
22 {
23 	drm_version_t version = { 0 };
24 	char name[5];
25 	int ret;
26 
27 	version.name_len = 4;
28 	version.name = name;
29 
30 	ret = ioctl(fd, DRM_IOCTL_VERSION, &version);
31 	if (ret)
32 		return 0;
33 
34 	return !strcmp(name, "vgem");
35 }
36 
37 static int open_vgem(void)
38 {
39 	int i, fd;
40 	const char *drmstr = "/dev/dri/card";
41 
42 	fd = -1;
43 	for (i = 0; i < 16; i++) {
44 		char name[80];
45 
46 		snprintf(name, 80, "%s%u", drmstr, i);
47 
48 		fd = open(name, O_RDWR);
49 		if (fd < 0)
50 			continue;
51 
52 		if (!check_vgem(fd)) {
53 			close(fd);
54 			fd = -1;
55 			continue;
56 		} else {
57 			break;
58 		}
59 	}
60 	return fd;
61 }
62 
63 static int import_vgem_fd(int vgem_fd, int dma_buf_fd, uint32_t *handle)
64 {
65 	struct drm_prime_handle import_handle = {
66 		.fd = dma_buf_fd,
67 		.flags = 0,
68 		.handle = 0,
69 	 };
70 	int ret;
71 
72 	ret = ioctl(vgem_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &import_handle);
73 	if (ret == 0)
74 		*handle = import_handle.handle;
75 	return ret;
76 }
77 
78 static void close_handle(int vgem_fd, uint32_t handle)
79 {
80 	struct drm_gem_close close = {
81 		.handle = handle,
82 	};
83 
84 	ioctl(vgem_fd, DRM_IOCTL_GEM_CLOSE, &close);
85 }
86 
87 static int dmabuf_heap_open(char *name)
88 {
89 	int ret, fd;
90 	char buf[256];
91 
92 	ret = snprintf(buf, 256, "%s/%s", DEVPATH, name);
93 	if (ret < 0) {
94 		printf("snprintf failed!\n");
95 		return ret;
96 	}
97 
98 	fd = open(buf, O_RDWR);
99 	if (fd < 0)
100 		printf("open %s failed!\n", buf);
101 	return fd;
102 }
103 
104 static int dmabuf_heap_alloc_fdflags(int fd, size_t len, unsigned int fd_flags,
105 				     unsigned int heap_flags, int *dmabuf_fd)
106 {
107 	struct dma_heap_allocation_data data = {
108 		.len = len,
109 		.fd = 0,
110 		.fd_flags = fd_flags,
111 		.heap_flags = heap_flags,
112 	};
113 	int ret;
114 
115 	if (!dmabuf_fd)
116 		return -EINVAL;
117 
118 	ret = ioctl(fd, DMA_HEAP_IOCTL_ALLOC, &data);
119 	if (ret < 0)
120 		return ret;
121 	*dmabuf_fd = (int)data.fd;
122 	return ret;
123 }
124 
125 static int dmabuf_heap_alloc(int fd, size_t len, unsigned int flags,
126 			     int *dmabuf_fd)
127 {
128 	return dmabuf_heap_alloc_fdflags(fd, len, O_RDWR | O_CLOEXEC, flags,
129 					 dmabuf_fd);
130 }
131 
132 static int dmabuf_sync(int fd, int start_stop)
133 {
134 	struct dma_buf_sync sync = {
135 		.flags = start_stop | DMA_BUF_SYNC_RW,
136 	};
137 
138 	return ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync);
139 }
140 
141 #define ONE_MEG (1024 * 1024)
142 
143 static int test_alloc_and_import(char *heap_name)
144 {
145 	int heap_fd = -1, dmabuf_fd = -1, importer_fd = -1;
146 	uint32_t handle = 0;
147 	void *p = NULL;
148 	int ret;
149 
150 	heap_fd = dmabuf_heap_open(heap_name);
151 	if (heap_fd < 0)
152 		return -1;
153 
154 	printf("  Testing allocation and importing:  ");
155 	ret = dmabuf_heap_alloc(heap_fd, ONE_MEG, 0, &dmabuf_fd);
156 	if (ret) {
157 		printf("FAIL (Allocation Failed!)\n");
158 		ret = -1;
159 		goto out;
160 	}
161 	/* mmap and write a simple pattern */
162 	p = mmap(NULL,
163 		 ONE_MEG,
164 		 PROT_READ | PROT_WRITE,
165 		 MAP_SHARED,
166 		 dmabuf_fd,
167 		 0);
168 	if (p == MAP_FAILED) {
169 		printf("FAIL (mmap() failed)\n");
170 		ret = -1;
171 		goto out;
172 	}
173 
174 	dmabuf_sync(dmabuf_fd, DMA_BUF_SYNC_START);
175 	memset(p, 1, ONE_MEG / 2);
176 	memset((char *)p + ONE_MEG / 2, 0, ONE_MEG / 2);
177 	dmabuf_sync(dmabuf_fd, DMA_BUF_SYNC_END);
178 
179 	importer_fd = open_vgem();
180 	if (importer_fd < 0) {
181 		ret = importer_fd;
182 		printf("(Could not open vgem - skipping):  ");
183 	} else {
184 		ret = import_vgem_fd(importer_fd, dmabuf_fd, &handle);
185 		if (ret < 0) {
186 			printf("FAIL (Failed to import buffer)\n");
187 			goto out;
188 		}
189 	}
190 
191 	ret = dmabuf_sync(dmabuf_fd, DMA_BUF_SYNC_START);
192 	if (ret < 0) {
193 		printf("FAIL (DMA_BUF_SYNC_START failed!)\n");
194 		goto out;
195 	}
196 
197 	memset(p, 0xff, ONE_MEG);
198 	ret = dmabuf_sync(dmabuf_fd, DMA_BUF_SYNC_END);
199 	if (ret < 0) {
200 		printf("FAIL (DMA_BUF_SYNC_END failed!)\n");
201 		goto out;
202 	}
203 
204 	close_handle(importer_fd, handle);
205 	ret = 0;
206 	printf(" OK\n");
207 out:
208 	if (p)
209 		munmap(p, ONE_MEG);
210 	if (importer_fd >= 0)
211 		close(importer_fd);
212 	if (dmabuf_fd >= 0)
213 		close(dmabuf_fd);
214 	if (heap_fd >= 0)
215 		close(heap_fd);
216 
217 	return ret;
218 }
219 
220 static int test_alloc_zeroed(char *heap_name, size_t size)
221 {
222 	int heap_fd = -1, dmabuf_fd[32];
223 	int i, j, ret;
224 	void *p = NULL;
225 	char *c;
226 
227 	printf("  Testing alloced %ldk buffers are zeroed:  ", size / 1024);
228 	heap_fd = dmabuf_heap_open(heap_name);
229 	if (heap_fd < 0)
230 		return -1;
231 
232 	/* Allocate and fill a bunch of buffers */
233 	for (i = 0; i < 32; i++) {
234 		ret = dmabuf_heap_alloc(heap_fd, size, 0, &dmabuf_fd[i]);
235 		if (ret < 0) {
236 			printf("FAIL (Allocation (%i) failed)\n", i);
237 			goto out;
238 		}
239 		/* mmap and fill with simple pattern */
240 		p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, dmabuf_fd[i], 0);
241 		if (p == MAP_FAILED) {
242 			printf("FAIL (mmap() failed!)\n");
243 			ret = -1;
244 			goto out;
245 		}
246 		dmabuf_sync(dmabuf_fd[i], DMA_BUF_SYNC_START);
247 		memset(p, 0xff, size);
248 		dmabuf_sync(dmabuf_fd[i], DMA_BUF_SYNC_END);
249 		munmap(p, size);
250 	}
251 	/* close them all */
252 	for (i = 0; i < 32; i++)
253 		close(dmabuf_fd[i]);
254 
255 	/* Allocate and validate all buffers are zeroed */
256 	for (i = 0; i < 32; i++) {
257 		ret = dmabuf_heap_alloc(heap_fd, size, 0, &dmabuf_fd[i]);
258 		if (ret < 0) {
259 			printf("FAIL (Allocation (%i) failed)\n", i);
260 			goto out;
261 		}
262 
263 		/* mmap and validate everything is zero */
264 		p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, dmabuf_fd[i], 0);
265 		if (p == MAP_FAILED) {
266 			printf("FAIL (mmap() failed!)\n");
267 			ret = -1;
268 			goto out;
269 		}
270 		dmabuf_sync(dmabuf_fd[i], DMA_BUF_SYNC_START);
271 		c = (char *)p;
272 		for (j = 0; j < size; j++) {
273 			if (c[j] != 0) {
274 				printf("FAIL (Allocated buffer not zeroed @ %i)\n", j);
275 				break;
276 			}
277 		}
278 		dmabuf_sync(dmabuf_fd[i], DMA_BUF_SYNC_END);
279 		munmap(p, size);
280 	}
281 	/* close them all */
282 	for (i = 0; i < 32; i++)
283 		close(dmabuf_fd[i]);
284 
285 	close(heap_fd);
286 	printf("OK\n");
287 	return 0;
288 
289 out:
290 	while (i > 0) {
291 		close(dmabuf_fd[i]);
292 		i--;
293 	}
294 	close(heap_fd);
295 	return ret;
296 }
297 
298 /* Test the ioctl version compatibility w/ a smaller structure then expected */
299 static int dmabuf_heap_alloc_older(int fd, size_t len, unsigned int flags,
300 				   int *dmabuf_fd)
301 {
302 	int ret;
303 	unsigned int older_alloc_ioctl;
304 	struct dma_heap_allocation_data_smaller {
305 		__u64 len;
306 		__u32 fd;
307 		__u32 fd_flags;
308 	} data = {
309 		.len = len,
310 		.fd = 0,
311 		.fd_flags = O_RDWR | O_CLOEXEC,
312 	};
313 
314 	older_alloc_ioctl = _IOWR(DMA_HEAP_IOC_MAGIC, 0x0,
315 				  struct dma_heap_allocation_data_smaller);
316 	if (!dmabuf_fd)
317 		return -EINVAL;
318 
319 	ret = ioctl(fd, older_alloc_ioctl, &data);
320 	if (ret < 0)
321 		return ret;
322 	*dmabuf_fd = (int)data.fd;
323 	return ret;
324 }
325 
326 /* Test the ioctl version compatibility w/ a larger structure then expected */
327 static int dmabuf_heap_alloc_newer(int fd, size_t len, unsigned int flags,
328 				   int *dmabuf_fd)
329 {
330 	int ret;
331 	unsigned int newer_alloc_ioctl;
332 	struct dma_heap_allocation_data_bigger {
333 		__u64 len;
334 		__u32 fd;
335 		__u32 fd_flags;
336 		__u64 heap_flags;
337 		__u64 garbage1;
338 		__u64 garbage2;
339 		__u64 garbage3;
340 	} data = {
341 		.len = len,
342 		.fd = 0,
343 		.fd_flags = O_RDWR | O_CLOEXEC,
344 		.heap_flags = flags,
345 		.garbage1 = 0xffffffff,
346 		.garbage2 = 0x88888888,
347 		.garbage3 = 0x11111111,
348 	};
349 
350 	newer_alloc_ioctl = _IOWR(DMA_HEAP_IOC_MAGIC, 0x0,
351 				  struct dma_heap_allocation_data_bigger);
352 	if (!dmabuf_fd)
353 		return -EINVAL;
354 
355 	ret = ioctl(fd, newer_alloc_ioctl, &data);
356 	if (ret < 0)
357 		return ret;
358 
359 	*dmabuf_fd = (int)data.fd;
360 	return ret;
361 }
362 
363 static int test_alloc_compat(char *heap_name)
364 {
365 	int heap_fd = -1, dmabuf_fd = -1;
366 	int ret;
367 
368 	heap_fd = dmabuf_heap_open(heap_name);
369 	if (heap_fd < 0)
370 		return -1;
371 
372 	printf("  Testing (theoretical)older alloc compat:  ");
373 	ret = dmabuf_heap_alloc_older(heap_fd, ONE_MEG, 0, &dmabuf_fd);
374 	if (ret) {
375 		printf("FAIL (Older compat allocation failed!)\n");
376 		ret = -1;
377 		goto out;
378 	}
379 	close(dmabuf_fd);
380 	printf("OK\n");
381 
382 	printf("  Testing (theoretical)newer alloc compat:  ");
383 	ret = dmabuf_heap_alloc_newer(heap_fd, ONE_MEG, 0, &dmabuf_fd);
384 	if (ret) {
385 		printf("FAIL (Newer compat allocation failed!)\n");
386 		ret = -1;
387 		goto out;
388 	}
389 	printf("OK\n");
390 out:
391 	if (dmabuf_fd >= 0)
392 		close(dmabuf_fd);
393 	if (heap_fd >= 0)
394 		close(heap_fd);
395 
396 	return ret;
397 }
398 
399 static int test_alloc_errors(char *heap_name)
400 {
401 	int heap_fd = -1, dmabuf_fd = -1;
402 	int ret;
403 
404 	heap_fd = dmabuf_heap_open(heap_name);
405 	if (heap_fd < 0)
406 		return -1;
407 
408 	printf("  Testing expected error cases:  ");
409 	ret = dmabuf_heap_alloc(0, ONE_MEG, 0x111111, &dmabuf_fd);
410 	if (!ret) {
411 		printf("FAIL (Did not see expected error (invalid fd)!)\n");
412 		ret = -1;
413 		goto out;
414 	}
415 
416 	ret = dmabuf_heap_alloc(heap_fd, ONE_MEG, 0x111111, &dmabuf_fd);
417 	if (!ret) {
418 		printf("FAIL (Did not see expected error (invalid heap flags)!)\n");
419 		ret = -1;
420 		goto out;
421 	}
422 
423 	ret = dmabuf_heap_alloc_fdflags(heap_fd, ONE_MEG,
424 					~(O_RDWR | O_CLOEXEC), 0, &dmabuf_fd);
425 	if (!ret) {
426 		printf("FAIL (Did not see expected error (invalid fd flags)!)\n");
427 		ret = -1;
428 		goto out;
429 	}
430 
431 	printf("OK\n");
432 	ret = 0;
433 out:
434 	if (dmabuf_fd >= 0)
435 		close(dmabuf_fd);
436 	if (heap_fd >= 0)
437 		close(heap_fd);
438 
439 	return ret;
440 }
441 
442 int main(void)
443 {
444 	DIR *d;
445 	struct dirent *dir;
446 	int ret = -1;
447 
448 	d = opendir(DEVPATH);
449 	if (!d) {
450 		printf("No %s directory?\n", DEVPATH);
451 		return -1;
452 	}
453 
454 	while ((dir = readdir(d)) != NULL) {
455 		if (!strncmp(dir->d_name, ".", 2))
456 			continue;
457 		if (!strncmp(dir->d_name, "..", 3))
458 			continue;
459 
460 		printf("Testing heap: %s\n", dir->d_name);
461 		printf("=======================================\n");
462 		ret = test_alloc_and_import(dir->d_name);
463 		if (ret)
464 			break;
465 
466 		ret = test_alloc_zeroed(dir->d_name, 4 * 1024);
467 		if (ret)
468 			break;
469 
470 		ret = test_alloc_zeroed(dir->d_name, ONE_MEG);
471 		if (ret)
472 			break;
473 
474 		ret = test_alloc_compat(dir->d_name);
475 		if (ret)
476 			break;
477 
478 		ret = test_alloc_errors(dir->d_name);
479 		if (ret)
480 			break;
481 	}
482 	closedir(d);
483 
484 	return ret;
485 }
486