From 3630506b9daec9167a89bc4525638ea45a00769e Mon Sep 17 00:00:00 2001 From: Toomas Soome Date: Mon, 21 Dec 2020 07:31:16 +0200 Subject: [PATCH] loader: implement framebuffer console Draw console on efi. Add vbe framebuffer for BIOS loader (vbe off, vbe on, vbe list, vbe set xxx). autoload font (/boot/fonts) based on resolution and font size. Add command loadfont (set font by file) and variable screen.font (set font by size). Pass loaded font to kernel. Export variables: screen.height screen.width screen.depth Add gfx primitives to draw the screen and put png image on the screen. Rework menu draw to iterate list of consoles to enamble device specific output. Probably something else I forgot... Relnotes: yes Differential Revision: https://reviews.freebsd.org/D27420 --- stand/Makefile | 2 + stand/common/bootstrap.h | 5 + stand/common/gfx_fb.c | 2641 +++++++++++++++++++++++ stand/common/gfx_fb.h | 274 +++ stand/common/module.c | 89 +- stand/defaults/loader.conf.5 | 4 +- stand/efi/include/efilib.h | 1 - stand/efi/libefi/Makefile | 2 +- stand/efi/libefi/efi_console.c | 490 +++-- stand/efi/loader/Makefile | 14 +- stand/efi/loader/bootinfo.c | 17 +- stand/efi/loader/framebuffer.c | 15 +- stand/efi/loader/main.c | 3 +- stand/ficl.mk | 2 +- stand/ficl/Makefile | 2 + stand/ficl/loader.c | 185 ++ stand/fonts/INDEX.fonts | 66 + stand/fonts/Makefile | 81 + stand/forth/beastie.4th | 4 + stand/forth/brand-fbsd.4th | 12 + stand/forth/brand.4th | 4 + stand/forth/color.4th | 12 +- stand/forth/frames.4th | 14 + stand/forth/logo-orb.4th | 12 + stand/forth/menu.4th | 34 +- stand/forth/support.4th | 91 +- stand/i386/libi386/Makefile | 9 +- stand/i386/libi386/bootinfo.c | 11 + stand/i386/libi386/bootinfo32.c | 3 + stand/i386/libi386/bootinfo64.c | 3 + stand/i386/libi386/libi386.h | 1 + stand/i386/libi386/vbe.c | 1226 +++++++++++ stand/i386/libi386/vbe.h | 163 ++ stand/i386/libi386/vidconsole.c | 659 ++++-- stand/i386/loader/Makefile | 13 +- stand/i386/loader/main.c | 10 +- stand/images/Makefile | 9 + stand/images/freebsd-brand-rev.png | Bin 0 -> 7709 bytes stand/images/freebsd-brand.png | Bin 0 -> 13788 bytes stand/images/freebsd-logo-rev.png | Bin 0 -> 43898 bytes stand/liblua/Makefile | 2 + stand/liblua/lutils.c | 192 ++ stand/loader.mk | 6 + stand/lua/color.lua | 2 +- stand/lua/core.lua | 13 + stand/lua/drawer.lua | 61 +- stand/lua/gfx-orb.lua | 4 +- stand/userboot/userboot/Makefile | 1 + stand/userboot/userboot/userboot_cons.c | 5 + sys/sys/font.h | 7 +- sys/teken/teken.h | 1 + 51 files changed, 6073 insertions(+), 404 deletions(-) create mode 100644 stand/common/gfx_fb.c create mode 100644 stand/common/gfx_fb.h create mode 100644 stand/fonts/INDEX.fonts create mode 100644 stand/fonts/Makefile create mode 100644 stand/i386/libi386/vbe.c create mode 100644 stand/i386/libi386/vbe.h create mode 100644 stand/images/Makefile create mode 100644 stand/images/freebsd-brand-rev.png create mode 100644 stand/images/freebsd-brand.png create mode 100644 stand/images/freebsd-logo-rev.png diff --git a/stand/Makefile b/stand/Makefile index 46e0856e45f..7b871d9164d 100644 --- a/stand/Makefile +++ b/stand/Makefile @@ -25,6 +25,8 @@ S.${MK_FORTH}+= forth S.${MK_LOADER_LUA}+= liblua S.${MK_LOADER_LUA}+= lua S.yes+= defaults +S.yes+= fonts +S.yes+= images S.yes+= man .if ${MK_FORTH} != "no" diff --git a/stand/common/bootstrap.h b/stand/common/bootstrap.h index a0640c2bac2..f671dba96e6 100644 --- a/stand/common/bootstrap.h +++ b/stand/common/bootstrap.h @@ -32,6 +32,7 @@ #include #include #include +#include #include "readin.h" @@ -119,6 +120,8 @@ struct console }; extern struct console *consoles[]; void cons_probe(void); +bool cons_update_mode(bool); +void autoload_font(bool); /* * Plug-and-play enumerator/configurator interface. @@ -258,6 +261,8 @@ int file_addmodule(struct preloaded_file *, char *, int, struct kernel_module **); void file_removemetadata(struct preloaded_file *fp); +vm_offset_t build_font_module(vm_offset_t); + /* MI module loaders */ #ifdef __elfN /* Relocation types. */ diff --git a/stand/common/gfx_fb.c b/stand/common/gfx_fb.c new file mode 100644 index 00000000000..f73899c5052 --- /dev/null +++ b/stand/common/gfx_fb.c @@ -0,0 +1,2641 @@ +/*- + * 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$ + */ + +#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/8x16v.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 */ +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 = NCMAP * NCMAP * NCMAP; + 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; + + 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; +} + +/* Our GFX Block transfer toolkit. */ +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; + } + 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, *buffer, *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; + + if (bgra) { + buffer = NULL; + } else { + buffer = malloc(copybytes); + if (buffer == NULL) + return (ENOMEM); + } + + for (sy = SourceY, dy = DestinationY; dy < Height + DestinationY; + sy++, dy++) { + off = sy * pitch + SourceX * bpp; + source = gfx_get_fb_address() + off; + + if (bgra) { + destination = (uint8_t *)BltBuffer + dy * Delta + + DestinationX * sizeof (*p); + } else { + destination = buffer; + } + + bcopy(source, destination, copybytes); + + if (!bgra) { + for (x = 0; x < Width; x++) { + uint32_t c = 0; + + p = (void *)((uint8_t *)BltBuffer + + dy * Delta + + (DestinationX + x) * sizeof (*p)); + sb = buffer + 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; + } + + 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; + } + } + } + } + + free(buffer); + 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, *buffer; + 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; + + if (bgra) { + buffer = NULL; + } else { + buffer = malloc(copybytes); + if (buffer == NULL) + return (ENOMEM); + } + 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); + } 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(buffer, copybytes, + off, (c < 16) ? + cons_to_vga_colors[c] : c); + break; + case 2: + gfx_mem_wr2(buffer, copybytes, + off, c); + break; + case 3: + gfx_mem_wr1(buffer, copybytes, + off, (c >> 16) & 0xff); + gfx_mem_wr1(buffer, copybytes, + off + 1, (c >> 8) & 0xff); + gfx_mem_wr1(buffer, copybytes, + off + 2, c & 0xff); + break; + case 4: + gfx_mem_wr4(buffer, copybytes, + x * bpp, c); + break; + } + } + source = buffer; + } + + bcopy(source, destination, copybytes); + } + + free(buffer); + 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; + + if (gop != NULL && (gop->Mode->Info->PixelFormat == PixelBltOnly || + gfx_state.tg_fb.fb_addr == 0)) { + 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; + } +} + +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) { + *rows = (height - BORDER_PIXELS) / font->vfbd_height; + *cols = (width - BORDER_PIXELS) / font->vfbd_width; + return (font); + } + + /* + * Find best font for these dimensions, or use default + * + * A 1 pixel border is the absolute minimum we could have + * as a border around the text window (BORDER_PIXELS = 2), + * however a slightly larger border not only looks better + * but for the fonts currently statically built into the + * emulator causes much better font selection for the + * normal range of screen resolutions. + */ + STAILQ_FOREACH(fl, &fonts, font_next) { + font = fl->font_data; + if ((((*rows * font->vfbd_height) + BORDER_PIXELS) <= height) && + (((*cols * font->vfbd_width) + BORDER_PIXELS) <= 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 - BORDER_PIXELS) / font->vfbd_height; + *cols = (width - BORDER_PIXELS) / 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 - BORDER_PIXELS) / font->vfbd_height; + *cols = (width - BORDER_PIXELS) / 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 new file mode 100644 index 00000000000..1424b822313 --- /dev/null +++ b/stand/common/gfx_fb.h @@ -0,0 +1,274 @@ +/*- + * 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; + +#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]; + +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; +} 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/common/module.c b/stand/common/module.c index bd8d091d91c..247fc54b602 100644 --- a/stand/common/module.c +++ b/stand/common/module.c @@ -38,6 +38,8 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include +#include #if defined(LOADER_FDT_SUPPORT) #include @@ -91,7 +93,6 @@ static char *kld_ext_list[] = { NULL }; - /* * load an object, either a disk file or code module. * @@ -616,6 +617,92 @@ file_load_dependencies(struct preloaded_file *base_file) return (error); } +vm_offset_t +build_font_module(vm_offset_t addr) +{ + vt_font_bitmap_data_t *bd; + struct vt_font *fd; + struct preloaded_file *fp; + size_t size; + uint32_t checksum; + int i; + struct font_info fi; + struct fontlist *fl; + uint64_t fontp; + + if (STAILQ_EMPTY(&fonts)) + return (addr); + + /* We can't load first */ + if ((file_findfile(NULL, NULL)) == NULL) { + printf("Can not load font module: %s\n", + "the kernel is not loaded"); + return (addr); + } + + /* helper pointers */ + bd = NULL; + STAILQ_FOREACH(fl, &fonts, font_next) { + if (gfx_state.tg_font.vf_width == fl->font_data->vfbd_width && + gfx_state.tg_font.vf_height == fl->font_data->vfbd_height) { + /* + * Kernel does have better built in font. + */ + if (fl->font_flags == FONT_BUILTIN) + return (addr); + + bd = fl->font_data; + break; + } + } + if (bd == NULL) + return (addr); + fd = bd->vfbd_font; + + fi.fi_width = fd->vf_width; + checksum = fi.fi_width; + fi.fi_height = fd->vf_height; + checksum += fi.fi_height; + fi.fi_bitmap_size = bd->vfbd_uncompressed_size; + checksum += fi.fi_bitmap_size; + + size = roundup2(sizeof (struct font_info), 8); + for (i = 0; i < VFNT_MAPS; i++) { + fi.fi_map_count[i] = fd->vf_map_count[i]; + checksum += fi.fi_map_count[i]; + size += fd->vf_map_count[i] * sizeof (struct vfnt_map); + size += roundup2(size, 8); + } + size += bd->vfbd_uncompressed_size; + + fi.fi_checksum = -checksum; + + fp = file_findfile(NULL, "elf kernel"); + if (fp == NULL) + fp = file_findfile(NULL, "elf64 kernel"); + if (fp == NULL) + panic("can't find kernel file"); + + fontp = addr; + addr += archsw.arch_copyin(&fi, addr, sizeof (struct font_info)); + addr = roundup2(addr, 8); + + /* Copy maps. */ + for (i = 0; i < VFNT_MAPS; i++) { + if (fd->vf_map_count[i] != 0) { + addr += archsw.arch_copyin(fd->vf_map[i], addr, + fd->vf_map_count[i] * sizeof (struct vfnt_map)); + addr = roundup2(addr, 8); + } + } + + /* Copy the bitmap. */ + addr += archsw.arch_copyin(fd->vf_bytes, addr, fi.fi_bitmap_size); + + /* Looks OK so far; populate control structure */ + file_addmetadata(fp, MODINFOMD_FONT, sizeof(fontp), &fontp); + return (addr); +} #ifdef LOADER_VERIEXEC_VECTX #define VECTX_HANDLE(fd) vctx diff --git a/stand/defaults/loader.conf.5 b/stand/defaults/loader.conf.5 index 1053191ec18..22fe9812a33 100644 --- a/stand/defaults/loader.conf.5 +++ b/stand/defaults/loader.conf.5 @@ -238,7 +238,9 @@ replacing it with .Dq spinning character (useful for embedded products and such). .It Va efi_max_resolution -Specify the maximum desired resolution for the EFI console. +.It Va vbe_max_resolution +Specify the maximum desired resolution for the EFI or VESA BIOS Extension (VBE) +framebuffer console. The following values are accepted: .Bl -column "WidthxHeight" .It Sy Value Ta Sy Resolution diff --git a/stand/efi/include/efilib.h b/stand/efi/include/efilib.h index 6f1dbac9873..b9b9030afd7 100644 --- a/stand/efi/include/efilib.h +++ b/stand/efi/include/efilib.h @@ -108,7 +108,6 @@ void efi_time_init(void); void efi_time_fini(void); int parse_uefi_con_out(void); -bool efi_cons_update_mode(void); EFI_STATUS efi_main(EFI_HANDLE Ximage, EFI_SYSTEM_TABLE* Xsystab); EFI_STATUS main(int argc, CHAR16 *argv[]); diff --git a/stand/efi/libefi/Makefile b/stand/efi/libefi/Makefile index 8006893d0cb..010754b5238 100644 --- a/stand/efi/libefi/Makefile +++ b/stand/efi/libefi/Makefile @@ -49,7 +49,7 @@ CFLAGS+= -fPIC -mno-red-zone .endif CFLAGS+= -I${EFIINC} CFLAGS+= -I${EFIINCMD} -CFLAGS.efi_console.c+= -I${SRCTOP}/sys/teken +CFLAGS.efi_console.c+= -I${SRCTOP}/sys/teken -I${SRCTOP}/contrib/pnglite CFLAGS.teken.c+= -I${SRCTOP}/sys/teken .if ${MK_LOADER_ZFS} != "no" CFLAGS+= -I${ZFSSRC} diff --git a/stand/efi/libefi/efi_console.c b/stand/efi/libefi/efi_console.c index a72fb4d561d..22adcd3a00e 100644 --- a/stand/efi/libefi/efi_console.c +++ b/stand/efi/libefi/efi_console.c @@ -27,17 +27,22 @@ #include __FBSDID("$FreeBSD$"); +#include #include #include #include #include - +#include +#include #include "bootstrap.h" +extern EFI_GUID gop_guid; +extern int efi_find_framebuffer(struct efi_fb *efifb); 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? */ @@ -59,6 +64,9 @@ 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; @@ -77,16 +85,16 @@ static teken_funcs_t tf = { .tf_respond = efi_cons_respond, }; -teken_t teken; -teken_pos_t tp; - -struct text_pixel { - teken_char_t c; - teken_attr_t a; +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, }; -static struct text_pixel *buffer; - #define KEYBUFSZ 10 static unsigned keybuf[KEYBUFSZ]; /* keybuf for extended codes */ static int key_pending; @@ -116,6 +124,7 @@ 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", @@ -128,6 +137,28 @@ struct console efi_console = { 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; + + 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. */ @@ -137,33 +168,30 @@ efi_cons_bell(void *s __unused) } static void -efi_text_cursor(void *s __unused, const teken_pos_t *p) +efi_text_cursor(void *arg, const teken_pos_t *p) { - UINTN row, col; + teken_gfx_t *state = arg; + UINTN col, row; - (void) conout->QueryMode(conout, conout->Mode->Mode, &col, &row); + row = p->tp_row; + if (p->tp_row >= state->tg_tp.tp_row) + row = state->tg_tp.tp_row - 1; - if (p->tp_col == col) - col = p->tp_col - 1; - else - col = p->tp_col; - - if (p->tp_row == row) - row = p->tp_row - 1; - else - row = p->tp_row; + 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(const teken_pos_t *p, bool autoscroll) +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 = buffer + p->tp_col + p->tp_row * tp.tp_col; + 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); @@ -184,10 +212,10 @@ efi_text_printchar(const teken_pos_t *p, bool autoscroll) conout->SetCursorPosition(conout, p->tp_col, p->tp_row); - /* to prvent autoscroll, skip print of lower right char */ + /* to prevent autoscroll, skip print of lower right char */ if (!autoscroll && - p->tp_row == tp.tp_row - 1 && - p->tp_col == tp.tp_col - 1) + p->tp_row == state->tg_tp.tp_row - 1 && + p->tp_col == state->tg_tp.tp_col - 1) return; (void) conout->SetAttribute(conout, attr); @@ -196,58 +224,80 @@ efi_text_printchar(const teken_pos_t *p, bool autoscroll) } static void -efi_text_putchar(void *s __unused, const teken_pos_t *p, teken_char_t c, +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 * tp.tp_col; - buffer[idx].c = c; - buffer[idx].a = *a; - efi_text_printchar(p, false); + 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 *s, const teken_rect_t *r, teken_char_t c, +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; - UINTN row, col; - (void) conout->QueryMode(conout, conout->Mode->Mode, &col, &row); - - conout->EnableCursor(conout, FALSE); + 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(s, &p, c, a); - conout->EnableCursor(conout, TRUE); -} - -static bool -efi_same_pixel(struct text_pixel *px1, struct text_pixel *px2) -{ - if (px1->c != px2->c) - 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); + efi_text_putchar(state, &p, c, a); + if (state->tg_cursor_visible) + conout->EnableCursor(conout, TRUE); } static void -efi_text_copy(void *ptr __unused, const teken_rect_t *r, const teken_pos_t *p) +efi_text_copy_line(teken_gfx_t *state, int ncol, teken_pos_t *s, + teken_pos_t *d, bool scroll) { - int srow, drow; - int nrow, ncol, x, y; /* Has to be signed - >= 0 comparison */ + 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; /* @@ -262,90 +312,47 @@ efi_text_copy(void *ptr __unused, const teken_rect_t *r, const teken_pos_t *p) * Check if we do copy whole screen. */ if (p->tp_row == 0 && p->tp_col == 0 && - nrow == tp.tp_row - 2 && ncol == tp.tp_col - 2) + nrow == state->tg_tp.tp_row - 2 && ncol == state->tg_tp.tp_col - 2) scroll = true; - conout->EnableCursor(conout, FALSE); - if (p->tp_row < r->tr_begin.tp_row) { - /* Copy from bottom to top. */ - for (y = 0; y < nrow; y++) { - d.tp_row = p->tp_row + y; - s.tp_row = r->tr_begin.tp_row + y; - drow = d.tp_row * tp.tp_col; - srow = s.tp_row * tp.tp_col; - for (x = 0; x < ncol; x++) { - d.tp_col = p->tp_col + x; - s.tp_col = r->tr_begin.tp_col + x; + 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; - if (!efi_same_pixel( - &buffer[d.tp_col + drow], - &buffer[s.tp_col + srow])) { - buffer[d.tp_col + drow] = - buffer[s.tp_col + srow]; - if (!scroll) - efi_text_printchar(&d, false); - } else if (scroll) { - /* - * Draw last char and trigger - * scroll. - */ - if (y == nrow - 1 && - x == ncol - 1) { - efi_text_printchar(&d, true); - } - } - } + /* 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 { - /* Copy from top to bottom. */ - if (p->tp_col < r->tr_begin.tp_col) { - /* Copy from right to left. */ - for (y = nrow - 1; y >= 0; y--) { - d.tp_row = p->tp_row + y; - s.tp_row = r->tr_begin.tp_row + y; - drow = d.tp_row * tp.tp_col; - srow = s.tp_row * tp.tp_col; - for (x = 0; x < ncol; x++) { - d.tp_col = p->tp_col + x; - s.tp_col = r->tr_begin.tp_col + x; + for (y = nrow - 1; y >= 0; y--) { + s.tp_row = r->tr_begin.tp_row + y; + d.tp_row = p->tp_row + y; - if (!efi_same_pixel( - &buffer[d.tp_col + drow], - &buffer[s.tp_col + srow])) { - buffer[d.tp_col + drow] = - buffer[s.tp_col + srow]; - efi_text_printchar(&d, false); - } - } - } - } else { - /* Copy from left to right. */ - for (y = nrow - 1; y >= 0; y--) { - d.tp_row = p->tp_row + y; - s.tp_row = r->tr_begin.tp_row + y; - drow = d.tp_row * tp.tp_col; - srow = s.tp_row * tp.tp_col; - for (x = ncol - 1; x >= 0; x--) { - d.tp_col = p->tp_col + x; - s.tp_col = r->tr_begin.tp_col + x; - - if (!efi_same_pixel( - &buffer[d.tp_col + drow], - &buffer[s.tp_col + srow])) { - buffer[d.tp_col + drow] = - buffer[s.tp_col + srow]; - efi_text_printchar(&d, false); - } - } - } + efi_text_copy_line(state, ncol, &s, &d, false); } } - conout->EnableCursor(conout, TRUE); + + /* display the cursor */ + if (state->tg_cursor_visible) + conout->EnableCursor(conout, TRUE); } static void -efi_text_param(void *s __unused, int cmd, unsigned int value) +efi_text_param(void *arg, int cmd, unsigned int value) { + teken_gfx_t *state = arg; + switch (cmd) { case TP_SETLOCALCURSOR: /* @@ -357,10 +364,13 @@ efi_text_param(void *s __unused, int cmd, unsigned int value) value = (value == 1) ? 0 : 1; /* FALLTHROUGH */ case TP_SHOWCURSOR: - if (value == 1) + if (value != 0) { conout->EnableCursor(conout, TRUE); - else + state->tg_cursor_visible = true; + } else { conout->EnableCursor(conout, FALSE); + state->tg_cursor_visible = false; + } break; default: /* Not yet implemented */ @@ -470,7 +480,7 @@ efi_set_colors(struct env_var *ev, int flags, const void *value) evalue = value; } - ap = teken_get_defattr(&teken); + ap = teken_get_defattr(&gfx_state.tg_teken); a = *ap; if (strcmp(ev->ev_name, "teken.fg_color") == 0) { /* is it already set? */ @@ -484,8 +494,15 @@ efi_set_colors(struct env_var *ev, int flags, const void *value) 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_set_defattr(&teken, &a); + teken_input(&gfx_state.tg_teken, "\e[2J", 4); return (CMD_OK); } @@ -823,19 +840,100 @@ efi_term_emu(int 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 -efi_cons_update_mode(void) +cons_update_mode(bool use_gfx_mode) { UINTN cols, rows; const teken_attr_t *a; teken_attr_t attr; EFI_STATUS status; - char env[8], *ptr; + EFI_GRAPHICS_OUTPUT *gop = NULL; + struct efi_fb efifb; + char env[10], *ptr; + + if (use_gfx_mode == true) { + gfx_state.tg_fb_type = FB_GOP; + if (gfx_state.tg_private == NULL) { + (void) BS->LocateProtocol(&gop_guid, NULL, + (void **)&gop); + gfx_state.tg_private = gop; + } else { + gop = gfx_state.tg_private; + } + + /* + * We have FB but no GOP - it must be UGA. + */ + if (gop == NULL) + gfx_state.tg_fb_type = FB_UGA; + + if (efi_find_framebuffer(&efifb) == 0) { + int roff, goff, boff; + + 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; + roff = ffs(efifb.fb_mask_red) - 1; + goff = ffs(efifb.fb_mask_green) - 1; + boff = ffs(efifb.fb_mask_blue) - 1; + + (void) generate_cons_palette(cmap, COLOR_FORMAT_RGB, + efifb.fb_mask_red >> roff, roff, + efifb.fb_mask_green >> goff, goff, + efifb.fb_mask_blue >> boff, boff); + gfx_state.tg_fb.fb_bpp = fls( + efifb.fb_mask_red | efifb.fb_mask_green | + efifb.fb_mask_blue | efifb.fb_mask_reserved); + } + } else { + gfx_state.tg_fb_type = FB_TEXT; + } status = conout->QueryMode(conout, conout->Mode->Mode, &cols, &rows); if (EFI_ERROR(status) || cols * rows == 0) { - cols = 80; - rows = 24; + cols = TEXT_COLS; + rows = TEXT_ROWS; } /* @@ -853,20 +951,74 @@ efi_cons_update_mode(void) */ mode = parse_uefi_con_out(); if ((mode & (RB_SERIAL | RB_MULTIPLE)) == 0) { - if (buffer != NULL) { - if (tp.tp_row == rows && tp.tp_col == cols) - return (true); - free(buffer); + 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 { - teken_init(&teken, &tf, NULL); + 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 assume 80x24 terminal 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. + */ + gfx_state.tg_tp.tp_row = TEXT_ROWS; + gfx_state.tg_tp.tp_col = TEXT_COLS; + 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); } - tp.tp_row = rows; - tp.tp_col = cols; - buffer = malloc(rows * cols * sizeof(*buffer)); - if (buffer != NULL) { - teken_set_winsize(&teken, &tp); - a = teken_get_defattr(&teken); + /* 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; /* @@ -880,7 +1032,7 @@ efi_cons_update_mode(void) ptr = getenv("teken.bg_color"); attr.ta_bgcolor = strtol(ptr, NULL, 10); - teken_set_defattr(&teken, &attr); + teken_set_defattr(&gfx_state.tg_teken, &attr); } else { snprintf(env, sizeof(env), "%d", attr.ta_fgcolor); @@ -891,18 +1043,12 @@ efi_cons_update_mode(void) env_setenv("teken.bg_color", EV_VOLATILE, env, efi_set_colors, env_nounset); } - - for (int row = 0; row < rows; row++) { - for (int col = 0; col < cols; col++) { - buffer[col + row * tp.tp_col].c = ' '; - buffer[col + row * tp.tp_col].a = attr; - } - } } } + if (screen_buffer == NULL) { + conout->EnableCursor(conout, TRUE); #ifdef TERM_EMU - if (buffer == NULL) { conout->SetAttribute(conout, EFI_TEXT_ATTR(DEFAULT_FGCOLOR, DEFAULT_BGCOLOR)); end_term(); @@ -910,8 +1056,23 @@ efi_cons_update_mode(void) 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); @@ -926,8 +1087,13 @@ efi_cons_init(int arg) { EFI_STATUS status; - conout->EnableCursor(conout, TRUE); - if (efi_cons_update_mode()) + 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); @@ -1035,12 +1201,12 @@ efi_cons_putchar(int 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 || buffer == NULL) { + if ((mode & (RB_SERIAL | RB_MULTIPLE)) != 0 || screen_buffer == NULL) { input_byte(ch); return; } - teken_input(&teken, &ch, sizeof (ch)); + teken_input(&gfx_state.tg_teken, &ch, sizeof (ch)); } static int diff --git a/stand/efi/loader/Makefile b/stand/efi/loader/Makefile index a0e7f95b820..3cf2df933cc 100644 --- a/stand/efi/loader/Makefile +++ b/stand/efi/loader/Makefile @@ -22,7 +22,9 @@ SRCS= autoload.c \ framebuffer.c \ main.c \ self_reloc.c \ - vers.c + vers.c \ + gfx_fb.c \ + 8x16.c CFLAGS+= -I${.CURDIR}/../loader .if ${MK_LOADER_ZFS} != "no" @@ -33,6 +35,11 @@ CFLAGS+= -DEFI_ZFS_BOOT HAVE_ZFS= yes .endif +CFLAGS.gfx_fb.c += -I$(SRCTOP)/sys/teken +CFLAGS.gfx_fb.c += -I${SRCTOP}/sys/cddl/contrib/opensolaris/common/lz4 +CFLAGS.gfx_fb.c += -I${SRCTOP}/contrib/pnglite +CFLAGS.gfx_fb.c += -DHAVE_MEMCPY -I${SRCTOP}/sys/contrib/zlib + # We implement a slightly non-standard %S in that it always takes a # CHAR16 that's common in UEFI-land instead of a wchar_t. This only # seems to matter on arm64 where wchar_t defaults to an int instead @@ -74,6 +81,11 @@ VERSION_FILE= ${.CURDIR}/../loader/version # Always add MI sources .include "${BOOTSRC}/loader.mk" +CLEANFILES+= 8x16.c + +8x16.c: ${SRCTOP}/contrib/terminus/ter-u16v.bdf + vtfontcvt -f compressed-source -o ${.TARGET} ${.ALLSRC} + FILES+= ${LOADER}.efi FILESMODE_${LOADER}.efi= ${BINMODE} diff --git a/stand/efi/loader/bootinfo.c b/stand/efi/loader/bootinfo.c index b1df11acd53..108f46c5f9c 100644 --- a/stand/efi/loader/bootinfo.c +++ b/stand/efi/loader/bootinfo.c @@ -447,7 +447,7 @@ bi_load(char *args, vm_offset_t *modulep, vm_offset_t *kernendp) */ uint32_t mdt[] = { MODINFOMD_SSYM, MODINFOMD_ESYM, MODINFOMD_KERNEND, - MODINFOMD_ENVP, + MODINFOMD_ENVP, MODINFOMD_FONT, #if defined(LOADER_FDT_SUPPORT) MODINFOMD_DTBP #endif @@ -480,6 +480,11 @@ bi_load(char *args, vm_offset_t *modulep, vm_offset_t *kernendp) /* Pad to a page boundary. */ addr = roundup(addr, PAGE_SIZE); + addr = build_font_module(addr); + + /* Pad to a page boundary. */ + addr = roundup(addr, PAGE_SIZE); + /* Copy our environment. */ envp = addr; addr = bi_copyenv(addr); @@ -503,17 +508,17 @@ bi_load(char *args, vm_offset_t *modulep, vm_offset_t *kernendp) if (kfp == NULL) panic("can't find kernel file"); kernend = 0; /* fill it in later */ - file_addmetadata(kfp, MODINFOMD_HOWTO, sizeof howto, &howto); - file_addmetadata(kfp, MODINFOMD_ENVP, sizeof envp, &envp); + file_addmetadata(kfp, MODINFOMD_HOWTO, sizeof(howto), &howto); + file_addmetadata(kfp, MODINFOMD_ENVP, sizeof(envp), &envp); #if defined(LOADER_FDT_SUPPORT) if (dtb_size) - file_addmetadata(kfp, MODINFOMD_DTBP, sizeof dtbp, &dtbp); + file_addmetadata(kfp, MODINFOMD_DTBP, sizeof(dtbp), &dtbp); else printf("WARNING! Trying to fire up the kernel, but no " "device tree blob found!\n"); #endif - file_addmetadata(kfp, MODINFOMD_KERNEND, sizeof kernend, &kernend); - file_addmetadata(kfp, MODINFOMD_FW_HANDLE, sizeof ST, &ST); + file_addmetadata(kfp, MODINFOMD_KERNEND, sizeof(kernend), &kernend); + file_addmetadata(kfp, MODINFOMD_FW_HANDLE, sizeof(ST), &ST); #ifdef LOADER_GELI_SUPPORT geli_export_key_metadata(kfp); #endif diff --git a/stand/efi/loader/framebuffer.c b/stand/efi/loader/framebuffer.c index dce49c69ab0..fccbd82a09f 100644 --- a/stand/efi/loader/framebuffer.c +++ b/stand/efi/loader/framebuffer.c @@ -40,9 +40,10 @@ __FBSDID("$FreeBSD$"); #include #include +#include "bootstrap.h" #include "framebuffer.h" -static EFI_GUID gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_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; @@ -586,7 +587,7 @@ gop_autoresize(EFI_GRAPHICS_OUTPUT *gop) mode, EFI_ERROR_CODE(status)); return (CMD_ERROR); } - (void) efi_cons_update_mode(); + (void) cons_update_mode(true); } return (CMD_OK); } @@ -611,7 +612,7 @@ text_autoresize() } if (max_dim > 0) conout->SetMode(conout, best_mode); - (void) efi_cons_update_mode(); + (void) cons_update_mode(true); return (CMD_OK); } @@ -699,8 +700,10 @@ command_gop(int argc, char *argv[]) argv[0], mode, EFI_ERROR_CODE(status)); return (CMD_ERROR); } - (void) efi_cons_update_mode(); - } else if (!strcmp(argv[1], "get")) { + (void) cons_update_mode(true); + } else if (strcmp(argv[1], "off") == 0) { + (void) cons_update_mode(false); + } else if (strcmp(argv[1], "get") == 0) { if (argc != 2) goto usage; efifb_from_gop(&efifb, gop->Mode, gop->Mode->Info); @@ -728,7 +731,7 @@ command_gop(int argc, char *argv[]) usage: snprintf(command_errbuf, sizeof(command_errbuf), - "usage: %s [list | get | set ]", argv[0]); + "usage: %s [list | get | set | off]", argv[0]); return (CMD_ERROR); } diff --git a/stand/efi/loader/main.c b/stand/efi/loader/main.c index aad285f5909..ca41cd4a261 100644 --- a/stand/efi/loader/main.c +++ b/stand/efi/loader/main.c @@ -1155,6 +1155,7 @@ main(int argc, CHAR16 *argv[]) !interactive_interrupt("Failed to find bootable partition")) return (EFI_NOT_FOUND); + autoload_font(false); /* Set up the font list for console. */ efi_init_environment(); #if !defined(__arm__) @@ -1354,7 +1355,7 @@ command_mode(int argc, char *argv[]) printf("couldn't set mode %d\n", mode); return (CMD_ERROR); } - (void) efi_cons_update_mode(); + (void) cons_update_mode(true); return (CMD_OK); } diff --git a/stand/ficl.mk b/stand/ficl.mk index 0f96532cc65..caa4aab951c 100644 --- a/stand/ficl.mk +++ b/stand/ficl.mk @@ -15,7 +15,7 @@ CFLAGS+= -fPIC .endif CFLAGS+= -I${FICLSRC} -I${FICLSRC}/${FICL_CPUARCH} -I${LDRSRC} -CFLAGS+= -DBF_DICTSIZE=15000 +CFLAGS+= -DBF_DICTSIZE=30000 .if ${MK_LOADER_VERIEXEC} != "no" CFLAGS+= -DLOADER_VERIEXEC -I${SRCTOP}/lib/libsecureboot/h diff --git a/stand/ficl/Makefile b/stand/ficl/Makefile index 35730851d88..0f70286ff7d 100644 --- a/stand/ficl/Makefile +++ b/stand/ficl/Makefile @@ -12,6 +12,8 @@ BASE_SRCS= dict.c ficl.c fileaccess.c float.c loader.c math64.c \ SRCS= ${BASE_SRCS} sysdep.c softcore.c CLEANFILES+= softcore.c testmain testmain.o +CFLAGS.loader.c += -I${SRCTOP}/sys/teken +CFLAGS.loader.c += -I${SRCTOP}/contrib/pnglite .ifmake testmain CFLAGS= -DTESTMAIN -D_TESTMAIN CFLAGS+= -I${FICLSRC} -I${FICLSRC}/${FICL_CPUARCH} -I${LDRSRC} diff --git a/stand/ficl/loader.c b/stand/ficl/loader.c index 2d1e1b908e9..fca2b7421ff 100644 --- a/stand/ficl/loader.c +++ b/stand/ficl/loader.c @@ -46,6 +46,8 @@ #include "bootstrap.h" #include #include +#include +#include #include "ficl.h" /* FreeBSD's loader interaction words and extras @@ -65,6 +67,182 @@ * .# ( value -- ) */ +#ifndef TESTMAIN +/* ( flags x1 y1 x2 y2 -- flag ) */ +void +ficl_term_putimage(FICL_VM *pVM) +{ + char *namep, *name; + int names; + unsigned long ret = FICL_FALSE; + uint32_t x1, y1, x2, y2, f; + png_t png; + int error; + +#if FICL_ROBUST > 1 + vmCheckStack(pVM, 7, 1); +#endif + names = stackPopINT(pVM->pStack); + namep = (char *) stackPopPtr(pVM->pStack); + y2 = stackPopINT(pVM->pStack); + x2 = stackPopINT(pVM->pStack); + y1 = stackPopINT(pVM->pStack); + x1 = stackPopINT(pVM->pStack); + f = stackPopINT(pVM->pStack); + + x1 = gfx_state.tg_origin.tp_col + x1 * gfx_state.tg_font.vf_width; + y1 = gfx_state.tg_origin.tp_row + y1 * gfx_state.tg_font.vf_height; + if (x2 != 0) { + x2 = gfx_state.tg_origin.tp_col + + x2 * gfx_state.tg_font.vf_width; + } + if (y2 != 0) { + y2 = gfx_state.tg_origin.tp_row + + y2 * gfx_state.tg_font.vf_height; + } + + name = ficlMalloc(names + 1); + if (!name) + vmThrowErr(pVM, "Error: out of memory"); + (void) strncpy(name, namep, names); + name[names] = '\0'; + + if ((error = png_open(&png, name)) != PNG_NO_ERROR) { + if (f & FL_PUTIMAGE_DEBUG) + printf("%s\n", png_error_string(error)); + } else { + if (gfx_fb_putimage(&png, x1, y1, x2, y2, f) == 0) + ret = FICL_TRUE; /* success */ + (void) png_close(&png); + } + ficlFree(name); + stackPushUNS(pVM->pStack, ret); +} + +/* ( flags x1 y1 x2 y2 -- flag ) */ +void +ficl_fb_putimage(FICL_VM *pVM) +{ + char *namep, *name; + int names; + unsigned long ret = FICL_FALSE; + uint32_t x1, y1, x2, y2, f; + png_t png; + int error; + +#if FICL_ROBUST > 1 + vmCheckStack(pVM, 7, 1); +#endif + names = stackPopINT(pVM->pStack); + namep = (char *) stackPopPtr(pVM->pStack); + y2 = stackPopINT(pVM->pStack); + x2 = stackPopINT(pVM->pStack); + y1 = stackPopINT(pVM->pStack); + x1 = stackPopINT(pVM->pStack); + f = stackPopINT(pVM->pStack); + + name = ficlMalloc(names + 1); + if (!name) + vmThrowErr(pVM, "Error: out of memory"); + (void) strncpy(name, namep, names); + name[names] = '\0'; + + if ((error = png_open(&png, name)) != PNG_NO_ERROR) { + if (f & FL_PUTIMAGE_DEBUG) + printf("%s\n", png_error_string(error)); + } else { + if (gfx_fb_putimage(&png, x1, y1, x2, y2, f) == 0) + ret = FICL_TRUE; /* success */ + (void) png_close(&png); + } + ficlFree(name); + stackPushUNS(pVM->pStack, ret); +} + +void +ficl_fb_setpixel(FICL_VM *pVM) +{ + FICL_UNS x, y; + +#if FICL_ROBUST > 1 + vmCheckStack(pVM, 2, 0); +#endif + + y = stackPopUNS(pVM->pStack); + x = stackPopUNS(pVM->pStack); + gfx_fb_setpixel(x, y); +} + +void +ficl_fb_line(FICL_VM *pVM) +{ + FICL_UNS x0, y0, x1, y1, wd; + +#if FICL_ROBUST > 1 + vmCheckStack(pVM, 5, 0); +#endif + + wd = stackPopUNS(pVM->pStack); + y1 = stackPopUNS(pVM->pStack); + x1 = stackPopUNS(pVM->pStack); + y0 = stackPopUNS(pVM->pStack); + x0 = stackPopUNS(pVM->pStack); + gfx_fb_line(x0, y0, x1, y1, wd); +} + +void +ficl_fb_bezier(FICL_VM *pVM) +{ + FICL_UNS x0, y0, x1, y1, x2, y2, width; + +#if FICL_ROBUST > 1 + vmCheckStack(pVM, 7, 0); +#endif + + width = stackPopUNS(pVM->pStack); + y2 = stackPopUNS(pVM->pStack); + x2 = stackPopUNS(pVM->pStack); + y1 = stackPopUNS(pVM->pStack); + x1 = stackPopUNS(pVM->pStack); + y0 = stackPopUNS(pVM->pStack); + x0 = stackPopUNS(pVM->pStack); + gfx_fb_bezier(x0, y0, x1, y1, x2, y2, width); +} + +void +ficl_fb_drawrect(FICL_VM *pVM) +{ + FICL_UNS x1, x2, y1, y2, fill; + +#if FICL_ROBUST > 1 + vmCheckStack(pVM, 5, 0); +#endif + + fill = stackPopUNS(pVM->pStack); + y2 = stackPopUNS(pVM->pStack); + x2 = stackPopUNS(pVM->pStack); + y1 = stackPopUNS(pVM->pStack); + x1 = stackPopUNS(pVM->pStack); + gfx_fb_drawrect(x1, y1, x2, y2, fill); +} + +void +ficl_term_drawrect(FICL_VM *pVM) +{ + FICL_UNS x1, x2, y1, y2; + +#if FICL_ROBUST > 1 + vmCheckStack(pVM, 4, 0); +#endif + + y2 = stackPopUNS(pVM->pStack); + x2 = stackPopUNS(pVM->pStack); + y1 = stackPopUNS(pVM->pStack); + x1 = stackPopUNS(pVM->pStack); + gfx_term_drawrect(x1, y1, x2, y2); +} +#endif /* TESTMAIN */ + void ficlSetenv(FICL_VM *pVM) { @@ -867,6 +1045,13 @@ void ficlCompilePlatform(FICL_SYSTEM *pSys) dictAppendWord(dp, "uuid-from-string", ficlUuidFromString, FW_DEFAULT); dictAppendWord(dp, "uuid-to-string", ficlUuidToString, FW_DEFAULT); #ifndef TESTMAIN + dictAppendWord(dp, "fb-setpixel", ficl_fb_setpixel, FW_DEFAULT); + dictAppendWord(dp, "fb-line", ficl_fb_line, FW_DEFAULT); + dictAppendWord(dp, "fb-bezier", ficl_fb_bezier, FW_DEFAULT); + dictAppendWord(dp, "fb-drawrect", ficl_fb_drawrect, FW_DEFAULT); + dictAppendWord(dp, "fb-putimage", ficl_fb_putimage, FW_DEFAULT); + dictAppendWord(dp, "term-drawrect", ficl_term_drawrect, FW_DEFAULT); + dictAppendWord(dp, "term-putimage", ficl_term_putimage, FW_DEFAULT); dictAppendWord(dp, "isvirtualized?",ficlIsvirtualizedQ, FW_DEFAULT); #endif diff --git a/stand/fonts/INDEX.fonts b/stand/fonts/INDEX.fonts new file mode 100644 index 00000000000..44aa6a3c81c --- /dev/null +++ b/stand/fonts/INDEX.fonts @@ -0,0 +1,66 @@ +# +# $FreeBSD$ +# +# database for vidfont(8) +# +# Format :: +# +# lang: ar bg cs da de el en es fi fr hr hu hy is it iw ja ko nl no pl +# pt ro ru sh sk sl sv tr uk zh +# lang: lang,lang +# +# Example: +# terminus-b32.fnt:de:Terminus Schriftart +# terminus-b32.fnt:en:Terminus font +# +# If lang is empty use 'en' (us-english) as default. +# +# See also setlocale(3), +# /usr/share/locale, /usr/X11/lib/X11/locale/locale.alias +# +################################ +# Language support: MENU, FONT +# +MENU:en:Choose your terminal font +MENU:da:Vælg skrifttypen til din terminal +MENU:de:Wählen Sie Ihre Schrift +MENU:fr:Choisissez votre fonte écran + +# +# The font definition for "en" is the fall-back font for +# all languages. +# Add language specific font definitions only where required! +# +FONT:en:8x16v.fnt +# + +6x12.fnt:en:Terminus BSD Console, size 12 +6x12.fnt:da:Terminus BSD-konsol, størrelse 12 +6x12.fnt:de:Terminus BSD Console, Größe 12 +8x14.fnt:en:Terminus BSD Console, size 14 +8x14.fnt:da:Terminus BSD-konsol, størrelse 14 +8x14.fnt:de:Terminus BSD Console, Größe 14 +8x16.fnt:en:Terminus BSD Console, size 16 +8x16.fnt:da:Terminus BSD-konsol, størrelse 16 +8x16.fnt:de:Terminus BSD Console, Größe 16 +10x18.fnt:en:Terminus BSD Console, size 18 +10x18.fnt:da:Terminus BSD-konsol, størrelse 18 +10x18.fnt:de:Terminus BSD Console, Größe 18 +10x20.fnt:en:Terminus BSD Console, size 20 +10x20.fnt:da:Terminus BSD-konsol, størrelse 20 +10x20.fnt:de:Terminus BSD Console, Größe 20 +11x22.fnt:en:Terminus BSD Console, size 22 +11x22.fnt:da:Terminus BSD-konsol, størrelse 22 +11x22.fnt:de:Terminus BSD Console, Größe 22 +12x24.fnt:en:Terminus BSD Console, size 24 +12x24.fnt:da:Terminus BSD-konsol, størrelse 24 +12x24.fnt:de:Terminus BSD Console, Größe 24 +14x28.fnt:en:Terminus BSD Console, size 28 +14x28.fnt:da:Terminus BSD-konsol, størrelse 28 +14x28.fnt:de:Terminus BSD Console, Größe 28 +16x32.fnt:en:Terminus BSD Console, size 32 +16x32.fnt:da:Terminus BSD-konsol, størrelse 32 +16x32.fnt:de:Terminus BSD Console, Größe 32 + +# (fset 'langnew +# "\M-}\C-p\C-k\C-y\C-m\C-y\M-}") diff --git a/stand/fonts/Makefile b/stand/fonts/Makefile new file mode 100644 index 00000000000..b7b2ea7a0f0 --- /dev/null +++ b/stand/fonts/Makefile @@ -0,0 +1,81 @@ +# $FreeBSD$ + +.include + +.PATH: ${SRCTOP}/contrib/terminus + +FONTS= \ + 6x12.fnt.gz \ + 8x14.fnt.gz \ + 8x14v.fnt.gz \ + 8x16.fnt.gz \ + 8x16v.fnt.gz \ + 10x18.fnt.gz \ + 10x20.fnt.gz \ + 11x22.fnt.gz \ + 12x24.fnt.gz \ + 14x28.fnt.gz \ + 16x32.fnt.gz \ + +FILES= ${FONTS} INDEX.fonts +FILESDIR= /boot/fonts + +CLEANFILES+= ${FONTS} ${FONTS:T:S/${COMPRESS_EXT}//g} + +6x12.fnt.gz: 6x12.fnt + ${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET} +8x14.fnt.gz: 8x14.fnt + ${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET} +8x14v.fnt.gz: 8x14v.fnt + ${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET} +8x16.fnt.gz: 8x16.fnt + ${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET} +8x16v.fnt.gz: 8x16v.fnt + ${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET} +10x18.fnt.gz: 10x18.fnt + ${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET} +10x20.fnt.gz: 10x20.fnt + ${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET} +11x22.fnt.gz: 11x22.fnt + ${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET} +12x24.fnt.gz: 12x24.fnt + ${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET} +14x28.fnt.gz: 14x28.fnt + ${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET} +16x32.fnt.gz: 16x32.fnt + ${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET} + +6x12.fnt: ter-u12n.bdf ter-u12b.bdf + vtfontcvt -o ${.TARGET} ${.ALLSRC} + +8x14.fnt: ter-u14n.bdf ter-u14b.bdf + vtfontcvt -o ${.TARGET} ${.ALLSRC} + +8x14v.fnt: ter-u14v.bdf + vtfontcvt -o ${.TARGET} ${.ALLSRC} + +8x16.fnt: ter-u16n.bdf ter-u16b.bdf + vtfontcvt -o ${.TARGET} ${.ALLSRC} + +8x16v.fnt: ter-u16v.bdf + vtfontcvt -o ${.TARGET} ${.ALLSRC} + +10x18.fnt: ter-u18n.bdf ter-u18b.bdf + vtfontcvt -o ${.TARGET} ${.ALLSRC} + +10x20.fnt: ter-u20n.bdf ter-u20b.bdf + vtfontcvt -o ${.TARGET} ${.ALLSRC} + +11x22.fnt: ter-u22n.bdf ter-u22b.bdf + vtfontcvt -o ${.TARGET} ${.ALLSRC} + +12x24.fnt: ter-u24n.bdf ter-u24b.bdf + vtfontcvt -o ${.TARGET} ${.ALLSRC} + +14x28.fnt: ter-u28n.bdf ter-u28b.bdf + vtfontcvt -o ${.TARGET} ${.ALLSRC} + +16x32.fnt: ter-u32n.bdf ter-u32b.bdf + vtfontcvt -o ${.TARGET} ${.ALLSRC} + +.include diff --git a/stand/forth/beastie.4th b/stand/forth/beastie.4th index 752cce22a4f..f64cf9f8720 100644 --- a/stand/forth/beastie.4th +++ b/stand/forth/beastie.4th @@ -82,6 +82,10 @@ variable logoY then ; +: draw-beastie + ['] draw-beastie console-iterate +; + also support-functions : beastie-start ( -- ) \ starts the menu diff --git a/stand/forth/brand-fbsd.4th b/stand/forth/brand-fbsd.4th index 9cd017f84a3..b8e43e601c9 100644 --- a/stand/forth/brand-fbsd.4th +++ b/stand/forth/brand-fbsd.4th @@ -34,6 +34,18 @@ : brand ( x y -- ) \ "FreeBSD" [wide] logo in B/W (7 rows x 42 columns) + framebuffer? if + s" term-putimage" sfind if + \ note, we use 0, 0 for image upper left as origin, + \ and 0, 7 for lower right to preserve aspect ratio + >r 0 0 0 0 7 + s" /boot/images/freebsd-brand-rev.png" + r> execute if 2drop exit then + else + drop + then + then + s" ______ ____ _____ _____ " brand+ s" | ____| | _ \ / ____| __ \ " brand+ s" | |___ _ __ ___ ___ | |_) | (___ | | | |" brand+ diff --git a/stand/forth/brand.4th b/stand/forth/brand.4th index 39a9bfae753..034e4eb4044 100644 --- a/stand/forth/brand.4th +++ b/stand/forth/brand.4th @@ -72,3 +72,7 @@ variable brandY else drop then then ; + +: draw-brand + ['] draw-brand console-iterate +; diff --git a/stand/forth/color.4th b/stand/forth/color.4th index fc5a4d564d0..e73e8b725c3 100644 --- a/stand/forth/color.4th +++ b/stand/forth/color.4th @@ -28,7 +28,7 @@ marker task-color.4th \ This function returns FALSE if the `loader_color' environment variable is set \ to NO, no, or 0. It returns TRUE if `loader_color' is set to any other value. -\ If `loader_color' is unset, TRUE is returned (unless booting serial). +\ If `loader_color' is unset, TRUE is returned. \ : loader_color? ( -- t ) @@ -44,12 +44,8 @@ marker task-color.4th FALSE exit then drop - \ It is enabled. - TRUE - else - \ `loader_color' is unset. - \ Default to using color unless serial boot is active. - drop - boot_serial? 0= then + drop + \ It is enabled. + TRUE ; diff --git a/stand/forth/frames.4th b/stand/forth/frames.4th index ba356cee4ac..8d2421c758d 100644 --- a/stand/forth/frames.4th +++ b/stand/forth/frames.4th @@ -121,6 +121,20 @@ only forth definitions also frame-drawing ; : box ( w h x y -- ) \ Draw a box + framebuffer? if + s" term-drawrect" sfind if + >R + rot ( w x y h ) + over + >R ( w x y -- R: y+h ) + swap rot ( y x w -- R: y+h ) + over + >R ( y x -- R: y+h x+w ) + swap R> R> R> execute + exit + else + drop + then + then + \ Non-framebuffer version 2dup 1+ 4 pick 1- -rot vline \ Draw left vert line 2dup 1+ swap 5 pick + swap 4 pick 1- -rot diff --git a/stand/forth/logo-orb.4th b/stand/forth/logo-orb.4th index c2a504d1ddf..289353a4692 100644 --- a/stand/forth/logo-orb.4th +++ b/stand/forth/logo-orb.4th @@ -35,6 +35,18 @@ : logo ( x y -- ) \ color Orb mascot (15 rows x 30 columns) + framebuffer? if + s" term-putimage" sfind if + >r 2dup ( x y x y ) + >r 0 swap r> ( x y 0 x y ) + dup 0 swap 15 + ( x y 0 x y 0 y+15 ) + s" /boot/images/freebsd-logo-rev.png" + r> execute if 2drop exit then + else + drop + then + then + s" @[31m``` @[31;1m`@[31m" logo+ s" s` `.....---...@[31;1m....--.``` -/@[31m" logo+ s" +o .--` @[31;1m/y:` +.@[31m" logo+ diff --git a/stand/forth/menu.4th b/stand/forth/menu.4th index ca166c6d934..6b0869cfe3b 100644 --- a/stand/forth/menu.4th +++ b/stand/forth/menu.4th @@ -991,6 +991,24 @@ only forth definitions also menu-infrastructure menu-create ; +: menu-box + f_double ( default frame type ) + \ Interpret a custom frame type for the menu + TRUE ( draw a box? default yes, but might be altered below ) + s" loader_menu_frame" getenv dup -1 = if ( 1 ) + drop \ no custom frame type + else ( 1 ) 2dup s" single" compare-insensitive 0= if ( 2 ) + f_single ( see frames.4th ) + else ( 2 ) 2dup s" double" compare-insensitive 0= if ( 3 ) + f_double ( see frames.4th ) + else ( 3 ) s" none" compare-insensitive 0= if ( 4 ) + drop FALSE \ don't draw a box + ( 4 ) then ( 3 ) then ( 2 ) then ( 1 ) then + if + 42 13 menuX @ 3 - menuY @ 1- box \ Draw frame (w,h,x,y) + then +; + \ This function initializes the menu. Call this from your `loader.rc' file \ before calling any other menu-related functions. \ @@ -1021,21 +1039,7 @@ only forth definitions also menu-infrastructure then menuX ! - \ Interpret a custom frame type for the menu - TRUE ( draw a box? default yes, but might be altered below ) - s" loader_menu_frame" getenv dup -1 = if ( 1 ) - drop \ no custom frame type - else ( 1 ) 2dup s" single" compare-insensitive 0= if ( 2 ) - f_single ( see frames.4th ) - else ( 2 ) 2dup s" double" compare-insensitive 0= if ( 3 ) - f_double ( see frames.4th ) - else ( 3 ) s" none" compare-insensitive 0= if ( 4 ) - drop FALSE \ don't draw a box - ( 4 ) then ( 3 ) then ( 2 ) then ( 1 ) then - if - 42 13 menuX @ 3 - menuY @ 1- box \ Draw frame (w,h,x,y) - then - + ['] menu-box console-iterate 0 25 at-xy \ Move cursor to the bottom for output ; diff --git a/stand/forth/support.4th b/stand/forth/support.4th index e6addfbdf7b..d87cf16a16d 100644 --- a/stand/forth/support.4th +++ b/stand/forth/support.4th @@ -190,6 +190,25 @@ create last_module_option sizeof module.next allot 0 last_module_option ! 0 0 ; +: strspn { addr len addr1 len1 | paddr plen -- addr' len' } + begin + len + while + addr1 to paddr + len1 to plen + begin + plen + while + addr c@ paddr c@ = if addr len exit then + paddr 1+ to paddr + plen 1- to plen + repeat + addr 1 + to addr + len 1 - to len + repeat + 0 0 +; + : s' \ same as s", allows " in the string [char] ' parse state @ if postpone sliteral then @@ -201,6 +220,53 @@ create last_module_option sizeof module.next allot 0 last_module_option ! : getenv? getenv -1 = if false else drop true then ; +\ execute xt for each device listed in console variable. +\ this allows us to have device specific output for logos, menu frames etc +: console-iterate { xt | caddr clen taddr tlen -- } + \ get current console and save it + s" console" getenv + ['] strdup catch if 2drop exit then + to clen to caddr + + clen to tlen + caddr to taddr + begin + tlen + while + taddr tlen s" , " strspn + \ we need to handle 3 cases for addr len pairs on stack: + \ addr len are 0 0 - there was no comma nor space + \ addr len are x 0 - the first char is either comma or space + \ addr len are x y. + 2dup + 0= if + \ there was no comma nor space. + 2drop + taddr tlen s" console" setenv + xt execute + 0 to tlen + else dup 0= if + 2drop + else + dup ( taddr' tlen' tlen' ) + tlen swap - dup + 0= if \ sequence of comma and space? + drop + else + taddr swap s" console" setenv + xt execute + then + to tlen + to taddr + then then + tlen 0> if \ step over separator + tlen 1- to tlen + taddr 1+ to taddr + then + repeat + caddr clen s" console" setenv \ restore console setup + caddr free drop +; + \ determine if a word appears in a string, case-insensitive : contains? ( addr1 len1 addr2 len2 -- 0 | -1 ) 2 pick 0= if 2drop 2drop true exit then @@ -231,14 +297,23 @@ create last_module_option sizeof module.next allot 0 last_module_option ! s" console" getenv dup -1 <> if s" comconsole" 2swap contains? else drop false then - s" boot_serial" getenv dup -1 <> if - swap drop 0> - else drop false then - or \ console contains comconsole ( or ) boot_serial - s" boot_multicons" getenv dup -1 <> if - swap drop 0> - else drop false then - or \ previous boolean ( or ) boot_multicons +\ s" boot_serial" getenv dup -1 <> if +\ swap drop 0> +\ else drop false then +\ or \ console contains comconsole ( or ) boot_serial +\ s" boot_multicons" getenv dup -1 <> if +\ swap drop 0> +\ else drop false then +\ or \ previous boolean ( or ) boot_multicons +; + +: framebuffer? ( -- t ) + s" console" getenv + 2dup s" efi" compare 0<> >r + s" vidconsole" compare 0<> r> and if + FALSE exit + then + s" screen.depth" getenv? ; \ Private definitions diff --git a/stand/i386/libi386/Makefile b/stand/i386/libi386/Makefile index 779575bff07..3dea78f1692 100644 --- a/stand/i386/libi386/Makefile +++ b/stand/i386/libi386/Makefile @@ -9,7 +9,7 @@ SRCS= bio.c biosacpi.c biosdisk.c biosmem.c biospnp.c \ comconsole.c devicename.c elf32_freebsd.c \ elf64_freebsd.c multiboot.c multiboot_tramp.S relocater_tramp.S \ i386_copy.c i386_module.c nullconsole.c pxe.c pxetramp.S \ - time.c vidconsole.c amd64_tramp.S spinconsole.c + time.c vidconsole.c vbe.c amd64_tramp.S spinconsole.c .PATH: ${ZFSSRC} SRCS+= devicename_stubs.c CFLAGS+= -I${ZFSSRC} @@ -29,8 +29,13 @@ CFLAGS+= -DDISK_DEBUG .endif # terminal emulation -CFLAGS.vidconsole.c+= -I${SRCTOP}/sys/teken +.if ${BOOT_FRAMEBUFFER_MODE:Uno} == "yes" +CFLAGS.vidconsole.c+= -DFRAMEBUFFER_MODE +.endif +CFLAGS.vidconsole.c+= -I${SRCTOP}/sys/teken -I${SRCTOP}/contrib/pnglite CFLAGS.teken.c+= -I${SRCTOP}/sys/teken +CFLAGS.bootinfo.c+= -I${SRCTOP}/sys/teken -I${SRCTOP}/contrib/pnglite +CFLAGS.vbe.c+= -I${SRCTOP}/sys/teken -I${SRCTOP}/contrib/pnglite # XXX: make alloca() useable CFLAGS+= -Dalloca=__builtin_alloca diff --git a/stand/i386/libi386/bootinfo.c b/stand/i386/libi386/bootinfo.c index 41901e5f928..71e07cfb970 100644 --- a/stand/i386/libi386/bootinfo.c +++ b/stand/i386/libi386/bootinfo.c @@ -32,10 +32,21 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include "bootstrap.h" #include "libi386.h" +#include "vbe.h" #include "btxv86.h" +void +bi_load_vbe_data(struct preloaded_file *kfp) +{ + if (vbe_available()) { + file_addmetadata(kfp, MODINFOMD_VBE_FB, + sizeof(gfx_state.tg_fb), &gfx_state.tg_fb); + } +} + int bi_getboothowto(char *kargs) { diff --git a/stand/i386/libi386/bootinfo32.c b/stand/i386/libi386/bootinfo32.c index e6a92a2164d..23b02693cf4 100644 --- a/stand/i386/libi386/bootinfo32.c +++ b/stand/i386/libi386/bootinfo32.c @@ -205,6 +205,8 @@ bi_load32(char *args, int *howtop, int *bootdevp, vm_offset_t *bip, vm_offset_t /* pad to a page boundary */ addr = roundup(addr, PAGE_SIZE); + addr = build_font_module(addr); + /* copy our environment */ envp = addr; addr = bi_copyenv(addr); @@ -225,6 +227,7 @@ bi_load32(char *args, int *howtop, int *bootdevp, vm_offset_t *bip, vm_offset_t #ifdef LOADER_GELI_SUPPORT geli_export_key_metadata(kfp); #endif + bi_load_vbe_data(kfp); /* Figure out the size and location of the metadata */ *modulep = addr; diff --git a/stand/i386/libi386/bootinfo64.c b/stand/i386/libi386/bootinfo64.c index 93723a6943e..9f038f2a4f2 100644 --- a/stand/i386/libi386/bootinfo64.c +++ b/stand/i386/libi386/bootinfo64.c @@ -227,6 +227,8 @@ bi_load64(char *args, vm_offset_t addr, vm_offset_t *modulep, /* pad to a page boundary */ addr = roundup(addr, PAGE_SIZE); + addr = build_font_module(addr); + /* place the metadata before anything */ module = *modulep = addr; @@ -245,6 +247,7 @@ bi_load64(char *args, vm_offset_t addr, vm_offset_t *modulep, #ifdef LOADER_GELI_SUPPORT geli_export_key_metadata(kfp); #endif + bi_load_vbe_data(kfp); size = bi_copymodules64(0); diff --git a/stand/i386/libi386/libi386.h b/stand/i386/libi386/libi386.h index d25df8fc44a..dbd6565f8d1 100644 --- a/stand/i386/libi386/libi386.h +++ b/stand/i386/libi386/libi386.h @@ -145,6 +145,7 @@ void biosacpi_detect(void); int i386_autoload(void); +void bi_load_vbe_data(struct preloaded_file *kfp); int bi_getboothowto(char *kargs); void bi_setboothowto(int howto); vm_offset_t bi_copyenv(vm_offset_t addr); diff --git a/stand/i386/libi386/vbe.c b/stand/i386/libi386/vbe.c new file mode 100644 index 00000000000..7681eb633b8 --- /dev/null +++ b/stand/i386/libi386/vbe.c @@ -0,0 +1,1226 @@ +/*- + * 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; +/* The default VGA color palette format is 6 bits per primary color. */ +int palette_format = 6; + +#define VESA_MODE_BASE 0x100 +#define VESA_MODE_MAX 0x1ff +#define VESA_MODE_COUNT (VESA_MODE_MAX - VESA_MODE_BASE + 1) + +/* + * 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); +} + +/* 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("hw.vga.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("hw.vga.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, "hw.vga.textmode") == 0) { + unsigned long v; + char *end; + + if (value == NULL) + return (0); + /* VT(4) describes hw.vga.textmode values 0 or 1. */ + errno = 0; + v = strtoul(value, &end, 0); + if (errno != 0 || *(char *)value == '\0' || *end != '\0' || + (v != 0 && v != 1)) + return (EINVAL); + env_setenv("hw.vga.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); +} + +void +vbe_init(void) +{ + /* 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; + + if (vbe == NULL) + vbe = malloc(sizeof(*vbe)); + + 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; + } + + env_setenv("hw.vga.textmode", EV_VOLATILE, "1", mode_set, + env_nounset); + env_setenv("vbe_max_resolution", EV_VOLATILE, NULL, mode_set, + env_nounset); + /* 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); +} + +static void * +vbe_farptr(uint32_t farptr) +{ + return (PTOV((((farptr & 0xffff0000) >> 12) + (farptr & 0xffff)))); +} + +/* + * 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; + uint32_t farptr; + uint16_t mode; + int safety, i; + + memset(vbe, 0, sizeof (*vbe)); + if (biosvbe_info(vbe) != VBE_SUCCESS) + return (0); + if (vbe->VideoModePtr == 0) + return (0); + + if (m != -1) + i = 8; + else if (depth == -1) + i = 32; + else + i = depth; + + while (i > 0) { + farptr = vbe->VideoModePtr; + safety = 0; + while ((mode = *(uint16_t *)vbe_farptr(farptr)) != 0xffff) { + safety++; + farptr += 2; + if (safety == VESA_MODE_COUNT) + 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; + const uint8_t magic[] = EDID_MAGIC; + int ddc_caps; + bool ret = false; + + ddc_caps = biosvbe_ddc_caps(); + if (ddc_caps == 0) { + return (ret); + } + + edid_info = bio_alloc(sizeof (*edid_info)); + if (edid_info == NULL) + return (ret); + memset(edid_info, 0, sizeof (*edid_info)); + + if (VBE_ERROR(biosvbe_ddc_read_edid(0, edid_info))) + goto done; + + if (memcmp(edid_info, magic, sizeof (magic)) != 0) + goto done; + + /* Unknown EDID version. */ + if (edid_info->header.version != 1) + goto done; + + ret = gfx_get_edid_resolution(edid_info, res); +done: + bio_free(edid_info, sizeof (*edid_info)); + 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; + uint32_t farptr; + uint16_t mode; + int nmodes = 0, safety = 0; + 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); + + 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: "); + + farptr = vbe->VideoModePtr; + if (farptr == 0) + goto done; + + while ((mode = *(uint16_t *)vbe_farptr(farptr)) != 0xffff) { + safety++; + farptr += 2; + if (safety == VESA_MODE_COUNT) { + printf("[?] "); + 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; + + /* we found some mode so reset safety counter */ + safety = 0; + + /* 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); +} diff --git a/stand/i386/libi386/vbe.h b/stand/i386/libi386/vbe.h new file mode 100644 index 00000000000..ff28b960df9 --- /dev/null +++ b/stand/i386/libi386/vbe.h @@ -0,0 +1,163 @@ +/*- + * Copyright (c) 2009 Jared D. McNeill + * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. + */ + +/* + * Default mode for VESA frame buffer. + * This mode is selected when there is no EDID inormation and + * mode is not provided by user. + * To provide consistent look with UEFI GOP, we use 800x600 here, + * and if this mode is not available, we fall back to text mode and + * VESA disabled. + */ + +#define VBE_DEFAULT_MODE "800x600" + +struct vbeinfoblock { + char VbeSignature[4]; + uint16_t VbeVersion; + uint32_t OemStringPtr; + uint32_t Capabilities; +#define VBE_CAP_DAC8 (1 << 0) /* Can switch DAC */ +#define VBE_CAP_NONVGA (1 << 1) /* Controller is not VGA comp. */ +#define VBE_CAP_SNOW (1 << 2) /* Set data during Vertical Reterace */ + uint32_t VideoModePtr; + uint16_t TotalMemory; + uint16_t OemSoftwareRev; + uint32_t OemVendorNamePtr, OemProductNamePtr, OemProductRevPtr; + /* data area, in total max 512 bytes for VBE 2.0 */ + uint8_t Reserved[222]; + uint8_t OemData[256]; +} __packed; + +struct modeinfoblock { + /* Mandatory information for all VBE revisions */ + uint16_t ModeAttributes; + uint8_t WinAAttributes, WinBAttributes; + uint16_t WinGranularity, WinSize, WinASegment, WinBSegment; + uint32_t WinFuncPtr; + uint16_t BytesPerScanLine; + /* Mandatory information for VBE 1.2 and above */ + uint16_t XResolution, YResolution; + uint8_t XCharSize, YCharSize, NumberOfPlanes, BitsPerPixel; + uint8_t NumberOfBanks, MemoryModel, BankSize, NumberOfImagePages; + uint8_t Reserved1; + /* Direct Color fields + (required for direct/6 and YUV/7 memory models) */ + uint8_t RedMaskSize, RedFieldPosition; + uint8_t GreenMaskSize, GreenFieldPosition; + uint8_t BlueMaskSize, BlueFieldPosition; + uint8_t RsvdMaskSize, RsvdFieldPosition; + uint8_t DirectColorModeInfo; + /* Mandatory information for VBE 2.0 and above */ + uint32_t PhysBasePtr; + uint32_t OffScreenMemOffset; /* reserved in VBE 3.0 and above */ + uint16_t OffScreenMemSize; /* reserved in VBE 3.0 and above */ + + /* Mandatory information for VBE 3.0 and above */ + uint16_t LinBytesPerScanLine; + uint8_t BnkNumberOfImagePages; + uint8_t LinNumberOfImagePages; + uint8_t LinRedMaskSize, LinRedFieldPosition; + uint8_t LinGreenMaskSize, LinGreenFieldPosition; + uint8_t LinBlueMaskSize, LinBlueFieldPosition; + uint8_t LinRsvdMaskSize, LinRsvdFieldPosition; + uint32_t MaxPixelClock; + /* + 1 will fix the size to 256 bytes */ + uint8_t Reserved4[189 + 1]; +} __packed; + +struct crtciinfoblock { + uint16_t HorizontalTotal; + uint16_t HorizontalSyncStart; + uint16_t HorizontalSyncEnd; + uint16_t VerticalTotal; + uint16_t VerticalSyncStart; + uint16_t VerticalSyncEnd; + uint8_t Flags; + uint32_t PixelClock; + uint16_t RefreshRate; + uint8_t Reserved[40]; +} __packed; + +struct paletteentry { + uint8_t Blue; + uint8_t Green; + uint8_t Red; + uint8_t Reserved; +} __packed; + +struct flatpanelinfo +{ + uint16_t HorizontalSize; + uint16_t VerticalSize; + uint16_t PanelType; + uint8_t RedBPP; + uint8_t GreenBPP; + uint8_t BlueBPP; + uint8_t ReservedBPP; + uint32_t ReservedOffScreenMemSize; + uint32_t ReservedOffScreenMemPtr; + + uint8_t Reserved[14]; +} __packed; + +#define VBE_BASE_MODE (0x100) /* VBE 3.0 page 18 */ +#define VBE_VALID_MODE(a) ((a) >= VBE_BASE_MODE) +#define VBE_ERROR(a) (((a) & 0xFF) != 0x4F || ((a) & 0xFF00) != 0) +#define VBE_SUCCESS (0x004F) +#define VBE_FAILED (0x014F) +#define VBE_NOTSUP (0x024F) +#define VBE_INVALID (0x034F) + +#define VGA_TEXT_MODE (3) /* 80x25 text mode */ +#define TEXT_ROWS (25) /* VGATEXT rows */ +#define TEXT_COLS (80) /* VGATEXT columns */ + +extern struct paletteentry *pe8; +extern int palette_format; + +int vga_get_reg(int, int); +int vga_get_atr(int, int); +void vga_set_atr(int, int, int); +void vga_set_indexed(int, int, int, uint8_t, uint8_t); +int vga_get_indexed(int, int, int, uint8_t); +int vga_get_crtc(int, int); +void vga_set_crtc(int, int, int); +int vga_get_seq(int, int); +void vga_set_seq(int, int, int); +int vga_get_grc(int, int); +void vga_set_grc(int, int, int); + +/* high-level VBE helpers, from vbe.c */ +void bios_set_text_mode(int); +int biosvbe_palette_format(int *); +void vbe_init(void); +bool vbe_available(void); +int vbe_default_mode(void); +int vbe_set_mode(int); +int vbe_get_mode(void); +int vbe_set_palette(const struct paletteentry *, size_t); +void vbe_modelist(int); diff --git a/stand/i386/libi386/vidconsole.c b/stand/i386/libi386/vidconsole.c index f965979d656..b4829db1ea4 100644 --- a/stand/i386/libi386/vidconsole.c +++ b/stand/i386/libi386/vidconsole.c @@ -31,12 +31,13 @@ __FBSDID("$FreeBSD$"); #include +#include #include #include -#include -#include +#include #include #include +#include "vbe.h" #include @@ -50,6 +51,7 @@ static int vidc_init(int arg); static void vidc_putchar(int c); static int vidc_getchar(void); static int vidc_ischar(void); +static void cons_draw_frame(teken_attr_t *); static int vidc_started; static uint16_t *vgatext; @@ -72,30 +74,16 @@ static teken_funcs_t tf = { .tf_respond = vidc_cons_respond, }; -teken_t teken; -teken_pos_t tp; - -struct text_pixel { - teken_char_t c; - teken_attr_t a; +static teken_funcs_t tfx = { + .tf_bell = vidc_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 = vidc_cons_respond, }; -static struct text_pixel *buffer; - -#define NCOLORS 16 - -/* - * Between console's palette and VGA's one: - * - blue and red are swapped (1 <-> 4) - * - yellow and cyan are swapped (3 <-> 6) - */ -static const int cons_to_vga_colors[NCOLORS] = { - 0, 4, 2, 6, 1, 5, 3, 7, - 8, 12, 10, 14, 9, 13, 11, 15 -}; - -#define TEXT_COLS 80 -#define TEXT_ROWS 25 #define KEYBUFSZ 10 static uint8_t keybuf[KEYBUFSZ]; /* keybuf for extended codes */ @@ -111,60 +99,26 @@ struct console vidconsole = { .c_ready = vidc_ischar }; -static int -vga_get_reg(int reg, int index) +/* + * 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) { - return (inb(reg + index)); -} + teken_pos_t p; + int idx; -static 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); -} - -static 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); -} - -static void -vga_set_indexed(int reg, int indexreg, int datareg, uint8_t index, uint8_t val) -{ - outb(reg + indexreg, index); - outb(reg + datareg, val); -} - -static int -vga_get_indexed(int reg, int indexreg, int datareg, uint8_t index) -{ - outb(reg + indexreg, index); - return (inb(reg + datareg)); -} - -static int -vga_get_crtc(int reg, int i) -{ - return (vga_get_indexed(reg, VGA_CRTC_ADDRESS, VGA_CRTC_DATA, i)); -} - -static void -vga_set_crtc(int reg, int i, int v) -{ - vga_set_indexed(reg, VGA_CRTC_ADDRESS, VGA_CRTC_DATA, i, v); + 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; + } + } } static void @@ -353,9 +307,9 @@ vga_get_cp437(teken_char_t c) } static void -vidc_text_printchar(const teken_pos_t *p) +vidc_text_printchar(teken_gfx_t *state, const teken_pos_t *p) { - int i; + int idx; uint8_t attr; struct text_pixel *px; teken_color_t fg, bg, tmp; @@ -364,7 +318,8 @@ vidc_text_printchar(const teken_pos_t *p) uint8_t attr; } *addr; - px = buffer + p->tp_col + p->tp_row * tp.tp_col; + idx = p->tp_col + p->tp_row * state->tg_tp.tp_col; + px = &screen_buffer[idx]; fg = teken_256to16(px->a.ta_fgcolor); bg = teken_256to16(px->a.ta_bgcolor); if (px->a.ta_format & TF_BOLD) @@ -378,29 +333,34 @@ vidc_text_printchar(const teken_pos_t *p) bg = tmp; } - attr = (cons_to_vga_colors[bg & 0xf] << 4) | - cons_to_vga_colors[fg & 0xf]; - addr = (struct cgatext *)vgatext + p->tp_col + p->tp_row * tp.tp_col; - addr->ch = vga_get_cp437(px->c); - addr->attr = attr; + attr = (cmap[bg & 0xf] << 4) | cmap[fg & 0xf]; + addr = (struct cgatext *)vgatext; + addr[idx].ch = vga_get_cp437(px->c); + addr[idx].attr = attr; } static void -vidc_text_putchar(void *s __unused, const teken_pos_t *p, teken_char_t c, +vidc_text_putchar(void *s, const teken_pos_t *p, teken_char_t c, const teken_attr_t *a) { - int attr, idx; + teken_gfx_t *state = s; + int attr, idx; - idx = p->tp_col + p->tp_row * tp.tp_col; - buffer[idx].c = c; - buffer[idx].a = *a; - vidc_text_printchar(p); + 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; + + vidc_text_printchar(state, p); } static void -vidc_text_fill(void *s, const teken_rect_t *r, teken_char_t c, +vidc_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; teken_unit_t row, col; @@ -410,29 +370,14 @@ vidc_text_fill(void *s, const teken_rect_t *r, teken_char_t c, p.tp_row++) for (p.tp_col = r->tr_begin.tp_col; p.tp_col < r->tr_end.tp_col; p.tp_col++) - vidc_text_putchar(s, &p, c, a); + vidc_text_putchar(state, &p, c, a); vidc_text_set_cursor(row, col, true); } -static bool -vidc_same_pixel(struct text_pixel *px1, struct text_pixel *px2) -{ - if (px1->c != px2->c) - 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 -vidc_text_copy(void *ptr __unused, const teken_rect_t *r, const teken_pos_t *p) +vidc_text_copy(void *ptr, const teken_rect_t *r, const teken_pos_t *p) { + teken_gfx_t *state = ptr; int srow, drow; int nrow, ncol, x, y; /* Has to be signed - >= 0 comparison */ teken_pos_t d, s; @@ -453,18 +398,18 @@ vidc_text_copy(void *ptr __unused, const teken_rect_t *r, const teken_pos_t *p) for (y = 0; y < nrow; y++) { d.tp_row = p->tp_row + y; s.tp_row = r->tr_begin.tp_row + y; - drow = d.tp_row * tp.tp_col; - srow = s.tp_row * tp.tp_col; + drow = d.tp_row * state->tg_tp.tp_col; + srow = s.tp_row * state->tg_tp.tp_col; for (x = 0; x < ncol; x++) { d.tp_col = p->tp_col + x; s.tp_col = r->tr_begin.tp_col + x; - if (!vidc_same_pixel( - &buffer[d.tp_col + drow], - &buffer[s.tp_col + srow])) { - buffer[d.tp_col + drow] = - buffer[s.tp_col + srow]; - vidc_text_printchar(&d); + if (!is_same_pixel( + &screen_buffer[d.tp_col + drow], + &screen_buffer[s.tp_col + srow])) { + screen_buffer[d.tp_col + drow] = + screen_buffer[s.tp_col + srow]; + vidc_text_printchar(state, &d); } } } @@ -475,18 +420,18 @@ vidc_text_copy(void *ptr __unused, const teken_rect_t *r, const teken_pos_t *p) for (y = nrow - 1; y >= 0; y--) { d.tp_row = p->tp_row + y; s.tp_row = r->tr_begin.tp_row + y; - drow = d.tp_row * tp.tp_col; - srow = s.tp_row * tp.tp_col; + drow = d.tp_row * state->tg_tp.tp_col; + srow = s.tp_row * state->tg_tp.tp_col; for (x = 0; x < ncol; x++) { d.tp_col = p->tp_col + x; s.tp_col = r->tr_begin.tp_col + x; - if (!vidc_same_pixel( - &buffer[d.tp_col + drow], - &buffer[s.tp_col + srow])) { - buffer[d.tp_col + drow] = - buffer[s.tp_col + srow]; - vidc_text_printchar(&d); + if (!is_same_pixel( + &screen_buffer[d.tp_col + drow], + &screen_buffer[s.tp_col + srow])) { + screen_buffer[d.tp_col + drow] = + screen_buffer[s.tp_col + srow]; + vidc_text_printchar(state, &d); } } } @@ -495,18 +440,18 @@ vidc_text_copy(void *ptr __unused, const teken_rect_t *r, const teken_pos_t *p) for (y = nrow - 1; y >= 0; y--) { d.tp_row = p->tp_row + y; s.tp_row = r->tr_begin.tp_row + y; - drow = d.tp_row * tp.tp_col; - srow = s.tp_row * tp.tp_col; + drow = d.tp_row * state->tg_tp.tp_col; + srow = s.tp_row * state->tg_tp.tp_col; for (x = ncol - 1; x >= 0; x--) { d.tp_col = p->tp_col + x; s.tp_col = r->tr_begin.tp_col + x; - if (!vidc_same_pixel( - &buffer[d.tp_col + drow], - &buffer[s.tp_col + srow])) { - buffer[d.tp_col + drow] = - buffer[s.tp_col + srow]; - vidc_text_printchar(&d); + if (!is_same_pixel( + &screen_buffer[d.tp_col + drow], + &screen_buffer[s.tp_col + srow])) { + screen_buffer[d.tp_col + drow] = + screen_buffer[s.tp_col + srow]; + vidc_text_printchar(state, &d); } } } @@ -516,8 +461,9 @@ vidc_text_copy(void *ptr __unused, const teken_rect_t *r, const teken_pos_t *p) } static void -vidc_text_param(void *s __unused, int cmd, unsigned int value) +vidc_text_param(void *arg, int cmd, unsigned int value) { + teken_gfx_t *state = arg; teken_unit_t row, col; switch (cmd) { @@ -532,10 +478,13 @@ vidc_text_param(void *s __unused, int cmd, unsigned int value) /* FALLTHROUGH */ case TP_SHOWCURSOR: vidc_text_get_cursor(&row, &col); - if (value == 1) + if (value != 0) { vidc_text_set_cursor(row, col, true); - else + state->tg_cursor_visible = true; + } else { vidc_text_set_cursor(row, col, false); + state->tg_cursor_visible = false; + } break; default: /* Not yet implemented */ @@ -635,7 +584,7 @@ vidc_set_colors(struct env_var *ev, int flags, const void *value) evalue = value; } - ap = teken_get_defattr(&teken); + ap = teken_get_defattr(&gfx_state.tg_teken); a = *ap; if (strcmp(ev->ev_name, "teken.fg_color") == 0) { /* is it already set? */ @@ -654,11 +603,381 @@ vidc_set_colors(struct env_var *ev, int flags, const void *value) 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_set_defattr(&teken, &a); + teken_input(&gfx_state.tg_teken, "\e[2J", 4); + return (CMD_OK); } +static int +env_screen_nounset(struct env_var *ev __unused) +{ + if (gfx_state.tg_fb_type == FB_TEXT) + return (0); + return (EPERM); +} + +static int +vidc_load_palette(uint32_t *cmap) +{ + int i, roff, goff, boff, rc; + + if (pe8 == NULL) + pe8 = calloc(sizeof(*pe8), NCMAP); + if (pe8 == NULL) + return (ENOMEM); + + /* Generate VGA colors */ + 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; + rc = generate_cons_palette((uint32_t *)pe8, 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); + + if (rc == 0) { + for (i = 0; i < NCMAP; i++) { + int idx; + + if (i < NCOLORS) + idx = cons_to_vga_colors[i]; + else + idx = i; + + rc = vbe_set_palette(&pe8[i], idx); + if (rc != 0) + break; + } + } + return (rc); +} + +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); +} + +/* + * Binary searchable table for CP437 to Unicode conversion. + */ +struct cp437uni { + uint8_t cp437_base; + uint16_t unicode_base; + uint8_t length; +}; + +static const struct cp437uni cp437unitable[] = { + { 0, 0x0000, 0 }, { 1, 0x263A, 1 }, { 3, 0x2665, 1 }, + { 5, 0x2663, 0 }, { 6, 0x2660, 0 }, { 7, 0x2022, 0 }, + { 8, 0x25D8, 0 }, { 9, 0x25CB, 0 }, { 10, 0x25D9, 0 }, + { 11, 0x2642, 0 }, { 12, 0x2640, 0 }, { 13, 0x266A, 1 }, + { 15, 0x263C, 0 }, { 16, 0x25BA, 0 }, { 17, 0x25C4, 0 }, + { 18, 0x2195, 0 }, { 19, 0x203C, 0 }, { 20, 0x00B6, 0 }, + { 21, 0x00A7, 0 }, { 22, 0x25AC, 0 }, { 23, 0x21A8, 0 }, + { 24, 0x2191, 0 }, { 25, 0x2193, 0 }, { 26, 0x2192, 0 }, + { 27, 0x2190, 0 }, { 28, 0x221F, 0 }, { 29, 0x2194, 0 }, + { 30, 0x25B2, 0 }, { 31, 0x25BC, 0 }, { 32, 0x0020, 0x5e }, + { 127, 0x2302, 0 }, { 128, 0x00C7, 0 }, { 129, 0x00FC, 0 }, + { 130, 0x00E9, 0 }, { 131, 0x00E2, 0 }, { 132, 0x00E4, 0 }, + { 133, 0x00E0, 0 }, { 134, 0x00E5, 0 }, { 135, 0x00E7, 0 }, + { 136, 0x00EA, 1 }, { 138, 0x00E8, 0 }, { 139, 0x00EF, 0 }, + { 140, 0x00EE, 0 }, { 141, 0x00EC, 0 }, { 142, 0x00C4, 1 }, + { 144, 0x00C9, 0 }, { 145, 0x00E6, 0 }, { 146, 0x00C6, 0 }, + { 147, 0x00F4, 0 }, { 148, 0x00F6, 0 }, { 149, 0x00F2, 0 }, + { 150, 0x00FB, 0 }, { 151, 0x00F9, 0 }, { 152, 0x00FF, 0 }, + { 153, 0x00D6, 0 }, { 154, 0x00DC, 0 }, { 155, 0x00A2, 1 }, + { 157, 0x00A5, 0 }, { 158, 0x20A7, 0 }, { 159, 0x0192, 0 }, + { 160, 0x00E1, 0 }, { 161, 0x00ED, 0 }, { 162, 0x00F3, 0 }, + { 163, 0x00FA, 0 }, { 164, 0x00F1, 0 }, { 165, 0x00D1, 0 }, + { 166, 0x00AA, 0 }, { 167, 0x00BA, 0 }, { 168, 0x00BF, 0 }, + { 169, 0x2310, 0 }, { 170, 0x00AC, 0 }, { 171, 0x00BD, 0 }, + { 172, 0x00BC, 0 }, { 173, 0x00A1, 0 }, { 174, 0x00AB, 0 }, + { 175, 0x00BB, 0 }, { 176, 0x2591, 2 }, { 179, 0x2502, 0 }, + { 180, 0x2524, 0 }, { 181, 0x2561, 1 }, { 183, 0x2556, 0 }, + { 184, 0x2555, 0 }, { 185, 0x2563, 0 }, { 186, 0x2551, 0 }, + { 187, 0x2557, 0 }, { 188, 0x255D, 0 }, { 189, 0x255C, 0 }, + { 190, 0x255B, 0 }, { 191, 0x2510, 0 }, { 192, 0x2514, 0 }, + { 193, 0x2534, 0 }, { 194, 0x252C, 0 }, { 195, 0x251C, 0 }, + { 196, 0x2500, 0 }, { 197, 0x253C, 0 }, { 198, 0x255E, 1 }, + { 200, 0x255A, 0 }, { 201, 0x2554, 0 }, { 202, 0x2569, 0 }, + { 203, 0x2566, 0 }, { 204, 0x2560, 0 }, { 205, 0x2550, 0 }, + { 206, 0x256C, 0 }, { 207, 0x2567, 1 }, { 209, 0x2564, 1 }, + { 211, 0x2559, 0 }, { 212, 0x2558, 0 }, { 213, 0x2552, 1 }, + { 215, 0x256B, 0 }, { 216, 0x256A, 0 }, { 217, 0x2518, 0 }, + { 218, 0x250C, 0 }, { 219, 0x2588, 0 }, { 220, 0x2584, 0 }, + { 221, 0x258C, 0 }, { 222, 0x2590, 0 }, { 223, 0x2580, 0 }, + { 224, 0x03B1, 0 }, { 225, 0x00DF, 0 }, { 226, 0x0393, 0 }, + { 227, 0x03C0, 0 }, { 228, 0x03A3, 0 }, { 229, 0x03C3, 0 }, + { 230, 0x00B5, 0 }, { 231, 0x03C4, 0 }, { 232, 0x03A6, 0 }, + { 233, 0x0398, 0 }, { 234, 0x03A9, 0 }, { 235, 0x03B4, 0 }, + { 236, 0x221E, 0 }, { 237, 0x03C6, 0 }, { 238, 0x03B5, 0 }, + { 239, 0x2229, 0 }, { 240, 0x2261, 0 }, { 241, 0x00B1, 0 }, + { 242, 0x2265, 0 }, { 243, 0x2264, 0 }, { 244, 0x2320, 1 }, + { 246, 0x00F7, 0 }, { 247, 0x2248, 0 }, { 248, 0x00B0, 0 }, + { 249, 0x2219, 0 }, { 250, 0x00B7, 0 }, { 251, 0x221A, 0 }, + { 252, 0x207F, 0 }, { 253, 0x00B2, 0 }, { 254, 0x25A0, 0 }, + { 255, 0x00A0, 0 } +}; + +static uint16_t +vga_cp437_to_uni(uint8_t c) +{ + int min, mid, max; + + min = 0; + max = (sizeof(cp437unitable) / sizeof(struct cp437uni)) - 1; + + while (max >= min) { + mid = (min + max) / 2; + if (c < cp437unitable[mid].cp437_base) + max = mid - 1; + else if (c > cp437unitable[mid].cp437_base + + cp437unitable[mid].length) + min = mid + 1; + else + return (c - cp437unitable[mid].cp437_base + + cp437unitable[mid].unicode_base); + } + + return ('?'); +} + +/* + * install font for text mode + */ +static void +vidc_install_font(void) +{ + static uint8_t fsreg[8] = {0x0, 0x30, 0x5, 0x35, 0xa, 0x3a, 0xf, 0x3f}; + const uint8_t *from; + uint8_t volatile *to; + uint16_t c; + int i, j, s; + int bpc, f_offset; + teken_attr_t a = { 0 }; + + if (gfx_state.tg_fb_type != FB_TEXT) + return; + + /* Sync-reset the sequencer registers */ + vga_set_seq(VGA_REG_BASE, 0x00, 0x01); + /* + * enable write to plane2, since fonts + * could only be loaded into plane2 + */ + vga_set_seq(VGA_REG_BASE, 0x02, 0x04); + /* + * sequentially access data in the bit map being + * selected by MapMask register (index 0x02) + */ + vga_set_seq(VGA_REG_BASE, 0x04, 0x07); + /* Sync-reset ended, and allow the sequencer to operate */ + vga_set_seq(VGA_REG_BASE, 0x00, 0x03); + + /* + * select plane 2 on Read Mode 0 + */ + vga_set_grc(VGA_REG_BASE, 0x04, 0x02); + /* + * system addresses sequentially access data, follow + * Memory Mode register bit 2 in the sequencer + */ + vga_set_grc(VGA_REG_BASE, 0x05, 0x00); + /* + * set range of host memory addresses decoded by VGA + * hardware -- A0000h-BFFFFh (128K region) + */ + vga_set_grc(VGA_REG_BASE, 0x06, 0x00); + + /* + * This assumes 8x16 characters, which yield the traditional 80x25 + * screen. + */ + bpc = 16; + s = 0; /* font slot, maybe should use tunable there. */ + f_offset = s * 8 * 1024; + for (i = 0; i < 256; i++) { + c = vga_cp437_to_uni(i); + from = font_lookup(&gfx_state.tg_font, c, &a); + to = (unsigned char *)ptov(VGA_MEM_BASE) + f_offset + + i * 0x20; + for (j = 0; j < bpc; j++) + *to++ = *from++; + } + + /* Sync-reset the sequencer registers */ + vga_set_seq(VGA_REG_BASE, 0x00, 0x01); + /* enable write to plane 0 and 1 */ + vga_set_seq(VGA_REG_BASE, 0x02, 0x03); + /* + * enable character map selection + * and odd/even addressing + */ + vga_set_seq(VGA_REG_BASE, 0x04, 0x03); + /* + * select font map + */ + vga_set_seq(VGA_REG_BASE, 0x03, fsreg[s]); + /* Sync-reset ended, and allow the sequencer to operate */ + vga_set_seq(VGA_REG_BASE, 0x00, 0x03); + + /* restore graphic registers */ + + /* select plane 0 */ + vga_set_grc(VGA_REG_BASE, 0x04, 0x00); + /* enable odd/even addressing mode */ + vga_set_grc(VGA_REG_BASE, 0x05, 0x10); + /* + * range of host memory addresses decoded by VGA + * hardware -- B8000h-BFFFFh (32K region) + */ + vga_set_grc(VGA_REG_BASE, 0x06, 0x0e); + /* enable all color plane */ + vga_set_atr(VGA_REG_BASE, 0x12, 0x0f); +} + +bool +cons_update_mode(bool use_gfx_mode) +{ + const teken_attr_t *a; + teken_attr_t attr; + char env[10], *ptr; + int format, roff, goff, boff; + + gfx_state.tg_tp.tp_row = TEXT_ROWS; + gfx_state.tg_tp.tp_col = TEXT_COLS; + + if (use_gfx_mode) { + setup_font(&gfx_state, gfx_state.tg_fb.fb_height, + gfx_state.tg_fb.fb_width); + /* Point of origin in pixels. */ + gfx_state.tg_origin.tp_row = (gfx_state.tg_fb.fb_height - + (gfx_state.tg_tp.tp_row * gfx_state.tg_font.vf_height)) / 2; + gfx_state.tg_origin.tp_col = (gfx_state.tg_fb.fb_width - + (gfx_state.tg_tp.tp_col * gfx_state.tg_font.vf_width)) / 2; + + 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", gfx_state.tg_fb.fb_height); + env_setenv("screen.height", EV_VOLATILE | EV_NOHOOK, env, + env_noset, env_screen_nounset); + snprintf(env, sizeof (env), "%d", gfx_state.tg_fb.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); + } else { + /* Trigger loading of 8x16 font. */ + setup_font(&gfx_state, + 16 * gfx_state.tg_fb.fb_height + BORDER_PIXELS, + 8 * gfx_state.tg_fb.fb_width + BORDER_PIXELS); + gfx_state.tg_functions = &tf; + /* ensure the following are not set for text mode */ + unsetenv("screen.height"); + unsetenv("screen.width"); + unsetenv("screen.depth"); + unsetenv("kern.vt.fb.default_mode"); + vidc_install_font(); + } + + free(screen_buffer); + screen_buffer = malloc(gfx_state.tg_tp.tp_row * gfx_state.tg_tp.tp_col * + sizeof(*screen_buffer)); + if (screen_buffer == NULL) + return (false); + + teken_init(&gfx_state.tg_teken, gfx_state.tg_functions, &gfx_state); + + if (gfx_state.tg_ctype == CT_INDEXED) + format = COLOR_FORMAT_VGA; + else + format = COLOR_FORMAT_RGB; + + 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, format, + 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); + + if (gfx_state.tg_ctype == CT_INDEXED) + vidc_load_palette(cmap); + + 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 vidc_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, + vidc_set_colors, env_nounset); + snprintf(env, sizeof(env), "%d", attr.ta_bgcolor); + env_setenv("teken.bg_color", EV_VOLATILE, env, + vidc_set_colors, env_nounset); + } + + /* Improve visibility */ + if (attr.ta_bgcolor == TC_WHITE) + attr.ta_bgcolor |= TC_LIGHT; + teken_set_defattr(&gfx_state.tg_teken, &attr); + + snprintf(env, sizeof (env), "%u", (unsigned)gfx_state.tg_tp.tp_row); + setenv("LINES", env, 1); + snprintf(env, sizeof (env), "%u", (unsigned)gfx_state.tg_tp.tp_col); + setenv("COLUMNS", env, 1); + + /* 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); + + return (true); +} + static int vidc_init(int arg) { @@ -670,6 +989,7 @@ vidc_init(int arg) return (0); vidc_started = 1; + vbe_init(); /* * Check Miscellaneous Output Register (Read at 3CCh, Write at 3C2h) @@ -687,31 +1007,20 @@ vidc_init(int arg) val &= ~VGA_AC_MC_ELG; vga_set_atr(VGA_REG_BASE, VGA_AC_MODE_CONTROL, val); - tp.tp_row = TEXT_ROWS; - tp.tp_col = TEXT_COLS; - buffer = malloc(tp.tp_row * tp.tp_col * sizeof(*buffer)); - if (buffer == NULL) +#if defined(FRAMEBUFFER_MODE) + val = vbe_default_mode(); + /* if val is not legal VBE mode, use text mode */ + if (VBE_VALID_MODE(val)) { + if (vbe_set_mode(val) != 0) + bios_set_text_mode(VGA_TEXT_MODE); + } +#endif + + gfx_framework_init(); + + if (!cons_update_mode(VBE_VALID_MODE(vbe_get_mode()))) return (1); - snprintf(env, sizeof (env), "%u", tp.tp_row); - setenv("LINES", env, 1); - snprintf(env, sizeof (env), "%u", tp.tp_col); - setenv("COLUMNS", env, 1); - - teken_init(&teken, &tf, NULL); - teken_set_winsize(&teken, &tp); - a = teken_get_defattr(&teken); - - snprintf(env, sizeof(env), "%d", a->ta_fgcolor); - env_setenv("teken.fg_color", EV_VOLATILE, env, vidc_set_colors, - env_nounset); - snprintf(env, sizeof(env), "%d", a->ta_bgcolor); - env_setenv("teken.bg_color", EV_VOLATILE, env, vidc_set_colors, - env_nounset); - - /* Erase display, this will also fill our screen buffer. */ - teken_input(&teken, "\e[J", 3); - for (int i = 0; i < 10 && vidc_ischar(); i++) (void) vidc_getchar(); @@ -734,8 +1043,8 @@ vidc_putchar(int c) { unsigned char ch = c; - if (buffer != NULL) - teken_input(&teken, &ch, sizeof (ch)); + if (screen_buffer != NULL) + teken_input(&gfx_state.tg_teken, &ch, sizeof (ch)); else vidc_biosputchar(c); } @@ -808,27 +1117,27 @@ vidc_ischar(void) #if KEYBOARD_PROBE -#define PROBE_MAXRETRY 5 -#define PROBE_MAXWAIT 400 -#define IO_DUMMY 0x84 -#define IO_KBD 0x060 /* 8042 Keyboard */ +#define PROBE_MAXRETRY 5 +#define PROBE_MAXWAIT 400 +#define IO_DUMMY 0x84 +#define IO_KBD 0x060 /* 8042 Keyboard */ /* selected defines from kbdio.h */ -#define KBD_STATUS_PORT 4 /* status port, read */ -#define KBD_DATA_PORT 0 /* data port, read/write +#define KBD_STATUS_PORT 4 /* status port, read */ +#define KBD_DATA_PORT 0 /* data port, read/write * also used as keyboard command * and mouse command port */ -#define KBDC_ECHO 0x00ee -#define KBDS_ANY_BUFFER_FULL 0x0001 -#define KBDS_INPUT_BUFFER_FULL 0x0002 -#define KBD_ECHO 0x00ee +#define KBDC_ECHO 0x00ee +#define KBDS_ANY_BUFFER_FULL 0x0001 +#define KBDS_INPUT_BUFFER_FULL 0x0002 +#define KBD_ECHO 0x00ee /* 7 microsec delay necessary for some keyboard controllers */ static void delay7(void) { - /* + /* * I know this is broken, but no timer is available yet at this stage... * See also comments in `delay1ms()'. */ @@ -854,7 +1163,7 @@ delay1ms(void) (void) inb(0x84); } -/* +/* * We use the presence/absence of a keyboard to determine whether the internal * console can be used for input. * diff --git a/stand/i386/loader/Makefile b/stand/i386/loader/Makefile index 74f39c07864..e80e19dfb9e 100644 --- a/stand/i386/loader/Makefile +++ b/stand/i386/loader/Makefile @@ -23,7 +23,13 @@ VERSION_FILE= ${.CURDIR}/../loader/version .PATH: ${BOOTSRC}/i386/loader # architecture-specific loader code -SRCS= main.c conf.c vers.c chain.c +SRCS= main.c conf.c vers.c chain.c gfx_fb.c 8x16.c + +CFLAGS.gfx_fb.c += -I${.CURDIR}/../libi386 +CFLAGS.gfx_fb.c += -I$(SRCTOP)/sys/teken +CFLAGS.gfx_fb.c += -I${SRCTOP}/sys/cddl/contrib/opensolaris/common/lz4 +CFLAGS.gfx_fb.c += -I${SRCTOP}/contrib/pnglite +CFLAGS.gfx_fb.c += -DHAVE_MEMCPY -I${SRCTOP}/sys/contrib/zlib # Include bcache code. HAVE_BCACHE= yes @@ -49,7 +55,7 @@ HELP_FILES= ${.CURDIR}/help.i386 # Always add MI sources .include "${BOOTSRC}/loader.mk" -CLEANFILES+= ${LOADER} ${LOADER}.bin +CLEANFILES+= ${LOADER} ${LOADER}.bin 8x16.c ORG= 0x0 @@ -64,6 +70,9 @@ CFLAGS+= -I${BOOTSRC}/i386 #CFLAGS+= -g #LDFLAGS+= -g +8x16.c: ${SRCTOP}/contrib/terminus/ter-u16v.bdf + vtfontcvt -f compressed-source -o ${.TARGET} ${.ALLSRC} + ${LOADER}: ${LOADER}.bin ${BTXLDR} ${BTXKERN} btxld -v -f aout -e ${LOADER_ADDRESS} -o ${.TARGET} -l ${BTXLDR} \ -b ${BTXKERN} ${LOADER}.bin diff --git a/stand/i386/loader/main.c b/stand/i386/loader/main.c index ceeaf7260ac..a595340d3a1 100644 --- a/stand/i386/loader/main.c +++ b/stand/i386/loader/main.c @@ -130,6 +130,12 @@ main(void) } setheap(heap_bottom, heap_top); + /* + * detect ACPI for future reference. This may set console to comconsole + * if we do have ACPI SPCR table. + */ + biosacpi_detect(); + /* * XXX Chicken-and-egg problem; we want to have console output early, * but some console attributes may depend on reading from eg. the boot @@ -242,9 +248,6 @@ main(void) initial_bootinfo->bi_extmem = bios_extmem / 1024; } - /* detect ACPI for future reference */ - biosacpi_detect(); - /* detect SMBIOS for future reference */ smbios_detect(NULL); @@ -254,6 +257,7 @@ main(void) printf("\n%s", bootprog_info); extract_currdev(); /* set $currdev and $loaddev */ + autoload_font(true); bios_getsmap(); diff --git a/stand/images/Makefile b/stand/images/Makefile new file mode 100644 index 00000000000..432b1daa851 --- /dev/null +++ b/stand/images/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.include + +FILES+= freebsd-brand-rev.png freebsd-brand.png freebsd-logo-rev.png + +FILESDIR= /boot/images + +.include diff --git a/stand/images/freebsd-brand-rev.png b/stand/images/freebsd-brand-rev.png new file mode 100644 index 0000000000000000000000000000000000000000..945c12da05565ec5857345d844caf3f81a8b7c8b GIT binary patch literal 7709 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 Yo<{OB%d?C3x&WZAtgTd{VE*v`0hhx4EC2ui literal 0 HcmV?d00001 diff --git a/stand/images/freebsd-brand.png b/stand/images/freebsd-brand.png new file mode 100644 index 0000000000000000000000000000000000000000..8ce3091bd83f9e7d43077f99b82a2576a76e5343 GIT binary patch literal 13788 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 literal 0 HcmV?d00001 diff --git a/stand/liblua/Makefile b/stand/liblua/Makefile index 43426806315..68fa2da64fd 100644 --- a/stand/liblua/Makefile +++ b/stand/liblua/Makefile @@ -35,6 +35,8 @@ CFLAGS+= -ffreestanding -nostdlib -DLUA_USE_POSIX CFLAGS+= -fno-stack-protector -D__BSD_VISIBLE CFLAGS+= -I${BOOTSRC}/include -I${LIBLUASRC} -I${LUASRC} -I${LDRSRC} +CFLAGS.lutils.c+= -I${SRCTOP}/sys/teken -I${SRCTOP}/contrib/pnglite + .if ${MACHINE_CPUARCH} == "amd64" && ${DO32:U0} == 0 CFLAGS+= -fPIC .endif diff --git a/stand/liblua/lutils.c b/stand/liblua/lutils.c index 7c4dcf01f7c..9243edd07e8 100644 --- a/stand/liblua/lutils.c +++ b/stand/liblua/lutils.c @@ -35,6 +35,8 @@ __FBSDID("$FreeBSD$"); #include "lstd.h" #include "lutils.h" #include "bootstrap.h" +#include +#include /* * Like loader.perform, except args are passed already parsed @@ -346,6 +348,189 @@ lua_writefile(lua_State *L) return 1; } +/* + * put image using terminal coordinates. + */ +static int +lua_term_putimage(lua_State *L) +{ + const char *name; + png_t png; + uint32_t x1, y1, x2, y2, f; + int nargs, ret = 0, error; + + nargs = lua_gettop(L); + if (nargs != 6) { + lua_pushboolean(L, 0); + return 1; + } + + name = luaL_checkstring(L, 1); + x1 = luaL_checknumber(L, 2); + y1 = luaL_checknumber(L, 3); + x2 = luaL_checknumber(L, 4); + y2 = luaL_checknumber(L, 5); + f = luaL_checknumber(L, 6); + + x1 = gfx_state.tg_origin.tp_col + x1 * gfx_state.tg_font.vf_width; + y1 = gfx_state.tg_origin.tp_row + y1 * gfx_state.tg_font.vf_height; + if (x2 != 0) { + x2 = gfx_state.tg_origin.tp_col + + x2 * gfx_state.tg_font.vf_width; + } + if (y2 != 0) { + y2 = gfx_state.tg_origin.tp_row + + y2 * gfx_state.tg_font.vf_height; + } + + if ((error = png_open(&png, name)) != PNG_NO_ERROR) { + if (f & FL_PUTIMAGE_DEBUG) + printf("%s\n", png_error_string(error)); + } else { + if (gfx_fb_putimage(&png, x1, y1, x2, y2, f) == 0) + ret = 1; + (void) png_close(&png); + } + lua_pushboolean(L, ret); + return 1; +} + +static int +lua_fb_putimage(lua_State *L) +{ + const char *name; + png_t png; + uint32_t x1, y1, x2, y2, f; + int nargs, ret = 0, error; + + nargs = lua_gettop(L); + if (nargs != 6) { + lua_pushboolean(L, 0); + return 1; + } + + name = luaL_checkstring(L, 1); + x1 = luaL_checknumber(L, 2); + y1 = luaL_checknumber(L, 3); + x2 = luaL_checknumber(L, 4); + y2 = luaL_checknumber(L, 5); + f = luaL_checknumber(L, 6); + + if ((error = png_open(&png, name)) != PNG_NO_ERROR) { + if (f & FL_PUTIMAGE_DEBUG) + printf("%s\n", png_error_string(error)); + } else { + if (gfx_fb_putimage(&png, x1, y1, x2, y2, f) == 0) + ret = 1; + (void) png_close(&png); + } + lua_pushboolean(L, ret); + return 1; +} + +static int +lua_fb_setpixel(lua_State *L) +{ + uint32_t x, y; + int nargs; + + nargs = lua_gettop(L); + if (nargs != 2) { + lua_pushnil(L); + return 1; + } + + x = luaL_checknumber(L, 1); + y = luaL_checknumber(L, 2); + gfx_fb_setpixel(x, y); + return 0; +} + +static int +lua_fb_line(lua_State *L) +{ + uint32_t x0, y0, x1, y1, wd; + int nargs; + + nargs = lua_gettop(L); + if (nargs != 5) { + lua_pushnil(L); + return 1; + } + + x0 = luaL_checknumber(L, 1); + y0 = luaL_checknumber(L, 2); + x1 = luaL_checknumber(L, 3); + y1 = luaL_checknumber(L, 4); + wd = luaL_checknumber(L, 5); + gfx_fb_line(x0, y0, x1, y1, wd); + return 0; +} + +static int +lua_fb_bezier(lua_State *L) +{ + uint32_t x0, y0, x1, y1, x2, y2, width; + int nargs; + + nargs = lua_gettop(L); + if (nargs != 7) { + lua_pushnil(L); + return 1; + } + + x0 = luaL_checknumber(L, 1); + y0 = luaL_checknumber(L, 2); + x1 = luaL_checknumber(L, 3); + y1 = luaL_checknumber(L, 4); + x2 = luaL_checknumber(L, 5); + y2 = luaL_checknumber(L, 6); + width = luaL_checknumber(L, 7); + gfx_fb_bezier(x0, y0, x1, y1, x2, y2, width); + return 0; +} + +static int +lua_fb_drawrect(lua_State *L) +{ + uint32_t x0, y0, x1, y1, fill; + int nargs; + + nargs = lua_gettop(L); + if (nargs != 5) { + lua_pushnil(L); + return 1; + } + + x0 = luaL_checknumber(L, 1); + y0 = luaL_checknumber(L, 2); + x1 = luaL_checknumber(L, 3); + y1 = luaL_checknumber(L, 4); + fill = luaL_checknumber(L, 5); + gfx_fb_drawrect(x0, y0, x1, y1, fill); + return 0; +} + +static int +lua_term_drawrect(lua_State *L) +{ + uint32_t x0, y0, x1, y1; + int nargs; + + nargs = lua_gettop(L); + if (nargs != 4) { + lua_pushnil(L); + return 1; + } + + x0 = luaL_checknumber(L, 1); + y0 = luaL_checknumber(L, 2); + x1 = luaL_checknumber(L, 3); + y1 = luaL_checknumber(L, 4); + gfx_term_drawrect(x0, y0, x1, y1); + return 0; +} + #define REG_SIMPLE(n) { #n, lua_ ## n } static const struct luaL_Reg loaderlib[] = { REG_SIMPLE(delay), @@ -360,6 +545,13 @@ static const struct luaL_Reg loaderlib[] = { REG_SIMPLE(setenv), REG_SIMPLE(time), REG_SIMPLE(unsetenv), + REG_SIMPLE(fb_bezier), + REG_SIMPLE(fb_drawrect), + REG_SIMPLE(fb_line), + REG_SIMPLE(fb_putimage), + REG_SIMPLE(fb_setpixel), + REG_SIMPLE(term_drawrect), + REG_SIMPLE(term_putimage), { NULL, NULL }, }; diff --git a/stand/loader.mk b/stand/loader.mk index f6341052ab7..9ed8b65a2d0 100644 --- a/stand/loader.mk +++ b/stand/loader.mk @@ -8,9 +8,15 @@ SRCS+= boot.c commands.c console.c devopen.c interp.c SRCS+= interp_backslash.c interp_parse.c ls.c misc.c SRCS+= module.c nvstore.c +CFLAGS.module.c += -I$(SRCTOP)/sys/teken -I${SRCTOP}/contrib/pnglite + .if ${MACHINE} == "i386" || ${MACHINE_CPUARCH} == "amd64" SRCS+= load_elf32.c load_elf32_obj.c reloc_elf32.c SRCS+= load_elf64.c load_elf64_obj.c reloc_elf64.c +SRCS+= pnglite.c +.PATH: ${SRCTOP}/contrib/pnglite +CFLAGS.pnglite.c+= -I${SRCTOP}/contrib/pnglite +CFLAGS.pnglite.c+= -DHAVE_MEMCPY -I${SRCTOP}/sys/contrib/zlib .elif ${MACHINE_CPUARCH} == "aarch64" SRCS+= load_elf64.c reloc_elf64.c .elif ${MACHINE_CPUARCH} == "arm" diff --git a/stand/lua/color.lua b/stand/lua/color.lua index 364d548ca3d..4dc16cacb97 100644 --- a/stand/lua/color.lua +++ b/stand/lua/color.lua @@ -56,7 +56,7 @@ function color.isEnabled() if c ~= nil then return c:lower() ~= "no" and c ~= "0" end - return not core.isSerialBoot() + return true end function color.escapefg(color_value) diff --git a/stand/lua/core.lua b/stand/lua/core.lua index 441a032dc51..9d331bc0ad3 100644 --- a/stand/lua/core.lua +++ b/stand/lua/core.lua @@ -383,6 +383,19 @@ function core.isZFSBoot() return false end +function core.isFramebufferConsole() + local c = loader.getenv("console") + if c ~= nil then + if c:find("efi") == nil and c:find("vidconsole") == nil then + return false + end + if loader.getenv("screen.depth") ~= nil then + return true + end + end + return false +end + function core.isSerialConsole() local c = loader.getenv("console") if c ~= nil then diff --git a/stand/lua/drawer.lua b/stand/lua/drawer.lua index 32483f1b424..3ace3884ff8 100644 --- a/stand/lua/drawer.lua +++ b/stand/lua/drawer.lua @@ -202,7 +202,7 @@ local function defaultframe() return "double" end -local function drawbox() +local function drawframe() local x = menu_position.x - 3 local y = menu_position.y - 1 local w = frame_size.w @@ -213,7 +213,7 @@ local function drawbox() -- If we don't have a framespec for the current frame style, just don't -- draw a box. if framespec == nil then - return + return false end local hl = framespec.horizontal @@ -227,6 +227,11 @@ local function drawbox() x = x + shift.x y = y + shift.y + if core.isFramebufferConsole() and loader.term_drawrect ~= nil then + loader.term_drawrect(x, y, x + w, y + h) + return true + end + screen.setcursor(x, y); printc(tl) screen.setcursor(x, y + h); printc(bl) screen.setcursor(x + w, y); printc(tr) @@ -248,12 +253,25 @@ local function drawbox() screen.setcursor(x + w, y + i) printc(vl) end + return true +end +local function drawbox() + local x = menu_position.x - 3 + local y = menu_position.y - 1 + local w = frame_size.w local menu_header = loader.getenv("loader_menu_title") or "Welcome to FreeBSD" local menu_header_align = loader.getenv("loader_menu_title_align") local menu_header_x + x = x + shift.x + y = y + shift.y + + if drawframe(x, y, w) == false then + return + end + if menu_header_align ~= nil then menu_header_align = menu_header_align:lower() if menu_header_align == "left" then @@ -287,6 +305,14 @@ local function drawbrand() x = x + shift.x y = y + shift.y + if core.isFramebufferConsole() and + loader.term_putimage ~= nil and + branddef.image ~= nil then + if loader.term_putimage(branddef.image, 0, 0, 0, 7, 0) + then + return true + end + end draw(x, y, graphic) end @@ -330,9 +356,33 @@ local function drawlogo() y = y + logodef.shift.y end + if core.isFramebufferConsole() and + loader.term_putimage ~= nil and + logodef.image ~= nil then + local y1 = 15 + + if logodef.image_rl ~= nil then + y1 = logodef.image_rl + end + if loader.term_putimage(logodef.image, x, y, 0, y + y1, 0) + then + return true + end + end draw(x, y, logodef.graphic) end +local function drawitem(func) + local console = loader.getenv("console") + local c + + for c in string.gmatch(console, "%w+") do + loader.setenv("console", c) + func() + end + loader.setenv("console", console) +end + fbsd_brand = { " ______ ____ _____ _____ ", " | ____| | _ \\ / ____| __ \\ ", @@ -378,6 +428,7 @@ branddefs = { -- keys are: graphic (table depicting graphic) ["fbsd"] = { graphic = fbsd_brand, + image = "/boot/images/freebsd-brand-rev.png", }, ["none"] = { graphic = none, @@ -458,9 +509,9 @@ drawer.frame_styles = { function drawer.drawscreen(menudef) -- drawlogo() must go first. -- it determines the positions of other elements - drawlogo() - drawbrand() - drawbox() + drawitem(drawlogo) + drawitem(drawbrand) + drawitem(drawbox) return drawmenu(menudef) end diff --git a/stand/lua/gfx-orb.lua b/stand/lua/gfx-orb.lua index 1af8f9f9607..2702618948d 100644 --- a/stand/lua/gfx-orb.lua +++ b/stand/lua/gfx-orb.lua @@ -47,6 +47,8 @@ return { " .---.....----.\027[m", }, requires_color = true, - shift = {x = 2, y = 4}, + shift = {x = 2, y = 3}, + image = "/boot/images/freebsd-logo-rev.png", + image_rl = 15 } } diff --git a/stand/userboot/userboot/Makefile b/stand/userboot/userboot/Makefile index f256ac2769e..49026375328 100644 --- a/stand/userboot/userboot/Makefile +++ b/stand/userboot/userboot/Makefile @@ -35,6 +35,7 @@ CFLAGS+= -I${BOOTSRC}/userboot CFLAGS.main.c+= -I${BOOTSRC}/libsa/zfs CFLAGS.main.c+= -I${SYSDIR}/contrib/openzfs/include CFLAGS.main.c+= -I${SYSDIR}/contrib/openzfs/include/os/freebsd/zfs +CFLAGS.userboot_cons.c+= -I$(SRCTOP)/sys/teken -I${SRCTOP}/contrib/pnglite CWARNFLAGS.main.c += -Wno-implicit-function-declaration LDFLAGS+= -nostdlib -Wl,-Bsymbolic diff --git a/stand/userboot/userboot/userboot_cons.c b/stand/userboot/userboot/userboot_cons.c index 6f73ad5633b..528f9ac40af 100644 --- a/stand/userboot/userboot/userboot_cons.c +++ b/stand/userboot/userboot/userboot_cons.c @@ -28,9 +28,14 @@ __FBSDID("$FreeBSD$"); #include +#include +#include "gfx_fb.h" #include "bootstrap.h" #include "libuserboot.h" +font_list_t fonts = STAILQ_HEAD_INITIALIZER(fonts); +teken_gfx_t gfx_state = { 0 }; + int console; static struct console *userboot_comconsp; diff --git a/sys/sys/font.h b/sys/sys/font.h index f8bf151c5f5..e09b2112959 100644 --- a/sys/sys/font.h +++ b/sys/sys/font.h @@ -93,9 +93,10 @@ typedef struct vt_font_bitmap_data { } vt_font_bitmap_data_t; typedef enum { - FONT_AUTO, - FONT_MANUAL, - FONT_BOOT + FONT_AUTO, /* This font is loaded by software */ + FONT_MANUAL, /* This font is loaded manually by user */ + FONT_BUILTIN, /* This font was built in at compile time */ + FONT_RELOAD /* This font is marked to be re-read from file */ } FONT_FLAGS; struct fontlist { diff --git a/sys/teken/teken.h b/sys/teken/teken.h index 0a3928a9ffd..994298b0dbc 100644 --- a/sys/teken/teken.h +++ b/sys/teken/teken.h @@ -48,6 +48,7 @@ typedef unsigned char teken_format_t; #define TF_BLINK 0x04 /* Blinking character. */ #define TF_REVERSE 0x08 /* Reverse rendered character. */ #define TF_CJK_RIGHT 0x10 /* Right-hand side of CJK character. */ +#define TF_IMAGE 0x20 /* This character space has image. */ typedef unsigned char teken_color_t; #define TC_BLACK 0 #define TC_RED 1