exa_offscreen.c revision 4642e01f
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 { 52 assert (area->offset >= area->base_offset && 53 area->offset < (area->base_offset + area->size)); 54 if (prev) 55 assert (prev->base_offset + prev->size == area->base_offset); 56 prev = area; 57 } 58 assert (prev->base_offset + prev->size == pExaScr->info->memorySize); 59} 60#else 61#define ExaOffscreenValidate(s) 62#endif 63 64static ExaOffscreenArea * 65ExaOffscreenKickOut (ScreenPtr pScreen, ExaOffscreenArea *area) 66{ 67 if (area->save) 68 (*area->save) (pScreen, area); 69 return exaOffscreenFree (pScreen, area); 70} 71 72static void 73exaUpdateEvictionCost(ExaOffscreenArea *area, unsigned offScreenCounter) 74{ 75 unsigned age; 76 77 if (area->state == ExaOffscreenAvail) 78 return; 79 80 age = offScreenCounter - area->last_use; 81 82 /* This is unlikely to happen, but could result in a division by zero... */ 83 if (age > (UINT_MAX / 2)) { 84 age = UINT_MAX / 2; 85 area->last_use = offScreenCounter - age; 86 } 87 88 area->eviction_cost = area->size / age; 89} 90 91static ExaOffscreenArea * 92exaFindAreaToEvict(ExaScreenPrivPtr pExaScr, int size, int align) 93{ 94 ExaOffscreenArea *begin, *end, *best; 95 unsigned cost, best_cost; 96 int avail, real_size, tmp; 97 98 best_cost = UINT_MAX; 99 begin = end = pExaScr->info->offScreenAreas; 100 avail = 0; 101 cost = 0; 102 best = 0; 103 104 while (end != NULL) 105 { 106 restart: 107 while (begin != NULL && begin->state == ExaOffscreenLocked) 108 begin = end = begin->next; 109 110 if (begin == NULL) 111 break; 112 113 /* adjust size needed to account for alignment loss for this area */ 114 real_size = size; 115 tmp = begin->base_offset % align; 116 if (tmp) 117 real_size += (align - tmp); 118 119 while (avail < real_size && end != NULL) 120 { 121 if (end->state == ExaOffscreenLocked) { 122 /* Can't more room here, restart after this locked area */ 123 avail = 0; 124 cost = 0; 125 begin = end; 126 goto restart; 127 } 128 avail += end->size; 129 exaUpdateEvictionCost(end, pExaScr->offScreenCounter); 130 cost += end->eviction_cost; 131 end = end->next; 132 } 133 134 /* Check the cost, update best */ 135 if (avail >= real_size && cost < best_cost) { 136 best = begin; 137 best_cost = cost; 138 } 139 140 avail -= begin->size; 141 cost -= begin->eviction_cost; 142 begin = begin->next; 143 } 144 145 return best; 146} 147 148/** 149 * exaOffscreenAlloc allocates offscreen memory 150 * 151 * @param pScreen current screen 152 * @param size size in bytes of the allocation 153 * @param align byte alignment requirement for the offset of the allocated area 154 * @param locked whether the allocated area is locked and can't be kicked out 155 * @param save callback for when the area is evicted from memory 156 * @param privdata private data for the save callback. 157 * 158 * Allocates offscreen memory from the device associated with pScreen. size 159 * and align deteremine where and how large the allocated area is, and locked 160 * will mark whether it should be held in card memory. privdata may be any 161 * pointer for the save callback when the area is removed. 162 * 163 * Note that locked areas do get evicted on VT switch unless the driver 164 * requested version 2.1 or newer behavior. In that case, the save callback is 165 * still called. 166 */ 167ExaOffscreenArea * 168exaOffscreenAlloc (ScreenPtr pScreen, int size, int align, 169 Bool locked, 170 ExaOffscreenSaveProc save, 171 pointer privData) 172{ 173 ExaOffscreenArea *area; 174 ExaScreenPriv (pScreen); 175 int tmp, real_size = 0; 176#if DEBUG_OFFSCREEN 177 static int number = 0; 178 ErrorF("================= ============ allocating a new pixmap %d\n", ++number); 179#endif 180 181 ExaOffscreenValidate (pScreen); 182 if (!align) 183 align = 1; 184 185 if (!size) 186 { 187 DBG_OFFSCREEN (("Alloc 0x%x -> EMPTY\n", size)); 188 return NULL; 189 } 190 191 /* throw out requests that cannot fit */ 192 if (size > (pExaScr->info->memorySize - pExaScr->info->offScreenBase)) 193 { 194 DBG_OFFSCREEN (("Alloc 0x%x vs (0x%lx) -> TOBIG\n", size, 195 pExaScr->info->memorySize - 196 pExaScr->info->offScreenBase)); 197 return NULL; 198 } 199 200 /* Try to find a free space that'll fit. */ 201 for (area = pExaScr->info->offScreenAreas; area; area = area->next) 202 { 203 /* skip allocated areas */ 204 if (area->state != ExaOffscreenAvail) 205 continue; 206 207 /* adjust size to match alignment requirement */ 208 real_size = size; 209 tmp = area->base_offset % align; 210 if (tmp) 211 real_size += (align - tmp); 212 213 /* does it fit? */ 214 if (real_size <= area->size) 215 break; 216 } 217 218 if (!area) 219 { 220 area = exaFindAreaToEvict(pExaScr, size, align); 221 222 if (!area) 223 { 224 DBG_OFFSCREEN (("Alloc 0x%x -> NOSPACE\n", size)); 225 /* Could not allocate memory */ 226 ExaOffscreenValidate (pScreen); 227 return NULL; 228 } 229 230 /* adjust size needed to account for alignment loss for this area */ 231 real_size = size; 232 tmp = area->base_offset % align; 233 if (tmp) 234 real_size += (align - tmp); 235 236 /* 237 * Kick out first area if in use 238 */ 239 if (area->state != ExaOffscreenAvail) 240 area = ExaOffscreenKickOut (pScreen, area); 241 /* 242 * Now get the system to merge the other needed areas together 243 */ 244 while (area->size < real_size) 245 { 246 assert (area->next && area->next->state == ExaOffscreenRemovable); 247 (void) ExaOffscreenKickOut (pScreen, area->next); 248 } 249 } 250 251 /* save extra space in new area */ 252 if (real_size < area->size) 253 { 254 ExaOffscreenArea *new_area = xalloc (sizeof (ExaOffscreenArea)); 255 if (!new_area) 256 return NULL; 257 new_area->base_offset = area->base_offset + real_size; 258 new_area->offset = new_area->base_offset; 259 new_area->size = area->size - real_size; 260 new_area->state = ExaOffscreenAvail; 261 new_area->save = NULL; 262 new_area->last_use = 0; 263 new_area->eviction_cost = 0; 264 new_area->next = area->next; 265 area->next = new_area; 266 area->size = real_size; 267 } 268 /* 269 * Mark this area as in use 270 */ 271 if (locked) 272 area->state = ExaOffscreenLocked; 273 else 274 area->state = ExaOffscreenRemovable; 275 area->privData = privData; 276 area->save = save; 277 area->last_use = pExaScr->offScreenCounter++; 278 area->offset = (area->base_offset + align - 1); 279 area->offset -= area->offset % 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 { 300 ExaOffscreenArea *area = pExaScr->info->offScreenAreas; 301 302 if (!area) 303 break; 304 if (area->state == ExaOffscreenAvail) 305 { 306 area = area->next; 307 if (!area) 308 break; 309 } 310 assert (area->state != ExaOffscreenAvail); 311 (void) ExaOffscreenKickOut (pScreen, area); 312 ExaOffscreenValidate (pScreen); 313 } 314 ExaOffscreenValidate (pScreen); 315 ExaOffscreenFini (pScreen); 316} 317 318/** Ejects all pixmaps managed by EXA. */ 319static void 320ExaOffscreenEjectPixmaps (ScreenPtr pScreen) 321{ 322 ExaScreenPriv (pScreen); 323 324 ExaOffscreenValidate (pScreen); 325 /* loop until a single free area spans the space */ 326 for (;;) 327 { 328 ExaOffscreenArea *area; 329 330 for (area = pExaScr->info->offScreenAreas; area != NULL; 331 area = area->next) 332 { 333 if (area->state == ExaOffscreenRemovable && 334 area->save == exaPixmapSave) 335 { 336 (void) ExaOffscreenKickOut (pScreen, area); 337 ExaOffscreenValidate (pScreen); 338 break; 339 } 340 } 341 if (area == NULL) 342 break; 343 } 344 ExaOffscreenValidate (pScreen); 345} 346 347void 348ExaOffscreenSwapIn (ScreenPtr pScreen) 349{ 350 exaOffscreenInit (pScreen); 351} 352 353/** 354 * Prepares EXA for disabling of FB access, or restoring it. 355 * 356 * In version 2.1, the disabling results in pixmaps being ejected, while other 357 * allocations remain. With this plus the prevention of migration while 358 * swappedOut is set, EXA by itself should not cause any access of the 359 * framebuffer to occur while swapped out. Any remaining issues are the 360 * responsibility of the driver. 361 * 362 * Prior to version 2.1, all allocations, including locked ones, are ejected 363 * when access is disabled, and the allocator is torn down while swappedOut 364 * is set. This is more drastic, and caused implementation difficulties for 365 * many drivers that could otherwise handle the lack of FB access while 366 * swapped out. 367 */ 368void 369exaEnableDisableFBAccess (int index, Bool enable) 370{ 371 ScreenPtr pScreen = screenInfo.screens[index]; 372 ExaScreenPriv (pScreen); 373 374 if (!enable && pExaScr->disableFbCount++ == 0) { 375 if (pExaScr->info->exa_minor < 1) 376 ExaOffscreenSwapOut (pScreen); 377 else 378 ExaOffscreenEjectPixmaps (pScreen); 379 pExaScr->swappedOut = TRUE; 380 } 381 382 if (enable && --pExaScr->disableFbCount == 0) { 383 if (pExaScr->info->exa_minor < 1) 384 ExaOffscreenSwapIn (pScreen); 385 pExaScr->swappedOut = FALSE; 386 } 387} 388 389/* merge the next free area into this one */ 390static void 391ExaOffscreenMerge (ExaOffscreenArea *area) 392{ 393 ExaOffscreenArea *next = area->next; 394 395 /* account for space */ 396 area->size += next->size; 397 /* frob pointer */ 398 area->next = next->next; 399 xfree (next); 400} 401 402/** 403 * exaOffscreenFree frees an allocation. 404 * 405 * @param pScreen current screen 406 * @param area offscreen area to free 407 * 408 * exaOffscreenFree frees an allocation created by exaOffscreenAlloc. Note that 409 * the save callback of the area is not called, and it is up to the driver to 410 * do any cleanup necessary as a result. 411 * 412 * @return pointer to the newly freed area. This behavior should not be relied 413 * on. 414 */ 415ExaOffscreenArea * 416exaOffscreenFree (ScreenPtr pScreen, ExaOffscreenArea *area) 417{ 418 ExaScreenPriv(pScreen); 419 ExaOffscreenArea *next = area->next; 420 ExaOffscreenArea *prev; 421 422 DBG_OFFSCREEN (("Free 0x%x -> 0x%x (0x%x)\n", area->size, 423 area->base_offset, area->offset)); 424 ExaOffscreenValidate (pScreen); 425 426 area->state = ExaOffscreenAvail; 427 area->save = NULL; 428 area->last_use = 0; 429 area->eviction_cost = 0; 430 /* 431 * Find previous area 432 */ 433 if (area == pExaScr->info->offScreenAreas) 434 prev = NULL; 435 else 436 for (prev = pExaScr->info->offScreenAreas; prev; prev = prev->next) 437 if (prev->next == area) 438 break; 439 440 /* link with next area if free */ 441 if (next && next->state == ExaOffscreenAvail) 442 ExaOffscreenMerge (area); 443 444 /* link with prev area if free */ 445 if (prev && prev->state == ExaOffscreenAvail) 446 { 447 area = prev; 448 ExaOffscreenMerge (area); 449 } 450 451 ExaOffscreenValidate (pScreen); 452 DBG_OFFSCREEN(("\tdone freeing\n")); 453 return area; 454} 455 456void 457ExaOffscreenMarkUsed (PixmapPtr pPixmap) 458{ 459 ExaPixmapPriv (pPixmap); 460 ExaScreenPriv (pPixmap->drawable.pScreen); 461 462 if (!pExaPixmap || !pExaPixmap->area) 463 return; 464 465 pExaPixmap->area->last_use = pExaScr->offScreenCounter++; 466} 467 468/** 469 * exaOffscreenInit initializes the offscreen memory manager. 470 * 471 * @param pScreen current screen 472 * 473 * exaOffscreenInit is called by exaDriverInit to set up the memory manager for 474 * the screen, if any offscreen memory is available. 475 */ 476Bool 477exaOffscreenInit (ScreenPtr pScreen) 478{ 479 ExaScreenPriv (pScreen); 480 ExaOffscreenArea *area; 481 482 /* Allocate a big free area */ 483 area = xalloc (sizeof (ExaOffscreenArea)); 484 485 if (!area) 486 return FALSE; 487 488 area->state = ExaOffscreenAvail; 489 area->base_offset = pExaScr->info->offScreenBase; 490 area->offset = area->base_offset; 491 area->size = pExaScr->info->memorySize - area->base_offset; 492 area->save = NULL; 493 area->next = NULL; 494 area->last_use = 0; 495 area->eviction_cost = 0; 496 497 /* Add it to the free areas */ 498 pExaScr->info->offScreenAreas = area; 499 pExaScr->offScreenCounter = 1; 500 501 ExaOffscreenValidate (pScreen); 502 503 return TRUE; 504} 505 506void 507ExaOffscreenFini (ScreenPtr pScreen) 508{ 509 ExaScreenPriv (pScreen); 510 ExaOffscreenArea *area; 511 512 /* just free all of the area records */ 513 while ((area = pExaScr->info->offScreenAreas)) 514 { 515 pExaScr->info->offScreenAreas = area->next; 516 xfree (area); 517 } 518} 519