/* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iontest.h" #include "compat_msm_ion_test_module.h" #define ION_TEST_DEV_NAME "msm_ion_test" #define CLIENT_NAME "ion_test_client" struct msm_ion_test { struct ion_client *ion_client; struct ion_handle *ion_handle; struct ion_test_data test_data; }; struct heap { unsigned int id; unsigned int size; enum ion_test_heap_type type; }; static struct class *ion_test_class; static int ion_test_major; static struct device *ion_test_dev; static struct heap *heap_list; static unsigned int heap_list_len; /*Utility apis*/ static inline int create_ion_client(struct msm_ion_test *ion_test) { ion_test->ion_client = msm_ion_client_create(CLIENT_NAME); if (IS_ERR_OR_NULL(ion_test->ion_client)) return -EIO; return 0; } static inline void free_ion_client(struct msm_ion_test *ion_test) { ion_client_destroy(ion_test->ion_client); } static int alloc_ion_buf(struct msm_ion_test *ion_test, struct ion_test_data *test_data) { ion_test->ion_handle = ion_alloc(ion_test->ion_client, test_data->size, test_data->align, test_data->heap_id_mask, test_data->flags); if (IS_ERR_OR_NULL(ion_test->ion_handle)) return -EIO; return 0; } static inline void free_ion_buf(struct msm_ion_test *ion_test) { ion_free(ion_test->ion_client, ion_test->ion_handle); } static int heap_detected; #ifdef CONFIG_OF_DEVICE static int ion_find_heaps_available(void) { struct device_node *ion_node; struct device_node *ion_child; unsigned int i = 0; int ret = 0; unsigned int val = 0; heap_list_len = 0; ion_node = of_find_compatible_node(NULL, NULL, "qcom,msm-ion"); for_each_child_of_node(ion_node, ion_child) heap_list_len++; heap_list = kzalloc(sizeof(struct heap) * heap_list_len, GFP_KERNEL); if (!heap_list) { ret = -ENOMEM; goto out; } for_each_child_of_node(ion_node, ion_child) { ret = of_property_read_u32(ion_child, "reg", &val); if (ret) { pr_err("%s: Unable to find reg key", __func__); goto free_heaps; } heap_list[i].id = val; switch (val) { case ION_SYSTEM_HEAP_ID: { heap_list[i].type = SYSTEM_MEM; break; } case ION_SYSTEM_CONTIG_HEAP_ID: { heap_list[i].type = SYSTEM_CONTIG; break; } case ION_CP_MM_HEAP_ID: { ret = of_property_read_u32(ion_child, "qcom,memory-reservation-size", &val); if (ret) { heap_list[i].type = SECURE_DMA; ret = 0; } else { heap_list[i].size = val; heap_list[i].type = CP_CARVEOUT; } break; } case ION_QSECOM_HEAP_ID: case ION_AUDIO_HEAP_ID: { ret = of_property_read_u32(ion_child, "qcom,memory-reservation-size", &val); if (ret) { heap_list[i].type = DMA; ret = 0; } else { heap_list[i].size = val; heap_list[i].type = CARVEOUT; } break; } default: break; } ++i; } heap_detected = 1; goto out; free_heaps: kfree(heap_list); heap_list = 0; out: return ret; } #else static int ion_find_heaps_available(void) { return 0; } #endif static int ion_test_open(struct inode *inode, struct file *file) { struct msm_ion_test *ion_test = kzalloc(sizeof(struct msm_ion_test), GFP_KERNEL); if (!ion_test) return -ENOMEM; pr_debug("ion test device opened\n"); file->private_data = ion_test; return ion_find_heaps_available(); } static int get_proper_heap(struct ion_heap_data *data) { int ret = -EINVAL; unsigned int i; if (!heap_detected) { data->valid = 1; ret = 0; } else { data->heap_id_mask = 0; data->size = 0; data->valid = 0; for (i = 0; i < heap_list_len; ++i) { if (data->type == heap_list[i].type) { data->size = heap_list[i].size; data->heap_id_mask = ION_HEAP(heap_list[i].id); data->valid = 1; ret = 0; break; } } } return ret; } static long ion_test_ioctl(struct file *file, unsigned cmd, unsigned long arg) { int ret; ion_phys_addr_t phys_addr; void *addr; size_t len; unsigned long flags; #if (LINUX_VERSION_CODE < KERNEL_VERSION(3,18,0)) unsigned long size; #else size_t size; #endif struct msm_ion_test *ion_test = file->private_data; struct ion_test_data *test_data = &ion_test->test_data; switch (cmd) { case IOC_ION_KCLIENT_CREATE: { ret = create_ion_client(ion_test); break; } case IOC_ION_KCLIENT_DESTROY: { free_ion_client(ion_test); ret = 0; break; } case IOC_ION_KALLOC: { if (copy_from_user(test_data, (void __user *)arg, sizeof(struct ion_test_data))) return -EFAULT; ret = alloc_ion_buf(ion_test, test_data); if (ret) pr_info("allocating ion buffer failed\n"); break; } case IOC_ION_KFREE: { free_ion_buf(ion_test); ret = 0; break; } case IOC_ION_KPHYS: { ret = ion_phys(ion_test->ion_client, ion_test->ion_handle, &phys_addr, &len); if (!ret) pr_info("size is 0x%zx\n phys addr 0x%x", len, (unsigned int)phys_addr); break; } case IOC_ION_KMAP: { addr = ion_map_kernel(ion_test->ion_client, ion_test->ion_handle); if (IS_ERR_OR_NULL(addr)) { ret = -EIO; pr_info("mapping kernel buffer failed\n"); } else { ret = 0; test_data->vaddr = (unsigned long)addr; } break; } case IOC_ION_KUMAP: { ion_unmap_kernel(ion_test->ion_client, ion_test->ion_handle); ret = 0; break; } case IOC_ION_UIMPORT: { if (copy_from_user(test_data, (void __user *)arg, sizeof(struct ion_test_data))) return -EFAULT; ion_test->ion_handle = ion_import_dma_buf(ion_test->ion_client, test_data->shared_fd); if (IS_ERR_OR_NULL(ion_test->ion_handle)) { ret = -EIO; pr_info("import of user buf failed\n"); } else ret = 0; break; } case IOC_ION_UBUF_FLAGS: { ret = ion_handle_get_flags(ion_test->ion_client, ion_test->ion_handle, &flags); if (ret) pr_info("user flags cannot be retrieved\n"); else if (copy_to_user((void __user *)arg, &flags, sizeof(unsigned long))) ret = -EFAULT; break; } case IOC_ION_UBUF_SIZE: { ret = ion_handle_get_size(ion_test->ion_client, ion_test->ion_handle, &size); if (ret) pr_info("buffer size cannot be retrieved\n"); else if (copy_to_user((void __user *)arg, &size, sizeof(unsigned long))) ret = -EFAULT; break; } case IOC_ION_WRITE_VERIFY: { write_pattern(test_data->vaddr, test_data->size); if (verify_pattern(test_data->vaddr, test_data->size)) { pr_info("verify of mapped buf failed\n"); ret = -EIO; } else ret = 0; break; } case IOC_ION_VERIFY: { if (verify_pattern(test_data->vaddr, test_data->size)) { pr_info("fail in verifying imported buffer\n"); ret = -EIO; } else ret = 0; break; } case IOC_ION_FIND_PROPER_HEAP: { struct ion_heap_data data; if (copy_from_user(&data, (void __user *)arg, sizeof(struct ion_heap_data))) return -EFAULT; ret = get_proper_heap(&data); if (!ret) { if (copy_to_user((void __user *)arg, &data, sizeof(struct ion_heap_data))) ret = -EFAULT; } break; } default: { pr_info("command not supported\n"); ret = -EINVAL; } }; return ret; } static int ion_test_release(struct inode *inode, struct file *file) { struct msm_ion_test *ion_test = file->private_data; pr_debug("ion test device closed\n"); kfree(ion_test); return 0; } /* * Register ourselves as a device to be able to test the ion code * from userspace. */ static const struct file_operations ion_test_fops = { .owner = THIS_MODULE, .unlocked_ioctl = ion_test_ioctl, .compat_ioctl = compat_ion_test_ioctl, .open = ion_test_open, .release = ion_test_release, }; static int ion_test_device_create(void) { int ret_val = 0; ion_test_major = register_chrdev(0, ION_TEST_DEV_NAME, &ion_test_fops); if (ion_test_major < 0) { pr_err("Unable to register chrdev: %d\n", ion_test_major); ret_val = ion_test_major; goto out; } ion_test_class = class_create(THIS_MODULE, ION_TEST_DEV_NAME); if (IS_ERR(ion_test_class)) { ret_val = PTR_ERR(ion_test_class); pr_err("Unable to create class: %d\n", ret_val); goto err_create_class; } ion_test_dev = device_create(ion_test_class, NULL, MKDEV(ion_test_major, 0), NULL, ION_TEST_DEV_NAME); if (IS_ERR(ion_test_dev)) { ret_val = PTR_ERR(ion_test_dev); pr_err("Unable to create device: %d\n", ret_val); goto err_create_device; } goto out; err_create_device: class_destroy(ion_test_class); err_create_class: unregister_chrdev(ion_test_major, ION_TEST_DEV_NAME); out: return ret_val; } static void ion_test_device_destroy(void) { device_destroy(ion_test_class, MKDEV(ion_test_major, 0)); class_destroy(ion_test_class); unregister_chrdev(ion_test_major, ION_TEST_DEV_NAME); kfree(heap_list); } static int ion_test_init(void) { return ion_test_device_create(); } static void ion_test_exit(void) { return ion_test_device_destroy(); } MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Test for MSM ION implementation"); module_init(ion_test_init); module_exit(ion_test_exit);