xref: /freebsd/sys/dev/drm2/drm_bufs.c (revision f34702b76ec5c872d9cc0c628e3b60a93e799e1a)
1592ffb21SWarner Losh /**
2592ffb21SWarner Losh  * \file drm_bufs.c
3592ffb21SWarner Losh  * Generic buffer template
4592ffb21SWarner Losh  *
5592ffb21SWarner Losh  * \author Rickard E. (Rik) Faith <faith@valinux.com>
6592ffb21SWarner Losh  * \author Gareth Hughes <gareth@valinux.com>
7592ffb21SWarner Losh  */
8592ffb21SWarner Losh 
9592ffb21SWarner Losh /*
10592ffb21SWarner Losh  * Created: Thu Nov 23 03:10:50 2000 by gareth@valinux.com
11592ffb21SWarner Losh  *
12592ffb21SWarner Losh  * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas.
13592ffb21SWarner Losh  * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
14592ffb21SWarner Losh  * All Rights Reserved.
15592ffb21SWarner Losh  *
16592ffb21SWarner Losh  * Permission is hereby granted, free of charge, to any person obtaining a
17592ffb21SWarner Losh  * copy of this software and associated documentation files (the "Software"),
18592ffb21SWarner Losh  * to deal in the Software without restriction, including without limitation
19592ffb21SWarner Losh  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
20592ffb21SWarner Losh  * and/or sell copies of the Software, and to permit persons to whom the
21592ffb21SWarner Losh  * Software is furnished to do so, subject to the following conditions:
22592ffb21SWarner Losh  *
23592ffb21SWarner Losh  * The above copyright notice and this permission notice (including the next
24592ffb21SWarner Losh  * paragraph) shall be included in all copies or substantial portions of the
25592ffb21SWarner Losh  * Software.
26592ffb21SWarner Losh  *
27592ffb21SWarner Losh  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28592ffb21SWarner Losh  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29592ffb21SWarner Losh  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
30592ffb21SWarner Losh  * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
31592ffb21SWarner Losh  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
32592ffb21SWarner Losh  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
33592ffb21SWarner Losh  * OTHER DEALINGS IN THE SOFTWARE.
34592ffb21SWarner Losh  */
35592ffb21SWarner Losh 
36592ffb21SWarner Losh #include <sys/cdefs.h>
37592ffb21SWarner Losh __FBSDID("$FreeBSD$");
38592ffb21SWarner Losh 
39592ffb21SWarner Losh #include <sys/param.h>
40592ffb21SWarner Losh #include <sys/shm.h>
41592ffb21SWarner Losh 
42592ffb21SWarner Losh #include <dev/pci/pcireg.h>
43592ffb21SWarner Losh 
44592ffb21SWarner Losh #include <dev/drm2/drmP.h>
45592ffb21SWarner Losh 
46592ffb21SWarner Losh /* Allocation of PCI memory resources (framebuffer, registers, etc.) for
47592ffb21SWarner Losh  * drm_get_resource_*.  Note that they are not RF_ACTIVE, so there's no virtual
48592ffb21SWarner Losh  * address for accessing them.  Cleaned up at unload.
49592ffb21SWarner Losh  */
50592ffb21SWarner Losh static int drm_alloc_resource(struct drm_device *dev, int resource)
51592ffb21SWarner Losh {
52592ffb21SWarner Losh 	struct resource *res;
53592ffb21SWarner Losh 	int rid;
54592ffb21SWarner Losh 
55592ffb21SWarner Losh 	if (resource >= DRM_MAX_PCI_RESOURCE) {
56592ffb21SWarner Losh 		DRM_ERROR("Resource %d too large\n", resource);
57592ffb21SWarner Losh 		return 1;
58592ffb21SWarner Losh 	}
59592ffb21SWarner Losh 
60592ffb21SWarner Losh 	if (dev->pcir[resource] != NULL) {
61592ffb21SWarner Losh 		return 0;
62592ffb21SWarner Losh 	}
63592ffb21SWarner Losh 
64592ffb21SWarner Losh 	rid = PCIR_BAR(resource);
65592ffb21SWarner Losh 	res = bus_alloc_resource_any(dev->dev, SYS_RES_MEMORY, &rid,
66592ffb21SWarner Losh 	    RF_SHAREABLE);
67592ffb21SWarner Losh 	if (res == NULL) {
68592ffb21SWarner Losh 		DRM_ERROR("Couldn't find resource 0x%x\n", resource);
69592ffb21SWarner Losh 		return 1;
70592ffb21SWarner Losh 	}
71592ffb21SWarner Losh 
72592ffb21SWarner Losh 	if (dev->pcir[resource] == NULL) {
73592ffb21SWarner Losh 		dev->pcirid[resource] = rid;
74592ffb21SWarner Losh 		dev->pcir[resource] = res;
75592ffb21SWarner Losh 	}
76592ffb21SWarner Losh 
77592ffb21SWarner Losh 	return 0;
78592ffb21SWarner Losh }
79592ffb21SWarner Losh 
80592ffb21SWarner Losh unsigned long drm_get_resource_start(struct drm_device *dev,
81592ffb21SWarner Losh 				     unsigned int resource)
82592ffb21SWarner Losh {
83592ffb21SWarner Losh 	unsigned long start;
84592ffb21SWarner Losh 
85592ffb21SWarner Losh 	mtx_lock(&dev->pcir_lock);
86592ffb21SWarner Losh 
87592ffb21SWarner Losh 	if (drm_alloc_resource(dev, resource) != 0)
88592ffb21SWarner Losh 		return 0;
89592ffb21SWarner Losh 
90592ffb21SWarner Losh 	start = rman_get_start(dev->pcir[resource]);
91592ffb21SWarner Losh 
92592ffb21SWarner Losh 	mtx_unlock(&dev->pcir_lock);
93592ffb21SWarner Losh 
94592ffb21SWarner Losh 	return (start);
95592ffb21SWarner Losh }
96592ffb21SWarner Losh 
97592ffb21SWarner Losh unsigned long drm_get_resource_len(struct drm_device *dev,
98592ffb21SWarner Losh 				   unsigned int resource)
99592ffb21SWarner Losh {
100592ffb21SWarner Losh 	unsigned long len;
101592ffb21SWarner Losh 
102592ffb21SWarner Losh 	mtx_lock(&dev->pcir_lock);
103592ffb21SWarner Losh 
104592ffb21SWarner Losh 	if (drm_alloc_resource(dev, resource) != 0)
105592ffb21SWarner Losh 		return 0;
106592ffb21SWarner Losh 
107592ffb21SWarner Losh 	len = rman_get_size(dev->pcir[resource]);
108592ffb21SWarner Losh 
109592ffb21SWarner Losh 	mtx_unlock(&dev->pcir_lock);
110592ffb21SWarner Losh 
111592ffb21SWarner Losh 	return (len);
112592ffb21SWarner Losh }
113592ffb21SWarner Losh 
114592ffb21SWarner Losh static struct drm_map_list *drm_find_matching_map(struct drm_device *dev,
115592ffb21SWarner Losh 						  struct drm_local_map *map)
116592ffb21SWarner Losh {
117592ffb21SWarner Losh 	struct drm_map_list *entry;
118592ffb21SWarner Losh 	list_for_each_entry(entry, &dev->maplist, head) {
119592ffb21SWarner Losh 		/*
120592ffb21SWarner Losh 		 * Because the kernel-userspace ABI is fixed at a 32-bit offset
121592ffb21SWarner Losh 		 * while PCI resources may live above that, we only compare the
122592ffb21SWarner Losh 		 * lower 32 bits of the map offset for maps of type
123592ffb21SWarner Losh 		 * _DRM_FRAMEBUFFER or _DRM_REGISTERS.
124592ffb21SWarner Losh 		 * It is assumed that if a driver have more than one resource
125592ffb21SWarner Losh 		 * of each type, the lower 32 bits are different.
126592ffb21SWarner Losh 		 */
127592ffb21SWarner Losh 		if (!entry->map ||
128592ffb21SWarner Losh 		    map->type != entry->map->type ||
129592ffb21SWarner Losh 		    entry->master != dev->primary->master)
130592ffb21SWarner Losh 			continue;
131592ffb21SWarner Losh 		switch (map->type) {
132592ffb21SWarner Losh 		case _DRM_SHM:
133592ffb21SWarner Losh 			if (map->flags != _DRM_CONTAINS_LOCK)
134592ffb21SWarner Losh 				break;
135592ffb21SWarner Losh 			return entry;
136592ffb21SWarner Losh 		case _DRM_REGISTERS:
137592ffb21SWarner Losh 		case _DRM_FRAME_BUFFER:
138592ffb21SWarner Losh 			if ((entry->map->offset & 0xffffffff) ==
139592ffb21SWarner Losh 			    (map->offset & 0xffffffff))
140592ffb21SWarner Losh 				return entry;
141592ffb21SWarner Losh 		default: /* Make gcc happy */
142592ffb21SWarner Losh 			;
143592ffb21SWarner Losh 		}
144592ffb21SWarner Losh 		if (entry->map->offset == map->offset)
145592ffb21SWarner Losh 			return entry;
146592ffb21SWarner Losh 	}
147592ffb21SWarner Losh 
148592ffb21SWarner Losh 	return NULL;
149592ffb21SWarner Losh }
150592ffb21SWarner Losh 
151592ffb21SWarner Losh static int drm_map_handle(struct drm_device *dev, struct drm_hash_item *hash,
152592ffb21SWarner Losh 			  unsigned long user_token, int hashed_handle, int shm)
153592ffb21SWarner Losh {
154592ffb21SWarner Losh 	int use_hashed_handle, shift;
155592ffb21SWarner Losh 	unsigned long add;
156592ffb21SWarner Losh 
157592ffb21SWarner Losh #if (BITS_PER_LONG == 64)
158592ffb21SWarner Losh 	use_hashed_handle = ((user_token & 0xFFFFFFFF00000000UL) || hashed_handle);
159592ffb21SWarner Losh #elif (BITS_PER_LONG == 32)
160592ffb21SWarner Losh 	use_hashed_handle = hashed_handle;
161592ffb21SWarner Losh #else
162592ffb21SWarner Losh #error Unsupported long size. Neither 64 nor 32 bits.
163592ffb21SWarner Losh #endif
164592ffb21SWarner Losh 
165592ffb21SWarner Losh 	if (!use_hashed_handle) {
166592ffb21SWarner Losh 		int ret;
167592ffb21SWarner Losh 		hash->key = user_token >> PAGE_SHIFT;
168592ffb21SWarner Losh 		ret = drm_ht_insert_item(&dev->map_hash, hash);
169592ffb21SWarner Losh 		if (ret != -EINVAL)
170592ffb21SWarner Losh 			return ret;
171592ffb21SWarner Losh 	}
172592ffb21SWarner Losh 
173592ffb21SWarner Losh 	shift = 0;
174592ffb21SWarner Losh 	add = DRM_MAP_HASH_OFFSET >> PAGE_SHIFT;
175592ffb21SWarner Losh 	if (shm && (SHMLBA > PAGE_SIZE)) {
176592ffb21SWarner Losh 		int bits = ilog2(SHMLBA >> PAGE_SHIFT) + 1;
177592ffb21SWarner Losh 
178592ffb21SWarner Losh 		/* For shared memory, we have to preserve the SHMLBA
179592ffb21SWarner Losh 		 * bits of the eventual vma->vm_pgoff value during
180592ffb21SWarner Losh 		 * mmap().  Otherwise we run into cache aliasing problems
181592ffb21SWarner Losh 		 * on some platforms.  On these platforms, the pgoff of
182592ffb21SWarner Losh 		 * a mmap() request is used to pick a suitable virtual
183592ffb21SWarner Losh 		 * address for the mmap() region such that it will not
184592ffb21SWarner Losh 		 * cause cache aliasing problems.
185592ffb21SWarner Losh 		 *
186592ffb21SWarner Losh 		 * Therefore, make sure the SHMLBA relevant bits of the
187592ffb21SWarner Losh 		 * hash value we use are equal to those in the original
188592ffb21SWarner Losh 		 * kernel virtual address.
189592ffb21SWarner Losh 		 */
190592ffb21SWarner Losh 		shift = bits;
191592ffb21SWarner Losh 		add |= ((user_token >> PAGE_SHIFT) & ((1UL << bits) - 1UL));
192592ffb21SWarner Losh 	}
193592ffb21SWarner Losh 
194592ffb21SWarner Losh 	return drm_ht_just_insert_please(&dev->map_hash, hash,
195592ffb21SWarner Losh 					 user_token, 32 - PAGE_SHIFT - 3,
196592ffb21SWarner Losh 					 shift, add);
197592ffb21SWarner Losh }
198592ffb21SWarner Losh 
199592ffb21SWarner Losh /**
200592ffb21SWarner Losh  * Core function to create a range of memory available for mapping by a
201592ffb21SWarner Losh  * non-root process.
202592ffb21SWarner Losh  *
203592ffb21SWarner Losh  * Adjusts the memory offset to its absolute value according to the mapping
204592ffb21SWarner Losh  * type.  Adds the map to the map list drm_device::maplist. Adds MTRR's where
205592ffb21SWarner Losh  * applicable and if supported by the kernel.
206592ffb21SWarner Losh  */
207592ffb21SWarner Losh static int drm_addmap_core(struct drm_device * dev, resource_size_t offset,
208592ffb21SWarner Losh 			   unsigned int size, enum drm_map_type type,
209592ffb21SWarner Losh 			   enum drm_map_flags flags,
210592ffb21SWarner Losh 			   struct drm_map_list ** maplist)
211592ffb21SWarner Losh {
212592ffb21SWarner Losh 	struct drm_local_map *map;
213592ffb21SWarner Losh 	struct drm_map_list *list;
214592ffb21SWarner Losh 	drm_dma_handle_t *dmah;
215592ffb21SWarner Losh 	unsigned long user_token;
216592ffb21SWarner Losh 	int ret;
217592ffb21SWarner Losh 	int align;
218592ffb21SWarner Losh 
219592ffb21SWarner Losh 	map = malloc(sizeof(*map), DRM_MEM_MAPS, M_NOWAIT);
220592ffb21SWarner Losh 	if (!map)
221592ffb21SWarner Losh 		return -ENOMEM;
222592ffb21SWarner Losh 
223592ffb21SWarner Losh 	map->offset = offset;
224592ffb21SWarner Losh 	map->size = size;
225592ffb21SWarner Losh 	map->flags = flags;
226592ffb21SWarner Losh 	map->type = type;
227592ffb21SWarner Losh 
228592ffb21SWarner Losh 	/* Only allow shared memory to be removable since we only keep enough
229592ffb21SWarner Losh 	 * book keeping information about shared memory to allow for removal
230592ffb21SWarner Losh 	 * when processes fork.
231592ffb21SWarner Losh 	 */
232592ffb21SWarner Losh 	if ((map->flags & _DRM_REMOVABLE) && map->type != _DRM_SHM) {
233592ffb21SWarner Losh 		free(map, DRM_MEM_MAPS);
234592ffb21SWarner Losh 		return -EINVAL;
235592ffb21SWarner Losh 	}
236592ffb21SWarner Losh 	DRM_DEBUG("offset = 0x%08llx, size = 0x%08lx, type = %d\n",
237592ffb21SWarner Losh 		  (unsigned long long)map->offset, map->size, map->type);
238592ffb21SWarner Losh 
239592ffb21SWarner Losh 	/* page-align _DRM_SHM maps. They are allocated here so there is no security
240592ffb21SWarner Losh 	 * hole created by that and it works around various broken drivers that use
241592ffb21SWarner Losh 	 * a non-aligned quantity to map the SAREA. --BenH
242592ffb21SWarner Losh 	 */
243592ffb21SWarner Losh 	if (map->type == _DRM_SHM)
244592ffb21SWarner Losh 		map->size = PAGE_ALIGN(map->size);
245592ffb21SWarner Losh 
246592ffb21SWarner Losh 	/*
247592ffb21SWarner Losh 	 * FreeBSD port note: FreeBSD's PAGE_MASK is the inverse of
248592ffb21SWarner Losh 	 * Linux's one. That's why the test below doesn't inverse the
249592ffb21SWarner Losh 	 * constant.
250592ffb21SWarner Losh 	 */
251592ffb21SWarner Losh 	if ((map->offset & ((resource_size_t)PAGE_MASK)) || (map->size & (PAGE_MASK))) {
252592ffb21SWarner Losh 		free(map, DRM_MEM_MAPS);
253592ffb21SWarner Losh 		return -EINVAL;
254592ffb21SWarner Losh 	}
255592ffb21SWarner Losh 	map->mtrr = -1;
256592ffb21SWarner Losh 	map->handle = NULL;
257592ffb21SWarner Losh 
258592ffb21SWarner Losh 	switch (map->type) {
259592ffb21SWarner Losh 	case _DRM_REGISTERS:
260592ffb21SWarner Losh 	case _DRM_FRAME_BUFFER:
261592ffb21SWarner Losh #ifdef __linux__
262592ffb21SWarner Losh #if !defined(__sparc__) && !defined(__alpha__) && !defined(__ia64__) && !defined(__powerpc64__) && !defined(__x86_64__) && !defined(__arm__)
263592ffb21SWarner Losh 		if (map->offset + (map->size-1) < map->offset ||
264592ffb21SWarner Losh 		    map->offset < virt_to_phys(high_memory)) {
265592ffb21SWarner Losh 			kfree(map);
266592ffb21SWarner Losh 			return -EINVAL;
267592ffb21SWarner Losh 		}
268592ffb21SWarner Losh #endif
269592ffb21SWarner Losh #endif
270592ffb21SWarner Losh 		/* Some drivers preinitialize some maps, without the X Server
271592ffb21SWarner Losh 		 * needing to be aware of it.  Therefore, we just return success
272592ffb21SWarner Losh 		 * when the server tries to create a duplicate map.
273592ffb21SWarner Losh 		 */
274592ffb21SWarner Losh 		list = drm_find_matching_map(dev, map);
275592ffb21SWarner Losh 		if (list != NULL) {
276592ffb21SWarner Losh 			if (list->map->size != map->size) {
277592ffb21SWarner Losh 				DRM_DEBUG("Matching maps of type %d with "
278592ffb21SWarner Losh 					  "mismatched sizes, (%ld vs %ld)\n",
279592ffb21SWarner Losh 					  map->type, map->size,
280592ffb21SWarner Losh 					  list->map->size);
281592ffb21SWarner Losh 				list->map->size = map->size;
282592ffb21SWarner Losh 			}
283592ffb21SWarner Losh 
284592ffb21SWarner Losh 			free(map, DRM_MEM_MAPS);
285592ffb21SWarner Losh 			*maplist = list;
286592ffb21SWarner Losh 			return 0;
287592ffb21SWarner Losh 		}
288592ffb21SWarner Losh 
289592ffb21SWarner Losh 		if (drm_core_has_MTRR(dev)) {
290592ffb21SWarner Losh 			if (map->type == _DRM_FRAME_BUFFER ||
291592ffb21SWarner Losh 			    (map->flags & _DRM_WRITE_COMBINING)) {
292592ffb21SWarner Losh 				if (drm_mtrr_add(
293592ffb21SWarner Losh 				    map->offset, map->size,
294592ffb21SWarner Losh 				    DRM_MTRR_WC) == 0)
295592ffb21SWarner Losh 					map->mtrr = 1;
296592ffb21SWarner Losh 			}
297592ffb21SWarner Losh 		}
298592ffb21SWarner Losh 		if (map->type == _DRM_REGISTERS) {
299592ffb21SWarner Losh 			drm_core_ioremap(map, dev);
300592ffb21SWarner Losh 			if (!map->handle) {
301592ffb21SWarner Losh 				free(map, DRM_MEM_MAPS);
302592ffb21SWarner Losh 				return -ENOMEM;
303592ffb21SWarner Losh 			}
304592ffb21SWarner Losh 		}
305592ffb21SWarner Losh 
306592ffb21SWarner Losh 		break;
307592ffb21SWarner Losh 	case _DRM_SHM:
308592ffb21SWarner Losh 		list = drm_find_matching_map(dev, map);
309592ffb21SWarner Losh 		if (list != NULL) {
310592ffb21SWarner Losh 			if(list->map->size != map->size) {
311592ffb21SWarner Losh 				DRM_DEBUG("Matching maps of type %d with "
312592ffb21SWarner Losh 					  "mismatched sizes, (%ld vs %ld)\n",
313592ffb21SWarner Losh 					  map->type, map->size, list->map->size);
314592ffb21SWarner Losh 				list->map->size = map->size;
315592ffb21SWarner Losh 			}
316592ffb21SWarner Losh 
317592ffb21SWarner Losh 			free(map, DRM_MEM_MAPS);
318592ffb21SWarner Losh 			*maplist = list;
319592ffb21SWarner Losh 			return 0;
320592ffb21SWarner Losh 		}
321592ffb21SWarner Losh 		map->handle = malloc(map->size, DRM_MEM_MAPS, M_NOWAIT);
322592ffb21SWarner Losh 		DRM_DEBUG("%lu %d %p\n",
323592ffb21SWarner Losh 			  map->size, drm_order(map->size), map->handle);
324592ffb21SWarner Losh 		if (!map->handle) {
325592ffb21SWarner Losh 			free(map, DRM_MEM_MAPS);
326592ffb21SWarner Losh 			return -ENOMEM;
327592ffb21SWarner Losh 		}
328592ffb21SWarner Losh 		map->offset = (unsigned long)map->handle;
329592ffb21SWarner Losh 		if (map->flags & _DRM_CONTAINS_LOCK) {
330592ffb21SWarner Losh 			/* Prevent a 2nd X Server from creating a 2nd lock */
331592ffb21SWarner Losh 			if (dev->primary->master->lock.hw_lock != NULL) {
332592ffb21SWarner Losh 				free(map->handle, DRM_MEM_MAPS);
333592ffb21SWarner Losh 				free(map, DRM_MEM_MAPS);
334592ffb21SWarner Losh 				return -EBUSY;
335592ffb21SWarner Losh 			}
336592ffb21SWarner Losh 			dev->sigdata.lock = dev->primary->master->lock.hw_lock = map->handle;	/* Pointer to lock */
337592ffb21SWarner Losh 		}
338592ffb21SWarner Losh 		break;
339592ffb21SWarner Losh 	case _DRM_AGP: {
340592ffb21SWarner Losh 		struct drm_agp_mem *entry;
341592ffb21SWarner Losh 		int valid = 0;
342592ffb21SWarner Losh 
343592ffb21SWarner Losh 		if (!drm_core_has_AGP(dev)) {
344592ffb21SWarner Losh 			free(map, DRM_MEM_MAPS);
345592ffb21SWarner Losh 			return -EINVAL;
346592ffb21SWarner Losh 		}
347592ffb21SWarner Losh #ifdef __linux__
348592ffb21SWarner Losh #ifdef __alpha__
349592ffb21SWarner Losh 		map->offset += dev->hose->mem_space->start;
350592ffb21SWarner Losh #endif
351592ffb21SWarner Losh #endif
352592ffb21SWarner Losh 		/* In some cases (i810 driver), user space may have already
353592ffb21SWarner Losh 		 * added the AGP base itself, because dev->agp->base previously
354592ffb21SWarner Losh 		 * only got set during AGP enable.  So, only add the base
355592ffb21SWarner Losh 		 * address if the map's offset isn't already within the
356592ffb21SWarner Losh 		 * aperture.
357592ffb21SWarner Losh 		 */
358592ffb21SWarner Losh 		if (map->offset < dev->agp->base ||
359592ffb21SWarner Losh 		    map->offset > dev->agp->base +
360592ffb21SWarner Losh 		    dev->agp->agp_info.ai_aperture_size * 1024 * 1024 - 1) {
361592ffb21SWarner Losh 			map->offset += dev->agp->base;
362592ffb21SWarner Losh 		}
363592ffb21SWarner Losh 		map->mtrr = dev->agp->agp_mtrr;	/* for getmap */
364592ffb21SWarner Losh 
365592ffb21SWarner Losh 		/* This assumes the DRM is in total control of AGP space.
366592ffb21SWarner Losh 		 * It's not always the case as AGP can be in the control
367592ffb21SWarner Losh 		 * of user space (i.e. i810 driver). So this loop will get
368592ffb21SWarner Losh 		 * skipped and we double check that dev->agp->memory is
369592ffb21SWarner Losh 		 * actually set as well as being invalid before EPERM'ing
370592ffb21SWarner Losh 		 */
371592ffb21SWarner Losh 		list_for_each_entry(entry, &dev->agp->memory, head) {
372592ffb21SWarner Losh 			if ((map->offset >= entry->bound) &&
373592ffb21SWarner Losh 			    (map->offset + map->size <= entry->bound + entry->pages * PAGE_SIZE)) {
374592ffb21SWarner Losh 				valid = 1;
375592ffb21SWarner Losh 				break;
376592ffb21SWarner Losh 			}
377592ffb21SWarner Losh 		}
378592ffb21SWarner Losh 		if (!list_empty(&dev->agp->memory) && !valid) {
379592ffb21SWarner Losh 			free(map, DRM_MEM_MAPS);
380592ffb21SWarner Losh 			return -EPERM;
381592ffb21SWarner Losh 		}
382592ffb21SWarner Losh 		DRM_DEBUG("AGP offset = 0x%08llx, size = 0x%08lx\n",
383592ffb21SWarner Losh 			  (unsigned long long)map->offset, map->size);
384592ffb21SWarner Losh 
385592ffb21SWarner Losh 		break;
386592ffb21SWarner Losh 	}
387592ffb21SWarner Losh 	case _DRM_GEM:
388592ffb21SWarner Losh 		DRM_ERROR("tried to addmap GEM object\n");
389592ffb21SWarner Losh 		break;
390592ffb21SWarner Losh 	case _DRM_SCATTER_GATHER:
391592ffb21SWarner Losh 		if (!dev->sg) {
392592ffb21SWarner Losh 			free(map, DRM_MEM_MAPS);
393592ffb21SWarner Losh 			return -EINVAL;
394592ffb21SWarner Losh 		}
395592ffb21SWarner Losh 		map->handle = (void *)(dev->sg->vaddr + offset);
396592ffb21SWarner Losh 		map->offset += dev->sg->vaddr;
397592ffb21SWarner Losh 		break;
398592ffb21SWarner Losh 	case _DRM_CONSISTENT:
399592ffb21SWarner Losh 		/* dma_addr_t is 64bit on i386 with CONFIG_HIGHMEM64G,
400592ffb21SWarner Losh 		 * As we're limiting the address to 2^32-1 (or less),
401592ffb21SWarner Losh 		 * casting it down to 32 bits is no problem, but we
402592ffb21SWarner Losh 		 * need to point to a 64bit variable first. */
403592ffb21SWarner Losh 		align = map->size;
404592ffb21SWarner Losh 		if ((align & (align - 1)) != 0)
405592ffb21SWarner Losh 			align = PAGE_SIZE;
406592ffb21SWarner Losh 		dmah = drm_pci_alloc(dev, map->size, align, BUS_SPACE_MAXADDR);
407592ffb21SWarner Losh 		if (!dmah) {
408592ffb21SWarner Losh 			free(map, DRM_MEM_MAPS);
409592ffb21SWarner Losh 			return -ENOMEM;
410592ffb21SWarner Losh 		}
411592ffb21SWarner Losh 		map->handle = dmah->vaddr;
412592ffb21SWarner Losh 		map->offset = dmah->busaddr;
413592ffb21SWarner Losh 		map->dmah = dmah;
414592ffb21SWarner Losh 		break;
415592ffb21SWarner Losh 	default:
416592ffb21SWarner Losh 		free(map, DRM_MEM_MAPS);
417592ffb21SWarner Losh 		return -EINVAL;
418592ffb21SWarner Losh 	}
419592ffb21SWarner Losh 
420592ffb21SWarner Losh 	list = malloc(sizeof(*list), DRM_MEM_MAPS, M_ZERO | M_NOWAIT);
421592ffb21SWarner Losh 	if (!list) {
422592ffb21SWarner Losh 		if (map->type == _DRM_REGISTERS)
423592ffb21SWarner Losh 			drm_core_ioremapfree(map, dev);
424592ffb21SWarner Losh 		free(map, DRM_MEM_MAPS);
425592ffb21SWarner Losh 		return -EINVAL;
426592ffb21SWarner Losh 	}
427592ffb21SWarner Losh 	list->map = map;
428592ffb21SWarner Losh 
429592ffb21SWarner Losh 	DRM_LOCK(dev);
430592ffb21SWarner Losh 	list_add(&list->head, &dev->maplist);
431592ffb21SWarner Losh 
432592ffb21SWarner Losh 	/* Assign a 32-bit handle */
433592ffb21SWarner Losh 	/* We do it here so that dev->struct_mutex protects the increment */
434592ffb21SWarner Losh 	user_token = (map->type == _DRM_SHM) ? (unsigned long)map->handle :
435592ffb21SWarner Losh 		map->offset;
436592ffb21SWarner Losh 	ret = drm_map_handle(dev, &list->hash, user_token, 0,
437592ffb21SWarner Losh 			     (map->type == _DRM_SHM));
438592ffb21SWarner Losh 	if (ret) {
439592ffb21SWarner Losh 		if (map->type == _DRM_REGISTERS)
440592ffb21SWarner Losh 			drm_core_ioremapfree(map, dev);
441592ffb21SWarner Losh 		free(map, DRM_MEM_MAPS);
442592ffb21SWarner Losh 		free(list, DRM_MEM_MAPS);
443592ffb21SWarner Losh 		DRM_UNLOCK(dev);
444592ffb21SWarner Losh 		return ret;
445592ffb21SWarner Losh 	}
446592ffb21SWarner Losh 
447592ffb21SWarner Losh 	list->user_token = list->hash.key << PAGE_SHIFT;
448592ffb21SWarner Losh 	DRM_UNLOCK(dev);
449592ffb21SWarner Losh 
450592ffb21SWarner Losh 	if (!(map->flags & _DRM_DRIVER))
451592ffb21SWarner Losh 		list->master = dev->primary->master;
452592ffb21SWarner Losh 	*maplist = list;
453592ffb21SWarner Losh 	return 0;
454592ffb21SWarner Losh 	}
455592ffb21SWarner Losh 
456592ffb21SWarner Losh int drm_addmap(struct drm_device * dev, resource_size_t offset,
457592ffb21SWarner Losh 	       unsigned int size, enum drm_map_type type,
458592ffb21SWarner Losh 	       enum drm_map_flags flags, struct drm_local_map ** map_ptr)
459592ffb21SWarner Losh {
460592ffb21SWarner Losh 	struct drm_map_list *list;
461592ffb21SWarner Losh 	int rc;
462592ffb21SWarner Losh 
463592ffb21SWarner Losh 	rc = drm_addmap_core(dev, offset, size, type, flags, &list);
464592ffb21SWarner Losh 	if (!rc)
465592ffb21SWarner Losh 		*map_ptr = list->map;
466592ffb21SWarner Losh 	return rc;
467592ffb21SWarner Losh }
468592ffb21SWarner Losh 
469592ffb21SWarner Losh EXPORT_SYMBOL(drm_addmap);
470592ffb21SWarner Losh 
471592ffb21SWarner Losh /**
472592ffb21SWarner Losh  * Ioctl to specify a range of memory that is available for mapping by a
473592ffb21SWarner Losh  * non-root process.
474592ffb21SWarner Losh  *
475592ffb21SWarner Losh  * \param inode device inode.
476592ffb21SWarner Losh  * \param file_priv DRM file private.
477592ffb21SWarner Losh  * \param cmd command.
478592ffb21SWarner Losh  * \param arg pointer to a drm_map structure.
479592ffb21SWarner Losh  * \return zero on success or a negative value on error.
480592ffb21SWarner Losh  *
481592ffb21SWarner Losh  */
482592ffb21SWarner Losh int drm_addmap_ioctl(struct drm_device *dev, void *data,
483592ffb21SWarner Losh 		     struct drm_file *file_priv)
484592ffb21SWarner Losh {
485592ffb21SWarner Losh 	struct drm_map *map = data;
486592ffb21SWarner Losh 	struct drm_map_list *maplist;
487592ffb21SWarner Losh 	int err;
488592ffb21SWarner Losh 
489592ffb21SWarner Losh 	if (!(DRM_SUSER(DRM_CURPROC) || map->type == _DRM_AGP || map->type == _DRM_SHM))
490592ffb21SWarner Losh 		return -EPERM;
491592ffb21SWarner Losh 
492592ffb21SWarner Losh 	err = drm_addmap_core(dev, map->offset, map->size, map->type,
493592ffb21SWarner Losh 			      map->flags, &maplist);
494592ffb21SWarner Losh 
495592ffb21SWarner Losh 	if (err)
496592ffb21SWarner Losh 		return err;
497592ffb21SWarner Losh 
498592ffb21SWarner Losh 	/* avoid a warning on 64-bit, this casting isn't very nice, but the API is set so too late */
499592ffb21SWarner Losh 	map->handle = (void *)(unsigned long)maplist->user_token;
500592ffb21SWarner Losh 	return 0;
501592ffb21SWarner Losh }
502592ffb21SWarner Losh 
503592ffb21SWarner Losh /**
504592ffb21SWarner Losh  * Remove a map private from list and deallocate resources if the mapping
505592ffb21SWarner Losh  * isn't in use.
506592ffb21SWarner Losh  *
507592ffb21SWarner Losh  * Searches the map on drm_device::maplist, removes it from the list, see if
508592ffb21SWarner Losh  * its being used, and free any associate resource (such as MTRR's) if it's not
509592ffb21SWarner Losh  * being on use.
510592ffb21SWarner Losh  *
511592ffb21SWarner Losh  * \sa drm_addmap
512592ffb21SWarner Losh  */
513592ffb21SWarner Losh int drm_rmmap_locked(struct drm_device *dev, struct drm_local_map *map)
514592ffb21SWarner Losh {
515592ffb21SWarner Losh 	struct drm_map_list *r_list = NULL, *list_t;
516592ffb21SWarner Losh 	int found = 0;
517592ffb21SWarner Losh 	struct drm_master *master;
518592ffb21SWarner Losh 
519592ffb21SWarner Losh 	/* Find the list entry for the map and remove it */
520592ffb21SWarner Losh 	list_for_each_entry_safe(r_list, list_t, &dev->maplist, head) {
521592ffb21SWarner Losh 		if (r_list->map == map) {
522592ffb21SWarner Losh 			master = r_list->master;
523592ffb21SWarner Losh 			list_del(&r_list->head);
524592ffb21SWarner Losh 			drm_ht_remove_key(&dev->map_hash,
525592ffb21SWarner Losh 					  r_list->user_token >> PAGE_SHIFT);
526592ffb21SWarner Losh 			free(r_list, DRM_MEM_MAPS);
527592ffb21SWarner Losh 			found = 1;
528592ffb21SWarner Losh 			break;
529592ffb21SWarner Losh 		}
530592ffb21SWarner Losh 	}
531592ffb21SWarner Losh 
532592ffb21SWarner Losh 	if (!found)
533592ffb21SWarner Losh 		return -EINVAL;
534592ffb21SWarner Losh 
535592ffb21SWarner Losh 	switch (map->type) {
536592ffb21SWarner Losh 	case _DRM_REGISTERS:
537592ffb21SWarner Losh 		drm_core_ioremapfree(map, dev);
538592ffb21SWarner Losh 		/* FALLTHROUGH */
539592ffb21SWarner Losh 	case _DRM_FRAME_BUFFER:
540592ffb21SWarner Losh 		if (drm_core_has_MTRR(dev) && map->mtrr >= 0) {
541592ffb21SWarner Losh 			int retcode;
542592ffb21SWarner Losh 			retcode = drm_mtrr_del(map->mtrr, map->offset,
543592ffb21SWarner Losh 			    map->size, DRM_MTRR_WC);
544592ffb21SWarner Losh 			DRM_DEBUG("mtrr_del=%d\n", retcode);
545592ffb21SWarner Losh 		}
546592ffb21SWarner Losh 		break;
547592ffb21SWarner Losh 	case _DRM_SHM:
548592ffb21SWarner Losh 		free(map->handle, DRM_MEM_MAPS);
549592ffb21SWarner Losh 		if (master) {
550592ffb21SWarner Losh 			if (dev->sigdata.lock == master->lock.hw_lock)
551592ffb21SWarner Losh 				dev->sigdata.lock = NULL;
552592ffb21SWarner Losh 			master->lock.hw_lock = NULL;   /* SHM removed */
553592ffb21SWarner Losh 			master->lock.file_priv = NULL;
554592ffb21SWarner Losh 			DRM_WAKEUP_INT((void *)&master->lock.lock_queue);
555592ffb21SWarner Losh 		}
556592ffb21SWarner Losh 		break;
557592ffb21SWarner Losh 	case _DRM_AGP:
558592ffb21SWarner Losh 	case _DRM_SCATTER_GATHER:
559592ffb21SWarner Losh 		break;
560592ffb21SWarner Losh 	case _DRM_CONSISTENT:
561592ffb21SWarner Losh 		drm_pci_free(dev, map->dmah);
562592ffb21SWarner Losh 		break;
563592ffb21SWarner Losh 	case _DRM_GEM:
564592ffb21SWarner Losh 		DRM_ERROR("tried to rmmap GEM object\n");
565592ffb21SWarner Losh 		break;
566592ffb21SWarner Losh 	}
567592ffb21SWarner Losh 	free(map, DRM_MEM_MAPS);
568592ffb21SWarner Losh 
569592ffb21SWarner Losh 	return 0;
570592ffb21SWarner Losh }
571592ffb21SWarner Losh EXPORT_SYMBOL(drm_rmmap_locked);
572592ffb21SWarner Losh 
573592ffb21SWarner Losh int drm_rmmap(struct drm_device *dev, struct drm_local_map *map)
574592ffb21SWarner Losh {
575592ffb21SWarner Losh 	int ret;
576592ffb21SWarner Losh 
577592ffb21SWarner Losh 	DRM_LOCK(dev);
578592ffb21SWarner Losh 	ret = drm_rmmap_locked(dev, map);
579592ffb21SWarner Losh 	DRM_UNLOCK(dev);
580592ffb21SWarner Losh 
581592ffb21SWarner Losh 	return ret;
582592ffb21SWarner Losh }
583592ffb21SWarner Losh EXPORT_SYMBOL(drm_rmmap);
584592ffb21SWarner Losh 
585592ffb21SWarner Losh /* The rmmap ioctl appears to be unnecessary.  All mappings are torn down on
586592ffb21SWarner Losh  * the last close of the device, and this is necessary for cleanup when things
587592ffb21SWarner Losh  * exit uncleanly.  Therefore, having userland manually remove mappings seems
588592ffb21SWarner Losh  * like a pointless exercise since they're going away anyway.
589592ffb21SWarner Losh  *
590592ffb21SWarner Losh  * One use case might be after addmap is allowed for normal users for SHM and
591592ffb21SWarner Losh  * gets used by drivers that the server doesn't need to care about.  This seems
592592ffb21SWarner Losh  * unlikely.
593592ffb21SWarner Losh  *
594592ffb21SWarner Losh  * \param inode device inode.
595592ffb21SWarner Losh  * \param file_priv DRM file private.
596592ffb21SWarner Losh  * \param cmd command.
597592ffb21SWarner Losh  * \param arg pointer to a struct drm_map structure.
598592ffb21SWarner Losh  * \return zero on success or a negative value on error.
599592ffb21SWarner Losh  */
600592ffb21SWarner Losh int drm_rmmap_ioctl(struct drm_device *dev, void *data,
601592ffb21SWarner Losh 		    struct drm_file *file_priv)
602592ffb21SWarner Losh {
603592ffb21SWarner Losh 	struct drm_map *request = data;
604592ffb21SWarner Losh 	struct drm_local_map *map = NULL;
605592ffb21SWarner Losh 	struct drm_map_list *r_list;
606592ffb21SWarner Losh 	int ret;
607592ffb21SWarner Losh 
608592ffb21SWarner Losh 	DRM_LOCK(dev);
609592ffb21SWarner Losh 	list_for_each_entry(r_list, &dev->maplist, head) {
610592ffb21SWarner Losh 		if (r_list->map &&
611592ffb21SWarner Losh 		    r_list->user_token == (unsigned long)request->handle &&
612592ffb21SWarner Losh 		    r_list->map->flags & _DRM_REMOVABLE) {
613592ffb21SWarner Losh 			map = r_list->map;
614592ffb21SWarner Losh 			break;
615592ffb21SWarner Losh 		}
616592ffb21SWarner Losh 	}
617592ffb21SWarner Losh 
618592ffb21SWarner Losh 	/* List has wrapped around to the head pointer, or its empty we didn't
619592ffb21SWarner Losh 	 * find anything.
620592ffb21SWarner Losh 	 */
621592ffb21SWarner Losh 	if (list_empty(&dev->maplist) || !map) {
622592ffb21SWarner Losh 		DRM_UNLOCK(dev);
623592ffb21SWarner Losh 		return -EINVAL;
624592ffb21SWarner Losh 	}
625592ffb21SWarner Losh 
626592ffb21SWarner Losh 	/* Register and framebuffer maps are permanent */
627592ffb21SWarner Losh 	if ((map->type == _DRM_REGISTERS) || (map->type == _DRM_FRAME_BUFFER)) {
628592ffb21SWarner Losh 		DRM_UNLOCK(dev);
629592ffb21SWarner Losh 		return 0;
630592ffb21SWarner Losh 	}
631592ffb21SWarner Losh 
632592ffb21SWarner Losh 	ret = drm_rmmap_locked(dev, map);
633592ffb21SWarner Losh 
634592ffb21SWarner Losh 	DRM_UNLOCK(dev);
635592ffb21SWarner Losh 
636592ffb21SWarner Losh 	return ret;
637592ffb21SWarner Losh }
638592ffb21SWarner Losh 
639592ffb21SWarner Losh /**
640592ffb21SWarner Losh  * Cleanup after an error on one of the addbufs() functions.
641592ffb21SWarner Losh  *
642592ffb21SWarner Losh  * \param dev DRM device.
643592ffb21SWarner Losh  * \param entry buffer entry where the error occurred.
644592ffb21SWarner Losh  *
645592ffb21SWarner Losh  * Frees any pages and buffers associated with the given entry.
646592ffb21SWarner Losh  */
647592ffb21SWarner Losh static void drm_cleanup_buf_error(struct drm_device * dev,
648592ffb21SWarner Losh 				  struct drm_buf_entry * entry)
649592ffb21SWarner Losh {
650592ffb21SWarner Losh 	int i;
651592ffb21SWarner Losh 
652592ffb21SWarner Losh 	if (entry->seg_count) {
653592ffb21SWarner Losh 		for (i = 0; i < entry->seg_count; i++) {
654592ffb21SWarner Losh 			if (entry->seglist[i]) {
655592ffb21SWarner Losh 				drm_pci_free(dev, entry->seglist[i]);
656592ffb21SWarner Losh 			}
657592ffb21SWarner Losh 		}
658592ffb21SWarner Losh 		free(entry->seglist, DRM_MEM_SEGS);
659592ffb21SWarner Losh 
660592ffb21SWarner Losh 		entry->seg_count = 0;
661592ffb21SWarner Losh 	}
662592ffb21SWarner Losh 
663592ffb21SWarner Losh 	if (entry->buf_count) {
664592ffb21SWarner Losh 		for (i = 0; i < entry->buf_count; i++) {
665592ffb21SWarner Losh 			free(entry->buflist[i].dev_private, DRM_MEM_BUFS);
666592ffb21SWarner Losh 		}
667592ffb21SWarner Losh 		free(entry->buflist, DRM_MEM_BUFS);
668592ffb21SWarner Losh 
669592ffb21SWarner Losh 		entry->buf_count = 0;
670592ffb21SWarner Losh 	}
671592ffb21SWarner Losh }
672592ffb21SWarner Losh 
673592ffb21SWarner Losh #if __OS_HAS_AGP
674592ffb21SWarner Losh /**
675592ffb21SWarner Losh  * Add AGP buffers for DMA transfers.
676592ffb21SWarner Losh  *
677592ffb21SWarner Losh  * \param dev struct drm_device to which the buffers are to be added.
678592ffb21SWarner Losh  * \param request pointer to a struct drm_buf_desc describing the request.
679592ffb21SWarner Losh  * \return zero on success or a negative number on failure.
680592ffb21SWarner Losh  *
681592ffb21SWarner Losh  * After some sanity checks creates a drm_buf structure for each buffer and
682592ffb21SWarner Losh  * reallocates the buffer list of the same size order to accommodate the new
683592ffb21SWarner Losh  * buffers.
684592ffb21SWarner Losh  */
685592ffb21SWarner Losh int drm_addbufs_agp(struct drm_device * dev, struct drm_buf_desc * request)
686592ffb21SWarner Losh {
687592ffb21SWarner Losh 	struct drm_device_dma *dma = dev->dma;
688592ffb21SWarner Losh 	struct drm_buf_entry *entry;
689592ffb21SWarner Losh 	struct drm_agp_mem *agp_entry;
690592ffb21SWarner Losh 	struct drm_buf *buf;
691592ffb21SWarner Losh 	unsigned long offset;
692592ffb21SWarner Losh 	unsigned long agp_offset;
693592ffb21SWarner Losh 	int count;
694592ffb21SWarner Losh 	int order;
695592ffb21SWarner Losh 	int size;
696592ffb21SWarner Losh 	int alignment;
697592ffb21SWarner Losh 	int page_order;
698592ffb21SWarner Losh 	int total;
699592ffb21SWarner Losh 	int byte_count;
700592ffb21SWarner Losh 	int i, valid;
701592ffb21SWarner Losh 	struct drm_buf **temp_buflist;
702592ffb21SWarner Losh 
703592ffb21SWarner Losh 	if (!dma)
704592ffb21SWarner Losh 		return -EINVAL;
705592ffb21SWarner Losh 
706592ffb21SWarner Losh 	count = request->count;
707592ffb21SWarner Losh 	order = drm_order(request->size);
708592ffb21SWarner Losh 	size = 1 << order;
709592ffb21SWarner Losh 
710592ffb21SWarner Losh 	alignment = (request->flags & _DRM_PAGE_ALIGN)
711592ffb21SWarner Losh 	    ? PAGE_ALIGN(size) : size;
712592ffb21SWarner Losh 	page_order = order - PAGE_SHIFT > 0 ? order - PAGE_SHIFT : 0;
713592ffb21SWarner Losh 	total = PAGE_SIZE << page_order;
714592ffb21SWarner Losh 
715592ffb21SWarner Losh 	byte_count = 0;
716592ffb21SWarner Losh 	agp_offset = dev->agp->base + request->agp_start;
717592ffb21SWarner Losh 
718592ffb21SWarner Losh 	DRM_DEBUG("count:      %d\n", count);
719592ffb21SWarner Losh 	DRM_DEBUG("order:      %d\n", order);
720592ffb21SWarner Losh 	DRM_DEBUG("size:       %d\n", size);
721592ffb21SWarner Losh 	DRM_DEBUG("agp_offset: %lx\n", agp_offset);
722592ffb21SWarner Losh 	DRM_DEBUG("alignment:  %d\n", alignment);
723592ffb21SWarner Losh 	DRM_DEBUG("page_order: %d\n", page_order);
724592ffb21SWarner Losh 	DRM_DEBUG("total:      %d\n", total);
725592ffb21SWarner Losh 
726592ffb21SWarner Losh 	if (order < DRM_MIN_ORDER || order > DRM_MAX_ORDER)
727592ffb21SWarner Losh 		return -EINVAL;
728592ffb21SWarner Losh 
729592ffb21SWarner Losh 	/* Make sure buffers are located in AGP memory that we own */
730592ffb21SWarner Losh 	valid = 0;
731592ffb21SWarner Losh 	list_for_each_entry(agp_entry, &dev->agp->memory, head) {
732592ffb21SWarner Losh 		if ((agp_offset >= agp_entry->bound) &&
733592ffb21SWarner Losh 		    (agp_offset + total * count <= agp_entry->bound + agp_entry->pages * PAGE_SIZE)) {
734592ffb21SWarner Losh 			valid = 1;
735592ffb21SWarner Losh 			break;
736592ffb21SWarner Losh 		}
737592ffb21SWarner Losh 	}
738592ffb21SWarner Losh 	if (!list_empty(&dev->agp->memory) && !valid) {
739592ffb21SWarner Losh 		DRM_DEBUG("zone invalid\n");
740592ffb21SWarner Losh 		return -EINVAL;
741592ffb21SWarner Losh 	}
742592ffb21SWarner Losh 	mtx_lock(&dev->count_lock);
743592ffb21SWarner Losh 	if (dev->buf_use) {
744592ffb21SWarner Losh 		mtx_unlock(&dev->count_lock);
745592ffb21SWarner Losh 		return -EBUSY;
746592ffb21SWarner Losh 	}
747592ffb21SWarner Losh 	atomic_inc(&dev->buf_alloc);
748592ffb21SWarner Losh 	mtx_unlock(&dev->count_lock);
749592ffb21SWarner Losh 
750592ffb21SWarner Losh 	DRM_LOCK(dev);
751592ffb21SWarner Losh 	entry = &dma->bufs[order];
752592ffb21SWarner Losh 	if (entry->buf_count) {
753592ffb21SWarner Losh 		DRM_UNLOCK(dev);
754592ffb21SWarner Losh 		atomic_dec(&dev->buf_alloc);
755592ffb21SWarner Losh 		return -ENOMEM;	/* May only call once for each order */
756592ffb21SWarner Losh 	}
757592ffb21SWarner Losh 
758592ffb21SWarner Losh 	if (count < 0 || count > 4096) {
759592ffb21SWarner Losh 		DRM_UNLOCK(dev);
760592ffb21SWarner Losh 		atomic_dec(&dev->buf_alloc);
761592ffb21SWarner Losh 		return -EINVAL;
762592ffb21SWarner Losh 	}
763592ffb21SWarner Losh 
764592ffb21SWarner Losh 	entry->buflist = malloc(count * sizeof(*entry->buflist), DRM_MEM_BUFS,
765592ffb21SWarner Losh 	    M_NOWAIT | M_ZERO);
766592ffb21SWarner Losh 	if (!entry->buflist) {
767592ffb21SWarner Losh 		DRM_UNLOCK(dev);
768592ffb21SWarner Losh 		atomic_dec(&dev->buf_alloc);
769592ffb21SWarner Losh 		return -ENOMEM;
770592ffb21SWarner Losh 	}
771592ffb21SWarner Losh 
772592ffb21SWarner Losh 	entry->buf_size = size;
773592ffb21SWarner Losh 	entry->page_order = page_order;
774592ffb21SWarner Losh 
775592ffb21SWarner Losh 	offset = 0;
776592ffb21SWarner Losh 
777592ffb21SWarner Losh 	while (entry->buf_count < count) {
778592ffb21SWarner Losh 		buf = &entry->buflist[entry->buf_count];
779592ffb21SWarner Losh 		buf->idx = dma->buf_count + entry->buf_count;
780592ffb21SWarner Losh 		buf->total = alignment;
781592ffb21SWarner Losh 		buf->order = order;
782592ffb21SWarner Losh 		buf->used = 0;
783592ffb21SWarner Losh 
784592ffb21SWarner Losh 		buf->offset = (dma->byte_count + offset);
785592ffb21SWarner Losh 		buf->bus_address = agp_offset + offset;
786592ffb21SWarner Losh 		buf->address = (void *)(agp_offset + offset);
787592ffb21SWarner Losh 		buf->next = NULL;
788592ffb21SWarner Losh 		buf->waiting = 0;
789592ffb21SWarner Losh 		buf->pending = 0;
790592ffb21SWarner Losh 		buf->file_priv = NULL;
791592ffb21SWarner Losh 
792592ffb21SWarner Losh 		buf->dev_priv_size = dev->driver->dev_priv_size;
793592ffb21SWarner Losh 		buf->dev_private = malloc(buf->dev_priv_size, DRM_MEM_BUFS,
794592ffb21SWarner Losh 		    M_NOWAIT | M_ZERO);
795592ffb21SWarner Losh 		if (!buf->dev_private) {
796592ffb21SWarner Losh 			/* Set count correctly so we free the proper amount. */
797592ffb21SWarner Losh 			entry->buf_count = count;
798592ffb21SWarner Losh 			drm_cleanup_buf_error(dev, entry);
799592ffb21SWarner Losh 			DRM_UNLOCK(dev);
800592ffb21SWarner Losh 			atomic_dec(&dev->buf_alloc);
801592ffb21SWarner Losh 			return -ENOMEM;
802592ffb21SWarner Losh 		}
803592ffb21SWarner Losh 
804592ffb21SWarner Losh 		DRM_DEBUG("buffer %d @ %p\n", entry->buf_count, buf->address);
805592ffb21SWarner Losh 
806592ffb21SWarner Losh 		offset += alignment;
807592ffb21SWarner Losh 		entry->buf_count++;
808592ffb21SWarner Losh 		byte_count += PAGE_SIZE << page_order;
809592ffb21SWarner Losh 	}
810592ffb21SWarner Losh 
811592ffb21SWarner Losh 	DRM_DEBUG("byte_count: %d\n", byte_count);
812592ffb21SWarner Losh 
813592ffb21SWarner Losh 	temp_buflist = realloc(dma->buflist,
814592ffb21SWarner Losh 	    (dma->buf_count + entry->buf_count) * sizeof(*dma->buflist),
815592ffb21SWarner Losh 	    DRM_MEM_BUFS, M_NOWAIT);
816592ffb21SWarner Losh 	if (!temp_buflist) {
817592ffb21SWarner Losh 		/* Free the entry because it isn't valid */
818592ffb21SWarner Losh 		drm_cleanup_buf_error(dev, entry);
819592ffb21SWarner Losh 		DRM_UNLOCK(dev);
820592ffb21SWarner Losh 		atomic_dec(&dev->buf_alloc);
821592ffb21SWarner Losh 		return -ENOMEM;
822592ffb21SWarner Losh 	}
823592ffb21SWarner Losh 	dma->buflist = temp_buflist;
824592ffb21SWarner Losh 
825592ffb21SWarner Losh 	for (i = 0; i < entry->buf_count; i++) {
826592ffb21SWarner Losh 		dma->buflist[i + dma->buf_count] = &entry->buflist[i];
827592ffb21SWarner Losh 	}
828592ffb21SWarner Losh 
829592ffb21SWarner Losh 	dma->buf_count += entry->buf_count;
830592ffb21SWarner Losh 	dma->seg_count += entry->seg_count;
831592ffb21SWarner Losh 	dma->page_count += byte_count >> PAGE_SHIFT;
832592ffb21SWarner Losh 	dma->byte_count += byte_count;
833592ffb21SWarner Losh 
834592ffb21SWarner Losh 	DRM_DEBUG("dma->buf_count : %d\n", dma->buf_count);
835592ffb21SWarner Losh 	DRM_DEBUG("entry->buf_count : %d\n", entry->buf_count);
836592ffb21SWarner Losh 
837592ffb21SWarner Losh 	DRM_UNLOCK(dev);
838592ffb21SWarner Losh 
839592ffb21SWarner Losh 	request->count = entry->buf_count;
840592ffb21SWarner Losh 	request->size = size;
841592ffb21SWarner Losh 
842592ffb21SWarner Losh 	dma->flags = _DRM_DMA_USE_AGP;
843592ffb21SWarner Losh 
844592ffb21SWarner Losh 	atomic_dec(&dev->buf_alloc);
845592ffb21SWarner Losh 	return 0;
846592ffb21SWarner Losh }
847592ffb21SWarner Losh EXPORT_SYMBOL(drm_addbufs_agp);
848592ffb21SWarner Losh #endif				/* __OS_HAS_AGP */
849592ffb21SWarner Losh 
850592ffb21SWarner Losh int drm_addbufs_pci(struct drm_device * dev, struct drm_buf_desc * request)
851592ffb21SWarner Losh {
852592ffb21SWarner Losh 	struct drm_device_dma *dma = dev->dma;
853592ffb21SWarner Losh 	int count;
854592ffb21SWarner Losh 	int order;
855592ffb21SWarner Losh 	int size;
856592ffb21SWarner Losh 	int total;
857592ffb21SWarner Losh 	int page_order;
858592ffb21SWarner Losh 	struct drm_buf_entry *entry;
859592ffb21SWarner Losh 	drm_dma_handle_t *dmah;
860592ffb21SWarner Losh 	struct drm_buf *buf;
861592ffb21SWarner Losh 	int alignment;
862592ffb21SWarner Losh 	unsigned long offset;
863592ffb21SWarner Losh 	int i;
864592ffb21SWarner Losh 	int byte_count;
865592ffb21SWarner Losh 	int page_count;
866592ffb21SWarner Losh 	unsigned long *temp_pagelist;
867592ffb21SWarner Losh 	struct drm_buf **temp_buflist;
868592ffb21SWarner Losh 
869592ffb21SWarner Losh 	if (!drm_core_check_feature(dev, DRIVER_PCI_DMA))
870592ffb21SWarner Losh 		return -EINVAL;
871592ffb21SWarner Losh 
872592ffb21SWarner Losh 	if (!dma)
873592ffb21SWarner Losh 		return -EINVAL;
874592ffb21SWarner Losh 
875592ffb21SWarner Losh 	if (!DRM_SUSER(DRM_CURPROC))
876592ffb21SWarner Losh 		return -EPERM;
877592ffb21SWarner Losh 
878592ffb21SWarner Losh 	count = request->count;
879592ffb21SWarner Losh 	order = drm_order(request->size);
880592ffb21SWarner Losh 	size = 1 << order;
881592ffb21SWarner Losh 
882592ffb21SWarner Losh 	DRM_DEBUG("count=%d, size=%d (%d), order=%d\n",
883592ffb21SWarner Losh 		  request->count, request->size, size, order);
884592ffb21SWarner Losh 
885592ffb21SWarner Losh 	if (order < DRM_MIN_ORDER || order > DRM_MAX_ORDER)
886592ffb21SWarner Losh 		return -EINVAL;
887592ffb21SWarner Losh 
888592ffb21SWarner Losh 	alignment = (request->flags & _DRM_PAGE_ALIGN)
889592ffb21SWarner Losh 	    ? PAGE_ALIGN(size) : size;
890592ffb21SWarner Losh 	page_order = order - PAGE_SHIFT > 0 ? order - PAGE_SHIFT : 0;
891592ffb21SWarner Losh 	total = PAGE_SIZE << page_order;
892592ffb21SWarner Losh 
893592ffb21SWarner Losh 	mtx_lock(&dev->count_lock);
894592ffb21SWarner Losh 	if (dev->buf_use) {
895592ffb21SWarner Losh 		mtx_unlock(&dev->count_lock);
896592ffb21SWarner Losh 		return -EBUSY;
897592ffb21SWarner Losh 	}
898592ffb21SWarner Losh 	atomic_inc(&dev->buf_alloc);
899592ffb21SWarner Losh 	mtx_unlock(&dev->count_lock);
900592ffb21SWarner Losh 
901592ffb21SWarner Losh 	DRM_LOCK(dev);
902592ffb21SWarner Losh 	entry = &dma->bufs[order];
903592ffb21SWarner Losh 	if (entry->buf_count) {
904592ffb21SWarner Losh 		DRM_UNLOCK(dev);
905592ffb21SWarner Losh 		atomic_dec(&dev->buf_alloc);
906592ffb21SWarner Losh 		return -ENOMEM;	/* May only call once for each order */
907592ffb21SWarner Losh 	}
908592ffb21SWarner Losh 
909592ffb21SWarner Losh 	if (count < 0 || count > 4096) {
910592ffb21SWarner Losh 		DRM_UNLOCK(dev);
911592ffb21SWarner Losh 		atomic_dec(&dev->buf_alloc);
912592ffb21SWarner Losh 		return -EINVAL;
913592ffb21SWarner Losh 	}
914592ffb21SWarner Losh 
915592ffb21SWarner Losh 	entry->buflist = malloc(count * sizeof(*entry->buflist), DRM_MEM_BUFS,
916592ffb21SWarner Losh 	    M_NOWAIT | M_ZERO);
917592ffb21SWarner Losh 	if (!entry->buflist) {
918592ffb21SWarner Losh 		DRM_UNLOCK(dev);
919592ffb21SWarner Losh 		atomic_dec(&dev->buf_alloc);
920592ffb21SWarner Losh 		return -ENOMEM;
921592ffb21SWarner Losh 	}
922592ffb21SWarner Losh 
923592ffb21SWarner Losh 	entry->seglist = malloc(count * sizeof(*entry->seglist), DRM_MEM_SEGS,
924592ffb21SWarner Losh 	    M_NOWAIT | M_ZERO);
925592ffb21SWarner Losh 	if (!entry->seglist) {
926592ffb21SWarner Losh 		free(entry->buflist, DRM_MEM_BUFS);
927592ffb21SWarner Losh 		DRM_UNLOCK(dev);
928592ffb21SWarner Losh 		atomic_dec(&dev->buf_alloc);
929592ffb21SWarner Losh 		return -ENOMEM;
930592ffb21SWarner Losh 	}
931592ffb21SWarner Losh 
932592ffb21SWarner Losh 	/* Keep the original pagelist until we know all the allocations
933592ffb21SWarner Losh 	 * have succeeded
934592ffb21SWarner Losh 	 */
935592ffb21SWarner Losh 	temp_pagelist = malloc((dma->page_count + (count << page_order)) *
936592ffb21SWarner Losh 	    sizeof(*dma->pagelist), DRM_MEM_PAGES, M_NOWAIT);
937592ffb21SWarner Losh 	if (!temp_pagelist) {
938592ffb21SWarner Losh 		free(entry->buflist, DRM_MEM_BUFS);
939592ffb21SWarner Losh 		free(entry->seglist, DRM_MEM_SEGS);
940592ffb21SWarner Losh 		DRM_UNLOCK(dev);
941592ffb21SWarner Losh 		atomic_dec(&dev->buf_alloc);
942592ffb21SWarner Losh 		return -ENOMEM;
943592ffb21SWarner Losh 	}
944592ffb21SWarner Losh 	memcpy(temp_pagelist,
945592ffb21SWarner Losh 	       dma->pagelist, dma->page_count * sizeof(*dma->pagelist));
946592ffb21SWarner Losh 	DRM_DEBUG("pagelist: %d entries\n",
947592ffb21SWarner Losh 		  dma->page_count + (count << page_order));
948592ffb21SWarner Losh 
949592ffb21SWarner Losh 	entry->buf_size = size;
950592ffb21SWarner Losh 	entry->page_order = page_order;
951592ffb21SWarner Losh 	byte_count = 0;
952592ffb21SWarner Losh 	page_count = 0;
953592ffb21SWarner Losh 
954592ffb21SWarner Losh 	while (entry->buf_count < count) {
955592ffb21SWarner Losh 
956592ffb21SWarner Losh 		dmah = drm_pci_alloc(dev, PAGE_SIZE << page_order, 0x1000, BUS_SPACE_MAXADDR);
957592ffb21SWarner Losh 
958592ffb21SWarner Losh 		if (!dmah) {
959592ffb21SWarner Losh 			/* Set count correctly so we free the proper amount. */
960592ffb21SWarner Losh 			entry->buf_count = count;
961592ffb21SWarner Losh 			entry->seg_count = count;
962592ffb21SWarner Losh 			drm_cleanup_buf_error(dev, entry);
963592ffb21SWarner Losh 			free(temp_pagelist, DRM_MEM_PAGES);
964592ffb21SWarner Losh 			DRM_UNLOCK(dev);
965592ffb21SWarner Losh 			atomic_dec(&dev->buf_alloc);
966592ffb21SWarner Losh 			return -ENOMEM;
967592ffb21SWarner Losh 		}
968592ffb21SWarner Losh 		entry->seglist[entry->seg_count++] = dmah;
969592ffb21SWarner Losh 		for (i = 0; i < (1 << page_order); i++) {
970592ffb21SWarner Losh 			DRM_DEBUG("page %d @ 0x%08lx\n",
971592ffb21SWarner Losh 				  dma->page_count + page_count,
972592ffb21SWarner Losh 				  (unsigned long)dmah->vaddr + PAGE_SIZE * i);
973592ffb21SWarner Losh 			temp_pagelist[dma->page_count + page_count++]
974592ffb21SWarner Losh 				= (unsigned long)dmah->vaddr + PAGE_SIZE * i;
975592ffb21SWarner Losh 		}
976592ffb21SWarner Losh 		for (offset = 0;
977592ffb21SWarner Losh 		     offset + size <= total && entry->buf_count < count;
978592ffb21SWarner Losh 		     offset += alignment, ++entry->buf_count) {
979592ffb21SWarner Losh 			buf = &entry->buflist[entry->buf_count];
980592ffb21SWarner Losh 			buf->idx = dma->buf_count + entry->buf_count;
981592ffb21SWarner Losh 			buf->total = alignment;
982592ffb21SWarner Losh 			buf->order = order;
983592ffb21SWarner Losh 			buf->used = 0;
984592ffb21SWarner Losh 			buf->offset = (dma->byte_count + byte_count + offset);
985592ffb21SWarner Losh 			buf->address = (void *)((char *)dmah->vaddr + offset);
986592ffb21SWarner Losh 			buf->bus_address = dmah->busaddr + offset;
987592ffb21SWarner Losh 			buf->next = NULL;
988592ffb21SWarner Losh 			buf->waiting = 0;
989592ffb21SWarner Losh 			buf->pending = 0;
990592ffb21SWarner Losh 			buf->file_priv = NULL;
991592ffb21SWarner Losh 
992592ffb21SWarner Losh 			buf->dev_priv_size = dev->driver->dev_priv_size;
993592ffb21SWarner Losh 			buf->dev_private = malloc(buf->dev_priv_size,
994592ffb21SWarner Losh 			    DRM_MEM_BUFS, M_NOWAIT | M_ZERO);
995592ffb21SWarner Losh 			if (!buf->dev_private) {
996592ffb21SWarner Losh 				/* Set count correctly so we free the proper amount. */
997592ffb21SWarner Losh 				entry->buf_count = count;
998592ffb21SWarner Losh 				entry->seg_count = count;
999592ffb21SWarner Losh 				drm_cleanup_buf_error(dev, entry);
1000592ffb21SWarner Losh 				free(temp_pagelist, DRM_MEM_PAGES);
1001592ffb21SWarner Losh 				DRM_UNLOCK(dev);
1002592ffb21SWarner Losh 				atomic_dec(&dev->buf_alloc);
1003592ffb21SWarner Losh 				return -ENOMEM;
1004592ffb21SWarner Losh 			}
1005592ffb21SWarner Losh 
1006592ffb21SWarner Losh 			DRM_DEBUG("buffer %d @ %p\n",
1007592ffb21SWarner Losh 				  entry->buf_count, buf->address);
1008592ffb21SWarner Losh 		}
1009592ffb21SWarner Losh 		byte_count += PAGE_SIZE << page_order;
1010592ffb21SWarner Losh 	}
1011592ffb21SWarner Losh 
1012592ffb21SWarner Losh 	temp_buflist = realloc(dma->buflist,
1013592ffb21SWarner Losh 	    (dma->buf_count + entry->buf_count) * sizeof(*dma->buflist),
1014592ffb21SWarner Losh 	    DRM_MEM_BUFS, M_NOWAIT);
1015592ffb21SWarner Losh 	if (!temp_buflist) {
1016592ffb21SWarner Losh 		/* Free the entry because it isn't valid */
1017592ffb21SWarner Losh 		drm_cleanup_buf_error(dev, entry);
1018592ffb21SWarner Losh 		free(temp_pagelist, DRM_MEM_PAGES);
1019592ffb21SWarner Losh 		DRM_UNLOCK(dev);
1020592ffb21SWarner Losh 		atomic_dec(&dev->buf_alloc);
1021592ffb21SWarner Losh 		return -ENOMEM;
1022592ffb21SWarner Losh 	}
1023592ffb21SWarner Losh 	dma->buflist = temp_buflist;
1024592ffb21SWarner Losh 
1025592ffb21SWarner Losh 	for (i = 0; i < entry->buf_count; i++) {
1026592ffb21SWarner Losh 		dma->buflist[i + dma->buf_count] = &entry->buflist[i];
1027592ffb21SWarner Losh 	}
1028592ffb21SWarner Losh 
1029592ffb21SWarner Losh 	/* No allocations failed, so now we can replace the original pagelist
1030592ffb21SWarner Losh 	 * with the new one.
1031592ffb21SWarner Losh 	 */
1032592ffb21SWarner Losh 	if (dma->page_count) {
1033592ffb21SWarner Losh 		free(dma->pagelist, DRM_MEM_PAGES);
1034592ffb21SWarner Losh 	}
1035592ffb21SWarner Losh 	dma->pagelist = temp_pagelist;
1036592ffb21SWarner Losh 
1037592ffb21SWarner Losh 	dma->buf_count += entry->buf_count;
1038592ffb21SWarner Losh 	dma->seg_count += entry->seg_count;
1039592ffb21SWarner Losh 	dma->page_count += entry->seg_count << page_order;
1040592ffb21SWarner Losh 	dma->byte_count += PAGE_SIZE * (entry->seg_count << page_order);
1041592ffb21SWarner Losh 
1042592ffb21SWarner Losh 	DRM_UNLOCK(dev);
1043592ffb21SWarner Losh 
1044592ffb21SWarner Losh 	request->count = entry->buf_count;
1045592ffb21SWarner Losh 	request->size = size;
1046592ffb21SWarner Losh 
1047592ffb21SWarner Losh 	if (request->flags & _DRM_PCI_BUFFER_RO)
1048592ffb21SWarner Losh 		dma->flags = _DRM_DMA_USE_PCI_RO;
1049592ffb21SWarner Losh 
1050592ffb21SWarner Losh 	atomic_dec(&dev->buf_alloc);
1051592ffb21SWarner Losh 	return 0;
1052592ffb21SWarner Losh 
1053592ffb21SWarner Losh }
1054592ffb21SWarner Losh EXPORT_SYMBOL(drm_addbufs_pci);
1055592ffb21SWarner Losh 
1056592ffb21SWarner Losh static int drm_addbufs_sg(struct drm_device * dev, struct drm_buf_desc * request)
1057592ffb21SWarner Losh {
1058592ffb21SWarner Losh 	struct drm_device_dma *dma = dev->dma;
1059592ffb21SWarner Losh 	struct drm_buf_entry *entry;
1060592ffb21SWarner Losh 	struct drm_buf *buf;
1061592ffb21SWarner Losh 	unsigned long offset;
1062592ffb21SWarner Losh 	unsigned long agp_offset;
1063592ffb21SWarner Losh 	int count;
1064592ffb21SWarner Losh 	int order;
1065592ffb21SWarner Losh 	int size;
1066592ffb21SWarner Losh 	int alignment;
1067592ffb21SWarner Losh 	int page_order;
1068592ffb21SWarner Losh 	int total;
1069592ffb21SWarner Losh 	int byte_count;
1070592ffb21SWarner Losh 	int i;
1071592ffb21SWarner Losh 	struct drm_buf **temp_buflist;
1072592ffb21SWarner Losh 
1073592ffb21SWarner Losh 	if (!drm_core_check_feature(dev, DRIVER_SG))
1074592ffb21SWarner Losh 		return -EINVAL;
1075592ffb21SWarner Losh 
1076592ffb21SWarner Losh 	if (!dma)
1077592ffb21SWarner Losh 		return -EINVAL;
1078592ffb21SWarner Losh 
1079592ffb21SWarner Losh 	if (!DRM_SUSER(DRM_CURPROC))
1080592ffb21SWarner Losh 		return -EPERM;
1081592ffb21SWarner Losh 
1082592ffb21SWarner Losh 	count = request->count;
1083592ffb21SWarner Losh 	order = drm_order(request->size);
1084592ffb21SWarner Losh 	size = 1 << order;
1085592ffb21SWarner Losh 
1086592ffb21SWarner Losh 	alignment = (request->flags & _DRM_PAGE_ALIGN)
1087592ffb21SWarner Losh 	    ? PAGE_ALIGN(size) : size;
1088592ffb21SWarner Losh 	page_order = order - PAGE_SHIFT > 0 ? order - PAGE_SHIFT : 0;
1089592ffb21SWarner Losh 	total = PAGE_SIZE << page_order;
1090592ffb21SWarner Losh 
1091592ffb21SWarner Losh 	byte_count = 0;
1092592ffb21SWarner Losh 	agp_offset = request->agp_start;
1093592ffb21SWarner Losh 
1094592ffb21SWarner Losh 	DRM_DEBUG("count:      %d\n", count);
1095592ffb21SWarner Losh 	DRM_DEBUG("order:      %d\n", order);
1096592ffb21SWarner Losh 	DRM_DEBUG("size:       %d\n", size);
1097592ffb21SWarner Losh 	DRM_DEBUG("agp_offset: %lu\n", agp_offset);
1098592ffb21SWarner Losh 	DRM_DEBUG("alignment:  %d\n", alignment);
1099592ffb21SWarner Losh 	DRM_DEBUG("page_order: %d\n", page_order);
1100592ffb21SWarner Losh 	DRM_DEBUG("total:      %d\n", total);
1101592ffb21SWarner Losh 
1102592ffb21SWarner Losh 	if (order < DRM_MIN_ORDER || order > DRM_MAX_ORDER)
1103592ffb21SWarner Losh 		return -EINVAL;
1104592ffb21SWarner Losh 
1105592ffb21SWarner Losh 	mtx_lock(&dev->count_lock);
1106592ffb21SWarner Losh 	if (dev->buf_use) {
1107592ffb21SWarner Losh 		mtx_unlock(&dev->count_lock);
1108592ffb21SWarner Losh 		return -EBUSY;
1109592ffb21SWarner Losh 	}
1110592ffb21SWarner Losh 	atomic_inc(&dev->buf_alloc);
1111592ffb21SWarner Losh 	mtx_unlock(&dev->count_lock);
1112592ffb21SWarner Losh 
1113592ffb21SWarner Losh 	DRM_LOCK(dev);
1114592ffb21SWarner Losh 	entry = &dma->bufs[order];
1115592ffb21SWarner Losh 	if (entry->buf_count) {
1116592ffb21SWarner Losh 		DRM_UNLOCK(dev);
1117592ffb21SWarner Losh 		atomic_dec(&dev->buf_alloc);
1118592ffb21SWarner Losh 		return -ENOMEM;	/* May only call once for each order */
1119592ffb21SWarner Losh 	}
1120592ffb21SWarner Losh 
1121592ffb21SWarner Losh 	if (count < 0 || count > 4096) {
1122592ffb21SWarner Losh 		DRM_UNLOCK(dev);
1123592ffb21SWarner Losh 		atomic_dec(&dev->buf_alloc);
1124592ffb21SWarner Losh 		return -EINVAL;
1125592ffb21SWarner Losh 	}
1126592ffb21SWarner Losh 
1127592ffb21SWarner Losh 	entry->buflist = malloc(count * sizeof(*entry->buflist), DRM_MEM_BUFS,
1128592ffb21SWarner Losh 	    M_NOWAIT | M_ZERO);
1129592ffb21SWarner Losh 	if (!entry->buflist) {
1130592ffb21SWarner Losh 		DRM_UNLOCK(dev);
1131592ffb21SWarner Losh 		atomic_dec(&dev->buf_alloc);
1132592ffb21SWarner Losh 		return -ENOMEM;
1133592ffb21SWarner Losh 	}
1134592ffb21SWarner Losh 
1135592ffb21SWarner Losh 	entry->buf_size = size;
1136592ffb21SWarner Losh 	entry->page_order = page_order;
1137592ffb21SWarner Losh 
1138592ffb21SWarner Losh 	offset = 0;
1139592ffb21SWarner Losh 
1140592ffb21SWarner Losh 	while (entry->buf_count < count) {
1141592ffb21SWarner Losh 		buf = &entry->buflist[entry->buf_count];
1142592ffb21SWarner Losh 		buf->idx = dma->buf_count + entry->buf_count;
1143592ffb21SWarner Losh 		buf->total = alignment;
1144592ffb21SWarner Losh 		buf->order = order;
1145592ffb21SWarner Losh 		buf->used = 0;
1146592ffb21SWarner Losh 
1147592ffb21SWarner Losh 		buf->offset = (dma->byte_count + offset);
1148592ffb21SWarner Losh 		buf->bus_address = agp_offset + offset;
1149592ffb21SWarner Losh 		buf->address = (void *)(agp_offset + offset
1150592ffb21SWarner Losh 					+ (unsigned long)dev->sg->vaddr);
1151592ffb21SWarner Losh 		buf->next = NULL;
1152592ffb21SWarner Losh 		buf->waiting = 0;
1153592ffb21SWarner Losh 		buf->pending = 0;
1154592ffb21SWarner Losh 		buf->file_priv = NULL;
1155592ffb21SWarner Losh 
1156592ffb21SWarner Losh 		buf->dev_priv_size = dev->driver->dev_priv_size;
1157592ffb21SWarner Losh 		buf->dev_private = malloc(buf->dev_priv_size, DRM_MEM_BUFS,
1158592ffb21SWarner Losh 		    M_NOWAIT | M_ZERO);
1159592ffb21SWarner Losh 		if (!buf->dev_private) {
1160592ffb21SWarner Losh 			/* Set count correctly so we free the proper amount. */
1161592ffb21SWarner Losh 			entry->buf_count = count;
1162592ffb21SWarner Losh 			drm_cleanup_buf_error(dev, entry);
1163592ffb21SWarner Losh 			DRM_UNLOCK(dev);
1164592ffb21SWarner Losh 			atomic_dec(&dev->buf_alloc);
1165592ffb21SWarner Losh 			return -ENOMEM;
1166592ffb21SWarner Losh 		}
1167592ffb21SWarner Losh 
1168592ffb21SWarner Losh 		DRM_DEBUG("buffer %d @ %p\n", entry->buf_count, buf->address);
1169592ffb21SWarner Losh 
1170592ffb21SWarner Losh 		offset += alignment;
1171592ffb21SWarner Losh 		entry->buf_count++;
1172592ffb21SWarner Losh 		byte_count += PAGE_SIZE << page_order;
1173592ffb21SWarner Losh 	}
1174592ffb21SWarner Losh 
1175592ffb21SWarner Losh 	DRM_DEBUG("byte_count: %d\n", byte_count);
1176592ffb21SWarner Losh 
1177592ffb21SWarner Losh 	temp_buflist = realloc(dma->buflist,
1178592ffb21SWarner Losh 	    (dma->buf_count + entry->buf_count) * sizeof(*dma->buflist),
1179592ffb21SWarner Losh 	    DRM_MEM_BUFS, M_NOWAIT);
1180592ffb21SWarner Losh 	if (!temp_buflist) {
1181592ffb21SWarner Losh 		/* Free the entry because it isn't valid */
1182592ffb21SWarner Losh 		drm_cleanup_buf_error(dev, entry);
1183592ffb21SWarner Losh 		DRM_UNLOCK(dev);
1184592ffb21SWarner Losh 		atomic_dec(&dev->buf_alloc);
1185592ffb21SWarner Losh 		return -ENOMEM;
1186592ffb21SWarner Losh 	}
1187592ffb21SWarner Losh 	dma->buflist = temp_buflist;
1188592ffb21SWarner Losh 
1189592ffb21SWarner Losh 	for (i = 0; i < entry->buf_count; i++) {
1190592ffb21SWarner Losh 		dma->buflist[i + dma->buf_count] = &entry->buflist[i];
1191592ffb21SWarner Losh 	}
1192592ffb21SWarner Losh 
1193592ffb21SWarner Losh 	dma->buf_count += entry->buf_count;
1194592ffb21SWarner Losh 	dma->seg_count += entry->seg_count;
1195592ffb21SWarner Losh 	dma->page_count += byte_count >> PAGE_SHIFT;
1196592ffb21SWarner Losh 	dma->byte_count += byte_count;
1197592ffb21SWarner Losh 
1198592ffb21SWarner Losh 	DRM_DEBUG("dma->buf_count : %d\n", dma->buf_count);
1199592ffb21SWarner Losh 	DRM_DEBUG("entry->buf_count : %d\n", entry->buf_count);
1200592ffb21SWarner Losh 
1201592ffb21SWarner Losh 	DRM_UNLOCK(dev);
1202592ffb21SWarner Losh 
1203592ffb21SWarner Losh 	request->count = entry->buf_count;
1204592ffb21SWarner Losh 	request->size = size;
1205592ffb21SWarner Losh 
1206592ffb21SWarner Losh 	dma->flags = _DRM_DMA_USE_SG;
1207592ffb21SWarner Losh 
1208592ffb21SWarner Losh 	atomic_dec(&dev->buf_alloc);
1209592ffb21SWarner Losh 	return 0;
1210592ffb21SWarner Losh }
1211592ffb21SWarner Losh 
1212592ffb21SWarner Losh static int drm_addbufs_fb(struct drm_device * dev, struct drm_buf_desc * request)
1213592ffb21SWarner Losh {
1214592ffb21SWarner Losh 	struct drm_device_dma *dma = dev->dma;
1215592ffb21SWarner Losh 	struct drm_buf_entry *entry;
1216592ffb21SWarner Losh 	struct drm_buf *buf;
1217592ffb21SWarner Losh 	unsigned long offset;
1218592ffb21SWarner Losh 	unsigned long agp_offset;
1219592ffb21SWarner Losh 	int count;
1220592ffb21SWarner Losh 	int order;
1221592ffb21SWarner Losh 	int size;
1222592ffb21SWarner Losh 	int alignment;
1223592ffb21SWarner Losh 	int page_order;
1224592ffb21SWarner Losh 	int total;
1225592ffb21SWarner Losh 	int byte_count;
1226592ffb21SWarner Losh 	int i;
1227592ffb21SWarner Losh 	struct drm_buf **temp_buflist;
1228592ffb21SWarner Losh 
1229592ffb21SWarner Losh 	if (!drm_core_check_feature(dev, DRIVER_FB_DMA))
1230592ffb21SWarner Losh 		return -EINVAL;
1231592ffb21SWarner Losh 
1232592ffb21SWarner Losh 	if (!dma)
1233592ffb21SWarner Losh 		return -EINVAL;
1234592ffb21SWarner Losh 
1235592ffb21SWarner Losh 	if (!DRM_SUSER(DRM_CURPROC))
1236592ffb21SWarner Losh 		return -EPERM;
1237592ffb21SWarner Losh 
1238592ffb21SWarner Losh 	count = request->count;
1239592ffb21SWarner Losh 	order = drm_order(request->size);
1240592ffb21SWarner Losh 	size = 1 << order;
1241592ffb21SWarner Losh 
1242592ffb21SWarner Losh 	alignment = (request->flags & _DRM_PAGE_ALIGN)
1243592ffb21SWarner Losh 	    ? PAGE_ALIGN(size) : size;
1244592ffb21SWarner Losh 	page_order = order - PAGE_SHIFT > 0 ? order - PAGE_SHIFT : 0;
1245592ffb21SWarner Losh 	total = PAGE_SIZE << page_order;
1246592ffb21SWarner Losh 
1247592ffb21SWarner Losh 	byte_count = 0;
1248592ffb21SWarner Losh 	agp_offset = request->agp_start;
1249592ffb21SWarner Losh 
1250592ffb21SWarner Losh 	DRM_DEBUG("count:      %d\n", count);
1251592ffb21SWarner Losh 	DRM_DEBUG("order:      %d\n", order);
1252592ffb21SWarner Losh 	DRM_DEBUG("size:       %d\n", size);
1253592ffb21SWarner Losh 	DRM_DEBUG("agp_offset: %lu\n", agp_offset);
1254592ffb21SWarner Losh 	DRM_DEBUG("alignment:  %d\n", alignment);
1255592ffb21SWarner Losh 	DRM_DEBUG("page_order: %d\n", page_order);
1256592ffb21SWarner Losh 	DRM_DEBUG("total:      %d\n", total);
1257592ffb21SWarner Losh 
1258592ffb21SWarner Losh 	if (order < DRM_MIN_ORDER || order > DRM_MAX_ORDER)
1259592ffb21SWarner Losh 		return -EINVAL;
1260592ffb21SWarner Losh 
1261592ffb21SWarner Losh 	mtx_lock(&dev->count_lock);
1262592ffb21SWarner Losh 	if (dev->buf_use) {
1263592ffb21SWarner Losh 		mtx_unlock(&dev->count_lock);
1264592ffb21SWarner Losh 		return -EBUSY;
1265592ffb21SWarner Losh 	}
1266592ffb21SWarner Losh 	atomic_inc(&dev->buf_alloc);
1267592ffb21SWarner Losh 	mtx_unlock(&dev->count_lock);
1268592ffb21SWarner Losh 
1269592ffb21SWarner Losh 	DRM_LOCK(dev);
1270592ffb21SWarner Losh 	entry = &dma->bufs[order];
1271592ffb21SWarner Losh 	if (entry->buf_count) {
1272592ffb21SWarner Losh 		DRM_UNLOCK(dev);
1273592ffb21SWarner Losh 		atomic_dec(&dev->buf_alloc);
1274592ffb21SWarner Losh 		return -ENOMEM;	/* May only call once for each order */
1275592ffb21SWarner Losh 	}
1276592ffb21SWarner Losh 
1277592ffb21SWarner Losh 	if (count < 0 || count > 4096) {
1278592ffb21SWarner Losh 		DRM_UNLOCK(dev);
1279592ffb21SWarner Losh 		atomic_dec(&dev->buf_alloc);
1280592ffb21SWarner Losh 		return -EINVAL;
1281592ffb21SWarner Losh 	}
1282592ffb21SWarner Losh 
1283592ffb21SWarner Losh 	entry->buflist = malloc(count * sizeof(*entry->buflist), DRM_MEM_BUFS,
1284592ffb21SWarner Losh 	    M_NOWAIT | M_ZERO);
1285592ffb21SWarner Losh 	if (!entry->buflist) {
1286592ffb21SWarner Losh 		DRM_UNLOCK(dev);
1287592ffb21SWarner Losh 		atomic_dec(&dev->buf_alloc);
1288592ffb21SWarner Losh 		return -ENOMEM;
1289592ffb21SWarner Losh 	}
1290592ffb21SWarner Losh 
1291592ffb21SWarner Losh 	entry->buf_size = size;
1292592ffb21SWarner Losh 	entry->page_order = page_order;
1293592ffb21SWarner Losh 
1294592ffb21SWarner Losh 	offset = 0;
1295592ffb21SWarner Losh 
1296592ffb21SWarner Losh 	while (entry->buf_count < count) {
1297592ffb21SWarner Losh 		buf = &entry->buflist[entry->buf_count];
1298592ffb21SWarner Losh 		buf->idx = dma->buf_count + entry->buf_count;
1299592ffb21SWarner Losh 		buf->total = alignment;
1300592ffb21SWarner Losh 		buf->order = order;
1301592ffb21SWarner Losh 		buf->used = 0;
1302592ffb21SWarner Losh 
1303592ffb21SWarner Losh 		buf->offset = (dma->byte_count + offset);
1304592ffb21SWarner Losh 		buf->bus_address = agp_offset + offset;
1305592ffb21SWarner Losh 		buf->address = (void *)(agp_offset + offset);
1306592ffb21SWarner Losh 		buf->next = NULL;
1307592ffb21SWarner Losh 		buf->waiting = 0;
1308592ffb21SWarner Losh 		buf->pending = 0;
1309592ffb21SWarner Losh 		buf->file_priv = NULL;
1310592ffb21SWarner Losh 
1311592ffb21SWarner Losh 		buf->dev_priv_size = dev->driver->dev_priv_size;
1312592ffb21SWarner Losh 		buf->dev_private = malloc(buf->dev_priv_size, DRM_MEM_BUFS,
1313592ffb21SWarner Losh 		    M_NOWAIT | M_ZERO);
1314592ffb21SWarner Losh 		if (!buf->dev_private) {
1315592ffb21SWarner Losh 			/* Set count correctly so we free the proper amount. */
1316592ffb21SWarner Losh 			entry->buf_count = count;
1317592ffb21SWarner Losh 			drm_cleanup_buf_error(dev, entry);
1318592ffb21SWarner Losh 			DRM_UNLOCK(dev);
1319592ffb21SWarner Losh 			atomic_dec(&dev->buf_alloc);
1320592ffb21SWarner Losh 			return -ENOMEM;
1321592ffb21SWarner Losh 		}
1322592ffb21SWarner Losh 
1323592ffb21SWarner Losh 		DRM_DEBUG("buffer %d @ %p\n", entry->buf_count, buf->address);
1324592ffb21SWarner Losh 
1325592ffb21SWarner Losh 		offset += alignment;
1326592ffb21SWarner Losh 		entry->buf_count++;
1327592ffb21SWarner Losh 		byte_count += PAGE_SIZE << page_order;
1328592ffb21SWarner Losh 	}
1329592ffb21SWarner Losh 
1330592ffb21SWarner Losh 	DRM_DEBUG("byte_count: %d\n", byte_count);
1331592ffb21SWarner Losh 
1332592ffb21SWarner Losh 	temp_buflist = realloc(dma->buflist,
1333592ffb21SWarner Losh 	    (dma->buf_count + entry->buf_count) * sizeof(*dma->buflist),
1334592ffb21SWarner Losh 	    DRM_MEM_BUFS, M_NOWAIT);
1335592ffb21SWarner Losh 	if (!temp_buflist) {
1336592ffb21SWarner Losh 		/* Free the entry because it isn't valid */
1337592ffb21SWarner Losh 		drm_cleanup_buf_error(dev, entry);
1338592ffb21SWarner Losh 		DRM_UNLOCK(dev);
1339592ffb21SWarner Losh 		atomic_dec(&dev->buf_alloc);
1340592ffb21SWarner Losh 		return -ENOMEM;
1341592ffb21SWarner Losh 	}
1342592ffb21SWarner Losh 	dma->buflist = temp_buflist;
1343592ffb21SWarner Losh 
1344592ffb21SWarner Losh 	for (i = 0; i < entry->buf_count; i++) {
1345592ffb21SWarner Losh 		dma->buflist[i + dma->buf_count] = &entry->buflist[i];
1346592ffb21SWarner Losh 	}
1347592ffb21SWarner Losh 
1348592ffb21SWarner Losh 	dma->buf_count += entry->buf_count;
1349592ffb21SWarner Losh 	dma->seg_count += entry->seg_count;
1350592ffb21SWarner Losh 	dma->page_count += byte_count >> PAGE_SHIFT;
1351592ffb21SWarner Losh 	dma->byte_count += byte_count;
1352592ffb21SWarner Losh 
1353592ffb21SWarner Losh 	DRM_DEBUG("dma->buf_count : %d\n", dma->buf_count);
1354592ffb21SWarner Losh 	DRM_DEBUG("entry->buf_count : %d\n", entry->buf_count);
1355592ffb21SWarner Losh 
1356592ffb21SWarner Losh 	DRM_UNLOCK(dev);
1357592ffb21SWarner Losh 
1358592ffb21SWarner Losh 	request->count = entry->buf_count;
1359592ffb21SWarner Losh 	request->size = size;
1360592ffb21SWarner Losh 
1361592ffb21SWarner Losh 	dma->flags = _DRM_DMA_USE_FB;
1362592ffb21SWarner Losh 
1363592ffb21SWarner Losh 	atomic_dec(&dev->buf_alloc);
1364592ffb21SWarner Losh 	return 0;
1365592ffb21SWarner Losh }
1366592ffb21SWarner Losh 
1367592ffb21SWarner Losh 
1368592ffb21SWarner Losh /**
1369592ffb21SWarner Losh  * Add buffers for DMA transfers (ioctl).
1370592ffb21SWarner Losh  *
1371592ffb21SWarner Losh  * \param inode device inode.
1372592ffb21SWarner Losh  * \param file_priv DRM file private.
1373592ffb21SWarner Losh  * \param cmd command.
1374592ffb21SWarner Losh  * \param arg pointer to a struct drm_buf_desc request.
1375592ffb21SWarner Losh  * \return zero on success or a negative number on failure.
1376592ffb21SWarner Losh  *
1377592ffb21SWarner Losh  * According with the memory type specified in drm_buf_desc::flags and the
1378592ffb21SWarner Losh  * build options, it dispatches the call either to addbufs_agp(),
1379592ffb21SWarner Losh  * addbufs_sg() or addbufs_pci() for AGP, scatter-gather or consistent
1380592ffb21SWarner Losh  * PCI memory respectively.
1381592ffb21SWarner Losh  */
1382592ffb21SWarner Losh int drm_addbufs(struct drm_device *dev, void *data,
1383592ffb21SWarner Losh 		struct drm_file *file_priv)
1384592ffb21SWarner Losh {
1385592ffb21SWarner Losh 	struct drm_buf_desc *request = data;
1386592ffb21SWarner Losh 	int ret;
1387592ffb21SWarner Losh 
1388592ffb21SWarner Losh 	if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
1389592ffb21SWarner Losh 		return -EINVAL;
1390592ffb21SWarner Losh 
1391592ffb21SWarner Losh #if __OS_HAS_AGP
1392592ffb21SWarner Losh 	if (request->flags & _DRM_AGP_BUFFER)
1393592ffb21SWarner Losh 		ret = drm_addbufs_agp(dev, request);
1394592ffb21SWarner Losh 	else
1395592ffb21SWarner Losh #endif
1396592ffb21SWarner Losh 	if (request->flags & _DRM_SG_BUFFER)
1397592ffb21SWarner Losh 		ret = drm_addbufs_sg(dev, request);
1398592ffb21SWarner Losh 	else if (request->flags & _DRM_FB_BUFFER)
1399592ffb21SWarner Losh 		ret = drm_addbufs_fb(dev, request);
1400592ffb21SWarner Losh 	else
1401592ffb21SWarner Losh 		ret = drm_addbufs_pci(dev, request);
1402592ffb21SWarner Losh 
1403592ffb21SWarner Losh 	return ret;
1404592ffb21SWarner Losh }
1405592ffb21SWarner Losh 
1406592ffb21SWarner Losh /**
1407592ffb21SWarner Losh  * Get information about the buffer mappings.
1408592ffb21SWarner Losh  *
1409592ffb21SWarner Losh  * This was originally mean for debugging purposes, or by a sophisticated
1410592ffb21SWarner Losh  * client library to determine how best to use the available buffers (e.g.,
1411592ffb21SWarner Losh  * large buffers can be used for image transfer).
1412592ffb21SWarner Losh  *
1413592ffb21SWarner Losh  * \param inode device inode.
1414592ffb21SWarner Losh  * \param file_priv DRM file private.
1415592ffb21SWarner Losh  * \param cmd command.
1416592ffb21SWarner Losh  * \param arg pointer to a drm_buf_info structure.
1417592ffb21SWarner Losh  * \return zero on success or a negative number on failure.
1418592ffb21SWarner Losh  *
1419592ffb21SWarner Losh  * Increments drm_device::buf_use while holding the drm_device::count_lock
1420592ffb21SWarner Losh  * lock, preventing of allocating more buffers after this call. Information
1421592ffb21SWarner Losh  * about each requested buffer is then copied into user space.
1422592ffb21SWarner Losh  */
1423592ffb21SWarner Losh int drm_infobufs(struct drm_device *dev, void *data,
1424592ffb21SWarner Losh 		 struct drm_file *file_priv)
1425592ffb21SWarner Losh {
1426592ffb21SWarner Losh 	struct drm_device_dma *dma = dev->dma;
1427592ffb21SWarner Losh 	struct drm_buf_info *request = data;
1428592ffb21SWarner Losh 	int i;
1429592ffb21SWarner Losh 	int count;
1430592ffb21SWarner Losh 
1431592ffb21SWarner Losh 	if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
1432592ffb21SWarner Losh 		return -EINVAL;
1433592ffb21SWarner Losh 
1434592ffb21SWarner Losh 	if (!dma)
1435592ffb21SWarner Losh 		return -EINVAL;
1436592ffb21SWarner Losh 
1437592ffb21SWarner Losh 	mtx_lock(&dev->count_lock);
1438592ffb21SWarner Losh 	if (atomic_read(&dev->buf_alloc)) {
1439592ffb21SWarner Losh 		mtx_unlock(&dev->count_lock);
1440592ffb21SWarner Losh 		return -EBUSY;
1441592ffb21SWarner Losh 	}
1442592ffb21SWarner Losh 	++dev->buf_use;		/* Can't allocate more after this call */
1443592ffb21SWarner Losh 	mtx_unlock(&dev->count_lock);
1444592ffb21SWarner Losh 
1445592ffb21SWarner Losh 	for (i = 0, count = 0; i < DRM_MAX_ORDER + 1; i++) {
1446592ffb21SWarner Losh 		if (dma->bufs[i].buf_count)
1447592ffb21SWarner Losh 			++count;
1448592ffb21SWarner Losh 	}
1449592ffb21SWarner Losh 
1450592ffb21SWarner Losh 	DRM_DEBUG("count = %d\n", count);
1451592ffb21SWarner Losh 
1452592ffb21SWarner Losh 	if (request->count >= count) {
1453592ffb21SWarner Losh 		for (i = 0, count = 0; i < DRM_MAX_ORDER + 1; i++) {
1454592ffb21SWarner Losh 			if (dma->bufs[i].buf_count) {
1455592ffb21SWarner Losh 				struct drm_buf_desc __user *to =
1456592ffb21SWarner Losh 				    &request->list[count];
1457592ffb21SWarner Losh 				struct drm_buf_entry *from = &dma->bufs[i];
1458592ffb21SWarner Losh 				struct drm_freelist *list = &dma->bufs[i].freelist;
1459592ffb21SWarner Losh 				if (copy_to_user(&to->count,
1460592ffb21SWarner Losh 						 &from->buf_count,
1461592ffb21SWarner Losh 						 sizeof(from->buf_count)) ||
1462592ffb21SWarner Losh 				    copy_to_user(&to->size,
1463592ffb21SWarner Losh 						 &from->buf_size,
1464592ffb21SWarner Losh 						 sizeof(from->buf_size)) ||
1465592ffb21SWarner Losh 				    copy_to_user(&to->low_mark,
1466592ffb21SWarner Losh 						 &list->low_mark,
1467592ffb21SWarner Losh 						 sizeof(list->low_mark)) ||
1468592ffb21SWarner Losh 				    copy_to_user(&to->high_mark,
1469592ffb21SWarner Losh 						 &list->high_mark,
1470592ffb21SWarner Losh 						 sizeof(list->high_mark)))
1471592ffb21SWarner Losh 					return -EFAULT;
1472592ffb21SWarner Losh 
1473592ffb21SWarner Losh 				DRM_DEBUG("%d %d %d %d %d\n",
1474592ffb21SWarner Losh 					  i,
1475592ffb21SWarner Losh 					  dma->bufs[i].buf_count,
1476592ffb21SWarner Losh 					  dma->bufs[i].buf_size,
1477592ffb21SWarner Losh 					  dma->bufs[i].freelist.low_mark,
1478592ffb21SWarner Losh 					  dma->bufs[i].freelist.high_mark);
1479592ffb21SWarner Losh 				++count;
1480592ffb21SWarner Losh 			}
1481592ffb21SWarner Losh 		}
1482592ffb21SWarner Losh 	}
1483592ffb21SWarner Losh 	request->count = count;
1484592ffb21SWarner Losh 
1485592ffb21SWarner Losh 	return 0;
1486592ffb21SWarner Losh }
1487592ffb21SWarner Losh 
1488592ffb21SWarner Losh /**
1489592ffb21SWarner Losh  * Specifies a low and high water mark for buffer allocation
1490592ffb21SWarner Losh  *
1491592ffb21SWarner Losh  * \param inode device inode.
1492592ffb21SWarner Losh  * \param file_priv DRM file private.
1493592ffb21SWarner Losh  * \param cmd command.
1494592ffb21SWarner Losh  * \param arg a pointer to a drm_buf_desc structure.
1495592ffb21SWarner Losh  * \return zero on success or a negative number on failure.
1496592ffb21SWarner Losh  *
1497592ffb21SWarner Losh  * Verifies that the size order is bounded between the admissible orders and
1498592ffb21SWarner Losh  * updates the respective drm_device_dma::bufs entry low and high water mark.
1499592ffb21SWarner Losh  *
1500592ffb21SWarner Losh  * \note This ioctl is deprecated and mostly never used.
1501592ffb21SWarner Losh  */
1502592ffb21SWarner Losh int drm_markbufs(struct drm_device *dev, void *data,
1503592ffb21SWarner Losh 		 struct drm_file *file_priv)
1504592ffb21SWarner Losh {
1505592ffb21SWarner Losh 	struct drm_device_dma *dma = dev->dma;
1506592ffb21SWarner Losh 	struct drm_buf_desc *request = data;
1507592ffb21SWarner Losh 	int order;
1508592ffb21SWarner Losh 	struct drm_buf_entry *entry;
1509592ffb21SWarner Losh 
1510592ffb21SWarner Losh 	if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
1511592ffb21SWarner Losh 		return -EINVAL;
1512592ffb21SWarner Losh 
1513592ffb21SWarner Losh 	if (!dma)
1514592ffb21SWarner Losh 		return -EINVAL;
1515592ffb21SWarner Losh 
1516592ffb21SWarner Losh 	DRM_DEBUG("%d, %d, %d\n",
1517592ffb21SWarner Losh 		  request->size, request->low_mark, request->high_mark);
1518592ffb21SWarner Losh 	order = drm_order(request->size);
1519592ffb21SWarner Losh 	if (order < DRM_MIN_ORDER || order > DRM_MAX_ORDER)
1520592ffb21SWarner Losh 		return -EINVAL;
1521592ffb21SWarner Losh 	entry = &dma->bufs[order];
1522592ffb21SWarner Losh 
1523592ffb21SWarner Losh 	if (request->low_mark < 0 || request->low_mark > entry->buf_count)
1524592ffb21SWarner Losh 		return -EINVAL;
1525592ffb21SWarner Losh 	if (request->high_mark < 0 || request->high_mark > entry->buf_count)
1526592ffb21SWarner Losh 		return -EINVAL;
1527592ffb21SWarner Losh 
1528592ffb21SWarner Losh 	entry->freelist.low_mark = request->low_mark;
1529592ffb21SWarner Losh 	entry->freelist.high_mark = request->high_mark;
1530592ffb21SWarner Losh 
1531592ffb21SWarner Losh 	return 0;
1532592ffb21SWarner Losh }
1533592ffb21SWarner Losh 
1534592ffb21SWarner Losh /**
1535592ffb21SWarner Losh  * Unreserve the buffers in list, previously reserved using drmDMA.
1536592ffb21SWarner Losh  *
1537592ffb21SWarner Losh  * \param inode device inode.
1538592ffb21SWarner Losh  * \param file_priv DRM file private.
1539592ffb21SWarner Losh  * \param cmd command.
1540592ffb21SWarner Losh  * \param arg pointer to a drm_buf_free structure.
1541592ffb21SWarner Losh  * \return zero on success or a negative number on failure.
1542592ffb21SWarner Losh  *
1543592ffb21SWarner Losh  * Calls free_buffer() for each used buffer.
1544592ffb21SWarner Losh  * This function is primarily used for debugging.
1545592ffb21SWarner Losh  */
1546592ffb21SWarner Losh int drm_freebufs(struct drm_device *dev, void *data,
1547592ffb21SWarner Losh 		 struct drm_file *file_priv)
1548592ffb21SWarner Losh {
1549592ffb21SWarner Losh 	struct drm_device_dma *dma = dev->dma;
1550592ffb21SWarner Losh 	struct drm_buf_free *request = data;
1551592ffb21SWarner Losh 	int i;
1552592ffb21SWarner Losh 	int idx;
1553592ffb21SWarner Losh 	struct drm_buf *buf;
1554592ffb21SWarner Losh 
1555592ffb21SWarner Losh 	if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
1556592ffb21SWarner Losh 		return -EINVAL;
1557592ffb21SWarner Losh 
1558592ffb21SWarner Losh 	if (!dma)
1559592ffb21SWarner Losh 		return -EINVAL;
1560592ffb21SWarner Losh 
1561592ffb21SWarner Losh 	DRM_DEBUG("%d\n", request->count);
1562592ffb21SWarner Losh 	for (i = 0; i < request->count; i++) {
1563592ffb21SWarner Losh 		if (copy_from_user(&idx, &request->list[i], sizeof(idx)))
1564592ffb21SWarner Losh 			return -EFAULT;
1565592ffb21SWarner Losh 		if (idx < 0 || idx >= dma->buf_count) {
1566592ffb21SWarner Losh 			DRM_ERROR("Index %d (of %d max)\n",
1567592ffb21SWarner Losh 				  idx, dma->buf_count - 1);
1568592ffb21SWarner Losh 			return -EINVAL;
1569592ffb21SWarner Losh 		}
1570592ffb21SWarner Losh 		buf = dma->buflist[idx];
1571592ffb21SWarner Losh 		if (buf->file_priv != file_priv) {
1572592ffb21SWarner Losh 			DRM_ERROR("Process %d freeing buffer not owned\n",
1573592ffb21SWarner Losh 				  DRM_CURRENTPID);
1574592ffb21SWarner Losh 			return -EINVAL;
1575592ffb21SWarner Losh 		}
1576592ffb21SWarner Losh 		drm_free_buffer(dev, buf);
1577592ffb21SWarner Losh 	}
1578592ffb21SWarner Losh 
1579592ffb21SWarner Losh 	return 0;
1580592ffb21SWarner Losh }
1581592ffb21SWarner Losh 
1582592ffb21SWarner Losh /**
1583592ffb21SWarner Losh  * Maps all of the DMA buffers into client-virtual space (ioctl).
1584592ffb21SWarner Losh  *
1585592ffb21SWarner Losh  * \param inode device inode.
1586592ffb21SWarner Losh  * \param file_priv DRM file private.
1587592ffb21SWarner Losh  * \param cmd command.
1588592ffb21SWarner Losh  * \param arg pointer to a drm_buf_map structure.
1589592ffb21SWarner Losh  * \return zero on success or a negative number on failure.
1590592ffb21SWarner Losh  *
1591592ffb21SWarner Losh  * Maps the AGP, SG or PCI buffer region with vm_mmap(), and copies information
1592592ffb21SWarner Losh  * about each buffer into user space. For PCI buffers, it calls vm_mmap() with
1593592ffb21SWarner Losh  * offset equal to 0, which drm_mmap() interpretes as PCI buffers and calls
1594592ffb21SWarner Losh  * drm_mmap_dma().
1595592ffb21SWarner Losh  */
1596592ffb21SWarner Losh int drm_mapbufs(struct drm_device *dev, void *data,
1597592ffb21SWarner Losh 	        struct drm_file *file_priv)
1598592ffb21SWarner Losh {
1599592ffb21SWarner Losh 	struct drm_device_dma *dma = dev->dma;
1600592ffb21SWarner Losh 	int retcode = 0;
1601592ffb21SWarner Losh 	const int zero = 0;
1602592ffb21SWarner Losh 	vm_offset_t virtual;
1603592ffb21SWarner Losh 	vm_offset_t address;
1604592ffb21SWarner Losh 	struct vmspace *vms;
1605592ffb21SWarner Losh 	struct drm_buf_map *request = data;
1606592ffb21SWarner Losh 	int i;
1607592ffb21SWarner Losh 
1608592ffb21SWarner Losh 	if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
1609592ffb21SWarner Losh 		return -EINVAL;
1610592ffb21SWarner Losh 
1611592ffb21SWarner Losh 	if (!dma)
1612592ffb21SWarner Losh 		return -EINVAL;
1613592ffb21SWarner Losh 
1614592ffb21SWarner Losh 	mtx_lock(&dev->count_lock);
1615592ffb21SWarner Losh 	if (atomic_read(&dev->buf_alloc)) {
1616592ffb21SWarner Losh 		mtx_unlock(&dev->count_lock);
1617592ffb21SWarner Losh 		return -EBUSY;
1618592ffb21SWarner Losh 	}
1619592ffb21SWarner Losh 	dev->buf_use++;		/* Can't allocate more after this call */
1620592ffb21SWarner Losh 	mtx_unlock(&dev->count_lock);
1621592ffb21SWarner Losh 
1622592ffb21SWarner Losh 	vms = DRM_CURPROC->td_proc->p_vmspace;
1623592ffb21SWarner Losh 
1624592ffb21SWarner Losh 	if (request->count >= dma->buf_count) {
1625592ffb21SWarner Losh 		if ((drm_core_has_AGP(dev) && (dma->flags & _DRM_DMA_USE_AGP))
1626592ffb21SWarner Losh 		    || (drm_core_check_feature(dev, DRIVER_SG)
1627592ffb21SWarner Losh 			&& (dma->flags & _DRM_DMA_USE_SG))
1628592ffb21SWarner Losh 		    || (drm_core_check_feature(dev, DRIVER_FB_DMA)
1629592ffb21SWarner Losh 			&& (dma->flags & _DRM_DMA_USE_FB))) {
1630592ffb21SWarner Losh 			struct drm_local_map *map = dev->agp_buffer_map;
1631592ffb21SWarner Losh 			vm_ooffset_t token = dev->agp_buffer_token;
1632592ffb21SWarner Losh 
1633592ffb21SWarner Losh 			if (!map) {
1634592ffb21SWarner Losh 				retcode = -EINVAL;
1635592ffb21SWarner Losh 				goto done;
1636592ffb21SWarner Losh 			}
1637592ffb21SWarner Losh 			retcode = vm_mmap(&vms->vm_map, &virtual, map->size,
1638*f34702b7SJohn Baldwin 			    VM_PROT_RW, VM_PROT_RW, MAP_SHARED | MAP_NOSYNC,
1639*f34702b7SJohn Baldwin 			    OBJT_DEVICE, file_priv->minor->device, token);
1640592ffb21SWarner Losh 		} else {
1641592ffb21SWarner Losh 			retcode = vm_mmap(&vms->vm_map, &virtual, dma->byte_count,
1642*f34702b7SJohn Baldwin 			    VM_PROT_RW, VM_PROT_RW, MAP_SHARED | MAP_NOSYNC,
1643*f34702b7SJohn Baldwin 			    OBJT_DEVICE, file_priv->minor->device, 0);
1644592ffb21SWarner Losh 		}
1645592ffb21SWarner Losh 		if (retcode) {
1646592ffb21SWarner Losh 			/* Real error */
1647592ffb21SWarner Losh 			retcode = -retcode;
1648592ffb21SWarner Losh 			goto done;
1649592ffb21SWarner Losh 		}
1650592ffb21SWarner Losh 		request->virtual = (void __user *)virtual;
1651592ffb21SWarner Losh 
1652592ffb21SWarner Losh 		for (i = 0; i < dma->buf_count; i++) {
1653592ffb21SWarner Losh 			if (copy_to_user(&request->list[i].idx,
1654592ffb21SWarner Losh 					 &dma->buflist[i]->idx,
1655592ffb21SWarner Losh 					 sizeof(request->list[0].idx))) {
1656592ffb21SWarner Losh 				retcode = -EFAULT;
1657592ffb21SWarner Losh 				goto done;
1658592ffb21SWarner Losh 			}
1659592ffb21SWarner Losh 			if (copy_to_user(&request->list[i].total,
1660592ffb21SWarner Losh 					 &dma->buflist[i]->total,
1661592ffb21SWarner Losh 					 sizeof(request->list[0].total))) {
1662592ffb21SWarner Losh 				retcode = -EFAULT;
1663592ffb21SWarner Losh 				goto done;
1664592ffb21SWarner Losh 			}
1665592ffb21SWarner Losh 			if (copy_to_user(&request->list[i].used,
1666592ffb21SWarner Losh 					 &zero, sizeof(zero))) {
1667592ffb21SWarner Losh 				retcode = -EFAULT;
1668592ffb21SWarner Losh 				goto done;
1669592ffb21SWarner Losh 			}
1670592ffb21SWarner Losh 			address = virtual + dma->buflist[i]->offset;	/* *** */
1671592ffb21SWarner Losh 			if (copy_to_user(&request->list[i].address,
1672592ffb21SWarner Losh 					 &address, sizeof(address))) {
1673592ffb21SWarner Losh 				retcode = -EFAULT;
1674592ffb21SWarner Losh 				goto done;
1675592ffb21SWarner Losh 			}
1676592ffb21SWarner Losh 		}
1677592ffb21SWarner Losh 	}
1678592ffb21SWarner Losh       done:
1679592ffb21SWarner Losh 	request->count = dma->buf_count;
1680592ffb21SWarner Losh 	DRM_DEBUG("%d buffers, retcode = %d\n", request->count, retcode);
1681592ffb21SWarner Losh 
1682592ffb21SWarner Losh 	return retcode;
1683592ffb21SWarner Losh }
1684592ffb21SWarner Losh 
1685592ffb21SWarner Losh /**
1686592ffb21SWarner Losh  * Compute size order.  Returns the exponent of the smaller power of two which
1687592ffb21SWarner Losh  * is greater or equal to given number.
1688592ffb21SWarner Losh  *
1689592ffb21SWarner Losh  * \param size size.
1690592ffb21SWarner Losh  * \return order.
1691592ffb21SWarner Losh  *
1692592ffb21SWarner Losh  * \todo Can be made faster.
1693592ffb21SWarner Losh  */
1694592ffb21SWarner Losh int drm_order(unsigned long size)
1695592ffb21SWarner Losh {
1696592ffb21SWarner Losh 	int order;
1697592ffb21SWarner Losh 	unsigned long tmp;
1698592ffb21SWarner Losh 
1699592ffb21SWarner Losh 	for (order = 0, tmp = size >> 1; tmp; tmp >>= 1, order++) ;
1700592ffb21SWarner Losh 
1701592ffb21SWarner Losh 	if (size & (size - 1))
1702592ffb21SWarner Losh 		++order;
1703592ffb21SWarner Losh 
1704592ffb21SWarner Losh 	return order;
1705592ffb21SWarner Losh }
1706592ffb21SWarner Losh EXPORT_SYMBOL(drm_order);
1707