radeon_mem.c revision 152909
1/* radeon_mem.c -- Simple GART/fb memory manager for radeon -*- linux-c -*- */ 2/*- 3 * Copyright (C) The Weather Channel, Inc. 2002. All Rights Reserved. 4 * 5 * The Weather Channel (TM) funded Tungsten Graphics to develop the 6 * initial release of the Radeon 8500 driver under the XFree86 license. 7 * This notice must be preserved. 8 * 9 * Permission is hereby granted, free of charge, to any person obtaining a 10 * copy of this software and associated documentation files (the "Software"), 11 * to deal in the Software without restriction, including without limitation 12 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 * and/or sell copies of the Software, and to permit persons to whom the 14 * Software is furnished to do so, subject to the following conditions: 15 * 16 * The above copyright notice and this permission notice (including the next 17 * paragraph) shall be included in all copies or substantial portions of the 18 * Software. 19 * 20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 23 * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 24 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 25 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 * DEALINGS IN THE SOFTWARE. 27 * 28 * Authors: 29 * Keith Whitwell <keith@tungstengraphics.com> 30 */ 31 32#include <sys/cdefs.h> 33__FBSDID("$FreeBSD: head/sys/dev/drm/radeon_mem.c 152909 2005-11-28 23:13:57Z anholt $"); 34 35#include "dev/drm/drmP.h" 36#include "dev/drm/drm.h" 37#include "dev/drm/radeon_drm.h" 38#include "dev/drm/radeon_drv.h" 39 40/* Very simple allocator for GART memory, working on a static range 41 * already mapped into each client's address space. 42 */ 43 44static struct mem_block *split_block(struct mem_block *p, int start, int size, 45 DRMFILE filp) 46{ 47 /* Maybe cut off the start of an existing block */ 48 if (start > p->start) { 49 struct mem_block *newblock = 50 drm_alloc(sizeof(*newblock), DRM_MEM_BUFS); 51 if (!newblock) 52 goto out; 53 newblock->start = start; 54 newblock->size = p->size - (start - p->start); 55 newblock->filp = NULL; 56 newblock->next = p->next; 57 newblock->prev = p; 58 p->next->prev = newblock; 59 p->next = newblock; 60 p->size -= newblock->size; 61 p = newblock; 62 } 63 64 /* Maybe cut off the end of an existing block */ 65 if (size < p->size) { 66 struct mem_block *newblock = 67 drm_alloc(sizeof(*newblock), DRM_MEM_BUFS); 68 if (!newblock) 69 goto out; 70 newblock->start = start + size; 71 newblock->size = p->size - size; 72 newblock->filp = NULL; 73 newblock->next = p->next; 74 newblock->prev = p; 75 p->next->prev = newblock; 76 p->next = newblock; 77 p->size = size; 78 } 79 80 out: 81 /* Our block is in the middle */ 82 p->filp = filp; 83 return p; 84} 85 86static struct mem_block *alloc_block(struct mem_block *heap, int size, 87 int align2, DRMFILE filp) 88{ 89 struct mem_block *p; 90 int mask = (1 << align2) - 1; 91 92 list_for_each(p, heap) { 93 int start = (p->start + mask) & ~mask; 94 if (p->filp == 0 && start + size <= p->start + p->size) 95 return split_block(p, start, size, filp); 96 } 97 98 return NULL; 99} 100 101static struct mem_block *find_block(struct mem_block *heap, int start) 102{ 103 struct mem_block *p; 104 105 list_for_each(p, heap) 106 if (p->start == start) 107 return p; 108 109 return NULL; 110} 111 112static void free_block(struct mem_block *p) 113{ 114 p->filp = NULL; 115 116 /* Assumes a single contiguous range. Needs a special filp in 117 * 'heap' to stop it being subsumed. 118 */ 119 if (p->next->filp == 0) { 120 struct mem_block *q = p->next; 121 p->size += q->size; 122 p->next = q->next; 123 p->next->prev = p; 124 drm_free(q, sizeof(*q), DRM_MEM_BUFS); 125 } 126 127 if (p->prev->filp == 0) { 128 struct mem_block *q = p->prev; 129 q->size += p->size; 130 q->next = p->next; 131 q->next->prev = q; 132 drm_free(p, sizeof(*q), DRM_MEM_BUFS); 133 } 134} 135 136/* Initialize. How to check for an uninitialized heap? 137 */ 138static int init_heap(struct mem_block **heap, int start, int size) 139{ 140 struct mem_block *blocks = drm_alloc(sizeof(*blocks), DRM_MEM_BUFS); 141 142 if (!blocks) 143 return DRM_ERR(ENOMEM); 144 145 *heap = drm_alloc(sizeof(**heap), DRM_MEM_BUFS); 146 if (!*heap) { 147 drm_free(blocks, sizeof(*blocks), DRM_MEM_BUFS); 148 return DRM_ERR(ENOMEM); 149 } 150 151 blocks->start = start; 152 blocks->size = size; 153 blocks->filp = NULL; 154 blocks->next = blocks->prev = *heap; 155 156 memset(*heap, 0, sizeof(**heap)); 157 (*heap)->filp = (DRMFILE) - 1; 158 (*heap)->next = (*heap)->prev = blocks; 159 return 0; 160} 161 162/* Free all blocks associated with the releasing file. 163 */ 164void radeon_mem_release(DRMFILE filp, struct mem_block *heap) 165{ 166 struct mem_block *p; 167 168 if (!heap || !heap->next) 169 return; 170 171 list_for_each(p, heap) { 172 if (p->filp == filp) 173 p->filp = NULL; 174 } 175 176 /* Assumes a single contiguous range. Needs a special filp in 177 * 'heap' to stop it being subsumed. 178 */ 179 list_for_each(p, heap) { 180 while (p->filp == 0 && p->next->filp == 0) { 181 struct mem_block *q = p->next; 182 p->size += q->size; 183 p->next = q->next; 184 p->next->prev = p; 185 drm_free(q, sizeof(*q), DRM_MEM_DRIVER); 186 } 187 } 188} 189 190/* Shutdown. 191 */ 192void radeon_mem_takedown(struct mem_block **heap) 193{ 194 struct mem_block *p; 195 196 if (!*heap) 197 return; 198 199 for (p = (*heap)->next; p != *heap;) { 200 struct mem_block *q = p; 201 p = p->next; 202 drm_free(q, sizeof(*q), DRM_MEM_DRIVER); 203 } 204 205 drm_free(*heap, sizeof(**heap), DRM_MEM_DRIVER); 206 *heap = NULL; 207} 208 209/* IOCTL HANDLERS */ 210 211static struct mem_block **get_heap(drm_radeon_private_t * dev_priv, int region) 212{ 213 switch (region) { 214 case RADEON_MEM_REGION_GART: 215 return &dev_priv->gart_heap; 216 case RADEON_MEM_REGION_FB: 217 return &dev_priv->fb_heap; 218 default: 219 return NULL; 220 } 221} 222 223int radeon_mem_alloc(DRM_IOCTL_ARGS) 224{ 225 DRM_DEVICE; 226 drm_radeon_private_t *dev_priv = dev->dev_private; 227 drm_radeon_mem_alloc_t alloc; 228 struct mem_block *block, **heap; 229 230 if (!dev_priv) { 231 DRM_ERROR("%s called with no initialization\n", __FUNCTION__); 232 return DRM_ERR(EINVAL); 233 } 234 235 DRM_COPY_FROM_USER_IOCTL(alloc, (drm_radeon_mem_alloc_t __user *) data, 236 sizeof(alloc)); 237 238 heap = get_heap(dev_priv, alloc.region); 239 if (!heap || !*heap) 240 return DRM_ERR(EFAULT); 241 242 /* Make things easier on ourselves: all allocations at least 243 * 4k aligned. 244 */ 245 if (alloc.alignment < 12) 246 alloc.alignment = 12; 247 248 block = alloc_block(*heap, alloc.size, alloc.alignment, filp); 249 250 if (!block) 251 return DRM_ERR(ENOMEM); 252 253 if (DRM_COPY_TO_USER(alloc.region_offset, &block->start, sizeof(int))) { 254 DRM_ERROR("copy_to_user\n"); 255 return DRM_ERR(EFAULT); 256 } 257 258 return 0; 259} 260 261int radeon_mem_free(DRM_IOCTL_ARGS) 262{ 263 DRM_DEVICE; 264 drm_radeon_private_t *dev_priv = dev->dev_private; 265 drm_radeon_mem_free_t memfree; 266 struct mem_block *block, **heap; 267 268 if (!dev_priv) { 269 DRM_ERROR("%s called with no initialization\n", __FUNCTION__); 270 return DRM_ERR(EINVAL); 271 } 272 273 DRM_COPY_FROM_USER_IOCTL(memfree, (drm_radeon_mem_free_t __user *) data, 274 sizeof(memfree)); 275 276 heap = get_heap(dev_priv, memfree.region); 277 if (!heap || !*heap) 278 return DRM_ERR(EFAULT); 279 280 block = find_block(*heap, memfree.region_offset); 281 if (!block) 282 return DRM_ERR(EFAULT); 283 284 if (block->filp != filp) 285 return DRM_ERR(EPERM); 286 287 free_block(block); 288 return 0; 289} 290 291int radeon_mem_init_heap(DRM_IOCTL_ARGS) 292{ 293 DRM_DEVICE; 294 drm_radeon_private_t *dev_priv = dev->dev_private; 295 drm_radeon_mem_init_heap_t initheap; 296 struct mem_block **heap; 297 298 if (!dev_priv) { 299 DRM_ERROR("%s called with no initialization\n", __FUNCTION__); 300 return DRM_ERR(EINVAL); 301 } 302 303 DRM_COPY_FROM_USER_IOCTL(initheap, 304 (drm_radeon_mem_init_heap_t __user *) data, 305 sizeof(initheap)); 306 307 heap = get_heap(dev_priv, initheap.region); 308 if (!heap) 309 return DRM_ERR(EFAULT); 310 311 if (*heap) { 312 DRM_ERROR("heap already initialized?"); 313 return DRM_ERR(EFAULT); 314 } 315 316 return init_heap(heap, initheap.start, initheap.size); 317} 318