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
21static 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
37static 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
63static 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
78static 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
87static 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
104static 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
125static 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
132static 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
143static 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");
207out:
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
220static 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
289out:
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 */
299static 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 */
327static 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
363static 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");
390out:
391	if (dmabuf_fd >= 0)
392		close(dmabuf_fd);
393	if (heap_fd >= 0)
394		close(heap_fd);
395
396	return ret;
397}
398
399static 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;
433out:
434	if (dmabuf_fd >= 0)
435		close(dmabuf_fd);
436	if (heap_fd >= 0)
437		close(heap_fd);
438
439	return ret;
440}
441
442int 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