Blame view

drivers/dma-buf/udmabuf.c 7.59 KB
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
1
  // SPDX-License-Identifier: GPL-2.0
913965c42   Gerd Hoffmann   udmabuf: sort hea...
2
  #include <linux/cred.h>
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
3
  #include <linux/device.h>
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
4
5
  #include <linux/dma-buf.h>
  #include <linux/highmem.h>
913965c42   Gerd Hoffmann   udmabuf: sort hea...
6
7
  #include <linux/init.h>
  #include <linux/kernel.h>
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
8
  #include <linux/memfd.h>
913965c42   Gerd Hoffmann   udmabuf: sort hea...
9
10
11
12
13
  #include <linux/miscdevice.h>
  #include <linux/module.h>
  #include <linux/shmem_fs.h>
  #include <linux/slab.h>
  #include <linux/udmabuf.h>
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
14

dc4716d75   Gerd Hoffmann   udmabuf: rework l...
15
16
  static const u32    list_limit = 1024;  /* udmabuf_create_list->count limit */
  static const size_t size_limit_mb = 64; /* total dmabuf size, in megabytes  */
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
17
  struct udmabuf {
b35f57c43   Gerd Hoffmann   udmabuf: use pgof...
18
  	pgoff_t pagecount;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
19
  	struct page **pages;
284562e1f   Gurchetan Singh   udmabuf: implemen...
20
  	struct sg_table *sg;
c1bbed668   Gurchetan Singh   udmabuf: add a po...
21
  	struct miscdevice *device;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
22
  };
300133d37   Souptick Joarder   drivers/dma-buf/u...
23
  static vm_fault_t udmabuf_vm_fault(struct vm_fault *vmf)
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
24
25
26
  {
  	struct vm_area_struct *vma = vmf->vma;
  	struct udmabuf *ubuf = vma->vm_private_data;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
  	vmf->page = ubuf->pages[vmf->pgoff];
  	get_page(vmf->page);
  	return 0;
  }
  
  static const struct vm_operations_struct udmabuf_vm_ops = {
  	.fault = udmabuf_vm_fault,
  };
  
  static int mmap_udmabuf(struct dma_buf *buf, struct vm_area_struct *vma)
  {
  	struct udmabuf *ubuf = buf->priv;
  
  	if ((vma->vm_flags & (VM_SHARED | VM_MAYSHARE)) == 0)
  		return -EINVAL;
  
  	vma->vm_ops = &udmabuf_vm_ops;
  	vma->vm_private_data = ubuf;
  	return 0;
  }
17a7ce203   Gurchetan Singh   udmabuf: separate...
47
48
  static struct sg_table *get_sg_table(struct device *dev, struct dma_buf *buf,
  				     enum dma_data_direction direction)
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
49
  {
17a7ce203   Gurchetan Singh   udmabuf: separate...
50
  	struct udmabuf *ubuf = buf->priv;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
51
  	struct sg_table *sg;
a3e722dad   Gerd Hoffmann   udmabuf: improve ...
52
  	int ret;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
53
54
55
  
  	sg = kzalloc(sizeof(*sg), GFP_KERNEL);
  	if (!sg)
a3e722dad   Gerd Hoffmann   udmabuf: improve ...
56
57
58
59
60
61
  		return ERR_PTR(-ENOMEM);
  	ret = sg_alloc_table_from_pages(sg, ubuf->pages, ubuf->pagecount,
  					0, ubuf->pagecount << PAGE_SHIFT,
  					GFP_KERNEL);
  	if (ret < 0)
  		goto err;
62296b395   Marek Szyprowski   dmabuf: fix commo...
62
63
  	ret = dma_map_sgtable(dev, sg, direction, 0);
  	if (ret < 0)
a3e722dad   Gerd Hoffmann   udmabuf: improve ...
64
  		goto err;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
65
  	return sg;
a3e722dad   Gerd Hoffmann   udmabuf: improve ...
66
  err:
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
67
  	sg_free_table(sg);
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
68
  	kfree(sg);
a3e722dad   Gerd Hoffmann   udmabuf: improve ...
69
  	return ERR_PTR(ret);
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
70
  }
17a7ce203   Gurchetan Singh   udmabuf: separate...
71
72
73
  static void put_sg_table(struct device *dev, struct sg_table *sg,
  			 enum dma_data_direction direction)
  {
62296b395   Marek Szyprowski   dmabuf: fix commo...
74
  	dma_unmap_sgtable(dev, sg, direction, 0);
17a7ce203   Gurchetan Singh   udmabuf: separate...
75
76
77
78
79
80
81
82
83
  	sg_free_table(sg);
  	kfree(sg);
  }
  
  static struct sg_table *map_udmabuf(struct dma_buf_attachment *at,
  				    enum dma_data_direction direction)
  {
  	return get_sg_table(at->dev, at->dmabuf, direction);
  }
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
84
85
86
87
  static void unmap_udmabuf(struct dma_buf_attachment *at,
  			  struct sg_table *sg,
  			  enum dma_data_direction direction)
  {
17a7ce203   Gurchetan Singh   udmabuf: separate...
88
  	return put_sg_table(at->dev, sg, direction);
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
89
90
91
92
93
  }
  
  static void release_udmabuf(struct dma_buf *buf)
  {
  	struct udmabuf *ubuf = buf->priv;
284562e1f   Gurchetan Singh   udmabuf: implemen...
94
  	struct device *dev = ubuf->device->this_device;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
95
  	pgoff_t pg;
284562e1f   Gurchetan Singh   udmabuf: implemen...
96
97
  	if (ubuf->sg)
  		put_sg_table(dev, ubuf->sg, DMA_BIDIRECTIONAL);
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
98
99
100
101
102
  	for (pg = 0; pg < ubuf->pagecount; pg++)
  		put_page(ubuf->pages[pg]);
  	kfree(ubuf->pages);
  	kfree(ubuf);
  }
284562e1f   Gurchetan Singh   udmabuf: implemen...
103
104
105
106
107
108
109
110
111
112
113
  static int begin_cpu_udmabuf(struct dma_buf *buf,
  			     enum dma_data_direction direction)
  {
  	struct udmabuf *ubuf = buf->priv;
  	struct device *dev = ubuf->device->this_device;
  
  	if (!ubuf->sg) {
  		ubuf->sg = get_sg_table(dev, buf, direction);
  		if (IS_ERR(ubuf->sg))
  			return PTR_ERR(ubuf->sg);
  	} else {
1ffe09590   Gurchetan Singh   udmabuf: fix dma-...
114
115
  		dma_sync_sg_for_cpu(dev, ubuf->sg->sgl, ubuf->sg->nents,
  				    direction);
284562e1f   Gurchetan Singh   udmabuf: implemen...
116
117
118
119
120
121
122
123
124
125
126
127
128
  	}
  
  	return 0;
  }
  
  static int end_cpu_udmabuf(struct dma_buf *buf,
  			   enum dma_data_direction direction)
  {
  	struct udmabuf *ubuf = buf->priv;
  	struct device *dev = ubuf->device->this_device;
  
  	if (!ubuf->sg)
  		return -EINVAL;
1ffe09590   Gurchetan Singh   udmabuf: fix dma-...
129
  	dma_sync_sg_for_device(dev, ubuf->sg->sgl, ubuf->sg->nents, direction);
284562e1f   Gurchetan Singh   udmabuf: implemen...
130
131
  	return 0;
  }
a34852891   Gerd Hoffmann   udmabuf: constify...
132
  static const struct dma_buf_ops udmabuf_ops = {
bc7a71da4   Gurchetan Singh   udmabuf: use cach...
133
134
135
136
137
  	.cache_sgt_mapping = true,
  	.map_dma_buf	   = map_udmabuf,
  	.unmap_dma_buf	   = unmap_udmabuf,
  	.release	   = release_udmabuf,
  	.mmap		   = mmap_udmabuf,
284562e1f   Gurchetan Singh   udmabuf: implemen...
138
139
  	.begin_cpu_access  = begin_cpu_udmabuf,
  	.end_cpu_access    = end_cpu_udmabuf,
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
140
141
142
143
  };
  
  #define SEALS_WANTED (F_SEAL_SHRINK)
  #define SEALS_DENIED (F_SEAL_WRITE)
c1bbed668   Gurchetan Singh   udmabuf: add a po...
144
145
146
  static long udmabuf_create(struct miscdevice *device,
  			   struct udmabuf_create_list *head,
  			   struct udmabuf_create_item *list)
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
147
148
149
150
151
  {
  	DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
  	struct file *memfd = NULL;
  	struct udmabuf *ubuf;
  	struct dma_buf *buf;
0d17455ca   Gerd Hoffmann   udmabuf: improve ...
152
  	pgoff_t pgoff, pgcnt, pgidx, pgbuf = 0, pglimit;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
153
154
155
  	struct page *page;
  	int seals, ret = -EINVAL;
  	u32 i, flags;
33f35429f   Gerd Hoffmann   udmabuf: use size...
156
  	ubuf = kzalloc(sizeof(*ubuf), GFP_KERNEL);
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
157
158
  	if (!ubuf)
  		return -ENOMEM;
dc4716d75   Gerd Hoffmann   udmabuf: rework l...
159
  	pglimit = (size_limit_mb * 1024 * 1024) >> PAGE_SHIFT;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
160
161
  	for (i = 0; i < head->count; i++) {
  		if (!IS_ALIGNED(list[i].offset, PAGE_SIZE))
0d17455ca   Gerd Hoffmann   udmabuf: improve ...
162
  			goto err;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
163
  		if (!IS_ALIGNED(list[i].size, PAGE_SIZE))
0d17455ca   Gerd Hoffmann   udmabuf: improve ...
164
  			goto err;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
165
  		ubuf->pagecount += list[i].size >> PAGE_SHIFT;
dc4716d75   Gerd Hoffmann   udmabuf: rework l...
166
  		if (ubuf->pagecount > pglimit)
0d17455ca   Gerd Hoffmann   udmabuf: improve ...
167
  			goto err;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
168
  	}
33f35429f   Gerd Hoffmann   udmabuf: use size...
169
  	ubuf->pages = kmalloc_array(ubuf->pagecount, sizeof(*ubuf->pages),
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
170
171
172
  				    GFP_KERNEL);
  	if (!ubuf->pages) {
  		ret = -ENOMEM;
0d17455ca   Gerd Hoffmann   udmabuf: improve ...
173
  		goto err;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
174
175
176
177
  	}
  
  	pgbuf = 0;
  	for (i = 0; i < head->count; i++) {
7a1c67d78   Gerd Hoffmann   udmabuf: use EBAD...
178
  		ret = -EBADFD;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
179
180
  		memfd = fget(list[i].memfd);
  		if (!memfd)
0d17455ca   Gerd Hoffmann   udmabuf: improve ...
181
  			goto err;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
182
  		if (!shmem_mapping(file_inode(memfd)->i_mapping))
0d17455ca   Gerd Hoffmann   udmabuf: improve ...
183
  			goto err;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
184
  		seals = memfd_fcntl(memfd, F_GET_SEALS, 0);
7a1c67d78   Gerd Hoffmann   udmabuf: use EBAD...
185
186
187
188
  		if (seals == -EINVAL)
  			goto err;
  		ret = -EINVAL;
  		if ((seals & SEALS_WANTED) != SEALS_WANTED ||
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
189
  		    (seals & SEALS_DENIED) != 0)
0d17455ca   Gerd Hoffmann   udmabuf: improve ...
190
  			goto err;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
191
192
193
194
195
196
197
  		pgoff = list[i].offset >> PAGE_SHIFT;
  		pgcnt = list[i].size   >> PAGE_SHIFT;
  		for (pgidx = 0; pgidx < pgcnt; pgidx++) {
  			page = shmem_read_mapping_page(
  				file_inode(memfd)->i_mapping, pgoff + pgidx);
  			if (IS_ERR(page)) {
  				ret = PTR_ERR(page);
0d17455ca   Gerd Hoffmann   udmabuf: improve ...
198
  				goto err;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
199
200
201
202
  			}
  			ubuf->pages[pgbuf++] = page;
  		}
  		fput(memfd);
0d17455ca   Gerd Hoffmann   udmabuf: improve ...
203
  		memfd = NULL;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
204
  	}
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
205
206
207
208
  
  	exp_info.ops  = &udmabuf_ops;
  	exp_info.size = ubuf->pagecount << PAGE_SHIFT;
  	exp_info.priv = ubuf;
5c074eeab   Gerd Hoffmann   udmabuf: set read...
209
  	exp_info.flags = O_RDWR;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
210

c1bbed668   Gurchetan Singh   udmabuf: add a po...
211
  	ubuf->device = device;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
212
213
214
  	buf = dma_buf_export(&exp_info);
  	if (IS_ERR(buf)) {
  		ret = PTR_ERR(buf);
0d17455ca   Gerd Hoffmann   udmabuf: improve ...
215
  		goto err;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
216
217
218
219
220
221
  	}
  
  	flags = 0;
  	if (head->flags & UDMABUF_FLAGS_CLOEXEC)
  		flags |= O_CLOEXEC;
  	return dma_buf_fd(buf, flags);
0d17455ca   Gerd Hoffmann   udmabuf: improve ...
222
  err:
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
223
224
  	while (pgbuf > 0)
  		put_page(ubuf->pages[--pgbuf]);
683a0e630   Gustavo A. R. Silva   dma-buf/udmabuf: ...
225
226
  	if (memfd)
  		fput(memfd);
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
227
228
229
230
231
232
233
234
235
236
237
238
  	kfree(ubuf->pages);
  	kfree(ubuf);
  	return ret;
  }
  
  static long udmabuf_ioctl_create(struct file *filp, unsigned long arg)
  {
  	struct udmabuf_create create;
  	struct udmabuf_create_list head;
  	struct udmabuf_create_item list;
  
  	if (copy_from_user(&create, (void __user *)arg,
33f35429f   Gerd Hoffmann   udmabuf: use size...
239
  			   sizeof(create)))
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
240
241
242
243
244
245
246
  		return -EFAULT;
  
  	head.flags  = create.flags;
  	head.count  = 1;
  	list.memfd  = create.memfd;
  	list.offset = create.offset;
  	list.size   = create.size;
c1bbed668   Gurchetan Singh   udmabuf: add a po...
247
  	return udmabuf_create(filp->private_data, &head, &list);
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
248
249
250
251
252
253
254
255
256
257
258
  }
  
  static long udmabuf_ioctl_create_list(struct file *filp, unsigned long arg)
  {
  	struct udmabuf_create_list head;
  	struct udmabuf_create_item *list;
  	int ret = -EINVAL;
  	u32 lsize;
  
  	if (copy_from_user(&head, (void __user *)arg, sizeof(head)))
  		return -EFAULT;
dc4716d75   Gerd Hoffmann   udmabuf: rework l...
259
  	if (head.count > list_limit)
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
260
261
262
263
264
  		return -EINVAL;
  	lsize = sizeof(struct udmabuf_create_item) * head.count;
  	list = memdup_user((void __user *)(arg + sizeof(head)), lsize);
  	if (IS_ERR(list))
  		return PTR_ERR(list);
c1bbed668   Gurchetan Singh   udmabuf: add a po...
265
  	ret = udmabuf_create(filp->private_data, &head, list);
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
  	kfree(list);
  	return ret;
  }
  
  static long udmabuf_ioctl(struct file *filp, unsigned int ioctl,
  			  unsigned long arg)
  {
  	long ret;
  
  	switch (ioctl) {
  	case UDMABUF_CREATE:
  		ret = udmabuf_ioctl_create(filp, arg);
  		break;
  	case UDMABUF_CREATE_LIST:
  		ret = udmabuf_ioctl_create_list(filp, arg);
  		break;
  	default:
52499d9cd   Gerd Hoffmann   udmabuf: use ENOT...
283
  		ret = -ENOTTY;
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
284
285
286
287
288
289
290
291
  		break;
  	}
  	return ret;
  }
  
  static const struct file_operations udmabuf_fops = {
  	.owner		= THIS_MODULE,
  	.unlocked_ioctl = udmabuf_ioctl,
d4a197f40   Kristian H. Kristensen   udmabuf: Add miss...
292
293
294
  #ifdef CONFIG_COMPAT
  	.compat_ioctl   = udmabuf_ioctl,
  #endif
fbb0de795   Gerd Hoffmann   Add udmabuf misc ...
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
  };
  
  static struct miscdevice udmabuf_misc = {
  	.minor          = MISC_DYNAMIC_MINOR,
  	.name           = "udmabuf",
  	.fops           = &udmabuf_fops,
  };
  
  static int __init udmabuf_dev_init(void)
  {
  	return misc_register(&udmabuf_misc);
  }
  
  static void __exit udmabuf_dev_exit(void)
  {
  	misc_deregister(&udmabuf_misc);
  }
  
  module_init(udmabuf_dev_init)
  module_exit(udmabuf_dev_exit)
  
  MODULE_AUTHOR("Gerd Hoffmann <kraxel@redhat.com>");
  MODULE_LICENSE("GPL v2");