projects/linux·blob master · refs

438 lines · 9.7 KiB

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 */
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 = 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
157static 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
167struct 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
184struct 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
193static 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
222static 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
255static 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
268static 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
308static 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
326static 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
359out:
360 unlock_fb_info(info);
361 if (res)
362 put_fb_info(info);
363 return res;
364}
365
366static 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)
386static 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
400static 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
422int 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
435void fb_unregister_chrdev(void)
436{
437 unregister_chrdev(FB_MAJOR, "fb");
438}