1
2#include <inttypes.h>
3
4#include "util/u_inlines.h"
5#include "util/u_memory.h"
6#include "util/list.h"
7
8#include "nouveau_winsys.h"
9#include "nouveau_screen.h"
10#include "nouveau_mm.h"
11
12/* TODO: Higher orders can waste a lot of space for npot size buffers, should
13 * add an extra cache for such buffer objects.
14 *
15 * HACK: Max order == 21 to accommodate TF2's 1.5 MiB, frequently reallocated
16 * vertex buffer (VM flush (?) decreases performance dramatically).
17 */
18
19#define MM_MIN_ORDER 7 /* >= 6 to not violate ARB_map_buffer_alignment */
20#define MM_MAX_ORDER 21
21
22#define MM_NUM_BUCKETS (MM_MAX_ORDER - MM_MIN_ORDER + 1)
23
24#define MM_MIN_SIZE (1 << MM_MIN_ORDER)
25#define MM_MAX_SIZE (1 << MM_MAX_ORDER)
26
27struct mm_bucket {
28   struct list_head free;
29   struct list_head used;
30   struct list_head full;
31   int num_free;
32};
33
34struct nouveau_mman {
35   struct nouveau_device *dev;
36   struct mm_bucket bucket[MM_NUM_BUCKETS];
37   uint32_t domain;
38   union nouveau_bo_config config;
39   uint64_t allocated;
40};
41
42struct mm_slab {
43   struct list_head head;
44   struct nouveau_bo *bo;
45   struct nouveau_mman *cache;
46   int order;
47   int count;
48   int free;
49   uint32_t bits[0];
50};
51
52static int
53mm_slab_alloc(struct mm_slab *slab)
54{
55   int i, n, b;
56
57   if (slab->free == 0)
58      return -1;
59
60   for (i = 0; i < (slab->count + 31) / 32; ++i) {
61      b = ffs(slab->bits[i]) - 1;
62      if (b >= 0) {
63         n = i * 32 + b;
64         assert(n < slab->count);
65         slab->free--;
66         slab->bits[i] &= ~(1 << b);
67         return n;
68      }
69   }
70   return -1;
71}
72
73static inline void
74mm_slab_free(struct mm_slab *slab, int i)
75{
76   assert(i < slab->count);
77   slab->bits[i / 32] |= 1 << (i % 32);
78   slab->free++;
79   assert(slab->free <= slab->count);
80}
81
82static inline int
83mm_get_order(uint32_t size)
84{
85   int s = __builtin_clz(size) ^ 31;
86
87   if (size > (1 << s))
88      s += 1;
89   return s;
90}
91
92static struct mm_bucket *
93mm_bucket_by_order(struct nouveau_mman *cache, int order)
94{
95   if (order > MM_MAX_ORDER)
96      return NULL;
97   return &cache->bucket[MAX2(order, MM_MIN_ORDER) - MM_MIN_ORDER];
98}
99
100static struct mm_bucket *
101mm_bucket_by_size(struct nouveau_mman *cache, unsigned size)
102{
103   return mm_bucket_by_order(cache, mm_get_order(size));
104}
105
106/* size of bo allocation for slab with chunks of (1 << chunk_order) bytes */
107static inline uint32_t
108mm_default_slab_size(unsigned chunk_order)
109{
110   static const int8_t slab_order[MM_MAX_ORDER - MM_MIN_ORDER + 1] =
111   {
112      12, 12, 13, 14, 14, 17, 17, 17, 17, 19, 19, 20, 21, 22, 22
113   };
114
115   assert(chunk_order <= MM_MAX_ORDER && chunk_order >= MM_MIN_ORDER);
116
117   return 1 << slab_order[chunk_order - MM_MIN_ORDER];
118}
119
120static int
121mm_slab_new(struct nouveau_mman *cache, int chunk_order)
122{
123   struct mm_slab *slab;
124   int words, ret;
125   const uint32_t size = mm_default_slab_size(chunk_order);
126
127   words = ((size >> chunk_order) + 31) / 32;
128   assert(words);
129
130   slab = MALLOC(sizeof(struct mm_slab) + words * 4);
131   if (!slab)
132      return PIPE_ERROR_OUT_OF_MEMORY;
133
134   memset(&slab->bits[0], ~0, words * 4);
135
136   slab->bo = NULL;
137
138   ret = nouveau_bo_new(cache->dev, cache->domain, 0, size, &cache->config,
139                        &slab->bo);
140   if (ret) {
141      FREE(slab);
142      return PIPE_ERROR_OUT_OF_MEMORY;
143   }
144
145   LIST_INITHEAD(&slab->head);
146
147   slab->cache = cache;
148   slab->order = chunk_order;
149   slab->count = slab->free = size >> chunk_order;
150
151   LIST_ADD(&slab->head, &mm_bucket_by_order(cache, chunk_order)->free);
152
153   cache->allocated += size;
154
155   if (nouveau_mesa_debug)
156      debug_printf("MM: new slab, total memory = %"PRIu64" KiB\n",
157                   cache->allocated / 1024);
158
159   return PIPE_OK;
160}
161
162/* @return token to identify slab or NULL if we just allocated a new bo */
163struct nouveau_mm_allocation *
164nouveau_mm_allocate(struct nouveau_mman *cache,
165                    uint32_t size, struct nouveau_bo **bo, uint32_t *offset)
166{
167   struct mm_bucket *bucket;
168   struct mm_slab *slab;
169   struct nouveau_mm_allocation *alloc;
170   int ret;
171
172   bucket = mm_bucket_by_size(cache, size);
173   if (!bucket) {
174      ret = nouveau_bo_new(cache->dev, cache->domain, 0, size, &cache->config,
175                           bo);
176      if (ret)
177         debug_printf("bo_new(%x, %x): %i\n",
178                      size, cache->config.nv50.memtype, ret);
179
180      *offset = 0;
181      return NULL;
182   }
183
184   if (!LIST_IS_EMPTY(&bucket->used)) {
185      slab = LIST_ENTRY(struct mm_slab, bucket->used.next, head);
186   } else {
187      if (LIST_IS_EMPTY(&bucket->free)) {
188         mm_slab_new(cache, MAX2(mm_get_order(size), MM_MIN_ORDER));
189      }
190      slab = LIST_ENTRY(struct mm_slab, bucket->free.next, head);
191
192      LIST_DEL(&slab->head);
193      LIST_ADD(&slab->head, &bucket->used);
194   }
195
196   *offset = mm_slab_alloc(slab) << slab->order;
197
198   alloc = MALLOC_STRUCT(nouveau_mm_allocation);
199   if (!alloc)
200      return NULL;
201
202   nouveau_bo_ref(slab->bo, bo);
203
204   if (slab->free == 0) {
205      LIST_DEL(&slab->head);
206      LIST_ADD(&slab->head, &bucket->full);
207   }
208
209   alloc->next = NULL;
210   alloc->offset = *offset;
211   alloc->priv = (void *)slab;
212
213   return alloc;
214}
215
216void
217nouveau_mm_free(struct nouveau_mm_allocation *alloc)
218{
219   struct mm_slab *slab = (struct mm_slab *)alloc->priv;
220   struct mm_bucket *bucket = mm_bucket_by_order(slab->cache, slab->order);
221
222   mm_slab_free(slab, alloc->offset >> slab->order);
223
224   if (slab->free == slab->count) {
225      LIST_DEL(&slab->head);
226      LIST_ADDTAIL(&slab->head, &bucket->free);
227   } else
228   if (slab->free == 1) {
229      LIST_DEL(&slab->head);
230      LIST_ADDTAIL(&slab->head, &bucket->used);
231   }
232
233   FREE(alloc);
234}
235
236void
237nouveau_mm_free_work(void *data)
238{
239   nouveau_mm_free(data);
240}
241
242struct nouveau_mman *
243nouveau_mm_create(struct nouveau_device *dev, uint32_t domain,
244                  union nouveau_bo_config *config)
245{
246   struct nouveau_mman *cache = MALLOC_STRUCT(nouveau_mman);
247   int i;
248
249   if (!cache)
250      return NULL;
251
252   cache->dev = dev;
253   cache->domain = domain;
254   cache->config = *config;
255   cache->allocated = 0;
256
257   for (i = 0; i < MM_NUM_BUCKETS; ++i) {
258      LIST_INITHEAD(&cache->bucket[i].free);
259      LIST_INITHEAD(&cache->bucket[i].used);
260      LIST_INITHEAD(&cache->bucket[i].full);
261   }
262
263   return cache;
264}
265
266static inline void
267nouveau_mm_free_slabs(struct list_head *head)
268{
269   struct mm_slab *slab, *next;
270
271   LIST_FOR_EACH_ENTRY_SAFE(slab, next, head, head) {
272      LIST_DEL(&slab->head);
273      nouveau_bo_ref(NULL, &slab->bo);
274      FREE(slab);
275   }
276}
277
278void
279nouveau_mm_destroy(struct nouveau_mman *cache)
280{
281   int i;
282
283   if (!cache)
284      return;
285
286   for (i = 0; i < MM_NUM_BUCKETS; ++i) {
287      if (!LIST_IS_EMPTY(&cache->bucket[i].used) ||
288          !LIST_IS_EMPTY(&cache->bucket[i].full))
289         debug_printf("WARNING: destroying GPU memory cache "
290                      "with some buffers still in use\n");
291
292      nouveau_mm_free_slabs(&cache->bucket[i].free);
293      nouveau_mm_free_slabs(&cache->bucket[i].used);
294      nouveau_mm_free_slabs(&cache->bucket[i].full);
295   }
296
297   FREE(cache);
298}
299