Index: contrib/pnglite/LICENSE =================================================================== --- /dev/null +++ contrib/pnglite/LICENSE @@ -0,0 +1,25 @@ +pnglite.h - Interface for pnglite library + +Copyright (c) 2007 Daniel Karling + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +Daniel Karling +daniel.karling@gmail.com Index: contrib/pnglite/README.md =================================================================== --- /dev/null +++ contrib/pnglite/README.md @@ -0,0 +1,5 @@ +pnglite +==== +#### A pretty small png library + +Currently all documentation resides in pnglite.h. Index: contrib/pnglite/pnglite.h =================================================================== --- /dev/null +++ contrib/pnglite/pnglite.h @@ -0,0 +1,169 @@ +/* + * pnglite.h - Interface for pnglite library + * Copyright (c) 2007 Daniel Karling + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not + * be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * Daniel Karling + * daniel.karling@gmail.com + */ + + +#ifndef _PNGLITE_H_ +#define _PNGLITE_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Enumerations for pnglite. + * Negative numbers are error codes and 0 and up are okay responses. + */ + +enum { + PNG_DONE = 1, + PNG_NO_ERROR = 0, + PNG_FILE_ERROR = -1, + PNG_HEADER_ERROR = -2, + PNG_IO_ERROR = -3, + PNG_EOF_ERROR = -4, + PNG_CRC_ERROR = -5, + PNG_MEMORY_ERROR = -6, + PNG_ZLIB_ERROR = -7, + PNG_UNKNOWN_FILTER = -8, + PNG_NOT_SUPPORTED = -9, + PNG_WRONG_ARGUMENTS = -10 +}; + +/* + * The five different kinds of color storage in PNG files. + */ + +enum { + PNG_GREYSCALE = 0, + PNG_TRUECOLOR = 2, + PNG_INDEXED = 3, + PNG_GREYSCALE_ALPHA = 4, + PNG_TRUECOLOR_ALPHA = 6 +}; + +typedef struct { + void *zs; /* pointer to z_stream */ + int fd; + unsigned char *image; + + unsigned char *png_data; + unsigned png_datalen; + + unsigned width; + unsigned height; + unsigned char depth; + unsigned char color_type; + unsigned char compression_method; + unsigned char filter_method; + unsigned char interlace_method; + unsigned char bpp; + + unsigned char *readbuf; + unsigned readbuflen; +} png_t; + + +/* + * Function: png_open + * + * This function is used to open a png file with the internal file + * IO system. + * + * Parameters: + * png - Empty png_t struct. + * filename - Filename of the file to be opened. + * + * Returns: + * PNG_NO_ERROR on success, otherwise an error code. + */ + +int png_open(png_t *png, const char *filename); + +/* + * Function: png_print_info + * + * This function prints some info about the opened png file to stdout. + * + * Parameters: + * png - png struct to get info from. + */ + +void png_print_info(png_t *png); + +/* + * Function: png_error_string + * + * This function translates an error code to a human readable string. + * + * Parameters: + * error - Error code. + * + * Returns: + * Pointer to string. + */ + +char *png_error_string(int error); + +/* + * Function: png_get_data + * + * This function decodes the opened png file and stores the result in data. + * data should be big enough to hold the decoded png. + * Required size will be: + * + * > width*height*(bytes per pixel) + * + * Parameters: + * data - Where to store result. + * + * Returns: + * PNG_NO_ERROR on success, otherwise an error code. + */ + +int png_get_data(png_t *png, uint8_t *data); + +/* + * Function: png_close + * + * Closes an open png file pointer. + * + * Parameters: + * png - png to close. + * + * Returns: + * PNG_NO_ERROR + */ + +int png_close(png_t *png); + +#ifdef __cplusplus +} +#endif + +#endif /* _PNGLITE_H_ */ Index: contrib/pnglite/pnglite.c =================================================================== --- /dev/null +++ contrib/pnglite/pnglite.c @@ -0,0 +1,628 @@ +/* + * pnglite.c - pnglite library + * For conditions of distribution and use, see copyright notice in pnglite.h + */ + +/* + * Note: this source is updated to enable build for FreeBSD boot loader. + */ + +#ifdef _STANDALONE +#include +#include +#else +#include +#include +#include +#include +#include +#endif +#include +#include "pnglite.h" + +#ifndef abs +#define abs(x) ((x) < 0? -(x):(x)) +#endif + +#define PNG_32b(b, s) ((uint32_t)(b) << (s)) +#define PNG_U32(b1, b2, b3, b4) \ + (PNG_32b(b1, 24) | PNG_32b(b2, 16) | PNG_32b(b3, 8) | PNG_32b(b4, 0)) + +#define png_IDAT PNG_U32(73, 68, 65, 84) +#define png_IEND PNG_U32(73, 69, 78, 68) + +static ssize_t +file_read(png_t *png, void *out, size_t size, size_t numel) +{ + ssize_t result; + off_t offset = (off_t)(size * numel); + + if (offset < 0) + return (PNG_FILE_ERROR); + + if (!out) { + result = lseek(png->fd, offset, SEEK_CUR); + } else { + result = read(png->fd, out, size * numel); + } + + return (result); +} + +static int +file_read_ul(png_t *png, unsigned *out) +{ + uint8_t buf[4]; + + if (file_read(png, buf, 1, 4) != 4) + return (PNG_FILE_ERROR); + + *out = (buf[0]<<24) | (buf[1]<<16) | (buf[2]<<8) | buf[3]; + + return (PNG_NO_ERROR); +} + +static unsigned +get_ul(uint8_t *buf) +{ + unsigned result; + uint8_t foo[4]; + + memcpy(foo, buf, 4); + + result = (foo[0]<<24) | (foo[1]<<16) | (foo[2]<<8) | foo[3]; + + return (result); +} + +static int +png_get_bpp(png_t *png) +{ + int bpp; + + switch (png->color_type) { + case PNG_GREYSCALE: + bpp = 1; break; + case PNG_TRUECOLOR: + bpp = 3; break; + case PNG_INDEXED: + bpp = 1; break; + case PNG_GREYSCALE_ALPHA: + bpp = 2; break; + case PNG_TRUECOLOR_ALPHA: + bpp = 4; break; + default: + return (PNG_FILE_ERROR); + } + + bpp *= png->depth / 8; + + return (bpp); +} + +static int +png_read_ihdr(png_t *png) +{ + unsigned length = 0; + unsigned orig_crc; + unsigned calc_crc; + uint8_t ihdr[13+4]; /* length should be 13, make room for type (IHDR) */ + + if (file_read_ul(png, &length) != PNG_NO_ERROR) + return (PNG_FILE_ERROR); + + if (length != 13) + return (PNG_CRC_ERROR); + + if (file_read(png, ihdr, 1, 13+4) != 13+4) + return (PNG_EOF_ERROR); + + if (file_read_ul(png, &orig_crc) != PNG_NO_ERROR) + return (PNG_FILE_ERROR); + + calc_crc = crc32(0L, Z_NULL, 0); + calc_crc = crc32(calc_crc, ihdr, 13+4); + + if (orig_crc != calc_crc) { + return (PNG_CRC_ERROR); + } + + png->width = get_ul(ihdr+4); + png->height = get_ul(ihdr+8); + png->depth = ihdr[12]; + png->color_type = ihdr[13]; + png->compression_method = ihdr[14]; + png->filter_method = ihdr[15]; + png->interlace_method = ihdr[16]; + + if (png->color_type == PNG_INDEXED) + return (PNG_NOT_SUPPORTED); + + if (png->depth != 8 && png->depth != 16) + return (PNG_NOT_SUPPORTED); + + if (png->interlace_method) + return (PNG_NOT_SUPPORTED); + + return (PNG_NO_ERROR); +} + +void +png_print_info(png_t *png) +{ + printf("PNG INFO:\n"); + printf("\twidth:\t\t%d\n", png->width); + printf("\theight:\t\t%d\n", png->height); + printf("\tdepth:\t\t%d\n", png->depth); + printf("\tcolor:\t\t"); + + switch (png->color_type) { + case PNG_GREYSCALE: + printf("greyscale\n"); break; + case PNG_TRUECOLOR: + printf("truecolor\n"); break; + case PNG_INDEXED: + printf("palette\n"); break; + case PNG_GREYSCALE_ALPHA: + printf("greyscale with alpha\n"); break; + case PNG_TRUECOLOR_ALPHA: + printf("truecolor with alpha\n"); break; + default: + printf("unknown, this is not good\n"); break; + } + + printf("\tcompression:\t%s\n", + png->compression_method? + "unknown, this is not good":"inflate/deflate"); + printf("\tfilter:\t\t%s\n", + png->filter_method? "unknown, this is not good":"adaptive"); + printf("\tinterlace:\t%s\n", + png->interlace_method? "interlace":"no interlace"); +} + +int +png_open(png_t *png, const char *filename) +{ + char header[8]; + int result; + + png->image = NULL; + png->fd = open(filename, O_RDONLY); + if (png->fd == -1) + return (PNG_FILE_ERROR); + + if (file_read(png, header, 1, 8) != 8) { + result = PNG_EOF_ERROR; + goto done; + } + + if (memcmp(header, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8) != 0) { + result = PNG_HEADER_ERROR; + goto done; + } + + result = png_read_ihdr(png); + if (result == PNG_NO_ERROR) { + result = png_get_bpp(png); + if (result > 0) { + png->bpp = (uint8_t)result; + result = PNG_NO_ERROR; + } + } + +done: + if (result == PNG_NO_ERROR) { + uint64_t size = png->width * png->height * png->bpp; + + if (size < UINT_MAX) + png->image = malloc(size); + if (png->image == NULL) + result = PNG_MEMORY_ERROR; + } + + if (result == PNG_NO_ERROR) + result = png_get_data(png, png->image); + + if (result != PNG_NO_ERROR) { + free(png->image); + (void) close(png->fd); + png->fd = -1; + return (result); + } + + return (result); +} + +int +png_close(png_t *png) +{ + (void) close(png->fd); + png->fd = -1; + free(png->image); + png->image = NULL; + + return (PNG_NO_ERROR); +} + +static int +png_init_inflate(png_t *png) +{ + z_stream *stream; + png->zs = calloc(1, sizeof (z_stream)); + + stream = png->zs; + + if (!stream) + return (PNG_MEMORY_ERROR); + + if (inflateInit(stream) != Z_OK) { + free(png->zs); + png->zs = NULL; + return (PNG_ZLIB_ERROR); + } + + stream->next_out = png->png_data; + stream->avail_out = png->png_datalen; + + return (PNG_NO_ERROR); +} + +static int +png_end_inflate(png_t *png) +{ + z_stream *stream = png->zs; + int rc = PNG_NO_ERROR; + + if (!stream) + return (PNG_MEMORY_ERROR); + + if (inflateEnd(stream) != Z_OK) { + printf("ZLIB says: %s\n", stream->msg); + rc = PNG_ZLIB_ERROR; + } + + free(png->zs); + png->zs = NULL; + + return (rc); +} + +static int +png_inflate(png_t *png, uint8_t *data, int len) +{ + int result; + z_stream *stream = png->zs; + + if (!stream) + return (PNG_MEMORY_ERROR); + + stream->next_in = data; + stream->avail_in = len; + + result = inflate(stream, Z_SYNC_FLUSH); + + if (result != Z_STREAM_END && result != Z_OK) { + printf("%s\n", stream->msg); + return (PNG_ZLIB_ERROR); + } + + if (stream->avail_in != 0) + return (PNG_ZLIB_ERROR); + + return (PNG_NO_ERROR); +} + +static int +png_read_idat(png_t *png, unsigned length) +{ + unsigned orig_crc; + unsigned calc_crc; + ssize_t len = length; + + if (!png->readbuf || png->readbuflen < length) { + png->readbuf = realloc(png->readbuf, length); + png->readbuflen = length; + } + + if (!png->readbuf) + return (PNG_MEMORY_ERROR); + + if (file_read(png, png->readbuf, 1, length) != len) + return (PNG_FILE_ERROR); + + calc_crc = crc32(0L, Z_NULL, 0); + calc_crc = crc32(calc_crc, (uint8_t *)"IDAT", 4); + calc_crc = crc32(calc_crc, (uint8_t *)png->readbuf, length); + + if (file_read_ul(png, &orig_crc) != PNG_NO_ERROR) + return (PNG_FILE_ERROR); + + if (orig_crc != calc_crc) + return (PNG_CRC_ERROR); + + return (png_inflate(png, png->readbuf, length)); +} + +static int +png_process_chunk(png_t *png) +{ + int result = PNG_NO_ERROR; + unsigned type; + unsigned length; + + if (file_read_ul(png, &length) != PNG_NO_ERROR) + return (PNG_FILE_ERROR); + + if (file_read_ul(png, &type) != PNG_NO_ERROR) + return (PNG_FILE_ERROR); + + /* + * if we found an idat, all other idats should be followed with no + * other chunks in between + */ + if (type == png_IDAT) { + if (!png->png_data) { /* first IDAT */ + png->png_datalen = png->width * png->height * + png->bpp + png->height; + png->png_data = malloc(png->png_datalen); + } + + if (!png->png_data) + return (PNG_MEMORY_ERROR); + + if (!png->zs) { + result = png_init_inflate(png); + if (result != PNG_NO_ERROR) + return (result); + } + + return (png_read_idat(png, length)); + } else if (type == png_IEND) + return (PNG_DONE); + else + (void) file_read(png, 0, 1, length + 4); /* unknown chunk */ + + return (result); +} + +static void +png_filter_sub(unsigned stride, uint8_t *in, uint8_t *out, unsigned len) +{ + unsigned i; + uint8_t a = 0; + + for (i = 0; i < len; i++) { + if (i >= stride) + a = out[i - stride]; + + out[i] = in[i] + a; + } +} + +static void +png_filter_up(unsigned stride __unused, uint8_t *in, uint8_t *out, + uint8_t *prev_line, unsigned len) +{ + unsigned i; + + if (prev_line) { + for (i = 0; i < len; i++) + out[i] = in[i] + prev_line[i]; + } else + memcpy(out, in, len); +} + +static void +png_filter_average(unsigned stride, uint8_t *in, uint8_t *out, + uint8_t *prev_line, unsigned len) +{ + unsigned int i; + uint8_t a = 0; + uint8_t b = 0; + unsigned int sum = 0; + + for (i = 0; i < len; i++) { + if (prev_line) + b = prev_line[i]; + + if (i >= stride) + a = out[i - stride]; + + sum = a; + sum += b; + + out[i] = in[i] + sum/2; + } +} + +static uint8_t +png_paeth(uint8_t a, uint8_t b, uint8_t c) +{ + int p = (int)a + b - c; + int pa = abs(p - a); + int pb = abs(p - b); + int pc = abs(p - c); + + int pr; + + if (pa <= pb && pa <= pc) + pr = a; + else if (pb <= pc) + pr = b; + else + pr = c; + + return (pr); +} + +static void +png_filter_paeth(unsigned stride, uint8_t *in, uint8_t *out, uint8_t *prev_line, + unsigned len) +{ + unsigned i; + uint8_t a; + uint8_t b; + uint8_t c; + + for (i = 0; i < len; i++) { + if (prev_line && i >= stride) { + a = out[i - stride]; + b = prev_line[i]; + c = prev_line[i - stride]; + } else { + if (prev_line) + b = prev_line[i]; + else + b = 0; + + if (i >= stride) + a = out[i - stride]; + else + a = 0; + + c = 0; + } + + out[i] = in[i] + png_paeth(a, b, c); + } +} + +static int +png_unfilter(png_t *png, uint8_t *data) +{ + unsigned i; + unsigned pos = 0; + unsigned outpos = 0; + uint8_t *filtered = png->png_data; + unsigned stride = png->bpp; + + while (pos < png->png_datalen) { + uint8_t filter = filtered[pos]; + + pos++; + + if (png->depth == 16) { + for (i = 0; i < png->width * stride; i += 2) { + *(short *)(filtered+pos+i) = + (filtered[pos+i] << 8) | filtered[pos+i+1]; + } + } + + switch (filter) { + case 0: /* none */ + memcpy(data+outpos, filtered+pos, png->width * stride); + break; + case 1: /* sub */ + png_filter_sub(stride, filtered+pos, data+outpos, + png->width * stride); + break; + case 2: /* up */ + if (outpos) { + png_filter_up(stride, filtered+pos, data+outpos, + data + outpos - (png->width*stride), + png->width*stride); + } else { + png_filter_up(stride, filtered+pos, data+outpos, + 0, png->width*stride); + } + break; + case 3: /* average */ + if (outpos) { + png_filter_average(stride, filtered+pos, + data+outpos, + data + outpos - (png->width*stride), + png->width*stride); + } else { + png_filter_average(stride, filtered+pos, + data+outpos, 0, png->width*stride); + } + break; + case 4: /* paeth */ + if (outpos) { + png_filter_paeth(stride, filtered+pos, + data+outpos, + data + outpos - (png->width*stride), + png->width*stride); + } else { + png_filter_paeth(stride, filtered+pos, + data+outpos, 0, png->width*stride); + } + break; + default: + return (PNG_UNKNOWN_FILTER); + } + + outpos += png->width * stride; + pos += png->width * stride; + } + + return (PNG_NO_ERROR); +} + +int +png_get_data(png_t *png, uint8_t *data) +{ + int result = PNG_NO_ERROR; + + png->zs = NULL; + png->png_datalen = 0; + png->png_data = NULL; + png->readbuf = NULL; + png->readbuflen = 0; + + while (result == PNG_NO_ERROR) + result = png_process_chunk(png); + + if (png->readbuf) { + free(png->readbuf); + png->readbuflen = 0; + } + if (png->zs) + (void) png_end_inflate(png); + + if (result != PNG_DONE) { + free(png->png_data); + return (result); + } + + result = png_unfilter(png, data); + + free(png->png_data); + + return (result); +} + +char * +png_error_string(int error) +{ + switch (error) { + case PNG_NO_ERROR: + return ("No error"); + case PNG_FILE_ERROR: + return ("Unknown file error."); + case PNG_HEADER_ERROR: + return ("No PNG header found. Are you sure this is a PNG?"); + case PNG_IO_ERROR: + return ("Failure while reading file."); + case PNG_EOF_ERROR: + return ("Reached end of file."); + case PNG_CRC_ERROR: + return ("CRC or chunk length error."); + case PNG_MEMORY_ERROR: + return ("Could not allocate memory."); + case PNG_ZLIB_ERROR: + return ("zlib reported an error."); + case PNG_UNKNOWN_FILTER: + return ("Unknown filter method used in scanline."); + case PNG_DONE: + return ("PNG done"); + case PNG_NOT_SUPPORTED: + return ("The PNG is unsupported by pnglite, too bad for you!"); + case PNG_WRONG_ARGUMENTS: + return ("Wrong combination of arguments passed to png_open. " + "You must use either a read_function or supply a file " + "pointer to use."); + default: + return ("Unknown error."); + }; +} Index: stand/Makefile =================================================================== --- stand/Makefile +++ stand/Makefile @@ -18,6 +18,7 @@ S.${MK_FDT}+= fdt S.${MK_LOADER_OFW}+= libofw S.yes+= defaults +S.yes+= images S.yes+= man .include Index: stand/common/bootstrap.h =================================================================== --- stand/common/bootstrap.h +++ 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. @@ -257,6 +260,8 @@ int file_addmodule(struct preloaded_file *, char *, int, struct kernel_module **); void file_removemetadata(struct preloaded_file *fp); + +vm_offset_t build_font_module(vm_offset_t); /* MI module loaders */ #ifdef __elfN Index: stand/common/console.c =================================================================== --- stand/common/console.c +++ stand/common/console.c @@ -147,6 +147,26 @@ } } +void +putchar_device(int c, void *name) +{ + int cons; + + if (name == NULL) + return; + + if (c == '\n') + putchar_device('\r', name); + cons = cons_find(name); + if (cons < 0) + return; + + /* Expand newlines if not in raw mode */ + if ((consoles[cons]->c_flags & C_PRESENTOUT) == C_PRESENTOUT) { + consoles[cons]->c_out(c); + } +} + /* * Find the console with the specified name. */ Index: stand/common/gfx_fb.h =================================================================== --- /dev/null +++ stand/common/gfx_fb.h @@ -0,0 +1,249 @@ +/*- + * 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 +extern uint32_t cmap[NCOLORS]; + +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; + +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 gfx_bitblt_bitmap(teken_gfx_t *, const uint8_t *, teken_char_t, + const teken_attr_t *, uint32_t); + +void gfx_fb_cons_display(uint32_t, uint32_t, uint32_t, uint32_t, void *); + +void bios_text_font(bool); +bool gfx_get_edid_resolution(struct vesa_edid_info *, edid_res_list_t *); +void gfx_framework_init(void); +void gfx_bm_cons_display(uint32_t, uint32_t, uint32_t, uint32_t, void *); +void gfx_bm_cursor_draw(teken_gfx_t *, const teken_pos_t *, uint32_t, uint32_t); +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 bitmap_cpy(uint8_t *, uint8_t *, uint32_t, int); + +void reset_font_flags(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _GFX_FB_H */ Index: stand/common/gfx_fb.c =================================================================== --- /dev/null +++ stand/common/gfx_fb.c @@ -0,0 +1,1845 @@ +/*- + * 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 + +/* VGA text mode does use bold font. */ +#if !defined(VGA_8X16_FONT) +#define VGA_8X16_FONT "/boot/fonts/vgarom-8x16.fnt" +#endif +#if !defined(DEFAULT_8X16_FONT) +#define DEFAULT_8X16_FONT "/boot/fonts/vgarom-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 }; + +#define NCOLORS 16 +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[NCOLORS]; + +/* + * 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 bool insert_font(char *, FONT_FLAGS); + +static int font_set(struct env_var *, int, const void *); + +/* + * Initialize gfx framework. + */ +void +gfx_framework_init(void) +{ + /* + * Setup font list to have builtin font. + */ + (void) insert_font(NULL, FONT_BUILTIN); +} + +/* + * 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); +} + +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]; + break; + case COLOR_FORMAT_RGB: +#define CF(_f, _i) ((_f ## max * color_def[(_i)]._f / 100) << _f ## offset) + for (i = 0; i < NCOLORS; i++) { + palette[i] = CF(r, i) | CF(g, i) | CF(b, i); + } +#undef CF + 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; +} + +void +gfx_bitblt_bitmap(teken_gfx_t *state, const uint8_t *glyph, teken_char_t c, + const teken_attr_t *a, uint32_t alpha) +{ + uint32_t width, height; + uint32_t fgc, bgc, bpl, cc, o; + int bpp, bit, byte; + + bpp = state->tg_fb.fb_bpp >> 3; + width = state->tg_font.vf_width; + height = state->tg_font.vf_height; + bpl = (width + 7) / 8; /* Bytes per source line. */ + + fgc = teken_256to16(a->ta_fgcolor); + bgc = teken_256to16(a->ta_bgcolor); + if (a->ta_format & TF_BOLD) + fgc |= TC_LIGHT; + if (a->ta_format & TF_BLINK) + bgc |= TC_LIGHT; + + fgc = cmap[fgc]; + bgc = cmap[bgc]; + + if (a->ta_format & TF_REVERSE) { + uint32_t tmp; + + tmp = fgc; + fgc = bgc; + bgc = tmp; + } + + bit = ffs(gfx_state.tg_fb.fb_mask_reserved) - 1; + alpha = ((gfx_state.tg_fb.fb_mask_reserved >> bit) & alpha) << bit; + 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; + + switch (bpp) { + case 1: + gfx_mem_wr1(state->tg_glyph, + state->tg_glyph_size, o, cc); + break; + case 2: + gfx_mem_wr2(state->tg_glyph, + state->tg_glyph_size, o, cc); + break; + case 3: + gfx_mem_wr1(state->tg_glyph, + state->tg_glyph_size, o, (cc >> 16) & 0xff); + gfx_mem_wr1(state->tg_glyph, + state->tg_glyph_size, o + 1, + (cc >> 8) & 0xff); + gfx_mem_wr1(state->tg_glyph, + state->tg_glyph_size, o + 2, cc & 0xff); + break; + case 4: + gfx_mem_wr4(state->tg_glyph, + state->tg_glyph_size, o, cc); + break; + } + } + } +} + +void +gfx_bm_cursor_draw(teken_gfx_t *state, const teken_pos_t *p, + uint32_t fg, uint32_t bg) +{ + uint32_t offset, size, *fb32; + uint16_t *fb16; + uint8_t *fb8, *fb; + uint32_t bpp, pitch; + + bpp = roundup2(state->tg_fb.fb_bpp, 8) >> 3; + fb = ptov((uint32_t)gfx_state.tg_fb.fb_addr & 0xffffffff); + pitch = state->tg_fb.fb_stride * bpp; + size = state->tg_font.vf_width * bpp; + + if (fb == NULL) + return; + + /* + * Build cursor image. We are building mirror image of data on + * frame buffer by (D xor FG) xor BG. + */ + offset = state->tg_origin.tp_row + p->tp_row * state->tg_font.vf_height; + offset *= pitch; + + offset += (state->tg_origin.tp_col + + p->tp_col * state->tg_font.vf_width) * bpp; + + switch (state->tg_fb.fb_bpp) { + case 8: /* 8 bit */ + for (int i = 0; i < state->tg_font.vf_height; i++) { + fb8 = fb + offset + i * pitch; + for (uint32_t j = 0; j < size; j += 1) { + fb8[j] = (fb8[j] ^ (fg & 0xff)) ^ (bg & 0xff); + } + } + break; + case 15: + case 16: /* 16 bit */ + for (int i = 0; i < state->tg_font.vf_height; i++) { + fb16 = (uint16_t *)(fb + offset + i * pitch); + for (int j = 0; j < state->tg_font.vf_width; j++) { + fb16[j] = (fb16[j] ^ (fg & 0xffff)) ^ + (bg & 0xffff); + } + } + break; + case 24: /* 24 bit */ + for (int i = 0; i < state->tg_font.vf_height; i++) { + fb8 = fb + offset + i * pitch; + for (uint32_t j = 0; j < size; j += 3) { + fb8[j] = (fb8[j] ^ ((fg >> 16) & 0xff)) ^ + ((bg >> 16) & 0xff); + fb8[j+1] = (fb8[j+1] ^ ((fg >> 8) & 0xff)) ^ + ((bg >> 8) & 0xff); + fb8[j+2] = (fb8[j+2] ^ (fg & 0xff)) ^ + (bg & 0xff); + } + } + break; + case 32: + for (int i = 0; i < state->tg_font.vf_height; i++) { + fb32 = (uint32_t *)(fb + offset + i * pitch); + for (int j = 0; j < state->tg_font.vf_width; j++) + fb32[j] = (fb32[j] ^ fg) ^ bg; + } + break; + } +} + +/* + * 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); +} + +/* Copy memory to framebuffer or to memory. */ +void +bitmap_cpy(uint8_t *dst, uint8_t *src, uint32_t len, int bpp) +{ + uint32_t i; + uint8_t a; + + switch (bpp) { + case 4: + /* + * we only implement alpha blending for depth 32, + * use memcpy for other cases. + */ + for (i = 0; i < len; i += bpp) { + a = src[i+3]; + dst[i] = alpha_blend(src[i], dst[i], a); + dst[i+1] = alpha_blend(src[i+1], dst[i+1], a); + dst[i+2] = alpha_blend(src[i+2], dst[i+2], a); + dst[i+3] = 0; + } + break; + default: + (void) memcpy(dst, src, len); + break; + } +} + +void +gfx_bm_cons_display(uint32_t x, uint32_t y, uint32_t width, uint32_t height, + void *data) +{ + uint32_t size; /* write size per scanline */ + uint8_t *fbp; /* fb + calculated offset */ + uint32_t i, bpp, pitch; + + fbp = ptov((uint32_t)gfx_state.tg_fb.fb_addr & 0xffffffff); + if (fbp == NULL) + return; + + bpp = roundup2(gfx_state.tg_fb.fb_bpp, 8) >> 3; + pitch = gfx_state.tg_fb.fb_stride * bpp; + + size = width * bpp; + fbp += x * bpp + y * pitch; + + /* write all scanlines in rectangle */ + for (i = 0; i < height; i++) { + uint8_t *dest = fbp + i * pitch; + uint8_t *src = (uint8_t *)data + i * size; + bitmap_cpy(dest, src, size, bpp); + } +} + +/* + * 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_defattr(&gfx_state.tg_teken); + if (ap->ta_format & TF_REVERSE) { + c = teken_256to16(ap->ta_bgcolor); + if (ap->ta_format & TF_BLINK) + c |= TC_LIGHT; + } else { + c = teken_256to16(ap->ta_fgcolor); + if (ap->ta_format & TF_BOLD) + c |= TC_LIGHT; + } + + if (gfx_state.tg_fb.fb_bpp == 32) + c = gfx_state.tg_fb.fb_mask_reserved | cmap[c]; /* Set alpha */ + else + c = cmap[c]; + + if (x >= gfx_state.tg_fb.fb_width || + y >= gfx_state.tg_fb.fb_height) + return; + + gfx_fb_cons_display(x, y, 1, 1, &c); +} + +/* + * 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) +{ + uint8_t *data; + uint32_t i, j, x, y, fheight, fwidth, color; + int fbpp; + uint8_t r, g, b, a, *p; + 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); + + fbpp = roundup2(gfx_state.tg_fb.fb_bpp, 8) >> 3; + + data = malloc(fwidth * fheight * fbpp); + 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); + + 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) * fbpp; + + 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); + color = + (r << ffs(gfx_state.tg_fb.fb_mask_red) - 1) & + gfx_state.tg_fb.fb_mask_red | + (g << ffs(gfx_state.tg_fb.fb_mask_green) - 1) & + gfx_state.tg_fb.fb_mask_green | + (b << ffs(gfx_state.tg_fb.fb_mask_blue) - 1) & + gfx_state.tg_fb.fb_mask_blue; + + switch (gfx_state.tg_fb.fb_bpp) { + case 8: { + uint32_t best, dist, k; + int diff; + + color = 0; + best = 256 * 256 * 256; +#define CF(_c, _m) (((_c) & (_m)) >> ffs(_m) - 1) + for (k = 0; k < 16; k++) { + diff = r - CF(cmap[k], + gfx_state.tg_fb.fb_mask_red); + dist = diff * diff; + diff = g - CF(cmap[k], + gfx_state.tg_fb.fb_mask_green); + dist += diff * diff; + diff = b - CF(cmap[k], + gfx_state.tg_fb.fb_mask_green); + dist += diff * diff; + + if (dist < best) { + color = k; + best = dist; + if (dist == 0) + break; + } + } +#undef CF + data[j] = cons_to_vga_colors[color]; + break; + } + case 15: + case 16: + *(uint16_t *)(data+j) = color; + break; + case 24: + p = (uint8_t *)&color; + data[j] = p[0]; + data[j+1] = p[1]; + data[j+2] = p[2]; + break; + case 32: + color |= a << 24; + *(uint32_t *)(data+j) = color; + break; + } + 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); + } + + unsetenv("screen.font"); + env_setenv("screen.font", EV_VOLATILE, NULL, font_set, env_nounset); + + /* + * 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)); +} Index: stand/common/module.c =================================================================== --- stand/common/module.c +++ 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 Index: stand/defaults/loader.conf.5 =================================================================== --- stand/defaults/loader.conf.5 +++ stand/defaults/loader.conf.5 @@ -233,7 +233,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 Index: stand/efi/include/efilib.h =================================================================== --- stand/efi/include/efilib.h +++ 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[]); Index: stand/efi/libefi/Makefile =================================================================== --- stand/efi/libefi/Makefile +++ 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} Index: stand/efi/libefi/efi_console.c =================================================================== --- stand/efi/libefi/efi_console.c +++ 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,12 +64,20 @@ 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_cursor_t efi_gfx_cursor; static tf_putchar_t efi_text_putchar; +static tf_putchar_t efi_gfx_putchar; static tf_fill_t efi_text_fill; +static tf_fill_t efi_gfx_fill; static tf_copy_t efi_text_copy; +static tf_copy_t efi_gfx_copy; static tf_param_t efi_text_param; +static tf_param_t efi_gfx_param; static tf_respond_t efi_cons_respond; static teken_funcs_t tf = { @@ -77,8 +90,15 @@ .tf_respond = efi_cons_respond, }; -teken_t teken; -teken_pos_t tp; +static teken_funcs_t tfx = { + .tf_bell = efi_cons_bell, + .tf_cursor = efi_gfx_cursor, + .tf_putchar = efi_gfx_putchar, + .tf_fill = efi_gfx_fill, + .tf_copy = efi_gfx_copy, + .tf_param = efi_gfx_param, + .tf_respond = efi_cons_respond, +}; struct text_pixel { teken_char_t c; @@ -116,6 +136,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", @@ -129,6 +150,28 @@ }; /* + * 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; + buffer[idx].a.ta_format |= TF_IMAGE; + } + } +} + +/* * Not implemented. */ static void @@ -137,33 +180,108 @@ } 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; + col = p->tp_col; + if (p->tp_col >= state->tg_tp.tp_col) + col = state->tg_tp.tp_col - 1; - if (p->tp_row == row) - row = p->tp_row - 1; - else - row = p->tp_row; - conout->SetCursorPosition(conout, col, row); } static void -efi_text_printchar(const teken_pos_t *p, bool autoscroll) +efi_gfx_cursor_draw(teken_gfx_t *state, const teken_pos_t *p) { + union pixel { + EFI_GRAPHICS_OUTPUT_BLT_PIXEL p; + uint32_t p32; + } *buf, *row, fg, bg; + EFI_GRAPHICS_OUTPUT *gop = state->tg_private; + struct text_pixel *px; + UINTN x, y, width, height; + + width = state->tg_font.vf_width; + height = state->tg_font.vf_height; + + x = p->tp_col * width + state->tg_origin.tp_col; + y = p->tp_row * height + state->tg_origin.tp_row; + + px = buffer + p->tp_col + p->tp_row * state->tg_tp.tp_col; + fg.p32 = teken_256to16(px->a.ta_fgcolor); + bg.p32 = teken_256to16(px->a.ta_bgcolor); + if (px->a.ta_format & TF_BOLD) + fg.p32 |= TC_LIGHT; + if (px->a.ta_format & TF_BLINK) + bg.p32 |= TC_LIGHT; + + fg.p32 = cmap[fg.p32]; + bg.p32 = cmap[bg.p32]; + + if (px->a.ta_format & TF_REVERSE) { + uint32_t tmp; + + tmp = fg.p32; + fg.p32 = bg.p32; + bg.p32 = tmp; + } + + state->tg_cursor = *p; + if (state->tg_fb_type != FB_GOP) { + gfx_bm_cursor_draw(state, p, fg.p32, bg.p32); + return; + } + + buf = (union pixel *)state->tg_glyph; + if (gop->Blt(gop, (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)buf, + EfiBltVideoToBltBuffer, x, y, 0, 0, + width, height, 0) != EFI_SUCCESS) { + return; + } + + /* + * Build cursor image. We are building mirror image of data on + * frame buffer by (D xor FG) xor BG. + */ + for (unsigned i = 0; i < height; i++) { + row = buf + i * width; + for (unsigned j = 0; j < width; j++) + row[j].p32 = (row[j].p32 ^ fg.p32) ^ bg.p32; + } + + (void) gop->Blt(gop, (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)buf, + EfiBltBufferToVideo, 0, 0, x, y, width, height, 0); +} + +static void +efi_gfx_cursor(void *arg, const teken_pos_t *p) +{ + teken_gfx_t *state = arg; + EFI_TPL tpl; + + /* Switch cursor off in old location and back on in new. */ + if (state->tg_cursor_visible) { + tpl = BS->RaiseTPL(TPL_NOTIFY); + efi_gfx_cursor_draw(state, &state->tg_cursor); + efi_gfx_cursor_draw(state, p); + BS->RestoreTPL(tpl); + } +} + +static void +efi_text_printchar(teken_gfx_t *state, const teken_pos_t *p, bool autoscroll) +{ UINTN a, attr; struct text_pixel *px; teken_color_t fg, bg, tmp; - px = buffer + p->tp_col + p->tp_row * tp.tp_col; + px = 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 +302,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,42 +314,147 @@ } 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; + 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; + buffer[idx].c = c; buffer[idx].a = *a; - efi_text_printchar(p, false); + + efi_text_printchar(s, p, false); } +/* + * Draw prepared glyph on terminal point p. + */ static void -efi_text_fill(void *s, const teken_rect_t *r, teken_char_t c, +efi_gfx_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. + */ +static void +efi_gfx_putchar(void *arg, const teken_pos_t *p, teken_char_t c, const teken_attr_t *a) { - teken_pos_t p; - UINTN row, col; + teken_gfx_t *state = arg; + const uint8_t *glyph; + EFI_TPL tpl; + int idx; - (void) conout->QueryMode(conout, conout->Mode->Mode, &col, &row); + 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; - conout->EnableCursor(conout, FALSE); + buffer[idx].c = c; + buffer[idx].a = *a; + + tpl = BS->RaiseTPL(TPL_NOTIFY); + + /* remove the cursor */ + if (state->tg_cursor_visible) + efi_gfx_cursor_draw(arg, &state->tg_cursor); + + glyph = font_lookup(&state->tg_font, c, a); + gfx_bitblt_bitmap(state, glyph, c, a, 0xff); + efi_gfx_printchar(state, p); + + /* display the cursor */ + if (state->tg_cursor_visible) { + const teken_pos_t *c; + + c = teken_get_cursor(&state->tg_teken); + efi_gfx_cursor_draw(state, c); + } + BS->RestoreTPL(tpl); +} + +static void +efi_text_fill(void *arg, const teken_rect_t *r, teken_char_t c, + const teken_attr_t *a) +{ + teken_gfx_t *state = arg; + teken_pos_t p; + + if (state->tg_cursor_visible) + conout->EnableCursor(conout, FALSE); for (p.tp_row = r->tr_begin.tp_row; p.tp_row < r->tr_end.tp_row; p.tp_row++) for (p.tp_col = r->tr_begin.tp_col; p.tp_col < r->tr_end.tp_col; p.tp_col++) - efi_text_putchar(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 void +efi_gfx_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; + EFI_TPL tpl; + teken_pos_t p; + struct text_pixel *row; + + tpl = BS->RaiseTPL(TPL_NOTIFY); + + /* remove the cursor */ + if (state->tg_cursor_visible) + efi_gfx_cursor_draw(state, &state->tg_cursor); + + glyph = font_lookup(&state->tg_font, c, a); + gfx_bitblt_bitmap(state, glyph, c, a, 0xff); + + for (p.tp_row = r->tr_begin.tp_row; p.tp_row < r->tr_end.tp_row; + p.tp_row++) { + row = &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; + efi_gfx_printchar(state, &p); + } + } + + /* display the cursor */ + if (state->tg_cursor_visible) { + const teken_pos_t *c; + + c = teken_get_cursor(&state->tg_teken); + efi_gfx_cursor_draw(state, c); + } + BS->RestoreTPL(tpl); +} + static bool efi_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) @@ -243,11 +466,44 @@ } static void -efi_text_copy(void *ptr __unused, const teken_rect_t *r, const teken_pos_t *p) +efi_text_copy_line(teken_gfx_t *state, int ncol, teken_pos_t *s, + teken_pos_t *d, bool scroll) { - int srow, drow; - int nrow, ncol, x, y; /* Has to be signed - >= 0 comparison */ + unsigned soffset, doffset; + teken_pos_t sp, dp; + int x; + + soffset = s->tp_col + s->tp_row * state->tg_tp.tp_col; + doffset = d->tp_col + d->tp_row * state->tg_tp.tp_col; + + sp = *s; + dp = *d; + for (x = 0; x < ncol; x++) { + sp.tp_col = s->tp_col + x; + dp.tp_col = d->tp_col + x; + if (!efi_same_pixel(&buffer[soffset + x], + &buffer[doffset + x])) { + buffer[doffset + x] = + buffer[soffset + x]; + if (!scroll) + efi_text_printchar(state, &dp, false); + } else if (scroll) { + /* Draw last char and trigger scroll. */ + if (dp.tp_col + 1 == state->tg_tp.tp_col && + dp.tp_row + 1 == state->tg_tp.tp_row) { + efi_text_printchar(state, &dp, true); + } + } + } +} + +static void +efi_text_copy(void *arg, const teken_rect_t *r, const teken_pos_t *p) +{ + teken_gfx_t *state = arg; + unsigned doffset, soffset; teken_pos_t d, s; + int nrow, ncol, x, y; /* Has to be signed - >= 0 comparison */ bool scroll = false; /* @@ -262,90 +518,189 @@ * 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++) { + s.tp_row = r->tr_begin.tp_row + y; d.tp_row = p->tp_row + y; + + efi_text_copy_line(state, ncol, &s, &d, scroll); + } + } else { + for (y = nrow - 1; y >= 0; y--) { s.tp_row = r->tr_begin.tp_row + y; - 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; + d.tp_row = p->tp_row + y; - if (!efi_same_pixel( - &buffer[d.tp_col + drow], - &buffer[s.tp_col + srow])) { - buffer[d.tp_col + drow] = - buffer[s.tp_col + srow]; - 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); - } - } - } + efi_text_copy_line(state, ncol, &s, &d, false); } + } + + /* display the cursor */ + if (state->tg_cursor_visible) + conout->EnableCursor(conout, TRUE); +} + +static void +efi_gfx_copy_area(teken_gfx_t *state, const teken_rect_t *s, + const teken_pos_t *d) +{ + UINTN sx, sy, dx, dy, width, height; + EFI_GRAPHICS_OUTPUT *gop = state->tg_private; + + 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); + + if (state->tg_fb_type == FB_GOP) { + (void) gop->Blt(gop, NULL, EfiBltVideoToVideo, sx, sy, dx, dy, + width, height, 0); } 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; + unsigned bpp, pitch; + uint8_t *fb, *src, *dst; - 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); - } - } + fb = (uint8_t *)gfx_state.tg_fb.fb_addr; + bpp = state->tg_fb.fb_bpp >> 3; + pitch = state->tg_fb.fb_stride * bpp; + + src = fb + sx * bpp + sy * pitch; + dst = fb + dx * bpp + dy * pitch; + width *= bpp; + + for (unsigned i = 0; i < height; i++) { + unsigned increment = i * pitch; + + (void) memmove(dst + increment, src + increment, width); + } + } +} + +static void +efi_gfx_copy_line(teken_gfx_t *state, int ncol, teken_pos_t *s, teken_pos_t *d) +{ + struct text_pixel *src, *dst; + 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 (efi_same_pixel(&buffer[soffset + x], + &buffer[doffset + x])) { + if (mark) { + efi_gfx_copy_area(state, &sr, &dp); + mark = 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); - } - } + buffer[doffset + x] = 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; } } } - conout->EnableCursor(conout, TRUE); + if (mark) { + efi_gfx_copy_area(state, &sr, &dp); + } } static void -efi_text_param(void *s __unused, int cmd, unsigned int value) +efi_gfx_copy(void *arg, const teken_rect_t *r, const teken_pos_t *p) { + teken_gfx_t *state = arg; + unsigned doffset, soffset; + teken_pos_t d, s; + int nrow, ncol, x, y; /* Has to be signed - >= 0 comparison */ + EFI_TPL tpl; + + /* + * 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; + + tpl = BS->RaiseTPL(TPL_NOTIFY); + /* remove the cursor */ + if (state->tg_cursor_visible) + efi_gfx_cursor_draw(state, &state->tg_cursor); + + /* + * Copy line by line. + */ + if (doffset <= soffset) { + s = r->tr_begin; + d = *p; + for (y = 0; y < nrow; y++) { + s.tp_row = r->tr_begin.tp_row + y; + d.tp_row = p->tp_row + y; + + efi_gfx_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; + + efi_gfx_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); + efi_gfx_cursor_draw(state, c); + } + BS->RestoreTPL(tpl); +} + +static void +efi_text_param(void *arg, int cmd, unsigned int value) +{ + teken_gfx_t *state = arg; + switch (cmd) { case TP_SETLOCALCURSOR: /* @@ -357,10 +712,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 */ @@ -368,6 +726,36 @@ } } +static void +efi_gfx_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); + efi_gfx_cursor_draw(state, c); + if (value != 0) + state->tg_cursor_visible = true; + else + state->tg_cursor_visible = false; + break; + default: + /* Not yet implemented */ + break; + } +} + /* * Not implemented. */ @@ -470,7 +858,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 +872,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 +1218,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 +1329,79 @@ */ 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), "%dx%d", + fb_width, fb_height); + env_setenv("kern.vt.fb.default_mode", + EV_VOLATILE | EV_NOHOOK, + env, env_noset, env_screen_nounset); + 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; + /* 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(buffer); buffer = malloc(rows * cols * sizeof(*buffer)); if (buffer != NULL) { - teken_set_winsize(&teken, &tp); - a = teken_get_defattr(&teken); + teken_set_winsize(&gfx_state.tg_teken, + &gfx_state.tg_tp); + a = teken_get_defattr(&gfx_state.tg_teken); attr = *a; /* @@ -880,7 +1415,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 +1426,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; - } - } } } -#ifdef TERM_EMU if (buffer == NULL) { + conout->EnableCursor(conout, TRUE); +#ifdef TERM_EMU conout->SetAttribute(conout, EFI_TEXT_ATTR(DEFAULT_FGCOLOR, DEFAULT_BGCOLOR)); end_term(); @@ -910,9 +1439,24 @@ curs_move(&curx, &cury, curx, cury); fg_c = DEFAULT_FGCOLOR; bg_c = DEFAULT_BGCOLOR; - } #endif + } else { + /* Improve visibility */ + if (attr.ta_bgcolor == TC_WHITE) + attr.ta_bgcolor |= TC_LIGHT; + teken_set_defattr(&gfx_state.tg_teken, &attr); + /* Draw frame around terminal area. */ + cons_draw_frame(&attr); + /* + * Erase display, this will also fill our screen + * buffer. + */ + teken_input(&gfx_state.tg_teken, "\e[2J", 4); + gfx_state.tg_functions->tf_param(&gfx_state, + TP_SHOWCURSOR, 1); + } + snprintf(env, sizeof (env), "%u", (unsigned)rows); setenv("LINES", env, 1); snprintf(env, sizeof (env), "%u", (unsigned)cols); @@ -926,10 +1470,15 @@ { 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); } @@ -1040,7 +1589,7 @@ return; } - teken_input(&teken, &ch, sizeof (ch)); + teken_input(&gfx_state.tg_teken, &ch, sizeof (ch)); } static int @@ -1232,4 +1781,32 @@ if (EFI_ERROR(status)) buf[0] = '?'; conout->OutputString(conout, buf); +} + +void +gfx_fb_cons_display(uint32_t x, uint32_t y, uint32_t width, uint32_t height, + void *data) +{ + EFI_GRAPHICS_OUTPUT *gop = gfx_state.tg_private; + EFI_GRAPHICS_OUTPUT_BLT_PIXEL *buf; + size_t size; + + if (gfx_state.tg_fb_type != FB_GOP) { + gfx_bm_cons_display(x, y, width, height, data); + return; + } + + size = width * height * sizeof(*buf); + buf = malloc(size); + if (buf == NULL) { + gfx_bm_cons_display(x, y, width, height, data); + return; + } + + (void) gop->Blt(gop, buf, EfiBltVideoToBltBuffer, x, y, 0, 0, + width, height, 0); + bitmap_cpy((uint8_t *)buf, data, size, sizeof(*buf)); + (void) gop->Blt(gop, buf, EfiBltBufferToVideo, 0, 0, x, y, + width, height, 0); + free(buf); } Index: stand/efi/loader/Makefile =================================================================== --- stand/efi/loader/Makefile +++ stand/efi/loader/Makefile @@ -22,7 +22,8 @@ framebuffer.c \ main.c \ self_reloc.c \ - vers.c + vers.c \ + 8x16.c CFLAGS+= -I${.CURDIR}/../loader .if ${MK_LOADER_ZFS} != "no" @@ -73,6 +74,11 @@ # Always add MI sources .include "${BOOTSRC}/loader.mk" + +CLEANFILES+= 8x16.c + +8x16.c: ${SRCTOP}/share/vt/fonts/vgarom-8x16.hex + vtfontcvt -f compressed-source -o ${.TARGET} ${.ALLSRC} FILES+= ${LOADER}.efi FILESMODE_${LOADER}.efi= ${BINMODE} Index: stand/efi/loader/bootinfo.c =================================================================== --- stand/efi/loader/bootinfo.c +++ 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 Index: stand/efi/loader/framebuffer.c =================================================================== --- stand/efi/loader/framebuffer.c +++ 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); } Index: stand/efi/loader/main.c =================================================================== --- stand/efi/loader/main.c +++ 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); } Index: stand/ficl.mk =================================================================== --- stand/ficl.mk +++ 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 Index: stand/ficl/Makefile =================================================================== --- stand/ficl/Makefile +++ 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} Index: stand/ficl/loader.c =================================================================== --- stand/ficl/loader.c +++ 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,7 +67,181 @@ * .# ( value -- ) */ +/* ( 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); +} + +void ficlSetenv(FICL_VM *pVM) { #ifndef TESTMAIN @@ -866,6 +1042,13 @@ dictAppendWord(dp, "ccall", ficlCcall, FW_DEFAULT); dictAppendWord(dp, "uuid-from-string", ficlUuidFromString, FW_DEFAULT); dictAppendWord(dp, "uuid-to-string", ficlUuidToString, FW_DEFAULT); + 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); #ifndef TESTMAIN dictAppendWord(dp, "isvirtualized?",ficlIsvirtualizedQ, FW_DEFAULT); #endif Index: stand/ficl/softwords/softcore.fr =================================================================== --- stand/ficl/softwords/softcore.fr +++ stand/ficl/softwords/softcore.fr @@ -67,6 +67,12 @@ : tuck ( y x -- x y x) swap over ; : within ( test low high -- flag ) over - >r - r> u< ; +: u.r ( n +n -- ) + swap 0 <# #s #> + rot over - dup 0< if + drop else spaces + then + type space ; \ ** LOCAL EXT word set \ #if FICL_WANT_LOCALS @@ -91,12 +97,32 @@ \ ** TOOLS word set... : ? ( addr -- ) @ . ; -: dump ( addr u -- ) - 0 ?do - dup c@ . 1+ - i 7 and 7 = if cr endif - loop drop -; + +Variable /dump + +: i' ( R:w R:w2 -- R:w R:w2 w ) + r> r> r> dup >r swap >r swap >r ; + +: .4 ( addr -- addr' ) + 4 0 DO -1 /dump +! /dump @ 0< + IF 3 spaces ELSE dup c@ 0 <# # # #> type space THEN + char+ LOOP ; + +: .chars ( addr -- ) + /dump @ over + swap + ?DO I c@ dup 127 bl within + IF drop [char] . THEN emit + LOOP ; + +: .line ( addr -- ) + dup .4 space .4 ." - " .4 space .4 drop 16 /dump +! space .chars ; + +: dump ( addr u -- ) \ tools dump + cr base @ >r hex \ save base on return stack + 0 ?DO I' I - 16 min /dump ! + dup 8 u.r ." : " dup .line cr 16 + + 16 +LOOP + drop r> base ! ; \ ** SEARCH+EXT words and ficl helpers \ BRAND-WORDLIST is a helper for ficl-named-wordlist. Usage idiom: Index: stand/forth/beastie.4th =================================================================== --- stand/forth/beastie.4th +++ stand/forth/beastie.4th @@ -82,6 +82,10 @@ then ; +: draw-beastie + ['] draw-beastie console-iterate +; + also support-functions : beastie-start ( -- ) \ starts the menu Index: stand/forth/brand-fbsd.4th =================================================================== --- stand/forth/brand-fbsd.4th +++ 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+ Index: stand/forth/brand.4th =================================================================== --- stand/forth/brand.4th +++ stand/forth/brand.4th @@ -72,3 +72,7 @@ else drop then then ; + +: draw-brand + ['] draw-brand console-iterate +; Index: stand/forth/color.4th =================================================================== --- stand/forth/color.4th +++ 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 ; Index: stand/forth/frames.4th =================================================================== --- stand/forth/frames.4th +++ 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 Index: stand/forth/logo-orb.4th =================================================================== --- stand/forth/logo-orb.4th +++ 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+ Index: stand/forth/menu.4th =================================================================== --- stand/forth/menu.4th +++ 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 ; Index: stand/forth/support.4th =================================================================== --- stand/forth/support.4th +++ 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-width" getenv? ; \ Private definitions Index: stand/i386/libi386/Makefile =================================================================== --- stand/i386/libi386/Makefile +++ 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,10 @@ .endif # terminal emulation -CFLAGS.vidconsole.c+= -I${SRCTOP}/sys/teken +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 Index: stand/i386/libi386/bootinfo.c =================================================================== --- stand/i386/libi386/bootinfo.c +++ stand/i386/libi386/bootinfo.c @@ -32,9 +32,20 @@ #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) Index: stand/i386/libi386/bootinfo32.c =================================================================== --- stand/i386/libi386/bootinfo32.c +++ 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; Index: stand/i386/libi386/bootinfo64.c =================================================================== --- stand/i386/libi386/bootinfo64.c +++ 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); Index: stand/i386/libi386/libi386.h =================================================================== --- stand/i386/libi386/libi386.h +++ 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); Index: stand/i386/libi386/vbe.h =================================================================== --- /dev/null +++ stand/i386/libi386/vbe.h @@ -0,0 +1,156 @@ +/*- + * 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 Alignment; +} __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 */ + +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); + +/* 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); Index: stand/i386/libi386/vbe.c =================================================================== --- /dev/null +++ stand/i386/libi386/vbe.c @@ -0,0 +1,1194 @@ +/*- + * 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; +/* + * 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); +} + +/* 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; +} + +/* 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; + } + 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, NULL, 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.Alignment = entry->Alignment; + + 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; + + 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 = 0, i; + + memset(vbe, 0, sizeof (*vbe)); + if (biosvbe_info(vbe) != VBE_SUCCESS) + return (0); + farptr = vbe->VideoModePtr; + if (farptr == 0) + return (0); + + if (m != -1) + i = 8; + else if (depth == -1) + i = 32; + else + i = depth; + + while (i > 0) { + while ((mode = *(uint16_t *)vbe_farptr(farptr)) != 0xffff) { + safety++; + farptr += 2; + if (safety == 100) + return (0); + 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; + safety = 0; + + 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 == 100) { + 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 >= 0x100) { + 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); +} Index: stand/i386/libi386/vidconsole.c =================================================================== --- stand/i386/libi386/vidconsole.c +++ 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,16 +51,22 @@ 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; static tf_bell_t vidc_cons_bell; static tf_cursor_t vidc_text_cursor; +static tf_cursor_t vidc_gfx_cursor; static tf_putchar_t vidc_text_putchar; +static tf_putchar_t vidc_gfx_putchar; static tf_fill_t vidc_text_fill; +static tf_fill_t vidc_gfx_fill; static tf_copy_t vidc_text_copy; +static tf_copy_t vidc_gfx_copy; static tf_param_t vidc_text_param; +static tf_param_t vidc_gfx_param; static tf_respond_t vidc_cons_respond; static teken_funcs_t tf = { @@ -72,8 +79,15 @@ .tf_respond = vidc_cons_respond, }; -teken_t teken; -teken_pos_t tp; +static teken_funcs_t tfx = { + .tf_bell = vidc_cons_bell, + .tf_cursor = vidc_gfx_cursor, + .tf_putchar = vidc_gfx_putchar, + .tf_fill = vidc_gfx_fill, + .tf_copy = vidc_gfx_copy, + .tf_param = vidc_gfx_param, + .tf_respond = vidc_cons_respond, +}; struct text_pixel { teken_char_t c; @@ -82,20 +96,6 @@ static struct text_pixel *buffer; -#define NCOLORS 16 - -/* - * Between console's palette and VGA's one: - * - blue and red are swapped (1 <-> 4) - * - yellow and cyan are swapped (3 <-> 6) - */ -static const int cons_to_vga_colors[NCOLORS] = { - 0, 4, 2, 6, 1, 5, 3, 7, - 8, 12, 10, 14, 9, 13, 11, 15 -}; - -#define TEXT_COLS 80 -#define TEXT_ROWS 25 #define KEYBUFSZ 10 static uint8_t keybuf[KEYBUFSZ]; /* keybuf for extended codes */ @@ -111,63 +111,29 @@ .c_ready = vidc_ischar }; -static int -vga_get_reg(int reg, int index) +/* + * This function is used to mark a rectangular image area so the scrolling + * will know we need to copy the data from there. + */ +void +term_image_display(teken_gfx_t *state, const teken_rect_t *r) { - return (inb(reg + index)); -} + teken_pos_t p; + int idx; -static int -vga_get_atr(int reg, int i) -{ - int ret; - - (void) inb(reg + VGA_GEN_INPUT_STAT_1); - outb(reg + VGA_AC_WRITE, i); - ret = inb(reg + VGA_AC_READ); - - (void) inb(reg + VGA_GEN_INPUT_STAT_1); - - return (ret); + 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; + buffer[idx].a.ta_format |= TF_IMAGE; + } + } } static void -vga_set_atr(int reg, int i, int v) -{ - (void) inb(reg + VGA_GEN_INPUT_STAT_1); - outb(reg + VGA_AC_WRITE, i); - outb(reg + VGA_AC_WRITE, v); - - (void) inb(reg + VGA_GEN_INPUT_STAT_1); -} - -static void -vga_set_indexed(int reg, int indexreg, int datareg, uint8_t index, uint8_t val) -{ - outb(reg + indexreg, index); - outb(reg + datareg, val); -} - -static int -vga_get_indexed(int reg, int indexreg, int datareg, uint8_t index) -{ - outb(reg + indexreg, index); - return (inb(reg + datareg)); -} - -static int -vga_get_crtc(int reg, int i) -{ - return (vga_get_indexed(reg, VGA_CRTC_ADDRESS, VGA_CRTC_DATA, i)); -} - -static void -vga_set_crtc(int reg, int i, int v) -{ - vga_set_indexed(reg, VGA_CRTC_ADDRESS, VGA_CRTC_DATA, i, v); -} - -static void vidc_text_set_cursor(teken_unit_t row, teken_unit_t col, bool visible) { uint16_t addr; @@ -228,6 +194,47 @@ vidc_text_set_cursor(row, col, true); } +static void +vidc_gfx_cursor_draw(teken_gfx_t *state, const teken_pos_t *p) +{ + struct text_pixel *px; + uint32_t fg, bg; + + px = buffer + p->tp_col + p->tp_row * state->tg_tp.tp_col; + fg = teken_256to16(px->a.ta_fgcolor); + bg = teken_256to16(px->a.ta_bgcolor); + if (px->a.ta_format & TF_BOLD) + fg |= TC_LIGHT; + if (px->a.ta_format & TF_BLINK) + bg |= TC_LIGHT; + + fg = cmap[fg]; + bg = cmap[bg]; + + if (px->a.ta_format & TF_REVERSE) { + uint32_t tmp; + + tmp = fg; + fg = bg; + bg = tmp; + } + + state->tg_cursor = *p; + gfx_bm_cursor_draw(state, p, fg, bg); +} + +static void +vidc_gfx_cursor(void *arg, const teken_pos_t *p) +{ + teken_gfx_t *state = arg; + + /* Switch cursor off in old location and back on in new. */ + if (state->tg_cursor_visible) { + vidc_gfx_cursor_draw(state, &state->tg_cursor); + vidc_gfx_cursor_draw(state, p); + } +} + /* * Binary searchable table for Unicode to CP437 conversion. */ @@ -353,9 +360,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 +371,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 = &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 +386,85 @@ 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; + + buffer[idx].c = c; + buffer[idx].a = *a; + + vidc_text_printchar(state, p); } +/* + * Draw prepared glyph on terminal point p. + */ static void -vidc_text_fill(void *s, const teken_rect_t *r, teken_char_t c, +vidc_gfx_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_bm_cons_display(x, y, width, height, state->tg_glyph); +} + +/* + * Store char with its attribute to buffer and put it on screen. + */ +static void +vidc_gfx_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; + + buffer[idx].c = c; + buffer[idx].a = *a; + + /* remove the cursor */ + if (state->tg_cursor_visible) + vidc_gfx_cursor_draw(arg, &state->tg_cursor); + + glyph = font_lookup(&state->tg_font, c, a); + gfx_bitblt_bitmap(state, glyph, c, a, 0xff); + vidc_gfx_printchar(state, p); + + /* display the cursor */ + if (state->tg_cursor_visible) { + const teken_pos_t *c; + + c = teken_get_cursor(&state->tg_teken); + vidc_gfx_cursor_draw(state, c); + } +} + +static void +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,16 +474,57 @@ 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 void +vidc_gfx_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) + vidc_gfx_cursor_draw(state, &state->tg_cursor); + + glyph = font_lookup(&state->tg_font, c, a); + gfx_bitblt_bitmap(state, glyph, c, a, 0xff); + + for (p.tp_row = r->tr_begin.tp_row; p.tp_row < r->tr_end.tp_row; + p.tp_row++) { + row = &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; + vidc_gfx_printchar(state, &p); + } + } + + /* display the cursor */ + if (state->tg_cursor_visible) { + const teken_pos_t *c; + + c = teken_get_cursor(&state->tg_teken); + vidc_gfx_cursor_draw(state, c); + } +} + static bool vidc_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) @@ -431,8 +536,9 @@ } 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,8 +559,8 @@ 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; @@ -464,7 +570,7 @@ &buffer[s.tp_col + srow])) { buffer[d.tp_col + drow] = buffer[s.tp_col + srow]; - vidc_text_printchar(&d); + vidc_text_printchar(state, &d); } } } @@ -475,8 +581,8 @@ 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; @@ -486,7 +592,7 @@ &buffer[s.tp_col + srow])) { buffer[d.tp_col + drow] = buffer[s.tp_col + srow]; - vidc_text_printchar(&d); + vidc_text_printchar(state, &d); } } } @@ -495,8 +601,8 @@ 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; @@ -506,7 +612,7 @@ &buffer[s.tp_col + srow])) { buffer[d.tp_col + drow] = buffer[s.tp_col + srow]; - vidc_text_printchar(&d); + vidc_text_printchar(state, &d); } } } @@ -516,8 +622,141 @@ } static void -vidc_text_param(void *s __unused, int cmd, unsigned int value) +vidc_gfx_copy_area(teken_gfx_t *state, const teken_rect_t *s, + const teken_pos_t *d) { + unsigned sx, sy, dx, dy, width, height; + unsigned bpp, pitch; + uint8_t *fb, *src, *dst; + + 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); + + fb = ptov((uint32_t)gfx_state.tg_fb.fb_addr & 0xffffffff); + bpp = roundup2(state->tg_fb.fb_bpp, 8) >> 3; + pitch = state->tg_fb.fb_stride * bpp; + + src = fb + sx * bpp + sy * pitch; + dst = fb + dx * bpp + dy * pitch; + width *= bpp; + + for (unsigned i = 0; i < height; i++) { + unsigned increment = i * pitch; + + (void) memmove(dst + increment, src + increment, width); + } +} + +static void +vidc_gfx_copy_line(teken_gfx_t *state, int ncol, teken_pos_t *s, teken_pos_t *d) +{ + struct text_pixel *src, *dst; + 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 (vidc_same_pixel(&buffer[soffset + x], + &buffer[doffset + x])) { + if (mark) { + vidc_gfx_copy_area(state, &sr, &dp); + mark = false; + } + } else { + buffer[doffset + x] = 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) { + vidc_gfx_copy_area(state, &sr, &dp); + } +} + +static void +vidc_gfx_copy(void *arg, const teken_rect_t *r, const teken_pos_t *p) +{ + teken_gfx_t *state = arg; + unsigned doffset, soffset; + teken_pos_t d, s; + int nrow, ncol, x, y; /* Has to be signed - >= 0 comparison */ + + /* + * 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) + vidc_gfx_cursor_draw(state, &state->tg_cursor); + + /* + * 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; + + vidc_gfx_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; + + vidc_gfx_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); + vidc_gfx_cursor_draw(state, c); + } +} + +static void +vidc_text_param(void *arg, int cmd, unsigned int value) +{ + teken_gfx_t *state = arg; teken_unit_t row, col; switch (cmd) { @@ -532,10 +771,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 */ @@ -543,6 +785,36 @@ } } +static void +vidc_gfx_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); + vidc_gfx_cursor_draw(state, c); + if (value != 0) + state->tg_cursor_visible = true; + else + state->tg_cursor_visible = false; + break; + default: + /* Not yet implemented */ + break; + } +} + /* * Not implemented. */ @@ -635,7 +907,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,12 +926,201 @@ 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; + extern struct paletteentry *pe8; + + if (pe8 == NULL) + pe8 = calloc(sizeof(*pe8), NCOLORS); + 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 < NCOLORS; i++) { + rc = vbe_set_palette(&pe8[cmap[i]], i); + 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); +} + +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 * + (roundup2(gfx_state.tg_fb.fb_bpp, 8) >> 3); + 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), "%dx%d", gfx_state.tg_fb.fb_width, + gfx_state.tg_fb.fb_height); + env_setenv("kern.vt.fb.default_mode", EV_VOLATILE | EV_NOHOOK, + env, env_noset, env_screen_nounset); + 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"); + } + + free(buffer); + buffer = malloc(gfx_state.tg_tp.tp_row * gfx_state.tg_tp.tp_col * + sizeof(*buffer)); + if (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) { const teken_attr_t *a; @@ -670,6 +1131,7 @@ return (0); vidc_started = 1; + vbe_init(); /* * Check Miscellaneous Output Register (Read at 3CCh, Write at 3C2h) @@ -687,31 +1149,18 @@ 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); + 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); + } - snprintf(env, sizeof (env), "%u", tp.tp_row); - setenv("LINES", env, 1); - snprintf(env, sizeof (env), "%u", tp.tp_col); - setenv("COLUMNS", env, 1); + gfx_framework_init(); - teken_init(&teken, &tf, NULL); - teken_set_winsize(&teken, &tp); - a = teken_get_defattr(&teken); + if (!cons_update_mode(VBE_VALID_MODE(vbe_get_mode()))) + return (1); - snprintf(env, sizeof(env), "%d", a->ta_fgcolor); - env_setenv("teken.fg_color", EV_VOLATILE, env, vidc_set_colors, - env_nounset); - snprintf(env, sizeof(env), "%d", a->ta_bgcolor); - env_setenv("teken.bg_color", EV_VOLATILE, env, vidc_set_colors, - env_nounset); - - /* Erase display, this will also fill our screen buffer. */ - teken_input(&teken, "\e[J", 3); - for (int i = 0; i < 10 && vidc_ischar(); i++) (void) vidc_getchar(); @@ -735,7 +1184,7 @@ unsigned char ch = c; if (buffer != NULL) - teken_input(&teken, &ch, sizeof (ch)); + teken_input(&gfx_state.tg_teken, &ch, sizeof (ch)); else vidc_biosputchar(c); } @@ -806,29 +1255,36 @@ return (!V86_ZR(v86.efl)); } +void +gfx_fb_cons_display(uint32_t x, uint32_t y, uint32_t width, uint32_t height, + void *data) +{ + gfx_bm_cons_display(x, y, width, height, data); +} + #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 +1310,7 @@ (void) inb(0x84); } -/* +/* * We use the presence/absence of a keyboard to determine whether the internal * console can be used for input. * Index: stand/i386/loader/Makefile =================================================================== --- stand/i386/loader/Makefile +++ stand/i386/loader/Makefile @@ -23,7 +23,7 @@ .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 8x16.c # Include bcache code. HAVE_BCACHE= yes @@ -49,7 +49,7 @@ # Always add MI sources .include "${BOOTSRC}/loader.mk" -CLEANFILES+= ${LOADER} ${LOADER}.bin +CLEANFILES+= ${LOADER} ${LOADER}.bin 8x16.c ORG= 0x0 @@ -63,6 +63,9 @@ # Debug me! #CFLAGS+= -g #LDFLAGS+= -g + +8x16.c: ${SRCTOP}/share/vt/fonts/vgarom-8x16.hex + vtfontcvt -f compressed-source -o ${.TARGET} ${.ALLSRC} ${LOADER}: ${LOADER}.bin ${BTXLDR} ${BTXKERN} btxld -v -f aout -e ${LOADER_ADDRESS} -o ${.TARGET} -l ${BTXLDR} \ Index: stand/i386/loader/main.c =================================================================== --- stand/i386/loader/main.c +++ stand/i386/loader/main.c @@ -131,6 +131,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 * device, which we can't do yet. @@ -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(); Index: stand/images/Makefile =================================================================== --- /dev/null +++ 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 Index: stand/liblua/Makefile =================================================================== --- stand/liblua/Makefile +++ stand/liblua/Makefile @@ -35,6 +35,8 @@ CFLAGS+= -fno-stack-protector -D__BSD_VISIBLE CFLAGS+= -I${BOOTSRC}/include -I${LIBLUASRC} -I${LUASRC} -I${LDRSRC} +CFLAGS.lutils.c+= -I${SRCTOP}/sys/teken -I${SRCTOP}/contrib/pnglite + .if ${MACHINE_CPUARCH} == "amd64" && ${DO32:U0} == 0 CFLAGS+= -fPIC .endif Index: stand/liblua/lutils.c =================================================================== --- stand/liblua/lutils.c +++ stand/liblua/lutils.c @@ -35,6 +35,8 @@ #include "lstd.h" #include "lutils.h" #include "bootstrap.h" +#include +#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 }, }; Index: stand/libsa/printf.c =================================================================== --- stand/libsa/printf.c +++ stand/libsa/printf.c @@ -61,6 +61,25 @@ static char *ksprintn (char *buf, uintmax_t num, int base, int *len, int upper); static int kvprintf(char const *fmt, kvprintf_fn_t *func, void *arg, int radix, va_list ap); +__weak_symbol void +putchar_device(int c __unused, void *ptr __unused) +{ +} + +int +diag_printf(const char *fmt, ...) +{ + va_list ap; + int retval; + char *diag; + + diag = getenv("diag-device"); + va_start(ap, fmt); + retval = kvprintf(fmt, putchar_device, diag, 10, ap); + va_end(ap); + return retval; +} + static void putchar_wrapper(int cc, void *arg) { Index: stand/libsa/stand.h =================================================================== --- stand/libsa/stand.h +++ stand/libsa/stand.h @@ -264,6 +264,7 @@ extern void setheap(void *base, void *top); extern char *sbrk(int incr); +extern int diag_printf(const char *fmt, ...) __printflike(1, 2); extern int printf(const char *fmt, ...) __printflike(1, 2); extern int asprintf(char **buf, const char *cfmt, ...) __printflike(2, 3); extern int sprintf(char *buf, const char *cfmt, ...) __printflike(2, 3); @@ -399,6 +400,7 @@ extern int getchar(void); extern int ischar(void); extern void putchar(int); +extern void putchar_device(int, void *); extern int devopen(struct open_file *, const char *, const char **); extern int devclose(struct open_file *f); extern void panic(const char *, ...) __dead2 __printflike(1, 2); Index: stand/loader.mk =================================================================== --- stand/loader.mk +++ stand/loader.mk @@ -8,9 +8,19 @@ 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+= gfx_fb.c pnglite.c +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 +.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" Index: stand/lua/color.lua =================================================================== --- stand/lua/color.lua +++ 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) Index: stand/lua/core.lua =================================================================== --- stand/lua/core.lua +++ 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-width") ~= nil then + return true + end + end + return false +end + function core.isSerialConsole() local c = loader.getenv("console") if c ~= nil then Index: stand/lua/drawer.lua =================================================================== --- stand/lua/drawer.lua +++ 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 Index: stand/lua/gfx-orb.lua =================================================================== --- stand/lua/gfx-orb.lua +++ 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 } } Index: sys/sys/font.h =================================================================== --- sys/sys/font.h +++ sys/sys/font.h @@ -91,9 +91,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 { Index: sys/teken/teken.h =================================================================== --- sys/teken/teken.h +++ 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