linux/drivers/video/fbmem.c
James Simmons f5a9951c94 [PATCH] fbdev: iomove removal
Since no one is using the inbuf, outbuf of struct fb_pixmap I removed their
use in the framebuffer console.  The idea is instead move the pixmap
functionality below the accelerated functions intead of on top as the way
it is now.  If there is no objection please apply.  This is against Linus
latestr GIT tree.  Thank you.

Signed-off-by: James Simmons <jsimmons@www.infradead.org>
Cc: "Antonino A. Daplas" <adaplas@pol.net>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2005-06-21 19:07:39 -07:00

1324 lines
32 KiB
C

/*
* linux/drivers/video/fbmem.c
*
* Copyright (C) 1994 Martin Schaller
*
* 2001 - Documented with DocBook
* - Brad Douglas <brad@neruo.com>
*
* 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/config.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/smp_lock.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/mman.h>
#include <linux/tty.h>
#include <linux/init.h>
#include <linux/linux_logo.h>
#include <linux/proc_fs.h>
#include <linux/console.h>
#ifdef CONFIG_KMOD
#include <linux/kmod.h>
#endif
#include <linux/devfs_fs_kernel.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/efi.h>
#if defined(__mc68000__) || defined(CONFIG_APUS)
#include <asm/setup.h>
#endif
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/page.h>
#include <asm/pgtable.h>
#include <linux/fb.h>
/*
* Frame buffer device initialization and setup routines
*/
#define FBPIXMAPSIZE (1024 * 8)
static struct notifier_block *fb_notifier_list;
struct fb_info *registered_fb[FB_MAX];
int num_registered_fb;
/*
* Helpers
*/
int fb_get_color_depth(struct fb_var_screeninfo *var)
{
if (var->green.length == var->blue.length &&
var->green.length == var->red.length &&
!var->green.offset && !var->blue.offset &&
!var->red.offset)
return var->green.length;
else
return (var->green.length + var->red.length +
var->blue.length);
}
EXPORT_SYMBOL(fb_get_color_depth);
/*
* Data padding functions.
*/
void fb_sysmove_buf_aligned(struct fb_info *info, struct fb_pixmap *buf,
u8 *dst, u32 d_pitch, u8 *src, u32 s_pitch,
u32 height)
{
int i;
for (i = height; i--; ) {
memcpy(dst, src, s_pitch);
src += s_pitch;
dst += d_pitch;
}
}
EXPORT_SYMBOL(fb_sysmove_buf_aligned);
void fb_sysmove_buf_unaligned(struct fb_info *info, struct fb_pixmap *buf,
u8 *dst, u32 d_pitch, u8 *src, u32 idx,
u32 height, u32 shift_high, u32 shift_low,
u32 mod)
{
u8 mask = (u8) (0xfff << shift_high), tmp;
int i, j;
for (i = height; i--; ) {
for (j = 0; j < idx; j++) {
tmp = dst[j];
tmp &= mask;
tmp |= *src >> shift_low;
dst[j] = tmp;
tmp = *src << shift_high;
dst[j+1] = tmp;
src++;
}
tmp = dst[idx];
tmp &= mask;
tmp |= *src >> shift_low;
dst[idx] = tmp;
if (shift_high < mod) {
tmp = *src << shift_high;
dst[idx+1] = tmp;
}
src++;
dst += d_pitch;
}
}
EXPORT_SYMBOL(fb_sysmove_buf_unaligned);
/*
* we need to lock this section since fb_cursor
* may use fb_imageblit()
*/
char* fb_get_buffer_offset(struct fb_info *info, struct fb_pixmap *buf, u32 size)
{
u32 align = buf->buf_align - 1, offset;
char *addr = buf->addr;
/* If IO mapped, we need to sync before access, no sharing of
* the pixmap is done
*/
if (buf->flags & FB_PIXMAP_IO) {
if (info->fbops->fb_sync && (buf->flags & FB_PIXMAP_SYNC))
info->fbops->fb_sync(info);
return addr;
}
/* See if we fit in the remaining pixmap space */
offset = buf->offset + align;
offset &= ~align;
if (offset + size > buf->size) {
/* We do not fit. In order to be able to re-use the buffer,
* we must ensure no asynchronous DMA'ing or whatever operation
* is in progress, we sync for that.
*/
if (info->fbops->fb_sync && (buf->flags & FB_PIXMAP_SYNC))
info->fbops->fb_sync(info);
offset = 0;
}
buf->offset = offset + size;
addr += offset;
return addr;
}
#ifdef CONFIG_LOGO
#include <linux/linux_logo.h>
static inline unsigned safe_shift(unsigned d, int n)
{
return n < 0 ? d >> -n : d << n;
}
static void fb_set_logocmap(struct fb_info *info,
const struct linux_logo *logo)
{
struct fb_cmap palette_cmap;
u16 palette_green[16];
u16 palette_blue[16];
u16 palette_red[16];
int i, j, n;
const unsigned char *clut = logo->clut;
palette_cmap.start = 0;
palette_cmap.len = 16;
palette_cmap.red = palette_red;
palette_cmap.green = palette_green;
palette_cmap.blue = palette_blue;
palette_cmap.transp = NULL;
for (i = 0; i < logo->clutsize; i += n) {
n = logo->clutsize - i;
/* palette_cmap provides space for only 16 colors at once */
if (n > 16)
n = 16;
palette_cmap.start = 32 + i;
palette_cmap.len = n;
for (j = 0; j < n; ++j) {
palette_cmap.red[j] = clut[0] << 8 | clut[0];
palette_cmap.green[j] = clut[1] << 8 | clut[1];
palette_cmap.blue[j] = clut[2] << 8 | clut[2];
clut += 3;
}
fb_set_cmap(&palette_cmap, info);
}
}
static void fb_set_logo_truepalette(struct fb_info *info,
const struct linux_logo *logo,
u32 *palette)
{
unsigned char mask[9] = { 0,0x80,0xc0,0xe0,0xf0,0xf8,0xfc,0xfe,0xff };
unsigned char redmask, greenmask, bluemask;
int redshift, greenshift, blueshift;
int i;
const unsigned char *clut = logo->clut;
/*
* We have to create a temporary palette since console palette is only
* 16 colors long.
*/
/* Bug: Doesn't obey msb_right ... (who needs that?) */
redmask = mask[info->var.red.length < 8 ? info->var.red.length : 8];
greenmask = mask[info->var.green.length < 8 ? info->var.green.length : 8];
bluemask = mask[info->var.blue.length < 8 ? info->var.blue.length : 8];
redshift = info->var.red.offset - (8 - info->var.red.length);
greenshift = info->var.green.offset - (8 - info->var.green.length);
blueshift = info->var.blue.offset - (8 - info->var.blue.length);
for ( i = 0; i < logo->clutsize; i++) {
palette[i+32] = (safe_shift((clut[0] & redmask), redshift) |
safe_shift((clut[1] & greenmask), greenshift) |
safe_shift((clut[2] & bluemask), blueshift));
clut += 3;
}
}
static void fb_set_logo_directpalette(struct fb_info *info,
const struct linux_logo *logo,
u32 *palette)
{
int redshift, greenshift, blueshift;
int i;
redshift = info->var.red.offset;
greenshift = info->var.green.offset;
blueshift = info->var.blue.offset;
for (i = 32; i < logo->clutsize; i++)
palette[i] = i << redshift | i << greenshift | i << blueshift;
}
static void fb_set_logo(struct fb_info *info,
const struct linux_logo *logo, u8 *dst,
int depth)
{
int i, j, k, fg = 1;
const u8 *src = logo->data;
u8 d, xor = (info->fix.visual == FB_VISUAL_MONO01) ? 0xff : 0;
if (fb_get_color_depth(&info->var) == 3)
fg = 7;
switch (depth) {
case 4:
for (i = 0; i < logo->height; i++)
for (j = 0; j < logo->width; src++) {
*dst++ = *src >> 4;
j++;
if (j < logo->width) {
*dst++ = *src & 0x0f;
j++;
}
}
break;
case 1:
for (i = 0; i < logo->height; i++) {
for (j = 0; j < logo->width; src++) {
d = *src ^ xor;
for (k = 7; k >= 0; k--) {
*dst++ = ((d >> k) & 1) ? fg : 0;
j++;
}
}
}
break;
}
}
/*
* Three (3) kinds of logo maps exist. linux_logo_clut224 (>16 colors),
* linux_logo_vga16 (16 colors) and linux_logo_mono (2 colors). Depending on
* the visual format and color depth of the framebuffer, the DAC, the
* pseudo_palette, and the logo data will be adjusted accordingly.
*
* Case 1 - linux_logo_clut224:
* Color exceeds the number of console colors (16), thus we set the hardware DAC
* using fb_set_cmap() appropriately. The "needs_cmapreset" flag will be set.
*
* For visuals that require color info from the pseudo_palette, we also construct
* one for temporary use. The "needs_directpalette" or "needs_truepalette" flags
* will be set.
*
* Case 2 - linux_logo_vga16:
* The number of colors just matches the console colors, thus there is no need
* to set the DAC or the pseudo_palette. However, the bitmap is packed, ie,
* each byte contains color information for two pixels (upper and lower nibble).
* To be consistent with fb_imageblit() usage, we therefore separate the two
* nibbles into separate bytes. The "depth" flag will be set to 4.
*
* Case 3 - linux_logo_mono:
* This is similar with Case 2. Each byte contains information for 8 pixels.
* We isolate each bit and expand each into a byte. The "depth" flag will
* be set to 1.
*/
static struct logo_data {
int depth;
int needs_directpalette;
int needs_truepalette;
int needs_cmapreset;
const struct linux_logo *logo;
} fb_logo;
int fb_prepare_logo(struct fb_info *info)
{
int depth = fb_get_color_depth(&info->var);
memset(&fb_logo, 0, sizeof(struct logo_data));
if (info->flags & FBINFO_MISC_TILEBLITTING)
return 0;
if (info->fix.visual == FB_VISUAL_DIRECTCOLOR) {
depth = info->var.blue.length;
if (info->var.red.length < depth)
depth = info->var.red.length;
if (info->var.green.length < depth)
depth = info->var.green.length;
}
if (depth >= 8) {
switch (info->fix.visual) {
case FB_VISUAL_TRUECOLOR:
fb_logo.needs_truepalette = 1;
break;
case FB_VISUAL_DIRECTCOLOR:
fb_logo.needs_directpalette = 1;
fb_logo.needs_cmapreset = 1;
break;
case FB_VISUAL_PSEUDOCOLOR:
fb_logo.needs_cmapreset = 1;
break;
}
}
/* Return if no suitable logo was found */
fb_logo.logo = fb_find_logo(depth);
if (!fb_logo.logo || fb_logo.logo->height > info->var.yres) {
fb_logo.logo = NULL;
return 0;
}
/* What depth we asked for might be different from what we get */
if (fb_logo.logo->type == LINUX_LOGO_CLUT224)
fb_logo.depth = 8;
else if (fb_logo.logo->type == LINUX_LOGO_VGA16)
fb_logo.depth = 4;
else
fb_logo.depth = 1;
return fb_logo.logo->height;
}
int fb_show_logo(struct fb_info *info)
{
u32 *palette = NULL, *saved_pseudo_palette = NULL;
unsigned char *logo_new = NULL;
struct fb_image image;
int x;
/* Return if the frame buffer is not mapped or suspended */
if (fb_logo.logo == NULL || info->state != FBINFO_STATE_RUNNING)
return 0;
image.depth = 8;
image.data = fb_logo.logo->data;
if (fb_logo.needs_cmapreset)
fb_set_logocmap(info, fb_logo.logo);
if (fb_logo.needs_truepalette ||
fb_logo.needs_directpalette) {
palette = kmalloc(256 * 4, GFP_KERNEL);
if (palette == NULL)
return 0;
if (fb_logo.needs_truepalette)
fb_set_logo_truepalette(info, fb_logo.logo, palette);
else
fb_set_logo_directpalette(info, fb_logo.logo, palette);
saved_pseudo_palette = info->pseudo_palette;
info->pseudo_palette = palette;
}
if (fb_logo.depth <= 4) {
logo_new = kmalloc(fb_logo.logo->width * fb_logo.logo->height,
GFP_KERNEL);
if (logo_new == NULL) {
kfree(palette);
if (saved_pseudo_palette)
info->pseudo_palette = saved_pseudo_palette;
return 0;
}
image.data = logo_new;
fb_set_logo(info, fb_logo.logo, logo_new, fb_logo.depth);
}
image.width = fb_logo.logo->width;
image.height = fb_logo.logo->height;
image.dy = 0;
for (x = 0; x < num_online_cpus() * (fb_logo.logo->width + 8) &&
x <= info->var.xres-fb_logo.logo->width; x += (fb_logo.logo->width + 8)) {
image.dx = x;
info->fbops->fb_imageblit(info, &image);
}
kfree(palette);
if (saved_pseudo_palette != NULL)
info->pseudo_palette = saved_pseudo_palette;
kfree(logo_new);
return fb_logo.logo->height;
}
#else
int fb_prepare_logo(struct fb_info *info) { return 0; }
int fb_show_logo(struct fb_info *info) { return 0; }
#endif /* CONFIG_LOGO */
static int fbmem_read_proc(char *buf, char **start, off_t offset,
int len, int *eof, void *private)
{
struct fb_info **fi;
int clen;
clen = 0;
for (fi = registered_fb; fi < &registered_fb[FB_MAX] && len < 4000; fi++)
if (*fi)
clen += sprintf(buf + clen, "%d %s\n",
(*fi)->node,
(*fi)->fix.id);
*start = buf + offset;
if (clen > offset)
clen -= offset;
else
clen = 0;
return clen < len ? clen : len;
}
static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
struct inode *inode = file->f_dentry->d_inode;
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
u32 *buffer, *dst;
u32 __iomem *src;
int c, i, cnt = 0, err = 0;
unsigned long total_size;
if (!info || ! info->screen_base)
return -ENODEV;
if (info->state != FBINFO_STATE_RUNNING)
return -EPERM;
if (info->fbops->fb_read)
return info->fbops->fb_read(file, buf, count, ppos);
total_size = info->screen_size;
if (total_size == 0)
total_size = info->fix.smem_len;
if (p >= total_size)
return 0;
if (count >= total_size)
count = total_size;
if (count + p > total_size)
count = total_size - p;
cnt = 0;
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
GFP_KERNEL);
if (!buffer)
return -ENOMEM;
src = (u32 __iomem *) (info->screen_base + p);
if (info->fbops->fb_sync)
info->fbops->fb_sync(info);
while (count) {
c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
dst = buffer;
for (i = c >> 2; i--; )
*dst++ = fb_readl(src++);
if (c & 3) {
u8 *dst8 = (u8 *) dst;
u8 __iomem *src8 = (u8 __iomem *) src;
for (i = c & 3; i--;)
*dst8++ = fb_readb(src8++);
src = (u32 __iomem *) src8;
}
if (copy_to_user(buf, buffer, c)) {
err = -EFAULT;
break;
}
*ppos += c;
buf += c;
cnt += c;
count -= c;
}
kfree(buffer);
return (err) ? err : cnt;
}
static ssize_t
fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
struct inode *inode = file->f_dentry->d_inode;
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
u32 *buffer, *src;
u32 __iomem *dst;
int c, i, cnt = 0, err;
unsigned long total_size;
if (!info || !info->screen_base)
return -ENODEV;
if (info->state != FBINFO_STATE_RUNNING)
return -EPERM;
if (info->fbops->fb_write)
return info->fbops->fb_write(file, buf, count, ppos);
total_size = info->screen_size;
if (total_size == 0)
total_size = info->fix.smem_len;
if (p > total_size)
return -ENOSPC;
if (count >= total_size)
count = total_size;
err = 0;
if (count + p > total_size) {
count = total_size - p;
err = -ENOSPC;
}
cnt = 0;
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
GFP_KERNEL);
if (!buffer)
return -ENOMEM;
dst = (u32 __iomem *) (info->screen_base + p);
if (info->fbops->fb_sync)
info->fbops->fb_sync(info);
while (count) {
c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
src = buffer;
if (copy_from_user(src, buf, c)) {
err = -EFAULT;
break;
}
for (i = c >> 2; i--; )
fb_writel(*src++, dst++);
if (c & 3) {
u8 *src8 = (u8 *) src;
u8 __iomem *dst8 = (u8 __iomem *) dst;
for (i = c & 3; i--; )
fb_writeb(*src8++, dst8++);
dst = (u32 __iomem *) dst8;
}
*ppos += c;
buf += c;
cnt += c;
count -= c;
}
kfree(buffer);
return (err) ? err : cnt;
}
#ifdef CONFIG_KMOD
static void try_to_load(int fb)
{
request_module("fb%d", fb);
}
#endif /* CONFIG_KMOD */
int
fb_pan_display(struct fb_info *info, struct fb_var_screeninfo *var)
{
int xoffset = var->xoffset;
int yoffset = var->yoffset;
int err;
if (xoffset < 0 || yoffset < 0 || !info->fbops->fb_pan_display ||
xoffset + info->var.xres > info->var.xres_virtual ||
yoffset + info->var.yres > info->var.yres_virtual)
return -EINVAL;
if ((err = info->fbops->fb_pan_display(var, info)))
return err;
info->var.xoffset = var->xoffset;
info->var.yoffset = var->yoffset;
if (var->vmode & FB_VMODE_YWRAP)
info->var.vmode |= FB_VMODE_YWRAP;
else
info->var.vmode &= ~FB_VMODE_YWRAP;
return 0;
}
int
fb_set_var(struct fb_info *info, struct fb_var_screeninfo *var)
{
int err;
if (var->activate & FB_ACTIVATE_INV_MODE) {
struct fb_videomode mode1, mode2;
int ret = 0;
fb_var_to_videomode(&mode1, var);
fb_var_to_videomode(&mode2, &info->var);
/* make sure we don't delete the videomode of current var */
ret = fb_mode_is_equal(&mode1, &mode2);
if (!ret) {
struct fb_event event;
event.info = info;
event.data = &mode1;
ret = notifier_call_chain(&fb_notifier_list,
FB_EVENT_MODE_DELETE, &event);
}
if (!ret)
fb_delete_videomode(&mode1, &info->modelist);
return ret;
}
if ((var->activate & FB_ACTIVATE_FORCE) ||
memcmp(&info->var, var, sizeof(struct fb_var_screeninfo))) {
if (!info->fbops->fb_check_var) {
*var = info->var;
return 0;
}
if ((err = info->fbops->fb_check_var(var, info)))
return err;
if ((var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) {
struct fb_videomode mode;
int err = 0;
info->var = *var;
if (info->fbops->fb_set_par)
info->fbops->fb_set_par(info);
fb_pan_display(info, &info->var);
fb_set_cmap(&info->cmap, info);
fb_var_to_videomode(&mode, &info->var);
if (info->modelist.prev && info->modelist.next &&
!list_empty(&info->modelist))
err = fb_add_videomode(&mode, &info->modelist);
if (!err && info->flags & FBINFO_MISC_USEREVENT) {
struct fb_event event;
info->flags &= ~FBINFO_MISC_USEREVENT;
event.info = info;
notifier_call_chain(&fb_notifier_list,
FB_EVENT_MODE_CHANGE,
&event);
}
}
}
return 0;
}
int
fb_blank(struct fb_info *info, int blank)
{
int ret = -EINVAL;
if (blank > FB_BLANK_POWERDOWN)
blank = FB_BLANK_POWERDOWN;
if (info->fbops->fb_blank)
ret = info->fbops->fb_blank(blank, info);
if (!ret) {
struct fb_event event;
event.info = info;
event.data = &blank;
notifier_call_chain(&fb_notifier_list, FB_EVENT_BLANK, &event);
}
return ret;
}
static int
fb_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
struct fb_ops *fb = info->fbops;
struct fb_var_screeninfo var;
struct fb_fix_screeninfo fix;
struct fb_con2fbmap con2fb;
struct fb_cmap_user cmap;
struct fb_event event;
void __user *argp = (void __user *)arg;
int i;
if (!fb)
return -ENODEV;
switch (cmd) {
case FBIOGET_VSCREENINFO:
return copy_to_user(argp, &info->var,
sizeof(var)) ? -EFAULT : 0;
case FBIOPUT_VSCREENINFO:
if (copy_from_user(&var, argp, sizeof(var)))
return -EFAULT;
acquire_console_sem();
info->flags |= FBINFO_MISC_USEREVENT;
i = fb_set_var(info, &var);
info->flags &= ~FBINFO_MISC_USEREVENT;
release_console_sem();
if (i) return i;
if (copy_to_user(argp, &var, sizeof(var)))
return -EFAULT;
return 0;
case FBIOGET_FSCREENINFO:
return copy_to_user(argp, &info->fix,
sizeof(fix)) ? -EFAULT : 0;
case FBIOPUTCMAP:
if (copy_from_user(&cmap, argp, sizeof(cmap)))
return -EFAULT;
return (fb_set_user_cmap(&cmap, info));
case FBIOGETCMAP:
if (copy_from_user(&cmap, argp, sizeof(cmap)))
return -EFAULT;
return fb_cmap_to_user(&info->cmap, &cmap);
case FBIOPAN_DISPLAY:
if (copy_from_user(&var, argp, sizeof(var)))
return -EFAULT;
acquire_console_sem();
i = fb_pan_display(info, &var);
release_console_sem();
if (i)
return i;
if (copy_to_user(argp, &var, sizeof(var)))
return -EFAULT;
return 0;
case FBIO_CURSOR:
return -EINVAL;
case FBIOGET_CON2FBMAP:
if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
return -EFAULT;
if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
return -EINVAL;
con2fb.framebuffer = -1;
event.info = info;
event.data = &con2fb;
notifier_call_chain(&fb_notifier_list,
FB_EVENT_GET_CONSOLE_MAP, &event);
return copy_to_user(argp, &con2fb,
sizeof(con2fb)) ? -EFAULT : 0;
case FBIOPUT_CON2FBMAP:
if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
return - EFAULT;
if (con2fb.console < 0 || con2fb.console > MAX_NR_CONSOLES)
return -EINVAL;
if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX)
return -EINVAL;
#ifdef CONFIG_KMOD
if (!registered_fb[con2fb.framebuffer])
try_to_load(con2fb.framebuffer);
#endif /* CONFIG_KMOD */
if (!registered_fb[con2fb.framebuffer])
return -EINVAL;
event.info = info;
event.data = &con2fb;
return notifier_call_chain(&fb_notifier_list,
FB_EVENT_SET_CONSOLE_MAP,
&event);
case FBIOBLANK:
acquire_console_sem();
info->flags |= FBINFO_MISC_USEREVENT;
i = fb_blank(info, arg);
info->flags &= ~FBINFO_MISC_USEREVENT;
release_console_sem();
return i;
default:
if (fb->fb_ioctl == NULL)
return -EINVAL;
return fb->fb_ioctl(inode, file, cmd, arg, info);
}
}
#ifdef CONFIG_COMPAT
static long
fb_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int fbidx = iminor(file->f_dentry->d_inode);
struct fb_info *info = registered_fb[fbidx];
struct fb_ops *fb = info->fbops;
long ret;
if (fb->fb_compat_ioctl == NULL)
return -ENOIOCTLCMD;
lock_kernel();
ret = fb->fb_compat_ioctl(file, cmd, arg, info);
unlock_kernel();
return ret;
}
#endif
static int
fb_mmap(struct file *file, struct vm_area_struct * vma)
{
int fbidx = iminor(file->f_dentry->d_inode);
struct fb_info *info = registered_fb[fbidx];
struct fb_ops *fb = info->fbops;
unsigned long off;
#if !defined(__sparc__) || defined(__sparc_v9__)
unsigned long start;
u32 len;
#endif
if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
return -EINVAL;
off = vma->vm_pgoff << PAGE_SHIFT;
if (!fb)
return -ENODEV;
if (fb->fb_mmap) {
int res;
lock_kernel();
res = fb->fb_mmap(info, file, vma);
unlock_kernel();
return res;
}
#if defined(__sparc__) && !defined(__sparc_v9__)
/* Should never get here, all fb drivers should have their own
mmap routines */
return -EINVAL;
#else
/* !sparc32... */
lock_kernel();
/* frame buffer memory */
start = info->fix.smem_start;
len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);
if (off >= len) {
/* memory mapped io */
off -= len;
if (info->var.accel_flags) {
unlock_kernel();
return -EINVAL;
}
start = info->fix.mmio_start;
len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len);
}
unlock_kernel();
start &= PAGE_MASK;
if ((vma->vm_end - vma->vm_start + off) > len)
return -EINVAL;
off += start;
vma->vm_pgoff = off >> PAGE_SHIFT;
/* This is an IO map - tell maydump to skip this VMA */
vma->vm_flags |= VM_IO | VM_RESERVED;
#if defined(__sparc_v9__)
if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
vma->vm_end - vma->vm_start, vma->vm_page_prot))
return -EAGAIN;
#else
#if defined(__mc68000__)
#if defined(CONFIG_SUN3)
pgprot_val(vma->vm_page_prot) |= SUN3_PAGE_NOCACHE;
#elif defined(CONFIG_MMU)
if (CPU_IS_020_OR_030)
pgprot_val(vma->vm_page_prot) |= _PAGE_NOCACHE030;
if (CPU_IS_040_OR_060) {
pgprot_val(vma->vm_page_prot) &= _CACHEMASK040;
/* Use no-cache mode, serialized */
pgprot_val(vma->vm_page_prot) |= _PAGE_NOCACHE_S;
}
#endif
#elif defined(__powerpc__)
vma->vm_page_prot = phys_mem_access_prot(file, off,
vma->vm_end - vma->vm_start,
vma->vm_page_prot);
#elif defined(__alpha__)
/* Caching is off in the I/O space quadrant by design. */
#elif defined(__i386__) || defined(__x86_64__)
if (boot_cpu_data.x86 > 3)
pgprot_val(vma->vm_page_prot) |= _PAGE_PCD;
#elif defined(__mips__)
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
#elif defined(__hppa__)
pgprot_val(vma->vm_page_prot) |= _PAGE_NO_CACHE;
#elif defined(__arm__) || defined(__sh__) || defined(__m32r__)
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
#elif defined(__ia64__)
if (efi_range_is_wc(vma->vm_start, vma->vm_end - vma->vm_start))
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
else
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
#else
#warning What do we have to do here??
#endif
if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
vma->vm_end - vma->vm_start, vma->vm_page_prot))
return -EAGAIN;
#endif /* !__sparc_v9__ */
return 0;
#endif /* !sparc32 */
}
static int
fb_open(struct inode *inode, struct file *file)
{
int fbidx = iminor(inode);
struct fb_info *info;
int res = 0;
if (fbidx >= FB_MAX)
return -ENODEV;
#ifdef CONFIG_KMOD
if (!(info = registered_fb[fbidx]))
try_to_load(fbidx);
#endif /* CONFIG_KMOD */
if (!(info = registered_fb[fbidx]))
return -ENODEV;
if (!try_module_get(info->fbops->owner))
return -ENODEV;
if (info->fbops->fb_open) {
res = info->fbops->fb_open(info,1);
if (res)
module_put(info->fbops->owner);
}
return res;
}
static int
fb_release(struct inode *inode, struct file *file)
{
int fbidx = iminor(inode);
struct fb_info *info;
lock_kernel();
info = registered_fb[fbidx];
if (info->fbops->fb_release)
info->fbops->fb_release(info,1);
module_put(info->fbops->owner);
unlock_kernel();
return 0;
}
static struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = fb_compat_ioctl,
#endif
.mmap = fb_mmap,
.open = fb_open,
.release = fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
.get_unmapped_area = get_fb_unmapped_area,
#endif
};
static struct class *fb_class;
/**
* register_framebuffer - registers a frame buffer device
* @fb_info: frame buffer info structure
*
* Registers a frame buffer device @fb_info.
*
* Returns negative errno on error, or zero for success.
*
*/
int
register_framebuffer(struct fb_info *fb_info)
{
int i;
struct fb_event event;
if (num_registered_fb == FB_MAX)
return -ENXIO;
num_registered_fb++;
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])
break;
fb_info->node = i;
fb_info->class_device = class_device_create(fb_class, MKDEV(FB_MAJOR, i),
fb_info->device, "fb%d", i);
if (IS_ERR(fb_info->class_device)) {
/* Not fatal */
printk(KERN_WARNING "Unable to create class_device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->class_device));
fb_info->class_device = NULL;
} else
fb_init_class_device(fb_info);
if (fb_info->pixmap.addr == NULL) {
fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
if (fb_info->pixmap.addr) {
fb_info->pixmap.size = FBPIXMAPSIZE;
fb_info->pixmap.buf_align = 1;
fb_info->pixmap.scan_align = 1;
fb_info->pixmap.access_align = 4;
fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
}
}
fb_info->pixmap.offset = 0;
if (!fb_info->modelist.prev ||
!fb_info->modelist.next ||
list_empty(&fb_info->modelist)) {
struct fb_videomode mode;
INIT_LIST_HEAD(&fb_info->modelist);
fb_var_to_videomode(&mode, &fb_info->var);
fb_add_videomode(&mode, &fb_info->modelist);
}
registered_fb[i] = fb_info;
devfs_mk_cdev(MKDEV(FB_MAJOR, i),
S_IFCHR | S_IRUGO | S_IWUGO, "fb/%d", i);
event.info = fb_info;
notifier_call_chain(&fb_notifier_list,
FB_EVENT_FB_REGISTERED, &event);
return 0;
}
/**
* unregister_framebuffer - releases a frame buffer device
* @fb_info: frame buffer info structure
*
* Unregisters a frame buffer device @fb_info.
*
* Returns negative errno on error, or zero for success.
*
*/
int
unregister_framebuffer(struct fb_info *fb_info)
{
int i;
i = fb_info->node;
if (!registered_fb[i])
return -EINVAL;
devfs_remove("fb/%d", i);
if (fb_info->pixmap.addr && (fb_info->pixmap.flags & FB_PIXMAP_DEFAULT))
kfree(fb_info->pixmap.addr);
fb_destroy_modelist(&fb_info->modelist);
registered_fb[i]=NULL;
num_registered_fb--;
fb_cleanup_class_device(fb_info);
class_device_destroy(fb_class, MKDEV(FB_MAJOR, i));
return 0;
}
/**
* fb_register_client - register a client notifier
* @nb: notifier block to callback on events
*/
int fb_register_client(struct notifier_block *nb)
{
return notifier_chain_register(&fb_notifier_list, nb);
}
/**
* fb_unregister_client - unregister a client notifier
* @nb: notifier block to callback on events
*/
int fb_unregister_client(struct notifier_block *nb)
{
return notifier_chain_unregister(&fb_notifier_list, nb);
}
/**
* fb_set_suspend - low level driver signals suspend
* @info: framebuffer affected
* @state: 0 = resuming, !=0 = suspending
*
* This is meant to be used by low level drivers to
* signal suspend/resume to the core & clients.
* It must be called with the console semaphore held
*/
void fb_set_suspend(struct fb_info *info, int state)
{
struct fb_event event;
event.info = info;
if (state) {
notifier_call_chain(&fb_notifier_list, FB_EVENT_SUSPEND, &event);
info->state = FBINFO_STATE_SUSPENDED;
} else {
info->state = FBINFO_STATE_RUNNING;
notifier_call_chain(&fb_notifier_list, FB_EVENT_RESUME, &event);
}
}
/**
* fbmem_init - init frame buffer subsystem
*
* Initialize the frame buffer subsystem.
*
* NOTE: This function is _only_ to be called by drivers/char/mem.c.
*
*/
static int __init
fbmem_init(void)
{
create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);
devfs_mk_dir("fb");
if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
printk("unable to get major %d for fb devs\n", FB_MAJOR);
fb_class = class_create(THIS_MODULE, "graphics");
if (IS_ERR(fb_class)) {
printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
fb_class = NULL;
}
return 0;
}
#ifdef MODULE
module_init(fbmem_init);
static void __exit
fbmem_exit(void)
{
class_destroy(fb_class);
}
module_exit(fbmem_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Framebuffer base");
#else
subsys_initcall(fbmem_init);
#endif
int fb_new_modelist(struct fb_info *info)
{
struct fb_event event;
struct fb_var_screeninfo var = info->var;
struct list_head *pos, *n;
struct fb_modelist *modelist;
struct fb_videomode *m, mode;
int err = 1;
list_for_each_safe(pos, n, &info->modelist) {
modelist = list_entry(pos, struct fb_modelist, list);
m = &modelist->mode;
fb_videomode_to_var(&var, m);
var.activate = FB_ACTIVATE_TEST;
err = fb_set_var(info, &var);
fb_var_to_videomode(&mode, &var);
if (err || !fb_mode_is_equal(m, &mode)) {
list_del(pos);
kfree(pos);
}
}
err = 1;
if (!list_empty(&info->modelist)) {
event.info = info;
err = notifier_call_chain(&fb_notifier_list,
FB_EVENT_NEW_MODELIST,
&event);
}
return err;
}
static char *video_options[FB_MAX];
static int ofonly;
extern const char *global_mode_option;
/**
* fb_get_options - get kernel boot parameters
* @name: framebuffer name as it would appear in
* the boot parameter line
* (video=<name>:<options>)
* @option: the option will be stored here
*
* NOTE: Needed to maintain backwards compatibility
*/
int fb_get_options(char *name, char **option)
{
char *opt, *options = NULL;
int opt_len, retval = 0;
int name_len = strlen(name), i;
if (name_len && ofonly && strncmp(name, "offb", 4))
retval = 1;
if (name_len && !retval) {
for (i = 0; i < FB_MAX; i++) {
if (video_options[i] == NULL)
continue;
opt_len = strlen(video_options[i]);
if (!opt_len)
continue;
opt = video_options[i];
if (!strncmp(name, opt, name_len) &&
opt[name_len] == ':')
options = opt + name_len + 1;
}
}
if (options && !strncmp(options, "off", 3))
retval = 1;
if (option)
*option = options;
return retval;
}
/**
* video_setup - process command line options
* @options: string of options
*
* Process command line options for frame buffer subsystem.
*
* NOTE: This function is a __setup and __init function.
* It only stores the options. Drivers have to call
* fb_get_options() as necessary.
*
* Returns zero.
*
*/
static int __init video_setup(char *options)
{
int i, global = 0;
if (!options || !*options)
global = 1;
if (!global && !strncmp(options, "ofonly", 6)) {
ofonly = 1;
global = 1;
}
if (!global && !strstr(options, "fb:")) {
global_mode_option = options;
global = 1;
}
if (!global) {
for (i = 0; i < FB_MAX; i++) {
if (video_options[i] == NULL) {
video_options[i] = options;
break;
}
}
}
return 0;
}
__setup("video=", video_setup);
/*
* Visible symbols for modules
*/
EXPORT_SYMBOL(register_framebuffer);
EXPORT_SYMBOL(unregister_framebuffer);
EXPORT_SYMBOL(num_registered_fb);
EXPORT_SYMBOL(registered_fb);
EXPORT_SYMBOL(fb_prepare_logo);
EXPORT_SYMBOL(fb_show_logo);
EXPORT_SYMBOL(fb_set_var);
EXPORT_SYMBOL(fb_blank);
EXPORT_SYMBOL(fb_pan_display);
EXPORT_SYMBOL(fb_get_buffer_offset);
EXPORT_SYMBOL(fb_set_suspend);
EXPORT_SYMBOL(fb_register_client);
EXPORT_SYMBOL(fb_unregister_client);
EXPORT_SYMBOL(fb_get_options);
EXPORT_SYMBOL(fb_new_modelist);
MODULE_LICENSE("GPL");