1234353Sdim/* i915_mem.c -- Simple agp/fb memory manager for i915 -*- linux-c -*-
2218885Sdim */
3218885Sdim/*-
4218885Sdim * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas.
5218885Sdim * All Rights Reserved.
6218885Sdim *
7218885Sdim * Permission is hereby granted, free of charge, to any person obtaining a
8218885Sdim * copy of this software and associated documentation files (the
9218885Sdim * "Software"), to deal in the Software without restriction, including
10218885Sdim * without limitation the rights to use, copy, modify, merge, publish,
11218885Sdim * distribute, sub license, and/or sell copies of the Software, and to
12218885Sdim * permit persons to whom the Software is furnished to do so, subject to
13218885Sdim * the following conditions:
14218885Sdim *
15218885Sdim * The above copyright notice and this permission notice (including the
16249423Sdim * next paragraph) shall be included in all copies or substantial portions
17218885Sdim * of the Software.
18218885Sdim *
19218885Sdim * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20218885Sdim * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21218885Sdim * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
22218885Sdim * IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS BE LIABLE FOR
23218885Sdim * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24249423Sdim * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25249423Sdim * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26218885Sdim *
27218885Sdim */
28218885Sdim
29218885Sdim#include <sys/cdefs.h>
30218885Sdim__FBSDID("$FreeBSD: releng/10.3/sys/dev/drm/i915_mem.c 182080 2008-08-23 20:59:12Z rnoland $");
31218885Sdim
32218885Sdim#include "dev/drm/drmP.h"
33218885Sdim#include "dev/drm/drm.h"
34218885Sdim#include "dev/drm/i915_drm.h"
35218885Sdim#include "dev/drm/i915_drv.h"
36218885Sdim
37218885Sdim/* This memory manager is integrated into the global/local lru
38218885Sdim * mechanisms used by the clients.  Specifically, it operates by
39218885Sdim * setting the 'in_use' fields of the global LRU to indicate whether
40218885Sdim * this region is privately allocated to a client.
41218885Sdim *
42234353Sdim * This does require the client to actually respect that field.
43218885Sdim *
44218885Sdim * Currently no effort is made to allocate 'private' memory in any
45218885Sdim * clever way - the LRU information isn't used to determine which
46218885Sdim * block to allocate, and the ring is drained prior to allocations --
47218885Sdim * in other words allocation is expensive.
48218885Sdim */
49218885Sdimstatic void mark_block(struct drm_device * dev, struct mem_block *p, int in_use)
50218885Sdim{
51218885Sdim	drm_i915_private_t *dev_priv = dev->dev_private;
52218885Sdim	drm_i915_sarea_t *sarea_priv = dev_priv->sarea_priv;
53218885Sdim	struct drm_tex_region *list;
54218885Sdim	unsigned shift, nr;
55218885Sdim	unsigned start;
56218885Sdim	unsigned end;
57218885Sdim	unsigned i;
58218885Sdim	int age;
59218885Sdim
60218885Sdim	shift = dev_priv->tex_lru_log_granularity;
61218885Sdim	nr = I915_NR_TEX_REGIONS;
62218885Sdim
63218885Sdim	start = p->start >> shift;
64218885Sdim	end = (p->start + p->size - 1) >> shift;
65218885Sdim
66218885Sdim	age = ++sarea_priv->texAge;
67218885Sdim	list = sarea_priv->texList;
68218885Sdim
69218885Sdim	/* Mark the regions with the new flag and update their age.  Move
70218885Sdim	 * them to head of list to preserve LRU semantics.
71218885Sdim	 */
72218885Sdim	for (i = start; i <= end; i++) {
73218885Sdim		list[i].in_use = in_use;
74218885Sdim		list[i].age = age;
75218885Sdim
76218885Sdim		/* remove_from_list(i)
77218885Sdim		 */
78218885Sdim		list[(unsigned)list[i].next].prev = list[i].prev;
79218885Sdim		list[(unsigned)list[i].prev].next = list[i].next;
80218885Sdim
81218885Sdim		/* insert_at_head(list, i)
82218885Sdim		 */
83218885Sdim		list[i].prev = nr;
84234353Sdim		list[i].next = list[nr].next;
85218885Sdim		list[(unsigned)list[nr].next].prev = i;
86218885Sdim		list[nr].next = i;
87218885Sdim	}
88218885Sdim}
89218885Sdim
90218885Sdim/* Very simple allocator for agp memory, working on a static range
91218885Sdim * already mapped into each client's address space.
92218885Sdim */
93218885Sdim
94218885Sdimstatic struct mem_block *split_block(struct mem_block *p, int start, int size,
95218885Sdim				     struct drm_file *file_priv)
96218885Sdim{
97218885Sdim	/* Maybe cut off the start of an existing block */
98218885Sdim	if (start > p->start) {
99218885Sdim		struct mem_block *newblock =
100218885Sdim		    drm_alloc(sizeof(*newblock), DRM_MEM_BUFLISTS);
101218885Sdim		if (!newblock)
102218885Sdim			goto out;
103218885Sdim		newblock->start = start;
104218885Sdim		newblock->size = p->size - (start - p->start);
105218885Sdim		newblock->file_priv = NULL;
106218885Sdim		newblock->next = p->next;
107218885Sdim		newblock->prev = p;
108218885Sdim		p->next->prev = newblock;
109218885Sdim		p->next = newblock;
110218885Sdim		p->size -= newblock->size;
111218885Sdim		p = newblock;
112218885Sdim	}
113218885Sdim
114218885Sdim	/* Maybe cut off the end of an existing block */
115218885Sdim	if (size < p->size) {
116218885Sdim		struct mem_block *newblock =
117218885Sdim		    drm_alloc(sizeof(*newblock), DRM_MEM_BUFLISTS);
118218885Sdim		if (!newblock)
119234353Sdim			goto out;
120218885Sdim		newblock->start = start + size;
121218885Sdim		newblock->size = p->size - size;
122218885Sdim		newblock->file_priv = NULL;
123218885Sdim		newblock->next = p->next;
124218885Sdim		newblock->prev = p;
125218885Sdim		p->next->prev = newblock;
126218885Sdim		p->next = newblock;
127218885Sdim		p->size = size;
128218885Sdim	}
129218885Sdim
130218885Sdim      out:
131218885Sdim	/* Our block is in the middle */
132218885Sdim	p->file_priv = file_priv;
133218885Sdim	return p;
134218885Sdim}
135218885Sdim
136218885Sdimstatic struct mem_block *alloc_block(struct mem_block *heap, int size,
137218885Sdim				     int align2, struct drm_file *file_priv)
138218885Sdim{
139218885Sdim	struct mem_block *p;
140218885Sdim	int mask = (1 << align2) - 1;
141218885Sdim
142218885Sdim	for (p = heap->next; p != heap; p = p->next) {
143218885Sdim		int start = (p->start + mask) & ~mask;
144218885Sdim		if (p->file_priv == NULL && start + size <= p->start + p->size)
145218885Sdim			return split_block(p, start, size, file_priv);
146234353Sdim	}
147234353Sdim
148234353Sdim	return NULL;
149218885Sdim}
150218885Sdim
151218885Sdimstatic struct mem_block *find_block(struct mem_block *heap, int start)
152218885Sdim{
153218885Sdim	struct mem_block *p;
154
155	for (p = heap->next; p != heap; p = p->next)
156		if (p->start == start)
157			return p;
158
159	return NULL;
160}
161
162static void free_block(struct mem_block *p)
163{
164	p->file_priv = NULL;
165
166	/* Assumes a single contiguous range.  Needs a special file_priv in
167	 * 'heap' to stop it being subsumed.
168	 */
169	if (p->next->file_priv == NULL) {
170		struct mem_block *q = p->next;
171		p->size += q->size;
172		p->next = q->next;
173		p->next->prev = p;
174		drm_free(q, sizeof(*q), DRM_MEM_BUFLISTS);
175	}
176
177	if (p->prev->file_priv == NULL) {
178		struct mem_block *q = p->prev;
179		q->size += p->size;
180		q->next = p->next;
181		q->next->prev = q;
182		drm_free(p, sizeof(*q), DRM_MEM_BUFLISTS);
183	}
184}
185
186/* Initialize.  How to check for an uninitialized heap?
187 */
188static int init_heap(struct mem_block **heap, int start, int size)
189{
190	struct mem_block *blocks = drm_alloc(sizeof(*blocks), DRM_MEM_BUFLISTS);
191
192	if (!blocks)
193		return -ENOMEM;
194
195	*heap = drm_alloc(sizeof(**heap), DRM_MEM_BUFLISTS);
196	if (!*heap) {
197		drm_free(blocks, sizeof(*blocks), DRM_MEM_BUFLISTS);
198		return -ENOMEM;
199	}
200
201	blocks->start = start;
202	blocks->size = size;
203	blocks->file_priv = NULL;
204	blocks->next = blocks->prev = *heap;
205
206	memset(*heap, 0, sizeof(**heap));
207	(*heap)->file_priv = (struct drm_file *) - 1;
208	(*heap)->next = (*heap)->prev = blocks;
209	return 0;
210}
211
212/* Free all blocks associated with the releasing file.
213 */
214void i915_mem_release(struct drm_device * dev, struct drm_file *file_priv,
215		      struct mem_block *heap)
216{
217	struct mem_block *p;
218
219	if (!heap || !heap->next)
220		return;
221
222	for (p = heap->next; p != heap; p = p->next) {
223		if (p->file_priv == file_priv) {
224			p->file_priv = NULL;
225			mark_block(dev, p, 0);
226		}
227	}
228
229	/* Assumes a single contiguous range.  Needs a special file_priv in
230	 * 'heap' to stop it being subsumed.
231	 */
232	for (p = heap->next; p != heap; p = p->next) {
233		while (p->file_priv == NULL && p->next->file_priv == NULL) {
234			struct mem_block *q = p->next;
235			p->size += q->size;
236			p->next = q->next;
237			p->next->prev = p;
238			drm_free(q, sizeof(*q), DRM_MEM_BUFLISTS);
239		}
240	}
241}
242
243/* Shutdown.
244 */
245void i915_mem_takedown(struct mem_block **heap)
246{
247	struct mem_block *p;
248
249	if (!*heap)
250		return;
251
252	for (p = (*heap)->next; p != *heap;) {
253		struct mem_block *q = p;
254		p = p->next;
255		drm_free(q, sizeof(*q), DRM_MEM_BUFLISTS);
256	}
257
258	drm_free(*heap, sizeof(**heap), DRM_MEM_BUFLISTS);
259	*heap = NULL;
260}
261
262static struct mem_block **get_heap(drm_i915_private_t * dev_priv, int region)
263{
264	switch (region) {
265	case I915_MEM_REGION_AGP:
266		return &dev_priv->agp_heap;
267	default:
268		return NULL;
269	}
270}
271
272/* IOCTL HANDLERS */
273
274int i915_mem_alloc(struct drm_device *dev, void *data,
275		   struct drm_file *file_priv)
276{
277	drm_i915_private_t *dev_priv = dev->dev_private;
278	drm_i915_mem_alloc_t *alloc = data;
279	struct mem_block *block, **heap;
280
281	if (!dev_priv) {
282		DRM_ERROR("called with no initialization\n");
283		return -EINVAL;
284	}
285
286	heap = get_heap(dev_priv, alloc->region);
287	if (!heap || !*heap)
288		return -EFAULT;
289
290	/* Make things easier on ourselves: all allocations at least
291	 * 4k aligned.
292	 */
293	if (alloc->alignment < 12)
294		alloc->alignment = 12;
295
296	block = alloc_block(*heap, alloc->size, alloc->alignment, file_priv);
297
298	if (!block)
299		return -ENOMEM;
300
301	mark_block(dev, block, 1);
302
303	if (DRM_COPY_TO_USER(alloc->region_offset, &block->start,
304			     sizeof(int))) {
305		DRM_ERROR("copy_to_user\n");
306		return -EFAULT;
307	}
308
309	return 0;
310}
311
312int i915_mem_free(struct drm_device *dev, void *data,
313		  struct drm_file *file_priv)
314{
315	drm_i915_private_t *dev_priv = dev->dev_private;
316	drm_i915_mem_free_t *memfree = data;
317	struct mem_block *block, **heap;
318
319	if (!dev_priv) {
320		DRM_ERROR("called with no initialization\n");
321		return -EINVAL;
322	}
323
324	heap = get_heap(dev_priv, memfree->region);
325	if (!heap || !*heap)
326		return -EFAULT;
327
328	block = find_block(*heap, memfree->region_offset);
329	if (!block)
330		return -EFAULT;
331
332	if (block->file_priv != file_priv)
333		return -EPERM;
334
335	mark_block(dev, block, 0);
336	free_block(block);
337	return 0;
338}
339
340int i915_mem_init_heap(struct drm_device *dev, void *data,
341		       struct drm_file *file_priv)
342{
343	drm_i915_private_t *dev_priv = dev->dev_private;
344	drm_i915_mem_init_heap_t *initheap = data;
345	struct mem_block **heap;
346
347	if (!dev_priv) {
348		DRM_ERROR("called with no initialization\n");
349		return -EINVAL;
350	}
351
352	heap = get_heap(dev_priv, initheap->region);
353	if (!heap)
354		return -EFAULT;
355
356	if (*heap) {
357		DRM_ERROR("heap already initialized?");
358		return -EFAULT;
359	}
360
361	return init_heap(heap, initheap->start, initheap->size);
362}
363
364int i915_mem_destroy_heap( struct drm_device *dev, void *data,
365			   struct drm_file *file_priv )
366{
367	drm_i915_private_t *dev_priv = dev->dev_private;
368	drm_i915_mem_destroy_heap_t *destroyheap = data;
369	struct mem_block **heap;
370
371	if ( !dev_priv ) {
372		DRM_ERROR( "called with no initialization\n" );
373		return -EINVAL;
374	}
375
376	heap = get_heap( dev_priv, destroyheap->region );
377	if (!heap) {
378		DRM_ERROR("get_heap failed");
379		return -EFAULT;
380	}
381
382	if (!*heap) {
383		DRM_ERROR("heap not initialized?");
384		return -EFAULT;
385	}
386
387	i915_mem_takedown( heap );
388	return 0;
389}
390