diff --git a/stand/Makefile b/stand/Makefile --- a/stand/Makefile +++ b/stand/Makefile @@ -25,6 +25,8 @@ 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 --- 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 @@ }; 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 @@ 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.h b/stand/common/gfx_fb.h new file mode 100644 --- /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/gfx_fb.c b/stand/common/gfx_fb.c new file mode 100644 --- /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/module.c b/stand/common/module.c --- a/stand/common/module.c +++ b/stand/common/module.c @@ -38,6 +38,8 @@ #include #include #include +#include +#include #if defined(LOADER_FDT_SUPPORT) #include @@ -91,7 +93,6 @@ NULL }; - /* * load an object, either a disk file or code module. * @@ -616,6 +617,92 @@ 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 --- a/stand/defaults/loader.conf.5 +++ b/stand/defaults/loader.conf.5 @@ -238,7 +238,9 @@ .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 --- a/stand/efi/include/efilib.h +++ b/stand/efi/include/efilib.h @@ -108,7 +108,6 @@ 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 --- a/stand/efi/libefi/Makefile +++ b/stand/efi/libefi/Makefile @@ -49,7 +49,7 @@ .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 --- 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 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 @@ .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 @@ 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 @@ 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 @@ } 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 @@ 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 @@ } 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); + efi_text_putchar(state, &p, c, a); + if (state->tg_cursor_visible) + conout->EnableCursor(conout, TRUE); } -static bool -efi_same_pixel(struct text_pixel *px1, struct text_pixel *px2) +static void +efi_text_copy_line(teken_gfx_t *state, int ncol, teken_pos_t *s, + teken_pos_t *d, bool scroll) { - 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); + 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 *ptr __unused, const teken_rect_t *r, const teken_pos_t *p) +efi_text_copy(void *arg, const teken_rect_t *r, const teken_pos_t *p) { - int srow, drow; - int nrow, ncol, x, y; /* Has to be signed - >= 0 comparison */ + 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 @@ * 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. */ + soffset = r->tr_begin.tp_col + r->tr_begin.tp_row * state->tg_tp.tp_col; + doffset = p->tp_col + p->tp_row * state->tg_tp.tp_col; + + /* remove the cursor */ + if (state->tg_cursor_visible) + conout->EnableCursor(conout, FALSE); + + /* + * Copy line by line. + */ + if (doffset <= soffset) { + s = r->tr_begin; + d = *p; for (y = 0; y < nrow; y++) { - 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; - - 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); - } - } - } + 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; - - 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); - } - } - } + for (y = nrow - 1; y >= 0; y--) { + s.tp_row = r->tr_begin.tp_row + y; + d.tp_row = p->tp_row + y; + + efi_text_copy_line(state, ncol, &s, &d, false); } } - 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 @@ 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 @@ 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 @@ 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 @@ #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 @@ */ 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 @@ 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 @@ 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 @@ 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_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 @@ * 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 --- a/stand/efi/loader/Makefile +++ b/stand/efi/loader/Makefile @@ -22,7 +22,9 @@ 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 @@ 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 @@ # 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 --- a/stand/efi/loader/bootinfo.c +++ b/stand/efi/loader/bootinfo.c @@ -447,7 +447,7 @@ */ 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 @@ /* 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 @@ 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 --- a/stand/efi/loader/framebuffer.c +++ b/stand/efi/loader/framebuffer.c @@ -40,9 +40,10 @@ #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 @@ 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 @@ } 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 @@ 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 @@ 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 --- a/stand/efi/loader/main.c +++ b/stand/efi/loader/main.c @@ -1155,6 +1155,7 @@ !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 @@ 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 --- a/stand/ficl.mk +++ b/stand/ficl.mk @@ -15,7 +15,7 @@ .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 --- a/stand/ficl/Makefile +++ b/stand/ficl/Makefile @@ -12,6 +12,8 @@ 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 --- 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 @@ 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 --- /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 --- /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 --- a/stand/forth/beastie.4th +++ b/stand/forth/beastie.4th @@ -82,6 +82,10 @@ 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 --- 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 --- a/stand/forth/brand.4th +++ b/stand/forth/brand.4th @@ -72,3 +72,7 @@ else drop then then ; + +: draw-brand + ['] draw-brand console-iterate +; diff --git a/stand/forth/color.4th b/stand/forth/color.4th --- a/stand/forth/color.4th +++ b/stand/forth/color.4th @@ -28,7 +28,7 @@ \ 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 @@ 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 --- a/stand/forth/frames.4th +++ b/stand/forth/frames.4th @@ -121,6 +121,20 @@ ; : 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 --- 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 --- a/stand/forth/menu.4th +++ b/stand/forth/menu.4th @@ -991,6 +991,24 @@ 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 @@ 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 --- a/stand/forth/support.4th +++ b/stand/forth/support.4th @@ -190,6 +190,25 @@ 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 @@ : 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 @@ 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 --- a/stand/i386/libi386/Makefile +++ b/stand/i386/libi386/Makefile @@ -9,7 +9,7 @@ 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 @@ .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 --- a/stand/i386/libi386/bootinfo.c +++ b/stand/i386/libi386/bootinfo.c @@ -32,10 +32,21 @@ #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 --- a/stand/i386/libi386/bootinfo32.c +++ b/stand/i386/libi386/bootinfo32.c @@ -205,6 +205,8 @@ /* 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 @@ #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 --- a/stand/i386/libi386/bootinfo64.c +++ b/stand/i386/libi386/bootinfo64.c @@ -227,6 +227,8 @@ /* 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 @@ #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 --- a/stand/i386/libi386/libi386.h +++ b/stand/i386/libi386/libi386.h @@ -145,6 +145,7 @@ 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.h b/stand/i386/libi386/vbe.h new file mode 100644 --- /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/vbe.c b/stand/i386/libi386/vbe.c new file mode 100644 --- /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/vidconsole.c b/stand/i386/libi386/vidconsole.c --- 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 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 @@ .tf_respond = vidc_cons_respond, }; -teken_t teken; -teken_pos_t tp; - -struct text_pixel { - teken_char_t c; - teken_attr_t a; -}; - -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 +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, }; -#define TEXT_COLS 80 -#define TEXT_ROWS 25 #define KEYBUFSZ 10 static uint8_t keybuf[KEYBUFSZ]; /* keybuf for extended codes */ @@ -111,60 +99,26 @@ .c_ready = vidc_ischar }; -static int -vga_get_reg(int reg, int index) -{ - return (inb(reg + index)); -} - -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) +/* + * 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) { - vga_set_indexed(reg, VGA_CRTC_ADDRESS, VGA_CRTC_DATA, i, v); + 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; + } + } } static void @@ -353,9 +307,9 @@ } 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ } 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 @@ /* 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 @@ 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 @@ 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 @@ return (0); vidc_started = 1; + vbe_init(); /* * Check Miscellaneous Output Register (Read at 3CCh, Write at 3C2h) @@ -687,30 +1007,19 @@ 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) - 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); +#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 - 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); + gfx_framework_init(); - /* Erase display, this will also fill our screen buffer. */ - teken_input(&teken, "\e[J", 3); + if (!cons_update_mode(VBE_VALID_MODE(vbe_get_mode()))) + return (1); for (int i = 0; i < 10 && vidc_ischar(); i++) (void) vidc_getchar(); @@ -734,8 +1043,8 @@ { 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 @@ #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 @@ (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 --- a/stand/i386/loader/Makefile +++ b/stand/i386/loader/Makefile @@ -23,7 +23,13 @@ .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 @@ # 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+= -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 --- a/stand/i386/loader/main.c +++ b/stand/i386/loader/main.c @@ -130,6 +130,12 @@ } 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 @@ 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 @@ 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 --- /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..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ +#include /* * Like loader.perform, except args are passed already parsed @@ -346,6 +348,189 @@ 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 @@ 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 --- a/stand/loader.mk +++ b/stand/loader.mk @@ -8,9 +8,15 @@ 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 --- a/stand/lua/color.lua +++ b/stand/lua/color.lua @@ -56,7 +56,7 @@ 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 --- a/stand/lua/core.lua +++ b/stand/lua/core.lua @@ -383,6 +383,19 @@ 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 --- a/stand/lua/drawer.lua +++ b/stand/lua/drawer.lua @@ -202,7 +202,7 @@ 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 @@ -- 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ -- keys are: graphic (table depicting graphic) ["fbsd"] = { graphic = fbsd_brand, + image = "/boot/images/freebsd-brand-rev.png", }, ["none"] = { graphic = none, @@ -458,9 +509,9 @@ 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 --- a/stand/lua/gfx-orb.lua +++ b/stand/lua/gfx-orb.lua @@ -47,6 +47,8 @@ " .---.....----.\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 --- a/stand/userboot/userboot/Makefile +++ b/stand/userboot/userboot/Makefile @@ -35,6 +35,7 @@ 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 --- 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 --- a/sys/sys/font.h +++ b/sys/sys/font.h @@ -93,9 +93,10 @@ } 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 --- a/sys/teken/teken.h +++ b/sys/teken/teken.h @@ -48,6 +48,7 @@ #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