/* * Frame buffer control * * (C) Copyright 2001-2003 Geert Uytterhoeven * * 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 #include #include #include #include #include #include #include #include #include "types.h" #include "fb.h" #include "util.h" #include "colormap.h" static int fb_fd = -1; struct fb_fix_screeninfo fb_fix; struct fb_var_screeninfo fb_var; struct fb_cmap fb_cmap; static unsigned long fb_start; static u32 fb_len, fb_offset; static void *fb_addr; u8 *fb; /* * Saved frame buffer device state */ static struct fb_var_screeninfo saved_var; static struct fb_fix_screeninfo saved_fix; static struct fb_cmap saved_cmap; static u16 *saved_red, *saved_green, *saved_blue, *saved_transp; static u8 *saved_fb; static void fix_validate(void); static void var_validate(void); static void var_validate_change(const struct fb_var_screeninfo *old, int error); static void cmap_validate(void); static void cmap_validate_change(const struct fb_cmap *old, int error); static void fb_dump_cmap(void); /* * Open the frame buffer device */ void fb_open(void) { Debug("fb_open()\n"); if ((fb_fd = open(Opt_Fbdev, O_RDWR)) == -1) { Fatal("open %s: %s\n", Opt_Fbdev, strerror(errno)); } } /* * Close the frame buffer device */ void fb_close(void) { Debug("fb_close()\n"); if (fb_fd != -1) { close(fb_fd); fb_fd = -1; } } /* * Get the fixed information about a frame buffer */ int fb_get_fix(void) { Debug("fb_get_fix()\n"); if (ioctl(fb_fd, FBIOGET_FSCREENINFO, &fb_fix) == -1) { Fatal("ioctl FBIOGET_FSCREENINFO: %s\n", strerror(errno)); } fix_validate(); return 1; } /* * Get the variable information about a frame buffer */ int fb_get_var(void) { Debug("fb_get_var()\n"); if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &fb_var) == -1) { Fatal("ioctl FBIOGET_VSCREENINFO: %s\n", strerror(errno)); } var_validate(); return 1; } /* * Set the variable information about a frame buffer */ int fb_set_var(void) { struct fb_var_screeninfo var = fb_var; int error; Debug("fb_set_var()\n"); error = ioctl(fb_fd, FBIOPUT_VSCREENINFO, &fb_var); var_validate_change(&var, error); if (error == -1) { Fatal("ioctl FBIOPUT_VSCREENINFO: %s\n", strerror(errno)); } return 1; } /* * Get the colormap */ int fb_get_cmap(void) { Debug("fb_get_cmap()\n"); if (ioctl(fb_fd, FBIOGETCMAP, &fb_cmap) == -1) { Fatal("ioctl FBIOGETCMAP: %s\n", strerror(errno)); } cmap_validate(); if (Opt_Debug) fb_dump_cmap(); return 1; } /* * Set the colormap */ int fb_set_cmap(void) { struct fb_cmap cmap = fb_cmap; int error; Debug("fb_set_cmap()\n"); if (Opt_Debug) fb_dump_cmap(); error = ioctl(fb_fd, FBIOPUTCMAP, &fb_cmap); cmap_validate_change(&cmap, error); if (error == -1) { Fatal("ioctl FBIOPUTCMAP: %s\n", strerror(errno)); } return 1; } /* * Pan the display */ int fb_pan(u32 xoffset, u32 yoffset) { struct fb_var_screeninfo var; int error; Debug("fb_pan(%u, %u)\n", xoffset, yoffset); fb_var.xoffset = xoffset; fb_var.yoffset = yoffset; var = fb_var; error = ioctl(fb_fd, FBIOPAN_DISPLAY, &fb_var); var_validate_change(&var, error); if (error == -1) { Fatal("ioctl FBIOPAN_DISPLAY: %s\n", strerror(errno)); } return 1; } /* * Map the frame buffer */ void fb_map(void) { long page_size; unsigned long page_mask; Debug("fb_map()\n"); page_size = sysconf(_SC_PAGESIZE); if (page_size == -1) Fatal("Cannot obtain page size: %s\n", strerror(errno)); page_mask = ~(page_size-1); fb_start = (unsigned long)fb_fix.smem_start & page_mask; fb_offset = (unsigned long)fb_fix.smem_start & ~page_mask; fb_len = (fb_offset+fb_fix.smem_len+~page_mask) & page_mask; Debug("fb_start = %lx, fb_offset = %x, fb_len = %x\n", fb_start, fb_offset, fb_len); fb_addr = mmap(NULL, fb_len, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0); if (fb_addr == MAP_FAILED) Fatal("mmap smem: %s\n", strerror(errno)); fb = fb_addr+fb_offset; } /* * Unmap the frame buffer */ void fb_unmap(void) { Debug("fb_unmap()\n"); if (munmap(fb_addr, fb_len) == -1) Fatal("munmap smem: %s\n", strerror(errno)); } /* * Save the frame buffer contents */ void fb_save(void) { Debug("fb_save()\n"); if (!(saved_fb = malloc(fb_fix.smem_len))) Fatal("malloc %d: %s\n", fb_fix.smem_len, strerror(errno)); memcpy(saved_fb, fb, fb_fix.smem_len); } /* * Restore the frame buffer contents */ void fb_restore(void) { Debug("fb_restore()\n"); memcpy(fb, saved_fb, fb_fix.smem_len); free(saved_fb); } /* * Clear the frame buffer * * We can't use memset(), because on PPC it uses dcbz, which is not * allowed on non-cacheable memory :-( */ void fb_clear(void) { u32 size = fb_fix.smem_len/sizeof(unsigned long); unsigned long *p = (unsigned long *)fb; Debug("fb_clear()\n"); while (size--) *p++ = 0; } /* * Validate the fixed information about a frame buffer */ static void fix_validate(void) { /* FIXME: check for impossible values */ } /* * Validate the variable information about a frame buffer */ static void var_validate(void) { /* FIXME: check for impossible values */ } /* * Validate a change of the variable information about a frame buffer */ #define CHECK_CHANGE(x) \ do { \ if (fb_var.x != old->x) { \ if (error == -1) \ Error(#x " changed from %u to %u\n", old->x, fb_var.x); \ else \ Warning(#x " changed from %u to %u\n", old->x, fb_var.x);\ } \ } while (0) #define CHECK_ROUNDING(x) \ do { \ if (fb_var.x < old->x) \ Error(#x " was rounded down\n"); \ } while (0) #define CHECK_CHANGE_AND_ROUNDING(x) \ do { \ CHECK_CHANGE(x); \ if (error != -1) \ CHECK_ROUNDING(x); \ } while (0) static void var_validate_change(const struct fb_var_screeninfo *old, int error) { unsigned int i; CHECK_CHANGE_AND_ROUNDING(xres); CHECK_CHANGE_AND_ROUNDING(yres); CHECK_CHANGE_AND_ROUNDING(xres_virtual); CHECK_CHANGE_AND_ROUNDING(yres_virtual); CHECK_CHANGE_AND_ROUNDING(xoffset); CHECK_CHANGE_AND_ROUNDING(yoffset); CHECK_CHANGE_AND_ROUNDING(bits_per_pixel); CHECK_CHANGE(grayscale); CHECK_CHANGE_AND_ROUNDING(red.offset); CHECK_CHANGE_AND_ROUNDING(red.length); CHECK_CHANGE(red.msb_right); CHECK_CHANGE_AND_ROUNDING(green.offset); CHECK_CHANGE_AND_ROUNDING(green.length); CHECK_CHANGE_AND_ROUNDING(green.length); CHECK_CHANGE(green.msb_right); CHECK_CHANGE_AND_ROUNDING(blue.offset); CHECK_CHANGE_AND_ROUNDING(blue.length); CHECK_CHANGE_AND_ROUNDING(blue.length); CHECK_CHANGE(blue.msb_right); CHECK_CHANGE_AND_ROUNDING(transp.offset); CHECK_CHANGE_AND_ROUNDING(transp.length); CHECK_CHANGE_AND_ROUNDING(transp.length); CHECK_CHANGE(transp.msb_right); CHECK_CHANGE(nonstd); CHECK_CHANGE(activate); CHECK_CHANGE_AND_ROUNDING(height); CHECK_CHANGE_AND_ROUNDING(width); CHECK_CHANGE(accel_flags); CHECK_CHANGE_AND_ROUNDING(pixclock); CHECK_CHANGE_AND_ROUNDING(left_margin); CHECK_CHANGE_AND_ROUNDING(right_margin); CHECK_CHANGE_AND_ROUNDING(upper_margin); CHECK_CHANGE_AND_ROUNDING(lower_margin); CHECK_CHANGE_AND_ROUNDING(hsync_len); CHECK_CHANGE_AND_ROUNDING(vsync_len); CHECK_CHANGE(sync); CHECK_CHANGE(vmode); CHECK_CHANGE(rotate); for (i = 0; i < sizeof(fb_var.reserved)/sizeof(fb_var.reserved[0]); i++) if (fb_var.reserved[i] != old->reserved[i]) { if (error == -1) Error("reserved[%u] changed from %u to %u\n", i, old->reserved[i], fb_var.reserved[i]); else Warning("reserved[%u] changed from %u to %u\n", i, old->reserved[i], fb_var.reserved[i]); } var_validate(); } #undef CHECK_CHANGE #undef CHECK_ROUNDING #undef CHECK_CHANGE_AND_ROUNDING /* * Validate the colormap */ static void cmap_validate(void) { /* FIXME */ } /* * Validate a change of the colormap */ static void cmap_validate_change(const struct fb_cmap *old, int error) { /* FIXME */ cmap_validate(); } /* * Validate the variable and fixed information about a frame buffer */ static void var_fix_validate(void) { /* FIXME: check for impossible combinations */ } /* * Initialization */ #define ALLOC_AND_SAVE_COMPONENT(name) \ do { \ if (!(saved_ ## name = malloc(fb_cmap.len*sizeof(u16)))) \ Fatal("malloc %zu: %s\n", fb_cmap.len*sizeof(u16), \ strerror(errno)); \ memcpy(saved_ ## name, fb_cmap.name, fb_cmap.len*sizeof(u16)); \ } while (0) void fb_init(void) { Debug("fb_init()\n"); fb_open(); fb_get_var(); saved_var = fb_var; if (fb_var.xoffset || fb_var.yoffset || fb_var.accel_flags || fb_var.vmode & FB_VMODE_YWRAP) { fb_var.xoffset = 0; fb_var.yoffset = 0; fb_var.accel_flags = 0; fb_var.vmode &= ~FB_VMODE_YWRAP; fb_set_var(); } fb_get_fix(); var_fix_validate(); saved_fix = fb_fix; switch (fb_fix.visual) { case FB_VISUAL_MONO01: case FB_VISUAL_MONO10: case FB_VISUAL_TRUECOLOR: /* no colormap */ break; case FB_VISUAL_PSEUDOCOLOR: case FB_VISUAL_STATIC_PSEUDOCOLOR: cmap_init(1<