438 lines · 9.7 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0 #include <linux/compat.h>#include <linux/console.h>#include <linux/fb.h>#include <linux/major.h> #include "fb_internal.h"#include "fbcon.h" /* * We hold a reference to the fb_info in file->private_data, * but if the current registered fb has changed, we don't * actually want to use it. * * So look up the fb_info using the inode minor number, * and just verify it against the reference we have. */static struct fb_info *file_fb_info(struct file *file){	struct inode *inode = file_inode(file);	int fbidx = iminor(inode);	struct fb_info *info = registered_fb[fbidx]; 	if (info != file->private_data)		info = NULL;	return info;} static ssize_t fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos){	struct fb_info *info = file_fb_info(file); 	if (!info)		return -ENODEV; 	if (fb_WARN_ON_ONCE(info, !info->fbops->fb_read))		return -EINVAL; 	if (info->state != FBINFO_STATE_RUNNING)		return -EPERM; 	return info->fbops->fb_read(info, buf, count, ppos);} static ssize_t fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos){	struct fb_info *info = file_fb_info(file); 	if (!info)		return -ENODEV; 	if (fb_WARN_ON_ONCE(info, !info->fbops->fb_write))		return -EINVAL; 	if (info->state != FBINFO_STATE_RUNNING)		return -EPERM; 	return info->fbops->fb_write(info, buf, count, ppos);} static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,			unsigned long arg){	const struct fb_ops *fb;	struct fb_var_screeninfo var;	struct fb_fix_screeninfo fix;	struct fb_cmap cmap_from;	struct fb_cmap_user cmap;	void __user *argp = (void __user *)arg;	long ret = 0; 	switch (cmd) {	case FBIOGET_VSCREENINFO:		lock_fb_info(info);		var = info->var;		unlock_fb_info(info); 		ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;		break;	case FBIOPUT_VSCREENINFO:		if (copy_from_user(&var, argp, sizeof(var)))			return -EFAULT;		/* only for kernel-internal use */		var.activate &= ~FB_ACTIVATE_KD_TEXT;		console_lock();		lock_fb_info(info);		ret = fb_set_var_from_user(info, &var);		unlock_fb_info(info);		console_unlock();		if (!ret && copy_to_user(argp, &var, sizeof(var)))			ret = -EFAULT;		break;	case FBIOGET_FSCREENINFO:		lock_fb_info(info);		memcpy(&fix, &info->fix, sizeof(fix));		if (info->flags & FBINFO_HIDE_SMEM_START)			fix.smem_start = 0;		unlock_fb_info(info); 		ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;		break;	case FBIOPUTCMAP:		if (copy_from_user(&cmap, argp, sizeof(cmap)))			return -EFAULT;		ret = fb_set_user_cmap(&cmap, info);		break;	case FBIOGETCMAP:		if (copy_from_user(&cmap, argp, sizeof(cmap)))			return -EFAULT;		lock_fb_info(info);		cmap_from = info->cmap;		unlock_fb_info(info);		ret = fb_cmap_to_user(&cmap_from, &cmap);		break;	case FBIOPAN_DISPLAY:		if (copy_from_user(&var, argp, sizeof(var)))			return -EFAULT;		console_lock();		lock_fb_info(info);		ret = fb_pan_display(info, &var);		unlock_fb_info(info);		console_unlock();		if (ret == 0 && copy_to_user(argp, &var, sizeof(var)))			return -EFAULT;		break;	case FBIO_CURSOR:		ret = -EINVAL;		break;	case FBIOGET_CON2FBMAP:		ret = fbcon_get_con2fb_map_ioctl(argp);		break;	case FBIOPUT_CON2FBMAP:		ret = fbcon_set_con2fb_map_ioctl(argp);		break;	case FBIOBLANK:		if (arg > FB_BLANK_POWERDOWN)			return -EINVAL;		console_lock();		lock_fb_info(info);		ret = fb_blank_from_user(info, arg);		unlock_fb_info(info);		console_unlock();		break;	default:		lock_fb_info(info);		fb = info->fbops;		if (fb->fb_ioctl)			ret = fb->fb_ioctl(info, cmd, arg);		else			ret = -ENOTTY;		unlock_fb_info(info);	}	return ret;} static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg){	struct fb_info *info = file_fb_info(file); 	if (!info)		return -ENODEV;	return do_fb_ioctl(info, cmd, arg);} #ifdef CONFIG_COMPATstruct fb_fix_screeninfo32 {	char			id[16];	compat_caddr_t		smem_start;	u32			smem_len;	u32			type;	u32			type_aux;	u32			visual;	u16			xpanstep;	u16			ypanstep;	u16			ywrapstep;	u32			line_length;	compat_caddr_t		mmio_start;	u32			mmio_len;	u32			accel;	u16			reserved[3];}; struct fb_cmap32 {	u32			start;	u32			len;	compat_caddr_t	red;	compat_caddr_t	green;	compat_caddr_t	blue;	compat_caddr_t	transp;}; static int fb_getput_cmap(struct fb_info *info, unsigned int cmd,			  unsigned long arg){	struct fb_cmap32 cmap32;	struct fb_cmap cmap_from;	struct fb_cmap_user cmap; 	if (copy_from_user(&cmap32, compat_ptr(arg), sizeof(cmap32)))		return -EFAULT; 	cmap = (struct fb_cmap_user) {		.start	= cmap32.start,		.len	= cmap32.len,		.red	= compat_ptr(cmap32.red),		.green	= compat_ptr(cmap32.green),		.blue	= compat_ptr(cmap32.blue),		.transp	= compat_ptr(cmap32.transp),	}; 	if (cmd == FBIOPUTCMAP)		return fb_set_user_cmap(&cmap, info); 	lock_fb_info(info);	cmap_from = info->cmap;	unlock_fb_info(info); 	return fb_cmap_to_user(&cmap_from, &cmap);} static int do_fscreeninfo_to_user(struct fb_fix_screeninfo *fix,				  struct fb_fix_screeninfo32 __user *fix32){	__u32 data;	int err; 	err = copy_to_user(&fix32->id, &fix->id, sizeof(fix32->id)); 	data = (__u32) (unsigned long) fix->smem_start;	err |= put_user(data, &fix32->smem_start); 	err |= put_user(fix->smem_len, &fix32->smem_len);	err |= put_user(fix->type, &fix32->type);	err |= put_user(fix->type_aux, &fix32->type_aux);	err |= put_user(fix->visual, &fix32->visual);	err |= put_user(fix->xpanstep, &fix32->xpanstep);	err |= put_user(fix->ypanstep, &fix32->ypanstep);	err |= put_user(fix->ywrapstep, &fix32->ywrapstep);	err |= put_user(fix->line_length, &fix32->line_length); 	data = (__u32) (unsigned long) fix->mmio_start;	err |= put_user(data, &fix32->mmio_start); 	err |= put_user(fix->mmio_len, &fix32->mmio_len);	err |= put_user(fix->accel, &fix32->accel);	err |= copy_to_user(fix32->reserved, fix->reserved,			    sizeof(fix->reserved)); 	if (err)		return -EFAULT;	return 0;} static int fb_get_fscreeninfo(struct fb_info *info, unsigned int cmd,			      unsigned long arg){	struct fb_fix_screeninfo fix; 	lock_fb_info(info);	fix = info->fix;	if (info->flags & FBINFO_HIDE_SMEM_START)		fix.smem_start = 0;	unlock_fb_info(info);	return do_fscreeninfo_to_user(&fix, compat_ptr(arg));} static long fb_compat_ioctl(struct file *file, unsigned int cmd,			    unsigned long arg){	struct fb_info *info = file_fb_info(file);	const struct fb_ops *fb;	long ret = -ENOIOCTLCMD; 	if (!info)		return -ENODEV;	fb = info->fbops;	switch (cmd) {	case FBIOGET_VSCREENINFO:	case FBIOPUT_VSCREENINFO:	case FBIOPAN_DISPLAY:	case FBIOGET_CON2FBMAP:	case FBIOPUT_CON2FBMAP:		arg = (unsigned long) compat_ptr(arg);		fallthrough;	case FBIOBLANK:		ret = do_fb_ioctl(info, cmd, arg);		break; 	case FBIOGET_FSCREENINFO:		ret = fb_get_fscreeninfo(info, cmd, arg);		break; 	case FBIOGETCMAP:	case FBIOPUTCMAP:		ret = fb_getput_cmap(info, cmd, arg);		break; 	default:		if (fb->fb_compat_ioctl)			ret = fb->fb_compat_ioctl(info, cmd, arg);		break;	}	return ret;}#endif static int fb_mmap(struct file *file, struct vm_area_struct *vma){	struct fb_info *info = file_fb_info(file);	int res; 	if (!info)		return -ENODEV; 	if (fb_WARN_ON_ONCE(info, !info->fbops->fb_mmap))		return -ENODEV; 	mutex_lock(&info->mm_lock);	res = info->fbops->fb_mmap(info, vma);	mutex_unlock(&info->mm_lock); 	return res;} static int fb_open(struct inode *inode, struct file *file)__acquires(&info->lock)__releases(&info->lock){	int fbidx = iminor(inode);	struct fb_info *info;	int res = 0; 	info = get_fb_info(fbidx);	if (!info) {		request_module("fb%d", fbidx);		info = get_fb_info(fbidx);		if (!info)			return -ENODEV;	}	if (IS_ERR(info))		return PTR_ERR(info); 	lock_fb_info(info);	if (!try_module_get(info->fbops->owner)) {		res = -ENODEV;		goto out;	}	file->private_data = info;	if (info->fbops->fb_open) {		res = info->fbops->fb_open(info, 1);		if (res)			module_put(info->fbops->owner);	}#ifdef CONFIG_FB_DEFERRED_IO	if (info->fbdefio)		fb_deferred_io_open(info, inode, file);#endifout:	unlock_fb_info(info);	if (res)		put_fb_info(info);	return res;} static int fb_release(struct inode *inode, struct file *file)__acquires(&info->lock)__releases(&info->lock){	struct fb_info * const info = file->private_data; 	lock_fb_info(info);#if IS_ENABLED(CONFIG_FB_DEFERRED_IO)	if (info->fbdefio)		fb_deferred_io_release(info);#endif	if (info->fbops->fb_release)		info->fbops->fb_release(info, 1);	module_put(info->fbops->owner);	unlock_fb_info(info);	put_fb_info(info);	return 0;} #if defined(CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA) && !defined(CONFIG_MMU)static unsigned long get_fb_unmapped_area(struct file *filp,				   unsigned long addr, unsigned long len,				   unsigned long pgoff, unsigned long flags){	struct fb_info * const info = filp->private_data;	unsigned long fb_size = PAGE_ALIGN(info->fix.smem_len); 	if (pgoff > fb_size || len > fb_size - pgoff)		return -EINVAL; 	return (unsigned long)info->screen_base + pgoff;}#endif static const struct file_operations fb_fops = {	.owner = THIS_MODULE,	.read = fb_read,	.write = fb_write,	.unlocked_ioctl = fb_ioctl,#ifdef CONFIG_COMPAT	.compat_ioctl = fb_compat_ioctl,#endif	.mmap = fb_mmap,	.open = fb_open,	.release = fb_release,#if defined(HAVE_ARCH_FB_UNMAPPED_AREA) || \	(defined(CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA) && \	 !defined(CONFIG_MMU))	.get_unmapped_area = get_fb_unmapped_area,#endif#ifdef CONFIG_FB_DEFERRED_IO	.fsync = fb_deferred_io_fsync,#endif	.llseek = default_llseek,}; int fb_register_chrdev(void){	int ret; 	ret = register_chrdev(FB_MAJOR, "fb", &fb_fops);	if (ret) {		pr_err("Unable to get major %d for fb devs\n", FB_MAJOR);		return ret;	} 	return ret;} void fb_unregister_chrdev(void){	unregister_chrdev(FB_MAJOR, "fb");}