drm_bufs.c 41.6 KB
Newer Older
L
Linus Torvalds 已提交
1
/**
D
Dave Airlie 已提交
2
 * \file drm_bufs.c
L
Linus Torvalds 已提交
3
 * Generic buffer template
D
Dave Airlie 已提交
4
 *
L
Linus Torvalds 已提交
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
 * \author Rickard E. (Rik) Faith <faith@valinux.com>
 * \author Gareth Hughes <gareth@valinux.com>
 */

/*
 * Created: Thu Nov 23 03:10:50 2000 by gareth@valinux.com
 *
 * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas.
 * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
 * 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, 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
 * VA LINUX SYSTEMS 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.
 */

#include <linux/vmalloc.h>
#include "drmP.h"

39
unsigned long drm_get_resource_start(drm_device_t *dev, unsigned int resource)
L
Linus Torvalds 已提交
40
{
D
Dave Airlie 已提交
41 42 43
	return pci_resource_start(dev->pdev, resource);
}
EXPORT_SYMBOL(drm_get_resource_start);
L
Linus Torvalds 已提交
44

45
unsigned long drm_get_resource_len(drm_device_t *dev, unsigned int resource)
D
Dave Airlie 已提交
46 47 48
{
	return pci_resource_len(dev->pdev, resource);
}
D
Dave Airlie 已提交
49

D
Dave Airlie 已提交
50
EXPORT_SYMBOL(drm_get_resource_len);
L
Linus Torvalds 已提交
51

52 53
static drm_map_list_t *drm_find_matching_map(drm_device_t *dev,
					     drm_local_map_t *map)
D
Dave Airlie 已提交
54 55
{
	struct list_head *list;
L
Linus Torvalds 已提交
56

D
Dave Airlie 已提交
57 58 59 60
	list_for_each(list, &dev->maplist->head) {
		drm_map_list_t *entry = list_entry(list, drm_map_list_t, head);
		if (entry->map && map->type == entry->map->type &&
		    entry->map->offset == map->offset) {
61
			return entry;
D
Dave Airlie 已提交
62 63 64 65
		}
	}

	return NULL;
L
Linus Torvalds 已提交
66 67
}

68 69
int drm_map_handle(drm_device_t *dev, drm_hash_item_t *hash,
		   unsigned long user_token, int hashed_handle)
70
{
71
	int use_hashed_handle;
D
Dave Airlie 已提交
72
#if (BITS_PER_LONG == 64)
73 74 75 76 77 78
	use_hashed_handle = ((user_token & 0xFFFFFFFF00000000UL) || hashed_handle);
#elif (BITS_PER_LONG == 32)
	use_hashed_handle = hashed_handle;
#else
#error Unsupported long size. Neither 64 nor 32 bits.
#endif
79

80 81 82 83 84 85 86
	if (use_hashed_handle) {
		return drm_ht_just_insert_please(&dev->map_hash, hash,
						 user_token, 32 - PAGE_SHIFT - 3,
						 PAGE_SHIFT, DRM_MAP_HASH_OFFSET);
	} else {
		hash->key = user_token;
		return drm_ht_insert_item(&dev->map_hash, hash);
87 88
	}
}
89

L
Linus Torvalds 已提交
90 91 92 93 94 95 96 97 98 99 100 101 102
/**
 * Ioctl to specify a range of memory that is available for mapping by a non-root process.
 *
 * \param inode device inode.
 * \param filp file pointer.
 * \param cmd command.
 * \param arg pointer to a drm_map structure.
 * \return zero on success or a negative value on error.
 *
 * Adjusts the memory offset to its absolute value according to the mapping
 * type.  Adds the map to the map list drm_device::maplist. Adds MTRR's where
 * applicable and if supported by the kernel.
 */
103 104 105
static int drm_addmap_core(drm_device_t * dev, unsigned int offset,
			   unsigned int size, drm_map_type_t type,
			   drm_map_flags_t flags, drm_map_list_t ** maplist)
L
Linus Torvalds 已提交
106 107 108
{
	drm_map_t *map;
	drm_map_list_t *list;
109
	drm_dma_handle_t *dmah;
110 111
	unsigned long user_token;
	int ret;
L
Linus Torvalds 已提交
112

D
Dave Airlie 已提交
113 114
	map = drm_alloc(sizeof(*map), DRM_MEM_MAPS);
	if (!map)
L
Linus Torvalds 已提交
115 116
		return -ENOMEM;

117 118 119 120
	map->offset = offset;
	map->size = size;
	map->flags = flags;
	map->type = type;
L
Linus Torvalds 已提交
121 122 123 124 125

	/* Only allow shared memory to be removable since we only keep enough
	 * book keeping information about shared memory to allow for removal
	 * when processes fork.
	 */
D
Dave Airlie 已提交
126 127
	if ((map->flags & _DRM_REMOVABLE) && map->type != _DRM_SHM) {
		drm_free(map, sizeof(*map), DRM_MEM_MAPS);
L
Linus Torvalds 已提交
128 129
		return -EINVAL;
	}
D
Dave Airlie 已提交
130 131 132 133
	DRM_DEBUG("offset = 0x%08lx, size = 0x%08lx, type = %d\n",
		  map->offset, map->size, map->type);
	if ((map->offset & (~PAGE_MASK)) || (map->size & (~PAGE_MASK))) {
		drm_free(map, sizeof(*map), DRM_MEM_MAPS);
L
Linus Torvalds 已提交
134 135
		return -EINVAL;
	}
D
Dave Airlie 已提交
136
	map->mtrr = -1;
L
Linus Torvalds 已提交
137 138
	map->handle = NULL;

D
Dave Airlie 已提交
139
	switch (map->type) {
L
Linus Torvalds 已提交
140 141
	case _DRM_REGISTERS:
	case _DRM_FRAME_BUFFER:
D
Dave Airlie 已提交
142
#if !defined(__sparc__) && !defined(__alpha__) && !defined(__ia64__) && !defined(__powerpc64__) && !defined(__x86_64__)
143
		if (map->offset + (map->size-1) < map->offset ||
D
Dave Airlie 已提交
144 145
		    map->offset < virt_to_phys(high_memory)) {
			drm_free(map, sizeof(*map), DRM_MEM_MAPS);
L
Linus Torvalds 已提交
146 147 148 149 150 151
			return -EINVAL;
		}
#endif
#ifdef __alpha__
		map->offset += dev->hose->mem_space->start;
#endif
D
Dave Airlie 已提交
152 153 154 155
		/* Some drivers preinitialize some maps, without the X Server
		 * needing to be aware of it.  Therefore, we just return success
		 * when the server tries to create a duplicate map.
		 */
156 157 158
		list = drm_find_matching_map(dev, map);
		if (list != NULL) {
			if (list->map->size != map->size) {
D
Dave Airlie 已提交
159
				DRM_DEBUG("Matching maps of type %d with "
D
Dave Airlie 已提交
160 161 162
					  "mismatched sizes, (%ld vs %ld)\n",
					  map->type, map->size,
					  list->map->size);
163
				list->map->size = map->size;
D
Dave Airlie 已提交
164 165 166
			}

			drm_free(map, sizeof(*map), DRM_MEM_MAPS);
167
			*maplist = list;
D
Dave Airlie 已提交
168 169 170
			return 0;
		}

L
Linus Torvalds 已提交
171
		if (drm_core_has_MTRR(dev)) {
D
Dave Airlie 已提交
172 173 174 175
			if (map->type == _DRM_FRAME_BUFFER ||
			    (map->flags & _DRM_WRITE_COMBINING)) {
				map->mtrr = mtrr_add(map->offset, map->size,
						     MTRR_TYPE_WRCOMB, 1);
L
Linus Torvalds 已提交
176 177 178
			}
		}
		if (map->type == _DRM_REGISTERS)
D
Dave Airlie 已提交
179
			map->handle = drm_ioremap(map->offset, map->size, dev);
L
Linus Torvalds 已提交
180 181 182 183
		break;

	case _DRM_SHM:
		map->handle = vmalloc_32(map->size);
D
Dave Airlie 已提交
184 185 186 187
		DRM_DEBUG("%lu %d %p\n",
			  map->size, drm_order(map->size), map->handle);
		if (!map->handle) {
			drm_free(map, sizeof(*map), DRM_MEM_MAPS);
L
Linus Torvalds 已提交
188 189 190
			return -ENOMEM;
		}
		map->offset = (unsigned long)map->handle;
D
Dave Airlie 已提交
191
		if (map->flags & _DRM_CONTAINS_LOCK) {
L
Linus Torvalds 已提交
192 193
			/* Prevent a 2nd X Server from creating a 2nd lock */
			if (dev->lock.hw_lock != NULL) {
D
Dave Airlie 已提交
194 195
				vfree(map->handle);
				drm_free(map, sizeof(*map), DRM_MEM_MAPS);
L
Linus Torvalds 已提交
196 197
				return -EBUSY;
			}
D
Dave Airlie 已提交
198
			dev->sigdata.lock = dev->lock.hw_lock = map->handle;	/* Pointer to lock */
L
Linus Torvalds 已提交
199 200 201 202 203 204 205 206
		}
		break;
	case _DRM_AGP:
		if (drm_core_has_AGP(dev)) {
#ifdef __alpha__
			map->offset += dev->hose->mem_space->start;
#endif
			map->offset += dev->agp->base;
D
Dave Airlie 已提交
207
			map->mtrr = dev->agp->agp_mtrr;	/* for getmap */
L
Linus Torvalds 已提交
208 209 210 211 212 213 214
		}
		break;
	case _DRM_SCATTER_GATHER:
		if (!dev->sg) {
			drm_free(map, sizeof(*map), DRM_MEM_MAPS);
			return -EINVAL;
		}
215
		map->offset += (unsigned long)dev->sg->virtual;
L
Linus Torvalds 已提交
216
		break;
D
Dave Airlie 已提交
217
	case _DRM_CONSISTENT:
D
Dave Airlie 已提交
218
		/* dma_addr_t is 64bit on i386 with CONFIG_HIGHMEM64G,
219
		 * As we're limiting the address to 2^32-1 (or less),
D
Dave Airlie 已提交
220 221
		 * casting it down to 32 bits is no problem, but we
		 * need to point to a 64bit variable first. */
222 223
		dmah = drm_pci_alloc(dev, map->size, map->size, 0xffffffffUL);
		if (!dmah) {
D
Dave Airlie 已提交
224 225 226
			drm_free(map, sizeof(*map), DRM_MEM_MAPS);
			return -ENOMEM;
		}
227 228 229
		map->handle = dmah->vaddr;
		map->offset = (unsigned long)dmah->busaddr;
		kfree(dmah);
D
Dave Airlie 已提交
230
		break;
L
Linus Torvalds 已提交
231
	default:
D
Dave Airlie 已提交
232
		drm_free(map, sizeof(*map), DRM_MEM_MAPS);
L
Linus Torvalds 已提交
233 234 235 236
		return -EINVAL;
	}

	list = drm_alloc(sizeof(*list), DRM_MEM_MAPS);
D
Dave Airlie 已提交
237
	if (!list) {
L
Linus Torvalds 已提交
238 239 240 241 242 243
		drm_free(map, sizeof(*map), DRM_MEM_MAPS);
		return -EINVAL;
	}
	memset(list, 0, sizeof(*list));
	list->map = map;

D
Dave Airlie 已提交
244
	mutex_lock(&dev->struct_mutex);
L
Linus Torvalds 已提交
245
	list_add(&list->head, &dev->maplist->head);
246

247
	/* Assign a 32-bit handle */
D
Dave Airlie 已提交
248
	/* We do it here so that dev->struct_mutex protects the increment */
249 250 251 252 253 254 255 256 257 258 259
	user_token = (map->type == _DRM_SHM) ? (unsigned long)map->handle :
		map->offset;
	ret = drm_map_handle(dev, &list->hash, user_token, FALSE);
	if (ret) {
		drm_free(map, sizeof(*map), DRM_MEM_MAPS);
		drm_free(list, sizeof(*list), DRM_MEM_MAPS);
		mutex_unlock(&dev->struct_mutex);
		return ret;
	}

	list->user_token = list->hash.key;
D
Dave Airlie 已提交
260
	mutex_unlock(&dev->struct_mutex);
L
Linus Torvalds 已提交
261

262
	*maplist = list;
263 264
	return 0;
}
265

D
Dave Airlie 已提交
266
int drm_addmap(drm_device_t * dev, unsigned int offset,
267
	       unsigned int size, drm_map_type_t type,
D
Dave Airlie 已提交
268
	       drm_map_flags_t flags, drm_local_map_t ** map_ptr)
269 270 271 272 273 274 275 276 277
{
	drm_map_list_t *list;
	int rc;

	rc = drm_addmap_core(dev, offset, size, type, flags, &list);
	if (!rc)
		*map_ptr = list->map;
	return rc;
}
D
Dave Airlie 已提交
278

279 280 281 282 283 284 285 286
EXPORT_SYMBOL(drm_addmap);

int drm_addmap_ioctl(struct inode *inode, struct file *filp,
		     unsigned int cmd, unsigned long arg)
{
	drm_file_t *priv = filp->private_data;
	drm_device_t *dev = priv->head->dev;
	drm_map_t map;
287
	drm_map_list_t *maplist;
288 289 290 291 292 293
	drm_map_t __user *argp = (void __user *)arg;
	int err;

	if (!(filp->f_mode & 3))
		return -EACCES;	/* Require read/write */

D
Dave Airlie 已提交
294
	if (copy_from_user(&map, argp, sizeof(map))) {
L
Linus Torvalds 已提交
295
		return -EFAULT;
296 297
	}

298 299 300
	if (!(capable(CAP_SYS_ADMIN) || map.type == _DRM_AGP))
		return -EPERM;

301 302
	err = drm_addmap_core(dev, map.offset, map.size, map.type, map.flags,
			      &maplist);
303

D
Dave Airlie 已提交
304
	if (err)
305
		return err;
306

307
	if (copy_to_user(argp, maplist->map, sizeof(drm_map_t)))
308
		return -EFAULT;
309 310 311

	/* avoid a warning on 64-bit, this casting isn't very nice, but the API is set so too late */
	if (put_user((void *)(unsigned long)maplist->user_token, &argp->handle))
312
		return -EFAULT;
L
Linus Torvalds 已提交
313
	return 0;
D
Dave Airlie 已提交
314
}
L
Linus Torvalds 已提交
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329

/**
 * Remove a map private from list and deallocate resources if the mapping
 * isn't in use.
 *
 * \param inode device inode.
 * \param filp file pointer.
 * \param cmd command.
 * \param arg pointer to a drm_map_t structure.
 * \return zero on success or a negative value on error.
 *
 * Searches the map on drm_device::maplist, removes it from the list, see if
 * its being used, and free any associate resource (such as MTRR's) if it's not
 * being on use.
 *
330
 * \sa drm_addmap
L
Linus Torvalds 已提交
331
 */
332
int drm_rmmap_locked(drm_device_t *dev, drm_local_map_t *map)
L
Linus Torvalds 已提交
333 334 335
{
	struct list_head *list;
	drm_map_list_t *r_list = NULL;
D
Dave Airlie 已提交
336
	drm_dma_handle_t dmah;
L
Linus Torvalds 已提交
337

D
Dave Airlie 已提交
338
	/* Find the list entry for the map and remove it */
L
Linus Torvalds 已提交
339 340 341
	list_for_each(list, &dev->maplist->head) {
		r_list = list_entry(list, drm_map_list_t, head);

D
Dave Airlie 已提交
342 343
		if (r_list->map == map) {
			list_del(list);
344
			drm_ht_remove_key(&dev->map_hash, r_list->user_token);
D
Dave Airlie 已提交
345 346 347
			drm_free(list, sizeof(*list), DRM_MEM_MAPS);
			break;
		}
L
Linus Torvalds 已提交
348 349
	}

D
Dave Airlie 已提交
350 351
	/* List has wrapped around to the head pointer, or it's empty and we
	 * didn't find anything.
L
Linus Torvalds 已提交
352
	 */
D
Dave Airlie 已提交
353
	if (list == (&dev->maplist->head)) {
L
Linus Torvalds 已提交
354 355 356
		return -EINVAL;
	}

D
Dave Airlie 已提交
357 358 359 360 361 362 363
	switch (map->type) {
	case _DRM_REGISTERS:
		drm_ioremapfree(map->handle, map->size, dev);
		/* FALLTHROUGH */
	case _DRM_FRAME_BUFFER:
		if (drm_core_has_MTRR(dev) && map->mtrr >= 0) {
			int retcode;
D
Dave Airlie 已提交
364 365
			retcode = mtrr_del(map->mtrr, map->offset, map->size);
			DRM_DEBUG("mtrr_del=%d\n", retcode);
L
Linus Torvalds 已提交
366
		}
D
Dave Airlie 已提交
367 368 369 370 371 372 373 374 375 376 377 378 379
		break;
	case _DRM_SHM:
		vfree(map->handle);
		break;
	case _DRM_AGP:
	case _DRM_SCATTER_GATHER:
		break;
	case _DRM_CONSISTENT:
		dmah.vaddr = map->handle;
		dmah.busaddr = map->offset;
		dmah.size = map->size;
		__drm_pci_free(dev, &dmah);
		break;
L
Linus Torvalds 已提交
380
	}
D
Dave Airlie 已提交
381 382
	drm_free(map, sizeof(*map), DRM_MEM_MAPS);

L
Linus Torvalds 已提交
383 384
	return 0;
}
D
Dave Airlie 已提交
385

386
int drm_rmmap(drm_device_t *dev, drm_local_map_t *map)
D
Dave Airlie 已提交
387 388 389
{
	int ret;

D
Dave Airlie 已提交
390
	mutex_lock(&dev->struct_mutex);
D
Dave Airlie 已提交
391
	ret = drm_rmmap_locked(dev, map);
D
Dave Airlie 已提交
392
	mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
393 394 395

	return ret;
}
396

D
Dave Airlie 已提交
397 398 399 400 401 402 403 404 405
/* The rmmap ioctl appears to be unnecessary.  All mappings are torn down on
 * the last close of the device, and this is necessary for cleanup when things
 * exit uncleanly.  Therefore, having userland manually remove mappings seems
 * like a pointless exercise since they're going away anyway.
 *
 * One use case might be after addmap is allowed for normal users for SHM and
 * gets used by drivers that the server doesn't need to care about.  This seems
 * unlikely.
 */
406 407 408 409 410 411
int drm_rmmap_ioctl(struct inode *inode, struct file *filp,
		    unsigned int cmd, unsigned long arg)
{
	drm_file_t *priv = filp->private_data;
	drm_device_t *dev = priv->head->dev;
	drm_map_t request;
D
Dave Airlie 已提交
412 413 414
	drm_local_map_t *map = NULL;
	struct list_head *list;
	int ret;
415

D
Dave Airlie 已提交
416
	if (copy_from_user(&request, (drm_map_t __user *) arg, sizeof(request))) {
417 418 419
		return -EFAULT;
	}

D
Dave Airlie 已提交
420
	mutex_lock(&dev->struct_mutex);
D
Dave Airlie 已提交
421 422 423 424
	list_for_each(list, &dev->maplist->head) {
		drm_map_list_t *r_list = list_entry(list, drm_map_list_t, head);

		if (r_list->map &&
D
Dave Airlie 已提交
425
		    r_list->user_token == (unsigned long)request.handle &&
D
Dave Airlie 已提交
426 427 428 429 430 431 432 433 434 435
		    r_list->map->flags & _DRM_REMOVABLE) {
			map = r_list->map;
			break;
		}
	}

	/* List has wrapped around to the head pointer, or its empty we didn't
	 * find anything.
	 */
	if (list == (&dev->maplist->head)) {
D
Dave Airlie 已提交
436
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
437 438 439
		return -EINVAL;
	}

T
Thomas Hellstrom 已提交
440 441
	if (!map) {
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
442
		return -EINVAL;
T
Thomas Hellstrom 已提交
443
	}
D
Dave Airlie 已提交
444 445 446

	/* Register and framebuffer maps are permanent */
	if ((map->type == _DRM_REGISTERS) || (map->type == _DRM_FRAME_BUFFER)) {
D
Dave Airlie 已提交
447
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
448 449 450 451 452
		return 0;
	}

	ret = drm_rmmap_locked(dev, map);

D
Dave Airlie 已提交
453
	mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
454 455

	return ret;
456
}
L
Linus Torvalds 已提交
457 458 459 460

/**
 * Cleanup after an error on one of the addbufs() functions.
 *
D
Dave Airlie 已提交
461
 * \param dev DRM device.
L
Linus Torvalds 已提交
462 463 464 465
 * \param entry buffer entry where the error occurred.
 *
 * Frees any pages and buffers associated with the given entry.
 */
D
Dave Airlie 已提交
466
static void drm_cleanup_buf_error(drm_device_t * dev, drm_buf_entry_t * entry)
L
Linus Torvalds 已提交
467 468 469 470 471 472
{
	int i;

	if (entry->seg_count) {
		for (i = 0; i < entry->seg_count; i++) {
			if (entry->seglist[i]) {
D
Dave Airlie 已提交
473
				drm_pci_free(dev, entry->seglist[i]);
L
Linus Torvalds 已提交
474 475 476
			}
		}
		drm_free(entry->seglist,
D
Dave Airlie 已提交
477 478
			 entry->seg_count *
			 sizeof(*entry->seglist), DRM_MEM_SEGS);
L
Linus Torvalds 已提交
479 480 481 482

		entry->seg_count = 0;
	}

D
Dave Airlie 已提交
483 484
	if (entry->buf_count) {
		for (i = 0; i < entry->buf_count; i++) {
L
Linus Torvalds 已提交
485 486
			if (entry->buflist[i].dev_private) {
				drm_free(entry->buflist[i].dev_private,
D
Dave Airlie 已提交
487 488
					 entry->buflist[i].dev_priv_size,
					 DRM_MEM_BUFS);
L
Linus Torvalds 已提交
489 490 491
			}
		}
		drm_free(entry->buflist,
D
Dave Airlie 已提交
492 493
			 entry->buf_count *
			 sizeof(*entry->buflist), DRM_MEM_BUFS);
L
Linus Torvalds 已提交
494 495 496 497 498 499 500

		entry->buf_count = 0;
	}
}

#if __OS_HAS_AGP
/**
501
 * Add AGP buffers for DMA transfers.
L
Linus Torvalds 已提交
502
 *
503 504
 * \param dev drm_device_t to which the buffers are to be added.
 * \param request pointer to a drm_buf_desc_t describing the request.
L
Linus Torvalds 已提交
505
 * \return zero on success or a negative number on failure.
D
Dave Airlie 已提交
506
 *
L
Linus Torvalds 已提交
507 508 509 510
 * After some sanity checks creates a drm_buf structure for each buffer and
 * reallocates the buffer list of the same size order to accommodate the new
 * buffers.
 */
D
Dave Airlie 已提交
511
int drm_addbufs_agp(drm_device_t * dev, drm_buf_desc_t * request)
L
Linus Torvalds 已提交
512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527
{
	drm_device_dma_t *dma = dev->dma;
	drm_buf_entry_t *entry;
	drm_buf_t *buf;
	unsigned long offset;
	unsigned long agp_offset;
	int count;
	int order;
	int size;
	int alignment;
	int page_order;
	int total;
	int byte_count;
	int i;
	drm_buf_t **temp_buflist;

D
Dave Airlie 已提交
528 529
	if (!dma)
		return -EINVAL;
L
Linus Torvalds 已提交
530

531 532
	count = request->count;
	order = drm_order(request->size);
L
Linus Torvalds 已提交
533 534
	size = 1 << order;

D
Dave Airlie 已提交
535 536
	alignment = (request->flags & _DRM_PAGE_ALIGN)
	    ? PAGE_ALIGN(size) : size;
L
Linus Torvalds 已提交
537 538 539 540
	page_order = order - PAGE_SHIFT > 0 ? order - PAGE_SHIFT : 0;
	total = PAGE_SIZE << page_order;

	byte_count = 0;
541
	agp_offset = dev->agp->base + request->agp_start;
L
Linus Torvalds 已提交
542

D
Dave Airlie 已提交
543 544 545
	DRM_DEBUG("count:      %d\n", count);
	DRM_DEBUG("order:      %d\n", order);
	DRM_DEBUG("size:       %d\n", size);
546
	DRM_DEBUG("agp_offset: %lx\n", agp_offset);
D
Dave Airlie 已提交
547 548 549
	DRM_DEBUG("alignment:  %d\n", alignment);
	DRM_DEBUG("page_order: %d\n", page_order);
	DRM_DEBUG("total:      %d\n", total);
L
Linus Torvalds 已提交
550

D
Dave Airlie 已提交
551 552 553 554
	if (order < DRM_MIN_ORDER || order > DRM_MAX_ORDER)
		return -EINVAL;
	if (dev->queue_count)
		return -EBUSY;	/* Not while in use */
L
Linus Torvalds 已提交
555

D
Dave Airlie 已提交
556 557 558
	spin_lock(&dev->count_lock);
	if (dev->buf_use) {
		spin_unlock(&dev->count_lock);
L
Linus Torvalds 已提交
559 560
		return -EBUSY;
	}
D
Dave Airlie 已提交
561 562
	atomic_inc(&dev->buf_alloc);
	spin_unlock(&dev->count_lock);
L
Linus Torvalds 已提交
563

D
Dave Airlie 已提交
564
	mutex_lock(&dev->struct_mutex);
L
Linus Torvalds 已提交
565
	entry = &dma->bufs[order];
D
Dave Airlie 已提交
566
	if (entry->buf_count) {
D
Dave Airlie 已提交
567
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
568 569
		atomic_dec(&dev->buf_alloc);
		return -ENOMEM;	/* May only call once for each order */
L
Linus Torvalds 已提交
570 571 572
	}

	if (count < 0 || count > 4096) {
D
Dave Airlie 已提交
573
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
574
		atomic_dec(&dev->buf_alloc);
L
Linus Torvalds 已提交
575 576 577
		return -EINVAL;
	}

D
Dave Airlie 已提交
578 579 580
	entry->buflist = drm_alloc(count * sizeof(*entry->buflist),
				   DRM_MEM_BUFS);
	if (!entry->buflist) {
D
Dave Airlie 已提交
581
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
582
		atomic_dec(&dev->buf_alloc);
L
Linus Torvalds 已提交
583 584
		return -ENOMEM;
	}
D
Dave Airlie 已提交
585
	memset(entry->buflist, 0, count * sizeof(*entry->buflist));
L
Linus Torvalds 已提交
586 587 588 589 590 591

	entry->buf_size = size;
	entry->page_order = page_order;

	offset = 0;

D
Dave Airlie 已提交
592 593 594 595 596 597
	while (entry->buf_count < count) {
		buf = &entry->buflist[entry->buf_count];
		buf->idx = dma->buf_count + entry->buf_count;
		buf->total = alignment;
		buf->order = order;
		buf->used = 0;
L
Linus Torvalds 已提交
598

D
Dave Airlie 已提交
599
		buf->offset = (dma->byte_count + offset);
L
Linus Torvalds 已提交
600 601
		buf->bus_address = agp_offset + offset;
		buf->address = (void *)(agp_offset + offset);
D
Dave Airlie 已提交
602
		buf->next = NULL;
L
Linus Torvalds 已提交
603 604
		buf->waiting = 0;
		buf->pending = 0;
D
Dave Airlie 已提交
605 606
		init_waitqueue_head(&buf->dma_wait);
		buf->filp = NULL;
L
Linus Torvalds 已提交
607 608

		buf->dev_priv_size = dev->driver->dev_priv_size;
D
Dave Airlie 已提交
609 610
		buf->dev_private = drm_alloc(buf->dev_priv_size, DRM_MEM_BUFS);
		if (!buf->dev_private) {
L
Linus Torvalds 已提交
611 612
			/* Set count correctly so we free the proper amount. */
			entry->buf_count = count;
D
Dave Airlie 已提交
613
			drm_cleanup_buf_error(dev, entry);
D
Dave Airlie 已提交
614
			mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
615
			atomic_dec(&dev->buf_alloc);
L
Linus Torvalds 已提交
616 617
			return -ENOMEM;
		}
D
Dave Airlie 已提交
618
		memset(buf->dev_private, 0, buf->dev_priv_size);
L
Linus Torvalds 已提交
619

D
Dave Airlie 已提交
620
		DRM_DEBUG("buffer %d @ %p\n", entry->buf_count, buf->address);
L
Linus Torvalds 已提交
621 622 623 624 625 626

		offset += alignment;
		entry->buf_count++;
		byte_count += PAGE_SIZE << page_order;
	}

D
Dave Airlie 已提交
627
	DRM_DEBUG("byte_count: %d\n", byte_count);
L
Linus Torvalds 已提交
628

D
Dave Airlie 已提交
629 630 631 632 633
	temp_buflist = drm_realloc(dma->buflist,
				   dma->buf_count * sizeof(*dma->buflist),
				   (dma->buf_count + entry->buf_count)
				   * sizeof(*dma->buflist), DRM_MEM_BUFS);
	if (!temp_buflist) {
L
Linus Torvalds 已提交
634
		/* Free the entry because it isn't valid */
D
Dave Airlie 已提交
635
		drm_cleanup_buf_error(dev, entry);
D
Dave Airlie 已提交
636
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
637
		atomic_dec(&dev->buf_alloc);
L
Linus Torvalds 已提交
638 639 640 641
		return -ENOMEM;
	}
	dma->buflist = temp_buflist;

D
Dave Airlie 已提交
642
	for (i = 0; i < entry->buf_count; i++) {
L
Linus Torvalds 已提交
643 644 645 646
		dma->buflist[i + dma->buf_count] = &entry->buflist[i];
	}

	dma->buf_count += entry->buf_count;
647 648
	dma->seg_count += entry->seg_count;
	dma->page_count += byte_count >> PAGE_SHIFT;
L
Linus Torvalds 已提交
649 650
	dma->byte_count += byte_count;

D
Dave Airlie 已提交
651 652
	DRM_DEBUG("dma->buf_count : %d\n", dma->buf_count);
	DRM_DEBUG("entry->buf_count : %d\n", entry->buf_count);
L
Linus Torvalds 已提交
653

D
Dave Airlie 已提交
654
	mutex_unlock(&dev->struct_mutex);
L
Linus Torvalds 已提交
655

656 657
	request->count = entry->buf_count;
	request->size = size;
L
Linus Torvalds 已提交
658 659 660

	dma->flags = _DRM_DMA_USE_AGP;

D
Dave Airlie 已提交
661
	atomic_dec(&dev->buf_alloc);
L
Linus Torvalds 已提交
662 663
	return 0;
}
664
EXPORT_SYMBOL(drm_addbufs_agp);
D
Dave Airlie 已提交
665
#endif				/* __OS_HAS_AGP */
L
Linus Torvalds 已提交
666

D
Dave Airlie 已提交
667
int drm_addbufs_pci(drm_device_t * dev, drm_buf_desc_t * request)
L
Linus Torvalds 已提交
668 669 670 671 672 673 674 675
{
	drm_device_dma_t *dma = dev->dma;
	int count;
	int order;
	int size;
	int total;
	int page_order;
	drm_buf_entry_t *entry;
D
Dave Airlie 已提交
676
	drm_dma_handle_t *dmah;
L
Linus Torvalds 已提交
677 678 679 680 681 682 683 684 685
	drm_buf_t *buf;
	int alignment;
	unsigned long offset;
	int i;
	int byte_count;
	int page_count;
	unsigned long *temp_pagelist;
	drm_buf_t **temp_buflist;

D
Dave Airlie 已提交
686 687
	if (!drm_core_check_feature(dev, DRIVER_PCI_DMA))
		return -EINVAL;
688

D
Dave Airlie 已提交
689 690
	if (!dma)
		return -EINVAL;
L
Linus Torvalds 已提交
691

692 693 694
	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;

695 696
	count = request->count;
	order = drm_order(request->size);
L
Linus Torvalds 已提交
697 698
	size = 1 << order;

D
Dave Airlie 已提交
699 700
	DRM_DEBUG("count=%d, size=%d (%d), order=%d, queue_count=%d\n",
		  request->count, request->size, size, order, dev->queue_count);
L
Linus Torvalds 已提交
701

D
Dave Airlie 已提交
702 703 704 705
	if (order < DRM_MIN_ORDER || order > DRM_MAX_ORDER)
		return -EINVAL;
	if (dev->queue_count)
		return -EBUSY;	/* Not while in use */
L
Linus Torvalds 已提交
706

707
	alignment = (request->flags & _DRM_PAGE_ALIGN)
D
Dave Airlie 已提交
708
	    ? PAGE_ALIGN(size) : size;
L
Linus Torvalds 已提交
709 710 711
	page_order = order - PAGE_SHIFT > 0 ? order - PAGE_SHIFT : 0;
	total = PAGE_SIZE << page_order;

D
Dave Airlie 已提交
712 713 714
	spin_lock(&dev->count_lock);
	if (dev->buf_use) {
		spin_unlock(&dev->count_lock);
L
Linus Torvalds 已提交
715 716
		return -EBUSY;
	}
D
Dave Airlie 已提交
717 718
	atomic_inc(&dev->buf_alloc);
	spin_unlock(&dev->count_lock);
L
Linus Torvalds 已提交
719

D
Dave Airlie 已提交
720
	mutex_lock(&dev->struct_mutex);
L
Linus Torvalds 已提交
721
	entry = &dma->bufs[order];
D
Dave Airlie 已提交
722
	if (entry->buf_count) {
D
Dave Airlie 已提交
723
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
724
		atomic_dec(&dev->buf_alloc);
L
Linus Torvalds 已提交
725 726 727 728
		return -ENOMEM;	/* May only call once for each order */
	}

	if (count < 0 || count > 4096) {
D
Dave Airlie 已提交
729
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
730
		atomic_dec(&dev->buf_alloc);
L
Linus Torvalds 已提交
731 732 733
		return -EINVAL;
	}

D
Dave Airlie 已提交
734 735 736
	entry->buflist = drm_alloc(count * sizeof(*entry->buflist),
				   DRM_MEM_BUFS);
	if (!entry->buflist) {
D
Dave Airlie 已提交
737
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
738
		atomic_dec(&dev->buf_alloc);
L
Linus Torvalds 已提交
739 740
		return -ENOMEM;
	}
D
Dave Airlie 已提交
741 742 743 744 745 746 747
	memset(entry->buflist, 0, count * sizeof(*entry->buflist));

	entry->seglist = drm_alloc(count * sizeof(*entry->seglist),
				   DRM_MEM_SEGS);
	if (!entry->seglist) {
		drm_free(entry->buflist,
			 count * sizeof(*entry->buflist), DRM_MEM_BUFS);
D
Dave Airlie 已提交
748
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
749
		atomic_dec(&dev->buf_alloc);
L
Linus Torvalds 已提交
750 751
		return -ENOMEM;
	}
D
Dave Airlie 已提交
752
	memset(entry->seglist, 0, count * sizeof(*entry->seglist));
L
Linus Torvalds 已提交
753 754 755 756

	/* Keep the original pagelist until we know all the allocations
	 * have succeeded
	 */
D
Dave Airlie 已提交
757 758
	temp_pagelist = drm_alloc((dma->page_count + (count << page_order))
				  * sizeof(*dma->pagelist), DRM_MEM_PAGES);
L
Linus Torvalds 已提交
759
	if (!temp_pagelist) {
D
Dave Airlie 已提交
760 761 762 763
		drm_free(entry->buflist,
			 count * sizeof(*entry->buflist), DRM_MEM_BUFS);
		drm_free(entry->seglist,
			 count * sizeof(*entry->seglist), DRM_MEM_SEGS);
D
Dave Airlie 已提交
764
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
765
		atomic_dec(&dev->buf_alloc);
L
Linus Torvalds 已提交
766 767 768
		return -ENOMEM;
	}
	memcpy(temp_pagelist,
D
Dave Airlie 已提交
769 770 771
	       dma->pagelist, dma->page_count * sizeof(*dma->pagelist));
	DRM_DEBUG("pagelist: %d entries\n",
		  dma->page_count + (count << page_order));
L
Linus Torvalds 已提交
772

D
Dave Airlie 已提交
773
	entry->buf_size = size;
L
Linus Torvalds 已提交
774 775 776 777
	entry->page_order = page_order;
	byte_count = 0;
	page_count = 0;

D
Dave Airlie 已提交
778
	while (entry->buf_count < count) {
D
Dave Airlie 已提交
779 780 781 782
		
		dmah = drm_pci_alloc(dev, PAGE_SIZE << page_order, 0x1000, 0xfffffffful);
		
		if (!dmah) {
L
Linus Torvalds 已提交
783 784 785 786
			/* Set count correctly so we free the proper amount. */
			entry->buf_count = count;
			entry->seg_count = count;
			drm_cleanup_buf_error(dev, entry);
D
Dave Airlie 已提交
787 788 789
			drm_free(temp_pagelist,
				 (dma->page_count + (count << page_order))
				 * sizeof(*dma->pagelist), DRM_MEM_PAGES);
D
Dave Airlie 已提交
790
			mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
791
			atomic_dec(&dev->buf_alloc);
L
Linus Torvalds 已提交
792 793
			return -ENOMEM;
		}
D
Dave Airlie 已提交
794
		entry->seglist[entry->seg_count++] = dmah;
D
Dave Airlie 已提交
795 796 797
		for (i = 0; i < (1 << page_order); i++) {
			DRM_DEBUG("page %d @ 0x%08lx\n",
				  dma->page_count + page_count,
D
Dave Airlie 已提交
798
				  (unsigned long)dmah->vaddr + PAGE_SIZE * i);
L
Linus Torvalds 已提交
799
			temp_pagelist[dma->page_count + page_count++]
D
Dave Airlie 已提交
800
				= (unsigned long)dmah->vaddr + PAGE_SIZE * i;
L
Linus Torvalds 已提交
801
		}
D
Dave Airlie 已提交
802 803 804 805 806 807 808 809 810
		for (offset = 0;
		     offset + size <= total && entry->buf_count < count;
		     offset += alignment, ++entry->buf_count) {
			buf = &entry->buflist[entry->buf_count];
			buf->idx = dma->buf_count + entry->buf_count;
			buf->total = alignment;
			buf->order = order;
			buf->used = 0;
			buf->offset = (dma->byte_count + byte_count + offset);
D
Dave Airlie 已提交
811 812
			buf->address = (void *)(dmah->vaddr + offset);
			buf->bus_address = dmah->busaddr + offset;
D
Dave Airlie 已提交
813
			buf->next = NULL;
L
Linus Torvalds 已提交
814 815
			buf->waiting = 0;
			buf->pending = 0;
D
Dave Airlie 已提交
816 817
			init_waitqueue_head(&buf->dma_wait);
			buf->filp = NULL;
L
Linus Torvalds 已提交
818 819

			buf->dev_priv_size = dev->driver->dev_priv_size;
D
Dave Airlie 已提交
820 821 822
			buf->dev_private = drm_alloc(buf->dev_priv_size,
						     DRM_MEM_BUFS);
			if (!buf->dev_private) {
L
Linus Torvalds 已提交
823 824 825
				/* Set count correctly so we free the proper amount. */
				entry->buf_count = count;
				entry->seg_count = count;
D
Dave Airlie 已提交
826 827 828 829 830 831
				drm_cleanup_buf_error(dev, entry);
				drm_free(temp_pagelist,
					 (dma->page_count +
					  (count << page_order))
					 * sizeof(*dma->pagelist),
					 DRM_MEM_PAGES);
D
Dave Airlie 已提交
832
				mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
833
				atomic_dec(&dev->buf_alloc);
L
Linus Torvalds 已提交
834 835
				return -ENOMEM;
			}
D
Dave Airlie 已提交
836
			memset(buf->dev_private, 0, buf->dev_priv_size);
L
Linus Torvalds 已提交
837

D
Dave Airlie 已提交
838 839
			DRM_DEBUG("buffer %d @ %p\n",
				  entry->buf_count, buf->address);
L
Linus Torvalds 已提交
840 841 842 843
		}
		byte_count += PAGE_SIZE << page_order;
	}

D
Dave Airlie 已提交
844 845 846 847
	temp_buflist = drm_realloc(dma->buflist,
				   dma->buf_count * sizeof(*dma->buflist),
				   (dma->buf_count + entry->buf_count)
				   * sizeof(*dma->buflist), DRM_MEM_BUFS);
L
Linus Torvalds 已提交
848 849
	if (!temp_buflist) {
		/* Free the entry because it isn't valid */
D
Dave Airlie 已提交
850 851 852 853
		drm_cleanup_buf_error(dev, entry);
		drm_free(temp_pagelist,
			 (dma->page_count + (count << page_order))
			 * sizeof(*dma->pagelist), DRM_MEM_PAGES);
D
Dave Airlie 已提交
854
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
855
		atomic_dec(&dev->buf_alloc);
L
Linus Torvalds 已提交
856 857 858 859
		return -ENOMEM;
	}
	dma->buflist = temp_buflist;

D
Dave Airlie 已提交
860
	for (i = 0; i < entry->buf_count; i++) {
L
Linus Torvalds 已提交
861 862 863 864 865 866 867 868
		dma->buflist[i + dma->buf_count] = &entry->buflist[i];
	}

	/* No allocations failed, so now we can replace the orginal pagelist
	 * with the new one.
	 */
	if (dma->page_count) {
		drm_free(dma->pagelist,
D
Dave Airlie 已提交
869 870
			 dma->page_count * sizeof(*dma->pagelist),
			 DRM_MEM_PAGES);
L
Linus Torvalds 已提交
871 872 873 874 875 876 877 878
	}
	dma->pagelist = temp_pagelist;

	dma->buf_count += entry->buf_count;
	dma->seg_count += entry->seg_count;
	dma->page_count += entry->seg_count << page_order;
	dma->byte_count += PAGE_SIZE * (entry->seg_count << page_order);

D
Dave Airlie 已提交
879
	mutex_unlock(&dev->struct_mutex);
L
Linus Torvalds 已提交
880

881 882
	request->count = entry->buf_count;
	request->size = size;
L
Linus Torvalds 已提交
883

D
Dave Airlie 已提交
884
	atomic_dec(&dev->buf_alloc);
L
Linus Torvalds 已提交
885 886 887
	return 0;

}
888
EXPORT_SYMBOL(drm_addbufs_pci);
L
Linus Torvalds 已提交
889

D
Dave Airlie 已提交
890
static int drm_addbufs_sg(drm_device_t * dev, drm_buf_desc_t * request)
L
Linus Torvalds 已提交
891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906
{
	drm_device_dma_t *dma = dev->dma;
	drm_buf_entry_t *entry;
	drm_buf_t *buf;
	unsigned long offset;
	unsigned long agp_offset;
	int count;
	int order;
	int size;
	int alignment;
	int page_order;
	int total;
	int byte_count;
	int i;
	drm_buf_t **temp_buflist;

D
Dave Airlie 已提交
907 908 909 910 911
	if (!drm_core_check_feature(dev, DRIVER_SG))
		return -EINVAL;

	if (!dma)
		return -EINVAL;
L
Linus Torvalds 已提交
912

913 914 915
	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;

916 917
	count = request->count;
	order = drm_order(request->size);
L
Linus Torvalds 已提交
918 919
	size = 1 << order;

D
Dave Airlie 已提交
920 921
	alignment = (request->flags & _DRM_PAGE_ALIGN)
	    ? PAGE_ALIGN(size) : size;
L
Linus Torvalds 已提交
922 923 924 925
	page_order = order - PAGE_SHIFT > 0 ? order - PAGE_SHIFT : 0;
	total = PAGE_SIZE << page_order;

	byte_count = 0;
926
	agp_offset = request->agp_start;
L
Linus Torvalds 已提交
927

D
Dave Airlie 已提交
928 929 930 931 932 933 934
	DRM_DEBUG("count:      %d\n", count);
	DRM_DEBUG("order:      %d\n", order);
	DRM_DEBUG("size:       %d\n", size);
	DRM_DEBUG("agp_offset: %lu\n", agp_offset);
	DRM_DEBUG("alignment:  %d\n", alignment);
	DRM_DEBUG("page_order: %d\n", page_order);
	DRM_DEBUG("total:      %d\n", total);
L
Linus Torvalds 已提交
935

D
Dave Airlie 已提交
936 937 938 939
	if (order < DRM_MIN_ORDER || order > DRM_MAX_ORDER)
		return -EINVAL;
	if (dev->queue_count)
		return -EBUSY;	/* Not while in use */
L
Linus Torvalds 已提交
940

D
Dave Airlie 已提交
941 942 943
	spin_lock(&dev->count_lock);
	if (dev->buf_use) {
		spin_unlock(&dev->count_lock);
L
Linus Torvalds 已提交
944 945
		return -EBUSY;
	}
D
Dave Airlie 已提交
946 947
	atomic_inc(&dev->buf_alloc);
	spin_unlock(&dev->count_lock);
L
Linus Torvalds 已提交
948

D
Dave Airlie 已提交
949
	mutex_lock(&dev->struct_mutex);
L
Linus Torvalds 已提交
950
	entry = &dma->bufs[order];
D
Dave Airlie 已提交
951
	if (entry->buf_count) {
D
Dave Airlie 已提交
952
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
953 954
		atomic_dec(&dev->buf_alloc);
		return -ENOMEM;	/* May only call once for each order */
L
Linus Torvalds 已提交
955 956 957
	}

	if (count < 0 || count > 4096) {
D
Dave Airlie 已提交
958
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
959
		atomic_dec(&dev->buf_alloc);
L
Linus Torvalds 已提交
960 961 962
		return -EINVAL;
	}

D
Dave Airlie 已提交
963 964 965
	entry->buflist = drm_alloc(count * sizeof(*entry->buflist),
				   DRM_MEM_BUFS);
	if (!entry->buflist) {
D
Dave Airlie 已提交
966
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
967
		atomic_dec(&dev->buf_alloc);
L
Linus Torvalds 已提交
968 969
		return -ENOMEM;
	}
D
Dave Airlie 已提交
970
	memset(entry->buflist, 0, count * sizeof(*entry->buflist));
L
Linus Torvalds 已提交
971 972 973 974 975 976

	entry->buf_size = size;
	entry->page_order = page_order;

	offset = 0;

D
Dave Airlie 已提交
977 978 979 980 981 982
	while (entry->buf_count < count) {
		buf = &entry->buflist[entry->buf_count];
		buf->idx = dma->buf_count + entry->buf_count;
		buf->total = alignment;
		buf->order = order;
		buf->used = 0;
L
Linus Torvalds 已提交
983

D
Dave Airlie 已提交
984
		buf->offset = (dma->byte_count + offset);
L
Linus Torvalds 已提交
985
		buf->bus_address = agp_offset + offset;
D
Dave Airlie 已提交
986
		buf->address = (void *)(agp_offset + offset
987
					+ (unsigned long)dev->sg->virtual);
D
Dave Airlie 已提交
988
		buf->next = NULL;
L
Linus Torvalds 已提交
989 990
		buf->waiting = 0;
		buf->pending = 0;
D
Dave Airlie 已提交
991 992
		init_waitqueue_head(&buf->dma_wait);
		buf->filp = NULL;
L
Linus Torvalds 已提交
993 994

		buf->dev_priv_size = dev->driver->dev_priv_size;
D
Dave Airlie 已提交
995 996
		buf->dev_private = drm_alloc(buf->dev_priv_size, DRM_MEM_BUFS);
		if (!buf->dev_private) {
L
Linus Torvalds 已提交
997 998
			/* Set count correctly so we free the proper amount. */
			entry->buf_count = count;
D
Dave Airlie 已提交
999
			drm_cleanup_buf_error(dev, entry);
D
Dave Airlie 已提交
1000
			mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
1001
			atomic_dec(&dev->buf_alloc);
L
Linus Torvalds 已提交
1002 1003 1004
			return -ENOMEM;
		}

D
Dave Airlie 已提交
1005
		memset(buf->dev_private, 0, buf->dev_priv_size);
L
Linus Torvalds 已提交
1006

D
Dave Airlie 已提交
1007
		DRM_DEBUG("buffer %d @ %p\n", entry->buf_count, buf->address);
L
Linus Torvalds 已提交
1008 1009 1010 1011 1012 1013

		offset += alignment;
		entry->buf_count++;
		byte_count += PAGE_SIZE << page_order;
	}

D
Dave Airlie 已提交
1014
	DRM_DEBUG("byte_count: %d\n", byte_count);
L
Linus Torvalds 已提交
1015

D
Dave Airlie 已提交
1016 1017 1018 1019 1020
	temp_buflist = drm_realloc(dma->buflist,
				   dma->buf_count * sizeof(*dma->buflist),
				   (dma->buf_count + entry->buf_count)
				   * sizeof(*dma->buflist), DRM_MEM_BUFS);
	if (!temp_buflist) {
L
Linus Torvalds 已提交
1021
		/* Free the entry because it isn't valid */
D
Dave Airlie 已提交
1022
		drm_cleanup_buf_error(dev, entry);
D
Dave Airlie 已提交
1023
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
1024
		atomic_dec(&dev->buf_alloc);
L
Linus Torvalds 已提交
1025 1026 1027 1028
		return -ENOMEM;
	}
	dma->buflist = temp_buflist;

D
Dave Airlie 已提交
1029
	for (i = 0; i < entry->buf_count; i++) {
L
Linus Torvalds 已提交
1030 1031 1032 1033
		dma->buflist[i + dma->buf_count] = &entry->buflist[i];
	}

	dma->buf_count += entry->buf_count;
1034 1035
	dma->seg_count += entry->seg_count;
	dma->page_count += byte_count >> PAGE_SHIFT;
L
Linus Torvalds 已提交
1036 1037
	dma->byte_count += byte_count;

D
Dave Airlie 已提交
1038 1039
	DRM_DEBUG("dma->buf_count : %d\n", dma->buf_count);
	DRM_DEBUG("entry->buf_count : %d\n", entry->buf_count);
L
Linus Torvalds 已提交
1040

D
Dave Airlie 已提交
1041
	mutex_unlock(&dev->struct_mutex);
L
Linus Torvalds 已提交
1042

1043 1044
	request->count = entry->buf_count;
	request->size = size;
L
Linus Torvalds 已提交
1045 1046 1047

	dma->flags = _DRM_DMA_USE_SG;

D
Dave Airlie 已提交
1048
	atomic_dec(&dev->buf_alloc);
L
Linus Torvalds 已提交
1049 1050 1051
	return 0;
}

D
Dave Airlie 已提交
1052
static int drm_addbufs_fb(drm_device_t * dev, drm_buf_desc_t * request)
D
Dave Airlie 已提交
1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070
{
	drm_device_dma_t *dma = dev->dma;
	drm_buf_entry_t *entry;
	drm_buf_t *buf;
	unsigned long offset;
	unsigned long agp_offset;
	int count;
	int order;
	int size;
	int alignment;
	int page_order;
	int total;
	int byte_count;
	int i;
	drm_buf_t **temp_buflist;

	if (!drm_core_check_feature(dev, DRIVER_FB_DMA))
		return -EINVAL;
D
Dave Airlie 已提交
1071

D
Dave Airlie 已提交
1072 1073 1074
	if (!dma)
		return -EINVAL;

1075 1076 1077
	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;

1078 1079
	count = request->count;
	order = drm_order(request->size);
D
Dave Airlie 已提交
1080 1081
	size = 1 << order;

1082
	alignment = (request->flags & _DRM_PAGE_ALIGN)
D
Dave Airlie 已提交
1083 1084 1085 1086 1087
	    ? PAGE_ALIGN(size) : size;
	page_order = order - PAGE_SHIFT > 0 ? order - PAGE_SHIFT : 0;
	total = PAGE_SIZE << page_order;

	byte_count = 0;
1088
	agp_offset = request->agp_start;
D
Dave Airlie 已提交
1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110

	DRM_DEBUG("count:      %d\n", count);
	DRM_DEBUG("order:      %d\n", order);
	DRM_DEBUG("size:       %d\n", size);
	DRM_DEBUG("agp_offset: %lu\n", agp_offset);
	DRM_DEBUG("alignment:  %d\n", alignment);
	DRM_DEBUG("page_order: %d\n", page_order);
	DRM_DEBUG("total:      %d\n", total);

	if (order < DRM_MIN_ORDER || order > DRM_MAX_ORDER)
		return -EINVAL;
	if (dev->queue_count)
		return -EBUSY;	/* Not while in use */

	spin_lock(&dev->count_lock);
	if (dev->buf_use) {
		spin_unlock(&dev->count_lock);
		return -EBUSY;
	}
	atomic_inc(&dev->buf_alloc);
	spin_unlock(&dev->count_lock);

D
Dave Airlie 已提交
1111
	mutex_lock(&dev->struct_mutex);
D
Dave Airlie 已提交
1112 1113
	entry = &dma->bufs[order];
	if (entry->buf_count) {
D
Dave Airlie 已提交
1114
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
1115 1116 1117 1118 1119
		atomic_dec(&dev->buf_alloc);
		return -ENOMEM;	/* May only call once for each order */
	}

	if (count < 0 || count > 4096) {
D
Dave Airlie 已提交
1120
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
1121 1122 1123 1124 1125 1126 1127
		atomic_dec(&dev->buf_alloc);
		return -EINVAL;
	}

	entry->buflist = drm_alloc(count * sizeof(*entry->buflist),
				   DRM_MEM_BUFS);
	if (!entry->buflist) {
D
Dave Airlie 已提交
1128
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160
		atomic_dec(&dev->buf_alloc);
		return -ENOMEM;
	}
	memset(entry->buflist, 0, count * sizeof(*entry->buflist));

	entry->buf_size = size;
	entry->page_order = page_order;

	offset = 0;

	while (entry->buf_count < count) {
		buf = &entry->buflist[entry->buf_count];
		buf->idx = dma->buf_count + entry->buf_count;
		buf->total = alignment;
		buf->order = order;
		buf->used = 0;

		buf->offset = (dma->byte_count + offset);
		buf->bus_address = agp_offset + offset;
		buf->address = (void *)(agp_offset + offset);
		buf->next = NULL;
		buf->waiting = 0;
		buf->pending = 0;
		init_waitqueue_head(&buf->dma_wait);
		buf->filp = NULL;

		buf->dev_priv_size = dev->driver->dev_priv_size;
		buf->dev_private = drm_alloc(buf->dev_priv_size, DRM_MEM_BUFS);
		if (!buf->dev_private) {
			/* Set count correctly so we free the proper amount. */
			entry->buf_count = count;
			drm_cleanup_buf_error(dev, entry);
D
Dave Airlie 已提交
1161
			mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182
			atomic_dec(&dev->buf_alloc);
			return -ENOMEM;
		}
		memset(buf->dev_private, 0, buf->dev_priv_size);

		DRM_DEBUG("buffer %d @ %p\n", entry->buf_count, buf->address);

		offset += alignment;
		entry->buf_count++;
		byte_count += PAGE_SIZE << page_order;
	}

	DRM_DEBUG("byte_count: %d\n", byte_count);

	temp_buflist = drm_realloc(dma->buflist,
				   dma->buf_count * sizeof(*dma->buflist),
				   (dma->buf_count + entry->buf_count)
				   * sizeof(*dma->buflist), DRM_MEM_BUFS);
	if (!temp_buflist) {
		/* Free the entry because it isn't valid */
		drm_cleanup_buf_error(dev, entry);
D
Dave Airlie 已提交
1183
		mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
1184 1185 1186 1187 1188 1189 1190 1191 1192 1193
		atomic_dec(&dev->buf_alloc);
		return -ENOMEM;
	}
	dma->buflist = temp_buflist;

	for (i = 0; i < entry->buf_count; i++) {
		dma->buflist[i + dma->buf_count] = &entry->buflist[i];
	}

	dma->buf_count += entry->buf_count;
1194 1195
	dma->seg_count += entry->seg_count;
	dma->page_count += byte_count >> PAGE_SHIFT;
D
Dave Airlie 已提交
1196 1197 1198 1199 1200
	dma->byte_count += byte_count;

	DRM_DEBUG("dma->buf_count : %d\n", dma->buf_count);
	DRM_DEBUG("entry->buf_count : %d\n", entry->buf_count);

D
Dave Airlie 已提交
1201
	mutex_unlock(&dev->struct_mutex);
D
Dave Airlie 已提交
1202

1203 1204
	request->count = entry->buf_count;
	request->size = size;
D
Dave Airlie 已提交
1205 1206 1207 1208 1209 1210

	dma->flags = _DRM_DMA_USE_FB;

	atomic_dec(&dev->buf_alloc);
	return 0;
}
1211

D
Dave Airlie 已提交
1212

L
Linus Torvalds 已提交
1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226
/**
 * Add buffers for DMA transfers (ioctl).
 *
 * \param inode device inode.
 * \param filp file pointer.
 * \param cmd command.
 * \param arg pointer to a drm_buf_desc_t request.
 * \return zero on success or a negative number on failure.
 *
 * According with the memory type specified in drm_buf_desc::flags and the
 * build options, it dispatches the call either to addbufs_agp(),
 * addbufs_sg() or addbufs_pci() for AGP, scatter-gather or consistent
 * PCI memory respectively.
 */
D
Dave Airlie 已提交
1227 1228
int drm_addbufs(struct inode *inode, struct file *filp,
		unsigned int cmd, unsigned long arg)
L
Linus Torvalds 已提交
1229 1230 1231 1232
{
	drm_buf_desc_t request;
	drm_file_t *priv = filp->private_data;
	drm_device_t *dev = priv->head->dev;
1233
	int ret;
D
Dave Airlie 已提交
1234

L
Linus Torvalds 已提交
1235 1236 1237
	if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
		return -EINVAL;

D
Dave Airlie 已提交
1238 1239
	if (copy_from_user(&request, (drm_buf_desc_t __user *) arg,
			   sizeof(request)))
L
Linus Torvalds 已提交
1240 1241 1242
		return -EFAULT;

#if __OS_HAS_AGP
D
Dave Airlie 已提交
1243 1244
	if (request.flags & _DRM_AGP_BUFFER)
		ret = drm_addbufs_agp(dev, &request);
L
Linus Torvalds 已提交
1245 1246
	else
#endif
D
Dave Airlie 已提交
1247 1248 1249 1250
	if (request.flags & _DRM_SG_BUFFER)
		ret = drm_addbufs_sg(dev, &request);
	else if (request.flags & _DRM_FB_BUFFER)
		ret = drm_addbufs_fb(dev, &request);
L
Linus Torvalds 已提交
1251
	else
D
Dave Airlie 已提交
1252
		ret = drm_addbufs_pci(dev, &request);
1253

D
Dave Airlie 已提交
1254 1255
	if (ret == 0) {
		if (copy_to_user((void __user *)arg, &request, sizeof(request))) {
1256 1257 1258 1259
			ret = -EFAULT;
		}
	}
	return ret;
L
Linus Torvalds 已提交
1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278
}

/**
 * Get information about the buffer mappings.
 *
 * This was originally mean for debugging purposes, or by a sophisticated
 * client library to determine how best to use the available buffers (e.g.,
 * large buffers can be used for image transfer).
 *
 * \param inode device inode.
 * \param filp file pointer.
 * \param cmd command.
 * \param arg pointer to a drm_buf_info structure.
 * \return zero on success or a negative number on failure.
 *
 * Increments drm_device::buf_use while holding the drm_device::count_lock
 * lock, preventing of allocating more buffers after this call. Information
 * about each requested buffer is then copied into user space.
 */
D
Dave Airlie 已提交
1279 1280
int drm_infobufs(struct inode *inode, struct file *filp,
		 unsigned int cmd, unsigned long arg)
L
Linus Torvalds 已提交
1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292
{
	drm_file_t *priv = filp->private_data;
	drm_device_t *dev = priv->head->dev;
	drm_device_dma_t *dma = dev->dma;
	drm_buf_info_t request;
	drm_buf_info_t __user *argp = (void __user *)arg;
	int i;
	int count;

	if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
		return -EINVAL;

D
Dave Airlie 已提交
1293 1294
	if (!dma)
		return -EINVAL;
L
Linus Torvalds 已提交
1295

D
Dave Airlie 已提交
1296 1297 1298
	spin_lock(&dev->count_lock);
	if (atomic_read(&dev->buf_alloc)) {
		spin_unlock(&dev->count_lock);
L
Linus Torvalds 已提交
1299 1300 1301
		return -EBUSY;
	}
	++dev->buf_use;		/* Can't allocate more after this call */
D
Dave Airlie 已提交
1302
	spin_unlock(&dev->count_lock);
L
Linus Torvalds 已提交
1303

D
Dave Airlie 已提交
1304
	if (copy_from_user(&request, argp, sizeof(request)))
L
Linus Torvalds 已提交
1305 1306
		return -EFAULT;

D
Dave Airlie 已提交
1307 1308 1309
	for (i = 0, count = 0; i < DRM_MAX_ORDER + 1; i++) {
		if (dma->bufs[i].buf_count)
			++count;
L
Linus Torvalds 已提交
1310 1311
	}

D
Dave Airlie 已提交
1312
	DRM_DEBUG("count = %d\n", count);
L
Linus Torvalds 已提交
1313

D
Dave Airlie 已提交
1314 1315 1316 1317 1318
	if (request.count >= count) {
		for (i = 0, count = 0; i < DRM_MAX_ORDER + 1; i++) {
			if (dma->bufs[i].buf_count) {
				drm_buf_desc_t __user *to =
				    &request.list[count];
L
Linus Torvalds 已提交
1319 1320
				drm_buf_entry_t *from = &dma->bufs[i];
				drm_freelist_t *list = &dma->bufs[i].freelist;
D
Dave Airlie 已提交
1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332
				if (copy_to_user(&to->count,
						 &from->buf_count,
						 sizeof(from->buf_count)) ||
				    copy_to_user(&to->size,
						 &from->buf_size,
						 sizeof(from->buf_size)) ||
				    copy_to_user(&to->low_mark,
						 &list->low_mark,
						 sizeof(list->low_mark)) ||
				    copy_to_user(&to->high_mark,
						 &list->high_mark,
						 sizeof(list->high_mark)))
L
Linus Torvalds 已提交
1333 1334
					return -EFAULT;

D
Dave Airlie 已提交
1335 1336 1337 1338 1339 1340
				DRM_DEBUG("%d %d %d %d %d\n",
					  i,
					  dma->bufs[i].buf_count,
					  dma->bufs[i].buf_size,
					  dma->bufs[i].freelist.low_mark,
					  dma->bufs[i].freelist.high_mark);
L
Linus Torvalds 已提交
1341 1342 1343 1344 1345 1346
				++count;
			}
		}
	}
	request.count = count;

D
Dave Airlie 已提交
1347
	if (copy_to_user(argp, &request, sizeof(request)))
L
Linus Torvalds 已提交
1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366
		return -EFAULT;

	return 0;
}

/**
 * Specifies a low and high water mark for buffer allocation
 *
 * \param inode device inode.
 * \param filp file pointer.
 * \param cmd command.
 * \param arg a pointer to a drm_buf_desc structure.
 * \return zero on success or a negative number on failure.
 *
 * Verifies that the size order is bounded between the admissible orders and
 * updates the respective drm_device_dma::bufs entry low and high water mark.
 *
 * \note This ioctl is deprecated and mostly never used.
 */
D
Dave Airlie 已提交
1367 1368
int drm_markbufs(struct inode *inode, struct file *filp,
		 unsigned int cmd, unsigned long arg)
L
Linus Torvalds 已提交
1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379
{
	drm_file_t *priv = filp->private_data;
	drm_device_t *dev = priv->head->dev;
	drm_device_dma_t *dma = dev->dma;
	drm_buf_desc_t request;
	int order;
	drm_buf_entry_t *entry;

	if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
		return -EINVAL;

D
Dave Airlie 已提交
1380 1381
	if (!dma)
		return -EINVAL;
L
Linus Torvalds 已提交
1382

D
Dave Airlie 已提交
1383 1384
	if (copy_from_user(&request,
			   (drm_buf_desc_t __user *) arg, sizeof(request)))
L
Linus Torvalds 已提交
1385 1386
		return -EFAULT;

D
Dave Airlie 已提交
1387 1388 1389 1390 1391
	DRM_DEBUG("%d, %d, %d\n",
		  request.size, request.low_mark, request.high_mark);
	order = drm_order(request.size);
	if (order < DRM_MIN_ORDER || order > DRM_MAX_ORDER)
		return -EINVAL;
L
Linus Torvalds 已提交
1392 1393
	entry = &dma->bufs[order];

D
Dave Airlie 已提交
1394
	if (request.low_mark < 0 || request.low_mark > entry->buf_count)
L
Linus Torvalds 已提交
1395
		return -EINVAL;
D
Dave Airlie 已提交
1396
	if (request.high_mark < 0 || request.high_mark > entry->buf_count)
L
Linus Torvalds 已提交
1397 1398
		return -EINVAL;

D
Dave Airlie 已提交
1399
	entry->freelist.low_mark = request.low_mark;
L
Linus Torvalds 已提交
1400 1401 1402 1403 1404 1405
	entry->freelist.high_mark = request.high_mark;

	return 0;
}

/**
D
Dave Airlie 已提交
1406
 * Unreserve the buffers in list, previously reserved using drmDMA.
L
Linus Torvalds 已提交
1407 1408 1409 1410 1411 1412
 *
 * \param inode device inode.
 * \param filp file pointer.
 * \param cmd command.
 * \param arg pointer to a drm_buf_free structure.
 * \return zero on success or a negative number on failure.
D
Dave Airlie 已提交
1413
 *
L
Linus Torvalds 已提交
1414 1415 1416
 * Calls free_buffer() for each used buffer.
 * This function is primarily used for debugging.
 */
D
Dave Airlie 已提交
1417 1418
int drm_freebufs(struct inode *inode, struct file *filp,
		 unsigned int cmd, unsigned long arg)
L
Linus Torvalds 已提交
1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430
{
	drm_file_t *priv = filp->private_data;
	drm_device_t *dev = priv->head->dev;
	drm_device_dma_t *dma = dev->dma;
	drm_buf_free_t request;
	int i;
	int idx;
	drm_buf_t *buf;

	if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
		return -EINVAL;

D
Dave Airlie 已提交
1431 1432
	if (!dma)
		return -EINVAL;
L
Linus Torvalds 已提交
1433

D
Dave Airlie 已提交
1434 1435
	if (copy_from_user(&request,
			   (drm_buf_free_t __user *) arg, sizeof(request)))
L
Linus Torvalds 已提交
1436 1437
		return -EFAULT;

D
Dave Airlie 已提交
1438 1439 1440
	DRM_DEBUG("%d\n", request.count);
	for (i = 0; i < request.count; i++) {
		if (copy_from_user(&idx, &request.list[i], sizeof(idx)))
L
Linus Torvalds 已提交
1441
			return -EFAULT;
D
Dave Airlie 已提交
1442 1443 1444
		if (idx < 0 || idx >= dma->buf_count) {
			DRM_ERROR("Index %d (of %d max)\n",
				  idx, dma->buf_count - 1);
L
Linus Torvalds 已提交
1445 1446 1447
			return -EINVAL;
		}
		buf = dma->buflist[idx];
D
Dave Airlie 已提交
1448 1449 1450
		if (buf->filp != filp) {
			DRM_ERROR("Process %d freeing buffer not owned\n",
				  current->pid);
L
Linus Torvalds 已提交
1451 1452
			return -EINVAL;
		}
D
Dave Airlie 已提交
1453
		drm_free_buffer(dev, buf);
L
Linus Torvalds 已提交
1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471
	}

	return 0;
}

/**
 * Maps all of the DMA buffers into client-virtual space (ioctl).
 *
 * \param inode device inode.
 * \param filp file pointer.
 * \param cmd command.
 * \param arg pointer to a drm_buf_map structure.
 * \return zero on success or a negative number on failure.
 *
 * Maps the AGP or SG buffer region with do_mmap(), and copies information
 * about each buffer into user space. The PCI buffers are already mapped on the
 * addbufs_pci() call.
 */
D
Dave Airlie 已提交
1472 1473
int drm_mapbufs(struct inode *inode, struct file *filp,
		unsigned int cmd, unsigned long arg)
L
Linus Torvalds 已提交
1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488
{
	drm_file_t *priv = filp->private_data;
	drm_device_t *dev = priv->head->dev;
	drm_device_dma_t *dma = dev->dma;
	drm_buf_map_t __user *argp = (void __user *)arg;
	int retcode = 0;
	const int zero = 0;
	unsigned long virtual;
	unsigned long address;
	drm_buf_map_t request;
	int i;

	if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
		return -EINVAL;

D
Dave Airlie 已提交
1489 1490
	if (!dma)
		return -EINVAL;
L
Linus Torvalds 已提交
1491

D
Dave Airlie 已提交
1492 1493 1494
	spin_lock(&dev->count_lock);
	if (atomic_read(&dev->buf_alloc)) {
		spin_unlock(&dev->count_lock);
L
Linus Torvalds 已提交
1495 1496 1497
		return -EBUSY;
	}
	dev->buf_use++;		/* Can't allocate more after this call */
D
Dave Airlie 已提交
1498
	spin_unlock(&dev->count_lock);
L
Linus Torvalds 已提交
1499

D
Dave Airlie 已提交
1500
	if (copy_from_user(&request, argp, sizeof(request)))
L
Linus Torvalds 已提交
1501 1502
		return -EFAULT;

D
Dave Airlie 已提交
1503
	if (request.count >= dma->buf_count) {
D
Dave Airlie 已提交
1504
		if ((drm_core_has_AGP(dev) && (dma->flags & _DRM_DMA_USE_AGP))
D
Dave Airlie 已提交
1505
		    || (drm_core_check_feature(dev, DRIVER_SG)
D
Dave Airlie 已提交
1506 1507 1508
			&& (dma->flags & _DRM_DMA_USE_SG))
		    || (drm_core_check_feature(dev, DRIVER_FB_DMA)
			&& (dma->flags & _DRM_DMA_USE_FB))) {
L
Linus Torvalds 已提交
1509
			drm_map_t *map = dev->agp_buffer_map;
1510
			unsigned long token = dev->agp_buffer_token;
L
Linus Torvalds 已提交
1511

D
Dave Airlie 已提交
1512
			if (!map) {
L
Linus Torvalds 已提交
1513 1514 1515 1516
				retcode = -EINVAL;
				goto done;
			}

D
Dave Airlie 已提交
1517 1518 1519 1520 1521
			down_write(&current->mm->mmap_sem);
			virtual = do_mmap(filp, 0, map->size,
					  PROT_READ | PROT_WRITE,
					  MAP_SHARED, token);
			up_write(&current->mm->mmap_sem);
L
Linus Torvalds 已提交
1522
		} else {
D
Dave Airlie 已提交
1523 1524 1525 1526 1527
			down_write(&current->mm->mmap_sem);
			virtual = do_mmap(filp, 0, dma->byte_count,
					  PROT_READ | PROT_WRITE,
					  MAP_SHARED, 0);
			up_write(&current->mm->mmap_sem);
L
Linus Torvalds 已提交
1528
		}
D
Dave Airlie 已提交
1529
		if (virtual > -1024UL) {
L
Linus Torvalds 已提交
1530 1531 1532 1533 1534 1535
			/* Real error */
			retcode = (signed long)virtual;
			goto done;
		}
		request.virtual = (void __user *)virtual;

D
Dave Airlie 已提交
1536 1537 1538 1539
		for (i = 0; i < dma->buf_count; i++) {
			if (copy_to_user(&request.list[i].idx,
					 &dma->buflist[i]->idx,
					 sizeof(request.list[0].idx))) {
L
Linus Torvalds 已提交
1540 1541 1542
				retcode = -EFAULT;
				goto done;
			}
D
Dave Airlie 已提交
1543 1544 1545
			if (copy_to_user(&request.list[i].total,
					 &dma->buflist[i]->total,
					 sizeof(request.list[0].total))) {
L
Linus Torvalds 已提交
1546 1547 1548
				retcode = -EFAULT;
				goto done;
			}
D
Dave Airlie 已提交
1549 1550
			if (copy_to_user(&request.list[i].used,
					 &zero, sizeof(zero))) {
L
Linus Torvalds 已提交
1551 1552 1553
				retcode = -EFAULT;
				goto done;
			}
D
Dave Airlie 已提交
1554 1555 1556
			address = virtual + dma->buflist[i]->offset;	/* *** */
			if (copy_to_user(&request.list[i].address,
					 &address, sizeof(address))) {
L
Linus Torvalds 已提交
1557 1558 1559 1560 1561
				retcode = -EFAULT;
				goto done;
			}
		}
	}
D
Dave Airlie 已提交
1562
      done:
L
Linus Torvalds 已提交
1563
	request.count = dma->buf_count;
D
Dave Airlie 已提交
1564
	DRM_DEBUG("%d buffers, retcode = %d\n", request.count, retcode);
L
Linus Torvalds 已提交
1565

D
Dave Airlie 已提交
1566
	if (copy_to_user(argp, &request, sizeof(request)))
L
Linus Torvalds 已提交
1567 1568 1569 1570 1571
		return -EFAULT;

	return retcode;
}

D
Dave Airlie 已提交
1572 1573 1574
/**
 * Compute size order.  Returns the exponent of the smaller power of two which
 * is greater or equal to given number.
D
Dave Airlie 已提交
1575
 *
D
Dave Airlie 已提交
1576 1577 1578 1579 1580
 * \param size size.
 * \return order.
 *
 * \todo Can be made faster.
 */
D
Dave Airlie 已提交
1581
int drm_order(unsigned long size)
D
Dave Airlie 已提交
1582 1583 1584 1585
{
	int order;
	unsigned long tmp;

D
Dave Airlie 已提交
1586
	for (order = 0, tmp = size >> 1; tmp; tmp >>= 1, order++) ;
D
Dave Airlie 已提交
1587 1588 1589 1590 1591 1592 1593

	if (size & (size - 1))
		++order;

	return order;
}
EXPORT_SYMBOL(drm_order);
1594 1595