/* * Copyright (c) 2009, Intel Corporation. * All Rights Reserved. * * 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 OR COPYRIGHT HOLDERS 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: * Eric Anholt * */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include "drmP.h" #include "drm.h" /* * @file drm_gem.c * * This file provides some of the base ioctls and library routines for * the graphics memory manager implemented by each device driver. * * Because various devices have different requirements in terms of * synchronization and migration strategies, implementing that is left up to * the driver, and all that the general API provides should be generic -- * allocating objects, reading/writing data with the cpu, freeing objects. * Even there, platform-dependent optimizations for reading/writing data with * the CPU mean we'll likely hook those out to driver-specific calls. However, * the DRI2 implementation wants to have at least allocate/mmap be generic. * * The goal was to have swap-backed object allocation managed through * struct file. However, file descriptors as handles to a struct file have * two major failings: * - Process limits prevent more than 1024 or so being used at a time by * default. * - Inability to allocate high fds will aggravate the X Server's select() * handling, and likely that of many GL client applications as well. * * This led to a plan of using our own integer IDs(called handles, following * DRM terminology) to mimic fds, and implement the fd syscalls we need as * ioctls. The objects themselves will still include the struct file so * that we can transition to fds if the required kernel infrastructure shows * up at a later date, and as our interface with shmfs for memory allocation. */ void idr_list_init(struct idr_list *head) { struct idr_list *entry; /* HASH for accelerate */ entry = kmem_zalloc(DRM_GEM_OBJIDR_HASHNODE * sizeof (struct idr_list), KM_SLEEP); head->next = entry; for (int i = 0; i < DRM_GEM_OBJIDR_HASHNODE; i++) { INIT_LIST_HEAD(&entry[i]); } } int idr_list_get_new_above(struct idr_list *head, struct drm_gem_object *obj, int *handlep) { struct idr_list *entry; int key; entry = kmem_zalloc(sizeof (*entry), KM_SLEEP); key = obj->name % DRM_GEM_OBJIDR_HASHNODE; list_add(entry, &head->next[key], NULL); entry->obj = obj; entry->handle = obj->name; *handlep = obj->name; return (0); } struct drm_gem_object * idr_list_find(struct idr_list *head, uint32_t name) { struct idr_list *entry; int key; key = name % DRM_GEM_OBJIDR_HASHNODE; list_for_each(entry, &head->next[key]) { if (entry->handle == name) return (entry->obj); } return (NULL); } int idr_list_remove(struct idr_list *head, uint32_t name) { struct idr_list *entry, *temp; int key; key = name % DRM_GEM_OBJIDR_HASHNODE; list_for_each_safe(entry, temp, &head->next[key]) { if (entry->handle == name) { list_del(entry); kmem_free(entry, sizeof (*entry)); return (0); } } DRM_ERROR("Failed to remove the object %d", name); return (-1); } void idr_list_free(struct idr_list *head) { struct idr_list *entry, *temp; for (int key = 0; key < DRM_GEM_OBJIDR_HASHNODE; key++) { list_for_each_safe(entry, temp, &head->next[key]) { list_del(entry); kmem_free(entry, sizeof (*entry)); } } kmem_free(head->next, DRM_GEM_OBJIDR_HASHNODE * sizeof (struct idr_list)); head->next = NULL; } int idr_list_empty(struct idr_list *head) { int empty; for (int key = 0; key < DRM_GEM_OBJIDR_HASHNODE; key++) { empty = list_empty(&(head)->next[key]); if (!empty) return (empty); } return (1); } static uint32_t shfile_name = 0; #define SHFILE_NAME_MAX 0xffffffff /* * will be set to 1 for 32 bit x86 systems only, in startup.c */ extern int segkp_fromheap; extern ulong_t *segkp_bitmap; void drm_gem_object_reference(struct drm_gem_object *obj) { atomic_inc(&obj->refcount); } void drm_gem_object_unreference(struct drm_gem_object *obj) { if (obj == NULL) return; atomic_sub(1, &obj->refcount); if (obj->refcount == 0) drm_gem_object_free(obj); } void drm_gem_object_handle_reference(struct drm_gem_object *obj) { drm_gem_object_reference(obj); atomic_inc(&obj->handlecount); } void drm_gem_object_handle_unreference(struct drm_gem_object *obj) { if (obj == NULL) return; /* * Must bump handle count first as this may be the last * ref, in which case the object would disappear before we * checked for a name */ atomic_sub(1, &obj->handlecount); if (obj->handlecount == 0) drm_gem_object_handle_free(obj); drm_gem_object_unreference(obj); } /* * Initialize the GEM device fields */ int drm_gem_init(struct drm_device *dev) { mutex_init(&dev->object_name_lock, NULL, MUTEX_DRIVER, NULL); idr_list_init(&dev->object_name_idr); atomic_set(&dev->object_count, 0); atomic_set(&dev->object_memory, 0); atomic_set(&dev->pin_count, 0); atomic_set(&dev->pin_memory, 0); atomic_set(&dev->gtt_count, 0); atomic_set(&dev->gtt_memory, 0); return (0); } /* * Allocate a GEM object of the specified size with shmfs backing store */ struct drm_gem_object * drm_gem_object_alloc(struct drm_device *dev, size_t size) { static ddi_dma_attr_t dma_attr = { DMA_ATTR_V0, 0U, /* dma_attr_addr_lo */ 0xffffffffU, /* dma_attr_addr_hi */ 0xffffffffU, /* dma_attr_count_max */ 4096, /* dma_attr_align */ 0x1fffU, /* dma_attr_burstsizes */ 1, /* dma_attr_minxfer */ 0xffffffffU, /* dma_attr_maxxfer */ 0xffffffffU, /* dma_attr_seg */ 1, /* dma_attr_sgllen, variable */ 4, /* dma_attr_granular */ 0 /* dma_attr_flags */ }; static ddi_device_acc_attr_t acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_MERGING_OK_ACC }; struct drm_gem_object *obj; ddi_dma_cookie_t cookie; uint_t cookie_cnt; drm_local_map_t *map; pgcnt_t real_pgcnt, pgcnt = btopr(size); uint32_t paddr, cookie_end; int i, n; obj = kmem_zalloc(sizeof (struct drm_gem_object), KM_NOSLEEP); if (obj == NULL) return (NULL); obj->dev = dev; obj->flink = 0; obj->size = size; if (shfile_name == SHFILE_NAME_MAX) { DRM_ERROR("No name space for object"); goto err1; } else { obj->name = ++shfile_name; } dma_attr.dma_attr_sgllen = (int)pgcnt; if (ddi_dma_alloc_handle(dev->dip, &dma_attr, DDI_DMA_DONTWAIT, NULL, &obj->dma_hdl)) { DRM_ERROR("drm_gem_object_alloc: " "ddi_dma_alloc_handle failed"); goto err1; } if (ddi_dma_mem_alloc(obj->dma_hdl, ptob(pgcnt), &acc_attr, IOMEM_DATA_UC_WR_COMBINE, DDI_DMA_DONTWAIT, NULL, &obj->kaddr, &obj->real_size, &obj->acc_hdl)) { DRM_ERROR("drm_gem_object_alloc: " "ddi_dma_mem_alloc failed"); goto err2; } if (ddi_dma_addr_bind_handle(obj->dma_hdl, NULL, obj->kaddr, obj->real_size, DDI_DMA_RDWR, DDI_DMA_DONTWAIT, NULL, &cookie, &cookie_cnt) != DDI_DMA_MAPPED) { DRM_ERROR("drm_gem_object_alloc: " "ddi_dma_addr_bind_handle failed"); goto err3; } real_pgcnt = btopr(obj->real_size); obj->pfnarray = kmem_zalloc(real_pgcnt * sizeof (pfn_t), KM_NOSLEEP); if (obj->pfnarray == NULL) { goto err4; } for (n = 0, i = 1; ; i++) { for (paddr = cookie.dmac_address, cookie_end = cookie.dmac_address + cookie.dmac_size; paddr < cookie_end; paddr += PAGESIZE) { obj->pfnarray[n++] = btop(paddr); if (n >= real_pgcnt) goto addmap; } if (i >= cookie_cnt) break; ddi_dma_nextcookie(obj->dma_hdl, &cookie); } addmap: map = drm_alloc(sizeof (struct drm_local_map), DRM_MEM_MAPS); if (map == NULL) { goto err5; } map->handle = obj; map->offset = (uintptr_t)map->handle; map->offset &= 0xffffffffUL; map->dev_addr = map->handle; map->size = obj->real_size; map->type = _DRM_TTM; map->flags = _DRM_WRITE_COMBINING | _DRM_REMOVABLE; map->drm_umem_cookie = gfxp_umem_cookie_init(obj->kaddr, obj->real_size); if (map->drm_umem_cookie == NULL) { goto err6; } obj->map = map; atomic_set(&obj->refcount, 1); atomic_set(&obj->handlecount, 1); if (dev->driver->gem_init_object != NULL && dev->driver->gem_init_object(obj) != 0) { goto err7; } atomic_inc(&dev->object_count); atomic_add(obj->size, &dev->object_memory); return (obj); err7: gfxp_umem_cookie_destroy(map->drm_umem_cookie); err6: drm_free(map, sizeof (struct drm_local_map), DRM_MEM_MAPS); err5: kmem_free(obj->pfnarray, real_pgcnt * sizeof (pfn_t)); err4: (void) ddi_dma_unbind_handle(obj->dma_hdl); err3: ddi_dma_mem_free(&obj->acc_hdl); err2: ddi_dma_free_handle(&obj->dma_hdl); err1: kmem_free(obj, sizeof (struct drm_gem_object)); return (NULL); } /* * Removes the mapping from handle to filp for this object. */ static int drm_gem_handle_delete(struct drm_file *filp, int handle) { struct drm_device *dev; struct drm_gem_object *obj; int err; /* * This is gross. The idr system doesn't let us try a delete and * return an error code. It just spews if you fail at deleting. * So, we have to grab a lock around finding the object and then * doing the delete on it and dropping the refcount, or the user * could race us to double-decrement the refcount and cause a * use-after-free later. Given the frequency of our handle lookups, * we may want to use ida for number allocation and a hash table * for the pointers, anyway. */ spin_lock(&filp->table_lock); /* Check if we currently have a reference on the object */ obj = idr_list_find(&filp->object_idr, handle); if (obj == NULL) { spin_unlock(&filp->table_lock); DRM_ERROR("obj %d is not in tne list, failed to close", handle); return (EINVAL); } dev = obj->dev; /* Release reference and decrement refcount. */ err = idr_list_remove(&filp->object_idr, handle); if (err == -1) DRM_ERROR("%s", __func__); spin_unlock(&filp->table_lock); spin_lock(&dev->struct_mutex); drm_gem_object_handle_unreference(obj); spin_unlock(&dev->struct_mutex); return (0); } /* * Create a handle for this object. This adds a handle reference * to the object, which includes a regular reference count. Callers * will likely want to dereference the object afterwards. */ int drm_gem_handle_create(struct drm_file *file_priv, struct drm_gem_object *obj, int *handlep) { int ret; /* * Get the user-visible handle using idr. */ again: /* ensure there is space available to allocate a handle */ /* do the allocation under our spinlock */ spin_lock(&file_priv->table_lock); ret = idr_list_get_new_above(&file_priv->object_idr, obj, handlep); spin_unlock(&file_priv->table_lock); if (ret == -EAGAIN) goto again; if (ret != 0) { DRM_ERROR("Failed to create handle"); return (ret); } drm_gem_object_handle_reference(obj); return (0); } /* Returns a reference to the object named by the handle. */ struct drm_gem_object * drm_gem_object_lookup(struct drm_file *filp, int handle) { struct drm_gem_object *obj; spin_lock(&filp->table_lock); /* Check if we currently have a reference on the object */ obj = idr_list_find(&filp->object_idr, handle); if (obj == NULL) { spin_unlock(&filp->table_lock); DRM_ERROR("object_lookup failed, handle %d", handle); return (NULL); } drm_gem_object_reference(obj); spin_unlock(&filp->table_lock); return (obj); } /* * Releases the handle to an mm object. */ /*ARGSUSED*/ int drm_gem_close_ioctl(DRM_IOCTL_ARGS) { DRM_DEVICE; struct drm_gem_close args; int ret; if (!(dev->driver->use_gem == 1)) return (ENODEV); DRM_COPYFROM_WITH_RETURN(&args, (void *)data, sizeof (args)); ret = drm_gem_handle_delete(fpriv, args.handle); return (ret); } /* * Create a global name for an object, returning the name. * * Note that the name does not hold a reference; when the object * is freed, the name goes away. */ /*ARGSUSED*/ int drm_gem_flink_ioctl(DRM_IOCTL_ARGS) { DRM_DEVICE; struct drm_gem_flink args; struct drm_gem_object *obj; int ret, handle; if (!(dev->driver->use_gem == 1)) return (ENODEV); DRM_COPYFROM_WITH_RETURN(&args, (void *)data, sizeof (args)); obj = drm_gem_object_lookup(fpriv, args.handle); if (obj == NULL) return (EINVAL); handle = args.handle; spin_lock(&dev->object_name_lock); if (!obj->flink) { /* only creat a node in object_name_idr, no update anything */ ret = idr_list_get_new_above(&dev->object_name_idr, obj, &handle); obj->flink = obj->name; /* Allocate a reference for the name table. */ drm_gem_object_reference(obj); } /* * Leave the reference from the lookup around as the * name table now holds one */ args.name = obj->name; spin_unlock(&dev->object_name_lock); ret = DRM_COPY_TO_USER((void *) data, &args, sizeof (args)); if (ret != 0) DRM_ERROR(" gem flink error! %d", ret); spin_lock(&dev->struct_mutex); drm_gem_object_unreference(obj); spin_unlock(&dev->struct_mutex); return (ret); } /* * Open an object using the global name, returning a handle and the size. * * This handle (of course) holds a reference to the object, so the object * will not go away until the handle is deleted. */ /*ARGSUSED*/ int drm_gem_open_ioctl(DRM_IOCTL_ARGS) { DRM_DEVICE; struct drm_gem_open args; struct drm_gem_object *obj; int ret; int handle; if (!(dev->driver->use_gem == 1)) { DRM_ERROR("Not support GEM"); return (ENODEV); } DRM_COPYFROM_WITH_RETURN(&args, (void *) data, sizeof (args)); spin_lock(&dev->object_name_lock); obj = idr_list_find(&dev->object_name_idr, args.name); if (obj) drm_gem_object_reference(obj); spin_unlock(&dev->object_name_lock); if (!obj) { DRM_ERROR("Can't find the obj %d", args.name); return (ENOENT); } ret = drm_gem_handle_create(fpriv, obj, &handle); spin_lock(&dev->struct_mutex); drm_gem_object_unreference(obj); spin_unlock(&dev->struct_mutex); args.handle = args.name; args.size = obj->size; ret = DRM_COPY_TO_USER((void *) data, &args, sizeof (args)); if (ret != 0) DRM_ERROR(" gem open error! %d", ret); return (ret); } /* * Called at device open time, sets up the structure for handling refcounting * of mm objects. */ void drm_gem_open(struct drm_file *file_private) { idr_list_init(&file_private->object_idr); mutex_init(&file_private->table_lock, NULL, MUTEX_DRIVER, NULL); } /* * Called at device close to release the file's * handle references on objects. */ static void drm_gem_object_release_handle(struct drm_gem_object *obj) { drm_gem_object_handle_unreference(obj); } /* * Called at close time when the filp is going away. * * Releases any remaining references on objects by this filp. */ void drm_gem_release(struct drm_device *dev, struct drm_file *file_private) { struct idr_list *entry; spin_lock(&dev->struct_mutex); idr_list_for_each(entry, &file_private->object_idr) drm_gem_object_release_handle(entry->obj); idr_list_free(&file_private->object_idr); spin_unlock(&dev->struct_mutex); } /* * Called after the last reference to the object has been lost. * * Frees the object */ void drm_gem_object_free(struct drm_gem_object *obj) { struct drm_device *dev = obj->dev; struct drm_local_map *map = obj->map; if (dev->driver->gem_free_object != NULL) dev->driver->gem_free_object(obj); gfxp_umem_cookie_destroy(map->drm_umem_cookie); drm_free(map, sizeof (struct drm_local_map), DRM_MEM_MAPS); kmem_free(obj->pfnarray, btopr(obj->real_size) * sizeof (pfn_t)); (void) ddi_dma_unbind_handle(obj->dma_hdl); ddi_dma_mem_free(&obj->acc_hdl); ddi_dma_free_handle(&obj->dma_hdl); atomic_dec(&dev->object_count); atomic_sub(obj->size, &dev->object_memory); kmem_free(obj, sizeof (struct drm_gem_object)); } /* * Called after the last handle to the object has been closed * * Removes any name for the object. Note that this must be * called before drm_gem_object_free or we'll be touching * freed memory */ void drm_gem_object_handle_free(struct drm_gem_object *obj) { int err; struct drm_device *dev = obj->dev; /* Remove any name for this object */ spin_lock(&dev->object_name_lock); if (obj->flink) { err = idr_list_remove(&dev->object_name_idr, obj->name); if (err == -1) DRM_ERROR("%s", __func__); obj->flink = 0; spin_unlock(&dev->object_name_lock); /* * The object name held a reference to this object, drop * that now. */ drm_gem_object_unreference(obj); } else spin_unlock(&dev->object_name_lock); }