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