diff --git a/stand/Makefile b/stand/Makefile
index 46e0856e45fd..7b871d9164db 100644
--- a/stand/Makefile
+++ b/stand/Makefile
@@ -1,83 +1,85 @@
 # $FreeBSD$
 
 .include <src.opts.mk>
 
 # For amd64 we have to build 32 and 64 bit versions of things. For
 # others we don't. LIB32LIST is a list of libraries, which if
 # included, need to be built 32-bit as well.
 .if ${MACHINE_ARCH} == "amd64"
 LIB32LIST=libsa
 .if ${MK_FORTH} != "no"
 LIB32LIST+=		ficl
 .endif
 .if ${MK_LOADER_LUA} != "no"
 LIB32LIST+=		liblua
 .endif
 .endif
 
 S.yes+=			libsa
 
 S.${MK_LOADER_OFW}+=	libofw
 S.${MK_FDT}+=		fdt
 
 S.${MK_FORTH}+=		ficl
 S.${MK_FORTH}+=		forth
 S.${MK_LOADER_LUA}+=	liblua
 S.${MK_LOADER_LUA}+=	lua
 S.yes+=			defaults
+S.yes+=			fonts
+S.yes+=			images
 S.yes+=			man
 
 .if ${MK_FORTH} != "no"
 INTERP_DEPENDS+=	forth
 .endif
 .if ${MK_LOADER_LUA} != "no"
 INTERP_DEPENDS+=	lua
 .endif
 
 .include <bsd.arch.inc.mk>
 
 S.${MK_EFI}+=		efi
 S.${MK_LOADER_UBOOT}+=	uboot
 
 .if defined(LIB32LIST)
 LIB32DEPENDS=	${LIB32LIST:S/$/32/}
 .endif
 
 .if exists(${.CURDIR}/${MACHINE}/.)
 S.yes+=         ${MACHINE}
 SUBDIR_DEPEND_${MACHINE}+=	${INTERP_DEPENDS}
 .if ${MK_FDT} != "no"
 SUBDIR_DEPEND_${MACHINE}+=	fdt
 .endif
 .if ${MK_LOADER_UBOOT} != "no"
 SUBDIR_DEPEND_${MACHINE}+=	uboot
 .endif
 .if ${MK_LOADER_OFW} != "no"
 SUBDIR_DEPEND_${MACHINE}+=	libofw
 .endif
 .endif
 
 # Build the actual subdir list from S.yes, adding in the 32-bit
 # variant if necessary.
 .for _x in ${S.yes}
 SUBDIR+=${_x}
 .if defined(LIB32LIST) && ${LIB32LIST:M${_x}}
 SUBDIR+=${_x}32
 .endif
 .if ${_x} != "libsa"
 SUBDIR_DEPEND_${_x}+=	libsa
 SUBDIR_DEPEND_${_x}32+=	libsa32
 .endif
 .endfor
 
 # Remaining dependencies
 SUBDIR_DEPEND_libsa32+=	libsa
 
 SUBDIR_DEPEND_forth+=	ficl
 SUBDIR_DEPEND_lua+=	liblua
 
 SUBDIR_DEPEND_efi+=	fdt
 
 SUBDIR_PARALLEL=	yes
 
 .include <bsd.subdir.mk>
diff --git a/stand/common/bootstrap.h b/stand/common/bootstrap.h
index a0640c2bac2c..f671dba96e63 100644
--- a/stand/common/bootstrap.h
+++ b/stand/common/bootstrap.h
@@ -1,396 +1,401 @@
 /*-
  * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
  * $FreeBSD$
  */
 
 #ifndef _BOOTSTRAP_H_
 #define	_BOOTSTRAP_H_
 
 #include <sys/types.h>
 #include <sys/queue.h>
 #include <sys/linker_set.h>
+#include <stdbool.h>
 
 #include "readin.h"
 
 /* Commands and return values; nonzero return sets command_errmsg != NULL */
 typedef int	(bootblk_cmd_t)(int argc, char *argv[]);
 #define	COMMAND_ERRBUFSZ	(256)
 extern const char *command_errmsg;
 extern char	command_errbuf[COMMAND_ERRBUFSZ];
 #define	CMD_OK		0
 #define	CMD_WARN	1
 #define	CMD_ERROR	2
 #define	CMD_CRIT	3
 #define	CMD_FATAL	4
 
 /* interp.c */
 void	interact(void);
 void	interp_emit_prompt(void);
 int	interp_builtin_cmd(int argc, char *argv[]);
 
 /* Called by interp.c for interp_*.c embedded interpreters */
 int	interp_include(const char *);	/* Execute commands from filename */
 void	interp_init(void);		/* Initialize interpreater */
 int	interp_run(const char *);	/* Run a single command */
 
 /* interp_backslash.c */
 char	*backslash(const char *str);
 
 /* interp_parse.c */
 int	parse(int *argc, char ***argv, const char *str);
 
 /* boot.c */
 void	autoboot_maybe(void);
 int	getrootmount(char *rootdev);
 
 /* misc.c */
 char	*unargv(int argc, char *argv[]);
 size_t	strlenout(vm_offset_t str);
 char	*strdupout(vm_offset_t str);
 void	kern_bzero(vm_offset_t dest, size_t len);
 int	kern_pread(readin_handle_t fd, vm_offset_t dest, size_t len, off_t off);
 void	*alloc_pread(readin_handle_t fd, off_t off, size_t len);
 
 /* bcache.c */
 void	bcache_init(size_t nblks, size_t bsize);
 void	bcache_add_dev(int);
 void	*bcache_allocate(void);
 void	bcache_free(void *);
 int	bcache_strategy(void *devdata, int rw, daddr_t blk, size_t size,
 			char *buf, size_t *rsize);
 
 /*
  * Disk block cache
  */
 struct bcache_devdata
 {
 	int	(*dv_strategy)(void *, int, daddr_t, size_t, char *, size_t *);
 	void	*dv_devdata;
 	void	*dv_cache;
 };
 
 /*
  * Modular console support.
  */
 struct console
 {
 	const char	*c_name;
 	const char	*c_desc;
 	int		c_flags;
 #define	C_PRESENTIN	(1<<0)	    /* console can provide input */
 #define	C_PRESENTOUT	(1<<1)	    /* console can provide output */
 #define	C_ACTIVEIN	(1<<2)	    /* user wants input from console */
 #define	C_ACTIVEOUT	(1<<3)	    /* user wants output to console */
 #define	C_WIDEOUT	(1<<4)	    /* c_out routine groks wide chars */
 
 	/* set c_flags to match hardware */
 	void	(* c_probe)(struct console *cp);
 	/* reinit XXX may need more args */
 	int		(* c_init)(int arg);
 	/* emit c */
 	void		(* c_out)(int c);
 	/* wait for and return input */
 	int		(* c_in)(void);
 	/* return nonzero if input waiting */
 	int		(* c_ready)(void);
 };
 extern struct console *consoles[];
 void cons_probe(void);
+bool		cons_update_mode(bool);
+void		autoload_font(bool);
 
 /*
  * Plug-and-play enumerator/configurator interface.
  */
 struct pnphandler
 {
 	const char *pp_name;		/* handler/bus name */
 	void (*pp_enumerate)(void); /* enumerate PnP devices, add to chain */
 };
 
 struct pnpident
 {
 	/* ASCII identifier, actual format varies with bus/handler */
 	char			*id_ident;
 	STAILQ_ENTRY(pnpident)	id_link;
 };
 
 struct pnpinfo
 {
 	char	*pi_desc;	/* ASCII description, optional */
 	int	pi_revision;	/* optional revision (or -1) if not supported */
 	char	*pi_module;	/* module/args nominated to handle device */
 	int	pi_argc;	/* module arguments */
 	char	**pi_argv;
 	struct pnphandler *pi_handler;	/* handler which detected this device */
 	STAILQ_HEAD(, pnpident)	pi_ident;	/* list of identifiers */
 	STAILQ_ENTRY(pnpinfo)	pi_link;
 };
 
 STAILQ_HEAD(pnpinfo_stql, pnpinfo);
 
 extern struct pnphandler *pnphandlers[];	/* provided by MD code */
 
 void			pnp_addident(struct pnpinfo *pi, char *ident);
 struct pnpinfo		*pnp_allocinfo(void);
 void			pnp_freeinfo(struct pnpinfo *pi);
 void			pnp_addinfo(struct pnpinfo *pi);
 char			*pnp_eisaformat(uint8_t *data);
 
 /*
  *  < 0	- No ISA in system
  * == 0	- Maybe ISA, search for read data port
  *  > 0	- ISA in system, value is read data port address
  */
 extern int isapnp_readport;
 
 /*
  * Version information
  */
 extern char bootprog_info[];
 
 /*
  * Interpreter information
  */
 extern const char bootprog_interp[];
 #define	INTERP_DEFINE(interpstr) \
 const char bootprog_interp[] = "$Interpreter:" interpstr
 
 
 /*
  * Preloaded file metadata header.
  *
  * Metadata are allocated on our heap, and copied into kernel space
  * before executing the kernel.
  */
 struct file_metadata
 {
 	size_t		md_size;
 	uint16_t	md_type;
 	struct file_metadata *md_next;
 	char		md_data[1];	/* data are immediately appended */
 };
 
 struct preloaded_file;
 struct mod_depend;
 
 struct kernel_module
 {
 	char	*m_name;	/* module name */
 	int	m_version;	/* module version */
 	/* char			*m_args; */	/* arguments for the module */
 	struct preloaded_file	*m_fp;
 	struct kernel_module	*m_next;
 };
 
 /*
  * Preloaded file information. Depending on type, file can contain
  * additional units called 'modules'.
  *
  * At least one file (the kernel) must be loaded in order to boot.
  * The kernel is always loaded first.
  *
  * String fields (m_name, m_type) should be dynamically allocated.
  */
 struct preloaded_file
 {
 	char *f_name;	/* file name */
 	char *f_type; /* verbose file type, eg 'ELF kernel', 'pnptable', etc. */
 	char *f_args;	/* arguments for the file */
 	/* metadata that will be placed in the module directory */
 	struct file_metadata *f_metadata;
 	int f_loader;	/* index of the loader that read the file */
 	vm_offset_t f_addr;	/* load address */
 	size_t f_size;		/* file size */
 	struct kernel_module	*f_modules;	/* list of modules if any */
 	struct preloaded_file	*f_next;	/* next file */
 };
 
 struct file_format
 {
 	/*
 	 * Load function must return EFTYPE if it can't handle
 	 * the module supplied
 	 */
 	int (*l_load)(char *, uint64_t, struct preloaded_file **);
 	/*
 	 * Only a loader that will load a kernel (first module)
 	 * should have an exec handler
 	 */
 	int (*l_exec)(struct preloaded_file *);
 };
 
 extern struct file_format *file_formats[];	/* supplied by consumer */
 extern struct preloaded_file *preloaded_files;
 
 int mod_load(char *name, struct mod_depend *verinfo, int argc, char *argv[]);
 int mod_loadkld(const char *name, int argc, char *argv[]);
 void unload(void);
 
 struct preloaded_file *file_alloc(void);
 struct preloaded_file *file_findfile(const char *name, const char *type);
 struct file_metadata *file_findmetadata(struct preloaded_file *fp, int type);
 struct preloaded_file *file_loadraw(const char *name, char *type, int insert);
 void file_discard(struct preloaded_file *fp);
 void file_addmetadata(struct preloaded_file *, int, size_t, void *);
 int file_addmodule(struct preloaded_file *, char *, int,
     struct kernel_module **);
 void file_removemetadata(struct preloaded_file *fp);
 
+vm_offset_t build_font_module(vm_offset_t);
+
 /* MI module loaders */
 #ifdef __elfN
 /* Relocation types. */
 #define	ELF_RELOC_REL	1
 #define	ELF_RELOC_RELA	2
 
 /* Relocation offset for some architectures */
 extern uint64_t __elfN(relocation_offset);
 
 struct elf_file;
 typedef Elf_Addr (symaddr_fn)(struct elf_file *ef, Elf_Size symidx);
 
 int	__elfN(loadfile)(char *, uint64_t, struct preloaded_file **);
 int	__elfN(obj_loadfile)(char *, uint64_t, struct preloaded_file **);
 int	__elfN(reloc)(struct elf_file *ef, symaddr_fn *symaddr,
 	    const void *reldata, int reltype, Elf_Addr relbase,
 	    Elf_Addr dataaddr, void *data, size_t len);
 int __elfN(loadfile_raw)(char *, uint64_t, struct preloaded_file **, int);
 int __elfN(load_modmetadata)(struct preloaded_file *, uint64_t);
 #endif
 
 /*
  * Support for commands
  */
 struct bootblk_command
 {
     const char		*c_name;
     const char		*c_desc;
     bootblk_cmd_t	*c_fn;
 };
 
 #define	COMMAND_SET(tag, key, desc, func)				\
     static bootblk_cmd_t func;						\
     static struct bootblk_command _cmd_ ## tag = { key, desc, func };	\
     DATA_SET(Xcommand_set, _cmd_ ## tag)
 
 SET_DECLARE(Xcommand_set, struct bootblk_command);
 
 /*
  * The intention of the architecture switch is to provide a convenient
  * encapsulation of the interface between the bootstrap MI and MD code.
  * MD code may selectively populate the switch at runtime based on the
  * actual configuration of the target system.
  */
 struct arch_switch
 {
 	/* Automatically load modules as required by detected hardware */
 	int (*arch_autoload)(void);
 	/* Locate the device for (name), return pointer to tail in (*path) */
 	int (*arch_getdev)(void **dev, const char *name, const char **path);
 	/*
 	 * Copy from local address space to module address space,
 	 * similar to bcopy()
 	 */
 	ssize_t	(*arch_copyin)(const void *, vm_offset_t, const size_t);
 	/*
 	 * Copy to local address space from module address space,
 	 * similar to bcopy()
 	 */
 	ssize_t	(*arch_copyout)(const vm_offset_t, void *, const size_t);
 	/* Read from file to module address space, same semantics as read() */
 	ssize_t	(*arch_readin)(readin_handle_t, vm_offset_t, const size_t);
 	/* Perform ISA byte port I/O (only for systems with ISA) */
 	int (*arch_isainb)(int port);
 	void (*arch_isaoutb)(int port, int value);
 
 	/*
 	 * Interface to adjust the load address according to the "object"
 	 * being loaded.
 	 */
 	uint64_t (*arch_loadaddr)(u_int type, void *data, uint64_t addr);
 #define	LOAD_ELF	1	/* data points to the ELF header. */
 #define	LOAD_RAW	2	/* data points to the file name. */
 
 	/*
 	 * Interface to inform MD code about a loaded (ELF) segment. This
 	 * can be used to flush caches and/or set up translations.
 	 */
 #ifdef __elfN
 	void (*arch_loadseg)(Elf_Ehdr *eh, Elf_Phdr *ph, uint64_t delta);
 #else
 	void (*arch_loadseg)(void *eh, void *ph, uint64_t delta);
 #endif
 
 	/* Probe ZFS pool(s), if needed. */
 	void (*arch_zfs_probe)(void);
 
 	/* Return the hypervisor name/type or NULL if not virtualized. */
 	const char *(*arch_hypervisor)(void);
 
 	/* For kexec-type loaders, get ksegment structure */
 	void (*arch_kexec_kseg_get)(int *nseg, void **kseg);
 };
 extern struct arch_switch archsw;
 
 /* This must be provided by the MD code, but should it be in the archsw? */
 void	delay(int delay);
 
 void	dev_cleanup(void);
 
 /*
  * nvstore API.
  */
 typedef int (nvstore_getter_cb_t)(void *, const char *, void **);
 typedef int (nvstore_setter_cb_t)(void *, int, const char *,
     const void *, size_t);
 typedef int (nvstore_setter_str_cb_t)(void *, const char *, const char *,
     const char *);
 typedef int (nvstore_unset_cb_t)(void *, const char *);
 typedef int (nvstore_print_cb_t)(void *, void *);
 typedef int (nvstore_iterate_cb_t)(void *, int (*)(void *, void *));
 
 typedef struct nvs_callbacks {
 	nvstore_getter_cb_t	*nvs_getter;
 	nvstore_setter_cb_t	*nvs_setter;
 	nvstore_setter_str_cb_t *nvs_setter_str;
 	nvstore_unset_cb_t	*nvs_unset;
 	nvstore_print_cb_t	*nvs_print;
 	nvstore_iterate_cb_t	*nvs_iterate;
 } nvs_callbacks_t;
 
 int nvstore_init(const char *, nvs_callbacks_t *, void *);
 int nvstore_fini(const char *);
 void *nvstore_get_store(const char *);
 int nvstore_print(void *);
 int nvstore_get_var(void *, const char *, void **);
 int nvstore_set_var(void *, int, const char *, void *, size_t);
 int nvstore_set_var_from_string(void *, const char *, const char *,
     const char *);
 int nvstore_unset_var(void *, const char *);
 
 #ifndef CTASSERT
 #define	CTASSERT(x)	_Static_assert(x, "compile-time assertion failed")
 #endif
 
 #endif /* !_BOOTSTRAP_H_ */
diff --git a/stand/common/gfx_fb.c b/stand/common/gfx_fb.c
new file mode 100644
index 000000000000..f73899c50525
--- /dev/null
+++ b/stand/common/gfx_fb.c
@@ -0,0 +1,2641 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2020 Toomas Soome
+ * Copyright 2019 OmniOS Community Edition (OmniOSce) Association.
+ * Copyright 2020 RackTop Systems, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/cdefs.h>
+#include <sys/param.h>
+#include <stand.h>
+#include <teken.h>
+#include <gfx_fb.h>
+#include <sys/font.h>
+#include <sys/stdint.h>
+#include <sys/endian.h>
+#include <pnglite.h>
+#include <bootstrap.h>
+#include <lz4.h>
+#if defined(EFI)
+#include <efi.h>
+#include <efilib.h>
+#else
+#include <vbe.h>
+#endif
+
+/* VGA text mode does use bold font. */
+#if !defined(VGA_8X16_FONT)
+#define	VGA_8X16_FONT		"/boot/fonts/8x16v.fnt"
+#endif
+#if !defined(DEFAULT_8X16_FONT)
+#define	DEFAULT_8X16_FONT	"/boot/fonts/8x16.fnt"
+#endif
+
+/*
+ * Must be sorted by font size in descending order
+ */
+font_list_t fonts = STAILQ_HEAD_INITIALIZER(fonts);
+
+#define	DEFAULT_FONT_DATA	font_data_8x16
+extern vt_font_bitmap_data_t	font_data_8x16;
+teken_gfx_t gfx_state = { 0 };
+
+static struct {
+	unsigned char r;	/* Red percentage value. */
+	unsigned char g;	/* Green percentage value. */
+	unsigned char b;	/* Blue percentage value. */
+} color_def[NCOLORS] = {
+	{0,	0,	0},	/* black */
+	{50,	0,	0},	/* dark red */
+	{0,	50,	0},	/* dark green */
+	{77,	63,	0},	/* dark yellow */
+	{20,	40,	64},	/* dark blue */
+	{50,	0,	50},	/* dark magenta */
+	{0,	50,	50},	/* dark cyan */
+	{75,	75,	75},	/* light gray */
+
+	{18,	20,	21},	/* dark gray */
+	{100,	0,	0},	/* light red */
+	{0,	100,	0},	/* light green */
+	{100,	100,	0},	/* light yellow */
+	{45,	62,	81},	/* light blue */
+	{100,	0,	100},	/* light magenta */
+	{0,	100,	100},	/* light cyan */
+	{100,	100,	100},	/* white */
+};
+uint32_t cmap[NCMAP];
+
+/*
+ * Between console's palette and VGA's one:
+ *  - blue and red are swapped (1 <-> 4)
+ *  - yellow and cyan are swapped (3 <-> 6)
+ */
+const int cons_to_vga_colors[NCOLORS] = {
+	0,  4,  2,  6,  1,  5,  3,  7,
+	8, 12, 10, 14,  9, 13, 11, 15
+};
+
+static const int vga_to_cons_colors[NCOLORS] = {
+	0,  1,  2,  3,  4,  5,  6,  7,
+	8,  9, 10, 11,  12, 13, 14, 15
+};
+
+struct text_pixel *screen_buffer;
+#if defined(EFI)
+static EFI_GRAPHICS_OUTPUT_BLT_PIXEL *GlyphBuffer;
+#else
+static struct paletteentry *GlyphBuffer;
+#endif
+static size_t GlyphBufferSize;
+
+static bool insert_font(char *, FONT_FLAGS);
+static int font_set(struct env_var *, int, const void *);
+static void * allocate_glyphbuffer(uint32_t, uint32_t);
+static void gfx_fb_cursor_draw(teken_gfx_t *, const teken_pos_t *, bool);
+
+/*
+ * Initialize gfx framework.
+ */
+void
+gfx_framework_init(void)
+{
+	/*
+	 * Setup font list to have builtin font.
+	 */
+	(void) insert_font(NULL, FONT_BUILTIN);
+}
+
+static uint8_t *
+gfx_get_fb_address(void)
+{
+	return (ptov((uint32_t)gfx_state.tg_fb.fb_addr));
+}
+
+/*
+ * Utility function to parse gfx mode line strings.
+ */
+bool
+gfx_parse_mode_str(char *str, int *x, int *y, int *depth)
+{
+	char *p, *end;
+
+	errno = 0;
+	p = str;
+	*x = strtoul(p, &end, 0);
+	if (*x == 0 || errno != 0)
+		return (false);
+	if (*end != 'x')
+		return (false);
+	p = end + 1;
+	*y = strtoul(p, &end, 0);
+	if (*y == 0 || errno != 0)
+		return (false);
+	if (*end != 'x') {
+		*depth = -1;    /* auto select */
+	} else {
+		p = end + 1;
+		*depth = strtoul(p, &end, 0);
+		if (*depth == 0 || errno != 0 || *end != '\0')
+			return (false);
+	}
+
+	return (true);
+}
+
+static uint32_t
+rgb_color_map(uint8_t index, uint32_t rmax, int roffset,
+    uint32_t gmax, int goffset, uint32_t bmax, int boffset)
+{
+	uint32_t color, code, gray, level;
+
+	if (index < NCOLORS) {
+#define	CF(_f, _i) ((_f ## max * color_def[(_i)]._f / 100) << _f ## offset)
+		return (CF(r, index) | CF(g, index) | CF(b, index));
+#undef  CF
+        }
+
+#define	CF(_f, _c) ((_f ## max & _c) << _f ## offset)
+        /* 6x6x6 color cube */
+        if (index > 15 && index < 232) {
+                uint32_t red, green, blue;
+
+                for (red = 0; red < 6; red++) {
+                        for (green = 0; green < 6; green++) {
+                                for (blue = 0; blue < 6; blue++) {
+                                        code = 16 + (red * 36) +
+                                            (green * 6) + blue;
+                                        if (code != index)
+                                                continue;
+                                        red = red ? (red * 40 + 55) : 0;
+                                        green = green ? (green * 40 + 55) : 0;
+                                        blue = blue ? (blue * 40 + 55) : 0;
+                                        color = CF(r, red);
+					color |= CF(g, green);
+					color |= CF(b, blue);
+					return (color);
+                                }
+                        }
+                }
+        }
+
+        /* colors 232-255 are a grayscale ramp */
+        for (gray = 0; gray < 24; gray++) {
+                level = (gray * 10) + 8;
+                code = 232 + gray;
+                if (code == index)
+                        break;
+        }
+        return (CF(r, level) | CF(g, level) | CF(b, level));
+#undef  CF
+}
+
+/*
+ * Support for color mapping.
+ * For 8, 24 and 32 bit depth, use mask size 8.
+ * 15/16 bit depth needs to use mask size from mode,
+ * or we will lose color information from 32-bit to 15/16 bit translation.
+ */
+uint32_t
+gfx_fb_color_map(uint8_t index)
+{
+	int rmask, gmask, bmask;
+	int roff, goff, boff, bpp;
+
+	roff = ffs(gfx_state.tg_fb.fb_mask_red) - 1;
+        goff = ffs(gfx_state.tg_fb.fb_mask_green) - 1;
+        boff = ffs(gfx_state.tg_fb.fb_mask_blue) - 1;
+	bpp = roundup2(gfx_state.tg_fb.fb_bpp, 8) >> 3;
+
+	if (bpp == 2)
+		rmask = gfx_state.tg_fb.fb_mask_red >> roff;
+	else
+		rmask = 0xff;
+
+	if (bpp == 2)
+		gmask = gfx_state.tg_fb.fb_mask_green >> goff;
+	else
+		gmask = 0xff;
+
+	if (bpp == 2)
+		bmask = gfx_state.tg_fb.fb_mask_blue >> boff;
+	else
+		bmask = 0xff;
+
+	return (rgb_color_map(index, rmask, 16, gmask, 8, bmask, 0));
+}
+
+/* Get indexed color */
+static uint8_t
+rgb_to_color_index(uint8_t r, uint8_t g, uint8_t b)
+{
+#if !defined(EFI)
+	uint32_t color, best, dist, k;
+	int diff;
+
+	color = 0;
+	best = NCMAP * NCMAP * NCMAP;
+	for (k = 0; k < NCMAP; k++) {
+		diff = r - pe8[k].Red;
+		dist = diff * diff;
+		diff = g - pe8[k].Green;
+		dist += diff * diff;
+		diff = b - pe8[k].Blue;
+		dist += diff * diff;
+
+		if (dist == 0)
+			break;
+		if (dist < best) {
+			color = k;
+			best = dist;
+		}
+	}
+	if (k == NCMAP)
+		k = color;
+	return (k);
+#else
+	(void) r;
+	(void) g;
+	(void) b;
+	return (0);
+#endif
+}
+
+int
+generate_cons_palette(uint32_t *palette, int format,
+    uint32_t rmax, int roffset, uint32_t gmax, int goffset,
+    uint32_t bmax, int boffset)
+{
+	int i;
+
+	switch (format) {
+	case COLOR_FORMAT_VGA:
+		for (i = 0; i < NCOLORS; i++)
+			palette[i] = cons_to_vga_colors[i];
+		for (; i < NCMAP; i++)
+			palette[i] = i;
+		break;
+	case COLOR_FORMAT_RGB:
+		for (i = 0; i < NCMAP; i++)
+			palette[i] = rgb_color_map(i, rmax, roffset,
+			    gmax, goffset, bmax, boffset);
+		break;
+	default:
+		return (ENODEV);
+	}
+
+	return (0);
+}
+
+static void
+gfx_mem_wr1(uint8_t *base, size_t size, uint32_t o, uint8_t v)
+{
+
+	if (o >= size)
+		return;
+	*(uint8_t *)(base + o) = v;
+}
+
+static void
+gfx_mem_wr2(uint8_t *base, size_t size, uint32_t o, uint16_t v)
+{
+
+	if (o >= size)
+		return;
+	*(uint16_t *)(base + o) = v;
+}
+
+static void
+gfx_mem_wr4(uint8_t *base, size_t size, uint32_t o, uint32_t v)
+{
+
+	if (o >= size)
+		return;
+	*(uint32_t *)(base + o) = v;
+}
+
+/* Our GFX Block transfer toolkit. */
+static int gfxfb_blt_fill(void *BltBuffer,
+    uint32_t DestinationX, uint32_t DestinationY,
+    uint32_t Width, uint32_t Height)
+{
+#if defined(EFI)
+	EFI_GRAPHICS_OUTPUT_BLT_PIXEL *p;
+#else
+	struct paletteentry *p;
+#endif
+	uint32_t data, bpp, pitch, y, x;
+	int roff, goff, boff;
+	size_t size;
+	off_t off;
+	uint8_t *destination;
+
+	if (BltBuffer == NULL)
+		return (EINVAL);
+
+	if (DestinationY + Height > gfx_state.tg_fb.fb_height)
+		return (EINVAL);
+
+	if (DestinationX + Width > gfx_state.tg_fb.fb_width)
+		return (EINVAL);
+
+	if (Width == 0 || Height == 0)
+		return (EINVAL);
+
+	p = BltBuffer;
+	roff = ffs(gfx_state.tg_fb.fb_mask_red) - 1;
+	goff = ffs(gfx_state.tg_fb.fb_mask_green) - 1;
+	boff = ffs(gfx_state.tg_fb.fb_mask_blue) - 1;
+
+	if (gfx_state.tg_fb.fb_bpp == 8) {
+		data = rgb_to_color_index(p->Red, p->Green, p->Blue);
+	} else {
+		data = (p->Red &
+		    (gfx_state.tg_fb.fb_mask_red >> roff)) << roff;
+		data |= (p->Green &
+		    (gfx_state.tg_fb.fb_mask_green >> goff)) << goff;
+		data |= (p->Blue &
+		    (gfx_state.tg_fb.fb_mask_blue >> boff)) << boff;
+	}
+
+	bpp = roundup2(gfx_state.tg_fb.fb_bpp, 8) >> 3;
+	pitch = gfx_state.tg_fb.fb_stride * bpp;
+	destination = gfx_get_fb_address();
+	size = gfx_state.tg_fb.fb_size;
+
+	for (y = DestinationY; y < Height + DestinationY; y++) {
+		off = y * pitch + DestinationX * bpp;
+		for (x = 0; x < Width; x++) {
+			switch (bpp) {
+			case 1:
+				gfx_mem_wr1(destination, size, off,
+				    (data < NCOLORS) ?
+				    cons_to_vga_colors[data] : data);
+				break;
+			case 2:
+				gfx_mem_wr2(destination, size, off, data);
+				break;
+			case 3:
+				gfx_mem_wr1(destination, size, off,
+				    (data >> 16) & 0xff);
+				gfx_mem_wr1(destination, size, off + 1,
+				    (data >> 8) & 0xff);
+				gfx_mem_wr1(destination, size, off + 2,
+				    data & 0xff);
+				break;
+			case 4:
+				gfx_mem_wr4(destination, size, off, data);
+				break;
+			}
+			off += bpp;
+		}
+	}
+
+	return (0);
+}
+
+static int
+gfxfb_blt_video_to_buffer(void *BltBuffer, uint32_t SourceX, uint32_t SourceY,
+    uint32_t DestinationX, uint32_t DestinationY,
+    uint32_t Width, uint32_t Height, uint32_t Delta)
+{
+#if defined(EFI)
+	EFI_GRAPHICS_OUTPUT_BLT_PIXEL *p;
+#else
+	struct paletteentry *p;
+#endif
+	uint32_t x, sy, dy;
+	uint32_t bpp, pitch, copybytes;
+	off_t off;
+	uint8_t *source, *destination, *buffer, *sb;
+	uint8_t rm, rp, gm, gp, bm, bp;
+	bool bgra;
+
+	if (BltBuffer == NULL)
+		return (EINVAL);
+
+	if (SourceY + Height >
+	    gfx_state.tg_fb.fb_height)
+		return (EINVAL);
+
+	if (SourceX + Width > gfx_state.tg_fb.fb_width)
+		return (EINVAL);
+
+	if (Width == 0 || Height == 0)
+		return (EINVAL);
+
+	if (Delta == 0)
+		Delta = Width * sizeof (*p);
+
+	bpp = roundup2(gfx_state.tg_fb.fb_bpp, 8) >> 3;
+	pitch = gfx_state.tg_fb.fb_stride * bpp;
+
+	copybytes = Width * bpp;
+
+	rp = ffs(gfx_state.tg_fb.fb_mask_red) - 1;
+	gp = ffs(gfx_state.tg_fb.fb_mask_green) - 1;
+	bp = ffs(gfx_state.tg_fb.fb_mask_blue) - 1;
+	rm = gfx_state.tg_fb.fb_mask_red >> rp;
+	gm = gfx_state.tg_fb.fb_mask_green >> gp;
+	bm = gfx_state.tg_fb.fb_mask_blue >> bp;
+
+	/* If FB pixel format is BGRA, we can use direct copy. */
+	bgra = bpp == 4 &&
+	    ffs(rm) - 1 == 8 && rp == 16 &&
+	    ffs(gm) - 1 == 8 && gp == 8 &&
+	    ffs(bm) - 1 == 8 && bp == 0;
+
+	if (bgra) {
+		buffer = NULL;
+	} else {
+		buffer = malloc(copybytes);
+		if (buffer == NULL)
+			return (ENOMEM);
+	}
+
+	for (sy = SourceY, dy = DestinationY; dy < Height + DestinationY;
+	    sy++, dy++) {
+		off = sy * pitch + SourceX * bpp;
+		source = gfx_get_fb_address() + off;
+
+		if (bgra) {
+			destination = (uint8_t *)BltBuffer + dy * Delta +
+			    DestinationX * sizeof (*p);
+		} else {
+			destination = buffer;
+		}
+
+		bcopy(source, destination, copybytes);
+
+		if (!bgra) {
+			for (x = 0; x < Width; x++) {
+				uint32_t c = 0;
+
+				p = (void *)((uint8_t *)BltBuffer +
+				    dy * Delta +
+				    (DestinationX + x) * sizeof (*p));
+				sb = buffer + x * bpp;
+				switch (bpp) {
+				case 1:
+					c = *sb;
+					break;
+				case 2:
+					c = *(uint16_t *)sb;
+					break;
+				case 3:
+					c = sb[0] << 16 | sb[1] << 8 | sb[2];
+					break;
+				case 4:
+					c = *(uint32_t *)sb;
+					break;
+				}
+
+				if (bpp == 1) {
+					*(uint32_t *)p = gfx_fb_color_map(
+					    (c < 16) ?
+					    vga_to_cons_colors[c] : c);
+				} else {
+					p->Red = (c >> rp) & rm;
+					p->Green = (c >> gp) & gm;
+					p->Blue = (c >> bp) & bm;
+					p->Reserved = 0;
+				}
+			}
+		}
+	}
+
+	free(buffer);
+	return (0);
+}
+
+static int
+gfxfb_blt_buffer_to_video(void *BltBuffer, uint32_t SourceX, uint32_t SourceY,
+    uint32_t DestinationX, uint32_t DestinationY,
+    uint32_t Width, uint32_t Height, uint32_t Delta)
+{
+#if defined(EFI)
+	EFI_GRAPHICS_OUTPUT_BLT_PIXEL *p;
+#else
+	struct paletteentry *p;
+#endif
+	uint32_t x, sy, dy;
+	uint32_t bpp, pitch, copybytes;
+	off_t off;
+	uint8_t *source, *destination, *buffer;
+	uint8_t rm, rp, gm, gp, bm, bp;
+	bool bgra;
+
+	if (BltBuffer == NULL)
+		return (EINVAL);
+
+	if (DestinationY + Height >
+	    gfx_state.tg_fb.fb_height)
+		return (EINVAL);
+
+	if (DestinationX + Width > gfx_state.tg_fb.fb_width)
+		return (EINVAL);
+
+	if (Width == 0 || Height == 0)
+		return (EINVAL);
+
+	if (Delta == 0)
+		Delta = Width * sizeof (*p);
+
+	bpp = roundup2(gfx_state.tg_fb.fb_bpp, 8) >> 3;
+	pitch = gfx_state.tg_fb.fb_stride * bpp;
+
+	copybytes = Width * bpp;
+
+	rp = ffs(gfx_state.tg_fb.fb_mask_red) - 1;
+	gp = ffs(gfx_state.tg_fb.fb_mask_green) - 1;
+	bp = ffs(gfx_state.tg_fb.fb_mask_blue) - 1;
+	rm = gfx_state.tg_fb.fb_mask_red >> rp;
+	gm = gfx_state.tg_fb.fb_mask_green >> gp;
+	bm = gfx_state.tg_fb.fb_mask_blue >> bp;
+
+	/* If FB pixel format is BGRA, we can use direct copy. */
+	bgra = bpp == 4 &&
+	    ffs(rm) - 1 == 8 && rp == 16 &&
+	    ffs(gm) - 1 == 8 && gp == 8 &&
+	    ffs(bm) - 1 == 8 && bp == 0;
+
+	if (bgra) {
+		buffer = NULL;
+	} else {
+		buffer = malloc(copybytes);
+		if (buffer == NULL)
+			return (ENOMEM);
+	}
+	for (sy = SourceY, dy = DestinationY; sy < Height + SourceY;
+	    sy++, dy++) {
+		off = dy * pitch + DestinationX * bpp;
+		destination = gfx_get_fb_address() + off;
+
+		if (bgra) {
+			source = (uint8_t *)BltBuffer + sy * Delta +
+			    SourceX * sizeof (*p);
+		} else {
+			for (x = 0; x < Width; x++) {
+				uint32_t c;
+
+				p = (void *)((uint8_t *)BltBuffer +
+				    sy * Delta +
+				    (SourceX + x) * sizeof (*p));
+				if (bpp == 1) {
+					c = rgb_to_color_index(p->Red,
+					    p->Green, p->Blue);
+				} else {
+					c = (p->Red & rm) << rp |
+					    (p->Green & gm) << gp |
+					    (p->Blue & bm) << bp;
+				}
+				off = x * bpp;
+				switch (bpp) {
+				case 1:
+					gfx_mem_wr1(buffer, copybytes,
+					    off, (c < 16) ?
+					    cons_to_vga_colors[c] : c);
+					break;
+				case 2:
+					gfx_mem_wr2(buffer, copybytes,
+					    off, c);
+					break;
+				case 3:
+					gfx_mem_wr1(buffer, copybytes,
+					    off, (c >> 16) & 0xff);
+					gfx_mem_wr1(buffer, copybytes,
+					    off + 1, (c >> 8) & 0xff);
+					gfx_mem_wr1(buffer, copybytes,
+					    off + 2, c & 0xff);
+					break;
+				case 4:
+					gfx_mem_wr4(buffer, copybytes,
+					    x * bpp, c);
+					break;
+				}
+			}
+			source = buffer;
+		}
+
+		bcopy(source, destination, copybytes);
+	}
+
+	free(buffer);
+	return (0);
+}
+
+static int
+gfxfb_blt_video_to_video(uint32_t SourceX, uint32_t SourceY,
+    uint32_t DestinationX, uint32_t DestinationY,
+    uint32_t Width, uint32_t Height)
+{
+	uint32_t bpp, copybytes;
+	int pitch;
+	uint8_t *source, *destination;
+	off_t off;
+
+	if (SourceY + Height >
+	    gfx_state.tg_fb.fb_height)
+		return (EINVAL);
+
+	if (SourceX + Width > gfx_state.tg_fb.fb_width)
+		return (EINVAL);
+
+	if (DestinationY + Height >
+	    gfx_state.tg_fb.fb_height)
+		return (EINVAL);
+
+	if (DestinationX + Width > gfx_state.tg_fb.fb_width)
+		return (EINVAL);
+
+	if (Width == 0 || Height == 0)
+		return (EINVAL);
+
+	bpp = roundup2(gfx_state.tg_fb.fb_bpp, 8) >> 3;
+	pitch = gfx_state.tg_fb.fb_stride * bpp;
+
+	copybytes = Width * bpp;
+
+	off = SourceY * pitch + SourceX * bpp;
+	source = gfx_get_fb_address() + off;
+	off = DestinationY * pitch + DestinationX * bpp;
+	destination = gfx_get_fb_address() + off;
+
+	if ((uintptr_t)destination > (uintptr_t)source) {
+		source += Height * pitch;
+		destination += Height * pitch;
+		pitch = -pitch;
+	}
+
+	while (Height-- > 0) {
+		bcopy(source, destination, copybytes);
+		source += pitch;
+		destination += pitch;
+	}
+
+	return (0);
+}
+
+int
+gfxfb_blt(void *BltBuffer, GFXFB_BLT_OPERATION BltOperation,
+    uint32_t SourceX, uint32_t SourceY,
+    uint32_t DestinationX, uint32_t DestinationY,
+    uint32_t Width, uint32_t Height, uint32_t Delta)
+{
+	int rv;
+#if defined(EFI)
+	EFI_STATUS status;
+	EFI_GRAPHICS_OUTPUT *gop = gfx_state.tg_private;
+
+	if (gop != NULL && (gop->Mode->Info->PixelFormat == PixelBltOnly ||
+	    gfx_state.tg_fb.fb_addr == 0)) {
+		switch (BltOperation) {
+		case GfxFbBltVideoFill:
+			status = gop->Blt(gop, BltBuffer, EfiBltVideoFill,
+			    SourceX, SourceY, DestinationX, DestinationY,
+			    Width, Height, Delta);
+			break;
+
+		case GfxFbBltVideoToBltBuffer:
+			status = gop->Blt(gop, BltBuffer,
+			    EfiBltVideoToBltBuffer,
+			    SourceX, SourceY, DestinationX, DestinationY,
+			    Width, Height, Delta);
+			break;
+
+		case GfxFbBltBufferToVideo:
+			status = gop->Blt(gop, BltBuffer, EfiBltBufferToVideo,
+			    SourceX, SourceY, DestinationX, DestinationY,
+			    Width, Height, Delta);
+			break;
+
+		case GfxFbBltVideoToVideo:
+			status = gop->Blt(gop, BltBuffer, EfiBltVideoToVideo,
+			    SourceX, SourceY, DestinationX, DestinationY,
+			    Width, Height, Delta);
+			break;
+
+		default:
+			status = EFI_INVALID_PARAMETER;
+			break;
+		}
+
+		switch (status) {
+		case EFI_SUCCESS:
+			rv = 0;
+			break;
+
+		case EFI_INVALID_PARAMETER:
+			rv = EINVAL;
+			break;
+
+		case EFI_DEVICE_ERROR:
+		default:
+			rv = EIO;
+			break;
+		}
+
+		return (rv);
+	}
+#endif
+
+	switch (BltOperation) {
+	case GfxFbBltVideoFill:
+		rv = gfxfb_blt_fill(BltBuffer, DestinationX, DestinationY,
+		    Width, Height);
+		break;
+
+	case GfxFbBltVideoToBltBuffer:
+		rv = gfxfb_blt_video_to_buffer(BltBuffer, SourceX, SourceY,
+		    DestinationX, DestinationY, Width, Height, Delta);
+		break;
+
+	case GfxFbBltBufferToVideo:
+		rv = gfxfb_blt_buffer_to_video(BltBuffer, SourceX, SourceY,
+		    DestinationX, DestinationY, Width, Height, Delta);
+		break;
+
+	case GfxFbBltVideoToVideo:
+		rv = gfxfb_blt_video_to_video(SourceX, SourceY,
+		    DestinationX, DestinationY, Width, Height);
+		break;
+
+	default:
+		rv = EINVAL;
+		break;
+	}
+	return (rv);
+}
+
+void
+gfx_bitblt_bitmap(teken_gfx_t *state, const uint8_t *glyph,
+    const teken_attr_t *a, uint32_t alpha, bool cursor)
+{
+	uint32_t width, height;
+	uint32_t fgc, bgc, bpl, cc, o;
+	int bpp, bit, byte;
+	bool invert = false;
+
+	bpp = 4;		/* We only generate BGRA */
+	width = state->tg_font.vf_width;
+	height = state->tg_font.vf_height;
+	bpl = (width + 7) / 8;  /* Bytes per source line. */
+
+	fgc = a->ta_fgcolor;
+	bgc = a->ta_bgcolor;
+	if (a->ta_format & TF_BOLD)
+		fgc |= TC_LIGHT;
+	if (a->ta_format & TF_BLINK)
+		bgc |= TC_LIGHT;
+
+	fgc = gfx_fb_color_map(fgc);
+	bgc = gfx_fb_color_map(bgc);
+
+	if (a->ta_format & TF_REVERSE)
+		invert = !invert;
+	if (cursor)
+		invert = !invert;
+	if (invert) {
+		uint32_t tmp;
+
+		tmp = fgc;
+		fgc = bgc;
+		bgc = tmp;
+	}
+
+	alpha = alpha << 24;
+	fgc |= alpha;
+	bgc |= alpha;
+
+	for (uint32_t y = 0; y < height; y++) {
+		for (uint32_t x = 0; x < width; x++) {
+			byte = y * bpl + x / 8;
+			bit = 0x80 >> (x % 8);
+			o = y * width * bpp + x * bpp;
+			cc = glyph[byte] & bit ? fgc : bgc;
+
+			gfx_mem_wr4(state->tg_glyph,
+			    state->tg_glyph_size, o, cc);
+		}
+	}
+}
+
+/*
+ * Draw prepared glyph on terminal point p.
+ */
+static void
+gfx_fb_printchar(teken_gfx_t *state, const teken_pos_t *p)
+{
+	unsigned x, y, width, height;
+
+	width = state->tg_font.vf_width;
+	height = state->tg_font.vf_height;
+	x = state->tg_origin.tp_col + p->tp_col * width;
+	y = state->tg_origin.tp_row + p->tp_row * height;
+
+	gfx_fb_cons_display(x, y, width, height, state->tg_glyph);
+}
+
+/*
+ * Store char with its attribute to buffer and put it on screen.
+ */
+void
+gfx_fb_putchar(void *arg, const teken_pos_t *p, teken_char_t c,
+    const teken_attr_t *a)
+{
+	teken_gfx_t *state = arg;
+	const uint8_t *glyph;
+	int idx;
+
+	idx = p->tp_col + p->tp_row * state->tg_tp.tp_col;
+	if (idx >= state->tg_tp.tp_col * state->tg_tp.tp_row)
+		return;
+
+	/* remove the cursor */
+	if (state->tg_cursor_visible)
+		gfx_fb_cursor_draw(state, &state->tg_cursor, false);
+
+	screen_buffer[idx].c = c;
+	screen_buffer[idx].a = *a;
+
+	glyph = font_lookup(&state->tg_font, c, a);
+	gfx_bitblt_bitmap(state, glyph, a, 0xff, false);
+	gfx_fb_printchar(state, p);
+
+	/* display the cursor */
+	if (state->tg_cursor_visible) {
+		const teken_pos_t *c;
+
+		c = teken_get_cursor(&state->tg_teken);
+		gfx_fb_cursor_draw(state, c, true);
+	}
+}
+
+void
+gfx_fb_fill(void *arg, const teken_rect_t *r, teken_char_t c,
+    const teken_attr_t *a)
+{
+	teken_gfx_t *state = arg;
+	const uint8_t *glyph;
+	teken_pos_t p;
+	struct text_pixel *row;
+
+	/* remove the cursor */
+	if (state->tg_cursor_visible)
+		gfx_fb_cursor_draw(state, &state->tg_cursor, false);
+
+	glyph = font_lookup(&state->tg_font, c, a);
+	gfx_bitblt_bitmap(state, glyph, a, 0xff, false);
+
+	for (p.tp_row = r->tr_begin.tp_row; p.tp_row < r->tr_end.tp_row;
+	    p.tp_row++) {
+		row = &screen_buffer[p.tp_row * state->tg_tp.tp_col];
+		for (p.tp_col = r->tr_begin.tp_col;
+		    p.tp_col < r->tr_end.tp_col; p.tp_col++) {
+			row[p.tp_col].c = c;
+			row[p.tp_col].a = *a;
+			gfx_fb_printchar(state, &p);
+		}
+	}
+
+	/* display the cursor */
+	if (state->tg_cursor_visible) {
+		const teken_pos_t *c;
+
+		c = teken_get_cursor(&state->tg_teken);
+		gfx_fb_cursor_draw(state, c, true);
+	}
+}
+
+static void
+gfx_fb_cursor_draw(teken_gfx_t *state, const teken_pos_t *p, bool on)
+{
+	const uint8_t *glyph;
+	int idx;
+
+	idx = p->tp_col + p->tp_row * state->tg_tp.tp_col;
+	if (idx >= state->tg_tp.tp_col * state->tg_tp.tp_row)
+		return;
+
+	glyph = font_lookup(&state->tg_font, screen_buffer[idx].c,
+	    &screen_buffer[idx].a);
+	gfx_bitblt_bitmap(state, glyph, &screen_buffer[idx].a, 0xff, on);
+	gfx_fb_printchar(state, p);
+	state->tg_cursor = *p;
+}
+
+void
+gfx_fb_cursor(void *arg, const teken_pos_t *p)
+{
+	teken_gfx_t *state = arg;
+#if defined(EFI)
+	EFI_TPL tpl;
+
+	tpl = BS->RaiseTPL(TPL_NOTIFY);
+#endif
+
+	/* Switch cursor off in old location and back on in new. */
+	if (state->tg_cursor_visible) {
+		gfx_fb_cursor_draw(state, &state->tg_cursor, false);
+		gfx_fb_cursor_draw(state, p, true);
+	}
+#if defined(EFI)
+	BS->RestoreTPL(tpl);
+#endif
+}
+
+void
+gfx_fb_param(void *arg, int cmd, unsigned int value)
+{
+	teken_gfx_t *state = arg;
+	const teken_pos_t *c;
+
+	switch (cmd) {
+	case TP_SETLOCALCURSOR:
+		/*
+		 * 0 means normal (usually block), 1 means hidden, and
+		 * 2 means blinking (always block) for compatibility with
+		 * syscons.  We don't support any changes except hiding,
+		 * so must map 2 to 0.
+		 */
+		value = (value == 1) ? 0 : 1;
+		/* FALLTHROUGH */
+	case TP_SHOWCURSOR:
+		c = teken_get_cursor(&state->tg_teken);
+		gfx_fb_cursor_draw(state, c, true);
+		if (value != 0)
+			state->tg_cursor_visible = true;
+		else
+			state->tg_cursor_visible = false;
+		break;
+	default:
+		/* Not yet implemented */
+		break;
+	}
+}
+
+bool
+is_same_pixel(struct text_pixel *px1, struct text_pixel *px2)
+{
+	if (px1->c != px2->c)
+		return (false);
+
+	/* Is there image stored? */
+	if ((px1->a.ta_format & TF_IMAGE) ||
+	    (px2->a.ta_format & TF_IMAGE))
+		return (false);
+
+	if (px1->a.ta_format != px2->a.ta_format)
+		return (false);
+	if (px1->a.ta_fgcolor != px2->a.ta_fgcolor)
+		return (false);
+	if (px1->a.ta_bgcolor != px2->a.ta_bgcolor)
+		return (false);
+
+	return (true);
+}
+
+static void
+gfx_fb_copy_area(teken_gfx_t *state, const teken_rect_t *s,
+    const teken_pos_t *d)
+{
+	uint32_t sx, sy, dx, dy, width, height;
+
+	width = state->tg_font.vf_width;
+	height = state->tg_font.vf_height;
+
+	sx = state->tg_origin.tp_col + s->tr_begin.tp_col * width;
+	sy = state->tg_origin.tp_row + s->tr_begin.tp_row * height;
+	dx = state->tg_origin.tp_col + d->tp_col * width;
+	dy = state->tg_origin.tp_row + d->tp_row * height;
+
+	width *= (s->tr_end.tp_col - s->tr_begin.tp_col + 1);
+
+	(void) gfxfb_blt(NULL, GfxFbBltVideoToVideo, sx, sy, dx, dy,
+		    width, height, 0);
+}
+
+static void
+gfx_fb_copy_line(teken_gfx_t *state, int ncol, teken_pos_t *s, teken_pos_t *d)
+{
+	teken_rect_t sr;
+	teken_pos_t dp;
+	unsigned soffset, doffset;
+	bool mark = false;
+	int x;
+
+	soffset = s->tp_col + s->tp_row * state->tg_tp.tp_col;
+	doffset = d->tp_col + d->tp_row * state->tg_tp.tp_col;
+
+	for (x = 0; x < ncol; x++) {
+		if (is_same_pixel(&screen_buffer[soffset + x],
+		    &screen_buffer[doffset + x])) {
+			if (mark) {
+				gfx_fb_copy_area(state, &sr, &dp);
+				mark = false;
+			}
+		} else {
+			screen_buffer[doffset + x] = screen_buffer[soffset + x];
+			if (mark) {
+				/* update end point */
+				sr.tr_end.tp_col = s->tp_col + x;;
+			} else {
+				/* set up new rectangle */
+				mark = true;
+				sr.tr_begin.tp_col = s->tp_col + x;
+				sr.tr_begin.tp_row = s->tp_row;
+				sr.tr_end.tp_col = s->tp_col + x;
+				sr.tr_end.tp_row = s->tp_row;
+				dp.tp_col = d->tp_col + x;
+				dp.tp_row = d->tp_row;
+			}
+		}
+	}
+	if (mark) {
+		gfx_fb_copy_area(state, &sr, &dp);
+	}
+}
+
+void
+gfx_fb_copy(void *arg, const teken_rect_t *r, const teken_pos_t *p)
+{
+	teken_gfx_t *state = arg;
+	unsigned doffset, soffset;
+	teken_pos_t d, s;
+	int nrow, ncol, y; /* Has to be signed - >= 0 comparison */
+
+	/*
+	 * Copying is a little tricky. We must make sure we do it in
+	 * correct order, to make sure we don't overwrite our own data.
+	 */
+
+	nrow = r->tr_end.tp_row - r->tr_begin.tp_row;
+	ncol = r->tr_end.tp_col - r->tr_begin.tp_col;
+
+	if (p->tp_row + nrow > state->tg_tp.tp_row ||
+	    p->tp_col + ncol > state->tg_tp.tp_col)
+		return;
+
+	soffset = r->tr_begin.tp_col + r->tr_begin.tp_row * state->tg_tp.tp_col;
+	doffset = p->tp_col + p->tp_row * state->tg_tp.tp_col;
+
+	/* remove the cursor */
+	if (state->tg_cursor_visible)
+		gfx_fb_cursor_draw(state, &state->tg_cursor, false);
+
+	/*
+	 * Copy line by line.
+	 */
+	if (doffset <= soffset) {
+		s = r->tr_begin;
+		d = *p;
+		for (y = 0; y < nrow; y++) {
+			s.tp_row = r->tr_begin.tp_row + y;
+			d.tp_row = p->tp_row + y;
+
+			gfx_fb_copy_line(state, ncol, &s, &d);
+		}
+	} else {
+		for (y = nrow - 1; y >= 0; y--) {
+			s.tp_row = r->tr_begin.tp_row + y;
+			d.tp_row = p->tp_row + y;
+
+			gfx_fb_copy_line(state, ncol, &s, &d);
+		}
+	}
+
+	/* display the cursor */
+	if (state->tg_cursor_visible) {
+		const teken_pos_t *c;
+
+		c = teken_get_cursor(&state->tg_teken);
+		gfx_fb_cursor_draw(state, c, true);
+	}
+}
+
+/*
+ * Implements alpha blending for RGBA data, could use pixels for arguments,
+ * but byte stream seems more generic.
+ * The generic alpha blending is:
+ * blend = alpha * fg + (1.0 - alpha) * bg.
+ * Since our alpha is not from range [0..1], we scale appropriately.
+ */
+static uint8_t
+alpha_blend(uint8_t fg, uint8_t bg, uint8_t alpha)
+{
+	uint16_t blend, h, l;
+
+	/* trivial corner cases */
+	if (alpha == 0)
+		return (bg);
+	if (alpha == 0xFF)
+		return (fg);
+	blend = (alpha * fg + (0xFF - alpha) * bg);
+	/* Division by 0xFF */
+	h = blend >> 8;
+	l = blend & 0xFF;
+	if (h + l >= 0xFF)
+		h++;
+	return (h);
+}
+
+/*
+ * Implements alpha blending for RGBA data, could use pixels for arguments,
+ * but byte stream seems more generic.
+ * The generic alpha blending is:
+ * blend = alpha * fg + (1.0 - alpha) * bg.
+ * Since our alpha is not from range [0..1], we scale appropriately.
+ */
+static void
+bitmap_cpy(void *dst, void *src, uint32_t size)
+{
+#if defined(EFI)
+	EFI_GRAPHICS_OUTPUT_BLT_PIXEL *ps, *pd;
+#else
+	struct paletteentry *ps, *pd;
+#endif
+	uint32_t i;
+	uint8_t a;
+
+	ps = src;
+	pd = dst;
+
+	/*
+	 * we only implement alpha blending for depth 32.
+	 */
+	for (i = 0; i < size; i ++) {
+		a = ps[i].Reserved;
+		pd[i].Red = alpha_blend(ps[i].Red, pd[i].Red, a);
+		pd[i].Green = alpha_blend(ps[i].Green, pd[i].Green, a);
+		pd[i].Blue = alpha_blend(ps[i].Blue, pd[i].Blue, a);
+		pd[i].Reserved = a;
+	}
+}
+
+static void *
+allocate_glyphbuffer(uint32_t width, uint32_t height)
+{
+	size_t size;
+
+	size = sizeof (*GlyphBuffer) * width * height;
+	if (size != GlyphBufferSize) {
+		free(GlyphBuffer);
+		GlyphBuffer = malloc(size);
+		if (GlyphBuffer == NULL)
+			return (NULL);
+		GlyphBufferSize = size;
+	}
+	return (GlyphBuffer);
+}
+
+void
+gfx_fb_cons_display(uint32_t x, uint32_t y, uint32_t width, uint32_t height,
+    void *data)
+{
+#if defined(EFI)
+	EFI_GRAPHICS_OUTPUT_BLT_PIXEL *buf;
+#else
+	struct paletteentry *buf;
+#endif
+	size_t size;
+
+	size = width * height * sizeof(*buf);
+
+	/*
+	 * Common data to display is glyph, use preallocated
+	 * glyph buffer.
+	 */
+        if (gfx_state.tg_glyph_size != GlyphBufferSize)
+                (void) allocate_glyphbuffer(width, height);
+
+	if (size == GlyphBufferSize)
+		buf = GlyphBuffer;
+	else
+		buf = malloc(size);
+	if (buf == NULL)
+		return;
+
+	if (gfxfb_blt(buf, GfxFbBltVideoToBltBuffer, x, y, 0, 0,
+	    width, height, 0) == 0) {
+		bitmap_cpy(buf, data, width * height);
+		(void) gfxfb_blt(buf, GfxFbBltBufferToVideo, 0, 0, x, y,
+		    width, height, 0);
+	}
+	if (buf != GlyphBuffer)
+		free(buf);
+}
+
+/*
+ * Public graphics primitives.
+ */
+
+static int
+isqrt(int num)
+{
+	int res = 0;
+	int bit = 1 << 30;
+
+	/* "bit" starts at the highest power of four <= the argument. */
+	while (bit > num)
+		bit >>= 2;
+
+	while (bit != 0) {
+		if (num >= res + bit) {
+			num -= res + bit;
+			res = (res >> 1) + bit;
+		} else {
+			res >>= 1;
+		}
+		bit >>= 2;
+	}
+	return (res);
+}
+
+/* set pixel in framebuffer using gfx coordinates */
+void
+gfx_fb_setpixel(uint32_t x, uint32_t y)
+{
+	uint32_t c;
+	const teken_attr_t *ap;
+
+	if (gfx_state.tg_fb_type == FB_TEXT)
+		return;
+
+	ap = teken_get_curattr(&gfx_state.tg_teken);
+        if (ap->ta_format & TF_REVERSE) {
+		c = ap->ta_bgcolor;
+		if (ap->ta_format & TF_BLINK)
+			c |= TC_LIGHT;
+	} else {
+		c = ap->ta_fgcolor;
+		if (ap->ta_format & TF_BOLD)
+			c |= TC_LIGHT;
+	}
+
+	c = gfx_fb_color_map(c);
+
+	if (x >= gfx_state.tg_fb.fb_width ||
+	    y >= gfx_state.tg_fb.fb_height)
+		return;
+
+	gfxfb_blt(&c, GfxFbBltVideoFill, 0, 0, x, y, 1, 1, 0);
+}
+
+/*
+ * draw rectangle in framebuffer using gfx coordinates.
+ * The function is borrowed from vt_fb.c
+ */
+void
+gfx_fb_drawrect(uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2,
+    uint32_t fill)
+{
+	uint32_t x, y;
+
+	if (gfx_state.tg_fb_type == FB_TEXT)
+		return;
+
+	for (y = y1; y <= y2; y++) {
+		if (fill || (y == y1) || (y == y2)) {
+			for (x = x1; x <= x2; x++)
+				gfx_fb_setpixel(x, y);
+		} else {
+			gfx_fb_setpixel(x1, y);
+			gfx_fb_setpixel(x2, y);
+		}
+	}
+}
+
+void
+gfx_fb_line(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1, uint32_t wd)
+{
+	int dx, sx, dy, sy;
+	int err, e2, x2, y2, ed, width;
+
+	if (gfx_state.tg_fb_type == FB_TEXT)
+		return;
+
+	width = wd;
+	sx = x0 < x1? 1 : -1;
+	sy = y0 < y1? 1 : -1;
+	dx = x1 > x0? x1 - x0 : x0 - x1;
+	dy = y1 > y0? y1 - y0 : y0 - y1;
+	err = dx + dy;
+	ed = dx + dy == 0 ? 1: isqrt(dx * dx + dy * dy);
+
+	for (;;) {
+		gfx_fb_setpixel(x0, y0);
+		e2 = err;
+		x2 = x0;
+		if ((e2 << 1) >= -dx) {		/* x step */
+			e2 += dy;
+			y2 = y0;
+			while (e2 < ed * width &&
+			    (y1 != (uint32_t)y2 || dx > dy)) {
+				y2 += sy;
+				gfx_fb_setpixel(x0, y2);
+				e2 += dx;
+			}
+			if (x0 == x1)
+				break;
+			e2 = err;
+			err -= dy;
+			x0 += sx;
+		}
+		if ((e2 << 1) <= dy) {		/* y step */
+			e2 = dx-e2;
+			while (e2 < ed * width &&
+			    (x1 != (uint32_t)x2 || dx < dy)) {
+				x2 += sx;
+				gfx_fb_setpixel(x2, y0);
+				e2 += dy;
+			}
+			if (y0 == y1)
+				break;
+			err += dx;
+			y0 += sy;
+		}
+	}
+}
+
+/*
+ * quadratic Bézier curve limited to gradients without sign change.
+ */
+void
+gfx_fb_bezier(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1, uint32_t x2,
+    uint32_t y2, uint32_t wd)
+{
+	int sx, sy, xx, yy, xy, width;
+	int dx, dy, err, curvature;
+	int i;
+
+	if (gfx_state.tg_fb_type == FB_TEXT)
+		return;
+
+	width = wd;
+	sx = x2 - x1;
+	sy = y2 - y1;
+	xx = x0 - x1;
+	yy = y0 - y1;
+	curvature = xx*sy - yy*sx;
+
+	if (sx*sx + sy*sy > xx*xx+yy*yy) {
+		x2 = x0;
+		x0 = sx + x1;
+		y2 = y0;
+		y0 = sy + y1;
+		curvature = -curvature;
+	}
+	if (curvature != 0) {
+		xx += sx;
+		sx = x0 < x2? 1 : -1;
+		xx *= sx;
+		yy += sy;
+		sy = y0 < y2? 1 : -1;
+		yy *= sy;
+		xy = (xx*yy) << 1;
+		xx *= xx;
+		yy *= yy;
+		if (curvature * sx * sy < 0) {
+			xx = -xx;
+			yy = -yy;
+			xy = -xy;
+			curvature = -curvature;
+		}
+		dx = 4 * sy * curvature * (x1 - x0) + xx - xy;
+		dy = 4 * sx * curvature * (y0 - y1) + yy - xy;
+		xx += xx;
+		yy += yy;
+		err = dx + dy + xy;
+		do {
+			for (i = 0; i <= width; i++)
+				gfx_fb_setpixel(x0 + i, y0);
+			if (x0 == x2 && y0 == y2)
+				return;  /* last pixel -> curve finished */
+			y1 = 2 * err < dx;
+			if (2 * err > dy) {
+				x0 += sx;
+				dx -= xy;
+				dy += yy;
+				err += dy;
+			}
+			if (y1 != 0) {
+				y0 += sy;
+				dy -= xy;
+				dx += xx;
+				err += dx;
+			}
+		} while (dy < dx); /* gradient negates -> algorithm fails */
+	}
+	gfx_fb_line(x0, y0, x2, y2, width);
+}
+
+/*
+ * draw rectangle using terminal coordinates and current foreground color.
+ */
+void
+gfx_term_drawrect(uint32_t ux1, uint32_t uy1, uint32_t ux2, uint32_t uy2)
+{
+	int x1, y1, x2, y2;
+	int xshift, yshift;
+	int width, i;
+	uint32_t vf_width, vf_height;
+	teken_rect_t r;
+
+	if (gfx_state.tg_fb_type == FB_TEXT)
+		return;
+
+	vf_width = gfx_state.tg_font.vf_width;
+	vf_height = gfx_state.tg_font.vf_height;
+	width = vf_width / 4;			/* line width */
+	xshift = (vf_width - width) / 2;
+	yshift = (vf_height - width) / 2;
+
+	/* Shift coordinates */
+	if (ux1 != 0)
+		ux1--;
+	if (uy1 != 0)
+		uy1--;
+	ux2--;
+	uy2--;
+
+	/* mark area used in terminal */
+	r.tr_begin.tp_col = ux1;
+	r.tr_begin.tp_row = uy1;
+	r.tr_end.tp_col = ux2 + 1;
+	r.tr_end.tp_row = uy2 + 1;
+
+	term_image_display(&gfx_state, &r);
+
+	/*
+	 * Draw horizontal lines width points thick, shifted from outer edge.
+	 */
+	x1 = (ux1 + 1) * vf_width + gfx_state.tg_origin.tp_col;
+	y1 = uy1 * vf_height + gfx_state.tg_origin.tp_row + yshift;
+	x2 = ux2 * vf_width + gfx_state.tg_origin.tp_col;
+	gfx_fb_drawrect(x1, y1, x2, y1 + width, 1);
+	y2 = uy2 * vf_height + gfx_state.tg_origin.tp_row;
+	y2 += vf_height - yshift - width;
+	gfx_fb_drawrect(x1, y2, x2, y2 + width, 1);
+
+	/*
+	 * Draw vertical lines width points thick, shifted from outer edge.
+	 */
+	x1 = ux1 * vf_width + gfx_state.tg_origin.tp_col + xshift;
+	y1 = uy1 * vf_height + gfx_state.tg_origin.tp_row;
+	y1 += vf_height;
+	y2 = uy2 * vf_height + gfx_state.tg_origin.tp_row;
+	gfx_fb_drawrect(x1, y1, x1 + width, y2, 1);
+	x1 = ux2 * vf_width + gfx_state.tg_origin.tp_col;
+	x1 += vf_width - xshift - width;
+	gfx_fb_drawrect(x1, y1, x1 + width, y2, 1);
+
+	/* Draw upper left corner. */
+	x1 = ux1 * vf_width + gfx_state.tg_origin.tp_col + xshift;
+	y1 = uy1 * vf_height + gfx_state.tg_origin.tp_row;
+	y1 += vf_height;
+
+	x2 = ux1 * vf_width + gfx_state.tg_origin.tp_col;
+	x2 += vf_width;
+	y2 = uy1 * vf_height + gfx_state.tg_origin.tp_row + yshift;
+	for (i = 0; i <= width; i++)
+		gfx_fb_bezier(x1 + i, y1, x1 + i, y2 + i, x2, y2 + i, width-i);
+
+	/* Draw lower left corner. */
+	x1 = ux1 * vf_width + gfx_state.tg_origin.tp_col;
+	x1 += vf_width;
+	y1 = uy2 * vf_height + gfx_state.tg_origin.tp_row;
+	y1 += vf_height - yshift;
+	x2 = ux1 * vf_width + gfx_state.tg_origin.tp_col + xshift;
+	y2 = uy2 * vf_height + gfx_state.tg_origin.tp_row;
+	for (i = 0; i <= width; i++)
+		gfx_fb_bezier(x1, y1 - i, x2 + i, y1 - i, x2 + i, y2, width-i);
+
+	/* Draw upper right corner. */
+	x1 = ux2 * vf_width + gfx_state.tg_origin.tp_col;
+	y1 = uy1 * vf_height + gfx_state.tg_origin.tp_row + yshift;
+	x2 = ux2 * vf_width + gfx_state.tg_origin.tp_col;
+	x2 += vf_width - xshift - width;
+	y2 = uy1 * vf_height + gfx_state.tg_origin.tp_row;
+	y2 += vf_height;
+	for (i = 0; i <= width; i++)
+		gfx_fb_bezier(x1, y1 + i, x2 + i, y1 + i, x2 + i, y2, width-i);
+
+	/* Draw lower right corner. */
+	x1 = ux2 * vf_width + gfx_state.tg_origin.tp_col;
+	y1 = uy2 * vf_height + gfx_state.tg_origin.tp_row;
+	y1 += vf_height - yshift;
+	x2 = ux2 * vf_width + gfx_state.tg_origin.tp_col;
+	x2 += vf_width - xshift - width;
+	y2 = uy2 * vf_height + gfx_state.tg_origin.tp_row;
+	for (i = 0; i <= width; i++)
+		gfx_fb_bezier(x1, y1 - i, x2 + i, y1 - i, x2 + i, y2, width-i);
+}
+
+int
+gfx_fb_putimage(png_t *png, uint32_t ux1, uint32_t uy1, uint32_t ux2,
+    uint32_t uy2, uint32_t flags)
+{
+#if defined(EFI)
+	EFI_GRAPHICS_OUTPUT_BLT_PIXEL *p;
+#else
+	struct paletteentry *p;
+#endif
+	uint8_t *data;
+	uint32_t i, j, x, y, fheight, fwidth;
+	int rs, gs, bs;
+	uint8_t r, g, b, a;
+	bool scale = false;
+	bool trace = false;
+	teken_rect_t rect;
+
+	trace = (flags & FL_PUTIMAGE_DEBUG) != 0;
+
+	if (gfx_state.tg_fb_type == FB_TEXT) {
+		if (trace)
+			printf("Framebuffer not active.\n");
+		return (1);
+	}
+
+	if (png->color_type != PNG_TRUECOLOR_ALPHA) {
+		if (trace)
+			printf("Not truecolor image.\n");
+		return (1);
+	}
+
+	if (ux1 > gfx_state.tg_fb.fb_width ||
+	    uy1 > gfx_state.tg_fb.fb_height) {
+		if (trace)
+			printf("Top left coordinate off screen.\n");
+		return (1);
+	}
+
+	if (png->width > UINT16_MAX || png->height > UINT16_MAX) {
+		if (trace)
+			printf("Image too large.\n");
+		return (1);
+	}
+
+	if (png->width < 1 || png->height < 1) {
+		if (trace)
+			printf("Image too small.\n");
+		return (1);
+	}
+
+	/*
+	 * If 0 was passed for either ux2 or uy2, then calculate the missing
+	 * part of the bottom right coordinate.
+	 */
+	scale = true;
+	if (ux2 == 0 && uy2 == 0) {
+		/* Both 0, use the native resolution of the image */
+		ux2 = ux1 + png->width;
+		uy2 = uy1 + png->height;
+		scale = false;
+	} else if (ux2 == 0) {
+		/* Set ux2 from uy2/uy1 to maintain aspect ratio */
+		ux2 = ux1 + (png->width * (uy2 - uy1)) / png->height;
+	} else if (uy2 == 0) {
+		/* Set uy2 from ux2/ux1 to maintain aspect ratio */
+		uy2 = uy1 + (png->height * (ux2 - ux1)) / png->width;
+	}
+
+	if (ux2 > gfx_state.tg_fb.fb_width ||
+	    uy2 > gfx_state.tg_fb.fb_height) {
+		if (trace)
+			printf("Bottom right coordinate off screen.\n");
+		return (1);
+	}
+
+	fwidth = ux2 - ux1;
+	fheight = uy2 - uy1;
+
+	/*
+	 * If the original image dimensions have been passed explicitly,
+	 * disable scaling.
+	 */
+	if (fwidth == png->width && fheight == png->height)
+		scale = false;
+
+	if (ux1 == 0) {
+		/*
+		 * No top left X co-ordinate (real coordinates start at 1),
+		 * place as far right as it will fit.
+		 */
+		ux2 = gfx_state.tg_fb.fb_width - gfx_state.tg_origin.tp_col;
+		ux1 = ux2 - fwidth;
+	}
+
+	if (uy1 == 0) {
+		/*
+		 * No top left Y co-ordinate (real coordinates start at 1),
+		 * place as far down as it will fit.
+		 */
+		uy2 = gfx_state.tg_fb.fb_height - gfx_state.tg_origin.tp_row;
+		uy1 = uy2 - fheight;
+	}
+
+	if (ux1 >= ux2 || uy1 >= uy2) {
+		if (trace)
+			printf("Image dimensions reversed.\n");
+		return (1);
+	}
+
+	if (fwidth < 2 || fheight < 2) {
+		if (trace)
+			printf("Target area too small\n");
+		return (1);
+	}
+
+	if (trace)
+		printf("Image %ux%u -> %ux%u @%ux%u\n",
+		    png->width, png->height, fwidth, fheight, ux1, uy1);
+
+	rect.tr_begin.tp_col = ux1 / gfx_state.tg_font.vf_width;
+	rect.tr_begin.tp_row = uy1 / gfx_state.tg_font.vf_height;
+	rect.tr_end.tp_col = (ux1 + fwidth) / gfx_state.tg_font.vf_width;
+	rect.tr_end.tp_row = (uy1 + fheight) / gfx_state.tg_font.vf_height;
+
+	/*
+	 * mark area used in terminal
+	 */
+	if (!(flags & FL_PUTIMAGE_NOSCROLL))
+		term_image_display(&gfx_state, &rect);
+
+	if ((flags & FL_PUTIMAGE_BORDER))
+		gfx_fb_drawrect(ux1, uy1, ux2, uy2, 0);
+
+	data = malloc(fwidth * fheight * sizeof(*p));
+	p = (void *)data;
+	if (data == NULL) {
+		if (trace)
+			printf("Out of memory.\n");
+		return (1);
+	}
+
+	/*
+	 * Build image for our framebuffer.
+	 */
+
+	/* Helper to calculate the pixel index from the source png */
+#define	GETPIXEL(xx, yy)	(((yy) * png->width + (xx)) * png->bpp)
+
+	/*
+	 * For each of the x and y directions, calculate the number of pixels
+	 * in the source image that correspond to a single pixel in the target.
+	 * Use fixed-point arithmetic with 16-bits for each of the integer and
+	 * fractional parts.
+	 */
+	const uint32_t wcstep = ((png->width - 1) << 16) / (fwidth - 1);
+	const uint32_t hcstep = ((png->height - 1) << 16) / (fheight - 1);
+
+	rs = 8 - (fls(gfx_state.tg_fb.fb_mask_red) -
+	    ffs(gfx_state.tg_fb.fb_mask_red) + 1);
+	gs = 8 - (fls(gfx_state.tg_fb.fb_mask_green) -
+	    ffs(gfx_state.tg_fb.fb_mask_green) + 1);
+	bs = 8 - (fls(gfx_state.tg_fb.fb_mask_blue) -
+	    ffs(gfx_state.tg_fb.fb_mask_blue) + 1);
+
+	uint32_t hc = 0;
+	for (y = 0; y < fheight; y++) {
+		uint32_t hc2 = (hc >> 9) & 0x7f;
+		uint32_t hc1 = 0x80 - hc2;
+
+		uint32_t offset_y = hc >> 16;
+		uint32_t offset_y1 = offset_y + 1;
+
+		uint32_t wc = 0;
+		for (x = 0; x < fwidth; x++) {
+			uint32_t wc2 = (wc >> 9) & 0x7f;
+			uint32_t wc1 = 0x80 - wc2;
+
+			uint32_t offset_x = wc >> 16;
+			uint32_t offset_x1 = offset_x + 1;
+
+			/* Target pixel index */
+			j = y * fwidth + x;
+
+			if (!scale) {
+				i = GETPIXEL(x, y);
+				r = png->image[i];
+				g = png->image[i + 1];
+				b = png->image[i + 2];
+				a = png->image[i + 3];
+			} else {
+				uint8_t pixel[4];
+
+				uint32_t p00 = GETPIXEL(offset_x, offset_y);
+				uint32_t p01 = GETPIXEL(offset_x, offset_y1);
+				uint32_t p10 = GETPIXEL(offset_x1, offset_y);
+				uint32_t p11 = GETPIXEL(offset_x1, offset_y1);
+
+				/*
+				 * Given a 2x2 array of pixels in the source
+				 * image, combine them to produce a single
+				 * value for the pixel in the target image.
+				 * Each column of pixels is combined using
+				 * a weighted average where the top and bottom
+				 * pixels contribute hc1 and hc2 respectively.
+				 * The calculation for bottom pixel pB and
+				 * top pixel pT is:
+				 *   (pT * hc1 + pB * hc2) / (hc1 + hc2)
+				 * Once the values are determined for the two
+				 * columns of pixels, then the columns are
+				 * averaged together in the same way but using
+				 * wc1 and wc2 for the weightings.
+				 *
+				 * Since hc1 and hc2 are chosen so that
+				 * hc1 + hc2 == 128 (and same for wc1 + wc2),
+				 * the >> 14 below is a quick way to divide by
+				 * (hc1 + hc2) * (wc1 + wc2)
+				 */
+				for (i = 0; i < 4; i++)
+					pixel[i] = (
+					    (png->image[p00 + i] * hc1 +
+					    png->image[p01 + i] * hc2) * wc1 +
+					    (png->image[p10 + i] * hc1 +
+					    png->image[p11 + i] * hc2) * wc2)
+					    >> 14;
+
+				r = pixel[0];
+				g = pixel[1];
+				b = pixel[2];
+				a = pixel[3];
+			}
+
+			if (trace)
+				printf("r/g/b: %x/%x/%x\n", r, g, b);
+			/*
+			 * Rough colorspace reduction for 15/16 bit colors.
+			 */
+			p[j].Red = r >> rs;
+                        p[j].Green = g >> gs;
+                        p[j].Blue = b >> bs;
+                        p[j].Reserved = a;
+
+			wc += wcstep;
+		}
+		hc += hcstep;
+	}
+
+	gfx_fb_cons_display(ux1, uy1, fwidth, fheight, data);
+	free(data);
+	return (0);
+}
+
+/*
+ * Reset font flags to FONT_AUTO.
+ */
+void
+reset_font_flags(void)
+{
+	struct fontlist *fl;
+
+	STAILQ_FOREACH(fl, &fonts, font_next) {
+		fl->font_flags = FONT_AUTO;
+	}
+}
+
+static vt_font_bitmap_data_t *
+set_font(teken_unit_t *rows, teken_unit_t *cols, teken_unit_t h, teken_unit_t w)
+{
+	vt_font_bitmap_data_t *font = NULL;
+	struct fontlist *fl;
+	unsigned height = h;
+	unsigned width = w;
+
+	/*
+	 * First check for manually loaded font.
+	 */
+	STAILQ_FOREACH(fl, &fonts, font_next) {
+		if (fl->font_flags == FONT_MANUAL) {
+			font = fl->font_data;
+			if (font->vfbd_font == NULL && fl->font_load != NULL &&
+			    fl->font_name != NULL) {
+				font = fl->font_load(fl->font_name);
+			}
+			if (font == NULL || font->vfbd_font == NULL)
+				font = NULL;
+			break;
+		}
+	}
+
+	if (font != NULL) {
+		*rows = (height - BORDER_PIXELS) / font->vfbd_height;
+		*cols = (width - BORDER_PIXELS) / font->vfbd_width;
+		return (font);
+	}
+
+	/*
+	 * Find best font for these dimensions, or use default
+	 *
+	 * A 1 pixel border is the absolute minimum we could have
+	 * as a border around the text window (BORDER_PIXELS = 2),
+	 * however a slightly larger border not only looks better
+	 * but for the fonts currently statically built into the
+	 * emulator causes much better font selection for the
+	 * normal range of screen resolutions.
+	 */
+	STAILQ_FOREACH(fl, &fonts, font_next) {
+		font = fl->font_data;
+		if ((((*rows * font->vfbd_height) + BORDER_PIXELS) <= height) &&
+		    (((*cols * font->vfbd_width) + BORDER_PIXELS) <= width)) {
+			if (font->vfbd_font == NULL ||
+			    fl->font_flags == FONT_RELOAD) {
+				if (fl->font_load != NULL &&
+				    fl->font_name != NULL) {
+					font = fl->font_load(fl->font_name);
+				}
+				if (font == NULL)
+					continue;
+			}
+			*rows = (height - BORDER_PIXELS) / font->vfbd_height;
+			*cols = (width - BORDER_PIXELS) / font->vfbd_width;
+			break;
+		}
+		font = NULL;
+	}
+
+	if (font == NULL) {
+		/*
+		 * We have fonts sorted smallest last, try it before
+		 * falling back to builtin.
+		 */
+		fl = STAILQ_LAST(&fonts, fontlist, font_next);
+		if (fl != NULL && fl->font_load != NULL &&
+		    fl->font_name != NULL) {
+			font = fl->font_load(fl->font_name);
+		}
+		if (font == NULL)
+			font = &DEFAULT_FONT_DATA;
+
+		*rows = (height - BORDER_PIXELS) / font->vfbd_height;
+		*cols = (width - BORDER_PIXELS) / font->vfbd_width;
+	}
+
+	return (font);
+}
+
+static void
+cons_clear(void)
+{
+	char clear[] = { '\033', 'c' };
+
+	/* Reset terminal */
+	teken_input(&gfx_state.tg_teken, clear, sizeof(clear));
+	gfx_state.tg_functions->tf_param(&gfx_state, TP_SHOWCURSOR, 0);
+}
+
+void
+setup_font(teken_gfx_t *state, teken_unit_t height, teken_unit_t width)
+{
+	vt_font_bitmap_data_t *font_data;
+	teken_pos_t *tp = &state->tg_tp;
+	char env[8];
+	int i;
+
+	/*
+	 * set_font() will select a appropriate sized font for
+	 * the number of rows and columns selected.  If we don't
+	 * have a font that will fit, then it will use the
+	 * default builtin font and adjust the rows and columns
+	 * to fit on the screen.
+	 */
+	font_data = set_font(&tp->tp_row, &tp->tp_col, height, width);
+
+        if (font_data == NULL)
+		panic("out of memory");
+
+	for (i = 0; i < VFNT_MAPS; i++) {
+		state->tg_font.vf_map[i] =
+		    font_data->vfbd_font->vf_map[i];
+		state->tg_font.vf_map_count[i] =
+		    font_data->vfbd_font->vf_map_count[i];
+	}
+
+	state->tg_font.vf_bytes = font_data->vfbd_font->vf_bytes;
+	state->tg_font.vf_height = font_data->vfbd_font->vf_height;
+	state->tg_font.vf_width = font_data->vfbd_font->vf_width;
+
+	snprintf(env, sizeof (env), "%ux%u",
+	    state->tg_font.vf_width, state->tg_font.vf_height);
+	env_setenv("screen.font", EV_VOLATILE | EV_NOHOOK,
+	    env, font_set, env_nounset);
+}
+
+/* Binary search for the glyph. Return 0 if not found. */
+static uint16_t
+font_bisearch(const vfnt_map_t *map, uint32_t len, teken_char_t src)
+{
+	unsigned min, mid, max;
+
+	min = 0;
+	max = len - 1;
+
+	/* Empty font map. */
+	if (len == 0)
+		return (0);
+	/* Character below minimal entry. */
+	if (src < map[0].vfm_src)
+		return (0);
+	/* Optimization: ASCII characters occur very often. */
+	if (src <= map[0].vfm_src + map[0].vfm_len)
+		return (src - map[0].vfm_src + map[0].vfm_dst);
+	/* Character above maximum entry. */
+	if (src > map[max].vfm_src + map[max].vfm_len)
+		return (0);
+
+	/* Binary search. */
+	while (max >= min) {
+		mid = (min + max) / 2;
+		if (src < map[mid].vfm_src)
+			max = mid - 1;
+		else if (src > map[mid].vfm_src + map[mid].vfm_len)
+			min = mid + 1;
+		else
+			return (src - map[mid].vfm_src + map[mid].vfm_dst);
+	}
+
+	return (0);
+}
+
+/*
+ * Return glyph bitmap. If glyph is not found, we will return bitmap
+ * for the first (offset 0) glyph.
+ */
+uint8_t *
+font_lookup(const struct vt_font *vf, teken_char_t c, const teken_attr_t *a)
+{
+	uint16_t dst;
+	size_t stride;
+
+	/* Substitute bold with normal if not found. */
+	if (a->ta_format & TF_BOLD) {
+		dst = font_bisearch(vf->vf_map[VFNT_MAP_BOLD],
+		    vf->vf_map_count[VFNT_MAP_BOLD], c);
+		if (dst != 0)
+			goto found;
+	}
+	dst = font_bisearch(vf->vf_map[VFNT_MAP_NORMAL],
+	    vf->vf_map_count[VFNT_MAP_NORMAL], c);
+
+found:
+	stride = howmany(vf->vf_width, 8) * vf->vf_height;
+	return (&vf->vf_bytes[dst * stride]);
+}
+
+static int
+load_mapping(int fd, struct vt_font *fp, int n)
+{
+	size_t i, size;
+	ssize_t rv;
+	vfnt_map_t *mp;
+
+	if (fp->vf_map_count[n] == 0)
+		return (0);
+
+	size = fp->vf_map_count[n] * sizeof(*mp);
+	mp = malloc(size);
+	if (mp == NULL)
+		return (ENOMEM);
+	fp->vf_map[n] = mp;
+
+	rv = read(fd, mp, size);
+	if (rv < 0 || (size_t)rv != size) {
+		free(fp->vf_map[n]);
+		fp->vf_map[n] = NULL;
+		return (EIO);
+	}
+
+	for (i = 0; i < fp->vf_map_count[n]; i++) {
+		mp[i].vfm_src = be32toh(mp[i].vfm_src);
+		mp[i].vfm_dst = be16toh(mp[i].vfm_dst);
+		mp[i].vfm_len = be16toh(mp[i].vfm_len);
+	}
+	return (0);
+}
+
+static int
+builtin_mapping(struct vt_font *fp, int n)
+{
+	size_t size;
+	struct vfnt_map *mp;
+
+	if (n >= VFNT_MAPS)
+		return (EINVAL);
+
+	if (fp->vf_map_count[n] == 0)
+		return (0);
+
+	size = fp->vf_map_count[n] * sizeof(*mp);
+	mp = malloc(size);
+	if (mp == NULL)
+		return (ENOMEM);
+	fp->vf_map[n] = mp;
+
+	memcpy(mp, DEFAULT_FONT_DATA.vfbd_font->vf_map[n], size);
+	return (0);
+}
+
+/*
+ * Load font from builtin or from file.
+ * We do need special case for builtin because the builtin font glyphs
+ * are compressed and we do need to uncompress them.
+ * Having single load_font() for both cases will help us to simplify
+ * font switch handling.
+ */
+static vt_font_bitmap_data_t *
+load_font(char *path)
+{
+	int fd, i;
+	uint32_t glyphs;
+	struct font_header fh;
+	struct fontlist *fl;
+	vt_font_bitmap_data_t *bp;
+	struct vt_font *fp;
+	size_t size;
+	ssize_t rv;
+
+	/* Get our entry from the font list. */
+	STAILQ_FOREACH(fl, &fonts, font_next) {
+		if (strcmp(fl->font_name, path) == 0)
+			break;
+	}
+	if (fl == NULL)
+		return (NULL);	/* Should not happen. */
+
+	bp = fl->font_data;
+	if (bp->vfbd_font != NULL && fl->font_flags != FONT_RELOAD)
+		return (bp);
+
+	fd = -1;
+	/*
+	 * Special case for builtin font.
+	 * Builtin font is the very first font we load, we do not have
+	 * previous loads to be released.
+	 */
+	if (fl->font_flags == FONT_BUILTIN) {
+		if ((fp = calloc(1, sizeof(struct vt_font))) == NULL)
+			return (NULL);
+
+		fp->vf_width = DEFAULT_FONT_DATA.vfbd_width;
+		fp->vf_height = DEFAULT_FONT_DATA.vfbd_height;
+
+		fp->vf_bytes = malloc(DEFAULT_FONT_DATA.vfbd_uncompressed_size);
+		if (fp->vf_bytes == NULL) {
+			free(fp);
+			return (NULL);
+		}
+
+		bp->vfbd_uncompressed_size =
+		    DEFAULT_FONT_DATA.vfbd_uncompressed_size;
+		bp->vfbd_compressed_size =
+		    DEFAULT_FONT_DATA.vfbd_compressed_size;
+
+		if (lz4_decompress(DEFAULT_FONT_DATA.vfbd_compressed_data,
+		    fp->vf_bytes,
+		    DEFAULT_FONT_DATA.vfbd_compressed_size,
+		    DEFAULT_FONT_DATA.vfbd_uncompressed_size, 0) != 0) {
+			free(fp->vf_bytes);
+			free(fp);
+			return (NULL);
+		}
+
+		for (i = 0; i < VFNT_MAPS; i++) {
+			fp->vf_map_count[i] =
+			    DEFAULT_FONT_DATA.vfbd_font->vf_map_count[i];
+			if (builtin_mapping(fp, i) != 0)
+				goto free_done;
+		}
+
+		bp->vfbd_font = fp;
+		return (bp);
+	}
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		return (NULL);
+
+	size = sizeof(fh);
+	rv = read(fd, &fh, size);
+	if (rv < 0 || (size_t)rv != size) {
+		bp = NULL;
+		goto done;
+	}
+	if (memcmp(fh.fh_magic, FONT_HEADER_MAGIC, sizeof(fh.fh_magic)) != 0) {
+		bp = NULL;
+		goto done;
+	}
+	if ((fp = calloc(1, sizeof(struct vt_font))) == NULL) {
+		bp = NULL;
+		goto done;
+	}
+	for (i = 0; i < VFNT_MAPS; i++)
+		fp->vf_map_count[i] = be32toh(fh.fh_map_count[i]);
+
+	glyphs = be32toh(fh.fh_glyph_count);
+	fp->vf_width = fh.fh_width;
+	fp->vf_height = fh.fh_height;
+
+	size = howmany(fp->vf_width, 8) * fp->vf_height * glyphs;
+	bp->vfbd_uncompressed_size = size;
+	if ((fp->vf_bytes = malloc(size)) == NULL)
+		goto free_done;
+
+	rv = read(fd, fp->vf_bytes, size);
+	if (rv < 0 || (size_t)rv != size)
+		goto free_done;
+	for (i = 0; i < VFNT_MAPS; i++) {
+		if (load_mapping(fd, fp, i) != 0)
+			goto free_done;
+	}
+
+	/*
+	 * Reset builtin flag now as we have full font loaded.
+	 */
+	if (fl->font_flags == FONT_BUILTIN)
+		fl->font_flags = FONT_AUTO;
+
+	/*
+	 * Release previously loaded entries. We can do this now, as
+	 * the new font is loaded. Note, there can be no console
+	 * output till the new font is in place and teken is notified.
+	 * We do need to keep fl->font_data for glyph dimensions.
+	 */
+	STAILQ_FOREACH(fl, &fonts, font_next) {
+		if (fl->font_data->vfbd_font == NULL)
+			continue;
+
+		for (i = 0; i < VFNT_MAPS; i++)
+			free(fl->font_data->vfbd_font->vf_map[i]);
+		free(fl->font_data->vfbd_font->vf_bytes);
+		free(fl->font_data->vfbd_font);
+		fl->font_data->vfbd_font = NULL;
+	}
+
+	bp->vfbd_font = fp;
+	bp->vfbd_compressed_size = 0;
+
+done:
+	if (fd != -1)
+		close(fd);
+	return (bp);
+
+free_done:
+	for (i = 0; i < VFNT_MAPS; i++)
+		free(fp->vf_map[i]);
+	free(fp->vf_bytes);
+	free(fp);
+	bp = NULL;
+	goto done;
+}
+
+struct name_entry {
+	char			*n_name;
+	SLIST_ENTRY(name_entry)	n_entry;
+};
+
+SLIST_HEAD(name_list, name_entry);
+
+/* Read font names from index file. */
+static struct name_list *
+read_list(char *fonts)
+{
+	struct name_list *nl;
+	struct name_entry *np;
+	char *dir, *ptr;
+	char buf[PATH_MAX];
+	int fd, len;
+
+	dir = strdup(fonts);
+	if (dir == NULL)
+		return (NULL);
+
+	ptr = strrchr(dir, '/');
+	*ptr = '\0';
+
+	fd = open(fonts, O_RDONLY);
+	if (fd < 0)
+		return (NULL);
+
+	nl = malloc(sizeof(*nl));
+	if (nl == NULL) {
+		close(fd);
+		return (nl);
+	}
+
+	SLIST_INIT(nl);
+	while ((len = fgetstr(buf, sizeof (buf), fd)) >= 0) {
+		if (*buf == '#' || *buf == '\0')
+			continue;
+
+		if (bcmp(buf, "MENU", 4) == 0)
+			continue;
+
+		if (bcmp(buf, "FONT", 4) == 0)
+			continue;
+
+		ptr = strchr(buf, ':');
+		if (ptr == NULL)
+			continue;
+		else
+			*ptr = '\0';
+
+		np = malloc(sizeof(*np));
+		if (np == NULL) {
+			close(fd);
+			return (nl);	/* return what we have */
+		}
+		if (asprintf(&np->n_name, "%s/%s", dir, buf) < 0) {
+			free(np);
+			close(fd);
+			return (nl);    /* return what we have */
+		}
+		SLIST_INSERT_HEAD(nl, np, n_entry);
+	}
+	close(fd);
+	return (nl);
+}
+
+/*
+ * Read the font properties and insert new entry into the list.
+ * The font list is built in descending order.
+ */
+static bool
+insert_font(char *name, FONT_FLAGS flags)
+{
+	struct font_header fh;
+	struct fontlist *fp, *previous, *entry, *next;
+	size_t size;
+	ssize_t rv;
+	int fd;
+	char *font_name;
+
+	font_name = NULL;
+	if (flags == FONT_BUILTIN) {
+		/*
+		 * We only install builtin font once, while setting up
+		 * initial console. Since this will happen very early,
+		 * we assume asprintf will not fail. Once we have access to
+		 * files, the builtin font will be replaced by font loaded
+		 * from file.
+		 */
+		if (!STAILQ_EMPTY(&fonts))
+			return (false);
+
+		fh.fh_width = DEFAULT_FONT_DATA.vfbd_width;
+		fh.fh_height = DEFAULT_FONT_DATA.vfbd_height;
+
+		(void) asprintf(&font_name, "%dx%d",
+		    DEFAULT_FONT_DATA.vfbd_width,
+		    DEFAULT_FONT_DATA.vfbd_height);
+	} else {
+		fd = open(name, O_RDONLY);
+		if (fd < 0)
+			return (false);
+		rv = read(fd, &fh, sizeof(fh));
+		close(fd);
+		if (rv < 0 || (size_t)rv != sizeof(fh))
+			return (false);
+
+		if (memcmp(fh.fh_magic, FONT_HEADER_MAGIC,
+		    sizeof(fh.fh_magic)) != 0)
+			return (false);
+		font_name = strdup(name);
+	}
+
+	if (font_name == NULL)
+		return (false);
+
+	/*
+	 * If we have an entry with the same glyph dimensions, replace
+	 * the file name and mark us. We only support unique dimensions.
+	 */
+	STAILQ_FOREACH(entry, &fonts, font_next) {
+		if (fh.fh_width == entry->font_data->vfbd_width &&
+		    fh.fh_height == entry->font_data->vfbd_height) {
+			free(entry->font_name);
+			entry->font_name = font_name;
+			entry->font_flags = FONT_RELOAD;
+			return (true);
+		}
+	}
+
+	fp = calloc(sizeof(*fp), 1);
+	if (fp == NULL) {
+		free(font_name);
+		return (false);
+	}
+	fp->font_data = calloc(sizeof(*fp->font_data), 1);
+	if (fp->font_data == NULL) {
+		free(font_name);
+		free(fp);
+		return (false);
+	}
+	fp->font_name = font_name;
+	fp->font_flags = flags;
+	fp->font_load = load_font;
+	fp->font_data->vfbd_width = fh.fh_width;
+	fp->font_data->vfbd_height = fh.fh_height;
+
+	if (STAILQ_EMPTY(&fonts)) {
+		STAILQ_INSERT_HEAD(&fonts, fp, font_next);
+		return (true);
+	}
+
+	previous = NULL;
+	size = fp->font_data->vfbd_width * fp->font_data->vfbd_height;
+
+	STAILQ_FOREACH(entry, &fonts, font_next) {
+		vt_font_bitmap_data_t *bd;
+
+		bd = entry->font_data;
+		/* Should fp be inserted before the entry? */
+		if (size > bd->vfbd_width * bd->vfbd_height) {
+			if (previous == NULL) {
+				STAILQ_INSERT_HEAD(&fonts, fp, font_next);
+			} else {
+				STAILQ_INSERT_AFTER(&fonts, previous, fp,
+				    font_next);
+			}
+			return (true);
+		}
+		next = STAILQ_NEXT(entry, font_next);
+		if (next == NULL ||
+		    size > next->font_data->vfbd_width *
+		    next->font_data->vfbd_height) {
+			STAILQ_INSERT_AFTER(&fonts, entry, fp, font_next);
+			return (true);
+		}
+		previous = entry;
+	}
+	return (true);
+}
+
+static int
+font_set(struct env_var *ev __unused, int flags __unused, const void *value)
+{
+	struct fontlist *fl;
+	char *eptr;
+	unsigned long x = 0, y = 0;
+
+	/*
+	 * Attempt to extract values from "XxY" string. In case of error,
+	 * we have unmaching glyph dimensions and will just output the
+	 * available values.
+	 */
+	if (value != NULL) {
+		x = strtoul(value, &eptr, 10);
+		if (*eptr == 'x')
+			y = strtoul(eptr + 1, &eptr, 10);
+	}
+	STAILQ_FOREACH(fl, &fonts, font_next) {
+		if (fl->font_data->vfbd_width == x &&
+		    fl->font_data->vfbd_height == y)
+			break;
+	}
+	if (fl != NULL) {
+		/* Reset any FONT_MANUAL flag. */
+		reset_font_flags();
+
+		/* Mark this font manually loaded */
+		fl->font_flags = FONT_MANUAL;
+		cons_update_mode(gfx_state.tg_fb_type != FB_TEXT);
+		return (CMD_OK);
+	}
+
+	printf("Available fonts:\n");
+	STAILQ_FOREACH(fl, &fonts, font_next) {
+		printf("    %dx%d\n", fl->font_data->vfbd_width,
+		    fl->font_data->vfbd_height);
+	}
+	return (CMD_OK);
+}
+
+void
+bios_text_font(bool use_vga_font)
+{
+	if (use_vga_font)
+		(void) insert_font(VGA_8X16_FONT, FONT_MANUAL);
+	else
+		(void) insert_font(DEFAULT_8X16_FONT, FONT_MANUAL);
+}
+
+void
+autoload_font(bool bios)
+{
+	struct name_list *nl;
+	struct name_entry *np;
+
+	nl = read_list("/boot/fonts/INDEX.fonts");
+	if (nl == NULL)
+		return;
+
+	while (!SLIST_EMPTY(nl)) {
+		np = SLIST_FIRST(nl);
+		SLIST_REMOVE_HEAD(nl, n_entry);
+		if (insert_font(np->n_name, FONT_AUTO) == false)
+			printf("failed to add font: %s\n", np->n_name);
+		free(np->n_name);
+		free(np);
+	}
+
+	/*
+	 * If vga text mode was requested, load vga.font (8x16 bold) font.
+	 */
+	if (bios) {
+		bios_text_font(true);
+	}
+
+	(void) cons_update_mode(gfx_state.tg_fb_type != FB_TEXT);
+}
+
+COMMAND_SET(load_font, "loadfont", "load console font from file", command_font);
+
+static int
+command_font(int argc, char *argv[])
+{
+	int i, c, rc;
+	struct fontlist *fl;
+	vt_font_bitmap_data_t *bd;
+	bool list;
+
+	list = false;
+	optind = 1;
+	optreset = 1;
+	rc = CMD_OK;
+
+	while ((c = getopt(argc, argv, "l")) != -1) {
+		switch (c) {
+		case 'l':
+			list = true;
+			break;
+		case '?':
+		default:
+			return (CMD_ERROR);
+		}
+	}
+
+	argc -= optind;
+	argv += optind;
+
+	if (argc > 1 || (list && argc != 0)) {
+		printf("Usage: loadfont [-l] | [file.fnt]\n");
+		return (CMD_ERROR);
+	}
+
+	if (list) {
+		STAILQ_FOREACH(fl, &fonts, font_next) {
+			printf("font %s: %dx%d%s\n", fl->font_name,
+			    fl->font_data->vfbd_width,
+			    fl->font_data->vfbd_height,
+			    fl->font_data->vfbd_font == NULL? "" : " loaded");
+		}
+		return (CMD_OK);
+	}
+
+	/* Clear scren */
+	cons_clear();
+
+	if (argc == 1) {
+		char *name = argv[0];
+
+		if (insert_font(name, FONT_MANUAL) == false) {
+			printf("loadfont error: failed to load: %s\n", name);
+			return (CMD_ERROR);
+		}
+
+		(void) cons_update_mode(gfx_state.tg_fb_type != FB_TEXT);
+		return (CMD_OK);
+	}
+
+	if (argc == 0) {
+		/*
+		 * Walk entire font list, release any loaded font, and set
+		 * autoload flag. The font list does have at least the builtin
+		 * default font.
+		 */
+		STAILQ_FOREACH(fl, &fonts, font_next) {
+			if (fl->font_data->vfbd_font != NULL) {
+
+				bd = fl->font_data;
+				/*
+				 * Note the setup_font() is releasing
+				 * font bytes.
+				 */
+				for (i = 0; i < VFNT_MAPS; i++)
+					free(bd->vfbd_font->vf_map[i]);
+				free(fl->font_data->vfbd_font);
+				fl->font_data->vfbd_font = NULL;
+				fl->font_data->vfbd_uncompressed_size = 0;
+				fl->font_flags = FONT_AUTO;
+			}
+		}
+		(void) cons_update_mode(gfx_state.tg_fb_type != FB_TEXT);
+	}
+	return (rc);
+}
+
+bool
+gfx_get_edid_resolution(struct vesa_edid_info *edid, edid_res_list_t *res)
+{
+	struct resolution *rp, *p;
+
+	/*
+	 * Walk detailed timings tables (4).
+	 */
+	if ((edid->display.supported_features
+	    & EDID_FEATURE_PREFERRED_TIMING_MODE) != 0) {
+		/* Walk detailed timing descriptors (4) */
+		for (int i = 0; i < DET_TIMINGS; i++) {
+			/*
+			 * Reserved value 0 is not used for display decriptor.
+			 */
+			if (edid->detailed_timings[i].pixel_clock == 0)
+				continue;
+			if ((rp = malloc(sizeof(*rp))) == NULL)
+				continue;
+			rp->width = GET_EDID_INFO_WIDTH(edid, i);
+			rp->height = GET_EDID_INFO_HEIGHT(edid, i);
+			if (rp->width > 0 && rp->width <= EDID_MAX_PIXELS &&
+			    rp->height > 0 && rp->height <= EDID_MAX_LINES)
+				TAILQ_INSERT_TAIL(res, rp, next);
+			else
+				free(rp);
+		}
+	}
+
+	/*
+	 * Walk standard timings list (8).
+	 */
+	for (int i = 0; i < STD_TIMINGS; i++) {
+		/* Is this field unused? */
+		if (edid->standard_timings[i] == 0x0101)
+			continue;
+
+		if ((rp = malloc(sizeof(*rp))) == NULL)
+			continue;
+
+		rp->width = HSIZE(edid->standard_timings[i]);
+		switch (RATIO(edid->standard_timings[i])) {
+		case RATIO1_1:
+			rp->height = HSIZE(edid->standard_timings[i]);
+			if (edid->header.version > 1 ||
+			    edid->header.revision > 2) {
+				rp->height = rp->height * 10 / 16;
+			}
+			break;
+		case RATIO4_3:
+			rp->height = HSIZE(edid->standard_timings[i]) * 3 / 4;
+			break;
+		case RATIO5_4:
+			rp->height = HSIZE(edid->standard_timings[i]) * 4 / 5;
+			break;
+		case RATIO16_9:
+			rp->height = HSIZE(edid->standard_timings[i]) * 9 / 16;
+			break;
+		}
+
+		/*
+		 * Create resolution list in decreasing order, except keep
+		 * first entry (preferred timing mode).
+		 */
+		TAILQ_FOREACH(p, res, next) {
+			if (p->width * p->height < rp->width * rp->height) {
+				/* Keep preferred mode first */
+				if (TAILQ_FIRST(res) == p)
+					TAILQ_INSERT_AFTER(res, p, rp, next);
+				else
+					TAILQ_INSERT_BEFORE(p, rp, next);
+				break;
+			}
+			if (TAILQ_NEXT(p, next) == NULL) {
+				TAILQ_INSERT_TAIL(res, rp, next);
+				break;
+			}
+		}
+	}
+	return (!TAILQ_EMPTY(res));
+}
diff --git a/stand/common/gfx_fb.h b/stand/common/gfx_fb.h
new file mode 100644
index 000000000000..1424b8223136
--- /dev/null
+++ b/stand/common/gfx_fb.h
@@ -0,0 +1,274 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2020 Toomas Soome
+ * Copyright 2020 RackTop Systems, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _GFX_FB_H
+#define	_GFX_FB_H
+
+#include <sys/font.h>
+#include <teken.h>
+#include <stdbool.h>
+#include <machine/metadata.h>
+#include <pnglite.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define	EDID_MAGIC	{ 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 }
+
+struct edid_header {
+	uint8_t header[8];	/* fixed header pattern */
+	uint16_t manufacturer_id;
+	uint16_t product_code;
+	uint32_t serial_number;
+	uint8_t week_of_manufacture;
+	uint8_t year_of_manufacture;
+	uint8_t version;
+	uint8_t revision;
+};
+
+struct edid_basic_display_parameters {
+	uint8_t video_input_parameters;
+	uint8_t max_horizontal_image_size;
+	uint8_t max_vertical_image_size;
+	uint8_t display_gamma;
+	uint8_t supported_features;
+};
+
+struct edid_chromaticity_coordinates {
+	uint8_t red_green_lo;
+	uint8_t blue_white_lo;
+	uint8_t red_x_hi;
+	uint8_t red_y_hi;
+	uint8_t green_x_hi;
+	uint8_t green_y_hi;
+	uint8_t blue_x_hi;
+	uint8_t blue_y_hi;
+	uint8_t white_x_hi;
+	uint8_t white_y_hi;
+};
+
+struct edid_detailed_timings {
+	uint16_t pixel_clock;
+	uint8_t horizontal_active_lo;
+	uint8_t horizontal_blanking_lo;
+	uint8_t horizontal_hi;
+	uint8_t vertical_active_lo;
+	uint8_t vertical_blanking_lo;
+	uint8_t vertical_hi;
+	uint8_t horizontal_sync_offset_lo;
+	uint8_t horizontal_sync_pulse_width_lo;
+	uint8_t vertical_sync_lo;
+	uint8_t sync_hi;
+	uint8_t horizontal_image_size_lo;
+	uint8_t vertical_image_size_lo;
+	uint8_t image_size_hi;
+	uint8_t horizontal_border;
+	uint8_t vertical_border;
+	uint8_t features;
+};
+
+struct vesa_edid_info {
+	struct edid_header header;
+	struct edid_basic_display_parameters display;
+#define	EDID_FEATURE_PREFERRED_TIMING_MODE	(1 << 1)
+	struct edid_chromaticity_coordinates chromaticity;
+	uint8_t established_timings_1;
+	uint8_t established_timings_2;
+	uint8_t manufacturer_reserved_timings;
+	uint16_t standard_timings[8];
+	struct edid_detailed_timings detailed_timings[4];
+	uint8_t number_of_extensions;
+	uint8_t checksum;
+} __packed;
+
+#define	STD_TIMINGS	8
+#define	DET_TIMINGS	4
+
+#define	HSIZE(x)	(((x & 0xff) + 31) * 8)
+#define	RATIO(x)	((x & 0xC000) >> 14)
+#define	RATIO1_1	0
+/* EDID Ver. 1.3 redefined this */
+#define	RATIO16_10	RATIO1_1
+#define	RATIO4_3	1
+#define	RATIO5_4	2
+#define	RATIO16_9	3
+
+/*
+ * Number of pixels and lines is 12-bit int, valid values 0-4095.
+ */
+#define	EDID_MAX_PIXELS	4095
+#define	EDID_MAX_LINES	4095
+
+#define	GET_EDID_INFO_WIDTH(edid_info, timings_num) \
+    ((edid_info)->detailed_timings[(timings_num)].horizontal_active_lo | \
+    (((uint32_t)(edid_info)->detailed_timings[(timings_num)].horizontal_hi & \
+    0xf0) << 4))
+
+#define	GET_EDID_INFO_HEIGHT(edid_info, timings_num) \
+    ((edid_info)->detailed_timings[(timings_num)].vertical_active_lo | \
+    (((uint32_t)(edid_info)->detailed_timings[(timings_num)].vertical_hi & \
+    0xf0) << 4))
+
+struct resolution {
+	uint32_t width;
+	uint32_t height;
+	TAILQ_ENTRY(resolution) next;
+};
+
+typedef TAILQ_HEAD(edid_resolution, resolution) edid_res_list_t;
+
+struct vesa_flat_panel_info {
+	uint16_t HSize;			/* Horizontal Size in Pixels */
+	uint16_t VSize;			/* Vertical Size in Lines */
+	uint16_t FPType;		/* Flat Panel Type */
+	uint8_t RedBPP;			/* Red Bits Per Primary */
+	uint8_t GreenBPP;		/* Green Bits Per Primary */
+	uint8_t BlueBPP;		/* Blue Bits Per Primary */
+	uint8_t ReservedBPP;		/* Reserved Bits Per Primary */
+	uint32_t RsvdOffScrnMemSize;	/* Size in KB of Offscreen Memory */
+	uint32_t RsvdOffScrnMemPtr; /* Pointer to reserved offscreen memory */
+	uint8_t Reserved[14];		/* remainder of FPInfo */
+} __packed;
+
+#define	COLOR_FORMAT_VGA 0
+#define	COLOR_FORMAT_RGB 1
+#define	NCOLORS	16
+#define	NCMAP	256
+extern uint32_t cmap[NCMAP];
+
+enum FB_TYPE {
+	FB_TEXT = -1,
+	FB_GOP,
+	FB_UGA,
+	FB_VBE
+};
+
+enum COLOR_TYPE {
+	CT_INDEXED,
+	CT_RGB
+};
+
+struct gen_fb {
+	uint64_t	fb_addr;
+	uint64_t	fb_size;
+	uint32_t	fb_height;
+	uint32_t	fb_width;
+	uint32_t	fb_stride;
+	uint32_t	fb_mask_red;
+	uint32_t	fb_mask_green;
+	uint32_t	fb_mask_blue;
+	uint32_t	fb_mask_reserved;
+	uint32_t	fb_bpp;
+};
+
+typedef struct teken_gfx {
+	enum FB_TYPE	tg_fb_type;
+	enum COLOR_TYPE tg_ctype;
+	unsigned	tg_mode;
+	teken_t		tg_teken;		/* Teken core */
+	teken_pos_t	tg_cursor;		/* Where cursor was drawn */
+	bool		tg_cursor_visible;
+	teken_pos_t	tg_tp;			/* Terminal dimensions */
+	teken_pos_t	tg_origin;		/* Point of origin in pixels */
+	uint8_t		*tg_glyph;		/* Memory for glyph */
+	size_t		tg_glyph_size;
+	struct vt_font	tg_font;
+	struct gen_fb	tg_fb;
+	teken_funcs_t	*tg_functions;
+	void		*tg_private;
+} teken_gfx_t;
+
+extern font_list_t fonts;
+extern teken_gfx_t gfx_state;
+
+typedef enum {
+	GfxFbBltVideoFill,
+	GfxFbBltVideoToBltBuffer,
+	GfxFbBltBufferToVideo,
+	GfxFbBltVideoToVideo,
+	GfxFbBltOperationMax,
+} GFXFB_BLT_OPERATION;
+
+int gfxfb_blt(void *, GFXFB_BLT_OPERATION, uint32_t, uint32_t,
+    uint32_t, uint32_t, uint32_t, uint32_t, uint32_t);
+
+int generate_cons_palette(uint32_t *, int, uint32_t, int, uint32_t, int,
+    uint32_t, int);
+bool console_update_mode(bool);
+void setup_font(teken_gfx_t *, teken_unit_t, teken_unit_t);
+uint8_t *font_lookup(const struct vt_font *, teken_char_t,
+    const teken_attr_t *);
+void bios_text_font(bool);
+
+/* teken callbacks. */
+tf_cursor_t gfx_fb_cursor;
+tf_putchar_t gfx_fb_putchar;
+tf_fill_t gfx_fb_fill;
+tf_copy_t gfx_fb_copy;
+tf_param_t gfx_fb_param;
+
+/* Screen buffer element */
+struct text_pixel {
+	teken_char_t c;
+	teken_attr_t a;
+};
+
+extern const int cons_to_vga_colors[NCOLORS];
+
+/* Screen buffer to track changes on the terminal screen. */
+extern struct text_pixel *screen_buffer;
+bool is_same_pixel(struct text_pixel *, struct text_pixel *);
+
+bool gfx_get_edid_resolution(struct vesa_edid_info *, edid_res_list_t *);
+void gfx_framework_init(void);
+void gfx_fb_cons_display(uint32_t, uint32_t, uint32_t, uint32_t, void *);
+void gfx_fb_setpixel(uint32_t, uint32_t);
+void gfx_fb_drawrect(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t);
+void gfx_term_drawrect(uint32_t, uint32_t, uint32_t, uint32_t);
+void gfx_fb_line(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t);
+void gfx_fb_bezier(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t,
+	uint32_t);
+
+#define	FL_PUTIMAGE_BORDER	0x1
+#define	FL_PUTIMAGE_NOSCROLL	0x2
+#define	FL_PUTIMAGE_DEBUG	0x80
+
+int gfx_fb_putimage(png_t *, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t);
+bool gfx_parse_mode_str(char *, int *, int *, int *);
+void term_image_display(teken_gfx_t *, const teken_rect_t *);
+
+void reset_font_flags(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _GFX_FB_H */
diff --git a/stand/common/module.c b/stand/common/module.c
index bd8d091d91c9..247fc54b6021 100644
--- a/stand/common/module.c
+++ b/stand/common/module.c
@@ -1,1683 +1,1770 @@
 /*-
  * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
 
 #include <sys/cdefs.h>
 __FBSDID("$FreeBSD$");
 
 /*
  * file/module function dispatcher, support, etc.
  */
 
 #include <stand.h>
 #include <string.h>
 #include <sys/param.h>
 #include <sys/linker.h>
 #include <sys/module.h>
 #include <sys/queue.h>
 #include <sys/stdint.h>
+#include <sys/font.h>
+#include <gfx_fb.h>
 
 #if defined(LOADER_FDT_SUPPORT)
 #include <fdt_platform.h>
 #endif
 
 #include "bootstrap.h"
 
 #define	MDIR_REMOVED	0x0001
 #define	MDIR_NOHINTS	0x0002
 
 struct moduledir {
 	char	*d_path;	/* path of modules directory */
 	u_char	*d_hints;	/* content of linker.hints file */
 	int	d_hintsz;	/* size of hints data */
 	int	d_flags;
 	STAILQ_ENTRY(moduledir) d_link;
 };
 
 static int			file_load(char *filename, vm_offset_t dest, struct preloaded_file **result);
 static int			file_load_dependencies(struct preloaded_file *base_mod);
 static char *			file_search(const char *name, char **extlist);
 static struct kernel_module *	file_findmodule(struct preloaded_file *fp, char *modname, struct mod_depend *verinfo);
 static int			file_havepath(const char *name);
 static char			*mod_searchmodule(char *name, struct mod_depend *verinfo);
 static char *			mod_searchmodule_pnpinfo(const char *bus, const char *pnpinfo);
 static void			file_insert_tail(struct preloaded_file *mp);
 static void			file_remove(struct preloaded_file *fp);
 struct file_metadata*		metadata_next(struct file_metadata *base_mp, int type);
 static void			moduledir_readhints(struct moduledir *mdp);
 static void			moduledir_rebuild(void);
 
 /* load address should be tweaked by first module loaded (kernel) */
 static vm_offset_t	loadaddr = 0;
 
 #if defined(LOADER_FDT_SUPPORT)
 static const char	*default_searchpath =
     "/boot/kernel;/boot/modules;/boot/dtb";
 #else
 static const char	*default_searchpath = "/boot/kernel;/boot/modules";
 #endif
 
 static STAILQ_HEAD(, moduledir) moduledir_list =
     STAILQ_HEAD_INITIALIZER(moduledir_list);
 
 struct preloaded_file *preloaded_files = NULL;
 
 static char *kld_ext_list[] = {
     ".ko",
     "",
     ".debug",
     NULL
 };
 
-
 /*
  * load an object, either a disk file or code module.
  *
  * To load a file, the syntax is:
  *
  * load -t <type> <path>
  *
  * code modules are loaded as:
  *
  * load <path> <options>
  */
 
 COMMAND_SET(load, "load", "load a kernel or module", command_load);
 
 static int
 command_load(int argc, char *argv[])
 {
 	struct preloaded_file *fp;
 	char	*typestr;
 	char	*prefix;
 	char	*skip;
 	int		dflag, dofile, dokld, ch, error;
 
 	dflag = dokld = dofile = 0;
 	optind = 1;
 	optreset = 1;
 	typestr = NULL;
 	if (argc == 1) {
 		command_errmsg = "no filename specified";
 		return (CMD_CRIT);
 	}
 	prefix = skip = NULL;
 	while ((ch = getopt(argc, argv, "dkp:s:t:")) != -1) {
 		switch(ch) {
 		case 'd':
 			dflag++;
 			break;
 		case 'k':
 			dokld = 1;
 			break;
 		case 'p':
 			prefix = optarg;
 			break;
 		case 's':
 			skip = optarg;
 			break;
 		case 't':
 			typestr = optarg;
 			dofile = 1;
 			break;
 		case '?':
 		default:
 			/* getopt has already reported an error */
 			return (CMD_OK);
 		}
 	}
 	argv += (optind - 1);
 	argc -= (optind - 1);
 
 	/*
 	 * Request to load a raw file?
 	 */
 	if (dofile) {
 		if ((argc != 2) || (typestr == NULL) || (*typestr == 0)) {
 			command_errmsg = "invalid load type";
 			return (CMD_CRIT);
 		}
 
 #ifdef LOADER_VERIEXEC
 		if (strncmp(typestr, "manifest", 8) == 0) {
 			if (dflag > 0)
 				ve_debug_set(dflag);
 			return (load_manifest(argv[1], prefix, skip, NULL));
 		}
 #ifdef LOADER_VERIEXEC_PASS_MANIFEST
 		if (strncmp(typestr, "pass_manifest", 13) == 0) {
 			if (dflag > 0)
 				ve_debug_set(dflag);
 		    return (pass_manifest(argv[1], prefix));
 		}
 #endif
 #endif
 
 		fp = file_findfile(argv[1], typestr);
 		if (fp) {
 			snprintf(command_errbuf, sizeof(command_errbuf),
 			  "warning: file '%s' already loaded", argv[1]);
 			return (CMD_WARN);
 		}
 
 		if (file_loadraw(argv[1], typestr, 1) != NULL)
 			return (CMD_OK);
 
 		/* Failing to load mfs_root is never going to end well! */
 		if (strcmp("mfs_root", typestr) == 0)
 			return (CMD_FATAL);
 
 		return (CMD_ERROR);
 	}
 	/*
 	 * Do we have explicit KLD load ?
 	 */
 	if (dokld || file_havepath(argv[1])) {
 		error = mod_loadkld(argv[1], argc - 2, argv + 2);
 		if (error == EEXIST) {
 			snprintf(command_errbuf, sizeof(command_errbuf),
 			  "warning: KLD '%s' already loaded", argv[1]);
 			return (CMD_WARN);
 		}
 	
 		return (error == 0 ? CMD_OK : CMD_CRIT);
 	}
 	/*
 	 * Looks like a request for a module.
 	 */
 	error = mod_load(argv[1], NULL, argc - 2, argv + 2);
 	if (error == EEXIST) {
 		snprintf(command_errbuf, sizeof(command_errbuf),
 		  "warning: module '%s' already loaded", argv[1]);
 		return (CMD_WARN);
 	}
 
 	return (error == 0 ? CMD_OK : CMD_CRIT);
 }
 
 #ifdef LOADER_GELI_SUPPORT
 COMMAND_SET(load_geli, "load_geli", "load a geli key", command_load_geli);
 
 static int
 command_load_geli(int argc, char *argv[])
 {
 	char	typestr[80];
 	char	*cp;
 	int		ch, num;
 
 	if (argc < 3) {
 		command_errmsg = "usage is [-n key#] <prov> <file>";
 		return(CMD_ERROR);
 	}
 
 	num = 0;
 	optind = 1;
 	optreset = 1;
 	while ((ch = getopt(argc, argv, "n:")) != -1) {
 		switch(ch) {
 		case 'n':
 			num = strtol(optarg, &cp, 0);
 			if (cp == optarg) {
 				snprintf(command_errbuf, sizeof(command_errbuf),
 				  "bad key index '%s'", optarg);
 				return(CMD_ERROR);
 			}
 			break;
 		case '?':
 		default:
 			/* getopt has already reported an error */
 			return(CMD_OK);
 		}
 	}
 	argv += (optind - 1);
 	argc -= (optind - 1);
 	sprintf(typestr, "%s:geli_keyfile%d", argv[1], num);
 	return (file_loadraw(argv[2], typestr, 1) ? CMD_OK : CMD_ERROR);
 }
 #endif
 
 void
 unload(void)
 {
 	struct preloaded_file *fp;
 
 	while (preloaded_files != NULL) {
 		fp = preloaded_files;
 		preloaded_files = preloaded_files->f_next;
 		file_discard(fp);
 	}
 	loadaddr = 0;
 	unsetenv("kernelname");
 }
 
 COMMAND_SET(unload, "unload", "unload all modules", command_unload);
 
 static int
 command_unload(int argc, char *argv[])
 {
 	unload();
 	return(CMD_OK);
 }
 
 COMMAND_SET(lsmod, "lsmod", "list loaded modules", command_lsmod);
 
 static int
 command_lsmod(int argc, char *argv[])
 {
 	struct preloaded_file	*fp;
 	struct kernel_module	*mp;
 	struct file_metadata	*md;
 	char			lbuf[80];
 	int				ch, verbose, ret = 0;
 
 	verbose = 0;
 	optind = 1;
 	optreset = 1;
 	while ((ch = getopt(argc, argv, "v")) != -1) {
 		switch(ch) {
 		case 'v':
 			verbose = 1;
 			break;
 		case '?':
 		default:
 			/* getopt has already reported an error */
 			return(CMD_OK);
 		}
 	}
 
 	pager_open();
 	for (fp = preloaded_files; fp; fp = fp->f_next) {
 		snprintf(lbuf, sizeof(lbuf), " %p: ", (void *) fp->f_addr);
 		pager_output(lbuf);
 		pager_output(fp->f_name);
 		snprintf(lbuf, sizeof(lbuf), " (%s, 0x%lx)\n", fp->f_type,
 		  (long)fp->f_size);
 		if (pager_output(lbuf))
 			break;
 		if (fp->f_args != NULL) {
 			pager_output("    args: ");
 			pager_output(fp->f_args);
 			if (pager_output("\n"))
 				break;
 		}
 		if (fp->f_modules) {
 			pager_output("  modules: ");
 			for (mp = fp->f_modules; mp; mp = mp->m_next) {
 				snprintf(lbuf, sizeof(lbuf), "%s.%d ", mp->m_name,
 				  mp->m_version);
 				pager_output(lbuf);
 			}
 			if (pager_output("\n"))
 				break;
 		}
 		if (verbose) {
 			/* XXX could add some formatting smarts here to display some better */
 			for (md = fp->f_metadata; md != NULL; md = md->md_next) {
 				snprintf(lbuf, sizeof(lbuf), "      0x%04x, 0x%lx\n",
 				  md->md_type, (long) md->md_size);
 				if (pager_output(lbuf))
 					break;
 			}
 		}
 		if (ret)
 			break;
 	}
 	pager_close();
 	return(CMD_OK);
 }
 
 COMMAND_SET(pnpmatch, "pnpmatch", "list matched modules based on pnpinfo", command_pnpmatch);
 
 static int pnp_dump_flag = 0;
 static int pnp_unbound_flag = 0;
 static int pnp_verbose_flag = 0;
 
 static int
 command_pnpmatch(int argc, char *argv[])
 {
 	char *module;
 	int ch;
 
 	pnp_verbose_flag = 0;
 	pnp_dump_flag = 0;
 	optind = 1;
 	optreset = 1;
 	while ((ch = getopt(argc, argv, "vd")) != -1) {
 		switch(ch) {
 		case 'v':
 			pnp_verbose_flag = 1;
 			break;
 		case 'd':
 			pnp_dump_flag = 1;
 			break;
 		case '?':
 		default:
 			/* getopt has already reported an error */
 			return(CMD_OK);
 		}
 	}
 	argv += (optind - 1);
 	argc -= (optind - 1);
 
 	module = mod_searchmodule_pnpinfo(argv[1], argv[2]);
 	if (module)
 		printf("Matched module: %s\n", module);
 	else if(argv[1])
 		printf("No module matches %s\n", argv[1]);
 
 	return (CMD_OK);
 }
 
 COMMAND_SET(pnpload, "pnpload", "load matched modules based on pnpinfo", command_pnpload);
 
 static int
 command_pnpload(int argc, char *argv[])
 {
 	char *module;
 	int ch, error;
 
 	pnp_verbose_flag = 0;
 	pnp_dump_flag = 0;
 	optind = 1;
 	optreset = 1;
 	while ((ch = getopt(argc, argv, "vd")) != -1) {
 		switch(ch) {
 		case 'v':
 			pnp_verbose_flag = 1;
 			break;
 		case 'd':
 			pnp_dump_flag = 1;
 			break;
 		case '?':
 		default:
 			/* getopt has already reported an error */
 			return(CMD_OK);
 		}
 	}
 	argv += (optind - 1);
 	argc -= (optind - 1);
 
 	if (argc != 2)
 		return (CMD_ERROR);
 
 	module = mod_searchmodule_pnpinfo(argv[1], argv[2]);
 
 	error = mod_load(module, NULL, 0, NULL);
 	if (error == EEXIST) {
 		snprintf(command_errbuf, sizeof(command_errbuf),
 		  "warning: module '%s' already loaded", argv[1]);
 		return (CMD_WARN);
 	}
 
 	return (error == 0 ? CMD_OK : CMD_CRIT);
 }
 
 #if defined(LOADER_FDT_SUPPORT)
 static void
 pnpautoload_simplebus(void) {
 	const char *pnpstring;
 	const char *compatstr;
 	char *pnpinfo = NULL;
 	char *module = NULL;
 	int tag = 0, len, pnplen;
 	int error;
 
 	while (1) {
 		pnpstring = fdt_devmatch_next(&tag, &len);
 		if (pnpstring == NULL)
 			return;
 
 		compatstr = pnpstring;
 		for (pnplen = 0; pnplen != len; compatstr = pnpstring + pnplen) {
 			pnplen += strlen(compatstr) + 1;
 			asprintf(&pnpinfo, "compat=%s", compatstr);
 
 			module = mod_searchmodule_pnpinfo("simplebus", pnpinfo);
 			if (module) {
 				error = mod_loadkld(module, 0, NULL);
 				if (error)
 					printf("Cannot load module %s\n", module);
 				break;
 			}
 		}
 		free(pnpinfo);
 		free(module);
 	}
 }
 #endif
 
 struct pnp_bus {
 	const char *name;
 	void (*load)(void);
 };
 
 struct pnp_bus pnp_buses[] = {
 #if defined(LOADER_FDT_SUPPORT)
 	{"simplebus", pnpautoload_simplebus},
 #endif
 };
 
 COMMAND_SET(pnpautoload, "pnpautoload", "auto load modules based on pnpinfo", command_pnpautoload);
 
 static int
 command_pnpautoload(int argc, char *argv[])
 {
 	int i;
 	int verbose;
 	int ch, match;
 
 	pnp_verbose_flag = 0;
 	pnp_dump_flag = 0;
 	verbose = 0;
 	optind = 1;
 	optreset = 1;
 	match = 0;
 	while ((ch = getopt(argc, argv, "v")) != -1) {
 		switch(ch) {
 		case 'v':
 			verbose = 1;
 			break;
 		case '?':
 		default:
 			/* getopt has already reported an error */
 			return(CMD_OK);
 		}
 	}
 	argv += (optind - 1);
 	argc -= (optind - 1);
 
 	if (argc > 2)
 		return (CMD_ERROR);
 
 	for (i = 0; i < nitems(pnp_buses); i++) {
 		if (argc == 2 && strcmp(argv[1], pnp_buses[i].name) != 0) {
 			if (verbose)
 				printf("Skipping bus %s\n", pnp_buses[i].name);
 			continue;
 		}
 		if (verbose)
 			printf("Autoloading modules for simplebus\n");
 		pnp_buses[i].load();
 		match = 1;
 	}
 	if (match == 0)
 		printf("Unsupported bus %s\n", argv[1]);
 
 	return (CMD_OK);
 }
 
 /*
  * File level interface, functions file_*
  */
 int
 file_load(char *filename, vm_offset_t dest, struct preloaded_file **result)
 {
 	static int last_file_format = 0;
 	struct preloaded_file *fp;
 	int error;
 	int i;
 
 	if (archsw.arch_loadaddr != NULL)
 		dest = archsw.arch_loadaddr(LOAD_RAW, filename, dest);
 
 	error = EFTYPE;
 	for (i = last_file_format, fp = NULL;
 	     file_formats[i] && fp == NULL; i++) {
 		error = (file_formats[i]->l_load)(filename, dest, &fp);
 		if (error == 0) {
 			fp->f_loader = last_file_format = i; /* remember the loader */
 			*result = fp;
 			break;
 		} else if (last_file_format == i && i != 0) {
 			/* Restart from the beginning */
 			i = -1;
 			last_file_format = 0;
 			fp = NULL;
 			continue;
 		}
 		if (error == EFTYPE)
 			continue;		/* Unknown to this handler? */
 		if (error) {
 			snprintf(command_errbuf, sizeof(command_errbuf),
 			  "can't load file '%s': %s", filename, strerror(error));
 			break;
 		}
 	}
 	return (error);
 }
 
 static int
 file_load_dependencies(struct preloaded_file *base_file)
 {
 	struct file_metadata *md;
 	struct preloaded_file *fp;
 	struct mod_depend *verinfo;
 	struct kernel_module *mp;
 	char *dmodname;
 	int error;
 
 	md = file_findmetadata(base_file, MODINFOMD_DEPLIST);
 	if (md == NULL)
 		return (0);
 	error = 0;
 	do {
 		verinfo = (struct mod_depend*)md->md_data;
 		dmodname = (char *)(verinfo + 1);
 		if (file_findmodule(NULL, dmodname, verinfo) == NULL) {
 			printf("loading required module '%s'\n", dmodname);
 			error = mod_load(dmodname, verinfo, 0, NULL);
 			if (error)
 				break;
 			/*
 			 * If module loaded via kld name which isn't listed
 			 * in the linker.hints file, we should check if it have
 			 * required version.
 			 */
 			mp = file_findmodule(NULL, dmodname, verinfo);
 			if (mp == NULL) {
 				snprintf(command_errbuf, sizeof(command_errbuf),
 				  "module '%s' exists but with wrong version", dmodname);
 				error = ENOENT;
 				break;
 			}
 		}
 		md = metadata_next(md, MODINFOMD_DEPLIST);
 	} while (md);
 	if (!error)
 		return (0);
 	/* Load failed; discard everything */
 	while (base_file != NULL) {
 		fp = base_file;
 		base_file = base_file->f_next;
 		file_discard(fp);
 	}
 	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
 #else
 #define VECTX_HANDLE(fd) fd
 #endif
 
 
 /*
  * We've been asked to load (fname) as (type), so just suck it in,
  * no arguments or anything.
  */
 struct preloaded_file *
 file_loadraw(const char *fname, char *type, int insert)
 {
 	struct preloaded_file	*fp;
 	char			*name;
 	int				fd, got;
 	vm_offset_t			laddr;
 #ifdef LOADER_VERIEXEC_VECTX
 	struct vectx		*vctx;
 	int			verror;
 #endif
 
 	/* We can't load first */
 	if ((file_findfile(NULL, NULL)) == NULL) {
 		command_errmsg = "can't load file before kernel";
 		return(NULL);
 	}
 
 	/* locate the file on the load path */
 	name = file_search(fname, NULL);
 	if (name == NULL) {
 		snprintf(command_errbuf, sizeof(command_errbuf),
 		  "can't find '%s'", fname);
 		return(NULL);
 	}
 
 	if ((fd = open(name, O_RDONLY)) < 0) {
 		snprintf(command_errbuf, sizeof(command_errbuf),
 		  "can't open '%s': %s", name, strerror(errno));
 		free(name);
 		return(NULL);
 	}
 
 #ifdef LOADER_VERIEXEC_VECTX
 	vctx = vectx_open(fd, name, 0L, NULL, &verror, __func__);
 	if (verror) {
 		sprintf(command_errbuf, "can't verify '%s': %s",
 		    name, ve_error_get());
 		free(name);
 		free(vctx);
 		close(fd);
 		return(NULL);
 	}
 #else
 #ifdef LOADER_VERIEXEC
 	if (verify_file(fd, name, 0, VE_MUST, __func__) < 0) {
 		sprintf(command_errbuf, "can't verify '%s': %s",
 		    name, ve_error_get());
 		free(name);
 		close(fd);
 		return(NULL);
 	}
 #endif
 #endif
 
 	if (archsw.arch_loadaddr != NULL)
 		loadaddr = archsw.arch_loadaddr(LOAD_RAW, name, loadaddr);
 
 	printf("%s ", name);
 
 	laddr = loadaddr;
 	for (;;) {
 		/* read in 4k chunks; size is not really important */
 		got = archsw.arch_readin(VECTX_HANDLE(fd), laddr, 4096);
 		if (got == 0)				/* end of file */
 			break;
 		if (got < 0) {				/* error */
 			snprintf(command_errbuf, sizeof(command_errbuf),
 			  "error reading '%s': %s", name, strerror(errno));
 			free(name);
 			close(fd);
 #ifdef LOADER_VERIEXEC_VECTX
 			free(vctx);
 #endif
 			return(NULL);
 		}
 		laddr += got;
 	}
 
 	printf("size=%#jx\n", (uintmax_t)(laddr - loadaddr));
 #ifdef LOADER_VERIEXEC_VECTX
 	verror = vectx_close(vctx, VE_MUST, __func__);
 	if (verror) {
 		free(name);
 		close(fd);
 		free(vctx);
 		return(NULL);
 	}
 #endif
 
 	/* Looks OK so far; create & populate control structure */
 	fp = file_alloc();
 	if (fp == NULL) {
 		snprintf(command_errbuf, sizeof (command_errbuf),
 		    "no memory to load %s", name);
 		free(name);
 		close(fd);
 		return (NULL);
 	}
 	fp->f_name = name;
 	fp->f_type = strdup(type);
 	fp->f_args = NULL;
 	fp->f_metadata = NULL;
 	fp->f_loader = -1;
 	fp->f_addr = loadaddr;
 	fp->f_size = laddr - loadaddr;
 
 	if (fp->f_type == NULL) {
 		snprintf(command_errbuf, sizeof (command_errbuf),
 		    "no memory to load %s", name);
 		free(name);
 		close(fd);
 		return (NULL);
 	}
 	/* recognise space consumption */
 	loadaddr = laddr;
 
 	/* Add to the list of loaded files */
 	if (insert != 0)
 		file_insert_tail(fp);
 	close(fd);
 	return(fp);
 }
 
 /*
  * Load the module (name), pass it (argc),(argv), add container file
  * to the list of loaded files.
  * If module is already loaded just assign new argc/argv.
  */
 int
 mod_load(char *modname, struct mod_depend *verinfo, int argc, char *argv[])
 {
 	struct kernel_module	*mp;
 	int				err;
 	char			*filename;
 
 	if (file_havepath(modname)) {
 		printf("Warning: mod_load() called instead of mod_loadkld() for module '%s'\n", modname);
 		return (mod_loadkld(modname, argc, argv));
 	}
 	/* see if module is already loaded */
 	mp = file_findmodule(NULL, modname, verinfo);
 	if (mp) {
 #ifdef moduleargs
 		free(mp->m_args);
 		mp->m_args = unargv(argc, argv);
 #endif
 		snprintf(command_errbuf, sizeof(command_errbuf),
 		  "warning: module '%s' already loaded", mp->m_name);
 		return (0);
 	}
 	/* locate file with the module on the search path */
 	filename = mod_searchmodule(modname, verinfo);
 	if (filename == NULL) {
 		snprintf(command_errbuf, sizeof(command_errbuf),
 		  "can't find '%s'", modname);
 		return (ENOENT);
 	}
 	err = mod_loadkld(filename, argc, argv);
 	free(filename);
 	return (err);
 }
 
 /*
  * Load specified KLD. If path is omitted, then try to locate it via
  * search path.
  */
 int
 mod_loadkld(const char *kldname, int argc, char *argv[])
 {
 	struct preloaded_file	*fp;
 	int			err;
 	char			*filename;
 	vm_offset_t		loadaddr_saved;
 
 	/*
 	 * Get fully qualified KLD name
 	 */
 	filename = file_search(kldname, kld_ext_list);
 	if (filename == NULL) {
 		snprintf(command_errbuf, sizeof(command_errbuf),
 		  "can't find '%s'", kldname);
 		return (ENOENT);
 	}
 	/*
 	 * Check if KLD already loaded
 	 */
 	fp = file_findfile(filename, NULL);
 	if (fp) {
 		snprintf(command_errbuf, sizeof(command_errbuf),
 		  "warning: KLD '%s' already loaded", filename);
 		free(filename);
 		return (0);
 	}
 
 	do {
 		err = file_load(filename, loadaddr, &fp);
 		if (err)
 			break;
 		fp->f_args = unargv(argc, argv);
 		loadaddr_saved = loadaddr;
 		loadaddr = fp->f_addr + fp->f_size;
 		file_insert_tail(fp);	/* Add to the list of loaded files */
 		if (file_load_dependencies(fp) != 0) {
 			err = ENOENT;
 			file_remove(fp);
 			loadaddr = loadaddr_saved;
 			fp = NULL;
 			break;
 		}
 	} while(0);
 	if (err == EFTYPE) {
 		snprintf(command_errbuf, sizeof(command_errbuf),
 		  "don't know how to load module '%s'", filename);
 	}
 	if (err)
 		file_discard(fp);
 	free(filename);
 	return (err);
 }
 
 /*
  * Find a file matching (name) and (type).
  * NULL may be passed as a wildcard to either.
  */
 struct preloaded_file *
 file_findfile(const char *name, const char *type)
 {
 	struct preloaded_file *fp;
 
 	for (fp = preloaded_files; fp != NULL; fp = fp->f_next) {
 		if (((name == NULL) || !strcmp(name, fp->f_name)) &&
 		  ((type == NULL) || !strcmp(type, fp->f_type)))
 			break;
 	}
 	return (fp);
 }
 
 /*
  * Find a module matching (name) inside of given file.
  * NULL may be passed as a wildcard.
  */
 struct kernel_module *
 file_findmodule(struct preloaded_file *fp, char *modname,
 	struct mod_depend *verinfo)
 {
 	struct kernel_module *mp, *best;
 	int bestver, mver;
 
 	if (fp == NULL) {
 		for (fp = preloaded_files; fp; fp = fp->f_next) {
 			mp = file_findmodule(fp, modname, verinfo);
 			if (mp)
 				return (mp);
 		}
 		return (NULL);
 	}
 	best = NULL;
 	bestver = 0;
 	for (mp = fp->f_modules; mp; mp = mp->m_next) {
 		if (strcmp(modname, mp->m_name) == 0) {
 			if (verinfo == NULL)
 				return (mp);
 			mver = mp->m_version;
 			if (mver == verinfo->md_ver_preferred)
 				return (mp);
 			if (mver >= verinfo->md_ver_minimum &&
 			  mver <= verinfo->md_ver_maximum &&
 			  mver > bestver) {
 				best = mp;
 				bestver = mver;
 			}
 		}
 	}
 	return (best);
 }
 /*
  * Make a copy of (size) bytes of data from (p), and associate them as
  * metadata of (type) to the module (mp).
  */
 void
 file_addmetadata(struct preloaded_file *fp, int type, size_t size, void *p)
 {
 	struct file_metadata	*md;
 
 	md = malloc(sizeof(struct file_metadata) - sizeof(md->md_data) + size);
 	if (md != NULL) {
 		md->md_size = size;
 		md->md_type = type;
 		bcopy(p, md->md_data, size);
 		md->md_next = fp->f_metadata;
 	}
 	fp->f_metadata = md;
 }
 
 /*
  * Find a metadata object of (type) associated with the file (fp)
  */
 struct file_metadata *
 file_findmetadata(struct preloaded_file *fp, int type)
 {
 	struct file_metadata *md;
 
 	for (md = fp->f_metadata; md != NULL; md = md->md_next)
 		if (md->md_type == type)
 			break;
 	return(md);
 }
 
 /*
  * Remove all metadata from the file.
  */
 void
 file_removemetadata(struct preloaded_file *fp)
 {
 	struct file_metadata *md, *next;
 
 	for (md = fp->f_metadata; md != NULL; md = next)
 	{
 		next = md->md_next;
 		free(md);
 	}
 	fp->f_metadata = NULL;
 }
 
 struct file_metadata *
 metadata_next(struct file_metadata *md, int type)
 {
 
 	if (md == NULL)
 		return (NULL);
 	while((md = md->md_next) != NULL)
 		if (md->md_type == type)
 			break;
 	return (md);
 }
 
 static char *emptyextlist[] = { "", NULL };
 
 /*
  * Check if the given file is in place and return full path to it.
  */
 static char *
 file_lookup(const char *path, const char *name, int namelen, char **extlist)
 {
 	struct stat	st;
 	char	*result, *cp, **cpp;
 	int		pathlen, extlen, len;
 
 	pathlen = strlen(path);
 	extlen = 0;
 	if (extlist == NULL)
 		extlist = emptyextlist;
 	for (cpp = extlist; *cpp; cpp++) {
 		len = strlen(*cpp);
 		if (len > extlen)
 			extlen = len;
 	}
 	result = malloc(pathlen + namelen + extlen + 2);
 	if (result == NULL)
 		return (NULL);
 	bcopy(path, result, pathlen);
 	if (pathlen > 0 && result[pathlen - 1] != '/')
 		result[pathlen++] = '/';
 	cp = result + pathlen;
 	bcopy(name, cp, namelen);
 	cp += namelen;
 	for (cpp = extlist; *cpp; cpp++) {
 		strcpy(cp, *cpp);
 		if (stat(result, &st) == 0 && S_ISREG(st.st_mode))
 			return result;
 	}
 	free(result);
 	return NULL;
 }
 
 /*
  * Check if file name have any qualifiers
  */
 static int
 file_havepath(const char *name)
 {
 	const char		*cp;
 
 	archsw.arch_getdev(NULL, name, &cp);
 	return (cp != name || strchr(name, '/') != NULL);
 }
 
 /*
  * Attempt to find the file (name) on the module searchpath.
  * If (name) is qualified in any way, we simply check it and
  * return it or NULL.  If it is not qualified, then we attempt
  * to construct a path using entries in the environment variable
  * module_path.
  *
  * The path we return a pointer to need never be freed, as we manage
  * it internally.
  */
 static char *
 file_search(const char *name, char **extlist)
 {
 	struct moduledir	*mdp;
 	struct stat		sb;
 	char		*result;
 	int			namelen;
 
 	/* Don't look for nothing */
 	if (name == NULL)
 		return(NULL);
 
 	if (*name == 0)
 		return(strdup(name));
 
 	if (file_havepath(name)) {
 		/* Qualified, so just see if it exists */
 		if (stat(name, &sb) == 0)
 			return(strdup(name));
 		return(NULL);
 	}
 	moduledir_rebuild();
 	result = NULL;
 	namelen = strlen(name);
 	STAILQ_FOREACH(mdp, &moduledir_list, d_link) {
 		result = file_lookup(mdp->d_path, name, namelen, extlist);
 		if (result)
 			break;
 	}
 	return(result);
 }
 
 #define	INT_ALIGN(base, ptr)	ptr = \
 	(base) + roundup2((ptr) - (base), sizeof(int))
 
 static char *
 mod_search_hints(struct moduledir *mdp, const char *modname,
 	struct mod_depend *verinfo)
 {
 	u_char	*cp, *recptr, *bufend, *best;
 	char	*result;
 	int		*intp, bestver, blen, clen, found, ival, modnamelen, reclen;
 
 	moduledir_readhints(mdp);
 	modnamelen = strlen(modname);
 	found = 0;
 	result = NULL;
 	bestver = 0;
 	if (mdp->d_hints == NULL)
 		goto bad;
 	recptr = mdp->d_hints;
 	bufend = recptr + mdp->d_hintsz;
 	clen = blen = 0;
 	best = cp = NULL;
 	while (recptr < bufend && !found) {
 		intp = (int*)recptr;
 		reclen = *intp++;
 		ival = *intp++;
 		cp = (u_char*)intp;
 		switch (ival) {
 		case MDT_VERSION:
 			clen = *cp++;
 			if (clen != modnamelen || bcmp(cp, modname, clen) != 0)
 				break;
 			cp += clen;
 			INT_ALIGN(mdp->d_hints, cp);
 			ival = *(int*)cp;
 			cp += sizeof(int);
 			clen = *cp++;
 			if (verinfo == NULL || ival == verinfo->md_ver_preferred) {
 				found = 1;
 				break;
 			}
 			if (ival >= verinfo->md_ver_minimum &&
 			  ival <= verinfo->md_ver_maximum &&
 			  ival > bestver) {
 				bestver = ival;
 				best = cp;
 				blen = clen;
 			}
 			break;
 		default:
 			break;
 		}
 		recptr += reclen + sizeof(int);
 	}
 	/*
 	 * Finally check if KLD is in the place
 	 */
 	if (found)
 		result = file_lookup(mdp->d_path, (const char *)cp, clen, NULL);
 	else if (best)
 		result = file_lookup(mdp->d_path, (const char *)best, blen, NULL);
 bad:
 	/*
 	 * If nothing found or hints is absent - fallback to the old way
 	 * by using "kldname[.ko]" as module name.
 	 */
 	if (!found && !bestver && result == NULL)
 		result = file_lookup(mdp->d_path, modname, modnamelen, kld_ext_list);
 	return result;
 }
 
 static int
 getint(void **ptr)
 {
 	int *p = *ptr;
 	int rv;
 
 	p = (int *)roundup2((intptr_t)p, sizeof(int));
 	rv = *p++;
 	*ptr = p;
 	return rv;
 }
 
 static void
 getstr(void **ptr, char *val)
 {
 	int *p = *ptr;
 	char *c = (char *)p;
 	int len = *(uint8_t *)c;
 
 	memcpy(val, c + 1, len);
 	val[len] = 0;
 	c += len + 1;
 	*ptr = (void *)c;
 }
 
 static int
 pnpval_as_int(const char *val, const char *pnpinfo)
 {
 	int rv;
 	char key[256];
 	char *cp;
 
 	if (pnpinfo == NULL)
 		return -1;
 
 	cp = strchr(val, ';');
 	key[0] = ' ';
 	if (cp == NULL)
 		strlcpy(key + 1, val, sizeof(key) - 1);
 	else {
 		memcpy(key + 1, val, cp - val);
 		key[cp - val + 1] = '\0';
 	}
 	strlcat(key, "=", sizeof(key));
 	if (strncmp(key + 1, pnpinfo, strlen(key + 1)) == 0)
 		rv = strtol(pnpinfo + strlen(key + 1), NULL, 0);
 	else {
 		cp = strstr(pnpinfo, key);
 		if (cp == NULL)
 			rv = -1;
 		else
 			rv = strtol(cp + strlen(key), NULL, 0);
 	}
 	return rv;
 }
 
 static void
 quoted_strcpy(char *dst, const char *src)
 {
 	char q = ' ';
 
 	if (*src == '\'' || *src == '"')
 		q = *src++;
 	while (*src && *src != q)
 		*dst++ = *src++; // XXX backtick quoting
 	*dst++ = '\0';
 	// XXX overflow
 }
 
 static char *
 pnpval_as_str(const char *val, const char *pnpinfo)
 {
 	static char retval[256];
 	char key[256];
 	char *cp;
 
 	if (pnpinfo == NULL) {
 		*retval = '\0';
 		return retval;
 	}
 
 	cp = strchr(val, ';');
 	key[0] = ' ';
 	if (cp == NULL)
 		strlcpy(key + 1, val, sizeof(key) - 1);
 	else {
 		memcpy(key + 1, val, cp - val);
 		key[cp - val + 1] = '\0';
 	}
 	strlcat(key, "=", sizeof(key));
 	if (strncmp(key + 1, pnpinfo, strlen(key + 1)) == 0)
 		quoted_strcpy(retval, pnpinfo + strlen(key + 1));
 	else {
 		cp = strstr(pnpinfo, key);
 		if (cp == NULL)
 			strcpy(retval, "MISSING");
 		else
 			quoted_strcpy(retval, cp + strlen(key));
 	}
 	return retval;
 }
 
 static char *
 devmatch_search_hints(struct moduledir *mdp, const char *bus, const char *dev, const char *pnpinfo)
 {
 	char val1[256], val2[256];
 	int ival, len, ents, i, notme, mask, bit, v, found;
 	void *ptr, *walker, *hints_end;
 	char *lastmod = NULL, *cp, *s;
 
 	moduledir_readhints(mdp);
 	found = 0;
 	if (mdp->d_hints == NULL)
 		goto bad;
 	walker = mdp->d_hints;
 	hints_end = walker + mdp->d_hintsz;
 	while (walker < hints_end && !found) {
 		len = getint(&walker);
 		ival = getint(&walker);
 		ptr = walker;
 		switch (ival) {
 		case MDT_VERSION:
 			getstr(&ptr, val1);
 			ival = getint(&ptr);
 			getstr(&ptr, val2);
 			if (pnp_dump_flag || pnp_verbose_flag)
 				printf("Version: if %s.%d kmod %s\n", val1, ival, val2);
 			break;
 		case MDT_MODULE:
 			getstr(&ptr, val1);
 			getstr(&ptr, val2);
 			if (lastmod)
 				free(lastmod);
 			lastmod = strdup(val2);
 			if (pnp_dump_flag || pnp_verbose_flag)
 				printf("module %s in %s\n", val1, val1);
 			break;
 		case MDT_PNP_INFO:
 			if (!pnp_dump_flag && !pnp_unbound_flag && lastmod && strcmp(lastmod, "kernel") == 0)
 				break;
 			getstr(&ptr, val1);
 			getstr(&ptr, val2);
 			ents = getint(&ptr);
 			if (pnp_dump_flag || pnp_verbose_flag)
 				printf("PNP info for bus %s format %s %d entries (%s)\n",
 				    val1, val2, ents, lastmod);
 			if (strcmp(val1, "usb") == 0) {
 				if (pnp_verbose_flag)
 					printf("Treating usb as uhub -- bug in source table still?\n");
 				strcpy(val1, "uhub");
 			}
 			if (bus && strcmp(val1, bus) != 0) {
 				if (pnp_verbose_flag)
 					printf("Skipped because table for bus %s, looking for %s\n",
 					    val1, bus);
 				break;
 			}
 			for (i = 0; i < ents; i++) {
 				if (pnp_verbose_flag)
 					printf("---------- Entry %d ----------\n", i);
 				if (pnp_dump_flag)
 					printf("   ");
 				cp = val2;
 				notme = 0;
 				mask = -1;
 				bit = -1;
 				do {
 					switch (*cp) {
 						/* All integer fields */
 					case 'I':
 					case 'J':
 					case 'G':
 					case 'L':
 					case 'M':
 						ival = getint(&ptr);
 						if (pnp_dump_flag) {
 							printf("%#x:", ival);
 							break;
 						}
 						if (bit >= 0 && ((1 << bit) & mask) == 0)
 							break;
 						v = pnpval_as_int(cp + 2, pnpinfo);
 						if (pnp_verbose_flag)
 							printf("Matching %s (%c) table=%#x tomatch=%#x\n",
 							    cp + 2, *cp, v, ival);
 						switch (*cp) {
 						case 'J':
 							if (ival == -1)
 								break;
 							/*FALLTHROUGH*/
 						case 'I':
 							if (v != ival)
 								notme++;
 							break;
 						case 'G':
 							if (v < ival)
 								notme++;
 							break;
 						case 'L':
 							if (v > ival)
 								notme++;
 							break;
 						case 'M':
 							mask = ival;
 							break;
 						}
 						break;
 						/* String fields */
 					case 'D':
 					case 'Z':
 						getstr(&ptr, val1);
 						if (pnp_dump_flag) {
 							printf("'%s':", val1);
 							break;
 						}
 						if (*cp == 'D')
 							break;
 						s = pnpval_as_str(cp + 2, pnpinfo);
 						if (strcmp(s, val1) != 0)
 							notme++;
 						break;
 						/* Key override fields, required to be last in the string */
 					case 'T':
 						/*
 						 * This is imperfect and only does one key and will be redone
 						 * to be more general for multiple keys. Currently, nothing
 						 * does that.
 						 */
 						if (pnp_dump_flag)				/* No per-row data stored */
 							break;
 						if (cp[strlen(cp) - 1] == ';')		/* Skip required ; at end */
 							cp[strlen(cp) - 1] = '\0';	/* in case it's not there */
 						if ((s = strstr(pnpinfo, cp + 2)) == NULL)
 							notme++;
 						else if (s > pnpinfo && s[-1] != ' ')
 							notme++;
 						break;
 					default:
 						printf("Unknown field type %c\n:", *cp);
 						break;
 					}
 					bit++;
 					cp = strchr(cp, ';');
 					if (cp)
 						cp++;
 				} while (cp && *cp);
 				if (pnp_dump_flag)
 					printf("\n");
 				else if (!notme) {
 					if (!pnp_unbound_flag) {
 						if (pnp_verbose_flag)
 							printf("Matches --- %s ---\n", lastmod);
 					}
 					found++;
 				}
 			}
 			break;
 		default:
 			break;
 		}
 		walker = (void *)(len - sizeof(int) + (intptr_t)walker);
 	}
 	if (pnp_unbound_flag && found == 0 && *pnpinfo) {
 		if (pnp_verbose_flag)
 			printf("------------------------- ");
 		printf("%s on %s pnpinfo %s", *dev ? dev : "unattached", bus, pnpinfo);
 		if (pnp_verbose_flag)
 			printf(" -------------------------");
 		printf("\n");
 	}
 	if (found != 0)
 		return (lastmod);
 	free(lastmod);
 
 bad:
 	return (NULL);
 }
 
 /*
  * Attempt to locate the file containing the module (name)
  */
 static char *
 mod_searchmodule(char *name, struct mod_depend *verinfo)
 {
 	struct	moduledir *mdp;
 	char	*result;
 
 	moduledir_rebuild();
 	/*
 	 * Now we ready to lookup module in the given directories
 	 */
 	result = NULL;
 	STAILQ_FOREACH(mdp, &moduledir_list, d_link) {
 		result = mod_search_hints(mdp, name, verinfo);
 		if (result)
 			break;
 	}
 
 	return(result);
 }
 
 static char *
 mod_searchmodule_pnpinfo(const char *bus, const char *pnpinfo)
 {
 	struct	moduledir *mdp;
 	char	*result;
 
 	moduledir_rebuild();
 	/*
 	 * Now we ready to lookup module in the given directories
 	 */
 	result = NULL;
 	STAILQ_FOREACH(mdp, &moduledir_list, d_link) {
 		result = devmatch_search_hints(mdp, bus, NULL, pnpinfo);
 		if (result)
 			break;
 	}
 
 	return(result);
 }
 
 int
 file_addmodule(struct preloaded_file *fp, char *modname, int version,
 	struct kernel_module **newmp)
 {
 	struct kernel_module *mp;
 	struct mod_depend mdepend;
 
 	bzero(&mdepend, sizeof(mdepend));
 	mdepend.md_ver_preferred = version;
 	mp = file_findmodule(fp, modname, &mdepend);
 	if (mp)
 		return (EEXIST);
 	mp = calloc(1, sizeof(struct kernel_module));
 	if (mp == NULL)
 		return (ENOMEM);
 	mp->m_name = strdup(modname);
 	if (mp->m_name == NULL) {
 		free(mp);
 		return (ENOMEM);
 	}
 	mp->m_version = version;
 	mp->m_fp = fp;
 	mp->m_next = fp->f_modules;
 	fp->f_modules = mp;
 	if (newmp)
 		*newmp = mp;
 	return (0);
 }
 
 /*
  * Throw a file away
  */
 void
 file_discard(struct preloaded_file *fp)
 {
 	struct file_metadata	*md, *md1;
 	struct kernel_module	*mp, *mp1;
 	if (fp == NULL)
 		return;
 	md = fp->f_metadata;
 	while (md) {
 		md1 = md;
 		md = md->md_next;
 		free(md1);
 	}
 	mp = fp->f_modules;
 	while (mp) {
 		free(mp->m_name);
 		mp1 = mp;
 		mp = mp->m_next;
 		free(mp1);
 	}
 	free(fp->f_name);
 	free(fp->f_type);
 	free(fp->f_args);
 	free(fp);
 }
 
 /*
  * Allocate a new file; must be used instead of malloc()
  * to ensure safe initialisation.
  */
 struct preloaded_file *
 file_alloc(void)
 {
 
 	return (calloc(1, sizeof(struct preloaded_file)));
 }
 
 /*
  * Add a module to the chain
  */
 static void
 file_insert_tail(struct preloaded_file *fp)
 {
 	struct preloaded_file	*cm;
     
 	/* Append to list of loaded file */
 	fp->f_next = NULL;
 	if (preloaded_files == NULL) {
 		preloaded_files = fp;
 	} else {
 		for (cm = preloaded_files; cm->f_next != NULL; cm = cm->f_next)
 			;
 		cm->f_next = fp;
 	}
 }
 
 /*
  * Remove module from the chain
  */
 static void
 file_remove(struct preloaded_file *fp)
 {
 	struct preloaded_file   *cm;
 
 	if (preloaded_files == NULL)
 		return;
 
 	if (preloaded_files == fp) {
 		preloaded_files = fp->f_next;
 		return;
         }
         for (cm = preloaded_files; cm->f_next != NULL; cm = cm->f_next) {
 		if (cm->f_next == fp) {
 			cm->f_next = fp->f_next;
 			return;
 		}
 	}
 }
 
 static char *
 moduledir_fullpath(struct moduledir *mdp, const char *fname)
 {
 	char *cp;
 
 	cp = malloc(strlen(mdp->d_path) + strlen(fname) + 2);
 	if (cp == NULL)
 		return NULL;
 	strcpy(cp, mdp->d_path);
 	strcat(cp, "/");
 	strcat(cp, fname);
 	return (cp);
 }
 
 /*
  * Read linker.hints file into memory performing some sanity checks.
  */
 static void
 moduledir_readhints(struct moduledir *mdp)
 {
 	struct stat	st;
 	char	*path;
 	int		fd, size, version;
 
 	if (mdp->d_hints != NULL || (mdp->d_flags & MDIR_NOHINTS))
 		return;
 	path = moduledir_fullpath(mdp, "linker.hints");
 	if (stat(path, &st) != 0 ||
 	  st.st_size < (ssize_t)(sizeof(version) + sizeof(int)) ||
 	  st.st_size > LINKER_HINTS_MAX || (fd = open(path, O_RDONLY)) < 0) {
 		free(path);
 		mdp->d_flags |= MDIR_NOHINTS;
 		return;
 	}
 	free(path);
 	size = read(fd, &version, sizeof(version));
 	if (size != sizeof(version) || version != LINKER_HINTS_VERSION)
 		goto bad;
 	size = st.st_size - size;
 	mdp->d_hints = malloc(size);
 	if (mdp->d_hints == NULL)
 		goto bad;
 	if (read(fd, mdp->d_hints, size) != size)
 		goto bad;
 	mdp->d_hintsz = size;
 	close(fd);
 	return;
 bad:
 	close(fd);
 	free(mdp->d_hints);
 	mdp->d_hints = NULL;
 	mdp->d_flags |= MDIR_NOHINTS;
 	return;
 }
 
 /*
  * Extract directories from the ';' separated list, remove duplicates.
  */
 static void
 moduledir_rebuild(void)
 {
 	struct	moduledir *mdp, *mtmp;
 	const char	*path, *cp, *ep;
 	size_t	cplen;
 
 	path = getenv("module_path");
 	if (path == NULL)
 		path = default_searchpath;
 	/*
 	 * Rebuild list of module directories if it changed
 	 */
 	STAILQ_FOREACH(mdp, &moduledir_list, d_link)
 		mdp->d_flags |= MDIR_REMOVED;
 
 	for (ep = path; *ep != 0;  ep++) {
 		cp = ep;
 		for (; *ep != 0 && *ep != ';'; ep++)
 			;
 		/*
 		 * Ignore trailing slashes
 		 */
 		for (cplen = ep - cp; cplen > 1 && cp[cplen - 1] == '/'; cplen--)
 			;
 		STAILQ_FOREACH(mdp, &moduledir_list, d_link) {
 			if (strlen(mdp->d_path) != cplen ||	bcmp(cp, mdp->d_path, cplen) != 0)
 				continue;
 			mdp->d_flags &= ~MDIR_REMOVED;
 			break;
 		}
 		if (mdp == NULL) {
 			mdp = malloc(sizeof(*mdp) + cplen + 1);
 			if (mdp == NULL)
 				return;
 			mdp->d_path = (char*)(mdp + 1);
 			bcopy(cp, mdp->d_path, cplen);
 			mdp->d_path[cplen] = 0;
 			mdp->d_hints = NULL;
 			mdp->d_flags = 0;
 			STAILQ_INSERT_TAIL(&moduledir_list, mdp, d_link);
 		}
 		if (*ep == 0)
 			break;
 	}
 	/*
 	 * Delete unused directories if any
 	 */
 	mdp = STAILQ_FIRST(&moduledir_list);
 	while (mdp) {
 		if ((mdp->d_flags & MDIR_REMOVED) == 0) {
 			mdp = STAILQ_NEXT(mdp, d_link);
 		} else {
 			free(mdp->d_hints);
 			mtmp = mdp;
 			mdp = STAILQ_NEXT(mdp, d_link);
 			STAILQ_REMOVE(&moduledir_list, mtmp, moduledir, d_link);
 			free(mtmp);
 		}
 	}
 	return;
 }
diff --git a/stand/defaults/loader.conf.5 b/stand/defaults/loader.conf.5
index 1053191ec181..22fe9812a33a 100644
--- a/stand/defaults/loader.conf.5
+++ b/stand/defaults/loader.conf.5
@@ -1,384 +1,386 @@
 .\" Copyright (c) 1999 Daniel C. Sobral
 .\" All rights reserved.
 .\"
 .\" Redistribution and use in source and binary forms, with or without
 .\" modification, are permitted provided that the following conditions
 .\" are met:
 .\" 1. Redistributions of source code must retain the above copyright
 .\"    notice, this list of conditions and the following disclaimer.
 .\" 2. Redistributions in binary form must reproduce the above copyright
 .\"    notice, this list of conditions and the following disclaimer in the
 .\"    documentation and/or other materials provided with the distribution.
 .\"
 .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 .\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
 .\" $FreeBSD$
 .Dd December 31, 2020
 .Dt LOADER.CONF 5
 .Os
 .Sh NAME
 .Nm loader.conf
 .Nd "system bootstrap configuration information"
 .Sh DESCRIPTION
 The file
 .Nm
 contains descriptive information on bootstrapping the system.
 Through
 it you can specify the kernel to be booted, parameters to be passed to
 it, and additional modules to be loaded; and generally set all variables
 described in
 .Xr loader 8 .
 .Sh SYNTAX
 Though
 .Nm Ns 's
 format was defined explicitly to resemble
 .Xr rc.conf 5 ,
 and can be sourced by
 .Xr sh 1 ,
 some settings are treated in a special fashion.
 Also, the
 behavior of some settings is defined by the setting's suffix;
 the prefix identifies which module the setting controls.
 .Pp
 The general parsing rules are:
 .Bl -bullet
 .It
 Spaces and empty lines are ignored.
 .It
 A # sign will mark the remainder of the line as a comment.
 .It
 Only one setting can be present on each line.
 .El
 .Pp
 All settings have the following format:
 .Pp
 .Dl variable="value"
 .Pp
 Unless it belongs to one of the classes of settings that receive special
 treatment, a setting will set the value of a
 .Xr loader 8
 environment variable.
 The settings that receive special
 treatment are listed below.
 Settings beginning with
 .Qq *
 below define the modules to be loaded and
 may have any prefix; the prefix identifies a module.
 All such settings sharing a common
 prefix refer to the same module.
 .Bl -tag -width Ar
 .It Ar exec
 Immediately executes a
 .Xr loader 8
 command.
 This type of setting cannot be processed by programs other
 than
 .Xr loader 8 ,
 so its use should be avoided.
 Multiple instances of it will be processed
 independently.
 .It Ar loader_conf_dirs
 Space separated list of directories to process for configuration files.
 The lua-based loader will process files with a
 .Dq .conf
 suffix that are placed in these directories.
 .It Ar loader_conf_files
 Defines additional configuration files to be processed right after the
 present file.
 .Ar loader_conf_files
 should be treated as write-only.
 One cannot depend on any value remaining in the loader environment or carried
 over into the kernel environment.
 .It Ar kernel
 Name of the kernel to be loaded.
 If no kernel name is set, no additional
 modules will be loaded.
 The name must be a subdirectory of
 .Pa /boot
 that contains a kernel.
 .It Ar kernel_options
 Flags to be passed to the kernel.
 .It Ar vfs.root.mountfrom
 Specify the root partition to mount.
 For example:
 .Pp
 .Dl vfs.root.mountfrom="ufs:/dev/da0s1a"
 .Pp
 .Xr loader 8
 automatically calculates the value of this tunable from
 .Pa /etc/fstab
 from the partition the kernel was loaded from.
 The calculated value might be calculated incorrectly when
 .Pa /etc/fstab
 is not available during
 .Xr loader 8
 startup (as during diskless booting from NFS), or if a different
 device is desired by the user.
 The preferred value can be set in
 .Pa /loader.conf .
 .Pp
 The value can also be overridden from the
 .Xr loader 8
 command line.
 This is useful for system recovery when
 .Pa /etc/fstab
 is damaged, lost, or read from the wrong partition.
 .It Ar password
 Protect boot menu with a password without interrupting
 .Ic autoboot
 process.
 The password should be in clear text format.
 If a password is set, boot menu will not appear until any key is pressed during
 countdown period specified by
 .Va autoboot_delay
 variable or
 .Ic autoboot
 process fails.
 In both cases user should provide specified password to be able to access boot
 menu.
 .It Ar bootlock_password
 Provides a password to be required by check-password before execution is
 allowed to continue.
 The password should be in clear text format.
 If a password is set, the user must provide specified password to boot.
 .It Ar verbose_loading
 If set to
 .Dq YES ,
 module names will be displayed as they are loaded.
 .It Ar module_blacklist
 Blacklist of modules.
 Modules specified in the blacklist may not be loaded automatically with a
 .Ar *_load
 directive, but they may be loaded directly at the
 .Xr loader 8
 prompt.
 Blacklisted modules may still be loaded indirectly as dependencies of other
 modules.
 .It Ar *_load
 If set to
 .Dq YES ,
 that module will be loaded.
 If no name is defined (see below), the
 module's name is taken to be the same as the prefix.
 .It Ar *_name
 Defines the name of the module.
 .It Ar *_type
 Defines the module's type.
 If none is given, it defaults to a kld module.
 .It Ar *_flags
 Flags and parameters to be passed to the module.
 .It Ar *_before
 Commands to be executed before the module is loaded.
 Use of this setting
 should be avoided.
 .It Ar *_after
 Commands to be executed after the module is loaded.
 Use of this setting
 should be avoided.
 .It Ar *_error
 Commands to be executed if the loading of a module fails.
 Except for the
 special value
 .Dq abort ,
 which aborts the bootstrap process, use of this setting should be avoided.
 .El
 .Pp
 .Em WARNING:
 developers should never use these suffixes for any kernel environment
 variables (tunables) or conflicts will result.
 .Sh DEFAULT SETTINGS
 Most of
 .Nm Ns 's
 default settings can be ignored.
 The few of them which are important
 or useful are:
 .Bl -tag -width bootfile -offset indent
 .It Va bitmap_load
 .Pq Dq NO
 If set to
 .Dq YES ,
 a bitmap will be loaded to be displayed on screen while booting.
 .It Va bitmap_name
 .Pq Dq Pa /boot/splash.bmp
 Name of the bitmap to be loaded.
 Any other name can be used.
 .It Va comconsole_speed
 .Dq ( 9600
 or the value of the
 .Va BOOT_COMCONSOLE_SPEED
 variable when
 .Xr loader 8
 was compiled).
 Sets the speed of the serial console.
 If the previous boot loader stage specified that a serial console
 is in use then the default speed is determined from the current
 serial port speed setting.
 .It Va console
 .Pq Dq vidconsole
 .Dq comconsole
 selects serial console,
 .Dq vidconsole
 selects the video console,
 .Dq nullconsole
 selects a mute console
 (useful for systems with neither a video console nor a serial port), and
 .Dq spinconsole
 selects the video console which prevents any input and hides all output
 replacing it with
 .Dq spinning
 character (useful for embedded products and such).
 .It Va efi_max_resolution
-Specify the maximum desired resolution for the EFI console.
+.It Va vbe_max_resolution
+Specify the maximum desired resolution for the EFI or VESA BIOS Extension (VBE)
+framebuffer console.
 The following values are accepted:
 .Bl -column "WidthxHeight"
 .It Sy Value Ta Sy Resolution
 .It 480p Ta 640x480
 .It 720p Ta 1280x720
 .It 1080p Ta 1920x1080
 .It 2160p Ta 3840x2160
 .It 4k Ta 3840x2160
 .It 5k Ta 5120x2880
 .It Va Width Ns x Ns Va Height Ta Va Width Ns x Ns Va Height
 .El
 .It Va kernel
 .Pq Dq kernel
 .It Va kernels
 .Pq Dq kernel kernel.old
 Space or comma separated list of kernels to present in the boot menu.
 .It Va loader_conf_files
 .Pq Dq Pa /boot/loader.conf /boot/loader.conf.local
 .It Va loader_conf_dirs
 .Pq Dq Pa /boot/loader.conf.d
 .It Va splash_bmp_load
 .Pq Dq NO
 If set to
 .Dq YES ,
 will load the splash screen module, making it possible to display a bmp image
 on the screen while booting.
 .It Va splash_pcx_load
 .Pq Dq NO
 If set to
 .Dq YES ,
 will load the splash screen module, making it possible to display a pcx image
 on the screen while booting.
 .It Va vesa_load
 .Pq Dq NO
 If set to
 .Dq YES ,
 the vesa module will be loaded, enabling bitmaps above VGA resolution to
 be displayed.
 .It Va beastie_disable
 If set to
 .Dq YES ,
 the beastie boot menu will be skipped.
 .It Va loader_logo Pq Dq Li orbbw
 Selects a desired logo in the beastie boot menu.
 Possible values are:
 .Dq Li orbbw ,
 .Dq Li orb ,
 .Dq Li fbsdbw ,
 .Dq Li beastiebw ,
 .Dq Li beastie ,
 and
 .Dq Li none .
 .It Va loader_color
 If set to
 .Dq NO ,
 the beastie boot menu will be displayed without ANSI coloring.
 .It Va entropy_cache_load
 .Pq Dq YES
 If set to
 .Dq NO ,
 the very early
 boot-time entropy file
 will not be loaded.
 See the entropy entries in
 .Xr rc.conf 5 .
 .It Va entropy_cache_name
 .Pq Dq /boot/entropy
 The name of the very early
 boot-time entropy cache file.
 .It Va cpu_microcode_load
 .Pq Dq NO
 If set to
 .Dq YES ,
 the microcode update file specified by
 .Va cpu_microcode_name
 will be loaded and applied very early during boot.
 This provides functionality similar to
 .Xr cpucontrol 8
 but ensures that CPU features enabled by microcode updates can be
 used by the kernel.
 The update will be re-applied automatically when resuming from an
 ACPI sleep state.
 If the update file contains updates for multiple processor models,
 the kernel will search for and extract a matching update.
 Currently this setting is supported only on Intel
 .Dv i386
 and
 .Dv amd64
 processors.
 It has no effect on other processor types.
 .It Va cpu_microcode_name
 A path to a microcode update file.
 .El
 .Sh OTHER SETTINGS
 Other settings that may be used in
 .Nm
 that have no default value:
 .Bl -tag -width bootfile -offset indent
 .It Va fdt_overlays
 Specifies a comma-delimited list of FDT overlays to apply.
 .Pa /boot/dtb/overlays
 is created by default for overlays to be placed in.
 .It Va kernels_autodetect
 If set to
 .Dq YES ,
 attempt to auto-detect kernels installed in
 .Pa /boot .
 This is an option specific to the Lua-based loader.
 It is not available in the default Forth-based loader.
 .El
 .Sh FILES
 .Bl -tag -width /boot/defaults/loader.conf -compact
 .It Pa /boot/defaults/loader.conf
 default settings -- do not change this file.
 .It Pa /boot/loader.conf
 user defined settings.
 .It Pa /boot/loader.conf.local
 machine-specific settings for sites with a common loader.conf.
 .El
 .Sh SEE ALSO
 .Xr rc.conf 5 ,
 .Xr boot 8 ,
 .Xr cpucontrol 8 ,
 .Xr loader 8 ,
 .Xr loader.4th 8
 .Sh HISTORY
 The file
 .Nm
 first appeared in
 .Fx 3.2 .
 .Sh AUTHORS
 This manual page was written by
 .An Daniel C. Sobral Aq dcs@FreeBSD.org .
 .Sh BUGS
 The
 .Xr loader 8
 stops reading
 .Nm
 when it encounters a syntax error, so any options which are vital for
 booting a particular system (i.e.\&
 .Dq Va hw.ata.ata_dma Ns "=0" )
 should precede any experimental additions to
 .Nm .
diff --git a/stand/efi/include/efilib.h b/stand/efi/include/efilib.h
index 6f1dbac98730..b9b9030afd7f 100644
--- a/stand/efi/include/efilib.h
+++ b/stand/efi/include/efilib.h
@@ -1,151 +1,150 @@
 /*-
  * Copyright (c) 2000 Doug Rabson
  * Copyright (c) 2006 Marcel Moolenaar
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
  * $FreeBSD$
  */
 
 #ifndef _LOADER_EFILIB_H
 #define	_LOADER_EFILIB_H
 
 #include <stand.h>
 #include <stdbool.h>
 #include <sys/queue.h>
 
 extern EFI_HANDLE		IH;
 extern EFI_SYSTEM_TABLE		*ST;
 extern EFI_BOOT_SERVICES	*BS;
 extern EFI_RUNTIME_SERVICES	*RS;
 
 extern struct devsw efipart_fddev;
 extern struct devsw efipart_cddev;
 extern struct devsw efipart_hddev;
 extern struct devsw efihttp_dev;
 extern struct devsw efinet_dev;
 extern struct netif_driver efinetif;
 
 /* EFI block device data, included here to help efi_zfs_probe() */
 typedef STAILQ_HEAD(pdinfo_list, pdinfo) pdinfo_list_t;
 
 typedef struct pdinfo
 {
 	STAILQ_ENTRY(pdinfo)	pd_link;	/* link in device list */
 	pdinfo_list_t		pd_part;	/* list of partitions */
 	EFI_HANDLE		pd_handle;
 	EFI_HANDLE		pd_alias;
 	EFI_DEVICE_PATH		*pd_devpath;
 	EFI_BLOCK_IO		*pd_blkio;
 	uint32_t		pd_unit;	/* unit number */
 	uint32_t		pd_open;	/* reference counter */
 	void			*pd_bcache;	/* buffer cache data */
 	struct pdinfo		*pd_parent;	/* Linked items (eg partitions) */
 	struct devsw		*pd_devsw;	/* Back pointer to devsw */
 } pdinfo_t;
 
 pdinfo_list_t *efiblk_get_pdinfo_list(struct devsw *dev);
 pdinfo_t *efiblk_get_pdinfo(struct devdesc *dev);
 pdinfo_t *efiblk_get_pdinfo_by_handle(EFI_HANDLE h);
 pdinfo_t *efiblk_get_pdinfo_by_device_path(EFI_DEVICE_PATH *path);
 
 void *efi_get_table(EFI_GUID *tbl);
 EFI_STATUS OpenProtocolByHandle(EFI_HANDLE, EFI_GUID *, void **);
 
 int efi_getdev(void **vdev, const char *devspec, const char **path);
 char *efi_fmtdev(void *vdev);
 int efi_setcurrdev(struct env_var *ev, int flags, const void *value);
 
 
 int efi_register_handles(struct devsw *, EFI_HANDLE *, EFI_HANDLE *, int);
 EFI_HANDLE efi_find_handle(struct devsw *, int);
 int efi_handle_lookup(EFI_HANDLE, struct devsw **, int *,  uint64_t *);
 int efi_handle_update_dev(EFI_HANDLE, struct devsw *, int, uint64_t);
 
 EFI_DEVICE_PATH *efi_lookup_image_devpath(EFI_HANDLE);
 EFI_DEVICE_PATH *efi_lookup_devpath(EFI_HANDLE);
 void efi_close_devpath(EFI_HANDLE);
 EFI_HANDLE efi_devpath_handle(EFI_DEVICE_PATH *);
 EFI_DEVICE_PATH *efi_devpath_last_node(EFI_DEVICE_PATH *);
 EFI_DEVICE_PATH *efi_devpath_trim(EFI_DEVICE_PATH *);
 bool efi_devpath_match(EFI_DEVICE_PATH *, EFI_DEVICE_PATH *);
 bool efi_devpath_match_node(EFI_DEVICE_PATH *, EFI_DEVICE_PATH *);
 bool efi_devpath_is_prefix(EFI_DEVICE_PATH *, EFI_DEVICE_PATH *);
 CHAR16 *efi_devpath_name(EFI_DEVICE_PATH *);
 void efi_free_devpath_name(CHAR16 *);
 bool efi_devpath_same_disk(EFI_DEVICE_PATH *, EFI_DEVICE_PATH *);
 EFI_DEVICE_PATH *efi_devpath_to_media_path(EFI_DEVICE_PATH *);
 UINTN efi_devpath_length(EFI_DEVICE_PATH *);
 EFI_DEVICE_PATH *efi_name_to_devpath(const char *path);
 EFI_DEVICE_PATH *efi_name_to_devpath16(CHAR16 *path);
 void efi_devpath_free(EFI_DEVICE_PATH *dp);
 EFI_HANDLE efi_devpath_to_handle(EFI_DEVICE_PATH *path, EFI_HANDLE *handles, unsigned nhandles);
 
 int efi_status_to_errno(EFI_STATUS);
 EFI_STATUS errno_to_efi_status(int errno);
 
 void efi_time_init(void);
 void efi_time_fini(void);
 
 int parse_uefi_con_out(void);
-bool efi_cons_update_mode(void);
 EFI_STATUS efi_main(EFI_HANDLE Ximage, EFI_SYSTEM_TABLE* Xsystab);
 
 EFI_STATUS main(int argc, CHAR16 *argv[]);
 void efi_exit(EFI_STATUS status) __dead2;
 
 /* EFI environment initialization. */
 void efi_init_environment(void);
 
 /* EFI Memory type strings. */
 const char *efi_memory_type(EFI_MEMORY_TYPE);
 
 /* CHAR16 utility functions. */
 int wcscmp(CHAR16 *, CHAR16 *);
 void cpy8to16(const char *, CHAR16 *, size_t);
 void cpy16to8(const CHAR16 *, char *, size_t);
 
 /*
  * Routines for interacting with EFI's env vars in a more unix-like
  * way than the standard APIs. In addition, convenience routines for
  * the loader setting / getting FreeBSD specific variables.
  */
 
 EFI_STATUS efi_delenv(EFI_GUID *guid, const char *varname);
 EFI_STATUS efi_freebsd_delenv(const char *varname);
 EFI_STATUS efi_freebsd_getenv(const char *v, void *data, __size_t *len);
 EFI_STATUS efi_getenv(EFI_GUID *g, const char *v, void *data, __size_t *len);
 EFI_STATUS efi_global_getenv(const char *v, void *data, __size_t *len);
 EFI_STATUS efi_setenv(EFI_GUID *guid, const char *varname, UINT32 attr, void *data, __size_t len);
 EFI_STATUS efi_setenv_freebsd_wcs(const char *varname, CHAR16 *valstr);
 
 /* guids and names */
 bool efi_guid_to_str(const EFI_GUID *, char **);
 bool efi_str_to_guid(const char *, EFI_GUID *);
 bool efi_name_to_guid(const char *, EFI_GUID *);
 bool efi_guid_to_name(EFI_GUID *, char **);
 
 /* efipart.c */
 int	efipart_inithandles(void);
 
 #endif	/* _LOADER_EFILIB_H */
diff --git a/stand/efi/libefi/Makefile b/stand/efi/libefi/Makefile
index 8006893d0cbe..010754b5238a 100644
--- a/stand/efi/libefi/Makefile
+++ b/stand/efi/libefi/Makefile
@@ -1,72 +1,72 @@
 # $FreeBSD$
 
 .include <bsd.init.mk>
 
 LIB=	efi
 WARNS?=	2
 
 SRCS=	delay.c \
 	devicename.c \
 	devpath.c \
 	efi_console.c \
 	efi_driver_utils.c \
 	efichar.c \
 	efienv.c \
 	efihttp.c \
 	efinet.c \
 	efipart.c \
 	efizfs.c \
 	env.c \
 	errno.c \
 	handles.c \
 	libefi.c \
 	wchar.c
 
 .PATH:  ${SYSDIR}/teken
 SRCS+=  teken.c
 
 .if ${MACHINE_CPUARCH} == "amd64" || ${MACHINE_CPUARCH} == "i386"
 SRCS+=	time.c
 .elif ${MACHINE_CPUARCH} == "aarch64" || ${MACHINE_CPUARCH} == "arm" || \
     ${MACHINE_CPUARCH} == "riscv"
 SRCS+=	time_event.c
 .endif
 
 # We implement a slightly non-standard %S in that it always takes a
 # CHAR16 that's common in UEFI-land instead of a wchar_t. This only
 # seems to matter on arm64 where wchar_t defaults to an int instead
 # of a short. There's no good cast to use here so just ignore the
 # warnings for now.
 CWARNFLAGS.efinet.c+=	-Wno-format
 CWARNFLAGS.efipart.c+=	-Wno-format
 CWARNFLAGS.env.c+=	-Wno-format
 
 .if ${MACHINE_CPUARCH} == "aarch64"
 CFLAGS+=	-mgeneral-regs-only
 .endif
 .if ${MACHINE_ARCH} == "amd64"
 CFLAGS+= -fPIC -mno-red-zone
 .endif
 CFLAGS+= -I${EFIINC}
 CFLAGS+= -I${EFIINCMD}
-CFLAGS.efi_console.c+= -I${SRCTOP}/sys/teken
+CFLAGS.efi_console.c+= -I${SRCTOP}/sys/teken -I${SRCTOP}/contrib/pnglite
 CFLAGS.teken.c+= -I${SRCTOP}/sys/teken
 .if ${MK_LOADER_ZFS} != "no"
 CFLAGS+=	-I${ZFSSRC}
 CFLAGS+=	-I${SYSDIR}/cddl/boot/zfs
 CFLAGS+=	-I${SYSDIR}/cddl/contrib/opensolaris/uts/common
 CFLAGS+=	-DEFI_ZFS_BOOT
 .endif
 
 # Pick up the bootstrap header for some interface items
 CFLAGS+= -I${LDRSRC}
 
 # Handle FreeBSD specific %b and %D printf format specifiers
 CFLAGS+= ${FORMAT_EXTENSIONS}
 
 # Do not use TERM_EMU on arm and arm64 as it doesn't behave well with serial console
 .if ${MACHINE_CPUARCH} != "arm" && ${MACHINE_CPUARCH} != "aarch64"
 CFLAGS+= -DTERM_EMU
 .endif
 
 .include <bsd.lib.mk>
diff --git a/stand/efi/libefi/efi_console.c b/stand/efi/libefi/efi_console.c
index a72fb4d561da..22adcd3a00e2 100644
--- a/stand/efi/libefi/efi_console.c
+++ b/stand/efi/libefi/efi_console.c
@@ -1,1235 +1,1401 @@
 /*-
  * Copyright (c) 2000 Doug Rabson
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
 
 #include <sys/cdefs.h>
 __FBSDID("$FreeBSD$");
 
+#include <sys/param.h>
 #include <efi.h>
 #include <efilib.h>
 #include <teken.h>
 #include <sys/reboot.h>
-
+#include <machine/metadata.h>
+#include <gfx_fb.h>
 #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? */
 
 static uint32_t utf8_left;
 static uint32_t utf8_partial;
 #ifdef TERM_EMU
 #define	DEFAULT_FGCOLOR EFI_LIGHTGRAY
 #define	DEFAULT_BGCOLOR EFI_BLACK
 
 #define	MAXARGS 8
 static int args[MAXARGS], argc;
 static int fg_c, bg_c, curx, cury;
 static int esc;
 
 void get_pos(int *x, int *y);
 void curs_move(int *_x, int *_y, int x, int y);
 static void CL(int);
 void HO(void);
 void end_term(void);
 #endif
 
+#define	TEXT_ROWS	24
+#define	TEXT_COLS	80
+
 static tf_bell_t	efi_cons_bell;
 static tf_cursor_t	efi_text_cursor;
 static tf_putchar_t	efi_text_putchar;
 static tf_fill_t	efi_text_fill;
 static tf_copy_t	efi_text_copy;
 static tf_param_t	efi_text_param;
 static tf_respond_t	efi_cons_respond;
 
 static teken_funcs_t tf = {
 	.tf_bell	= efi_cons_bell,
 	.tf_cursor	= efi_text_cursor,
 	.tf_putchar	= efi_text_putchar,
 	.tf_fill	= efi_text_fill,
 	.tf_copy	= efi_text_copy,
 	.tf_param	= efi_text_param,
 	.tf_respond	= efi_cons_respond,
 };
 
-teken_t teken;
-teken_pos_t tp;
-
-struct text_pixel {
-	teken_char_t c;
-	teken_attr_t a;
+static teken_funcs_t tfx = {
+	.tf_bell	= efi_cons_bell,
+	.tf_cursor	= gfx_fb_cursor,
+	.tf_putchar	= gfx_fb_putchar,
+	.tf_fill	= gfx_fb_fill,
+	.tf_copy	= gfx_fb_copy,
+	.tf_param	= gfx_fb_param,
+	.tf_respond	= efi_cons_respond,
 };
 
-static struct text_pixel *buffer;
-
 #define	KEYBUFSZ 10
 static unsigned keybuf[KEYBUFSZ];	/* keybuf for extended codes */
 static int key_pending;
 
 static const unsigned char teken_color_to_efi_color[16] = {
 	EFI_BLACK,
 	EFI_RED,
 	EFI_GREEN,
 	EFI_BROWN,
 	EFI_BLUE,
 	EFI_MAGENTA,
 	EFI_CYAN,
 	EFI_LIGHTGRAY,
 	EFI_DARKGRAY,
 	EFI_LIGHTRED,
 	EFI_LIGHTGREEN,
 	EFI_YELLOW,
 	EFI_LIGHTBLUE,
 	EFI_LIGHTMAGENTA,
 	EFI_LIGHTCYAN,
 	EFI_WHITE
 };
 
 static void efi_cons_probe(struct console *);
 static int efi_cons_init(int);
 void efi_cons_putchar(int);
 int efi_cons_getchar(void);
 void efi_cons_efiputchar(int);
 int efi_cons_poll(void);
+static void cons_draw_frame(teken_attr_t *);
 
 struct console efi_console = {
 	"efi",
 	"EFI console",
 	C_WIDEOUT,
 	efi_cons_probe,
 	efi_cons_init,
 	efi_cons_putchar,
 	efi_cons_getchar,
 	efi_cons_poll
 };
 
+/*
+ * This function is used to mark a rectangular image area so the scrolling
+ * will know we need to copy the data from there.
+ */
+void
+term_image_display(teken_gfx_t *state, const teken_rect_t *r)
+{
+	teken_pos_t p;
+	int idx;
+
+	for (p.tp_row = r->tr_begin.tp_row;
+	    p.tp_row < r->tr_end.tp_row; p.tp_row++) {
+		for (p.tp_col = r->tr_begin.tp_col;
+		    p.tp_col < r->tr_end.tp_col; p.tp_col++) {
+			idx = p.tp_col + p.tp_row * state->tg_tp.tp_col;
+			if (idx >= state->tg_tp.tp_col * state->tg_tp.tp_row)
+				return;
+			screen_buffer[idx].a.ta_format |= TF_IMAGE;
+		}
+	}
+}
+
 /*
  * Not implemented.
  */
 static void
 efi_cons_bell(void *s __unused)
 {
 }
 
 static void
-efi_text_cursor(void *s __unused, const teken_pos_t *p)
+efi_text_cursor(void *arg, const teken_pos_t *p)
 {
-	UINTN row, col;
+	teken_gfx_t *state = arg;
+	UINTN col, row;
 
-	(void) conout->QueryMode(conout, conout->Mode->Mode, &col, &row);
+	row = p->tp_row;
+	if (p->tp_row >= state->tg_tp.tp_row)
+		row = state->tg_tp.tp_row - 1;
 
-	if (p->tp_col == col)
-		col = p->tp_col - 1;
-	else
-		col = p->tp_col;
-
-	if (p->tp_row == row)
-		row = p->tp_row - 1;
-	else
-		row = p->tp_row;
+	col = p->tp_col;
+	if (p->tp_col >= state->tg_tp.tp_col)
+		col = state->tg_tp.tp_col - 1;
 
 	conout->SetCursorPosition(conout, col, row);
 }
 
 static void
-efi_text_printchar(const teken_pos_t *p, bool autoscroll)
+efi_text_printchar(teken_gfx_t *state, const teken_pos_t *p, bool autoscroll)
 {
 	UINTN a, attr;
 	struct text_pixel *px;
 	teken_color_t fg, bg, tmp;
 
-	px = buffer + p->tp_col + p->tp_row * tp.tp_col;
+	px = screen_buffer + p->tp_col + p->tp_row * state->tg_tp.tp_col;
 	a = conout->Mode->Attribute;
 
 	fg = teken_256to16(px->a.ta_fgcolor);
 	bg = teken_256to16(px->a.ta_bgcolor);
 	if (px->a.ta_format & TF_BOLD)
 		fg |= TC_LIGHT;
 	if (px->a.ta_format & TF_BLINK)
 		bg |= TC_LIGHT;
 
 	if (px->a.ta_format & TF_REVERSE) {
 		tmp = fg;
 		fg = bg;
 		bg = tmp;
 	}
 
 	attr = EFI_TEXT_ATTR(teken_color_to_efi_color[fg],
 	    teken_color_to_efi_color[bg] & 0x7);
 
 	conout->SetCursorPosition(conout, p->tp_col, p->tp_row);
 
-	/* to 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);
 	efi_cons_efiputchar(px->c);
 	(void) conout->SetAttribute(conout, a);
 }
 
 static void
-efi_text_putchar(void *s __unused, const teken_pos_t *p, teken_char_t c,
+efi_text_putchar(void *s, const teken_pos_t *p, teken_char_t c,
     const teken_attr_t *a)
 {
+	teken_gfx_t *state = s;
 	EFI_STATUS status;
 	int idx;
 
-	idx = p->tp_col + p->tp_row * tp.tp_col;
-	buffer[idx].c = c;
-	buffer[idx].a = *a;
-	efi_text_printchar(p, false);
+	idx = p->tp_col + p->tp_row * state->tg_tp.tp_col;
+	if (idx >= state->tg_tp.tp_col * state->tg_tp.tp_row)
+		return;
+
+	screen_buffer[idx].c = c;
+	screen_buffer[idx].a = *a;
+
+	efi_text_printchar(s, p, false);
 }
 
 static void
-efi_text_fill(void *s, const teken_rect_t *r, teken_char_t c,
+efi_text_fill(void *arg, const teken_rect_t *r, teken_char_t c,
     const teken_attr_t *a)
 {
+	teken_gfx_t *state = arg;
 	teken_pos_t p;
-	UINTN row, col;
-
-	(void) conout->QueryMode(conout, conout->Mode->Mode, &col, &row);
 
-	conout->EnableCursor(conout, FALSE);
+	if (state->tg_cursor_visible)
+		conout->EnableCursor(conout, FALSE);
 	for (p.tp_row = r->tr_begin.tp_row; p.tp_row < r->tr_end.tp_row;
 	    p.tp_row++)
 		for (p.tp_col = r->tr_begin.tp_col;
 		    p.tp_col < r->tr_end.tp_col; p.tp_col++)
-			efi_text_putchar(s, &p, c, a);
-	conout->EnableCursor(conout, TRUE);
+			efi_text_putchar(state, &p, c, a);
+	if (state->tg_cursor_visible)
+		conout->EnableCursor(conout, TRUE);
 }
 
-static bool
-efi_same_pixel(struct text_pixel *px1, struct text_pixel *px2)
+static void
+efi_text_copy_line(teken_gfx_t *state, int ncol, teken_pos_t *s,
+    teken_pos_t *d, bool scroll)
 {
-	if (px1->c != px2->c)
-		return (false);
-
-	if (px1->a.ta_format != px2->a.ta_format)
-		return (false);
-	if (px1->a.ta_fgcolor != px2->a.ta_fgcolor)
-		return (false);
-	if (px1->a.ta_bgcolor != px2->a.ta_bgcolor)
-		return (false);
-
-	return (true);
+	unsigned soffset, doffset;
+	teken_pos_t sp, dp;
+	int x;
+
+	soffset = s->tp_col + s->tp_row * state->tg_tp.tp_col;
+	doffset = d->tp_col + d->tp_row * state->tg_tp.tp_col;
+
+	sp = *s;
+	dp = *d;
+	for (x = 0; x < ncol; x++) {
+		sp.tp_col = s->tp_col + x;
+		dp.tp_col = d->tp_col + x;
+		if (!is_same_pixel(&screen_buffer[soffset + x],
+		    &screen_buffer[doffset + x])) {
+			screen_buffer[doffset + x] =
+			    screen_buffer[soffset + x];
+			if (!scroll)
+				efi_text_printchar(state, &dp, false);
+		} else if (scroll) {
+			/* Draw last char and trigger scroll. */
+			if (dp.tp_col + 1 == state->tg_tp.tp_col &&
+			    dp.tp_row + 1 == state->tg_tp.tp_row) {
+				efi_text_printchar(state, &dp, true);
+			}
+		}
+	}
 }
 
 static void
-efi_text_copy(void *ptr __unused, const teken_rect_t *r, const teken_pos_t *p)
+efi_text_copy(void *arg, const teken_rect_t *r, const teken_pos_t *p)
 {
-	int srow, drow;
-	int nrow, ncol, x, y; /* Has to be signed - >= 0 comparison */
+	teken_gfx_t *state = arg;
+	unsigned doffset, soffset;
 	teken_pos_t d, s;
+	int nrow, ncol, x, y; /* Has to be signed - >= 0 comparison */
 	bool scroll = false;
 
 	/*
 	 * Copying is a little tricky. We must make sure we do it in
 	 * correct order, to make sure we don't overwrite our own data.
 	 */
 
 	nrow = r->tr_end.tp_row - r->tr_begin.tp_row;
 	ncol = r->tr_end.tp_col - r->tr_begin.tp_col;
 
 	/*
 	 * Check if we do copy whole screen.
 	 */
 	if (p->tp_row == 0 && p->tp_col == 0 &&
-	    nrow == tp.tp_row - 2 && ncol == tp.tp_col - 2)
+	    nrow == state->tg_tp.tp_row - 2 && ncol == state->tg_tp.tp_col - 2)
 		scroll = true;
 
-	conout->EnableCursor(conout, FALSE);
-	if (p->tp_row < r->tr_begin.tp_row) {
-		/* Copy from bottom to top. */
+	soffset = r->tr_begin.tp_col + r->tr_begin.tp_row * state->tg_tp.tp_col;
+	doffset = p->tp_col + p->tp_row * state->tg_tp.tp_col;
+
+	/* remove the cursor */
+	if (state->tg_cursor_visible)
+		conout->EnableCursor(conout, FALSE);
+
+	/*
+	 * Copy line by line.
+	 */
+	if (doffset <= soffset) {
+		s = r->tr_begin;
+		d = *p;
 		for (y = 0; y < nrow; y++) {
-			d.tp_row = p->tp_row + y;
 			s.tp_row = r->tr_begin.tp_row + y;
-			drow = d.tp_row * tp.tp_col;
-			srow = s.tp_row * tp.tp_col;
-			for (x = 0; x < ncol; x++) {
-				d.tp_col = p->tp_col + x;
-				s.tp_col = r->tr_begin.tp_col + x;
-
-				if (!efi_same_pixel(
-				    &buffer[d.tp_col + drow],
-				    &buffer[s.tp_col + srow])) {
-					buffer[d.tp_col + drow] =
-					    buffer[s.tp_col + srow];
-					if (!scroll)
-						efi_text_printchar(&d, false);
-				} else if (scroll) {
-					/*
-					 * Draw last char and trigger
-					 * scroll.
-					 */
-					if (y == nrow - 1 &&
-					    x == ncol - 1) {
-						efi_text_printchar(&d, true);
-					}
-				}
-			}
+			d.tp_row = p->tp_row + y;
+
+			efi_text_copy_line(state, ncol, &s, &d, scroll);
 		}
 	} else {
-		/* Copy from top to bottom. */
-		if (p->tp_col < r->tr_begin.tp_col) {
-			/* Copy from right to left. */
-			for (y = nrow - 1; y >= 0; y--) {
-				d.tp_row = p->tp_row + y;
-				s.tp_row = r->tr_begin.tp_row + y;
-				drow = d.tp_row * tp.tp_col;
-				srow = s.tp_row * tp.tp_col;
-				for (x = 0; x < ncol; x++) {
-					d.tp_col = p->tp_col + x;
-					s.tp_col = r->tr_begin.tp_col + x;
-
-					if (!efi_same_pixel(
-					    &buffer[d.tp_col + drow],
-					    &buffer[s.tp_col + srow])) {
-						buffer[d.tp_col + drow] =
-						    buffer[s.tp_col + srow];
-						efi_text_printchar(&d, false);
-					}
-				}
-			}
-		} else {
-			/* Copy from left to right. */
-			for (y = nrow - 1; y >= 0; y--) {
-				d.tp_row = p->tp_row + y;
-				s.tp_row = r->tr_begin.tp_row + y;
-				drow = d.tp_row * tp.tp_col;
-				srow = s.tp_row * tp.tp_col;
-				for (x = ncol - 1; x >= 0; x--) {
-					d.tp_col = p->tp_col + x;
-					s.tp_col = r->tr_begin.tp_col + x;
-
-					if (!efi_same_pixel(
-					    &buffer[d.tp_col + drow],
-					    &buffer[s.tp_col + srow])) {
-						buffer[d.tp_col + drow] =
-						    buffer[s.tp_col + srow];
-						efi_text_printchar(&d, false);
-					}
-				}
-			}
+		for (y = nrow - 1; y >= 0; y--) {
+			s.tp_row = r->tr_begin.tp_row + y;
+			d.tp_row = p->tp_row + y;
+
+			efi_text_copy_line(state, ncol, &s, &d, false);
 		}
 	}
-	conout->EnableCursor(conout, TRUE);
+
+	/* display the cursor */
+	if (state->tg_cursor_visible)
+		conout->EnableCursor(conout, TRUE);
 }
 
 static void
-efi_text_param(void *s __unused, int cmd, unsigned int value)
+efi_text_param(void *arg, int cmd, unsigned int value)
 {
+	teken_gfx_t *state = arg;
+
 	switch (cmd) {
 	case TP_SETLOCALCURSOR:
 		/*
 		 * 0 means normal (usually block), 1 means hidden, and
 		 * 2 means blinking (always block) for compatibility with
 		 * syscons.  We don't support any changes except hiding,
 		 * so must map 2 to 0.
 		 */
 		value = (value == 1) ? 0 : 1;
 		/* FALLTHROUGH */
 	case TP_SHOWCURSOR:
-		if (value == 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 */
 		break;
 	}
 }
 
 /*
  * Not implemented.
  */
 static void
 efi_cons_respond(void *s __unused, const void *buf __unused,
     size_t len __unused)
 {
 }
 
 /*
  * Set up conin/conout/coninex to make sure we have input ready.
  */
 static void
 efi_cons_probe(struct console *cp)
 {
 	EFI_STATUS status;
 
 	conout = ST->ConOut;
 	conin = ST->ConIn;
 
 	/*
 	 * Call SetMode to work around buggy firmware.
 	 */
 	status = conout->SetMode(conout, conout->Mode->Mode);
 
 	if (coninex == NULL) {
 		status = BS->OpenProtocol(ST->ConsoleInHandle,
 		    &simple_input_ex_guid, (void **)&coninex,
 		    IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
 		if (status != EFI_SUCCESS)
 			coninex = NULL;
 	}
 
 	cp->c_flags |= C_PRESENTIN | C_PRESENTOUT;
 }
 
 static bool
 color_name_to_teken(const char *name, int *val)
 {
 	if (strcasecmp(name, "black") == 0) {
 		*val = TC_BLACK;
 		return (true);
 	}
 	if (strcasecmp(name, "red") == 0) {
 		*val = TC_RED;
 		return (true);
 	}
 	if (strcasecmp(name, "green") == 0) {
 		*val = TC_GREEN;
 		return (true);
 	}
 	if (strcasecmp(name, "brown") == 0) {
 		*val = TC_BROWN;
 		return (true);
 	}
 	if (strcasecmp(name, "blue") == 0) {
 		*val = TC_BLUE;
 		return (true);
 	}
 	if (strcasecmp(name, "magenta") == 0) {
 		*val = TC_MAGENTA;
 		return (true);
 	}
 	if (strcasecmp(name, "cyan") == 0) {
 		*val = TC_CYAN;
 		return (true);
 	}
 	if (strcasecmp(name, "white") == 0) {
 		*val = TC_WHITE;
 		return (true);
 	}
 	return (false);
 }
 
 static int
 efi_set_colors(struct env_var *ev, int flags, const void *value)
 {
 	int val = 0;
 	char buf[2];
 	const void *evalue;
 	const teken_attr_t *ap;
 	teken_attr_t a;
 
 	if (value == NULL)
 		return (CMD_OK);
 
 	if (color_name_to_teken(value, &val)) {
 		snprintf(buf, sizeof (buf), "%d", val);
 		evalue = buf;
 	} else {
 		char *end;
 
 		errno = 0;
 		val = (int)strtol(value, &end, 0);
 		if (errno != 0 || *end != '\0') {
 			printf("Allowed values are either ansi color name or "
 			    "number from range [0-7].\n");
 			return (CMD_OK);
 		}
 		evalue = value;
 	}
 
-	ap = teken_get_defattr(&teken);
+	ap = teken_get_defattr(&gfx_state.tg_teken);
 	a = *ap;
 	if (strcmp(ev->ev_name, "teken.fg_color") == 0) {
 		/* is it already set? */
 		if (ap->ta_fgcolor == val)
 			return (CMD_OK);
 		a.ta_fgcolor = val;
 	}
 	if (strcmp(ev->ev_name, "teken.bg_color") == 0) {
 		/* is it already set? */
 		if (ap->ta_bgcolor == val)
 			return (CMD_OK);
 		a.ta_bgcolor = val;
 	}
+
+	/* Improve visibility */
+	if (a.ta_bgcolor == TC_WHITE)
+		a.ta_bgcolor |= TC_LIGHT;
+
+	teken_set_defattr(&gfx_state.tg_teken, &a);
+	cons_draw_frame(&a);
 	env_setenv(ev->ev_name, flags | EV_NOHOOK, evalue, NULL, NULL);
-	teken_set_defattr(&teken, &a);
+	teken_input(&gfx_state.tg_teken, "\e[2J", 4);
 	return (CMD_OK);
 }
 
 #ifdef TERM_EMU
 /* Get cursor position. */
 void
 get_pos(int *x, int *y)
 {
 	*x = conout->Mode->CursorColumn;
 	*y = conout->Mode->CursorRow;
 }
 
 /* Move cursor to x rows and y cols (0-based). */
 void
 curs_move(int *_x, int *_y, int x, int y)
 {
 	conout->SetCursorPosition(conout, x, y);
 	if (_x != NULL)
 		*_x = conout->Mode->CursorColumn;
 	if (_y != NULL)
 		*_y = conout->Mode->CursorRow;
 }
  
 /* Clear internal state of the terminal emulation code. */
 void
 end_term(void)
 {
 	esc = 0;
 	argc = -1;
 }
 #endif
 
 static void
 efi_cons_rawputchar(int c)
 {
 	int i;
 	UINTN x, y;
 	conout->QueryMode(conout, conout->Mode->Mode, &x, &y);
 
 	if (c == '\t') {
 		int n;
 
 		n = 8 - ((conout->Mode->CursorColumn + 8) % 8);
 		for (i = 0; i < n; i++)
 			efi_cons_rawputchar(' ');
 	} else {
 #ifndef TERM_EMU
 		if (c == '\n')
 			efi_cons_efiputchar('\r');
 		efi_cons_efiputchar(c);
 #else
 		switch (c) {
 		case '\r':
 			curx = 0;
 			efi_cons_efiputchar('\r');
 			return;
 		case '\n':
 			efi_cons_efiputchar('\n');
 			efi_cons_efiputchar('\r');
 			cury++;
 			if (cury >= y)
 				cury--;
 			curx = 0;
 			return;
 		case '\b':
 			if (curx > 0) {
 				efi_cons_efiputchar('\b');
 				curx--;
 			}
 			return;
 		default:
 			efi_cons_efiputchar(c);
 			curx++;
 			if (curx > x-1) {
 				curx = 0;
 				cury++;
 			}
 			if (cury > y-1) {
 				curx = 0;
 				cury--;
 			}
 		}
 #endif
 	}
 	conout->EnableCursor(conout, TRUE);
 }
 
 #ifdef TERM_EMU
 /* Gracefully exit ESC-sequence processing in case of misunderstanding. */
 static void
 bail_out(int c)
 {
 	char buf[16], *ch;
 	int i;
 
 	if (esc) {
 		efi_cons_rawputchar('\033');
 		if (esc != '\033')
 			efi_cons_rawputchar(esc);
 		for (i = 0; i <= argc; ++i) {
 			sprintf(buf, "%d", args[i]);
 			ch = buf;
 			while (*ch)
 				efi_cons_rawputchar(*ch++);
 		}
 	}
 	efi_cons_rawputchar(c);
 	end_term();
 }
 
 /* Clear display from current position to end of screen. */
 static void
 CD(void)
 {
 	int i;
 	UINTN x, y;
 
 	get_pos(&curx, &cury);
 	if (curx == 0 && cury == 0) {
 		conout->ClearScreen(conout);
 		end_term();
 		return;
 	}
 
 	conout->QueryMode(conout, conout->Mode->Mode, &x, &y);
 	CL(0);  /* clear current line from cursor to end */
 	for (i = cury + 1; i < y-1; i++) {
 		curs_move(NULL, NULL, 0, i);
 		CL(0);
 	}
 	curs_move(NULL, NULL, curx, cury);
 	end_term();
 }
 
 /*
  * Absolute cursor move to args[0] rows and args[1] columns
  * (the coordinates are 1-based).
  */
 static void
 CM(void)
 {
 	if (args[0] > 0)
 		args[0]--;
 	if (args[1] > 0)
 		args[1]--;
 	curs_move(&curx, &cury, args[1], args[0]);
 	end_term();
 }
 
 /* Home cursor (left top corner), also called from mode command. */
 void
 HO(void)
 {
 	argc = 1;
 	args[0] = args[1] = 1;
 	CM();
 }
  
 /* Clear line from current position to end of line */
 static void
 CL(int direction)
 {
 	int i, len;
 	UINTN x, y;
 	CHAR16 *line;
 
 	conout->QueryMode(conout, conout->Mode->Mode, &x, &y);
 	switch (direction) {
 	case 0:	/* from cursor to end */
 		len = x - curx + 1;
 		break;
 	case 1:	/* from beginning to cursor */
 		len = curx;
 		break;
 	case 2:	/* entire line */
 		len = x;
 		break;
 	default:	/* NOTREACHED */
 		__unreachable();
 	}
  
 	if (cury == y - 1)
 		len--;
 
 	line = malloc(len * sizeof (CHAR16));
 	if (line == NULL) {
 		printf("out of memory\n");
 		return;
 	}
 	for (i = 0; i < len; i++)
 		line[i] = ' ';
 	line[len-1] = 0;
 
 	if (direction != 0)
 		curs_move(NULL, NULL, 0, cury);
  
 	conout->OutputString(conout, line);
 	/* restore cursor position */
 	curs_move(NULL, NULL, curx, cury);
 	free(line);
 	end_term();
 }
 
 static void
 get_arg(int c)
 {
 	if (argc < 0)
 		argc = 0;
 	args[argc] *= 10;
 	args[argc] += c - '0';
 }
 #endif
  
 /* Emulate basic capabilities of cons25 terminal */
 static void
 efi_term_emu(int c)
 {
 #ifdef TERM_EMU
 	static int ansi_col[] = {
 		0, 4, 2, 6, 1, 5, 3, 7
 	};
 	int t, i;
 	EFI_STATUS status;
  
 	switch (esc) {
 	case 0:
 		switch (c) {
 		case '\033':
 			esc = c;
 			break;
 		default:
 			efi_cons_rawputchar(c);
 			break;
 		}
 		break;
 	case '\033':
 		switch (c) {
 		case '[':
 			esc = c;
 			args[0] = 0;
 			argc = -1;
 			break;
 		default:
 			bail_out(c);
 			break;
 		}
 		break;
 	case '[':
 		switch (c) {
 		case ';':
 			if (argc < 0)
 				argc = 0;
 			else if (argc + 1 >= MAXARGS)
 				bail_out(c);
 			else
 				args[++argc] = 0;
 			break;
 		case 'H':		/* ho = \E[H */
 			if (argc < 0)
 				HO();
 			else if (argc == 1)
 				CM();
 			else
 				bail_out(c);
 			break;
 		case 'J':		/* cd = \E[J */
 			if (argc < 0)
 				CD();
 			else
 				bail_out(c);
 			break;
 		case 'm':
 			if (argc < 0) {
 				fg_c = DEFAULT_FGCOLOR;
 				bg_c = DEFAULT_BGCOLOR;
 			}
 			for (i = 0; i <= argc; ++i) {
 				switch (args[i]) {
 				case 0:		/* back to normal */
 					fg_c = DEFAULT_FGCOLOR;
 					bg_c = DEFAULT_BGCOLOR;
 					break;
 				case 1:		/* bold */
 					fg_c |= 0x8;
 					break;
 				case 4:		/* underline */
 				case 5:		/* blink */
 					bg_c |= 0x8;
 					break;
 				case 7:		/* reverse */
 					t = fg_c;
 					fg_c = bg_c;
 					bg_c = t;
 					break;
 				case 22:	/* normal intensity */
 					fg_c &= ~0x8;
 					break;
 				case 24:	/* not underline */
 				case 25:	/* not blinking */
 					bg_c &= ~0x8;
 					break;
 				case 30: case 31: case 32: case 33:
 				case 34: case 35: case 36: case 37:
 					fg_c = ansi_col[args[i] - 30];
 					break;
 				case 39:	/* normal */
 					fg_c = DEFAULT_FGCOLOR;
 					break;
 				case 40: case 41: case 42: case 43:
 				case 44: case 45: case 46: case 47:
 					bg_c = ansi_col[args[i] - 40];
 					break;
 				case 49:	/* normal */
 					bg_c = DEFAULT_BGCOLOR;
 					break;
 				}
 			}
 			conout->SetAttribute(conout, EFI_TEXT_ATTR(fg_c, bg_c));
 			end_term();
 			break;
 		default:
 			if (isdigit(c))
 				get_arg(c);
 			else
 				bail_out(c);
 			break;
 		}
 		break;
 	default:
 		bail_out(c);
 		break;
 	}
 #else
 	efi_cons_rawputchar(c);
 #endif
 }
 
+static int
+env_screen_nounset(struct env_var *ev __unused)
+{
+	if (gfx_state.tg_fb_type == FB_TEXT)
+		return (0);
+	return (EPERM);
+}
+
+static void
+cons_draw_frame(teken_attr_t *a)
+{
+	teken_attr_t attr = *a;
+	teken_color_t fg = a->ta_fgcolor;
+
+	attr.ta_fgcolor = attr.ta_bgcolor;
+	teken_set_defattr(&gfx_state.tg_teken, &attr);
+
+	gfx_fb_drawrect(0, 0, gfx_state.tg_fb.fb_width,
+	    gfx_state.tg_origin.tp_row, 1);
+	gfx_fb_drawrect(0,
+	    gfx_state.tg_fb.fb_height - gfx_state.tg_origin.tp_row - 1,
+	    gfx_state.tg_fb.fb_width, gfx_state.tg_fb.fb_height, 1);
+	gfx_fb_drawrect(0, gfx_state.tg_origin.tp_row,
+	    gfx_state.tg_origin.tp_col,
+	    gfx_state.tg_fb.fb_height - gfx_state.tg_origin.tp_row - 1, 1);
+	gfx_fb_drawrect(
+	    gfx_state.tg_fb.fb_width - gfx_state.tg_origin.tp_col - 1,
+	    gfx_state.tg_origin.tp_row, gfx_state.tg_fb.fb_width,
+	    gfx_state.tg_fb.fb_height, 1);
+
+	attr.ta_fgcolor = fg;
+	teken_set_defattr(&gfx_state.tg_teken, &attr);
+}
+
 bool
-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;
 	}
 
 	/*
 	 * When we have serial port listed in ConOut, use pre-teken emulator,
 	 * if built with.
 	 * The problem is, we can not output text on efi and comconsole when
 	 * efi also has comconsole bound. But then again, we need to have
 	 * terminal emulator for efi text mode to support the menu.
 	 * While teken is too expensive to be used on serial console, the
 	 * pre-teken emulator is light enough to be used on serial console.
 	 *
 	 * When doing multiple consoles (both serial and video),
 	 * also just use the old emulator. RB_MULTIPLE also implies
 	 * we're using a serial console.
 	 */
 	mode = parse_uefi_con_out();
 	if ((mode & (RB_SERIAL | RB_MULTIPLE)) == 0) {
-		if (buffer != NULL) {
-			if (tp.tp_row == rows && tp.tp_col == cols)
-				return (true);
-			free(buffer);
+		conout->EnableCursor(conout, FALSE);
+		gfx_state.tg_cursor_visible = false;
+
+		if (gfx_state.tg_fb_type == FB_TEXT) {
+
+			gfx_state.tg_functions = &tf;
+			/* ensure the following are not set for text mode */
+			unsetenv("screen.height");
+			unsetenv("screen.width");
+			unsetenv("screen.depth");
 		} else {
-			teken_init(&teken, &tf, NULL);
+			uint32_t fb_height, fb_width;
+
+			fb_height = gfx_state.tg_fb.fb_height;
+			fb_width = gfx_state.tg_fb.fb_width;
+
+			/*
+			 * setup_font() can adjust terminal size.
+			 * Note, we assume 80x24 terminal first, this is
+			 * because the font selection will attempt to achieve
+			 * at least this terminal dimension and we do not
+			 * end up with too small font.
+			 */
+			gfx_state.tg_tp.tp_row = TEXT_ROWS;
+			gfx_state.tg_tp.tp_col = TEXT_COLS;
+			setup_font(&gfx_state, fb_height, fb_width);
+			rows = gfx_state.tg_tp.tp_row;
+			cols = gfx_state.tg_tp.tp_col;
+			/* Point of origin in pixels. */
+			gfx_state.tg_origin.tp_row = (fb_height -
+			    (rows * gfx_state.tg_font.vf_height)) / 2;
+			gfx_state.tg_origin.tp_col = (fb_width -
+			    (cols * gfx_state.tg_font.vf_width)) / 2;
+
+			/* UEFI gop has depth 32. */
+			gfx_state.tg_glyph_size = gfx_state.tg_font.vf_height *
+			    gfx_state.tg_font.vf_width * 4;
+			free(gfx_state.tg_glyph);
+			gfx_state.tg_glyph = malloc(gfx_state.tg_glyph_size);
+			if (gfx_state.tg_glyph == NULL)
+				return (false);
+
+			gfx_state.tg_functions = &tfx;
+			snprintf(env, sizeof (env), "%d", fb_height);
+			env_setenv("screen.height", EV_VOLATILE | EV_NOHOOK,
+			    env, env_noset, env_screen_nounset);
+			snprintf(env, sizeof (env), "%d", fb_width);
+			env_setenv("screen.width", EV_VOLATILE | EV_NOHOOK,
+			    env, env_noset, env_screen_nounset);
+			snprintf(env, sizeof (env), "%d",
+			    gfx_state.tg_fb.fb_bpp);
+			env_setenv("screen.depth", EV_VOLATILE | EV_NOHOOK,
+			    env, env_noset, env_screen_nounset);
 		}
 
-		tp.tp_row = rows;
-		tp.tp_col = cols;
-		buffer = malloc(rows * cols * sizeof(*buffer));
-		if (buffer != NULL) {
-			teken_set_winsize(&teken, &tp);
-			a = teken_get_defattr(&teken);
+		/* Record our terminal screen size. */
+		gfx_state.tg_tp.tp_row = rows;
+		gfx_state.tg_tp.tp_col = cols;
+
+		teken_init(&gfx_state.tg_teken, gfx_state.tg_functions,
+		    &gfx_state);
+
+		free(screen_buffer);
+		screen_buffer = malloc(rows * cols * sizeof(*screen_buffer));
+		if (screen_buffer != NULL) {
+			teken_set_winsize(&gfx_state.tg_teken,
+			    &gfx_state.tg_tp);
+			a = teken_get_defattr(&gfx_state.tg_teken);
 			attr = *a;
 
 			/*
 			 * On first run, we set up the efi_set_colors()
 			 * callback. If the env is already set, we
 			 * pick up fg and bg color values from the environment.
 			 */
 			ptr = getenv("teken.fg_color");
 			if (ptr != NULL) {
 				attr.ta_fgcolor = strtol(ptr, NULL, 10);
 				ptr = getenv("teken.bg_color");
 				attr.ta_bgcolor = strtol(ptr, NULL, 10);
 
-				teken_set_defattr(&teken, &attr);
+				teken_set_defattr(&gfx_state.tg_teken, &attr);
 			} else {
 				snprintf(env, sizeof(env), "%d",
 				    attr.ta_fgcolor);
 				env_setenv("teken.fg_color", EV_VOLATILE, env,
 				    efi_set_colors, env_nounset);
 				snprintf(env, sizeof(env), "%d",
 				    attr.ta_bgcolor);
 				env_setenv("teken.bg_color", EV_VOLATILE, env,
 				    efi_set_colors, env_nounset);
 			}
-
-			for (int row = 0; row < rows; row++) {
-				for (int col = 0; col < cols; col++) {
-					buffer[col + row * tp.tp_col].c = ' ';
-					buffer[col + row * tp.tp_col].a = attr;
-				}
-			}
 		}
 	}
 
+	if (screen_buffer == NULL) {
+		conout->EnableCursor(conout, TRUE);
 #ifdef TERM_EMU
-	if (buffer == NULL) {
 		conout->SetAttribute(conout, EFI_TEXT_ATTR(DEFAULT_FGCOLOR,
 		    DEFAULT_BGCOLOR));
 		end_term();
 		get_pos(&curx, &cury);
 		curs_move(&curx, &cury, curx, cury);
 		fg_c = DEFAULT_FGCOLOR;
 		bg_c = DEFAULT_BGCOLOR;
-	}
 #endif
+	} else {
+		/* Improve visibility */
+		if (attr.ta_bgcolor == TC_WHITE)
+			attr.ta_bgcolor |= TC_LIGHT;
+		teken_set_defattr(&gfx_state.tg_teken, &attr);
+
+		/* Draw frame around terminal area. */
+		cons_draw_frame(&attr);
+		/*
+		 * Erase display, this will also fill our screen
+		 * buffer.
+		 */
+		teken_input(&gfx_state.tg_teken, "\e[2J", 4);
+		gfx_state.tg_functions->tf_param(&gfx_state,
+		    TP_SHOWCURSOR, 1);
+	}
 
 	snprintf(env, sizeof (env), "%u", (unsigned)rows);
 	setenv("LINES", env, 1);
 	snprintf(env, sizeof (env), "%u", (unsigned)cols);
 	setenv("COLUMNS", env, 1);
 
 	return (true);
 }
 
 static int
 efi_cons_init(int arg)
 {
 	EFI_STATUS status;
 
-	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);
 }
 
 static void
 input_partial(void)
 {
 	unsigned i;
 	uint32_t c;
 
 	if (utf8_left == 0)
 		return;
 
 	for (i = 0; i < sizeof(utf8_partial); i++) {
 		c = (utf8_partial >> (24 - (i << 3))) & 0xff;
 		if (c != 0)
 			efi_term_emu(c);
 	}
 	utf8_left = 0;
 	utf8_partial = 0;
 }
 
 static void
 input_byte(uint8_t c)
 {
 	if ((c & 0x80) == 0x00) {
 		/* One-byte sequence. */
 		input_partial();
 		efi_term_emu(c);
 		return;
 	}
 	if ((c & 0xe0) == 0xc0) {
 		/* Two-byte sequence. */
 		input_partial();
 		utf8_left = 1;
 		utf8_partial = c;
 		return;
 	}
 	if ((c & 0xf0) == 0xe0) {
 		/* Three-byte sequence. */
 		input_partial();
 		utf8_left = 2;
 		utf8_partial = c;
 		return;
 	}
 	if ((c & 0xf8) == 0xf0) {
 		/* Four-byte sequence. */
 		input_partial();
 		utf8_left = 3;
 		utf8_partial = c;
 		return;
 	}
 	if ((c & 0xc0) == 0x80) {
 		/* Invalid state? */
 		if (utf8_left == 0) {
 			efi_term_emu(c);
 			return;
 		}
 		utf8_left--;
 		utf8_partial = (utf8_partial << 8) | c;
 		if (utf8_left == 0) {
 			uint32_t v, u;
 			uint8_t b;
 
 			v = 0;
 			u = utf8_partial;
 			b = (u >> 24) & 0xff;
 			if (b != 0) {		/* Four-byte sequence */
 				v = b & 0x07;
 				b = (u >> 16) & 0xff;
 				v = (v << 6) | (b & 0x3f);
 				b = (u >> 8) & 0xff;
 				v = (v << 6) | (b & 0x3f);
 				b = u & 0xff;
 				v = (v << 6) | (b & 0x3f);
 			} else if ((b = (u >> 16) & 0xff) != 0) {
 				v = b & 0x0f;	/* Three-byte sequence */
 				b = (u >> 8) & 0xff;
 				v = (v << 6) | (b & 0x3f);
 				b = u & 0xff;
 				v = (v << 6) | (b & 0x3f);
 			} else if ((b = (u >> 8) & 0xff) != 0) {
 				v = b & 0x1f;	/* Two-byte sequence */
 				b = u & 0xff;
 				v = (v << 6) | (b & 0x3f);
 			}
 			/* Send unicode char directly to console. */
 			efi_cons_efiputchar(v);
 			utf8_partial = 0;
 		}
 		return;
 	}
 	/* Anything left is illegal in UTF-8 sequence. */
 	input_partial();
 	efi_term_emu(c);
 }
 
 void
 efi_cons_putchar(int c)
 {
 	unsigned char ch = c;
 
 	/*
 	 * Don't use Teken when we're doing pure serial, or a multiple console
 	 * with video "primary" because that's also serial.
 	 */
-	if ((mode & (RB_SERIAL | RB_MULTIPLE)) != 0 || buffer == NULL) {
+	if ((mode & (RB_SERIAL | RB_MULTIPLE)) != 0 || screen_buffer == NULL) {
 		input_byte(ch);
 		return;
 	}
 
-	teken_input(&teken, &ch, sizeof (ch));
+	teken_input(&gfx_state.tg_teken, &ch, sizeof (ch));
 }
 
 static int
 keybuf_getchar(void)
 {
 	int i, c = 0;
 
 	for (i = 0; i < KEYBUFSZ; i++) {
 		if (keybuf[i] != 0) {
 			c = keybuf[i];
 			keybuf[i] = 0;
 			break;
 		}
 	}
 
 	return (c);
 }
 
 static bool
 keybuf_ischar(void)
 {
 	int i;
 
 	for (i = 0; i < KEYBUFSZ; i++) {
 		if (keybuf[i] != 0)
 			return (true);
 	}
 	return (false);
 }
 
 /*
  * We are not reading input before keybuf is empty, so we are safe
  * just to fill keybuf from the beginning.
  */
 static void
 keybuf_inschar(EFI_INPUT_KEY *key)
 {
 
 	switch (key->ScanCode) {
 	case SCAN_UP: /* UP */
 		keybuf[0] = 0x1b;	/* esc */
 		keybuf[1] = '[';
 		keybuf[2] = 'A';
 		break;
 	case SCAN_DOWN: /* DOWN */
 		keybuf[0] = 0x1b;	/* esc */
 		keybuf[1] = '[';
 		keybuf[2] = 'B';
 		break;
 	case SCAN_RIGHT: /* RIGHT */
 		keybuf[0] = 0x1b;	/* esc */
 		keybuf[1] = '[';
 		keybuf[2] = 'C';
 		break;
 	case SCAN_LEFT: /* LEFT */
 		keybuf[0] = 0x1b;	/* esc */
 		keybuf[1] = '[';
 		keybuf[2] = 'D';
 		break;
 	case SCAN_DELETE:
 		keybuf[0] = CHAR_BACKSPACE;
 		break;
 	case SCAN_ESC:
 		keybuf[0] = 0x1b;	/* esc */
 		break;
 	default:
 		keybuf[0] = key->UnicodeChar;
 		break;
 	}
 }
 
 static bool
 efi_readkey(void)
 {
 	EFI_STATUS status;
 	EFI_INPUT_KEY key;
 
 	status = conin->ReadKeyStroke(conin, &key);
 	if (status == EFI_SUCCESS) {
 		keybuf_inschar(&key);
 		return (true);
 	}
 	return (false);
 }
 
 static bool
 efi_readkey_ex(void)
 {
 	EFI_STATUS status;
 	EFI_INPUT_KEY *kp;
 	EFI_KEY_DATA  key_data;
 	uint32_t kss;
 
 	status = coninex->ReadKeyStrokeEx(coninex, &key_data);
 	if (status == EFI_SUCCESS) {
 		kss = key_data.KeyState.KeyShiftState;
 		kp = &key_data.Key;
 		if (kss & EFI_SHIFT_STATE_VALID) {
 
 			/*
 			 * quick mapping to control chars, replace with
 			 * map lookup later.
 			 */
 			if (kss & EFI_RIGHT_CONTROL_PRESSED ||
 			    kss & EFI_LEFT_CONTROL_PRESSED) {
 				if (kp->UnicodeChar >= 'a' &&
 				    kp->UnicodeChar <= 'z') {
 					kp->UnicodeChar -= 'a';
 					kp->UnicodeChar++;
 				}
 			}
 		}
 		/*
 		 * The shift state and/or toggle state may not be valid,
 		 * but we still can have ScanCode or UnicodeChar.
 		 */
 		if (kp->ScanCode == 0 && kp->UnicodeChar == 0)
 			return (false);
 		keybuf_inschar(kp);
 		return (true);
 	}
 	return (false);
 }
 
 int
 efi_cons_getchar(void)
 {
 	int c;
 
 	if ((c = keybuf_getchar()) != 0)
 		return (c);
 
 	key_pending = 0;
 
 	if (coninex == NULL) {
 		if (efi_readkey())
 			return (keybuf_getchar());
 	} else {
 		if (efi_readkey_ex())
 			return (keybuf_getchar());
 	}
 
 	return (-1);
 }
 
 int
 efi_cons_poll(void)
 {
 	EFI_STATUS status;
 
 	if (keybuf_ischar() || key_pending)
 		return (1);
 
 	/*
 	 * Some EFI implementation (u-boot for example) do not support
 	 * WaitForKey().
 	 * CheckEvent() can clear the signaled state.
 	 */
 	if (coninex != NULL) {
 		if (coninex->WaitForKeyEx == NULL) {
 			key_pending = efi_readkey_ex();
 		} else {
 			status = BS->CheckEvent(coninex->WaitForKeyEx);
 			key_pending = status == EFI_SUCCESS;
 		}
 	} else {
 		if (conin->WaitForKey == NULL) {
 			key_pending = efi_readkey();
 		} else {
 			status = BS->CheckEvent(conin->WaitForKey);
 			key_pending = status == EFI_SUCCESS;
 		}
 	}
 
 	return (key_pending);
 }
 
 /* Plain direct access to EFI OutputString(). */
 void
 efi_cons_efiputchar(int c)
 {
 	CHAR16 buf[2];
 	EFI_STATUS status;
 
 	buf[0] = c;
         buf[1] = 0;     /* terminate string */
 
 	status = conout->TestString(conout, buf);
 	if (EFI_ERROR(status))
 		buf[0] = '?';
 	conout->OutputString(conout, buf);
 }
diff --git a/stand/efi/loader/Makefile b/stand/efi/loader/Makefile
index a0e7f95b8200..3cf2df933cc3 100644
--- a/stand/efi/loader/Makefile
+++ b/stand/efi/loader/Makefile
@@ -1,106 +1,118 @@
 # $FreeBSD$
 
 LOADER_NET_SUPPORT?=	yes
 LOADER_MSDOS_SUPPORT?=	yes
 LOADER_UFS_SUPPORT?=	yes
 LOADER_CD9660_SUPPORT?=	no
 LOADER_EXT2FS_SUPPORT?=	no
 
 .include <bsd.init.mk>
 
 LOADER?=	loader_${LOADER_INTERP}
 PROG=		${LOADER}.sym
 INTERNALPROG=
 WARNS?=		3
 
 # architecture-specific loader code
 SRCS=	autoload.c \
 	bootinfo.c \
 	conf.c \
 	copy.c \
 	efi_main.c \
 	framebuffer.c \
 	main.c \
 	self_reloc.c \
-	vers.c
+	vers.c \
+	gfx_fb.c \
+	8x16.c
 
 CFLAGS+=	-I${.CURDIR}/../loader
 .if ${MK_LOADER_ZFS} != "no"
 CFLAGS+=	-I${ZFSSRC}
 CFLAGS+=        -I${SYSDIR}/contrib/openzfs/include
 CFLAGS+=        -I${SYSDIR}/contrib/openzfs/include/os/freebsd/zfs
 CFLAGS+=	-DEFI_ZFS_BOOT
 HAVE_ZFS=	yes
 .endif
 
+CFLAGS.gfx_fb.c += -I$(SRCTOP)/sys/teken
+CFLAGS.gfx_fb.c += -I${SRCTOP}/sys/cddl/contrib/opensolaris/common/lz4
+CFLAGS.gfx_fb.c += -I${SRCTOP}/contrib/pnglite
+CFLAGS.gfx_fb.c += -DHAVE_MEMCPY -I${SRCTOP}/sys/contrib/zlib
+
 # We implement a slightly non-standard %S in that it always takes a
 # CHAR16 that's common in UEFI-land instead of a wchar_t. This only
 # seems to matter on arm64 where wchar_t defaults to an int instead
 # of a short. There's no good cast to use here so just ignore the
 # warnings for now.
 CWARNFLAGS.main.c+=	-Wno-format
 
 .PATH: ${.CURDIR}/../loader
 .PATH: ${.CURDIR}/../loader/arch/${MACHINE}
 .include "${.CURDIR}/../loader/arch/${MACHINE}/Makefile.inc"
 
 CFLAGS+=	-I${.CURDIR}
 CFLAGS+=	-I${.CURDIR}/arch/${MACHINE}
 CFLAGS+=	-I${EFISRC}/include
 CFLAGS+=	-I${EFISRC}/include/${MACHINE}
 CFLAGS+=	-I${SYSDIR}/contrib/dev/acpica/include
 CFLAGS+=	-I${BOOTSRC}/i386/libi386
 CFLAGS+=	-DEFI
 
 .if defined(HAVE_FDT) && ${MK_FDT} != "no"
 .include	"${BOOTSRC}/fdt.mk"
 LIBEFI_FDT=	${BOOTOBJ}/efi/fdt/libefi_fdt.a
 .endif
 
 # Include bcache code.
 HAVE_BCACHE=    yes
 
 .if defined(EFI_STAGING_SIZE)
 CFLAGS+=	-DEFI_STAGING_SIZE=${EFI_STAGING_SIZE}
 .endif
 
 .if ${MK_LOADER_EFI_SECUREBOOT} != "no"
 CFLAGS+= -DEFI_SECUREBOOT
 .endif
 
 NEWVERSWHAT=	"EFI loader" ${MACHINE}
 VERSION_FILE=	${.CURDIR}/../loader/version
 
 # Always add MI sources
 .include	"${BOOTSRC}/loader.mk"
 
+CLEANFILES+=	8x16.c
+
+8x16.c:		${SRCTOP}/contrib/terminus/ter-u16v.bdf
+	vtfontcvt -f compressed-source -o ${.TARGET} ${.ALLSRC}
+
 FILES+=	${LOADER}.efi
 FILESMODE_${LOADER}.efi=	${BINMODE}
 
 .if ${LOADER_INTERP} == ${LOADER_DEFAULT_INTERP}
 LINKS+=		${BINDIR}/${LOADER}.efi ${BINDIR}/loader.efi
 .endif
 
 LDSCRIPT=	${.CURDIR}/../loader/arch/${MACHINE}/ldscript.${MACHINE}
 LDFLAGS+=	-Wl,-T${LDSCRIPT},-Bsymbolic,-znotext -pie
 
 CLEANFILES+=	loader.efi
 
 ${LOADER}.efi: ${PROG}
 	if ${NM} ${.ALLSRC} | grep ' U '; then \
 		echo "Undefined symbols in ${.ALLSRC}"; \
 		exit 1; \
 	fi
 	SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH} \
 	${OBJCOPY} -j .peheader -j .text -j .sdata -j .data \
 		-j .dynamic -j .dynsym -j .rel.dyn \
 		-j .rela.dyn -j .reloc -j .eh_frame -j set_Xcommand_set \
 		-j set_Xficl_compile_set \
 		--output-target=${EFI_TARGET} ${.ALLSRC} ${.TARGET}
 
 LIBEFI=		${BOOTOBJ}/efi/libefi/libefi.a
 
 DPADD=		${LDR_INTERP} ${LIBEFI} ${LIBFDT} ${LIBEFI_FDT} ${LIBSA} ${LDSCRIPT}
 LDADD=		${LDR_INTERP} ${LIBEFI} ${LIBFDT} ${LIBEFI_FDT} ${LIBSA}
 
 .include <bsd.prog.mk>
diff --git a/stand/efi/loader/bootinfo.c b/stand/efi/loader/bootinfo.c
index b1df11acd53f..108f46c5f9c4 100644
--- a/stand/efi/loader/bootinfo.c
+++ b/stand/efi/loader/bootinfo.c
@@ -1,552 +1,557 @@
 /*-
  * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
  * Copyright (c) 2004, 2006 Marcel Moolenaar
  * Copyright (c) 2014 The FreeBSD Foundation
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
 
 #include <sys/cdefs.h>
 __FBSDID("$FreeBSD$");
 
 #include <stand.h>
 #include <string.h>
 #include <sys/param.h>
 #include <sys/linker.h>
 #include <sys/reboot.h>
 #include <sys/boot.h>
 #include <machine/cpufunc.h>
 #include <machine/elf.h>
 #include <machine/metadata.h>
 #include <machine/psl.h>
 
 #include <efi.h>
 #include <efilib.h>
 
 #include "bootstrap.h"
 #include "loader_efi.h"
 
 #if defined(__amd64__)
 #include <machine/specialreg.h>
 #endif
 
 #include "framebuffer.h"
 
 #if defined(LOADER_FDT_SUPPORT)
 #include <fdt_platform.h>
 #endif
 
 #ifdef LOADER_GELI_SUPPORT
 #include "geliboot.h"
 #endif
 
 int bi_load(char *args, vm_offset_t *modulep, vm_offset_t *kernendp);
 
 extern EFI_SYSTEM_TABLE	*ST;
 
 static int
 bi_getboothowto(char *kargs)
 {
 	const char *sw, *tmp;
 	char *opts;
 	char *console;
 	int howto, speed, port;
 	char buf[50];
 
 	howto = boot_parse_cmdline(kargs);
 	howto |= boot_env_to_howto();
 
 	console = getenv("console");
 	if (console != NULL) {
 		if (strcmp(console, "comconsole") == 0)
 			howto |= RB_SERIAL;
 		if (strcmp(console, "nullconsole") == 0)
 			howto |= RB_MUTE;
 #if defined(__i386__) || defined(__amd64__)
 		if (strcmp(console, "efi") == 0 &&
 		    getenv("efi_8250_uid") != NULL &&
 		    getenv("hw.uart.console") == NULL) {
 			/*
 			 * If we found a 8250 com port and com speed, we need to
 			 * tell the kernel where the serial port is, and how
 			 * fast. Ideally, we'd get the port from ACPI, but that
 			 * isn't running in the loader. Do the next best thing
 			 * by allowing it to be set by a loader.conf variable,
 			 * either a EFI specific one, or the compatible
 			 * comconsole_port if not. PCI support is needed, but
 			 * for that we'd ideally refactor the
 			 * libi386/comconsole.c code to have identical behavior.
 			 * We only try to set the port for cases where we saw
 			 * the Serial(x) node when parsing, otherwise
 			 * specialized hardware that has Uart nodes will have a
 			 * bogus address set.
 			 * But if someone specifically setup hw.uart.console,
 			 * don't override that.
 			 */
 			speed = -1;
 			port = -1;
 			tmp = getenv("efi_com_speed");
 			if (tmp != NULL)
 				speed = strtol(tmp, NULL, 0);
 			tmp = getenv("efi_com_port");
 			if (tmp == NULL)
 				tmp = getenv("comconsole_port");
 			if (tmp != NULL)
 				port = strtol(tmp, NULL, 0);
 			if (speed != -1 && port != -1) {
 				snprintf(buf, sizeof(buf), "io:%d,br:%d", port,
 				    speed);
 				env_setenv("hw.uart.console", EV_VOLATILE, buf,
 				    NULL, NULL);
 			}
 		}
 #endif
 	}
 
 	return (howto);
 }
 
 /*
  * Copy the environment into the load area starting at (addr).
  * Each variable is formatted as <name>=<value>, with a single nul
  * separating each variable, and a double nul terminating the environment.
  */
 static vm_offset_t
 bi_copyenv(vm_offset_t start)
 {
 	struct env_var *ep;
 	vm_offset_t addr, last;
 	size_t len;
 
 	addr = last = start;
 
 	/* Traverse the environment. */
 	for (ep = environ; ep != NULL; ep = ep->ev_next) {
 		len = strlen(ep->ev_name);
 		if ((size_t)archsw.arch_copyin(ep->ev_name, addr, len) != len)
 			break;
 		addr += len;
 		if (archsw.arch_copyin("=", addr, 1) != 1)
 			break;
 		addr++;
 		if (ep->ev_value != NULL) {
 			len = strlen(ep->ev_value);
 			if ((size_t)archsw.arch_copyin(ep->ev_value, addr, len) != len)
 				break;
 			addr += len;
 		}
 		if (archsw.arch_copyin("", addr, 1) != 1)
 			break;
 		last = ++addr;
 	}
 
 	if (archsw.arch_copyin("", last++, 1) != 1)
 		last = start;
 	return(last);
 }
 
 /*
  * Copy module-related data into the load area, where it can be
  * used as a directory for loaded modules.
  *
  * Module data is presented in a self-describing format.  Each datum
  * is preceded by a 32-bit identifier and a 32-bit size field.
  *
  * Currently, the following data are saved:
  *
  * MOD_NAME	(variable)		module name (string)
  * MOD_TYPE	(variable)		module type (string)
  * MOD_ARGS	(variable)		module parameters (string)
  * MOD_ADDR	sizeof(vm_offset_t)	module load address
  * MOD_SIZE	sizeof(size_t)		module size
  * MOD_METADATA	(variable)		type-specific metadata
  */
 #define	COPY32(v, a, c) {					\
 	uint32_t x = (v);					\
 	if (c)							\
 		archsw.arch_copyin(&x, a, sizeof(x));		\
 	a += sizeof(x);						\
 }
 
 #define	MOD_STR(t, a, s, c) {					\
 	COPY32(t, a, c);					\
 	COPY32(strlen(s) + 1, a, c);				\
 	if (c)							\
 		archsw.arch_copyin(s, a, strlen(s) + 1);	\
 	a += roundup(strlen(s) + 1, sizeof(u_long));		\
 }
 
 #define	MOD_NAME(a, s, c)	MOD_STR(MODINFO_NAME, a, s, c)
 #define	MOD_TYPE(a, s, c)	MOD_STR(MODINFO_TYPE, a, s, c)
 #define	MOD_ARGS(a, s, c)	MOD_STR(MODINFO_ARGS, a, s, c)
 
 #define	MOD_VAR(t, a, s, c) {					\
 	COPY32(t, a, c);					\
 	COPY32(sizeof(s), a, c);				\
 	if (c)							\
 		archsw.arch_copyin(&s, a, sizeof(s));		\
 	a += roundup(sizeof(s), sizeof(u_long));		\
 }
 
 #define	MOD_ADDR(a, s, c)	MOD_VAR(MODINFO_ADDR, a, s, c)
 #define	MOD_SIZE(a, s, c)	MOD_VAR(MODINFO_SIZE, a, s, c)
 
 #define	MOD_METADATA(a, mm, c) {				\
 	COPY32(MODINFO_METADATA | mm->md_type, a, c);		\
 	COPY32(mm->md_size, a, c);				\
 	if (c)							\
 		archsw.arch_copyin(mm->md_data, a, mm->md_size);	\
 	a += roundup(mm->md_size, sizeof(u_long));		\
 }
 
 #define	MOD_END(a, c) {						\
 	COPY32(MODINFO_END, a, c);				\
 	COPY32(0, a, c);					\
 }
 
 static vm_offset_t
 bi_copymodules(vm_offset_t addr)
 {
 	struct preloaded_file *fp;
 	struct file_metadata *md;
 	int c;
 	uint64_t v;
 
 	c = addr != 0;
 	/* Start with the first module on the list, should be the kernel. */
 	for (fp = file_findfile(NULL, NULL); fp != NULL; fp = fp->f_next) {
 		MOD_NAME(addr, fp->f_name, c); /* This must come first. */
 		MOD_TYPE(addr, fp->f_type, c);
 		if (fp->f_args)
 			MOD_ARGS(addr, fp->f_args, c);
 		v = fp->f_addr;
 #if defined(__arm__)
 		v -= __elfN(relocation_offset);
 #endif
 		MOD_ADDR(addr, v, c);
 		v = fp->f_size;
 		MOD_SIZE(addr, v, c);
 		for (md = fp->f_metadata; md != NULL; md = md->md_next)
 			if (!(md->md_type & MODINFOMD_NOCOPY))
 				MOD_METADATA(addr, md, c);
 	}
 	MOD_END(addr, c);
 	return(addr);
 }
 
 static EFI_STATUS
 efi_do_vmap(EFI_MEMORY_DESCRIPTOR *mm, UINTN sz, UINTN mmsz, UINT32 mmver)
 {
 	EFI_MEMORY_DESCRIPTOR *desc, *viter, *vmap;
 	EFI_STATUS ret;
 	int curr, ndesc, nset;
 
 	nset = 0;
 	desc = mm;
 	ndesc = sz / mmsz;
 	vmap = malloc(sz);
 	if (vmap == NULL)
 		/* This isn't really an EFI error case, but pretend it is */
 		return (EFI_OUT_OF_RESOURCES);
 	viter = vmap;
 	for (curr = 0; curr < ndesc;
 	    curr++, desc = NextMemoryDescriptor(desc, mmsz)) {
 		if ((desc->Attribute & EFI_MEMORY_RUNTIME) != 0) {
 			++nset;
 			desc->VirtualStart = desc->PhysicalStart;
 			*viter = *desc;
 			viter = NextMemoryDescriptor(viter, mmsz);
 		}
 	}
 	ret = RS->SetVirtualAddressMap(nset * mmsz, mmsz, mmver, vmap);
 	free(vmap);
 	return (ret);
 }
 
 static int
 bi_load_efi_data(struct preloaded_file *kfp)
 {
 	EFI_MEMORY_DESCRIPTOR *mm;
 	EFI_PHYSICAL_ADDRESS addr = 0;
 	EFI_STATUS status;
 	const char *efi_novmap;
 	size_t efisz;
 	UINTN efi_mapkey;
 	UINTN dsz, pages, retry, sz;
 	UINT32 mmver;
 	struct efi_map_header *efihdr;
 	bool do_vmap;
 
 #if defined(__amd64__) || defined(__aarch64__)
 	struct efi_fb efifb;
 
 	if (efi_find_framebuffer(&efifb) == 0) {
 		printf("EFI framebuffer information:\n");
 		printf("addr, size     0x%jx, 0x%jx\n", efifb.fb_addr,
 		    efifb.fb_size);
 		printf("dimensions     %d x %d\n", efifb.fb_width,
 		    efifb.fb_height);
 		printf("stride         %d\n", efifb.fb_stride);
 		printf("masks          0x%08x, 0x%08x, 0x%08x, 0x%08x\n",
 		    efifb.fb_mask_red, efifb.fb_mask_green, efifb.fb_mask_blue,
 		    efifb.fb_mask_reserved);
 
 		file_addmetadata(kfp, MODINFOMD_EFI_FB, sizeof(efifb), &efifb);
 	}
 #endif
 
 	do_vmap = true;
 	efi_novmap = getenv("efi_disable_vmap");
 	if (efi_novmap != NULL)
 		do_vmap = strcasecmp(efi_novmap, "YES") != 0;
 
 	efisz = (sizeof(struct efi_map_header) + 0xf) & ~0xf;
 
 	/*
 	 * Assign size of EFI_MEMORY_DESCRIPTOR to keep compatible with
 	 * u-boot which doesn't fill this value when buffer for memory
 	 * descriptors is too small (eg. 0 to obtain memory map size)
 	 */
 	dsz = sizeof(EFI_MEMORY_DESCRIPTOR);
 
 	/*
 	 * Allocate enough pages to hold the bootinfo block and the
 	 * memory map EFI will return to us. The memory map has an
 	 * unknown size, so we have to determine that first. Note that
 	 * the AllocatePages call can itself modify the memory map, so
 	 * we have to take that into account as well. The changes to
 	 * the memory map are caused by splitting a range of free
 	 * memory into two, so that one is marked as being loader
 	 * data.
 	 */
 
 	sz = 0;
 
 	/*
 	 * Matthew Garrett has observed at least one system changing the
 	 * memory map when calling ExitBootServices, causing it to return an
 	 * error, probably because callbacks are allocating memory.
 	 * So we need to retry calling it at least once.
 	 */
 	for (retry = 2; retry > 0; retry--) {
 		for (;;) {
 			status = BS->GetMemoryMap(&sz, mm, &efi_mapkey, &dsz, &mmver);
 			if (!EFI_ERROR(status))
 				break;
 
 			if (status != EFI_BUFFER_TOO_SMALL) {
 				printf("%s: GetMemoryMap error %lu\n", __func__,
 	                           EFI_ERROR_CODE(status));
 				return (EINVAL);
 			}
 
 			if (addr != 0)
 				BS->FreePages(addr, pages);
 
 			/* Add 10 descriptors to the size to allow for
 			 * fragmentation caused by calling AllocatePages */
 			sz += (10 * dsz);
 			pages = EFI_SIZE_TO_PAGES(sz + efisz);
 			status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData,
 					pages, &addr);
 			if (EFI_ERROR(status)) {
 				printf("%s: AllocatePages error %lu\n", __func__,
 				    EFI_ERROR_CODE(status));
 				return (ENOMEM);
 			}
 
 			/*
 			 * Read the memory map and stash it after bootinfo. Align the
 			 * memory map on a 16-byte boundary (the bootinfo block is page
 			 * aligned).
 			 */
 			efihdr = (struct efi_map_header *)(uintptr_t)addr;
 			mm = (void *)((uint8_t *)efihdr + efisz);
 			sz = (EFI_PAGE_SIZE * pages) - efisz;
 		}
 
 		status = BS->ExitBootServices(IH, efi_mapkey);
 		if (!EFI_ERROR(status))
 			break;
 	}
 
 	if (retry == 0) {
 		BS->FreePages(addr, pages);
 		printf("ExitBootServices error %lu\n", EFI_ERROR_CODE(status));
 		return (EINVAL);
 	}
 
 	/*
 	 * This may be disabled by setting efi_disable_vmap in
 	 * loader.conf(5). By default we will setup the virtual
 	 * map entries.
 	 */
 
 	if (do_vmap)
 		efi_do_vmap(mm, sz, dsz, mmver);
 	efihdr->memory_size = sz;
 	efihdr->descriptor_size = dsz;
 	efihdr->descriptor_version = mmver;
 	file_addmetadata(kfp, MODINFOMD_EFI_MAP, efisz + sz,
 	    efihdr);
 
 	return (0);
 }
 
 /*
  * Load the information expected by an amd64 kernel.
  *
  * - The 'boothowto' argument is constructed.
  * - The 'bootdev' argument is constructed.
  * - The 'bootinfo' struct is constructed, and copied into the kernel space.
  * - The kernel environment is copied into kernel space.
  * - Module metadata are formatted and placed in kernel space.
  */
 int
 bi_load(char *args, vm_offset_t *modulep, vm_offset_t *kernendp)
 {
 	struct preloaded_file *xp, *kfp;
 	struct devdesc *rootdev;
 	struct file_metadata *md;
 	vm_offset_t addr;
 	uint64_t kernend;
 	uint64_t envp;
 	vm_offset_t size;
 	char *rootdevname;
 	int howto;
 #if defined(LOADER_FDT_SUPPORT)
 	vm_offset_t dtbp;
 	int dtb_size;
 #endif
 #if defined(__arm__)
 	vm_offset_t vaddr;
 	size_t i;
 	/*
 	 * These metadata addreses must be converted for kernel after
 	 * relocation.
 	 */
 	uint32_t		mdt[] = {
 	    MODINFOMD_SSYM, MODINFOMD_ESYM, MODINFOMD_KERNEND,
-	    MODINFOMD_ENVP,
+	    MODINFOMD_ENVP, MODINFOMD_FONT,
 #if defined(LOADER_FDT_SUPPORT)
 	    MODINFOMD_DTBP
 #endif
 	};
 #endif
 
 	howto = bi_getboothowto(args);
 
 	/*
 	 * Allow the environment variable 'rootdev' to override the supplied
 	 * device. This should perhaps go to MI code and/or have $rootdev
 	 * tested/set by MI code before launching the kernel.
 	 */
 	rootdevname = getenv("rootdev");
 	archsw.arch_getdev((void**)(&rootdev), rootdevname, NULL);
 	if (rootdev == NULL) {
 		printf("Can't determine root device.\n");
 		return(EINVAL);
 	}
 
 	/* Try reading the /etc/fstab file to select the root device */
 	getrootmount(efi_fmtdev((void *)rootdev));
 
 	addr = 0;
 	for (xp = file_findfile(NULL, NULL); xp != NULL; xp = xp->f_next) {
 		if (addr < (xp->f_addr + xp->f_size))
 			addr = xp->f_addr + xp->f_size;
 	}
 
 	/* 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);
 
 	/* Pad to a page boundary. */
 	addr = roundup(addr, PAGE_SIZE);
 
 #if defined(LOADER_FDT_SUPPORT)
 	/* Handle device tree blob */
 	dtbp = addr;
 	dtb_size = fdt_copy(addr);
 		
 	/* Pad to a page boundary */
 	if (dtb_size)
 		addr += roundup(dtb_size, PAGE_SIZE);
 #endif
 
 	kfp = file_findfile(NULL, "elf kernel");
 	if (kfp == NULL)
 		kfp = file_findfile(NULL, "elf64 kernel");
 	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
 	bi_load_efi_data(kfp);
 
 	/* Figure out the size and location of the metadata. */
 	*modulep = addr;
 	size = bi_copymodules(0);
 	kernend = roundup(addr + size, PAGE_SIZE);
 	*kernendp = kernend;
 
 	/* patch MODINFOMD_KERNEND */
 	md = file_findmetadata(kfp, MODINFOMD_KERNEND);
 	bcopy(&kernend, md->md_data, sizeof kernend);
 
 #if defined(__arm__)
 	*modulep -= __elfN(relocation_offset);
 
 	/* Do relocation fixup on metadata of each module. */
 	for (xp = file_findfile(NULL, NULL); xp != NULL; xp = xp->f_next) {
 		for (i = 0; i < nitems(mdt); i++) {
 			md = file_findmetadata(xp, mdt[i]);
 			if (md) {
 				bcopy(md->md_data, &vaddr, sizeof vaddr);
 				vaddr -= __elfN(relocation_offset);
 				bcopy(&vaddr, md->md_data, sizeof vaddr);
 			}
 		}
 	}
 #endif
 
 	/* Copy module list and metadata. */
 	(void)bi_copymodules(addr);
 
 	return (0);
 }
diff --git a/stand/efi/loader/framebuffer.c b/stand/efi/loader/framebuffer.c
index dce49c69ab0e..fccbd82a09f7 100644
--- a/stand/efi/loader/framebuffer.c
+++ b/stand/efi/loader/framebuffer.c
@@ -1,768 +1,771 @@
 /*-
  * Copyright (c) 2013 The FreeBSD Foundation
  * All rights reserved.
  *
  * This software was developed by Benno Rice under sponsorship from
  * the FreeBSD Foundation.
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
 
 #include <sys/cdefs.h>
 __FBSDID("$FreeBSD$");
 
 #include <bootstrap.h>
 #include <sys/endian.h>
 #include <sys/param.h>
 #include <stand.h>
 
 #include <efi.h>
 #include <efilib.h>
 #include <efiuga.h>
 #include <efipciio.h>
 #include <machine/metadata.h>
 
+#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;
 
 static struct named_resolution {
 	const char *name;
 	const char *alias;
 	unsigned int width;
 	unsigned int height;
 } resolutions[] = {
 	{
 		.name = "480p",
 		.width = 640,
 		.height = 480,
 	},
 	{
 		.name = "720p",
 		.width = 1280,
 		.height = 720,
 	},
 	{
 		.name = "1080p",
 		.width = 1920,
 		.height = 1080,
 	},
 	{
 		.name = "2160p",
 		.alias = "4k",
 		.width = 3840,
 		.height = 2160,
 	},
 	{
 		.name = "5k",
 		.width = 5120,
 		.height = 2880,
 	}
 };
 
 static u_int
 efifb_color_depth(struct efi_fb *efifb)
 {
 	uint32_t mask;
 	u_int depth;
 
 	mask = efifb->fb_mask_red | efifb->fb_mask_green |
 	    efifb->fb_mask_blue | efifb->fb_mask_reserved;
 	if (mask == 0)
 		return (0);
 	for (depth = 1; mask != 1; depth++)
 		mask >>= 1;
 	return (depth);
 }
 
 static int
 efifb_mask_from_pixfmt(struct efi_fb *efifb, EFI_GRAPHICS_PIXEL_FORMAT pixfmt,
     EFI_PIXEL_BITMASK *pixinfo)
 {
 	int result;
 
 	result = 0;
 	switch (pixfmt) {
 	case PixelRedGreenBlueReserved8BitPerColor:
 		efifb->fb_mask_red = 0x000000ff;
 		efifb->fb_mask_green = 0x0000ff00;
 		efifb->fb_mask_blue = 0x00ff0000;
 		efifb->fb_mask_reserved = 0xff000000;
 		break;
 	case PixelBlueGreenRedReserved8BitPerColor:
 		efifb->fb_mask_red = 0x00ff0000;
 		efifb->fb_mask_green = 0x0000ff00;
 		efifb->fb_mask_blue = 0x000000ff;
 		efifb->fb_mask_reserved = 0xff000000;
 		break;
 	case PixelBitMask:
 		efifb->fb_mask_red = pixinfo->RedMask;
 		efifb->fb_mask_green = pixinfo->GreenMask;
 		efifb->fb_mask_blue = pixinfo->BlueMask;
 		efifb->fb_mask_reserved = pixinfo->ReservedMask;
 		break;
 	default:
 		result = 1;
 		break;
 	}
 	return (result);
 }
 
 static int
 efifb_from_gop(struct efi_fb *efifb, EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *mode,
     EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info)
 {
 	int result;
 
 	efifb->fb_addr = mode->FrameBufferBase;
 	efifb->fb_size = mode->FrameBufferSize;
 	efifb->fb_height = info->VerticalResolution;
 	efifb->fb_width = info->HorizontalResolution;
 	efifb->fb_stride = info->PixelsPerScanLine;
 	result = efifb_mask_from_pixfmt(efifb, info->PixelFormat,
 	    &info->PixelInformation);
 	return (result);
 }
 
 static ssize_t
 efifb_uga_find_pixel(EFI_UGA_DRAW_PROTOCOL *uga, u_int line,
     EFI_PCI_IO_PROTOCOL *pciio, uint64_t addr, uint64_t size)
 {
 	EFI_UGA_PIXEL pix0, pix1;
 	uint8_t *data1, *data2;
 	size_t count, maxcount = 1024;
 	ssize_t ofs;
 	EFI_STATUS status;
 	u_int idx;
 
 	status = uga->Blt(uga, &pix0, EfiUgaVideoToBltBuffer,
 	    0, line, 0, 0, 1, 1, 0);
 	if (EFI_ERROR(status)) {
 		printf("UGA BLT operation failed (video->buffer)");
 		return (-1);
 	}
 	pix1.Red = ~pix0.Red;
 	pix1.Green = ~pix0.Green;
 	pix1.Blue = ~pix0.Blue;
 	pix1.Reserved = 0;
 
 	data1 = calloc(maxcount, 2);
 	if (data1 == NULL) {
 		printf("Unable to allocate memory");
 		return (-1);
 	}
 	data2 = data1 + maxcount;
 
 	ofs = 0;
 	while (size > 0) {
 		count = min(size, maxcount);
 
 		status = pciio->Mem.Read(pciio, EfiPciIoWidthUint32,
 		    EFI_PCI_IO_PASS_THROUGH_BAR, addr + ofs, count >> 2,
 		    data1);
 		if (EFI_ERROR(status)) {
 			printf("Error reading frame buffer (before)");
 			goto fail;
 		}
 		status = uga->Blt(uga, &pix1, EfiUgaBltBufferToVideo,
 		    0, 0, 0, line, 1, 1, 0);
 		if (EFI_ERROR(status)) {
 			printf("UGA BLT operation failed (modify)");
 			goto fail;
 		}
 		status = pciio->Mem.Read(pciio, EfiPciIoWidthUint32,
 		    EFI_PCI_IO_PASS_THROUGH_BAR, addr + ofs, count >> 2,
 		    data2);
 		if (EFI_ERROR(status)) {
 			printf("Error reading frame buffer (after)");
 			goto fail;
 		}
 		status = uga->Blt(uga, &pix0, EfiUgaBltBufferToVideo,
 		    0, 0, 0, line, 1, 1, 0);
 		if (EFI_ERROR(status)) {
 			printf("UGA BLT operation failed (restore)");
 			goto fail;
 		}
 		for (idx = 0; idx < count; idx++) {
 			if (data1[idx] != data2[idx]) {
 				free(data1);
 				return (ofs + (idx & ~3));
 			}
 		}
 		ofs += count;
 		size -= count;
 	}
 	printf("No change detected in frame buffer");
 
  fail:
 	printf(" -- error %lu\n", EFI_ERROR_CODE(status));
 	free(data1);
 	return (-1);
 }
 
 static EFI_PCI_IO_PROTOCOL *
 efifb_uga_get_pciio(void)
 {
 	EFI_PCI_IO_PROTOCOL *pciio;
 	EFI_HANDLE *buf, *hp;
 	EFI_STATUS status;
 	UINTN bufsz;
 
 	/* Get all handles that support the UGA protocol. */
 	bufsz = 0;
 	status = BS->LocateHandle(ByProtocol, &uga_guid, NULL, &bufsz, NULL);
 	if (status != EFI_BUFFER_TOO_SMALL)
 		return (NULL);
 	buf = malloc(bufsz);
 	status = BS->LocateHandle(ByProtocol, &uga_guid, NULL, &bufsz, buf);
 	if (status != EFI_SUCCESS) {
 		free(buf);
 		return (NULL);
 	}
 	bufsz /= sizeof(EFI_HANDLE);
 
 	/* Get the PCI I/O interface of the first handle that supports it. */
 	pciio = NULL;
 	for (hp = buf; hp < buf + bufsz; hp++) {
 		status = OpenProtocolByHandle(*hp, &pciio_guid,
 		    (void **)&pciio);
 		if (status == EFI_SUCCESS) {
 			free(buf);
 			return (pciio);
 		}
 	}
 	free(buf);
 	return (NULL);
 }
 
 static EFI_STATUS
 efifb_uga_locate_framebuffer(EFI_PCI_IO_PROTOCOL *pciio, uint64_t *addrp,
     uint64_t *sizep)
 {
 	uint8_t *resattr;
 	uint64_t addr, size;
 	EFI_STATUS status;
 	u_int bar;
 
 	if (pciio == NULL)
 		return (EFI_DEVICE_ERROR);
 
 	/* Attempt to get the frame buffer address (imprecise). */
 	*addrp = 0;
 	*sizep = 0;
 	for (bar = 0; bar < 6; bar++) {
 		status = pciio->GetBarAttributes(pciio, bar, NULL,
 		    (void **)&resattr);
 		if (status != EFI_SUCCESS)
 			continue;
 		/* XXX magic offsets and constants. */
 		if (resattr[0] == 0x87 && resattr[3] == 0) {
 			/* 32-bit address space descriptor (MEMIO) */
 			addr = le32dec(resattr + 10);
 			size = le32dec(resattr + 22);
 		} else if (resattr[0] == 0x8a && resattr[3] == 0) {
 			/* 64-bit address space descriptor (MEMIO) */
 			addr = le64dec(resattr + 14);
 			size = le64dec(resattr + 38);
 		} else {
 			addr = 0;
 			size = 0;
 		}
 		BS->FreePool(resattr);
 		if (addr == 0 || size == 0)
 			continue;
 
 		/* We assume the largest BAR is the frame buffer. */
 		if (size > *sizep) {
 			*addrp = addr;
 			*sizep = size;
 		}
 	}
 	return ((*addrp == 0 || *sizep == 0) ? EFI_DEVICE_ERROR : 0);
 }
 
 static int
 efifb_from_uga(struct efi_fb *efifb, EFI_UGA_DRAW_PROTOCOL *uga)
 {
 	EFI_PCI_IO_PROTOCOL *pciio;
 	char *ev, *p;
 	EFI_STATUS status;
 	ssize_t offset;
 	uint64_t fbaddr;
 	uint32_t horiz, vert, stride;
 	uint32_t np, depth, refresh;
 
 	status = uga->GetMode(uga, &horiz, &vert, &depth, &refresh);
 	if (EFI_ERROR(status))
 		return (1);
 	efifb->fb_height = vert;
 	efifb->fb_width = horiz;
 	/* Paranoia... */
 	if (efifb->fb_height == 0 || efifb->fb_width == 0)
 		return (1);
 
 	/* The color masks are fixed AFAICT. */
 	efifb_mask_from_pixfmt(efifb, PixelBlueGreenRedReserved8BitPerColor,
 	    NULL);
 
 	/* pciio can be NULL on return! */
 	pciio = efifb_uga_get_pciio();
 
 	/* Try to find the frame buffer. */
 	status = efifb_uga_locate_framebuffer(pciio, &efifb->fb_addr,
 	    &efifb->fb_size);
 	if (EFI_ERROR(status)) {
 		efifb->fb_addr = 0;
 		efifb->fb_size = 0;
 	}
 
 	/*
 	 * There's no reliable way to detect the frame buffer or the
 	 * offset within the frame buffer of the visible region, nor
 	 * the stride. Our only option is to look at the system and
 	 * fill in the blanks based on that. Luckily, UGA was mostly
 	 * only used on Apple hardware.
 	 */
 	offset = -1;
 	ev = getenv("smbios.system.maker");
 	if (ev != NULL && !strcmp(ev, "Apple Inc.")) {
 		ev = getenv("smbios.system.product");
 		if (ev != NULL && !strcmp(ev, "iMac7,1")) {
 			/* These are the expected values we should have. */
 			horiz = 1680;
 			vert = 1050;
 			fbaddr = 0xc0000000;
 			/* These are the missing bits. */
 			offset = 0x10000;
 			stride = 1728;
 		} else if (ev != NULL && !strcmp(ev, "MacBook3,1")) {
 			/* These are the expected values we should have. */
 			horiz = 1280;
 			vert = 800;
 			fbaddr = 0xc0000000;
 			/* These are the missing bits. */
 			offset = 0x0;
 			stride = 2048;
 		}
 	}
 
 	/*
 	 * If this is hardware we know, make sure that it looks familiar
 	 * before we accept our hardcoded values.
 	 */
 	if (offset >= 0 && efifb->fb_width == horiz &&
 	    efifb->fb_height == vert && efifb->fb_addr == fbaddr) {
 		efifb->fb_addr += offset;
 		efifb->fb_size -= offset;
 		efifb->fb_stride = stride;
 		return (0);
 	} else if (offset >= 0) {
 		printf("Hardware make/model known, but graphics not "
 		    "as expected.\n");
 		printf("Console may not work!\n");
 	}
 
 	/*
 	 * The stride is equal or larger to the width. Often it's the
 	 * next larger power of two. We'll start with that...
 	 */
 	efifb->fb_stride = efifb->fb_width;
 	do {
 		np = efifb->fb_stride & (efifb->fb_stride - 1);
 		if (np) {
 			efifb->fb_stride |= (np - 1);
 			efifb->fb_stride++;
 		}
 	} while (np);
 
 	ev = getenv("hw.efifb.address");
 	if (ev == NULL) {
 		if (efifb->fb_addr == 0) {
 			printf("Please set hw.efifb.address and "
 			    "hw.efifb.stride.\n");
 			return (1);
 		}
 
 		/*
 		 * The visible part of the frame buffer may not start at
 		 * offset 0, so try to detect it. Note that we may not
 		 * always be able to read from the frame buffer, which
 		 * means that we may not be able to detect anything. In
 		 * that case, we would take a long time scanning for a
 		 * pixel change in the frame buffer, which would have it
 		 * appear that we're hanging, so we limit the scan to
 		 * 1/256th of the frame buffer. This number is mostly
 		 * based on PR 202730 and the fact that on a MacBoook,
 		 * where we can't read from the frame buffer the offset
 		 * of the visible region is 0. In short: we want to scan
 		 * enough to handle all adapters that have an offset
 		 * larger than 0 and we want to scan as little as we can
 		 * to not appear to hang when we can't read from the
 		 * frame buffer.
 		 */
 		offset = efifb_uga_find_pixel(uga, 0, pciio, efifb->fb_addr,
 		    efifb->fb_size >> 8);
 		if (offset == -1) {
 			printf("Unable to reliably detect frame buffer.\n");
 		} else if (offset > 0) {
 			efifb->fb_addr += offset;
 			efifb->fb_size -= offset;
 		}
 	} else {
 		offset = 0;
 		efifb->fb_size = efifb->fb_height * efifb->fb_stride * 4;
 		efifb->fb_addr = strtoul(ev, &p, 0);
 		if (*p != '\0')
 			return (1);
 	}
 
 	ev = getenv("hw.efifb.stride");
 	if (ev == NULL) {
 		if (pciio != NULL && offset != -1) {
 			/* Determine the stride. */
 			offset = efifb_uga_find_pixel(uga, 1, pciio,
 			    efifb->fb_addr, horiz * 8);
 			if (offset != -1)
 				efifb->fb_stride = offset >> 2;
 		} else {
 			printf("Unable to reliably detect the stride.\n");
 		}
 	} else {
 		efifb->fb_stride = strtoul(ev, &p, 0);
 		if (*p != '\0')
 			return (1);
 	}
 
 	/*
 	 * We finalized on the stride, so recalculate the size of the
 	 * frame buffer.
 	 */
 	efifb->fb_size = efifb->fb_height * efifb->fb_stride * 4;
 	return (0);
 }
 
 int
 efi_find_framebuffer(struct efi_fb *efifb)
 {
 	EFI_GRAPHICS_OUTPUT *gop;
 	EFI_UGA_DRAW_PROTOCOL *uga;
 	EFI_STATUS status;
 
 	status = BS->LocateProtocol(&gop_guid, NULL, (VOID **)&gop);
 	if (status == EFI_SUCCESS)
 		return (efifb_from_gop(efifb, gop->Mode, gop->Mode->Info));
 
 	status = BS->LocateProtocol(&uga_guid, NULL, (VOID **)&uga);
 	if (status == EFI_SUCCESS)
 		return (efifb_from_uga(efifb, uga));
 
 	return (1);
 }
 
 static void
 print_efifb(int mode, struct efi_fb *efifb, int verbose)
 {
 	u_int depth;
 
 	if (mode >= 0)
 		printf("mode %d: ", mode);
 	depth = efifb_color_depth(efifb);
 	printf("%ux%ux%u, stride=%u", efifb->fb_width, efifb->fb_height,
 	    depth, efifb->fb_stride);
 	if (verbose) {
 		printf("\n    frame buffer: address=%jx, size=%jx",
 		    (uintmax_t)efifb->fb_addr, (uintmax_t)efifb->fb_size);
 		printf("\n    color mask: R=%08x, G=%08x, B=%08x\n",
 		    efifb->fb_mask_red, efifb->fb_mask_green,
 		    efifb->fb_mask_blue);
 	}
 }
 
 static bool
 efi_resolution_compare(struct named_resolution *res, const char *cmp)
 {
 
 	if (strcasecmp(res->name, cmp) == 0)
 		return (true);
 	if (res->alias != NULL && strcasecmp(res->alias, cmp) == 0)
 		return (true);
 	return (false);
 }
 
 
 static void
 efi_get_max_resolution(int *width, int *height)
 {
 	struct named_resolution *res;
 	char *maxres;
 	char *height_start, *width_start;
 	int idx;
 
 	*width = *height = 0;
 	maxres = getenv("efi_max_resolution");
 	/* No max_resolution set? Bail out; choose highest resolution */
 	if (maxres == NULL)
 		return;
 	/* See if it matches one of our known resolutions */
 	for (idx = 0; idx < nitems(resolutions); ++idx) {
 		res = &resolutions[idx];
 		if (efi_resolution_compare(res, maxres)) {
 			*width = res->width;
 			*height = res->height;
 			return;
 		}
 	}
 	/* Not a known resolution, try to parse it; make a copy we can modify */
 	maxres = strdup(maxres);
 	if (maxres == NULL)
 		return;
 	height_start = strchr(maxres, 'x');
 	if (height_start == NULL) {
 		free(maxres);
 		return;
 	}
 	width_start = maxres;
 	*height_start++ = 0;
 	/* Errors from this will effectively mean "no max" */
 	*width = (int)strtol(width_start, NULL, 0);
 	*height = (int)strtol(height_start, NULL, 0);
 	free(maxres);
 }
 
 static int
 gop_autoresize(EFI_GRAPHICS_OUTPUT *gop)
 {
 	struct efi_fb efifb;
 	EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
 	EFI_STATUS status;
 	UINTN infosz;
 	UINT32 best_mode, currdim, maxdim, mode;
 	int height, max_height, max_width, width;
 
 	best_mode = maxdim = 0;
 	efi_get_max_resolution(&max_width, &max_height);
 	for (mode = 0; mode < gop->Mode->MaxMode; mode++) {
 		status = gop->QueryMode(gop, mode, &infosz, &info);
 		if (EFI_ERROR(status))
 			continue;
 		efifb_from_gop(&efifb, gop->Mode, info);
 		width = info->HorizontalResolution;
 		height = info->VerticalResolution;
 		currdim = width * height;
 		if (currdim > maxdim) {
 			if ((max_width != 0 && width > max_width) ||
 			    (max_height != 0 && height > max_height))
 				continue;
 			maxdim = currdim;
 			best_mode = mode;
 		}
 	}
 
 	if (maxdim != 0) {
 		status = gop->SetMode(gop, best_mode);
 		if (EFI_ERROR(status)) {
 			snprintf(command_errbuf, sizeof(command_errbuf),
 			    "gop_autoresize: Unable to set mode to %u (error=%lu)",
 			    mode, EFI_ERROR_CODE(status));
 			return (CMD_ERROR);
 		}
-		(void) efi_cons_update_mode();
+		(void) cons_update_mode(true);
 	}
 	return (CMD_OK);
 }
 
 static int
 text_autoresize()
 {
 	SIMPLE_TEXT_OUTPUT_INTERFACE *conout;
 	EFI_STATUS status;
 	UINTN i, max_dim, best_mode, cols, rows;
 
 	conout = ST->ConOut;
 	max_dim = best_mode = 0;
 	for (i = 0; i < conout->Mode->MaxMode; i++) {
 		status = conout->QueryMode(conout, i, &cols, &rows);
 		if (EFI_ERROR(status))
 			continue;
 		if (cols * rows > max_dim) {
 			max_dim = cols * rows;
 			best_mode = i;
 		}
 	}
 	if (max_dim > 0)
 		conout->SetMode(conout, best_mode);
-	(void) efi_cons_update_mode();
+	(void) cons_update_mode(true);
 	return (CMD_OK);
 }
 
 static int
 uga_autoresize(EFI_UGA_DRAW_PROTOCOL *uga)
 {
 
 	return (text_autoresize());
 }
 
 COMMAND_SET(efi_autoresize, "efi-autoresizecons", "EFI Auto-resize Console", command_autoresize);
 
 static int
 command_autoresize(int argc, char *argv[])
 {
 	EFI_GRAPHICS_OUTPUT *gop;
 	EFI_UGA_DRAW_PROTOCOL *uga;
 	char *textmode;
 	EFI_STATUS status;
 	u_int mode;
 
 	textmode = getenv("hw.vga.textmode");
 	/* If it's set and non-zero, we'll select a console mode instead */
 	if (textmode != NULL && strcmp(textmode, "0") != 0)
 		return (text_autoresize());
 
 	gop = NULL;
 	uga = NULL;
 	status = BS->LocateProtocol(&gop_guid, NULL, (VOID **)&gop);
 	if (EFI_ERROR(status) == 0)
 		return (gop_autoresize(gop));
 
 	status = BS->LocateProtocol(&uga_guid, NULL, (VOID **)&uga);
 	if (EFI_ERROR(status) == 0)
 		return (uga_autoresize(uga));
 
 	snprintf(command_errbuf, sizeof(command_errbuf),
 	    "%s: Neither Graphics Output Protocol nor Universal Graphics Adapter present",
 	    argv[0]);
 
 	/*
 	 * Default to text_autoresize if we have neither GOP or UGA.  This won't
 	 * give us the most ideal resolution, but it will at least leave us
 	 * functional rather than failing the boot for an objectively bad
 	 * reason.
 	 */
 	return (text_autoresize());
 }
 
 COMMAND_SET(gop, "gop", "graphics output protocol", command_gop);
 
 static int
 command_gop(int argc, char *argv[])
 {
 	struct efi_fb efifb;
 	EFI_GRAPHICS_OUTPUT *gop;
 	EFI_STATUS status;
 	u_int mode;
 
 	status = BS->LocateProtocol(&gop_guid, NULL, (VOID **)&gop);
 	if (EFI_ERROR(status)) {
 		snprintf(command_errbuf, sizeof(command_errbuf),
 		    "%s: Graphics Output Protocol not present (error=%lu)",
 		    argv[0], EFI_ERROR_CODE(status));
 		return (CMD_ERROR);
 	}
 
 	if (argc < 2)
 		goto usage;
 
 	if (!strcmp(argv[1], "set")) {
 		char *cp;
 
 		if (argc != 3)
 			goto usage;
 		mode = strtol(argv[2], &cp, 0);
 		if (cp[0] != '\0') {
 			sprintf(command_errbuf, "mode is an integer");
 			return (CMD_ERROR);
 		}
 		status = gop->SetMode(gop, mode);
 		if (EFI_ERROR(status)) {
 			snprintf(command_errbuf, sizeof(command_errbuf),
 			    "%s: Unable to set mode to %u (error=%lu)",
 			    argv[0], mode, EFI_ERROR_CODE(status));
 			return (CMD_ERROR);
 		}
-		(void) 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);
 		print_efifb(gop->Mode->Mode, &efifb, 1);
 		printf("\n");
 	} else if (!strcmp(argv[1], "list")) {
 		EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
 		UINTN infosz;
 
 		if (argc != 2)
 			goto usage;
 		pager_open();
 		for (mode = 0; mode < gop->Mode->MaxMode; mode++) {
 			status = gop->QueryMode(gop, mode, &infosz, &info);
 			if (EFI_ERROR(status))
 				continue;
 			efifb_from_gop(&efifb, gop->Mode, info);
 			print_efifb(mode, &efifb, 0);
 			if (pager_output("\n"))
 				break;
 		}
 		pager_close();
 	}
 	return (CMD_OK);
 
  usage:
 	snprintf(command_errbuf, sizeof(command_errbuf),
-	    "usage: %s [list | get | set <mode>]", argv[0]);
+	    "usage: %s [list | get | set <mode> | off]", argv[0]);
 	return (CMD_ERROR);
 }
 
 COMMAND_SET(uga, "uga", "universal graphics adapter", command_uga);
 
 static int
 command_uga(int argc, char *argv[])
 {
 	struct efi_fb efifb;
 	EFI_UGA_DRAW_PROTOCOL *uga;
 	EFI_STATUS status;
 
 	status = BS->LocateProtocol(&uga_guid, NULL, (VOID **)&uga);
 	if (EFI_ERROR(status)) {
 		snprintf(command_errbuf, sizeof(command_errbuf),
 		    "%s: UGA Protocol not present (error=%lu)",
 		    argv[0], EFI_ERROR_CODE(status));
 		return (CMD_ERROR);
 	}
 
 	if (argc != 1)
 		goto usage;
 
 	if (efifb_from_uga(&efifb, uga) != CMD_OK) {
 		snprintf(command_errbuf, sizeof(command_errbuf),
 		    "%s: Unable to get UGA information", argv[0]);
 		return (CMD_ERROR);
 	}
 
 	print_efifb(-1, &efifb, 1);
 	printf("\n");
 	return (CMD_OK);
 
  usage:
 	snprintf(command_errbuf, sizeof(command_errbuf), "usage: %s", argv[0]);
 	return (CMD_ERROR);
 }
diff --git a/stand/efi/loader/main.c b/stand/efi/loader/main.c
index aad285f59092..ca41cd4a2610 100644
--- a/stand/efi/loader/main.c
+++ b/stand/efi/loader/main.c
@@ -1,1631 +1,1632 @@
 /*-
  * Copyright (c) 2008-2010 Rui Paulo
  * Copyright (c) 2006 Marcel Moolenaar
  * All rights reserved.
  *
  * Copyright (c) 2016-2019 Netflix, Inc. written by M. Warner Losh
  * 
  * 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 ``AS IS'' AND ANY EXPRESS OR
  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #include <sys/cdefs.h>
 __FBSDID("$FreeBSD$");
 
 #include <stand.h>
 
 #include <sys/disk.h>
 #include <sys/param.h>
 #include <sys/reboot.h>
 #include <sys/boot.h>
 #ifdef EFI_ZFS_BOOT
 #include <sys/zfs_bootenv.h>
 #endif
 #include <paths.h>
 #include <netinet/in.h>
 #include <netinet/in_systm.h>
 #include <stdint.h>
 #include <string.h>
 #include <setjmp.h>
 #include <disk.h>
 #include <dev_net.h>
 #include <net.h>
 
 #include <efi.h>
 #include <efilib.h>
 #include <efichar.h>
 
 #include <uuid.h>
 
 #include <bootstrap.h>
 #include <smbios.h>
 
 #include "efizfs.h"
 
 #include "loader_efi.h"
 
 struct arch_switch archsw;	/* MI/MD interface boundary */
 
 EFI_GUID acpi = ACPI_TABLE_GUID;
 EFI_GUID acpi20 = ACPI_20_TABLE_GUID;
 EFI_GUID devid = DEVICE_PATH_PROTOCOL;
 EFI_GUID imgid = LOADED_IMAGE_PROTOCOL;
 EFI_GUID mps = MPS_TABLE_GUID;
 EFI_GUID netid = EFI_SIMPLE_NETWORK_PROTOCOL;
 EFI_GUID smbios = SMBIOS_TABLE_GUID;
 EFI_GUID smbios3 = SMBIOS3_TABLE_GUID;
 EFI_GUID dxe = DXE_SERVICES_TABLE_GUID;
 EFI_GUID hoblist = HOB_LIST_TABLE_GUID;
 EFI_GUID lzmadecomp = LZMA_DECOMPRESSION_GUID;
 EFI_GUID mpcore = ARM_MP_CORE_INFO_TABLE_GUID;
 EFI_GUID esrt = ESRT_TABLE_GUID;
 EFI_GUID memtype = MEMORY_TYPE_INFORMATION_TABLE_GUID;
 EFI_GUID debugimg = DEBUG_IMAGE_INFO_TABLE_GUID;
 EFI_GUID fdtdtb = FDT_TABLE_GUID;
 EFI_GUID inputid = SIMPLE_TEXT_INPUT_PROTOCOL;
 
 /*
  * Number of seconds to wait for a keystroke before exiting with failure
  * in the event no currdev is found. -2 means always break, -1 means
  * never break, 0 means poll once and then reboot, > 0 means wait for
  * that many seconds. "fail_timeout" can be set in the environment as
  * well.
  */
 static int fail_timeout = 5;
 
 /*
  * Current boot variable
  */
 UINT16 boot_current;
 
 /*
  * Image that we booted from.
  */
 EFI_LOADED_IMAGE *boot_img;
 
 static bool
 has_keyboard(void)
 {
 	EFI_STATUS status;
 	EFI_DEVICE_PATH *path;
 	EFI_HANDLE *hin, *hin_end, *walker;
 	UINTN sz;
 	bool retval = false;
 
 	/*
 	 * Find all the handles that support the SIMPLE_TEXT_INPUT_PROTOCOL and
 	 * do the typical dance to get the right sized buffer.
 	 */
 	sz = 0;
 	hin = NULL;
 	status = BS->LocateHandle(ByProtocol, &inputid, 0, &sz, 0);
 	if (status == EFI_BUFFER_TOO_SMALL) {
 		hin = (EFI_HANDLE *)malloc(sz);
 		status = BS->LocateHandle(ByProtocol, &inputid, 0, &sz,
 		    hin);
 		if (EFI_ERROR(status))
 			free(hin);
 	}
 	if (EFI_ERROR(status))
 		return retval;
 
 	/*
 	 * Look at each of the handles. If it supports the device path protocol,
 	 * use it to get the device path for this handle. Then see if that
 	 * device path matches either the USB device path for keyboards or the
 	 * legacy device path for keyboards.
 	 */
 	hin_end = &hin[sz / sizeof(*hin)];
 	for (walker = hin; walker < hin_end; walker++) {
 		status = OpenProtocolByHandle(*walker, &devid, (void **)&path);
 		if (EFI_ERROR(status))
 			continue;
 
 		while (!IsDevicePathEnd(path)) {
 			/*
 			 * Check for the ACPI keyboard node. All PNP3xx nodes
 			 * are keyboards of different flavors. Note: It is
 			 * unclear of there's always a keyboard node when
 			 * there's a keyboard controller, or if there's only one
 			 * when a keyboard is detected at boot.
 			 */
 			if (DevicePathType(path) == ACPI_DEVICE_PATH &&
 			    (DevicePathSubType(path) == ACPI_DP ||
 				DevicePathSubType(path) == ACPI_EXTENDED_DP)) {
 				ACPI_HID_DEVICE_PATH  *acpi;
 
 				acpi = (ACPI_HID_DEVICE_PATH *)(void *)path;
 				if ((EISA_ID_TO_NUM(acpi->HID) & 0xff00) == 0x300 &&
 				    (acpi->HID & 0xffff) == PNP_EISA_ID_CONST) {
 					retval = true;
 					goto out;
 				}
 			/*
 			 * Check for USB keyboard node, if present. Unlike a
 			 * PS/2 keyboard, these definitely only appear when
 			 * connected to the system.
 			 */
 			} else if (DevicePathType(path) == MESSAGING_DEVICE_PATH &&
 			    DevicePathSubType(path) == MSG_USB_CLASS_DP) {
 				USB_CLASS_DEVICE_PATH *usb;
 
 				usb = (USB_CLASS_DEVICE_PATH *)(void *)path;
 				if (usb->DeviceClass == 3 && /* HID */
 				    usb->DeviceSubClass == 1 && /* Boot devices */
 				    usb->DeviceProtocol == 1) { /* Boot keyboards */
 					retval = true;
 					goto out;
 				}
 			}
 			path = NextDevicePathNode(path);
 		}
 	}
 out:
 	free(hin);
 	return retval;
 }
 
 static void
 set_currdev(const char *devname)
 {
 
 	/*
 	 * Don't execute hooks here; we may need to try setting these more than
 	 * once here if we're probing for the ZFS pool we're supposed to boot.
 	 * The currdev hook is intended to just validate user input anyways,
 	 * while the loaddev hook makes it immutable once we've determined what
 	 * the proper currdev is.
 	 */
 	env_setenv("currdev", EV_VOLATILE | EV_NOHOOK, devname, efi_setcurrdev,
 	    env_nounset);
 	env_setenv("loaddev", EV_VOLATILE | EV_NOHOOK, devname, env_noset,
 	    env_nounset);
 }
 
 static void
 set_currdev_devdesc(struct devdesc *currdev)
 {
 	const char *devname;
 
 	devname = efi_fmtdev(currdev);
 	printf("Setting currdev to %s\n", devname);
 	set_currdev(devname);
 }
 
 static void
 set_currdev_devsw(struct devsw *dev, int unit)
 {
 	struct devdesc currdev;
 
 	currdev.d_dev = dev;
 	currdev.d_unit = unit;
 
 	set_currdev_devdesc(&currdev);
 }
 
 static void
 set_currdev_pdinfo(pdinfo_t *dp)
 {
 
 	/*
 	 * Disks are special: they have partitions. if the parent
 	 * pointer is non-null, we're a partition not a full disk
 	 * and we need to adjust currdev appropriately.
 	 */
 	if (dp->pd_devsw->dv_type == DEVT_DISK) {
 		struct disk_devdesc currdev;
 
 		currdev.dd.d_dev = dp->pd_devsw;
 		if (dp->pd_parent == NULL) {
 			currdev.dd.d_unit = dp->pd_unit;
 			currdev.d_slice = D_SLICENONE;
 			currdev.d_partition = D_PARTNONE;
 		} else {
 			currdev.dd.d_unit = dp->pd_parent->pd_unit;
 			currdev.d_slice = dp->pd_unit;
 			currdev.d_partition = D_PARTISGPT; /* XXX Assumes GPT */
 		}
 		set_currdev_devdesc((struct devdesc *)&currdev);
 	} else {
 		set_currdev_devsw(dp->pd_devsw, dp->pd_unit);
 	}
 }
 
 static bool
 sanity_check_currdev(void)
 {
 	struct stat st;
 
 	return (stat(PATH_DEFAULTS_LOADER_CONF, &st) == 0 ||
 #ifdef PATH_BOOTABLE_TOKEN
 	    stat(PATH_BOOTABLE_TOKEN, &st) == 0 || /* non-standard layout */
 #endif
 	    stat(PATH_KERNEL, &st) == 0);
 }
 
 #ifdef EFI_ZFS_BOOT
 static bool
 probe_zfs_currdev(uint64_t guid)
 {
 	char *devname;
 	struct zfs_devdesc currdev;
 	char *buf = NULL;
 	bool rv;
 
 	currdev.dd.d_dev = &zfs_dev;
 	currdev.dd.d_unit = 0;
 	currdev.pool_guid = guid;
 	currdev.root_guid = 0;
 	set_currdev_devdesc((struct devdesc *)&currdev);
 	devname = efi_fmtdev(&currdev);
 	init_zfs_boot_options(devname);
 
 	rv = sanity_check_currdev();
 	if (rv) {
 		buf = malloc(VDEV_PAD_SIZE);
 		if (buf != NULL) {
 			if (zfs_get_bootonce(&currdev, OS_BOOTONCE, buf,
 			    VDEV_PAD_SIZE) == 0) {
 				printf("zfs bootonce: %s\n", buf);
 				set_currdev(buf);
 				setenv("zfs-bootonce", buf, 1);
 			}
 			free(buf);
 			(void) zfs_attach_nvstore(&currdev);
 		}
 	}
 	return (rv);
 }
 #endif
 
 static bool
 try_as_currdev(pdinfo_t *hd, pdinfo_t *pp)
 {
 	uint64_t guid;
 
 #ifdef EFI_ZFS_BOOT
 	/*
 	 * If there's a zpool on this device, try it as a ZFS
 	 * filesystem, which has somewhat different setup than all
 	 * other types of fs due to imperfect loader integration.
 	 * This all stems from ZFS being both a device (zpool) and
 	 * a filesystem, plus the boot env feature.
 	 */
 	if (efizfs_get_guid_by_handle(pp->pd_handle, &guid))
 		return (probe_zfs_currdev(guid));
 #endif
 	/*
 	 * All other filesystems just need the pdinfo
 	 * initialized in the standard way.
 	 */
 	set_currdev_pdinfo(pp);
 	return (sanity_check_currdev());
 }
 
 /*
  * Sometimes we get filenames that are all upper case
  * and/or have backslashes in them. Filter all this out
  * if it looks like we need to do so.
  */
 static void
 fix_dosisms(char *p)
 {
 	while (*p) {
 		if (isupper(*p))
 			*p = tolower(*p);
 		else if (*p == '\\')
 			*p = '/';
 		p++;
 	}
 }
 
 #define SIZE(dp, edp) (size_t)((intptr_t)(void *)edp - (intptr_t)(void *)dp)
 
 enum { BOOT_INFO_OK = 0, BAD_CHOICE = 1, NOT_SPECIFIC = 2  };
 static int
 match_boot_info(char *boot_info, size_t bisz)
 {
 	uint32_t attr;
 	uint16_t fplen;
 	size_t len;
 	char *walker, *ep;
 	EFI_DEVICE_PATH *dp, *edp, *first_dp, *last_dp;
 	pdinfo_t *pp;
 	CHAR16 *descr;
 	char *kernel = NULL;
 	FILEPATH_DEVICE_PATH  *fp;
 	struct stat st;
 	CHAR16 *text;
 
 	/*
 	 * FreeBSD encodes its boot loading path into the boot loader
 	 * BootXXXX variable. We look for the last one in the path
 	 * and use that to load the kernel. However, if we only find
 	 * one DEVICE_PATH, then there's nothing specific and we should
 	 * fall back.
 	 *
 	 * In an ideal world, we'd look at the image handle we were
 	 * passed, match up with the loader we are and then return the
 	 * next one in the path. This would be most flexible and cover
 	 * many chain booting scenarios where you need to use this
 	 * boot loader to get to the next boot loader. However, that
 	 * doesn't work. We rarely have the path to the image booted
 	 * (just the device) so we can't count on that. So, we do the
 	 * next best thing: we look through the device path(s) passed
 	 * in the BootXXXX variable. If there's only one, we return
 	 * NOT_SPECIFIC. Otherwise, we look at the last one and try to
 	 * load that. If we can, we return BOOT_INFO_OK. Otherwise we
 	 * return BAD_CHOICE for the caller to sort out.
 	 */
 	if (bisz < sizeof(attr) + sizeof(fplen) + sizeof(CHAR16))
 		return NOT_SPECIFIC;
 	walker = boot_info;
 	ep = walker + bisz;
 	memcpy(&attr, walker, sizeof(attr));
 	walker += sizeof(attr);
 	memcpy(&fplen, walker, sizeof(fplen));
 	walker += sizeof(fplen);
 	descr = (CHAR16 *)(intptr_t)walker;
 	len = ucs2len(descr);
 	walker += (len + 1) * sizeof(CHAR16);
 	last_dp = first_dp = dp = (EFI_DEVICE_PATH *)walker;
 	edp = (EFI_DEVICE_PATH *)(walker + fplen);
 	if ((char *)edp > ep)
 		return NOT_SPECIFIC;
 	while (dp < edp && SIZE(dp, edp) > sizeof(EFI_DEVICE_PATH)) {
 		text = efi_devpath_name(dp);
 		if (text != NULL) {
 			printf("   BootInfo Path: %S\n", text);
 			efi_free_devpath_name(text);
 		}
 		last_dp = dp;
 		dp = (EFI_DEVICE_PATH *)((char *)dp + efi_devpath_length(dp));
 	}
 
 	/*
 	 * If there's only one item in the list, then nothing was
 	 * specified. Or if the last path doesn't have a media
 	 * path in it. Those show up as various VenHw() nodes
 	 * which are basically opaque to us. Don't count those
 	 * as something specifc.
 	 */
 	if (last_dp == first_dp) {
 		printf("Ignoring Boot%04x: Only one DP found\n", boot_current);
 		return NOT_SPECIFIC;
 	}
 	if (efi_devpath_to_media_path(last_dp) == NULL) {
 		printf("Ignoring Boot%04x: No Media Path\n", boot_current);
 		return NOT_SPECIFIC;
 	}
 
 	/*
 	 * OK. At this point we either have a good path or a bad one.
 	 * Let's check.
 	 */
 	pp = efiblk_get_pdinfo_by_device_path(last_dp);
 	if (pp == NULL) {
 		printf("Ignoring Boot%04x: Device Path not found\n", boot_current);
 		return BAD_CHOICE;
 	}
 	set_currdev_pdinfo(pp);
 	if (!sanity_check_currdev()) {
 		printf("Ignoring Boot%04x: sanity check failed\n", boot_current);
 		return BAD_CHOICE;
 	}
 
 	/*
 	 * OK. We've found a device that matches, next we need to check the last
 	 * component of the path. If it's a file, then we set the default kernel
 	 * to that. Otherwise, just use this as the default root.
 	 *
 	 * Reminder: we're running very early, before we've parsed the defaults
 	 * file, so we may need to have a hack override.
 	 */
 	dp = efi_devpath_last_node(last_dp);
 	if (DevicePathType(dp) !=  MEDIA_DEVICE_PATH ||
 	    DevicePathSubType(dp) != MEDIA_FILEPATH_DP) {
 		printf("Using Boot%04x for root partition\n", boot_current);
 		return (BOOT_INFO_OK);		/* use currdir, default kernel */
 	}
 	fp = (FILEPATH_DEVICE_PATH *)dp;
 	ucs2_to_utf8(fp->PathName, &kernel);
 	if (kernel == NULL) {
 		printf("Not using Boot%04x: can't decode kernel\n", boot_current);
 		return (BAD_CHOICE);
 	}
 	if (*kernel == '\\' || isupper(*kernel))
 		fix_dosisms(kernel);
 	if (stat(kernel, &st) != 0) {
 		free(kernel);
 		printf("Not using Boot%04x: can't find %s\n", boot_current,
 		    kernel);
 		return (BAD_CHOICE);
 	}
 	setenv("kernel", kernel, 1);
 	free(kernel);
 	text = efi_devpath_name(last_dp);
 	if (text) {
 		printf("Using Boot%04x %S + %s\n", boot_current, text,
 		    kernel);
 		efi_free_devpath_name(text);
 	}
 
 	return (BOOT_INFO_OK);
 }
 
 /*
  * Look at the passed-in boot_info, if any. If we find it then we need
  * to see if we can find ourselves in the boot chain. If we can, and
  * there's another specified thing to boot next, assume that the file
  * is loaded from / and use that for the root filesystem. If can't
  * find the specified thing, we must fail the boot. If we're last on
  * the list, then we fallback to looking for the first available /
  * candidate (ZFS, if there's a bootable zpool, otherwise a UFS
  * partition that has either /boot/defaults/loader.conf on it or
  * /boot/kernel/kernel (the default kernel) that we can use.
  *
  * We always fail if we can't find the right thing. However, as
  * a concession to buggy UEFI implementations, like u-boot, if
  * we have determined that the host is violating the UEFI boot
  * manager protocol, we'll signal the rest of the program that
  * a drop to the OK boot loader prompt is possible.
  */
 static int
 find_currdev(bool do_bootmgr, bool is_last,
     char *boot_info, size_t boot_info_sz)
 {
 	pdinfo_t *dp, *pp;
 	EFI_DEVICE_PATH *devpath, *copy;
 	EFI_HANDLE h;
 	CHAR16 *text;
 	struct devsw *dev;
 	int unit;
 	uint64_t extra;
 	int rv;
 	char *rootdev;
 
 	/*
 	 * First choice: if rootdev is already set, use that, even if
 	 * it's wrong.
 	 */
 	rootdev = getenv("rootdev");
 	if (rootdev != NULL) {
 		printf("    Setting currdev to configured rootdev %s\n",
 		    rootdev);
 		set_currdev(rootdev);
 		return (0);
 	}
 
 	/*
 	 * Second choice: If uefi_rootdev is set, translate that UEFI device
 	 * path to the loader's internal name and use that.
 	 */
 	do {
 		rootdev = getenv("uefi_rootdev");
 		if (rootdev == NULL)
 			break;
 		devpath = efi_name_to_devpath(rootdev);
 		if (devpath == NULL)
 			break;
 		dp = efiblk_get_pdinfo_by_device_path(devpath);
 		efi_devpath_free(devpath);
 		if (dp == NULL)
 			break;
 		printf("    Setting currdev to UEFI path %s\n",
 		    rootdev);
 		set_currdev_pdinfo(dp);
 		return (0);
 	} while (0);
 
 	/*
 	 * Third choice: If we can find out image boot_info, and there's
 	 * a follow-on boot image in that boot_info, use that. In this
 	 * case root will be the partition specified in that image and
 	 * we'll load the kernel specified by the file path. Should there
 	 * not be a filepath, we use the default. This filepath overrides
 	 * loader.conf.
 	 */
 	if (do_bootmgr) {
 		rv = match_boot_info(boot_info, boot_info_sz);
 		switch (rv) {
 		case BOOT_INFO_OK:	/* We found it */
 			return (0);
 		case BAD_CHOICE:	/* specified file not found -> error */
 			/* XXX do we want to have an escape hatch for last in boot order? */
 			return (ENOENT);
 		} /* Nothing specified, try normal match */
 	}
 
 #ifdef EFI_ZFS_BOOT
 	/*
 	 * Did efi_zfs_probe() detect the boot pool? If so, use the zpool
 	 * it found, if it's sane. ZFS is the only thing that looks for
 	 * disks and pools to boot. This may change in the future, however,
 	 * if we allow specifying which pool to boot from via UEFI variables
 	 * rather than the bootenv stuff that FreeBSD uses today.
 	 */
 	if (pool_guid != 0) {
 		printf("Trying ZFS pool\n");
 		if (probe_zfs_currdev(pool_guid))
 			return (0);
 	}
 #endif /* EFI_ZFS_BOOT */
 
 	/*
 	 * Try to find the block device by its handle based on the
 	 * image we're booting. If we can't find a sane partition,
 	 * search all the other partitions of the disk. We do not
 	 * search other disks because it's a violation of the UEFI
 	 * boot protocol to do so. We fail and let UEFI go on to
 	 * the next candidate.
 	 */
 	dp = efiblk_get_pdinfo_by_handle(boot_img->DeviceHandle);
 	if (dp != NULL) {
 		text = efi_devpath_name(dp->pd_devpath);
 		if (text != NULL) {
 			printf("Trying ESP: %S\n", text);
 			efi_free_devpath_name(text);
 		}
 		set_currdev_pdinfo(dp);
 		if (sanity_check_currdev())
 			return (0);
 		if (dp->pd_parent != NULL) {
 			pdinfo_t *espdp = dp;
 			dp = dp->pd_parent;
 			STAILQ_FOREACH(pp, &dp->pd_part, pd_link) {
 				/* Already tried the ESP */
 				if (espdp == pp)
 					continue;
 				/*
 				 * Roll up the ZFS special case
 				 * for those partitions that have
 				 * zpools on them.
 				 */
 				text = efi_devpath_name(pp->pd_devpath);
 				if (text != NULL) {
 					printf("Trying: %S\n", text);
 					efi_free_devpath_name(text);
 				}
 				if (try_as_currdev(dp, pp))
 					return (0);
 			}
 		}
 	}
 
 	/*
 	 * Try the device handle from our loaded image first.  If that
 	 * fails, use the device path from the loaded image and see if
 	 * any of the nodes in that path match one of the enumerated
 	 * handles. Currently, this handle list is only for netboot.
 	 */
 	if (efi_handle_lookup(boot_img->DeviceHandle, &dev, &unit, &extra) == 0) {
 		set_currdev_devsw(dev, unit);
 		if (sanity_check_currdev())
 			return (0);
 	}
 
 	copy = NULL;
 	devpath = efi_lookup_image_devpath(IH);
 	while (devpath != NULL) {
 		h = efi_devpath_handle(devpath);
 		if (h == NULL)
 			break;
 
 		free(copy);
 		copy = NULL;
 
 		if (efi_handle_lookup(h, &dev, &unit, &extra) == 0) {
 			set_currdev_devsw(dev, unit);
 			if (sanity_check_currdev())
 				return (0);
 		}
 
 		devpath = efi_lookup_devpath(h);
 		if (devpath != NULL) {
 			copy = efi_devpath_trim(devpath);
 			devpath = copy;
 		}
 	}
 	free(copy);
 
 	return (ENOENT);
 }
 
 static bool
 interactive_interrupt(const char *msg)
 {
 	time_t now, then, last;
 
 	last = 0;
 	now = then = getsecs();
 	printf("%s\n", msg);
 	if (fail_timeout == -2)		/* Always break to OK */
 		return (true);
 	if (fail_timeout == -1)		/* Never break to OK */
 		return (false);
 	do {
 		if (last != now) {
 			printf("press any key to interrupt reboot in %d seconds\r",
 			    fail_timeout - (int)(now - then));
 			last = now;
 		}
 
 		/* XXX no pause or timeout wait for char */
 		if (ischar())
 			return (true);
 		now = getsecs();
 	} while (now - then < fail_timeout);
 	return (false);
 }
 
 static int
 parse_args(int argc, CHAR16 *argv[])
 {
 	int i, j, howto;
 	bool vargood;
 	char var[128];
 
 	/*
 	 * Parse the args to set the console settings, etc
 	 * boot1.efi passes these in, if it can read /boot.config or /boot/config
 	 * or iPXE may be setup to pass these in. Or the optional argument in the
 	 * boot environment was used to pass these arguments in (in which case
 	 * neither /boot.config nor /boot/config are consulted).
 	 *
 	 * Loop through the args, and for each one that contains an '=' that is
 	 * not the first character, add it to the environment.  This allows
 	 * loader and kernel env vars to be passed on the command line.  Convert
 	 * args from UCS-2 to ASCII (16 to 8 bit) as they are copied (though this
 	 * method is flawed for non-ASCII characters).
 	 */
 	howto = 0;
 	for (i = 1; i < argc; i++) {
 		cpy16to8(argv[i], var, sizeof(var));
 		howto |= boot_parse_arg(var);
 	}
 
 	return (howto);
 }
 
 static void
 setenv_int(const char *key, int val)
 {
 	char buf[20];
 
 	snprintf(buf, sizeof(buf), "%d", val);
 	setenv(key, buf, 1);
 }
 
 /*
  * Parse ConOut (the list of consoles active) and see if we can find a
  * serial port and/or a video port. It would be nice to also walk the
  * ACPI name space to map the UID for the serial port to a port. The
  * latter is especially hard.
  */
 int
 parse_uefi_con_out(void)
 {
 	int how, rv;
 	int vid_seen = 0, com_seen = 0, seen = 0;
 	size_t sz;
 	char buf[4096], *ep;
 	EFI_DEVICE_PATH *node;
 	ACPI_HID_DEVICE_PATH  *acpi;
 	UART_DEVICE_PATH  *uart;
 	bool pci_pending;
 
 	how = 0;
 	sz = sizeof(buf);
 	rv = efi_global_getenv("ConOut", buf, &sz);
 	if (rv != EFI_SUCCESS) {
 		/* If we don't have any ConOut default to serial */
 		how = RB_SERIAL;
 		goto out;
 	}
 	ep = buf + sz;
 	node = (EFI_DEVICE_PATH *)buf;
 	while ((char *)node < ep) {
 		if (IsDevicePathEndType(node)) {
 			if (pci_pending && vid_seen == 0)
 				vid_seen = ++seen;
 		}
 		pci_pending = false;
 		if (DevicePathType(node) == ACPI_DEVICE_PATH &&
 		    (DevicePathSubType(node) == ACPI_DP ||
 		    DevicePathSubType(node) == ACPI_EXTENDED_DP)) {
 			/* Check for Serial node */
 			acpi = (void *)node;
 			if (EISA_ID_TO_NUM(acpi->HID) == 0x501) {
 				setenv_int("efi_8250_uid", acpi->UID);
 				com_seen = ++seen;
 			}
 		} else if (DevicePathType(node) == MESSAGING_DEVICE_PATH &&
 		    DevicePathSubType(node) == MSG_UART_DP) {
 			com_seen = ++seen;
 			uart = (void *)node;
 			setenv_int("efi_com_speed", uart->BaudRate);
 		} else if (DevicePathType(node) == ACPI_DEVICE_PATH &&
 		    DevicePathSubType(node) == ACPI_ADR_DP) {
 			/* Check for AcpiAdr() Node for video */
 			vid_seen = ++seen;
 		} else if (DevicePathType(node) == HARDWARE_DEVICE_PATH &&
 		    DevicePathSubType(node) == HW_PCI_DP) {
 			/*
 			 * Note, vmware fusion has a funky console device
 			 *	PciRoot(0x0)/Pci(0xf,0x0)
 			 * which we can only detect at the end since we also
 			 * have to cope with:
 			 *	PciRoot(0x0)/Pci(0x1f,0x0)/Serial(0x1)
 			 * so only match it if it's last.
 			 */
 			pci_pending = true;
 		}
 		node = NextDevicePathNode(node);
 	}
 
 	/*
 	 * Truth table for RB_MULTIPLE | RB_SERIAL
 	 * Value		Result
 	 * 0			Use only video console
 	 * RB_SERIAL		Use only serial console
 	 * RB_MULTIPLE		Use both video and serial console
 	 *			(but video is primary so gets rc messages)
 	 * both			Use both video and serial console
 	 *			(but serial is primary so gets rc messages)
 	 *
 	 * Try to honor this as best we can. If only one of serial / video
 	 * found, then use that. Otherwise, use the first one we found.
 	 * This also implies if we found nothing, default to video.
 	 */
 	how = 0;
 	if (vid_seen && com_seen) {
 		how |= RB_MULTIPLE;
 		if (com_seen < vid_seen)
 			how |= RB_SERIAL;
 	} else if (com_seen)
 		how |= RB_SERIAL;
 out:
 	return (how);
 }
 
 void
 parse_loader_efi_config(EFI_HANDLE h, const char *env_fn)
 {
 	pdinfo_t *dp;
 	struct stat st;
 	int fd = -1;
 	char *env = NULL;
 
 	dp = efiblk_get_pdinfo_by_handle(h);
 	if (dp == NULL)
 		return;
 	set_currdev_pdinfo(dp);
 	if (stat(env_fn, &st) != 0)
 		return;
 	fd = open(env_fn, O_RDONLY);
 	if (fd == -1)
 		return;
 	env = malloc(st.st_size + 1);
 	if (env == NULL)
 		goto out;
 	if (read(fd, env, st.st_size) != st.st_size)
 		goto out;
 	env[st.st_size] = '\0';
 	boot_parse_cmdline(env);
 out:
 	free(env);
 	close(fd);
 }
 
 static void
 read_loader_env(const char *name, char *def_fn, bool once)
 {
 	UINTN len;
 	char *fn, *freeme = NULL;
 
 	len = 0;
 	fn = def_fn;
 	if (efi_freebsd_getenv(name, NULL, &len) == EFI_BUFFER_TOO_SMALL) {
 		freeme = fn = malloc(len + 1);
 		if (fn != NULL) {
 			if (efi_freebsd_getenv(name, fn, &len) != EFI_SUCCESS) {
 				free(fn);
 				fn = NULL;
 				printf(
 			    "Can't fetch FreeBSD::%s we know is there\n", name);
 			} else {
 				/*
 				 * if tagged as 'once' delete the env variable so we
 				 * only use it once.
 				 */
 				if (once)
 					efi_freebsd_delenv(name);
 				/*
 				 * We malloced 1 more than len above, then redid the call.
 				 * so now we have room at the end of the string to NUL terminate
 				 * it here, even if the typical idium would have '- 1' here to
 				 * not overflow. len should be the same on return both times.
 				 */
 				fn[len] = '\0';
 			}
 		} else {
 			printf(
 		    "Can't allocate %d bytes to fetch FreeBSD::%s env var\n",
 			    len, name);
 		}
 	}
 	if (fn) {
 		printf("    Reading loader env vars from %s\n", fn);
 		parse_loader_efi_config(boot_img->DeviceHandle, fn);
 	}
 }
 
 caddr_t
 ptov(uintptr_t x)
 {
 	return ((caddr_t)x);
 }
 
 EFI_STATUS
 main(int argc, CHAR16 *argv[])
 {
 	EFI_GUID *guid;
 	int howto, i, uhowto;
 	UINTN k;
 	bool has_kbd, is_last;
 	char *s;
 	EFI_DEVICE_PATH *imgpath;
 	CHAR16 *text;
 	EFI_STATUS rv;
 	size_t sz, bosz = 0, bisz = 0;
 	UINT16 boot_order[100];
 	char boot_info[4096];
 	char buf[32];
 	bool uefi_boot_mgr;
 
 	archsw.arch_autoload = efi_autoload;
 	archsw.arch_getdev = efi_getdev;
 	archsw.arch_copyin = efi_copyin;
 	archsw.arch_copyout = efi_copyout;
 #ifdef __amd64__
 	archsw.arch_hypervisor = x86_hypervisor;
 #endif
 	archsw.arch_readin = efi_readin;
 	archsw.arch_zfs_probe = efi_zfs_probe;
 
         /* Get our loaded image protocol interface structure. */
 	(void) OpenProtocolByHandle(IH, &imgid, (void **)&boot_img);
 
 	/*
 	 * 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.  We can use printf() etc. once this is
 	 * done. So, we set it to the efi console, then call console init. This
 	 * gets us printf early, but also primes the pump for all future console
 	 * changes to take effect, regardless of where they come from.
 	 */
 	setenv("console", "efi", 1);
 	uhowto = parse_uefi_con_out();
 #if defined(__aarch64__) || defined(__arm__) || defined(__riscv)
 	if ((uhowto & RB_SERIAL) != 0)
 		setenv("console", "comconsole", 1);
 #endif
 	cons_probe();
 
 	/* Init the time source */
 	efi_time_init();
 
 	/*
 	 * Initialise the block cache. Set the upper limit.
 	 */
 	bcache_init(32768, 512);
 
 	/*
 	 * Scan the BLOCK IO MEDIA handles then
 	 * march through the device switch probing for things.
 	 */
 	i = efipart_inithandles();
 	if (i != 0 && i != ENOENT) {
 		printf("efipart_inithandles failed with ERRNO %d, expect "
 		    "failures\n", i);
 	}
 
 	for (i = 0; devsw[i] != NULL; i++)
 		if (devsw[i]->dv_init != NULL)
 			(devsw[i]->dv_init)();
 
 	/*
 	 * Detect console settings two different ways: one via the command
 	 * args (eg -h) or via the UEFI ConOut variable.
 	 */
 	has_kbd = has_keyboard();
 	howto = parse_args(argc, argv);
 	if (!has_kbd && (howto & RB_PROBE))
 		howto |= RB_SERIAL | RB_MULTIPLE;
 	howto &= ~RB_PROBE;
 
 	/*
 	 * Read additional environment variables from the boot device's
 	 * "LoaderEnv" file. Any boot loader environment variable may be set
 	 * there, which are subtly different than loader.conf variables. Only
 	 * the 'simple' ones may be set so things like foo_load="YES" won't work
 	 * for two reasons.  First, the parser is simplistic and doesn't grok
 	 * quotes.  Second, because the variables that cause an action to happen
 	 * are parsed by the lua, 4th or whatever code that's not yet
 	 * loaded. This is relative to the root directory when loader.efi is
 	 * loaded off the UFS root drive (when chain booted), or from the ESP
 	 * when directly loaded by the BIOS.
 	 *
 	 * We also read in NextLoaderEnv if it was specified. This allows next boot
 	 * functionality to be implemented and to override anything in LoaderEnv.
 	 */
 	read_loader_env("LoaderEnv", "/efi/freebsd/loader.env", false);
 	read_loader_env("NextLoaderEnv", NULL, true);
 
 	/*
 	 * We now have two notions of console. howto should be viewed as
 	 * overrides. If console is already set, don't set it again.
 	 */
 #define	VIDEO_ONLY	0
 #define	SERIAL_ONLY	RB_SERIAL
 #define	VID_SER_BOTH	RB_MULTIPLE
 #define	SER_VID_BOTH	(RB_SERIAL | RB_MULTIPLE)
 #define	CON_MASK	(RB_SERIAL | RB_MULTIPLE)
 	if (strcmp(getenv("console"), "efi") == 0) {
 		if ((howto & CON_MASK) == 0) {
 			/* No override, uhowto is controlling and efi cons is perfect */
 			howto = howto | (uhowto & CON_MASK);
 		} else if ((howto & CON_MASK) == (uhowto & CON_MASK)) {
 			/* override matches what UEFI told us, efi console is perfect */
 		} else if ((uhowto & (CON_MASK)) != 0) {
 			/*
 			 * We detected a serial console on ConOut. All possible
 			 * overrides include serial. We can't really override what efi
 			 * gives us, so we use it knowing it's the best choice.
 			 */
 			/* Do nothing */
 		} else {
 			/*
 			 * We detected some kind of serial in the override, but ConOut
 			 * has no serial, so we have to sort out which case it really is.
 			 */
 			switch (howto & CON_MASK) {
 			case SERIAL_ONLY:
 				setenv("console", "comconsole", 1);
 				break;
 			case VID_SER_BOTH:
 				setenv("console", "efi comconsole", 1);
 				break;
 			case SER_VID_BOTH:
 				setenv("console", "comconsole efi", 1);
 				break;
 				/* case VIDEO_ONLY can't happen -- it's the first if above */
 			}
 		}
 	}
 
 	/*
 	 * howto is set now how we want to export the flags to the kernel, so
 	 * set the env based on it.
 	 */
 	boot_howto_to_env(howto);
 
 	if (efi_copy_init()) {
 		printf("failed to allocate staging area\n");
 		return (EFI_BUFFER_TOO_SMALL);
 	}
 
 	if ((s = getenv("fail_timeout")) != NULL)
 		fail_timeout = strtol(s, NULL, 10);
 
 	printf("%s\n", bootprog_info);
 	printf("   Command line arguments:");
 	for (i = 0; i < argc; i++)
 		printf(" %S", argv[i]);
 	printf("\n");
 
 	printf("   Image base: 0x%lx\n", (unsigned long)boot_img->ImageBase);
 	printf("   EFI version: %d.%02d\n", ST->Hdr.Revision >> 16,
 	    ST->Hdr.Revision & 0xffff);
 	printf("   EFI Firmware: %S (rev %d.%02d)\n", ST->FirmwareVendor,
 	    ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
 	printf("   Console: %s (%#x)\n", getenv("console"), howto);
 
 	/* Determine the devpath of our image so we can prefer it. */
 	text = efi_devpath_name(boot_img->FilePath);
 	if (text != NULL) {
 		printf("   Load Path: %S\n", text);
 		efi_setenv_freebsd_wcs("LoaderPath", text);
 		efi_free_devpath_name(text);
 	}
 
 	rv = OpenProtocolByHandle(boot_img->DeviceHandle, &devid,
 	    (void **)&imgpath);
 	if (rv == EFI_SUCCESS) {
 		text = efi_devpath_name(imgpath);
 		if (text != NULL) {
 			printf("   Load Device: %S\n", text);
 			efi_setenv_freebsd_wcs("LoaderDev", text);
 			efi_free_devpath_name(text);
 		}
 	}
 
 	if (getenv("uefi_ignore_boot_mgr") != NULL) {
 		printf("    Ignoring UEFI boot manager\n");
 		uefi_boot_mgr = false;
 	} else {
 		uefi_boot_mgr = true;
 		boot_current = 0;
 		sz = sizeof(boot_current);
 		rv = efi_global_getenv("BootCurrent", &boot_current, &sz);
 		if (rv == EFI_SUCCESS)
 			printf("   BootCurrent: %04x\n", boot_current);
 		else {
 			boot_current = 0xffff;
 			uefi_boot_mgr = false;
 		}
 
 		sz = sizeof(boot_order);
 		rv = efi_global_getenv("BootOrder", &boot_order, &sz);
 		if (rv == EFI_SUCCESS) {
 			printf("   BootOrder:");
 			for (i = 0; i < sz / sizeof(boot_order[0]); i++)
 				printf(" %04x%s", boot_order[i],
 				    boot_order[i] == boot_current ? "[*]" : "");
 			printf("\n");
 			is_last = boot_order[(sz / sizeof(boot_order[0])) - 1] == boot_current;
 			bosz = sz;
 		} else if (uefi_boot_mgr) {
 			/*
 			 * u-boot doesn't set BootOrder, but otherwise participates in the
 			 * boot manager protocol. So we fake it here and don't consider it
 			 * a failure.
 			 */
 			bosz = sizeof(boot_order[0]);
 			boot_order[0] = boot_current;
 			is_last = true;
 		}
 	}
 
 	/*
 	 * Next, find the boot info structure the UEFI boot manager is
 	 * supposed to setup. We need this so we can walk through it to
 	 * find where we are in the booting process and what to try to
 	 * boot next.
 	 */
 	if (uefi_boot_mgr) {
 		snprintf(buf, sizeof(buf), "Boot%04X", boot_current);
 		sz = sizeof(boot_info);
 		rv = efi_global_getenv(buf, &boot_info, &sz);
 		if (rv == EFI_SUCCESS)
 			bisz = sz;
 		else
 			uefi_boot_mgr = false;
 	}
 
 	/*
 	 * Disable the watchdog timer. By default the boot manager sets
 	 * the timer to 5 minutes before invoking a boot option. If we
 	 * want to return to the boot manager, we have to disable the
 	 * watchdog timer and since we're an interactive program, we don't
 	 * want to wait until the user types "quit". The timer may have
 	 * fired by then. We don't care if this fails. It does not prevent
 	 * normal functioning in any way...
 	 */
 	BS->SetWatchdogTimer(0, 0, 0, NULL);
 
 	/*
 	 * Initialize the trusted/forbidden certificates from UEFI.
 	 * They will be later used to verify the manifest(s),
 	 * which should contain hashes of verified files.
 	 * This needs to be initialized before any configuration files
 	 * are loaded.
 	 */
 #ifdef EFI_SECUREBOOT
 	ve_efi_init();
 #endif
 
 	/*
 	 * Try and find a good currdev based on the image that was booted.
 	 * It might be desirable here to have a short pause to allow falling
 	 * through to the boot loader instead of returning instantly to follow
 	 * the boot protocol and also allow an escape hatch for users wishing
 	 * to try something different.
 	 */
 	if (find_currdev(uefi_boot_mgr, is_last, boot_info, bisz) != 0)
 		if (uefi_boot_mgr &&
 		    !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__)
 	for (k = 0; k < ST->NumberOfTableEntries; k++) {
 		guid = &ST->ConfigurationTable[k].VendorGuid;
 		if (!memcmp(guid, &smbios, sizeof(EFI_GUID))) {
 			char buf[40];
 
 			snprintf(buf, sizeof(buf), "%p",
 			    ST->ConfigurationTable[k].VendorTable);
 			setenv("hint.smbios.0.mem", buf, 1);
 			smbios_detect(ST->ConfigurationTable[k].VendorTable);
 			break;
 		}
 	}
 #endif
 
 	interact();			/* doesn't return */
 
 	return (EFI_SUCCESS);		/* keep compiler happy */
 }
 
 COMMAND_SET(poweroff, "poweroff", "power off the system", command_poweroff);
 
 static int
 command_poweroff(int argc __unused, char *argv[] __unused)
 {
 	int i;
 
 	for (i = 0; devsw[i] != NULL; ++i)
 		if (devsw[i]->dv_cleanup != NULL)
 			(devsw[i]->dv_cleanup)();
 
 	RS->ResetSystem(EfiResetShutdown, EFI_SUCCESS, 0, NULL);
 
 	/* NOTREACHED */
 	return (CMD_ERROR);
 }
 
 COMMAND_SET(reboot, "reboot", "reboot the system", command_reboot);
 
 static int
 command_reboot(int argc, char *argv[])
 {
 	int i;
 
 	for (i = 0; devsw[i] != NULL; ++i)
 		if (devsw[i]->dv_cleanup != NULL)
 			(devsw[i]->dv_cleanup)();
 
 	RS->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
 
 	/* NOTREACHED */
 	return (CMD_ERROR);
 }
 
 COMMAND_SET(quit, "quit", "exit the loader", command_quit);
 
 static int
 command_quit(int argc, char *argv[])
 {
 	exit(0);
 	return (CMD_OK);
 }
 
 COMMAND_SET(memmap, "memmap", "print memory map", command_memmap);
 
 static int
 command_memmap(int argc __unused, char *argv[] __unused)
 {
 	UINTN sz;
 	EFI_MEMORY_DESCRIPTOR *map, *p;
 	UINTN key, dsz;
 	UINT32 dver;
 	EFI_STATUS status;
 	int i, ndesc;
 	char line[80];
 
 	sz = 0;
 	status = BS->GetMemoryMap(&sz, 0, &key, &dsz, &dver);
 	if (status != EFI_BUFFER_TOO_SMALL) {
 		printf("Can't determine memory map size\n");
 		return (CMD_ERROR);
 	}
 	map = malloc(sz);
 	status = BS->GetMemoryMap(&sz, map, &key, &dsz, &dver);
 	if (EFI_ERROR(status)) {
 		printf("Can't read memory map\n");
 		return (CMD_ERROR);
 	}
 
 	ndesc = sz / dsz;
 	snprintf(line, sizeof(line), "%23s %12s %12s %8s %4s\n",
 	    "Type", "Physical", "Virtual", "#Pages", "Attr");
 	pager_open();
 	if (pager_output(line)) {
 		pager_close();
 		return (CMD_OK);
 	}
 
 	for (i = 0, p = map; i < ndesc;
 	     i++, p = NextMemoryDescriptor(p, dsz)) {
 		snprintf(line, sizeof(line), "%23s %012jx %012jx %08jx ",
 		    efi_memory_type(p->Type), (uintmax_t)p->PhysicalStart,
 		    (uintmax_t)p->VirtualStart, (uintmax_t)p->NumberOfPages);
 		if (pager_output(line))
 			break;
 
 		if (p->Attribute & EFI_MEMORY_UC)
 			printf("UC ");
 		if (p->Attribute & EFI_MEMORY_WC)
 			printf("WC ");
 		if (p->Attribute & EFI_MEMORY_WT)
 			printf("WT ");
 		if (p->Attribute & EFI_MEMORY_WB)
 			printf("WB ");
 		if (p->Attribute & EFI_MEMORY_UCE)
 			printf("UCE ");
 		if (p->Attribute & EFI_MEMORY_WP)
 			printf("WP ");
 		if (p->Attribute & EFI_MEMORY_RP)
 			printf("RP ");
 		if (p->Attribute & EFI_MEMORY_XP)
 			printf("XP ");
 		if (p->Attribute & EFI_MEMORY_NV)
 			printf("NV ");
 		if (p->Attribute & EFI_MEMORY_MORE_RELIABLE)
 			printf("MR ");
 		if (p->Attribute & EFI_MEMORY_RO)
 			printf("RO ");
 		if (pager_output("\n"))
 			break;
 	}
 
 	pager_close();
 	return (CMD_OK);
 }
 
 COMMAND_SET(configuration, "configuration", "print configuration tables",
     command_configuration);
 
 static int
 command_configuration(int argc, char *argv[])
 {
 	UINTN i;
 	char *name;
 
 	printf("NumberOfTableEntries=%lu\n",
 		(unsigned long)ST->NumberOfTableEntries);
 
 	for (i = 0; i < ST->NumberOfTableEntries; i++) {
 		EFI_GUID *guid;
 
 		printf("  ");
 		guid = &ST->ConfigurationTable[i].VendorGuid;
 
 		if (efi_guid_to_name(guid, &name) == true) {
 			printf(name);
 			free(name);
 		} else {
 			printf("Error while translating UUID to name");
 		}
 		printf(" at %p\n", ST->ConfigurationTable[i].VendorTable);
 	}
 
 	return (CMD_OK);
 }
 
 
 COMMAND_SET(mode, "mode", "change or display EFI text modes", command_mode);
 
 static int
 command_mode(int argc, char *argv[])
 {
 	UINTN cols, rows;
 	unsigned int mode;
 	int i;
 	char *cp;
 	EFI_STATUS status;
 	SIMPLE_TEXT_OUTPUT_INTERFACE *conout;
 
 	conout = ST->ConOut;
 
 	if (argc > 1) {
 		mode = strtol(argv[1], &cp, 0);
 		if (cp[0] != '\0') {
 			printf("Invalid mode\n");
 			return (CMD_ERROR);
 		}
 		status = conout->QueryMode(conout, mode, &cols, &rows);
 		if (EFI_ERROR(status)) {
 			printf("invalid mode %d\n", mode);
 			return (CMD_ERROR);
 		}
 		status = conout->SetMode(conout, mode);
 		if (EFI_ERROR(status)) {
 			printf("couldn't set mode %d\n", mode);
 			return (CMD_ERROR);
 		}
-		(void) efi_cons_update_mode();
+		(void) cons_update_mode(true);
 		return (CMD_OK);
 	}
 
 	printf("Current mode: %d\n", conout->Mode->Mode);
 	for (i = 0; i <= conout->Mode->MaxMode; i++) {
 		status = conout->QueryMode(conout, i, &cols, &rows);
 		if (EFI_ERROR(status))
 			continue;
 		printf("Mode %d: %u columns, %u rows\n", i, (unsigned)cols,
 		    (unsigned)rows);
 	}
 
 	if (i != 0)
 		printf("Select a mode with the command \"mode <number>\"\n");
 
 	return (CMD_OK);
 }
 
 COMMAND_SET(lsefi, "lsefi", "list EFI handles", command_lsefi);
 
 static int
 command_lsefi(int argc __unused, char *argv[] __unused)
 {
 	char *name;
 	EFI_HANDLE *buffer = NULL;
 	EFI_HANDLE handle;
 	UINTN bufsz = 0, i, j;
 	EFI_STATUS status;
 	int ret = 0;
 
 	status = BS->LocateHandle(AllHandles, NULL, NULL, &bufsz, buffer);
 	if (status != EFI_BUFFER_TOO_SMALL) {
 		snprintf(command_errbuf, sizeof (command_errbuf),
 		    "unexpected error: %lld", (long long)status);
 		return (CMD_ERROR);
 	}
 	if ((buffer = malloc(bufsz)) == NULL) {
 		sprintf(command_errbuf, "out of memory");
 		return (CMD_ERROR);
 	}
 
 	status = BS->LocateHandle(AllHandles, NULL, NULL, &bufsz, buffer);
 	if (EFI_ERROR(status)) {
 		free(buffer);
 		snprintf(command_errbuf, sizeof (command_errbuf),
 		    "LocateHandle() error: %lld", (long long)status);
 		return (CMD_ERROR);
 	}
 
 	pager_open();
 	for (i = 0; i < (bufsz / sizeof (EFI_HANDLE)); i++) {
 		UINTN nproto = 0;
 		EFI_GUID **protocols = NULL;
 
 		handle = buffer[i];
 		printf("Handle %p", handle);
 		if (pager_output("\n"))
 			break;
 		/* device path */
 
 		status = BS->ProtocolsPerHandle(handle, &protocols, &nproto);
 		if (EFI_ERROR(status)) {
 			snprintf(command_errbuf, sizeof (command_errbuf),
 			    "ProtocolsPerHandle() error: %lld",
 			    (long long)status);
 			continue;
 		}
 
 		for (j = 0; j < nproto; j++) {
 			if (efi_guid_to_name(protocols[j], &name) == true) {
 				printf("  %s", name);
 				free(name);
 			} else {
 				printf("Error while translating UUID to name");
 			}
 			if ((ret = pager_output("\n")) != 0)
 				break;
 		}
 		BS->FreePool(protocols);
 		if (ret != 0)
 			break;
 	}
 	pager_close();
 	free(buffer);
 	return (CMD_OK);
 }
 
 #ifdef LOADER_FDT_SUPPORT
 extern int command_fdt_internal(int argc, char *argv[]);
 
 /*
  * Since proper fdt command handling function is defined in fdt_loader_cmd.c,
  * and declaring it as extern is in contradiction with COMMAND_SET() macro
  * (which uses static pointer), we're defining wrapper function, which
  * calls the proper fdt handling routine.
  */
 static int
 command_fdt(int argc, char *argv[])
 {
 
 	return (command_fdt_internal(argc, argv));
 }
 
 COMMAND_SET(fdt, "fdt", "flattened device tree handling", command_fdt);
 #endif
 
 /*
  * Chain load another efi loader.
  */
 static int
 command_chain(int argc, char *argv[])
 {
 	EFI_GUID LoadedImageGUID = LOADED_IMAGE_PROTOCOL;
 	EFI_HANDLE loaderhandle;
 	EFI_LOADED_IMAGE *loaded_image;
 	EFI_STATUS status;
 	struct stat st;
 	struct devdesc *dev;
 	char *name, *path;
 	void *buf;
 	int fd;
 
 	if (argc < 2) {
 		command_errmsg = "wrong number of arguments";
 		return (CMD_ERROR);
 	}
 
 	name = argv[1];
 
 	if ((fd = open(name, O_RDONLY)) < 0) {
 		command_errmsg = "no such file";
 		return (CMD_ERROR);
 	}
 
 #ifdef LOADER_VERIEXEC
 	if (verify_file(fd, name, 0, VE_MUST, __func__) < 0) {
 		sprintf(command_errbuf, "can't verify: %s", name);
 		close(fd);
 		return (CMD_ERROR);
 	}
 #endif
 
 	if (fstat(fd, &st) < -1) {
 		command_errmsg = "stat failed";
 		close(fd);
 		return (CMD_ERROR);
 	}
 
 	status = BS->AllocatePool(EfiLoaderCode, (UINTN)st.st_size, &buf);
 	if (status != EFI_SUCCESS) {
 		command_errmsg = "failed to allocate buffer";
 		close(fd);
 		return (CMD_ERROR);
 	}
 	if (read(fd, buf, st.st_size) != st.st_size) {
 		command_errmsg = "error while reading the file";
 		(void)BS->FreePool(buf);
 		close(fd);
 		return (CMD_ERROR);
 	}
 	close(fd);
 	status = BS->LoadImage(FALSE, IH, NULL, buf, st.st_size, &loaderhandle);
 	(void)BS->FreePool(buf);
 	if (status != EFI_SUCCESS) {
 		command_errmsg = "LoadImage failed";
 		return (CMD_ERROR);
 	}
 	status = OpenProtocolByHandle(loaderhandle, &LoadedImageGUID,
 	    (void **)&loaded_image);
 
 	if (argc > 2) {
 		int i, len = 0;
 		CHAR16 *argp;
 
 		for (i = 2; i < argc; i++)
 			len += strlen(argv[i]) + 1;
 
 		len *= sizeof (*argp);
 		loaded_image->LoadOptions = argp = malloc (len);
 		loaded_image->LoadOptionsSize = len;
 		for (i = 2; i < argc; i++) {
 			char *ptr = argv[i];
 			while (*ptr)
 				*(argp++) = *(ptr++);
 			*(argp++) = ' ';
 		}
 		*(--argv) = 0;
 	}
 
 	if (efi_getdev((void **)&dev, name, (const char **)&path) == 0) {
 #ifdef EFI_ZFS_BOOT
 		struct zfs_devdesc *z_dev;
 #endif
 		struct disk_devdesc *d_dev;
 		pdinfo_t *hd, *pd;
 
 		switch (dev->d_dev->dv_type) {
 #ifdef EFI_ZFS_BOOT
 		case DEVT_ZFS:
 			z_dev = (struct zfs_devdesc *)dev;
 			loaded_image->DeviceHandle =
 			    efizfs_get_handle_by_guid(z_dev->pool_guid);
 			break;
 #endif
 		case DEVT_NET:
 			loaded_image->DeviceHandle =
 			    efi_find_handle(dev->d_dev, dev->d_unit);
 			break;
 		default:
 			hd = efiblk_get_pdinfo(dev);
 			if (STAILQ_EMPTY(&hd->pd_part)) {
 				loaded_image->DeviceHandle = hd->pd_handle;
 				break;
 			}
 			d_dev = (struct disk_devdesc *)dev;
 			STAILQ_FOREACH(pd, &hd->pd_part, pd_link) {
 				/*
 				 * d_partition should be 255
 				 */
 				if (pd->pd_unit == (uint32_t)d_dev->d_slice) {
 					loaded_image->DeviceHandle =
 					    pd->pd_handle;
 					break;
 				}
 			}
 			break;
 		}
 	}
 
 	dev_cleanup();
 	status = BS->StartImage(loaderhandle, NULL, NULL);
 	if (status != EFI_SUCCESS) {
 		command_errmsg = "StartImage failed";
 		free(loaded_image->LoadOptions);
 		loaded_image->LoadOptions = NULL;
 		status = BS->UnloadImage(loaded_image);
 		return (CMD_ERROR);
 	}
 
 	return (CMD_ERROR);	/* not reached */
 }
 
 COMMAND_SET(chain, "chain", "chain load file", command_chain);
 
 extern struct in_addr servip;
 static int
 command_netserver(int argc, char *argv[])
 {
 	char *proto;
 	n_long rootaddr;
 
 	if (argc > 2) {
 		command_errmsg = "wrong number of arguments";
 		return (CMD_ERROR);
 	}
 	if (argc < 2) {
 		proto = netproto == NET_TFTP ? "tftp://" : "nfs://";
 		printf("Netserver URI: %s%s%s\n", proto, intoa(rootip.s_addr),
 		    rootpath);
 		return (CMD_OK);
 	}
 	if (argc == 2) {
 		strncpy(rootpath, argv[1], sizeof(rootpath));
 		rootpath[sizeof(rootpath) -1] = '\0';
 		if ((rootaddr = net_parse_rootpath()) != INADDR_NONE)
 			servip.s_addr = rootip.s_addr = rootaddr;
 		return (CMD_OK);
 	}
 	return (CMD_ERROR);	/* not reached */
 
 }
 
 COMMAND_SET(netserver, "netserver", "change or display netserver URI",
     command_netserver);
diff --git a/stand/ficl.mk b/stand/ficl.mk
index 0f96532cc65b..caa4aab951c3 100644
--- a/stand/ficl.mk
+++ b/stand/ficl.mk
@@ -1,22 +1,22 @@
 # $FreeBSD$
 
 # Common flags to build FICL related files
 
 .if ${MACHINE_CPUARCH} == "amd64" && ${DO32:U0} == 1
 FICL_CPUARCH=	i386
 .elif ${MACHINE_ARCH:Mmips64*} != ""
 FICL_CPUARCH=	mips64
 .else
 FICL_CPUARCH=	${MACHINE_CPUARCH}
 .endif
 
 .if ${MACHINE_CPUARCH} == "amd64" && ${DO32:U0} == 0
 CFLAGS+=	-fPIC
 .endif
 
 CFLAGS+=	-I${FICLSRC} -I${FICLSRC}/${FICL_CPUARCH} -I${LDRSRC}
-CFLAGS+=	-DBF_DICTSIZE=15000
+CFLAGS+=	-DBF_DICTSIZE=30000
 
 .if ${MK_LOADER_VERIEXEC} != "no"
 CFLAGS+= -DLOADER_VERIEXEC -I${SRCTOP}/lib/libsecureboot/h
 .endif
diff --git a/stand/ficl/Makefile b/stand/ficl/Makefile
index 35730851d884..0f70286ff7df 100644
--- a/stand/ficl/Makefile
+++ b/stand/ficl/Makefile
@@ -1,33 +1,35 @@
 # $FreeBSD$
 #
 
 .include <bsd.init.mk>
 .include "${BOOTSRC}/ficl.mk"
 
 .PATH: ${FICLSRC} ${FICLSRC}/${FICL_CPUARCH}
 
 BASE_SRCS=	dict.c ficl.c fileaccess.c float.c loader.c math64.c \
 		prefix.c search.c stack.c tools.c vm.c words.c
 
 SRCS=		${BASE_SRCS} sysdep.c softcore.c
 CLEANFILES+=	softcore.c testmain testmain.o
 
+CFLAGS.loader.c += -I${SRCTOP}/sys/teken
+CFLAGS.loader.c += -I${SRCTOP}/contrib/pnglite
 .ifmake testmain
 CFLAGS=	-DTESTMAIN -D_TESTMAIN
 CFLAGS+=	-I${FICLSRC} -I${FICLSRC}/${FICL_CPUARCH} -I${LDRSRC}
 SRCS+=		testmain.c
 PROG=		testmain
 .include <bsd.prog.mk>
 .else
 LIB=		ficl
 .include <bsd.lib.mk>
 .endif
 
 # Standard softwords
 .PATH: ${FICLSRC}/softwords
 SOFTWORDS=	softcore.fr jhlocal.fr marker.fr freebsd.fr ficllocal.fr \
 		ifbrack.fr
 
 softcore.c: ${SOFTWORDS} softcore.awk
 	(cd ${FICLSRC}/softwords; cat ${SOFTWORDS} \
 	    | awk -f softcore.awk -v datestamp="`LC_ALL=C date`") > ${.TARGET}
diff --git a/stand/ficl/loader.c b/stand/ficl/loader.c
index 2d1e1b908e9d..fca2b7421ffb 100644
--- a/stand/ficl/loader.c
+++ b/stand/ficl/loader.c
@@ -1,885 +1,1070 @@
 /*-
  * Copyright (c) 2000 Daniel Capo Sobral
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
  *	$FreeBSD$
  */
 
 /*******************************************************************
 ** l o a d e r . c
 ** Additional FICL words designed for FreeBSD's loader
 ** 
 *******************************************************************/
 
 #ifdef TESTMAIN
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <dirent.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #else
 #include <stand.h>
 #endif
 #include "bootstrap.h"
 #include <string.h>
 #include <uuid.h>
+#include <gfx_fb.h>
+#include <pnglite.h>
 #include "ficl.h"
 
 /*		FreeBSD's loader interaction words and extras
  *
  * 		setenv      ( value n name n' -- )
  * 		setenv?     ( value n name n' flag -- )
  * 		getenv      ( addr n -- addr' n' | -1 )
  * 		unsetenv    ( addr n -- )
  * 		copyin      ( addr addr' len -- )
  * 		copyout     ( addr addr' len -- )
  * 		findfile    ( name len type len' -- addr )
  * 		pnpdevices  ( -- addr )
  * 		pnphandlers ( -- addr )
  * 		ccall       ( [[...[p10] p9] ... p1] n addr -- result )
  *		uuid-from-string ( addr n -- addr' )
  *		uuid-to-string ( addr' -- addr n )
  * 		.#	    ( value -- )
  */
 
+#ifndef TESTMAIN
+/* ( flags x1 y1 x2 y2 -- flag ) */
+void
+ficl_term_putimage(FICL_VM *pVM)
+{
+        char *namep, *name;
+        int names;
+        unsigned long ret = FICL_FALSE;
+        uint32_t x1, y1, x2, y2, f;
+        png_t png;
+	int error;
+
+#if FICL_ROBUST > 1
+	vmCheckStack(pVM, 7, 1);
+#endif
+        names = stackPopINT(pVM->pStack);
+        namep = (char *) stackPopPtr(pVM->pStack);
+        y2 = stackPopINT(pVM->pStack);
+        x2 = stackPopINT(pVM->pStack);
+        y1 = stackPopINT(pVM->pStack);
+        x1 = stackPopINT(pVM->pStack);
+        f = stackPopINT(pVM->pStack);
+
+	x1 = gfx_state.tg_origin.tp_col + x1 * gfx_state.tg_font.vf_width;
+	y1 = gfx_state.tg_origin.tp_row + y1 * gfx_state.tg_font.vf_height;
+	if (x2 != 0) {
+		x2 = gfx_state.tg_origin.tp_col +
+		    x2 * gfx_state.tg_font.vf_width;
+	}
+	if (y2 != 0) {
+		y2 = gfx_state.tg_origin.tp_row +
+		    y2 * gfx_state.tg_font.vf_height;
+	}
+
+        name = ficlMalloc(names + 1);
+        if (!name)
+		vmThrowErr(pVM, "Error: out of memory");
+        (void) strncpy(name, namep, names);
+        name[names] = '\0';
+
+        if ((error = png_open(&png, name)) != PNG_NO_ERROR) {
+		if (f & FL_PUTIMAGE_DEBUG)
+			printf("%s\n", png_error_string(error));
+	} else {
+                if (gfx_fb_putimage(&png, x1, y1, x2, y2, f) == 0)
+                        ret = FICL_TRUE;        /* success */
+                (void) png_close(&png);
+	}
+        ficlFree(name);
+	stackPushUNS(pVM->pStack, ret);
+}
+
+/* ( flags x1 y1 x2 y2 -- flag ) */
+void
+ficl_fb_putimage(FICL_VM *pVM)
+{
+        char *namep, *name;
+        int names;
+        unsigned long ret = FICL_FALSE;
+        uint32_t x1, y1, x2, y2, f;
+        png_t png;
+	int error;
+
+#if FICL_ROBUST > 1
+	vmCheckStack(pVM, 7, 1);
+#endif
+        names = stackPopINT(pVM->pStack);
+        namep = (char *) stackPopPtr(pVM->pStack);
+        y2 = stackPopINT(pVM->pStack);
+        x2 = stackPopINT(pVM->pStack);
+        y1 = stackPopINT(pVM->pStack);
+        x1 = stackPopINT(pVM->pStack);
+        f = stackPopINT(pVM->pStack);
+
+        name = ficlMalloc(names + 1);
+        if (!name)
+		vmThrowErr(pVM, "Error: out of memory");
+        (void) strncpy(name, namep, names);
+        name[names] = '\0';
+
+        if ((error = png_open(&png, name)) != PNG_NO_ERROR) {
+		if (f & FL_PUTIMAGE_DEBUG)
+			printf("%s\n", png_error_string(error));
+	} else {
+                if (gfx_fb_putimage(&png, x1, y1, x2, y2, f) == 0)
+                        ret = FICL_TRUE;        /* success */
+                (void) png_close(&png);
+	}
+        ficlFree(name);
+	stackPushUNS(pVM->pStack, ret);
+}
+
+void
+ficl_fb_setpixel(FICL_VM *pVM)
+{
+        FICL_UNS x, y;
+
+#if FICL_ROBUST > 1
+	vmCheckStack(pVM, 2, 0);
+#endif
+
+        y = stackPopUNS(pVM->pStack);
+        x = stackPopUNS(pVM->pStack);
+        gfx_fb_setpixel(x, y);
+}
+
+void
+ficl_fb_line(FICL_VM *pVM)
+{
+	FICL_UNS x0, y0, x1, y1, wd;
+
+#if FICL_ROBUST > 1
+	vmCheckStack(pVM, 5, 0);
+#endif
+
+	wd = stackPopUNS(pVM->pStack);
+	y1 = stackPopUNS(pVM->pStack);
+	x1 = stackPopUNS(pVM->pStack);
+	y0 = stackPopUNS(pVM->pStack);
+	x0 = stackPopUNS(pVM->pStack);
+	gfx_fb_line(x0, y0, x1, y1, wd);
+}
+
+void
+ficl_fb_bezier(FICL_VM *pVM)
+{
+	FICL_UNS x0, y0, x1, y1, x2, y2, width;
+
+#if FICL_ROBUST > 1
+	vmCheckStack(pVM, 7, 0);
+#endif
+
+	width = stackPopUNS(pVM->pStack);
+	y2 = stackPopUNS(pVM->pStack);
+	x2 = stackPopUNS(pVM->pStack);
+	y1 = stackPopUNS(pVM->pStack);
+	x1 = stackPopUNS(pVM->pStack);
+	y0 = stackPopUNS(pVM->pStack);
+	x0 = stackPopUNS(pVM->pStack);
+	gfx_fb_bezier(x0, y0, x1, y1, x2, y2, width);
+}
+
+void
+ficl_fb_drawrect(FICL_VM *pVM)
+{
+	FICL_UNS x1, x2, y1, y2, fill;
+
+#if FICL_ROBUST > 1
+	vmCheckStack(pVM, 5, 0);
+#endif
+
+	fill = stackPopUNS(pVM->pStack);
+	y2 = stackPopUNS(pVM->pStack);
+	x2 = stackPopUNS(pVM->pStack);
+	y1 = stackPopUNS(pVM->pStack);
+	x1 = stackPopUNS(pVM->pStack);
+	gfx_fb_drawrect(x1, y1, x2, y2, fill);
+}
+
+void
+ficl_term_drawrect(FICL_VM *pVM)
+{
+	FICL_UNS x1, x2, y1, y2;
+
+#if FICL_ROBUST > 1
+	vmCheckStack(pVM, 4, 0);
+#endif
+
+	y2 = stackPopUNS(pVM->pStack);
+	x2 = stackPopUNS(pVM->pStack);
+	y1 = stackPopUNS(pVM->pStack);
+	x1 = stackPopUNS(pVM->pStack);
+	gfx_term_drawrect(x1, y1, x2, y2);
+}
+#endif	/* TESTMAIN */
+
 void
 ficlSetenv(FICL_VM *pVM)
 {
 #ifndef TESTMAIN
 	char	*name, *value;
 #endif
 	char	*namep, *valuep;
 	int	names, values;
 
 #if FICL_ROBUST > 1
 	vmCheckStack(pVM, 4, 0);
 #endif
 	names = stackPopINT(pVM->pStack);
 	namep = (char*) stackPopPtr(pVM->pStack);
 	values = stackPopINT(pVM->pStack);
 	valuep = (char*) stackPopPtr(pVM->pStack);
 
 #ifndef TESTMAIN
 	name = (char*) ficlMalloc(names+1);
 	if (!name)
 		vmThrowErr(pVM, "Error: out of memory");
 	strncpy(name, namep, names);
 	name[names] = '\0';
 	value = (char*) ficlMalloc(values+1);
 	if (!value)
 		vmThrowErr(pVM, "Error: out of memory");
 	strncpy(value, valuep, values);
 	value[values] = '\0';
 
 	setenv(name, value, 1);
 	ficlFree(name);
 	ficlFree(value);
 #endif
 
 	return;
 }
 
 void
 ficlSetenvq(FICL_VM *pVM)
 {
 #ifndef TESTMAIN
 	char	*name, *value;
 #endif
 	char	*namep, *valuep;
 	int	names, values, overwrite;
 
 #if FICL_ROBUST > 1
 	vmCheckStack(pVM, 5, 0);
 #endif
 	overwrite = stackPopINT(pVM->pStack);
 	names = stackPopINT(pVM->pStack);
 	namep = (char*) stackPopPtr(pVM->pStack);
 	values = stackPopINT(pVM->pStack);
 	valuep = (char*) stackPopPtr(pVM->pStack);
 
 #ifndef TESTMAIN
 	name = (char*) ficlMalloc(names+1);
 	if (!name)
 		vmThrowErr(pVM, "Error: out of memory");
 	strncpy(name, namep, names);
 	name[names] = '\0';
 	value = (char*) ficlMalloc(values+1);
 	if (!value)
 		vmThrowErr(pVM, "Error: out of memory");
 	strncpy(value, valuep, values);
 	value[values] = '\0';
 
 	setenv(name, value, overwrite);
 	ficlFree(name);
 	ficlFree(value);
 #endif
 
 	return;
 }
 
 void
 ficlGetenv(FICL_VM *pVM)
 {
 #ifndef TESTMAIN
 	char	*name, *value;
 #endif
 	char	*namep;
 	int	names;
 
 #if FICL_ROBUST > 1
 	vmCheckStack(pVM, 2, 2);
 #endif
 	names = stackPopINT(pVM->pStack);
 	namep = (char*) stackPopPtr(pVM->pStack);
 
 #ifndef TESTMAIN
 	name = (char*) ficlMalloc(names+1);
 	if (!name)
 		vmThrowErr(pVM, "Error: out of memory");
 	strncpy(name, namep, names);
 	name[names] = '\0';
 
 	value = getenv(name);
 	ficlFree(name);
 
 	if(value != NULL) {
 		stackPushPtr(pVM->pStack, value);
 		stackPushINT(pVM->pStack, strlen(value));
 	} else
 #endif
 		stackPushINT(pVM->pStack, -1);
 
 	return;
 }
 
 void
 ficlUnsetenv(FICL_VM *pVM)
 {
 #ifndef TESTMAIN
 	char	*name;
 #endif
 	char	*namep;
 	int	names;
 
 #if FICL_ROBUST > 1
 	vmCheckStack(pVM, 2, 0);
 #endif
 	names = stackPopINT(pVM->pStack);
 	namep = (char*) stackPopPtr(pVM->pStack);
 
 #ifndef TESTMAIN
 	name = (char*) ficlMalloc(names+1);
 	if (!name)
 		vmThrowErr(pVM, "Error: out of memory");
 	strncpy(name, namep, names);
 	name[names] = '\0';
 
 	unsetenv(name);
 	ficlFree(name);
 #endif
 
 	return;
 }
 
 void
 ficlCopyin(FICL_VM *pVM)
 {
 	void*		src;
 	vm_offset_t	dest;
 	size_t		len;
 
 #if FICL_ROBUST > 1
 	vmCheckStack(pVM, 3, 0);
 #endif
 
 	len = stackPopINT(pVM->pStack);
 	dest = stackPopINT(pVM->pStack);
 	src = stackPopPtr(pVM->pStack);
 
 #ifndef TESTMAIN
 	archsw.arch_copyin(src, dest, len);
 #endif
 
 	return;
 }
 
 void
 ficlCopyout(FICL_VM *pVM)
 {
 	void*		dest;
 	vm_offset_t	src;
 	size_t		len;
 
 #if FICL_ROBUST > 1
 	vmCheckStack(pVM, 3, 0);
 #endif
 
 	len = stackPopINT(pVM->pStack);
 	dest = stackPopPtr(pVM->pStack);
 	src = stackPopINT(pVM->pStack);
 
 #ifndef TESTMAIN
 	archsw.arch_copyout(src, dest, len);
 #endif
 
 	return;
 }
 
 void
 ficlFindfile(FICL_VM *pVM)
 {
 #ifndef TESTMAIN
 	char	*name, *type;
 #endif
 	char	*namep, *typep;
 	struct	preloaded_file* fp;
 	int	names, types;
 
 #if FICL_ROBUST > 1
 	vmCheckStack(pVM, 4, 1);
 #endif
 
 	types = stackPopINT(pVM->pStack);
 	typep = (char*) stackPopPtr(pVM->pStack);
 	names = stackPopINT(pVM->pStack);
 	namep = (char*) stackPopPtr(pVM->pStack);
 #ifndef TESTMAIN
 	name = (char*) ficlMalloc(names+1);
 	if (!name)
 		vmThrowErr(pVM, "Error: out of memory");
 	strncpy(name, namep, names);
 	name[names] = '\0';
 	type = (char*) ficlMalloc(types+1);
 	if (!type)
 		vmThrowErr(pVM, "Error: out of memory");
 	strncpy(type, typep, types);
 	type[types] = '\0';
 
 	fp = file_findfile(name, type);
 #else
 	fp = NULL;
 #endif
 	stackPushPtr(pVM->pStack, fp);
 
 	return;
 }
 
 #ifndef TESTMAIN
 
 /*	isvirtualized? - Return whether the loader runs under a
  *			hypervisor.
  *
  * isvirtualized? ( -- flag )
  */
 static void
 ficlIsvirtualizedQ(FICL_VM *pVM)
 {
 	FICL_INT flag;
 	const char *hv;
 
 #if FICL_ROBUST > 1
 	vmCheckStack(pVM, 0, 1);
 #endif
 
 	hv = (archsw.arch_hypervisor != NULL)
 	    ? (*archsw.arch_hypervisor)()
 	    : NULL;
 	flag = (hv != NULL) ? FICL_TRUE : FICL_FALSE;
 	stackPushINT(pVM->pStack, flag);
 }
 
 #endif /* ndef TESTMAIN */
 
 void
 ficlCcall(FICL_VM *pVM)
 {
 	int (*func)(int, ...);
 	int result, p[10];
 	int nparam, i;
 
 #if FICL_ROBUST > 1
 	vmCheckStack(pVM, 2, 0);
 #endif
 
 	func = stackPopPtr(pVM->pStack);
 	nparam = stackPopINT(pVM->pStack);
 
 #if FICL_ROBUST > 1
 	vmCheckStack(pVM, nparam, 1);
 #endif
 
 	for (i = 0; i < nparam; i++)
 		p[i] = stackPopINT(pVM->pStack);
 
 	result = func(p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8],
 	    p[9]);
 
 	stackPushINT(pVM->pStack, result);
 
 	return;
 }
 
 void
 ficlUuidFromString(FICL_VM *pVM)
 {
 #ifndef	TESTMAIN
 	char	*uuid;
 	uint32_t status;
 #endif
 	char	*uuidp;
 	int	uuids;
 	uuid_t	*u;
 
 #if FICL_ROBUST > 1
 	vmCheckStack(pVM, 2, 0);
 #endif
 
 	uuids = stackPopINT(pVM->pStack);
 	uuidp = (char *) stackPopPtr(pVM->pStack);
 
 #ifndef	TESTMAIN
 	uuid = (char *)ficlMalloc(uuids + 1);
 	if (!uuid)
 		vmThrowErr(pVM, "Error: out of memory");
 	strncpy(uuid, uuidp, uuids);
 	uuid[uuids] = '\0';
 
 	u = (uuid_t *)ficlMalloc(sizeof (*u));
 
 	uuid_from_string(uuid, u, &status);
 	ficlFree(uuid);
 	if (status != uuid_s_ok) {
 		ficlFree(u);
 		u = NULL;
 	}
 #else
 	u = NULL;
 #endif
 	stackPushPtr(pVM->pStack, u);
 
 
 	return;
 }
 
 void
 ficlUuidToString(FICL_VM *pVM)
 {
 #ifndef	TESTMAIN
 	char	*uuid;
 	uint32_t status;
 #endif
 	uuid_t	*u;
 
 #if FICL_ROBUST > 1
 	vmCheckStack(pVM, 1, 0);
 #endif
 
 	u = (uuid_t *)stackPopPtr(pVM->pStack);
 
 #ifndef	TESTMAIN
 	uuid_to_string(u, &uuid, &status);
 	if (status != uuid_s_ok) {
 		stackPushPtr(pVM->pStack, uuid);
 		stackPushINT(pVM->pStack, strlen(uuid));
 	} else
 #endif
 		stackPushINT(pVM->pStack, -1);
 
 	return;
 }
 
 /**************************************************************************
                         f i c l E x e c F D
 ** reads in text from file fd and passes it to ficlExec()
  * returns VM_OUTOFTEXT on success or the ficlExec() error code on
  * failure.
  */ 
 #define nLINEBUF 256
 int ficlExecFD(FICL_VM *pVM, int fd)
 {
     char    cp[nLINEBUF];
     int     nLine = 0, rval = VM_OUTOFTEXT;
     char    ch;
     CELL    id;
 
     id = pVM->sourceID;
     pVM->sourceID.i = fd;
 
     /* feed each line to ficlExec */
     while (1) {
 	int status, i;
 
 	i = 0;
 	while ((status = read(fd, &ch, 1)) > 0 && ch != '\n')
 	    cp[i++] = ch;
         nLine++;
 	if (!i) {
 	    if (status < 1)
 		break;
 	    continue;
 	}
         rval = ficlExecC(pVM, cp, i);
 	if(rval != VM_QUIT && rval != VM_USEREXIT && rval != VM_OUTOFTEXT)
         {
             pVM->sourceID = id;
             return rval; 
         }
     }
     /*
     ** Pass an empty line with SOURCE-ID == -1 to flush
     ** any pending REFILLs (as required by FILE wordset)
     */
     pVM->sourceID.i = -1;
     ficlExec(pVM, "");
 
     pVM->sourceID = id;
     return rval;
 }
 
 static void displayCellNoPad(FICL_VM *pVM)
 {
     CELL c;
 #if FICL_ROBUST > 1
     vmCheckStack(pVM, 1, 0);
 #endif
     c = stackPop(pVM->pStack);
     ltoa((c).i, pVM->pad, pVM->base);
     vmTextOut(pVM, pVM->pad, 0);
     return;
 }
 
 /*      isdir? - Return whether an fd corresponds to a directory.
  *
  * isdir? ( fd -- bool )
  */
 static void isdirQuestion(FICL_VM *pVM)
 {
     struct stat sb;
     FICL_INT flag;
     int fd;
 
 #if FICL_ROBUST > 1
     vmCheckStack(pVM, 1, 1);
 #endif
 
     fd = stackPopINT(pVM->pStack);
     flag = FICL_FALSE;
     do {
         if (fd < 0)
             break;
         if (fstat(fd, &sb) < 0)
             break;
         if (!S_ISDIR(sb.st_mode))
             break;
         flag = FICL_TRUE;
     } while (0);
     stackPushINT(pVM->pStack, flag);
 }
 
 /*          fopen - open a file and return new fd on stack.
  *
  * fopen ( ptr count mode -- fd )
  */
 static void pfopen(FICL_VM *pVM)
 {
     int     mode, fd, count;
     char    *ptr, *name;
 
 #if FICL_ROBUST > 1
     vmCheckStack(pVM, 3, 1);
 #endif
 
     mode = stackPopINT(pVM->pStack);    /* get mode */
     count = stackPopINT(pVM->pStack);   /* get count */
     ptr = stackPopPtr(pVM->pStack);     /* get ptr */
 
     if ((count < 0) || (ptr == NULL)) {
         stackPushINT(pVM->pStack, -1);
         return;
     }
 
     /* ensure that the string is null terminated */
     name = (char *)malloc(count+1);
     bcopy(ptr,name,count);
     name[count] = 0;
 
     /* open the file */
     fd = open(name, mode);
 #ifdef LOADER_VERIEXEC
     if (fd >= 0) {
 	if (verify_file(fd, name, 0, VE_GUESS, __func__) < 0) {
 	    /* not verified writing ok but reading is not */
 	    if ((mode & O_ACCMODE) != O_WRONLY) {
 		close(fd);
 		fd = -1;
 	    }
 	} else {
 	    /* verified reading ok but writing is not */
 	    if ((mode & O_ACCMODE) != O_RDONLY) {
 		close(fd);
 		fd = -1;
 	    }
 	}
     }
 #endif
     free(name);
     stackPushINT(pVM->pStack, fd);
     return;
 }
  
 /*          fclose - close a file who's fd is on stack.
  *
  * fclose ( fd -- )
  */
 static void pfclose(FICL_VM *pVM)
 {
     int fd;
 
 #if FICL_ROBUST > 1
     vmCheckStack(pVM, 1, 0);
 #endif
     fd = stackPopINT(pVM->pStack); /* get fd */
     if (fd != -1)
 	close(fd);
     return;
 }
 
 /*          fread - read file contents
  *
  * fread  ( fd buf nbytes  -- nread )
  */
 static void pfread(FICL_VM *pVM)
 {
     int     fd, len;
     char *buf;
 
 #if FICL_ROBUST > 1
     vmCheckStack(pVM, 3, 1);
 #endif
     len = stackPopINT(pVM->pStack); /* get number of bytes to read */
     buf = stackPopPtr(pVM->pStack); /* get buffer */
     fd = stackPopINT(pVM->pStack); /* get fd */
     if (len > 0 && buf && fd != -1)
 	stackPushINT(pVM->pStack, read(fd, buf, len));
     else
 	stackPushINT(pVM->pStack, -1);
     return;
 }
 
 /*      freaddir - read directory contents
  *
  * freaddir ( fd -- ptr len TRUE | FALSE )
  */
 static void pfreaddir(FICL_VM *pVM)
 {
 #ifdef TESTMAIN
     static struct dirent dirent;
     struct stat sb;
     char *buf;
     off_t off, ptr;
     u_int blksz;
     int bufsz;
 #endif
     struct dirent *d;
     int fd;
 
 #if FICL_ROBUST > 1
     vmCheckStack(pVM, 1, 3);
 #endif
 
     fd = stackPopINT(pVM->pStack);
 #if TESTMAIN
     /*
      * The readdirfd() function is specific to the loader environment.
      * We do the best we can to make freaddir work, but it's not at
      * all guaranteed.
      */
     d = NULL;
     buf = NULL;
     do {
 	if (fd == -1)
 	    break;
 	if (fstat(fd, &sb) == -1)
 	    break;
 	blksz = (sb.st_blksize) ? sb.st_blksize : getpagesize();
 	if ((blksz & (blksz - 1)) != 0)
 	    break;
 	buf = malloc(blksz);
 	if (buf == NULL)
 	    break;
 	off = lseek(fd, 0LL, SEEK_CUR);
 	if (off == -1)
 	    break;
 	ptr = off;
 	if (lseek(fd, 0, SEEK_SET) == -1)
 	    break;
 	bufsz = getdents(fd, buf, blksz);
 	while (bufsz > 0 && bufsz <= ptr) {
 	    ptr -= bufsz;
 	    bufsz = getdents(fd, buf, blksz);
 	}
 	if (bufsz <= 0)
 	    break;
 	d = (void *)(buf + ptr);
 	dirent = *d;
 	off += d->d_reclen;
 	d = (lseek(fd, off, SEEK_SET) != off) ? NULL : &dirent;
     } while (0);
     if (buf != NULL)
 	free(buf);
 #else
     d = readdirfd(fd);
 #endif
     if (d != NULL) {
         stackPushPtr(pVM->pStack, d->d_name);
         stackPushINT(pVM->pStack, strlen(d->d_name));
         stackPushINT(pVM->pStack, FICL_TRUE);
     } else {
         stackPushINT(pVM->pStack, FICL_FALSE);
     }
 }
 
 /*          fload - interpret file contents
  *
  * fload  ( fd -- )
  */
 static void pfload(FICL_VM *pVM)
 {
     int     fd;
 
 #if FICL_ROBUST > 1
     vmCheckStack(pVM, 1, 0);
 #endif
     fd = stackPopINT(pVM->pStack); /* get fd */
     if (fd != -1)
 	ficlExecFD(pVM, fd);
     return;
 }
 
 /*          fwrite - write file contents
  *
  * fwrite  ( fd buf nbytes  -- nwritten )
  */
 static void pfwrite(FICL_VM *pVM)
 {
     int     fd, len;
     char *buf;
 
 #if FICL_ROBUST > 1
     vmCheckStack(pVM, 3, 1);
 #endif
     len = stackPopINT(pVM->pStack); /* get number of bytes to read */
     buf = stackPopPtr(pVM->pStack); /* get buffer */
     fd = stackPopINT(pVM->pStack); /* get fd */
     if (len > 0 && buf && fd != -1)
 	stackPushINT(pVM->pStack, write(fd, buf, len));
     else
 	stackPushINT(pVM->pStack, -1);
     return;
 }
 
 /*          fseek - seek to a new position in a file
  *
  * fseek  ( fd ofs whence  -- pos )
  */
 static void pfseek(FICL_VM *pVM)
 {
     int     fd, pos, whence;
 
 #if FICL_ROBUST > 1
     vmCheckStack(pVM, 3, 1);
 #endif
     whence = stackPopINT(pVM->pStack);
     pos = stackPopINT(pVM->pStack);
     fd = stackPopINT(pVM->pStack);
     stackPushINT(pVM->pStack, lseek(fd, pos, whence));
     return;
 }
 
 /*           key - get a character from stdin
  *
  * key ( -- char )
  */
 static void key(FICL_VM *pVM)
 {
 #if FICL_ROBUST > 1
     vmCheckStack(pVM, 0, 1);
 #endif
     stackPushINT(pVM->pStack, getchar());
     return;
 }
 
 /*           key? - check for a character from stdin (FACILITY)
  *
  * key? ( -- flag )
  */
 static void keyQuestion(FICL_VM *pVM)
 {
 #if FICL_ROBUST > 1
     vmCheckStack(pVM, 0, 1);
 #endif
 #ifdef TESTMAIN
     /* XXX Since we don't fiddle with termios, let it always succeed... */
     stackPushINT(pVM->pStack, FICL_TRUE);
 #else
     /* But here do the right thing. */
     stackPushINT(pVM->pStack, ischar()? FICL_TRUE : FICL_FALSE);
 #endif
     return;
 }
 
 /* seconds - gives number of seconds since beginning of time
  *
  * beginning of time is defined as:
  *
  *	BTX	- number of seconds since midnight
  *	FreeBSD	- number of seconds since Jan 1 1970
  *
  * seconds ( -- u )
  */
 static void pseconds(FICL_VM *pVM)
 {
 #if FICL_ROBUST > 1
     vmCheckStack(pVM,0,1);
 #endif
     stackPushUNS(pVM->pStack, (FICL_UNS) time(NULL));
     return;
 }
 
 /* ms - wait at least that many milliseconds (FACILITY)
  *
  * ms ( u -- )
  *
  */
 static void ms(FICL_VM *pVM)
 {
 #if FICL_ROBUST > 1
     vmCheckStack(pVM,1,0);
 #endif
 #ifdef TESTMAIN
     usleep(stackPopUNS(pVM->pStack)*1000);
 #else
     delay(stackPopUNS(pVM->pStack)*1000);
 #endif
     return;
 }
 
 /*           fkey - get a character from a file
  *
  * fkey ( file -- char )
  */
 static void fkey(FICL_VM *pVM)
 {
     int i, fd;
     char ch;
 
 #if FICL_ROBUST > 1
     vmCheckStack(pVM, 1, 1);
 #endif
     fd = stackPopINT(pVM->pStack);
     i = read(fd, &ch, 1);
     stackPushINT(pVM->pStack, i > 0 ? ch : -1);
     return;
 }
 
 
 /*
 ** Retrieves free space remaining on the dictionary
 */
 
 static void freeHeap(FICL_VM *pVM)
 {
     stackPushINT(pVM->pStack, dictCellsAvail(ficlGetDict(pVM->pSys)));
 }
 
 
 /******************* Increase dictionary size on-demand ******************/
  
 static void ficlDictThreshold(FICL_VM *pVM)
 {
     stackPushPtr(pVM->pStack, &dictThreshold);
 }
  
 static void ficlDictIncrease(FICL_VM *pVM)
 {
     stackPushPtr(pVM->pStack, &dictIncrease);
 }
 
 /**************************************************************************
                         f i c l C o m p i l e P l a t f o r m
 ** Build FreeBSD platform extensions into the system dictionary
 **************************************************************************/
 void ficlCompilePlatform(FICL_SYSTEM *pSys)
 {
     ficlCompileFcn **fnpp;
     FICL_DICT *dp = pSys->dp;
     assert (dp);
 
     dictAppendWord(dp, ".#",        displayCellNoPad,    FW_DEFAULT);
     dictAppendWord(dp, "isdir?",    isdirQuestion,  FW_DEFAULT);
     dictAppendWord(dp, "fopen",	    pfopen,	    FW_DEFAULT);
     dictAppendWord(dp, "fclose",    pfclose,	    FW_DEFAULT);
     dictAppendWord(dp, "fread",	    pfread,	    FW_DEFAULT);
     dictAppendWord(dp, "freaddir",  pfreaddir,	    FW_DEFAULT);
     dictAppendWord(dp, "fload",	    pfload,	    FW_DEFAULT);
     dictAppendWord(dp, "fkey",	    fkey,	    FW_DEFAULT);
     dictAppendWord(dp, "fseek",     pfseek,	    FW_DEFAULT);
     dictAppendWord(dp, "fwrite",    pfwrite,	    FW_DEFAULT);
     dictAppendWord(dp, "key",	    key,	    FW_DEFAULT);
     dictAppendWord(dp, "key?",	    keyQuestion,    FW_DEFAULT);
     dictAppendWord(dp, "ms",        ms,             FW_DEFAULT);
     dictAppendWord(dp, "seconds",   pseconds,       FW_DEFAULT);
     dictAppendWord(dp, "heap?",     freeHeap,       FW_DEFAULT);
     dictAppendWord(dp, "dictthreshold", ficlDictThreshold, FW_DEFAULT);
     dictAppendWord(dp, "dictincrease", ficlDictIncrease, FW_DEFAULT);
 
     dictAppendWord(dp, "setenv",    ficlSetenv,	    FW_DEFAULT);
     dictAppendWord(dp, "setenv?",   ficlSetenvq,    FW_DEFAULT);
     dictAppendWord(dp, "getenv",    ficlGetenv,	    FW_DEFAULT);
     dictAppendWord(dp, "unsetenv",  ficlUnsetenv,   FW_DEFAULT);
     dictAppendWord(dp, "copyin",    ficlCopyin,	    FW_DEFAULT);
     dictAppendWord(dp, "copyout",   ficlCopyout,    FW_DEFAULT);
     dictAppendWord(dp, "findfile",  ficlFindfile,   FW_DEFAULT);
     dictAppendWord(dp, "ccall",	    ficlCcall,	    FW_DEFAULT);
     dictAppendWord(dp, "uuid-from-string", ficlUuidFromString, FW_DEFAULT);
     dictAppendWord(dp, "uuid-to-string", ficlUuidToString, FW_DEFAULT);
 #ifndef TESTMAIN
+    dictAppendWord(dp, "fb-setpixel", ficl_fb_setpixel, FW_DEFAULT);
+    dictAppendWord(dp, "fb-line", ficl_fb_line, FW_DEFAULT);
+    dictAppendWord(dp, "fb-bezier", ficl_fb_bezier, FW_DEFAULT);
+    dictAppendWord(dp, "fb-drawrect", ficl_fb_drawrect, FW_DEFAULT);
+    dictAppendWord(dp, "fb-putimage", ficl_fb_putimage, FW_DEFAULT);
+    dictAppendWord(dp, "term-drawrect", ficl_term_drawrect, FW_DEFAULT);
+    dictAppendWord(dp, "term-putimage", ficl_term_putimage, FW_DEFAULT);
     dictAppendWord(dp, "isvirtualized?",ficlIsvirtualizedQ, FW_DEFAULT);
 #endif
     
     SET_FOREACH(fnpp, Xficl_compile_set)
 	(*fnpp)(pSys);
 
 #if defined(__i386__)
     ficlSetEnv(pSys, "arch-i386",         FICL_TRUE);
     ficlSetEnv(pSys, "arch-powerpc",      FICL_FALSE);
 #elif defined(__powerpc__)
     ficlSetEnv(pSys, "arch-i386",         FICL_FALSE);
     ficlSetEnv(pSys, "arch-powerpc",      FICL_TRUE);
 #endif
 
     return;
 }
diff --git a/stand/fonts/INDEX.fonts b/stand/fonts/INDEX.fonts
new file mode 100644
index 000000000000..44aa6a3c81cc
--- /dev/null
+++ b/stand/fonts/INDEX.fonts
@@ -0,0 +1,66 @@
+#
+# $FreeBSD$
+#
+# database for vidfont(8)
+#
+# Format <file>:<lang>:<description>
+#
+# lang: ar bg cs da de el en es fi fr hr hu hy is it iw ja ko nl no pl
+#       pt ro ru sh sk sl sv tr uk zh
+# lang: lang,lang
+#
+# Example:
+# 	terminus-b32.fnt:de:Terminus Schriftart
+# 	terminus-b32.fnt:en:Terminus font
+#
+# If lang is empty use 'en' (us-english) as default.
+#
+# See also setlocale(3),
+# /usr/share/locale, /usr/X11/lib/X11/locale/locale.alias
+#
+################################
+# Language support: MENU, FONT
+#
+MENU:en:Choose your terminal font
+MENU:da:Vælg skrifttypen til din terminal
+MENU:de:Wählen Sie Ihre Schrift
+MENU:fr:Choisissez votre fonte écran
+
+#
+# The font definition for "en" is the fall-back font for
+# all languages.
+# Add language specific font definitions only where required!
+#
+FONT:en:8x16v.fnt
+#
+
+6x12.fnt:en:Terminus BSD Console, size 12
+6x12.fnt:da:Terminus BSD-konsol, størrelse 12
+6x12.fnt:de:Terminus BSD Console, Größe 12
+8x14.fnt:en:Terminus BSD Console, size 14
+8x14.fnt:da:Terminus BSD-konsol, størrelse 14
+8x14.fnt:de:Terminus BSD Console, Größe 14
+8x16.fnt:en:Terminus BSD Console, size 16
+8x16.fnt:da:Terminus BSD-konsol, størrelse 16
+8x16.fnt:de:Terminus BSD Console, Größe 16
+10x18.fnt:en:Terminus BSD Console, size 18
+10x18.fnt:da:Terminus BSD-konsol, størrelse 18
+10x18.fnt:de:Terminus BSD Console, Größe 18
+10x20.fnt:en:Terminus BSD Console, size 20
+10x20.fnt:da:Terminus BSD-konsol, størrelse 20
+10x20.fnt:de:Terminus BSD Console, Größe 20
+11x22.fnt:en:Terminus BSD Console, size 22
+11x22.fnt:da:Terminus BSD-konsol, størrelse 22
+11x22.fnt:de:Terminus BSD Console, Größe 22
+12x24.fnt:en:Terminus BSD Console, size 24
+12x24.fnt:da:Terminus BSD-konsol, størrelse 24
+12x24.fnt:de:Terminus BSD Console, Größe 24
+14x28.fnt:en:Terminus BSD Console, size 28
+14x28.fnt:da:Terminus BSD-konsol, størrelse 28
+14x28.fnt:de:Terminus BSD Console, Größe 28
+16x32.fnt:en:Terminus BSD Console, size 32
+16x32.fnt:da:Terminus BSD-konsol, størrelse 32
+16x32.fnt:de:Terminus BSD Console, Größe 32
+
+# (fset 'langnew
+#   "\M-}\C-p\C-k\C-y\C-m\C-y\M-}")
diff --git a/stand/fonts/Makefile b/stand/fonts/Makefile
new file mode 100644
index 000000000000..b7b2ea7a0f0d
--- /dev/null
+++ b/stand/fonts/Makefile
@@ -0,0 +1,81 @@
+# $FreeBSD$
+
+.include <bsd.init.mk>
+
+.PATH: ${SRCTOP}/contrib/terminus
+
+FONTS=	\
+	6x12.fnt.gz \
+	8x14.fnt.gz \
+	8x14v.fnt.gz \
+	8x16.fnt.gz \
+	8x16v.fnt.gz \
+	10x18.fnt.gz \
+	10x20.fnt.gz \
+	11x22.fnt.gz \
+	12x24.fnt.gz \
+	14x28.fnt.gz \
+	16x32.fnt.gz \
+
+FILES=	${FONTS} INDEX.fonts
+FILESDIR=	/boot/fonts
+
+CLEANFILES+=	${FONTS} ${FONTS:T:S/${COMPRESS_EXT}//g}
+
+6x12.fnt.gz: 6x12.fnt
+	${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET}
+8x14.fnt.gz: 8x14.fnt
+	${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET}
+8x14v.fnt.gz: 8x14v.fnt
+	${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET}
+8x16.fnt.gz: 8x16.fnt
+	${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET}
+8x16v.fnt.gz: 8x16v.fnt
+	${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET}
+10x18.fnt.gz: 10x18.fnt
+	${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET}
+10x20.fnt.gz: 10x20.fnt
+	${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET}
+11x22.fnt.gz: 11x22.fnt
+	${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET}
+12x24.fnt.gz: 12x24.fnt
+	${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET}
+14x28.fnt.gz: 14x28.fnt
+	${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET}
+16x32.fnt.gz: 16x32.fnt
+	${COMPRESS_CMD} ${.ALLSRC} > ${.TARGET}
+
+6x12.fnt: ter-u12n.bdf ter-u12b.bdf
+	vtfontcvt -o ${.TARGET} ${.ALLSRC}
+
+8x14.fnt: ter-u14n.bdf ter-u14b.bdf
+	vtfontcvt -o ${.TARGET} ${.ALLSRC}
+
+8x14v.fnt: ter-u14v.bdf
+	vtfontcvt -o ${.TARGET} ${.ALLSRC}
+
+8x16.fnt: ter-u16n.bdf ter-u16b.bdf
+	vtfontcvt -o ${.TARGET} ${.ALLSRC}
+
+8x16v.fnt: ter-u16v.bdf
+	vtfontcvt -o ${.TARGET} ${.ALLSRC}
+
+10x18.fnt: ter-u18n.bdf ter-u18b.bdf
+	vtfontcvt -o ${.TARGET} ${.ALLSRC}
+
+10x20.fnt: ter-u20n.bdf ter-u20b.bdf
+	vtfontcvt -o ${.TARGET} ${.ALLSRC}
+
+11x22.fnt: ter-u22n.bdf ter-u22b.bdf
+	vtfontcvt -o ${.TARGET} ${.ALLSRC}
+
+12x24.fnt: ter-u24n.bdf ter-u24b.bdf
+	vtfontcvt -o ${.TARGET} ${.ALLSRC}
+
+14x28.fnt: ter-u28n.bdf ter-u28b.bdf
+	vtfontcvt -o ${.TARGET} ${.ALLSRC}
+
+16x32.fnt: ter-u32n.bdf ter-u32b.bdf
+	vtfontcvt -o ${.TARGET} ${.ALLSRC}
+
+.include <bsd.prog.mk>
diff --git a/stand/forth/beastie.4th b/stand/forth/beastie.4th
index 752cce22a4ff..f64cf9f8720b 100644
--- a/stand/forth/beastie.4th
+++ b/stand/forth/beastie.4th
@@ -1,110 +1,114 @@
 \ Copyright (c) 2003 Scott Long <scottl@FreeBSD.org>
 \ Copyright (c) 2003 Aleksander Fafula <alex@fafula.com>
 \ Copyright (c) 2006-2015 Devin Teske <dteske@FreeBSD.org>
 \ All rights reserved.
 \ 
 \ Redistribution and use in source and binary forms, with or without
 \ modification, are permitted provided that the following conditions
 \ are met:
 \ 1. Redistributions of source code must retain the above copyright
 \    notice, this list of conditions and the following disclaimer.
 \ 2. Redistributions in binary form must reproduce the above copyright
 \    notice, this list of conditions and the following disclaimer in the
 \    documentation and/or other materials provided with the distribution.
 \ 
 \ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 \ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 \ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 \ ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 \ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 \ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 \ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 \ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 \ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 \ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 \ SUCH DAMAGE.
 \ 
 \ $FreeBSD$
 
 marker task-beastie.4th
 
 only forth definitions
 
 variable logoX
 variable logoY
 
 \ Initialize logo placement to defaults
 46 logoX !
 4  logoY !
 
 \ This function draws any number of beastie logos at (loader_logo_x,
 \ loader_logo_y) if defined, else (46,4) (to the right of the menu). To choose
 \ your beastie, set the variable `loader_logo' to the respective logo name.
 \ 
 \ NOTE: Each is defined as a logo function in /boot/logo-${loader_logo}.4th
 \ NOTE: If `/boot/logo-${loader_logo}.4th' does not exist or does not define
 \       a `logo' function, no beastie is drawn.
 \ 
 : draw-beastie ( -- ) \ at (loader_logo_x,loader_logo_y), else (46,4)
 
 	s" loader_logo_x" getenv dup -1 <> if
 		?number 1 = if logoX ! then
 	else drop then
 	s" loader_logo_y" getenv dup -1 <> if
 		?number 1 = if logoY ! then
 	else drop then
 
 
 	\ If `logo' is defined, execute it
 	s" logo" sfind ( -- xt|0 bool ) if
 		logoX @ logoY @ rot execute
 	else
 		\ Not defined; try-include desired logo file
 		drop ( xt = 0 ) \ cruft
 		s" loader_logo" getenv dup -1 = over 0= or if
 			dup 0= if 2drop else drop then \ getenv result unused
 			loader_color? if
 				s" try-include /boot/logo-orb.4th"
 			else
 				s" try-include /boot/logo-orbbw.4th"
 			then
 		else
 			2drop ( c-addr/u -- ) \ getenv result unused
 			s" try-include /boot/logo-${loader_logo}.4th"
 		then
 		evaluate
 		1 spaces
 
 		\ Execute `logo' if defined now
 		s" logo" sfind if
 			logoX @ logoY @ rot execute
 		else drop then
 	then
 ;
 
+: draw-beastie
+	['] draw-beastie console-iterate
+;
+
 also support-functions
 
 : beastie-start ( -- ) \ starts the menu
 	s" beastie_disable" getenv dup -1 <> if
 		s" YES" compare-insensitive 0= if
 			any_conf_read? if
 				load_xen_throw
 				load_kernel
 				load_modules
 			then
 			exit \ to autoboot (default)
 		then
 	else drop then
 
 	s" loader_delay" getenv -1 = if
 		s" include /boot/menu.rc" evaluate
 	else
 		drop
 		." Loading Menu (Ctrl-C to Abort)" cr
 		s" set delay_command='include /boot/menu.rc'" evaluate
 		s" set delay_showdots" evaluate
 		delay_execute
 	then
 ;
 
 only forth definitions
diff --git a/stand/forth/brand-fbsd.4th b/stand/forth/brand-fbsd.4th
index 9cd017f84a3f..b8e43e601c96 100644
--- a/stand/forth/brand-fbsd.4th
+++ b/stand/forth/brand-fbsd.4th
@@ -1,46 +1,58 @@
 \ Copyright (c) 2006-2015 Devin Teske <dteske@FreeBSD.org>
 \ All rights reserved.
 \ 
 \ Redistribution and use in source and binary forms, with or without
 \ modification, are permitted provided that the following conditions
 \ are met:
 \ 1. Redistributions of source code must retain the above copyright
 \    notice, this list of conditions and the following disclaimer.
 \ 2. Redistributions in binary form must reproduce the above copyright
 \    notice, this list of conditions and the following disclaimer in the
 \    documentation and/or other materials provided with the distribution.
 \ 
 \ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 \ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 \ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 \ ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 \ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 \ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 \ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 \ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 \ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 \ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 \ SUCH DAMAGE.
 \ 
 \ $FreeBSD$
 
 2 brandX ! 1 brandY ! \ Initialize brand placement defaults
 
 : brand+ ( x y c-addr/u -- x y' )
 	2swap 2dup at-xy 2swap \ position the cursor
 	type \ print to the screen
 	1+ \ increase y for next time we're called
 ;
 
 : 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+
 	s" |  ___| '__/ _ \/ _ \|  _ < \___ \| |  | |" brand+
 	s" | |   | | |  __/  __/| |_) |____) | |__| |" brand+
 	s" | |   | | |    |    ||     |      |      |" brand+
 	s" |_|   |_|  \___|\___||____/|_____/|_____/ " brand+
 
 	2drop
 ;
diff --git a/stand/forth/brand.4th b/stand/forth/brand.4th
index 39a9bfae753c..034e4eb40445 100644
--- a/stand/forth/brand.4th
+++ b/stand/forth/brand.4th
@@ -1,74 +1,78 @@
 \ Copyright (c) 2006-2015 Devin Teske <dteske@FreeBSD.org>
 \ All rights reserved.
 \ 
 \ Redistribution and use in source and binary forms, with or without
 \ modification, are permitted provided that the following conditions
 \ are met:
 \ 1. Redistributions of source code must retain the above copyright
 \    notice, this list of conditions and the following disclaimer.
 \ 2. Redistributions in binary form must reproduce the above copyright
 \    notice, this list of conditions and the following disclaimer in the
 \    documentation and/or other materials provided with the distribution.
 \ 
 \ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 \ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 \ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 \ ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 \ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 \ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 \ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 \ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 \ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 \ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 \ SUCH DAMAGE.
 \ 
 \ $FreeBSD$
 
 marker task-brand.4th
 
 variable brandX
 variable brandY
 
 \ Initialize brand placement to defaults
 2 brandX !
 1 brandY !
 
 \ This function draws any number of company brands at (loader_brand_x,
 \ loader_brand_y) if defined, or (2,1) (top-left). To choose your brand, set
 \ the variable `loader_brand' to the respective brand name.
 \ 
 \ NOTE: Each is defined as a brand function in /boot/brand-${loader_brand}.4th
 \ NOTE: If `/boot/brand-${loader_brand}.4th' does not exist or does not define
 \       a `brand' function, no brand is drawn.
 \ 
 : draw-brand ( -- ) \ at (loader_brand_x,loader_brand_y), else (2,1)
 
 	s" loader_brand_x" getenv dup -1 <> if
 		?number 1 = if brandX ! then
 	else drop then
  	s" loader_brand_y" getenv dup -1 <> if
  		?number 1 = if brandY ! then
  	else drop then
 
 	\ If `brand' is defined, execute it
 	s" brand" sfind ( -- xt|0 bool ) if
 		brandX @ brandY @ rot execute
 	else
 		\ Not defined; try-include desired brand file
 		drop ( xt = 0 ) \ cruft
 		s" loader_brand" getenv dup -1 = over 0= or if
 			dup 0= if 2drop else drop then \ getenv result unused
 			s" try-include /boot/brand-fbsd.4th"
 		else
 			2drop ( c-addr/u -- ) \ getenv result unused
 			s" try-include /boot/brand-${loader_brand}.4th"
 		then
 		evaluate
 		1 spaces
 
 		\ Execute `brand' if defined now
 		s" brand" sfind if
 			brandX @ brandY @ rot execute
 		else drop then
 	then
 ;
+
+: draw-brand
+	['] draw-brand console-iterate
+;
diff --git a/stand/forth/color.4th b/stand/forth/color.4th
index fc5a4d564d0a..e73e8b725c31 100644
--- a/stand/forth/color.4th
+++ b/stand/forth/color.4th
@@ -1,55 +1,51 @@
 \ Copyright (c) 2011-2013 Devin Teske <dteske@FreeBSD.org>
 \ All rights reserved.
 \ 
 \ Redistribution and use in source and binary forms, with or without
 \ modification, are permitted provided that the following conditions
 \ are met:
 \ 1. Redistributions of source code must retain the above copyright
 \    notice, this list of conditions and the following disclaimer.
 \ 2. Redistributions in binary form must reproduce the above copyright
 \    notice, this list of conditions and the following disclaimer in the
 \    documentation and/or other materials provided with the distribution.
 \ 
 \ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 \ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 \ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 \ ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 \ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 \ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 \ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 \ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 \ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 \ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 \ SUCH DAMAGE.
 \ 
 \ $FreeBSD$
 
 marker task-color.4th
 
 \ This function returns FALSE if the `loader_color' environment variable is set
 \ to NO, no, or 0. It returns TRUE if `loader_color' is set to any other value.
-\ If `loader_color' is unset, TRUE is returned (unless booting serial).
+\ If `loader_color' is unset, TRUE is returned.
 \ 
 : loader_color? ( -- t )
 
 	s" loader_color" getenv dup -1 <> if
 		\ `loader_color' is set.
 		\ Check if it is explicitly disabled.
 		2dup s" NO" compare-insensitive 0= if
 			2drop
 			FALSE exit
 		then
 		2dup s" 0" compare 0= if
 			2drop
 			FALSE exit
 		then
 		drop
-		\ It is enabled.
-		TRUE
-	else
-		\ `loader_color' is unset.
-		\ Default to using color unless serial boot is active.
-		drop
-		boot_serial? 0=
 	then
+	drop
+	\ It is enabled.
+	TRUE
 ;
diff --git a/stand/forth/frames.4th b/stand/forth/frames.4th
index ba356cee4acb..8d2421c758d9 100644
--- a/stand/forth/frames.4th
+++ b/stand/forth/frames.4th
@@ -1,142 +1,156 @@
 \ Copyright (c) 2003 Scott Long <scottl@FreeBSD.org>
 \ Copyright (c) 2012-2015 Devin Teske <dteske@FreeBSD.org>
 \ All rights reserved.
 \ 
 \ Redistribution and use in source and binary forms, with or without
 \ modification, are permitted provided that the following conditions
 \ are met:
 \ 1. Redistributions of source code must retain the above copyright
 \    notice, this list of conditions and the following disclaimer.
 \ 2. Redistributions in binary form must reproduce the above copyright
 \    notice, this list of conditions and the following disclaimer in the
 \    documentation and/or other materials provided with the distribution.
 \ 
 \ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 \ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 \ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 \ ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 \ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 \ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 \ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 \ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 \ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 \ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 \ SUCH DAMAGE.
 \ 
 \ $FreeBSD$
 
 marker task-frames.4th
 
 vocabulary frame-drawing
 only forth also frame-drawing definitions
 
 \ XXX Filled boxes are left as an exercise for the reader... ;-/
 
 variable h_el
 variable v_el
 variable lt_el
 variable lb_el
 variable rt_el
 variable rb_el
 variable fill
 
 \ ASCII frames (used when serial console is detected)
  45 constant ascii_dash
  61 constant ascii_equal
 124 constant ascii_pipe
  43 constant ascii_plus
 
 \ Single frames
 0x2500 constant sh_el
 0x2502 constant sv_el
 0x250c constant slt_el
 0x2514 constant slb_el
 0x2510 constant srt_el
 0x2518 constant srb_el
 \ Double frames
 0x2550 constant dh_el
 0x2551 constant dv_el
 0x2554 constant dlt_el
 0x255a constant dlb_el
 0x2557 constant drt_el
 0x255d constant drb_el
 \ Fillings
 0 constant fill_none
 32 constant fill_blank
 0x2591 constant fill_dark
 0x2592 constant fill_med
 0x2593 constant fill_bright
 
 only forth definitions also frame-drawing
 
 : hline	( len x y -- )	\ Draw horizontal single line
 	at-xy		\ move cursor
 	0 do
 		h_el @ xemit
 	loop
 ;
 
 : f_ascii ( -- )	( -- )	\ set frames to ascii
 	ascii_dash h_el !
 	ascii_pipe v_el !
 	ascii_plus lt_el !
 	ascii_plus lb_el !
 	ascii_plus rt_el !
 	ascii_plus rb_el !
 ;
 
 : f_single	( -- )	\ set frames to single
 	boot_serial? if f_ascii exit then
 	sh_el h_el !
 	sv_el v_el !
 	slt_el lt_el !
 	slb_el lb_el !
 	srt_el rt_el !
 	srb_el rb_el !
 ;
 
 : f_double	( -- )	\ set frames to double
 	boot_serial? if
 		f_ascii
 		ascii_equal h_el !
 		exit
 	then
 	dh_el h_el !
 	dv_el v_el !
 	dlt_el lt_el !
 	dlb_el lb_el !
 	drt_el rt_el !
 	drb_el rb_el !
 ;
 
 : vline	( len x y -- )	\ Draw vertical single line
 	2dup 4 pick
 	0 do
 		at-xy
 		v_el @ xemit
 		1+
 		2dup
 	loop
 	2drop 2drop drop
 ;
 
 : 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
 	vline		\ Draw right vert line
 	2dup swap 1+ swap 5 pick 1- -rot
 	hline		\ Draw top horiz line
 	2dup swap 1+ swap 4 pick + 5 pick 1- -rot
 	hline		\ Draw bottom horiz line
 	2dup at-xy lt_el @ xemit	\ Draw left-top corner
 	2dup 4 pick + at-xy lb_el @ xemit	\ Draw left bottom corner
 	2dup swap 5 pick + swap at-xy rt_el @ xemit	\ Draw right top corner
 	2 pick + swap 3 pick + swap at-xy rb_el @ xemit
 	2drop
 ;
 
 f_single
 fill_none fill !
 
 only forth definitions
diff --git a/stand/forth/logo-orb.4th b/stand/forth/logo-orb.4th
index c2a504d1ddf5..289353a46926 100644
--- a/stand/forth/logo-orb.4th
+++ b/stand/forth/logo-orb.4th
@@ -1,55 +1,67 @@
 \ Copyright (c) 2006-2015 Devin Teske <dteske@FreeBSD.org>
 \ All rights reserved.
 \ 
 \ Redistribution and use in source and binary forms, with or without
 \ modification, are permitted provided that the following conditions
 \ are met:
 \ 1. Redistributions of source code must retain the above copyright
 \    notice, this list of conditions and the following disclaimer.
 \ 2. Redistributions in binary form must reproduce the above copyright
 \    notice, this list of conditions and the following disclaimer in the
 \    documentation and/or other materials provided with the distribution.
 \ 
 \ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 \ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 \ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 \ ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 \ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 \ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 \ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 \ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 \ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 \ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 \ SUCH DAMAGE.
 \ 
 \ $FreeBSD$
 
 46 logoX ! 7 logoY ! \ Initialize logo placement defaults
 
 : logo+ ( x y c-addr/u -- x y' )
 	2swap 2dup at-xy 2swap \ position the cursor
 	[char] @ escc! \ replace @ with Esc
 	type \ print to the screen
 	1+ \ increase y for next time we're called
 ;
 
 : 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+
 	s"  yo`:.            @[31;1m:o      `+-@[31m"      logo+
 	s"   y/               @[31;1m-/`   -o/@[31m"       logo+
 	s"  .-                  @[31;1m::/sy+:.@[31m"      logo+
 	s"  /                     @[31;1m`--  /@[31m"      logo+
 	s" `:                          @[31;1m:`@[31m"     logo+
 	s" `:                          @[31;1m:`@[31m"     logo+
 	s"  /                          @[31;1m/@[31m"      logo+
 	s"  .-                        @[31;1m-.@[31m"      logo+
 	s"   --                      @[31;1m-.@[31m"       logo+
 	s"    `:`                  @[31;1m`:`"             logo+
 	s"      @[31;1m.--             `--."               logo+
 	s"         .---.....----.@[m"                      logo+
 
 	2drop
 ;
diff --git a/stand/forth/menu.4th b/stand/forth/menu.4th
index ca166c6d934b..6b0869cfe3b9 100644
--- a/stand/forth/menu.4th
+++ b/stand/forth/menu.4th
@@ -1,1319 +1,1323 @@
 \ Copyright (c) 2003 Scott Long <scottl@FreeBSD.org>
 \ Copyright (c) 2003 Aleksander Fafula <alex@fafula.com>
 \ Copyright (c) 2006-2015 Devin Teske <dteske@FreeBSD.org>
 \ All rights reserved.
 \ 
 \ Redistribution and use in source and binary forms, with or without
 \ modification, are permitted provided that the following conditions
 \ are met:
 \ 1. Redistributions of source code must retain the above copyright
 \    notice, this list of conditions and the following disclaimer.
 \ 2. Redistributions in binary form must reproduce the above copyright
 \    notice, this list of conditions and the following disclaimer in the
 \    documentation and/or other materials provided with the distribution.
 \ 
 \ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 \ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 \ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 \ ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 \ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 \ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 \ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 \ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 \ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 \ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 \ SUCH DAMAGE.
 \ 
 \ $FreeBSD$
 
 marker task-menu.4th
 
 \ Frame drawing
 include /boot/frames.4th
 
 vocabulary menu-infrastructure
 vocabulary menu-namespace
 vocabulary menu-command-helpers
 
 only forth also menu-infrastructure definitions
 
 f_double        \ Set frames to double (see frames.4th). Replace with
                 \ f_single if you want single frames.
 46 constant dot \ ASCII definition of a period (in decimal)
 
  5 constant menu_default_x         \ default column position of timeout
 10 constant menu_default_y         \ default row position of timeout msg
  4 constant menu_timeout_default_x \ default column position of timeout
 23 constant menu_timeout_default_y \ default row position of timeout msg
 10 constant menu_timeout_default   \ default timeout (in seconds)
 
 \ Customize the following values with care
 
   1 constant menu_start \ Numerical prefix of first menu item
 dot constant bullet     \ Menu bullet (appears after numerical prefix)
   5 constant menu_x     \ Row position of the menu (from the top)
  10 constant menu_y     \ Column position of the menu (from left side)
 
 \ Menu Appearance
 variable menuidx   \ Menu item stack for number prefixes
 variable menurow   \ Menu item stack for positioning
 variable menubllt  \ Menu item bullet
 
 \ Menu Positioning
 variable menuX     \ Menu X offset (columns)
 variable menuY     \ Menu Y offset (rows)
 
 \ Menu-item elements
 variable menurebootadded
 
 \ Parsing of kernels into menu-items
 variable kernidx
 variable kernlen
 variable kernmenuidx
 
 \ Menu timer [count-down] variables
 variable menu_timeout_enabled \ timeout state (internal use only)
 variable menu_time            \ variable for tracking the passage of time
 variable menu_timeout         \ determined configurable delay duration
 variable menu_timeout_x       \ column position of timeout message
 variable menu_timeout_y       \ row position of timeout message
 
 \ Containers for parsing kernels into menu-items
 create kerncapbuf 64 allot
 create kerndefault 64 allot
 create kernelsbuf 256 allot
 
 only forth also menu-namespace definitions
 
 \ Menu-item key association/detection
 variable menukey1
 variable menukey2
 variable menukey3
 variable menukey4
 variable menukey5
 variable menukey6
 variable menukey7
 variable menukey8
 variable menureboot
 variable menuacpi
 variable menuoptions
 variable menukernel
 
 \ Menu initialization status variables
 variable init_state1
 variable init_state2
 variable init_state3
 variable init_state4
 variable init_state5
 variable init_state6
 variable init_state7
 variable init_state8
 
 \ Boolean option status variables
 variable toggle_state1
 variable toggle_state2
 variable toggle_state3
 variable toggle_state4
 variable toggle_state5
 variable toggle_state6
 variable toggle_state7
 variable toggle_state8
 
 \ Array option status variables
 variable cycle_state1
 variable cycle_state2
 variable cycle_state3
 variable cycle_state4
 variable cycle_state5
 variable cycle_state6
 variable cycle_state7
 variable cycle_state8
 
 \ Containers for storing the initial caption text
 create init_text1 64 allot
 create init_text2 64 allot
 create init_text3 64 allot
 create init_text4 64 allot
 create init_text5 64 allot
 create init_text6 64 allot
 create init_text7 64 allot
 create init_text8 64 allot
 
 only forth definitions
 
 : arch-i386? ( -- BOOL ) \ Returns TRUE (-1) on i386, FALSE (0) otherwise.
 	s" arch-i386" environment? dup if
 		drop
 	then
 ;
 
 : acpipresent? ( -- flag ) \ Returns TRUE if ACPI is present, FALSE otherwise
 	s" hint.acpi.0.rsdp" getenv
 	dup -1 = if
 		drop false exit
 	then
 	2drop
 	true
 ;
 
 : acpienabled? ( -- flag ) \ Returns TRUE if ACPI is enabled, FALSE otherwise
 	s" hint.acpi.0.disabled" getenv
 	dup -1 <> if
 		s" 0" compare 0<> if
 			false exit
 		then
 	else
 		drop
 	then
 	true
 ;
 
 : +c! ( N C-ADDR/U K -- C-ADDR/U )
 	3 pick 3 pick	( n c-addr/u k -- n c-addr/u k n c-addr )
 	rot + c!	( n c-addr/u k n c-addr -- n c-addr/u )
 	rot drop	( n c-addr/u -- c-addr/u )
 ;
 
 only forth also menu-namespace definitions
 
 \ Forth variables
 : namespace     ( C-ADDR/U N -- ) also menu-namespace +c! evaluate previous ;
 : menukeyN      ( N -- ADDR )   s" menukeyN"       7 namespace ;
 : init_stateN   ( N -- ADDR )   s" init_stateN"   10 namespace ;
 : toggle_stateN ( N -- ADDR )   s" toggle_stateN" 12 namespace ;
 : cycle_stateN  ( N -- ADDR )   s" cycle_stateN"  11 namespace ;
 : init_textN    ( N -- C-ADDR ) s" init_textN"     9 namespace ;
 
 \ Environment variables
 : kernel[x]          ( N -- C-ADDR/U )   s" kernel[x]"           7 +c! ;
 : menu_init[x]       ( N -- C-ADDR/U )   s" menu_init[x]"       10 +c! ;
 : menu_command[x]    ( N -- C-ADDR/U )   s" menu_command[x]"    13 +c! ;
 : menu_caption[x]    ( N -- C-ADDR/U )   s" menu_caption[x]"    13 +c! ;
 : ansi_caption[x]    ( N -- C-ADDR/U )   s" ansi_caption[x]"    13 +c! ;
 : menu_keycode[x]    ( N -- C-ADDR/U )   s" menu_keycode[x]"    13 +c! ;
 : toggled_text[x]    ( N -- C-ADDR/U )   s" toggled_text[x]"    13 +c! ;
 : toggled_ansi[x]    ( N -- C-ADDR/U )   s" toggled_ansi[x]"    13 +c! ;
 : menu_caption[x][y] ( N M -- C-ADDR/U ) s" menu_caption[x][y]" 16 +c! 13 +c! ;
 : ansi_caption[x][y] ( N M -- C-ADDR/U ) s" ansi_caption[x][y]" 16 +c! 13 +c! ;
 
 also menu-infrastructure definitions
 
 \ This function prints a menu item at menuX (row) and menuY (column), returns
 \ the incremental decimal ASCII value associated with the menu item, and
 \ increments the cursor position to the next row for the creation of the next
 \ menu item. This function is called by the menu-create function. You need not
 \ call it directly.
 \ 
 : printmenuitem ( menu_item_str -- ascii_keycode )
 
 	loader_color? if [char] ^ escc! then
 
 	menurow dup @ 1+ swap ! ( increment menurow )
 	menuidx dup @ 1+ swap ! ( increment menuidx )
 
 	\ Calculate the menuitem row position
 	menurow @ menuY @ +
 
 	\ Position the cursor at the menuitem position
 	dup menuX @ swap at-xy
 
 	\ Print the value of menuidx
 	loader_color? dup ( -- bool bool )
 	if b then
 	menuidx @ .
 	if me then
 
 	\ Move the cursor forward 1 column
 	dup menuX @ 1+ swap at-xy
 
 	menubllt @ emit	\ Print the menu bullet using the emit function
 
 	\ Move the cursor to the 3rd column from the current position
 	\ to allow for a space between the numerical prefix and the
 	\ text caption
 	menuX @ 3 + swap at-xy
 
 	\ Print the menu caption (we expect a string to be on the stack
 	\ prior to invoking this function)
 	type
 
 	\ Here we will add the ASCII decimal of the numerical prefix
 	\ to the stack (decimal ASCII for `1' is 49) as a "return value"
 	menuidx @ 48 +
 ;
 
 \ This function prints the appropriate menuitem basename to the stack if an
 \ ACPI option is to be presented to the user, otherwise returns -1. Used
 \ internally by menu-create, you need not (nor should you) call this directly.
 \ 
 : acpimenuitem ( -- C-Addr/U | -1 )
 
 	arch-i386? if
 		acpipresent? if
 			acpienabled? if
 				loader_color? if
 					s" toggled_ansi[x]"
 				else
 					s" toggled_text[x]"
 				then
 			else
 				loader_color? if
 					s" ansi_caption[x]"
 				else
 					s" menu_caption[x]"
 				then
 			then
 		else
 			menuidx dup @ 1+ swap ! ( increment menuidx )
 			-1
 		then
 	else
 		-1
 	then
 ;
 
 : delim? ( C -- BOOL )
 	dup  32 =		( c -- c bool )		\ [sp] space
 	over  9 = or		( c bool -- c bool )	\ [ht] horizontal tab
 	over 10 = or		( c bool -- c bool )	\ [nl] newline
 	over 13 = or		( c bool -- c bool )	\ [cr] carriage return
 	over [char] , =	or	( c bool -- c bool )	\ comma
 	swap drop		( c bool -- bool )	\ return boolean
 ;
 
 \ This function parses $kernels into variables that are used by the menu to
 \ display which kernel to boot when the [overloaded] `boot' word is interpreted.
 \ Used internally by menu-create, you need not (nor should you) call this
 \ directly.
 \ 
 : parse-kernels ( N -- ) \ kernidx
 	kernidx ! ( n -- )	\ store provided `x' value
 	[char] 0 kernmenuidx !	\ initialize `y' value for menu_caption[x][y]
 
 	\ Attempt to get a list of kernels, fall back to sensible default
 	s" kernels" getenv dup -1 = if
 		drop ( cruft )
 		s" kernel kernel.old"
 	then ( -- c-addr/u )
 
 	\ Check to see if the user has altered $kernel by comparing it against
 	\ $kernel[N] where N is kernel_state (the actively displayed kernel).
 	s" kernel_state" evaluate @ 48 + s" kernel[N]" 7 +c! getenv
 	dup -1 <> if
 		s" kernel" getenv dup -1 = if
 			drop ( cruft ) s" "
 		then
 		2swap 2over compare 0= if
 			2drop FALSE ( skip below conditional )
 		else \ User has changed $kernel
 			TRUE ( slurp in new value )
 		then
 	else \ We haven't yet parsed $kernels into $kernel[N]
 		drop ( getenv cruft )
 		s" kernel" getenv dup -1 = if
 			drop ( cruft ) s" "
 		then
 		TRUE ( slurp in initial value )
 	then ( c-addr/u -- c-addr/u c-addr/u,-1 | 0 )
 	if \ slurp new value into kerndefault
 		kerndefault 1+ 0 2swap strcat swap 1- c!
 	then
 
 	\ Clear out existing parsed-kernels
 	kernidx @ [char] 0
 	begin
 		dup kernel[x] unsetenv
 		2dup menu_caption[x][y] unsetenv
 		2dup ansi_caption[x][y] unsetenv
 		1+ dup [char] 8 >
 	until
 	2drop
 
 	\ Step through the string until we find the end
 	begin
 		0 kernlen ! \ initialize length of value
 
 		\ Skip leading whitespace and/or comma delimiters
 		begin
 			dup 0<> if
 				over c@ delim? ( c-addr/u -- c-addr/u bool )
 			else
 				false ( c-addr/u -- c-addr/u bool )
 			then
 		while
 			1- swap 1+ swap ( c-addr/u -- c-addr'/u' )
 		repeat
 		( c-addr/u -- c-addr'/u' )
 
 		dup 0= if \ end of string while eating whitespace
 			2drop ( c-addr/u -- )
 			kernmenuidx @ [char] 0 <> if \ found at least one
 				exit \ all done
 			then
 
 			\ No entries in $kernels; use $kernel instead
 			s" kernel" getenv dup -1 = if
 				drop ( cruft ) s" "
 			then ( -- c-addr/u )
 			dup kernlen ! \ store entire value length as kernlen
 		else
 			\ We're still within $kernels parsing toward the end;
 			\ find delimiter/end to determine kernlen
 			2dup ( c-addr/u -- c-addr/u c-addr/u )
 			begin dup 0<> while
 				over c@ delim? if
 					drop 0 ( break ) \ found delimiter
 				else
 					kernlen @ 1+ kernlen ! \ incrememnt
 					1- swap 1+ swap \ c-addr++ u--
 				then
 			repeat
 			2drop ( c-addr/u c-addr'/u' -- c-addr/u )
 
 			\ If this is the first entry, compare it to $kernel
 			\ If different, then insert $kernel beforehand
 			kernmenuidx @ [char] 0 = if
 				over kernlen @ kerndefault count compare if
 					kernelsbuf 0 kerndefault count strcat
 					s" ," strcat 2swap strcat
 					kerndefault count swap drop kernlen !
 				then
 			then
 		then
 		( c-addr/u -- c-addr'/u' )
 
 		\ At this point, we should have something on the stack to store
 		\ as the next kernel menu option; start assembling variables
 
 		over kernlen @ ( c-addr/u -- c-addr/u c-addr/u2 )
 
 		\ Assign first to kernel[x]
 		2dup kernmenuidx @ kernel[x] setenv
 
 		\ Assign second to menu_caption[x][y]
 		kerncapbuf 0 s" [K]ernel: " strcat
 		2over strcat
 		kernidx @ kernmenuidx @ menu_caption[x][y]
 		setenv
 
 		\ Assign third to ansi_caption[x][y]
 		kerncapbuf 0 s" @[1mK@[mernel: " [char] @ escc! strcat
 		kernmenuidx @ [char] 0 = if
 			s" default/@[32m"
 		else
 			s" @[34;1m"
 		then
 		[char] @ escc! strcat
 		2over strcat
 		s" @[m" [char] @ escc! strcat
 		kernidx @ kernmenuidx @ ansi_caption[x][y]
 		setenv
 
 		2drop ( c-addr/u c-addr/u2 -- c-addr/u )
 
 		kernmenuidx @ 1+ dup kernmenuidx ! [char] 8 > if
 			2drop ( c-addr/u -- ) exit
 		then
 
 		kernlen @ - swap kernlen @ + swap ( c-addr/u -- c-addr'/u' )
 	again
 ;
 
 \ This function goes through the kernels that were discovered by the
 \ parse-kernels function [above], adding " (# of #)" text to the end of each
 \ caption.
 \ 
 : tag-kernels ( -- )
 	kernidx @ ( -- x ) dup 0= if exit then
 	[char] 0 s"  (Y of Z)" ( x -- x y c-addr/u )
 	kernmenuidx @ -rot 7 +c! \ Replace 'Z' with number of kernels parsed
 	begin
 		2 pick 1+ -rot 2 +c! \ Replace 'Y' with current ASCII num
 
 		2over menu_caption[x][y] getenv dup -1 <> if
 			2dup + 1- c@ [char] ) = if
 				2drop \ Already tagged
 			else
 				kerncapbuf 0 2swap strcat
 				2over strcat
 				5 pick 5 pick menu_caption[x][y] setenv
 			then
 		else
 			drop ( getenv cruft )
 		then
 
 		2over ansi_caption[x][y] getenv dup -1 <> if
 			2dup + 1- c@ [char] ) = if
 				2drop \ Already tagged
 			else
 				kerncapbuf 0 2swap strcat
 				2over strcat
 				5 pick 5 pick ansi_caption[x][y] setenv
 			then
 		else
 			drop ( getenv cruft )
 		then
 
 		rot 1+ dup [char] 8 > if
 			-rot 2drop TRUE ( break )
 		else
 			-rot FALSE
 		then
 	until
 	2drop ( x y -- )
 ;
 
 \ This function creates the list of menu items. This function is called by the
 \ menu-display function. You need not call it directly.
 \ 
 : menu-create ( -- )
 
 	\ Print the frame caption at (x,y)
 	s" loader_menu_title" getenv dup -1 = if
 		drop s" Welcome to FreeBSD"
 	then
 	TRUE ( use default alignment )
 	s" loader_menu_title_align" getenv dup -1 <> if
 		2dup s" left" compare-insensitive 0= if ( 1 )
 			2drop ( c-addr/u ) drop ( bool )
 			menuX @ menuY @ 1-
 			FALSE ( don't use default alignment )
 		else ( 1 ) 2dup s" right" compare-insensitive 0= if ( 2 )
 			2drop ( c-addr/u ) drop ( bool )
 			menuX @ 42 + 4 - over - menuY @ 1-
 			FALSE ( don't use default alignment )
 		else ( 2 ) 2drop ( c-addr/u ) then ( 1 ) then
 	else
 		drop ( getenv cruft )
 	then
 	if ( use default center alignement? )
 		menuX @ 19 + over 2 / - menuY @ 1-
 	then
 	at-xy type 
 
 	\ If $menu_init is set, evaluate it (allowing for whole menus to be
 	\ constructed dynamically -- as this function could conceivably set
 	\ the remaining environment variables to construct the menu entirely).
 	\ 
 	s" menu_init" getenv dup -1 <> if
 		evaluate
 	else
 		drop
 	then
 
 	\ Print our menu options with respective key/variable associations.
 	\ `printmenuitem' ends by adding the decimal ASCII value for the
 	\ numerical prefix to the stack. We store the value left on the stack
 	\ to the key binding variable for later testing against a character
 	\ captured by the `getkey' function.
 
 	\ Note that any menu item beyond 9 will have a numerical prefix on the
 	\ screen consisting of the first digit (ie. 1 for the tenth menu item)
 	\ and the key required to activate that menu item will be the decimal
 	\ ASCII of 48 plus the menu item (ie. 58 for the tenth item, aka. `:')
 	\ which is misleading and not desirable.
 	\ 
 	\ Thus, we do not allow more than 8 configurable items on the menu
 	\ (with "Reboot" as the optional ninth and highest numbered item).
 
 	\ 
 	\ Initialize the ACPI option status.
 	\ 
 	0 menuacpi !
 	s" menu_acpi" getenv -1 <> if
 		c@ dup 48 > over 57 < and if ( '1' <= c1 <= '8' )
 			menuacpi !
 			arch-i386? if acpipresent? if
 				\ 
 				\ Set menu toggle state to active state
 				\ (required by generic toggle_menuitem)
 				\ 
 				acpienabled? menuacpi @ toggle_stateN !
 			then then
 		else
 			drop
 		then
 	then
 
 	\ 
 	\ Initialize kernel captions after parsing $kernels
 	\ 
 	0 menukernel !
 	s" menu_kernel" getenv -1 <> if
 		c@ dup 48 > over 57 < and if ( '1' <= c1 <= '8' )
 			dup menukernel !
 			dup parse-kernels tag-kernels
 
 			\ Get the current cycle state (entry to use)
 			s" kernel_state" evaluate @ 48 + ( n -- n y )
 
 			\ If state is invalid, reset
 			dup kernmenuidx @ 1- > if
 				drop [char] 0 ( n y -- n 48 )
 				0 s" kernel_state" evaluate !
 				over s" init_kernel" evaluate drop
 			then
 
 			\ Set the current non-ANSI caption
 			2dup swap dup ( n y -- n y y n n )
 			s" set menu_caption[x]=$menu_caption[x][y]"
 			17 +c! 34 +c! 37 +c! evaluate
 			( n y y n n c-addr/u -- n y  )
 
 			\ Set the current ANSI caption
 			2dup swap dup ( n y -- n y y n n )
 			s" set ansi_caption[x]=$ansi_caption[x][y]"
 			17 +c! 34 +c! 37 +c! evaluate
 			( n y y n n c-addr/u -- n y )
 
 			\ Initialize cycle state from stored value
 			48 - ( n y -- n k )
 			s" init_cyclestate" evaluate ( n k -- n )
 
 			\ Set $kernel to $kernel[y]
 			s" activate_kernel" evaluate ( n -- n )
 		then
 		drop
 	then
 
 	\ 
 	\ Initialize the menu_options visual separator.
 	\ 
 	0 menuoptions !
 	s" menu_options" getenv -1 <> if
 		c@ dup 48 > over 57 < and if ( '1' <= c1 <= '8' )
 			menuoptions !
 		else
 			drop
 		then
 	then
 
 	\ Initialize "Reboot" menu state variable (prevents double-entry)
 	false menurebootadded !
 
 	menu_start
 	1- menuidx !    \ Initialize the starting index for the menu
 	0 menurow !     \ Initialize the starting position for the menu
 
 	49 \ Iterator start (loop range 49 to 56; ASCII '1' to '8')
 	begin
 		\ If the "Options:" separator, print it.
 		dup menuoptions @ = if
 			\ Optionally add a reboot option to the menu
 			s" menu_reboot" getenv -1 <> if
 				drop
 				s" Reboot" printmenuitem menureboot !
 				true menurebootadded !
 			then
 
 			menuX @
 			menurow @ 2 + menurow !
 			menurow @ menuY @ +
 			at-xy
 			s" menu_optionstext" getenv dup -1 <> if
 				type
 			else
 				drop ." Options:"
 			then
 		then
 
 		\ If this is the ACPI menu option, act accordingly.
 		dup menuacpi @ = if
 			dup acpimenuitem ( n -- n n c-addr/u | n n -1 )
 			dup -1 <> if
 				13 +c! ( n n c-addr/u -- n c-addr/u )
 				       \ replace 'x' with n
 			else
 				swap drop ( n n -1 -- n -1 )
 				over menu_command[x] unsetenv
 			then
 		else
 			\ make sure we have not already initialized this item
 			dup init_stateN dup @ 0= if
 				1 swap !
 
 				\ If this menuitem has an initializer, run it
 				dup menu_init[x]
 				getenv dup -1 <> if
 					evaluate
 				else
 					drop
 				then
 			else
 				drop
 			then
 
 			dup
 			loader_color? if
 				ansi_caption[x]
 			else
 				menu_caption[x]
 			then
 		then
 
 		dup -1 <> if
 			\ test for environment variable
 			getenv dup -1 <> if
 				printmenuitem ( c-addr/u -- n )
 				dup menukeyN !
 			else
 				drop
 			then
 		else
 			drop
 		then
 
 		1+ dup 56 > \ add 1 to iterator, continue if less than 57
 	until
 	drop \ iterator
 
 	\ Optionally add a reboot option to the menu
 	menurebootadded @ true <> if
 		s" menu_reboot" getenv -1 <> if
 			drop       \ no need for the value
 			s" Reboot" \ menu caption (required by printmenuitem)
 
 			printmenuitem
 			menureboot !
 		else
 			0 menureboot !
 		then
 	then
 ;
 
 \ Takes a single integer on the stack and updates the timeout display. The
 \ integer must be between 0 and 9 (we will only update a single digit in the
 \ source message).
 \ 
 : menu-timeout-update ( N -- )
 
 	\ Enforce minimum/maximum
 	dup 9 > if drop 9 then
 	dup 0 < if drop 0 then
 
 	s" Autoboot in N seconds. [Space] to pause" ( n -- n c-addr/u )
 
 	2 pick 0> if
 		rot 48 + -rot ( n c-addr/u -- n' c-addr/u ) \ convert to ASCII
 		12 +c!        ( n' c-addr/u -- c-addr/u )   \ replace 'N' above
 
 		menu_timeout_x @ menu_timeout_y @ at-xy \ position cursor
 		type ( c-addr/u -- ) \ print message
 	else
 		menu_timeout_x @ menu_timeout_y @ at-xy \ position cursor
 		spaces ( n c-addr/u -- n c-addr ) \ erase message
 		2drop ( n c-addr -- )
 	then
 
 	0 25 at-xy ( position cursor back at bottom-left )
 ;
 
 \ This function blocks program flow (loops forever) until a key is pressed.
 \ The key that was pressed is added to the top of the stack in the form of its
 \ decimal ASCII representation. This function is called by the menu-display
 \ function. You need not call it directly.
 \ 
 : getkey ( -- ascii_keycode )
 
 	begin \ loop forever
 
 		menu_timeout_enabled @ 1 = if
 			( -- )
 			seconds ( get current time: -- N )
 			dup menu_time @ <> if ( has time elapsed?: N N N -- N )
 
 				\ At least 1 second has elapsed since last loop
 				\ so we will decrement our "timeout" (really a
 				\ counter, insuring that we do not proceed too
 				\ fast) and update our timeout display.
 
 				menu_time ! ( update time record: N -- )
 				menu_timeout @ ( "time" remaining: -- N )
 				dup 0> if ( greater than 0?: N N 0 -- N )
 					1- ( decrement counter: N -- N )
 					dup menu_timeout !
 						( re-assign: N N Addr -- N )
 				then
 				( -- N )
 
 				dup 0= swap 0< or if ( N <= 0?: N N -- )
 					\ halt the timer
 					0 menu_timeout ! ( 0 Addr -- )
 					0 menu_timeout_enabled ! ( 0 Addr -- )
 				then
 
 				\ update the timer display ( N -- )
 				menu_timeout @ menu-timeout-update
 
 				menu_timeout @ 0= if
 					\ We've reached the end of the timeout
 					\ (user did not cancel by pressing ANY
 					\ key)
 
 					s" menu_timeout_command"  getenv dup
 					-1 = if
 						drop \ clean-up
 					else
 						evaluate
 					then
 				then
 
 			else ( -- N )
 				\ No [detectable] time has elapsed (in seconds)
 				drop ( N -- )
 			then
 			( -- )
 		then
 
 		key? if \ Was a key pressed? (see loader(8))
 
 			\ An actual key was pressed (if the timeout is running,
 			\ kill it regardless of which key was pressed)
 			menu_timeout @ 0<> if
 				0 menu_timeout !
 				0 menu_timeout_enabled !
 
 				\ clear screen of timeout message
 				0 menu-timeout-update
 			then
 
 			\ get the key that was pressed and exit (if we
 			\ get a non-zero ASCII code)
 			key dup 0<> if
 				exit
 			else
 				drop
 			then
 		then
 		50 ms \ sleep for 50 milliseconds (see loader(8))
 
 	again
 ;
 
 : menu-erase ( -- ) \ Erases menu and resets positioning variable to position 1.
 
 	\ Clear the screen area associated with the interactive menu
 	menuX @ menuY @
 	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
 	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
 	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
 	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
 	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
 	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces
 	2drop
 
 	\ Reset the starting index and position for the menu
 	menu_start 1- menuidx !
 	0 menurow !
 ;
 
 only forth
 also menu-infrastructure
 also menu-namespace
 also menu-command-helpers definitions
 
 : toggle_menuitem ( N -- N ) \ toggles caption text and internal menuitem state
 
 	\ ASCII numeral equal to user-selected menu item must be on the stack.
 	\ We do not modify the stack, so the ASCII numeral is left on top.
 
 	dup init_textN c@ 0= if
 		\ NOTE: no need to check toggle_stateN since the first time we
 		\ are called, we will populate init_textN. Further, we don't
 		\ need to test whether menu_caption[x] (ansi_caption[x] when
 		\ loader_color?=1) is available since we would not have been
 		\ called if the caption was NULL.
 
 		\ base name of environment variable
 		dup ( n -- n n ) \ key pressed
 		loader_color? if
 			ansi_caption[x]
 		else
 			menu_caption[x]
 		then	
 		getenv dup -1 <> if
 
 			2 pick ( n c-addr/u -- n c-addr/u n )
 			init_textN ( n c-addr/u n -- n c-addr/u c-addr )
 
 			\ now we have the buffer c-addr on top
 			\ ( followed by c-addr/u of current caption )
 
 			\ Copy the current caption into our buffer
 			2dup c! -rot \ store strlen at first byte
 			begin
 				rot 1+    \ bring alt addr to top and increment
 				-rot -rot \ bring buffer addr to top
 				2dup c@ swap c! \ copy current character
 				1+     \ increment buffer addr
 				rot 1- \ bring buffer len to top and decrement
 				dup 0= \ exit loop if buffer len is zero
 			until
 			2drop \ buffer len/addr
 			drop  \ alt addr
 
 		else
 			drop
 		then
 	then
 
 	\ Now we are certain to have init_textN populated with the initial
 	\ value of menu_caption[x] (ansi_caption[x] with loader_color enabled).
 	\ We can now use init_textN as the untoggled caption and
 	\ toggled_text[x] (toggled_ansi[x] with loader_color enabled) as the
 	\ toggled caption and store the appropriate value into menu_caption[x]
 	\ (again, ansi_caption[x] with loader_color enabled). Last, we'll
 	\ negate the toggled state so that we reverse the flow on subsequent
 	\ calls.
 
 	dup toggle_stateN @ 0= if
 		\ state is OFF, toggle to ON
 
 		dup ( n -- n n ) \ key pressed
 		loader_color? if
 			toggled_ansi[x]
 		else
 			toggled_text[x]
 		then
 		getenv dup -1 <> if
 			\ Assign toggled text to menu caption
 			2 pick ( n c-addr/u -- n c-addr/u n ) \ key pressed
 			loader_color? if
 				ansi_caption[x]
 			else
 				menu_caption[x]
 			then
 			setenv
 		else
 			\ No toggled text, keep the same caption
 			drop ( n -1 -- n ) \ getenv cruft
 		then
 
 		true \ new value of toggle state var (to be stored later)
 	else
 		\ state is ON, toggle to OFF
 
 		dup init_textN count ( n -- n c-addr/u )
 
 		\ Assign init_textN text to menu caption
 		2 pick ( n c-addr/u -- n c-addr/u n ) \ key pressed
 		loader_color? if
 			ansi_caption[x]
 		else
 			menu_caption[x]
 		then
 		setenv
 
 		false \ new value of toggle state var (to be stored below)
 	then
 
 	\ now we'll store the new toggle state (on top of stack)
 	over toggle_stateN !
 ;
 
 : cycle_menuitem ( N -- N ) \ cycles through array of choices for a menuitem
 
 	\ ASCII numeral equal to user-selected menu item must be on the stack.
 	\ We do not modify the stack, so the ASCII numeral is left on top.
 
 	dup cycle_stateN dup @ 1+ \ get value and increment
 
 	\ Before assigning the (incremented) value back to the pointer,
 	\ let's test for the existence of this particular array element.
 	\ If the element exists, we'll store index value and move on.
 	\ Otherwise, we'll loop around to zero and store that.
 
 	dup 48 + ( n addr k -- n addr k k' )
 	         \ duplicate array index and convert to ASCII numeral
 
 	3 pick swap ( n addr k k' -- n addr k n k' ) \ (n,k') as (x,y)
 	loader_color? if
 		ansi_caption[x][y]
 	else
 		menu_caption[x][y]
 	then
 	( n addr k n k' -- n addr k c-addr/u )
 
 	\ Now test for the existence of our incremented array index in the
 	\ form of $menu_caption[x][y] ($ansi_caption[x][y] with loader_color
 	\ enabled) as set in loader.rc(5), et. al.
 
 	getenv dup -1 = if
 		\ No caption set for this array index. Loop back to zero.
 
 		drop ( n addr k -1 -- n addr k ) \ getenv cruft
 		drop 0 ( n addr k -- n addr 0 )  \ new value to store later
 
 		2 pick [char] 0 ( n addr 0 -- n addr 0 n 48 ) \ (n,48) as (x,y)
 		loader_color? if
 			ansi_caption[x][y]
 		else
 			menu_caption[x][y]
 		then
 		( n addr 0 n 48 -- n addr 0 c-addr/u )
 		getenv dup -1 = if
 			\ Highly unlikely to occur, but to ensure things move
 			\ along smoothly, allocate a temporary NULL string
 			drop ( cruft ) s" "
 		then
 	then
 
 	\ At this point, we should have the following on the stack (in order,
 	\ from bottom to top):
 	\ 
 	\    n        - Ascii numeral representing the menu choice (inherited)
 	\    addr     - address of our internal cycle_stateN variable
 	\    k        - zero-based number we intend to store to the above
 	\    c-addr/u - string value we intend to store to menu_caption[x]
 	\               (or ansi_caption[x] with loader_color enabled)
 	\ 
 	\ Let's perform what we need to with the above.
 
 	\ Assign array value text to menu caption
 	4 pick ( n addr k c-addr/u -- n addr k c-addr/u n )
 	loader_color? if
 		ansi_caption[x]
 	else
 		menu_caption[x]
 	then
 	setenv
 
 	swap ! ( n addr k -- n ) \ update array state variable
 ;
 
 only forth definitions also menu-infrastructure
 
 \ Erase and redraw the menu. Useful if you change a caption and want to
 \ update the menu to reflect the new value.
 \ 
 : menu-redraw ( -- )
 	menu-erase
 	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.
 \ 
 : menu-init ( -- )
 	menu_start
 	1- menuidx !    \ Initialize the starting index for the menu
 	0 menurow !     \ Initialize the starting position for the menu
 
 	\ Assign configuration values
 	s" loader_menu_y" getenv dup -1 = if
 		drop \ no custom row position
 		menu_default_y
 	else
 		\ make sure custom position is a number
 		?number 0= if
 			menu_default_y \ or use default
 		then
 	then
 	menuY !
 	s" loader_menu_x" getenv dup -1 = if
 		drop \ no custom column position
 		menu_default_x
 	else
 		\ make sure custom position is a number
 		?number 0= if
 			menu_default_x \ or use default
 		then
 	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
 ;
 
 also menu-namespace
 
 \ Main function. Call this from your `loader.rc' file.
 \ 
 : menu-display ( -- )
 
 	0 menu_timeout_enabled ! \ start with automatic timeout disabled
 
 	\ check indication that automatic execution after delay is requested
 	s" menu_timeout_command" getenv -1 <> if ( Addr C -1 -- | Addr )
 		drop ( just testing existence right now: Addr -- )
 
 		\ initialize state variables
 		seconds menu_time ! ( store the time we started )
 		1 menu_timeout_enabled ! ( enable automatic timeout )
 
 		\ read custom time-duration (if set)
 		s" autoboot_delay" getenv dup -1 = if
 			drop \ no custom duration (remove dup'd bunk -1)
 			menu_timeout_default \ use default setting
 		else
 			2dup ?number 0= if ( if not a number )
 				\ disable timeout if "NO", else use default
 				s" NO" compare-insensitive 0= if
 					0 menu_timeout_enabled !
 					0 ( assigned to menu_timeout below )
 				else
 					menu_timeout_default
 				then
 			else
 				-rot 2drop
 
 				\ boot immediately if less than zero
 				dup 0< if
 					drop
 					menu-create
 					0 25 at-xy
 					0 boot
 				then
 			then
 		then
 		menu_timeout ! ( store value on stack from above )
 
 		menu_timeout_enabled @ 1 = if
 			\ read custom column position (if set)
 			s" loader_menu_timeout_x" getenv dup -1 = if
 				drop \ no custom column position
 				menu_timeout_default_x \ use default setting
 			else
 				\ make sure custom position is a number
 				?number 0= if
 					menu_timeout_default_x \ or use default
 				then
 			then
 			menu_timeout_x ! ( store value on stack from above )
         
 			\ read custom row position (if set)
 			s" loader_menu_timeout_y" getenv dup -1 = if
 				drop \ no custom row position
 				menu_timeout_default_y \ use default setting
 			else
 				\ make sure custom position is a number
 				?number 0= if
 					menu_timeout_default_y \ or use default
 				then
 			then
 			menu_timeout_y ! ( store value on stack from above )
 		then
 	then
 
 	menu-create
 
 	begin \ Loop forever
 
 		0 25 at-xy \ Move cursor to the bottom for output
 		getkey     \ Block here, waiting for a key to be pressed
 
 		dup -1 = if
 			drop exit \ Caught abort (abnormal return)
 		then
 
 		\ Boot if the user pressed Enter/Ctrl-M (13) or
 		\ Ctrl-Enter/Ctrl-J (10)
 		dup over 13 = swap 10 = or if
 			drop ( no longer needed )
 			s" boot" evaluate
 			exit ( pedantic; never reached )
 		then
 
 		dup menureboot @ = if 0 reboot then
 
 		\ Evaluate the decimal ASCII value against known menu item
 		\ key associations and act accordingly
 
 		49 \ Iterator start (loop range 49 to 56; ASCII '1' to '8')
 		begin
 			dup menukeyN @
 			rot tuck = if
 
 				\ Adjust for missing ACPI menuitem on non-i386
 				arch-i386? true <> menuacpi @ 0<> and if
 					menuacpi @ over 2dup < -rot = or
 					over 58 < and if
 					( key >= menuacpi && key < 58: N -- N )
 						1+
 					then
 				then
 
 				\ Test for the environment variable
 				dup menu_command[x]
 				getenv dup -1 <> if
 					\ Execute the stored procedure
 					evaluate
 
 					\ We expect there to be a non-zero
 					\  value left on the stack after
 					\ executing the stored procedure.
 					\ If so, continue to run, else exit.
 
 					0= if
 						drop \ key pressed
 						drop \ loop iterator
 						exit
 					else
 						swap \ need iterator on top
 					then
 				then
 
 				\ Re-adjust for missing ACPI menuitem
 				arch-i386? true <> menuacpi @ 0<> and if
 					swap
 					menuacpi @ 1+ over 2dup < -rot = or
 					over 59 < and if
 						1-
 					then
 					swap
 				then
 			else
 				swap \ need iterator on top
 			then
 
 			\ 
 			\ Check for menu keycode shortcut(s)
 			\ 
 			dup menu_keycode[x]
 			getenv dup -1 = if
 				drop
 			else
 				?number 0<> if
 					rot tuck = if
 						swap
 						dup menu_command[x]
 						getenv dup -1 <> if
 							evaluate
 							0= if
 								2drop
 								exit
 							then
 						else
 							drop
 						then
 					else
 						swap
 					then
 				then
 			then
 
 			1+ dup 56 > \ increment iterator
 			            \ continue if less than 57
 		until
 		drop \ loop iterator
 		drop \ key pressed
 
 	again	\ Non-operational key was pressed; repeat
 ;
 
 \ This function unsets all the possible environment variables associated with
 \ creating the interactive menu.
 \ 
 : menu-unset ( -- )
 
 	49 \ Iterator start (loop range 49 to 56; ASCII '1' to '8')
 	begin
 		dup menu_init[x]    unsetenv	\ menu initializer
 		dup menu_command[x] unsetenv	\ menu command
 		dup menu_caption[x] unsetenv	\ menu caption
 		dup ansi_caption[x] unsetenv	\ ANSI caption
 		dup menu_keycode[x] unsetenv	\ menu keycode
 		dup toggled_text[x] unsetenv	\ toggle_menuitem caption
 		dup toggled_ansi[x] unsetenv	\ toggle_menuitem ANSI caption
 
 		48 \ Iterator start (inner range 48 to 57; ASCII '0' to '9')
 		begin
 			\ cycle_menuitem caption and ANSI caption
 			2dup menu_caption[x][y] unsetenv
 			2dup ansi_caption[x][y] unsetenv
 			1+ dup 57 >
 		until
 		drop \ inner iterator
 
 		0 over menukeyN      !	\ used by menu-create, menu-display
 		0 over init_stateN   !	\ used by menu-create
 		0 over toggle_stateN !	\ used by toggle_menuitem
 		0 over init_textN   c!	\ used by toggle_menuitem
 		0 over cycle_stateN  !	\ used by cycle_menuitem
 
 		1+ dup 56 >	\ increment, continue if less than 57
 	until
 	drop \ iterator
 
 	s" menu_timeout_command" unsetenv	\ menu timeout command
 	s" menu_reboot"          unsetenv	\ Reboot menu option flag
 	s" menu_acpi"            unsetenv	\ ACPI menu option flag
 	s" menu_kernel"          unsetenv	\ Kernel menu option flag
 	s" menu_options"         unsetenv	\ Options separator flag
 	s" menu_optionstext"     unsetenv	\ separator display text
 	s" menu_init"            unsetenv	\ menu initializer
 
 	0 menureboot !
 	0 menuacpi !
 	0 menuoptions !
 ;
 
 only forth definitions also menu-infrastructure
 
 \ This function both unsets menu variables and visually erases the menu area
 \ in-preparation for another menu.
 \ 
 : menu-clear ( -- )
 	menu-unset
 	menu-erase
 ;
 
 bullet menubllt !
 
 also menu-namespace
 
 \ Initialize our menu initialization state variables
 0 init_state1 !
 0 init_state2 !
 0 init_state3 !
 0 init_state4 !
 0 init_state5 !
 0 init_state6 !
 0 init_state7 !
 0 init_state8 !
 
 \ Initialize our boolean state variables
 0 toggle_state1 !
 0 toggle_state2 !
 0 toggle_state3 !
 0 toggle_state4 !
 0 toggle_state5 !
 0 toggle_state6 !
 0 toggle_state7 !
 0 toggle_state8 !
 
 \ Initialize our array state variables
 0 cycle_state1 !
 0 cycle_state2 !
 0 cycle_state3 !
 0 cycle_state4 !
 0 cycle_state5 !
 0 cycle_state6 !
 0 cycle_state7 !
 0 cycle_state8 !
 
 \ Initialize string containers
 0 init_text1 c!
 0 init_text2 c!
 0 init_text3 c!
 0 init_text4 c!
 0 init_text5 c!
 0 init_text6 c!
 0 init_text7 c!
 0 init_text8 c!
 
 only forth definitions
diff --git a/stand/forth/support.4th b/stand/forth/support.4th
index e6addfbdf7bb..d87cf16a16dd 100644
--- a/stand/forth/support.4th
+++ b/stand/forth/support.4th
@@ -1,1617 +1,1692 @@
 \ Copyright (c) 1999 Daniel C. Sobral <dcs@FreeBSD.org>
 \ All rights reserved.
 \ 
 \ Redistribution and use in source and binary forms, with or without
 \ modification, are permitted provided that the following conditions
 \ are met:
 \ 1. Redistributions of source code must retain the above copyright
 \    notice, this list of conditions and the following disclaimer.
 \ 2. Redistributions in binary form must reproduce the above copyright
 \    notice, this list of conditions and the following disclaimer in the
 \    documentation and/or other materials provided with the distribution.
 \
 \ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 \ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 \ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 \ ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 \ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 \ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 \ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 \ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 \ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 \ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 \ SUCH DAMAGE.
 \
 \ $FreeBSD$
 
 \ Loader.rc support functions:
 \
 \ initialize ( addr len -- )	as above, plus load_conf_files
 \ load_conf ( addr len -- )	load conf file given
 \ include_conf_files ( -- )	load all conf files in load_conf_files
 \ print_syntax_error ( -- )	print line and marker of where a syntax
 \				error was detected
 \ print_line ( -- )		print last line processed
 \ load_kernel ( -- )		load kernel
 \ load_modules ( -- )		load modules flagged
 \
 \ Exported structures:
 \
 \ string			counted string structure
 \	cell .addr			string address
 \	cell .len			string length
 \ module			module loading information structure
 \	cell module.flag		should we load it?
 \	string module.name		module's name
 \	string module.loadname		name to be used in loading the module
 \	string module.type		module's type
 \	string module.args		flags to be passed during load
 \	string module.beforeload	command to be executed before load
 \	string module.afterload		command to be executed after load
 \	string module.loaderror		command to be executed if load fails
 \	cell module.next		list chain
 \
 \ Exported global variables;
 \
 \ string conf_files		configuration files to be loaded
 \ cell modules_options		pointer to first module information
 \ value verbose?		indicates if user wants a verbose loading
 \ value any_conf_read?		indicates if a conf file was successfully read
 \
 \ Other exported words:
 \    note, strlen is internal
 \ strdup ( addr len -- addr' len)			similar to strdup(3)
 \ strcat ( addr len addr' len' -- addr len+len' )	similar to strcat(3)
 \ s' ( | string' -- addr len | )			similar to s"
 \ rudimentary structure support
 
 \ Exception values
 
 1 constant ESYNTAX
 2 constant ENOMEM
 3 constant EFREE
 4 constant ESETERROR	\ error setting environment variable
 5 constant EREAD	\ error reading
 6 constant EOPEN
 7 constant EEXEC	\ XXX never catched
 8 constant EBEFORELOAD
 9 constant EAFTERLOAD
 
 \ I/O constants
 
 0 constant SEEK_SET
 1 constant SEEK_CUR
 2 constant SEEK_END
 
 0 constant O_RDONLY
 1 constant O_WRONLY
 2 constant O_RDWR
 
 \ Crude structure support
 
 : structure:
   create here 0 , ['] drop , 0
   does> create here swap dup @ allot cell+ @ execute
 ;
 : member: create dup , over , + does> cell+ @ + ;
 : ;structure swap ! ;
 : constructor! >body cell+ ! ;
 : constructor: over :noname ;
 : ;constructor postpone ; swap cell+ ! ; immediate
 : sizeof ' >body @ state @ if postpone literal then ; immediate
 : offsetof ' >body cell+ @ state @ if postpone literal then ; immediate
 : ptr 1 cells member: ;
 : int 1 cells member: ;
 
 \ String structure
 
 structure: string
 	ptr .addr
 	int .len
 	constructor:
 	  0 over .addr !
 	  0 swap .len !
 	;constructor
 ;structure
 
 
 \ Module options linked list
 
 structure: module
 	int module.flag
 	sizeof string member: module.name
 	sizeof string member: module.loadname
 	sizeof string member: module.type
 	sizeof string member: module.args
 	sizeof string member: module.beforeload
 	sizeof string member: module.afterload
 	sizeof string member: module.loaderror
 	ptr module.next
 ;structure
 
 \ Internal loader structures (preloaded_file, kernel_module, file_metadata)
 \ must be in sync with the C struct in stand/common/bootstrap.h
 structure: preloaded_file
 	ptr pf.name
 	ptr pf.type
 	ptr pf.args
 	ptr pf.metadata	\ file_metadata
 	int pf.loader
 	int pf.addr
 	int pf.size
 	ptr pf.modules	\ kernel_module
 	ptr pf.next	\ preloaded_file
 ;structure
 
 structure: kernel_module
 	ptr km.name
 	\ ptr km.args
 	ptr km.fp	\ preloaded_file
 	ptr km.next	\ kernel_module
 ;structure
 
 structure: file_metadata
 	int		md.size
 	2 member:	md.type	\ this is not ANS Forth compatible (XXX)
 	ptr		md.next	\ file_metadata
 	0 member:	md.data	\ variable size
 ;structure
 
 \ end of structures
 
 \ Global variables
 
 string conf_files
 string nextboot_conf_file
 create module_options sizeof module.next allot 0 module_options !
 create last_module_option sizeof module.next allot 0 last_module_option !
 0 value verbose?
 0 value nextboot?
 
 \ Support string functions
 : strdup { addr len -- addr' len' }
   len allocate if ENOMEM throw then
   addr over len move len
 ;
 
 : strcat  { addr len addr' len' -- addr len+len' }
   addr' addr len + len' move
   addr len len' +
 ;
 
 : strchr { addr len c -- addr' len' }
   begin
     len
   while
     addr c@ c = if addr len exit then
     addr 1 + to addr
     len 1 - to len
   repeat
   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
 ; immediate
 
 : 2>r postpone >r postpone >r ; immediate
 : 2r> postpone r> postpone r> ; immediate
 : 2r@ postpone 2r> postpone 2dup postpone 2>r ; immediate
 
 : 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
 	dup 0= if 2drop 2drop false exit then
 	begin
 		begin
 			swap dup c@ dup 32 = over 9 = or over 10 = or
 			over 13 = or over 44 = or swap drop
 		while 1+ swap 1- repeat
 		swap 2 pick 1- over <
 	while
 		2over 2over drop over compare-insensitive 0= if
 			2 pick over = if 2drop 2drop true exit then
 			2 pick tuck - -rot + swap over c@ dup 32 =
 			over 9 = or over 10 = or over 13 = or over 44 = or
 			swap drop if 2drop 2drop true exit then
 		then begin
 			swap dup c@ dup 32 = over 9 = or over 10 = or
 			over 13 = or over 44 = or swap drop
 			if false else true then 2 pick 0> and
 		while 1+ swap 1- repeat
 		swap
 	repeat
 	2drop 2drop false
 ;
 
 : boot_serial? ( -- 0 | -1 )
 	s" console" getenv dup -1 <> if
 		s" comconsole" 2swap contains?
 	else drop false then
-	s" boot_serial" getenv dup -1 <> if
-		swap drop 0>
-	else drop false then
-	or \ console contains comconsole ( or ) boot_serial
-	s" boot_multicons" getenv dup -1 <> if
-		swap drop 0>
-	else drop false then
-	or \ previous boolean ( or ) boot_multicons
+\	s" boot_serial" getenv dup -1 <> if
+\		swap drop 0>
+\	else drop false then
+\	or \ console contains comconsole ( or ) boot_serial
+\	s" boot_multicons" getenv dup -1 <> if
+\		swap drop 0>
+\	else drop false then
+\	or \ previous boolean ( or ) boot_multicons
+;
+
+: framebuffer? ( -- t )
+	s" console" getenv
+	2dup s" efi" compare 0<> >r
+	s" vidconsole" compare 0<> r> and if
+		FALSE exit
+	then
+	s" screen.depth" getenv?
 ;
 
 \ Private definitions
 
 vocabulary support-functions
 only forth also support-functions definitions
 
 \ Some control characters constants
 
 7 constant bell
 8 constant backspace
 9 constant tab
 10 constant lf
 13 constant <cr>
 
 \ Read buffer size
 
 80 constant read_buffer_size
 
 \ Standard suffixes
 
 : load_module_suffix		s" _load" ;
 : module_loadname_suffix	s" _name" ;
 : module_type_suffix		s" _type" ;
 : module_args_suffix		s" _flags" ;
 : module_beforeload_suffix	s" _before" ;
 : module_afterload_suffix	s" _after" ;
 : module_loaderror_suffix	s" _error" ;
 
 \ Support operators
 
 : >= < 0= ;
 : <= > 0= ;
 
 \ Assorted support functions
 
 : free-memory free if EFREE throw then ;
 
 : strget { var -- addr len } var .addr @ var .len @ ;
 
 \ assign addr len to variable.
 : strset  { addr len var -- } addr var .addr !  len var .len !  ;
 
 \ free memory and reset fields
 : strfree { var -- } var .addr @ ?dup if free-memory 0 0 var strset then ;
 
 \ free old content, make a copy of the string and assign to variable
 : string= { addr len var -- } var strfree addr len strdup var strset ;
 
 : strtype ( str -- ) strget type ;
 
 \ assign a reference to what is on the stack
 : strref { addr len var -- addr len }
   addr var .addr ! len var .len ! addr len
 ;
 
 \ unquote a string
 : unquote ( addr len -- addr len )
   over c@ [char] " = if 2 chars - swap char+ swap then
 ;
 
 \ Assignment data temporary storage
 
 string name_buffer
 string value_buffer
 
 \ Line by line file reading functions
 \
 \ exported:
 \	line_buffer
 \	end_of_file?
 \	fd
 \	read_line
 \	reset_line_reading
 
 vocabulary line-reading
 also line-reading definitions
 
 \ File data temporary storage
 
 string read_buffer
 0 value read_buffer_ptr
 
 \ File's line reading function
 
 get-current ( -- wid ) previous definitions
 
 string line_buffer
 0 value end_of_file?
 variable fd
 
 >search ( wid -- ) definitions
 
 : skip_newlines
   begin
     read_buffer .len @ read_buffer_ptr >
   while
     read_buffer .addr @ read_buffer_ptr + c@ lf = if
       read_buffer_ptr char+ to read_buffer_ptr
     else
       exit
     then
   repeat
 ;
 
 : scan_buffer  ( -- addr len )
   read_buffer_ptr >r
   begin
     read_buffer .len @ r@ >
   while
     read_buffer .addr @ r@ + c@ lf = if
       read_buffer .addr @ read_buffer_ptr +  ( -- addr )
       r@ read_buffer_ptr -                   ( -- len )
       r> to read_buffer_ptr
       exit
     then
     r> char+ >r
   repeat
   read_buffer .addr @ read_buffer_ptr +  ( -- addr )
   r@ read_buffer_ptr -                   ( -- len )
   r> to read_buffer_ptr
 ;
 
 : line_buffer_resize  ( len -- len )
   dup 0= if exit then
   >r
   line_buffer .len @ if
     line_buffer .addr @
     line_buffer .len @ r@ +
     resize if ENOMEM throw then
   else
     r@ allocate if ENOMEM throw then
   then
   line_buffer .addr !
   r>
 ;
     
 : append_to_line_buffer  ( addr len -- )
   dup 0= if 2drop exit then
   line_buffer strget
   2swap strcat
   line_buffer .len !
   drop
 ;
 
 : read_from_buffer
   scan_buffer            ( -- addr len )
   line_buffer_resize     ( len -- len )
   append_to_line_buffer  ( addr len -- )
 ;
 
 : refill_required?
   read_buffer .len @ read_buffer_ptr =
   end_of_file? 0= and
 ;
 
 : refill_buffer
   0 to read_buffer_ptr
   read_buffer .addr @ 0= if
     read_buffer_size allocate if ENOMEM throw then
     read_buffer .addr !
   then
   fd @ read_buffer .addr @ read_buffer_size fread
   dup -1 = if EREAD throw then
   dup 0= if true to end_of_file? then
   read_buffer .len !
 ;
 
 get-current ( -- wid ) previous definitions >search ( wid -- )
 
 : reset_line_reading
   0 to read_buffer_ptr
 ;
 
 : read_line
   line_buffer strfree
   skip_newlines
   begin
     read_from_buffer
     refill_required?
   while
     refill_buffer
   repeat
 ;
 
 only forth also support-functions definitions
 
 \ Conf file line parser:
 \ <line> ::= <spaces><name><spaces>'='<spaces><value><spaces>[<comment>] |
 \            <spaces>[<comment>]
 \ <name> ::= <letter>{<letter>|<digit>|'_'}
 \ <value> ::= '"'{<character_set>|'\'<anything>}'"' | <name>
 \ <character_set> ::= ASCII 32 to 126, except '\' and '"'
 \ <comment> ::= '#'{<anything>}
 \
 \ exported:
 \	line_pointer
 \	process_conf
 
 0 value line_pointer
 
 vocabulary file-processing
 also file-processing definitions
 
 \ parser functions
 \
 \ exported:
 \	get_assignment
 
 vocabulary parser
 also parser definitions
 
 0 value parsing_function
 0 value end_of_line
 
 : end_of_line?  line_pointer end_of_line = ;
 
 \ classifiers for various character classes in the input line
 
 : letter?
   line_pointer c@ >r
   r@ [char] A >=
   r@ [char] Z <= and
   r@ [char] a >=
   r> [char] z <= and
   or
 ;
 
 : digit?
   line_pointer c@ >r
   r@ [char] - =
   r@ [char] 0 >=
   r> [char] 9 <= and
   or
 ;
 
 : quote?  line_pointer c@ [char] " = ;
 
 : assignment_sign?  line_pointer c@ [char] = = ;
 
 : comment?  line_pointer c@ [char] # = ;
 
 : space?  line_pointer c@ bl = line_pointer c@ tab = or ;
 
 : backslash?  line_pointer c@ [char] \ = ;
 
 : underscore?  line_pointer c@ [char] _ = ;
 
 : dot?  line_pointer c@ [char] . = ;
 
 \ manipulation of input line
 : skip_character line_pointer char+ to line_pointer ;
 
 : skip_to_end_of_line end_of_line to line_pointer ;
 
 : eat_space
   begin
     end_of_line? if 0 else space? then
   while
     skip_character
   repeat
 ;
 
 : parse_name  ( -- addr len )
   line_pointer
   begin
     end_of_line? if 0 else letter? digit? underscore? dot? or or or then
   while
     skip_character
   repeat
   line_pointer over -
   strdup
 ;
 
 : remove_backslashes  { addr len | addr' len' -- addr' len' }
   len allocate if ENOMEM throw then
   to addr'
   addr >r
   begin
     addr c@ [char] \ <> if
       addr c@ addr' len' + c!
       len' char+ to len'
     then
     addr char+ to addr
     r@ len + addr =
   until
   r> drop
   addr' len'
 ;
 
 : parse_quote  ( -- addr len )
   line_pointer
   skip_character
   end_of_line? if ESYNTAX throw then
   begin
     quote? 0=
   while
     backslash? if
       skip_character
       end_of_line? if ESYNTAX throw then
     then
     skip_character
     end_of_line? if ESYNTAX throw then 
   repeat
   skip_character
   line_pointer over -
   remove_backslashes
 ;
 
 : read_name
   parse_name		( -- addr len )
   name_buffer strset
 ;
 
 : read_value
   quote? if
     parse_quote		( -- addr len )
   else
     parse_name		( -- addr len )
   then
   value_buffer strset
 ;
 
 : comment
   skip_to_end_of_line
 ;
 
 : white_space_4
   eat_space
   comment? if ['] comment to parsing_function exit then
   end_of_line? 0= if ESYNTAX throw then
 ;
 
 : variable_value
   read_value
   ['] white_space_4 to parsing_function
 ;
 
 : white_space_3
   eat_space
   letter? digit? quote? or or if
     ['] variable_value to parsing_function exit
   then
   ESYNTAX throw
 ;
 
 : assignment_sign
   skip_character
   ['] white_space_3 to parsing_function
 ;
 
 : white_space_2
   eat_space
   assignment_sign? if ['] assignment_sign to parsing_function exit then
   ESYNTAX throw
 ;
 
 : variable_name
   read_name
   ['] white_space_2 to parsing_function
 ;
 
 : white_space_1
   eat_space
   letter?  if ['] variable_name to parsing_function exit then
   comment? if ['] comment to parsing_function exit then
   end_of_line? 0= if ESYNTAX throw then
 ;
 
 get-current ( -- wid ) previous definitions >search ( wid -- )
 
 : get_assignment
   line_buffer strget + to end_of_line
   line_buffer .addr @ to line_pointer
   ['] white_space_1 to parsing_function
   begin
     end_of_line? 0=
   while
     parsing_function execute
   repeat
   parsing_function ['] comment =
   parsing_function ['] white_space_1 =
   parsing_function ['] white_space_4 =
   or or 0= if ESYNTAX throw then
 ;
 
 only forth also support-functions also file-processing definitions
 
 \ Process line
 
 : assignment_type?  ( addr len -- flag )
   name_buffer strget
   compare 0=
 ;
 
 : suffix_type?  ( addr len -- flag )
   name_buffer .len @ over <= if 2drop false exit then
   name_buffer .len @ over - name_buffer .addr @ +
   over compare 0=
 ;
 
 : loader_conf_files?  s" loader_conf_files" assignment_type?  ;
 
 : nextboot_flag?  s" nextboot_enable" assignment_type?  ;
 
 : nextboot_conf? s" nextboot_conf" assignment_type?  ;
 
 : verbose_flag? s" verbose_loading" assignment_type?  ;
 
 : execute? s" exec" assignment_type?  ;
 
 : module_load? load_module_suffix suffix_type? ;
 
 : module_loadname?  module_loadname_suffix suffix_type?  ;
 
 : module_type?  module_type_suffix suffix_type?  ;
 
 : module_args?  module_args_suffix suffix_type?  ;
 
 : module_beforeload?  module_beforeload_suffix suffix_type?  ;
 
 : module_afterload?  module_afterload_suffix suffix_type?  ;
 
 : module_loaderror?  module_loaderror_suffix suffix_type?  ;
 
 \ build a 'set' statement and execute it
 : set_environment_variable
   name_buffer .len @ value_buffer .len @ + 5 chars + \ size of result string
   allocate if ENOMEM throw then
   dup 0  \ start with an empty string and append the pieces
   s" set " strcat
   name_buffer strget strcat
   s" =" strcat
   value_buffer strget strcat
   ['] evaluate catch if
     2drop free drop
     ESETERROR throw
   else
     free-memory
   then
 ;
 
 : set_conf_files
   set_environment_variable
   s" loader_conf_files" getenv conf_files string=
 ;
 
 : set_nextboot_conf
   value_buffer strget unquote nextboot_conf_file string=
 ;
 
 : append_to_module_options_list  ( addr -- )
   module_options @ 0= if
     dup module_options !
     last_module_option !
   else
     dup last_module_option @ module.next !
     last_module_option !
   then
 ;
 
 : set_module_name  { addr -- }	\ check leaks
   name_buffer strget addr module.name string=
 ;
 
 : yes_value?
   value_buffer strget	\ XXX could use unquote
   2dup s' "YES"' compare >r
   2dup s' "yes"' compare >r
   2dup s" YES" compare >r
   s" yes" compare r> r> r> and and and 0=
 ;
 
 : find_module_option  ( -- addr | 0 ) \ return ptr to entry matching name_buffer
   module_options @
   begin
     dup
   while
     dup module.name strget
     name_buffer strget
     compare 0= if exit then
     module.next @
   repeat
 ;
 
 : new_module_option  ( -- addr )
   sizeof module allocate if ENOMEM throw then
   dup sizeof module erase
   dup append_to_module_options_list
   dup set_module_name
 ;
 
 : get_module_option  ( -- addr )
   find_module_option
   ?dup 0= if new_module_option then
 ;
 
 : set_module_flag
   name_buffer .len @ load_module_suffix nip - name_buffer .len !
   yes_value? get_module_option module.flag !
 ;
 
 : set_module_args
   name_buffer .len @ module_args_suffix nip - name_buffer .len !
   value_buffer strget unquote
   get_module_option module.args string=
 ;
 
 : set_module_loadname
   name_buffer .len @ module_loadname_suffix nip - name_buffer .len !
   value_buffer strget unquote
   get_module_option module.loadname string=
 ;
 
 : set_module_type
   name_buffer .len @ module_type_suffix nip - name_buffer .len !
   value_buffer strget unquote
   get_module_option module.type string=
 ;
 
 : set_module_beforeload
   name_buffer .len @ module_beforeload_suffix nip - name_buffer .len !
   value_buffer strget unquote
   get_module_option module.beforeload string=
 ;
 
 : set_module_afterload
   name_buffer .len @ module_afterload_suffix nip - name_buffer .len !
   value_buffer strget unquote
   get_module_option module.afterload string=
 ;
 
 : set_module_loaderror
   name_buffer .len @ module_loaderror_suffix nip - name_buffer .len !
   value_buffer strget unquote
   get_module_option module.loaderror string=
 ;
 
 : set_nextboot_flag
   yes_value? to nextboot?
 ;
 
 : set_verbose
   yes_value? to verbose?
 ;
 
 : execute_command
   value_buffer strget unquote
   ['] evaluate catch if EEXEC throw then
 ;
 
 : process_assignment
   name_buffer .len @ 0= if exit then
   loader_conf_files?	if set_conf_files exit then
   nextboot_flag?	if set_nextboot_flag exit then
   nextboot_conf?	if set_nextboot_conf exit then
   verbose_flag?		if set_verbose exit then
   execute?		if execute_command exit then
   module_load?		if set_module_flag exit then
   module_loadname?	if set_module_loadname exit then
   module_type?		if set_module_type exit then
   module_args?		if set_module_args exit then
   module_beforeload?	if set_module_beforeload exit then
   module_afterload?	if set_module_afterload exit then
   module_loaderror?	if set_module_loaderror exit then
   set_environment_variable
 ;
 
 \ free_buffer  ( -- )
 \
 \ Free some pointers if needed. The code then tests for errors
 \ in freeing, and throws an exception if needed. If a pointer is
 \ not allocated, it's value (0) is used as flag.
 
 : free_buffers
   name_buffer strfree
   value_buffer strfree
 ;
 
 \ Higher level file processing
 
 get-current ( -- wid ) previous definitions >search ( wid -- )
 
 : process_conf
   begin
     end_of_file? 0=
   while
     free_buffers
     read_line
     get_assignment
     ['] process_assignment catch
     ['] free_buffers catch
     swap throw throw
   repeat
 ;
 
 : peek_file ( addr len -- )
   0 to end_of_file?
   reset_line_reading
   O_RDONLY fopen fd !
   fd @ -1 = if EOPEN throw then
   free_buffers
   read_line
   get_assignment
   ['] process_assignment catch
   ['] free_buffers catch
   fd @ fclose
   swap throw throw
 ;
   
 only forth also support-functions definitions
 
 \ Interface to loading conf files
 
 : load_conf  ( addr len -- )
   0 to end_of_file?
   reset_line_reading
   O_RDONLY fopen fd !
   fd @ -1 = if EOPEN throw then
   ['] process_conf catch
   fd @ fclose
   throw
 ;
 
 : print_line line_buffer strtype cr ;
 
 : print_syntax_error
   line_buffer strtype cr
   line_buffer .addr @
   begin
     line_pointer over <>
   while
     bl emit char+
   repeat
   drop
   ." ^" cr
 ;
 
 
 \ Debugging support functions
 
 only forth definitions also support-functions
 
 : test-file 
   ['] load_conf catch dup .
   ESYNTAX = if cr print_syntax_error then
 ;
 
 \ find a module name, leave addr on the stack (0 if not found)
 : find-module ( <module> -- ptr | 0 )
   bl parse ( addr len )
   module_options @ >r ( store current pointer )
   begin
     r@
   while
     2dup ( addr len addr len )
     r@ module.name strget
     compare 0= if drop drop r> exit then ( found it )
     r> module.next @ >r
   repeat
   type ."  was not found" cr r>
 ;
 
 : show-nonempty ( addr len mod -- )
   strget dup verbose? or if
     2swap type type cr
   else
     drop drop drop drop
   then ;
 
 : show-one-module { addr -- addr }
   ." Name:        " addr module.name strtype cr
   s" Path:        " addr module.loadname show-nonempty
   s" Type:        " addr module.type show-nonempty
   s" Flags:       " addr module.args show-nonempty
   s" Before load: " addr module.beforeload show-nonempty
   s" After load:  " addr module.afterload show-nonempty
   s" Error:       " addr module.loaderror show-nonempty
   ." Status:      " addr module.flag @ if ." Load" else ." Don't load" then cr
   cr
   addr
 ;
 
 : show-module-options
   module_options @
   begin
     ?dup
   while
     show-one-module
     module.next @
   repeat
 ;
 
 : free-one-module { addr -- addr }
   addr module.name strfree
   addr module.loadname strfree
   addr module.type strfree
   addr module.args strfree
   addr module.beforeload strfree
   addr module.afterload strfree
   addr module.loaderror strfree
   addr
 ;
 
 : free-module-options
   module_options @
   begin
     ?dup
   while
     free-one-module
     dup module.next @
     swap free-memory
   repeat
   0 module_options !
   0 last_module_option !
 ;
 
 only forth also support-functions definitions
 
 \ Variables used for processing multiple conf files
 
 string current_file_name_ref	\ used to print the file name
 
 \ Indicates if any conf file was successfully read
 
 0 value any_conf_read?
 
 \ loader_conf_files processing support functions
 
 : get_conf_files ( -- addr len )  \ put addr/len on stack, reset var
   conf_files strget 0 0 conf_files strset
 ;
 
 : skip_leading_spaces  { addr len pos -- addr len pos' }
   begin
     pos len = if 0 else addr pos + c@ bl = then
   while
     pos char+ to pos
   repeat
   addr len pos
 ;
 
 \ return the file name at pos, or free the string if nothing left
 : get_file_name  { addr len pos -- addr len pos' addr' len' || 0 }
   pos len = if 
     addr free abort" Fatal error freeing memory"
     0 exit
   then
   pos >r
   begin
     \ stay in the loop until have chars and they are not blank
     pos len = if 0 else addr pos + c@ bl <> then
   while
     pos char+ to pos
   repeat
   addr len pos addr r@ + pos r> -
 ;
 
 : get_next_file  ( addr len ptr -- addr len ptr' addr' len' | 0 )
   skip_leading_spaces
   get_file_name
 ;
 
 : print_current_file
   current_file_name_ref strtype
 ;
 
 : process_conf_errors
   dup 0= if true to any_conf_read? drop exit then
   >r 2drop r>
   dup ESYNTAX = if
     ." Warning: syntax error on file " print_current_file cr
     print_syntax_error drop exit
   then
   dup ESETERROR = if
     ." Warning: bad definition on file " print_current_file cr
     print_line drop exit
   then
   dup EREAD = if
     ." Warning: error reading file " print_current_file cr drop exit
   then
   dup EOPEN = if
     verbose? if ." Warning: unable to open file " print_current_file cr then
     drop exit
   then
   dup EFREE = abort" Fatal error freeing memory"
   dup ENOMEM = abort" Out of memory"
   throw  \ Unknown error -- pass ahead
 ;
 
 \ Process loader_conf_files recursively
 \ Interface to loader_conf_files processing
 
 : include_conf_files
   get_conf_files 0	( addr len offset )
   begin
     get_next_file ?dup ( addr len 1 | 0 )
   while
     current_file_name_ref strref
     ['] load_conf catch
     process_conf_errors
     conf_files .addr @ if recurse then
   repeat
 ;
 
 : get_nextboot_conf_file ( -- addr len )
   nextboot_conf_file strget
 ;
 
 : rewrite_nextboot_file ( -- )
   get_nextboot_conf_file
   O_WRONLY fopen fd !
   fd @ -1 = if EOPEN throw then
   fd @ s' nextboot_enable="NO" ' fwrite ( fd buf len -- nwritten ) drop
   fd @ fclose
 ;
 
 : include_nextboot_file ( -- )
   s" nextboot_enable" getenv dup -1 <> if
     2dup s' "YES"' compare >r
     2dup s' "yes"' compare >r
     2dup s" YES" compare >r
     2dup s" yes" compare r> r> r> and and and 0= to nextboot?
   else
     drop
     get_nextboot_conf_file
     ['] peek_file catch if 2drop then
   then
   nextboot? if
     get_nextboot_conf_file
     current_file_name_ref strref
     ['] load_conf catch
     process_conf_errors
     ['] rewrite_nextboot_file catch if 2drop then
   then
   s' "NO"' s" nextboot_enable" setenv
 ;
 
 \ Module loading functions
 
 : load_parameters  { addr -- addr addrN lenN ... addr1 len1 N }
   addr
   addr module.args strget
   addr module.loadname .len @ if
     addr module.loadname strget
   else
     addr module.name strget
   then
   addr module.type .len @ if
     addr module.type strget
     s" -t "
     4 ( -t type name flags )
   else
     2 ( name flags )
   then
 ;
 
 : before_load  ( addr -- addr )
   dup module.beforeload .len @ if
     dup module.beforeload strget
     ['] evaluate catch if EBEFORELOAD throw then
   then
 ;
 
 : after_load  ( addr -- addr )
   dup module.afterload .len @ if
     dup module.afterload strget
     ['] evaluate catch if EAFTERLOAD throw then
   then
 ;
 
 : load_error  ( addr -- addr )
   dup module.loaderror .len @ if
     dup module.loaderror strget
     evaluate  \ This we do not intercept so it can throw errors
   then
 ;
 
 : pre_load_message  ( addr -- addr )
   verbose? if
     dup module.name strtype
     ." ..."
   then
 ;
 
 : load_error_message verbose? if ." failed!" cr then ;
 
 : load_successful_message verbose? if ." ok" cr then ;
 
 : load_module
   load_parameters load
 ;
 
 : process_module  ( addr -- addr )
   pre_load_message
   before_load
   begin
     ['] load_module catch if
       dup module.loaderror .len @ if
         load_error			\ Command should return a flag!
       else 
         load_error_message true		\ Do not retry
       then
     else
       after_load
       load_successful_message true	\ Successful, do not retry
     then
   until
 ;
 
 : process_module_errors  ( addr ior -- )
   dup EBEFORELOAD = if
     drop
     ." Module "
     dup module.name strtype
     dup module.loadname .len @ if
       ." (" dup module.loadname strtype ." )"
     then
     cr
     ." Error executing "
     dup module.beforeload strtype cr	\ XXX there was a typo here
     abort
   then
 
   dup EAFTERLOAD = if
     drop
     ." Module "
     dup module.name .addr @ over module.name .len @ type
     dup module.loadname .len @ if
       ." (" dup module.loadname strtype ." )"
     then
     cr
     ." Error executing "
     dup module.afterload strtype cr
     abort
   then
 
   throw  \ Don't know what it is all about -- pass ahead
 ;
 
 \ Module loading interface
 
 \ scan the list of modules, load enabled ones.
 : load_modules  ( -- ) ( throws: abort & user-defined )
   module_options @	( list_head )
   begin
     ?dup
   while
     dup module.flag @ if
       ['] process_module catch
       process_module_errors
     then
     module.next @
   repeat
 ;
 
 \ h00h00 magic used to try loading either a kernel with a given name,
 \ or a kernel with the default name in a directory of a given name
 \ (the pain!)
 
 : bootpath s" /boot/" ;
 : modulepath s" module_path" ;
 
 \ Functions used to save and restore module_path's value.
 : saveenv ( addr len | -1 -- addr' len | 0 -1 )
   dup -1 = if 0 swap exit then
   strdup
 ;
 : freeenv ( addr len | 0 -1 )
   -1 = if drop else free abort" Freeing error" then
 ;
 : restoreenv  ( addr len | 0 -1 -- )
   dup -1 = if ( it wasn't set )
     2drop
     modulepath unsetenv
   else
     over >r
     modulepath setenv
     r> free abort" Freeing error"
   then
 ;
 
 : clip_args   \ Drop second string if only one argument is passed
   1 = if
     2swap 2drop
     1
   else
     2
   then
 ;
 
 also builtins
 
 \ Parse filename from a semicolon-separated list
 
 \ replacement, not working yet
 : newparse-; { addr len | a1 -- a' len-x addr x }
   addr len [char] ; strchr dup if	( a1 len1 )
     swap to a1	( store address )
     1 - a1 @ 1 + swap ( remove match )
     addr a1 addr -
   else
     0 0 addr len
   then
 ;
 
 : parse-; ( addr len -- addr' len-x addr x )
   over 0 2swap			( addr 0 addr len )
   begin
     dup 0 <>			( addr 0 addr len )
   while
     over c@ [char] ; <>		( addr 0 addr len flag )
   while
     1- swap 1+ swap
     2swap 1+ 2swap
   repeat then
   dup 0 <> if
     1- swap 1+ swap
   then
   2swap
 ;
 
 \ Try loading one of multiple kernels specified
 
 : try_multiple_kernels ( addr len addr' len' args -- flag )
   >r
   begin
     parse-; 2>r
     2over 2r>
     r@ clip_args
     s" DEBUG" getenv? if
       s" echo Module_path: ${module_path}" evaluate
       ." Kernel     : " >r 2dup type r> cr
       dup 2 = if ." Flags      : " >r 2over type r> cr then
     then
     1 load
   while
     dup 0=
   until
     1 >r \ Failure
   else
     0 >r \ Success
   then
   2drop 2drop
   r>
   r> drop
 ;
 
 \ Try to load a kernel; the kernel name is taken from one of
 \ the following lists, as ordered:
 \
 \   1. The "bootfile" environment variable
 \   2. The "kernel" environment variable
 \
 \ Flags are passed, if available. If not, dummy values must be given.
 \
 \ The kernel gets loaded from the current module_path.
 
 : load_a_kernel ( flags len 1 | x x 0 -- flag )
   local args
   2local flags
   0 0 2local kernel
   end-locals
 
   \ Check if a default kernel name exists at all, exits if not
   s" bootfile" getenv dup -1 <> if
     to kernel
     flags kernel args 1+ try_multiple_kernels
     dup 0= if exit then
   then
   drop
 
   s" kernel" getenv dup -1 <> if
     to kernel
   else
     drop
     1 exit \ Failure
   then
 
   \ Try all default kernel names
   flags kernel args 1+ try_multiple_kernels
 ;
 
 \ Try to load a kernel; the kernel name is taken from one of
 \ the following lists, as ordered:
 \
 \   1. The "bootfile" environment variable
 \   2. The "kernel" environment variable
 \
 \ Flags are passed, if provided.
 \
 \ The kernel will be loaded from a directory computed from the
 \ path given. Two directories will be tried in the following order:
 \
 \   1. /boot/path
 \   2. path
 \
 \ The module_path variable is overridden if load is successful, by
 \ prepending the successful path.
 
 : load_from_directory ( path len 1 | flags len' path len 2 -- flag )
   local args
   2local path
   args 1 = if 0 0 then
   2local flags
   0 0 2local oldmodulepath \ like a string
   0 0 2local newmodulepath \ like a string
   end-locals
 
   \ Set the environment variable module_path, and try loading
   \ the kernel again.
   modulepath getenv saveenv to oldmodulepath
 
   \ Try prepending /boot/ first
   bootpath nip path nip + 	\ total length
   oldmodulepath nip dup -1 = if
     drop
   else
     1+ +			\ add oldpath -- XXX why the 1+ ?
   then
   allocate if ( out of memory ) 1 exit then \ XXX throw ?
 
   0
   bootpath strcat
   path strcat
   2dup to newmodulepath
   modulepath setenv
 
   \ Try all default kernel names
   flags args 1- load_a_kernel
   0= if ( success )
     oldmodulepath nip -1 <> if
       newmodulepath s" ;" strcat
       oldmodulepath strcat
       modulepath setenv
       newmodulepath drop free-memory
       oldmodulepath drop free-memory
     then
     0 exit
   then
 
   \ Well, try without the prepended /boot/
   path newmodulepath drop swap move
   newmodulepath drop path nip
   2dup to newmodulepath
   modulepath setenv
 
   \ Try all default kernel names
   flags args 1- load_a_kernel
   if ( failed once more )
     oldmodulepath restoreenv
     newmodulepath drop free-memory
     1
   else
     oldmodulepath nip -1 <> if
       newmodulepath s" ;" strcat
       oldmodulepath strcat
       modulepath setenv
       newmodulepath drop free-memory
       oldmodulepath drop free-memory
     then
     0
   then
 ;
 
 \ Try to load a kernel; the kernel name is taken from one of
 \ the following lists, as ordered:
 \
 \   1. The "bootfile" environment variable
 \   2. The "kernel" environment variable
 \   3. The "path" argument
 \
 \ Flags are passed, if provided.
 \
 \ The kernel will be loaded from a directory computed from the
 \ path given. Two directories will be tried in the following order:
 \
 \   1. /boot/path
 \   2. path
 \
 \ Unless "path" is meant to be kernel name itself. In that case, it
 \ will first be tried as a full path, and, next, search on the
 \ directories pointed by module_path.
 \
 \ The module_path variable is overridden if load is successful, by
 \ prepending the successful path.
 
 : load_directory_or_file ( path len 1 | flags len' path len 2 -- flag )
   local args
   2local path
   args 1 = if 0 0 then
   2local flags
   end-locals
 
   \ First, assume path is an absolute path to a directory
   flags path args clip_args load_from_directory
   dup 0= if exit else drop then
 
   \ Next, assume path points to the kernel
   flags path args try_multiple_kernels
 ;
 
 : initialize  ( addr len -- )
   strdup conf_files strset
 ;
 
 : kernel_options ( -- addr len 1 | 0 )
   s" kernel_options" getenv
   dup -1 = if drop 0 else 1 then
 ;
 
 : standard_kernel_search  ( flags 1 | 0 -- flag )
   local args
   args 0= if 0 0 then
   2local flags
   s" kernel" getenv
   dup -1 = if 0 swap then
   2local path
   end-locals
 
   path nip -1 = if ( there isn't a "kernel" environment variable )
     flags args load_a_kernel
   else
     flags path args 1+ clip_args load_directory_or_file
   then
 ;
 
 : load_kernel  ( -- ) ( throws: abort )
   kernel_options standard_kernel_search
   abort" Unable to load a kernel!"
 ;
 
 : load_xen ( -- flag )
   s" xen_kernel" getenv dup -1 <> if
     1 1 load ( c-addr/u flag N -- flag )
   else
     drop
     0 ( -1 -- flag )
   then
 ;
 
 : load_xen_throw ( -- ) ( throws: abort )
   load_xen
   abort" Unable to load Xen!"
 ;
 
 : set_defaultoptions  ( -- )
   s" kernel_options" getenv dup -1 = if
     drop
   else
     s" temp_options" setenv
   then
 ;
 
 \ pick the i-th argument, i starts at 0
 : argv[]  ( aN uN ... a1 u1 N i -- aN uN ... a1 u1 N ai+1 ui+1 )
   2dup = if 0 0 exit then	\ out of range
   dup >r
   1+ 2* ( skip N and ui )
   pick
   r>
   1+ 2* ( skip N and ai )
   pick
 ;
 
 : drop_args  ( aN uN ... a1 u1 N -- )
   0 ?do 2drop loop
 ;
 
 : argc
   dup
 ;
 
 : queue_argv  ( aN uN ... a1 u1 N a u -- a u aN uN ... a1 u1 N+1 )
   >r
   over 2* 1+ -roll
   r>
   over 2* 1+ -roll
   1+
 ;
 
 : unqueue_argv  ( aN uN ... a1 u1 N -- aN uN ... a2 u2 N-1 a1 u1 )
   1- -rot
 ;
 
 \ compute the length of the buffer including the spaces between words
 : strlen(argv) ( aN uN .. a1 u1 N -- aN uN .. a1 u1 N len )
   dup 0= if 0 exit then
   0 >r	\ Size
   0 >r	\ Index
   begin
     argc r@ <>
   while
     r@ argv[]
     nip
     r> r> rot + 1+
     >r 1+ >r
   repeat
   r> drop
   r>
 ;
 
 : concat_argv  ( aN uN ... a1 u1 N -- a u )
   strlen(argv) allocate if ENOMEM throw then
   0 2>r ( save addr 0 on return stack )
 
   begin
     dup
   while
     unqueue_argv ( ... N a1 u1 )
     2r> 2swap	 ( old a1 u1 )
     strcat
     s"  " strcat ( append one space ) \ XXX this gives a trailing space
     2>r		( store string on the result stack )
   repeat
   drop_args
   2r>
 ;
 
 : set_tempoptions  ( addrN lenN ... addr1 len1 N -- addr len 1 | 0 )
   \ Save the first argument, if it exists and is not a flag
   argc if
     0 argv[] drop c@ [char] - <> if
       unqueue_argv 2>r  \ Filename
       1 >r		\ Filename present
     else
       0 >r		\ Filename not present
     then
   else
     0 >r		\ Filename not present
   then
 
   \ If there are other arguments, assume they are flags
   ?dup if
     concat_argv
     2dup s" temp_options" setenv
     drop free if EFREE throw then
   else
     set_defaultoptions
   then
 
   \ Bring back the filename, if one was provided
   r> if 2r> 1 else 0 then
 ;
 
 : get_arguments ( -- addrN lenN ... addr1 len1 N )
   0
   begin
     \ Get next word on the command line
     parse-word
   ?dup while
     queue_argv
   repeat
   drop ( empty string )
 ;
 
 : load_kernel_and_modules  ( args -- flag )
   set_tempoptions
   argc >r
   s" temp_options" getenv dup -1 <> if
     queue_argv
   else
     drop
   then
   load_xen
   ?dup 0= if ( success )
     r> if ( a path was passed )
       load_directory_or_file
     else
       standard_kernel_search
     then
     ?dup 0= if ['] load_modules catch then
   then
 ;
 
 only forth definitions
diff --git a/stand/i386/libi386/Makefile b/stand/i386/libi386/Makefile
index 779575bff071..3dea78f16929 100644
--- a/stand/i386/libi386/Makefile
+++ b/stand/i386/libi386/Makefile
@@ -1,47 +1,52 @@
 # $FreeBSD$
 
 .include <bsd.init.mk>
 
 LIB=			i386
 
 SRCS=	bio.c biosacpi.c biosdisk.c biosmem.c biospnp.c \
 	biospci.c biossmap.c bootinfo.c bootinfo32.c bootinfo64.c \
 	comconsole.c devicename.c elf32_freebsd.c \
 	elf64_freebsd.c multiboot.c multiboot_tramp.S relocater_tramp.S \
 	i386_copy.c i386_module.c nullconsole.c pxe.c pxetramp.S \
-	time.c vidconsole.c amd64_tramp.S spinconsole.c
+	time.c vidconsole.c vbe.c amd64_tramp.S spinconsole.c
 .PATH:	${ZFSSRC}
 SRCS+=  devicename_stubs.c
 CFLAGS+= -I${ZFSSRC}
 
 .PATH:	${SYSDIR}/teken
 SRCS+=	teken.c
 
 BOOT_COMCONSOLE_PORT?= 0x3f8
 CFLAGS+=	-DCOMPORT=${BOOT_COMCONSOLE_PORT}
 
 BOOT_COMCONSOLE_SPEED?= 9600
 CFLAGS+=	-DCOMSPEED=${BOOT_COMCONSOLE_SPEED}
 
 .ifdef(BOOT_BIOSDISK_DEBUG)
 # Make the disk code more talkative
 CFLAGS+= -DDISK_DEBUG
 .endif
 
 # terminal emulation
-CFLAGS.vidconsole.c+= -I${SRCTOP}/sys/teken
+.if ${BOOT_FRAMEBUFFER_MODE:Uno} == "yes"
+CFLAGS.vidconsole.c+= -DFRAMEBUFFER_MODE
+.endif
+CFLAGS.vidconsole.c+= -I${SRCTOP}/sys/teken -I${SRCTOP}/contrib/pnglite
 CFLAGS.teken.c+= -I${SRCTOP}/sys/teken
+CFLAGS.bootinfo.c+= -I${SRCTOP}/sys/teken -I${SRCTOP}/contrib/pnglite
+CFLAGS.vbe.c+= -I${SRCTOP}/sys/teken -I${SRCTOP}/contrib/pnglite
 
 # XXX: make alloca() useable
 CFLAGS+= -Dalloca=__builtin_alloca
 
 CFLAGS+=	-I${BOOTSRC}/ficl -I${BOOTSRC}/ficl/i386 \
 		-I${LDRSRC} -I${BOOTSRC}/i386/common \
 		-I${SYSDIR}/cddl/boot/zfs \
 		-I${SYSDIR}/cddl/contrib/opensolaris/uts/common \
 		-I${SYSDIR}/contrib/dev/acpica/include
 
 # Handle FreeBSD specific %b and %D printf format specifiers
 CFLAGS+= ${FORMAT_EXTENSIONS}
 
 .include <bsd.lib.mk>
diff --git a/stand/i386/libi386/bootinfo.c b/stand/i386/libi386/bootinfo.c
index 41901e5f928f..71e07cfb9702 100644
--- a/stand/i386/libi386/bootinfo.c
+++ b/stand/i386/libi386/bootinfo.c
@@ -1,112 +1,123 @@
 /*-
  * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
 
 #include <sys/cdefs.h>
 __FBSDID("$FreeBSD$");
 
 #include <stand.h>
 #include <sys/param.h>
 #include <sys/reboot.h>
 #include <sys/boot.h>
 #include <sys/linker.h>
+#include <gfx_fb.h>
 #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)
 {
     char	*curpos, *next, *string;
     int		howto;
     int		vidconsole;
 
     howto = boot_parse_cmdline(kargs);
     howto |= boot_env_to_howto();
 
     /* Enable selected consoles */
     string = next = strdup(getenv("console"));
     vidconsole = 0;
     while (next != NULL) {
 	curpos = strsep(&next, " ,");
 	if (*curpos == '\0')
 		continue;
 	if (!strcmp(curpos, "vidconsole"))
 	    vidconsole = 1;
 	else if (!strcmp(curpos, "comconsole"))
 	    howto |= RB_SERIAL;
 	else if (!strcmp(curpos, "nullconsole"))
 	    howto |= RB_MUTE;
     }
 
     if (vidconsole && (howto & RB_SERIAL))
 	howto |= RB_MULTIPLE;
 
     /*
      * XXX: Note that until the kernel is ready to respect multiple consoles
      * for the boot messages, the first named console is the primary console
      */
     if (!strcmp(string, "vidconsole"))
 	howto &= ~RB_SERIAL;
 
     free(string);
 
     return(howto);
 }
 
 void
 bi_setboothowto(int howto)
 {
 
     boot_howto_to_env(howto);
 }
 
 /*
  * Copy the environment into the load area starting at (addr).
  * Each variable is formatted as <name>=<value>, with a single nul
  * separating each variable, and a double nul terminating the environment.
  */
 vm_offset_t
 bi_copyenv(vm_offset_t addr)
 {
     struct env_var	*ep;
     
     /* traverse the environment */
     for (ep = environ; ep != NULL; ep = ep->ev_next) {
 	i386_copyin(ep->ev_name, addr, strlen(ep->ev_name));
 	addr += strlen(ep->ev_name);
 	i386_copyin("=", addr, 1);
 	addr++;
 	if (ep->ev_value != NULL) {
 	    i386_copyin(ep->ev_value, addr, strlen(ep->ev_value));
 	    addr += strlen(ep->ev_value);
 	}
 	i386_copyin("", addr, 1);
 	addr++;
     }
     i386_copyin("", addr, 1);
     addr++;
     return(addr);
 }
diff --git a/stand/i386/libi386/bootinfo32.c b/stand/i386/libi386/bootinfo32.c
index e6a92a2164d7..23b02693cf48 100644
--- a/stand/i386/libi386/bootinfo32.c
+++ b/stand/i386/libi386/bootinfo32.c
@@ -1,278 +1,281 @@
 /*-
  * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
 
 #include <sys/cdefs.h>
 __FBSDID("$FreeBSD$");
 
 #include <stand.h>
 #include <sys/param.h>
 #include <sys/reboot.h>
 #include <sys/linker.h>
 #include <machine/bootinfo.h>
 #include <machine/metadata.h>
 #include "bootstrap.h"
 #include "libi386.h"
 #include "btxv86.h"
 
 #ifdef LOADER_GELI_SUPPORT
 #include "geliboot.h"
 #endif
 
 static struct bootinfo  bi;
 
 /*
  * Copy module-related data into the load area, where it can be
  * used as a directory for loaded modules.
  *
  * Module data is presented in a self-describing format.  Each datum
  * is preceded by a 32-bit identifier and a 32-bit size field.
  *
  * Currently, the following data are saved:
  *
  * MOD_NAME	(variable)		module name (string)
  * MOD_TYPE	(variable)		module type (string)
  * MOD_ARGS	(variable)		module parameters (string)
  * MOD_ADDR	sizeof(vm_offset_t)	module load address
  * MOD_SIZE	sizeof(size_t)		module size
  * MOD_METADATA	(variable)		type-specific metadata
  */
 #define COPY32(v, a, c) {			\
     uint32_t	x = (v);			\
     if (c)					\
 	i386_copyin(&x, a, sizeof(x));		\
     a += sizeof(x);				\
 }
 
 #define MOD_STR(t, a, s, c) {			\
     COPY32(t, a, c);				\
     COPY32(strlen(s) + 1, a, c);		\
     if (c)					\
 	i386_copyin(s, a, strlen(s) + 1);	\
     a += roundup(strlen(s) + 1, sizeof(u_long));\
 }
 
 #define MOD_NAME(a, s, c)	MOD_STR(MODINFO_NAME, a, s, c)
 #define MOD_TYPE(a, s, c)	MOD_STR(MODINFO_TYPE, a, s, c)
 #define MOD_ARGS(a, s, c)	MOD_STR(MODINFO_ARGS, a, s, c)
 
 #define MOD_VAR(t, a, s, c) {			\
     COPY32(t, a, c);				\
     COPY32(sizeof(s), a, c);			\
     if (c)					\
 	i386_copyin(&s, a, sizeof(s));		\
     a += roundup(sizeof(s), sizeof(u_long));	\
 }
 
 #define MOD_ADDR(a, s, c)	MOD_VAR(MODINFO_ADDR, a, s, c)
 #define MOD_SIZE(a, s, c)	MOD_VAR(MODINFO_SIZE, a, s, c)
 
 #define MOD_METADATA(a, mm, c) {		\
     COPY32(MODINFO_METADATA | mm->md_type, a, c); \
     COPY32(mm->md_size, a, c);			\
     if (c)					\
 	i386_copyin(mm->md_data, a, mm->md_size); \
     a += roundup(mm->md_size, sizeof(u_long));\
 }
 
 #define MOD_END(a, c) {				\
     COPY32(MODINFO_END, a, c);			\
     COPY32(0, a, c);				\
 }
 
 static vm_offset_t
 bi_copymodules32(vm_offset_t addr)
 {
     struct preloaded_file	*fp;
     struct file_metadata	*md;
     int				c;
 
     c = addr != 0;
     /* start with the first module on the list, should be the kernel */
     for (fp = file_findfile(NULL, NULL); fp != NULL; fp = fp->f_next) {
 
 	MOD_NAME(addr, fp->f_name, c);	/* this field must come first */
 	MOD_TYPE(addr, fp->f_type, c);
 	if (fp->f_args)
 	    MOD_ARGS(addr, fp->f_args, c);
 	MOD_ADDR(addr, fp->f_addr, c);
 	MOD_SIZE(addr, fp->f_size, c);
 	for (md = fp->f_metadata; md != NULL; md = md->md_next)
 	    if (!(md->md_type & MODINFOMD_NOCOPY))
 		MOD_METADATA(addr, md, c);
     }
     MOD_END(addr, c);
     return(addr);
 }
 
 /*
  * Load the information expected by an i386 kernel.
  *
  * - The 'boothowto' argument is constructed
  * - The 'bootdev' argument is constructed
  * - The 'bootinfo' struct is constructed, and copied into the kernel space.
  * - The kernel environment is copied into kernel space.
  * - Module metadata are formatted and placed in kernel space.
  */
 int
 bi_load32(char *args, int *howtop, int *bootdevp, vm_offset_t *bip, vm_offset_t *modulep, vm_offset_t *kernendp)
 {
     struct preloaded_file	*xp, *kfp;
     struct i386_devdesc		*rootdev;
     struct file_metadata	*md;
     vm_offset_t			addr;
     vm_offset_t			kernend;
     vm_offset_t			envp;
     vm_offset_t			size;
     vm_offset_t			ssym, esym;
     char			*rootdevname;
     int				bootdevnr, i, howto;
     char			*kernelname;
     const char			*kernelpath;
 
     howto = bi_getboothowto(args);
 
     /*
      * Allow the environment variable 'rootdev' to override the supplied device
      * This should perhaps go to MI code and/or have $rootdev tested/set by
      * MI code before launching the kernel.
      */
     rootdevname = getenv("rootdev");
     i386_getdev((void **)(&rootdev), rootdevname, NULL);
     if (rootdev == NULL) {		/* bad $rootdev/$currdev */
 	printf("can't determine root device\n");
 	return(EINVAL);
     }
 
     /* Try reading the /etc/fstab file to select the root device */
     getrootmount(i386_fmtdev((void *)rootdev));
 
     /* Do legacy rootdev guessing */
 
     /* XXX - use a default bootdev of 0.  Is this ok??? */
     bootdevnr = 0;
 
     switch(rootdev->dd.d_dev->dv_type) {
     case DEVT_CD:
     case DEVT_DISK:
 	/* pass in the BIOS device number of the current disk */
 	bi.bi_bios_dev = bd_unit2bios(rootdev);
 	bootdevnr = bd_getdev(rootdev);
 	break;
 
     case DEVT_NET:
     case DEVT_ZFS:
 	    break;
 
     default:
 	printf("WARNING - don't know how to boot from device type %d\n",
 	    rootdev->dd.d_dev->dv_type);
     }
     if (bootdevnr == -1) {
 	printf("root device %s invalid\n", i386_fmtdev(rootdev));
 	return (EINVAL);
     }
     free(rootdev);
 
     /* find the last module in the chain */
     addr = 0;
     for (xp = file_findfile(NULL, NULL); xp != NULL; xp = xp->f_next) {
 	if (addr < (xp->f_addr + xp->f_size))
 	    addr = xp->f_addr + xp->f_size;
     }
     /* pad to a page boundary */
     addr = roundup(addr, PAGE_SIZE);
 
+    addr = build_font_module(addr);
+
     /* copy our environment */
     envp = addr;
     addr = bi_copyenv(addr);
 
     /* pad to a page boundary */
     addr = roundup(addr, PAGE_SIZE);
 
     kfp = file_findfile(NULL, "elf kernel");
     if (kfp == NULL)
       kfp = file_findfile(NULL, "elf32 kernel");
     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_KERNEND, sizeof kernend, &kernend);
     bios_addsmapdata(kfp);
 #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;
     size = bi_copymodules32(0);
     kernend = roundup(addr + size, PAGE_SIZE);
     *kernendp = kernend;
 
     /* patch MODINFOMD_KERNEND */
     md = file_findmetadata(kfp, MODINFOMD_KERNEND);
     bcopy(&kernend, md->md_data, sizeof kernend);
 
     /* copy module list and metadata */
     (void)bi_copymodules32(addr);
 
     ssym = esym = 0;
     md = file_findmetadata(kfp, MODINFOMD_SSYM);
     if (md != NULL)
 	ssym = *((vm_offset_t *)&(md->md_data));
     md = file_findmetadata(kfp, MODINFOMD_ESYM);
     if (md != NULL)
 	esym = *((vm_offset_t *)&(md->md_data));
     if (ssym == 0 || esym == 0)
 	ssym = esym = 0;		/* sanity */
 
     /* legacy bootinfo structure */
     kernelname = getenv("kernelname");
     i386_getdev(NULL, kernelname, &kernelpath);
     bi.bi_version = BOOTINFO_VERSION;
     bi.bi_kernelname = 0;		/* XXX char * -> kernel name */
     bi.bi_nfs_diskless = 0;		/* struct nfs_diskless * */
     bi.bi_n_bios_used = 0;		/* XXX would have to hook biosdisk driver for these */
     for (i = 0; i < N_BIOS_GEOM; i++)
         bi.bi_bios_geom[i] = bd_getbigeom(i);
     bi.bi_size = sizeof(bi);
     bi.bi_memsizes_valid = 1;
     bi.bi_basemem = bios_basemem / 1024;
     bi.bi_extmem = bios_extmem / 1024;
     bi.bi_envp = envp;
     bi.bi_modulep = *modulep;
     bi.bi_kernend = kernend;
     bi.bi_kernelname = VTOP(kernelpath);
     bi.bi_symtab = ssym;       /* XXX this is only the primary kernel symtab */
     bi.bi_esymtab = esym;
 
     /* legacy boot arguments */
     *howtop = howto | RB_BOOTINFO;
     *bootdevp = bootdevnr;
     *bip = VTOP(&bi);
 
     return(0);
 }
diff --git a/stand/i386/libi386/bootinfo64.c b/stand/i386/libi386/bootinfo64.c
index 93723a6943e3..9f038f2a4f2b 100644
--- a/stand/i386/libi386/bootinfo64.c
+++ b/stand/i386/libi386/bootinfo64.c
@@ -1,271 +1,274 @@
 /*-
  * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
 
 #include <sys/cdefs.h>
 __FBSDID("$FreeBSD$");
 
 #include <stand.h>
 #include <sys/param.h>
 #include <sys/reboot.h>
 #include <sys/linker.h>
 #include <machine/bootinfo.h>
 #include <machine/cpufunc.h>
 #include <machine/metadata.h>
 #include <machine/psl.h>
 #include <machine/specialreg.h>
 #include "bootstrap.h"
 #include "libi386.h"
 #include "btxv86.h"
 
 #ifdef LOADER_GELI_SUPPORT
 #include "geliboot.h"
 #endif
 
 /*
  * Copy module-related data into the load area, where it can be
  * used as a directory for loaded modules.
  *
  * Module data is presented in a self-describing format.  Each datum
  * is preceded by a 32-bit identifier and a 32-bit size field.
  *
  * Currently, the following data are saved:
  *
  * MOD_NAME	(variable)		module name (string)
  * MOD_TYPE	(variable)		module type (string)
  * MOD_ARGS	(variable)		module parameters (string)
  * MOD_ADDR	sizeof(vm_offset_t)	module load address
  * MOD_SIZE	sizeof(size_t)		module size
  * MOD_METADATA	(variable)		type-specific metadata
  */
 #define COPY32(v, a, c) {			\
     uint32_t	x = (v);			\
     if (c)					\
 	i386_copyin(&x, a, sizeof(x));		\
     a += sizeof(x);				\
 }
 
 #define MOD_STR(t, a, s, c) {			\
     COPY32(t, a, c);				\
     COPY32(strlen(s) + 1, a, c);		\
     if (c)					\
 	i386_copyin(s, a, strlen(s) + 1);	\
     a += roundup(strlen(s) + 1, sizeof(uint64_t));\
 }
 
 #define MOD_NAME(a, s, c)	MOD_STR(MODINFO_NAME, a, s, c)
 #define MOD_TYPE(a, s, c)	MOD_STR(MODINFO_TYPE, a, s, c)
 #define MOD_ARGS(a, s, c)	MOD_STR(MODINFO_ARGS, a, s, c)
 
 #define MOD_VAR(t, a, s, c) {			\
     COPY32(t, a, c);				\
     COPY32(sizeof(s), a, c);			\
     if (c)					\
 	i386_copyin(&s, a, sizeof(s));		\
     a += roundup(sizeof(s), sizeof(uint64_t));	\
 }
 
 #define MOD_ADDR(a, s, c)	MOD_VAR(MODINFO_ADDR, a, s, c)
 #define MOD_SIZE(a, s, c)	MOD_VAR(MODINFO_SIZE, a, s, c)
 
 #define MOD_METADATA(a, mm, c) {		\
     COPY32(MODINFO_METADATA | mm->md_type, a, c); \
     COPY32(mm->md_size, a, c);			\
     if (c)					\
 	i386_copyin(mm->md_data, a, mm->md_size); \
     a += roundup(mm->md_size, sizeof(uint64_t));\
 }
 
 #define MOD_END(a, c) {				\
     COPY32(MODINFO_END, a, c);			\
     COPY32(0, a, c);				\
 }
 
 static vm_offset_t
 bi_copymodules64(vm_offset_t addr)
 {
     struct preloaded_file	*fp;
     struct file_metadata	*md;
     int				c;
     uint64_t			v;
 
     c = addr != 0;
     /* start with the first module on the list, should be the kernel */
     for (fp = file_findfile(NULL, NULL); fp != NULL; fp = fp->f_next) {
 
 	MOD_NAME(addr, fp->f_name, c);	/* this field must come first */
 	MOD_TYPE(addr, fp->f_type, c);
 	if (fp->f_args)
 	    MOD_ARGS(addr, fp->f_args, c);
 	v = fp->f_addr;
 	MOD_ADDR(addr, v, c);
 	v = fp->f_size;
 	MOD_SIZE(addr, v, c);
 	for (md = fp->f_metadata; md != NULL; md = md->md_next)
 	    if (!(md->md_type & MODINFOMD_NOCOPY))
 		MOD_METADATA(addr, md, c);
     }
     MOD_END(addr, c);
     return(addr);
 }
 
 /*
  * Check to see if this CPU supports long mode.
  */
 static int
 bi_checkcpu(void)
 {
     char *cpu_vendor;
     int vendor[3];
     int eflags;
     unsigned int regs[4];
 
     /* Check for presence of "cpuid". */
     eflags = read_eflags();
     write_eflags(eflags ^ PSL_ID);
     if (!((eflags ^ read_eflags()) & PSL_ID))
 	return (0);
 
     /* Fetch the vendor string. */
     do_cpuid(0, regs);
     vendor[0] = regs[1];
     vendor[1] = regs[3];
     vendor[2] = regs[2];
     cpu_vendor = (char *)vendor;
 
     /* Check for vendors that support AMD features. */
     if (strncmp(cpu_vendor, INTEL_VENDOR_ID, 12) != 0 &&
 	strncmp(cpu_vendor, AMD_VENDOR_ID, 12) != 0 &&
 	strncmp(cpu_vendor, HYGON_VENDOR_ID, 12) != 0 &&
 	strncmp(cpu_vendor, CENTAUR_VENDOR_ID, 12) != 0)
 	return (0);
 
     /* Has to support AMD features. */
     do_cpuid(0x80000000, regs);
     if (!(regs[0] >= 0x80000001))
 	return (0);
 
     /* Check for long mode. */
     do_cpuid(0x80000001, regs);
     return (regs[3] & AMDID_LM);
 }
 
 /*
  * Load the information expected by an amd64 kernel.
  *
  * - The 'boothowto' argument is constructed
  * - The 'bootdev' argument is constructed
  * - The 'bootinfo' struct is constructed, and copied into the kernel space.
  * - The kernel environment is copied into kernel space.
  * - Module metadata are formatted and placed in kernel space.
  */
 int
 bi_load64(char *args, vm_offset_t addr, vm_offset_t *modulep,
     vm_offset_t *kernendp, int add_smap)
 {
     struct preloaded_file	*xp, *kfp;
     struct i386_devdesc		*rootdev;
     struct file_metadata	*md;
     uint64_t			kernend;
     uint64_t			envp;
     uint64_t			module;
     vm_offset_t			size;
     char			*rootdevname;
     int				howto;
 
     if (!bi_checkcpu()) {
 	printf("CPU doesn't support long mode\n");
 	return (EINVAL);
     }
 
     howto = bi_getboothowto(args);
 
     /*
      * Allow the environment variable 'rootdev' to override the supplied device
      * This should perhaps go to MI code and/or have $rootdev tested/set by
      * MI code before launching the kernel.
      */
     rootdevname = getenv("rootdev");
     i386_getdev((void **)(&rootdev), rootdevname, NULL);
     if (rootdev == NULL) {		/* bad $rootdev/$currdev */
 	printf("can't determine root device\n");
 	return(EINVAL);
     }
 
     /* Try reading the /etc/fstab file to select the root device */
     getrootmount(i386_fmtdev((void *)rootdev));
 
     if (addr == 0) {
         /* find the last module in the chain */
         for (xp = file_findfile(NULL, NULL); xp != NULL; xp = xp->f_next) {
             if (addr < (xp->f_addr + xp->f_size))
                 addr = xp->f_addr + xp->f_size;
         }
     }
     /* pad to a page boundary */
     addr = roundup(addr, PAGE_SIZE);
 
+    addr = build_font_module(addr);
+
     /* place the metadata before anything */
     module = *modulep = addr;
 
     kfp = file_findfile(NULL, "elf kernel");
     if (kfp == NULL)
       kfp = file_findfile(NULL, "elf64 kernel");
     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_KERNEND, sizeof kernend, &kernend);
     file_addmetadata(kfp, MODINFOMD_MODULEP, sizeof module, &module);
     if (add_smap != 0)
         bios_addsmapdata(kfp);
 #ifdef LOADER_GELI_SUPPORT
     geli_export_key_metadata(kfp);
 #endif
+    bi_load_vbe_data(kfp);
 
     size = bi_copymodules64(0);
 
     /* copy our environment */
     envp = roundup(addr + size, PAGE_SIZE);
     addr = bi_copyenv(envp);
 
     /* set kernend */
     kernend = roundup(addr, PAGE_SIZE);
     *kernendp = kernend;
 
     /* patch MODINFOMD_KERNEND */
     md = file_findmetadata(kfp, MODINFOMD_KERNEND);
     bcopy(&kernend, md->md_data, sizeof kernend);
 
     /* patch MODINFOMD_ENVP */
     md = file_findmetadata(kfp, MODINFOMD_ENVP);
     bcopy(&envp, md->md_data, sizeof envp);
 
     /* copy module list and metadata */
     (void)bi_copymodules64(*modulep);
 
     return(0);
 }
diff --git a/stand/i386/libi386/libi386.h b/stand/i386/libi386/libi386.h
index d25df8fc44a5..dbd6565f8d1c 100644
--- a/stand/i386/libi386/libi386.h
+++ b/stand/i386/libi386/libi386.h
@@ -1,156 +1,157 @@
 /*-
  * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
  * $FreeBSD$
  */
 
 
 /*
  * i386 fully-qualified device descriptor.
  */
 struct i386_devdesc {
     struct devdesc	dd;		/* Must be first. */
     union 
     {
 	struct 
 	{
 	    int		slice;
 	    int		partition;
 	    off_t	offset;
 	} biosdisk;
 	struct
 	{
 	    uint64_t	pool_guid;
 	    uint64_t	root_guid;
 	} zfs;
     } d_kind;
 };
 
 /*
  * relocater trampoline support.
  */
 struct relocate_data {
 	uint32_t	src;
 	uint32_t	dest;
 	uint32_t	size;
 };
 
 extern void relocater(void);
 
 /*
  * The relocater_data[] is fixed size array allocated in relocater_tramp.S
  */
 extern struct relocate_data relocater_data[];
 extern uint32_t relocater_size;
 
 extern uint16_t relocator_ip;
 extern uint16_t relocator_cs;
 extern uint16_t relocator_ds;
 extern uint16_t relocator_es;
 extern uint16_t relocator_fs;
 extern uint16_t relocator_gs;
 extern uint16_t relocator_ss;
 extern uint16_t relocator_sp;
 extern uint32_t relocator_esi;
 extern uint32_t relocator_eax;
 extern uint32_t relocator_ebx;
 extern uint32_t relocator_edx;
 extern uint32_t relocator_ebp;
 extern uint16_t relocator_a20_enabled;
 
 int	i386_getdev(void **vdev, const char *devspec, const char **path);
 char	*i386_fmtdev(void *vdev);
 int	i386_setcurrdev(struct env_var *ev, int flags, const void *value);
 
 extern struct devdesc	currdev;	/* our current device */
 
 #define MAXDEV		31		/* maximum number of distinct devices */
 #define MAXBDDEV	MAXDEV
 
 #include <readin.h>
 
 /* exported devices XXX rename? */
 extern struct devsw bioscd;
 extern struct devsw biosfd;
 extern struct devsw bioshd;
 extern struct devsw pxedisk;
 extern struct fs_ops pxe_fsops;
 
 int	bc_add(int biosdev);		/* Register CD booted from. */
 uint32_t bd_getbigeom(int bunit);	/* return geometry in bootinfo format */
 int	bd_bios2unit(int biosdev);	/* xlate BIOS device -> biosdisk unit */
 int	bd_unit2bios(struct i386_devdesc *); /* xlate biosdisk -> BIOS device */
 int	bd_getdev(struct i386_devdesc *dev);	/* return dev_t for (dev) */
 
 ssize_t	i386_copyin(const void *src, vm_offset_t dest, const size_t len);
 ssize_t	i386_copyout(const vm_offset_t src, void *dest, const size_t len);
 ssize_t	i386_readin(readin_handle_t fd, vm_offset_t dest, const size_t len);
 
 struct preloaded_file;
 void	bios_addsmapdata(struct preloaded_file *);
 void	bios_getsmap(void);
 
 void	bios_getmem(void);
 extern uint32_t		bios_basemem;	/* base memory in bytes */
 extern uint32_t		bios_extmem;	/* extended memory in bytes */
 extern vm_offset_t	memtop;		/* last address of physical memory + 1 */
 extern vm_offset_t	memtop_copyin;	/* memtop less heap size for the cases */
 					/*  when heap is at the top of         */
 					/*  extended memory; for other cases   */
 					/*  just the same as memtop            */
 extern uint32_t		high_heap_size;	/* extended memory region available */
 extern vm_offset_t	high_heap_base;	/* for use as the heap */
 
 /* 16KB buffer space for real mode data transfers. */
 #define	BIO_BUFFER_SIZE 0x4000
 void *bio_alloc(size_t size);
 void bio_free(void *ptr, size_t size);
 
 /*
  * Values for width parameter to biospci_{read,write}_config
  */
 #define BIOSPCI_8BITS	0
 #define BIOSPCI_16BITS	1
 #define BIOSPCI_32BITS	2
 
 void	biospci_detect(void);
 int	biospci_find_devclass(uint32_t class, int index, uint32_t *locator);
 int	biospci_read_config(uint32_t locator, int offset, int width, uint32_t *val);
 uint32_t biospci_locator(int8_t bus, uint8_t device, uint8_t function);
 int	biospci_write_config(uint32_t locator, int offset, int width, uint32_t val);
 
 void	biosacpi_detect(void);
 
 int	i386_autoload(void);
 
+void	bi_load_vbe_data(struct preloaded_file *kfp);
 int	bi_getboothowto(char *kargs);
 void	bi_setboothowto(int howto);
 vm_offset_t	bi_copyenv(vm_offset_t addr);
 int	bi_load32(char *args, int *howtop, int *bootdevp, vm_offset_t *bip,
 	    vm_offset_t *modulep, vm_offset_t *kernend);
 int	bi_load64(char *args, vm_offset_t addr, vm_offset_t *modulep,
 	    vm_offset_t *kernend, int add_smap);
 
 void	pxe_enable(void *pxeinfo);
diff --git a/stand/i386/libi386/vbe.c b/stand/i386/libi386/vbe.c
new file mode 100644
index 000000000000..7681eb633b85
--- /dev/null
+++ b/stand/i386/libi386/vbe.c
@@ -0,0 +1,1226 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2009 Jared D. McNeill <jmcneill@invisible.ca>
+ * 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 <sys/cdefs.h>
+#include <stand.h>
+#include <sys/param.h>
+#include <machine/psl.h>
+#include <machine/cpufunc.h>
+#include <stdbool.h>
+#include <bootstrap.h>
+#include <btxv86.h>
+#include <gfx_fb.h>
+#include <dev/vt/hw/vga/vt_vga_reg.h>
+#include "libi386.h"
+#include "vbe.h"
+
+/*
+ * VESA BIOS Extensions routines
+ */
+
+static struct vbeinfoblock *vbe;
+static struct modeinfoblock *vbe_mode;
+/* The default VGA color palette format is 6 bits per primary color. */
+int palette_format = 6;
+
+#define	VESA_MODE_BASE	0x100
+#define	VESA_MODE_MAX	0x1ff
+#define	VESA_MODE_COUNT	(VESA_MODE_MAX - VESA_MODE_BASE + 1)
+
+/*
+ * palette array for 8-bit indexed colors. In this case, cmap does store
+ * index and pe8 does store actual RGB. This is needed because we may
+ * not be able to read palette data from hardware.
+ */
+struct paletteentry *pe8 = NULL;
+
+static struct named_resolution {
+	const char *name;
+	const char *alias;
+	unsigned int width;
+	unsigned int height;
+} resolutions[] = {
+	{
+		.name = "480p",
+		.width = 640,
+		.height = 480,
+	},
+	{
+		.name = "720p",
+		.width = 1280,
+		.height = 720,
+	},
+	{
+		.name = "1080p",
+		.width = 1920,
+		.height = 1080,
+	},
+	{
+		.name = "2160p",
+		.alias = "4k",
+		.width = 3840,
+		.height = 2160,
+	},
+	{
+		.name = "5k",
+		.width = 5120,
+		.height = 2880,
+	}
+};
+
+static bool
+vbe_resolution_compare(struct named_resolution *res, const char *cmp)
+{
+
+	if (strcasecmp(res->name, cmp) == 0)
+		return (true);
+	if (res->alias != NULL && strcasecmp(res->alias, cmp) == 0)
+		return (true);
+	return (false);
+}
+
+static void
+vbe_get_max_resolution(int *width, int *height)
+{
+	struct named_resolution *res;
+	char *maxres;
+	char *height_start, *width_start;
+	int idx;
+
+	*width = *height = 0;
+	maxres = getenv("vbe_max_resolution");
+	/* No max_resolution set? Bail out; choose highest resolution */
+	if (maxres == NULL)
+		return;
+	/* See if it matches one of our known resolutions */
+	for (idx = 0; idx < nitems(resolutions); ++idx) {
+		res = &resolutions[idx];
+		if (vbe_resolution_compare(res, maxres)) {
+			*width = res->width;
+			*height = res->height;
+			return;
+		}
+	}
+	/* Not a known resolution, try to parse it; make a copy we can modify */
+	maxres = strdup(maxres);
+	if (maxres == NULL)
+		return;
+	height_start = strchr(maxres, 'x');
+	if (height_start == NULL) {
+		free(maxres);
+		return;
+	}
+	width_start = maxres;
+	*height_start++ = 0;
+	/* Errors from this will effectively mean "no max" */
+	*width = (int)strtol(width_start, NULL, 0);
+	*height = (int)strtol(height_start, NULL, 0);
+	free(maxres);
+}
+
+int
+vga_get_reg(int reg, int index)
+{
+	return (inb(reg + index));
+}
+
+int
+vga_get_atr(int reg, int i)
+{
+	int ret;
+
+	(void) inb(reg + VGA_GEN_INPUT_STAT_1);
+	outb(reg + VGA_AC_WRITE, i);
+	ret = inb(reg + VGA_AC_READ);
+
+	(void) inb(reg + VGA_GEN_INPUT_STAT_1);
+
+	return (ret);
+}
+
+void
+vga_set_atr(int reg, int i, int v)
+{
+	(void) inb(reg + VGA_GEN_INPUT_STAT_1);
+	outb(reg + VGA_AC_WRITE, i);
+	outb(reg + VGA_AC_WRITE, v);
+
+	(void) inb(reg + VGA_GEN_INPUT_STAT_1);
+}
+
+void
+vga_set_indexed(int reg, int indexreg, int datareg, uint8_t index, uint8_t val)
+{
+	outb(reg + indexreg, index);
+	outb(reg + datareg, val);
+}
+
+int
+vga_get_indexed(int reg, int indexreg, int datareg, uint8_t index)
+{
+	outb(reg + indexreg, index);
+	return (inb(reg + datareg));
+}
+
+int
+vga_get_crtc(int reg, int i)
+{
+	return (vga_get_indexed(reg, VGA_CRTC_ADDRESS, VGA_CRTC_DATA, i));
+}
+
+void
+vga_set_crtc(int reg, int i, int v)
+{
+	vga_set_indexed(reg, VGA_CRTC_ADDRESS, VGA_CRTC_DATA, i, v);
+}
+
+int
+vga_get_seq(int reg, int i)
+{
+	return (vga_get_indexed(reg, VGA_SEQ_ADDRESS, VGA_SEQ_DATA, i));
+}
+
+void
+vga_set_seq(int reg, int i, int v)
+{
+	vga_set_indexed(reg, VGA_SEQ_ADDRESS, VGA_SEQ_DATA, i, v);
+}
+
+int
+vga_get_grc(int reg, int i)
+{
+	return (vga_get_indexed(reg, VGA_GC_ADDRESS, VGA_GC_DATA, i));
+}
+
+void
+vga_set_grc(int reg, int i, int v)
+{
+	vga_set_indexed(reg, VGA_GC_ADDRESS, VGA_GC_DATA, i, v);
+}
+
+/* Actually assuming mode 3. */
+void
+bios_set_text_mode(int mode)
+{
+	int atr;
+
+	if (vbe->Capabilities & VBE_CAP_DAC8) {
+		int m;
+
+		/*
+		 * The mode change should reset the palette format to
+		 * 6 bits, but apparently some systems do fail with 8-bit
+		 * palette, so we switch to 6-bit here.
+		 */
+		m = 0x0600;
+		(void) biosvbe_palette_format(&m);
+		palette_format = m;
+	}
+	v86.ctl = V86_FLAGS;
+	v86.addr = 0x10;
+	v86.eax = mode;				/* set VGA text mode */
+	v86int();
+	atr = vga_get_atr(VGA_REG_BASE, VGA_AC_MODE_CONTROL);
+	atr &= ~VGA_AC_MC_BI;
+	atr &= ~VGA_AC_MC_ELG;
+	vga_set_atr(VGA_REG_BASE, VGA_AC_MODE_CONTROL, atr);
+
+	gfx_state.tg_mode = mode;
+	gfx_state.tg_fb_type = FB_TEXT;
+	gfx_state.tg_fb.fb_height = TEXT_ROWS;
+	gfx_state.tg_fb.fb_width = TEXT_COLS;
+
+	gfx_state.tg_fb.fb_mask_red = (1 << palette_format) - 1 << 16;
+	gfx_state.tg_fb.fb_mask_green = (1 << palette_format) - 1 << 8;
+	gfx_state.tg_fb.fb_mask_blue = (1 << palette_format) - 1 << 0;
+	gfx_state.tg_ctype = CT_INDEXED;
+	env_setenv("hw.vga.textmode", EV_VOLATILE | EV_NOHOOK, "1", NULL, NULL);
+}
+
+/* Function 00h - Return VBE Controller Information */
+static int
+biosvbe_info(struct vbeinfoblock *vbep)
+{
+	struct vbeinfoblock *rvbe;
+	int ret;
+
+	if (vbep == NULL)
+		return (VBE_FAILED);
+
+	rvbe = bio_alloc(sizeof(*rvbe));
+	if (rvbe == NULL)
+		return (VBE_FAILED);
+
+	/* Now check if we have vesa. */
+	memset(rvbe, 0, sizeof (*vbe));
+	memcpy(rvbe->VbeSignature, "VBE2", 4);
+
+	v86.ctl = V86_FLAGS;
+	v86.addr = 0x10;
+	v86.eax = 0x4f00;
+	v86.es = VTOPSEG(rvbe);
+	v86.edi = VTOPOFF(rvbe);
+	v86int();
+	ret = v86.eax & 0xffff;
+
+	if (ret != VBE_SUCCESS)
+		goto done;
+
+	if (memcmp(rvbe->VbeSignature, "VESA", 4) != 0) {
+		ret = VBE_NOTSUP;
+		goto done;
+	}
+	bcopy(rvbe, vbep, sizeof(*vbep));
+done:
+	bio_free(rvbe, sizeof(*rvbe));
+	return (ret);
+}
+
+/* Function 01h - Return VBE Mode Information */
+static int
+biosvbe_get_mode_info(int mode, struct modeinfoblock *mi)
+{
+	struct modeinfoblock *rmi;
+	int ret;
+
+	rmi = bio_alloc(sizeof(*rmi));
+	if (rmi == NULL)
+		return (VBE_FAILED);
+
+	v86.ctl = V86_FLAGS;
+	v86.addr = 0x10;
+	v86.eax = 0x4f01;
+	v86.ecx = mode;
+	v86.es = VTOPSEG(rmi);
+	v86.edi = VTOPOFF(rmi);
+	v86int();
+
+	ret = v86.eax & 0xffff;
+	if (ret != VBE_SUCCESS)
+		goto done;
+	bcopy(rmi, mi, sizeof(*rmi));
+done:
+	bio_free(rmi, sizeof(*rmi));
+	return (ret);
+}
+
+/* Function 02h - Set VBE Mode */
+static int
+biosvbe_set_mode(int mode, struct crtciinfoblock *ci)
+{
+	int rv;
+
+	if (vbe->Capabilities & VBE_CAP_DAC8) {
+		int m;
+
+		/*
+		 * The mode change should reset the palette format to
+		 * 6 bits, but apparently some systems do fail with 8-bit
+		 * palette, so we switch to 6-bit here.
+		 */
+		m = 0x0600;
+		if (biosvbe_palette_format(&m) == VBE_SUCCESS)
+			palette_format = m;
+	}
+	v86.ctl = V86_FLAGS;
+	v86.addr = 0x10;
+	v86.eax = 0x4f02;
+	v86.ebx = mode | 0x4000;	/* set linear FB bit */
+	v86.es = VTOPSEG(ci);
+	v86.edi = VTOPOFF(ci);
+	v86int();
+	rv = v86.eax & 0xffff;
+	if (vbe->Capabilities & VBE_CAP_DAC8) {
+		int m;
+
+		/* Switch to 8-bits per primary color. */
+		m = 0x0800;
+		if (biosvbe_palette_format(&m) == VBE_SUCCESS)
+			palette_format = m;
+	}
+	env_setenv("hw.vga.textmode", EV_VOLATILE | EV_NOHOOK, "0", NULL, NULL);
+	return (rv);
+}
+
+/* Function 03h - Get VBE Mode */
+static int
+biosvbe_get_mode(int *mode)
+{
+	v86.ctl = V86_FLAGS;
+	v86.addr = 0x10;
+	v86.eax = 0x4f03;
+	v86int();
+	*mode = v86.ebx & 0x3fff;	/* Bits 0-13 */
+	return (v86.eax & 0xffff);
+}
+
+/* Function 08h - Set/Get DAC Palette Format */
+int
+biosvbe_palette_format(int *format)
+{
+	v86.ctl = V86_FLAGS;
+	v86.addr = 0x10;
+	v86.eax = 0x4f08;
+	v86.ebx = *format;
+	v86int();
+	*format = (v86.ebx >> 8) & 0xff;
+	return (v86.eax & 0xffff);
+}
+
+/* Function 09h - Set/Get Palette Data */
+static int
+biosvbe_palette_data(int mode, int reg, struct paletteentry *pe)
+{
+	v86.ctl = V86_FLAGS;
+	v86.addr = 0x10;
+	v86.eax = 0x4f09;
+	v86.ebx = mode;
+	v86.edx = reg;
+	v86.ecx = 1;
+	v86.es = VTOPSEG(pe);
+	v86.edi = VTOPOFF(pe);
+	v86int();
+	return (v86.eax & 0xffff);
+}
+
+/*
+ * Function 15h BL=00h - Report VBE/DDC Capabilities
+ *
+ * int biosvbe_ddc_caps(void)
+ * return: VBE/DDC capabilities
+ */
+static int
+biosvbe_ddc_caps(void)
+{
+	v86.ctl = V86_FLAGS;
+	v86.addr = 0x10;
+	v86.eax = 0x4f15;	/* display identification extensions */
+	v86.ebx = 0;		/* report DDC capabilities */
+	v86.ecx = 0;		/* controller unit number (00h = primary) */
+	v86.es = 0;
+	v86.edi = 0;
+	v86int();
+	if (VBE_ERROR(v86.eax & 0xffff))
+		return (0);
+	return (v86.ebx & 0xffff);
+}
+
+/* Function 11h BL=01h - Flat Panel status */
+static int
+biosvbe_ddc_read_flat_panel_info(void *buf)
+{
+	v86.ctl = V86_FLAGS;
+	v86.addr = 0x10;
+	v86.eax = 0x4f11;	/* Flat Panel Interface extensions */
+	v86.ebx = 1;		/* Return Flat Panel Information */
+	v86.es = VTOPSEG(buf);
+	v86.edi = VTOPOFF(buf);
+	v86int();
+	return (v86.eax & 0xffff);
+}
+
+/* Function 15h BL=01h - Read EDID */
+static int
+biosvbe_ddc_read_edid(int blockno, void *buf)
+{
+	v86.ctl = V86_FLAGS;
+	v86.addr = 0x10;
+	v86.eax = 0x4f15;	/* display identification extensions */
+	v86.ebx = 1;		/* read EDID */
+	v86.ecx = 0;		/* controller unit number (00h = primary) */
+	v86.edx = blockno;
+	v86.es = VTOPSEG(buf);
+	v86.edi = VTOPOFF(buf);
+	v86int();
+	return (v86.eax & 0xffff);
+}
+
+static int
+vbe_mode_is_supported(struct modeinfoblock *mi)
+{
+	if ((mi->ModeAttributes & 0x01) == 0)
+		return (0);	/* mode not supported by hardware */
+	if ((mi->ModeAttributes & 0x08) == 0)
+		return (0);	/* linear fb not available */
+	if ((mi->ModeAttributes & 0x10) == 0)
+		return (0);	/* text mode */
+	if (mi->NumberOfPlanes != 1)
+		return (0);	/* planar mode not supported */
+	if (mi->MemoryModel != 0x04 /* Packed pixel */ &&
+	    mi->MemoryModel != 0x06 /* Direct Color */)
+		return (0);	/* unsupported pixel format */
+	return (1);
+}
+
+static bool
+vbe_check(void)
+{
+
+	if (vbe == NULL) {
+		printf("VBE not available\n");
+		return (false);
+	}
+	return (true);
+}
+
+static int
+mode_set(struct env_var *ev, int flags __unused, const void *value)
+{
+	int mode;
+
+	if (strcmp(ev->ev_name, "hw.vga.textmode") == 0) {
+		unsigned long v;
+		char *end;
+
+		if (value == NULL)
+			return (0);
+		/* VT(4) describes hw.vga.textmode values 0 or 1. */
+		errno = 0;
+		v = strtoul(value, &end, 0);
+		if (errno != 0 || *(char *)value == '\0' || *end != '\0' ||
+		    (v != 0 && v != 1))
+			return (EINVAL);
+		env_setenv("hw.vga.textmode", EV_VOLATILE | EV_NOHOOK,
+		    value, NULL, NULL);
+		if (v == 1) {
+			reset_font_flags();
+			bios_text_font(true);
+			bios_set_text_mode(VGA_TEXT_MODE);
+			(void) cons_update_mode(false);
+			return (0);
+		}
+	} else if (strcmp(ev->ev_name, "vbe_max_resolution") == 0) {
+		env_setenv("vbe_max_resolution", EV_VOLATILE | EV_NOHOOK,
+		    value, NULL, NULL);
+	} else {
+		return (EINVAL);
+	}
+
+	mode = vbe_default_mode();
+	if (gfx_state.tg_mode != mode) {
+		reset_font_flags();
+		bios_text_font(false);
+		vbe_set_mode(mode);
+		cons_update_mode(true);
+	}
+	return (0);
+}
+
+void
+vbe_init(void)
+{
+	/* First set FB for text mode. */
+	gfx_state.tg_fb_type = FB_TEXT;
+	gfx_state.tg_fb.fb_height = TEXT_ROWS;
+	gfx_state.tg_fb.fb_width = TEXT_COLS;
+	gfx_state.tg_ctype = CT_INDEXED;
+	gfx_state.tg_mode = 3;
+
+	if (vbe == NULL)
+		vbe = malloc(sizeof(*vbe));
+
+	if (vbe_mode == NULL) {
+		vbe_mode = malloc(sizeof(*vbe_mode));
+		if (vbe_mode == NULL) {
+			free(vbe);
+			vbe = NULL;
+		}
+	}
+
+	if (biosvbe_info(vbe) != VBE_SUCCESS) {
+		free(vbe);
+		vbe = NULL;
+		free(vbe_mode);
+		vbe_mode = NULL;
+	}
+
+	env_setenv("hw.vga.textmode", EV_VOLATILE, "1", mode_set,
+	    env_nounset);
+	env_setenv("vbe_max_resolution", EV_VOLATILE, NULL, mode_set,
+	    env_nounset);
+	/* vbe_set_mode() will set up the rest. */
+}
+
+bool
+vbe_available(void)
+{
+	return (gfx_state.tg_fb_type == FB_VBE);
+}
+
+int
+vbe_set_palette(const struct paletteentry *entry, size_t slot)
+{
+	struct paletteentry pe;
+	int mode, ret;
+
+	if (!vbe_check() || (vbe->Capabilities & VBE_CAP_DAC8) == 0)
+		return (1);
+
+	if (gfx_state.tg_ctype != CT_INDEXED) {
+		return (1);
+	}
+
+	pe.Blue = entry->Blue;
+	pe.Green = entry->Green;
+	pe.Red = entry->Red;
+	pe.Reserved = entry->Reserved;
+
+	if (vbe->Capabilities & VBE_CAP_SNOW)
+		mode = 0x80;
+	else
+		mode = 0;
+
+	ret = biosvbe_palette_data(mode, slot, &pe);
+
+	return (ret == VBE_SUCCESS ? 0 : 1);
+}
+
+int
+vbe_get_mode(void)
+{
+	return (gfx_state.tg_mode);
+}
+
+int
+vbe_set_mode(int modenum)
+{
+	struct modeinfoblock mi;
+	int bpp, ret;
+
+	if (!vbe_check())
+		return (1);
+
+	ret = biosvbe_get_mode_info(modenum, &mi);
+	if (VBE_ERROR(ret)) {
+		printf("mode 0x%x invalid\n", modenum);
+		return (1);
+	}
+
+	if (!vbe_mode_is_supported(&mi)) {
+		printf("mode 0x%x not supported\n", modenum);
+		return (1);
+	}
+
+	/* calculate bytes per pixel */
+	switch (mi.BitsPerPixel) {
+	case 32:
+	case 24:
+	case 16:
+	case 15:
+	case 8:
+		break;
+	default:
+		printf("BitsPerPixel %d is not supported\n", mi.BitsPerPixel);
+		return (1);
+	}
+
+	ret = biosvbe_set_mode(modenum, NULL);
+	if (VBE_ERROR(ret)) {
+		printf("mode 0x%x could not be set\n", modenum);
+		return (1);
+	}
+
+	gfx_state.tg_mode = modenum;
+	gfx_state.tg_fb_type = FB_VBE;
+	/* make sure we have current MI in vbestate */
+	memcpy(vbe_mode, &mi, sizeof (*vbe_mode));
+
+	gfx_state.tg_fb.fb_addr = (uint64_t)mi.PhysBasePtr & 0xffffffff;
+	gfx_state.tg_fb.fb_height = mi.YResolution;
+	gfx_state.tg_fb.fb_width = mi.XResolution;
+	gfx_state.tg_fb.fb_bpp = mi.BitsPerPixel;
+
+	/* Bytes per pixel */
+	bpp = roundup2(mi.BitsPerPixel, NBBY) / NBBY;
+
+	/* vbe_mode_is_supported() excludes the rest */
+	switch (mi.MemoryModel) {
+	case 0x4:
+		gfx_state.tg_ctype = CT_INDEXED;
+		break;
+	case 0x6:
+		gfx_state.tg_ctype = CT_RGB;
+		break;
+	}
+
+#define	COLOR_MASK(size, pos) (((1 << size) - 1) << pos)
+	if (gfx_state.tg_ctype == CT_INDEXED) {
+		gfx_state.tg_fb.fb_mask_red = COLOR_MASK(palette_format, 16);
+		gfx_state.tg_fb.fb_mask_green = COLOR_MASK(palette_format, 8);
+		gfx_state.tg_fb.fb_mask_blue = COLOR_MASK(palette_format, 0);
+	} else if (vbe->VbeVersion >= 0x300) {
+		gfx_state.tg_fb.fb_mask_red =
+		    COLOR_MASK(mi.LinRedMaskSize, mi.LinRedFieldPosition);
+		gfx_state.tg_fb.fb_mask_green =
+		    COLOR_MASK(mi.LinGreenMaskSize, mi.LinGreenFieldPosition);
+		gfx_state.tg_fb.fb_mask_blue =
+		    COLOR_MASK(mi.LinBlueMaskSize, mi.LinBlueFieldPosition);
+	} else {
+		gfx_state.tg_fb.fb_mask_red =
+		    COLOR_MASK(mi.RedMaskSize, mi.RedFieldPosition);
+		gfx_state.tg_fb.fb_mask_green =
+		    COLOR_MASK(mi.GreenMaskSize, mi.GreenFieldPosition);
+		gfx_state.tg_fb.fb_mask_blue =
+		    COLOR_MASK(mi.BlueMaskSize, mi.BlueFieldPosition);
+	}
+	gfx_state.tg_fb.fb_mask_reserved = ~(gfx_state.tg_fb.fb_mask_red |
+	    gfx_state.tg_fb.fb_mask_green |
+	    gfx_state.tg_fb.fb_mask_blue);
+
+	if (vbe->VbeVersion >= 0x300)
+		gfx_state.tg_fb.fb_stride = mi.LinBytesPerScanLine / bpp;
+	else
+		gfx_state.tg_fb.fb_stride = mi.BytesPerScanLine / bpp;
+
+	gfx_state.tg_fb.fb_size = mi.YResolution * gfx_state.tg_fb.fb_stride *
+	    bpp;
+
+	return (0);
+}
+
+static void *
+vbe_farptr(uint32_t farptr)
+{
+	return (PTOV((((farptr & 0xffff0000) >> 12) + (farptr & 0xffff))));
+}
+
+/*
+ * Verify existance of mode number or find mode by
+ * dimensions. If depth is not given, walk values 32, 24, 16, 8.
+ */
+static int
+vbe_find_mode_xydm(int x, int y, int depth, int m)
+{
+	struct modeinfoblock mi;
+	uint32_t farptr;
+	uint16_t mode;
+	int safety, i;
+
+	memset(vbe, 0, sizeof (*vbe));
+	if (biosvbe_info(vbe) != VBE_SUCCESS)
+		return (0);
+	if (vbe->VideoModePtr == 0)
+		return (0);
+
+	if (m != -1)
+		i = 8;
+	else if (depth == -1)
+		i = 32;
+	else
+		i = depth;
+
+	while (i > 0) {
+		farptr = vbe->VideoModePtr;
+		safety = 0;
+		while ((mode = *(uint16_t *)vbe_farptr(farptr)) != 0xffff) {
+			safety++;
+			farptr += 2;
+			if (safety == VESA_MODE_COUNT)
+				break;
+			if (biosvbe_get_mode_info(mode, &mi) != VBE_SUCCESS) {
+				continue;
+			}
+			/* we only care about linear modes here */
+			if (vbe_mode_is_supported(&mi) == 0)
+				continue;
+
+			if (m != -1) {
+				if (m == mode)
+					return (mode);
+				else
+					continue;
+			}
+
+			if (mi.XResolution == x &&
+			    mi.YResolution == y &&
+			    mi.BitsPerPixel == i)
+				return (mode);
+		}
+		if (depth != -1)
+			break;
+
+		i -= 8;
+	}
+
+	return (0);
+}
+
+static int
+vbe_find_mode(char *str)
+{
+	int x, y, depth;
+
+	if (!gfx_parse_mode_str(str, &x, &y, &depth))
+		return (0);
+
+	return (vbe_find_mode_xydm(x, y, depth, -1));
+}
+
+static void
+vbe_dump_mode(int modenum, struct modeinfoblock *mi)
+{
+	printf("0x%x=%dx%dx%d", modenum,
+	    mi->XResolution, mi->YResolution, mi->BitsPerPixel);
+}
+
+static bool
+vbe_get_edid(edid_res_list_t *res)
+{
+	struct vesa_edid_info *edid_info;
+	const uint8_t magic[] = EDID_MAGIC;
+	int ddc_caps;
+	bool ret = false;
+
+	ddc_caps = biosvbe_ddc_caps();
+	if (ddc_caps == 0) {
+		return (ret);
+	}
+
+	edid_info = bio_alloc(sizeof (*edid_info));
+	if (edid_info == NULL)
+		return (ret);
+	memset(edid_info, 0, sizeof (*edid_info));
+
+	if (VBE_ERROR(biosvbe_ddc_read_edid(0, edid_info)))
+		goto done;
+
+	if (memcmp(edid_info, magic, sizeof (magic)) != 0)
+		goto done;
+
+	/* Unknown EDID version. */
+	if (edid_info->header.version != 1)
+		goto done;
+
+	ret = gfx_get_edid_resolution(edid_info, res);
+done:
+	bio_free(edid_info, sizeof (*edid_info));
+	return (ret);
+}
+
+static bool
+vbe_get_flatpanel(uint32_t *pwidth, uint32_t *pheight)
+{
+	struct vesa_flat_panel_info *fp_info;
+	bool ret = false;
+
+	fp_info = bio_alloc(sizeof (*fp_info));
+	if (fp_info == NULL)
+		return (ret);
+	memset(fp_info, 0, sizeof (*fp_info));
+
+	if (VBE_ERROR(biosvbe_ddc_read_flat_panel_info(fp_info)))
+		goto done;
+
+	*pwidth = fp_info->HSize;
+	*pheight = fp_info->VSize;
+	ret = true;
+
+done:
+	bio_free(fp_info, sizeof (*fp_info));
+	return (ret);
+}
+
+static void
+vbe_print_memory(unsigned vmem)
+{
+	char unit = 'K';
+
+	vmem /= 1024;
+	if (vmem >= 10240000) {
+		vmem /= 1048576;
+		unit = 'G';
+	} else if (vmem >= 10000) {
+		vmem /= 1024;
+		unit = 'M';
+	}
+	printf("Total memory: %u%cB\n", vmem, unit);
+}
+
+static void
+vbe_print_vbe_info(struct vbeinfoblock *vbep)
+{
+	char *oemstring = "";
+	char *oemvendor = "", *oemproductname = "", *oemproductrev = "";
+
+	if (vbep->OemStringPtr != 0)
+		oemstring = vbe_farptr(vbep->OemStringPtr);
+
+	if (vbep->OemVendorNamePtr != 0)
+		oemvendor = vbe_farptr(vbep->OemVendorNamePtr);
+
+	if (vbep->OemProductNamePtr != 0)
+		oemproductname = vbe_farptr(vbep->OemProductNamePtr);
+
+	if (vbep->OemProductRevPtr != 0)
+		oemproductrev = vbe_farptr(vbep->OemProductRevPtr);
+
+	printf("VESA VBE Version %d.%d\n%s\n", vbep->VbeVersion >> 8,
+	    vbep->VbeVersion & 0xF, oemstring);
+
+	if (vbep->OemSoftwareRev != 0) {
+		printf("OEM Version %d.%d, %s (%s, %s)\n",
+		    vbep->OemSoftwareRev >> 8, vbep->OemSoftwareRev & 0xF,
+		    oemvendor, oemproductname, oemproductrev);
+	}
+	vbe_print_memory(vbep->TotalMemory << 16);
+	printf("Number of Image Pages: %d\n", vbe_mode->LinNumberOfImagePages);
+}
+
+/* List available modes, filter by depth. If depth is -1, list all. */
+void
+vbe_modelist(int depth)
+{
+	struct modeinfoblock mi;
+	uint32_t farptr;
+	uint16_t mode;
+	int nmodes = 0, safety = 0;
+	int ddc_caps;
+	uint32_t width, height;
+	bool edid = false;
+	edid_res_list_t res;
+	struct resolution *rp;
+
+	if (!vbe_check())
+		return;
+
+	ddc_caps = biosvbe_ddc_caps();
+	if (ddc_caps & 3) {
+		printf("DDC");
+		if (ddc_caps & 1)
+			printf(" [DDC1]");
+		if (ddc_caps & 2)
+			printf(" [DDC2]");
+
+		TAILQ_INIT(&res);
+		edid = vbe_get_edid(&res);
+		if (edid) {
+			printf(": EDID");
+			while ((rp = TAILQ_FIRST(&res)) != NULL) {
+				printf(" %dx%d", rp->width, rp->height);
+				TAILQ_REMOVE(&res, rp, next);
+				free(rp);
+			}
+			printf("\n");
+		} else {
+			printf(": no EDID information\n");
+		}
+	}
+	if (!edid)
+		if (vbe_get_flatpanel(&width, &height))
+			printf(": Panel %dx%d\n", width, height);
+
+	memset(vbe, 0, sizeof (*vbe));
+	memcpy(vbe->VbeSignature, "VBE2", 4);
+	if (biosvbe_info(vbe) != VBE_SUCCESS)
+		goto done;
+	if (memcmp(vbe->VbeSignature, "VESA", 4) != 0)
+		goto done;
+
+	vbe_print_vbe_info(vbe);
+	printf("Modes: ");
+
+	farptr = vbe->VideoModePtr;
+	if (farptr == 0)
+		goto done;
+
+	while ((mode = *(uint16_t *)vbe_farptr(farptr)) != 0xffff) {
+		safety++;
+		farptr += 2;
+		if (safety == VESA_MODE_COUNT) {
+			printf("[?] ");
+			break;
+		}
+		if (biosvbe_get_mode_info(mode, &mi) != VBE_SUCCESS)
+			continue;
+		/* we only care about linear modes here */
+		if (vbe_mode_is_supported(&mi) == 0)
+			continue;
+
+		/* we found some mode so reset safety counter */
+		safety = 0;
+
+		/* apply requested filter */
+		if (depth != -1 && mi.BitsPerPixel != depth)
+			continue;
+
+		if (nmodes % 4 == 0)
+			printf("\n");
+		else
+			printf("  ");
+
+		vbe_dump_mode(mode, &mi);
+		nmodes++;
+	}
+
+done:
+	if (nmodes == 0)
+		printf("none found");
+	printf("\n");
+}
+
+static void
+vbe_print_mode(bool verbose __unused)
+{
+	int nc, mode, i, rc;
+
+	nc = NCOLORS;
+
+	memset(vbe, 0, sizeof (*vbe));
+	if (biosvbe_info(vbe) != VBE_SUCCESS)
+		return;
+
+	vbe_print_vbe_info(vbe);
+
+	if (biosvbe_get_mode(&mode) != VBE_SUCCESS) {
+		printf("Error getting current VBE mode\n");
+		return;
+	}
+
+	if (biosvbe_get_mode_info(mode, vbe_mode) != VBE_SUCCESS ||
+	    vbe_mode_is_supported(vbe_mode) == 0) {
+		printf("VBE mode (0x%x) is not framebuffer mode\n", mode);
+		return;
+	}
+
+	printf("\nCurrent VBE mode: ");
+	vbe_dump_mode(mode, vbe_mode);
+	printf("\n");
+
+	printf("%ux%ux%u, stride=%u\n",
+	    gfx_state.tg_fb.fb_width,
+	    gfx_state.tg_fb.fb_height,
+	    gfx_state.tg_fb.fb_bpp,
+	    gfx_state.tg_fb.fb_stride *
+	    (roundup2(gfx_state.tg_fb.fb_bpp, NBBY) / NBBY));
+	printf("    frame buffer: address=%jx, size=%jx\n",
+	    (uintmax_t)gfx_state.tg_fb.fb_addr,
+	    (uintmax_t)gfx_state.tg_fb.fb_size);
+
+	if (vbe_mode->MemoryModel == 0x6) {
+		printf("    color mask: R=%08x, G=%08x, B=%08x\n",
+		    gfx_state.tg_fb.fb_mask_red,
+		    gfx_state.tg_fb.fb_mask_green,
+		    gfx_state.tg_fb.fb_mask_blue);
+		pager_open();
+		for (i = 0; i < nc; i++) {
+			printf("%d: R=%02x, G=%02x, B=%02x %08x", i,
+			    (cmap[i] & gfx_state.tg_fb.fb_mask_red) >>
+			    ffs(gfx_state.tg_fb.fb_mask_red) - 1,
+			    (cmap[i] & gfx_state.tg_fb.fb_mask_green) >>
+			    ffs(gfx_state.tg_fb.fb_mask_green) - 1,
+			    (cmap[i] & gfx_state.tg_fb.fb_mask_blue) >>
+			    ffs(gfx_state.tg_fb.fb_mask_blue) - 1, cmap[i]);
+			if (pager_output("\n") != 0)
+				break;
+		}
+		pager_close();
+		return;
+	}
+
+	mode = 1;	/* get DAC palette width */
+	rc = biosvbe_palette_format(&mode);
+	if (rc != VBE_SUCCESS)
+		return;
+
+	printf("    palette format: %x bits per primary\n", mode);
+	if (pe8 == NULL)
+		return;
+
+	pager_open();
+	for (i = 0; i < nc; i++) {
+		printf("%d: R=%02x, G=%02x, B=%02x", i,
+		    pe8[i].Red, pe8[i].Green, pe8[i].Blue);
+		if (pager_output("\n") != 0)
+			break;
+	}
+	pager_close();
+}
+
+/*
+ * Try EDID preferred mode, if EDID or the suggested mode is not available,
+ * then try flat panel information.
+ * Fall back to VBE_DEFAULT_MODE.
+ */
+int
+vbe_default_mode(void)
+{
+	edid_res_list_t res;
+	struct resolution *rp;
+	int modenum;
+	uint32_t width, height;
+
+	modenum = 0;
+	vbe_get_max_resolution(&width, &height);
+	if (width != 0 && height != 0)
+		modenum = vbe_find_mode_xydm(width, height, -1, -1);
+
+	TAILQ_INIT(&res);
+	if (vbe_get_edid(&res)) {
+		while ((rp = TAILQ_FIRST(&res)) != NULL) {
+			if (modenum == 0) {
+				modenum = vbe_find_mode_xydm(
+				    rp->width, rp->height, -1, -1);
+			}
+			TAILQ_REMOVE(&res, rp, next);
+			free(rp);
+		}
+	}
+
+	if (modenum == 0 &&
+	    vbe_get_flatpanel(&width, &height)) {
+		modenum = vbe_find_mode_xydm(width, height, -1, -1);
+	}
+
+	/* Still no mode? Fall back to default. */
+	if (modenum == 0)
+		modenum = vbe_find_mode(VBE_DEFAULT_MODE);
+	return (modenum);
+}
+
+COMMAND_SET(vbe, "vbe", "vesa framebuffer mode management", command_vesa);
+
+int
+command_vesa(int argc, char *argv[])
+{
+	char *arg, *cp;
+	int modenum = -1, n;
+
+	if (!vbe_check())
+		return (CMD_OK);
+
+	if (argc < 2)
+		goto usage;
+
+	if (strcmp(argv[1], "list") == 0) {
+		n = -1;
+		if (argc != 2 && argc != 3)
+			goto usage;
+
+		if (argc == 3) {
+			arg = argv[2];
+			errno = 0;
+			n = strtoul(arg, &cp, 0);
+			if (errno != 0 || *arg == '\0' || cp[0] != '\0') {
+				snprintf(command_errbuf,
+				    sizeof (command_errbuf),
+				    "depth should be an integer");
+				return (CMD_ERROR);
+			}
+		}
+		vbe_modelist(n);
+		return (CMD_OK);
+	}
+
+	if (strcmp(argv[1], "get") == 0) {
+		bool verbose = false;
+
+		if (argc != 2) {
+			if (argc > 3 || strcmp(argv[2], "-v") != 0)
+				goto usage;
+			verbose = true;
+		}
+		vbe_print_mode(verbose);
+		return (CMD_OK);
+	}
+
+	if (strcmp(argv[1], "off") == 0) {
+		if (argc != 2)
+			goto usage;
+
+		if (gfx_state.tg_mode == VGA_TEXT_MODE)
+			return (CMD_OK);
+
+		reset_font_flags();
+		bios_text_font(true);
+		bios_set_text_mode(VGA_TEXT_MODE);
+		cons_update_mode(false);
+		return (CMD_OK);
+	}
+
+	if (strcmp(argv[1], "on") == 0) {
+		if (argc != 2)
+			goto usage;
+
+		modenum = vbe_default_mode();
+		if (modenum == 0) {
+			snprintf(command_errbuf, sizeof (command_errbuf),
+			    "%s: no suitable VBE mode number found", argv[0]);
+			return (CMD_ERROR);
+		}
+	} else if (strcmp(argv[1], "set") == 0) {
+		if (argc != 3)
+			goto usage;
+
+		if (strncmp(argv[2], "0x", 2) == 0) {
+			arg = argv[2];
+			errno = 0;
+			n = strtoul(arg, &cp, 0);
+			if (errno != 0 || *arg == '\0' || cp[0] != '\0') {
+				snprintf(command_errbuf,
+				    sizeof (command_errbuf),
+				    "mode should be an integer");
+				return (CMD_ERROR);
+			}
+			modenum = vbe_find_mode_xydm(0, 0, 0, n);
+		} else if (strchr(argv[2], 'x') != NULL) {
+			modenum = vbe_find_mode(argv[2]);
+		}
+	} else {
+		goto usage;
+	}
+
+	if (modenum == 0) {
+		snprintf(command_errbuf, sizeof (command_errbuf),
+		    "%s: mode %s not supported by firmware\n",
+		    argv[0], argv[2]);
+		return (CMD_ERROR);
+	}
+
+	if (modenum >= VESA_MODE_BASE) {
+		if (gfx_state.tg_mode != modenum) {
+			reset_font_flags();
+			bios_text_font(false);
+			vbe_set_mode(modenum);
+			cons_update_mode(true);
+		}
+		return (CMD_OK);
+	} else {
+		snprintf(command_errbuf, sizeof (command_errbuf),
+		    "%s: mode %s is not framebuffer mode\n", argv[0], argv[2]);
+		return (CMD_ERROR);
+	}
+
+usage:
+	snprintf(command_errbuf, sizeof (command_errbuf),
+	    "usage: %s on | off | get | list [depth] | "
+	    "set <display or VBE mode number>", argv[0]);
+	return (CMD_ERROR);
+}
diff --git a/stand/i386/libi386/vbe.h b/stand/i386/libi386/vbe.h
new file mode 100644
index 000000000000..ff28b960df9c
--- /dev/null
+++ b/stand/i386/libi386/vbe.h
@@ -0,0 +1,163 @@
+/*-
+ * Copyright (c) 2009 Jared D. McNeill <jmcneill@invisible.ca>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Default mode for VESA frame buffer.
+ * This mode is selected when there is no EDID inormation and
+ * mode is not provided by user.
+ * To provide consistent look with UEFI GOP, we use 800x600 here,
+ * and if this mode is not available, we fall back to text mode and
+ * VESA disabled.
+ */
+
+#define VBE_DEFAULT_MODE	"800x600"
+
+struct vbeinfoblock {
+	char VbeSignature[4];
+	uint16_t VbeVersion;
+	uint32_t OemStringPtr;
+	uint32_t Capabilities;
+#define	VBE_CAP_DAC8	(1 << 0)	/* Can switch DAC */
+#define	VBE_CAP_NONVGA	(1 << 1)	/* Controller is not VGA comp. */
+#define	VBE_CAP_SNOW	(1 << 2)	/* Set data during Vertical Reterace */
+	uint32_t VideoModePtr;
+	uint16_t TotalMemory;
+	uint16_t OemSoftwareRev;
+	uint32_t OemVendorNamePtr, OemProductNamePtr, OemProductRevPtr;
+	/* data area, in total max 512 bytes for VBE 2.0 */
+	uint8_t Reserved[222];
+	uint8_t OemData[256];
+} __packed;
+
+struct modeinfoblock {
+	/* Mandatory information for all VBE revisions */
+	uint16_t ModeAttributes;
+	uint8_t WinAAttributes, WinBAttributes;
+	uint16_t WinGranularity, WinSize, WinASegment, WinBSegment;
+	uint32_t WinFuncPtr;
+	uint16_t BytesPerScanLine;
+	/* Mandatory information for VBE 1.2 and above */
+	uint16_t XResolution, YResolution;
+	uint8_t XCharSize, YCharSize, NumberOfPlanes, BitsPerPixel;
+	uint8_t NumberOfBanks, MemoryModel, BankSize, NumberOfImagePages;
+	uint8_t Reserved1;
+	/* Direct Color fields
+	   (required for direct/6 and YUV/7 memory models) */
+	uint8_t RedMaskSize, RedFieldPosition;
+	uint8_t GreenMaskSize, GreenFieldPosition;
+	uint8_t BlueMaskSize, BlueFieldPosition;
+	uint8_t RsvdMaskSize, RsvdFieldPosition;
+	uint8_t DirectColorModeInfo;
+	/* Mandatory information for VBE 2.0 and above */
+	uint32_t PhysBasePtr;
+	uint32_t OffScreenMemOffset;	/* reserved in VBE 3.0 and above */
+	uint16_t OffScreenMemSize;	/* reserved in VBE 3.0 and above */
+
+	/* Mandatory information for VBE 3.0 and above */
+	uint16_t LinBytesPerScanLine;
+	uint8_t BnkNumberOfImagePages;
+	uint8_t LinNumberOfImagePages;
+	uint8_t LinRedMaskSize, LinRedFieldPosition;
+	uint8_t LinGreenMaskSize, LinGreenFieldPosition;
+	uint8_t LinBlueMaskSize, LinBlueFieldPosition;
+	uint8_t LinRsvdMaskSize, LinRsvdFieldPosition;
+	uint32_t MaxPixelClock;
+	/* + 1 will fix the size to 256 bytes */
+	uint8_t Reserved4[189 + 1];
+} __packed;
+
+struct crtciinfoblock {
+	uint16_t HorizontalTotal;
+	uint16_t HorizontalSyncStart;
+	uint16_t HorizontalSyncEnd;
+	uint16_t VerticalTotal;
+	uint16_t VerticalSyncStart;
+	uint16_t VerticalSyncEnd;
+	uint8_t Flags;
+	uint32_t PixelClock;
+	uint16_t RefreshRate;
+	uint8_t Reserved[40];
+} __packed;
+
+struct paletteentry {
+	uint8_t Blue;
+	uint8_t Green;
+	uint8_t Red;
+	uint8_t Reserved;
+} __packed;
+
+struct flatpanelinfo
+{
+	uint16_t HorizontalSize;
+	uint16_t VerticalSize;
+	uint16_t PanelType;
+	uint8_t RedBPP;
+	uint8_t GreenBPP;
+	uint8_t BlueBPP;
+	uint8_t ReservedBPP;
+	uint32_t ReservedOffScreenMemSize;
+	uint32_t ReservedOffScreenMemPtr;
+
+	uint8_t Reserved[14];
+} __packed;
+
+#define	VBE_BASE_MODE		(0x100)		/* VBE 3.0 page 18 */
+#define	VBE_VALID_MODE(a)	((a) >= VBE_BASE_MODE)
+#define	VBE_ERROR(a)		(((a) & 0xFF) != 0x4F || ((a) & 0xFF00) != 0)
+#define	VBE_SUCCESS		(0x004F)
+#define	VBE_FAILED		(0x014F)
+#define	VBE_NOTSUP		(0x024F)
+#define	VBE_INVALID		(0x034F)
+
+#define	VGA_TEXT_MODE		(3)		/* 80x25 text mode */
+#define	TEXT_ROWS		(25)		/* VGATEXT rows */
+#define	TEXT_COLS		(80)		/* VGATEXT columns */
+
+extern struct paletteentry *pe8;
+extern int palette_format;
+
+int vga_get_reg(int, int);
+int vga_get_atr(int, int);
+void vga_set_atr(int, int, int);
+void vga_set_indexed(int, int, int, uint8_t, uint8_t);
+int vga_get_indexed(int, int, int, uint8_t);
+int vga_get_crtc(int, int);
+void vga_set_crtc(int, int, int);
+int vga_get_seq(int, int);
+void vga_set_seq(int, int, int);
+int vga_get_grc(int, int);
+void vga_set_grc(int, int, int);
+
+/* high-level VBE helpers, from vbe.c */
+void bios_set_text_mode(int);
+int biosvbe_palette_format(int *);
+void vbe_init(void);
+bool vbe_available(void);
+int vbe_default_mode(void);
+int vbe_set_mode(int);
+int vbe_get_mode(void);
+int vbe_set_palette(const struct paletteentry *, size_t);
+void vbe_modelist(int);
diff --git a/stand/i386/libi386/vidconsole.c b/stand/i386/libi386/vidconsole.c
index f965979d6565..b4829db1ea42 100644
--- a/stand/i386/libi386/vidconsole.c
+++ b/stand/i386/libi386/vidconsole.c
@@ -1,921 +1,1230 @@
 /*-
  * Copyright (c) 1998 Michael Smith (msmith@freebsd.org)
  * Copyright (c) 1997 Kazutaka YOKOTA (yokota@zodiac.mech.utsunomiya-u.ac.jp)
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
  * 	Id: probe_keyboard.c,v 1.13 1997/06/09 05:10:55 bde Exp
  */
 
 #include <sys/cdefs.h>
 __FBSDID("$FreeBSD$");
 
 #include <stand.h>
+#include <sys/param.h>
 #include <bootstrap.h>
 #include <btxv86.h>
-#include <machine/psl.h>
-#include <machine/cpufunc.h>
+#include <gfx_fb.h>
 #include <teken.h>
 #include <stdbool.h>
+#include "vbe.h"
 
 #include <dev/vt/hw/vga/vt_vga_reg.h>
 
 #include "libi386.h"
 
 #if KEYBOARD_PROBE
 static int	probe_keyboard(void);
 #endif
 static void	vidc_probe(struct console *cp);
 static int	vidc_init(int arg);
 static void	vidc_putchar(int c);
 static int	vidc_getchar(void);
 static int	vidc_ischar(void);
+static void	cons_draw_frame(teken_attr_t *);
 
 static int	vidc_started;
 static uint16_t	*vgatext;
 
 static tf_bell_t	vidc_cons_bell;
 static tf_cursor_t	vidc_text_cursor;
 static tf_putchar_t	vidc_text_putchar;
 static tf_fill_t	vidc_text_fill;
 static tf_copy_t	vidc_text_copy;
 static tf_param_t	vidc_text_param;
 static tf_respond_t	vidc_cons_respond;
  
 static teken_funcs_t tf = {
 	.tf_bell	= vidc_cons_bell,
 	.tf_cursor	= vidc_text_cursor,
 	.tf_putchar	= vidc_text_putchar,
 	.tf_fill	= vidc_text_fill,
 	.tf_copy	= vidc_text_copy,
 	.tf_param	= vidc_text_param,
 	.tf_respond	= vidc_cons_respond,
 };
 
-teken_t		teken;
-teken_pos_t	tp;
-
-struct text_pixel {
-	teken_char_t c;
-	teken_attr_t a;
-};
-
-static struct text_pixel *buffer;
-
-#define	NCOLORS	16
-
-/*
- * Between console's palette and VGA's one:
- *   - blue and red are swapped (1 <-> 4)
- *   - yellow and cyan are swapped (3 <-> 6)
- */
-static const int cons_to_vga_colors[NCOLORS] = {
-	0,  4,  2,  6,  1,  5,  3,  7,
-	8, 12, 10, 14,  9, 13, 11, 15
+static teken_funcs_t tfx = {
+	.tf_bell	= vidc_cons_bell,
+	.tf_cursor	= gfx_fb_cursor,
+	.tf_putchar	= gfx_fb_putchar,
+	.tf_fill	= gfx_fb_fill,
+	.tf_copy	= gfx_fb_copy,
+	.tf_param	= gfx_fb_param,
+	.tf_respond	= vidc_cons_respond,
 };
 
-#define	TEXT_COLS	80
-#define	TEXT_ROWS	25
 #define	KEYBUFSZ	10
 
 static uint8_t	keybuf[KEYBUFSZ];	/* keybuf for extended codes */
 
 struct console vidconsole = {
 	.c_name = "vidconsole",
 	.c_desc = "internal video/keyboard",
 	.c_flags = 0,
 	.c_probe = vidc_probe,
 	.c_init = vidc_init,
 	.c_out = vidc_putchar,
 	.c_in = vidc_getchar,
 	.c_ready = vidc_ischar
 };
 
-static int
-vga_get_reg(int reg, int index)
-{
-        return (inb(reg + index));
-}
-
-static int
-vga_get_atr(int reg, int i)
-{
-	int ret;
-
-	(void) inb(reg + VGA_GEN_INPUT_STAT_1);
-	outb(reg + VGA_AC_WRITE, i);
-	ret = inb(reg + VGA_AC_READ);
-
-	(void) inb(reg + VGA_GEN_INPUT_STAT_1);
-
-	return (ret);
-}
-
-static void
-vga_set_atr(int reg, int i, int v)
-{
-        (void) inb(reg + VGA_GEN_INPUT_STAT_1);
-        outb(reg + VGA_AC_WRITE, i);
-        outb(reg + VGA_AC_WRITE, v);
-
-        (void) inb(reg + VGA_GEN_INPUT_STAT_1);
-}
-
-static void
-vga_set_indexed(int reg, int indexreg, int datareg, uint8_t index, uint8_t val)
-{
-	outb(reg + indexreg, index);
-	outb(reg + datareg, val);
-}
-
-static int
-vga_get_indexed(int reg, int indexreg, int datareg, uint8_t index)
-{
-	outb(reg + indexreg, index);
-	return (inb(reg + datareg));
-}
-
-static int
-vga_get_crtc(int reg, int i)
-{
-	return (vga_get_indexed(reg, VGA_CRTC_ADDRESS, VGA_CRTC_DATA, i));
-}
-
-static void
-vga_set_crtc(int reg, int i, int v)
+/*
+ * This function is used to mark a rectangular image area so the scrolling
+ * will know we need to copy the data from there.
+ */
+void
+term_image_display(teken_gfx_t *state, const teken_rect_t *r)
 {
-	vga_set_indexed(reg, VGA_CRTC_ADDRESS, VGA_CRTC_DATA, i, v);
+	teken_pos_t p;
+	int idx;
+
+	for (p.tp_row = r->tr_begin.tp_row;
+	    p.tp_row < r->tr_end.tp_row; p.tp_row++) {
+                for (p.tp_col = r->tr_begin.tp_col;
+                    p.tp_col < r->tr_end.tp_col; p.tp_col++) {
+			idx = p.tp_col + p.tp_row * state->tg_tp.tp_col;
+			if (idx >= state->tg_tp.tp_col * state->tg_tp.tp_row)
+				return;
+			screen_buffer[idx].a.ta_format |= TF_IMAGE;
+		}
+	}
 }
 
 static void
 vidc_text_set_cursor(teken_unit_t row, teken_unit_t col, bool visible)
 {
         uint16_t addr;
         uint8_t msl, s, e;
 
         msl = vga_get_crtc(VGA_REG_BASE, VGA_CRTC_MAX_SCAN_LINE) & 0x1f;
         s = vga_get_crtc(VGA_REG_BASE, VGA_CRTC_CURSOR_START) & 0xC0;
         e = vga_get_crtc(VGA_REG_BASE, VGA_CRTC_CURSOR_END);
 
         if (visible == true) {
                 addr = row * TEXT_COLS + col;
                 vga_set_crtc(VGA_REG_BASE, VGA_CRTC_CURSOR_LOC_HIGH, addr >> 8);
                 vga_set_crtc(VGA_REG_BASE, VGA_CRTC_CURSOR_LOC_LOW,
 		    addr & 0xff);
                 e = msl;
         } else {
                 s |= (1<<5);
         }
         vga_set_crtc(VGA_REG_BASE, VGA_CRTC_CURSOR_START, s);
         vga_set_crtc(VGA_REG_BASE, VGA_CRTC_CURSOR_END, e);
 }
 
 static void
 vidc_text_get_cursor(teken_unit_t *row, teken_unit_t *col)
 {
 	uint16_t addr;
 
 	addr = (vga_get_crtc(VGA_REG_BASE, VGA_CRTC_CURSOR_LOC_HIGH) << 8) +
 	    vga_get_crtc(VGA_REG_BASE, VGA_CRTC_CURSOR_LOC_LOW);
 
 	*row = addr / TEXT_COLS;
 	*col = addr % TEXT_COLS;
 }
 
 /*
  * Not implemented.
  */
 static void 
 vidc_cons_bell(void *s __unused)
 {
 }
 
 static void
 vidc_text_cursor(void *s __unused, const teken_pos_t *p)
 {
 	teken_unit_t row, col;
 
 	if (p->tp_col == TEXT_COLS)
 		col = p->tp_col - 1;
 	else
 		col = p->tp_col;
 
 	if (p->tp_row == TEXT_ROWS)
 		row = p->tp_row - 1;
 	else
 		row = p->tp_row;
 
 	vidc_text_set_cursor(row, col, true);
 }
 
 /*
  * Binary searchable table for Unicode to CP437 conversion.
  */
 struct unicp437 {
 	uint16_t	unicode_base;
 	uint8_t		cp437_base;
 	uint8_t		length;
 };
 
 static const struct unicp437 cp437table[] = {
 	{ 0x0020, 0x20, 0x5e }, { 0x00a0, 0x20, 0x00 },
 	{ 0x00a1, 0xad, 0x00 }, { 0x00a2, 0x9b, 0x00 },
 	{ 0x00a3, 0x9c, 0x00 }, { 0x00a5, 0x9d, 0x00 },
 	{ 0x00a7, 0x15, 0x00 }, { 0x00aa, 0xa6, 0x00 },
 	{ 0x00ab, 0xae, 0x00 }, { 0x00ac, 0xaa, 0x00 },
 	{ 0x00b0, 0xf8, 0x00 }, { 0x00b1, 0xf1, 0x00 },
 	{ 0x00b2, 0xfd, 0x00 }, { 0x00b5, 0xe6, 0x00 },
 	{ 0x00b6, 0x14, 0x00 }, { 0x00b7, 0xfa, 0x00 },
 	{ 0x00ba, 0xa7, 0x00 }, { 0x00bb, 0xaf, 0x00 },
 	{ 0x00bc, 0xac, 0x00 }, { 0x00bd, 0xab, 0x00 },
 	{ 0x00bf, 0xa8, 0x00 }, { 0x00c4, 0x8e, 0x01 },
 	{ 0x00c6, 0x92, 0x00 }, { 0x00c7, 0x80, 0x00 },
 	{ 0x00c9, 0x90, 0x00 }, { 0x00d1, 0xa5, 0x00 },
 	{ 0x00d6, 0x99, 0x00 }, { 0x00dc, 0x9a, 0x00 },
 	{ 0x00df, 0xe1, 0x00 }, { 0x00e0, 0x85, 0x00 },
 	{ 0x00e1, 0xa0, 0x00 }, { 0x00e2, 0x83, 0x00 },
 	{ 0x00e4, 0x84, 0x00 }, { 0x00e5, 0x86, 0x00 },
 	{ 0x00e6, 0x91, 0x00 }, { 0x00e7, 0x87, 0x00 },
 	{ 0x00e8, 0x8a, 0x00 }, { 0x00e9, 0x82, 0x00 },
 	{ 0x00ea, 0x88, 0x01 }, { 0x00ec, 0x8d, 0x00 },
 	{ 0x00ed, 0xa1, 0x00 }, { 0x00ee, 0x8c, 0x00 },
 	{ 0x00ef, 0x8b, 0x00 }, { 0x00f0, 0xeb, 0x00 },
 	{ 0x00f1, 0xa4, 0x00 }, { 0x00f2, 0x95, 0x00 },
 	{ 0x00f3, 0xa2, 0x00 }, { 0x00f4, 0x93, 0x00 },
 	{ 0x00f6, 0x94, 0x00 }, { 0x00f7, 0xf6, 0x00 },
 	{ 0x00f8, 0xed, 0x00 }, { 0x00f9, 0x97, 0x00 },
 	{ 0x00fa, 0xa3, 0x00 }, { 0x00fb, 0x96, 0x00 },
 	{ 0x00fc, 0x81, 0x00 }, { 0x00ff, 0x98, 0x00 },
 	{ 0x0192, 0x9f, 0x00 }, { 0x0393, 0xe2, 0x00 },
 	{ 0x0398, 0xe9, 0x00 }, { 0x03a3, 0xe4, 0x00 },
 	{ 0x03a6, 0xe8, 0x00 }, { 0x03a9, 0xea, 0x00 },
 	{ 0x03b1, 0xe0, 0x01 }, { 0x03b4, 0xeb, 0x00 },
 	{ 0x03b5, 0xee, 0x00 }, { 0x03bc, 0xe6, 0x00 },
 	{ 0x03c0, 0xe3, 0x00 }, { 0x03c3, 0xe5, 0x00 },
 	{ 0x03c4, 0xe7, 0x00 }, { 0x03c6, 0xed, 0x00 },
 	{ 0x03d5, 0xed, 0x00 }, { 0x2010, 0x2d, 0x00 },
 	{ 0x2014, 0x2d, 0x00 }, { 0x2018, 0x60, 0x00 },
 	{ 0x2019, 0x27, 0x00 }, { 0x201c, 0x22, 0x00 },
 	{ 0x201d, 0x22, 0x00 }, { 0x2022, 0x07, 0x00 },
 	{ 0x203c, 0x13, 0x00 }, { 0x207f, 0xfc, 0x00 },
 	{ 0x20a7, 0x9e, 0x00 }, { 0x20ac, 0xee, 0x00 },
 	{ 0x2126, 0xea, 0x00 }, { 0x2190, 0x1b, 0x00 },
 	{ 0x2191, 0x18, 0x00 }, { 0x2192, 0x1a, 0x00 },
 	{ 0x2193, 0x19, 0x00 }, { 0x2194, 0x1d, 0x00 },
 	{ 0x2195, 0x12, 0x00 }, { 0x21a8, 0x17, 0x00 },
 	{ 0x2202, 0xeb, 0x00 }, { 0x2208, 0xee, 0x00 },
 	{ 0x2211, 0xe4, 0x00 }, { 0x2212, 0x2d, 0x00 },
 	{ 0x2219, 0xf9, 0x00 }, { 0x221a, 0xfb, 0x00 },
 	{ 0x221e, 0xec, 0x00 }, { 0x221f, 0x1c, 0x00 },
 	{ 0x2229, 0xef, 0x00 }, { 0x2248, 0xf7, 0x00 },
 	{ 0x2261, 0xf0, 0x00 }, { 0x2264, 0xf3, 0x00 },
 	{ 0x2265, 0xf2, 0x00 }, { 0x2302, 0x7f, 0x00 },
 	{ 0x2310, 0xa9, 0x00 }, { 0x2320, 0xf4, 0x00 },
 	{ 0x2321, 0xf5, 0x00 }, { 0x2500, 0xc4, 0x00 },
 	{ 0x2502, 0xb3, 0x00 }, { 0x250c, 0xda, 0x00 },
 	{ 0x2510, 0xbf, 0x00 }, { 0x2514, 0xc0, 0x00 },
 	{ 0x2518, 0xd9, 0x00 }, { 0x251c, 0xc3, 0x00 },
 	{ 0x2524, 0xb4, 0x00 }, { 0x252c, 0xc2, 0x00 },
 	{ 0x2534, 0xc1, 0x00 }, { 0x253c, 0xc5, 0x00 },
 	{ 0x2550, 0xcd, 0x00 }, { 0x2551, 0xba, 0x00 },
 	{ 0x2552, 0xd5, 0x00 }, { 0x2553, 0xd6, 0x00 },
 	{ 0x2554, 0xc9, 0x00 }, { 0x2555, 0xb8, 0x00 },
 	{ 0x2556, 0xb7, 0x00 }, { 0x2557, 0xbb, 0x00 },
 	{ 0x2558, 0xd4, 0x00 }, { 0x2559, 0xd3, 0x00 },
 	{ 0x255a, 0xc8, 0x00 }, { 0x255b, 0xbe, 0x00 },
 	{ 0x255c, 0xbd, 0x00 }, { 0x255d, 0xbc, 0x00 },
 	{ 0x255e, 0xc6, 0x01 }, { 0x2560, 0xcc, 0x00 },
 	{ 0x2561, 0xb5, 0x00 }, { 0x2562, 0xb6, 0x00 },
 	{ 0x2563, 0xb9, 0x00 }, { 0x2564, 0xd1, 0x01 },
 	{ 0x2566, 0xcb, 0x00 }, { 0x2567, 0xcf, 0x00 },
 	{ 0x2568, 0xd0, 0x00 }, { 0x2569, 0xca, 0x00 },
 	{ 0x256a, 0xd8, 0x00 }, { 0x256b, 0xd7, 0x00 },
 	{ 0x256c, 0xce, 0x00 }, { 0x2580, 0xdf, 0x00 },
 	{ 0x2584, 0xdc, 0x00 }, { 0x2588, 0xdb, 0x00 },
 	{ 0x258c, 0xdd, 0x00 }, { 0x2590, 0xde, 0x00 },
 	{ 0x2591, 0xb0, 0x02 }, { 0x25a0, 0xfe, 0x00 },
 	{ 0x25ac, 0x16, 0x00 }, { 0x25b2, 0x1e, 0x00 },
 	{ 0x25ba, 0x10, 0x00 }, { 0x25bc, 0x1f, 0x00 },
 	{ 0x25c4, 0x11, 0x00 }, { 0x25cb, 0x09, 0x00 },
 	{ 0x25d8, 0x08, 0x00 }, { 0x25d9, 0x0a, 0x00 },
 	{ 0x263a, 0x01, 0x01 }, { 0x263c, 0x0f, 0x00 },
 	{ 0x2640, 0x0c, 0x00 }, { 0x2642, 0x0b, 0x00 },
 	{ 0x2660, 0x06, 0x00 }, { 0x2663, 0x05, 0x00 },
 	{ 0x2665, 0x03, 0x01 }, { 0x266a, 0x0d, 0x00 },
 	{ 0x266c, 0x0e, 0x00 }
 };
 
 static uint8_t
 vga_get_cp437(teken_char_t c)
 {
 	int min, mid, max;
 
 	min = 0;
 	max = (sizeof(cp437table) / sizeof(struct unicp437)) - 1;
 
 	if (c < cp437table[0].unicode_base ||
 	    c > cp437table[max].unicode_base + cp437table[max].length)
 		return ('?');
 
 	while (max >= min) {
 		mid = (min + max) / 2;
 		if (c < cp437table[mid].unicode_base)
 			max = mid - 1;
 		else if (c > cp437table[mid].unicode_base +
 		    cp437table[mid].length)
 			min = mid + 1;
 		else
 			return (c - cp437table[mid].unicode_base +
 			    cp437table[mid].cp437_base);
 	}
 
 	return ('?');
 }
 
 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;
 	struct cgatext {
 		uint8_t ch;
 		uint8_t attr;
 	} *addr;
 
-	px = buffer + p->tp_col + p->tp_row * tp.tp_col;
+	idx = p->tp_col + p->tp_row * state->tg_tp.tp_col;
+	px = &screen_buffer[idx];
 	fg = teken_256to16(px->a.ta_fgcolor);
 	bg = teken_256to16(px->a.ta_bgcolor);
 	if (px->a.ta_format & TF_BOLD)
 		fg |= TC_LIGHT;
 	if (px->a.ta_format & TF_BLINK)
 		bg |= TC_LIGHT;
 
 	if (px->a.ta_format & TF_REVERSE) {
 		tmp = fg;
 		fg = bg;
 		bg = tmp;
 	}
 
-	attr = (cons_to_vga_colors[bg & 0xf] << 4) |
-	    cons_to_vga_colors[fg & 0xf];
-	addr = (struct cgatext *)vgatext + p->tp_col + p->tp_row * tp.tp_col;
-	addr->ch = vga_get_cp437(px->c);
-	addr->attr = attr;
+	attr = (cmap[bg & 0xf] << 4) | cmap[fg & 0xf];
+	addr = (struct cgatext *)vgatext;
+	addr[idx].ch = vga_get_cp437(px->c);
+	addr[idx].attr = attr;
 }
 
 static void
-vidc_text_putchar(void *s __unused, const teken_pos_t *p, teken_char_t c,
+vidc_text_putchar(void *s, const teken_pos_t *p, teken_char_t c,
     const teken_attr_t *a)
 {
-        int attr, idx;
+	teken_gfx_t *state = s;
+	int attr, idx;
 
-        idx = p->tp_col + p->tp_row * tp.tp_col;
-        buffer[idx].c = c;
-        buffer[idx].a = *a;
-        vidc_text_printchar(p);
+	idx = p->tp_col + p->tp_row * state->tg_tp.tp_col;
+	if (idx >= state->tg_tp.tp_col * state->tg_tp.tp_row)
+		return;
+
+	screen_buffer[idx].c = c;
+	screen_buffer[idx].a = *a;
+
+	vidc_text_printchar(state, p);
 }
 
 static void
-vidc_text_fill(void *s, const teken_rect_t *r, teken_char_t c,
+vidc_text_fill(void *arg, const teken_rect_t *r, teken_char_t c,
     const teken_attr_t *a)
 {
+	teken_gfx_t *state = arg;
 	teken_pos_t p;
 	teken_unit_t row, col;
 
 	vidc_text_get_cursor(&row, &col);
 	vidc_text_set_cursor(row, col, 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++)
-			vidc_text_putchar(s, &p, c, a);
+			vidc_text_putchar(state, &p, c, a);
 	vidc_text_set_cursor(row, col, true);
 }
 
-static bool
-vidc_same_pixel(struct text_pixel *px1, struct text_pixel *px2)
-{
-	if (px1->c != px2->c)
-		return (false);
-
-	if (px1->a.ta_format != px2->a.ta_format)
-		return (false);
-	if (px1->a.ta_fgcolor != px2->a.ta_fgcolor)
-		return (false);
-	if (px1->a.ta_bgcolor != px2->a.ta_bgcolor)
-		return (false);
-
-	return (true);
-}
-
 static void
-vidc_text_copy(void *ptr __unused, const teken_rect_t *r, const teken_pos_t *p)
+vidc_text_copy(void *ptr, const teken_rect_t *r, const teken_pos_t *p)
 {
+	teken_gfx_t *state = ptr;
 	int srow, drow;
 	int nrow, ncol, x, y; /* Has to be signed - >= 0 comparison */
 	teken_pos_t d, s;
 	teken_unit_t row, col;
 
 	/*
 	 * 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;
 
 	vidc_text_get_cursor(&row, &col);
 	vidc_text_set_cursor(row, col, false);
 	if (p->tp_row < r->tr_begin.tp_row) {
 		/* Copy from bottom to top. */
 		for (y = 0; y < nrow; y++) {
 			d.tp_row = p->tp_row + y;
 			s.tp_row = r->tr_begin.tp_row + y;
-			drow = d.tp_row * tp.tp_col;
-			srow = s.tp_row * tp.tp_col;
+			drow = d.tp_row * state->tg_tp.tp_col;
+			srow = s.tp_row * state->tg_tp.tp_col;
 			for (x = 0; x < ncol; x++) {
 				d.tp_col = p->tp_col + x;
 				s.tp_col = r->tr_begin.tp_col + x;
 
-				if (!vidc_same_pixel(
-				    &buffer[d.tp_col + drow],
-				    &buffer[s.tp_col + srow])) {
-					buffer[d.tp_col + drow] =
-					    buffer[s.tp_col + srow];
-					vidc_text_printchar(&d);
+				if (!is_same_pixel(
+				    &screen_buffer[d.tp_col + drow],
+				    &screen_buffer[s.tp_col + srow])) {
+					screen_buffer[d.tp_col + drow] =
+					    screen_buffer[s.tp_col + srow];
+					vidc_text_printchar(state, &d);
 				}
 			}
 		}
 	} 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;
+				drow = d.tp_row * state->tg_tp.tp_col;
+				srow = s.tp_row * state->tg_tp.tp_col;
 				for (x = 0; x < ncol; x++) {
 					d.tp_col = p->tp_col + x;
 					s.tp_col = r->tr_begin.tp_col + x;
 
-					if (!vidc_same_pixel(
-					    &buffer[d.tp_col + drow],
-					    &buffer[s.tp_col + srow])) {
-						buffer[d.tp_col + drow] =
-						    buffer[s.tp_col + srow];
-						vidc_text_printchar(&d);
+					if (!is_same_pixel(
+					    &screen_buffer[d.tp_col + drow],
+					    &screen_buffer[s.tp_col + srow])) {
+						screen_buffer[d.tp_col + drow] =
+						    screen_buffer[s.tp_col + srow];
+						vidc_text_printchar(state, &d);
 					}
 				}
 			}
 		} 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;
+				drow = d.tp_row * state->tg_tp.tp_col;
+				srow = s.tp_row * state->tg_tp.tp_col;
 				for (x = ncol - 1; x >= 0; x--) {
 					d.tp_col = p->tp_col + x;
 					s.tp_col = r->tr_begin.tp_col + x;
 
-					if (!vidc_same_pixel(
-					    &buffer[d.tp_col + drow],
-					    &buffer[s.tp_col + srow])) {
-						buffer[d.tp_col + drow] =
-						    buffer[s.tp_col + srow];
-						vidc_text_printchar(&d);
+					if (!is_same_pixel(
+					    &screen_buffer[d.tp_col + drow],
+					    &screen_buffer[s.tp_col + srow])) {
+						screen_buffer[d.tp_col + drow] =
+						    screen_buffer[s.tp_col + srow];
+						vidc_text_printchar(state, &d);
 					}
 				}
 			}
 		}
 	}
 	vidc_text_set_cursor(row, col, true);
 }
 
 static void
-vidc_text_param(void *s __unused, int cmd, unsigned int value)
+vidc_text_param(void *arg, int cmd, unsigned int value)
 {
+	teken_gfx_t *state = arg;
 	teken_unit_t row, col;
 
 	switch (cmd) {
 	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:
 		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 */
 		break;
 	}
 }
 
 /*
  * Not implemented.
  */
 static void
 vidc_cons_respond(void *s __unused, const void *buf __unused,
     size_t len __unused)
 {
 }
 
 static void
 vidc_probe(struct console *cp)
 {
     
     /* look for a keyboard */
 #if KEYBOARD_PROBE
     if (probe_keyboard())
 #endif
     {
 	
 	cp->c_flags |= C_PRESENTIN;
     }
 
     /* XXX for now, always assume we can do BIOS screen output */
     cp->c_flags |= C_PRESENTOUT;
 }
 
 static bool
 color_name_to_teken(const char *name, int *val)
 {
 	if (strcasecmp(name, "black") == 0) {
 		*val = TC_BLACK;
 		return (true);
 	}
 	if (strcasecmp(name, "red") == 0) {
 		*val = TC_RED;
 		return (true);
 	}
 	if (strcasecmp(name, "green") == 0) {
 		*val = TC_GREEN;
 		return (true);
 	}
 	if (strcasecmp(name, "brown") == 0) {
 		*val = TC_BROWN;
 		return (true);
 	}
 	if (strcasecmp(name, "blue") == 0) {
 		*val = TC_BLUE;
 		return (true);
 	}
 	if (strcasecmp(name, "magenta") == 0) {
 		*val = TC_MAGENTA;
 		return (true);
 	}
 	if (strcasecmp(name, "cyan") == 0) {
 		*val = TC_CYAN;
 		return (true);
 	}
 	if (strcasecmp(name, "white") == 0) {
 		*val = TC_WHITE;
 		return (true);
 	}
 	return (false);
 }
 
 static int
 vidc_set_colors(struct env_var *ev, int flags, const void *value)
 {
 	int val = 0;
 	char buf[2];
 	const void *evalue;
 	const teken_attr_t *ap;
 	teken_attr_t a;
 
 	if (value == NULL)
 		return (CMD_OK);
 
 	if (color_name_to_teken(value, &val)) {
 		snprintf(buf, sizeof (buf), "%d", val);
 		evalue = buf;
 	} else {
 		char *end;
 
 		errno = 0;
 		val = (int)strtol(value, &end, 0);
 		if (errno != 0 || *end != '\0') {
 			printf("Allowed values are either ansi color name or "
 			    "number from range [0-7].\n");
 			return (CMD_OK);
 		}
 		evalue = value;
 	}
 
-	ap = teken_get_defattr(&teken);
+	ap = teken_get_defattr(&gfx_state.tg_teken);
 	a = *ap;
 	if (strcmp(ev->ev_name, "teken.fg_color") == 0) {
 		/* is it already set? */
 		if (ap->ta_fgcolor == val)
 			return (CMD_OK);
 		a.ta_fgcolor = val;
 	}
 	if (strcmp(ev->ev_name, "teken.bg_color") == 0) {
 		/* is it already set? */
 		if (ap->ta_bgcolor == val)
 			return (CMD_OK);
 		a.ta_bgcolor = val;
 	}
 
 	/* Improve visibility */
 	if (a.ta_bgcolor == TC_WHITE)
 		a.ta_bgcolor |= TC_LIGHT;
 
+	teken_set_defattr(&gfx_state.tg_teken, &a);
+	cons_draw_frame(&a);
 	env_setenv(ev->ev_name, flags | EV_NOHOOK, evalue, NULL, NULL);
-	teken_set_defattr(&teken, &a);
+	teken_input(&gfx_state.tg_teken, "\e[2J", 4);
+
 	return (CMD_OK);
 }
 
+static int
+env_screen_nounset(struct env_var *ev __unused)
+{
+	if (gfx_state.tg_fb_type == FB_TEXT)
+		return (0);
+	return (EPERM);
+}
+
+static int
+vidc_load_palette(uint32_t *cmap)
+{
+	int i, roff, goff, boff, rc;
+
+	if (pe8 == NULL)
+		pe8 = calloc(sizeof(*pe8), NCMAP);
+	if (pe8 == NULL)
+		return (ENOMEM);
+
+	/* Generate VGA colors */
+	roff = ffs(gfx_state.tg_fb.fb_mask_red) - 1;
+	goff = ffs(gfx_state.tg_fb.fb_mask_green) - 1;
+	boff = ffs(gfx_state.tg_fb.fb_mask_blue) - 1;
+	rc = generate_cons_palette((uint32_t *)pe8, COLOR_FORMAT_RGB,
+	    gfx_state.tg_fb.fb_mask_red >> roff, roff,
+	    gfx_state.tg_fb.fb_mask_green >> goff, goff,
+	    gfx_state.tg_fb.fb_mask_blue >> boff, boff);
+
+	if (rc == 0) {
+		for (i = 0; i < NCMAP; i++) {
+			int idx;
+
+			if (i < NCOLORS)
+				idx = cons_to_vga_colors[i];
+			else
+				idx = i;
+
+			rc = vbe_set_palette(&pe8[i], idx);
+			if (rc != 0)
+				break;
+		}
+	}
+	return (rc);
+}
+
+static void
+cons_draw_frame(teken_attr_t *a)
+{
+	teken_attr_t attr = *a;
+	teken_color_t fg = a->ta_fgcolor;
+
+	attr.ta_fgcolor = attr.ta_bgcolor;
+	teken_set_defattr(&gfx_state.tg_teken, &attr);
+
+	gfx_fb_drawrect(0, 0, gfx_state.tg_fb.fb_width,
+	    gfx_state.tg_origin.tp_row, 1);
+	gfx_fb_drawrect(0,
+	    gfx_state.tg_fb.fb_height - gfx_state.tg_origin.tp_row - 1,
+	    gfx_state.tg_fb.fb_width, gfx_state.tg_fb.fb_height, 1);
+	gfx_fb_drawrect(0, gfx_state.tg_origin.tp_row,
+	    gfx_state.tg_origin.tp_col,
+	    gfx_state.tg_fb.fb_height - gfx_state.tg_origin.tp_row - 1, 1);
+	gfx_fb_drawrect(
+	    gfx_state.tg_fb.fb_width - gfx_state.tg_origin.tp_col - 1,
+	    gfx_state.tg_origin.tp_row, gfx_state.tg_fb.fb_width,
+	    gfx_state.tg_fb.fb_height, 1);
+
+	attr.ta_fgcolor = fg;
+	teken_set_defattr(&gfx_state.tg_teken, &attr);
+}
+
+/*
+ * Binary searchable table for CP437 to Unicode conversion.
+ */
+struct cp437uni {
+	uint8_t		cp437_base;
+	uint16_t	unicode_base;
+	uint8_t		length;
+};
+
+static const struct cp437uni cp437unitable[] = {
+	{   0, 0x0000, 0 }, {   1, 0x263A, 1 }, {   3, 0x2665, 1 },
+	{   5, 0x2663, 0 }, {   6, 0x2660, 0 }, {   7, 0x2022, 0 },
+	{   8, 0x25D8, 0 }, {   9, 0x25CB, 0 }, {  10, 0x25D9, 0 },
+	{  11, 0x2642, 0 }, {  12, 0x2640, 0 }, {  13, 0x266A, 1 },
+	{  15, 0x263C, 0 }, {  16, 0x25BA, 0 }, {  17, 0x25C4, 0 },
+	{  18, 0x2195, 0 }, {  19, 0x203C, 0 }, {  20, 0x00B6, 0 },
+	{  21, 0x00A7, 0 }, {  22, 0x25AC, 0 }, {  23, 0x21A8, 0 },
+	{  24, 0x2191, 0 }, {  25, 0x2193, 0 }, {  26, 0x2192, 0 },
+	{  27, 0x2190, 0 }, {  28, 0x221F, 0 }, {  29, 0x2194, 0 },
+	{  30, 0x25B2, 0 }, {  31, 0x25BC, 0 }, {  32, 0x0020, 0x5e },
+	{ 127, 0x2302, 0 }, { 128, 0x00C7, 0 }, { 129, 0x00FC, 0 },
+	{ 130, 0x00E9, 0 }, { 131, 0x00E2, 0 }, { 132, 0x00E4, 0 },
+	{ 133, 0x00E0, 0 }, { 134, 0x00E5, 0 }, { 135, 0x00E7, 0 },
+	{ 136, 0x00EA, 1 }, { 138, 0x00E8, 0 }, { 139, 0x00EF, 0 },
+	{ 140, 0x00EE, 0 }, { 141, 0x00EC, 0 }, { 142, 0x00C4, 1 },
+	{ 144, 0x00C9, 0 }, { 145, 0x00E6, 0 }, { 146, 0x00C6, 0 },
+	{ 147, 0x00F4, 0 }, { 148, 0x00F6, 0 }, { 149, 0x00F2, 0 },
+	{ 150, 0x00FB, 0 }, { 151, 0x00F9, 0 }, { 152, 0x00FF, 0 },
+	{ 153, 0x00D6, 0 }, { 154, 0x00DC, 0 }, { 155, 0x00A2, 1 },
+	{ 157, 0x00A5, 0 }, { 158, 0x20A7, 0 }, { 159, 0x0192, 0 },
+	{ 160, 0x00E1, 0 }, { 161, 0x00ED, 0 }, { 162, 0x00F3, 0 },
+	{ 163, 0x00FA, 0 }, { 164, 0x00F1, 0 }, { 165, 0x00D1, 0 },
+	{ 166, 0x00AA, 0 }, { 167, 0x00BA, 0 }, { 168, 0x00BF, 0 },
+	{ 169, 0x2310, 0 }, { 170, 0x00AC, 0 }, { 171, 0x00BD, 0 },
+	{ 172, 0x00BC, 0 }, { 173, 0x00A1, 0 }, { 174, 0x00AB, 0 },
+	{ 175, 0x00BB, 0 }, { 176, 0x2591, 2 }, { 179, 0x2502, 0 },
+	{ 180, 0x2524, 0 }, { 181, 0x2561, 1 }, { 183, 0x2556, 0 },
+	{ 184, 0x2555, 0 }, { 185, 0x2563, 0 }, { 186, 0x2551, 0 },
+	{ 187, 0x2557, 0 }, { 188, 0x255D, 0 }, { 189, 0x255C, 0 },
+	{ 190, 0x255B, 0 }, { 191, 0x2510, 0 }, { 192, 0x2514, 0 },
+	{ 193, 0x2534, 0 }, { 194, 0x252C, 0 }, { 195, 0x251C, 0 },
+	{ 196, 0x2500, 0 }, { 197, 0x253C, 0 }, { 198, 0x255E, 1 },
+	{ 200, 0x255A, 0 }, { 201, 0x2554, 0 }, { 202, 0x2569, 0 },
+	{ 203, 0x2566, 0 }, { 204, 0x2560, 0 }, { 205, 0x2550, 0 },
+	{ 206, 0x256C, 0 }, { 207, 0x2567, 1 }, { 209, 0x2564, 1 },
+	{ 211, 0x2559, 0 }, { 212, 0x2558, 0 }, { 213, 0x2552, 1 },
+	{ 215, 0x256B, 0 }, { 216, 0x256A, 0 }, { 217, 0x2518, 0 },
+	{ 218, 0x250C, 0 }, { 219, 0x2588, 0 }, { 220, 0x2584, 0 },
+	{ 221, 0x258C, 0 }, { 222, 0x2590, 0 }, { 223, 0x2580, 0 },
+	{ 224, 0x03B1, 0 }, { 225, 0x00DF, 0 }, { 226, 0x0393, 0 },
+	{ 227, 0x03C0, 0 }, { 228, 0x03A3, 0 }, { 229, 0x03C3, 0 },
+	{ 230, 0x00B5, 0 }, { 231, 0x03C4, 0 }, { 232, 0x03A6, 0 },
+	{ 233, 0x0398, 0 }, { 234, 0x03A9, 0 }, { 235, 0x03B4, 0 },
+	{ 236, 0x221E, 0 }, { 237, 0x03C6, 0 }, { 238, 0x03B5, 0 },
+	{ 239, 0x2229, 0 }, { 240, 0x2261, 0 }, { 241, 0x00B1, 0 },
+	{ 242, 0x2265, 0 }, { 243, 0x2264, 0 }, { 244, 0x2320, 1 },
+	{ 246, 0x00F7, 0 }, { 247, 0x2248, 0 }, { 248, 0x00B0, 0 },
+	{ 249, 0x2219, 0 }, { 250, 0x00B7, 0 }, { 251, 0x221A, 0 },
+	{ 252, 0x207F, 0 }, { 253, 0x00B2, 0 }, { 254, 0x25A0, 0 },
+	{ 255, 0x00A0, 0 }
+};
+
+static uint16_t
+vga_cp437_to_uni(uint8_t c)
+{
+	int min, mid, max;
+
+	min = 0;
+	max = (sizeof(cp437unitable) / sizeof(struct cp437uni)) - 1;
+
+	while (max >= min) {
+		mid = (min + max) / 2;
+		if (c < cp437unitable[mid].cp437_base)
+			max = mid - 1;
+		else if (c > cp437unitable[mid].cp437_base +
+		    cp437unitable[mid].length)
+			min = mid + 1;
+		else
+			return (c - cp437unitable[mid].cp437_base +
+			    cp437unitable[mid].unicode_base);
+	}
+
+	return ('?');
+}
+
+/*
+ * install font for text mode
+ */
+static void
+vidc_install_font(void)
+{
+	static uint8_t fsreg[8] = {0x0, 0x30, 0x5, 0x35, 0xa, 0x3a, 0xf, 0x3f};
+	const uint8_t *from;
+	uint8_t volatile *to;
+	uint16_t c;
+	int i, j, s;
+	int bpc, f_offset;
+	teken_attr_t a = { 0 };
+
+	if (gfx_state.tg_fb_type != FB_TEXT)
+		return;
+
+	/* Sync-reset the sequencer registers */
+	vga_set_seq(VGA_REG_BASE, 0x00, 0x01);
+	/*
+	 * enable write to plane2, since fonts
+	 * could only be loaded into plane2
+	 */
+	vga_set_seq(VGA_REG_BASE, 0x02, 0x04);
+	/*
+	 * sequentially access data in the bit map being
+	 * selected by MapMask register (index 0x02)
+	 */
+	vga_set_seq(VGA_REG_BASE, 0x04, 0x07);
+	/* Sync-reset ended, and allow the sequencer to operate */
+	vga_set_seq(VGA_REG_BASE, 0x00, 0x03);
+
+	/*
+	 * select plane 2 on Read Mode 0
+	 */
+	vga_set_grc(VGA_REG_BASE, 0x04, 0x02);
+	/*
+	 * system addresses sequentially access data, follow
+	 * Memory Mode register bit 2 in the sequencer
+	 */
+	vga_set_grc(VGA_REG_BASE, 0x05, 0x00);
+	/*
+	 * set range of host memory addresses decoded by VGA
+	 * hardware -- A0000h-BFFFFh (128K region)
+	 */
+	vga_set_grc(VGA_REG_BASE, 0x06, 0x00);
+
+	/*
+	 * This assumes 8x16 characters, which yield the traditional 80x25
+	 * screen.
+	 */
+	bpc = 16;
+	s = 0;	/* font slot, maybe should use tunable there. */
+	f_offset = s * 8 * 1024;
+	for (i = 0; i < 256; i++) {
+		c = vga_cp437_to_uni(i);
+		from = font_lookup(&gfx_state.tg_font, c, &a);
+		to = (unsigned char *)ptov(VGA_MEM_BASE) + f_offset +
+		    i * 0x20;
+		for (j = 0; j < bpc; j++)
+			*to++ = *from++;
+	}
+
+	/* Sync-reset the sequencer registers */
+	vga_set_seq(VGA_REG_BASE, 0x00, 0x01);
+	/* enable write to plane 0 and 1 */
+	vga_set_seq(VGA_REG_BASE, 0x02, 0x03);
+	/*
+	 * enable character map selection
+	 * and odd/even addressing
+	 */
+	vga_set_seq(VGA_REG_BASE, 0x04, 0x03);
+	/*
+	 * select font map
+	 */
+	vga_set_seq(VGA_REG_BASE, 0x03, fsreg[s]);
+	/* Sync-reset ended, and allow the sequencer to operate */
+	vga_set_seq(VGA_REG_BASE, 0x00, 0x03);
+
+	/* restore graphic registers */
+
+	/* select plane 0 */
+	vga_set_grc(VGA_REG_BASE, 0x04, 0x00);
+	/* enable odd/even addressing mode */
+	vga_set_grc(VGA_REG_BASE, 0x05, 0x10);
+	/*
+	 * range of host memory addresses decoded by VGA
+	 * hardware -- B8000h-BFFFFh (32K region)
+	 */
+	vga_set_grc(VGA_REG_BASE, 0x06, 0x0e);
+	/* enable all color plane */
+	vga_set_atr(VGA_REG_BASE, 0x12, 0x0f);
+}
+
+bool
+cons_update_mode(bool use_gfx_mode)
+{
+	const teken_attr_t *a;
+	teken_attr_t attr;
+	char env[10], *ptr;
+	int format, roff, goff, boff;
+
+	gfx_state.tg_tp.tp_row = TEXT_ROWS;
+	gfx_state.tg_tp.tp_col = TEXT_COLS;
+
+	if (use_gfx_mode) {
+		setup_font(&gfx_state, gfx_state.tg_fb.fb_height,
+		    gfx_state.tg_fb.fb_width);
+		/* Point of origin in pixels. */
+		gfx_state.tg_origin.tp_row = (gfx_state.tg_fb.fb_height -
+		    (gfx_state.tg_tp.tp_row * gfx_state.tg_font.vf_height)) / 2;
+		gfx_state.tg_origin.tp_col = (gfx_state.tg_fb.fb_width -
+		    (gfx_state.tg_tp.tp_col * gfx_state.tg_font.vf_width)) / 2;
+
+		gfx_state.tg_glyph_size = gfx_state.tg_font.vf_height *
+		    gfx_state.tg_font.vf_width * 4;
+		free(gfx_state.tg_glyph);
+		gfx_state.tg_glyph = malloc(gfx_state.tg_glyph_size);
+		if (gfx_state.tg_glyph == NULL)
+			return (false);
+		gfx_state.tg_functions = &tfx;
+		snprintf(env, sizeof (env), "%d", gfx_state.tg_fb.fb_height);
+		env_setenv("screen.height", EV_VOLATILE | EV_NOHOOK, env,
+		    env_noset, env_screen_nounset);
+		snprintf(env, sizeof (env), "%d", gfx_state.tg_fb.fb_width);
+		env_setenv("screen.width", EV_VOLATILE | EV_NOHOOK, env,
+		    env_noset, env_screen_nounset);
+		snprintf(env, sizeof (env), "%d", gfx_state.tg_fb.fb_bpp);
+		env_setenv("screen.depth", EV_VOLATILE | EV_NOHOOK, env,
+		    env_noset, env_screen_nounset);
+	} else {
+		/* Trigger loading of 8x16 font. */
+		setup_font(&gfx_state,
+		    16 * gfx_state.tg_fb.fb_height + BORDER_PIXELS,
+		    8 * gfx_state.tg_fb.fb_width + BORDER_PIXELS);
+		gfx_state.tg_functions = &tf;
+		/* ensure the following are not set for text mode */
+		unsetenv("screen.height");
+		unsetenv("screen.width");
+		unsetenv("screen.depth");
+		unsetenv("kern.vt.fb.default_mode");
+		vidc_install_font();
+	}
+
+	free(screen_buffer);
+	screen_buffer = malloc(gfx_state.tg_tp.tp_row * gfx_state.tg_tp.tp_col *
+	    sizeof(*screen_buffer));
+	if (screen_buffer == NULL)
+		return (false);
+
+	teken_init(&gfx_state.tg_teken, gfx_state.tg_functions, &gfx_state);
+
+	if (gfx_state.tg_ctype == CT_INDEXED)
+		format = COLOR_FORMAT_VGA;
+	else
+		format = COLOR_FORMAT_RGB;
+
+	roff = ffs(gfx_state.tg_fb.fb_mask_red) - 1;
+	goff = ffs(gfx_state.tg_fb.fb_mask_green) - 1;
+	boff = ffs(gfx_state.tg_fb.fb_mask_blue) - 1;
+	(void) generate_cons_palette(cmap, format,
+	    gfx_state.tg_fb.fb_mask_red >> roff, roff,
+	    gfx_state.tg_fb.fb_mask_green >> goff, goff,
+	    gfx_state.tg_fb.fb_mask_blue >> boff, boff);
+
+	if (gfx_state.tg_ctype == CT_INDEXED)
+		vidc_load_palette(cmap);
+
+	teken_set_winsize(&gfx_state.tg_teken, &gfx_state.tg_tp);
+	a = teken_get_defattr(&gfx_state.tg_teken);
+	attr = *a;
+
+	/*
+	 * On first run, we set up the vidc_set_colors()
+	 * callback. If the env is already set, we
+	 * pick up fg and bg color values from the environment.
+	 */
+	ptr = getenv("teken.fg_color");
+	if (ptr != NULL) {
+		attr.ta_fgcolor = strtol(ptr, NULL, 10);
+		ptr = getenv("teken.bg_color");
+		attr.ta_bgcolor = strtol(ptr, NULL, 10);
+
+		teken_set_defattr(&gfx_state.tg_teken, &attr);
+	} else {
+		snprintf(env, sizeof(env), "%d", attr.ta_fgcolor);
+		env_setenv("teken.fg_color", EV_VOLATILE, env,
+		    vidc_set_colors, env_nounset);
+		snprintf(env, sizeof(env), "%d", attr.ta_bgcolor);
+		env_setenv("teken.bg_color", EV_VOLATILE, env,
+		    vidc_set_colors, env_nounset);
+	}
+
+	/* Improve visibility */
+	if (attr.ta_bgcolor == TC_WHITE)
+		attr.ta_bgcolor |= TC_LIGHT;
+	teken_set_defattr(&gfx_state.tg_teken, &attr);
+
+	snprintf(env, sizeof (env), "%u", (unsigned)gfx_state.tg_tp.tp_row);
+	setenv("LINES", env, 1);
+	snprintf(env, sizeof (env), "%u", (unsigned)gfx_state.tg_tp.tp_col);
+	setenv("COLUMNS", env, 1);
+
+	/* Draw frame around terminal area. */
+	cons_draw_frame(&attr);
+	/* Erase display, this will also fill our screen buffer. */
+	teken_input(&gfx_state.tg_teken, "\e[2J", 4);
+	gfx_state.tg_functions->tf_param(&gfx_state, TP_SHOWCURSOR, 1);
+
+        return (true);
+}
+
 static int
 vidc_init(int arg)
 {
 	const teken_attr_t *a;
 	int val;
 	char env[8];
 
 	if (vidc_started && arg == 0)
 		return (0);
 
 	vidc_started = 1;
+	vbe_init();
 
 	/*
 	 * Check Miscellaneous Output Register (Read at 3CCh, Write at 3C2h)
 	 * for bit 1 (Input/Output Address Select), which means
 	 * color/graphics adapter.
 	 */
 	if (vga_get_reg(VGA_REG_BASE, VGA_GEN_MISC_OUTPUT_R) & VGA_GEN_MO_IOA)
 		vgatext = (uint16_t *)PTOV(VGA_TXT_BASE);
 	else
 		vgatext = (uint16_t *)PTOV(VGA_MEM_BASE + VGA_MEM_SIZE);
 
         /* set 16bit colors */
         val = vga_get_atr(VGA_REG_BASE, VGA_AC_MODE_CONTROL);
         val &= ~VGA_AC_MC_BI;
         val &= ~VGA_AC_MC_ELG;
         vga_set_atr(VGA_REG_BASE, VGA_AC_MODE_CONTROL, val);
 
-	tp.tp_row = TEXT_ROWS;
-	tp.tp_col = TEXT_COLS;
-	buffer = malloc(tp.tp_row * tp.tp_col * sizeof(*buffer));
-	if (buffer == NULL)
-		return (1);
-
-	snprintf(env, sizeof (env), "%u", tp.tp_row);
-	setenv("LINES", env, 1);
-	snprintf(env, sizeof (env), "%u", tp.tp_col);
-	setenv("COLUMNS", env, 1);
-
-	teken_init(&teken, &tf, NULL);
-	teken_set_winsize(&teken, &tp);
-	a = teken_get_defattr(&teken);
+#if defined(FRAMEBUFFER_MODE)
+	val = vbe_default_mode();
+	/* if val is not legal VBE mode, use text mode */
+	if (VBE_VALID_MODE(val)) {
+		if (vbe_set_mode(val) != 0)
+			bios_set_text_mode(VGA_TEXT_MODE);
+	}
+#endif
 
-	snprintf(env, sizeof(env), "%d", a->ta_fgcolor);
-	env_setenv("teken.fg_color", EV_VOLATILE, env, vidc_set_colors,
-	    env_nounset);
-	snprintf(env, sizeof(env), "%d", a->ta_bgcolor);
-	env_setenv("teken.bg_color", EV_VOLATILE, env, vidc_set_colors,
-	    env_nounset);
+	gfx_framework_init();
 
-	/* Erase display, this will also fill our screen buffer. */
-	teken_input(&teken, "\e[J", 3);
+	if (!cons_update_mode(VBE_VALID_MODE(vbe_get_mode())))
+		return (1);
 
 	for (int i = 0; i < 10 && vidc_ischar(); i++)
 		(void) vidc_getchar();
 
 	return (0);	/* XXX reinit? */
 }
 
 void
 vidc_biosputchar(int c)
 {
 
     v86.ctl = 0;
     v86.addr = 0x10;
     v86.eax = 0xe00 | (c & 0xff);
     v86.ebx = 0x7;
     v86int();
 }
 
 static void
 vidc_putchar(int c)
 {
 	unsigned char ch = c;
 
-	if (buffer != NULL)
-		teken_input(&teken, &ch, sizeof (ch));
+	if (screen_buffer != NULL)
+		teken_input(&gfx_state.tg_teken, &ch, sizeof (ch));
 	else
 		vidc_biosputchar(c);
 }
 
 static int
 vidc_getchar(void)
 {
 	int i, c;
 
 	for (i = 0; i < KEYBUFSZ; i++) {
 		if (keybuf[i] != 0) {
 			c = keybuf[i];
 			keybuf[i] = 0;
 			return (c);
 		}
 	}
 
 	if (vidc_ischar()) {
 		v86.ctl = 0;
 		v86.addr = 0x16;
 		v86.eax = 0x0;
 		v86int();
 		if ((v86.eax & 0xff) != 0) {
 			return (v86.eax & 0xff);
 		}
 
 		/* extended keys */
 		switch (v86.eax & 0xff00) {
 		case 0x4800:	/* up */
 			keybuf[0] = '[';
 			keybuf[1] = 'A';
 			return (0x1b);	/* esc */
 		case 0x4b00:	/* left */
 			keybuf[0] = '[';
 			keybuf[1] = 'D';
 			return (0x1b);	/* esc */
 		case 0x4d00:	/* right */
 			keybuf[0] = '[';
 			keybuf[1] = 'C';
 			return (0x1b);	/* esc */
 		case 0x5000:	/* down */
 			keybuf[0] = '[';
 			keybuf[1] = 'B';
 			return (0x1b);	/* esc */
 		default:
 			return (-1);
 		}
 	} else {
 		return (-1);
 	}
 }
 
 static int
 vidc_ischar(void)
 {
 	int i;
 
 	for (i = 0; i < KEYBUFSZ; i++) {
 		if (keybuf[i] != 0) {
 			return (1);
 		}
 	}
 
 	v86.ctl = V86_FLAGS;
 	v86.addr = 0x16;
 	v86.eax = 0x100;
 	v86int();
 	return (!V86_ZR(v86.efl));
 }
 
 #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()'.
 	 */
 	inb(IO_DUMMY); inb(IO_DUMMY);
 	inb(IO_DUMMY); inb(IO_DUMMY);
 	inb(IO_DUMMY); inb(IO_DUMMY);
 }
 
 /*
  * This routine uses an inb to an unused port, the time to execute that
  * inb is approximately 1.25uS.  This value is pretty constant across
  * all CPU's and all buses, with the exception of some PCI implentations
  * that do not forward this I/O address to the ISA bus as they know it
  * is not a valid ISA bus address, those machines execute this inb in
  * 60 nS :-(.
  *
  */
 static void
 delay1ms(void)
 {
 	int i = 800;
 	while (--i >= 0)
 		(void) inb(0x84);
 }
 
-/* 
+/*
  * We use the presence/absence of a keyboard to determine whether the internal
  * console can be used for input.
  *
  * Perform a simple test on the keyboard; issue the ECHO command and see
  * if the right answer is returned. We don't do anything as drastic as
  * full keyboard reset; it will be too troublesome and take too much time.
  */
 static int
 probe_keyboard(void)
 {
 	int retry = PROBE_MAXRETRY;
 	int wait;
 	int i;
 
 	while (--retry >= 0) {
 		/* flush any noise */
 		while (inb(IO_KBD + KBD_STATUS_PORT) & KBDS_ANY_BUFFER_FULL) {
 			delay7();
 			inb(IO_KBD + KBD_DATA_PORT);
 			delay1ms();
 		}
 
 		/* wait until the controller can accept a command */
 		for (wait = PROBE_MAXWAIT; wait > 0; --wait) {
 			if (((i = inb(IO_KBD + KBD_STATUS_PORT)) 
 			    & (KBDS_INPUT_BUFFER_FULL | KBDS_ANY_BUFFER_FULL))
 			    == 0)
 				break;
 			if (i & KBDS_ANY_BUFFER_FULL) {
 				delay7();
 				inb(IO_KBD + KBD_DATA_PORT);
 			}
 			delay1ms();
 		}
 		if (wait <= 0)
 			continue;
 
 		/* send the ECHO command */
 		outb(IO_KBD + KBD_DATA_PORT, KBDC_ECHO);
 
 		/* wait for a response */
 		for (wait = PROBE_MAXWAIT; wait > 0; --wait) {
 			if (inb(IO_KBD + KBD_STATUS_PORT) &
 			    KBDS_ANY_BUFFER_FULL)
 				break;
 			delay1ms();
 		}
 		if (wait <= 0)
 			continue;
 
 		delay7();
 		i = inb(IO_KBD + KBD_DATA_PORT);
 #ifdef PROBE_KBD_BEBUG
 		printf("probe_keyboard: got 0x%x.\n", i);
 #endif
 		if (i == KBD_ECHO) {
 			/* got the right answer */
 			return (1);
 		}
 	}
 
 	return (0);
 }
 #endif /* KEYBOARD_PROBE */
diff --git a/stand/i386/loader/Makefile b/stand/i386/loader/Makefile
index 74f39c078645..e80e19dfb9e6 100644
--- a/stand/i386/loader/Makefile
+++ b/stand/i386/loader/Makefile
@@ -1,93 +1,102 @@
 # $FreeBSD$
 
 HAVE_ZFS=		${MK_LOADER_ZFS}
 
 LOADER_NET_SUPPORT?=	yes
 LOADER_NFS_SUPPORT?=	yes
 LOADER_TFTP_SUPPORT?=	yes
 LOADER_CD9660_SUPPORT?=	yes
 LOADER_EXT2FS_SUPPORT?=	yes
 LOADER_MSDOS_SUPPORT?=	yes
 LOADER_UFS_SUPPORT?=	yes
 LOADER_GZIP_SUPPORT?=	yes
 LOADER_BZIP2_SUPPORT?=	yes
 
 .include <bsd.init.mk>
 
 LOADER?=	loader_${LOADER_INTERP}
 PROG=		${LOADER}.sym
 INTERNALPROG=
 NEWVERSWHAT?=	"bootstrap loader" x86
 VERSION_FILE=	${.CURDIR}/../loader/version
 
 .PATH:		${BOOTSRC}/i386/loader
 
 # architecture-specific loader code
-SRCS=		main.c conf.c vers.c chain.c
+SRCS=		main.c conf.c vers.c chain.c gfx_fb.c 8x16.c
+
+CFLAGS.gfx_fb.c += -I${.CURDIR}/../libi386
+CFLAGS.gfx_fb.c += -I$(SRCTOP)/sys/teken
+CFLAGS.gfx_fb.c += -I${SRCTOP}/sys/cddl/contrib/opensolaris/common/lz4
+CFLAGS.gfx_fb.c += -I${SRCTOP}/contrib/pnglite
+CFLAGS.gfx_fb.c += -DHAVE_MEMCPY -I${SRCTOP}/sys/contrib/zlib
 
 # Include bcache code.
 HAVE_BCACHE=	yes
 
 # Enable PnP and ISA-PnP code.
 HAVE_PNP=	yes
 HAVE_ISABUS=	yes
 
 .if ${MK_LOADER_FIREWIRE} == "yes"
 CFLAGS+=	-DLOADER_FIREWIRE_SUPPORT
 LIBFIREWIRE=	${BOOTOBJ}/i386/libfirewire/libfirewire.a
 .endif
 
 .if ${MK_LOADER_ZFS} == "yes"
 CFLAGS.main.c+=	-I${SYSDIR}/contrib/openzfs/include
 CFLAGS.main.c+=	-I${SYSDIR}/contrib/openzfs/include/os/freebsd/zfs
 .endif
 
 .if exists(${.CURDIR}/help.i386)
 HELP_FILES=	${.CURDIR}/help.i386
 .endif
 
 # Always add MI sources
 .include	"${BOOTSRC}/loader.mk"
 
-CLEANFILES+=	${LOADER} ${LOADER}.bin
+CLEANFILES+=	${LOADER} ${LOADER}.bin 8x16.c
 
 ORG=		0x0
 
 CFLAGS+=	-Wall
 LDFLAGS+=	-static ${LDFLAGS_ORG} -Wl,--gc-sections
 
 # i386 standalone support library
 LIBI386=	${BOOTOBJ}/i386/libi386/libi386.a
 CFLAGS+=	-I${BOOTSRC}/i386
 
 # Debug me!
 #CFLAGS+=	-g
 #LDFLAGS+=	-g
 
+8x16.c: ${SRCTOP}/contrib/terminus/ter-u16v.bdf
+	vtfontcvt -f compressed-source -o ${.TARGET} ${.ALLSRC}
+
 ${LOADER}: ${LOADER}.bin ${BTXLDR} ${BTXKERN}
 	btxld -v -f aout -e ${LOADER_ADDRESS} -o ${.TARGET} -l ${BTXLDR} \
 		-b ${BTXKERN} ${LOADER}.bin
 
 ${LOADER}.bin: ${LOADER}.sym
 	${STRIPBIN} -R .comment -R .note -o ${.TARGET} ${.ALLSRC}
 
 .if ${MK_LOADER_ZFS} == "yes" && ${LOADER_INTERP} == ${LOADER_DEFAULT_INTERP}
 LINKS+=		${BINDIR}/${LOADER} ${BINDIR}/zfsloader
 .endif
 .if ${LOADER_INTERP} == ${LOADER_DEFAULT_INTERP}
 LINKS+=		${BINDIR}/${LOADER} ${BINDIR}/loader
 .endif
 FILES+=	${LOADER}
 FILESMODE_${LOADER}= ${BINMODE} -b
 
 # XXX crt0.o needs to be first for pxeboot(8) to work
 OBJS=	${BTXCRT}
 
 DPADD=	${LDR_INTERP32} ${LIBFIREWIRE} ${LIBI386} ${LIBSA32}
 LDADD=	${LDR_INTERP32} ${LIBFIREWIRE} ${LIBI386} ${LIBSA32}
 
 .if ${MACHINE_CPUARCH} == "amd64"
 CFLAGS+=	-DLOADER_PREFER_AMD64
 .endif
 
 .include <bsd.prog.mk>
diff --git a/stand/i386/loader/main.c b/stand/i386/loader/main.c
index ceeaf7260acc..a595340d3a13 100644
--- a/stand/i386/loader/main.c
+++ b/stand/i386/loader/main.c
@@ -1,454 +1,458 @@
 /*-
  * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
 
 #include <sys/cdefs.h>
 __FBSDID("$FreeBSD$");
 
 /*
  * MD bootstrap main() and assorted miscellaneous
  * commands.
  */
 
 #include <stand.h>
 #include <stddef.h>
 #include <string.h>
 #include <machine/bootinfo.h>
 #include <machine/cpufunc.h>
 #include <machine/psl.h>
 #include <sys/disk.h>
 #include <sys/reboot.h>
 #include <common/drv.h>
 
 #include "bootstrap.h"
 #include "common/bootargs.h"
 #include "libi386/libi386.h"
 #include <smbios.h>
 #include "btxv86.h"
 
 #ifdef LOADER_ZFS_SUPPORT
 #include <sys/zfs_bootenv.h>
 #include "libzfs.h"
 #endif
 
 CTASSERT(sizeof(struct bootargs) == BOOTARGS_SIZE);
 CTASSERT(offsetof(struct bootargs, bootinfo) == BA_BOOTINFO);
 CTASSERT(offsetof(struct bootargs, bootflags) == BA_BOOTFLAGS);
 CTASSERT(offsetof(struct bootinfo, bi_size) == BI_SIZE);
 
 /* Arguments passed in from the boot1/boot2 loader */
 static struct bootargs *kargs;
 
 static uint32_t		initial_howto;
 static uint32_t		initial_bootdev;
 static struct bootinfo	*initial_bootinfo;
 
 struct arch_switch	archsw;		/* MI/MD interface boundary */
 
 static void		extract_currdev(void);
 static int		isa_inb(int port);
 static void		isa_outb(int port, int value);
 void			exit(int code);
 #ifdef LOADER_GELI_SUPPORT
 #include "geliboot.h"
 struct geli_boot_args	*gargs;
 struct geli_boot_data	*gbdata;
 #endif
 #ifdef LOADER_ZFS_SUPPORT
 struct zfs_boot_args	*zargs;
 static void		i386_zfs_probe(void);
 #endif
 
 /* XXX debugging */
 extern char end[];
 
 static void *heap_top;
 static void *heap_bottom;
 
 caddr_t
 ptov(uintptr_t x)
 {
 	return (PTOV(x));
 }
 
 int
 main(void)
 {
 	int	i;
 
 	/* Pick up arguments */
 	kargs = (void *)__args;
 	initial_howto = kargs->howto;
 	initial_bootdev = kargs->bootdev;
 	initial_bootinfo = kargs->bootinfo ?
 	    (struct bootinfo *)PTOV(kargs->bootinfo) : NULL;
 
 	/* Initialize the v86 register set to a known-good state. */
 	bzero(&v86, sizeof(v86));
 	v86.efl = PSL_RESERVED_DEFAULT | PSL_I;
 
 	/* 
 	 * Initialise the heap as early as possible.
 	 * Once this is done, malloc() is usable.
 	 */
 	bios_getmem();
 
 #if defined(LOADER_BZIP2_SUPPORT) || defined(LOADER_FIREWIRE_SUPPORT) || \
     defined(LOADER_GPT_SUPPORT) || defined(LOADER_ZFS_SUPPORT)
 	if (high_heap_size > 0) {
 		heap_top = PTOV(high_heap_base + high_heap_size);
 		heap_bottom = PTOV(high_heap_base);
 		if (high_heap_base < memtop_copyin)
 			memtop_copyin = high_heap_base;
 	} else
 #endif
 	{
 		heap_top = (void *)PTOV(bios_basemem);
 		heap_bottom = (void *)end;
 	}
 	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.
 	 *
 	 * We can use printf() etc. once this is done.
 	 * If the previous boot stage has requested a serial console,
 	 * prefer that.
 	 */
 	bi_setboothowto(initial_howto);
 	if (initial_howto & RB_MULTIPLE) {
 		if (initial_howto & RB_SERIAL)
 			setenv("console", "comconsole vidconsole", 1);
 		else
 			setenv("console", "vidconsole comconsole", 1);
 	} else if (initial_howto & RB_SERIAL) {
 		setenv("console", "comconsole", 1);
 	} else if (initial_howto & RB_MUTE) {
 		setenv("console", "nullconsole", 1);
 	}
 	cons_probe();
 
 	/*
 	 * Initialise the block cache. Set the upper limit.
 	 */
 	bcache_init(32768, 512);
 
 	/*
 	 * Special handling for PXE and CD booting.
 	 */
 	if (kargs->bootinfo == 0) {
 		/*
 		 * We only want the PXE disk to try to init itself in the below
 		 * walk through devsw if we actually booted off of PXE.
 		 */
 		if (kargs->bootflags & KARGS_FLAGS_PXE)
 			pxe_enable(kargs->pxeinfo ?
 			    PTOV(kargs->pxeinfo) : NULL);
 		else if (kargs->bootflags & KARGS_FLAGS_CD)
 			bc_add(initial_bootdev);
 	}
 
 	archsw.arch_autoload = i386_autoload;
 	archsw.arch_getdev = i386_getdev;
 	archsw.arch_copyin = i386_copyin;
 	archsw.arch_copyout = i386_copyout;
 	archsw.arch_readin = i386_readin;
 	archsw.arch_isainb = isa_inb;
 	archsw.arch_isaoutb = isa_outb;
 	archsw.arch_hypervisor = x86_hypervisor;
 #ifdef LOADER_ZFS_SUPPORT
 	archsw.arch_zfs_probe = i386_zfs_probe;
 
 	/*
 	 * zfsboot and gptzfsboot have always passed KARGS_FLAGS_ZFS,
 	 * so if that is set along with KARGS_FLAGS_EXTARG we know we
 	 * can interpret the extarg data as a struct zfs_boot_args.
 	 */
 #define	KARGS_EXTARGS_ZFS	(KARGS_FLAGS_EXTARG | KARGS_FLAGS_ZFS)
 
 	if ((kargs->bootflags & KARGS_EXTARGS_ZFS) == KARGS_EXTARGS_ZFS) {
 		zargs = (struct zfs_boot_args *)(kargs + 1);
 	}
 #endif /* LOADER_ZFS_SUPPORT */
 
 #ifdef LOADER_GELI_SUPPORT
 	/*
 	 * If we decided earlier that we have zfs_boot_args extarg data,
 	 * and it is big enough to contain the embedded geli data
 	 * (the early zfs_boot_args structs weren't), then init the gbdata
 	 * pointer accordingly. If there is extarg data which isn't
 	 * zfs_boot_args data, determine whether it is geli_boot_args data.
 	 * Recent versions of gptboot set KARGS_FLAGS_GELI to indicate that.
 	 * Earlier versions didn't, but we presume that's what we
 	 * have if the extarg size exactly matches the size of the
 	 * geli_boot_args struct during that pre-flag era.
 	 */
 #define	LEGACY_GELI_ARGS_SIZE	260	/* This can never change */
 
 #ifdef LOADER_ZFS_SUPPORT
 	if (zargs != NULL) {
 		if (zargs->size > offsetof(struct zfs_boot_args, gelidata)) {
 			gbdata = &zargs->gelidata;
 		}
 	} else
 #endif /* LOADER_ZFS_SUPPORT */
 	if ((kargs->bootflags & KARGS_FLAGS_EXTARG) != 0) {
 		gargs = (struct geli_boot_args *)(kargs + 1);
 		if ((kargs->bootflags & KARGS_FLAGS_GELI) ||
 		    gargs->size == LEGACY_GELI_ARGS_SIZE) {
 			gbdata = &gargs->gelidata;
 		}
 	}
 
 	if (gbdata != NULL)
 		import_geli_boot_data(gbdata);
 #endif /* LOADER_GELI_SUPPORT */
 
 	/*
 	 * March through the device switch probing for things.
 	 */
 	for (i = 0; devsw[i] != NULL; i++)
 		if (devsw[i]->dv_init != NULL)
 			(devsw[i]->dv_init)();
 
 	printf("BIOS %dkB/%dkB available memory\n", bios_basemem / 1024,
 	    bios_extmem / 1024);
 	if (initial_bootinfo != NULL) {
 		initial_bootinfo->bi_basemem = bios_basemem / 1024;
 		initial_bootinfo->bi_extmem = bios_extmem / 1024;
 	}
 
-	/* detect ACPI for future reference */
-	biosacpi_detect();
-
 	/* detect SMBIOS for future reference */
 	smbios_detect(NULL);
 
 	/* detect PCI BIOS for future reference */
 	biospci_detect();
 
 	printf("\n%s", bootprog_info);
 
 	extract_currdev();		/* set $currdev and $loaddev */
+	autoload_font(true);
     
 	bios_getsmap();
 
 	interact();
 
 	/* if we ever get here, it is an error */
 	return (1);
 }
 
 /*
  * Set the 'current device' by (if possible) recovering the boot device as 
  * supplied by the initial bootstrap.
  *
  * XXX should be extended for netbooting.
  */
 static void
 extract_currdev(void)
 {
 	struct i386_devdesc	new_currdev;
 #ifdef LOADER_ZFS_SUPPORT
 	char			buf[20];
 	char			*bootonce;
 #endif
 	int			biosdev = -1;
 
 	/* Assume we are booting from a BIOS disk by default */
 	new_currdev.dd.d_dev = &bioshd;
 
 	/* new-style boot loaders such as pxeldr and cdldr */
 	if (kargs->bootinfo == 0) {
 		if ((kargs->bootflags & KARGS_FLAGS_CD) != 0) {
 			/* we are booting from a CD with cdboot */
 			new_currdev.dd.d_dev = &bioscd;
 			new_currdev.dd.d_unit = bd_bios2unit(initial_bootdev);
 		} else if ((kargs->bootflags & KARGS_FLAGS_PXE) != 0) {
 			/* we are booting from pxeldr */
 			new_currdev.dd.d_dev = &pxedisk;
 			new_currdev.dd.d_unit = 0;
 		} else {
 			/* we don't know what our boot device is */
 			new_currdev.d_kind.biosdisk.slice = -1;
 			new_currdev.d_kind.biosdisk.partition = 0;
 			biosdev = -1;
 		}
 #ifdef LOADER_ZFS_SUPPORT
 	} else if ((kargs->bootflags & KARGS_FLAGS_ZFS) != 0) {
 		/*
 		 * zargs was set in main() if we have new style extended
 		 * argument
 		 */
 		if (zargs != NULL &&
 		    zargs->size >=
 		    offsetof(struct zfs_boot_args, primary_pool)) {
 			/* sufficient data is provided */
 			new_currdev.d_kind.zfs.pool_guid = zargs->pool;
 			new_currdev.d_kind.zfs.root_guid = zargs->root;
 			if (zargs->size >= sizeof(*zargs) &&
 			    zargs->primary_vdev != 0) {
 				sprintf(buf, "%llu", zargs->primary_pool);
 				setenv("vfs.zfs.boot.primary_pool", buf, 1);
 				sprintf(buf, "%llu", zargs->primary_vdev);
 				setenv("vfs.zfs.boot.primary_vdev", buf, 1);
 			}
 		} else {
 			/* old style zfsboot block */
 			new_currdev.d_kind.zfs.pool_guid = kargs->zfspool;
 			new_currdev.d_kind.zfs.root_guid = 0;
 		}
 		new_currdev.dd.d_dev = &zfs_dev;
 
 		if ((bootonce = malloc(VDEV_PAD_SIZE)) != NULL) {
 			if (zfs_get_bootonce(&new_currdev, OS_BOOTONCE_USED,
 			    bootonce, VDEV_PAD_SIZE) == 0) {
 				setenv("zfs-bootonce", bootonce, 1);
 			}
 			free(bootonce);
 			(void) zfs_attach_nvstore(&new_currdev);
 		}
 
 #endif
 	} else if ((initial_bootdev & B_MAGICMASK) != B_DEVMAGIC) {
 		/* The passed-in boot device is bad */
 		new_currdev.d_kind.biosdisk.slice = -1;
 		new_currdev.d_kind.biosdisk.partition = 0;
 		biosdev = -1;
 	} else {
 		new_currdev.d_kind.biosdisk.slice =
 		    B_SLICE(initial_bootdev) - 1;
 		new_currdev.d_kind.biosdisk.partition =
 		    B_PARTITION(initial_bootdev);
 		biosdev = initial_bootinfo->bi_bios_dev;
 
 		/*
 		 * If we are booted by an old bootstrap, we have to guess at
 		 * the BIOS unit number. We will lose if there is more than
 		 * one disk type and we are not booting from the
 		 * lowest-numbered disk type (ie. SCSI when IDE also exists).
 		 */
 		if ((biosdev == 0) && (B_TYPE(initial_bootdev) != 2)) {
 			/*
 			 * biosdev doesn't match major, assume harddisk
 			 */
 			biosdev = 0x80 + B_UNIT(initial_bootdev);
 		}
 	}
 
 	/*
 	 * If we are booting off of a BIOS disk and we didn't succeed
 	 * in determining which one we booted off of, just use disk0:
 	 * as a reasonable default.
 	 */
 	if ((new_currdev.dd.d_dev->dv_type == bioshd.dv_type) &&
 	    ((new_currdev.dd.d_unit = bd_bios2unit(biosdev)) == -1)) {
 		printf("Can't work out which disk we are booting "
 		    "from.\nGuessed BIOS device 0x%x not found by "
 		    "probes, defaulting to disk0:\n", biosdev);
 		new_currdev.dd.d_unit = 0;
 	}
 
 #ifdef LOADER_ZFS_SUPPORT
 	if (new_currdev.dd.d_dev->dv_type == DEVT_ZFS)
 		init_zfs_boot_options(zfs_fmtdev(&new_currdev));
 #endif
 
 	env_setenv("currdev", EV_VOLATILE, i386_fmtdev(&new_currdev),
 	    i386_setcurrdev, env_nounset);
 	env_setenv("loaddev", EV_VOLATILE, i386_fmtdev(&new_currdev),
 	    env_noset, env_nounset);
 }
 
 COMMAND_SET(reboot, "reboot", "reboot the system", command_reboot);
 
 static int
 command_reboot(int argc, char *argv[])
 {
 	int i;
 
 	for (i = 0; devsw[i] != NULL; ++i)
 		if (devsw[i]->dv_cleanup != NULL)
 			(devsw[i]->dv_cleanup)();
 
 	printf("Rebooting...\n");
 	delay(1000000);
 	__exit(0);
 }
 
 /* provide this for panic, as it's not in the startup code */
 void
 exit(int code)
 {
 	__exit(code);
 }
 
 COMMAND_SET(heap, "heap", "show heap usage", command_heap);
 
 static int
 command_heap(int argc, char *argv[])
 {
 	mallocstats();
 	printf("heap base at %p, top at %p, upper limit at %p\n", heap_bottom,
 	    sbrk(0), heap_top);
 	return (CMD_OK);
 }
 
 /* ISA bus access functions for PnP. */
 static int
 isa_inb(int port)
 {
 
 	return (inb(port));
 }
 
 static void
 isa_outb(int port, int value)
 {
 
 	outb(port, value);
 }
 
 #ifdef LOADER_ZFS_SUPPORT
 static void
 i386_zfs_probe(void)
 {
 	char devname[32];
 	struct i386_devdesc dev;
 
 	/*
 	 * Open all the disks we can find and see if we can reconstruct
 	 * ZFS pools from them.
 	 */
 	dev.dd.d_dev = &bioshd;
 	for (dev.dd.d_unit = 0; bd_unit2bios(&dev) >= 0; dev.dd.d_unit++) {
 		snprintf(devname, sizeof(devname), "%s%d:", bioshd.dv_name,
 		    dev.dd.d_unit);
 		zfs_probe_dev(devname, NULL);
 	}
 }
 #endif
diff --git a/stand/images/Makefile b/stand/images/Makefile
new file mode 100644
index 000000000000..432b1daa851b
--- /dev/null
+++ b/stand/images/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+.include <bsd.init.mk>
+
+FILES+=	freebsd-brand-rev.png freebsd-brand.png freebsd-logo-rev.png
+
+FILESDIR=  /boot/images
+
+.include <bsd.prog.mk>
diff --git a/stand/images/freebsd-brand-rev.png b/stand/images/freebsd-brand-rev.png
new file mode 100644
index 000000000000..945c12da0556
Binary files /dev/null and b/stand/images/freebsd-brand-rev.png differ
diff --git a/stand/images/freebsd-brand.png b/stand/images/freebsd-brand.png
new file mode 100644
index 000000000000..8ce3091bd83f
Binary files /dev/null and b/stand/images/freebsd-brand.png differ
diff --git a/stand/images/freebsd-logo-rev.png b/stand/images/freebsd-logo-rev.png
new file mode 100644
index 000000000000..5279b718218c
Binary files /dev/null and b/stand/images/freebsd-logo-rev.png differ
diff --git a/stand/liblua/Makefile b/stand/liblua/Makefile
index 434268063158..68fa2da64fdb 100644
--- a/stand/liblua/Makefile
+++ b/stand/liblua/Makefile
@@ -1,45 +1,47 @@
 # $FreeBSD$
 
 .include <bsd.init.mk>
 
 .PATH:		${LUASRC}
 .PATH:		${LIBLUASRC}
 
 .include	"${BOOTSRC}/lua.mk"
 
 LIB=		lua
 INTERNALLIB=
 
 # Core Lua.
 SRCS=	lapi.c lcode.c lctype.c ldebug.c ldo.c ldump.c lfunc.c lgc.c llex.c \
 	lmem.c lobject.c lopcodes.c lparser.c lstate.c lstring.c ltable.c \
         ltm.c lundump.c lvm.c lzio.c
 SRCS+=	lauxlib.c lbaselib.c lstrlib.c loadlib.c
 
 # These aren't yet included, but link now, omitting them saves 15k
 #SRCS+=	lcorolib.c ldblib.c lutf8lib.c
 
 # These aren't yet compatible with the boot environment, and some may never be
 #SRCS+=	lbitlib.c liolib.c lmathlib.c loslib.c ltablib.c
 
 # Our utilities.
 SRCS+=	lerrno.c lpager.c lstd.c lutils.c
 
 .PATH:	${FLUASRC}/modules
 SRCS+=	lfs.c
 
 WARNS?=	3
 
 CFLAGS+= -DLUA_PATH=\"${LUAPATH}\" -DLUA_PATH_DEFAULT=\"${LUAPATH}/\?.lua\"
 CFLAGS+= -ffreestanding -nostdlib -DLUA_USE_POSIX
 CFLAGS+= -fno-stack-protector -D__BSD_VISIBLE
 CFLAGS+= -I${BOOTSRC}/include -I${LIBLUASRC} -I${LUASRC} -I${LDRSRC}
 
+CFLAGS.lutils.c+= -I${SRCTOP}/sys/teken -I${SRCTOP}/contrib/pnglite
+
 .if ${MACHINE_CPUARCH} == "amd64" && ${DO32:U0} == 0
 CFLAGS+=	-fPIC
 .endif
 .if ${MK_LOADER_VERIEXEC} == "yes"
 CFLAGS+= -I${SRCTOP}/lib/libsecureboot/h -DLOADER_VERIEXEC
 .endif
 
 .include <bsd.lib.mk>
diff --git a/stand/liblua/lutils.c b/stand/liblua/lutils.c
index 7c4dcf01f7cc..9243edd07e8f 100644
--- a/stand/liblua/lutils.c
+++ b/stand/liblua/lutils.c
@@ -1,399 +1,591 @@
 /*-
  * Copyright (c) 2014 Pedro Souza <pedrosouza@freebsd.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
  */
 
 #include <sys/cdefs.h>
 __FBSDID("$FreeBSD$");
 
 #include <sys/param.h>
 
 #include "lua.h"
 #include "lauxlib.h"
 #include "lstd.h"
 #include "lutils.h"
 #include "bootstrap.h"
+#include <gfx_fb.h>
+#include <pnglite.h>
 
 /*
  * Like loader.perform, except args are passed already parsed
  * on the stack.
  */
 static int
 lua_command(lua_State *L)
 {
 	int	i;
 	int	res = 1;
 	int 	argc = lua_gettop(L);
 	char	**argv;
 
 	argv = malloc(sizeof(char *) * (argc + 1));
 	if (argv == NULL)
 		return 0;
 	for (i = 0; i < argc; i++)
 		argv[i] = (char *)(intptr_t)luaL_checkstring(L, i + 1);
 	argv[argc] = NULL;
 	res = interp_builtin_cmd(argc, argv);
 	free(argv);
 	lua_pushinteger(L, res);
 
 	return 1;
 }
 
 static int
 lua_perform(lua_State *L)
 {
 	int	argc;
 	char	**argv;
 	int	res = 1;
 
 	if (parse(&argc, &argv, luaL_checkstring(L, 1)) == 0) {
 		res = interp_builtin_cmd(argc, argv);
 		free(argv);
 	}
 	lua_pushinteger(L, res);
 
 	return 1;
 }
 
 static int
 lua_command_error(lua_State *L)
 {
 
 	lua_pushstring(L, command_errbuf);
 	return 1;
 }
 
 /*
  * Accepts a space-delimited loader command and runs it through the standard
  * loader parsing, as if it were executed at the loader prompt by the user.
  */
 static int
 lua_interpret(lua_State *L)
 {
 	const char	*interp_string;
 
 	if (lua_gettop(L) != 1) {
 		lua_pushnil(L);
 		return 1;
 	}
 
 	interp_string = luaL_checkstring(L, 1);
 	lua_pushinteger(L, interp_run(interp_string));
 	return 1;
 }
 
 static int
 lua_parse(lua_State *L)
 {
 	int	argc, nargc;
 	char	**argv;
 
 	if (parse(&argc, &argv, luaL_checkstring(L, 1)) == 0) {
 		for (nargc = 0; nargc < argc; ++nargc) {
 			lua_pushstring(L, argv[nargc]);
 		}
 		free(argv);
 		return nargc;
 	}
 
 	lua_pushnil(L);
 	return 1;
 }
 
 static int
 lua_getchar(lua_State *L)
 {
 
 	lua_pushinteger(L, getchar());
 	return 1;
 }
 
 static int
 lua_ischar(lua_State *L)
 {
 
 	lua_pushboolean(L, ischar());
 	return 1;
 }
 
 static int
 lua_gets(lua_State *L)
 {
 	char	buf[129];
 
 	ngets(buf, 128);
 	lua_pushstring(L, buf);
 	return 1;
 }
 
 static int
 lua_time(lua_State *L)
 {
 
 	lua_pushinteger(L, time(NULL));
 	return 1;
 }
 
 static int
 lua_delay(lua_State *L)
 {
 
 	delay((int)luaL_checknumber(L, 1));
 	return 0;
 }
 
 static int
 lua_getenv(lua_State *L)
 {
 	lua_pushstring(L, getenv(luaL_checkstring(L, 1)));
 
 	return 1;
 }
 
 static int
 lua_setenv(lua_State *L)
 {
 	const char *key, *val;
 
 	key = luaL_checkstring(L, 1);
 	val = luaL_checkstring(L, 2);
 	lua_pushinteger(L, setenv(key, val, 1));
 
 	return 1;
 }
 
 static int
 lua_unsetenv(lua_State *L)
 {
 	const char	*ev;
 
 	ev = luaL_checkstring(L, 1);
 	lua_pushinteger(L, unsetenv(ev));
 
 	return 1;
 }
 
 static int
 lua_printc(lua_State *L)
 {
 	ssize_t cur, l;
 	const char *s = luaL_checklstring(L, 1, &l);
 
 	for (cur = 0; cur < l; ++cur)
 		putchar((unsigned char)*(s++));
 
 	return 1;
 }
 
 static int
 lua_openfile(lua_State *L)
 {
 	const char	*mode, *str;
 	int	nargs;
 
 	nargs = lua_gettop(L);
 	if (nargs < 1 || nargs > 2) {
 		lua_pushnil(L);
 		return 1;
 	}
 	str = lua_tostring(L, 1);
 	mode = "r";
 	if (nargs > 1) {
 		mode = lua_tostring(L, 2);
 		if (mode == NULL) {
 			lua_pushnil(L);
 			return 1;
 		}
 	}
 	FILE * f = fopen(str, mode);
 	if (f != NULL) {
 		FILE ** ptr = (FILE**)lua_newuserdata(L, sizeof(FILE**));
 		*ptr = f;
 	} else
 		lua_pushnil(L);
 	return 1;
 }
 
 static int
 lua_closefile(lua_State *L)
 {
 	FILE ** f;
 	if (lua_gettop(L) != 1) {
 		lua_pushboolean(L, 0);
 		return 1;
 	}
 
 	f = (FILE**)lua_touserdata(L, 1);
 	if (f != NULL && *f != NULL) {
 		lua_pushboolean(L, fclose(*f) == 0 ? 1 : 0);
 		*f = NULL;
 	} else
 		lua_pushboolean(L, 0);
 
 	return 1;
 }
 
 static int
 lua_readfile(lua_State *L)
 {
 	FILE	**f;
 	size_t	size, r;
 	char * buf;
 
 	if (lua_gettop(L) < 1 || lua_gettop(L) > 2) {
 		lua_pushnil(L);
 		lua_pushinteger(L, 0);
 		return 2;
 	}
 
 	f = (FILE**)lua_touserdata(L, 1);
 
 	if (f == NULL || *f == NULL) {
 		lua_pushnil(L);
 		lua_pushinteger(L, 0);
 		return 2;
 	}
 
 	if (lua_gettop(L) == 2)
 		size = (size_t)lua_tonumber(L, 2);
 	else
 		size = (*f)->size;
 
 
 	buf = (char*)malloc(size);
 	r = fread(buf, 1, size, *f);
 	lua_pushlstring(L, buf, r);
 	free(buf);
 	lua_pushinteger(L, r);
 
 	return 2;
 }
 
 /*
  * Implements io.write(file, ...)
  * Any number of string and number arguments may be passed to it,
  * and it will return the number of bytes written, or nil, an error string, and
  * the errno.
  */
 static int
 lua_writefile(lua_State *L)
 {
 	FILE	**f;
 	const char	*buf;
 	int	i, nargs;
 	size_t	bufsz, w, wrsz;
 
 	buf = NULL;
 	bufsz = 0;
 	w = 0;
 	wrsz = 0;
 	nargs = lua_gettop(L);
 	if (nargs < 2) {
 		errno = EINVAL;
 		return luaL_fileresult(L, 0, NULL);
 	}
 
 	f = (FILE**)lua_touserdata(L, 1);
 
 	if (f == NULL || *f == NULL) {
 		errno = EINVAL;
 		return luaL_fileresult(L, 0, NULL);
 	}
 
 	/* Do a validation pass first */
 	for (i = 0; i < nargs - 1; i++) {
 		/*
 		 * With Lua's API, lua_isstring really checks if the argument
 		 * is a string or a number.  The latter will be implicitly
 		 * converted to a string by our later call to lua_tolstring.
 		 */
 		if (!lua_isstring(L, i + 2)) {
 			errno = EINVAL;
 			return luaL_fileresult(L, 0, NULL);
 		}
 	}
 	for (i = 0; i < nargs - 1; i++) {
 		/* We've already validated; there's no chance of failure */
 		buf = lua_tolstring(L, i + 2, &bufsz);
 		wrsz = fwrite(buf, 1, bufsz, *f);
 		if (wrsz < bufsz)
 			return luaL_fileresult(L, 0, NULL);
 		w += wrsz;
 	}
 	lua_pushinteger(L, w);
 	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),
 	REG_SIMPLE(command_error),
 	REG_SIMPLE(command),
 	REG_SIMPLE(interpret),
 	REG_SIMPLE(parse),
 	REG_SIMPLE(getenv),
 	REG_SIMPLE(perform),
 	/* Also registered as the global 'printc' */
 	REG_SIMPLE(printc),
 	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 },
 };
 
 static const struct luaL_Reg iolib[] = {
 	{ "close", lua_closefile },
 	REG_SIMPLE(getchar),
 	REG_SIMPLE(gets),
 	REG_SIMPLE(ischar),
 	{ "open", lua_openfile },
 	{ "read", lua_readfile },
 	{ "write", lua_writefile },
 	{ NULL, NULL },
 };
 #undef REG_SIMPLE
 
 int
 luaopen_loader(lua_State *L)
 {
 	luaL_newlib(L, loaderlib);
 	/* Add loader.machine and loader.machine_arch properties */
 	lua_pushstring(L, MACHINE);
 	lua_setfield(L, -2, "machine");
 	lua_pushstring(L, MACHINE_ARCH);
 	lua_setfield(L, -2, "machine_arch");
 	lua_pushstring(L, LUA_PATH);
 	lua_setfield(L, -2, "lua_path");
 	/* Set global printc to loader.printc */
 	lua_register(L, "printc", lua_printc);
 	return 1;
 }
 
 int
 luaopen_io(lua_State *L)
 {
 	luaL_newlib(L, iolib);
 	return 1;
 }
diff --git a/stand/loader.mk b/stand/loader.mk
index f6341052ab71..9ed8b65a2d06 100644
--- a/stand/loader.mk
+++ b/stand/loader.mk
@@ -1,181 +1,187 @@
 # $FreeBSD$
 
 .PATH: ${LDRSRC} ${BOOTSRC}/libsa
 
 CFLAGS+=-I${LDRSRC}
 
 SRCS+=	boot.c commands.c console.c devopen.c interp.c 
 SRCS+=	interp_backslash.c interp_parse.c ls.c misc.c 
 SRCS+=	module.c nvstore.c
 
+CFLAGS.module.c += -I$(SRCTOP)/sys/teken -I${SRCTOP}/contrib/pnglite
+
 .if ${MACHINE} == "i386" || ${MACHINE_CPUARCH} == "amd64"
 SRCS+=	load_elf32.c load_elf32_obj.c reloc_elf32.c
 SRCS+=	load_elf64.c load_elf64_obj.c reloc_elf64.c
+SRCS+=  pnglite.c
+.PATH: ${SRCTOP}/contrib/pnglite
+CFLAGS.pnglite.c+= -I${SRCTOP}/contrib/pnglite
+CFLAGS.pnglite.c+= -DHAVE_MEMCPY -I${SRCTOP}/sys/contrib/zlib
 .elif ${MACHINE_CPUARCH} == "aarch64"
 SRCS+=	load_elf64.c reloc_elf64.c
 .elif ${MACHINE_CPUARCH} == "arm"
 SRCS+=	load_elf32.c reloc_elf32.c
 .elif ${MACHINE_CPUARCH} == "powerpc"
 SRCS+=	load_elf32.c reloc_elf32.c
 SRCS+=	load_elf64.c reloc_elf64.c
 SRCS+=	metadata.c
 .elif ${MACHINE_ARCH:Mmips64*} != ""
 SRCS+= load_elf64.c reloc_elf64.c
 SRCS+=	metadata.c
 .elif ${MACHINE} == "mips"
 SRCS+=	load_elf32.c reloc_elf32.c
 SRCS+=	metadata.c
 .elif ${MACHINE_CPUARCH} == "riscv"
 SRCS+=	load_elf64.c reloc_elf64.c
 SRCS+=	metadata.c
 .endif
 
 .if ${LOADER_DISK_SUPPORT:Uyes} == "yes"
 CFLAGS.part.c+= -DHAVE_MEMCPY -I${SRCTOP}/sys/contrib/zlib
 SRCS+=	disk.c part.c vdisk.c
 .endif
 
 .if ${LOADER_NET_SUPPORT:Uno} == "yes"
 SRCS+= dev_net.c
 .endif
 
 .if defined(HAVE_BCACHE)
 SRCS+=  bcache.c
 .endif
 
 .if defined(MD_IMAGE_SIZE)
 CFLAGS+= -DMD_IMAGE_SIZE=${MD_IMAGE_SIZE}
 SRCS+=	md.c
 .else
 CLEANFILES+=	md.o
 .endif
 
 # Machine-independant ISA PnP
 .if defined(HAVE_ISABUS)
 SRCS+=	isapnp.c
 .endif
 .if defined(HAVE_PNP)
 SRCS+=	pnp.c
 .endif
 
 .if ${LOADER_INTERP} == "lua"
 SRCS+=	interp_lua.c
 .include "${BOOTSRC}/lua.mk"
 LDR_INTERP=	${LIBLUA}
 LDR_INTERP32=	${LIBLUA32}
 CFLAGS.interp_lua.c= -DLUA_PATH=\"${LUAPATH}\" -I${FLUASRC}/modules
 .elif ${LOADER_INTERP} == "4th"
 SRCS+=	interp_forth.c
 .include "${BOOTSRC}/ficl.mk"
 LDR_INTERP=	${LIBFICL}
 LDR_INTERP32=	${LIBFICL32}
 .elif ${LOADER_INTERP} == "simp"
 SRCS+=	interp_simple.c
 .else
 .error Unknown interpreter ${LOADER_INTERP}
 .endif
 
 .if ${MK_LOADER_VERIEXEC} != "no"
 CFLAGS+= -DLOADER_VERIEXEC -I${SRCTOP}/lib/libsecureboot/h
 .if ${MK_LOADER_VERIEXEC_VECTX} != "no"
 CFLAGS+= -DLOADER_VERIEXEC_VECTX
 .endif
 .endif
 
 .if ${MK_LOADER_VERIEXEC_PASS_MANIFEST} != "no"
 CFLAGS+= -DLOADER_VERIEXEC_PASS_MANIFEST -I${SRCTOP}/lib/libsecureboot/h
 .endif
 
 .if defined(BOOT_PROMPT_123)
 CFLAGS+=	-DBOOT_PROMPT_123
 .endif
 
 .if defined(LOADER_INSTALL_SUPPORT)
 SRCS+=	install.c
 .endif
 
 # Filesystem support
 .if ${LOADER_CD9660_SUPPORT:Uno} == "yes"
 CFLAGS+=	-DLOADER_CD9660_SUPPORT
 .endif
 .if ${LOADER_EXT2FS_SUPPORT:Uno} == "yes"
 CFLAGS+=	-DLOADER_EXT2FS_SUPPORT
 .endif
 .if ${LOADER_MSDOS_SUPPORT:Uno} == "yes"
 CFLAGS+=	-DLOADER_MSDOS_SUPPORT
 .endif
 .if ${LOADER_UFS_SUPPORT:Uyes} == "yes"
 CFLAGS+=	-DLOADER_UFS_SUPPORT
 .endif
 
 # Compression
 .if ${LOADER_GZIP_SUPPORT:Uno} == "yes"
 CFLAGS+=	-DLOADER_GZIP_SUPPORT
 .endif
 .if ${LOADER_BZIP2_SUPPORT:Uno} == "yes"
 CFLAGS+=	-DLOADER_BZIP2_SUPPORT
 .endif
 
 # Network related things
 .if ${LOADER_NET_SUPPORT:Uno} == "yes"
 CFLAGS+=	-DLOADER_NET_SUPPORT
 .endif
 .if ${LOADER_NFS_SUPPORT:Uno} == "yes"
 CFLAGS+=	-DLOADER_NFS_SUPPORT
 .endif
 .if ${LOADER_TFTP_SUPPORT:Uno} == "yes"
 CFLAGS+=	-DLOADER_TFTP_SUPPORT
 .endif
 
 # Partition support
 .if ${LOADER_GPT_SUPPORT:Uyes} == "yes"
 CFLAGS+= -DLOADER_GPT_SUPPORT
 .endif
 .if ${LOADER_MBR_SUPPORT:Uyes} == "yes"
 CFLAGS+= -DLOADER_MBR_SUPPORT
 .endif
 
 .if ${HAVE_ZFS:Uno} == "yes"
 CFLAGS+=	-DLOADER_ZFS_SUPPORT
 CFLAGS+=	-I${ZFSSRC}
 CFLAGS+=	-I${SYSDIR}/cddl/boot/zfs
 CFLAGS+=	-I${SYSDIR}/cddl/contrib/opensolaris/uts/common
 SRCS+=		zfs_cmd.c
 .endif
 
 LIBFICL=	${BOOTOBJ}/ficl/libficl.a
 .if ${MACHINE} == "i386"
 LIBFICL32=	${LIBFICL}
 .else
 LIBFICL32=	${BOOTOBJ}/ficl32/libficl.a
 .endif
 
 LIBLUA=		${BOOTOBJ}/liblua/liblua.a
 .if ${MACHINE} == "i386"
 LIBLUA32=	${LIBLUA}
 .else
 LIBLUA32=	${BOOTOBJ}/liblua32/liblua.a
 .endif
 
 CLEANFILES+=	vers.c
 VERSION_FILE?=	${.CURDIR}/version
 .if ${MK_REPRODUCIBLE_BUILD} != no
 REPRO_FLAG=	-r
 .endif
 vers.c: ${LDRSRC}/newvers.sh ${VERSION_FILE}
 	sh ${LDRSRC}/newvers.sh ${REPRO_FLAG} ${VERSION_FILE} \
 	    ${NEWVERSWHAT}
 
 .if ${MK_LOADER_VERBOSE} != "no"
 CFLAGS+=	-DELF_VERBOSE
 .endif
 
 .if !empty(HELP_FILES)
 HELP_FILES+=	${LDRSRC}/help.common
 
 CLEANFILES+=	loader.help
 FILES+=		loader.help
 
 loader.help: ${HELP_FILES}
 	cat ${HELP_FILES} | awk -f ${LDRSRC}/merge_help.awk > ${.TARGET}
 .endif
diff --git a/stand/lua/color.lua b/stand/lua/color.lua
index 364d548ca3dc..4dc16cacb97e 100644
--- a/stand/lua/color.lua
+++ b/stand/lua/color.lua
@@ -1,122 +1,122 @@
 --
 -- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
 --
 -- Copyright (c) 2015 Pedro Souza <pedrosouza@freebsd.org>
 -- All rights reserved.
 --
 -- Redistribution and use in source and binary forms, with or without
 -- modification, are permitted provided that the following conditions
 -- are met:
 -- 1. Redistributions of source code must retain the above copyright
 --    notice, this list of conditions and the following disclaimer.
 -- 2. Redistributions in binary form must reproduce the above copyright
 --    notice, this list of conditions and the following disclaimer in the
 --    documentation and/or other materials provided with the distribution.
 --
 -- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 -- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 -- ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 -- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 -- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 -- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 -- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 -- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 -- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 -- SUCH DAMAGE.
 --
 -- $FreeBSD$
 --
 
 local core = require("core")
 local hook = require("hook")
 
 local color = {}
 
 local function recalcDisabled()
 	color.disabled = not color.isEnabled()
 end
 
 -- Module exports
 color.BLACK   = 0
 color.RED     = 1
 color.GREEN   = 2
 color.YELLOW  = 3
 color.BLUE    = 4
 color.MAGENTA = 5
 color.CYAN    = 6
 color.WHITE   = 7
 
 color.DEFAULT = 9
 color.BRIGHT  = 1
 color.DIM     = 2
 
 function color.isEnabled()
 	local c = loader.getenv("loader_color")
 	if c ~= nil then
 		return c:lower() ~= "no" and c ~= "0"
 	end
-	return not core.isSerialBoot()
+	return true
 end
 
 function color.escapefg(color_value)
 	if color.disabled then
 		return ''
 	end
 	return core.KEYSTR_CSI .. "3" .. color_value .. "m"
 end
 
 function color.resetfg()
 	if color.disabled then
 		return ''
 	end
 	return color.escapefg(color.DEFAULT)
 end
 
 function color.escapebg(color_value)
 	if color.disabled then
 		return ''
 	end
 	return core.KEYSTR_CSI .. "4" .. color_value .. "m"
 end
 
 function color.resetbg()
 	if color.disabled then
 		return ''
 	end
 	return color.escapebg(color.DEFAULT)
 end
 
 function color.escape(fg_color, bg_color, attribute)
 	if color.disabled then
 		return ""
 	end
 	if attribute == nil then
 		attribute = ""
 	else
 		attribute = attribute .. ";"
 	end
 	return core.KEYSTR_CSI .. attribute ..
 	    "3" .. fg_color .. ";4" .. bg_color .. "m"
 end
 
 function color.default()
 	if color.disabled then
 		return ""
 	end
 	return color.escape(color.DEFAULT, color.DEFAULT)
 end
 
 function color.highlight(str)
 	if color.disabled then
 		return str
 	end
 	-- We need to reset attributes as well as color scheme here, just in
 	-- case the terminal defaults don't match what we're expecting.
 	return core.KEYSTR_CSI .. "1m" .. str .. core.KEYSTR_CSI .. "22m"
 end
 
 recalcDisabled()
 hook.register("config.loaded", recalcDisabled)
 
 return color
diff --git a/stand/lua/core.lua b/stand/lua/core.lua
index 441a032dc51a..9d331bc0ad3a 100644
--- a/stand/lua/core.lua
+++ b/stand/lua/core.lua
@@ -1,490 +1,503 @@
 --
 -- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
 --
 -- Copyright (c) 2015 Pedro Souza <pedrosouza@freebsd.org>
 -- Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
 -- All rights reserved.
 --
 -- Redistribution and use in source and binary forms, with or without
 -- modification, are permitted provided that the following conditions
 -- are met:
 -- 1. Redistributions of source code must retain the above copyright
 --    notice, this list of conditions and the following disclaimer.
 -- 2. Redistributions in binary form must reproduce the above copyright
 --    notice, this list of conditions and the following disclaimer in the
 --    documentation and/or other materials provided with the distribution.
 --
 -- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 -- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 -- ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 -- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 -- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 -- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 -- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 -- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 -- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 -- SUCH DAMAGE.
 --
 -- $FreeBSD$
 --
 
 local config = require("config")
 local hook = require("hook")
 
 local core = {}
 
 local default_safe_mode = false
 local default_single_user = false
 local default_verbose = false
 
 local bootenv_list = "bootenvs"
 
 local function composeLoaderCmd(cmd_name, argstr)
 	if argstr ~= nil then
 		cmd_name = cmd_name .. " " .. argstr
 	end
 	return cmd_name
 end
 
 local function recordDefaults()
 	-- On i386, hint.acpi.0.rsdp will be set before we're loaded. On !i386,
 	-- it will generally be set upon execution of the kernel. Because of
 	-- this, we can't (or don't really want to) detect/disable ACPI on !i386
 	-- reliably. Just set it enabled if we detect it and leave well enough
 	-- alone if we don't.
 	local boot_acpi = core.isSystem386() and core.getACPIPresent(false)
 	local boot_single = loader.getenv("boot_single") or "no"
 	local boot_verbose = loader.getenv("boot_verbose") or "no"
 	default_single_user = boot_single:lower() ~= "no"
 	default_verbose = boot_verbose:lower() ~= "no"
 
 	if boot_acpi then
 		core.setACPI(true)
 	end
 	core.setSingleUser(default_single_user)
 	core.setVerbose(default_verbose)
 end
 
 
 -- Globals
 -- try_include will return the loaded module on success, or false and the error
 -- message on failure.
 function try_include(module)
 	if module:sub(1, 1) ~= "/" then
 		local lua_path = loader.lua_path
 		-- XXX Temporary compat shim; this should be removed once the
 		-- loader.lua_path export has sufficiently spread.
 		if lua_path == nil then
 			lua_path = "/boot/lua"
 		end
 		module = lua_path .. "/" .. module
 		-- We only attempt to append an extension if an absolute path
 		-- wasn't specified.  This assumes that the caller either wants
 		-- to treat this like it would require() and specify just the
 		-- base filename, or they know what they're doing as they've
 		-- specified an absolute path and we shouldn't impede.
 		if module:match(".lua$") == nil then
 			module = module .. ".lua"
 		end
 	end
 	if lfs.attributes(module, "mode") ~= "file" then
 		return
 	end
 
 	return dofile(module)
 end
 
 -- Module exports
 -- Commonly appearing constants
 core.KEY_BACKSPACE	= 8
 core.KEY_ENTER		= 13
 core.KEY_DELETE		= 127
 
 -- Note that this is a decimal representation, despite the leading 0 that in
 -- other contexts (outside of Lua) may mean 'octal'
 core.KEYSTR_ESCAPE	= "\027"
 core.KEYSTR_CSI		= core.KEYSTR_ESCAPE .. "["
 core.KEYSTR_RESET	= core.KEYSTR_ESCAPE .. "c"
 
 core.MENU_RETURN	= "return"
 core.MENU_ENTRY		= "entry"
 core.MENU_SEPARATOR	= "separator"
 core.MENU_SUBMENU	= "submenu"
 core.MENU_CAROUSEL_ENTRY	= "carousel_entry"
 
 function core.setVerbose(verbose)
 	if verbose == nil then
 		verbose = not core.verbose
 	end
 
 	if verbose then
 		loader.setenv("boot_verbose", "YES")
 	else
 		loader.unsetenv("boot_verbose")
 	end
 	core.verbose = verbose
 end
 
 function core.setSingleUser(single_user)
 	if single_user == nil then
 		single_user = not core.su
 	end
 
 	if single_user then
 		loader.setenv("boot_single", "YES")
 	else
 		loader.unsetenv("boot_single")
 	end
 	core.su = single_user
 end
 
 function core.getACPIPresent(checking_system_defaults)
 	local c = loader.getenv("hint.acpi.0.rsdp")
 
 	if c ~= nil then
 		if checking_system_defaults then
 			return true
 		end
 		-- Otherwise, respect disabled if it's set
 		c = loader.getenv("hint.acpi.0.disabled")
 		return c == nil or tonumber(c) ~= 1
 	end
 	return false
 end
 
 function core.setACPI(acpi)
 	if acpi == nil then
 		acpi = not core.acpi
 	end
 
 	if acpi then
 		loader.setenv("acpi_load", "YES")
 		loader.setenv("hint.acpi.0.disabled", "0")
 		loader.unsetenv("loader.acpi_disabled_by_user")
 	else
 		loader.unsetenv("acpi_load")
 		loader.setenv("hint.acpi.0.disabled", "1")
 		loader.setenv("loader.acpi_disabled_by_user", "1")
 	end
 	core.acpi = acpi
 end
 
 function core.setSafeMode(safe_mode)
 	if safe_mode == nil then
 		safe_mode = not core.sm
 	end
 	if safe_mode then
 		loader.setenv("kern.smp.disabled", "1")
 		loader.setenv("hw.ata.ata_dma", "0")
 		loader.setenv("hw.ata.atapi_dma", "0")
 		loader.setenv("hw.ata.wc", "0")
 		loader.setenv("hw.eisa_slots", "0")
 		loader.setenv("kern.eventtimer.periodic", "1")
 		loader.setenv("kern.geom.part.check_integrity", "0")
 	else
 		loader.unsetenv("kern.smp.disabled")
 		loader.unsetenv("hw.ata.ata_dma")
 		loader.unsetenv("hw.ata.atapi_dma")
 		loader.unsetenv("hw.ata.wc")
 		loader.unsetenv("hw.eisa_slots")
 		loader.unsetenv("kern.eventtimer.periodic")
 		loader.unsetenv("kern.geom.part.check_integrity")
 	end
 	core.sm = safe_mode
 end
 
 function core.clearCachedKernels()
 	-- Clear the kernel cache on config changes, autodetect might have
 	-- changed or if we've switched boot environments then we could have
 	-- a new kernel set.
 	core.cached_kernels = nil
 end
 
 function core.kernelList()
 	if core.cached_kernels ~= nil then
 		return core.cached_kernels
 	end
 
 	local k = loader.getenv("kernel")
 	local v = loader.getenv("kernels")
 	local autodetect = loader.getenv("kernels_autodetect") or ""
 
 	local kernels = {}
 	local unique = {}
 	local i = 0
 	if k ~= nil then
 		i = i + 1
 		kernels[i] = k
 		unique[k] = true
 	end
 
 	if v ~= nil then
 		for n in v:gmatch("([^;, ]+)[;, ]?") do
 			if unique[n] == nil then
 				i = i + 1
 				kernels[i] = n
 				unique[n] = true
 			end
 		end
 	end
 
 	-- Base whether we autodetect kernels or not on a loader.conf(5)
 	-- setting, kernels_autodetect. If it's set to 'yes', we'll add
 	-- any kernels we detect based on the criteria described.
 	if autodetect:lower() ~= "yes" then
 		core.cached_kernels = kernels
 		return core.cached_kernels
 	end
 
 	-- Automatically detect other bootable kernel directories using a
 	-- heuristic.  Any directory in /boot that contains an ordinary file
 	-- named "kernel" is considered eligible.
 	for file in lfs.dir("/boot") do
 		local fname = "/boot/" .. file
 
 		if file == "." or file == ".." then
 			goto continue
 		end
 
 		if lfs.attributes(fname, "mode") ~= "directory" then
 			goto continue
 		end
 
 		if lfs.attributes(fname .. "/kernel", "mode") ~= "file" then
 			goto continue
 		end
 
 		if unique[file] == nil then
 			i = i + 1
 			kernels[i] = file
 			unique[file] = true
 		end
 
 		::continue::
 	end
 	core.cached_kernels = kernels
 	return core.cached_kernels
 end
 
 function core.bootenvDefault()
 	return loader.getenv("zfs_be_active")
 end
 
 function core.bootenvList()
 	local bootenv_count = tonumber(loader.getenv(bootenv_list .. "_count"))
 	local bootenvs = {}
 	local curenv
 	local envcount = 0
 	local unique = {}
 
 	if bootenv_count == nil or bootenv_count <= 0 then
 		return bootenvs
 	end
 
 	-- Currently selected bootenv is always first/default
 	-- On the rewinded list the bootenv may not exists
 	if core.isRewinded() then
 		curenv = core.bootenvDefaultRewinded()
 	else
 		curenv = core.bootenvDefault()
 	end
 	if curenv ~= nil then
 		envcount = envcount + 1
 		bootenvs[envcount] = curenv
 		unique[curenv] = true
 	end
 
 	for curenv_idx = 0, bootenv_count - 1 do
 		curenv = loader.getenv(bootenv_list .. "[" .. curenv_idx .. "]")
 		if curenv ~= nil and unique[curenv] == nil then
 			envcount = envcount + 1
 			bootenvs[envcount] = curenv
 			unique[curenv] = true
 		end
 	end
 	return bootenvs
 end
 
 function core.isCheckpointed()
 	return loader.getenv("zpool_checkpoint") ~= nil
 end
 
 function core.bootenvDefaultRewinded()
 	local defname =  "zfs:!" .. string.sub(core.bootenvDefault(), 5)
 	local bootenv_count = tonumber("bootenvs_check_count")
 
 	if bootenv_count == nil or bootenv_count <= 0 then
 		return defname
 	end
 
 	for curenv_idx = 0, bootenv_count - 1 do
 		local curenv = loader.getenv("bootenvs_check[" .. curenv_idx .. "]")
 		if curenv == defname then
 			return defname
 		end
 	end
 
 	return loader.getenv("bootenvs_check[0]")
 end
 
 function core.isRewinded()
 	return bootenv_list == "bootenvs_check"
 end
 
 function core.changeRewindCheckpoint()
 	if core.isRewinded() then
 		bootenv_list = "bootenvs"
 	else
 		bootenv_list = "bootenvs_check"
 	end
 end
 
 function core.setDefaults()
 	core.setACPI(core.getACPIPresent(true))
 	core.setSafeMode(default_safe_mode)
 	core.setSingleUser(default_single_user)
 	core.setVerbose(default_verbose)
 end
 
 function core.autoboot(argstr)
 	-- loadelf() only if we've not already loaded a kernel
 	if loader.getenv("kernelname") == nil then
 		config.loadelf()
 	end
 	loader.perform(composeLoaderCmd("autoboot", argstr))
 end
 
 function core.boot(argstr)
 	-- loadelf() only if we've not already loaded a kernel
 	if loader.getenv("kernelname") == nil then
 		config.loadelf()
 	end
 	loader.perform(composeLoaderCmd("boot", argstr))
 end
 
 function core.isSingleUserBoot()
 	local single_user = loader.getenv("boot_single")
 	return single_user ~= nil and single_user:lower() == "yes"
 end
 
 function core.isUEFIBoot()
 	local efiver = loader.getenv("efi-version")
 
 	return efiver ~= nil
 end
 
 function core.isZFSBoot()
 	local c = loader.getenv("currdev")
 
 	if c ~= nil then
 		return c:match("^zfs:") ~= nil
 	end
 	return false
 end
 
+function core.isFramebufferConsole()
+	local c = loader.getenv("console")
+	if c ~= nil then
+		if c:find("efi") == nil and c:find("vidconsole") == nil then
+			return false
+		end
+		if loader.getenv("screen.depth") ~= nil then
+			return true
+		end
+	end
+	return false
+end
+
 function core.isSerialConsole()
 	local c = loader.getenv("console")
 	if c ~= nil then
 		if c:find("comconsole") ~= nil then
 			return true
 		end
 	end
 	return false
 end
 
 function core.isSerialBoot()
 	local s = loader.getenv("boot_serial")
 	if s ~= nil then
 		return true
 	end
 
 	local m = loader.getenv("boot_multicons")
 	if m ~= nil then
 		return true
 	end
 	return false
 end
 
 function core.isSystem386()
 	return loader.machine_arch == "i386"
 end
 
 -- Is the menu skipped in the environment in which we've booted?
 function core.isMenuSkipped()
 	return string.lower(loader.getenv("beastie_disable") or "") == "yes"
 end
 
 -- This may be a better candidate for a 'utility' module.
 function core.deepCopyTable(tbl)
 	local new_tbl = {}
 	for k, v in pairs(tbl) do
 		if type(v) == "table" then
 			new_tbl[k] = core.deepCopyTable(v)
 		else
 			new_tbl[k] = v
 		end
 	end
 	return new_tbl
 end
 
 -- XXX This should go away if we get the table lib into shape for importing.
 -- As of now, it requires some 'os' functions, so we'll implement this in lua
 -- for our uses
 function core.popFrontTable(tbl)
 	-- Shouldn't reasonably happen
 	if #tbl == 0 then
 		return nil, nil
 	elseif #tbl == 1 then
 		return tbl[1], {}
 	end
 
 	local first_value = tbl[1]
 	local new_tbl = {}
 	-- This is not a cheap operation
 	for k, v in ipairs(tbl) do
 		if k > 1 then
 			new_tbl[k - 1] = v
 		end
 	end
 
 	return first_value, new_tbl
 end
 
 function core.getConsoleName()
 	if loader.getenv("boot_multicons") ~= nil then
 		if loader.getenv("boot_serial") ~= nil then
 			return "Dual (Serial primary)"
 		else
 			return "Dual (Video primary)"
 		end
 	else
 		if loader.getenv("boot_serial") ~= nil then
 			return "Serial"
 		else
 			return "Video"
 		end
 	end
 end
 
 function core.nextConsoleChoice()
 	if loader.getenv("boot_multicons") ~= nil then
 		if loader.getenv("boot_serial") ~= nil then
 			loader.unsetenv("boot_serial")
 		else
 			loader.unsetenv("boot_multicons")
 			loader.setenv("boot_serial", "YES")
 		end
 	else
 		if loader.getenv("boot_serial") ~= nil then
 			loader.unsetenv("boot_serial")
 		else
 			loader.setenv("boot_multicons", "YES")
 			loader.setenv("boot_serial", "YES")
 		end
 	end
 end
 
 recordDefaults()
 hook.register("config.reloaded", core.clearCachedKernels)
 return core
diff --git a/stand/lua/drawer.lua b/stand/lua/drawer.lua
index 32483f1b424e..3ace3884ff8a 100644
--- a/stand/lua/drawer.lua
+++ b/stand/lua/drawer.lua
@@ -1,467 +1,518 @@
 --
 -- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
 --
 -- Copyright (c) 2015 Pedro Souza <pedrosouza@freebsd.org>
 -- Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
 -- All rights reserved.
 --
 -- Redistribution and use in source and binary forms, with or without
 -- modification, are permitted provided that the following conditions
 -- are met:
 -- 1. Redistributions of source code must retain the above copyright
 --    notice, this list of conditions and the following disclaimer.
 -- 2. Redistributions in binary form must reproduce the above copyright
 --    notice, this list of conditions and the following disclaimer in the
 --    documentation and/or other materials provided with the distribution.
 --
 -- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 -- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 -- ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 -- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 -- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 -- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 -- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 -- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 -- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 -- SUCH DAMAGE.
 --
 -- $FreeBSD$
 --
 
 local color = require("color")
 local config = require("config")
 local core = require("core")
 local screen = require("screen")
 
 local drawer = {}
 
 local fbsd_brand
 local none
 
 local menu_name_handlers
 local branddefs
 local logodefs
 local brand_position
 local logo_position
 local menu_position
 local frame_size
 local default_shift
 local shift
 
 local function menuEntryName(drawing_menu, entry)
 	local name_handler = menu_name_handlers[entry.entry_type]
 
 	if name_handler ~= nil then
 		return name_handler(drawing_menu, entry)
 	end
 	if type(entry.name) == "function" then
 		return entry.name()
 	end
 	return entry.name
 end
 
 local function processFile(gfxname)
 	if gfxname == nil then
 		return false, "Missing filename"
 	end
 
 	local ret = try_include('gfx-' .. gfxname)
 	if ret == nil then
 		return false, "Failed to include gfx-" .. gfxname
 	end
 
 	-- Legacy format
 	if type(ret) ~= "table" then
 		return true
 	end
 
 	for gfxtype, def in pairs(ret) do
 		if gfxtype == "brand" then
 			drawer.addBrand(gfxname, def)
 		elseif gfxtype == "logo" then
 			drawer.addLogo(gfxname, def)
 		else
 			return false, "Unknown graphics type '" .. gfxtype ..
 			    "'"
 		end
 	end
 
 	return true
 end
 
 local function getBranddef(brand)
 	if brand == nil then
 		return nil
 	end
 	-- Look it up
 	local branddef = branddefs[brand]
 
 	-- Try to pull it in
 	if branddef == nil then
 		local res, err = processFile(brand)
 		if not res then
 			-- This fallback should go away after FreeBSD 13.
 			try_include('brand-' .. brand)
 			-- If the fallback also failed, print whatever error
 			-- we encountered in the original processing.
 			if branddefs[brand] == nil then
 				print(err)
 				return nil
 			end
 		end
 
 		branddef = branddefs[brand]
 	end
 
 	return branddef
 end
 
 local function getLogodef(logo)
 	if logo == nil then
 		return nil
 	end
 	-- Look it up
 	local logodef = logodefs[logo]
 
 	-- Try to pull it in
 	if logodef == nil then
 		local res, err = processFile(logo)
 		if not res then
 			-- This fallback should go away after FreeBSD 13.
 			try_include('logo-' .. logo)
 			-- If the fallback also failed, print whatever error
 			-- we encountered in the original processing.
 			if logodefs[logo] == nil then
 				print(err)
 				return nil
 			end
 		end
 
 		logodef = logodefs[logo]
 	end
 
 	return logodef
 end
 
 local function draw(x, y, logo)
 	for i = 1, #logo do
 		screen.setcursor(x, y + i - 1)
 		printc(logo[i])
 	end
 end
 
 local function drawmenu(menudef)
 	local x = menu_position.x
 	local y = menu_position.y
 
 	x = x + shift.x
 	y = y + shift.y
 
 	-- print the menu and build the alias table
 	local alias_table = {}
 	local entry_num = 0
 	local menu_entries = menudef.entries
 	local effective_line_num = 0
 	if type(menu_entries) == "function" then
 		menu_entries = menu_entries()
 	end
 	for _, e in ipairs(menu_entries) do
 		-- Allow menu items to be conditionally visible by specifying
 		-- a visible function.
 		if e.visible ~= nil and not e.visible() then
 			goto continue
 		end
 		effective_line_num = effective_line_num + 1
 		if e.entry_type ~= core.MENU_SEPARATOR then
 			entry_num = entry_num + 1
 			screen.setcursor(x, y + effective_line_num)
 
 			printc(entry_num .. ". " .. menuEntryName(menudef, e))
 
 			-- fill the alias table
 			alias_table[tostring(entry_num)] = e
 			if e.alias ~= nil then
 				for _, a in ipairs(e.alias) do
 					alias_table[a] = e
 				end
 			end
 		else
 			screen.setcursor(x, y + effective_line_num)
 			printc(menuEntryName(menudef, e))
 		end
 		::continue::
 	end
 	return alias_table
 end
 
 local function defaultframe()
 	if core.isSerialConsole() then
 		return "ascii"
 	end
 	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
 	local h = frame_size.h
 
 	local framestyle = loader.getenv("loader_menu_frame") or defaultframe()
 	local framespec = drawer.frame_styles[framestyle]
 	-- 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
 	local vl = framespec.vertical
 
 	local tl = framespec.top_left
 	local bl = framespec.bottom_left
 	local tr = framespec.top_right
 	local br = framespec.bottom_right
 
 	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)
 	screen.setcursor(x + w, y + h); printc(br)
 
 	screen.setcursor(x + 1, y)
 	for _ = 1, w - 1 do
 		printc(hl)
 	end
 
 	screen.setcursor(x + 1, y + h)
 	for _ = 1, w - 1 do
 		printc(hl)
 	end
 
 	for i = 1, h - 1 do
 		screen.setcursor(x, y + i)
 		printc(vl)
 		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
 			-- Just inside the left border on top
 			menu_header_x = x + 1
 		elseif menu_header_align == "right" then
 			-- Just inside the right border on top
 			menu_header_x = x + w - #menu_header
 		end
 	end
 	if menu_header_x == nil then
 		menu_header_x = x + (w / 2) - (#menu_header / 2)
 	end
 	screen.setcursor(menu_header_x, y)
 	printc(menu_header)
 end
 
 local function drawbrand()
 	local x = tonumber(loader.getenv("loader_brand_x")) or
 	    brand_position.x
 	local y = tonumber(loader.getenv("loader_brand_y")) or
 	    brand_position.y
 
 	local branddef = getBranddef(loader.getenv("loader_brand"))
 
 	if branddef == nil then
 		branddef = getBranddef(drawer.default_brand)
 	end
 
 	local graphic = branddef.graphic
 
 	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
 
 local function drawlogo()
 	local x = tonumber(loader.getenv("loader_logo_x")) or
 	    logo_position.x
 	local y = tonumber(loader.getenv("loader_logo_y")) or
 	    logo_position.y
 
 	local logo = loader.getenv("loader_logo")
 	local colored = color.isEnabled()
 
 	local logodef = getLogodef(logo)
 
 	if logodef == nil or logodef.graphic == nil or
 	    (not colored and logodef.requires_color) then
 		-- Choose a sensible default
 		if colored then
 			logodef = getLogodef(drawer.default_color_logodef)
 		else
 			logodef = getLogodef(drawer.default_bw_logodef)
 		end
 
 		-- Something has gone terribly wrong.
 		if logodef == nil then
 			logodef = getLogodef(drawer.default_fallback_logodef)
 		end
 	end
 
 	if logodef ~= nil and logodef.graphic == none then
 		shift = logodef.shift
 	else
 		shift = default_shift
 	end
 
 	x = x + shift.x
 	y = y + shift.y
 
 	if logodef ~= nil and logodef.shift ~= nil then
 		x = x + logodef.shift.x
 		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 = {
 "  ______               ____   _____ _____  ",
 " |  ____|             |  _ \\ / ____|  __ \\ ",
 " | |___ _ __ ___  ___ | |_) | (___ | |  | |",
 " |  ___| '__/ _ \\/ _ \\|  _ < \\___ \\| |  | |",
 " | |   | | |  __/  __/| |_) |____) | |__| |",
 " | |   | | |    |    ||     |      |      |",
 " |_|   |_|  \\___|\\___||____/|_____/|_____/ "
 }
 none = {""}
 
 menu_name_handlers = {
 	-- Menu name handlers should take the menu being drawn and entry being
 	-- drawn as parameters, and return the name of the item.
 	-- This is designed so that everything, including menu separators, may
 	-- have their names derived differently. The default action for entry
 	-- types not specified here is to use entry.name directly.
 	[core.MENU_SEPARATOR] = function(_, entry)
 		if entry.name ~= nil then
 			if type(entry.name) == "function" then
 				return entry.name()
 			end
 			return entry.name
 		end
 		return ""
 	end,
 	[core.MENU_CAROUSEL_ENTRY] = function(_, entry)
 		local carid = entry.carousel_id
 		local caridx = config.getCarouselIndex(carid)
 		local choices = entry.items
 		if type(choices) == "function" then
 			choices = choices()
 		end
 		if #choices < caridx then
 			caridx = 1
 		end
 		return entry.name(caridx, choices[caridx], choices)
 	end,
 }
 
 branddefs = {
 	-- Indexed by valid values for loader_brand in loader.conf(5). Valid
 	-- keys are: graphic (table depicting graphic)
 	["fbsd"] = {
 		graphic = fbsd_brand,
+		image = "/boot/images/freebsd-brand-rev.png",
 	},
 	["none"] = {
 		graphic = none,
 	},
 }
 
 logodefs = {
 	-- Indexed by valid values for loader_logo in loader.conf(5). Valid keys
 	-- are: requires_color (boolean), graphic (table depicting graphic), and
 	-- shift (table containing x and y).
 	["tribute"] = {
 		graphic = fbsd_brand,
 	},
 	["tributebw"] = {
 		graphic = fbsd_brand,
 	},
 	["none"] = {
 		graphic = none,
 		shift = {x = 17, y = 0},
 	},
 }
 
 brand_position = {x = 2, y = 1}
 logo_position = {x = 46, y = 4}
 menu_position = {x = 5, y = 10}
 frame_size = {w = 42, h = 13}
 default_shift = {x = 0, y = 0}
 shift = default_shift
 
 -- Module exports
 drawer.default_brand = 'fbsd'
 drawer.default_color_logodef = 'orb'
 drawer.default_bw_logodef = 'orbbw'
 -- For when things go terribly wrong; this def should be present here in the
 -- drawer module in case it's a filesystem issue.
 drawer.default_fallback_logodef = 'none'
 
 -- These should go away after FreeBSD 13; only available for backwards
 -- compatibility with old logo- files.
 function drawer.addBrand(name, def)
 	branddefs[name] = def
 end
 
 function drawer.addLogo(name, def)
 	logodefs[name] = def
 end
 
 drawer.frame_styles = {
 	-- Indexed by valid values for loader_menu_frame in loader.conf(5).
 	-- All of the keys appearing below must be set for any menu frame style
 	-- added to drawer.frame_styles.
 	["ascii"] = {
 		horizontal	= "-",
 		vertical	= "|",
 		top_left	= "+",
 		bottom_left	= "+",
 		top_right	= "+",
 		bottom_right	= "+",
 	},
 	["single"] = {
 		horizontal	= "\xE2\x94\x80",
 		vertical	= "\xE2\x94\x82",
 		top_left	= "\xE2\x94\x8C",
 		bottom_left	= "\xE2\x94\x94",
 		top_right	= "\xE2\x94\x90",
 		bottom_right	= "\xE2\x94\x98",
 	},
 	["double"] = {
 		horizontal	= "\xE2\x95\x90",
 		vertical	= "\xE2\x95\x91",
 		top_left	= "\xE2\x95\x94",
 		bottom_left	= "\xE2\x95\x9A",
 		top_right	= "\xE2\x95\x97",
 		bottom_right	= "\xE2\x95\x9D",
 	},
 }
 
 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
 
 return drawer
diff --git a/stand/lua/gfx-orb.lua b/stand/lua/gfx-orb.lua
index 1af8f9f96072..2702618948d6 100644
--- a/stand/lua/gfx-orb.lua
+++ b/stand/lua/gfx-orb.lua
@@ -1,52 +1,54 @@
 --
 -- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
 --
 -- Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
 --
 -- 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$
 --
 
 return {
 	logo = {
 		graphic = {
 		    "  \027[31m```                        \027[31;1m`\027[31m",
 		    " s` `.....---...\027[31;1m....--.```   -/\027[31m",
 		    " +o   .--`         \027[31;1m/y:`      +.\027[31m",
 		    "  yo`:.            \027[31;1m:o      `+-\027[31m",
 		    "   y/               \027[31;1m-/`   -o/\027[31m",
 		    "  .-                  \027[31;1m::/sy+:.\027[31m",
 		    "  /                     \027[31;1m`--  /\027[31m",
 		    " `:                          \027[31;1m:`\027[31m",
 		    " `:                          \027[31;1m:`\027[31m",
 		    "  /                          \027[31;1m/\027[31m",
 		    "  .-                        \027[31;1m-.\027[31m",
 		    "   --                      \027[31;1m-.\027[31m",
 		    "    `:`                  \027[31;1m`:`",
 		    "      \027[31;1m.--             `--.",
 		    "         .---.....----.\027[m",
 		},
 		requires_color = true,
-		shift = {x = 2, y = 4},
+		shift = {x = 2, y = 3},
+		image = "/boot/images/freebsd-logo-rev.png",
+		image_rl = 15
 	}
 }
diff --git a/stand/userboot/userboot/Makefile b/stand/userboot/userboot/Makefile
index f256ac2769e5..49026375328e 100644
--- a/stand/userboot/userboot/Makefile
+++ b/stand/userboot/userboot/Makefile
@@ -1,60 +1,61 @@
 # $FreeBSD$
 
 LOADER_MSDOS_SUPPORT?=	yes
 LOADER_UFS_SUPPORT?=	yes
 LOADER_CD9660_SUPPORT?=	no
 LOADER_EXT2FS_SUPPORT?=	no
 PIC=yes
 
 .include <bsd.init.mk>
 
 SHLIB_NAME=	userboot_${LOADER_INTERP}.so
 STRIP=
 LIBDIR=		/boot
 
 .PATH:		${.CURDIR}/../userboot
 SRCS=		autoload.c
 SRCS+=		bcache.c
 SRCS+=		biossmap.c
 SRCS+=		bootinfo.c
 SRCS+=		bootinfo32.c
 SRCS+=		bootinfo64.c
 SRCS+=		conf.c
 SRCS+=		copy.c
 SRCS+=		devicename.c
 SRCS+=		elf32_freebsd.c
 SRCS+=		elf64_freebsd.c
 SRCS+=		host.c
 SRCS+=		main.c
 SRCS+=		userboot_cons.c
 SRCS+=		userboot_disk.c
 SRCS+=		vers.c
 
 CFLAGS+=	-Wall
 CFLAGS+=	-I${BOOTSRC}/userboot
 CFLAGS.main.c+=	-I${BOOTSRC}/libsa/zfs
 CFLAGS.main.c+=	-I${SYSDIR}/contrib/openzfs/include
 CFLAGS.main.c+=	-I${SYSDIR}/contrib/openzfs/include/os/freebsd/zfs
+CFLAGS.userboot_cons.c+= -I$(SRCTOP)/sys/teken -I${SRCTOP}/contrib/pnglite
 CWARNFLAGS.main.c += -Wno-implicit-function-declaration
 
 LDFLAGS+=	-nostdlib -Wl,-Bsymbolic
 
 NEWVERSWHAT=	"User boot ${LOADER_INTERP}" ${MACHINE_CPUARCH}
 VERSION_FILE=	${.CURDIR}/../userboot/version
 
 .if ${LOADER_INTERP} == ${LOADER_DEFAULT_INTERP}
 LINKS+=		${BINDIR}/${SHLIB_NAME} ${BINDIR}/userboot.so
 .endif
 
 .if ${MK_LOADER_ZFS} != "no"
 CFLAGS+=	-DUSERBOOT_ZFS_SUPPORT
 HAVE_ZFS=yes
 .endif
 
 # Always add MI sources
 .include	"${BOOTSRC}/loader.mk"
 CFLAGS+=	-I.
 DPADD+=		${LDR_INTERP} ${LIBSA}
 LDADD+=		${LDR_INTERP} ${LIBSA}
 
 .include <bsd.lib.mk>
diff --git a/stand/userboot/userboot/userboot_cons.c b/stand/userboot/userboot/userboot_cons.c
index 6f73ad5633bb..528f9ac40af9 100644
--- a/stand/userboot/userboot/userboot_cons.c
+++ b/stand/userboot/userboot/userboot_cons.c
@@ -1,130 +1,135 @@
 /*-
  * Copyright (c) 2011 Google, Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
 
 #include <sys/cdefs.h>
 __FBSDID("$FreeBSD$");
 
 #include <stand.h>
+#include <sys/font.h>
+#include "gfx_fb.h"
 #include "bootstrap.h"
 #include "libuserboot.h"
 
+font_list_t fonts = STAILQ_HEAD_INITIALIZER(fonts);
+teken_gfx_t gfx_state = { 0 };
+
 int console;
 
 static struct console *userboot_comconsp;
 
 static void userboot_cons_probe(struct console *cp);
 static int userboot_cons_init(int);
 static void userboot_comcons_probe(struct console *cp);
 static int userboot_comcons_init(int);
 static void userboot_cons_putchar(int);
 static int userboot_cons_getchar(void);
 static int userboot_cons_poll(void);
 
 struct console userboot_console = {
 	"userboot",
 	"userboot",
 	0,
 	userboot_cons_probe,
 	userboot_cons_init,
 	userboot_cons_putchar,
 	userboot_cons_getchar,
 	userboot_cons_poll,
 };
 
 /*
  * Provide a simple alias to allow loader scripts to set the
  * console to comconsole without resulting in an error
  */
 struct console userboot_comconsole = {
 	"comconsole",
 	"comconsole",
 	0,
 	userboot_comcons_probe,
 	userboot_comcons_init,
 	userboot_cons_putchar,
 	userboot_cons_getchar,
 	userboot_cons_poll,
 };
 
 static void
 userboot_cons_probe(struct console *cp)
 {
 
 	cp->c_flags |= (C_PRESENTIN | C_PRESENTOUT);
 }
 
 static int
 userboot_cons_init(int arg)
 {
 
 	return (0);
 }
 
 static void
 userboot_comcons_probe(struct console *cp)
 {
 
 	/*
 	 * Save the console pointer so the comcons_init routine
 	 * can set the C_PRESENT* flags. They are not set
 	 * here to allow the existing userboot console to
 	 * be elected the default.
 	 */
 	userboot_comconsp = cp;
 }
 
 static int
 userboot_comcons_init(int arg)
 {
 
 	/*
 	 * Set the C_PRESENT* flags to allow the comconsole
 	 * to be selected as the active console
 	 */
 	userboot_comconsp->c_flags |= (C_PRESENTIN | C_PRESENTOUT);
 	return (0);
 }
 
 static void
 userboot_cons_putchar(int c)
 {
 
         CALLBACK(putc, c);
 }
 
 static int
 userboot_cons_getchar()
 {
 
 	return (CALLBACK(getc));
 }
 
 static int
 userboot_cons_poll()
 {
 
 	return (CALLBACK(poll));
 }
diff --git a/sys/sys/font.h b/sys/sys/font.h
index f8bf151c5f52..e09b2112959d 100644
--- a/sys/sys/font.h
+++ b/sys/sys/font.h
@@ -1,122 +1,123 @@
 /*-
  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
  *
  * Copyright (c) 2009, 2013 The FreeBSD Foundation
  * All rights reserved.
  *
  * This software was developed by Ed Schouten under sponsorship from the
  * FreeBSD Foundation.
  *
  * Portions of this software were developed by Oleksandr Rybalko
  * under sponsorship from the FreeBSD Foundation.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
  * $FreeBSD$
  */
 
 #ifndef _SYS_FONT_H_
 #define	_SYS_FONT_H_
 
 #include <sys/queue.h>
 
 /*
  * Fonts.
  *
  * Fonts support normal and bold weights, and single and double width glyphs.
  * Mapping tables are used to map Unicode points to glyphs.  They are sorted by
  * code point, and vtfont_lookup() uses this to perform a binary search.  Each
  * font has four mapping tables: two weights times two halves (left/single,
  * right).  When a character is not present in a bold map the glyph from the
  * normal map is used.  When no glyph is available, it uses glyph 0, which is
  * normally equal to U+FFFD.
  */
 
 enum vfnt_map_type {
 	VFNT_MAP_NORMAL = 0,	/* Normal font. */
 	VFNT_MAP_NORMAL_RIGHT,	/* Normal font right hand. */
 	VFNT_MAP_BOLD,		/* Bold font. */
 	VFNT_MAP_BOLD_RIGHT,	/* Bold font right hand. */
 	VFNT_MAPS		/* Number of maps. */
 };
 
 struct font_info {
 	int32_t fi_checksum;
 	uint32_t fi_width;
 	uint32_t fi_height;
 	uint32_t fi_bitmap_size;
 	uint32_t fi_map_count[VFNT_MAPS];
 };
 
 struct vfnt_map {
 	uint32_t	 vfm_src;
 	uint16_t	 vfm_dst;
 	uint16_t	 vfm_len;
 } __packed;
 typedef struct vfnt_map vfnt_map_t;
 
 struct vt_font {
 	vfnt_map_t	*vf_map[VFNT_MAPS];
 	uint8_t		*vf_bytes;
 	uint32_t	 vf_height;
 	uint32_t	 vf_width;
 	uint32_t	 vf_map_count[VFNT_MAPS];
 	uint32_t	 vf_refcount;
 };
 
 typedef struct vt_font_bitmap_data {
         uint32_t	vfbd_width;
         uint32_t	vfbd_height;
         uint32_t	vfbd_compressed_size;
         uint32_t	vfbd_uncompressed_size;
         uint8_t		*vfbd_compressed_data;
         struct vt_font	*vfbd_font;
 } 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 {
 	char			*font_name;
 	FONT_FLAGS		font_flags;
 	vt_font_bitmap_data_t	*font_data;
 	vt_font_bitmap_data_t	*(*font_load)(char *);
 	STAILQ_ENTRY(fontlist)	font_next;
 };
 
 #define	BORDER_PIXELS	10	/* space from screen border */
 typedef STAILQ_HEAD(font_list, fontlist) font_list_t;
 
 #define	FONT_HEADER_MAGIC	"VFNT0002"
 struct font_header {
 	uint8_t		fh_magic[8];
 	uint8_t		fh_width;
 	uint8_t		fh_height;
 	uint16_t	fh_pad;
 	uint32_t	fh_glyph_count;
 	uint32_t	fh_map_count[VFNT_MAPS];
 } __packed;
 
 #endif /* !_SYS_FONT_H_ */
diff --git a/sys/teken/teken.h b/sys/teken/teken.h
index 0a3928a9ffd1..994298b0dbc6 100644
--- a/sys/teken/teken.h
+++ b/sys/teken/teken.h
@@ -1,221 +1,222 @@
 /*-
  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
  *
  * Copyright (c) 2008-2009 Ed Schouten <ed@FreeBSD.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
  * $FreeBSD$
  */
 
 #ifndef _TEKEN_H_
 #define	_TEKEN_H_
 
 #include <sys/types.h>
 
 /*
  * libteken: terminal emulation library.
  *
  * This library converts an UTF-8 stream of bytes to terminal drawing
  * commands.
  */
 
 typedef uint32_t teken_char_t;
 typedef unsigned short teken_unit_t;
 typedef unsigned char teken_format_t;
 #define	TF_BOLD		0x01	/* Bold character. */
 #define	TF_UNDERLINE	0x02	/* Underline character. */
 #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
 #define	TC_GREEN	2
 #define	TC_BROWN	3
 #define	TC_BLUE		4
 #define	TC_MAGENTA	5
 #define	TC_CYAN		6
 #define	TC_WHITE	7
 #define	TC_NCOLORS	8
 #define	TC_LIGHT	8	/* ORed with the others. */
 
 typedef struct {
 	teken_unit_t	tp_row;
 	teken_unit_t	tp_col;
 } teken_pos_t;
 typedef struct {
 	teken_pos_t	tr_begin;
 	teken_pos_t	tr_end;
 } teken_rect_t;
 typedef struct {
 	teken_format_t	ta_format;
 	teken_color_t	ta_fgcolor;
 	teken_color_t	ta_bgcolor;
 } teken_attr_t;
 typedef struct {
 	teken_unit_t	ts_begin;
 	teken_unit_t	ts_end;
 } teken_span_t;
 
 typedef struct __teken teken_t;
 
 typedef void teken_state_t(teken_t *, teken_char_t);
 
 /*
  * Drawing routines supplied by the user.
  */
 
 typedef void tf_bell_t(void *);
 typedef void tf_cursor_t(void *, const teken_pos_t *);
 typedef void tf_putchar_t(void *, const teken_pos_t *, teken_char_t,
     const teken_attr_t *);
 typedef void tf_fill_t(void *, const teken_rect_t *, teken_char_t,
     const teken_attr_t *);
 typedef void tf_copy_t(void *, const teken_rect_t *, const teken_pos_t *);
 typedef void tf_pre_input_t(void *);
 typedef void tf_post_input_t(void *);
 typedef void tf_param_t(void *, int, unsigned int);
 #define	TP_SHOWCURSOR	0
 #define	TP_KEYPADAPP	1
 #define	TP_AUTOREPEAT	2
 #define	TP_SWITCHVT	3
 #define	TP_132COLS	4
 #define	TP_SETBELLPD	5
 #define	TP_SETBELLPD_PITCH(pd)		((pd) >> 16)
 #define	TP_SETBELLPD_DURATION(pd)	((pd) & 0xffff)
 #define	TP_MOUSE	6
 #define	TP_SETBORDER	7
 #define	TP_SETLOCALCURSOR	8
 #define	TP_SETGLOBALCURSOR	9
 typedef void tf_respond_t(void *, const void *, size_t);
 
 typedef struct {
 	tf_bell_t	*tf_bell;
 	tf_cursor_t	*tf_cursor;
 	tf_putchar_t	*tf_putchar;
 	tf_fill_t	*tf_fill;
 	tf_copy_t	*tf_copy;
 	tf_pre_input_t	*tf_pre_input;
 	tf_post_input_t	*tf_post_input;
 	tf_param_t	*tf_param;
 	tf_respond_t	*tf_respond;
 } teken_funcs_t;
 
 typedef teken_char_t teken_scs_t(const teken_t *, teken_char_t);
 
 /*
  * Terminal state.
  */
 
 struct __teken {
 	const teken_funcs_t *t_funcs;
 	void		*t_softc;
 
 	teken_state_t	*t_nextstate;
 	unsigned int	 t_stateflags;
 
 #define T_NUMSIZE	8
 	unsigned int	 t_nums[T_NUMSIZE];
 	unsigned int	 t_curnum;
 
 	teken_pos_t	 t_cursor;
 	teken_attr_t	 t_curattr;
 	teken_pos_t	 t_saved_cursor;
 	teken_attr_t	 t_saved_curattr;
 
 	teken_attr_t	 t_defattr;
 	teken_pos_t	 t_winsize;
 
 	/* For DECSTBM. */
 	teken_span_t	 t_scrollreg;
 	/* For DECOM. */
 	teken_span_t	 t_originreg;
 
 #define	T_NUMCOL	160
 	unsigned int	 t_tabstops[T_NUMCOL / (sizeof(unsigned int) * 8)];
 
 	unsigned int	 t_utf8_left;
 	teken_char_t	 t_utf8_partial;
 	teken_char_t	 t_last;
 
 	unsigned int	 t_curscs;
 	teken_scs_t	*t_saved_curscs;
 	teken_scs_t	*t_scs[2];
 };
 
 /* Initialize teken structure. */
 void	teken_init(teken_t *, const teken_funcs_t *, void *);
 
 /* Deliver character input. */
 void	teken_input(teken_t *, const void *, size_t);
 
 /* Get/set teken attributes. */
 const teken_pos_t *teken_get_cursor(const teken_t *);
 const teken_attr_t *teken_get_curattr(const teken_t *);
 const teken_attr_t *teken_get_defattr(const teken_t *);
 void	teken_get_defattr_cons25(const teken_t *, int *, int *);
 const teken_pos_t *teken_get_winsize(const teken_t *);
 void	teken_set_cursor(teken_t *, const teken_pos_t *);
 void	teken_set_curattr(teken_t *, const teken_attr_t *);
 void	teken_set_defattr(teken_t *, const teken_attr_t *);
 void	teken_set_winsize(teken_t *, const teken_pos_t *);
 void	teken_set_winsize_noreset(teken_t *, const teken_pos_t *);
 
 /* Key input escape sequences. */
 #define	TKEY_UP		0x00
 #define	TKEY_DOWN	0x01
 #define	TKEY_LEFT	0x02
 #define	TKEY_RIGHT	0x03
 
 #define	TKEY_HOME	0x04
 #define	TKEY_END	0x05
 #define	TKEY_INSERT	0x06
 #define	TKEY_DELETE	0x07
 #define	TKEY_PAGE_UP	0x08
 #define	TKEY_PAGE_DOWN	0x09
 
 #define	TKEY_F1		0x0a
 #define	TKEY_F2		0x0b
 #define	TKEY_F3		0x0c
 #define	TKEY_F4		0x0d
 #define	TKEY_F5		0x0e
 #define	TKEY_F6		0x0f
 #define	TKEY_F7		0x10
 #define	TKEY_F8		0x11
 #define	TKEY_F9		0x12
 #define	TKEY_F10	0x13
 #define	TKEY_F11	0x14
 #define	TKEY_F12	0x15
 const char *teken_get_sequence(const teken_t *, unsigned int);
 
 /* Legacy features. */
 void	teken_set_8bit(teken_t *);
 void	teken_set_cons25(teken_t *);
 void	teken_set_cons25keys(teken_t *);
 
 /* Color conversion. */
 teken_color_t teken_256to16(teken_color_t);
 teken_color_t teken_256to8(teken_color_t);
 
 #endif /* !_TEKEN_H_ */