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