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 <sys/cdefs.h> 30__FBSDID("$FreeBSD$"); 31 32#include "dev/drm/drmP.h" 33#include "dev/drm/drm.h" 34#include "dev/drm/i915_drm.h" 35#include "dev/drm/i915_drv.h" 36 37/* This memory manager is integrated into the global/local lru 38 * mechanisms used by the clients. Specifically, it operates by 39 * setting the 'in_use' fields of the global LRU to indicate whether 40 * this region is privately allocated to a client. 41 * 42 * This does require the client to actually respect that field. 43 * 44 * Currently no effort is made to allocate 'private' memory in any 45 * clever way - the LRU information isn't used to determine which 46 * block to allocate, and the ring is drained prior to allocations -- 47 * in other words allocation is expensive. 48 */ 49static void mark_block(struct drm_device * dev, struct mem_block *p, int in_use) 50{ 51 drm_i915_private_t *dev_priv = dev->dev_private; 52 drm_i915_sarea_t *sarea_priv = dev_priv->sarea_priv; 53 struct drm_tex_region *list; 54 unsigned shift, nr; 55 unsigned start; 56 unsigned end; 57 unsigned i; 58 int age; 59 60 shift = dev_priv->tex_lru_log_granularity; 61 nr = I915_NR_TEX_REGIONS; 62 63 start = p->start >> shift; 64 end = (p->start + p->size - 1) >> shift; 65 66 age = ++sarea_priv->texAge; 67 list = sarea_priv->texList; 68 69 /* Mark the regions with the new flag and update their age. Move 70 * them to head of list to preserve LRU semantics. 71 */ 72 for (i = start; i <= end; i++) { 73 list[i].in_use = in_use; 74 list[i].age = age; 75 76 /* remove_from_list(i) 77 */ 78 list[(unsigned)list[i].next].prev = list[i].prev; 79 list[(unsigned)list[i].prev].next = list[i].next; 80 81 /* insert_at_head(list, i) 82 */ 83 list[i].prev = nr; 84 list[i].next = list[nr].next; 85 list[(unsigned)list[nr].next].prev = i; 86 list[nr].next = i; 87 } 88} 89 90/* Very simple allocator for agp memory, working on a static range 91 * already mapped into each client's address space. 92 */ 93 94static struct mem_block *split_block(struct mem_block *p, int start, int size, 95 struct drm_file *file_priv) 96{ 97 /* Maybe cut off the start of an existing block */ 98 if (start > p->start) { 99 struct mem_block *newblock = 100 drm_alloc(sizeof(*newblock), DRM_MEM_BUFLISTS); 101 if (!newblock) 102 goto out; 103 newblock->start = start; 104 newblock->size = p->size - (start - p->start); 105 newblock->file_priv = NULL; 106 newblock->next = p->next; 107 newblock->prev = p; 108 p->next->prev = newblock; 109 p->next = newblock; 110 p->size -= newblock->size; 111 p = newblock; 112 } 113 114 /* Maybe cut off the end of an existing block */ 115 if (size < p->size) { 116 struct mem_block *newblock = 117 drm_alloc(sizeof(*newblock), DRM_MEM_BUFLISTS); 118 if (!newblock) 119 goto out; 120 newblock->start = start + size; 121 newblock->size = p->size - size; 122 newblock->file_priv = NULL; 123 newblock->next = p->next; 124 newblock->prev = p; 125 p->next->prev = newblock; 126 p->next = newblock; 127 p->size = size; 128 } 129 130 out: 131 /* Our block is in the middle */ 132 p->file_priv = file_priv; 133 return p; 134} 135 136static struct mem_block *alloc_block(struct mem_block *heap, int size, 137 int align2, struct drm_file *file_priv) 138{ 139 struct mem_block *p; 140 int mask = (1 << align2) - 1; 141 142 for (p = heap->next; p != heap; p = p->next) { 143 int start = (p->start + mask) & ~mask; 144 if (p->file_priv == NULL && start + size <= p->start + p->size) 145 return split_block(p, start, size, file_priv); 146 } 147 148 return NULL; 149} 150 151static struct mem_block *find_block(struct mem_block *heap, int start) 152{ 153 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