diff options
Diffstat (limited to 'drivers/base/ump/src/linux/ump_kernel_linux.c')
-rwxr-xr-x | drivers/base/ump/src/linux/ump_kernel_linux.c | 831 |
1 files changed, 831 insertions, 0 deletions
diff --git a/drivers/base/ump/src/linux/ump_kernel_linux.c b/drivers/base/ump/src/linux/ump_kernel_linux.c new file mode 100755 index 000000000000..d6c3c5354907 --- /dev/null +++ b/drivers/base/ump/src/linux/ump_kernel_linux.c @@ -0,0 +1,831 @@ +/* + * + * (C) COPYRIGHT 2008-2014 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/ump-ioctl.h> +#include <linux/ump.h> + +#include <asm/uaccess.h> /* copy_*_user */ +#include <linux/compat.h> +#include <linux/module.h> /* kernel module definitions */ +#include <linux/fs.h> /* file system operations */ +#include <linux/cdev.h> /* character device definitions */ +#include <linux/ioport.h> /* request_mem_region */ +#include <linux/device.h> /* class registration support */ + +#include <common/ump_kernel_core.h> + +#include "ump_kernel_linux_mem.h" +#include <ump_arch.h> + + +struct ump_linux_device +{ + struct cdev cdev; + struct class * ump_class; +}; + +/* Name of the UMP device driver */ +static char ump_dev_name[] = "ump"; /* should be const, but the functions we call requires non-cost */ + +/* Module parameter to control log level */ +int ump_debug_level = 2; +module_param(ump_debug_level, int, S_IRUSR | S_IWUSR | S_IWGRP | S_IRGRP | S_IROTH); /* rw-rw-r-- */ +MODULE_PARM_DESC(ump_debug_level, "Higher number, more dmesg output"); + +/* By default the module uses any available major, but it's possible to set it at load time to a specific number */ +int ump_major = 0; +module_param(ump_major, int, S_IRUGO); /* r--r--r-- */ +MODULE_PARM_DESC(ump_major, "Device major number"); + +#define UMP_REV_STRING "1.0" + +char * ump_revision = UMP_REV_STRING; +module_param(ump_revision, charp, S_IRUGO); /* r--r--r-- */ +MODULE_PARM_DESC(ump_revision, "Revision info"); + +static int umpp_linux_open(struct inode *inode, struct file *filp); +static int umpp_linux_release(struct inode *inode, struct file *filp); +#ifdef HAVE_UNLOCKED_IOCTL +static long umpp_linux_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); +#else +static int umpp_linux_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); +#endif + +/* This variable defines the file operations this UMP device driver offers */ +static struct file_operations ump_fops = +{ + .owner = THIS_MODULE, + .open = umpp_linux_open, + .release = umpp_linux_release, +#ifdef HAVE_UNLOCKED_IOCTL + .unlocked_ioctl = umpp_linux_ioctl, +#else + .ioctl = umpp_linux_ioctl, +#endif + .compat_ioctl = umpp_linux_ioctl, + .mmap = umpp_linux_mmap +}; + +/* import module handling */ +DEFINE_MUTEX(import_list_lock); +struct ump_import_handler * import_handlers[UMPP_EXTERNAL_MEM_COUNT]; + +/* The global variable containing the global device data */ +static struct ump_linux_device ump_linux_device; + +#define DBG_MSG(level, ...) do { \ +if ((level) <= ump_debug_level)\ +{\ +printk(KERN_DEBUG "UMP<" #level ">:\n" __VA_ARGS__);\ +} \ +} while (0) + +#define MSG_ERR(...) do{ \ +printk(KERN_ERR "UMP: ERR: %s\n %s()%4d\n", __FILE__, __func__ , __LINE__) ; \ +printk(KERN_ERR __VA_ARGS__); \ +printk(KERN_ERR "\n"); \ +} while(0) + +#define MSG(...) do{ \ +printk(KERN_INFO "UMP: " __VA_ARGS__);\ +} while (0) + +/* + * This function is called by Linux to initialize this module. + * All we do is initialize the UMP device driver. + */ +static int __init umpp_linux_initialize_module(void) +{ + ump_result err; + + err = umpp_core_constructor(); + if (UMP_OK != err) + { + MSG_ERR("UMP device driver init failed\n"); + return -ENOTTY; + } + + MSG("UMP device driver %s loaded\n", UMP_REV_STRING); + return 0; +} + + + +/* + * This function is called by Linux to unload/terminate/exit/cleanup this module. + * All we do is terminate the UMP device driver. + */ +static void __exit umpp_linux_cleanup_module(void) +{ + DBG_MSG(2, "Unloading UMP device driver\n"); + umpp_core_destructor(); + DBG_MSG(2, "Module unloaded\n"); +} + + + +/* + * Initialize the UMP device driver. + */ +ump_result umpp_device_initialize(void) +{ + int err; + dev_t dev = 0; + + if (0 == ump_major) + { + /* auto select a major */ + err = alloc_chrdev_region(&dev, 0, 1, ump_dev_name); + ump_major = MAJOR(dev); + } + else + { + /* use load time defined major number */ + dev = MKDEV(ump_major, 0); + err = register_chrdev_region(dev, 1, ump_dev_name); + } + + if (0 == err) + { + memset(&ump_linux_device, 0, sizeof(ump_linux_device)); + + /* initialize our char dev data */ + cdev_init(&ump_linux_device.cdev, &ump_fops); + ump_linux_device.cdev.owner = THIS_MODULE; + ump_linux_device.cdev.ops = &ump_fops; + + /* register char dev with the kernel */ + err = cdev_add(&ump_linux_device.cdev, dev, 1/*count*/); + if (0 == err) + { + + ump_linux_device.ump_class = class_create(THIS_MODULE, ump_dev_name); + if (IS_ERR(ump_linux_device.ump_class)) + { + err = PTR_ERR(ump_linux_device.ump_class); + } + else + { + struct device * mdev; + mdev = device_create(ump_linux_device.ump_class, NULL, dev, NULL, ump_dev_name); + if (!IS_ERR(mdev)) + { + return UMP_OK; + } + + err = PTR_ERR(mdev); + class_destroy(ump_linux_device.ump_class); + } + cdev_del(&ump_linux_device.cdev); + } + + unregister_chrdev_region(dev, 1); + } + + return UMP_ERROR; +} + + + +/* + * Terminate the UMP device driver + */ +void umpp_device_terminate(void) +{ + dev_t dev = MKDEV(ump_major, 0); + + device_destroy(ump_linux_device.ump_class, dev); + class_destroy(ump_linux_device.ump_class); + + /* unregister char device */ + cdev_del(&ump_linux_device.cdev); + + /* free major */ + unregister_chrdev_region(dev, 1); +} + + +static int umpp_linux_open(struct inode *inode, struct file *filp) +{ + umpp_session *session; + + session = umpp_core_session_start(); + if (NULL == session) + { + return -EFAULT; + } + + filp->private_data = session; + + return 0; +} + +static int umpp_linux_release(struct inode *inode, struct file *filp) +{ + umpp_session *session; + + session = filp->private_data; + + umpp_core_session_end(session); + + filp->private_data = NULL; + + return 0; +} + +/**************************/ +/*ioctl specific functions*/ +/**************************/ +static int do_ump_dd_allocate(umpp_session * session, ump_k_allocate * params) +{ + ump_dd_handle new_allocation; + new_allocation = ump_dd_allocate_64(params->size, params->alloc_flags, NULL, NULL, NULL); + + if (UMP_DD_INVALID_MEMORY_HANDLE != new_allocation) + { + umpp_session_memory_usage * tracker; + + tracker = kmalloc(sizeof(*tracker), GFP_KERNEL | __GFP_HARDWALL); + if (NULL != tracker) + { + /* update the return struct with the new ID */ + params->secure_id = ump_dd_secure_id_get(new_allocation); + + tracker->mem = new_allocation; + tracker->id = params->secure_id; + atomic_set(&tracker->process_usage_count, 1); + + /* link it into the session in-use list */ + mutex_lock(&session->session_lock); + list_add(&tracker->link, &session->memory_usage); + mutex_unlock(&session->session_lock); + + return 0; + } + ump_dd_release(new_allocation); + } + + printk(KERN_WARNING "UMP: Allocation FAILED\n"); + return -ENOMEM; +} + +static int do_ump_dd_retain(umpp_session * session, ump_k_retain * params) +{ + umpp_session_memory_usage * it; + + mutex_lock(&session->session_lock); + + /* try to find it on the session usage list */ + list_for_each_entry(it, &session->memory_usage, link) + { + if (it->id == params->secure_id) + { + /* found to already be in use */ + /* check for overflow */ + while(1) + { + int refcnt = atomic_read(&it->process_usage_count); + if (refcnt + 1 > 0) + { + /* add a process local ref */ + if(atomic_cmpxchg(&it->process_usage_count, refcnt, refcnt + 1) == refcnt) + { + mutex_unlock(&session->session_lock); + return 0; + } + } + else + { + /* maximum usage cap reached */ + mutex_unlock(&session->session_lock); + return -EBUSY; + } + } + } + } + /* try to look it up globally */ + + it = kmalloc(sizeof(*it), GFP_KERNEL); + + if (NULL != it) + { + it->mem = ump_dd_from_secure_id(params->secure_id); + if (UMP_DD_INVALID_MEMORY_HANDLE != it->mem) + { + /* found, add it to the session usage list */ + it->id = params->secure_id; + atomic_set(&it->process_usage_count, 1); + list_add(&it->link, &session->memory_usage); + } + else + { + /* not found */ + kfree(it); + it = NULL; + } + } + + mutex_unlock(&session->session_lock); + + return (NULL != it) ? 0 : -ENODEV; +} + + +static int do_ump_dd_release(umpp_session * session, ump_k_release * params) +{ + umpp_session_memory_usage * it; + int result = -ENODEV; + + mutex_lock(&session->session_lock); + + /* only do a release if found on the session list */ + list_for_each_entry(it, &session->memory_usage, link) + { + if (it->id == params->secure_id) + { + /* found, a valid call */ + result = 0; + + if (0 == atomic_sub_return(1, &it->process_usage_count)) + { + /* last ref in this process remove from the usage list and remove the underlying ref */ + list_del(&it->link); + ump_dd_release(it->mem); + kfree(it); + } + + break; + } + } + mutex_unlock(&session->session_lock); + + return result; +} + +static int do_ump_dd_sizequery(umpp_session * session, ump_k_sizequery * params) +{ + umpp_session_memory_usage * it; + int result = -ENODEV; + + mutex_lock(&session->session_lock); + + /* only valid if found on the session list */ + list_for_each_entry(it, &session->memory_usage, link) + { + if (it->id == params->secure_id) + { + /* found, a valid call */ + params->size = ump_dd_size_get_64(it->mem); + result = 0; + break; + } + + } + mutex_unlock(&session->session_lock); + + return result; +} + +static int do_ump_dd_allocation_flags_get(umpp_session * session, ump_k_allocation_flags * params) +{ + umpp_session_memory_usage * it; + int result = -ENODEV; + + mutex_lock(&session->session_lock); + + /* only valid if found on the session list */ + list_for_each_entry(it, &session->memory_usage, link) + { + if (it->id == params->secure_id) + { + /* found, a valid call */ + params->alloc_flags = ump_dd_allocation_flags_get(it->mem); + result = 0; + break; + } + + } + mutex_unlock(&session->session_lock); + + return result; +} + +static int do_ump_dd_msync_now(umpp_session * session, ump_k_msync * params) +{ + umpp_session_memory_usage * it; + int result = -ENODEV; + + mutex_lock(&session->session_lock); + + /* only valid if found on the session list */ + list_for_each_entry(it, &session->memory_usage, link) + { + if (it->id == params->secure_id) + { + /* found, do the cache op */ +#ifdef CONFIG_COMPAT + if (is_compat_task()) + { + umpp_dd_cpu_msync_now(it->mem, params->cache_operation, compat_ptr(params->mapped_ptr.compat_value), params->size); + result = 0; + } + else + { +#endif + umpp_dd_cpu_msync_now(it->mem, params->cache_operation, params->mapped_ptr.value, params->size); + result = 0; +#ifdef CONFIG_COMPAT + } +#endif + break; + } + } + mutex_unlock(&session->session_lock); + + return result; +} + + +void umpp_import_handlers_init(umpp_session * session) +{ + int i; + mutex_lock(&import_list_lock); + for ( i = 1; i < UMPP_EXTERNAL_MEM_COUNT; i++ ) + { + if (import_handlers[i]) + { + import_handlers[i]->session_begin(&session->import_handler_data[i]); + /* It is OK if session_begin returned an error. + * We won't do any import calls if so */ + } + } + mutex_unlock(&import_list_lock); +} + +void umpp_import_handlers_term(umpp_session * session) +{ + int i; + mutex_lock(&import_list_lock); + for ( i = 1; i < UMPP_EXTERNAL_MEM_COUNT; i++ ) + { + /* only call if session_begin succeeded */ + if (session->import_handler_data[i] != NULL) + { + /* if session_beging succeeded the handler + * should not have unregistered with us */ + BUG_ON(!import_handlers[i]); + import_handlers[i]->session_end(session->import_handler_data[i]); + session->import_handler_data[i] = NULL; + } + } + mutex_unlock(&import_list_lock); +} + +int ump_import_module_register(enum ump_external_memory_type type, struct ump_import_handler * handler) +{ + int res = -EEXIST; + + /* validate input */ + BUG_ON(type == 0 || type >= UMPP_EXTERNAL_MEM_COUNT); + BUG_ON(!handler); + BUG_ON(!handler->linux_module); + BUG_ON(!handler->session_begin); + BUG_ON(!handler->session_end); + BUG_ON(!handler->import); + + mutex_lock(&import_list_lock); + + if (!import_handlers[type]) + { + import_handlers[type] = handler; + res = 0; + } + + mutex_unlock(&import_list_lock); + + return res; +} + +void ump_import_module_unregister(enum ump_external_memory_type type) +{ + BUG_ON(type == 0 || type >= UMPP_EXTERNAL_MEM_COUNT); + + mutex_lock(&import_list_lock); + /* an error to call this if ump_import_module_register didn't succeed */ + BUG_ON(!import_handlers[type]); + import_handlers[type] = NULL; + mutex_unlock(&import_list_lock); +} + +static struct ump_import_handler * import_handler_get(unsigned int type_id) +{ + enum ump_external_memory_type type; + struct ump_import_handler * handler; + + /* validate and convert input */ + /* handle bad data here, not just BUG_ON */ + if (type_id == 0 || type_id >= UMPP_EXTERNAL_MEM_COUNT) + return NULL; + + type = (enum ump_external_memory_type)type_id; + + /* find the handler */ + mutex_lock(&import_list_lock); + + handler = import_handlers[type]; + + if (handler) + { + if (!try_module_get(handler->linux_module)) + { + handler = NULL; + } + } + + mutex_unlock(&import_list_lock); + + return handler; +} + +static void import_handler_put(struct ump_import_handler * handler) +{ + module_put(handler->linux_module); +} + +static int do_ump_dd_import(umpp_session * session, ump_k_import * params) +{ + ump_dd_handle new_allocation = UMP_DD_INVALID_MEMORY_HANDLE; + struct ump_import_handler * handler; + + handler = import_handler_get(params->type); + + if (handler) + { + /* try late binding if not already bound */ + if (!session->import_handler_data[params->type]) + { + handler->session_begin(&session->import_handler_data[params->type]); + } + + /* do we have a bound session? */ + if (session->import_handler_data[params->type]) + { + new_allocation = handler->import( session->import_handler_data[params->type], + params->phandle.value, + params->alloc_flags); + } + + /* done with the handler */ + import_handler_put(handler); + } + + /* did the import succeed? */ + if (UMP_DD_INVALID_MEMORY_HANDLE != new_allocation) + { + umpp_session_memory_usage * tracker; + + tracker = kmalloc(sizeof(*tracker), GFP_KERNEL | __GFP_HARDWALL); + if (NULL != tracker) + { + /* update the return struct with the new ID */ + params->secure_id = ump_dd_secure_id_get(new_allocation); + + tracker->mem = new_allocation; + tracker->id = params->secure_id; + atomic_set(&tracker->process_usage_count, 1); + + /* link it into the session in-use list */ + mutex_lock(&session->session_lock); + list_add(&tracker->link, &session->memory_usage); + mutex_unlock(&session->session_lock); + + return 0; + } + ump_dd_release(new_allocation); + } + + return -ENOMEM; + +} + +#ifdef HAVE_UNLOCKED_IOCTL +static long umpp_linux_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +#else +static int umpp_linux_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) +#endif +{ + int ret; + uint64_t msg[(UMP_CALL_MAX_SIZE+7)>>3]; /* alignment fixup */ + uint32_t size = _IOC_SIZE(cmd); + struct umpp_session *session = filp->private_data; + +#ifndef HAVE_UNLOCKED_IOCTL + (void)inode; /* unused arg */ +#endif + + /* + * extract the type and number bitfields, and don't decode + * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok() + */ + if (_IOC_TYPE(cmd) != UMP_IOC_MAGIC) + { + return -ENOTTY; + + } + if (_IOC_NR(cmd) > UMP_IOC_MAXNR) + { + return -ENOTTY; + } + + switch(cmd) + { + case UMP_FUNC_ALLOCATE: + if (size != sizeof(ump_k_allocate)) + { + return -ENOTTY; + } + if (copy_from_user(&msg, (void __user *)arg, size)) + { + return -EFAULT; + } + ret = do_ump_dd_allocate(session, (ump_k_allocate *)&msg); + if (ret) + { + return ret; + } + if (copy_to_user((void *)arg, &msg, size)) + { + return -EFAULT; + } + return 0; + case UMP_FUNC_SIZEQUERY: + if (size != sizeof(ump_k_sizequery)) + { + return -ENOTTY; + } + if (copy_from_user(&msg, (void __user *)arg, size)) + { + return -EFAULT; + } + ret = do_ump_dd_sizequery(session,(ump_k_sizequery*) &msg); + if (ret) + { + return ret; + } + if (copy_to_user((void *)arg, &msg, size)) + { + return -EFAULT; + } + return 0; + case UMP_FUNC_MSYNC: + if (size != sizeof(ump_k_msync)) + { + return -ENOTTY; + } + if (copy_from_user(&msg, (void __user *)arg, size)) + { + return -EFAULT; + } + ret = do_ump_dd_msync_now(session,(ump_k_msync*) &msg); + if (ret) + { + return ret; + } + if (copy_to_user((void *)arg, &msg, size)) + { + return -EFAULT; + } + return 0; + case UMP_FUNC_IMPORT: + if (size != sizeof(ump_k_import)) + { + return -ENOTTY; + } + if (copy_from_user(&msg, (void __user*)arg, size)) + { + return -EFAULT; + } + ret = do_ump_dd_import(session, (ump_k_import*) &msg); + if (ret) + { + return ret; + } + if (copy_to_user((void *)arg, &msg, size)) + { + return -EFAULT; + } + return 0; + /* used only by v1 API */ + case UMP_FUNC_ALLOCATION_FLAGS_GET: + if (size != sizeof(ump_k_allocation_flags)) + { + return -ENOTTY; + } + if (copy_from_user(&msg, (void __user *)arg, size)) + { + return -EFAULT; + } + ret = do_ump_dd_allocation_flags_get(session,(ump_k_allocation_flags*) &msg); + if (ret) + { + return ret; + } + if (copy_to_user((void *)arg, &msg, size)) + { + return -EFAULT; + } + return 0; + case UMP_FUNC_RETAIN: + if (size != sizeof(ump_k_retain)) + { + return -ENOTTY; + } + if (copy_from_user(&msg, (void __user *)arg, size)) + { + return -EFAULT; + } + ret = do_ump_dd_retain(session,(ump_k_retain*) &msg); + if (ret) + { + return ret; + } + return 0; + case UMP_FUNC_RELEASE: + if (size != sizeof(ump_k_release)) + { + return -ENOTTY; + } + if (copy_from_user(&msg, (void __user *)arg, size)) + { + return -EFAULT; + } + ret = do_ump_dd_release(session,(ump_k_release*) &msg); + if (ret) + { + return ret; + } + return 0; + default: + /* not ours */ + return -ENOTTY; + } + /*redundant below*/ + return -ENOTTY; +} + + +/* Export UMP kernel space API functions */ +EXPORT_SYMBOL(ump_dd_allocate_64); +EXPORT_SYMBOL(ump_dd_allocation_flags_get); +EXPORT_SYMBOL(ump_dd_secure_id_get); +EXPORT_SYMBOL(ump_dd_from_secure_id); +EXPORT_SYMBOL(ump_dd_phys_blocks_get_64); +EXPORT_SYMBOL(ump_dd_size_get_64); +EXPORT_SYMBOL(ump_dd_retain); +EXPORT_SYMBOL(ump_dd_release); +EXPORT_SYMBOL(ump_dd_create_from_phys_blocks_64); +#ifdef CONFIG_KDS +EXPORT_SYMBOL(ump_dd_kds_resource_get); +#endif + +/* import API */ +EXPORT_SYMBOL(ump_import_module_register); +EXPORT_SYMBOL(ump_import_module_unregister); + + + +/* V1 API */ +EXPORT_SYMBOL(ump_dd_handle_create_from_secure_id); +EXPORT_SYMBOL(ump_dd_phys_block_count_get); +EXPORT_SYMBOL(ump_dd_phys_block_get); +EXPORT_SYMBOL(ump_dd_phys_blocks_get); +EXPORT_SYMBOL(ump_dd_size_get); +EXPORT_SYMBOL(ump_dd_reference_add); +EXPORT_SYMBOL(ump_dd_reference_release); +EXPORT_SYMBOL(ump_dd_handle_create_from_phys_blocks); + + +/* Setup init and exit functions for this module */ +module_init(umpp_linux_initialize_module); +module_exit(umpp_linux_cleanup_module); + +/* And some module informatio */ +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("ARM Ltd."); +MODULE_VERSION(UMP_REV_STRING); |