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