diff --git a/stand/common/gfx_fb.c b/stand/common/gfx_fb.c index 02a0a3d2be22..77cf1d39854f 100644 --- a/stand/common/gfx_fb.c +++ b/stand/common/gfx_fb.c @@ -1,2685 +1,2795 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright 2020 Toomas Soome * Copyright 2019 OmniOS Community Edition (OmniOSce) Association. * Copyright 2020 RackTop Systems, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. * * $FreeBSD$ */ /* * The workhorse here is gfxfb_blt(). It is implemented to mimic UEFI * GOP Blt, and allows us to fill the rectangle on screen, copy * rectangle from video to buffer and buffer to video and video to video. * Such implementation does allow us to have almost identical implementation * for both BIOS VBE and UEFI. * * ALL pixel data is assumed to be 32-bit BGRA (byte order Blue, Green, Red, * Alpha) format, this allows us to only handle RGB data and not to worry * about mixing RGB with indexed colors. * Data exchange between memory buffer and video will translate BGRA * and native format as following: * * 32-bit to/from 32-bit is trivial case. * 32-bit to/from 24-bit is also simple - we just drop the alpha channel. * 32-bit to/from 16-bit is more complicated, because we nee to handle * data loss from 32-bit to 16-bit. While reading/writing from/to video, we * need to apply masks of 16-bit color components. This will preserve * colors for terminal text. For 32-bit truecolor PMG images, we need to * translate 32-bit colors to 15/16 bit colors and this means data loss. * There are different algorithms how to perform such color space reduction, * we are currently using bitwise right shift to reduce color space and so far * this technique seems to be sufficient (see also gfx_fb_putimage(), the * end of for loop). * 32-bit to/from 8-bit is the most troublesome because 8-bit colors are * indexed. From video, we do get color indexes, and we do translate * color index values to RGB. To write to video, we again need to translate * RGB to color index. Additionally, we need to translate between VGA and * console colors. * * Our internal color data is represented using BGRA format. But the hardware * used indexed colors for 8-bit colors (0-255) and for this mode we do * need to perform translation to/from BGRA and index values. * * - paletteentry RGB <-> index - * BGRA BUFFER <----/ \ - VIDEO * \ / * - RGB (16/24/32) - * * To perform index to RGB translation, we use palette table generated * from when we set up 8-bit mode video. We cannot read palette data from * the hardware, because not all hardware supports reading it. * * BGRA to index is implemented in rgb_to_color_index() by searching * palette array for closest match of RBG values. * * Note: In 8-bit mode, We do store first 16 colors to palette registers * in VGA color order, this serves two purposes; firstly, * if palette update is not supported, we still have correct 16 colors. * Secondly, the kernel does get correct 16 colors when some other boot * loader is used. However, the palette map for 8-bit colors is using * console color ordering - this does allow us to skip translation * from VGA colors to console colors, while we are reading RGB data. */ #include #include #include #include #include #include #include #include #include #include #include #if defined(EFI) #include #include #else #include #endif /* VGA text mode does use bold font. */ #if !defined(VGA_8X16_FONT) #define VGA_8X16_FONT "/boot/fonts/8x16b.fnt" #endif #if !defined(DEFAULT_8X16_FONT) #define DEFAULT_8X16_FONT "/boot/fonts/8x16.fnt" #endif /* * Must be sorted by font size in descending order */ font_list_t fonts = STAILQ_HEAD_INITIALIZER(fonts); #define DEFAULT_FONT_DATA font_data_8x16 extern vt_font_bitmap_data_t font_data_8x16; teken_gfx_t gfx_state = { 0 }; static struct { unsigned char r; /* Red percentage value. */ unsigned char g; /* Green percentage value. */ unsigned char b; /* Blue percentage value. */ } color_def[NCOLORS] = { {0, 0, 0}, /* black */ {50, 0, 0}, /* dark red */ {0, 50, 0}, /* dark green */ {77, 63, 0}, /* dark yellow */ {20, 40, 64}, /* dark blue */ {50, 0, 50}, /* dark magenta */ {0, 50, 50}, /* dark cyan */ {75, 75, 75}, /* light gray */ {18, 20, 21}, /* dark gray */ {100, 0, 0}, /* light red */ {0, 100, 0}, /* light green */ {100, 100, 0}, /* light yellow */ {45, 62, 81}, /* light blue */ {100, 0, 100}, /* light magenta */ {0, 100, 100}, /* light cyan */ {100, 100, 100}, /* white */ }; uint32_t cmap[NCMAP]; /* * Between console's palette and VGA's one: * - blue and red are swapped (1 <-> 4) * - yellow and cyan are swapped (3 <-> 6) */ const int cons_to_vga_colors[NCOLORS] = { 0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15 }; static const int vga_to_cons_colors[NCOLORS] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; struct text_pixel *screen_buffer; #if defined(EFI) static EFI_GRAPHICS_OUTPUT_BLT_PIXEL *GlyphBuffer; #else static struct paletteentry *GlyphBuffer; #endif static size_t GlyphBufferSize; static bool insert_font(char *, FONT_FLAGS); static int font_set(struct env_var *, int, const void *); static void * allocate_glyphbuffer(uint32_t, uint32_t); static void gfx_fb_cursor_draw(teken_gfx_t *, const teken_pos_t *, bool); /* * Initialize gfx framework. */ void gfx_framework_init(void) { /* * Setup font list to have builtin font. */ (void) insert_font(NULL, FONT_BUILTIN); } static uint8_t * gfx_get_fb_address(void) { return (ptov((uint32_t)gfx_state.tg_fb.fb_addr)); } /* * Utility function to parse gfx mode line strings. */ bool gfx_parse_mode_str(char *str, int *x, int *y, int *depth) { char *p, *end; errno = 0; p = str; *x = strtoul(p, &end, 0); if (*x == 0 || errno != 0) return (false); if (*end != 'x') return (false); p = end + 1; *y = strtoul(p, &end, 0); if (*y == 0 || errno != 0) return (false); if (*end != 'x') { *depth = -1; /* auto select */ } else { p = end + 1; *depth = strtoul(p, &end, 0); if (*depth == 0 || errno != 0 || *end != '\0') return (false); } return (true); } static uint32_t rgb_color_map(uint8_t index, uint32_t rmax, int roffset, uint32_t gmax, int goffset, uint32_t bmax, int boffset) { uint32_t color, code, gray, level; if (index < NCOLORS) { #define CF(_f, _i) ((_f ## max * color_def[(_i)]._f / 100) << _f ## offset) return (CF(r, index) | CF(g, index) | CF(b, index)); #undef CF } #define CF(_f, _c) ((_f ## max & _c) << _f ## offset) /* 6x6x6 color cube */ if (index > 15 && index < 232) { uint32_t red, green, blue; for (red = 0; red < 6; red++) { for (green = 0; green < 6; green++) { for (blue = 0; blue < 6; blue++) { code = 16 + (red * 36) + (green * 6) + blue; if (code != index) continue; red = red ? (red * 40 + 55) : 0; green = green ? (green * 40 + 55) : 0; blue = blue ? (blue * 40 + 55) : 0; color = CF(r, red); color |= CF(g, green); color |= CF(b, blue); return (color); } } } } /* colors 232-255 are a grayscale ramp */ for (gray = 0; gray < 24; gray++) { level = (gray * 10) + 8; code = 232 + gray; if (code == index) break; } return (CF(r, level) | CF(g, level) | CF(b, level)); #undef CF } /* * Support for color mapping. * For 8, 24 and 32 bit depth, use mask size 8. * 15/16 bit depth needs to use mask size from mode, * or we will lose color information from 32-bit to 15/16 bit translation. */ uint32_t gfx_fb_color_map(uint8_t index) { int rmask, gmask, bmask; int roff, goff, boff, bpp; roff = ffs(gfx_state.tg_fb.fb_mask_red) - 1; goff = ffs(gfx_state.tg_fb.fb_mask_green) - 1; boff = ffs(gfx_state.tg_fb.fb_mask_blue) - 1; bpp = roundup2(gfx_state.tg_fb.fb_bpp, 8) >> 3; if (bpp == 2) rmask = gfx_state.tg_fb.fb_mask_red >> roff; else rmask = 0xff; if (bpp == 2) gmask = gfx_state.tg_fb.fb_mask_green >> goff; else gmask = 0xff; if (bpp == 2) bmask = gfx_state.tg_fb.fb_mask_blue >> boff; else bmask = 0xff; return (rgb_color_map(index, rmask, 16, gmask, 8, bmask, 0)); } /* * Get indexed color from RGB. This function is used to write data to video * memory when the adapter is set to use indexed colors. * Since UEFI does only support 32-bit colors, we do not implement it for * UEFI because there is no need for it and we do not have palette array * for UEFI. */ static uint8_t rgb_to_color_index(uint8_t r, uint8_t g, uint8_t b) { #if !defined(EFI) uint32_t color, best, dist, k; int diff; color = 0; best = 255 * 255 * 255; for (k = 0; k < NCMAP; k++) { diff = r - pe8[k].Red; dist = diff * diff; diff = g - pe8[k].Green; dist += diff * diff; diff = b - pe8[k].Blue; dist += diff * diff; /* Exact match, exit the loop */ if (dist == 0) break; if (dist < best) { color = k; best = dist; } } if (k == NCMAP) k = color; return (k); #else (void) r; (void) g; (void) b; return (0); #endif } int generate_cons_palette(uint32_t *palette, int format, uint32_t rmax, int roffset, uint32_t gmax, int goffset, uint32_t bmax, int boffset) { int i; switch (format) { case COLOR_FORMAT_VGA: for (i = 0; i < NCOLORS; i++) palette[i] = cons_to_vga_colors[i]; for (; i < NCMAP; i++) palette[i] = i; break; case COLOR_FORMAT_RGB: for (i = 0; i < NCMAP; i++) palette[i] = rgb_color_map(i, rmax, roffset, gmax, goffset, bmax, boffset); break; default: return (ENODEV); } return (0); } static void gfx_mem_wr1(uint8_t *base, size_t size, uint32_t o, uint8_t v) { if (o >= size) return; *(uint8_t *)(base + o) = v; } static void gfx_mem_wr2(uint8_t *base, size_t size, uint32_t o, uint16_t v) { if (o >= size) return; *(uint16_t *)(base + o) = v; } static void gfx_mem_wr4(uint8_t *base, size_t size, uint32_t o, uint32_t v) { if (o >= size) return; *(uint32_t *)(base + o) = v; } static int gfxfb_blt_fill(void *BltBuffer, uint32_t DestinationX, uint32_t DestinationY, uint32_t Width, uint32_t Height) { #if defined(EFI) EFI_GRAPHICS_OUTPUT_BLT_PIXEL *p; #else struct paletteentry *p; #endif uint32_t data, bpp, pitch, y, x; int roff, goff, boff; size_t size; off_t off; uint8_t *destination; if (BltBuffer == NULL) return (EINVAL); if (DestinationY + Height > gfx_state.tg_fb.fb_height) return (EINVAL); if (DestinationX + Width > gfx_state.tg_fb.fb_width) return (EINVAL); if (Width == 0 || Height == 0) return (EINVAL); p = BltBuffer; roff = ffs(gfx_state.tg_fb.fb_mask_red) - 1; goff = ffs(gfx_state.tg_fb.fb_mask_green) - 1; boff = ffs(gfx_state.tg_fb.fb_mask_blue) - 1; if (gfx_state.tg_fb.fb_bpp == 8) { data = rgb_to_color_index(p->Red, p->Green, p->Blue); } else { data = (p->Red & (gfx_state.tg_fb.fb_mask_red >> roff)) << roff; data |= (p->Green & (gfx_state.tg_fb.fb_mask_green >> goff)) << goff; data |= (p->Blue & (gfx_state.tg_fb.fb_mask_blue >> boff)) << boff; } bpp = roundup2(gfx_state.tg_fb.fb_bpp, 8) >> 3; pitch = gfx_state.tg_fb.fb_stride * bpp; destination = gfx_get_fb_address(); size = gfx_state.tg_fb.fb_size; for (y = DestinationY; y < Height + DestinationY; y++) { off = y * pitch + DestinationX * bpp; for (x = 0; x < Width; x++) { switch (bpp) { case 1: gfx_mem_wr1(destination, size, off, (data < NCOLORS) ? cons_to_vga_colors[data] : data); break; case 2: gfx_mem_wr2(destination, size, off, data); break; case 3: gfx_mem_wr1(destination, size, off, (data >> 16) & 0xff); gfx_mem_wr1(destination, size, off + 1, (data >> 8) & 0xff); gfx_mem_wr1(destination, size, off + 2, data & 0xff); break; case 4: gfx_mem_wr4(destination, size, off, data); break; default: return (EINVAL); } off += bpp; } } return (0); } static int gfxfb_blt_video_to_buffer(void *BltBuffer, uint32_t SourceX, uint32_t SourceY, uint32_t DestinationX, uint32_t DestinationY, uint32_t Width, uint32_t Height, uint32_t Delta) { #if defined(EFI) EFI_GRAPHICS_OUTPUT_BLT_PIXEL *p; #else struct paletteentry *p; #endif uint32_t x, sy, dy; uint32_t bpp, pitch, copybytes; off_t off; uint8_t *source, *destination, *sb; uint8_t rm, rp, gm, gp, bm, bp; bool bgra; if (BltBuffer == NULL) return (EINVAL); if (SourceY + Height > gfx_state.tg_fb.fb_height) return (EINVAL); if (SourceX + Width > gfx_state.tg_fb.fb_width) return (EINVAL); if (Width == 0 || Height == 0) return (EINVAL); if (Delta == 0) Delta = Width * sizeof (*p); bpp = roundup2(gfx_state.tg_fb.fb_bpp, 8) >> 3; pitch = gfx_state.tg_fb.fb_stride * bpp; copybytes = Width * bpp; rp = ffs(gfx_state.tg_fb.fb_mask_red) - 1; gp = ffs(gfx_state.tg_fb.fb_mask_green) - 1; bp = ffs(gfx_state.tg_fb.fb_mask_blue) - 1; rm = gfx_state.tg_fb.fb_mask_red >> rp; gm = gfx_state.tg_fb.fb_mask_green >> gp; bm = gfx_state.tg_fb.fb_mask_blue >> bp; /* If FB pixel format is BGRA, we can use direct copy. */ bgra = bpp == 4 && ffs(rm) - 1 == 8 && rp == 16 && ffs(gm) - 1 == 8 && gp == 8 && ffs(bm) - 1 == 8 && bp == 0; for (sy = SourceY, dy = DestinationY; dy < Height + DestinationY; sy++, dy++) { off = sy * pitch + SourceX * bpp; source = gfx_get_fb_address() + off; destination = (uint8_t *)BltBuffer + dy * Delta + DestinationX * sizeof (*p); if (bgra) { bcopy(source, destination, copybytes); } else { for (x = 0; x < Width; x++) { uint32_t c = 0; p = (void *)(destination + x * sizeof (*p)); sb = source + x * bpp; switch (bpp) { case 1: c = *sb; break; case 2: c = *(uint16_t *)sb; break; case 3: c = sb[0] << 16 | sb[1] << 8 | sb[2]; break; case 4: c = *(uint32_t *)sb; break; default: return (EINVAL); } if (bpp == 1) { *(uint32_t *)p = gfx_fb_color_map( (c < 16) ? vga_to_cons_colors[c] : c); } else { p->Red = (c >> rp) & rm; p->Green = (c >> gp) & gm; p->Blue = (c >> bp) & bm; p->Reserved = 0; } } } } return (0); } static int gfxfb_blt_buffer_to_video(void *BltBuffer, uint32_t SourceX, uint32_t SourceY, uint32_t DestinationX, uint32_t DestinationY, uint32_t Width, uint32_t Height, uint32_t Delta) { #if defined(EFI) EFI_GRAPHICS_OUTPUT_BLT_PIXEL *p; #else struct paletteentry *p; #endif uint32_t x, sy, dy; uint32_t bpp, pitch, copybytes; off_t off; uint8_t *source, *destination; uint8_t rm, rp, gm, gp, bm, bp; bool bgra; if (BltBuffer == NULL) return (EINVAL); if (DestinationY + Height > gfx_state.tg_fb.fb_height) return (EINVAL); if (DestinationX + Width > gfx_state.tg_fb.fb_width) return (EINVAL); if (Width == 0 || Height == 0) return (EINVAL); if (Delta == 0) Delta = Width * sizeof (*p); bpp = roundup2(gfx_state.tg_fb.fb_bpp, 8) >> 3; pitch = gfx_state.tg_fb.fb_stride * bpp; copybytes = Width * bpp; rp = ffs(gfx_state.tg_fb.fb_mask_red) - 1; gp = ffs(gfx_state.tg_fb.fb_mask_green) - 1; bp = ffs(gfx_state.tg_fb.fb_mask_blue) - 1; rm = gfx_state.tg_fb.fb_mask_red >> rp; gm = gfx_state.tg_fb.fb_mask_green >> gp; bm = gfx_state.tg_fb.fb_mask_blue >> bp; /* If FB pixel format is BGRA, we can use direct copy. */ bgra = bpp == 4 && ffs(rm) - 1 == 8 && rp == 16 && ffs(gm) - 1 == 8 && gp == 8 && ffs(bm) - 1 == 8 && bp == 0; for (sy = SourceY, dy = DestinationY; sy < Height + SourceY; sy++, dy++) { off = dy * pitch + DestinationX * bpp; destination = gfx_get_fb_address() + off; if (bgra) { source = (uint8_t *)BltBuffer + sy * Delta + SourceX * sizeof (*p); bcopy(source, destination, copybytes); } else { for (x = 0; x < Width; x++) { uint32_t c; p = (void *)((uint8_t *)BltBuffer + sy * Delta + (SourceX + x) * sizeof (*p)); if (bpp == 1) { c = rgb_to_color_index(p->Red, p->Green, p->Blue); } else { c = (p->Red & rm) << rp | (p->Green & gm) << gp | (p->Blue & bm) << bp; } off = x * bpp; switch (bpp) { case 1: gfx_mem_wr1(destination, copybytes, off, (c < 16) ? cons_to_vga_colors[c] : c); break; case 2: gfx_mem_wr2(destination, copybytes, off, c); break; case 3: gfx_mem_wr1(destination, copybytes, off, (c >> 16) & 0xff); gfx_mem_wr1(destination, copybytes, off + 1, (c >> 8) & 0xff); gfx_mem_wr1(destination, copybytes, off + 2, c & 0xff); break; case 4: gfx_mem_wr4(destination, copybytes, x * bpp, c); break; default: return (EINVAL); } } } } return (0); } static int gfxfb_blt_video_to_video(uint32_t SourceX, uint32_t SourceY, uint32_t DestinationX, uint32_t DestinationY, uint32_t Width, uint32_t Height) { uint32_t bpp, copybytes; int pitch; uint8_t *source, *destination; off_t off; if (SourceY + Height > gfx_state.tg_fb.fb_height) return (EINVAL); if (SourceX + Width > gfx_state.tg_fb.fb_width) return (EINVAL); if (DestinationY + Height > gfx_state.tg_fb.fb_height) return (EINVAL); if (DestinationX + Width > gfx_state.tg_fb.fb_width) return (EINVAL); if (Width == 0 || Height == 0) return (EINVAL); bpp = roundup2(gfx_state.tg_fb.fb_bpp, 8) >> 3; pitch = gfx_state.tg_fb.fb_stride * bpp; copybytes = Width * bpp; off = SourceY * pitch + SourceX * bpp; source = gfx_get_fb_address() + off; off = DestinationY * pitch + DestinationX * bpp; destination = gfx_get_fb_address() + off; if ((uintptr_t)destination > (uintptr_t)source) { source += Height * pitch; destination += Height * pitch; pitch = -pitch; } while (Height-- > 0) { bcopy(source, destination, copybytes); source += pitch; destination += pitch; } return (0); } int gfxfb_blt(void *BltBuffer, GFXFB_BLT_OPERATION BltOperation, uint32_t SourceX, uint32_t SourceY, uint32_t DestinationX, uint32_t DestinationY, uint32_t Width, uint32_t Height, uint32_t Delta) { int rv; #if defined(EFI) EFI_STATUS status; EFI_GRAPHICS_OUTPUT *gop = gfx_state.tg_private; /* * We assume Blt() does work, if not, we will need to build * exception list case by case. */ if (gop != NULL) { switch (BltOperation) { case GfxFbBltVideoFill: status = gop->Blt(gop, BltBuffer, EfiBltVideoFill, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta); break; case GfxFbBltVideoToBltBuffer: status = gop->Blt(gop, BltBuffer, EfiBltVideoToBltBuffer, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta); break; case GfxFbBltBufferToVideo: status = gop->Blt(gop, BltBuffer, EfiBltBufferToVideo, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta); break; case GfxFbBltVideoToVideo: status = gop->Blt(gop, BltBuffer, EfiBltVideoToVideo, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta); break; default: status = EFI_INVALID_PARAMETER; break; } switch (status) { case EFI_SUCCESS: rv = 0; break; case EFI_INVALID_PARAMETER: rv = EINVAL; break; case EFI_DEVICE_ERROR: default: rv = EIO; break; } return (rv); } #endif switch (BltOperation) { case GfxFbBltVideoFill: rv = gfxfb_blt_fill(BltBuffer, DestinationX, DestinationY, Width, Height); break; case GfxFbBltVideoToBltBuffer: rv = gfxfb_blt_video_to_buffer(BltBuffer, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta); break; case GfxFbBltBufferToVideo: rv = gfxfb_blt_buffer_to_video(BltBuffer, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta); break; case GfxFbBltVideoToVideo: rv = gfxfb_blt_video_to_video(SourceX, SourceY, DestinationX, DestinationY, Width, Height); break; default: rv = EINVAL; break; } return (rv); } void gfx_bitblt_bitmap(teken_gfx_t *state, const uint8_t *glyph, const teken_attr_t *a, uint32_t alpha, bool cursor) { uint32_t width, height; uint32_t fgc, bgc, bpl, cc, o; int bpp, bit, byte; bool invert = false; bpp = 4; /* We only generate BGRA */ width = state->tg_font.vf_width; height = state->tg_font.vf_height; bpl = (width + 7) / 8; /* Bytes per source line. */ fgc = a->ta_fgcolor; bgc = a->ta_bgcolor; if (a->ta_format & TF_BOLD) fgc |= TC_LIGHT; if (a->ta_format & TF_BLINK) bgc |= TC_LIGHT; fgc = gfx_fb_color_map(fgc); bgc = gfx_fb_color_map(bgc); if (a->ta_format & TF_REVERSE) invert = !invert; if (cursor) invert = !invert; if (invert) { uint32_t tmp; tmp = fgc; fgc = bgc; bgc = tmp; } alpha = alpha << 24; fgc |= alpha; bgc |= alpha; for (uint32_t y = 0; y < height; y++) { for (uint32_t x = 0; x < width; x++) { byte = y * bpl + x / 8; bit = 0x80 >> (x % 8); o = y * width * bpp + x * bpp; cc = glyph[byte] & bit ? fgc : bgc; gfx_mem_wr4(state->tg_glyph, state->tg_glyph_size, o, cc); } } } /* * Draw prepared glyph on terminal point p. */ static void gfx_fb_printchar(teken_gfx_t *state, const teken_pos_t *p) { unsigned x, y, width, height; width = state->tg_font.vf_width; height = state->tg_font.vf_height; x = state->tg_origin.tp_col + p->tp_col * width; y = state->tg_origin.tp_row + p->tp_row * height; gfx_fb_cons_display(x, y, width, height, state->tg_glyph); } /* * Store char with its attribute to buffer and put it on screen. */ void gfx_fb_putchar(void *arg, const teken_pos_t *p, teken_char_t c, const teken_attr_t *a) { teken_gfx_t *state = arg; const uint8_t *glyph; int idx; idx = p->tp_col + p->tp_row * state->tg_tp.tp_col; if (idx >= state->tg_tp.tp_col * state->tg_tp.tp_row) return; /* remove the cursor */ if (state->tg_cursor_visible) gfx_fb_cursor_draw(state, &state->tg_cursor, false); screen_buffer[idx].c = c; screen_buffer[idx].a = *a; glyph = font_lookup(&state->tg_font, c, a); gfx_bitblt_bitmap(state, glyph, a, 0xff, false); gfx_fb_printchar(state, p); /* display the cursor */ if (state->tg_cursor_visible) { const teken_pos_t *c; c = teken_get_cursor(&state->tg_teken); gfx_fb_cursor_draw(state, c, true); } } void gfx_fb_fill(void *arg, const teken_rect_t *r, teken_char_t c, const teken_attr_t *a) { teken_gfx_t *state = arg; const uint8_t *glyph; teken_pos_t p; struct text_pixel *row; /* remove the cursor */ if (state->tg_cursor_visible) gfx_fb_cursor_draw(state, &state->tg_cursor, false); glyph = font_lookup(&state->tg_font, c, a); gfx_bitblt_bitmap(state, glyph, a, 0xff, false); for (p.tp_row = r->tr_begin.tp_row; p.tp_row < r->tr_end.tp_row; p.tp_row++) { row = &screen_buffer[p.tp_row * state->tg_tp.tp_col]; for (p.tp_col = r->tr_begin.tp_col; p.tp_col < r->tr_end.tp_col; p.tp_col++) { row[p.tp_col].c = c; row[p.tp_col].a = *a; gfx_fb_printchar(state, &p); } } /* display the cursor */ if (state->tg_cursor_visible) { const teken_pos_t *c; c = teken_get_cursor(&state->tg_teken); gfx_fb_cursor_draw(state, c, true); } } static void gfx_fb_cursor_draw(teken_gfx_t *state, const teken_pos_t *p, bool on) { const uint8_t *glyph; int idx; idx = p->tp_col + p->tp_row * state->tg_tp.tp_col; if (idx >= state->tg_tp.tp_col * state->tg_tp.tp_row) return; glyph = font_lookup(&state->tg_font, screen_buffer[idx].c, &screen_buffer[idx].a); gfx_bitblt_bitmap(state, glyph, &screen_buffer[idx].a, 0xff, on); gfx_fb_printchar(state, p); state->tg_cursor = *p; } void gfx_fb_cursor(void *arg, const teken_pos_t *p) { teken_gfx_t *state = arg; #if defined(EFI) EFI_TPL tpl; tpl = BS->RaiseTPL(TPL_NOTIFY); #endif /* Switch cursor off in old location and back on in new. */ if (state->tg_cursor_visible) { gfx_fb_cursor_draw(state, &state->tg_cursor, false); gfx_fb_cursor_draw(state, p, true); } #if defined(EFI) BS->RestoreTPL(tpl); #endif } void gfx_fb_param(void *arg, int cmd, unsigned int value) { teken_gfx_t *state = arg; const teken_pos_t *c; switch (cmd) { case TP_SETLOCALCURSOR: /* * 0 means normal (usually block), 1 means hidden, and * 2 means blinking (always block) for compatibility with * syscons. We don't support any changes except hiding, * so must map 2 to 0. */ value = (value == 1) ? 0 : 1; /* FALLTHROUGH */ case TP_SHOWCURSOR: c = teken_get_cursor(&state->tg_teken); gfx_fb_cursor_draw(state, c, true); if (value != 0) state->tg_cursor_visible = true; else state->tg_cursor_visible = false; break; default: /* Not yet implemented */ break; } } bool is_same_pixel(struct text_pixel *px1, struct text_pixel *px2) { if (px1->c != px2->c) return (false); /* Is there image stored? */ if ((px1->a.ta_format & TF_IMAGE) || (px2->a.ta_format & TF_IMAGE)) return (false); if (px1->a.ta_format != px2->a.ta_format) return (false); if (px1->a.ta_fgcolor != px2->a.ta_fgcolor) return (false); if (px1->a.ta_bgcolor != px2->a.ta_bgcolor) return (false); return (true); } static void gfx_fb_copy_area(teken_gfx_t *state, const teken_rect_t *s, const teken_pos_t *d) { uint32_t sx, sy, dx, dy, width, height; width = state->tg_font.vf_width; height = state->tg_font.vf_height; sx = state->tg_origin.tp_col + s->tr_begin.tp_col * width; sy = state->tg_origin.tp_row + s->tr_begin.tp_row * height; dx = state->tg_origin.tp_col + d->tp_col * width; dy = state->tg_origin.tp_row + d->tp_row * height; width *= (s->tr_end.tp_col - s->tr_begin.tp_col + 1); (void) gfxfb_blt(NULL, GfxFbBltVideoToVideo, sx, sy, dx, dy, width, height, 0); } static void gfx_fb_copy_line(teken_gfx_t *state, int ncol, teken_pos_t *s, teken_pos_t *d) { teken_rect_t sr; teken_pos_t dp; unsigned soffset, doffset; bool mark = false; int x; soffset = s->tp_col + s->tp_row * state->tg_tp.tp_col; doffset = d->tp_col + d->tp_row * state->tg_tp.tp_col; for (x = 0; x < ncol; x++) { if (is_same_pixel(&screen_buffer[soffset + x], &screen_buffer[doffset + x])) { if (mark) { gfx_fb_copy_area(state, &sr, &dp); mark = false; } } else { screen_buffer[doffset + x] = screen_buffer[soffset + x]; if (mark) { /* update end point */ sr.tr_end.tp_col = s->tp_col + x;; } else { /* set up new rectangle */ mark = true; sr.tr_begin.tp_col = s->tp_col + x; sr.tr_begin.tp_row = s->tp_row; sr.tr_end.tp_col = s->tp_col + x; sr.tr_end.tp_row = s->tp_row; dp.tp_col = d->tp_col + x; dp.tp_row = d->tp_row; } } } if (mark) { gfx_fb_copy_area(state, &sr, &dp); } } void gfx_fb_copy(void *arg, const teken_rect_t *r, const teken_pos_t *p) { teken_gfx_t *state = arg; unsigned doffset, soffset; teken_pos_t d, s; int nrow, ncol, y; /* Has to be signed - >= 0 comparison */ /* * Copying is a little tricky. We must make sure we do it in * correct order, to make sure we don't overwrite our own data. */ nrow = r->tr_end.tp_row - r->tr_begin.tp_row; ncol = r->tr_end.tp_col - r->tr_begin.tp_col; if (p->tp_row + nrow > state->tg_tp.tp_row || p->tp_col + ncol > state->tg_tp.tp_col) return; soffset = r->tr_begin.tp_col + r->tr_begin.tp_row * state->tg_tp.tp_col; doffset = p->tp_col + p->tp_row * state->tg_tp.tp_col; /* remove the cursor */ if (state->tg_cursor_visible) gfx_fb_cursor_draw(state, &state->tg_cursor, false); /* * Copy line by line. */ if (doffset <= soffset) { s = r->tr_begin; d = *p; for (y = 0; y < nrow; y++) { s.tp_row = r->tr_begin.tp_row + y; d.tp_row = p->tp_row + y; gfx_fb_copy_line(state, ncol, &s, &d); } } else { for (y = nrow - 1; y >= 0; y--) { s.tp_row = r->tr_begin.tp_row + y; d.tp_row = p->tp_row + y; gfx_fb_copy_line(state, ncol, &s, &d); } } /* display the cursor */ if (state->tg_cursor_visible) { const teken_pos_t *c; c = teken_get_cursor(&state->tg_teken); gfx_fb_cursor_draw(state, c, true); } } /* * Implements alpha blending for RGBA data, could use pixels for arguments, * but byte stream seems more generic. * The generic alpha blending is: * blend = alpha * fg + (1.0 - alpha) * bg. * Since our alpha is not from range [0..1], we scale appropriately. */ static uint8_t alpha_blend(uint8_t fg, uint8_t bg, uint8_t alpha) { uint16_t blend, h, l; /* trivial corner cases */ if (alpha == 0) return (bg); if (alpha == 0xFF) return (fg); blend = (alpha * fg + (0xFF - alpha) * bg); /* Division by 0xFF */ h = blend >> 8; l = blend & 0xFF; if (h + l >= 0xFF) h++; return (h); } /* * Implements alpha blending for RGBA data, could use pixels for arguments, * but byte stream seems more generic. * The generic alpha blending is: * blend = alpha * fg + (1.0 - alpha) * bg. * Since our alpha is not from range [0..1], we scale appropriately. */ static void bitmap_cpy(void *dst, void *src, uint32_t size) { #if defined(EFI) EFI_GRAPHICS_OUTPUT_BLT_PIXEL *ps, *pd; #else struct paletteentry *ps, *pd; #endif uint32_t i; uint8_t a; ps = src; pd = dst; /* * we only implement alpha blending for depth 32. */ for (i = 0; i < size; i ++) { a = ps[i].Reserved; pd[i].Red = alpha_blend(ps[i].Red, pd[i].Red, a); pd[i].Green = alpha_blend(ps[i].Green, pd[i].Green, a); pd[i].Blue = alpha_blend(ps[i].Blue, pd[i].Blue, a); pd[i].Reserved = a; } } static void * allocate_glyphbuffer(uint32_t width, uint32_t height) { size_t size; size = sizeof (*GlyphBuffer) * width * height; if (size != GlyphBufferSize) { free(GlyphBuffer); GlyphBuffer = malloc(size); if (GlyphBuffer == NULL) return (NULL); GlyphBufferSize = size; } return (GlyphBuffer); } void gfx_fb_cons_display(uint32_t x, uint32_t y, uint32_t width, uint32_t height, void *data) { #if defined(EFI) EFI_GRAPHICS_OUTPUT_BLT_PIXEL *buf; #else struct paletteentry *buf; #endif size_t size; size = width * height * sizeof(*buf); /* * Common data to display is glyph, use preallocated * glyph buffer. */ if (gfx_state.tg_glyph_size != GlyphBufferSize) (void) allocate_glyphbuffer(width, height); if (size == GlyphBufferSize) buf = GlyphBuffer; else buf = malloc(size); if (buf == NULL) return; if (gfxfb_blt(buf, GfxFbBltVideoToBltBuffer, x, y, 0, 0, width, height, 0) == 0) { bitmap_cpy(buf, data, width * height); (void) gfxfb_blt(buf, GfxFbBltBufferToVideo, 0, 0, x, y, width, height, 0); } if (buf != GlyphBuffer) free(buf); } /* * Public graphics primitives. */ static int isqrt(int num) { int res = 0; int bit = 1 << 30; /* "bit" starts at the highest power of four <= the argument. */ while (bit > num) bit >>= 2; while (bit != 0) { if (num >= res + bit) { num -= res + bit; res = (res >> 1) + bit; } else { res >>= 1; } bit >>= 2; } return (res); } /* set pixel in framebuffer using gfx coordinates */ void gfx_fb_setpixel(uint32_t x, uint32_t y) { uint32_t c; const teken_attr_t *ap; if (gfx_state.tg_fb_type == FB_TEXT) return; ap = teken_get_curattr(&gfx_state.tg_teken); if (ap->ta_format & TF_REVERSE) { c = ap->ta_bgcolor; if (ap->ta_format & TF_BLINK) c |= TC_LIGHT; } else { c = ap->ta_fgcolor; if (ap->ta_format & TF_BOLD) c |= TC_LIGHT; } c = gfx_fb_color_map(c); if (x >= gfx_state.tg_fb.fb_width || y >= gfx_state.tg_fb.fb_height) return; gfxfb_blt(&c, GfxFbBltVideoFill, 0, 0, x, y, 1, 1, 0); } /* * draw rectangle in framebuffer using gfx coordinates. * The function is borrowed from vt_fb.c */ void gfx_fb_drawrect(uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2, uint32_t fill) { uint32_t x, y; if (gfx_state.tg_fb_type == FB_TEXT) return; for (y = y1; y <= y2; y++) { if (fill || (y == y1) || (y == y2)) { for (x = x1; x <= x2; x++) gfx_fb_setpixel(x, y); } else { gfx_fb_setpixel(x1, y); gfx_fb_setpixel(x2, y); } } } void gfx_fb_line(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1, uint32_t wd) { int dx, sx, dy, sy; int err, e2, x2, y2, ed, width; if (gfx_state.tg_fb_type == FB_TEXT) return; width = wd; sx = x0 < x1? 1 : -1; sy = y0 < y1? 1 : -1; dx = x1 > x0? x1 - x0 : x0 - x1; dy = y1 > y0? y1 - y0 : y0 - y1; err = dx + dy; ed = dx + dy == 0 ? 1: isqrt(dx * dx + dy * dy); for (;;) { gfx_fb_setpixel(x0, y0); e2 = err; x2 = x0; if ((e2 << 1) >= -dx) { /* x step */ e2 += dy; y2 = y0; while (e2 < ed * width && (y1 != (uint32_t)y2 || dx > dy)) { y2 += sy; gfx_fb_setpixel(x0, y2); e2 += dx; } if (x0 == x1) break; e2 = err; err -= dy; x0 += sx; } if ((e2 << 1) <= dy) { /* y step */ e2 = dx-e2; while (e2 < ed * width && (x1 != (uint32_t)x2 || dx < dy)) { x2 += sx; gfx_fb_setpixel(x2, y0); e2 += dy; } if (y0 == y1) break; err += dx; y0 += sy; } } } /* * quadratic Bézier curve limited to gradients without sign change. */ void gfx_fb_bezier(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2, uint32_t wd) { int sx, sy, xx, yy, xy, width; int dx, dy, err, curvature; int i; if (gfx_state.tg_fb_type == FB_TEXT) return; width = wd; sx = x2 - x1; sy = y2 - y1; xx = x0 - x1; yy = y0 - y1; curvature = xx*sy - yy*sx; if (sx*sx + sy*sy > xx*xx+yy*yy) { x2 = x0; x0 = sx + x1; y2 = y0; y0 = sy + y1; curvature = -curvature; } if (curvature != 0) { xx += sx; sx = x0 < x2? 1 : -1; xx *= sx; yy += sy; sy = y0 < y2? 1 : -1; yy *= sy; xy = (xx*yy) << 1; xx *= xx; yy *= yy; if (curvature * sx * sy < 0) { xx = -xx; yy = -yy; xy = -xy; curvature = -curvature; } dx = 4 * sy * curvature * (x1 - x0) + xx - xy; dy = 4 * sx * curvature * (y0 - y1) + yy - xy; xx += xx; yy += yy; err = dx + dy + xy; do { for (i = 0; i <= width; i++) gfx_fb_setpixel(x0 + i, y0); if (x0 == x2 && y0 == y2) return; /* last pixel -> curve finished */ y1 = 2 * err < dx; if (2 * err > dy) { x0 += sx; dx -= xy; dy += yy; err += dy; } if (y1 != 0) { y0 += sy; dy -= xy; dx += xx; err += dx; } } while (dy < dx); /* gradient negates -> algorithm fails */ } gfx_fb_line(x0, y0, x2, y2, width); } /* * draw rectangle using terminal coordinates and current foreground color. */ void gfx_term_drawrect(uint32_t ux1, uint32_t uy1, uint32_t ux2, uint32_t uy2) { int x1, y1, x2, y2; int xshift, yshift; int width, i; uint32_t vf_width, vf_height; teken_rect_t r; if (gfx_state.tg_fb_type == FB_TEXT) return; vf_width = gfx_state.tg_font.vf_width; vf_height = gfx_state.tg_font.vf_height; width = vf_width / 4; /* line width */ xshift = (vf_width - width) / 2; yshift = (vf_height - width) / 2; /* Shift coordinates */ if (ux1 != 0) ux1--; if (uy1 != 0) uy1--; ux2--; uy2--; /* mark area used in terminal */ r.tr_begin.tp_col = ux1; r.tr_begin.tp_row = uy1; r.tr_end.tp_col = ux2 + 1; r.tr_end.tp_row = uy2 + 1; term_image_display(&gfx_state, &r); /* * Draw horizontal lines width points thick, shifted from outer edge. */ x1 = (ux1 + 1) * vf_width + gfx_state.tg_origin.tp_col; y1 = uy1 * vf_height + gfx_state.tg_origin.tp_row + yshift; x2 = ux2 * vf_width + gfx_state.tg_origin.tp_col; gfx_fb_drawrect(x1, y1, x2, y1 + width, 1); y2 = uy2 * vf_height + gfx_state.tg_origin.tp_row; y2 += vf_height - yshift - width; gfx_fb_drawrect(x1, y2, x2, y2 + width, 1); /* * Draw vertical lines width points thick, shifted from outer edge. */ x1 = ux1 * vf_width + gfx_state.tg_origin.tp_col + xshift; y1 = uy1 * vf_height + gfx_state.tg_origin.tp_row; y1 += vf_height; y2 = uy2 * vf_height + gfx_state.tg_origin.tp_row; gfx_fb_drawrect(x1, y1, x1 + width, y2, 1); x1 = ux2 * vf_width + gfx_state.tg_origin.tp_col; x1 += vf_width - xshift - width; gfx_fb_drawrect(x1, y1, x1 + width, y2, 1); /* Draw upper left corner. */ x1 = ux1 * vf_width + gfx_state.tg_origin.tp_col + xshift; y1 = uy1 * vf_height + gfx_state.tg_origin.tp_row; y1 += vf_height; x2 = ux1 * vf_width + gfx_state.tg_origin.tp_col; x2 += vf_width; y2 = uy1 * vf_height + gfx_state.tg_origin.tp_row + yshift; for (i = 0; i <= width; i++) gfx_fb_bezier(x1 + i, y1, x1 + i, y2 + i, x2, y2 + i, width-i); /* Draw lower left corner. */ x1 = ux1 * vf_width + gfx_state.tg_origin.tp_col; x1 += vf_width; y1 = uy2 * vf_height + gfx_state.tg_origin.tp_row; y1 += vf_height - yshift; x2 = ux1 * vf_width + gfx_state.tg_origin.tp_col + xshift; y2 = uy2 * vf_height + gfx_state.tg_origin.tp_row; for (i = 0; i <= width; i++) gfx_fb_bezier(x1, y1 - i, x2 + i, y1 - i, x2 + i, y2, width-i); /* Draw upper right corner. */ x1 = ux2 * vf_width + gfx_state.tg_origin.tp_col; y1 = uy1 * vf_height + gfx_state.tg_origin.tp_row + yshift; x2 = ux2 * vf_width + gfx_state.tg_origin.tp_col; x2 += vf_width - xshift - width; y2 = uy1 * vf_height + gfx_state.tg_origin.tp_row; y2 += vf_height; for (i = 0; i <= width; i++) gfx_fb_bezier(x1, y1 + i, x2 + i, y1 + i, x2 + i, y2, width-i); /* Draw lower right corner. */ x1 = ux2 * vf_width + gfx_state.tg_origin.tp_col; y1 = uy2 * vf_height + gfx_state.tg_origin.tp_row; y1 += vf_height - yshift; x2 = ux2 * vf_width + gfx_state.tg_origin.tp_col; x2 += vf_width - xshift - width; y2 = uy2 * vf_height + gfx_state.tg_origin.tp_row; for (i = 0; i <= width; i++) gfx_fb_bezier(x1, y1 - i, x2 + i, y1 - i, x2 + i, y2, width-i); } int gfx_fb_putimage(png_t *png, uint32_t ux1, uint32_t uy1, uint32_t ux2, uint32_t uy2, uint32_t flags) { #if defined(EFI) EFI_GRAPHICS_OUTPUT_BLT_PIXEL *p; #else struct paletteentry *p; #endif uint8_t *data; uint32_t i, j, x, y, fheight, fwidth; int rs, gs, bs; uint8_t r, g, b, a; bool scale = false; bool trace = false; teken_rect_t rect; trace = (flags & FL_PUTIMAGE_DEBUG) != 0; if (gfx_state.tg_fb_type == FB_TEXT) { if (trace) printf("Framebuffer not active.\n"); return (1); } if (png->color_type != PNG_TRUECOLOR_ALPHA) { if (trace) printf("Not truecolor image.\n"); return (1); } if (ux1 > gfx_state.tg_fb.fb_width || uy1 > gfx_state.tg_fb.fb_height) { if (trace) printf("Top left coordinate off screen.\n"); return (1); } if (png->width > UINT16_MAX || png->height > UINT16_MAX) { if (trace) printf("Image too large.\n"); return (1); } if (png->width < 1 || png->height < 1) { if (trace) printf("Image too small.\n"); return (1); } /* * If 0 was passed for either ux2 or uy2, then calculate the missing * part of the bottom right coordinate. */ scale = true; if (ux2 == 0 && uy2 == 0) { /* Both 0, use the native resolution of the image */ ux2 = ux1 + png->width; uy2 = uy1 + png->height; scale = false; } else if (ux2 == 0) { /* Set ux2 from uy2/uy1 to maintain aspect ratio */ ux2 = ux1 + (png->width * (uy2 - uy1)) / png->height; } else if (uy2 == 0) { /* Set uy2 from ux2/ux1 to maintain aspect ratio */ uy2 = uy1 + (png->height * (ux2 - ux1)) / png->width; } if (ux2 > gfx_state.tg_fb.fb_width || uy2 > gfx_state.tg_fb.fb_height) { if (trace) printf("Bottom right coordinate off screen.\n"); return (1); } fwidth = ux2 - ux1; fheight = uy2 - uy1; /* * If the original image dimensions have been passed explicitly, * disable scaling. */ if (fwidth == png->width && fheight == png->height) scale = false; if (ux1 == 0) { /* * No top left X co-ordinate (real coordinates start at 1), * place as far right as it will fit. */ ux2 = gfx_state.tg_fb.fb_width - gfx_state.tg_origin.tp_col; ux1 = ux2 - fwidth; } if (uy1 == 0) { /* * No top left Y co-ordinate (real coordinates start at 1), * place as far down as it will fit. */ uy2 = gfx_state.tg_fb.fb_height - gfx_state.tg_origin.tp_row; uy1 = uy2 - fheight; } if (ux1 >= ux2 || uy1 >= uy2) { if (trace) printf("Image dimensions reversed.\n"); return (1); } if (fwidth < 2 || fheight < 2) { if (trace) printf("Target area too small\n"); return (1); } if (trace) printf("Image %ux%u -> %ux%u @%ux%u\n", png->width, png->height, fwidth, fheight, ux1, uy1); rect.tr_begin.tp_col = ux1 / gfx_state.tg_font.vf_width; rect.tr_begin.tp_row = uy1 / gfx_state.tg_font.vf_height; rect.tr_end.tp_col = (ux1 + fwidth) / gfx_state.tg_font.vf_width; rect.tr_end.tp_row = (uy1 + fheight) / gfx_state.tg_font.vf_height; /* * mark area used in terminal */ if (!(flags & FL_PUTIMAGE_NOSCROLL)) term_image_display(&gfx_state, &rect); if ((flags & FL_PUTIMAGE_BORDER)) gfx_fb_drawrect(ux1, uy1, ux2, uy2, 0); data = malloc(fwidth * fheight * sizeof(*p)); p = (void *)data; if (data == NULL) { if (trace) printf("Out of memory.\n"); return (1); } /* * Build image for our framebuffer. */ /* Helper to calculate the pixel index from the source png */ #define GETPIXEL(xx, yy) (((yy) * png->width + (xx)) * png->bpp) /* * For each of the x and y directions, calculate the number of pixels * in the source image that correspond to a single pixel in the target. * Use fixed-point arithmetic with 16-bits for each of the integer and * fractional parts. */ const uint32_t wcstep = ((png->width - 1) << 16) / (fwidth - 1); const uint32_t hcstep = ((png->height - 1) << 16) / (fheight - 1); rs = 8 - (fls(gfx_state.tg_fb.fb_mask_red) - ffs(gfx_state.tg_fb.fb_mask_red) + 1); gs = 8 - (fls(gfx_state.tg_fb.fb_mask_green) - ffs(gfx_state.tg_fb.fb_mask_green) + 1); bs = 8 - (fls(gfx_state.tg_fb.fb_mask_blue) - ffs(gfx_state.tg_fb.fb_mask_blue) + 1); uint32_t hc = 0; for (y = 0; y < fheight; y++) { uint32_t hc2 = (hc >> 9) & 0x7f; uint32_t hc1 = 0x80 - hc2; uint32_t offset_y = hc >> 16; uint32_t offset_y1 = offset_y + 1; uint32_t wc = 0; for (x = 0; x < fwidth; x++) { uint32_t wc2 = (wc >> 9) & 0x7f; uint32_t wc1 = 0x80 - wc2; uint32_t offset_x = wc >> 16; uint32_t offset_x1 = offset_x + 1; /* Target pixel index */ j = y * fwidth + x; if (!scale) { i = GETPIXEL(x, y); r = png->image[i]; g = png->image[i + 1]; b = png->image[i + 2]; a = png->image[i + 3]; } else { uint8_t pixel[4]; uint32_t p00 = GETPIXEL(offset_x, offset_y); uint32_t p01 = GETPIXEL(offset_x, offset_y1); uint32_t p10 = GETPIXEL(offset_x1, offset_y); uint32_t p11 = GETPIXEL(offset_x1, offset_y1); /* * Given a 2x2 array of pixels in the source * image, combine them to produce a single * value for the pixel in the target image. * Each column of pixels is combined using * a weighted average where the top and bottom * pixels contribute hc1 and hc2 respectively. * The calculation for bottom pixel pB and * top pixel pT is: * (pT * hc1 + pB * hc2) / (hc1 + hc2) * Once the values are determined for the two * columns of pixels, then the columns are * averaged together in the same way but using * wc1 and wc2 for the weightings. * * Since hc1 and hc2 are chosen so that * hc1 + hc2 == 128 (and same for wc1 + wc2), * the >> 14 below is a quick way to divide by * (hc1 + hc2) * (wc1 + wc2) */ for (i = 0; i < 4; i++) pixel[i] = ( (png->image[p00 + i] * hc1 + png->image[p01 + i] * hc2) * wc1 + (png->image[p10 + i] * hc1 + png->image[p11 + i] * hc2) * wc2) >> 14; r = pixel[0]; g = pixel[1]; b = pixel[2]; a = pixel[3]; } if (trace) printf("r/g/b: %x/%x/%x\n", r, g, b); /* * Rough colorspace reduction for 15/16 bit colors. */ p[j].Red = r >> rs; p[j].Green = g >> gs; p[j].Blue = b >> bs; p[j].Reserved = a; wc += wcstep; } hc += hcstep; } gfx_fb_cons_display(ux1, uy1, fwidth, fheight, data); free(data); return (0); } /* * Reset font flags to FONT_AUTO. */ void reset_font_flags(void) { struct fontlist *fl; STAILQ_FOREACH(fl, &fonts, font_next) { fl->font_flags = FONT_AUTO; } } +/* Return w^2 + h^2 or 0, if the dimensions are unknown */ +static unsigned +edid_diagonal_squared(void) +{ + unsigned w, h; + + if (edid_info == NULL) + return (0); + + w = edid_info->display.max_horizontal_image_size; + h = edid_info->display.max_vertical_image_size; + + /* If either one is 0, we have aspect ratio, not size */ + if (w == 0 || h == 0) + return (0); + + /* + * some monitors encode the aspect ratio instead of the physical size. + */ + if ((w == 16 && h == 9) || (w == 16 && h == 10) || + (w == 4 && h == 3) || (w == 5 && h == 4)) + return (0); + + /* + * translate cm to inch, note we scale by 100 here. + */ + w = w * 100 / 254; + h = h * 100 / 254; + + /* Return w^2 + h^2 */ + return (w * w + h * h); +} + +/* + * calculate pixels per inch. + */ +static unsigned +gfx_get_ppi(void) +{ + unsigned dp, di; + + di = edid_diagonal_squared(); + if (di == 0) + return (0); + + dp = gfx_state.tg_fb.fb_width * + gfx_state.tg_fb.fb_width + + gfx_state.tg_fb.fb_height * + gfx_state.tg_fb.fb_height; + + return (isqrt(dp / di)); +} + +/* + * Calculate font size from density independent pixels (dp): + * ((16dp * ppi) / 160) * display_factor. + * Here we are using fixed constants: 1dp == 160 ppi and + * display_factor 2. + * + * We are rounding font size up and are searching for font which is + * not smaller than calculated size value. + */ +static vt_font_bitmap_data_t * +gfx_get_font(void) +{ + unsigned ppi, size; + vt_font_bitmap_data_t *font = NULL; + struct fontlist *fl, *next; + + /* Text mode is not supported here. */ + if (gfx_state.tg_fb_type == FB_TEXT) + return (NULL); + + ppi = gfx_get_ppi(); + if (ppi == 0) + return (NULL); + + /* + * We will search for 16dp font. + * We are using scale up by 10 for roundup. + */ + size = (16 * ppi * 10) / 160; + /* Apply display factor 2. */ + size = roundup(size * 2, 10) / 10; + + STAILQ_FOREACH(fl, &fonts, font_next) { + next = STAILQ_NEXT(fl, font_next); + + /* + * If this is last font or, if next font is smaller, + * we have our font. Make sure, it actually is loaded. + */ + if (next == NULL || next->font_data->vfbd_height < size) { + font = fl->font_data; + if (font->vfbd_font == NULL || + fl->font_flags == FONT_RELOAD) { + if (fl->font_load != NULL && + fl->font_name != NULL) + font = fl->font_load(fl->font_name); + } + break; + } + } + + return (font); +} + static vt_font_bitmap_data_t * set_font(teken_unit_t *rows, teken_unit_t *cols, teken_unit_t h, teken_unit_t w) { vt_font_bitmap_data_t *font = NULL; struct fontlist *fl; unsigned height = h; unsigned width = w; /* * First check for manually loaded font. */ STAILQ_FOREACH(fl, &fonts, font_next) { if (fl->font_flags == FONT_MANUAL) { font = fl->font_data; if (font->vfbd_font == NULL && fl->font_load != NULL && fl->font_name != NULL) { font = fl->font_load(fl->font_name); } if (font == NULL || font->vfbd_font == NULL) font = NULL; break; } } + if (font == NULL) + font = gfx_get_font(); + if (font != NULL) { *rows = height / font->vfbd_height; *cols = width / font->vfbd_width; return (font); } /* * Find best font for these dimensions, or use default. * If height >= VT_FB_MAX_HEIGHT and width >= VT_FB_MAX_WIDTH, * do not use smaller font than our DEFAULT_FONT_DATA. */ STAILQ_FOREACH(fl, &fonts, font_next) { font = fl->font_data; if ((*rows * font->vfbd_height <= height && *cols * font->vfbd_width <= width) || (height >= VT_FB_MAX_HEIGHT && width >= VT_FB_MAX_WIDTH && font->vfbd_height == DEFAULT_FONT_DATA.vfbd_height && font->vfbd_width == DEFAULT_FONT_DATA.vfbd_width)) { if (font->vfbd_font == NULL || fl->font_flags == FONT_RELOAD) { if (fl->font_load != NULL && fl->font_name != NULL) { font = fl->font_load(fl->font_name); } if (font == NULL) continue; } *rows = height / font->vfbd_height; *cols = width / font->vfbd_width; break; } font = NULL; } if (font == NULL) { /* * We have fonts sorted smallest last, try it before * falling back to builtin. */ fl = STAILQ_LAST(&fonts, fontlist, font_next); if (fl != NULL && fl->font_load != NULL && fl->font_name != NULL) { font = fl->font_load(fl->font_name); } if (font == NULL) font = &DEFAULT_FONT_DATA; *rows = height / font->vfbd_height; *cols = width / font->vfbd_width; } return (font); } static void cons_clear(void) { char clear[] = { '\033', 'c' }; /* Reset terminal */ teken_input(&gfx_state.tg_teken, clear, sizeof(clear)); gfx_state.tg_functions->tf_param(&gfx_state, TP_SHOWCURSOR, 0); } void setup_font(teken_gfx_t *state, teken_unit_t height, teken_unit_t width) { vt_font_bitmap_data_t *font_data; teken_pos_t *tp = &state->tg_tp; char env[8]; int i; /* * set_font() will select a appropriate sized font for * the number of rows and columns selected. If we don't * have a font that will fit, then it will use the * default builtin font and adjust the rows and columns * to fit on the screen. */ font_data = set_font(&tp->tp_row, &tp->tp_col, height, width); if (font_data == NULL) panic("out of memory"); for (i = 0; i < VFNT_MAPS; i++) { state->tg_font.vf_map[i] = font_data->vfbd_font->vf_map[i]; state->tg_font.vf_map_count[i] = font_data->vfbd_font->vf_map_count[i]; } state->tg_font.vf_bytes = font_data->vfbd_font->vf_bytes; state->tg_font.vf_height = font_data->vfbd_font->vf_height; state->tg_font.vf_width = font_data->vfbd_font->vf_width; snprintf(env, sizeof (env), "%ux%u", state->tg_font.vf_width, state->tg_font.vf_height); env_setenv("screen.font", EV_VOLATILE | EV_NOHOOK, env, font_set, env_nounset); } /* Binary search for the glyph. Return 0 if not found. */ static uint16_t font_bisearch(const vfnt_map_t *map, uint32_t len, teken_char_t src) { unsigned min, mid, max; min = 0; max = len - 1; /* Empty font map. */ if (len == 0) return (0); /* Character below minimal entry. */ if (src < map[0].vfm_src) return (0); /* Optimization: ASCII characters occur very often. */ if (src <= map[0].vfm_src + map[0].vfm_len) return (src - map[0].vfm_src + map[0].vfm_dst); /* Character above maximum entry. */ if (src > map[max].vfm_src + map[max].vfm_len) return (0); /* Binary search. */ while (max >= min) { mid = (min + max) / 2; if (src < map[mid].vfm_src) max = mid - 1; else if (src > map[mid].vfm_src + map[mid].vfm_len) min = mid + 1; else return (src - map[mid].vfm_src + map[mid].vfm_dst); } return (0); } /* * Return glyph bitmap. If glyph is not found, we will return bitmap * for the first (offset 0) glyph. */ uint8_t * font_lookup(const struct vt_font *vf, teken_char_t c, const teken_attr_t *a) { uint16_t dst; size_t stride; /* Substitute bold with normal if not found. */ if (a->ta_format & TF_BOLD) { dst = font_bisearch(vf->vf_map[VFNT_MAP_BOLD], vf->vf_map_count[VFNT_MAP_BOLD], c); if (dst != 0) goto found; } dst = font_bisearch(vf->vf_map[VFNT_MAP_NORMAL], vf->vf_map_count[VFNT_MAP_NORMAL], c); found: stride = howmany(vf->vf_width, 8) * vf->vf_height; return (&vf->vf_bytes[dst * stride]); } static int load_mapping(int fd, struct vt_font *fp, int n) { size_t i, size; ssize_t rv; vfnt_map_t *mp; if (fp->vf_map_count[n] == 0) return (0); size = fp->vf_map_count[n] * sizeof(*mp); mp = malloc(size); if (mp == NULL) return (ENOMEM); fp->vf_map[n] = mp; rv = read(fd, mp, size); if (rv < 0 || (size_t)rv != size) { free(fp->vf_map[n]); fp->vf_map[n] = NULL; return (EIO); } for (i = 0; i < fp->vf_map_count[n]; i++) { mp[i].vfm_src = be32toh(mp[i].vfm_src); mp[i].vfm_dst = be16toh(mp[i].vfm_dst); mp[i].vfm_len = be16toh(mp[i].vfm_len); } return (0); } static int builtin_mapping(struct vt_font *fp, int n) { size_t size; struct vfnt_map *mp; if (n >= VFNT_MAPS) return (EINVAL); if (fp->vf_map_count[n] == 0) return (0); size = fp->vf_map_count[n] * sizeof(*mp); mp = malloc(size); if (mp == NULL) return (ENOMEM); fp->vf_map[n] = mp; memcpy(mp, DEFAULT_FONT_DATA.vfbd_font->vf_map[n], size); return (0); } /* * Load font from builtin or from file. * We do need special case for builtin because the builtin font glyphs * are compressed and we do need to uncompress them. * Having single load_font() for both cases will help us to simplify * font switch handling. */ static vt_font_bitmap_data_t * load_font(char *path) { int fd, i; uint32_t glyphs; struct font_header fh; struct fontlist *fl; vt_font_bitmap_data_t *bp; struct vt_font *fp; size_t size; ssize_t rv; /* Get our entry from the font list. */ STAILQ_FOREACH(fl, &fonts, font_next) { if (strcmp(fl->font_name, path) == 0) break; } if (fl == NULL) return (NULL); /* Should not happen. */ bp = fl->font_data; if (bp->vfbd_font != NULL && fl->font_flags != FONT_RELOAD) return (bp); fd = -1; /* * Special case for builtin font. * Builtin font is the very first font we load, we do not have * previous loads to be released. */ if (fl->font_flags == FONT_BUILTIN) { if ((fp = calloc(1, sizeof(struct vt_font))) == NULL) return (NULL); fp->vf_width = DEFAULT_FONT_DATA.vfbd_width; fp->vf_height = DEFAULT_FONT_DATA.vfbd_height; fp->vf_bytes = malloc(DEFAULT_FONT_DATA.vfbd_uncompressed_size); if (fp->vf_bytes == NULL) { free(fp); return (NULL); } bp->vfbd_uncompressed_size = DEFAULT_FONT_DATA.vfbd_uncompressed_size; bp->vfbd_compressed_size = DEFAULT_FONT_DATA.vfbd_compressed_size; if (lz4_decompress(DEFAULT_FONT_DATA.vfbd_compressed_data, fp->vf_bytes, DEFAULT_FONT_DATA.vfbd_compressed_size, DEFAULT_FONT_DATA.vfbd_uncompressed_size, 0) != 0) { free(fp->vf_bytes); free(fp); return (NULL); } for (i = 0; i < VFNT_MAPS; i++) { fp->vf_map_count[i] = DEFAULT_FONT_DATA.vfbd_font->vf_map_count[i]; if (builtin_mapping(fp, i) != 0) goto free_done; } bp->vfbd_font = fp; return (bp); } fd = open(path, O_RDONLY); if (fd < 0) return (NULL); size = sizeof(fh); rv = read(fd, &fh, size); if (rv < 0 || (size_t)rv != size) { bp = NULL; goto done; } if (memcmp(fh.fh_magic, FONT_HEADER_MAGIC, sizeof(fh.fh_magic)) != 0) { bp = NULL; goto done; } if ((fp = calloc(1, sizeof(struct vt_font))) == NULL) { bp = NULL; goto done; } for (i = 0; i < VFNT_MAPS; i++) fp->vf_map_count[i] = be32toh(fh.fh_map_count[i]); glyphs = be32toh(fh.fh_glyph_count); fp->vf_width = fh.fh_width; fp->vf_height = fh.fh_height; size = howmany(fp->vf_width, 8) * fp->vf_height * glyphs; bp->vfbd_uncompressed_size = size; if ((fp->vf_bytes = malloc(size)) == NULL) goto free_done; rv = read(fd, fp->vf_bytes, size); if (rv < 0 || (size_t)rv != size) goto free_done; for (i = 0; i < VFNT_MAPS; i++) { if (load_mapping(fd, fp, i) != 0) goto free_done; } /* * Reset builtin flag now as we have full font loaded. */ if (fl->font_flags == FONT_BUILTIN) fl->font_flags = FONT_AUTO; /* * Release previously loaded entries. We can do this now, as * the new font is loaded. Note, there can be no console * output till the new font is in place and teken is notified. * We do need to keep fl->font_data for glyph dimensions. */ STAILQ_FOREACH(fl, &fonts, font_next) { if (fl->font_data->vfbd_font == NULL) continue; for (i = 0; i < VFNT_MAPS; i++) free(fl->font_data->vfbd_font->vf_map[i]); free(fl->font_data->vfbd_font->vf_bytes); free(fl->font_data->vfbd_font); fl->font_data->vfbd_font = NULL; } bp->vfbd_font = fp; bp->vfbd_compressed_size = 0; done: if (fd != -1) close(fd); return (bp); free_done: for (i = 0; i < VFNT_MAPS; i++) free(fp->vf_map[i]); free(fp->vf_bytes); free(fp); bp = NULL; goto done; } struct name_entry { char *n_name; SLIST_ENTRY(name_entry) n_entry; }; SLIST_HEAD(name_list, name_entry); /* Read font names from index file. */ static struct name_list * read_list(char *fonts) { struct name_list *nl; struct name_entry *np; char *dir, *ptr; char buf[PATH_MAX]; int fd, len; dir = strdup(fonts); if (dir == NULL) return (NULL); ptr = strrchr(dir, '/'); *ptr = '\0'; fd = open(fonts, O_RDONLY); if (fd < 0) return (NULL); nl = malloc(sizeof(*nl)); if (nl == NULL) { close(fd); return (nl); } SLIST_INIT(nl); while ((len = fgetstr(buf, sizeof (buf), fd)) >= 0) { if (*buf == '#' || *buf == '\0') continue; if (bcmp(buf, "MENU", 4) == 0) continue; if (bcmp(buf, "FONT", 4) == 0) continue; ptr = strchr(buf, ':'); if (ptr == NULL) continue; else *ptr = '\0'; np = malloc(sizeof(*np)); if (np == NULL) { close(fd); return (nl); /* return what we have */ } if (asprintf(&np->n_name, "%s/%s", dir, buf) < 0) { free(np); close(fd); return (nl); /* return what we have */ } SLIST_INSERT_HEAD(nl, np, n_entry); } close(fd); return (nl); } /* * Read the font properties and insert new entry into the list. * The font list is built in descending order. */ static bool insert_font(char *name, FONT_FLAGS flags) { struct font_header fh; struct fontlist *fp, *previous, *entry, *next; size_t size; ssize_t rv; int fd; char *font_name; font_name = NULL; if (flags == FONT_BUILTIN) { /* * We only install builtin font once, while setting up * initial console. Since this will happen very early, * we assume asprintf will not fail. Once we have access to * files, the builtin font will be replaced by font loaded * from file. */ if (!STAILQ_EMPTY(&fonts)) return (false); fh.fh_width = DEFAULT_FONT_DATA.vfbd_width; fh.fh_height = DEFAULT_FONT_DATA.vfbd_height; (void) asprintf(&font_name, "%dx%d", DEFAULT_FONT_DATA.vfbd_width, DEFAULT_FONT_DATA.vfbd_height); } else { fd = open(name, O_RDONLY); if (fd < 0) return (false); rv = read(fd, &fh, sizeof(fh)); close(fd); if (rv < 0 || (size_t)rv != sizeof(fh)) return (false); if (memcmp(fh.fh_magic, FONT_HEADER_MAGIC, sizeof(fh.fh_magic)) != 0) return (false); font_name = strdup(name); } if (font_name == NULL) return (false); /* * If we have an entry with the same glyph dimensions, replace * the file name and mark us. We only support unique dimensions. */ STAILQ_FOREACH(entry, &fonts, font_next) { if (fh.fh_width == entry->font_data->vfbd_width && fh.fh_height == entry->font_data->vfbd_height) { free(entry->font_name); entry->font_name = font_name; entry->font_flags = FONT_RELOAD; return (true); } } fp = calloc(sizeof(*fp), 1); if (fp == NULL) { free(font_name); return (false); } fp->font_data = calloc(sizeof(*fp->font_data), 1); if (fp->font_data == NULL) { free(font_name); free(fp); return (false); } fp->font_name = font_name; fp->font_flags = flags; fp->font_load = load_font; fp->font_data->vfbd_width = fh.fh_width; fp->font_data->vfbd_height = fh.fh_height; if (STAILQ_EMPTY(&fonts)) { STAILQ_INSERT_HEAD(&fonts, fp, font_next); return (true); } previous = NULL; size = fp->font_data->vfbd_width * fp->font_data->vfbd_height; STAILQ_FOREACH(entry, &fonts, font_next) { vt_font_bitmap_data_t *bd; bd = entry->font_data; /* Should fp be inserted before the entry? */ if (size > bd->vfbd_width * bd->vfbd_height) { if (previous == NULL) { STAILQ_INSERT_HEAD(&fonts, fp, font_next); } else { STAILQ_INSERT_AFTER(&fonts, previous, fp, font_next); } return (true); } next = STAILQ_NEXT(entry, font_next); if (next == NULL || size > next->font_data->vfbd_width * next->font_data->vfbd_height) { STAILQ_INSERT_AFTER(&fonts, entry, fp, font_next); return (true); } previous = entry; } return (true); } static int font_set(struct env_var *ev __unused, int flags __unused, const void *value) { struct fontlist *fl; char *eptr; unsigned long x = 0, y = 0; /* * Attempt to extract values from "XxY" string. In case of error, * we have unmaching glyph dimensions and will just output the * available values. */ if (value != NULL) { x = strtoul(value, &eptr, 10); if (*eptr == 'x') y = strtoul(eptr + 1, &eptr, 10); } STAILQ_FOREACH(fl, &fonts, font_next) { if (fl->font_data->vfbd_width == x && fl->font_data->vfbd_height == y) break; } if (fl != NULL) { /* Reset any FONT_MANUAL flag. */ reset_font_flags(); /* Mark this font manually loaded */ fl->font_flags = FONT_MANUAL; cons_update_mode(gfx_state.tg_fb_type != FB_TEXT); return (CMD_OK); } printf("Available fonts:\n"); STAILQ_FOREACH(fl, &fonts, font_next) { printf(" %dx%d\n", fl->font_data->vfbd_width, fl->font_data->vfbd_height); } return (CMD_OK); } void bios_text_font(bool use_vga_font) { if (use_vga_font) (void) insert_font(VGA_8X16_FONT, FONT_MANUAL); else (void) insert_font(DEFAULT_8X16_FONT, FONT_MANUAL); } void autoload_font(bool bios) { struct name_list *nl; struct name_entry *np; nl = read_list("/boot/fonts/INDEX.fonts"); if (nl == NULL) return; while (!SLIST_EMPTY(nl)) { np = SLIST_FIRST(nl); SLIST_REMOVE_HEAD(nl, n_entry); if (insert_font(np->n_name, FONT_AUTO) == false) printf("failed to add font: %s\n", np->n_name); free(np->n_name); free(np); } /* * If vga text mode was requested, load vga.font (8x16 bold) font. */ if (bios) { bios_text_font(true); } (void) cons_update_mode(gfx_state.tg_fb_type != FB_TEXT); } COMMAND_SET(load_font, "loadfont", "load console font from file", command_font); static int command_font(int argc, char *argv[]) { int i, c, rc; struct fontlist *fl; vt_font_bitmap_data_t *bd; bool list; list = false; optind = 1; optreset = 1; rc = CMD_OK; while ((c = getopt(argc, argv, "l")) != -1) { switch (c) { case 'l': list = true; break; case '?': default: return (CMD_ERROR); } } argc -= optind; argv += optind; if (argc > 1 || (list && argc != 0)) { printf("Usage: loadfont [-l] | [file.fnt]\n"); return (CMD_ERROR); } if (list) { STAILQ_FOREACH(fl, &fonts, font_next) { printf("font %s: %dx%d%s\n", fl->font_name, fl->font_data->vfbd_width, fl->font_data->vfbd_height, fl->font_data->vfbd_font == NULL? "" : " loaded"); } return (CMD_OK); } /* Clear scren */ cons_clear(); if (argc == 1) { char *name = argv[0]; if (insert_font(name, FONT_MANUAL) == false) { printf("loadfont error: failed to load: %s\n", name); return (CMD_ERROR); } (void) cons_update_mode(gfx_state.tg_fb_type != FB_TEXT); return (CMD_OK); } if (argc == 0) { /* * Walk entire font list, release any loaded font, and set * autoload flag. The font list does have at least the builtin * default font. */ STAILQ_FOREACH(fl, &fonts, font_next) { if (fl->font_data->vfbd_font != NULL) { bd = fl->font_data; /* * Note the setup_font() is releasing * font bytes. */ for (i = 0; i < VFNT_MAPS; i++) free(bd->vfbd_font->vf_map[i]); free(fl->font_data->vfbd_font); fl->font_data->vfbd_font = NULL; fl->font_data->vfbd_uncompressed_size = 0; fl->font_flags = FONT_AUTO; } } (void) cons_update_mode(gfx_state.tg_fb_type != FB_TEXT); } return (rc); } bool gfx_get_edid_resolution(struct vesa_edid_info *edid, edid_res_list_t *res) { struct resolution *rp, *p; /* * Walk detailed timings tables (4). */ if ((edid->display.supported_features & EDID_FEATURE_PREFERRED_TIMING_MODE) != 0) { /* Walk detailed timing descriptors (4) */ for (int i = 0; i < DET_TIMINGS; i++) { /* * Reserved value 0 is not used for display decriptor. */ if (edid->detailed_timings[i].pixel_clock == 0) continue; if ((rp = malloc(sizeof(*rp))) == NULL) continue; rp->width = GET_EDID_INFO_WIDTH(edid, i); rp->height = GET_EDID_INFO_HEIGHT(edid, i); if (rp->width > 0 && rp->width <= EDID_MAX_PIXELS && rp->height > 0 && rp->height <= EDID_MAX_LINES) TAILQ_INSERT_TAIL(res, rp, next); else free(rp); } } /* * Walk standard timings list (8). */ for (int i = 0; i < STD_TIMINGS; i++) { /* Is this field unused? */ if (edid->standard_timings[i] == 0x0101) continue; if ((rp = malloc(sizeof(*rp))) == NULL) continue; rp->width = HSIZE(edid->standard_timings[i]); switch (RATIO(edid->standard_timings[i])) { case RATIO1_1: rp->height = HSIZE(edid->standard_timings[i]); if (edid->header.version > 1 || edid->header.revision > 2) { rp->height = rp->height * 10 / 16; } break; case RATIO4_3: rp->height = HSIZE(edid->standard_timings[i]) * 3 / 4; break; case RATIO5_4: rp->height = HSIZE(edid->standard_timings[i]) * 4 / 5; break; case RATIO16_9: rp->height = HSIZE(edid->standard_timings[i]) * 9 / 16; break; } /* * Create resolution list in decreasing order, except keep * first entry (preferred timing mode). */ TAILQ_FOREACH(p, res, next) { if (p->width * p->height < rp->width * rp->height) { /* Keep preferred mode first */ if (TAILQ_FIRST(res) == p) TAILQ_INSERT_AFTER(res, p, rp, next); else TAILQ_INSERT_BEFORE(p, rp, next); break; } if (TAILQ_NEXT(p, next) == NULL) { TAILQ_INSERT_TAIL(res, rp, next); break; } } } return (!TAILQ_EMPTY(res)); } diff --git a/stand/common/gfx_fb.h b/stand/common/gfx_fb.h index 04076a2c6d38..ac63d7939cef 100644 --- a/stand/common/gfx_fb.h +++ b/stand/common/gfx_fb.h @@ -1,287 +1,289 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright 2020 Toomas Soome * Copyright 2020 RackTop Systems, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. * * $FreeBSD$ */ #ifndef _GFX_FB_H #define _GFX_FB_H #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #define EDID_MAGIC { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 } struct edid_header { uint8_t header[8]; /* fixed header pattern */ uint16_t manufacturer_id; uint16_t product_code; uint32_t serial_number; uint8_t week_of_manufacture; uint8_t year_of_manufacture; uint8_t version; uint8_t revision; }; struct edid_basic_display_parameters { uint8_t video_input_parameters; uint8_t max_horizontal_image_size; uint8_t max_vertical_image_size; uint8_t display_gamma; uint8_t supported_features; }; struct edid_chromaticity_coordinates { uint8_t red_green_lo; uint8_t blue_white_lo; uint8_t red_x_hi; uint8_t red_y_hi; uint8_t green_x_hi; uint8_t green_y_hi; uint8_t blue_x_hi; uint8_t blue_y_hi; uint8_t white_x_hi; uint8_t white_y_hi; }; struct edid_detailed_timings { uint16_t pixel_clock; uint8_t horizontal_active_lo; uint8_t horizontal_blanking_lo; uint8_t horizontal_hi; uint8_t vertical_active_lo; uint8_t vertical_blanking_lo; uint8_t vertical_hi; uint8_t horizontal_sync_offset_lo; uint8_t horizontal_sync_pulse_width_lo; uint8_t vertical_sync_lo; uint8_t sync_hi; uint8_t horizontal_image_size_lo; uint8_t vertical_image_size_lo; uint8_t image_size_hi; uint8_t horizontal_border; uint8_t vertical_border; uint8_t features; }; struct vesa_edid_info { struct edid_header header; struct edid_basic_display_parameters display; #define EDID_FEATURE_PREFERRED_TIMING_MODE (1 << 1) struct edid_chromaticity_coordinates chromaticity; uint8_t established_timings_1; uint8_t established_timings_2; uint8_t manufacturer_reserved_timings; uint16_t standard_timings[8]; struct edid_detailed_timings detailed_timings[4]; uint8_t number_of_extensions; uint8_t checksum; } __packed; +extern struct vesa_edid_info *edid_info; + #define STD_TIMINGS 8 #define DET_TIMINGS 4 #define HSIZE(x) (((x & 0xff) + 31) * 8) #define RATIO(x) ((x & 0xC000) >> 14) #define RATIO1_1 0 /* EDID Ver. 1.3 redefined this */ #define RATIO16_10 RATIO1_1 #define RATIO4_3 1 #define RATIO5_4 2 #define RATIO16_9 3 /* * Number of pixels and lines is 12-bit int, valid values 0-4095. */ #define EDID_MAX_PIXELS 4095 #define EDID_MAX_LINES 4095 #define GET_EDID_INFO_WIDTH(edid_info, timings_num) \ ((edid_info)->detailed_timings[(timings_num)].horizontal_active_lo | \ (((uint32_t)(edid_info)->detailed_timings[(timings_num)].horizontal_hi & \ 0xf0) << 4)) #define GET_EDID_INFO_HEIGHT(edid_info, timings_num) \ ((edid_info)->detailed_timings[(timings_num)].vertical_active_lo | \ (((uint32_t)(edid_info)->detailed_timings[(timings_num)].vertical_hi & \ 0xf0) << 4)) struct resolution { uint32_t width; uint32_t height; TAILQ_ENTRY(resolution) next; }; typedef TAILQ_HEAD(edid_resolution, resolution) edid_res_list_t; struct vesa_flat_panel_info { uint16_t HSize; /* Horizontal Size in Pixels */ uint16_t VSize; /* Vertical Size in Lines */ uint16_t FPType; /* Flat Panel Type */ uint8_t RedBPP; /* Red Bits Per Primary */ uint8_t GreenBPP; /* Green Bits Per Primary */ uint8_t BlueBPP; /* Blue Bits Per Primary */ uint8_t ReservedBPP; /* Reserved Bits Per Primary */ uint32_t RsvdOffScrnMemSize; /* Size in KB of Offscreen Memory */ uint32_t RsvdOffScrnMemPtr; /* Pointer to reserved offscreen memory */ uint8_t Reserved[14]; /* remainder of FPInfo */ } __packed; #define COLOR_FORMAT_VGA 0 #define COLOR_FORMAT_RGB 1 #define NCOLORS 16 #define NCMAP 256 extern uint32_t cmap[NCMAP]; /* * VT_FB_MAX_WIDTH and VT_FB_MAX_HEIGHT are dimensions from where * we will not auto select smaller font than 8x16. * See also sys/dev/vt/vt.h */ #ifndef VT_FB_MAX_WIDTH #define VT_FB_MAX_WIDTH 4096 #endif #ifndef VT_FB_MAX_HEIGHT #define VT_FB_MAX_HEIGHT 2400 #endif enum FB_TYPE { FB_TEXT = -1, FB_GOP, FB_UGA, FB_VBE }; enum COLOR_TYPE { CT_INDEXED, CT_RGB }; struct gen_fb { uint64_t fb_addr; uint64_t fb_size; uint32_t fb_height; uint32_t fb_width; uint32_t fb_stride; uint32_t fb_mask_red; uint32_t fb_mask_green; uint32_t fb_mask_blue; uint32_t fb_mask_reserved; uint32_t fb_bpp; }; typedef struct teken_gfx { enum FB_TYPE tg_fb_type; enum COLOR_TYPE tg_ctype; unsigned tg_mode; teken_t tg_teken; /* Teken core */ teken_pos_t tg_cursor; /* Where cursor was drawn */ bool tg_cursor_visible; teken_pos_t tg_tp; /* Terminal dimensions */ teken_pos_t tg_origin; /* Point of origin in pixels */ uint8_t *tg_glyph; /* Memory for glyph */ size_t tg_glyph_size; struct vt_font tg_font; struct gen_fb tg_fb; teken_funcs_t *tg_functions; void *tg_private; bool tg_kernel_supported; /* Loaded kernel is supported */ } teken_gfx_t; extern font_list_t fonts; extern teken_gfx_t gfx_state; typedef enum { GfxFbBltVideoFill, GfxFbBltVideoToBltBuffer, GfxFbBltBufferToVideo, GfxFbBltVideoToVideo, GfxFbBltOperationMax, } GFXFB_BLT_OPERATION; int gfxfb_blt(void *, GFXFB_BLT_OPERATION, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); int generate_cons_palette(uint32_t *, int, uint32_t, int, uint32_t, int, uint32_t, int); bool console_update_mode(bool); void setup_font(teken_gfx_t *, teken_unit_t, teken_unit_t); uint8_t *font_lookup(const struct vt_font *, teken_char_t, const teken_attr_t *); void bios_text_font(bool); /* teken callbacks. */ tf_cursor_t gfx_fb_cursor; tf_putchar_t gfx_fb_putchar; tf_fill_t gfx_fb_fill; tf_copy_t gfx_fb_copy; tf_param_t gfx_fb_param; /* Screen buffer element */ struct text_pixel { teken_char_t c; teken_attr_t a; }; extern const int cons_to_vga_colors[NCOLORS]; /* Screen buffer to track changes on the terminal screen. */ extern struct text_pixel *screen_buffer; bool is_same_pixel(struct text_pixel *, struct text_pixel *); bool gfx_get_edid_resolution(struct vesa_edid_info *, edid_res_list_t *); void gfx_framework_init(void); void gfx_fb_cons_display(uint32_t, uint32_t, uint32_t, uint32_t, void *); void gfx_fb_setpixel(uint32_t, uint32_t); void gfx_fb_drawrect(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); void gfx_term_drawrect(uint32_t, uint32_t, uint32_t, uint32_t); void gfx_fb_line(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); void gfx_fb_bezier(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); #define FL_PUTIMAGE_BORDER 0x1 #define FL_PUTIMAGE_NOSCROLL 0x2 #define FL_PUTIMAGE_DEBUG 0x80 int gfx_fb_putimage(png_t *, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); bool gfx_parse_mode_str(char *, int *, int *, int *); void term_image_display(teken_gfx_t *, const teken_rect_t *); void reset_font_flags(void); #ifdef __cplusplus } #endif #endif /* _GFX_FB_H */ diff --git a/stand/efi/libefi/efi_console.c b/stand/efi/libefi/efi_console.c index 3cbd121c41da..0c40b362f276 100644 --- a/stand/efi/libefi/efi_console.c +++ b/stand/efi/libefi/efi_console.c @@ -1,1384 +1,1395 @@ /*- * Copyright (c) 2000 Doug Rabson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include "bootstrap.h" extern EFI_GUID gop_guid; static EFI_GUID simple_input_ex_guid = EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID; static SIMPLE_TEXT_OUTPUT_INTERFACE *conout; static SIMPLE_INPUT_INTERFACE *conin; static EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *coninex; static bool efi_started; static int mode; /* Does ConOut have serial console? */ static uint32_t utf8_left; static uint32_t utf8_partial; #ifdef TERM_EMU #define DEFAULT_FGCOLOR EFI_LIGHTGRAY #define DEFAULT_BGCOLOR EFI_BLACK #define MAXARGS 8 static int args[MAXARGS], argc; static int fg_c, bg_c, curx, cury; static int esc; void get_pos(int *x, int *y); void curs_move(int *_x, int *_y, int x, int y); static void CL(int); void HO(void); void end_term(void); #endif #define TEXT_ROWS 24 #define TEXT_COLS 80 static tf_bell_t efi_cons_bell; static tf_cursor_t efi_text_cursor; static tf_putchar_t efi_text_putchar; static tf_fill_t efi_text_fill; static tf_copy_t efi_text_copy; static tf_param_t efi_text_param; static tf_respond_t efi_cons_respond; static teken_funcs_t tf = { .tf_bell = efi_cons_bell, .tf_cursor = efi_text_cursor, .tf_putchar = efi_text_putchar, .tf_fill = efi_text_fill, .tf_copy = efi_text_copy, .tf_param = efi_text_param, .tf_respond = efi_cons_respond, }; static teken_funcs_t tfx = { .tf_bell = efi_cons_bell, .tf_cursor = gfx_fb_cursor, .tf_putchar = gfx_fb_putchar, .tf_fill = gfx_fb_fill, .tf_copy = gfx_fb_copy, .tf_param = gfx_fb_param, .tf_respond = efi_cons_respond, }; #define KEYBUFSZ 10 static unsigned keybuf[KEYBUFSZ]; /* keybuf for extended codes */ static int key_pending; static const unsigned char teken_color_to_efi_color[16] = { EFI_BLACK, EFI_RED, EFI_GREEN, EFI_BROWN, EFI_BLUE, EFI_MAGENTA, EFI_CYAN, EFI_LIGHTGRAY, EFI_DARKGRAY, EFI_LIGHTRED, EFI_LIGHTGREEN, EFI_YELLOW, EFI_LIGHTBLUE, EFI_LIGHTMAGENTA, EFI_LIGHTCYAN, EFI_WHITE }; static void efi_cons_probe(struct console *); static int efi_cons_init(int); void efi_cons_putchar(int); int efi_cons_getchar(void); void efi_cons_efiputchar(int); int efi_cons_poll(void); static void cons_draw_frame(teken_attr_t *); struct console efi_console = { "efi", "EFI console", C_WIDEOUT, efi_cons_probe, efi_cons_init, efi_cons_putchar, efi_cons_getchar, efi_cons_poll }; /* * This function is used to mark a rectangular image area so the scrolling * will know we need to copy the data from there. */ void term_image_display(teken_gfx_t *state, const teken_rect_t *r) { teken_pos_t p; int idx; if (screen_buffer == NULL) return; for (p.tp_row = r->tr_begin.tp_row; p.tp_row < r->tr_end.tp_row; p.tp_row++) { for (p.tp_col = r->tr_begin.tp_col; p.tp_col < r->tr_end.tp_col; p.tp_col++) { idx = p.tp_col + p.tp_row * state->tg_tp.tp_col; if (idx >= state->tg_tp.tp_col * state->tg_tp.tp_row) return; screen_buffer[idx].a.ta_format |= TF_IMAGE; } } } /* * Not implemented. */ static void efi_cons_bell(void *s __unused) { } static void efi_text_cursor(void *arg, const teken_pos_t *p) { teken_gfx_t *state = arg; UINTN col, row; row = p->tp_row; if (p->tp_row >= state->tg_tp.tp_row) row = state->tg_tp.tp_row - 1; col = p->tp_col; if (p->tp_col >= state->tg_tp.tp_col) col = state->tg_tp.tp_col - 1; conout->SetCursorPosition(conout, col, row); } static void efi_text_printchar(teken_gfx_t *state, const teken_pos_t *p, bool autoscroll) { UINTN a, attr; struct text_pixel *px; teken_color_t fg, bg, tmp; px = screen_buffer + p->tp_col + p->tp_row * state->tg_tp.tp_col; a = conout->Mode->Attribute; fg = teken_256to16(px->a.ta_fgcolor); bg = teken_256to16(px->a.ta_bgcolor); if (px->a.ta_format & TF_BOLD) fg |= TC_LIGHT; if (px->a.ta_format & TF_BLINK) bg |= TC_LIGHT; if (px->a.ta_format & TF_REVERSE) { tmp = fg; fg = bg; bg = tmp; } attr = EFI_TEXT_ATTR(teken_color_to_efi_color[fg], teken_color_to_efi_color[bg] & 0x7); conout->SetCursorPosition(conout, p->tp_col, p->tp_row); /* to prevent autoscroll, skip print of lower right char */ if (!autoscroll && p->tp_row == state->tg_tp.tp_row - 1 && p->tp_col == state->tg_tp.tp_col - 1) return; (void) conout->SetAttribute(conout, attr); efi_cons_efiputchar(px->c); (void) conout->SetAttribute(conout, a); } static void efi_text_putchar(void *s, const teken_pos_t *p, teken_char_t c, const teken_attr_t *a) { teken_gfx_t *state = s; EFI_STATUS status; int idx; idx = p->tp_col + p->tp_row * state->tg_tp.tp_col; if (idx >= state->tg_tp.tp_col * state->tg_tp.tp_row) return; screen_buffer[idx].c = c; screen_buffer[idx].a = *a; efi_text_printchar(s, p, false); } static void efi_text_fill(void *arg, const teken_rect_t *r, teken_char_t c, const teken_attr_t *a) { teken_gfx_t *state = arg; teken_pos_t p; if (state->tg_cursor_visible) conout->EnableCursor(conout, FALSE); for (p.tp_row = r->tr_begin.tp_row; p.tp_row < r->tr_end.tp_row; p.tp_row++) for (p.tp_col = r->tr_begin.tp_col; p.tp_col < r->tr_end.tp_col; p.tp_col++) efi_text_putchar(state, &p, c, a); if (state->tg_cursor_visible) conout->EnableCursor(conout, TRUE); } static void efi_text_copy_line(teken_gfx_t *state, int ncol, teken_pos_t *s, teken_pos_t *d, bool scroll) { unsigned soffset, doffset; teken_pos_t sp, dp; int x; soffset = s->tp_col + s->tp_row * state->tg_tp.tp_col; doffset = d->tp_col + d->tp_row * state->tg_tp.tp_col; sp = *s; dp = *d; for (x = 0; x < ncol; x++) { sp.tp_col = s->tp_col + x; dp.tp_col = d->tp_col + x; if (!is_same_pixel(&screen_buffer[soffset + x], &screen_buffer[doffset + x])) { screen_buffer[doffset + x] = screen_buffer[soffset + x]; if (!scroll) efi_text_printchar(state, &dp, false); } else if (scroll) { /* Draw last char and trigger scroll. */ if (dp.tp_col + 1 == state->tg_tp.tp_col && dp.tp_row + 1 == state->tg_tp.tp_row) { efi_text_printchar(state, &dp, true); } } } } static void efi_text_copy(void *arg, const teken_rect_t *r, const teken_pos_t *p) { teken_gfx_t *state = arg; unsigned doffset, soffset; teken_pos_t d, s; int nrow, ncol, x, y; /* Has to be signed - >= 0 comparison */ bool scroll = false; /* * Copying is a little tricky. We must make sure we do it in * correct order, to make sure we don't overwrite our own data. */ nrow = r->tr_end.tp_row - r->tr_begin.tp_row; ncol = r->tr_end.tp_col - r->tr_begin.tp_col; /* * Check if we do copy whole screen. */ if (p->tp_row == 0 && p->tp_col == 0 && nrow == state->tg_tp.tp_row - 2 && ncol == state->tg_tp.tp_col - 2) scroll = true; soffset = r->tr_begin.tp_col + r->tr_begin.tp_row * state->tg_tp.tp_col; doffset = p->tp_col + p->tp_row * state->tg_tp.tp_col; /* remove the cursor */ if (state->tg_cursor_visible) conout->EnableCursor(conout, FALSE); /* * Copy line by line. */ if (doffset <= soffset) { s = r->tr_begin; d = *p; for (y = 0; y < nrow; y++) { s.tp_row = r->tr_begin.tp_row + y; d.tp_row = p->tp_row + y; efi_text_copy_line(state, ncol, &s, &d, scroll); } } else { for (y = nrow - 1; y >= 0; y--) { s.tp_row = r->tr_begin.tp_row + y; d.tp_row = p->tp_row + y; efi_text_copy_line(state, ncol, &s, &d, false); } } /* display the cursor */ if (state->tg_cursor_visible) conout->EnableCursor(conout, TRUE); } static void efi_text_param(void *arg, int cmd, unsigned int value) { teken_gfx_t *state = arg; switch (cmd) { case TP_SETLOCALCURSOR: /* * 0 means normal (usually block), 1 means hidden, and * 2 means blinking (always block) for compatibility with * syscons. We don't support any changes except hiding, * so must map 2 to 0. */ value = (value == 1) ? 0 : 1; /* FALLTHROUGH */ case TP_SHOWCURSOR: if (value != 0) { conout->EnableCursor(conout, TRUE); state->tg_cursor_visible = true; } else { conout->EnableCursor(conout, FALSE); state->tg_cursor_visible = false; } break; default: /* Not yet implemented */ break; } } /* * Not implemented. */ static void efi_cons_respond(void *s __unused, const void *buf __unused, size_t len __unused) { } /* * Set up conin/conout/coninex to make sure we have input ready. */ static void efi_cons_probe(struct console *cp) { EFI_STATUS status; conout = ST->ConOut; conin = ST->ConIn; /* * Call SetMode to work around buggy firmware. */ status = conout->SetMode(conout, conout->Mode->Mode); if (coninex == NULL) { status = BS->OpenProtocol(ST->ConsoleInHandle, &simple_input_ex_guid, (void **)&coninex, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (status != EFI_SUCCESS) coninex = NULL; } cp->c_flags |= C_PRESENTIN | C_PRESENTOUT; } static bool color_name_to_teken(const char *name, int *val) { if (strcasecmp(name, "black") == 0) { *val = TC_BLACK; return (true); } if (strcasecmp(name, "red") == 0) { *val = TC_RED; return (true); } if (strcasecmp(name, "green") == 0) { *val = TC_GREEN; return (true); } if (strcasecmp(name, "brown") == 0) { *val = TC_BROWN; return (true); } if (strcasecmp(name, "blue") == 0) { *val = TC_BLUE; return (true); } if (strcasecmp(name, "magenta") == 0) { *val = TC_MAGENTA; return (true); } if (strcasecmp(name, "cyan") == 0) { *val = TC_CYAN; return (true); } if (strcasecmp(name, "white") == 0) { *val = TC_WHITE; return (true); } return (false); } static int efi_set_colors(struct env_var *ev, int flags, const void *value) { int val = 0; char buf[2]; const void *evalue; const teken_attr_t *ap; teken_attr_t a; if (value == NULL) return (CMD_OK); if (color_name_to_teken(value, &val)) { snprintf(buf, sizeof (buf), "%d", val); evalue = buf; } else { char *end; errno = 0; val = (int)strtol(value, &end, 0); if (errno != 0 || *end != '\0') { printf("Allowed values are either ansi color name or " "number from range [0-7].\n"); return (CMD_OK); } evalue = value; } ap = teken_get_defattr(&gfx_state.tg_teken); a = *ap; if (strcmp(ev->ev_name, "teken.fg_color") == 0) { /* is it already set? */ if (ap->ta_fgcolor == val) return (CMD_OK); a.ta_fgcolor = val; } if (strcmp(ev->ev_name, "teken.bg_color") == 0) { /* is it already set? */ if (ap->ta_bgcolor == val) return (CMD_OK); a.ta_bgcolor = val; } /* Improve visibility */ if (a.ta_bgcolor == TC_WHITE) a.ta_bgcolor |= TC_LIGHT; teken_set_defattr(&gfx_state.tg_teken, &a); cons_draw_frame(&a); env_setenv(ev->ev_name, flags | EV_NOHOOK, evalue, NULL, NULL); teken_input(&gfx_state.tg_teken, "\e[2J", 4); return (CMD_OK); } #ifdef TERM_EMU /* Get cursor position. */ void get_pos(int *x, int *y) { *x = conout->Mode->CursorColumn; *y = conout->Mode->CursorRow; } /* Move cursor to x rows and y cols (0-based). */ void curs_move(int *_x, int *_y, int x, int y) { conout->SetCursorPosition(conout, x, y); if (_x != NULL) *_x = conout->Mode->CursorColumn; if (_y != NULL) *_y = conout->Mode->CursorRow; } /* Clear internal state of the terminal emulation code. */ void end_term(void) { esc = 0; argc = -1; } #endif static void efi_cons_rawputchar(int c) { int i; UINTN x, y; conout->QueryMode(conout, conout->Mode->Mode, &x, &y); if (c == '\t') { int n; n = 8 - ((conout->Mode->CursorColumn + 8) % 8); for (i = 0; i < n; i++) efi_cons_rawputchar(' '); } else { #ifndef TERM_EMU if (c == '\n') efi_cons_efiputchar('\r'); efi_cons_efiputchar(c); #else switch (c) { case '\r': curx = 0; efi_cons_efiputchar('\r'); return; case '\n': efi_cons_efiputchar('\n'); efi_cons_efiputchar('\r'); cury++; if (cury >= y) cury--; curx = 0; return; case '\b': if (curx > 0) { efi_cons_efiputchar('\b'); curx--; } return; default: efi_cons_efiputchar(c); curx++; if (curx > x-1) { curx = 0; cury++; } if (cury > y-1) { curx = 0; cury--; } } #endif } conout->EnableCursor(conout, TRUE); } #ifdef TERM_EMU /* Gracefully exit ESC-sequence processing in case of misunderstanding. */ static void bail_out(int c) { char buf[16], *ch; int i; if (esc) { efi_cons_rawputchar('\033'); if (esc != '\033') efi_cons_rawputchar(esc); for (i = 0; i <= argc; ++i) { sprintf(buf, "%d", args[i]); ch = buf; while (*ch) efi_cons_rawputchar(*ch++); } } efi_cons_rawputchar(c); end_term(); } /* Clear display from current position to end of screen. */ static void CD(void) { int i; UINTN x, y; get_pos(&curx, &cury); if (curx == 0 && cury == 0) { conout->ClearScreen(conout); end_term(); return; } conout->QueryMode(conout, conout->Mode->Mode, &x, &y); CL(0); /* clear current line from cursor to end */ for (i = cury + 1; i < y-1; i++) { curs_move(NULL, NULL, 0, i); CL(0); } curs_move(NULL, NULL, curx, cury); end_term(); } /* * Absolute cursor move to args[0] rows and args[1] columns * (the coordinates are 1-based). */ static void CM(void) { if (args[0] > 0) args[0]--; if (args[1] > 0) args[1]--; curs_move(&curx, &cury, args[1], args[0]); end_term(); } /* Home cursor (left top corner), also called from mode command. */ void HO(void) { argc = 1; args[0] = args[1] = 1; CM(); } /* Clear line from current position to end of line */ static void CL(int direction) { int i, len; UINTN x, y; CHAR16 *line; conout->QueryMode(conout, conout->Mode->Mode, &x, &y); switch (direction) { case 0: /* from cursor to end */ len = x - curx + 1; break; case 1: /* from beginning to cursor */ len = curx; break; case 2: /* entire line */ len = x; break; default: /* NOTREACHED */ __unreachable(); } if (cury == y - 1) len--; line = malloc(len * sizeof (CHAR16)); if (line == NULL) { printf("out of memory\n"); return; } for (i = 0; i < len; i++) line[i] = ' '; line[len-1] = 0; if (direction != 0) curs_move(NULL, NULL, 0, cury); conout->OutputString(conout, line); /* restore cursor position */ curs_move(NULL, NULL, curx, cury); free(line); end_term(); } static void get_arg(int c) { if (argc < 0) argc = 0; args[argc] *= 10; args[argc] += c - '0'; } #endif /* Emulate basic capabilities of cons25 terminal */ static void efi_term_emu(int c) { #ifdef TERM_EMU static int ansi_col[] = { 0, 4, 2, 6, 1, 5, 3, 7 }; int t, i; EFI_STATUS status; switch (esc) { case 0: switch (c) { case '\033': esc = c; break; default: efi_cons_rawputchar(c); break; } break; case '\033': switch (c) { case '[': esc = c; args[0] = 0; argc = -1; break; default: bail_out(c); break; } break; case '[': switch (c) { case ';': if (argc < 0) argc = 0; else if (argc + 1 >= MAXARGS) bail_out(c); else args[++argc] = 0; break; case 'H': /* ho = \E[H */ if (argc < 0) HO(); else if (argc == 1) CM(); else bail_out(c); break; case 'J': /* cd = \E[J */ if (argc < 0) CD(); else bail_out(c); break; case 'm': if (argc < 0) { fg_c = DEFAULT_FGCOLOR; bg_c = DEFAULT_BGCOLOR; } for (i = 0; i <= argc; ++i) { switch (args[i]) { case 0: /* back to normal */ fg_c = DEFAULT_FGCOLOR; bg_c = DEFAULT_BGCOLOR; break; case 1: /* bold */ fg_c |= 0x8; break; case 4: /* underline */ case 5: /* blink */ bg_c |= 0x8; break; case 7: /* reverse */ t = fg_c; fg_c = bg_c; bg_c = t; break; case 22: /* normal intensity */ fg_c &= ~0x8; break; case 24: /* not underline */ case 25: /* not blinking */ bg_c &= ~0x8; break; case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37: fg_c = ansi_col[args[i] - 30]; break; case 39: /* normal */ fg_c = DEFAULT_FGCOLOR; break; case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47: bg_c = ansi_col[args[i] - 40]; break; case 49: /* normal */ bg_c = DEFAULT_BGCOLOR; break; } } conout->SetAttribute(conout, EFI_TEXT_ATTR(fg_c, bg_c)); end_term(); break; default: if (isdigit(c)) get_arg(c); else bail_out(c); break; } break; default: bail_out(c); break; } #else efi_cons_rawputchar(c); #endif } static int env_screen_nounset(struct env_var *ev __unused) { if (gfx_state.tg_fb_type == FB_TEXT) return (0); return (EPERM); } static void cons_draw_frame(teken_attr_t *a) { teken_attr_t attr = *a; teken_color_t fg = a->ta_fgcolor; attr.ta_fgcolor = attr.ta_bgcolor; teken_set_defattr(&gfx_state.tg_teken, &attr); gfx_fb_drawrect(0, 0, gfx_state.tg_fb.fb_width, gfx_state.tg_origin.tp_row, 1); gfx_fb_drawrect(0, gfx_state.tg_fb.fb_height - gfx_state.tg_origin.tp_row - 1, gfx_state.tg_fb.fb_width, gfx_state.tg_fb.fb_height, 1); gfx_fb_drawrect(0, gfx_state.tg_origin.tp_row, gfx_state.tg_origin.tp_col, gfx_state.tg_fb.fb_height - gfx_state.tg_origin.tp_row - 1, 1); gfx_fb_drawrect( gfx_state.tg_fb.fb_width - gfx_state.tg_origin.tp_col - 1, gfx_state.tg_origin.tp_row, gfx_state.tg_fb.fb_width, gfx_state.tg_fb.fb_height, 1); attr.ta_fgcolor = fg; teken_set_defattr(&gfx_state.tg_teken, &attr); } bool cons_update_mode(bool use_gfx_mode) { UINTN cols, rows; const teken_attr_t *a; teken_attr_t attr; EFI_STATUS status; char env[10], *ptr; if (!efi_started) return (false); /* * Despite the use_gfx_mode, we want to make sure we call * efi_find_framebuffer(). This will populate the fb data, * which will be passed to kernel. */ if (efi_find_framebuffer(&gfx_state) == 0 && use_gfx_mode) { int roff, goff, boff; roff = ffs(gfx_state.tg_fb.fb_mask_red) - 1; goff = ffs(gfx_state.tg_fb.fb_mask_green) - 1; boff = ffs(gfx_state.tg_fb.fb_mask_blue) - 1; (void) generate_cons_palette(cmap, COLOR_FORMAT_RGB, gfx_state.tg_fb.fb_mask_red >> roff, roff, gfx_state.tg_fb.fb_mask_green >> goff, goff, gfx_state.tg_fb.fb_mask_blue >> boff, boff); } else { /* * Either text mode was asked by user or we failed to * find frame buffer. */ gfx_state.tg_fb_type = FB_TEXT; } status = conout->QueryMode(conout, conout->Mode->Mode, &cols, &rows); if (EFI_ERROR(status) || cols * rows == 0) { cols = TEXT_COLS; rows = TEXT_ROWS; } /* * When we have serial port listed in ConOut, use pre-teken emulator, * if built with. * The problem is, we can not output text on efi and comconsole when * efi also has comconsole bound. But then again, we need to have * terminal emulator for efi text mode to support the menu. * While teken is too expensive to be used on serial console, the * pre-teken emulator is light enough to be used on serial console. * * When doing multiple consoles (both serial and video), * also just use the old emulator. RB_MULTIPLE also implies * we're using a serial console. */ mode = parse_uefi_con_out(); if ((mode & (RB_SERIAL | RB_MULTIPLE)) == 0) { conout->EnableCursor(conout, FALSE); gfx_state.tg_cursor_visible = false; if (gfx_state.tg_fb_type == FB_TEXT) { gfx_state.tg_functions = &tf; /* ensure the following are not set for text mode */ unsetenv("screen.height"); unsetenv("screen.width"); unsetenv("screen.depth"); } else { uint32_t fb_height, fb_width; fb_height = gfx_state.tg_fb.fb_height; fb_width = gfx_state.tg_fb.fb_width; /* * setup_font() can adjust terminal size. - * Note, we do use UEFI terminal dimensions first, - * this is because the font selection will attempt - * to achieve at least this terminal dimension and - * we do not end up with too small font. + * We can see two kind of bad happening. + * We either can get too small console font - requested + * terminal size is large, display resolution is + * large, and we get very small font. + * Or, we can get too large font - requested + * terminal size is small and this will cause large + * font to be selected. + * Now, the setup_font() is updated to consider + * display density and this should give us mostly + * acceptable font. However, the catch is, not all + * display devices will give us display density. + * Still, we do hope, external monitors do - this is + * where the display size will matter the most. + * And for laptop screens, we should still get good + * results by requesting 80x25 terminal. */ - gfx_state.tg_tp.tp_row = rows; - gfx_state.tg_tp.tp_col = cols; + gfx_state.tg_tp.tp_row = 25; + gfx_state.tg_tp.tp_col = 80; setup_font(&gfx_state, fb_height, fb_width); rows = gfx_state.tg_tp.tp_row; cols = gfx_state.tg_tp.tp_col; /* Point of origin in pixels. */ gfx_state.tg_origin.tp_row = (fb_height - (rows * gfx_state.tg_font.vf_height)) / 2; gfx_state.tg_origin.tp_col = (fb_width - (cols * gfx_state.tg_font.vf_width)) / 2; /* UEFI gop has depth 32. */ gfx_state.tg_glyph_size = gfx_state.tg_font.vf_height * gfx_state.tg_font.vf_width * 4; free(gfx_state.tg_glyph); gfx_state.tg_glyph = malloc(gfx_state.tg_glyph_size); if (gfx_state.tg_glyph == NULL) return (false); gfx_state.tg_functions = &tfx; snprintf(env, sizeof (env), "%d", fb_height); env_setenv("screen.height", EV_VOLATILE | EV_NOHOOK, env, env_noset, env_screen_nounset); snprintf(env, sizeof (env), "%d", fb_width); env_setenv("screen.width", EV_VOLATILE | EV_NOHOOK, env, env_noset, env_screen_nounset); snprintf(env, sizeof (env), "%d", gfx_state.tg_fb.fb_bpp); env_setenv("screen.depth", EV_VOLATILE | EV_NOHOOK, env, env_noset, env_screen_nounset); } /* Record our terminal screen size. */ gfx_state.tg_tp.tp_row = rows; gfx_state.tg_tp.tp_col = cols; teken_init(&gfx_state.tg_teken, gfx_state.tg_functions, &gfx_state); free(screen_buffer); screen_buffer = malloc(rows * cols * sizeof(*screen_buffer)); if (screen_buffer != NULL) { teken_set_winsize(&gfx_state.tg_teken, &gfx_state.tg_tp); a = teken_get_defattr(&gfx_state.tg_teken); attr = *a; /* * On first run, we set up the efi_set_colors() * callback. If the env is already set, we * pick up fg and bg color values from the environment. */ ptr = getenv("teken.fg_color"); if (ptr != NULL) { attr.ta_fgcolor = strtol(ptr, NULL, 10); ptr = getenv("teken.bg_color"); attr.ta_bgcolor = strtol(ptr, NULL, 10); teken_set_defattr(&gfx_state.tg_teken, &attr); } else { snprintf(env, sizeof(env), "%d", attr.ta_fgcolor); env_setenv("teken.fg_color", EV_VOLATILE, env, efi_set_colors, env_nounset); snprintf(env, sizeof(env), "%d", attr.ta_bgcolor); env_setenv("teken.bg_color", EV_VOLATILE, env, efi_set_colors, env_nounset); } } } if (screen_buffer == NULL) { conout->EnableCursor(conout, TRUE); #ifdef TERM_EMU conout->SetAttribute(conout, EFI_TEXT_ATTR(DEFAULT_FGCOLOR, DEFAULT_BGCOLOR)); end_term(); get_pos(&curx, &cury); curs_move(&curx, &cury, curx, cury); fg_c = DEFAULT_FGCOLOR; bg_c = DEFAULT_BGCOLOR; #endif } else { /* Improve visibility */ if (attr.ta_bgcolor == TC_WHITE) attr.ta_bgcolor |= TC_LIGHT; teken_set_defattr(&gfx_state.tg_teken, &attr); /* Draw frame around terminal area. */ cons_draw_frame(&attr); /* * Erase display, this will also fill our screen * buffer. */ teken_input(&gfx_state.tg_teken, "\e[2J", 4); gfx_state.tg_functions->tf_param(&gfx_state, TP_SHOWCURSOR, 1); } snprintf(env, sizeof (env), "%u", (unsigned)rows); setenv("LINES", env, 1); snprintf(env, sizeof (env), "%u", (unsigned)cols); setenv("COLUMNS", env, 1); return (true); } static int efi_cons_init(int arg) { EFI_STATUS status; if (efi_started) return (0); efi_started = true; gfx_framework_init(); if (cons_update_mode(gfx_state.tg_fb_type != FB_TEXT)) return (0); return (1); } static void input_partial(void) { unsigned i; uint32_t c; if (utf8_left == 0) return; for (i = 0; i < sizeof(utf8_partial); i++) { c = (utf8_partial >> (24 - (i << 3))) & 0xff; if (c != 0) efi_term_emu(c); } utf8_left = 0; utf8_partial = 0; } static void input_byte(uint8_t c) { if ((c & 0x80) == 0x00) { /* One-byte sequence. */ input_partial(); efi_term_emu(c); return; } if ((c & 0xe0) == 0xc0) { /* Two-byte sequence. */ input_partial(); utf8_left = 1; utf8_partial = c; return; } if ((c & 0xf0) == 0xe0) { /* Three-byte sequence. */ input_partial(); utf8_left = 2; utf8_partial = c; return; } if ((c & 0xf8) == 0xf0) { /* Four-byte sequence. */ input_partial(); utf8_left = 3; utf8_partial = c; return; } if ((c & 0xc0) == 0x80) { /* Invalid state? */ if (utf8_left == 0) { efi_term_emu(c); return; } utf8_left--; utf8_partial = (utf8_partial << 8) | c; if (utf8_left == 0) { uint32_t v, u; uint8_t b; v = 0; u = utf8_partial; b = (u >> 24) & 0xff; if (b != 0) { /* Four-byte sequence */ v = b & 0x07; b = (u >> 16) & 0xff; v = (v << 6) | (b & 0x3f); b = (u >> 8) & 0xff; v = (v << 6) | (b & 0x3f); b = u & 0xff; v = (v << 6) | (b & 0x3f); } else if ((b = (u >> 16) & 0xff) != 0) { v = b & 0x0f; /* Three-byte sequence */ b = (u >> 8) & 0xff; v = (v << 6) | (b & 0x3f); b = u & 0xff; v = (v << 6) | (b & 0x3f); } else if ((b = (u >> 8) & 0xff) != 0) { v = b & 0x1f; /* Two-byte sequence */ b = u & 0xff; v = (v << 6) | (b & 0x3f); } /* Send unicode char directly to console. */ efi_cons_efiputchar(v); utf8_partial = 0; } return; } /* Anything left is illegal in UTF-8 sequence. */ input_partial(); efi_term_emu(c); } void efi_cons_putchar(int c) { unsigned char ch = c; /* * Don't use Teken when we're doing pure serial, or a multiple console * with video "primary" because that's also serial. */ if ((mode & (RB_SERIAL | RB_MULTIPLE)) != 0 || screen_buffer == NULL) { input_byte(ch); return; } teken_input(&gfx_state.tg_teken, &ch, sizeof (ch)); } static int keybuf_getchar(void) { int i, c = 0; for (i = 0; i < KEYBUFSZ; i++) { if (keybuf[i] != 0) { c = keybuf[i]; keybuf[i] = 0; break; } } return (c); } static bool keybuf_ischar(void) { int i; for (i = 0; i < KEYBUFSZ; i++) { if (keybuf[i] != 0) return (true); } return (false); } /* * We are not reading input before keybuf is empty, so we are safe * just to fill keybuf from the beginning. */ static void keybuf_inschar(EFI_INPUT_KEY *key) { switch (key->ScanCode) { case SCAN_UP: /* UP */ keybuf[0] = 0x1b; /* esc */ keybuf[1] = '['; keybuf[2] = 'A'; break; case SCAN_DOWN: /* DOWN */ keybuf[0] = 0x1b; /* esc */ keybuf[1] = '['; keybuf[2] = 'B'; break; case SCAN_RIGHT: /* RIGHT */ keybuf[0] = 0x1b; /* esc */ keybuf[1] = '['; keybuf[2] = 'C'; break; case SCAN_LEFT: /* LEFT */ keybuf[0] = 0x1b; /* esc */ keybuf[1] = '['; keybuf[2] = 'D'; break; case SCAN_DELETE: keybuf[0] = CHAR_BACKSPACE; break; case SCAN_ESC: keybuf[0] = 0x1b; /* esc */ break; default: keybuf[0] = key->UnicodeChar; break; } } static bool efi_readkey(void) { EFI_STATUS status; EFI_INPUT_KEY key; status = conin->ReadKeyStroke(conin, &key); if (status == EFI_SUCCESS) { keybuf_inschar(&key); return (true); } return (false); } static bool efi_readkey_ex(void) { EFI_STATUS status; EFI_INPUT_KEY *kp; EFI_KEY_DATA key_data; uint32_t kss; status = coninex->ReadKeyStrokeEx(coninex, &key_data); if (status == EFI_SUCCESS) { kss = key_data.KeyState.KeyShiftState; kp = &key_data.Key; if (kss & EFI_SHIFT_STATE_VALID) { /* * quick mapping to control chars, replace with * map lookup later. */ if (kss & EFI_RIGHT_CONTROL_PRESSED || kss & EFI_LEFT_CONTROL_PRESSED) { if (kp->UnicodeChar >= 'a' && kp->UnicodeChar <= 'z') { kp->UnicodeChar -= 'a'; kp->UnicodeChar++; } } } /* * The shift state and/or toggle state may not be valid, * but we still can have ScanCode or UnicodeChar. */ if (kp->ScanCode == 0 && kp->UnicodeChar == 0) return (false); keybuf_inschar(kp); return (true); } return (false); } int efi_cons_getchar(void) { int c; if ((c = keybuf_getchar()) != 0) return (c); key_pending = 0; if (coninex == NULL) { if (efi_readkey()) return (keybuf_getchar()); } else { if (efi_readkey_ex()) return (keybuf_getchar()); } return (-1); } int efi_cons_poll(void) { EFI_STATUS status; if (keybuf_ischar() || key_pending) return (1); /* * Some EFI implementation (u-boot for example) do not support * WaitForKey(). * CheckEvent() can clear the signaled state. */ if (coninex != NULL) { if (coninex->WaitForKeyEx == NULL) { key_pending = efi_readkey_ex(); } else { status = BS->CheckEvent(coninex->WaitForKeyEx); key_pending = status == EFI_SUCCESS; } } else { if (conin->WaitForKey == NULL) { key_pending = efi_readkey(); } else { status = BS->CheckEvent(conin->WaitForKey); key_pending = status == EFI_SUCCESS; } } return (key_pending); } /* Plain direct access to EFI OutputString(). */ void efi_cons_efiputchar(int c) { CHAR16 buf[2]; EFI_STATUS status; buf[0] = c; buf[1] = 0; /* terminate string */ status = conout->TestString(conout, buf); if (EFI_ERROR(status)) buf[0] = '?'; conout->OutputString(conout, buf); } diff --git a/stand/efi/loader/framebuffer.c b/stand/efi/loader/framebuffer.c index 509c41844dcb..adb9dfb62cee 100644 --- a/stand/efi/loader/framebuffer.c +++ b/stand/efi/loader/framebuffer.c @@ -1,830 +1,918 @@ /*- * Copyright (c) 2013 The FreeBSD Foundation * All rights reserved. * * This software was developed by Benno Rice under sponsorship from * the FreeBSD Foundation. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include +#include +#include #include #include "bootstrap.h" #include "framebuffer.h" static EFI_GUID conout_guid = EFI_CONSOLE_OUT_DEVICE_GUID; EFI_GUID gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; static EFI_GUID pciio_guid = EFI_PCI_IO_PROTOCOL_GUID; static EFI_GUID uga_guid = EFI_UGA_DRAW_PROTOCOL_GUID; +static EFI_GUID active_edid_guid = EFI_EDID_ACTIVE_PROTOCOL_GUID; +static EFI_GUID discovered_edid_guid = EFI_EDID_DISCOVERED_PROTOCOL_GUID; +static EFI_HANDLE gop_handle; + +/* Cached EDID. */ +struct vesa_edid_info *edid_info = NULL; static EFI_GRAPHICS_OUTPUT *gop; static EFI_UGA_DRAW_PROTOCOL *uga; static struct named_resolution { const char *name; const char *alias; unsigned int width; unsigned int height; } resolutions[] = { { .name = "480p", .width = 640, .height = 480, }, { .name = "720p", .width = 1280, .height = 720, }, { .name = "1080p", .width = 1920, .height = 1080, }, { .name = "2160p", .alias = "4k", .width = 3840, .height = 2160, }, { .name = "5k", .width = 5120, .height = 2880, } }; static u_int efifb_color_depth(struct efi_fb *efifb) { uint32_t mask; u_int depth; mask = efifb->fb_mask_red | efifb->fb_mask_green | efifb->fb_mask_blue | efifb->fb_mask_reserved; if (mask == 0) return (0); for (depth = 1; mask != 1; depth++) mask >>= 1; return (depth); } static int efifb_mask_from_pixfmt(struct efi_fb *efifb, EFI_GRAPHICS_PIXEL_FORMAT pixfmt, EFI_PIXEL_BITMASK *pixinfo) { int result; result = 0; switch (pixfmt) { case PixelRedGreenBlueReserved8BitPerColor: case PixelBltOnly: efifb->fb_mask_red = 0x000000ff; efifb->fb_mask_green = 0x0000ff00; efifb->fb_mask_blue = 0x00ff0000; efifb->fb_mask_reserved = 0xff000000; break; case PixelBlueGreenRedReserved8BitPerColor: efifb->fb_mask_red = 0x00ff0000; efifb->fb_mask_green = 0x0000ff00; efifb->fb_mask_blue = 0x000000ff; efifb->fb_mask_reserved = 0xff000000; break; case PixelBitMask: efifb->fb_mask_red = pixinfo->RedMask; efifb->fb_mask_green = pixinfo->GreenMask; efifb->fb_mask_blue = pixinfo->BlueMask; efifb->fb_mask_reserved = pixinfo->ReservedMask; break; default: result = 1; break; } return (result); } static int efifb_from_gop(struct efi_fb *efifb, EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *mode, EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info) { int result; efifb->fb_addr = mode->FrameBufferBase; efifb->fb_size = mode->FrameBufferSize; efifb->fb_height = info->VerticalResolution; efifb->fb_width = info->HorizontalResolution; efifb->fb_stride = info->PixelsPerScanLine; result = efifb_mask_from_pixfmt(efifb, info->PixelFormat, &info->PixelInformation); return (result); } static ssize_t efifb_uga_find_pixel(EFI_UGA_DRAW_PROTOCOL *uga, u_int line, EFI_PCI_IO_PROTOCOL *pciio, uint64_t addr, uint64_t size) { EFI_UGA_PIXEL pix0, pix1; uint8_t *data1, *data2; size_t count, maxcount = 1024; ssize_t ofs; EFI_STATUS status; u_int idx; status = uga->Blt(uga, &pix0, EfiUgaVideoToBltBuffer, 0, line, 0, 0, 1, 1, 0); if (EFI_ERROR(status)) { printf("UGA BLT operation failed (video->buffer)"); return (-1); } pix1.Red = ~pix0.Red; pix1.Green = ~pix0.Green; pix1.Blue = ~pix0.Blue; pix1.Reserved = 0; data1 = calloc(maxcount, 2); if (data1 == NULL) { printf("Unable to allocate memory"); return (-1); } data2 = data1 + maxcount; ofs = 0; while (size > 0) { count = min(size, maxcount); status = pciio->Mem.Read(pciio, EfiPciIoWidthUint32, EFI_PCI_IO_PASS_THROUGH_BAR, addr + ofs, count >> 2, data1); if (EFI_ERROR(status)) { printf("Error reading frame buffer (before)"); goto fail; } status = uga->Blt(uga, &pix1, EfiUgaBltBufferToVideo, 0, 0, 0, line, 1, 1, 0); if (EFI_ERROR(status)) { printf("UGA BLT operation failed (modify)"); goto fail; } status = pciio->Mem.Read(pciio, EfiPciIoWidthUint32, EFI_PCI_IO_PASS_THROUGH_BAR, addr + ofs, count >> 2, data2); if (EFI_ERROR(status)) { printf("Error reading frame buffer (after)"); goto fail; } status = uga->Blt(uga, &pix0, EfiUgaBltBufferToVideo, 0, 0, 0, line, 1, 1, 0); if (EFI_ERROR(status)) { printf("UGA BLT operation failed (restore)"); goto fail; } for (idx = 0; idx < count; idx++) { if (data1[idx] != data2[idx]) { free(data1); return (ofs + (idx & ~3)); } } ofs += count; size -= count; } printf("No change detected in frame buffer"); fail: printf(" -- error %lu\n", EFI_ERROR_CODE(status)); free(data1); return (-1); } static EFI_PCI_IO_PROTOCOL * efifb_uga_get_pciio(void) { EFI_PCI_IO_PROTOCOL *pciio; EFI_HANDLE *buf, *hp; EFI_STATUS status; UINTN bufsz; /* Get all handles that support the UGA protocol. */ bufsz = 0; status = BS->LocateHandle(ByProtocol, &uga_guid, NULL, &bufsz, NULL); if (status != EFI_BUFFER_TOO_SMALL) return (NULL); buf = malloc(bufsz); status = BS->LocateHandle(ByProtocol, &uga_guid, NULL, &bufsz, buf); if (status != EFI_SUCCESS) { free(buf); return (NULL); } bufsz /= sizeof(EFI_HANDLE); /* Get the PCI I/O interface of the first handle that supports it. */ pciio = NULL; for (hp = buf; hp < buf + bufsz; hp++) { status = OpenProtocolByHandle(*hp, &pciio_guid, (void **)&pciio); if (status == EFI_SUCCESS) { free(buf); return (pciio); } } free(buf); return (NULL); } static EFI_STATUS efifb_uga_locate_framebuffer(EFI_PCI_IO_PROTOCOL *pciio, uint64_t *addrp, uint64_t *sizep) { uint8_t *resattr; uint64_t addr, size; EFI_STATUS status; u_int bar; if (pciio == NULL) return (EFI_DEVICE_ERROR); /* Attempt to get the frame buffer address (imprecise). */ *addrp = 0; *sizep = 0; for (bar = 0; bar < 6; bar++) { status = pciio->GetBarAttributes(pciio, bar, NULL, (void **)&resattr); if (status != EFI_SUCCESS) continue; /* XXX magic offsets and constants. */ if (resattr[0] == 0x87 && resattr[3] == 0) { /* 32-bit address space descriptor (MEMIO) */ addr = le32dec(resattr + 10); size = le32dec(resattr + 22); } else if (resattr[0] == 0x8a && resattr[3] == 0) { /* 64-bit address space descriptor (MEMIO) */ addr = le64dec(resattr + 14); size = le64dec(resattr + 38); } else { addr = 0; size = 0; } BS->FreePool(resattr); if (addr == 0 || size == 0) continue; /* We assume the largest BAR is the frame buffer. */ if (size > *sizep) { *addrp = addr; *sizep = size; } } return ((*addrp == 0 || *sizep == 0) ? EFI_DEVICE_ERROR : 0); } static int efifb_from_uga(struct efi_fb *efifb) { EFI_PCI_IO_PROTOCOL *pciio; char *ev, *p; EFI_STATUS status; ssize_t offset; uint64_t fbaddr; uint32_t horiz, vert, stride; uint32_t np, depth, refresh; status = uga->GetMode(uga, &horiz, &vert, &depth, &refresh); if (EFI_ERROR(status)) return (1); efifb->fb_height = vert; efifb->fb_width = horiz; /* Paranoia... */ if (efifb->fb_height == 0 || efifb->fb_width == 0) return (1); /* The color masks are fixed AFAICT. */ efifb_mask_from_pixfmt(efifb, PixelBlueGreenRedReserved8BitPerColor, NULL); /* pciio can be NULL on return! */ pciio = efifb_uga_get_pciio(); /* Try to find the frame buffer. */ status = efifb_uga_locate_framebuffer(pciio, &efifb->fb_addr, &efifb->fb_size); if (EFI_ERROR(status)) { efifb->fb_addr = 0; efifb->fb_size = 0; } /* * There's no reliable way to detect the frame buffer or the * offset within the frame buffer of the visible region, nor * the stride. Our only option is to look at the system and * fill in the blanks based on that. Luckily, UGA was mostly * only used on Apple hardware. */ offset = -1; ev = getenv("smbios.system.maker"); if (ev != NULL && !strcmp(ev, "Apple Inc.")) { ev = getenv("smbios.system.product"); if (ev != NULL && !strcmp(ev, "iMac7,1")) { /* These are the expected values we should have. */ horiz = 1680; vert = 1050; fbaddr = 0xc0000000; /* These are the missing bits. */ offset = 0x10000; stride = 1728; } else if (ev != NULL && !strcmp(ev, "MacBook3,1")) { /* These are the expected values we should have. */ horiz = 1280; vert = 800; fbaddr = 0xc0000000; /* These are the missing bits. */ offset = 0x0; stride = 2048; } } /* * If this is hardware we know, make sure that it looks familiar * before we accept our hardcoded values. */ if (offset >= 0 && efifb->fb_width == horiz && efifb->fb_height == vert && efifb->fb_addr == fbaddr) { efifb->fb_addr += offset; efifb->fb_size -= offset; efifb->fb_stride = stride; return (0); } else if (offset >= 0) { printf("Hardware make/model known, but graphics not " "as expected.\n"); printf("Console may not work!\n"); } /* * The stride is equal or larger to the width. Often it's the * next larger power of two. We'll start with that... */ efifb->fb_stride = efifb->fb_width; do { np = efifb->fb_stride & (efifb->fb_stride - 1); if (np) { efifb->fb_stride |= (np - 1); efifb->fb_stride++; } } while (np); ev = getenv("hw.efifb.address"); if (ev == NULL) { if (efifb->fb_addr == 0) { printf("Please set hw.efifb.address and " "hw.efifb.stride.\n"); return (1); } /* * The visible part of the frame buffer may not start at * offset 0, so try to detect it. Note that we may not * always be able to read from the frame buffer, which * means that we may not be able to detect anything. In * that case, we would take a long time scanning for a * pixel change in the frame buffer, which would have it * appear that we're hanging, so we limit the scan to * 1/256th of the frame buffer. This number is mostly * based on PR 202730 and the fact that on a MacBoook, * where we can't read from the frame buffer the offset * of the visible region is 0. In short: we want to scan * enough to handle all adapters that have an offset * larger than 0 and we want to scan as little as we can * to not appear to hang when we can't read from the * frame buffer. */ offset = efifb_uga_find_pixel(uga, 0, pciio, efifb->fb_addr, efifb->fb_size >> 8); if (offset == -1) { printf("Unable to reliably detect frame buffer.\n"); } else if (offset > 0) { efifb->fb_addr += offset; efifb->fb_size -= offset; } } else { offset = 0; efifb->fb_size = efifb->fb_height * efifb->fb_stride * 4; efifb->fb_addr = strtoul(ev, &p, 0); if (*p != '\0') return (1); } ev = getenv("hw.efifb.stride"); if (ev == NULL) { if (pciio != NULL && offset != -1) { /* Determine the stride. */ offset = efifb_uga_find_pixel(uga, 1, pciio, efifb->fb_addr, horiz * 8); if (offset != -1) efifb->fb_stride = offset >> 2; } else { printf("Unable to reliably detect the stride.\n"); } } else { efifb->fb_stride = strtoul(ev, &p, 0); if (*p != '\0') return (1); } /* * We finalized on the stride, so recalculate the size of the * frame buffer. */ efifb->fb_size = efifb->fb_height * efifb->fb_stride * 4; return (0); } +/* + * Fetch EDID info. Caller must free the buffer. + */ +static struct vesa_edid_info * +efifb_gop_get_edid(EFI_HANDLE h) +{ + const uint8_t magic[] = EDID_MAGIC; + EFI_EDID_ACTIVE_PROTOCOL *edid; + struct vesa_edid_info *edid_infop; + EFI_GUID *guid; + EFI_STATUS status; + size_t size; + + guid = &active_edid_guid; + status = BS->OpenProtocol(h, guid, (void **)&edid, IH, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (status != EFI_SUCCESS || + edid->SizeOfEdid == 0) { + guid = &discovered_edid_guid; + status = BS->OpenProtocol(h, guid, (void **)&edid, IH, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (status != EFI_SUCCESS || + edid->SizeOfEdid == 0) + return (NULL); + } + + size = MAX(sizeof(*edid_infop), edid->SizeOfEdid); + + edid_infop = calloc(1, size); + if (edid_infop == NULL) + return (NULL); + + memcpy(edid_infop, edid->Edid, edid->SizeOfEdid); + + /* Validate EDID */ + if (memcmp(edid_infop, magic, sizeof (magic)) != 0) + goto error; + + if (edid_infop->header.version != 1) + goto error; + + return (edid_infop); +error: + free(edid_infop); + return (NULL); +} + +static bool +efifb_get_edid(edid_res_list_t *res) +{ + bool rv = false; + + if (edid_info == NULL) + edid_info = efifb_gop_get_edid(gop_handle); + + if (edid_info != NULL) + rv = gfx_get_edid_resolution(edid_info, res); + + return (rv); +} + int efi_find_framebuffer(teken_gfx_t *gfx_state) { - EFI_HANDLE h, *hlist; + EFI_HANDLE *hlist; UINTN nhandles, i, hsize; struct efi_fb efifb; EFI_STATUS status; int rv; gfx_state->tg_fb_type = FB_TEXT; hsize = 0; hlist = NULL; status = BS->LocateHandle(ByProtocol, &gop_guid, NULL, &hsize, hlist); if (status == EFI_BUFFER_TOO_SMALL) { hlist = malloc(hsize); if (hlist == NULL) return (ENOMEM); status = BS->LocateHandle(ByProtocol, &gop_guid, NULL, &hsize, hlist); if (EFI_ERROR(status)) free(hlist); } if (EFI_ERROR(status)) return (efi_status_to_errno(status)); nhandles = hsize / sizeof(*hlist); /* * Search for ConOut protocol, if not found, use first handle. */ - h = *hlist; + gop_handle = *hlist; for (i = 0; i < nhandles; i++) { void *dummy = NULL; status = OpenProtocolByHandle(hlist[i], &conout_guid, &dummy); if (status == EFI_SUCCESS) { - h = hlist[i]; + gop_handle = hlist[i]; break; } } - status = OpenProtocolByHandle(h, &gop_guid, (void **)&gop); + status = OpenProtocolByHandle(gop_handle, &gop_guid, (void **)&gop); free(hlist); if (status == EFI_SUCCESS) { gfx_state->tg_fb_type = FB_GOP; gfx_state->tg_private = gop; + if (edid_info == NULL) + edid_info = efifb_gop_get_edid(gop_handle); } else { status = BS->LocateProtocol(&uga_guid, NULL, (VOID **)&uga); if (status == EFI_SUCCESS) { gfx_state->tg_fb_type = FB_UGA; gfx_state->tg_private = uga; } else { return (1); } } switch (gfx_state->tg_fb_type) { case FB_GOP: rv = efifb_from_gop(&efifb, gop->Mode, gop->Mode->Info); break; case FB_UGA: rv = efifb_from_uga(&efifb); break; default: return (1); } gfx_state->tg_fb.fb_addr = efifb.fb_addr; gfx_state->tg_fb.fb_size = efifb.fb_size; gfx_state->tg_fb.fb_height = efifb.fb_height; gfx_state->tg_fb.fb_width = efifb.fb_width; gfx_state->tg_fb.fb_stride = efifb.fb_stride; gfx_state->tg_fb.fb_mask_red = efifb.fb_mask_red; gfx_state->tg_fb.fb_mask_green = efifb.fb_mask_green; gfx_state->tg_fb.fb_mask_blue = efifb.fb_mask_blue; gfx_state->tg_fb.fb_mask_reserved = efifb.fb_mask_reserved; gfx_state->tg_fb.fb_bpp = fls(efifb.fb_mask_red | efifb.fb_mask_green | efifb.fb_mask_blue | efifb.fb_mask_reserved); return (0); } static void print_efifb(int mode, struct efi_fb *efifb, int verbose) { u_int depth; if (mode >= 0) printf("mode %d: ", mode); depth = efifb_color_depth(efifb); printf("%ux%ux%u, stride=%u", efifb->fb_width, efifb->fb_height, depth, efifb->fb_stride); if (verbose) { printf("\n frame buffer: address=%jx, size=%jx", (uintmax_t)efifb->fb_addr, (uintmax_t)efifb->fb_size); printf("\n color mask: R=%08x, G=%08x, B=%08x\n", efifb->fb_mask_red, efifb->fb_mask_green, efifb->fb_mask_blue); } } static bool efi_resolution_compare(struct named_resolution *res, const char *cmp) { if (strcasecmp(res->name, cmp) == 0) return (true); if (res->alias != NULL && strcasecmp(res->alias, cmp) == 0) return (true); return (false); } static void efi_get_max_resolution(int *width, int *height) { struct named_resolution *res; char *maxres; char *height_start, *width_start; int idx; *width = *height = 0; maxres = getenv("efi_max_resolution"); /* No max_resolution set? Bail out; choose highest resolution */ if (maxres == NULL) return; /* See if it matches one of our known resolutions */ for (idx = 0; idx < nitems(resolutions); ++idx) { res = &resolutions[idx]; if (efi_resolution_compare(res, maxres)) { *width = res->width; *height = res->height; return; } } /* Not a known resolution, try to parse it; make a copy we can modify */ maxres = strdup(maxres); if (maxres == NULL) return; height_start = strchr(maxres, 'x'); if (height_start == NULL) { free(maxres); return; } width_start = maxres; *height_start++ = 0; /* Errors from this will effectively mean "no max" */ *width = (int)strtol(width_start, NULL, 0); *height = (int)strtol(height_start, NULL, 0); free(maxres); } static int gop_autoresize(void) { struct efi_fb efifb; EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info; EFI_STATUS status; UINTN infosz; UINT32 best_mode, currdim, maxdim, mode; int height, max_height, max_width, width; best_mode = maxdim = 0; efi_get_max_resolution(&max_width, &max_height); for (mode = 0; mode < gop->Mode->MaxMode; mode++) { status = gop->QueryMode(gop, mode, &infosz, &info); if (EFI_ERROR(status)) continue; efifb_from_gop(&efifb, gop->Mode, info); width = info->HorizontalResolution; height = info->VerticalResolution; currdim = width * height; if (currdim > maxdim) { if ((max_width != 0 && width > max_width) || (max_height != 0 && height > max_height)) continue; maxdim = currdim; best_mode = mode; } } if (maxdim != 0) { status = gop->SetMode(gop, best_mode); if (EFI_ERROR(status)) { snprintf(command_errbuf, sizeof(command_errbuf), "gop_autoresize: Unable to set mode to %u (error=%lu)", mode, EFI_ERROR_CODE(status)); return (CMD_ERROR); } (void) cons_update_mode(true); } return (CMD_OK); } static int text_autoresize() { SIMPLE_TEXT_OUTPUT_INTERFACE *conout; EFI_STATUS status; UINTN i, max_dim, best_mode, cols, rows; conout = ST->ConOut; max_dim = best_mode = 0; for (i = 0; i < conout->Mode->MaxMode; i++) { status = conout->QueryMode(conout, i, &cols, &rows); if (EFI_ERROR(status)) continue; if (cols * rows > max_dim) { max_dim = cols * rows; best_mode = i; } } if (max_dim > 0) conout->SetMode(conout, best_mode); (void) cons_update_mode(true); return (CMD_OK); } static int uga_autoresize(void) { return (text_autoresize()); } COMMAND_SET(efi_autoresize, "efi-autoresizecons", "EFI Auto-resize Console", command_autoresize); static int command_autoresize(int argc, char *argv[]) { char *textmode; textmode = getenv("hw.vga.textmode"); /* If it's set and non-zero, we'll select a console mode instead */ if (textmode != NULL && strcmp(textmode, "0") != 0) return (text_autoresize()); if (gop != NULL) return (gop_autoresize()); if (uga != NULL) return (uga_autoresize()); snprintf(command_errbuf, sizeof(command_errbuf), "%s: Neither Graphics Output Protocol nor Universal Graphics Adapter present", argv[0]); /* * Default to text_autoresize if we have neither GOP or UGA. This won't * give us the most ideal resolution, but it will at least leave us * functional rather than failing the boot for an objectively bad * reason. */ return (text_autoresize()); } COMMAND_SET(gop, "gop", "graphics output protocol", command_gop); static int command_gop(int argc, char *argv[]) { struct efi_fb efifb; EFI_STATUS status; u_int mode; if (gop == NULL) { snprintf(command_errbuf, sizeof(command_errbuf), "%s: Graphics Output Protocol not present", argv[0]); return (CMD_ERROR); } if (argc < 2) goto usage; if (!strcmp(argv[1], "set")) { char *cp; if (argc != 3) goto usage; mode = strtol(argv[2], &cp, 0); if (cp[0] != '\0') { sprintf(command_errbuf, "mode is an integer"); return (CMD_ERROR); } status = gop->SetMode(gop, mode); if (EFI_ERROR(status)) { snprintf(command_errbuf, sizeof(command_errbuf), "%s: Unable to set mode to %u (error=%lu)", argv[0], mode, EFI_ERROR_CODE(status)); return (CMD_ERROR); } (void) cons_update_mode(true); } else if (strcmp(argv[1], "off") == 0) { (void) cons_update_mode(false); } else if (strcmp(argv[1], "get") == 0) { + edid_res_list_t res; + if (argc != 2) goto usage; + TAILQ_INIT(&res); efifb_from_gop(&efifb, gop->Mode, gop->Mode->Info); + if (efifb_get_edid(&res)) { + struct resolution *rp; + + printf("EDID"); + while ((rp = TAILQ_FIRST(&res)) != NULL) { + printf(" %dx%d", rp->width, rp->height); + TAILQ_REMOVE(&res, rp, next); + free(rp); + } + printf("\n"); + } else { + printf("no EDID information\n"); + } print_efifb(gop->Mode->Mode, &efifb, 1); printf("\n"); } else if (!strcmp(argv[1], "list")) { EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info; UINTN infosz; if (argc != 2) goto usage; + pager_open(); for (mode = 0; mode < gop->Mode->MaxMode; mode++) { status = gop->QueryMode(gop, mode, &infosz, &info); if (EFI_ERROR(status)) continue; efifb_from_gop(&efifb, gop->Mode, info); print_efifb(mode, &efifb, 0); if (pager_output("\n")) break; } pager_close(); } return (CMD_OK); usage: snprintf(command_errbuf, sizeof(command_errbuf), "usage: %s [list | get | set | off]", argv[0]); return (CMD_ERROR); } COMMAND_SET(uga, "uga", "universal graphics adapter", command_uga); static int command_uga(int argc, char *argv[]) { struct efi_fb efifb; if (uga == NULL) { snprintf(command_errbuf, sizeof(command_errbuf), "%s: UGA Protocol not present", argv[0]); return (CMD_ERROR); } if (argc != 1) goto usage; if (efifb_from_uga(&efifb) != CMD_OK) { snprintf(command_errbuf, sizeof(command_errbuf), "%s: Unable to get UGA information", argv[0]); return (CMD_ERROR); } print_efifb(-1, &efifb, 1); printf("\n"); return (CMD_OK); usage: snprintf(command_errbuf, sizeof(command_errbuf), "usage: %s", argv[0]); return (CMD_ERROR); } diff --git a/stand/i386/libi386/vbe.c b/stand/i386/libi386/vbe.c index 0e9f6929ac05..ef4daffa8380 100644 --- a/stand/i386/libi386/vbe.c +++ b/stand/i386/libi386/vbe.c @@ -1,1261 +1,1268 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2009 Jared D. McNeill * All rights reserved. * Copyright 2020 Toomas Soome * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include "libi386.h" #include "vbe.h" /* * VESA BIOS Extensions routines */ static struct vbeinfoblock *vbe; static struct modeinfoblock *vbe_mode; static uint16_t *vbe_mode_list; static size_t vbe_mode_list_size; +struct vesa_edid_info *edid_info = NULL; /* The default VGA color palette format is 6 bits per primary color. */ int palette_format = 6; #define VESA_MODE_BASE 0x100 /* * palette array for 8-bit indexed colors. In this case, cmap does store * index and pe8 does store actual RGB. This is needed because we may * not be able to read palette data from hardware. */ struct paletteentry *pe8 = NULL; static struct named_resolution { const char *name; const char *alias; unsigned int width; unsigned int height; } resolutions[] = { { .name = "480p", .width = 640, .height = 480, }, { .name = "720p", .width = 1280, .height = 720, }, { .name = "1080p", .width = 1920, .height = 1080, }, { .name = "2160p", .alias = "4k", .width = 3840, .height = 2160, }, { .name = "5k", .width = 5120, .height = 2880, } }; static bool vbe_resolution_compare(struct named_resolution *res, const char *cmp) { if (strcasecmp(res->name, cmp) == 0) return (true); if (res->alias != NULL && strcasecmp(res->alias, cmp) == 0) return (true); return (false); } static void vbe_get_max_resolution(int *width, int *height) { struct named_resolution *res; char *maxres; char *height_start, *width_start; int idx; *width = *height = 0; maxres = getenv("vbe_max_resolution"); /* No max_resolution set? Bail out; choose highest resolution */ if (maxres == NULL) return; /* See if it matches one of our known resolutions */ for (idx = 0; idx < nitems(resolutions); ++idx) { res = &resolutions[idx]; if (vbe_resolution_compare(res, maxres)) { *width = res->width; *height = res->height; return; } } /* Not a known resolution, try to parse it; make a copy we can modify */ maxres = strdup(maxres); if (maxres == NULL) return; height_start = strchr(maxres, 'x'); if (height_start == NULL) { free(maxres); return; } width_start = maxres; *height_start++ = 0; /* Errors from this will effectively mean "no max" */ *width = (int)strtol(width_start, NULL, 0); *height = (int)strtol(height_start, NULL, 0); free(maxres); } int vga_get_reg(int reg, int index) { return (inb(reg + index)); } int vga_get_atr(int reg, int i) { int ret; (void) inb(reg + VGA_GEN_INPUT_STAT_1); outb(reg + VGA_AC_WRITE, i); ret = inb(reg + VGA_AC_READ); (void) inb(reg + VGA_GEN_INPUT_STAT_1); return (ret); } void vga_set_atr(int reg, int i, int v) { (void) inb(reg + VGA_GEN_INPUT_STAT_1); outb(reg + VGA_AC_WRITE, i); outb(reg + VGA_AC_WRITE, v); (void) inb(reg + VGA_GEN_INPUT_STAT_1); } void vga_set_indexed(int reg, int indexreg, int datareg, uint8_t index, uint8_t val) { outb(reg + indexreg, index); outb(reg + datareg, val); } int vga_get_indexed(int reg, int indexreg, int datareg, uint8_t index) { outb(reg + indexreg, index); return (inb(reg + datareg)); } int vga_get_crtc(int reg, int i) { return (vga_get_indexed(reg, VGA_CRTC_ADDRESS, VGA_CRTC_DATA, i)); } void vga_set_crtc(int reg, int i, int v) { vga_set_indexed(reg, VGA_CRTC_ADDRESS, VGA_CRTC_DATA, i, v); } int vga_get_seq(int reg, int i) { return (vga_get_indexed(reg, VGA_SEQ_ADDRESS, VGA_SEQ_DATA, i)); } void vga_set_seq(int reg, int i, int v) { vga_set_indexed(reg, VGA_SEQ_ADDRESS, VGA_SEQ_DATA, i, v); } int vga_get_grc(int reg, int i) { return (vga_get_indexed(reg, VGA_GC_ADDRESS, VGA_GC_DATA, i)); } void vga_set_grc(int reg, int i, int v) { vga_set_indexed(reg, VGA_GC_ADDRESS, VGA_GC_DATA, i, v); } /* * Return true when this controller is VGA compatible. */ bool vbe_is_vga(void) { if (vbe == NULL) return (false); return ((vbe->Capabilities & VBE_CAP_NONVGA) == 0); } /* Actually assuming mode 3. */ void bios_set_text_mode(int mode) { int atr; if (vbe->Capabilities & VBE_CAP_DAC8) { int m; /* * The mode change should reset the palette format to * 6 bits, but apparently some systems do fail with 8-bit * palette, so we switch to 6-bit here. */ m = 0x0600; (void) biosvbe_palette_format(&m); palette_format = m; } v86.ctl = V86_FLAGS; v86.addr = 0x10; v86.eax = mode; /* set VGA text mode */ v86int(); atr = vga_get_atr(VGA_REG_BASE, VGA_AC_MODE_CONTROL); atr &= ~VGA_AC_MC_BI; atr &= ~VGA_AC_MC_ELG; vga_set_atr(VGA_REG_BASE, VGA_AC_MODE_CONTROL, atr); gfx_state.tg_mode = mode; gfx_state.tg_fb_type = FB_TEXT; gfx_state.tg_fb.fb_height = TEXT_ROWS; gfx_state.tg_fb.fb_width = TEXT_COLS; gfx_state.tg_fb.fb_mask_red = (1 << palette_format) - 1 << 16; gfx_state.tg_fb.fb_mask_green = (1 << palette_format) - 1 << 8; gfx_state.tg_fb.fb_mask_blue = (1 << palette_format) - 1 << 0; gfx_state.tg_ctype = CT_INDEXED; env_setenv("screen.textmode", EV_VOLATILE | EV_NOHOOK, "1", NULL, NULL); } /* Function 00h - Return VBE Controller Information */ static int biosvbe_info(struct vbeinfoblock *vbep) { struct vbeinfoblock *rvbe; int ret; if (vbep == NULL) return (VBE_FAILED); rvbe = bio_alloc(sizeof(*rvbe)); if (rvbe == NULL) return (VBE_FAILED); /* Now check if we have vesa. */ memset(rvbe, 0, sizeof (*vbe)); memcpy(rvbe->VbeSignature, "VBE2", 4); v86.ctl = V86_FLAGS; v86.addr = 0x10; v86.eax = 0x4f00; v86.es = VTOPSEG(rvbe); v86.edi = VTOPOFF(rvbe); v86int(); ret = v86.eax & 0xffff; if (ret != VBE_SUCCESS) goto done; if (memcmp(rvbe->VbeSignature, "VESA", 4) != 0) { ret = VBE_NOTSUP; goto done; } bcopy(rvbe, vbep, sizeof(*vbep)); done: bio_free(rvbe, sizeof(*rvbe)); return (ret); } /* Function 01h - Return VBE Mode Information */ static int biosvbe_get_mode_info(int mode, struct modeinfoblock *mi) { struct modeinfoblock *rmi; int ret; rmi = bio_alloc(sizeof(*rmi)); if (rmi == NULL) return (VBE_FAILED); v86.ctl = V86_FLAGS; v86.addr = 0x10; v86.eax = 0x4f01; v86.ecx = mode; v86.es = VTOPSEG(rmi); v86.edi = VTOPOFF(rmi); v86int(); ret = v86.eax & 0xffff; if (ret != VBE_SUCCESS) goto done; bcopy(rmi, mi, sizeof(*rmi)); done: bio_free(rmi, sizeof(*rmi)); return (ret); } /* Function 02h - Set VBE Mode */ static int biosvbe_set_mode(int mode, struct crtciinfoblock *ci) { int rv; if (vbe->Capabilities & VBE_CAP_DAC8) { int m; /* * The mode change should reset the palette format to * 6 bits, but apparently some systems do fail with 8-bit * palette, so we switch to 6-bit here. */ m = 0x0600; if (biosvbe_palette_format(&m) == VBE_SUCCESS) palette_format = m; } v86.ctl = V86_FLAGS; v86.addr = 0x10; v86.eax = 0x4f02; v86.ebx = mode | 0x4000; /* set linear FB bit */ v86.es = VTOPSEG(ci); v86.edi = VTOPOFF(ci); v86int(); rv = v86.eax & 0xffff; if (vbe->Capabilities & VBE_CAP_DAC8) { int m; /* Switch to 8-bits per primary color. */ m = 0x0800; if (biosvbe_palette_format(&m) == VBE_SUCCESS) palette_format = m; } env_setenv("screen.textmode", EV_VOLATILE | EV_NOHOOK, "0", NULL, NULL); return (rv); } /* Function 03h - Get VBE Mode */ static int biosvbe_get_mode(int *mode) { v86.ctl = V86_FLAGS; v86.addr = 0x10; v86.eax = 0x4f03; v86int(); *mode = v86.ebx & 0x3fff; /* Bits 0-13 */ return (v86.eax & 0xffff); } /* Function 08h - Set/Get DAC Palette Format */ int biosvbe_palette_format(int *format) { v86.ctl = V86_FLAGS; v86.addr = 0x10; v86.eax = 0x4f08; v86.ebx = *format; v86int(); *format = (v86.ebx >> 8) & 0xff; return (v86.eax & 0xffff); } /* Function 09h - Set/Get Palette Data */ static int biosvbe_palette_data(int mode, int reg, struct paletteentry *pe) { v86.ctl = V86_FLAGS; v86.addr = 0x10; v86.eax = 0x4f09; v86.ebx = mode; v86.edx = reg; v86.ecx = 1; v86.es = VTOPSEG(pe); v86.edi = VTOPOFF(pe); v86int(); return (v86.eax & 0xffff); } /* * Function 15h BL=00h - Report VBE/DDC Capabilities * * int biosvbe_ddc_caps(void) * return: VBE/DDC capabilities */ static int biosvbe_ddc_caps(void) { v86.ctl = V86_FLAGS; v86.addr = 0x10; v86.eax = 0x4f15; /* display identification extensions */ v86.ebx = 0; /* report DDC capabilities */ v86.ecx = 0; /* controller unit number (00h = primary) */ v86.es = 0; v86.edi = 0; v86int(); if (VBE_ERROR(v86.eax & 0xffff)) return (0); return (v86.ebx & 0xffff); } /* Function 11h BL=01h - Flat Panel status */ static int biosvbe_ddc_read_flat_panel_info(void *buf) { v86.ctl = V86_FLAGS; v86.addr = 0x10; v86.eax = 0x4f11; /* Flat Panel Interface extensions */ v86.ebx = 1; /* Return Flat Panel Information */ v86.es = VTOPSEG(buf); v86.edi = VTOPOFF(buf); v86int(); return (v86.eax & 0xffff); } /* Function 15h BL=01h - Read EDID */ static int biosvbe_ddc_read_edid(int blockno, void *buf) { v86.ctl = V86_FLAGS; v86.addr = 0x10; v86.eax = 0x4f15; /* display identification extensions */ v86.ebx = 1; /* read EDID */ v86.ecx = 0; /* controller unit number (00h = primary) */ v86.edx = blockno; v86.es = VTOPSEG(buf); v86.edi = VTOPOFF(buf); v86int(); return (v86.eax & 0xffff); } static int vbe_mode_is_supported(struct modeinfoblock *mi) { if ((mi->ModeAttributes & 0x01) == 0) return (0); /* mode not supported by hardware */ if ((mi->ModeAttributes & 0x08) == 0) return (0); /* linear fb not available */ if ((mi->ModeAttributes & 0x10) == 0) return (0); /* text mode */ if (mi->NumberOfPlanes != 1) return (0); /* planar mode not supported */ if (mi->MemoryModel != 0x04 /* Packed pixel */ && mi->MemoryModel != 0x06 /* Direct Color */) return (0); /* unsupported pixel format */ return (1); } static bool vbe_check(void) { if (vbe == NULL) { printf("VBE not available\n"); return (false); } return (true); } static int mode_set(struct env_var *ev, int flags __unused, const void *value) { int mode; if (strcmp(ev->ev_name, "screen.textmode") == 0) { unsigned long v; char *end; if (value == NULL) return (0); errno = 0; v = strtoul(value, &end, 0); if (errno != 0 || *(char *)value == '\0' || *end != '\0' || (v != 0 && v != 1)) return (EINVAL); env_setenv("screen.textmode", EV_VOLATILE | EV_NOHOOK, value, NULL, NULL); if (v == 1) { reset_font_flags(); bios_text_font(true); bios_set_text_mode(VGA_TEXT_MODE); (void) cons_update_mode(false); return (0); } } else if (strcmp(ev->ev_name, "vbe_max_resolution") == 0) { env_setenv("vbe_max_resolution", EV_VOLATILE | EV_NOHOOK, value, NULL, NULL); } else { return (EINVAL); } mode = vbe_default_mode(); if (gfx_state.tg_mode != mode) { reset_font_flags(); bios_text_font(false); vbe_set_mode(mode); cons_update_mode(true); } return (0); } static void * vbe_farptr(uint32_t farptr) { return (PTOV((((farptr & 0xffff0000) >> 12) + (farptr & 0xffff)))); } void vbe_init(void) { uint16_t *p, *ml; /* First set FB for text mode. */ gfx_state.tg_fb_type = FB_TEXT; gfx_state.tg_fb.fb_height = TEXT_ROWS; gfx_state.tg_fb.fb_width = TEXT_COLS; gfx_state.tg_ctype = CT_INDEXED; gfx_state.tg_mode = 3; env_setenv("screen.textmode", EV_VOLATILE, "1", mode_set, env_nounset); env_setenv("vbe_max_resolution", EV_VOLATILE, NULL, mode_set, env_nounset); if (vbe == NULL) { vbe = malloc(sizeof(*vbe)); if (vbe == NULL) return; } if (vbe_mode == NULL) { vbe_mode = malloc(sizeof(*vbe_mode)); if (vbe_mode == NULL) { free(vbe); vbe = NULL; } } if (biosvbe_info(vbe) != VBE_SUCCESS) { free(vbe); vbe = NULL; free(vbe_mode); vbe_mode = NULL; return; } /* * Copy mode list. We must do this because some systems do * corrupt the provided list (vbox 6.1 is one example). */ p = ml = vbe_farptr(vbe->VideoModePtr); while(*p++ != 0xFFFF) ; vbe_mode_list_size = (uintptr_t)p - (uintptr_t)ml; /* * Since vbe_init() is used only once at very start of the loader, * we assume malloc will not fail there, but in case it does, * we point vbe_mode_list to memory pointed by VideoModePtr. */ vbe_mode_list = malloc(vbe_mode_list_size); if (vbe_mode_list == NULL) vbe_mode_list = ml; else bcopy(ml, vbe_mode_list, vbe_mode_list_size); /* reset VideoModePtr, to make sure, we only do use vbe_mode_list. */ vbe->VideoModePtr = 0; /* vbe_set_mode() will set up the rest. */ } bool vbe_available(void) { return (gfx_state.tg_fb_type == FB_VBE); } int vbe_set_palette(const struct paletteentry *entry, size_t slot) { struct paletteentry pe; int mode, ret; if (!vbe_check() || (vbe->Capabilities & VBE_CAP_DAC8) == 0) return (1); if (gfx_state.tg_ctype != CT_INDEXED) { return (1); } pe.Blue = entry->Blue; pe.Green = entry->Green; pe.Red = entry->Red; pe.Reserved = entry->Reserved; if (vbe->Capabilities & VBE_CAP_SNOW) mode = 0x80; else mode = 0; ret = biosvbe_palette_data(mode, slot, &pe); return (ret == VBE_SUCCESS ? 0 : 1); } int vbe_get_mode(void) { return (gfx_state.tg_mode); } int vbe_set_mode(int modenum) { struct modeinfoblock mi; int bpp, ret; if (!vbe_check()) return (1); ret = biosvbe_get_mode_info(modenum, &mi); if (VBE_ERROR(ret)) { printf("mode 0x%x invalid\n", modenum); return (1); } if (!vbe_mode_is_supported(&mi)) { printf("mode 0x%x not supported\n", modenum); return (1); } /* calculate bytes per pixel */ switch (mi.BitsPerPixel) { case 32: case 24: case 16: case 15: case 8: break; default: printf("BitsPerPixel %d is not supported\n", mi.BitsPerPixel); return (1); } ret = biosvbe_set_mode(modenum, NULL); if (VBE_ERROR(ret)) { printf("mode 0x%x could not be set\n", modenum); return (1); } gfx_state.tg_mode = modenum; gfx_state.tg_fb_type = FB_VBE; /* make sure we have current MI in vbestate */ memcpy(vbe_mode, &mi, sizeof (*vbe_mode)); gfx_state.tg_fb.fb_addr = (uint64_t)mi.PhysBasePtr & 0xffffffff; gfx_state.tg_fb.fb_height = mi.YResolution; gfx_state.tg_fb.fb_width = mi.XResolution; gfx_state.tg_fb.fb_bpp = mi.BitsPerPixel; /* Bytes per pixel */ bpp = roundup2(mi.BitsPerPixel, NBBY) / NBBY; /* vbe_mode_is_supported() excludes the rest */ switch (mi.MemoryModel) { case 0x4: gfx_state.tg_ctype = CT_INDEXED; break; case 0x6: gfx_state.tg_ctype = CT_RGB; break; } #define COLOR_MASK(size, pos) (((1 << size) - 1) << pos) if (gfx_state.tg_ctype == CT_INDEXED) { gfx_state.tg_fb.fb_mask_red = COLOR_MASK(palette_format, 16); gfx_state.tg_fb.fb_mask_green = COLOR_MASK(palette_format, 8); gfx_state.tg_fb.fb_mask_blue = COLOR_MASK(palette_format, 0); } else if (vbe->VbeVersion >= 0x300) { gfx_state.tg_fb.fb_mask_red = COLOR_MASK(mi.LinRedMaskSize, mi.LinRedFieldPosition); gfx_state.tg_fb.fb_mask_green = COLOR_MASK(mi.LinGreenMaskSize, mi.LinGreenFieldPosition); gfx_state.tg_fb.fb_mask_blue = COLOR_MASK(mi.LinBlueMaskSize, mi.LinBlueFieldPosition); } else { gfx_state.tg_fb.fb_mask_red = COLOR_MASK(mi.RedMaskSize, mi.RedFieldPosition); gfx_state.tg_fb.fb_mask_green = COLOR_MASK(mi.GreenMaskSize, mi.GreenFieldPosition); gfx_state.tg_fb.fb_mask_blue = COLOR_MASK(mi.BlueMaskSize, mi.BlueFieldPosition); } gfx_state.tg_fb.fb_mask_reserved = ~(gfx_state.tg_fb.fb_mask_red | gfx_state.tg_fb.fb_mask_green | gfx_state.tg_fb.fb_mask_blue); if (vbe->VbeVersion >= 0x300) gfx_state.tg_fb.fb_stride = mi.LinBytesPerScanLine / bpp; else gfx_state.tg_fb.fb_stride = mi.BytesPerScanLine / bpp; gfx_state.tg_fb.fb_size = mi.YResolution * gfx_state.tg_fb.fb_stride * bpp; return (0); } /* * Verify existance of mode number or find mode by * dimensions. If depth is not given, walk values 32, 24, 16, 8. */ static int vbe_find_mode_xydm(int x, int y, int depth, int m) { struct modeinfoblock mi; uint16_t *farptr; uint16_t mode; int idx, nentries, i; memset(vbe, 0, sizeof (*vbe)); if (biosvbe_info(vbe) != VBE_SUCCESS) return (0); if (m != -1) i = 8; else if (depth == -1) i = 32; else i = depth; nentries = vbe_mode_list_size / sizeof(*vbe_mode_list); while (i > 0) { for (idx = 0; idx < nentries; idx++) { mode = vbe_mode_list[idx]; if (mode == 0xffff) break; if (biosvbe_get_mode_info(mode, &mi) != VBE_SUCCESS) { continue; } /* we only care about linear modes here */ if (vbe_mode_is_supported(&mi) == 0) continue; if (m != -1) { if (m == mode) return (mode); else continue; } if (mi.XResolution == x && mi.YResolution == y && mi.BitsPerPixel == i) return (mode); } if (depth != -1) break; i -= 8; } return (0); } static int vbe_find_mode(char *str) { int x, y, depth; if (!gfx_parse_mode_str(str, &x, &y, &depth)) return (0); return (vbe_find_mode_xydm(x, y, depth, -1)); } static void vbe_dump_mode(int modenum, struct modeinfoblock *mi) { printf("0x%x=%dx%dx%d", modenum, mi->XResolution, mi->YResolution, mi->BitsPerPixel); } static bool vbe_get_edid(edid_res_list_t *res) { - struct vesa_edid_info *edid_info; + struct vesa_edid_info *edidp; const uint8_t magic[] = EDID_MAGIC; int ddc_caps; bool ret = false; + if (edid_info != NULL) + return (gfx_get_edid_resolution(edid_info, res)); + ddc_caps = biosvbe_ddc_caps(); if (ddc_caps == 0) { return (ret); } - edid_info = bio_alloc(sizeof (*edid_info)); - if (edid_info == NULL) + edidp = bio_alloc(sizeof(*edidp)); + if (edidp == NULL) return (ret); - memset(edid_info, 0, sizeof (*edid_info)); + memset(edidp, 0, sizeof(*edidp)); - if (VBE_ERROR(biosvbe_ddc_read_edid(0, edid_info))) + if (VBE_ERROR(biosvbe_ddc_read_edid(0, edidp))) goto done; - if (memcmp(edid_info, magic, sizeof (magic)) != 0) + if (memcmp(edidp, magic, sizeof(magic)) != 0) goto done; /* Unknown EDID version. */ - if (edid_info->header.version != 1) + if (edidp->header.version != 1) goto done; - ret = gfx_get_edid_resolution(edid_info, res); + ret = gfx_get_edid_resolution(edidp, res); + edid_info = malloc(sizeof(*edid_info)); + if (edid_info != NULL) + memcpy(edid_info, edidp, sizeof (*edid_info)); done: - bio_free(edid_info, sizeof (*edid_info)); + bio_free(edidp, sizeof(*edidp)); return (ret); } static bool vbe_get_flatpanel(uint32_t *pwidth, uint32_t *pheight) { struct vesa_flat_panel_info *fp_info; bool ret = false; fp_info = bio_alloc(sizeof (*fp_info)); if (fp_info == NULL) return (ret); memset(fp_info, 0, sizeof (*fp_info)); if (VBE_ERROR(biosvbe_ddc_read_flat_panel_info(fp_info))) goto done; *pwidth = fp_info->HSize; *pheight = fp_info->VSize; ret = true; done: bio_free(fp_info, sizeof (*fp_info)); return (ret); } static void vbe_print_memory(unsigned vmem) { char unit = 'K'; vmem /= 1024; if (vmem >= 10240000) { vmem /= 1048576; unit = 'G'; } else if (vmem >= 10000) { vmem /= 1024; unit = 'M'; } printf("Total memory: %u%cB\n", vmem, unit); } static void vbe_print_vbe_info(struct vbeinfoblock *vbep) { char *oemstring = ""; char *oemvendor = "", *oemproductname = "", *oemproductrev = ""; if (vbep->OemStringPtr != 0) oemstring = vbe_farptr(vbep->OemStringPtr); if (vbep->OemVendorNamePtr != 0) oemvendor = vbe_farptr(vbep->OemVendorNamePtr); if (vbep->OemProductNamePtr != 0) oemproductname = vbe_farptr(vbep->OemProductNamePtr); if (vbep->OemProductRevPtr != 0) oemproductrev = vbe_farptr(vbep->OemProductRevPtr); printf("VESA VBE Version %d.%d\n%s\n", vbep->VbeVersion >> 8, vbep->VbeVersion & 0xF, oemstring); if (vbep->OemSoftwareRev != 0) { printf("OEM Version %d.%d, %s (%s, %s)\n", vbep->OemSoftwareRev >> 8, vbep->OemSoftwareRev & 0xF, oemvendor, oemproductname, oemproductrev); } vbe_print_memory(vbep->TotalMemory << 16); printf("Number of Image Pages: %d\n", vbe_mode->LinNumberOfImagePages); } /* List available modes, filter by depth. If depth is -1, list all. */ void vbe_modelist(int depth) { struct modeinfoblock mi; uint16_t mode; int nmodes, idx, nentries; int ddc_caps; uint32_t width, height; bool edid = false; edid_res_list_t res; struct resolution *rp; if (!vbe_check()) return; ddc_caps = biosvbe_ddc_caps(); if (ddc_caps & 3) { printf("DDC"); if (ddc_caps & 1) printf(" [DDC1]"); if (ddc_caps & 2) printf(" [DDC2]"); TAILQ_INIT(&res); edid = vbe_get_edid(&res); if (edid) { printf(": EDID"); while ((rp = TAILQ_FIRST(&res)) != NULL) { printf(" %dx%d", rp->width, rp->height); TAILQ_REMOVE(&res, rp, next); free(rp); } printf("\n"); } else { printf(": no EDID information\n"); } } if (!edid) if (vbe_get_flatpanel(&width, &height)) printf(": Panel %dx%d\n", width, height); nmodes = 0; memset(vbe, 0, sizeof (*vbe)); memcpy(vbe->VbeSignature, "VBE2", 4); if (biosvbe_info(vbe) != VBE_SUCCESS) goto done; if (memcmp(vbe->VbeSignature, "VESA", 4) != 0) goto done; vbe_print_vbe_info(vbe); printf("Modes: "); nentries = vbe_mode_list_size / sizeof(*vbe_mode_list); for (idx = 0; idx < nentries; idx++) { mode = vbe_mode_list[idx]; if (mode == 0xffff) break; if (biosvbe_get_mode_info(mode, &mi) != VBE_SUCCESS) continue; /* we only care about linear modes here */ if (vbe_mode_is_supported(&mi) == 0) continue; /* apply requested filter */ if (depth != -1 && mi.BitsPerPixel != depth) continue; if (nmodes % 4 == 0) printf("\n"); else printf(" "); vbe_dump_mode(mode, &mi); nmodes++; } done: if (nmodes == 0) printf("none found"); printf("\n"); } static void vbe_print_mode(bool verbose __unused) { int nc, mode, i, rc; nc = NCOLORS; memset(vbe, 0, sizeof (*vbe)); if (biosvbe_info(vbe) != VBE_SUCCESS) return; vbe_print_vbe_info(vbe); if (biosvbe_get_mode(&mode) != VBE_SUCCESS) { printf("Error getting current VBE mode\n"); return; } if (biosvbe_get_mode_info(mode, vbe_mode) != VBE_SUCCESS || vbe_mode_is_supported(vbe_mode) == 0) { printf("VBE mode (0x%x) is not framebuffer mode\n", mode); return; } printf("\nCurrent VBE mode: "); vbe_dump_mode(mode, vbe_mode); printf("\n"); printf("%ux%ux%u, stride=%u\n", gfx_state.tg_fb.fb_width, gfx_state.tg_fb.fb_height, gfx_state.tg_fb.fb_bpp, gfx_state.tg_fb.fb_stride * (roundup2(gfx_state.tg_fb.fb_bpp, NBBY) / NBBY)); printf(" frame buffer: address=%jx, size=%jx\n", (uintmax_t)gfx_state.tg_fb.fb_addr, (uintmax_t)gfx_state.tg_fb.fb_size); if (vbe_mode->MemoryModel == 0x6) { printf(" color mask: R=%08x, G=%08x, B=%08x\n", gfx_state.tg_fb.fb_mask_red, gfx_state.tg_fb.fb_mask_green, gfx_state.tg_fb.fb_mask_blue); pager_open(); for (i = 0; i < nc; i++) { printf("%d: R=%02x, G=%02x, B=%02x %08x", i, (cmap[i] & gfx_state.tg_fb.fb_mask_red) >> ffs(gfx_state.tg_fb.fb_mask_red) - 1, (cmap[i] & gfx_state.tg_fb.fb_mask_green) >> ffs(gfx_state.tg_fb.fb_mask_green) - 1, (cmap[i] & gfx_state.tg_fb.fb_mask_blue) >> ffs(gfx_state.tg_fb.fb_mask_blue) - 1, cmap[i]); if (pager_output("\n") != 0) break; } pager_close(); return; } mode = 1; /* get DAC palette width */ rc = biosvbe_palette_format(&mode); if (rc != VBE_SUCCESS) return; printf(" palette format: %x bits per primary\n", mode); if (pe8 == NULL) return; pager_open(); for (i = 0; i < nc; i++) { printf("%d: R=%02x, G=%02x, B=%02x", i, pe8[i].Red, pe8[i].Green, pe8[i].Blue); if (pager_output("\n") != 0) break; } pager_close(); } /* * Try EDID preferred mode, if EDID or the suggested mode is not available, * then try flat panel information. * Fall back to VBE_DEFAULT_MODE. */ int vbe_default_mode(void) { edid_res_list_t res; struct resolution *rp; int modenum; uint32_t width, height; modenum = 0; vbe_get_max_resolution(&width, &height); if (width != 0 && height != 0) modenum = vbe_find_mode_xydm(width, height, -1, -1); TAILQ_INIT(&res); if (vbe_get_edid(&res)) { while ((rp = TAILQ_FIRST(&res)) != NULL) { if (modenum == 0) { modenum = vbe_find_mode_xydm( rp->width, rp->height, -1, -1); } TAILQ_REMOVE(&res, rp, next); free(rp); } } if (modenum == 0 && vbe_get_flatpanel(&width, &height)) { modenum = vbe_find_mode_xydm(width, height, -1, -1); } /* Still no mode? Fall back to default. */ if (modenum == 0) modenum = vbe_find_mode(VBE_DEFAULT_MODE); return (modenum); } COMMAND_SET(vbe, "vbe", "vesa framebuffer mode management", command_vesa); int command_vesa(int argc, char *argv[]) { char *arg, *cp; int modenum = -1, n; if (!vbe_check()) return (CMD_OK); if (argc < 2) goto usage; if (strcmp(argv[1], "list") == 0) { n = -1; if (argc != 2 && argc != 3) goto usage; if (argc == 3) { arg = argv[2]; errno = 0; n = strtoul(arg, &cp, 0); if (errno != 0 || *arg == '\0' || cp[0] != '\0') { snprintf(command_errbuf, sizeof (command_errbuf), "depth should be an integer"); return (CMD_ERROR); } } vbe_modelist(n); return (CMD_OK); } if (strcmp(argv[1], "get") == 0) { bool verbose = false; if (argc != 2) { if (argc > 3 || strcmp(argv[2], "-v") != 0) goto usage; verbose = true; } vbe_print_mode(verbose); return (CMD_OK); } if (strcmp(argv[1], "off") == 0) { if (argc != 2) goto usage; if (gfx_state.tg_mode == VGA_TEXT_MODE) return (CMD_OK); reset_font_flags(); bios_text_font(true); bios_set_text_mode(VGA_TEXT_MODE); cons_update_mode(false); return (CMD_OK); } if (strcmp(argv[1], "on") == 0) { if (argc != 2) goto usage; modenum = vbe_default_mode(); if (modenum == 0) { snprintf(command_errbuf, sizeof (command_errbuf), "%s: no suitable VBE mode number found", argv[0]); return (CMD_ERROR); } } else if (strcmp(argv[1], "set") == 0) { if (argc != 3) goto usage; if (strncmp(argv[2], "0x", 2) == 0) { arg = argv[2]; errno = 0; n = strtoul(arg, &cp, 0); if (errno != 0 || *arg == '\0' || cp[0] != '\0') { snprintf(command_errbuf, sizeof (command_errbuf), "mode should be an integer"); return (CMD_ERROR); } modenum = vbe_find_mode_xydm(0, 0, 0, n); } else if (strchr(argv[2], 'x') != NULL) { modenum = vbe_find_mode(argv[2]); } } else { goto usage; } if (modenum == 0) { snprintf(command_errbuf, sizeof (command_errbuf), "%s: mode %s not supported by firmware\n", argv[0], argv[2]); return (CMD_ERROR); } if (modenum >= VESA_MODE_BASE) { if (gfx_state.tg_mode != modenum) { reset_font_flags(); bios_text_font(false); vbe_set_mode(modenum); cons_update_mode(true); } return (CMD_OK); } else { snprintf(command_errbuf, sizeof (command_errbuf), "%s: mode %s is not framebuffer mode\n", argv[0], argv[2]); return (CMD_ERROR); } usage: snprintf(command_errbuf, sizeof (command_errbuf), "usage: %s on | off | get | list [depth] | " "set ", argv[0]); return (CMD_ERROR); }