diff options
Diffstat (limited to 'xorg-server/exa/exa_offscreen.c')
-rw-r--r-- | xorg-server/exa/exa_offscreen.c | 518 |
1 files changed, 518 insertions, 0 deletions
diff --git a/xorg-server/exa/exa_offscreen.c b/xorg-server/exa/exa_offscreen.c new file mode 100644 index 000000000..4aaa2c132 --- /dev/null +++ b/xorg-server/exa/exa_offscreen.c @@ -0,0 +1,518 @@ +/* + * Copyright © 2003 Anders Carlsson + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Anders Carlsson not be used in + * advertising or publicity pertaining to distribution of the software without + * specific, written prior permission. Anders Carlsson makes no + * representations about the suitability of this software for any purpose. It + * is provided "as is" without express or implied warranty. + * + * ANDERS CARLSSON DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL ANDERS CARLSSON BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/** @file + * This allocator allocates blocks of memory by maintaining a list of areas. + * When allocating, the contiguous block of areas with the minimum eviction + * cost is found and evicted in order to make room for the new allocation. + */ + +#include "exa_priv.h" + +#include <limits.h> +#include <assert.h> +#include <stdlib.h> + +#if DEBUG_OFFSCREEN +#define DBG_OFFSCREEN(a) ErrorF a +#else +#define DBG_OFFSCREEN(a) +#endif + +#if DEBUG_OFFSCREEN +static void +ExaOffscreenValidate (ScreenPtr pScreen) +{ + ExaScreenPriv (pScreen); + ExaOffscreenArea *prev = 0, *area; + + assert (pExaScr->info->offScreenAreas->base_offset == + pExaScr->info->offScreenBase); + for (area = pExaScr->info->offScreenAreas; area; area = area->next) + { + assert (area->offset >= area->base_offset && + area->offset < (area->base_offset + area->size)); + if (prev) + assert (prev->base_offset + prev->size == area->base_offset); + prev = area; + } + assert (prev->base_offset + prev->size == pExaScr->info->memorySize); +} +#else +#define ExaOffscreenValidate(s) +#endif + +static ExaOffscreenArea * +ExaOffscreenKickOut (ScreenPtr pScreen, ExaOffscreenArea *area) +{ + if (area->save) + (*area->save) (pScreen, area); + return exaOffscreenFree (pScreen, area); +} + +static void +exaUpdateEvictionCost(ExaOffscreenArea *area, unsigned offScreenCounter) +{ + unsigned age; + + if (area->state == ExaOffscreenAvail) + return; + + age = offScreenCounter - area->last_use; + + /* This is unlikely to happen, but could result in a division by zero... */ + if (age > (UINT_MAX / 2)) { + age = UINT_MAX / 2; + area->last_use = offScreenCounter - age; + } + + area->eviction_cost = area->size / age; +} + +static ExaOffscreenArea * +exaFindAreaToEvict(ExaScreenPrivPtr pExaScr, int size, int align) +{ + ExaOffscreenArea *begin, *end, *best; + unsigned cost, best_cost; + int avail, real_size, tmp; + + best_cost = UINT_MAX; + begin = end = pExaScr->info->offScreenAreas; + avail = 0; + cost = 0; + best = 0; + + while (end != NULL) + { + restart: + while (begin != NULL && begin->state == ExaOffscreenLocked) + begin = end = begin->next; + + if (begin == NULL) + break; + + /* adjust size needed to account for alignment loss for this area */ + real_size = size; + tmp = begin->base_offset % align; + if (tmp) + real_size += (align - tmp); + + while (avail < real_size && end != NULL) + { + if (end->state == ExaOffscreenLocked) { + /* Can't more room here, restart after this locked area */ + avail = 0; + cost = 0; + begin = end; + goto restart; + } + avail += end->size; + exaUpdateEvictionCost(end, pExaScr->offScreenCounter); + cost += end->eviction_cost; + end = end->next; + } + + /* Check the cost, update best */ + if (avail >= real_size && cost < best_cost) { + best = begin; + best_cost = cost; + } + + avail -= begin->size; + cost -= begin->eviction_cost; + begin = begin->next; + } + + return best; +} + +/** + * exaOffscreenAlloc allocates offscreen memory + * + * @param pScreen current screen + * @param size size in bytes of the allocation + * @param align byte alignment requirement for the offset of the allocated area + * @param locked whether the allocated area is locked and can't be kicked out + * @param save callback for when the area is evicted from memory + * @param privdata private data for the save callback. + * + * Allocates offscreen memory from the device associated with pScreen. size + * and align deteremine where and how large the allocated area is, and locked + * will mark whether it should be held in card memory. privdata may be any + * pointer for the save callback when the area is removed. + * + * Note that locked areas do get evicted on VT switch unless the driver + * requested version 2.1 or newer behavior. In that case, the save callback is + * still called. + */ +ExaOffscreenArea * +exaOffscreenAlloc (ScreenPtr pScreen, int size, int align, + Bool locked, + ExaOffscreenSaveProc save, + pointer privData) +{ + ExaOffscreenArea *area; + ExaScreenPriv (pScreen); + int tmp, real_size = 0; +#if DEBUG_OFFSCREEN + static int number = 0; + ErrorF("================= ============ allocating a new pixmap %d\n", ++number); +#endif + + ExaOffscreenValidate (pScreen); + if (!align) + align = 1; + + if (!size) + { + DBG_OFFSCREEN (("Alloc 0x%x -> EMPTY\n", size)); + return NULL; + } + + /* throw out requests that cannot fit */ + if (size > (pExaScr->info->memorySize - pExaScr->info->offScreenBase)) + { + DBG_OFFSCREEN (("Alloc 0x%x vs (0x%lx) -> TOBIG\n", size, + pExaScr->info->memorySize - + pExaScr->info->offScreenBase)); + return NULL; + } + + /* Try to find a free space that'll fit. */ + for (area = pExaScr->info->offScreenAreas; area; area = area->next) + { + /* skip allocated areas */ + if (area->state != ExaOffscreenAvail) + continue; + + /* adjust size to match alignment requirement */ + real_size = size; + tmp = area->base_offset % align; + if (tmp) + real_size += (align - tmp); + + /* does it fit? */ + if (real_size <= area->size) + break; + } + + if (!area) + { + area = exaFindAreaToEvict(pExaScr, size, align); + + if (!area) + { + DBG_OFFSCREEN (("Alloc 0x%x -> NOSPACE\n", size)); + /* Could not allocate memory */ + ExaOffscreenValidate (pScreen); + return NULL; + } + + /* adjust size needed to account for alignment loss for this area */ + real_size = size; + tmp = area->base_offset % align; + if (tmp) + real_size += (align - tmp); + + /* + * Kick out first area if in use + */ + if (area->state != ExaOffscreenAvail) + area = ExaOffscreenKickOut (pScreen, area); + /* + * Now get the system to merge the other needed areas together + */ + while (area->size < real_size) + { + assert (area->next && area->next->state == ExaOffscreenRemovable); + (void) ExaOffscreenKickOut (pScreen, area->next); + } + } + + /* save extra space in new area */ + if (real_size < area->size) + { + ExaOffscreenArea *new_area = xalloc (sizeof (ExaOffscreenArea)); + if (!new_area) + return NULL; + new_area->base_offset = area->base_offset + real_size; + new_area->offset = new_area->base_offset; + new_area->size = area->size - real_size; + new_area->state = ExaOffscreenAvail; + new_area->save = NULL; + new_area->last_use = 0; + new_area->eviction_cost = 0; + new_area->next = area->next; + area->next = new_area; + area->size = real_size; + } + /* + * Mark this area as in use + */ + if (locked) + area->state = ExaOffscreenLocked; + else + area->state = ExaOffscreenRemovable; + area->privData = privData; + area->save = save; + area->last_use = pExaScr->offScreenCounter++; + area->offset = (area->base_offset + align - 1); + area->offset -= area->offset % align; + + ExaOffscreenValidate (pScreen); + + DBG_OFFSCREEN (("Alloc 0x%x -> 0x%x (0x%x)\n", size, + area->base_offset, area->offset)); + return area; +} + +/** + * Ejects all offscreen areas, and uninitializes the offscreen memory manager. + */ +void +ExaOffscreenSwapOut (ScreenPtr pScreen) +{ + ExaScreenPriv (pScreen); + + ExaOffscreenValidate (pScreen); + /* loop until a single free area spans the space */ + for (;;) + { + ExaOffscreenArea *area = pExaScr->info->offScreenAreas; + + if (!area) + break; + if (area->state == ExaOffscreenAvail) + { + area = area->next; + if (!area) + break; + } + assert (area->state != ExaOffscreenAvail); + (void) ExaOffscreenKickOut (pScreen, area); + ExaOffscreenValidate (pScreen); + } + ExaOffscreenValidate (pScreen); + ExaOffscreenFini (pScreen); +} + +/** Ejects all pixmaps managed by EXA. */ +static void +ExaOffscreenEjectPixmaps (ScreenPtr pScreen) +{ + ExaScreenPriv (pScreen); + + ExaOffscreenValidate (pScreen); + /* loop until a single free area spans the space */ + for (;;) + { + ExaOffscreenArea *area; + + for (area = pExaScr->info->offScreenAreas; area != NULL; + area = area->next) + { + if (area->state == ExaOffscreenRemovable && + area->save == exaPixmapSave) + { + (void) ExaOffscreenKickOut (pScreen, area); + ExaOffscreenValidate (pScreen); + break; + } + } + if (area == NULL) + break; + } + ExaOffscreenValidate (pScreen); +} + +void +ExaOffscreenSwapIn (ScreenPtr pScreen) +{ + exaOffscreenInit (pScreen); +} + +/** + * Prepares EXA for disabling of FB access, or restoring it. + * + * In version 2.1, the disabling results in pixmaps being ejected, while other + * allocations remain. With this plus the prevention of migration while + * swappedOut is set, EXA by itself should not cause any access of the + * framebuffer to occur while swapped out. Any remaining issues are the + * responsibility of the driver. + * + * Prior to version 2.1, all allocations, including locked ones, are ejected + * when access is disabled, and the allocator is torn down while swappedOut + * is set. This is more drastic, and caused implementation difficulties for + * many drivers that could otherwise handle the lack of FB access while + * swapped out. + */ +void +exaEnableDisableFBAccess (int index, Bool enable) +{ + ScreenPtr pScreen = screenInfo.screens[index]; + ExaScreenPriv (pScreen); + + if (!enable && pExaScr->disableFbCount++ == 0) { + if (pExaScr->info->exa_minor < 1) + ExaOffscreenSwapOut (pScreen); + else + ExaOffscreenEjectPixmaps (pScreen); + pExaScr->swappedOut = TRUE; + } + + if (enable && --pExaScr->disableFbCount == 0) { + if (pExaScr->info->exa_minor < 1) + ExaOffscreenSwapIn (pScreen); + pExaScr->swappedOut = FALSE; + } +} + +/* merge the next free area into this one */ +static void +ExaOffscreenMerge (ExaOffscreenArea *area) +{ + ExaOffscreenArea *next = area->next; + + /* account for space */ + area->size += next->size; + /* frob pointer */ + area->next = next->next; + xfree (next); +} + +/** + * exaOffscreenFree frees an allocation. + * + * @param pScreen current screen + * @param area offscreen area to free + * + * exaOffscreenFree frees an allocation created by exaOffscreenAlloc. Note that + * the save callback of the area is not called, and it is up to the driver to + * do any cleanup necessary as a result. + * + * @return pointer to the newly freed area. This behavior should not be relied + * on. + */ +ExaOffscreenArea * +exaOffscreenFree (ScreenPtr pScreen, ExaOffscreenArea *area) +{ + ExaScreenPriv(pScreen); + ExaOffscreenArea *next = area->next; + ExaOffscreenArea *prev; + + DBG_OFFSCREEN (("Free 0x%x -> 0x%x (0x%x)\n", area->size, + area->base_offset, area->offset)); + ExaOffscreenValidate (pScreen); + + area->state = ExaOffscreenAvail; + area->save = NULL; + area->last_use = 0; + area->eviction_cost = 0; + /* + * Find previous area + */ + if (area == pExaScr->info->offScreenAreas) + prev = NULL; + else + for (prev = pExaScr->info->offScreenAreas; prev; prev = prev->next) + if (prev->next == area) + break; + + /* link with next area if free */ + if (next && next->state == ExaOffscreenAvail) + ExaOffscreenMerge (area); + + /* link with prev area if free */ + if (prev && prev->state == ExaOffscreenAvail) + { + area = prev; + ExaOffscreenMerge (area); + } + + ExaOffscreenValidate (pScreen); + DBG_OFFSCREEN(("\tdone freeing\n")); + return area; +} + +void +ExaOffscreenMarkUsed (PixmapPtr pPixmap) +{ + ExaPixmapPriv (pPixmap); + ExaScreenPriv (pPixmap->drawable.pScreen); + + if (!pExaPixmap || !pExaPixmap->area) + return; + + pExaPixmap->area->last_use = pExaScr->offScreenCounter++; +} + +/** + * exaOffscreenInit initializes the offscreen memory manager. + * + * @param pScreen current screen + * + * exaOffscreenInit is called by exaDriverInit to set up the memory manager for + * the screen, if any offscreen memory is available. + */ +Bool +exaOffscreenInit (ScreenPtr pScreen) +{ + ExaScreenPriv (pScreen); + ExaOffscreenArea *area; + + /* Allocate a big free area */ + area = xalloc (sizeof (ExaOffscreenArea)); + + if (!area) + return FALSE; + + area->state = ExaOffscreenAvail; + area->base_offset = pExaScr->info->offScreenBase; + area->offset = area->base_offset; + area->size = pExaScr->info->memorySize - area->base_offset; + area->save = NULL; + area->next = NULL; + area->last_use = 0; + area->eviction_cost = 0; + + /* Add it to the free areas */ + pExaScr->info->offScreenAreas = area; + pExaScr->offScreenCounter = 1; + + ExaOffscreenValidate (pScreen); + + return TRUE; +} + +void +ExaOffscreenFini (ScreenPtr pScreen) +{ + ExaScreenPriv (pScreen); + ExaOffscreenArea *area; + + /* just free all of the area records */ + while ((area = pExaScr->info->offScreenAreas)) + { + pExaScr->info->offScreenAreas = area->next; + xfree (area); + } +} |