1279 lines
30 KiB
C
1279 lines
30 KiB
C
/*
|
|
* 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/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);
|
|
clk_enable(kgsl_driver.grp_clk);
|
|
#ifdef CONFIG_ARCH_MSM7227
|
|
clk_enable(kgsl_driver.grp_pclk);
|
|
#endif
|
|
}
|
|
|
|
static void kgsl_clk_disable(void)
|
|
{
|
|
#ifdef CONFIG_ARCH_MSM7227
|
|
clk_disable(kgsl_driver.grp_pclk);
|
|
#endif
|
|
clk_disable(kgsl_driver.grp_clk);
|
|
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(512));
|
|
}
|
|
}
|
|
|
|
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_noncached(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 int kgsl_mmap(struct file *file, struct vm_area_struct *vma)
|
|
{
|
|
int result;
|
|
struct kgsl_memdesc *memdesc = NULL;
|
|
unsigned long vma_size = vma->vm_end - vma->vm_start;
|
|
unsigned long vma_offset = vma->vm_pgoff << PAGE_SHIFT;
|
|
struct kgsl_device *device = NULL;
|
|
|
|
mutex_lock(&kgsl_driver.mutex);
|
|
|
|
device = &kgsl_driver.yamato_device;
|
|
|
|
/*allow yamato memstore to be mapped read only */
|
|
if (vma_offset == device->memstore.physaddr) {
|
|
if (vma->vm_flags & VM_WRITE) {
|
|
result = -EPERM;
|
|
goto done;
|
|
}
|
|
memdesc = &device->memstore;
|
|
}
|
|
|
|
if (memdesc->size != vma_size) {
|
|
KGSL_MEM_ERR("file %p bad size %ld, should be %d\n",
|
|
file, vma_size, memdesc->size);
|
|
result = -EINVAL;
|
|
goto done;
|
|
}
|
|
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
|
|
|
|
result = remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
|
|
vma_size, vma->vm_page_prot);
|
|
if (result != 0) {
|
|
KGSL_MEM_ERR("remap_pfn_range returned %d\n",
|
|
result);
|
|
goto done;
|
|
}
|
|
done:
|
|
mutex_unlock(&kgsl_driver.mutex);
|
|
return result;
|
|
}
|
|
|
|
static struct file_operations kgsl_fops = {
|
|
.owner = THIS_MODULE,
|
|
.release = kgsl_release,
|
|
.open = kgsl_open,
|
|
.mmap = kgsl_mmap,
|
|
.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);
|
|
#ifdef CONFIG_ARCH_MSM7227
|
|
BUG_ON(kgsl_driver.grp_pclk != NULL);
|
|
#endif
|
|
|
|
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, "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;
|
|
|
|
#ifdef CONFIG_ARCH_MSM7227
|
|
clk = clk_get(&pdev->dev, "grp_pclk");
|
|
if (IS_ERR(clk)) {
|
|
result = PTR_ERR(clk);
|
|
KGSL_DRV_ERR("clk_get(grp_pclk) returned %d\n", result);
|
|
goto done;
|
|
}
|
|
kgsl_driver.grp_pclk = clk;
|
|
#endif
|
|
/*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");
|