| /* |
| * Copyright (c) 2008-2009 QUALCOMM USA, INC. |
| * |
| * All source code in this file is licensed under the following license |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, you can find it at http://www.fsf.org |
| */ |
| #include <linux/miscdevice.h> |
| #include <linux/platform_device.h> |
| #include <linux/slab.h> |
| #include <linux/fb.h> |
| #include <linux/file.h> |
| #include <linux/fs.h> |
| #include <linux/interrupt.h> |
| #include <linux/clk.h> |
| #include <linux/uaccess.h> |
| #include <linux/init.h> |
| #include <linux/list.h> |
| #include <linux/io.h> |
| #include <linux/mm.h> |
| #include <linux/android_pmem.h> |
| #include <linux/highmem.h> |
| #include <linux/vmalloc.h> |
| #include <asm/cacheflush.h> |
| |
| #include <asm/atomic.h> |
| |
| #include "kgsl.h" |
| #include "kgsl_drawctxt.h" |
| #include "kgsl_ringbuffer.h" |
| #include "kgsl_cmdstream.h" |
| #include "kgsl_log.h" |
| |
| struct kgsl_file_private { |
| struct list_head list; |
| struct list_head mem_list; |
| uint32_t ctxt_id_mask; |
| struct kgsl_pagetable *pagetable; |
| unsigned long vmalloc_size; |
| }; |
| |
| static void kgsl_put_phys_file(struct file *file); |
| |
| #ifdef CONFIG_MSM_KGSL_MMU |
| static long flush_l1_cache_range(unsigned long addr, int size) |
| { |
| struct page *page; |
| pte_t *pte_ptr; |
| unsigned long end; |
| |
| for (end = addr; end < (addr + size); end += KGSL_PAGESIZE) { |
| pte_ptr = kgsl_get_pte_from_vaddr(end); |
| if (!pte_ptr) |
| return -EINVAL; |
| |
| page = pte_page(pte_val(*pte_ptr)); |
| if (!page) { |
| KGSL_DRV_ERR("could not find page for pte\n"); |
| pte_unmap(pte_ptr); |
| return -EINVAL; |
| } |
| |
| pte_unmap(pte_ptr); |
| flush_dcache_page(page); |
| } |
| |
| return 0; |
| } |
| |
| static long flush_l1_cache_all(struct kgsl_file_private *private) |
| { |
| int result = 0; |
| struct kgsl_mem_entry *entry = NULL; |
| |
| kgsl_yamato_runpending(&kgsl_driver.yamato_device); |
| list_for_each_entry(entry, &private->mem_list, list) { |
| if (KGSL_MEMFLAGS_MEM_REQUIRES_FLUSH & entry->memdesc.priv) { |
| result = |
| flush_l1_cache_range((unsigned long)entry-> |
| memdesc.hostptr, |
| entry->memdesc.size); |
| if (result) |
| goto done; |
| } |
| } |
| done: |
| return result; |
| } |
| #else |
| static inline long flush_l1_cache_range(unsigned long addr, int size) |
| { return 0; } |
| |
| static inline long flush_l1_cache_all(struct kgsl_file_private *private) |
| { return 0; } |
| #endif |
| |
| /*this is used for logging, so that we can call the dev_printk |
| functions without export struct kgsl_driver everywhere*/ |
| struct device *kgsl_driver_getdevnode(void) |
| { |
| BUG_ON(kgsl_driver.pdev == NULL); |
| return &kgsl_driver.pdev->dev; |
| } |
| |
| /* the hw and clk enable/disable funcs must be either called from softirq or |
| * with mutex held */ |
| static void kgsl_clk_enable(void) |
| { |
| clk_set_rate(kgsl_driver.ebi1_clk, 128000000); |
| clk_enable(kgsl_driver.imem_clk); |
| if (kgsl_driver.grp_pclk) |
| clk_enable(kgsl_driver.grp_pclk); |
| clk_enable(kgsl_driver.grp_clk); |
| } |
| |
| static void kgsl_clk_disable(void) |
| { |
| clk_disable(kgsl_driver.grp_clk); |
| if (kgsl_driver.grp_pclk) |
| clk_disable(kgsl_driver.grp_pclk); |
| clk_disable(kgsl_driver.imem_clk); |
| clk_set_rate(kgsl_driver.ebi1_clk, 0); |
| } |
| |
| static void kgsl_hw_disable(void) |
| { |
| kgsl_driver.active = false; |
| disable_irq(kgsl_driver.interrupt_num); |
| kgsl_clk_disable(); |
| pr_debug("kgsl: hw disabled\n"); |
| wake_unlock(&kgsl_driver.wake_lock); |
| } |
| |
| static void kgsl_hw_enable(void) |
| { |
| wake_lock(&kgsl_driver.wake_lock); |
| kgsl_clk_enable(); |
| enable_irq(kgsl_driver.interrupt_num); |
| kgsl_driver.active = true; |
| pr_debug("kgsl: hw enabled\n"); |
| } |
| |
| static void kgsl_hw_get_locked(void) |
| { |
| /* active_cnt is protected by driver mutex */ |
| if (kgsl_driver.active_cnt++ == 0) { |
| if (kgsl_driver.active) { |
| del_timer_sync(&kgsl_driver.standby_timer); |
| barrier(); |
| } |
| if (!kgsl_driver.active) |
| kgsl_hw_enable(); |
| } |
| } |
| |
| static void kgsl_hw_put_locked(bool start_timer) |
| { |
| if ((--kgsl_driver.active_cnt == 0) && start_timer) { |
| mod_timer(&kgsl_driver.standby_timer, |
| jiffies + msecs_to_jiffies(20)); |
| } |
| } |
| |
| static void kgsl_do_standby_timer(unsigned long data) |
| { |
| if (kgsl_yamato_is_idle(&kgsl_driver.yamato_device)) |
| kgsl_hw_disable(); |
| else |
| mod_timer(&kgsl_driver.standby_timer, |
| jiffies + msecs_to_jiffies(10)); |
| } |
| |
| /* file operations */ |
| static int kgsl_first_open_locked(void) |
| { |
| int result = 0; |
| |
| BUG_ON(kgsl_driver.active); |
| BUG_ON(kgsl_driver.active_cnt); |
| |
| kgsl_clk_enable(); |
| |
| /* init memory apertures */ |
| result = kgsl_sharedmem_init(&kgsl_driver.shmem); |
| if (result != 0) |
| goto done; |
| |
| /* init devices */ |
| result = kgsl_yamato_init(&kgsl_driver.yamato_device, |
| &kgsl_driver.yamato_config); |
| if (result != 0) |
| goto done; |
| |
| result = kgsl_yamato_start(&kgsl_driver.yamato_device, 0); |
| if (result != 0) |
| goto done; |
| |
| done: |
| kgsl_clk_disable(); |
| return result; |
| } |
| |
| static int kgsl_last_release_locked(void) |
| { |
| BUG_ON(kgsl_driver.active_cnt); |
| |
| disable_irq(kgsl_driver.interrupt_num); |
| |
| kgsl_yamato_stop(&kgsl_driver.yamato_device); |
| |
| /* close devices */ |
| kgsl_yamato_close(&kgsl_driver.yamato_device); |
| |
| /* shutdown memory apertures */ |
| kgsl_sharedmem_close(&kgsl_driver.shmem); |
| |
| kgsl_clk_disable(); |
| kgsl_driver.active = false; |
| wake_unlock(&kgsl_driver.wake_lock); |
| |
| return 0; |
| } |
| |
| static int kgsl_release(struct inode *inodep, struct file *filep) |
| { |
| int result = 0; |
| unsigned int i; |
| struct kgsl_mem_entry *entry, *entry_tmp; |
| struct kgsl_file_private *private = NULL; |
| |
| mutex_lock(&kgsl_driver.mutex); |
| |
| private = filep->private_data; |
| BUG_ON(private == NULL); |
| filep->private_data = NULL; |
| list_del(&private->list); |
| |
| kgsl_hw_get_locked(); |
| |
| for (i = 0; i < KGSL_CONTEXT_MAX; i++) |
| if (private->ctxt_id_mask & (1 << i)) |
| kgsl_drawctxt_destroy(&kgsl_driver.yamato_device, i); |
| |
| list_for_each_entry_safe(entry, entry_tmp, &private->mem_list, list) |
| kgsl_remove_mem_entry(entry); |
| |
| if (private->pagetable != NULL) { |
| kgsl_yamato_cleanup_pt(&kgsl_driver.yamato_device, |
| private->pagetable); |
| kgsl_mmu_destroypagetableobject(private->pagetable); |
| private->pagetable = NULL; |
| } |
| |
| kfree(private); |
| |
| if (atomic_dec_return(&kgsl_driver.open_count) == 0) { |
| KGSL_DRV_VDBG("last_release\n"); |
| kgsl_hw_put_locked(false); |
| result = kgsl_last_release_locked(); |
| } else |
| kgsl_hw_put_locked(true); |
| |
| mutex_unlock(&kgsl_driver.mutex); |
| |
| return result; |
| } |
| |
| static int kgsl_open(struct inode *inodep, struct file *filep) |
| { |
| int result = 0; |
| struct kgsl_file_private *private = NULL; |
| |
| KGSL_DRV_DBG("file %p pid %d\n", filep, task_pid_nr(current)); |
| |
| |
| if (filep->f_flags & O_EXCL) { |
| KGSL_DRV_ERR("O_EXCL not allowed\n"); |
| return -EBUSY; |
| } |
| |
| private = kzalloc(sizeof(*private), GFP_KERNEL); |
| if (private == NULL) { |
| KGSL_DRV_ERR("cannot allocate file private data\n"); |
| return -ENOMEM; |
| } |
| |
| mutex_lock(&kgsl_driver.mutex); |
| |
| private->ctxt_id_mask = 0; |
| INIT_LIST_HEAD(&private->mem_list); |
| |
| filep->private_data = private; |
| |
| list_add(&private->list, &kgsl_driver.client_list); |
| |
| if (atomic_inc_return(&kgsl_driver.open_count) == 1) { |
| result = kgsl_first_open_locked(); |
| if (result != 0) |
| goto done; |
| } |
| |
| kgsl_hw_get_locked(); |
| |
| /*NOTE: this must happen after first_open */ |
| private->pagetable = |
| kgsl_mmu_createpagetableobject(&kgsl_driver.yamato_device.mmu); |
| if (private->pagetable == NULL) { |
| result = -ENOMEM; |
| goto done; |
| } |
| result = kgsl_yamato_setup_pt(&kgsl_driver.yamato_device, |
| private->pagetable); |
| if (result) { |
| kgsl_mmu_destroypagetableobject(private->pagetable); |
| private->pagetable = NULL; |
| goto done; |
| } |
| private->vmalloc_size = 0; |
| done: |
| kgsl_hw_put_locked(true); |
| mutex_unlock(&kgsl_driver.mutex); |
| if (result != 0) |
| kgsl_release(inodep, filep); |
| return result; |
| } |
| |
| |
| /*call with driver locked */ |
| static struct kgsl_mem_entry * |
| kgsl_sharedmem_find(struct kgsl_file_private *private, unsigned int gpuaddr) |
| { |
| struct kgsl_mem_entry *entry = NULL, *result = NULL; |
| |
| BUG_ON(private == NULL); |
| |
| list_for_each_entry(entry, &private->mem_list, list) { |
| if (entry->memdesc.gpuaddr == gpuaddr) { |
| result = entry; |
| break; |
| } |
| } |
| return result; |
| } |
| |
| /*call with driver locked */ |
| struct kgsl_mem_entry * |
| kgsl_sharedmem_find_region(struct kgsl_file_private *private, |
| unsigned int gpuaddr, |
| size_t size) |
| { |
| struct kgsl_mem_entry *entry = NULL, *result = NULL; |
| |
| BUG_ON(private == NULL); |
| |
| list_for_each_entry(entry, &private->mem_list, list) { |
| if (gpuaddr >= entry->memdesc.gpuaddr && |
| ((gpuaddr + size) <= |
| (entry->memdesc.gpuaddr + entry->memdesc.size))) { |
| result = entry; |
| break; |
| } |
| } |
| |
| return result; |
| } |
| |
| /*call all ioctl sub functions with driver locked*/ |
| |
| static long kgsl_ioctl_device_getproperty(struct kgsl_file_private *private, |
| void __user *arg) |
| { |
| int result = 0; |
| struct kgsl_device_getproperty param; |
| |
| if (copy_from_user(¶m, arg, sizeof(param))) { |
| result = -EFAULT; |
| goto done; |
| } |
| result = kgsl_yamato_getproperty(&kgsl_driver.yamato_device, |
| param.type, |
| param.value, param.sizebytes); |
| done: |
| return result; |
| } |
| |
| static long kgsl_ioctl_device_regread(struct kgsl_file_private *private, |
| void __user *arg) |
| { |
| int result = 0; |
| struct kgsl_device_regread param; |
| |
| if (copy_from_user(¶m, arg, sizeof(param))) { |
| result = -EFAULT; |
| goto done; |
| } |
| result = kgsl_yamato_regread(&kgsl_driver.yamato_device, |
| param.offsetwords, ¶m.value); |
| if (result != 0) |
| goto done; |
| |
| if (copy_to_user(arg, ¶m, sizeof(param))) { |
| result = -EFAULT; |
| goto done; |
| } |
| done: |
| return result; |
| } |
| |
| |
| static long kgsl_ioctl_device_waittimestamp(struct kgsl_file_private *private, |
| void __user *arg) |
| { |
| int result = 0; |
| struct kgsl_device_waittimestamp param; |
| |
| if (copy_from_user(¶m, arg, sizeof(param))) { |
| result = -EFAULT; |
| goto done; |
| } |
| |
| /* Don't wait forever, set a max value for now */ |
| if (param.timeout == -1) |
| param.timeout = 10 * MSEC_PER_SEC; |
| result = kgsl_yamato_waittimestamp(&kgsl_driver.yamato_device, |
| param.timestamp, |
| param.timeout); |
| |
| kgsl_yamato_runpending(&kgsl_driver.yamato_device); |
| done: |
| return result; |
| } |
| |
| static long kgsl_ioctl_rb_issueibcmds(struct kgsl_file_private *private, |
| void __user *arg) |
| { |
| int result = 0; |
| struct kgsl_ringbuffer_issueibcmds param; |
| |
| if (copy_from_user(¶m, arg, sizeof(param))) { |
| result = -EFAULT; |
| goto done; |
| } |
| |
| if (param.drawctxt_id >= KGSL_CONTEXT_MAX |
| || (private->ctxt_id_mask & 1 << param.drawctxt_id) == 0) { |
| result = -EINVAL; |
| KGSL_DRV_ERR("invalid drawctxt drawctxt_id %d\n", |
| param.drawctxt_id); |
| result = -EINVAL; |
| goto done; |
| } |
| |
| if (kgsl_sharedmem_find_region(private, param.ibaddr, |
| param.sizedwords*sizeof(uint32_t)) == NULL) { |
| KGSL_DRV_ERR("invalid cmd buffer ibaddr %08x sizedwords %d\n", |
| param.ibaddr, param.sizedwords); |
| result = -EINVAL; |
| goto done; |
| |
| } |
| |
| result = kgsl_ringbuffer_issueibcmds(&kgsl_driver.yamato_device, |
| param.drawctxt_id, |
| param.ibaddr, |
| param.sizedwords, |
| ¶m.timestamp, |
| param.flags); |
| if (result != 0) |
| goto done; |
| |
| if (copy_to_user(arg, ¶m, sizeof(param))) { |
| result = -EFAULT; |
| goto done; |
| } |
| done: |
| return result; |
| } |
| |
| static long kgsl_ioctl_cmdstream_readtimestamp(struct kgsl_file_private |
| *private, void __user *arg) |
| { |
| int result = 0; |
| struct kgsl_cmdstream_readtimestamp param; |
| |
| if (copy_from_user(¶m, arg, sizeof(param))) { |
| result = -EFAULT; |
| goto done; |
| } |
| |
| param.timestamp = |
| kgsl_cmdstream_readtimestamp(&kgsl_driver.yamato_device, |
| param.type); |
| if (result != 0) |
| goto done; |
| |
| if (copy_to_user(arg, ¶m, sizeof(param))) { |
| result = -EFAULT; |
| goto done; |
| } |
| done: |
| return result; |
| } |
| |
| static long kgsl_ioctl_cmdstream_freememontimestamp(struct kgsl_file_private |
| *private, void __user *arg) |
| { |
| int result = 0; |
| struct kgsl_cmdstream_freememontimestamp param; |
| struct kgsl_mem_entry *entry = NULL; |
| |
| if (copy_from_user(¶m, arg, sizeof(param))) { |
| result = -EFAULT; |
| goto done; |
| } |
| |
| entry = kgsl_sharedmem_find(private, param.gpuaddr); |
| if (entry == NULL) { |
| KGSL_DRV_ERR("invalid gpuaddr %08x\n", param.gpuaddr); |
| result = -EINVAL; |
| goto done; |
| } |
| |
| if (entry->memdesc.priv & KGSL_MEMFLAGS_VMALLOC_MEM) |
| entry->memdesc.priv &= ~KGSL_MEMFLAGS_MEM_REQUIRES_FLUSH; |
| |
| result = kgsl_cmdstream_freememontimestamp(&kgsl_driver.yamato_device, |
| entry, |
| param.timestamp, |
| param.type); |
| |
| kgsl_yamato_runpending(&kgsl_driver.yamato_device); |
| |
| done: |
| return result; |
| } |
| |
| static long kgsl_ioctl_drawctxt_create(struct kgsl_file_private *private, |
| void __user *arg) |
| { |
| int result = 0; |
| struct kgsl_drawctxt_create param; |
| |
| if (copy_from_user(¶m, arg, sizeof(param))) { |
| result = -EFAULT; |
| goto done; |
| } |
| |
| result = kgsl_drawctxt_create(&kgsl_driver.yamato_device, |
| private->pagetable, |
| param.flags, |
| ¶m.drawctxt_id); |
| if (result != 0) |
| goto done; |
| |
| if (copy_to_user(arg, ¶m, sizeof(param))) { |
| result = -EFAULT; |
| goto done; |
| } |
| |
| private->ctxt_id_mask |= 1 << param.drawctxt_id; |
| |
| done: |
| return result; |
| } |
| |
| static long kgsl_ioctl_drawctxt_destroy(struct kgsl_file_private *private, |
| void __user *arg) |
| { |
| int result = 0; |
| struct kgsl_drawctxt_destroy param; |
| |
| if (copy_from_user(¶m, arg, sizeof(param))) { |
| result = -EFAULT; |
| goto done; |
| } |
| |
| if (param.drawctxt_id >= KGSL_CONTEXT_MAX |
| || (private->ctxt_id_mask & 1 << param.drawctxt_id) == 0) { |
| result = -EINVAL; |
| goto done; |
| } |
| |
| result = kgsl_drawctxt_destroy(&kgsl_driver.yamato_device, |
| param.drawctxt_id); |
| if (result == 0) |
| private->ctxt_id_mask &= ~(1 << param.drawctxt_id); |
| |
| done: |
| return result; |
| } |
| |
| void kgsl_remove_mem_entry(struct kgsl_mem_entry *entry) |
| { |
| kgsl_mmu_unmap(entry->memdesc.pagetable, |
| entry->memdesc.gpuaddr & KGSL_PAGEMASK, |
| entry->memdesc.size); |
| if (KGSL_MEMFLAGS_VMALLOC_MEM & entry->memdesc.priv) { |
| vfree((void *)entry->memdesc.physaddr); |
| entry->priv->vmalloc_size -= entry->memdesc.size; |
| } else |
| kgsl_put_phys_file(entry->pmem_file); |
| list_del(&entry->list); |
| |
| if (entry->free_list.prev) |
| list_del(&entry->free_list); |
| |
| kfree(entry); |
| |
| } |
| |
| static long kgsl_ioctl_sharedmem_free(struct kgsl_file_private *private, |
| void __user *arg) |
| { |
| int result = 0; |
| struct kgsl_sharedmem_free param; |
| struct kgsl_mem_entry *entry = NULL; |
| |
| if (copy_from_user(¶m, arg, sizeof(param))) { |
| result = -EFAULT; |
| goto done; |
| } |
| |
| entry = kgsl_sharedmem_find(private, param.gpuaddr); |
| if (entry == NULL) { |
| KGSL_DRV_ERR("invalid gpuaddr %08x\n", param.gpuaddr); |
| result = -EINVAL; |
| goto done; |
| } |
| |
| kgsl_remove_mem_entry(entry); |
| done: |
| return result; |
| } |
| |
| #ifdef CONFIG_MSM_KGSL_MMU |
| static int kgsl_ioctl_sharedmem_from_vmalloc(struct kgsl_file_private *private, |
| void __user *arg) |
| { |
| int result = 0, len; |
| struct kgsl_sharedmem_from_vmalloc param; |
| struct kgsl_mem_entry *entry = NULL; |
| void *vmalloc_area; |
| struct vm_area_struct *vma; |
| |
| if (copy_from_user(¶m, arg, sizeof(param))) { |
| result = -EFAULT; |
| goto error; |
| } |
| |
| if (!param.hostptr) { |
| KGSL_DRV_ERR |
| ("Invalid host pointer of malloc passed: param.hostptr " |
| "%08x\n", param.hostptr); |
| result = -EINVAL; |
| goto error; |
| } |
| |
| vma = find_vma(current->mm, param.hostptr); |
| if (!vma) { |
| KGSL_MEM_ERR("Could not find vma for address %x\n", |
| param.hostptr); |
| result = -EINVAL; |
| goto error; |
| } |
| len = vma->vm_end - vma->vm_start; |
| if (vma->vm_pgoff || !IS_ALIGNED(len, PAGE_SIZE) |
| || !IS_ALIGNED(vma->vm_start, PAGE_SIZE)) { |
| KGSL_MEM_ERR |
| ("kgsl vmalloc mapping must be at offset 0 and page aligned\n"); |
| result = -EINVAL; |
| goto error; |
| } |
| if (vma->vm_start != param.hostptr) { |
| KGSL_MEM_ERR |
| ("vma start address is not equal to mmap address\n"); |
| result = -EINVAL; |
| goto error; |
| } |
| |
| if ((private->vmalloc_size + len) > KGSL_GRAPHICS_MEMORY_LOW_WATERMARK |
| && !param.force_no_low_watermark) { |
| result = -ENOMEM; |
| goto error; |
| } |
| |
| entry = kzalloc(sizeof(struct kgsl_mem_entry), GFP_KERNEL); |
| if (entry == NULL) { |
| result = -ENOMEM; |
| goto error; |
| } |
| |
| /* allocate memory and map it to user space */ |
| vmalloc_area = vmalloc_user(len); |
| if (!vmalloc_area) { |
| KGSL_MEM_ERR("vmalloc failed\n"); |
| result = -ENOMEM; |
| goto error_free_entry; |
| } |
| if (!kgsl_cache_enable) { |
| /* If we are going to map non-cached, make sure to flush the |
| * cache to ensure that previously cached data does not |
| * overwrite this memory */ |
| dmac_flush_range(vmalloc_area, vmalloc_area + len); |
| KGSL_MEM_INFO("Caching for memory allocation turned off\n"); |
| vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); |
| } else { |
| KGSL_MEM_INFO("Caching for memory allocation turned on\n"); |
| } |
| |
| result = remap_vmalloc_range(vma, vmalloc_area, 0); |
| if (result) { |
| KGSL_MEM_ERR("remap_vmalloc_range returned %d\n", result); |
| goto error_free_vmalloc; |
| } |
| |
| result = |
| kgsl_mmu_map(private->pagetable, (unsigned long)vmalloc_area, len, |
| GSL_PT_PAGE_RV | GSL_PT_PAGE_WV, |
| &entry->memdesc.gpuaddr, KGSL_MEMFLAGS_ALIGN4K); |
| |
| if (result != 0) |
| goto error_free_vmalloc; |
| |
| entry->memdesc.pagetable = private->pagetable; |
| entry->memdesc.size = len; |
| entry->memdesc.hostptr = (void *)param.hostptr; |
| entry->memdesc.priv = KGSL_MEMFLAGS_VMALLOC_MEM | |
| KGSL_MEMFLAGS_MEM_REQUIRES_FLUSH; |
| entry->memdesc.physaddr = (unsigned long)vmalloc_area; |
| entry->priv = private; |
| |
| param.gpuaddr = entry->memdesc.gpuaddr; |
| |
| if (copy_to_user(arg, ¶m, sizeof(param))) { |
| result = -EFAULT; |
| goto error_unmap_entry; |
| } |
| private->vmalloc_size += len; |
| list_add(&entry->list, &private->mem_list); |
| |
| return 0; |
| |
| error_unmap_entry: |
| kgsl_mmu_unmap(private->pagetable, entry->memdesc.gpuaddr, |
| entry->memdesc.size); |
| |
| error_free_vmalloc: |
| vfree(vmalloc_area); |
| |
| error_free_entry: |
| kfree(entry); |
| |
| error: |
| return result; |
| } |
| #else |
| static inline int kgsl_ioctl_sharedmem_from_vmalloc( |
| struct kgsl_file_private *private, void __user *arg) |
| { |
| return -ENOSYS; |
| } |
| #endif |
| |
| static int kgsl_get_phys_file(int fd, unsigned long *start, unsigned long *len, |
| struct file **filep) |
| { |
| struct file *fbfile; |
| int put_needed; |
| unsigned long vstart = 0; |
| int ret = 0; |
| dev_t rdev; |
| struct fb_info *info; |
| |
| *filep = NULL; |
| if (!get_pmem_file(fd, start, &vstart, len, filep)) |
| return 0; |
| |
| fbfile = fget_light(fd, &put_needed); |
| if (fbfile == NULL) |
| return -1; |
| |
| rdev = fbfile->f_dentry->d_inode->i_rdev; |
| info = MAJOR(rdev) == FB_MAJOR ? registered_fb[MINOR(rdev)] : NULL; |
| if (info) { |
| *start = info->fix.smem_start; |
| *len = info->fix.smem_len; |
| ret = 0; |
| } else |
| ret = -1; |
| fput_light(fbfile, put_needed); |
| |
| return ret; |
| } |
| |
| static void kgsl_put_phys_file(struct file *file) |
| { |
| KGSL_DRV_DBG("put phys file %p\n", file); |
| if (file) |
| put_pmem_file(file); |
| } |
| |
| static int kgsl_ioctl_sharedmem_from_pmem(struct kgsl_file_private *private, |
| void __user *arg) |
| { |
| int result = 0; |
| struct kgsl_sharedmem_from_pmem param; |
| struct kgsl_mem_entry *entry = NULL; |
| unsigned long start = 0, len = 0; |
| struct file *pmem_file = NULL; |
| |
| if (copy_from_user(¶m, arg, sizeof(param))) { |
| result = -EFAULT; |
| goto error; |
| } |
| |
| if (kgsl_get_phys_file(param.pmem_fd, &start, &len, &pmem_file)) { |
| result = -EINVAL; |
| goto error; |
| } else if (param.offset + param.len > len) { |
| KGSL_DRV_ERR("%s: region too large 0x%x + 0x%x >= 0x%lx\n", |
| __func__, param.offset, param.len, len); |
| result = -EINVAL; |
| goto error_put_pmem; |
| } |
| |
| KGSL_MEM_INFO("get phys file %p start 0x%lx len 0x%lx\n", |
| pmem_file, start, len); |
| KGSL_DRV_DBG("locked phys file %p\n", pmem_file); |
| |
| entry = kzalloc(sizeof(*entry), GFP_KERNEL); |
| if (entry == NULL) { |
| result = -ENOMEM; |
| goto error_put_pmem; |
| } |
| |
| entry->pmem_file = pmem_file; |
| |
| entry->memdesc.pagetable = private->pagetable; |
| |
| /* Any MMU mapped memory must have a length in multiple of PAGESIZE */ |
| entry->memdesc.size = ALIGN(param.len, PAGE_SIZE); |
| |
| /*we shouldn't need to write here from kernel mode */ |
| entry->memdesc.hostptr = NULL; |
| |
| /* ensure that MMU mappings are at page boundary */ |
| entry->memdesc.physaddr = start + (param.offset & KGSL_PAGEMASK); |
| result = kgsl_mmu_map(private->pagetable, entry->memdesc.physaddr, |
| entry->memdesc.size, GSL_PT_PAGE_RV | GSL_PT_PAGE_WV, |
| &entry->memdesc.gpuaddr, |
| KGSL_MEMFLAGS_ALIGN4K | KGSL_MEMFLAGS_CONPHYS); |
| if (result) |
| goto error_free_entry; |
| |
| /* If the offset is not at 4K boundary then add the correct offset |
| * value to gpuaddr */ |
| entry->memdesc.gpuaddr += (param.offset & ~KGSL_PAGEMASK); |
| param.gpuaddr = entry->memdesc.gpuaddr; |
| |
| if (copy_to_user(arg, ¶m, sizeof(param))) { |
| result = -EFAULT; |
| goto error_unmap_entry; |
| } |
| list_add(&entry->list, &private->mem_list); |
| return result; |
| |
| error_unmap_entry: |
| kgsl_mmu_unmap(entry->memdesc.pagetable, |
| entry->memdesc.gpuaddr & KGSL_PAGEMASK, |
| entry->memdesc.size); |
| error_free_entry: |
| kfree(entry); |
| |
| error_put_pmem: |
| kgsl_put_phys_file(pmem_file); |
| |
| error: |
| return result; |
| } |
| |
| #ifdef CONFIG_MSM_KGSL_MMU |
| /*This function flushes a graphics memory allocation from CPU cache |
| *when caching is enabled with MMU*/ |
| static int kgsl_ioctl_sharedmem_flush_cache(struct kgsl_file_private *private, |
| void __user *arg) |
| { |
| int result = 0; |
| struct kgsl_mem_entry *entry; |
| struct kgsl_sharedmem_free param; |
| |
| if (copy_from_user(¶m, arg, sizeof(param))) { |
| result = -EFAULT; |
| goto done; |
| } |
| |
| entry = kgsl_sharedmem_find(private, param.gpuaddr); |
| if (!entry) { |
| KGSL_DRV_ERR("invalid gpuaddr %08x\n", param.gpuaddr); |
| result = -EINVAL; |
| goto done; |
| } |
| result = flush_l1_cache_range((unsigned long)entry->memdesc.hostptr, |
| entry->memdesc.size); |
| /* Mark memory as being flushed so we don't flush it again */ |
| entry->memdesc.priv &= ~KGSL_MEMFLAGS_MEM_REQUIRES_FLUSH; |
| done: |
| return result; |
| } |
| #else |
| static int kgsl_ioctl_sharedmem_flush_cache(struct kgsl_file_private *private, |
| void __user *arg) |
| { |
| return -ENOSYS; |
| } |
| #endif |
| |
| |
| static long kgsl_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) |
| { |
| int result = 0; |
| struct kgsl_file_private *private = filep->private_data; |
| struct kgsl_drawctxt_set_bin_base_offset binbase; |
| |
| BUG_ON(private == NULL); |
| |
| KGSL_DRV_VDBG("filep %p cmd 0x%08x arg 0x%08lx\n", filep, cmd, arg); |
| |
| mutex_lock(&kgsl_driver.mutex); |
| |
| kgsl_hw_get_locked(); |
| |
| switch (cmd) { |
| |
| case IOCTL_KGSL_DEVICE_GETPROPERTY: |
| result = |
| kgsl_ioctl_device_getproperty(private, (void __user *)arg); |
| break; |
| |
| case IOCTL_KGSL_DEVICE_REGREAD: |
| result = kgsl_ioctl_device_regread(private, (void __user *)arg); |
| break; |
| |
| case IOCTL_KGSL_DEVICE_WAITTIMESTAMP: |
| result = kgsl_ioctl_device_waittimestamp(private, |
| (void __user *)arg); |
| break; |
| |
| case IOCTL_KGSL_RINGBUFFER_ISSUEIBCMDS: |
| if (kgsl_cache_enable) |
| flush_l1_cache_all(private); |
| result = kgsl_ioctl_rb_issueibcmds(private, (void __user *)arg); |
| break; |
| |
| case IOCTL_KGSL_CMDSTREAM_READTIMESTAMP: |
| result = |
| kgsl_ioctl_cmdstream_readtimestamp(private, |
| (void __user *)arg); |
| break; |
| |
| case IOCTL_KGSL_CMDSTREAM_FREEMEMONTIMESTAMP: |
| result = |
| kgsl_ioctl_cmdstream_freememontimestamp(private, |
| (void __user *)arg); |
| break; |
| |
| case IOCTL_KGSL_DRAWCTXT_CREATE: |
| result = kgsl_ioctl_drawctxt_create(private, |
| (void __user *)arg); |
| break; |
| |
| case IOCTL_KGSL_DRAWCTXT_DESTROY: |
| result = |
| kgsl_ioctl_drawctxt_destroy(private, (void __user *)arg); |
| break; |
| |
| case IOCTL_KGSL_SHAREDMEM_FREE: |
| result = kgsl_ioctl_sharedmem_free(private, (void __user *)arg); |
| break; |
| |
| case IOCTL_KGSL_SHAREDMEM_FROM_VMALLOC: |
| kgsl_yamato_runpending(&kgsl_driver.yamato_device); |
| result = kgsl_ioctl_sharedmem_from_vmalloc(private, |
| (void __user *)arg); |
| break; |
| |
| case IOCTL_KGSL_SHAREDMEM_FLUSH_CACHE: |
| if (kgsl_cache_enable) |
| result = kgsl_ioctl_sharedmem_flush_cache(private, |
| (void __user *)arg); |
| break; |
| case IOCTL_KGSL_SHAREDMEM_FROM_PMEM: |
| kgsl_yamato_runpending(&kgsl_driver.yamato_device); |
| result = kgsl_ioctl_sharedmem_from_pmem(private, |
| (void __user *)arg); |
| break; |
| |
| case IOCTL_KGSL_DRAWCTXT_SET_BIN_BASE_OFFSET: |
| if (copy_from_user(&binbase, (void __user *)arg, |
| sizeof(binbase))) { |
| result = -EFAULT; |
| break; |
| } |
| |
| if (private->ctxt_id_mask & (1 << binbase.drawctxt_id)) { |
| result = kgsl_drawctxt_set_bin_base_offset( |
| &kgsl_driver.yamato_device, |
| binbase.drawctxt_id, |
| binbase.offset); |
| } else { |
| result = -EINVAL; |
| KGSL_DRV_ERR("invalid drawctxt drawctxt_id %d\n", |
| binbase.drawctxt_id); |
| } |
| break; |
| |
| default: |
| KGSL_DRV_ERR("invalid ioctl code %08x\n", cmd); |
| result = -EINVAL; |
| break; |
| } |
| |
| kgsl_hw_put_locked(true); |
| mutex_unlock(&kgsl_driver.mutex); |
| KGSL_DRV_VDBG("result %d\n", result); |
| return result; |
| } |
| |
| static struct file_operations kgsl_fops = { |
| .owner = THIS_MODULE, |
| .release = kgsl_release, |
| .open = kgsl_open, |
| .unlocked_ioctl = kgsl_ioctl, |
| }; |
| |
| |
| struct kgsl_driver kgsl_driver = { |
| .misc = { |
| .name = DRIVER_NAME, |
| .minor = MISC_DYNAMIC_MINOR, |
| .fops = &kgsl_fops, |
| }, |
| .open_count = ATOMIC_INIT(0), |
| .mutex = __MUTEX_INITIALIZER(kgsl_driver.mutex), |
| }; |
| |
| static void kgsl_driver_cleanup(void) |
| { |
| |
| wake_lock_destroy(&kgsl_driver.wake_lock); |
| |
| if (kgsl_driver.interrupt_num > 0) { |
| if (kgsl_driver.have_irq) { |
| free_irq(kgsl_driver.interrupt_num, NULL); |
| kgsl_driver.have_irq = 0; |
| } |
| kgsl_driver.interrupt_num = 0; |
| } |
| |
| if (kgsl_driver.grp_clk) { |
| clk_put(kgsl_driver.grp_clk); |
| kgsl_driver.grp_clk = NULL; |
| } |
| |
| if (kgsl_driver.imem_clk != NULL) { |
| clk_put(kgsl_driver.imem_clk); |
| kgsl_driver.imem_clk = NULL; |
| } |
| |
| if (kgsl_driver.ebi1_clk != NULL) { |
| clk_put(kgsl_driver.ebi1_clk); |
| kgsl_driver.ebi1_clk = NULL; |
| } |
| |
| kgsl_driver.pdev = NULL; |
| |
| } |
| |
| |
| static int __devinit kgsl_platform_probe(struct platform_device *pdev) |
| { |
| int result = 0; |
| struct clk *clk; |
| struct resource *res = NULL; |
| |
| kgsl_debug_init(); |
| |
| INIT_LIST_HEAD(&kgsl_driver.client_list); |
| |
| /*acquire clocks */ |
| BUG_ON(kgsl_driver.grp_clk != NULL); |
| BUG_ON(kgsl_driver.imem_clk != NULL); |
| BUG_ON(kgsl_driver.ebi1_clk != NULL); |
| |
| kgsl_driver.pdev = pdev; |
| |
| setup_timer(&kgsl_driver.standby_timer, kgsl_do_standby_timer, 0); |
| wake_lock_init(&kgsl_driver.wake_lock, WAKE_LOCK_SUSPEND, "kgsl"); |
| |
| clk = clk_get(&pdev->dev, "grp_clk"); |
| if (IS_ERR(clk)) { |
| result = PTR_ERR(clk); |
| KGSL_DRV_ERR("clk_get(grp_clk) returned %d\n", result); |
| goto done; |
| } |
| kgsl_driver.grp_clk = clk; |
| |
| clk = clk_get(&pdev->dev, "grp_pclk"); |
| if (IS_ERR(clk)) { |
| KGSL_DRV_ERR("no grp_pclk, continuing\n"); |
| clk = NULL; |
| } |
| kgsl_driver.grp_pclk = clk; |
| |
| clk = clk_get(&pdev->dev, "imem_clk"); |
| if (IS_ERR(clk)) { |
| result = PTR_ERR(clk); |
| KGSL_DRV_ERR("clk_get(imem_clk) returned %d\n", result); |
| goto done; |
| } |
| kgsl_driver.imem_clk = clk; |
| |
| clk = clk_get(&pdev->dev, "ebi1_clk"); |
| if (IS_ERR(clk)) { |
| result = PTR_ERR(clk); |
| KGSL_DRV_ERR("clk_get(ebi1_clk) returned %d\n", result); |
| goto done; |
| } |
| kgsl_driver.ebi1_clk = clk; |
| |
| /*acquire interrupt */ |
| kgsl_driver.interrupt_num = platform_get_irq(pdev, 0); |
| if (kgsl_driver.interrupt_num <= 0) { |
| KGSL_DRV_ERR("platform_get_irq() returned %d\n", |
| kgsl_driver.interrupt_num); |
| result = -EINVAL; |
| goto done; |
| } |
| |
| result = request_irq(kgsl_driver.interrupt_num, kgsl_yamato_isr, |
| IRQF_TRIGGER_HIGH, DRIVER_NAME, NULL); |
| if (result) { |
| KGSL_DRV_ERR("request_irq(%d) returned %d\n", |
| kgsl_driver.interrupt_num, result); |
| goto done; |
| } |
| kgsl_driver.have_irq = 1; |
| disable_irq(kgsl_driver.interrupt_num); |
| |
| result = kgsl_yamato_config(&kgsl_driver.yamato_config, pdev); |
| if (result != 0) |
| goto done; |
| |
| res = platform_get_resource_byname(pdev, IORESOURCE_MEM, |
| "kgsl_phys_memory"); |
| if (res == NULL) { |
| result = -EINVAL; |
| goto done; |
| } |
| |
| kgsl_driver.shmem.physbase = res->start; |
| kgsl_driver.shmem.size = resource_size(res); |
| |
| done: |
| if (result) |
| kgsl_driver_cleanup(); |
| else |
| result = misc_register(&kgsl_driver.misc); |
| |
| return result; |
| } |
| |
| static int kgsl_platform_remove(struct platform_device *pdev) |
| { |
| |
| kgsl_driver_cleanup(); |
| misc_deregister(&kgsl_driver.misc); |
| |
| return 0; |
| } |
| |
| static int kgsl_platform_suspend(struct platform_device *pdev, |
| pm_message_t state) |
| { |
| mutex_lock(&kgsl_driver.mutex); |
| if (atomic_read(&kgsl_driver.open_count) > 0) { |
| if (kgsl_driver.active) |
| pr_err("%s: Suspending while active???\n", __func__); |
| } |
| mutex_unlock(&kgsl_driver.mutex); |
| return 0; |
| } |
| |
| static struct platform_driver kgsl_platform_driver = { |
| .probe = kgsl_platform_probe, |
| .remove = __devexit_p(kgsl_platform_remove), |
| .suspend = kgsl_platform_suspend, |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = DRIVER_NAME |
| } |
| }; |
| |
| static int __init kgsl_mod_init(void) |
| { |
| return platform_driver_register(&kgsl_platform_driver); |
| } |
| |
| static void __exit kgsl_mod_exit(void) |
| { |
| platform_driver_unregister(&kgsl_platform_driver); |
| } |
| |
| module_init(kgsl_mod_init); |
| module_exit(kgsl_mod_exit); |
| |
| MODULE_AUTHOR("QUALCOMM"); |
| MODULE_DESCRIPTION("3D graphics driver for QSD8x50 and MSM7x27"); |
| MODULE_VERSION("1.0"); |
| MODULE_LICENSE("Dual BSD/GPL"); |
| MODULE_ALIAS("platform:kgsl"); |