diff options
Diffstat (limited to 'shared-core/nouveau_mem.c')
-rw-r--r-- | shared-core/nouveau_mem.c | 506 |
1 files changed, 506 insertions, 0 deletions
diff --git a/shared-core/nouveau_mem.c b/shared-core/nouveau_mem.c new file mode 100644 index 00000000..897badd4 --- /dev/null +++ b/shared-core/nouveau_mem.c @@ -0,0 +1,506 @@ +/* + * Copyright (C) The Weather Channel, Inc. 2002. All Rights Reserved. + * Copyright 2005 Stephane Marchesin + * + * The Weather Channel (TM) funded Tungsten Graphics to develop the + * initial release of the Radeon 8500 driver under the XFree86 license. + * This notice must be preserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Keith Whitwell <keith@tungstengraphics.com> + */ + + +#include "drmP.h" +#include "drm.h" +#include "drm_sarea.h" +#include "nouveau_drv.h" + +static int meminit_ok=0; + +static struct mem_block *split_block(struct mem_block *p, uint64_t start, uint64_t size, + DRMFILE filp) +{ + /* Maybe cut off the start of an existing block */ + if (start > p->start) { + struct mem_block *newblock = + drm_alloc(sizeof(*newblock), DRM_MEM_BUFS); + if (!newblock) + goto out; + newblock->start = start; + newblock->size = p->size - (start - p->start); + newblock->filp = NULL; + newblock->next = p->next; + newblock->prev = p; + p->next->prev = newblock; + p->next = newblock; + p->size -= newblock->size; + p = newblock; + } + + /* Maybe cut off the end of an existing block */ + if (size < p->size) { + struct mem_block *newblock = + drm_alloc(sizeof(*newblock), DRM_MEM_BUFS); + if (!newblock) + goto out; + newblock->start = start + size; + newblock->size = p->size - size; + newblock->filp = NULL; + newblock->next = p->next; + newblock->prev = p; + p->next->prev = newblock; + p->next = newblock; + p->size = size; + } + +out: + /* Our block is in the middle */ + p->filp = filp; + return p; +} + +static struct mem_block *alloc_block(struct mem_block *heap, uint64_t size, + int align2, DRMFILE filp) +{ + struct mem_block *p; + uint64_t mask = (1 << align2) - 1; + + if (!heap) + return NULL; + + list_for_each(p, heap) { + uint64_t start = (p->start + mask) & ~mask; + if (p->filp == 0 && start + size <= p->start + p->size) + return split_block(p, start, size, filp); + } + + return NULL; +} + +static struct mem_block *find_block(struct mem_block *heap, uint64_t start) +{ + struct mem_block *p; + + list_for_each(p, heap) + if (p->start == start) + return p; + + return NULL; +} + +static void free_block(struct mem_block *p) +{ + p->filp = NULL; + + /* Assumes a single contiguous range. Needs a special filp in + * 'heap' to stop it being subsumed. + */ + if (p->next->filp == 0) { + struct mem_block *q = p->next; + p->size += q->size; + p->next = q->next; + p->next->prev = p; + drm_free(q, sizeof(*q), DRM_MEM_BUFS); + } + + if (p->prev->filp == 0) { + struct mem_block *q = p->prev; + q->size += p->size; + q->next = p->next; + q->next->prev = q; + drm_free(p, sizeof(*q), DRM_MEM_BUFS); + } +} + +/* Initialize. How to check for an uninitialized heap? + */ +static int init_heap(struct mem_block **heap, uint64_t start, uint64_t size) +{ + struct mem_block *blocks = drm_alloc(sizeof(*blocks), DRM_MEM_BUFS); + + if (!blocks) + return DRM_ERR(ENOMEM); + + *heap = drm_alloc(sizeof(**heap), DRM_MEM_BUFS); + if (!*heap) { + drm_free(blocks, sizeof(*blocks), DRM_MEM_BUFS); + return DRM_ERR(ENOMEM); + } + + blocks->start = start; + blocks->size = size; + blocks->filp = NULL; + blocks->next = blocks->prev = *heap; + + memset(*heap, 0, sizeof(**heap)); + (*heap)->filp = (DRMFILE) - 1; + (*heap)->next = (*heap)->prev = blocks; + return 0; +} + +/* + * Free all blocks associated with the releasing filp + */ +void nouveau_mem_release(DRMFILE filp, struct mem_block *heap) +{ + struct mem_block *p; + + if (!heap || !heap->next) + return; + + list_for_each(p, heap) { + if (p->filp == filp) + p->filp = NULL; + } + + /* Assumes a single contiguous range. Needs a special filp in + * 'heap' to stop it being subsumed. + */ + list_for_each(p, heap) { + while ((p->filp == 0) && (p->next->filp == 0) && (p->next!=heap)) { + struct mem_block *q = p->next; + p->size += q->size; + p->next = q->next; + p->next->prev = p; + drm_free(q, sizeof(*q), DRM_MEM_DRIVER); + } + } +} + +/* + * Cleanup everything + */ +static void nouveau_mem_takedown(struct mem_block **heap) +{ + struct mem_block *p; + + if (!*heap) + return; + + for (p = (*heap)->next; p != *heap;) { + struct mem_block *q = p; + p = p->next; + drm_free(q, sizeof(*q), DRM_MEM_DRIVER); + } + + drm_free(*heap, sizeof(**heap), DRM_MEM_DRIVER); + *heap = NULL; +} + +void nouveau_mem_close(struct drm_device *dev) +{ + drm_nouveau_private_t *dev_priv = dev->dev_private; + nouveau_mem_takedown(&dev_priv->agp_heap); + nouveau_mem_takedown(&dev_priv->fb_heap); +} + +/* returns the amount of FB ram in bytes */ +uint64_t nouveau_mem_fb_amount(struct drm_device *dev) +{ + drm_nouveau_private_t *dev_priv=dev->dev_private; + switch(dev_priv->card_type) + { + case NV_03: + switch(NV_READ(NV03_BOOT_0)&NV03_BOOT_0_RAM_AMOUNT) + { + case NV03_BOOT_0_RAM_AMOUNT_8MB: + case NV03_BOOT_0_RAM_AMOUNT_8MB_SDRAM: + return 8*1024*1024; + case NV03_BOOT_0_RAM_AMOUNT_4MB: + return 4*1024*1024; + case NV03_BOOT_0_RAM_AMOUNT_2MB: + return 2*1024*1024; + } + break; + case NV_04: + case NV_05: + if (NV_READ(NV03_BOOT_0) & 0x00000100) { + return (((NV_READ(NV03_BOOT_0) >> 12) & 0xf)*2+2)*1024*1024; + } else + switch(NV_READ(NV03_BOOT_0)&NV03_BOOT_0_RAM_AMOUNT) + { + case NV04_BOOT_0_RAM_AMOUNT_32MB: + return 32*1024*1024; + case NV04_BOOT_0_RAM_AMOUNT_16MB: + return 16*1024*1024; + case NV04_BOOT_0_RAM_AMOUNT_8MB: + return 8*1024*1024; + case NV04_BOOT_0_RAM_AMOUNT_4MB: + return 4*1024*1024; + } + break; + case NV_10: + case NV_20: + case NV_30: + case NV_40: + case G_70: + default: + // XXX won't work on BSD because of pci_read_config_dword + if (dev_priv->flags&NV_NFORCE) { + uint32_t mem; + pci_read_config_dword(dev->pdev, 0x7C, &mem); + return (uint64_t)(((mem >> 6) & 31) + 1)*1024*1024; + } else if(dev_priv->flags&NV_NFORCE2) { + uint32_t mem; + pci_read_config_dword(dev->pdev, 0x84, &mem); + return (uint64_t)(((mem >> 4) & 127) + 1)*1024*1024; + } else { + uint64_t mem; + mem=(NV_READ(NV04_FIFO_DATA)&NV10_FIFO_DATA_RAM_AMOUNT_MB_MASK) >> NV10_FIFO_DATA_RAM_AMOUNT_MB_SHIFT; + return mem*1024*1024; + } + break; + } + + DRM_ERROR("Unable to detect video ram size. Please report your setup to " DRIVER_EMAIL "\n"); + return 0; +} + + + +int nouveau_mem_init(struct drm_device *dev) +{ + drm_nouveau_private_t *dev_priv = dev->dev_private; + + /* init AGP */ + dev_priv->agp_heap=NULL; + if (drm_device_is_agp(dev)) + { + int err; + drm_agp_info_t info; + drm_agp_mode_t mode; + drm_agp_buffer_t agp_req; + drm_agp_binding_t bind_req; + + err = drm_agp_acquire(dev); + if (err) { + DRM_ERROR("Unable to acquire AGP: %d\n", err); + goto no_agp; + } + + err = drm_agp_info(dev, &info); + if (err) { + DRM_ERROR("Unable to get AGP info: %d\n", err); + goto no_agp; + } + + /* see agp.h for the AGPSTAT_* modes available */ + mode.mode = info.mode; + err = drm_agp_enable(dev, mode); + if (err) { + DRM_ERROR("Unable to enable AGP: %d\n", err); + goto no_agp; + } + + agp_req.size = info.aperture_size; + agp_req.type = 0; + err = drm_agp_alloc(dev, &agp_req); + if (err) { + DRM_ERROR("Unable to alloc AGP: %d\n", err); + goto no_agp; + } + + bind_req.handle = agp_req.handle; + bind_req.offset = 0; + err = drm_agp_bind(dev, &bind_req); + if (err) { + DRM_ERROR("Unable to bind AGP: %d\n", err); + goto no_agp; + } + + if (init_heap(&dev_priv->agp_heap, info.aperture_base, info.aperture_size)) + goto no_agp; + } +no_agp: + + /* Init FB */ + if (nouveau_mem_fb_amount(dev)>256*1024*1024) { + /* On cards with > 256Mb, you can't map everything. + * So we create a second FB heap for that type of memory */ + if (init_heap(&dev_priv->fb_heap, drm_get_resource_start(dev,1), 256*1024*1024)) + return DRM_ERR(ENOMEM); + if (init_heap(&dev_priv->fb_nomap_heap, drm_get_resource_start(dev,1)+256*1024*1024, nouveau_mem_fb_amount(dev)-256*1024*1024)) + return DRM_ERR(ENOMEM); + } else { + if (init_heap(&dev_priv->fb_heap, drm_get_resource_start(dev,1), nouveau_mem_fb_amount(dev))) + return DRM_ERR(ENOMEM); + dev_priv->fb_nomap_heap=NULL; + } + + return 0; +} + +struct mem_block* nouveau_mem_alloc(struct drm_device *dev, int alignment, uint64_t size, int flags, DRMFILE filp) +{ + struct mem_block *block; + int type; + drm_nouveau_private_t *dev_priv = dev->dev_private; + + /* + * Init memory if needed + */ + if (meminit_ok==0) + { + nouveau_mem_init(dev); + meminit_ok=1; + } + + /* + * Make things easier on ourselves: all allocations are page-aligned. + * We need that to map allocated regions into the user space + */ + if (alignment < PAGE_SHIFT) + alignment = PAGE_SHIFT; + + /* + * Warn about 0 sized allocations, but let it go through. It'll return 1 page + */ + if (size == 0) + DRM_INFO("warning : 0 byte allocation\n"); + + /* + * Keep alloc size a multiple of the page size to keep drm_addmap() happy + */ + if (size & (~PAGE_MASK)) + size = ((size/PAGE_SIZE) + 1) * PAGE_SIZE; + + if (flags&NOUVEAU_MEM_AGP) { + type=NOUVEAU_MEM_AGP; + block = alloc_block(dev_priv->agp_heap, size, alignment, filp); + if (block) goto alloc_ok; + } + if (flags&(NOUVEAU_MEM_FB|NOUVEAU_MEM_FB_ACCEPTABLE)) { + type=NOUVEAU_MEM_FB; + if (!(flags&NOUVEAU_MEM_MAPPED)) { + block = alloc_block(dev_priv->fb_nomap_heap, size, alignment, filp); + if (block) goto alloc_ok; + } + block = alloc_block(dev_priv->fb_heap, size, alignment, filp); + if (block) goto alloc_ok; + } + if (flags&NOUVEAU_MEM_AGP_ACCEPTABLE) { + type=NOUVEAU_MEM_AGP; + block = alloc_block(dev_priv->agp_heap, size, alignment, filp); + if (block) goto alloc_ok; + } + + return NULL; + +alloc_ok: + block->flags=type; + + if (flags&NOUVEAU_MEM_MAPPED) + { + int ret; + block->flags|=NOUVEAU_MEM_MAPPED; + + if (type == NOUVEAU_MEM_AGP) + ret = drm_addmap(dev, block->start - dev->agp->base, block->size, + _DRM_AGP, 0, &block->map); + else + ret = drm_addmap(dev, block->start, block->size, + _DRM_FRAME_BUFFER, 0, &block->map); + if (ret) { + free_block(block); + return NULL; + } + } + + DRM_INFO("allocated 0x%llx\n", block->start); + return block; +} + +void nouveau_mem_free(struct drm_device* dev, struct mem_block* block) +{ + DRM_INFO("freeing 0x%llx\n", block->start); + if (meminit_ok==0) + { + DRM_ERROR("%s called without init\n", __FUNCTION__); + return; + } + if (block->flags&NOUVEAU_MEM_MAPPED) + drm_rmmap(dev, block->map); + free_block(block); +} + +/* + * Ioctls + */ + +int nouveau_ioctl_mem_alloc(DRM_IOCTL_ARGS) +{ + DRM_DEVICE; + drm_nouveau_private_t *dev_priv = dev->dev_private; + drm_nouveau_mem_alloc_t alloc; + struct mem_block *block; + + if (!dev_priv) { + DRM_ERROR("%s called with no initialization\n", __FUNCTION__); + return DRM_ERR(EINVAL); + } + + DRM_COPY_FROM_USER_IOCTL(alloc, (drm_nouveau_mem_alloc_t __user *) data, + sizeof(alloc)); + + block=nouveau_mem_alloc(dev, alloc.alignment, alloc.size, alloc.flags, filp); + if (!block) + return DRM_ERR(ENOMEM); + + if (DRM_COPY_TO_USER(alloc.region_offset, &block->start, sizeof(uint64_t))) { + DRM_ERROR("copy_to_user\n"); + return DRM_ERR(EFAULT); + } + + return 0; +} + +int nouveau_ioctl_mem_free(DRM_IOCTL_ARGS) +{ + DRM_DEVICE; + drm_nouveau_private_t *dev_priv = dev->dev_private; + drm_nouveau_mem_free_t memfree; + struct mem_block *block; + + if (!dev_priv) { + DRM_ERROR("%s called with no initialization\n", __FUNCTION__); + return DRM_ERR(EINVAL); + } + + DRM_COPY_FROM_USER_IOCTL(memfree, (drm_nouveau_mem_free_t __user *) data, + sizeof(memfree)); + + block=NULL; + if (memfree.flags&NOUVEAU_MEM_FB) + block = find_block(dev_priv->fb_heap, memfree.region_offset); + else if (memfree.flags&NOUVEAU_MEM_AGP) + block = find_block(dev_priv->agp_heap, memfree.region_offset); + if (!block) + return DRM_ERR(EFAULT); + if (block->filp != filp) + return DRM_ERR(EPERM); + + nouveau_mem_free(dev, block); + return 0; +} + + |