2f822f04c2191def27c2220f9561a998ada5cf5d
[mesa.git] / src / gallium / drivers / vc4 / vc4_bufmgr.c
1 /*
2 * Copyright © 2014-2015 Broadcom
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24 #include <errno.h>
25 #include <err.h>
26 #include <sys/mman.h>
27 #include <fcntl.h>
28 #include <xf86drm.h>
29 #include <xf86drmMode.h>
30
31 #include "util/u_memory.h"
32 #include "util/ralloc.h"
33
34 #include "vc4_context.h"
35 #include "vc4_screen.h"
36
37 static bool dump_stats = false;
38
39 static void
40 vc4_bo_cache_free_all(struct vc4_bo_cache *cache);
41
42 static void
43 vc4_bo_dump_stats(struct vc4_screen *screen)
44 {
45 struct vc4_bo_cache *cache = &screen->bo_cache;
46
47 fprintf(stderr, " BOs allocated: %d\n", screen->bo_count);
48 fprintf(stderr, " BOs size: %dkb\n", screen->bo_size / 1024);
49 fprintf(stderr, " BOs cached: %d\n", cache->bo_count);
50 fprintf(stderr, " BOs cached size: %dkb\n", cache->bo_size / 1024);
51
52 if (!list_empty(&cache->time_list)) {
53 struct vc4_bo *first = LIST_ENTRY(struct vc4_bo,
54 cache->time_list.next,
55 time_list);
56 struct vc4_bo *last = LIST_ENTRY(struct vc4_bo,
57 cache->time_list.prev,
58 time_list);
59
60 fprintf(stderr, " oldest cache time: %ld\n",
61 (long)first->free_time);
62 fprintf(stderr, " newest cache time: %ld\n",
63 (long)last->free_time);
64
65 struct timespec time;
66 clock_gettime(CLOCK_MONOTONIC, &time);
67 fprintf(stderr, " now: %ld\n",
68 time.tv_sec);
69 }
70 }
71
72 static void
73 vc4_bo_remove_from_cache(struct vc4_bo_cache *cache, struct vc4_bo *bo)
74 {
75 list_del(&bo->time_list);
76 list_del(&bo->size_list);
77 cache->bo_count--;
78 cache->bo_size -= bo->size;
79 }
80
81 static struct vc4_bo *
82 vc4_bo_from_cache(struct vc4_screen *screen, uint32_t size, const char *name)
83 {
84 struct vc4_bo_cache *cache = &screen->bo_cache;
85 uint32_t page_index = size / 4096 - 1;
86
87 if (cache->size_list_size <= page_index)
88 return NULL;
89
90 struct vc4_bo *bo = NULL;
91 pipe_mutex_lock(cache->lock);
92 if (!list_empty(&cache->size_list[page_index])) {
93 bo = LIST_ENTRY(struct vc4_bo, cache->size_list[page_index].next,
94 size_list);
95
96 /* Check that the BO has gone idle. If not, then we want to
97 * allocate something new instead, since we assume that the
98 * user will proceed to CPU map it and fill it with stuff.
99 */
100 if (!vc4_bo_wait(bo, 0, NULL)) {
101 pipe_mutex_unlock(cache->lock);
102 return NULL;
103 }
104
105 pipe_reference_init(&bo->reference, 1);
106 vc4_bo_remove_from_cache(cache, bo);
107
108 bo->name = name;
109 }
110 pipe_mutex_unlock(cache->lock);
111 return bo;
112 }
113
114 struct vc4_bo *
115 vc4_bo_alloc(struct vc4_screen *screen, uint32_t size, const char *name)
116 {
117 struct vc4_bo *bo;
118 int ret;
119
120 size = align(size, 4096);
121
122 bo = vc4_bo_from_cache(screen, size, name);
123 if (bo) {
124 if (dump_stats) {
125 fprintf(stderr, "Allocated %s %dkb from cache:\n",
126 name, size / 1024);
127 vc4_bo_dump_stats(screen);
128 }
129 return bo;
130 }
131
132 bo = CALLOC_STRUCT(vc4_bo);
133 if (!bo)
134 return NULL;
135
136 pipe_reference_init(&bo->reference, 1);
137 bo->screen = screen;
138 bo->size = size;
139 bo->name = name;
140 bo->private = true;
141
142 bool cleared_and_retried = false;
143 retry:
144 if (!using_vc4_simulator) {
145 struct drm_vc4_create_bo create;
146 memset(&create, 0, sizeof(create));
147
148 create.size = size;
149
150 ret = drmIoctl(screen->fd, DRM_IOCTL_VC4_CREATE_BO, &create);
151 bo->handle = create.handle;
152 } else {
153 struct drm_mode_create_dumb create;
154 memset(&create, 0, sizeof(create));
155
156 create.width = 128;
157 create.bpp = 8;
158 create.height = (size + 127) / 128;
159
160 ret = drmIoctl(screen->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
161 bo->handle = create.handle;
162 assert(create.size >= size);
163 }
164 if (ret != 0) {
165 if (!list_empty(&screen->bo_cache.time_list) &&
166 !cleared_and_retried) {
167 cleared_and_retried = true;
168 vc4_bo_cache_free_all(&screen->bo_cache);
169 goto retry;
170 }
171 fprintf(stderr, "create ioctl failure\n");
172 abort();
173 }
174
175 screen->bo_count++;
176 screen->bo_size += bo->size;
177 if (dump_stats) {
178 fprintf(stderr, "Allocated %s %dkb:\n", name, size / 1024);
179 vc4_bo_dump_stats(screen);
180 }
181
182 return bo;
183 }
184
185 void
186 vc4_bo_last_unreference(struct vc4_bo *bo)
187 {
188 struct vc4_screen *screen = bo->screen;
189
190 struct timespec time;
191 clock_gettime(CLOCK_MONOTONIC, &time);
192 pipe_mutex_lock(screen->bo_cache.lock);
193 vc4_bo_last_unreference_locked_timed(bo, time.tv_sec);
194 pipe_mutex_unlock(screen->bo_cache.lock);
195 }
196
197 static void
198 vc4_bo_free(struct vc4_bo *bo)
199 {
200 struct vc4_screen *screen = bo->screen;
201
202 if (bo->map) {
203 #ifdef USE_VC4_SIMULATOR
204 if (bo->simulator_winsys_map) {
205 free(bo->map);
206 bo->map = bo->simulator_winsys_map;
207 }
208 #endif
209 munmap(bo->map, bo->size);
210 }
211
212 struct drm_gem_close c;
213 memset(&c, 0, sizeof(c));
214 c.handle = bo->handle;
215 int ret = drmIoctl(screen->fd, DRM_IOCTL_GEM_CLOSE, &c);
216 if (ret != 0)
217 fprintf(stderr, "close object %d: %s\n", bo->handle, strerror(errno));
218
219 screen->bo_count--;
220 screen->bo_size -= bo->size;
221
222 if (dump_stats) {
223 fprintf(stderr, "Freed %s%s%dkb:\n",
224 bo->name ? bo->name : "",
225 bo->name ? " " : "",
226 bo->size / 1024);
227 vc4_bo_dump_stats(screen);
228 }
229
230 free(bo);
231 }
232
233 static void
234 free_stale_bos(struct vc4_screen *screen, time_t time)
235 {
236 struct vc4_bo_cache *cache = &screen->bo_cache;
237 bool freed_any = false;
238
239 list_for_each_entry_safe(struct vc4_bo, bo, &cache->time_list,
240 time_list) {
241 if (dump_stats && !freed_any) {
242 fprintf(stderr, "Freeing stale BOs:\n");
243 vc4_bo_dump_stats(screen);
244 freed_any = true;
245 }
246
247 /* If it's more than a second old, free it. */
248 if (time - bo->free_time > 2) {
249 vc4_bo_remove_from_cache(cache, bo);
250 vc4_bo_free(bo);
251 } else {
252 break;
253 }
254 }
255
256 if (dump_stats && freed_any) {
257 fprintf(stderr, "Freed stale BOs:\n");
258 vc4_bo_dump_stats(screen);
259 }
260 }
261
262 static void
263 vc4_bo_cache_free_all(struct vc4_bo_cache *cache)
264 {
265 pipe_mutex_lock(cache->lock);
266 list_for_each_entry_safe(struct vc4_bo, bo, &cache->time_list,
267 time_list) {
268 vc4_bo_remove_from_cache(cache, bo);
269 vc4_bo_free(bo);
270 }
271 pipe_mutex_unlock(cache->lock);
272 }
273
274 void
275 vc4_bo_last_unreference_locked_timed(struct vc4_bo *bo, time_t time)
276 {
277 struct vc4_screen *screen = bo->screen;
278 struct vc4_bo_cache *cache = &screen->bo_cache;
279 uint32_t page_index = bo->size / 4096 - 1;
280
281 if (!bo->private) {
282 vc4_bo_free(bo);
283 return;
284 }
285
286 if (cache->size_list_size <= page_index) {
287 struct list_head *new_list =
288 ralloc_array(screen, struct list_head, page_index + 1);
289
290 /* Move old list contents over (since the array has moved, and
291 * therefore the pointers to the list heads have to change).
292 */
293 for (int i = 0; i < cache->size_list_size; i++) {
294 struct list_head *old_head = &cache->size_list[i];
295 if (list_empty(old_head))
296 list_inithead(&new_list[i]);
297 else {
298 new_list[i].next = old_head->next;
299 new_list[i].prev = old_head->prev;
300 new_list[i].next->prev = &new_list[i];
301 new_list[i].prev->next = &new_list[i];
302 }
303 }
304 for (int i = cache->size_list_size; i < page_index + 1; i++)
305 list_inithead(&new_list[i]);
306
307 cache->size_list = new_list;
308 cache->size_list_size = page_index + 1;
309 }
310
311 bo->free_time = time;
312 list_addtail(&bo->size_list, &cache->size_list[page_index]);
313 list_addtail(&bo->time_list, &cache->time_list);
314 cache->bo_count++;
315 cache->bo_size += bo->size;
316 if (dump_stats) {
317 fprintf(stderr, "Freed %s %dkb to cache:\n",
318 bo->name, bo->size / 1024);
319 vc4_bo_dump_stats(screen);
320 }
321 bo->name = NULL;
322
323 free_stale_bos(screen, time);
324 }
325
326 static struct vc4_bo *
327 vc4_bo_open_handle(struct vc4_screen *screen,
328 uint32_t winsys_stride,
329 uint32_t handle, uint32_t size)
330 {
331 struct vc4_bo *bo = CALLOC_STRUCT(vc4_bo);
332
333 assert(size);
334
335 pipe_reference_init(&bo->reference, 1);
336 bo->screen = screen;
337 bo->handle = handle;
338 bo->size = size;
339 bo->name = "winsys";
340 bo->private = false;
341
342 #ifdef USE_VC4_SIMULATOR
343 vc4_bo_map(bo);
344 bo->simulator_winsys_map = bo->map;
345 bo->simulator_winsys_stride = winsys_stride;
346 bo->map = malloc(bo->size);
347 #endif
348
349 return bo;
350 }
351
352 struct vc4_bo *
353 vc4_bo_open_name(struct vc4_screen *screen, uint32_t name,
354 uint32_t winsys_stride)
355 {
356 struct drm_gem_open o = {
357 .name = name
358 };
359 int ret = drmIoctl(screen->fd, DRM_IOCTL_GEM_OPEN, &o);
360 if (ret) {
361 fprintf(stderr, "Failed to open bo %d: %s\n",
362 name, strerror(errno));
363 return NULL;
364 }
365
366 return vc4_bo_open_handle(screen, winsys_stride, o.handle, o.size);
367 }
368
369 struct vc4_bo *
370 vc4_bo_open_dmabuf(struct vc4_screen *screen, int fd, uint32_t winsys_stride)
371 {
372 uint32_t handle;
373 int ret = drmPrimeFDToHandle(screen->fd, fd, &handle);
374 int size;
375 if (ret) {
376 fprintf(stderr, "Failed to get vc4 handle for dmabuf %d\n", fd);
377 return NULL;
378 }
379
380 /* Determine the size of the bo we were handed. */
381 size = lseek(fd, 0, SEEK_END);
382 if (size == -1) {
383 fprintf(stderr, "Couldn't get size of dmabuf fd %d.\n", fd);
384 return NULL;
385 }
386
387 return vc4_bo_open_handle(screen, winsys_stride, handle, size);
388 }
389
390 int
391 vc4_bo_get_dmabuf(struct vc4_bo *bo)
392 {
393 int fd;
394 int ret = drmPrimeHandleToFD(bo->screen->fd, bo->handle,
395 O_CLOEXEC, &fd);
396 if (ret != 0) {
397 fprintf(stderr, "Failed to export gem bo %d to dmabuf\n",
398 bo->handle);
399 return -1;
400 }
401 bo->private = false;
402
403 return fd;
404 }
405
406 struct vc4_bo *
407 vc4_bo_alloc_shader(struct vc4_screen *screen, const void *data, uint32_t size)
408 {
409 struct vc4_bo *bo;
410 int ret;
411
412 bo = CALLOC_STRUCT(vc4_bo);
413 if (!bo)
414 return NULL;
415
416 pipe_reference_init(&bo->reference, 1);
417 bo->screen = screen;
418 bo->size = align(size, 4096);
419 bo->name = "code";
420 bo->private = false; /* Make sure it doesn't go back to the cache. */
421
422 if (!using_vc4_simulator) {
423 struct drm_vc4_create_shader_bo create = {
424 .size = size,
425 .data = (uintptr_t)data,
426 };
427
428 ret = drmIoctl(screen->fd, DRM_IOCTL_VC4_CREATE_SHADER_BO,
429 &create);
430 bo->handle = create.handle;
431 } else {
432 struct drm_mode_create_dumb create;
433 memset(&create, 0, sizeof(create));
434
435 create.width = 128;
436 create.bpp = 8;
437 create.height = (size + 127) / 128;
438
439 ret = drmIoctl(screen->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
440 bo->handle = create.handle;
441 assert(create.size >= size);
442
443 vc4_bo_map(bo);
444 memcpy(bo->map, data, size);
445 }
446 if (ret != 0) {
447 fprintf(stderr, "create shader ioctl failure\n");
448 abort();
449 }
450
451 screen->bo_count++;
452 screen->bo_size += bo->size;
453 if (dump_stats) {
454 fprintf(stderr, "Allocated shader %dkb:\n", bo->size / 1024);
455 vc4_bo_dump_stats(screen);
456 }
457
458 return bo;
459 }
460
461 bool
462 vc4_bo_flink(struct vc4_bo *bo, uint32_t *name)
463 {
464 struct drm_gem_flink flink = {
465 .handle = bo->handle,
466 };
467 int ret = drmIoctl(bo->screen->fd, DRM_IOCTL_GEM_FLINK, &flink);
468 if (ret) {
469 fprintf(stderr, "Failed to flink bo %d: %s\n",
470 bo->handle, strerror(errno));
471 free(bo);
472 return false;
473 }
474
475 bo->private = false;
476 *name = flink.name;
477
478 return true;
479 }
480
481 static int vc4_wait_seqno_ioctl(int fd, uint64_t seqno, uint64_t timeout_ns)
482 {
483 if (using_vc4_simulator)
484 return 0;
485
486 struct drm_vc4_wait_seqno wait = {
487 .seqno = seqno,
488 .timeout_ns = timeout_ns,
489 };
490 int ret = drmIoctl(fd, DRM_IOCTL_VC4_WAIT_SEQNO, &wait);
491 if (ret == -1)
492 return -errno;
493 else
494 return 0;
495
496 }
497
498 bool
499 vc4_wait_seqno(struct vc4_screen *screen, uint64_t seqno, uint64_t timeout_ns,
500 const char *reason)
501 {
502 if (screen->finished_seqno >= seqno)
503 return true;
504
505 if (unlikely(vc4_debug & VC4_DEBUG_PERF) && timeout_ns && reason) {
506 if (vc4_wait_seqno_ioctl(screen->fd, seqno, 0) == -ETIME) {
507 fprintf(stderr, "Blocking on seqno %lld for %s\n",
508 (long long)seqno, reason);
509 }
510 }
511
512 int ret = vc4_wait_seqno_ioctl(screen->fd, seqno, timeout_ns);
513 if (ret) {
514 if (ret != -ETIME) {
515 fprintf(stderr, "wait failed: %d\n", ret);
516 abort();
517 }
518
519 return false;
520 }
521
522 screen->finished_seqno = seqno;
523 return true;
524 }
525
526 static int vc4_wait_bo_ioctl(int fd, uint32_t handle, uint64_t timeout_ns)
527 {
528 if (using_vc4_simulator)
529 return 0;
530
531 struct drm_vc4_wait_bo wait = {
532 .handle = handle,
533 .timeout_ns = timeout_ns,
534 };
535 int ret = drmIoctl(fd, DRM_IOCTL_VC4_WAIT_BO, &wait);
536 if (ret == -1)
537 return -errno;
538 else
539 return 0;
540
541 }
542
543 bool
544 vc4_bo_wait(struct vc4_bo *bo, uint64_t timeout_ns, const char *reason)
545 {
546 struct vc4_screen *screen = bo->screen;
547
548 if (unlikely(vc4_debug & VC4_DEBUG_PERF) && timeout_ns && reason) {
549 if (vc4_wait_bo_ioctl(screen->fd, bo->handle, 0) == -ETIME) {
550 fprintf(stderr, "Blocking on %s BO for %s\n",
551 bo->name, reason);
552 }
553 }
554
555 int ret = vc4_wait_bo_ioctl(screen->fd, bo->handle, timeout_ns);
556 if (ret) {
557 if (ret != -ETIME) {
558 fprintf(stderr, "wait failed: %d\n", ret);
559 abort();
560 }
561
562 return false;
563 }
564
565 return true;
566 }
567
568 void *
569 vc4_bo_map_unsynchronized(struct vc4_bo *bo)
570 {
571 uint64_t offset;
572 int ret;
573
574 if (bo->map)
575 return bo->map;
576
577 if (!using_vc4_simulator) {
578 struct drm_vc4_mmap_bo map;
579 memset(&map, 0, sizeof(map));
580 map.handle = bo->handle;
581 ret = drmIoctl(bo->screen->fd, DRM_IOCTL_VC4_MMAP_BO, &map);
582 offset = map.offset;
583 } else {
584 struct drm_mode_map_dumb map;
585 memset(&map, 0, sizeof(map));
586 map.handle = bo->handle;
587 ret = drmIoctl(bo->screen->fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
588 offset = map.offset;
589 }
590 if (ret != 0) {
591 fprintf(stderr, "map ioctl failure\n");
592 abort();
593 }
594
595 bo->map = mmap(NULL, bo->size, PROT_READ | PROT_WRITE, MAP_SHARED,
596 bo->screen->fd, offset);
597 if (bo->map == MAP_FAILED) {
598 fprintf(stderr, "mmap of bo %d (offset 0x%016llx, size %d) failed\n",
599 bo->handle, (long long)offset, bo->size);
600 abort();
601 }
602
603 return bo->map;
604 }
605
606 void *
607 vc4_bo_map(struct vc4_bo *bo)
608 {
609 void *map = vc4_bo_map_unsynchronized(bo);
610
611 bool ok = vc4_bo_wait(bo, PIPE_TIMEOUT_INFINITE, "bo map");
612 if (!ok) {
613 fprintf(stderr, "BO wait for map failed\n");
614 abort();
615 }
616
617 return map;
618 }
619
620 void
621 vc4_bufmgr_destroy(struct pipe_screen *pscreen)
622 {
623 struct vc4_screen *screen = vc4_screen(pscreen);
624 struct vc4_bo_cache *cache = &screen->bo_cache;
625
626 vc4_bo_cache_free_all(cache);
627
628 if (dump_stats) {
629 fprintf(stderr, "BO stats after screen destroy:\n");
630 vc4_bo_dump_stats(screen);
631 }
632 }