445 lines · c
1// SPDX-License-Identifier: GPL-2.02 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't14 * 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 else157 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_COMPAT173struct 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#endif313 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_IO362 if (info->fbdefio)363 fb_deferred_io_open(info, inode, file);364#endif365out: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#endif383 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#endif405 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_COMPAT412 .compat_ioctl = fb_compat_ioctl,413#endif414 .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#endif422#ifdef CONFIG_FB_DEFERRED_IO423 .fsync = fb_deferred_io_fsync,424#endif425 .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}445