1/*
2 * Copyright © 2003 Anders Carlsson
3 *
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that
7 * copyright notice and this permission notice appear in supporting
8 * documentation, and that the name of Anders Carlsson not be used in
9 * advertising or publicity pertaining to distribution of the software without
10 * specific, written prior permission.  Anders Carlsson makes no
11 * representations about the suitability of this software for any purpose.  It
12 * is provided "as is" without express or implied warranty.
13 *
14 * ANDERS CARLSSON DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
15 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
16 * EVENT SHALL ANDERS CARLSSON BE LIABLE FOR ANY SPECIAL, INDIRECT OR
17 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
18 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
19 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
20 * PERFORMANCE OF THIS SOFTWARE.
21 */
22
23/** @file
24 * This allocator allocates blocks of memory by maintaining a list of areas.
25 * When allocating, the contiguous block of areas with the minimum eviction
26 * cost is found and evicted in order to make room for the new allocation.
27 */
28
29#include "exa_priv.h"
30
31#include <limits.h>
32#include <assert.h>
33#include <stdlib.h>
34
35#if DEBUG_OFFSCREEN
36#define DBG_OFFSCREEN(a) ErrorF a
37#else
38#define DBG_OFFSCREEN(a)
39#endif
40
41#if DEBUG_OFFSCREEN
42static void
43ExaOffscreenValidate(ScreenPtr pScreen)
44{
45    ExaScreenPriv(pScreen);
46    ExaOffscreenArea *prev = 0, *area;
47
48    assert(pExaScr->info->offScreenAreas->base_offset ==
49           pExaScr->info->offScreenBase);
50    for (area = pExaScr->info->offScreenAreas; area; area = area->next) {
51        assert(area->offset >= area->base_offset);
52        assert(area->offset < (area->base_offset + area->size));
53        if (prev)
54            assert(prev->base_offset + prev->size == area->base_offset);
55        prev = area;
56    }
57    assert(prev->base_offset + prev->size == pExaScr->info->memorySize);
58}
59#else
60#define ExaOffscreenValidate(s)
61#endif
62
63static ExaOffscreenArea *
64ExaOffscreenKickOut(ScreenPtr pScreen, ExaOffscreenArea * area)
65{
66    if (area->save)
67        (*area->save) (pScreen, area);
68    return exaOffscreenFree(pScreen, area);
69}
70
71static void
72exaUpdateEvictionCost(ExaOffscreenArea * area, unsigned offScreenCounter)
73{
74    unsigned age;
75
76    if (area->state == ExaOffscreenAvail)
77        return;
78
79    age = offScreenCounter - area->last_use;
80
81    /* This is unlikely to happen, but could result in a division by zero... */
82    if (age > (UINT_MAX / 2)) {
83        age = UINT_MAX / 2;
84        area->last_use = offScreenCounter - age;
85    }
86
87    area->eviction_cost = area->size / age;
88}
89
90static ExaOffscreenArea *
91exaFindAreaToEvict(ExaScreenPrivPtr pExaScr, int size, int align)
92{
93    ExaOffscreenArea *begin, *end, *best;
94    unsigned cost, best_cost;
95    int avail, real_size;
96
97    best_cost = UINT_MAX;
98    begin = end = pExaScr->info->offScreenAreas;
99    avail = 0;
100    cost = 0;
101    best = 0;
102
103    while (end != NULL) {
104 restart:
105        while (begin != NULL && begin->state == ExaOffscreenLocked)
106            begin = end = begin->next;
107
108        if (begin == NULL)
109            break;
110
111        /* adjust size needed to account for alignment loss for this area */
112        real_size = size + (begin->base_offset + begin->size - size) % align;
113
114        while (avail < real_size && end != NULL) {
115            if (end->state == ExaOffscreenLocked) {
116                /* Can't more room here, restart after this locked area */
117                avail = 0;
118                cost = 0;
119                begin = end;
120                goto restart;
121            }
122            avail += end->size;
123            exaUpdateEvictionCost(end, pExaScr->offScreenCounter);
124            cost += end->eviction_cost;
125            end = end->next;
126        }
127
128        /* Check the cost, update best */
129        if (avail >= real_size && cost < best_cost) {
130            best = begin;
131            best_cost = cost;
132        }
133
134        avail -= begin->size;
135        cost -= begin->eviction_cost;
136        begin = begin->next;
137    }
138
139    return best;
140}
141
142/**
143 * exaOffscreenAlloc allocates offscreen memory
144 *
145 * @param pScreen current screen
146 * @param size size in bytes of the allocation
147 * @param align byte alignment requirement for the offset of the allocated area
148 * @param locked whether the allocated area is locked and can't be kicked out
149 * @param save callback for when the area is evicted from memory
150 * @param privdata private data for the save callback.
151 *
152 * Allocates offscreen memory from the device associated with pScreen.  size
153 * and align determine where and how large the allocated area is, and locked
154 * will mark whether it should be held in card memory.  privdata may be any
155 * pointer for the save callback when the area is removed.
156 *
157 * Note that locked areas do get evicted on VT switch unless the driver
158 * requested version 2.1 or newer behavior.  In that case, the save callback is
159 * still called.
160 */
161ExaOffscreenArea *
162exaOffscreenAlloc(ScreenPtr pScreen, int size, int align,
163                  Bool locked, ExaOffscreenSaveProc save, void *privData)
164{
165    ExaOffscreenArea *area;
166
167    ExaScreenPriv(pScreen);
168    int real_size = 0, largest_avail = 0;
169
170#if DEBUG_OFFSCREEN
171    static int number = 0;
172
173    ErrorF("================= ============ allocating a new pixmap %d\n",
174           ++number);
175#endif
176
177    ExaOffscreenValidate(pScreen);
178    if (!align)
179        align = 1;
180
181    if (!size) {
182        DBG_OFFSCREEN(("Alloc 0x%x -> EMPTY\n", size));
183        return NULL;
184    }
185
186    /* throw out requests that cannot fit */
187    if (size > (pExaScr->info->memorySize - pExaScr->info->offScreenBase)) {
188        DBG_OFFSCREEN(("Alloc 0x%x vs (0x%lx) -> TOBIG\n", size,
189                       pExaScr->info->memorySize -
190                       pExaScr->info->offScreenBase));
191        return NULL;
192    }
193
194    /* Try to find a free space that'll fit. */
195    for (area = pExaScr->info->offScreenAreas; area; area = area->next) {
196        /* skip allocated areas */
197        if (area->state != ExaOffscreenAvail)
198            continue;
199
200        /* adjust size to match alignment requirement */
201        real_size = size + (area->base_offset + area->size - size) % align;
202
203        /* does it fit? */
204        if (real_size <= area->size)
205            break;
206
207        if (area->size > largest_avail)
208            largest_avail = area->size;
209    }
210
211    if (!area) {
212        area = exaFindAreaToEvict(pExaScr, size, align);
213
214        if (!area) {
215            DBG_OFFSCREEN(("Alloc 0x%x -> NOSPACE\n", size));
216            /* Could not allocate memory */
217            ExaOffscreenValidate(pScreen);
218            return NULL;
219        }
220
221        /* adjust size needed to account for alignment loss for this area */
222        real_size = size + (area->base_offset + area->size - size) % align;
223
224        /*
225         * Kick out first area if in use
226         */
227        if (area->state != ExaOffscreenAvail)
228            area = ExaOffscreenKickOut(pScreen, area);
229        /*
230         * Now get the system to merge the other needed areas together
231         */
232        while (area->size < real_size) {
233            assert(area->next);
234            assert(area->next->state == ExaOffscreenRemovable);
235            (void) ExaOffscreenKickOut(pScreen, area->next);
236        }
237    }
238
239    /* save extra space in new area */
240    if (real_size < area->size) {
241        ExaOffscreenArea *new_area = malloc(sizeof(ExaOffscreenArea));
242
243        if (!new_area)
244            return NULL;
245        new_area->base_offset = area->base_offset;
246
247        new_area->offset = new_area->base_offset;
248        new_area->align = 0;
249        new_area->size = area->size - real_size;
250        new_area->state = ExaOffscreenAvail;
251        new_area->save = NULL;
252        new_area->last_use = 0;
253        new_area->eviction_cost = 0;
254        new_area->next = area;
255        new_area->prev = area->prev;
256        if (area->prev->next)
257            area->prev->next = new_area;
258        else
259            pExaScr->info->offScreenAreas = new_area;
260        area->prev = new_area;
261        area->base_offset = new_area->base_offset + new_area->size;
262        area->size = real_size;
263    }
264    else
265        pExaScr->numOffscreenAvailable--;
266
267    /*
268     * Mark this area as in use
269     */
270    if (locked)
271        area->state = ExaOffscreenLocked;
272    else
273        area->state = ExaOffscreenRemovable;
274    area->privData = privData;
275    area->save = save;
276    area->last_use = pExaScr->offScreenCounter++;
277    area->offset = (area->base_offset + align - 1);
278    area->offset -= area->offset % align;
279    area->align = align;
280
281    ExaOffscreenValidate(pScreen);
282
283    DBG_OFFSCREEN(("Alloc 0x%x -> 0x%x (0x%x)\n", size,
284                   area->base_offset, area->offset));
285    return area;
286}
287
288/**
289 * Ejects all offscreen areas, and uninitializes the offscreen memory manager.
290 */
291void
292ExaOffscreenSwapOut(ScreenPtr pScreen)
293{
294    ExaScreenPriv(pScreen);
295
296    ExaOffscreenValidate(pScreen);
297    /* loop until a single free area spans the space */
298    for (;;) {
299        ExaOffscreenArea *area = pExaScr->info->offScreenAreas;
300
301        if (!area)
302            break;
303        if (area->state == ExaOffscreenAvail) {
304            area = area->next;
305            if (!area)
306                break;
307        }
308        assert(area->state != ExaOffscreenAvail);
309        (void) ExaOffscreenKickOut(pScreen, area);
310        ExaOffscreenValidate(pScreen);
311    }
312    ExaOffscreenValidate(pScreen);
313    ExaOffscreenFini(pScreen);
314}
315
316/** Ejects all pixmaps managed by EXA. */
317static void
318ExaOffscreenEjectPixmaps(ScreenPtr pScreen)
319{
320    ExaScreenPriv(pScreen);
321
322    ExaOffscreenValidate(pScreen);
323    /* loop until a single free area spans the space */
324    for (;;) {
325        ExaOffscreenArea *area;
326
327        for (area = pExaScr->info->offScreenAreas; area != NULL;
328             area = area->next) {
329            if (area->state == ExaOffscreenRemovable &&
330                area->save == exaPixmapSave) {
331                (void) ExaOffscreenKickOut(pScreen, area);
332                ExaOffscreenValidate(pScreen);
333                break;
334            }
335        }
336        if (area == NULL)
337            break;
338    }
339    ExaOffscreenValidate(pScreen);
340}
341
342void
343ExaOffscreenSwapIn(ScreenPtr pScreen)
344{
345    exaOffscreenInit(pScreen);
346}
347
348/**
349 * Prepares EXA for disabling of FB access, or restoring it.
350 *
351 * In version 2.1, the disabling results in pixmaps being ejected, while other
352 * allocations remain.  With this plus the prevention of migration while
353 * swappedOut is set, EXA by itself should not cause any access of the
354 * framebuffer to occur while swapped out.  Any remaining issues are the
355 * responsibility of the driver.
356 *
357 * Prior to version 2.1, all allocations, including locked ones, are ejected
358 * when access is disabled, and the allocator is torn down while swappedOut
359 * is set.  This is more drastic, and caused implementation difficulties for
360 * many drivers that could otherwise handle the lack of FB access while
361 * swapped out.
362 */
363void
364exaEnableDisableFBAccess(ScreenPtr pScreen, Bool enable)
365{
366    ExaScreenPriv(pScreen);
367
368    if (pExaScr->info->flags & EXA_HANDLES_PIXMAPS)
369        return;
370
371    if (!enable && pExaScr->disableFbCount++ == 0) {
372        if (pExaScr->info->exa_minor < 1)
373            ExaOffscreenSwapOut(pScreen);
374        else
375            ExaOffscreenEjectPixmaps(pScreen);
376        pExaScr->swappedOut = TRUE;
377    }
378
379    if (enable && --pExaScr->disableFbCount == 0) {
380        if (pExaScr->info->exa_minor < 1)
381            ExaOffscreenSwapIn(pScreen);
382        pExaScr->swappedOut = FALSE;
383    }
384}
385
386/* merge the next free area into this one */
387static void
388ExaOffscreenMerge(ExaScreenPrivPtr pExaScr, ExaOffscreenArea * area)
389{
390    ExaOffscreenArea *next = area->next;
391
392    /* account for space */
393    area->size += next->size;
394    /* frob pointer */
395    area->next = next->next;
396    if (area->next)
397        area->next->prev = area;
398    else
399        pExaScr->info->offScreenAreas->prev = area;
400    free(next);
401
402    pExaScr->numOffscreenAvailable--;
403}
404
405/**
406 * exaOffscreenFree frees an allocation.
407 *
408 * @param pScreen current screen
409 * @param area offscreen area to free
410 *
411 * exaOffscreenFree frees an allocation created by exaOffscreenAlloc.  Note that
412 * the save callback of the area is not called, and it is up to the driver to
413 * do any cleanup necessary as a result.
414 *
415 * @return pointer to the newly freed area. This behavior should not be relied
416 * on.
417 */
418ExaOffscreenArea *
419exaOffscreenFree(ScreenPtr pScreen, ExaOffscreenArea * area)
420{
421    ExaScreenPriv(pScreen);
422    ExaOffscreenArea *next = area->next;
423    ExaOffscreenArea *prev;
424
425    DBG_OFFSCREEN(("Free 0x%x -> 0x%x (0x%x)\n", area->size,
426                   area->base_offset, area->offset));
427    ExaOffscreenValidate(pScreen);
428
429    area->state = ExaOffscreenAvail;
430    area->save = NULL;
431    area->last_use = 0;
432    area->eviction_cost = 0;
433    /*
434     * Find previous area
435     */
436    if (area == pExaScr->info->offScreenAreas)
437        prev = NULL;
438    else
439        prev = area->prev;
440
441    pExaScr->numOffscreenAvailable++;
442
443    /* link with next area if free */
444    if (next && next->state == ExaOffscreenAvail)
445        ExaOffscreenMerge(pExaScr, area);
446
447    /* link with prev area if free */
448    if (prev && prev->state == ExaOffscreenAvail) {
449        area = prev;
450        ExaOffscreenMerge(pExaScr, area);
451    }
452
453    ExaOffscreenValidate(pScreen);
454    DBG_OFFSCREEN(("\tdone freeing\n"));
455    return area;
456}
457
458void
459ExaOffscreenMarkUsed(PixmapPtr pPixmap)
460{
461    ExaPixmapPriv(pPixmap);
462    ExaScreenPriv(pPixmap->drawable.pScreen);
463
464    if (!pExaPixmap || !pExaPixmap->area)
465        return;
466
467    pExaPixmap->area->last_use = pExaScr->offScreenCounter++;
468}
469
470/**
471 * Defragment offscreen memory by compacting allocated areas at the end of it,
472 * leaving the total amount of memory available as a single area at the
473 * beginning (when there are no pinned allocations).
474 */
475_X_HIDDEN ExaOffscreenArea *
476ExaOffscreenDefragment(ScreenPtr pScreen)
477{
478    ExaScreenPriv(pScreen);
479    ExaOffscreenArea *area, *largest_available = NULL;
480    int largest_size = 0;
481    PixmapPtr pDstPix;
482    ExaPixmapPrivPtr pExaDstPix;
483
484    pDstPix = (*pScreen->CreatePixmap) (pScreen, 0, 0, 0, 0);
485
486    if (!pDstPix)
487        return NULL;
488
489    pExaDstPix = ExaGetPixmapPriv(pDstPix);
490    pExaDstPix->use_gpu_copy = TRUE;
491
492    for (area = pExaScr->info->offScreenAreas->prev;
493         area != pExaScr->info->offScreenAreas;) {
494        ExaOffscreenArea *prev = area->prev;
495        PixmapPtr pSrcPix;
496        ExaPixmapPrivPtr pExaSrcPix;
497        Bool save_use_gpu_copy;
498        int save_pitch;
499
500        if (area->state != ExaOffscreenAvail ||
501            prev->state == ExaOffscreenLocked ||
502            (prev->state == ExaOffscreenRemovable &&
503             prev->save != exaPixmapSave)) {
504            area = prev;
505            continue;
506        }
507
508        if (prev->state == ExaOffscreenAvail) {
509            if (area == largest_available) {
510                largest_available = prev;
511                largest_size += prev->size;
512            }
513            area = prev;
514            ExaOffscreenMerge(pExaScr, area);
515            continue;
516        }
517
518        if (area->size > largest_size) {
519            largest_available = area;
520            largest_size = area->size;
521        }
522
523        pSrcPix = prev->privData;
524        pExaSrcPix = ExaGetPixmapPriv(pSrcPix);
525
526        pExaDstPix->fb_ptr = pExaScr->info->memoryBase +
527            area->base_offset + area->size - prev->size + prev->base_offset -
528            prev->offset;
529        pExaDstPix->fb_ptr -= (unsigned long) pExaDstPix->fb_ptr % prev->align;
530
531        if (pExaDstPix->fb_ptr <= pExaSrcPix->fb_ptr) {
532            area = prev;
533            continue;
534        }
535
536        if (!(pExaScr->info->flags & EXA_SUPPORTS_OFFSCREEN_OVERLAPS) &&
537            (pExaSrcPix->fb_ptr + prev->size) > pExaDstPix->fb_ptr) {
538            area = prev;
539            continue;
540        }
541
542        save_use_gpu_copy = pExaSrcPix->use_gpu_copy;
543        save_pitch = pSrcPix->devKind;
544
545        pExaSrcPix->use_gpu_copy = TRUE;
546        pSrcPix->devKind = pExaSrcPix->fb_pitch;
547
548        pDstPix->drawable.width = pSrcPix->drawable.width;
549        pDstPix->devKind = pSrcPix->devKind;
550        pDstPix->drawable.height = pSrcPix->drawable.height;
551        pDstPix->drawable.depth = pSrcPix->drawable.depth;
552        pDstPix->drawable.bitsPerPixel = pSrcPix->drawable.bitsPerPixel;
553
554        if (!pExaScr->info->PrepareCopy(pSrcPix, pDstPix, -1, -1, GXcopy, ~0)) {
555            pExaSrcPix->use_gpu_copy = save_use_gpu_copy;
556            pSrcPix->devKind = save_pitch;
557            area = prev;
558            continue;
559        }
560
561        pExaScr->info->Copy(pDstPix, 0, 0, 0, 0, pDstPix->drawable.width,
562                            pDstPix->drawable.height);
563        pExaScr->info->DoneCopy(pDstPix);
564        exaMarkSync(pScreen);
565
566        DBG_OFFSCREEN(("Before swap: prev=0x%08x-0x%08x-0x%08x area=0x%08x-0x%08x-0x%08x\n", prev->base_offset, prev->offset, prev->base_offset + prev->size, area->base_offset, area->offset, area->base_offset + area->size));
567
568        /* Calculate swapped area offsets and sizes */
569        area->base_offset = prev->base_offset;
570        area->offset = area->base_offset;
571        prev->offset += pExaDstPix->fb_ptr - pExaSrcPix->fb_ptr;
572        assert(prev->offset >= pExaScr->info->offScreenBase);
573        assert(prev->offset < pExaScr->info->memorySize);
574        prev->base_offset = prev->offset;
575        if (area->next)
576            prev->size = area->next->base_offset - prev->base_offset;
577        else
578            prev->size = pExaScr->info->memorySize - prev->base_offset;
579        area->size = prev->base_offset - area->base_offset;
580
581        DBG_OFFSCREEN(("After swap: area=0x%08x-0x%08x-0x%08x prev=0x%08x-0x%08x-0x%08x\n", area->base_offset, area->offset, area->base_offset + area->size, prev->base_offset, prev->offset, prev->base_offset + prev->size));
582
583        /* Swap areas in list */
584        if (area->next)
585            area->next->prev = prev;
586        else
587            pExaScr->info->offScreenAreas->prev = prev;
588        if (prev->prev->next)
589            prev->prev->next = area;
590        else
591            pExaScr->info->offScreenAreas = area;
592        prev->next = area->next;
593        area->next = prev;
594        area->prev = prev->prev;
595        prev->prev = area;
596        if (!area->prev->next)
597            pExaScr->info->offScreenAreas = area;
598
599#if DEBUG_OFFSCREEN
600        if (prev->prev == prev || prev->next == prev)
601            ErrorF("Whoops, prev points to itself!\n");
602
603        if (area->prev == area || area->next == area)
604            ErrorF("Whoops, area points to itself!\n");
605#endif
606
607        pExaSrcPix->fb_ptr = pExaDstPix->fb_ptr;
608        pExaSrcPix->use_gpu_copy = save_use_gpu_copy;
609        pSrcPix->devKind = save_pitch;
610    }
611
612    pDstPix->drawable.width = 0;
613    pDstPix->drawable.height = 0;
614    pDstPix->drawable.depth = 0;
615    pDstPix->drawable.bitsPerPixel = 0;
616
617    (*pScreen->DestroyPixmap) (pDstPix);
618
619    if (area->state == ExaOffscreenAvail && area->size > largest_size)
620        return area;
621
622    return largest_available;
623}
624
625/**
626 * exaOffscreenInit initializes the offscreen memory manager.
627 *
628 * @param pScreen current screen
629 *
630 * exaOffscreenInit is called by exaDriverInit to set up the memory manager for
631 * the screen, if any offscreen memory is available.
632 */
633Bool
634exaOffscreenInit(ScreenPtr pScreen)
635{
636    ExaScreenPriv(pScreen);
637    ExaOffscreenArea *area;
638
639    /* Allocate a big free area */
640    area = malloc(sizeof(ExaOffscreenArea));
641
642    if (!area)
643        return FALSE;
644
645    area->state = ExaOffscreenAvail;
646    area->base_offset = pExaScr->info->offScreenBase;
647    area->offset = area->base_offset;
648    area->align = 0;
649    area->size = pExaScr->info->memorySize - area->base_offset;
650    area->save = NULL;
651    area->next = NULL;
652    area->prev = area;
653    area->last_use = 0;
654    area->eviction_cost = 0;
655
656    /* Add it to the free areas */
657    pExaScr->info->offScreenAreas = area;
658    pExaScr->offScreenCounter = 1;
659    pExaScr->numOffscreenAvailable = 1;
660
661    ExaOffscreenValidate(pScreen);
662
663    return TRUE;
664}
665
666void
667ExaOffscreenFini(ScreenPtr pScreen)
668{
669    ExaScreenPriv(pScreen);
670    ExaOffscreenArea *area;
671
672    /* just free all of the area records */
673    while ((area = pExaScr->info->offScreenAreas)) {
674        pExaScr->info->offScreenAreas = area->next;
675        free(area);
676    }
677}
678