/* Copyright (c) 2009-2013, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ /* Implements an interface between KGSL and the DRM subsystem. For now this * is pretty simple, but it will take on more of the workload as time goes * on */ #include "drmP.h" #include "drm.h" #include #include #include "kgsl.h" #include "kgsl_device.h" #include "kgsl_drm.h" #include "kgsl_mmu.h" #include "kgsl_sharedmem.h" #define DRIVER_AUTHOR "Qualcomm" #define DRIVER_NAME "kgsl" #define DRIVER_DESC "KGSL DRM" #define DRIVER_DATE "20121107" #define DRIVER_MAJOR 2 #define DRIVER_MINOR 1 #define DRIVER_PATCHLEVEL 1 #define DRM_KGSL_GEM_FLAG_MAPPED (1 << 0) #define ENTRY_EMPTY -1 #define ENTRY_NEEDS_CLEANUP -2 #define DRM_KGSL_NOT_INITED -1 #define DRM_KGSL_INITED 1 #define DRM_KGSL_NUM_FENCE_ENTRIES (DRM_KGSL_HANDLE_WAIT_ENTRIES << 2) #define DRM_KGSL_HANDLE_WAIT_ENTRIES 5 /* Returns true if the memory type is in PMEM */ #ifdef CONFIG_KERNEL_PMEM_SMI_REGION #define TYPE_IS_PMEM(_t) \ (((_t & DRM_KGSL_GEM_TYPE_MEM_MASK) == DRM_KGSL_GEM_TYPE_EBI) || \ ((_t & DRM_KGSL_GEM_TYPE_MEM_MASK) == DRM_KGSL_GEM_TYPE_SMI) || \ ((_t) & DRM_KGSL_GEM_TYPE_PMEM)) #else #define TYPE_IS_PMEM(_t) \ (((_t & DRM_KGSL_GEM_TYPE_MEM_MASK) == DRM_KGSL_GEM_TYPE_EBI) || \ ((_t) & (DRM_KGSL_GEM_TYPE_PMEM | DRM_KGSL_GEM_PMEM_EBI))) #endif /* Returns true if the memory type is regular */ #define TYPE_IS_MEM(_t) \ (((_t & DRM_KGSL_GEM_TYPE_MEM_MASK) == DRM_KGSL_GEM_TYPE_KMEM) || \ ((_t & DRM_KGSL_GEM_TYPE_MEM_MASK) == DRM_KGSL_GEM_TYPE_KMEM_NOCACHE) || \ ((_t) & DRM_KGSL_GEM_TYPE_MEM)) #define TYPE_IS_FD(_t) ((_t) & DRM_KGSL_GEM_TYPE_FD_MASK) /* Returns true if KMEM region is uncached */ #define IS_MEM_UNCACHED(_t) \ ((_t == DRM_KGSL_GEM_TYPE_KMEM_NOCACHE) || \ (_t == DRM_KGSL_GEM_TYPE_KMEM) || \ (TYPE_IS_MEM(_t) && (_t & DRM_KGSL_GEM_CACHE_WCOMBINE))) struct drm_kgsl_gem_object_wait_list_entry { struct list_head list; int pid; int in_use; wait_queue_head_t process_wait_q; }; struct drm_kgsl_gem_object_fence { int32_t fence_id; unsigned int num_buffers; int ts_valid; unsigned int timestamp; int ts_device; int lockpid; struct list_head buffers_in_fence; }; struct drm_kgsl_gem_object_fence_list_entry { struct list_head list; int in_use; struct drm_gem_object *gem_obj; }; static int32_t fence_id = 0x1; static struct drm_kgsl_gem_object_fence gem_buf_fence[DRM_KGSL_NUM_FENCE_ENTRIES]; struct drm_kgsl_gem_object { struct drm_gem_object *obj; uint32_t type; struct kgsl_memdesc memdesc; struct kgsl_pagetable *pagetable; struct ion_handle *ion_handle; uint64_t mmap_offset; int bufcount; int flags; struct list_head list; int active; struct { uint32_t offset; uint32_t gpuaddr; } bufs[DRM_KGSL_GEM_MAX_BUFFERS]; struct genlock_handle *glock_handle[DRM_KGSL_GEM_MAX_BUFFERS]; int bound; int lockpid; /* Put these here to avoid allocing all the time */ struct drm_kgsl_gem_object_wait_list_entry wait_entries[DRM_KGSL_HANDLE_WAIT_ENTRIES]; /* Each object can only appear in a single fence */ struct drm_kgsl_gem_object_fence_list_entry fence_entries[DRM_KGSL_NUM_FENCE_ENTRIES]; struct list_head wait_list; }; static struct ion_client *kgsl_drm_ion_client; static int kgsl_drm_inited = DRM_KGSL_NOT_INITED; /* This is a global list of all the memory currently mapped in the MMU */ static struct list_head kgsl_mem_list; struct kgsl_drm_device_priv { struct kgsl_device *device[KGSL_DEVICE_MAX]; struct kgsl_device_private *devpriv[KGSL_DEVICE_MAX]; }; static int kgsl_gem_memory_allocated(struct drm_gem_object *obj) { struct drm_kgsl_gem_object *priv = obj->driver_private; return priv->memdesc.size ? 1 : 0; } static int kgsl_gem_alloc_memory(struct drm_gem_object *obj) { struct drm_kgsl_gem_object *priv = obj->driver_private; struct kgsl_mmu *mmu; struct sg_table *sg_table; struct scatterlist *s; int index; int result = 0; /* Return if the memory is already allocated */ if (kgsl_gem_memory_allocated(obj) || TYPE_IS_FD(priv->type)) return 0; if (priv->pagetable == NULL) { /* Hard coded to use A2X device for MSM7X27 and MSM8625 * Others to use A3X device */ #if defined(CONFIG_ARCH_MSM7X27) || defined(CONFIG_ARCH_MSM8625) mmu = &kgsl_get_device(KGSL_DEVICE_2D0)->mmu; #else mmu = &kgsl_get_device(KGSL_DEVICE_3D0)->mmu; #endif priv->pagetable = kgsl_mmu_getpagetable(mmu, KGSL_MMU_GLOBAL_PT); if (priv->pagetable == NULL) { DRM_ERROR("Unable to get the GPU MMU pagetable\n"); return -EINVAL; } } if (TYPE_IS_PMEM(priv->type)) { if (priv->type == DRM_KGSL_GEM_TYPE_EBI || priv->type & DRM_KGSL_GEM_PMEM_EBI) { priv->ion_handle = ion_alloc(kgsl_drm_ion_client, obj->size * priv->bufcount, PAGE_SIZE, ION_HEAP(ION_SF_HEAP_ID), 0); if (IS_ERR_OR_NULL(priv->ion_handle)) { DRM_ERROR( "Unable to allocate ION Phys memory handle\n"); return -ENOMEM; } priv->memdesc.pagetable = priv->pagetable; result = ion_phys(kgsl_drm_ion_client, priv->ion_handle, (ion_phys_addr_t *) &priv->memdesc.physaddr, &priv->memdesc.size); if (result) { DRM_ERROR( "Unable to get ION Physical memory address\n"); ion_free(kgsl_drm_ion_client, priv->ion_handle); priv->ion_handle = NULL; return result; } result = memdesc_sg_phys(&priv->memdesc, priv->memdesc.physaddr, priv->memdesc.size); if (result) { DRM_ERROR( "Unable to get sg list\n"); ion_free(kgsl_drm_ion_client, priv->ion_handle); priv->ion_handle = NULL; return result; } result = kgsl_mmu_map(priv->pagetable, &priv->memdesc); if (result) { DRM_ERROR( "kgsl_mmu_map failed. result = %d\n", result); ion_free(kgsl_drm_ion_client, priv->ion_handle); priv->ion_handle = NULL; return result; } } else return -EINVAL; } else if (TYPE_IS_MEM(priv->type)) { if (priv->type == DRM_KGSL_GEM_TYPE_KMEM || priv->type & DRM_KGSL_GEM_CACHE_MASK) list_add(&priv->list, &kgsl_mem_list); priv->memdesc.pagetable = priv->pagetable; priv->ion_handle = ion_alloc(kgsl_drm_ion_client, obj->size * priv->bufcount, PAGE_SIZE, ION_HEAP(ION_IOMMU_HEAP_ID), 0); if (IS_ERR_OR_NULL(priv->ion_handle)) { DRM_ERROR( "Unable to allocate ION IOMMU memory handle\n"); return -ENOMEM; } sg_table = ion_sg_table(kgsl_drm_ion_client, priv->ion_handle); if (IS_ERR_OR_NULL(priv->ion_handle)) { DRM_ERROR( "Unable to get ION sg table\n"); goto memerr; } priv->memdesc.sg = sg_table->sgl; /* Calculate the size of the memdesc from the sglist */ priv->memdesc.sglen = 0; for (s = priv->memdesc.sg; s != NULL; s = sg_next(s)) { priv->memdesc.size += s->length; priv->memdesc.sglen++; } result = kgsl_mmu_map(priv->pagetable, &priv->memdesc); if (result) { DRM_ERROR( "kgsl_mmu_map failed. result = %d\n", result); goto memerr; } } else return -EINVAL; for (index = 0; index < priv->bufcount; index++) { priv->bufs[index].offset = index * obj->size; priv->bufs[index].gpuaddr = priv->memdesc.gpuaddr + priv->bufs[index].offset; } priv->flags |= DRM_KGSL_GEM_FLAG_MAPPED; return 0; memerr: ion_free(kgsl_drm_ion_client, priv->ion_handle); priv->ion_handle = NULL; return -ENOMEM; } static void kgsl_gem_free_memory(struct drm_gem_object *obj) { struct drm_kgsl_gem_object *priv = obj->driver_private; int index; if (!kgsl_gem_memory_allocated(obj) || TYPE_IS_FD(priv->type)) return; if (priv->memdesc.gpuaddr) kgsl_mmu_unmap(priv->memdesc.pagetable, &priv->memdesc); /* ION will take care of freeing the sg table. */ priv->memdesc.sg = NULL; priv->memdesc.sglen = 0; if (priv->ion_handle) ion_free(kgsl_drm_ion_client, priv->ion_handle); priv->ion_handle = NULL; memset(&priv->memdesc, 0, sizeof(priv->memdesc)); for (index = 0; index < priv->bufcount; index++) { if (priv->glock_handle[index]) genlock_put_handle(priv->glock_handle[index]); } kgsl_mmu_putpagetable(priv->pagetable); priv->pagetable = NULL; if ((priv->type == DRM_KGSL_GEM_TYPE_KMEM) || (priv->type & DRM_KGSL_GEM_CACHE_MASK)) list_del(&priv->list); priv->flags &= ~DRM_KGSL_GEM_FLAG_MAPPED; } int kgsl_gem_init_object(struct drm_gem_object *obj) { struct drm_kgsl_gem_object *priv; priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (priv == NULL) { DRM_ERROR("Unable to create GEM object\n"); return -ENOMEM; } obj->driver_private = priv; priv->obj = obj; return 0; } void kgsl_gem_free_object(struct drm_gem_object *obj) { kgsl_gem_free_memory(obj); drm_gem_object_release(obj); kfree(obj->driver_private); } int kgsl_gem_obj_addr(int drm_fd, int handle, unsigned long *start, unsigned long *len) { struct file *filp; struct drm_device *dev; struct drm_file *file_priv; struct drm_gem_object *obj; struct drm_kgsl_gem_object *priv; int ret = 0; filp = fget(drm_fd); if (unlikely(filp == NULL)) { DRM_ERROR("Unable to get the DRM file descriptor\n"); return -EINVAL; } file_priv = filp->private_data; if (unlikely(file_priv == NULL)) { DRM_ERROR("Unable to get the file private data\n"); fput(filp); return -EINVAL; } dev = file_priv->minor->dev; if (unlikely(dev == NULL)) { DRM_ERROR("Unable to get the minor device\n"); fput(filp); return -EINVAL; } obj = drm_gem_object_lookup(dev, file_priv, handle); if (unlikely(obj == NULL)) { DRM_ERROR("Invalid GEM handle %x\n", handle); fput(filp); return -EBADF; } mutex_lock(&dev->struct_mutex); priv = obj->driver_private; /* We can only use the MDP for PMEM regions */ if (TYPE_IS_PMEM(priv->type)) { *start = priv->memdesc.physaddr + priv->bufs[priv->active].offset; *len = priv->memdesc.size; } else { *start = 0; *len = 0; ret = -EINVAL; } drm_gem_object_unreference(obj); mutex_unlock(&dev->struct_mutex); fput(filp); return ret; } static int kgsl_gem_init_obj(struct drm_device *dev, struct drm_file *file_priv, struct drm_gem_object *obj, int *handle) { struct drm_kgsl_gem_object *priv; int ret, i; mutex_lock(&dev->struct_mutex); priv = obj->driver_private; memset(&priv->memdesc, 0, sizeof(priv->memdesc)); priv->bufcount = 1; priv->active = 0; priv->bound = 0; priv->type = DRM_KGSL_GEM_TYPE_KMEM; ret = drm_gem_handle_create(file_priv, obj, handle); drm_gem_object_unreference(obj); INIT_LIST_HEAD(&priv->wait_list); for (i = 0; i < DRM_KGSL_HANDLE_WAIT_ENTRIES; i++) { INIT_LIST_HEAD((struct list_head *) &priv->wait_entries[i]); priv->wait_entries[i].pid = 0; init_waitqueue_head(&priv->wait_entries[i].process_wait_q); } for (i = 0; i < DRM_KGSL_NUM_FENCE_ENTRIES; i++) { INIT_LIST_HEAD((struct list_head *) &priv->fence_entries[i]); priv->fence_entries[i].in_use = 0; priv->fence_entries[i].gem_obj = obj; } mutex_unlock(&dev->struct_mutex); return ret; } int kgsl_gem_create_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_kgsl_gem_create *create = data; struct drm_gem_object *obj; int ret, handle; /* Page align the size so we can allocate multiple buffers */ create->size = ALIGN(create->size, 4096); obj = drm_gem_object_alloc(dev, create->size); if (obj == NULL) { DRM_ERROR("Unable to allocate the GEM object\n"); return -ENOMEM; } ret = kgsl_gem_init_obj(dev, file_priv, obj, &handle); if (ret) { drm_gem_object_release(obj); DRM_ERROR("Unable to initialize GEM object ret = %d\n", ret); return ret; } create->handle = handle; return 0; } int kgsl_gem_create_fd_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_kgsl_gem_create_fd *args = data; struct file *file; dev_t rdev; struct fb_info *info; struct drm_gem_object *obj; struct drm_kgsl_gem_object *priv; int ret, put_needed, handle; file = fget_light(args->fd, &put_needed); if (file == NULL) { DRM_ERROR("Unable to get the file object\n"); return -EBADF; } rdev = file->f_dentry->d_inode->i_rdev; /* Only framebuffer objects are supported ATM */ if (MAJOR(rdev) != FB_MAJOR) { DRM_ERROR("File descriptor is not a framebuffer\n"); ret = -EBADF; goto error_fput; } info = registered_fb[MINOR(rdev)]; if (info == NULL) { DRM_ERROR("Framebuffer minor %d is not registered\n", MINOR(rdev)); ret = -EBADF; goto error_fput; } obj = drm_gem_object_alloc(dev, info->fix.smem_len); if (obj == NULL) { DRM_ERROR("Unable to allocate GEM object\n"); ret = -ENOMEM; goto error_fput; } ret = kgsl_gem_init_obj(dev, file_priv, obj, &handle); if (ret) goto error_fput; mutex_lock(&dev->struct_mutex); priv = obj->driver_private; priv->memdesc.physaddr = info->fix.smem_start; priv->type = DRM_KGSL_GEM_TYPE_FD_FBMEM; mutex_unlock(&dev->struct_mutex); args->handle = handle; error_fput: fput_light(file, put_needed); return ret; } int kgsl_gem_create_from_ion_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_kgsl_gem_create_from_ion *args = data; struct drm_gem_object *obj; struct ion_handle *ion_handle; struct drm_kgsl_gem_object *priv; struct sg_table *sg_table; struct scatterlist *s; int ret, handle; unsigned long size; struct kgsl_mmu *mmu; ion_handle = ion_import_dma_buf(kgsl_drm_ion_client, args->ion_fd); if (IS_ERR_OR_NULL(ion_handle)) { DRM_ERROR("Unable to import dmabuf. Error number = %d\n", (int)PTR_ERR(ion_handle)); return -EINVAL; } ion_handle_get_size(kgsl_drm_ion_client, ion_handle, &size); if (size == 0) { ion_free(kgsl_drm_ion_client, ion_handle); DRM_ERROR( "cannot create GEM object from zero size ION buffer\n"); return -EINVAL; } obj = drm_gem_object_alloc(dev, size); if (obj == NULL) { ion_free(kgsl_drm_ion_client, ion_handle); DRM_ERROR("Unable to allocate the GEM object\n"); return -ENOMEM; } ret = kgsl_gem_init_obj(dev, file_priv, obj, &handle); if (ret) { ion_free(kgsl_drm_ion_client, ion_handle); drm_gem_object_release(obj); DRM_ERROR("Unable to initialize GEM object ret = %d\n", ret); return ret; } priv = obj->driver_private; priv->ion_handle = ion_handle; priv->type = DRM_KGSL_GEM_TYPE_KMEM; list_add(&priv->list, &kgsl_mem_list); #if defined(CONFIG_ARCH_MSM7X27) || defined(CONFIG_ARCH_MSM8625) mmu = &kgsl_get_device(KGSL_DEVICE_2D0)->mmu; #else mmu = &kgsl_get_device(KGSL_DEVICE_3D0)->mmu; #endif priv->pagetable = kgsl_mmu_getpagetable(mmu, KGSL_MMU_GLOBAL_PT); priv->memdesc.pagetable = priv->pagetable; sg_table = ion_sg_table(kgsl_drm_ion_client, priv->ion_handle); if (IS_ERR_OR_NULL(priv->ion_handle)) { DRM_ERROR("Unable to get ION sg table\n"); ion_free(kgsl_drm_ion_client, priv->ion_handle); priv->ion_handle = NULL; kgsl_mmu_putpagetable(priv->pagetable); drm_gem_object_release(obj); kfree(priv); return -ENOMEM; } priv->memdesc.sg = sg_table->sgl; /* Calculate the size of the memdesc from the sglist */ priv->memdesc.sglen = 0; for (s = priv->memdesc.sg; s != NULL; s = sg_next(s)) { priv->memdesc.size += s->length; priv->memdesc.sglen++; } ret = kgsl_mmu_map(priv->pagetable, &priv->memdesc); if (ret) { DRM_ERROR("kgsl_mmu_map failed. ret = %d\n", ret); ion_free(kgsl_drm_ion_client, priv->ion_handle); priv->ion_handle = NULL; kgsl_mmu_putpagetable(priv->pagetable); drm_gem_object_release(obj); kfree(priv); return -ENOMEM; } priv->bufs[0].offset = 0; priv->bufs[0].gpuaddr = priv->memdesc.gpuaddr; priv->flags |= DRM_KGSL_GEM_FLAG_MAPPED; args->handle = handle; return 0; } int kgsl_gem_get_ion_fd_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_kgsl_gem_get_ion_fd *args = data; struct drm_gem_object *obj; struct drm_kgsl_gem_object *priv; int ret = 0; obj = drm_gem_object_lookup(dev, file_priv, args->handle); if (obj == NULL) { DRM_ERROR("Invalid GEM handle %x\n", args->handle); return -EBADF; } mutex_lock(&dev->struct_mutex); priv = obj->driver_private; if (TYPE_IS_FD(priv->type)) ret = -EINVAL; else if (TYPE_IS_PMEM(priv->type) || TYPE_IS_MEM(priv->type)) { if (priv->ion_handle) { args->ion_fd = ion_share_dma_buf( kgsl_drm_ion_client, priv->ion_handle); if (args->ion_fd < 0) { DRM_ERROR( "Could not share ion buffer. Error = %d\n", args->ion_fd); ret = -EINVAL; } } else { DRM_ERROR("GEM object has no ion memory allocated.\n"); ret = -EINVAL; } } drm_gem_object_unreference(obj); mutex_unlock(&dev->struct_mutex); return ret; } int kgsl_gem_setmemtype_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_kgsl_gem_memtype *args = data; struct drm_gem_object *obj; struct drm_kgsl_gem_object *priv; int ret = 0; obj = drm_gem_object_lookup(dev, file_priv, args->handle); if (obj == NULL) { DRM_ERROR("Invalid GEM handle %x\n", args->handle); return -EBADF; } mutex_lock(&dev->struct_mutex); priv = obj->driver_private; if (TYPE_IS_FD(priv->type)) ret = -EINVAL; else { if (TYPE_IS_PMEM(args->type) || TYPE_IS_MEM(args->type)) priv->type = args->type; else ret = -EINVAL; } drm_gem_object_unreference(obj); mutex_unlock(&dev->struct_mutex); return ret; } int kgsl_gem_getmemtype_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_kgsl_gem_memtype *args = data; struct drm_gem_object *obj; struct drm_kgsl_gem_object *priv; obj = drm_gem_object_lookup(dev, file_priv, args->handle); if (obj == NULL) { DRM_ERROR("Invalid GEM handle %x\n", args->handle); return -EBADF; } mutex_lock(&dev->struct_mutex); priv = obj->driver_private; args->type = priv->type; drm_gem_object_unreference(obj); mutex_unlock(&dev->struct_mutex); return 0; } int kgsl_gem_unbind_gpu_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { return 0; } int kgsl_gem_bind_gpu_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { return 0; } /* Allocate the memory and prepare it for CPU mapping */ int kgsl_gem_alloc_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_kgsl_gem_alloc *args = data; struct drm_gem_object *obj; struct drm_kgsl_gem_object *priv; int ret; obj = drm_gem_object_lookup(dev, file_priv, args->handle); if (obj == NULL) { DRM_ERROR("Invalid GEM handle %x\n", args->handle); return -EBADF; } mutex_lock(&dev->struct_mutex); priv = obj->driver_private; ret = kgsl_gem_alloc_memory(obj); if (ret) { DRM_ERROR("Unable to allocate object memory\n"); } args->offset = 0; drm_gem_object_unreference(obj); mutex_unlock(&dev->struct_mutex); return ret; } int kgsl_gem_mmap_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { /* Ion is used for mmap at this time */ return 0; } /* This function is deprecated */ int kgsl_gem_prep_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_kgsl_gem_prep *args = data; struct drm_gem_object *obj; struct drm_kgsl_gem_object *priv; int ret; obj = drm_gem_object_lookup(dev, file_priv, args->handle); if (obj == NULL) { DRM_ERROR("Invalid GEM handle %x\n", args->handle); return -EBADF; } mutex_lock(&dev->struct_mutex); priv = obj->driver_private; ret = kgsl_gem_alloc_memory(obj); if (ret) { DRM_ERROR("Unable to allocate object memory\n"); drm_gem_object_unreference(obj); mutex_unlock(&dev->struct_mutex); return ret; } drm_gem_object_unreference(obj); mutex_unlock(&dev->struct_mutex); return 0; } int kgsl_gem_get_bufinfo_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_kgsl_gem_bufinfo *args = data; struct drm_gem_object *obj; struct drm_kgsl_gem_object *priv; int ret = -EINVAL; int index; obj = drm_gem_object_lookup(dev, file_priv, args->handle); if (obj == NULL) { DRM_ERROR("Invalid GEM handle %x\n", args->handle); return -EBADF; } mutex_lock(&dev->struct_mutex); priv = obj->driver_private; if (!kgsl_gem_memory_allocated(obj)) { DRM_ERROR("Memory not allocated for this object\n"); goto out; } for (index = 0; index < priv->bufcount; index++) { args->offset[index] = priv->bufs[index].offset; args->gpuaddr[index] = priv->bufs[index].gpuaddr; } args->count = priv->bufcount; args->active = priv->active; ret = 0; out: drm_gem_object_unreference(obj); mutex_unlock(&dev->struct_mutex); return ret; } /* Get the genlock handles base off the GEM handle */ int kgsl_gem_get_glock_handles_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_kgsl_gem_glockinfo *args = data; struct drm_gem_object *obj; struct drm_kgsl_gem_object *priv; int index; obj = drm_gem_object_lookup(dev, file_priv, args->handle); if (obj == NULL) { DRM_ERROR("Invalid GEM handle %x\n", args->handle); return -EBADF; } mutex_lock(&dev->struct_mutex); priv = obj->driver_private; for (index = 0; index < priv->bufcount; index++) { args->glockhandle[index] = genlock_get_fd_handle( priv->glock_handle[index]); } drm_gem_object_unreference(obj); mutex_unlock(&dev->struct_mutex); return 0; } int kgsl_gem_set_glock_handles_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_kgsl_gem_glockinfo *args = data; struct drm_gem_object *obj; struct drm_kgsl_gem_object *priv; int index; obj = drm_gem_object_lookup(dev, file_priv, args->handle); if (obj == NULL) { DRM_ERROR("Invalid GEM handle %x\n", args->handle); return -EBADF; } mutex_lock(&dev->struct_mutex); priv = obj->driver_private; for (index = 0; index < priv->bufcount; index++) { priv->glock_handle[index] = genlock_get_handle_fd( args->glockhandle[index]); } drm_gem_object_unreference(obj); mutex_unlock(&dev->struct_mutex); return 0; } int kgsl_gem_set_bufcount_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_kgsl_gem_bufcount *args = data; struct drm_gem_object *obj; struct drm_kgsl_gem_object *priv; int ret = -EINVAL; if (args->bufcount < 1 || args->bufcount > DRM_KGSL_GEM_MAX_BUFFERS) return -EINVAL; obj = drm_gem_object_lookup(dev, file_priv, args->handle); if (obj == NULL) { DRM_ERROR("Invalid GEM handle %x\n", args->handle); return -EBADF; } mutex_lock(&dev->struct_mutex); priv = obj->driver_private; /* It is too much math to worry about what happens if we are already allocated, so just bail if we are */ if (kgsl_gem_memory_allocated(obj)) { DRM_ERROR("Memory already allocated - cannot change" "number of buffers\n"); goto out; } priv->bufcount = args->bufcount; ret = 0; out: drm_gem_object_unreference(obj); mutex_unlock(&dev->struct_mutex); return ret; } int kgsl_gem_get_bufcount_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_kgsl_gem_bufcount *args = data; struct drm_gem_object *obj; struct drm_kgsl_gem_object *priv; obj = drm_gem_object_lookup(dev, file_priv, args->handle); if (obj == NULL) { DRM_ERROR("Invalid GEM handle %x\n", args->handle); return -EBADF; } mutex_lock(&dev->struct_mutex); priv = obj->driver_private; args->bufcount = priv->bufcount; drm_gem_object_unreference(obj); mutex_unlock(&dev->struct_mutex); return 0; } int kgsl_gem_set_active_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_kgsl_gem_active *args = data; struct drm_gem_object *obj; struct drm_kgsl_gem_object *priv; int ret = -EINVAL; obj = drm_gem_object_lookup(dev, file_priv, args->handle); if (obj == NULL) { DRM_ERROR("Invalid GEM handle %x\n", args->handle); return -EBADF; } mutex_lock(&dev->struct_mutex); priv = obj->driver_private; if (args->active < 0 || args->active >= priv->bufcount) { DRM_ERROR("Invalid active buffer %d\n", args->active); goto out; } priv->active = args->active; ret = 0; out: drm_gem_object_unreference(obj); mutex_unlock(&dev->struct_mutex); return ret; } int kgsl_gem_kmem_fault(struct vm_area_struct *vma, struct vm_fault *vmf) { struct drm_gem_object *obj = vma->vm_private_data; struct drm_device *dev = obj->dev; struct drm_kgsl_gem_object *priv; unsigned long offset; struct page *page; int i; mutex_lock(&dev->struct_mutex); priv = obj->driver_private; offset = (unsigned long) vmf->virtual_address - vma->vm_start; i = offset >> PAGE_SHIFT; page = sg_page(&(priv->memdesc.sg[i])); if (!page) { mutex_unlock(&dev->struct_mutex); return VM_FAULT_SIGBUS; } get_page(page); vmf->page = page; mutex_unlock(&dev->struct_mutex); return 0; } int kgsl_gem_phys_fault(struct vm_area_struct *vma, struct vm_fault *vmf) { struct drm_gem_object *obj = vma->vm_private_data; struct drm_device *dev = obj->dev; struct drm_kgsl_gem_object *priv; unsigned long offset, pfn; int ret = 0; offset = ((unsigned long) vmf->virtual_address - vma->vm_start) >> PAGE_SHIFT; mutex_lock(&dev->struct_mutex); priv = obj->driver_private; pfn = (priv->memdesc.physaddr >> PAGE_SHIFT) + offset; ret = vm_insert_pfn(vma, (unsigned long) vmf->virtual_address, pfn); mutex_unlock(&dev->struct_mutex); switch (ret) { case -ENOMEM: case -EAGAIN: return VM_FAULT_OOM; case -EFAULT: return VM_FAULT_SIGBUS; default: return VM_FAULT_NOPAGE; } } void cleanup_fence(struct drm_kgsl_gem_object_fence *fence, int check_waiting) { int j; struct drm_kgsl_gem_object_fence_list_entry *this_fence_entry = NULL; struct drm_kgsl_gem_object *unlock_obj; struct drm_gem_object *obj; struct drm_kgsl_gem_object_wait_list_entry *lock_next; fence->ts_valid = 0; fence->timestamp = -1; fence->ts_device = -1; /* Walk the list of buffers in this fence and clean up the */ /* references. Note that this can cause memory allocations */ /* to be freed */ for (j = fence->num_buffers; j > 0; j--) { this_fence_entry = (struct drm_kgsl_gem_object_fence_list_entry *) fence->buffers_in_fence.prev; this_fence_entry->in_use = 0; obj = this_fence_entry->gem_obj; unlock_obj = obj->driver_private; /* Delete it from the list */ list_del(&this_fence_entry->list); /* we are unlocking - see if there are other pids waiting */ if (check_waiting) { if (!list_empty(&unlock_obj->wait_list)) { lock_next = (struct drm_kgsl_gem_object_wait_list_entry *) unlock_obj->wait_list.prev; list_del((struct list_head *)&lock_next->list); unlock_obj->lockpid = 0; wake_up_interruptible( &lock_next->process_wait_q); lock_next->pid = 0; } else { /* List is empty so set pid to 0 */ unlock_obj->lockpid = 0; } } drm_gem_object_unreference(obj); } /* here all the buffers in the fence are released */ /* clear the fence entry */ fence->fence_id = ENTRY_EMPTY; } int find_empty_fence(void) { int i; for (i = 0; i < DRM_KGSL_NUM_FENCE_ENTRIES; i++) { if (gem_buf_fence[i].fence_id == ENTRY_EMPTY) { gem_buf_fence[i].fence_id = fence_id++; gem_buf_fence[i].ts_valid = 0; INIT_LIST_HEAD(&(gem_buf_fence[i].buffers_in_fence)); if (fence_id == 0xFFFFFFF0) fence_id = 1; return i; } else { /* Look for entries to be cleaned up */ if (gem_buf_fence[i].fence_id == ENTRY_NEEDS_CLEANUP) cleanup_fence(&gem_buf_fence[i], 0); } } return ENTRY_EMPTY; } int find_fence(int index) { int i; for (i = 0; i < DRM_KGSL_NUM_FENCE_ENTRIES; i++) { if (gem_buf_fence[i].fence_id == index) return i; } return ENTRY_EMPTY; } void wakeup_fence_entries(struct drm_kgsl_gem_object_fence *fence) { struct drm_kgsl_gem_object_fence_list_entry *this_fence_entry = NULL; struct drm_kgsl_gem_object_wait_list_entry *lock_next; struct drm_kgsl_gem_object *unlock_obj; struct drm_gem_object *obj; /* TS has expired when we get here */ fence->ts_valid = 0; fence->timestamp = -1; fence->ts_device = -1; list_for_each_entry(this_fence_entry, &fence->buffers_in_fence, list) { obj = this_fence_entry->gem_obj; unlock_obj = obj->driver_private; if (!list_empty(&unlock_obj->wait_list)) { lock_next = (struct drm_kgsl_gem_object_wait_list_entry *) unlock_obj->wait_list.prev; /* Unblock the pid */ lock_next->pid = 0; /* Delete it from the list */ list_del((struct list_head *)&lock_next->list); unlock_obj->lockpid = 0; wake_up_interruptible(&lock_next->process_wait_q); } else { /* List is empty so set pid to 0 */ unlock_obj->lockpid = 0; } } fence->fence_id = ENTRY_NEEDS_CLEANUP; /* Mark it as needing cleanup */ } int kgsl_gem_lock_handle_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { /* The purpose of this function is to lock a given set of handles. */ /* The driver will maintain a list of locked handles. */ /* If a request comes in for a handle that's locked the thread will */ /* block until it's no longer in use. */ struct drm_kgsl_gem_lock_handles *args = data; struct drm_gem_object *obj; struct drm_kgsl_gem_object *priv; struct drm_kgsl_gem_object_fence_list_entry *this_fence_entry = NULL; struct drm_kgsl_gem_object_fence *fence; struct drm_kgsl_gem_object_wait_list_entry *lock_item; int i, j; int result = 0; uint32_t *lock_list; uint32_t *work_list = NULL; int32_t fence_index; /* copy in the data from user space */ lock_list = kzalloc(sizeof(uint32_t) * args->num_handles, GFP_KERNEL); if (!lock_list) { DRM_ERROR("Unable allocate memory for lock list\n"); result = -ENOMEM; goto error; } if (copy_from_user(lock_list, args->handle_list, sizeof(uint32_t) * args->num_handles)) { DRM_ERROR("Unable to copy the lock list from the user\n"); result = -EFAULT; goto free_handle_list; } work_list = lock_list; mutex_lock(&dev->struct_mutex); /* build the fence for this group of handles */ fence_index = find_empty_fence(); if (fence_index == ENTRY_EMPTY) { DRM_ERROR("Unable to find a empty fence\n"); args->lock_id = 0xDEADBEEF; result = -EFAULT; goto out_unlock; } fence = &gem_buf_fence[fence_index]; gem_buf_fence[fence_index].num_buffers = args->num_handles; args->lock_id = gem_buf_fence[fence_index].fence_id; for (j = args->num_handles; j > 0; j--, lock_list++) { obj = drm_gem_object_lookup(dev, file_priv, *lock_list); if (obj == NULL) { DRM_ERROR("Invalid GEM handle %x\n", *lock_list); result = -EBADF; goto out_unlock; } priv = obj->driver_private; this_fence_entry = NULL; /* get a fence entry to hook into the fence */ for (i = 0; i < DRM_KGSL_NUM_FENCE_ENTRIES; i++) { if (!priv->fence_entries[i].in_use) { this_fence_entry = &priv->fence_entries[i]; this_fence_entry->in_use = 1; break; } } if (this_fence_entry == NULL) { fence->num_buffers = 0; fence->fence_id = ENTRY_EMPTY; args->lock_id = 0xDEADBEAD; result = -EFAULT; drm_gem_object_unreference(obj); goto out_unlock; } /* We're trying to lock - add to a fence */ list_add((struct list_head *)this_fence_entry, &gem_buf_fence[fence_index].buffers_in_fence); if (priv->lockpid) { if (priv->lockpid == args->pid) { /* now that things are running async this */ /* happens when an op isn't done */ /* so it's already locked by the calling pid */ continue; } /* if a pid already had it locked */ /* create and add to wait list */ for (i = 0; i < DRM_KGSL_HANDLE_WAIT_ENTRIES; i++) { if (priv->wait_entries[i].in_use == 0) { /* this one is empty */ lock_item = &priv->wait_entries[i]; lock_item->in_use = 1; lock_item->pid = args->pid; INIT_LIST_HEAD((struct list_head *) &priv->wait_entries[i]); break; } } if (i == DRM_KGSL_HANDLE_WAIT_ENTRIES) { result = -EFAULT; drm_gem_object_unreference(obj); goto out_unlock; } list_add_tail((struct list_head *)&lock_item->list, &priv->wait_list); mutex_unlock(&dev->struct_mutex); /* here we need to block */ wait_event_interruptible_timeout( priv->wait_entries[i].process_wait_q, (priv->lockpid == 0), msecs_to_jiffies(64)); mutex_lock(&dev->struct_mutex); lock_item->in_use = 0; } /* Getting here means no one currently holds the lock */ priv->lockpid = args->pid; args->lock_id = gem_buf_fence[fence_index].fence_id; } fence->lockpid = args->pid; out_unlock: mutex_unlock(&dev->struct_mutex); free_handle_list: kfree(work_list); error: return result; } int kgsl_gem_unlock_handle_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_kgsl_gem_unlock_handles *args = data; int result = 0; int32_t fence_index; mutex_lock(&dev->struct_mutex); fence_index = find_fence(args->lock_id); if (fence_index == ENTRY_EMPTY) { DRM_ERROR("Invalid lock ID: %x\n", args->lock_id); result = -EFAULT; goto out_unlock; } cleanup_fence(&gem_buf_fence[fence_index], 1); out_unlock: mutex_unlock(&dev->struct_mutex); return result; } int kgsl_gem_unlock_on_ts_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_kgsl_gem_unlock_on_ts *args = data; int result = 0; int ts_done = 0; int32_t fence_index, ts_device; struct drm_kgsl_gem_object_fence *fence; struct kgsl_device *device; if (args->type == DRM_KGSL_GEM_TS_3D) ts_device = KGSL_DEVICE_3D0; else if (args->type == DRM_KGSL_GEM_TS_2D) ts_device = KGSL_DEVICE_2D0; else { result = -EINVAL; goto error; } device = kgsl_get_device(ts_device); ts_done = kgsl_check_timestamp(device, NULL, args->timestamp); mutex_lock(&dev->struct_mutex); fence_index = find_fence(args->lock_id); if (fence_index == ENTRY_EMPTY) { DRM_ERROR("Invalid lock ID: %x\n", args->lock_id); result = -EFAULT; goto out_unlock; } fence = &gem_buf_fence[fence_index]; fence->ts_device = ts_device; if (!ts_done) fence->ts_valid = 1; else cleanup_fence(fence, 1); out_unlock: mutex_unlock(&dev->struct_mutex); error: return result; } struct drm_ioctl_desc kgsl_drm_ioctls[] = { DRM_IOCTL_DEF_DRV(KGSL_GEM_CREATE, kgsl_gem_create_ioctl, 0), DRM_IOCTL_DEF_DRV(KGSL_GEM_PREP, kgsl_gem_prep_ioctl, 0), DRM_IOCTL_DEF_DRV(KGSL_GEM_SETMEMTYPE, kgsl_gem_setmemtype_ioctl, 0), DRM_IOCTL_DEF_DRV(KGSL_GEM_GETMEMTYPE, kgsl_gem_getmemtype_ioctl, 0), DRM_IOCTL_DEF_DRV(KGSL_GEM_BIND_GPU, kgsl_gem_bind_gpu_ioctl, 0), DRM_IOCTL_DEF_DRV(KGSL_GEM_UNBIND_GPU, kgsl_gem_unbind_gpu_ioctl, 0), DRM_IOCTL_DEF_DRV(KGSL_GEM_ALLOC, kgsl_gem_alloc_ioctl, 0), DRM_IOCTL_DEF_DRV(KGSL_GEM_MMAP, kgsl_gem_mmap_ioctl, 0), DRM_IOCTL_DEF_DRV(KGSL_GEM_GET_BUFINFO, kgsl_gem_get_bufinfo_ioctl, 0), DRM_IOCTL_DEF_DRV(KGSL_GEM_GET_ION_FD, kgsl_gem_get_ion_fd_ioctl, 0), DRM_IOCTL_DEF_DRV(KGSL_GEM_CREATE_FROM_ION, kgsl_gem_create_from_ion_ioctl, 0), DRM_IOCTL_DEF_DRV(KGSL_GEM_SET_BUFCOUNT, kgsl_gem_set_bufcount_ioctl, 0), DRM_IOCTL_DEF_DRV(KGSL_GEM_GET_BUFCOUNT, kgsl_gem_get_bufcount_ioctl, 0), DRM_IOCTL_DEF_DRV(KGSL_GEM_SET_GLOCK_HANDLES_INFO, kgsl_gem_set_glock_handles_ioctl, 0), DRM_IOCTL_DEF_DRV(KGSL_GEM_GET_GLOCK_HANDLES_INFO, kgsl_gem_get_glock_handles_ioctl, 0), DRM_IOCTL_DEF_DRV(KGSL_GEM_SET_ACTIVE, kgsl_gem_set_active_ioctl, 0), DRM_IOCTL_DEF_DRV(KGSL_GEM_LOCK_HANDLE, kgsl_gem_lock_handle_ioctl, 0), DRM_IOCTL_DEF_DRV(KGSL_GEM_UNLOCK_HANDLE, kgsl_gem_unlock_handle_ioctl, 0), DRM_IOCTL_DEF_DRV(KGSL_GEM_UNLOCK_ON_TS, kgsl_gem_unlock_on_ts_ioctl, 0), DRM_IOCTL_DEF_DRV(KGSL_GEM_CREATE_FD, kgsl_gem_create_fd_ioctl, DRM_MASTER), }; static const struct file_operations kgsl_drm_driver_fops = { .owner = THIS_MODULE, .open = drm_open, .release = drm_release, .unlocked_ioctl = drm_ioctl, .mmap = drm_gem_mmap, .poll = drm_poll, .fasync = drm_fasync, }; static struct drm_driver driver = { .driver_features = DRIVER_GEM, .gem_init_object = kgsl_gem_init_object, .gem_free_object = kgsl_gem_free_object, .ioctls = kgsl_drm_ioctls, .fops = &kgsl_drm_driver_fops, .name = DRIVER_NAME, .desc = DRIVER_DESC, .date = DRIVER_DATE, .major = DRIVER_MAJOR, .minor = DRIVER_MINOR, .patchlevel = DRIVER_PATCHLEVEL, }; int kgsl_drm_init(struct platform_device *dev) { int i; /* Only initialize once */ if (kgsl_drm_inited == DRM_KGSL_INITED) return 0; kgsl_drm_inited = DRM_KGSL_INITED; driver.num_ioctls = DRM_ARRAY_SIZE(kgsl_drm_ioctls); INIT_LIST_HEAD(&kgsl_mem_list); for (i = 0; i < DRM_KGSL_NUM_FENCE_ENTRIES; i++) { gem_buf_fence[i].num_buffers = 0; gem_buf_fence[i].ts_valid = 0; gem_buf_fence[i].fence_id = ENTRY_EMPTY; } /* Create ION Client */ kgsl_drm_ion_client = msm_ion_client_create( 0xffffffff, "kgsl_drm"); if (!kgsl_drm_ion_client) { DRM_ERROR("Unable to create ION client\n"); return -ENOMEM; } return drm_platform_init(&driver, dev); } void kgsl_drm_exit(void) { kgsl_drm_inited = DRM_KGSL_NOT_INITED; if (kgsl_drm_ion_client) ion_client_destroy(kgsl_drm_ion_client); kgsl_drm_ion_client = NULL; drm_platform_exit(&driver, driver.kdriver.platform_device); }