diff options
Diffstat (limited to 'drivers/gpu/arm/t6xx/kbase/src/linux')
10 files changed, 4417 insertions, 0 deletions
diff --git a/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_config_linux.c b/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_config_linux.c new file mode 100755 index 00000000000..667e16c1193 --- /dev/null +++ b/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_config_linux.c @@ -0,0 +1,44 @@ +/* + * + * (C) COPYRIGHT 2011-2012 ARM Limited. All rights reserved. + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * A copy of the licence is included with the program, and can also be obtained + * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + + +#include <linux/string.h> +#include <kbase/src/linux/mali_kbase_config_linux.h> +#include <kbase/mali_kbase_debug.h> + +#ifdef CONFIG_MALI_PLATFORM_FAKE + +void kbasep_config_parse_io_resources(const kbase_io_resources *io_resources, struct resource *const linux_resources) +{ + KBASE_DEBUG_ASSERT(io_resources != NULL); + KBASE_DEBUG_ASSERT(linux_resources != NULL); + + memset(linux_resources, 0, PLATFORM_CONFIG_RESOURCE_COUNT * sizeof(struct resource)); + + linux_resources[0].start = io_resources->io_memory_region.start; + linux_resources[0].end = io_resources->io_memory_region.end; + linux_resources[0].flags = IORESOURCE_MEM; + + linux_resources[1].start = linux_resources[1].end = io_resources->job_irq_number; + linux_resources[1].flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL; + + linux_resources[2].start = linux_resources[2].end = io_resources->mmu_irq_number; + linux_resources[2].flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL; + + linux_resources[3].start = linux_resources[3].end = io_resources->gpu_irq_number; + linux_resources[3].flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL; +} + +#endif /* CONFIG_MALI_PLATFORM_FAKE */ diff --git a/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_config_linux.h b/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_config_linux.h new file mode 100755 index 00000000000..2613a5a6d7c --- /dev/null +++ b/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_config_linux.h @@ -0,0 +1,41 @@ +/* + * + * (C) COPYRIGHT 2011-2012 ARM Limited. All rights reserved. + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * A copy of the licence is included with the program, and can also be obtained + * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + + + +#ifndef _KBASE_CONFIG_LINUX_H_ +#define _KBASE_CONFIG_LINUX_H_ + +#include <kbase/mali_kbase_config.h> +#include <linux/ioport.h> + +#define PLATFORM_CONFIG_RESOURCE_COUNT 4 +#define PLATFORM_CONFIG_IRQ_RES_COUNT 3 + +#ifdef CONFIG_MALI_PLATFORM_FAKE +/** + * @brief Convert data in kbase_io_resources struct to Linux-specific resources + * + * Function converts data in kbase_io_resources struct to an array of Linux resource structures. Note that function + * assumes that size of linux_resource array is at least PLATFORM_CONFIG_RESOURCE_COUNT. + * Resources are put in fixed order: I/O memory region, job IRQ, MMU IRQ, GPU IRQ. + * + * @param[in] io_resource Input IO resource data + * @param[out] linux_resources Pointer to output array of Linux resource structures + */ +void kbasep_config_parse_io_resources(const kbase_io_resources *io_resource, struct resource *const linux_resources); +#endif /* CONFIG_MALI_PLATFORM_FAKE */ + +#endif /* _KBASE_CONFIG_LINUX_H_ */ diff --git a/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_core_linux.c b/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_core_linux.c new file mode 100755 index 00000000000..ed3da683a87 --- /dev/null +++ b/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_core_linux.c @@ -0,0 +1,2921 @@ +/* + * + * (C) COPYRIGHT 2010-2013 ARM Limited. All rights reserved. + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * A copy of the licence is included with the program, and can also be obtained + * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + + + +/** + * @file mali_kbase_core_linux.c + * Base kernel driver init. + */ + +#include <kbase/src/common/mali_kbase.h> +#include <kbase/src/common/mali_kbase_uku.h> +#include <kbase/src/common/mali_midg_regmap.h> +#include <kbase/src/linux/mali_kbase_mem_linux.h> +#include <kbase/src/linux/mali_kbase_config_linux.h> +#ifdef CONFIG_MALI_NO_MALI +#include "mali_kbase_model_linux.h" +#endif /* CONFIG_MALI_NO_MALI */ + +#ifdef CONFIG_KDS +#include <linux/kds.h> +#include <linux/anon_inodes.h> +#include <linux/syscalls.h> +#endif /* CONFIG_KDS */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/miscdevice.h> +#include <linux/list.h> +#include <linux/semaphore.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/compat.h> /* is_compat_task */ +#include <kbase/src/common/mali_kbase_8401_workaround.h> +#include <kbase/src/common/mali_kbase_hw.h> +#ifdef CONFIG_SYNC +#include <kbase/src/linux/mali_kbase_sync.h> +#endif /* CONFIG_SYNC */ + +#ifdef CONFIG_MACH_MANTA +#include <plat/devs.h> +#endif + +#define JOB_IRQ_TAG 0 +#define MMU_IRQ_TAG 1 +#define GPU_IRQ_TAG 2 + +struct kbase_irq_table { + u32 tag; + irq_handler_t handler; +}; +#if MALI_UNIT_TEST +kbase_exported_test_data shared_kernel_test_data; +EXPORT_SYMBOL(shared_kernel_test_data); +#endif /* MALI_UNIT_TEST */ + +#define KBASE_DRV_NAME "mali" + +static const char kbase_drv_name[] = KBASE_DRV_NAME; + +static int kbase_dev_nr; + +static DEFINE_SEMAPHORE(kbase_dev_list_lock); +static LIST_HEAD(kbase_dev_list); + +KBASE_EXPORT_TEST_API(kbase_dev_list_lock) +KBASE_EXPORT_TEST_API(kbase_dev_list) +#define KERNEL_SIDE_DDK_VERSION_STRING "K:" MALI_RELEASE_NAME "(GPL)" +static INLINE void __compile_time_asserts(void) +{ + CSTD_COMPILE_TIME_ASSERT(sizeof(KERNEL_SIDE_DDK_VERSION_STRING) <= KBASE_GET_VERSION_BUFFER_SIZE); +} + +#ifdef CONFIG_KDS + +typedef struct kbasep_kds_resource_set_file_data { + struct kds_resource_set *lock; +} kbasep_kds_resource_set_file_data; + +static int kds_resource_release(struct inode *inode, struct file *file); + +static const struct file_operations kds_resource_fops = { + .release = kds_resource_release +}; + +typedef struct kbase_kds_resource_list_data { + struct kds_resource **kds_resources; + unsigned long *kds_access_bitmap; + int num_elems; +} kbase_kds_resource_list_data; + +static int kds_resource_release(struct inode *inode, struct file *file) +{ + struct kbasep_kds_resource_set_file_data *data; + + data = (struct kbasep_kds_resource_set_file_data *)file->private_data; + if (NULL != data) { + if (NULL != data->lock) + kds_resource_set_release(&data->lock); + + kfree(data); + } + return 0; +} + +mali_error kbasep_kds_allocate_resource_list_data(kbase_context *kctx, base_external_resource *ext_res, int num_elems, kbase_kds_resource_list_data *resources_list) +{ + base_external_resource *res = ext_res; + int res_id; + + /* assume we have to wait for all */ + + KBASE_DEBUG_ASSERT(0 != num_elems); + resources_list->kds_resources = kmalloc(sizeof(struct kds_resource *) * num_elems, GFP_KERNEL); + + if (NULL == resources_list->kds_resources) + return MALI_ERROR_OUT_OF_MEMORY; + + KBASE_DEBUG_ASSERT(0 != num_elems); + resources_list->kds_access_bitmap = kzalloc(sizeof(unsigned long) * ((num_elems + BITS_PER_LONG - 1) / BITS_PER_LONG), GFP_KERNEL); + + if (NULL == resources_list->kds_access_bitmap) { + kfree(resources_list->kds_access_bitmap); + return MALI_ERROR_OUT_OF_MEMORY; + } + + for (res_id = 0; res_id < num_elems; res_id++, res++) { + int exclusive; + kbase_va_region *reg; + struct kds_resource *kds_res = NULL; + + exclusive = res->ext_resource & BASE_EXT_RES_ACCESS_EXCLUSIVE; + reg = kbase_region_tracker_find_region_enclosing_address(kctx, res->ext_resource & ~BASE_EXT_RES_ACCESS_EXCLUSIVE); + + /* did we find a matching region object? */ + if (NULL == reg) + break; + + switch (reg->imported_type) { +#if defined(CONFIG_UMP) && defined(CONFIG_KDS) + case BASE_TMEM_IMPORT_TYPE_UMP: + kds_res = ump_dd_kds_resource_get(reg->imported_metadata.ump_handle); + break; +#endif /* defined(CONFIG_UMP) && defined(CONFIG_KDS) */ + default: + break; + } + + /* no kds resource for the region ? */ + if (!kds_res) + break; + + resources_list->kds_resources[res_id] = kds_res; + + if (exclusive) + set_bit(res_id, resources_list->kds_access_bitmap); + } + + /* did the loop run to completion? */ + if (res_id == num_elems) + return MALI_ERROR_NONE; + + /* Clean up as the resource list is not valid. */ + kfree(resources_list->kds_resources); + kfree(resources_list->kds_access_bitmap); + + return MALI_ERROR_FUNCTION_FAILED; +} + +mali_bool kbasep_validate_kbase_pointer(kbase_pointer *p) +{ +#ifdef CONFIG_COMPAT + if (is_compat_task()) { + if (p->compat_value == 0) + return MALI_FALSE; + } else { +#endif /* CONFIG_COMPAT */ + if (NULL == p->value) + return MALI_FALSE; +#ifdef CONFIG_COMPAT + } +#endif /* CONFIG_COMPAT */ + return MALI_TRUE; +} + +mali_error kbase_external_buffer_lock(kbase_context *kctx, kbase_uk_ext_buff_kds_data *args, u32 args_size) +{ + base_external_resource *ext_res_copy; + size_t ext_resource_size; + mali_error return_error = MALI_ERROR_FUNCTION_FAILED; + int fd; + + if (args_size != sizeof(kbase_uk_ext_buff_kds_data)) + return MALI_ERROR_FUNCTION_FAILED; + + /* Check user space has provided valid data */ + if (!kbasep_validate_kbase_pointer(&args->external_resource) || !kbasep_validate_kbase_pointer(&args->file_descriptor) || (0 == args->num_res) || (args->num_res > KBASE_MAXIMUM_EXT_RESOURCES)) + return MALI_ERROR_FUNCTION_FAILED; + + ext_resource_size = sizeof(base_external_resource) * args->num_res; + + KBASE_DEBUG_ASSERT(0 != ext_resource_size); + ext_res_copy = kmalloc(ext_resource_size, GFP_KERNEL); + + if (NULL != ext_res_copy) { + base_external_resource *__user ext_res_user; + int *__user file_descriptor_user; +#ifdef CONFIG_COMPAT + if (is_compat_task()) { + ext_res_user = args->external_resource.compat_value; + file_descriptor_user = args->file_descriptor.compat_value; + } else { +#endif /* CONFIG_COMPAT */ + ext_res_user = args->external_resource.value; + file_descriptor_user = args->file_descriptor.value; +#ifdef CONFIG_COMPAT + } +#endif /* CONFIG_COMPAT */ + + /* Copy the external resources to lock from user space */ + if (0 == copy_from_user(ext_res_copy, ext_res_user, ext_resource_size)) { + kbasep_kds_resource_set_file_data *fdata; + + /* Allocate data to be stored in the file */ + fdata = kmalloc(sizeof(kbasep_kds_resource_set_file_data), GFP_KERNEL); + + if (NULL != fdata) { + kbase_kds_resource_list_data resource_list_data; + /* Parse given elements and create resource and access lists */ + return_error = kbasep_kds_allocate_resource_list_data(kctx, ext_res_copy, args->num_res, &resource_list_data); + if (MALI_ERROR_NONE == return_error) { + long err; + + fdata->lock = NULL; + + fd = anon_inode_getfd("kds_ext", &kds_resource_fops, fdata, 0); + + err = copy_to_user(file_descriptor_user, &fd, sizeof(fd)); + + /* If the file descriptor was valid and we successfully copied it to user space, then we + * can try and lock the requested kds resources. + */ + if ((fd >= 0) && (0 == err)) { + struct kds_resource_set *lock; + + lock = kds_waitall(args->num_res, resource_list_data.kds_access_bitmap, resource_list_data.kds_resources, KDS_WAIT_BLOCKING); + + if (IS_ERR_OR_NULL(lock)) { + return_error = MALI_ERROR_FUNCTION_FAILED; + } else { + return_error = MALI_ERROR_NONE; + fdata->lock = lock; + } + } else { + return_error = MALI_ERROR_FUNCTION_FAILED; + } + + kfree(resource_list_data.kds_resources); + kfree(resource_list_data.kds_access_bitmap); + } + + if (MALI_ERROR_NONE != return_error) { + /* If the file was opened successfully then close it which will clean up + * the file data, otherwise we clean up the file data ourself. */ + if (fd >= 0) + sys_close(fd); + else + kfree(fdata); + } + } else { + return_error = MALI_ERROR_OUT_OF_MEMORY; + } + } + kfree(ext_res_copy); + } + return return_error; +} +#endif /* CONFIG_KDS */ + +static mali_error kbase_dispatch(kbase_context *kctx, void * const args, u32 args_size) +{ + struct kbase_device *kbdev; + uk_header *ukh = args; + u32 id; + + KBASE_DEBUG_ASSERT(ukh != NULL); + + kbdev = kctx->kbdev; + id = ukh->id; + ukh->ret = MALI_ERROR_NONE; /* Be optimistic */ + + if (UKP_FUNC_ID_CHECK_VERSION == id) { + if (args_size == sizeof(uku_version_check_args)) { + uku_version_check_args *version_check = (uku_version_check_args *)args; + + version_check->major = BASE_UK_VERSION_MAJOR; + version_check->minor = BASE_UK_VERSION_MINOR; + + ukh->ret = MALI_ERROR_NONE; + } else { + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + } + return MALI_ERROR_NONE; + } + + + if (!atomic_read(&kctx->setup_complete)) { + /* setup pending, try to signal that we'll do the setup */ + if (atomic_cmpxchg(&kctx->setup_in_progress, 0, 1)) { + /* setup was already in progress, err this call */ + return MALI_ERROR_FUNCTION_FAILED; + } + + /* we're the one doing setup */ + + /* is it the only call we accept? */ + if (id == KBASE_FUNC_SET_FLAGS) { + kbase_uk_set_flags *kbase_set_flags = (kbase_uk_set_flags *) args; + + if (sizeof(*kbase_set_flags) != args_size) { + /* not matching the expected call, stay stuck in setup mode */ + goto bad_size; + } + + if (MALI_ERROR_NONE != kbase_context_set_create_flags(kctx, kbase_set_flags->create_flags)) { + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + /* bad flags, will stay stuck in setup mode */ + return MALI_ERROR_NONE; + } else { + /* we've done the setup, all OK */ + atomic_set(&kctx->setup_complete, 1); + return MALI_ERROR_NONE; + } + } else { + /* unexpected call, will stay stuck in setup mode */ + return MALI_ERROR_FUNCTION_FAILED; + } + } + + /* setup complete, perform normal operation */ + switch (id) { + case KBASE_FUNC_TMEM_ALLOC: + { + kbase_uk_tmem_alloc *tmem = args; + struct kbase_va_region *reg; + + if (sizeof(*tmem) != args_size) + goto bad_size; + + reg = kbase_tmem_alloc(kctx, tmem->vsize, tmem->psize, tmem->extent, tmem->flags, tmem->is_growable); + if (reg) + tmem->gpu_addr = reg->start_pfn << PAGE_SHIFT; + else + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + break; + } + + case KBASE_FUNC_TMEM_IMPORT: + { + kbase_uk_tmem_import *tmem_import = args; + struct kbase_va_region *reg; + int *__user phandle; + int handle; + + if (sizeof(*tmem_import) != args_size) + goto bad_size; +#ifdef CONFIG_COMPAT + if (is_compat_task()) { + phandle = tmem_import->phandle.compat_value; + } else { +#endif /* CONFIG_COMPAT */ + phandle = tmem_import->phandle.value; +#ifdef CONFIG_COMPAT + } +#endif /* CONFIG_COMPAT */ + + /* code should be in kbase_tmem_import and its helpers, but uk dropped its get_user abstraction */ + switch (tmem_import->type) { +#ifdef CONFIG_UMP + case BASE_TMEM_IMPORT_TYPE_UMP: + get_user(handle, phandle); + break; +#endif /* CONFIG_UMP */ + case BASE_TMEM_IMPORT_TYPE_UMM: + get_user(handle, phandle); + break; + default: + goto bad_type; + break; + } + + reg = kbase_tmem_import(kctx, tmem_import->type, handle, &tmem_import->pages); + + if (reg) { + tmem_import->gpu_addr = reg->start_pfn << PAGE_SHIFT; + } else { + bad_type: + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + } + break; + } + case KBASE_FUNC_PMEM_ALLOC: + { + kbase_uk_pmem_alloc *pmem = args; + struct kbase_va_region *reg; + + if (sizeof(*pmem) != args_size) + goto bad_size; + + reg = kbase_pmem_alloc(kctx, pmem->vsize, pmem->flags, &pmem->cookie); + if (!reg) + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + break; + } + + case KBASE_FUNC_MEM_FREE: + { + kbase_uk_mem_free *mem = args; + + if (sizeof(*mem) != args_size) + goto bad_size; + + if ((mem->gpu_addr & ~PAGE_MASK) && (mem->gpu_addr >= PAGE_SIZE)) { + KBASE_DEBUG_PRINT_WARN(KBASE_MEM, "kbase_dispatch case KBASE_FUNC_MEM_FREE: mem->gpu_addr: passed parameter is invalid"); + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + break; + } + + if (kbase_mem_free(kctx, mem->gpu_addr)) + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + break; + } + + case KBASE_FUNC_JOB_SUBMIT: + { + kbase_uk_job_submit *job = args; + + if (sizeof(*job) != args_size) + goto bad_size; + + if (MALI_ERROR_NONE != kbase_jd_submit(kctx, job)) + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + break; + } + + case KBASE_FUNC_SYNC: + { + kbase_uk_sync_now *sn = args; + + if (sizeof(*sn) != args_size) + goto bad_size; + + if (sn->sset.basep_sset.mem_handle & ~PAGE_MASK) { + KBASE_DEBUG_PRINT_WARN(KBASE_MEM, "kbase_dispatch case KBASE_FUNC_SYNC: sn->sset.basep_sset.mem_handle: passed parameter is invalid"); + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + break; + } + + if (MALI_ERROR_NONE != kbase_sync_now(kctx, &sn->sset)) + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + break; + } + + case KBASE_FUNC_POST_TERM: + { + kbase_event_close(kctx); + break; + } + + case KBASE_FUNC_HWCNT_SETUP: + { + kbase_uk_hwcnt_setup *setup = args; + + if (sizeof(*setup) != args_size) + goto bad_size; + + if (MALI_ERROR_NONE != kbase_instr_hwcnt_setup(kctx, setup)) + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + break; + } + + case KBASE_FUNC_HWCNT_DUMP: + { + /* args ignored */ + if (MALI_ERROR_NONE != kbase_instr_hwcnt_dump(kctx)) + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + break; + } + + case KBASE_FUNC_HWCNT_CLEAR: + { + /* args ignored */ + if (MALI_ERROR_NONE != kbase_instr_hwcnt_clear(kctx)) + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + break; + } + + case KBASE_FUNC_CPU_PROPS_REG_DUMP: + { + kbase_uk_cpuprops *setup = args; + + if (sizeof(*setup) != args_size) + goto bad_size; + + if (MALI_ERROR_NONE != kbase_cpuprops_uk_get_props(kctx, setup)) + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + break; + } + + case KBASE_FUNC_GPU_PROPS_REG_DUMP: + { + kbase_uk_gpuprops *setup = args; + + if (sizeof(*setup) != args_size) + goto bad_size; + + if (MALI_ERROR_NONE != kbase_gpuprops_uk_get_props(kctx, setup)) + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + break; + } + + case KBASE_FUNC_TMEM_GETSIZE: + { + kbase_uk_tmem_get_size *getsize = args; + if (sizeof(*getsize) != args_size) + goto bad_size; + + if (getsize->gpu_addr & ~PAGE_MASK) { + KBASE_DEBUG_PRINT_WARN(KBASE_MEM, "kbase_dispatch case KBASE_FUNC_TMEM_GETSIZE: getsize->gpu_addr: passed parameter is invalid"); + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + break; + } + + ukh->ret = kbase_tmem_get_size(kctx, getsize->gpu_addr, &getsize->actual_size); + break; + } + break; + + case KBASE_FUNC_TMEM_SETSIZE: + { + kbase_uk_tmem_set_size *set_size = args; + + if (sizeof(*set_size) != args_size) + goto bad_size; + + if (set_size->gpu_addr & ~PAGE_MASK) { + KBASE_DEBUG_PRINT_WARN(KBASE_MEM, "kbase_dispatch case KBASE_FUNC_TMEM_SETSIZE: set_size->gpu_addr: passed parameter is invalid"); + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + break; + } + + ukh->ret = kbase_tmem_set_size(kctx, set_size->gpu_addr, set_size->size, &set_size->actual_size, &set_size->result_subcode); + break; + } + + case KBASE_FUNC_TMEM_RESIZE: + { + kbase_uk_tmem_resize *resize = args; + if (sizeof(*resize) != args_size) + goto bad_size; + + if (resize->gpu_addr & ~PAGE_MASK) { + KBASE_DEBUG_PRINT_WARN(KBASE_MEM, "kbase_dispatch case KBASE_FUNC_TMEM_RESIZE: resize->gpu_addr: passed parameter is invalid"); + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + break; + } + + ukh->ret = kbase_tmem_resize(kctx, resize->gpu_addr, resize->delta, &resize->actual_size, &resize->result_subcode); + break; + } + + case KBASE_FUNC_TMEM_SET_ATTRIBUTES: + { + kbase_uk_tmem_set_attributes* set_attributes = args; + + if (sizeof(*set_attributes) != args_size) + goto bad_size; + + if (set_attributes->gpu_addr & ~PAGE_MASK) { + KBASE_DEBUG_PRINT_WARN(KBASE_MEM, "kbase_dispatch case KBASE_FUNC_TMEM_SET_ATTRIBUTES: set_attributes->gpu_addr: passed parameter is invalid"); + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + break; + } + ukh->ret = kbase_tmem_set_attributes(kctx, set_attributes->gpu_addr, set_attributes->attributes); + break; + + } + + case KBASE_FUNC_TMEM_GET_ATTRIBUTES: + { + kbase_uk_tmem_get_attributes *get_attributes = args; + + if (sizeof(*get_attributes) != args_size) + goto bad_size; + + if (get_attributes->gpu_addr & ~PAGE_MASK) { + KBASE_DEBUG_PRINT_WARN(KBASE_MEM, "kbase_dispatch case KBASE_FUNC_TMEM_GET_ATTRIBUTES: get_attributes->gpu_addr: passed parameter is invalid"); + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + break; + } + + ukh->ret = kbase_tmem_get_attributes(kctx, get_attributes->gpu_addr, &get_attributes->attributes); + break; + + } + + case KBASE_FUNC_FIND_CPU_MAPPING: + { + kbase_uk_find_cpu_mapping *find = args; + struct kbase_cpu_mapping *map; + + if (sizeof(*find) != args_size) + goto bad_size; + + if (find->gpu_addr & ~PAGE_MASK) { + KBASE_DEBUG_PRINT_WARN(KBASE_MEM, "kbase_dispatch case KBASE_FUNC_FIND_CPU_MAPPING: find->gpu_addr: passed parameter is invalid"); + goto out_bad; + } + + KBASE_DEBUG_ASSERT(find != NULL); + if (find->size > SIZE_MAX || find->cpu_addr > ULONG_MAX) + map = NULL; + else + map = kbasep_find_enclosing_cpu_mapping(kctx, find->gpu_addr, (void *)(uintptr_t) find->cpu_addr, (size_t) find->size); + + if (NULL != map) { + find->uaddr = PTR_TO_U64(map->uaddr); + find->nr_pages = map->nr_pages; + find->page_off = map->page_off; + } else { + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + } + break; + } + case KBASE_FUNC_GET_VERSION: + { + kbase_uk_get_ddk_version *get_version = (kbase_uk_get_ddk_version *) args; + + if (sizeof(*get_version) != args_size) + goto bad_size; + + /* version buffer size check is made in compile time assert */ + memcpy(get_version->version_buffer, KERNEL_SIDE_DDK_VERSION_STRING, sizeof(KERNEL_SIDE_DDK_VERSION_STRING)); + get_version->version_string_size = sizeof(KERNEL_SIDE_DDK_VERSION_STRING); + break; + } + + case KBASE_FUNC_STREAM_CREATE: + { +#ifdef CONFIG_SYNC + kbase_uk_stream_create *screate = (kbase_uk_stream_create *) args; + + if (sizeof(*screate) != args_size) + goto bad_size; + + if (strnlen(screate->name, sizeof(screate->name)) >= sizeof(screate->name)) { + /* not NULL terminated */ + ukh->ret = MALI_ERROR_FUNCTION_FAILED; + break; + } + + ukh->ret = kbase_stream_create(screate->name, &screate->fd); +#else /* CONFIG_SYNC */ + ukh->ret = MALI_ERROR_FUNCTION_FAILED; +#endif /* CONFIG_SYNC */ + break; + } + case KBASE_FUNC_FENCE_VALIDATE: + { +#ifdef CONFIG_SYNC + kbase_uk_fence_validate *fence_validate = (kbase_uk_fence_validate *) args; + if (sizeof(*fence_validate) != args_size) + goto bad_size; + + ukh->ret = kbase_fence_validate(fence_validate->fd); +#endif /* CONFIG_SYNC */ + break; + } + + case KBASE_FUNC_EXT_BUFFER_LOCK: + { +#ifdef CONFIG_KDS + ukh->ret = kbase_external_buffer_lock(kctx, (kbase_uk_ext_buff_kds_data *) args, args_size); +#endif /* CONFIG_KDS */ + break; + } + + case KBASE_FUNC_SET_TEST_DATA: + { +#if MALI_UNIT_TEST + kbase_uk_set_test_data *set_data = args; + + shared_kernel_test_data = set_data->test_data; + shared_kernel_test_data.kctx.value = kctx; + shared_kernel_test_data.mm.value = (void *)current->mm; + ukh->ret = MALI_ERROR_NONE; +#endif /* MALI_UNIT_TEST */ + break; + } + + case KBASE_FUNC_INJECT_ERROR: + { +#ifdef CONFIG_MALI_ERROR_INJECT + unsigned long flags; + kbase_error_params params = ((kbase_uk_error_params *) args)->params; + /*mutex lock */ + spin_lock_irqsave(&kbdev->osdev.reg_op_lock, flags); + ukh->ret = job_atom_inject_error(¶ms); + spin_unlock_irqrestore(&kbdev->osdev.reg_op_lock, flags); + /*mutex unlock */ +#endif /* CONFIG_MALI_ERROR_INJECT */ + break; + } + + case KBASE_FUNC_MODEL_CONTROL: + { +#ifdef CONFIG_MALI_NO_MALI + unsigned long flags; + kbase_model_control_params params = ((kbase_uk_model_control_params *) args)->params; + /*mutex lock */ + spin_lock_irqsave(&kbdev->osdev.reg_op_lock, flags); + ukh->ret = midg_model_control(kbdev->osdev.model, ¶ms); + spin_unlock_irqrestore(&kbdev->osdev.reg_op_lock, flags); + /*mutex unlock */ +#endif /* CONFIG_MALI_NO_MALI */ + break; + } + + case KBASE_FUNC_KEEP_GPU_POWERED: + { + kbase_uk_keep_gpu_powered *kgp = (kbase_uk_keep_gpu_powered *) args; + /* A suspend won't happen here, because we're in a syscall from a + * userspace thread. + * + * Nevertheless, we'd get the wrong pm_context_active/idle counting + * here if a suspend did happen, so let's assert it won't: */ + KBASE_DEBUG_ASSERT(!kbase_pm_is_suspending(kbdev)); + + if (kgp->enabled && !kctx->keep_gpu_powered) { + kbase_pm_context_active(kbdev); + atomic_inc(&kbdev->keep_gpu_powered_count); + kctx->keep_gpu_powered = MALI_TRUE; + } else if (!kgp->enabled && kctx->keep_gpu_powered) { + atomic_dec(&kbdev->keep_gpu_powered_count); + kbase_pm_context_idle(kbdev); + kctx->keep_gpu_powered = MALI_FALSE; + } + + break; + } + default: + dev_err(kbdev->osdev.dev, "unknown ioctl %u", id); + goto out_bad; + } + + return MALI_ERROR_NONE; + + bad_size: + dev_err(kbdev->osdev.dev, "Wrong syscall size (%d) for %08x\n", args_size, id); + out_bad: + return MALI_ERROR_FUNCTION_FAILED; +} + +static struct kbase_device *to_kbase_device(struct device *dev) +{ + return dev_get_drvdata(dev); +} + +/* Find a particular kbase device (as specified by minor number), or find the "first" device if -1 is specified */ +struct kbase_device *kbase_find_device(int minor) +{ + struct kbase_device *kbdev = NULL; + struct list_head *entry; + + down(&kbase_dev_list_lock); + list_for_each(entry, &kbase_dev_list) { + struct kbase_device *tmp; + + tmp = list_entry(entry, struct kbase_device, osdev.entry); + if (tmp->osdev.mdev.minor == minor || minor == -1) { + kbdev = tmp; + get_device(kbdev->osdev.dev); + break; + } + } + up(&kbase_dev_list_lock); + + return kbdev; +} +EXPORT_SYMBOL(kbase_find_device); + +void kbase_release_device(struct kbase_device *kbdev) +{ + put_device(kbdev->osdev.dev); +} +EXPORT_SYMBOL(kbase_release_device); + +static int kbase_open(struct inode *inode, struct file *filp) +{ + struct kbase_device *kbdev = NULL; + kbase_context *kctx; + int ret = 0; + + kbdev = kbase_find_device(iminor(inode)); + + if (!kbdev) + return -ENODEV; + + kctx = kbase_create_context(kbdev); + if (!kctx) { + ret = -ENOMEM; + goto out; + } + + init_waitqueue_head(&kctx->osctx.event_queue); + filp->private_data = kctx; + + dev_dbg(kbdev->osdev.dev, "created base context\n"); + + { + kbasep_kctx_list_element *element; + + element = kzalloc(sizeof(kbasep_kctx_list_element), GFP_KERNEL); + if (element) { + mutex_lock(&kbdev->kctx_list_lock); + element->kctx = kctx; + list_add(&element->link, &kbdev->kctx_list); + mutex_unlock(&kbdev->kctx_list_lock); + } else { + /* we don't treat this as a fail - just warn about it */ + printk(KERN_WARNING KBASE_DRV_NAME "couldn't add kctx to kctx_list\n"); + } + } + return 0; + + out: + kbase_release_device(kbdev); + return ret; +} + +static int kbase_release(struct inode *inode, struct file *filp) +{ + kbase_context *kctx = filp->private_data; + struct kbase_device *kbdev = kctx->kbdev; + kbasep_kctx_list_element *element, *tmp; + mali_bool found_element = MALI_FALSE; + + mutex_lock(&kbdev->kctx_list_lock); + list_for_each_entry_safe(element, tmp, &kbdev->kctx_list, link) { + if (element->kctx == kctx) { + list_del(&element->link); + kfree(element); + found_element = MALI_TRUE; + } + } + mutex_unlock(&kbdev->kctx_list_lock); + if (!found_element) + printk(KERN_WARNING KBASE_DRV_NAME "kctx not in kctx_list\n"); + + filp->private_data = NULL; + kbase_destroy_context(kctx); + + dev_dbg(kbdev->osdev.dev, "deleted base context\n"); + kbase_release_device(kbdev); + return 0; +} + +#define CALL_MAX_SIZE 528 + +static long kbase_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + u64 msg[(CALL_MAX_SIZE + 7) >> 3] = { 0xdeadbeefdeadbeefull }; /* alignment fixup */ + u32 size = _IOC_SIZE(cmd); + kbase_context *kctx = filp->private_data; + + if (size > CALL_MAX_SIZE) + return -ENOTTY; + + if (0 != copy_from_user(&msg, (void *)arg, size)) { + pr_err("failed to copy ioctl argument into kernel space\n"); + return -EFAULT; + } + + if (MALI_ERROR_NONE != kbase_dispatch(kctx, &msg, size)) + return -EFAULT; + + if (0 != copy_to_user((void *)arg, &msg, size)) { + pr_err("failed to copy results of UK call back to user space\n"); + return -EFAULT; + } + return 0; +} + +static ssize_t kbase_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) +{ + kbase_context *kctx = filp->private_data; + base_jd_event_v2 uevent; + int out_count = 0; + + if (count < sizeof(uevent)) + return -ENOBUFS; + + do { + while (kbase_event_dequeue(kctx, &uevent)) { + if (out_count > 0) + goto out; + + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (wait_event_interruptible(kctx->osctx.event_queue, kbase_event_pending(kctx))) + return -ERESTARTSYS; + } + if (uevent.event_code == BASE_JD_EVENT_DRV_TERMINATED) { + if (out_count == 0) + return -EPIPE; + goto out; + } + + if (copy_to_user(buf, &uevent, sizeof(uevent))) + return -EFAULT; + + buf += sizeof(uevent); + out_count++; + count -= sizeof(uevent); + } while (count >= sizeof(uevent)); + + out: + return out_count * sizeof(uevent); +} + +static unsigned int kbase_poll(struct file *filp, poll_table *wait) +{ + kbase_context *kctx = filp->private_data; + + poll_wait(filp, &kctx->osctx.event_queue, wait); + if (kbase_event_pending(kctx)) + return POLLIN | POLLRDNORM; + + return 0; +} + +void kbase_event_wakeup(kbase_context *kctx) +{ + KBASE_DEBUG_ASSERT(kctx); + + wake_up_interruptible(&kctx->osctx.event_queue); +} + +KBASE_EXPORT_TEST_API(kbase_event_wakeup) + +int kbase_check_flags(int flags) +{ + /* Enforce that the driver keeps the O_CLOEXEC flag so that execve() always + * closes the file descriptor in a child process. + */ + if (0 == (flags & O_CLOEXEC)) + return -EINVAL; + + return 0; +} + +static const struct file_operations kbase_fops = { + .owner = THIS_MODULE, + .open = kbase_open, + .release = kbase_release, + .read = kbase_read, + .poll = kbase_poll, + .unlocked_ioctl = kbase_ioctl, + .mmap = kbase_mmap, + .check_flags = kbase_check_flags, +}; + +#ifndef CONFIG_MALI_NO_MALI +void kbase_os_reg_write(kbase_device *kbdev, u16 offset, u32 value) +{ + writel(value, kbdev->osdev.reg + offset); +} + +u32 kbase_os_reg_read(kbase_device *kbdev, u16 offset) +{ + return readl(kbdev->osdev.reg + offset); +} + +static void *kbase_tag(void *ptr, u32 tag) +{ + return (void *)(((uintptr_t) ptr) | tag); +} + +static void *kbase_untag(void *ptr) +{ + return (void *)(((uintptr_t) ptr) & ~3); +} + +static irqreturn_t kbase_job_irq_handler(int irq, void *data) +{ + unsigned long flags; + struct kbase_device *kbdev = kbase_untag(data); + u32 val; + + spin_lock_irqsave(&kbdev->pm.gpu_powered_lock, flags); + + if (!kbdev->pm.gpu_powered) { + /* GPU is turned off - IRQ is not for us */ + spin_unlock_irqrestore(&kbdev->pm.gpu_powered_lock, flags); + return IRQ_NONE; + } + + val = kbase_reg_read(kbdev, JOB_CONTROL_REG(JOB_IRQ_STATUS), NULL); + +#ifdef CONFIG_MALI_DEBUG + if (!kbdev->pm.driver_ready_for_irqs) + dev_dbg(kbdev->osdev.dev, "%s: irq %d irqstatus 0x%x before driver is ready\n", + __func__, irq, val ); +#endif /* CONFIG_MALI_DEBUG */ + spin_unlock_irqrestore(&kbdev->pm.gpu_powered_lock, flags); + + if (!val) + return IRQ_NONE; + + dev_dbg(kbdev->osdev.dev, "%s: irq %d irqstatus 0x%x\n", __func__, irq, val); + + kbase_job_done(kbdev, val); + + return IRQ_HANDLED; +} + +KBASE_EXPORT_TEST_API(kbase_job_irq_handler); + +static irqreturn_t kbase_mmu_irq_handler(int irq, void *data) +{ + unsigned long flags; + struct kbase_device *kbdev = kbase_untag(data); + u32 val; + + spin_lock_irqsave(&kbdev->pm.gpu_powered_lock, flags); + + if (!kbdev->pm.gpu_powered) { + /* GPU is turned off - IRQ is not for us */ + spin_unlock_irqrestore(&kbdev->pm.gpu_powered_lock, flags); + return IRQ_NONE; + } + + val = kbase_reg_read(kbdev, MMU_REG(MMU_IRQ_STATUS), NULL); + +#ifdef CONFIG_MALI_DEBUG + if (!kbdev->pm.driver_ready_for_irqs) + dev_dbg(kbdev->osdev.dev, "%s: irq %d irqstatus 0x%x before driver is ready\n", + __func__, irq, val ); +#endif /* CONFIG_MALI_DEBUG */ + spin_unlock_irqrestore(&kbdev->pm.gpu_powered_lock, flags); + + if (!val) + return IRQ_NONE; + + dev_dbg(kbdev->osdev.dev, "%s: irq %d irqstatus 0x%x\n", __func__, irq, val); + + kbase_mmu_interrupt(kbdev, val); + + return IRQ_HANDLED; +} + +static irqreturn_t kbase_gpu_irq_handler(int irq, void *data) +{ + unsigned long flags; + struct kbase_device *kbdev = kbase_untag(data); + u32 val; + + spin_lock_irqsave(&kbdev->pm.gpu_powered_lock, flags); + + if (!kbdev->pm.gpu_powered) { + /* GPU is turned off - IRQ is not for us */ + spin_unlock_irqrestore(&kbdev->pm.gpu_powered_lock, flags); + return IRQ_NONE; + } + + val = kbase_reg_read(kbdev, GPU_CONTROL_REG(GPU_IRQ_STATUS), NULL); + +#ifdef CONFIG_MALI_DEBUG + if (!kbdev->pm.driver_ready_for_irqs) + dev_dbg(kbdev->osdev.dev, "%s: irq %d irqstatus 0x%x before driver is ready\n", + __func__, irq, val ); +#endif /* CONFIG_MALI_DEBUG */ + spin_unlock_irqrestore(&kbdev->pm.gpu_powered_lock, flags); + + if (!val) + return IRQ_NONE; + + dev_dbg(kbdev->osdev.dev, "%s: irq %d irqstatus 0x%x\n", __func__, irq, val); + + kbase_gpu_interrupt(kbdev, val); + + return IRQ_HANDLED; +} + +static irq_handler_t kbase_handler_table[] = { + [JOB_IRQ_TAG] = kbase_job_irq_handler, + [MMU_IRQ_TAG] = kbase_mmu_irq_handler, + [GPU_IRQ_TAG] = kbase_gpu_irq_handler, +}; + +#ifdef CONFIG_MALI_DEBUG +#define JOB_IRQ_HANDLER JOB_IRQ_TAG +#define MMU_IRQ_HANDLER MMU_IRQ_TAG +#define GPU_IRQ_HANDLER GPU_IRQ_TAG + +/** + * @brief Registers given interrupt handler for requested interrupt type + * Case irq handler is not specified default handler shall be registered + * + * @param[in] kbdev - Device for which the handler is to be registered + * @param[in] custom_handler - Handler to be registered + * @param[in] irq_type - Interrupt type + * @return MALI_ERROR_NONE case success, MALI_ERROR_FUNCTION_FAILED otherwise + */ +static mali_error kbase_set_custom_irq_handler(kbase_device *kbdev, irq_handler_t custom_handler, int irq_type) +{ + struct kbase_os_device *osdev = &kbdev->osdev; + mali_error result = MALI_ERROR_NONE; + irq_handler_t requested_irq_handler = NULL; + KBASE_DEBUG_ASSERT((JOB_IRQ_HANDLER <= irq_type) && (GPU_IRQ_HANDLER >= irq_type)); + + /* Release previous handler */ + if (osdev->irqs[irq_type].irq) + free_irq(osdev->irqs[irq_type].irq, kbase_tag(kbdev, irq_type)); + + requested_irq_handler = (NULL != custom_handler) ? custom_handler : kbase_handler_table[irq_type]; + + if (0 != request_irq(osdev->irqs[irq_type].irq, requested_irq_handler, osdev->irqs[irq_type].flags | IRQF_SHARED, dev_name(osdev->dev), kbase_tag(kbdev, irq_type))) { + result = MALI_ERROR_FUNCTION_FAILED; + dev_err(osdev->dev, "Can't request interrupt %d (index %d)\n", osdev->irqs[irq_type].irq, irq_type); +#ifdef CONFIG_SPARSE_IRQ + dev_err(osdev->dev, "You have CONFIG_SPARSE_IRQ support enabled - is the interrupt number correct for this configuration?\n"); +#endif /* CONFIG_SPARSE_IRQ */ + } + + return result; +} + +KBASE_EXPORT_TEST_API(kbase_set_custom_irq_handler) + +/* test correct interrupt assigment and reception by cpu */ +typedef struct kbasep_irq_test { + struct hrtimer timer; + wait_queue_head_t wait; + int triggered; + u32 timeout; +} kbasep_irq_test; + +static kbasep_irq_test kbasep_irq_test_data; + +#define IRQ_TEST_TIMEOUT 500 + +static irqreturn_t kbase_job_irq_test_handler(int irq, void *data) +{ + unsigned long flags; + struct kbase_device *kbdev = kbase_untag(data); + u32 val; + + spin_lock_irqsave(&kbdev->pm.gpu_powered_lock, flags); + + if (!kbdev->pm.gpu_powered) { + /* GPU is turned off - IRQ is not for us */ + spin_unlock_irqrestore(&kbdev->pm.gpu_powered_lock, flags); + return IRQ_NONE; + } + + val = kbase_reg_read(kbdev, JOB_CONTROL_REG(JOB_IRQ_STATUS), NULL); + + spin_unlock_irqrestore(&kbdev->pm.gpu_powered_lock, flags); + + if (!val) + return IRQ_NONE; + + dev_dbg(kbdev->osdev.dev, "%s: irq %d irqstatus 0x%x\n", __func__, irq, val); + + kbasep_irq_test_data.triggered = 1; + wake_up(&kbasep_irq_test_data.wait); + + kbase_reg_write(kbdev, JOB_CONTROL_REG(JOB_IRQ_CLEAR), val, NULL); + + return IRQ_HANDLED; +} + +static irqreturn_t kbase_mmu_irq_test_handler(int irq, void *data) +{ + unsigned long flags; + struct kbase_device *kbdev = kbase_untag(data); + u32 val; + + spin_lock_irqsave(&kbdev->pm.gpu_powered_lock, flags); + + if (!kbdev->pm.gpu_powered) { + /* GPU is turned off - IRQ is not for us */ + spin_unlock_irqrestore(&kbdev->pm.gpu_powered_lock, flags); + return IRQ_NONE; + } + + val = kbase_reg_read(kbdev, MMU_REG(MMU_IRQ_STATUS), NULL); + + spin_unlock_irqrestore(&kbdev->pm.gpu_powered_lock, flags); + + if (!val) + return IRQ_NONE; + + dev_dbg(kbdev->osdev.dev, "%s: irq %d irqstatus 0x%x\n", __func__, irq, val); + + kbasep_irq_test_data.triggered = 1; + wake_up(&kbasep_irq_test_data.wait); + + kbase_reg_write(kbdev, MMU_REG(MMU_IRQ_CLEAR), val, NULL); + + return IRQ_HANDLED; +} + +static enum hrtimer_restart kbasep_test_interrupt_timeout(struct hrtimer *timer) +{ + kbasep_irq_test *test_data = container_of(timer, kbasep_irq_test, timer); + + test_data->timeout = 1; + test_data->triggered = 1; + wake_up(&test_data->wait); + return HRTIMER_NORESTART; +} + +static mali_error kbasep_common_test_interrupt(kbase_device * const kbdev, u32 tag) +{ + struct kbase_os_device *osdev = &kbdev->osdev; + mali_error err = MALI_ERROR_NONE; + irq_handler_t test_handler; + + u32 old_mask_val; + u16 mask_offset; + u16 rawstat_offset; + + switch (tag) { + case JOB_IRQ_TAG: + test_handler = kbase_job_irq_test_handler; + rawstat_offset = JOB_CONTROL_REG(JOB_IRQ_RAWSTAT); + mask_offset = JOB_CONTROL_REG(JOB_IRQ_MASK); + break; + case MMU_IRQ_TAG: + test_handler = kbase_mmu_irq_test_handler; + rawstat_offset = MMU_REG(MMU_IRQ_RAWSTAT); + mask_offset = MMU_REG(MMU_IRQ_MASK); + break; + case GPU_IRQ_TAG: + /* already tested by pm_driver - bail out */ + default: + return MALI_ERROR_NONE; + } + + /* store old mask */ + old_mask_val = kbase_reg_read(kbdev, mask_offset, NULL); + /* mask interrupts */ + kbase_reg_write(kbdev, mask_offset, 0x0, NULL); + + if (osdev->irqs[tag].irq) { + /* release original handler and install test handler */ + if (MALI_ERROR_NONE != kbase_set_custom_irq_handler(kbdev, test_handler, tag)) { + err = MALI_ERROR_FUNCTION_FAILED; + } else { + kbasep_irq_test_data.timeout = 0; + hrtimer_init(&kbasep_irq_test_data.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + kbasep_irq_test_data.timer.function = kbasep_test_interrupt_timeout; + + /* trigger interrupt */ + kbase_reg_write(kbdev, mask_offset, 0x1, NULL); + kbase_reg_write(kbdev, rawstat_offset, 0x1, NULL); + + hrtimer_start(&kbasep_irq_test_data.timer, HR_TIMER_DELAY_MSEC(IRQ_TEST_TIMEOUT), HRTIMER_MODE_REL); + + wait_event(kbasep_irq_test_data.wait, kbasep_irq_test_data.triggered != 0); + + if (kbasep_irq_test_data.timeout != 0) { + dev_err(osdev->dev, "Interrupt %d (index %d) didn't reach CPU.\n", osdev->irqs[tag].irq, tag); + err = MALI_ERROR_FUNCTION_FAILED; + } else { + dev_dbg(osdev->dev, "Interrupt %d (index %d) reached CPU.\n", osdev->irqs[tag].irq, tag); + } + + hrtimer_cancel(&kbasep_irq_test_data.timer); + kbasep_irq_test_data.triggered = 0; + + /* mask interrupts */ + kbase_reg_write(kbdev, mask_offset, 0x0, NULL); + + /* release test handler */ + free_irq(osdev->irqs[tag].irq, kbase_tag(kbdev, tag)); + } + + /* restore original interrupt */ + if (request_irq(osdev->irqs[tag].irq, kbase_handler_table[tag], osdev->irqs[tag].flags | IRQF_SHARED, dev_name(osdev->dev), kbase_tag(kbdev, tag))) { + dev_err(osdev->dev, "Can't restore original interrupt %d (index %d)\n", osdev->irqs[tag].irq, tag); + err = MALI_ERROR_FUNCTION_FAILED; + } + } + /* restore old mask */ + kbase_reg_write(kbdev, mask_offset, old_mask_val, NULL); + + return err; +} + +static mali_error kbasep_common_test_interrupt_handlers(kbase_device * const kbdev) +{ + struct kbase_os_device *osdev = &kbdev->osdev; + mali_error err; + + init_waitqueue_head(&kbasep_irq_test_data.wait); + kbasep_irq_test_data.triggered = 0; + + /* A suspend won't happen during startup/insmod */ + kbase_pm_context_active(kbdev); + + err = kbasep_common_test_interrupt(kbdev, JOB_IRQ_TAG); + if (MALI_ERROR_NONE != err) { + dev_err(osdev->dev, "Interrupt JOB_IRQ didn't reach CPU. Check interrupt assignments.\n"); + goto out; + } + + err = kbasep_common_test_interrupt(kbdev, MMU_IRQ_TAG); + if (MALI_ERROR_NONE != err) { + dev_err(osdev->dev, "Interrupt MMU_IRQ didn't reach CPU. Check interrupt assignments.\n"); + goto out; + } + + dev_err(osdev->dev, "Interrupts are correctly assigned.\n"); + + out: + kbase_pm_context_idle(kbdev); + + return err; + +} +#endif /* CONFIG_MALI_DEBUG */ + +static int kbase_install_interrupts(kbase_device *kbdev) +{ + struct kbase_os_device *osdev = &kbdev->osdev; + u32 nr = ARRAY_SIZE(kbase_handler_table); + int err; + u32 i; + + BUG_ON(nr > PLATFORM_CONFIG_IRQ_RES_COUNT); /* Only 3 interrupts! */ + + for (i = 0; i < nr; i++) { + err = request_irq(osdev->irqs[i].irq, kbase_handler_table[i], osdev->irqs[i].flags | IRQF_SHARED, dev_name(osdev->dev), kbase_tag(kbdev, i)); + if (err) { + dev_err(osdev->dev, "Can't request interrupt %d (index %d)\n", osdev->irqs[i].irq, i); +#ifdef CONFIG_SPARSE_IRQ + dev_err(osdev->dev, "You have CONFIG_SPARSE_IRQ support enabled - is the interrupt number correct for this configuration?\n"); +#endif /* CONFIG_SPARSE_IRQ */ + goto release; + } + } + + return 0; + + release: + while (i-- > 0) + free_irq(osdev->irqs[i].irq, kbase_tag(kbdev, i)); + + return err; +} + +static void kbase_release_interrupts(kbase_device *kbdev) +{ + struct kbase_os_device *osdev = &kbdev->osdev; + u32 nr = ARRAY_SIZE(kbase_handler_table); + u32 i; + + for (i = 0; i < nr; i++) { + if (osdev->irqs[i].irq) + free_irq(osdev->irqs[i].irq, kbase_tag(kbdev, i)); + } +} + +void kbase_synchronize_irqs(kbase_device *kbdev) +{ + struct kbase_os_device *osdev = &kbdev->osdev; + u32 nr = ARRAY_SIZE(kbase_handler_table); + u32 i; + + for (i = 0; i < nr; i++) { + if (osdev->irqs[i].irq) + synchronize_irq(osdev->irqs[i].irq); + } +} + +#endif /* CONFIG_MALI_NO_MALI */ + +/** Show callback for the @c gpu_memory sysfs file. + * + * This function is called to get the contents of the @c gpu_memory sysfs + * file. This is a report of current gpu memory usage. + * + * @param dev The device this sysfs file is for + * @param attr The attributes of the sysfs file + * @param buf The output buffer for the sysfs file contents + * + * @return The number of bytes output to @c buf. + */ +static ssize_t show_gpu_memory(struct device *dev, struct device_attribute *attr, char *buf) +{ + ssize_t ret = 0; + struct list_head *entry; + + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "Name cap(pages) usage(pages)\n" "=========================================\n"); + down(&kbase_dev_list_lock); + list_for_each(entry, &kbase_dev_list) { + struct kbase_device *kbdev = NULL; + kbasep_kctx_list_element *element; + + kbdev = list_entry(entry, struct kbase_device, osdev.entry); + /* output the total memory usage and cap for this device */ + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "%-16s %10u %10u\n", kbdev->osdev.devname, kbdev->memdev.usage.max_pages, atomic_read(&(kbdev->memdev.usage.cur_pages)) + ); + mutex_lock(&kbdev->kctx_list_lock); + list_for_each_entry(element, &kbdev->kctx_list, link) { + /* output the memory usage and cap for each kctx opened on this device */ + ret += scnprintf(buf + ret, PAGE_SIZE - ret, " %s-0x%p %10u %10u\n", "kctx", element->kctx, element->kctx->usage.max_pages, atomic_read(&(element->kctx->usage.cur_pages)) + ); + } + mutex_unlock(&kbdev->kctx_list_lock); + } + up(&kbase_dev_list_lock); + if (PAGE_SIZE == ret) { + /* we attempted to write more than a page full - truncate */ + buf[PAGE_SIZE - 2] = '\n'; + buf[PAGE_SIZE - 1] = '\0'; + ret = PAGE_SIZE - 1; + } + return ret; +} + +/** The sysfs file @c gpu_memory. + * + * This is used for obtaining a report of current gpu memory usage. + */ +DEVICE_ATTR(gpu_memory, S_IRUGO, show_gpu_memory, NULL); + + +/** Show callback for the @c power_policy sysfs file. + * + * This function is called to get the contents of the @c power_policy sysfs + * file. This is a list of the available policies with the currently active one + * surrounded by square brackets. + * + * @param dev The device this sysfs file is for + * @param attr The attributes of the sysfs file + * @param buf The output buffer for the sysfs file contents + * + * @return The number of bytes output to @c buf. + */ +static ssize_t show_policy(struct device *dev, struct device_attribute *attr, char *const buf) +{ + struct kbase_device *kbdev; + const struct kbase_pm_policy *current_policy; + const struct kbase_pm_policy *const *policy_list; + int policy_count; + int i; + ssize_t ret = 0; + + kbdev = to_kbase_device(dev); + + if (!kbdev) + return -ENODEV; + + current_policy = kbase_pm_get_policy(kbdev); + + policy_count = kbase_pm_list_policies(&policy_list); + + for (i = 0; i < policy_count && ret < PAGE_SIZE; i++) { + if (policy_list[i] == current_policy) + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "[%s] ", policy_list[i]->name); + else + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "%s ", policy_list[i]->name); + } + + if (ret < PAGE_SIZE - 1) { + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "\n"); + } else { + buf[PAGE_SIZE - 2] = '\n'; + buf[PAGE_SIZE - 1] = '\0'; + ret = PAGE_SIZE - 1; + } + + return ret; +} + +/** Store callback for the @c power_policy sysfs file. + * + * This function is called when the @c power_policy sysfs file is written to. + * It matches the requested policy against the available policies and if a + * matching policy is found calls @ref kbase_pm_set_policy to change the + * policy. + * + * @param dev The device with sysfs file is for + * @param attr The attributes of the sysfs file + * @param buf The value written to the sysfs file + * @param count The number of bytes written to the sysfs file + * + * @return @c count if the function succeeded. An error code on failure. + */ +static ssize_t set_policy(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct kbase_device *kbdev; + const struct kbase_pm_policy *new_policy = NULL; + const struct kbase_pm_policy *const *policy_list; + int policy_count; + int i; + + kbdev = to_kbase_device(dev); + + if (!kbdev) + return -ENODEV; + + policy_count = kbase_pm_list_policies(&policy_list); + + for (i = 0; i < policy_count; i++) { + if (sysfs_streq(policy_list[i]->name, buf)) { + new_policy = policy_list[i]; + break; + } + } + + if (!new_policy) { + dev_err(dev, "power_policy: policy not found\n"); + return -EINVAL; + } + + kbase_pm_set_policy(kbdev, new_policy); + + return count; +} + +/** The sysfs file @c power_policy. + * + * This is used for obtaining information about the available policies, + * determining which policy is currently active, and changing the active + * policy. + */ +DEVICE_ATTR(power_policy, S_IRUGO | S_IWUSR, show_policy, set_policy); + +/** Show callback for the @c core_availability_policy sysfs file. + * + * This function is called to get the contents of the @c core_availability_policy + * sysfs file. This is a list of the available policies with the currently + * active one surrounded by square brackets. + * + * @param dev The device this sysfs file is for + * @param attr The attributes of the sysfs file + * @param buf The output buffer for the sysfs file contents + * + * @return The number of bytes output to @c buf. + */ +static ssize_t show_ca_policy(struct device *dev, struct device_attribute *attr, char *const buf) +{ + struct kbase_device *kbdev; + const struct kbase_pm_ca_policy *current_policy; + const struct kbase_pm_ca_policy *const *policy_list; + int policy_count; + int i; + ssize_t ret = 0; + + kbdev = to_kbase_device(dev); + + if (!kbdev) + return -ENODEV; + + current_policy = kbase_pm_ca_get_policy(kbdev); + + policy_count = kbase_pm_ca_list_policies(&policy_list); + + for (i = 0; i < policy_count && ret < PAGE_SIZE; i++) { + if (policy_list[i] == current_policy) + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "[%s] ", policy_list[i]->name); + else + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "%s ", policy_list[i]->name); + } + + if (ret < PAGE_SIZE - 1) { + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "\n"); + } else { + buf[PAGE_SIZE - 2] = '\n'; + buf[PAGE_SIZE - 1] = '\0'; + ret = PAGE_SIZE - 1; + } + + return ret; +} + +/** Store callback for the @c core_availability_policy sysfs file. + * + * This function is called when the @c core_availability_policy sysfs file is + * written to. It matches the requested policy against the available policies + * and if a matching policy is found calls @ref kbase_pm_set_policy to change + * the policy. + * + * @param dev The device with sysfs file is for + * @param attr The attributes of the sysfs file + * @param buf The value written to the sysfs file + * @param count The number of bytes written to the sysfs file + * + * @return @c count if the function succeeded. An error code on failure. + */ +static ssize_t set_ca_policy(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct kbase_device *kbdev; + const struct kbase_pm_ca_policy *new_policy = NULL; + const struct kbase_pm_ca_policy *const *policy_list; + int policy_count; + int i; + + kbdev = to_kbase_device(dev); + + if (!kbdev) + return -ENODEV; + + policy_count = kbase_pm_ca_list_policies(&policy_list); + + for (i = 0; i < policy_count; i++) { + if (sysfs_streq(policy_list[i]->name, buf)) { + new_policy = policy_list[i]; + break; + } + } + + if (!new_policy) { + dev_err(dev, "core_availability_policy: policy not found\n"); + return -EINVAL; + } + + kbase_pm_ca_set_policy(kbdev, new_policy); + + return count; +} + +/** The sysfs file @c core_availability_policy + * + * This is used for obtaining information about the available policies, + * determining which policy is currently active, and changing the active + * policy. + */ +DEVICE_ATTR(core_availability_policy, S_IRUGO | S_IWUSR, show_ca_policy, set_ca_policy); + +/** Show callback for the @c core_mask sysfs file. + * + * This function is called to get the contents of the @c core_mask sysfs + * file. + * + * @param dev The device this sysfs file is for + * @param attr The attributes of the sysfs file + * @param buf The output buffer for the sysfs file contents + * + * @return The number of bytes output to @c buf. + */ +static ssize_t show_core_mask(struct device *dev, struct device_attribute *attr, char *const buf) +{ + struct kbase_device *kbdev; + ssize_t ret = 0; + + kbdev = to_kbase_device(dev); + + if (!kbdev) + return -ENODEV; + + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "Current core mask : 0x%llX\n", kbdev->pm.debug_core_mask); + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "Available core mask : 0x%llX\n", kbdev->shader_present_bitmap); + + return ret; +} + +/** Store callback for the @c core_mask sysfs file. + * + * This function is called when the @c core_mask sysfs file is written to. + * + * @param dev The device with sysfs file is for + * @param attr The attributes of the sysfs file + * @param buf The value written to the sysfs file + * @param count The number of bytes written to the sysfs file + * + * @return @c count if the function succeeded. An error code on failure. + */ +static ssize_t set_core_mask(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct kbase_device *kbdev; + u64 new_core_mask; + + kbdev = to_kbase_device(dev); + + if (!kbdev) + return -ENODEV; + + new_core_mask = simple_strtoull(buf, NULL, 16); + + if ((new_core_mask & kbdev->shader_present_bitmap) != new_core_mask || + !(new_core_mask & kbdev->gpu_props.props.coherency_info.group[0].core_mask)) { + dev_err(dev, "power_policy: invalid core specification\n"); + return -EINVAL; + } + + if (kbdev->pm.debug_core_mask != new_core_mask) { + unsigned long flags; + + spin_lock_irqsave(&kbdev->pm.power_change_lock, flags); + + kbdev->pm.debug_core_mask = new_core_mask; + kbase_pm_update_cores_state_nolock(kbdev); + + spin_unlock_irqrestore(&kbdev->pm.power_change_lock, flags); + } + + return count; +} + +/** The sysfs file @c core_mask. + * + * This is used to restrict shader core availability for debugging purposes. + * Reading it will show the current core mask and the mask of cores available. + * Writing to it will set the current core mask. + */ +DEVICE_ATTR(core_mask, S_IRUGO | S_IWUSR, show_core_mask, set_core_mask); + + +#ifdef CONFIG_MALI_DEBUG_SHADER_SPLIT_FS +/* Import the external affinity mask variables */ +extern u64 mali_js0_affinity_mask; +extern u64 mali_js1_affinity_mask; +extern u64 mali_js2_affinity_mask; + +/** + * Structure containing a single shader affinity split configuration. + */ +typedef struct { + char const * tag; + char const * human_readable; + u64 js0_mask; + u64 js1_mask; + u64 js2_mask; +} sc_split_config; + +/** + * Array of available shader affinity split configurations. + */ +static sc_split_config const sc_split_configs[] = +{ + /* All must be the first config (default). */ + { + "all", "All cores", + 0xFFFFFFFFFFFFFFFFULL, 0xFFFFFFFFFFFFFFFFULL, 0xFFFFFFFFFFFFFFFFULL + }, + { + "mp1", "MP1 shader core", + 0x1, 0x1, 0x1 + }, + { + "mp2", "MP2 shader core", + 0x3, 0x3, 0x3 + }, + { + "mp4", "MP4 shader core", + 0xF, 0xF, 0xF + }, + { + "mp1_vf", "MP1 vertex + MP1 fragment shader core", + 0x2, 0x1, 0xFFFFFFFFFFFFFFFFULL + }, + { + "mp2_vf", "MP2 vertex + MP2 fragment shader core", + 0xA, 0x5, 0xFFFFFFFFFFFFFFFFULL + }, + /* This must be the last config. */ + { + NULL, NULL, + 0x0, 0x0, 0x0 + }, +}; + +/* Pointer to the currently active shader split configuration. */ +static sc_split_config const * current_sc_split_config = &sc_split_configs[0]; + +/** Show callback for the @c sc_split sysfs file + * + * Returns the current shader core affinity policy. + */ +static ssize_t show_split(struct device *dev, struct device_attribute *attr, char * const buf) +{ + ssize_t ret; + /* We know we are given a buffer which is PAGE_SIZE long. Our strings are all guaranteed + * to be shorter than that at this time so no length check needed. */ + ret = scnprintf(buf, PAGE_SIZE, "Current sc_split: '%s'\n", current_sc_split_config->tag ); + return ret; +} + +/** Store callback for the @c sc_split sysfs file. + * + * This function is called when the @c sc_split sysfs file is written to + * It modifies the system shader core affinity configuration to allow + * system profiling with different hardware configurations. + * + * @param dev The device with sysfs file is for + * @param attr The attributes of the sysfs file + * @param buf The value written to the sysfs file + * @param count The number of bytes written to the sysfs file + * + * @return @c count if the function succeeded. An error code on failure. + */ +static ssize_t set_split(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + sc_split_config const * config = &sc_split_configs[0]; + + /* Try to match: loop until we hit the last "NULL" entry */ + while( config->tag ) + { + if (sysfs_streq(config->tag, buf)) + { + current_sc_split_config = config; + mali_js0_affinity_mask = config->js0_mask; + mali_js1_affinity_mask = config->js1_mask; + mali_js2_affinity_mask = config->js2_mask; + dev_info(dev, "Setting sc_split: '%s'\n", config->tag); + return count; + } + config++; + } + + /* No match found in config list */ + dev_err(dev, "sc_split: invalid value\n"); + dev_err(dev, " Possible settings: mp[1|2|4], mp[1|2]_vf\n"); + return -ENOENT; +} + +/** The sysfs file @c sc_split + * + * This is used for configuring/querying the current shader core work affinity + * configuration. + */ +DEVICE_ATTR(sc_split, S_IRUGO|S_IWUSR, show_split, set_split); +#endif /* CONFIG_MALI_DEBUG_SHADER_SPLIT_FS */ + + +#if MALI_CUSTOMER_RELEASE == 0 +/** Store callback for the @c js_timeouts sysfs file. + * + * This function is called to get the contents of the @c js_timeouts sysfs + * file. This file contains five values separated by whitespace. The values + * are basically the same as KBASE_CONFIG_ATTR_JS_SOFT_STOP_TICKS, + * KBASE_CONFIG_ATTR_JS_HARD_STOP_TICKS_SS, KBASE_CONFIG_ATTR_JS_HARD_STOP_TICKS_NSS, + * KBASE_CONFIG_ATTR_JS_RESET_TICKS_SS, BASE_CONFIG_ATTR_JS_RESET_TICKS_NSS + * configuration values (in that order), with the difference that the js_timeout + * valus are expressed in MILLISECONDS. + * + * The js_timeouts sysfile file allows the current values in + * use by the job scheduler to get override. Note that a value needs to + * be other than 0 for it to override the current job scheduler value. + * + * @param dev The device with sysfs file is for + * @param attr The attributes of the sysfs file + * @param buf The value written to the sysfs file + * @param count The number of bytes written to the sysfs file + * + * @return @c count if the function succeeded. An error code on failure. + */ +static ssize_t set_js_timeouts(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct kbase_device *kbdev; + int items; + unsigned long js_soft_stop_ms; + unsigned long js_hard_stop_ms_ss; + unsigned long js_hard_stop_ms_nss; + unsigned long js_reset_ms_ss; + unsigned long js_reset_ms_nss; + + kbdev = to_kbase_device(dev); + if (!kbdev) + return -ENODEV; + + items = sscanf(buf, "%lu %lu %lu %lu %lu", &js_soft_stop_ms, &js_hard_stop_ms_ss, &js_hard_stop_ms_nss, &js_reset_ms_ss, &js_reset_ms_nss); + if (items == 5) { + u64 ticks; + + ticks = js_soft_stop_ms * 1000000ULL; + do_div(ticks, kbdev->js_data.scheduling_tick_ns); + kbdev->js_soft_stop_ticks = ticks; + + ticks = js_hard_stop_ms_ss * 1000000ULL; + do_div(ticks, kbdev->js_data.scheduling_tick_ns); + kbdev->js_hard_stop_ticks_ss = ticks; + + ticks = js_hard_stop_ms_nss * 1000000ULL; + do_div(ticks, kbdev->js_data.scheduling_tick_ns); + kbdev->js_hard_stop_ticks_nss = ticks; + + ticks = js_reset_ms_ss * 1000000ULL; + do_div(ticks, kbdev->js_data.scheduling_tick_ns); + kbdev->js_reset_ticks_ss = ticks; + + ticks = js_reset_ms_nss * 1000000ULL; + do_div(ticks, kbdev->js_data.scheduling_tick_ns); + kbdev->js_reset_ticks_nss = ticks; + + dev_info(kbdev->osdev.dev, "Overriding KBASE_CONFIG_ATTR_JS_SOFT_STOP_TICKS with %lu ticks (%lu ms)\n", (unsigned long)kbdev->js_soft_stop_ticks, js_soft_stop_ms); + dev_info(kbdev->osdev.dev, "Overriding KBASE_CONFIG_ATTR_JS_HARD_STOP_TICKS_SS with %lu ticks (%lu ms)\n", (unsigned long)kbdev->js_hard_stop_ticks_ss, js_hard_stop_ms_ss); + dev_info(kbdev->osdev.dev, "Overriding KBASE_CONFIG_ATTR_JS_HARD_STOP_TICKS_NSS with %lu ticks (%lu ms)\n", (unsigned long)kbdev->js_hard_stop_ticks_nss, js_hard_stop_ms_nss); + dev_info(kbdev->osdev.dev, "Overriding KBASE_CONFIG_ATTR_JS_RESET_TICKS_SS with %lu ticks (%lu ms)\n", (unsigned long)kbdev->js_reset_ticks_ss, js_reset_ms_ss); + dev_info(kbdev->osdev.dev, "Overriding KBASE_CONFIG_ATTR_JS_RESET_TICKS_NSS with %lu ticks (%lu ms)\n", (unsigned long)kbdev->js_reset_ticks_nss, js_reset_ms_nss); + + return count; + } else { + dev_err(kbdev->osdev.dev, "Couldn't process js_timeouts write operation.\nUse format " "<soft_stop_ms> <hard_stop_ms_ss> <hard_stop_ms_nss> <reset_ms_ss> <reset_ms_nss>\n"); + return -EINVAL; + } +} + +/** Show callback for the @c js_timeouts sysfs file. + * + * This function is called to get the contents of the @c js_timeouts sysfs + * file. It returns the last set values written to the js_timeouts sysfs file. + * If the file didn't get written yet, the values will be 0. + * + * @param dev The device this sysfs file is for + * @param attr The attributes of the sysfs file + * @param buf The output buffer for the sysfs file contents + * + * @return The number of bytes output to @c buf. + */ +static ssize_t show_js_timeouts(struct device *dev, struct device_attribute *attr, char * const buf) +{ + struct kbase_device *kbdev; + ssize_t ret; + u64 ms; + unsigned long js_soft_stop_ms; + unsigned long js_hard_stop_ms_ss; + unsigned long js_hard_stop_ms_nss; + unsigned long js_reset_ms_ss; + unsigned long js_reset_ms_nss; + + kbdev = to_kbase_device(dev); + if (!kbdev) + return -ENODEV; + + ms = (u64) kbdev->js_soft_stop_ticks * kbdev->js_data.scheduling_tick_ns; + do_div(ms, 1000000UL); + js_soft_stop_ms = (unsigned long)ms; + + ms = (u64) kbdev->js_hard_stop_ticks_ss * kbdev->js_data.scheduling_tick_ns; + do_div(ms, 1000000UL); + js_hard_stop_ms_ss = (unsigned long)ms; + + ms = (u64) kbdev->js_hard_stop_ticks_nss * kbdev->js_data.scheduling_tick_ns; + do_div(ms, 1000000UL); + js_hard_stop_ms_nss = (unsigned long)ms; + + ms = (u64) kbdev->js_reset_ticks_ss * kbdev->js_data.scheduling_tick_ns; + do_div(ms, 1000000UL); + js_reset_ms_ss = (unsigned long)ms; + + ms = (u64) kbdev->js_reset_ticks_nss * kbdev->js_data.scheduling_tick_ns; + do_div(ms, 1000000UL); + js_reset_ms_nss = (unsigned long)ms; + + ret = scnprintf(buf, PAGE_SIZE, "%lu %lu %lu %lu %lu\n", js_soft_stop_ms, js_hard_stop_ms_ss, js_hard_stop_ms_nss, js_reset_ms_ss, js_reset_ms_nss); + + if (ret >= PAGE_SIZE) { + buf[PAGE_SIZE - 2] = '\n'; + buf[PAGE_SIZE - 1] = '\0'; + ret = PAGE_SIZE - 1; + } + + return ret; +} + +/** The sysfs file @c js_timeouts. + * + * This is used to override the current job scheduler values for + * KBASE_CONFIG_ATTR_JS_STOP_STOP_TICKS_SS + * KBASE_CONFIG_ATTR_JS_HARD_STOP_TICKS_SS + * KBASE_CONFIG_ATTR_JS_HARD_STOP_TICKS_NSS + * KBASE_CONFIG_ATTR_JS_RESET_TICKS_SS + * KBASE_CONFIG_ATTR_JS_RESET_TICKS_NSS. + */ +DEVICE_ATTR(js_timeouts, S_IRUGO | S_IWUSR, show_js_timeouts, set_js_timeouts); +#endif /* MALI_CUSTOMER_RELEASE == 0 */ + +#ifdef CONFIG_MALI_DEBUG +static ssize_t set_js_softstop_always(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct kbase_device *kbdev; + int items; + int softstop_always; + + kbdev = to_kbase_device(dev); + if (!kbdev) + return -ENODEV; + + items = sscanf(buf, "%d", &softstop_always); + if ((items == 1) && ((softstop_always == 0) || (softstop_always == 1))) { + kbdev->js_data.softstop_always = (mali_bool) softstop_always; + + dev_info(kbdev->osdev.dev, "Support for softstop on a single context: %s\n", (kbdev->js_data.softstop_always == MALI_FALSE) ? "Disabled" : "Enabled"); + return count; + } else { + dev_err(kbdev->osdev.dev, "Couldn't process js_softstop_always write operation.\nUse format " "<soft_stop_always>\n"); + return -EINVAL; + } +} + +static ssize_t show_js_softstop_always(struct device *dev, struct device_attribute *attr, char * const buf) +{ + struct kbase_device *kbdev; + ssize_t ret; + + kbdev = to_kbase_device(dev); + if (!kbdev) + return -ENODEV; + + ret = scnprintf(buf, PAGE_SIZE, "%d\n", kbdev->js_data.softstop_always); + + if (ret >= PAGE_SIZE) { + buf[PAGE_SIZE - 2] = '\n'; + buf[PAGE_SIZE - 1] = '\0'; + ret = PAGE_SIZE - 1; + } + + return ret; +} + +/** + * By default, soft-stops are disabled when only a single context is present. The ability to + * enable soft-stop when only a single context is present can be used for debug and unit-testing purposes. + * (see CL t6xx_stress_1 unit-test as an example whereby this feature is used.) + */ +DEVICE_ATTR(js_softstop_always, S_IRUGO | S_IWUSR, show_js_softstop_always, set_js_softstop_always); +#endif /* CONFIG_MALI_DEBUG */ + +#ifdef CONFIG_MALI_DEBUG +typedef void (kbasep_debug_command_func) (kbase_device *); + +typedef enum { + KBASEP_DEBUG_COMMAND_DUMPTRACE, + + /* This must be the last enum */ + KBASEP_DEBUG_COMMAND_COUNT +} kbasep_debug_command_code; + +typedef struct kbasep_debug_command { + char *str; + kbasep_debug_command_func *func; +} kbasep_debug_command; + +/** Debug commands supported by the driver */ +static const kbasep_debug_command debug_commands[] = { + { + .str = "dumptrace", + .func = &kbasep_trace_dump, + } +}; + +/** Show callback for the @c debug_command sysfs file. + * + * This function is called to get the contents of the @c debug_command sysfs + * file. This is a list of the available debug commands, separated by newlines. + * + * @param dev The device this sysfs file is for + * @param attr The attributes of the sysfs file + * @param buf The output buffer for the sysfs file contents + * + * @return The number of bytes output to @c buf. + */ +static ssize_t show_debug(struct device *dev, struct device_attribute *attr, char *const buf) +{ + struct kbase_device *kbdev; + int i; + ssize_t ret = 0; + + kbdev = to_kbase_device(dev); + + if (!kbdev) + return -ENODEV; + + for (i = 0; i < KBASEP_DEBUG_COMMAND_COUNT && ret < PAGE_SIZE; i++) + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "%s\n", debug_commands[i].str); + + if (ret >= PAGE_SIZE) { + buf[PAGE_SIZE - 2] = '\n'; + buf[PAGE_SIZE - 1] = '\0'; + ret = PAGE_SIZE - 1; + } + + return ret; +} + +/** Store callback for the @c debug_command sysfs file. + * + * This function is called when the @c debug_command sysfs file is written to. + * It matches the requested command against the available commands, and if + * a matching command is found calls the associated function from + * @ref debug_commands to issue the command. + * + * @param dev The device with sysfs file is for + * @param attr The attributes of the sysfs file + * @param buf The value written to the sysfs file + * @param count The number of bytes written to the sysfs file + * + * @return @c count if the function succeeded. An error code on failure. + */ +static ssize_t issue_debug(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct kbase_device *kbdev; + int i; + + kbdev = to_kbase_device(dev); + + if (!kbdev) + return -ENODEV; + + for (i = 0; i < KBASEP_DEBUG_COMMAND_COUNT; i++) { + if (sysfs_streq(debug_commands[i].str, buf)) { + debug_commands[i].func(kbdev); + return count; + } + } + + /* Debug Command not found */ + dev_err(dev, "debug_command: command not known\n"); + return -EINVAL; +} + +/** The sysfs file @c debug_command. + * + * This is used to issue general debug commands to the device driver. + * Reading it will produce a list of debug commands, separated by newlines. + * Writing to it with one of those commands will issue said command. + */ +DEVICE_ATTR(debug_command, S_IRUGO | S_IWUSR, show_debug, issue_debug); +#endif /* CONFIG_MALI_DEBUG */ + +#ifdef CONFIG_MALI_NO_MALI +static int kbase_common_reg_map(kbase_device *kbdev) +{ + return 0; +} +static void kbase_common_reg_unmap(kbase_device * const kbdev) +{ + return; +} +#else /* CONFIG_MALI_NO_MALI */ +static int kbase_common_reg_map(kbase_device *kbdev) +{ + struct kbase_os_device *osdev = &kbdev->osdev; + int err = -ENOMEM; + + osdev->reg_res = request_mem_region(osdev->reg_start, osdev->reg_size, dev_name(osdev->dev)); + if (!osdev->reg_res) { + dev_err(osdev->dev, "Register window unavailable\n"); + err = -EIO; + goto out_region; + } + + osdev->reg = ioremap(osdev->reg_start, osdev->reg_size); + if (!osdev->reg) { + dev_err(osdev->dev, "Can't remap register window\n"); + err = -EINVAL; + goto out_ioremap; + } + + return 0; + + out_ioremap: + release_resource(osdev->reg_res); + kfree(osdev->reg_res); + out_region: + return err; +} + +static void kbase_common_reg_unmap(kbase_device * const kbdev) +{ + struct kbase_os_device *osdev = &kbdev->osdev; + + iounmap(osdev->reg); + release_resource(osdev->reg_res); + kfree(osdev->reg_res); +} +#endif /* CONFIG_MALI_NO_MALI */ + +static int kbase_common_device_init(kbase_device *kbdev) +{ + struct kbase_os_device *osdev = &kbdev->osdev; + int err = -ENOMEM; + mali_error mali_err; + enum { + inited_mem = (1u << 0), + inited_job_slot = (1u << 1), + inited_pm = (1u << 2), + inited_js = (1u << 3), + inited_irqs = (1u << 4) + , inited_debug = (1u << 5) + , inited_js_softstop = (1u << 6) +#if MALI_CUSTOMER_RELEASE == 0 + , inited_js_timeouts = (1u << 7) +#endif /* MALI_CUSTOMER_RELEASE == 0 */ + /* BASE_HW_ISSUE_8401 */ + , inited_workaround = (1u << 8) + , inited_pm_runtime_init = (1u << 9) + , inited_gpu_memory = (1u << 10) +#ifdef CONFIG_MALI_DEBUG_SHADER_SPLIT_FS + ,inited_sc_split = (1u << 11) +#endif /* CONFIG_MALI_DEBUG_SHADER_SPLIT_FS */ +#ifdef CONFIG_MALI_TRACE_TIMELINE + ,inited_timeline = (1u << 12) +#endif /* CONFIG_MALI_TRACE_LINE */ + }; + + int inited = 0; + + dev_set_drvdata(osdev->dev, kbdev); + + osdev->mdev.minor = MISC_DYNAMIC_MINOR; + osdev->mdev.name = osdev->devname; + osdev->mdev.fops = &kbase_fops; + osdev->mdev.parent = get_device(osdev->dev); + + scnprintf(osdev->devname, DEVNAME_SIZE, "%s%d", kbase_drv_name, kbase_dev_nr++); + + if (misc_register(&osdev->mdev)) { + dev_err(osdev->dev, "Couldn't register misc dev %s\n", osdev->devname); + err = -EINVAL; + goto out_misc; + } + + if (device_create_file(osdev->dev, &dev_attr_power_policy)) { + dev_err(osdev->dev, "Couldn't create power_policy sysfs file\n"); + goto out_file; + } + + if (device_create_file(osdev->dev, &dev_attr_core_availability_policy)) { + dev_err(osdev->dev, "Couldn't create core_availability_policy sysfs file\n"); + goto out_file_core_availability_policy; + } + + if (device_create_file(osdev->dev, &dev_attr_core_mask)) { + dev_err(osdev->dev, "Couldn't create core_mask sysfs file\n"); + goto out_file_core_mask; + } + + down(&kbase_dev_list_lock); + list_add(&osdev->entry, &kbase_dev_list); + up(&kbase_dev_list_lock); + dev_info(osdev->dev, "Probed as %s\n", dev_name(osdev->mdev.this_device)); + + mali_err = kbase_pm_init(kbdev); + if (MALI_ERROR_NONE != mali_err) + goto out_partial; + + inited |= inited_pm; + + if (kbdev->pm.callback_power_runtime_init) { + mali_err = kbdev->pm.callback_power_runtime_init(kbdev); + if (MALI_ERROR_NONE != mali_err) + goto out_partial; + + inited |= inited_pm_runtime_init; + } + + mali_err = kbase_mem_init(kbdev); + if (MALI_ERROR_NONE != mali_err) + goto out_partial; + + inited |= inited_mem; + + mali_err = kbase_job_slot_init(kbdev); + if (MALI_ERROR_NONE != mali_err) + goto out_partial; + + inited |= inited_job_slot; + + mali_err = kbasep_js_devdata_init(kbdev); + if (MALI_ERROR_NONE != mali_err) + goto out_partial; + + inited |= inited_js; + + err = kbase_install_interrupts(kbdev); + if (err) + goto out_partial; + + inited |= inited_irqs; + +#ifdef CONFIG_MALI_DEBUG_SHADER_SPLIT_FS + if (device_create_file(osdev->dev, &dev_attr_sc_split)) + { + dev_err(osdev->dev, "Couldn't create sc_split sysfs file\n"); + goto out_partial; + } + + inited |= inited_sc_split; +#endif /* CONFIG_MALI_DEBUG_SHADER_SPLIT_FS */ + + if (device_create_file(osdev->dev, &dev_attr_gpu_memory)) { + dev_err(osdev->dev, "Couldn't create gpu_memory sysfs file\n"); + goto out_partial; + } + inited |= inited_gpu_memory; +#ifdef CONFIG_MALI_DEBUG + + if (device_create_file(osdev->dev, &dev_attr_debug_command)) { + dev_err(osdev->dev, "Couldn't create debug_command sysfs file\n"); + goto out_partial; + } + inited |= inited_debug; + + if (device_create_file(osdev->dev, &dev_attr_js_softstop_always)) { + dev_err(osdev->dev, "Couldn't create js_softstop_always sysfs file\n"); + goto out_partial; + } + inited |= inited_js_softstop; +#endif /* CONFIG_MALI_DEBUG */ + +#if MALI_CUSTOMER_RELEASE == 0 + if (device_create_file(osdev->dev, &dev_attr_js_timeouts)) { + dev_err(osdev->dev, "Couldn't create js_timeouts sysfs file\n"); + goto out_partial; + } + inited |= inited_js_timeouts; +#endif /* MALI_CUSTOMER_RELEASE */ + +#ifdef CONFIG_MALI_TRACE_TIMELINE + if (kbasep_trace_timeline_debugfs_init(kbdev)) { + dev_err(osdev->dev, "Couldn't create mali_timeline_defs debugfs file\n"); + goto out_partial; + } + inited |= inited_timeline; +#endif /* CONFIG_MALI_TRACE_TIMELINE */ + + if (kbase_hw_has_issue(kbdev, BASE_HW_ISSUE_8401)) { + if (MALI_ERROR_NONE != kbasep_8401_workaround_init(kbdev)) + goto out_partial; + + inited |= inited_workaround; + } + + mali_err = kbase_pm_powerup(kbdev); + if (MALI_ERROR_NONE == mali_err) { +#ifdef CONFIG_MALI_DEBUG +#ifndef CONFIG_MALI_NO_MALI + if (MALI_ERROR_NONE != kbasep_common_test_interrupt_handlers(kbdev)) { + dev_err(osdev->dev, "Interrupt assigment check failed.\n"); + err = -EINVAL; + goto out_partial; + } +#endif /* CONFIG_MALI_NO_MALI */ +#endif /* CONFIG_MALI_DEBUG */ + + /* intialise the kctx list */ + mutex_init(&kbdev->kctx_list_lock); + INIT_LIST_HEAD(&kbdev->kctx_list); + return 0; + } + + out_partial: + if (kbase_hw_has_issue(kbdev, BASE_HW_ISSUE_8401)) { + if (inited & inited_workaround) + kbasep_8401_workaround_term(kbdev); + } +#ifdef CONFIG_MALI_TRACE_TIMELINE + if (inited & inited_timeline) + kbasep_trace_timeline_debugfs_term(kbdev); +#endif /* CONFIG_MALI_TRACE_TIMELINE */ +#if MALI_CUSTOMER_RELEASE == 0 + if (inited & inited_js_timeouts) + device_remove_file(kbdev->osdev.dev, &dev_attr_js_timeouts); +#endif /* MALI_CUSTOMER_RELEASE */ +#ifdef CONFIG_MALI_DEBUG + if (inited & inited_js_softstop) + device_remove_file(kbdev->osdev.dev, &dev_attr_js_softstop_always); + + if (inited & inited_debug) + device_remove_file(kbdev->osdev.dev, &dev_attr_debug_command); + +#endif /* CONFIG_MALI_DEBUG */ + if (inited & inited_gpu_memory) + device_remove_file(kbdev->osdev.dev, &dev_attr_gpu_memory); + +#ifdef CONFIG_MALI_DEBUG_SHADER_SPLIT_FS + if (inited & inited_sc_split) + { + device_remove_file(kbdev->osdev.dev, &dev_attr_sc_split); + } +#endif /* CONFIG_MALI_DEBUG_SHADER_SPLIT_FS */ + + if (inited & inited_js) + kbasep_js_devdata_halt(kbdev); + + if (inited & inited_job_slot) + kbase_job_slot_halt(kbdev); + + if (inited & inited_mem) + kbase_mem_halt(kbdev); + + if (inited & inited_pm) + kbase_pm_halt(kbdev); + + if (inited & inited_irqs) + kbase_release_interrupts(kbdev); + + if (inited & inited_js) + kbasep_js_devdata_term(kbdev); + + if (inited & inited_job_slot) + kbase_job_slot_term(kbdev); + + if (inited & inited_mem) + kbase_mem_term(kbdev); + + if (inited & inited_pm_runtime_init) { + if (kbdev->pm.callback_power_runtime_term) + kbdev->pm.callback_power_runtime_term(kbdev); + } + + if (inited & inited_pm) + kbase_pm_term(kbdev); + + down(&kbase_dev_list_lock); + list_del(&osdev->entry); + up(&kbase_dev_list_lock); + + device_remove_file(kbdev->osdev.dev, &dev_attr_core_mask); + out_file_core_mask: + device_remove_file(kbdev->osdev.dev, &dev_attr_core_availability_policy); + out_file_core_availability_policy: + device_remove_file(kbdev->osdev.dev, &dev_attr_power_policy); + out_file: + misc_deregister(&kbdev->osdev.mdev); + out_misc: + put_device(osdev->dev); + return err; +} + +static int kbase_platform_device_probe(struct platform_device *pdev) +{ + struct kbase_device *kbdev; + struct kbase_os_device *osdev; + struct resource *reg_res; + kbase_attribute *platform_data; + int err; + int i; + struct mali_base_gpu_core_props *core_props; +#ifdef CONFIG_MALI_NO_MALI + mali_error mali_err; +#endif /* CONFIG_MALI_NO_MALI */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) && CONFIG_CHROMEOS + kbase_platform_config *config; + int attribute_count; + struct resource resources[PLATFORM_CONFIG_RESOURCE_COUNT]; + + config = kbase_get_platform_config(); + if (config == NULL) + { + printk(KERN_ERR KBASE_DRV_NAME "couldn't get platform config\n"); + return -ENODEV; + } + + attribute_count = kbasep_get_config_attribute_count(config->attributes); + + kbasep_config_parse_io_resources(config->io_resources, resources); + + err = platform_device_add_data(pdev, config->attributes, attribute_count * sizeof(config->attributes[0])); + if (err) + goto out; +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) && CONFIG_CHROMEOS */ + + kbdev = kbase_device_alloc(); + if (!kbdev) { + dev_err(&pdev->dev, "Can't allocate device\n"); + err = -ENOMEM; + goto out; + } + +#ifdef CONFIG_MALI_NO_MALI + mali_err = midg_device_create(kbdev); + if (MALI_ERROR_NONE != mali_err) { + dev_err(&pdev->dev, "Can't initialize dummy model\n"); + err = -ENOMEM; + goto out_midg; + } +#endif /* CONFIG_MALI_NO_MALI */ + + osdev = &kbdev->osdev; + osdev->dev = &pdev->dev; + platform_data = (kbase_attribute *) osdev->dev->platform_data; + + if (NULL == platform_data) { + dev_err(osdev->dev, "Platform data not specified\n"); + err = -ENOENT; + goto out_free_dev; + } + + if (MALI_TRUE != kbasep_validate_configuration_attributes(kbdev, platform_data)) { + dev_err(osdev->dev, "Configuration attributes failed to validate\n"); + err = -EINVAL; + goto out_free_dev; + } + kbdev->config_attributes = platform_data; + + /* 3 IRQ resources */ + for (i = 0; i < 3; i++) { + struct resource *irq_res; + + irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, i); + if (!irq_res) { + dev_err(osdev->dev, "No IRQ resource at index %d\n", i); + err = -ENOENT; + goto out_free_dev; + } + + osdev->irqs[i].irq = irq_res->start; + osdev->irqs[i].flags = (irq_res->flags & IRQF_TRIGGER_MASK); + } + + /* the first memory resource is the physical address of the GPU registers */ + reg_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!reg_res) { + dev_err(&pdev->dev, "Invalid register resource\n"); + err = -ENOENT; + goto out_free_dev; + } + + osdev->reg_start = reg_res->start; + osdev->reg_size = resource_size(reg_res); + + err = kbase_common_reg_map(kbdev); + if (err) + goto out_free_dev; + + if (MALI_ERROR_NONE != kbase_device_init(kbdev)) { + dev_err(&pdev->dev, "Can't initialize device\n"); + err = -ENOMEM; + goto out_reg_unmap; + } +#ifdef CONFIG_UMP + kbdev->memdev.ump_device_id = kbasep_get_config_value(kbdev, platform_data, KBASE_CONFIG_ATTR_UMP_DEVICE); +#endif /* CONFIG_UMP */ + + kbdev->memdev.per_process_memory_limit = kbasep_get_config_value(kbdev, platform_data, KBASE_CONFIG_ATTR_MEMORY_PER_PROCESS_LIMIT); + + /* obtain min/max configured gpu frequencies */ + core_props = &(kbdev->gpu_props.props.core_props); + core_props->gpu_freq_khz_min = kbasep_get_config_value(kbdev, platform_data, KBASE_CONFIG_ATTR_GPU_FREQ_KHZ_MIN); + core_props->gpu_freq_khz_max = kbasep_get_config_value(kbdev, platform_data, KBASE_CONFIG_ATTR_GPU_FREQ_KHZ_MAX); + kbdev->gpu_props.irq_throttle_time_us = kbasep_get_config_value(kbdev, platform_data, KBASE_CONFIG_ATTR_GPU_IRQ_THROTTLE_TIME_US); + + err = kbase_common_device_init(kbdev); + if (err) { + dev_err(osdev->dev, "Failed kbase_common_device_init\n"); + goto out_term_dev; + } + return 0; + + out_term_dev: + kbase_device_term(kbdev); + out_reg_unmap: + kbase_common_reg_unmap(kbdev); + out_free_dev: +#ifdef CONFIG_MALI_NO_MALI + midg_device_destroy(kbdev); + out_midg: +#endif /* CONFIG_MALI_NO_MALI */ + kbase_device_free(kbdev); + out: + return err; +} + +static int kbase_common_device_remove(struct kbase_device *kbdev) +{ + if (kbase_hw_has_issue(kbdev, BASE_HW_ISSUE_8401)) + kbasep_8401_workaround_term(kbdev); + + if (kbdev->pm.callback_power_runtime_term) + kbdev->pm.callback_power_runtime_term(kbdev); + + /* Remove the sys power policy file */ + device_remove_file(kbdev->osdev.dev, &dev_attr_power_policy); + device_remove_file(kbdev->osdev.dev, &dev_attr_core_availability_policy); + device_remove_file(kbdev->osdev.dev, &dev_attr_core_mask); + +#ifdef CONFIG_MALI_TRACE_TIMELINE + kbasep_trace_timeline_debugfs_term(kbdev); +#endif /* CONFIG_MALI_TRACE_TIMELINE */ + +#ifdef CONFIG_MALI_DEBUG + device_remove_file(kbdev->osdev.dev, &dev_attr_js_softstop_always); + device_remove_file(kbdev->osdev.dev, &dev_attr_debug_command); +#endif /* CONFIG_MALI_DEBUG */ + device_remove_file(kbdev->osdev.dev, &dev_attr_gpu_memory); + +#ifdef CONFIG_MALI_DEBUG_SHADER_SPLIT_FS + device_remove_file(kbdev->osdev.dev, &dev_attr_sc_split); +#endif /* CONFIG_MALI_DEBUG_SHADER_SPLIT_FS */ + + kbasep_js_devdata_halt(kbdev); + kbase_job_slot_halt(kbdev); + kbase_mem_halt(kbdev); + kbase_pm_halt(kbdev); + + kbase_release_interrupts(kbdev); + + kbasep_js_devdata_term(kbdev); + kbase_job_slot_term(kbdev); + kbase_mem_term(kbdev); + kbase_pm_term(kbdev); + + down(&kbase_dev_list_lock); + list_del(&kbdev->osdev.entry); + up(&kbase_dev_list_lock); + + misc_deregister(&kbdev->osdev.mdev); + put_device(kbdev->osdev.dev); + kbase_common_reg_unmap(kbdev); + kbase_device_term(kbdev); +#ifdef CONFIG_MALI_NO_MALI + midg_device_destroy(kbdev); +#endif /* CONFIG_MALI_NO_MALI */ + kbase_device_free(kbdev); + + return 0; +} + +static int kbase_platform_device_remove(struct platform_device *pdev) +{ + struct kbase_device *kbdev = to_kbase_device(&pdev->dev); + + if (!kbdev) + return -ENODEV; + + return kbase_common_device_remove(kbdev); +} + +/** Suspend callback from the OS. + * + * This is called by Linux when the device should suspend. + * + * @param dev The device to suspend + * + * @return A standard Linux error code + */ +static int kbase_device_suspend(struct device *dev) +{ + struct kbase_device *kbdev = to_kbase_device(dev); + + if (!kbdev) + return -ENODEV; + + kbase_pm_suspend(kbdev); + return 0; +} + +/** Resume callback from the OS. + * + * This is called by Linux when the device should resume from suspension. + * + * @param dev The device to resume + * + * @return A standard Linux error code + */ +static int kbase_device_resume(struct device *dev) +{ + struct kbase_device *kbdev = to_kbase_device(dev); + + if (!kbdev) + return -ENODEV; + + kbase_pm_resume(kbdev); + return 0; +} + +/** Runtime suspend callback from the OS. + * + * This is called by Linux when the device should prepare for a condition in which it will + * not be able to communicate with the CPU(s) and RAM due to power management. + * + * @param dev The device to suspend + * + * @return A standard Linux error code + */ +#ifdef CONFIG_PM_RUNTIME +static int kbase_device_runtime_suspend(struct device *dev) +{ + struct kbase_device *kbdev = to_kbase_device(dev); + + if (!kbdev) + return -ENODEV; + + if (kbdev->pm.callback_power_runtime_off) { + kbdev->pm.callback_power_runtime_off(kbdev); + KBASE_DEBUG_PRINT_INFO(KBASE_PM, "runtime suspend\n"); + } + return 0; +} +#endif /* CONFIG_PM_RUNTIME */ + +/** Runtime resume callback from the OS. + * + * This is called by Linux when the device should go into a fully active state. + * + * @param dev The device to suspend + * + * @return A standard Linux error code + */ + +#ifdef CONFIG_PM_RUNTIME +int kbase_device_runtime_resume(struct device *dev) +{ + int ret = 0; + struct kbase_device *kbdev = to_kbase_device(dev); + + if (!kbdev) + return -ENODEV; + + if (kbdev->pm.callback_power_runtime_on) { + ret = kbdev->pm.callback_power_runtime_on(kbdev); + KBASE_DEBUG_PRINT_INFO(KBASE_PM, "runtime resume\n"); + } + return ret; +} +#endif /* CONFIG_PM_RUNTIME */ + +/** Runtime idle callback from the OS. + * + * This is called by Linux when the device appears to be inactive and it might be + * placed into a low power state + * + * @param dev The device to suspend + * + * @return A standard Linux error code + */ + +#ifdef CONFIG_PM_RUNTIME +static int kbase_device_runtime_idle(struct device *dev) +{ + /* Avoid pm_runtime_suspend being called */ + return 1; +} +#endif /* CONFIG_PM_RUNTIME */ + +/** The power management operations for the platform driver. + */ +static const struct dev_pm_ops kbase_pm_ops = { + .suspend = kbase_device_suspend, + .resume = kbase_device_resume, +#ifdef CONFIG_PM_RUNTIME + .runtime_suspend = kbase_device_runtime_suspend, + .runtime_resume = kbase_device_runtime_resume, + .runtime_idle = kbase_device_runtime_idle, +#endif /* CONFIG_PM_RUNTIME */ +}; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) && CONFIG_CHROMEOS +static const struct of_device_id mali_of_match[] = { + { + .compatible = "arm,mali", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, mali_of_match); +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) && CONFIG_CHROMEOS */ + +static struct platform_driver kbase_platform_driver = { + .probe = kbase_platform_device_probe, + .remove = kbase_platform_device_remove, + .driver = { + .name = kbase_drv_name, + .owner = THIS_MODULE, + .pm = &kbase_pm_ops, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) && CONFIG_CHROMEOS + .of_match_table = mali_of_match, +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) && CONFIG_CHROMEOS */ + }, +}; + +#ifdef CONFIG_MALI_PLATFORM_FAKE +static struct platform_device *mali_device; +#endif /* CONFIG_MALI_PLATFORM_FAKE */ + +static int __init kbase_driver_init(void) +{ + int err; +#ifdef CONFIG_MALI_PLATFORM_FAKE +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 8, 0)) || !CONFIG_CHROMEOS + kbase_platform_config *config; + int attribute_count; + struct resource resources[PLATFORM_CONFIG_RESOURCE_COUNT]; + + config = kbase_get_platform_config(); + if (config == NULL) + { + printk(KERN_ERR KBASE_DRV_NAME "couldn't get platform config\n"); + return -ENODEV; + } + + attribute_count = kbasep_get_config_attribute_count(config->attributes); +#ifdef CONFIG_MACH_MANTA + err = platform_device_add_data(&exynos5_device_g3d, config->attributes, attribute_count * sizeof(config->attributes[0])); + if (err) + return err; +#else + + mali_device = platform_device_alloc(kbase_drv_name, 0); + if (mali_device == NULL) + return -ENOMEM; + + kbasep_config_parse_io_resources(config->io_resources, resources); + err = platform_device_add_resources(mali_device, resources, PLATFORM_CONFIG_RESOURCE_COUNT); + if (err) { + platform_device_put(mali_device); + mali_device = NULL; + return err; + } + + err = platform_device_add_data(mali_device, config->attributes, attribute_count * sizeof(config->attributes[0])); + if (err) { + platform_device_unregister(mali_device); + mali_device = NULL; + return err; + } + + err = platform_device_add(mali_device); + if (err) { + platform_device_unregister(mali_device); + mali_device = NULL; + return err; + } +#endif /* CONFIG_CONFIG_MACH_MANTA */ +#endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(3, 8, 0)) || !CONFIG_CHROMEOS */ +#endif /* CONFIG_MALI_PLATFORM_FAKE */ + err = platform_driver_register(&kbase_platform_driver); + if (err) + return err; + + return 0; +} + +static void __exit kbase_driver_exit(void) +{ + platform_driver_unregister(&kbase_platform_driver); +#ifdef CONFIG_MALI_PLATFORM_FAKE + if (mali_device) + platform_device_unregister(mali_device); +#endif /* CONFIG_MALI_PLATFORM_FAKE */ +} + +module_init(kbase_driver_init); +module_exit(kbase_driver_exit); + +MODULE_LICENSE("GPL"); +MODULE_VERSION(MALI_RELEASE_NAME); + +#ifdef CONFIG_MALI_GATOR_SUPPORT +/* Create the trace points (otherwise we just get code to call a tracepoint) */ +#define CREATE_TRACE_POINTS +#include "mali_linux_trace.h" + +void kbase_trace_mali_pm_status(u32 event, u64 value) +{ + trace_mali_pm_status(event, value); +} + +void kbase_trace_mali_pm_power_off(u32 event, u64 value) +{ + trace_mali_pm_power_off(event, value); +} + +void kbase_trace_mali_pm_power_on(u32 event, u64 value) +{ + trace_mali_pm_power_on(event, value); +} + +void kbase_trace_mali_job_slots_event(u32 event, const kbase_context *kctx) +{ + trace_mali_job_slots_event(event, (kctx != NULL ? kctx->osctx.tgid : 0), 0); +} + +void kbase_trace_mali_page_fault_insert_pages(int event, u32 value) +{ + trace_mali_page_fault_insert_pages(event, value); +} + +void kbase_trace_mali_mmu_as_in_use(int event) +{ + trace_mali_mmu_as_in_use(event); +} + +void kbase_trace_mali_mmu_as_released(int event) +{ + trace_mali_mmu_as_released(event); +} + +void kbase_trace_mali_total_alloc_pages_change(long long int event) +{ + trace_mali_total_alloc_pages_change(event); +} +#endif /* CONFIG_MALI_GATOR_SUPPORT */ diff --git a/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_linux.h b/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_linux.h new file mode 100755 index 00000000000..02120e1d372 --- /dev/null +++ b/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_linux.h @@ -0,0 +1,79 @@ +/* + * + * (C) COPYRIGHT 2010-2012 ARM Limited. All rights reserved. + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * A copy of the licence is included with the program, and can also be obtained + * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + + + +/** + * @file mali_kbase_linux.h + * Base kernel APIs, Linux implementation. + */ + +#ifndef _KBASE_LINUX_H_ +#define _KBASE_LINUX_H_ + +/* All things that are needed for the Linux port. */ +#include <linux/platform_device.h> +#include <linux/miscdevice.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/atomic.h> + +typedef struct kbase_os_context { + u64 cookies; + struct list_head reg_pending; + wait_queue_head_t event_queue; + pid_t tgid; +} kbase_os_context; + +#define DEVNAME_SIZE 16 + +typedef struct kbase_os_device { + struct list_head entry; + struct device *dev; + struct miscdevice mdev; + u64 reg_start; + size_t reg_size; + void __iomem *reg; + struct resource *reg_res; + struct { + int irq; + int flags; + } irqs[3]; + char devname[DEVNAME_SIZE]; + +#ifdef CONFIG_MALI_NO_MALI + void *model; + struct kmem_cache *irq_slab; + struct workqueue_struct *irq_workq; + atomic_t serving_job_irq; + atomic_t serving_gpu_irq; + atomic_t serving_mmu_irq; + spinlock_t reg_op_lock; +#endif /* CONFIG_MALI_NO_MALI */ +} kbase_os_device; + +#if defined(MALI_KERNEL_TEST_API) +#if (1 == MALI_KERNEL_TEST_API) +#define KBASE_EXPORT_TEST_API(func) EXPORT_SYMBOL(func); +#else +#define KBASE_EXPORT_TEST_API(func) +#endif +#else +#define KBASE_EXPORT_TEST_API(func) +#endif + +#define KBASE_EXPORT_SYMBOL(func) EXPORT_SYMBOL(func); + +#endif /* _KBASE_LINUX_H_ */ diff --git a/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_mem_linux.c b/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_mem_linux.c new file mode 100755 index 00000000000..571745453de --- /dev/null +++ b/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_mem_linux.c @@ -0,0 +1,724 @@ +/* + * + * (C) COPYRIGHT 2010-2013 ARM Limited. All rights reserved. + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * A copy of the licence is included with the program, and can also be obtained + * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + + + +/** + * @file mali_kbase_mem_linux.c + * Base kernel memory APIs, Linux implementation. + */ + +#include <linux/kernel.h> +#include <linux/bug.h> +#include <linux/mm.h> +#include <linux/fs.h> +#include <linux/dma-mapping.h> +#ifdef CONFIG_DMA_SHARED_BUFFER +#include <linux/dma-buf.h> +#endif /* defined(CONFIG_DMA_SHARED_BUFFER) */ + +#include <kbase/src/common/mali_kbase.h> +#include <kbase/src/linux/mali_kbase_mem_linux.h> + +static int kbase_tracking_page_setup(struct kbase_context * kctx, struct vm_area_struct * vma); + +struct kbase_va_region *kbase_pmem_alloc(kbase_context *kctx, u32 size, u32 flags, u16 * const pmem_cookie) +{ + struct kbase_va_region *reg; + u16 cookie; + + KBASE_DEBUG_ASSERT(kctx != NULL); + KBASE_DEBUG_ASSERT(pmem_cookie != NULL); + + if (0 == size) + goto out1; + + if (!kbase_check_alloc_flags(flags)) + goto out1; + + reg = kbase_alloc_free_region(kctx, 0, size, KBASE_REG_ZONE_PMEM); + if (!reg) + goto out1; + + reg->flags &= ~KBASE_REG_FREE; + + kbase_update_region_flags(reg, flags, MALI_FALSE); + + if (kbase_alloc_phy_pages(reg, size, size)) + goto out2; + + reg->nr_alloc_pages = size; + reg->extent = 0; + + kbase_gpu_vm_lock(kctx); + if (!kctx->osctx.cookies) + goto out3; + + cookie = __ffs(kctx->osctx.cookies); + kctx->osctx.cookies &= ~(1UL << cookie); + reg->flags &= ~KBASE_REG_COOKIE_MASK; + reg->flags |= KBASE_REG_COOKIE(cookie); + + list_add(®->link, &kctx->osctx.reg_pending); + + *pmem_cookie = cookie; + kbase_gpu_vm_unlock(kctx); + + return reg; + + out3: + kbase_gpu_vm_unlock(kctx); + kbase_free_phy_pages(reg); + out2: + kfree(reg); + out1: + return NULL; + +} + +KBASE_EXPORT_TEST_API(kbase_pmem_alloc) + +/* + * Callback for munmap(). PMEM receives a special treatment, as it + * frees the memory at the same time it gets unmapped. This avoids the + * map/unmap race where map reuses a memory range that has been + * unmapped from CPU, but still mapped on GPU. + */ +STATIC void kbase_cpu_vm_close(struct vm_area_struct *vma) +{ + struct kbase_va_region *reg = vma->vm_private_data; + kbase_context *kctx = reg->kctx; + mali_error err; + + kbase_gpu_vm_lock(kctx); + + err = kbase_cpu_free_mapping(reg, vma); + if (!err && (reg->flags & KBASE_REG_ZONE_MASK) == KBASE_REG_ZONE_PMEM) + kbase_mem_free_region(kctx, reg); + + kbase_gpu_vm_unlock(kctx); +} + +KBASE_EXPORT_TEST_API(kbase_cpu_vm_close) + +static const struct vm_operations_struct kbase_vm_ops = { + .close = kbase_cpu_vm_close, +}; + +static int kbase_cpu_mmap(struct kbase_va_region *reg, struct vm_area_struct *vma, void *kaddr, u32 nr_pages) +{ + struct kbase_cpu_mapping *map; + u64 start_off = vma->vm_pgoff - reg->start_pfn; + phys_addr_t *page_array; + int err = 0; + int i; + + map = kzalloc(sizeof(*map), GFP_KERNEL); + + if (!map) { + WARN_ON(1); + err = -ENOMEM; + goto out; + } + + /* + * VM_DONTCOPY - don't make this mapping available in fork'ed processes + * VM_DONTEXPAND - disable mremap on this region + * VM_IO - disables paging + * VM_DONTDUMP - Don't include in core dumps (3.7 only) + * VM_MIXEDMAP - Support mixing struct page*s and raw pfns. + * This is needed to support using the dedicated and + * the OS based memory backends together. + */ + /* + * This will need updating to propagate coherency flags + * See MIDBASE-1057 + */ + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)) + vma->vm_flags |= VM_DONTCOPY | VM_DONTDUMP | VM_DONTEXPAND | VM_IO | VM_MIXEDMAP; +#else + vma->vm_flags |= VM_DONTCOPY | VM_DONTEXPAND | VM_RESERVED | VM_IO | VM_MIXEDMAP; +#endif + vma->vm_ops = &kbase_vm_ops; + vma->vm_private_data = reg; + + page_array = kbase_get_phy_pages(reg); + + if (!(reg->flags & KBASE_REG_CPU_CACHED)) { + /* We can't map vmalloc'd memory uncached. + * Other memory will have been returned from + * kbase_phy_pages_alloc which should have done the cache + * maintenance necessary to support an uncached mapping + */ + BUG_ON(kaddr); + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + } + + if (!kaddr) { + for (i = 0; i < nr_pages; i++) { + err = vm_insert_mixed(vma, vma->vm_start + (i << PAGE_SHIFT), page_array[i + start_off] >> PAGE_SHIFT); + WARN_ON(err); + if (err) + break; + } + } else { + /* vmalloc remaping is easy... */ + err = remap_vmalloc_range(vma, kaddr, 0); + WARN_ON(err); + } + + if (err) { + kfree(map); + goto out; + } + + map->uaddr = (void *)vma->vm_start; + map->nr_pages = nr_pages; + map->page_off = start_off; + map->private = vma; + + if ( (reg->flags & KBASE_REG_ZONE_MASK) == KBASE_REG_ZONE_TMEM) + { + kbase_process_page_usage_dec(reg->kctx, nr_pages); + } + + list_add(&map->link, ®->map_list); + + out: + return err; +} + +static int kbase_trace_buffer_mmap(kbase_context *kctx, struct vm_area_struct *vma, struct kbase_va_region **const reg, void **const kaddr) +{ + struct kbase_va_region *new_reg; + u32 nr_pages; + size_t size; + int err = 0; + u32 *tb; + + pr_debug("in %s\n", __func__); + size = (vma->vm_end - vma->vm_start); + nr_pages = size >> PAGE_SHIFT; + + if (!kctx->jctx.tb) { + KBASE_DEBUG_ASSERT(0 != size); + tb = vmalloc_user(size); + + if (NULL == tb) { + err = -ENOMEM; + goto out; + } + + kbase_device_trace_buffer_install(kctx, tb, size); + } else { + err = -EINVAL; + goto out; + } + + *kaddr = kctx->jctx.tb; + + new_reg = kbase_alloc_free_region(kctx, 0, nr_pages, KBASE_REG_ZONE_PMEM); + if (!new_reg) { + err = -ENOMEM; + WARN_ON(1); + goto out_disconnect; + } + + new_reg->flags &= ~KBASE_REG_FREE; + new_reg->flags |= KBASE_REG_IS_TB | KBASE_REG_CPU_CACHED; + new_reg->nr_alloc_pages = nr_pages; + if (MALI_ERROR_NONE != kbase_add_va_region(kctx, new_reg, vma->vm_start, nr_pages, 1)) { + err = -ENOMEM; + WARN_ON(1); + goto out_va_region; + } + + *reg = new_reg; + + /* map read only, noexec */ + vma->vm_flags &= ~(VM_WRITE | VM_EXEC); + /* the rest of the flags is added by the cpu_mmap handler */ + + pr_debug("%s done\n", __func__); + return 0; + + out_va_region: + kbase_free_alloced_region(new_reg); + out_disconnect: + kbase_device_trace_buffer_uninstall(kctx); + vfree(tb); + out: + return err; + +} + +static int kbase_mmu_dump_mmap(kbase_context *kctx, struct vm_area_struct *vma, struct kbase_va_region **const reg, void **const kmap_addr) +{ + struct kbase_va_region *new_reg; + void *kaddr; + u32 nr_pages; + size_t size; + int err = 0; + + pr_debug("in kbase_mmu_dump_mmap\n"); + size = (vma->vm_end - vma->vm_start); + nr_pages = size >> PAGE_SHIFT; + + kaddr = kbase_mmu_dump(kctx, nr_pages); + + if (!kaddr) { + err = -ENOMEM; + goto out; + } + + new_reg = kbase_alloc_free_region(kctx, 0, nr_pages, KBASE_REG_ZONE_PMEM); + if (!new_reg) { + err = -ENOMEM; + WARN_ON(1); + goto out; + } + + new_reg->flags &= ~KBASE_REG_FREE; + new_reg->flags |= KBASE_REG_IS_MMU_DUMP | KBASE_REG_CPU_CACHED; + new_reg->nr_alloc_pages = nr_pages; + if (MALI_ERROR_NONE != kbase_add_va_region(kctx, new_reg, vma->vm_start, nr_pages, 1)) { + err = -ENOMEM; + WARN_ON(1); + goto out_va_region; + } + + *kmap_addr = kaddr; + *reg = new_reg; + + pr_debug("kbase_mmu_dump_mmap done\n"); + return 0; + + out_va_region: + kbase_free_alloced_region(new_reg); + out: + return err; +} + +/* must be called with the gpu vm lock held */ + +struct kbase_va_region *kbase_lookup_cookie(kbase_context *kctx, mali_addr64 cookie) +{ + struct kbase_va_region *reg; + struct list_head *pos; + mali_addr64 test_cookie; + + KBASE_DEBUG_ASSERT(kctx != NULL); + BUG_ON(!mutex_is_locked(&kctx->reg_lock)); + + test_cookie = KBASE_REG_COOKIE(cookie); + + list_for_each(pos, &kctx->osctx.reg_pending) { + reg = list_entry(pos, struct kbase_va_region, link); + if ((reg->flags & KBASE_REG_COOKIE_MASK) == test_cookie) + return reg; + } + + return NULL; /* not found */ +} + +KBASE_EXPORT_TEST_API(kbase_lookup_cookie) + +void kbase_unlink_cookie(kbase_context *kctx, mali_addr64 cookie, struct kbase_va_region *reg) +{ + KBASE_DEBUG_ASSERT(kctx != NULL); + KBASE_DEBUG_ASSERT(reg != NULL); + KBASE_DEBUG_ASSERT(MALI_TRUE == kbasep_list_member_of(&kctx->osctx.reg_pending, ®->link)); + KBASE_DEBUG_ASSERT(KBASE_REG_COOKIE(cookie) == (reg->flags & KBASE_REG_COOKIE_MASK)); + KBASE_DEBUG_ASSERT((kctx->osctx.cookies & (1UL << cookie)) == 0); + + list_del(®->link); + kctx->osctx.cookies |= (1UL << cookie); /* mark as resolved */ +} + +KBASE_EXPORT_TEST_API(kbase_unlink_cookie) + +void kbase_os_mem_map_lock(kbase_context *kctx) +{ + struct mm_struct *mm = current->mm; + (void)kctx; + down_read(&mm->mmap_sem); +} + +void kbase_os_mem_map_unlock(kbase_context *kctx) +{ + struct mm_struct *mm = current->mm; + (void)kctx; + up_read(&mm->mmap_sem); +} + +int kbase_mmap(struct file *file, struct vm_area_struct *vma) +{ + kbase_context *kctx = file->private_data; + struct kbase_va_region *reg; + void *kaddr = NULL; + u32 nr_pages; + int err = 0; + + pr_debug("kbase_mmap\n"); + nr_pages = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT; + + if (0 == nr_pages) { + err = -EINVAL; + goto out; + } + + kbase_gpu_vm_lock(kctx); + + if (vma->vm_pgoff == KBASE_REG_COOKIE_MTP) + { + /* The non-mapped tracking helper page */ + err = kbase_tracking_page_setup(kctx, vma); + goto out_unlock; + } + + /* if not the MTP, verify that the MTP has been mapped */ + rcu_read_lock(); + /* catches both when the special page isn't present or when we've forked */ + if (rcu_dereference(kctx->process_mm) != current->mm) + { + err = -EINVAL; + rcu_read_unlock(); + goto out_unlock; + } + rcu_read_unlock(); + + if (vma->vm_pgoff == KBASE_REG_COOKIE_RB) { + /* Ring buffer doesn't exist any more */ + err = -EINVAL; + goto out_unlock; + } else if (vma->vm_pgoff == KBASE_REG_COOKIE_TB) { + err = kbase_trace_buffer_mmap(kctx, vma, ®, &kaddr); + if (0 != err) + goto out_unlock; + pr_debug("kbase_trace_buffer_mmap ok\n"); + goto map; + } else if (vma->vm_pgoff == KBASE_REG_COOKIE_MMU_DUMP) { + /* MMU dump */ + err = kbase_mmu_dump_mmap(kctx, vma, ®, &kaddr); + if (0 != err) + goto out_unlock; + + goto map; + } + + if (vma->vm_pgoff < PAGE_SIZE) { /* first page is reserved for cookie resolution */ + /* PMEM stuff, fetch the right region */ + reg = kbase_lookup_cookie(kctx, vma->vm_pgoff); + + if (NULL != reg) { + if (reg->nr_pages != nr_pages) { + /* incorrect mmap size */ + /* leave the cookie for a potential later mapping, or to be reclaimed later when the context is freed */ + err = -ENOMEM; + goto out_unlock; + } + + kbase_unlink_cookie(kctx, vma->vm_pgoff, reg); + + if (MALI_ERROR_NONE != kbase_gpu_mmap(kctx, reg, vma->vm_start, nr_pages, 1)) { + /* Unable to map in GPU space. Recover from kbase_unlink_cookie */ + list_add(®->link, &kctx->osctx.reg_pending); + kctx->osctx.cookies &= ~(1UL << vma->vm_pgoff); + WARN_ON(1); + err = -ENOMEM; + goto out_unlock; + } + + /* + * Overwrite the offset with the + * region start_pfn, so we effectively + * map from offset 0 in the region. + */ + vma->vm_pgoff = reg->start_pfn; + goto map; + } + + err = -ENOMEM; + goto out_unlock; + } else if (vma->vm_pgoff < KBASE_REG_ZONE_EXEC_BASE) { + /* invalid offset as it identifies an already mapped pmem */ + err = -ENOMEM; + goto out_unlock; + } else { + u32 zone; + + /* TMEM case or EXEC case */ + if (vma->vm_pgoff < KBASE_REG_ZONE_TMEM_BASE) + zone = KBASE_REG_ZONE_EXEC; + else + zone = KBASE_REG_ZONE_TMEM; + + reg = kbase_region_tracker_find_region_enclosing_range(kctx, vma->vm_pgoff, nr_pages); + if (reg && (reg->flags & (KBASE_REG_ZONE_MASK | KBASE_REG_FREE)) == zone) { +#ifdef CONFIG_DMA_SHARED_BUFFER + if (reg->imported_type == BASE_TMEM_IMPORT_TYPE_UMM) + goto dma_map; +#endif /* CONFIG_DMA_SHARED_BUFFER */ + goto map; + } + + err = -ENOMEM; + goto out_unlock; + } + map: + err = kbase_cpu_mmap(reg, vma, kaddr, nr_pages); + + if (vma->vm_pgoff == KBASE_REG_COOKIE_MMU_DUMP) { + /* MMU dump - userspace should now have a reference on + * the pages, so we can now free the kernel mapping */ + vfree(kaddr); + } + goto out_unlock; + +#ifdef CONFIG_DMA_SHARED_BUFFER + dma_map: + err = dma_buf_mmap(reg->imported_metadata.umm.dma_buf, vma, vma->vm_pgoff - reg->start_pfn); +#endif /* CONFIG_DMA_SHARED_BUFFER */ + out_unlock: + kbase_gpu_vm_unlock(kctx); + out: + if (err) + pr_err("mmap failed %d\n", err); + + return err; +} + +KBASE_EXPORT_TEST_API(kbase_mmap) + +mali_error kbase_create_os_context(kbase_os_context * const osctx) +{ + KBASE_DEBUG_ASSERT(osctx != NULL); + + INIT_LIST_HEAD(&osctx->reg_pending); + osctx->cookies = ~KBASE_REG_RESERVED_COOKIES; + osctx->tgid = current->tgid; + init_waitqueue_head(&osctx->event_queue); + + return MALI_ERROR_NONE; +} + +KBASE_EXPORT_TEST_API(kbase_create_os_context) + +static void kbase_reg_pending_dtor(struct kbase_va_region *reg) +{ + kbase_free_phy_pages(reg); + pr_info("Freeing pending unmapped region\n"); + kfree(reg); +} + +void kbase_destroy_os_context(kbase_os_context *osctx) +{ + struct kbase_va_region *reg; + + KBASE_DEBUG_ASSERT(osctx != NULL); + + while (!list_empty(&osctx->reg_pending)) { + reg = list_entry(osctx->reg_pending.next, struct kbase_va_region, link); + list_del(osctx->reg_pending.next); + kbase_reg_pending_dtor(reg); + } +} + +KBASE_EXPORT_TEST_API(kbase_destroy_os_context) + +void *kbase_va_alloc(kbase_context *kctx, u32 size, kbase_hwc_dma_mapping * handle) +{ + int i; + void *va; + dma_addr_t dma_pa; + struct kbase_va_region *reg; + phys_addr_t *page_array; + + u32 pages = ((size - 1) >> PAGE_SHIFT) + 1; + u32 flags = BASE_MEM_PROT_CPU_RD | BASE_MEM_PROT_CPU_WR | BASE_MEM_PROT_GPU_RD | BASE_MEM_PROT_GPU_WR; + + KBASE_DEBUG_ASSERT(kctx != NULL); + KBASE_DEBUG_ASSERT(0 != size); + + if (size == 0) + goto err; + + va = dma_alloc_writecombine(NULL, size, &dma_pa, GFP_KERNEL); + if (!va) + goto err; + + memset(va, 0x0, size); + + /* Store the state so we can free it later. */ + handle->cpu_va = va; + handle->dma_pa = dma_pa; + handle->size = size; + + kbase_gpu_vm_lock(kctx); + + reg = kbase_alloc_free_region(kctx, 0, pages, KBASE_REG_ZONE_PMEM); + if (!reg) + goto vm_unlock; + + reg->flags &= ~KBASE_REG_FREE; + kbase_update_region_flags(reg, flags, MALI_FALSE); + + reg->nr_alloc_pages = pages; + reg->extent = 0; + + KBASE_DEBUG_ASSERT(0 != pages); + page_array = vmalloc_user(pages * sizeof(*page_array)); + + if (!page_array) + goto free_reg; + + for (i = 0; i < pages; i++) { + page_array[i] = dma_pa + (i << PAGE_SHIFT); + } + + kbase_set_phy_pages(reg, page_array); + + if (kbase_gpu_mmap(kctx, reg, (uintptr_t) va, pages, 1)) + goto free_array; + + kbase_gpu_vm_unlock(kctx); + + return va; + + free_array: + vfree(page_array); + free_reg: + kfree(reg); + vm_unlock: + kbase_gpu_vm_unlock(kctx); + dma_free_writecombine(NULL, size, va, dma_pa); + err: + return NULL; +} +KBASE_EXPORT_SYMBOL(kbase_va_alloc) + +void kbasep_os_process_page_usage_update( kbase_context *kctx, int pages ) +{ + struct mm_struct *mm; + + rcu_read_lock(); + mm = rcu_dereference(kctx->process_mm); + if (mm) + { + atomic_add(pages, &kctx->nonmapped_pages); +#ifdef SPLIT_RSS_COUNTING + add_mm_counter(mm, MM_FILEPAGES, pages); +#else + spin_lock(&mm->page_table_lock); + add_mm_counter(mm, MM_FILEPAGES, pages); + spin_unlock(&mm->page_table_lock); +#endif + } + rcu_read_unlock(); +} + +static void kbasep_os_process_page_usage_drain(kbase_context * kctx) +{ + int pages; + struct mm_struct * mm; + + spin_lock(&kctx->mm_update_lock); + mm = rcu_dereference_protected(kctx->process_mm, lockdep_is_held(&kctx->mm_update_lock)); + if (!mm) + { + spin_unlock(&kctx->mm_update_lock); + return; + } + + rcu_assign_pointer(kctx->process_mm, NULL); + spin_unlock(&kctx->mm_update_lock); + synchronize_rcu(); + + pages = atomic_xchg(&kctx->nonmapped_pages, 0); +#ifdef SPLIT_RSS_COUNTING + add_mm_counter(mm, MM_FILEPAGES, -pages); +#else + spin_lock(&mm->page_table_lock); + add_mm_counter(mm, MM_FILEPAGES, -pages); + spin_unlock(&mm->page_table_lock); +#endif +} + +static void kbase_special_vm_close(struct vm_area_struct *vma) +{ + kbase_context * kctx; + kctx = vma->vm_private_data; + kbasep_os_process_page_usage_drain(kctx); +} + +static const struct vm_operations_struct kbase_vm_special_ops = { + .close = kbase_special_vm_close, +}; + +static int kbase_tracking_page_setup(struct kbase_context * kctx, struct vm_area_struct * vma) +{ + /* check that this is the only tracking page */ + spin_lock(&kctx->mm_update_lock); + if (rcu_dereference_protected(kctx->process_mm, lockdep_is_held(&kctx->mm_update_lock))) + { + spin_unlock(&kctx->mm_update_lock); + return -EFAULT; + } + + rcu_assign_pointer(kctx->process_mm, current->mm); + + spin_unlock(&kctx->mm_update_lock); + + /* no real access */ + vma->vm_flags &= ~(VM_READ | VM_WRITE | VM_EXEC); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)) + vma->vm_flags |= VM_DONTCOPY | VM_DONTEXPAND | VM_DONTDUMP | VM_IO; +#else + vma->vm_flags |= VM_DONTCOPY | VM_DONTEXPAND | VM_RESERVED | VM_IO; +#endif + vma->vm_ops = &kbase_vm_special_ops; + vma->vm_private_data = kctx; + + return 0; +} + +void kbase_va_free(kbase_context *kctx, kbase_hwc_dma_mapping * handle) +{ + struct kbase_va_region *reg; + phys_addr_t *page_array; + mali_error err; + + KBASE_DEBUG_ASSERT(kctx != NULL); + KBASE_DEBUG_ASSERT(handle->cpu_va != NULL); + + kbase_gpu_vm_lock(kctx); + + reg = kbase_region_tracker_find_region_base_address(kctx, (uintptr_t)handle->cpu_va); + KBASE_DEBUG_ASSERT(reg); + + err = kbase_gpu_munmap(kctx, reg); + KBASE_DEBUG_ASSERT(err == MALI_ERROR_NONE); + + page_array = kbase_get_phy_pages(reg); + vfree(page_array); + + kfree(reg); + + kbase_gpu_vm_unlock(kctx); + + dma_free_writecombine(NULL, handle->size, handle->cpu_va, handle->dma_pa); +} +KBASE_EXPORT_SYMBOL(kbase_va_free) diff --git a/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_mem_linux.h b/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_mem_linux.h new file mode 100755 index 00000000000..b7269057d5b --- /dev/null +++ b/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_mem_linux.h @@ -0,0 +1,60 @@ +/* + * + * (C) COPYRIGHT 2010, 2012-2013 ARM Limited. All rights reserved. + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * A copy of the licence is included with the program, and can also be obtained + * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + + + +/** + * @file mali_kbase_mem_linux.h + * Base kernel memory APIs, Linux implementation. + */ + +#ifndef _KBASE_MEM_LINUX_H_ +#define _KBASE_MEM_LINUX_H_ + +/* This define is used by the gator kernel module compile to select which DDK + * API calling convention to use. If not defined (legacy DDK) gator assumes + * version 1. The version to DDK release mapping is: + * Version 1 API: DDK versions r1px, r2px + * Version 2 API: DDK versions r3px and newer + **/ +#define MALI_DDK_GATOR_API_VERSION 2 + +/** A HWC dump mapping */ +typedef struct kbase_hwc_dma_mapping { + void * cpu_va; + dma_addr_t dma_pa; + size_t size; +} kbase_hwc_dma_mapping; + +struct kbase_va_region *kbase_pmem_alloc(kbase_context *kctx, u32 size, u32 flags, u16 * const pmem_cookie); +int kbase_mmap(struct file *file, struct vm_area_struct *vma); + +/** @brief Allocate memory from kernel space and map it onto the GPU + * + * @param kctx The context used for the allocation/mapping + * @param size The size of the allocation in bytes + * @param handle An opaque structure used to contain the state needed to free the memory + * @return the VA for kernel space and GPU MMU + */ +void * kbase_va_alloc(kbase_context *kctx, u32 size, kbase_hwc_dma_mapping * handle); + +/** @brief Free/unmap memory allocated by kbase_va_alloc + * + * @param kctx The context used for the allocation/mapping + * @param handle An opaque structure returned by the kbase_va_alloc function. + */ +void kbase_va_free(kbase_context *kctx, kbase_hwc_dma_mapping * handle); + +#endif /* _KBASE_MEM_LINUX_H_ */ diff --git a/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_sync.c b/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_sync.c new file mode 100755 index 00000000000..be9bff803bc --- /dev/null +++ b/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_sync.c @@ -0,0 +1,190 @@ +/* + * + * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved. + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * A copy of the licence is included with the program, and can also be obtained + * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + + + +/** + * @file mali_kbase_sync.c + * + */ + +#ifdef CONFIG_SYNC + +#include <linux/sync.h> +#include <kbase/src/common/mali_kbase.h> + +struct mali_sync_timeline { + struct sync_timeline timeline; + atomic_t counter; + atomic_t signalled; +}; + +struct mali_sync_pt { + struct sync_pt pt; + u32 order; + int result; +}; + +static struct mali_sync_timeline *to_mali_sync_timeline(struct sync_timeline *timeline) +{ + return container_of(timeline, struct mali_sync_timeline, timeline); +} + +static struct mali_sync_pt *to_mali_sync_pt(struct sync_pt *pt) +{ + return container_of(pt, struct mali_sync_pt, pt); +} + +static struct sync_pt *timeline_dup(struct sync_pt *pt) +{ + struct mali_sync_pt *mpt = to_mali_sync_pt(pt); + struct mali_sync_pt *new_mpt; + struct sync_pt *new_pt = sync_pt_create(pt->parent, sizeof(struct mali_sync_pt)); + + if (!new_pt) + return NULL; + + new_mpt = to_mali_sync_pt(new_pt); + new_mpt->order = mpt->order; + new_mpt->result = mpt->result; + + return new_pt; + +} + +static int timeline_has_signaled(struct sync_pt *pt) +{ + struct mali_sync_pt *mpt = to_mali_sync_pt(pt); + struct mali_sync_timeline *mtl = to_mali_sync_timeline(pt->parent); + int result = mpt->result; + + long diff = atomic_read(&mtl->signalled) - mpt->order; + + if (diff >= 0) + { + return result < 0 ? result : 1; + } + else + return 0; +} + +static int timeline_compare(struct sync_pt *a, struct sync_pt *b) +{ + struct mali_sync_pt *ma = container_of(a, struct mali_sync_pt, pt); + struct mali_sync_pt *mb = container_of(b, struct mali_sync_pt, pt); + + long diff = ma->order - mb->order; + + if (diff < 0) + return -1; + else if (diff == 0) + return 0; + else + return 1; +} + +static void timeline_value_str(struct sync_timeline *timeline, char * str, + int size) +{ + struct mali_sync_timeline *mtl = to_mali_sync_timeline(timeline); + snprintf(str, size, "%d", atomic_read(&mtl->signalled)); +} + +static void pt_value_str(struct sync_pt *pt, char *str, int size) +{ + struct mali_sync_pt *mpt = to_mali_sync_pt(pt); + snprintf(str, size, "%d(%d)", mpt->order, mpt->result); +} + +static struct sync_timeline_ops mali_timeline_ops = { + .driver_name = "Mali", + .dup = timeline_dup, + .has_signaled = timeline_has_signaled, + .compare = timeline_compare, + .timeline_value_str = timeline_value_str, + .pt_value_str = pt_value_str, +#if 0 + .free_pt = timeline_free_pt, + .release_obj = timeline_release_obj +#endif +}; + +int kbase_sync_timeline_is_ours(struct sync_timeline *timeline) +{ + return timeline->ops == &mali_timeline_ops; +} + +struct sync_timeline *kbase_sync_timeline_alloc(const char *name) +{ + struct sync_timeline *tl; + struct mali_sync_timeline *mtl; + + tl = sync_timeline_create(&mali_timeline_ops, sizeof(struct mali_sync_timeline), name); + if (!tl) + return NULL; + + /* Set the counter in our private struct */ + mtl = to_mali_sync_timeline(tl); + atomic_set(&mtl->counter, 0); + atomic_set(&mtl->signalled, 0); + + return tl; +} + +struct sync_pt *kbase_sync_pt_alloc(struct sync_timeline *parent) +{ + struct sync_pt *pt = sync_pt_create(parent, sizeof(struct mali_sync_pt)); + struct mali_sync_timeline *mtl = to_mali_sync_timeline(parent); + struct mali_sync_pt *mpt; + + if (!pt) + return NULL; + + mpt = to_mali_sync_pt(pt); + mpt->order = atomic_inc_return(&mtl->counter); + mpt->result = 0; + + return pt; +} + +void kbase_sync_signal_pt(struct sync_pt *pt, int result) +{ + struct mali_sync_pt *mpt = to_mali_sync_pt(pt); + struct mali_sync_timeline *mtl = to_mali_sync_timeline(pt->parent); + int signalled; + long diff; + + mpt->result = result; + + do { + + signalled = atomic_read(&mtl->signalled); + + diff = signalled - mpt->order; + + if (diff > 0) { + /* The timeline is already at or ahead of this point. This should not happen unless userspace + * has been signalling fences out of order, so warn but don't violate the sync_pt API. + * The warning is only in release builds to prevent a malicious user being able to spam dmesg. + */ +#ifdef CONFIG_MALI_DEBUG + KBASE_DEBUG_PRINT_ERROR(KBASE_JD, "Fences were triggered in a different order to allocation!"); +#endif /* CONFIG_MALI_DEBUG */ + return; + } + } while (atomic_cmpxchg(&mtl->signalled, signalled, mpt->order) != signalled); +} + +#endif /* CONFIG_SYNC */ diff --git a/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_sync.h b/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_sync.h new file mode 100755 index 00000000000..9898ae40cd6 --- /dev/null +++ b/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_sync.h @@ -0,0 +1,81 @@ +/* + * + * (C) COPYRIGHT 2012 ARM Limited. All rights reserved. + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * A copy of the licence is included with the program, and can also be obtained + * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + + + +/** + * @file mali_kbase_sync.h + * + */ + +#ifndef MALI_KBASE_SYNC_H +#define MALI_KBASE_SYNC_H + +#include <linux/sync.h> +#include <malisw/mali_malisw.h> + +/* + * Create a stream object. + * Built on top of timeline object. + * Exposed as a file descriptor. + * Life-time controlled via the file descriptor: + * - dup to add a ref + * - close to remove a ref + */ +mali_error kbase_stream_create(const char *name, int *const out_fd); + +/* + * Create a fence in a stream object + */ +int kbase_stream_create_fence(int tl_fd); + +/* + * Validate a fd to be a valid fence + * No reference is taken. + * + * This function is only usable to catch unintentional user errors early, + * it does not stop malicious code changing the fd after this function returns. + */ +mali_error kbase_fence_validate(int fd); + +/* Returns true if the specified timeline is allocated by Mali */ +int kbase_sync_timeline_is_ours(struct sync_timeline *timeline); + +/* Allocates a timeline for Mali + * + * One timeline should be allocated per API context. + */ +struct sync_timeline *kbase_sync_timeline_alloc(const char *name); + +/* Allocates a sync point within the timeline. + * + * The timeline must be the one allocated by kbase_sync_timeline_alloc + * + * Sync points must be triggered in *exactly* the same order as they are allocated. + */ +struct sync_pt *kbase_sync_pt_alloc(struct sync_timeline *parent); + +/* Signals a particular sync point + * + * Sync points must be triggered in *exactly* the same order as they are allocated. + * + * If they are signalled in the wrong order then a message will be printed in debug + * builds and otherwise attempts to signal order sync_pts will be ignored. + * + * result can be negative to indicate error, any other value is interpreted as success. + */ +void kbase_sync_signal_pt(struct sync_pt *pt, int result); + +#endif diff --git a/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_sync_user.c b/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_sync_user.c new file mode 100755 index 00000000000..6d58557c8c0 --- /dev/null +++ b/drivers/gpu/arm/t6xx/kbase/src/linux/mali_kbase_sync_user.c @@ -0,0 +1,153 @@ +/* + * + * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved. + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * A copy of the licence is included with the program, and can also be obtained + * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + + + +/** + * @file mali_kbase_sync_user.c + * + */ + +#ifdef CONFIG_SYNC + +#include <linux/sched.h> +#include <linux/fdtable.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/anon_inodes.h> +#include <linux/version.h> +#include <linux/uaccess.h> +#include <kbase/src/linux/mali_kbase_sync.h> +#include <kbase/mali_base_kernel_sync.h> + +static int kbase_stream_close(struct inode *inode, struct file *file) +{ + struct sync_timeline *tl; + tl = (struct sync_timeline *)file->private_data; + BUG_ON(!tl); + sync_timeline_destroy(tl); + return 0; +} + +static const struct file_operations stream_fops = { + .owner = THIS_MODULE, + .release = kbase_stream_close, +}; + +mali_error kbase_stream_create(const char *name, int *const out_fd) +{ + struct sync_timeline *tl; + BUG_ON(!out_fd); + + tl = kbase_sync_timeline_alloc(name); + if (!tl) + return MALI_ERROR_FUNCTION_FAILED; + + *out_fd = anon_inode_getfd(name, &stream_fops, tl, O_RDONLY | O_CLOEXEC); + + if (*out_fd < 0) { + sync_timeline_destroy(tl); + return MALI_ERROR_FUNCTION_FAILED; + } else { + return MALI_ERROR_NONE; + } +} + +int kbase_stream_create_fence(int tl_fd) +{ + struct sync_timeline *tl; + struct sync_pt *pt; + struct sync_fence *fence; +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 7, 0) + struct files_struct *files; + struct fdtable *fdt; +#endif + int fd; + struct file *tl_file; + + tl_file = fget(tl_fd); + if (tl_file == NULL) + return -EBADF; + + if (tl_file->f_op != &stream_fops) { + fd = -EBADF; + goto out; + } + + tl = tl_file->private_data; + + pt = kbase_sync_pt_alloc(tl); + if (!pt) { + fd = -EFAULT; + goto out; + } + + fence = sync_fence_create("mali_fence", pt); + if (!fence) { + sync_pt_free(pt); + fd = -EFAULT; + goto out; + } + + /* from here the fence owns the sync_pt */ + + /* create a fd representing the fence */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0) + fd = get_unused_fd_flags(O_RDWR | O_CLOEXEC); + if (fd < 0) { + sync_fence_put(fence); + goto out; + } +#else + fd = get_unused_fd(); + if (fd < 0) { + sync_fence_put(fence); + goto out; + } + + files = current->files; + spin_lock(&files->file_lock); + fdt = files_fdtable(files); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) + __set_close_on_exec(fd, fdt); +#else + FD_SET(fd, fdt->close_on_exec); +#endif + spin_unlock(&files->file_lock); +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0) */ + + /* bind fence to the new fd */ + sync_fence_install(fence, fd); + + out: + fput(tl_file); + + return fd; +} + +mali_error kbase_fence_validate(int fd) +{ + struct sync_fence *fence; + fence = sync_fence_fdget(fd); + if (NULL != fence) { + sync_fence_put(fence); + return MALI_ERROR_NONE; + } else { + return MALI_ERROR_FUNCTION_FAILED; + } +} + +#endif /* CONFIG_SYNC */ diff --git a/drivers/gpu/arm/t6xx/kbase/src/linux/mali_linux_trace.h b/drivers/gpu/arm/t6xx/kbase/src/linux/mali_linux_trace.h new file mode 100755 index 00000000000..5e209484e75 --- /dev/null +++ b/drivers/gpu/arm/t6xx/kbase/src/linux/mali_linux_trace.h @@ -0,0 +1,124 @@ +/* + * + * (C) COPYRIGHT 2011-2013 ARM Limited. All rights reserved. + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * A copy of the licence is included with the program, and can also be obtained + * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + + + +#if !defined(_TRACE_MALI_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_MALI_H + +#include <linux/stringify.h> +#include <linux/tracepoint.h> + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM mali +#define TRACE_SYSTEM_STRING __stringify(TRACE_SYSTEM) +#define TRACE_INCLUDE_FILE mali_linux_trace + +/** + * mali_job_slots_event - called from mali_kbase_core_linux.c + * @event_id: ORed together bitfields representing a type of event, made with the GATOR_MAKE_EVENT() macro. + */ +TRACE_EVENT(mali_job_slots_event, TP_PROTO(unsigned int event_id, unsigned int tgid, unsigned int pid), TP_ARGS(event_id, tgid, pid), TP_STRUCT__entry(__field(unsigned int, event_id) + __field(unsigned int, tgid) + __field(unsigned int, pid) + ), TP_fast_assign(__entry->event_id = event_id; __entry->tgid = tgid; __entry->pid = pid;), TP_printk("event=%u tgid=%u pid=%u", __entry->event_id, __entry->tgid, __entry->pid) + ); + +/** + * mali_pm_status - Called by mali_kbase_pm_driver.c + * @event_id: core type (shader, tiler, l2 cache, l3 cache) + * @value: 64bits bitmask reporting either power status of the cores (1-ON, 0-OFF) + */ +TRACE_EVENT(mali_pm_status, TP_PROTO(unsigned int event_id, unsigned long long value), TP_ARGS(event_id, value), TP_STRUCT__entry(__field(unsigned int, event_id) + __field(unsigned long long, value) + ), TP_fast_assign(__entry->event_id = event_id;), TP_printk("event %u = %llu", __entry->event_id, __entry->value) + ); + +/** + * mali_pm_power_on - Called by mali_kbase_pm_driver.c + * @event_id: core type (shader, tiler, l2 cache, l3 cache) + * @value: 64bits bitmask reporting the cores to power up + */ +TRACE_EVENT(mali_pm_power_on, TP_PROTO(unsigned int event_id, unsigned long long value), TP_ARGS(event_id, value), TP_STRUCT__entry(__field(unsigned int, event_id) + __field(unsigned long long, value) + ), TP_fast_assign(__entry->event_id = event_id;), TP_printk("event %u = %llu", __entry->event_id, __entry->value) + ); + +/** + * mali_pm_power_off - Called by mali_kbase_pm_driver.c + * @event_id: core type (shader, tiler, l2 cache, l3 cache) + * @value: 64bits bitmask reporting the cores to power down + */ +TRACE_EVENT(mali_pm_power_off, TP_PROTO(unsigned int event_id, unsigned long long value), TP_ARGS(event_id, value), TP_STRUCT__entry(__field(unsigned int, event_id) + __field(unsigned long long, value) + ), TP_fast_assign(__entry->event_id = event_id;), TP_printk("event %u = %llu", __entry->event_id, __entry->value) + ); + +/** + * mali_page_fault_insert_pages - Called by page_fault_worker() + * it reports an MMU page fault resulting in new pages being mapped. + * @event_id: MMU address space number. + * @value: number of newly allocated pages + */ +TRACE_EVENT(mali_page_fault_insert_pages, TP_PROTO(int event_id, unsigned long value), TP_ARGS(event_id, value), TP_STRUCT__entry(__field(int, event_id) + __field(unsigned long, value) + ), TP_fast_assign(__entry->event_id = event_id;), TP_printk("event %d = %lu", __entry->event_id, __entry->value) + ); + +/** + * mali_mmu_as_in_use - Called by assign_and_activate_kctx_addr_space() + * it reports that a certain MMU address space is in use now. + * @event_id: MMU address space number. + */ +TRACE_EVENT(mali_mmu_as_in_use, TP_PROTO(int event_id), TP_ARGS(event_id), TP_STRUCT__entry(__field(int, event_id) + ), TP_fast_assign(__entry->event_id = event_id;), TP_printk("event=%d", __entry->event_id) + ); + +/** + * mali_mmu_as_released - Called by kbasep_js_runpool_release_ctx_internal() + * it reports that a certain MMU address space has been released now. + * @event_id: MMU address space number. + */ +TRACE_EVENT(mali_mmu_as_released, TP_PROTO(int event_id), TP_ARGS(event_id), TP_STRUCT__entry(__field(int, event_id) + ), TP_fast_assign(__entry->event_id = event_id;), TP_printk("event=%d", __entry->event_id) + ); + +/** + * mali_total_alloc_pages_change - Called by kbase_mem_usage_request_pages() + * and by kbase_mem_usage_release_pages + * it reports that the total number of allocated pages is changed. + * @event_id: number of pages to be added or subtracted (according to the sign). + */ +TRACE_EVENT(mali_total_alloc_pages_change, TP_PROTO(long long int event_id), TP_ARGS(event_id), TP_STRUCT__entry(__field(long long int, event_id) + ), TP_fast_assign(__entry->event_id = event_id;), TP_printk("event=%lld", __entry->event_id) + ); + +/** + * mali_sw_counter - not currently used + * @event_id: counter id + */ +TRACE_EVENT(mali_sw_counter, TP_PROTO(unsigned int event_id, signed long long value), TP_ARGS(event_id, value), TP_STRUCT__entry(__field(int, event_id) + __field(long long, value) + ), TP_fast_assign(__entry->event_id = event_id;), TP_printk("event %d = %lld", __entry->event_id, __entry->value) + ); + +#endif /* _TRACE_MALI_H */ + +#undef TRACE_INCLUDE_PATH +#undef linux +#define TRACE_INCLUDE_PATH . + +/* This part must be outside protection */ +#include <trace/define_trace.h> |