1 /*
2 * Copyright © 2022 Google LLC
3 * SPDX-License-Identifier: MIT
4 */
5
6 /**
7 * Suballocator for space within BOs.
8 *
9 * BOs are allocated at PAGE_SIZE (typically 4k) granularity, so small
10 * allocations are a waste to have in their own BO. Moreover, on DRM we track a
11 * list of all BOs currently allocated and submit the whole list for validation
12 * (busy tracking and implicit sync) on every submit, and that validation is a
13 * non-trivial cost. So, being able to pack multiple allocations into a BO can
14 * be a significant performance win.
15 *
16 * The allocator tracks a current BO it is linearly allocating from, and up to
17 * one extra BO returned to the pool when all of its previous suballocations
18 * have been freed. This means that fragmentation can be an issue for
19 * default_size > PAGE_SIZE and small allocations. Also, excessive BO
20 * reallocation may happen for workloads where default size < working set size.
21 */
22
23 #include "tu_suballoc.h"
24
25 /* Initializes a BO sub-allocator using refcounts on BOs.
26 */
27 void
tu_bo_suballocator_init(struct tu_suballocator * suballoc,struct tu_device * dev,uint32_t default_size,enum tu_bo_alloc_flags flags,const char * name)28 tu_bo_suballocator_init(struct tu_suballocator *suballoc,
29 struct tu_device *dev,
30 uint32_t default_size,
31 enum tu_bo_alloc_flags flags,
32 const char *name)
33 {
34 suballoc->dev = dev;
35 suballoc->default_size = default_size;
36 suballoc->flags = flags;
37 suballoc->bo = NULL;
38 suballoc->cached_bo = NULL;
39 suballoc->name = name;
40 }
41
42 void
tu_bo_suballocator_finish(struct tu_suballocator * suballoc)43 tu_bo_suballocator_finish(struct tu_suballocator *suballoc)
44 {
45 if (suballoc->bo)
46 tu_bo_finish(suballoc->dev, suballoc->bo);
47 if (suballoc->cached_bo)
48 tu_bo_finish(suballoc->dev, suballoc->cached_bo);
49 }
50
51 VkResult
tu_suballoc_bo_alloc(struct tu_suballoc_bo * suballoc_bo,struct tu_suballocator * suballoc,uint32_t size,uint32_t alignment)52 tu_suballoc_bo_alloc(struct tu_suballoc_bo *suballoc_bo,
53 struct tu_suballocator *suballoc,
54 uint32_t size, uint32_t alignment)
55 {
56 struct tu_bo *bo = suballoc->bo;
57 if (bo) {
58 uint32_t offset = ALIGN(suballoc->next_offset, alignment);
59 if (offset + size <= bo->size) {
60 suballoc_bo->bo = tu_bo_get_ref(bo);
61 suballoc_bo->iova = bo->iova + offset;
62 suballoc_bo->size = size;
63
64 suballoc->next_offset = offset + size;
65 return VK_SUCCESS;
66 } else {
67 tu_bo_finish(suballoc->dev, bo);
68 suballoc->bo = NULL;
69 }
70 }
71
72 uint32_t alloc_size = MAX2(size, suballoc->default_size);
73
74 /* Reuse a recycled suballoc BO if we have one and it's big enough, otherwise free it. */
75 if (suballoc->cached_bo) {
76 if (alloc_size <= suballoc->cached_bo->size)
77 suballoc->bo = suballoc->cached_bo;
78 else
79 tu_bo_finish(suballoc->dev, suballoc->cached_bo);
80 suballoc->cached_bo = NULL;
81 }
82
83 /* Allocate the new BO if we didn't have one cached. */
84 if (!suballoc->bo) {
85 VkResult result = tu_bo_init_new(suballoc->dev, NULL,
86 &suballoc->bo, alloc_size,
87 suballoc->flags, suballoc->name);
88 if (result != VK_SUCCESS)
89 return result;
90 }
91
92 VkResult result = tu_bo_map(suballoc->dev, suballoc->bo, NULL);
93 if (result != VK_SUCCESS) {
94 tu_bo_finish(suballoc->dev, suballoc->bo);
95 return VK_ERROR_OUT_OF_HOST_MEMORY;
96 }
97
98 suballoc_bo->bo = tu_bo_get_ref(suballoc->bo);
99 suballoc_bo->iova = suballoc_bo->bo->iova;
100 suballoc_bo->size = size;
101 suballoc->next_offset = size;
102
103 return VK_SUCCESS;
104 }
105
106 void
tu_suballoc_bo_free(struct tu_suballocator * suballoc,struct tu_suballoc_bo * bo)107 tu_suballoc_bo_free(struct tu_suballocator *suballoc, struct tu_suballoc_bo *bo)
108 {
109 if (!bo->bo)
110 return;
111
112 /* If we we held the last reference to this BO, so just move it to the
113 * suballocator for the next time we need to allocate.
114 */
115 if (p_atomic_read(&bo->bo->refcnt) == 1 && !suballoc->cached_bo) {
116 suballoc->cached_bo = bo->bo;
117 return;
118 }
119
120 /* Otherwise, drop the refcount on it normally. */
121 tu_bo_finish(suballoc->dev, bo->bo);
122 }
123
124 void *
tu_suballoc_bo_map(struct tu_suballoc_bo * bo)125 tu_suballoc_bo_map(struct tu_suballoc_bo *bo)
126 {
127 return (char *)bo->bo->map + (bo->iova - bo->bo->iova);
128 }
129