xref: /linux/drivers/video/fbdev/core/fb_chrdev.c (revision 98c2b2551b22d975f8f94ad19169a06339da3d8a)
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/major.h>
7 
8 #include "fb_internal.h"
9 #include "fbcon.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 (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 
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 (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 
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 = fb_set_var_from_user(info, &var);
89 		unlock_fb_info(info);
90 		console_unlock();
91 		if (!ret && copy_to_user(argp, &var, sizeof(var)))
92 			ret = -EFAULT;
93 		break;
94 	case FBIOGET_FSCREENINFO:
95 		lock_fb_info(info);
96 		memcpy(&fix, &info->fix, sizeof(fix));
97 		if (info->flags & FBINFO_HIDE_SMEM_START)
98 			fix.smem_start = 0;
99 		unlock_fb_info(info);
100 
101 		ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;
102 		break;
103 	case FBIOPUTCMAP:
104 		if (copy_from_user(&cmap, argp, sizeof(cmap)))
105 			return -EFAULT;
106 		ret = fb_set_user_cmap(&cmap, info);
107 		break;
108 	case FBIOGETCMAP:
109 		if (copy_from_user(&cmap, argp, sizeof(cmap)))
110 			return -EFAULT;
111 		lock_fb_info(info);
112 		cmap_from = info->cmap;
113 		unlock_fb_info(info);
114 		ret = fb_cmap_to_user(&cmap_from, &cmap);
115 		break;
116 	case FBIOPAN_DISPLAY:
117 		if (copy_from_user(&var, argp, sizeof(var)))
118 			return -EFAULT;
119 		console_lock();
120 		lock_fb_info(info);
121 		ret = fb_pan_display(info, &var);
122 		unlock_fb_info(info);
123 		console_unlock();
124 		if (ret == 0 && copy_to_user(argp, &var, sizeof(var)))
125 			return -EFAULT;
126 		break;
127 	case FBIO_CURSOR:
128 		ret = -EINVAL;
129 		break;
130 	case FBIOGET_CON2FBMAP:
131 		ret = fbcon_get_con2fb_map_ioctl(argp);
132 		break;
133 	case FBIOPUT_CON2FBMAP:
134 		ret = fbcon_set_con2fb_map_ioctl(argp);
135 		break;
136 	case FBIOBLANK:
137 		if (arg > FB_BLANK_POWERDOWN)
138 			return -EINVAL;
139 		console_lock();
140 		lock_fb_info(info);
141 		ret = fb_blank_from_user(info, arg);
142 		unlock_fb_info(info);
143 		console_unlock();
144 		break;
145 	default:
146 		lock_fb_info(info);
147 		fb = info->fbops;
148 		if (fb->fb_ioctl)
149 			ret = fb->fb_ioctl(info, cmd, arg);
150 		else
151 			ret = -ENOTTY;
152 		unlock_fb_info(info);
153 	}
154 	return ret;
155 }
156 
157 static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
158 {
159 	struct fb_info *info = file_fb_info(file);
160 
161 	if (!info)
162 		return -ENODEV;
163 	return do_fb_ioctl(info, cmd, arg);
164 }
165 
166 #ifdef CONFIG_COMPAT
167 struct fb_fix_screeninfo32 {
168 	char			id[16];
169 	compat_caddr_t		smem_start;
170 	u32			smem_len;
171 	u32			type;
172 	u32			type_aux;
173 	u32			visual;
174 	u16			xpanstep;
175 	u16			ypanstep;
176 	u16			ywrapstep;
177 	u32			line_length;
178 	compat_caddr_t		mmio_start;
179 	u32			mmio_len;
180 	u32			accel;
181 	u16			reserved[3];
182 };
183 
184 struct fb_cmap32 {
185 	u32			start;
186 	u32			len;
187 	compat_caddr_t	red;
188 	compat_caddr_t	green;
189 	compat_caddr_t	blue;
190 	compat_caddr_t	transp;
191 };
192 
193 static int fb_getput_cmap(struct fb_info *info, unsigned int cmd,
194 			  unsigned long arg)
195 {
196 	struct fb_cmap32 cmap32;
197 	struct fb_cmap cmap_from;
198 	struct fb_cmap_user cmap;
199 
200 	if (copy_from_user(&cmap32, compat_ptr(arg), sizeof(cmap32)))
201 		return -EFAULT;
202 
203 	cmap = (struct fb_cmap_user) {
204 		.start	= cmap32.start,
205 		.len	= cmap32.len,
206 		.red	= compat_ptr(cmap32.red),
207 		.green	= compat_ptr(cmap32.green),
208 		.blue	= compat_ptr(cmap32.blue),
209 		.transp	= compat_ptr(cmap32.transp),
210 	};
211 
212 	if (cmd == FBIOPUTCMAP)
213 		return fb_set_user_cmap(&cmap, info);
214 
215 	lock_fb_info(info);
216 	cmap_from = info->cmap;
217 	unlock_fb_info(info);
218 
219 	return fb_cmap_to_user(&cmap_from, &cmap);
220 }
221 
222 static int do_fscreeninfo_to_user(struct fb_fix_screeninfo *fix,
223 				  struct fb_fix_screeninfo32 __user *fix32)
224 {
225 	__u32 data;
226 	int err;
227 
228 	err = copy_to_user(&fix32->id, &fix->id, sizeof(fix32->id));
229 
230 	data = (__u32) (unsigned long) fix->smem_start;
231 	err |= put_user(data, &fix32->smem_start);
232 
233 	err |= put_user(fix->smem_len, &fix32->smem_len);
234 	err |= put_user(fix->type, &fix32->type);
235 	err |= put_user(fix->type_aux, &fix32->type_aux);
236 	err |= put_user(fix->visual, &fix32->visual);
237 	err |= put_user(fix->xpanstep, &fix32->xpanstep);
238 	err |= put_user(fix->ypanstep, &fix32->ypanstep);
239 	err |= put_user(fix->ywrapstep, &fix32->ywrapstep);
240 	err |= put_user(fix->line_length, &fix32->line_length);
241 
242 	data = (__u32) (unsigned long) fix->mmio_start;
243 	err |= put_user(data, &fix32->mmio_start);
244 
245 	err |= put_user(fix->mmio_len, &fix32->mmio_len);
246 	err |= put_user(fix->accel, &fix32->accel);
247 	err |= copy_to_user(fix32->reserved, fix->reserved,
248 			    sizeof(fix->reserved));
249 
250 	if (err)
251 		return -EFAULT;
252 	return 0;
253 }
254 
255 static int fb_get_fscreeninfo(struct fb_info *info, unsigned int cmd,
256 			      unsigned long arg)
257 {
258 	struct fb_fix_screeninfo fix;
259 
260 	lock_fb_info(info);
261 	fix = info->fix;
262 	if (info->flags & FBINFO_HIDE_SMEM_START)
263 		fix.smem_start = 0;
264 	unlock_fb_info(info);
265 	return do_fscreeninfo_to_user(&fix, compat_ptr(arg));
266 }
267 
268 static long fb_compat_ioctl(struct file *file, unsigned int cmd,
269 			    unsigned long arg)
270 {
271 	struct fb_info *info = file_fb_info(file);
272 	const struct fb_ops *fb;
273 	long ret = -ENOIOCTLCMD;
274 
275 	if (!info)
276 		return -ENODEV;
277 	fb = info->fbops;
278 	switch (cmd) {
279 	case FBIOGET_VSCREENINFO:
280 	case FBIOPUT_VSCREENINFO:
281 	case FBIOPAN_DISPLAY:
282 	case FBIOGET_CON2FBMAP:
283 	case FBIOPUT_CON2FBMAP:
284 		arg = (unsigned long) compat_ptr(arg);
285 		fallthrough;
286 	case FBIOBLANK:
287 		ret = do_fb_ioctl(info, cmd, arg);
288 		break;
289 
290 	case FBIOGET_FSCREENINFO:
291 		ret = fb_get_fscreeninfo(info, cmd, arg);
292 		break;
293 
294 	case FBIOGETCMAP:
295 	case FBIOPUTCMAP:
296 		ret = fb_getput_cmap(info, cmd, arg);
297 		break;
298 
299 	default:
300 		if (fb->fb_compat_ioctl)
301 			ret = fb->fb_compat_ioctl(info, cmd, arg);
302 		break;
303 	}
304 	return ret;
305 }
306 #endif
307 
308 static int fb_mmap(struct file *file, struct vm_area_struct *vma)
309 {
310 	struct fb_info *info = file_fb_info(file);
311 	int res;
312 
313 	if (!info)
314 		return -ENODEV;
315 
316 	if (fb_WARN_ON_ONCE(info, !info->fbops->fb_mmap))
317 		return -ENODEV;
318 
319 	mutex_lock(&info->mm_lock);
320 	res = info->fbops->fb_mmap(info, vma);
321 	mutex_unlock(&info->mm_lock);
322 
323 	return res;
324 }
325 
326 static int fb_open(struct inode *inode, struct file *file)
327 __acquires(&info->lock)
328 __releases(&info->lock)
329 {
330 	int fbidx = iminor(inode);
331 	struct fb_info *info;
332 	int res = 0;
333 
334 	info = get_fb_info(fbidx);
335 	if (!info) {
336 		request_module("fb%d", fbidx);
337 		info = get_fb_info(fbidx);
338 		if (!info)
339 			return -ENODEV;
340 	}
341 	if (IS_ERR(info))
342 		return PTR_ERR(info);
343 
344 	lock_fb_info(info);
345 	if (!try_module_get(info->fbops->owner)) {
346 		res = -ENODEV;
347 		goto out;
348 	}
349 	file->private_data = info;
350 	if (info->fbops->fb_open) {
351 		res = info->fbops->fb_open(info, 1);
352 		if (res)
353 			module_put(info->fbops->owner);
354 	}
355 #ifdef CONFIG_FB_DEFERRED_IO
356 	if (info->fbdefio)
357 		fb_deferred_io_open(info, inode, file);
358 #endif
359 out:
360 	unlock_fb_info(info);
361 	if (res)
362 		put_fb_info(info);
363 	return res;
364 }
365 
366 static int fb_release(struct inode *inode, struct file *file)
367 __acquires(&info->lock)
368 __releases(&info->lock)
369 {
370 	struct fb_info * const info = file->private_data;
371 
372 	lock_fb_info(info);
373 #if IS_ENABLED(CONFIG_FB_DEFERRED_IO)
374 	if (info->fbdefio)
375 		fb_deferred_io_release(info);
376 #endif
377 	if (info->fbops->fb_release)
378 		info->fbops->fb_release(info, 1);
379 	module_put(info->fbops->owner);
380 	unlock_fb_info(info);
381 	put_fb_info(info);
382 	return 0;
383 }
384 
385 #if defined(CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA) && !defined(CONFIG_MMU)
386 static unsigned long get_fb_unmapped_area(struct file *filp,
387 				   unsigned long addr, unsigned long len,
388 				   unsigned long pgoff, unsigned long flags)
389 {
390 	struct fb_info * const info = filp->private_data;
391 	unsigned long fb_size = PAGE_ALIGN(info->fix.smem_len);
392 
393 	if (pgoff > fb_size || len > fb_size - pgoff)
394 		return -EINVAL;
395 
396 	return (unsigned long)info->screen_base + pgoff;
397 }
398 #endif
399 
400 static const struct file_operations fb_fops = {
401 	.owner = THIS_MODULE,
402 	.read = fb_read,
403 	.write = fb_write,
404 	.unlocked_ioctl = fb_ioctl,
405 #ifdef CONFIG_COMPAT
406 	.compat_ioctl = fb_compat_ioctl,
407 #endif
408 	.mmap = fb_mmap,
409 	.open = fb_open,
410 	.release = fb_release,
411 #if defined(HAVE_ARCH_FB_UNMAPPED_AREA) || \
412 	(defined(CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA) && \
413 	 !defined(CONFIG_MMU))
414 	.get_unmapped_area = get_fb_unmapped_area,
415 #endif
416 #ifdef CONFIG_FB_DEFERRED_IO
417 	.fsync = fb_deferred_io_fsync,
418 #endif
419 	.llseek = default_llseek,
420 };
421 
422 int fb_register_chrdev(void)
423 {
424 	int ret;
425 
426 	ret = register_chrdev(FB_MAJOR, "fb", &fb_fops);
427 	if (ret) {
428 		pr_err("Unable to get major %d for fb devs\n", FB_MAJOR);
429 		return ret;
430 	}
431 
432 	return ret;
433 }
434 
435 void fb_unregister_chrdev(void)
436 {
437 	unregister_chrdev(FB_MAJOR, "fb");
438 }
439