summaryrefslogtreecommitdiff
path: root/xserver/exa/exa_offscreen.c
blob: 4aaa2c1320e96242a12f4eb401faec995b1f2871 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
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);
    }
}