/* * Copyright (c) 2008 Martin Decky * Copyright (c) 2006 Ondrej Palkovsky * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @addtogroup kernel_genarch * @{ */ /** @file */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BG_COLOR 0x001620 #define FG_COLOR 0xf3cf65 #define INV_COLOR 0xaaaaaa #define FB_PAGES 8 #define RED(x, bits) (((x) >> (8 + 8 + 8 - (bits))) & ((1 << (bits)) - 1)) #define GREEN(x, bits) (((x) >> (8 + 8 - (bits))) & ((1 << (bits)) - 1)) #define BLUE(x, bits) (((x) >> (8 - (bits))) & ((1 << (bits)) - 1)) #define COL2X(col) ((col) * FONT_WIDTH) #define ROW2Y(row) ((row) * FONT_SCANLINES) #define X2COL(x) ((x) / FONT_WIDTH) #define Y2ROW(y) ((y) / FONT_SCANLINES) #define FB_POS(instance, x, y) \ ((y) * (instance)->scanline + (x) * (instance)->pixelbytes) #define BB_POS(instance, col, row) \ ((((instance)->start_row + (row)) % (instance)->rows) * \ (instance)->cols + (col)) #define BB_NEXT_COL(pos) (++(pos)) #define GLYPH_POS(instance, glyph, y) \ ((glyph) * (instance)->glyphbytes + (y) * (instance)->glyphscanline) #define TAB_WIDTH 8 typedef void (*rgb_conv_t)(void *, uint32_t); typedef struct { SPINLOCK_DECLARE(lock); parea_t parea; uint8_t *addr; uint16_t *backbuf; uint8_t *glyphs; uint8_t *bgscan; rgb_conv_t rgb_conv; unsigned int xres; unsigned int yres; /** Number of rows that fit on framebuffer */ unsigned int screen_rows; unsigned int scanline; unsigned int glyphscanline; unsigned int pixelbytes; unsigned int glyphbytes; unsigned int bgscanbytes; /** Number of columns in the backbuffer */ unsigned int cols; /** Number of rows in the backbuffer */ unsigned int rows; /** Starting row in the cyclic backbuffer */ unsigned int start_row; /** Top-most visible row (relative to start_row) */ unsigned int offset_row; /** Current backbuffer position */ unsigned int position; /** Partial character between writes */ mbstate_t mbstate; unsigned int row; unsigned int column; } fb_instance_t; static void fb_write(outdev_t *, const char *, size_t); static void fb_redraw(outdev_t *); static void fb_scroll_up(outdev_t *); static void fb_scroll_down(outdev_t *); static outdev_operations_t fbdev_ops = { .write = fb_write, .redraw = fb_redraw, .scroll_up = fb_scroll_up, .scroll_down = fb_scroll_down }; /* * RGB conversion functions. * * These functions write an RGB value to some memory in some predefined format. * The naming convention corresponds to the format created by these functions. * The functions use the so called network order (i.e. big endian) with respect * to their names. */ static void rgb_0888(void *dst, uint32_t rgb) { *((uint32_t *) dst) = host2uint32_t_be((0 << 24) | (RED(rgb, 8) << 16) | (GREEN(rgb, 8) << 8) | (BLUE(rgb, 8))); } static void bgr_0888(void *dst, uint32_t rgb) { *((uint32_t *) dst) = host2uint32_t_be((0 << 24) | (BLUE(rgb, 8) << 16) | (GREEN(rgb, 8) << 8) | (RED(rgb, 8))); } static void rgb_8880(void *dst, uint32_t rgb) { *((uint32_t *) dst) = host2uint32_t_be((RED(rgb, 8) << 24) | (GREEN(rgb, 8) << 16) | (BLUE(rgb, 8) << 8) | 0); } static void bgr_8880(void *dst, uint32_t rgb) { *((uint32_t *) dst) = host2uint32_t_be((BLUE(rgb, 8) << 24) | (GREEN(rgb, 8) << 16) | (RED(rgb, 8) << 8) | 0); } static void rgb_888(void *dst, uint32_t rgb) { ((uint8_t *) dst)[0] = RED(rgb, 8); ((uint8_t *) dst)[1] = GREEN(rgb, 8); ((uint8_t *) dst)[2] = BLUE(rgb, 8); } static void bgr_888(void *dst, uint32_t rgb) { ((uint8_t *) dst)[0] = BLUE(rgb, 8); ((uint8_t *) dst)[1] = GREEN(rgb, 8); ((uint8_t *) dst)[2] = RED(rgb, 8); } static void rgb_555_be(void *dst, uint32_t rgb) { *((uint16_t *) dst) = host2uint16_t_be(RED(rgb, 5) << 10 | GREEN(rgb, 5) << 5 | BLUE(rgb, 5)); } static void rgb_555_le(void *dst, uint32_t rgb) { *((uint16_t *) dst) = host2uint16_t_le(RED(rgb, 5) << 10 | GREEN(rgb, 5) << 5 | BLUE(rgb, 5)); } static void rgb_565_be(void *dst, uint32_t rgb) { *((uint16_t *) dst) = host2uint16_t_be(RED(rgb, 5) << 11 | GREEN(rgb, 6) << 5 | BLUE(rgb, 5)); } static void rgb_565_le(void *dst, uint32_t rgb) { *((uint16_t *) dst) = host2uint16_t_le(RED(rgb, 5) << 11 | GREEN(rgb, 6) << 5 | BLUE(rgb, 5)); } /** BGR 3:2:3 * * Even though we try 3:2:3 color scheme here, an 8-bit framebuffer * will most likely use a color palette. The color appearance * will be pretty random and depend on the default installed * palette. This could be fixed by supporting custom palette * and setting it to simulate the 8-bit truecolor. * * Currently we set the palette on the ia32, amd64, ppc32 and sparc64 port. * * Note that the byte is being inverted by this function. The reason is * that we would like to use a color palette where the white color code * is 0 and the black color code is 255, as some machines (Sun Blade 1500) * use these codes for black and white and prevent to set codes * 0 and 255 to other colors. * */ static void bgr_323(void *dst, uint32_t rgb) { *((uint8_t *) dst) = ~((RED(rgb, 3) << 5) | (GREEN(rgb, 2) << 3) | BLUE(rgb, 3)); } /** Draw character at given position * */ static void glyph_draw(fb_instance_t *instance, uint16_t glyph, unsigned int col, unsigned int row, bool overlay) { if (!overlay) instance->backbuf[BB_POS(instance, col, row)] = glyph; /* Do not output if the framebuffer is used by user space */ if ((instance->parea.mapped) && (!console_override)) return; /* Check whether the glyph should be visible */ if (row < instance->offset_row) return; unsigned int rel_row = row - instance->offset_row; if (rel_row >= instance->screen_rows) return; unsigned int x = COL2X(col); unsigned int y = ROW2Y(rel_row); for (unsigned int yd = 0; yd < FONT_SCANLINES; yd++) memcpy(&instance->addr[FB_POS(instance, x, y + yd)], &instance->glyphs[GLYPH_POS(instance, glyph, yd)], instance->glyphscanline); } /** Scroll screen down by one row * */ static void screen_scroll(fb_instance_t *instance) { if ((!instance->parea.mapped) || (console_override)) { for (unsigned int rel_row = 0; rel_row < instance->screen_rows; rel_row++) { unsigned int y = ROW2Y(rel_row); unsigned int row = rel_row + instance->offset_row; for (unsigned int yd = 0; yd < FONT_SCANLINES; yd++) { unsigned int x; unsigned int col; size_t bb_pos = BB_POS(instance, 0, row); size_t bb_pos1 = BB_POS(instance, 0, row + 1); for (col = 0, x = 0; col < instance->cols; col++, x += FONT_WIDTH) { uint16_t glyph; if (row < instance->rows - 1) { if (instance->backbuf[bb_pos] == instance->backbuf[bb_pos1]) goto skip; glyph = instance->backbuf[bb_pos1]; } else glyph = 0; memcpy(&instance->addr[FB_POS(instance, x, y + yd)], &instance->glyphs[GLYPH_POS(instance, glyph, yd)], instance->glyphscanline); skip: BB_NEXT_COL(bb_pos); BB_NEXT_COL(bb_pos1); } } } } /* * Implement backbuffer scrolling by wrapping around * the cyclic buffer. */ instance->start_row++; if (instance->start_row == instance->rows) instance->start_row = 0; memsetw(&instance->backbuf[BB_POS(instance, 0, instance->rows - 1)], instance->cols, 0); } static void cursor_put(fb_instance_t *instance) { unsigned int col = instance->column; unsigned int row = instance->row; glyph_draw(instance, fb_font_glyph(U_CURSOR), col, row, true); } static void cursor_remove(fb_instance_t *instance) { unsigned int col = instance->column; unsigned int row = instance->row; glyph_draw(instance, instance->backbuf[BB_POS(instance, col, row)], col, row, true); } /** Render glyphs * * Convert glyphs from device independent font * description to current visual representation. * */ static void glyphs_render(fb_instance_t *instance) { /* Prerender glyphs */ uint16_t glyph; for (glyph = 0; glyph < FONT_GLYPHS; glyph++) { uint32_t fg_color; if (glyph == FONT_GLYPHS - 1) fg_color = INV_COLOR; else fg_color = FG_COLOR; unsigned int y; for (y = 0; y < FONT_SCANLINES; y++) { unsigned int x; for (x = 0; x < FONT_WIDTH; x++) { void *dst = &instance->glyphs[GLYPH_POS(instance, glyph, y) + x * instance->pixelbytes]; uint32_t rgb = (fb_font[glyph][y] & (1 << (7 - x))) ? fg_color : BG_COLOR; instance->rgb_conv(dst, rgb); } } } /* Prerender background scanline */ unsigned int x; for (x = 0; x < instance->xres; x++) instance->rgb_conv(&instance->bgscan[x * instance->pixelbytes], BG_COLOR); } static void fb_redraw_internal(fb_instance_t *instance) { for (unsigned int rel_row = 0; rel_row < instance->screen_rows; rel_row++) { unsigned int y = ROW2Y(rel_row); unsigned int row = rel_row + instance->offset_row; for (unsigned int yd = 0; yd < FONT_SCANLINES; yd++) { unsigned int x; unsigned int col; size_t bb_pos = BB_POS(instance, 0, row); for (col = 0, x = 0; col < instance->cols; col++, x += FONT_WIDTH) { uint16_t glyph = instance->backbuf[bb_pos]; void *dst = &instance->addr[FB_POS(instance, x, y + yd)]; void *src = &instance->glyphs[GLYPH_POS(instance, glyph, yd)]; memcpy(dst, src, instance->glyphscanline); BB_NEXT_COL(bb_pos); } } } if (COL2X(instance->cols) < instance->xres) { unsigned int y; unsigned int size = (instance->xres - COL2X(instance->cols)) * instance->pixelbytes; for (y = 0; y < instance->yres; y++) memcpy(&instance->addr[FB_POS(instance, COL2X(instance->cols), y)], instance->bgscan, size); } if (ROW2Y(instance->screen_rows) < instance->yres) { unsigned int y; for (y = ROW2Y(instance->screen_rows); y < instance->yres; y++) memcpy(&instance->addr[FB_POS(instance, 0, y)], instance->bgscan, instance->bgscanbytes); } } static void _advance_row(fb_instance_t *instance) { instance->column = 0; instance->row++; } static void _advance_column(fb_instance_t *instance) { instance->column++; if (instance->column == instance->cols) _advance_row(instance); } /** Print character to screen * * Emulate basic terminal commands. * */ static void _putuchar(fb_instance_t *instance, char32_t ch) { switch (ch) { case '\n': _advance_row(instance); break; case '\r': instance->column = 0; break; case '\b': if (instance->column > 0) instance->column--; break; case '\t': do { glyph_draw(instance, fb_font_glyph(' '), instance->column, instance->row, false); _advance_column(instance); } while (instance->column % TAB_WIDTH != 0); break; default: glyph_draw(instance, fb_font_glyph(ch), instance->column, instance->row, false); _advance_column(instance); } while (instance->row >= instance->rows) { instance->row--; screen_scroll(instance); } } static void fb_write(outdev_t *dev, const char *s, size_t n) { fb_instance_t *instance = (fb_instance_t *) dev->data; spinlock_lock(&instance->lock); cursor_remove(instance); size_t offset = 0; char32_t ch; while ((ch = str_decode_r(s, &offset, n, U_SPECIAL, &instance->mbstate))) _putuchar(instance, ch); cursor_put(instance); spinlock_unlock(&instance->lock); } /** Scroll the framebuffer up * */ static void fb_scroll_up(outdev_t *dev) { fb_instance_t *instance = (fb_instance_t *) dev->data; spinlock_lock(&instance->lock); if (instance->offset_row >= instance->screen_rows / 2) instance->offset_row -= instance->screen_rows / 2; else instance->offset_row = 0; fb_redraw_internal(instance); cursor_put(instance); spinlock_unlock(&instance->lock); } /** Scroll the framebuffer down * */ static void fb_scroll_down(outdev_t *dev) { fb_instance_t *instance = (fb_instance_t *) dev->data; spinlock_lock(&instance->lock); if (instance->offset_row + instance->screen_rows / 2 <= instance->rows - instance->screen_rows) instance->offset_row += instance->screen_rows / 2; else instance->offset_row = instance->rows - instance->screen_rows; fb_redraw_internal(instance); cursor_put(instance); spinlock_unlock(&instance->lock); } /** Refresh the screen * */ static void fb_redraw(outdev_t *dev) { fb_instance_t *instance = (fb_instance_t *) dev->data; spinlock_lock(&instance->lock); fb_redraw_internal(instance); spinlock_unlock(&instance->lock); } /** Framebuffer was mapped or unmapped. * * @param arg Framebuffer instance */ static void fb_mapped_changed(void *arg) { fb_instance_t *instance = (fb_instance_t *) arg; if (!instance->parea.mapped) { spinlock_lock(&instance->lock); fb_redraw_internal(instance); spinlock_unlock(&instance->lock); } } /** Initialize framebuffer as a output character device * */ outdev_t *fb_init(fb_properties_t *props) { assert(props); assert(props->x > 0); assert(props->y > 0); assert(props->scan > 0); rgb_conv_t rgb_conv; unsigned int pixelbytes; switch (props->visual) { case VISUAL_INDIRECT_8: rgb_conv = bgr_323; pixelbytes = 1; break; case VISUAL_RGB_5_5_5_LE: rgb_conv = rgb_555_le; pixelbytes = 2; break; case VISUAL_RGB_5_5_5_BE: rgb_conv = rgb_555_be; pixelbytes = 2; break; case VISUAL_RGB_5_6_5_LE: rgb_conv = rgb_565_le; pixelbytes = 2; break; case VISUAL_RGB_5_6_5_BE: rgb_conv = rgb_565_be; pixelbytes = 2; break; case VISUAL_RGB_8_8_8: rgb_conv = rgb_888; pixelbytes = 3; break; case VISUAL_BGR_8_8_8: rgb_conv = bgr_888; pixelbytes = 3; break; case VISUAL_RGB_8_8_8_0: rgb_conv = rgb_8880; pixelbytes = 4; break; case VISUAL_RGB_0_8_8_8: rgb_conv = rgb_0888; pixelbytes = 4; break; case VISUAL_BGR_0_8_8_8: rgb_conv = bgr_0888; pixelbytes = 4; break; case VISUAL_BGR_8_8_8_0: rgb_conv = bgr_8880; pixelbytes = 4; break; default: LOG("Unsupported visual."); return NULL; } outdev_t *fbdev = malloc(sizeof(outdev_t)); if (!fbdev) return NULL; fb_instance_t *instance = malloc(sizeof(fb_instance_t)); if (!instance) { free(fbdev); return NULL; } outdev_initialize("fbdev", fbdev, &fbdev_ops); fbdev->data = instance; spinlock_initialize(&instance->lock, "*fb.instance.lock"); instance->rgb_conv = rgb_conv; instance->pixelbytes = pixelbytes; instance->xres = props->x; instance->yres = props->y; instance->scanline = props->scan; instance->screen_rows = Y2ROW(instance->yres); instance->cols = X2COL(instance->xres); instance->rows = FB_PAGES * instance->screen_rows; instance->start_row = instance->rows - instance->screen_rows; instance->offset_row = instance->start_row; instance->row = instance->start_row; instance->column = 0; instance->glyphscanline = FONT_WIDTH * instance->pixelbytes; instance->glyphbytes = ROW2Y(instance->glyphscanline); instance->bgscanbytes = instance->xres * instance->pixelbytes; size_t fbsize = instance->scanline * instance->yres; size_t bbsize = instance->cols * instance->rows * sizeof(uint16_t); size_t glyphsize = FONT_GLYPHS * instance->glyphbytes; instance->addr = (uint8_t *) km_map((uintptr_t) props->addr, fbsize, KM_NATURAL_ALIGNMENT, PAGE_WRITE | PAGE_WRITE_COMBINE); if (!instance->addr) { LOG("Unable to map framebuffer."); free(instance); free(fbdev); return NULL; } instance->backbuf = (uint16_t *) malloc(bbsize); if (!instance->backbuf) { LOG("Unable to allocate backbuffer."); free(instance); free(fbdev); return NULL; } instance->glyphs = (uint8_t *) malloc(glyphsize); if (!instance->glyphs) { LOG("Unable to allocate glyphs."); free(instance->backbuf); free(instance); free(fbdev); return NULL; } instance->bgscan = malloc(instance->bgscanbytes); if (!instance->bgscan) { LOG("Unable to allocate background pixel."); free(instance->glyphs); free(instance->backbuf); free(instance); free(fbdev); return NULL; } memsetw(instance->backbuf, instance->cols * instance->rows, 0); glyphs_render(instance); ddi_parea_init(&instance->parea); instance->parea.pbase = props->addr; instance->parea.frames = SIZE2FRAMES(fbsize); instance->parea.unpriv = false; instance->parea.mapped = false; instance->parea.mapped_changed = fb_mapped_changed; instance->parea.arg = (void *) instance; ddi_parea_register(&instance->parea); if (!fb_exported) { /* * We export the kernel framebuffer for uspace usage. * This is used in the case the uspace framebuffer * driver is not self-sufficient. */ sysinfo_set_item_val("fb", NULL, true); sysinfo_set_item_val("fb.kind", NULL, 1); sysinfo_set_item_val("fb.width", NULL, instance->xres); sysinfo_set_item_val("fb.height", NULL, instance->yres); sysinfo_set_item_val("fb.scanline", NULL, instance->scanline); sysinfo_set_item_val("fb.visual", NULL, props->visual); sysinfo_set_item_val("fb.address.physical", NULL, props->addr); fb_exported = true; } fb_redraw(fbdev); return fbdev; } /** @} */