Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mesa
Path: blob/21.2-virgl/src/broadcom/vulkan/v3dv_bo.c
4560 views
1
/*
2
* Copyright © 2019 Raspberry Pi
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 "v3dv_private.h"
25
26
#include <errno.h>
27
#include <sys/mman.h>
28
29
#include "drm-uapi/v3d_drm.h"
30
#include "util/u_memory.h"
31
32
/* Default max size of the bo cache, in MB.
33
*
34
* FIXME: we got this value when testing some apps using the rpi4 with 4GB,
35
* but it should depend on the total amount of RAM. But for that we would need
36
* to test on real hw with different amount of RAM. Using this value for now.
37
*/
38
#define DEFAULT_MAX_BO_CACHE_SIZE 512
39
40
/* Discarded to use a V3D_DEBUG for this, as it would mean adding a run-time
41
* check for most of the calls
42
*/
43
static const bool dump_stats = false;
44
45
static void
46
bo_dump_stats(struct v3dv_device *device)
47
{
48
struct v3dv_bo_cache *cache = &device->bo_cache;
49
50
fprintf(stderr, " BOs allocated: %d\n", device->bo_count);
51
fprintf(stderr, " BOs size: %dkb\n", device->bo_size / 1024);
52
fprintf(stderr, " BOs cached: %d\n", cache->cache_count);
53
fprintf(stderr, " BOs cached size: %dkb\n", cache->cache_size / 1024);
54
55
if (!list_is_empty(&cache->time_list)) {
56
struct v3dv_bo *first = list_first_entry(&cache->time_list,
57
struct v3dv_bo,
58
time_list);
59
struct v3dv_bo *last = list_last_entry(&cache->time_list,
60
struct v3dv_bo,
61
time_list);
62
63
fprintf(stderr, " oldest cache time: %ld\n",
64
(long)first->free_time);
65
fprintf(stderr, " newest cache time: %ld\n",
66
(long)last->free_time);
67
68
struct timespec time;
69
clock_gettime(CLOCK_MONOTONIC, &time);
70
fprintf(stderr, " now: %ld\n",
71
time.tv_sec);
72
}
73
74
if (cache->size_list_size) {
75
uint32_t empty_size_list = 0;
76
for (uint32_t i = 0; i < cache->size_list_size; i++) {
77
if (list_is_empty(&cache->size_list[i]))
78
empty_size_list++;
79
}
80
fprintf(stderr, " Empty size_list lists: %d\n", empty_size_list);
81
}
82
}
83
84
static void
85
bo_remove_from_cache(struct v3dv_bo_cache *cache, struct v3dv_bo *bo)
86
{
87
list_del(&bo->time_list);
88
list_del(&bo->size_list);
89
90
cache->cache_count--;
91
cache->cache_size -= bo->size;
92
}
93
94
static struct v3dv_bo *
95
bo_from_cache(struct v3dv_device *device, uint32_t size, const char *name)
96
{
97
struct v3dv_bo_cache *cache = &device->bo_cache;
98
uint32_t page_index = size / 4096 - 1;
99
100
if (cache->size_list_size <= page_index)
101
return NULL;
102
103
struct v3dv_bo *bo = NULL;
104
105
mtx_lock(&cache->lock);
106
if (!list_is_empty(&cache->size_list[page_index])) {
107
bo = list_first_entry(&cache->size_list[page_index],
108
struct v3dv_bo, size_list);
109
110
/* Check that the BO has gone idle. If not, then we want to
111
* allocate something new instead, since we assume that the
112
* user will proceed to CPU map it and fill it with stuff.
113
*/
114
if (!v3dv_bo_wait(device, bo, 0)) {
115
mtx_unlock(&cache->lock);
116
return NULL;
117
}
118
119
bo_remove_from_cache(cache, bo);
120
121
bo->name = name;
122
}
123
mtx_unlock(&cache->lock);
124
return bo;
125
}
126
127
static bool
128
bo_free(struct v3dv_device *device,
129
struct v3dv_bo *bo)
130
{
131
if (!bo)
132
return true;
133
134
if (bo->map)
135
v3dv_bo_unmap(device, bo);
136
137
struct drm_gem_close c;
138
memset(&c, 0, sizeof(c));
139
c.handle = bo->handle;
140
int ret = v3dv_ioctl(device->pdevice->render_fd, DRM_IOCTL_GEM_CLOSE, &c);
141
if (ret != 0)
142
fprintf(stderr, "close object %d: %s\n", bo->handle, strerror(errno));
143
144
device->bo_count--;
145
device->bo_size -= bo->size;
146
147
if (dump_stats) {
148
fprintf(stderr, "Freed %s%s%dkb:\n",
149
bo->name ? bo->name : "",
150
bo->name ? " " : "",
151
bo->size / 1024);
152
bo_dump_stats(device);
153
}
154
155
vk_free(&device->vk.alloc, bo);
156
157
return ret == 0;
158
}
159
160
static void
161
bo_cache_free_all(struct v3dv_device *device,
162
bool with_lock)
163
{
164
struct v3dv_bo_cache *cache = &device->bo_cache;
165
166
if (with_lock)
167
mtx_lock(&cache->lock);
168
list_for_each_entry_safe(struct v3dv_bo, bo, &cache->time_list,
169
time_list) {
170
bo_remove_from_cache(cache, bo);
171
bo_free(device, bo);
172
}
173
if (with_lock)
174
mtx_unlock(&cache->lock);
175
176
}
177
178
void
179
v3dv_bo_init(struct v3dv_bo *bo,
180
uint32_t handle,
181
uint32_t size,
182
uint32_t offset,
183
const char *name,
184
bool private)
185
{
186
bo->handle = handle;
187
bo->handle_bit = 1ull << (handle % 64);
188
bo->size = size;
189
bo->offset = offset;
190
bo->map = NULL;
191
bo->map_size = 0;
192
bo->name = name;
193
bo->private = private;
194
bo->dumb_handle = -1;
195
list_inithead(&bo->list_link);
196
}
197
198
struct v3dv_bo *
199
v3dv_bo_alloc(struct v3dv_device *device,
200
uint32_t size,
201
const char *name,
202
bool private)
203
{
204
struct v3dv_bo *bo;
205
206
const uint32_t page_align = 4096; /* Always allocate full pages */
207
size = align(size, page_align);
208
209
if (private) {
210
bo = bo_from_cache(device, size, name);
211
if (bo) {
212
if (dump_stats) {
213
fprintf(stderr, "Allocated %s %dkb from cache:\n",
214
name, size / 1024);
215
bo_dump_stats(device);
216
}
217
return bo;
218
}
219
}
220
221
bo = vk_alloc(&device->vk.alloc, sizeof(struct v3dv_bo), 8,
222
VK_SYSTEM_ALLOCATION_SCOPE_DEVICE);
223
224
if (!bo) {
225
fprintf(stderr, "Failed to allocate host memory for BO\n");
226
return NULL;
227
}
228
229
retry:
230
;
231
232
bool cleared_and_retried = false;
233
struct drm_v3d_create_bo create = {
234
.size = size
235
};
236
237
int ret = v3dv_ioctl(device->pdevice->render_fd,
238
DRM_IOCTL_V3D_CREATE_BO, &create);
239
if (ret != 0) {
240
if (!list_is_empty(&device->bo_cache.time_list) &&
241
!cleared_and_retried) {
242
cleared_and_retried = true;
243
bo_cache_free_all(device, true);
244
goto retry;
245
}
246
247
vk_free(&device->vk.alloc, bo);
248
fprintf(stderr, "Failed to allocate device memory for BO\n");
249
return NULL;
250
}
251
252
assert(create.offset % page_align == 0);
253
assert((create.offset & 0xffffffff) == create.offset);
254
255
v3dv_bo_init(bo, create.handle, size, create.offset, name, private);
256
257
device->bo_count++;
258
device->bo_size += bo->size;
259
if (dump_stats) {
260
fprintf(stderr, "Allocated %s %dkb:\n", name, size / 1024);
261
bo_dump_stats(device);
262
}
263
264
return bo;
265
}
266
267
bool
268
v3dv_bo_map_unsynchronized(struct v3dv_device *device,
269
struct v3dv_bo *bo,
270
uint32_t size)
271
{
272
assert(bo != NULL && size <= bo->size);
273
274
if (bo->map)
275
return bo->map;
276
277
struct drm_v3d_mmap_bo map;
278
memset(&map, 0, sizeof(map));
279
map.handle = bo->handle;
280
int ret = v3dv_ioctl(device->pdevice->render_fd,
281
DRM_IOCTL_V3D_MMAP_BO, &map);
282
if (ret != 0) {
283
fprintf(stderr, "map ioctl failure\n");
284
return false;
285
}
286
287
bo->map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED,
288
device->pdevice->render_fd, map.offset);
289
if (bo->map == MAP_FAILED) {
290
fprintf(stderr, "mmap of bo %d (offset 0x%016llx, size %d) failed\n",
291
bo->handle, (long long)map.offset, (uint32_t)bo->size);
292
return false;
293
}
294
VG(VALGRIND_MALLOCLIKE_BLOCK(bo->map, bo->size, 0, false));
295
296
bo->map_size = size;
297
298
return true;
299
}
300
301
bool
302
v3dv_bo_wait(struct v3dv_device *device,
303
struct v3dv_bo *bo,
304
uint64_t timeout_ns)
305
{
306
struct drm_v3d_wait_bo wait = {
307
.handle = bo->handle,
308
.timeout_ns = timeout_ns,
309
};
310
return v3dv_ioctl(device->pdevice->render_fd,
311
DRM_IOCTL_V3D_WAIT_BO, &wait) == 0;
312
}
313
314
bool
315
v3dv_bo_map(struct v3dv_device *device, struct v3dv_bo *bo, uint32_t size)
316
{
317
assert(bo && size <= bo->size);
318
319
bool ok = v3dv_bo_map_unsynchronized(device, bo, size);
320
if (!ok)
321
return false;
322
323
ok = v3dv_bo_wait(device, bo, PIPE_TIMEOUT_INFINITE);
324
if (!ok) {
325
fprintf(stderr, "memory wait for map failed\n");
326
return false;
327
}
328
329
return true;
330
}
331
332
void
333
v3dv_bo_unmap(struct v3dv_device *device, struct v3dv_bo *bo)
334
{
335
assert(bo && bo->map && bo->map_size > 0);
336
337
munmap(bo->map, bo->map_size);
338
VG(VALGRIND_FREELIKE_BLOCK(bo->map, 0));
339
bo->map = NULL;
340
bo->map_size = 0;
341
}
342
343
static boolean
344
reallocate_size_list(struct v3dv_bo_cache *cache,
345
struct v3dv_device *device,
346
uint32_t size)
347
{
348
struct list_head *new_list =
349
vk_alloc(&device->vk.alloc, sizeof(struct list_head) * size, 8,
350
VK_SYSTEM_ALLOCATION_SCOPE_DEVICE);
351
352
if (!new_list) {
353
fprintf(stderr, "Failed to allocate host memory for cache bo list\n");
354
return false;
355
}
356
struct list_head *old_list = cache->size_list;
357
358
/* Move old list contents over (since the array has moved, and
359
* therefore the pointers to the list heads have to change).
360
*/
361
for (int i = 0; i < cache->size_list_size; i++) {
362
struct list_head *old_head = &cache->size_list[i];
363
if (list_is_empty(old_head)) {
364
list_inithead(&new_list[i]);
365
} else {
366
new_list[i].next = old_head->next;
367
new_list[i].prev = old_head->prev;
368
new_list[i].next->prev = &new_list[i];
369
new_list[i].prev->next = &new_list[i];
370
}
371
}
372
for (int i = cache->size_list_size; i < size; i++)
373
list_inithead(&new_list[i]);
374
375
cache->size_list = new_list;
376
cache->size_list_size = size;
377
vk_free(&device->vk.alloc, old_list);
378
379
return true;
380
}
381
382
void
383
v3dv_bo_cache_init(struct v3dv_device *device)
384
{
385
device->bo_size = 0;
386
device->bo_count = 0;
387
list_inithead(&device->bo_cache.time_list);
388
/* FIXME: perhaps set a initial size for the size-list, to avoid run-time
389
* reallocations
390
*/
391
device->bo_cache.size_list_size = 0;
392
393
const char *max_cache_size_str = getenv("V3DV_MAX_BO_CACHE_SIZE");
394
if (max_cache_size_str == NULL)
395
device->bo_cache.max_cache_size = DEFAULT_MAX_BO_CACHE_SIZE;
396
else
397
device->bo_cache.max_cache_size = atoll(max_cache_size_str);
398
399
if (dump_stats) {
400
fprintf(stderr, "MAX BO CACHE SIZE: %iMB\n", device->bo_cache.max_cache_size);
401
}
402
403
device->bo_cache.max_cache_size *= 1024 * 1024;
404
device->bo_cache.cache_count = 0;
405
device->bo_cache.cache_size = 0;
406
}
407
408
void
409
v3dv_bo_cache_destroy(struct v3dv_device *device)
410
{
411
bo_cache_free_all(device, true);
412
vk_free(&device->vk.alloc, device->bo_cache.size_list);
413
414
if (dump_stats) {
415
fprintf(stderr, "BO stats after screen destroy:\n");
416
bo_dump_stats(device);
417
}
418
}
419
420
421
static void
422
free_stale_bos(struct v3dv_device *device,
423
time_t time)
424
{
425
struct v3dv_bo_cache *cache = &device->bo_cache;
426
bool freed_any = false;
427
428
list_for_each_entry_safe(struct v3dv_bo, bo, &cache->time_list,
429
time_list) {
430
/* If it's more than a second old, free it. */
431
if (time - bo->free_time > 2) {
432
if (dump_stats && !freed_any) {
433
fprintf(stderr, "Freeing stale BOs:\n");
434
bo_dump_stats(device);
435
freed_any = true;
436
}
437
438
bo_remove_from_cache(cache, bo);
439
bo_free(device, bo);
440
} else {
441
break;
442
}
443
}
444
445
if (dump_stats && freed_any) {
446
fprintf(stderr, "Freed stale BOs:\n");
447
bo_dump_stats(device);
448
}
449
}
450
451
bool
452
v3dv_bo_free(struct v3dv_device *device,
453
struct v3dv_bo *bo)
454
{
455
if (!bo)
456
return true;
457
458
struct timespec time;
459
struct v3dv_bo_cache *cache = &device->bo_cache;
460
uint32_t page_index = bo->size / 4096 - 1;
461
462
if (bo->private &&
463
bo->size > cache->max_cache_size - cache->cache_size) {
464
clock_gettime(CLOCK_MONOTONIC, &time);
465
mtx_lock(&cache->lock);
466
free_stale_bos(device, time.tv_sec);
467
mtx_unlock(&cache->lock);
468
}
469
470
if (!bo->private ||
471
bo->size > cache->max_cache_size - cache->cache_size) {
472
return bo_free(device, bo);
473
}
474
475
clock_gettime(CLOCK_MONOTONIC, &time);
476
mtx_lock(&cache->lock);
477
478
if (cache->size_list_size <= page_index) {
479
if (!reallocate_size_list(cache, device, page_index + 1)) {
480
bool outcome = bo_free(device, bo);
481
/* If the reallocation failed, it usually means that we are out of
482
* memory, so we also free all the bo cache. We need to call it to
483
* not use the cache lock, as we are already under it.
484
*/
485
bo_cache_free_all(device, false);
486
mtx_unlock(&cache->lock);
487
return outcome;
488
}
489
}
490
491
bo->free_time = time.tv_sec;
492
list_addtail(&bo->size_list, &cache->size_list[page_index]);
493
list_addtail(&bo->time_list, &cache->time_list);
494
495
cache->cache_count++;
496
cache->cache_size += bo->size;
497
498
if (dump_stats) {
499
fprintf(stderr, "Freed %s %dkb to cache:\n",
500
bo->name, bo->size / 1024);
501
bo_dump_stats(device);
502
}
503
bo->name = NULL;
504
505
free_stale_bos(device, time.tv_sec);
506
507
mtx_unlock(&cache->lock);
508
509
return true;
510
}
511
512