Home | History | Annotate | Line # | Download | only in shared-core
      1 /* i915_mem.c -- Simple agp/fb memory manager for i915 -*- linux-c -*-
      2  */
      3 /*
      4  * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas.
      5  * All Rights Reserved.
      6  *
      7  * Permission is hereby granted, free of charge, to any person obtaining a
      8  * copy of this software and associated documentation files (the
      9  * "Software"), to deal in the Software without restriction, including
     10  * without limitation the rights to use, copy, modify, merge, publish,
     11  * distribute, sub license, and/or sell copies of the Software, and to
     12  * permit persons to whom the Software is furnished to do so, subject to
     13  * the following conditions:
     14  *
     15  * The above copyright notice and this permission notice (including the
     16  * next paragraph) shall be included in all copies or substantial portions
     17  * of the Software.
     18  *
     19  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
     20  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     21  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
     22  * IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS BE LIABLE FOR
     23  * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
     24  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
     25  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     26  *
     27  */
     28 
     29 #include "drmP.h"
     30 #include "drm.h"
     31 #include "i915_drm.h"
     32 #include "i915_drv.h"
     33 
     34 /* This memory manager is integrated into the global/local lru
     35  * mechanisms used by the clients.  Specifically, it operates by
     36  * setting the 'in_use' fields of the global LRU to indicate whether
     37  * this region is privately allocated to a client.
     38  *
     39  * This does require the client to actually respect that field.
     40  *
     41  * Currently no effort is made to allocate 'private' memory in any
     42  * clever way - the LRU information isn't used to determine which
     43  * block to allocate, and the ring is drained prior to allocations --
     44  * in other words allocation is expensive.
     45  */
     46 static void mark_block(struct drm_device * dev, struct mem_block *p, int in_use)
     47 {
     48 	drm_i915_private_t *dev_priv = dev->dev_private;
     49 	drm_i915_sarea_t *sarea_priv = dev_priv->sarea_priv;
     50 	struct drm_tex_region *list;
     51 	unsigned shift, nr;
     52 	unsigned start;
     53 	unsigned end;
     54 	unsigned i;
     55 	int age;
     56 
     57 	shift = dev_priv->tex_lru_log_granularity;
     58 	nr = I915_NR_TEX_REGIONS;
     59 
     60 	start = p->start >> shift;
     61 	end = (p->start + p->size - 1) >> shift;
     62 
     63 	age = ++sarea_priv->texAge;
     64 	list = sarea_priv->texList;
     65 
     66 	/* Mark the regions with the new flag and update their age.  Move
     67 	 * them to head of list to preserve LRU semantics.
     68 	 */
     69 	for (i = start; i <= end; i++) {
     70 		list[i].in_use = in_use;
     71 		list[i].age = age;
     72 
     73 		/* remove_from_list(i)
     74 		 */
     75 		list[(unsigned)list[i].next].prev = list[i].prev;
     76 		list[(unsigned)list[i].prev].next = list[i].next;
     77 
     78 		/* insert_at_head(list, i)
     79 		 */
     80 		list[i].prev = nr;
     81 		list[i].next = list[nr].next;
     82 		list[(unsigned)list[nr].next].prev = i;
     83 		list[nr].next = i;
     84 	}
     85 }
     86 
     87 /* Very simple allocator for agp memory, working on a static range
     88  * already mapped into each client's address space.
     89  */
     90 
     91 static struct mem_block *split_block(struct mem_block *p, int start, int size,
     92 				     struct drm_file *file_priv)
     93 {
     94 	/* Maybe cut off the start of an existing block */
     95 	if (start > p->start) {
     96 		struct mem_block *newblock =
     97 		    drm_alloc(sizeof(*newblock), DRM_MEM_BUFLISTS);
     98 		if (!newblock)
     99 			goto out;
    100 		newblock->start = start;
    101 		newblock->size = p->size - (start - p->start);
    102 		newblock->file_priv = NULL;
    103 		newblock->next = p->next;
    104 		newblock->prev = p;
    105 		p->next->prev = newblock;
    106 		p->next = newblock;
    107 		p->size -= newblock->size;
    108 		p = newblock;
    109 	}
    110 
    111 	/* Maybe cut off the end of an existing block */
    112 	if (size < p->size) {
    113 		struct mem_block *newblock =
    114 		    drm_alloc(sizeof(*newblock), DRM_MEM_BUFLISTS);
    115 		if (!newblock)
    116 			goto out;
    117 		newblock->start = start + size;
    118 		newblock->size = p->size - size;
    119 		newblock->file_priv = NULL;
    120 		newblock->next = p->next;
    121 		newblock->prev = p;
    122 		p->next->prev = newblock;
    123 		p->next = newblock;
    124 		p->size = size;
    125 	}
    126 
    127       out:
    128 	/* Our block is in the middle */
    129 	p->file_priv = file_priv;
    130 	return p;
    131 }
    132 
    133 static struct mem_block *alloc_block(struct mem_block *heap, int size,
    134 				     int align2, struct drm_file *file_priv)
    135 {
    136 	struct mem_block *p;
    137 	int mask = (1 << align2) - 1;
    138 
    139 	for (p = heap->next; p != heap; p = p->next) {
    140 		int start = (p->start + mask) & ~mask;
    141 		if (p->file_priv == NULL && start + size <= p->start + p->size)
    142 			return split_block(p, start, size, file_priv);
    143 	}
    144 
    145 	return NULL;
    146 }
    147 
    148 static struct mem_block *find_block(struct mem_block *heap, int start)
    149 {
    150 	struct mem_block *p;
    151 
    152 	for (p = heap->next; p != heap; p = p->next)
    153 		if (p->start == start)
    154 			return p;
    155 
    156 	return NULL;
    157 }
    158 
    159 static void free_block(struct mem_block *p)
    160 {
    161 	p->file_priv = NULL;
    162 
    163 	/* Assumes a single contiguous range.  Needs a special file_priv in
    164 	 * 'heap' to stop it being subsumed.
    165 	 */
    166 	if (p->next->file_priv == NULL) {
    167 		struct mem_block *q = p->next;
    168 		p->size += q->size;
    169 		p->next = q->next;
    170 		p->next->prev = p;
    171 		drm_free(q, sizeof(*q), DRM_MEM_BUFLISTS);
    172 	}
    173 
    174 	if (p->prev->file_priv == NULL) {
    175 		struct mem_block *q = p->prev;
    176 		q->size += p->size;
    177 		q->next = p->next;
    178 		q->next->prev = q;
    179 		drm_free(p, sizeof(*q), DRM_MEM_BUFLISTS);
    180 	}
    181 }
    182 
    183 /* Initialize.  How to check for an uninitialized heap?
    184  */
    185 static int init_heap(struct mem_block **heap, int start, int size)
    186 {
    187 	struct mem_block *blocks = drm_alloc(sizeof(*blocks), DRM_MEM_BUFLISTS);
    188 
    189 	if (!blocks)
    190 		return -ENOMEM;
    191 
    192 	*heap = drm_alloc(sizeof(**heap), DRM_MEM_BUFLISTS);
    193 	if (!*heap) {
    194 		drm_free(blocks, sizeof(*blocks), DRM_MEM_BUFLISTS);
    195 		return -ENOMEM;
    196 	}
    197 
    198 	blocks->start = start;
    199 	blocks->size = size;
    200 	blocks->file_priv = NULL;
    201 	blocks->next = blocks->prev = *heap;
    202 
    203 	memset(*heap, 0, sizeof(**heap));
    204 	(*heap)->file_priv = (struct drm_file *) - 1;
    205 	(*heap)->next = (*heap)->prev = blocks;
    206 	return 0;
    207 }
    208 
    209 /* Free all blocks associated with the releasing file.
    210  */
    211 void i915_mem_release(struct drm_device * dev, struct drm_file *file_priv,
    212 		      struct mem_block *heap)
    213 {
    214 	struct mem_block *p;
    215 
    216 	if (!heap || !heap->next)
    217 		return;
    218 
    219 	for (p = heap->next; p != heap; p = p->next) {
    220 		if (p->file_priv == file_priv) {
    221 			p->file_priv = NULL;
    222 			mark_block(dev, p, 0);
    223 		}
    224 	}
    225 
    226 	/* Assumes a single contiguous range.  Needs a special file_priv in
    227 	 * 'heap' to stop it being subsumed.
    228 	 */
    229 	for (p = heap->next; p != heap; p = p->next) {
    230 		while (p->file_priv == NULL && p->next->file_priv == NULL) {
    231 			struct mem_block *q = p->next;
    232 			p->size += q->size;
    233 			p->next = q->next;
    234 			p->next->prev = p;
    235 			drm_free(q, sizeof(*q), DRM_MEM_BUFLISTS);
    236 		}
    237 	}
    238 }
    239 
    240 /* Shutdown.
    241  */
    242 void i915_mem_takedown(struct mem_block **heap)
    243 {
    244 	struct mem_block *p;
    245 
    246 	if (!*heap)
    247 		return;
    248 
    249 	for (p = (*heap)->next; p != *heap;) {
    250 		struct mem_block *q = p;
    251 		p = p->next;
    252 		drm_free(q, sizeof(*q), DRM_MEM_BUFLISTS);
    253 	}
    254 
    255 	drm_free(*heap, sizeof(**heap), DRM_MEM_BUFLISTS);
    256 	*heap = NULL;
    257 }
    258 
    259 static struct mem_block **get_heap(drm_i915_private_t * dev_priv, int region)
    260 {
    261 	switch (region) {
    262 	case I915_MEM_REGION_AGP:
    263 		return &dev_priv->agp_heap;
    264 	default:
    265 		return NULL;
    266 	}
    267 }
    268 
    269 /* IOCTL HANDLERS */
    270 
    271 int i915_mem_alloc(struct drm_device *dev, void *data,
    272 		   struct drm_file *file_priv)
    273 {
    274 	drm_i915_private_t *dev_priv = dev->dev_private;
    275 	drm_i915_mem_alloc_t *alloc = data;
    276 	struct mem_block *block, **heap;
    277 
    278 	if (!dev_priv) {
    279 		DRM_ERROR("called with no initialization\n");
    280 		return -EINVAL;
    281 	}
    282 
    283 	heap = get_heap(dev_priv, alloc->region);
    284 	if (!heap || !*heap)
    285 		return -EFAULT;
    286 
    287 	/* Make things easier on ourselves: all allocations at least
    288 	 * 4k aligned.
    289 	 */
    290 	if (alloc->alignment < 12)
    291 		alloc->alignment = 12;
    292 
    293 	block = alloc_block(*heap, alloc->size, alloc->alignment, file_priv);
    294 
    295 	if (!block)
    296 		return -ENOMEM;
    297 
    298 	mark_block(dev, block, 1);
    299 
    300 	if (DRM_COPY_TO_USER(alloc->region_offset, &block->start,
    301 			     sizeof(int))) {
    302 		DRM_ERROR("copy_to_user\n");
    303 		return -EFAULT;
    304 	}
    305 
    306 	return 0;
    307 }
    308 
    309 int i915_mem_free(struct drm_device *dev, void *data,
    310 		  struct drm_file *file_priv)
    311 {
    312 	drm_i915_private_t *dev_priv = dev->dev_private;
    313 	drm_i915_mem_free_t *memfree = data;
    314 	struct mem_block *block, **heap;
    315 
    316 	if (!dev_priv) {
    317 		DRM_ERROR("called with no initialization\n");
    318 		return -EINVAL;
    319 	}
    320 
    321 	heap = get_heap(dev_priv, memfree->region);
    322 	if (!heap || !*heap)
    323 		return -EFAULT;
    324 
    325 	block = find_block(*heap, memfree->region_offset);
    326 	if (!block)
    327 		return -EFAULT;
    328 
    329 	if (block->file_priv != file_priv)
    330 		return -EPERM;
    331 
    332 	mark_block(dev, block, 0);
    333 	free_block(block);
    334 	return 0;
    335 }
    336 
    337 int i915_mem_init_heap(struct drm_device *dev, void *data,
    338 		       struct drm_file *file_priv)
    339 {
    340 	drm_i915_private_t *dev_priv = dev->dev_private;
    341 	drm_i915_mem_init_heap_t *initheap = data;
    342 	struct mem_block **heap;
    343 
    344 	if (!dev_priv) {
    345 		DRM_ERROR("called with no initialization\n");
    346 		return -EINVAL;
    347 	}
    348 
    349 	heap = get_heap(dev_priv, initheap->region);
    350 	if (!heap)
    351 		return -EFAULT;
    352 
    353 	if (*heap) {
    354 		DRM_ERROR("heap already initialized?");
    355 		return -EFAULT;
    356 	}
    357 
    358 	return init_heap(heap, initheap->start, initheap->size);
    359 }
    360 
    361 int i915_mem_destroy_heap( struct drm_device *dev, void *data,
    362 			   struct drm_file *file_priv )
    363 {
    364 	drm_i915_private_t *dev_priv = dev->dev_private;
    365 	drm_i915_mem_destroy_heap_t *destroyheap = data;
    366 	struct mem_block **heap;
    367 
    368 	if ( !dev_priv ) {
    369 		DRM_ERROR( "called with no initialization\n" );
    370 		return -EINVAL;
    371 	}
    372 
    373 	heap = get_heap( dev_priv, destroyheap->region );
    374 	if (!heap) {
    375 		DRM_ERROR("get_heap failed");
    376 		return -EFAULT;
    377 	}
    378 
    379 	if (!*heap) {
    380 		DRM_ERROR("heap not initialized?");
    381 		return -EFAULT;
    382 	}
    383 
    384 	i915_mem_takedown( heap );
    385 	return 0;
    386 }
    387