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