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(buf: name, size: 80, fmt: "%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, size: 256, fmt: "%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, heap_flags: 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(name: heap_name); |
151 | if (heap_fd < 0) |
152 | return -1; |
153 | |
154 | printf(" Testing allocation and importing: " ); |
155 | ret = dmabuf_heap_alloc(fd: heap_fd, ONE_MEG, flags: 0, dmabuf_fd: &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(fd: dmabuf_fd, start_stop: 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(fd: dmabuf_fd, start_stop: 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(vgem_fd: importer_fd, dma_buf_fd: dmabuf_fd, handle: &handle); |
185 | if (ret < 0) { |
186 | printf("FAIL (Failed to import buffer)\n" ); |
187 | goto out; |
188 | } |
189 | } |
190 | |
191 | ret = dmabuf_sync(fd: dmabuf_fd, start_stop: 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(fd: dmabuf_fd, start_stop: 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(vgem_fd: 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(name: 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(fd: heap_fd, len: size, flags: 0, dmabuf_fd: &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(fd: dmabuf_fd[i], start_stop: DMA_BUF_SYNC_START); |
247 | memset(p, 0xff, size); |
248 | dmabuf_sync(fd: dmabuf_fd[i], start_stop: 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(fd: heap_fd, len: size, flags: 0, dmabuf_fd: &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(fd: dmabuf_fd[i], start_stop: 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(fd: dmabuf_fd[i], start_stop: 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(name: heap_name); |
369 | if (heap_fd < 0) |
370 | return -1; |
371 | |
372 | printf(" Testing (theoretical)older alloc compat: " ); |
373 | ret = dmabuf_heap_alloc_older(fd: heap_fd, ONE_MEG, flags: 0, dmabuf_fd: &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(fd: heap_fd, ONE_MEG, flags: 0, dmabuf_fd: &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(name: heap_name); |
405 | if (heap_fd < 0) |
406 | return -1; |
407 | |
408 | printf(" Testing expected error cases: " ); |
409 | ret = dmabuf_heap_alloc(fd: 0, ONE_MEG, flags: 0x111111, dmabuf_fd: &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(fd: heap_fd, ONE_MEG, flags: 0x111111, dmabuf_fd: &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(fd: heap_fd, ONE_MEG, |
424 | fd_flags: ~(O_RDWR | O_CLOEXEC), heap_flags: 0, dmabuf_fd: &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(heap_name: dir->d_name); |
463 | if (ret) |
464 | break; |
465 | |
466 | ret = test_alloc_zeroed(heap_name: dir->d_name, size: 4 * 1024); |
467 | if (ret) |
468 | break; |
469 | |
470 | ret = test_alloc_zeroed(heap_name: dir->d_name, ONE_MEG); |
471 | if (ret) |
472 | break; |
473 | |
474 | ret = test_alloc_compat(heap_name: dir->d_name); |
475 | if (ret) |
476 | break; |
477 | |
478 | ret = test_alloc_errors(heap_name: dir->d_name); |
479 | if (ret) |
480 | break; |
481 | } |
482 | closedir(d); |
483 | |
484 | return ret; |
485 | } |
486 | |