File Index
Files and directories
Sign in
FILE

fb_chrdev.c

File preview with syntax highlighting.

linux-6.19.14/drivers/video/fbdev/core/fb_chrdev.c 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 */
 19static 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
 30static 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 (fb_WARN_ON_ONCE(info, !info->fbops->fb_read))
 38		return -EINVAL;
 39
 40	if (info->state != FBINFO_STATE_RUNNING)
 41		return -EPERM;
 42
 43	return info->fbops->fb_read(info, buf, count, ppos);
 44}
 45
 46static 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 (fb_WARN_ON_ONCE(info, !info->fbops->fb_write))
 54		return -EINVAL;
 55
 56	if (info->state != FBINFO_STATE_RUNNING)
 57		return -EPERM;
 58
 59	return info->fbops->fb_write(info, buf, count, ppos);
 60}
 61
 62static 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
163static 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
173struct 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
190struct 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
199static 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
228static 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
261static 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
274static 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
314static int fb_mmap(struct file *file, struct vm_area_struct *vma)
315{
316	struct fb_info *info = file_fb_info(file);
317	int res;
318
319	if (!info)
320		return -ENODEV;
321
322	if (fb_WARN_ON_ONCE(info, !info->fbops->fb_mmap))
323		return -ENODEV;
324
325	mutex_lock(&info->mm_lock);
326	res = info->fbops->fb_mmap(info, vma);
327	mutex_unlock(&info->mm_lock);
328
329	return res;
330}
331
332static int fb_open(struct inode *inode, struct file *file)
333__acquires(&info->lock)
334__releases(&info->lock)
335{
336	int fbidx = iminor(inode);
337	struct fb_info *info;
338	int res = 0;
339
340	info = get_fb_info(fbidx);
341	if (!info) {
342		request_module("fb%d", fbidx);
343		info = get_fb_info(fbidx);
344		if (!info)
345			return -ENODEV;
346	}
347	if (IS_ERR(info))
348		return PTR_ERR(info);
349
350	lock_fb_info(info);
351	if (!try_module_get(info->fbops->owner)) {
352		res = -ENODEV;
353		goto out;
354	}
355	file->private_data = info;
356	if (info->fbops->fb_open) {
357		res = info->fbops->fb_open(info, 1);
358		if (res)
359			module_put(info->fbops->owner);
360	}
361#ifdef CONFIG_FB_DEFERRED_IO
362	if (info->fbdefio)
363		fb_deferred_io_open(info, inode, file);
364#endif
365out:
366	unlock_fb_info(info);
367	if (res)
368		put_fb_info(info);
369	return res;
370}
371
372static int fb_release(struct inode *inode, struct file *file)
373__acquires(&info->lock)
374__releases(&info->lock)
375{
376	struct fb_info * const info = file->private_data;
377
378	lock_fb_info(info);
379#if IS_ENABLED(CONFIG_FB_DEFERRED_IO)
380	if (info->fbdefio)
381		fb_deferred_io_release(info);
382#endif
383	if (info->fbops->fb_release)
384		info->fbops->fb_release(info, 1);
385	module_put(info->fbops->owner);
386	unlock_fb_info(info);
387	put_fb_info(info);
388	return 0;
389}
390
391#if defined(CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA) && !defined(CONFIG_MMU)
392static unsigned long get_fb_unmapped_area(struct file *filp,
393				   unsigned long addr, unsigned long len,
394				   unsigned long pgoff, unsigned long flags)
395{
396	struct fb_info * const info = filp->private_data;
397	unsigned long fb_size = PAGE_ALIGN(info->fix.smem_len);
398
399	if (pgoff > fb_size || len > fb_size - pgoff)
400		return -EINVAL;
401
402	return (unsigned long)info->screen_base + pgoff;
403}
404#endif
405
406static const struct file_operations fb_fops = {
407	.owner = THIS_MODULE,
408	.read = fb_read,
409	.write = fb_write,
410	.unlocked_ioctl = fb_ioctl,
411#ifdef CONFIG_COMPAT
412	.compat_ioctl = fb_compat_ioctl,
413#endif
414	.mmap = fb_mmap,
415	.open = fb_open,
416	.release = fb_release,
417#if defined(HAVE_ARCH_FB_UNMAPPED_AREA) || \
418	(defined(CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA) && \
419	 !defined(CONFIG_MMU))
420	.get_unmapped_area = get_fb_unmapped_area,
421#endif
422#ifdef CONFIG_FB_DEFERRED_IO
423	.fsync = fb_deferred_io_fsync,
424#endif
425	.llseek = default_llseek,
426};
427
428int fb_register_chrdev(void)
429{
430	int ret;
431
432	ret = register_chrdev(FB_MAJOR, "fb", &fb_fops);
433	if (ret) {
434		pr_err("Unable to get major %d for fb devs\n", FB_MAJOR);
435		return ret;
436	}
437
438	return ret;
439}
440
441void fb_unregister_chrdev(void)
442{
443	unregister_chrdev(FB_MAJOR, "fb");
444}
~/ Read-only file preview.