1/* 2 * 3 * Copyright (C) 2013 Google, Inc. 4 * 5 * This software is licensed under the terms of the GNU General Public 6 * License version 2, as published by the Free Software Foundation, and 7 * may be copied, distributed, and modified under those terms. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 */ 15 16#define pr_fmt(fmt) "ion-test: " fmt 17 18#include <linux/dma-buf.h> 19#include <linux/dma-direction.h> 20#include <linux/fs.h> 21#include <linux/miscdevice.h> 22#include <linux/mm.h> 23#include <linux/module.h> 24#include <linux/platform_device.h> 25#include <linux/sched.h> 26#include <linux/slab.h> 27#include <linux/uaccess.h> 28#include <linux/vmalloc.h> 29 30#include "ion.h" 31#include "../uapi/ion_test.h" 32 33#define u64_to_uptr(x) ((void __user *)(unsigned long)(x)) 34 35struct ion_test_device { 36 struct miscdevice misc; 37}; 38 39struct ion_test_data { 40 struct dma_buf *dma_buf; 41 struct device *dev; 42}; 43 44static int ion_handle_test_dma(struct device *dev, struct dma_buf *dma_buf, 45 void __user *ptr, size_t offset, size_t size, bool write) 46{ 47 int ret = 0; 48 struct dma_buf_attachment *attach; 49 struct sg_table *table; 50 pgprot_t pgprot = pgprot_writecombine(PAGE_KERNEL); 51 enum dma_data_direction dir = write ? DMA_FROM_DEVICE : DMA_TO_DEVICE; 52 struct sg_page_iter sg_iter; 53 unsigned long offset_page; 54 55 attach = dma_buf_attach(dma_buf, dev); 56 if (IS_ERR(attach)) 57 return PTR_ERR(attach); 58 59 table = dma_buf_map_attachment(attach, dir); 60 if (IS_ERR(table)) 61 return PTR_ERR(table); 62 63 offset_page = offset >> PAGE_SHIFT; 64 offset %= PAGE_SIZE; 65 66 for_each_sg_page(table->sgl, &sg_iter, table->nents, offset_page) { 67 struct page *page = sg_page_iter_page(&sg_iter); 68 void *vaddr = vmap(&page, 1, VM_MAP, pgprot); 69 size_t to_copy = PAGE_SIZE - offset; 70 71 to_copy = min(to_copy, size); 72 if (!vaddr) { 73 ret = -ENOMEM; 74 goto err; 75 } 76 77 if (write) 78 ret = copy_from_user(vaddr + offset, ptr, to_copy); 79 else 80 ret = copy_to_user(ptr, vaddr + offset, to_copy); 81 82 vunmap(vaddr); 83 if (ret) { 84 ret = -EFAULT; 85 goto err; 86 } 87 size -= to_copy; 88 if (!size) 89 break; 90 ptr += to_copy; 91 offset = 0; 92 } 93 94err: 95 dma_buf_unmap_attachment(attach, table, dir); 96 dma_buf_detach(dma_buf, attach); 97 return ret; 98} 99 100static int ion_handle_test_kernel(struct dma_buf *dma_buf, void __user *ptr, 101 size_t offset, size_t size, bool write) 102{ 103 int ret; 104 unsigned long page_offset = offset >> PAGE_SHIFT; 105 size_t copy_offset = offset % PAGE_SIZE; 106 size_t copy_size = size; 107 enum dma_data_direction dir = write ? DMA_FROM_DEVICE : DMA_TO_DEVICE; 108 109 if (offset > dma_buf->size || size > dma_buf->size - offset) 110 return -EINVAL; 111 112 ret = dma_buf_begin_cpu_access(dma_buf, offset, size, dir); 113 if (ret) 114 return ret; 115 116 while (copy_size > 0) { 117 size_t to_copy; 118 void *vaddr = dma_buf_kmap(dma_buf, page_offset); 119 120 if (!vaddr) 121 goto err; 122 123 to_copy = min_t(size_t, PAGE_SIZE - copy_offset, copy_size); 124 125 if (write) 126 ret = copy_from_user(vaddr + copy_offset, ptr, to_copy); 127 else 128 ret = copy_to_user(ptr, vaddr + copy_offset, to_copy); 129 130 dma_buf_kunmap(dma_buf, page_offset, vaddr); 131 if (ret) { 132 ret = -EFAULT; 133 goto err; 134 } 135 136 copy_size -= to_copy; 137 ptr += to_copy; 138 page_offset++; 139 copy_offset = 0; 140 } 141err: 142 dma_buf_end_cpu_access(dma_buf, offset, size, dir); 143 return ret; 144} 145 146static long ion_test_ioctl(struct file *filp, unsigned int cmd, 147 unsigned long arg) 148{ 149 struct ion_test_data *test_data = filp->private_data; 150 int ret = 0; 151 152 union { 153 struct ion_test_rw_data test_rw; 154 } data; 155 156 if (_IOC_SIZE(cmd) > sizeof(data)) 157 return -EINVAL; 158 159 if (_IOC_DIR(cmd) & _IOC_WRITE) 160 if (copy_from_user(&data, (void __user *)arg, _IOC_SIZE(cmd))) 161 return -EFAULT; 162 163 switch (cmd) { 164 case ION_IOC_TEST_SET_FD: 165 { 166 struct dma_buf *dma_buf = NULL; 167 int fd = arg; 168 169 if (fd >= 0) { 170 dma_buf = dma_buf_get((int)arg); 171 if (IS_ERR(dma_buf)) 172 return PTR_ERR(dma_buf); 173 } 174 if (test_data->dma_buf) 175 dma_buf_put(test_data->dma_buf); 176 test_data->dma_buf = dma_buf; 177 break; 178 } 179 case ION_IOC_TEST_DMA_MAPPING: 180 { 181 ret = ion_handle_test_dma(test_data->dev, test_data->dma_buf, 182 u64_to_uptr(data.test_rw.ptr), 183 data.test_rw.offset, data.test_rw.size, 184 data.test_rw.write); 185 break; 186 } 187 case ION_IOC_TEST_KERNEL_MAPPING: 188 { 189 ret = ion_handle_test_kernel(test_data->dma_buf, 190 u64_to_uptr(data.test_rw.ptr), 191 data.test_rw.offset, data.test_rw.size, 192 data.test_rw.write); 193 break; 194 } 195 default: 196 return -ENOTTY; 197 } 198 199 if (_IOC_DIR(cmd) & _IOC_READ) { 200 if (copy_to_user((void __user *)arg, &data, sizeof(data))) 201 return -EFAULT; 202 } 203 return ret; 204} 205 206static int ion_test_open(struct inode *inode, struct file *file) 207{ 208 struct ion_test_data *data; 209 struct miscdevice *miscdev = file->private_data; 210 211 data = kzalloc(sizeof(struct ion_test_data), GFP_KERNEL); 212 if (!data) 213 return -ENOMEM; 214 215 data->dev = miscdev->parent; 216 217 file->private_data = data; 218 219 return 0; 220} 221 222static int ion_test_release(struct inode *inode, struct file *file) 223{ 224 struct ion_test_data *data = file->private_data; 225 226 kfree(data); 227 228 return 0; 229} 230 231static const struct file_operations ion_test_fops = { 232 .owner = THIS_MODULE, 233 .unlocked_ioctl = ion_test_ioctl, 234 .compat_ioctl = ion_test_ioctl, 235 .open = ion_test_open, 236 .release = ion_test_release, 237}; 238 239static int __init ion_test_probe(struct platform_device *pdev) 240{ 241 int ret; 242 struct ion_test_device *testdev; 243 244 testdev = devm_kzalloc(&pdev->dev, sizeof(struct ion_test_device), 245 GFP_KERNEL); 246 if (!testdev) 247 return -ENOMEM; 248 249 testdev->misc.minor = MISC_DYNAMIC_MINOR; 250 testdev->misc.name = "ion-test"; 251 testdev->misc.fops = &ion_test_fops; 252 testdev->misc.parent = &pdev->dev; 253 ret = misc_register(&testdev->misc); 254 if (ret) { 255 pr_err("failed to register misc device.\n"); 256 return ret; 257 } 258 259 platform_set_drvdata(pdev, testdev); 260 261 return 0; 262} 263 264static struct platform_driver ion_test_platform_driver = { 265 .driver = { 266 .name = "ion-test", 267 }, 268}; 269 270static int __init ion_test_init(void) 271{ 272 platform_device_register_simple("ion-test", -1, NULL, 0); 273 return platform_driver_probe(&ion_test_platform_driver, ion_test_probe); 274} 275 276static void __exit ion_test_exit(void) 277{ 278 platform_driver_unregister(&ion_test_platform_driver); 279} 280 281module_init(ion_test_init); 282module_exit(ion_test_exit); 283