satip-axe/kernel/drivers/video/console/fbcondecor.c
2015-03-26 17:24:57 +01:00

419 lines
10 KiB
C

/*
* linux/drivers/video/console/fbcondecor.c -- Framebuffer console decorations+ *
* Copyright (C) 2004 Michal Januszewski <spock@gentoo.org>
*
* Code based upon "Bootsplash" (C) 2001-2003
* Volker Poplawski <volker@poplawski.de>,
* Stefan Reinauer <stepan@suse.de>,
* Steffen Winterfeldt <snwint@suse.de>,
* Michael Schroeder <mls@suse.de>,
* Ken Wimer <wimer@suse.de>.
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive for
* more details.
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/fb.h>
#include <linux/vt_kern.h>
#include <linux/vmalloc.h>
#include <linux/unistd.h>
#include <linux/syscalls.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/workqueue.h>
#include <linux/kmod.h>
#include <linux/miscdevice.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/system.h>
#include "fbcon.h"
#include "fbcondecor.h"
extern signed char con2fb_map[];
static int fbcon_decor_enable(struct vc_data *vc);
char fbcon_decor_path[KMOD_PATH_LEN] = "/sbin/fbcondecor_helper";
static int initialized = 0;
int fbcon_decor_call_helper(char* cmd, unsigned short vc)
{
char *envp[] = {
"HOME=/",
"PATH=/sbin:/bin",
NULL
};
char tfb[5];
char tcons[5];
unsigned char fb = (int) con2fb_map[vc];
char *argv[] = {
fbcon_decor_path,
"2",
cmd,
tcons,
tfb,
vc_cons[vc].d->vc_decor.theme,
NULL
};
snprintf(tfb,5,"%d",fb);
snprintf(tcons,5,"%d",vc);
return call_usermodehelper(fbcon_decor_path, argv, envp, 1);
}
/* Disables fbcondecor on a virtual console; called with console sem held. */
int fbcon_decor_disable(struct vc_data *vc, unsigned char redraw)
{
struct fb_info* info;
if (!vc->vc_decor.state)
return -EINVAL;
info = registered_fb[(int) con2fb_map[vc->vc_num]];
if (info == NULL)
return -EINVAL;
vc->vc_decor.state = 0;
vc_resize(vc, info->var.xres / vc->vc_font.width,
info->var.yres / vc->vc_font.height);
if (fg_console == vc->vc_num && redraw) {
redraw_screen(vc, 0);
update_region(vc, vc->vc_origin +
vc->vc_size_row * vc->vc_top,
vc->vc_size_row * (vc->vc_bottom - vc->vc_top) / 2);
}
printk(KERN_INFO "fbcondecor: switched decor state to 'off' on console %d\n",
vc->vc_num);
return 0;
}
/* Enables fbcondecor on a virtual console; called with console sem held. */
static int fbcon_decor_enable(struct vc_data *vc)
{
struct fb_info* info;
info = registered_fb[(int) con2fb_map[vc->vc_num]];
if (vc->vc_decor.twidth == 0 || vc->vc_decor.theight == 0 ||
info == NULL || vc->vc_decor.state || (!info->bgdecor.data &&
vc->vc_num == fg_console))
return -EINVAL;
vc->vc_decor.state = 1;
vc_resize(vc, vc->vc_decor.twidth / vc->vc_font.width,
vc->vc_decor.theight / vc->vc_font.height);
if (fg_console == vc->vc_num) {
redraw_screen(vc, 0);
update_region(vc, vc->vc_origin +
vc->vc_size_row * vc->vc_top,
vc->vc_size_row * (vc->vc_bottom - vc->vc_top) / 2);
fbcon_decor_clear_margins(vc, info, 0);
}
printk(KERN_INFO "fbcondecor: switched decor state to 'on' on console %d\n",
vc->vc_num);
return 0;
}
static inline int fbcon_decor_ioctl_dosetstate(struct vc_data *vc, unsigned int __user* state, unsigned char origin)
{
int tmp, ret;
if (get_user(tmp, state))
return -EFAULT;
if (origin == FBCON_DECOR_IO_ORIG_USER)
acquire_console_sem();
if (!tmp)
ret = fbcon_decor_disable(vc, 1);
else
ret = fbcon_decor_enable(vc);
if (origin == FBCON_DECOR_IO_ORIG_USER)
release_console_sem();
return ret;
}
static inline int fbcon_decor_ioctl_dogetstate(struct vc_data *vc, unsigned int __user *state)
{
return put_user(vc->vc_decor.state, (unsigned int __user*) state);
}
static int fbcon_decor_ioctl_dosetcfg(struct vc_data *vc, struct vc_decor __user *arg, unsigned char origin)
{
struct vc_decor cfg;
struct fb_info *info;
int len;
char *tmp;
info = registered_fb[(int) con2fb_map[vc->vc_num]];
if (copy_from_user(&cfg, arg, sizeof(struct vc_decor)))
return -EFAULT;
if (info == NULL || !cfg.twidth || !cfg.theight ||
cfg.tx + cfg.twidth > info->var.xres ||
cfg.ty + cfg.theight > info->var.yres)
return -EINVAL;
len = strlen_user(cfg.theme);
if (!len || len > FBCON_DECOR_THEME_LEN)
return -EINVAL;
tmp = kmalloc(len, GFP_KERNEL);
if (!tmp)
return -ENOMEM;
if (copy_from_user(tmp, (void __user *)cfg.theme, len))
return -EFAULT;
cfg.theme = tmp;
cfg.state = 0;
/* If this ioctl is a response to a request from kernel, the console sem
* is already held; we also don't need to disable decor because either the
* new config and background picture will be successfully loaded, and the
* decor will stay on, or in case of a failure it'll be turned off in fbcon. */
if (origin == FBCON_DECOR_IO_ORIG_USER) {
acquire_console_sem();
if (vc->vc_decor.state)
fbcon_decor_disable(vc, 1);
}
if (vc->vc_decor.theme)
kfree(vc->vc_decor.theme);
vc->vc_decor = cfg;
if (origin == FBCON_DECOR_IO_ORIG_USER)
release_console_sem();
printk(KERN_INFO "fbcondecor: console %d using theme '%s'\n",
vc->vc_num, vc->vc_decor.theme);
return 0;
}
static int fbcon_decor_ioctl_dogetcfg(struct vc_data *vc, struct vc_decor __user *arg)
{
struct vc_decor decor;
char __user *tmp;
if (get_user(tmp, &arg->theme))
return -EFAULT;
decor = vc->vc_decor;
decor.theme = tmp;
if (vc->vc_decor.theme) {
if (copy_to_user(tmp, vc->vc_decor.theme, strlen(vc->vc_decor.theme) + 1))
return -EFAULT;
} else
if (put_user(0, tmp))
return -EFAULT;
if (copy_to_user(arg, &decor, sizeof(struct vc_decor)))
return -EFAULT;
return 0;
}
static int fbcon_decor_ioctl_dosetpic(struct vc_data *vc, struct fb_image __user *arg, unsigned char origin)
{
struct fb_image img;
struct fb_info *info;
int len;
u8 *tmp;
if (vc->vc_num != fg_console)
return -EINVAL;
info = registered_fb[(int) con2fb_map[vc->vc_num]];
if (info == NULL)
return -EINVAL;
if (copy_from_user(&img, arg, sizeof(struct fb_image)))
return -EFAULT;
if (img.width != info->var.xres || img.height != info->var.yres) {
printk(KERN_ERR "fbcondecor: picture dimensions mismatch\n");
return -EINVAL;
}
if (img.depth != info->var.bits_per_pixel) {
printk(KERN_ERR "fbcondecor: picture depth mismatch\n");
return -EINVAL;
}
if (img.depth == 8) {
if (!img.cmap.len || !img.cmap.red || !img.cmap.green ||
!img.cmap.blue)
return -EINVAL;
tmp = vmalloc(img.cmap.len * 3 * 2);
if (!tmp)
return -ENOMEM;
if (copy_from_user(tmp, (void __user*)img.cmap.red, img.cmap.len * 2) ||
copy_from_user(tmp + (img.cmap.len << 1),
(void __user*)img.cmap.green, (img.cmap.len << 1)) ||
copy_from_user(tmp + (img.cmap.len << 2),
(void __user*)img.cmap.blue, (img.cmap.len << 1))) {
vfree(tmp);
return -EFAULT;
}
img.cmap.transp = NULL;
img.cmap.red = (u16*)tmp;
img.cmap.green = img.cmap.red + img.cmap.len;
img.cmap.blue = img.cmap.green + img.cmap.len;
} else {
img.cmap.red = NULL;
}
len = ((img.depth + 7) >> 3) * img.width * img.height;
tmp = vmalloc(len);
if (!tmp)
goto out;
if (copy_from_user(tmp, (void __user*)img.data, len))
goto out;
img.data = tmp;
/* If this ioctl is a response to a request from kernel, the console sem
* is already held. */
if (origin == FBCON_DECOR_IO_ORIG_USER)
acquire_console_sem();
if (info->bgdecor.data)
vfree((u8*)info->bgdecor.data);
if (info->bgdecor.cmap.red)
vfree(info->bgdecor.cmap.red);
info->bgdecor = img;
if (origin == FBCON_DECOR_IO_ORIG_USER)
release_console_sem();
return 0;
out: if (img.cmap.red)
vfree(img.cmap.red);
if (tmp)
vfree(tmp);
return -ENOMEM;
}
static int fbcon_decor_ioctl(struct inode * inode, struct file *filp, u_int cmd,
u_long arg)
{
struct fbcon_decor_iowrapper __user *wrapper = (void __user*) arg;
struct vc_data *vc = NULL;
unsigned short vc_num = 0;
unsigned char origin = 0;
void __user *data = NULL;
if (!access_ok(VERIFY_READ, wrapper,
sizeof(struct fbcon_decor_iowrapper)))
return -EFAULT;
__get_user(vc_num, &wrapper->vc);
__get_user(origin, &wrapper->origin);
__get_user(data, &wrapper->data);
if (!vc_cons_allocated(vc_num))
return -EINVAL;
vc = vc_cons[vc_num].d;
switch (cmd) {
case FBIOCONDECOR_SETPIC:
return fbcon_decor_ioctl_dosetpic(vc, (struct fb_image __user*)data, origin);
case FBIOCONDECOR_SETCFG:
return fbcon_decor_ioctl_dosetcfg(vc, (struct vc_decor*)data, origin);
case FBIOCONDECOR_GETCFG:
return fbcon_decor_ioctl_dogetcfg(vc, (struct vc_decor*)data);
case FBIOCONDECOR_SETSTATE:
return fbcon_decor_ioctl_dosetstate(vc, (unsigned int *)data, origin);
case FBIOCONDECOR_GETSTATE:
return fbcon_decor_ioctl_dogetstate(vc, (unsigned int *)data);
default:
return -ENOIOCTLCMD;
}
}
static struct file_operations fbcon_decor_ops = {
.owner = THIS_MODULE,
.ioctl = fbcon_decor_ioctl
};
static struct miscdevice fbcon_decor_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "fbcondecor",
.fops = &fbcon_decor_ops
};
void fbcon_decor_reset(void)
{
struct fb_info *info;
struct vc_data *vc;
int i;
vc = vc_cons[0].d;
info = registered_fb[0];
for (i = 0; i < num_registered_fb; i++) {
registered_fb[i]->bgdecor.data = NULL;
registered_fb[i]->bgdecor.cmap.red = NULL;
}
for (i = 0; i < MAX_NR_CONSOLES && vc_cons[i].d; i++) {
vc_cons[i].d->vc_decor.state = vc_cons[i].d->vc_decor.twidth =
vc_cons[i].d->vc_decor.theight = 0;
vc_cons[i].d->vc_decor.theme = NULL;
}
return;
}
int fbcon_decor_init(void)
{
int i;
fbcon_decor_reset();
if (initialized)
return 0;
i = misc_register(&fbcon_decor_dev);
if (i) {
printk(KERN_ERR "fbcondecor: failed to register device\n");
return i;
}
fbcon_decor_call_helper("init", 0);
initialized = 1;
return 0;
}
int fbcon_decor_exit(void)
{
fbcon_decor_reset();
return 0;
}
EXPORT_SYMBOL(fbcon_decor_path);