Commit | Line | Data |
---|---|---|
ceff95d4 CC |
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 | ||
35 | struct ion_test_device { | |
36 | struct miscdevice misc; | |
37 | }; | |
38 | ||
39 | struct ion_test_data { | |
40 | struct dma_buf *dma_buf; | |
41 | struct device *dev; | |
42 | }; | |
43 | ||
44 | static 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 | ||
94 | err: | |
95 | dma_buf_unmap_attachment(attach, table, dir); | |
96 | dma_buf_detach(dma_buf, attach); | |
97 | return ret; | |
98 | } | |
99 | ||
100 | static 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 | ||
831e9da7 | 112 | ret = dma_buf_begin_cpu_access(dma_buf, dir); |
ceff95d4 CC |
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 | } | |
141 | err: | |
831e9da7 | 142 | dma_buf_end_cpu_access(dma_buf, dir); |
ceff95d4 CC |
143 | return ret; |
144 | } | |
145 | ||
146 | static 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 | ||
206 | static 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 | ||
222 | static 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 | ||
231 | static const struct file_operations ion_test_fops = { | |
232 | .owner = THIS_MODULE, | |
233 | .unlocked_ioctl = ion_test_ioctl, | |
07300f43 | 234 | .compat_ioctl = ion_test_ioctl, |
ceff95d4 CC |
235 | .open = ion_test_open, |
236 | .release = ion_test_release, | |
237 | }; | |
238 | ||
239 | static 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 | ||
954c2d8f PT |
264 | static int ion_test_remove(struct platform_device *pdev) |
265 | { | |
266 | struct ion_test_device *testdev; | |
267 | ||
268 | testdev = platform_get_drvdata(pdev); | |
269 | if (!testdev) | |
270 | return -ENODATA; | |
271 | ||
f368ed60 GKH |
272 | misc_deregister(&testdev->misc); |
273 | return 0; | |
954c2d8f PT |
274 | } |
275 | ||
81fb0b90 | 276 | static struct platform_device *ion_test_pdev; |
ceff95d4 | 277 | static struct platform_driver ion_test_platform_driver = { |
954c2d8f | 278 | .remove = ion_test_remove, |
ceff95d4 CC |
279 | .driver = { |
280 | .name = "ion-test", | |
281 | }, | |
282 | }; | |
283 | ||
284 | static int __init ion_test_init(void) | |
285 | { | |
81fb0b90 PT |
286 | ion_test_pdev = platform_device_register_simple("ion-test", |
287 | -1, NULL, 0); | |
288 | if (!ion_test_pdev) | |
289 | return -ENODEV; | |
290 | ||
ceff95d4 CC |
291 | return platform_driver_probe(&ion_test_platform_driver, ion_test_probe); |
292 | } | |
293 | ||
294 | static void __exit ion_test_exit(void) | |
295 | { | |
296 | platform_driver_unregister(&ion_test_platform_driver); | |
81fb0b90 | 297 | platform_device_unregister(ion_test_pdev); |
ceff95d4 CC |
298 | } |
299 | ||
300 | module_init(ion_test_init); | |
301 | module_exit(ion_test_exit); | |
86d7b29e | 302 | MODULE_LICENSE("GPL v2"); |