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