summaryrefslogtreecommitdiff
path: root/shared-core/radeon_mem.c
blob: 1e582ee0799f1a6189258ccb7ac2ae3cc1f41cc4 (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
/* radeon_mem.c -- Simple GART/fb memory manager for radeon -*- linux-c -*- */
/*
 * Copyright (C) The Weather Channel, Inc.  2002.  All Rights Reserved.
 *
 * The Weather Channel (TM) funded Tungsten Graphics to develop the
 * initial release of the Radeon 8500 driver under the XFree86 license.
 * This notice must be preserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Authors:
 *    Keith Whitwell <keith@tungstengraphics.com>
 */

#include "drmP.h"
#include "drm.h"
#include "radeon_drm.h"
#include "radeon_drv.h"

/* Very simple allocator for GART memory, working on a static range
 * already mapped into each client's address space.
 */

static struct mem_block *split_block(struct mem_block *p, int start, int size,
				     struct drm_file *file_priv)
{
	/* Maybe cut off the start of an existing block */
	if (start > p->start) {
		struct mem_block *newblock =
		    drm_alloc(sizeof(*newblock), DRM_MEM_BUFS);
		if (!newblock)
			goto out;
		newblock->start = start;
		newblock->size = p->size - (start - p->start);
		newblock->file_priv = NULL;
		newblock->next = p->next;
		newblock->prev = p;
		p->next->prev = newblock;
		p->next = newblock;
		p->size -= newblock->size;
		p = newblock;
	}

	/* Maybe cut off the end of an existing block */
	if (size < p->size) {
		struct mem_block *newblock =
		    drm_alloc(sizeof(*newblock), DRM_MEM_BUFS);
		if (!newblock)
			goto out;
		newblock->start = start + size;
		newblock->size = p->size - size;
		newblock->file_priv = NULL;
		newblock->next = p->next;
		newblock->prev = p;
		p->next->prev = newblock;
		p->next = newblock;
		p->size = size;
	}

      out:
	/* Our block is in the middle */
	p->file_priv = file_priv;
	return p;
}

static struct mem_block *alloc_block(struct mem_block *heap, int size,
				     int align2, struct drm_file *file_priv)
{
	struct mem_block *p;
	int mask = (1 << align2) - 1;

	list_for_each(p, heap) {
		int start = (p->start + mask) & ~mask;
		if (p->file_priv == 0 && start + size <= p->start + p->size)
			return split_block(p, start, size, file_priv);
	}

	return NULL;
}

static struct mem_block *find_block(struct mem_block *heap, int start)
{
	struct mem_block *p;

	list_for_each(p, heap)
		if (p->start == start)
			return p;

	return NULL;
}

static void free_block(struct mem_block *p)
{
	p->file_priv = NULL;

	/* Assumes a single contiguous range.  Needs a special file_priv in
	 * 'heap' to stop it being subsumed.
	 */
	if (p->next->file_priv == 0) {
		struct mem_block *q = p->next;
		p->size += q->size;
		p->next = q->next;
		p->next->prev = p;
		drm_free(q, sizeof(*q), DRM_MEM_BUFS);
	}

	if (p->prev->file_priv == 0) {
		struct mem_block *q = p->prev;
		q->size += p->size;
		q->next = p->next;
		q->next->prev = q;
		drm_free(p, sizeof(*q), DRM_MEM_BUFS);
	}
}

/* Initialize.  How to check for an uninitialized heap?
 */
static int init_heap(struct mem_block **heap, int start, int size)
{
	struct mem_block *blocks = drm_alloc(sizeof(*blocks), DRM_MEM_BUFS);

	if (!blocks)
		return -ENOMEM;

	*heap = drm_alloc(sizeof(**heap), DRM_MEM_BUFS);
	if (!*heap) {
		drm_free(blocks, sizeof(*blocks), DRM_MEM_BUFS);
		return -ENOMEM;
	}

	blocks->start = start;
	blocks->size = size;
	blocks->file_priv = NULL;
	blocks->next = blocks->prev = *heap;

	memset(*heap, 0, sizeof(**heap));
	(*heap)->file_priv = (struct drm_file *) - 1;
	(*heap)->next = (*heap)->prev = blocks;
	return 0;
}

/* Free all blocks associated with the releasing file.
 */
void radeon_mem_release(struct drm_file *file_priv, struct mem_block *heap)
{
	struct mem_block *p;

	if (!heap || !heap->next)
		return;

	list_for_each(p, heap) {
		if (p->file_priv == file_priv)
			p->file_priv = NULL;
	}

	/* Assumes a single contiguous range.  Needs a special file_priv in
	 * 'heap' to stop it being subsumed.
	 */
	list_for_each(p, heap) {
		while (p->file_priv == 0 && p->next->file_priv == 0) {
			struct mem_block *q = p->next;
			p->size += q->size;
			p->next = q->next;
			p->next->prev = p;
			drm_free(q, sizeof(*q), DRM_MEM_DRIVER);
		}
	}
}

/* Shutdown.
 */
void radeon_mem_takedown(struct mem_block **heap)
{
	struct mem_block *p;

	if (!*heap)
		return;

	for (p = (*heap)->next; p != *heap;) {
		struct mem_block *q = p;
		p = p->next;
		drm_free(q, sizeof(*q), DRM_MEM_DRIVER);
	}

	drm_free(*heap, sizeof(**heap), DRM_MEM_DRIVER);
	*heap = NULL;
}

/* IOCTL HANDLERS */

static struct mem_block **get_heap(drm_radeon_private_t * dev_priv, int region)
{
	switch (region) {
	case RADEON_MEM_REGION_GART:
		return &dev_priv->gart_heap;
	case RADEON_MEM_REGION_FB:
		return &dev_priv->fb_heap;
	default:
		return NULL;
	}
}

int radeon_mem_alloc(struct drm_device *dev, void *data, struct drm_file *file_priv)
{
	drm_radeon_private_t *dev_priv = dev->dev_private;
	drm_radeon_mem_alloc_t *alloc = data;
	struct mem_block *block, **heap;

	if (!dev_priv) {
		DRM_ERROR("called with no initialization\n");
		return -EINVAL;
	}

	heap = get_heap(dev_priv, alloc->region);
	if (!heap || !*heap)
		return -EFAULT;

	/* Make things easier on ourselves: all allocations at least
	 * 4k aligned.
	 */
	if (alloc->alignment < 12)
		alloc->alignment = 12;

	block = alloc_block(*heap, alloc->size, alloc->alignment, file_priv);

	if (!block)
		return -ENOMEM;

	if (DRM_COPY_TO_USER(alloc->region_offset, &block->start,
			     sizeof(int))) {
		DRM_ERROR("copy_to_user\n");
		return -EFAULT;
	}

	return 0;
}

int radeon_mem_free(struct drm_device *dev, void *data, struct drm_file *file_priv)
{
	drm_radeon_private_t *dev_priv = dev->dev_private;
	drm_radeon_mem_free_t *memfree = data;
	struct mem_block *block, **heap;

	if (!dev_priv) {
		DRM_ERROR("called with no initialization\n");
		return -EINVAL;
	}

	heap = get_heap(dev_priv, memfree->region);
	if (!heap || !*heap)
		return -EFAULT;

	block = find_block(*heap, memfree->region_offset);
	if (!block)
		return -EFAULT;

	if (block->file_priv != file_priv)
		return -EPERM;

	free_block(block);
	return 0;
}

int radeon_mem_init_heap(struct drm_device *dev, void *data, struct drm_file *file_priv)
{
	drm_radeon_private_t *dev_priv = dev->dev_private;
	drm_radeon_mem_init_heap_t *initheap = data;
	struct mem_block **heap;

	if (!dev_priv) {
		DRM_ERROR("called with no initialization\n");
		return -EINVAL;
	}

	heap = get_heap(dev_priv, initheap->region);
	if (!heap)
		return -EFAULT;

	if (*heap) {
		DRM_ERROR("heap already initialized?");
		return -EFAULT;
	}

	return init_heap(heap, initheap->start, initheap->size);
}
210' href='#n1210'>1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405
/**************************************************************************
 *
 * Copyright © 2007 Red Hat Inc.
 * Copyright © 2007 Intel Corporation
 * Copyright 2006 Tungsten Graphics, Inc., Bismarck, ND., USA
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sub license, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 * USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 *
 *
 **************************************************************************/
/*
 * Authors: Thomas Hellström <thomas-at-tungstengraphics-dot-com>
 *          Keith Whitwell <keithw-at-tungstengraphics-dot-com>
 *	    Eric Anholt <eric@anholt.net>
 *	    Dave Airlie <airlied@linux.ie>
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <xf86drm.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "errno.h"
#include "libdrm_lists.h"
#include "intel_bufmgr.h"
#include "intel_bufmgr_priv.h"
#include "intel_chipset.h"
#include "string.h"

#include "i915_drm.h"

#define DBG(...) do {					\
   if (bufmgr_gem->bufmgr.debug)			\
      fprintf(stderr, __VA_ARGS__);			\
} while (0)

typedef struct _drm_intel_bo_gem drm_intel_bo_gem;

struct drm_intel_gem_bo_bucket {
   drmMMListHead head;

   /**
    * Limit on the number of entries in this bucket.
    *
    * 0 means that this caching at this bucket size is disabled.
    * -1 means that there is no limit to caching at this size.
    */
   int max_entries;
   int num_entries;
};

/* Arbitrarily chosen, 16 means that the maximum size we'll cache for reuse
 * is 1 << 16 pages, or 256MB.
 */
#define DRM_INTEL_GEM_BO_BUCKETS	16
typedef struct _drm_intel_bufmgr_gem {
    drm_intel_bufmgr bufmgr;

    int fd;

    int max_relocs;

    pthread_mutex_t lock;

    struct drm_i915_gem_exec_object *exec_objects;
    drm_intel_bo **exec_bos;
    int exec_size;
    int exec_count;

    /** Array of lists of cached gem objects of power-of-two sizes */
    struct drm_intel_gem_bo_bucket cache_bucket[DRM_INTEL_GEM_BO_BUCKETS];

    uint64_t gtt_size;
    int available_fences;
    int pci_device;
} drm_intel_bufmgr_gem;

struct _drm_intel_bo_gem {
    drm_intel_bo bo;

    int refcount;
    /** Boolean whether the mmap ioctl has been called for this buffer yet. */
    uint32_t gem_handle;
    const char *name;

    /**
     * Kenel-assigned global name for this object
     */
    unsigned int global_name;
    
    /**
     * Index of the buffer within the validation list while preparing a
     * batchbuffer execution.
     */
    int validate_index;

    /**
     * Boolean whether we've started swrast
     * Set when the buffer has been mapped
     * Cleared when the buffer is unmapped
     */
    int swrast;

    /**
     * Current tiling mode
     */
    uint32_t tiling_mode;
    uint32_t swizzle_mode;

    /** Array passed to the DRM containing relocation information. */
    struct drm_i915_gem_relocation_entry *relocs;
    /** Array of bos corresponding to relocs[i].target_handle */
    drm_intel_bo **reloc_target_bo;
    /** Number of entries in relocs */
    int reloc_count;
    /** Mapped address for the buffer, saved across map/unmap cycles */
    void *virtual;

    /** BO cache list */
    drmMMListHead head;

    /**
     * Boolean of whether this BO and its children have been included in
     * the current drm_intel_bufmgr_check_aperture_space() total.
     */
    char included_in_check_aperture;

    /**
     * Boolean of whether this buffer has been used as a relocation
     * target and had its size accounted for, and thus can't have any
     * further relocations added to it.
     */
     char used_as_reloc_target;

    /**
     * Size in bytes of this buffer and its relocation descendents.
     *
     * Used to avoid costly tree walking in drm_intel_bufmgr_check_aperture in
     * the common case.
     */
    int reloc_tree_size;
    /**
     * Number of potential fence registers required by this buffer and its
     * relocations.
     */
    int reloc_tree_fences;
};

static void drm_intel_gem_bo_reference_locked(drm_intel_bo *bo);

static unsigned int
drm_intel_gem_estimate_batch_space(drm_intel_bo **bo_array, int count);

static unsigned int
drm_intel_gem_compute_batch_space(drm_intel_bo **bo_array, int count);

static int
drm_intel_gem_bo_get_tiling(drm_intel_bo *bo, uint32_t *tiling_mode,
			    uint32_t *swizzle_mode);

static int
drm_intel_gem_bo_set_tiling(drm_intel_bo *bo, uint32_t *tiling_mode,
			    uint32_t stride);

static void
drm_intel_gem_bo_unreference(drm_intel_bo *bo);

static int
logbase2(int n)
{
   int i = 1;
   int log2 = 0;

   while (n > i) {
      i *= 2;
      log2++;
   }

   return log2;
}

static struct drm_intel_gem_bo_bucket *
drm_intel_gem_bo_bucket_for_size(drm_intel_bufmgr_gem *bufmgr_gem,
				 unsigned long size)
{
    int i;

    /* We only do buckets in power of two increments */
    if ((size & (size - 1)) != 0)
	return NULL;

    /* We should only see sizes rounded to pages. */
    assert((size % 4096) == 0);

    /* We always allocate in units of pages */
    i = ffs(size / 4096) - 1;
    if (i >= DRM_INTEL_GEM_BO_BUCKETS)
	return NULL;

    return &bufmgr_gem->cache_bucket[i];
}


static void drm_intel_gem_dump_validation_list(drm_intel_bufmgr_gem *bufmgr_gem)
{
    int i, j;

    for (i = 0; i < bufmgr_gem->exec_count; i++) {
	drm_intel_bo *bo = bufmgr_gem->exec_bos[i];
	drm_intel_bo_gem *bo_gem = (drm_intel_bo_gem *)bo;

	if (bo_gem->relocs == NULL) {
	    DBG("%2d: %d (%s)\n", i, bo_gem->gem_handle, bo_gem->name);
	    continue;
	}

	for (j = 0; j < bo_gem->reloc_count; j++) {
	    drm_intel_bo *target_bo = bo_gem->reloc_target_bo[j];
	    drm_intel_bo_gem *target_gem = (drm_intel_bo_gem *)target_bo;

	    DBG("%2d: %d (%s)@0x%08llx -> %d (%s)@0x%08lx + 0x%08x\n",
		i,
		bo_gem->gem_handle, bo_gem->name,
		(unsigned long long)bo_gem->relocs[j].offset,
		target_gem->gem_handle, target_gem->name, target_bo->offset,
		bo_gem->relocs[j].delta);
	}
    }
}

/**
 * Adds the given buffer to the list of buffers to be validated (moved into the
 * appropriate memory type) with the next batch submission.
 *
 * If a buffer is validated multiple times in a batch submission, it ends up
 * with the intersection of the memory type flags and the union of the
 * access flags.
 */
static void
drm_intel_add_validate_buffer(drm_intel_bo *bo)
{
    drm_intel_bufmgr_gem *bufmgr_gem = (drm_intel_bufmgr_gem *)bo->bufmgr;
    drm_intel_bo_gem *bo_gem = (drm_intel_bo_gem *)bo;
    int index;

    if (bo_gem->validate_index != -1)
	return;

    /* Extend the array of validation entries as necessary. */
    if (bufmgr_gem->exec_count == bufmgr_gem->exec_size) {
	int new_size = bufmgr_gem->exec_size * 2;

	if (new_size == 0)
	    new_size = 5;

	bufmgr_gem->exec_objects =
	    realloc(bufmgr_gem->exec_objects,
		    sizeof(*bufmgr_gem->exec_objects) * new_size);
	bufmgr_gem->exec_bos =
	    realloc(bufmgr_gem->exec_bos,
		    sizeof(*bufmgr_gem->exec_bos) * new_size);
	bufmgr_gem->exec_size = new_size;
    }

    index = bufmgr_gem->exec_count;
    bo_gem->validate_index = index;
    /* Fill in array entry */
    bufmgr_gem->exec_objects[index].handle = bo_gem->gem_handle;
    bufmgr_gem->exec_objects[index].relocation_count = bo_gem->reloc_count;
    bufmgr_gem->exec_objects[index].relocs_ptr = (uintptr_t)bo_gem->relocs;
    bufmgr_gem->exec_objects[index].alignment = 0;
    bufmgr_gem->exec_objects[index].offset = 0;
    bufmgr_gem->exec_bos[index] = bo;
    drm_intel_gem_bo_reference_locked(bo);
    bufmgr_gem->exec_count++;
}


#define RELOC_BUF_SIZE(x) ((I915_RELOC_HEADER + x * I915_RELOC0_STRIDE) * \
	sizeof(uint32_t))

static int
drm_intel_setup_reloc_list(drm_intel_bo *bo)
{
    drm_intel_bo_gem *bo_gem = (drm_intel_bo_gem *)bo;
    drm_intel_bufmgr_gem *bufmgr_gem = (drm_intel_bufmgr_gem *)bo->bufmgr;

    bo_gem->relocs = malloc(bufmgr_gem->max_relocs *
			    sizeof(struct drm_i915_gem_relocation_entry));
    bo_gem->reloc_target_bo = malloc(bufmgr_gem->max_relocs *
				     sizeof(drm_intel_bo *));

    return 0;
}

static drm_intel_bo *
drm_intel_gem_bo_alloc_internal(drm_intel_bufmgr *bufmgr, const char *name,
				unsigned long size, unsigned int alignment,
				int for_render)
{
    drm_intel_bufmgr_gem *bufmgr_gem = (drm_intel_bufmgr_gem *)bufmgr;
    drm_intel_bo_gem *bo_gem;
    unsigned int page_size = getpagesize();
    int ret;
    struct drm_intel_gem_bo_bucket *bucket;
    int alloc_from_cache = 0;
    unsigned long bo_size;

    /* Round the allocated size up to a power of two number of pages. */
    bo_size = 1 << logbase2(size);
    if (bo_size < page_size)
	bo_size = page_size;
    bucket = drm_intel_gem_bo_bucket_for_size(bufmgr_gem, bo_size);

    /* If we don't have caching at this size, don't actually round the
     * allocation up.
     */
    if (bucket == NULL || bucket->max_entries == 0) {
	bo_size = size;
	if (bo_size < page_size)
	    bo_size = page_size;
    }

    pthread_mutex_lock(&bufmgr_gem->lock);
    /* Get a buffer out of the cache if available */
    if (bucket != NULL && bucket->num_entries > 0) {
	struct drm_i915_gem_busy busy;

	if (for_render) {
	    /* Allocate new render-target BOs from the tail (MRU)
	     * of the list, as it will likely be hot in the GPU cache
	     * and in the aperture for us.
	     */
	    bo_gem = DRMLISTENTRY(drm_intel_bo_gem, bucket->head.prev, head);
	    DRMLISTDEL(&bo_gem->head);
	    bucket->num_entries--;
	    alloc_from_cache = 1;
	} else {
	    /* For non-render-target BOs (where we're probably going to map it
	     * first thing in order to fill it with data), check if the
	     * last BO in the cache is unbusy, and only reuse in that case.
	     * Otherwise, allocating a new buffer is probably faster than
	     * waiting for the GPU to finish.
	     */
	    bo_gem = DRMLISTENTRY(drm_intel_bo_gem, bucket->head.next, head);

	    memset(&busy, 0, sizeof(busy));
	    busy.handle = bo_gem->gem_handle;

	    ret = ioctl(bufmgr_gem->fd, DRM_IOCTL_I915_GEM_BUSY, &busy);
	    alloc_from_cache = (ret == 0 && busy.busy == 0);

	    if (alloc_from_cache) {
		DRMLISTDEL(&bo_gem->head);
		bucket->num_entries--;
	    }
	}
    }
    pthread_mutex_unlock(&bufmgr_gem->lock);

    if (!alloc_from_cache) {
	struct drm_i915_gem_create create;

	bo_gem = calloc(1, sizeof(*bo_gem));
	if (!bo_gem)
	    return NULL;

	bo_gem->bo.size = bo_size;
	memset(&create, 0, sizeof(create));
	create.size = bo_size;

	ret = ioctl(bufmgr_gem->fd, DRM_IOCTL_I915_GEM_CREATE, &create);
	bo_gem->gem_handle = create.handle;
	bo_gem->bo.handle = bo_gem->gem_handle;
	if (ret != 0) {
	    free(bo_gem);
	    return NULL;
	}
	bo_gem->bo.bufmgr = bufmgr;
    }

    bo_gem->name = name;
    bo_gem->refcount = 1;
    bo_gem->validate_index = -1;
    bo_gem->reloc_tree_size = bo_gem->bo.size;
    bo_gem->reloc_tree_fences = 0;
    bo_gem->used_as_reloc_target = 0;
    bo_gem->tiling_mode = I915_TILING_NONE;
    bo_gem->swizzle_mode = I915_BIT_6_SWIZZLE_NONE;

    DBG("bo_create: buf %d (%s) %ldb\n",
	bo_gem->gem_handle, bo_gem->name, size);

    return &bo_gem->bo;
}

static drm_intel_bo *
drm_intel_gem_bo_alloc_for_render(drm_intel_bufmgr *bufmgr, const char *name,
				  unsigned long size, unsigned int alignment)
{
    return drm_intel_gem_bo_alloc_internal(bufmgr, name, size, alignment, 1);
}

static drm_intel_bo *
drm_intel_gem_bo_alloc(drm_intel_bufmgr *bufmgr, const char *name,
		       unsigned long size, unsigned int alignment)
{
    return drm_intel_gem_bo_alloc_internal(bufmgr, name, size, alignment, 0);
}

/**
 * Returns a drm_intel_bo wrapping the given buffer object handle.
 *
 * This can be used when one application needs to pass a buffer object
 * to another.
 */
drm_intel_bo *
drm_intel_bo_gem_create_from_name(drm_intel_bufmgr *bufmgr, const char *name,
				  unsigned int handle)
{
    drm_intel_bufmgr_gem *bufmgr_gem = (drm_intel_bufmgr_gem *)bufmgr;
    drm_intel_bo_gem *bo_gem;
    int ret;
    struct drm_gem_open open_arg;
    struct drm_i915_gem_get_tiling get_tiling;

    bo_gem = calloc(1, sizeof(*bo_gem));
    if (!bo_gem)
	return NULL;

    memset(&open_arg, 0, sizeof(open_arg));
    open_arg.name = handle;
    ret = ioctl(bufmgr_gem->fd, DRM_IOCTL_GEM_OPEN, &open_arg);
    if (ret != 0) {
	fprintf(stderr, "Couldn't reference %s handle 0x%08x: %s\n",
	       name, handle, strerror(errno));
	free(bo_gem);
	return NULL;
    }
    bo_gem->bo.size = open_arg.size;
    bo_gem->bo.offset = 0;
    bo_gem->bo.virtual = NULL;
    bo_gem->bo.bufmgr = bufmgr;
    bo_gem->name = name;
    bo_gem->refcount = 1;
    bo_gem->validate_index = -1;
    bo_gem->gem_handle = open_arg.handle;
    bo_gem->global_name = handle;

    memset(&get_tiling, 0, sizeof(get_tiling));
    get_tiling.handle = bo_gem->gem_handle;
    ret = ioctl(bufmgr_gem->fd, DRM_IOCTL_I915_GEM_GET_TILING, &get_tiling);
    if (ret != 0) {
	drm_intel_gem_bo_unreference(&bo_gem->bo);
	return NULL;
    }
    bo_gem->tiling_mode = get_tiling.tiling_mode;
    bo_gem->swizzle_mode = get_tiling.swizzle_mode;
    if (bo_gem->tiling_mode == I915_TILING_NONE)
	bo_gem->reloc_tree_fences = 0;
    else
	bo_gem->reloc_tree_fences = 1;

    DBG("bo_create_from_handle: %d (%s)\n", handle, bo_gem->name);

    return &bo_gem->bo;
}

static void
drm_intel_gem_bo_reference(drm_intel_bo *bo)
{
    drm_intel_bufmgr_gem *bufmgr_gem = (drm_intel_bufmgr_gem *)bo->bufmgr;
    drm_intel_bo_gem *bo_gem = (drm_intel_bo_gem *)bo;

    pthread_mutex_lock(&bufmgr_gem->lock);
    bo_gem->refcount++;
    pthread_mutex_unlock(&bufmgr_gem->lock);
}

static void
drm_intel_gem_bo_reference_locked(drm_intel_bo *bo)
{
    drm_intel_bo_gem *bo_gem = (drm_intel_bo_gem *)bo;

    bo_gem->refcount++;
}

static void
drm_intel_gem_bo_free(drm_intel_bo *bo)
{
    drm_intel_bufmgr_gem *bufmgr_gem = (drm_intel_bufmgr_gem *)bo->bufmgr;
    drm_intel_bo_gem *bo_gem = (drm_intel_bo_gem *)bo;
    struct drm_gem_close close;
    int ret;

    if (bo_gem->virtual)
	munmap (bo_gem->virtual, bo_gem->bo.size);

    /* Close this object */
    memset(&close, 0, sizeof(close));
    close.handle = bo_gem->gem_handle;
    ret = ioctl(bufmgr_gem->fd, DRM_IOCTL_GEM_CLOSE, &close);
    if (ret != 0) {
	fprintf(stderr,
		"DRM_IOCTL_GEM_CLOSE %d failed (%s): %s\n",
		bo_gem->gem_handle, bo_gem->name, strerror(errno));
    }
    free(bo);
}

static void
drm_intel_gem_bo_unreference_locked(drm_intel_bo *bo)
{
    drm_intel_bufmgr_gem *bufmgr_gem = (drm_intel_bufmgr_gem *)bo->bufmgr;
    drm_intel_bo_gem *bo_gem = (drm_intel_bo_gem *)bo;

    if (--bo_gem->refcount == 0) {
	struct drm_intel_gem_bo_bucket *bucket;
	uint32_t tiling_mode;

	if (bo_gem->relocs != NULL) {
	    int i;

	    /* Unreference all the target buffers */
	    for (i = 0; i < bo_gem->reloc_count; i++)
		 drm_intel_gem_bo_unreference_locked(bo_gem->reloc_target_bo[i]);
	    free(bo_gem->reloc_target_bo);
	    free(bo_gem->relocs);
	}

	DBG("bo_unreference final: %d (%s)\n",
	    bo_gem->gem_handle, bo_gem->name);

	bucket = drm_intel_gem_bo_bucket_for_size(bufmgr_gem, bo->size);
	/* Put the buffer into our internal cache for reuse if we can. */
	tiling_mode = I915_TILING_NONE;
	if (bo_gem->global_name == 0 &&
	    bucket != NULL &&
	    (bucket->max_entries == -1 ||
	     (bucket->max_entries > 0 &&
	      bucket->num_entries < bucket->max_entries)) &&
	    drm_intel_gem_bo_set_tiling(bo, &tiling_mode, 0) == 0)
	{
	    bo_gem->name = NULL;
	    bo_gem->validate_index = -1;
	    bo_gem->relocs = NULL;
	    bo_gem->reloc_target_bo = NULL;
	    bo_gem->reloc_count = 0;

	    DRMLISTADDTAIL(&bo_gem->head, &bucket->head);
	    bucket->num_entries++;
	} else {
	    drm_intel_gem_bo_free(bo);
	}
    }
}

static void
drm_intel_gem_bo_unreference(drm_intel_bo *bo)
{
    drm_intel_bufmgr_gem *bufmgr_gem = (drm_intel_bufmgr_gem *)bo->bufmgr;

    pthread_mutex_lock(&bufmgr_gem->lock);
    drm_intel_gem_bo_unreference_locked(bo);
    pthread_mutex_unlock(&bufmgr_gem->lock);
}

static int
drm_intel_gem_bo_map(drm_intel_bo *bo, int write_enable)
{
    drm_intel_bufmgr_gem *bufmgr_gem = (drm_intel_bufmgr_gem *)bo->bufmgr;
    drm_intel_bo_gem *bo_gem = (drm_intel_bo_gem *)bo;
    struct drm_i915_gem_set_domain set_domain;
    int ret;

    pthread_mutex_lock(&bufmgr_gem->lock);

    /* Allow recursive mapping. Mesa may recursively map buffers with
     * nested display loops.
     */
    if (!bo_gem->virtual) {
	struct drm_i915_gem_mmap mmap_arg;

	DBG("bo_map: %d (%s)\n", bo_gem->gem_handle, bo_gem->name);

	memset(&mmap_arg, 0, sizeof(mmap_arg));
	mmap_arg.handle = bo_gem->gem_handle;
	mmap_arg.offset = 0;
	mmap_arg.size = bo->size;
	ret = ioctl(bufmgr_gem->fd, DRM_IOCTL_I915_GEM_MMAP, &mmap_arg);
	if (ret != 0) {
	    fprintf(stderr, "%s:%d: Error mapping buffer %d (%s): %s .\n",
		    __FILE__, __LINE__,
		    bo_gem->gem_handle, bo_gem->name, strerror(errno));
	    pthread_mutex_unlock(&bufmgr_gem->lock);
	    return ret;
	}
	bo_gem->virtual = (void *)(uintptr_t)mmap_arg.addr_ptr;
	bo_gem->swrast = 0;
    }
    DBG("bo_map: %d (%s) -> %p\n", bo_gem->gem_handle, bo_gem->name,
	bo_gem->virtual);
    bo->virtual = bo_gem->virtual;

    if (bo_gem->global_name != 0 || !bo_gem->swrast) {
	set_domain.handle = bo_gem->gem_handle;
	set_domain.read_domains = I915_GEM_DOMAIN_CPU;
	if (write_enable)
	    set_domain.write_domain = I915_GEM_DOMAIN_CPU;
	else
	    set_domain.write_domain = 0;
	do {
	    ret = ioctl(bufmgr_gem->fd, DRM_IOCTL_I915_GEM_SET_DOMAIN,
			&set_domain);
	} while (ret == -1 && errno == EINTR);
	if (ret != 0) {
	    fprintf (stderr, "%s:%d: Error setting swrast %d: %s\n",
		     __FILE__, __LINE__, bo_gem->gem_handle, strerror (errno));
	    pthread_mutex_unlock(&bufmgr_gem->lock);
	    return ret;
	}
	bo_gem->swrast = 1;
    }

    pthread_mutex_unlock(&bufmgr_gem->lock);

    return 0;
}

int
drm_intel_gem_bo_map_gtt(drm_intel_bo *bo)
{
    drm_intel_bufmgr_gem *bufmgr_gem = (drm_intel_bufmgr_gem *)bo->bufmgr;
    drm_intel_bo_gem *bo_gem = (drm_intel_bo_gem *)bo;
    struct drm_i915_gem_set_domain set_domain;
    int ret;

    pthread_mutex_lock(&bufmgr_gem->lock);

    /* Get a mapping of the buffer if we haven't before. */
    if (bo_gem->virtual == NULL) {
	struct drm_i915_gem_mmap_gtt mmap_arg;

	DBG("bo_map_gtt: %d (%s)\n", bo_gem->gem_handle, bo_gem->name);

	memset(&mmap_arg, 0, sizeof(mmap_arg));
	mmap_arg.handle = bo_gem->gem_handle;

	/* Get the fake offset back... */
	ret = ioctl(bufmgr_gem->fd, DRM_IOCTL_I915_GEM_MMAP_GTT, &mmap_arg);
	if (ret != 0) {
	    fprintf(stderr,
		    "%s:%d: Error preparing buffer map %d (%s): %s .\n",
		    __FILE__, __LINE__,
		    bo_gem->gem_handle, bo_gem->name,
		    strerror(errno));
	    pthread_mutex_unlock(&bufmgr_gem->lock);
	    return ret;
	}

	/* and mmap it */
	bo_gem->virtual = mmap(0, bo->size, PROT_READ | PROT_WRITE,
			       MAP_SHARED, bufmgr_gem->fd,
			       mmap_arg.offset);
	if (bo_gem->virtual == MAP_FAILED) {
	    fprintf(stderr,
		    "%s:%d: Error mapping buffer %d (%s): %s .\n",
		    __FILE__, __LINE__,
		    bo_gem->gem_handle, bo_gem->name,
		    strerror(errno));
	    pthread_mutex_unlock(&bufmgr_gem->lock);
	    return errno;
	}
    }

    bo->virtual = bo_gem->virtual;

    DBG("bo_map: %d (%s) -> %p\n", bo_gem->gem_handle, bo_gem->name,
	bo_gem->virtual);

    /* Now move it to the GTT domain so that the CPU caches are flushed */
    set_domain.handle = bo_gem->gem_handle;
    set_domain.read_domains = I915_GEM_DOMAIN_GTT;
    set_domain.write_domain = I915_GEM_DOMAIN_GTT;
    do {
	    ret = ioctl(bufmgr_gem->fd, DRM_IOCTL_I915_GEM_SET_DOMAIN,
			&set_domain);
    } while (ret == -1 && errno == EINTR);

    if (ret != 0) {
	    fprintf (stderr, "%s:%d: Error setting swrast %d: %s\n",
		     __FILE__, __LINE__, bo_gem->gem_handle, strerror (errno));
    }

    pthread_mutex_unlock(&bufmgr_gem->lock);

    return 0;
}

static int
drm_intel_gem_bo_unmap(drm_intel_bo *bo)
{
    drm_intel_bufmgr_gem *bufmgr_gem = (drm_intel_bufmgr_gem *)bo->bufmgr;
    drm_intel_bo_gem *bo_gem = (drm_intel_bo_gem *)bo;
    struct drm_i915_gem_sw_finish sw_finish;
    int ret;

    if (bo == NULL)
	return 0;

    assert(bo_gem->virtual != NULL);

    pthread_mutex_lock(&bufmgr_gem->lock);
    if (bo_gem->swrast) {
	sw_finish.handle = bo_gem->gem_handle;
	do {
	    ret = ioctl(bufmgr_gem->fd, DRM_IOCTL_I915_GEM_SW_FINISH,
			&sw_finish);
	} while (ret == -1 && errno == EINTR);
	bo_gem->swrast = 0;
    }
    pthread_mutex_unlock(&bufmgr_gem->lock);
    return 0;
}

static int
drm_intel_gem_bo_subdata (drm_intel_bo *bo, unsigned long offset,
			  unsigned long size, const void *data)
{
    drm_intel_bufmgr_gem *bufmgr_gem = (drm_intel_bufmgr_gem *)bo->bufmgr;