xref: /openbmc/linux/drivers/video/fbdev/core/fb_chrdev.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
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   */
file_fb_info(struct file * file)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  
fb_read(struct file * file,char __user * buf,size_t count,loff_t * ppos)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  
fb_write(struct file * file,const char __user * buf,size_t count,loff_t * ppos)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  
do_fb_ioctl(struct fb_info * info,unsigned int cmd,unsigned long arg)62  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  	void __user *argp = (void __user *)arg;
71  	long ret = 0;
72  
73  	switch (cmd) {
74  	case FBIOGET_VSCREENINFO:
75  		lock_fb_info(info);
76  		var = info->var;
77  		unlock_fb_info(info);
78  
79  		ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;
80  		break;
81  	case FBIOPUT_VSCREENINFO:
82  		if (copy_from_user(&var, argp, sizeof(var)))
83  			return -EFAULT;
84  		/* only for kernel-internal use */
85  		var.activate &= ~FB_ACTIVATE_KD_TEXT;
86  		console_lock();
87  		lock_fb_info(info);
88  		ret = fbcon_modechange_possible(info, &var);
89  		if (!ret)
90  			ret = fb_set_var(info, &var);
91  		if (!ret)
92  			fbcon_update_vcs(info, var.activate & FB_ACTIVATE_ALL);
93  		unlock_fb_info(info);
94  		console_unlock();
95  		if (!ret && copy_to_user(argp, &var, sizeof(var)))
96  			ret = -EFAULT;
97  		break;
98  	case FBIOGET_FSCREENINFO:
99  		lock_fb_info(info);
100  		memcpy(&fix, &info->fix, sizeof(fix));
101  		if (info->flags & FBINFO_HIDE_SMEM_START)
102  			fix.smem_start = 0;
103  		unlock_fb_info(info);
104  
105  		ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;
106  		break;
107  	case FBIOPUTCMAP:
108  		if (copy_from_user(&cmap, argp, sizeof(cmap)))
109  			return -EFAULT;
110  		ret = fb_set_user_cmap(&cmap, info);
111  		break;
112  	case FBIOGETCMAP:
113  		if (copy_from_user(&cmap, argp, sizeof(cmap)))
114  			return -EFAULT;
115  		lock_fb_info(info);
116  		cmap_from = info->cmap;
117  		unlock_fb_info(info);
118  		ret = fb_cmap_to_user(&cmap_from, &cmap);
119  		break;
120  	case FBIOPAN_DISPLAY:
121  		if (copy_from_user(&var, argp, sizeof(var)))
122  			return -EFAULT;
123  		console_lock();
124  		lock_fb_info(info);
125  		ret = fb_pan_display(info, &var);
126  		unlock_fb_info(info);
127  		console_unlock();
128  		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  		ret = fbcon_get_con2fb_map_ioctl(argp);
136  		break;
137  	case FBIOPUT_CON2FBMAP:
138  		ret = fbcon_set_con2fb_map_ioctl(argp);
139  		break;
140  	case FBIOBLANK:
141  		if (arg > FB_BLANK_POWERDOWN)
142  			return -EINVAL;
143  		console_lock();
144  		lock_fb_info(info);
145  		ret = fb_blank(info, arg);
146  		/* might again call into fb_blank */
147  		fbcon_fb_blanked(info, arg);
148  		unlock_fb_info(info);
149  		console_unlock();
150  		break;
151  	default:
152  		lock_fb_info(info);
153  		fb = info->fbops;
154  		if (fb->fb_ioctl)
155  			ret = fb->fb_ioctl(info, cmd, arg);
156  		else
157  			ret = -ENOTTY;
158  		unlock_fb_info(info);
159  	}
160  	return ret;
161  }
162  
fb_ioctl(struct file * file,unsigned int cmd,unsigned long arg)163  static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
164  {
165  	struct fb_info *info = file_fb_info(file);
166  
167  	if (!info)
168  		return -ENODEV;
169  	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  
fb_getput_cmap(struct fb_info * info,unsigned int cmd,unsigned long arg)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  
do_fscreeninfo_to_user(struct fb_fix_screeninfo * fix,struct fb_fix_screeninfo32 __user * fix32)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  
fb_get_fscreeninfo(struct fb_info * info,unsigned int cmd,unsigned long arg)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  
fb_compat_ioctl(struct file * file,unsigned int cmd,unsigned long arg)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  
fb_mmap(struct file * file,struct vm_area_struct * vma)314  static int fb_mmap(struct file *file, struct vm_area_struct *vma)
315  {
316  	struct fb_info *info = file_fb_info(file);
317  	unsigned long mmio_pgoff;
318  	unsigned long start;
319  	u32 len;
320  
321  	if (!info)
322  		return -ENODEV;
323  	mutex_lock(&info->mm_lock);
324  
325  	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  		res = info->fbops->fb_mmap(info, vma);
334  		mutex_unlock(&info->mm_lock);
335  		return res;
336  #if IS_ENABLED(CONFIG_FB_DEFERRED_IO)
337  	} 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  		dev_warn_once(info->dev, "fbdev mmap not set up for deferred I/O.\n");
343  		mutex_unlock(&info->mm_lock);
344  		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  	start = info->fix.smem_start;
353  	len = info->fix.smem_len;
354  	mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT;
355  	if (vma->vm_pgoff >= mmio_pgoff) {
356  		if (info->var.accel_flags) {
357  			mutex_unlock(&info->mm_lock);
358  			return -EINVAL;
359  		}
360  
361  		vma->vm_pgoff -= mmio_pgoff;
362  		start = info->fix.mmio_start;
363  		len = info->fix.mmio_len;
364  	}
365  	mutex_unlock(&info->mm_lock);
366  
367  	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
368  	fb_pgprotect(file, vma, start);
369  
370  	return vm_iomap_memory(vma, start, len);
371  }
372  
fb_open(struct inode * inode,struct file * file)373  static int fb_open(struct inode *inode, struct file *file)
374  __acquires(&info->lock)
375  __releases(&info->lock)
376  {
377  	int fbidx = iminor(inode);
378  	struct fb_info *info;
379  	int res = 0;
380  
381  	info = get_fb_info(fbidx);
382  	if (!info) {
383  		request_module("fb%d", fbidx);
384  		info = get_fb_info(fbidx);
385  		if (!info)
386  			return -ENODEV;
387  	}
388  	if (IS_ERR(info))
389  		return PTR_ERR(info);
390  
391  	lock_fb_info(info);
392  	if (!try_module_get(info->fbops->owner)) {
393  		res = -ENODEV;
394  		goto out;
395  	}
396  	file->private_data = info;
397  	if (info->fbops->fb_open) {
398  		res = info->fbops->fb_open(info, 1);
399  		if (res)
400  			module_put(info->fbops->owner);
401  	}
402  #ifdef CONFIG_FB_DEFERRED_IO
403  	if (info->fbdefio)
404  		fb_deferred_io_open(info, inode, file);
405  #endif
406  out:
407  	unlock_fb_info(info);
408  	if (res)
409  		put_fb_info(info);
410  	return res;
411  }
412  
fb_release(struct inode * inode,struct file * file)413  static int fb_release(struct inode *inode, struct file *file)
414  __acquires(&info->lock)
415  __releases(&info->lock)
416  {
417  	struct fb_info * const info = file->private_data;
418  
419  	lock_fb_info(info);
420  #if IS_ENABLED(CONFIG_FB_DEFERRED_IO)
421  	if (info->fbdefio)
422  		fb_deferred_io_release(info);
423  #endif
424  	if (info->fbops->fb_release)
425  		info->fbops->fb_release(info, 1);
426  	module_put(info->fbops->owner);
427  	unlock_fb_info(info);
428  	put_fb_info(info);
429  	return 0;
430  }
431  
432  #if defined(CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA) && !defined(CONFIG_MMU)
get_fb_unmapped_area(struct file * filp,unsigned long addr,unsigned long len,unsigned long pgoff,unsigned long flags)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  
fb_register_chrdev(void)469  int fb_register_chrdev(void)
470  {
471  	int ret;
472  
473  	ret = register_chrdev(FB_MAJOR, "fb", &fb_fops);
474  	if (ret) {
475  		pr_err("Unable to get major %d for fb devs\n", FB_MAJOR);
476  		return ret;
477  	}
478  
479  	return ret;
480  }
481  
fb_unregister_chrdev(void)482  void fb_unregister_chrdev(void)
483  {
484  	unregister_chrdev(FB_MAJOR, "fb");
485  }
486