• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright © 2014-2017 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_hash_table.h"
32 #include "util/u_memory.h"
33 #include "util/ralloc.h"
34 
35 #include "v3d_context.h"
36 #include "v3d_screen.h"
37 
38 static bool dump_stats = false;
39 
40 static void
41 v3d_bo_cache_free_all(struct v3d_bo_cache *cache);
42 
43 static void
v3d_bo_dump_stats(struct v3d_screen * screen)44 v3d_bo_dump_stats(struct v3d_screen *screen)
45 {
46         struct v3d_bo_cache *cache = &screen->bo_cache;
47 
48         uint32_t cache_count = 0;
49         uint32_t cache_size = 0;
50         list_for_each_entry(struct v3d_bo, bo, &cache->time_list, time_list) {
51                 cache_count++;
52                 cache_size += bo->size;
53         }
54 
55         fprintf(stderr, "  BOs allocated:   %d\n", screen->bo_count);
56         fprintf(stderr, "  BOs size:        %dkb\n", screen->bo_size / 1024);
57         fprintf(stderr, "  BOs cached:      %d\n", cache_count);
58         fprintf(stderr, "  BOs cached size: %dkb\n", cache_size / 1024);
59 
60         if (!list_is_empty(&cache->time_list)) {
61                 struct v3d_bo *first = list_first_entry(&cache->time_list,
62                                                         struct v3d_bo,
63                                                         time_list);
64                 struct v3d_bo *last = list_last_entry(&cache->time_list,
65                                                       struct v3d_bo,
66                                                       time_list);
67 
68                 fprintf(stderr, "  oldest cache time: %ld\n",
69                         (long)first->free_time);
70                 fprintf(stderr, "  newest cache time: %ld\n",
71                         (long)last->free_time);
72 
73                 struct timespec time;
74                 clock_gettime(CLOCK_MONOTONIC, &time);
75                 fprintf(stderr, "  now:               %jd\n",
76                         (intmax_t)time.tv_sec);
77         }
78 }
79 
80 static void
v3d_bo_remove_from_cache(struct v3d_bo_cache * cache,struct v3d_bo * bo)81 v3d_bo_remove_from_cache(struct v3d_bo_cache *cache, struct v3d_bo *bo)
82 {
83         list_del(&bo->time_list);
84         list_del(&bo->size_list);
85 }
86 
87 static struct v3d_bo *
v3d_bo_from_cache(struct v3d_screen * screen,uint32_t size,const char * name)88 v3d_bo_from_cache(struct v3d_screen *screen, uint32_t size, const char *name)
89 {
90         struct v3d_bo_cache *cache = &screen->bo_cache;
91         uint32_t page_index = size / 4096 - 1;
92 
93         if (cache->size_list_size <= page_index)
94                 return NULL;
95 
96         struct v3d_bo *bo = NULL;
97         mtx_lock(&cache->lock);
98         if (!list_is_empty(&cache->size_list[page_index])) {
99                 bo = list_first_entry(&cache->size_list[page_index],
100                                       struct v3d_bo, size_list);
101 
102                 /* Check that the BO has gone idle.  If not, then we want to
103                  * allocate something new instead, since we assume that the
104                  * user will proceed to CPU map it and fill it with stuff.
105                  */
106                 if (!v3d_bo_wait(bo, 0, NULL)) {
107                         mtx_unlock(&cache->lock);
108                         return NULL;
109                 }
110 
111                 pipe_reference_init(&bo->reference, 1);
112                 v3d_bo_remove_from_cache(cache, bo);
113 
114                 bo->name = name;
115         }
116         mtx_unlock(&cache->lock);
117         return bo;
118 }
119 
120 struct v3d_bo *
v3d_bo_alloc(struct v3d_screen * screen,uint32_t size,const char * name)121 v3d_bo_alloc(struct v3d_screen *screen, uint32_t size, const char *name)
122 {
123         struct v3d_bo *bo;
124         int ret;
125 
126         /* The CLIF dumping requires that there is no whitespace in the name.
127          */
128         assert(!strchr(name, ' '));
129 
130         size = align(size, 4096);
131 
132         bo = v3d_bo_from_cache(screen, size, name);
133         if (bo) {
134                 if (dump_stats) {
135                         fprintf(stderr, "Allocated %s %dkb from cache:\n",
136                                 name, size / 1024);
137                         v3d_bo_dump_stats(screen);
138                 }
139                 return bo;
140         }
141 
142         bo = CALLOC_STRUCT(v3d_bo);
143         if (!bo)
144                 return NULL;
145 
146         pipe_reference_init(&bo->reference, 1);
147         bo->screen = screen;
148         bo->size = size;
149         bo->name = name;
150         bo->private = true;
151 
152  retry:
153         ;
154 
155         bool cleared_and_retried = false;
156         struct drm_v3d_create_bo create = {
157                 .size = size
158         };
159 
160         ret = v3d_ioctl(screen->fd, DRM_IOCTL_V3D_CREATE_BO, &create);
161         bo->handle = create.handle;
162         bo->offset = create.offset;
163 
164         if (ret != 0) {
165                 if (!list_is_empty(&screen->bo_cache.time_list) &&
166                     !cleared_and_retried) {
167                         cleared_and_retried = true;
168                         v3d_bo_cache_free_all(&screen->bo_cache);
169                         goto retry;
170                 }
171 
172                 free(bo);
173                 return NULL;
174         }
175 
176         screen->bo_count++;
177         screen->bo_size += bo->size;
178         if (dump_stats) {
179                 fprintf(stderr, "Allocated %s %dkb:\n", name, size / 1024);
180                 v3d_bo_dump_stats(screen);
181         }
182 
183         return bo;
184 }
185 
186 void
v3d_bo_last_unreference(struct v3d_bo * bo)187 v3d_bo_last_unreference(struct v3d_bo *bo)
188 {
189         struct v3d_screen *screen = bo->screen;
190 
191         struct timespec time;
192         clock_gettime(CLOCK_MONOTONIC, &time);
193         mtx_lock(&screen->bo_cache.lock);
194         v3d_bo_last_unreference_locked_timed(bo, time.tv_sec);
195         mtx_unlock(&screen->bo_cache.lock);
196 }
197 
198 static void
v3d_bo_free(struct v3d_bo * bo)199 v3d_bo_free(struct v3d_bo *bo)
200 {
201         struct v3d_screen *screen = bo->screen;
202 
203         if (bo->map) {
204                 if (using_v3d_simulator && bo->name &&
205                     strcmp(bo->name, "winsys") == 0) {
206                         free(bo->map);
207                 } else {
208                         munmap(bo->map, bo->size);
209                         VG(VALGRIND_FREELIKE_BLOCK(bo->map, 0));
210                 }
211         }
212 
213         struct drm_gem_close c;
214         memset(&c, 0, sizeof(c));
215         c.handle = bo->handle;
216         int ret = v3d_ioctl(screen->fd, DRM_IOCTL_GEM_CLOSE, &c);
217         if (ret != 0)
218                 fprintf(stderr, "close object %d: %s\n", bo->handle, strerror(errno));
219 
220         screen->bo_count--;
221         screen->bo_size -= bo->size;
222 
223         if (dump_stats) {
224                 fprintf(stderr, "Freed %s%s%dkb:\n",
225                         bo->name ? bo->name : "",
226                         bo->name ? " " : "",
227                         bo->size / 1024);
228                 v3d_bo_dump_stats(screen);
229         }
230 
231         free(bo);
232 }
233 
234 static void
free_stale_bos(struct v3d_screen * screen,time_t time)235 free_stale_bos(struct v3d_screen *screen, time_t time)
236 {
237         struct v3d_bo_cache *cache = &screen->bo_cache;
238         bool freed_any = false;
239 
240         list_for_each_entry_safe(struct v3d_bo, bo, &cache->time_list,
241                                  time_list) {
242                 /* If it's more than a second old, free it. */
243                 if (time - bo->free_time > 2) {
244                         if (dump_stats && !freed_any) {
245                                 fprintf(stderr, "Freeing stale BOs:\n");
246                                 v3d_bo_dump_stats(screen);
247                                 freed_any = true;
248                         }
249                         v3d_bo_remove_from_cache(cache, bo);
250                         v3d_bo_free(bo);
251                 } else {
252                         break;
253                 }
254         }
255 
256         if (dump_stats && freed_any) {
257                 fprintf(stderr, "Freed stale BOs:\n");
258                 v3d_bo_dump_stats(screen);
259         }
260 }
261 
262 static void
v3d_bo_cache_free_all(struct v3d_bo_cache * cache)263 v3d_bo_cache_free_all(struct v3d_bo_cache *cache)
264 {
265         mtx_lock(&cache->lock);
266         list_for_each_entry_safe(struct v3d_bo, bo, &cache->time_list,
267                                  time_list) {
268                 v3d_bo_remove_from_cache(cache, bo);
269                 v3d_bo_free(bo);
270         }
271         mtx_unlock(&cache->lock);
272 }
273 
274 void
v3d_bo_last_unreference_locked_timed(struct v3d_bo * bo,time_t time)275 v3d_bo_last_unreference_locked_timed(struct v3d_bo *bo, time_t time)
276 {
277         struct v3d_screen *screen = bo->screen;
278         struct v3d_bo_cache *cache = &screen->bo_cache;
279         uint32_t page_index = bo->size / 4096 - 1;
280 
281         if (!bo->private) {
282                 v3d_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_is_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         if (dump_stats) {
315                 fprintf(stderr, "Freed %s %dkb to cache:\n",
316                         bo->name, bo->size / 1024);
317                 v3d_bo_dump_stats(screen);
318         }
319         bo->name = NULL;
320 
321         free_stale_bos(screen, time);
322 }
323 
324 static struct v3d_bo *
v3d_bo_open_handle(struct v3d_screen * screen,uint32_t handle,uint32_t size)325 v3d_bo_open_handle(struct v3d_screen *screen,
326                    uint32_t handle, uint32_t size)
327 {
328         struct v3d_bo *bo;
329 
330         /* Note: the caller is responsible for locking screen->bo_handles_mutex.
331          * This allows the lock to cover the actual BO import, avoiding a race.
332          */
333 
334         assert(size);
335 
336         bo = util_hash_table_get(screen->bo_handles, (void*)(uintptr_t)handle);
337         if (bo) {
338                 pipe_reference(NULL, &bo->reference);
339                 goto done;
340         }
341 
342         bo = CALLOC_STRUCT(v3d_bo);
343         pipe_reference_init(&bo->reference, 1);
344         bo->screen = screen;
345         bo->handle = handle;
346         bo->size = size;
347         bo->name = "winsys";
348         bo->private = false;
349 
350 #ifdef USE_V3D_SIMULATOR
351         v3d_simulator_open_from_handle(screen->fd, bo->handle, bo->size);
352         bo->map = malloc(bo->size);
353 #endif
354 
355         struct drm_v3d_get_bo_offset get = {
356                 .handle = handle,
357         };
358         int ret = v3d_ioctl(screen->fd, DRM_IOCTL_V3D_GET_BO_OFFSET, &get);
359         if (ret) {
360                 fprintf(stderr, "Failed to get BO offset: %s\n",
361                         strerror(errno));
362                 free(bo->map);
363                 free(bo);
364                 bo = NULL;
365                 goto done;
366         }
367         bo->offset = get.offset;
368         assert(bo->offset != 0);
369 
370         _mesa_hash_table_insert(screen->bo_handles, (void *)(uintptr_t)handle, bo);
371 
372         screen->bo_count++;
373         screen->bo_size += bo->size;
374 
375 done:
376         mtx_unlock(&screen->bo_handles_mutex);
377         return bo;
378 }
379 
380 struct v3d_bo *
v3d_bo_open_name(struct v3d_screen * screen,uint32_t name)381 v3d_bo_open_name(struct v3d_screen *screen, uint32_t name)
382 {
383         struct drm_gem_open o = {
384                 .name = name
385         };
386         mtx_lock(&screen->bo_handles_mutex);
387 
388         int ret = v3d_ioctl(screen->fd, DRM_IOCTL_GEM_OPEN, &o);
389         if (ret) {
390                 fprintf(stderr, "Failed to open bo %d: %s\n",
391                         name, strerror(errno));
392                 mtx_unlock(&screen->bo_handles_mutex);
393                 return NULL;
394         }
395 
396         return v3d_bo_open_handle(screen, o.handle, o.size);
397 }
398 
399 struct v3d_bo *
v3d_bo_open_dmabuf(struct v3d_screen * screen,int fd)400 v3d_bo_open_dmabuf(struct v3d_screen *screen, int fd)
401 {
402         uint32_t handle;
403 
404         mtx_lock(&screen->bo_handles_mutex);
405 
406         int ret = drmPrimeFDToHandle(screen->fd, fd, &handle);
407         int size;
408         if (ret) {
409                 fprintf(stderr, "Failed to get v3d handle for dmabuf %d\n", fd);
410                 mtx_unlock(&screen->bo_handles_mutex);
411                 return NULL;
412         }
413 
414         /* Determine the size of the bo we were handed. */
415         size = lseek(fd, 0, SEEK_END);
416         if (size == -1) {
417                 fprintf(stderr, "Couldn't get size of dmabuf fd %d.\n", fd);
418                 mtx_unlock(&screen->bo_handles_mutex);
419                 return NULL;
420         }
421 
422         return v3d_bo_open_handle(screen, handle, size);
423 }
424 
425 int
v3d_bo_get_dmabuf(struct v3d_bo * bo)426 v3d_bo_get_dmabuf(struct v3d_bo *bo)
427 {
428         int fd;
429         int ret = drmPrimeHandleToFD(bo->screen->fd, bo->handle,
430                                      O_CLOEXEC, &fd);
431         if (ret != 0) {
432                 fprintf(stderr, "Failed to export gem bo %d to dmabuf\n",
433                         bo->handle);
434                 return -1;
435         }
436 
437         mtx_lock(&bo->screen->bo_handles_mutex);
438         bo->private = false;
439         _mesa_hash_table_insert(bo->screen->bo_handles, (void *)(uintptr_t)bo->handle, bo);
440         mtx_unlock(&bo->screen->bo_handles_mutex);
441 
442         return fd;
443 }
444 
445 bool
v3d_bo_flink(struct v3d_bo * bo,uint32_t * name)446 v3d_bo_flink(struct v3d_bo *bo, uint32_t *name)
447 {
448         struct drm_gem_flink flink = {
449                 .handle = bo->handle,
450         };
451         int ret = v3d_ioctl(bo->screen->fd, DRM_IOCTL_GEM_FLINK, &flink);
452         if (ret) {
453                 fprintf(stderr, "Failed to flink bo %d: %s\n",
454                         bo->handle, strerror(errno));
455                 free(bo);
456                 return false;
457         }
458 
459         bo->private = false;
460         *name = flink.name;
461 
462         return true;
463 }
464 
v3d_wait_bo_ioctl(int fd,uint32_t handle,uint64_t timeout_ns)465 static int v3d_wait_bo_ioctl(int fd, uint32_t handle, uint64_t timeout_ns)
466 {
467         struct drm_v3d_wait_bo wait = {
468                 .handle = handle,
469                 .timeout_ns = timeout_ns,
470         };
471         int ret = v3d_ioctl(fd, DRM_IOCTL_V3D_WAIT_BO, &wait);
472         if (ret == -1)
473                 return -errno;
474         else
475                 return 0;
476 
477 }
478 
479 bool
v3d_bo_wait(struct v3d_bo * bo,uint64_t timeout_ns,const char * reason)480 v3d_bo_wait(struct v3d_bo *bo, uint64_t timeout_ns, const char *reason)
481 {
482         struct v3d_screen *screen = bo->screen;
483 
484         if (V3D_DBG(PERF) && timeout_ns && reason) {
485                 if (v3d_wait_bo_ioctl(screen->fd, bo->handle, 0) == -ETIME) {
486                         fprintf(stderr, "Blocking on %s BO for %s\n",
487                                 bo->name, reason);
488                 }
489         }
490 
491         int ret = v3d_wait_bo_ioctl(screen->fd, bo->handle, timeout_ns);
492         if (ret) {
493                 if (ret != -ETIME) {
494                         fprintf(stderr, "wait failed: %d\n", ret);
495                         abort();
496                 }
497 
498                 return false;
499         }
500 
501         return true;
502 }
503 
504 void *
v3d_bo_map_unsynchronized(struct v3d_bo * bo)505 v3d_bo_map_unsynchronized(struct v3d_bo *bo)
506 {
507         uint64_t offset;
508         int ret;
509 
510         if (bo->map)
511                 return bo->map;
512 
513         struct drm_v3d_mmap_bo map;
514         memset(&map, 0, sizeof(map));
515         map.handle = bo->handle;
516         ret = v3d_ioctl(bo->screen->fd, DRM_IOCTL_V3D_MMAP_BO, &map);
517         offset = map.offset;
518         if (ret != 0) {
519                 fprintf(stderr, "map ioctl failure\n");
520                 abort();
521         }
522 
523         bo->map = mmap(NULL, bo->size, PROT_READ | PROT_WRITE, MAP_SHARED,
524                        bo->screen->fd, offset);
525         if (bo->map == MAP_FAILED) {
526                 fprintf(stderr, "mmap of bo %d (offset 0x%016llx, size %d) failed\n",
527                         bo->handle, (long long)offset, bo->size);
528                 abort();
529         }
530         VG(VALGRIND_MALLOCLIKE_BLOCK(bo->map, bo->size, 0, false));
531 
532         return bo->map;
533 }
534 
535 void *
v3d_bo_map(struct v3d_bo * bo)536 v3d_bo_map(struct v3d_bo *bo)
537 {
538         void *map = v3d_bo_map_unsynchronized(bo);
539 
540         bool ok = v3d_bo_wait(bo, OS_TIMEOUT_INFINITE, "bo map");
541         if (!ok) {
542                 fprintf(stderr, "BO wait for map failed\n");
543                 abort();
544         }
545 
546         return map;
547 }
548 
549 void
v3d_bufmgr_destroy(struct pipe_screen * pscreen)550 v3d_bufmgr_destroy(struct pipe_screen *pscreen)
551 {
552         struct v3d_screen *screen = v3d_screen(pscreen);
553         struct v3d_bo_cache *cache = &screen->bo_cache;
554 
555         v3d_bo_cache_free_all(cache);
556 
557         if (dump_stats) {
558                 fprintf(stderr, "BO stats after screen destroy:\n");
559                 v3d_bo_dump_stats(screen);
560         }
561 }
562