fbdev: add FBIOCOPYAREA ioctl
[platform/kernel/linux-rpi.git] / drivers / video / fbdev / core / fb_chrdev.c
1 // SPDX-License-Identifier: GPL-2.0
2
3 #include <linux/compat.h>
4 #include <linux/console.h>
5 #include <linux/fb.h>
6 #include <linux/fbcon.h>
7 #include <linux/major.h>
8
9 #include "fb_internal.h"
10
11 /*
12  * We hold a reference to the fb_info in file->private_data,
13  * but if the current registered fb has changed, we don't
14  * actually want to use it.
15  *
16  * So look up the fb_info using the inode minor number,
17  * and just verify it against the reference we have.
18  */
19 static struct fb_info *file_fb_info(struct file *file)
20 {
21         struct inode *inode = file_inode(file);
22         int fbidx = iminor(inode);
23         struct fb_info *info = registered_fb[fbidx];
24
25         if (info != file->private_data)
26                 info = NULL;
27         return info;
28 }
29
30 static ssize_t fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
31 {
32         struct fb_info *info = file_fb_info(file);
33
34         if (!info)
35                 return -ENODEV;
36
37         if (info->state != FBINFO_STATE_RUNNING)
38                 return -EPERM;
39
40         if (info->fbops->fb_read)
41                 return info->fbops->fb_read(info, buf, count, ppos);
42
43         return fb_io_read(info, buf, count, ppos);
44 }
45
46 static ssize_t fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
47 {
48         struct fb_info *info = file_fb_info(file);
49
50         if (!info)
51                 return -ENODEV;
52
53         if (info->state != FBINFO_STATE_RUNNING)
54                 return -EPERM;
55
56         if (info->fbops->fb_write)
57                 return info->fbops->fb_write(info, buf, count, ppos);
58
59         return fb_io_write(info, buf, count, ppos);
60 }
61
62 static int fb_copyarea_user(struct fb_info *info,
63                             struct fb_copyarea *copy)
64 {
65         int ret = 0;
66         lock_fb_info(info);
67         if (copy->dx >= info->var.xres ||
68             copy->sx >= info->var.xres ||
69             copy->width > info->var.xres ||
70             copy->dy >= info->var.yres ||
71             copy->sy >= info->var.yres ||
72             copy->height > info->var.yres ||
73             copy->dx + copy->width > info->var.xres ||
74             copy->sx + copy->width > info->var.xres ||
75             copy->dy + copy->height > info->var.yres ||
76             copy->sy + copy->height > info->var.yres) {
77                 ret = -EINVAL;
78                 goto out;
79         }
80         info->fbops->fb_copyarea(info, copy);
81 out:
82         unlock_fb_info(info);
83         return ret;
84 }
85
86 static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
87                         unsigned long arg)
88 {
89         const struct fb_ops *fb;
90         struct fb_var_screeninfo var;
91         struct fb_fix_screeninfo fix;
92         struct fb_cmap cmap_from;
93         struct fb_cmap_user cmap;
94         struct fb_copyarea copy;
95         void __user *argp = (void __user *)arg;
96         long ret = 0;
97
98         switch (cmd) {
99         case FBIOGET_VSCREENINFO:
100                 lock_fb_info(info);
101                 var = info->var;
102                 unlock_fb_info(info);
103
104                 ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;
105                 break;
106         case FBIOPUT_VSCREENINFO:
107                 if (copy_from_user(&var, argp, sizeof(var)))
108                         return -EFAULT;
109                 /* only for kernel-internal use */
110                 var.activate &= ~FB_ACTIVATE_KD_TEXT;
111                 console_lock();
112                 lock_fb_info(info);
113                 ret = fbcon_modechange_possible(info, &var);
114                 if (!ret)
115                         ret = fb_set_var(info, &var);
116                 if (!ret)
117                         fbcon_update_vcs(info, var.activate & FB_ACTIVATE_ALL);
118                 unlock_fb_info(info);
119                 console_unlock();
120                 if (!ret && copy_to_user(argp, &var, sizeof(var)))
121                         ret = -EFAULT;
122                 break;
123         case FBIOGET_FSCREENINFO:
124                 lock_fb_info(info);
125                 memcpy(&fix, &info->fix, sizeof(fix));
126                 if (info->flags & FBINFO_HIDE_SMEM_START)
127                         fix.smem_start = 0;
128                 unlock_fb_info(info);
129
130                 ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;
131                 break;
132         case FBIOPUTCMAP:
133                 if (copy_from_user(&cmap, argp, sizeof(cmap)))
134                         return -EFAULT;
135                 ret = fb_set_user_cmap(&cmap, info);
136                 break;
137         case FBIOGETCMAP:
138                 if (copy_from_user(&cmap, argp, sizeof(cmap)))
139                         return -EFAULT;
140                 lock_fb_info(info);
141                 cmap_from = info->cmap;
142                 unlock_fb_info(info);
143                 ret = fb_cmap_to_user(&cmap_from, &cmap);
144                 break;
145         case FBIOPAN_DISPLAY:
146                 if (copy_from_user(&var, argp, sizeof(var)))
147                         return -EFAULT;
148                 console_lock();
149                 lock_fb_info(info);
150                 ret = fb_pan_display(info, &var);
151                 unlock_fb_info(info);
152                 console_unlock();
153                 if (ret == 0 && copy_to_user(argp, &var, sizeof(var)))
154                         return -EFAULT;
155                 break;
156         case FBIO_CURSOR:
157                 ret = -EINVAL;
158                 break;
159         case FBIOGET_CON2FBMAP:
160                 ret = fbcon_get_con2fb_map_ioctl(argp);
161                 break;
162         case FBIOPUT_CON2FBMAP:
163                 ret = fbcon_set_con2fb_map_ioctl(argp);
164                 break;
165         case FBIOBLANK:
166                 if (arg > FB_BLANK_POWERDOWN)
167                         return -EINVAL;
168                 console_lock();
169                 lock_fb_info(info);
170                 ret = fb_blank(info, arg);
171                 /* might again call into fb_blank */
172                 fbcon_fb_blanked(info, arg);
173                 unlock_fb_info(info);
174                 console_unlock();
175                 break;
176         case FBIOCOPYAREA:
177                 if (info->flags & FBINFO_HWACCEL_COPYAREA) {
178                         /* only provide this ioctl if it is accelerated */
179                         if (copy_from_user(&copy, argp, sizeof(copy)))
180                                 return -EFAULT;
181                         ret = fb_copyarea_user(info, &copy);
182                         break;
183                 }
184         fallthrough;
185         default:
186                 lock_fb_info(info);
187                 fb = info->fbops;
188                 if (fb->fb_ioctl)
189                         ret = fb->fb_ioctl(info, cmd, arg);
190                 else
191                         ret = -ENOTTY;
192                 unlock_fb_info(info);
193         }
194         return ret;
195 }
196
197 static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
198 {
199         struct fb_info *info = file_fb_info(file);
200
201         if (!info)
202                 return -ENODEV;
203         return do_fb_ioctl(info, cmd, arg);
204 }
205
206 #ifdef CONFIG_COMPAT
207 struct fb_fix_screeninfo32 {
208         char                    id[16];
209         compat_caddr_t          smem_start;
210         u32                     smem_len;
211         u32                     type;
212         u32                     type_aux;
213         u32                     visual;
214         u16                     xpanstep;
215         u16                     ypanstep;
216         u16                     ywrapstep;
217         u32                     line_length;
218         compat_caddr_t          mmio_start;
219         u32                     mmio_len;
220         u32                     accel;
221         u16                     reserved[3];
222 };
223
224 struct fb_cmap32 {
225         u32                     start;
226         u32                     len;
227         compat_caddr_t  red;
228         compat_caddr_t  green;
229         compat_caddr_t  blue;
230         compat_caddr_t  transp;
231 };
232
233 static int fb_getput_cmap(struct fb_info *info, unsigned int cmd,
234                           unsigned long arg)
235 {
236         struct fb_cmap32 cmap32;
237         struct fb_cmap cmap_from;
238         struct fb_cmap_user cmap;
239
240         if (copy_from_user(&cmap32, compat_ptr(arg), sizeof(cmap32)))
241                 return -EFAULT;
242
243         cmap = (struct fb_cmap_user) {
244                 .start  = cmap32.start,
245                 .len    = cmap32.len,
246                 .red    = compat_ptr(cmap32.red),
247                 .green  = compat_ptr(cmap32.green),
248                 .blue   = compat_ptr(cmap32.blue),
249                 .transp = compat_ptr(cmap32.transp),
250         };
251
252         if (cmd == FBIOPUTCMAP)
253                 return fb_set_user_cmap(&cmap, info);
254
255         lock_fb_info(info);
256         cmap_from = info->cmap;
257         unlock_fb_info(info);
258
259         return fb_cmap_to_user(&cmap_from, &cmap);
260 }
261
262 static int do_fscreeninfo_to_user(struct fb_fix_screeninfo *fix,
263                                   struct fb_fix_screeninfo32 __user *fix32)
264 {
265         __u32 data;
266         int err;
267
268         err = copy_to_user(&fix32->id, &fix->id, sizeof(fix32->id));
269
270         data = (__u32) (unsigned long) fix->smem_start;
271         err |= put_user(data, &fix32->smem_start);
272
273         err |= put_user(fix->smem_len, &fix32->smem_len);
274         err |= put_user(fix->type, &fix32->type);
275         err |= put_user(fix->type_aux, &fix32->type_aux);
276         err |= put_user(fix->visual, &fix32->visual);
277         err |= put_user(fix->xpanstep, &fix32->xpanstep);
278         err |= put_user(fix->ypanstep, &fix32->ypanstep);
279         err |= put_user(fix->ywrapstep, &fix32->ywrapstep);
280         err |= put_user(fix->line_length, &fix32->line_length);
281
282         data = (__u32) (unsigned long) fix->mmio_start;
283         err |= put_user(data, &fix32->mmio_start);
284
285         err |= put_user(fix->mmio_len, &fix32->mmio_len);
286         err |= put_user(fix->accel, &fix32->accel);
287         err |= copy_to_user(fix32->reserved, fix->reserved,
288                             sizeof(fix->reserved));
289
290         if (err)
291                 return -EFAULT;
292         return 0;
293 }
294
295 static int fb_get_fscreeninfo(struct fb_info *info, unsigned int cmd,
296                               unsigned long arg)
297 {
298         struct fb_fix_screeninfo fix;
299
300         lock_fb_info(info);
301         fix = info->fix;
302         if (info->flags & FBINFO_HIDE_SMEM_START)
303                 fix.smem_start = 0;
304         unlock_fb_info(info);
305         return do_fscreeninfo_to_user(&fix, compat_ptr(arg));
306 }
307
308 static long fb_compat_ioctl(struct file *file, unsigned int cmd,
309                             unsigned long arg)
310 {
311         struct fb_info *info = file_fb_info(file);
312         const struct fb_ops *fb;
313         long ret = -ENOIOCTLCMD;
314
315         if (!info)
316                 return -ENODEV;
317         fb = info->fbops;
318         switch (cmd) {
319         case FBIOGET_VSCREENINFO:
320         case FBIOPUT_VSCREENINFO:
321         case FBIOPAN_DISPLAY:
322         case FBIOGET_CON2FBMAP:
323         case FBIOPUT_CON2FBMAP:
324         case FBIOCOPYAREA:
325                 arg = (unsigned long) compat_ptr(arg);
326                 fallthrough;
327         case FBIOBLANK:
328                 ret = do_fb_ioctl(info, cmd, arg);
329                 break;
330
331         case FBIOGET_FSCREENINFO:
332                 ret = fb_get_fscreeninfo(info, cmd, arg);
333                 break;
334
335         case FBIOGETCMAP:
336         case FBIOPUTCMAP:
337                 ret = fb_getput_cmap(info, cmd, arg);
338                 break;
339
340         default:
341                 if (fb->fb_compat_ioctl)
342                         ret = fb->fb_compat_ioctl(info, cmd, arg);
343                 break;
344         }
345         return ret;
346 }
347 #endif
348
349 static int fb_mmap(struct file *file, struct vm_area_struct *vma)
350 {
351         struct fb_info *info = file_fb_info(file);
352         unsigned long mmio_pgoff;
353         unsigned long start;
354         u32 len;
355
356         if (!info)
357                 return -ENODEV;
358         mutex_lock(&info->mm_lock);
359
360         if (info->fbops->fb_mmap) {
361                 int res;
362
363                 /*
364                  * The framebuffer needs to be accessed decrypted, be sure
365                  * SME protection is removed ahead of the call
366                  */
367                 vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
368                 res = info->fbops->fb_mmap(info, vma);
369                 mutex_unlock(&info->mm_lock);
370                 return res;
371 #if IS_ENABLED(CONFIG_FB_DEFERRED_IO)
372         } else if (info->fbdefio) {
373                 /*
374                  * FB deferred I/O wants you to handle mmap in your drivers. At a
375                  * minimum, point struct fb_ops.fb_mmap to fb_deferred_io_mmap().
376                  */
377                 dev_warn_once(info->dev, "fbdev mmap not set up for deferred I/O.\n");
378                 mutex_unlock(&info->mm_lock);
379                 return -ENODEV;
380 #endif
381         }
382
383         /*
384          * Ugh. This can be either the frame buffer mapping, or
385          * if pgoff points past it, the mmio mapping.
386          */
387         start = info->fix.smem_start;
388         len = info->fix.smem_len;
389         mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT;
390         if (vma->vm_pgoff >= mmio_pgoff) {
391                 if (info->var.accel_flags) {
392                         mutex_unlock(&info->mm_lock);
393                         return -EINVAL;
394                 }
395
396                 vma->vm_pgoff -= mmio_pgoff;
397                 start = info->fix.mmio_start;
398                 len = info->fix.mmio_len;
399         }
400         mutex_unlock(&info->mm_lock);
401
402         vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
403         fb_pgprotect(file, vma, start);
404
405         return vm_iomap_memory(vma, start, len);
406 }
407
408 static int fb_open(struct inode *inode, struct file *file)
409 __acquires(&info->lock)
410 __releases(&info->lock)
411 {
412         int fbidx = iminor(inode);
413         struct fb_info *info;
414         int res = 0;
415
416         info = get_fb_info(fbidx);
417         if (!info) {
418                 request_module("fb%d", fbidx);
419                 info = get_fb_info(fbidx);
420                 if (!info)
421                         return -ENODEV;
422         }
423         if (IS_ERR(info))
424                 return PTR_ERR(info);
425
426         lock_fb_info(info);
427         if (!try_module_get(info->fbops->owner)) {
428                 res = -ENODEV;
429                 goto out;
430         }
431         file->private_data = info;
432         if (info->fbops->fb_open) {
433                 res = info->fbops->fb_open(info, 1);
434                 if (res)
435                         module_put(info->fbops->owner);
436         }
437 #ifdef CONFIG_FB_DEFERRED_IO
438         if (info->fbdefio)
439                 fb_deferred_io_open(info, inode, file);
440 #endif
441 out:
442         unlock_fb_info(info);
443         if (res)
444                 put_fb_info(info);
445         return res;
446 }
447
448 static int fb_release(struct inode *inode, struct file *file)
449 __acquires(&info->lock)
450 __releases(&info->lock)
451 {
452         struct fb_info * const info = file->private_data;
453
454         lock_fb_info(info);
455 #if IS_ENABLED(CONFIG_FB_DEFERRED_IO)
456         if (info->fbdefio)
457                 fb_deferred_io_release(info);
458 #endif
459         if (info->fbops->fb_release)
460                 info->fbops->fb_release(info, 1);
461         module_put(info->fbops->owner);
462         unlock_fb_info(info);
463         put_fb_info(info);
464         return 0;
465 }
466
467 #if defined(CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA) && !defined(CONFIG_MMU)
468 static unsigned long get_fb_unmapped_area(struct file *filp,
469                                    unsigned long addr, unsigned long len,
470                                    unsigned long pgoff, unsigned long flags)
471 {
472         struct fb_info * const info = filp->private_data;
473         unsigned long fb_size = PAGE_ALIGN(info->fix.smem_len);
474
475         if (pgoff > fb_size || len > fb_size - pgoff)
476                 return -EINVAL;
477
478         return (unsigned long)info->screen_base + pgoff;
479 }
480 #endif
481
482 static const struct file_operations fb_fops = {
483         .owner = THIS_MODULE,
484         .read = fb_read,
485         .write = fb_write,
486         .unlocked_ioctl = fb_ioctl,
487 #ifdef CONFIG_COMPAT
488         .compat_ioctl = fb_compat_ioctl,
489 #endif
490         .mmap = fb_mmap,
491         .open = fb_open,
492         .release = fb_release,
493 #if defined(HAVE_ARCH_FB_UNMAPPED_AREA) || \
494         (defined(CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA) && \
495          !defined(CONFIG_MMU))
496         .get_unmapped_area = get_fb_unmapped_area,
497 #endif
498 #ifdef CONFIG_FB_DEFERRED_IO
499         .fsync = fb_deferred_io_fsync,
500 #endif
501         .llseek = default_llseek,
502 };
503
504 int fb_register_chrdev(void)
505 {
506         int ret;
507
508         ret = register_chrdev(FB_MAJOR, "fb", &fb_fops);
509         if (ret) {
510                 pr_err("Unable to get major %d for fb devs\n", FB_MAJOR);
511                 return ret;
512         }
513
514         return ret;
515 }
516
517 void fb_unregister_chrdev(void)
518 {
519         unregister_chrdev(FB_MAJOR, "fb");
520 }