108ca56c77788782f10e0c20316db5500d5a8f39
[dragonfly.git] / sys / dev / drm2 / ttm / ttm_memory.c
1 /**************************************************************************
2  *
3  * Copyright (c) 2006-2009 VMware, Inc., Palo Alto, CA., USA
4  * All Rights Reserved.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the
8  * "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sub license, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice (including the
15  * next paragraph) shall be included in all copies or substantial portions
16  * of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20  * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
21  * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
22  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
23  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
24  * USE OR OTHER DEALINGS IN THE SOFTWARE.
25  *
26  * $FreeBSD: head/sys/dev/drm2/ttm/ttm_memory.c 248663 2013-03-23 20:46:47Z dumbbell $
27  **************************************************************************/
28
29 #include <dev/drm2/drmP.h>
30 #include <dev/drm2/ttm/ttm_memory.h>
31 #include <dev/drm2/ttm/ttm_module.h>
32 #include <dev/drm2/ttm/ttm_page_alloc.h>
33
34 #define TTM_MEMORY_ALLOC_RETRIES 4
35
36 struct ttm_mem_zone {
37         u_int kobj_ref;
38         struct ttm_mem_global *glob;
39         const char *name;
40         uint64_t zone_mem;
41         uint64_t emer_mem;
42         uint64_t max_mem;
43         uint64_t swap_limit;
44         uint64_t used_mem;
45 };
46
47 MALLOC_DEFINE(M_TTM_ZONE, "ttm_zone", "TTM Zone");
48
49 static void ttm_mem_zone_kobj_release(struct ttm_mem_zone *zone)
50 {
51
52         kprintf("[TTM] Zone %7s: Used memory at exit: %llu kiB\n",
53                 zone->name, (unsigned long long)zone->used_mem >> 10);
54         drm_free(zone, M_TTM_ZONE);
55 }
56
57 #if 0
58 /* XXXKIB sysctl */
59 static ssize_t ttm_mem_zone_show(struct ttm_mem_zone *zone;
60                                  struct attribute *attr,
61                                  char *buffer)
62 {
63         uint64_t val = 0;
64
65         mtx_lock(&zone->glob->lock);
66         if (attr == &ttm_mem_sys)
67                 val = zone->zone_mem;
68         else if (attr == &ttm_mem_emer)
69                 val = zone->emer_mem;
70         else if (attr == &ttm_mem_max)
71                 val = zone->max_mem;
72         else if (attr == &ttm_mem_swap)
73                 val = zone->swap_limit;
74         else if (attr == &ttm_mem_used)
75                 val = zone->used_mem;
76         mtx_unlock(&zone->glob->lock);
77
78         return snprintf(buffer, PAGE_SIZE, "%llu\n",
79                         (unsigned long long) val >> 10);
80 }
81 #endif
82
83 static void ttm_check_swapping(struct ttm_mem_global *glob);
84
85 #if 0
86 /* XXXKIB sysctl */
87 static ssize_t ttm_mem_zone_store(struct ttm_mem_zone *zone,
88                                   struct attribute *attr,
89                                   const char *buffer,
90                                   size_t size)
91 {
92         int chars;
93         unsigned long val;
94         uint64_t val64;
95
96         chars = sscanf(buffer, "%lu", &val);
97         if (chars == 0)
98                 return size;
99
100         val64 = val;
101         val64 <<= 10;
102
103         mtx_lock(&zone->glob->lock);
104         if (val64 > zone->zone_mem)
105                 val64 = zone->zone_mem;
106         if (attr == &ttm_mem_emer) {
107                 zone->emer_mem = val64;
108                 if (zone->max_mem > val64)
109                         zone->max_mem = val64;
110         } else if (attr == &ttm_mem_max) {
111                 zone->max_mem = val64;
112                 if (zone->emer_mem < val64)
113                         zone->emer_mem = val64;
114         } else if (attr == &ttm_mem_swap)
115                 zone->swap_limit = val64;
116         mtx_unlock(&zone->glob->lock);
117
118         ttm_check_swapping(zone->glob);
119
120         return size;
121 }
122 #endif
123
124 static void ttm_mem_global_kobj_release(struct ttm_mem_global *glob)
125 {
126 }
127
128 static bool ttm_zones_above_swap_target(struct ttm_mem_global *glob,
129                                         bool from_wq, uint64_t extra)
130 {
131         unsigned int i;
132         struct ttm_mem_zone *zone;
133         uint64_t target;
134
135         for (i = 0; i < glob->num_zones; ++i) {
136                 zone = glob->zones[i];
137
138                 if (from_wq)
139                         target = zone->swap_limit;
140                 else if (priv_check(curthread, PRIV_VM_MLOCK) == 0)
141                         target = zone->emer_mem;
142                 else
143                         target = zone->max_mem;
144
145                 target = (extra > target) ? 0ULL : target;
146
147                 if (zone->used_mem > target)
148                         return true;
149         }
150         return false;
151 }
152
153 /**
154  * At this point we only support a single shrink callback.
155  * Extend this if needed, perhaps using a linked list of callbacks.
156  * Note that this function is reentrant:
157  * many threads may try to swap out at any given time.
158  */
159
160 static void ttm_shrink(struct ttm_mem_global *glob, bool from_wq,
161                        uint64_t extra)
162 {
163         int ret;
164         struct ttm_mem_shrink *shrink;
165
166         spin_lock(&glob->spin);
167         if (glob->shrink == NULL)
168                 goto out;
169
170         while (ttm_zones_above_swap_target(glob, from_wq, extra)) {
171                 shrink = glob->shrink;
172                 spin_lock(&glob->spin);
173                 ret = shrink->do_shrink(shrink);
174                 spin_unlock(&glob->spin);
175                 if (unlikely(ret != 0))
176                         goto out;
177         }
178 out:
179         spin_unlock(&glob->spin);
180 }
181
182
183
184 static void ttm_shrink_work(void *arg, int pending __unused)
185 {
186         struct ttm_mem_global *glob = arg;
187
188         ttm_shrink(glob, true, 0ULL);
189 }
190
191 static int ttm_mem_init_kernel_zone(struct ttm_mem_global *glob,
192     uint64_t mem)
193 {
194         struct ttm_mem_zone *zone;
195
196         zone = kmalloc(sizeof(*zone), M_TTM_ZONE, M_WAITOK | M_ZERO);
197
198         zone->name = "kernel";
199         zone->zone_mem = mem;
200         zone->max_mem = mem >> 1;
201         zone->emer_mem = (mem >> 1) + (mem >> 2);
202         zone->swap_limit = zone->max_mem - (mem >> 3);
203         zone->used_mem = 0;
204         zone->glob = glob;
205         glob->zone_kernel = zone;
206         refcount_init(&zone->kobj_ref, 1);
207         glob->zones[glob->num_zones++] = zone;
208         return 0;
209 }
210
211 static int ttm_mem_init_dma32_zone(struct ttm_mem_global *glob,
212     uint64_t mem)
213 {
214         struct ttm_mem_zone *zone;
215
216         zone = kmalloc(sizeof(*zone), M_TTM_ZONE, M_WAITOK | M_ZERO);
217
218         /**
219          * No special dma32 zone needed.
220          */
221
222         if (mem <= ((uint64_t) 1ULL << 32)) {
223                 drm_free(zone, M_TTM_ZONE);
224                 return 0;
225         }
226
227         /*
228          * Limit max dma32 memory to 4GB for now
229          * until we can figure out how big this
230          * zone really is.
231          */
232
233         mem = ((uint64_t) 1ULL << 32);
234         zone->name = "dma32";
235         zone->zone_mem = mem;
236         zone->max_mem = mem >> 1;
237         zone->emer_mem = (mem >> 1) + (mem >> 2);
238         zone->swap_limit = zone->max_mem - (mem >> 3);
239         zone->used_mem = 0;
240         zone->glob = glob;
241         glob->zone_dma32 = zone;
242         refcount_init(&zone->kobj_ref, 1);
243         glob->zones[glob->num_zones++] = zone;
244         return 0;
245 }
246
247 int ttm_mem_global_init(struct ttm_mem_global *glob)
248 {
249         u_int64_t mem;
250         int ret;
251         int i;
252         struct ttm_mem_zone *zone;
253
254         spin_init(&glob->spin);
255         glob->swap_queue = taskqueue_create("ttm_swap", M_WAITOK,
256             taskqueue_thread_enqueue, &glob->swap_queue);
257         taskqueue_start_threads(&glob->swap_queue, 1, PVM, "ttm swap");
258         TASK_INIT(&glob->work, 0, ttm_shrink_work, glob);
259
260         refcount_init(&glob->kobj_ref, 1);
261
262         mem = physmem * PAGE_SIZE;
263
264         ret = ttm_mem_init_kernel_zone(glob, mem);
265         if (unlikely(ret != 0))
266                 goto out_no_zone;
267         ret = ttm_mem_init_dma32_zone(glob, mem);
268         if (unlikely(ret != 0))
269                 goto out_no_zone;
270         for (i = 0; i < glob->num_zones; ++i) {
271                 zone = glob->zones[i];
272                 kprintf("[TTM] Zone %7s: Available graphics memory: %llu kiB\n",
273                         zone->name, (unsigned long long)zone->max_mem >> 10);
274         }
275         ttm_page_alloc_init(glob, glob->zone_kernel->max_mem/(2*PAGE_SIZE));
276         ttm_dma_page_alloc_init(glob, glob->zone_kernel->max_mem/(2*PAGE_SIZE));
277         return 0;
278 out_no_zone:
279         ttm_mem_global_release(glob);
280         return ret;
281 }
282
283 void ttm_mem_global_release(struct ttm_mem_global *glob)
284 {
285         unsigned int i;
286         struct ttm_mem_zone *zone;
287
288         /* let the page allocator first stop the shrink work. */
289         ttm_page_alloc_fini();
290         ttm_dma_page_alloc_fini();
291
292         taskqueue_drain(glob->swap_queue, &glob->work);
293         taskqueue_free(glob->swap_queue);
294         glob->swap_queue = NULL;
295         for (i = 0; i < glob->num_zones; ++i) {
296                 zone = glob->zones[i];
297                 if (refcount_release(&zone->kobj_ref))
298                         ttm_mem_zone_kobj_release(zone);
299         }
300         if (refcount_release(&glob->kobj_ref))
301                 ttm_mem_global_kobj_release(glob);
302 }
303
304 static void ttm_check_swapping(struct ttm_mem_global *glob)
305 {
306         bool needs_swapping = false;
307         unsigned int i;
308         struct ttm_mem_zone *zone;
309
310         spin_lock(&glob->spin);
311         for (i = 0; i < glob->num_zones; ++i) {
312                 zone = glob->zones[i];
313                 if (zone->used_mem > zone->swap_limit) {
314                         needs_swapping = true;
315                         break;
316                 }
317         }
318         spin_unlock(&glob->spin);
319
320         if (unlikely(needs_swapping))
321                 taskqueue_enqueue(glob->swap_queue, &glob->work);
322
323 }
324
325 static void ttm_mem_global_free_zone(struct ttm_mem_global *glob,
326                                      struct ttm_mem_zone *single_zone,
327                                      uint64_t amount)
328 {
329         unsigned int i;
330         struct ttm_mem_zone *zone;
331
332         spin_lock(&glob->spin);
333         for (i = 0; i < glob->num_zones; ++i) {
334                 zone = glob->zones[i];
335                 if (single_zone && zone != single_zone)
336                         continue;
337                 zone->used_mem -= amount;
338         }
339         spin_unlock(&glob->spin);
340 }
341
342 void ttm_mem_global_free(struct ttm_mem_global *glob,
343                          uint64_t amount)
344 {
345         return ttm_mem_global_free_zone(glob, NULL, amount);
346 }
347
348 static int ttm_mem_global_reserve(struct ttm_mem_global *glob,
349                                   struct ttm_mem_zone *single_zone,
350                                   uint64_t amount, bool reserve)
351 {
352         uint64_t limit;
353         int ret = -ENOMEM;
354         unsigned int i;
355         struct ttm_mem_zone *zone;
356
357         spin_lock(&glob->spin);
358         for (i = 0; i < glob->num_zones; ++i) {
359                 zone = glob->zones[i];
360                 if (single_zone && zone != single_zone)
361                         continue;
362
363                 limit = (priv_check(curthread, PRIV_VM_MLOCK) == 0) ?
364                         zone->emer_mem : zone->max_mem;
365
366                 if (zone->used_mem > limit)
367                         goto out_unlock;
368         }
369
370         if (reserve) {
371                 for (i = 0; i < glob->num_zones; ++i) {
372                         zone = glob->zones[i];
373                         if (single_zone && zone != single_zone)
374                                 continue;
375                         zone->used_mem += amount;
376                 }
377         }
378
379         ret = 0;
380 out_unlock:
381         spin_unlock(&glob->spin);
382         ttm_check_swapping(glob);
383
384         return ret;
385 }
386
387
388 static int ttm_mem_global_alloc_zone(struct ttm_mem_global *glob,
389                                      struct ttm_mem_zone *single_zone,
390                                      uint64_t memory,
391                                      bool no_wait, bool interruptible)
392 {
393         int count = TTM_MEMORY_ALLOC_RETRIES;
394
395         while (unlikely(ttm_mem_global_reserve(glob,
396                                                single_zone,
397                                                memory, true)
398                         != 0)) {
399                 if (no_wait)
400                         return -ENOMEM;
401                 if (unlikely(count-- == 0))
402                         return -ENOMEM;
403                 ttm_shrink(glob, false, memory + (memory >> 2) + 16);
404         }
405
406         return 0;
407 }
408
409 int ttm_mem_global_alloc(struct ttm_mem_global *glob, uint64_t memory,
410                          bool no_wait, bool interruptible)
411 {
412         /**
413          * Normal allocations of kernel memory are registered in
414          * all zones.
415          */
416
417         return ttm_mem_global_alloc_zone(glob, NULL, memory, no_wait,
418                                          interruptible);
419 }
420
421 #define page_to_pfn(pp) OFF_TO_IDX(VM_PAGE_TO_PHYS(pp))
422
423 int ttm_mem_global_alloc_page(struct ttm_mem_global *glob,
424                               struct vm_page *page,
425                               bool no_wait, bool interruptible)
426 {
427
428         struct ttm_mem_zone *zone = NULL;
429
430         /**
431          * Page allocations may be registed in a single zone
432          * only if highmem or !dma32.
433          */
434
435         if (glob->zone_dma32 && page_to_pfn(page) > 0x00100000UL)
436                 zone = glob->zone_kernel;
437         return ttm_mem_global_alloc_zone(glob, zone, PAGE_SIZE, no_wait,
438                                          interruptible);
439 }
440
441 void ttm_mem_global_free_page(struct ttm_mem_global *glob, struct vm_page *page)
442 {
443         struct ttm_mem_zone *zone = NULL;
444
445         if (glob->zone_dma32 && page_to_pfn(page) > 0x00100000UL)
446                 zone = glob->zone_kernel;
447         ttm_mem_global_free_zone(glob, zone, PAGE_SIZE);
448 }
449
450
451 size_t ttm_round_pot(size_t size)
452 {
453         if ((size & (size - 1)) == 0)
454                 return size;
455         else if (size > PAGE_SIZE)
456                 return PAGE_ALIGN(size);
457         else {
458                 size_t tmp_size = 4;
459
460                 while (tmp_size < size)
461                         tmp_size <<= 1;
462
463                 return tmp_size;
464         }
465         return 0;
466 }