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